Skip to content

Commit a046bf9

Browse files
committed
Updates for build system integration
- Use a config file as input to `dol split` - Add depfile output - Adjust splits config writing
1 parent 4b6e317 commit a046bf9

File tree

9 files changed

+141
-37
lines changed

9 files changed

+141
-37
lines changed

Cargo.lock

Lines changed: 13 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ name = "decomp-toolkit"
33
description = "Yet another GameCube/Wii decompilation toolkit."
44
authors = ["Luke Street <luke@street.dev>"]
55
license = "MIT OR Apache-2.0"
6-
version = "0.3.1"
6+
version = "0.3.2"
77
edition = "2021"
88
publish = false
99
build = "build.rs"
@@ -49,6 +49,7 @@ petgraph = "0.6.3"
4949
ppc750cl = { git = "https://github.com/encounter/ppc750cl", rev = "5f6e991bf495388c4104f188d2e90c79da9f78de" }
5050
regex = "1.9.0"
5151
serde = "1.0.166"
52+
serde_json = "1.0.104"
5253
serde_repr = "0.1.14"
5354
serde_yaml = "0.9.22"
5455
sha-1 = "0.10.1"

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,9 +269,11 @@ $ dtk dol info input.dol
269269
Analyzes and splits a DOL file into relocatable objects based on user configuration.
270270

271271
```shell
272-
$ dtk dol split input.dol target -s config/symbols.txt -p config/splits.txt
272+
$ dtk dol split config.yml target
273273
```
274274

275+
TODO: document configuration file
276+
275277
### dwarf dump
276278

277279
Dumps DWARF 1.1 information from an ELF file. (Does **not** support DWARF 2+)

src/cmd/dol.rs

Lines changed: 87 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use std::{
88

99
use anyhow::{anyhow, bail, Context, Result};
1010
use argp::FromArgs;
11+
use serde::{Deserialize, Serialize};
1112

1213
use crate::{
1314
analysis::{
@@ -24,6 +25,7 @@ use crate::{
2425
util::{
2526
asm::write_asm,
2627
config::{apply_splits, parse_symbol_line, write_splits, write_symbols},
28+
dep::DepFile,
2729
dol::process_dol,
2830
elf::{process_elf, write_elf},
2931
file::{map_file, map_reader, touch},
@@ -60,25 +62,45 @@ pub struct InfoArgs {
6062
#[argp(subcommand, name = "split")]
6163
pub struct SplitArgs {
6264
#[argp(positional)]
63-
/// input file
64-
in_file: PathBuf,
65+
/// input configuration file
66+
config: PathBuf,
6567
#[argp(positional)]
6668
/// output directory
6769
out_dir: PathBuf,
68-
#[argp(option, short = 's')]
69-
/// path to symbols file
70-
symbols_file: Option<PathBuf>,
71-
#[argp(option, short = 'p')]
72-
/// path to splits file
73-
splits_file: Option<PathBuf>,
74-
#[argp(option, short = 'e')]
75-
/// ELF file to validate against (debugging only)
76-
elf_file: Option<PathBuf>,
7770
#[argp(switch)]
7871
/// skip updating splits & symbol files (for build systems)
7972
no_update: bool,
8073
}
8174

75+
#[inline]
76+
fn bool_true() -> bool { true }
77+
78+
#[derive(Serialize, Deserialize, Debug, Clone)]
79+
pub struct ProjectConfig {
80+
pub object: PathBuf,
81+
pub splits: Option<PathBuf>,
82+
pub symbols: Option<PathBuf>,
83+
// Analysis options
84+
#[serde(default = "bool_true")]
85+
pub detect_objects: bool,
86+
#[serde(default = "bool_true")]
87+
pub detect_strings: bool,
88+
#[serde(default = "bool_true")]
89+
pub write_asm: bool,
90+
}
91+
92+
#[derive(Serialize, Deserialize, Debug, Clone)]
93+
pub struct OutputUnit {
94+
pub object: PathBuf,
95+
pub name: String,
96+
pub autogenerated: bool,
97+
}
98+
99+
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
100+
pub struct OutputConfig {
101+
pub units: Vec<OutputUnit>,
102+
}
103+
82104
pub fn run(args: Args) -> Result<()> {
83105
match args.command {
84106
SubCommand::Info(c_args) => info(c_args),
@@ -128,10 +150,20 @@ fn info(args: InfoArgs) -> Result<()> {
128150
}
129151

130152
fn split(args: SplitArgs) -> Result<()> {
131-
log::info!("Loading {}", args.in_file.display());
132-
let mut obj = process_dol(&args.in_file)?;
153+
log::info!("Loading {}", args.config.display());
154+
let mut config_file = File::open(&args.config)
155+
.with_context(|| format!("Failed to open config file '{}'", args.config.display()))?;
156+
let config: ProjectConfig = serde_yaml::from_reader(&mut config_file)?;
133157

134-
if let Some(splits_path) = &args.splits_file {
158+
let out_config_path = args.out_dir.join("config.json");
159+
let mut dep = DepFile::new(out_config_path.clone());
160+
161+
log::info!("Loading {}", config.object.display());
162+
let mut obj = process_dol(&config.object)?;
163+
dep.push(config.object.clone());
164+
165+
if let Some(splits_path) = &config.splits {
166+
dep.push(splits_path.clone());
135167
if splits_path.is_file() {
136168
let map = map_file(splits_path)?;
137169
apply_splits(map_reader(&map), &mut obj)?;
@@ -140,7 +172,8 @@ fn split(args: SplitArgs) -> Result<()> {
140172

141173
let mut state = AnalyzerState::default();
142174

143-
if let Some(symbols_path) = &args.symbols_file {
175+
if let Some(symbols_path) = &config.symbols {
176+
dep.push(symbols_path.clone());
144177
if symbols_path.is_file() {
145178
let map = map_file(symbols_path)?;
146179
for result in map_reader(&map).lines() {
@@ -176,22 +209,29 @@ fn split(args: SplitArgs) -> Result<()> {
176209
log::info!("Applying relocations");
177210
tracker.apply(&mut obj, false)?;
178211

179-
log::info!("Detecting object boundaries");
180-
detect_object_boundaries(&mut obj)?;
212+
if config.detect_objects {
213+
log::info!("Detecting object boundaries");
214+
detect_object_boundaries(&mut obj)?;
215+
}
181216

182-
log::info!("Detecting strings");
183-
detect_strings(&mut obj)?;
217+
if config.detect_strings {
218+
log::info!("Detecting strings");
219+
detect_strings(&mut obj)?;
220+
}
221+
222+
log::info!("Adjusting splits");
223+
update_splits(&mut obj)?;
184224

185225
if !args.no_update {
186-
if let Some(symbols_path) = &args.symbols_file {
226+
if let Some(symbols_path) = &config.symbols {
187227
let mut symbols_writer = BufWriter::new(
188228
File::create(symbols_path)
189229
.with_context(|| format!("Failed to create '{}'", symbols_path.display()))?,
190230
);
191231
write_symbols(&mut symbols_writer, &obj)?;
192232
}
193233

194-
if let Some(splits_path) = &args.splits_file {
234+
if let Some(splits_path) = &config.splits {
195235
let mut splits_writer = BufWriter::new(
196236
File::create(splits_path)
197237
.with_context(|| format!("Failed to create '{}'", splits_path.display()))?,
@@ -200,9 +240,6 @@ fn split(args: SplitArgs) -> Result<()> {
200240
}
201241
}
202242

203-
log::info!("Adjusting splits");
204-
update_splits(&mut obj)?;
205-
206243
log::info!("Splitting {} objects", obj.link_order.len());
207244
let split_objs = split_obj(&obj)?;
208245

@@ -224,13 +261,17 @@ fn split(args: SplitArgs) -> Result<()> {
224261
};
225262
}
226263

227-
let mut units_file = BufWriter::new(File::create(args.out_dir.join("units.txt"))?);
264+
let mut out_config = OutputConfig::default();
228265
for unit in &obj.link_order {
229266
let object = file_map
230267
.get(unit)
231268
.ok_or_else(|| anyhow!("Failed to find object file for unit '{unit}'"))?;
232269
let out_path = obj_dir.join(obj_path_for_unit(unit));
233-
writeln!(units_file, "{}:{}", out_path.display(), unit)?;
270+
out_config.units.push(OutputUnit {
271+
object: out_path.clone(),
272+
name: unit.clone(),
273+
autogenerated: false,
274+
});
234275
if let Some(parent) = out_path.parent() {
235276
DirBuilder::new().recursive(true).create(parent)?;
236277
}
@@ -239,7 +280,11 @@ fn split(args: SplitArgs) -> Result<()> {
239280
file.write_all(object)?;
240281
file.flush()?;
241282
}
242-
units_file.flush()?;
283+
{
284+
let mut out_file = BufWriter::new(File::create(&out_config_path)?);
285+
serde_json::to_writer_pretty(&mut out_file, &out_config)?;
286+
out_file.flush()?;
287+
}
243288

244289
// Generate ldscript.lcf
245290
fs::write(args.out_dir.join("ldscript.lcf"), generate_ldscript(&obj)?)?;
@@ -256,14 +301,26 @@ fn split(args: SplitArgs) -> Result<()> {
256301
w.flush()?;
257302
}
258303

259-
// (debugging) validate against ELF
260-
if let Some(file) = &args.elf_file {
261-
validate(&obj, file, &state)?;
304+
// Write dep file
305+
{
306+
let dep_path = args.out_dir.join("dep");
307+
let mut dep_file = BufWriter::new(
308+
File::create(&dep_path)
309+
.with_context(|| format!("Failed to create dep file '{}'", dep_path.display()))?,
310+
);
311+
dep.write(&mut dep_file)?;
312+
dep_file.flush()?;
262313
}
263314

315+
// (debugging) validate against ELF
316+
// if let Some(file) = &args.elf_file {
317+
// validate(&obj, file, &state)?;
318+
// }
319+
264320
Ok(())
265321
}
266322

323+
#[allow(dead_code)]
267324
fn validate<P: AsRef<Path>>(obj: &ObjInfo, elf_file: P, state: &AnalyzerState) -> Result<()> {
268325
let real_obj = process_elf(elf_file)?;
269326
for real_section in &real_obj.sections {

src/obj/mod.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -764,6 +764,12 @@ impl ObjInfo {
764764
self.splits.entry(address).or_default().push(split);
765765
Ok(())
766766
}
767+
768+
pub fn is_unit_autogenerated(&self, unit: &str) -> bool {
769+
self.splits_for_range(..)
770+
.filter(|(_, split)| split.unit == unit)
771+
.all(|(_, split)| split.autogenerated)
772+
}
767773
}
768774

769775
impl ObjSection {

src/obj/split.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -402,8 +402,7 @@ fn resolve_link_order(obj: &ObjInfo) -> Result<Vec<String>> {
402402
unit_to_index_map.insert(split.unit.clone(), NodeIndex::new(0));
403403
}
404404
for (unit, index) in unit_to_index_map.iter_mut() {
405-
let new_index = graph.add_node(unit.clone());
406-
*index = new_index;
405+
*index = graph.add_node(unit.clone());
407406
}
408407

409408
for section in &obj.sections {

src/util/config.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,14 @@ fn symbol_data_kind_from_str(s: &str) -> Option<ObjDataKind> {
244244
}
245245

246246
pub fn write_splits<W: Write>(w: &mut W, obj: &ObjInfo) -> Result<()> {
247-
for unit in &obj.link_order {
247+
let mut begin = true;
248+
for unit in obj.link_order.iter().filter(|unit| !obj.is_unit_autogenerated(unit)) {
249+
if begin {
250+
begin = false;
251+
} else {
252+
writeln!(w)?;
253+
}
254+
248255
writeln!(w, "{}:", unit)?;
249256
let mut split_iter = obj.splits_for_range(..).peekable();
250257
while let Some((addr, split)) = split_iter.next() {
@@ -260,7 +267,6 @@ pub fn write_splits<W: Write>(w: &mut W, obj: &ObjInfo) -> Result<()> {
260267
writeln!(w, "\t{:<11} start:{:#010X} end:{:#010X}", section.name, addr, end)?;
261268
// align:{}
262269
}
263-
writeln!(w)?;
264270
}
265271
Ok(())
266272
}

src/util/dep.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
use std::{io::Write, path::PathBuf};
2+
3+
pub struct DepFile {
4+
pub name: PathBuf,
5+
pub dependencies: Vec<PathBuf>,
6+
}
7+
8+
impl DepFile {
9+
pub fn new(name: PathBuf) -> Self { Self { name, dependencies: vec![] } }
10+
11+
pub fn push(&mut self, dependency: PathBuf) { self.dependencies.push(dependency); }
12+
13+
pub fn write<W: Write>(&self, w: &mut W) -> std::io::Result<()> {
14+
write!(w, "{}:", self.name.display())?;
15+
for dep in &self.dependencies {
16+
write!(w, " \\\n {}", dep.display())?;
17+
}
18+
Ok(())
19+
}
20+
}

src/util/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
pub mod asm;
22
pub mod comment;
33
pub mod config;
4+
pub mod dep;
45
pub mod dol;
56
pub mod dwarf;
67
pub mod elf;

0 commit comments

Comments
 (0)