From d8fbf80a4bfd5f9c1ab6f93a4d2dc839339b6908 Mon Sep 17 00:00:00 2001 From: mid-kid Date: Sun, 7 Apr 2019 22:38:51 +0200 Subject: [PATCH] Initial commit --- README.md | 2 + pk3/calc.c | 98 +++++ pk3/calc.py | 38 ++ pk3/fools.asm | 222 ++++++++++ pk3/output.buf | Bin 0 -> 512 bytes pk3/pk3_5seconds.c | 86 ++++ pk4/dumprop.py | 29 ++ pk4/dumpropscript.py | 78 ++++ pk4/fools.asm | 997 +++++++++++++++++++++++++++++++++++++++++++ pk4/fools.sav | Bin 0 -> 32816 bytes pk4/hook.asm | 30 ++ pk4/orig.sav | Bin 0 -> 32816 bytes pk4/ropgadgets.txt | 41 ++ pk4/ropscript.c | 196 +++++++++ pokecrystal.diff | 72 ++++ 15 files changed, 1889 insertions(+) create mode 100644 README.md create mode 100644 pk3/calc.c create mode 100644 pk3/calc.py create mode 100644 pk3/fools.asm create mode 100644 pk3/output.buf create mode 100644 pk3/pk3_5seconds.c create mode 100755 pk4/dumprop.py create mode 100755 pk4/dumpropscript.py create mode 100644 pk4/fools.asm create mode 100644 pk4/fools.sav create mode 100644 pk4/hook.asm create mode 100644 pk4/orig.sav create mode 100644 pk4/ropgadgets.txt create mode 100644 pk4/ropscript.c create mode 100644 pokecrystal.diff diff --git a/README.md b/README.md new file mode 100644 index 0000000..c6b6226 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +My resources used to beat https://zzazzdzz.github.io/fools2019/ +No writeups or explanations provided. diff --git a/pk3/calc.c b/pk3/calc.c new file mode 100644 index 0000000..49b5c0c --- /dev/null +++ b/pk3/calc.c @@ -0,0 +1,98 @@ +#include +#include +#include +#include + +int s03_A0F3 = 0x5D0B1C11; +int s03_A0F7 = 0x35E79125; +int s03_A0FB = 0x56596b10; +int s03_A0FF = 0x7FFFFB0A; +int s03_A103 = 0x1B080733; + +int s03_A107; +int s03_A10B; +int s03_A10D; +int s03_A10E; + +char buffer[0x200]; + +int main() +{ + FILE *f = fopen("fools.sav", "r"); + if (!f) { + perror("fopen"); + exit(1); + } + fseek(f, 0x6567, SEEK_SET); + fread(buffer, 1, 0x200, f); + fclose(f); + + GHashTable *hashtable = g_hash_table_new(g_direct_hash, g_direct_equal); + + double start = clock(); + do { + gint lcg = s03_A0F3 & 0xFFFFFE00; + gint cur_lcg = GPOINTER_TO_INT(g_hash_table_lookup(hashtable, GINT_TO_POINTER(lcg))); + cur_lcg += 1; + g_hash_table_replace(hashtable, GINT_TO_POINTER(lcg), GINT_TO_POINTER(cur_lcg)); + + s03_A0F3 = (s03_A0F3 * s03_A0F7 + s03_A0FB) & 0xFFFFFFFF; + + if (s03_A0FF % 0x100000 == 0) { + double framespers = 0x100000 / ((double)(clock() - start) / CLOCKS_PER_SEC); + unsigned eta = (double)(s03_A0FF) / framespers; + fprintf(stderr, "Progress: %d (speed: %.2f iter/s, ETA: %02u:%02u:%02u)\n", + s03_A0FF, framespers, + eta / 60 / 60, (eta / 60) % 60, eta % 60); + start = clock(); + } + } while (--s03_A0FF); + + int count = 0; + + GHashTableIter iter; + gpointer key, value; + g_hash_table_iter_init(&iter, hashtable); + while (g_hash_table_iter_next(&iter, &key, &value)) { + count += GPOINTER_TO_INT(value) % 2; + } + + int i = 0; + start = clock(); + g_hash_table_iter_init(&iter, hashtable); + while (g_hash_table_iter_next(&iter, &key, &value)) { + if (GPOINTER_TO_INT(value) % 2 == 0) { + continue; + } + + double framespers = 1 / ((double)(clock() - start) / CLOCKS_PER_SEC); + unsigned eta = (double)(count - i) / framespers; + fprintf(stderr, "Progress: %d/%d %.2f%% (speed: %.2f iter/s, ETA: %02u:%02u:%02u)\n", + i, count, (double)i / count * 100, framespers, + eta / 60 / 60, (eta / 60) % 60, eta % 60); + start = clock(); + i += 1; + + s03_A107 = s03_A103; + + s03_A10B = (GPOINTER_TO_INT(key) & 0xFF00) >> 8; + s03_A10D = (GPOINTER_TO_INT(key) & 0xFF0000) >> 16; + s03_A10E = (GPOINTER_TO_INT(key) & 0xFF000000) >> 24; + + int bufi = 0; + do { + s03_A10B = ((s03_A10B / 2) * s03_A10D + s03_A10E) & 0xFFFF; + buffer[bufi] ^= s03_A10B & 0xFF; + + if (++bufi >= 0x200) bufi = 0; + } while (--s03_A107); + } + + g_hash_table_destroy(hashtable); + + f = fopen("output.buf", "w"); + fwrite(buffer, 1, 0x200, f); + fclose(f); + + return 0; +} diff --git a/pk3/calc.py b/pk3/calc.py new file mode 100644 index 0000000..f884f43 --- /dev/null +++ b/pk3/calc.py @@ -0,0 +1,38 @@ +# Seed values (break at s03_A13A to fetch them) +s03_A0F3 = 0x5D0B1C11 # Match +s03_A0F7 = 0x35E79125 # Match +s03_A0FB = 0x56596b10 # Match +s03_A0FF = 0x7FFFFB0A # Match +s03_A103 = 0x1b080733 # Match + +file = open("fools.sav", "rb") +file.seek(0x6567) +buf = bytearray(file.read(0x200)) +file.close() + +while s03_A0FF != 0: + s03_A107 = s03_A103 + + s03_A10B = (s03_A0F3 & 0xFF00) >> 8 + s03_A10D = (s03_A0F3 & 0xFF0000) >> 16 + s03_A10E = (s03_A0F3 & 0xFF000000) >> 24 + + print(s03_A0FF - i_out) + + bufi = 0 + i = 0 + while s03_A107 != 0: + s03_A10B = ((s03_A10B // 2) * s03_A10D + s03_A10E) & 0xFFFF + buf[bufi] ^= (s03_A10B) & 0xFF + + bufi += 1 + if bufi >= 0x200: + bufi = 0 + + s03_A107 -= 1 + + s03_A0F3 = (s03_A0F3 * s03_A0F7 + s03_A0FB) & 0xFFFFFFFF + + s03_A0FF -= 1 + +file = open("output.buffer", "wb").write(buf) diff --git a/pk3/fools.asm b/pk3/fools.asm new file mode 100644 index 0000000..a89f9d1 --- /dev/null +++ b/pk3/fools.asm @@ -0,0 +1,222 @@ +CopyDEtoHL_32bit: ; s03_A084 +; Copies value pointed to by de to hl + + ld a, [de] + ld [hl+], a + inc de + ld a, [de] + ld [hl+], a + inc de + ld a, [de] + ld [hl+], a + inc de + ld a, [de] + ld [hl+], a + ret + +AddDEToHL_32bit: ; s03_A090 +; Adds value pointed to by de to value pointed to by hl + + ld a, [de] + add [hl] + ld [hl+], a + inc de + + ld a, [de] + adc [hl] + ld [hl+], a + inc de + + ld a, [de] + adc [hl] + ld [hl+], a + inc de + + ld a, [de] + adc [hl] + ld [hl], a + ret + +s03_A0A0: dl $FFFFFFFF + +DecHL_32bit: ; s03_A0A4 +; Decrements 32-bit value at hl, checks if result is 0 + + push hl + ld de, s03_A0A0 + call AddDEToHL_32bit + pop hl + + ; Checks 4-byte value at hl is 0 + ld a, [hl+] + and a + ret nz + ld a, [hl+] + and a + ret nz + ld a, [hl+] + and a + ret nz + ld a, [hl+] + and a + ret + +Multiplier: ds 4 ; s03_A0B8 +CalcResult: ds 4 ; s03_A0BC +Clean32bit: dl $00000000 ; s03_A0C0 + +MulDEToHL_32bit: ; s03_A0C4 +; Multiplies value at de with value at hl, stores in hl + + push hl + + ; Copy DE to the Multiplier + push hl + push de + ld hl, Multiplier + call CopyDEtoHL_32bit + pop de + pop hl + + ; Clear the result value + push hl + ld hl, CalcResult + ld de, Clean32bit + call CopyDEtoHL_32bit + pop de + + ; Add HL (now DE) Multiplier times to CalcResult +.loop + push de + ld hl, CalcResult + call AddDEToHL_32bit + pop de + push de + ld hl, Multiplier + call DecHL_32bit + pop de + jr nz, .loop + + ; Copy the result back to HL + pop hl + ld de, CalcResult + jp CopyDEtoHL_32bit + + +s03_A0F3: ds 4 ; Initial value: $5D0B1C11 +s03_A0F7: dl $35E79125 +s03_A0FB: dl $56596b10 +s03_A0FF: ds 4 ; Initial value: $7FFFFB0A + +s03_A103: dl $1b080733 +s03_A107: ds 4 ; Initialized with A103 + +s03_A10B: dw +s03_A10D: db +s03_A10E: db + +s03_A10F: +; (u16)s03_A10B = ((u16)s03_A10B / 2) * s03_A10D + s03_A10E + + ld hl, s03_A10B + ld a, [hl+] + ld h, [hl] + ld l, a + + and a + srl h + rr l + + ld d, h + ld e, l + ld hl, 0 + ld a, [s03_A10D] + ld c, a + and a + jr z, .end +.loop + add hl, de + dec c + jr nz, .loop +.end + + ld a, [s03_A10E] + ld c, a + ld b, 0 + add hl, bc + + ld a, h + ld [s03_A10B + 1], a + ld a, l + ld [s03_A10B], a + ret + +s03_A13A: +; Main decryption routine + +.outer_loop + ld hl, s03_A107 + ld de, s03_A103 + call CopyDEtoHL_32bit + + ; Initialize values + ld hl, s03_A0F3 + 1 + ld de, s03_A10B + ld a, [hl+] + ld [de], a + inc de ; s03_A10B + 1 + xor a + ld [de], a + inc de ; s03_A10D + ld a, [hl+] + ld [de], a + inc de ; s03_A10E + ld a, [hl] + ld [de], a + + ld hl, Buffer + ld bc, 0 + +; Problem with this routine: Writing to SRAM!!! +; This means that whatever is in hl's buffer is useless on next bootup, so we +; need to make sure to start from a clean save file. + +.loop ; s03_A15A + push hl + push bc + call s03_A10F ; (u16)s03_A10B = ((u16)s03_A10B / 2) * s03_A10D + s03_A10E + pop bc + pop hl + + ; Exor the buffer + xor [hl] + ld [hl+], a + + ; Reset HL to Buffer every $200 bytes + inc bc + ld a, b + cp $2 + jr nz, .s03_A16F + ld bc, 0 + ld hl, Buffer +.s03_A16F + + push hl + ld hl, s03_A107 + call DecHL_32bit + pop hl + jr nz, .loop + + ld hl, s03_A0F3 + ld de, s03_A0F7 + call MulDEToHL_32bit + ld hl, s03_A0F3 + ld de, s03_A0FB + call AddDEToHL_32bit + + ld hl, s03_A0FF + call DecHL_32bit + jr nz, .outer_loop + ret + +Buffer: ds $200 ; s03_A567 diff --git a/pk3/output.buf b/pk3/output.buf new file mode 100644 index 0000000000000000000000000000000000000000..96c9c1aa1fd73eae7d44d8db4fb36d8d227390bf GIT binary patch literal 512 zcmXS38p+hXboNrAWkSms7#JCu87C$K=`aosMuz|YH!w8a|Nmb=m{*XCNs5C@fr*2Y zm6dbuQa;9tGtyS~8QrDL?{Am3xWCkA%Kg@<3?ZHqellxnDC$V->8nZWDQjxzntqbh z)VGt>RM%3I*OQmC(^HbSlU0&dQZK^0J746mjD0& literal 0 HcmV?d00001 diff --git a/pk3/pk3_5seconds.c b/pk3/pk3_5seconds.c new file mode 100644 index 0000000..190899f --- /dev/null +++ b/pk3/pk3_5seconds.c @@ -0,0 +1,86 @@ +// Written by luckytyphlosion + +#include +#include +#include + +typedef unsigned int uint; +typedef unsigned short u16; +typedef unsigned char u8; + +#define _A0F4 (_A0F3 >> 8) & 0xff +#define _A0F5 (_A0F3 >> 16) & 0xff +#define _A0F6 (_A0F3 >> 24) & 0xff +#define TRUE 1 +#define FALSE 0 + +u8 sA567[] = {0x15, 0xc8, 0x7b, 0x6b, 0x7f, 0xa1, 0xa3, 0x29, 0xe8, 0x81, 0x1, 0x99, 0x9f, 0xeb, 0xf2, 0x80, 0x5f, 0xa3, 0x96, 0x7c, 0x17, 0xdb, 0xd2, 0x41, 0xa1, 0xbc, 0xbb, 0x13, 0xbc, 0xd8, 0xc3, 0x9, 0xf4, 0x2, 0x1b, 0x95, 0xf3, 0x72, 0x65, 0xdb, 0x6f, 0x82, 0x9e, 0x52, 0xd9, 0x89, 0xd8, 0xdf, 0x5a, 0xb0, 0x3, 0xf2, 0x9a, 0xe6, 0xef, 0x81, 0x32, 0xa5, 0x57, 0x60, 0xe0, 0xc8, 0x2d, 0x8c, 0x52, 0x63, 0x5d, 0xd8, 0xf, 0x72, 0xdf, 0xa1, 0x36, 0x1c, 0x3e, 0xc8, 0xc2, 0x4f, 0x1e, 0xfd, 0x1d, 0x11, 0xc, 0x74, 0x13, 0x21, 0x49, 0xad, 0xc0, 0xd9, 0xba, 0xda, 0xd, 0xe4, 0x6, 0x8e, 0x94, 0xe6, 0x25, 0x8b, 0xba, 0xc2, 0x2b, 0x92, 0xa, 0x90, 0xa1, 0x4a, 0x6, 0x53, 0x84, 0x79, 0xe5, 0xe7, 0xcf, 0x60, 0x74, 0xd2, 0x3b, 0x2a, 0xe0, 0x56, 0x32, 0xa6, 0x10, 0x69, 0x52, 0xf, 0xd3, 0x3d, 0x7f, 0xf3, 0x12, 0x6d, 0x0, 0x57, 0x41, 0x3f, 0x96, 0x3b, 0x8, 0xb7, 0x48, 0x72, 0xb2, 0x1e, 0x12, 0x57, 0x60, 0x95, 0x23, 0xc8, 0xab, 0x52, 0xc8, 0x3f, 0xc4, 0xc0, 0x50, 0xc1, 0xd3, 0x38, 0x28, 0x6e, 0x96, 0x6d, 0xdf, 0x8f, 0x41, 0x24, 0x21, 0x91, 0xf4, 0x64, 0x3e, 0xc9, 0xe6, 0xc7, 0x45, 0x26, 0xa1, 0x4c, 0xa, 0xd3, 0x2f, 0x8f, 0x77, 0x7d, 0x68, 0x86, 0x23, 0x43, 0xcf, 0xbf, 0xe1, 0xf4, 0xc8, 0x9e, 0x35, 0x5a, 0x11, 0x26, 0xb7, 0x9f, 0x23, 0xcd, 0x68, 0xa2, 0xb5, 0xca, 0xd3, 0x53, 0x95, 0xda, 0xb, 0x87, 0x15, 0xca, 0xe7, 0x73, 0xbd, 0x66, 0x39, 0x90, 0xc0, 0xa4, 0x94, 0x94, 0x3e, 0x61, 0x9e, 0xef, 0x89, 0x1e, 0x93, 0x13, 0xc1, 0xfa, 0xd9, 0xee, 0x5b, 0x27, 0x71, 0x16, 0xcd, 0xa2, 0xef, 0x49, 0xcc, 0xd6, 0x1d, 0x24, 0xa6, 0x3b, 0x89, 0x94, 0x58, 0x28, 0xd2, 0x83, 0x5b, 0x25, 0x8a, 0x5f, 0x25, 0xfe, 0xe3, 0x2d, 0xe8, 0x58, 0x5e, 0xbb, 0x9b, 0xd5, 0xe8, 0x46, 0xa5, 0xdc, 0xde, 0x8f, 0x97, 0x3, 0xf1, 0x16, 0x1, 0xfc, 0x2e, 0x3f, 0x47, 0x5b, 0xaf, 0x71, 0x2e, 0x7f, 0x19, 0xce, 0xe3, 0x7d, 0xe2, 0x49, 0x5e, 0x71, 0xa, 0x57, 0xe1, 0x98, 0xac, 0xdd, 0x7f, 0xfd, 0x5d, 0xae, 0xdc, 0x24, 0xc5, 0x30, 0x3b, 0x65, 0xc1, 0xd6, 0xf4, 0x62, 0xa0, 0xee, 0xbc, 0x86, 0x68, 0xb4, 0x5, 0x68, 0x1b, 0xdb, 0x5c, 0xa8, 0x25, 0x92, 0x70, 0xc8, 0xa5, 0xf6, 0x1a, 0x55, 0xad, 0xae, 0x44, 0x2a, 0x4a, 0x77, 0xca, 0x2d, 0x6b, 0xdd, 0x47, 0x51, 0x95, 0x58, 0xff, 0x75, 0xdd, 0x30, 0x81, 0x80, 0x1b, 0xdb, 0xc2, 0xa9, 0x29, 0xce, 0xa8, 0xd6, 0xd8, 0x8, 0xc1, 0x86, 0x1c, 0xec, 0xf, 0x63, 0x48, 0x38, 0x1b, 0xeb, 0x32, 0xe3, 0xa0, 0xe6, 0x28, 0x5e, 0xf4, 0xa, 0xa2, 0x2d, 0xf1, 0xb0, 0x61, 0xfc, 0xdd, 0x4c, 0x6b, 0x43, 0xe8, 0x82, 0x4, 0xe8, 0xf3, 0x3d, 0x43, 0xf5, 0x6d, 0x4e, 0x8a, 0x45, 0xc9, 0x6a, 0x96, 0x1d, 0x37, 0x93, 0x76, 0x53, 0x9e, 0x33, 0xa, 0xaf, 0x8, 0x3e, 0x80, 0xfc, 0xe7, 0xc5, 0x17, 0x69, 0xe7, 0x91, 0x55, 0x52, 0x4a, 0xa5, 0x51, 0x6, 0xa8, 0x3e, 0x82, 0x1d, 0xff, 0x27, 0x14, 0xee, 0x26, 0x39, 0x55, 0xc0, 0x8f, 0xd7, 0xf2, 0x47, 0x14, 0x9a, 0x54, 0x5e, 0xac, 0x9a, 0xc2, 0x93, 0xc0, 0x96, 0x60, 0x44, 0x11, 0x8, 0xed, 0x22, 0x20, 0x44, 0x77, 0xb3, 0xfc, 0x78, 0xe5, 0xda, 0xa, 0xc5, 0xa1, 0xbe, 0x64, 0x80, 0x25, 0x20, 0xa7, 0xfb, 0x3e, 0xdb, 0x26, 0x39, 0x99, 0x2a, 0xee, 0xa3, 0x36, 0x7b, 0xb6, 0x63, 0x24, 0xf6, 0xf6, 0x13, 0xe, 0x83, 0x86, 0x1f, 0x3a, 0xf}; + +u16 values[65536] = {0}; +u16 values_set[65536] = {0}; +int values_size = 0; + +int main(void) { + uint _A0F3 = 0x5d0b1c11; + const uint _A0F7 = 0x35e79125; + const uint _A0FB = 0x56596b10; + struct timespec start_time; + + for (int i = 0; i < 0x3ffffb0a; i++) { + _A0F3 = _A0F3 * _A0F7 + _A0FB; + } + + for (int i = 0; i < 0x4f6; i++) { + u16 _A10B = _A0F4; + int _A10D = _A0F5; + int _A10E = _A0F6; + int data_count = 0; + int start_index = -1; + values_size = 0; + int sublcg_count = 0; + memset(values_set, 0xff, sizeof(values_set)); + while (TRUE) { + values[values_size++] = _A10B; + _A10B = (_A10B >> 1) * _A10D + _A10E; + if (values_set[_A10B] != 0xffff) { + start_index = values_set[_A10B]; + break; + } + values_set[_A10B] = values_size; + sA567[data_count++] ^= (_A10B & 0xff); + data_count = data_count & 0x1ff; + sublcg_count++; + } + int sublcg_index = start_index; + + int period = values_size - start_index; + while ((period & 1) == 0) { + period >>= 1; + } + + int num_xors_until_repeat = period * 512; + int num_repeat_iterations = (0x1b080733 - sublcg_count) / num_xors_until_repeat; + int remaining_sublcg_iterations = (0x1b080733 - sublcg_count) % num_xors_until_repeat; + if (num_repeat_iterations & 1 == 1) { + remaining_sublcg_iterations += num_xors_until_repeat; + } + + for (int j = 0; j < remaining_sublcg_iterations; j++) { + sA567[data_count++] ^= (values[sublcg_index++] & 0xff); + data_count = data_count & 0x1ff; + if (sublcg_index >= values_size) { + sublcg_index = start_index; + } + } + _A0F3 = _A0F3 * _A0F7 + _A0FB; + if (i % 10000 == 9999) { + struct timespec end_time; + clock_gettime(CLOCK_MONOTONIC, &end_time); + fprintf(stderr, "difftime: %f, iter: %u\n", 1000.0*end_time.tv_sec + 1e-6*end_time.tv_nsec - (1000.0*start_time.tv_sec + 1e-6*start_time.tv_nsec), i); + } + } + + for (int i = 0; i < 0x200; i++) { + printf("0x%02x, ", sA567[i]); + } +} diff --git a/pk4/dumprop.py b/pk4/dumprop.py new file mode 100755 index 0000000..eee9bb0 --- /dev/null +++ b/pk4/dumprop.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 + +from sys import argv +from struct import unpack + +gadgets = {} + +for line in open("ropgadgets.txt"): + split = line.split("-") + if len(split) < 2: + continue + + addr = int(split[0][1:], 16) + cmd = "-".join(split[1:]).strip() + gadgets[addr] = cmd + +mem = open("fools.dump", "rb").read() + +addr = int(argv[1], 0) + +while True: + gadget = unpack("^QWT>vyfZh6wXXZV-M)RhbvGlq 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.m+5X`9u|1St=>P7mm;E=A!{m6ik8 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 literal 0 HcmV?d00001 diff --git a/pk4/ropgadgets.txt b/pk4/ropgadgets.txt new file mode 100644 index 0000000..5cbc2d0 --- /dev/null +++ b/pk4/ropgadgets.txt @@ -0,0 +1,41 @@ +$0394 - and a +$0831 - ld hl, n16 +$08F1 - ld bc, n16 +$0933 - xor b +$09A2 - ld bc, de, hl, af, n16 +$09A4 - ld hl, af, n16 +$0D8E - ld c, a +$106A - inc de +$106A - inc de +$1380 - dec de +$1447 - inc hl +$1489 - ld b, a +$1708 - jp hl +$1898 - ld a, [de] +$18D0 - ld a, e +$18DC - ld c, l +$1C85 - ldw hl, [hl] +$1D17 - add hl, bc +$1FA5 - ld e, a +$1FEE - ld [hl], b +$2250 - ld [hl+], a +$2CAD - ld a, c +$2DE0 - or [hl] +$2EC9 - dec a +$3351 - ld [hl], d +$37C2 - inc a +$3E3D - add a +$4128 - ld [de], a +$4404 - ld b, h +$4D6A - ld d, b +$50EC - ld d, [hl] +$65E1 - inc [hl] +$ADBA - ld a, [hl+] +$ADBC - ld a, [hl-] +$ADBE - ld [hl], c +$ADC0 - ld b, [hl] +$ADC2 - add b +$ADC4 - srl a +$ADC7 - xor [hl] +$ADC9 - call z, n16 +$ADCC - add [hl]/xor [hl] diff --git a/pk4/ropscript.c b/pk4/ropscript.c new file mode 100644 index 0000000..36870f4 --- /dev/null +++ b/pk4/ropscript.c @@ -0,0 +1,196 @@ +#include + +// Final buffer goes at 3:A100 of the save file + +char buf[0x1b0]; + +int s02_ADB1; + +char s02_ADB1_b; +char s02_ADB2; +char s02_ADB3; +char s02_ADB4; + +char sSaveBlockChecksum; + +char func() +{ + s02_ADB1_b = (s02_ADB1 >> 0) & 0xFF; + s02_ADB2 = (s02_ADB1 >> 8) & 0xFF; + s02_ADB3 = (s02_ADB1 >> 16) & 0xFF; + s02_ADB4 = (s02_ADB1 >> 24) & 0xFF; + + s02_ADB2 ^= (s02_ADB1_b + 1) ^ s02_ADB4; + s02_ADB3 += s02_ADB2; + s02_ADB4 = s02_ADB2 ^ (s02_ADB4 + (s02_ADB3 / 2)); + + s02_ADB1 = (s02_ADB4 << 24) | (s02_ADB3 << 16) | (s02_ADB2 << 8) | s02_ADB1_b; + + return s02_ADB4; +} + +int main() +{ + int bufi = 0; + + // sSaveSource = $FCDF; + sSaveBlockChecksum = 0x7F; + s02_ADB1 = 0x8C10E62F; + for (int i = 0; i < 0x48; i++) { + buf[bufi] = (char)(bufi & 0xFF); + buf[bufi] ^= func(); + sSaveBlockChecksum += buf[bufi]; + bufi++; + + buf[bufi] = (char)(bufi & 0xFF); + sSaveBlockChecksum ^= buf[bufi]; + buf[bufi] ^= func(); + bufi++; + } + buf[bufi] = sSaveBlockChecksum; + bufi++; + + // sSaveSource = $FA7A; + sSaveBlockChecksum = 0xC2; + s02_ADB1 = 0x6AF528C2; + for (int i = 0; i < 0x14; i++) { + buf[bufi] = (char)(bufi & 0xFF); + buf[bufi] ^= func(); + sSaveBlockChecksum ^= buf[bufi]; + bufi++; + } + buf[bufi] = sSaveBlockChecksum; + bufi++; + + // sSaveSource = $F84E; + for (int i = 0; i < 0x03; i++) { + buf[bufi] = (char)(bufi & 0xFF); + bufi++; + } + + // sSaveSource = $F859; + sSaveBlockChecksum = 0x06; + s02_ADB1 = 0xEF7305A6; + for (int i = 0; i < 0x4C; i++) { + buf[bufi] = (char)(bufi & 0xFF); + buf[bufi] ^= func(); + sSaveBlockChecksum ^= buf[bufi]; + bufi++; + + buf[bufi] = (char)(bufi & 0xFF); + buf[bufi] ^= func(); + sSaveBlockChecksum += buf[bufi]; + bufi++; + } + buf[bufi] = sSaveBlockChecksum; + bufi++; + + // sSaveSource = $DE41; + s02_ADB1 = 0x2A7FEC38; + sSaveBlockChecksum = 0x3C; + for (int i = 0; i < 0x21; i++) { + buf[bufi] = (char)(bufi & 0xFF); + sSaveBlockChecksum += buf[bufi]; + buf[bufi] ^= func(); + bufi++; + } + buf[bufi] = sSaveBlockChecksum; + bufi++; + + // sSaveSource = $DE99; + sSaveBlockChecksum = 0xE2; + s02_ADB1 = 0x4BFC1115; + for (int i = 0 ; i < 0x40; i++) { + buf[bufi] = (char)(bufi & 0xFF); + buf[bufi] ^= func(); + sSaveBlockChecksum += buf[bufi]; + bufi++; + } + buf[bufi] = sSaveBlockChecksum; + bufi++; + + s02_ADB1 = 0xA23F387C; + sSaveBlockChecksum = 0x16; + // sSaveSource = $A003; + for (int i = 0; i < 0x04; i++) { + buf[bufi] = (char)(bufi & 0xFF); + buf[bufi] ^= func(); + sSaveBlockChecksum ^= buf[bufi]; + sSaveBlockChecksum += buf[bufi]; + bufi++; + } + buf[bufi] = sSaveBlockChecksum; + bufi++; + + sSaveBlockChecksum = 0x00; + + // sSaveSource = $FFE1; + buf[bufi] = (char)(bufi & 0xFF); + sSaveBlockChecksum += buf[bufi]; + sSaveSource = 0xB16B; + *(sSaveSource++) = sSaveBlockChecksum; + bufi++; + + // sSaveSource = $FFE2; + // buf[bufi] = *(sSaveSource++); + // sSaveBlockChecksum ^= buf[bufi]; + // buf[bufi] = sSaveBlockChecksum; + // sSaveSource = $B16C; + // *(sSaveSource++) = sSaveBlockChecksum; + // bufi++; + + // sSaveSource = $FF04; + // buf[bufi] = *(sSaveSource++); + // sSaveBlockChecksum += buf[bufi]; + // buf[bufi] = sSaveBlockChecksum; + // sSaveSource = $B16D; + // *(sSaveSource++) = sSaveBlockChecksum; + // bufi++; + // sSaveSource = $FF05; + // buf[bufi] = *(sSaveSource++); + // sSaveBlockChecksum ^= buf[bufi]; + // buf[bufi] = sSaveBlockChecksum; + // sSaveSource = $B16E; + // *(sSaveSource++) = sSaveBlockChecksum; + // bufi++; + // s02_ADB1 = $EFBEADDE; + // bufi++; + // bufi++; + // sSaveBlockChecksum = $55; + // sSaveSource = $F350; + // *(sSaveSource++) = sSaveBlockChecksum; + // *(sSaveSource++) = sSaveBlockChecksum; + // rept ($1AB) {; + // sSaveSource = $F350; + // sSaveBlockChecksum = *sSaveSource; + // sSaveBlockChecksum += buf[bufi]; + // *(sSaveSource++) = sSaveBlockChecksum; + // sSaveBlockChecksum = *sSaveSource; + // sSaveBlockChecksum ^= buf[bufi]; + // *(sSaveSource++) = sSaveBlockChecksum; + // buf[bufi] ^= func(s02_ADB1); + // bufi++; + // }; + + // sSaveBlockChecksum = $CC; + // sSaveSource = $ADB1; + // sSaveBlockChecksum ^= buf[bufi]; + // *(sSaveSource++) = sSaveBlockChecksum; + // bufi++; + // sSaveBlockChecksum ^= buf[bufi]; + // *(sSaveSource++) = sSaveBlockChecksum; + // bufi++; + // sSaveBlockChecksum ^= buf[bufi]; + // *(sSaveSource++) = sSaveBlockChecksum; + // bufi++; + // sSaveBlockChecksum ^= buf[bufi]; + // *(sSaveSource++) = sSaveBlockChecksum; + // bufi++; + // sSaveSource = $F350; + // buf[bufi] = *(sSaveSource++); + // buf[bufi] ^= func(s02_ADB1); + // bufi++; + // buf[bufi] = *(sSaveSource++); + // buf[bufi] ^= func(s02_ADB1); + // bufi++; +} diff --git a/pokecrystal.diff b/pokecrystal.diff new file mode 100644 index 0000000..09c4a63 --- /dev/null +++ b/pokecrystal.diff @@ -0,0 +1,72 @@ +# These are the patches I used to speed through the game. +# They include: +# - All pokemon's stats and HP are max when sent into battle +# - HP bars deplete instantly (to avoid destiny bond softlocking the game...) +# - No trainers can spot you (used to solve PK1 mostly) + +diff --git a/Makefile b/Makefile +index e213bf63c..812cb9799 100644 +--- a/Makefile ++++ b/Makefile +@@ -89,6 +89,7 @@ endif + pokecrystal.gbc: $(crystal_obj) pokecrystal.link + $(RGBLINK) -n pokecrystal.sym -m pokecrystal.map -l pokecrystal.link -o $@ $(crystal_obj) + $(RGBFIX) -Cjv -i BYTE -k 01 -l 0x33 -m 0x10 -p 0 -r 3 -t PM_CRYSTAL $@ ++ dd bs=1 if=baserom.gbc of=pokecrystal.gbc count=2 conv=notrunc skip=334 seek=334 + tools/sort_symfile.sh pokecrystal.sym + + pokecrystal11.gbc: $(crystal11_obj) pokecrystal.link +diff --git a/engine/battle/core.asm b/engine/battle/core.asm +index 38abbb51f..f752e4d46 100644 +--- a/engine/battle/core.asm ++++ b/engine/battle/core.asm +@@ -3884,11 +3884,13 @@ InitBattleMon: + ld bc, MON_NAME_LENGTH + call CopyBytes + ld hl, wBattleMonAttack +- ld de, wPlayerStats ++ ld a, $ff + ld bc, PARTYMON_STRUCT_LENGTH - MON_ATK +- call CopyBytes +- call ApplyStatusEffectOnPlayerStats +- call BadgeStatBoosts ++ call ByteFill ++ ld a, $ff ++ ld [wBattleMonHP], a ++ nop ++ nop + ret + + BattleCheckPlayerShininess: +diff --git a/engine/overworld/events.asm b/engine/overworld/events.asm +index a84d72db4..9d1dd8d44 100644 +--- a/engine/overworld/events.asm ++++ b/engine/overworld/events.asm +@@ -294,8 +294,10 @@ PlayerEvents: + CheckTrainerBattle_GetPlayerEvent: + nop + nop +- call CheckTrainerBattle +- jr nc, .nope ++ nop ++ nop ++ nop ++ jr .nope + + ld a, PLAYEREVENT_SEENBYTRAINER + scf +diff --git a/engine/pokemon/health.asm b/engine/pokemon/health.asm +index d11a073b5..0cd97d237 100644 +--- a/engine/pokemon/health.asm ++++ b/engine/pokemon/health.asm +@@ -104,7 +104,9 @@ ComputeHPBarPixels: + ret + + AnimateHPBar: +- call WaitBGMap ++ ret ++ ret ++ ret + call _AnimateHPBar + call WaitBGMap + ret