1use std::{borrow::Cow, io, mem::replace, ops::Range};
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};
18
19#[derive(Clone)]
21pub struct Arm9<'a> {
22 data: Cow<'a, [u8]>,
23 offsets: Arm9Offsets,
24 originally_compressed: bool,
25 originally_encrypted: bool,
26}
27
28#[derive(Serialize, Deserialize, Clone, Copy)]
30pub struct Arm9Offsets {
31 pub base_address: u32,
33 pub entry_function: u32,
35 pub build_info: u32,
37 pub autoload_callback: u32,
39 pub overlay_signatures: u32,
41}
42
43const SECURE_AREA_ID: [u8; 8] = [0xff, 0xde, 0xff, 0xe7, 0xff, 0xde, 0xff, 0xe7];
44const SECURE_AREA_ENCRY_OBJ: &[u8] = "encryObj".as_bytes();
45
46const LZ77: Lz77 = Lz77 {};
47
48const COMPRESSION_START: usize = 0x4000;
49
50#[derive(Debug, Snafu)]
52pub enum Arm9Error {
53 #[snafu(display("expected {expected:#x} bytes for secure area but had only {actual:#x}:\n{backtrace}"))]
55 DataTooSmall {
56 expected: usize,
58 actual: usize,
60 backtrace: Backtrace,
62 },
63 #[snafu(transparent)]
65 Blowfish {
66 source: BlowfishError,
68 },
69 #[snafu(display("invalid encryption, 'encryObj' not found"))]
71 NotEncryObj {
72 backtrace: Backtrace,
74 },
75 #[snafu(transparent)]
77 RawBuildInfo {
78 source: RawBuildInfoError,
80 },
81 #[snafu(transparent)]
83 Lz77Decompress {
84 source: Lz77DecompressError,
86 },
87 #[snafu(transparent)]
89 Io {
90 source: io::Error,
92 },
93}
94
95#[derive(Debug, Snafu)]
97pub enum Arm9AutoloadError {
98 #[snafu(transparent)]
100 RawBuildInfo {
101 source: RawBuildInfoError,
103 },
104 #[snafu(transparent)]
106 RawAutoloadInfo {
107 source: RawAutoloadInfoError,
109 },
110 #[snafu(display("ARM9 program must be decompressed before accessing autoload blocks:\n{backtrace}"))]
112 Compressed {
113 backtrace: Backtrace,
115 },
116 #[snafu(display("autoload block {kind} could not be found:\n{backtrace}"))]
118 NotFound {
119 kind: AutoloadKind,
121 backtrace: Backtrace,
123 },
124}
125
126#[derive(Debug, Snafu)]
128pub enum Arm9OverlaySignaturesError {
129 #[snafu(transparent)]
131 HmacSha1Signature {
132 source: HmacSha1SignatureError,
134 },
135 #[snafu(transparent)]
137 RawBuildInfo {
138 source: RawBuildInfoError,
140 },
141 #[snafu(display("ARM9 program must be decompressed before accessing overlay signatures:\n{backtrace}"))]
143 OverlaySignaturesCompressed {
144 backtrace: Backtrace,
146 },
147}
148
149#[derive(Debug, Snafu)]
151pub enum Arm9HmacSha1KeyError {
152 #[snafu(transparent)]
154 RawBuildInfo {
155 source: RawBuildInfoError,
157 },
158 #[snafu(display("ARM9 program must be decompressed before accessing HMAC-SHA1 key:\n{backtrace}"))]
160 HmacSha1KeyCompressed {
161 backtrace: Backtrace,
163 },
164}
165
166pub struct Arm9WithTcmsOptions {
168 pub originally_compressed: bool,
170 pub originally_encrypted: bool,
172}
173
174impl<'a> Arm9<'a> {
175 pub fn new<T: Into<Cow<'a, [u8]>>>(data: T, offsets: Arm9Offsets) -> Result<Self, RawBuildInfoError> {
177 let mut arm9 = Arm9 { data: data.into(), offsets, originally_compressed: false, originally_encrypted: false };
178 arm9.originally_compressed = arm9.is_compressed()?;
179 arm9.originally_encrypted = arm9.is_encrypted();
180 Ok(arm9)
181 }
182
183 pub fn with_two_tcms(
189 mut data: Vec<u8>,
190 itcm: Autoload,
191 dtcm: Autoload,
192 offsets: Arm9Offsets,
193 options: Arm9WithTcmsOptions,
194 ) -> Result<Self, RawBuildInfoError> {
195 let autoload_infos = [*itcm.info().entry(), *dtcm.info().entry()];
196
197 let autoload_blocks = data.len() as u32 + offsets.base_address;
198 data.extend(itcm.into_data().iter());
199 data.extend(dtcm.into_data().iter());
200 let autoload_infos_start = data.len() as u32 + offsets.base_address;
201 data.extend(bytemuck::bytes_of(&autoload_infos));
202 let autoload_infos_end = data.len() as u32 + offsets.base_address;
203
204 let Arm9WithTcmsOptions { originally_compressed, originally_encrypted } = options;
205 let mut arm9 = Self { data: data.into(), offsets, originally_compressed, originally_encrypted };
206
207 let build_info = arm9.build_info_mut()?;
208 build_info.autoload_blocks = autoload_blocks;
209 build_info.autoload_infos_start = autoload_infos_start;
210 build_info.autoload_infos_end = autoload_infos_end;
211
212 Ok(arm9)
213 }
214
215 pub fn with_autoloads(
221 mut data: Vec<u8>,
222 autoloads: &[Autoload],
223 offsets: Arm9Offsets,
224 options: Arm9WithTcmsOptions,
225 ) -> Result<Self, RawBuildInfoError> {
226 let autoload_blocks = data.len() as u32 + offsets.base_address;
227
228 for autoload in autoloads {
229 data.extend(autoload.full_data());
230 }
231
232 let autoload_infos_start = data.len() as u32 + offsets.base_address;
233 for autoload in autoloads {
234 data.extend(bytemuck::bytes_of(autoload.info().entry()));
235 }
236 let autoload_infos_end = data.len() as u32 + offsets.base_address;
237
238 let Arm9WithTcmsOptions { originally_compressed, originally_encrypted } = options;
239 let mut arm9 = Self { data: data.into(), offsets, originally_compressed, originally_encrypted };
240
241 let build_info = arm9.build_info_mut()?;
242 build_info.autoload_blocks = autoload_blocks;
243 build_info.autoload_infos_start = autoload_infos_start;
244 build_info.autoload_infos_end = autoload_infos_end;
245
246 Ok(arm9)
247 }
248
249 pub fn is_encrypted(&self) -> bool {
252 self.data.len() < 8 || self.data[0..8] != SECURE_AREA_ID
253 }
254
255 pub fn decrypt(&mut self, key: &BlowfishKey, gamecode: u32) -> Result<(), Arm9Error> {
262 if !self.is_encrypted() {
263 return Ok(());
264 }
265
266 if self.data.len() < 0x4000 {
267 DataTooSmallSnafu { expected: 0x4000usize, actual: self.data.len() }.fail()?;
268 }
269
270 let mut secure_area = [0u8; 0x4000];
271 secure_area.clone_from_slice(&self.data[0..0x4000]);
272
273 let blowfish = Blowfish::new(key, gamecode, BlowfishLevel::Level2);
274 blowfish.decrypt(&mut secure_area[0..8])?;
275
276 let blowfish = Blowfish::new(key, gamecode, BlowfishLevel::Level3);
277 blowfish.decrypt(&mut secure_area[0..0x800])?;
278
279 if &secure_area[0..8] != SECURE_AREA_ENCRY_OBJ {
280 NotEncryObjSnafu {}.fail()?;
281 }
282
283 secure_area[0..8].copy_from_slice(&SECURE_AREA_ID);
284 self.data.to_mut()[0..0x4000].copy_from_slice(&secure_area);
285 Ok(())
286 }
287
288 pub fn encrypt(&mut self, key: &BlowfishKey, gamecode: u32) -> Result<(), Arm9Error> {
295 if self.is_encrypted() {
296 return Ok(());
297 }
298
299 if self.data.len() < 0x4000 {
300 DataTooSmallSnafu { expected: 0x4000usize, actual: self.data.len() }.fail()?;
301 }
302
303 if self.data[0..8] != SECURE_AREA_ID {
304 NotEncryObjSnafu {}.fail()?;
305 }
306
307 let secure_area = self.encrypted_secure_area(key, gamecode);
308 self.data.to_mut()[0..0x4000].copy_from_slice(&secure_area);
309 Ok(())
310 }
311
312 pub fn encrypted_secure_area(&self, key: &BlowfishKey, gamecode: u32) -> [u8; 0x4000] {
314 let mut secure_area = [0u8; 0x4000];
315 secure_area.copy_from_slice(&self.data[0..0x4000]);
316 if self.is_encrypted() {
317 return secure_area;
318 }
319
320 secure_area[0..8].copy_from_slice(SECURE_AREA_ENCRY_OBJ);
321
322 let blowfish = Blowfish::new(key, gamecode, BlowfishLevel::Level3);
323 blowfish.encrypt(&mut secure_area[0..0x800]).unwrap();
324
325 let blowfish = Blowfish::new(key, gamecode, BlowfishLevel::Level2);
326 blowfish.encrypt(&mut secure_area[0..8]).unwrap();
327
328 secure_area
329 }
330
331 pub fn secure_area_crc(&self, key: &BlowfishKey, gamecode: u32) -> u16 {
333 let secure_area = self.encrypted_secure_area(key, gamecode);
334 CRC_16_MODBUS.checksum(&secure_area)
335 }
336
337 pub fn build_info(&self) -> Result<&BuildInfo, RawBuildInfoError> {
343 BuildInfo::borrow_from_slice(&self.data[self.offsets.build_info as usize..])
344 }
345
346 pub fn build_info_mut(&mut self) -> Result<&mut BuildInfo, RawBuildInfoError> {
352 BuildInfo::borrow_from_slice_mut(&mut self.data.to_mut()[self.offsets.build_info as usize..])
353 }
354
355 pub fn is_compressed(&self) -> Result<bool, RawBuildInfoError> {
362 Ok(self.build_info()?.is_compressed())
363 }
364
365 pub fn decompress(&mut self) -> Result<(), Arm9Error> {
371 if !self.is_compressed()? {
372 return Ok(());
373 }
374
375 let data: Cow<[u8]> = LZ77.decompress(&self.data)?.into_vec().into();
376 let old_data = replace(&mut self.data, data);
377 let build_info = match self.build_info_mut() {
378 Ok(build_info) => build_info,
379 Err(e) => {
380 self.data = old_data;
381 return Err(e.into());
382 }
383 };
384 build_info.compressed_code_end = 0;
385 Ok(())
386 }
387
388 pub fn compress(&mut self) -> Result<(), Arm9Error> {
394 if self.is_compressed()? {
395 return Ok(());
396 }
397
398 let data: Cow<[u8]> = LZ77.compress(&self.data, COMPRESSION_START)?.into_vec().into();
399 let length = data.len();
400 let old_data = replace(&mut self.data, data);
401 let base_address = self.base_address();
402 let build_info = match self.build_info_mut() {
403 Ok(build_info) => build_info,
404 Err(e) => {
405 self.data = old_data;
406 return Err(e.into());
407 }
408 };
409 build_info.compressed_code_end = base_address + length as u32;
410 Ok(())
411 }
412
413 fn get_autoload_info_entries(&self, build_info: &BuildInfo) -> Result<&[AutoloadInfoEntry], Arm9AutoloadError> {
414 let start = (build_info.autoload_infos_start - self.base_address()) as usize;
415 let end = (build_info.autoload_infos_end - self.base_address()) as usize;
416 let autoload_info = AutoloadInfoEntry::borrow_from_slice(&self.data[start..end])?;
417 Ok(autoload_info)
418 }
419
420 pub fn autoload_infos(&self) -> Result<Vec<AutoloadInfo>, Arm9AutoloadError> {
427 let build_info: &BuildInfo = self.build_info()?;
428 if build_info.is_compressed() {
429 CompressedSnafu {}.fail()?;
430 }
431 Ok(self
432 .get_autoload_info_entries(build_info)?
433 .iter()
434 .enumerate()
435 .map(|(index, entry)| AutoloadInfo::new(*entry, index as u32))
436 .collect())
437 }
438
439 pub fn autoloads(&self) -> Result<Box<[Autoload]>, Arm9AutoloadError> {
446 let build_info = self.build_info()?;
447 if build_info.is_compressed() {
448 CompressedSnafu {}.fail()?;
449 }
450 let autoload_infos = self.autoload_infos()?;
451
452 let mut autoloads = vec![];
453 let mut load_offset = build_info.autoload_blocks - self.base_address();
454 for autoload_info in autoload_infos {
455 let start = load_offset as usize;
456 let end = start + autoload_info.code_size() as usize;
457 let data = &self.data[start..end];
458 autoloads.push(Autoload::new(data, autoload_info));
459 load_offset += autoload_info.code_size();
460 }
461
462 Ok(autoloads.into_boxed_slice())
463 }
464
465 pub fn num_unknown_autoloads(&self) -> Result<usize, Arm9AutoloadError> {
471 Ok(self.autoloads()?.iter().filter(|a| matches!(a.kind(), AutoloadKind::Unknown(_))).count())
472 }
473
474 pub fn hmac_sha1_key(&self) -> Result<Option<[u8; 64]>, Arm9HmacSha1KeyError> {
476 if self.is_compressed()? {
477 HmacSha1KeyCompressedSnafu {}.fail()?
478 }
479
480 let Some((i, _)) = self.data.chunks(4).enumerate().filter(|(_, chunk)| *chunk == NITROCODE_BYTES).nth(1) else {
482 return Ok(None);
483 };
484 let start = i * 4;
485 let end = start + 64;
486 if end > self.data.len() {
487 return Ok(None);
488 }
489 let mut key = [0u8; 64];
490 key.copy_from_slice(&self.data[start..end]);
491 Ok(Some(key))
492 }
493
494 fn overlay_table_signature_range(&self) -> Result<Option<Range<usize>>, Arm9OverlaySignaturesError> {
495 let overlay_signatures_offset = self.overlay_signatures_offset() as usize;
496 if overlay_signatures_offset == 0 {
497 return Ok(None);
498 }
499
500 if self.is_compressed()? {
501 OverlaySignaturesCompressedSnafu {}.fail()?;
502 }
503
504 let start = overlay_signatures_offset - size_of::<HmacSha1Signature>();
506 let end = overlay_signatures_offset;
507 if end > self.data.len() {
508 return Ok(None);
509 }
510 return Ok(Some(start..end));
511 }
512
513 pub fn overlay_table_signature(&self) -> Result<Option<&HmacSha1Signature>, Arm9OverlaySignaturesError> {
519 let Some(range) = self.overlay_table_signature_range()? else {
520 return Ok(None);
521 };
522 let data = &self.data[range];
523
524 let signature = HmacSha1Signature::borrow_from_slice(data)?;
525 Ok(Some(signature.first().unwrap()))
526 }
527
528 pub fn overlay_table_signature_mut(&mut self) -> Result<Option<&mut HmacSha1Signature>, Arm9OverlaySignaturesError> {
535 let Some(range) = self.overlay_table_signature_range()? else {
536 return Ok(None);
537 };
538 let data = &mut self.data.to_mut()[range];
539
540 let signature = HmacSha1Signature::borrow_from_slice_mut(data)?;
541 Ok(Some(signature.first_mut().unwrap()))
542 }
543
544 fn overlay_signatures_range(&self, num_overlays: usize) -> Result<Option<Range<usize>>, Arm9OverlaySignaturesError> {
545 let start = self.overlay_signatures_offset() as usize;
546 if start == 0 {
547 return Ok(None);
548 }
549
550 if self.is_compressed()? {
551 OverlaySignaturesCompressedSnafu {}.fail()?;
552 }
553
554 let end = start + size_of::<HmacSha1Signature>() * num_overlays;
555 if end > self.data.len() {
556 return Ok(None);
557 }
558 return Ok(Some(start..end));
559 }
560
561 pub fn overlay_signatures(&self, num_overlays: usize) -> Result<Option<&[HmacSha1Signature]>, Arm9OverlaySignaturesError> {
568 let Some(range) = self.overlay_signatures_range(num_overlays)? else {
569 return Ok(None);
570 };
571 let data = &self.data[range];
572 Ok(Some(HmacSha1Signature::borrow_from_slice(data)?))
573 }
574
575 pub fn overlay_signatures_mut(
582 &mut self,
583 num_overlays: usize,
584 ) -> Result<Option<&mut [HmacSha1Signature]>, Arm9OverlaySignaturesError> {
585 let Some(range) = self.overlay_signatures_range(num_overlays)? else {
586 return Ok(None);
587 };
588 let data = &mut self.data.to_mut()[range];
589 Ok(Some(HmacSha1Signature::borrow_from_slice_mut(data)?))
590 }
591
592 pub fn code(&self) -> Result<&[u8], RawBuildInfoError> {
598 let build_info = self.build_info()?;
599 Ok(&self.data[..(build_info.bss_start - self.base_address()) as usize])
600 }
601
602 pub fn full_data(&self) -> &[u8] {
604 &self.data
605 }
606
607 pub fn base_address(&self) -> u32 {
609 self.offsets.base_address
610 }
611
612 pub fn end_address(&self) -> Result<u32, RawBuildInfoError> {
614 let build_info = self.build_info()?;
615 Ok(build_info.bss_end)
616 }
617
618 pub fn entry_function(&self) -> u32 {
620 self.offsets.entry_function
621 }
622
623 pub fn build_info_offset(&self) -> u32 {
625 self.offsets.build_info
626 }
627
628 pub fn autoload_callback(&self) -> u32 {
630 self.offsets.autoload_callback
631 }
632
633 pub fn overlay_signatures_offset(&self) -> u32 {
635 self.offsets.overlay_signatures
636 }
637
638 pub fn bss(&self) -> Result<Range<u32>, RawBuildInfoError> {
644 let build_info = self.build_info()?;
645 Ok(build_info.bss_start..build_info.bss_end)
646 }
647
648 pub fn offsets(&self) -> &Arm9Offsets {
650 &self.offsets
651 }
652
653 pub fn originally_compressed(&self) -> bool {
655 self.originally_compressed
656 }
657
658 pub fn originally_encrypted(&self) -> bool {
660 self.originally_encrypted
661 }
662
663 pub(crate) fn update_overlay_signatures(
664 &mut self,
665 arm9_overlay_table: &OverlayTable,
666 ) -> Result<(), Arm9OverlaySignaturesError> {
667 let arm9_overlays = arm9_overlay_table.overlays();
668 let Some(signatures) = self.overlay_signatures_mut(arm9_overlays.len())? else {
669 return Ok(());
670 };
671 for overlay in arm9_overlays {
672 if let Some(signature) = overlay.signature() {
673 signatures[overlay.id() as usize] = signature;
674 }
675 }
676
677 if let Some(signature) = arm9_overlay_table.signature() {
678 let Some(table_signature) = self.overlay_table_signature_mut()? else {
679 return Ok(());
680 };
681 *table_signature = signature;
682 }
683
684 Ok(())
685 }
686}
687
688impl AsRef<[u8]> for Arm9<'_> {
689 fn as_ref(&self) -> &[u8] {
690 &self.data
691 }
692}