Fools2021 Writeup - Stranck
So here we goooo, I finally had some time to write all of this.
As always,
⚠️ My english is really bad. Sorry if I make you cringe af.
Oh btw, the space in my leaderboard’s name is a typo lmao
Full list of achievments/hacking challs description: 
Normal achievements
Actually so far I’ve never played the game in the intended way. I collected all of the “normal gameplay achievement” by just completing the second hacking challenge
Second hacking challenge
“This achievement is unobtainable thru normal means. You know what to do!”
When I first looked to the hacking challs of fools2021 this immediately popped onto my eyes. That’s why I solved it first. I see you TheZZAZZGlitch, this is EXACTLY the same chall you did for fools2017, last achievement. (Little cool note here: fools2017 was the first hacking chall I’ve EVER played in my life. I remember waking up in the morning, watching the thezzazz’s video, trying the chall and being surprised that by just normal gameplay I was 7th. This pushed me playing also for the hacking achievements and it was really pleasant ending with a perfect score. I can pretty much say that’s thanks to fools2017 and thanks to TheZZAZZGlitch if my cybersec career started and if I now play in the most important CTF teams of Italy (mHACKeroni, TRX, TeamItaly @ ECSC))
COFF
Btw, I solved that chall exactly how I solved the one in fools2017 (however that year I was helped by NieDzeJkob).
I checked what data is included in form of achievements inside the final password. Pikachu’s max HP looked like something interesting. So I opened the cheat searcher on my bgb, filtered all values that had the current pikachu’s maxHP, done some battles till a level up, filtered again for the current maxHP, and after a while I got my address: DADF. I put a read breakpoint on that address, called the password generation and boom, I was in the middle of the function. I quickly looked around and noticed it was doing stuff with addresses between DAC0 and DAC4. Soooo, I put all of them to 0xff, continued, and got almost all of the normal achievement + the one for the 2nd hacking chall. I said almost, because I fcked up also the time and missed the “defeat the last boss in under one hour” one. So I checked the function better, noticed that DAC0 contained the time, and I changed everything again except that one. Gotem.
First hacking challenge
For the first hacking challenge we had to walk 524.272 steps in one direction. I know there are some people who really walked thru there, but I didn’t want to go full monke, at least this time.
The first thing I did was searching the pokemon red ram map online, to check how long is the variable containing our position. Comes out it’s only one byte long, so no way I could hack it to be on the exact spot.
While playing I noticed some stuttering, similar to what happens in the normal game when changing maps. I quickly realised that I was changing chunks and I thought that maybe my steps were somehow tracked in them. I walked through a chunk and counted 16 steps before a new chunk was loaded. Soooo, 524272/16 = 32.767. Hey, that’s 0x7fff, this could be easily stored somewhere in ram. Also 0x7fff is the biggest value we can get with an int16_t. Maybe we just need to walk up to chunk 0x7fff?
Soooo, I started searching for the chunk data in ram similar to what I did in chall2. I opened the cheat search in bgb, I positioned myself between two chunks, assumed that walking to the right whould have increased my chunk value, did the first search, walked to the right, filtered for all values that have been increased, walked to the left, filtered for all values that have been decreased, and so on. At the end I got really few values remaining, one of these being DAB4. So I’ve set this to ff7f, walked one chunk left and another one right and booom, got it.
“Congratulations, you reached the edge of the world.
Kind of. The world will loop back around. Glitchtopia isn’t flat.
That being said, here’s a reward for coming this far! ToTheEdgeAndBeyond”
After working on the chall4 however I realised that there’s a byte that controls which textbox we were seeing on screen. To get the flag from this chall you can just bruteforce this till you get the correct textbox.
I also tried just out of curiosity to use the TheZZAZZGlitch’s rbmemviewer to see if I could see the text into the memory, but I couldn’t get it to show the correct address ranges.
Btw I take my merits and I say that I was the first one completing both chall2 and chall1. It was cool staying on top of the leaderboard for a while, till my worst enemy of every fools so far came to me blasting into my face. Bruteforce on chall3.
Third hacking challenge
You know the crazy guy who found the world seed of the Minecraft’s main menu background? Well, here we had to do the same thing.
Who knows me, also knows how much I hate any chall that involves crypto and any math that goes something more than 1+1. I was really happy that this didn’t come out to be a crypto chall, but more a rev one.
After noticing that our seed in our menu was composed by just hex values, I tried searching it inside the ram and I found it at DAB8. So I started the game, put a breakpoint on these values, pressed continue on the start menu, and I was inside the function that was randomly generating it. I traced the execution till I reached 02:AE24. That was the starting function of the generation map algorithm. This time I decided to go full monkey mode. I quickly reversed everything just to check which values it was writing while generating the map (Comes out to be: 4 bytes after the seed, that’s “chunk seed”; 01:D800 that’s the map data; and currentChunkX + currentChunkY) and then I wrote a bruteforcer INSIDE the game that was just resetting these values, setting a new seed, calling the genMap function and then checking if it could find the correct tiles sequence inside the map data. I must admit that the first thing I thought to was making something like the Truecoin miner made by TheZZAZZGlitch (so writing the seed to a savestate, run bgb on it, then check the map tiles), but I thought that opening and closing bgb everytime would’ve given us a lot of overhead.
I found the various tile sequences by just trial and error, changing random bytes inside the map data till I got them properly aligned on screen. I actually wasn’t sure that the tiles would’ve always been in a fixed location in the map data, so I chose to just search the tile sequences without checking where they actually were.
Here’s my code that was doing all of that inside the gameboy.
I asked some cpu power to Parzival (my cpu’s 100% shit, I got an overclocked amd fx6300 and unironically, after doing a bunch of benchmarks, I can say it does worse both in single core and multicore than the cpu of a server I pay 5€/month) who I noticed was struggling too on this chall by giving him a savestate that was already running this code, and we started searching on different address ranges. We left them going for 2 days, buuuut nothing was found. Luckily I’m not so dumb. I made this bruteforcer exactly to let it going while I was properly reversing the code. Who knows that maybe it could’ve found the results before me. After I reversed everything and I still didn’t get any results from the ingame, I decided to rewrite the map gen algorithm in C, so I could let my -O3 go BRRRR.
So I did, and I have to really thank my friends Dav and Lorenzo Fiocco who have donate their AMD R7 5800x’s power to me.
Aaaaand it didn’t work either. Or, at least, I was having a fuckton of results, but none of them were valid. So many results that at some point I wrote a script to automatically submit them for me.
Here I really have to thank NieDzejkob that checked if my code was able to generate and then check the correct seed and guess what, it wasn’t. In particularly, my check function was dumb. Let’s see if you can spot the problem there.
void searchInData(){
int currentSequenceCounter = 0;
uint8_t *currentSequence = sequences[currentSequenceCounter];
int indexInSequence = 0;
for(int i = 0; i < FINAL_DATA_SIZE; i++){
uint8_t b = finalData[i];
if(b == currentSequence[indexInSequence]){
indexInSequence++;
if(indexInSequence >= SEQUENCE_DIMENSION){
indexInSequence = 0;
currentSequenceCounter++;
if(currentSequenceCounter < SEQUENCE_NO){
currentSequence = sequences[currentSequenceCounter];
} else {
printf("!!! FOUND A POSSIBLE ");
printSeed();
}
}
} else {
indexInSequence = 0;
}
}
}
Well, let’s say I’m searching for the sequence AABC, and in the map data we have the sequence AAABC. It checks the first As. Ok. It checks the second As. Ok. It then checks the B from our sequence with the third A from the map data. It fails. Ok. But here’s the problem. Instead of resetting back our index inside the map data to the second A, it was continuing to check from the B in the map data. So A (from our sequence) differs from B (from our map data), and rip, the function says we had no results, even if AABC is a subsequence of AAABC. The ASM code I wrote for the first bruteforcer also had this problem, that’s why it didn’t give me results.
Btw after fixing that bug I started getting even more results, so I had to cut down the area where I was searching the tiles to not DDos the event servers, hoping to find my sequence in there. After a while I got the correct seed: 6521E0A1. Here you can find my final cpp code and my autosubmitter
Fourth hacking challenge
You’ve heard of problems inside my bruteforcer, now get ready for desperation, pain, stress, sadness, sense of being mediocre, inside my mind. I played all of this with some problems irl, prolly my grandma is about to take a selfie with Nadia Toffa (Sorry for the Italian black humor) [Editor’s note: For non Italian readers, Nadia Toffa is a dead Italian TV conductor], plus I postponed a fuckton of work I had to do that was pressing against me a lot. For my job as lighting designer I’m pretty used to be in stressed situations, but holy cow if all of this combined + this fourth chall drained me. I must admit that I really considered a lot of times to drop everything, but I have to thank many people that encouraged me. vcvtne2ps2bf16, issoTM, jfb1337, buurazu, radixan. It’s thanks to them if I managed to got this (Even if just one hour after the end of the event, but i’m happy anyway). Trying over and over again a lot of different paths, managing to get everything ready and then fail at the end just for being a few bytes off, really destroyed me a lot. I’m not gonna include every path I tried, because I don’t remember most of them anymore
Btw let’s start from the start. start start start start start
I don’t remember anymore why I contacted vcvtne2ps2bf16 on discord for the first time, but we decided to collaborate, especially after we discovered that we were both italian. No language barrier, yeeee
Btw we shared to each other the progress made till that moment, his datamining was really helpful for me.
He told me about the glitch item 0x2D, capable of executing code from DAB3. He also told me some ideas about how we could probably obtain it (Glitch pokémon battles, glitch biome, currentItemMenuPage > 9, etc). I knew about various bugs involving the old save file, but I had to make my TAS on the new one, so diffed them to get inspiration for an exploit. After a while digging in this direction, reversing various functions like the battle engine, the give item routine, the change chunk/biome routine, trying some race conditions, trying to go up in map fast enough to exit the matrix (Parzival achieved this, idk how and idk on which version of the savefile), I decided to throw up on this road and try something else.
I knew from the GCRI discord that there was a bug in the inventory: if you have exactly 40 items and you try to open the item menù the game crashes. I investigated into that, noticing that the game was searching for a terminator inside the code after the inventory, and placing a lot of terminators after the first FF it could find (That’s because every time you open the item menù, it fills the empty part of the inventory with terminators, to prevent the exploit we ended up using in our final strat) . This code is often used (it handles bank switching), so that’s why it was crashing. By doing this research I noticed that correctly up to 40 items, places the last terminator onto the music control byte, that than gets immediately overridden by the music itself. This opened our possibilities, because we can now collect items to change data (code) near inventory and hopefully get ace. After some research I found two bytes we could manipulate: DB3C and DB74. We could set DB3C to 0x63 by just obtaining a super potion (Check the ItemList), and we could set DB74 to any opcode between 0x11 and 0x63 by just obtaining reviver seeds.
Btw the DB74 opcode is a ld de, F9D4 and it’s at the start of the password hash function. So we only needed to call the password generation to execute our exploit
Now, here I committed a very big error. Idk why, but I focused myself to think that the only thing we could do is to change DB74 to 0x31, making it a ld sp, F9D4 (with F9D4 being in the middle of the map data).
So I started searching a lot in this direction. Using the map tiles I found in the chall3, and dumping some map data, I got this set of tiles I could combine and use as return addresses by stackflipping onto the map data.
00 nop <---
31 ld sp, d16 <---
33 inc sp <---
C9 ret <---
CA jp z, a16 <---
D9 reti <---
F1 pop af <---
04 inc b
08 ld (a16), sp
0A ld a, (bc)
0B dec bc
0F rrca
11 ld DE, d16
13
32 ldd (hl), a
34 inc (hl)
4E
4D
4F ld c, a
50 ld d, b
51
52
55
60 ld h, b
6C ld l, h
6E ld l, (hl)
6D ld l, l
6F ld l, a
74 ld (hl), h
7A ld a, d
7B
88 adc a, b
8E adc a, (hl)
8D adc a, l
8F adc a, a
93 sub e
CB bitwise operation
DB -
EC -
FA ld a, (a16)
FF rst $38 NOPE
(Also: idk why I noted what instructions they correspond to, we’re executing the map tiles as stack, not as code)
I found many possible solutions but guess what, idk why but I got a fuckton of extra tiles. Making all of them impossible.
Thanks to radixan I got the correct Tile List, making my job even more difficult, however I managed to get 4 different payloads:
PAYLOAD #1
db08: inventory
1374: ld b, l; jp bankswitch -> jp (hl)
PAYLOAD #2
db08: inventory
db55: call DB5A -> jp (hl)
PAYLOAD #3
0b52: pop bc; pop hl; ret
dbfa: hl += bc; ret
db55: call DB5A -> jp (hl)
PAYLOAD #4
0b52: pop bc; pop hl; ret
1374: ld b, l; jp bankswitch -> jp (hl)
With the inventory in our payload being this one:
inc c
;DB0A
ld h, 10
ld l, 0a
inc bc
ld b, 54
ld c, 3c
add hl, bc
add hl, bc
dec c
ld e, h
;fill instructions inc b; ld h, e
jr 0x11 ;DB32. Jumps on DB45: nop; nop; ret
Making a working code for the inventory was especially a pain, because we had a lot of constraints:
- data on odd addresses can be only between 0x01 and 0x11, this limits our opcodes and our args A LOT
- data on even addresses can be only between 0x01 and 0x63
- you can’t have the same byte on odd addresses, except when the previous equal bytes are followed by a 0x63
- you can never decrease a byte, only increase it
With this constraint I made the previous inventory payload. However, because I’ve never played the game so far, I didn’t know there are some items you can obtain only once, making the job even harder. I discovered this only way later tho, when I had already discarded the idea of executing the inventory.
I also made a list of functions that could jump on hl:
Bankswitch
SpritePositionBankswitch (bankswitch)
GetTrainerInformation (bankswitchhome)
CheckForHiddenObjectOrBookchelfOrCardKeyDoor
SeafoamIsIslands1F_h
RocketHideout3BattleTxt
RocketHideout3EndBattleTxt
SilphCoElevator_Script -> SilphCoElevatorWarpMaps (potential call to d502 and jp hl)
fls21Func_callHL
$FB5A
and a full 100% working plan:
overflow inventory
change DB74 to $31 (+), item 04
generate password
stack flip on F9D4 (tile data)
there it should be DB0A
exec inventory
decrement DB3C
inventory at DB32 JRs on DB3C, that’s a jp (hl)
However I didn’t think about one thing. Not all of the tiles will be present in the map data at the same time. Take for example the address db55. It needs the 0xdb and the 0x55 tile. The first one comes from the glitching biome, the second one from construction. No way I could get them aligned. Yeah, I still could’ve used payload4. But 1) good luck searching the 8 bytes aligned correctly, 2) it’s pointless since the value we’re putting in hl and then jumping to was coming still from the map tiles. We could have used it directly as return address lmao
At this point Buurazu was the only one that resolved this challenge. So I came to him crying asking for any tips. He made me realise that I’m an idiot I literally had any other opcode to try as value for DB74.
So I did, I divided the opcodes in three categories, based on how many params they were taking:
0 params -> the following instruction would have been a call nc, 7AF9. We arrive to that with the carry flag already set, so based on the previous opcode we were changing, we could take this call or not. However when we execute the hash function, the current rom bank is 0x17, and there it’s filled with 00, till the start of VRAM. So yeah, we could fall back and execute VRAM, but I think I didn’t really want to mess with that. Btw, if we don’t take the call, well, nothing interesting happens beside having a corrupted password.
2 params -> Nothing interesting, except ld sp, d16 that I’ve tried before by stack flipping over the tile map
1 param -> the following instruction will be a ld sp, hl. That’s pretty interesting since hl points to our hashed passwords and we have semicontrol of that. Also, with our instruction we could change some registers used by the hashing function (like DE) to D4, and get more plausible results. We can also change h or l to D4 and use something else than the unhashed password as input hash data and then stack values, or we can do a ld (hl), D4, setting the first unhashed password byte to, you guessed it, 0xD4
So, we have some possible end results and we want to know which values can generate that. Because I’m as bad at math as I’m bad at english [Editor’s note: FFS yes, I noticed]… Time to “appicciare” [Editor’s note: That would mean “to turn on”] z3! I don’t have the previous version of this script, so here’s directly the last version of my code. Little note: OFC the first version of my script was calculating the wrong results (I missed the a = 0x3B before the checksum), so I lost like two days working on bad data :)))
Btw one of the first things I tried was checking the results having DB74 = ld h, d8 or ld l, d8. With the first one I would have hashed just a bunch of zeroes (Hashed password = OUT: 0x62 0x62 0x0 0x62 0x9d), so nope; with the second one I would have hashed part of the last encountered pokémon data (From DAD4, 1+wEncounterTextPtr). Check PokémonList. Btw these were the possible functions I could execute ropchaining on the hashed password made from each pokémon’s data:
Starting: D = 0x62 E = 0x0 HL = DAD4 Pkm = 1
IN: 0xbc 0x24 0xbf 0x3 OUT: 0xde 0xfa 0x9b 0x62 0x2a
Starting: D = 0x62 E = 0x0 HL = DAD4 Pkm = 2
IN: 0xbc 0x2b 0xbf 0x3 OUT: 0xde 0xf5 0x94 0x62 0x3a
Starting: D = 0x62 E = 0x0 HL = DAD4 Pkm = 3
IN: 0xbc 0x33 0xbf 0x4 OUT: 0xde 0xed 0x8c 0x65 0xd
Starting: D = 0x62 E = 0x0 HL = DAD4 Pkm = 4
IN: 0xbd 0x3a 0xbf 0x2 OUT: 0xdf 0xe5 0x85 0x62 0x18
Starting: D = 0x62 E = 0x0 HL = DAD4 Pkm = 5
IN: 0xbc 0x42 0xbf 0x6 OUT: 0xde 0x9c 0xfd 0x67 0xef
Starting: D = 0x62 E = 0x0 HL = DAD4 Pkm = 6
IN: 0xbd 0x4d 0xbf 0xa OUT: 0xdf 0x92 0xf2 0x6a 0xfc
Starting: D = 0x62 E = 0x0 HL = DAD4 Pkm = 7
IN: 0xbd 0x55 0xbf 0x4 OUT: 0xdf 0x8a 0xea 0x64 0xc2
Starting: D = 0x62 E = 0x0 HL = DAD4 Pkm = 8
IN: 0xbd 0x5f 0xbf 0x5 OUT: 0xdf 0x80 0xe0 0x65 0xdf
Starting: D = 0x62 E = 0x0 HL = DAD4 Pkm = 9
IN: 0xbd 0x66 0xbf 0x8 OUT: 0xdf 0xb9 0xd9 0x68 0xa2
Starting: D = 0x62 E = 0x0 HL = DAD4 Pkm = A
IN: 0xbd 0x6b 0xbf 0xe OUT: 0xdf 0xb4 0xd4 0x6e 0xb4
Starting: D = 0x62 E = 0x0 HL = DAD4 Pkm = B
IN: 0xbe 0x71 0xbf 0x10 OUT: 0xdc 0xad 0xce 0x73 0x9f
Starting: D = 0x62 E = 0x0 HL = DAD4 Pkm = C
IN: 0xbe 0x7b 0xbf 0x12 OUT: 0xdc 0xa7 0xc4 0x71 0x9d
Starting: D = 0x62 E = 0x0 HL = DAD4 Pkm = D
IN: 0xbe 0x83 0xbf 0x19 OUT: 0xdc 0x5f 0x3c 0x7a 0x76
None of them were actually usefull ofc. So I started digging inside the unhashed password itself. The final math formula for each one of the first four hashed bytes is this one:
f0 = d ^ e ^ pw0
f1 = d ^ pw0 ^ pw1
f2 = pw1 ^ pw2
f3 = d ^ e ^ pw0 ^ pw2 ^ pw3
f4 = 0x3B + f0 ^ f1 - f2 ^ f3 = 0x3b + e ^ pw1 - pw1 ^ d ^ e ^ pw0 ^ pw3 (idk if I can simplify the xor with +/- in the middle)
With pw1 and pw2 being really small numbers (pw1 >= 0 and <= 7, pw2 >= 0 and <= 4). This meant that f2 ALWAYS is a small number, and if we want to somehow execute our seed/chunkCoords (variables we can easily control), or the winFunction, that are placed at xxAA (DAB8, DAB4, B882) with AA > 7, we needed the stack disaligned in some way, so the return after the first call would have called f4f3 instead of f3f2 (f4 can be “any” value). So I started searching for any combination of f1f0 that would have executed a dec/inc sp in some way, or execute something else useful that would have returned to the overworld loop, give us items, change map, etc.
At that time the only way we knew to obtain a buffer overflow was collecting (40 - 12 * 99) items. This meant that we would have the byte of the password rapresenting the time (The password is formatted as time|achievmentflags|bossesbeaten|rng byte) maxed out at 0xff and our pikachu’s would definitely have more of 100hp, so the relative achievement was set (and achievement flags respecting the constraint Or(pw1 == 2, pw1 == 3, pw1 == 6, pw1 == 7). However in the first place I was using the “wrong” constraint pw1 >= 2, obtaining unhashed passwords that would have other achievements set but not necessarily the pikachuMAXhpOver100. I noticed this error only after I got my first working password).
It’s there that my mood started to get worse. I’ve found many many many potential passwords that were using completely different paths. From doing stuff with the real start menù, to buying glitch items from a glitchy pokémarket, to executing link trades, to saving the game, to restarting the game with ace, to changing maps to something useful, jumping back to the overworld loop, to executing yet again the inventory and much much more stuff. In my notes I can see I digged into pokémon and item name ptrs, encounter rates, inventory pages, pikachu stats… I was able to do stuff with all of them, but not reaching something useful with them. I always was one/few byte(s) off what I needed. This was a struggle.
I was really lost, but then someone finally tunred on the lights. On the GCRI someone reminded that this video actually exists. Thanks to this video we all I realised that we don’t need 40 stacks to overflow our inventory. We just needed one full stack of the item we want to overflow (in our case, rev seed, to change DB74). This opened my possibilities a lot, because now we’ve lost all of the constraints on our password, and I can freely search for any password. However my mood was getting even worse, because even if I could execute a lot of stuff in memory (all addreses like XX00-f + many other I can’t remember) without constraints, I couldn’t really control that or I didn’t find that useful. Moreover, I had to exclude the ld (hl), d8 and ld e, d8 from my tests, because they were returning an address into vram with the first two bytes of the hashed passwords. That halved my total possibilities.
However, after a while of searching for different values, I finally got a result: Function: 2b2f Input: 0xfb 0x4 0x2 0x36 Empty menù that returns on pw3 (DAC3)
You have no idea how much I prayed about having this f1f0 but with f4f3 pointing to something useful. I finally got some luck and I noticed that with the unhashed password 0xfb 0x4 0x3 0xae we would return exactly on B882 (our winning function). Perfect.
I noticed only after a while that this password was needing to NOT have our pikachu’s maxHP > 100. At that time I had no idea how easy it was getting rev seeds from items, and I thought the only way would have been collect them thru pokémon battles, leveling up my pikachu a lot. That’s why I started searching for other passwords, by adding the constraint to having the maxHpOver100 achievement set.
I was again lucky, and I found this one Function: 33 e5 Input: 0x53 0x2 0x4 0x5a B: 0x33 #boom i think, that was returning on the chunk’s hashed seed. Something I could control, but I would have to probably travel a lot to reach the correct chunk (Implying the correct chunk is actually reachable). This also needed some bruteforce to find a good seed/chunk combination.
So I searched a little bit better for hashes starting with the same two bytes (e5, 33) and I got a really similar one: 0x53 0x2 0x4 0x7a. The only difference is the rng byte, but this one executes code from DA9F. We could easily fallback thru the textbox buffer, the chunks and reach the seed. Comes out that for the textbox buffer you’re good if the last one you’ve read is the one of the pokéball-overworld-pick, that you have to pay attention to not do any ld (bc), x because it would deactivate the SRAM making B882 inaccesible, to pay attention to be in a chunk with good coords as opcodes, to pay attention to being in the correct biome and having the correct flags in DAB1 and DAB2 (they control the input inside the menus).
The only big constraint about this password, is that we only had 83 minutes to do everything (including fighting 4 bosses and breed our pikachu to reach 100maxHP) and I still didn’t realise I could get the rev seeds thru items. So I tried searching if there was any way I could have used to manipulate rng to always get rev seeds from battles. If you check the PokémonList you can easily see that in corruption we have 5/8 chances of encountering a pokémon that will drop a rev seed, and up to 0C/FF chances of having the drop. Waaaay too low. Also, the first gen pokémon rng is shit and hard af to predict.
Little write up on my small research on manipulating the RNG
The value you get from the RNG is based (on what? xdlmaorotfl) on the combination between the previous status AND the approximate number of cpu cycles that have passed between rng calls. There’s an rng call every frame, and asynched from that (the difference in time between these two calls is based on how much cpu cycles we need before the next VBlank interrupt) there’s ofc an rng call each time you do some actions in game (like walking in grass). Doing actions ingame that would require a different amount of cpu clocks, changes the future rng values. This implies also playing notes in our musing, redrawing the player to look in a different direction, and so on. Also the point in cpu cycles where a rng call is made inside the game’s overworldLoop changes the future rng values (Because it means that more/less time has passed from the next/previous rng call). The battles are fully automated and during them our inputs are disabled, so no way we could manipulate the seed inside the battle. However I noticed that if we reset our DIV register (The one who contains the number of cpu cycles between rng calls) and FFD3/FFD4 (two bytes containing the result of the rng call/the previous status for the next rng call) at the first rng call made by vblank (An interrupt called each frame where we can draw stuff on screen), when the game does the rng call to check if it should drop an item or not at the end of the battle the DIV register contains more or less the same value (with an error of ±1. We have an error so small because the battles are automated and they require the same time), however FFD3 and FFD4 contains an hella different values. My plan of doing the battle, check the diff of the DIV register/FFD3/FFD4 between start and end, calc good starting DIV/FFD3/FFD4 values, and do action till we have them, is now ruined :)
I still think that rng manipulation/prediction is possible tho, it just has too much math for me.
Btw this was the point when with issotm (That had been helping me for a while, huge shoutouts to him) I realised that we could get the rev seeds by pokéballs. If you look at the ItemDropTable you can see we have 2/8 chances of getting a rev seed drop in construction and steppes. Waaaay better. I also asked around and it looks like that with a route planned and lua scripts it was easy and fast (both irl and in game) to collect them.
So I finally had a route. And I also had a backup route
- ROUTE 1: (Unhashed password:
0x53 0x2 0x4 0x7a)
- spawn on seed C382B8xx
- DONT OPEN THE INVENTORY, or we can’t edit DB74 anymore without collecting a fuckton of items
- collect the items to fight 4 bosses, but 0 rev seed. They need to be the last element of our inventory to edit DB74 without collecting a fuckton of items
- collect the rev seed thru pokéballs in construction and steppes
- breed our pikachu up to 100 max hp
- fight 4 bosses
- place on a good chunk, ask the password with good rng, win
- all of this in 83 minutes
- ROUTE 2: (Unhashed password:
0xfb 0x4 0x3 0xae)
- DONT OPEN THE INVENTORY, or we can’t edit DB74 anymore without collecting a fuckton of items
- collect the items to fight 2 bosses, but 0 rev seed. They need to be the last element of our inventory to edit DB74 without collecting a fuckton of items
- collect a daredevil potion (for the achievment byte)
- collect the rev seed thru pokéballs in construction and steppes
- breed our pikachu to a level where we can fight the bosses without being destroyed, but keeping him under 100hp max
- use the daredevil potion on the first boss
- fight the two bosses
- ask the password with good rng, press b to win
- all of this in 251 minutes
I ended up mixing both routes till they differ, so if one route fails for some reason, I could’ve just used the other one without redoing all of the TAS from 0. This was my final route:
- spawn on seed C382B8xx
- DONT OPEN THE INVENTORY, or we can’t edit DB74 anymore without collecting a fuckton of items
- collect the items to fight 4 bosses, but 0 rev seed. They need to be the last element of our inventory to edit DB74 without collecting a fuckton of items
- collect a daredevil potion (for the achievement byte)
- collect the rev seed thru pokéballs in construction and steppes
- breed our pikachu to a level where we can fight the bosses without being destroyed, but keeping him under 100hp max
- (ROUTE 1 ONLY)
- fight 4 bosses
- place on a good chunk
- breed our pikachu up to 100 max hp
- ask the password with good rng, win
- all of this in 83 minutes
Here’s our item list:
- 1 daredevil potion (construction 1/8)
- (4) 1+1+1+1 salt (grasslands, steppers, corruption 2/8)
- (5) 3 + 2 glitch shards (corruption 4/8)
- 2 shyhorns (50% drop rate from shydon, construction 3/8)
- (112) 99 + 13 rev seed (steppers, construction 2/8; corruption 1/8) (To change DB74 to
ld e, d8)
I then asked issoTM (who, I’m gonna say again, helped me A LOT in the final parts of this chall) if he could work on the tas of this chall during the weekend, because he has much more experience than me and I had some clients pushing over me to get their job done. However he had some problems so there I was, waking up at 6:30am on the last day of chall with 3 hours of sleep, ready to speedrun that tas. Oh, I’ve never made a tas before this one. It was “”“hella fun”"" discovering weird stuff while being so under pressure. IssoTM passed me the work he made so far (it was at the point of collecting two shy horns for the bosses), and some cool lua script to print the inventory/which items we’re gonna get from a drop, etc. I was having struggles with writing a script that would constantly retry obtaining a rev seed from the pokéball, so I wrote to a bunch of people and Buurazu answered my call. He helped me a lot understanding all of the lua functions inside the bizhawk docs and passed me directly his script to obtain rev seeds. “That’s not a lua script competition”. So much love, man.
I also asked to Radixan his map viewer, that was so much helpful in building my path between pokéballs for the rev seeds.
Collecting all of the rev seeds required only 0x27 minutes. That surprised me, so that’s why at the end I went for the first route. My irl time was also tight, I couldn’t wait so many minutes, even with speed increased (reminder that my cpu is shit).
I reached the part where I had to just wait and get the correct rng value for the final byte of the unhashed password, exactly one hour before the end of the event. I managed to write a lua script from scratch that was bruteforcing the frames till it got the correct rng in like 20 mins. I had to go thru the fact that bizhawk doesn’t like a framejump in the tas while being paused midframe due to a breakpoint. This consumed quite some time, and I solved it thanks to Buurazu suggestion to use a while true loop and advance frame calls. This is my script.
It looks like that luck abandoned me again.
Do you believe me if I say that, into the full minute-window where I could generate the password (So in between 60 * 30 = 1800 frames), I didn’t have one single frame that was bringing me to having a 0x7a as the rng value for the last byte of the password?
Well, trying all of the possible frames took one hour, even in ultraomegaiperspeed mode, I was already out of the fools2021’s dedadline. So I decided to revert my tas back to the final breed, do it again from scratch, wait for the correct time, and relaunched my rng bruteforcer. It found a good frame in like 5 minutes this time, at the 4th second of the 83rd minute.
I did it
I won (Is THiS a bRBa rEFeReNcE)
Just one hour late :(
I was still happy tho, morally I had a perfect score this time too. Plus I finally had the results of the work of a lot of days. A little bit of satisfaction to push up my moral. I don’t care if I was one hour late, and I don’t care if my achievement is not in the leaderboards. I won anyway. And I have to thank again a lot of people for that.
Btw, just out of curiosity I loaded and analyzed the logs of the first run of my rng bruteforcer with this script:
values = {}
for i in range(256):
values[i] = 0
f = open("resultsRNGfinal.txt", "r")
lines = f.readlines()
f.close()
for l in lines:
data = l.split("$")[1].split(",")[0]
n = int(data, 16)
values[n] += 1
for i in range(256):
print(f"Number {hex(i)} --> {values[i]}")
These are the results. As you can see there actually were some rng bytes that never occurred. With my luck OFC the byte I needed was in them. Prolly by redoing the battles I scrambled my rng in some way and now I managed to obtain the 0x7a from it.
Btw here’s my final TAS download: https://stranck.ovh/writeups/data/fools2021/fools2021.tasproj
And this is a record of it: https://youtu.be/oMQw5mZv8X0
This is instead the list of all the passwords I found and I tried: https://stranck.ovh/writeups/data/fools2021/hashedPasswords.txt (You can find really interesting stuff in this list, go check it out ;) )
I sadly don’t have a screenshot of my path onto the radixan map viewer, because I made it crash by zooming out too much to take the screenshot (…)
Conclusion
Phew, I didn’t expect this writeup to be so long [Editor’s Note: Neither did I].
I would like to thank anyone on the GCRI discord that always makes an effort to put an even more “community vibe” over these events. I would like to thank anyone who helped me in any way during all of these challenges. And I would like to thank TheZZAZZGlitch A LOT for still making these cool events after so much time.
I love all of this. These events are unironically something I wait for with a lot of enthusiasm every year. Thank you everybody. And thank you, reader, for having reached this point.
See you next year guys!
PS: sorry again for my shitty english [Editor’s note: I might have fixed some of that]
Oh, almost forgot! These are some notes I made for the third/fourth challs: https://stranck.ovh/writeups/data/fools2021/notes.html
Thanks to Dav for reviewing everything and fixing my shitty english!
Sym-file
01:DADF fls21_pkmMaxHP
01:DAE0 fls21_pkmCurrentHP
01:DAE1 fls21_pkmCurrentLevel
01:DAE2 fls21_pkmExpToNextLevel
01:DAE5 fls21_startItems
01:DAC0 fls21_winData0
01:DAC1 fls21_winData1
01:DAC2 fls21_winData2
01:DAC3 fls21_winData3
01:DAC4 fls21_winData4
01:DAB4 fls21_currentChunkLOW
01:DAB5 fls21_currentChunkHigh
01:D800 fls21_currentMap ;Correct tyles: d880 - d900
01:DAB8 fls21_seed
02:B156 fls21_biomeSeedMenuText
02:A142 fls21Func_PrintSeed
02:A151 fls21Func_CustomHexEncode
02:BCD9 fls21Func_SeedGenerated
02:A878 fls21Func_RandFromSeed
02:A850 fls21Func_RandomScramble
02:A956 fls21Func_WriteHLfromFF97 ; Writes to H = $d1, L = (B >> 0 + ((B & 0x0f) * 4 + L))
02:AA87 fls21Func_GenMapInternal2
;02:ADF6 fls21Func_GenMapInternal1 ;V1
02:ADED fls21Func_GenMapInternal1 ;V1.01
02:ADDD fls21Func_CopyMapTiles
;02:AE0C fls21Func_ReadChunk ;V1
;02:AE17 fls21Func_WriteChunk ;V1
01:DB3f fls21Func_changeSRAMBank
01:DB63 fls21Func_llHLfromOtherBank3
02:DB64 fls21Func_cryptResult
01:DB6F fls21Func_hashResult
01:D170 fls21_randBuffer
02:A896 fls21Func_chooseRandomByte
02:A8DF fls21Func_WriteRandomByte_internal
02:A907 fls21Func_WriteFF97andBlocks
02:A90A fls21Func_WriteFF97andBlocksDontSaveE
02:A96C fls21Func_WriteRandomByte
02:A92A .fls21_writeFF97loop
02:A998 fls21Func_ExpandSelectedTile
02:A98C fls21Func_ExpandLeft
02:A992 fls21Func_ExpandRight
02:A974 fls21Func_ExpandUp
02:A980 fls21Func_ExpandDown
;02:AE24 fls21Func_genMap ;V1
02:AE1B fls21Func_genMap ;V1.01
02:AB39 .fls21_scanForItemsLoop
02:AB88 fls21Func_getRandomDE
02:ABAF fls21Func_genRandomItem
02:AB6C fls21Func_HLstrangeADDa
02:ABCE fls21Func_addRandomTiles_ABCE
02:AC51 fls21Func_addRandomTiles_AC51
02:AC71 fls21Func_addRandomTiles_AC71
02:AD24 fls21Func_addRandomTiles_AD24
02:A9DC fls21Func_ReplaceRandBwithC
02:A9F8 fls21Func_ReplaceRandBwithCOnly30Bno0and7
02:AA1D fls21Func_RandomFill+IfPatter ;b = central value, c = substituteWith, pattern on ff00+98
; *** CHALL4 ADDRESSES ***
02:A5EE fls21Func_MapScript
02:A5EF fls21Func_CheckChunkLimit
02:A62C fls21Func_ChunkChanged
02:A1AE fls21Func_FilldItems
02:AE03 fls21Func_LoadChunkIntoDEHL ;V1.01
02:AE0E fls21Func_WriteChunk ;V1.01
02:AF27 fls21Func_OpenStartMenu
01:DB55 fls21Func_callHL
01:DB48 fls21Func_loadSRAM3
01:DC03 fls21Func_printItemName
03:A420 fls21_pokemonSpawnTable
03:A480 fls21_pokemonDataTable
03:A552 fls21_itemNamesTable
02:DAD2 fls21_pokemonDataInBattle
02:DADB fls21_pokemonDropItem
02:A78D fls21_checkBattle
02:B1EC fls21_launchBattle
01:DADD fls21_itemNamePtr
01:A61A fls21_mapScriptPtr
01:DB8F fls21_firstScreenEntrypoint
01:DACB fls21_isBossFight
01:DAC7 fls21_encounterRateFIXED
; *** BRUTEFORCER ADDRESSES ***
01:D3D0 brtfrc_data0
01:D3D8 brtfrc_data1
01:D3E0 brtfrc_data2
01:D3E8 brtfrc_data3
01:D3F0 brtfrc_data4
01:D400 brtfrc_GenNewKey
01:D410 brtfrc_BruteForcer
01:D414 brtfrc_.start
01:D41B brtfrc_.resetKeyBuffer
01:D422 brtfrc_.resetMapBuffer
01:D42A brtfrc_.resetLoop
01:D431 brtfrc_.resetChecksum
01:D43D brtfrc_.genMapCall
01:D440 brtfrc_.startCheck
01:D44B brtfrc_.checkLoop
01:D459 brtfrc_.doneWord
01:D466 brtfrc_.notDoneCheckLoop
01:D469 brtfrc_.wrongCheckLoop
01:D46F brtfrc_.doneCheckLoop
01:d473 brtfrc_.win
; *** END BRUTEFORCE ADDRESSES ***
;0A vuoto
;0F alberi
;0B erba
;74 fiori
; **** VCVTNE *****
00:ff96 fls21_hCurrentItem
00:d800 fls21_wOverworldMap
00:dab0 fls21_wCurrentBiome
00:dab3 fls21_wBattleScratch
00:dab4 fls21_wChunkX
00:dab6 fls21_wChunkY
00:dab8 fls21_wSeed
00:dac5 fls21_wInventoryPage
00:dac6 fls21_wInventorySelectedItem
00:dac9 fls21_wWildSpecies
00:daca fls21_wWildLevel
00:dacc fls21_wNextEncounterLevelModifier
00:dacd fls21_wWildLevelModifier
00:dace fls21_wPikachuAttackModifier
00:dad2 fls21_wEnemyData
00:dad2 fls21_wEnemyPicID
00:dad3 fls21_wEncounterTextPtr
00:dad5 fls21_wEnemyNamePtr
00:dad7 fls21_wEnemyExpYield
00:dad8 fls21_wEnemyBaseHP
00:dad9 fls21_wEnemyBaseAttack
00:dada fls21_wEnemuItemDropChance
00:dadb fls21_wEnemyWildItem
00:dadf fls21_wPikachuMaxHealth
00:dae0 fls21_wPikachuCurrentHealth
00:dae1 fls21_wPikachuLevel
00:dae2 fls21_wPikachuExperienceLeft
00:dae4 fls21_wInventoryCount
00:dae5 fls21_wInventory
00:db35 fls21_wCurrentSong
00:db36 fls21_wSkipSandwichCounter
00:db37 fls21_wCurrentVWFEventText
00:db38 fls21_wProgressFlags
00:db39 fls21_wBossesBeaten
00:db63 fls21Func_LoadHLFromBank3
02:a002 fls21Func_InitGame
02:a087 fls21Func_CheckROMIntegrity
02:a142 fls21Func_PrintSeed
02:a23f fls21Func_PrintVWFText
02:a4f8 fls21Func_EchoRAMTest
02:a50b fls21Func_VRAMTest
02:a520 fls21Func_CheckAccuracy
02:a6e1 fls21Func_CheckItemPickup
02:a6f7 fls21Func_PickupItem
02:ab7f fls21Func_GenerateHash
02:ae03 fls21Func_LoadChunkCoords
02:ae76 fls21Func_SummoningChoiceMenu
02:af27 fls21Func_OpenStartMenu
02:af67 fls21Func_GenerateItem
02:afa0 fls21Func_DisplayInventory
02:bae2 fls21Func_DisplayFailedSummoningText
02:b4ec fls21Func_DropItem
02:bcac fls21Func_RunGame
02:bcfa fls21Func_EditSeedMenu
02:b8b8 fls21Func_UseInventoryItem
02:b8f1 fls21Func_ItemEffectsTable
02:b917 fls21Func_PotionItemEffect
02:b967 fls21Func_IncreaseHPItemEffect
02:b9b1 fls21Func_NoUseItemEffect
02:b9c6 fls21Func_SkipSandwichItemEffect
02:b9de fls21Func_SummoningSaltItemEffect
02:bb12 fls21Func_TelltaleOrbItemEffect
02:bbfb fls21Func_CrystalItemEffect
02:bc13 fls21Func_DaredevilPotionItemEffect
02:bc40 fls21Func_BraveryPotionItemEffect
02:bc6d fls21Func_IncreaseAttackItemEffect
03:a420 fls21Func_EncountersTable
03:a480 fls21Func_WildMonsTable
DAB1-DAB2
Thanks ISSOtm for that!
; sbBR Llll
; |||| ||||
; |||| |+++-- How many lines each entry is
; |||| +----- Left is ignored if reset
; |||+------- Right is ignored if reset
; ||+-------- B is ignored if reset
; |+--------- Border gets drawn if reset
; +---------- Screen saved to buffer2 if reset
01:DAB1 wMenuFlags
01:DAB2 wMenuSize
ItemDropTable
(Biomes order: grasslands, steppes, construction, corruption)
00000000 01 01 07 07 07 08 08 06 |................|
00000008 02 02 03 07 0d 04 04 05 |................|
00000010 02 02 08 08 0c 0d 04 04 |................|
00000018 08 08 0a 0a 0a 0a 04 05 |................|
PokémonList
Thanks vcvtne2ps2bf16 for them!
Pokémon table starting at 03:A480
Format: | PicID | Encounter text | Name | Exp | HP | Atk | Drop rate | Item drop |
01 FB BC 24 BF 03 05 04 7F 0B
A5 E3 BC 2B BF 03 07 03 07 01
24 E3 BC 33 BF 04 09 04 07 01
7C 23 BD 3A BF 02 08 01 0C 06
7F E3 BC 42 BF 06 0A 06 0C 04
1A 3D BD 4D BF 0A 0B 04 00 00
42 23 BD 55 BF 04 07 07 0C 05
33 6F BD 5F BF 05 05 09 07 04
7A 9F BD 66 BF 08 0A 02 E5 0A
5F D2 BD 6B BF 0E 0A 05 FF 0E
53 00 BE 71 BF 10 0A 06 FF 0F
28 2E BE 7B BF 12 0B 06 FF 10
83 5D BE 83 BF 19 0B 08 00 00
Spawnrate for each biome (Biomes order: grasslands, steppes, construction, corruption): (by me)
03:A420 02 04 02 05 02 06 03 04 | 03 05 04 05 04 06 04 04 |................|
03:A430 0e 06 0e 07 0f 06 0f 07 | 10 06 10 07 11 06 11 05 |................|
03:A440 01 06 01 07 01 06 12 06 | 12 07 14 06 14 07 13 06 |................|
03:A450 05 04 05 05 05 06 07 04 | 07 05 08 05 08 04 09 04 |................|
TileList
Thanks Radixan for these!
TREES = 0x0F
TREES_UP = 0x6C
TREES_DOWN = 0x6F
TREES_LEFT = 0x6E
TREES_RIGHT = 0x6D
GRASS = 0x0A
GRASSIER = 0x7A
GRASSIEST = 0x7B
MEGA_GRASS = 0x31
TALL_GRASS = 0x0B
FLOWER = 0x74
POKE_UP = 0x33
POKE_RIGHT = 0x32
POKE_LEFT = 0x60
POKE_DOWN = 0x34
POKE_GRASS = 0x08
ROAD = 0x55
BARRIER = 0x13
BARRIER_UP = 0x4E
BARRIER_DOWN = 0x4D
BARRIER_LEFT = 0x51
BARRIER_RIGHT = 0x52
GLITCH_C9 = 0xC9
GLITCH_CA = 0xCA
GLITCH_CB = 0xCB
GLITCH_D9 = 0xD9
GLITCH_DB = 0xDB
GLITCH_EC = 0xEC
ItemList
Thanks vcvtne2ps2bf16 for them!
b917
b917
b917
b9b1
b967
b967
b9c6
b9de
bb12
b9b1
b9b1
bc13
bc40
bbfb
bbfb
bbfb
bc6d
bc6d
a621
Chall3-ASMbruteforcer
db 0b0b0b740a ;D3D0
db 0f0b0f0a0a ;D3D8
db 0f0f0a0a0b ;D3E0
db 0f0b0a0a0a ;D3E8
db 0b0b0a0a74 ;D3F0
D3F0 db firstExec
D3F1 db seed3
D3F2 db seed2
D3F3 db seed1
D3F3 db seed0
genNewKey: ;D400
ld a, 10
add a, (hl)
ldd (hl), a
ret nc
inc (hl)
ret nz
dec hl
inc (hl)
ret nz
dec hl
inc (hl)
ret
BruteForcer: ;D410
push af
push bc
push de
push hl
.start: ;D414
ld hl, DABB
push hl
call D400 ; genNewKey ;gen new key. REMEMBER TO SET THE LSB TO 1
.resetKeyBuffer ;D41B
pop hl
xor a ;resets the other buffer
inc hl
ldi (hl), a
ldi (hl), a
ldi (hl), a
ld (hl), a
.resetMapBuffer ;D422
ld hl, fls21_currentMap ;clears the map buffer
ld bc, 0240
ld d, 54
.resetLoop ;D42A
ld (hl), d
inc hl
dec bc
ld a, c
or b
jr nz, D42a :resetLoop
.resetChecknum ;D431
ld hl, fls21_currentChunkLOW ;reset chunkNum
xor a ;nop
ld b, a ;nop
inc a
ldi (hl), a
inc a
ld (hl), b
inc hl
ldi (hl), a
ld (hl), b
.genMapCall: ;D43D
call fls21Func_genMap
.startCheck ;D440
ld hl, D3D0
ld de, D890
ld c, 5 ;sequence equal
ld b, 5 ;charsEqual
push hl
.checkLoop: ;D44B
ld a, (de)
push af
inc de
ld a, e
and a
jr z, D46F; doneCheckLoop
pop af
cp (hl)
jr nz, D469;.wrongCheckLoop
dec c
jr nz, d466;.notDoneCheckLoop:
.doneWord: ;D459
pop hl
ld c, 5
ld a, 8
add a, l
ld l, a
push hl
dec b
jr z, d473; .win
jr D44B;checkLoop ;DONE
.notDoneCheckLoop: ;d466
inc hl
jr D44B; checkLoop
.wrongCheckLoop: D469;
ld c, 5
pop hl
push hl
jr D44B; checkLoop
.doneCheckLoop: ;D46F
pop af
pop hl
jr D414 ;start
.win: ;d473
pop hl
xor a
xor a
ei
.end:
pop hl
pop de
pop bc
pop af
ret
Chall3-bruteforcer
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
using namespace std;
const uint8_t DD0C_DATA[255] = {
0x05,0x0b,0x06,0x00,0x0e,0x05,0x0b,0x03,0x09,0x0e,0x00,0x00,0x00,0x0c,0x00,0x00,0x00,0x0c,0x05,0x06,0x06,0x0d,0x0a,0x09,0x09,0x0b,0x06,0x00,0x00,0x05,0x0a,0x00,0x05,0x0b,0x06,0x00,0x0b,0x06,0x09,0x07,0x00,0x09,0x07,0x0a,0x00,0x05,0x0a,0x00,0x00,0x09,0x06,0x00,0x03,0x06,0x0c,0x05,0x00,0x09,0x0f,0x0a,0x00,0x05,0x0a,0x00,0x05,0x0a,0x00,0x00,0x0f,0x06,0x05,0x03,0x09,0x0f,0x0a,0x00,0x00,0x0c,0x00,0x00,0x05,0x0b,0x03,0x06,0x0e,0x00,0x00,0x0d,0x09,0x06,0x00,0x0c,0x00,0x0d,0x03,0x0a,0x00,0x09,0x06,0x00,0x06,0x05,0x0a,0x05,0x09,0x0f,0x06,0x0c,0x00,0x0c,0x09,0x0a,0x00,0x0c,0x00,0x00,0x03,0x0f,0x07,0x03,0x00,0x0d,0x0a,0x00,0x00,0x0c,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
};
const uint8_t JUMP_TABLE[255] = {
0xce,0xab,0x51,0xac,0x71,0xac,0x24,0xad,0x44,0x4d,0xcb,0x25,0xcb,0x14,0xcb,0x25,0xcb,0x14,0xcb,0x25,0xcb,0x14,0xcb,0x25,0xcb,0x14,0xcb,0x25,0xcb,0x14,0x09,0x06,0x00,0x4f,0x09,0xc9,0x21,0x05,0x15,0x7a,0xcd,0x6c,0xab,0x7b,0xcd,0x6c,0xab,0x11,0xb4,0xda,0x1a,0xcd,0x6c,0xab,0x13,0x1a,0xcd,0x6c,0xab,0x13,0x1a,0xcd,0x6c,0xab,0x13,0x1a,0xcd,0x6c,0xab,0x7c,0xe6,0x3f,0x57,0x5d,0xc9,0xd5,0xe5,0xcd,0x88,0xab,0x06,0x01,0xcd,0x48,0xdc,0x28,0x10,0xe1,0x2b,0x11,0x1d,0xa2,0x1a,0x13,0x13,0xbe,0x20,0xfa,0x1b,0x1a,0x22,0xd1,0xc9,0xe1,0xd1,0xc9,0x01,0x0b,0x0a,0x16,0x30,0xcd,0xdc,0xa9,0x3e,0x0b,0xe0,0x97,0xcd,0x98,0xa9,0x3e,0x0f,0xe0,0x98,0x3e,0x0a,0xe0,0x99,0xaf,0xe0,0x9a,0xe0,0x9b,0x01,0x6c,0x0f,0x16,0x20,0xcd,0x1d,0xaa,0x3e,0x0a,0xe0,0x98,0x3e,0x0f,0xe0,0x99,0x01,0x6f,0x0f,0xcd,0x1d,0xaa,0xaf,0xe0,0x98,0xe0,0x99,0x3e,0x0a,0xe0,0x9a,0x3e,0x0f,0xe0,0x9b,0x01,0x6e,0x0f,0xcd,0x1d,0xaa,0x3e,0x0f,0xe0,0x9a,0x3e,0x0a,0xe0,0x9b,0x01,0x6d,0x0f,0xcd,0x1d,0xaa,0x01,0x74,0x0a,0x16,0x30,0xcd,0xdc,0xa9,0x01,0x7a,0x0a,0x16,0x30,0xcd,0xdc,0xa9,0x01,0x33,0x6c,0x16,0x40,0xcd,0xf8,0xa9,0x01,0x32,0x6d,0x16,0x40,0xcd,0xf8,0xa9,0x01,0x60,0x6e,0x16,0x40,0xcd,0xf8,0xa9,0x01,0x34,0x6f,0x16,0x40,0xc3,0xf8,0xa9,0x01,0x7b,0x0a,0x16,0x40,0xcd,0xdc,0xa9,0x01,0x7a,0x0a,0x16,0x30,0xcd,0xdc,0xa9,0x01,0x0b
};
#define BASE_MAP_DATA_OFFSET 0xD800
#define MAP_DATA_OFFSET0 0xD8C8 - BASE_MAP_DATA_OFFSET
#define MAP_DATA_OFFSET1 0xD8C0 - BASE_MAP_DATA_OFFSET
#define MAP_DATA_OFFSET2 0xD800 - BASE_MAP_DATA_OFFSET
#define MAP_DATA_OFFSET3 0xD808 - BASE_MAP_DATA_OFFSET
#define MAP_DATA_OFFSET4 0xD810 - BASE_MAP_DATA_OFFSET
#define MAP_DATA_OFFSET5 0xD8D0 - BASE_MAP_DATA_OFFSET
#define MAP_DATA_OFFSET6 0xD990 - BASE_MAP_DATA_OFFSET
#define MAP_DATA_OFFSET7 0xD988 - BASE_MAP_DATA_OFFSET
#define MAP_DATA_OFFSET8 0xD980 - BASE_MAP_DATA_OFFSET
#define NULL_BYTE_MAP_DATA 0xD887 - BASE_MAP_DATA_OFFSET
char asdjasdjidjasdnasd;
#define FINAL_DATA_SIZE 0x240
uint8_t *finalData = (uint8_t *) malloc(FINAL_DATA_SIZE * sizeof(uint8_t));
#define RAND_BUFFER_BASE 0xD170
#define RAND_BUFFER_BASE_OFFSET 0xD178
uint8_t *randBuffer1 = (uint8_t *) malloc(0x40 * sizeof(uint8_t));
uint8_t *randBuffer1_offset = randBuffer1 + 0x8;
uint8_t *randBuffer2 = (uint8_t *) malloc(0x40 * sizeof(uint8_t));
uint8_t _dab0;
uint8_t _ff95;
uint8_t _ff96;
uint8_t _ff97;
uint8_t _ff98;
uint8_t _ff99;
uint8_t _ff9a;
uint8_t _ff9b;
void hexDump(){
printf("-----------------------------------------------------------------------------------------------");
uint16_t startAddr = BASE_MAP_DATA_OFFSET;
for(int i = 0; i < FINAL_DATA_SIZE; i++){
if(i % 16 == 0)
printf("\n%04hx | ", startAddr);
else if(i % 8 == 0)
printf("| ");
printf("0x%02x ", finalData[i]);
startAddr++;
}
puts("\n-----------------------------------------------------------------------------------------------");
puts("Raw:");
for(int i = 0; i < FINAL_DATA_SIZE; i++){
printf("%02x", finalData[i]);
}
puts("\n-----------------------------------------------------------------------------------------------");
}
void hexDumpRand(uint8_t *randBuf){
printf("-----------------------------------------------------------------------------------------------");
uint16_t startAddr = 0xD170;
for(int i = 0; i < 0x40; i++){
if(i % 16 == 0)
printf("\n%04hx | ", startAddr);
else if(i % 8 == 0)
printf("| ");
printf("0x%02x ", randBuf[i]);
startAddr++;
}
puts("\n-----------------------------------------------------------------------------------------------");
puts("Raw:");
for(int i = 0; i < 0x40; i++){
printf("%02x", randBuf[i]);
}
puts("\n-----------------------------------------------------------------------------------------------");
}
uint8_t swap(uint8_t in){
return (in << 4) | (in >> 4);
}
uint8_t rotateLeft(uint8_t in, uint8_t qty){
return (in << qty) | (in >> (8 - qty));
}
uint16_t chunkDEsaved, chunkHLsaved;
void readChunk(uint16_t *chunkDE, uint16_t *chunkHL){
*chunkDE = chunkDEsaved;
*chunkHL = chunkHLsaved;
}
void writeChunk(uint16_t chunkDE, uint16_t chunkHL){
chunkDEsaved = chunkDE;
chunkHLsaved = chunkHL;
}
void copyMapTiles(uint8_t *mapData){
uint16_t hl = 0;
for(int x = 0; x < 8; x++){
for(int y = 0; y < 8; y++){
mapData[0] = randBuffer1[hl++];
mapData++;
}
mapData += 0x10;
}
}
uint8_t *seed = (uint8_t *) malloc(0x4 * sizeof(uint8_t));
uint8_t *seedBuffer = (uint8_t *) malloc(0x4 * sizeof(uint8_t));
void printSeed(){
printf("Seed: %02x %02x %02x %02x\n", seed[0], seed[1], seed[2], seed[3]);
}
uint8_t randomScramble(){
uint8_t a, b;
a = ++seedBuffer[0];
b = a;
a = seedBuffer[3];
a ^= b;
b = a;
a = seedBuffer[1];
a ^= b;
seedBuffer[1] = a;
b = a;
a = seedBuffer[2];
a += b;
seedBuffer[2] = a;
a = a >> 1;
a ^= b;
b = a;
a = seedBuffer[3];
a += b;
seedBuffer[3] = a;
return a;
}
void randomFromSeed(uint8_t d, uint8_t e, uint8_t b, uint8_t c){
seedBuffer[0] = seed[0] ^ d;
seedBuffer[1] = seed[1] ^ e;
seedBuffer[2] = seed[2] ^ b;
seedBuffer[3] = seed[3] ^ c;
for(int c = 0; c < 0x10; c++){
randomScramble();
}
}
void chooseRandomByte(uint16_t chunkDE, uint16_t chunkHL){
uint8_t b = chunkDE & 0x3, c = chunkHL & 0x3;
randomFromSeed(chunkDE >> 8, chunkDE & 0xfc, chunkHL >> 8, chunkHL & 0xfc);
c = DD0C_DATA[(c * 4 + b) + swap(randomScramble() & 0x7)];
_ff95 = randomScramble() & 0x30 | c;
uint8_t a = (((chunkDE & 0xff) | (chunkHL & 0xff)) & 0xfc) | (chunkDE >> 8) | (chunkHL >> 8);
if(!a) {
_ff95 = c & 0x0f;
}
}
void writeHLfromFF97(uint8_t b){
randBuffer1[(swap(b) & 0x0F) + ((b & 0x0f) * 8)] = _ff97;
}
void writeFF97andBlocksDontSaveE(uint8_t d, uint8_t e, uint8_t b, uint8_t c){
d = 0xF0;
if((swap(c) & 0x0f) >= (swap(b) & 0x0f))
d = 0x10;
e = 0xFF;
if((c & 0x0f) >= (b & 0x0f))
e = 0x01;
do {
writeHLfromFF97(b);
if((swap(c) & 0x0f) != (swap(b) & 0x0f))
b += d;
writeHLfromFF97(b);
if((c & 0x0f) != (b & 0x0f))
b += e;
writeHLfromFF97(b);
} while (c != b);
}
void writeFF97andBlocks(uint8_t d, uint8_t e, uint8_t b, uint8_t c){
_ff97 = e;
writeFF97andBlocksDontSaveE(d, e, b, c);
}
void writeRandomByte(uint8_t d, uint8_t e, uint8_t b, uint8_t c){
uint8_t a, h, l;
do { a = randomScramble() & 0x7; } while(a == 0 || a == 0x7);
d = swap(a);
do { a = randomScramble() & 0x7; } while(a == 0 || a == 0x7);
d |= a;
uint8_t bBak = d;
uint8_t cBak = c;
writeFF97andBlocks(d, e, b, d);
writeFF97andBlocks(d, e, bBak, cBak);
}
void expandLeft(uint8_t *buf, uint8_t searchingByte, int index){
buf[index - 1] = searchingByte;
}
void expandRight(uint8_t *buf, uint8_t searchingByte, int index){
buf[index + 1] = searchingByte;
}
void expandUp(uint8_t *buf, uint8_t searchingByte, int index){
buf[index - 8] = searchingByte;
}
void expandDown(uint8_t *buf, uint8_t searchingByte, int index){
buf[index + 8] = searchingByte;
}
void expandSelectedTile(){
uint8_t *buf = randBuffer1_offset;
for(int c = 0; c < 0x30; c++)
randBuffer2[c] = buf[c];
uint8_t searchingByte = _ff97;
uint16_t realAddress = RAND_BUFFER_BASE_OFFSET;
for(int c = 0; c < 0x30; c++){
if(randBuffer2[c] == searchingByte){
uint16_t addrNibble = realAddress & 0x7;
if(addrNibble == 0 || addrNibble == 0x7)
goto doLoop;
uint8_t testByte = randomScramble();
if(testByte & 0x1) expandLeft(buf, searchingByte, c);
if((testByte >> 1) & 0x1) expandRight(buf, searchingByte, c);
if((testByte >> 2) & 0x1) expandUp(buf, searchingByte, c);
if((testByte >> 3) & 0x1) expandDown(buf, searchingByte, c);
}
doLoop:
realAddress++;
}
}
void replaceRandBwithC(uint8_t b, uint8_t c, uint8_t chances){
_ff97 = b;
for(int e = 0; e < 0x40; e++){
if(randBuffer1[e] == b){
uint8_t a = randomScramble();
if(a < chances)
randBuffer1[e] = c;
}
}
}
void randomFillPLUSifPattern(uint8_t b, uint8_t c, uint8_t chances){
_ff97 = b;
uint8_t *buf = randBuffer1_offset;
uint16_t realAddress = RAND_BUFFER_BASE_OFFSET;
for(int e = 0; e < 0x30; e++){
if(buf[e] == b){
uint16_t addrNibble = realAddress & 0x7;
if(addrNibble == 0 || addrNibble == 0x7)
goto doLoop;
uint8_t a = randomScramble();
if(a >= chances){
int editE = e - 0x8;
if(_ff98 != 0)
if(_ff98 != buf[editE])
goto doLoop;
editE += 0x10;
if(_ff99 != 0)
if(_ff99 != buf[editE])
goto doLoop;
editE -= 0x9;
if(_ff9a != 0)
if(_ff9a != buf[editE])
goto doLoop;
editE += 0x2;
if(_ff9b != 0)
if(_ff9b != buf[editE])
goto doLoop;
buf[e] = c;
}
}
doLoop:
realAddress++;
}
}
void replaceRandBwithCOnly30Bno0and7(uint8_t b, uint8_t c, uint8_t chances){
_ff97 = b;
uint8_t *buf = randBuffer1_offset;
uint16_t realAddress = RAND_BUFFER_BASE_OFFSET;
for(int e = 0; e < 0x30; e++){
uint16_t addrNibble = realAddress & 0x7;
if(addrNibble == 0 || addrNibble == 0x7)
goto doLoop;
if(buf[e] == b){
uint8_t a = randomScramble();
if(a < chances)
buf[e] = c;
}
doLoop:
realAddress++;
}
}
void addRandomTiles_ABCE(){
replaceRandBwithC(0x0A, 0x0b, 0X30);
_ff97 = 0x0B;
expandSelectedTile();
_ff98 = 0x0f;
_ff99 = 0x0a;
_ff9a = 0x0;
_ff9b = 0x0;
randomFillPLUSifPattern(0x0f, 0x6c, 0x20);
_ff98 = 0x0a;
_ff99 = 0x0f;
randomFillPLUSifPattern(0x0f, 0x6f, 0x20);
_ff98 = 0x0;
_ff99 = 0x0;
_ff9a = 0x0a;
_ff9b = 0x0f;
randomFillPLUSifPattern(0x0f, 0x6e, 0x20);
_ff9a = 0x0f;
_ff9b = 0x0a;
randomFillPLUSifPattern(0x0f, 0x6d, 0x20);
replaceRandBwithC(0x0A, 0x74, 0x30);
replaceRandBwithC(0x0A, 0x7a, 0x30);
replaceRandBwithCOnly30Bno0and7(0x6c, 0x33, 0x40);
replaceRandBwithCOnly30Bno0and7(0x6d, 0x32, 0x40);
replaceRandBwithCOnly30Bno0and7(0x6e, 0x60, 0x40);
replaceRandBwithCOnly30Bno0and7(0x6f, 0x34, 0x40);
}
void addRandomTiles_AC51(){
replaceRandBwithC(0x0A, 0x7B, 0x40);
replaceRandBwithC(0x0A, 0x7A, 0x30);
replaceRandBwithC(0x0A, 0x0B, 0xD0);
replaceRandBwithCOnly30Bno0and7(0x0a, 0x08, 0x20);
}
void addRandomTiles_AC71(){
_ff98 = 0x0;
_ff99 = 0x0;
_ff9a = 0x0;
_ff9b = 0x0a;
randomFillPLUSifPattern(0x0f, 0x13, 0xc0);
_ff9a = 0x0a;
_ff9b = 0x0;
randomFillPLUSifPattern(0x0f, 0x13, 0xc0);
_ff9a = 0x0f;
randomFillPLUSifPattern(0x0a, 0x4e, 0x60);
_ff9a = 0x0;
_ff9b = 0x0f;
randomFillPLUSifPattern(0x0a, 0x4d, 0x60);
_ff9b = 0x0;
_ff98 = 0x0f;
randomFillPLUSifPattern(0x0a, 0x51, 0x60);
_ff98 = 0x0;
_ff99 = 0x0f;
randomFillPLUSifPattern(0x0a, 0x52, 0x60);
_ff97 = 0x55;
uint8_t d = _ff95, e = _ff96, b, c;
uint8_t testByte = d;
if((testByte >> 3) & 0x1) writeFF97andBlocksDontSaveE(d, e, e, 0x40);
if((testByte >> 2) & 0x1) writeFF97andBlocksDontSaveE(d, e, e, 0x47);
if((testByte >> 1) & 0x1) writeFF97andBlocksDontSaveE(d, e, e, 0x04);
if(testByte & 0x1) writeFF97andBlocksDontSaveE(d, e, e, 0x74);
replaceRandBwithC(0x0a, 0x0b, 0x50);
replaceRandBwithC(0x0a, 0x7a, 0xc0);
replaceRandBwithC(0x0a, 0x0b, 0x74);
replaceRandBwithCOnly30Bno0and7(0x4e, 0x32, 0x18);
replaceRandBwithCOnly30Bno0and7(0x4d, 0x60, 0x18);
}
void addRandomTiles_AD24(){
_ff98 = 0x0;
puts("REACHED AD24 ATTENCTION!!!");
exit(1);
}
void addRandomTiles(uint16_t jumpTo){
switch(jumpTo){
case 0xABCE: { addRandomTiles_ABCE(); return; }
case 0xAC51: { addRandomTiles_AC51(); return; }
case 0xAC71: { addRandomTiles_AC71(); return; }
case 0xAD24: { addRandomTiles_AD24(); return; }
}
}
void hlStrangeAddA(uint8_t a, uint8_t *h, uint8_t *l){
uint8_t b = *h;
uint8_t c = *l;
*l = *l << 5;
*h = rotateLeft(*h, 5);
uint16_t hl = (*h << 8) | *l;
uint16_t bc = (b << 8) | c;
hl += bc + a;
*l = hl & 0xff;
*h = hl >> 8;
}
void getRandomDE(uint8_t *d, uint8_t *e){
uint8_t h = 0x15, l = 0x05;
hlStrangeAddA(*d, &h, &l);
hlStrangeAddA(*e, &h, &l);
hlStrangeAddA(chunkDEsaved & 0xff, &h, &l);
hlStrangeAddA(chunkDEsaved >> 8, &h, &l);
hlStrangeAddA(chunkHLsaved & 0xff, &h, &l);
hlStrangeAddA(chunkHLsaved >> 8, &h, &l);
*d = h & 0x3F;
*e = l;
}
void genRandomItem(uint8_t d, uint8_t e, uint16_t hl){
getRandomDE(&d, &e);
}
void genMapInternal_2(uint8_t *mapData, uint16_t chunkDE, uint16_t chunkHL){
chooseRandomByte(chunkDE, chunkHL);
memset(randBuffer1, 0x0F, 0x40);
uint8_t testByte = _ff95;
if(testByte & 0x1) _ff96 = 0x74;
if((testByte >> 1) & 0x1) _ff96 = 0x04;
if((testByte >> 2) & 0x1) _ff96 = 0x47;
if((testByte >> 3) & 0x1) _ff96 = 0x40;
randomFromSeed(chunkDE >> 8, chunkDE & 0xff, chunkHL >> 8, chunkHL & 0xff);
uint8_t b = _ff96, d = _ff95, e = 0x0A;
if(testByte & 0x1) writeRandomByte(d, e, b, 0x74);
if((testByte >> 1) & 0x1) writeRandomByte(d, e, b, 0x04);
if((testByte >> 2) & 0x1) writeRandomByte(d, e, b, 0x47);
if((testByte >> 3) & 0x1) writeRandomByte(d, e, b, 0x40);
_ff97 = 0x0A;
expandSelectedTile();
if((testByte >> 3) & 0x1){
writeHLfromFF97(0x30);
writeHLfromFF97(0x40);
}
if((testByte >> 2) & 0x1){
writeHLfromFF97(0x37);
writeHLfromFF97(0x47);
}
if((testByte >> 1) & 0x1){
writeHLfromFF97(0x03);
writeHLfromFF97(0x04);
}
if(testByte & 0x1){
writeHLfromFF97(0x73);
writeHLfromFF97(0x74);
}
e = (swap(d) & 0x3) * 2;
uint16_t jumpTo = JUMP_TABLE[e] | (JUMP_TABLE[e + 1] << 8);
addRandomTiles(jumpTo);
}
void genMapInternal_1(uint8_t *mapData, uint16_t chunkDE, uint16_t chunkHL){
writeChunk(chunkDE, chunkHL);
genMapInternal_2(mapData, chunkDE, chunkHL);
copyMapTiles(mapData);
}
void reset(){
memset(finalData, 0x54, FINAL_DATA_SIZE);
memset(seedBuffer, 0, 0x4);
_dab0 = 0;
_ff95 = 0;
_ff96 = 0;
_ff97 = 0;
_ff98 = 0;
_ff99 = 0;
_ff9a = 0;
_ff9b = 0;
}
void genMap(){
uint16_t chunkDE = 1, chunkHL = 2; writeChunk(chunkDE, chunkHL);
reset();
genMapInternal_1(finalData + MAP_DATA_OFFSET0, chunkDE, chunkHL);
_dab0 = _ff95;
chunkDE--;
genMapInternal_1(finalData + MAP_DATA_OFFSET1, chunkDE, chunkHL);
chunkHL--;
genMapInternal_1(finalData + MAP_DATA_OFFSET2, chunkDE, chunkHL);
chunkDE++;
genMapInternal_1(finalData + MAP_DATA_OFFSET3, chunkDE, chunkHL);
chunkDE++;
genMapInternal_1(finalData + MAP_DATA_OFFSET4, chunkDE, chunkHL);
chunkHL++;
genMapInternal_1(finalData + MAP_DATA_OFFSET5, chunkDE, chunkHL);
chunkHL++;
genMapInternal_1(finalData + MAP_DATA_OFFSET6, chunkDE, chunkHL);
chunkDE--;
genMapInternal_1(finalData + MAP_DATA_OFFSET7, chunkDE, chunkHL);
chunkDE--;
genMapInternal_1(finalData + MAP_DATA_OFFSET8, chunkDE, chunkHL);
writeChunk(chunkDE, chunkHL);
finalData[NULL_BYTE_MAP_DATA] = 0;
}
void updateKey(){
seed[3] += 0x10;
if(seed[3] == 0x1){
seed[2]++;
if(seed[2] == 0){
seed[1]++;
if(seed[1] == 0){
seed[0]++;
if(seed[0] == 0){
puts("This is the end of our journey :(");
exit(1);
}
printSeed();
}
}
}
}
#define SEQUENCE_NO 5
#define SEQUENCE_DIMENSION 5
char idoapdkopsakdopsa;
uint8_t sequences[SEQUENCE_NO][SEQUENCE_DIMENSION] = {
{0x0b, 0x0b, 0x0b, 0x74, 0x0a},
{0x0f, 0x0b, 0x0f, 0x0a, 0x0a},
{0x0f, 0x0f, 0x0a, 0x0a, 0x0b},
{0x0f, 0x0b, 0x0a, 0x0a, 0x0a},
{0x0b, 0x0b, 0x0a, 0x0a, 0x74}
};
void searchInData(){
int currentSequenceCounter = 0;
uint8_t *currentSequence = sequences[currentSequenceCounter];
int indexInSequence = 0;
int startMatchI = -1;
int start = (0xD890 - BASE_MAP_DATA_OFFSET);
int end = 0xD900 - 0xD890 + start + 1;
for(int i = start; i < end; i++){
uint8_t b = finalData[i];
if(b == currentSequence[indexInSequence]){
if(startMatchI < 0)
startMatchI = i;
indexInSequence++;
if(indexInSequence >= SEQUENCE_DIMENSION){
indexInSequence = 0;
currentSequenceCounter++;
if(currentSequenceCounter < SEQUENCE_NO){
currentSequence = sequences[currentSequenceCounter];
} else {
printf("!!! FOUND A POSSIBLE ");
printSeed();
}
}
} else {
indexInSequence = 0;
if(startMatchI >= 0){
i = startMatchI;
startMatchI = -1;
}
}
}
}
void bruteforce(){
printf("Inserit the starting byte in hex: ");
int in;
scanf("%x", &in);
memset(seed, 0, 0x4);
seed[0] = in & 0xff;
seed[3] = 0x1;
printf("Hex of tile 1 last row [4][2] (6F: half tree, 0A: ground tile): ");
scanf("%x", &in);
sequences[4][2] = in & 0xff;
printf("Hex of tile 2 last row [4][3] (6F: half tree, 0A: ground tile): ");
scanf("%x", &in);
sequences[4][3] = in & 0xff;
printf("Starting by ");
printSeed();
printf("With low tiles [4][2] = 0x%02x and [4][3] = 0x%02x\n", sequences[4][2], sequences[4][3]);
reset();
puts("GO!");
while(true){
genMap();
searchInData();
updateKey();
}
}
int main(){
bruteforce();
seed[0] = 0x8b;
seed[1] = 0xdc;
seed[2] = 0x2d;
seed[3] = 0x80;
genMap();
hexDump();
searchInData();
}
Chall3-autosubmitter
I used pwntools because I had no idea how to read/write from/to stdin/out in python.
I’m a dirty java developer after all, if I don’t have my StdinoutCustomFactoryBuilderSingleton i can’t do anything
import requests
from pwn import *
import threading
INDEX = "http://fools2021.online/index.php"
LOGIN = "http://fools2021.online/login.php"
EXTRA = "http://fools2021.online/extras.php"
USR = "! Stranck"
PW = "https://youtu.be/mhIrhcZ087c"
TRIED_SEEDS = ['20150441', '40282AD1', '8030C831', '602DD231', 'E066AC41', 'E08D10B1', '6096B651', 'C0B73D31', '40CB4DB1', 'E0D55731', 'A0D8BDE1', '20F10D81', '81047421', '21195601', 'E15BFAE1', 'A15D3EC1', 'C1697D31', 'A17BA351', 'C1859F01', 'E19C2B81', '01AA8E61', '21BFE621', 'E20607A1', '621B7C11', 'C26D5F01', '627A30A1', 'A2BF5961', '42D577F1', '82F7E391', '03193461', 'E33E6291', '434838E1', '6345F431', '6359CAF1', 'C3723361', 'A37A9C21', '837FDFE1', '23BFA431', '43D40321', '83E606A1', 'A42791A1', 'C434BF91', '0441CCA1', 'E4958F41', '8495D0B1', 'E4958F41', '84D24D61', 'A4D9AC61', 'A4F54441', '05071461', 'A51248B1', 'C5431D81', 'E54E6211', '054DA6A1', '2583E711', '2586DB71', 'A5A6ED91', '25EB7AC1', '461D9C51', 'A6A34CD1', '66A8EF81', '46B9BB01', 'A6DCF0B1', '872374B1', '2725EBB1', '872374B1', '472ED791', '67318151', '07625421', '8770B001', '8770B001', '0782F7C1', '678F9B11', 'C7B47AE1', 'E7C30341', '2821BE41', '4829AA71', '283A5801', 'C84F3C51', 'C8502C51', 'A86098D1', 'E869DD31', '68695A01', 'E874EDB1', '68778001', 'E8BADC81', '28DDE7E1', 'E90201E1', 'C928BEF1', 'C97498D1', '89B477A1', '29B57A01', 'C9CB6E11', '8A037E61', '4A11B611', '0A1E56D1', '4A275021', 'EA5A3541', 'CA5DB181', 'EA6C7871', '2A836E41', 'EA821F31', '4A813571', 'CAA37A61', '2AE9C451', 'EAEBD421', '2B005A41', 'EB0781A1', '4B4244B1', 'AB45ABF1', '4B499151', '2B8EB841', '4B8A0DC1', '4B9AA8C1', 'CBBF1541', '4BDEC061', '0BE2EAC1', '0C058251', '8C10F531', 'AC37E801', '4C59C011', '8C967AF1', '8D1070A1', 'ED0D39C1', '4D267771', '6D6A47D1', '6D6DFA71', 'CD7BA8F1', 'ED9A6851', '8DA383A1', 'AD983191', '8DB0B831', '6DB2D741', 'EDE52E61', 'EDF49E21', '4DF3F551', 'AE19D6D1', 'AE59F071', '6E6D8B71', 'CE936271', 'EECA5D91', '8EC14F11', '6EF89421', '8F178601', '2F28C1E1', '8F696951', '0F73F241', '4F89A371', 'EF8B0DF1', '8FB5D6A1', '6FB31C31', '0FBE7881', '2FF5DAE1', '7014A731', '7046A501', '1048DC31', 'B03C8621', '30635CE1', '7084F711', '909D7411', 'D0F88841', '91136F91', '911BB401', '70F6C6C1', '3161E7E1', 'D14CE681', '916D5471', 'B17EDE61', 'B1B7AB41', 'F1D789C1', 'D1CEEF71', '723AE011', '12376241', 'F2403C11', '1277C151', '9290FE01', '92D7D431', '72D9ABA1', '12E7E501', 'F2E46891', '32E59E21', '73029A41', '52D15B41', '52D15B41', '52F0D431', '93221D31', '1325DAD1', 'F33FA091', '1370D541', '1398E811', 'D389C2F1', 'D3C6D7E1', 'B3FD8341', 'F4200B21', '14459321', '14562C61', '147B5FD1', '14E526A1', 'B5099251', 'D50628A1', '55397731', '553AFB71', 'D551EC91', '756F6581', '55AB1A31', '35D4C601', 'F5C40C01', '75DC6571', '55D6F491', 'F5E80E31', 'B5EB0381', 'D5E7F3C1', '160484E1', '961B3621', 'B64D29E1', 'B67F0E31', '16993951', 'B6C76031', '56D76601', '5748A101', '576D3921', '579ACBE1', 'F7A184E1', 'D7B2DAC1', 'D7C896E1', '77F694F1', '1819A371', '183A9CD1', 'B85A04C1', '5860DC81', '98740131', 'B870EF31', 'B88913B1', '189D32D1', '58D15D31', 'B8EEEC91', '58FDD5D1', 'B9163DD1', '59209921', 'B92C7D41', '7965B641', 'F972A311', '39C07371', 'D9B186A1', 'D9F6F6E1', '7A18A6F1', '7A3E5871', '7A5E6401', '5A530971', 'BA72CF11', 'DAA9CA31', '7ADB4271', '9AD7C3E1', '3AE82BA1', '7AEA9681', '9B0A5861', '3B145651', 'BB01FD61', '3B570241', '7B6D64A1', '7B6D64A1', '1B99E7E1', 'FB9D4551', 'DBB47721', 'FBEBE211', '9BFFD9C1', '9C1123B1', '5C0C70B1', 'DC469BB1', 'FC9EA381', '5CA44201', '7CE262A1', '7CE262A1', '5D06A451', '1D98EFA1', '5DAAC591', '5DCE1301', '7E01FAF1', '7E588F71', '7E6E9871', '1EDCDDF1', 'BF00E621', '3F262ED1', 'DF151D01', '9F49F281', '7F5EB2F1', 'FF7FB981', 'FFAE9751', 'BFC70201', '20150441', '40282AD1', '8030C831', '602DD231', 'E066AC41', '6096B651', 'E08D10B1', '40CB4DB1', 'C0B73D31', 'A0D8BDE1', '20F10D81', 'E0D55731', 'A15D3EC1', 'C1697D31', 'A17BA351', 'E15BFAE1', 'C1859F01', 'E19C2B81', 'E20607A1', 'C26D5F01', 'A2BF5961', 'E33E6291', 'A37A9C21', 'C3723361', 'DF151D01', 'E066AC41', 'E08D10B1', 'E0D55731', 'E15BFAE1', 'E19C2B81', 'E20607A1', 'E33E6291', 'E4958F41', 'E4958F41', 'FF7FB981', 'FFAE9751', '20150441', '40282AD1', '602DD231', '8030C831', 'E066AC41', 'E08D10B1', '6096B651', 'C0B73D31', '40CB4DB1', 'E0D55731', 'A0D8BDE1', '20F10D81', '81047421', '21195601', 'E149CE31', 'A15D3EC1', 'E15BFAE1', 'C1697D31', 'A17BA351', 'C1859F01', 'E19C2B81', '01AA8E61', '21BFE621', 'E20607A1', '621B7C11', '824EF8C1', '425EE2F1', 'C26D5F01', '627A30A1', 'A2BF5961', '42D577F1', '82F7E391', '03193461', 'E33E6291', '6345F431', '6359CAF1', 'C3723361', 'A37A9C21', '837FDFE1', '23BFA431', '43D40321', '83E606A1', 'A42791A1', 'C434BF91', '0441CCA1', '8495D0B1', 'E4958F41', 'E4958F41', '84D24D61', 'A4D9AC61', 'A4F54441', 'A51248B1', '05071461', 'C5431D81', 'E54E6211', '2583E711', '2586DB71', 'A5A6ED91', '25EB7AC1', 'A60076C1', '461D9C51', '66548271', 'A6A34CD1', '66A8EF81', '46B9BB01', 'C6B06451', 'A6DCF0B1', '872374B1', '872374B1', '2725EBB1', '472ED791', '67318151', '8770B001', '8770B001', '07625421', '0782F7C1', '678F9B11', 'C7A42471', 'C7B47AE1', '47C123F1', 'E7C30341', '48090FD1', '2821BE41', '4829AA71', '283A5801', 'C84F3C51', 'C8502C51', 'A86098D1', '68695A01', '886FBBB1', 'E869DD31', '48754CA1', '68778001', 'E874EDB1', 'E8BADC81', '28DDE7E1', 'E90201E1', 'C928BEF1', 'C97498D1', '698EB131', '89B477A1', '29B57A01', 'C9CB6E11', '8A037E61', '4A11B611', '4A275021', '0A1E56D1', 'CA5DB181', '2A5554B1', 'EA5A3541', '4A813571', '2A836E41', 'CA838891', 'EA821F31', 'CAA37A61', '2AE9C451', 'EAEBD421', '2B005A41', 'EB0781A1', '0B1DB681', '4B4244B1', 'AB45ABF1', '4B499151', '4B8A0DC1', '2B8EB841', '4B9AA8C1', 'CBBF1541', '0BE2EAC1', '8C10F531', '0C058251', 'AC37E801', '4C59C011', '8C967AF1', '8D1070A1', '4D267771', 'ED0D39C1', '6D6A47D1', '6D6DFA71', 'CD7BA8F1', 'AD983191', '8DA383A1', '8DB0B831', '6DB2D741', 'ED9A6851', '4DF3F551', 'EDE52E61', 'EDF49E21', 'AE19D6D1', 'AE59F071', '6E6D8B71', 'CE936271', '8EC14F11', 'EECA5D91', '8F178601', '2F28C1E1', '8F696951', '0F73F241', 'EF8B0DF1', '8FB5D6A1', '6FB31C31', '0FBE7881', '6FFB4691', '2FF5DAE1', '7014A731', 'B03C8621', '7046A501', '30635CE1', '7084F711', '909D7411', '70F6C6C1', 'D0F88841', '91136F91', '911BB401', 'D14CE681', '916D5471', '3161E7E1', 'B17EDE61', 'B1B7AB41', 'D1CEEF71', 'F1D789C1', '12376241', 'B266AC71', 'F2403C11', '1277C151', '9290FE01', '52D15B41', '52D15B41', '92D7D431', '72D9ABA1', '12CA0491', '52F0D431', '32E59E21', '12E7E501', '73029A41', 'F2E46891', '931D27E1', '93221D31', '1325DAD1', 'F33FA091', '1370D541', 'D389C2F1', '1398E811', 'D3C6D7E1', 'B3FD8341', '14459321', 'F4200B21', '14562C61', '147B5FD1', '14E526A1', 'B5099251', 'D50628A1', '55397731', '553AFB71', 'D551EC91', '55AB1A31', '35D4C601', '75DC6571', 'B5EB0381', 'D5E7F3C1', 'F5C40C01', '961B3621', '160484E1', 'B64D29E1', 'B67F0E31', '16993951', 'B6C76031', '56D76601', '5748A101', '576D3921', '579ACBE1', 'D7B2DAC1', 'D7C896E1', 'F7A184E1', '77F694F1', '17FF7E51', '1819A371', '183A9CD1', 'B85A04C1', '5860DC81', 'B870EF31', '98740131', 'B88913B1', '189D32D1', '58D15D31', 'B8EEEC91', '58FDD5D1', 'B9163DD1', '59209921', 'B92C7D41', '7965B641', 'D9B186A1', 'F972A311', '39C07371', 'D9F6F6E1', '7A18A6F1', '9A3CF3B1', '7A3E5871', '5A530971', '7A5E6401', 'BA72CF11', 'DAA9CA31', '7ADB4271', '3AE82BA1', '7AEA9681', 'BB01FD61', '9B0A5861', '3B145651', '3B570241', '7B6D64A1', '7B6D64A1', '1B99E7E1', 'DBB47721', '9BFFD9C1', '9C1123B1', '5C0C70B1', 'FBEBE211', 'DC469BB1', '5C6094E1', '5CA44201', '7CE262A1', '7CE262A1', '5D06A451', '1D98EFA1', '5DCE1301', '7E01FAF1', '7E588F71', '7E6E9871', '1E8CB681', '1EDCDDF1', 'BF00E621', 'DF151D01', '3F262ED1', '7F5EB2F1', '1FA166E1', 'BFC70201', 'FF7FB981', 'FFAE9751', '20150441', '602DD231', '40282AD1', '8030C831', 'E066AC41', '6096B651', 'E08D10B1', 'C0B73D31', 'A0D8BDE1', '40CB4DB1', 'E0D55731', '20F10D81', 'A15D3EC1', 'E149CE31', 'C1697D31', 'E15BFAE1', 'A17BA351', 'C1859F01', 'E19C2B81', 'E20607A1', 'C26D5F01', 'A2BF5961', 'E33E6291', 'C3723361', 'A37A9C21', 'A42791A1', 'C434BF91', 'E4958F41', 'E4958F41', 'A4D9AC61', 'A4F54441', 'A51248B1', 'C5431D81', 'E54E6211', 'A5A6ED91', 'A60076C1', 'A6A34CD1', 'C6B06451', 'A6DCF0B1', 'C7A42471', 'C7B47AE1', 'E7C30341', 'C84F3C51', 'C8502C51', 'A86098D1', 'E869DD31', 'E874EDB1', 'E8BADC81', 'C928BEF1', 'E90201E1', 'C97498D1', 'C9CB6E11', 'CA5DB181', 'EA5A3541', 'CA838891', 'CAA37A61', 'EA821F31', 'EAEBD421', 'EB0781A1', 'AB45ABF1', 'CBBF1541', 'AC37E801', 'ED0D39C1', 'CD7BA8F1', 'AD983191', 'ED9A6851', 'AE19D6D1', 'EDE52E61', 'EDF49E21', 'AE59F071', 'CE936271', 'EECA5D91', 'EF8B0DF1', 'B03C8621', 'D0F88841', 'D14CE681', 'B17EDE61', 'B1B7AB41', 'D1CEEF71', 'F1D789C1', 'B266AC71', 'F2403C11', 'F2E46891', 'F33FA091', 'D389C2F1', 'D3C6D7E1', 'B3FD8341', 'F4200B21', 'B5099251', 'D50628A1', 'D551EC91', 'B5EB0381', 'D5E7F3C1', 'F5C40C01', 'B64D29E1', 'B67F0E31', 'B6C76031', 'D7B2DAC1', 'D7C896E1', 'F7A184E1', 'B85A04C1', 'B870EF31', 'B88913B1', 'B8EEEC91', 'B9163DD1', 'B92C7D41', 'D9B186A1', 'F972A311', 'D9F6F6E1', 'BA72CF11', 'DAA9CA31', 'BB01FD61', 'DBB47721', 'FBEBE211', 'DC469BB1', 'BF00E621', 'DF151D01', 'BFC70201', 'FF7FB981', 'FFAE9751', 'E066AC41', 'E08D10B1', 'C0B73D31', 'E0D55731', 'E149CE31', 'E15BFAE1', 'C1697D31', 'C1859F01', 'E19C2B81', 'E20607A1', 'C26D5F01', 'E33E6291', 'C3723361', 'C434BF91', 'E4958F41', 'E4958F41', 'C5431D81', 'E54E6211', 'C6B06451', 'C7A42471', 'C7B47AE1', 'E7C30341', 'C84F3C51', 'C8502C51', 'E869DD31', 'E874EDB1', 'E8BADC81', 'E90201E1', 'C928BEF1', 'C97498D1', 'C9CB6E11', 'CA5DB181', 'EA5A3541', 'EA821F31', 'CA838891', 'CAA37A61', 'EAEBD421', 'EB0781A1', 'CBBF1541', 'ED0D39C1', 'CD7BA8F1', 'ED9A6851', 'EDE52E61', 'EDF49E21', 'CE936271', 'EECA5D91', 'EF8B0DF1', 'D0F88841', 'D14CE681', 'D1CEEF71', 'F1D789C1', 'F2403C11', 'F2E46891', 'F33FA091', 'D389C2F1', 'D3C6D7E1', 'F4200B21', 'D50628A1', 'D551EC91', 'F5C40C01', 'D5E7F3C1', 'F7A184E1', 'D7B2DAC1', 'D7C896E1', 'F972A311', 'D9B186A1', 'D9F6F6E1', 'DAA9CA31', 'DBB47721', 'FBEBE211', 'DC469BB1', 'DF151D01', 'FF7FB981', 'FFAE9751', 'E066AC41', 'E08D10B1', 'E0D55731', 'E149CE31', 'E15BFAE1', 'E19C2B81', 'E20607A1', 'E33E6291', 'E4958F41', 'E4958F41', 'E54E6211', 'E7C30341', 'E869DD31', 'E874EDB1', 'E8BADC81', 'E90201E1', 'EA5A3541', 'EA821F31', 'EAEBD421', 'EB0781A1', 'ED0D39C1', 'ED9A6851', 'EDE52E61', 'EDF49E21', 'EECA5D91', 'EF8B0DF1', 'F1D789C1', 'F2403C11', 'F2E46891', 'F33FA091', 'F4200B21', 'F5C40C01', 'F7A184E1', 'F972A311', 'FBEBE211', 'FF7FB981', 'FFAE9751']
def getCSR(text):
return text.split("name='csr' value='")[1].split("'")[0]
'''
s = requests.Session()
r = s.get(LOGIN)
csr = getCSR(r.text)
r = s.post(LOGIN, {"csr": csr, "user": USR, "pass": PW})
resp = s.get(EXTRA)
csr = getCSR(resp.text)
resp = s.post(EXTRA, {"csr": csr, "challenge3": "asdasd"})
print(resp.text)
print(resp.url)
print(resp.status_code)
exit(0)'''
def login(s):
print("Loggin in")
r = s.get(LOGIN)
csr = getCSR(r.text)
r = s.post(LOGIN, {"csr": csr, "user": USR, "pass": PW})
assert r.url == INDEX
def submit(s, seed, csr):
try:
if seed in TRIED_SEEDS:
print(f"Already tried: {seed}")
return csr
else:
resp = s.post(EXTRA, {"csr": csr, "challenge3": seed})
if(resp.url != EXTRA):
print("Login expired")
login(s)
resp = s.get(EXTRA)
return submit(s, seed, getCSR(resp.text))
elif("Invalid session." in resp.text):
print("Csr expired")
resp = s.get(EXTRA)
return submit(s, seed, getCSR(resp.text))
elif("The password is invalid." in resp.text):
print(f"Tried seed {seed}, but it failed :(")
else:
print(f"BOOOM! >>>{seed}<<<")
print(resp.status_code)
print(resp.text)
csr = getCSR(resp.text)
return csr
except:
print(f"An error occurred while submitting {seed}")
return csr
def run(start, stop):
s = requests.Session()
login(s)
print("Getting extra page")
r = s.get(EXTRA)
csr = getCSR(r.text)
r = process("./chall3")
r.recvuntil(":", timeout = 1)
r.sendline(start)
r.recvuntil(":", timeout = 1)
r.sendline("0a")
r.recvuntil(":", timeout = 1)
r.sendline("0a")
r.recvuntil("GO!")
r.recvline()
print("GO!")
while True:
line = r.recvline().decode("utf-8")
if(line.startswith("This is the end of our journey")):
print("Done!")
r.kill()
break
seed = line.split("Seed: ")[1].split("\n")[0].replace(" ", "").upper()
if seed.startswith(stop):
print("Done!")
r.kill()
break
if line.startswith("!!!"):
csr = submit(s, seed, csr)
else:
print("Progress update: " + seed)
threading.Thread(target=run, args=("00", "21")).start()
threading.Thread(target=run, args=("20", "41")).start()
threading.Thread(target=run, args=("40", "61")).start()
threading.Thread(target=run, args=("60", "81")).start()
threading.Thread(target=run, args=("80", "a1")).start()
threading.Thread(target=run, args=("a0", "c1")).start()
threading.Thread(target=run, args=("c0", "e1")).start()
threading.Thread(target=run, args=("e0", "01")).start()
while True:
sleep(99)
Chall4-Z3solver
from z3 import *
solutions = set()
functions = {}
def checkPasswords(startingD, startingE):
s = Solver()
a, e, d = BitVecs('a e d', 8)
pw0, pw1, pw2, pw3 = BitVecs('pw0 pw1 pw2 pw3', 8)
pw = [pw0, pw1, pw2, pw3]
fin = [0, 0, 0, 0, 0]
print(f"Starting: D = {hex(startingD)} E = {hex(startingE)} Searching for: DA FA 82 B8")
d = startingD
e = startingE
a = 0
for i in range(4):
a = d
a = a ^ e ^ pw[i]
d = e
e = a
fin[i] = a
a = 0x3B
a = a + fin[0] & 0xff
a = a ^ fin[1] & 0xff
a = a - fin[2] & 0xff
a = a ^ fin[3] & 0xff
fin[4] = a
s.add(Or(pw1 == 2, pw1 == 3, pw1 == 6, pw1 == 7))
s.add(pw2 < 5)
s.add(pw2 >= 0)
s.add(Or(fin[4] == 0xDA, fin[4] == 0xFA))
s.add(And(fin[3] >= 0x86, fin[3] < 0xBB))
s.add(Or(fin[4] != 0x0))
i = 0
try :
while(s.check() == sat):
m = s.model()
data0 = m[pw0].as_long()
data1 = m[pw1].as_long()
data2 = m[pw2].as_long()
data3 = m[pw3].as_long()
datas = (data0, data1, data2, data3)
if(datas not in solutions):
finData = [0, 0, 0, 0, 0]
d = startingD
e = startingE
a = 0
for i in range(4):
a = d
a = a ^ e ^ datas[i]
d = e
e = a
finData[i] = a
a = 0x3B
a = a + finData[0] & 0xff
a = a ^ finData[1] & 0xff
a = a - finData[2] & 0xff
a = a ^ finData[3] & 0xff
finData[4] = a
func = (finData[1], finData[0])
if(func not in functions):
functions[func] = datas
s.add(Or(pw0 != data0, pw1 != data1, pw2 != data2, pw3 != data3))
solutions.add(datas)
else:
i += 1
if(i > 32):
break
except:
pass
checkPasswords(0x62, 0x00)
checkPasswords(0xd4, 0x00)
checkPasswords(0x62, 0xd4)
data = 4316
with open("loadedROM.bin", "rb") as binary_file:
data = binary_file.read()
import collections
for key, value in collections.OrderedDict(sorted(functions.items())).items():
addr = key[0] << 8 | key[1]
b = 0
i = 0
while i < 8:
if(data[addr] == 0x31 or data[addr] == 0x33 or data[addr] == 0x3B or data[addr] == 0xE8 or data[addr] == 0xF9):
b = data[addr]
break
addr += 1
i += 1
if(b != 0):
print(f"Function: {hex(key[0])[2:]} {hex(key[1])[2:]}\t\tInput: {hex(value[0])} {hex(value[1])} {hex(value[2])} {hex(value[3])}\t\tB: {hex(b)}")
pass
chall4-log_inventory.lua
Made by ISSOtm!
memory.usememorydomain("System Bus")
wNbItems = 0xDAE4
wItems = 0xDAE5
function get_item_name(id)
names = {
"potion",
"super potion",
"hyper potion",
"reviver seed",
"life seed",
"hardened scale",
"skip sandwich",
"summoning salt",
"telltale orb",
"glitch shard",
"shyhorn",
"daredevil potion",
"bravery potion",
"dark crystal",
"amber crystal",
"fluffy crystal",
"sharp beak",
"sharp horn"
}
if id > #names then
return string.format("item $%X", id)
else
return names[id]
end
end
function printInventoryMain()
console.clear()
console.log(string.format("%u items", memory.readbyte(wNbItems)))
ptr = wItems
while true do
item = memory.readbyte(ptr)
if item == 0xFF then
break
end
ptr = ptr + 1
console.log(string.format("%s (%u)", get_item_name(item), memory.readbyte(ptr)))
ptr = ptr + 1
end
end
event.unregisterbyname("printInventoryMain")
event.onmemoryexecute(printInventoryMain, 0x2BE5, "printInventoryMain")
event.unregisterbyname("printInventoryMainRem")
event.onmemoryexecute(printInventoryMain, 0x2BCE, "printInventoryMainRem")
chall4-finalRNG.lua
startFrame = 299871
currentFrame = 0
runitemchange = false
function final_check_bkpt()
rng = memory.readbyte(0xDAC3)
gui.text(5,45,"DAC3: " .. bizstring.hex(rng))
if rng ~= 0x7a then
console.log(string.format("Nope. $%X, %u + %u", rng, startFrame, currentFrame))
runitemchange = true
else
console.log(string.format("YESS $%X, %u + %u", rng, startFrame, currentFrame))
end
end
event.unregisterbyname("final_check_bkpt")
event.onmemoryexecute(final_check_bkpt, 0xB7CF, "final_check_bkpt")
while true do
if (runitemchange) then
tastudio.submitinputchange(startFrame + currentFrame,"A",false)
tastudio.submitinputchange(startFrame + currentFrame + 1,"A",false)
currentFrame = currentFrame + 1
tastudio.submitinputchange(startFrame + currentFrame,"A",true)
tastudio.submitinputchange(startFrame + currentFrame + 1,"A",true)
tastudio.applyinputchanges()
tastudio.setplayback(startFrame + currentFrame - 5)
runitemchange = false
end
emu.frameadvance()
end
Fools2021 Writeup - Stranck
So here we goooo, I finally had some time to write all of this.
As always,
⚠️ My english is really bad. Sorry if I make you cringe af.
Oh btw, the space in my leaderboard’s name is a typo lmao
Full list of achievments/hacking challs description:
Normal achievements
Actually so far I’ve never played the game in the intended way. I collected all of the “normal gameplay achievement” by just completing the second hacking challenge
Second hacking challenge
“This achievement is unobtainable thru normal means. You know what to do!”
When I first looked to the hacking challs of fools2021 this immediately popped onto my eyes. That’s why I solved it first. I see you TheZZAZZGlitch, this is EXACTLY the same chall you did for fools2017, last achievement. (Little cool note here: fools2017 was the first hacking chall I’ve EVER played in my life. I remember waking up in the morning, watching the thezzazz’s video, trying the chall and being surprised that by just normal gameplay I was 7th. This pushed me playing also for the hacking achievements and it was really pleasant ending with a perfect score. I can pretty much say that’s thanks to fools2017 and thanks to TheZZAZZGlitch if my cybersec career started and if I now play in the most important CTF teams of Italy (mHACKeroni, TRX, TeamItaly @ ECSC))
COFF
Btw, I solved that chall exactly how I solved the one in fools2017 (however that year I was helped by NieDzeJkob).
I checked what data is included in form of achievements inside the final password. Pikachu’s max HP looked like something interesting. So I opened the cheat searcher on my bgb, filtered all values that had the current pikachu’s maxHP, done some battles till a level up, filtered again for the current maxHP, and after a while I got my address: DADF. I put a read breakpoint on that address, called the password generation and boom, I was in the middle of the function. I quickly looked around and noticed it was doing stuff with addresses between DAC0 and DAC4. Soooo, I put all of them to 0xff, continued, and got almost all of the normal achievement + the one for the 2nd hacking chall. I said almost, because I fcked up also the time and missed the “defeat the last boss in under one hour” one. So I checked the function better, noticed that DAC0 contained the time, and I changed everything again except that one. Gotem.
First hacking challenge
For the first hacking challenge we had to walk 524.272 steps in one direction. I know there are some people who really walked thru there, but I didn’t want to go full monke, at least this time.
The first thing I did was searching the pokemon red ram map online, to check how long is the variable containing our position. Comes out it’s only one byte long, so no way I could hack it to be on the exact spot.
While playing I noticed some stuttering, similar to what happens in the normal game when changing maps. I quickly realised that I was changing chunks and I thought that maybe my steps were somehow tracked in them. I walked through a chunk and counted 16 steps before a new chunk was loaded. Soooo, 524272/16 = 32.767. Hey, that’s 0x7fff, this could be easily stored somewhere in ram. Also 0x7fff is the biggest value we can get with an int16_t. Maybe we just need to walk up to chunk 0x7fff?
Soooo, I started searching for the chunk data in ram similar to what I did in chall2. I opened the cheat search in bgb, I positioned myself between two chunks, assumed that walking to the right whould have increased my chunk value, did the first search, walked to the right, filtered for all values that have been increased, walked to the left, filtered for all values that have been decreased, and so on. At the end I got really few values remaining, one of these being DAB4. So I’ve set this to ff7f, walked one chunk left and another one right and booom, got it.
“Congratulations, you reached the edge of the world.
Kind of. The world will loop back around. Glitchtopia isn’t flat.
That being said, here’s a reward for coming this far! ToTheEdgeAndBeyond”
After working on the chall4 however I realised that there’s a byte that controls which textbox we were seeing on screen. To get the flag from this chall you can just bruteforce this till you get the correct textbox.
I also tried just out of curiosity to use the TheZZAZZGlitch’s rbmemviewer to see if I could see the text into the memory, but I couldn’t get it to show the correct address ranges.
Btw I take my merits and I say that I was the first one completing both chall2 and chall1. It was cool staying on top of the leaderboard for a while, till my worst enemy of every fools so far came to me blasting into my face. Bruteforce on chall3.
Third hacking challenge
You know the crazy guy who found the world seed of the Minecraft’s main menu background? Well, here we had to do the same thing.
Who knows me, also knows how much I hate any chall that involves crypto and any math that goes something more than 1+1. I was really happy that this didn’t come out to be a crypto chall, but more a rev one.
After noticing that our seed in our menu was composed by just hex values, I tried searching it inside the ram and I found it at DAB8. So I started the game, put a breakpoint on these values, pressed continue on the start menu, and I was inside the function that was randomly generating it. I traced the execution till I reached 02:AE24. That was the starting function of the generation map algorithm. This time I decided to go full monkey mode. I quickly reversed everything just to check which values it was writing while generating the map (Comes out to be: 4 bytes after the seed, that’s “chunk seed”; 01:D800 that’s the map data; and currentChunkX + currentChunkY) and then I wrote a bruteforcer INSIDE the game that was just resetting these values, setting a new seed, calling the genMap function and then checking if it could find the correct tiles sequence inside the map data. I must admit that the first thing I thought to was making something like the Truecoin miner made by TheZZAZZGlitch (so writing the seed to a savestate, run bgb on it, then check the map tiles), but I thought that opening and closing bgb everytime would’ve given us a lot of overhead.
I found the various tile sequences by just trial and error, changing random bytes inside the map data till I got them properly aligned on screen. I actually wasn’t sure that the tiles would’ve always been in a fixed location in the map data, so I chose to just search the tile sequences without checking where they actually were.
Here’s my code that was doing all of that inside the gameboy.
I asked some cpu power to Parzival (my cpu’s 100% shit, I got an overclocked amd fx6300 and unironically, after doing a bunch of benchmarks, I can say it does worse both in single core and multicore than the cpu of a server I pay 5€/month) who I noticed was struggling too on this chall by giving him a savestate that was already running this code, and we started searching on different address ranges. We left them going for 2 days, buuuut nothing was found. Luckily I’m not so dumb. I made this bruteforcer exactly to let it going while I was properly reversing the code. Who knows that maybe it could’ve found the results before me. After I reversed everything and I still didn’t get any results from the ingame, I decided to rewrite the map gen algorithm in C, so I could let my -O3 go BRRRR.
So I did, and I have to really thank my friends Dav and Lorenzo Fiocco who have donate their AMD R7 5800x’s power to me.
Aaaaand it didn’t work either. Or, at least, I was having a fuckton of results, but none of them were valid. So many results that at some point I wrote a script to automatically submit them for me.
Here I really have to thank NieDzejkob that checked if my code was able to generate and then check the correct seed and guess what, it wasn’t. In particularly, my check function was dumb. Let’s see if you can spot the problem there.
Well, let’s say I’m searching for the sequence AABC, and in the map data we have the sequence AAABC. It checks the first As. Ok. It checks the second As. Ok. It then checks the B from our sequence with the third A from the map data. It fails. Ok. But here’s the problem. Instead of resetting back our index inside the map data to the second A, it was continuing to check from the B in the map data. So A (from our sequence) differs from B (from our map data), and rip, the function says we had no results, even if AABC is a subsequence of AAABC. The ASM code I wrote for the first bruteforcer also had this problem, that’s why it didn’t give me results.
Btw after fixing that bug I started getting even more results, so I had to cut down the area where I was searching the tiles to not DDos the event servers, hoping to find my sequence in there. After a while I got the correct seed:
6521E0A1. Here you can find my final cpp code and my autosubmitterFourth hacking challenge
You’ve heard of problems inside my bruteforcer, now get ready for
desperation, pain, stress, sadness, sense of being mediocre, inside my mind. I played all of this with some problems irl, prolly my grandma is about to take a selfie with Nadia Toffa (Sorry for the Italian black humor) [Editor’s note: For non Italian readers, Nadia Toffa is a dead Italian TV conductor], plus I postponed a fuckton of work I had to do that was pressing against me a lot. For my job as lighting designer I’m pretty used to be in stressed situations, but holy cow if all of this combined + this fourth chall drained me. I must admit that I really considered a lot of times to drop everything, but I have to thank many people that encouraged me. vcvtne2ps2bf16, issoTM, jfb1337, buurazu, radixan. It’s thanks to them if I managed to got this (Even if just one hour after the end of the event, but i’m happy anyway). Trying over and over again a lot of different paths, managing to get everything ready and then fail at the end just for being a few bytes off, really destroyed me a lot. I’m not gonna include every path I tried, because I don’t remember most of them anymoreBtw let’s start from the start. start start start start start
I don’t remember anymore why I contacted vcvtne2ps2bf16 on discord for the first time, but we decided to collaborate, especially after we discovered that we were both italian. No language barrier, yeeee
Btw we shared to each other the progress made till that moment, his datamining was really helpful for me.
He told me about the glitch item 0x2D, capable of executing code from DAB3. He also told me some ideas about how we could probably obtain it (Glitch pokémon battles, glitch biome, currentItemMenuPage > 9, etc). I knew about various bugs involving the old save file, but I had to make my TAS on the new one, so diffed them to get inspiration for an exploit. After a while digging in this direction, reversing various functions like the battle engine, the give item routine, the change chunk/biome routine, trying some race conditions, trying to go up in map fast enough to exit the matrix (Parzival achieved this, idk how and idk on which version of the savefile), I decided to throw up on this road and try something else.
I knew from the GCRI discord that there was a bug in the inventory: if you have exactly 40 items and you try to open the item menù the game crashes. I investigated into that, noticing that the game was searching for a terminator inside the code after the inventory, and placing a lot of terminators after the first FF it could find (That’s because every time you open the item menù, it fills the empty part of the inventory with terminators, to prevent the exploit we ended up using in our final strat) . This code is often used (it handles bank switching), so that’s why it was crashing. By doing this research I noticed that correctly up to 40 items, places the last terminator onto the music control byte, that than gets immediately overridden by the music itself. This opened our possibilities, because we can now collect items to change data (code) near inventory and hopefully get ace. After some research I found two bytes we could manipulate: DB3C and DB74. We could set DB3C to 0x63 by just obtaining a super potion (Check the ItemList), and we could set DB74 to any opcode between 0x11 and 0x63 by just obtaining reviver seeds.
Btw the DB74 opcode is a
ld de, F9D4and it’s at the start of the password hash function. So we only needed to call the password generation to execute our exploitNow, here I committed a very big error. Idk why, but I focused myself to think that the only thing we could do is to change DB74 to 0x31, making it a
ld sp, F9D4(with F9D4 being in the middle of the map data).So I started searching a lot in this direction. Using the map tiles I found in the chall3, and dumping some map data, I got this set of tiles I could combine and use as return addresses by stackflipping onto the map data.
(Also: idk why I noted what instructions they correspond to, we’re executing the map tiles as stack, not as code)
I found many possible solutions but guess what, idk why but I got a fuckton of extra tiles. Making all of them impossible.
Thanks to radixan I got the correct Tile List, making my job even more difficult, however I managed to get 4 different payloads:
With the inventory in our payload being this one:
Making a working code for the inventory was especially a pain, because we had a lot of constraints:
With this constraint I made the previous inventory payload. However, because I’ve never played the game so far, I didn’t know there are some items you can obtain only once, making the job even harder. I discovered this only way later tho, when I had already discarded the idea of executing the inventory.
I also made a list of functions that could jump on hl:
and a full 100% working plan:
However I didn’t think about one thing. Not all of the tiles will be present in the map data at the same time. Take for example the address db55. It needs the 0xdb and the 0x55 tile. The first one comes from the glitching biome, the second one from construction. No way I could get them aligned. Yeah, I still could’ve used payload4. But 1) good luck searching the 8 bytes aligned correctly, 2) it’s pointless since the value we’re putting in hl and then jumping to was coming still from the map tiles. We could have used it directly as return address lmao
At this point Buurazu was the only one that resolved this challenge. So I came to him
cryingasking for any tips. He made me realise thatI’m an idiotI literally had any other opcode to try as value for DB74.So I did, I divided the opcodes in three categories, based on how many params they were taking:
0 params -> the following instruction would have been a
call nc, 7AF9. We arrive to that with the carry flag already set, so based on the previous opcode we were changing, we could take this call or not. However when we execute the hash function, the current rom bank is 0x17, and there it’s filled with 00, till the start of VRAM. So yeah, we could fall back and execute VRAM, but I think I didn’t really want to mess with that. Btw, if we don’t take the call, well, nothing interesting happens beside having a corrupted password.2 params -> Nothing interesting, except
ld sp, d16that I’ve tried before by stack flipping over the tile map1 param -> the following instruction will be a
ld sp, hl. That’s pretty interesting since hl points to our hashed passwords and we have semicontrol of that. Also, with our instruction we could change some registers used by the hashing function (like DE) to D4, and get more plausible results. We can also change h or l to D4 and use something else than the unhashed password as input hash data and then stack values, or we can do ald (hl), D4, setting the first unhashed password byte to, you guessed it, 0xD4So, we have some possible end results and we want to know which values can generate that. Because I’m as bad at math as I’m bad at english [Editor’s note: FFS yes, I noticed]… Time to “appicciare” [Editor’s note: That would mean “to turn on”] z3! I don’t have the previous version of this script, so here’s directly the last version of my code. Little note: OFC the first version of my script was calculating the wrong results (I missed the a = 0x3B before the checksum), so I lost like two days working on bad data :)))
Btw one of the first things I tried was checking the results having DB74 =
ld h, d8orld l, d8. With the first one I would have hashed just a bunch of zeroes (Hashed password = OUT: 0x62 0x62 0x0 0x62 0x9d), so nope; with the second one I would have hashed part of the last encountered pokémon data (From DAD4, 1+wEncounterTextPtr). Check PokémonList. Btw these were the possible functions I could execute ropchaining on the hashed password made from each pokémon’s data:None of them were actually usefull ofc. So I started digging inside the unhashed password itself. The final math formula for each one of the first four hashed bytes is this one:
With pw1 and pw2 being really small numbers (pw1 >= 0 and <= 7, pw2 >= 0 and <= 4). This meant that f2 ALWAYS is a small number, and if we want to somehow execute our seed/chunkCoords (variables we can easily control), or the winFunction, that are placed at xxAA (DAB8, DAB4, B882) with AA > 7, we needed the stack disaligned in some way, so the return after the first call would have called f4f3 instead of f3f2 (f4 can be “any” value). So I started searching for any combination of f1f0 that would have executed a dec/inc sp in some way, or execute something else useful that would have returned to the overworld loop, give us items, change map, etc.
At that time the only way we knew to obtain a buffer overflow was collecting (40 - 12 * 99) items. This meant that we would have the byte of the password rapresenting the time (The password is formatted as
time|achievmentflags|bossesbeaten|rng byte) maxed out at 0xff and our pikachu’s would definitely have more of 100hp, so the relative achievement was set (and achievement flags respecting the constraintOr(pw1 == 2, pw1 == 3, pw1 == 6, pw1 == 7). However in the first place I was using the “wrong” constraintpw1 >= 2, obtaining unhashed passwords that would have other achievements set but not necessarily the pikachuMAXhpOver100. I noticed this error only after I got my first working password).It’s there that my mood started to get worse. I’ve found many many many potential passwords that were using completely different paths. From doing stuff with the real start menù, to buying glitch items from a glitchy pokémarket, to executing link trades, to saving the game, to restarting the game with ace, to changing maps to something useful, jumping back to the overworld loop, to executing yet again the inventory and much much more stuff. In my notes I can see I digged into pokémon and item name ptrs, encounter rates, inventory pages, pikachu stats… I was able to do stuff with all of them, but not reaching something useful with them. I always was one/few byte(s) off what I needed. This was a struggle.
I was really lost, but then someone finally tunred on the lights. On the GCRI someone reminded that this video actually exists. Thanks to this video
we allI realised that we don’t need 40 stacks to overflow our inventory. We just needed one full stack of the item we want to overflow (in our case, rev seed, to change DB74). This opened my possibilities a lot, because now we’ve lost all of the constraints on our password, and I can freely search for any password. However my mood was getting even worse, because even if I could execute a lot of stuff in memory (all addreses like XX00-f + many other I can’t remember) without constraints, I couldn’t really control that or I didn’t find that useful. Moreover, I had to exclude theld (hl), d8andld e, d8from my tests, because they were returning an address into vram with the first two bytes of the hashed passwords. That halved my total possibilities.However, after a while of searching for different values, I finally got a result:
Function: 2b2f Input: 0xfb 0x4 0x2 0x36 Empty menù that returns on pw3 (DAC3)You have no idea how much I prayed about having this f1f0 but with f4f3 pointing to something useful. I finally got some luck and I noticed that with the unhashed password
0xfb 0x4 0x3 0xaewe would return exactly on B882 (our winning function). Perfect.I noticed only after a while that this password was needing to NOT have our pikachu’s maxHP > 100. At that time I had no idea how easy it was getting rev seeds from items, and I thought the only way would have been collect them thru pokémon battles, leveling up my pikachu a lot. That’s why I started searching for other passwords, by adding the constraint to having the maxHpOver100 achievement set.
I was again lucky, and I found this one
Function: 33 e5 Input: 0x53 0x2 0x4 0x5a B: 0x33 #boom i think, that was returning on the chunk’s hashed seed. Something I could control, but I would have to probably travel a lot to reach the correct chunk (Implying the correct chunk is actually reachable). This also needed some bruteforce to find a good seed/chunk combination.So I searched a little bit better for hashes starting with the same two bytes (e5, 33) and I got a really similar one:
0x53 0x2 0x4 0x7a. The only difference is the rng byte, but this one executes code from DA9F. We could easily fallback thru the textbox buffer, the chunks and reach the seed. Comes out that for the textbox buffer you’re good if the last one you’ve read is the one of the pokéball-overworld-pick, that you have to pay attention to not do anyld (bc), xbecause it would deactivate the SRAM making B882 inaccesible, to pay attention to be in a chunk with good coords as opcodes, to pay attention to being in the correct biome and having the correct flags in DAB1 and DAB2 (they control the input inside the menus).The only big constraint about this password, is that we only had 83 minutes to do everything (including fighting 4 bosses and breed our pikachu to reach 100maxHP) and I still didn’t realise I could get the rev seeds thru items. So I tried searching if there was any way I could have used to manipulate rng to always get rev seeds from battles. If you check the PokémonList you can easily see that in corruption we have 5/8 chances of encountering a pokémon that will drop a rev seed, and up to 0C/FF chances of having the drop. Waaaay too low. Also, the first gen pokémon rng is shit and hard af to predict.
Little write up on my small research on manipulating the RNG
The value you get from the RNG is based
(on what? xdlmaorotfl)on the combination between the previous status AND the approximate number of cpu cycles that have passed between rng calls. There’s an rng call every frame, and asynched from that (the difference in time between these two calls is based on how much cpu cycles we need before the next VBlank interrupt) there’s ofc an rng call each time you do some actions in game (like walking in grass). Doing actions ingame that would require a different amount of cpu clocks, changes the future rng values. This implies also playing notes in our musing, redrawing the player to look in a different direction, and so on. Also the point in cpu cycles where a rng call is made inside the game’s overworldLoop changes the future rng values (Because it means that more/less time has passed from the next/previous rng call). The battles are fully automated and during them our inputs are disabled, so no way we could manipulate the seed inside the battle. However I noticed that if we reset our DIV register (The one who contains the number of cpu cycles between rng calls) and FFD3/FFD4 (two bytes containing the result of the rng call/the previous status for the next rng call) at the first rng call made by vblank (An interrupt called each frame where we can draw stuff on screen), when the game does the rng call to check if it should drop an item or not at the end of the battle the DIV register contains more or less the same value (with an error of ±1. We have an error so small because the battles are automated and they require the same time), however FFD3 and FFD4 contains an hella different values. My plan of doing the battle, check the diff of the DIV register/FFD3/FFD4 between start and end, calc good starting DIV/FFD3/FFD4 values, and do action till we have them, is now ruined :)I still think that rng manipulation/prediction is possible tho, it just has too much math for me.
Btw this was the point when with issotm (That had been helping me for a while, huge shoutouts to him) I realised that we could get the rev seeds by pokéballs. If you look at the ItemDropTable you can see we have 2/8 chances of getting a rev seed drop in construction and steppes. Waaaay better. I also asked around and it looks like that with a route planned and lua scripts it was easy and fast (both irl and in game) to collect them.
So I finally had a route. And I also had a backup route
0x53 0x2 0x4 0x7a)0xfb 0x4 0x3 0xae)I ended up mixing both routes till they differ, so if one route fails for some reason, I could’ve just used the other one without redoing all of the TAS from 0. This was my final route:
Here’s our item list:
ld e, d8)I then asked issoTM (who, I’m gonna say again, helped me A LOT in the final parts of this chall) if he could work on the tas of this chall during the weekend, because he has much more experience than me
and I had some clients pushing over me to get their job done. However he had some problems so there I was, waking up at 6:30am on the last day of chall with 3 hours of sleep, ready to speedrun that tas. Oh, I’ve never made a tas before this one. It was “”“hella fun”"" discovering weird stuff while being so under pressure. IssoTM passed me the work he made so far (it was at the point of collecting two shy horns for the bosses), and some cool lua script to print the inventory/which items we’re gonna get from a drop, etc. I was having struggles with writing a script that would constantly retry obtaining a rev seed from the pokéball, so I wrote to a bunch of people and Buurazu answered my call. He helped me a lot understanding all of the lua functions inside the bizhawk docs and passed me directly his script to obtain rev seeds. “That’s not a lua script competition”. So much love, man.I also asked to Radixan his map viewer, that was so much helpful in building my path between pokéballs for the rev seeds.
Collecting all of the rev seeds required only 0x27 minutes. That surprised me, so that’s why at the end I went for the first route. My irl time was also tight, I couldn’t wait so many minutes, even with speed increased (reminder that my cpu is shit).
I reached the part where I had to just wait and get the correct rng value for the final byte of the unhashed password, exactly one hour before the end of the event. I managed to write a lua script from scratch that was bruteforcing the frames till it got the correct rng in like 20 mins. I had to go thru the fact that bizhawk doesn’t like a framejump in the tas while being paused midframe due to a breakpoint. This consumed quite some time, and I solved it thanks to Buurazu suggestion to use a while true loop and advance frame calls. This is my script.
It looks like that luck abandoned me again.
Do you believe me if I say that, into the full minute-window where I could generate the password (So in between 60 * 30 = 1800 frames), I didn’t have one single frame that was bringing me to having a 0x7a as the rng value for the last byte of the password?
Well, trying all of the possible frames took one hour, even in ultraomegaiperspeed mode, I was already out of the fools2021’s dedadline. So I decided to revert my tas back to the final breed, do it again from scratch, wait for the correct time, and relaunched my rng bruteforcer. It found a good frame in like 5 minutes this time, at the 4th second of the 83rd minute.
I did it
I won
(Is THiS a bRBa rEFeReNcE)Just one hour late :(
I was still happy tho, morally I had a perfect score this time too. Plus I finally had the results of the work of a lot of days. A little bit of satisfaction to push up my moral. I don’t care if I was one hour late, and I don’t care if my achievement is not in the leaderboards. I won anyway. And I have to thank again a lot of people for that.
Btw, just out of curiosity I loaded and analyzed the logs of the first run of my rng bruteforcer with this script:
These are the results. As you can see there actually were some rng bytes that never occurred. With my luck OFC the byte I needed was in them. Prolly by redoing the battles I scrambled my rng in some way and now I managed to obtain the 0x7a from it.
Btw here’s my final TAS download: https://stranck.ovh/writeups/data/fools2021/fools2021.tasproj
And this is a record of it: https://youtu.be/oMQw5mZv8X0
This is instead the list of all the passwords I found and I tried: https://stranck.ovh/writeups/data/fools2021/hashedPasswords.txt (You can find really interesting stuff in this list, go check it out ;) )
I sadly don’t have a screenshot of my path onto the radixan map viewer, because I made it crash by zooming out too much to take the screenshot (…)
Conclusion
Phew, I didn’t expect this writeup to be so long [Editor’s Note: Neither did I].
I would like to thank anyone on the GCRI discord that always makes an effort to put an even more “community vibe” over these events. I would like to thank anyone who helped me in any way during all of these challenges. And I would like to thank TheZZAZZGlitch A LOT for still making these cool events after so much time.
I love all of this. These events are unironically something I wait for with a lot of enthusiasm every year. Thank you everybody. And thank you, reader, for having reached this point.
See you next year guys!
PS: sorry again for my shitty english [Editor’s note: I might have fixed some of that]
Oh, almost forgot! These are some notes I made for the third/fourth challs: https://stranck.ovh/writeups/data/fools2021/notes.html
Thanks to Dav for reviewing everything and fixing my shitty english!
Extras
Sym-file
DAB1-DAB2
Thanks ISSOtm for that!
ItemDropTable
(Biomes order: grasslands, steppes, construction, corruption)
PokémonList
Thanks vcvtne2ps2bf16 for them!
Pokémon table starting at 03:A480
Format: | PicID | Encounter text | Name | Exp | HP | Atk | Drop rate | Item drop |
Spawnrate for each biome (Biomes order: grasslands, steppes, construction, corruption): (by me)
TileList
Thanks Radixan for these!
TREES = 0x0F TREES_UP = 0x6C TREES_DOWN = 0x6F TREES_LEFT = 0x6E TREES_RIGHT = 0x6D GRASS = 0x0A GRASSIER = 0x7A GRASSIEST = 0x7B MEGA_GRASS = 0x31 # idk xD TALL_GRASS = 0x0B FLOWER = 0x74 POKE_UP = 0x33 POKE_RIGHT = 0x32 POKE_LEFT = 0x60 POKE_DOWN = 0x34 POKE_GRASS = 0x08 ROAD = 0x55 BARRIER = 0x13 BARRIER_UP = 0x4E BARRIER_DOWN = 0x4D BARRIER_LEFT = 0x51 BARRIER_RIGHT = 0x52 GLITCH_C9 = 0xC9 GLITCH_CA = 0xCA GLITCH_CB = 0xCB GLITCH_D9 = 0xD9 GLITCH_DB = 0xDB GLITCH_EC = 0xECItemList
Thanks vcvtne2ps2bf16 for them!
Chall3-ASMbruteforcer
Chall3-bruteforcer
Chall3-autosubmitter
I used pwntools because I had no idea how to read/write from/to stdin/out in python.
I’m a dirty java developer after all, if I don’t have my StdinoutCustomFactoryBuilderSingleton i can’t do anything
Chall4-Z3solver
chall4-log_inventory.lua
Made by ISSOtm!
chall4-finalRNG.lua