1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
//! This module contains structs and methods for reading, writing, and manipulating different
//! symbol data formats.
//!
//! The code for each data format is separated into its own module, including the `resymgen` YAML
//! format itself (the [`symgen_yml`] module).

pub mod ghidra;
pub mod ghidra_csv;
pub mod json;
pub mod sym;
pub mod symgen_yml;

use std::error::Error;
use std::fs::File;
use std::io::{Read, Write};
use std::path::Path;

use ghidra::GhidraFormatter;
use ghidra_csv::CsvLoader;
use json::JsonFormatter;
use sym::SymFormatter;
pub use symgen_yml::Generate;
use symgen_yml::{Load, LoadParams, Subregion, SymGen, Symbol};

// `OutFormat` is like a poor man's version of trait objects for Generate. Real trait objects don't
// work because `Generate` isn't object-safe (generate() is generic), so we can't use dynamic
// polymorphism and need to define an enum instead for static polymorphism (the idea is to be able
// to make an array of `Generate` objects).

/// Output formats that can be generated from the [`resymgen` YAML] format
/// (all types that are [`Generate`]).
///
/// [`resymgen` YAML]: symgen_yml
#[derive(Debug, Clone, Copy)]
pub enum OutFormat {
    /// [`ghidra`] format
    Ghidra,
    /// [`sym`] format
    Sym,
    /// [`json`] format
    Json,
}

// Technically this makes it redundant to impl Generate for the individual formatters, but I think
// the trait still adds clarity, even though it doesn't add utility :)
impl Generate for OutFormat {
    fn generate<W: Write>(
        &self,
        writer: W,
        symgen: &SymGen,
        version: &str,
    ) -> Result<(), Box<dyn Error>> {
        match self {
            Self::Ghidra => GhidraFormatter {}.generate(writer, symgen, version),
            Self::Sym => SymFormatter {}.generate(writer, symgen, version),
            Self::Json => JsonFormatter {}.generate(writer, symgen, version),
        }
    }
}

impl OutFormat {
    /// Returns the [`OutFormat`] corresponding to the given `name`, if there is one.
    pub fn from(name: &str) -> Option<Self> {
        match name {
            "ghidra" => Some(Self::Ghidra),
            "sym" => Some(Self::Sym),
            "json" => Some(Self::Json),
            _ => None,
        }
    }
    /// Returns the file extension associated with the [`OutFormat`].
    pub fn extension(&self) -> String {
        match self {
            Self::Ghidra => String::from("ghidra"),
            Self::Sym => String::from("sym"),
            Self::Json => String::from("json"),
        }
    }
    /// Returns an [`Iterator`] over all [`OutFormat`] variants.
    pub fn all() -> impl Iterator<Item = OutFormat> {
        [Self::Ghidra, Self::Sym, Self::Json].iter().copied()
    }
}

/// Input formats that can be merged into a symbol table in the [`resymgen` YAML] format
/// (all types that are [`Load`]).
///
/// [`resymgen` YAML]: symgen_yml
#[derive(Debug, Clone, Copy)]
pub enum InFormat {
    /// [`resymgen` YAML] format.
    ///
    /// [`resymgen` YAML]: symgen_yml
    Yaml,
    /// A specific [CSV] format exported from Ghidra projects.
    ///
    /// [CSV]: ghidra_csv
    Csv,
}

impl InFormat {
    /// Returns the [`InFormat`] corresponding to the given `name`, if there is one.
    pub fn from(name: &str) -> Option<Self> {
        match name {
            "yml" => Some(Self::Yaml),
            "csv" => Some(Self::Csv),
            _ => None,
        }
    }
    /// Returns the file extension associated with the [`InFormat`].
    pub fn extension(&self) -> String {
        match self {
            Self::Yaml => String::from("yml"),
            Self::Csv => String::from("csv"),
        }
    }
    /// Returns an [`Iterator`] over all [`InFormat`] variants.
    pub fn all() -> impl Iterator<Item = InFormat> {
        [Self::Yaml, Self::Csv].iter().copied()
    }

    /// Reads data from `rdr` in the format specified by the [`InFormat`], and merges it into
    /// `symgen` using the options specified in `params`.
    ///
    /// If a `file_name` is provided, it may be used for subregion resolution, depending on the
    /// [`InFormat`].
    pub fn merge<R, P>(
        &self,
        symgen: &mut SymGen,
        rdr: R,
        file_name: Option<P>,
        params: &LoadParams,
    ) -> Result<Vec<Symbol>, Box<dyn Error>>
    where
        R: Read,
        P: AsRef<Path>,
    {
        let unmerged = match self {
            Self::Yaml => {
                let mut other = SymGen::read_no_init(rdr)?;
                if let Some(file_name) = file_name {
                    other
                        .resolve_subregions(Subregion::subregion_dir(file_name.as_ref()), |p| {
                            File::open(p)
                        })?;
                }
                symgen.merge_symgen(&other)?;
                Vec::new()
            }
            Self::Csv => symgen.merge_symbols(CsvLoader::load(rdr, params)?)?,
        };
        Ok(unmerged)
    }
}