Yuu

最近有迁移个人存储内容到DSM的需求,本来服务器上使用的是CloudReve。
其文件夹结构和文件名都与上传时候的版本有诸多差异。
写了这个小工具来帮助解决,敬请使用。

#!/bin/bash

CLEAN_EMPTY=0
CLEAN_THUMB=0
CLEAN_FILENAME=0

while [ "$#" -gt 0 ]; do
    case $1 in
        --clsempty) CLEAN_EMPTY=1 ;;
        --clsthumb) CLEAN_THUMB=1 ;;
        --clsfilename) CLEAN_FILENAME=1 ;;
        *) break ;;
    esac
    shift
done

if [ "$#" -ne 1 ]; then
    echo "Cloudreve Cleaner 1.0"
    echo "Usage: ./clscrve.sh [--clsempty] [--clsthumb] [--clsfilename] <directory>"
    exit 1
fi

DIR=$1

if [ ! -d "$DIR" ]; then
    echo "Directory not found: $DIR"
    exit 1
fi

if [ "$CLEAN_FILENAME" -eq 1 ]; then
    echo "Cleaning filenames..."
    find "$DIR" -type f | while read -r file; do
        dir=$(dirname "$file")
        base=$(basename "$file")
        originname=$(echo "$base" | awk -F'_' '{ $1=$2=""; sub(/^  /,""); print }')
        if [ "$base" == "$originname" ]; then
            continue
        fi
        mv -n "$file" "$dir/$originname"
        echo "${base} -> ${originname}"
    done
fi

if [ "$CLEAN_EMPTY" -eq 1 ]; then
    echo "Cleaning empty directories..."
    find "$DIR" -type d -empty -delete
fi

if [ "$CLEAN_THUMB" -eq 1 ]; then
    echo "Cleaning thumbnail files..."
    find "$DIR" -type f -name '*. thumb' -delete
fi

echo "SUCCESS IN $(date '+%Y-%m-%d %H:%M:%S')"

前言

朋友的网站DemoDB近来被刷评分的问题所困扰,导致其排行榜内容失真。在一天的无聊讨论之中,我突然想到可以基于网站本身的特色来制作一个特别的验证码——音高验证码,即符合了网站本身的主题,又达到了防护的效果,无障碍的标准也得到了满足。

一般原理

利用了硬编码的频率值,使得可以使用JavaScript的AudioContext来基于随机Challenge产生音频并且保存Challenge的值,而后基于Challenge值使用HTML Canvas图形来绘制五线谱和音符,再基于前面保存的Challenge与前台用户输入的Challenge作比较来判断用户状态。

测试样本

可在线体验
Tone CAPTCHA Demo
ToneCAPTCHA_Demo

采用实绩

DemoDB的示范曲评分功能

版本更新

1.5
Jan.05-2024
添加ADSR包络支持以优化听感
1.4
Jan.04-2024
合入来自dgy18787的音符显示错误修复补丁
修改调试日志展示
添加标准音播放功能
1.3
Dec.17-2023
优化Demo界面UI
1.2
Dec.10-2023
添加加线功能
添加简谱输入转换器功能
修复音符漂移问题
1.1
Dec.09-2023
首次发布



前言

最近在一个Discord群里跟一群大佬们折腾老的日系传统平台手机,有群友发现SH-03E这个MOAP(Symbian)设备使用的是eMMC,并且成功进行了Dump,历经一些研究,我和另一位群友一起研究了ROFS相关内容,成功解压了它的Z分区,Z分区中很多图符资源都是使用MBM(MultiBitmap/多重位图)格式打包并压缩的,需要使用塞班SDK里面的bmconv小工具来解压,但是其使用逻辑很奇怪,所以这次尝试写了一个图形界面。又由于群内很多人使用的是Linux,所以这个小软件支持Windows/Linux双平台使用。

注意

请使用ARCHIVE.ORG取得塞班SDK,安装之后在<安装目录>\epoc32\bin下找到bmconv的Windows和Linux可执行文件

截图

Windows下
MBME_Windows
Linux下
MBME_Linux

代码

import tkinter as tk
from tkinter import filedialog, messagebox, Toplevel, Text, Scrollbar
from tkinter.ttk import Progressbar
from subprocess import Popen, PIPE
from PIL import Image, ImageTk
import os
import re


class mbmExtractor:
    def __init__(self, root):
        self.root = root
        self.uiInit()
        self.mbmFile = ""
        self.currentImageIndex = 0
        self.bmpFiles = []
        self.tempDir = "temp"
        if not os.path.exists(self.tempDir):
            os.makedirs(self.tempDir)

    def uiInit(self):
        self.root.title("MultiBitMap Extractor")
        self.root.geometry('800x600')

        self.previewAreaPanel = tk.Label(self.root, text="Preview Area", relief=tk.SUNKEN)
        self.previewAreaPanel.pack(side=tk.TOP, fill=tk.BOTH, expand=True)

        navigationFrame = tk.Frame(self.root)
        navigationFrame.pack(side=tk.TOP, fill=tk.X)

        self.counterLabel = tk.Label(navigationFrame, text="No Bitmaps")
        self.counterLabel.pack(side=tk.LEFT, padx=5)

        self.prevButton = tk.Button(navigationFrame, text="Prev", command=self.prevImage)
        self.prevButton.pack(side=tk.LEFT, padx=5)

        self.nextButton = tk.Button(navigationFrame, text="Next", command=self.nextImage)
        self.nextButton.pack(side=tk.LEFT, padx=5)

        actionFrame = tk.Frame(self.root)
        actionFrame.pack(side=tk.BOTTOM, fill=tk.X)

        self.openButton = tk.Button(actionFrame, text="Open", command=self.askOpenFile)
        self.openButton.pack(side=tk.LEFT, padx=5)

        self.extractButton = tk.Button(actionFrame, text="Extract", state='disabled', command=self.extractMbm)
        self.extractButton.pack(side=tk.LEFT, padx=5)

        self.debugButton = tk.Button(actionFrame, text="Debug", command=self.debugInfoForm)
        self.debugButton.pack(side=tk.LEFT, padx=5)

        self.progressBar = Progressbar(actionFrame, orient=tk.HORIZONTAL, length=100, mode='determinate')
        self.progressBar.pack(side=tk.LEFT, padx=5, fill=tk.X, expand=True)

    def askOpenFile(self):
        self.mbmFile = filedialog.askopenfilename(
            title="Choose MBM file",
            filetypes=(("MultiBitMap File", "*.mbm"), ("All Files", "*.*"))
        )
        if self.mbmFile:
            self.extractButton['state'] = 'normal'
            mbmFilename = os.path.basename(self.mbmFile)
            self.root.title(f"MultiBitMap Extractor - {mbmFilename}")

    def extractMbm(self):
        if not self.mbmFile:
            messagebox.showerror("ERROR", "No MBM file selected.")
            return

        bitmapCount = self.counterBitmap()
        if bitmapCount > 0:
            bmpArgs = " ".join(f'"{os.path.join(self.tempDir, f"{i}.bmp")}"' for i in range(1, bitmapCount + 1))
            for i in range(1, bitmapCount + 1):
                self.bmconvAdapter(f'/u "{self.mbmFile}" {bmpArgs}')
                self.progressBar['value'] = (i / bitmapCount) * 100
                self.root.update_idletasks()
            self.loadBitmap()
            self.displayBitmap(self.currentImageIndex)
            self.progressBar['value'] = 0
        else:
            messagebox.showinfo("INFO", "No bitmaps found in the MBM file.")

    def counterBitmap(self):
        output = self.bmconvAdapter(f'/v "{self.mbmFile}"')
        match = re.search(r'containing (\d+) bitmaps', output)
        return int(match.group(1)) if match else 0

    def loadBitmap(self):
        self.bmpFiles = self.sortNumber(os.listdir(self.tempDir))
        self.currentImageIndex = 0
        self.updateNavigationButtonStatus()
        self.updateCounterLabel()

    @staticmethod
    def sortNumber(data):
        convert = lambda text: int(text) if text.isdigit() else text.lower()
        alphanumKey = lambda key: [convert(c) for c in re.split('([0-9]+)', key)]
        return sorted(data, key=alphanumKey)

    def displayBitmap(self, index):
        if 0 <= index < len(self.bmpFiles):
            imgPath = os.path.join(self.tempDir, self.bmpFiles[index])
            try:
                img = Image.open(imgPath)
                imgtk = ImageTk.PhotoImage(image=img)
                self.previewAreaPanel.config(image=imgtk)
                self.previewAreaPanel.image = imgtk
            except IOError as e:
                messagebox.showerror("ERROR", f"Can't open bitmap file {imgPath}:\n{e}")

    def prevImage(self):
        if self.currentImageIndex > 0:
            self.currentImageIndex -= 1
            self.displayBitmap(self.currentImageIndex)
            self.updateNavigationButtonStatus()
            self.updateCounterLabel()

    def nextImage(self):
        if self.currentImageIndex < len(self.bmpFiles) - 1:
            self.currentImageIndex += 1
            self.displayBitmap(self.currentImageIndex)
            self.updateNavigationButtonStatus()
            self.updateCounterLabel()

    def updateCounterLabel(self):
        text = f"{self.currentImageIndex + 1}/{len(self.bmpFiles)}"
        self.counterLabel.config(text=text)

    def updateNavigationButtonStatus(self):
        self.prevButton['state'] = 'normal' if self.currentImageIndex > 0 else 'disabled'
        self.nextButton['state'] = 'normal' if self.currentImageIndex < len(self.bmpFiles) - 1 else 'disabled'

    def debugInfoForm(self):
        debugForm = Toplevel(self.root)
        debugForm.title("Debug Info")
        debugForm.geometry('400x300')

        scrollbar = Scrollbar(debugForm)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)

        text = Text(debugForm, yscrollcommand=scrollbar.set)
        text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        scrollbar.config(command=text.yview)

        debugOutput = self.bmconvAdapter(f'/v "{self.mbmFile}"')
        text.insert(tk.END, debugOutput)

    def bmconvAdapter(self, command):
        bmconvLocalPath = os.path.join(os.path.dirname(__file__), 'bmconv')
        process = Popen(f'{bmconvLocalPath} {command}', stdout=PIPE, stderr=PIPE, shell=True)
        output, error = process.communicate()
        if error:
            messagebox.showerror("ERROR",
                                 f"Not Found BMCONV Executable File! Please Download S60 SDK and Copy bmconv to {bmconvLocalPath}")
        return output.decode("utf-8")

    def getBinaryVersion(self):
        output = self.bmconvAdapter('')
        match = re.search(r'version (\d+)', output)
        return match.group(1) if match else 'Unknown'

    def clearTempDir(self):
        for file in os.listdir(self.tempDir):
            os.remove(os.path.join(self.tempDir, file))
            os.rmdir(self.tempDir)

    def Starter(self):
        version = self.getBinaryVersion()
        self.root.title(f"MultiBitMap Extractor - BMCONV Version {version}")
        self.root.mainloop()

if __name__ == "__main__":
    root = tk.Tk()
    exec = mbmExtractor(root)
    exec.Starter()
    exec.clearTempDir()

整合包

整合包是一个彩蛋
可在OBJect找到
其中的可执行文件来自于Symbian^3 SDK for Belle
感谢Symbian Foundation(虽然其已在法理上消失)


解锁完成之后对其系统更新包产生了兴趣
仔细看了下发现结构简单,所以写了个基于python的解包器
有空再看Cramfs里面是什么吧~哈哈哈哈

import struct
import sys

def extractCramfs(fwData, offset):
    sizeStruct = struct.unpack('<I', fwData[offset+4:offset+8])[0]
    return fwData[offset:offset+sizeStruct]

def extractKernel(fwData, offset):
    headerSize = 64
    imageSize = struct.unpack('>I', fwData[offset+12:offset+16])[0]
    return fwData[offset:offset+headerSize+imageSize]

def extractLinuxVersion(uImageHeader):
    verStartSize = 32
    versionInfo = uImageHeader[verStartSize:64].decode('utf-8', 'ignore')
    return versionInfo

if __name__ == "__main__":
    if len(sys.argv) != 3 or sys.argv[1] != '--firm':
        print("Usage: python uroad_extractor.py --firm <path/to/firmware>")
        sys.exit(1)

    firmwarePath = sys.argv[2]
    with open(firmwarePath, 'rb') as firmwareFile:
        fwData = firmwareFile.read()

        cramfsData = extractCramfs(fwData, 32)
        with open('rootfs.cramfs', 'wb') as cramfsFile:
            print("Cramfs Size:", len(cramfsData))
            print("Cramfs Offset:", 32)
            print("Cramfs End:", 32 + len(cramfsData))
            cramfsFile.write(cramfsData)

        kernelStartAddr = 32 + len(cramfsData)
        uImageData = extractKernel(fwData, kernelStartAddr)
        with open('uImage.bin', 'wb') as uImageFile:
            print("Kernel Size:", len(uImageData))
            print("Kernel Offset:", kernelStartAddr)
            print("Kernel End:", kernelStartAddr + len(uImageData))
            print("Kernel CRC:", hex(struct.unpack('>I', uImageData[16:20])[0]))
            uImageFile.write(uImageData)

        linuxVersion = extractLinuxVersion(uImageData[:64])
        print("Linux Version:", linuxVersion)
        
        print("Extract Complete!")