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 location = Location::find(codes.copy_byte(0), codes.copy_byte(1), Some(Version::ER12))
180 .unwrap_or_default();
181
182 let mii = Mii::new(&header_data[0x3C..0x3C + 0x4A])?;
183
184 let mii_crc16 = ByteHandler::try_from(&header_data[0x86..=0x87])?.copy_word(0);
185
186 Ok(Self {
187 raw_data: header_data.try_into().unwrap(),
188 finish_time,
189 slot_id,
190 combo,
191 date_set,
192 controller,
193 is_compressed,
194 transmission_mod,
195 ghost_type,
196 is_automatic_drift,
197 decompressed_input_data_length,
198 lap_count,
199 lap_split_times,
200 location,
201 mii,
202 mii_crc16,
203 })
204 }
205
206 pub fn verify_mii_crc16(&self) -> bool {
209 crc16(&self.raw_data[0x3C..0x86]) == self.mii_crc16()
210 }
211
212 pub fn fix_mii_crc16(&mut self) {
215 self.mii_crc16 = crc16(&self.raw_data[0x3C..0x86]);
216 self.raw_data[0x86..0x88].copy_from_slice(&self.mii_crc16.to_be_bytes());
217 }
218
219 pub fn raw_data(&self) -> &[u8; 0x88] {
221 &self.raw_data
222 }
223
224 pub fn raw_data_mut(&mut self) -> &mut [u8; 0x88] {
226 &mut self.raw_data
227 }
228
229 pub fn finish_time(&self) -> &InGameTime {
231 &self.finish_time
232 }
233
234 pub fn set_finish_time(&mut self, finish_time: InGameTime) {
236 self.finish_time = finish_time;
237 write_in_game_time(self.raw_data_mut(), 0x04, 0, &finish_time);
238 }
239
240 pub fn slot_id(&self) -> SlotId {
242 self.slot_id
243 }
244
245 pub fn set_slot_id(&mut self, slot_id: SlotId) {
247 self.slot_id = slot_id;
248 write_bits(self.raw_data_mut(), 0x07, 0, 6, u8::from(slot_id) as u64);
249 }
250
251 pub fn combo(&self) -> &Combo {
253 &self.combo
254 }
255
256 pub fn set_combo(&mut self, combo: Combo) {
258 write_bits(
259 self.raw_data_mut(),
260 0x08,
261 0,
262 6,
263 u8::from(combo.vehicle()) as u64,
264 );
265 write_bits(
266 self.raw_data_mut(),
267 0x08,
268 6,
269 6,
270 u8::from(combo.character()) as u64,
271 );
272
273 self.combo = combo;
274 }
275
276 pub fn date_set(&self) -> &Date {
278 &self.date_set
279 }
280
281 pub fn set_date_set(&mut self, date_set: Date) {
283 write_bits(
284 self.raw_data_mut(),
285 0x09,
286 4,
287 7,
288 (date_set.year() - 2000) as u64,
289 );
290 write_bits(self.raw_data_mut(), 0x0A, 3, 4, date_set.month() as u64);
291 write_bits(self.raw_data_mut(), 0x0A, 7, 5, date_set.day() as u64);
292
293 self.date_set = date_set;
294 }
295
296 pub fn controller(&self) -> Controller {
298 self.controller
299 }
300
301 pub fn set_controller(&mut self, controller: Controller) {
303 self.controller = controller;
304 write_bits(self.raw_data_mut(), 0x0B, 4, 4, u8::from(controller) as u64);
305 }
306
307 pub fn is_compressed(&self) -> bool {
309 self.is_compressed
310 }
311
312 pub(crate) fn set_compressed(&mut self, is_compressed: bool) {
317 self.is_compressed = is_compressed;
318 write_bits(self.raw_data_mut(), 0x0C, 4, 1, is_compressed as u64);
319 }
320
321 pub fn transmission_mod(&self) -> TransmissionMod {
323 self.transmission_mod
324 }
325
326 pub fn set_transmission_mod(&mut self, transmission_mod: TransmissionMod) {
328 self.transmission_mod = transmission_mod;
329 write_bits(
330 self.raw_data_mut(),
331 0x0C,
332 5,
333 2,
334 u8::from(transmission_mod) as u64,
335 );
336 }
337
338 pub fn ghost_type(&self) -> GhostType {
340 self.ghost_type
341 }
342
343 pub fn set_ghost_type(&mut self, ghost_type: GhostType) {
345 self.ghost_type = ghost_type;
346 write_bits(self.raw_data_mut(), 0x0C, 7, 7, u8::from(ghost_type) as u64);
347 }
348
349 pub fn is_automatic_drift(&self) -> bool {
351 self.is_automatic_drift
352 }
353
354 pub fn set_automatic_drift(&mut self, is_automatic_drift: bool) {
356 self.is_automatic_drift = is_automatic_drift;
357 write_bits(self.raw_data_mut(), 0x0D, 6, 1, is_automatic_drift as u64);
358 }
359
360 pub fn decompressed_input_data_length(&self) -> u16 {
362 self.decompressed_input_data_length
363 }
364
365 pub fn lap_count(&self) -> u8 {
367 self.lap_count
368 }
369
370 pub fn lap_split_times(&self) -> &[InGameTime] {
372 &self.lap_split_times[0..self.lap_count as usize]
373 }
374
375 pub fn lap_split_time(&self, idx: usize) -> Result<InGameTime, HeaderError> {
382 if idx >= self.lap_count as usize {
383 return Err(HeaderError::LapSplitIndexError);
384 }
385 Ok(self.lap_split_times[idx])
386 }
387
388 pub fn set_lap_split_time(&mut self, idx: usize, lap_split_time: InGameTime) {
392 if idx >= self.lap_count as usize {
393 return;
394 }
395 self.lap_split_times[idx] = lap_split_time;
396
397 write_bits(
398 self.raw_data_mut(),
399 0x11 + idx * 0x03,
400 0,
401 7,
402 lap_split_time.minutes() as u64,
403 );
404 write_bits(
405 self.raw_data_mut(),
406 0x11 + idx * 0x03,
407 7,
408 7,
409 lap_split_time.seconds() as u64,
410 );
411 write_bits(
412 self.raw_data_mut(),
413 0x12 + idx * 0x03,
414 6,
415 10,
416 lap_split_time.milliseconds() as u64,
417 );
418 }
419
420 pub fn location(&self) -> &Location {
422 &self.location
423 }
424
425 pub fn set_location(&mut self, location: Location) {
430 write_bits(
431 self.raw_data_mut(),
432 0x34,
433 0,
434 8,
435 u8::from(location.country()) as u64,
436 );
437
438 let subregion_id = if location.country() != Country::NotSet {
439 u8::from(location.subregion()) as u64
440 } else {
441 0xFF
442 };
443
444 write_bits(self.raw_data_mut(), 0x35, 0, 8, subregion_id);
445
446 self.location = location;
447 }
448
449 pub fn mii(&self) -> &Mii {
451 &self.mii
452 }
453
454 pub fn mii_mut(&mut self) -> &mut Mii {
456 &mut self.mii
457 }
458
459 pub fn set_mii(&mut self, mii: Mii) {
462 self.mii_crc16 = crc16(mii.raw_data());
463 self.raw_data_mut()[0x3C..0x86].copy_from_slice(mii.raw_data());
464 self.mii = mii;
465 }
466
467 pub fn mii_crc16(&self) -> u16 {
469 self.mii_crc16
470 }
471
472 pub const fn transmission_adjusted(&self) -> Transmission {
474 match self.transmission_mod {
475 TransmissionMod::Vanilla => self.combo.get_transmission(),
476 TransmissionMod::AllInside => Transmission::Inside,
477 TransmissionMod::AllOutside => Transmission::Outside,
478 TransmissionMod::AllBikeInside if self.combo.vehicle().is_bike() => {
479 Transmission::Inside
480 }
481 TransmissionMod::AllBikeInside => Transmission::Outside,
482 }
483 }
484}
485
486fn write_in_game_time(
489 buf: &mut [u8],
490 byte_offset: usize,
491 bit_offset: usize,
492 in_game_time: &InGameTime,
493) {
494 let mut bit_offset = bit_offset + byte_offset * 8;
495
496 write_bits(
497 buf,
498 bit_offset / 8,
499 bit_offset % 8,
500 7,
501 in_game_time.minutes() as u64,
502 );
503
504 bit_offset += 7;
505
506 write_bits(
507 buf,
508 bit_offset / 8,
509 bit_offset % 8,
510 7,
511 in_game_time.seconds() as u64,
512 );
513
514 bit_offset += 7;
515
516 write_bits(
517 buf,
518 bit_offset / 8,
519 bit_offset % 8,
520 10,
521 in_game_time.milliseconds() as u64,
522 );
523}