Link’s Awakening disassembly progress report – part 11

This article is part of an ongoing “Disassembling Link’s Awakening” series, where I attempt to gain some understanding on how special effects were implemented in this game.

✨ New contributors

First let’s congrats the following new contributors, who made their first commit to the project during the past months:

🔀 Source-code shiftability

Efforts to make the disassembled source code shiftable have been ongoing for half a year now. And this time, there’s a great news:

Zelda: Link’s Awakening source code is now shiftable! 🥳

What does that mean? Let’s borrow an analogy from Revo, of the sm64decomp project:

You have a piece of graph paper on a table, except there’s a problem: nails are nailed through certain coordinates to the table. Try to move the paper and it tears it up.

Shiftability means pulling each nail and writing down the coordinate it was pointing to.

Binary modders are just really good at working with the nailed paper, regardless of the nails, by cutting and pasting available pieces and drawing on the empty spaces and stitching stuff (and in some cases just tapes on an extra piece of paper which is used as a dumping ground).

For more details about shiftability, see the previous disassembly progress report.

Getting the source code shiftable means that we reached the point where all nails are removed. Which means it is now much easier to add new code, or change what the current code is doing–without breaking the game. For instance, it could make it easier to create a level editor, a full-conversion mod, a gender-swapped version of the game, or a randomizer.

Building tools to help pointers resolution

This was a months-long effort, that required scanning 150,000 lines of code for hardcoded pointers. To help this effort, some of it was automated.

ℹ️ This section dives deeper into the technical details of pointers resolution. If you prefer a higher-level view, jump directly to the next section.

Most pointers can’t be reliably identified using purely automated ways: a series of bytes like db $01, $4E, $87, $4F could be the definition of two pointers ($4E01 and $4F87), but might as well be a display list, or any data block.

However, load instructions (such as ld hl, $65E2) almost always refer to a data pointer in the current bank. This is not 100% foolproof: sometimes the loading instruction may be data that was wrongly interpreted as code, or it may be loading a pointer to be used later in another bank. But mostly, this is a good guess.

Using these guesses, a script scanning the source code for these loading instructions was written. Using the current state of the source code, and the symbols already identified, the script can output a list of missing data pointers for the current bank – including the length of the data block.

The way the script work is:

  1. Read all the symbols already generated;
  2. Read the given source file (e.g. bank_1F.asm);
  3. Find all the ASM loading instructions in the source file that refer to a raw pointer (e.g. ld hl, $65E2);
  4. Convert the raw pointers to data symbols in the current bank (e.g. $652EData_01F_652E);
  5. Guess the size of the data blocks, by assuming the blocks runs up to the next symbol defined in the bank;
  6. Emit a new set of debug symbols, augmented with the newly extracted pointers;
  7. Run the disassembler again, feeding it the augmented symbols.

Once the disassembler is run again, it produces an updated version of the source file–but with the data blocks properly labeled, and with those labels correctly referenced the loading instructions.

This new source file must then be manually merged with the original source file, by cherry-picking the data-label changes, while keeping the annotated comments from the original files.

In the end, this script helped to resolve around 50% of the raw data pointers. The rest of it (jump tables, pointers tables, etc.) had to be labeled manually.

Ensuring shiftability

If some hardcoded pointers remain in a code bank, adding or removing code from those banks won’t shift those pointers. This could result in subtle bugs in the compiled game.

To ensure that a given code bank is shiftable, a simple check is to insert some nop instructions at the beginning of the bank (thus shifting the entire code by some amount), and play the game to see if anything breaks.

This method is simple–but running all code paths to ensure that everything works smoothly is difficult and time-consuming. Moreover, once a bug is found, it can be tedious to identify the precise location of the faulty pointer.

@marijnvdwerf found a better way: he used other versions of the game. Three main different versions of Link’s Awakening DX were released: v1.0, v1.1 and v1.2 (and this doesn’t count smaller changes localized versions in English, French and German). While attempting to add support for other versions, Marijn found many small non-matching data blocks. Indeed, these data blocks contained raw pointers, that weren’t properly shifted when compiling another version of the game.

Luckily, unmatched data blocks are easy to pinpoint–for instance using an hex diffing tool. The pointers still had to be fixed manually, but at least they were precisely identified.

Caveats

Although the code is shiftable, some of the graphics data may not be moved around freely: it requires splitting large graphics sheets into several smaller pieces, which is not entirely done yet.

But shiftability is definitely a huge milestone, and should make the life of moders easier.

🎵 Music disassembling

Around March, @Drenn1 started to have a look at the format of music tracks. Rather than documenting the code, he was attempting to understand the meaning of the music data.1

This led him to write an impressive Python script, that can read the music track binary data, and dump them in a human-readable form.

Long-story short, a music track is defined by:

  1. A default transposition factor (usually 0);
  2. A default speed;
  3. Up to 4 channels tracks.

The 4 channels tracks are controlling the 4 hardware audio channels of the Game Boy: two square wave (that produces MIDI-like music), one programmable waveform (for playing custom sounds), and one programmable noise generator (for playing noise-based SFX).

And each channel track is actually a program. Channels tracks are a sequence of opcodes, that can either:

For instance, let’s have a look at this channel track:

ChannelDefinition_1b_50ab::
    set_envelope_duty $a0, $84, 2, 0
    notelen 4
    note A#3
    notelen 2
    rest
    note A#3
    note A#3
    notelen 1
    note A#3
    note A#3

    begin_loop $02
        notelen 6
        note A#3
        notelen 1
        note G#3
        notelen 3
        note A#3
        notelen 2
        rest
        note A#3
        note A#3
        notelen 1
        note A#3
        note A#3
    next_loop

    ; snip…

This channel track will produce the first 4 seconds of the main channel of the Title Screen theme, which you can hear below:

Some opcodes are still not fully understood. Nonetheless, this is an incredible work, that allow us to both understand how the sound engine works, and how it can be modded to add new music tracks.

Transposition and trivia

Interestingly, only a handful of music tracks actually use a non-zero “default transpose factor”:

Except for the mini-boss, it seems that at some point during development the game designers decided to make musics playing in several houses higher-pitched. We can only guess why; but maybe that was to give a slightly more upbeat tone.

You can listen to the differences in MUSIC_HOUSE below:

HOUSE_MUSIC – Final pitch as in the released game.

HOUSE_MUSIC – Originally programmed pitch.

📦 Pre-composed save game

Did you know that Link’s Awakening includes a pre-composed saved game, that gives you right from the start all inventory items, dungeons keys, special items, and so on?

It was included as a developers feature, and disabled in the released game. But with a bit of tweaking, it’s easy to re-enable it. As Xkeeper and the fine people of The Cutting Room Floor documented more than ten years ago, enabling the Debug tools of the game will automatically write this pre-composed saved game to the first slot.

So Xkeeper dug into the code, and found how this feature works: under some conditions, a segment of data is simply copied to the save slot.

Before they documented this block of data, it looked like this:

label_4667::
    db 4, 1, 2, 3, 5, 6, 7, 8, 9, $A, $B, $C, 1, 1, 1, 0
    db 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2
    db 1, 1, 1, 1, 3, 1, 1, 1, 1, 4, 1, 1, 1, 1, 5, 1
    db 1, 1, 1, 6, 1, 1, 1, 1, 7, 1, 1, 1, 1, 8, 1, 1
    db 1, 1, 9

And after their work, and some bits of clever ASCII-art, the data for the pre-composed saved game now look like this:

DebugSaveFileData::
    db INVENTORY_SHIELD          ; B button
    db INVENTORY_SWORD           ; A button
    db INVENTORY_BOMBS           ; Inventory slots
    db INVENTORY_POWER_BRACELET  ; .
    db INVENTORY_BOW             ; .
    db INVENTORY_HOOKSHOT        ; .
    db INVENTORY_MAGIC_ROD       ; .
    db INVENTORY_PEGASUS_BOOTS   ; .
    db INVENTORY_OCARINA         ; .
    db INVENTORY_ROCS_FEATHER    ; .
    db INVENTORY_SHOVEL          ; .
    db INVENTORY_MAGIC_POWDER    ; .

    db 1  ; Have Flippers
    db 1  ; Have Medicine
    db 1  ; Trading item = Yoshi doll
    db 0  ; 0 Secret Seashells
    db 0  ; (@TODO "Medicine count: found?")
    db 1  ; Have Tail Key
    db 1  ; Have Angler Key
    db 1  ; Have Face Key
    db 1  ; Have Bird Key
    db 0  ; 0 Golden Leaves / no Slime Key

    ; Dungeon flags ...
    ;  +-------------- Map
    ;  |  +----------- Compass
    ;  |  |  +-------- Owl Beak / Stone Tablet
    ;  |  |  |  +----- Nightmare Key
    ;  |  |  |  |  +-- Small keys
    ;  |  |  |  |  |
    db 1, 1, 1, 1, 1 ; Tail Cave
    db 1, 1, 1, 1, 2 ; Bottle Grotto
    db 1, 1, 1, 1, 3 ; Key Cavern
    db 1, 1, 1, 1, 4 ; Angler's Tunnel
    db 1, 1, 1, 1, 5 ; Catfish's Maw
    db 1, 1, 1, 1, 6 ; Face Shrine
    db 1, 1, 1, 1, 7 ; Eagle's Tower
    db 1, 1, 1, 1, 8 ; Turtle Rock
    db 1, 1, 1, 1, 9 ; POI: unused? (9th dungeon?)

Way easier to understand.

As you can see, two details are a little curious:

🗺 Minimaps format

Although the Overworld map is accessible all the time by pressing the SELECT button, dungeon minimaps are displayed in the inventory.

Inventory displaying the Tail Cave minimap
The minimap of Tail Cave, the first dungeon.

To display those minimaps, the game stores one array of bytes per map. The 0xEF value stands for a simple room, 0xED for a room with a chest, and 0xEE for the dungeon Nightmare room.

In the disassembled code source, these maps were previously formatted as a simple array of values:

Minimap0::
    db   $7D, $7D, $7D, $7D, $7D, $7D, $7D, $7D
    db   $7D, $7D, $7D, $7D, $7D, $7D, $7D, $7D
    db   $7D, $7D, $7D, $7D, $7D, $7D, $EF, $7D
    db   $7D, $EF, $EF, $EF, $7D, $7D, $EE, $7D
    db   $ED, $7D, $EF, $ED, $EF, $ED, $EF, $7D
    db   $EF, $EF, $ED, $ED, $EF, $EF, $EF, $7D
    db   $EF, $7D, $EF, $ED, $ED, $7D, $7D, $7D
    db   $7D, $ED, $EF, $EF, $7D, $7D, $7D, $7D

Minimap1::
    db   $7D, $7D, $7D, $7D, $7D, $7D, $7D, $7D
    db   $7D, $ED, $ED, $ED, $EF, $EF, $EF, $7D
    db   $7D, $7D, $ED, $7D, $7D, $ED, $7D, $7D
    db   $7D, $EF, $EF, $7D, $7D, $EF, $EE, $7D
    db   $7D, $EF, $7D, $7D, $7D, $7D, $EF, $7D
    db   $7D, $ED, $7D, $7D, $7D, $7D, $EF, $7D
    db   $7D, $EF, $EF, $EF, $EF, $EF, $EF, $7D
    db   $7D, $7D, $ED, $ED, $ED, $ED, $7D, $7D

Xkeeper found a clever way to make this data more readable: using rgbasm charmaps.

In the source code, a CHARMAP command tells the assembler how to convert ASCII characters to sequences of bytes. This allow for instance to map the text of dialogs to the indices of the tile to use for each letter. Xkeeper found these charmaps another use: by defining a custom charmap, it becomes possible to format the dungeon minimap data as text and symbols.

NEWCHARMAP MinimapCharmap
CHARMAP "  ", $7D   ; Blank (not shown on map)
CHARMAP "##", $EF   ; Room (shows up on map)
CHARMAP "Ch", $ED   ; Room with chest
CHARMAP "Nm", $EE   ; Nightmare boss marker

At compile-time, the charmap gets the text converted to the expected bytes. Which means that the dungeon minimap data now looks like this:

    ;    0 1 2 3 4 5 6 7  - Minimap arrow positions.
Minimap0::
    db "                "
    db "                "
    db "            ##  "
    db "  ######    Nm  "
    db "Ch  ##Ch##Ch##  "
    db "####ChCh######  "
    db "##  ##ChCh      "
    db "  Ch####        "

Minimap1::
    db "                "
    db "  ChChCh######  "
    db "    Ch    Ch    "
    db "  ####    ##Nm  "
    db "  ##        ##  "
    db "  Ch        ##  "
    db "  ############  "
    db "    ChChChCh    "

Much more readable, and easier to edit.

Plus we can now clearly see the map of the first dungeon (Tail Cave) being shaped like a Mini-Moldorm, and the second dungeon (Bottle Grotto) being shaped like the jar of the boss.

What’s next?

With source code shiftability achieved, the next point of focus is graphics data. For now the graphics of the game are not so easy to edit: many of them are laid out in a complicated way, using baroque color palettes. Some issues have been opened: hopefully we’ll find a way to convert all graphics to easily editable sprite sheets, that can be transposed to the format expected by the engine at compile time.

Marijn is also close to merge an impressive PR that allows to build every single revision and language of the game.

And of course, the documentation of the physics engine and entities behavior is still an ongoing work.

Want to read more? Discover more of the code, or join the discussion on Discord.


Notes

  1. You may already know Drenn, because years ago he gave a new start to this project. He dived into all the numerous errors that had crept in the then-partial disassembled source code, and fixed them all. Moreover, he added a checksum step, to ensure the code would never diverge from the compiled game again.

    Drenn is also working on a fairly complete disassembly of Zelda Oracle of Ages/Oracle of Seasons–including a level editor for these games. Check it out! 

Certaines personnes

Ce texte est une traduction de « Some people », de Jason Kottke.

Certaines personnes se sentent angoissées et impuissantes.

Certaines personnes s’ennuient.

Certaines personnes sont isolées en confinement et se sentent seules.

Certaines personnes se rendent comptent que l’Après sera très différent de l’Avant.

Certaines personnes profitent du temps supplémentaire avec leurs enfants et quand ça sera fini, ce temps leur manquera.

Certaines personnes viennent de sortir de leur douzième créneau d’affilé à l’hôpital et ne peuvent pas embrasser leur famille.

Certaines personnes ont mangé à leur restaurant préféré pour la dernière fois et ne le savent pas encore.

Certaines personnes sont mortes du coronavirus.

Certaines personnes ne peuvent pas s’empêcher de lire les nouvelles.

Certaines personnes n’ont pas les moyens d’acheter du savon.

Certaines personnes apprennent à faire du pain.

Certaines personnes travaillent de chez elles tout en essayant de faire l’école à la maison.

Certaines personnes sont des parents seuls qui essayent de travailler de chez eux tout en essayant de faire l’école à la maison.

Certaines personnes ont du mal à boucler les fins de mois et la prochaine paye ne viendra pas.

Certaines personnes sont inaptes à la fonction de président.

Certaines personnes ont quitté la ville pour leurs résidences secondaires.

Certaines personnes ne peuvent pas faire les courses parce qu’elles sont des personnes à risque.

Certaines personnes ont perdu leur travail.

Certaines personnes n’arrivent pas à dormir.

Certaines personnes regardent gratuitement des opéras en ligne.

Certaines personnes sont en quarantaine depuis plusieurs semaines.

Certaines personnes ne peuvent pas télé-travailler.

Certaines personnes ont attrapé le coronavirus et ne le savent pas encore.

Certaines personnes sont trop angoissées pour se concentrer sur leur travail.

Certaines personnes n’ont pas les moyens de payer leur loyer du mois prochain.

Certaines personnes continuent à se réunir en grands groupes.

Certaines personnes prennent de vrais risques pour sauver nos vies.

Certaines personnes n’ont pas acheté assez de solution hydro-alcoolique.

Certaines personnes ont acheté trop de solution hydro-alcoolique.

Certaines personnes n’ont plus accès à leur thérapeute.

Certaines personnes ne peuvent pas aller travailler mais sont toujours payées par leur employeur. Pour l’instant.

Pour certaines personnes, le principal souci est de décider ce qu’elles vont regarder ensuite sur Netflix.

Certaines personnes se portent volontaires.

Certaines personnes ont une entreprise qui va faire faillite.

Certaines personnes se rendent comptent que les enseignants sont formidables.

Certaines personnes commandent à emporter aux restaurants du coin.

Certaines personnes voudraient vraiment juste un câlin.

Certaines personnes n’arrivent pas à convaincre leurs parents âgés de prendre tout ça au sérieux.

Certaines personnes s’inquiètent de leurs investissements en bourse pour leurs vieux jours.

Et certaines personnes n’ont jamais eu d’investissements.

Certaines personnes vont être confrontées à plus de violences domestiques.

Certaines personnes vont tomber malade ou se blesser et auront plus de mal à accéder à des soins médicaux.

Certaines personnes ne peuvent pas acheter la nourriture dont elles ont besoin parce que les produits acceptés par les aides sociales alimentaires ne sont plus en rayon.

Certaines personnes ne veulent pas arrêter de faire la fête.

Certaines personnes ont perdu leur solution de garde d’enfant.

Certaines personnes font tout ce qu’elles peuvent pour rester calmes et garder l’espoir et ça ne marche pas.

Certaines personnes regardent Contagion et jouent à Pandémie.

Certaines personnes ne savent pas ce qu’elles vont faire.

Certaines personnes sont surchargées de conseils sur comment travailler depuis chez soi.

Certaines personnes mangent ou boivent trop.

Certaines personnes pensent à l’après.

Certaines personnes sont contrariées de ne pas pouvoir voyager.

Certaines personnes sont en manque de sexe.

Certaines personnes prévoient de jardiner plus cette année.

Certaines personnes ne vont pas voir leur famille pendant des mois.

Certaines personnes se déconnectent pour garder la tête froide.

Certaines personnes n’arrivent pas à voir le bout du tunnel.

Certaines personnes vont se rendre compte qu’il faut qu’elles rompent avec leur conjoint.

Certaines personnes chantent Imagine all the people.

Certaines personnes ne sont pas dans cette liste.

Ces expériences sont toutes celles de vraies personnes, tirées de journaux, des réseaux sociaux, et d’amis. Courage : vous n’êtes pas la seule personne à passer par ce que vous vivez en ce moment. Mais soyez attentionné : tout le monde ne passe pas par la même chose que vous. Même si en dernier lieu, tous nous sommes affectés par ces mêmes évènements.

Jason Kottke

Link’s Awakening disassembly progress report – part 10

This article is part of an ongoing “Disassembling Link’s Awakening” series, where I attempt to gain some understanding on how special effects were implemented in this game.

Entities respawn mechanism

In a previous progress report, I wondered how Link’s Awakening respawn mechanism works.

Specifically, when destroying enemy entities on a specific area, moving out and back into this area doesn’t reload all the entities. Only the surviving enemies (if any) are visible. For a moment, at least. After a while, the enemies seems to respawn again.

A portion of gameplay showing how entities don’t respawn when leaving a room
Entities don’t respawn when returning to a previously visited area.

Eventually I found the piece of code responsible for this behavior. Turns out the implementation is cleverly simple.

It relies on two separate mechanisms: a recents rooms list, and flags depending on the entity load order.

Clearing rooms

The first mechanism used is a 16x16 array, wEntitiesClearedRooms. It contains one byte per area (or “room”). When all the enemy entities in a room are destroyed, this is recorded into this array.

When this room is visited later again, the game checks whether the room has been cleared or not before loading the entities. Simple enough.

But how do entities respawn again after a while? Well, because the wEntitiesClearedRooms also has companion variable: wRecentRooms.

wRecentRooms stores the six most recently visited rooms. At its core, it’s a simplified implementation of an LRU cache:

Clean and tight. But importantly, this means that new rooms will start overwriting older ones.

How does this relate to entities respawning again? Well, the single and unique purpose of wRecentRooms is actually not to store a list of recently visited rooms, but to detect when a room is evicted from this list. When a recent room is overwritten by a newer one, the byte corresponding to the evicted room in wEntitiesClearedRooms is reset to zero. Which means the entities of this room will start spawning again when the room is visited.

Entity flag

The game has actually a finer granularity than that. It’s not about whether then entire room is cleared or not: even destroying a single entity in a room will cause it not to respawn the next time (even if other entities in the room still do spawn). How does that work?

A portion of gameplay showing how only destroyed entities don’t respawn when leaving a room
Only destroyed entities don’t respawn. The others are still loaded when visiting the room again.

Turns out that the wEntitiesClearedRooms array doesn’t only tell if a room has been cleared or not, but also which entities have been destroyed in that room.

For this, entities are identified by their load order. Each entity has an index indicating in which order it was loaded into the room. So when an entity is destroyed, the game takes the entity load order, turns it into a bitmask, and stores it into wEntitiesClearedRooms.

A diagram of how the entities load order is encoded

Next time this room will be visited, when each entity is loaded, the game uses the load order to check if the entity has already been destroyed – and skip it if so.

Statistics

Knowing where we are and how much progress we made is instructing and motivating. For this purpose, similar to some of the pret projects, the LADX disassembly now has a script that can output various statistics about the overall completion state of the project.

Here is an example output:

$ tools/stats.sh
Number of remaining raw addresses:
   Referencing Home (0000-3FFF):
       0
   Referencing non-Home ROM banks (4000-7FFF):
    2551
   Referencing RAM (8000-FFFF):
    6478

Number of unlabeled functions:
    1033

Number of unlabeled jumps:
    7706

This should help to:

In the future, the script may present percentages instead of raw numbers; something like “Unlabeled functions: 1033 (34%)”.

Shiftable bank

Speaking of shiftable banks, work has begun towards making the disassembly shiftable!

But what’s a shiftable disassembly? When starting a disassembling project, the first step is often to run an automatic disassembler on the whole ROM binary. This automatic disassembler can only decode instructions, and add auto-generated-labels to the most obvious locations. But the output is quite limited: it will have data interpreted as code, no meaningful labels – and, crucially, many memory addresses will be left unresolved.

  ; Loading data from an unresolved `$4206` address.
  ld   hl, $4206
  ld   a, [hl]

What’s the problem with that? Well, if we start tweaking the original code (for instance to add a new feature that wasn’t present in the original game), the new code will slightly push the old code around. But places in the code using unresolved addresses won’t be updated, and will still point to the former location. This will lead to data-corruption and crashes very quickly.

How to avoid these corruptions? Either by:

In shiftable code, all unresolved raw addresses have been resolved to proper labels. Because of that, even if the data location is pushed around by new code, the code referencing this data location will also change – and the game will still work.

  ; Loading data from a labeled address.
  ld   hl, Data_004_4206
  ld   a, [hl]

Now, resolving data addresses in the whole reconstructed source code isn’t easy. There’s a reason disassemblers can’t do it automatically: the banks system.

When the disassembler sees, for instance, a pointer being created with the address $4206, it can’t know if this address means:

So cross-referencing these addresses has to be made manually. An human must understand what the code is actually trying to do, and replace the raw address with a label at the right location. And it takes time.

But already, as a first milestone, the main bank (bank 0) is now shiftable! That means new code can be added or removed from this bank, without breaking the game. As the bank 0 is always mapped into memory, this is already quite useful to insert some hooks for new features.

And meanwhile, the work to make the other banks shiftable continues. About half of it is now done, but it involves quite a bit of repetitive work (although some of it has been automated).

Want to give it some help, and contribute to make Link’s Awakening easier to mod than ever? Drop on the Discord channel!

Want to read more? Discover more of the code, or join the discussion on Discord.

Link’s Awakening disassembly progress report – part 9

This article is part of an ongoing “Disassembling Link’s Awakening” series, where I attempt to gain some understanding on how special effects were implemented in this game.

New contributors

Since the last report, the following people made first-time contributions to the disassembling project:

Super Game Boy frame

First, let’s expand a little on @marijnvdwerf’s improvements to the Super Game Boy code.

The Super Game Boy (a device that allowed to play Game Boy cartridges on a Super NES) has many features games could use – including custom color palettes, custom audio, or multiplayer support. But Zelda: Link’s Awakening included support only for the more prominent SGB feature: a custom border surrounding the display when playing the game.

Zelda Link’s Awakening being played on the Super Game Boy, including the SGB custom frame.

Sending the frame

Like all SNES or Game Boy graphics, the custom frame is made of tiles. Practically, this requires tiles data describing the tile graphics, a tilemap describing how the tiles are laid out, and some color palettes.

Now, as the Super GameBoy was created after the release of the original Game Boy, the Game Boy doesn’t know about this device. Specifically, it doesn’t have a specific communication channel with external hardware.

So, how can a Game Boy communicate with another device that was released as an afterthought? Well, the method eventually chosen was to abuse an existing hardware port. To communicate with a Super GameBoy, a game must write to the joypad registers. Of course, the joypad registers are usually read-only (a game can’t press a hardware button by itself). But when running on a Super GameBoy, the joypad hardware registers become also writable, and are used to send data to the device.

Using this communication method, uploading a custom frame involves a series of steps to communicate with the Super GameBoy:

  1. Send a MLT_REQ command to switch to 2-players mode (this will confirm the game is running on a Super GameBoy);
  2. Send a MASK_EN command to make the displayed screen black while the game will be messing with VRAM;
  3. Copy the tiles data for the custom frame from the ROM to the Game Boy VRAM;
  4. Send a CHR_TRN command to upload the VRAM content to the Super GameBoy;
  5. Upload the tilemap and palettes for the custom frame from the ROM to the Game Boy VRAM;
  6. Send a PCT_TRN command to upload the VRAM content to the Super GameBoy;
  7. Send a MASK_EN command to finally make the displayed screen visible again.

Timing the transfers

Each of these transfers takes some time. The usual way to wait for the transfers to be completed would be to sync on the v-blank intervals, occurring every 16,6 ms. But during these operations, the screen is off, so no v-blank occurs.

Instead, the code uses carefully crafted busy-loops to execute a specific number of instructions. Once all the instructions are executed, the game knows the right amount of time must have passed, and the transfer should be complete.

Extra background

While documenting the Super GameBoy code, data and commands, @marijnvdwerf found that the tilemap for the custom frame includes a bit of content that is never visible in-game.

Zelda Link’s Awakening Super GameBoy frame as coded in the ROM, with extra content visible
Only the content inside the red square is visible during normal gameplay; the rest of the frame is clipped.

Why this extra image section was left in the game is still to be discovered.

Using the Zelda III disassembly to fill out entities data

Game using an entity system tend to store entity attributes in a not-so-straightforward way. Instead of declaring an array of entities, game often use several arrays of attributes, indexed by entity.

// An array of entity structs would be the idiomatic way
// to declare entities in C.
struct entity = { int x, int y, int health /*, … */ };
struct entity entities[MAX_ENTITIES];

// But in games, entities are more often stored
// as arrays of entity attributes.
int entitiesX[MAX_ENTITIES];
int entitiesX[MAX_ENTITIES];
int entitiesHealth[MAX_ENTITIES];

When writing actual assembly code, it’s easy to see why.

To access an attribute of a single entity using the “one single array” variant, we need to perform one multiplication and two additions:

// Get the health of entity 5, using the "one single array" variant.
int entityIndex = 5;
int health = *(entities + sizeof(struct entity) * entityIndex + 3);

Whereas with the “several arrays of attributes” variant, accessing an attribute is only one single addition:

// Get the health of entity 5, using the "several arrays of attributes" variant.
int entityIndex = 5;
int health = *(entitiesHealth + entityIndex);

Link’s Awakening has about 35 of these entity attribute arrays. And the purpose and behavior of these attributes is often difficult to figure out.

After spending some time trying to make sense of some of the less-often used attributes, I eventually took another approach.

As the history records, Link’s Awakening was started right after the release “Zelda: A Link to the Past” release, by Nintendo employees working after-hours. Using a spare Game Boy development kit, they were trying to see if an ambitious Zelda game, similar to the SNES version, could run on the much-less-powerful Game Boy.

Given than the programmer team was partially made of the same programmers than “A Link to the Past”, could it be that they re-used some of the code structures? This was worth checking out.

Turns out that a fairly complete disassembly of Zelda SNES does actually exist. And indeed, some of the entities data structures look similar! This provided some hints to some of the less-used entities tables.

For instance one of these, wEntitiesPhysicsFlagsTable, exposes flags about the entity physics and rendering: whether it has a shadow, or if it reacts to projectiles, and so on. Another table figured out by looking at the Zelda SNES disassembly is wEntitiesFlashCountdownTable, which is used to make an entity flash for a while after it received a hit.

Generic trampoline

A while ago, user @spaceotter on Discord asked if a generic trampoline function was available in the game.

A what?

Remember how the game code has to be divided into different code sections, which mostly can’t be loaded at the same time? This creates an issue: in this configuration, how is it possible to call, from bank X, a function residing in bank Y? Bank X can’t directly call of jump to the function, because if bank X is loaded, then bank Y isn’t, and vice-versa.

The solution: a trampoline. It’s a small piece of code in bank 0 (the one that is always loaded) that allows to call a function from one bank to another.

The structure of a trampoline is almost always the same:

  1. Jump from bank X to bank 0 (which is always loaded);
  2. Switch to bank Y;
  3. Call the function in bank Y;
  4. Switch back to bank X;
  5. Return to the caller in bank X.

In Link’s Awakening, many trampolines are defined for specific uses: the target bank, function and return bank are hardcoded. As a trampoline is only a few instructions, hard-coding and duplication isn’t that bad. And if you have the original source code of the game, adding another ad-hoc trampoline when needed is easy.

But the ROM-hacking community usually doesn’t have the original source code of the same. And most of the ROM-hacking work is patching existing functions at specific places, to call newly-added code. It is quite useful for new code to call existing functions, but what if a trampoline for these functions doesn’t exist in the original game – or exists, but returns to the wrong bank?

This is where a generic trampoline function is really useful. Until a few days, I though developers never bothered to actually code one. But as I was randomly browsing some code in bank 0, I found this piece of code:

func_BD7:
    ld   a, [$DE01]
    ld   [MBC3SelectBank], a
    call label_BE7
    ld   a, [$DE04]
    ld   [MBC3SelectBank], a
    ret

When called, $DE01 and $DE04 are usually two different bank numbers, and the address of a function is also stored… Here we are: this is actually our generic trampoline!

Here is the documented version of it:

; Generic trampoline, for calling a function into another bank.
Farcall:
    ; Switch to bank wFarcallBank
    ld   a, [wFarcallBank]
    ld   [MBC3SelectBank], a
    ; Call the target function
    call Farcall_trampoline
    ; Switch back to bank wFarcallReturnBank
    ld   a, [wFarcallReturnBank]
    ld   [MBC3SelectBank], a
    ret

This function is only used in three different places: once in the credits, and twice in palette-related code. It was probably added to the code base while making the DX version – but wasn’t ever used a lot. Maybe because it doesn’t preserve some of the registers (making argument passing cumbersome), or because it is slower than a hardcoded trampoline for a specific use.

But ROM-hackers should enjoy this: hardcoded-trampolines cannot be easily patched into the original binary, so a generic function may prove useful to hook new code into the game.

What’s next?

Now that the disassembly is complete, and the entity system is getting in a decent shape, the next important milestone is to make the disassembly shiftable. Work has already begun – and we’ll see how and what does that mean for the project in the next progress report.

Want to read more? Discover more of the code, or join the discussion on Discord.

Quelles grèves pour l’informatique ?

Depuis le 5 décembre, je suis en grève contre la retraite individualiste dite « à points », et pour la mise en place d’un système de retraite solidaire.

Faire grève, dans l’informatique, c’est pas courant. Les informaticien·nes sont peu syndiqué·es, et ne participent que rarement à des mouvements sociaux. Moi-même, ce n’est que la troisième fois que je fais grève de toute ma carrière – et la première grève reconductible.

J’imagine qu’il y a des spécificités liées à l’informatique, qui font qu’on envisage moins la grève comme moyen d’action :

Dans ces conditions, quel sens peut avoir une grève dans l’informatique ?

La grève pour discuter

Depuis le début de la grève, et même un peu avant, on a beaucoup discuté avec des collègues. De la réforme à venir, de la grève, des moyens d’actions. Qui fera grève, quand, comment ? On partage des liens, on s’informe. On se retrouve en manifestation. Beaucoup de collègues ont écrit des articles pour parler de la réforme, et de la grève dans l’informatique.

De ce temps disponible sont nés par exemple :

Bref, la grève a libéré du temps pour s’informer, discuter, réfléchir. C’est déjà une belle réussite.

Visibiliser la grève

Comment rendre visible l’arrêt de travail alors que les systèmes que nous gérons sont largement automatisés ? Même si on est nombreux·ses à cesser le travail, les systèmes continueront à tourner pendant un moment : ça se remarquera à peine. Et l’enjeu d’une grève, c’est quand même que ça soit visible.

Avant la grève, on a joué avec quelques idées pour rendre notre arrêt de travail plus visible. Coder un fonctionnement où les systèmes se désactivent si on ne pointe pas le matin ? Faire la grève de résolution des soucis en production ?

Finalement on est un certain nombre à être partis sur une idée pas nouvelle, mais simple : mettre un bandeau en haut des sites web qu’on gère, qui explique qu’une partie de l’équipe est en grève. Le bandeau ne bloque pas l’accès au site, ni son fonctionnement.

Selon les sites et les contextes, la formulation peut être plus ou moins forte. Il y a par exemple le bandeau de grève de mon blog personnel, qui mentionne explicitement les revendications :

Bannière de grève sur mon blog personnel

En revanche, le site sur lequel je travaille comme indépendant fait partie du service public, et demande donc une formulation plus neutre :

Bannière de grève sur un site du service public

Si l’idée d’un bandeau n’est pas bien radicale, elle a tout de même fait débat. Certaines personnes s’interrogent sur la légitimité de ces bannières : n’est-ce pas utiliser une resource professionnelle pour faire passer un message personnel ?

Alors on en discute, on essaie de trouver des arguments. Une bannière sur un site, ce n’est sans doute pas différent d’une banderole devant une usine, ou une école. Et d’ailleurs, une banderole, ça ne signifie pas que toute l’équipe ou l’établissement derrière est d’accord avec le message – mais que personne ne va y opposer un veto fort, ou aller la décrocher.

Par ailleurs, ce genre de bannière existe sur de nombreux sites, y compris du service public : Radio-France, la RATP, la SNCF… L’enjeu, c’est sans doute plus la formulation du message, plus ou moins militante. Mais ce genre de question montre que la culture du mouvement social en informatique part de loin, et ne commence que tout doucement à se construire.

Et le blocage ?

Au bout de quelques jours de grève, on se rend compte que malgré la visibilité apportée, une bannière, ça ne dérange pas grand monde. Que le mouvement dure, et que la grève se fait longue.

Alors les discussions continuent. On se dit que probablement, la grève n’est efficace que lorsqu’elle dérange, qu’elle perturbe. Si la vie continue, si l’usine tourne malgré tout, si les systèmes ronronnent, rien ne se passe.

Et de fait, dans les mouvements sociaux, on a l’impression que des concessions sont obtenues par les actions qui ont un impact sur la vie quotidienne, moins que par le nombre de manifestants dans la rue. Blocages de la production (grève massive dans l’usine, piquets de grève, etc.) ou de la circulation (arrêt des transports, blocage des dépôts de bus ou de raffineries, opérations escargot sur les routes), tracteurs d’agriculteur·trices devant la préfecture, ordures qui s’accumulent.

Pour certaines professions, l’arrêt de travail est rapidement visible (chauffeurs, éboueurs, enseignants) ; d’autres où c’est moins le cas (argiculteurs, avocats, étudiants). Dans ce cas, les piquets de grève et les blocages sont régulièrement utilisés pour renforcer l’impact de la grève :

Dans tous ces cas, les blocages et les piquets de grève, s’ils sont techniquement illégaux, sont des outils très efficaces utilisés régulièrement dans le rapport de force.

Et justement, ces professions ont lancé il y a quelques jours un appel à leurs collègues informaticien·nes, les enjoignant à rejoindre la lutte avec leurs moyens.

Bloquer légitimement en informatique

Alors, comment transposer ces moyens d’action à l’informatique ?

Il faudrait sans doute déjà ne pas être seul·e. Bloquer une ressource sans subir de répression, ça fonctionne si on est nombreux·ses à le faire – que le cadre soit une équipe, un collectif informel ou un syndicat.

La légitimité du blocage se pose aussi. Idéalement, des blocages ciblés concerneraient au maximum les entreprises et le gouvernement – et au minimum les usagers ordinaires. Ou en tout cas aurait un effet insupportable mais ciblé, qui pousse au maximum à ce que les revendications de la grève soient satisfaite.

Par exemple, si des informaticien·nes décident de bloquer un service, il peut être intéressant de ne le rendre indisponible que la section utilisée par les administrateurs du site (le backoffice), et de laisser la partie utilisée par des utilisateurs externes disponible.

Avec tout ça, des idées émergent :

J’ai l’impression que ces réflexions n’en sont qu’au tout début. Et vous, qu’en pensez vous ?
Discutons-en sur Mastodon, ou sur le chat de onestla.tech.