mirror of
https://github.com/encounter/decomp-toolkit.git
synced 2025-12-13 07:06:16 +00:00
Reorganize files; start RSO support; config & split updates
This commit is contained in:
@@ -1,14 +1,16 @@
|
||||
use std::{
|
||||
collections::{btree_map::Entry, BTreeMap},
|
||||
fs::File,
|
||||
io::{BufRead, BufReader, BufWriter, Write},
|
||||
io::{BufRead, BufWriter, Write},
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
use anyhow::{anyhow, bail, Context, Result};
|
||||
use anyhow::{anyhow, bail, Result};
|
||||
use argh::FromArgs;
|
||||
use object::{Object, ObjectSymbol, SymbolScope};
|
||||
|
||||
use crate::util::file::{buf_reader, map_file};
|
||||
|
||||
#[derive(FromArgs, PartialEq, Debug)]
|
||||
/// Commands for processing static libraries.
|
||||
#[argh(subcommand, name = "ar")]
|
||||
@@ -49,10 +51,7 @@ fn create(args: CreateArgs) -> Result<()> {
|
||||
path.to_str().ok_or_else(|| anyhow!("'{}' is not valid UTF-8", path.display()))?;
|
||||
match path_str.strip_prefix('@') {
|
||||
Some(rsp_file) => {
|
||||
let reader = BufReader::new(
|
||||
File::open(rsp_file)
|
||||
.with_context(|| format!("Failed to open file '{rsp_file}'"))?,
|
||||
);
|
||||
let reader = buf_reader(rsp_file)?;
|
||||
for result in reader.lines() {
|
||||
let line = result?;
|
||||
if !line.is_empty() {
|
||||
@@ -79,11 +78,8 @@ fn create(args: CreateArgs) -> Result<()> {
|
||||
Entry::Vacant(e) => e.insert(Vec::new()),
|
||||
Entry::Occupied(_) => bail!("Duplicate file name '{path_str}'"),
|
||||
};
|
||||
let object_file = File::open(path)
|
||||
.with_context(|| format!("Failed to open object file '{}'", path.display()))?;
|
||||
let map = unsafe { memmap2::MmapOptions::new().map(&object_file) }
|
||||
.with_context(|| format!("Failed to mmap object file: '{}'", path.display()))?;
|
||||
let obj = object::File::parse(map.as_ref())?;
|
||||
let mmap = map_file(path)?;
|
||||
let obj = object::File::parse(&*mmap)?;
|
||||
for symbol in obj.symbols() {
|
||||
if symbol.scope() == SymbolScope::Dynamic {
|
||||
entries.push(symbol.name_bytes()?.to_vec());
|
||||
@@ -93,8 +89,13 @@ fn create(args: CreateArgs) -> Result<()> {
|
||||
|
||||
// Write archive
|
||||
let out = BufWriter::new(File::create(&args.out)?);
|
||||
let mut builder =
|
||||
ar::GnuBuilder::new(out, identifiers, ar::GnuSymbolTableFormat::Size32, symbol_table)?;
|
||||
let mut builder = ar::GnuBuilder::new_with_symbol_table(
|
||||
out,
|
||||
true,
|
||||
identifiers,
|
||||
ar::GnuSymbolTableFormat::Size32,
|
||||
symbol_table,
|
||||
)?;
|
||||
for path in files {
|
||||
let path_str =
|
||||
path.to_str().ok_or_else(|| anyhow!("'{}' is not valid UTF-8", path.display()))?;
|
||||
|
||||
509
src/cmd/dol.rs
509
src/cmd/dol.rs
@@ -1,30 +1,37 @@
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
fs::File,
|
||||
io::{BufRead, BufReader, BufWriter},
|
||||
fs,
|
||||
fs::{DirBuilder, File},
|
||||
io::{BufRead, BufWriter, Write},
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
use std::collections::{hash_map, HashMap};
|
||||
|
||||
use anyhow::{anyhow, bail, Context, Result};
|
||||
use argh::FromArgs;
|
||||
|
||||
use crate::util::{
|
||||
cfa::{
|
||||
locate_sda_bases, AnalysisPass, AnalyzerState, FindSaveRestSleds,
|
||||
FindTRKInterruptVectorTable,
|
||||
use crate::{
|
||||
analysis::{
|
||||
cfa::AnalyzerState,
|
||||
pass::{AnalysisPass, FindSaveRestSleds, FindTRKInterruptVectorTable},
|
||||
read_u32,
|
||||
tracker::Tracker,
|
||||
},
|
||||
config::{parse_symbol_line, write_symbols},
|
||||
dol::process_dol,
|
||||
elf::process_elf,
|
||||
executor::read_u32,
|
||||
map::process_map,
|
||||
obj::{
|
||||
ObjInfo, ObjRelocKind, ObjSectionKind, ObjSymbol, ObjSymbolFlagSet, ObjSymbolFlags,
|
||||
ObjSymbolKind,
|
||||
signatures::{apply_signature, check_signatures, check_signatures_str, parse_signatures},
|
||||
split::split_obj,
|
||||
ObjInfo, ObjRelocKind, ObjSectionKind, ObjSymbolKind,
|
||||
},
|
||||
util::{
|
||||
asm::write_asm,
|
||||
config::{apply_splits, parse_symbol_line, write_symbols},
|
||||
dol::process_dol,
|
||||
elf::process_elf,
|
||||
file::{map_file, map_reader},
|
||||
map::process_map,
|
||||
},
|
||||
sigs::check_signatures,
|
||||
tracker::Tracker,
|
||||
};
|
||||
use crate::util::elf::write_elf;
|
||||
|
||||
#[derive(FromArgs, PartialEq, Debug)]
|
||||
/// Commands for processing DOL files.
|
||||
@@ -42,7 +49,7 @@ enum SubCommand {
|
||||
}
|
||||
|
||||
#[derive(FromArgs, PartialEq, Eq, Debug)]
|
||||
/// Disassembles a DOL file.
|
||||
/// disassembles a DOL file
|
||||
#[argh(subcommand, name = "disasm")]
|
||||
pub struct DisasmArgs {
|
||||
#[argh(option, short = 'm')]
|
||||
@@ -51,12 +58,18 @@ pub struct DisasmArgs {
|
||||
#[argh(option, short = 's')]
|
||||
/// path to symbols file
|
||||
symbols_file: Option<PathBuf>,
|
||||
#[argh(option, short = 'p')]
|
||||
/// path to splits file
|
||||
splits_file: Option<PathBuf>,
|
||||
#[argh(option, short = 'e')]
|
||||
/// ELF file to validate against (debugging only)
|
||||
elf_file: Option<PathBuf>,
|
||||
#[argh(positional)]
|
||||
/// DOL file
|
||||
dol_file: PathBuf,
|
||||
#[argh(option, short = 'o')]
|
||||
/// output file (or directory, if splitting)
|
||||
out: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(FromArgs, PartialEq, Eq, Debug)]
|
||||
@@ -76,152 +89,201 @@ pub fn run(args: Args) -> Result<()> {
|
||||
}
|
||||
|
||||
const SIGNATURES: &[(&str, &str)] = &[
|
||||
("__init_registers", include_str!("../../assets/__init_registers.yml")),
|
||||
("__init_hardware", include_str!("../../assets/__init_hardware.yml")),
|
||||
("__init_data", include_str!("../../assets/__init_data.yml")),
|
||||
("__set_debug_bba", include_str!("../../assets/__set_debug_bba.yml")),
|
||||
("__OSPSInit", include_str!("../../assets/__OSPSInit.yml")),
|
||||
("__OSFPRInit", include_str!("../../assets/__OSFPRInit.yml")),
|
||||
("__OSCacheInit", include_str!("../../assets/__OSCacheInit.yml")),
|
||||
("DMAErrorHandler", include_str!("../../assets/DMAErrorHandler.yml")),
|
||||
("DBInit", include_str!("../../assets/DBInit.yml")),
|
||||
("OSInit", include_str!("../../assets/OSInit.yml")),
|
||||
("__OSThreadInit", include_str!("../../assets/__OSThreadInit.yml")),
|
||||
("__OSInitIPCBuffer", include_str!("../../assets/__OSInitIPCBuffer.yml")),
|
||||
("EXIInit", include_str!("../../assets/EXIInit.yml")),
|
||||
("EXIGetID", include_str!("../../assets/EXIGetID.yml")),
|
||||
("exit", include_str!("../../assets/exit.yml")),
|
||||
("_ExitProcess", include_str!("../../assets/_ExitProcess.yml")),
|
||||
("__fini_cpp", include_str!("../../assets/__fini_cpp.yml")),
|
||||
("__destroy_global_chain", include_str!("../../assets/__destroy_global_chain.yml")),
|
||||
("InitMetroTRK", include_str!("../../assets/InitMetroTRK.yml")),
|
||||
("InitMetroTRKCommTable", include_str!("../../assets/InitMetroTRKCommTable.yml")),
|
||||
("OSExceptionInit", include_str!("../../assets/OSExceptionInit.yml")),
|
||||
("OSDefaultExceptionHandler", include_str!("../../assets/OSDefaultExceptionHandler.yml")),
|
||||
("__OSUnhandledException", include_str!("../../assets/__OSUnhandledException.yml")),
|
||||
("OSDisableScheduler", include_str!("../../assets/OSDisableScheduler.yml")),
|
||||
("__OSReschedule", include_str!("../../assets/__OSReschedule.yml")),
|
||||
("__OSInitSystemCall", include_str!("../../assets/__OSInitSystemCall.yml")),
|
||||
("OSInitAlarm", include_str!("../../assets/OSInitAlarm.yml")),
|
||||
("__OSInitAlarm", include_str!("../../assets/__OSInitAlarm.yml")),
|
||||
("__OSEVStart", include_str!("../../assets/OSExceptionVector.yml")),
|
||||
("__OSDBINTSTART", include_str!("../../assets/__OSDBIntegrator.yml")),
|
||||
("__OSDBJUMPSTART", include_str!("../../assets/__OSDBJump.yml")),
|
||||
("SIInit", include_str!("../../assets/SIInit.yml")),
|
||||
("SIGetType", include_str!("../../assets/SIGetType.yml")),
|
||||
("SISetSamplingRate", include_str!("../../assets/SISetSamplingRate.yml")),
|
||||
("SISetXY", include_str!("../../assets/SISetXY.yml")),
|
||||
("VIGetTvFormat", include_str!("../../assets/VIGetTvFormat.yml")),
|
||||
("DVDInit", include_str!("../../assets/DVDInit.yml")),
|
||||
("DVDSetAutoFatalMessaging", include_str!("../../assets/DVDSetAutoFatalMessaging.yml")),
|
||||
("OSSetArenaLo", include_str!("../../assets/OSSetArenaLo.yml")),
|
||||
("OSSetArenaHi", include_str!("../../assets/OSSetArenaHi.yml")),
|
||||
("OSSetMEM1ArenaLo", include_str!("../../assets/OSSetMEM1ArenaLo.yml")),
|
||||
("OSSetMEM1ArenaHi", include_str!("../../assets/OSSetMEM1ArenaHi.yml")),
|
||||
("OSSetMEM2ArenaLo", include_str!("../../assets/OSSetMEM2ArenaLo.yml")),
|
||||
("OSSetMEM2ArenaHi", include_str!("../../assets/OSSetMEM2ArenaHi.yml")),
|
||||
("__OSInitAudioSystem", include_str!("../../assets/__OSInitAudioSystem.yml")),
|
||||
("__OSInitMemoryProtection", include_str!("../../assets/__OSInitMemoryProtection.yml")),
|
||||
// ("BATConfig", include_str!("../../assets/BATConfig.yml")), TODO
|
||||
("ReportOSInfo", include_str!("../../assets/ReportOSInfo.yml")),
|
||||
("__check_pad3", include_str!("../../assets/__check_pad3.yml")),
|
||||
("OSResetSystem", include_str!("../../assets/OSResetSystem.yml")),
|
||||
("OSReturnToMenu", include_str!("../../assets/OSReturnToMenu.yml")),
|
||||
("__OSReturnToMenu", include_str!("../../assets/__OSReturnToMenu.yml")),
|
||||
("__OSShutdownDevices", include_str!("../../assets/__OSShutdownDevices.yml")),
|
||||
("__OSInitSram", include_str!("../../assets/__OSInitSram.yml")),
|
||||
("__OSSyncSram", include_str!("../../assets/__OSSyncSram.yml")),
|
||||
("__OSGetExceptionHandler", include_str!("../../assets/__OSGetExceptionHandler.yml")),
|
||||
("OSRegisterResetFunction", include_str!("../../assets/OSRegisterResetFunction.yml")),
|
||||
("OSRegisterShutdownFunction", include_str!("../../assets/OSRegisterShutdownFunction.yml")),
|
||||
("DecrementerExceptionHandler", include_str!("../../assets/DecrementerExceptionHandler.yml")),
|
||||
("DecrementerExceptionCallback", include_str!("../../assets/DecrementerExceptionCallback.yml")),
|
||||
("__OSInterruptInit", include_str!("../../assets/__OSInterruptInit.yml")),
|
||||
("__OSContextInit", include_str!("../../assets/__OSContextInit.yml")),
|
||||
("OSSwitchFPUContext", include_str!("../../assets/OSSwitchFPUContext.yml")),
|
||||
("OSReport", include_str!("../../assets/OSReport.yml")),
|
||||
("TRK_main", include_str!("../../assets/TRK_main.yml")),
|
||||
("TRKNubWelcome", include_str!("../../assets/TRKNubWelcome.yml")),
|
||||
("TRKInitializeNub", include_str!("../../assets/TRKInitializeNub.yml")),
|
||||
("TRKInitializeIntDrivenUART", include_str!("../../assets/TRKInitializeIntDrivenUART.yml")),
|
||||
("TRKEXICallBack", include_str!("../../assets/TRKEXICallBack.yml")),
|
||||
("TRKLoadContext", include_str!("../../assets/TRKLoadContext.yml")),
|
||||
("TRKInterruptHandler", include_str!("../../assets/TRKInterruptHandler.yml")),
|
||||
("TRKExceptionHandler", include_str!("../../assets/TRKExceptionHandler.yml")),
|
||||
("TRKSaveExtended1Block", include_str!("../../assets/TRKSaveExtended1Block.yml")),
|
||||
("TRKNubMainLoop", include_str!("../../assets/TRKNubMainLoop.yml")),
|
||||
("TRKTargetContinue", include_str!("../../assets/TRKTargetContinue.yml")),
|
||||
("TRKSwapAndGo", include_str!("../../assets/TRKSwapAndGo.yml")),
|
||||
("TRKRestoreExtended1Block", include_str!("../../assets/TRKRestoreExtended1Block.yml")),
|
||||
("__init_registers", include_str!("../../assets/signatures/__init_registers.yml")),
|
||||
("__init_hardware", include_str!("../../assets/signatures/__init_hardware.yml")),
|
||||
("__init_data", include_str!("../../assets/signatures/__init_data.yml")),
|
||||
("__set_debug_bba", include_str!("../../assets/signatures/__set_debug_bba.yml")),
|
||||
("__OSPSInit", include_str!("../../assets/signatures/__OSPSInit.yml")),
|
||||
("__OSFPRInit", include_str!("../../assets/signatures/__OSFPRInit.yml")),
|
||||
("__OSCacheInit", include_str!("../../assets/signatures/__OSCacheInit.yml")),
|
||||
("DMAErrorHandler", include_str!("../../assets/signatures/DMAErrorHandler.yml")),
|
||||
("DBInit", include_str!("../../assets/signatures/DBInit.yml")),
|
||||
("OSInit", include_str!("../../assets/signatures/OSInit.yml")),
|
||||
("__OSThreadInit", include_str!("../../assets/signatures/__OSThreadInit.yml")),
|
||||
("__OSInitIPCBuffer", include_str!("../../assets/signatures/__OSInitIPCBuffer.yml")),
|
||||
("EXIInit", include_str!("../../assets/signatures/EXIInit.yml")),
|
||||
("EXIGetID", include_str!("../../assets/signatures/EXIGetID.yml")),
|
||||
("exit", include_str!("../../assets/signatures/exit.yml")),
|
||||
("_ExitProcess", include_str!("../../assets/signatures/_ExitProcess.yml")),
|
||||
("__fini_cpp", include_str!("../../assets/signatures/__fini_cpp.yml")),
|
||||
("__destroy_global_chain", include_str!("../../assets/signatures/__destroy_global_chain.yml")),
|
||||
("InitMetroTRK", include_str!("../../assets/signatures/InitMetroTRK.yml")),
|
||||
("InitMetroTRKCommTable", include_str!("../../assets/signatures/InitMetroTRKCommTable.yml")),
|
||||
("OSExceptionInit", include_str!("../../assets/signatures/OSExceptionInit.yml")),
|
||||
(
|
||||
"OSDefaultExceptionHandler",
|
||||
include_str!("../../assets/signatures/OSDefaultExceptionHandler.yml"),
|
||||
),
|
||||
("__OSUnhandledException", include_str!("../../assets/signatures/__OSUnhandledException.yml")),
|
||||
("OSDisableScheduler", include_str!("../../assets/signatures/OSDisableScheduler.yml")),
|
||||
("__OSReschedule", include_str!("../../assets/signatures/__OSReschedule.yml")),
|
||||
("__OSInitSystemCall", include_str!("../../assets/signatures/__OSInitSystemCall.yml")),
|
||||
("OSInitAlarm", include_str!("../../assets/signatures/OSInitAlarm.yml")),
|
||||
("__OSInitAlarm", include_str!("../../assets/signatures/__OSInitAlarm.yml")),
|
||||
("__OSEVStart", include_str!("../../assets/signatures/OSExceptionVector.yml")),
|
||||
("__OSDBINTSTART", include_str!("../../assets/signatures/__OSDBIntegrator.yml")),
|
||||
("__OSDBJUMPSTART", include_str!("../../assets/signatures/__OSDBJump.yml")),
|
||||
("SIInit", include_str!("../../assets/signatures/SIInit.yml")),
|
||||
("SIGetType", include_str!("../../assets/signatures/SIGetType.yml")),
|
||||
("SISetSamplingRate", include_str!("../../assets/signatures/SISetSamplingRate.yml")),
|
||||
("SISetXY", include_str!("../../assets/signatures/SISetXY.yml")),
|
||||
("VIGetTvFormat", include_str!("../../assets/signatures/VIGetTvFormat.yml")),
|
||||
("DVDInit", include_str!("../../assets/signatures/DVDInit.yml")),
|
||||
(
|
||||
"DVDSetAutoFatalMessaging",
|
||||
include_str!("../../assets/signatures/DVDSetAutoFatalMessaging.yml"),
|
||||
),
|
||||
("OSSetArenaLo", include_str!("../../assets/signatures/OSSetArenaLo.yml")),
|
||||
("OSSetArenaHi", include_str!("../../assets/signatures/OSSetArenaHi.yml")),
|
||||
("OSSetMEM1ArenaLo", include_str!("../../assets/signatures/OSSetMEM1ArenaLo.yml")),
|
||||
("OSSetMEM1ArenaHi", include_str!("../../assets/signatures/OSSetMEM1ArenaHi.yml")),
|
||||
("OSSetMEM2ArenaLo", include_str!("../../assets/signatures/OSSetMEM2ArenaLo.yml")),
|
||||
("OSSetMEM2ArenaHi", include_str!("../../assets/signatures/OSSetMEM2ArenaHi.yml")),
|
||||
("__OSInitAudioSystem", include_str!("../../assets/signatures/__OSInitAudioSystem.yml")),
|
||||
(
|
||||
"__OSInitMemoryProtection",
|
||||
include_str!("../../assets/signatures/__OSInitMemoryProtection.yml"),
|
||||
),
|
||||
// ("BATConfig", include_str!("../../assets/signatures/BATConfig.yml")), TODO
|
||||
("ReportOSInfo", include_str!("../../assets/signatures/ReportOSInfo.yml")),
|
||||
("__check_pad3", include_str!("../../assets/signatures/__check_pad3.yml")),
|
||||
("OSResetSystem", include_str!("../../assets/signatures/OSResetSystem.yml")),
|
||||
("OSReturnToMenu", include_str!("../../assets/signatures/OSReturnToMenu.yml")),
|
||||
("__OSReturnToMenu", include_str!("../../assets/signatures/__OSReturnToMenu.yml")),
|
||||
("__OSShutdownDevices", include_str!("../../assets/signatures/__OSShutdownDevices.yml")),
|
||||
("__OSInitSram", include_str!("../../assets/signatures/__OSInitSram.yml")),
|
||||
("__OSSyncSram", include_str!("../../assets/signatures/__OSSyncSram.yml")),
|
||||
(
|
||||
"__OSGetExceptionHandler",
|
||||
include_str!("../../assets/signatures/__OSGetExceptionHandler.yml"),
|
||||
),
|
||||
(
|
||||
"OSRegisterResetFunction",
|
||||
include_str!("../../assets/signatures/OSRegisterResetFunction.yml"),
|
||||
),
|
||||
(
|
||||
"OSRegisterShutdownFunction",
|
||||
include_str!("../../assets/signatures/OSRegisterShutdownFunction.yml"),
|
||||
),
|
||||
(
|
||||
"DecrementerExceptionHandler",
|
||||
include_str!("../../assets/signatures/DecrementerExceptionHandler.yml"),
|
||||
),
|
||||
(
|
||||
"DecrementerExceptionCallback",
|
||||
include_str!("../../assets/signatures/DecrementerExceptionCallback.yml"),
|
||||
),
|
||||
("__OSInterruptInit", include_str!("../../assets/signatures/__OSInterruptInit.yml")),
|
||||
("__OSContextInit", include_str!("../../assets/signatures/__OSContextInit.yml")),
|
||||
("OSSwitchFPUContext", include_str!("../../assets/signatures/OSSwitchFPUContext.yml")),
|
||||
("OSReport", include_str!("../../assets/signatures/OSReport.yml")),
|
||||
("TRK_main", include_str!("../../assets/signatures/TRK_main.yml")),
|
||||
("TRKNubWelcome", include_str!("../../assets/signatures/TRKNubWelcome.yml")),
|
||||
("TRKInitializeNub", include_str!("../../assets/signatures/TRKInitializeNub.yml")),
|
||||
(
|
||||
"TRKInitializeIntDrivenUART",
|
||||
include_str!("../../assets/signatures/TRKInitializeIntDrivenUART.yml"),
|
||||
),
|
||||
("TRKEXICallBack", include_str!("../../assets/signatures/TRKEXICallBack.yml")),
|
||||
("TRKLoadContext", include_str!("../../assets/signatures/TRKLoadContext.yml")),
|
||||
("TRKInterruptHandler", include_str!("../../assets/signatures/TRKInterruptHandler.yml")),
|
||||
("TRKExceptionHandler", include_str!("../../assets/signatures/TRKExceptionHandler.yml")),
|
||||
("TRKSaveExtended1Block", include_str!("../../assets/signatures/TRKSaveExtended1Block.yml")),
|
||||
("TRKNubMainLoop", include_str!("../../assets/signatures/TRKNubMainLoop.yml")),
|
||||
("TRKTargetContinue", include_str!("../../assets/signatures/TRKTargetContinue.yml")),
|
||||
("TRKSwapAndGo", include_str!("../../assets/signatures/TRKSwapAndGo.yml")),
|
||||
(
|
||||
"TRKRestoreExtended1Block",
|
||||
include_str!("../../assets/signatures/TRKRestoreExtended1Block.yml"),
|
||||
),
|
||||
(
|
||||
"TRKInterruptHandlerEnableInterrupts",
|
||||
include_str!("../../assets/TRKInterruptHandlerEnableInterrupts.yml"),
|
||||
include_str!("../../assets/signatures/TRKInterruptHandlerEnableInterrupts.yml"),
|
||||
),
|
||||
("memset", include_str!("../../assets/memset.yml")),
|
||||
("memset", include_str!("../../assets/signatures/memset.yml")),
|
||||
(
|
||||
"__msl_runtime_constraint_violation_s",
|
||||
include_str!("../../assets/__msl_runtime_constraint_violation_s.yml"),
|
||||
include_str!("../../assets/signatures/__msl_runtime_constraint_violation_s.yml"),
|
||||
),
|
||||
("ClearArena", include_str!("../../assets/ClearArena.yml")),
|
||||
("IPCCltInit", include_str!("../../assets/IPCCltInit.yml")),
|
||||
("__OSInitSTM", include_str!("../../assets/__OSInitSTM.yml")),
|
||||
("IOS_Open", include_str!("../../assets/IOS_Open.yml")),
|
||||
("__ios_Ipc2", include_str!("../../assets/__ios_Ipc2.yml")),
|
||||
("IPCiProfQueueReq", include_str!("../../assets/IPCiProfQueueReq.yml")),
|
||||
("SCInit", include_str!("../../assets/SCInit.yml")),
|
||||
("SCReloadConfFileAsync", include_str!("../../assets/SCReloadConfFileAsync.yml")),
|
||||
("NANDPrivateOpenAsync", include_str!("../../assets/NANDPrivateOpenAsync.yml")),
|
||||
("nandIsInitialized", include_str!("../../assets/nandIsInitialized.yml")),
|
||||
("nandOpen", include_str!("../../assets/nandOpen.yml")),
|
||||
("nandGenerateAbsPath", include_str!("../../assets/nandGenerateAbsPath.yml")),
|
||||
("nandGetHeadToken", include_str!("../../assets/nandGetHeadToken.yml")),
|
||||
("ISFS_OpenAsync", include_str!("../../assets/ISFS_OpenAsync.yml")),
|
||||
("nandConvertErrorCode", include_str!("../../assets/nandConvertErrorCode.yml")),
|
||||
("NANDLoggingAddMessageAsync", include_str!("../../assets/NANDLoggingAddMessageAsync.yml")),
|
||||
("__NANDPrintErrorMessage", include_str!("../../assets/__NANDPrintErrorMessage.yml")),
|
||||
("__OSInitNet", include_str!("../../assets/__OSInitNet.yml")),
|
||||
("__DVDCheckDevice", include_str!("../../assets/__DVDCheckDevice.yml")),
|
||||
("__OSInitPlayTime", include_str!("../../assets/__OSInitPlayTime.yml")),
|
||||
("__OSStartPlayRecord", include_str!("../../assets/__OSStartPlayRecord.yml")),
|
||||
("NANDInit", include_str!("../../assets/NANDInit.yml")),
|
||||
("ISFS_OpenLib", include_str!("../../assets/ISFS_OpenLib.yml")),
|
||||
("ESP_GetTitleId", include_str!("../../assets/ESP_GetTitleId.yml")),
|
||||
("NANDSetAutoErrorMessaging", include_str!("../../assets/NANDSetAutoErrorMessaging.yml")),
|
||||
("__DVDFSInit", include_str!("../../assets/__DVDFSInit.yml")),
|
||||
("__DVDClearWaitingQueue", include_str!("../../assets/__DVDClearWaitingQueue.yml")),
|
||||
("__DVDInitWA", include_str!("../../assets/__DVDInitWA.yml")),
|
||||
("__DVDLowSetWAType", include_str!("../../assets/__DVDLowSetWAType.yml")),
|
||||
("__fstLoad", include_str!("../../assets/__fstLoad.yml")),
|
||||
("DVDReset", include_str!("../../assets/DVDReset.yml")),
|
||||
("DVDLowReset", include_str!("../../assets/DVDLowReset.yml")),
|
||||
("DVDReadDiskID", include_str!("../../assets/DVDReadDiskID.yml")),
|
||||
("stateReady", include_str!("../../assets/stateReady.yml")),
|
||||
("DVDLowWaitCoverClose", include_str!("../../assets/DVDLowWaitCoverClose.yml")),
|
||||
("__DVDStoreErrorCode", include_str!("../../assets/__DVDStoreErrorCode.yml")),
|
||||
("DVDLowStopMotor", include_str!("../../assets/DVDLowStopMotor.yml")),
|
||||
("DVDGetDriveStatus", include_str!("../../assets/DVDGetDriveStatus.yml")),
|
||||
("printf", include_str!("../../assets/printf.yml")),
|
||||
("sprintf", include_str!("../../assets/sprintf.yml")),
|
||||
("vprintf", include_str!("../../assets/vprintf.yml")),
|
||||
("vsprintf", include_str!("../../assets/vsprintf.yml")),
|
||||
("vsnprintf", include_str!("../../assets/vsnprintf.yml")),
|
||||
("__pformatter", include_str!("../../assets/__pformatter.yml")),
|
||||
("longlong2str", include_str!("../../assets/longlong2str.yml")),
|
||||
("__mod2u", include_str!("../../assets/__mod2u.yml")),
|
||||
("__FileWrite", include_str!("../../assets/__FileWrite.yml")),
|
||||
("fwrite", include_str!("../../assets/fwrite.yml")),
|
||||
("__fwrite", include_str!("../../assets/__fwrite.yml")),
|
||||
("__stdio_atexit", include_str!("../../assets/__stdio_atexit.yml")),
|
||||
("__StringWrite", include_str!("../../assets/__StringWrite.yml")),
|
||||
("ClearArena", include_str!("../../assets/signatures/ClearArena.yml")),
|
||||
("IPCCltInit", include_str!("../../assets/signatures/IPCCltInit.yml")),
|
||||
("__OSInitSTM", include_str!("../../assets/signatures/__OSInitSTM.yml")),
|
||||
("IOS_Open", include_str!("../../assets/signatures/IOS_Open.yml")),
|
||||
("__ios_Ipc2", include_str!("../../assets/signatures/__ios_Ipc2.yml")),
|
||||
("IPCiProfQueueReq", include_str!("../../assets/signatures/IPCiProfQueueReq.yml")),
|
||||
("SCInit", include_str!("../../assets/signatures/SCInit.yml")),
|
||||
("SCReloadConfFileAsync", include_str!("../../assets/signatures/SCReloadConfFileAsync.yml")),
|
||||
("NANDPrivateOpenAsync", include_str!("../../assets/signatures/NANDPrivateOpenAsync.yml")),
|
||||
("nandIsInitialized", include_str!("../../assets/signatures/nandIsInitialized.yml")),
|
||||
("nandOpen", include_str!("../../assets/signatures/nandOpen.yml")),
|
||||
("nandGenerateAbsPath", include_str!("../../assets/signatures/nandGenerateAbsPath.yml")),
|
||||
("nandGetHeadToken", include_str!("../../assets/signatures/nandGetHeadToken.yml")),
|
||||
("ISFS_OpenAsync", include_str!("../../assets/signatures/ISFS_OpenAsync.yml")),
|
||||
("nandConvertErrorCode", include_str!("../../assets/signatures/nandConvertErrorCode.yml")),
|
||||
(
|
||||
"NANDLoggingAddMessageAsync",
|
||||
include_str!("../../assets/signatures/NANDLoggingAddMessageAsync.yml"),
|
||||
),
|
||||
(
|
||||
"__NANDPrintErrorMessage",
|
||||
include_str!("../../assets/signatures/__NANDPrintErrorMessage.yml"),
|
||||
),
|
||||
("__OSInitNet", include_str!("../../assets/signatures/__OSInitNet.yml")),
|
||||
("__DVDCheckDevice", include_str!("../../assets/signatures/__DVDCheckDevice.yml")),
|
||||
("__OSInitPlayTime", include_str!("../../assets/signatures/__OSInitPlayTime.yml")),
|
||||
("__OSStartPlayRecord", include_str!("../../assets/signatures/__OSStartPlayRecord.yml")),
|
||||
("NANDInit", include_str!("../../assets/signatures/NANDInit.yml")),
|
||||
("ISFS_OpenLib", include_str!("../../assets/signatures/ISFS_OpenLib.yml")),
|
||||
("ESP_GetTitleId", include_str!("../../assets/signatures/ESP_GetTitleId.yml")),
|
||||
(
|
||||
"NANDSetAutoErrorMessaging",
|
||||
include_str!("../../assets/signatures/NANDSetAutoErrorMessaging.yml"),
|
||||
),
|
||||
("__DVDFSInit", include_str!("../../assets/signatures/__DVDFSInit.yml")),
|
||||
("__DVDClearWaitingQueue", include_str!("../../assets/signatures/__DVDClearWaitingQueue.yml")),
|
||||
("__DVDInitWA", include_str!("../../assets/signatures/__DVDInitWA.yml")),
|
||||
("__DVDLowSetWAType", include_str!("../../assets/signatures/__DVDLowSetWAType.yml")),
|
||||
("__fstLoad", include_str!("../../assets/signatures/__fstLoad.yml")),
|
||||
("DVDReset", include_str!("../../assets/signatures/DVDReset.yml")),
|
||||
("DVDLowReset", include_str!("../../assets/signatures/DVDLowReset.yml")),
|
||||
("DVDReadDiskID", include_str!("../../assets/signatures/DVDReadDiskID.yml")),
|
||||
("stateReady", include_str!("../../assets/signatures/stateReady.yml")),
|
||||
("DVDLowWaitCoverClose", include_str!("../../assets/signatures/DVDLowWaitCoverClose.yml")),
|
||||
("__DVDStoreErrorCode", include_str!("../../assets/signatures/__DVDStoreErrorCode.yml")),
|
||||
("DVDLowStopMotor", include_str!("../../assets/signatures/DVDLowStopMotor.yml")),
|
||||
("DVDGetDriveStatus", include_str!("../../assets/signatures/DVDGetDriveStatus.yml")),
|
||||
("printf", include_str!("../../assets/signatures/printf.yml")),
|
||||
("sprintf", include_str!("../../assets/signatures/sprintf.yml")),
|
||||
("vprintf", include_str!("../../assets/signatures/vprintf.yml")),
|
||||
("vsprintf", include_str!("../../assets/signatures/vsprintf.yml")),
|
||||
("vsnprintf", include_str!("../../assets/signatures/vsnprintf.yml")),
|
||||
("__pformatter", include_str!("../../assets/signatures/__pformatter.yml")),
|
||||
("longlong2str", include_str!("../../assets/signatures/longlong2str.yml")),
|
||||
("__mod2u", include_str!("../../assets/signatures/__mod2u.yml")),
|
||||
("__FileWrite", include_str!("../../assets/signatures/__FileWrite.yml")),
|
||||
("fwrite", include_str!("../../assets/signatures/fwrite.yml")),
|
||||
("__fwrite", include_str!("../../assets/signatures/__fwrite.yml")),
|
||||
("__stdio_atexit", include_str!("../../assets/signatures/__stdio_atexit.yml")),
|
||||
("__StringWrite", include_str!("../../assets/signatures/__StringWrite.yml")),
|
||||
];
|
||||
const POST_SIGNATURES: &[(&str, &str)] = &[
|
||||
("RSOStaticLocateObject", include_str!("../../assets/signatures/RSOStaticLocateObject.yml")),
|
||||
// ("GXInit", include_str!("../../assets/signatures/GXInit.yml")),
|
||||
];
|
||||
|
||||
pub fn apply_signatures(obj: &mut ObjInfo) -> Result<()> {
|
||||
let entry = obj.entry as u32;
|
||||
check_signatures(obj, entry, include_str!("../../assets/__start.yml"))?;
|
||||
if let Some(signature) =
|
||||
check_signatures_str(obj, entry, include_str!("../../assets/signatures/__start.yml"))?
|
||||
{
|
||||
apply_signature(obj, entry, &signature)?;
|
||||
}
|
||||
for &(name, sig_str) in SIGNATURES {
|
||||
if let Some(symbol) = obj.symbols.iter().find(|symbol| symbol.name == name) {
|
||||
let addr = symbol.address as u32;
|
||||
check_signatures(obj, addr, sig_str)?;
|
||||
if let Some(signature) = check_signatures_str(obj, addr, sig_str)? {
|
||||
apply_signature(obj, addr, &signature)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(symbol) = obj.symbols.iter().find(|symbol| symbol.name == "__init_user") {
|
||||
@@ -229,7 +291,12 @@ pub fn apply_signatures(obj: &mut ObjInfo) -> Result<()> {
|
||||
let mut analyzer = AnalyzerState::default();
|
||||
analyzer.process_function_at(&obj, symbol.address as u32)?;
|
||||
for addr in analyzer.function_entries {
|
||||
if check_signatures(obj, addr, include_str!("../../assets/__init_cpp.yml"))? {
|
||||
if let Some(signature) = check_signatures_str(
|
||||
obj,
|
||||
addr,
|
||||
include_str!("../../assets/signatures/__init_cpp.yml"),
|
||||
)? {
|
||||
apply_signature(obj, addr, &signature)?;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -240,7 +307,13 @@ pub fn apply_signatures(obj: &mut ObjInfo) -> Result<()> {
|
||||
let target = read_u32(§ion.data, symbol.address as u32, section.address as u32)
|
||||
.ok_or_else(|| anyhow!("Failed to read _ctors data"))?;
|
||||
if target != 0 {
|
||||
check_signatures(obj, target, include_str!("../../assets/__init_cpp_exceptions.yml"))?;
|
||||
if let Some(signature) = check_signatures_str(
|
||||
obj,
|
||||
target,
|
||||
include_str!("../../assets/signatures/__init_cpp_exceptions.yml"),
|
||||
)? {
|
||||
apply_signature(obj, target, &signature)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(symbol) = obj.symbols.iter().find(|symbol| symbol.name == "_dtors") {
|
||||
@@ -249,12 +322,34 @@ pub fn apply_signatures(obj: &mut ObjInfo) -> Result<()> {
|
||||
let target = read_u32(§ion.data, symbol.address as u32 + 4, section.address as u32)
|
||||
.ok_or_else(|| anyhow!("Failed to read _dtors data"))?;
|
||||
if target != 0 {
|
||||
check_signatures(obj, target, include_str!("../../assets/__fini_cpp_exceptions.yml"))?;
|
||||
if let Some(signature) = check_signatures_str(
|
||||
obj,
|
||||
target,
|
||||
include_str!("../../assets/signatures/__fini_cpp_exceptions.yml"),
|
||||
)? {
|
||||
apply_signature(obj, target, &signature)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn apply_signatures_post(obj: &mut ObjInfo) -> Result<()> {
|
||||
log::info!("Checking post CFA signatures...");
|
||||
for &(_name, sig_str) in POST_SIGNATURES {
|
||||
let signatures = parse_signatures(sig_str)?;
|
||||
for symbol in obj.symbols.iter().filter(|symbol| symbol.kind == ObjSymbolKind::Function) {
|
||||
let addr = symbol.address as u32;
|
||||
if let Some(signature) = check_signatures(obj, addr, &signatures)? {
|
||||
apply_signature(obj, addr, &signature)?;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
log::info!("Done!");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn info(args: InfoArgs) -> Result<()> {
|
||||
let mut obj = process_dol(&args.dol_file)?;
|
||||
apply_signatures(&mut obj)?;
|
||||
@@ -288,6 +383,8 @@ fn info(args: InfoArgs) -> Result<()> {
|
||||
FindSaveRestSleds::execute(&mut state, &obj)?;
|
||||
state.apply(&mut obj)?;
|
||||
|
||||
apply_signatures_post(&mut obj)?;
|
||||
|
||||
println!("{}:", obj.name);
|
||||
println!("Entry point: {:#010X}", obj.entry);
|
||||
println!("\nSections:");
|
||||
@@ -329,20 +426,20 @@ fn disasm(args: DisasmArgs) -> Result<()> {
|
||||
// }
|
||||
|
||||
if let Some(map) = &args.map_file {
|
||||
let mut reader = BufReader::new(
|
||||
File::open(map)
|
||||
.with_context(|| format!("Failed to open map file '{}'", map.display()))?,
|
||||
);
|
||||
let _entries = process_map(&mut reader)?;
|
||||
let mmap = map_file(map)?;
|
||||
let _entries = process_map(map_reader(&mmap))?;
|
||||
}
|
||||
|
||||
if let Some(splits_file) = &args.splits_file {
|
||||
let map = map_file(splits_file)?;
|
||||
apply_splits(map_reader(&map), &mut obj)?;
|
||||
}
|
||||
|
||||
let mut state = AnalyzerState::default();
|
||||
|
||||
if let Some(symbols_path) = &args.symbols_file {
|
||||
let mut reader = BufReader::new(File::open(symbols_path).with_context(|| {
|
||||
format!("Failed to open symbols file '{}'", symbols_path.display())
|
||||
})?);
|
||||
for result in reader.lines() {
|
||||
let map = map_file(symbols_path)?;
|
||||
for result in map_reader(&map).lines() {
|
||||
let line = match result {
|
||||
Ok(line) => line,
|
||||
Err(e) => bail!("Failed to process symbols file: {e:?}"),
|
||||
@@ -407,10 +504,66 @@ fn disasm(args: DisasmArgs) -> Result<()> {
|
||||
|
||||
log::info!("Applying relocations");
|
||||
tracker.apply(&mut obj, false)?;
|
||||
//
|
||||
// log::info!("Writing disassembly");
|
||||
// let mut w = BufWriter::new(File::create("out.s")?);
|
||||
// write_asm(&mut w, &obj)?;
|
||||
|
||||
if args.splits_file.is_some() {
|
||||
|
||||
log::info!("Splitting {} objects", obj.link_order.len());
|
||||
let split_objs = split_obj(&obj)?;
|
||||
|
||||
// Create out dirs
|
||||
let asm_dir = args.out.join("asm");
|
||||
let include_dir = args.out.join("include");
|
||||
let obj_dir = args.out.join("expected");
|
||||
DirBuilder::new().recursive(true).create(&include_dir)?;
|
||||
fs::write(include_dir.join("macros.inc"), include_bytes!("../../assets/macros.inc"))?;
|
||||
|
||||
log::info!("Writing object files");
|
||||
let mut file_map = HashMap::<String, Vec<u8>>::new();
|
||||
for (unit, split_obj) in obj.link_order.iter().zip(&split_objs) {
|
||||
let out_obj = write_elf(split_obj)?;
|
||||
match file_map.entry(unit.clone()) {
|
||||
hash_map::Entry::Vacant(e) => e.insert(out_obj),
|
||||
hash_map::Entry::Occupied(_) => bail!("Duplicate file {unit}"),
|
||||
};
|
||||
}
|
||||
|
||||
let mut rsp_file = BufWriter::new(File::create("rsp")?);
|
||||
for unit in &obj.link_order {
|
||||
let object = file_map
|
||||
.get(unit)
|
||||
.ok_or_else(|| anyhow!("Failed to find object file for unit '{unit}'"))?;
|
||||
let out_path = obj_dir.join(unit);
|
||||
writeln!(rsp_file, "{}", out_path.display())?;
|
||||
if let Some(parent) = out_path.parent() {
|
||||
DirBuilder::new().recursive(true).create(parent)?;
|
||||
}
|
||||
let mut file = File::create(&out_path)
|
||||
.with_context(|| format!("Failed to create '{}'", out_path.display()))?;
|
||||
file.write_all(object)?;
|
||||
file.flush()?;
|
||||
}
|
||||
rsp_file.flush()?;
|
||||
|
||||
log::info!("Writing disassembly");
|
||||
let mut files_out = File::create(args.out.join("link_order.txt"))?;
|
||||
for (unit, split_obj) in obj.link_order.iter().zip(&split_objs) {
|
||||
let out_path = asm_dir.join(format!("{}.s", unit.trim_end_matches(".o")));
|
||||
|
||||
if let Some(parent) = out_path.parent() {
|
||||
DirBuilder::new().recursive(true).create(parent)?;
|
||||
}
|
||||
let mut w = BufWriter::new(File::create(out_path)?);
|
||||
write_asm(&mut w, split_obj)?;
|
||||
w.flush()?;
|
||||
|
||||
writeln!(files_out, "{}", unit)?;
|
||||
}
|
||||
files_out.flush()?;
|
||||
} else {
|
||||
log::info!("Writing disassembly");
|
||||
let mut w = BufWriter::new(File::create("out.s")?);
|
||||
write_asm(&mut w, &obj)?;
|
||||
}
|
||||
|
||||
if let Some(symbols_path) = &args.symbols_file {
|
||||
let mut symbols_writer = BufWriter::new(
|
||||
@@ -500,6 +653,7 @@ fn validate<P: AsRef<Path>>(obj: &ObjInfo, elf_file: P, state: &AnalyzerState) -
|
||||
);
|
||||
}
|
||||
}
|
||||
return Ok(()); // TODO
|
||||
for real_section in &real_obj.sections {
|
||||
let obj_section = match obj.sections.get(real_section.index) {
|
||||
Some(v) => v,
|
||||
@@ -562,20 +716,17 @@ fn validate<P: AsRef<Path>>(obj: &ObjInfo, elf_file: P, state: &AnalyzerState) -
|
||||
}
|
||||
for (&obj_addr, obj_reloc) in &obj_map {
|
||||
let obj_symbol = &obj.symbols[obj_reloc.target_symbol];
|
||||
let real_reloc = match real_map.get(&obj_addr) {
|
||||
Some(v) => v,
|
||||
None => {
|
||||
log::warn!(
|
||||
"Relocation not real @ {:#010X} {:?} to {:#010X}+{:X} ({})",
|
||||
obj_addr,
|
||||
obj_reloc.kind,
|
||||
obj_symbol.address,
|
||||
obj_reloc.addend,
|
||||
obj_symbol.demangled_name.as_ref().unwrap_or(&obj_symbol.name)
|
||||
);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
if !real_map.contains_key(&obj_addr) {
|
||||
log::warn!(
|
||||
"Relocation not real @ {:#010X} {:?} to {:#010X}+{:X} ({})",
|
||||
obj_addr,
|
||||
obj_reloc.kind,
|
||||
obj_symbol.address,
|
||||
obj_reloc.addend,
|
||||
obj_symbol.demangled_name.as_ref().unwrap_or(&obj_symbol.name)
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use std::{
|
||||
collections::{btree_map, btree_map::Entry, hash_map, BTreeMap, HashMap, HashSet},
|
||||
collections::{btree_map, hash_map, BTreeMap, HashMap},
|
||||
fs,
|
||||
fs::{DirBuilder, File},
|
||||
io::{BufRead, BufReader, BufWriter, Write},
|
||||
path::{Path, PathBuf},
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
use anyhow::{anyhow, bail, ensure, Context, Result};
|
||||
@@ -13,18 +13,19 @@ use object::{
|
||||
Object, ObjectSection, ObjectSymbol, RelocationKind, RelocationTarget, SectionFlags,
|
||||
SectionIndex, SectionKind, SymbolFlags, SymbolKind, SymbolScope, SymbolSection,
|
||||
};
|
||||
use ppc750cl::Ins;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sha1::{Digest, Sha1};
|
||||
|
||||
use crate::util::{
|
||||
asm::write_asm,
|
||||
config::write_symbols,
|
||||
elf::{process_elf, write_elf},
|
||||
obj::{ObjKind, ObjReloc, ObjRelocKind, ObjSymbolFlagSet, ObjSymbolKind},
|
||||
sigs::{check_signature, compare_signature, generate_signature, FunctionSignature},
|
||||
split::split_obj,
|
||||
tracker::Tracker,
|
||||
use crate::{
|
||||
obj::{
|
||||
signatures::{compare_signature, generate_signature, FunctionSignature},
|
||||
split::split_obj,
|
||||
ObjKind,
|
||||
},
|
||||
util::{
|
||||
asm::write_asm,
|
||||
config::{write_splits, write_symbols},
|
||||
elf::{process_elf, write_elf},
|
||||
file::buf_reader,
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(FromArgs, PartialEq, Debug)]
|
||||
@@ -91,6 +92,9 @@ pub struct ConfigArgs {
|
||||
#[argh(positional)]
|
||||
/// output directory
|
||||
out_dir: PathBuf,
|
||||
#[argh(option, short = 'm')]
|
||||
/// path to obj_files.mk
|
||||
obj_files: Option<PathBuf>,
|
||||
}
|
||||
|
||||
#[derive(FromArgs, PartialEq, Eq, Debug)]
|
||||
@@ -120,15 +124,43 @@ pub fn run(args: Args) -> Result<()> {
|
||||
|
||||
fn config(args: ConfigArgs) -> Result<()> {
|
||||
log::info!("Loading {}", args.in_file.display());
|
||||
let mut obj = process_elf(&args.in_file)?;
|
||||
let obj = process_elf(&args.in_file)?;
|
||||
|
||||
DirBuilder::new().recursive(true).create(&args.out_dir)?;
|
||||
let symbols_path = args.out_dir.join("symbols.txt");
|
||||
let mut symbols_writer = BufWriter::new(
|
||||
File::create(&symbols_path)
|
||||
.with_context(|| format!("Failed to create '{}'", symbols_path.display()))?,
|
||||
);
|
||||
write_symbols(&mut symbols_writer, &obj)?;
|
||||
{
|
||||
let symbols_path = args.out_dir.join("symbols.txt");
|
||||
let mut symbols_writer = BufWriter::new(
|
||||
File::create(&symbols_path)
|
||||
.with_context(|| format!("Failed to create '{}'", symbols_path.display()))?,
|
||||
);
|
||||
write_symbols(&mut symbols_writer, &obj)?;
|
||||
}
|
||||
|
||||
{
|
||||
let obj_files = if let Some(path) = &args.obj_files {
|
||||
Some(
|
||||
BufReader::new(
|
||||
File::open(path)
|
||||
.with_context(|| format!("Failed to open '{}'", path.display()))?,
|
||||
)
|
||||
.lines()
|
||||
.filter(|line| match line {
|
||||
Ok(line) => line.contains(".o"),
|
||||
Err(_) => false,
|
||||
})
|
||||
.map(|result| result.unwrap())
|
||||
.collect::<Vec<String>>(),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let splits_path = args.out_dir.join("splits.txt");
|
||||
let mut splits_writer = BufWriter::new(
|
||||
File::create(&splits_path)
|
||||
.with_context(|| format!("Failed to create '{}'", splits_path.display()))?,
|
||||
);
|
||||
write_splits(&mut splits_writer, &obj, obj_files)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -449,10 +481,7 @@ fn signatures(args: SignaturesArgs) -> Result<()> {
|
||||
path.to_str().ok_or_else(|| anyhow!("'{}' is not valid UTF-8", path.display()))?;
|
||||
match path_str.strip_prefix('@') {
|
||||
Some(rsp_file) => {
|
||||
let reader = BufReader::new(
|
||||
File::open(rsp_file)
|
||||
.with_context(|| format!("Failed to open file '{rsp_file}'"))?,
|
||||
);
|
||||
let reader = buf_reader(rsp_file)?;
|
||||
for result in reader.lines() {
|
||||
let line = result?;
|
||||
if !line.is_empty() {
|
||||
@@ -466,10 +495,10 @@ fn signatures(args: SignaturesArgs) -> Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
let mut signatures: HashMap<Vec<u8>, FunctionSignature> = HashMap::new();
|
||||
let mut signatures: HashMap<String, FunctionSignature> = HashMap::new();
|
||||
for path in files {
|
||||
log::info!("Processing {}", path.display());
|
||||
let (data, signature) = match generate_signature(&path, &args.symbol) {
|
||||
let signature = match generate_signature(&path, &args.symbol) {
|
||||
Ok(Some(signature)) => signature,
|
||||
Ok(None) => continue,
|
||||
Err(e) => {
|
||||
@@ -478,13 +507,13 @@ fn signatures(args: SignaturesArgs) -> Result<()> {
|
||||
}
|
||||
};
|
||||
log::info!("Comparing hash {}", signature.hash);
|
||||
if let Some((_, existing)) = signatures.iter_mut().find(|(a, b)| *a == &data) {
|
||||
if let Some(existing) = signatures.get_mut(&signature.hash) {
|
||||
compare_signature(existing, &signature)?;
|
||||
} else {
|
||||
signatures.insert(data, signature);
|
||||
signatures.insert(signature.hash.clone(), signature);
|
||||
}
|
||||
}
|
||||
let mut signatures = signatures.into_iter().map(|(a, b)| b).collect::<Vec<FunctionSignature>>();
|
||||
let mut signatures = signatures.into_values().collect::<Vec<FunctionSignature>>();
|
||||
log::info!("{} unique signatures", signatures.len());
|
||||
signatures.sort_by_key(|s| s.signature.len());
|
||||
let out =
|
||||
|
||||
@@ -1,23 +1,25 @@
|
||||
use std::{
|
||||
fs::File,
|
||||
io::{BufWriter, Seek, SeekFrom, Write},
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
use anyhow::{anyhow, bail, ensure, Context, Result};
|
||||
use argh::FromArgs;
|
||||
use memmap2::MmapOptions;
|
||||
use object::{Architecture, Endianness, Object, ObjectKind, ObjectSection, SectionKind};
|
||||
|
||||
use crate::util::file::map_file;
|
||||
|
||||
#[derive(FromArgs, PartialEq, Eq, Debug)]
|
||||
/// Converts an ELF file to a DOL file.
|
||||
#[argh(subcommand, name = "elf2dol")]
|
||||
pub struct Args {
|
||||
#[argh(positional)]
|
||||
/// path to input ELF
|
||||
elf_file: String,
|
||||
elf_file: PathBuf,
|
||||
#[argh(positional)]
|
||||
/// path to output DOL
|
||||
dol_file: String,
|
||||
dol_file: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
@@ -42,10 +44,7 @@ const MAX_TEXT_SECTIONS: usize = 7;
|
||||
const MAX_DATA_SECTIONS: usize = 11;
|
||||
|
||||
pub fn run(args: Args) -> Result<()> {
|
||||
let elf_file = File::open(&args.elf_file)
|
||||
.with_context(|| format!("Failed to open ELF file '{}'", args.elf_file))?;
|
||||
let map = unsafe { MmapOptions::new().map(&elf_file) }
|
||||
.with_context(|| format!("Failed to mmap ELF file: '{}'", args.elf_file))?;
|
||||
let map = map_file(&args.elf_file)?;
|
||||
let obj_file = object::read::File::parse(&*map)?;
|
||||
match obj_file.architecture() {
|
||||
Architecture::PowerPc => {}
|
||||
@@ -61,7 +60,7 @@ pub fn run(args: Args) -> Result<()> {
|
||||
let mut offset = 0x100u32;
|
||||
let mut out = BufWriter::new(
|
||||
File::create(&args.dol_file)
|
||||
.with_context(|| format!("Failed to create DOL file '{}'", args.dol_file))?,
|
||||
.with_context(|| format!("Failed to create DOL file '{}'", args.dol_file.display()))?,
|
||||
);
|
||||
out.seek(SeekFrom::Start(offset as u64))?;
|
||||
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
use std::{fs::File, io::BufReader};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::{bail, ensure, Context, Result};
|
||||
use anyhow::{bail, ensure, Result};
|
||||
use argh::FromArgs;
|
||||
|
||||
use crate::util::map::{process_map, resolve_link_order, SymbolEntry, SymbolRef};
|
||||
use crate::util::{
|
||||
file::{map_file, map_reader},
|
||||
map::{process_map, resolve_link_order, SymbolEntry, SymbolRef},
|
||||
};
|
||||
|
||||
#[derive(FromArgs, PartialEq, Debug)]
|
||||
/// Commands for processing CodeWarrior maps.
|
||||
@@ -29,7 +32,7 @@ enum SubCommand {
|
||||
pub struct EntriesArgs {
|
||||
#[argh(positional)]
|
||||
/// path to input map
|
||||
map_file: String,
|
||||
map_file: PathBuf,
|
||||
#[argh(positional)]
|
||||
/// TU to display entries for
|
||||
unit: String,
|
||||
@@ -41,7 +44,7 @@ pub struct EntriesArgs {
|
||||
pub struct SymbolArgs {
|
||||
#[argh(positional)]
|
||||
/// path to input map
|
||||
map_file: String,
|
||||
map_file: PathBuf,
|
||||
#[argh(positional)]
|
||||
/// symbol to display references for
|
||||
symbol: String,
|
||||
@@ -53,7 +56,7 @@ pub struct SymbolArgs {
|
||||
pub struct OrderArgs {
|
||||
#[argh(positional)]
|
||||
/// path to input map
|
||||
map_file: String,
|
||||
map_file: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(FromArgs, PartialEq, Eq, Debug)]
|
||||
@@ -62,7 +65,7 @@ pub struct OrderArgs {
|
||||
pub struct SlicesArgs {
|
||||
#[argh(positional)]
|
||||
/// path to input map
|
||||
map_file: String,
|
||||
map_file: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(FromArgs, PartialEq, Eq, Debug)]
|
||||
@@ -71,7 +74,7 @@ pub struct SlicesArgs {
|
||||
pub struct SymbolsArgs {
|
||||
#[argh(positional)]
|
||||
/// path to input map
|
||||
map_file: String,
|
||||
map_file: PathBuf,
|
||||
}
|
||||
|
||||
pub fn run(args: Args) -> Result<()> {
|
||||
@@ -85,11 +88,8 @@ pub fn run(args: Args) -> Result<()> {
|
||||
}
|
||||
|
||||
fn entries(args: EntriesArgs) -> Result<()> {
|
||||
let reader = BufReader::new(
|
||||
File::open(&args.map_file)
|
||||
.with_context(|| format!("Failed to open file '{}'", args.map_file))?,
|
||||
);
|
||||
let entries = process_map(reader)?;
|
||||
let map = map_file(&args.map_file)?;
|
||||
let entries = process_map(map_reader(&map))?;
|
||||
match entries.unit_entries.get_vec(&args.unit) {
|
||||
Some(vec) => {
|
||||
for symbol_ref in vec {
|
||||
@@ -109,11 +109,8 @@ fn entries(args: EntriesArgs) -> Result<()> {
|
||||
}
|
||||
|
||||
fn symbol(args: SymbolArgs) -> Result<()> {
|
||||
let reader = BufReader::new(
|
||||
File::open(&args.map_file)
|
||||
.with_context(|| format!("Failed to open file '{}'", args.map_file))?,
|
||||
);
|
||||
let entries = process_map(reader)?;
|
||||
let map = map_file(&args.map_file)?;
|
||||
let entries = process_map(map_reader(&map))?;
|
||||
let mut opt_ref: Option<(SymbolRef, SymbolEntry)> = None;
|
||||
for (symbol_ref, entry) in &entries.symbols {
|
||||
if symbol_ref.name == args.symbol {
|
||||
@@ -164,11 +161,8 @@ fn symbol(args: SymbolArgs) -> Result<()> {
|
||||
}
|
||||
|
||||
fn order(args: OrderArgs) -> Result<()> {
|
||||
let reader = BufReader::new(
|
||||
File::open(&args.map_file)
|
||||
.with_context(|| format!("Failed to open file '{}'", args.map_file))?,
|
||||
);
|
||||
let entries = process_map(reader)?;
|
||||
let map = map_file(&args.map_file)?;
|
||||
let entries = process_map(map_reader(&map))?;
|
||||
let order = resolve_link_order(&entries.unit_order)?;
|
||||
for unit in order {
|
||||
println!("{unit}");
|
||||
@@ -177,11 +171,8 @@ fn order(args: OrderArgs) -> Result<()> {
|
||||
}
|
||||
|
||||
fn slices(args: SlicesArgs) -> Result<()> {
|
||||
let reader = BufReader::new(
|
||||
File::open(&args.map_file)
|
||||
.with_context(|| format!("Failed to open file '{}'", args.map_file))?,
|
||||
);
|
||||
let entries = process_map(reader)?;
|
||||
let map = map_file(&args.map_file)?;
|
||||
let entries = process_map(map_reader(&map))?;
|
||||
let order = resolve_link_order(&entries.unit_order)?;
|
||||
for unit in order {
|
||||
let unit_path = if let Some((lib, name)) = unit.split_once(' ') {
|
||||
@@ -210,11 +201,8 @@ fn slices(args: SlicesArgs) -> Result<()> {
|
||||
}
|
||||
|
||||
fn symbols(args: SymbolsArgs) -> Result<()> {
|
||||
let reader = BufReader::new(
|
||||
File::open(&args.map_file)
|
||||
.with_context(|| format!("Failed to open file '{}'", args.map_file))?,
|
||||
);
|
||||
let _entries = process_map(reader)?;
|
||||
let map = map_file(&args.map_file)?;
|
||||
let _entries = process_map(map_reader(&map))?;
|
||||
// for (address, symbol) in entries.address_to_symbol {
|
||||
// if symbol.name.starts_with('@') {
|
||||
// continue;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::{bail, ensure, Context, Result};
|
||||
use argh::FromArgs;
|
||||
use memchr::memmem;
|
||||
@@ -9,18 +11,19 @@ use memmap2::MmapOptions;
|
||||
pub struct Args {
|
||||
#[argh(positional)]
|
||||
/// path to source binary
|
||||
binary: String,
|
||||
binary: PathBuf,
|
||||
#[argh(positional)]
|
||||
/// path to build info string
|
||||
build_info: String,
|
||||
build_info: PathBuf,
|
||||
}
|
||||
|
||||
const BUILD_STRING_MAX: usize = 35;
|
||||
const BUILD_STRING_TAG: &str = "!#$MetroidBuildInfo!#$";
|
||||
|
||||
pub fn run(args: Args) -> Result<()> {
|
||||
let build_string = std::fs::read_to_string(&args.build_info)
|
||||
.with_context(|| format!("Failed to read build info string from '{}'", args.build_info))?;
|
||||
let build_string = std::fs::read_to_string(&args.build_info).with_context(|| {
|
||||
format!("Failed to read build info string from '{}'", args.build_info.display())
|
||||
})?;
|
||||
let build_string_trim = build_string.trim_end();
|
||||
let build_string_bytes = build_string_trim.as_bytes();
|
||||
ensure!(
|
||||
@@ -28,13 +31,12 @@ pub fn run(args: Args) -> Result<()> {
|
||||
"Build string '{build_string_trim}' is greater than maximum size of {BUILD_STRING_MAX}"
|
||||
);
|
||||
|
||||
let binary_file = std::fs::File::options()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.open(&args.binary)
|
||||
.with_context(|| format!("Failed to open binary for writing: '{}'", args.binary))?;
|
||||
let binary_file =
|
||||
std::fs::File::options().read(true).write(true).open(&args.binary).with_context(|| {
|
||||
format!("Failed to open binary for writing: '{}'", args.binary.display())
|
||||
})?;
|
||||
let mut map = unsafe { MmapOptions::new().map_mut(&binary_file) }
|
||||
.with_context(|| format!("Failed to mmap binary: '{}'", args.binary))?;
|
||||
.with_context(|| format!("Failed to mmap binary: '{}'", args.binary.display()))?;
|
||||
let start = match memmem::find(&map, BUILD_STRING_TAG.as_bytes()) {
|
||||
Some(idx) => idx + BUILD_STRING_TAG.as_bytes().len(),
|
||||
None => bail!("Failed to find build string tag in binary"),
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
pub(crate) mod ar;
|
||||
pub(crate) mod demangle;
|
||||
pub(crate) mod dol;
|
||||
pub(crate) mod elf;
|
||||
pub(crate) mod elf2dol;
|
||||
pub(crate) mod map;
|
||||
pub(crate) mod metroidbuildinfo;
|
||||
pub(crate) mod rel;
|
||||
pub(crate) mod shasum;
|
||||
pub mod ar;
|
||||
pub mod demangle;
|
||||
pub mod dol;
|
||||
pub mod elf;
|
||||
pub mod elf2dol;
|
||||
pub mod map;
|
||||
pub mod metroidbuildinfo;
|
||||
pub mod rel;
|
||||
pub mod rso;
|
||||
pub mod shasum;
|
||||
|
||||
@@ -1,25 +1,28 @@
|
||||
use std::{
|
||||
collections::{btree_map, BTreeMap},
|
||||
fs::File,
|
||||
io::{BufWriter, Write},
|
||||
io::Write,
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
use anyhow::{anyhow, bail, ensure, Context, Result};
|
||||
use anyhow::{bail, ensure, Context, Result};
|
||||
use argh::FromArgs;
|
||||
|
||||
use crate::{
|
||||
analysis::{
|
||||
cfa::AnalyzerState,
|
||||
pass::{AnalysisPass, FindSaveRestSleds, FindTRKInterruptVectorTable},
|
||||
tracker::Tracker,
|
||||
},
|
||||
cmd::dol::apply_signatures,
|
||||
obj::{ObjInfo, ObjReloc, ObjRelocKind, ObjSection, ObjSectionKind, ObjSymbol, ObjSymbolKind},
|
||||
util::{
|
||||
dol::process_dol,
|
||||
elf::write_elf,
|
||||
obj::{ObjInfo, ObjSection, ObjSymbol},
|
||||
nested::{NestedMap, NestedVec},
|
||||
rel::process_rel,
|
||||
},
|
||||
};
|
||||
use crate::util::cfa::{AnalysisPass, AnalyzerState, FindSaveRestSleds, FindTRKInterruptVectorTable};
|
||||
use crate::util::obj::{nested_push, ObjReloc, ObjRelocKind, ObjSectionKind, ObjSymbolKind};
|
||||
use crate::util::tracker::Tracker;
|
||||
|
||||
#[derive(FromArgs, PartialEq, Debug)]
|
||||
/// Commands for processing REL files.
|
||||
@@ -111,12 +114,7 @@ fn merge(args: MergeArgs) -> Result<()> {
|
||||
file_offset: mod_section.file_offset,
|
||||
section_known: mod_section.section_known,
|
||||
});
|
||||
nested_try_insert(
|
||||
&mut section_map,
|
||||
module.module_id,
|
||||
mod_section.elf_index as u32,
|
||||
offset,
|
||||
)?;
|
||||
section_map.nested_insert(module.module_id, mod_section.elf_index as u32, offset)?;
|
||||
let symbols = module.symbols_for_section(mod_section.index);
|
||||
for (_, mod_symbol) in symbols {
|
||||
obj.symbols.push(ObjSymbol {
|
||||
@@ -159,7 +157,7 @@ fn merge(args: MergeArgs) -> Result<()> {
|
||||
let sym_map = &mut symbol_maps[target_section_index];
|
||||
let target_symbol = {
|
||||
let mut result = None;
|
||||
for (&addr, symbol_idxs) in sym_map.range(..=target_addr).rev() {
|
||||
for (_addr, symbol_idxs) in sym_map.range(..=target_addr).rev() {
|
||||
let symbol_idx = if symbol_idxs.len() == 1 {
|
||||
symbol_idxs.first().cloned().unwrap()
|
||||
} else {
|
||||
@@ -183,10 +181,10 @@ fn merge(args: MergeArgs) -> Result<()> {
|
||||
ObjRelocKind::PpcAddr16Hi
|
||||
| ObjRelocKind::PpcAddr16Ha
|
||||
| ObjRelocKind::PpcAddr16Lo
|
||||
if !symbol.name.starts_with("..") =>
|
||||
{
|
||||
3
|
||||
}
|
||||
if !symbol.name.starts_with("..") =>
|
||||
{
|
||||
3
|
||||
}
|
||||
_ => 1,
|
||||
},
|
||||
ObjSymbolKind::Section => -1,
|
||||
@@ -231,7 +229,7 @@ fn merge(args: MergeArgs) -> Result<()> {
|
||||
flags: Default::default(),
|
||||
kind: Default::default(),
|
||||
});
|
||||
nested_push(sym_map, target_addr, symbol_idx);
|
||||
sym_map.nested_push(target_addr, symbol_idx);
|
||||
(symbol_idx, 0)
|
||||
};
|
||||
obj.sections[target_section_index].relocations.push(ObjReloc {
|
||||
@@ -288,25 +286,3 @@ fn merge(args: MergeArgs) -> Result<()> {
|
||||
file.flush()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn nested_try_insert<T1, T2, T3>(
|
||||
map: &mut BTreeMap<T1, BTreeMap<T2, T3>>,
|
||||
v1: T1,
|
||||
v2: T2,
|
||||
v3: T3,
|
||||
) -> Result<()>
|
||||
where
|
||||
T1: Eq + Ord,
|
||||
T2: Eq + Ord,
|
||||
{
|
||||
let map = match map.entry(v1) {
|
||||
btree_map::Entry::Occupied(entry) => entry.into_mut(),
|
||||
btree_map::Entry::Vacant(entry) => entry.insert(Default::default()),
|
||||
};
|
||||
match map.entry(v2) {
|
||||
btree_map::Entry::Occupied(_) => bail!("Entry already exists"),
|
||||
btree_map::Entry::Vacant(entry) => entry.insert(v3),
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
41
src/cmd/rso.rs
Normal file
41
src/cmd/rso.rs
Normal file
@@ -0,0 +1,41 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::Result;
|
||||
use argh::FromArgs;
|
||||
|
||||
use crate::util::rso::process_rso;
|
||||
|
||||
#[derive(FromArgs, PartialEq, Debug)]
|
||||
/// Commands for processing RSO files.
|
||||
#[argh(subcommand, name = "rso")]
|
||||
pub struct Args {
|
||||
#[argh(subcommand)]
|
||||
command: SubCommand,
|
||||
}
|
||||
|
||||
#[derive(FromArgs, PartialEq, Debug)]
|
||||
#[argh(subcommand)]
|
||||
enum SubCommand {
|
||||
Info(InfoArgs),
|
||||
}
|
||||
|
||||
#[derive(FromArgs, PartialEq, Eq, Debug)]
|
||||
/// Views RSO file information.
|
||||
#[argh(subcommand, name = "info")]
|
||||
pub struct InfoArgs {
|
||||
#[argh(positional)]
|
||||
/// RSO file
|
||||
rso_file: PathBuf,
|
||||
}
|
||||
|
||||
pub fn run(args: Args) -> Result<()> {
|
||||
match args.command {
|
||||
SubCommand::Info(c_args) => info(c_args),
|
||||
}
|
||||
}
|
||||
|
||||
fn info(args: InfoArgs) -> Result<()> {
|
||||
let rso = process_rso(&args.rso_file)?;
|
||||
println!("Read RSO module {}", rso.name);
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::{
|
||||
fs::{File, OpenOptions},
|
||||
io::{BufRead, BufReader, Read},
|
||||
path::Path,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use anyhow::{anyhow, bail, Context, Result};
|
||||
@@ -18,17 +18,17 @@ pub struct Args {
|
||||
check: bool,
|
||||
#[argh(positional)]
|
||||
/// path to file
|
||||
file: String,
|
||||
file: PathBuf,
|
||||
#[argh(option, short = 'o')]
|
||||
/// touch output file on successful check
|
||||
output: Option<String>,
|
||||
output: Option<PathBuf>,
|
||||
}
|
||||
|
||||
const DEFAULT_BUF_SIZE: usize = 8192;
|
||||
|
||||
pub fn run(args: Args) -> Result<()> {
|
||||
let file =
|
||||
File::open(&args.file).with_context(|| format!("Failed to open file '{}'", args.file))?;
|
||||
let file = File::open(&args.file)
|
||||
.with_context(|| format!("Failed to open file '{}'", args.file.display()))?;
|
||||
if args.check {
|
||||
check(args, file)
|
||||
} else {
|
||||
@@ -69,7 +69,8 @@ fn check(args: Args, file: File) -> Result<()> {
|
||||
std::process::exit(1);
|
||||
}
|
||||
if let Some(out_path) = args.output {
|
||||
touch(&out_path).with_context(|| format!("Failed to touch output file '{out_path}'"))?;
|
||||
touch(&out_path)
|
||||
.with_context(|| format!("Failed to touch output file '{}'", out_path.display()))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -79,7 +80,7 @@ fn hash(args: Args, file: File) -> Result<()> {
|
||||
let mut hash_buf = [0u8; 40];
|
||||
let hash_str = base16ct::lower::encode_str(&hash, &mut hash_buf)
|
||||
.map_err(|e| anyhow!("Failed to encode hash: {e}"))?;
|
||||
println!("{} {}", hash_str, args.file);
|
||||
println!("{} {}", hash_str, args.file.display());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user