1use std::{borrow::Cow, io, mem::replace, ops::Range};
2
3use serde::{Deserialize, Serialize};
4use snafu::{Backtrace, Snafu};
5
6use super::{
7 raw::{AutoloadInfo, AutoloadKind, BuildInfo, RawAutoloadInfoError, RawBuildInfoError},
8 Autoload,
9};
10use crate::{
11 compress::lz77::{Lz77, Lz77DecompressError},
12 crc::CRC_16_MODBUS,
13 crypto::blowfish::{Blowfish, BlowfishError, BlowfishKey, BlowfishLevel},
14};
15
16#[derive(Clone)]
18pub struct Arm9<'a> {
19 data: Cow<'a, [u8]>,
20 offsets: Arm9Offsets,
21 originally_compressed: bool,
22 originally_encrypted: bool,
23}
24
25#[derive(Serialize, Deserialize, Clone, Copy)]
27pub struct Arm9Offsets {
28 pub base_address: u32,
30 pub entry_function: u32,
32 pub build_info: u32,
34 pub autoload_callback: u32,
36}
37
38const SECURE_AREA_ID: [u8; 8] = [0xff, 0xde, 0xff, 0xe7, 0xff, 0xde, 0xff, 0xe7];
39const SECURE_AREA_ENCRY_OBJ: &[u8] = "encryObj".as_bytes();
40
41const LZ77: Lz77 = Lz77 {};
42
43const COMPRESSION_START: usize = 0x4000;
44
45#[derive(Debug, Snafu)]
47pub enum Arm9Error {
48 #[snafu(display("expected {expected:#x} bytes for secure area but had only {actual:#x}:\n{backtrace}"))]
50 DataTooSmall {
51 expected: usize,
53 actual: usize,
55 backtrace: Backtrace,
57 },
58 #[snafu(transparent)]
60 Blowfish {
61 source: BlowfishError,
63 },
64 #[snafu(display("invalid encryption, 'encryObj' not found"))]
66 NotEncryObj {
67 backtrace: Backtrace,
69 },
70 #[snafu(transparent)]
72 RawBuildInfo {
73 source: RawBuildInfoError,
75 },
76 #[snafu(transparent)]
78 Lz77Decompress {
79 source: Lz77DecompressError,
81 },
82 #[snafu(transparent)]
84 Io {
85 source: io::Error,
87 },
88}
89
90#[derive(Debug, Snafu)]
92pub enum Arm9AutoloadError {
93 #[snafu(transparent)]
95 RawBuildInfo {
96 source: RawBuildInfoError,
98 },
99 #[snafu(transparent)]
101 RawAutoloadInfo {
102 source: RawAutoloadInfoError,
104 },
105 #[snafu(display("ARM9 program must be decompressed before accessing autoload blocks:\n{backtrace}"))]
107 Compressed {
108 backtrace: Backtrace,
110 },
111 #[snafu(display("autoload block {kind} could not be found:\n{backtrace}"))]
113 NotFound {
114 kind: AutoloadKind,
116 backtrace: Backtrace,
118 },
119}
120
121pub struct Arm9WithTcmsOptions {
123 pub originally_compressed: bool,
125 pub originally_encrypted: bool,
127}
128
129impl<'a> Arm9<'a> {
130 pub fn new<T: Into<Cow<'a, [u8]>>>(data: T, offsets: Arm9Offsets) -> Result<Self, RawBuildInfoError> {
132 let mut arm9 = Arm9 { data: data.into(), offsets, originally_compressed: false, originally_encrypted: false };
133 arm9.originally_compressed = arm9.is_compressed()?;
134 arm9.originally_encrypted = arm9.is_encrypted();
135 Ok(arm9)
136 }
137
138 pub fn with_two_tcms(
144 mut data: Vec<u8>,
145 itcm: Autoload,
146 dtcm: Autoload,
147 offsets: Arm9Offsets,
148 options: Arm9WithTcmsOptions,
149 ) -> Result<Self, RawBuildInfoError> {
150 let autoload_infos = [*itcm.info(), *dtcm.info()];
151
152 let autoload_blocks = data.len() as u32 + offsets.base_address;
153 data.extend(itcm.into_data().iter());
154 data.extend(dtcm.into_data().iter());
155 let autoload_infos_start = data.len() as u32 + offsets.base_address;
156 data.extend(bytemuck::bytes_of(&autoload_infos));
157 let autoload_infos_end = data.len() as u32 + offsets.base_address;
158
159 let Arm9WithTcmsOptions { originally_compressed, originally_encrypted } = options;
160 let mut arm9 = Self { data: data.into(), offsets, originally_compressed, originally_encrypted };
161
162 let build_info = arm9.build_info_mut()?;
163 build_info.autoload_blocks = autoload_blocks;
164 build_info.autoload_infos_start = autoload_infos_start;
165 build_info.autoload_infos_end = autoload_infos_end;
166
167 Ok(arm9)
168 }
169
170 pub fn with_autoloads(
176 mut data: Vec<u8>,
177 autoloads: &[Autoload],
178 offsets: Arm9Offsets,
179 options: Arm9WithTcmsOptions,
180 ) -> Result<Self, RawBuildInfoError> {
181 let autoload_blocks = data.len() as u32 + offsets.base_address;
182
183 for autoload in autoloads {
184 data.extend(autoload.full_data());
185 }
186
187 let autoload_infos_start = data.len() as u32 + offsets.base_address;
188 for autoload in autoloads {
189 data.extend(bytemuck::bytes_of(autoload.info()));
190 }
191 let autoload_infos_end = data.len() as u32 + offsets.base_address;
192
193 let Arm9WithTcmsOptions { originally_compressed, originally_encrypted } = options;
194 let mut arm9 = Self { data: data.into(), offsets, originally_compressed, originally_encrypted };
195
196 let build_info = arm9.build_info_mut()?;
197 build_info.autoload_blocks = autoload_blocks;
198 build_info.autoload_infos_start = autoload_infos_start;
199 build_info.autoload_infos_end = autoload_infos_end;
200
201 Ok(arm9)
202 }
203
204 pub fn is_encrypted(&self) -> bool {
207 self.data.len() < 8 || self.data[0..8] != SECURE_AREA_ID
208 }
209
210 pub fn decrypt(&mut self, key: &BlowfishKey, gamecode: u32) -> Result<(), Arm9Error> {
217 if !self.is_encrypted() {
218 return Ok(());
219 }
220
221 if self.data.len() < 0x4000 {
222 DataTooSmallSnafu { expected: 0x4000usize, actual: self.data.len() }.fail()?;
223 }
224
225 let mut secure_area = [0u8; 0x4000];
226 secure_area.clone_from_slice(&self.data[0..0x4000]);
227
228 let blowfish = Blowfish::new(key, gamecode, BlowfishLevel::Level2);
229 blowfish.decrypt(&mut secure_area[0..8])?;
230
231 let blowfish = Blowfish::new(key, gamecode, BlowfishLevel::Level3);
232 blowfish.decrypt(&mut secure_area[0..0x800])?;
233
234 if &secure_area[0..8] != SECURE_AREA_ENCRY_OBJ {
235 NotEncryObjSnafu {}.fail()?;
236 }
237
238 secure_area[0..8].copy_from_slice(&SECURE_AREA_ID);
239 self.data.to_mut()[0..0x4000].copy_from_slice(&secure_area);
240 Ok(())
241 }
242
243 pub fn encrypt(&mut self, key: &BlowfishKey, gamecode: u32) -> Result<(), Arm9Error> {
250 if self.is_encrypted() {
251 return Ok(());
252 }
253
254 if self.data.len() < 0x4000 {
255 DataTooSmallSnafu { expected: 0x4000usize, actual: self.data.len() }.fail()?;
256 }
257
258 if self.data[0..8] != SECURE_AREA_ID {
259 NotEncryObjSnafu {}.fail()?;
260 }
261
262 let secure_area = self.encrypted_secure_area(key, gamecode);
263 self.data.to_mut()[0..0x4000].copy_from_slice(&secure_area);
264 Ok(())
265 }
266
267 pub fn encrypted_secure_area(&self, key: &BlowfishKey, gamecode: u32) -> [u8; 0x4000] {
269 let mut secure_area = [0u8; 0x4000];
270 secure_area.copy_from_slice(&self.data[0..0x4000]);
271 if self.is_encrypted() {
272 return secure_area;
273 }
274
275 secure_area[0..8].copy_from_slice(SECURE_AREA_ENCRY_OBJ);
276
277 let blowfish = Blowfish::new(key, gamecode, BlowfishLevel::Level3);
278 blowfish.encrypt(&mut secure_area[0..0x800]).unwrap();
279
280 let blowfish = Blowfish::new(key, gamecode, BlowfishLevel::Level2);
281 blowfish.encrypt(&mut secure_area[0..8]).unwrap();
282
283 secure_area
284 }
285
286 pub fn secure_area_crc(&self, key: &BlowfishKey, gamecode: u32) -> u16 {
288 let secure_area = self.encrypted_secure_area(key, gamecode);
289 CRC_16_MODBUS.checksum(&secure_area)
290 }
291
292 pub fn build_info(&self) -> Result<&BuildInfo, RawBuildInfoError> {
298 BuildInfo::borrow_from_slice(&self.data[self.offsets.build_info as usize..])
299 }
300
301 pub fn build_info_mut(&mut self) -> Result<&mut BuildInfo, RawBuildInfoError> {
307 BuildInfo::borrow_from_slice_mut(&mut self.data.to_mut()[self.offsets.build_info as usize..])
308 }
309
310 pub fn is_compressed(&self) -> Result<bool, RawBuildInfoError> {
317 Ok(self.build_info()?.is_compressed())
318 }
319
320 pub fn decompress(&mut self) -> Result<(), Arm9Error> {
326 if !self.is_compressed()? {
327 return Ok(());
328 }
329
330 let data: Cow<[u8]> = LZ77.decompress(&self.data)?.into_vec().into();
331 let old_data = replace(&mut self.data, data);
332 let build_info = match self.build_info_mut() {
333 Ok(build_info) => build_info,
334 Err(e) => {
335 self.data = old_data;
336 return Err(e.into());
337 }
338 };
339 build_info.compressed_code_end = 0;
340 Ok(())
341 }
342
343 pub fn compress(&mut self) -> Result<(), Arm9Error> {
349 if self.is_compressed()? {
350 return Ok(());
351 }
352
353 let data: Cow<[u8]> = LZ77.compress(&self.data, COMPRESSION_START)?.into_vec().into();
354 let length = data.len();
355 let old_data = replace(&mut self.data, data);
356 let base_address = self.base_address();
357 let build_info = match self.build_info_mut() {
358 Ok(build_info) => build_info,
359 Err(e) => {
360 self.data = old_data;
361 return Err(e.into());
362 }
363 };
364 build_info.compressed_code_end = base_address + length as u32;
365 Ok(())
366 }
367
368 fn get_autoload_infos(&self, build_info: &BuildInfo) -> Result<&[AutoloadInfo], Arm9AutoloadError> {
369 let start = (build_info.autoload_infos_start - self.base_address()) as usize;
370 let end = (build_info.autoload_infos_end - self.base_address()) as usize;
371 let autoload_info = AutoloadInfo::borrow_from_slice(&self.data[start..end])?;
372 Ok(autoload_info)
373 }
374
375 pub fn autoload_infos(&self) -> Result<&[AutoloadInfo], Arm9AutoloadError> {
382 let build_info: &BuildInfo = self.build_info()?;
383 if build_info.is_compressed() {
384 CompressedSnafu {}.fail()?;
385 }
386 self.get_autoload_infos(build_info)
387 }
388
389 pub fn autoloads(&self) -> Result<Box<[Autoload]>, Arm9AutoloadError> {
396 let build_info = self.build_info()?;
397 if build_info.is_compressed() {
398 CompressedSnafu {}.fail()?;
399 }
400 let autoload_infos = self.get_autoload_infos(build_info)?;
401
402 let mut autoloads = vec![];
403 let mut load_offset = build_info.autoload_blocks - self.base_address();
404 for autoload_info in autoload_infos {
405 let start = load_offset as usize;
406 let end = start + autoload_info.code_size as usize;
407 let data = &self.data[start..end];
408 autoloads.push(Autoload::new(data, *autoload_info));
409 load_offset += autoload_info.code_size;
410 }
411
412 Ok(autoloads.into_boxed_slice())
413 }
414
415 pub fn num_unknown_autoloads(&self) -> Result<usize, Arm9AutoloadError> {
421 Ok(self.autoloads()?.iter().filter(|a| matches!(a.kind(), AutoloadKind::Unknown(_))).count())
422 }
423
424 pub fn code(&self) -> Result<&[u8], RawBuildInfoError> {
430 let build_info = self.build_info()?;
431 Ok(&self.data[..(build_info.bss_start - self.base_address()) as usize])
432 }
433
434 pub fn full_data(&self) -> &[u8] {
436 &self.data
437 }
438
439 pub fn base_address(&self) -> u32 {
441 self.offsets.base_address
442 }
443
444 pub fn end_address(&self) -> Result<u32, RawBuildInfoError> {
446 let build_info = self.build_info()?;
447 Ok(build_info.bss_end)
448 }
449
450 pub fn entry_function(&self) -> u32 {
452 self.offsets.entry_function
453 }
454
455 pub fn build_info_offset(&self) -> u32 {
457 self.offsets.build_info
458 }
459
460 pub fn autoload_callback(&self) -> u32 {
462 self.offsets.autoload_callback
463 }
464
465 pub fn bss(&self) -> Result<Range<u32>, RawBuildInfoError> {
471 let build_info = self.build_info()?;
472 Ok(build_info.bss_start..build_info.bss_end)
473 }
474
475 pub fn offsets(&self) -> &Arm9Offsets {
477 &self.offsets
478 }
479
480 pub fn originally_compressed(&self) -> bool {
482 self.originally_compressed
483 }
484
485 pub fn originally_encrypted(&self) -> bool {
487 self.originally_encrypted
488 }
489}
490
491impl AsRef<[u8]> for Arm9<'_> {
492 fn as_ref(&self) -> &[u8] {
493 &self.data
494 }
495}