- Rust 100%
| arch | ||
| common | ||
| data | ||
| doc | ||
| math | ||
| meta | ||
| src | ||
| .gitattributes | ||
| .gitignore | ||
| build.rs | ||
| Cargo.lock | ||
| Cargo.toml | ||
| README.md | ||
| rust-toolchain.toml | ||
Super Mario 64, GBA Edition
What is this?
Super Mario 64 GBA (SM64 GBA) is an unofficial demake (i.e: from-scratch remake for less capable hardware) of the 1996 game Super Mario 64, for the Nintendo Gameboy Advance. It is not a complete game and should, for the time being, be considered a tech demo only.
Legal status
SM64 GBA is a clean-room reimplementation of the mechanics of the original SM64. That means that none of the following have been used in its creation:
- Disassembly of the original SM64 ROMs
- Information observed in SM64 decompilation projects
- Propertiary technical information about the original SM64
- Use of Nintendo patents or other intellectual property not broadly considered fair use
An exception to this are the game's assets, which are based on rips from a combination of SM64 and SM64 DS. However, effort has been made to keep the code and assets logically distinct, allowing the code to be freely reused by anybody that wants to make use of it, without infringing on Nintendo's intellectual property. We do not condone the public redistribution of proprietary Nintendo assets.
Note that a license has not yet been chosen for the source code. A reasonable assumption is that the code will be placed under a 'credit required, for non-profit use only' style license, allowing its use in personal, hobby, and fan projects but not commercial ventures.
Goals
- Demonstrate that a port of SM64 to the GBA is, at least in theory, possible
Non-goals
- Reimplementation of buggy behaviour (no BLJs or HOLPs, for example)
- Perfect 1:1 recreation of game mechanics. SM64 GBA is its own game, and should be considered 'inspired by' the original
Features
The following list hasn't been properly maintained and might not be up to date.
Feature list
- [-] Rendering - [x] Implement basic rasterisation - [x] Texture support - [x] Figures - [x] Animation - [x] Stage - [-] Water - [x] Water level - [x] Switch palette when underwater - [-] Interaction with figures - [x] Translucent shadows - [-] Particles - [x] Level geometry - [x] Build-time mesh processing - [x] Build-time flat colour derivation - [x] Acceleration structure - [x] Render sky texture - [-] Camera - [x] Vertex matrix transform - [x] Perspective transform - [x] Basic movement - [-] Camera reorientation - [-] First person - [-] Mario - [x] Basic rendering (blob) - [x] Basic camera-relative movement - [x] Gravity - [x] More complex movement - [x] Wall slide/jump - [x] Long jump - [x] Triple jump - [x] Ground pound - [x] Crouch - [x] Dive - [x] Belly slide - [x] Butt slide - [x] Punch - [x] Kick - [x] Side jump - [-] Ledge grab - [x] Swimming - [x] Pre-render sprites for each limb - [x] Animations - [-] Physics - [x] Collision detection - [x] Basic collision resolution - [-] Ordered/iterative collision resolution - [x] Moving terrain-like objects - [-] Entities - [x] Implement ECS - [-] Objects - [x] Doors - [x] Stars - [-] Coins - [-] Trees - [-] Enemies - [-] Goombas - [-] Characters - [-] Toads - [-] Koopa the Quick - [-] Scenes - [x] Stage changes - [-] Scene transitions - [x] Doors allow transitions - [x] Allow toggling stage elements when going through transitions - [x] Porthole - [x] Bowser face - [-] Separate 'stage' and 'scene' - [-] Intro sequence - [-] Progression - [x] Stars - [-] Saving game state - [-] Associate stars with save state - [-] Gameplay - [x] Scene changes - [x] Health - [-] Lives - [-] Menu/UI - [x] Pause menu - [-] Main menu - [-] Signs - [-] Audio - [x] Basic sample-based audio - [x] Simple additive mixer - [x] Support for playing sound effects through channels - [x] Background music - [-] Sequenced music - [-] Debugging - [x] Build-time metrics switch - [x] Metric: pixel tests - [x] Metric: pixel draws - [x] Metric: vertex transforms - [x] Metric: polygon drawsBuild & run (mGBA)
Note that these instructions assume a Unix-like environment. Linux, Mac OS, or Windows Subsystem for Linux (WSL) should all work.
Requirements:
-
Install
rustup. -
Install mGBA (on Ubuntu, the package is
mgba-qt).
Run the executable with mGBA (mGBA can run ELF executables directly):
# Build and run the game executable
cargo --config arch/gba.toml run -p sm64-gba-gba --release
Build & run (PC)
Note that these instructions assume a Unix-like environment. Linux, Mac OS, or Windows Subsystem for Linux (WSL) should all work.
Requirements:
- Install
rustup.
Run the executable:
# Build and run the game executable
cargo --config arch/pc.toml run -p sm64-gba-pc --release
Build (hardware / other emulators)
Note that these instructions assume a Unix-like environment. Linux, Mac OS, or Windows Subsystem for Linux (WSL) should all work.
Requirements:
-
Install
rustup. -
Install the freestanding ARM version of GCC's
binutils(on Ubuntu, the package isbinutils-arm-none-eabi). -
Install
gbafix(there are several implementations. Since you have Rust installed,cargo install gbafixwill do).
# Build the game executable
cargo --config arch/gba.toml build -p sm64-gba-gba --release
# Copy the contents of the game executable's ELF into a ROM binary
arm-none-eabi-objcopy -O binary target/gba/release/sm64-gba-gba target/sm64-gba.gba
# Fix-up the ROM header such that it is compatible with the GBA's BIOS
gbafix target/sm64-gba.gba
Side-load the .gba using your desired method, such as a flash cart.
Debugging
Requirements:
- Installed
cargo-asm, by runningcargo install cargo-show-asm.
When developing, it's often useful to disassemble a specific function to ensure that the compiler is generating good assembly code.
# Disassemble the given function with inline Rust annotations, stripping data (LUTs, etc.)
cargo asm --config arch/gba.toml -p sm64-gba-gba --bin sm64-gba-gba --color --rust --simplify --this-workspace -- function_name | grep -v "asciz"
# Disassemble math code
cargo asm --target armv5te-unknown-linux-gnueabi --rust -p sm64-gba-math --lib
# Inspect symbols in address order
arm-none-eabi-nm -S -C rust target/gba/release/sm64-gba-gba | sort
Testing
# Run tests
cargo test -p sm64-gba-math
The build script
To minimise the amount of work that needs to happen as the game is running, SM64 GBA uses a compile-time build script
(build.rs) to preprocess assets before inserting them into the game ROM. A lot of this code is untidy and quite
heavily macro-ified. It's certainly possible to improve it, but be aware that a lot of its behaviour is surprisingly
subtle. Changes to some assets can also require corresponding changes in the source code to match. For example, the
number of vertices in the player model is a hard-coded constant that needs to be updated when the model changes. Most
of these constants are specified in common/src/lib.rs, and the build script will at least make an attempt to indicate
which value requires changing.
A note on unsafe
Rust is what is often referred to as a 'memory-safe' language. In reality, this means that it has a memory-safe subset that cannot invoke undefined behaviour. However, for this to be true first requires that any unsafe code respects the language's semantic safety rules.
In SM64 GBA, this is not always the case. Dynamic safety mechanisms like bounds checks are much costlier on old ARM devices like the GBA than on modern hardware, so SM64 GBA very liberally removes them. In fact, bounds checks in particular are removed by default where possible when running in release mode, and there are several edge cases that result in UB as a result: knobbled ECS memory, rasteriser overflows corrupting the palette memory, etc. Fixing them all, and in all cases, has proven to be extremely difficult without compromising performance. Many of them, particularly in the rasteriser, rely on subtle numerical properties of the calculations being performed.
Needless to say, this isn't how you should be writing Rust. If you're new to the language, don't take SM64 GBA as an example of best-practice. It is deeply utilitarian, domain-specific code. The GBA is a very low-stakes environment: it is difficult to write code that can permanently damage the hardware and the worst that can happen is a corrupted save file.
Material attributes
Stage material attributes are applied with material names like <name>:attr0,attr1,attr2.
Attributes include:
slide: causes the player to enter a sliding state when standing on the surfaceslippery: reduces friction when moving on the surfaceunsolid: Surface does not partake in collision detectionfloor: Surface always behaves like a floor, no matter how steepmonkeybars: Surface can be traversed when a ceilinganimate_u=<f32>andanimate_v=<f32>: Surface texture coordinates scroll by the given amount over one seconddouble_sided: causes surfaces to have a front and back side, useful for transparent objects like fencesbillboard: causes surfaces to turn into a billboard. Ensure that you do not triangular the surface or you will end up with multiple billboardsnode=<name>: label the surface as the origin of a particular world node. Can be used to create entities, specify the spawn point, and much more. Does not respect other attributes unlessalso_geometryis specified too.also_geometry: Used only to specify that anodematerial also generates geometry (used for doors, for example)face=<name>: label the surface as having particular non-trivial properties (TODO: rename tosurfaceand squash all other attributes into this)transparent=<kind>: make a surface semi-transparent (acceptsdarken,lighten,water)invisible: make a surface entirely invisible (i.e: does not partake in rendering at all)doorway: The geometry is solid except if the player is performing some forced action (like walking through a door). Impliesalso_geometryandinvisible.
