首页
社区
课程
招聘
[原创]Galgame汉化中的逆向(六):动态汉化分析_以MAJIROv3引擎为例
发表于: 2021-7-18 22:05 17781

[原创]Galgame汉化中的逆向(六):动态汉化分析_以MAJIROv3引擎为例

2021-7-18 22:05
17781

by devseed, 本贴论坛和我的博客同时发布
上期链接:Galgame汉化中的逆向(四)_IDA静态分析psv游戏

之前我们谈论的基本上都是静态汉化。所谓静态汉化,即分析文件结构、二进制脚本opcode,然后进行静态封包等方法。与类似于静态编译的语言类似,在运行前数据类型等已经确定完成,程序运行时按照既定的逻辑执行,静态汉化显示的是我们提前准备好的汉化文本。大部分的主机游戏汉化都是静态汉化,因为权限等问题,主机几乎不可能动态调试(即使有,gdbserver等用起来也挺费劲,也可能有兼容性问题调试失败)。再加上在主机上hook也很麻烦,测试极不方便,所以大部分主机游戏汉化以静态汉化为主,有模拟器的可能会结合一些动态调试辅助分析(不过别指望模拟器的调试有多好用了...)。

静态汉化是基础,对于常见文件结构、二进制脚本、算法等有了一定了解后,我们才能更好地找到关键位置dump、文本注入点等,因此我之前的汉化教程都是以静态汉化为主。与静态汉化相对的是动态汉化,往往不需要进行复杂的文件分析和二进制脚本分析,通常也不用考虑封包问题。动态汉化中,文本显示是程序运行时动态注入和替换的,重点是找到:

目前关于动态汉化的分析帖相对来说比较少,下面我们就以Majirov3引擎为例,来谈谈如何进行动态分析、如何进行动态汉化、以及如何解决一些动态汉化中出现的问题。

winterpolaris_dynamic_chs1

动态汉化的第一步,动态dump封包中已经解密完成的二进制脚本,从中提取文本和对应的偏移。那么如何去找呢?通常可以在游戏运行时候去搜索内存中的特定文本,找出最像是二进制脚本的那部分(可能有多个搜索结果,但有些并不是源头,类似于用CE去搜索会有多个数值匹配),然后下硬件访问断点,看是哪些代码生成的。

但是这个方法有个问题,解密文本的位置可能是malloc动态生成的缓冲区,重启调试器后位置会改变,导致断点失效。这时候我们可以考虑hook文件访问的API,如fopen,CreateFile等,来顺藤摸瓜找到读取封包和解密文本的位置。有可能没有动态链接msvcrt.dll,而是静态链接到exe里了,导致导入表没有此函数。一般ida可以识别出这些静态链接的C库函数,如下:

之后我们可以对这些函数进行hook,此游戏用的都是c库函数进行文件读取。代码如下:

之后我们可以查看日志,在进入章节的时候,看看是哪些函数调用了文件API。

fread的读取数据的大小可能和二进制文件的结构相关,比如说第一个fread先在scenario.arc文件开头读取了0x1c大小,我们可以推测文件头的大小是0x1c。用同样的方法,可以顺便把封包结构分析出来了,包括封包内的每个子项(mjo)数据结构。下图为scenario.arc在开头和0x114dfb位置的内容,观察发现在utf-8sjis下没有有意义字符串,可以断定mjo是加密或压缩的

whiterpolaris_scenario_arc

whiterpolaris_scenario_arc_114dfb

majiroV3封包文件结构总结如下:

当然了,这个封包结构很简单,直接静态黑箱分析也完全能猜出来,上面只是为了演示一下动态分析的一些思路。分析封包文件结构不是必须的,但是可以帮助我们更好的找到解密文本的位置。之后,定位到fopenscenario.arc后的fread,在缓冲区下写入断点(此地址就是之前所说的每次都会变的malloc地址),即可定位到解密文本的内容。

或者通过日志0x440cd6 fread(0x80f3b0, 0x10, 0x1, 0x4ca198) offset=0x114dfb的返回地址0x440cd6,找到准备读取每一个mjo的函数,即sub_440AB0。这个引擎定位还是比较容易的,有日语错误信息辅助定位,默认显示乱码,需要把IDA的Cstyle default-8bit encoding改成Shift-jis编码。

我们已经找到了二进制脚本读取的函数,稍微分析一下不难找到文本解密函数sub_478E70。虽然用了SSE指令集优化,但是不难分析的,典型的xor加密,ida伪代码可读性已经很强了。本节以动态dump讲解为主,此处就不再详细分析解密函数了。

关于具体的dump点,可以在sub_440AB0的末尾进行hook,返回值eaxmjo_struct指针,同时储存在[4DC350]全局变量中。下面为此函数返回处的反汇编代码:

根据sub_440AB0反汇编伪代码(见上节)中的sub_478E70(*((__m128i **)context + 0x29), *((_DWORD *)context + 0x24))解密函数,可以得出下面结论:

有了这些信息,我们可以写dump解密文本函数了,如下:

dump完后,查看一下,二进制脚本已经解密。至于提取剧本,简单观察大概是这样的结构40 08 [size 2] text 00,匹配这种结构即可,当然也可以直接检测sjis编码提取,详见我写的binary_text.py

动态汉化的好处是我们不用去费半天劲逆向封包算法、不用再去分析二进制指令opcode。

但是同样动态汉化也有一些问题:

因此,选择文件hook点的位置,原则上越接近原始位置(读取二进制文本的位置)越好 。直接搜索显示在屏幕上的文本,得到的搜索结果可能有多个,分别修改一下看看对游戏产生什么影响。同时也要兼顾能否找到当前文本在脚本中的偏移来进行定位,在查找到文本缓存的周围(如堆栈中的指针,寄存器,或者反汇编指令里引用的全局变量)来找找有没有标识当前文本在二进制脚本中位置的指针。下面的反汇编为本游戏的一些显示文本位置:

根据测试a. showtext_screen这个位置最适合作为动态hook替换文本的位置,显示内容最接近实际显示的,而且修改后的字符串也会被记录到游戏backlog里,可供回看文本。其他的hook点有些会调用多次、有些文本不全、有些会显示过多字符(如人名,这些会作为语音标识,但不会显示在对话中)。对于显示函数的hook,总结如下:

写个脚本hook验证一下:

frida -l winterpolaris_hook.js -f Polaris_chs.exe >1.txt将脚本注入游戏,由于控制台无法显示sjis字符,因此将输出内容重定向到文件中,然后用sjis编码查看。得到的内容如下:

winterpolaris_frida_showtext

对照我们用binary_text.py提取的文本, 偏移(当前位置指针-解密缓冲区基址)正好是文件中的偏移,至此这个游戏动态汉化的理论研究已经完成,之后就是用c和内联汇编写程序实践了。下面是我们用于翻译的文本格式,白点列用于原文,黑点列用于译文,每行是●|num|addr|size●的索引格式,详见binary_text.h

其实有时候如果我们实在找不到文本标识的偏移,也可以强行把游戏从从头到尾过一遍,把每句输出的文本提取出来。汉化的时候,再用hashmapLongest Common Subsequencedp计算当前文本与文本数据库中的相似度,选取相似度最高的匹配用于替换。

以上,我们谈了谈如何进行动态汉化的相关分析,方便起见都是用的frida进行hook。但是frida属于测试环境,不可能要求每个人电脑上都有这个环境,而且也可能有python版本冲突等问题。需要用尽可能少的依赖制作汉化,因此就要结合C与内联汇编来写汉化程序了。在制作汉化程序之前,来科普一下汉化游戏常用的hook方法。

即把相应函数的导入表的地址(FirstThunk)替换成我们的函数,实现hook。关于IAT结构和导入表相关内容,可以参考我之前写的文章SimpleDpack。下面是IAT hook的代码,兼容64位,详见我的github, win_hook,c

IAThook只适用于动态链接外部DLL的函数,对于exe内部的函数,就需要Inline hook了,操作如下:

不过我们不用再自己解析函数开头处的机器码了,直接用微软的detoursInline hook即可。细节上和上述可能有些区别,不过原理都是一样的。detours用法如下:

上述hook代码编译成的载体是DLL,我们还需要把此DLL注入目标到exe中,接管某些函数改变其功能。

有三种常用方法:

代码如下,详见我的githubinjectdll.py, win_hook,c

到此,主要问题我们都搞清楚了,现在可以愉快地编写动态汉化程序了。动态汉化程序主要包括下面几个部分:

Inlinehook处汇编环境与C语言函数的对接, 注意cdeclstdcall,汇编调用C函数要自己保存寄存器

维护日文文本与汉化文本的对应关系,文本偏移的定位等数据。并且用二分法等算法来查找替换文本等。

这里采取的__declspec(naked)形式进行内联汇编,进行获取当前文本指针、计算在文件中的偏移、调用相应的C函数查找字符串、替换汉化文本等操作。此处为了方便使用了一些全局变量,以g_前缀开头。

此处用双向链表数据结构来存储文件名与文本项索引,g_mjos全局变量来指向索引链表,g_cur_mjo指向当前文本索引位置。当游戏加载脚本时会查询当前链表中是否已经加载过,可以避免重复加载造成的内存泄露。PFTEXTS数据结构详见我的通用汉化文本格式,binary_text.h

由于我们用的是日文和中文对照文本,因此文件用的utf-8格式存储,动态替换汉化文本要转换为gb2312格式。

lplf->lfCharSet改为0x86即可,字体改成simhei

当然改完后读取gb2312也可能没法正常显示,因为游戏可能对字符进行限制。未处于sjis区间的字符可能会显示成方框,也可能会被当成单字节字符显示,造成接下来运行错误。

这个游戏比较特殊,没有用cmp xx 81h等直接判断,而是用了charmap映射了当前字节数值的类型,与“是否能构成sjis字符”相关。bp TextOutA可以发现非sjis字符会被当成单字节字符。再稍微跟一下,可以看见其通过查表确定是否为sjis字符,0x4AE2E9为字符类型映射表,如下图所示。

winterpolaris_tonextchar

解决方法也很简单,直接用内联汇编来替换sub_47EE00,去除sjis范围限制。当然也可以去修改映射表,但是不确定是不是其他的函数也用这个映射表,改了后可能会出现问题。

最后就是处理一些小问题了,比如说有些字符没有显示全,可能是因为字体高度不够;菜单乱码等问题,可能对应的文本是通过其他函数显示的,或是菜单文本本身是在exe里面的,此处不再赘述。

winterpolaris_textheight_before

winterpolaris_textheight

折腾了半天,现在我们的动态汉化终于成功运行了!完整代码详见我的github, winterpolaris_hook.c

winterpolaris_dynamic_chs2

虽然难度不大,但是这篇教程写了也快一天才完成,之前搜集素材、编写程序、调试等断断续续地也用了将近一周。主要是想着如何叙述得容易理解,如何使得结构清晰有条理性。其实动态汉化更多的意义在于折腾,自己一步步地探索与改造的乐趣,就像是DIY的乐趣。下面再补充一些关于编译与调试的内容。

因为windows下没有regex.h头文件,所以一开始我是用mingwgcc来编译的。有个问题是,无法链接msvc编译的detours.lib(很多符号找不到,报错),也不太清楚怎么用gcc编译detours。而且gcc貌似没法声明naked函数类型?

于是就用clang了,因为-target i686-pc-windows-msvc可以兼容msvc的link, 同时语法上也接近gcc用起来会比较方便。但是这个模式就无法链接GNU的静态库了如libxxx.a。虽然强行把libregex.dll.a改名为regex.lib倒是也能识别,但是没法静态链接,会附加一大堆mingw的dll。makefile如下,里面会用到我以前写的一些文件,现在都已上传到GalgameReverse

clang编译的时候加入-g调试信息到dll中,直接用mklink符号链接把dll链接到exe所在的路径下,vscode里面launch.jsonlldb中program填写对应的exe。在C源代码下断点,F5启动exe即可调试。launch.json如下:

不过vscode目前好像不支持内联汇编调试,那么就用x64dbg调试吧,可以读取到调试信息并显示源码行数。这里有个小技巧,我们可以打印出来Inlinehook的地址,然后再用x64dbg调试,方便定位。

winterpolaris_x32dbg_inlineasm

 
 
 
.text:00488F86 ; FILE *__cdecl fopen(const char *FileName, const char *Mode)
.text:00488F86 _fopen          proc near               ; CODE XREF: sub_42D210+177↑p
.text:00488F86                                         ; sub_42D210+3F2↑p ...
.text:00488F86
.text:00488F86 FileName        = dword ptr  8
.text:00488F86 Mode            = dword ptr  0Ch
.text:00488F86
.text:00488F86                 push    ebp
.text:00488F87                 mov     ebp, esp
.text:00488F89                 push    40h ; '@'       ; ShFlag
.text:00488F8B                 push    [ebp+Mode]      ; Mode
.text:00488F8E                 push    [ebp+FileName]  ; FileName
.text:00488F91                 call    __fsopen
.text:00488F96                 add     esp, 0Ch
.text:00488F99                 pop     ebp
.text:00488F9A                 retn
.text:00488F9A _fopen          endp
.text:00488F86 ; FILE *__cdecl fopen(const char *FileName, const char *Mode)
.text:00488F86 _fopen          proc near               ; CODE XREF: sub_42D210+177↑p
.text:00488F86                                         ; sub_42D210+3F2↑p ...
.text:00488F86
.text:00488F86 FileName        = dword ptr  8
.text:00488F86 Mode            = dword ptr  0Ch
.text:00488F86
.text:00488F86                 push    ebp
.text:00488F87                 mov     ebp, esp
.text:00488F89                 push    40h ; '@'       ; ShFlag
.text:00488F8B                 push    [ebp+Mode]      ; Mode
.text:00488F8E                 push    [ebp+FileName]  ; FileName
.text:00488F91                 call    __fsopen
.text:00488F96                 add     esp, 0Ch
.text:00488F99                 pop     ebp
.text:00488F9A                 retn
.text:00488F9A _fopen          endp
var g_base =  0x400000;
 
function hook_fopen_fread() // print fopen and fread to investigate file structor
{
    var memove = new NativeFunction(ptr(g_base + 0x8aa80),
        'void', ["pointer", "pointer", "int"]);
    var sprintf = new NativeFunction(ptr(g_base + 0x89493),
        'int', ["pointer", "pointer", "..."], "mscdecl");
    var fopen = new NativeFunction(ptr(g_base + 0x88F86),
        'pointer', ["pointer", "pointer"]); // in this game, all file function is static link
    var fread = new NativeFunction(ptr(g_base + 0x8B609),
        'size_t', ['pointer', 'size_t', 'size_t', 'size_t']);
    var fseek = new NativeFunction(ptr(g_base + 0x8DAD2),
        'int', ["pointer", "int", "int"]);
    var ftell = new NativeFunction(ptr(g_base + 0x8EEF6),
        'int', ["pointer"]);
    var g_fargs = [];
    Interceptor.attach(fopen, {
        onEnter: function(args)
        {
            g_fargs.push(args[0].readCString());
        },
        onLeave: function(retval)
        {
            var ret_addr = this.context.esp.readPointer();
            var filepath = g_fargs[0];
            if(retval.toInt32()!=0)
            {
                console.log(ret_addr,
                    "fopen",
                    filepath.split('\\')[filepath.split('\\').length-1],
                    "fp=" + retval);
            }
            g_fargs = []
        }
    })
    Interceptor.attach(fread, {
        onEnter: function(args)
        {
            var ret_addr = this.context.esp.readPointer();
            var fp = args[3];
            var offset = ftell(fp);
            console.log(ret_addr,
                "fread(" + args[0]+", " + args[1]+", " + args[2] + ", " + fp + ")",
                "offset=0x" + offset.toString(16));
        }
    })
}
var g_base =  0x400000;
 
function hook_fopen_fread() // print fopen and fread to investigate file structor
{
    var memove = new NativeFunction(ptr(g_base + 0x8aa80),
        'void', ["pointer", "pointer", "int"]);
    var sprintf = new NativeFunction(ptr(g_base + 0x89493),
        'int', ["pointer", "pointer", "..."], "mscdecl");
    var fopen = new NativeFunction(ptr(g_base + 0x88F86),
        'pointer', ["pointer", "pointer"]); // in this game, all file function is static link
    var fread = new NativeFunction(ptr(g_base + 0x8B609),
        'size_t', ['pointer', 'size_t', 'size_t', 'size_t']);
    var fseek = new NativeFunction(ptr(g_base + 0x8DAD2),
        'int', ["pointer", "int", "int"]);
    var ftell = new NativeFunction(ptr(g_base + 0x8EEF6),
        'int', ["pointer"]);
    var g_fargs = [];
    Interceptor.attach(fopen, {
        onEnter: function(args)
        {
            g_fargs.push(args[0].readCString());
        },
        onLeave: function(retval)
        {
            var ret_addr = this.context.esp.readPointer();
            var filepath = g_fargs[0];
            if(retval.toInt32()!=0)
            {
                console.log(ret_addr,
                    "fopen",
                    filepath.split('\\')[filepath.split('\\').length-1],
                    "fp=" + retval);
            }
            g_fargs = []
        }
    })
    Interceptor.attach(fread, {
        onEnter: function(args)
        {
            var ret_addr = this.context.esp.readPointer();
            var fp = args[3];
            var offset = ftell(fp);
            console.log(ret_addr,
                "fread(" + args[0]+", " + args[1]+", " + args[2] + ", " + fp + ")",
                "offset=0x" + offset.toString(16));
        }
    })
}
0x47a51f fopen scenario.arc fp=0x4ca198 // first test the file size
0x47a547 fread(0xd12d2c, 0x1c, 0x1, 0x4ca198) offset=0x0
0x47a796 fread(0xd12d98, 0x3a0, 0x1, 0x4ca198) offset=0x1c
0x47a80f fread(0x80f304, 0x1, 0x1, 0x4ca198) offset=0x14de4a //end
0x47a82a fread(0x80f304, 0x1, 0x1, 0x4ca198) offset=0x14de4b
0x47b705 fopen scenario.arc fp=0x4ca198
0x440cd6 fread(0x80f3b0, 0x10, 0x1, 0x4ca198) offset=0x114dfb
0x440d50 fread(0xba4477c, 0x4, 0x1, 0x4ca198) offset=0x114e0b
0x440d6c fread(0xba44780, 0x4, 0x1, 0x4ca198) offset=0x114e0f
0x440d88 fread(0xba44774, 0x4, 0x1, 0x4ca198) offset=0x114e13
0x440db4 fread(0x766f1f0, 0x28, 0x1, 0x4ca198) offset=0x114e17
0x440dcc fread(0xba44778, 0x4, 0x1, 0x4ca198) offset=0x114e3f
0x440df0 fread(0xb99ac90, 0xeab, 0x1, 0x4ca198) offset=0x114e43 // read mjo content
0x47b705 fopen scenario.arc fp=0x4ca198
0x440cd6 fread(0x80f220, 0x10, 0x1, 0x4ca198) offset=0x12e5d2
0x440d50 fread(0xba446a4, 0x4, 0x1, 0x4ca198) offset=0x12e5e2
0x440d6c fread(0xba446a8, 0x4, 0x1, 0x4ca198) offset=0x12e5e6
0x440d88 fread(0xba4469c, 0x4, 0x1, 0x4ca198) offset=0x12e5ea
0x440db4 fread(0xb9654b8, 0x570, 0x1, 0x4ca198) offset=0x12e5ee
0x440dcc fread(0xba446a0, 0x4, 0x1, 0x4ca198) offset=0x12eb5e
0x440df0 fread(0xbbb9850, 0x17e39, 0x1, 0x4ca198) offset=0x12eb62
0x47b705 fopen scenario.arc fp=0x4ca198
0x440cd6 fread(0x80f220, 0x10, 0x1, 0x4ca198) offset=0xea802
0x440d50 fread(0xba4318c, 0x4, 0x1, 0x4ca198) offset=0xea812
0x440d6c fread(0xba43190, 0x4, 0x1, 0x4ca198) offset=0xea816
0x440d88 fread(0xba43184, 0x4, 0x1, 0x4ca198) offset=0xea81a
0x440db4 fread(0x76774c8, 0x10, 0x1, 0x4ca198) offset=0xea81e
0x440dcc fread(0xba43188, 0x4, 0x1, 0x4ca198) offset=0xea82e
0x440df0 fread(0xbb95f08, 0x11ea, 0x1, 0x4ca198) offset=0xea832
0x47a51f fopen scenario.arc fp=0x4ca198 // first test the file size
0x47a547 fread(0xd12d2c, 0x1c, 0x1, 0x4ca198) offset=0x0
0x47a796 fread(0xd12d98, 0x3a0, 0x1, 0x4ca198) offset=0x1c
0x47a80f fread(0x80f304, 0x1, 0x1, 0x4ca198) offset=0x14de4a //end
0x47a82a fread(0x80f304, 0x1, 0x1, 0x4ca198) offset=0x14de4b
0x47b705 fopen scenario.arc fp=0x4ca198
0x440cd6 fread(0x80f3b0, 0x10, 0x1, 0x4ca198) offset=0x114dfb
0x440d50 fread(0xba4477c, 0x4, 0x1, 0x4ca198) offset=0x114e0b
0x440d6c fread(0xba44780, 0x4, 0x1, 0x4ca198) offset=0x114e0f
0x440d88 fread(0xba44774, 0x4, 0x1, 0x4ca198) offset=0x114e13
0x440db4 fread(0x766f1f0, 0x28, 0x1, 0x4ca198) offset=0x114e17
0x440dcc fread(0xba44778, 0x4, 0x1, 0x4ca198) offset=0x114e3f
0x440df0 fread(0xb99ac90, 0xeab, 0x1, 0x4ca198) offset=0x114e43 // read mjo content
0x47b705 fopen scenario.arc fp=0x4ca198
0x440cd6 fread(0x80f220, 0x10, 0x1, 0x4ca198) offset=0x12e5d2
0x440d50 fread(0xba446a4, 0x4, 0x1, 0x4ca198) offset=0x12e5e2
0x440d6c fread(0xba446a8, 0x4, 0x1, 0x4ca198) offset=0x12e5e6
0x440d88 fread(0xba4469c, 0x4, 0x1, 0x4ca198) offset=0x12e5ea
0x440db4 fread(0xb9654b8, 0x570, 0x1, 0x4ca198) offset=0x12e5ee
0x440dcc fread(0xba446a0, 0x4, 0x1, 0x4ca198) offset=0x12eb5e
0x440df0 fread(0xbbb9850, 0x17e39, 0x1, 0x4ca198) offset=0x12eb62
0x47b705 fopen scenario.arc fp=0x4ca198
0x440cd6 fread(0x80f220, 0x10, 0x1, 0x4ca198) offset=0xea802
0x440d50 fread(0xba4318c, 0x4, 0x1, 0x4ca198) offset=0xea812
0x440d6c fread(0xba43190, 0x4, 0x1, 0x4ca198) offset=0xea816
0x440d88 fread(0xba43184, 0x4, 0x1, 0x4ca198) offset=0xea81a
0x440db4 fread(0x76774c8, 0x10, 0x1, 0x4ca198) offset=0xea81e
0x440dcc fread(0xba43188, 0x4, 0x1, 0x4ca198) offset=0xea82e
0x440df0 fread(0xbb95f08, 0x11ea, 0x1, 0x4ca198) offset=0xea832
 
 
 
scenario.arc, header size: 1C
0~0x10 MajiroArcV3.000
0x10~0x1C  index_count 4, name_table_offset 4, frist_mjo_offset 4
          // 41 00 00 00 2C 04 00 00 AA 07 00 00
0x1C~0x42C arc_index[index_count] // arc_block_num * 0x10 = 0x410
    | unknow1 4  // hash?
    | unknow2 4
    | mjo_offset 4
    | mjo_size 4
    // CA 91 E5 51 F5 10 EE 87 67 C6 0A 00 7B B7 00 00
0x42C~0x7AA name_table
0x7AA~ mjo[index_count]
 
mjo_entry at 0x114dfb
0x0~0x10  MajiroObjX1.000
0x10~0x1c n1 4, unknow2 4, mjo_block_num 4 // E9 06 00 00 00 00 00 00 05 00 00 00
0x1c~0x44 mjo_block // mjo_block_num*8 = 0x28
0x44~0x48 mjo_size 4
scenario.arc, header size: 1C
0~0x10 MajiroArcV3.000
0x10~0x1C  index_count 4, name_table_offset 4, frist_mjo_offset 4
          // 41 00 00 00 2C 04 00 00 AA 07 00 00
0x1C~0x42C arc_index[index_count] // arc_block_num * 0x10 = 0x410
    | unknow1 4  // hash?
    | unknow2 4
    | mjo_offset 4
    | mjo_size 4
    // CA 91 E5 51 F5 10 EE 87 67 C6 0A 00 7B B7 00 00
0x42C~0x7AA name_table
0x7AA~ mjo[index_count]
 
mjo_entry at 0x114dfb
0x0~0x10  MajiroObjX1.000
0x10~0x1c n1 4, unknow2 4, mjo_block_num 4 // E9 06 00 00 00 00 00 00 05 00 00 00
0x1c~0x44 mjo_block // mjo_block_num*8 = 0x28
0x44~0x48 mjo_size 4
 
0047A537  | 6A 01            | push 1                             |
0047A539  | 8DBE 04020000    | lea edi,dword ptr ds:[esi+204]     | edi:"MajiroArcV3.000", esi+204:"MajiroArcV3.000"
0047A53F  | 6A 1C            | push 1C                            |
0047A541  | 57               | push edi                           | edi:"MajiroArcV3.000"
0047A542  | E8 C2100100      | call <polaris_chs.sub_48B609>      | fread
 
// read scenerio mjo
char *__usercall sub_440AB0@<eax>(int a1@<ebx>, int a2@<edi>, int a3@<esi>, char *FullPath)
{
  char *v4; // ecx
  char *context; // esi
  int v7; // ebx
  int v8; // edx
  int v9; // edx
  FILE *fp; // eax MAPDST
  char *v12; // ecx
  char *v13; // edx
  bool v14; // cf
  char *v15; // ecx
  char *v16; // edx
  void *buf_mjoblock; // eax
  void *buf_mjo; // eax
  char *v19; // ecx
  char v20; // al
  size_t mjo_block_size; // [esp-1Ch] [ebp-32Ch]
  size_t mjo_size; // [esp-1Ch] [ebp-32Ch]
  int v28; // [esp+4h] [ebp-30Ch]
  int v29; // [esp+4h] [ebp-30Ch]
  int v30; // [esp+8h] [ebp-308h]
  char Buffer[255]; // [esp+Ch] [ebp-304h] BYREF
  char v32; // [esp+10Bh] [ebp-205h] BYREF
  char mjo_Filename[512]; // [esp+10Ch] [ebp-204h] BYREF
 
  _splitpath(FullPath, 0, 0, mjo_Filename, 0);
  v4 = &v32;
  while ( *++v4 )
    ;
  strcpy(v4, ".mjo");
  tolower((unsigned __int8 *)mjo_Filename);
  if ( strlen(mjo_Filename) > 0x7F )
    sub_441150(
      "ファイル名[%s]が長すぎます%d文字以内にしてください。",
      (int)mjo_Filename,
      127,
      (int)FullPath,
      v28);
  context = dword_4DC350;
  v7 = 0;
  v30 = 0;
  if ( !dword_4DC350 )
    goto LABEL_12;
  while ( sub_47C550(context, mjo_Filename) )   // strcmp?
  {
    context = (char *)*((_DWORD *)context + 0x2A);
    if ( !context )
    {
LABEL_13:
      context = (char *)try_malloc(0xB0);
      memset(context, 0, 0xB0u);
      while ( 1 )
      {
        if ( sub_47BE30(mjo_Filename) )         // if not find target mjo, to load scenario
          goto LABEL_16;
        sub_47A310("scenario", 0);              // test scenario files
        sub_47A310("scenario9", 0);
        sub_47A310("scenario8", 0);
        sub_47A310("scenario7", 0);
        sub_47A310("scenario6", 0);
        sub_47A310("scenario5", 0);
        sub_47A310("scenario4", 0);
        sub_47A310("scenario3", 0);
        sub_47A310("scenario2", 0);
        sub_47A310("scenario1", 0);
        if ( sub_47BE30(mjo_Filename) )
        {
LABEL_16:
          *((_DWORD *)context + 0x20) = ((int (__cdecl *)(LPCSTR))sub_479FE0)(mjo_Filename);
          *((_DWORD *)context + 0x21) = v9;
          fp = (FILE *)try_fopen(a2, (int)context, mjo_Filename, "rb");// fopen
          if ( fp && fread(Buffer, 0x10u, 1u, fp) == 1 )// MajiroObjV1.000
          {
            v12 = off_4C7ABC[0];
            v13 = Buffer;
            a2 = 12;
            do
            {
              if ( *(_DWORD *)v12 != *(_DWORD *)v13 )
              {
                v15 = off_4C7AC0;
                v16 = Buffer;
                a2 = 12;
                while ( *(_DWORD *)v15 == *(_DWORD *)v16 )
                {
                  v15 += 4;
                  v16 += 4;
                  v14 = (unsigned int)a2 < 4;
                  a2 -= 4;
                  if ( v14 )
                  {
                    v29 = 1;
                    goto LABEL_26;
                  }
                }
                goto LABEL_32;
              }
              v12 += 4;
              v13 += 4;
              v14 = (unsigned int)a2 < 4;
              a2 -= 4;
            }
            while ( !v14 );
            v29 = 0;
LABEL_26:
            if ( fread(context + 0x94, 4u, 1u, fp) == 1 && fread(context + 0x98, 4u, 1u, fp) == 1 )// read n1, n2
            {
              a2 = (int)(context + 0x8C);
              if ( fread(context + 0x8C, 4u, 1u, fp) == 1 )// read mjo_block_num
              {
                buf_mjoblock = try_malloc(8 * *(_DWORD *)a2 + 0x20);// malloc
                mjo_block_size = 8 * *(_DWORD *)a2;
                *((_DWORD *)context + 0x28) = buf_mjoblock;
                if ( fread(buf_mjoblock, mjo_block_size, 1u, fp) == 1 )// read mjo_block
                {
                  a2 = (int)(context + 0x90);
                  if ( fread(context + 0x90, 4u, 1u, fp) == 1 )
                  {
                    buf_mjo = try_malloc(*(_DWORD *)a2 + 0x20);// malloc
                    mjo_size = *(_DWORD *)a2;
                    *((_DWORD *)context + 0x29) = buf_mjo;
                    if ( fread(buf_mjo, mjo_size, 1u, fp) == 1 )
                    {
                      fclose(fp);
                      if ( v29 )
                        sub_478E70(*((__m128i **)context + 0x29), *((_DWORD *)context + 0x24));// decrypt mjo, dword 0x24 is context+0x90
                      v19 = mjo_Filename;
                      do
                      {
                        v20 = *v19++;
                        v19[context - mjo_Filename - 1] = v20;
                      }
                      while ( v20 );
                      *((_DWORD *)context + 0x22) = sub_478E10(context);
                      if ( !v30 )
                      {
                        *((_DWORD *)context + 0x2A) = dword_4DC350;
                        dword_4DC350 = context;
                      }
                      *((_DWORD *)context + 0x27) = sub_43A370(context, *((_DWORD *)context + 0x26));
                      return context;
                    }
                  }
                }
              }
            }
          }
LABEL_32:
          v7 = v30;
        }
        sub_4793F0("MajiroObj : ファイル [%s] の読み込みで失敗しました", (int)FullPath, a2, a3, a1);
        if ( *((_DWORD *)context + 0x28) )
          free(*((void **)context + 0x28));
        if ( *((_DWORD *)context + 0x29) )
          free(*((void **)context + 0x29));
        free(context);
LABEL_12:
        if ( !v7 )
          goto LABEL_13;
      }
    }
  }
  if ( *((_DWORD *)context + 0x20) != ((int (__cdecl *)(LPCSTR))sub_479FE0)(mjo_Filename)
    || *((_DWORD *)context + 0x21) != v8 )
  {
    v7 = 1;
    v30 = 1;
    free(*((void **)context + 0x28));
    free(*((void **)context + 0x29));
    goto LABEL_12;
  }
  return context;
}
0047A537  | 6A 01            | push 1                             |
0047A539  | 8DBE 04020000    | lea edi,dword ptr ds:[esi+204]     | edi:"MajiroArcV3.000", esi+204:"MajiroArcV3.000"
0047A53F  | 6A 1C            | push 1C                            |
0047A541  | 57               | push edi                           | edi:"MajiroArcV3.000"
0047A542  | E8 C2100100      | call <polaris_chs.sub_48B609>      | fread
 
// read scenerio mjo
char *__usercall sub_440AB0@<eax>(int a1@<ebx>, int a2@<edi>, int a3@<esi>, char *FullPath)
{
  char *v4; // ecx
  char *context; // esi
  int v7; // ebx
  int v8; // edx
  int v9; // edx
  FILE *fp; // eax MAPDST
  char *v12; // ecx
  char *v13; // edx
  bool v14; // cf
  char *v15; // ecx
  char *v16; // edx
  void *buf_mjoblock; // eax
  void *buf_mjo; // eax
  char *v19; // ecx
  char v20; // al
  size_t mjo_block_size; // [esp-1Ch] [ebp-32Ch]
  size_t mjo_size; // [esp-1Ch] [ebp-32Ch]
  int v28; // [esp+4h] [ebp-30Ch]
  int v29; // [esp+4h] [ebp-30Ch]
  int v30; // [esp+8h] [ebp-308h]
  char Buffer[255]; // [esp+Ch] [ebp-304h] BYREF
  char v32; // [esp+10Bh] [ebp-205h] BYREF
  char mjo_Filename[512]; // [esp+10Ch] [ebp-204h] BYREF
 
  _splitpath(FullPath, 0, 0, mjo_Filename, 0);
  v4 = &v32;
  while ( *++v4 )
    ;
  strcpy(v4, ".mjo");
  tolower((unsigned __int8 *)mjo_Filename);
  if ( strlen(mjo_Filename) > 0x7F )
    sub_441150(
      "ファイル名[%s]が長すぎます%d文字以内にしてください。",
      (int)mjo_Filename,
      127,
      (int)FullPath,
      v28);
  context = dword_4DC350;
  v7 = 0;
  v30 = 0;
  if ( !dword_4DC350 )
    goto LABEL_12;
  while ( sub_47C550(context, mjo_Filename) )   // strcmp?
  {
    context = (char *)*((_DWORD *)context + 0x2A);
    if ( !context )
    {
LABEL_13:
      context = (char *)try_malloc(0xB0);
      memset(context, 0, 0xB0u);
      while ( 1 )
      {
        if ( sub_47BE30(mjo_Filename) )         // if not find target mjo, to load scenario
          goto LABEL_16;
        sub_47A310("scenario", 0);              // test scenario files
        sub_47A310("scenario9", 0);
        sub_47A310("scenario8", 0);
        sub_47A310("scenario7", 0);
        sub_47A310("scenario6", 0);
        sub_47A310("scenario5", 0);
        sub_47A310("scenario4", 0);
        sub_47A310("scenario3", 0);
        sub_47A310("scenario2", 0);
        sub_47A310("scenario1", 0);
        if ( sub_47BE30(mjo_Filename) )
        {
LABEL_16:
          *((_DWORD *)context + 0x20) = ((int (__cdecl *)(LPCSTR))sub_479FE0)(mjo_Filename);
          *((_DWORD *)context + 0x21) = v9;
          fp = (FILE *)try_fopen(a2, (int)context, mjo_Filename, "rb");// fopen
          if ( fp && fread(Buffer, 0x10u, 1u, fp) == 1 )// MajiroObjV1.000
          {
            v12 = off_4C7ABC[0];
            v13 = Buffer;
            a2 = 12;
            do
            {
              if ( *(_DWORD *)v12 != *(_DWORD *)v13 )
              {
                v15 = off_4C7AC0;
                v16 = Buffer;
                a2 = 12;
                while ( *(_DWORD *)v15 == *(_DWORD *)v16 )
                {
                  v15 += 4;
                  v16 += 4;
                  v14 = (unsigned int)a2 < 4;
                  a2 -= 4;
                  if ( v14 )
                  {
                    v29 = 1;
                    goto LABEL_26;
                  }
                }
                goto LABEL_32;
              }
              v12 += 4;
              v13 += 4;
              v14 = (unsigned int)a2 < 4;
              a2 -= 4;
            }
            while ( !v14 );
            v29 = 0;
LABEL_26:
            if ( fread(context + 0x94, 4u, 1u, fp) == 1 && fread(context + 0x98, 4u, 1u, fp) == 1 )// read n1, n2
            {
              a2 = (int)(context + 0x8C);
              if ( fread(context + 0x8C, 4u, 1u, fp) == 1 )// read mjo_block_num
              {
                buf_mjoblock = try_malloc(8 * *(_DWORD *)a2 + 0x20);// malloc
                mjo_block_size = 8 * *(_DWORD *)a2;
                *((_DWORD *)context + 0x28) = buf_mjoblock;
                if ( fread(buf_mjoblock, mjo_block_size, 1u, fp) == 1 )// read mjo_block
                {
                  a2 = (int)(context + 0x90);
                  if ( fread(context + 0x90, 4u, 1u, fp) == 1 )
                  {
                    buf_mjo = try_malloc(*(_DWORD *)a2 + 0x20);// malloc
                    mjo_size = *(_DWORD *)a2;
                    *((_DWORD *)context + 0x29) = buf_mjo;
                    if ( fread(buf_mjo, mjo_size, 1u, fp) == 1 )
                    {
                      fclose(fp);
                      if ( v29 )
                        sub_478E70(*((__m128i **)context + 0x29), *((_DWORD *)context + 0x24));// decrypt mjo, dword 0x24 is context+0x90
                      v19 = mjo_Filename;
                      do
                      {
                        v20 = *v19++;
                        v19[context - mjo_Filename - 1] = v20;
                      }
                      while ( v20 );
                      *((_DWORD *)context + 0x22) = sub_478E10(context);
                      if ( !v30 )
                      {
                        *((_DWORD *)context + 0x2A) = dword_4DC350;
                        dword_4DC350 = context;
                      }
                      *((_DWORD *)context + 0x27) = sub_43A370(context, *((_DWORD *)context + 0x26));
                      return context;
                    }
                  }
                }
              }
            }
          }
LABEL_32:
          v7 = v30;
        }
        sub_4793F0("MajiroObj : ファイル [%s] の読み込みで失敗しました", (int)FullPath, a2, a3, a1);
        if ( *((_DWORD *)context + 0x28) )
          free(*((void **)context + 0x28));
        if ( *((_DWORD *)context + 0x29) )
          free(*((void **)context + 0x29));
        free(context);
LABEL_12:
        if ( !v7 )
          goto LABEL_13;
      }
    }
  }
  if ( *((_DWORD *)context + 0x20) != ((int (__cdecl *)(LPCSTR))sub_479FE0)(mjo_Filename)
    || *((_DWORD *)context + 0x21) != v8 )
  {
    v7 = 1;
    v30 = 1;
    free(*((void **)context + 0x28));
    free(*((void **)context + 0x29));
    goto LABEL_12;
  }
  return context;
}
char __cdecl sub_478E70(__m128i *buf, unsigned int size)
{
  __m128i *cur; // esi
  __int32 v3; // eax
  signed int v4; // edx
  unsigned int v5; // edi
  unsigned int i; // ecx
  int v7; // ecx
  int v8; // ecx
 
  cur = buf;
  LOBYTE(v3) = sub_479070(0xFFFFFFFF, (int)buf, 0);
  v4 = size;
  if ( size >= 0x400 )
  {
    v5 = size >> 10;
    v4 = -1024 * (size >> 10) + size;
    do
    {
      if ( cur > (__m128i *)&unk_5CB5C4 || (__m128i *)((char *)&cur[63].m128i_u64[1] + 4) < &stru_5CB1C8 )
      {
        v3 = (__int32)&unk_5CB1D8;
        v7 = 0x20;
        do
        {
          v3 += 0x20;
          *cur = _mm_xor_si128(_mm_loadu_si128((const __m128i *)(v3 - 0x30)), _mm_loadu_si128(cur));
          cur[1] = _mm_xor_si128(_mm_loadu_si128((const __m128i *)(v3 - 0x20)), _mm_loadu_si128(cur + 1));
          cur += 2;
          --v7;
        }
        while ( v7 );
      }
      else
      {
        for ( i = 0; i < 256; ++i )
        {
          v3 = stru_5CB1C8.m128i_i32[i];
          cur->m128i_i32[0] ^= v3;
          cur = (__m128i *)((char *)cur + 4);
        }
      }
      --v5;
    }
    while ( v5 );
  }
  if ( v4 > 0 )
  {
    v8 = (char *)&stru_5CB1C8 - (char *)cur;
    do
    {
      LOBYTE(v3) = cur->m128i_i8[v8];
      cur = (__m128i *)((char *)cur + 1);
      cur[-1].m128i_i8[15] ^= v3;
      --v4;
    }
    while ( v4 > 0 );
  }
  return v3;
}
char __cdecl sub_478E70(__m128i *buf, unsigned int size)
{
  __m128i *cur; // esi
  __int32 v3; // eax
  signed int v4; // edx
  unsigned int v5; // edi
  unsigned int i; // ecx
  int v7; // ecx
  int v8; // ecx
 
  cur = buf;
  LOBYTE(v3) = sub_479070(0xFFFFFFFF, (int)buf, 0);
  v4 = size;
  if ( size >= 0x400 )
  {
    v5 = size >> 10;
    v4 = -1024 * (size >> 10) + size;
    do
    {
      if ( cur > (__m128i *)&unk_5CB5C4 || (__m128i *)((char *)&cur[63].m128i_u64[1] + 4) < &stru_5CB1C8 )
      {
        v3 = (__int32)&unk_5CB1D8;
        v7 = 0x20;
        do
        {
          v3 += 0x20;
          *cur = _mm_xor_si128(_mm_loadu_si128((const __m128i *)(v3 - 0x30)), _mm_loadu_si128(cur));
          cur[1] = _mm_xor_si128(_mm_loadu_si128((const __m128i *)(v3 - 0x20)), _mm_loadu_si128(cur + 1));
          cur += 2;
          --v7;
        }
        while ( v7 );
      }
      else
      {
        for ( i = 0; i < 256; ++i )
        {
          v3 = stru_5CB1C8.m128i_i32[i];
          cur->m128i_i32[0] ^= v3;
          cur = (__m128i *)((char *)cur + 4);
        }
      }
      --v5;
    }
    while ( v5 );
  }
  if ( v4 > 0 )
  {
    v8 = (char *)&stru_5CB1C8 - (char *)cur;
    do
    {
      LOBYTE(v3) = cur->m128i_i8[v8];
      cur = (__m128i *)((char *)cur + 1);
      cur[-1].m128i_i8[15] ^= v3;
      --v4;
    }
    while ( v4 > 0 );
  }
  return v3;
}
sub_440AB0
...
00440E9C  | A1 50C34D00      | mov eax,dword ptr ds:[4DC350]      | eax:"SUB_TITLE.MJO", 004DC350:&"SUB_TITLE.MJO"
00440EA1  | 8986 A8000000    | mov dword ptr ds:[esi+A8],eax      | eax:"SUB_TITLE.MJO"
00440EA7  | 8935 50C34D00    | mov dword ptr ds:[4DC350],esi      | 004DC350:&"SUB_TITLE.MJO"
00440EAD  | FFB6 98000000    | push dword ptr ds:[esi+98]         |
00440EB3  | 56               | push esi                           |
00440EB4  | E8 B794FFFF      | call <polaris_chs.sub_43A370>      | sub_43A370
00440EB9  | 83C4 08          | add esp,8                          |
00440EBC  | 8986 9C000000    | mov dword ptr ds:[esi+9C],eax      | eax:"SUB_TITLE.MJO"
00440EC2  | 8B4D FC          | mov ecx,dword ptr ss:[ebp-4]       |
00440EC5  | 8BC6             | mov eax,esi                        | eax:"SUB_TITLE.MJO"
00440EC7  | 5F               | pop edi                            |
00440EC8  | 5E               | pop esi                            |
00440EC9  | 33CD             | xor ecx,ebp                        |
00440ECB  | 5B               | pop ebx                            |
00440ECC  | E8 66950400      | call <polaris_chs.sub_48A437>      |
00440ED1  | 8BE5             | mov esp,ebp                        |
00440ED3  | 5D               | pop ebp                            |
00440ED4  | C3               | ret                                | load mjo end;
sub_440AB0
...
00440E9C  | A1 50C34D00      | mov eax,dword ptr ds:[4DC350]      | eax:"SUB_TITLE.MJO", 004DC350:&"SUB_TITLE.MJO"
00440EA1  | 8986 A8000000    | mov dword ptr ds:[esi+A8],eax      | eax:"SUB_TITLE.MJO"
00440EA7  | 8935 50C34D00    | mov dword ptr ds:[4DC350],esi      | 004DC350:&"SUB_TITLE.MJO"
00440EAD  | FFB6 98000000    | push dword ptr ds:[esi+98]         |
00440EB3  | 56               | push esi                           |
00440EB4  | E8 B794FFFF      | call <polaris_chs.sub_43A370>      | sub_43A370
00440EB9  | 83C4 08          | add esp,8                          |
00440EBC  | 8986 9C000000    | mov dword ptr ds:[esi+9C],eax      | eax:"SUB_TITLE.MJO"
00440EC2  | 8B4D FC          | mov ecx,dword ptr ss:[ebp-4]       |
00440EC5  | 8BC6             | mov eax,esi                        | eax:"SUB_TITLE.MJO"
00440EC7  | 5F               | pop edi                            |
00440EC8  | 5E               | pop esi                            |
00440EC9  | 33CD             | xor ecx,ebp                        |
00440ECB  | 5B               | pop ebx                            |
00440ECC  | E8 66950400      | call <polaris_chs.sub_48A437>      |
00440ED1  | 8BE5             | mov esp,ebp                        |
00440ED3  | 5D               | pop ebp                            |
00440ED4  | C3               | ret                                | load mjo end;
[eax] mjo name , [4DC350] // at 00440ED4
[[eax+0x29*4]] decrypted mjo buf,
[eax+0x24*4] mjo size //雨的边界同理,貌似没什么明显的特征,要手动找函数位置
[eax] mjo name , [4DC350] // at 00440ED4
[[eax+0x29*4]] decrypted mjo buf,
[eax+0x24*4] mjo size //雨的边界同理,貌似没什么明显的特征,要手动找函数位置
function dump_mjo(mjo_name, dump_dir="./dump/") // to dump decrypted mjo
{   
    // better to attach process, after initial, or access violation
    var decrypt_func = new NativeFunction(ptr(g_base + 0x40AB0),
        'pointer', ['pointer'], 'stdcall');
    var name_buf = Memory.alloc(256).writeAnsiString(mjo_name);
    var decrypt_ret = decrypt_func(name_buf);
    let mjo_size = decrypt_ret.add(0x24*4).readU32();
    let mjo_buf  = decrypt_ret.add(0x29*4).readPointer();
    console.log(mjo_name, mjo_buf, mjo_size);
    var fp = new File(dump_dir + mjo_name, "wb");
    fp.write(mjo_buf.readByteArray(mjo_size));
    fp.close()
}
function dump_mjo(mjo_name, dump_dir="./dump/") // to dump decrypted mjo
{   
    // better to attach process, after initial, or access violation
    var decrypt_func = new NativeFunction(ptr(g_base + 0x40AB0),
        'pointer', ['pointer'], 'stdcall');
    var name_buf = Memory.alloc(256).writeAnsiString(mjo_name);
    var decrypt_ret = decrypt_func(name_buf);
    let mjo_size = decrypt_ret.add(0x24*4).readU32();
    let mjo_buf  = decrypt_ret.add(0x29*4).readPointer();
    console.log(mjo_name, mjo_buf, mjo_size);
    var fp = new File(dump_dir + mjo_name, "wb");
    fp.write(mjo_buf.readByteArray(mjo_size));
    fp.close()
}
 
 
a. showtext_screen
004453E9   | 50                   | push eax                                | [[5D1A58]] current text addr in mjo buffer
004453EA   | 53                   | push ebx                                |
004453EB   | 8D85 FCFBFFFF        | lea eax,dword ptr ss:[ebp-404]          |
004453F1   | 50                   | push eax                                |
004453F2   | FF35 E4CC5200        | push dword ptr ds:[52CCE4]              | 0052CCE4:"煨R"
004453F8   | 68 90065300          | push polaris_chs.530690                 |
004453FD   | E8 1ED4FFFF          | call <polaris_chs.sub_442820>           | showtext_screen
 
b. move to next text
0043A750  | 8B15 581A5D00                 | mov edx,dword ptr ds:[5D1A58]      | edx:&"1"
0043A756  | 8B0A                          | mov ecx,dword ptr ds:[edx]         | [edx]:"1"
0043A758  | 0FBF01                        | movsx eax,word ptr ds:[ecx]        | get_text_len
0043A75B  | 83C1 02                       | add ecx,2                          | move to text
0043A75E  | 890A                          | mov dword ptr ds:[edx],ecx         | [edx]:"1"
0043A760  | C3                            | ret                                |
 
c. preshow_text
00445140  | 55               | push ebp                           |
00445141  | 8BEC             | mov ebp,esp                        |
00445143  | 81EC 180C0000    | sub esp,C18                        |
00445149  | A1 10A04C00      | mov eax,dword ptr ds:[4CA010]      |
0044514E  | 33C5             | xor eax,ebp                        |
00445150  | 8945 FC          | mov dword ptr ss:[ebp-4],eax       |
00445153  | 8B0D E4CC5200    | mov ecx,dword ptr ds:[52CCE4]      | [52cce4] text
00445159  | 85C9             | test ecx,ecx                       |
0044515B  | 74 19            | je polaris_chs.445176              |
0044515D  | 8039 00          | cmp byte ptr ds:[ecx],0            |
00445160  | 75 24            | jne polaris_chs.445186             |
00445162  | C781 00040000 00 | mov dword ptr ds:[ecx+400],0       |
0044516C  | C705 E4CC5200 00 | mov dword ptr ds:[52CCE4],0        | 0052CCE4:"杼R"
00445176  | 33C0             | xor eax,eax                        |
00445178  | 8B4D FC          | mov ecx,dword ptr ss:[ebp-4]       |
0044517B  | 33CD             | xor ecx,ebp                        |
0044517D  | E8 B5520400      | call <polaris_chs.sub_48A437>      |
00445182  | 8BE5             | mov esp,ebp                        |
00445184  | 5D               | pop ebp                            |
00445185  | C3               | ret                                |
00445186  | 8B15 581A5D00    | mov edx,dword ptr ds:[5D1A58]      | [[5D1A58]] current text addr in mjo buffer
0044518C  | A1 94CB4D00      | mov eax,dword ptr ds:[4DCB94]      |
00445191  | 53               | push ebx                           |
00445192  | 33DB             | xor ebx,ebx                        |
00445194  | 3B02             | cmp eax,dword ptr ds:[edx]         |
00445196  | 74 1D            | je polaris_chs.4451B5              |
00445198  | 53               | push ebx                           | extra always 0 ?
00445199  | 51               | push ecx                           | buf
0044519A  | E8 C1D5FFFF      | call <polaris_chs.sub_442760>      | show_text
 
d. memove, copy string to showbuf
00445BA0  | C780 E8D05200 01000000        | mov dword ptr ds:[eax+52D0E8],1    |
00445BAA  | 8D80 E8CC5200                 | lea eax,dword ptr ds:[eax+52CCE8]  | eax:L"簀簀簀簀簀簀簀簀簀"
00445BB0  | 8B35 581A5D00                 | mov esi,dword ptr ds:[5D1A58]      | 5D1A58, mjo decrypt text(some of)
00445BB6  | 53                            | push ebx                           | size
00445BB7  | A3 E4CC5200                   | mov dword ptr ds:[52CCE4],eax      | write 52cce4
00445BBC  | FF36                          | push dword ptr ds:[esi]            | src: [esi] mjo decrypt text
00445BBE  | 50                            | push eax                           | dst: 52cce4, show test
00445BBF  | E8 BC4E0400                   | call <polaris_chs.sub_48AA80>      | memmove
00445BC4  | 83C4 0C                       | add esp,C                          |
00445BC7  | 011E                          | add dword ptr ds:[esi],ebx         |
00445BC9  | 5E                            | pop esi                            |
00445BCA  | 5B                            | pop ebx                            |
00445BCB  | C3                            | ret                                |
a. showtext_screen
004453E9   | 50                   | push eax                                | [[5D1A58]] current text addr in mjo buffer
004453EA   | 53                   | push ebx                                |
004453EB   | 8D85 FCFBFFFF        | lea eax,dword ptr ss:[ebp-404]          |
004453F1   | 50                   | push eax                                |
004453F2   | FF35 E4CC5200        | push dword ptr ds:[52CCE4]              | 0052CCE4:"煨R"
004453F8   | 68 90065300          | push polaris_chs.530690                 |
004453FD   | E8 1ED4FFFF          | call <polaris_chs.sub_442820>           | showtext_screen
 
b. move to next text
0043A750  | 8B15 581A5D00                 | mov edx,dword ptr ds:[5D1A58]      | edx:&"1"
0043A756  | 8B0A                          | mov ecx,dword ptr ds:[edx]         | [edx]:"1"
0043A758  | 0FBF01                        | movsx eax,word ptr ds:[ecx]        | get_text_len
0043A75B  | 83C1 02                       | add ecx,2                          | move to text
0043A75E  | 890A                          | mov dword ptr ds:[edx],ecx         | [edx]:"1"
0043A760  | C3                            | ret                                |
 
c. preshow_text
00445140  | 55               | push ebp                           |
00445141  | 8BEC             | mov ebp,esp                        |
00445143  | 81EC 180C0000    | sub esp,C18                        |
00445149  | A1 10A04C00      | mov eax,dword ptr ds:[4CA010]      |
0044514E  | 33C5             | xor eax,ebp                        |
00445150  | 8945 FC          | mov dword ptr ss:[ebp-4],eax       |
00445153  | 8B0D E4CC5200    | mov ecx,dword ptr ds:[52CCE4]      | [52cce4] text
00445159  | 85C9             | test ecx,ecx                       |
0044515B  | 74 19            | je polaris_chs.445176              |
0044515D  | 8039 00          | cmp byte ptr ds:[ecx],0            |
00445160  | 75 24            | jne polaris_chs.445186             |
00445162  | C781 00040000 00 | mov dword ptr ds:[ecx+400],0       |
0044516C  | C705 E4CC5200 00 | mov dword ptr ds:[52CCE4],0        | 0052CCE4:"杼R"
00445176  | 33C0             | xor eax,eax                        |
00445178  | 8B4D FC          | mov ecx,dword ptr ss:[ebp-4]       |
0044517B  | 33CD             | xor ecx,ebp                        |
0044517D  | E8 B5520400      | call <polaris_chs.sub_48A437>      |
00445182  | 8BE5             | mov esp,ebp                        |
00445184  | 5D               | pop ebp                            |
00445185  | C3               | ret                                |
00445186  | 8B15 581A5D00    | mov edx,dword ptr ds:[5D1A58]      | [[5D1A58]] current text addr in mjo buffer
0044518C  | A1 94CB4D00      | mov eax,dword ptr ds:[4DCB94]      |
00445191  | 53               | push ebx                           |
00445192  | 33DB             | xor ebx,ebx                        |
00445194  | 3B02             | cmp eax,dword ptr ds:[edx]         |
00445196  | 74 1D            | je polaris_chs.4451B5              |
00445198  | 53               | push ebx                           | extra always 0 ?
00445199  | 51               | push ecx                           | buf

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

收藏
免费 11
支持
分享
最新回复 (9)
雪    币: 4
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
2
博主让我学到了新知识,点赞!
2021-7-18 22:40
1
雪    币: 4911
活跃值: (4597)
能力值: ( LV10,RANK:171 )
在线值:
发帖
回帖
粉丝
3
感谢分享。
boost有regex.h,记得detours不需要依赖boost库吧
2021-7-19 10:12
0
雪    币: 6296
活跃值: (4962)
能力值: ( LV12,RANK:250 )
在线值:
发帖
回帖
粉丝
4
coneco 感谢分享。 boost有regex.h,记得detours不需要依赖boost库吧
是的,detours不依赖boost库。我的意思是说我想同时用regex.h和detours,但是regex.h是mingw环境的,而detours没有用mingw编译的版本,这两个没法同时链接。
2021-7-19 12:20
0
雪    币: 1641
活跃值: (3601)
能力值: (RANK:15 )
在线值:
发帖
回帖
粉丝
5
很有精神
2021-7-19 12:23
0
雪    币: 8435
活跃值: (18395)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
6
谢谢分享
2021-7-23 07:34
0
雪    币: 4911
活跃值: (4597)
能力值: ( LV10,RANK:171 )
在线值:
发帖
回帖
粉丝
7
devseed 是的,detours不依赖boost库。我的意思是说我想同时用regex.h和detours,但是regex.h是mingw环境的,而detours没有用mingw编译的版本,这两个没法同时链接。

那如果用boost的regex.h,那应该可以不用mingw,然后避过mingw吧,不知道我这么想可以吗?

最后于 2021-7-23 15:36 被coneco编辑 ,原因:
2021-7-23 15:28
0
雪    币: 1556
活跃值: (2297)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
呐喊
2021-7-23 15:33
0
雪    币: 360
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
9
大佬,看了你这么多篇教程,是不是可以理解为所有游戏都能通过HOOK注入的方式来汉化,不管是否有内核文本,我目前汉化了3款游戏只成功了1次,这一次还是在找到游戏文本的情况下汉化成功的,其他2个不是找不到文本就是文本用XML格式储存的被加密了,不解密就无解
2021-8-10 16:12
0
雪    币: 6296
活跃值: (4962)
能力值: ( LV12,RANK:250 )
在线值:
发帖
回帖
粉丝
10
迷茫by蝼蚁 大佬,看了你这么多篇教程,是不是可以理解为所有游戏都能通过HOOK注入的方式来汉化,不管是否有内核文本,我目前汉化了3款游戏只成功了1次,这一次还是在找到游戏文本的情况下汉化成功的,其他2个不是找不到 ...
理论上可以,但是动态汉化调试起来比较麻烦,兼容性和稳定性需要测试
2021-8-11 20:59
0
游客
登录 | 注册 方可回帖
返回
//