Ra3在线大厅房主选择地图逆向分析

Ra3在线大厅房主选择地图逆向分析

由于玩了很久, 现在我拥有大约1000张地图, 每次想玩到特定的地图找图都是一种折磨, 于是打算写一个辅助工具来快速寻找地图, 记录下过程

正向分析

如果我是开发者, 我会如何写这个功能? 应该是将所有的地图放入一个数组中, 然后由一个下标来控制当前选择的地图

如果我想选择地图, 只需要控制此下标, 即可选择对应的地图

下标分析

尝试搜索下标, 包括精确搜索和模糊搜索并没有搜到对应的下标, 所以它更可能是一种通知机制, UI发生变化时, 通知主程序 call 设置当前函数(下标) , 下标作为栈变量很快会被释放不会长久驻留在程序中, 现在可以获得的信息只有地图名称, 现在开始从字符串进行入手分析

字符串分析

搜索地图时发现确实有地图字符串跟着, 但是地图内存中放的并不是直接的地图名称, 而是地图路径, 且是经过小写后的路径. 直接搜精确地图名称是搜不到的, 说明UI里的数据存储是一种奇怪的方式

c:\users\simon\appdata\roaming\red alert 3\maps\mission01_v7.2_sargon\mission01_v7.2_sargon.map

image1

经过搜集资料, 查到Ra3的UI其实是一种flash动画, 它是解释语言, 不太好控制. 它的显示结果应该是专门的数据结构, 但是由于是解释型语言并不好控制所以还是得从C入手, 同时证明肯定存在Flash回调C的接口, 现在开始从此方向分析

搜索精确路径字符串发现了3个结果 057F60B0 05B5B690 05B5B700 , 查看对应字符串的内存区域如下

image2

PLAIN
057F60B0 - 12 01 00 00 00 D1 17 00 00 C8 78 CD 00 C8 78 CD 00  后接字符串...
05B5B690 - 8  02 00 00 00 5F 00 60 00 后接字符串
05B5B700 - 8  02 00 00 00 5F 00 60 00 后接字符串

字符串的长度为95, 换算后是5F, 可以看到内存中有5F和60, 可以推测出前一个5F是字符数量, 60是实际分配内存长度, 带了一个 \0 结尾 字符串前面有一个小数字, 发现当选中此图时此数字会+1, 取消选中会-1, 怀疑是引用计数, 那么字符串的结构应该如下

C
struct MapString {
    uint32_t refCount;
    uint16_t length;
    uint16_t capacity;
    char* data;
}

其中 05B5B690 在地图发生变化的时候会更新, 05B5B700 不会, 说明界面上有关的是05B5B690, 对05B5B690进行指针扫描

扫出了三个结果, 更新地图再恢复后发现有一个地址 0603E9CC 会跟着变化, 其他两个会变飞, 说明 0603E9CC 指向地图路径

Snipaste 2026 09 17 13 58 23

栈分析

对 0603E9CC 下4字节写入断点, 点击地图中查看栈, 对栈一个一个分析

首先看当前断下函数

C
004CB650 | push ebx                                |
004CB651 | push ebp                                |
004CB652 | push esi                                |
004CB653 | mov esi,ecx                             | // this call
004CB655 | call ra3_1.12.4CB0E0                    | // InitializeCriticalSection
004CB65A | mov ebx,eax                             | // ebx应该是锁
004CB65C | mov eax,dword ptr ds:[ebx]              | [ebx]:"pdA"
004CB65E | mov edx,dword ptr ds:[eax]              |
004CB660 | push FFFFFFFF                           |
004CB662 | mov ecx,ebx                             |
004CB664 | call edx                                | // 虚表调用
004CB666 | mov ebp,dword ptr ss:[esp+10]           | // 开头压3个寄存器, ebp = 参数1
004CB66A | cmp ebp,esi                             | // ecx为this, 对比 this 是否和参数1相同
004CB66C | je ra3_1.12.4CB6B5                      |
004CB66E | push edi                                |
004CB66F | call ra3_1.12.4CB0E0                    |
004CB674 | mov edi,eax                             |
004CB676 | mov eax,dword ptr ds:[edi]              | [edi]:"pdA"
004CB678 | mov edx,dword ptr ds:[eax]              |
004CB67A | push FFFFFFFF                           |
004CB67C | mov ecx,edi                             |
004CB67E | call edx                                |

可以看出有临界区和锁, 切包含this和参数比较, 意味着这里应该是一个字符串比较功能, 相同跳过

ASM
001AE5F0  05D85D60  返回到 05D85D60 自 E0605D7C
001AE5F4  05D85D60  返回到 05D85D60 自 E0605D7C
001AE5F8  05D85D94
001AE5FC  00000001
001AE600  00684216  返回到 ra3_1.12
001AE604  001AE628  &"c:\\users\\simon\\appdata\\roaming\\red alert 3\\maps\\pacific_showdown_corona\\pacific_showdown_corona.map"
001AE608  05B63BCC  &L"$Map:pacific_showdown_corona"
001AE60C  05B63C50  &"c:\\users\\simon\\appdata\\roaming\\red alert 3\\maps\\pacific_showdown_corona\\pacific_showdown_corona.map"
001AE610  004CB5FF  返回到 ra3_1.12
001AE614  0422B050
001AE618  05B63BCC  &L"$Map:pacific_showdown_corona"
001AE61C  05D85D60  返回到 05D85D60 自 E0605D7C
001AE620  00691AF2  返回到 ra3_1.12
001AE624  00691AF9  返回到 ra3_1.12
001AE628  05B5CDA8  "c:\\users\\simon\\appdata\\roaming\\red alert 3\\maps\\pacific_showdown_corona\\pacific_showdown_corona.map"

分析第一个 001AE600 00684216

ASM
00684200 | sub esp,14                              |
00684203 | push ebp                                |
00684204 | push edi                                |
00684205 | mov edi,ecx                             |
00684207 | lea eax,dword ptr ss:[esp+20]           | [esp+20] 第一个参数 (14 + 4 + 4), 取第一个参数地址
0068420B | lea ebp,dword ptr ds:[edi+34]           |
0068420E | push eax                                |
0068420F | mov ecx,ebp                             |
00684211 | call ra3_1.12.4CB650                    | 比较([edi+34], 第一个参数)
00684216 | cmp byte ptr ds:[edi+2C],0              |
0068421A | je ra3_1.12.684559                      | 相同跳过, 直接跳到最后了
00684220 | mov edx,dword ptr ds:[edi]              | [edi]:"pdA"
00684222 | mov eax,dword ptr ds:[edx+2C]           |
00684225 | mov ecx,edi                             |
00684227 | call eax                                |

这里应该是一个thiscall, 调用了对比功能, 相同则几乎跳过了全部代码, 总之应该是一个和字符串相关的功能, 则继续往上

ASM
004CB5D0 | push esi                                |
004CB5D1 | push edi                                |
004CB5D2 | mov edi,ecx                             |
004CB5D4 | call ra3_1.12.4CB0E0                    |
004CB5D9 | mov esi,eax                             |
004CB5DB | mov eax,dword ptr ds:[esi]              |
004CB5DD | mov edx,dword ptr ds:[eax]              |
004CB5DF | push FFFFFFFF                           |
004CB5E1 | mov ecx,esi                             |
004CB5E3 | call edx                                |
004CB5E5 | mov eax,dword ptr ss:[esp+C]            |
004CB5E9 | mov eax,dword ptr ds:[eax]              |
004CB5EB | test eax,eax                            |
004CB5ED | mov dword ptr ds:[edi],eax              | [edi]:"pdA"
004CB5EF | je ra3_1.12.4CB5F5                      |
004CB5F1 | add dword ptr ds:[eax-8],1              |
004CB5F5 | add esi,8                               |
004CB5F8 | push esi                                |
004CB5F9 | call dword ptr ds:[<LeaveCriticalSectio |
004CB5FF | mov eax,edi                             | <--- 在这里
004CB601 | pop edi                                 |
004CB602 | pop esi                                 |
004CB603 | ret 4                                   |

这里的内存地址已经不在游戏里了, 是一个系统调用, 它可能是某次call的残骸, 这个不进行分析

ASM
00691A70 | push ecx                                |
00691A71 | mov ecx,dword ptr ds:[CE8BD8]           | 00CE8BD8:&"h锪"
00691A77 | test ecx,ecx                            |
00691A79 | push ebx                                |
00691A7A | push ebp                                |
00691A7B | push esi                                |
00691A7C | push edi                                |
00691A7D | je ra3_1.12.691B41                      | // ecx = dword ptr ds:[CE8BD8] 为 0 时跳到最后
00691A83 | mov eax,dword ptr ds:[ecx]              | // 取出 [CE8BD8] 虚表
00691A85 | mov edx,dword ptr ds:[eax+104]          | // 取出 [CE8BD8] 虚表的 104 偏移
00691A8B | call edx                                | // 做查询, 返回值在eax
00691A8D | mov ecx,dword ptr ds:[CE0930]           | // 换另一个全局对象
00691A93 | mov esi,eax                             | // 上个查询结果放到esi
00691A95 | mov eax,dword ptr ss:[esp+18]           | // 开头压5参数占用 14, 说明第一参数
00691A99 | push eax                                |
00691A9A | call ra3_1.12.56DA60                    | // this call, [CE0930].(参数1)
00691A9F | mov edi,eax                             | // 返回值放到eax -> edi

查看此函数调用方

ASM
00ABFD73 | push eax                                |
00ABFD74 | call ra3_1.12.691A70                    |

可以看到此函数只有一个参数 eax, 且断点发现是一个比较小的值, 每个图的eax都不同, 且差距比较小, 基本可以确认此值为地图索引


Ra3在线大厅房主选择地图逆向分析
https://simonkimi.github.io/posts/20260917035914/
作者
simonkimi
发布于
2026年9月17日
许可协议