1use std::{borrow::Cow, io, mem::replace, ops::Range, str::Utf8Error};
2
3use serde::{Deserialize, Serialize};
4use snafu::{Backtrace, Snafu};
5
6use super::{
7 raw::{
8 AutoloadInfo, AutoloadInfoEntry, AutoloadKind, BuildInfo, HmacSha1Signature, HmacSha1SignatureError,
9 RawAutoloadInfoError, RawBuildInfoError, NITROCODE_BYTES,
10 },
11 Autoload, OverlayTable,
12};
13use crate::{
14 compress::lz77::{Lz77, Lz77DecompressError},
15 crc::CRC_16_MODBUS,
16 crypto::blowfish::{Blowfish, BlowfishError, BlowfishKey, BlowfishLevel},
17 rom::LibraryEntry,
18};
19
20#[derive(Clone)]
22pub struct Arm9<'a> {
23 data: Cow<'a, [u8]>,
24 offsets: Arm9Offsets,
25 originally_compressed: bool,
26 originally_encrypted: bool,
27}
28
29#[derive(Serialize, Deserialize, Clone, Copy)]
31pub struct Arm9Offsets {
32 pub base_address: u32,
34 pub entry_function: u32,
36 pub build_info: u32,
38 pub autoload_callback: u32,
40 pub overlay_signatures: u32,
42}
43
44const SECURE_AREA_ID: [u8; 8] = [0xff, 0xde, 0xff, 0xe7, 0xff, 0xde, 0xff, 0xe7];
45const SECURE_AREA_ENCRY_OBJ: &[u8] = "encryObj".as_bytes();
46
47const LZ77: Lz77 = Lz77 {};
48
49const COMPRESSION_START: usize = 0x4000;
50
51#[derive(Debug, Snafu)]
53pub enum Arm9Error {
54 #[snafu(display("expected {expected:#x} bytes for secure area but had only {actual:#x}:\n{backtrace}"))]
56 DataTooSmall {
57 expected: usize,
59 actual: usize,
61 backtrace: Backtrace,
63 },
64 #[snafu(transparent)]
66 Blowfish {
67 source: BlowfishError,
69 },
70 #[snafu(display("invalid encryption, 'encryObj' not found"))]
72 NotEncryObj {
73 backtrace: Backtrace,
75 },
76 #[snafu(transparent)]
78 RawBuildInfo {
79 source: RawBuildInfoError,
81 },
82 #[snafu(transparent)]
84 Lz77Decompress {
85 source: Lz77DecompressError,
87 },
88 #[snafu(transparent)]
90 Io {
91 source: io::Error,
93 },
94}
95
96#[derive(Debug, Snafu)]
98pub enum Arm9AutoloadError {
99 #[snafu(transparent)]
101 RawBuildInfo {
102 source: RawBuildInfoError,
104 },
105 #[snafu(transparent)]
107 RawAutoloadInfo {
108 source: RawAutoloadInfoError,
110 },
111 #[snafu(display("ARM9 program must be decompressed before accessing autoload blocks:\n{backtrace}"))]
113 Compressed {
114 backtrace: Backtrace,
116 },
117 #[snafu(display("autoload block {kind} could not be found:\n{backtrace}"))]
119 NotFound {
120 kind: AutoloadKind,
122 backtrace: Backtrace,
124 },
125}
126
127#[derive(Debug, Snafu)]
129pub enum Arm9OverlaySignaturesError {
130 #[snafu(transparent)]
132 HmacSha1Signature {
133 source: HmacSha1SignatureError,
135 },
136 #[snafu(transparent)]
138 RawBuildInfo {
139 source: RawBuildInfoError,
141 },
142 #[snafu(display("ARM9 program must be decompressed before accessing overlay signatures:\n{backtrace}"))]
144 OverlaySignaturesCompressed {
145 backtrace: Backtrace,
147 },
148}
149
150#[derive(Debug, Snafu)]
152pub enum Arm9HmacSha1KeyError {
153 #[snafu(transparent)]
155 RawBuildInfo {
156 source: RawBuildInfoError,
158 },
159 #[snafu(display("ARM9 program must be decompressed before accessing HMAC-SHA1 key:\n{backtrace}"))]
161 HmacSha1KeyCompressed {
162 backtrace: Backtrace,
164 },
165}
166
167pub struct Arm9WithTcmsOptions {
169 pub originally_compressed: bool,
171 pub originally_encrypted: bool,
173}
174
175impl<'a> Arm9<'a> {
176 pub fn new<T: Into<Cow<'a, [u8]>>>(data: T, offsets: Arm9Offsets) -> Result<Self, RawBuildInfoError> {
178 let mut arm9 = Arm9 { data: data.into(), offsets, originally_compressed: false, originally_encrypted: false };
179 arm9.originally_compressed = arm9.is_compressed()?;
180 arm9.originally_encrypted = arm9.is_encrypted();
181 Ok(arm9)
182 }
183
184 pub fn with_two_tcms(
190 mut data: Vec<u8>,
191 itcm: Autoload,
192 dtcm: Autoload,
193 offsets: Arm9Offsets,
194 options: Arm9WithTcmsOptions,
195 ) -> Result<Self, RawBuildInfoError> {
196 let autoload_infos = [*itcm.info().entry(), *dtcm.info().entry()];
197
198 let autoload_blocks = data.len() as u32 + offsets.base_address;
199 data.extend(itcm.into_data().iter());
200 data.extend(dtcm.into_data().iter());
201 let autoload_infos_start = data.len() as u32 + offsets.base_address;
202 data.extend(bytemuck::bytes_of(&autoload_infos));
203 let autoload_infos_end = data.len() as u32 + offsets.base_address;
204
205 let Arm9WithTcmsOptions { originally_compressed, originally_encrypted } = options;
206 let mut arm9 = Self { data: data.into(), offsets, originally_compressed, originally_encrypted };
207
208 let build_info = arm9.build_info_mut()?;
209 build_info.autoload_blocks = autoload_blocks;
210 build_info.autoload_infos_start = autoload_infos_start;
211 build_info.autoload_infos_end = autoload_infos_end;
212
213 Ok(arm9)
214 }
215
216 pub fn with_autoloads(
222 mut data: Vec<u8>,
223 autoloads: &[Autoload],
224 offsets: Arm9Offsets,
225 options: Arm9WithTcmsOptions,
226 ) -> Result<Self, RawBuildInfoError> {
227 let autoload_blocks = data.len() as u32 + offsets.base_address;
228
229 for autoload in autoloads {
230 data.extend(autoload.full_data());
231 }
232
233 let autoload_infos_start = data.len() as u32 + offsets.base_address;
234 for autoload in autoloads {
235 data.extend(bytemuck::bytes_of(autoload.info().entry()));
236 }
237 let autoload_infos_end = data.len() as u32 + offsets.base_address;
238
239 let Arm9WithTcmsOptions { originally_compressed, originally_encrypted } = options;
240 let mut arm9 = Self { data: data.into(), offsets, originally_compressed, originally_encrypted };
241
242 let build_info = arm9.build_info_mut()?;
243 build_info.autoload_blocks = autoload_blocks;
244 build_info.autoload_infos_start = autoload_infos_start;
245 build_info.autoload_infos_end = autoload_infos_end;
246
247 Ok(arm9)
248 }
249
250 pub fn is_encrypted(&self) -> bool {
253 self.data.len() < 8 || self.data[0..8] != SECURE_AREA_ID
254 }
255
256 pub fn decrypt(&mut self, key: &BlowfishKey, gamecode: u32) -> Result<(), Arm9Error> {
263 if !self.is_encrypted() {
264 return Ok(());
265 }
266
267 if self.data.len() < 0x4000 {
268 DataTooSmallSnafu { expected: 0x4000usize, actual: self.data.len() }.fail()?;
269 }
270
271 let mut secure_area = [0u8; 0x4000];
272 secure_area.clone_from_slice(&self.data[0..0x4000]);
273
274 let blowfish = Blowfish::new(key, gamecode, BlowfishLevel::Level2);
275 blowfish.decrypt(&mut secure_area[0..8])?;
276
277 let blowfish = Blowfish::new(key, gamecode, BlowfishLevel::Level3);
278 blowfish.decrypt(&mut secure_area[0..0x800])?;
279
280 if &secure_area[0..8] != SECURE_AREA_ENCRY_OBJ {
281 NotEncryObjSnafu {}.fail()?;
282 }
283
284 secure_area[0..8].copy_from_slice(&SECURE_AREA_ID);
285 self.data.to_mut()[0..0x4000].copy_from_slice(&secure_area);
286 Ok(())
287 }
288
289 pub fn encrypt(&mut self, key: &BlowfishKey, gamecode: u32) -> Result<(), Arm9Error> {
296 if self.is_encrypted() {
297 return Ok(());
298 }
299
300 if self.data.len() < 0x4000 {
301 DataTooSmallSnafu { expected: 0x4000usize, actual: self.data.len() }.fail()?;
302 }
303
304 if self.data[0..8] != SECURE_AREA_ID {
305 NotEncryObjSnafu {}.fail()?;
306 }
307
308 let secure_area = self.encrypted_secure_area(key, gamecode);
309 self.data.to_mut()[0..0x4000].copy_from_slice(&secure_area);
310 Ok(())
311 }
312
313 pub fn encrypted_secure_area(&self, key: &BlowfishKey, gamecode: u32) -> [u8; 0x4000] {
315 let mut secure_area = [0u8; 0x4000];
316 secure_area.copy_from_slice(&self.data[0..0x4000]);
317 if self.is_encrypted() {
318 return secure_area;
319 }
320
321 secure_area[0..8].copy_from_slice(SECURE_AREA_ENCRY_OBJ);
322
323 let blowfish = Blowfish::new(key, gamecode, BlowfishLevel::Level3);
324 blowfish.encrypt(&mut secure_area[0..0x800]).unwrap();
325
326 let blowfish = Blowfish::new(key, gamecode, BlowfishLevel::Level2);
327 blowfish.encrypt(&mut secure_area[0..8]).unwrap();
328
329 secure_area
330 }
331
332 pub fn secure_area_crc(&self, key: &BlowfishKey, gamecode: u32) -> u16 {
334 let secure_area = self.encrypted_secure_area(key, gamecode);
335 CRC_16_MODBUS.checksum(&secure_area)
336 }
337
338 pub fn build_info(&self) -> Result<&BuildInfo, RawBuildInfoError> {
344 BuildInfo::borrow_from_slice(&self.data[self.offsets.build_info as usize..])
345 }
346
347 pub fn build_info_mut(&mut self) -> Result<&mut BuildInfo, RawBuildInfoError> {
353 BuildInfo::borrow_from_slice_mut(&mut self.data.to_mut()[self.offsets.build_info as usize..])
354 }
355
356 pub fn libraries(&self) -> Result<Box<[LibraryEntry<'_>]>, Utf8Error> {
362 let libraries_start = self.offsets.build_info as usize + size_of::<BuildInfo>();
363 let mut address = self.base_address() + libraries_start as u32;
364 let mut data = &self.data[libraries_start..];
365
366 let mut libraries = Vec::new();
367
368 'outer: while data[0] == b'[' {
369 let Some((end_pos, _)) = data.iter().enumerate().find(|(_, c)| **c == b']') else {
370 break;
371 };
372 let version_string = str::from_utf8(&data[..end_pos + 1])?;
373
374 libraries.push(LibraryEntry::new(address, version_string));
375
376 address += end_pos as u32 + 1;
377 data = &data[end_pos + 1..];
378 loop {
379 match data[0] {
380 b'\0' => {
381 address += 1;
382 data = &data[1..]
383 }
384 b'[' => break,
385 _ => break 'outer,
386 }
387 }
388 }
389
390 Ok(libraries.into_boxed_slice())
391 }
392
393 pub fn is_compressed(&self) -> Result<bool, RawBuildInfoError> {
400 Ok(self.build_info()?.is_compressed())
401 }
402
403 pub fn decompress(&mut self) -> Result<(), Arm9Error> {
409 if !self.is_compressed()? {
410 return Ok(());
411 }
412
413 let data: Cow<[u8]> = LZ77.decompress(&self.data)?.into_vec().into();
414 let old_data = replace(&mut self.data, data);
415 let build_info = match self.build_info_mut() {
416 Ok(build_info) => build_info,
417 Err(e) => {
418 self.data = old_data;
419 return Err(e.into());
420 }
421 };
422 build_info.compressed_code_end = 0;
423 Ok(())
424 }
425
426 pub fn compress(&mut self) -> Result<(), Arm9Error> {
432 if self.is_compressed()? {
433 return Ok(());
434 }
435
436 let data: Cow<[u8]> = LZ77.compress(&self.data, COMPRESSION_START)?.into_vec().into();
437 let length = data.len();
438 let old_data = replace(&mut self.data, data);
439 let base_address = self.base_address();
440 let build_info = match self.build_info_mut() {
441 Ok(build_info) => build_info,
442 Err(e) => {
443 self.data = old_data;
444 return Err(e.into());
445 }
446 };
447 build_info.compressed_code_end = base_address + length as u32;
448 Ok(())
449 }
450
451 fn get_autoload_info_entries(&self, build_info: &BuildInfo) -> Result<&[AutoloadInfoEntry], Arm9AutoloadError> {
452 let start = (build_info.autoload_infos_start - self.base_address()) as usize;
453 let end = (build_info.autoload_infos_end - self.base_address()) as usize;
454 let autoload_info = AutoloadInfoEntry::borrow_from_slice(&self.data[start..end])?;
455 Ok(autoload_info)
456 }
457
458 pub fn autoload_infos(&self) -> Result<Vec<AutoloadInfo>, Arm9AutoloadError> {
465 let build_info: &BuildInfo = self.build_info()?;
466 if build_info.is_compressed() {
467 CompressedSnafu {}.fail()?;
468 }
469 Ok(self
470 .get_autoload_info_entries(build_info)?
471 .iter()
472 .enumerate()
473 .map(|(index, entry)| AutoloadInfo::new(*entry, index as u32))
474 .collect())
475 }
476
477 pub fn autoloads(&self) -> Result<Box<[Autoload<'_>]>, Arm9AutoloadError> {
484 let build_info = self.build_info()?;
485 if build_info.is_compressed() {
486 CompressedSnafu {}.fail()?;
487 }
488 let autoload_infos = self.autoload_infos()?;
489
490 let mut autoloads = vec![];
491 let mut load_offset = build_info.autoload_blocks - self.base_address();
492 for autoload_info in autoload_infos {
493 let start = load_offset as usize;
494 let end = start + autoload_info.code_size() as usize;
495 let data = &self.data[start..end];
496 autoloads.push(Autoload::new(data, autoload_info));
497 load_offset += autoload_info.code_size();
498 }
499
500 Ok(autoloads.into_boxed_slice())
501 }
502
503 pub fn num_unknown_autoloads(&self) -> Result<usize, Arm9AutoloadError> {
509 Ok(self.autoloads()?.iter().filter(|a| matches!(a.kind(), AutoloadKind::Unknown(_))).count())
510 }
511
512 pub fn hmac_sha1_key(&self) -> Result<Option<[u8; 64]>, Arm9HmacSha1KeyError> {
514 if self.is_compressed()? {
515 HmacSha1KeyCompressedSnafu {}.fail()?
516 }
517
518 let Some((i, _)) = self.data.chunks(4).enumerate().filter(|(_, chunk)| *chunk == NITROCODE_BYTES).nth(1) else {
520 return Ok(None);
521 };
522 let start = i * 4;
523 let end = start + 64;
524 if end > self.data.len() {
525 return Ok(None);
526 }
527 let mut key = [0u8; 64];
528 key.copy_from_slice(&self.data[start..end]);
529 Ok(Some(key))
530 }
531
532 fn overlay_table_signature_range(&self) -> Result<Option<Range<usize>>, Arm9OverlaySignaturesError> {
533 let overlay_signatures_offset = self.overlay_signatures_offset() as usize;
534 if overlay_signatures_offset == 0 {
535 return Ok(None);
536 }
537
538 if self.is_compressed()? {
539 OverlaySignaturesCompressedSnafu {}.fail()?;
540 }
541
542 let start = overlay_signatures_offset - size_of::<HmacSha1Signature>();
544 let end = overlay_signatures_offset;
545 if end > self.data.len() {
546 return Ok(None);
547 }
548 Ok(Some(start..end))
549 }
550
551 pub fn overlay_table_signature(&self) -> Result<Option<&HmacSha1Signature>, Arm9OverlaySignaturesError> {
557 let Some(range) = self.overlay_table_signature_range()? else {
558 return Ok(None);
559 };
560 let data = &self.data[range];
561
562 let signature = HmacSha1Signature::borrow_from_slice(data)?;
563 Ok(Some(signature.first().unwrap()))
564 }
565
566 pub fn overlay_table_signature_mut(&mut self) -> Result<Option<&mut HmacSha1Signature>, Arm9OverlaySignaturesError> {
573 let Some(range) = self.overlay_table_signature_range()? else {
574 return Ok(None);
575 };
576 let data = &mut self.data.to_mut()[range];
577
578 let signature = HmacSha1Signature::borrow_from_slice_mut(data)?;
579 Ok(Some(signature.first_mut().unwrap()))
580 }
581
582 fn overlay_signatures_range(&self, num_overlays: usize) -> Result<Option<Range<usize>>, Arm9OverlaySignaturesError> {
583 let start = self.overlay_signatures_offset() as usize;
584 if start == 0 {
585 return Ok(None);
586 }
587
588 if self.is_compressed()? {
589 OverlaySignaturesCompressedSnafu {}.fail()?;
590 }
591
592 let end = start + size_of::<HmacSha1Signature>() * num_overlays;
593 if end > self.data.len() {
594 return Ok(None);
595 }
596 Ok(Some(start..end))
597 }
598
599 pub fn overlay_signatures(&self, num_overlays: usize) -> Result<Option<&[HmacSha1Signature]>, Arm9OverlaySignaturesError> {
606 let Some(range) = self.overlay_signatures_range(num_overlays)? else {
607 return Ok(None);
608 };
609 let data = &self.data[range];
610 Ok(Some(HmacSha1Signature::borrow_from_slice(data)?))
611 }
612
613 pub fn overlay_signatures_mut(
620 &mut self,
621 num_overlays: usize,
622 ) -> Result<Option<&mut [HmacSha1Signature]>, Arm9OverlaySignaturesError> {
623 let Some(range) = self.overlay_signatures_range(num_overlays)? else {
624 return Ok(None);
625 };
626 let data = &mut self.data.to_mut()[range];
627 Ok(Some(HmacSha1Signature::borrow_from_slice_mut(data)?))
628 }
629
630 pub fn code(&self) -> Result<&[u8], RawBuildInfoError> {
636 let build_info = self.build_info()?;
637 Ok(&self.data[..(build_info.bss_start - self.base_address()) as usize])
638 }
639
640 pub fn full_data(&self) -> &[u8] {
642 &self.data
643 }
644
645 pub fn base_address(&self) -> u32 {
647 self.offsets.base_address
648 }
649
650 pub fn end_address(&self) -> Result<u32, RawBuildInfoError> {
652 let build_info = self.build_info()?;
653 Ok(build_info.bss_end)
654 }
655
656 pub fn entry_function(&self) -> u32 {
658 self.offsets.entry_function
659 }
660
661 pub fn build_info_offset(&self) -> u32 {
663 self.offsets.build_info
664 }
665
666 pub fn autoload_callback(&self) -> u32 {
668 self.offsets.autoload_callback
669 }
670
671 pub fn overlay_signatures_offset(&self) -> u32 {
673 self.offsets.overlay_signatures
674 }
675
676 pub fn bss(&self) -> Result<Range<u32>, RawBuildInfoError> {
682 let build_info = self.build_info()?;
683 Ok(build_info.bss_start..build_info.bss_end)
684 }
685
686 pub fn offsets(&self) -> &Arm9Offsets {
688 &self.offsets
689 }
690
691 pub fn originally_compressed(&self) -> bool {
693 self.originally_compressed
694 }
695
696 pub fn originally_encrypted(&self) -> bool {
698 self.originally_encrypted
699 }
700
701 pub(crate) fn update_overlay_signatures(
702 &mut self,
703 arm9_overlay_table: &OverlayTable,
704 ) -> Result<(), Arm9OverlaySignaturesError> {
705 let arm9_overlays = arm9_overlay_table.overlays();
706 let Some(signatures) = self.overlay_signatures_mut(arm9_overlays.len())? else {
707 return Ok(());
708 };
709 for overlay in arm9_overlays {
710 if let Some(signature) = overlay.signature() {
711 signatures[overlay.id() as usize] = signature;
712 }
713 }
714
715 if let Some(signature) = arm9_overlay_table.signature() {
716 let Some(table_signature) = self.overlay_table_signature_mut()? else {
717 return Ok(());
718 };
719 *table_signature = signature;
720 }
721
722 Ok(())
723 }
724}
725
726impl AsRef<[u8]> for Arm9<'_> {
727 fn as_ref(&self) -> &[u8] {
728 &self.data
729 }
730}