以光翼战姬1为例,探索MED引擎汉化方法
1.引擎介绍
MED引擎是魔改自DXLib的一个Gal游戏引擎,继承了DXLib引擎的一部分特点,但是引擎本身做了很多修改,比如文本包的加密,字体缓存的生成方式等(其实DXLib本身是支持中文和韩文的,但是MED的开发者很好心的进行了阉割,为此还重写了文字缓冲的写入和读取方式,我可真是谢谢你啊(●'◡'●))
2.游戏汉化过程
主要流程
- 文本解包解密
- 文本替换
- 文本封包
- 引擎汉化
1.文本解包解密
解密方式我在https://www.jianshu.com/p/f2c5cf9a9e4e聊过了,这里不再赘述,主要谈谈拆包。
md_scr.med的文件格式为:
- 0-16:header
- 0-4:MDE0
- 4-6:单个ENTRY长度
- 6-8:ENTRY个数
- 8-16:0
- ENTRY表
- data表
ENTRY(不定长)
- 文件名
- 4Byte 未知数,但是对封包有影响不能忽略
- 4Byte 文件偏移
- 4Byte 文件长度
看了GARbro的源代码,它在拆包的时候会忽略ENTRY内的未知数据,所以想要汉化文本必须自己实现拆包和封包函数,代码我放最后了。
简单说明下,调用unpack函数传入md_scr.med 和保存位置的路径,拆解完毕后函数会将一个name_list.json放到脚本所在目录的intermediate_file文件夹内,之后封包要用,同时,程序会尝试解密文本,如果解密成功程序会显示密钥。记得将密钥复制到类的key属性,方便以后回封。
2.文本替换
拆解出来的文本并没有文件扩展名,二进制打开后结构如下:
- Header 16Byte
- 未知区 大小未知
- 字符串区 大小未知
Header结构
- 0-4 文件大小减去0x10
- 4-8 unk1
- 8-16 unk2
我并没有研究出如何一步定位出字符串的位置,但是unk1指向的位置非常接近字符串所在的位置,所以从unk1开始扫描可以找到文件内所有的字符串。
文件内的字符串并没有用偏移和长度记录位置,只和顺序有关,所以替换的时候只要保证顺序不出错就可以了。
3.文本封包
封包时必须保证文件的顺序和源文件内的一致,所以需要上面提到的name_list.json来控制文件顺序。详细自己看代码中的repack函数吧。
4.引擎汉化
最最最麻烦的步骤!!!x32dbg用的不熟的可以放弃了!!!这一步折腾了我好几天,这里我以光翼战姬1的exe为例子说明。
首先的游戏文本的生成方式:
- exe检测文件夹内是否存在_FONTSET.MED文件,如果存在直接将字体数据读入内存。
- 如果不存在字体文件则调用createfontindirect创建字体,调用GetGlyphOutline创建字体点阵,将点阵写入内存,之后保存到_FONTSET.MED文件内,给下次打开游戏时使用。
- 在显示文字时在缓冲区直接读取字体的点阵,显示到屏幕上。
有问题的地方
- 在生成字体点阵时只会生成0x8140-0xB9FC 和 0xE040-0xF0FC的文字点阵,每个文字点阵占0xA80字节,这远远小于汉字GBK编码的范围,所以只修改createfontindirect不能完成文字汉化。
- 内存在开点阵缓冲区时只开了0x2370*0xA80的大小,存不下中文编码的字体。
- 引擎在存储和读取字体点阵时跳过了0xA000-0xE000的范围,所以需要修改计算文字点阵偏移的函数,才能让引擎正常读取汉字编码点阵。
exe修改步骤 光翼战姬1为例
- 修改CreateFontIndirectA的charset。首先删除文件夹内的_FONTSET.MED文件,给CreateFontIndirectA和CreateFont都下断点,F9运行,主要过程为:先断到CreateFontIndirectA大概3次然后断到CreateFont大概两次,继续F9,之后又会断到CreateFontIndirectA这时点运行到返回 再F8单步回到引擎的代码,可以看到引擎调用CreateFontIndirectA的代码
004B2EF1 | B0 86 | mov al,86 | 将这里改为mov al,0x86 最后一个字节用nop填充
004B2EF3 | 90 | nop |
004B2EF4 | 8845 D7 | mov byte ptr ss:[ebp-29],al | 写入charset
004B2EF7 | 8D45 BC | lea eax,dword ptr ss:[ebp-44] |
004B2EFA | 8B55 FC | mov edx,dword ptr ss:[ebp-4] |
004B2EFD | 83C2 1B | add edx,1B |
004B2F00 | E8 738BFEFF | call exstia1.49BA78 |
004B2F05 | 8B45 BC | mov eax,dword ptr ss:[ebp-44] |
004B2F08 | BA DC2F4B00 | mov edx,exstia1.4B2FDC | 4B2FDC:"Default"
004B2F0D | E8 865BFDFF | call exstia1.488A98 |
004B2F12 | 85C0 | test eax,eax |
004B2F14 | 75 1A | jne exstia1.4B2F30 |
004B2F16 | 8D45 B8 | lea eax,dword ptr ss:[ebp-48] |
004B2F19 | BA DF7E6E00 | mov edx,exstia1.6E7EDF | 6E7EDF:"\rMS Sans Serif"
004B2F1E | E8 558BFEFF | call exstia1.49BA78 |
004B2F23 | 8B55 B8 | mov edx,dword ptr ss:[ebp-48] |
004B2F26 | 8D45 DC | lea eax,dword ptr ss:[ebp-24] |
004B2F29 | E8 A266FDFF | call exstia1.4895D0 |
004B2F2E | EB 19 | jmp exstia1.4B2F49 |
004B2F30 | 8D45 B4 | lea eax,dword ptr ss:[ebp-4C] |
004B2F33 | 8B55 FC | mov edx,dword ptr ss:[ebp-4] |
004B2F36 | 83C2 1B | add edx,1B |
004B2F39 | E8 3A8BFEFF | call exstia1.49BA78 |
004B2F3E | 8B55 B4 | mov edx,dword ptr ss:[ebp-4C] |
004B2F41 | 8D45 DC | lea eax,dword ptr ss:[ebp-24] |
004B2F44 | E8 8766FDFF | call exstia1.4895D0 |
004B2F49 | C645 DA 00 | mov byte ptr ss:[ebp-26],0 |
004B2F4D | C645 D8 00 | mov byte ptr ss:[ebp-28],0 |
004B2F51 | C645 D9 00 | mov byte ptr ss:[ebp-27],0 |
004B2F55 | 8BC3 | mov eax,ebx |
004B2F57 | E8 C4010000 | call exstia1.4B3120 |
004B2F5C | FEC8 | dec al |
004B2F5E | 74 06 | je exstia1.4B2F66 |
004B2F60 | FEC8 | dec al |
004B2F62 | 74 08 | je exstia1.4B2F6C |
004B2F64 | EB 0C | jmp exstia1.4B2F72 |
004B2F66 | C645 DB 02 | mov byte ptr ss:[ebp-25],2 |
004B2F6A | EB 0A | jmp exstia1.4B2F76 |
004B2F6C | C645 DB 01 | mov byte ptr ss:[ebp-25],1 |
004B2F70 | EB 04 | jmp exstia1.4B2F76 |
004B2F72 | C645 DB 00 | mov byte ptr ss:[ebp-25],0 |
004B2F76 | 8D45 C0 | lea eax,dword ptr ss:[ebp-40] |
004B2F79 | 50 | push eax | LOGFONT参数
004B2F7A | E8 31C12100 | call <JMP.&CreateFontIndirectA> | 调用CreateFontIndirectA
就按上面写的改就行了
- 修改缓冲区开辟内存的大小。引擎开辟内存长度的计算方式为0x2370*0xA80只需要把0x2370改为0x6000就够GBK编码的汉字使用了。修改时只需要搜索特征值 70 23 00 00就可以搜到所有开辟内存的函数段
00406092 | EB 65 | jmp exstia1.4060F9 |
00406094 | 690D 647A7500 70230000 | imul ecx,dword ptr ds:[757A64],2370 | 2370改为6000
0040609E | A1 647A7500 | mov eax,dword ptr ds:[757A64] |
004060A3 | C1E0 06 | shl eax,6 |
004060A6 | 8D0440 | lea eax,dword ptr ds:[eax+eax*2] |
004060A9 | 03C8 | add ecx,eax |
004060AB | 51 | push ecx |
004060AC | E8 93650D00 | call exstia1.4DC644 |
004060B1 | 59 | pop ecx |
004060B2 | A3 847A7500 | mov dword ptr ds:[757A84],eax |
004060B7 | FFB5 3CFEFFFF | push dword ptr ss:[ebp-1C4] |
004060BD | 6915 647A7500 70230000 | imul edx,dword ptr ds:[757A64],2370 | 2370改为6000
004060C7 | 8B0D 647A7500 | mov ecx,dword ptr ds:[757A64] |
004060CD | C1E1 06 | shl ecx,6 |
应该要改前5处,改的时候只改命令里的立即数,后面搜索出来奇怪的命令不要去改!!
- 修改存取文字点阵的函数。一共有四处
第一处:读取对话文字的点阵,偏移地址的计算函数为:
if ( _wchar >= 0xE0 ) // 全角字符
{
if ( _wchar < 0x8140
|| _wchar >= 0xF000
|| _wchar >= 0xA000 && _wchar < 0xE040
|| (signed int)(unsigned __int8)_wchar < 0x40
|| (signed int)(unsigned __int8)_wchar > 0xFC )
{
return wchar;
}
if ( _wchar >= 0xA000 )
wchar_l = (_wchar >> 8) - 0xC1;
else
wchar_l = (_wchar >> 8) - 0x81;
_ptr = (char *)ptr + 0xC0 * dword_757A64 + ((unsigned __int8)_wchar + 0xBD * wchar_l - 0x40) * dword_757A64;
}
else // 半角字符
{
_ptr = (char *)ptr + (_wchar - 0x20) * dword_757A64;// 757A64 = 0xA80 = 0x20*0xA0*2
}
得出当编码大于0xA000时文字点阵偏移的计算公式为
_wchar = b'\xa1\xb8'
offset = 0xC0 * 0xA80 + (_wchar[1] - 0x40 + 0xBD * (_wchar[0]-0xC1) ) * 0xA80 + 0x134E0004
其中_wchar为GBK编码的汉字
0x134E0004为点阵缓冲区的基址,每次运行都不一样
修改目标:
- 让其遇到0xA000-0xE040的编码不会退出
- 第二字节的上限改为 0xFE
- 当编码大于0xA000时修改其偏移的计算公式为
_wchar = b'\xa1\xb8'
offset = 0xC0 * 0xA80 + (_wchar[1] - 0x40 + 0xBF * (_wchar[0]-0x81) ) * 0xA80 + 0x134E0004
这里的代码修改后就像下面的样子
0042F839 | F76D F0 | imul dword ptr ss:[ebp-10] |
0042F83C | 0305 847A7500 | add eax,dword ptr ds:[757A84] | 基址
0042F842 | 8945 E0 | mov dword ptr ss:[ebp-20],eax |
0042F845 | E9 C4000000 | jmp exstia1.42F90E |
0042F84A | 817D FC 40810000 | cmp dword ptr ss:[ebp-4],8140 |
0042F851 | 0F8C 75010000 | jl exstia1.42F9CC |
0042F857 | 817D FC 00FE0000 | cmp dword ptr ss:[ebp-4],FE00 | 改成FE00
0042F85E | 0F8D 68010000 | jge exstia1.42F9CC |
0042F864 | 817D FC 00FE0000 | cmp dword ptr ss:[ebp-4],FE00 | 改成FE00
0042F86B | 7C 0D | jl exstia1.42F87A |
0042F86D | 817D FC 40FE0000 | cmp dword ptr ss:[ebp-4],FE40 | 改成FE00
0042F874 | 0F8C 52010000 | jl exstia1.42F9CC |
0042F87A | 8B55 FC | mov edx,dword ptr ss:[ebp-4] |
0042F87D | 81E2 FF000000 | and edx,FF |
0042F883 | 8955 F0 | mov dword ptr ss:[ebp-10],edx |
0042F886 | 837D F0 40 | cmp dword ptr ss:[ebp-10],40 | 40:'@'
0042F88A | 0F8C 3C010000 | jl exstia1.42F9CC |
0042F890 | 817D F0 FE000000 | cmp dword ptr ss:[ebp-10],FE | 0xFC -> 0xFE
0042F897 | 0F8F 2F010000 | jg exstia1.42F9CC |
0042F89D | 817D FC 00A00000 | cmp dword ptr ss:[ebp-4],A000 |
0042F8A4 | 7D 25 | jge exstia1.42F8CB |
0042F8A6 | 8B4D FC | mov ecx,dword ptr ss:[ebp-4] |
0042F8A9 | C1F9 08 | sar ecx,8 |
0042F8AC | 81C1 7FFFFFFF | add ecx,FFFFFF7F |
0042F8B2 | 69C1 BF000000 | imul eax,ecx,BF | *0xBD -> *0xBF
0042F8B8 | 8B55 FC | mov edx,dword ptr ss:[ebp-4] |
0042F8BB | 81E2 FF000000 | and edx,FF |
0042F8C1 | 03C2 | add eax,edx |
0042F8C3 | 83C0 C0 | add eax,FFFFFFC0 |
0042F8C6 | 8945 F0 | mov dword ptr ss:[ebp-10],eax |
0042F8C9 | EB 23 | jmp exstia1.42F8EE |
0042F8CB | 8B4D FC | mov ecx,dword ptr ss:[ebp-4] |
0042F8CE | C1F9 08 | sar ecx,8 |
0042F8D1 | 81C1 7FFFFFFF | add ecx,FFFFFF7F | -0xC1 -> -0x81
0042F8D7 | 69C1 BF000000 | imul eax,ecx,BF | *0xBD -> *0xBF
0042F8DD | 8B55 FC | mov edx,dword ptr ss:[ebp-4] |
第二处:读取人名、log、对话框点阵
原理和上面一样,我直接给改好的汇编
00430ACA | 7C 09 | jl exstia1.430AD5 |
00430ACC | 817D FC 00FE0000 | cmp dword ptr ss:[ebp-4],FE00 | 改成FE00
00430AD3 | 7C 07 | jl exstia1.430ADC |
00430AD5 | B0 01 | mov al,1 |
00430AD7 | E9 6F010000 | jmp exstia1.430C4B |
00430ADC | 817D FC 00FE0000 | cmp dword ptr ss:[ebp-4],FE00 | 改成FE00
00430AE3 | 7C 10 | jl exstia1.430AF5 |
00430AE5 | 817D FC 40FE0000 | cmp dword ptr ss:[ebp-4],FE40 | 改成FE40
00430AEC | 7D 07 | jge exstia1.430AF5 |
00430AEE | B0 01 | mov al,1 |
00430AF0 | E9 56010000 | jmp exstia1.430C4B |
00430AF5 | 8B55 FC | mov edx,dword ptr ss:[ebp-4] |
00430AF8 | 81E2 FF000000 | and edx,FF |
00430AFE | 8955 F0 | mov dword ptr ss:[ebp-10],edx |
00430B01 | 837D F0 40 | cmp dword ptr ss:[ebp-10],40 | 40:'@'
00430B05 | 7C 09 | jl exstia1.430B10 |
00430B07 | 817D F0 FE000000 | cmp dword ptr ss:[ebp-10],FE | 改成FE
00430B0E | 7E 07 | jle exstia1.430B17 |
00430B10 | B0 01 | mov al,1 |
00430B12 | E9 34010000 | jmp exstia1.430C4B |
00430B17 | 817D FC 00A00000 | cmp dword ptr ss:[ebp-4],A000 |
00430B1E | 7D 24 | jge exstia1.430B44 |
00430B20 | 8B55 FC | mov edx,dword ptr ss:[ebp-4] |
00430B23 | C1FA 08 | sar edx,8 |
00430B26 | 81C2 7FFFFFFF | add edx,FFFFFF7F |
00430B2C | 69CA BF000000 | imul ecx,edx,BF | 改成BF
00430B32 | 8B45 FC | mov eax,dword ptr ss:[ebp-4] |
00430B35 | 25 FF000000 | and eax,FF |
00430B3A | 03C8 | add ecx,eax |
00430B3C | 83C1 C0 | add ecx,FFFFFFC0 |
00430B3F | 894D F0 | mov dword ptr ss:[ebp-10],ecx |
00430B42 | EB 22 | jmp exstia1.430B66 |
00430B44 | 8B55 FC | mov edx,dword ptr ss:[ebp-4] |
00430B47 | C1FA 08 | sar edx,8 |
00430B4A | 81C2 7FFFFFFF | add edx,FFFFFF7F | 改成FFFFFF7F
00430B50 | 69CA BF000000 | imul ecx,edx,BF |
00430B56 | 8B45 FC | mov eax,dword ptr ss:[ebp-4] | 改成BF
第三次:存储字体点阵的函数
00432065 | 81EA 81000000 | sub edx,81 |
0043206B | 69CA BF000000 | imul ecx,edx,BF | 改成BF
00432071 | 8B45 C8 | mov eax,dword ptr ss:[ebp-38] |
00432074 | 25 FF000000 | and eax,FF |
00432079 | 03C8 | add ecx,eax |
0043207B | 83E9 40 | sub ecx,40 |
0043207E | 894D 8C | mov dword ptr ss:[ebp-74],ecx |
00432081 | EB 25 | jmp exstia1.4320A8 |
00432083 | 8B55 C8 | mov edx,dword ptr ss:[ebp-38] |
00432086 | C1EA 08 | shr edx,8 |
00432089 | 81EA 81000000 | sub edx,81 | 改成81
0043208F | 90 | nop |
00432090 | 90 | nop |
00432091 | 90 | nop |
00432092 | 69CA BF000000 | imul ecx,edx,BF | 改成BF
00432098 | 8B45 C8 | mov eax,dword ptr ss:[ebp-38] |
0043209B | 25 FF000000 | and eax,FF |
第四处:让生成文字点阵的范围包含0xA000-0xE040的编码,同时第二个字节的上限改为FE
004317DB | C745 BC 81000000 | mov dword ptr ss:[ebp-44],81 |
004317E2 | 817D BC A0000000 | cmp dword ptr ss:[ebp-44],A0 | 生成文字缓冲
004317E9 | 75 07 | jne exstia1.4317F2 |
004317EB | C745 BC A0000000 | mov dword ptr ss:[ebp-44],A0 | E0改为A0
004317F2 | C745 B8 40000000 | mov dword ptr ss:[ebp-48],40 | 40:'@'
004317F9 | 837D B8 7F | cmp dword ptr ss:[ebp-48],7F |
004317FD | 74 10 | je exstia1.43180F |
004317FF | 8B45 BC | mov eax,dword ptr ss:[ebp-44] |
00431802 | C1E0 08 | shl eax,8 |
00431805 | 0345 B8 | add eax,dword ptr ss:[ebp-48] |
00431808 | 33D2 | xor edx,edx |
0043180A | E8 79010000 | call exstia1.431988 |
0043180F | FF45 B8 | inc dword ptr ss:[ebp-48] |
00431812 | 817D B8 FE000000 | cmp dword ptr ss:[ebp-48],FE | 第二个字节的上限改为FE
00431819 | 7E DE | jle exstia1.4317F9 |
0043181B | FF45 BC | inc dword ptr ss:[ebp-44] |
0043181E | 817D BC F8000000 | cmp dword ptr ss:[ebp-44],F8 | 第一个字节的上限改为F8
00431825 | 7C BB | jl exstia1.4317E2 |
00431827 | 8A0D 5C7A7500 | mov cl,byte ptr ds:[757A5C] |
0043182D | 888D 2CFFFFFF | mov byte ptr ss:[ebp-D4],cl |
00431833 | A0 607A7500 | mov al,byte ptr ds:[757A60] |
最后是Python脚本
class MED():
key = b'\x9b\xd0\xcf\xd0\x9b\x88\x8d\x8c\x97\x9f\xd0\xcf\x94\x8b\x8d\x8c\x9b\x8e\x97\x8d'
def decrypt(_data: bytes, _key=None):
if _key:
MED.key = _key
_data = bytearray(_data)
for i in range(0x10, len(_data)):
_data[i] = (_data[i]-MED.key[(i-0x10) % len(MED.key)]) & 0xff
return _data
def encrypt(_data: bytes):
_data = bytearray(_data)
for i in range(0x10, len(_data)):
_data[i] = (_data[i]+MED.key[(i-0x10) % len(MED.key)]) & 0xff
return _data
def extract_med(name=None):
'''
从input文件夹内的脚本文件抽取文本,放入intermediate_file/jp_all.txt
如果游戏可以自定义姓名,则需要将文本中的$0进行替换,使用时只需要传入主人公的名字就可以了
$0 主人公名字
'''
def _has_jp(line: str) -> bool:
'''
如果含有日文文字(除日文标点)则认为传入文字含有日文, 返回true
'''
for ch in line:
if ('\u0800' <= ch and ch <= '\u9fa5') or ('\uff01'<=ch<='\uff5e'):
return True
return False
file_all = os.listdir('input')
ans = []
for f in file_all:
_data = open_file_b(f'input/{f}')
# _data = MED.decrypt(_data)
_offset = int.from_bytes(_data[4:8], byteorder='little') + 0x10
_str = _data[_offset:]
_buff = b''
for i in _str:
if i:
_buff += to_bytes(i, 1)
else:
try:
_buff = _buff.decode('cp932')
if _has_jp(_buff) and _buff[0] not in ';#':
if name:
_buff = _buff.replace('$0', name)
ans.append(_buff)
except Exception as e:
print(e)
print(f, _buff)
_buff = b''
save_file('intermediate_file/jp_all.txt', '\n'.join(ans))
def output(name=None):
'''
替换原文件中的文本,将替换后的结果放入output文件夹
使用前需要利用jp_all.txt生成翻译字典,并将字典翻译放入intermediate_file文件夹,字典的格式为
{
'Japanese':'chinese',
'Japanese':'chinese',
...
}
'''
def _has_jp(line: str) -> bool:
'''
如果含有日文文字(除日文标点)则认为传入文字含有日文, 返回true
'''
for ch in line:
if ('\u0800' <= ch and ch <= '\u9fa5') or ('\uff01'<=ch<='\uff5e'):
return True
return False
file_all = os.listdir('input')
jp_chs = open_json('intermediate_file/jp_chs.json')
cnt = 0
failed = []
for f in file_all:
_data = open_file_b(f'input/{f}')
_data = bytearray(_data)
_offset = int.from_bytes(_data[4:8], byteorder='little') + 0x10
_str = _data[_offset:]
_buff = b''
while _offset < len(_data):
if _data[_offset]:
_buff += _data[_offset:_offset+1]
else:
try:
_str = _buff.decode('cp932')
# print(_str)
# if len(failed) >5:
# return
if not _has_jp(_str) or _str[0] in ';#':
_offset += 1
continue
if name:
_str = _str.replace('$0', name)
if _str in jp_chs and jp_chs[_str]:
_offset -= len(_buff)
_new_bytes = jp_chs[_str].encode('gb2312', errors='ignore')
_data[_offset:_offset+len(_buff)] = _new_bytes
_offset += len(_new_bytes)
cnt += 1
else:
failed.append(_str)
except Exception as e:
print('失败')
print(f, _buff)
print(e)
print()
finally:
_buff = b''
_offset += 1
_data[:4] = to_bytes(len(_data)-0x10, 4)
save_file_b(f'output/{f}', _data)
save_file('intermediate_file/failed.txt', '\n'.join(failed))
print('替换:', cnt)
print('失败:', len(failed))
def unpack(path='md_scr.med', output='input'):
def remove_dumplicate_str(key):
for i in range(2, len(key)):
cnt = int(len(key) / i + 1)
tmp = key[:i]
ans = bytearray(tmp)
tmp *= cnt
tmp = tmp[:len(key)]
# print(tmp, len(tmp))
if tmp == key:
# print(ans)
return ans
return None
data = open_file_b(path)
entry_length = from_bytes(data[4:6])
entry_count = from_bytes(data[6:8])
name_list = []
if not os.path.exists(output):
os.mkdir(output)
for i in range(entry_count):
entry = data[16+i*entry_length:16+(i+1)*entry_length]
offset = from_bytes(entry[-4:])
length = from_bytes(entry[-8:-4])
unk = from_bytes(entry[-12:-8])
name = ''
for i in entry:
if not i:
break
else:
name+=chr(i)
_file_data = data[offset:offset+length]
file_name = f'{name}_{unk}'
save_file_b(f'{output}/{file_name}', _file_data)
# print(file_name, offset, length)
name_list.append(file_name)
key = None
file_all = os.listdir(output)
for f in file_all:
if f[:5] == '_VIEW':
_view = open_file_b(f'{output}/{f}')
_raw = _view[0x10:0x28]
_base = b'\x00\x23\x52\x55\x4C\x45\x5F\x56\x49\x45\x57\x45\x52\x00\x3A\x56\x49\x45\x57\x5F\x30\x00\x7B\x00'
key = []
for i in range(24):
key.append(_raw[i] - _base[i])
break
if key:
print((key))
key = bytearray(map(lambda x:x&0xff,key))
key = remove_dumplicate_str(key)
print(key, len(key))
for f in file_all:
_data = open_file_b(f'{output}/{f}')
_data = MED.decrypt(_data, key)
save_file_b(f'{output}/{f}', _data)
if not os.path.exists('intermediate_file'):
os.mkdir('intermediate_file')
save_json(f'intermediate_file/name_list.json', name_list)
else:
print('无法解密')
def repack(path='output'):
'''
调用此函数先需要先将解包时产生的密钥填入key属性
'''
name_list = open_json(f'intermediate_file/name_list.json')
# name_list = os.listdir(path)
entry_length = 0x17
header = b'MDE0\x17\x00'
header += to_bytes(len(name_list), 2) + b'\x00' * 8
entry_all = []
file_data = []
offset = 0x10 + len(name_list)*entry_length
for f in name_list:
_p = len(f)-1
while f[_p] != '_':
_p-=1
name = f[:_p].encode()
name += b'\x00'*(entry_length-len(name)-12)
unk = int(f[_p+1:])
unk = to_bytes(unk, 4)
_file_data = open_file_b(f'{path}/{f}')
_file_data = MED.encrypt(_file_data)
entry = name + unk + to_bytes(len(_file_data), 4) + to_bytes(offset, 4)
entry_all.append(entry)
file_data.append(_file_data)
offset += len(_file_data)
save_file_b('md_scr.med.chs', header + b''.join(entry_all) + b''.join(file_data))
我的妈呀累死我了