Een Nintendo 64-game in 84 dagen decompileren

Dit was overduidelijk geen prestatie van één persoon. Ik ben in het bijzonder dankbaar aan inspectredc, Bl00D4NGEL en queueRAM voor hun aanzienlijke bijdragen aan het project. Geen enkele hoeveelheid AI had hen kunnen vervangen.² Ook wil ik iFuzzle, JamesBLewis en douglasjv bedanken voor het ter beschikking stellen van hun tokens voor dit doel.

Mijn hoop is dat een volledige decompilatie nuttig zal blijken voor de Snowboard Kids-community. Vooral speedrunners hebben zich lang gericht op het eerste deel. Werkende broncode kan licht werpen op extern geobserveerde verschijnselen, zoals CPU-paden (pathing) en de exacte factoren die bijdragen aan de snelheid van de speler. Een volledig begrip van de broncode zal ook nuttig zijn voor statische recompilatie en ambitieuzere modding-inspanningen in de toekomst.

Ook de snelheid waarmee het project is voltooid is opmerkelijk. Het decompileren van Snowboard Kids duurde slechts 84 dagen, vergeleken met 596 dagen voor Snowboard Kids 2; dat is ongeveer een zevende van de tijd.³

Wat was er anders?

Om het voor de hand liggende te benoemen: ik begon niet bij nul. Op dit punt had ik al bijna twee jaar gewerkt aan een soortgelijk project en was ik veel sneller dan toen ik begon. Dit voordeel is moeilijk te kwantificeren en werd enigszins tenietgedaan door nieuwe uitdagingen, zoals het werken met een andere compiler.

Over het algemeen was bij ongeveer 4,8% van de commits die een match opleverden, expertinterventie nodig.⁴ Ik heb deze geweldige mensen al eens genoemd, maar het is de moeite waard om dit te herhalen: dit project zou niet mogelijk zijn geweest zonder aanzienlijke hulp van de decompilatie-community, in het bijzonder inspectredc, queueRAM en Bl00D4NGEL <3.

De moeilijkheid zat hem over het algemeen niet in het begrijpen van wat een functie deed⁵, maar eerder in hoe die logica werd uitgedrukt in C en hoe de resulterende code werd gecompileerd. Snowboard Kids werd gecompileerd met IDO 5.3, in plaats van de GCC 2.7.2-compiler die werd gebruikt voor Snowboard Kids 2.

De meeste programmeurs zijn bekend met GCC. Het is een veelgebruikte open-source compiler die vandaag de dag nog steeds actief wordt ontwikkeld. IDO daarentegen was een propriëtaire compiler ontwikkeld door SGI, wiens eigen verhaal nauw verweven is met dat van de Nintendo 64.⁶ De broncode hiervan was niet beschikbaar en de originele ontwikkelomgeving was gebonden aan verouderde SGI-hardware en -software. Om deze vandaag de dag te gebruiken en correct te begrijpen, heeft de decompilatie-community delen van de compiler-toolchain moeten reverse-engineeren en decompileren, en de IDO 5.3- en 7.1-suites statisch moeten recompileren om ze op moderne hardware te kunnen draaien. Die gesloten geschiedenis maakt IDO moeilijker te analyseren dan een open compiler zoals GCC.

IDO splitst optimalisatie en codegeneratie over verschillende passes, waarbij de code onderweg vrij agressief wordt getransformeerd.⁷ Kleine wijzigingen in de C-code kunnen vervolgens door die passes rimpelen en een volledig andere registerallocatie produceren.

De community heeft grote stappen gezet in het begrijpen van IDO en de eigenaardigheden ervan, maar dit blijft meer een kunst dan een wetenschap. LLM's en ik zijn niet bijzonder goed in het reproduceren van de output hiervan. De gebruikelijke workflow, voor zowel mijzelf als de agents, was om uit te zoeken wat een functie deed en vervolgens C-code te schrijven die dat doel benaderde. Vanaf dat punt konden kleine aanpassingen, ondersteund door de permuteerder, de resterende verschillen overbruggen. Maar je kunt jezelf niet in alle gevallen naar een match permuteeren, vooral niet wanneer de onderliggende structuur onjuist is. Het gedrag van IDO maakte deze workflow veel minder voorspelbaar.

Een gemotiveerd menselijk team met de juiste expertise en intuïtie kan het tempo van de Snowboard Kids-decompilatie evenaren of zelfs overtreffen. De decompilatie van Pilotwings 64 werd voltooid in slechts 74 dagen.⁸ Pilotwings 64 had 16% minder functies dan Snowboard Kids, maar in totaal meer gecompileerde code, dus dit is ook geen zuivere vergelijking.

Snowboard Kids was bovendien kleiner dan het vervolg, met 2.145 functies tegenover 2.995 in Snowboard Kids 2. Het aantal functies is een grove maatstaf voor moeilijkheid, maar er waren simpelweg minder functies om te decompileren.

Waar AI-agents hielpen

Ik heb elders al geschreven over het gebruik van agents om functies te decompileren. In dit project is hetzelfde basisproces gebruikt, dus ik zal me concentreren op wat er is veranderd. In tegenstelling tot het vorige project begon deze keer met toegang tot frontier-modellen en een capabel agent-harnas.

Bibliotheekcode en 'low-hanging fruit'

Ik was benieuwd hoe agents zouden presteren tijdens de beginfase van een project. Eén gebied waar ze uitblonken, was het matchen van standaard-bibliotheekcode. In theorie is dit het meest voor de hand liggende deel van bijna elke Nintendo 64-decompilatie. De code is niet uniek voor het spel en versies ervan zijn online beschikbaar. Snowboard Kids bevat meer dan honderd bronsegmenten van Nintendo's libultra, naast functies uit de libmus audiobibliotheek. Tools zoals N64Sym kunnen waarschijnlijke bibliotheekfuncties in de ROM identificeren.

Deze fase was redelijk succesvol. Het grootste struikelblok was om agents te overtuigen om te vertrouwen op de bestaande bibliotheekbronnen in plaats van dezelfde functies opnieuw vanaf nul te decompileren. Dit vereiste sterkere prompting. Zodra een waarschijnlijke bibliotheekfunctie was geïdentificeerd, kregen agents de instructie om de bijbehorende bron als startpunt te gebruiken en alle plausibele SDK-versies, compiler-opties en conditionele compilatiepaden uit te putten voordat ze hun eigen implementatie probeerden.

Een andere optimalisatie was om een agent een script te laten schrijven dat m2c uitvoerde op elke niet-gematchte functie en eventuele exacte matches automatisch integreerde, in plaats van te vertrouwen op agents die die functies individueel probeerden.⁹ Het script matchte slechts 17 van de 1.830 functies (een ongelooflijk laag succespercentage van 0,93%), maar alles wat op deze manier werd gematcht, was goedkoper dan het verbruiken van agent-tokens.

IDO-tooling en vaardigheden

IDO is vreemd, maar vaak op een terugkerende manier. Het succesvol matchen van één functie kon een compiler-eigenaardigheid onthullen die op veel andere van toepassing was. Codex is beter geworden in het overdragen van lessen tussen taken via functies zoals lokale geheugens. Om deze lessen beschikbaar te maken buiten één enkele agent, instrueerde ik agents om geobserveerd IDO-gedrag vast te leggen in een DECOMPILATION_LEARNINGS.md-bestand. Wanneer een agent een generaliseerbare compiler-eigenaardigheid ontdekte, kon hij het bewijs daar vastleggen voor latere pogingen. Dit creëerde een nuttige feedbackloop: agents hielpen IDO te documenteren, en de resulterende documentatie maakte volgende agents beter in het matchen van IDO-code.

Maar de meest nuttige bron was de N64 Decomp Workbench, een verzameling tooling en documentatie voor het debuggen van MIPS-decompilatie-mismatches in de laatste fase. Het kan mismatches classificeren, rekening houden met relocaties, individuele compiler-passes herhalen en helpen onderscheid te maken tussen een structureel probleem en een registerallocatie-probleem. Pass-replay vereist de relevante compiler-binaries en projectspecifieke configuratie, maar zodra dit is ingesteld, legt het informatie bloot die een ruwe assembly-diff niet kan geven. Een ruwe diff vertelt je dat twee functies verschillen; de Workbench kan agents een veel beter idee geven van waarom de functies verschillen en welk type wijziging ze zou kunnen oplossen.

Worktrees en synchronisatie

Voor dit project draaide ik het decompilatie-harnas over vier Git-worktrees. Elke worktree gaf een agent een onafhankelijke kopie van de repository, waardoor meerdere functies parallel konden worden aangepakt.

Een kleine maar nuttige verbetering was om elke taak een expliciete deadline te geven en die deadline aan de agent kenbaar te maken. Tijdens het Snowboard Kids 2-project hadden agents vaak moeite om de permuteerder effectief te gebruiken, omdat deze bleef draaien tot er een 100% match was gevonden of tot hij handmatig werd gestopt. Een expliciete deadline stelde agents in staat om redelijke timeouts in te stellen en permuteertijd af te wegen tegen andere vormen van probleemoplossing. Anekdotisch gezien hielp het hen ook in te schatten hoe lang ze aan een moeilijke functie moesten blijven werken voordat ze het opgaven. Ik kon vervolgens de tijdstoelage verhogen naarmate de makkelijke functies verdwenen en het resterende werk moeilijker werd.

Een ander probleem dat al bestond bij Snowboard Kids 2, maar duidelijker werd toen ik worktrees toevoegde, was synchronisatie. Zoals besproken in mijn vorige bericht, kreeg elke agent een functie om te decompileren samen met een set vergelijkbare functies die al waren gematcht. Deze boden nuttige referentiepunten voor het reproduceren van specifieke IDO-instructiepatronen. Het werk werd verdeeld over de worktrees met behulp van de --shard optie van Nigel, die basis-hashing gebruikt om kandidaten te partitioneren over een gespecificeerd aantal workers.

Dit maakte het mogelijk om meer werk parallel uit te voeren zonder duplicatie, maar introduceerde een nieuw probleem naarmate de worktrees uitegingen. Eén worktree kon bijvoorbeeld succesvol een functie decompileren die voor 99% leek op een functie die elders werd geprobeerd, maar die nieuwe referentie bleef onzichtbaar voor de andere agent tot de wijzigingen waren samengevoegd. Periodiek alles samenvoegen in de main-branch en de worktrees opnieuw synchroniseren loste dit op, maar het synchroniseren van alle vier kon meer dan een uur duren. Continu synchroniseren verspilde tijd, terwijl te lang wachten leidde tot meer drift.

Om synchronisatie minder kritiek te maken, heb ik de similarity search bijgewerkt om elke worktree te inspecteren. Een nieuw gematchte functie kon onmiddellijk een referentie worden voor een andere agent zonder te wachten tot deze de main-branch bereikte. Het harnas produceerde dan berichten zoals:

1✓ Candidate ["func80094A94", "func80094FF4 (../sbk-c)", 2 "func80094808", "func8009491C (../sbk-a)", 3 "func8009469C (../sbk-c)", "func80093144"] was fixed!

Dit hielp het proces efficiënt te houden naarmate de makkelijke functies verdwenen. Synchronisatie was nog steeds nodig om wijzigingen te consolideren en te pushen, maar het was niet langer vereist voor agents om van elkaar te leren.

Keuze van modellen

Ik heb GPT-5.5 en 5.6, Claude 4.5 en Fable, en GLM 5.2 geprobeerd. Dit is volledig onwetenschappelijk. De modellen werden getest tegen een veranderende set moeilijke functies, soms nadat een ander model al gedeeltelijke voortgang had geboekt. Over het algemeen bleef Codex echter beter presteren dan Claude, zoals aan het einde van het vorige project al het geval was. Sol xhigh was bijzonder effectief zodra dit beschikbaar kwam.

GLM 5.2, aangeboden door z.ai, was erg teleurstellend. Voorheen was ik een groot fan van GLM. Het was effectief, zelfs als het geen frontier-model was, en de gulle gebruikslimieten compenseerden het verschil. Die afweging werd veel minder aantrekkelijk naarmate de limieten minder gulle werden terwijl de latentie verschrikkelijk bleef. De feedbackcycli waren zo lang dat ik stopte met het toewijzen van werk en uiteindelijk mijn abonnement opzegde.

Wat nu?

De onmiddellijke prioriteit is om het spel beter te documenteren. Een 100% match betekent dat we C-code hebben voor elke functie; het betekent niet dat we begrijpen wat elke functie doet. Er zijn nog gegenereerde namen die vervangen moeten worden, onbekende structureervelden die geïdentificeerd moeten worden, onhandige matches die opgeschoond moeten worden en grote hoeveelheden data die beschreven moeten worden.

Er wordt ook gewerkt aan een recompilatie van Snowboard Kids. Gelukkig deelt het eerste deel veel eigenaardigheden die zijn aangepakt door patches in Snowboard Kids 2: Recompiled.

Ik onderzoek of ik de levels en andere content van het eerste deel kan porteren naar de engine van het tweede deel, hoewel ik nog geen idee heb hoeveel werk dat zal zijn.

Daarnaast ben ik geïnteresseerd in het decompileren van Snowboard Kids Plus op de PlayStation, een Japan-exclusieve uitgebreide release van het eerste deel met extra levels en personages.

---

Voetnoten

¹ Een kleine hoeveelheid handgeschreven assembly uit systeembibliotheken blijft over, omdat assembly de originele bron was in plaats van een tijdelijke decompilatie-placeholder.

² In het beste geval denk ik dat het project rond de 89–90% zou zijn gestagneerd zonder hun IDO-expertise.

³ Voor Snowboard Kids 2 mat ik van de eerste commit op 28 september 2024 tot de laatste matchende functie op 17 mei 2026, een periode van 596 dagen. Het cijfer van 84 dagen voor Snowboard Kids is mijn telling op projectniveau; de publieke repository zelf loopt van de eerste commit op 28 mei 2026 tot de laatste matchende functie op 19 augustus 2026, net iets meer dan 83 dagen.

⁴ Op basis van een handmatige review van de Git-geschiedenis identificeerde ik ongeveer 41 matches met hulp van experts, wat overeenkomt met ongeveer 4,8% van de commits die ik classificeerde als functie-matching werk. Deze retrospectieve classificatie is benaderend omdat commits meerdere functies kunnen bevatten, hulp vaak werd opgenomen in mijn eigen commits, en het cijfer geen manuren meet. Let op: ik tel mezelf niet mee als "expert".

⁵ Een opmerkelijke uitzondering was validateControllerPakSave, die blijkbaar werd gebruikt om Controller Pak-saves te debuggen. Het grootste deel van de debug- en validatielogica werd weggelaten uit de definitieve build.

⁶ Nintendo heeft beschreven een architectuur van Silicon Graphics te hebben overgenomen voor de Nintendo 64. Het originele ontwikkelsysteem was op een vergelijkbare manier SGI-centrisch. De ontwikkeldocumentatie van Nintendo beschrijft het verbinden van de emulatorboard met een SGI Indy-workstation, en IDO was een van de ondersteunde compilers. De relatie strekte zich dus uit van de onderliggende architectuur van de console tot de machines en tools die werden gebruikt om de games te ontwikkelen.

⁷ IDO gebruikte een compilatiepijplijn met meerdere fasen. De uopt-documentatie zegt dat geschikte lussen standaard vier keer werden uitgerold (unrolled). Toevallig is vier ook het aantal racers in het spel. Lussen met vier iteraties zijn daarom kandidaten voor volledige uitrolling, en ze komen frequent voor 🫠.

⁸ Gemeten vanaf de toevoeging van voortgangstracking op 26 januari 2026 tot de laatste matchende functies op 10 april 2026, duurde de decompilatie bijna precies 74 dagen. Pilotwings 64 had 1.791 functies vergeleken met 2.145 voor Snowboard Kids, maar ongeveer 838 KB aan code tegenover 732 KB voor Snowboard Kids.

⁹ Het gebruik van een one-shot script zoals dit is geen bijzonder nieuw idee. Ik ben er vrij zeker van dat ik het ergens heb gekopieerd, mogelijk uit de m2c_all.sh van Diddy Kong Racing.