1use crate::{
2 byte_handler::{ByteHandler, ByteHandlerError, FromByteHandler},
3 crc::crc16,
4 header::{
5 combo::{Combo, ComboError, transmission::Transmission},
6 controller::{Controller, ControllerError},
7 date::{Date, DateError},
8 ghost_type::{GhostType, GhostTypeError},
9 in_game_time::{InGameTime, InGameTimeError},
10 location::{
11 Location,
12 constants::{Country, CountryError, SubregionError, Version},
13 },
14 mii::{Mii, MiiError},
15 slot_id::{SlotId, SlotIdError},
16 transmission_mod::{TransmissionMod, TransmissionModError},
17 },
18 write_bits,
19};
20
21use std::io::Read;
22
23pub mod combo;
24pub mod controller;
25pub mod date;
26pub mod ghost_type;
27pub mod in_game_time;
28pub mod location;
29pub mod mii;
30pub mod slot_id;
31pub mod transmission_mod;
32
33#[derive(thiserror::Error, Debug)]
35pub enum HeaderError {
36 #[error("File is not RKGD")]
38 NotRKGD,
39 #[error("Data passed is not correct size (0x88)")]
41 NotCorrectSize,
42 #[error("Friend ghost number out of range (1-30)")]
44 FriendNumberOutOfRange,
45 #[error("Lap split idx not semantically valid")]
47 LapSplitIndexError,
48 #[error("In Game Time Error: {0}")]
50 InGameTimeError(#[from] InGameTimeError),
51 #[error("Slot ID Error: {0}")]
53 SlotIdError(#[from] SlotIdError),
54 #[error("Combo Error: {0}")]
56 ComboError(#[from] ComboError),
57 #[error("Date Error: {0}")]
59 DateError(#[from] DateError),
60 #[error("Controller Error: {0}")]
62 ControllerError(#[from] ControllerError),
63 #[error("Transmission Mod Error: {0}")]
65 TransmissionModError(#[from] TransmissionModError),
66 #[error("Ghost Type Error: {0}")]
68 GhostTypeError(#[from] GhostTypeError),
69 #[error("Mii Error: {0}")]
71 MiiError(#[from] MiiError),
72 #[error("Io Error: {0}")]
74 IoError(#[from] std::io::Error),
75 #[error("Country Error: {0}")]
77 CountryError(#[from] CountryError),
78 #[error("Subregion Error: {0}")]
80 SubregionError(#[from] SubregionError),
81 #[error("ByteHandler Error: {0}")]
83 ByteHandlerError(#[from] ByteHandlerError),
84}
85
86pub struct Header {
92 raw_data: [u8; 0x88],
94 finish_time: InGameTime,
96 slot_id: SlotId,
98 combo: Combo,
100 date_set: Date,
102 controller: Controller,
104 is_compressed: bool,
106 transmission_mod: TransmissionMod,
108 ghost_type: GhostType,
110 is_automatic_drift: bool,
112 decompressed_input_data_length: u16,
114 lap_count: u8,
116 lap_split_times: [InGameTime; 11],
118 location: Location,
120 mii: Mii,
122 mii_crc16: u16,
124}
125
126impl Header {
127 pub fn new_from_path<P: AsRef<std::path::Path>>(p: P) -> Result<Self, HeaderError> {
136 let mut rkg_data = [0u8; 0x88];
137 std::fs::File::open(p)?.read_exact(&mut rkg_data)?;
138 Self::new(&rkg_data)
139 }
140
141 pub fn new(header_data: &[u8]) -> Result<Self, HeaderError> {
150 if header_data.len() != 0x88 {
151 return Err(HeaderError::NotCorrectSize);
152 }
153 if header_data[0..4] != [0x52, 0x4B, 0x47, 0x44] {
154 return Err(HeaderError::NotRKGD);
155 }
156
157 let finish_time = InGameTime::from_byte_handler(&header_data[4..7])?;
158 let slot_id = SlotId::from_byte_handler(header_data[7])?;
159 let combo = Combo::from_byte_handler(&header_data[0x08..0x0A])?;
160 let date_set = Date::from_byte_handler(&header_data[0x09..=0x0B])?;
161 let controller = Controller::from_byte_handler(header_data[0x0B])?;
162 let is_compressed = ByteHandler::from(header_data[0x0C]).read_bool(3);
163 let transmission_mod = TransmissionMod::from_byte_handler(header_data[0x0C])?;
164 let ghost_type = GhostType::from_byte_handler(&header_data[0x0C..=0x0D])?;
165 let is_automatic_drift = ByteHandler::from(header_data[0x0D]).read_bool(1);
166 let decompressed_input_data_length =
167 ByteHandler::try_from(&header_data[0x0E..=0x0F])?.copy_word(0);
168
169 let lap_count = header_data[0x10];
170 let mut lap_split_times = [InGameTime::default(); 11];
171 for idx in 0..lap_count {
172 let start = (0x11 + idx * 3) as usize;
173 lap_split_times[idx as usize] =
174 InGameTime::from_byte_handler(&header_data[start..start + 3])?;
175 }
176
177 let codes = ByteHandler::try_from(&header_data[0x34..=0x37]).unwrap();
178
179 let mut location = Location::find(codes.copy_byte(0), codes.copy_byte(1), Some(Version::ER12));
180
181 if location.is_none() {
182 location = Location::find(codes.copy_byte(0), codes.copy_byte(1), Some(Version::ER11));
183
184 if location.is_none() {
185 location = Location::find(codes.copy_byte(0), codes.copy_byte(1), Some(Version::ER10));
186
187 if location.is_none() {
188 location = Location::find(codes.copy_byte(0), codes.copy_byte(1), Some(Version::Vanilla));
189 }
190 }
191 }
192
193 let location = location.unwrap_or_default();
194
195 let mii = Mii::new(&header_data[0x3C..0x3C + 0x4A])?;
196
197 let mii_crc16 = ByteHandler::try_from(&header_data[0x86..=0x87])?.copy_word(0);
198
199 Ok(Self {
200 raw_data: header_data.try_into().unwrap(),
201 finish_time,
202 slot_id,
203 combo,
204 date_set,
205 controller,
206 is_compressed,
207 transmission_mod,
208 ghost_type,
209 is_automatic_drift,
210 decompressed_input_data_length,
211 lap_count,
212 lap_split_times,
213 location,
214 mii,
215 mii_crc16,
216 })
217 }
218
219 pub fn verify_mii_crc16(&self) -> bool {
222 crc16(&self.raw_data[0x3C..0x86]) == self.mii_crc16()
223 }
224
225 pub fn fix_mii_crc16(&mut self) {
228 self.mii_crc16 = crc16(&self.raw_data[0x3C..0x86]);
229 self.raw_data[0x86..0x88].copy_from_slice(&self.mii_crc16.to_be_bytes());
230 }
231
232 pub fn raw_data(&self) -> &[u8; 0x88] {
234 &self.raw_data
235 }
236
237 pub fn raw_data_mut(&mut self) -> &mut [u8; 0x88] {
239 &mut self.raw_data
240 }
241
242 pub fn finish_time(&self) -> &InGameTime {
244 &self.finish_time
245 }
246
247 pub fn set_finish_time(&mut self, finish_time: InGameTime) {
249 self.finish_time = finish_time;
250 write_in_game_time(self.raw_data_mut(), 0x04, 0, &finish_time);
251 }
252
253 pub fn slot_id(&self) -> SlotId {
255 self.slot_id
256 }
257
258 pub fn set_slot_id(&mut self, slot_id: SlotId) {
260 self.slot_id = slot_id;
261 write_bits(self.raw_data_mut(), 0x07, 0, 6, u8::from(slot_id) as u64);
262 }
263
264 pub fn combo(&self) -> &Combo {
266 &self.combo
267 }
268
269 pub fn set_combo(&mut self, combo: Combo) {
271 write_bits(
272 self.raw_data_mut(),
273 0x08,
274 0,
275 6,
276 u8::from(combo.vehicle()) as u64,
277 );
278 write_bits(
279 self.raw_data_mut(),
280 0x08,
281 6,
282 6,
283 u8::from(combo.character()) as u64,
284 );
285
286 self.combo = combo;
287 }
288
289 pub fn date_set(&self) -> &Date {
291 &self.date_set
292 }
293
294 pub fn set_date_set(&mut self, date_set: Date) {
296 write_bits(
297 self.raw_data_mut(),
298 0x09,
299 4,
300 7,
301 (date_set.year() - 2000) as u64,
302 );
303 write_bits(self.raw_data_mut(), 0x0A, 3, 4, date_set.month() as u64);
304 write_bits(self.raw_data_mut(), 0x0A, 7, 5, date_set.day() as u64);
305
306 self.date_set = date_set;
307 }
308
309 pub fn controller(&self) -> Controller {
311 self.controller
312 }
313
314 pub fn set_controller(&mut self, controller: Controller) {
316 self.controller = controller;
317 write_bits(self.raw_data_mut(), 0x0B, 4, 4, u8::from(controller) as u64);
318 }
319
320 pub fn is_compressed(&self) -> bool {
322 self.is_compressed
323 }
324
325 pub(crate) fn set_compressed(&mut self, is_compressed: bool) {
330 self.is_compressed = is_compressed;
331 write_bits(self.raw_data_mut(), 0x0C, 4, 1, is_compressed as u64);
332 }
333
334 pub fn transmission_mod(&self) -> TransmissionMod {
336 self.transmission_mod
337 }
338
339 pub fn set_transmission_mod(&mut self, transmission_mod: TransmissionMod) {
341 self.transmission_mod = transmission_mod;
342 write_bits(
343 self.raw_data_mut(),
344 0x0C,
345 5,
346 2,
347 u8::from(transmission_mod) as u64,
348 );
349 }
350
351 pub fn ghost_type(&self) -> GhostType {
353 self.ghost_type
354 }
355
356 pub fn set_ghost_type(&mut self, ghost_type: GhostType) {
358 self.ghost_type = ghost_type;
359 write_bits(self.raw_data_mut(), 0x0C, 7, 7, u8::from(ghost_type) as u64);
360 }
361
362 pub fn is_automatic_drift(&self) -> bool {
364 self.is_automatic_drift
365 }
366
367 pub fn set_automatic_drift(&mut self, is_automatic_drift: bool) {
369 self.is_automatic_drift = is_automatic_drift;
370 write_bits(self.raw_data_mut(), 0x0D, 6, 1, is_automatic_drift as u64);
371 }
372
373 pub fn decompressed_input_data_length(&self) -> u16 {
375 self.decompressed_input_data_length
376 }
377
378 pub fn lap_count(&self) -> u8 {
380 self.lap_count
381 }
382
383 pub fn lap_split_times(&self) -> &[InGameTime] {
385 &self.lap_split_times[0..self.lap_count as usize]
386 }
387
388 pub fn lap_split_time(&self, idx: usize) -> Result<InGameTime, HeaderError> {
395 if idx >= self.lap_count as usize {
396 return Err(HeaderError::LapSplitIndexError);
397 }
398 Ok(self.lap_split_times[idx])
399 }
400
401 pub fn set_lap_split_time(&mut self, idx: usize, lap_split_time: InGameTime) {
405 if idx >= self.lap_count as usize {
406 return;
407 }
408 self.lap_split_times[idx] = lap_split_time;
409
410 write_bits(
411 self.raw_data_mut(),
412 0x11 + idx * 0x03,
413 0,
414 7,
415 lap_split_time.minutes() as u64,
416 );
417 write_bits(
418 self.raw_data_mut(),
419 0x11 + idx * 0x03,
420 7,
421 7,
422 lap_split_time.seconds() as u64,
423 );
424 write_bits(
425 self.raw_data_mut(),
426 0x12 + idx * 0x03,
427 6,
428 10,
429 lap_split_time.milliseconds() as u64,
430 );
431 }
432
433 pub fn location(&self) -> &Location {
435 &self.location
436 }
437
438 pub fn set_location(&mut self, location: Location) {
443 write_bits(
444 self.raw_data_mut(),
445 0x34,
446 0,
447 8,
448 u8::from(location.country()) as u64,
449 );
450
451 let subregion_id = if location.country() != Country::NotSet {
452 u8::from(location.subregion()) as u64
453 } else {
454 0xFF
455 };
456
457 write_bits(self.raw_data_mut(), 0x35, 0, 8, subregion_id);
458
459 self.location = location;
460 }
461
462 pub fn mii(&self) -> &Mii {
464 &self.mii
465 }
466
467 pub fn mii_mut(&mut self) -> &mut Mii {
469 &mut self.mii
470 }
471
472 pub fn set_mii(&mut self, mii: Mii) {
475 self.mii_crc16 = crc16(mii.raw_data());
476 self.raw_data_mut()[0x3C..0x86].copy_from_slice(mii.raw_data());
477 self.mii = mii;
478 }
479
480 pub fn mii_crc16(&self) -> u16 {
482 self.mii_crc16
483 }
484
485 pub const fn transmission_adjusted(&self) -> Transmission {
487 match self.transmission_mod {
488 TransmissionMod::Vanilla => self.combo.get_transmission(),
489 TransmissionMod::AllInside => Transmission::Inside,
490 TransmissionMod::AllOutside => Transmission::Outside,
491 TransmissionMod::AllBikeInside if self.combo.vehicle().is_bike() => {
492 Transmission::Inside
493 }
494 TransmissionMod::AllBikeInside => Transmission::Outside,
495 }
496 }
497}
498
499fn write_in_game_time(
502 buf: &mut [u8],
503 byte_offset: usize,
504 bit_offset: usize,
505 in_game_time: &InGameTime,
506) {
507 let mut bit_offset = bit_offset + byte_offset * 8;
508
509 write_bits(
510 buf,
511 bit_offset / 8,
512 bit_offset % 8,
513 7,
514 in_game_time.minutes() as u64,
515 );
516
517 bit_offset += 7;
518
519 write_bits(
520 buf,
521 bit_offset / 8,
522 bit_offset % 8,
523 7,
524 in_game_time.seconds() as u64,
525 );
526
527 bit_offset += 7;
528
529 write_bits(
530 buf,
531 bit_offset / 8,
532 bit_offset % 8,
533 10,
534 in_game_time.milliseconds() as u64,
535 );
536}