Skip to main content

ds_decomp/config/
relocations.rs

1use std::{
2    backtrace::Backtrace,
3    collections::{BTreeMap, btree_map},
4    fmt::Display,
5    io::{self, BufWriter, Write},
6    iter,
7    num::ParseIntError,
8    ops::Range,
9    path::Path,
10};
11
12use ds_rom::rom::raw::AutoloadKind;
13use serde::{Deserialize, Serialize};
14use snafu::Snafu;
15
16use super::{
17    ParseContext, iter_attributes,
18    module::{Module, ModuleKind},
19};
20use crate::{
21    config::{
22        CommentedLine, Comments,
23        link_time_const::{LinkTimeConst, LinkTimeConstParseError},
24        symbol::{Symbol, SymbolMap},
25    },
26    util::{
27        io::{FileError, create_file},
28        parse::{parse_i32, parse_u16, parse_u32},
29    },
30};
31
32pub struct Relocations {
33    /// Maps from_address to [`Relocation`]
34    relocations: BTreeMap<u32, Relocation>,
35    /// Maps to_address to list of from_address
36    relocations_by_to_address: BTreeMap<u32, Vec<u32>>,
37}
38
39#[derive(Debug, Snafu)]
40pub enum RelocationsParseError {
41    #[snafu(transparent)]
42    File { source: FileError },
43    #[snafu(transparent)]
44    Io { source: io::Error },
45    #[snafu(transparent)]
46    RelocationParse { source: RelocationParseError },
47    #[snafu(transparent)]
48    Relocations { source: RelocationsError },
49}
50
51#[derive(Debug, Snafu)]
52pub enum RelocationsWriteError {
53    #[snafu(transparent)]
54    File { source: FileError },
55    #[snafu(transparent)]
56    Io { source: io::Error },
57}
58
59#[derive(Debug, Snafu)]
60pub enum RelocationsError {
61    #[snafu(display(
62        "Relocation from {from:#010x} to {curr_to:#010x} in {curr_module} collides with existing one to {prev_to:#010x} in {prev_module}"
63    ))]
64    RelocationCollision {
65        from: u32,
66        curr_to: u32,
67        curr_module: RelocationModule,
68        prev_to: u32,
69        prev_module: RelocationModule,
70    },
71}
72
73impl Relocations {
74    pub fn new() -> Self {
75        Self { relocations: BTreeMap::new(), relocations_by_to_address: BTreeMap::new() }
76    }
77
78    pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self, RelocationsParseError> {
79        let path = path.as_ref();
80        let mut context = ParseContext { file_path: path.to_str().unwrap().to_string(), row: 0 };
81
82        let lines = CommentedLine::read(path)?;
83        let mut relocs = Self::new();
84        for line in lines {
85            let line = line?;
86            context.row = line.row;
87
88            let Some(relocation) = Relocation::parse(line, &context)? else {
89                continue;
90            };
91            relocs.add(relocation)?;
92        }
93
94        Ok(relocs)
95    }
96
97    pub fn to_file<P: AsRef<Path>>(&self, path: P) -> Result<(), RelocationsWriteError> {
98        let path = path.as_ref();
99
100        let file = create_file(path)?;
101        let mut writer = BufWriter::new(file);
102
103        for relocation in self.relocations.values() {
104            writeln!(writer, "{relocation}")?;
105        }
106        Ok(())
107    }
108
109    pub fn add(&mut self, relocation: Relocation) -> Result<&mut Relocation, RelocationsError> {
110        let relocation = match self.relocations.entry(relocation.from) {
111            btree_map::Entry::Vacant(entry) => entry.insert(relocation),
112            btree_map::Entry::Occupied(entry) => {
113                if entry.get() == &relocation {
114                    log::warn!(
115                        "Relocation from {:#010x} to {:#010x} in {} is identical to existing one",
116                        relocation.from,
117                        relocation.to,
118                        relocation.module
119                    );
120                    entry.into_mut()
121                } else {
122                    let other = entry.get();
123                    let error = RelocationCollisionSnafu {
124                        from: relocation.from,
125                        curr_to: relocation.to,
126                        curr_module: relocation.module,
127                        prev_to: other.to,
128                        prev_module: other.module.clone(),
129                    }
130                    .build();
131                    log::error!("{error}");
132                    return Err(error);
133                }
134            }
135        };
136
137        self.relocations_by_to_address.entry(relocation.to).or_default().push(relocation.from);
138
139        Ok(relocation)
140    }
141
142    pub fn add_call(
143        &mut self,
144        from: u32,
145        to: u32,
146        module: RelocationModule,
147        from_thumb: bool,
148        to_thumb: bool,
149    ) -> Result<&mut Relocation, RelocationsError> {
150        self.add(Relocation::new_call(from, to, module, from_thumb, to_thumb))
151    }
152
153    pub fn add_load(
154        &mut self,
155        from: u32,
156        to: u32,
157        addend: i32,
158        module: RelocationModule,
159    ) -> Result<&mut Relocation, RelocationsError> {
160        self.add(Relocation::new_load(from, to, addend, module))
161    }
162
163    pub fn get(&self, from: u32) -> Option<&Relocation> {
164        self.relocations.get(&from)
165    }
166
167    pub fn get_mut(&mut self, from: u32) -> Option<&mut Relocation> {
168        self.relocations.get_mut(&from)
169    }
170
171    pub fn iter(&self) -> impl Iterator<Item = &Relocation> {
172        self.relocations.values()
173    }
174
175    pub fn iter_range(&self, range: Range<u32>) -> impl Iterator<Item = (&u32, &Relocation)> {
176        self.relocations.range(range)
177    }
178
179    /// Returns a list of from-addresses for relocations that point to `to_address`
180    pub fn get_by_to_address(&self, to_address: u32) -> &[u32] {
181        self.relocations_by_to_address.get(&to_address).map(|v| v.as_slice()).unwrap_or(&[])
182    }
183
184    pub fn remove(&mut self, from: u32) -> Option<Relocation> {
185        self.relocations.remove(&from)
186    }
187}
188
189#[derive(PartialEq, Eq, Clone)]
190pub struct Relocation {
191    from: u32,
192    to: u32,
193    addend: i32,
194    kind: RelocationKind,
195    module: RelocationModule,
196    pub comments: Comments,
197}
198
199pub struct RelocationOptions {
200    pub from: u32,
201    pub to: u32,
202    pub addend: i32,
203    pub kind: RelocationKind,
204    pub module: RelocationModule,
205    pub comments: Comments,
206}
207
208#[derive(Debug, Snafu)]
209pub enum RelocationParseError {
210    #[snafu(display(
211        "{context}: failed to parse \"from\" address '{value}': {error}\n{backtrace}"
212    ))]
213    ParseFrom { context: ParseContext, value: String, error: ParseIntError, backtrace: Backtrace },
214    #[snafu(display("{context}: failed to parse \"to\" address '{value}': {error}\n{backtrace}"))]
215    ParseTo { context: ParseContext, value: String, error: ParseIntError, backtrace: Backtrace },
216    #[snafu(display("{context}: failed to parse \"add\" addend '{value}': {error}\n{backtrace}"))]
217    ParseAdd { context: ParseContext, value: String, error: ParseIntError, backtrace: Backtrace },
218    #[snafu(transparent)]
219    RelocationKindParse { source: RelocationKindParseError },
220    #[snafu(transparent)]
221    RelocationModuleParse { source: Box<RelocationModuleParseError> },
222    #[snafu(display(
223        "{context}: expected relocation attribute 'from', 'to', 'add', 'kind' or 'module' but got '{key}':\n{backtrace}"
224    ))]
225    UnknownAttribute { context: ParseContext, key: String, backtrace: Backtrace },
226    #[snafu(display("{context}: missing '{attribute}' attribute"))]
227    MissingAttribute { context: ParseContext, attribute: String, backtrace: Backtrace },
228}
229
230impl Relocation {
231    pub fn new(options: RelocationOptions) -> Self {
232        let RelocationOptions { from, to, addend, kind, module, comments } = options;
233        Self { from, to, addend, kind, module, comments }
234    }
235
236    fn parse(
237        line: CommentedLine,
238        context: &ParseContext,
239    ) -> Result<Option<Self>, RelocationParseError> {
240        let words = line.text.split_whitespace();
241
242        let mut from = None;
243        let mut to = None;
244        let mut addend = 0;
245        let mut kind = None;
246        let mut module = None;
247        for (key, value) in iter_attributes(words) {
248            match key {
249                "from" => {
250                    from = Some(
251                        parse_u32(value)
252                            .map_err(|error| ParseFromSnafu { context, value, error }.build())?,
253                    )
254                }
255                "to" => {
256                    to = Some(
257                        parse_u32(value)
258                            .map_err(|error| ParseToSnafu { context, value, error }.build())?,
259                    )
260                }
261                "add" => {
262                    addend = parse_i32(value)
263                        .map_err(|error| ParseAddSnafu { context, value, error }.build())?
264                }
265                "kind" => kind = Some(RelocationKind::parse(value, context)?),
266                "module" => module = Some(RelocationModule::parse(value, context)?),
267                _ => return UnknownAttributeSnafu { context, key }.fail(),
268            }
269        }
270
271        let from =
272            from.ok_or_else(|| MissingAttributeSnafu { context, attribute: "from" }.build())?;
273        let kind =
274            kind.ok_or_else(|| MissingAttributeSnafu { context, attribute: "kind" }.build())?;
275        let to = match kind {
276            // `to` is not used for link-time constants other than overlay ID
277            RelocationKind::LinkTimeConst(_) => 0,
278            _ => to.ok_or_else(|| MissingAttributeSnafu { context, attribute: "to" }.build())?,
279        };
280        let module = match kind {
281            // `module` is not used for link-time constants
282            RelocationKind::OverlayId | RelocationKind::LinkTimeConst(_) => RelocationModule::None,
283            _ => module
284                .ok_or_else(|| MissingAttributeSnafu { context, attribute: "module" }.build())?,
285        };
286
287        Ok(Some(Self { from, to, addend, kind, module, comments: line.comments }))
288    }
289
290    pub fn new_call(
291        from: u32,
292        to: u32,
293        module: RelocationModule,
294        from_thumb: bool,
295        to_thumb: bool,
296    ) -> Self {
297        Self {
298            from,
299            to,
300            addend: 0,
301            kind: match (from_thumb, to_thumb) {
302                (true, true) => RelocationKind::ThumbCall,
303                (true, false) => RelocationKind::ThumbCallArm,
304                (false, true) => RelocationKind::ArmCallThumb,
305                (false, false) => RelocationKind::ArmCall,
306            },
307            module,
308            comments: Comments::new(),
309        }
310    }
311
312    pub fn new_branch(from: u32, to: u32, module: RelocationModule) -> Self {
313        Self {
314            from,
315            to,
316            addend: 0,
317            kind: RelocationKind::ArmBranch,
318            module,
319            comments: Comments::new(),
320        }
321    }
322
323    pub fn new_load(from: u32, to: u32, addend: i32, module: RelocationModule) -> Self {
324        Self { from, to, addend, kind: RelocationKind::Load, module, comments: Comments::new() }
325    }
326
327    pub fn from_address(&self) -> u32 {
328        self.from
329    }
330
331    pub fn to_address(&self) -> u32 {
332        self.to
333    }
334
335    pub fn kind(&self) -> RelocationKind {
336        self.kind
337    }
338
339    pub fn set_kind(&mut self, kind: RelocationKind) {
340        self.kind = kind;
341    }
342
343    pub fn module(&self) -> &RelocationModule {
344        &self.module
345    }
346
347    pub fn set_module(&mut self, module: RelocationModule) {
348        self.module = module;
349    }
350
351    pub fn destination_module(&self) -> Option<ModuleKind> {
352        match &self.module {
353            RelocationModule::None => None,
354            RelocationModule::Overlay { id } => Some(ModuleKind::Overlay(*id)),
355            RelocationModule::Overlays { .. } => None,
356            RelocationModule::Main => Some(ModuleKind::Arm9),
357            RelocationModule::Itcm => Some(ModuleKind::Autoload(AutoloadKind::Itcm)),
358            RelocationModule::Dtcm => Some(ModuleKind::Autoload(AutoloadKind::Dtcm)),
359            RelocationModule::Autoload { index } => {
360                Some(ModuleKind::Autoload(AutoloadKind::Unknown(*index)))
361            }
362        }
363    }
364
365    pub fn addend(&self) -> i64 {
366        i64::from(self.addend) + self.kind.addend()
367    }
368
369    pub fn addend_value(&self) -> i32 {
370        self.addend
371    }
372
373    pub fn set_addend(&mut self, addend: i32) {
374        self.addend = addend;
375    }
376
377    pub fn find_symbol_location<'a>(&self, symbol_map: &'a SymbolMap) -> Option<(&'a Symbol, u32)> {
378        let symbol = symbol_map
379            .first_symbol_before(self.from_address())
380            .and_then(|symbols| (!symbols.is_empty()).then_some(symbols[0].1))?;
381        let offset = self.from_address() - symbol.addr;
382
383        Some((symbol, offset))
384    }
385}
386
387impl Display for Relocation {
388    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
389        write!(f, "{}", self.comments.display_pre_comments())?;
390        write!(f, "from:{:#010x} kind:{}", self.from, self.kind)?;
391
392        match self.kind {
393            RelocationKind::OverlayId => write!(f, " to:{}", self.to)?,
394            RelocationKind::LinkTimeConst(_) => {}
395            _ => write!(f, " to:{:#010x}", self.to)?,
396        }
397
398        if self.addend != 0 {
399            write!(f, " add:{:#x}", self.addend)?;
400        }
401
402        match self.kind {
403            RelocationKind::OverlayId | RelocationKind::LinkTimeConst(_) => {}
404            _ => write!(f, " module:{}", self.module)?,
405        }
406
407        write!(f, "{}", self.comments.display_post_comment())?;
408        Ok(())
409    }
410}
411
412#[derive(Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
413pub enum RelocationKind {
414    ArmCall,
415    ThumbCall,
416    ArmCallThumb,
417    ThumbCallArm,
418    ArmBranch,
419    Load,
420    OverlayId,
421    LinkTimeConst(LinkTimeConst),
422}
423
424#[derive(Debug, Snafu)]
425pub enum RelocationKindParseError {
426    #[snafu(display(
427        "{context}: unknown relocation kind '{value}', must be one of:
428        arm_call, thumb_call, arm_call_thumb, thumb_call_arm, arm_branch, load, link_time_const(...):
429        {backtrace}"
430    ))]
431    UnknownKind { context: ParseContext, value: String, backtrace: Backtrace },
432    #[snafu(transparent)]
433    LinkTimeConstParse { source: LinkTimeConstParseError },
434}
435
436impl RelocationKind {
437    fn parse(value: &str, context: &ParseContext) -> Result<Self, RelocationKindParseError> {
438        match value {
439            "arm_call" => Ok(Self::ArmCall),
440            "thumb_call" => Ok(Self::ThumbCall),
441            "arm_call_thumb" => Ok(Self::ArmCallThumb),
442            "thumb_call_arm" => Ok(Self::ThumbCallArm),
443            "arm_branch" => Ok(Self::ArmBranch),
444            "load" => Ok(Self::Load),
445            "overlay_id" => Ok(Self::OverlayId),
446            value => {
447                if let Some(link_time_const) = value.strip_prefix("link_time_const(")
448                    && let Some(link_time_const) = link_time_const.strip_suffix(")")
449                {
450                    Ok(Self::LinkTimeConst(LinkTimeConst::parse(link_time_const, context)?))
451                } else {
452                    UnknownKindSnafu { context, value }.fail()
453                }
454            }
455        }
456    }
457
458    pub fn addend(&self) -> i64 {
459        match self {
460            Self::ArmCall => -8,
461            Self::ThumbCall => -4,
462            Self::ArmCallThumb => -8,
463            Self::ThumbCallArm => -4,
464            Self::ArmBranch => -8,
465            Self::Load => 0,
466            Self::OverlayId => 0,
467            Self::LinkTimeConst(_) => 0,
468        }
469    }
470}
471
472impl Display for RelocationKind {
473    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
474        match self {
475            Self::ArmCall => write!(f, "arm_call"),
476            Self::ThumbCall => write!(f, "thumb_call"),
477            Self::ArmCallThumb => write!(f, "arm_call_thumb"),
478            Self::ThumbCallArm => write!(f, "thumb_call_arm"),
479            Self::ArmBranch => write!(f, "arm_branch"),
480            Self::Load => write!(f, "load"),
481            Self::OverlayId => write!(f, "overlay_id"),
482            Self::LinkTimeConst(link_time_const) => write!(f, "link_time_const({link_time_const})"),
483        }
484    }
485}
486
487#[derive(PartialEq, Eq, Debug, Clone, Deserialize, Serialize)]
488pub enum RelocationModule {
489    None,
490    Overlay { id: u16 },
491    Overlays { ids: Vec<u16> },
492    Main,
493    Itcm,
494    Dtcm,
495    Autoload { index: u32 },
496}
497
498#[derive(Debug, Snafu)]
499pub enum RelocationFromModulesError {
500    #[snafu(display("Relocations to {module_kind} should be unambiguous:\n{backtrace}"))]
501    AmbiguousNonOverlayRelocation { module_kind: ModuleKind, backtrace: Backtrace },
502}
503
504#[derive(Debug, Snafu)]
505pub enum RelocationModuleParseError {
506    #[snafu(display(
507        "{context}: relocations to '{module}' have no options, but got '({options})':\n{backtrace}"
508    ))]
509    UnexpectedOptions {
510        context: ParseContext,
511        module: String,
512        options: String,
513        backtrace: Backtrace,
514    },
515    #[snafu(display("{context}: failed to parse overlay ID '{value}': {error}\n{backtrace}"))]
516    ParseOverlayId {
517        context: ParseContext,
518        value: String,
519        error: ParseIntError,
520        backtrace: Backtrace,
521    },
522    #[snafu(display(
523        "{context}: relocation to 'overlays' must have two or more overlay IDs, but got {ids:?}:\n{backtrace}"
524    ))]
525    ExpectedMultipleOverlays { context: ParseContext, ids: Vec<u16>, backtrace: Backtrace },
526    #[snafu(display("{context}: failed to parse autoload index '{value}': {error}\n{backtrace}"))]
527    ParseAutoloadIndex {
528        context: ParseContext,
529        value: String,
530        error: ParseIntError,
531        backtrace: Backtrace,
532    },
533    #[snafu(display(
534        "{context}: unknown relocation to '{module}', must be one of: overlays, overlay, main, itcm, dtcm, none:\n{backtrace}"
535    ))]
536    UnknownModule { context: ParseContext, module: String, backtrace: Backtrace },
537}
538
539impl RelocationModule {
540    pub fn from_modules<'a, I>(mut modules: I) -> Result<Self, RelocationFromModulesError>
541    where
542        I: Iterator<Item = &'a Module>,
543    {
544        let Some(first) = modules.next() else { return Ok(Self::None) };
545
546        let module_kind = first.kind();
547        match module_kind {
548            ModuleKind::Arm9 => {
549                if modules.next().is_some() {
550                    return AmbiguousNonOverlayRelocationSnafu { module_kind }.fail();
551                }
552                Ok(Self::Main)
553            }
554            ModuleKind::Autoload(kind) => {
555                if modules.next().is_some() {
556                    return AmbiguousNonOverlayRelocationSnafu { module_kind }.fail();
557                }
558                match kind {
559                    AutoloadKind::Itcm => Ok(Self::Itcm),
560                    AutoloadKind::Dtcm => Ok(Self::Dtcm),
561                    AutoloadKind::Unknown(index) => Ok(Self::Autoload { index }),
562                }
563            }
564            ModuleKind::Overlay(id) => {
565                let ids = iter::once(first)
566                    .chain(modules)
567                    .map(|module| {
568                        if let ModuleKind::Overlay(id) = module.kind() {
569                            Ok(id)
570                        } else {
571                            AmbiguousNonOverlayRelocationSnafu { module_kind: module.kind() }.fail()
572                        }
573                    })
574                    .collect::<Result<Vec<_>, _>>()?;
575                if ids.len() > 1 {
576                    Ok(Self::Overlays { ids })
577                } else {
578                    Ok(Self::Overlay { id })
579                }
580            }
581        }
582    }
583
584    fn parse(text: &str, context: &ParseContext) -> Result<Self, Box<RelocationModuleParseError>> {
585        let (value, options) = text.split_once('(').unwrap_or((text, ""));
586        let options = options.strip_suffix(')').unwrap_or(options);
587
588        match value {
589            "none" => {
590                if options.is_empty() {
591                    Ok(Self::None)
592                } else {
593                    Err(Box::new(
594                        UnexpectedOptionsSnafu { context, module: "none", options }.build(),
595                    ))
596                }
597            }
598            "overlay" => Ok(Self::Overlay {
599                id: parse_u16(options).map_err(|error| {
600                    ParseOverlayIdSnafu { context, value: options, error }.build()
601                })?,
602            }),
603            "overlays" => {
604                let ids = options
605                    .split(',')
606                    .map(|x| {
607                        parse_u16(x).map_err(|error| {
608                            Box::new(ParseOverlayIdSnafu { context, value: x, error }.build())
609                        })
610                    })
611                    .collect::<Result<Vec<_>, _>>()?;
612                if ids.len() < 2 {
613                    Err(Box::new(ExpectedMultipleOverlaysSnafu { context, ids }.build()))
614                } else {
615                    Ok(Self::Overlays { ids })
616                }
617            }
618            "main" => {
619                if options.is_empty() {
620                    Ok(Self::Main)
621                } else {
622                    Err(Box::new(
623                        UnexpectedOptionsSnafu { context, module: "main", options }.build(),
624                    ))
625                }
626            }
627            "itcm" => {
628                if options.is_empty() {
629                    Ok(Self::Itcm)
630                } else {
631                    Err(Box::new(
632                        UnexpectedOptionsSnafu { context, module: "itcm", options }.build(),
633                    ))
634                }
635            }
636            "dtcm" => {
637                if options.is_empty() {
638                    Ok(Self::Dtcm)
639                } else {
640                    Err(Box::new(
641                        UnexpectedOptionsSnafu { context, module: "dtcm", options }.build(),
642                    ))
643                }
644            }
645            "autoload" => Ok(Self::Autoload {
646                index: parse_u32(options).map_err(|error| {
647                    ParseAutoloadIndexSnafu { context, value: options, error }.build()
648                })?,
649            }),
650            _ => Err(Box::new(UnknownModuleSnafu { context, module: value }.build())),
651        }
652    }
653}
654
655impl From<ModuleKind> for RelocationModule {
656    fn from(value: ModuleKind) -> Self {
657        match value {
658            ModuleKind::Arm9 => Self::Main,
659            ModuleKind::Overlay(id) => Self::Overlay { id },
660            ModuleKind::Autoload(kind) => match kind {
661                AutoloadKind::Itcm => Self::Itcm,
662                AutoloadKind::Dtcm => Self::Dtcm,
663                AutoloadKind::Unknown(index) => Self::Autoload { index },
664            },
665        }
666    }
667}
668
669impl Display for RelocationModule {
670    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
671        match self {
672            RelocationModule::None => write!(f, "none"),
673            RelocationModule::Overlay { id } => write!(f, "overlay({id})"),
674            RelocationModule::Overlays { ids } => {
675                write!(f, "overlays({}", ids[0])?;
676                for id in &ids[1..] {
677                    write!(f, ",{id}")?;
678                }
679                write!(f, ")")?;
680                Ok(())
681            }
682            RelocationModule::Main => write!(f, "main"),
683            RelocationModule::Itcm => write!(f, "itcm"),
684            RelocationModule::Dtcm => write!(f, "dtcm"),
685            RelocationModule::Autoload { index } => write!(f, "autoload({index})"),
686        }
687    }
688}