mapfile_parser/
section.rs

1/* SPDX-FileCopyrightText: © 2023-2025 Decompollaborate */
2/* SPDX-License-Identifier: MIT */
3
4use std::{
5    fmt::Write,
6    hash::{Hash, Hasher},
7    path::{Path, PathBuf},
8};
9
10#[cfg(feature = "python_bindings")]
11use pyo3::prelude::*;
12
13#[cfg(feature = "serde")]
14use serde::{Deserialize, Serialize};
15
16use crate::{symbol, utils, SymbolDecompStateIter};
17
18#[derive(Debug, Clone)]
19#[non_exhaustive]
20#[cfg_attr(feature = "python_bindings", pyclass(module = "mapfile_parser"))]
21#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
22pub struct Section {
23    pub filepath: PathBuf,
24
25    pub vram: u64,
26
27    pub size: u64,
28
29    pub section_type: String,
30
31    pub vrom: Option<u64>,
32
33    pub align: Option<u64>,
34
35    pub is_fill: bool,
36
37    pub symbols: Vec<symbol::Symbol>,
38}
39
40impl Section {
41    pub fn new(
42        filepath: PathBuf,
43        vram: u64,
44        size: u64,
45        section_type: &str,
46        vrom: Option<u64>,
47        align: Option<u64>,
48    ) -> Self {
49        Self::new_impl(filepath, vram, size, section_type, vrom, align, false)
50    }
51
52    pub(crate) fn new_impl(
53        filepath: PathBuf,
54        vram: u64,
55        size: u64,
56        section_type: &str,
57        vrom: Option<u64>,
58        align: Option<u64>,
59        is_fill: bool,
60    ) -> Self {
61        Self {
62            filepath,
63            vram,
64            size,
65            section_type: section_type.into(),
66            vrom,
67            align,
68            is_fill,
69            symbols: Vec::new(),
70        }
71    }
72
73    pub fn is_noload_section(&self) -> bool {
74        utils::is_noload_section(&self.section_type)
75    }
76
77    pub fn find_symbol_by_name(&self, sym_name: &str) -> Option<&symbol::Symbol> {
78        self.symbols.iter().find(|&sym| sym.name == sym_name)
79    }
80
81    pub fn find_symbol_and_index_by_name(
82        &self,
83        sym_name: &str,
84    ) -> Option<(&symbol::Symbol, usize)> {
85        for (index, sym) in self.symbols.iter().enumerate() {
86            if sym.name == sym_name {
87                return Some((sym, index));
88            }
89        }
90        None
91    }
92
93    pub fn find_symbol_by_name_mut(&mut self, sym_name: &str) -> Option<&mut symbol::Symbol> {
94        self.symbols.iter_mut().find(|sym| sym.name == sym_name)
95    }
96
97    #[deprecated(
98        since = "2.7.0",
99        note = "Use `find_symbol_by_vram` or `find_symbol_by_vrom` instead."
100    )]
101    pub fn find_symbol_by_vram_or_vrom(&self, address: u64) -> Option<(&symbol::Symbol, i64)> {
102        let mut prev_sym: Option<&symbol::Symbol> = None;
103
104        let is_vram = address >= 0x1000000;
105
106        for sym in &self.symbols {
107            if sym.vram == address {
108                return Some((sym, 0));
109            }
110            if let Some(sym_vrom_temp) = sym.vrom {
111                if sym_vrom_temp == address {
112                    return Some((sym, 0));
113                }
114            }
115
116            if let Some(prev_sym_temp) = prev_sym {
117                if let Some(sym_vrom) = sym.vrom {
118                    if sym_vrom > address {
119                        if let Some(prev_vrom_temp) = prev_sym_temp.vrom {
120                            let offset = address as i64 - prev_vrom_temp as i64;
121                            if offset < 0 {
122                                return None;
123                            }
124                            return Some((prev_sym_temp, offset));
125                        }
126                    }
127                }
128                if is_vram && sym.vram > address {
129                    let offset = address as i64 - prev_sym_temp.vram as i64;
130                    if offset < 0 {
131                        return None;
132                    }
133                    return Some((prev_sym_temp, offset));
134                }
135            }
136
137            prev_sym = Some(sym);
138        }
139
140        if let Some(prev_sym_temp) = prev_sym {
141            if let Some(prev_sym_temp_vrom) = prev_sym_temp.vrom {
142                if prev_sym_temp_vrom + prev_sym_temp.size > address {
143                    let offset = address as i64 - prev_sym_temp_vrom as i64;
144                    if offset < 0 {
145                        return None;
146                    }
147                    return Some((prev_sym_temp, offset));
148                }
149            }
150
151            if is_vram && prev_sym_temp.vram + prev_sym_temp.size > address {
152                let offset = address as i64 - prev_sym_temp.vram as i64;
153                if offset < 0 {
154                    return None;
155                }
156                return Some((prev_sym_temp, offset));
157            }
158        }
159
160        None
161    }
162
163    pub fn find_symbol_by_vram(&self, address: u64) -> Option<(&symbol::Symbol, i64)> {
164        let mut prev_sym: Option<&symbol::Symbol> = None;
165
166        for sym in &self.symbols {
167            if sym.vram == address {
168                return Some((sym, 0));
169            }
170
171            if let Some(prev_sym_temp) = prev_sym {
172                if sym.vram > address {
173                    let offset = address as i64 - prev_sym_temp.vram as i64;
174                    if offset < 0 {
175                        return None;
176                    }
177                    return Some((prev_sym_temp, offset));
178                }
179            }
180
181            prev_sym = Some(sym);
182        }
183
184        if let Some(prev_sym_temp) = prev_sym {
185            if prev_sym_temp.vram + prev_sym_temp.size > address {
186                let offset = address as i64 - prev_sym_temp.vram as i64;
187                if offset < 0 {
188                    return None;
189                }
190                return Some((prev_sym_temp, offset));
191            }
192        }
193
194        None
195    }
196
197    pub fn find_symbol_by_vrom(&self, address: u64) -> Option<(&symbol::Symbol, i64)> {
198        let mut prev_sym: Option<&symbol::Symbol> = None;
199
200        for sym in &self.symbols {
201            if let Some(sym_vrom_temp) = sym.vrom {
202                if sym_vrom_temp == address {
203                    return Some((sym, 0));
204                }
205            }
206
207            if let Some(prev_sym_temp) = prev_sym {
208                if let Some(sym_vrom) = sym.vrom {
209                    if sym_vrom > address {
210                        if let Some(prev_vrom_temp) = prev_sym_temp.vrom {
211                            let offset = address as i64 - prev_vrom_temp as i64;
212                            if offset < 0 {
213                                return None;
214                            }
215                            return Some((prev_sym_temp, offset));
216                        }
217                    }
218                }
219            }
220
221            prev_sym = Some(sym);
222        }
223
224        if let Some(prev_sym_temp) = prev_sym {
225            if let Some(prev_sym_temp_vrom) = prev_sym_temp.vrom {
226                if prev_sym_temp_vrom + prev_sym_temp.size > address {
227                    let offset = address as i64 - prev_sym_temp_vrom as i64;
228                    if offset < 0 {
229                        return None;
230                    }
231                    return Some((prev_sym_temp, offset));
232                }
233            }
234        }
235
236        None
237    }
238
239    #[deprecated(
240        since = "2.8.0",
241        note = "This functionality is perform automatically during parsing now."
242    )]
243    pub fn fixup_non_matching_symbols(&mut self) {
244        // This is a no-op now
245    }
246
247    pub fn to_csv_header(print_vram: bool) -> String {
248        let mut ret = String::new();
249
250        if print_vram {
251            ret.push_str("VRAM,");
252        }
253        ret.push_str("File,Section type,Num symbols,Max size,Total size,Average size");
254        ret
255    }
256
257    pub fn to_csv(&self, print_vram: bool) -> String {
258        let mut ret = String::new();
259
260        // Calculate stats
261        let sym_count = self.symbols.len() as u64;
262        let mut max_size = 0;
263        let average_size = if sym_count > 0 {
264            self.size as f64 / sym_count as f64
265        } else {
266            self.size as f64 / 1.0
267        };
268
269        for sym in &self.symbols {
270            if sym.size > max_size {
271                max_size = sym.size;
272            }
273        }
274
275        if print_vram {
276            //ret.push_str(format!("{:08X}", self.vram));
277            write!(ret, "{:08X},", self.vram).unwrap();
278            //ret += f"{self.vram:08X},";
279        }
280        write!(
281            ret,
282            "{},{},{},{},{},{:0.2}",
283            self.filepath.display(),
284            self.section_type,
285            sym_count,
286            max_size,
287            self.size,
288            average_size
289        )
290        .unwrap();
291
292        ret
293    }
294
295    pub fn print_csv_header(print_vram: bool) {
296        println!("{}", Self::to_csv_header(print_vram));
297    }
298
299    pub fn print_as_csv(&self, print_vram: bool) {
300        println!("{}", self.to_csv(print_vram));
301    }
302}
303
304impl Section {
305    pub(crate) fn new_default(
306        filepath: std::path::PathBuf,
307        vram: u64,
308        size: u64,
309        section_type: &str,
310    ) -> Self {
311        Section {
312            filepath,
313            vram,
314            size,
315            section_type: section_type.into(),
316            vrom: None,
317            align: None,
318            is_fill: false,
319            symbols: Vec::new(),
320        }
321    }
322
323    pub(crate) fn new_placeholder() -> Self {
324        Self {
325            filepath: "".into(),
326            vram: 0,
327            size: 0,
328            section_type: "".into(),
329            vrom: None,
330            align: None,
331            is_fill: false,
332            symbols: Vec::new(),
333        }
334    }
335
336    pub(crate) fn new_fill(
337        filepath: std::path::PathBuf,
338        vram: u64,
339        size: u64,
340        section_type: &str,
341    ) -> Self {
342        Self {
343            filepath,
344            vram,
345            size,
346            section_type: section_type.into(),
347            vrom: None,
348            align: None,
349            is_fill: true,
350            symbols: Vec::new(),
351        }
352    }
353
354    pub fn is_placeholder(&self) -> bool {
355        self.filepath.as_os_str().is_empty()
356            && self.vram == 0
357            && self.size == 0
358            && self.section_type.is_empty()
359            && self.vrom.is_none()
360            && self.align.is_none()
361            && self.symbols.is_empty()
362    }
363
364    pub fn symbol_match_state_iter(
365        &self,
366        path_decomp_settings: Option<&PathDecompSettings>,
367    ) -> SymbolDecompStateIter {
368        let mut whole_file_is_undecomped = false;
369        let mut functions_path = None;
370
371        if let Some(path_decomp_settings) = path_decomp_settings {
372            let original_file_path: PathBuf = self
373                .filepath
374                .components()
375                .skip(path_decomp_settings.path_index)
376                .collect();
377
378            let mut extensionless_file_path = original_file_path;
379            while extensionless_file_path.extension().is_some() {
380                extensionless_file_path.set_extension("");
381            }
382
383            let full_asm_file = path_decomp_settings
384                .asm_path
385                .join(extensionless_file_path.with_extension("s"));
386            whole_file_is_undecomped = full_asm_file.exists();
387            functions_path = path_decomp_settings
388                .nonmatchings
389                .map(|x| x.join(extensionless_file_path.clone()));
390        }
391
392        SymbolDecompStateIter::new(self, whole_file_is_undecomped, functions_path)
393    }
394}
395
396pub struct PathDecompSettings<'ap, 'np> {
397    pub asm_path: &'ap Path,
398    pub path_index: usize,
399    pub nonmatchings: Option<&'np Path>,
400}
401
402// https://doc.rust-lang.org/std/cmp/trait.Eq.html
403impl PartialEq for Section {
404    fn eq(&self, other: &Self) -> bool {
405        self.filepath == other.filepath && self.section_type == other.section_type
406    }
407}
408impl Eq for Section {}
409
410// https://doc.rust-lang.org/std/hash/trait.Hash.html
411impl Hash for Section {
412    fn hash<H: Hasher>(&self, state: &mut H) {
413        self.filepath.hash(state);
414        self.section_type.hash(state);
415    }
416}
417
418#[cfg(feature = "python_bindings")]
419#[allow(non_snake_case)]
420pub(crate) mod python_bindings {
421    use pyo3::{intern, prelude::*, IntoPyObjectExt};
422
423    use std::path::PathBuf;
424
425    // Required to call the `.hash` and `.finish` methods, which are defined on traits.
426    use std::hash::{Hash, Hasher};
427
428    use crate::symbol;
429
430    use std::collections::hash_map::DefaultHasher;
431
432    use super::*;
433
434    #[pymethods]
435    impl Section {
436        #[new]
437        #[pyo3(signature = (filepath, vram, size, section_type, vrom=None, align=None, is_fill=false))]
438        fn py_new(
439            filepath: PathBuf,
440            vram: u64,
441            size: u64,
442            section_type: &str,
443            vrom: Option<u64>,
444            align: Option<u64>,
445            is_fill: bool,
446        ) -> Self {
447            Self::new_impl(filepath, vram, size, section_type, vrom, align, is_fill)
448        }
449
450        /* Getters and setters */
451
452        // Manually convert PathBuf into a pathlib.Path object since pyo3 refuses to do so
453        #[getter]
454        fn get_filepath(&self) -> PyResult<PyObject> {
455            Python::with_gil(|py| {
456                let pathlib = py.import("pathlib")?;
457                let pathlib_path = pathlib.getattr(intern!(py, "Path"))?;
458                let args = (self.filepath.clone(),);
459
460                pathlib_path.call1(args)?.into_py_any(py)
461            })
462        }
463
464        #[setter]
465        fn set_filepath(&mut self, value: PathBuf) -> PyResult<()> {
466            self.filepath = value;
467            Ok(())
468        }
469
470        #[getter]
471        fn get_vram(&self) -> PyResult<u64> {
472            Ok(self.vram)
473        }
474
475        #[setter]
476        fn set_vram(&mut self, value: u64) -> PyResult<()> {
477            self.vram = value;
478            Ok(())
479        }
480
481        #[getter]
482        fn get_size(&self) -> PyResult<u64> {
483            Ok(self.size)
484        }
485
486        #[setter]
487        fn set_size(&mut self, value: u64) -> PyResult<()> {
488            self.size = value;
489            Ok(())
490        }
491
492        #[getter]
493        fn get_sectionType(&self) -> PyResult<String> {
494            Ok(self.section_type.clone())
495        }
496
497        #[setter]
498        fn set_sectionType(&mut self, value: String) -> PyResult<()> {
499            self.section_type = value;
500            Ok(())
501        }
502
503        #[getter]
504        fn get_vrom(&self) -> PyResult<Option<u64>> {
505            Ok(self.vrom)
506        }
507
508        #[setter]
509        fn set_vrom(&mut self, value: Option<u64>) -> PyResult<()> {
510            self.vrom = value;
511            Ok(())
512        }
513
514        #[getter]
515        fn get_align(&self) -> PyResult<Option<u64>> {
516            Ok(self.align)
517        }
518
519        #[setter]
520        fn set_align(&mut self, value: Option<u64>) -> PyResult<()> {
521            self.align = value;
522            Ok(())
523        }
524
525        #[getter]
526        fn get_isFill(&self) -> PyResult<bool> {
527            Ok(self.is_fill)
528        }
529
530        #[setter]
531        fn set_isFill(&mut self, value: bool) -> PyResult<()> {
532            self.is_fill = value;
533            Ok(())
534        }
535
536        /*
537        #[getter]
538        fn get__symbols(&self) -> PyResult<Vec<symbol::Symbol>> {
539            Ok(self.symbols)
540        }
541
542        #[setter]
543        fn set__symbols(&mut self, value: Vec<symbol::Symbol>) -> PyResult<()> {
544            self.symbols = value;
545            Ok(())
546        }
547        */
548
549        #[getter]
550        fn isNoloadSection(&self) -> bool {
551            self.is_noload_section()
552        }
553
554        /* Methods */
555
556        // ! @deprecated
557        fn getName(&self) -> PathBuf {
558            self.filepath
559                .with_extension("")
560                .components()
561                .skip(2)
562                .collect()
563        }
564
565        fn findSymbolByName(&self, sym_name: &str) -> Option<symbol::Symbol> {
566            self.find_symbol_by_name(sym_name).cloned()
567        }
568
569        fn findSymbolByVramOrVrom(&self, address: u64) -> Option<(symbol::Symbol, i64)> {
570            #[allow(deprecated)]
571            if let Some((sym, offset)) = self.find_symbol_by_vram_or_vrom(address) {
572                Some((sym.clone(), offset))
573            } else {
574                None
575            }
576        }
577
578        fn findSymbolByVram(&self, address: u64) -> Option<(symbol::Symbol, i64)> {
579            if let Some((sym, offset)) = self.find_symbol_by_vram(address) {
580                Some((sym.clone(), offset))
581            } else {
582                None
583            }
584        }
585
586        fn findSymbolByVrom(&self, address: u64) -> Option<(symbol::Symbol, i64)> {
587            if let Some((sym, offset)) = self.find_symbol_by_vrom(address) {
588                Some((sym.clone(), offset))
589            } else {
590                None
591            }
592        }
593
594        fn fixupNonMatchingSymbols(&mut self) {
595            #[allow(deprecated)]
596            self.fixup_non_matching_symbols()
597        }
598
599        #[staticmethod]
600        #[pyo3(signature=(print_vram=true))]
601        fn toCsvHeader(print_vram: bool) -> String {
602            Self::to_csv_header(print_vram)
603        }
604
605        #[pyo3(signature=(print_vram=true))]
606        fn toCsv(&self, print_vram: bool) -> String {
607            self.to_csv(print_vram)
608        }
609
610        #[staticmethod]
611        #[pyo3(signature=(print_vram=true))]
612        fn printCsvHeader(print_vram: bool) {
613            Self::print_csv_header(print_vram)
614        }
615
616        #[pyo3(signature=(print_vram=true))]
617        fn printAsCsv(&self, print_vram: bool) {
618            self.print_as_csv(print_vram)
619        }
620
621        /*
622        def toJson(self, humanReadable: bool=True) -> dict[str, Any]:
623            fileDict: dict[str, Any] = {
624                "filepath": str(self.filepath),
625                "sectionType": self.sectionType,
626                "vram": self.serializeVram(humanReadable=humanReadable),
627                "size": self.serializeSize(humanReadable=humanReadable),
628                "vrom": self.serializeVrom(humanReadable=humanReadable),
629            }
630
631            symbolsList = []
632            for symbol in self._symbols:
633                symbolsList.append(symbol.toJson(humanReadable=humanReadable))
634
635            fileDict["symbols"] = symbolsList
636            return fileDict
637        */
638
639        fn copySymbolList(&self) -> Vec<symbol::Symbol> {
640            self.symbols.clone()
641        }
642
643        fn setSymbolList(&mut self, new_list: Vec<symbol::Symbol>) {
644            self.symbols = new_list;
645        }
646
647        fn appendSymbol(&mut self, sym: symbol::Symbol) {
648            self.symbols.push(sym);
649        }
650
651        fn __iter__(slf: PyRef<'_, Self>) -> PyResult<Py<SymbolVecIter>> {
652            let iter = SymbolVecIter {
653                inner: slf.symbols.clone().into_iter(),
654            };
655            Py::new(slf.py(), iter)
656        }
657
658        fn __getitem__(&self, index: usize) -> symbol::Symbol {
659            self.symbols[index].clone()
660        }
661
662        fn __setitem__(&mut self, index: usize, element: symbol::Symbol) {
663            self.symbols[index] = element;
664        }
665
666        fn __len__(&self) -> usize {
667            self.symbols.len()
668        }
669
670        fn __eq__(&self, other: &Self) -> bool {
671            self == other
672        }
673
674        fn __hash__(&self) -> isize {
675            let mut hasher = DefaultHasher::new();
676            self.hash(&mut hasher);
677            hasher.finish() as isize
678        }
679
680        // TODO: __str__ and __repr__
681    }
682
683    #[pyclass]
684    struct SymbolVecIter {
685        inner: std::vec::IntoIter<symbol::Symbol>,
686    }
687
688    #[pymethods]
689    impl SymbolVecIter {
690        fn __iter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> {
691            slf
692        }
693
694        fn __next__(mut slf: PyRefMut<'_, Self>) -> Option<symbol::Symbol> {
695            slf.inner.next()
696        }
697    }
698}