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 =
180 Location::find(codes.copy_byte(0), codes.copy_byte(1), Some(Version::ER12));
181
182 if location.is_none() {
183 location = Location::find(codes.copy_byte(0), codes.copy_byte(1), Some(Version::ER11));
184
185 if location.is_none() {
186 location =
187 Location::find(codes.copy_byte(0), codes.copy_byte(1), Some(Version::ER10));
188
189 if location.is_none() {
190 location = Location::find(
191 codes.copy_byte(0),
192 codes.copy_byte(1),
193 Some(Version::Vanilla),
194 );
195 }
196 }
197 }
198
199 let location = location.unwrap_or_default();
200
201 let mii = Mii::new(&header_data[0x3C..0x3C + 0x4A])?;
202
203 let mii_crc16 = ByteHandler::try_from(&header_data[0x86..=0x87])?.copy_word(0);
204
205 Ok(Self {
206 raw_data: header_data.try_into().unwrap(),
207 finish_time,
208 slot_id,
209 combo,
210 date_set,
211 controller,
212 is_compressed,
213 transmission_mod,
214 ghost_type,
215 is_automatic_drift,
216 decompressed_input_data_length,
217 lap_count,
218 lap_split_times,
219 location,
220 mii,
221 mii_crc16,
222 })
223 }
224
225 pub fn verify_mii_crc16(&self) -> bool {
228 crc16(&self.raw_data[0x3C..0x86]) == self.mii_crc16()
229 }
230
231 pub fn fix_mii_crc16(&mut self) {
234 self.mii_crc16 = crc16(&self.raw_data[0x3C..0x86]);
235 self.raw_data[0x86..0x88].copy_from_slice(&self.mii_crc16.to_be_bytes());
236 }
237
238 pub fn raw_data(&self) -> &[u8; 0x88] {
240 &self.raw_data
241 }
242
243 pub fn raw_data_mut(&mut self) -> &mut [u8; 0x88] {
245 &mut self.raw_data
246 }
247
248 pub fn finish_time(&self) -> &InGameTime {
250 &self.finish_time
251 }
252
253 pub fn set_finish_time(&mut self, finish_time: InGameTime) {
255 self.finish_time = finish_time;
256 write_in_game_time(self.raw_data_mut(), 0x04, 0, &finish_time);
257 }
258
259 pub fn slot_id(&self) -> SlotId {
261 self.slot_id
262 }
263
264 pub fn set_slot_id(&mut self, slot_id: SlotId) {
266 self.slot_id = slot_id;
267 write_bits(self.raw_data_mut(), 0x07, 0, 6, u8::from(slot_id) as u64);
268 }
269
270 pub fn combo(&self) -> &Combo {
272 &self.combo
273 }
274
275 pub fn set_combo(&mut self, combo: Combo) {
277 write_bits(
278 self.raw_data_mut(),
279 0x08,
280 0,
281 6,
282 u8::from(combo.vehicle()) as u64,
283 );
284 write_bits(
285 self.raw_data_mut(),
286 0x08,
287 6,
288 6,
289 u8::from(combo.character()) as u64,
290 );
291
292 self.combo = combo;
293 }
294
295 pub fn date_set(&self) -> &Date {
297 &self.date_set
298 }
299
300 pub fn set_date_set(&mut self, date_set: Date) {
302 write_bits(
303 self.raw_data_mut(),
304 0x09,
305 4,
306 7,
307 (date_set.year() - 2000) as u64,
308 );
309 write_bits(self.raw_data_mut(), 0x0A, 3, 4, date_set.month() as u64);
310 write_bits(self.raw_data_mut(), 0x0A, 7, 5, date_set.day() as u64);
311
312 self.date_set = date_set;
313 }
314
315 pub fn controller(&self) -> Controller {
317 self.controller
318 }
319
320 pub fn set_controller(&mut self, controller: Controller) {
322 self.controller = controller;
323 write_bits(self.raw_data_mut(), 0x0B, 4, 4, u8::from(controller) as u64);
324 }
325
326 pub fn is_compressed(&self) -> bool {
328 self.is_compressed
329 }
330
331 pub(crate) fn set_compressed(&mut self, is_compressed: bool) {
336 self.is_compressed = is_compressed;
337 write_bits(self.raw_data_mut(), 0x0C, 4, 1, is_compressed as u64);
338 }
339
340 pub fn transmission_mod(&self) -> TransmissionMod {
342 self.transmission_mod
343 }
344
345 pub fn set_transmission_mod(&mut self, transmission_mod: TransmissionMod) {
347 self.transmission_mod = transmission_mod;
348 write_bits(
349 self.raw_data_mut(),
350 0x0C,
351 5,
352 2,
353 u8::from(transmission_mod) as u64,
354 );
355 }
356
357 pub fn ghost_type(&self) -> GhostType {
359 self.ghost_type
360 }
361
362 pub fn set_ghost_type(&mut self, ghost_type: GhostType) {
364 self.ghost_type = ghost_type;
365 write_bits(self.raw_data_mut(), 0x0C, 7, 7, u8::from(ghost_type) as u64);
366 }
367
368 pub fn is_automatic_drift(&self) -> bool {
370 self.is_automatic_drift
371 }
372
373 pub fn set_automatic_drift(&mut self, is_automatic_drift: bool) {
375 self.is_automatic_drift = is_automatic_drift;
376 write_bits(self.raw_data_mut(), 0x0D, 6, 1, is_automatic_drift as u64);
377 }
378
379 pub fn decompressed_input_data_length(&self) -> u16 {
381 self.decompressed_input_data_length
382 }
383
384 pub fn lap_count(&self) -> u8 {
386 self.lap_count
387 }
388
389 pub fn lap_split_times(&self) -> &[InGameTime] {
391 &self.lap_split_times[0..self.lap_count as usize]
392 }
393
394 pub fn lap_split_time(&self, idx: usize) -> Result<InGameTime, HeaderError> {
401 if idx >= self.lap_count as usize {
402 return Err(HeaderError::LapSplitIndexError);
403 }
404 Ok(self.lap_split_times[idx])
405 }
406
407 pub fn set_lap_split_time(&mut self, idx: usize, lap_split_time: InGameTime) {
411 if idx >= self.lap_count as usize {
412 return;
413 }
414 self.lap_split_times[idx] = lap_split_time;
415
416 write_bits(
417 self.raw_data_mut(),
418 0x11 + idx * 0x03,
419 0,
420 7,
421 lap_split_time.minutes() as u64,
422 );
423 write_bits(
424 self.raw_data_mut(),
425 0x11 + idx * 0x03,
426 7,
427 7,
428 lap_split_time.seconds() as u64,
429 );
430 write_bits(
431 self.raw_data_mut(),
432 0x12 + idx * 0x03,
433 6,
434 10,
435 lap_split_time.milliseconds() as u64,
436 );
437 }
438
439 pub fn location(&self) -> &Location {
441 &self.location
442 }
443
444 pub fn set_location(&mut self, location: Location) {
449 write_bits(
450 self.raw_data_mut(),
451 0x34,
452 0,
453 8,
454 u8::from(location.country()) as u64,
455 );
456
457 let subregion_id = if location.country() != Country::NotSet {
458 u8::from(location.subregion()) as u64
459 } else {
460 0xFF
461 };
462
463 write_bits(self.raw_data_mut(), 0x35, 0, 8, subregion_id);
464
465 self.location = location;
466 }
467
468 pub fn mii(&self) -> &Mii {
470 &self.mii
471 }
472
473 pub fn mii_mut(&mut self) -> &mut Mii {
475 &mut self.mii
476 }
477
478 pub fn set_mii(&mut self, mii: Mii) {
481 self.mii_crc16 = crc16(mii.raw_data());
482 self.raw_data_mut()[0x3C..0x86].copy_from_slice(mii.raw_data());
483 self.mii = mii;
484 }
485
486 pub fn mii_crc16(&self) -> u16 {
488 self.mii_crc16
489 }
490
491 pub const fn transmission_adjusted(&self) -> Transmission {
493 match self.transmission_mod {
494 TransmissionMod::Vanilla => self.combo.get_transmission(),
495 TransmissionMod::AllInside => Transmission::Inside,
496 TransmissionMod::AllOutside => Transmission::Outside,
497 TransmissionMod::AllBikeInside if self.combo.vehicle().is_bike() => {
498 Transmission::Inside
499 }
500 TransmissionMod::AllBikeInside => Transmission::Outside,
501 }
502 }
503}
504
505fn write_in_game_time(
508 buf: &mut [u8],
509 byte_offset: usize,
510 bit_offset: usize,
511 in_game_time: &InGameTime,
512) {
513 let mut bit_offset = bit_offset + byte_offset * 8;
514
515 write_bits(
516 buf,
517 bit_offset / 8,
518 bit_offset % 8,
519 7,
520 in_game_time.minutes() as u64,
521 );
522
523 bit_offset += 7;
524
525 write_bits(
526 buf,
527 bit_offset / 8,
528 bit_offset % 8,
529 7,
530 in_game_time.seconds() as u64,
531 );
532
533 bit_offset += 7;
534
535 write_bits(
536 buf,
537 bit_offset / 8,
538 bit_offset % 8,
539 10,
540 in_game_time.milliseconds() as u64,
541 );
542}