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 {
					//puts("vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv");
					printf("!!! FOUND A POSSIBLE ");
					printSeed();
					//hexDump();
					//puts("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^");
				}
			}
		} 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:

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

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:

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] #print(data) n = int(data, 16) #print(n) 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!

Extras

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 # shydon A5 E3 BC 2B BF 03 07 03 07 01 # rattata 24 E3 BC 33 BF 04 09 04 07 01 # pidgey 7C 23 BD 3A BF 02 08 01 0C 06 # metapod 7F E3 BC 42 BF 06 0A 06 0C 04 # glitchling 1A 3D BD 4D BF 0A 0B 04 00 00 # scyther 42 23 BD 55 BF 04 07 07 0C 05 # dragonite 33 6F BD 5F BF 05 05 09 07 04 # Magmar 7A 9F BD 66 BF 08 0A 02 E5 0A # TMZ4 5F D2 BD 6B BF 0E 0A 05 FF 0E # ????? 53 00 BE 71 BF 10 0A 06 FF 0F # Ninetales 28 2E BE 7B BF 12 0B 06 FF 10 # Chansey 83 5D BE 83 BF 19 0B 08 00 00 # Mewtwo

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 # 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 = 0xEC

ItemList

Thanks vcvtne2ps2bf16 for them!

b917 # potion b917 # super potion b917 # hyper potion b9b1 # reviver seed b967 # life seed b967 # hardened scale b9c6 # skip sandwich b9de # summoning salt bb12 # telltale orb b9b1 # glitch shard b9b1 # shyhorn bc13 # daredevil potion bc40 # bravery potion bbfb # dark crystal bbfb # amber crystal bbfb # fluffy crystal bc6d # sharp beak bc6d # sharp horn 12 a621 # start of glitch items

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; /*uint8_t a, f, b, c, d, e, h, l; uint16_t getAF(){ uint16_t reg = a << 8 | f; return reg; } uint16_t getBC(){ uint16_t reg = b << 8 | c; return reg; } uint16_t getDE(){ uint16_t reg = d << 8 | e; return reg; } uint16_t getHL(){ uint16_t reg = h << 8 | l; return reg; }*/ 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; //idk, without this vs code is not coloring the line below #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; //used to choose case inside genMapInternal_2 uint8_t _ff96; uint8_t _ff97; //byte we're writing uint8_t _ff98; //Patterns 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)); } //uint8_t chunkData[4]; 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++; //Yeah, that's correct } 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; //printf("%02x\n", a); seedBuffer[1] = a; //printf("%02x\n", seedBuffer[1]); 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; //printf("%02x %02x %02x %02x\n", seedBuffer[0], seedBuffer[1], seedBuffer[2], seedBuffer[3]); //exit(0); 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; //printf("%02x %02x %02x %02x\n", seedBuffer[0], seedBuffer[1], seedBuffer[2], seedBuffer[3]); for(int c = 0; c < 0x10; c++){ randomScramble(); } } void chooseRandomByte(uint16_t chunkDE, uint16_t chunkHL){ //uint16_t backupChunkDE = chunkDE, backupChunkHL = chunkHL; //printf("DE: 0x%04x HL: 0x%04x\n", chunkDE, chunkHL); uint8_t b = chunkDE & 0x3, c = chunkHL & 0x3; //printf("B: 0x%02x C: 0x%02x\n", b, c); randomFromSeed(chunkDE >> 8, chunkDE & 0xfc, chunkHL >> 8, chunkHL & 0xfc); c = DD0C_DATA[(c * 4 + b) + swap(randomScramble() & 0x7)]; //printf("C: %02x\n", c); _ff95 = randomScramble() & 0x30 | c; uint8_t a = (((chunkDE & 0xff) | (chunkHL & 0xff)) & 0xfc) | (chunkDE >> 8) | (chunkHL >> 8); if(!a) { _ff95 = c & 0x0f; } //printf("FF95: %02x\n", _ff95); } void writeHLfromFF97(uint8_t b){ //uint8_t asd = (((b & 0x0f) * 8) + 0x70) + (swap(b) & 0x0F); printf("B: %02x W: %02x\n", b, asd); //printf("ff97: %02x\n", _ff97); randBuffer1[(swap(b) & 0x0F) + ((b & 0x0f) * 8)] = _ff97; //uint8_t l = } void writeFF97andBlocksDontSaveE(uint8_t d, uint8_t e, uint8_t b, uint8_t c){ d = 0xF0; if((swap(c) & 0x0f) >= (swap(b) & 0x0f)) //a >= d d = 0x10; e = 0xFF; if((c & 0x0f) >= (b & 0x0f)) //a >= d e = 0x01; //printf("D: %02x E: %02x\n", d, e); do { writeHLfromFF97(b); if((swap(c) & 0x0f) != (swap(b) & 0x0f)) //a == d b += d; writeHLfromFF97(b); if((c & 0x0f) != (b & 0x0f)) //a == d b += e; writeHLfromFF97(b); } while (c != b); //hexDumpRand(randBuffer1); // exit(0); } 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){ //printf("a\n"); 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; //printf("%02x\n", d); exit(0); //h = b; //l = c; uint8_t bBak = d; uint8_t cBak = c; //printf("%02x %02x %02x %02x\n", d, e, b, d); //d e b c writeFF97andBlocks(d, e, b, d); //printf("%02x %02x %02x %02x\n", d, e, bBak, cBak); //d e b c writeFF97andBlocks(d, e, bBak, cBak); //exit(0); //uint16_t bc1 = (d << 8) | c; } 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(){ //buf hl //randBuffer2 de 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){ //d = 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){ //buf hl _ff97 = b; uint8_t *buf = randBuffer1_offset; uint16_t realAddress = RAND_BUFFER_BASE_OFFSET; //printf("*************************************************\n"); for(int e = 0; e < 0x30; e++){ //printf("1) %02x == %02x\n", buf[e], b); if(buf[e] == b){ uint16_t addrNibble = realAddress & 0x7; //printf("2) %x not 0 or 0x7\n", addrNibble); if(addrNibble == 0 || addrNibble == 0x7) goto doLoop; uint8_t a = randomScramble(); //printf("3) %02x >= %02x\n", a, chances); if(a >= chances){ //TODO are we sure about that? int editE = e - 0x8; //printf("4) [%d] %02x != %02x\n", editE, _ff98, buf[editE]); if(_ff98 != 0) if(_ff98 != buf[editE]) goto doLoop; editE += 0x10; //printf("5) [%d] %02x != %02x\n", editE, _ff99, buf[editE]); if(_ff99 != 0) if(_ff99 != buf[editE]) goto doLoop; editE -= 0x9; //printf("6) [%d] %02x != %02x\n", editE, _ff9a, buf[editE]); if(_ff9a != 0) if(_ff9a != buf[editE]) goto doLoop; editE += 0x2; //printf("7) [%d] %02x != %02x\n", editE, _ff9b, buf[editE]); if(_ff9b != 0) if(_ff9b != buf[editE]) goto doLoop; //printf("8) Setting %d to %02x (Was %02x)\n", e, c, buf[e]); buf[e] = c; } } doLoop: //printf("\t%02x, continue\n", ((0x30 - e) & 0xff)); //printf("\t%d, continue\n", e); realAddress++; } } void replaceRandBwithCOnly30Bno0and7(uint8_t b, uint8_t c, uint8_t chances){ //d = 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(){ //printf("a\n"); replaceRandBwithC(0x0A, 0x0b, 0X30); //hexDumpRand(randBuffer1); exit(0); _ff97 = 0x0B; expandSelectedTile(); //hexDumpRand(randBuffer1); exit(0); _ff98 = 0x0f; _ff99 = 0x0a; _ff9a = 0x0; _ff9b = 0x0; randomFillPLUSifPattern(0x0f, 0x6c, 0x20); //hexDumpRand(randBuffer1); exit(0); _ff98 = 0x0a; _ff99 = 0x0f; randomFillPLUSifPattern(0x0f, 0x6f, 0x20); //hexDumpRand(randBuffer1); exit(0); _ff98 = 0x0; _ff99 = 0x0; _ff9a = 0x0a; _ff9b = 0x0f; randomFillPLUSifPattern(0x0f, 0x6e, 0x20); //hexDumpRand(randBuffer1); exit(0); _ff9a = 0x0f; _ff9b = 0x0a; randomFillPLUSifPattern(0x0f, 0x6d, 0x20); //hexDumpRand(randBuffer1); exit(0); replaceRandBwithC(0x0A, 0x74, 0x30); //hexDumpRand(randBuffer1); exit(0); replaceRandBwithC(0x0A, 0x7a, 0x30); //hexDumpRand(randBuffer1); exit(0); replaceRandBwithCOnly30Bno0and7(0x6c, 0x33, 0x40); //hexDumpRand(randBuffer1); exit(0); replaceRandBwithCOnly30Bno0and7(0x6d, 0x32, 0x40); //hexDumpRand(randBuffer1); exit(0); replaceRandBwithCOnly30Bno0and7(0x6e, 0x60, 0x40); //hexDumpRand(randBuffer1); exit(0); replaceRandBwithCOnly30Bno0and7(0x6f, 0x34, 0x40); //hexDumpRand(randBuffer1); exit(0); } 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; //ACDC if((testByte >> 3) & 0x1) writeFF97andBlocksDontSaveE(d, e, e, 0x40); // d e b c if((testByte >> 2) & 0x1) writeFF97andBlocksDontSaveE(d, e, e, 0x47); // d e b c if((testByte >> 1) & 0x1) writeFF97andBlocksDontSaveE(d, e, e, 0x04); // d e b c if(testByte & 0x1) writeFF97andBlocksDontSaveE(d, e, e, 0x74); // d e b c 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); //Looks like this is never called... } 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; //not sure about that 5 *h = rotateLeft(*h, 5); //same here 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){ //ABAF getRandomDE(&d, &e); //stuff prolly useless } void genMapInternal_2(uint8_t *mapData, uint16_t chunkDE, uint16_t chunkHL){ //uint16_t backupChunkDE = chunkDE, backupChunkHL = 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; //printf("FF96 %02x\n", _ff96); randomFromSeed(chunkDE >> 8, chunkDE & 0xff, chunkHL >> 8, chunkHL & 0xff); //printf("%02x %02x %02x %02x\n", seedBuffer[0], seedBuffer[1], seedBuffer[2], seedBuffer[3]); uint8_t b = _ff96, d = _ff95, e = 0x0A; //printf("%02x %02x %02x %02x\n", d, e, b, 0x74); if(testByte & 0x1) writeRandomByte(d, e, b, 0x74); //d e b c 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); //exit(0); _ff97 = 0x0A; expandSelectedTile(); //hexDumpRand(randBuffer1); exit(0); if((testByte >> 3) & 0x1){ //printf("3\n"); writeHLfromFF97(0x30); writeHLfromFF97(0x40); //**** TODO **** check if the nz here comes from the previous call or from the previous bit instruction } if((testByte >> 2) & 0x1){ //printf("2\n"); writeHLfromFF97(0x37); writeHLfromFF97(0x47); //**** TODO **** check if the nz here comes from the previous call or from the previous bit instruction } if((testByte >> 1) & 0x1){ //printf("1\n"); writeHLfromFF97(0x03); writeHLfromFF97(0x04); //**** TODO **** check if the nz here comes from the previous call or from the previous bit instruction } if(testByte & 0x1){ //printf("0\n"); writeHLfromFF97(0x73); writeHLfromFF97(0x74); //**** TODO **** check if the nz here comes from the previous call or from the previous bit instruction } //hexDumpRand(randBuffer1); exit(0); e = (swap(d) & 0x3) * 2; //printf("E: 0x%02x D: 0x%02x\n", e, d); uint16_t jumpTo = JUMP_TABLE[e] | (JUMP_TABLE[e + 1] << 8); //printf("JT: 0x%04x\n", jumpTo); addRandomTiles(jumpTo); //exit(0); /*uint16_t hl = 0; for(d = 0; d != 0x8; d++){ for(e = 0; e != 0x8; e++){ uint8_t a = randBuffer1[hl]; if(a == 0x33 || a == 0x32 || a == 0x60 || a == 0x34 || a == 0x08){ genRandomItem(d, e, hl); } hl++; } }*/ } 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(); //readChunk(&chunkDE, &chunkHL); //printf("Final data AT 0x%016llx\n", finalData); genMapInternal_1(finalData + MAP_DATA_OFFSET0, chunkDE, chunkHL); //bc, de, hl _dab0 = _ff95; chunkDE--; genMapInternal_1(finalData + MAP_DATA_OFFSET1, chunkDE, chunkHL); //bc, de, hl chunkHL--; genMapInternal_1(finalData + MAP_DATA_OFFSET2, chunkDE, chunkHL); //bc, de, hl chunkDE++; genMapInternal_1(finalData + MAP_DATA_OFFSET3, chunkDE, chunkHL); //bc, de, hl chunkDE++; genMapInternal_1(finalData + MAP_DATA_OFFSET4, chunkDE, chunkHL); //bc, de, hl chunkHL++; genMapInternal_1(finalData + MAP_DATA_OFFSET5, chunkDE, chunkHL); //bc, de, hl chunkHL++; genMapInternal_1(finalData + MAP_DATA_OFFSET6, chunkDE, chunkHL); //bc, de, hl chunkDE--; genMapInternal_1(finalData + MAP_DATA_OFFSET7, chunkDE, chunkHL); //bc, de, hl chunkDE--; genMapInternal_1(finalData + MAP_DATA_OFFSET8, chunkDE, chunkHL); //bc, de, hl //hexDump(); return; writeChunk(chunkDE, chunkHL); finalData[NULL_BYTE_MAP_DATA] = 0; } /* BRUTEFORCE PART *imagine this as an ascii art, is 5:06am of 4/4/21 i dont really want to search for them* */ 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] = { //TEST ONE {0x74, 0x74, 0x74, 0x0a, 0x0f}, {0x0a, 0x0b, 0x0b, 0x0b, 0x0b}, {0x0f, 0x0f, 0x6d, 0x0a, 0x74}, {0x0f, 0x7a, 0x0a, 0x74, 0x0a}, {0x0f, 0x0f, 0x0a, 0x0f, 0x0f} };*/ uint8_t sequences[SEQUENCE_NO][SEQUENCE_DIMENSION] = { //CORRECT ONE {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; //printf("i: %d\n", (0xD890 - BASE_MAP_DATA_OFFSET)); //printf("i: %d\n", (0xD900 - 0xD890)); int start = (0xD890 - BASE_MAP_DATA_OFFSET); int end = 0xD900 - 0xD890 + start + 1; for(int i = start; i < end/*FINAL_DATA_SIZE*/; i++){ //printf("i: %d\n", i); uint8_t b = finalData[i]; if(b == currentSequence[indexInSequence]){ if(startMatchI < 0) startMatchI = i; indexInSequence++; //printf("indexInSequence: %d (I: %04x)\n", indexInSequence, (i + BASE_MAP_DATA_OFFSET)); if(indexInSequence >= SEQUENCE_DIMENSION){ indexInSequence = 0; currentSequenceCounter++; //printf("currentSequenceCounter: %d (I: %04x)\n", currentSequenceCounter, (i + BASE_MAP_DATA_OFFSET)); if(currentSequenceCounter < SEQUENCE_NO){ currentSequence = sequences[currentSequenceCounter]; } else { //puts("vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv"); printf("!!! FOUND A POSSIBLE "); printSeed(); //hexDump(); //puts("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^"); } } } 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(); //printf("--------\n"); seed[0] = 0xF5; seed[1] = 0x90; seed[2] = 0x2c; seed[3] = 0xc9; //genMap(); seed[0] = 0x89; seed[1] = 0x43; seed[2] = 0x67; seed[3] = 0x41; //genMap(); seed[0] = 0x18; seed[1] = 0x19; seed[2] = 0xa3; seed[3] = 0x71; //genMap();*/ //seed[0] = 0x01; //seed[1] = 0xaa; //seed[2] = 0x8e; //seed[3] = 0x61; 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] #monke mode ''' 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) #idk why it was not returning after receiving the :, do I had to add a timeout 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 #print("Got new line!") seed = line.split("Seed: ")[1].split("\n")[0].replace(" ", "").upper() #monkeMode pt2 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) #pwData start pw = [pw0, pw1, pw2, pw3] #f0, f1, f2, f3 = BitVecs('f0 f1 f2 f3', 8) #finalData end #fin = [f0, f1, f2, f3] fin = [0, 0, 0, 0, 0] #pw = [0x20, 0, 0, 0x22] #Default: 6200. Original instruction: F9D4. Could ld x, D4 #startingD = 0x62 #0xF9 #startingE = 0xd4 #0xD4 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 #s.add(fin[i] == a) fin[i] = a a = 0x3B #print(hex(a)) a = a + fin[0] & 0xff #print(hex(a)) a = a ^ fin[1] & 0xff #print(hex(a)) a = a - fin[2] & 0xff #print(hex(a)) a = a ^ fin[3] & 0xff #print(hex(a)) fin[4] = a #print(f"{hex(fin[0])} {hex(fin[1])} {hex(fin[2])} {hex(fin[3])} {hex(fin[4])}") #exit(0) #f0 = f9 ^ d4 ^ pw0 #f1 = f9 ^ pw0 ^ pw1 #f2 = pw1 ^ pw2 #f3 = dF9 ^ eD4 ^ pw0 ^ pw2 ^ pw3 #print(fin) #s.add(Or(pw0 == 0xFF, pw0 == 0xFE, pw0 == 0xFD, pw0 == 0xFC, pw0 == 0xFB, pw0 == 0xFA)) #s.add(pw0 == 0xff) s.add(Or(pw1 == 2, pw1 == 3, pw1 == 6, pw1 == 7)) #s.add(pw1 > 1) #s.add(pw1 < 8) s.add(pw2 < 5) #s.add(pw1 >= 0) s.add(pw2 >= 0) #s.add(And(pw0 == 0xBE, pw1 == 0x83, pw2 == 0xBF, pw3 == 0x19)) #s.add(And(pw0 == 0, pw1 == 0, pw2 == 0, pw3 == 0)) #s.add(Or(And(fin[0] == 0x2B, fin[1] == 0x28), And(fin[0] == 0x2B, fin[1] == 0x2c), And(fin[0] == 0x2B, fin[1] == 0x2d), And(fin[0] == 0x2B, fin[1] == 0x2e))) #s.add(And(fin[4] == 0xb8, fin[3] == 0x82)) #s.add(fin[0] == 0x82) #s.add(And(fin[1] == 0x33, fin[0] == 0xe5)) s.add(Or(fin[4] == 0xDA, fin[4] == 0xFA)) s.add(And(fin[3] >= 0x86, fin[3] < 0xBB)) #s.add(fin[2] >= 0xD0, fin[2] < 0xE0) #s.add(fin[3] == 0xDA) s.add(Or(fin[4] != 0x0)) #searchByte = 0xDA #o1 = Or(fin[0] == searchByte, fin[1] == searchByte, fin[2] == searchByte, fin[3] == searchByte, fin[4] == searchByte) #searchByte = 0xFA #o2 = Or(fin[0] == searchByte, fin[1] == searchByte, fin[2] == searchByte, fin[3] == searchByte, fin[4] == searchByte) #searchByte = 0xB8 #o3 = Or(fin[0] == searchByte, fin[1] == searchByte, fin[2] == searchByte, fin[3] == searchByte, fin[4] == searchByte) #searchByte = 0x82 #o4 = Or(fin[0] == searchByte, fin[1] == searchByte, fin[2] == searchByte, fin[3] == searchByte, fin[4] == searchByte) #s.add(Or(o1, o2, o3, o4)) #searchByte = 0xDA #s.add(Or(fin[0] == searchByte, fin[1] == searchByte, fin[2] == searchByte, fin[3] == searchByte, fin[4] == searchByte)) #s.add(pw0 >= 0) #s.add(pw3 >= 0) #s.add(fin[0] >= 0) #s.add(fin[1] >= 0) #s.add(fin[2] >= 0) #s.add(fin[3] >= 0) #print(fin) #print(s.check()) i = 0 try : while(s.check() == sat): m = s.model() data0 = m[pw0].as_long() #if pw0 in m else 999 data1 = m[pw1].as_long() #if pw1 in m else 999 data2 = m[pw2].as_long() #if pw2 in m else 999 data3 = m[pw3].as_long() #if pw3 in m else 999 datas = (data0, data1, data2, data3) if(datas not in solutions): finData = [0, 0, 0, 0, 0] d = startingD #this is shit but i don't remember how to use z3 properly lmao 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 #print(f"IN: {hex(data0)} {hex(data1)} {hex(data2)} {hex(data3)}\t\tOUT: {hex(finData[0])} {hex(finData[1])} {hex(finData[2])} {hex(finData[3])} {hex(finData[4])}") #print("") func = (finData[1], finData[0]) #func = finData[4] 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 #print(e) 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(): #value = functions[key] #print(f"Function: {hex(key)}\t\tInput: {hex(value[0])} {hex(value[1])} {hex(value[2])} {hex(value[3])}") #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])}") 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 --emu.framecount() 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)) --client.pause() runitemchange = true --tastudio.setrecording(false) --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.setplayback("End wait") --tastudio.applyinputchanges() --tastudio.setplayback(startFrame) --tastudio.setrecording(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.setplayback("End wait") tastudio.applyinputchanges() tastudio.setplayback(startFrame + currentFrame - 5) --client.unpause() runitemchange = false end emu.frameadvance() end