firstRun.rar (1.0 KB)
他里面有个私有虚拟机,这个gs是虚拟机字节码,他这个是内置的私有虚拟机解释执行的enc函数,必须得逆向虚拟机才能看出这个enc干了啥
草?vmp
差不多是这样的
但是他图像数据其实没加密,应该只是加密了图像的头部,我这边给图像重新拼个头部倒是可以解出有效图像
可以請教一下是如何拚的嗎
這邊有一個原檔案可以測試
這遊戲的atlas json 名子都不太一樣
我也不知道找到的texture atlas json是不是正確的
enc文件的56 ~ 60四个字节是图片数据区域长度 60 ~ len + 60 就是图片的raw数据类似bitmap,你给raw数据截取出来重新封一个png或者ktx的文件头就行了,不过有个问题,就是你得知道图片的长宽才行
1 个赞
它的图片是 ATF 格式 是flash用的贴图类型, ATF相当于pvr格式, 不过我看这游戏应该只是用了ETC1
获取原尺寸很简单, 只需要apk里的 desc.txt
from io import BytesIO
from pathlib import Path
from re import finditer as reFit
from zlib import decompress as zlibDec
from PIL.Image import frombytes as imgFB
from texture2ddecoder import decode_etc1 as ETC1
def ReadFile(file: str|Path) -> bytes:
with open(file, 'rb') as f:
data = f.read()
return zlibDec(data) if data.startswith(b'\x78\xDA') else data
class IronSaga:
@classmethod
def GetTexOrigNames(cls, file: str|Path = 'all.bin') -> dict[str, set[str]]:
data = ReadFile(file)
mats = [i.start() - 2 for i in reFit(rb'driverAsset/ex.image.spine.', data)]
r = BytesIO(data)
names: dict[str, set[str]] = {}
for i in mats:
r.seek(i)
o = r.read(int.from_bytes(r.read(2))).decode().rsplit('/', 1)[1]
n = r.read(int.from_bytes(r.read(2))).decode()
if not n: continue
names.setdefault(o, set()).add(n)
return names
@classmethod
def GetTexSize(cls, file: str|Path = 'desc.txt') -> dict[str, list[int]]:
names: dict[str, list[int]] = {}
lines = ReadFile(file).decode().splitlines()
for i in lines:
if not i.startswith('section ') or i.count(' ') != 5: continue
_, n, cw, ch, ow, oh = i.split(' ')
names[n] = [int(j) for j in (cw, ch, ow, oh)]
return names
@classmethod
def CovImg(cls, file: Path, names: set[str], sizes: list[int]):
cw, ch, ow, oh = sizes
with open(file, 'rb') as f:
if f.read(2) == b'\x78\xDA':
f.seek(0)
r = BytesIO(zlibDec(f.read()))
r.seek(56)
data = r.read(int.from_bytes(r.read(4)))
else:
f.seek(56)
data = f.read(int.from_bytes(f.read(4)))
height = oh*2
imgBytes = ETC1(data, ow, height)
img = imgFB('RGBA', (ow, height), imgBytes, 'raw', ('BGRA'))
rgb = img.crop((0, 0, ow, oh))
a = img.crop((0, oh, ow, height)).convert('L')
rgb.putalpha(a)
rgba = rgb.crop((0, 0, cw, ch)) if (cw, ch) != (ow, oh) else rgb
if names is None:
rgba.save(file.parent.joinpath(f'{file.stem}.png'))
else:
path = file.parent
for i in names:
rgba.save(path.joinpath(f'{i}.png'))
def batch(texPath: str = '', allbin: str = 'all.bin', desc: str = 'desc.txt', exts: tuple[str] = (r'.enc', r'.cet'), subfolder: bool = False):
path = Path(texPath) if texPath else Path.cwd()
need = [i for i in (path.rglob('*') if subfolder else path.glob('*')) if i.name.endswith(exts) and i.is_file()]
names = IronSaga.GetTexOrigNames(allbin)
sizes = IronSaga.GetTexSize(desc)
for i in need:
item = i.stem
nSet = names.get(item, None)
size = sizes.get(item, None)
if size is None:
print(f'没有查找到该贴图的尺寸 --- {i.as_posix()}')
continue
try:
IronSaga.CovImg(i, nSet, size)
except:
print(f'转换错误 --- {i.as_posix()}')
if __name__ == '__main__':
path = r'' # 贴图路径
allbin = r'all.bin' # all.bin文件路径
desc = r'desc.txt' # desc.txt文件路径
exts = (r'.enc', r'.cet') # 贴图后缀
subfolder = False # 是否查找子文件夹内的贴图
batch(path, allbin, desc, exts, subfolder)
暴力转换贴图外加还原部分spine的贴图名字, 注意atlas里记录的贴图名是不正确的, 还原的贴图名是和atlas文件名字一样的
3 个赞
all.bin 在apk里的 bin/all.bin
desc.txt 在 texture/desc.txt
all.bin 里记录了 部分texture的原名
2 个赞
大佬太强了
谢谢截图参考,我把ex.image单独放到别的文件夹就解出来了,不知道是啥原因
学着大佬的代码解了一下,发现spine文件夹里仍然有不少的atlas和json文件没有对应的png,我解的是ex.image文件。