美文网首页
以光翼战姬1为例,探索MED引擎汉化方法

以光翼战姬1为例,探索MED引擎汉化方法

作者: ssynn | 来源:发表于2020-06-27 17:16 被阅读0次

    1.引擎介绍

    MED引擎是魔改自DXLib的一个Gal游戏引擎,继承了DXLib引擎的一部分特点,但是引擎本身做了很多修改,比如文本包的加密,字体缓存的生成方式等(其实DXLib本身是支持中文和韩文的,但是MED的开发者很好心的进行了阉割,为此还重写了文字缓冲的写入和读取方式,我可真是谢谢你啊(●'◡'●))


    2.游戏汉化过程

    主要流程

    1. 文本解包解密
    2. 文本替换
    3. 文本封包
    4. 引擎汉化

    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为例子说明。

    首先的游戏文本的生成方式:

    1. exe检测文件夹内是否存在_FONTSET.MED文件,如果存在直接将字体数据读入内存。
    2. 如果不存在字体文件则调用createfontindirect创建字体,调用GetGlyphOutline创建字体点阵,将点阵写入内存,之后保存到_FONTSET.MED文件内,给下次打开游戏时使用。
    3. 在显示文字时在缓冲区直接读取字体的点阵,显示到屏幕上。

    有问题的地方

    1. 在生成字体点阵时只会生成0x8140-0xB9FC 和 0xE040-0xF0FC的文字点阵,每个文字点阵占0xA80字节,这远远小于汉字GBK编码的范围,所以只修改createfontindirect不能完成文字汉化。
    2. 内存在开点阵缓冲区时只开了0x2370*0xA80的大小,存不下中文编码的字体。
    3. 引擎在存储和读取字体点阵时跳过了0xA000-0xE000的范围,所以需要修改计算文字点阵偏移的函数,才能让引擎正常读取汉字编码点阵。

    exe修改步骤 光翼战姬1为例

    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
    

    就按上面写的改就行了

    1. 修改缓冲区开辟内存的大小。引擎开辟内存长度的计算方式为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处,改的时候只改命令里的立即数,后面搜索出来奇怪的命令不要去改!!

    1. 修改存取文字点阵的函数。一共有四处

    第一处:读取对话文字的点阵,偏移地址的计算函数为:

    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))
    
    
    

    我的妈呀累死我了

    相关文章

      网友评论

          本文标题:以光翼战姬1为例,探索MED引擎汉化方法

          本文链接:https://www.haomeiwen.com/subject/juomfktx.html