REV (714 points, 5 solves)
Here we explain the solution for the reversing challenge yasashiimon from our recent CTF.
The challenge is about a malicious emulator patching the game.
Let’s face it, this “hacking” hobby of yours can be quite daunting at times. How about you get out your Gameboy and have some fun instead?
Note: You’re looking for a Gameboy Advance game. Replace <
and >
by {
and }
before submitting your flag.
In this challenge we’re presented with a compiled version of the Visual Boy Advance emulator. When trying to open any (legally owned) ROM image of a game, we’re greeted with the following error message, indicating that we’re dealing with a modified version of the emulator:
Decompiling the binary and searching for references to the string of the error message, we find a suspcious looking check in the pseudocode:
for ( i = 0; i != 20; ++i )
v76[i] = (unsigned __int32)v77.m128i_i32[(unsigned int)i >> 2] >> (8 * (~(_BYTE)i & 3));
if ( *(_QWORD *)v76 ^ 0x1DE2CBDE7BC66278LL | *(_QWORD *)&v76[8] ^ 0x2743E32C08CE691DLL
|| *(_DWORD *)&v76[16] != 0x5EEDC6E1 )
{
sub_14005AF70(
9,
"Error opening image %s.\n"
"To comply with international anti-piracy laws, hxp locked down this emulator. Your flag is in another castle. Much sad.",
a1);
return 0;
}
Digging a bit more, we find familiar-looking pieces of code and data (for example sub_14076D050
, xmmword_14106A8B0
, state initialization at loc_14076FF98
, …) that we quickly recognize as a SHA-1 implementation. Making an educated guess that the SHA1 input is probably the loaded ROM file, and converting the magic constants to a proper hash
>>> struct.pack("<QQI", 0x1DE2CBDE7BC66278, 0x2743E32C08CE691D, 0x5EEDC6E1).hex()
'7862c67bdecbe21d1d69ce082ce34327e1c6ed5e'
a quick internet research reveals that the check can be satisfied by loading a copy of Pokemon Leafgreen (Revision 1). This is a game for which source code has been almost fully restored by the pret project, which obviously simplifies our reversing efforts substantially.
To understand what happens inside the emulator when loading the correct ROM file, we inspect the function with the lockdown error code (sub_14076FED0
) a bit more. The prologue makes use of some global variables:
__int64 __fastcall sub_14076FED0(const char *a1)
{
// [ local variable declarations removed for brevity ]
dword_140EB1C08 = 0x2000000;
if ( unk_1418689A8 )
sub_14076CF30();
dword_1414656D0 = 0;
unk_1418689A8 = malloc(0x2000000);
v2 = unk_1418689A8;
if ( !unk_1418689A8 )
{
sub_14005AF70(41, "Failed to allocate memory for %s", "ROM");
return 0;
}
v3 = calloc(1, 0x40000);
unk_141868998 = v3;
if ( !v3 )
{
sub_14005AF70(41, "Failed to allocate memory for %s", "WRAM");
return 0;
}
if ( unk_140EAE240 )
v2 = v3;
We can recover meaningful names surprisingly easily by compiling a copy of the Visual Boy Advance source code and comparing the debugging-symbols-enhanced binary to the challenge binary (this also works well when comparing across operating system versions). Finding the right functions to match is again conveniently made possible by following string references (such as, for example, "Error opening image %s"
). This way we are able to identify sub_14076FED0
as CPULoadRom
, and steal some rather useful global variable names (such as g_rom
, and g_workRAM
):
__int64 __fastcall CPULoadRom(const char *file, __m128i a2)
{
// [ local variable declarations removed for brevity ]
v384[1] = __readfsqword(0x28u);
romSize = 0x2000000;
if ( g_rom )
CPUCleanUp();
systemSaveUpdateCounter = 0;
g_rom = (uint8_t *)malloc(0x2000000);
v4 = g_rom;
if ( !g_rom )
{
systemMessage(41, "Failed to allocate memory for %s", "ROM");
return 0;
}
g_workRAM = (voidp)calloc(1, 0x40000);
v5 = (uint8_t *)g_workRAM;
if ( !g_workRAM )
{
systemMessage(41, "Failed to allocate memory for %s", "WRAM");
return 0;
}
if ( !coreOptions )
v5 = v4;
With this information available, it becomes immediately clear that the code triggered after loading the right ROM performs some interesting updates to the game:
v33 = _mm_loadu_si128((const __m128i *)&xmmword_14106A8D0);
*(_BYTE *)(g_rom_ptr + 186238) = 3;
*(_BYTE *)(g_rom_ptr + 522804) = -108;
*(_WORD *)(g_rom_ptr + 1441752) = -30584;
*(__m128i *)(g_rom_ptr + 4183584) = v33;
v34 = _mm_loadu_si128((const __m128i *)&xmmword_14106A8F0);
*(_WORD *)(g_rom_ptr + 3860764) = 3632;
*(__m128i *)(g_rom_ptr + 4294298) = v34;
v35 = _mm_loadu_si128((const __m128i *)&xmmword_14106A920);
*(_QWORD *)(g_rom_ptr + 4183600) = 0xC6BBBC00CCBFCECDuLL;
*(__m128i *)(g_rom_ptr + 4490381) = v35;
*(_WORD *)(g_rom_ptr + 4183608) = -21562;
*(_BYTE *)(g_rom_ptr + 1441754) = -21;
*(_DWORD *)(g_rom_ptr + 4294314) = -1378101792;
*(_BYTE *)(g_rom_ptr + 3860760) = 2;
*(_QWORD *)(g_rom_ptr + 4295006) = 0xCAC1BBC6C0BCC3C1uLL;
*(_BYTE *)(g_rom_ptr + 3860766) = -21;
*(_DWORD *)(g_rom_ptr + 4295014) = -843333690;
*(_BYTE *)(g_rom_ptr + 4183610) = -1;
*(_DWORD *)(g_rom_ptr + 4490397) = -603925248;
*(_BYTE *)(g_rom_ptr + 4294318) = -1;
*(_WORD *)(g_rom_ptr + 4490401) = -6932;
*(_QWORD *)(g_rom_ptr + 4490404) = 0xA5A3A1A300C0CEBDuLL;
v36 = _mm_loadu_si128((const __m128i *)&xmmword_14106A950);
*(_BYTE *)(g_rom_ptr + 4490418) = -1;
*(_DWORD *)(g_rom_ptr + 4490412) = -706684416;
*(__m128i *)(g_rom_ptr + 15404592) = v36;
v37 = _mm_loadu_si128((const __m128i *)&xmmword_14106A960);
*(_WORD *)(g_rom_ptr + 4490416) = -21029;
*(__m128i *)(g_rom_ptr + 15404608) = v37;
v38 = _mm_loadu_si128((const __m128i *)&xmmword_14106A990);
*(_QWORD *)(g_rom_ptr + 15404624) = 2051;
*(__m128i *)(g_rom_ptr + 15404647) = v38;
v39 = _mm_loadu_si128((const __m128i *)&xmmword_14106A9A0);
*(_DWORD *)(g_rom_ptr + 15404632) = 149622368;
*(__m128i *)(g_rom_ptr + 15404663) = v39;
v40 = _mm_loadu_si128((const __m128i *)&xmmword_14106A9B0);
*(_WORD *)(g_rom_ptr + 15404636) = 774;
*(__m128i *)(g_rom_ptr + 15404679) = v40;
v41 = _mm_loadu_si128((const __m128i *)&xmmword_14106A9D0);
*(_DWORD *)(g_rom_ptr + 15404640) = 17904489;
*(__m128i *)(g_rom_ptr + 15405056) = v41;
v42 = _mm_loadu_si128((const __m128i *)&unk_14106A9E0);
*(_WORD *)(g_rom_ptr + 15404644) = 20224;
*(__m128i *)(g_rom_ptr + 15405072) = v42;
v43 = _mm_loadu_si128((const __m128i *)&xmmword_14106A9F0);
*(_DWORD *)(g_rom_ptr + 15404695) = 33605669;
*(__m128i *)(g_rom_ptr + 15405088) = v43;
v44 = _mm_loadu_si128((const __m128i *)&xmmword_14106AA00);
*(_BYTE *)(g_rom_ptr + 15405106) = 8;
*(__m128i *)(g_rom_ptr + 15405120) = v44;
v45 = _mm_loadu_si128((const __m128i *)&xmmword_14106AA10);
*(_WORD *)(g_rom_ptr + 15405104) = 6802;
*(__m128i *)(g_rom_ptr + 15405136) = v45;
v46 = _mm_loadu_si128((const __m128i *)&xmmword_14106AA20);
*(_WORD *)(g_rom_ptr + 15405200) = -21017;
*(__m128i *)(g_rom_ptr + 15405152) = v46;
v47 = _mm_loadu_si128((const __m128i *)&xmmword_14106AA30);
*(_QWORD *)(g_rom_ptr + 15435944) = 0xA00000113A03000LL;
*(__m128i *)(g_rom_ptr + 15405168) = v47;
v48 = _mm_loadu_si128((const __m128i *)&xmmword_14106AA40);
*(_DWORD *)(g_rom_ptr + 15435952) = -507432784;
*(__m128i *)(g_rom_ptr + 15405184) = v48;
v49 = _mm_loadu_si128((const __m128i *)&xmmword_14106AA60);
*(_DWORD *)(g_rom_ptr + 15435974) = 805364048;
*(__m128i *)(g_rom_ptr + 15435912) = v49;
v50 = _mm_loadu_si128((const __m128i *)&xmmword_14106AA70);
*(_WORD *)(g_rom_ptr + 15435978) = 5024;
*(__m128i *)(g_rom_ptr + 15435928) = v50;
v51 = _mm_loadu_si128((const __m128i *)&xmmword_14106AA90);
*(_BYTE *)(g_rom_ptr + 15435956) = 30;
*(__m128i *)(g_rom_ptr + 15435958) = v51;
v52 = _mm_loadu_si128((const __m128i *)&xmmword_14106AAB0);
*(_BYTE *)(g_rom_ptr + 15435980) = -9;
*(__m128i *)(g_rom_ptr + 15435983) = v52;
v53 = g_rom;
v54 = _mm_loadu_si128((const __m128i *)&xmmword_14106AAD0);
*(_BYTE *)(g_rom_ptr + 15436063) = 0;
*(_DWORD *)(g_rom_ptr + 15435999) = -1607466783;
*(__m128i *)(g_rom_ptr + 15436007) = v54;
v55 = _mm_loadu_si128((const __m128i *)&xmmword_14106AAF0);
*(_WORD *)(g_rom_ptr + 15436003) = -3821;
*(__m128i *)(g_rom_ptr + 15436035) = v55;
*(_QWORD *)(g_rom_ptr + 15436023) = unk_14106AAE0;
*(_WORD *)(g_rom_ptr + 15436031) = -5613;
*(_QWORD *)(g_rom_ptr + 15436051) = 0x1E1B00000A0200LL;
*(_DWORD *)(g_rom_ptr + 15436059) = 1259264;
Taking a binary diff of the original ROM and bytes of the game when running inside the emulator, we see that the following patches are applied:
0x0802d77e 0x04 -> 0x03
0x0807fa34 0x85 -> 0x94
0x0815ffd8 0xa9 -> 0x88
0x0815ffd9 0xad -> 0x88
0x0815ffda 0x0c -> 0xeb
0x083ae918 0x01 -> 0x02
0x083ae91c 0xcc -> 0x30
0x083ae91d 0xe8 -> 0x0e
0x083ae91e 0x3a -> 0xeb
0x083fd620 0xc3 -> 0xdc
0x083fd621 0xe8 -> 0xec
0x083fd622 0x00 -> 0xe4
0x083fd623 0xeb -> 0x00
0x083fd624 0xd5 -> 0xd6
0x083fd625 0xe7 -> 0xe3
0x083fd626 0x00 -> 0xe6
0x083fd627 0xe7 -> 0xdf
0x083fd628 0xe3 -> 0xd9
0x083fd629 0x00 -> 0xd8
0x083fd62a 0xd7 -> 0x00
0x083fd62b 0xe0 -> 0xe1
0x083fd62c 0xe3 -> 0xed
0x083fd62d 0xe7 -> 0x00
0x083fd62e 0xd9 -> 0xc7
0x083fd62f 0xb8 -> 0xbb
0x083fd630 0x00 -> 0xcd
0x083fd631 0xe8 -> 0xce
0x083fd632 0xe3 -> 0xbf
0x083fd633 0xe3 -> 0xcc
0x083fd634 0xab -> 0x00
0x083fd635 0xff -> 0xbc
0x083fd636 0x26 -> 0xbb
0x083fd637 0x09 -> 0xc6
0x083fd638 0x27 -> 0xc6
0x083fd639 0x2a -> 0xab
0x083fd63a 0x10 -> 0xff
0x0841869a 0xd5 -> 0xe8
0x0841869b 0xe2 -> 0xe3
0x0841869c 0xd8 -> 0x00
0x0841869d 0x00 -> 0xeb
0x0841869e 0xda -> 0xdd
0x0841869f 0xdd -> 0xe2
0x084186a0 0xe0 -> 0x00
0x084186a1 0xe0 -> 0xd5
0x084186a2 0x00 -> 0xe2
0x084186a3 0xe3 -> 0x00
0x084186a4 0xe9 -> 0xd9
0x084186a5 0xe8 -> 0xd5
0x084186a6 0x00 -> 0xe7
0x084186a7 0xe8 -> 0xed
0x084186a8 0xdc -> 0x00
0x084186a9 0xd9 -> 0xda
0x084186aa 0x00 -> 0xe0
0x084186ab 0xe5 -> 0xd5
0x084186ac 0xe9 -> 0xdb
0x084186ad 0xd9 -> 0xad
0x084186ae 0xe7 -> 0xff
0x0841895e 0xcb -> 0xc1
0x0841895f 0xcf -> 0xc3
0x08418960 0xbf -> 0xbc
0x08418961 0xcd -> 0xc0
0x08418962 0xce -> 0xc6
0x08418963 0xc3 -> 0xbb
0x08418964 0xc9 -> 0xc1
0x08418965 0xc8 -> 0xca
0x08418966 0xc8 -> 0xc6
0x08418967 0xbb -> 0xbf
0x08418968 0xc3 -> 0xbb
0x08418969 0xcc -> 0xcd
0x0844848d 0xc9 -> 0xc3
0x0844848e 0xe2 -> 0xe8
0x0844848f 0xe0 -> 0x00
0x08448490 0xed -> 0xe7
0x08448491 0x00 -> 0xd9
0x08448492 0xd5 -> 0xd9
0x08448493 0x00 -> 0xe1
0x08448494 0xda -> 0xe7
0x08448495 0xd9 -> 0x00
0x08448496 0xeb -> 0xe8
0x08448497 0x00 -> 0xe3
0x08448498 0xe4 -> 0xfe
0x08448499 0xd9 -> 0xdf
0x0844849a 0xe3 -> 0xe2
0x0844849b 0xe4 -> 0xe3
0x0844849c 0xe0 -> 0xeb
0x0844849d 0xd9 -> 0x00
0x0844849e 0xfe -> 0xd5
0x0844849f 0xdc -> 0x00
0x084484a0 0xd5 -> 0xdc
0x084484a1 0xea -> 0xec
0x084484a2 0xd9 -> 0xe4
0x084484a4 0xe7 -> 0xbd
0x084484a5 0xd9 -> 0xce
0x084484a6 0xd9 -> 0xc0
0x084484a7 0xe2 -> 0x00
0x084484a8 0x00 -> 0xa3
0x084484a9 0xdd -> 0xa1
0x084484aa 0xe8 -> 0xa3
0x084484ab 0x00 -> 0xa5
0x084484ac 0xeb -> 0x00
0x084484ad 0xe3 -> 0xda
0x084484ae 0xe6 -> 0xe0
0x084484af 0xe0 -> 0xd5
0x084484b0 0xd8 -> 0xdb
0x084484b1 0xeb -> 0xad
0x084484b2 0xdd -> 0xff
0x08eb0e30 0xff -> 0x01
0x08eb0e31 0xff -> 0x97
0x08eb0e32 0xff -> 0x00
0x08eb0e33 0xff -> 0x00
0x08eb0e34 0xff -> 0x1e
0x08eb0e35 0xff -> 0x00
0x08eb0e36 0xff -> 0x10
0x08eb0e37 0xff -> 0x00
0x08eb0e38 0xff -> 0x01
0x08eb0e39 0xff -> 0x08
0x08eb0e3a 0xff -> 0x11
0x08eb0e3b 0xff -> 0x00
0x08eb0e3c 0xff -> 0x00
0x08eb0e3d 0xff -> 0x00
0x08eb0e3e 0xff -> 0x00
0x08eb0e3f 0xff -> 0x00
0x08eb0e40 0xff -> 0x00
0x08eb0e41 0xff -> 0x00
0x08eb0e42 0xff -> 0x00
0x08eb0e43 0xff -> 0x00
0x08eb0e44 0xff -> 0x87
0x08eb0e45 0xff -> 0x00
0x08eb0e46 0xff -> 0x00
0x08eb0e47 0xff -> 0x00
0x08eb0e48 0xff -> 0x02
0x08eb0e49 0xff -> 0x8c
0x08eb0e4a 0xff -> 0x00
0x08eb0e4b 0xff -> 0x00
0x08eb0e4c 0xff -> 0x3a
0x08eb0e4d 0xff -> 0x00
0x08eb0e4e 0xff -> 0x02
0x08eb0e4f 0xff -> 0x00
0x08eb0e50 0xff -> 0x03
0x08eb0e51 0xff -> 0x08
0x08eb0e52 0xff -> 0x00
0x08eb0e53 0xff -> 0x00
0x08eb0e54 0xff -> 0x00
0x08eb0e55 0xff -> 0x00
0x08eb0e56 0xff -> 0x00
0x08eb0e57 0xff -> 0x00
0x08eb0e58 0xff -> 0x60
0x08eb0e59 0xff -> 0x0e
0x08eb0e5a 0xff -> 0xeb
0x08eb0e5b 0xff -> 0x08
0x08eb0e5c 0xff -> 0x06
0x08eb0e5d 0xff -> 0x03
0x08eb0e60 0xff -> 0x69
0x08eb0e61 0xff -> 0x33
0x08eb0e62 0xff -> 0x11
0x08eb0e63 0xff -> 0x01
0x08eb0e64 0xff -> 0x00
0x08eb0e65 0xff -> 0x4f
0x08eb0e67 0xff -> 0x00
0x08eb0e68 0xff -> 0x2f
0x08eb0e69 0xff -> 0x76
0x08eb0e6a 0xff -> 0x1a
0x08eb0e6b 0xff -> 0x08
0x08eb0e6c 0xff -> 0x51
0x08eb0e6d 0xff -> 0x00
0x08eb0e6e 0xff -> 0x00
0x08eb0e6f 0xff -> 0x28
0x08eb0e70 0xff -> 0x6f
0x08eb0e71 0xff -> 0x00
0x08eb0e72 0xff -> 0x16
0x08eb0e73 0xff -> 0x04
0x08eb0e74 0xff -> 0x80
0x08eb0e75 0xff -> 0x0e
0x08eb0e76 0xff -> 0x00
0x08eb0e77 0xff -> 0x97
0x08eb0e78 0xff -> 0x01
0x08eb0e79 0xff -> 0x25
0x08eb0e7a 0xff -> 0x5f
0x08eb0e7b 0xff -> 0x00
0x08eb0e7c 0xff -> 0x97
0x08eb0e7d 0xff -> 0x00
0x08eb0e7e 0xff -> 0x37
0x08eb0e7f 0xff -> 0x00
0x08eb0e80 0xff -> 0x25
0x08eb0e81 0xff -> 0x89
0x08eb0e82 0xff -> 0x00
0x08eb0e83 0xff -> 0x00
0x08eb0e84 0xff -> 0x00
0x08eb0e85 0xff -> 0x21
0x08eb0e86 0xff -> 0x07
0x08eb0e87 0xff -> 0x80
0x08eb0e88 0xff -> 0x37
0x08eb0e89 0xff -> 0x13
0x08eb0e8a 0xff -> 0x06
0x08eb0e8b 0xff -> 0x01
0x08eb0e8c 0xff -> 0x00
0x08eb0e8d 0xff -> 0x10
0x08eb0e8e 0xff -> 0xeb
0x08eb0e8f 0xff -> 0x08
0x08eb0e90 0xff -> 0x6b
0x08eb0e91 0xff -> 0x2f
0x08eb0e92 0xff -> 0x0e
0x08eb0e93 0xff -> 0x00
0x08eb0e94 0xff -> 0x30
0x08eb0e95 0xff -> 0x97
0x08eb0e96 0xff -> 0x01
0x08eb0e97 0xff -> 0x25
0x08eb0e98 0xff -> 0xc8
0x08eb0e99 0xff -> 0x00
0x08eb0e9a 0xff -> 0x02
0x08eb1000 0xff -> 0x5a
0x08eb1001 0xff -> 0xb6
0x08eb1002 0xff -> 0x97
0x08eb1003 0xff -> 0x00
0x08eb1004 0xff -> 0x64
0x08eb1005 0xff -> 0x01
0x08eb1006 0xff -> 0x00
0x08eb1007 0xff -> 0x30
0x08eb1008 0xff -> 0xa1
0x08eb1009 0xff -> 0x97
0x08eb100a 0xff -> 0x00
0x08eb100b 0xff -> 0x02
0x08eb100c 0xff -> 0x00
0x08eb100d 0xff -> 0xc5
0x08eb100e 0xff -> 0x28
0x08eb100f 0xff -> 0x6f
0x08eb1010 0xff -> 0x00
0x08eb1011 0xff -> 0x29
0x08eb1012 0xff -> 0x07
0x08eb1013 0xff -> 0x08
0x08eb1014 0xff -> 0x25
0x08eb1015 0xff -> 0x38
0x08eb1016 0xff -> 0x01
0x08eb1017 0xff -> 0x27
0x08eb1018 0xff -> 0x2a
0x08eb1019 0xff -> 0x07
0x08eb101a 0xff -> 0x08
0x08eb101b 0xff -> 0x29
0x08eb101c 0xff -> 0x06
0x08eb101d 0xff -> 0x03
0x08eb101e 0xff -> 0x26
0x08eb101f 0xff -> 0x0d
0x08eb1020 0xff -> 0x80
0x08eb1021 0xff -> 0xb4
0x08eb1022 0xff -> 0x00
0x08eb1023 0xff -> 0x21
0x08eb1024 0xff -> 0x0d
0x08eb1025 0xff -> 0x80
0x08eb1026 0xff -> 0x07
0x08eb1027 0xff -> 0x00
0x08eb1028 0xff -> 0x06
0x08eb1029 0xff -> 0x01
0x08eb102a 0xff -> 0x40
0x08eb102b 0xff -> 0x10
0x08eb102c 0xff -> 0xeb
0x08eb102d 0xff -> 0x08
0x08eb102e 0xff -> 0x05
0x08eb102f 0xff -> 0x81
0x08eb1030 0xff -> 0x92
0x08eb1031 0xff -> 0x1a
0x08eb1032 0xff -> 0x08
0x08eb1040 0xff -> 0x67
0x08eb1041 0xff -> 0x58
0x08eb1042 0xff -> 0x10
0x08eb1043 0xff -> 0xeb
0x08eb1044 0xff -> 0x08
0x08eb1045 0xff -> 0x31
0x08eb1046 0xff -> 0x03
0x08eb1047 0xff -> 0x01
0x08eb1048 0xff -> 0x32
0x08eb1049 0xff -> 0x66
0x08eb104a 0xff -> 0x6d
0x08eb104b 0xff -> 0x68
0x08eb104c 0xff -> 0x05
0x08eb104d 0xff -> 0x81
0x08eb104e 0xff -> 0x92
0x08eb104f 0xff -> 0x1a
0x08eb1050 0xff -> 0x08
0x08eb1051 0xff -> 0x02
0x08eb1052 0xff -> 0x02
0x08eb1053 0xff -> 0x02
0x08eb1054 0xff -> 0x02
0x08eb1055 0xff -> 0x02
0x08eb1056 0xff -> 0x02
0x08eb1057 0xff -> 0x02
0x08eb1058 0xff -> 0xce
0x08eb1059 0xff -> 0xdc
0x08eb105a 0xff -> 0xdd
0x08eb105b 0xff -> 0xe7
0x08eb105c 0xff -> 0x00
0x08eb105d 0xff -> 0xd7
0x08eb105e 0xff -> 0xd5
0x08eb105f 0xff -> 0xe2
0x08eb1060 0xff -> 0xe2
0x08eb1061 0xff -> 0xe3
0x08eb1062 0xff -> 0xe8
0x08eb1063 0xff -> 0x00
0x08eb1064 0xff -> 0xd6
0x08eb1065 0xff -> 0xd9
0x08eb1066 0xff -> 0x00
0x08eb1067 0xff -> 0xe1
0x08eb1068 0xff -> 0xed
0x08eb1069 0xff -> 0x00
0x08eb106a 0xff -> 0xd8
0x08eb106b 0xff -> 0xd9
0x08eb106c 0xff -> 0xe7
0x08eb106d 0xff -> 0xe8
0x08eb106e 0xff -> 0xdd
0x08eb106f 0xff -> 0xe2
0x08eb1070 0xff -> 0xed
0x08eb1071 0xff -> 0xad
0x08eb1072 0xff -> 0xfe
0x08eb1073 0xff -> 0xc8
0x08eb1074 0xff -> 0xe3
0x08eb1075 0xff -> 0xeb
0x08eb1076 0xff -> 0x00
0x08eb1077 0xff -> 0xe8
0x08eb1078 0xff -> 0xdc
0x08eb1079 0xff -> 0xd9
0x08eb107a 0xff -> 0x00
0x08eb107b 0xff -> 0xe7
0x08eb107c 0xff -> 0xd9
0x08eb107d 0xff -> 0xe6
0x08eb107e 0xff -> 0xdd
0x08eb107f 0xff -> 0xe3
0x08eb1080 0xff -> 0xe9
0x08eb1081 0xff -> 0xe7
0x08eb1082 0xff -> 0x00
0x08eb1083 0xff -> 0xe8
0x08eb1084 0xff -> 0xd9
0x08eb1085 0xff -> 0xe7
0x08eb1086 0xff -> 0xe8
0x08eb1087 0xff -> 0xdd
0x08eb1088 0xff -> 0xe2
0x08eb1089 0xff -> 0xdb
0x08eb108a 0xff -> 0x00
0x08eb108b 0xff -> 0xd6
0x08eb108c 0xff -> 0xd9
0x08eb108d 0xff -> 0xdb
0x08eb108e 0xff -> 0xdd
0x08eb108f 0xff -> 0xe2
0x08eb1090 0xff -> 0xe7
0x08eb1091 0xff -> 0xad
0x08eb8888 0xff -> 0x74
0x08eb8889 0xff -> 0x30
0x08eb888a 0xff -> 0x9f
0x08eb888b 0xff -> 0xe5
0x08eb888c 0xff -> 0x7c
0x08eb888d 0xff -> 0x1d
0x08eb888e 0xff -> 0x93
0x08eb888f 0xff -> 0xe5
0x08eb8890 0xff -> 0x70
0x08eb8891 0xff -> 0x30
0x08eb8892 0xff -> 0x9f
0x08eb8893 0xff -> 0xe5
0x08eb8894 0xff -> 0x08
0x08eb8895 0xff -> 0x20
0x08eb8896 0xff -> 0x93
0x08eb8897 0xff -> 0xe5
0x08eb8898 0xff -> 0xd1
0x08eb8899 0xff -> 0x3d
0x08eb889a 0xff -> 0x82
0x08eb889b 0xff -> 0xe2
0x08eb889c 0xff -> 0xb8
0x08eb889d 0xff -> 0x01
0x08eb889e 0xff -> 0xd3
0x08eb889f 0xff -> 0xe1
0x08eb88a0 0xff -> 0x64
0x08eb88a1 0xff -> 0x30
0x08eb88a2 0xff -> 0x9f
0x08eb88a3 0xff -> 0xe5
0x08eb88a4 0xff -> 0x03
0x08eb88a5 0xff -> 0x00
0x08eb88a6 0xff -> 0x50
0x08eb88a7 0xff -> 0xe1
0x08eb88a8 0xff -> 0x00
0x08eb88a9 0xff -> 0x30
0x08eb88aa 0xff -> 0xa0
0x08eb88ab 0xff -> 0x13
0x08eb88ac 0xff -> 0x01
0x08eb88ad 0xff -> 0x00
0x08eb88ae 0xff -> 0x00
0x08eb88af 0xff -> 0x0a
0x08eb88b0 0xff -> 0xb0
0x08eb88b1 0xff -> 0x30
0x08eb88b2 0xff -> 0xc1
0x08eb88b3 0xff -> 0xe1
0x08eb88b4 0xff -> 0x1e
0x08eb88b6 0xff -> 0x2f
0x08eb88b7 0xff -> 0xe1
0x08eb88b8 0xff -> 0xd1
0x08eb88b9 0xff -> 0x3d
0x08eb88ba 0xff -> 0x82
0x08eb88bb 0xff -> 0xe2
0x08eb88bc 0xff -> 0xba
0x08eb88bd 0xff -> 0x01
0x08eb88be 0xff -> 0xd3
0x08eb88bf 0xff -> 0xe1
0x08eb88c0 0xff -> 0x48
0x08eb88c1 0xff -> 0x30
0x08eb88c2 0xff -> 0x9f
0x08eb88c3 0xff -> 0xe5
0x08eb88c4 0xff -> 0x03
0x08eb88c5 0xff -> 0x00
0x08eb88c6 0xff -> 0x50
0x08eb88c7 0xff -> 0xe1
0x08eb88c8 0xff -> 0x00
0x08eb88c9 0xff -> 0x30
0x08eb88ca 0xff -> 0xa0
0x08eb88cb 0xff -> 0x13
0x08eb88cc 0xff -> 0xf7
0x08eb88cf 0xff -> 0x1a
0x08eb88d0 0xff -> 0xd1
0x08eb88d1 0xff -> 0x3d
0x08eb88d2 0xff -> 0x82
0x08eb88d3 0xff -> 0xe2
0x08eb88d4 0xff -> 0xbc
0x08eb88d5 0xff -> 0x01
0x08eb88d6 0xff -> 0xd3
0x08eb88d7 0xff -> 0xe1
0x08eb88d8 0xff -> 0x34
0x08eb88d9 0xff -> 0x30
0x08eb88da 0xff -> 0x9f
0x08eb88db 0xff -> 0xe5
0x08eb88dc 0xff -> 0x03
0x08eb88dd 0xff -> 0x00
0x08eb88de 0xff -> 0x50
0x08eb88df 0xff -> 0xe1
0x08eb88e0 0xff -> 0x00
0x08eb88e1 0xff -> 0x30
0x08eb88e2 0xff -> 0xa0
0x08eb88e3 0xff -> 0x13
0x08eb88e4 0xff -> 0xf1
0x08eb88e7 0xff -> 0x1a
0x08eb88e8 0xff -> 0xd1
0x08eb88e9 0xff -> 0x2d
0x08eb88ea 0xff -> 0x82
0x08eb88eb 0xff -> 0xe2
0x08eb88ec 0xff -> 0xbe
0x08eb88ed 0xff -> 0x01
0x08eb88ee 0xff -> 0xd2
0x08eb88ef 0xff -> 0xe1
0x08eb88f0 0xff -> 0x20
0x08eb88f1 0xff -> 0x20
0x08eb88f2 0xff -> 0x9f
0x08eb88f3 0xff -> 0xe5
0x08eb88f4 0xff -> 0x20
0x08eb88f5 0xff -> 0x30
0x08eb88f6 0xff -> 0x9f
0x08eb88f7 0xff -> 0xe5
0x08eb88f8 0xff -> 0x02
0x08eb88f9 0xff -> 0x00
0x08eb88fa 0xff -> 0x50
0x08eb88fb 0xff -> 0xe1
0x08eb88fc 0xff -> 0x00
0x08eb88fd 0xff -> 0x30
0x08eb88fe 0xff -> 0xa0
0x08eb88ff 0xff -> 0x13
0x08eb8900 0xff -> 0xea
0x08eb8903 0xff -> 0xea
0x08eb8904 0xff -> 0x00
0x08eb8905 0xff -> 0xf0
0x08eb8906 0xff -> 0x15
0x08eb8907 0xff -> 0x08
0x08eb8908 0xff -> 0x00
0x08eb8909 0xff -> 0x50
0x08eb890a 0xff -> 0x00
0x08eb890b 0xff -> 0x03
0x08eb890c 0xff -> 0x1e
0x08eb890d 0xff -> 0x12
0x08eb890e 0xff -> 0x00
0x08eb890f 0xff -> 0x00
0x08eb8910 0xff -> 0x03
0x08eb8911 0xff -> 0x16
0x08eb8912 0xff -> 0x00
0x08eb8913 0xff -> 0x00
0x08eb8914 0xff -> 0x02
0x08eb8915 0xff -> 0x0a
0x08eb8916 0xff -> 0x00
0x08eb8917 0xff -> 0x00
0x08eb8918 0xff -> 0x1b
0x08eb8919 0xff -> 0x1e
0x08eb891a 0xff -> 0x00
0x08eb891b 0xff -> 0x00
0x08eb891c 0xff -> 0x37
0x08eb891d 0xff -> 0x13
0x08eb891e 0xff -> 0x00
0x08eb891f 0xff -> 0x00
While this list looks intimidating at first, the general intent becomes clear when taking the patched addresses, grouping them into consecutive runs, and looking up the closest preceding symbol in the ROM produced from the PRET sources:
0x0802d448 Cmd_handleballthrow
0x0802d77e 0x04 -> 0x03
0x0807f9c0 StartLegendaryBattle
0x0807fa34 0x85 -> 0x94
0x0815fdb4 gSpecials
0x0815ffd8 0xa9 -> 0x88
0x0815ffd9 0xad -> 0x88
0x0815ffda 0x0c -> 0xeb
0x0815ffdb 0x08 == 0x08
0x083ae918 SSAnne_Exterior_MapEvents
0x083ae918 0x01 -> 0x02
0x083ae91c 0xcc -> 0x30
0x083ae91d 0xe8 -> 0x0e
0x083ae91e 0x3a -> 0xeb
0x083ae91f 0x08 == 0x08
0x083fd619 sText_ShootSoClose
0x083fd620 0xc3 -> 0xdc
0x083fd621 0xe8 -> 0xec
[ ... snip ... ]
0x083fd637 0x09 -> 0xc6
0x083fd638 0x27 -> 0xc6
0x083fd63a 0x10 -> 0xff
0x0841869a gText_AndFillOutTheQuestionnaire
0x0841869a 0xd5 -> 0xe8
0x0841869b 0xe2 -> 0xe3
[ ... snip ... ]
0x084186ac 0xe9 -> 0xdb
0x084186ad 0xd9 -> 0xad
0x084186ae 0xe7 -> 0xff
0x0841895e gText_Questionnaire
0x0841895e 0xcb -> 0xc1
0x0841895f 0xcf -> 0xc3
[ ... snip ... ]
0x08418967 0xbb -> 0xbf
0x08418968 0xc3 -> 0xbb
0x08418969 0xcc -> 0xcd
0x0844844f gMewPokedexText
0x0844848d 0xc9 -> 0xc3
0x0844848e 0xe2 -> 0xe8
[ ... snip ... ]
0x084484b0 0xd8 -> 0xdb
0x084484b1 0xeb -> 0xad
0x084484b2 0xdd -> 0xff
[ ... all subsequent accesses removed as they target "empty" parts of the ROM ]
SSAnneExterior_MapEvents
, I wonder …?
And indeed, firing up the emulator, loading a suitable savegame and checking the surroundings of S.S. Anne shows that there’s a Mew sitting right next to the truck, waiting for us:
When approaching Mew, it asks us for a password via the game’s built-in easychat questionnaire dialog:
To find the password, we have to analyze the ROM patches a bit more. Starting from the topmost entry point that we found earlier, this is what the map events of a regular ROM look like:
.rodata:083AE918 SSAnne_Exterior_MapEvents DCB 1 ; DATA XREF: .rodata:0834F2D8↑o
.rodata:083AE919 DCB 5
.rodata:083AE91A DCB 0
.rodata:083AE91B DCB 1
.rodata:083AE91C DCD SSAnne_Exterior_ObjectEvents
.rodata:083AE920 DCD SSAnne_Exterior_MapWarps
.rodata:083AE924 DCD 0
.rodata:083AE928 DCD SSAnne_Exterior_MapBGEvents
And this is the same location in the patched ROM:
ROM:083AE918 SSAnne_Exterior_MapEvents DCB 2 ; DATA XREF: ROM:0834F2D8↑o
ROM:083AE919 DCB 5
ROM:083AE91A DCB 0
ROM:083AE91B DCB 1
ROM:083AE91C DCD unk_8EB0E30
ROM:083AE920 DCD SSAnne_Exterior_MapWarps
ROM:083AE924 DCD 0
ROM:083AE928 DCD SSAnne_Exterior_MapBGEvents
The corresponding struct
definition is in include/global.fieldmap.h
:
struct MapEvents
{
u8 objectEventCount;
u8 warpCount;
u8 coordEventCount;
u8 bgEventCount;
struct ObjectEventTemplate *objectEvents;
struct WarpEvent *warps;
struct CoordEvent *coordEvents;
struct BgEvent *bgEvents;
};
So we can see that the patch added an event to the map (probably the Mew NPC) and changed the objectEents
pointer to point somewhere else. Lets examine the objectEvents
in both versions.
Vanilla:
.rodata:083AE8CC SSAnne_Exterior_ObjectEvents DCB 1 ; localId
.rodata:083AE8CC ; DATA XREF: .rodata:083AE91C↓o
.rodata:083AE8CD DCB 0x97 ; graphicsId
.rodata:083AE8CE DCB 0 ; kind
.rodata:083AE8CF DCB 0
.rodata:083AE8D0 DCW 0x1E ; x
.rodata:083AE8D2 DCW 0x10 ; y
.rodata:083AE8D4 DCB 1 ; objUnion.normal.elevation
.rodata:083AE8D5 DCB 8 ; objUnion.normal.movementType
.rodata:083AE8D6.0 .bits(4) 1 ; objUnion.normal.movementRangeX
.rodata:083AE8D6.4 .bits(4) 1 ; objUnion.normal.movementRangeY
.rodata:083AE8D6.8 .bits(8) 0
.rodata:083AE8D8 DCW 0 ; objUnion.normal.trainerType
.rodata:083AE8DA DCW 0 ; objUnion.normal.trainerRange_berryTreeId
.rodata:083AE8DC DCD 0 ; script
.rodata:083AE8E0 DCW 0x87 ; flagId
Patched:
ROM:08EB0E30 stru_8EB0E30 DCB 1 ; localId
ROM:08EB0E30 ; DATA XREF: ROM:083AE91C↑o
ROM:08EB0E31 DCB 0x97 ; graphicsId
ROM:08EB0E32 DCB 0 ; kind
ROM:08EB0E33 DCB 0
ROM:08EB0E34 DCW 0x1E ; x
ROM:08EB0E36 DCW 0x10 ; y
ROM:08EB0E38 DCB 1 ; objUnion.normal.elevation
ROM:08EB0E39 DCB 8 ; objUnion.normal.movementType
ROM:08EB0E3A.0 .bits(4) 1 ; objUnion.normal.movementRangeX
ROM:08EB0E3A.4 .bits(4) 1 ; objUnion.normal.movementRangeY
ROM:08EB0E3A.8 .bits(8) 0
ROM:08EB0E3C DCW 0 ; objUnion.normal.trainerType
ROM:08EB0E3E DCW 0 ; objUnion.normal.trainerRange_berryTreeId
ROM:08EB0E40 DCD 0 ; script
ROM:08EB0E44 DCW 0x87 ; flagId
ROM:08EB0E46 DCB 0, 0
ROM:08EB0E48 DCB 2 ; localId
ROM:08EB0E49 DCB 0x8C ; graphicsId
ROM:08EB0E4A DCB 0 ; kind
ROM:08EB0E4B DCB 0
ROM:08EB0E4C DCW 0x3A ; x
ROM:08EB0E4E DCW 2 ; y
ROM:08EB0E50 DCB 3 ; objUnion.normal.elevation
ROM:08EB0E51 DCB 8 ; objUnion.normal.movementType
ROM:08EB0E52.0 .bits(4) 0 ; objUnion.normal.movementRangeX
ROM:08EB0E52.4 .bits(4) 0 ; objUnion.normal.movementRangeY
ROM:08EB0E52.8 .bits(8) 0
ROM:08EB0E54 DCW 0 ; objUnion.normal.trainerType
ROM:08EB0E56 DCW 0 ; objUnion.normal.trainerRange_berryTreeId
ROM:08EB0E58 DCD unk_8EB0E60 ; script
ROM:08EB0E5C DCW 0x306 ; flagId
ROM:08EB0E5E DCB 0xFF, 0xFF
We can see that the first object event was copied without modifications, and that a second entry was added. Of this second object, the most interesting field is the non-empty script
member. Following this pointer we end up in data that is interpreted by the game’s internal scripting engine. In order to understand the script, we can look at data/script_cmd_table.inc
for the opcode numbers, and at the corresponding header implementations for the number and size of the operands. Since the script at hand is so short, manual disassembly is enough to roughly understand what is going on:
ROM:08EB0E60 byte_8EB0E60 DCB 0x69 ; lockall
ROM:08EB0E61 DCB 0x33 ; playbgm, MUS_GAME_CORNER
ROM:08EB0E62 DCW 0x111
ROM:08EB0E64 DCB 0
ROM:08EB0E65 DCB 0x4F ; applymovement OBJ_EVENT_ID_PLAYER, Common_Movement_ExclamationMark
ROM:08EB0E66 DCW 0xFF
ROM:08EB0E68 DCD Common_Movement_ExclamationMark
ROM:08EB0E6C DCB 0x51 ; waitmovement 0
ROM:08EB0E6D DCW 0
ROM:08EB0E6F DCB 0x28 ; delay 111
ROM:08EB0E70 DCW 0x6F
ROM:08EB0E72 DCB 0x16 ; setar VAR8004, EASY_CHAT_TYPE_QUESTIONNAIRE
ROM:08EB0E73 DCW 0x8004
ROM:08EB0E75 DCW 0xE
ROM:08EB0E77 DCB 0x97 ; fadescreen FADE_TO_BLACK
ROM:08EB0E78 DCB 1
ROM:08EB0E79 DCB 0x25 ; callspecial ShowEasyChatScreen (0x5f)
ROM:08EB0E7A DCW 0x5F
ROM:08EB0E7C DCB 0x97 ; fadescreen FADE_FROM_BLACK
ROM:08EB0E7D DCB 0
ROM:08EB0E7E DCB 0x37 ; fadeoutbgm
ROM:08EB0E7F DCB 0
ROM:08EB0E80 DCB 0x25 ; callspecial ??? (0x89)
ROM:08EB0E81 DCD 0x89
ROM:08EB0E85 DCB 0x21 ; compare VAR8007, 0x1337
ROM:08EB0E86 DCW 0x8007
ROM:08EB0E88 DCW 0x1337
ROM:08EB0E8A DCB 6 ; goto_if 1, unk_8eb1000
ROM:08EB0E8B DCB 1
ROM:08EB0E8C DCD unk_8EB1000
ROM:08EB0E90 DCB 0x6B ; releaseall
ROM:08EB0E91 DCB 0x2F ; playse SE_SUPER_EFFECTIVE
ROM:08EB0E92 DCW 0xE
ROM:08EB0E94 DCB 0x30 ; waitse
ROM:08EB0E95 DCB 0x97 ; fadescreen FADE_TO_BLACK
ROM:08EB0E96 DCB 1
ROM:08EB0E97 DCB 0x25 ; callspecial faint (0xc8)
ROM:08EB0E98 DCW 0xC8
ROM:08EB0E9A DCB 2 ; end
The scripts opens the questionnaire window, then calls special function with id 0x89. If that function sets VAR8007
to 0x1337
, we continue script execution at address 0x8eb1000
, otherwise the iconic “it’s super effective” sound effect is played, the screen blacks out, and the game over screen is displayed.
Let’s continue analysis of the happy path at 0x8eb1000
:
ROM:08EB1000 byte_8EB1000 DCB 0x5A ; faceplayer
ROM:08EB1001 DCB 0xB6 ; setwildbattle SPECIES_MEW, 100, ITEM_MASTER_BALL
ROM:08EB1002 DCW 0x97
ROM:08EB1004 DCB 0x64
ROM:08EB1005 DCW 1
ROM:08EB1007 DCB 0x30 ; waitse
ROM:08EB1008 DCB 0xA1 ; playmoncry SPECIES_MEW, CRY_MODE_ENCOUNTER
ROM:08EB1009 DCW 0x97
ROM:08EB100B DCW 2
ROM:08EB100D DCB 0xC5 ; waitmoncry
ROM:08EB100E DCB 0x28 ; delay 111
ROM:08EB100F DCW 0x6F
ROM:08EB1011 DCB 0x29 ; setflag FLAG_SYS_SPECIAL_WILD_BATTLE
ROM:08EB1012 DCW 0x807
ROM:08EB1014 DCB 0x25 ; callspecial StartLegendaryBattle
ROM:08EB1015 DCW 0x138
ROM:08EB1017 DCB 0x27 ; waitstate
ROM:08EB1018 DCB 0x2A ; clearflag FLAG_SYS_SPECIAL_WILD_BATTLE
ROM:08EB1019 DCW 0x807
ROM:08EB101B DCB 0x29 ; setflag 0x306
ROM:08EB101C DCW 0x306
ROM:08EB101E DCB 0x26 ; specialvar VAR_RESULT, GetBattleOutcome
ROM:08EB101F DCW 0x800D
ROM:08EB1021 DCW 0xB4
ROM:08EB1023 DCB 0x21 ; compare VAR_RESULT, B_OUTCOME_CAUGHT
ROM:08EB1024 DCW 0x800D
ROM:08EB1026 DCW 7
ROM:08EB1028 DCB 6 ; goto_if VAR_RESULT, 1, 0x8eb1040
ROM:08EB1029 DCB 1
ROM:08EB102A DCD unk_8EB1040
ROM:08EB102E DCB 5 ; goto EventScript_RemoveStaticMon
ROM:08EB102F DCD EventScript_RemoveStaticMon
This starts a battle against a wild Mew. If the player catches it, script execution continues at 0x8eb1040
:
ROM:08EB1040 byte_8EB1040 DCB 0x67 ; message 0x8eb1058
ROM:08EB1041 DCD unk_8EB1058
ROM:08EB1045 DCB 0x31 ; playfanfare MUS_OBTAIN_KEY_ITEM
ROM:08EB1046 DCW 0x103
ROM:08EB1048 DCB 0x32 ; waitfanfare
ROM:08EB1049 DCB 0x66 ; waitmessage
ROM:08EB104A DCB 0x6D ; waitbuttonpress
ROM:08EB104B DCB 0x68 ; closemessage
ROM:08EB104C DCB 5 ; goto EventScript_RemoveStaticMon
ROM:08EB104D DCD EventScript_RemoveStaticMon
Nice! This prints the message stored at address 0x8eb1058
and plays a fanfare. So, just a matter of deciphering the message, surely it will contain the flag, right?
ROM:08EB1058 msg_flag DCB 0xCE, 0xDC, 0xDD, 0xE7, 0, 0xD7, 0xD5, 0xE2, 0xE2
ROM:08EB1058 ; DATA XREF: ROM:08EB1041↑o
ROM:08EB1061 DCB 0xE3, 0xE8, 0, 0xD6, 0xD9, 0, 0xE1, 0xED, 0, 0xD8
ROM:08EB106B DCB 0xD9, 0xE7, 0xE8, 0xDD, 0xE2, 0xED, 0xAD, 0xFE, 0xC8
ROM:08EB1074 DCB 0xE3, 0xEB, 0, 0xE8, 0xDC, 0xD9, 0, 0xE7, 0xD9, 0xE6
ROM:08EB107E DCB 0xDD, 0xE3, 0xE9, 0xE7, 0, 0xE8, 0xD9, 0xE7, 0xE8
ROM:08EB1087 DCB 0xDD, 0xE2, 0xDB, 0, 0xD6, 0xD9, 0xDB, 0xDD, 0xE2
ROM:08EB1090 DCB 0xE7, 0xAD
The game uses a special charset, the translation can be found in include/characters.h
:
// [ ... snip ... ]
#define CHAR_A 0xBB
#define CHAR_B 0xBC
#define CHAR_C 0xBD
#define CHAR_D 0xBE
#define CHAR_E 0xBF
#define CHAR_F 0xC0
#define CHAR_G 0xC1
#define CHAR_H 0xC2
#define CHAR_I 0xC3
#define CHAR_J 0xC4
#define CHAR_K 0xC5
#define CHAR_L 0xC6
#define CHAR_M 0xC7
#define CHAR_N 0xC8
#define CHAR_O 0xC9
#define CHAR_P 0xCA
#define CHAR_Q 0xCB
#define CHAR_R 0xCC
#define CHAR_S 0xCD
#define CHAR_T 0xCE
#define CHAR_U 0xCF
#define CHAR_V 0xD0
#define CHAR_W 0xD1
#define CHAR_X 0xD2
#define CHAR_Y 0xD3
#define CHAR_Z 0xD4
#define CHAR_a 0xD5
#define CHAR_b 0xD6
#define CHAR_c 0xD7
#define CHAR_d 0xD8
#define CHAR_e 0xD9
#define CHAR_f 0xDA
#define CHAR_g 0xDB
#define CHAR_h 0xDC
#define CHAR_i 0xDD
#define CHAR_j 0xDE
#define CHAR_k 0xDF
#define CHAR_l 0xE0
#define CHAR_m 0xE1
#define CHAR_n 0xE2
#define CHAR_o 0xE3
#define CHAR_p 0xE4
#define CHAR_q 0xE5
#define CHAR_r 0xE6
#define CHAR_s 0xE7
#define CHAR_t 0xE8
#define CHAR_u 0xE9
#define CHAR_v 0xEA
#define CHAR_w 0xEB
#define CHAR_x 0xEC
#define CHAR_y 0xED
#define CHAR_z 0xEE
// [ ... snip ... ]
Quickly writing a python script for translation
CHARS = {
' ': 0x00, 'À': 0x01, 'Á': 0x02, 'Â': 0x03, 'Ç': 0x04, 'È': 0x05, 'É': 0x06,
'Ê': 0x07, 'Ë': 0x08, 'Ì': 0x09, 'Î': 0x0B, 'Ï': 0x0C, 'Ò': 0x0D, 'Ó': 0x0E,
'Ô': 0x0F, 'Œ': 0x10, 'Ù': 0x11, 'Ú': 0x12, 'Û': 0x13, 'Ñ': 0x14, 'ß': 0x15,
'à': 0x16, 'á': 0x17, 'ç': 0x19, 'è': 0x1A, 'é': 0x1B, 'ê': 0x1C, 'ë': 0x1D,
'ì': 0x1E, 'î': 0x20, 'ï': 0x21, 'ò': 0x22, 'ó': 0x23, 'ô': 0x24, 'œ': 0x25,
'ù': 0x26, 'ú': 0x27, 'û': 0x28, 'ñ': 0x29, 'º': 0x2A, 'ª': 0x2B, '&': 0x2D,
'+': 0x2E, '=': 0x35, ';': 0x36, '¿': 0x51, '¡': 0x52, 'Í': 0x5A, '%': 0x5B,
'(': 0x5C, ')': 0x5D, 'â': 0x68, 'í': 0x6F, '<': 0x85, '>': 0x86, '0': 0xA1,
'1': 0xA2, '2': 0xA3, '3': 0xA4, '4': 0xA5, '5': 0xA6, '6': 0xA7, '7': 0xA8,
'8': 0xA9, '9': 0xAA, '!': 0xAB, '?': 0xAC, '.': 0xAD, '-': 0xAE, '…': 0xB0,
'“': 0xB1, '”': 0xB2, '‘': 0xB3, '\\': 0xB4, '♂': 0xB5, '♀': 0xB6, '¥': 0xB7,
',': 0xB8, '×': 0xB9, '/': 0xBA, 'A': 0xBB, 'B': 0xBC, 'C': 0xBD, 'D': 0xBE,
'E': 0xBF, 'F': 0xC0, 'G': 0xC1, 'H': 0xC2, 'I': 0xC3, 'J': 0xC4, 'K': 0xC5,
'L': 0xC6, 'M': 0xC7, 'N': 0xC8, 'O': 0xC9, 'P': 0xCA, 'Q': 0xCB, 'R': 0xCC,
'S': 0xCD, 'T': 0xCE, 'U': 0xCF, 'V': 0xD0, 'W': 0xD1, 'X': 0xD2, 'Y': 0xD3,
'Z': 0xD4, 'a': 0xD5, 'b': 0xD6, 'c': 0xD7, 'd': 0xD8, 'e': 0xD9, 'f': 0xDA,
'g': 0xDB, 'h': 0xDC, 'i': 0xDD, 'j': 0xDE, 'k': 0xDF, 'l': 0xE0, 'm': 0xE1,
'n': 0xE2, 'o': 0xE3, 'p': 0xE4, 'q': 0xE5, 'r': 0xE6, 's': 0xE7, 't': 0xE8,
'u': 0xE9, 'v': 0xEA, 'w': 0xEB, 'x': 0xEC, 'y': 0xED, 'z': 0xEE, '▶': 0xEF,
':': 0xF0, 'Ä': 0xF1, 'Ö': 0xF2, 'Ü': 0xF3, 'ä': 0xF4, 'ö': 0xF5, 'ü': 0xF6,
'\n': 0xFE
}
def bytes2msg(bs):
return "".join([ [ k for k in CHARS.keys() if CHARS[k] == b ][0] for b in bs ])
msg_flag = bytes([ 0xCE, 0xDC, 0xDD, 0xE7, 0x00, 0xD7, 0xD5, 0xE2, 0xE2, 0xE3,
0xE8, 0x00, 0xD6, 0xD9, 0x00, 0xE1, 0xED, 0x00, 0xD8, 0xD9,
0xE7, 0xE8, 0xDD, 0xE2, 0xED, 0xAD, 0xFE, 0xC8, 0xE3, 0xEB,
0x00, 0xE8, 0xDC, 0xD9, 0x00, 0xE7, 0xD9, 0xE6, 0xDD, 0xE3,
0xE9, 0xE7, 0x00, 0xE8, 0xD9, 0xE7, 0xE8, 0xDD, 0xE2, 0xDB,
0x00, 0xD6, 0xD9, 0xDB, 0xDD, 0xE2, 0xE7, 0xAD
])
print(bytes2msg(msg_flag))
yields the following:
This cannot be my destiny.
Now the serious testing begins.
This doesn’t look like a flag at all!
Looks like we have to dig deeper. We skipped the password prompt altogether, so it kind of makes sense that getting the flag is not that easy. Recall the important part in the first script:
ROM:08EB0E80 DCB 0x25 ; callspecial ??? (0x89)
ROM:08EB0E81 DCD 0x89
ROM:08EB0E85 DCB 0x21 ; compare VAR8007, 0x1337
ROM:08EB0E86 DCW 0x8007
ROM:08EB0E88 DCW 0x1337
ROM:08EB0E8A DCB 6 ; goto_if 1, unk_8eb1000
ROM:08EB0E8B DCB 1
ROM:08EB0E8C DCD unk_8EB1000
callspecial
really just interprets its argument as an index into a big function pointer table
and transfers control:
int ScrCmd_special()
{
_DWORD *v0; // r1
v0 = (_UNKNOWN **)((char *)&gSpecials + ((4 * ScriptReadHalfword()) & 0x3FFFF));
if ( v0 >= &gStdScripts )
((void (__fastcall *)(const char *, int, const char *, int))AGBAssert)(
"C:/WORK/POKeFRLG/Src/pm_lgfr_ose/source/scrcmd.c",
241,
"0",
1);
else
call_via_r0(*v0);
return 0;
}
Doing the math, we find gSpecials + 4 * 0x89 = 0x0815FFD8
. Rings a bell? Sure it does. In the original patch sequence, we had
0x0815fdb4 gSpecials
0x0815ffd8 0xa9 -> 0x88
0x0815ffd9 0xad -> 0x88
0x0815ffda 0x0c -> 0xeb
0x0815ffdb 0x08 == 0x08
meaning that particular special event 0x89
is redirected to location 0x08eb8888
. The function inserted at this address looks as follows:
int special_0x89()
{
int result; // r0
__int16 v1; // r3
result = *(unsigned __int16 *)(gSaveBlock1Ptr + 0x3458);
if ( result == 0x121E )
{
result = *(unsigned __int16 *)(gSaveBlock1Ptr + 0x345A);
if ( result == 0x1603 )
{
result = *(unsigned __int16 *)(gSaveBlock1Ptr + 0x345C);
if ( result == 0xA02 )
{
result = *(unsigned __int16 *)(gSaveBlock1Ptr + 0x345E);
v1 = 0x1337;
if ( result != 0x1E1B )
v1 = 0;
}
else
{
v1 = 0;
}
}
else
{
v1 = 0;
}
}
else
{
v1 = 0;
}
*gSpecialVar_0x8007 = v1;
return result;
}
That looks quite promising: Check four locations against constants, and if they all match, assign 0x1337
to the location holding VAR8007
(remember how the first stage script checks this variable being 0x1337
to determine whether to start the Mew battle? (see 0x08eb0e85
)). So, if we manage to figure out the meaning of those checks we should be good to go, right?
Assuming those four locations hold the responses we picked in the questionnaire, we can look up 0x121e
, 0x1603
, 0x0a02
, 0x1e1b
in include/easy_chat.h
and get EC_WORD_NEVER
, EC_WORD_GIVES
, EC_WORD_YOU
, EC_WORD_UP
. Unsurprisingly, using NEVER GIVES YOU UP
as password does not trigger the Mew battle and we still don’t get a flag.
Clearly, some shenanigans are at work. The code that the patch adds to the game sets a return value of 0x1337
, still some component in the emulator seems to intercept the function and set the value back to zero. (To confirm this suspicion we could run the ROM in a different emulator and find that the rickroll passes the check there.)
A stupid search for instructions using 0x1337
as an immediate turns up exactly one match in the emulator binary at 0x1407e8869
:
.text:00000001407E8862 mov rax, cs:off_1411F6180
.text:00000001407E8869 mov word ptr [rax+0Ch], 1337h
0x1411f6180
holds a pointer to the register file, so the write of constant 0x1337
would happen to the emulated R3
(0x0c / 4 = 3
). Decompiling this function in the emulator, we figure out that we’re in CPULoop
, and that there’s an awful lot of vector instructions gated by an if ( armNextPc_ == 0x8EB88B0 )
. Checking this virtual address in the ROM, we get confirmation that the emulator indeed patches the return value of the password check function:
ROM:08EB88B0 STRH R3, [R1]
ROM:08EB88B4 BX LR
We also find a code path in the emulator that can set the return value to zero (0x1407E82C7
). This is very likely the reason why NEVER GIVES YOU UP
isn’t accepted as a password, despite the game logic validating it as correct. Looks like we have to deal with the vector instructions and figure out what the input for the real password check in the emulator should be:
if ( armNextPc_ == 0x8EB88B0 )
{
v222 = 0x8EB88B0;
v11 = _mm_loadu_si128((const __m128i *)&xmmword_141076B30);
v12 = _mm_loadu_si128((const __m128i *)&xmmword_141076B40);
v210 = _mm_srli_epi64(_mm_loadu_si128((const __m128i *)&xmmword_141076B10), 0x20u);
v13 = *(_DWORD *)(unk_1418689A0 + 0x5008LL);
v211 = _mm_srli_epi64(_mm_loadu_si128((const __m128i *)&xmmword_141076B60), 0x20u);
LOWORD(v7) = *(_WORD *)(g_workRAM + ((v13 + 0x3458) & 0x3FFFE));
v218 = _mm_srli_epi64(_mm_loadu_si128((const __m128i *)&xmmword_141076B70), 0x20u);
v14 = *(unsigned __int16 *)(g_workRAM + ((v13 + 0x345A) & 0x3FFFE));
LOWORD(v15) = *(_WORD *)(g_workRAM + ((v13 + 0x345C) & 0x3FFFE));
v212 = _mm_srli_epi64(_mm_loadu_si128((const __m128i *)&xmmword_141076B80), 0x20u);
v16 = *(unsigned __int16 *)(g_workRAM + ((v13 + 0x345E) & 0x3FFFE));
v219 = _mm_srli_epi64(_mm_loadu_si128((const __m128i *)&xmmword_141076B90), 0x20u);
v17 = v16;
v213 = _mm_srli_epi64(_mm_loadu_si128((const __m128i *)&xmmword_141076BA0), 0x20u);
v226 = (unsigned __int16)v7
| (((unsigned __int16)v14 | (((unsigned __int16)v15 | (unsigned __int64)(v16 << 16)) << 16)) << 16);
v18 = L"VENUSAURSQUIRTLECATERPIEBEEDRILLRATICATENIDORANFNIDORINANIDORANMNIDORINONIDOKINGCLEFAIRYCLEFABLEPARASECTVENO"
"MOTHPRIMEAPEARCANINEALAKAZAMGRAVELERRAPIDASHSLOWPOKEMAGNETONSHELLDERCLOYSTERMAGIKARPGYARADOSVAPOREONKABUTOP"
"SARTICUNOMEGANIUMTOTODILECROCONAWHOOTHOOTSPINARAKCHINCHOUAMPHAROSPOLITOEDSKIPLOOMJUMPLUFFSUNFLORAQUAGSIRESL"
"OWKINGSNUBBULLGRANBULLQWILFISHURSARINGMAGCARGOREMORAIDDELIBIRDSKARMORYHOUNDOURHOUNDOOMPORYGON2STANTLERSMEAR"
"GLESMOOCHUMLARVITAR";
v19 = *(unsigned __int16 *)(g_workRAM + (v13 & 0x3FFFE));
v20 = *(unsigned __int16 *)(g_workRAM + ((v13 + 2) & 0x3FFFE));
LOWORD(v21) = *(_WORD *)(g_workRAM + (v13 & 0x3FFFE));
v22 = *(unsigned __int16 *)(g_workRAM + ((v13 + 4) & 0x3FFFE));
v227 = (v22 << 32) | v19 | (unsigned int)(v20 << 16);
v23 = 0;
while ( 1 )
{
v24 = _mm_shuffle_epi32(
_mm_cvtsi32_si128(
(unsigned __int16)v7
* ((unsigned __int16)v7 * (unsigned int)(unsigned __int16)v7 % 0xF001
* ((unsigned __int16)v7 * (unsigned int)(unsigned __int16)v7 % 0xF001)
% 0xF001
* ((unsigned __int16)v7 * (unsigned int)(unsigned __int16)v7 % 0xF001)
% 0xF001)
% 0xF001),
0);
v25 = _mm_srli_epi64(v24, 0x20u);
v26 = _mm_shuffle_epi32(_mm_mul_epu32(_mm_loadu_si128(&v214), v25), 8);
v27 = _mm_shuffle_epi32(
_mm_cvtsi32_si128(v14
* (v14 * v14 % 0xF001u * (v14 * v14 % 0xF001u) % 0xF001 * (v14 * v14 % 0xF001u) % 0xF001) % 0xF001),
0);
v28 = _mm_shuffle_epi32(
_mm_cvtsi32_si128(
(unsigned __int16)v15
* ((unsigned __int16)v15 * (unsigned int)(unsigned __int16)v15 % 0xF001
* ((unsigned __int16)v15 * (unsigned int)(unsigned __int16)v15 % 0xF001)
% 0xF001
* ((unsigned __int16)v15 * (unsigned int)(unsigned __int16)v15 % 0xF001)
% 0xF001)
% 0xF001),
0);
v29 = _mm_shuffle_epi32(
_mm_cvtsi32_si128(v17
* (v17 * v17 % 0xF001u * (v17 * v17 % 0xF001u * (v17 * v17 % 0xF001u) % 0xF001) % 0xF001) % 0xF001),
0);
v30 = _mm_shuffle_epi32(
_mm_cvtsi32_si128(
(unsigned __int16)v21
* ((unsigned __int16)v21 * (unsigned int)(unsigned __int16)v21 % 0xF001
* ((unsigned __int16)v21 * (unsigned int)(unsigned __int16)v21 % 0xF001)
% 0xF001
* ((unsigned __int16)v21 * (unsigned int)(unsigned __int16)v21 % 0xF001)
% 0xF001)
% 0xF001),
0);
v31 = _mm_shuffle_epi32(
_mm_cvtsi32_si128(v20 * v20 % 0xF001u * (v20 * v20 % 0xF001u) % 0xF001 * (v20 * v20 % 0xF001u) % 0xF001
* v20 % 0xF001),
0);
v216 = _mm_shuffle_epi32(
_mm_cvtsi32_si128(
(unsigned __int16)v22
* ((unsigned __int16)v22 * (unsigned int)(unsigned __int16)v22 % 0xF001
* ((unsigned __int16)v22 * (unsigned int)(unsigned __int16)v22 % 0xF001)
% 0xF001
* ((unsigned __int16)v22 * (unsigned int)(unsigned __int16)v22 % 0xF001)
% 0xF001)
% 0xF001),
0);
v32 = _mm_unpacklo_epi32(_mm_shuffle_epi32(_mm_mul_epu32(v24, (__m128i)xmmword_141076B00), 8), v26);
v217 = _mm_shuffle_epi32(
_mm_cvtsi32_si128(
v23
* (v23 * (unsigned int)v23 % 0xF001
* (v23 * (unsigned int)v23 % 0xF001 * (v23 * (unsigned int)v23 % 0xF001) % 0xF001)
% 0xF001)
% 0xF001),
0);
v33 = _mm_unpacklo_epi32(
_mm_shuffle_epi32(_mm_mul_epu32(v24, (__m128i)xmmword_141076B10), 8),
_mm_shuffle_epi32(_mm_mul_epu32(v25, v210), 8));
v34 = _mm_or_si128(
_mm_shuffle_epi8(_mm_mul_epu32(v32, v0), v11),
_mm_shuffle_epi8(_mm_mul_epu32(_mm_srli_epi64(v32, 0x20u), v0), v12));
v35 = _mm_or_si128(
_mm_shuffle_epi8(_mm_mul_epu32(v33, v0), v11),
_mm_shuffle_epi8(_mm_mul_epu32(_mm_srli_epi64(v33, 0x20u), v0), v12));
v36 = _mm_srli_epi32(_mm_add_epi32(_mm_srli_epi32(_mm_sub_epi32(v32, v34), 1u), v34), 0xFu);
v37 = _mm_sub_epi32(v32, _mm_add_epi32(v36, _mm_slli_epi32(_mm_sub_epi32(_mm_slli_epi32(v36, 4u), v36), 0xCu)));
v38 = _mm_srli_epi32(_mm_add_epi32(_mm_srli_epi32(_mm_sub_epi32(v33, v35), 1u), v35), 0xFu);
v39 = _mm_sub_epi32(v33, _mm_add_epi32(v38, _mm_slli_epi32(_mm_sub_epi32(_mm_slli_epi32(v38, 4u), v38), 0xCu)));
v40 = _mm_or_si128(
_mm_shuffle_epi8(_mm_mul_epu32(v37, v0), v11),
_mm_shuffle_epi8(_mm_mul_epu32(_mm_srli_epi64(v37, 0x20u), v0), v12));
v41 = _mm_srli_epi64(v27, 0x20u);
v42 = _mm_or_si128(
_mm_shuffle_epi8(_mm_mul_epu32(v39, v0), v11),
_mm_shuffle_epi8(_mm_mul_epu32(_mm_srli_epi64(v39, 0x20u), v0), v12));
v43 = _mm_unpacklo_epi32(
_mm_shuffle_epi32(_mm_mul_epu32(v27, (__m128i)xmmword_141076B50), 8),
_mm_shuffle_epi32(_mm_mul_epu32(_mm_loadu_si128(&v215), v41), 8));
v44 = _mm_unpacklo_epi32(
_mm_shuffle_epi32(_mm_mul_epu32(v27, (__m128i)xmmword_141076B60), 8),
_mm_shuffle_epi32(_mm_mul_epu32(v41, v211), 8));
v45 = _mm_or_si128(
_mm_shuffle_epi8(_mm_mul_epu32(v43, v0), v11),
_mm_shuffle_epi8(_mm_mul_epu32(_mm_srli_epi64(v43, 0x20u), v0), v12));
v46 = _mm_or_si128(
_mm_shuffle_epi8(_mm_mul_epu32(v44, v0), v11),
_mm_shuffle_epi8(_mm_mul_epu32(_mm_srli_epi64(v44, 0x20u), v0), v12));
v47 = _mm_srli_epi32(_mm_add_epi32(_mm_srli_epi32(_mm_sub_epi32(v37, v40), 1u), v40), 0xFu);
v48 = v43;
v49 = _mm_srli_epi32(_mm_add_epi32(_mm_srli_epi32(_mm_sub_epi32(v43, v45), 1u), v45), 0xFu);
v50 = _mm_add_epi32(
_mm_sub_epi32(
v37,
_mm_add_epi32(_mm_slli_epi32(_mm_sub_epi32(_mm_slli_epi32(v49, 4u), v49), 0xCu), v49)),
_mm_sub_epi32(
v48,
_mm_add_epi32(v47, _mm_slli_epi32(_mm_sub_epi32(_mm_slli_epi32(v47, 4u), v47), 0xCu))));
v51 = _mm_srli_epi32(_mm_add_epi32(_mm_srli_epi32(_mm_sub_epi32(v39, v42), 1u), v42), 0xFu);
v52 = v44;
v53 = _mm_srli_epi32(_mm_add_epi32(_mm_srli_epi32(_mm_sub_epi32(v44, v46), 1u), v46), 0xFu);
v54 = _mm_add_epi32(
_mm_sub_epi32(
v39,
_mm_add_epi32(_mm_slli_epi32(_mm_sub_epi32(_mm_slli_epi32(v53, 4u), v53), 0xCu), v53)),
_mm_sub_epi32(
v52,
_mm_add_epi32(v51, _mm_slli_epi32(_mm_sub_epi32(_mm_slli_epi32(v51, 4u), v51), 0xCu))));
v55 = _mm_loadu_si128(&v218);
v56 = _mm_or_si128(
_mm_shuffle_epi8(_mm_mul_epu32(v50, v0), v11),
_mm_shuffle_epi8(_mm_mul_epu32(_mm_srli_epi64(v50, 0x20u), v0), v12));
v57 = _mm_or_si128(
_mm_shuffle_epi8(_mm_mul_epu32(v54, v0), v11),
_mm_shuffle_epi8(_mm_mul_epu32(_mm_srli_epi64(v54, 0x20u), v0), v12));
v58 = _mm_srli_epi64(v28, 0x20u);
v59 = _mm_unpacklo_epi32(
_mm_shuffle_epi32(_mm_mul_epu32(v28, (__m128i)xmmword_141076B70), 8),
_mm_shuffle_epi32(_mm_mul_epu32(v55, v58), 8));
v60 = _mm_unpacklo_epi32(
_mm_shuffle_epi32(_mm_mul_epu32(v28, (__m128i)xmmword_141076B80), 8),
_mm_shuffle_epi32(_mm_mul_epu32(v58, v212), 8));
v61 = _mm_or_si128(
_mm_shuffle_epi8(_mm_mul_epu32(v59, v0), v11),
_mm_shuffle_epi8(_mm_mul_epu32(_mm_srli_epi64(v59, 0x20u), v0), v12));
v62 = _mm_or_si128(
_mm_shuffle_epi8(_mm_mul_epu32(v60, v0), v11),
_mm_shuffle_epi8(_mm_mul_epu32(_mm_srli_epi64(v60, 0x20u), v0), v12));
v63 = _mm_srli_epi32(_mm_add_epi32(_mm_srli_epi32(_mm_sub_epi32(v50, v56), 1u), v56), 0xFu);
v64 = _mm_sub_epi32(v50, _mm_add_epi32(_mm_slli_epi32(_mm_sub_epi32(_mm_slli_epi32(v63, 4u), v63), 0xCu), v63));
v65 = _mm_srli_epi32(_mm_add_epi32(_mm_srli_epi32(_mm_sub_epi32(v59, v61), 1u), v61), 0xFu);
v66 = _mm_add_epi32(
v64,
_mm_sub_epi32(
v59,
_mm_add_epi32(_mm_slli_epi32(_mm_sub_epi32(_mm_slli_epi32(v65, 4u), v65), 0xCu), v65)));
v67 = _mm_srli_epi32(_mm_add_epi32(_mm_srli_epi32(_mm_sub_epi32(v54, v57), 1u), v57), 0xFu);
v68 = _mm_sub_epi32(v54, _mm_add_epi32(_mm_slli_epi32(_mm_sub_epi32(_mm_slli_epi32(v67, 4u), v67), 0xCu), v67));
v69 = _mm_srli_epi32(_mm_add_epi32(_mm_srli_epi32(_mm_sub_epi32(v60, v62), 1u), v62), 0xFu);
v70 = _mm_add_epi32(
v68,
_mm_sub_epi32(
v60,
_mm_add_epi32(_mm_slli_epi32(_mm_sub_epi32(_mm_slli_epi32(v69, 4u), v69), 0xCu), v69)));
v71 = _mm_loadu_si128(&v219);
v72 = _mm_or_si128(
_mm_shuffle_epi8(_mm_mul_epu32(v66, v0), v11),
_mm_shuffle_epi8(_mm_mul_epu32(_mm_srli_epi64(v66, 0x20u), v0), v12));
v73 = _mm_or_si128(
_mm_shuffle_epi8(_mm_mul_epu32(v70, v0), v11),
_mm_shuffle_epi8(_mm_mul_epu32(_mm_srli_epi64(v70, 0x20u), v0), v12));
v74 = _mm_srli_epi64(v29, 0x20u);
v75 = _mm_unpacklo_epi32(
_mm_shuffle_epi32(_mm_mul_epu32(v29, (__m128i)xmmword_141076B90), 8),
_mm_shuffle_epi32(_mm_mul_epu32(v71, v74), 8));
v76 = _mm_unpacklo_epi32(
_mm_shuffle_epi32(_mm_mul_epu32(v29, (__m128i)xmmword_141076BA0), 8),
_mm_shuffle_epi32(_mm_mul_epu32(v74, v213), 8));
v77 = _mm_or_si128(
_mm_shuffle_epi8(_mm_mul_epu32(v75, v0), v11),
_mm_shuffle_epi8(_mm_mul_epu32(_mm_srli_epi64(v75, 0x20u), v0), v12));
v78 = _mm_or_si128(
_mm_shuffle_epi8(_mm_mul_epu32(v76, v0), v11),
_mm_shuffle_epi8(_mm_mul_epu32(_mm_srli_epi64(v76, 0x20u), v0), v12));
v79 = _mm_srli_epi32(_mm_add_epi32(_mm_srli_epi32(_mm_sub_epi32(v66, v72), 1u), v72), 0xFu);
v80 = _mm_sub_epi32(v66, _mm_add_epi32(_mm_slli_epi32(_mm_sub_epi32(_mm_slli_epi32(v79, 4u), v79), 0xCu), v79));
v81 = _mm_add_epi32(_mm_srli_epi32(_mm_sub_epi32(v75, v77), 1u), v77);
v82 = _mm_loadu_si128(&v216);
v83 = _mm_srli_epi32(v81, 0xFu);
v84 = _mm_add_epi32(
v80,
_mm_sub_epi32(
v75,
_mm_add_epi32(_mm_slli_epi32(_mm_sub_epi32(_mm_slli_epi32(v83, 4u), v83), 0xCu), v83)));
v85 = _mm_srli_epi32(_mm_add_epi32(_mm_srli_epi32(_mm_sub_epi32(v70, v73), 1u), v73), 0xFu);
v86 = _mm_add_epi32(_mm_slli_epi32(_mm_sub_epi32(_mm_slli_epi32(v85, 4u), v85), 0xCu), v85);
v87 = _mm_srli_epi32(_mm_add_epi32(_mm_srli_epi32(_mm_sub_epi32(v76, v78), 1u), v78), 0xFu);
v88 = _mm_add_epi32(
_mm_sub_epi32(v70, v86),
_mm_sub_epi32(
v76,
_mm_add_epi32(_mm_slli_epi32(_mm_sub_epi32(_mm_slli_epi32(v87, 4u), v87), 0xCu), v87)));
v89 = _mm_or_si128(
_mm_shuffle_epi8(_mm_mul_epu32(v84, v0), v11),
_mm_shuffle_epi8(_mm_mul_epu32(_mm_srli_epi64(v84, 0x20u), v0), v12));
v90 = _mm_or_si128(
_mm_shuffle_epi8(_mm_mul_epu32(v88, v0), v11),
_mm_shuffle_epi8(_mm_mul_epu32(_mm_srli_epi64(v88, 0x20u), v0), v12));
v91 = _mm_srli_epi64(v30, 0x20u);
v92 = _mm_unpacklo_epi32(
_mm_shuffle_epi32(_mm_mul_epu32(v30, (__m128i)xmmword_141076B10), 8),
_mm_shuffle_epi32(_mm_mul_epu32(_mm_loadu_si128(&v210), v91), 8));
v93 = _mm_unpacklo_epi32(
_mm_shuffle_epi32(_mm_mul_epu32(v30, (__m128i)xmmword_141076B00), 8),
_mm_shuffle_epi32(_mm_mul_epu32(v91, v214), 8));
v94 = _mm_or_si128(
_mm_shuffle_epi8(_mm_mul_epu32(v92, v0), v11),
_mm_shuffle_epi8(_mm_mul_epu32(_mm_srli_epi64(v92, 0x20u), v0), v12));
v95 = _mm_or_si128(
_mm_shuffle_epi8(_mm_mul_epu32(v93, v0), v11),
_mm_shuffle_epi8(_mm_mul_epu32(_mm_srli_epi64(v93, 0x20u), v0), v12));
v96 = _mm_srli_epi32(_mm_add_epi32(_mm_srli_epi32(_mm_sub_epi32(v84, v89), 1u), v89), 0xFu);
v97 = _mm_sub_epi32(v84, _mm_add_epi32(_mm_slli_epi32(_mm_sub_epi32(_mm_slli_epi32(v96, 4u), v96), 0xCu), v96));
v98 = _mm_srli_epi32(_mm_add_epi32(_mm_srli_epi32(_mm_sub_epi32(v92, v94), 1u), v94), 0xFu);
v99 = _mm_add_epi32(
v97,
_mm_sub_epi32(
v92,
_mm_add_epi32(_mm_slli_epi32(_mm_sub_epi32(_mm_slli_epi32(v98, 4u), v98), 0xCu), v98)));
v100 = _mm_srli_epi32(_mm_add_epi32(_mm_srli_epi32(_mm_sub_epi32(v88, v90), 1u), v90), 0xFu);
v101 = _mm_srli_epi64(v31, 0x20u);
v102 = _mm_add_epi32(_mm_slli_epi32(_mm_sub_epi32(_mm_slli_epi32(v100, 4u), v100), 0xCu), v100);
v103 = _mm_srli_epi32(_mm_add_epi32(_mm_srli_epi32(_mm_sub_epi32(v93, v95), 1u), v95), 0xFu);
v104 = _mm_add_epi32(
_mm_sub_epi32(v88, v102),
_mm_sub_epi32(
v93,
_mm_add_epi32(_mm_slli_epi32(_mm_sub_epi32(_mm_slli_epi32(v103, 4u), v103), 0xCu), v103)));
v105 = _mm_or_si128(
_mm_shuffle_epi8(_mm_mul_epu32(v99, v0), v11),
_mm_shuffle_epi8(_mm_mul_epu32(_mm_srli_epi64(v99, 0x20u), v0), v12));
v106 = _mm_unpacklo_epi32(
_mm_shuffle_epi32(_mm_mul_epu32(v31, (__m128i)xmmword_141076B60), 8),
_mm_shuffle_epi32(_mm_mul_epu32(_mm_loadu_si128(&v211), v101), 8));
v107 = _mm_or_si128(
_mm_shuffle_epi8(_mm_mul_epu32(v104, v0), v11),
_mm_shuffle_epi8(_mm_mul_epu32(_mm_srli_epi64(v104, 0x20u), v0), v12));
v108 = _mm_unpacklo_epi32(
_mm_shuffle_epi32(_mm_mul_epu32(v31, (__m128i)xmmword_141076B50), 8),
_mm_shuffle_epi32(_mm_mul_epu32(v101, v215), 8));
v109 = _mm_or_si128(
_mm_shuffle_epi8(_mm_mul_epu32(v106, v0), v11),
_mm_shuffle_epi8(_mm_mul_epu32(_mm_srli_epi64(v106, 0x20u), v0), v12));
v110 = _mm_or_si128(
_mm_shuffle_epi8(_mm_mul_epu32(v108, v0), v11),
_mm_shuffle_epi8(_mm_mul_epu32(_mm_srli_epi64(v108, 0x20u), v0), v12));
v111 = _mm_srli_epi32(_mm_add_epi32(_mm_srli_epi32(_mm_sub_epi32(v99, v105), 1u), v105), 0xFu);
v112 = _mm_sub_epi32(
v99,
_mm_add_epi32(_mm_slli_epi32(_mm_sub_epi32(_mm_slli_epi32(v111, 4u), v111), 0xCu), v111));
v113 = _mm_srli_epi32(_mm_add_epi32(_mm_srli_epi32(_mm_sub_epi32(v106, v109), 1u), v109), 0xFu);
v114 = _mm_add_epi32(
v112,
_mm_sub_epi32(
v106,
_mm_add_epi32(_mm_slli_epi32(_mm_sub_epi32(_mm_slli_epi32(v113, 4u), v113), 0xCu), v113)));
v115 = _mm_srli_epi32(_mm_add_epi32(_mm_srli_epi32(_mm_sub_epi32(v104, v107), 1u), v107), 0xFu);
v116 = _mm_add_epi32(_mm_slli_epi32(_mm_sub_epi32(_mm_slli_epi32(v115, 4u), v115), 0xCu), v115);
v117 = _mm_srli_epi32(_mm_add_epi32(_mm_srli_epi32(_mm_sub_epi32(v108, v110), 1u), v110), 0xFu);
v118 = _mm_add_epi32(
_mm_sub_epi32(v104, v116),
_mm_sub_epi32(
v108,
_mm_add_epi32(_mm_slli_epi32(_mm_sub_epi32(_mm_slli_epi32(v117, 4u), v117), 0xCu), v117)));
v119 = _mm_srli_epi64(v82, 0x20u);
v120 = _mm_or_si128(
_mm_shuffle_epi8(_mm_mul_epu32(v114, v0), v11),
_mm_shuffle_epi8(_mm_mul_epu32(_mm_srli_epi64(v114, 0x20u), v0), v12));
v121 = _mm_or_si128(
_mm_shuffle_epi8(_mm_mul_epu32(v118, v0), v11),
_mm_shuffle_epi8(_mm_mul_epu32(_mm_srli_epi64(v118, 0x20u), v0), v12));
v122 = _mm_unpacklo_epi32(
_mm_shuffle_epi32(_mm_mul_epu32(v82, (__m128i)xmmword_141076B80), 8),
_mm_shuffle_epi32(_mm_mul_epu32(_mm_loadu_si128(&v212), v119), 8));
v123 = _mm_unpacklo_epi32(
_mm_shuffle_epi32(_mm_mul_epu32(v82, (__m128i)xmmword_141076B70), 8),
_mm_shuffle_epi32(_mm_mul_epu32(v119, v55), 8));
v124 = _mm_or_si128(
_mm_shuffle_epi8(_mm_mul_epu32(v122, v0), v11),
_mm_shuffle_epi8(_mm_mul_epu32(_mm_srli_epi64(v122, 0x20u), v0), v12));
v125 = _mm_or_si128(
_mm_shuffle_epi8(_mm_mul_epu32(v123, v0), v11),
_mm_shuffle_epi8(_mm_mul_epu32(_mm_srli_epi64(v123, 0x20u), v0), v12));
v126 = _mm_srli_epi32(_mm_add_epi32(_mm_srli_epi32(_mm_sub_epi32(v114, v120), 1u), v120), 0xFu);
v127 = _mm_add_epi32(_mm_slli_epi32(_mm_sub_epi32(_mm_slli_epi32(v126, 4u), v126), 0xCu), v126);
v128 = _mm_srli_epi32(_mm_add_epi32(_mm_srli_epi32(_mm_sub_epi32(v122, v124), 1u), v124), 0xFu);
v129 = _mm_add_epi32(
_mm_sub_epi32(v114, v127),
_mm_sub_epi32(
v122,
_mm_add_epi32(_mm_slli_epi32(_mm_sub_epi32(_mm_slli_epi32(v128, 4u), v128), 0xCu), v128)));
v130 = _mm_add_epi32(_mm_srli_epi32(_mm_sub_epi32(v118, v121), 1u), v121);
v131 = _mm_loadu_si128(&v217);
v132 = _mm_srli_epi32(v130, 0xFu);
v133 = _mm_sub_epi32(
v118,
_mm_add_epi32(_mm_slli_epi32(_mm_sub_epi32(_mm_slli_epi32(v132, 4u), v132), 0xCu), v132));
v134 = _mm_srli_epi32(_mm_add_epi32(_mm_srli_epi32(_mm_sub_epi32(v123, v125), 1u), v125), 0xFu);
v135 = _mm_add_epi32(
v133,
_mm_sub_epi32(
v123,
_mm_add_epi32(_mm_slli_epi32(_mm_sub_epi32(_mm_slli_epi32(v134, 4u), v134), 0xCu), v134)));
v136 = _mm_or_si128(
_mm_shuffle_epi8(_mm_mul_epu32(v129, v0), v11),
_mm_shuffle_epi8(_mm_mul_epu32(_mm_srli_epi64(v129, 0x20u), v0), v12));
v137 = _mm_srli_epi64(v131, 0x20u);
v138 = _mm_or_si128(
_mm_shuffle_epi8(_mm_mul_epu32(v135, v0), v11),
_mm_shuffle_epi8(_mm_mul_epu32(_mm_srli_epi64(v135, 0x20u), v0), v12));
v139 = _mm_unpacklo_epi32(
_mm_shuffle_epi32(_mm_mul_epu32(v131, (__m128i)xmmword_141076BA0), 8),
_mm_shuffle_epi32(_mm_mul_epu32(_mm_loadu_si128(&v213), v137), 8));
v140 = _mm_unpacklo_epi32(
_mm_shuffle_epi32(_mm_mul_epu32(v131, (__m128i)xmmword_141076B90), 8),
_mm_shuffle_epi32(_mm_mul_epu32(v137, v71), 8));
v141 = _mm_or_si128(
_mm_shuffle_epi8(_mm_mul_epu32(v139, v0), v11),
_mm_shuffle_epi8(_mm_mul_epu32(_mm_srli_epi64(v139, 0x20u), v0), v12));
v142 = _mm_or_si128(
_mm_shuffle_epi8(_mm_mul_epu32(v140, v0), v11),
_mm_shuffle_epi8(_mm_mul_epu32(_mm_srli_epi64(v140, 0x20u), v0), v12));
v143 = _mm_srli_epi32(_mm_add_epi32(_mm_srli_epi32(_mm_sub_epi32(v129, v136), 1u), v136), 0xFu);
v144 = _mm_sub_epi32(
v129,
_mm_add_epi32(_mm_slli_epi32(_mm_sub_epi32(_mm_slli_epi32(v143, 4u), v143), 0xCu), v143));
v145 = _mm_srli_epi32(_mm_add_epi32(_mm_srli_epi32(_mm_sub_epi32(v139, v141), 1u), v141), 0xFu);
v146 = _mm_add_epi32(
_mm_sub_epi32(
v139,
_mm_add_epi32(_mm_slli_epi32(_mm_sub_epi32(_mm_slli_epi32(v145, 4u), v145), 0xCu), v145)),
v144);
v147 = _mm_srli_epi32(_mm_add_epi32(_mm_srli_epi32(_mm_sub_epi32(v140, v142), 1u), v142), 0xFu);
v148 = _mm_add_epi32(_mm_slli_epi32(_mm_sub_epi32(_mm_slli_epi32(v147, 4u), v147), 0xCu), v147);
v149 = _mm_srli_epi32(_mm_add_epi32(_mm_srli_epi32(_mm_sub_epi32(v135, v138), 1u), v138), 0xFu);
v150 = _mm_add_epi32(
_mm_sub_epi32(
v135,
_mm_add_epi32(v149, _mm_slli_epi32(_mm_sub_epi32(_mm_slli_epi32(v149, 4u), v149), 0xCu))),
_mm_sub_epi32(v140, v148));
v151 = _mm_or_si128(
_mm_shuffle_epi8(_mm_mul_epu32(v146, v0), v11),
_mm_shuffle_epi8(_mm_mul_epu32(_mm_srli_epi64(v146, 0x20u), v0), v12));
v152 = _mm_or_si128(
_mm_shuffle_epi8(_mm_mul_epu32(v150, v0), v11),
_mm_shuffle_epi8(_mm_mul_epu32(_mm_srli_epi64(v150, 0x20u), v0), v12));
v153 = _mm_srli_epi32(_mm_add_epi32(_mm_srli_epi32(_mm_sub_epi32(v146, v151), 1u), v151), 0xFu);
v154 = _mm_srli_epi32(_mm_add_epi32(_mm_srli_epi32(_mm_sub_epi32(v150, v152), 1u), v152), 0xFu);
v155 = _mm_or_si128(
_mm_shuffle_epi8(
_mm_sub_epi32(
v146,
_mm_add_epi32(_mm_slli_epi32(_mm_sub_epi32(_mm_slli_epi32(v153, 4u), v153), 0xCu), v153)),
(__m128i)xmmword_141076BB0),
_mm_shuffle_epi8(
_mm_sub_epi32(
v150,
_mm_add_epi32(v154, _mm_slli_epi32(_mm_sub_epi32(_mm_slli_epi32(v154, 4u), v154), 0xCu))),
(__m128i)xmmword_141076BC0));
epi16 = _mm_extract_epi16(v155, 0);
v157 = 286256589LL * ((unsigned int)*(unsigned __int16 *)v18 + epi16);
LODWORD(v7) = *(unsigned __int16 *)v18
+ epi16
- 61441
* ((HIDWORD(v157) + (((unsigned int)*(unsigned __int16 *)v18 + epi16 - HIDWORD(v157)) >> 1)) >> 15);
v158 = _mm_extract_epi16(v155, 1);
v159 = 286256589LL * ((unsigned int)*((unsigned __int16 *)v18 + 1) + v158);
v160 = *((unsigned __int16 *)v18 + 1)
+ v158
- 61441
* ((HIDWORD(v159) + (((unsigned int)*((unsigned __int16 *)v18 + 1) + v158 - HIDWORD(v159)) >> 1)) >> 15);
v161 = _mm_extract_epi16(v155, 2);
v14 = (unsigned __int16)v160;
v162 = 286256589LL * ((unsigned int)*((unsigned __int16 *)v18 + 2) + v161);
v15 = *((unsigned __int16 *)v18 + 2)
+ v161
- 61441
* ((HIDWORD(v162) + (((unsigned int)*((unsigned __int16 *)v18 + 2) + v161 - HIDWORD(v162)) >> 1)) >> 15);
v163 = _mm_extract_epi16(v155, 3);
v164 = 286256589LL * ((unsigned int)*((unsigned __int16 *)v18 + 3) + v163);
v165 = *((unsigned __int16 *)v18 + 3)
+ v163
- 61441
* ((HIDWORD(v164) + (((unsigned int)*((unsigned __int16 *)v18 + 3) + v163 - HIDWORD(v164)) >> 1)) >> 15);
v166 = _mm_extract_epi16(v155, 4);
v17 = (unsigned __int16)v165;
v167 = 286256589LL * ((unsigned int)*((unsigned __int16 *)v18 + 4) + v166);
v21 = *((unsigned __int16 *)v18 + 4)
+ v166
- 61441
* ((HIDWORD(v167) + (((unsigned int)*((unsigned __int16 *)v18 + 4) + v166 - HIDWORD(v167)) >> 1)) >> 15);
v168 = _mm_extract_epi16(v155, 5);
v22 = 286256589LL * ((unsigned int)*((unsigned __int16 *)v18 + 5) + v168);
v169 = *((unsigned __int16 *)v18 + 5)
+ v168
- 61441
* ((HIDWORD(v22) + (((unsigned int)*((unsigned __int16 *)v18 + 5) + v168 - HIDWORD(v22)) >> 1)) >> 15);
v170 = _mm_extract_epi16(v155, 6);
v171 = 286256589LL * ((unsigned int)*((unsigned __int16 *)v18 + 6) + v170);
LODWORD(v22) = *((unsigned __int16 *)v18 + 6)
+ v170
- 61441
* ((HIDWORD(v171) + (((unsigned int)*((unsigned __int16 *)v18 + 6) + v170 - HIDWORD(v171)) >> 1)) >> 15);
v172 = _mm_extract_epi16(v155, 7);
v173 = 286256589LL * ((unsigned int)*((unsigned __int16 *)v18 + 7) + v172);
v174 = *((unsigned __int16 *)v18 + 7)
+ v172
- 61441
* ((HIDWORD(v173) + (((unsigned int)*((unsigned __int16 *)v18 + 7) + v172 - HIDWORD(v173)) >> 1)) >> 15);
v18 += 8;
v23 = v174;
if ( armInsnTable == (__int64 (__fastcall **)())v18 )
break;
v20 = (unsigned __int16)v169;
}
armNextPc_ = v222;
if ( (_WORD)v7 != 0xB020
|| (_WORD)v160 != 20628
|| (_WORD)v15 != 0xB950
|| (_WORD)v165 != 0x9B15
|| (_WORD)v21 != 0x92F0
|| (_WORD)v169 != 0x88DE
|| (_WORD)v22 != 0x9A41
|| (_WORD)v174 != 22260 )
{
unk_14186BDCC = 0;
goto LABEL_5;
}
v175 = 0;
v176 = &v233.m128i_i32[1];
v228 = 0;
v177 = _mm_loadu_si128((const __m128i *)&jpt_1408091AF);
v178 = (__int8 *)&v226;
v229 = 0;
for ( i = 18; i; --i )
*v176++ = 0;
v230 = 0;
v233.m128i_i32[0] = -1009589776;
v232 = v177;
while ( 1 )
{
v180 = v233.m128i_u32[1];
v181 = __CFADD__(v233.m128i_i32[1], 8);
v233.m128i_i32[1] += 8;
if ( v181 )
{
++v233.m128i_i32[2];
}
else
{
v182 = (v180 >> 3) & 0x3F;
if ( v182 != 63 )
goto LABEL_86;
}
v237.m128i_i8[11] = *v178;
sub_1407956D0(&v232, (char *)&v233.m128i_u64[1] + 4, v175);
v182 = 0;
v181 = 1;
LABEL_86:
v183 = &v232.m128i_i8[v182 + 28];
v184 = &v178[v181];
v185 = (unsigned int)(1 - v181);
if ( !v181 )
{
v186 = 0;
do
{
v175 = v186++;
v183[v175] = v184[v175];
}
while ( v186 < (unsigned int)v185 );
}
if ( &v227 == (__int64 *)++v178 )
{
v224 = 0x80;
v225 = _byteswap_uint64(*(unsigned __int64 *)((char *)v233.m128i_u64 + 4));
while ( 1 )
{
sub_140797850(&v232, &v224, v175, v185);
v187 = v233.m128i_u32[1];
if ( (v233.m128i_i16[2] & 0x1F8) == 0x1C0 )
break;
v224 = 0;
}
v188 = __CFADD__(v233.m128i_i32[1], 64);
v233.m128i_i32[1] += 64;
v189 = (v187 >> 3) & 0x3F;
if ( v188 || (v190 = 0, (unsigned int)(v189 + 8) > 0x3F) )
{
v188 = 64 - v189;
v190 = (unsigned int)(64 - v189);
qmemcpy(&v232.m128i_i8[v189 + 28], &v225, v190);
sub_1407956D0(&v232, (char *)&v233.m128i_u64[1] + 4, v175);
v189 = 0;
}
v191 = &v232.m128i_i8[v189 + 28];
v192 = 0;
qmemcpy(v191, (char *)&v225 + v190, (unsigned int)(8 - v188));
do
{
*((_BYTE *)&v228 + v192) = (unsigned __int32)v232.m128i_i32[(unsigned int)v192 >> 2] >> (8 * (~(_BYTE)v192 & 3));
++v192;
}
while ( v192 != 20 );
v193 = _mm_loadu_si128((const __m128i *)&xmmword_141076BE0);
LOBYTE(v194) = 0;
v195 = g_rom;
v196 = g_rom + 0xFFFFC4LL;
qmemcpy((void *)(g_rom + 0xFFFFC4LL), &unk_14106E6F8, 0x3Bu);
v197 = 0;
v232 = v193;
v233 = _mm_loadu_si128((const __m128i *)&xmmword_141076BF0);
v234 = _mm_loadu_si128((const __m128i *)&xmmword_141076C00);
v235 = _mm_loadu_si128((const __m128i *)&xmmword_141076C10);
v236 = _mm_loadu_si128((const __m128i *)&xmmword_141076C20);
v237 = _mm_loadu_si128((const __m128i *)&xmmword_141076C30);
v238 = _mm_loadu_si128((const __m128i *)&xmmword_141076C40);
v239 = _mm_loadu_si128((const __m128i *)&xmmword_141076C50);
v240 = _mm_loadu_si128((const __m128i *)&xmmword_141076C60);
v241 = _mm_loadu_si128((const __m128i *)&xmmword_141076C70);
v242 = _mm_loadu_si128((const __m128i *)&xmmword_141076C80);
v243 = _mm_loadu_si128((const __m128i *)&xmmword_141076C90);
v244 = _mm_loadu_si128((const __m128i *)&xmmword_141076CA0);
v245 = _mm_loadu_si128((const __m128i *)&xmmword_141076CB0);
v246 = _mm_loadu_si128((const __m128i *)&xmmword_141076CC0);
v247 = _mm_loadu_si128((const __m128i *)&xmmword_141076CD0);
do
{
v198 = v232.m128i_i8[v197];
v194 = (unsigned __int8)(v194 + v198 + *((_BYTE *)&v228 + v197 % 0x14));
v199 = &v232.m128i_i8[v194];
v232.m128i_i8[v197++] = *v199;
*v199 = v198;
}
while ( v197 != 256 );
v7 = v195 + 0xFFFFFF;
LOBYTE(v200) = 0;
LOBYTE(v201) = 0;
do
{
++v196;
v201 = (unsigned __int8)(v201 + 1);
v202 = &v232.m128i_i8[v201];
v203 = *v202;
v200 = (unsigned __int8)(*v202 + v200);
v204 = &v232.m128i_i8[v200];
*v202 = *v204;
*v204 = v203;
*(_BYTE *)(v196 - 1) = ~(*(_BYTE *)(v196 - 1) ^ v232.m128i_i8[(unsigned __int8)(*v202 + v203)]);
}
while ( v196 != v7 );
*(_BYTE *)(g_rom + 0xFFFFFFLL) = -18;
unk_14186BDCC = 0x1337;
break;
}
}
}
This looks pretty bad. A few key observations help us understanding the overall structure:
v7
, v14
, v15
, and v16
(or its alias v17
) hold our input words. This can be recognized via the immediate offsets used in the load operations. They are the same as in the password check function inside the ROM.while
loop runs (0x14106EB00 - 0x14106E780) / (2*8) = 56
times.v7
, v160
, v15
, v165
, v21
, v169
, v22
, v174
have to take
values 0xB020 0x5094 0xB950 0x9B15 0x92F0 0x88DE 0x9A41 0x56F4
to make the emulator write 0x1337
into the return register.The large amount of arithmetic operations in the loop are pretty repetitive. Upon closer inspection, we just find three operations:
0xf001
Multiply all 8 16-bit state elements with the matrix
[[45003, 9938, 52063, 34055, 59174, 25705, 22867, 25942],
[25942, 45003, 9938, 52063, 34055, 59174, 25705, 22867],
[22867, 25942, 45003, 9938, 52063, 34055, 59174, 25705],
[25705, 22867, 25942, 45003, 9938, 52063, 34055, 59174],
[59174, 25705, 22867, 25942, 45003, 9938, 52063, 34055],
[34055, 59174, 25705, 22867, 25942, 45003, 9938, 52063],
[52063, 34055, 59174, 25705, 22867, 25942, 45003, 9938],
[9938, 52063, 34055, 59174, 25705, 22867, 25942, 45003],]
mod 0xf001
. This can be somewhat tough to figure out, xmmword_141076B10
and xmmword_141076B50
hold the constants of the last column of the matrix and the modulo operation has been replaced by the classic combination of subtraction, integer division and multiplication.)
Add (again mod 0xf001
) a round constant to every state element. The constants probably look rather familiar to the astute reader:
>>> [ "VENUSAURSQUIRTLECATERPIEBEEDRILLRATICATENIDORANFNIDORINANIDORANMNIDORINONIDOKINGCLEFAIRYCLEFABLEPARASECTVENOMOTHPRIMEAPEARCANINEALAKAZAMGRAVELERRAPIDASHSLOWPOKEMAGNETONSHELLDERCLOYSTERMAGIKARPGYARADOSVAPOREONKABUTOPSARTICUNOMEGANIUMTOTODILECROCONAWHOOTHOOTSPINARAKCHINCHOUAMPHAROSPOLITOEDSKIPLOOMJUMPLUFFSUNFLORAQUAGSIRESLOWKINGSNUBBULLGRANBULLQWILFISHURSARINGMAGCARGOREMORAIDDELIBIRDSKARMORYHOUNDOURHOUNDOOMPORYGON2STANTLERSMEARGLESMOOCHUMLARVITAR"[i:i+8] for i in range(0 , 56*8, 8) ]
['VENUSAUR', 'SQUIRTLE', 'CATERPIE', 'BEEDRILL', 'RATICATE', 'NIDORANF', 'NIDORINA', 'NIDORANM',
'NIDORINO', 'NIDOKING', 'CLEFAIRY', 'CLEFABLE', 'PARASECT', 'VENOMOTH', 'PRIMEAPE', 'ARCANINE',
'ALAKAZAM', 'GRAVELER', 'RAPIDASH', 'SLOWPOKE', 'MAGNETON', 'SHELLDER', 'CLOYSTER', 'MAGIKARP',
'GYARADOS', 'VAPOREON', 'KABUTOPS', 'ARTICUNO', 'MEGANIUM', 'TOTODILE', 'CROCONAW', 'HOOTHOOT',
'SPINARAK', 'CHINCHOU', 'AMPHAROS', 'POLITOED', 'SKIPLOOM', 'JUMPLUFF', 'SUNFLORA', 'QUAGSIRE',
'SLOWKING', 'SNUBBULL', 'GRANBULL', 'QWILFISH', 'URSARING', 'MAGCARGO', 'REMORAID', 'DELIBIRD',
'SKARMORY', 'HOUNDOUR', 'HOUNDOOM', 'PORYGON2', 'STANTLER', 'SMEARGLE', 'SMOOCHUM', 'LARVITAR']
(Painfully) translating the code into python, we obtain the following code for the checker:
#!/usr/bin/env python3
P = 2**16-2**12+1 # 0xf001
PKMN = [ "Bulbasaur", "Ivysaur", "Venusaur", "Charmander", "Charmeleon", "Charizard",
"Squirtle", "Wartortle", "Blastoise", "Caterpie", "Metapod", "Butterfree",
"Weedle", "Kakuna", "Beedrill", "Pidgey", "Pidgeotto", "Pidgeot", "Rattata",
"Raticate", "Spearow", "Fearow", "Ekans", "Arbok", "Pikachu", "Raichu",
"Sandshrew", "Sandslash", "NidoranF", "Nidorina", "Nidoqueen", "NidoranM",
"Nidorino", "Nidoking", "Clefairy", "Clefable", "Vulpix", "Ninetales",
"Jigglypuff", "Wigglytuff", "Zubat", "Golbat", "Oddish", "Gloom", "Vileplume",
"Paras", "Parasect", "Venonat", "Venomoth", "Diglett", "Dugtrio", "Meowth",
"Persian", "Psyduck", "Golduck", "Mankey", "Primeape", "Growlithe", "Arcanine",
"Poliwag", "Poliwhirl", "Poliwrath", "Abra", "Kadabra", "Alakazam", "Machop",
"Machoke", "Machamp", "Bellsprout", "Weepinbell", "Victreebel", "Tentacool",
"Tentacruel", "Geodude", "Graveler", "Golem", "Ponyta", "Rapidash", "Slowpoke",
"Slowbro", "Magnemite", "Magneton", "Farfetch'd", "Doduo", "Dodrio", "Seel",
"Dewgong", "Grimer", "Muk", "Shellder", "Cloyster", "Gastly", "Haunter",
"Gengar", "Onix", "Drowzee", "Hypno", "Krabby", "Kingler", "Voltorb",
"Electrode", "Exeggcute", "Exeggutor", "Cubone", "Marowak", "Hitmonlee",
"Hitmonchan", "Lickitung", "Koffing", "Weezing", "Rhyhorn", "Rhydon", "Chansey",
"Tangela", "Kangaskhan", "Horsea", "Seadra", "Goldeen", "Seaking", "Staryu",
"Starmie", "Mr.", "Mime", "Scyther", "Jynx", "Electabuzz", "Magmar", "Pinsir",
"Tauros", "Magikarp", "Gyarados", "Lapras", "Ditto", "Eevee", "Vaporeon",
"Jolteon", "Flareon", "Porygon", "Omanyte", "Omastar", "Kabuto", "Kabutops",
"Aerodactyl", "Snorlax", "Articuno", "Zapdos", "Moltres", "Dratini",
"Dragonair", "Dragonite", "Mewtwo", "Mew", "MissingNo.", "Chikorita", "Bayleef",
"Meganium", "Cyndaquil", "Quilava", "Typhlosion", "Totodile", "Croconaw",
"Feraligatr", "Sentret", "Furret", "Hoothoot", "Noctowl", "Ledyba", "Ledian",
"Spinarak", "Ariados", "Crobat", "Chinchou", "Lanturn", "Pichu", "Cleffa",
"Igglybuff", "Togepi", "Togetic", "Natu", "Xatu", "Mareep", "Flaaffy",
"Ampharos", "Bellossom", "Marill", "Azumarill", "Sudowoodo", "Politoed",
"Hoppip", "Skiploom", "Jumpluff", "Aipom", "Sunkern", "Sunflora", "Yanma",
"Wooper", "Quagsire", "Espeon", "Umbreon", "Murkrow", "Slowking", "Misdreavus",
"Unown", "Wobbuffet", "Girafarig", "Pineco", "Forretress", "Dunsparce",
"Gligar", "Steelix", "Snubbull", "Granbull", "Qwilfish", "Scizor", "Shuckle",
"Heracross", "Sneasel", "Teddiursa", "Ursaring", "Slugma", "Magcargo", "Swinub",
"Piloswine", "Corsola", "Remoraid", "Octillery", "Delibird", "Mantine",
"Skarmory", "Houndour", "Houndoom", "Kingdra", "Phanpy", "Donphan", "Porygon2",
"Stantler", "Smeargle", "Tyrogue", "Hitmontop", "Smoochum", "Elekid", "Magby",
"Miltank", "Blissey", "Raikou", "Entei", "Suicune", "Larvitar", "Pupitar",
"Tyranitar", "Lugia", "Ho-Oh", "Celebi"
]
STATE_SIZE = 8
RCS = [ [ x for x in PKMN[i].upper().encode() ] for i in range(len(PKMN)) if len(PKMN[i]) == 8 ]
MDS = [[45003, 9938, 52063, 34055, 59174, 25705, 22867, 25942],
[25942, 45003, 9938, 52063, 34055, 59174, 25705, 22867],
[22867, 25942, 45003, 9938, 52063, 34055, 59174, 25705],
[25705, 22867, 25942, 45003, 9938, 52063, 34055, 59174],
[59174, 25705, 22867, 25942, 45003, 9938, 52063, 34055],
[34055, 59174, 25705, 22867, 25942, 45003, 9938, 52063],
[52063, 34055, 59174, 25705, 22867, 25942, 45003, 9938],
[9938, 52063, 34055, 59174, 25705, 22867, 25942, 45003],]
def add(a, b):
return (a + b) % P
def mul(a, b):
return (a * b) % P
def perm(state):
assert all([ state[i] < P for i in range(len(state)) ])
for ri in range(56):
### Non-linear component / "poor man's substitution"
state = [ pow(state[i], 7, P) for i in range(len(state)) ]
## Diffusion
tmp = [ 0 for _ in range(len(state)) ]
for i in range(len(state)):
for j in range(len(state)):
tmp[i] = add(tmp[i], mul(MDS[i][j], state[j]))
state = tmp
## Round constants
state = [ (state[i] + RCS[ri][i]) % P for i in range(len(state)) ]
return state
# TODO: Findme
inp = [ 0x1234 for _ in range(8) ]
out = perm(inp)
assert out == [0xb020, 0x5094, 0xb950, 0x9b15, 0x92f0, 0x88de, 0x9a41, 0x56f4]
print("ok")
We can quickly write a function invperm
that performs the inverse steps in reverse order:
#!/usr/bin/env python3
import genericmatrix
P = 2**16-2**12+1
PKMN = [ "Bulbasaur", "Ivysaur", "Venusaur", "Charmander", "Charmeleon", "Charizard",
"Squirtle", "Wartortle", "Blastoise", "Caterpie", "Metapod", "Butterfree",
"Weedle", "Kakuna", "Beedrill", "Pidgey", "Pidgeotto", "Pidgeot", "Rattata",
"Raticate", "Spearow", "Fearow", "Ekans", "Arbok", "Pikachu", "Raichu",
"Sandshrew", "Sandslash", "NidoranF", "Nidorina", "Nidoqueen", "NidoranM",
"Nidorino", "Nidoking", "Clefairy", "Clefable", "Vulpix", "Ninetales",
"Jigglypuff", "Wigglytuff", "Zubat", "Golbat", "Oddish", "Gloom", "Vileplume",
"Paras", "Parasect", "Venonat", "Venomoth", "Diglett", "Dugtrio", "Meowth",
"Persian", "Psyduck", "Golduck", "Mankey", "Primeape", "Growlithe", "Arcanine",
"Poliwag", "Poliwhirl", "Poliwrath", "Abra", "Kadabra", "Alakazam", "Machop",
"Machoke", "Machamp", "Bellsprout", "Weepinbell", "Victreebel", "Tentacool",
"Tentacruel", "Geodude", "Graveler", "Golem", "Ponyta", "Rapidash", "Slowpoke",
"Slowbro", "Magnemite", "Magneton", "Farfetch'd", "Doduo", "Dodrio", "Seel",
"Dewgong", "Grimer", "Muk", "Shellder", "Cloyster", "Gastly", "Haunter",
"Gengar", "Onix", "Drowzee", "Hypno", "Krabby", "Kingler", "Voltorb",
"Electrode", "Exeggcute", "Exeggutor", "Cubone", "Marowak", "Hitmonlee",
"Hitmonchan", "Lickitung", "Koffing", "Weezing", "Rhyhorn", "Rhydon", "Chansey",
"Tangela", "Kangaskhan", "Horsea", "Seadra", "Goldeen", "Seaking", "Staryu",
"Starmie", "Mr.", "Mime", "Scyther", "Jynx", "Electabuzz", "Magmar", "Pinsir",
"Tauros", "Magikarp", "Gyarados", "Lapras", "Ditto", "Eevee", "Vaporeon",
"Jolteon", "Flareon", "Porygon", "Omanyte", "Omastar", "Kabuto", "Kabutops",
"Aerodactyl", "Snorlax", "Articuno", "Zapdos", "Moltres", "Dratini",
"Dragonair", "Dragonite", "Mewtwo", "Mew", "MissingNo.", "Chikorita", "Bayleef",
"Meganium", "Cyndaquil", "Quilava", "Typhlosion", "Totodile", "Croconaw",
"Feraligatr", "Sentret", "Furret", "Hoothoot", "Noctowl", "Ledyba", "Ledian",
"Spinarak", "Ariados", "Crobat", "Chinchou", "Lanturn", "Pichu", "Cleffa",
"Igglybuff", "Togepi", "Togetic", "Natu", "Xatu", "Mareep", "Flaaffy",
"Ampharos", "Bellossom", "Marill", "Azumarill", "Sudowoodo", "Politoed",
"Hoppip", "Skiploom", "Jumpluff", "Aipom", "Sunkern", "Sunflora", "Yanma",
"Wooper", "Quagsire", "Espeon", "Umbreon", "Murkrow", "Slowking", "Misdreavus",
"Unown", "Wobbuffet", "Girafarig", "Pineco", "Forretress", "Dunsparce",
"Gligar", "Steelix", "Snubbull", "Granbull", "Qwilfish", "Scizor", "Shuckle",
"Heracross", "Sneasel", "Teddiursa", "Ursaring", "Slugma", "Magcargo", "Swinub",
"Piloswine", "Corsola", "Remoraid", "Octillery", "Delibird", "Mantine",
"Skarmory", "Houndour", "Houndoom", "Kingdra", "Phanpy", "Donphan", "Porygon2",
"Stantler", "Smeargle", "Tyrogue", "Hitmontop", "Smoochum", "Elekid", "Magby",
"Miltank", "Blissey", "Raikou", "Entei", "Suicune", "Larvitar", "Pupitar",
"Tyranitar", "Lugia", "Ho-Oh", "Celebi"
]
STATE_SIZE = 8
RCS = [ [ x for x in PKMN[i].upper().encode() ] for i in range(len(PKMN)) if len(PKMN[i]) == 8 ]
MDS = [[45003, 9938, 52063, 34055, 59174, 25705, 22867, 25942],
[25942, 45003, 9938, 52063, 34055, 59174, 25705, 22867],
[22867, 25942, 45003, 9938, 52063, 34055, 59174, 25705],
[25705, 22867, 25942, 45003, 9938, 52063, 34055, 59174],
[59174, 25705, 22867, 25942, 45003, 9938, 52063, 34055],
[34055, 59174, 25705, 22867, 25942, 45003, 9938, 52063],
[52063, 34055, 59174, 25705, 22867, 25942, 45003, 9938],
[9938, 52063, 34055, 59174, 25705, 22867, 25942, 45003],]
prime_add = lambda a, b: (int(a) + int(b)) % P
prime_sub = lambda a, b: (int(a) - int(b)) % P
prime_mul = lambda a, b: (int(a) * int(b)) % P
prime_div = lambda a, b: (int(a) * pow(int(b), -1, P) % P)
tmp = genericmatrix.GenericMatrix((len(MDS), len(MDS)),
add = prime_add, sub = prime_sub,
mul = prime_mul, div = prime_div,
)
for i, line in enumerate(MDS):
tmp.SetRow(i, line)
inv = tmp.Inverse()
MDS_INV = [ [ inv[i, j] for j in range(len(MDS[0])) ] for i in range(len(MDS)) ]
for j in range(8):
print([ chr(x) for x in MDS_INV[j]])
def invperm(state):
assert all([ state[i] < P for i in range(len(state)) ])
for ri in range(56)[::-1]:
## Round constant addition
state = [ (state[i] - RCS[ri][i] + P) % P for i in range(len(state)) ]
## Linear diffusion
tmp = [ 0 for _ in range(len(state)) ]
for i in range(len(state)):
for j in range(len(state)):
tmp[i] = (tmp[i] + (MDS_INV[i][j] * state[j])) % P
state = tmp
## Substitution
state = [ pow(state[i], 52663, P) for i in range(len(state)) ]
return state
def sub(a, b):
return (a - b + P) % P
def add(a, b):
return (a + b) % P
def mul(a, b):
return (a * b) % P
out = [0xb020, 0x5094, 0xb950, 0x9b15, 0x92f0, 0x88de, 0x9a41, 0x56f4]
print([ f"{x:#06x}" for x in out ])
inp = invperm(out)
print([ f"{x:#06x}" for x in inp ])
This prints
['N', 'o', 'd', 'n', 'e', 't', 'n', 'i']
['i', 'N', 'o', 'd', 'n', 'e', 't', 'n']
['n', 'i', 'N', 'o', 'd', 'n', 'e', 't']
['t', 'n', 'i', 'N', 'o', 'd', 'n', 'e']
['e', 't', 'n', 'i', 'N', 'o', 'd', 'n']
['n', 'e', 't', 'n', 'i', 'N', 'o', 'd']
['d', 'n', 'e', 't', 'n', 'i', 'N', 'o']
['o', 'd', 'n', 'e', 't', 'n', 'i', 'N']
['0xb020', '0x5094', '0xb950', '0x9b15', '0x92f0', '0x88de', '0x9a41', '0x56f4']
['0x020e', '0x0628', '0x1013', '0x180d', '0x003a', '0x0003', '0x0401', '0x0000']
Translating the magic constants 0x020e
, 0x0628
, 0x1013
, 0x180d
to words, we obtain POKEMON BATTLE ISN'T WORK
. Entering this as the password, the battle against Mew starts in the challenge emulator! Battle and catch Mew to obtain the flag (don’t use a Master Ball, since one of the patches applied by the emulator intentionally breaks its catch-all property).
Replacing <
and >
with {
and }
, we finally get the flag: hxp{Surpr1s3dP1k4chu1Ch00s3Y0u}