Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/HarbourMasters/Starship/llms.txt

Use this file to discover all available pages before exploring further.

The .o2r format is Starship’s native asset archive container. It uses a standard ZIP file as its outer envelope, with individual entries storing serialized game resources. This design is fully compatible with .otr archives from Ship of Harkinian—the two formats share the same container layout and differ only in the file extension and the toolchain that produces them.

Container structure

An .o2r file is a ZIP archive. Each ZIP entry corresponds to one game resource and is addressed by its archive path—a forward-slash-separated string that mirrors the original ROM’s segment hierarchy (e.g. sf64/assets/ast_corneria/texture_wall). The resource manager in libultraship indexes all entries from all loaded archives at startup, building an in-memory map from archive path → binary payload. When game code calls ResourceGetDataByName("sf64/assets/..."), the manager looks up that path across every loaded archive, with later archives (including those in mods/) taking precedence over earlier ones.
Because .o2r and .otr share the same container format, any tool that reads one can also read the other. Starship accepts both extensions in the mods/ folder.

Archive path sentinel

Archive-resident paths are distinguished from raw file-system paths at runtime by a seven-byte sentinel prefix. GameEngine_OTRSigCheck checks for this marker:
static const char* sOtrSignature = "__OTR__";

uint8_t GameEngine_OTRSigCheck(const char* data) {
    if (data == nullptr) {
        return 0;
    }
    return strncmp(data, sOtrSignature, strlen(sOtrSignature)) == 0;
}
When the resource manager injects a pointer to a loaded resource it prepends __OTR__ to the path string. The LOAD_ASSET macro uses this check to decide whether to call ResourceGetDataByName or pass the pointer through unchanged.

Resource types

Every entry in an .o2r archive is tagged with a four-character type code stored as a 32-bit big-endian integer in the serialized header. Starship’s ResourceType.h defines all recognized codes in the SF64::ResourceType enum.

Game asset types

Type name4-char codeHex valueDescription
AnimDataANIM0x414E494DCharacter and object animation data
ColPolyCPLY0x43504C59Collision polygon geometry
EnvironmentENVS0x454E5653Per-stage environment settings (fog, lighting, sky)
LimbLIMB0x4C494D42Individual skeleton limb node
MessageMSG 0x4D534720In-game message text (note the trailing space)
MessageTableMSGT0x4D534754Message index lookup table
SkeletonSKEL0x534B454CFull character skeleton hierarchy
ScriptSCRP0x53435250Gameplay event script
ScriptCmdSCMD0x53434D44Individual script command entry
HitboxHTBX0x48544258Object hitbox definition
ObjectInitOBIN0x4F42494EObject initialization parameters
Vec3fVC3F0x56433346Array of 32-bit floating-point 3D vectors
Vec3sVC3S0x56433353Array of 16-bit integer 3D vectors
GenericArrayGARR0x47415252Raw byte array for miscellaneous binary data

NAudio v0 types (legacy audio)

Type name4-char codeHex valueDescription
BankBANK0x42414E4BAudio bank (instrument collection)
SampleAIFC0x41554643Compressed audio sample (AIFC/ADPCM)
SequenceSEQC0x53455143MIDI-like music sequence

NAudio v1 types (current audio)

Type name4-char codeHex valueDescription
SoundFontSFNT0x53464E54Sound font (maps notes to samples)
DrumDRUM0x4452554DPercussion drum instrument definition
InstrumentINST0x494E5354Melodic instrument definition
AdpcmLoopAPCL0x4150434CADPCM loop point data
AdpcmBookAPCB0x41504342ADPCM predictor coefficient book
EnvelopeEVLP0x45564C50Audio amplitude envelope (ADSR)
AudioTableATBL0x4154424CAudio asset lookup table

How the engine loads resources

At startup GameEngine builds an ordered list of archive files and passes it to libultraship’s resource manager:
// Engine.cpp — archive loading order
const std::string main_path   = Ship::Context::GetPathRelativeToAppDirectory("sf64.o2r");
const std::string assets_path = Ship::Context::LocateFileAcrossAppDirs("starship.o2r");

if (std::filesystem::exists(main_path))   archiveFiles.push_back(main_path);
if (std::filesystem::exists(assets_path)) archiveFiles.push_back(assets_path);

// mods/ folder — scanned recursively; .otr and .o2r both accepted
for (const auto& p : std::filesystem::recursive_directory_iterator(patches_path)) {
    const auto ext = p.path().extension().string();
    if (StringHelper::IEquals(ext, ".otr") || StringHelper::IEquals(ext, ".o2r")) {
        archiveFiles.push_back(p.path().generic_string());
    }
}
The resource manager registers all archives in order. When the same archive path appears in multiple archives, the last loaded entry wins—giving files in mods/ the highest precedence. Game code retrieves a resource’s raw binary payload by name:
// Returns a pointer to the deserialized resource payload, or NULL if not found
void* data = ResourceGetDataByName("sf64/assets/ast_corneria/texture_wall");
The LOAD_ASSET macro wraps this call with the __OTR__ sentinel check so it degrades gracefully when passed a plain (non-archived) pointer:
#define LOAD_ASSET(path) \
    (path == NULL ? NULL : \
        (GameEngine_OTRSigCheck((const char*) path) \
            ? ResourceGetDataByName((const char*) path) \
            : path))

Creating .o2r files

The retro tool is the recommended way to produce .o2r archives for mods.

retro — OTR & O2R Generator

Command-line tool that reads asset definition files and packs them into a standards-compliant .o2r (or .otr) archive. Point it at a directory of exported assets, specify the output format as o2r, and drop the result into the mods/ folder.
When authoring replacement assets, keep the internal archive path identical to the original. For example, to replace a texture from sf64.o2r, give your replacement entry the same path in your mod archive. The engine’s load-order priority will automatically prefer the mod version at runtime.

Build docs developers (and LLMs) love