Skip to content
Ebbe edited this page Aug 3, 2026 · 5 revisions

Spyro: A Hero's Tail Archipelago Mod - Technical Specification

This wiki serves as a reference for developers of this archipelago, or simply those curious about how it works.

Overview

This mod implements several features and patches to allow an external archipelago client to monitor and make changes to the game's state and enable/disable certain features.

A struct with several settings and variables is set up in two places in memory, the patch area and the gamestate area. The initial settings for the archipelago save is written to the patch struct first. Once a new file is started, the settings are copied over from patch to gamestate by the game, after which the gamestate struct is used by the game while the save is being played, and the patch area is no longer used for anything. The gamestate area values persist in the save file on the memory card.

The addresses for every setting in both areas are printed to Dolphin's log window when the game boots. Make sure you have "OSReport HLE" enabled in the log configuration.

Settings

These are all variables in the settings struct stored in both the patch and gamestate areas, in order of memory location.

location_bitfield

Type: U8 array of length 80

One long bitfield for every location in the multiworld. This includes collectables, objectives for NPC quests or minigames, "junk chests" (locked chests without a Dragon Egg or Light Gem), dragon elder abilities, bosses and fireworks.

Every element is stored as two bits. The first is 1 if the item has been collected, and the second is 1 if it's deemed "reachable". The "collected" bit is set by the game, the "reachable" bit is set by the client and determines which checkboxes in the minimap are green or red.

An example algorithm in C to read a specific item at index i:

int byte = (i*2) / 8;
int bit = (i*2) % 8;

u8 reachable_mask = 0b10 << bit;
u8 collected_mask = 0b01 << bit;

bool reachable = (bitfield[byte] & reachable_mask) != 0;
bool collected = (bitfield[byte] & collected_mask) != 0;

keyring_bitfield

Type: U8 array of length 2

A bitfield for which of the 14 keyrings have been obtained in this save. Either set by the game if the shop isn't randomized, or by the client if it is.

An example algorithm in C to read a specific keyring bit at index i:

int byte = i / 8;
int bit = i % 8;

u8 mask = 1 << bit;

bool obtained = (bitfield[byte] & mask) != 0;

shoppad_bitfield

Type: U8 array of length 5

A bitfield for which of the 37 shop pads are enabled in this save. If unlock_all_shops is enabled, all 37 bits are set by the game when the save is started.

An example algorithm in C to read a specific shop pad bit at index i:

int byte = i / 8;
int bit = i % 8;

u8 mask = 1 << bit;

bool unlocked = (bitfield[byte] & mask) != 0;

Note: If shop pads are unlocked by the client, it's recommended to also enable disable_shop_pad_proximity_activate, to disable the game unlocking shop pad locations itself.

num_gem_packs_received

Type: U8

A value used by the client to keep track of the amount of gem packs obtained on this savefile.

num_lock_picks_received

Type: U8

A value used by the client to keep track of the amount of lock picks obtained on this savefile. Only applies if key rings are off and shop randomization is on.

num_fire_ammo_received

Type: U8

A value used by the client to keep track of the amount of fire bombs obtained on this savefile. Only applies if shop randomization is on.

num_electric_ammo_received

Type: U8

A value used by the client to keep track of the amount of electric missiles obtained on this savefile. Only applies if shop randomization is on.

num_water_ammo_received

Type: U8

A value used by the client to keep track of the amount of water bombs obtained on this savefile. Only applies if shop randomization is on.

num_ice_ammo_received

Type: U8

A value used by the client to keep track of the amount of ice missiles obtained on this savefile. Only applies if shop randomization is on.

deathlink_ingoing

Type: U8

The ingoing deathlink signal. Set to 1 or 2 by the client to indicate a deathlink has been received and the game should kill the player. When the player is dead, this value is set back to 0.

Setting the value to 1 is a regular deathlink and will just kill the player as soon as it's able. 2 means it's "shielded" and the game will only kill the player if they don't have the butterfly jar, otherwise it'll just take the jar away.

deathlink_outgoing

Type: U8

The outgoing deathlink signal. Set to a non-zero value by the game if the player has died. The client sets it back to 0 after it has processed the signal.

The game will intentionally not set this value if the death was a result of an ingoing deathlink, to prevent infinite loops.

The value it's set to determine the reason for the death, though for now there's only a possible value of 1 (default).

infinite_butterfly_jar

Type: Boolean

Set to true by the client if the game should give the player a butterfly jar every time they die and respawn. Only used when shop randomization is on.

infinite_double_gem

Type: Boolean

Set to true by the client if the game should turn on the double gem powerup permanently. Only used when shop randomization is on.

fireworks_are_randomized

Type: Boolean

Initialized to true in the patch area if the fireworks are randomized (items in the multiworld) this playthrough. Even if false, the game will report the fireworks in the location_bitfield when lit. This setting simply determines if the fireworks are shown as checkboxes on the minimap.

randomize_shop

Type: Boolean

Initialized to true in the patch area if the shop is randomized. If the shop is randomized, every shop item besides the teleport ticket will be filled in by the client instead of the game.

use_key_rings

Type: Boolean

Initialized to true in the patch area if keyrings are used. If the shop is randomized, the client is responsible for distributing key rings into the multiworld as items, otherwise they are simply added to the shop list by the game.

skip_cutscene_button

Type: Boolean

Initialized to true in the patch area if the cutscene skipping feature should be enabled.

When enabled, the player can skip (most) cutscenes with the Y button.

allow_teleport_to_hub

Type: Boolean

Initialized to true in the patch area if the option to teleport back to HUB should be present in the pause menu.

This setting is always on.

disable_popups

Type: Boolean

Initialized to true in the patch area if the game's tutorial pop-ups should be disabled..

This setting is always on.

instant_elevators

Type: Boolean

Initialized to true in the patch area if the instant elevator feature should be enabled. This teleports the player to the top/bottom of the elevators when the player interacts with them.

starting_realm

Type: U8

Initialized in the patch area to a value from 0 to 3 depending on which realm the player should start the save in.

  • 0: Dragon Kingdom
  • 1: Lost Cities
  • 2: Icy Wilderness
  • 3: Volcanic Isle

realm_access

Type: Boolean array of length 4

4 booleans determining which realms the player has access to. Can either be written to in the patch area before starting, or the gamestate area during play. The mod automatically writes true to the starting realm's access.

patch_been_written_to

Type: Boolean

Used to check if the patch area is ready to be used for a new savefile. Set to 1 by the client after all the other settings have been written.

This is what determines if the "Settings not initialized" or "Settings initialized" messages on the title screen are shown, and a warning message will be shown on the file select screen if this value is 0.

mw_seed

Type: U32

The lower 4 bytes of the multiworld seed. Used to ensure that the patch settings matches the save file if it's continued. A warning is displayed on the file select screen if there's a mismatch.

init

Type: U32

The magic value 0x45424245 (spells "EBBE" in ASCII) is written here to the gamestate area by the mod after copying over the settings from the patch area.

This is simply used to check if the save has been initialized with the archipelago settings. A warning saying "This save was not started with archipelago" appears on the file select screen if the selected save does not have this magic value.

boss_costs

Type: U8 array of length 4

The Dark Gem costs for the barriers for each of the 4 bosses in the order of: Gnasty Gnorc, Ineptune, Red and Mecha Red.

lg_door_costs

Type: U8 array of length 4

The Light Gem costs for each of the 4 light gem doors in the order of: Dragonfly Falls, Coastal Remains, Frostbite Village and Dark Mine.

ball_gadget_cost

Type: U8

The Light Gem cost of the ball gadget. The ball gadget stations after the first one have been patched to also require this cost.

invincibility_cost

Type: U8

The Light Gem cost of the invincibility gadget.

supercharge_cost

Type: U8

The Light Gem cost of the supercharge gadget.

boss_easy_mode

Type: Boolean array of length 4

Whether the "boss easy mode" should be set for each of the 4 bosses in the order of: Gnasty Gnorc, Ineptune, Red and Mecha Red.

When a boss' easy mode is enabled, each hit from the player deals triple damage, dramatically shortening the fight.

shop_unlock_mode

Type: Boolean

Initialized in the patch area to true to make a few things happen:

  • Items in the shop don't subtract gems when purchased.
  • Changes the text to say "Unlocked at x Gems" instead of just "x Gems".
  • Makes the "cost" field in the shop item struct be one S32 value instead of two U16 values.

teleport_anywhere

Type: Boolean

Initialized in the patch area to true to make the shop pad teleport ticket not limited to within the realm the player is in (you can teleport from a shop pad in one realm to a shop pad in another).

Make sure you only set this in the patch area, as this can otherwise cause instability.

unlock_all_shops

Type: Boolean

Initialized in the patch area to true to immediately unlock every shop pad teleport location from the start of the game.

disable_shop_pad_proximity_activate

Type: Boolean

Disables the behavior where the game unlocks a shop pad for teleporting when you get close to it.

xls_shop_sheetcount_ALWAYS_1

Type: S32

The amount of datasheets in the "GUI_Shop" spreadsheet. This is always 1.

xls_shop_sheet_offset_ALWAYS_4

Type: S32

The offset in bytes from this variable to the data held in the first datasheet. This is always 4.

xls_shop_rowcount

Type: S32

The number of rows in the GUI_Shop datasheet. This determines how many items are in the shop.

xls_shop_items

Type: Shop item array of length 61

The array of custom shop items set by the client in the patch area when shop randomization is on. If this is off, this array should be left untouched.

The first item is a teleport ticket by default and should be left untouched by the client. The client writes items from the 2nd item onward.

This array follows the standard format used by the game. The value xls_shop_rowcount controls the amount of items read by the game from this table. Each entry is a 32 byte struct with info about a shopping item:

Name Type Description
Entity HashCode The HashCode of the Entity (3D model) used for the shop item.
File HashCode The HashCode of the file containing the Entity used for the shop item.
In vanilla this is always HT_File_Panel (0x01000028).
ItemText HashCode Unused.
DescText HashCode The HashCode for the text string shown in the item's description.
cost U16[2]/S32 If shop_unlock_mode is off: Two 16-bit values for the item's cost. The first is for the main shop cost, the second is the remote shop cost.
If shop_unlock_mode is on: One 32-bit value for the item's cost.
Count S16 The amount of the item that can be purchased. Always either 1 (one-time-purchase) or -1 (which means unlimited).
Num S16 The amount of the item available. 0 means unlimited.
AvailableFlags U32 The ability flags that must be set before this item can be purchased.
BroughtFlags U32 The ability flags that are set upon this item being purchased.

(Note: HashCodes are 4 bytes.)

When the client adds custom shop items, it should follow these guidelines:

Entity can be any 3D model from the file HT_File_Panel, which includes:

  • HT_Entity_Lockpicker (0x0200014c) - Lock Pick
  • HT_Entity_SparxHit (0x0200014b) - Butterfly bottle
  • HT_Entity_FlameBreath (0x02000077) - Fire bomb
  • HT_Entity_ElectricBreath (0x020000a7) - Electric missile
  • HT_Entity_WaterBreath (0x02000114) - Water bomb
  • HT_Entity_IceBreath (0x020000a1) - Ice missile
  • HT_Entity_FlameBreathMag (0x0200023f) - Fire bomb magazine
  • HT_Entity_ElectricBreathMag (0x0200023e) - Electric missile magazine
  • HT_Entity_WaterBreathMag (0x02000241) - Water bomb magazine
  • HT_Entity_IceBreathMag (0x02000240) - Ice missile magazine
  • HT_Entity_Lockpicker_Mag (0x02000242) - Keychain
  • HT_Entity_FullHealth (0x020001b1) - Butterfly jar
  • HT_Entity_Shop_DoubleGemVal (0x0200023a) - Double gem
  • HT_Entity_Shop_HornDive (0x0200023b) - Horn dive upgrade
  • HT_Entity_Shop_RightsOfPassage (0x0200023c) - Teleport ticket
  • HT_Entity_Shop_TeleportMainShop (0x0200023d) - Teleport ticket (yellower unused variant)

File should be left as HT_File_Panel (0x01000028). This file is always guaranteed to be loaded, and so is safe to use.

Both ItemText and DescText should get a custom hashcode corresponding to a text element in the shop_text array. Just make both ItemText and DescText the same value.

The cost values depend on the shop_unlock_mode setting. If on, write a single price as a 32-bit signed value. If off, write two 16-bit unsigned values, the first for the main shop price, and the second for the remote shop price.

Leave count as 1 (makes each item be a one-time purchase).

Leave the rest of the values as 0.

Once all items have been written, write the total number of items to the xls_shop_rowcount value (including the teleport ticket at the start).

shop_text

Type: Shop text array of length 60

The array of custom text elements used by the custom shop items in xls_shop_items. Each text element is distinguished by a custom HashCode (4 bytes) stored in the shop item. Each element also stores the boolean value for whether the custom item has been purchased or not.

The 98 byte structure of each element looks like this:

Name Type Description
been_bought Boolean 0 if the item hasn't been bought, 1 if it has. Written to by the game.
padding U8 Padding byte, leave as 0.
text wchar[48] The null-terminated UTF-16 string. 48 max characters.

The custom HashCode used in the shop items have the format 0x2801XXXX. The XXXX should be the index of the text element.

So the first custom item in xls_shop_items should store 0x28010000 as its ItemText and DescText HashCodes, which points to the first text element in shop_text. The second custom item should store 0x28010001, which points to the second text element, and so on.

Notification System

Also printed in the Dolphin log window on boot is the addresses for the values controlling the notifications shown on screen.

To show a notification, the client sets the color of the text, writes a UTF-16 string, then sets the timer for how long in frames the notification should show. As soon as the timer is set to a non-zero value, the game displays the notification while ticking the timer down. It stops when the timer reaches 0. The client can also force a notification off by setting the timer to 0.

ap_notification_color

Type: 4-byte RGBA hex color

The color of the notification text. Stored in a custom RGBA format where each component is a byte ranging from 0 to 0x80. Full white is 0x80808080.

ap_notification_timer

Type: U32

The time remaining for the current notification in frames (60 = 1 second).

ap_notification_text_buffer

Type: UTF-16 string

The null-terminated UTF-16 string the game will print to the screen for the notification. 256 characters max.

Clone this wiki locally