前言
最近在一个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下
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(虽然其已在法理上消失)