Skip to main content

rkg_utils/header/
slot_id.rs

1use std::convert::Infallible;
2use std::fmt::Display;
3
4use crate::byte_handler::{ByteHandler, ByteHandlerError, FromByteHandler};
5
6/// Errors that can occur while constructing a [`SlotId`].
7#[derive(thiserror::Error, Debug)]
8pub enum SlotIdError {
9    /// The slot ID byte did not map to any known [`SlotId`] variant.
10    #[error("Non Existent Slot ID")]
11    NonExistentSlotId,
12    /// A `ByteHandler` operation failed.
13    #[error("ByteHandler Error: {0}")]
14    ByteHandlerError(#[from] ByteHandlerError),
15    /// Infallible conversion error; cannot occur at runtime.
16    #[error("")]
17    Infallible(#[from] Infallible),
18}
19
20/// All Nintendo-Assigned Slot IDs. Some have special effects such as switching OST.
21///
22/// Each variant corresponds to a specific slot in the game's internal course
23/// table. Slot IDs are not assigned sequentially by cup order; for example,
24/// `GalaxyColosseum` (`0xC9`) is a special slot that can trigger unique behavior.
25///
26/// <https://wiki.tockdom.com/wiki/List_of_Identifiers#Courses>
27/// <https://wiki.tockdom.com/wiki/Slot>
28#[derive(Clone, Copy, Debug, PartialEq)]
29pub enum SlotId {
30    // Normal Tracks
31    LuigiCircuit,
32    MooMooMeadows,
33    MushroomGorge,
34    ToadsFactory,
35    MarioCircuit,
36    CoconutMall,
37    DKSnowboardCross,
38    WariosGoldMine,
39    DaisyCircuit,
40    KoopaCape,
41    MapleTreeway,
42    GrumbleVolcano,
43    DryDryRuins,
44    MoonviewHighway,
45    BowsersCastle,
46    RainbowRoad,
47    GCNPeachBeach,
48    DSYoshiFalls,
49    SNESGhostValley2,
50    N64MarioRaceway,
51    N64SherbetLand,
52    GBAShyGuyBeach,
53    DSDelfinoSquare,
54    GCNWaluigiStadium,
55    DSDesertHills,
56    GBABowserCastle3,
57    N64DKJungleParkway,
58    GCNMarioCircuit,
59    SNESMarioCircuit3,
60    DSPeachGardens,
61    GCNDKMountain,
62    N64BowsersCastle,
63
64    // Battle Arenas
65    BlockPlaza,
66    DelfinoPier,
67    FunkyStadium,
68    ChainChompWheel,
69    ThwompDesert,
70    SNESBattleCourse4,
71    GBABattleCourse3,
72    N64Skscraper,
73    GCNCookieLand,
74    DSTwilightHouse,
75
76    // Other Slots
77    /// The only non-standard slot technically possible via Nintendo's Galaxy Colosseum-based tournament.
78    GalaxyColosseum,
79    /// The post-race victory slot.
80    WinningScene,
81    /// The post-race defeat slot.
82    LosingScene,
83    /// The credits sequence slot.
84    Credits,
85}
86
87/// Formats the slot as its full course name (e.g. `"Maple Treeway"`, `"GCN DK Mountain"`).
88impl Display for SlotId {
89    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
90        let s = match self {
91            // Normal Tracks
92            SlotId::LuigiCircuit => "Luigi Circuit",
93            SlotId::MooMooMeadows => "Moo Moo Meadows",
94            SlotId::MushroomGorge => "Mushroom Gorge",
95            SlotId::ToadsFactory => "Toad's Factory",
96            SlotId::MarioCircuit => "Mario Circuit",
97            SlotId::CoconutMall => "Coconut Mall",
98            SlotId::DKSnowboardCross => "DK Summit",
99            SlotId::WariosGoldMine => "Wario's Gold Mine",
100            SlotId::DaisyCircuit => "Daisy Circuit",
101            SlotId::KoopaCape => "Koopa Cape",
102            SlotId::MapleTreeway => "Maple Treeway",
103            SlotId::GrumbleVolcano => "Grumble Volcano",
104            SlotId::DryDryRuins => "Dry Dry Ruins",
105            SlotId::MoonviewHighway => "Moonview Highway",
106            SlotId::BowsersCastle => "Bowser's Castle",
107            SlotId::RainbowRoad => "Rainbow Road",
108
109            SlotId::GCNPeachBeach => "GCN Peach Beach",
110            SlotId::DSYoshiFalls => "DS Yoshi Falls",
111            SlotId::SNESGhostValley2 => "SNES Ghost Valley 2",
112            SlotId::N64MarioRaceway => "N64 Mario Raceway",
113            SlotId::N64SherbetLand => "N64 Sherbet Land",
114            SlotId::GBAShyGuyBeach => "GBA Shy Guy Beach",
115            SlotId::DSDelfinoSquare => "DS Delfino Square",
116            SlotId::GCNWaluigiStadium => "GCN Waluigi Stadium",
117            SlotId::DSDesertHills => "DS Desert Hills",
118            SlotId::GBABowserCastle3 => "GBA Bowser Castle 3",
119            SlotId::N64DKJungleParkway => "N64 DK Jungle Parkway",
120            SlotId::GCNMarioCircuit => "GCN Mario Circuit",
121            SlotId::SNESMarioCircuit3 => "SNES Mario Circuit 3",
122            SlotId::DSPeachGardens => "DS Peach Gardens",
123            SlotId::GCNDKMountain => "GCN DK Mountain",
124            SlotId::N64BowsersCastle => "N64 Bowser's Castle",
125
126            // Battle Arenas
127            SlotId::BlockPlaza => "Block Plaza",
128            SlotId::DelfinoPier => "Delfino Pier",
129            SlotId::FunkyStadium => "Funky Stadium",
130            SlotId::ChainChompWheel => "Chain Chomp Wheel",
131            SlotId::ThwompDesert => "Thwomp Desert",
132            SlotId::SNESBattleCourse4 => "SNES Battle Course 4",
133            SlotId::GBABattleCourse3 => "GBA Battle Course 3",
134            SlotId::N64Skscraper => "N64 Skyscraper",
135            SlotId::GCNCookieLand => "GCN Cookie Land",
136            SlotId::DSTwilightHouse => "DS Twilight House",
137
138            // Other Slots
139            SlotId::GalaxyColosseum => "Galaxy Colosseum",
140            SlotId::WinningScene => "Winning Scene",
141            SlotId::LosingScene => "Losing Scene",
142            SlotId::Credits => "Credits",
143        };
144
145        write!(f, "{}", s)
146    }
147}
148
149/// Converts a [`SlotId`] into its raw byte representation for the RKG header.
150impl From<SlotId> for u8 {
151    fn from(value: SlotId) -> u8 {
152        match value {
153            SlotId::LuigiCircuit => 0x08,
154            SlotId::MooMooMeadows => 0x01,
155            SlotId::MushroomGorge => 0x02,
156            SlotId::ToadsFactory => 0x04,
157            SlotId::MarioCircuit => 0x00,
158            SlotId::CoconutMall => 0x05,
159            SlotId::DKSnowboardCross => 0x06,
160            SlotId::WariosGoldMine => 0x07,
161            SlotId::DaisyCircuit => 0x09,
162            SlotId::KoopaCape => 0x0F,
163            SlotId::MapleTreeway => 0x0B,
164            SlotId::GrumbleVolcano => 0x03,
165            SlotId::DryDryRuins => 0x0E,
166            SlotId::MoonviewHighway => 0x0A,
167            SlotId::BowsersCastle => 0x0C,
168            SlotId::RainbowRoad => 0x0D,
169            SlotId::GCNPeachBeach => 0x10,
170            SlotId::DSYoshiFalls => 0x14,
171            SlotId::SNESGhostValley2 => 0x19,
172            SlotId::N64MarioRaceway => 0x1A,
173            SlotId::N64SherbetLand => 0x1B,
174            SlotId::GBAShyGuyBeach => 0x1F,
175            SlotId::DSDelfinoSquare => 0x17,
176            SlotId::GCNWaluigiStadium => 0x12,
177            SlotId::DSDesertHills => 0x15,
178            SlotId::GBABowserCastle3 => 0x1E,
179            SlotId::N64DKJungleParkway => 0x1D,
180            SlotId::GCNMarioCircuit => 0x11,
181            SlotId::SNESMarioCircuit3 => 0x18,
182            SlotId::DSPeachGardens => 0x16,
183            SlotId::GCNDKMountain => 0x13,
184            SlotId::N64BowsersCastle => 0x1C,
185            SlotId::BlockPlaza => 0x21,
186            SlotId::DelfinoPier => 0x20,
187            SlotId::FunkyStadium => 0x23,
188            SlotId::ChainChompWheel => 0x22,
189            SlotId::ThwompDesert => 0x24,
190            SlotId::SNESBattleCourse4 => 0x27,
191            SlotId::GBABattleCourse3 => 0x28,
192            SlotId::N64Skscraper => 0x29,
193            SlotId::GCNCookieLand => 0x25,
194            SlotId::DSTwilightHouse => 0x26,
195            SlotId::GalaxyColosseum => 0xC9,
196            SlotId::WinningScene => 0x37,
197            SlotId::LosingScene => 0x38,
198            SlotId::Credits => 0x3A,
199        }
200    }
201}
202
203/// Converts a raw byte value from the RKG header into a [`SlotId`].
204///
205/// # Errors
206///
207/// Returns [`SlotIdError::NonExistentSlotId`] if the byte does not correspond
208/// to any known slot.
209impl TryFrom<u8> for SlotId {
210    type Error = SlotIdError;
211    fn try_from(value: u8) -> Result<Self, Self::Error> {
212        match value {
213            0x08 => Ok(SlotId::LuigiCircuit),
214            0x01 => Ok(SlotId::MooMooMeadows),
215            0x02 => Ok(SlotId::MushroomGorge),
216            0x04 => Ok(SlotId::ToadsFactory),
217            0x00 => Ok(SlotId::MarioCircuit),
218            0x05 => Ok(SlotId::CoconutMall),
219            0x06 => Ok(SlotId::DKSnowboardCross),
220            0x07 => Ok(SlotId::WariosGoldMine),
221            0x09 => Ok(SlotId::DaisyCircuit),
222            0x0F => Ok(SlotId::KoopaCape),
223            0x0B => Ok(SlotId::MapleTreeway),
224            0x03 => Ok(SlotId::GrumbleVolcano),
225            0x0E => Ok(SlotId::DryDryRuins),
226            0x0A => Ok(SlotId::MoonviewHighway),
227            0x0C => Ok(SlotId::BowsersCastle),
228            0x0D => Ok(SlotId::RainbowRoad),
229            0x10 => Ok(SlotId::GCNPeachBeach),
230            0x14 => Ok(SlotId::DSYoshiFalls),
231            0x19 => Ok(SlotId::SNESGhostValley2),
232            0x1A => Ok(SlotId::N64MarioRaceway),
233            0x1B => Ok(SlotId::N64SherbetLand),
234            0x1F => Ok(SlotId::GBAShyGuyBeach),
235            0x17 => Ok(SlotId::DSDelfinoSquare),
236            0x12 => Ok(SlotId::GCNWaluigiStadium),
237            0x15 => Ok(SlotId::DSDesertHills),
238            0x1E => Ok(SlotId::GBABowserCastle3),
239            0x1D => Ok(SlotId::N64DKJungleParkway),
240            0x11 => Ok(SlotId::GCNMarioCircuit),
241            0x18 => Ok(SlotId::SNESMarioCircuit3),
242            0x16 => Ok(SlotId::DSPeachGardens),
243            0x13 => Ok(SlotId::GCNDKMountain),
244            0x1C => Ok(SlotId::N64BowsersCastle),
245            0x21 => Ok(SlotId::BlockPlaza),
246            0x20 => Ok(SlotId::DelfinoPier),
247            0x23 => Ok(SlotId::FunkyStadium),
248            0x22 => Ok(SlotId::ChainChompWheel),
249            0x24 => Ok(SlotId::ThwompDesert),
250            0x27 => Ok(SlotId::SNESBattleCourse4),
251            0x28 => Ok(SlotId::GBABattleCourse3),
252            0x29 => Ok(SlotId::N64Skscraper),
253            0x25 => Ok(SlotId::GCNCookieLand),
254            0x26 => Ok(SlotId::DSTwilightHouse),
255            0xC9 => Ok(SlotId::GalaxyColosseum),
256            0x37 => Ok(SlotId::WinningScene),
257            0x38 => Ok(SlotId::LosingScene),
258            0x3A => Ok(SlotId::Credits),
259            _ => Err(SlotIdError::NonExistentSlotId),
260        }
261    }
262}
263
264/// Deserializes a [`SlotId`] from header byte `0x07`.
265impl FromByteHandler for SlotId {
266    type Err = SlotIdError;
267    /// Expects Header 0x07
268    fn from_byte_handler<T>(handler: T) -> Result<Self, Self::Err>
269    where
270        T: TryInto<ByteHandler>,
271        Self::Err: From<T::Error>,
272    {
273        (handler.try_into()?.copy_byte(0) >> 2).try_into()
274    }
275}