lies2014 发表于 2024-6-10 17:10

3DNes字符串汉化不成功的问题定位解决

本帖最后由 lies2014 于 2024-6-10 20:59 编辑

3DNes字符串汉化不成功的问题定位解决

这篇文章的起因是来自 shaokui123 在论坛的求助帖:
https://www.52pojie.cn/thread-1927454-1-1.html
(原帖附件火绒报毒并成功杀毒,我在百度云盘传了一份杀过毒的以供练手:
链接:https://pan.baidu.com/s/1QD-ffT2LAalOoE7jXxaYQw 提取码:0fkp)

shaokui123 在汉化 3DNes 的过程中遇到两处汉化不成功的字符串(位于文件“\3DNes\3dnes_Data\level0”中):
1、文件偏移 0x1E7A0 处的字符串“Depth”
2、文件偏移 0x197B8 处的字符串“Open”

一、“Depth”汉化
需要汉化为两个字的 UTF8 编码汉字,占用 6 各字节
字符串会有长度信息和不带长度信息两种,前者根据长度信息读取相应位数的字符串,后者遇到0才停止读取
再看看字符串之前4个字节,“05”这个数字正好是字符串的长度,猜想有可能是长度信息,把它改成“06”试试,果然就成功了。这处比较简单,下一处就有点麻烦了



二、“Open”汉化
这个字符串藏得比较深,打开程序(注意勾选窗口化,否则全屏了),点击“Play”按键,再点击“Open”按键,下一界面出现的“Open”按键才是汉化不成功的目标字符串
这个字符串之前也有长度信息,但改过之后,整个按键的文字都不显示了,猜测还有地方限制了显示长度
这个软件是个 Unity 程序,试过用 AssetStudio 查找资源,也没有头绪,看来还得硬钢
首先想到的是要找到这个字符串出现的代码位置,再跟踪程序是如何处理这个字符串的,看看是否有线索
(“Open”在文件中出现两次,为了便于分析,把目标字符串改成了“1pen”)
1、先在内存中搜索“1pen”这串字符,找不到,改成搜索 Unicode,这次倒是搜到了,但这已经是转换过的,不是数据来源
考虑到字符串保存在文件“level0”中,要使用肯定要先读取
用调试器 x32dbg 载入主程序,在 API ReadFile 上下断点,运行至断下后回溯到用户代码
00B491BD call dword ptr ds:[<ReadFile>]      
00B491C3 mov esi,eax      

发现读取了很多不同的文件,“level0”也是分段多次读取,怎样才准确定位到“1pen”读入的时机呢?
在地址 0xB491C3 下断点(此时 ReadFile 已执行完毕将文件读入内存),并编辑断点条件如下:
暂停条件:$result>0 (找到结果暂停)
命令:findallmem 0,"31 70 65 6E" (内存查找“1pen”)
命令条件:strstr(utf8(ebx),"level0" (文件名包含“level0”)
运行后一会儿成功断下,查看内存,字符串“1pen”已经读入

2、在字符串“1pen”上设置硬件访问断点,继续执行后断下,但断下的位置看不出端倪
我们来到“堆栈调用”标签中查看堆栈回溯,看到经过了多级调用才来到断点位置

地址返回到返回自大小方注释
1142FACCB28852010CF6D824用户模块3dnes.UnwindUp1+4
1142FAF0A60CB700B288521C用户模块3dnes.CachedReader::Read+22
1142FB0CC72C5D00A60CB734用户模块3dnes.StreamedBinaryRead<0>::TransferSTLStyleArray+67
1142FB40C823A500C72C5D18用户模块3dnes.TransferField_NonArray<StreamedBinaryRead<0>,Converter_String>+2D
1142FB58FED40000C823A52C用户模块3dnes.Transfer_String<StreamedBinaryRead<0>,0>+15
1142FB84C8465100FED40064用户模块3dnes.ExecuteSerializationCommands<ProxyTransfer>+50
1142FBE8BF38EB00C8465124用户模块3dnes.TransferScriptingObject<StreamedBinaryRead<0> >+B1
1142FC0CCABBBC00BF38EBDC用户模块3dnes.MonoBehaviour::TransferEngineAndInstance<StreamedBinaryRead<0> >+5B
1142FCE8CA6E8B00CABBBCB4用户模块3dnes.SerializedFile::ReadObject+26C
1142FD9CC8794F00CA6E8BC4用户模块3dnes.PersistentManager::LoadFileCompletelyThreaded+2EB
1142FE60C859F100C8794F18用户模块3dnes.LoadSceneOperation::Perform+2AF
1142FE78C85A7B00C859F18用户模块3dnes.PreloadManager::Run+A1
1142FE80CC7D5D00C85A7B18用户模块3dnes.PreloadManager::Run+B
1142FE984BFA2900CC7D5D10系统模块3dnes.Thread::RunThreadWrapper+2D
1142FEA88A7A9E764BFA295C系统模块kernel32.@BaseThreadInitThunk@12+19
1142FF048A7A6E778A7A9E10系统模块ntdll.__RtlUserThreadStart+2F
1142FF14000000778A7A6E
用户模块ntdll.__RtlUserThreadStart@8+1B

以上堆栈记录的调用都是公共函数,经过多次执行才来到我们设置的断点,我们现在需要准确定位到“1pen”这个字符串的处理流程

在堆栈调用中点击应用程序空间的每一级调用,返回地址的上一句调用 call 上下断点,从上至下注释好调用层级,设置中断条件为“0”(不中断只记录触发次数)


3、取消步骤 2 的硬件断点,重启程序,执行至步骤 1 的断点,在字符串“1pen”上设置硬件访问断点,继续执行至断下
此时查看断点清单,1-8 级调用均有命中,并记录下了命中次数,先取消未命中断点和步骤 1 的断点
然后用这个命中次数为调用 call 设置点断条件(比如 1 级命中 4156 次设置断点条件 $breakpointcounter==.4156)
这样设置后确保调用 call 断下的时候正在处理目标字符串,设置完后我的机器上断点清单如下:

地址反汇编命中摘要(前面数字是注释的调用层级)
00A60CB2call <3dnes.public: void __thiscall StreamedBinaryRead<0>::ReadDirect(void *, int)>11142, 暂停条件($breakpointcounter==.1114)
00B2884Dcall <3dnes._memcpy>41561, 暂停条件($breakpointcounter==.4156)
00BF38E6call <3dnes.void __cdecl TransferScriptingObject>317, 暂停条件($breakpointcounter==.31)
00C72C58call <3dnes.public: void __thiscall StreamedBinaryRead<0>::TransferSTLStyleArray>693, 暂停条件($breakpointcounter==.69)
00C823A0call <3dnes.void __cdecl TransferField_NonArray>694, 暂停条件($breakpointcounter==.69)
00C8464Ccall <3dnes.void __cdecl ExecuteSerializationCommands>316, 暂停条件($breakpointcounter==.31)
00CABBBAcall edx16318, 暂停条件($breakpointcounter==.1631)
00FED3FEcall eax6785, 暂停条件($breakpointcounter==.678)


4、接着我们来看看将字符串长度改变会发生什么,将字符串长度信息由“04”改为“06”,字符串“1pen”改为“1penen”

重新载入程序,一路执行,发现执行到第 1 级调用 0xB2884D 的 call <3dnes._memcpy> 时出现问题了
本该内存拷贝复制的目标字符串没有出现,是哪里出了问题呢?
重启程序,到第 2 级调用 call 的时候 F7 单步进入第 1 级段首
00B28830 push ebp      
00B28831 mov ebp,esp      
00B28833 sub esp,0x8      
00B28836 push ebx      
00B28837 mov ebx,dword ptr ss:      ;字符串长度
00B2883A push edi      
00B2883B mov edi,ecx      ;edi指向字符串地址
00B2883D mov eax,dword ptr ds:      ;字符串地址
00B2883F lea ecx,dword ptr ds:      ;字符串最后位+1的地址
00B28842 cmp ecx,dword ptr ds:      
00B28845 ja 3dnes.B2885F      改过会跳过
00B28847 mov edx,dword ptr ss:      
00B2884A push ebx      
00B2884B push eax      
00B2884C push edx      
00B2884D call <3dnes._memcpy>
00B28852 add esp,0xC      
00B28855 add dword ptr ds:,ebx      
00B28857 pop edi      
00B28858 pop ebx      
00B28859 mov esp,ebp      
00B2885B pop ebp      
00B2885C ret 0x8      
00B2885F push esi      

单步执行下来,执行到 0xB28842 将字符串最后位+1 的地址和 ds: 保存的地址进行比较
ds: 保存的地址指向的是“1penen”的第 5 位,刚好是我们修改增加后的内容,这里很可疑
我们将 ds: 保存的地址增加 2,即指向“1penen”地址+6 的地方,继续执行后成功显示了

5、现在我们要找到 ds: 的数据来源,按数据产生的流程当然要从最高层级开始分析
首先在第 8 级调用 call 的段首设置条件断点,断点条件跟第 8 级调用 call 一样
然后重启程序,执行到第 8 级调用 call 的段首断下,此时“1penen”已经在内存中
找到“1penen”地址,因为步骤 4 中错误的 ds: 地址为“1penen”地址+4,我们在内存中搜索这个地址
现在还找不到这个地址,因为还没有开始处理(注意这个地址在内存中是倒序存放的(小端存储))
我们单步向下执行,遇到 call 步过,然后搜索一下内存是否出现上面的错误地址
执行到 0xCABB63 call <3dnes.public: void __thiscall CachedReader::InitRead 后目标地址出现了

6、继续向下分析“1penen”地址+4 的来源,重启程序,执行到0xCABB63 的时候 F7 单步进入分析
00B28653 mov ecx,dword ptr ss:      ;定位数据,原为0x104,修改为6位应为0x106
00B28656 lea edx,dword ptr ds:      
00B28659 mov ecx,dword ptr ds:      
00B2865C mov dword ptr ds:,edx      
00B2865F lea edi,dword ptr ds:      
00B28662 push edi      
00B28663 lea edx,dword ptr ds:      
00B28666 mov dword ptr ds:,ebx      
00B28669 push edx      
00B2866A mov dword ptr ds:,eax      
00B2866D mov ecx,dword ptr ds:      
00B2866F push eax      
00B28670 mov eax,ecx      
00B28672 mov ecx,dword ptr ds:      
00B28675 mov eax,dword ptr ds:      
00B28678 call eax      
00B2867A mov ecx,dword ptr ds:      
00B2867D imul ecx,dword ptr ds:      
00B28681 mov eax,dword ptr ds:      
00B28684 sub eax,ecx      
00B28686 add eax,dword ptr ds:      ;字符串最后位+1地址(“1penen”地址+4)

执行到 0xB28686 的时候出现了目标地址,经分析,这个地址是用 0xB28653 的数据经过计算得来
0xB28653 的数据是个偏移,与载入内存的地址计算得出绝对地址,我们姑且称它为“定位数据”

这个“定位数据”来自上级 call 的第 4 个参数,还得回到上一级来分析
(这里的分析没有什么好讲的,纯粹是硬啃汇编指令,结合动态分析会更容易一些)

7、重启程序来到第 8 级调用 call 的段首,从 0xCABB63 开始向上看
00CAB97C call <3dnes.struct std::pair<__int64, struct SerializedFile::ObjectInfo> const * __cdecl std::_Lower_bound<struct std::pair<__int64, struct SerializedFile::ObjectInfo> const *, __int64, int, class vector_map<__int64, struct SerializedFile::ObjectInfo, struct std::les>      ;读入字符串和定位数据
00CAB981 add esp,0x14      
00CAB984 cmp eax,esi      
00CAB986 je 3dnes.CAB99D      
00CAB988 mov edx,dword ptr ss:      
00CAB98B cmp edx,dword ptr ds:      
00CAB98E jl 3dnes.CAB99D      
00CAB990 jg 3dnes.CAB999      
00CAB992 mov ecx,dword ptr ss:      
00CAB995 cmp ecx,dword ptr ds:      
00CAB997 jb 3dnes.CAB99D      
00CAB999 mov edi,eax      定位数据=
00CAB99B jmp 3dnes.CAB99F      
00CAB99D mov edi,esi      
00CAB99F cmp edi,esi      
00CAB9A1 je 3dnes.CABC00      
00CAB9A7 lea eax,dword ptr ds:      
00CAB9AA push eax      
00CAB9AB mov ecx,ebx      
00CAB9AD call <3dnes.public: class SerializedFile::Type & __thiscall std::map<int, class SerializedFile::Type, struct std::less<int>, class std::allocator<struct std::pair<int const, class SerializedFile::Type>>>::operator[](int const &)>      
00CAB9B2 cmp byte ptr ds:,0x0      
00CAB9B6 mov esi,dword ptr ss:      
00CAB9B9 mov dword ptr ss:,eax      
00CAB9BC je 3dnes.CABA30      
00CAB9BE cmp dword ptr ds:,0xFFFFFFFF      
00CAB9C2 jne 3dnes.CABA30      
00CAB9C4 mov edx,dword ptr ds:      
00CAB9C7 shr edx,0x15      
00CAB9CA cmp edx,dword ptr ds:      
00CAB9CD jne 3dnes.CABA30      
00CAB9CF push 0x6DF      
00CAB9D4 push <3dnes."">      
00CAB9D9 push 0x10      
00CAB9DB push 0x4B      
00CAB9DD push 0x30      
00CAB9DF call <3dnes.void * __cdecl operator new(unsigned int, enum MemLabelIdentifier, int, char const *, int)>      
00CAB9E4 add esp,0x14      
00CAB9E7 test eax,eax      
00CAB9E9 je 3dnes.CAB9F9      
00CAB9EB push 0x4B      
00CAB9ED mov ecx,eax      
00CAB9EF call <3dnes.public: __thiscall TypeTree::TypeTree(enum MemLabelIdentifier)>      
00CAB9F4 mov dword ptr ss:,eax      
00CAB9F7 jmp 3dnes.CABA00      
00CAB9F9 mov dword ptr ss:,0x0      
00CABA00 mov eax,dword ptr ds:      
00CABA03 mov ecx,dword ptr ss:      
00CABA06 or eax,0x2000      
00CABA0B push eax      
00CABA0C push ecx      
00CABA0D push esi      
00CABA0E call <3dnes.void __cdecl GenerateTypeTree(class Object &, class TypeTree *, enum TransferInstructionFlags)>      
00CABA13 mov edx,dword ptr ss:      
00CABA16 mov ecx,dword ptr ss:      
00CABA19 add esp,0xC      
00CABA1C push edx      
00CABA1D call <3dnes.public: void __thiscall SerializedFile::Type::CompareAgainstNewType(class TypeTree *)>      
00CABA22 mov eax,dword ptr ss:      
00CABA25 push 0x4B      
00CABA27 push eax      
00CABA28 call <3dnes.void __cdecl delete_internal<class TypeTree>(class TypeTree *, enum MemLabelIdentifier)>      
00CABA2D add esp,0x8      
00CABA30 mov eax,dword ptr ds:      
00CABA33 or eax,0x1      
00CABA36 cmp byte ptr ds:,0x0      
00CABA3A mov dword ptr ss:,eax      
00CABA3D je 3dnes.CABA47      
00CABA3F or eax,0x200      
00CABA44 mov dword ptr ss:,eax      
00CABA47 cmp dword ptr ss:,0x1      
00CABA4B jne 3dnes.CABA55      
00CABA4D or eax,0x800000      
00CABA52 mov dword ptr ss:,eax      
00CABA55 mov ecx,dword ptr ss:      
00CABA58 push ecx      
00CABA59 mov ecx,esi      
00CABA5B call <3dnes.private: void __thiscall Object::SetIsPersistent(bool)>      
00CABA60 mov eax,dword ptr ds:      
00CABA63 add eax,dword ptr ds:      
00CABA66 mov ecx,dword ptr ss:      
00CABA69 mov dword ptr ss:,eax      
00CABA6C xor eax,eax      
00CABA6E cmp dword ptr ds:,eax      
00CABA71 je 3dnes.CABB24      
00CABA77 cmp dword ptr ds:,eax      
00CABA7A je 3dnes.CABB24      
00CABA80 lea ecx,dword ptr ss:      
00CABA86 call <3dnes.public: __thiscall SafeBinaryRead::SafeBinaryRead(void)>      
00CABA8B mov edx,dword ptr ss:      
00CABA8E mov eax,dword ptr ds:      
00CABA91 mov ecx,esi      
00CABA93 mov dword ptr ss:,eax      
00CABA96 mov dword ptr ss:,0x0      
00CABA9D call <3dnes.public: enum MemLabelIdentifier __thiscall Object::GetMemoryLabel(void) const>      
00CABAA2 mov ecx,dword ptr ss:      
00CABAA5 mov edx,dword ptr ds:      
00CABAA8 push eax      
00CABAA9 mov eax,dword ptr ss:      
00CABAAC push ecx      
00CABAAD push edx      
00CABAAE push eax      
00CABAAF lea ecx,dword ptr ss:      
00CABAB2 push ecx      
00CABAB3 lea ecx,dword ptr ss:      
00CABAB9 call <3dnes.public: class CachedReader & __thiscall SafeBinaryRead::Init(class TypeTreeIterator const &, int, int, enum TransferInstructionFlags, enum MemLabelIdentifier)>      
00CABABE mov edx,dword ptr ds:      
00CABAC1 mov ecx,dword ptr ss:      
00CABAC4 push edx      
00CABAC5 mov edx,dword ptr ds:      
00CABAC8 push ecx      
00CABAC9 push edx      
00CABACA mov ecx,eax      
00CABACC mov dword ptr ss:,eax      
00CABACF call <3dnes.public: void __thiscall CachedReader::InitRead(class CacheReaderBase &, unsigned int, unsigned int)>      
00CABAD4 mov eax,dword ptr ds:      
00CABAD6 mov edx,dword ptr ds:      
00CABAD9 mov ecx,esi      
00CABADB call edx      
00CABADD mov eax,dword ptr ds:      
00CABADF mov edx,dword ptr ds:      
00CABAE2 lea ecx,dword ptr ss:      
00CABAE8 push ecx      
00CABAE9 mov ecx,esi      
00CABAEB call edx      
00CABAED mov ecx,dword ptr ss:      
00CABAF0 call <3dnes.public: unsigned int __thiscall CachedReader::End(void)>      
00CABAF5 sub eax,dword ptr ss:      
00CABAF8 mov ecx,dword ptr ds:      
00CABAFB cmp eax,ecx      
00CABAFD jbe 3dnes.CABB0E      
00CABAFF push esi      
00CABB00 push eax      
00CABB01 movsx eax,word ptr ds:      
00CABB05 push ecx      
00CABB06 call <3dnes.OutOfBoundsReadingError>      
00CABB0B add esp,0xC      
00CABB0E mov eax,dword ptr ss:      
00CABB11 lea ecx,dword ptr ss:      
00CABB17 mov byte ptr ds:,0x1      
00CABB1A call <3dnes.public: __thiscall SafeBinaryRead::~SafeBinaryRead(void)>      
00CABB1F jmp 3dnes.CABBE5      
00CABB24 mov dword ptr ss:,eax      
00CABB27 mov dword ptr ss:,eax      
00CABB2A mov dword ptr ss:,eax      
00CABB2D lea ecx,dword ptr ss:      
00CABB30 cmp byte ptr ds:,al      
00CABB33 jne 3dnes.CABB7F      
00CABB35 call <3dnes.public: __thiscall CachedReader::CachedReader(void)>      
00CABB3A mov ecx,esi      
00CABB3C call <3dnes.public: enum MemLabelIdentifier __thiscall Object::GetMemoryLabel(void) const>      
00CABB41 mov edx,dword ptr ds:      定位数据
00CABB44 mov ecx,dword ptr ss:      
00CABB47 mov dword ptr ss:,eax      
00CABB4A mov eax,dword ptr ds:      
00CABB4D add eax,dword ptr ds:      
00CABB50 push edx      定位数据
00CABB51 mov dword ptr ss:,ecx      
00CABB54 mov ecx,dword ptr ds:      
00CABB57 push eax      
00CABB58 push ecx      
00CABB59 lea ecx,dword ptr ss:      
00CABB5C mov dword ptr ss:,0x0      
00CABB63 call <3dnes.public: void __thiscall CachedReader::InitRead(class CacheReaderBase &, unsigned int, unsigned int)>      ;出现字符串最后位+1地址

0xCABB63 第 4 个参数来自 0xCABB50,0xCABB50 的数据来自 0xCABB41,0xCABB41 的数据来自 0xCAB999
我们先执行到 0xCAB999,看看 eax 指向的地址存放的是什么


的地址存放的就是我们要找的定位数据,将此处“04”改为“06”,结果是意料之中的


8、eax 是 0xCAB97C 的返回值,理论上应该进入这个 call 进一步分析来源
但我们有更简单的方法,那就是:猜!!!
从前后结构来看 eax 指向的这块数据挺有规律,就像一张表,我们先到文件里找找看
在文件“level0”中搜索“04 01 00 00”这一串数据,搜到很多项,我们不断扩大特征码来缩小范围
搜索到“04 01 00 00 FC FF FF FF 72 00 1F 00 00 00 00 00”的时候,再扩大特征码就搜不到数据了
我们看看搜到的数据,再与程序内存数据比较一下


第一处搜到的数据后面跟着“25 03 00 00”,内存中也有这串数据,只不过中间多了“00 00 00 00”
向后看看,下一项数据也能对上,这里很可能就是关键的数据
不管它,先改改看,改完运行,结果很完美,成功找到了关键数据位置
最终修改之处有三,分别是字符串长度、字符串字符数据及显示长度,前后对比如下:


后记:
调试过程中的一些办法(非明文字符串的搜索、关键流程的准确定位)是我在平时调试过程中自己摸索出来的,在这里抛砖引玉,如果有更好更快捷的方法不妨提出来大家共同进步。字符串是很多程序执行流程的关键提示,越来越多的程序将字符串做了加密,导致内存中搜索不到,但只要显示了,它肯定得先解密,用完再销毁,我们先粗略定位,再通过 x32dbg 灵活的条件断点(复杂一点的需要编写脚本),最终很可能就找到关键位置了,我用这个办法在不同软件中也找到过加密字符串,最终实现了自己想要的目标。x32dbg 是个很强大的工具,我也一直在学习中,多用、善用工具,可以大大提高自己的工作效率。
下面是我在这次分析过程中写的一个脚本,这个脚本初始的设想是用于定位比特流的处理过程,具体方法从头开始执行程序,遇到 call 就检测目标比特流,检测到目标出现就记录日志并进入这个 call 继续向下寻找,最后查看日志就可以看到这个比特流处理的整个执行流程,从而分析到程序的关键处理流程。这个脚本最终在这次分析中没有发挥作用,因为写得匆忙,仅仅是一个初步的思路,也许可以应付一些简单的程序,还有没考虑到的地方(如线程等),可能还有 bug 需要修改,这里也分享出来供大家参考,希望各路高手能提供更好更完善的分析思路。
$addr=0
reload:
init "D:\Down\汉化字符大于原始长度(字符是主程序读取资源文件)\3DNes\3dnes.exe"
cmp $addr,0
je cont
g $addr
cont:
tocnd "dis.iscall(cip)"
$addr=cip
step
findallmem 0,"31 70 65 6E"
cmp $result,0
je cont
log "found pattern at "{$addr}
jmp reload

lies2014 发表于 2024-6-12 08:56

本帖最后由 lies2014 于 2024-6-12 09:07 编辑

雪很冷 发表于 2024-6-12 02:31
感谢大佬无私分享实用经验,受益匪浅。有个问题请教,为何我在多次调试中发现,命中次数容易发生变化,并不 ...
不同的机器确实会有不同的表现,因此我强调这是我机器上的命中次数,有些不能固定的可以不要这个断点,总会有一些是固定的,留着那些固定的就可以了,需要调试到不固定的层级时就通过上一级中断一路跟过来
还有就是条件断点的中断条件设置是很灵活很强大的,不一定用计数器作为中断条件,遇到命中次数不固定的断点,可以设置别的条件或多种条件的组合,只要开动脑筋,总能找到合适的设置,有时间多读读x32dbg的帮助文档有助于开拓思路

XinCb 发表于 2024-6-10 18:38

有的软件翻译成gb18030的中文显示乱码,是啥原因?

xiaofan1990 发表于 2024-6-10 20:55

大神学习了

Lty20000423 发表于 2024-6-11 07:38

很有参考意义的原贴

shaokui123 发表于 2024-6-11 08:26

这个大佬非常热心,并且技术高超有耐心能对新手的问题一直研究帮助解惑,非常感谢。
美中不足的是自己能力太差,大佬的文章大部分看不懂最好是视频的能动手跟着做一下,惭愧。。。

emcell 发表于 2024-6-11 11:51

感谢楼主的分享!~学习中。

wjx1201 发表于 2024-6-11 13:20

感谢大佬

Z781287 发表于 2024-6-11 19:24

感谢分享

雪很冷 发表于 2024-6-12 02:31

感谢大佬无私分享实用经验,受益匪浅。有个问题请教,为何我在多次调试中发现,命中次数容易发生变化,并不是那么固定。以命中次数来定位8层调用,没法做到精准跟踪。有什么办法可以让这个命中次数基本不变吗?我是不是有什么步骤疏漏了呢。还请大佬亲自指点下
页: [1] 2
查看完整版本: 3DNes字符串汉化不成功的问题定位解决