1use std::path::{Path, PathBuf};
2
3use log::{debug, trace};
4use thiserror::Error;
5
6#[derive(Debug, Clone, PartialEq, Eq, Hash)]
8pub struct Payload {
9 data: Box<[u8]>,
10}
11
12const BUILT_PAYLOAD_MAX_LENGTH: usize = 0x30298;
14const PAYLOAD_MIN_LENGTH: usize = PADDING_SIZE_2;
17const PAYLOAD_MAX_LENGTH: usize = 183_640;
19
20const STACK_SPRAY_START: usize = 0x4001_4E40;
22const STACK_SPRAY_END: usize = 0x4001_7000;
24const PADDING_SIZE_2: usize = STACK_SPRAY_START - PAYLOAD_START_ADDR;
25
26const PAYLOAD_START_ADDR: usize = 0x4001_0E40;
27const RCM_PAYLOAD_ADDR: usize = 0x4001_0000;
28
29const REPEAT_COUNT: usize = (STACK_SPRAY_END - STACK_SPRAY_START) / 4;
30
31impl Payload {
32 pub fn new(payload: &[u8]) -> Result<Self, PayloadError> {
35 if payload.len() < PAYLOAD_MIN_LENGTH {
36 return Err(PayloadError::PayloadTooShort(payload.len()));
37 }
38
39 if payload.len() >= PAYLOAD_MAX_LENGTH {
40 return Err(PayloadError::PayloadTooLong(payload.len()));
41 }
42
43 debug!(
44 "Provided payload within size bounds with a size is: {} bytes",
45 payload.len()
46 );
47
48 const INTERMEZZO: &[u8; 124] = include_bytes!("intermezzo/intermezzo.bin");
49 trace!("Injected intermezzo.bin");
50
51 let mut payload_builder = Vec::with_capacity(BUILT_PAYLOAD_MAX_LENGTH);
52 payload_builder.extend((BUILT_PAYLOAD_MAX_LENGTH as u32).to_le_bytes());
54 payload_builder
56 .extend([b'\0'; 680 - (BUILT_PAYLOAD_MAX_LENGTH as u32).to_le_bytes().len()]);
57 payload_builder.extend(INTERMEZZO);
59
60 const PADDING_SIZE_1: usize = PAYLOAD_START_ADDR - (RCM_PAYLOAD_ADDR + INTERMEZZO.len());
61 payload_builder.extend([b'\0'; PADDING_SIZE_1]);
62
63 let split = payload.split_at(PADDING_SIZE_2);
65 payload_builder.extend(split.0);
66 for _ in 0..REPEAT_COUNT {
68 payload_builder.extend((RCM_PAYLOAD_ADDR as u32).to_le_bytes());
69 }
70 payload_builder.extend(split.1);
71
72 let padding_size = 0x1000 - (payload_builder.len() % 0x1000);
74 payload_builder.resize(payload_builder.len() + padding_size, b'\0');
75
76 debug_assert_eq!(payload_builder.len() % 0x1000, 0);
77
78 let data = payload_builder.into_boxed_slice();
79
80 debug_assert!(data.len() <= BUILT_PAYLOAD_MAX_LENGTH);
81 debug!(
82 "A completed payload has been build with a size of: {} bytes",
83 data.len()
84 );
85
86 Ok(Self { data })
87 }
88
89 pub fn read<P: AsRef<Path>>(path: P) -> Result<Self, PayloadError> {
91 let Ok(bytes) = std::fs::read(path.as_ref()) else {
92 return Err(PayloadError::Io(path.as_ref().into()));
93 };
94 Self::new(&bytes)
95 }
96
97 pub fn data(&self) -> &[u8] {
99 &self.data
100 }
101}
102
103#[derive(Debug, PartialEq, Eq, Error, Clone)]
105#[non_exhaustive]
106pub enum PayloadError {
107 #[error("Payload failed to read from file: {0}")]
109 Io(PathBuf),
110
111 #[error("Payload invalid size: `{0}` (expected >= {})", PAYLOAD_MIN_LENGTH)]
113 PayloadTooShort(usize),
114
115 #[error("Payload invalid size: `{0}` (expected < {})", PAYLOAD_MAX_LENGTH)]
117 PayloadTooLong(usize),
118}
119
120#[cfg(test)]
121mod tests {
122 use super::Payload;
123
124 #[test]
126 fn basic_correctness() {
127 let correct = include_bytes!("test/hekate_ctcaer_5.7.2_ref_payload.bin");
128 let payload = Payload::new(include_bytes!("test/hekate_ctcaer_5.7.2.bin"))
129 .expect("This should give us a valid payload");
130
131 assert_eq!(payload.data.as_ref(), correct);
132 }
133}