galwiki吧 关注:308贴子:4,802
  • 12回复贴,共1

imperfect blue机翻补丁制作记录

只看楼主收藏回复

本来在g吧的也搬到这边来


IP属地:北京1楼2024-11-24 15:12回复
    制作补丁的第一步是提取文本。
    程序想要将文本显示在屏幕上,需要通过一系列的命令。我们可以在它运行时修改这些命令,改成让在它显示文字的同时向我指定的内存空间写入。
    通常而言,galgame引擎不太可能自己写出最底层的实现,而是调用公开的windows库和c库中的函数。使用x64dbg打开程序,点击“符号”,我们可以看到程序调用了这些函数。它们的作用可以通过阅读文档了解。

    FT_GetCharIndex 是 FreeType 库中的一个函数,用于根据给定的字符代码获取对应的字形索引。我推测它与文本显示有关,因此我在函数开头下断点。


    IP属地:北京2楼2024-11-24 15:15
    收起回复
      先打开游戏,而后在点击开始(显示文本)前,x64dbg左上角文件>附加。注意32位程序要用32位dbg,64位要用64位。如果点附加没找到游戏进程,看看是不是用错了。

      附加之后开始游戏。碰到断点停下。在调用函数(使用call命令)时,返回地址会被压入栈。因此,可以通过观察栈,追踪何处发起了对函数的调用。

      于是我们来到C3ED48下断点(由于动态基址,每次运行发起调用的位置可能是不一样的)


      IP属地:北京4楼2024-11-24 22:39
      回复
        观察每次下断点时程序的行为。最终我们期望能找到某个函数(或者其实应该说是,proc?),其输入参数为屏幕上显示的字符串或指向字符串的指针(一段内存区域的标号,那段内存区域存着字符串)。
        在这里。

        Push是推的意思,程序常常将函数的参数压入栈来传递参数,因此我们仔细观察这几个参数,edi,esi,ebx。

        右键内存窗口,转到14a22ce8

        0通常作为字符串的结尾。这里的字节都是E开头推测可能是utf8编码,试试是不是。右键内存区域,十六进制->代码页->utf8。发现正好是开头的“月人母”。

        这样我们就找到了文本指针,在strlen执行前的ebx中。接下来要在每次执行这个函数时,导出文本。


        IP属地:北京5楼2024-11-24 22:43
        回复
          Detours是微软开发的一款注入工具。在将动态链接库(dll)注入程序后,其将执行detours操作,将指定位置开始的五字节改为跳转到用户定义函数的jmp命令,而被覆盖的命令多于五字节的部分使用0xCC(int3指令)填充。
          用来把dll注入游戏程序的程序(有点绕)大致是个启动器,通过游戏程序名字在所有进程中搜索它,如果搜不到就启动它。我让gpt写的自己没太懂,所以不说了。这里写出来是说需要这么个启动器。
          我们要勾取的是调用这个strlen函数的函数,它没有名字,所以需要使用字节特征匹配。也就是在程序中搜索这段函数的二进制码。搜索函数我也让gpt帮我写了。

          勾取部分大概长这样。

          其中hook_addr为将要勾取的地址,HookedFuction是我们自己写的导出函数。VitualProtect打开那段内存空间的读写权限否则有bug。在begin-attach-commit后,指定位置才会替换成我们的自定义函数。


          IP属地:北京6楼2024-11-24 22:49
          回复
            自定义一个裸汇编函数,其中调用一个命名的导出函数。Stdcall约定的函数会在返回前自己清理栈,就不用在汇编里另外清理了。


            为了防止原本栈中的数据被覆盖,通过改变esp为自定义函数创建自己的栈。而后将ebx作为导出函数参数压入栈。导出函数接收ebx中的字符串指针(一段内存空间的标号)而后补上被jmp命令覆盖的原始命令。des_addr为返回地址,待会说。


            IP属地:北京7楼2024-11-24 22:52
            回复


              IP属地:山东来自iPhone客户端8楼2024-11-24 22:54
              回复
                如果成功hook进去效果应该如图。可以看到jmp后,我们需要返回jmp命令的5字节+1字节int3处,一共6字节。所以des_addr = hook_addr+6。

                而后把游戏开auto或者C一遍(不过这个游戏很神奇禁止未读C,我认为这是对文本的一种自信)就可以把文本提出来了,类似krkrzextract?
                不过事实上这做的很粗糙。Suika2引擎是有解包软件的,而且我也看到了疑似免封包的段落。所以更漂亮的做法是观察汇编代码找到它的加密方式,反向解密得到完整包,然后解包,但果然还是好难...下次我一定试试。
                此外,如果想要提高翻译质量,应该把人名和对话分开。这作人少好分开,但应该在名字显示时和对话显示时分别勾取更好。


                IP属地:北京9楼2024-11-24 22:57
                回复
                  之后把文本转成机翻工具可以接受的形式,比如json。然后开始翻译吧,我用的galtrasl。
                  怎么感觉ainiee好像有bug...sakura的接口好像没有这个v1来着,不过更可能是我的问题(


                  IP属地:北京10楼2024-11-24 22:58
                  回复
                    翻译好后我们将原文和译文做成哈希表。哈希表的作用是,给定原文,能在与总语句数无关的常数次搜索内,找到译文。

                    这之后,我们需要设置自定义函数,接受指向原文的指针,改成指向译文的指针。内嵌回去时的勾取位置更需要仔细选取。因为提取文本只需要找到一个参数是文本指针的位置,但最终文本的显示可能由多处命令决定。即使修改了刚才那一处,若别的地方仍然压入原文本指针,最终仍会显示原文本。
                    不过直接勾取诸如textoutA等,输入为字符串指针的库函数我觉得也没问题。但是这作底层实现输入似乎是单个字符,而我不可能建立单字符对应的的哈希表,比如,一个あ,根据上下文可能会被翻译成很多不同的东西,(あかねー>茜色,あおぞらー>青空)


                    IP属地:北京11楼2024-11-24 22:59
                    回复
                      再次进行追踪。期望找到某个函数,每次即将加载文字时触发一次断点。它就很有可能是文本显示函数。
                      它的前一个断点在点击一次屏幕后触发多次,是单字处理函数;后一个断点即使什么都不做也不断触发,是监听函数。
                      最终大致是在这个位置。这里并没有完全追踪call,还稍微往上追溯到了循环之前。可以通过右键调出流程图更方便的观察程序结构。

                      DBB230是一个确定的位置。很可能存放着重要的信息,比如密钥。查看后发现是字符串指针。同样,DBB270存放着字符串长度。找到位置后和提取时一样,写一个自定义函数,接收原文指针,找到译文,而后改变两个位置内存放的指针。注意如果出现了一些原文之外的意外字符就不改变它。


                      但是,事实上,这两个位置在每次游戏中不变,重新打开会变。因此,我们需要模糊匹配,在每次打开游戏时动态获取这两个位置。


                      IP属地:北京12楼2024-11-24 23:09
                      回复
                        这样,补丁就做好了。


                        IP属地:北京13楼2024-11-24 23:11
                        回复