1use std::{
2 backtrace::Backtrace,
3 collections::{btree_map, BTreeMap},
4 fmt::Display,
5 io::{self, BufRead, BufReader, BufWriter, Write},
6 iter,
7 num::ParseIntError,
8 ops::Range,
9 path::Path,
10};
11
12use ds_rom::rom::raw::AutoloadKind;
13use snafu::Snafu;
14
15use crate::util::{
16 io::{create_file, open_file, FileError},
17 parse::{parse_i32, parse_u16, parse_u32},
18};
19
20use super::{
21 iter_attributes,
22 module::{Module, ModuleKind},
23 ParseContext,
24};
25
26pub struct Relocations {
27 relocations: BTreeMap<u32, Relocation>,
28}
29
30#[derive(Debug, Snafu)]
31pub enum RelocationsParseError {
32 #[snafu(transparent)]
33 File { source: FileError },
34 #[snafu(transparent)]
35 Io { source: io::Error },
36 #[snafu(transparent)]
37 RelocationParse { source: RelocationParseError },
38}
39
40#[derive(Debug, Snafu)]
41pub enum RelocationsWriteError {
42 #[snafu(transparent)]
43 File { source: FileError },
44 #[snafu(transparent)]
45 Io { source: io::Error },
46}
47
48#[derive(Debug, Snafu)]
49pub enum RelocationsError {
50 #[snafu(display("Relocation from {from:#010x} to {curr_to:#010x} in {curr_module} collides with existing one to {prev_to:#010x} in {prev_module}"))]
51 RelocationCollision { from: u32, curr_to: u32, curr_module: RelocationModule, prev_to: u32, prev_module: RelocationModule },
52}
53
54impl Relocations {
55 pub fn new() -> Self {
56 Self { relocations: BTreeMap::new() }
57 }
58
59 pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self, RelocationsParseError> {
60 let path = path.as_ref();
61 let mut context = ParseContext { file_path: path.to_str().unwrap().to_string(), row: 0 };
62
63 let file = open_file(path)?;
64 let reader = BufReader::new(file);
65
66 let mut relocations = BTreeMap::new();
67 for line in reader.lines() {
68 context.row += 1;
69
70 let line = line?;
71 let comment_start = line.find("//").unwrap_or(line.len());
72 let line = &line[..comment_start];
73
74 let Some(relocation) = Relocation::parse(line, &context)? else {
75 continue;
76 };
77 relocations.insert(relocation.from, relocation);
78 }
79
80 Ok(Self { relocations })
81 }
82
83 pub fn to_file<P: AsRef<Path>>(&self, path: P) -> Result<(), RelocationsWriteError> {
84 let path = path.as_ref();
85
86 let file = create_file(path)?;
87 let mut writer = BufWriter::new(file);
88
89 for relocation in self.relocations.values() {
90 writeln!(writer, "{relocation}")?;
91 }
92 Ok(())
93 }
94
95 pub fn add(&mut self, relocation: Relocation) -> Result<&mut Relocation, RelocationsError> {
96 match self.relocations.entry(relocation.from) {
97 btree_map::Entry::Vacant(entry) => Ok(entry.insert(relocation)),
98 btree_map::Entry::Occupied(entry) => {
99 if entry.get() == &relocation {
100 log::warn!(
101 "Relocation from {:#010x} to {:#010x} in {} is identical to existing one",
102 relocation.from,
103 relocation.to,
104 relocation.module
105 );
106 Ok(entry.into_mut())
107 } else {
108 let other = entry.get();
109 let error = RelocationCollisionSnafu {
110 from: relocation.from,
111 curr_to: relocation.to,
112 curr_module: relocation.module,
113 prev_to: other.to,
114 prev_module: other.module.clone(),
115 }
116 .build();
117 log::error!("{error}");
118 Err(error)
119 }
120 }
121 }
122 }
123
124 pub fn add_call(
125 &mut self,
126 from: u32,
127 to: u32,
128 module: RelocationModule,
129 from_thumb: bool,
130 to_thumb: bool,
131 ) -> Result<&mut Relocation, RelocationsError> {
132 self.add(Relocation::new_call(from, to, module, from_thumb, to_thumb))
133 }
134
135 pub fn add_load(
136 &mut self,
137 from: u32,
138 to: u32,
139 addend: i32,
140 module: RelocationModule,
141 ) -> Result<&mut Relocation, RelocationsError> {
142 self.add(Relocation::new_load(from, to, addend, module))
143 }
144
145 pub fn get(&self, from: u32) -> Option<&Relocation> {
146 self.relocations.get(&from)
147 }
148
149 pub fn iter(&self) -> impl Iterator<Item = &Relocation> {
150 self.relocations.values()
151 }
152
153 pub fn iter_range(&self, range: Range<u32>) -> impl Iterator<Item = (&u32, &Relocation)> {
154 self.relocations.range(range)
155 }
156}
157
158#[derive(PartialEq, Eq)]
159pub struct Relocation {
160 from: u32,
161 to: u32,
162 addend: i32,
163 kind: RelocationKind,
164 module: RelocationModule,
165 pub source: Option<String>,
166}
167
168#[derive(Debug, Snafu)]
169pub enum RelocationParseError {
170 #[snafu(display("{context}: failed to parse \"from\" address '{value}': {error}\n{backtrace}"))]
171 ParseFrom { context: ParseContext, value: String, error: ParseIntError, backtrace: Backtrace },
172 #[snafu(display("{context}: failed to parse \"to\" address '{value}': {error}\n{backtrace}"))]
173 ParseTo { context: ParseContext, value: String, error: ParseIntError, backtrace: Backtrace },
174 #[snafu(display("{context}: failed to parse \"add\" addend '{value}': {error}\n{backtrace}"))]
175 ParseAdd { context: ParseContext, value: String, error: ParseIntError, backtrace: Backtrace },
176 #[snafu(transparent)]
177 RelocationKindParse { source: RelocationKindParseError },
178 #[snafu(transparent)]
179 RelocationModuleParse { source: Box<RelocationModuleParseError> },
180 #[snafu(display(
181 "{context}: expected relocation attribute 'from', 'to', 'add', 'kind' or 'module' but got '{key}':\n{backtrace}"
182 ))]
183 UnknownAttribute { context: ParseContext, key: String, backtrace: Backtrace },
184 #[snafu(display("{context}: missing '{attribute}' attribute"))]
185 MissingAttribute { context: ParseContext, attribute: String, backtrace: Backtrace },
186}
187
188impl Relocation {
189 fn parse(line: &str, context: &ParseContext) -> Result<Option<Self>, RelocationParseError> {
190 let words = line.split_whitespace();
191
192 let mut from = None;
193 let mut to = None;
194 let mut addend = 0;
195 let mut kind = None;
196 let mut module = None;
197 for (key, value) in iter_attributes(words) {
198 match key {
199 "from" => from = Some(parse_u32(value).map_err(|error| ParseFromSnafu { context, value, error }.build())?),
200 "to" => to = Some(parse_u32(value).map_err(|error| ParseToSnafu { context, value, error }.build())?),
201 "add" => addend = parse_i32(value).map_err(|error| ParseAddSnafu { context, value, error }.build())?,
202 "kind" => kind = Some(RelocationKind::parse(value, context)?),
203 "module" => module = Some(RelocationModule::parse(value, context)?),
204 _ => return UnknownAttributeSnafu { context, key }.fail(),
205 }
206 }
207
208 let from = from.ok_or_else(|| MissingAttributeSnafu { context, attribute: "from" }.build())?;
209 let to = to.ok_or_else(|| MissingAttributeSnafu { context, attribute: "to" }.build())?;
210 let kind = kind.ok_or_else(|| MissingAttributeSnafu { context, attribute: "kind" }.build())?;
211 let module = module.ok_or_else(|| MissingAttributeSnafu { context, attribute: "module" }.build())?;
212
213 Ok(Some(Self { from, to, addend, kind, module, source: None }))
214 }
215
216 pub fn new_call(from: u32, to: u32, module: RelocationModule, from_thumb: bool, to_thumb: bool) -> Self {
217 Self {
218 from,
219 to,
220 addend: 0,
221 kind: match (from_thumb, to_thumb) {
222 (true, true) => RelocationKind::ThumbCall,
223 (true, false) => RelocationKind::ThumbCallArm,
224 (false, true) => RelocationKind::ArmCallThumb,
225 (false, false) => RelocationKind::ArmCall,
226 },
227 module,
228 source: None,
229 }
230 }
231
232 pub fn new_branch(from: u32, to: u32, module: RelocationModule) -> Self {
233 Self { from, to, addend: 0, kind: RelocationKind::ArmBranch, module, source: None }
234 }
235
236 pub fn new_load(from: u32, to: u32, addend: i32, module: RelocationModule) -> Self {
237 Self { from, to, addend, kind: RelocationKind::Load, module, source: None }
238 }
239
240 pub fn from_address(&self) -> u32 {
241 self.from
242 }
243
244 pub fn to_address(&self) -> u32 {
245 self.to
246 }
247
248 pub fn kind(&self) -> RelocationKind {
249 self.kind
250 }
251
252 pub fn module(&self) -> &RelocationModule {
253 &self.module
254 }
255
256 pub fn addend(&self) -> i64 {
257 self.addend as i64 + self.kind.addend()
258 }
259
260 pub fn addend_value(&self) -> i32 {
261 self.addend
262 }
263}
264
265impl Display for Relocation {
266 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
267 write!(f, "from:{:#010x} kind:{} to:{:#010x} module:{}", self.from, self.kind, self.to, self.module)?;
268 if let Some(source) = &self.source {
269 write!(f, " // {source}")?;
270 }
271 Ok(())
272 }
273}
274
275#[derive(Clone, Copy, PartialEq, Eq)]
276pub enum RelocationKind {
277 ArmCall,
278 ThumbCall,
279 ArmCallThumb,
280 ThumbCallArm,
281 ArmBranch,
282 Load,
283}
284
285#[derive(Debug, Snafu)]
286pub enum RelocationKindParseError {
287 #[snafu(display("{context}: unknown relocation kind '{value}', must be one of: arm_call, thumb_call, arm_call_thumb, thumb_call_arm, arm_branch, load:\n{backtrace}"))]
288 UnknownKind { context: ParseContext, value: String, backtrace: Backtrace },
289}
290
291impl RelocationKind {
292 fn parse(value: &str, context: &ParseContext) -> Result<Self, RelocationKindParseError> {
293 match value {
294 "arm_call" => Ok(Self::ArmCall),
295 "thumb_call" => Ok(Self::ThumbCall),
296 "arm_call_thumb" => Ok(Self::ArmCallThumb),
297 "thumb_call_arm" => Ok(Self::ThumbCallArm),
298 "arm_branch" => Ok(Self::ArmBranch),
299 "load" => Ok(Self::Load),
300 _ => UnknownKindSnafu { context, value }.fail(),
301 }
302 }
303
304 pub fn addend(&self) -> i64 {
305 match self {
306 Self::ArmCall => -8,
307 Self::ThumbCall => -4,
308 Self::ArmCallThumb => -8,
309 Self::ThumbCallArm => -4,
310 Self::ArmBranch => -8,
311 Self::Load => 0,
312 }
313 }
314}
315
316impl Display for RelocationKind {
317 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
318 match self {
319 Self::ArmCall => write!(f, "arm_call"),
320 Self::ThumbCall => write!(f, "thumb_call"),
321 Self::ArmCallThumb => write!(f, "arm_call_thumb"),
322 Self::ThumbCallArm => write!(f, "thumb_call_arm"),
323 Self::ArmBranch => write!(f, "arm_branch"),
324 Self::Load => write!(f, "load"),
325 }
326 }
327}
328
329#[derive(PartialEq, Eq, Debug, Clone)]
330pub enum RelocationModule {
331 None,
332 Overlay { id: u16 },
333 Overlays { ids: Vec<u16> },
334 Main,
335 Itcm,
336 Dtcm,
337}
338
339#[derive(Debug, Snafu)]
340pub enum RelocationFromModulesError {
341 #[snafu(display("Relocations to {module_kind} should be unambiguous:\n{backtrace}"))]
342 AmbiguousNonOverlayRelocation { module_kind: ModuleKind, backtrace: Backtrace },
343 #[snafu(display("Unknown autoload kind '{kind}':\n{backtrace}"))]
344 UnknownAutoloadKind { kind: AutoloadKind, backtrace: Backtrace },
345}
346
347#[derive(Debug, Snafu)]
348pub enum RelocationModuleParseError {
349 #[snafu(display("{context}: relocations to '{module}' have no options, but got '({options})':\n{backtrace}"))]
350 UnexpectedOptions { context: ParseContext, module: String, options: String, backtrace: Backtrace },
351 #[snafu(display("{context}: failed to parse overlay ID '{value}': {error}\n{backtrace}"))]
352 ParseOverlayId { context: ParseContext, value: String, error: ParseIntError, backtrace: Backtrace },
353 #[snafu(display("{context}: relocation to 'overlays' must have two or more overlay IDs, but got {ids:?}:\n{backtrace}"))]
354 ExpectedMultipleOverlays { context: ParseContext, ids: Vec<u16>, backtrace: Backtrace },
355 #[snafu(display(
356 "{context}: unknown relocation to '{module}', must be one of: overlays, overlay, main, itcm, dtcm, none:\n{backtrace}"
357 ))]
358 UnknownModule { context: ParseContext, module: String, backtrace: Backtrace },
359}
360
361#[derive(Debug, Snafu)]
362pub enum RelocationModuleKindNotSupportedError {
363 #[snafu(display("Unknown autoload kind '{kind}'"))]
364 UnknownAutoload { kind: AutoloadKind, backtrace: Backtrace },
365}
366
367impl RelocationModule {
368 pub fn from_modules<'a, I>(mut modules: I) -> Result<Self, RelocationFromModulesError>
369 where
370 I: Iterator<Item = &'a Module<'a>>,
371 {
372 let Some(first) = modules.next() else { return Ok(Self::None) };
373
374 let module_kind = first.kind();
375 match module_kind {
376 ModuleKind::Arm9 => {
377 if modules.next().is_some() {
378 return AmbiguousNonOverlayRelocationSnafu { module_kind }.fail();
379 }
380 Ok(Self::Main)
381 }
382 ModuleKind::Autoload(AutoloadKind::Itcm) => {
383 if modules.next().is_some() {
384 return AmbiguousNonOverlayRelocationSnafu { module_kind }.fail();
385 }
386 Ok(Self::Itcm)
387 }
388 ModuleKind::Autoload(AutoloadKind::Dtcm) => {
389 if modules.next().is_some() {
390 return AmbiguousNonOverlayRelocationSnafu { module_kind }.fail();
391 }
392 Ok(Self::Dtcm)
393 }
394 ModuleKind::Autoload(kind) => UnknownAutoloadKindSnafu { kind }.fail(),
395 ModuleKind::Overlay(id) => {
396 let ids = iter::once(first)
397 .chain(modules)
398 .map(|module| {
399 if let ModuleKind::Overlay(id) = module.kind() {
400 Ok(id)
401 } else {
402 AmbiguousNonOverlayRelocationSnafu { module_kind: module.kind() }.fail()
403 }
404 })
405 .collect::<Result<Vec<_>, _>>()?;
406 if ids.len() > 1 {
407 Ok(Self::Overlays { ids })
408 } else {
409 Ok(Self::Overlay { id })
410 }
411 }
412 }
413 }
414
415 fn parse(text: &str, context: &ParseContext) -> Result<Self, Box<RelocationModuleParseError>> {
416 let (value, options) = text.split_once('(').unwrap_or((text, ""));
417 let options = options.strip_suffix(')').unwrap_or(options);
418
419 match value {
420 "none" => {
421 if options.is_empty() {
422 Ok(Self::None)
423 } else {
424 Err(Box::new(UnexpectedOptionsSnafu { context, module: "none", options }.build()))
425 }
426 }
427 "overlay" => Ok(Self::Overlay {
428 id: parse_u16(options).map_err(|error| ParseOverlayIdSnafu { context, value: options, error }.build())?,
429 }),
430 "overlays" => {
431 let ids = options
432 .split(',')
433 .map(|x| parse_u16(x).map_err(|error| ParseOverlayIdSnafu { context, value: x, error }.build()))
434 .collect::<Result<Vec<_>, _>>()?;
435 if ids.len() < 2 {
436 Err(Box::new(ExpectedMultipleOverlaysSnafu { context, ids }.build()))
437 } else {
438 Ok(Self::Overlays { ids })
439 }
440 }
441 "main" => {
442 if options.is_empty() {
443 Ok(Self::Main)
444 } else {
445 Err(Box::new(UnexpectedOptionsSnafu { context, module: "main", options }.build()))
446 }
447 }
448 "itcm" => {
449 if options.is_empty() {
450 Ok(Self::Itcm)
451 } else {
452 Err(Box::new(UnexpectedOptionsSnafu { context, module: "itcm", options }.build()))
453 }
454 }
455 "dtcm" => {
456 if options.is_empty() {
457 Ok(Self::Dtcm)
458 } else {
459 Err(Box::new(UnexpectedOptionsSnafu { context, module: "dtcm", options }.build()))
460 }
461 }
462 _ => Err(Box::new(UnknownModuleSnafu { context, module: value }.build())),
463 }
464 }
465}
466
467impl TryFrom<ModuleKind> for RelocationModule {
468 type Error = RelocationModuleKindNotSupportedError;
469
470 fn try_from(value: ModuleKind) -> Result<Self, Self::Error> {
471 match value {
472 ModuleKind::Arm9 => Ok(Self::Main),
473 ModuleKind::Overlay(id) => Ok(Self::Overlay { id }),
474 ModuleKind::Autoload(kind) => match kind {
475 AutoloadKind::Itcm => Ok(Self::Itcm),
476 AutoloadKind::Dtcm => Ok(Self::Dtcm),
477 AutoloadKind::Unknown(_) => UnknownAutoloadSnafu { kind }.fail(),
478 },
479 }
480 }
481}
482
483impl Display for RelocationModule {
484 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
485 match self {
486 RelocationModule::None => write!(f, "none"),
487 RelocationModule::Overlay { id } => write!(f, "overlay({id})"),
488 RelocationModule::Overlays { ids } => {
489 write!(f, "overlays({}", ids[0])?;
490 for id in &ids[1..] {
491 write!(f, ",{}", id)?;
492 }
493 write!(f, ")")?;
494 Ok(())
495 }
496 RelocationModule::Main => write!(f, "main"),
497 RelocationModule::Itcm => write!(f, "itcm"),
498 RelocationModule::Dtcm => write!(f, "dtcm"),
499 }
500 }
501}