Compare commits

..

No commits in common. "f984bc3fb2c2728c047cb9a64b85521ec792be50" and "c484952912e895dbeff5fa5b52ceb822f36a6ae2" have entirely different histories.

61 changed files with 2411 additions and 5113 deletions

View File

@ -1,17 +1,16 @@
name: Build
on:
pull_request:
push:
paths-ignore:
- '*.md'
- 'LICENSE*'
workflow_dispatch:
pull_request:
env:
BUILD_PROFILE: release-lto
CARGO_BIN_NAME: dtk
CARGO_TARGET_DIR: target
CARGO_INCREMENTAL: 0
jobs:
check:
@ -26,8 +25,6 @@ jobs:
uses: dtolnay/rust-toolchain@stable
with:
components: clippy
- name: Cache Rust workspace
uses: Swatinem/rust-cache@v2
- name: Cargo check
run: cargo check --all-features --all-targets
- name: Cargo clippy
@ -77,15 +74,11 @@ jobs:
uses: actions/checkout@v4
- name: Setup Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Cache Rust workspace
uses: Swatinem/rust-cache@v2
- name: Cargo test
run: cargo test --release --all-features
build:
name: Build dtk
env:
CARGO_BIN_NAME: dtk
name: Build
strategy:
matrix:
include:
@ -101,10 +94,10 @@ jobs:
target: aarch64-unknown-linux-musl
name: linux-aarch64
build: zigbuild
- platform: windows-latest
target: i686-pc-windows-msvc
name: windows-x86
build: build
- platform: ubuntu-latest
target: armv7-unknown-linux-musleabi
name: linux-armv7l
build: zigbuild
- platform: windows-latest
target: x86_64-pc-windows-msvc
name: windows-x86_64
@ -126,6 +119,19 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Check git tag against Cargo version
if: startsWith(github.ref, 'refs/tags/')
shell: bash
run: |
set -eou
version=$(grep '^version' Cargo.toml | awk -F' = ' '{print $2}' | tr -d '"')
version="v$version"
tag='${{github.ref}}'
tag="${tag#refs/tags/}"
if [ "$tag" != "$version" ]; then
echo "::error::Git tag doesn't match the Cargo version! ($tag != $version)"
exit 1
fi
- name: Install dependencies
if: matrix.packages != ''
run: |
@ -133,28 +139,20 @@ jobs:
sudo apt-get -y install ${{ matrix.packages }}
- name: Install cargo-zigbuild
if: matrix.build == 'zigbuild'
run: |
python3 -m venv .venv
. .venv/bin/activate
echo PATH=$PATH >> $GITHUB_ENV
pip install ziglang==0.13.0 cargo-zigbuild==0.19.1
run: pip install ziglang==0.12.0 cargo-zigbuild==0.18.4
- name: Setup Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- name: Cache Rust workspace
uses: Swatinem/rust-cache@v2
with:
key: ${{ matrix.target }}
- name: Cargo build
run: >
cargo ${{ matrix.build }} --profile ${{ env.BUILD_PROFILE }} --target ${{ matrix.target }}
--bin ${{ env.CARGO_BIN_NAME }}
run: cargo ${{ matrix.build }} --profile ${{ env.BUILD_PROFILE }} --all-features --target ${{ matrix.target }} --bin ${{ env.CARGO_BIN_NAME }}
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: ${{ env.CARGO_BIN_NAME }}-${{ matrix.name }}
name: ${{ matrix.name }}
path: |
${{ env.CARGO_TARGET_DIR }}/${{ env.BUILD_PROFILE }}/${{ env.CARGO_BIN_NAME }}
${{ env.CARGO_TARGET_DIR }}/${{ env.BUILD_PROFILE }}/${{ env.CARGO_BIN_NAME }}.exe
${{ env.CARGO_TARGET_DIR }}/${{ matrix.target }}/${{ env.BUILD_PROFILE }}/${{ env.CARGO_BIN_NAME }}
${{ env.CARGO_TARGET_DIR }}/${{ matrix.target }}/${{ env.BUILD_PROFILE }}/${{ env.CARGO_BIN_NAME }}.exe
if-no-files-found: error
@ -164,23 +162,7 @@ jobs:
if: startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
needs: [ build ]
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Check git tag against Cargo version
shell: bash
run: |
set -eou pipefail
tag='${{github.ref}}'
tag="${tag#refs/tags/}"
version=$(grep '^version' Cargo.toml | head -1 | awk -F' = ' '{print $2}' | tr -d '"')
version="v$version"
if [ "$tag" != "$version" ]; then
echo "::error::Git tag doesn't match the Cargo version! ($tag != $version)"
exit 1
fi
- name: Download artifacts
uses: actions/download-artifact@v4
with:
@ -188,28 +170,12 @@ jobs:
- name: Rename artifacts
working-directory: artifacts
run: |
set -euo pipefail
mkdir ../out
for dir in */; do
for file in "$dir"*; do
base=$(basename "$file")
name="${base%.*}"
ext="${base##*.}"
if [ "$ext" = "$base" ]; then
ext=""
else
ext=".$ext"
fi
arch="${dir%/}" # remove trailing slash
arch="${arch##"$name-"}" # remove bin name
dst="../out/${name}-${arch}${ext}"
mv "$file" "$dst"
done
for i in */*/$BUILD_PROFILE/$CARGO_BIN_NAME*; do
mv "$i" "../out/$(sed -E "s/([^/]+)\/[^/]+\/$BUILD_PROFILE\/($CARGO_BIN_NAME)/\2-\1/" <<< "$i")"
done
ls -R ../out
- name: Release
uses: softprops/action-gh-release@v2
with:
files: out/*
draft: true
generate_release_notes: true

973
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -3,13 +3,13 @@ name = "decomp-toolkit"
description = "Yet another GameCube/Wii decompilation toolkit."
authors = ["Luke Street <luke@street.dev>"]
license = "MIT OR Apache-2.0"
version = "1.1.4"
version = "0.9.3"
edition = "2021"
publish = false
repository = "https://github.com/encounter/decomp-toolkit"
readme = "README.md"
categories = ["command-line-utilities"]
rust-version = "1.81"
rust-version = "1.73.0"
[[bin]]
name = "dtk"
@ -20,63 +20,56 @@ panic = "abort"
[profile.release-lto]
inherits = "release"
lto = "fat"
lto = "thin"
strip = "debuginfo"
codegen-units = 1
[dependencies]
anyhow = { version = "1.0", features = ["backtrace"] }
anyhow = { version = "1.0.82", features = ["backtrace"] }
ar = { git = "https://github.com/bjorn3/rust-ar.git", branch = "write_symbol_table" }
argp = "0.3"
base16ct = "0.2"
base64 = "0.22"
byteorder = "1.5"
typed-path = "0.9"
crossterm = "0.28"
cwdemangle = "1.0"
cwextab = "1.0"
dyn-clone = "1.0"
enable-ansi-support = "0.2"
filetime = "0.2"
fixedbitset = "0.5"
flagset = { version = "0.4", features = ["serde"] }
glob = "0.3"
hex = "0.4"
indent = "0.1"
indexmap = "2.6"
itertools = "0.13"
log = "0.4"
memchr = "2.7"
memmap2 = "0.9"
multimap = "0.10"
nodtool = "1.4"
argp = "0.3.0"
base16ct = "0.2.0"
base64 = "0.22.1"
crossterm = "0.27.0"
cwdemangle = "1.0.0"
enable-ansi-support = "0.2.1"
filetime = "0.2.23"
fixedbitset = "0.5.7"
flagset = { version = "0.4.5", features = ["serde"] }
glob = "0.3.1"
hex = "0.4.3"
indent = "0.1.1"
indexmap = "2.2.6"
itertools = "0.12.1"
log = "0.4.21"
memchr = "2.7.2"
memmap2 = "0.9.4"
multimap = "0.10.0"
nintendo-lz = "0.1.3"
nodtool = { git = "https://github.com/encounter/nod-rs", rev = "03b83484cb17f94408fa0ef8e50d94951464d1b2" }
#nodtool = { path = "../nod-rs/nodtool" }
num_enum = "0.7"
objdiff-core = { version = "2.2", features = ["ppc"] }
num_enum = "0.7.2"
objdiff-core = { git = "https://github.com/encounter/objdiff", rev = "a5a6a3928e392d5af5d92826e73b77e074b8788c", features = ["ppc"] }
#objdiff-core = { path = "../objdiff/objdiff-core", features = ["ppc"] }
object = { version = "0.36", features = ["read_core", "std", "elf", "write_std"], default-features = false }
once_cell = "1.20"
orthrus-ncompress = "0.2"
owo-colors = { version = "4.1", features = ["supports-colors"] }
petgraph = { version = "0.6", default-features = false }
ppc750cl = "0.3"
rayon = "1.10"
regex = "1.11"
rustc-hash = "2.0"
sanitise-file-name = "1.0"
serde = "1.0"
serde_json = "1.0"
serde_repr = "0.1"
serde_yaml = "0.9"
sha-1 = "0.10"
size = "0.4"
supports-color = "3.0"
syntect = { version = "5.2", features = ["parsing", "regex-fancy", "dump-load"], default-features = false }
tracing = "0.1"
tracing-attributes = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
xxhash-rust = { version = "0.8", features = ["xxh3"] }
zerocopy = { version = "0.8", features = ["derive"] }
[target.'cfg(target_env = "musl")'.dependencies]
mimalloc = "0.1"
object = { version = "0.35.0", features = ["read_core", "std", "elf", "write_std"], default-features = false }
once_cell = "1.19.0"
orthrus-ncompress = "0.2.1"
owo-colors = { version = "4.0.0", features = ["supports-colors"] }
path-slash = "0.2.1"
petgraph = { version = "0.6.4", default-features = false }
ppc750cl = { git = "https://github.com/encounter/ppc750cl", rev = "6cbd7d888c7082c2c860f66cbb9848d633f753ed" }
rayon = "1.10.0"
regex = "1.10.4"
rustc-hash = "1.1.0"
sanitise-file-name = "1.0.0"
serde = "1.0.199"
serde_json = "1.0.116"
serde_repr = "0.1.19"
serde_yaml = "0.9.34"
sha-1 = "0.10.1"
supports-color = "3.0.0"
syntect = { version = "5.2.0", features = ["parsing", "regex-fancy", "dump-load"], default-features = false }
tracing = "0.1.40"
tracing-attributes = "0.1.27"
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
xxhash-rust = { version = "0.8.10", features = ["xxh3"] }
zerocopy = { version = "0.7.34", features = ["derive"] }

127
README.md
View File

@ -39,15 +39,12 @@ project structure and build system that uses decomp-toolkit under the hood.
- [rel info](#rel-info)
- [rel merge](#rel-merge)
- [rso info](#rso-info)
- [rso make](#rso-make)
- [shasum](#shasum)
- [nlzss decompress](#nlzss-decompress)
- [rarc list](#rarc-list)
- [rarc extract](#rarc-extract)
- [u8 list](#u8-list)
- [u8 extract](#u8-extract)
- [vfs ls](#vfs-ls)
- [vfs cp](#vfs-cp)
- [yay0 decompress](#yay0-decompress)
- [yay0 compress](#yay0-compress)
- [yaz0 decompress](#yaz0-decompress)
@ -91,6 +88,8 @@ binary that is byte-for-byte identical to the original, then we know that the de
decomp-toolkit provides tooling for analyzing and splitting the original binary into relocatable objects, as well
as generating the linker script and other files needed to link the decompiled code.
## Analyzer features
**Function boundary analysis**
@ -126,6 +125,11 @@ If desired, optionally writes GNU assembler-compatible files alongside the objec
**Linker script generation**
Generates `ldscript.lcf` for `mwldeppc.exe`.
**Future work**
- Support RSO files
- Add more signatures
## Commands
### ar create
@ -176,8 +180,6 @@ and its `nodtool` command line tool._
Displays information about disc images.
To list the contents of a disc image, use [vfs ls](#vfs-ls).
Supported disc image formats:
- ISO (GCM)
@ -186,7 +188,6 @@ Supported disc image formats:
- CISO (+ NKit 2 lossless)
- NFS (Wii U VC)
- GCZ
- TGC
```shell
$ dtk disc info /path/to/game.iso
@ -198,9 +199,6 @@ Extracts the contents of disc images to a directory.
See [disc info](#disc-info) for supported formats.
> [!NOTE]
> [vfs cp](#vfs-cp) is more flexible and supports disc images.
```shell
$ dtk disc extract /path/to/game.iso [outdir]
```
@ -235,23 +233,21 @@ $ dtk disc verify /path/to/game.iso
Analyzes a DOL file and outputs information section and symbol information.
See [vfs ls](#vfs-ls) for information on the VFS abstraction.
```shell
$ dtk dol info input.dol
# or, directly from a disc image
$ dtk dol info 'disc.rvz:sys/main.dol'
```
### dol split
> [!IMPORTANT]
> **This command is intended to be used as part of a decompilation project's build system.**
> For an example project structure and for documentation on the configuration, see
> [dtk-template](https://github.com/encounter/dtk-template).
> [!NOTE]
> This command is a work-in-progress.
Analyzes and splits a DOL file into relocatable objects based on user configuration.
**This command is intended to be used as part of a decompilation project's build system.**
For an example project structure and for documentation on the configuration, see
[dtk-template](https://github.com/encounter/dtk-template).
```shell
$ dtk dol split config.yml target
```
@ -325,8 +321,6 @@ Creates a DOL file from the provided ELF file.
```shell
$ dtk elf2dol input.elf output.dol
# or, to ignore certain sections
$ dtk elf2dol input.elf output.dol --ignore debug_section1 --ignore debug_section2
```
### map
@ -350,12 +344,8 @@ $ dtk map symbol Game.MAP 'Function__5ClassFv'
Prints information about a REL file.
See [vfs ls](#vfs-ls) for information on the VFS abstraction.
```shell
$ dtk rel info input.rel
# or, directly from a disc image
$ dtk rel info 'disc.rvz:files/RELS.arc:amem/d_a_tag_so.rel'
```
### rel merge
@ -378,22 +368,6 @@ Prints information about an RSO file.
$ dtk rso info input.rso
```
### rso make
> [!WARNING]
> This command does not yet support creating SEL files.
Creates an RSO file from a relocatable ELF file.
Options:
- `-o`, `--output <File>`: Output RSO file.
- `-m`, `--module-name <Name>`: Module name (or path). Default: input name
- `-e`, `--export <File>`: File containing exported symbol names. (Newline separated)
```shell
$ dtk rso make input.elf -o input.rso
```
### shasum
Calculate and verify SHA-1 hashes.
@ -418,10 +392,6 @@ $ dtk nlzss decompress rels/*.lz -o rels
### rarc list
> [!NOTE]
> [vfs ls](#vfs-ls) is more flexible and supports RARC archives.
> This command is now equivalent to `dtk vfs ls -r input.arc:`
Lists the contents of an RARC (older .arc) archive.
```shell
@ -430,10 +400,6 @@ $ dtk rarc list input.arc
### rarc extract
> [!NOTE]
> [vfs cp](#vfs-cp) is more flexible and supports RARC archives.
> This command is now equivalent to `dtk vfs cp input.arc: output_dir`
Extracts the contents of an RARC (older .arc) archive.
```shell
@ -442,10 +408,6 @@ $ dtk rarc extract input.arc -o output_dir
### u8 list
> [!NOTE]
> [vfs ls](#vfs-ls) is more flexible and supports U8 archives.
> This command is now equivalent to `dtk vfs ls -r input.arc:`
Extracts the contents of a U8 (newer .arc) archive.
```shell
@ -454,75 +416,12 @@ $ dtk u8 list input.arc
### u8 extract
> [!NOTE]
> [vfs cp](#vfs-cp) is more flexible and supports U8 archives.
> This command is now equivalent to `dtk vfs cp input.arc: output_dir`
Extracts the contents of a U8 (newer .arc) archive.
```shell
$ dtk u8 extract input.arc -o output_dir
```
### vfs ls
decomp-toolkit has a powerful virtual filesystem (VFS) abstraction that allows you to work with a
variety of containers. All operations happen in memory with minimal overhead and no temporary files.
Supported containers:
- Disc images (see [disc info](#disc-info) for supported formats)
- RARC archives (older .arc)
- U8 archives (newer .arc)
Supported compression formats are handled transparently:
- Yay0 (SZP) / Yaz0 (SZS)
- NLZSS (.lz) (Use `:nlzss` in the path)
`vfs ls` lists the contents of a container or directory.
Options:
- `-r`, `--recursive`: Recursively list contents.
- `-s`, `--short`: Only list file names.
Examples:
```shell
# List the contents of the `amem` directory inside `RELS.arc` in a disc image
$ dtk vfs ls 'disc.rvz:files/RELS.arc:amem'
# List the contents of `RELS.arc` recursively
$ dtk vfs ls -r 'disc.rvz:files/RELS.arc:'
# All commands that accept a file path can also accept a VFS path
$ dtk rel info 'disc.rvz:files/RELS.arc:amem/d_a_tag_so.rel'
# Example disc image within a disc image
$ dtk dol info 'disc.rvz:files/zz_demo.tgc:sys/main.dol'
````
### vfs cp
See [vfs ls](#vfs-ls) for information on the VFS abstraction.
`vfs cp` copies files and directories recursively to the host filesystem.
Options:
- `--no-decompress`: Do not decompress files when copying.
- `-q`, `--quiet`: Suppresses all output except errors.
Examples:
```shell
# Extract a file from a nested path in a disc image to the current directory
$ dtk vfs cp 'disc.rvz:files/RELS.arc:amem/d_a_tag_so.rel' .
# Directories are copied recursively, making it easy to extract entire archives
$ dtk vfs cp 'disc.rvz:files/RELS.arc:' rels
# Or, to disable automatic decompression
$ dtk vfs cp --no-decompress 'disc.rvz:files/RELS.arc:' rels
```
### yay0 decompress
Decompresses Yay0-compressed files.

182
deny.toml
View File

@ -9,11 +9,6 @@
# The values provided in this template are the default values that will be used
# when any section or field is not specified in your own configuration
# Root options
# The graph table configures how the dependency graph is constructed and thus
# which crates the checks are performed against
[graph]
# If 1 or more target triples (and optionally, target_features) are specified,
# only the specified targets will be checked when running `cargo deny check`.
# This means, if a particular package is only ever used as a target specific
@ -25,67 +20,53 @@
targets = [
# The triple can be any string, but only the target triples built in to
# rustc (as of 1.40) can be checked against actual config expressions
#"x86_64-unknown-linux-musl",
#{ triple = "x86_64-unknown-linux-musl" },
# You can also specify which target_features you promise are enabled for a
# particular target. target_features are currently not validated against
# the actual valid features supported by the target architecture.
#{ triple = "wasm32-unknown-unknown", features = ["atomics"] },
]
# When creating the dependency graph used as the source of truth when checks are
# executed, this field can be used to prune crates from the graph, removing them
# from the view of cargo-deny. This is an extremely heavy hammer, as if a crate
# is pruned from the graph, all of its dependencies will also be pruned unless
# they are connected to another crate in the graph that hasn't been pruned,
# so it should be used with care. The identifiers are [Package ID Specifications]
# (https://doc.rust-lang.org/cargo/reference/pkgid-spec.html)
#exclude = []
# If true, metadata will be collected with `--all-features`. Note that this can't
# be toggled off if true, if you want to conditionally enable `--all-features` it
# is recommended to pass `--all-features` on the cmd line instead
all-features = false
# If true, metadata will be collected with `--no-default-features`. The same
# caveat with `all-features` applies
no-default-features = false
# If set, these feature will be enabled when collecting metadata. If `--features`
# is specified on the cmd line they will take precedence over this option.
#features = []
# The output table provides options for how/if diagnostics are outputted
[output]
# When outputting inclusion graphs in diagnostics that include features, this
# option can be used to specify the depth at which feature edges will be added.
# This option is included since the graphs can be quite large and the addition
# of features from the crate(s) to all of the graph roots can be far too verbose.
# This option can be overridden via `--feature-depth` on the cmd line
feature-depth = 1
# This section is considered when running `cargo deny check advisories`
# More documentation for the advisories section can be found here:
# https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html
[advisories]
# The path where the advisory databases are cloned/fetched into
#db-path = "$CARGO_HOME/advisory-dbs"
# The path where the advisory database is cloned/fetched into
db-path = "~/.cargo/advisory-db"
# The url(s) of the advisory databases to use
#db-urls = ["https://github.com/rustsec/advisory-db"]
db-urls = ["https://github.com/rustsec/advisory-db"]
# The lint level for security vulnerabilities
vulnerability = "deny"
# The lint level for unmaintained crates
unmaintained = "warn"
# The lint level for crates that have been yanked from their source registry
yanked = "warn"
# The lint level for crates with security notices. Note that as of
# 2019-12-17 there are no security notice advisories in
# https://github.com/rustsec/advisory-db
notice = "warn"
# A list of advisory IDs to ignore. Note that ignored advisories will still
# output a note when they are encountered.
ignore = [
#"RUSTSEC-0000-0000",
#{ id = "RUSTSEC-0000-0000", reason = "you can specify a reason the advisory is ignored" },
#"a-crate-that-is-yanked@0.1.1", # you can also ignore yanked crate versions if you wish
#{ crate = "a-crate-that-is-yanked@0.1.1", reason = "you can specify why you are ignoring the yanked crate" },
]
# If this is true, then cargo deny will use the git executable to fetch advisory database.
# If this is false, then it uses a built-in git library.
# Setting this to true can be helpful if you have special authentication requirements that cargo-deny does not support.
# See Git Authentication for more information about setting up git authentication.
#git-fetch-with-cli = true
# Threshold for security vulnerabilities, any vulnerability with a CVSS score
# lower than the range specified will be ignored. Note that ignored advisories
# will still output a note when they are encountered.
# * None - CVSS Score 0.0
# * Low - CVSS Score 0.1 - 3.9
# * Medium - CVSS Score 4.0 - 6.9
# * High - CVSS Score 7.0 - 8.9
# * Critical - CVSS Score 9.0 - 10.0
#severity-threshold =
# This section is considered when running `cargo deny check licenses`
# More documentation for the licenses section can be found here:
# https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html
[licenses]
# List of explicitly allowed licenses
# The lint level for crates which do not have a detectable license
unlicensed = "deny"
# List of explictly allowed licenses
# See https://spdx.org/licenses/ for list of possible licenses
# [possible values: any SPDX 3.11 short identifier (+ optional exception)].
allow = [
@ -96,10 +77,28 @@ allow = [
"BSL-1.0",
"ISC",
"MIT",
"MPL-2.0",
"Unicode-DFS-2016",
"Zlib",
]
# List of explictly disallowed licenses
# See https://spdx.org/licenses/ for list of possible licenses
# [possible values: any SPDX 3.11 short identifier (+ optional exception)].
deny = [
#"Nokia",
]
# Lint level for licenses considered copyleft
copyleft = "warn"
# Blanket approval or denial for OSI-approved or FSF Free/Libre licenses
# * both - The license will be approved if it is both OSI-approved *AND* FSF
# * either - The license will be approved if it is either OSI-approved *OR* FSF
# * osi-only - The license will be approved if is OSI-approved *AND NOT* FSF
# * fsf-only - The license will be approved if is FSF *AND NOT* OSI-approved
# * neither - This predicate is ignored and the default lint level is used
allow-osi-fsf-free = "neither"
# Lint level used when no other predicates are matched
# 1. License isn't in the allow or deny lists
# 2. License isn't copyleft
# 3. License isn't OSI/FSF, or allow-osi-fsf-free = "neither"
default = "deny"
# The confidence threshold for detecting a license from license text.
# The higher the value, the more closely the license text must be to the
# canonical license text of a valid SPDX license file.
@ -110,32 +109,32 @@ confidence-threshold = 0.8
exceptions = [
# Each entry is the crate and version constraint, and its specific allow
# list
#{ allow = ["Zlib"], crate = "adler32" },
#{ allow = ["Zlib"], name = "adler32", version = "*" },
]
# Some crates don't have (easily) machine readable licensing information,
# adding a clarification entry for it allows you to manually specify the
# licensing information
#[[licenses.clarify]]
# The package spec the clarification applies to
#crate = "ring"
[[licenses.clarify]]
# The name of the crate the clarification applies to
name = "encoding_rs"
# The optional version constraint for the crate
#version = "*"
# The SPDX expression for the license requirements of the crate
#expression = "MIT AND ISC AND OpenSSL"
expression = "(Apache-2.0 OR MIT) AND BSD-3-Clause"
# One or more files in the crate's source used as the "source of truth" for
# the license expression. If the contents match, the clarification will be used
# when running the license check, otherwise the clarification will be ignored
# and the crate will be checked normally, which may produce warnings or errors
# depending on the rest of your configuration
#license-files = [
# Each entry is a crate relative path, and the (opaque) hash of its contents
#{ path = "LICENSE", hash = 0xbd0eed23 }
#]
license-files = [
# Each entry is a crate relative path, and the (opaque) hash of its contents
{ path = "COPYRIGHT", hash = 0x39f8ad31 }
]
[licenses.private]
# If true, ignores workspace crates that aren't published, or are only
# published to private registries.
# To see how to mark a crate as unpublished (to the official registry),
# visit https://doc.rust-lang.org/cargo/reference/manifest.html#the-publish-field.
# published to private registries
ignore = false
# One or more private registries that you might publish crates to, if a crate
# is only published to private registries, and ignore is true, the crate will
@ -149,7 +148,7 @@ registries = [
# https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html
[bans]
# Lint level for when multiple versions of the same crate are detected
multiple-versions = "allow"
multiple-versions = "warn"
# Lint level for when a crate version requirement is `*`
wildcards = "allow"
# The graph highlighting used when creating dotgraphs for crates
@ -158,63 +157,30 @@ wildcards = "allow"
# * simplest-path - The path to the version with the fewest edges is highlighted
# * all - Both lowest-version and simplest-path are used
highlight = "all"
# The default lint level for `default` features for crates that are members of
# the workspace that is being checked. This can be overridden by allowing/denying
# `default` on a crate-by-crate basis if desired.
workspace-default-features = "allow"
# The default lint level for `default` features for external crates that are not
# members of the workspace. This can be overridden by allowing/denying `default`
# on a crate-by-crate basis if desired.
external-default-features = "allow"
# List of crates that are allowed. Use with care!
allow = [
#"ansi_term@0.11.0",
#{ crate = "ansi_term@0.11.0", reason = "you can specify a reason it is allowed" },
#{ name = "ansi_term", version = "=0.11.0" },
]
# List of crates to deny
deny = [
#"ansi_term@0.11.0",
#{ crate = "ansi_term@0.11.0", reason = "you can specify a reason it is banned" },
# Each entry the name of a crate and a version range. If version is
# not specified, all versions will be matched.
#{ name = "ansi_term", version = "=0.11.0" },
#
# Wrapper crates can optionally be specified to allow the crate when it
# is a direct dependency of the otherwise banned crate
#{ crate = "ansi_term@0.11.0", wrappers = ["this-crate-directly-depends-on-ansi_term"] },
#{ name = "ansi_term", version = "=0.11.0", wrappers = [] },
]
# List of features to allow/deny
# Each entry the name of a crate and a version range. If version is
# not specified, all versions will be matched.
#[[bans.features]]
#crate = "reqwest"
# Features to not allow
#deny = ["json"]
# Features to allow
#allow = [
# "rustls",
# "__rustls",
# "__tls",
# "hyper-rustls",
# "rustls",
# "rustls-pemfile",
# "rustls-tls-webpki-roots",
# "tokio-rustls",
# "webpki-roots",
#]
# If true, the allowed features must exactly match the enabled feature set. If
# this is set there is no point setting `deny`
#exact = true
# Certain crates/versions that will be skipped when doing duplicate detection.
skip = [
#"ansi_term@0.11.0",
#{ crate = "ansi_term@0.11.0", reason = "you can specify a reason why it can't be updated/removed" },
#{ name = "ansi_term", version = "=0.11.0" },
]
# Similarly to `skip` allows you to skip certain crates during duplicate
# detection. Unlike skip, it also includes the entire tree of transitive
# dependencies starting at the specified crate, up to a certain depth, which is
# by default infinite.
# by default infinite
skip-tree = [
#"ansi_term@0.11.0", # will be skipped along with _all_ of its direct and transitive dependencies
#{ crate = "ansi_term@0.11.0", depth = 20 },
#{ name = "ansi_term", version = "=0.11.0", depth = 20 },
]
# This section is considered when running `cargo deny check sources`.
@ -234,9 +200,9 @@ allow-registry = ["https://github.com/rust-lang/crates.io-index"]
allow-git = []
[sources.allow-org]
# github.com organizations to allow git sources for
github = ["bjorn3"]
# gitlab.com organizations to allow git sources for
gitlab = []
# bitbucket.org organizations to allow git sources for
bitbucket = []
# 1 or more github.com organizations to allow git sources for
#github = [""]
# 1 or more gitlab.com organizations to allow git sources for
#gitlab = [""]
# 1 or more bitbucket.org organizations to allow git sources for
#bitbucket = [""]

View File

@ -16,15 +16,12 @@ use crate::{
vm::{BranchTarget, GprValue, StepResult, VM},
RelocationTarget,
},
obj::{
ObjInfo, ObjSectionKind, ObjSymbol, ObjSymbolFlagSet, ObjSymbolFlags, ObjSymbolKind,
SectionIndex,
},
obj::{ObjInfo, ObjSectionKind, ObjSymbol, ObjSymbolFlagSet, ObjSymbolFlags, ObjSymbolKind},
};
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct SectionAddress {
pub section: SectionIndex,
pub section: usize,
pub address: u32,
}
@ -41,7 +38,7 @@ impl Display for SectionAddress {
}
impl SectionAddress {
pub fn new(section: SectionIndex, address: u32) -> Self { Self { section, address } }
pub fn new(section: usize, address: u32) -> Self { Self { section, address } }
pub fn offset(self, offset: i32) -> Self {
Self { section: self.section, address: self.address.wrapping_add_signed(offset) }
@ -56,10 +53,6 @@ impl SectionAddress {
}
pub fn is_aligned(self, align: u32) -> bool { self.address & (align - 1) == 0 }
pub fn wrapping_add(self, rhs: u32) -> Self {
Self { section: self.section, address: self.address.wrapping_add(rhs) }
}
}
impl Add<u32> for SectionAddress {
@ -123,7 +116,7 @@ pub struct AnalyzerState {
pub functions: BTreeMap<SectionAddress, FunctionInfo>,
pub jump_tables: BTreeMap<SectionAddress, u32>,
pub known_symbols: BTreeMap<SectionAddress, Vec<ObjSymbol>>,
pub known_sections: BTreeMap<SectionIndex, String>,
pub known_sections: BTreeMap<usize, String>,
}
impl AnalyzerState {
@ -604,27 +597,19 @@ pub fn locate_bss_memsets(obj: &mut ObjInfo) -> Result<Vec<(u32, u32)>> {
StepResult::Branch(branches) => {
for branch in branches {
if branch.link {
// Some ProDG crt0.s versions use the wrong registers, some don't
// ProDG bug? Registers are supposed to start at r3
if let (
GprValue::Constant(addr),
GprValue::Constant(value),
GprValue::Constant(size),
) = {
if vm.gpr_value(4) == GprValue::Constant(0) {
(vm.gpr_value(3), vm.gpr_value(4), vm.gpr_value(5))
} else {
(vm.gpr_value(4), vm.gpr_value(5), vm.gpr_value(6))
}
} {
) = (vm.gpr_value(4), vm.gpr_value(5), vm.gpr_value(6))
{
if value == 0 && size > 0 {
bss_sections.push((addr, size));
}
}
}
}
if bss_sections.len() >= 2 {
return Ok(ExecCbResult::End(()));
}
Ok(ExecCbResult::Continue)
}
}

View File

@ -18,7 +18,7 @@ struct VisitedAddresses {
impl VisitedAddresses {
pub fn new(obj: &ObjInfo) -> Self {
let mut inner = Vec::with_capacity(obj.sections.len() as usize);
let mut inner = Vec::with_capacity(obj.sections.len());
for (_, section) in obj.sections.iter() {
if section.kind == ObjSectionKind::Code {
let size = (section.size / 4) as usize;
@ -32,13 +32,11 @@ impl VisitedAddresses {
}
pub fn contains(&self, section_address: u32, address: SectionAddress) -> bool {
self.inner[address.section as usize]
.contains(Self::bit_for(section_address, address.address))
self.inner[address.section].contains(Self::bit_for(section_address, address.address))
}
pub fn insert(&mut self, section_address: u32, address: SectionAddress) {
self.inner[address.section as usize]
.insert(Self::bit_for(section_address, address.address));
self.inner[address.section].insert(Self::bit_for(section_address, address.address));
}
#[inline]

View File

@ -6,9 +6,7 @@ use ppc750cl::Ins;
use crate::{
analysis::cfa::SectionAddress,
array_ref,
obj::{
ObjInfo, ObjKind, ObjRelocKind, ObjSection, ObjSectionKind, ObjSymbolKind, SectionIndex,
},
obj::{ObjInfo, ObjKind, ObjRelocKind, ObjSection, ObjSectionKind, ObjSymbolKind},
};
pub mod cfa;
@ -38,9 +36,11 @@ fn read_unresolved_relocation_address(
address: u32,
reloc_kind: Option<ObjRelocKind>,
) -> Result<Option<RelocationTarget>> {
if let Some(reloc) = obj.unresolved_relocations.iter().find(|reloc| {
reloc.section as SectionIndex == section.elf_index && reloc.address == address
}) {
if let Some(reloc) = obj
.unresolved_relocations
.iter()
.find(|reloc| reloc.section as usize == section.elf_index && reloc.address == address)
{
if reloc.module_id != obj.module_id {
return Ok(Some(RelocationTarget::External));
}
@ -48,7 +48,7 @@ fn read_unresolved_relocation_address(
ensure!(reloc.kind == reloc_kind);
}
let (target_section_index, target_section) =
obj.sections.get_elf_index(reloc.target_section as SectionIndex).ok_or_else(|| {
obj.sections.get_elf_index(reloc.target_section as usize).ok_or_else(|| {
anyhow!(
"Failed to find target section {} for unresolved relocation",
reloc.target_section

View File

@ -1,7 +1,7 @@
use anyhow::Result;
use crate::{
obj::{ObjDataKind, ObjInfo, ObjSectionKind, ObjSymbolKind, SymbolIndex},
obj::{ObjDataKind, ObjInfo, ObjSectionKind, ObjSymbolKind},
util::split::is_linker_generated_label,
};
@ -64,7 +64,7 @@ pub fn detect_objects(obj: &mut ObjInfo) -> Result<()> {
}
pub fn detect_strings(obj: &mut ObjInfo) -> Result<()> {
let mut symbols_set = Vec::<(SymbolIndex, ObjDataKind, usize)>::new();
let mut symbols_set = Vec::<(usize, ObjDataKind, usize)>::new();
for (section_index, section) in obj
.sections
.iter()

View File

@ -7,7 +7,7 @@ use crate::{
analysis::cfa::{AnalyzerState, FunctionInfo, SectionAddress},
obj::{
ObjInfo, ObjKind, ObjRelocKind, ObjSectionKind, ObjSymbol, ObjSymbolFlagSet,
ObjSymbolFlags, ObjSymbolKind, SectionIndex,
ObjSymbolFlags, ObjSymbolKind,
},
};
@ -147,14 +147,14 @@ impl AnalysisPass for FindRelCtorsDtors {
// And the section ends with a null pointer
while let Some(reloc) = obj.unresolved_relocations.iter().find(|reloc| {
reloc.module_id == obj.module_id
&& reloc.section as SectionIndex == section.elf_index
&& reloc.section == section.elf_index as u8
&& reloc.address == current_address
&& reloc.kind == ObjRelocKind::Absolute
}) {
let Some((target_section_index, target_section)) =
obj.sections.iter().find(|(_, section)| {
section.elf_index == reloc.target_section as SectionIndex
})
let Some((target_section_index, target_section)) = obj
.sections
.iter()
.find(|(_, section)| section.elf_index == reloc.target_section as usize)
else {
return false;
};

View File

@ -4,7 +4,6 @@ use std::{
};
use anyhow::{bail, Result};
use cwextab::decode_extab;
use ppc750cl::Opcode;
use tracing::{debug_span, info_span};
use tracing_attributes::instrument;
@ -19,7 +18,7 @@ use crate::{
},
obj::{
ObjDataKind, ObjInfo, ObjKind, ObjReloc, ObjRelocKind, ObjSection, ObjSectionKind,
ObjSymbol, ObjSymbolFlagSet, ObjSymbolFlags, ObjSymbolKind, SectionIndex, SymbolIndex,
ObjSymbol, ObjSymbolFlagSet, ObjSymbolFlags, ObjSymbolKind,
},
};
@ -121,7 +120,6 @@ impl Tracker {
self.process_data(obj, section_index, section)?;
}
}
self.check_extab_relocations(obj)?;
self.reject_invalid_relocations(obj)?;
Ok(())
}
@ -149,62 +147,6 @@ impl Tracker {
Ok(())
}
/// Check all of the extab relocations, and reject any invalid ones by checking against the decoded table data
/// of each table.
fn check_extab_relocations(&mut self, obj: &ObjInfo) -> Result<()> {
let mut to_reject = vec![];
let Some((section_index, section)) = obj.sections.by_name("extab")? else {
// No extab section found, return
return Ok(());
};
let mut decoded_reloc_addrs: BTreeSet<u32> = BTreeSet::new();
// Decode each exception table, and collect all of the relocations from the decoded data for each
for (_, symbol) in obj.symbols.for_section(section_index) {
let extab_name = &symbol.name;
let extab_start_addr: u32 = symbol.address as u32;
let extab_end_addr: u32 = extab_start_addr + symbol.size as u32;
let Ok(extab_data) = section.data_range(extab_start_addr, extab_end_addr) else {
log::warn!("Failed to get extab data for symbol {}", extab_name);
continue;
};
let data = match decode_extab(extab_data) {
Ok(decoded_data) => decoded_data,
Err(e) => {
log::warn!(
"Exception table decoding failed for symbol {}, reason: {}",
extab_name,
e
);
continue;
}
};
for reloc in data.relocations {
let reloc_addr = extab_start_addr + reloc.offset;
decoded_reloc_addrs.insert(reloc_addr);
}
}
let section_start_addr = SectionAddress::new(section_index, section.address as u32);
let section_end_addr = section_start_addr + (section.size as u32);
// Check all the extab relocations against the list of relocations from the decoded tables. Any
// relocations that aren't in the list are invalid, and are removed (if a table fails to decode,
// however, its relocations are all removed).
for (&address, _) in self.relocations.range(section_start_addr..section_end_addr) {
if !decoded_reloc_addrs.contains(&address.address) {
log::debug!("Rejecting invalid extab relocation @ {}", address);
to_reject.push(address);
}
}
for address in to_reject {
self.relocations.remove(&address);
}
Ok(())
}
fn process_code(&mut self, obj: &ObjInfo) -> Result<()> {
if let Some(entry) = obj.entry {
let (section_index, _) = obj.sections.at_address(entry as u32)?;
@ -298,7 +240,7 @@ impl Tracker {
debug_assert_ne!(
value,
RelocationTarget::Address(SectionAddress::new(
SectionIndex::MAX,
usize::MAX,
0
))
);
@ -359,7 +301,7 @@ impl Tracker {
debug_assert_ne!(
address,
RelocationTarget::Address(SectionAddress::new(
SectionIndex::MAX,
usize::MAX,
0
))
);
@ -380,7 +322,7 @@ impl Tracker {
debug_assert_ne!(
address,
RelocationTarget::Address(SectionAddress::new(
SectionIndex::MAX,
usize::MAX,
0
))
);
@ -464,7 +406,7 @@ impl Tracker {
{
(addr, is_function_addr(addr))
} else {
(SectionAddress::new(SectionIndex::MAX, 0), false)
(SectionAddress::new(usize::MAX, 0), false)
};
if branch.link || !is_fn_addr {
self.relocations.insert(ins_addr, match ins.op {
@ -549,7 +491,7 @@ impl Tracker {
fn process_data(
&mut self,
obj: &ObjInfo,
section_index: SectionIndex,
section_index: usize,
section: &ObjSection,
) -> Result<()> {
let mut addr = SectionAddress::new(section_index, section.address as u32);
@ -602,7 +544,7 @@ impl Tracker {
} else {
// Check known relocations (function signature matching)
if self.known_relocations.contains(&from) {
return Some(SectionAddress::new(SectionIndex::MAX, addr));
return Some(SectionAddress::new(usize::MAX, addr));
}
// Check special symbols
if self.stack_address == Some(addr)
@ -613,7 +555,7 @@ impl Tracker {
|| self.sda2_base == Some(addr)
|| self.sda_base == Some(addr)
{
return Some(SectionAddress::new(SectionIndex::MAX, addr));
return Some(SectionAddress::new(usize::MAX, addr));
}
// Not valid
None
@ -625,7 +567,7 @@ impl Tracker {
obj: &mut ObjInfo,
addr: u32,
reloc_kind: ObjRelocKind,
) -> Option<SymbolIndex> {
) -> Option<usize> {
if !matches!(
reloc_kind,
ObjRelocKind::PpcAddr16Ha | ObjRelocKind::PpcAddr16Lo
@ -641,7 +583,7 @@ impl Tracker {
// return generate_special_symbol(obj, addr, &name).ok();
// }
// }
let mut check_symbol = |opt: Option<u32>, name: &str| -> Option<SymbolIndex> {
let mut check_symbol = |opt: Option<u32>, name: &str| -> Option<usize> {
if let Some(value) = opt {
if addr == value {
return generate_special_symbol(obj, value, name).ok();
@ -866,7 +808,7 @@ fn data_kind_from_op(op: Opcode) -> DataKind {
}
}
fn generate_special_symbol(obj: &mut ObjInfo, addr: u32, name: &str) -> Result<SymbolIndex> {
fn generate_special_symbol(obj: &mut ObjInfo, addr: u32, name: &str) -> Result<usize> {
obj.add_symbol(
ObjSymbol {
name: name.to_string(),

View File

@ -208,11 +208,11 @@ impl VM {
(
GprValue::Address(RelocationTarget::Address(left)),
GprValue::Constant(right),
) => GprValue::Address(RelocationTarget::Address(left.wrapping_add(right))),
) => GprValue::Address(RelocationTarget::Address(left + right)),
(
GprValue::Constant(left),
GprValue::Address(RelocationTarget::Address(right)),
) => GprValue::Address(RelocationTarget::Address(right.wrapping_add(left))),
) => GprValue::Address(RelocationTarget::Address(right + left)),
_ => GprValue::Unknown,
};
self.gpr[ins.field_rd() as usize].set_direct(value);

View File

@ -1,18 +1,18 @@
use std::io::{stdout, Write};
use std::{
io::{stdout, Write},
path::PathBuf,
};
use anyhow::Result;
use argp::FromArgs;
use typed_path::Utf8NativePathBuf;
use crate::{
cmd,
util::{
alf::AlfFile,
file::buf_writer,
path::native_path,
file::{buf_writer, map_file},
reader::{Endian, FromReader},
},
vfs::open_file,
};
#[derive(FromArgs, PartialEq, Debug)]
@ -34,21 +34,21 @@ enum SubCommand {
/// Prints information about an alf file. (Same as `dol info`)
#[argp(subcommand, name = "info")]
pub struct InfoArgs {
#[argp(positional, from_str_fn(native_path))]
#[argp(positional)]
/// alf file
file: Utf8NativePathBuf,
file: PathBuf,
}
#[derive(FromArgs, PartialEq, Debug)]
/// Extracts symbol hashes from an alf file.
#[argp(subcommand, name = "hashes")]
pub struct HashesArgs {
#[argp(positional, from_str_fn(native_path))]
#[argp(positional)]
/// alf file
alf_file: Utf8NativePathBuf,
#[argp(positional, from_str_fn(native_path))]
alf_file: PathBuf,
#[argp(positional)]
/// output file
output: Option<Utf8NativePathBuf>,
output: Option<PathBuf>,
}
pub fn run(args: Args) -> Result<()> {
@ -60,10 +60,11 @@ pub fn run(args: Args) -> Result<()> {
fn hashes(args: HashesArgs) -> Result<()> {
let alf_file = {
let mut file = open_file(&args.alf_file, true)?;
AlfFile::from_reader(file.as_mut(), Endian::Little)?
let file = map_file(&args.alf_file)?;
let mut reader = file.as_reader();
AlfFile::from_reader(&mut reader, Endian::Little)?
};
let mut w: Box<dyn Write> = if let Some(output) = &args.output {
let mut w: Box<dyn Write> = if let Some(output) = args.output {
Box::new(buf_writer(output)?)
} else {
Box::new(stdout())

View File

@ -2,20 +2,14 @@ use std::{
collections::{btree_map::Entry, BTreeMap},
fs::File,
io::Write,
path::PathBuf,
};
use anyhow::{anyhow, bail, Context, Result};
use argp::FromArgs;
use object::{Object, ObjectSymbol, SymbolScope};
use typed_path::Utf8NativePathBuf;
use crate::{
util::{
file::{buf_writer, process_rsp},
path::native_path,
},
vfs::open_file,
};
use crate::util::file::{buf_writer, map_file, map_file_basic, process_rsp};
#[derive(FromArgs, PartialEq, Debug)]
/// Commands for processing static libraries.
@ -36,24 +30,24 @@ enum SubCommand {
/// Creates a static library.
#[argp(subcommand, name = "create")]
pub struct CreateArgs {
#[argp(positional, from_str_fn(native_path))]
#[argp(positional)]
/// output file
out: Utf8NativePathBuf,
#[argp(positional, from_str_fn(native_path))]
out: PathBuf,
#[argp(positional)]
/// input files
files: Vec<Utf8NativePathBuf>,
files: Vec<PathBuf>,
}
#[derive(FromArgs, PartialEq, Eq, Debug)]
/// Extracts a static library.
#[argp(subcommand, name = "extract")]
pub struct ExtractArgs {
#[argp(positional, from_str_fn(native_path))]
#[argp(positional)]
/// input files
files: Vec<Utf8NativePathBuf>,
#[argp(option, short = 'o', from_str_fn(native_path))]
files: Vec<PathBuf>,
#[argp(option, short = 'o')]
/// output directory
out: Option<Utf8NativePathBuf>,
out: Option<PathBuf>,
#[argp(switch, short = 'q')]
/// quiet output
quiet: bool,
@ -77,16 +71,17 @@ fn create(args: CreateArgs) -> Result<()> {
let mut identifiers = Vec::with_capacity(files.len());
let mut symbol_table = BTreeMap::new();
for path in &files {
let unix_path = path.with_unix_encoding();
let identifier = unix_path.as_str().as_bytes().to_vec();
let path_str =
path.to_str().ok_or_else(|| anyhow!("'{}' is not valid UTF-8", path.display()))?;
let identifier = path_str.as_bytes().to_vec();
identifiers.push(identifier.clone());
let entries = match symbol_table.entry(identifier) {
Entry::Vacant(e) => e.insert(Vec::new()),
Entry::Occupied(_) => bail!("Duplicate file name '{unix_path}'"),
Entry::Occupied(_) => bail!("Duplicate file name '{path_str}'"),
};
let mut file = open_file(path, false)?;
let obj = object::File::parse(file.map()?)?;
let file = map_file_basic(path)?;
let obj = object::File::parse(file.as_slice())?;
for symbol in obj.symbols() {
if symbol.scope() == SymbolScope::Dynamic {
entries.push(symbol.name_bytes()?.to_vec());
@ -104,8 +99,10 @@ fn create(args: CreateArgs) -> Result<()> {
symbol_table,
)?;
for path in files {
let path_str =
path.to_str().ok_or_else(|| anyhow!("'{}' is not valid UTF-8", path.display()))?;
let mut file = File::open(&path)?;
builder.append_file(path.as_str().as_bytes(), &mut file)?;
builder.append_file(path_str.as_bytes(), &mut file)?;
}
builder.into_inner()?.flush()?;
Ok(())
@ -118,8 +115,7 @@ fn extract(args: ExtractArgs) -> Result<()> {
// Extract files
let mut num_files = 0;
for path in &files {
let mut out_dir =
if let Some(out) = &args.out { out.clone() } else { Utf8NativePathBuf::new() };
let mut out_dir = if let Some(out) = &args.out { out.clone() } else { PathBuf::new() };
// If there are multiple files, extract to separate directories
if files.len() > 1 {
out_dir
@ -127,13 +123,14 @@ fn extract(args: ExtractArgs) -> Result<()> {
}
std::fs::create_dir_all(&out_dir)?;
if !args.quiet {
println!("Extracting {} to {}", path, out_dir);
println!("Extracting {} to {}", path.display(), out_dir.display());
}
let mut file = open_file(path, false)?;
let mut archive = ar::Archive::new(file.map()?);
let file = map_file(path)?;
let mut archive = ar::Archive::new(file.as_slice());
while let Some(entry) = archive.next_entry() {
let mut entry = entry.with_context(|| format!("Processing entry in {}", path))?;
let mut entry =
entry.with_context(|| format!("Processing entry in {}", path.display()))?;
let file_name = std::str::from_utf8(entry.header().identifier())?;
if !args.quiet && args.verbose {
println!("\t{}", file_name);
@ -146,7 +143,7 @@ fn extract(args: ExtractArgs) -> Result<()> {
std::fs::create_dir_all(parent)?;
}
let mut file = File::create(&file_path)
.with_context(|| format!("Failed to create file {}", file_path))?;
.with_context(|| format!("Failed to create file {}", file_path.display()))?;
std::io::copy(&mut entry, &mut file)?;
file.flush()?;

File diff suppressed because it is too large Load Diff

View File

@ -2,6 +2,7 @@ use std::{
collections::{btree_map, BTreeMap},
io::{stdout, Cursor, Read, Write},
ops::Bound::{Excluded, Unbounded},
path::PathBuf,
str::from_utf8,
};
@ -14,18 +15,13 @@ use syntect::{
highlighting::{Color, HighlightIterator, HighlightState, Highlighter, Theme, ThemeSet},
parsing::{ParseState, ScopeStack, SyntaxReference, SyntaxSet},
};
use typed_path::Utf8NativePathBuf;
use crate::{
util::{
dwarf::{
process_compile_unit, process_cu_tag, process_overlay_branch, read_debug_section,
should_skip_tag, tag_type_string, AttributeKind, TagKind,
},
file::buf_writer,
path::native_path,
use crate::util::{
dwarf::{
process_compile_unit, process_cu_tag, process_overlay_branch, read_debug_section,
should_skip_tag, tag_type_string, AttributeKind, TagKind,
},
vfs::open_file,
file::{buf_writer, map_file},
};
#[derive(FromArgs, PartialEq, Debug)]
@ -46,12 +42,12 @@ enum SubCommand {
/// Dumps DWARF 1.1 info from an object or archive.
#[argp(subcommand, name = "dump")]
pub struct DumpArgs {
#[argp(positional, from_str_fn(native_path))]
#[argp(positional)]
/// Input object. (ELF or archive)
in_file: Utf8NativePathBuf,
#[argp(option, short = 'o', from_str_fn(native_path))]
in_file: PathBuf,
#[argp(option, short = 'o')]
/// Output file. (Or directory, for archive)
out: Option<Utf8NativePathBuf>,
out: Option<PathBuf>,
#[argp(switch)]
/// Disable color output.
no_color: bool,
@ -77,8 +73,8 @@ fn dump(args: DumpArgs) -> Result<()> {
let theme = theme_set.themes.get("Solarized (dark)").context("Failed to load theme")?;
let syntax = syntax_set.find_syntax_by_name("C++").context("Failed to find syntax")?.clone();
let mut file = open_file(&args.in_file, true)?;
let buf = file.map()?;
let file = map_file(&args.in_file)?;
let buf = file.as_slice();
if buf.starts_with(b"!<arch>\n") {
let mut archive = ar::Archive::new(buf);
while let Some(result) = archive.next_entry() {
@ -105,7 +101,7 @@ fn dump(args: DumpArgs) -> Result<()> {
let name = name.trim_start_matches("D:").replace('\\', "/");
let name = name.rsplit_once('/').map(|(_, b)| b).unwrap_or(&name);
let file_path = out_path.join(format!("{}.txt", name));
let mut file = buf_writer(&file_path)?;
let mut file = buf_writer(file_path)?;
dump_debug_section(&args, &mut file, &obj_file, debug_section)?;
file.flush()?;
} else if args.no_color {

View File

@ -3,6 +3,7 @@ use std::{
fs,
fs::DirBuilder,
io::{Cursor, Write},
path::PathBuf,
};
use anyhow::{anyhow, bail, ensure, Context, Result};
@ -14,7 +15,6 @@ use object::{
FileFlags, Object, ObjectSection, ObjectSymbol, RelocationTarget, SectionFlags, SectionIndex,
SectionKind, SymbolFlags, SymbolIndex, SymbolKind, SymbolScope, SymbolSection,
};
use typed_path::{Utf8NativePath, Utf8NativePathBuf};
use crate::{
obj::ObjKind,
@ -24,7 +24,6 @@ use crate::{
config::{write_splits_file, write_symbols_file},
elf::{process_elf, write_elf},
file::{buf_writer, process_rsp},
path::native_path,
reader::{Endian, FromReader},
signatures::{compare_signature, generate_signature, FunctionSignature},
split::split_obj,
@ -55,72 +54,72 @@ enum SubCommand {
/// Disassembles an ELF file.
#[argp(subcommand, name = "disasm")]
pub struct DisasmArgs {
#[argp(positional, from_str_fn(native_path))]
#[argp(positional)]
/// input file
elf_file: Utf8NativePathBuf,
#[argp(positional, from_str_fn(native_path))]
elf_file: PathBuf,
#[argp(positional)]
/// output file (.o) or directory (.elf)
out: Utf8NativePathBuf,
out: PathBuf,
}
#[derive(FromArgs, PartialEq, Eq, Debug)]
/// Fixes issues with GNU assembler built object files.
#[argp(subcommand, name = "fixup")]
pub struct FixupArgs {
#[argp(positional, from_str_fn(native_path))]
#[argp(positional)]
/// input file
in_file: Utf8NativePathBuf,
#[argp(positional, from_str_fn(native_path))]
in_file: PathBuf,
#[argp(positional)]
/// output file
out_file: Utf8NativePathBuf,
out_file: PathBuf,
}
#[derive(FromArgs, PartialEq, Eq, Debug)]
/// Splits an executable ELF into relocatable objects.
#[argp(subcommand, name = "split")]
pub struct SplitArgs {
#[argp(positional, from_str_fn(native_path))]
#[argp(positional)]
/// input file
in_file: Utf8NativePathBuf,
#[argp(positional, from_str_fn(native_path))]
in_file: PathBuf,
#[argp(positional)]
/// output directory
out_dir: Utf8NativePathBuf,
out_dir: PathBuf,
}
#[derive(FromArgs, PartialEq, Eq, Debug)]
/// Generates configuration files from an executable ELF.
#[argp(subcommand, name = "config")]
pub struct ConfigArgs {
#[argp(positional, from_str_fn(native_path))]
#[argp(positional)]
/// input file
in_file: Utf8NativePathBuf,
#[argp(positional, from_str_fn(native_path))]
in_file: PathBuf,
#[argp(positional)]
/// output directory
out_dir: Utf8NativePathBuf,
out_dir: PathBuf,
}
#[derive(FromArgs, PartialEq, Eq, Debug)]
/// Builds function signatures from an ELF file.
#[argp(subcommand, name = "sigs")]
pub struct SignaturesArgs {
#[argp(positional, from_str_fn(native_path))]
#[argp(positional)]
/// input file(s)
files: Vec<Utf8NativePathBuf>,
files: Vec<PathBuf>,
#[argp(option, short = 's')]
/// symbol name
symbol: String,
#[argp(option, short = 'o', from_str_fn(native_path))]
#[argp(option, short = 'o')]
/// output yml
out_file: Utf8NativePathBuf,
out_file: PathBuf,
}
#[derive(FromArgs, PartialEq, Eq, Debug)]
/// Prints information about an ELF file.
#[argp(subcommand, name = "info")]
pub struct InfoArgs {
#[argp(positional, from_str_fn(native_path))]
#[argp(positional)]
/// input file
input: Utf8NativePathBuf,
input: PathBuf,
}
pub fn run(args: Args) -> Result<()> {
@ -135,17 +134,17 @@ pub fn run(args: Args) -> Result<()> {
}
fn config(args: ConfigArgs) -> Result<()> {
log::info!("Loading {}", args.in_file);
log::info!("Loading {}", args.in_file.display());
let obj = process_elf(&args.in_file)?;
DirBuilder::new().recursive(true).create(&args.out_dir)?;
write_symbols_file(&args.out_dir.join("symbols.txt"), &obj, None)?;
write_splits_file(&args.out_dir.join("splits.txt"), &obj, false, None)?;
write_symbols_file(args.out_dir.join("symbols.txt"), &obj, None)?;
write_splits_file(args.out_dir.join("splits.txt"), &obj, false, None)?;
Ok(())
}
fn disasm(args: DisasmArgs) -> Result<()> {
log::info!("Loading {}", args.elf_file);
log::info!("Loading {}", args.elf_file.display());
let obj = process_elf(&args.elf_file)?;
match obj.kind {
ObjKind::Executable => {
@ -157,12 +156,12 @@ fn disasm(args: DisasmArgs) -> Result<()> {
DirBuilder::new().recursive(true).create(&include_dir)?;
fs::write(include_dir.join("macros.inc"), include_bytes!("../../assets/macros.inc"))?;
let mut files_out = buf_writer(&args.out.join("link_order.txt"))?;
let mut files_out = buf_writer(args.out.join("link_order.txt"))?;
for (unit, split_obj) in obj.link_order.iter().zip(&split_objs) {
let out_path = asm_dir.join(file_name_from_unit(&unit.name, ".s"));
log::info!("Writing {}", out_path);
log::info!("Writing {}", out_path.display());
let mut w = buf_writer(&out_path)?;
let mut w = buf_writer(out_path)?;
write_asm(&mut w, split_obj)?;
w.flush()?;
@ -171,7 +170,7 @@ fn disasm(args: DisasmArgs) -> Result<()> {
files_out.flush()?;
}
ObjKind::Relocatable => {
let mut w = buf_writer(&args.out)?;
let mut w = buf_writer(args.out)?;
write_asm(&mut w, &obj)?;
w.flush()?;
}
@ -194,17 +193,18 @@ fn split(args: SplitArgs) -> Result<()> {
};
}
let mut rsp_file = buf_writer(Utf8NativePath::new("rsp"))?;
let mut rsp_file = buf_writer("rsp")?;
for unit in &obj.link_order {
let object = file_map
.get(&unit.name)
.ok_or_else(|| anyhow!("Failed to find object file for unit '{}'", unit.name))?;
let out_path = args.out_dir.join(file_name_from_unit(&unit.name, ".o"));
writeln!(rsp_file, "{}", out_path)?;
writeln!(rsp_file, "{}", out_path.display())?;
if let Some(parent) = out_path.parent() {
DirBuilder::new().recursive(true).create(parent)?;
}
fs::write(&out_path, object).with_context(|| format!("Failed to write '{}'", out_path))?;
fs::write(&out_path, object)
.with_context(|| format!("Failed to write '{}'", out_path.display()))?;
}
rsp_file.flush()?;
Ok(())
@ -237,7 +237,7 @@ const ASM_SUFFIX: &str = " (asm)";
fn fixup(args: FixupArgs) -> Result<()> {
let in_buf = fs::read(&args.in_file)
.with_context(|| format!("Failed to open input file: '{}'", args.in_file))?;
.with_context(|| format!("Failed to open input file: '{}'", args.in_file.display()))?;
let in_file = object::read::File::parse(&*in_buf).context("Failed to parse input ELF")?;
let mut out_file =
object::write::Object::new(in_file.format(), in_file.architecture(), in_file.endianness());
@ -262,7 +262,10 @@ fn fixup(args: FixupArgs) -> Result<()> {
let file_name = args
.in_file
.file_name()
.ok_or_else(|| anyhow!("'{}' is not a file path", args.in_file))?;
.ok_or_else(|| anyhow!("'{}' is not a file path", args.in_file.display()))?;
let file_name = file_name
.to_str()
.ok_or_else(|| anyhow!("'{}' is not valid UTF-8", file_name.to_string_lossy()))?;
let mut name_bytes = file_name.as_bytes().to_vec();
name_bytes.append(&mut ASM_SUFFIX.as_bytes().to_vec());
out_file.add_symbol(object::write::Symbol {
@ -278,7 +281,7 @@ fn fixup(args: FixupArgs) -> Result<()> {
}
// Write section symbols & sections
let mut section_ids: Vec<Option<SectionId>> = vec![None /* ELF null section */];
let mut section_ids: Vec<Option<SectionId>> = vec![];
for section in in_file.sections() {
// Skip empty sections or metadata sections
if section.size() == 0 || section.kind() == SectionKind::Metadata {
@ -301,11 +304,11 @@ fn fixup(args: FixupArgs) -> Result<()> {
}
// Write symbols
let mut symbol_ids: Vec<Option<SymbolId>> = vec![None /* ELF null symbol */];
let mut symbol_ids: Vec<Option<SymbolId>> = vec![];
let mut addr_to_sym: BTreeMap<SectionId, BTreeMap<u32, SymbolId>> = BTreeMap::new();
for symbol in in_file.symbols() {
// Skip section and file symbols, we wrote them above
if matches!(symbol.kind(), SymbolKind::Section | SymbolKind::File) {
if matches!(symbol.kind(), SymbolKind::Section | SymbolKind::File | SymbolKind::Null) {
symbol_ids.push(None);
continue;
}
@ -442,7 +445,7 @@ fn signatures(args: SignaturesArgs) -> Result<()> {
let mut signatures: HashMap<String, FunctionSignature> = HashMap::new();
for path in files {
log::info!("Processing {}", path);
log::info!("Processing {}", path.display());
let signature = match generate_signature(&path, &args.symbol) {
Ok(Some(signature)) => signature,
Ok(None) => continue,
@ -469,7 +472,7 @@ fn signatures(args: SignaturesArgs) -> Result<()> {
fn info(args: InfoArgs) -> Result<()> {
let in_buf = fs::read(&args.input)
.with_context(|| format!("Failed to open input file: '{}'", args.input))?;
.with_context(|| format!("Failed to open input file: '{}'", args.input.display()))?;
let in_file = object::read::File::parse(&*in_buf).context("Failed to parse input ELF")?;
println!("ELF type: {:?}", in_file.kind());
@ -485,7 +488,7 @@ fn info(args: InfoArgs) -> Result<()> {
"{: >15} | {: <10} | {: <10} | {: <10} | {: <10}",
"Name", "Type", "Size", "File Off", "Index"
);
for section in in_file.sections() {
for section in in_file.sections().skip(1) {
let kind_str = match section.kind() {
SectionKind::Text => "code".to_cow(),
SectionKind::Data => "data".to_cow(),
@ -562,7 +565,6 @@ fn info(args: InfoArgs) -> Result<()> {
);
println!("\tUnsafe global reg vars: {}", header.unsafe_global_reg_vars);
println!("\n{: >10} | {: <6} | {: <6} | {: <10}", "Align", "Vis", "Active", "Symbol");
CommentSym::from_reader(&mut reader, Endian::Big)?; // ELF null symbol
for symbol in in_file.symbols() {
let comment_sym = CommentSym::from_reader(&mut reader, Endian::Big)?;
if symbol.is_definition() {
@ -601,7 +603,7 @@ fn info(args: InfoArgs) -> Result<()> {
if let Some(virtual_addresses) = &meta.virtual_addresses {
println!("\tVirtual addresses:");
println!("\t{: >10} | {: <10}", "Addr", "Symbol");
for (symbol, addr) in in_file.symbols().zip(virtual_addresses.iter().skip(1)) {
for (symbol, addr) in in_file.symbols().zip(virtual_addresses) {
if symbol.is_definition() {
println!("\t{: >10} | {: <10}", format!("{:#X}", addr), symbol.name()?);
}

View File

@ -1,28 +1,24 @@
use std::io::{Seek, SeekFrom, Write};
use std::{
io::{Seek, SeekFrom, Write},
path::PathBuf,
};
use anyhow::{anyhow, bail, ensure, Result};
use argp::FromArgs;
use object::{Architecture, Endianness, Object, ObjectKind, ObjectSection, SectionKind};
use typed_path::Utf8NativePathBuf;
use crate::{
util::{file::buf_writer, path::native_path},
vfs::open_file,
};
use crate::util::file::{buf_writer, map_file};
#[derive(FromArgs, PartialEq, Eq, Debug)]
/// Converts an ELF file to a DOL file.
#[argp(subcommand, name = "elf2dol")]
pub struct Args {
#[argp(positional, from_str_fn(native_path))]
#[argp(positional)]
/// path to input ELF
elf_file: Utf8NativePathBuf,
#[argp(positional, from_str_fn(native_path))]
elf_file: PathBuf,
#[argp(positional)]
/// path to output DOL
dol_file: Utf8NativePathBuf,
/// sections (by name) to ignore
#[argp(option, long = "ignore")]
deny_sections: Vec<String>,
dol_file: PathBuf,
}
#[derive(Debug, Clone, Default)]
@ -47,8 +43,8 @@ const MAX_TEXT_SECTIONS: usize = 7;
const MAX_DATA_SECTIONS: usize = 11;
pub fn run(args: Args) -> Result<()> {
let mut file = open_file(&args.elf_file, true)?;
let obj_file = object::read::File::parse(file.map()?)?;
let file = map_file(&args.elf_file)?;
let obj_file = object::read::File::parse(file.as_slice())?;
match obj_file.architecture() {
Architecture::PowerPc => {}
arch => bail!("Unexpected architecture: {arch:?}"),
@ -65,11 +61,9 @@ pub fn run(args: Args) -> Result<()> {
out.seek(SeekFrom::Start(offset as u64))?;
// Text sections
for section in obj_file.sections().filter(|s| {
section_kind(s) == SectionKind::Text
&& is_alloc(s.flags())
&& is_name_allowed(s, &args.deny_sections)
}) {
for section in
obj_file.sections().filter(|s| section_kind(s) == SectionKind::Text && is_alloc(s.flags()))
{
log::debug!("Processing text section '{}'", section.name().unwrap_or("[error]"));
let address = section.address() as u32;
let size = align32(section.size() as u32);
@ -85,11 +79,9 @@ pub fn run(args: Args) -> Result<()> {
}
// Data sections
for section in obj_file.sections().filter(|s| {
section_kind(s) == SectionKind::Data
&& is_alloc(s.flags())
&& is_name_allowed(s, &args.deny_sections)
}) {
for section in
obj_file.sections().filter(|s| section_kind(s) == SectionKind::Data && is_alloc(s.flags()))
{
log::debug!("Processing data section '{}'", section.name().unwrap_or("[error]"));
let address = section.address() as u32;
let size = align32(section.size() as u32);
@ -105,11 +97,10 @@ pub fn run(args: Args) -> Result<()> {
}
// BSS sections
for section in obj_file.sections().filter(|s| {
section_kind(s) == SectionKind::UninitializedData
&& is_alloc(s.flags())
&& is_name_allowed(s, &args.deny_sections)
}) {
for section in obj_file
.sections()
.filter(|s| section_kind(s) == SectionKind::UninitializedData && is_alloc(s.flags()))
{
let address = section.address() as u32;
let size = section.size() as u32;
if header.bss_address == 0 {
@ -193,8 +184,3 @@ fn section_kind(section: &object::Section) -> SectionKind {
fn is_alloc(flags: object::SectionFlags) -> bool {
matches!(flags, object::SectionFlags::Elf { sh_flags } if sh_flags & object::elf::SHF_ALLOC as u64 != 0)
}
#[inline]
fn is_name_allowed(s: &object::Section, denied: &[String]) -> bool {
!denied.contains(&s.name().unwrap_or("[error]").to_string())
}

View File

@ -1,19 +1,12 @@
use std::fs::DirBuilder;
use std::path::PathBuf;
use anyhow::{bail, ensure, Result};
use argp::FromArgs;
use cwdemangle::{demangle, DemangleOptions};
use tracing::error;
use typed_path::Utf8NativePathBuf;
use crate::{
util::{
config::{write_splits_file, write_symbols_file},
map::{create_obj, process_map, SymbolEntry, SymbolRef},
path::native_path,
split::update_splits,
},
vfs::open_file,
use crate::util::{
file::map_file,
map::{process_map, SymbolEntry, SymbolRef},
};
#[derive(FromArgs, PartialEq, Debug)]
@ -29,16 +22,15 @@ pub struct Args {
enum SubCommand {
Entries(EntriesArgs),
Symbol(SymbolArgs),
Config(ConfigArgs),
}
#[derive(FromArgs, PartialEq, Eq, Debug)]
/// Displays all entries for a particular TU.
#[argp(subcommand, name = "entries")]
pub struct EntriesArgs {
#[argp(positional, from_str_fn(native_path))]
#[argp(positional)]
/// path to input map
map_file: Utf8NativePathBuf,
map_file: PathBuf,
#[argp(positional)]
/// TU to display entries for
unit: String,
@ -48,37 +40,24 @@ pub struct EntriesArgs {
/// Displays all references to a symbol.
#[argp(subcommand, name = "symbol")]
pub struct SymbolArgs {
#[argp(positional, from_str_fn(native_path))]
#[argp(positional)]
/// path to input map
map_file: Utf8NativePathBuf,
map_file: PathBuf,
#[argp(positional)]
/// symbol to display references for
symbol: String,
}
#[derive(FromArgs, PartialEq, Eq, Debug)]
/// Generates project configuration files from a map. (symbols.txt, splits.txt)
#[argp(subcommand, name = "config")]
pub struct ConfigArgs {
#[argp(positional, from_str_fn(native_path))]
/// path to input map
map_file: Utf8NativePathBuf,
#[argp(positional, from_str_fn(native_path))]
/// output directory for symbols.txt and splits.txt
out_dir: Utf8NativePathBuf,
}
pub fn run(args: Args) -> Result<()> {
match args.command {
SubCommand::Entries(c_args) => entries(c_args),
SubCommand::Symbol(c_args) => symbol(c_args),
SubCommand::Config(c_args) => config(c_args),
}
}
fn entries(args: EntriesArgs) -> Result<()> {
let mut file = open_file(&args.map_file, true)?;
let entries = process_map(file.as_mut(), None, None)?;
let file = map_file(&args.map_file)?;
let entries = process_map(&mut file.as_reader(), None, None)?;
match entries.unit_entries.get_vec(&args.unit) {
Some(vec) => {
println!("Entries for {}:", args.unit);
@ -108,9 +87,9 @@ fn entries(args: EntriesArgs) -> Result<()> {
}
fn symbol(args: SymbolArgs) -> Result<()> {
let mut file = open_file(&args.map_file, true)?;
let file = map_file(&args.map_file)?;
log::info!("Processing map...");
let entries = process_map(file.as_mut(), None, None)?;
let entries = process_map(&mut file.as_reader(), None, None)?;
log::info!("Done!");
let mut opt_ref: Option<(String, SymbolEntry)> = None;
@ -181,18 +160,3 @@ fn symbol(args: SymbolArgs) -> Result<()> {
println!("\n");
Ok(())
}
fn config(args: ConfigArgs) -> Result<()> {
let mut file = open_file(&args.map_file, true)?;
log::info!("Processing map...");
let entries = process_map(file.as_mut(), None, None)?;
let mut obj = create_obj(&entries)?;
if let Err(e) = update_splits(&mut obj, None, false) {
error!("Failed to update splits: {}", e)
}
DirBuilder::new().recursive(true).create(&args.out_dir)?;
write_symbols_file(&args.out_dir.join("symbols.txt"), &obj, None)?;
write_splits_file(&args.out_dir.join("splits.txt"), &obj, false, None)?;
log::info!("Done!");
Ok(())
}

View File

@ -0,0 +1,48 @@
use std::path::PathBuf;
use anyhow::{bail, ensure, Context, Result};
use argp::FromArgs;
use memchr::memmem;
use memmap2::MmapOptions;
#[derive(FromArgs, PartialEq, Eq, Debug)]
/// Sets the MetroidBuildInfo tag value in a given binary.
#[argp(subcommand, name = "metroidbuildinfo")]
pub struct Args {
#[argp(positional)]
/// path to source binary
binary: PathBuf,
#[argp(positional)]
/// path to 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.display())
})?;
let build_string_trim = build_string.trim_end();
let build_string_bytes = build_string_trim.as_bytes();
ensure!(
build_string_bytes.len() <= BUILD_STRING_MAX,
"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.display())
})?;
let mut map = unsafe { MmapOptions::new().map_mut(&binary_file) }
.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"),
};
let end = start + build_string_bytes.len();
map[start..end].copy_from_slice(build_string_bytes);
map[end] = 0;
Ok(())
}

View File

@ -7,12 +7,12 @@ pub mod dwarf;
pub mod elf;
pub mod elf2dol;
pub mod map;
pub mod metroidbuildinfo;
pub mod nlzss;
pub mod rarc;
pub mod rel;
pub mod rso;
pub mod shasum;
pub mod u8_arc;
pub mod vfs;
pub mod yay0;
pub mod yaz0;

View File

@ -1,12 +1,11 @@
use std::fs;
use std::{fs, path::PathBuf};
use anyhow::{anyhow, Context, Result};
use argp::FromArgs;
use typed_path::Utf8NativePathBuf;
use crate::{
util::{file::process_rsp, nlzss, path::native_path, IntoCow, ToCow},
vfs::open_file,
use crate::util::{
file::{open_file, process_rsp},
IntoCow, ToCow,
};
#[derive(FromArgs, PartialEq, Debug)]
@ -27,13 +26,13 @@ enum SubCommand {
/// Decompresses NLZSS-compressed files.
#[argp(subcommand, name = "decompress")]
pub struct DecompressArgs {
#[argp(positional, from_str_fn(native_path))]
#[argp(positional)]
/// NLZSS-compressed file(s)
files: Vec<Utf8NativePathBuf>,
#[argp(option, short = 'o', from_str_fn(native_path))]
files: Vec<PathBuf>,
#[argp(option, short = 'o')]
/// Output file (or directory, if multiple files are specified).
/// If not specified, decompresses in-place.
output: Option<Utf8NativePathBuf>,
output: Option<PathBuf>,
}
pub fn run(args: Args) -> Result<()> {
@ -46,9 +45,8 @@ fn decompress(args: DecompressArgs) -> Result<()> {
let files = process_rsp(&args.files)?;
let single_file = files.len() == 1;
for path in files {
let mut file = open_file(&path, false)?;
let data = nlzss::decompress(file.as_mut())
.map_err(|e| anyhow!("Failed to decompress '{}' with NLZSS: {}", path, e))?;
let data = nintendo_lz::decompress(&mut open_file(&path)?)
.map_err(|e| anyhow!("Failed to decompress '{}' with NLZSS: {}", path.display(), e))?;
let out_path = if let Some(output) = &args.output {
if single_file {
output.as_path().to_cow()
@ -59,7 +57,7 @@ fn decompress(args: DecompressArgs) -> Result<()> {
path.as_path().to_cow()
};
fs::write(out_path.as_ref(), data)
.with_context(|| format!("Failed to write '{}'", out_path))?;
.with_context(|| format!("Failed to write '{}'", out_path.display()))?;
}
Ok(())
}

View File

@ -1,9 +1,12 @@
use anyhow::Result;
use argp::FromArgs;
use typed_path::Utf8NativePathBuf;
use std::{fs, fs::DirBuilder, path::PathBuf};
use super::vfs;
use crate::util::path::native_path;
use anyhow::{Context, Result};
use argp::FromArgs;
use crate::util::{
file::{decompress_if_needed, map_file},
rarc::{Node, RarcReader},
};
#[derive(FromArgs, PartialEq, Debug)]
/// Commands for processing RSO files.
@ -24,29 +27,23 @@ enum SubCommand {
/// Views RARC file information.
#[argp(subcommand, name = "list")]
pub struct ListArgs {
#[argp(positional, from_str_fn(native_path))]
#[argp(positional)]
/// RARC file
file: Utf8NativePathBuf,
#[argp(switch, short = 's')]
/// Only print filenames.
short: bool,
file: PathBuf,
}
#[derive(FromArgs, PartialEq, Eq, Debug)]
/// Extracts RARC file contents.
#[argp(subcommand, name = "extract")]
pub struct ExtractArgs {
#[argp(positional, from_str_fn(native_path))]
#[argp(positional)]
/// RARC file
file: Utf8NativePathBuf,
#[argp(option, short = 'o', from_str_fn(native_path))]
file: PathBuf,
#[argp(option, short = 'o')]
/// output directory
output: Option<Utf8NativePathBuf>,
#[argp(switch)]
/// Do not decompress files when copying.
no_decompress: bool,
output: Option<PathBuf>,
#[argp(switch, short = 'q')]
/// Quiet output. Don't print anything except errors.
/// quiet output
quiet: bool,
}
@ -58,16 +55,71 @@ pub fn run(args: Args) -> Result<()> {
}
fn list(args: ListArgs) -> Result<()> {
let path = Utf8NativePathBuf::from(format!("{}:", args.file));
vfs::ls(vfs::LsArgs { path, short: args.short, recursive: true })
let file = map_file(&args.file)?;
let rarc = RarcReader::new(&mut file.as_reader())
.with_context(|| format!("Failed to process RARC file '{}'", args.file.display()))?;
let mut current_path = PathBuf::new();
for node in rarc.nodes() {
match node {
Node::DirectoryBegin { name } => {
current_path.push(name.name);
}
Node::DirectoryEnd { name: _ } => {
current_path.pop();
}
Node::File { name, offset, size } => {
let path = current_path.join(name.name);
println!("{}: {} bytes, offset {:#X}", path.display(), size, offset);
}
Node::CurrentDirectory => {}
Node::ParentDirectory => {}
}
}
Ok(())
}
fn extract(args: ExtractArgs) -> Result<()> {
let path = Utf8NativePathBuf::from(format!("{}:", args.file));
let output = args.output.unwrap_or_else(|| Utf8NativePathBuf::from("."));
vfs::cp(vfs::CpArgs {
paths: vec![path, output],
no_decompress: args.no_decompress,
quiet: args.quiet,
})
let file = map_file(&args.file)?;
let rarc = RarcReader::new(&mut file.as_reader())
.with_context(|| format!("Failed to process RARC file '{}'", args.file.display()))?;
let mut current_path = PathBuf::new();
for node in rarc.nodes() {
match node {
Node::DirectoryBegin { name } => {
current_path.push(name.name);
}
Node::DirectoryEnd { name: _ } => {
current_path.pop();
}
Node::File { name, offset, size } => {
let file_data = decompress_if_needed(
&file.as_slice()[offset as usize..offset as usize + size as usize],
)?;
let file_path = current_path.join(&name.name);
let output_path = args
.output
.as_ref()
.map(|p| p.join(&file_path))
.unwrap_or_else(|| file_path.clone());
if !args.quiet {
println!(
"Extracting {} to {} ({} bytes)",
file_path.display(),
output_path.display(),
size
);
}
if let Some(parent) = output_path.parent() {
DirBuilder::new().recursive(true).create(parent)?;
}
fs::write(&output_path, file_data)
.with_context(|| format!("Failed to write file '{}'", output_path.display()))?;
}
Node::CurrentDirectory => {}
Node::ParentDirectory => {}
}
}
Ok(())
}

View File

@ -1,7 +1,8 @@
use std::{
collections::{btree_map, BTreeMap},
fs,
io::{Cursor, Write},
io::Write,
path::PathBuf,
time::Instant,
};
@ -14,7 +15,6 @@ use object::{
use rayon::prelude::*;
use rustc_hash::FxHashMap;
use tracing::{info, info_span};
use typed_path::Utf8NativePathBuf;
use crate::{
analysis::{
@ -27,25 +27,20 @@ use crate::{
tracker::Tracker,
},
array_ref_mut,
cmd::dol::{find_object_base, ModuleConfig, ObjectBase, ProjectConfig},
obj::{
ObjInfo, ObjReloc, ObjRelocKind, ObjSection, ObjSectionKind, ObjSymbol,
SectionIndex as ObjSectionIndex,
},
cmd::dol::{ModuleConfig, ProjectConfig},
obj::{ObjInfo, ObjReloc, ObjRelocKind, ObjSection, ObjSectionKind, ObjSymbol},
util::{
config::{is_auto_symbol, read_splits_sections, SectionDef},
dol::process_dol,
elf::{to_obj_reloc_kind, write_elf},
file::{buf_writer, process_rsp, verify_hash, FileIterator},
file::{buf_reader, buf_writer, map_file, process_rsp, verify_hash, FileIterator},
nested::NestedMap,
path::native_path,
rel::{
print_relocations, process_rel, process_rel_header, process_rel_sections, write_rel,
RelHeader, RelReloc, RelSectionHeader, RelWriteInfo, PERMITTED_SECTIONS,
},
IntoCow, ToCow,
},
vfs::open_file,
};
#[derive(FromArgs, PartialEq, Debug)]
@ -68,9 +63,9 @@ enum SubCommand {
/// Views REL file information.
#[argp(subcommand, name = "info")]
pub struct InfoArgs {
#[argp(positional, from_str_fn(native_path))]
#[argp(positional)]
/// REL file
rel_file: Utf8NativePathBuf,
rel_file: PathBuf,
#[argp(switch, short = 'r')]
/// print relocations
relocations: bool,
@ -80,27 +75,27 @@ pub struct InfoArgs {
/// Merges a DOL + REL(s) into an ELF.
#[argp(subcommand, name = "merge")]
pub struct MergeArgs {
#[argp(positional, from_str_fn(native_path))]
#[argp(positional)]
/// DOL file
dol_file: Utf8NativePathBuf,
#[argp(positional, from_str_fn(native_path))]
dol_file: PathBuf,
#[argp(positional)]
/// REL file(s)
rel_files: Vec<Utf8NativePathBuf>,
#[argp(option, short = 'o', from_str_fn(native_path))]
rel_files: Vec<PathBuf>,
#[argp(option, short = 'o')]
/// output ELF
out_file: Utf8NativePathBuf,
out_file: PathBuf,
}
#[derive(FromArgs, PartialEq, Eq, Debug)]
/// Creates RELs from an ELF + PLF(s).
#[argp(subcommand, name = "make")]
pub struct MakeArgs {
#[argp(positional, from_str_fn(native_path))]
#[argp(positional)]
/// input file(s)
files: Vec<Utf8NativePathBuf>,
#[argp(option, short = 'c', from_str_fn(native_path))]
files: Vec<PathBuf>,
#[argp(option, short = 'c')]
/// (optional) project configuration file
config: Option<Utf8NativePathBuf>,
config: Option<PathBuf>,
#[argp(option, short = 'n')]
/// (optional) module names
names: Vec<String>,
@ -167,17 +162,16 @@ fn match_section_index(
// })
}
fn load_rel(module_config: &ModuleConfig, object_base: &ObjectBase) -> Result<RelInfo> {
let mut file = object_base.open(&module_config.object)?;
let data = file.map()?;
fn load_rel(module_config: &ModuleConfig) -> Result<RelInfo> {
let file = map_file(&module_config.object)?;
if let Some(hash_str) = &module_config.hash {
verify_hash(data, hash_str)?;
verify_hash(file.as_slice(), hash_str)?;
}
let mut reader = Cursor::new(data);
let mut reader = file.as_reader();
let header = process_rel_header(&mut reader)?;
let sections = process_rel_sections(&mut reader, &header)?;
let section_defs = if let Some(splits_path) = &module_config.splits {
read_splits_sections(&splits_path.with_encoding())?
read_splits_sections(splits_path)?
} else {
None
};
@ -187,7 +181,7 @@ fn load_rel(module_config: &ModuleConfig, object_base: &ObjectBase) -> Result<Re
struct LoadedModule<'a> {
module_id: u32,
file: File<'a>,
path: Utf8NativePathBuf,
path: PathBuf,
}
fn resolve_relocations(
@ -267,19 +261,15 @@ fn make(args: MakeArgs) -> Result<()> {
let mut existing_headers = BTreeMap::<u32, RelInfo>::new();
let mut name_to_module_id = FxHashMap::<String, u32>::default();
if let Some(config_path) = &args.config {
let config: ProjectConfig = {
let mut file = open_file(config_path, true)?;
serde_yaml::from_reader(file.as_mut())?
};
let object_base = find_object_base(&config)?;
let config: ProjectConfig = serde_yaml::from_reader(&mut buf_reader(config_path)?)?;
for module_config in &config.modules {
let module_name = module_config.name();
if !args.names.is_empty() && !args.names.iter().any(|n| n == module_name) {
if !args.names.is_empty() && !args.names.iter().any(|n| n == &module_name) {
continue;
}
let _span = info_span!("module", name = %module_name).entered();
let info = load_rel(module_config, &object_base).with_context(|| {
format!("While loading REL '{}'", object_base.join(&module_config.object))
let info = load_rel(module_config).with_context(|| {
format!("While loading REL '{}'", module_config.object.display())
})?;
name_to_module_id.insert(module_name.to_string(), info.0.module_id);
match existing_headers.entry(info.0.module_id) {
@ -297,9 +287,9 @@ fn make(args: MakeArgs) -> Result<()> {
}
// Load all modules
let mut files = paths.iter().map(|p| open_file(p, true)).collect::<Result<Vec<_>>>()?;
let files = paths.iter().map(map_file).collect::<Result<Vec<_>>>()?;
let modules = files
.par_iter_mut()
.par_iter()
.enumerate()
.zip(&paths)
.map(|((idx, file), path)| {
@ -311,9 +301,9 @@ fn make(args: MakeArgs) -> Result<()> {
.and_then(|n| name_to_module_id.get(n))
.copied()
.unwrap_or(idx as u32);
load_obj(file.map()?)
load_obj(file.as_slice())
.map(|o| LoadedModule { module_id, file: o, path: path.clone() })
.with_context(|| format!("Failed to load '{}'", path))
.with_context(|| format!("Failed to load '{}'", path.display()))
})
.collect::<Result<Vec<_>>>()?;
@ -321,7 +311,7 @@ fn make(args: MakeArgs) -> Result<()> {
let start = Instant::now();
let mut symbol_map = FxHashMap::<&[u8], (u32, SymbolIndex)>::default();
for module_info in modules.iter() {
let _span = info_span!("file", path = %module_info.path).entered();
let _span = info_span!("file", path = %module_info.path.display()).entered();
for symbol in module_info.file.symbols() {
if symbol.scope() == object::SymbolScope::Dynamic {
symbol_map
@ -336,7 +326,7 @@ fn make(args: MakeArgs) -> Result<()> {
let mut relocations = Vec::<Vec<RelReloc>>::with_capacity(modules.len() - 1);
relocations.resize_with(modules.len() - 1, Vec::new);
for (module_info, relocations) in modules.iter().skip(1).zip(&mut relocations) {
let _span = info_span!("file", path = %module_info.path).entered();
let _span = info_span!("file", path = %module_info.path.display()).entered();
resolved += resolve_relocations(
&module_info.file,
&existing_headers,
@ -345,7 +335,9 @@ fn make(args: MakeArgs) -> Result<()> {
&modules,
relocations,
)
.with_context(|| format!("While resolving relocations in '{}'", module_info.path))?;
.with_context(|| {
format!("While resolving relocations in '{}'", module_info.path.display())
})?;
}
if !args.quiet {
@ -361,7 +353,7 @@ fn make(args: MakeArgs) -> Result<()> {
// Write RELs
let start = Instant::now();
for (module_info, relocations) in modules.iter().skip(1).zip(relocations) {
let _span = info_span!("file", path = %module_info.path).entered();
let _span = info_span!("file", path = %module_info.path.display()).entered();
let mut info = RelWriteInfo {
module_id: module_info.module_id,
version: 3,
@ -392,7 +384,7 @@ fn make(args: MakeArgs) -> Result<()> {
let rel_path = module_info.path.with_extension("rel");
let mut w = buf_writer(&rel_path)?;
write_rel(&mut w, &info, &module_info.file, relocations)
.with_context(|| format!("Failed to write '{}'", rel_path))?;
.with_context(|| format!("Failed to write '{}'", rel_path.display()))?;
w.flush()?;
}
@ -407,8 +399,8 @@ fn make(args: MakeArgs) -> Result<()> {
}
fn info(args: InfoArgs) -> Result<()> {
let mut file = open_file(&args.rel_file, true)?;
let (header, mut module_obj) = process_rel(file.as_mut(), "")?;
let file = map_file(args.rel_file)?;
let (header, mut module_obj) = process_rel(&mut file.as_reader(), "")?;
let mut state = AnalyzerState::default();
state.detect_functions(&module_obj)?;
@ -466,7 +458,7 @@ fn info(args: InfoArgs) -> Result<()> {
if args.relocations {
println!("\nRelocations:");
println!(" [Source] section:address RelocType -> [Target] module:section:address");
print_relocations(file.as_mut(), &header)?;
print_relocations(&mut file.as_reader(), &header)?;
}
Ok(())
}
@ -475,11 +467,11 @@ fn info(args: InfoArgs) -> Result<()> {
const fn align32(x: u32) -> u32 { (x + 31) & !31 }
fn merge(args: MergeArgs) -> Result<()> {
log::info!("Loading {}", args.dol_file);
log::info!("Loading {}", args.dol_file.display());
let mut obj = {
let mut file = open_file(&args.dol_file, true)?;
let name = args.dol_file.file_stem().unwrap_or_default();
process_dol(file.map()?, name)?
let file = map_file(&args.dol_file)?;
let name = args.dol_file.file_stem().map(|s| s.to_string_lossy()).unwrap_or_default();
process_dol(file.as_slice(), name.as_ref())?
};
log::info!("Performing signature analysis");
@ -489,10 +481,10 @@ fn merge(args: MergeArgs) -> Result<()> {
let mut processed = 0;
let mut module_map = BTreeMap::<u32, ObjInfo>::new();
for result in FileIterator::new(&args.rel_files)? {
let (path, mut entry) = result?;
log::info!("Loading {}", path);
let name = path.file_stem().unwrap_or_default();
let (_, obj) = process_rel(&mut entry, name)?;
let (path, entry) = result?;
log::info!("Loading {}", path.display());
let name = path.file_stem().map(|s| s.to_string_lossy()).unwrap_or_default();
let (_, obj) = process_rel(&mut entry.as_reader(), name.as_ref())?;
match module_map.entry(obj.module_id) {
btree_map::Entry::Vacant(e) => e.insert(obj),
btree_map::Entry::Occupied(_) => bail!("Duplicate module ID {}", obj.module_id),
@ -501,7 +493,7 @@ fn merge(args: MergeArgs) -> Result<()> {
}
log::info!("Merging {} REL(s)", processed);
let mut section_map: BTreeMap<u32, BTreeMap<ObjSectionIndex, u32>> = BTreeMap::new();
let mut section_map: BTreeMap<u32, BTreeMap<u32, u32>> = BTreeMap::new();
let mut offset = align32(arena_lo + 0x2000);
for module in module_map.values() {
for (mod_section_index, mod_section) in module.sections.iter() {
@ -520,7 +512,7 @@ fn merge(args: MergeArgs) -> Result<()> {
section_known: mod_section.section_known,
splits: mod_section.splits.clone(),
});
section_map.nested_insert(module.module_id, mod_section.elf_index, offset)?;
section_map.nested_insert(module.module_id, mod_section.elf_index as u32, offset)?;
for (_, mod_symbol) in module.symbols.for_section(mod_section_index) {
obj.symbols.add_direct(ObjSymbol {
name: mod_symbol.name.clone(),
@ -544,8 +536,7 @@ fn merge(args: MergeArgs) -> Result<()> {
log::info!("Applying REL relocations");
for module in module_map.values() {
for rel_reloc in &module.unresolved_relocations {
let source_addr = (section_map[&module.module_id]
[&(rel_reloc.section as ObjSectionIndex)]
let source_addr = (section_map[&module.module_id][&(rel_reloc.section as u32)]
+ rel_reloc.address)
& !3;
let target_addr = if rel_reloc.module_id == 0 {
@ -609,7 +600,7 @@ fn merge(args: MergeArgs) -> Result<()> {
tracker.apply(&mut obj, false)?;
// Write ELF
log::info!("Writing {}", args.out_file);
log::info!("Writing {}", args.out_file.display());
fs::write(&args.out_file, write_elf(&obj, false)?)?;
Ok(())
}

View File

@ -1,26 +1,9 @@
use std::io::{BufRead, Seek, Write};
use std::path::PathBuf;
use anyhow::{bail, ensure, Context, Result};
use anyhow::Result;
use argp::FromArgs;
use object::{
elf::{R_PPC_NONE, R_PPC_REL24},
Architecture, Endianness, Object, ObjectKind, ObjectSection, ObjectSymbol, SectionKind,
SymbolIndex, SymbolKind, SymbolSection,
};
use typed_path::{Utf8NativePath, Utf8NativePathBuf};
use crate::{
util::{
file::buf_writer,
path::native_path,
reader::{Endian, ToWriter},
rso::{
process_rso, symbol_hash, RsoHeader, RsoRelocation, RsoSectionHeader, RsoSymbol,
RSO_SECTION_NAMES,
},
},
vfs::open_file,
};
use crate::util::{file::map_file, rso::process_rso};
#[derive(FromArgs, PartialEq, Debug)]
/// Commands for processing RSO files.
@ -34,486 +17,30 @@ pub struct Args {
#[argp(subcommand)]
enum SubCommand {
Info(InfoArgs),
Make(MakeArgs),
}
#[derive(FromArgs, PartialEq, Eq, Debug)]
/// Views RSO file information.
#[argp(subcommand, name = "info")]
pub struct InfoArgs {
#[argp(positional, from_str_fn(native_path))]
#[argp(positional)]
/// RSO file
rso_file: Utf8NativePathBuf,
}
#[derive(FromArgs, PartialEq, Eq, Debug)]
/// Creates an RSO from an ELF.
#[argp(subcommand, name = "make")]
pub struct MakeArgs {
#[argp(positional, arg_name = "ELF File", from_str_fn(native_path))]
/// elf file
input: Utf8NativePathBuf,
#[argp(option, short = 'o', arg_name = "File", from_str_fn(native_path))]
/// output file path
output: Utf8NativePathBuf,
#[argp(option, short = 'm', arg_name = "Name")]
/// module name (or path). Default: input name
module_name: Option<String>,
#[argp(option, short = 'e', arg_name = "File", from_str_fn(native_path))]
/// file containing exported symbol names (newline separated)
export: Option<Utf8NativePathBuf>,
rso_file: PathBuf,
}
pub fn run(args: Args) -> Result<()> {
match args.command {
SubCommand::Info(c_args) => info(c_args),
SubCommand::Make(c_args) => make(c_args),
}
}
fn info(args: InfoArgs) -> Result<()> {
let rso = {
let mut file = open_file(&args.rso_file, true)?;
process_rso(file.as_mut())?
let file = map_file(args.rso_file)?;
let obj = process_rso(&mut file.as_reader())?;
#[allow(clippy::let_and_return)]
obj
};
println!("Read RSO module {}", rso.name);
Ok(())
}
fn make(args: MakeArgs) -> Result<()> {
let mut file = open_file(&args.input, true)?;
let obj_file = object::read::File::parse(file.map()?)?;
match obj_file.architecture() {
Architecture::PowerPc => {}
arch => bail!("Unexpected architecture: {arch:?}"),
};
ensure!(obj_file.endianness() == Endianness::Big, "Expected big endian");
let module_name = match args.module_name {
Some(n) => n,
None => args.input.to_string(),
};
let symbols_to_export = match &args.export {
Some(export_file_path) => {
let export_file_reader = open_file(export_file_path, true)?;
export_file_reader.lines().map_while(Result::ok).collect()
}
None => vec![],
};
match obj_file.kind() {
ObjectKind::Executable => {
make_sel(obj_file, &args.output, &module_name, symbols_to_export)?
}
ObjectKind::Relocatable => {
make_rso(obj_file, &args.output, &module_name, symbols_to_export)?
}
kind => bail!("Unexpected ELF type: {kind:?}"),
}
Ok(())
}
fn make_sel(
_file: object::File,
_output: &Utf8NativePath,
_module_name: &str,
_symbols_to_export: Vec<String>,
) -> Result<()> {
bail!("Creating SEL files is not supported yet.");
}
fn make_rso(
file: object::File,
output: &Utf8NativePath,
module_name: &str,
symbols_to_export: Vec<String>,
) -> Result<()> {
let mut out = buf_writer(output)?;
let try_populate_symbol_index_and_offset =
|name: &str, index: &mut u8, offset: &mut u32| -> Result<()> {
let Some(sym) = file.symbol_by_name(name) else {
return Ok(());
};
let si = sym
.section_index()
.with_context(|| format!("Failed to find symbol `{}` section index", name))?;
let addr = sym.address();
*index = si.0 as u8;
*offset = addr as u32;
Ok(())
};
let pad_to_alignment =
|out: &mut std::io::BufWriter<std::fs::File>, alignment: u64| -> Result<()> {
if alignment == 0 {
return Ok(());
}
const ZERO_BUF: [u8; 32] = [0u8; 32];
let pos = out.stream_position()?;
let mut count = (!(alignment - 1) & ((alignment + pos) - 1)) - pos;
while count > 0 {
let slice_size = std::cmp::min(ZERO_BUF.len(), count as usize);
out.write_all(&ZERO_BUF[0..slice_size])?;
count -= slice_size as u64;
}
Ok(())
};
let mut header = RsoHeader::new();
try_populate_symbol_index_and_offset(
"_prolog",
&mut header.prolog_section,
&mut header.prolog_offset,
)?;
try_populate_symbol_index_and_offset(
"_epilog",
&mut header.epilog_section,
&mut header.epilog_offset,
)?;
try_populate_symbol_index_and_offset(
"_unresolved",
&mut header.unresolved_section,
&mut header.unresolved_offset,
)?;
header.to_writer(&mut out, Endian::Big)?;
header.section_info_offset = out.stream_position()? as u32;
{
// Write Sections Info Table (Blank)
let blank_section = RsoSectionHeader::default();
// Include ELF null section
for _ in 0..file.sections().count() + 1 {
header.num_sections += 1;
blank_section.to_writer(&mut out, Endian::Big)?;
}
}
let mut rso_sections: Vec<RsoSectionHeader> =
vec![RsoSectionHeader::default() /* ELF null section */];
for section in file.sections() {
let is_valid_section =
section.name().is_ok_and(|n| RSO_SECTION_NAMES.iter().any(|&s| s == n));
let section_size = section.size();
if !is_valid_section || section_size == 0 {
rso_sections.push(RsoSectionHeader::default());
continue;
}
if section.kind() == SectionKind::UninitializedData {
header.bss_size += section_size as u32;
rso_sections.push(RsoSectionHeader { offset_and_flags: 0, size: section_size as u32 });
continue;
}
pad_to_alignment(&mut out, section.align())?;
let section_offset_in_file = out.stream_position()?;
let section_data = section.data()?;
out.write_all(section_data)?;
rso_sections.push(RsoSectionHeader {
offset_and_flags: section_offset_in_file as u32,
size: section_size as u32,
});
}
pad_to_alignment(&mut out, 4)?;
header.name_offset = out.stream_position()? as u32;
// Rewind and write the correct section info table
out.seek(std::io::SeekFrom::Start(header.section_info_offset as u64))?;
for section in &rso_sections {
section.to_writer(&mut out, Endian::Big)?;
}
// Write the module name
out.seek(std::io::SeekFrom::Start(header.name_offset as u64))?;
let module_name = module_name.as_bytes();
out.write_all(module_name)?;
header.name_size = module_name.len() as u32;
// Accumulate exported and imported symbol
let mut import_symbol_table: Vec<RsoSymbol> = vec![];
let mut export_symbol_table: Vec<RsoSymbol> = vec![];
for symbol in file.symbols() {
let sym_binding = match symbol.flags() {
object::SymbolFlags::Elf { st_info, st_other: _ } => st_info >> 4,
flag => bail!("Unknown symbol flag found `{:?}`", flag),
};
let symbol_name = match symbol.name() {
Ok(n) => {
if n.is_empty() {
continue;
}
n
}
Err(_) => continue,
};
// In the [`RsoSymbol::name_offset`] field we would store the symbol index temp
match symbol.section_index() {
Some(section_index)
if sym_binding != object::elf::STB_LOCAL && section_index.0 != 0 =>
{
// Symbol to export
if !symbols_to_export.iter().any(|s| s == symbol_name) {
continue;
}
let hash = symbol_hash(symbol_name);
export_symbol_table.push(RsoSymbol {
name_offset: symbol.index().0 as u32,
offset: symbol.address() as u32,
section_index: section_index.0 as u32,
hash: Some(hash),
});
}
None => {
if matches!(symbol.kind(), SymbolKind::File) {
continue;
}
if symbol.section() == SymbolSection::Absolute {
if !symbols_to_export.iter().any(|s| s == symbol_name) {
continue;
}
// Special Symbols
let hash = symbol_hash(symbol_name);
export_symbol_table.push(RsoSymbol {
name_offset: symbol.index().0 as u32,
offset: symbol.address() as u32,
section_index: 0xFFF1_u32,
hash: Some(hash),
});
continue;
}
// Symbol to import
import_symbol_table.push(RsoSymbol {
name_offset: symbol.index().0 as u32,
offset: symbol.address() as u32,
section_index: 0, // Relocation offset
hash: None,
});
}
_ => continue,
}
}
// Accumulate relocations
let mut imported_relocations: Vec<RsoRelocation> = vec![];
let mut exported_relocations: Vec<RsoRelocation> = vec![];
for section in file.sections() {
let is_valid_section =
section.name().is_ok_and(|n| RSO_SECTION_NAMES.iter().any(|&s| s == n));
if !is_valid_section {
continue;
}
let relocation_section_idx = section.index().0 as u32;
let relocation_section_offset =
rso_sections[relocation_section_idx as usize].offset_and_flags;
for (reloc_addr, reloc) in section.relocations() {
let reloc_target_symbol_idx = match reloc.target() {
object::RelocationTarget::Symbol(t) => t,
_ => continue,
};
let Ok(reloc_target_symbol) = file.symbol_by_index(reloc_target_symbol_idx) else {
bail!(
"Failed to find relocation `{:08X}` symbol ({})",
reloc_addr,
reloc_target_symbol_idx.0
);
};
let reloc_type = match reloc.flags() {
object::RelocationFlags::Elf { r_type } => r_type,
_ => continue,
};
if reloc_type == R_PPC_NONE {
continue;
}
match reloc_target_symbol.section_index() {
None => {
// Imported symbol relocation
// Get the symbol index inside the import symbol table
let symbol_table_idx = match import_symbol_table
.iter()
.position(|s| s.name_offset == reloc_target_symbol_idx.0 as u32)
{
Some(idx) => idx,
// We should always find the symbol. If not, it means a logic error in the symbol accumulator loop
// panic?
None => {
bail!("Failed to find imported symbol in the accumulated symbol table.")
}
};
let id_and_type = ((symbol_table_idx as u32) << 8) | (reloc_type & 0xFF);
imported_relocations.push(RsoRelocation {
// Convert the relocation offset from being section relative to file relative
offset: relocation_section_offset + reloc_addr as u32,
id_and_type,
target_offset: 0,
});
}
Some(reloc_symbol_section_idx) => {
// Exported symbol relocation
let id_and_type =
((reloc_symbol_section_idx.0 as u32) << 8) | (reloc_type & 0xFF);
exported_relocations.push(RsoRelocation {
// Convert the relocation offset from being section relative to file relative
offset: relocation_section_offset + reloc_addr as u32,
id_and_type,
target_offset: reloc.addend() as u32 + reloc_target_symbol.address() as u32,
});
}
}
// Apply relocation with the `_unresolved` as the symbol, if the module export the function
if reloc_type == R_PPC_REL24
&& header.unresolved_offset != 0
&& header.unresolved_section == relocation_section_idx as u8
{
let target_section = file
.section_by_index(object::SectionIndex(relocation_section_idx as usize))
.unwrap();
let target_section_data = target_section.data().unwrap();
// Copy instruction
let mut intruction_buff = [0u8; 4];
intruction_buff.copy_from_slice(
&target_section_data[(reloc_addr as usize)..(reloc_addr + 4) as usize],
);
let target_instruction = u32::from_be_bytes(intruction_buff);
let off_diff = header.unresolved_offset as i64 - reloc_addr as i64;
let replacement_instruction =
(off_diff as u32 & 0x3fffffcu32) | (target_instruction & 0xfc000003u32);
let intruction_buff = replacement_instruction.to_be_bytes();
let relocation_file_offset = relocation_section_offset as u64 + reloc_addr;
let current_stream_pos = out.stream_position()?;
out.seek(std::io::SeekFrom::Start(relocation_file_offset))?;
out.write_all(&intruction_buff)?;
out.seek(std::io::SeekFrom::Start(current_stream_pos))?;
}
}
}
// Sort imported relocation, by symbol index
imported_relocations.sort_by(|lhs, rhs| {
let lhs_symbol_idx = lhs.id();
let rhs_symbol_idx = rhs.id();
rhs_symbol_idx.cmp(&lhs_symbol_idx)
});
// Sort Export Symbol by Hash
export_symbol_table.sort_by(|lhs, rhs| rhs.hash.unwrap().cmp(&lhs.hash.unwrap()));
{
// Write Export Symbol Table
pad_to_alignment(&mut out, 4)?;
header.export_table_offset = out.stream_position()? as u32;
header.export_table_size = (export_symbol_table.len() * 16) as u32;
let mut export_symbol_name_table: Vec<u8> = vec![];
for export_symbol in &mut export_symbol_table {
let export_elf_symbol =
file.symbol_by_index(SymbolIndex(export_symbol.name_offset as usize)).unwrap();
let export_elf_symbol_name = export_elf_symbol.name().unwrap();
export_symbol.name_offset = export_symbol_name_table.len() as u32;
export_symbol.to_writer(&mut out, Endian::Big)?;
export_symbol_name_table.extend_from_slice(export_elf_symbol_name.as_bytes());
export_symbol_name_table.push(0u8); // '\0'
}
// Write Export Symbol Name Table
pad_to_alignment(&mut out, 4)?;
header.export_table_name_offset = out.stream_position()? as u32;
out.write_all(&export_symbol_name_table)?;
}
{
// Write Imported Symbol Relocation
pad_to_alignment(&mut out, 4)?;
header.external_rel_offset = out.stream_position()? as u32;
header.external_rel_size = (imported_relocations.len() * 12) as u32;
for reloc in &imported_relocations {
reloc.to_writer(&mut out, Endian::Big)?;
}
}
{
pad_to_alignment(&mut out, 4)?;
header.import_table_offset = out.stream_position()? as u32;
header.import_table_size = (import_symbol_table.len() * 12) as u32;
let mut import_symbol_name_table: Vec<u8> = vec![];
for (import_symbol_idx, import_symbol) in import_symbol_table.iter_mut().enumerate() {
let import_elf_symbol_idx = import_symbol.name_offset as usize;
let import_elf_symbol =
file.symbol_by_index(SymbolIndex(import_elf_symbol_idx)).unwrap();
let import_elf_symbol_name = import_elf_symbol.name().unwrap();
import_symbol.name_offset = import_symbol_name_table.len() as u32;
// Gather the index of the first relocation that utilize this symbol
let first_relocation_offset = imported_relocations
.iter()
.position(|r| r.id() == import_symbol_idx as u32)
.map(|idx| idx * 12)
.unwrap_or(usize::MAX) as u32;
import_symbol.section_index = first_relocation_offset;
import_symbol.to_writer(&mut out, Endian::Big)?;
import_symbol_name_table.extend_from_slice(import_elf_symbol_name.as_bytes());
import_symbol_name_table.push(0u8); // '\0'
}
// Write Export Symbol Name Table
pad_to_alignment(&mut out, 4)?;
header.import_table_name_offset = out.stream_position()? as u32;
out.write_all(&import_symbol_name_table)?;
}
{
// Write Internal Relocation Table
pad_to_alignment(&mut out, 4)?;
header.internal_rel_offset = out.stream_position()? as u32;
header.internal_rel_size = (exported_relocations.len() * 12) as u32;
for reloc in &exported_relocations {
reloc.to_writer(&mut out, Endian::Big)?;
}
}
pad_to_alignment(&mut out, 32)?;
out.seek(std::io::SeekFrom::Start(0))?;
header.to_writer(&mut out, Endian::Big)?;
Ok(())
}

View File

@ -1,21 +1,16 @@
use std::{
fs::File,
io::{stdout, BufRead, Read, Write},
io::{stdout, BufRead, BufReader, Read, Write},
path::{Path, PathBuf},
};
use anyhow::{anyhow, bail, Context, Result};
use argp::FromArgs;
use owo_colors::{OwoColorize, Stream};
use path_slash::PathExt;
use sha1::{Digest, Sha1};
use typed_path::{Utf8NativePath, Utf8NativePathBuf};
use crate::{
util::{
file::{buf_writer, process_rsp, touch},
path::native_path,
},
vfs::open_file,
};
use crate::util::file::{buf_writer, open_file, process_rsp, touch};
#[derive(FromArgs, PartialEq, Eq, Debug)]
/// Print or check SHA1 (160-bit) checksums.
@ -24,13 +19,13 @@ pub struct Args {
#[argp(switch, short = 'c')]
/// check SHA sums against given list
check: bool,
#[argp(positional, from_str_fn(native_path))]
#[argp(positional)]
/// path to input file(s)
files: Vec<Utf8NativePathBuf>,
#[argp(option, short = 'o', from_str_fn(native_path))]
files: Vec<PathBuf>,
#[argp(option, short = 'o')]
/// (check) touch output file on successful check
/// (hash) write hash(es) to output file
output: Option<Utf8NativePathBuf>,
output: Option<PathBuf>,
#[argp(switch, short = 'q')]
/// only print failures and a summary
quiet: bool,
@ -41,25 +36,25 @@ const DEFAULT_BUF_SIZE: usize = 8192;
pub fn run(args: Args) -> Result<()> {
if args.check {
for path in process_rsp(&args.files)? {
let mut file = open_file(&path, false)?;
check(&args, file.as_mut())?;
let file = open_file(&path)?;
check(&args, &mut BufReader::new(file))?;
}
if let Some(out_path) = &args.output {
touch(out_path)
.with_context(|| format!("Failed to touch output file '{}'", out_path))?;
.with_context(|| format!("Failed to touch output file '{}'", out_path.display()))?;
}
} else {
let mut w: Box<dyn Write> = if let Some(out_path) = &args.output {
Box::new(
buf_writer(out_path)
.with_context(|| format!("Failed to open output file '{}'", out_path))?,
)
} else {
Box::new(stdout())
};
let mut w: Box<dyn Write> =
if let Some(out_path) = &args.output {
Box::new(buf_writer(out_path).with_context(|| {
format!("Failed to open output file '{}'", out_path.display())
})?)
} else {
Box::new(stdout())
};
for path in process_rsp(&args.files)? {
let mut file = open_file(&path, false)?;
hash(w.as_mut(), file.as_mut(), &path)?
let mut file = open_file(&path)?;
hash(w.as_mut(), &mut file, &path)?
}
}
Ok(())
@ -116,7 +111,7 @@ where R: BufRead + ?Sized {
Ok(())
}
fn hash<R, W>(w: &mut W, reader: &mut R, path: &Utf8NativePath) -> Result<()>
fn hash<R, W>(w: &mut W, reader: &mut R, path: &Path) -> Result<()>
where
R: Read + ?Sized,
W: Write + ?Sized,
@ -125,7 +120,7 @@ where
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}"))?;
writeln!(w, "{} {}", hash_str, path.with_unix_encoding())?;
writeln!(w, "{} {}", hash_str, path.to_slash_lossy())?;
Ok(())
}

View File

@ -1,9 +1,13 @@
use anyhow::Result;
use argp::FromArgs;
use typed_path::Utf8NativePathBuf;
use std::{borrow::Cow, fs, fs::DirBuilder, path::PathBuf};
use super::vfs;
use crate::util::path::native_path;
use anyhow::{anyhow, Context, Result};
use argp::FromArgs;
use itertools::Itertools;
use crate::util::{
file::{decompress_if_needed, map_file},
u8_arc::{U8Node, U8View},
};
#[derive(FromArgs, PartialEq, Debug)]
/// Commands for processing U8 (arc) files.
@ -24,29 +28,23 @@ enum SubCommand {
/// Views U8 (arc) file information.
#[argp(subcommand, name = "list")]
pub struct ListArgs {
#[argp(positional, from_str_fn(native_path))]
#[argp(positional)]
/// U8 (arc) file
file: Utf8NativePathBuf,
#[argp(switch, short = 's')]
/// Only print filenames.
short: bool,
file: PathBuf,
}
#[derive(FromArgs, PartialEq, Eq, Debug)]
/// Extracts U8 (arc) file contents.
#[argp(subcommand, name = "extract")]
pub struct ExtractArgs {
#[argp(positional, from_str_fn(native_path))]
#[argp(positional)]
/// U8 (arc) file
file: Utf8NativePathBuf,
#[argp(option, short = 'o', from_str_fn(native_path))]
file: PathBuf,
#[argp(option, short = 'o')]
/// output directory
output: Option<Utf8NativePathBuf>,
#[argp(switch)]
/// Do not decompress files when copying.
no_decompress: bool,
output: Option<PathBuf>,
#[argp(switch, short = 'q')]
/// Quiet output. Don't print anything except errors.
/// quiet output
quiet: bool,
}
@ -58,16 +56,66 @@ pub fn run(args: Args) -> Result<()> {
}
fn list(args: ListArgs) -> Result<()> {
let path = Utf8NativePathBuf::from(format!("{}:", args.file));
vfs::ls(vfs::LsArgs { path, short: args.short, recursive: true })
let file = map_file(&args.file)?;
let view = U8View::new(file.as_slice())
.map_err(|e| anyhow!("Failed to open U8 file '{}': {}", args.file.display(), e))?;
visit_files(&view, |_, node, path| {
println!("{}: {} bytes, offset {:#X}", path, node.length(), node.offset());
Ok(())
})
}
fn extract(args: ExtractArgs) -> Result<()> {
let path = Utf8NativePathBuf::from(format!("{}:", args.file));
let output = args.output.unwrap_or_else(|| Utf8NativePathBuf::from("."));
vfs::cp(vfs::CpArgs {
paths: vec![path, output],
no_decompress: args.no_decompress,
quiet: args.quiet,
let file = map_file(&args.file)?;
let view = U8View::new(file.as_slice())
.map_err(|e| anyhow!("Failed to open U8 file '{}': {}", args.file.display(), e))?;
visit_files(&view, |_, node, path| {
let offset = node.offset();
let size = node.length();
let file_data = decompress_if_needed(
&file.as_slice()[offset as usize..offset as usize + size as usize],
)?;
let output_path = args
.output
.as_ref()
.map(|p| p.join(&path))
.unwrap_or_else(|| PathBuf::from(path.clone()));
if !args.quiet {
println!("Extracting {} to {} ({} bytes)", path, output_path.display(), size);
}
if let Some(parent) = output_path.parent() {
DirBuilder::new().recursive(true).create(parent)?;
}
fs::write(&output_path, file_data)
.with_context(|| format!("Failed to write file '{}'", output_path.display()))?;
Ok(())
})
}
fn visit_files(
view: &U8View,
mut visitor: impl FnMut(usize, &U8Node, String) -> Result<()>,
) -> Result<()> {
let mut path_segments = Vec::<(Cow<str>, usize)>::new();
for (idx, node, name) in view.iter() {
// Remove ended path segments
let mut new_size = 0;
for (_, end) in path_segments.iter() {
if *end == idx {
break;
}
new_size += 1;
}
path_segments.truncate(new_size);
// Add the new path segment
let end = if node.is_dir() { node.length() as usize } else { idx + 1 };
path_segments.push((name.map_err(|e| anyhow!("{}", e))?, end));
let path = path_segments.iter().map(|(name, _)| name.as_ref()).join("/");
if !node.is_dir() {
visitor(idx, node, path)?;
}
}
Ok(())
}

View File

@ -1,286 +0,0 @@
use std::{fs, fs::File, io::Write};
use anyhow::{anyhow, bail, Context};
use argp::FromArgs;
use size::Size;
use typed_path::{Utf8NativePath, Utf8NativePathBuf, Utf8UnixPath};
use crate::{
util::{file::buf_copy, path::native_path},
vfs::{
decompress_file, detect, open_path, FileFormat, OpenResult, Vfs, VfsFile, VfsFileType,
VfsMetadata,
},
};
#[derive(FromArgs, PartialEq, Debug)]
/// Commands for interacting with discs and containers.
#[argp(subcommand, name = "vfs")]
pub struct Args {
#[argp(subcommand)]
command: SubCommand,
}
#[derive(FromArgs, PartialEq, Debug)]
#[argp(subcommand)]
enum SubCommand {
Ls(LsArgs),
Cp(CpArgs),
}
#[derive(FromArgs, PartialEq, Eq, Debug)]
/// List files in a directory or container.
#[argp(subcommand, name = "ls")]
pub struct LsArgs {
#[argp(positional, from_str_fn(native_path))]
/// Directory or container path.
pub path: Utf8NativePathBuf,
#[argp(switch, short = 's')]
/// Only print filenames.
pub short: bool,
#[argp(switch, short = 'r')]
/// Recursively list files in directories.
pub recursive: bool,
}
#[derive(FromArgs, PartialEq, Eq, Debug)]
/// Copy files from a container.
#[argp(subcommand, name = "cp")]
pub struct CpArgs {
#[argp(positional, from_str_fn(native_path))]
/// Source path(s) and destination path.
pub paths: Vec<Utf8NativePathBuf>,
#[argp(switch)]
/// Do not decompress files when copying.
pub no_decompress: bool,
#[argp(switch, short = 'q')]
/// Quiet output. Don't print anything except errors.
pub quiet: bool,
}
pub fn run(args: Args) -> anyhow::Result<()> {
match args.command {
SubCommand::Ls(args) => ls(args),
SubCommand::Cp(args) => cp(args),
}
}
const SEPARATOR: &str = " | ";
type Columns<const N: usize> = [String; N];
fn column_widths<const N: usize>(entries: &[Columns<N>]) -> [usize; N] {
let mut widths = [0usize; N];
for text in entries {
for (i, column) in text.iter().enumerate() {
widths[i] = widths[i].max(column.len());
}
}
widths
}
fn file_info(
filename: &str,
file: &mut dyn VfsFile,
metadata: &VfsMetadata,
) -> anyhow::Result<Columns<5>> {
let format =
detect(file).with_context(|| format!("Failed to detect file format for {}", filename))?;
let mut info: Columns<5> = [
Size::from_bytes(metadata.len).to_string(),
filename.to_string(),
format.to_string(),
String::new(),
String::new(),
];
if let FileFormat::Compressed(kind) = format {
let mut decompressed = decompress_file(file, kind)?;
let metadata = decompressed
.metadata()
.with_context(|| format!("Failed to fetch metadata for {}", filename))?;
let format = detect(decompressed.as_mut())
.with_context(|| format!("Failed to detect file format for {}", filename))?;
info[3] = format!("Decompressed: {}", Size::from_bytes(metadata.len));
info[4] = format.to_string();
}
Ok(info)
}
pub fn ls(args: LsArgs) -> anyhow::Result<()> {
let mut files = Vec::new();
match open_path(&args.path, false)? {
OpenResult::File(mut file, path) => {
let filename = path.file_name().ok_or_else(|| anyhow!("Path has no filename"))?;
if args.short {
println!("{}", filename);
} else {
let metadata = file
.metadata()
.with_context(|| format!("Failed to fetch metadata for {}", path))?;
files.push(file_info(filename, file.as_mut(), &metadata)?);
}
}
OpenResult::Directory(mut fs, path) => {
ls_directory(fs.as_mut(), &path, Utf8UnixPath::new(""), &args, &mut files)?;
}
}
if !args.short {
let widths = column_widths(&files);
for entry in files {
let mut written = 0;
for (i, column) in entry.iter().enumerate() {
if widths[i] > 0 {
if written > 0 {
print!("{}", SEPARATOR);
}
written += 1;
print!("{:width$}", column, width = widths[i]);
}
}
println!();
}
}
Ok(())
}
fn ls_directory(
fs: &mut dyn Vfs,
path: &Utf8UnixPath,
base_filename: &Utf8UnixPath,
args: &LsArgs,
files: &mut Vec<Columns<5>>,
) -> anyhow::Result<()> {
let entries = fs.read_dir(path)?;
files.reserve(entries.len());
for filename in entries {
let entry_path = path.join(&filename);
let display_path = base_filename.join(&filename);
let metadata = fs
.metadata(&entry_path)
.with_context(|| format!("Failed to fetch metadata for {}", entry_path))?;
match metadata.file_type {
VfsFileType::File => {
let mut file = fs
.open(&entry_path)
.with_context(|| format!("Failed to open file {}", entry_path))?;
if args.short {
println!("{}", display_path);
} else {
files.push(file_info(display_path.as_str(), file.as_mut(), &metadata)?);
}
}
VfsFileType::Directory => {
if args.short {
println!("{}/", display_path);
} else {
files.push([
" ".to_string(),
format!("{}/", display_path),
"Directory".to_string(),
String::new(),
String::new(),
]);
}
if args.recursive {
ls_directory(fs, &entry_path, &display_path, args, files)?;
}
}
}
}
Ok(())
}
pub fn cp(mut args: CpArgs) -> anyhow::Result<()> {
if args.paths.len() < 2 {
bail!("Both source and destination paths must be provided");
}
let dest = args.paths.pop().unwrap();
let dest_is_dir = args.paths.len() > 1 || fs::metadata(&dest).ok().is_some_and(|m| m.is_dir());
let auto_decompress = !args.no_decompress;
for path in args.paths {
match open_path(&path, auto_decompress)? {
OpenResult::File(file, path) => {
let dest = if dest_is_dir {
fs::create_dir_all(&dest)
.with_context(|| format!("Failed to create directory {}", dest))?;
let filename =
path.file_name().ok_or_else(|| anyhow!("Path has no filename"))?;
dest.join(filename)
} else {
dest.clone()
};
cp_file(file, &path, &dest, auto_decompress, args.quiet)?;
}
OpenResult::Directory(mut fs, path) => {
cp_recursive(fs.as_mut(), &path, &dest, auto_decompress, args.quiet)?;
}
}
}
Ok(())
}
fn cp_file(
mut file: Box<dyn VfsFile>,
path: &Utf8UnixPath,
dest: &Utf8NativePath,
auto_decompress: bool,
quiet: bool,
) -> anyhow::Result<()> {
let mut compression = None;
if let FileFormat::Compressed(kind) = detect(file.as_mut())? {
if auto_decompress {
file = decompress_file(file.as_mut(), kind)
.with_context(|| format!("Failed to decompress file {}", dest))?;
compression = Some(kind);
}
}
let metadata =
file.metadata().with_context(|| format!("Failed to fetch metadata for {}", dest))?;
if !quiet {
if let Some(kind) = compression {
println!(
"{} -> {} ({}) [Decompressed {}]",
path,
dest,
Size::from_bytes(metadata.len),
kind
);
} else {
println!("{} -> {} ({})", path, dest, Size::from_bytes(metadata.len));
}
}
let mut dest_file =
File::create(dest).with_context(|| format!("Failed to create file {}", dest))?;
buf_copy(file.as_mut(), &mut dest_file)
.with_context(|| format!("Failed to copy file {}", dest))?;
dest_file.flush().with_context(|| format!("Failed to flush file {}", dest))?;
Ok(())
}
fn cp_recursive(
fs: &mut dyn Vfs,
path: &Utf8UnixPath,
dest: &Utf8NativePath,
auto_decompress: bool,
quiet: bool,
) -> anyhow::Result<()> {
fs::create_dir_all(dest).with_context(|| format!("Failed to create directory {}", dest))?;
let entries = fs.read_dir(path)?;
for filename in entries {
let entry_path = path.join(&filename);
let metadata = fs
.metadata(&entry_path)
.with_context(|| format!("Failed to fetch metadata for {}", entry_path))?;
match metadata.file_type {
VfsFileType::File => {
let file = fs
.open(&entry_path)
.with_context(|| format!("Failed to open file {}", entry_path))?;
cp_file(file, &entry_path, &dest.join(filename), auto_decompress, quiet)?;
}
VfsFileType::Directory => {
cp_recursive(fs, &entry_path, &dest.join(filename), auto_decompress, quiet)?;
}
}
}
Ok(())
}

View File

@ -1,17 +1,12 @@
use std::fs;
use std::{fs, path::PathBuf};
use anyhow::{Context, Result};
use argp::FromArgs;
use typed_path::Utf8NativePathBuf;
use crate::{
util::{
file::process_rsp,
ncompress::{compress_yay0, decompress_yay0},
path::native_path,
IntoCow, ToCow,
},
vfs::open_file,
use crate::util::{
file::{map_file_basic, process_rsp},
ncompress::{compress_yay0, decompress_yay0},
IntoCow, ToCow,
};
#[derive(FromArgs, PartialEq, Debug)]
@ -33,26 +28,26 @@ enum SubCommand {
/// Compresses files using YAY0.
#[argp(subcommand, name = "compress")]
pub struct CompressArgs {
#[argp(positional, from_str_fn(native_path))]
#[argp(positional)]
/// Files to compress
files: Vec<Utf8NativePathBuf>,
#[argp(option, short = 'o', from_str_fn(native_path))]
files: Vec<PathBuf>,
#[argp(option, short = 'o')]
/// Output file (or directory, if multiple files are specified).
/// If not specified, compresses in-place.
output: Option<Utf8NativePathBuf>,
output: Option<PathBuf>,
}
#[derive(FromArgs, PartialEq, Eq, Debug)]
/// Decompresses YAY0-compressed files.
#[argp(subcommand, name = "decompress")]
pub struct DecompressArgs {
#[argp(positional, from_str_fn(native_path))]
#[argp(positional)]
/// YAY0-compressed files
files: Vec<Utf8NativePathBuf>,
#[argp(option, short = 'o', from_str_fn(native_path))]
files: Vec<PathBuf>,
#[argp(option, short = 'o')]
/// Output file (or directory, if multiple files are specified).
/// If not specified, decompresses in-place.
output: Option<Utf8NativePathBuf>,
output: Option<PathBuf>,
}
pub fn run(args: Args) -> Result<()> {
@ -67,8 +62,8 @@ fn compress(args: CompressArgs) -> Result<()> {
let single_file = files.len() == 1;
for path in files {
let data = {
let mut file = open_file(&path, false)?;
compress_yay0(file.map()?)
let file = map_file_basic(&path)?;
compress_yay0(file.as_slice())
};
let out_path = if let Some(output) = &args.output {
if single_file {
@ -80,7 +75,7 @@ fn compress(args: CompressArgs) -> Result<()> {
path.as_path().to_cow()
};
fs::write(out_path.as_ref(), data)
.with_context(|| format!("Failed to write '{}'", out_path))?;
.with_context(|| format!("Failed to write '{}'", out_path.display()))?;
}
Ok(())
}
@ -90,9 +85,9 @@ fn decompress(args: DecompressArgs) -> Result<()> {
let single_file = files.len() == 1;
for path in files {
let data = {
let mut file = open_file(&path, true)?;
decompress_yay0(file.map()?)
.with_context(|| format!("Failed to decompress '{}' using Yay0", path))?
let file = map_file_basic(&path)?;
decompress_yay0(file.as_slice())
.with_context(|| format!("Failed to decompress '{}' using Yay0", path.display()))?
};
let out_path = if let Some(output) = &args.output {
if single_file {
@ -104,7 +99,7 @@ fn decompress(args: DecompressArgs) -> Result<()> {
path.as_path().to_cow()
};
fs::write(out_path.as_ref(), data)
.with_context(|| format!("Failed to write '{}'", out_path))?;
.with_context(|| format!("Failed to write '{}'", out_path.display()))?;
}
Ok(())
}

View File

@ -1,17 +1,12 @@
use std::fs;
use std::{fs, path::PathBuf};
use anyhow::{Context, Result};
use argp::FromArgs;
use typed_path::Utf8NativePathBuf;
use crate::{
util::{
file::process_rsp,
ncompress::{compress_yaz0, decompress_yaz0},
path::native_path,
IntoCow, ToCow,
},
vfs::open_file,
use crate::util::{
file::{map_file_basic, process_rsp},
ncompress::{compress_yaz0, decompress_yaz0},
IntoCow, ToCow,
};
#[derive(FromArgs, PartialEq, Debug)]
@ -33,26 +28,26 @@ enum SubCommand {
/// Compresses files using YAZ0.
#[argp(subcommand, name = "compress")]
pub struct CompressArgs {
#[argp(positional, from_str_fn(native_path))]
#[argp(positional)]
/// Files to compress
files: Vec<Utf8NativePathBuf>,
#[argp(option, short = 'o', from_str_fn(native_path))]
files: Vec<PathBuf>,
#[argp(option, short = 'o')]
/// Output file (or directory, if multiple files are specified).
/// If not specified, compresses in-place.
output: Option<Utf8NativePathBuf>,
output: Option<PathBuf>,
}
#[derive(FromArgs, PartialEq, Eq, Debug)]
/// Decompresses YAZ0-compressed files.
#[argp(subcommand, name = "decompress")]
pub struct DecompressArgs {
#[argp(positional, from_str_fn(native_path))]
#[argp(positional)]
/// YAZ0-compressed files
files: Vec<Utf8NativePathBuf>,
#[argp(option, short = 'o', from_str_fn(native_path))]
files: Vec<PathBuf>,
#[argp(option, short = 'o')]
/// Output file (or directory, if multiple files are specified).
/// If not specified, decompresses in-place.
output: Option<Utf8NativePathBuf>,
output: Option<PathBuf>,
}
pub fn run(args: Args) -> Result<()> {
@ -67,8 +62,8 @@ fn compress(args: CompressArgs) -> Result<()> {
let single_file = files.len() == 1;
for path in files {
let data = {
let mut file = open_file(&path, false)?;
compress_yaz0(file.map()?)
let file = map_file_basic(&path)?;
compress_yaz0(file.as_slice())
};
let out_path = if let Some(output) = &args.output {
if single_file {
@ -80,7 +75,7 @@ fn compress(args: CompressArgs) -> Result<()> {
path.as_path().to_cow()
};
fs::write(out_path.as_ref(), data)
.with_context(|| format!("Failed to write '{}'", out_path))?;
.with_context(|| format!("Failed to write '{}'", out_path.display()))?;
}
Ok(())
}
@ -90,9 +85,9 @@ fn decompress(args: DecompressArgs) -> Result<()> {
let single_file = files.len() == 1;
for path in files {
let data = {
let mut file = open_file(&path, false)?;
decompress_yaz0(file.map()?)
.with_context(|| format!("Failed to decompress '{}' using Yaz0", path))?
let file = map_file_basic(&path)?;
decompress_yaz0(file.as_slice())
.with_context(|| format!("Failed to decompress '{}' using Yaz0", path.display()))?
};
let out_path = if let Some(output) = &args.output {
if single_file {
@ -104,7 +99,7 @@ fn decompress(args: DecompressArgs) -> Result<()> {
path.as_path().to_cow()
};
fs::write(out_path.as_ref(), data)
.with_context(|| format!("Failed to write '{}'", out_path))?;
.with_context(|| format!("Failed to write '{}'", out_path.display()))?;
}
Ok(())
}

View File

@ -1,4 +1,3 @@
#![deny(unused_crate_dependencies)]
use std::{env, ffi::OsStr, fmt::Display, path::PathBuf, process::exit, str::FromStr};
use anyhow::Error;
@ -13,13 +12,6 @@ pub mod argp_version;
pub mod cmd;
pub mod obj;
pub mod util;
pub mod vfs;
// musl's allocator is very slow, so use mimalloc when targeting musl.
// Otherwise, use the system allocator to avoid extra code size.
#[cfg(target_env = "musl")]
#[global_allocator]
static ALLOC: mimalloc::MiMalloc = mimalloc::MiMalloc;
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
enum LogLevel {
@ -97,13 +89,13 @@ enum SubCommand {
Elf(cmd::elf::Args),
Elf2Dol(cmd::elf2dol::Args),
Map(cmd::map::Args),
MetroidBuildInfo(cmd::metroidbuildinfo::Args),
Nlzss(cmd::nlzss::Args),
Rarc(cmd::rarc::Args),
Rel(cmd::rel::Args),
Rso(cmd::rso::Args),
Shasum(cmd::shasum::Args),
U8(cmd::u8_arc::Args),
Vfs(cmd::vfs::Args),
Yay0(cmd::yay0::Args),
Yaz0(cmd::yaz0::Args),
}
@ -172,13 +164,13 @@ fn main() {
SubCommand::Elf(c_args) => cmd::elf::run(c_args),
SubCommand::Elf2Dol(c_args) => cmd::elf2dol::run(c_args),
SubCommand::Map(c_args) => cmd::map::run(c_args),
SubCommand::MetroidBuildInfo(c_args) => cmd::metroidbuildinfo::run(c_args),
SubCommand::Nlzss(c_args) => cmd::nlzss::run(c_args),
SubCommand::Rarc(c_args) => cmd::rarc::run(c_args),
SubCommand::Rel(c_args) => cmd::rel::run(c_args),
SubCommand::Rso(c_args) => cmd::rso::run(c_args),
SubCommand::Shasum(c_args) => cmd::shasum::run(c_args),
SubCommand::U8(c_args) => cmd::u8_arc::run(c_args),
SubCommand::Vfs(c_args) => cmd::vfs::run(c_args),
SubCommand::Yay0(c_args) => cmd::yay0::run(c_args),
SubCommand::Yaz0(c_args) => cmd::yaz0::run(c_args),
});

View File

@ -13,9 +13,7 @@ use std::{
use anyhow::{anyhow, bail, ensure, Result};
use objdiff_core::obj::split_meta::SplitMeta;
pub use relocations::{ObjReloc, ObjRelocKind, ObjRelocations};
pub use sections::{
section_kind_for_section, ObjSection, ObjSectionKind, ObjSections, SectionIndex,
};
pub use sections::{ObjSection, ObjSectionKind, ObjSections};
pub use splits::{ObjSplit, ObjSplits};
pub use symbols::{
best_match_for_reloc, ObjDataKind, ObjSymbol, ObjSymbolFlagSet, ObjSymbolFlags, ObjSymbolKind,
@ -49,8 +47,6 @@ pub struct ObjUnit {
pub autogenerated: bool,
/// MW `.comment` section version.
pub comment_version: Option<u8>,
/// Influences the order of this unit relative to other ordered units.
pub order: Option<i32>,
}
#[derive(Debug, Clone)]
@ -134,12 +130,7 @@ impl ObjInfo {
self.symbols.add(in_symbol, replace)
}
pub fn add_split(
&mut self,
section_index: SectionIndex,
address: u32,
split: ObjSplit,
) -> Result<()> {
pub fn add_split(&mut self, section_index: usize, address: u32, split: ObjSplit) -> Result<()> {
let section = self
.sections
.get_mut(section_index)
@ -329,8 +320,8 @@ impl ObjInfo {
// Include common symbols
self.symbols
.iter()
.filter(|&(_, symbol)| symbol.flags.is_common())
.map(|(_, s)| s.size as u32),
.filter(|&symbol| symbol.flags.is_common())
.map(|s| s.size as u32),
)
.sum()
}

View File

@ -7,10 +7,7 @@ use std::{
use anyhow::{anyhow, bail, ensure, Result};
use itertools::Itertools;
use crate::{
analysis::cfa::SectionAddress,
obj::{ObjKind, ObjRelocations, ObjSplit, ObjSplits, ObjSymbol},
};
use crate::obj::{ObjKind, ObjRelocations, ObjSplit, ObjSplits, ObjSymbol};
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum ObjSectionKind {
@ -29,7 +26,7 @@ pub struct ObjSection {
pub data: Vec<u8>,
pub align: u64,
/// REL files reference the original ELF section indices
pub elf_index: SectionIndex,
pub elf_index: usize,
pub relocations: ObjRelocations,
pub virtual_address: Option<u64>,
pub file_offset: u64,
@ -43,45 +40,38 @@ pub struct ObjSections {
sections: Vec<ObjSection>,
}
pub type SectionIndex = u32;
impl ObjSections {
pub fn new(obj_kind: ObjKind, sections: Vec<ObjSection>) -> Self { Self { obj_kind, sections } }
pub fn iter(&self) -> impl DoubleEndedIterator<Item = (SectionIndex, &ObjSection)> {
self.sections.iter().enumerate().map(|(i, s)| (i as SectionIndex, s))
pub fn iter(&self) -> impl DoubleEndedIterator<Item = (usize, &ObjSection)> {
self.sections.iter().enumerate()
}
pub fn iter_mut(&mut self) -> impl DoubleEndedIterator<Item = (SectionIndex, &mut ObjSection)> {
self.sections.iter_mut().enumerate().map(|(i, s)| (i as SectionIndex, s))
pub fn iter_mut(&mut self) -> impl DoubleEndedIterator<Item = (usize, &mut ObjSection)> {
self.sections.iter_mut().enumerate()
}
pub fn len(&self) -> SectionIndex { self.sections.len() as SectionIndex }
pub fn len(&self) -> usize { self.sections.len() }
pub fn is_empty(&self) -> bool { self.sections.is_empty() }
pub fn next_section_index(&self) -> SectionIndex { self.sections.len() as SectionIndex }
pub fn next_section_index(&self) -> usize { self.sections.len() }
pub fn get(&self, index: SectionIndex) -> Option<&ObjSection> {
self.sections.get(index as usize)
pub fn get(&self, index: usize) -> Option<&ObjSection> { self.sections.get(index) }
pub fn get_mut(&mut self, index: usize) -> Option<&mut ObjSection> {
self.sections.get_mut(index)
}
pub fn get_mut(&mut self, index: SectionIndex) -> Option<&mut ObjSection> {
self.sections.get_mut(index as usize)
}
pub fn get_elf_index(&self, elf_index: SectionIndex) -> Option<(SectionIndex, &ObjSection)> {
pub fn get_elf_index(&self, elf_index: usize) -> Option<(usize, &ObjSection)> {
self.iter().find(|&(_, s)| s.elf_index == elf_index)
}
pub fn get_elf_index_mut(
&mut self,
elf_index: SectionIndex,
) -> Option<(SectionIndex, &mut ObjSection)> {
pub fn get_elf_index_mut(&mut self, elf_index: usize) -> Option<(usize, &mut ObjSection)> {
self.iter_mut().find(|(_, s)| s.elf_index == elf_index)
}
pub fn at_address(&self, addr: u32) -> Result<(SectionIndex, &ObjSection)> {
pub fn at_address(&self, addr: u32) -> Result<(usize, &ObjSection)> {
ensure!(
self.obj_kind == ObjKind::Executable,
"Use of ObjSections::at_address in relocatable object"
@ -91,7 +81,7 @@ impl ObjSections {
.ok_or_else(|| anyhow!("Failed to locate section @ {:#010X}", addr))
}
pub fn at_address_mut(&mut self, addr: u32) -> Result<(SectionIndex, &mut ObjSection)> {
pub fn at_address_mut(&mut self, addr: u32) -> Result<(usize, &mut ObjSection)> {
ensure!(
self.obj_kind == ObjKind::Executable,
"Use of ObjSections::at_address_mut in relocatable object"
@ -101,7 +91,7 @@ impl ObjSections {
.ok_or_else(|| anyhow!("Failed to locate section @ {:#010X}", addr))
}
pub fn with_range(&self, range: Range<u32>) -> Result<(SectionIndex, &ObjSection)> {
pub fn with_range(&self, range: Range<u32>) -> Result<(usize, &ObjSection)> {
ensure!(
self.obj_kind == ObjKind::Executable,
"Use of ObjSections::with_range in relocatable object"
@ -114,52 +104,46 @@ impl ObjSections {
pub fn by_kind(
&self,
kind: ObjSectionKind,
) -> impl DoubleEndedIterator<Item = (SectionIndex, &ObjSection)> {
) -> impl DoubleEndedIterator<Item = (usize, &ObjSection)> {
self.iter().filter(move |(_, s)| s.kind == kind)
}
pub fn by_name(&self, name: &str) -> Result<Option<(SectionIndex, &ObjSection)>> {
pub fn by_name(&self, name: &str) -> Result<Option<(usize, &ObjSection)>> {
self.iter()
.filter(move |(_, s)| s.name == name)
.at_most_one()
.map_err(|_| anyhow!("Multiple sections with name {}", name))
}
pub fn push(&mut self, section: ObjSection) -> SectionIndex {
pub fn push(&mut self, section: ObjSection) -> usize {
let index = self.sections.len();
self.sections.push(section);
index as SectionIndex
index
}
pub fn all_splits(
&self,
) -> impl DoubleEndedIterator<Item = (SectionIndex, &ObjSection, u32, &ObjSplit)> {
) -> impl DoubleEndedIterator<Item = (usize, &ObjSection, u32, &ObjSplit)> {
self.iter()
.flat_map(|(idx, s)| s.splits.iter().map(move |(addr, split)| (idx, s, addr, split)))
}
pub fn common_bss_start(&self) -> Option<SectionAddress> {
pub fn common_bss_start(&self) -> Option<(usize, u32)> {
let Ok(Some((section_index, section))) = self.by_name(".bss") else {
return None;
};
section
.splits
.iter()
.find(|(_, split)| split.common)
.map(|(addr, _)| SectionAddress::new(section_index, addr))
section.splits.iter().find(|(_, split)| split.common).map(|(addr, _)| (section_index, addr))
}
}
impl Index<SectionIndex> for ObjSections {
impl Index<usize> for ObjSections {
type Output = ObjSection;
fn index(&self, index: SectionIndex) -> &Self::Output { &self.sections[index as usize] }
fn index(&self, index: usize) -> &Self::Output { &self.sections[index] }
}
impl IndexMut<SectionIndex> for ObjSections {
fn index_mut(&mut self, index: SectionIndex) -> &mut Self::Output {
&mut self.sections[index as usize]
}
impl IndexMut<usize> for ObjSections {
fn index_mut(&mut self, index: usize) -> &mut Self::Output { &mut self.sections[index] }
}
impl ObjSection {
@ -234,7 +218,7 @@ impl ObjSection {
}
}
pub fn section_kind_for_section(section_name: &str) -> Result<ObjSectionKind> {
fn section_kind_for_section(section_name: &str) -> Result<ObjSectionKind> {
Ok(match section_name {
".init" | ".text" | ".dbgtext" | ".vmtext" => ObjSectionKind::Code,
".ctors" | ".dtors" | ".rodata" | ".sdata2" | "extab" | "extabindex" | ".BINARY" => {

View File

@ -4,7 +4,7 @@ use anyhow::{anyhow, Result};
use itertools::Itertools;
use crate::{
obj::{ObjInfo, ObjSection, SectionIndex},
obj::{ObjInfo, ObjSection},
util::{nested::NestedVec, split::default_section_align},
};
@ -28,7 +28,7 @@ impl ObjSplit {
pub fn alignment(
&self,
obj: &ObjInfo,
section_index: SectionIndex,
section_index: usize,
section: &ObjSection,
split_addr: u32,
) -> u32 {

View File

@ -12,7 +12,7 @@ use serde_repr::{Deserialize_repr, Serialize_repr};
use crate::{
analysis::cfa::SectionAddress,
obj::{sections::SectionIndex, ObjKind, ObjRelocKind, ObjSections},
obj::{ObjKind, ObjRelocKind, ObjSections},
util::{
config::{is_auto_jump_table, is_auto_label, is_auto_symbol, parse_u32},
nested::NestedVec,
@ -187,7 +187,7 @@ pub struct ObjSymbol {
pub name: String,
pub demangled_name: Option<String>,
pub address: u64,
pub section: Option<SectionIndex>,
pub section: Option<usize>,
pub size: u64,
pub size_known: bool,
pub flags: ObjSymbolFlagSet,
@ -199,7 +199,7 @@ pub struct ObjSymbol {
pub demangled_name_hash: Option<u32>,
}
pub type SymbolIndex = u32;
pub type SymbolIndex = usize;
#[derive(Debug, Clone)]
pub struct ObjSymbols {
@ -216,10 +216,8 @@ impl ObjSymbols {
let mut symbols_by_section: Vec<BTreeMap<u32, Vec<SymbolIndex>>> = vec![];
let mut symbols_by_name = HashMap::<String, Vec<SymbolIndex>>::new();
for (idx, symbol) in symbols.iter().enumerate() {
let idx = idx as SymbolIndex;
symbols_by_address.nested_push(symbol.address as u32, idx);
if let Some(section_idx) = symbol.section {
let section_idx = section_idx as usize;
if section_idx >= symbols_by_section.len() {
symbols_by_section.resize_with(section_idx + 1, BTreeMap::new);
}
@ -314,7 +312,7 @@ impl ObjSymbols {
}
symbol_idx
} else {
let target_symbol_idx = self.symbols.len() as SymbolIndex;
let target_symbol_idx = self.symbols.len();
self.add_direct(ObjSymbol {
name: in_symbol.name,
demangled_name: in_symbol.demangled_name,
@ -335,10 +333,9 @@ impl ObjSymbols {
}
pub fn add_direct(&mut self, in_symbol: ObjSymbol) -> Result<SymbolIndex> {
let symbol_idx = self.symbols.len() as SymbolIndex;
let symbol_idx = self.symbols.len();
self.symbols_by_address.nested_push(in_symbol.address as u32, symbol_idx);
if let Some(section_idx) = in_symbol.section {
let section_idx = section_idx as usize;
if section_idx >= self.symbols_by_section.len() {
self.symbols_by_section.resize_with(section_idx + 1, BTreeMap::new);
}
@ -358,30 +355,28 @@ impl ObjSymbols {
Ok(symbol_idx)
}
pub fn iter(&self) -> impl DoubleEndedIterator<Item = (SymbolIndex, &ObjSymbol)> {
self.symbols.iter().enumerate().map(|(i, s)| (i as SymbolIndex, s))
}
pub fn iter(&self) -> impl DoubleEndedIterator<Item = &ObjSymbol> { self.symbols.iter() }
pub fn count(&self) -> SymbolIndex { self.symbols.len() as SymbolIndex }
pub fn count(&self) -> usize { self.symbols.len() }
pub fn at_section_address(
&self,
section_idx: SectionIndex,
section_idx: usize,
addr: u32,
) -> impl DoubleEndedIterator<Item = (SymbolIndex, &ObjSymbol)> {
self.symbols_by_section
.get(section_idx as usize)
.get(section_idx)
.and_then(|v| v.get(&addr))
.into_iter()
.flatten()
.map(move |&idx| (idx, &self.symbols[idx as usize]))
.map(move |&idx| (idx, &self.symbols[idx]))
// "Stripped" symbols don't actually exist at the address
.filter(|(_, sym)| !sym.flags.is_stripped())
}
pub fn kind_at_section_address(
&self,
section_idx: SectionIndex,
section_idx: usize,
addr: u32,
kind: ObjSymbolKind,
) -> Result<Option<(SymbolIndex, &ObjSymbol)>> {
@ -402,7 +397,7 @@ impl ObjSymbols {
self.symbols_by_section
.iter()
.flat_map(|v| v.iter().map(|(_, v)| v))
.flat_map(move |v| v.iter().map(move |u| (*u, &self.symbols[*u as usize])))
.flat_map(move |v| v.iter().map(move |u| (*u, &self.symbols[*u])))
}
// Iterate over all ABS symbols
@ -410,24 +405,24 @@ impl ObjSymbols {
debug_assert!(self.obj_kind == ObjKind::Executable);
self.symbols_by_address
.iter()
.flat_map(|(_, v)| v.iter().map(|&u| (u, &self.symbols[u as usize])))
.flat_map(|(_, v)| v.iter().map(|&u| (u, &self.symbols[u])))
.filter(|(_, s)| s.section.is_none())
}
// Iterate over range in address ascending order, excluding ABS symbols
pub fn for_section_range<R>(
&self,
section_index: SectionIndex,
section_index: usize,
range: R,
) -> impl DoubleEndedIterator<Item = (SymbolIndex, &ObjSymbol)>
where
R: RangeBounds<u32> + Clone,
{
self.symbols_by_section
.get(section_index as usize)
.get(section_index)
.into_iter()
.flat_map(move |v| v.range(range.clone()))
.flat_map(move |(_, v)| v.iter().map(move |u| (*u, &self.symbols[*u as usize])))
.flat_map(move |(_, v)| v.iter().map(move |u| (*u, &self.symbols[*u])))
}
pub fn indexes_for_range<R>(
@ -443,13 +438,13 @@ impl ObjSymbols {
pub fn for_section(
&self,
section_idx: SectionIndex,
section_idx: usize,
) -> impl DoubleEndedIterator<Item = (SymbolIndex, &ObjSymbol)> {
self.symbols_by_section
.get(section_idx as usize)
.get(section_idx)
.into_iter()
.flat_map(|v| v.iter().map(|(_, v)| v))
.flat_map(move |v| v.iter().map(move |u| (*u, &self.symbols[*u as usize])))
.flat_map(move |v| v.iter().map(move |u| (*u, &self.symbols[*u])))
}
pub fn for_name(
@ -459,7 +454,7 @@ impl ObjSymbols {
self.symbols_by_name
.get(name)
.into_iter()
.flat_map(move |v| v.iter().map(move |u| (*u, &self.symbols[*u as usize])))
.flat_map(move |v| v.iter().map(move |u| (*u, &self.symbols[*u])))
}
pub fn by_name(&self, name: &str) -> Result<Option<(SymbolIndex, &ObjSymbol)>> {
@ -520,11 +515,11 @@ impl ObjSymbols {
&self,
kind: ObjSymbolKind,
) -> impl DoubleEndedIterator<Item = (SymbolIndex, &ObjSymbol)> {
self.iter().filter(move |(_, sym)| sym.kind == kind)
self.symbols.iter().enumerate().filter(move |(_, sym)| sym.kind == kind)
}
pub fn replace(&mut self, index: SymbolIndex, symbol: ObjSymbol) -> Result<()> {
let symbol_ref = &mut self.symbols[index as usize];
let symbol_ref = &mut self.symbols[index];
ensure!(symbol_ref.address == symbol.address, "Can't modify address with replace_symbol");
ensure!(symbol_ref.section == symbol.section, "Can't modify section with replace_symbol");
if symbol_ref.name != symbol.name {
@ -550,7 +545,7 @@ impl ObjSymbols {
for (_addr, symbol_idxs) in self.indexes_for_range(..=target_addr.address).rev() {
let symbols = symbol_idxs
.iter()
.map(|&idx| (idx, &self.symbols[idx as usize]))
.map(|&idx| (idx, &self.symbols[idx]))
.filter(|(_, sym)| {
(sym.section.is_none() || sym.section == Some(target_addr.section))
&& sym.referenced_by(reloc_kind)
@ -575,14 +570,14 @@ impl ObjSymbols {
#[inline]
pub fn flags(&mut self, idx: SymbolIndex) -> &mut ObjSymbolFlagSet {
&mut self.symbols[idx as usize].flags
&mut self.symbols[idx].flags
}
}
impl Index<SymbolIndex> for ObjSymbols {
type Output = ObjSymbol;
fn index(&self, index: SymbolIndex) -> &Self::Output { &self.symbols[index as usize] }
fn index(&self, index: usize) -> &Self::Output { &self.symbols[index] }
}
impl ObjSymbol {

View File

@ -7,7 +7,7 @@ use anyhow::Result;
use io::{Error, ErrorKind};
use crate::{
obj::{ObjSymbol, ObjSymbolKind, SectionIndex},
obj::{ObjSymbol, ObjSymbolKind},
util::{
dol::{DolLike, DolSection, DolSectionKind},
reader::{
@ -55,7 +55,7 @@ impl FromReader for AlfFile {
data_size: section.data_size,
size: section.size,
kind,
index: sections.len() as SectionIndex,
index: sections.len(),
});
}
for sym in &symtab.symbols {
@ -230,7 +230,7 @@ impl AlfSymbol {
name,
demangled_name,
address: self.address as u64,
section: Some(self.section as SectionIndex - 1),
section: Some(self.section as usize - 1),
size: self.size as u64,
size_known: true,
flags: Default::default(),

View File

@ -11,7 +11,7 @@ use ppc750cl::{Argument, Ins, InsIter, Opcode};
use crate::{
obj::{
ObjDataKind, ObjInfo, ObjReloc, ObjRelocKind, ObjSection, ObjSectionKind, ObjSymbol,
ObjSymbolKind, SymbolIndex,
ObjSymbolKind,
},
util::nested::NestedVec,
};
@ -25,7 +25,7 @@ enum SymbolEntryKind {
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
struct SymbolEntry {
index: SymbolIndex,
index: usize,
kind: SymbolEntryKind,
}
@ -44,7 +44,7 @@ where W: Write + ?Sized {
}
// We'll append generated symbols to the end
let mut symbols: Vec<ObjSymbol> = obj.symbols.iter().map(|(_, s)| s.clone()).collect();
let mut symbols: Vec<ObjSymbol> = obj.symbols.iter().cloned().collect();
let mut section_entries: Vec<BTreeMap<u32, Vec<SymbolEntry>>> = vec![];
let mut section_relocations: Vec<BTreeMap<u32, ObjReloc>> = vec![];
for (section_idx, section) in obj.sections.iter() {
@ -90,7 +90,7 @@ where W: Write + ?Sized {
.map(|e| e.index);
if target_symbol_idx.is_none() {
let display_address = address as u64 + section.virtual_address.unwrap_or(0);
let symbol_idx = symbols.len() as SymbolIndex;
let symbol_idx = symbols.len();
symbols.push(ObjSymbol {
name: format!(".L_{display_address:08X}"),
address: display_address,
@ -131,7 +131,7 @@ where W: Write + ?Sized {
if reloc.addend == 0 {
continue;
}
let target = &symbols[reloc.target_symbol as usize];
let target = &symbols[reloc.target_symbol];
let target_section_idx = match target.section {
Some(v) => v,
None => continue,
@ -140,7 +140,7 @@ where W: Write + ?Sized {
anyhow!("Invalid relocation target section: {:#010X} {:?}", reloc_address, target)
})?;
let address = (target.address as i64 + reloc.addend) as u64;
let vec = match section_entries[target_section_idx as usize].entry(address as u32) {
let vec = match section_entries[target_section_idx].entry(address as u32) {
btree_map::Entry::Occupied(e) => e.into_mut(),
btree_map::Entry::Vacant(e) => e.insert(vec![]),
};
@ -149,7 +149,7 @@ where W: Write + ?Sized {
.any(|e| e.kind == SymbolEntryKind::Label || e.kind == SymbolEntryKind::Start)
{
let display_address = address + target_section.virtual_address.unwrap_or(0);
let symbol_idx = symbols.len() as SymbolIndex;
let symbol_idx = symbols.len();
symbols.push(ObjSymbol {
name: format!(".L_{display_address:08X}"),
address: display_address,
@ -181,17 +181,13 @@ where W: Write + ?Sized {
}
for (section_index, section) in obj.sections.iter() {
let entries = &section_entries[section_index as usize];
let relocations = &section_relocations[section_index as usize];
let entries = &section_entries[section_index];
let relocations = &section_relocations[section_index];
let mut current_address = section.address as u32;
let section_end = (section.address + section.size) as u32;
let subsection = obj
.sections
.iter()
.take(section_index as usize)
.filter(|(_, s)| s.name == section.name)
.count();
let subsection =
obj.sections.iter().take(section_index).filter(|(_, s)| s.name == section.name).count();
loop {
if current_address >= section_end {
@ -373,7 +369,7 @@ fn write_symbol_entry<W>(
where
W: Write + ?Sized,
{
let symbol = &symbols[entry.index as usize];
let symbol = &symbols[entry.index];
// Skip writing certain symbols
if symbol.kind == ObjSymbolKind::Section {
@ -436,37 +432,9 @@ where
write_symbol_name(w, &symbol.name)?;
writeln!(w)?;
}
if entry.kind == SymbolEntryKind::Start && section.name == "extab" {
writeln!(w, "/*")?;
match parse_extab(symbols, entry, section) {
Ok(s) => {
for line in s.trim_end().lines() {
writeln!(w, " * {}", line)?;
}
}
Err(e) => {
log::warn!("Failed to decode extab entry {}: {}", symbol.name, e);
writeln!(w, " * Failed to decode extab entry: {}", e)?;
}
}
writeln!(w, " */")?;
}
Ok(())
}
fn parse_extab(symbols: &[ObjSymbol], entry: &SymbolEntry, section: &ObjSection) -> Result<String> {
let symbol = &symbols[entry.index as usize];
let data = section.symbol_data(symbol)?;
let decoded = cwextab::decode_extab(data)?;
let function_names = section
.relocations
.range(symbol.address as u32..(symbol.address + symbol.size) as u32)
.map(|(_, reloc)| symbols[reloc.target_symbol as usize].name.clone())
.collect_vec();
decoded.to_string(function_names).ok_or_else(|| anyhow!("Failed to print extab entry"))
}
#[allow(clippy::too_many_arguments)]
fn write_data<W>(
w: &mut W,
@ -508,7 +476,7 @@ where
.with_context(|| format!("At address {:#010X}", sym_addr))?;
entry = entry_iter.next();
} else if current_address > sym_addr {
let dbg_symbols = vec.iter().map(|e| &symbols[e.index as usize]).collect_vec();
let dbg_symbols = vec.iter().map(|e| &symbols[e.index]).collect_vec();
bail!(
"Unaligned symbol entry @ {:#010X}:\n\t{:?}",
section.virtual_address.unwrap_or(0) as u32 + sym_addr,
@ -589,7 +557,7 @@ fn find_symbol_kind(
for entry in entries {
match entry.kind {
SymbolEntryKind::Start => {
let new_kind = symbols[entry.index as usize].kind;
let new_kind = symbols[entry.index].kind;
if !matches!(new_kind, ObjSymbolKind::Unknown | ObjSymbolKind::Section) {
ensure!(
!found || new_kind == kind,
@ -615,11 +583,11 @@ fn find_data_kind(
for entry in entries {
match entry.kind {
SymbolEntryKind::Start => {
let new_kind = symbols[entry.index as usize].data_kind;
let new_kind = symbols[entry.index].data_kind;
if !matches!(new_kind, ObjDataKind::Unknown) {
if found && new_kind != kind {
for entry in entries {
log::error!("Symbol {:?}", symbols[entry.index as usize]);
log::error!("Symbol {:?}", symbols[entry.index]);
}
bail!(
"Conflicting data kinds found: {kind:?} and {new_kind:?}",
@ -810,14 +778,14 @@ where
ObjRelocKind::Absolute => {
// Attempt to use .rel macro for relative relocations
if reloc.addend != 0 {
let target = &symbols[reloc.target_symbol as usize];
let target = &symbols[reloc.target_symbol];
let target_addr = (target.address as i64 + reloc.addend) as u32;
if let Some(entry) = target
.section
.and_then(|section_idx| section_entries[section_idx as usize].get(&target_addr))
.and_then(|section_idx| section_entries[section_idx].get(&target_addr))
.and_then(|entries| entries.iter().find(|e| e.kind == SymbolEntryKind::Label))
{
let symbol = &symbols[entry.index as usize];
let symbol = &symbols[entry.index];
write!(w, "\t.rel ")?;
write_symbol_name(w, &target.name)?;
write!(w, ", ")?;
@ -963,7 +931,7 @@ fn write_reloc_symbol<W>(
where
W: Write + ?Sized,
{
write_symbol_name(w, &symbols[reloc.target_symbol as usize].name)?;
write_symbol_name(w, &symbols[reloc.target_symbol].name)?;
match reloc.addend.cmp(&0i64) {
Ordering::Greater => write!(w, "+{:#X}", reloc.addend),
Ordering::Less => write!(w, "-{:#X}", -reloc.addend),

View File

@ -2,6 +2,7 @@ use std::{
fs,
io::{BufRead, Write},
num::ParseIntError,
path::Path,
str::FromStr,
};
@ -11,20 +12,18 @@ use filetime::FileTime;
use once_cell::sync::Lazy;
use regex::{Captures, Regex};
use tracing::{debug, info, warn};
use typed_path::Utf8NativePath;
use xxhash_rust::xxh3::xxh3_64;
use crate::{
analysis::cfa::SectionAddress,
obj::{
ObjDataKind, ObjInfo, ObjKind, ObjSectionKind, ObjSplit, ObjSymbol, ObjSymbolFlagSet,
ObjSymbolFlags, ObjSymbolKind, ObjUnit, SectionIndex,
ObjSymbolFlags, ObjSymbolKind, ObjUnit,
},
util::{
file::{buf_writer, FileReadInfo},
file::{buf_writer, map_file, FileReadInfo},
split::default_section_align,
},
vfs::open_file,
};
pub fn parse_u32(s: &str) -> Result<u32, ParseIntError> {
@ -35,24 +34,12 @@ pub fn parse_u32(s: &str) -> Result<u32, ParseIntError> {
}
}
pub fn parse_i32(s: &str) -> Result<i32, ParseIntError> {
if let Some(s) = s.strip_prefix("-0x").or_else(|| s.strip_prefix("-0X")) {
i32::from_str_radix(s, 16).map(|v| -v)
} else if let Some(s) = s.strip_prefix("0x").or_else(|| s.strip_prefix("0X")) {
i32::from_str_radix(s, 16)
} else {
s.parse::<i32>()
}
}
pub fn apply_symbols_file(
path: &Utf8NativePath,
obj: &mut ObjInfo,
) -> Result<Option<FileReadInfo>> {
Ok(if fs::metadata(path).is_ok_and(|m| m.is_file()) {
let mut file = open_file(path, true)?;
let cached = FileReadInfo::new(file.as_mut())?;
for result in file.lines() {
pub fn apply_symbols_file<P>(path: P, obj: &mut ObjInfo) -> Result<Option<FileReadInfo>>
where P: AsRef<Path> {
Ok(if path.as_ref().is_file() {
let file = map_file(path)?;
let cached = FileReadInfo::new(&file)?;
for result in file.as_reader().lines() {
let line = match result {
Ok(line) => line,
Err(e) => bail!("Failed to process symbols file: {e:?}"),
@ -200,21 +187,19 @@ pub fn is_auto_label(symbol: &ObjSymbol) -> bool { symbol.name.starts_with("lbl_
pub fn is_auto_jump_table(symbol: &ObjSymbol) -> bool { symbol.name.starts_with("jumptable_") }
fn write_if_unchanged<Cb>(
path: &Utf8NativePath,
cb: Cb,
cached_file: Option<FileReadInfo>,
) -> Result<()>
fn write_if_unchanged<P, Cb>(path: P, cb: Cb, cached_file: Option<FileReadInfo>) -> Result<()>
where
P: AsRef<Path>,
Cb: FnOnce(&mut dyn Write) -> Result<()>,
{
if let Some(cached_file) = cached_file {
// Check file mtime
let path = path.as_ref();
let new_mtime = fs::metadata(path).ok().map(|m| FileTime::from_last_modification_time(&m));
if let (Some(new_mtime), Some(old_mtime)) = (new_mtime, cached_file.mtime) {
if new_mtime != old_mtime {
if let Some(new_mtime) = new_mtime {
if new_mtime != cached_file.mtime {
// File changed, don't write
warn!(path = %path, "File changed since read, not updating");
warn!(path = %path.display(), "File changed since read, not updating");
return Ok(());
}
}
@ -224,12 +209,12 @@ where
cb(&mut buf)?;
if xxh3_64(&buf) == cached_file.hash {
// No changes
debug!(path = %path, "File unchanged");
debug!(path = %path.display(), "File unchanged");
return Ok(());
}
// Write to file
info!("Writing updated {}", path);
info!("Writing updated {}", path.display());
fs::write(path, &buf)?;
} else {
// Write directly
@ -241,11 +226,14 @@ where
}
#[inline]
pub fn write_symbols_file(
path: &Utf8NativePath,
pub fn write_symbols_file<P>(
path: P,
obj: &ObjInfo,
cached_file: Option<FileReadInfo>,
) -> Result<()> {
) -> Result<()>
where
P: AsRef<Path>,
{
write_if_unchanged(path, |w| write_symbols(w, obj), cached_file)
}
@ -413,12 +401,15 @@ fn section_kind_to_str(kind: ObjSectionKind) -> &'static str {
}
#[inline]
pub fn write_splits_file(
path: &Utf8NativePath,
pub fn write_splits_file<P>(
path: P,
obj: &ObjInfo,
all: bool,
cached_file: Option<FileReadInfo>,
) -> Result<()> {
) -> Result<()>
where
P: AsRef<Path>,
{
write_if_unchanged(path, |w| write_splits(w, obj, all), cached_file)
}
@ -437,9 +428,6 @@ where W: Write + ?Sized {
if let Some(comment_version) = unit.comment_version {
write!(w, " comment:{}", comment_version)?;
}
if let Some(order) = unit.order {
write!(w, " order:{}", order)?;
}
writeln!(w)?;
let mut split_iter = obj.sections.all_splits().peekable();
while let Some((_section_index, section, addr, split)) = split_iter.next() {
@ -487,8 +475,6 @@ struct SplitUnit {
name: String,
/// MW `.comment` section version
comment_version: Option<u8>,
/// Influences the order of this unit relative to other ordered units.
order: Option<i32>,
}
pub struct SectionDef {
@ -529,13 +515,12 @@ fn parse_unit_line(captures: Captures) -> Result<SplitLine> {
if name == "Sections" {
return Ok(SplitLine::SectionsStart);
}
let mut unit = SplitUnit { name: name.to_string(), comment_version: None, order: None };
let mut unit = SplitUnit { name: name.to_string(), comment_version: None };
for attr in captures["attrs"].split(' ').filter(|&s| !s.is_empty()) {
if let Some((attr, value)) = attr.split_once(':') {
match attr {
"comment" => unit.comment_version = Some(u8::from_str(value)?),
"order" => unit.order = Some(parse_i32(value)?),
_ => bail!("Unknown unit attribute '{}'", attr),
}
} else {
@ -618,15 +603,16 @@ fn parse_section_line(captures: Captures, state: &SplitState) -> Result<SplitLin
enum SplitState {
None,
Sections(SectionIndex),
Sections(usize),
Unit(String),
}
pub fn apply_splits_file(path: &Utf8NativePath, obj: &mut ObjInfo) -> Result<Option<FileReadInfo>> {
Ok(if fs::metadata(path).is_ok_and(|m| m.is_file()) {
let mut file = open_file(path, true)?;
let cached = FileReadInfo::new(file.as_mut())?;
apply_splits(file.as_mut(), obj)?;
pub fn apply_splits_file<P>(path: P, obj: &mut ObjInfo) -> Result<Option<FileReadInfo>>
where P: AsRef<Path> {
Ok(if path.as_ref().is_file() {
let file = map_file(path)?;
let cached = FileReadInfo::new(&file)?;
apply_splits(&mut file.as_reader(), obj)?;
Some(cached)
} else {
None
@ -645,13 +631,12 @@ where R: BufRead + ?Sized {
match (&mut state, split_line) {
(
SplitState::None | SplitState::Unit(_) | SplitState::Sections(_),
SplitLine::Unit(SplitUnit { name, comment_version, order }),
SplitLine::Unit(SplitUnit { name, comment_version }),
) => {
obj.link_order.push(ObjUnit {
name: name.clone(),
autogenerated: false,
comment_version,
order,
});
state = SplitState::Unit(name);
}
@ -733,14 +718,16 @@ where R: BufRead + ?Sized {
Ok(())
}
pub fn read_splits_sections(path: &Utf8NativePath) -> Result<Option<Vec<SectionDef>>> {
if !fs::metadata(path).is_ok_and(|m| m.is_file()) {
pub fn read_splits_sections<P>(path: P) -> Result<Option<Vec<SectionDef>>>
where P: AsRef<Path> {
if !path.as_ref().is_file() {
return Ok(None);
}
let file = open_file(path, true)?;
let file = map_file(path)?;
let r = file.as_reader();
let mut sections = Vec::new();
let mut state = SplitState::None;
for result in file.lines() {
for result in r.lines() {
let line = match result {
Ok(line) => line,
Err(e) => return Err(e.into()),

View File

@ -1,39 +1,40 @@
use std::io::Write;
use std::{
io::Write,
path::{Path, PathBuf},
};
use itertools::Itertools;
use typed_path::{Utf8NativePath, Utf8NativePathBuf, Utf8UnixPathBuf};
use path_slash::PathBufExt;
use crate::util::file::split_path;
pub struct DepFile {
pub name: Utf8UnixPathBuf,
pub dependencies: Vec<Utf8UnixPathBuf>,
}
fn normalize_path(path: Utf8NativePathBuf) -> Utf8UnixPathBuf {
if let Some((a, _)) = path.as_str().split_once(':') {
Utf8NativePath::new(a).with_unix_encoding()
} else {
path.with_unix_encoding()
}
pub name: PathBuf,
pub dependencies: Vec<PathBuf>,
}
impl DepFile {
pub fn new(name: Utf8NativePathBuf) -> Self {
Self { name: name.with_unix_encoding(), dependencies: vec![] }
pub fn new(name: PathBuf) -> Self { Self { name, dependencies: vec![] } }
pub fn push<P>(&mut self, dependency: P)
where P: AsRef<Path> {
let path = split_path(dependency.as_ref())
.map(|(p, _)| p)
.unwrap_or_else(|_| dependency.as_ref().to_path_buf());
self.dependencies.push(path);
}
pub fn push(&mut self, dependency: Utf8NativePathBuf) {
self.dependencies.push(normalize_path(dependency));
}
pub fn extend(&mut self, dependencies: Vec<Utf8NativePathBuf>) {
self.dependencies.extend(dependencies.into_iter().map(normalize_path));
pub fn extend(&mut self, dependencies: Vec<PathBuf>) {
self.dependencies.extend(dependencies.iter().map(|dependency| {
split_path(dependency).map(|(p, _)| p).unwrap_or_else(|_| dependency.clone())
}));
}
pub fn write<W>(&self, w: &mut W) -> std::io::Result<()>
where W: Write + ?Sized {
write!(w, "{}:", self.name)?;
write!(w, "{}:", self.name.to_slash_lossy())?;
for dep in self.dependencies.iter().unique() {
write!(w, " \\\n {}", dep.as_str().replace(' ', "\\ "))?;
write!(w, " \\\n {}", dep.to_slash_lossy().replace(' ', "\\ "))?;
}
Ok(())
}

View File

@ -1,5 +1,4 @@
//! This includes helpers to convert between decomp-toolkit types and objdiff-core types.
//!
//! Eventually it'd be nice to share [ObjInfo] and related types between decomp-toolkit and
//! objdiff-core to avoid this conversion.
use std::{
@ -29,7 +28,7 @@ pub fn process_code(
section: &ObjSection,
config: &DiffObjConfig,
) -> Result<ProcessCodeResult> {
let arch = objdiff_core::arch::ppc::ObjArchPpc { extab: None };
let arch = objdiff_core::arch::ppc::ObjArchPpc {};
let orig_relocs = section
.relocations
.range(symbol.address as u32..symbol.address as u32 + symbol.size as u32)
@ -40,7 +39,7 @@ pub fn process_code(
arch.process_code(
symbol.address,
orig_data,
section.elf_index as usize,
section.elf_index,
&orig_relocs,
&Default::default(),
config,
@ -225,11 +224,8 @@ fn print_line(ins_diff: &ObjInsDiff, base_addr: u64) -> Vec<Span> {
base_color = COLOR_ROTATION[diff.idx % COLOR_ROTATION.len()]
}
}
DiffText::BranchDest(addr, diff) => {
DiffText::BranchDest(addr) => {
label_text = format!("{addr:x}");
if let Some(diff) = diff {
base_color = COLOR_ROTATION[diff.idx % COLOR_ROTATION.len()]
}
}
DiffText::Symbol(sym) => {
let name = sym.demangled_name.as_ref().unwrap_or(&sym.name);
@ -291,7 +287,6 @@ fn to_objdiff_symbol(
if symbol.flags.is_hidden() {
flags.0 |= objdiff_core::obj::ObjSymbolFlags::Hidden;
}
let bytes = section.and_then(|s| s.symbol_data(symbol).ok()).map_or(vec![], |d| d.to_vec());
objdiff_core::obj::ObjSymbol {
name: symbol.name.clone(),
demangled_name: symbol.demangled_name.clone(),
@ -302,8 +297,6 @@ fn to_objdiff_symbol(
flags,
addend,
virtual_address: None,
original_index: None,
bytes,
}
}

View File

@ -11,7 +11,7 @@ use crate::{
array_ref,
obj::{
ObjArchitecture, ObjInfo, ObjKind, ObjSection, ObjSectionKind, ObjSymbol, ObjSymbolFlagSet,
ObjSymbolFlags, ObjSymbolKind, SectionIndex,
ObjSymbolFlags, ObjSymbolKind,
},
util::{
alf::{AlfFile, AlfSymbol, ALF_MAGIC},
@ -75,7 +75,7 @@ pub struct DolSection {
pub size: u32,
pub kind: DolSectionKind,
// TODO remove
pub index: SectionIndex,
pub index: usize,
}
#[derive(Debug, Clone)]
@ -103,7 +103,7 @@ impl FromReader for DolFile {
data_size: size,
size,
kind: DolSectionKind::Text,
index: sections.len() as SectionIndex,
index: sections.len(),
});
}
for (idx, &size) in header.data_sizes.iter().enumerate() {
@ -116,7 +116,7 @@ impl FromReader for DolFile {
data_size: size,
size,
kind: DolSectionKind::Data,
index: sections.len() as SectionIndex,
index: sections.len(),
});
}
sections.push(DolSection {
@ -125,7 +125,7 @@ impl FromReader for DolFile {
data_size: 0,
size: header.bss_size,
kind: DolSectionKind::Bss,
index: sections.len() as SectionIndex,
index: sections.len(),
});
Ok(Self { header, sections })
}
@ -286,8 +286,8 @@ pub fn process_dol(buf: &[u8], name: &str) -> Result<ObjInfo> {
dol.sections().iter().filter(|section| section.kind == DolSectionKind::Text).count();
let mut eti_entries: Vec<EtiEntry> = Vec::new();
let mut eti_init_info_range: Option<(u32, u32)> = None;
let mut extab_section: Option<SectionIndex> = None;
let mut extabindex_section: Option<SectionIndex> = None;
let mut extab_section: Option<usize> = None;
let mut extabindex_section: Option<usize> = None;
'outer: for dol_section in
dol.sections().iter().filter(|section| section.kind == DolSectionKind::Data)
{
@ -537,9 +537,8 @@ pub fn process_dol(buf: &[u8], name: &str) -> Result<ObjInfo> {
}
// Apply section indices
let mut init_section_index: Option<SectionIndex> = None;
let mut init_section_index = None;
for (idx, section) in sections.iter_mut().enumerate() {
let idx = idx as SectionIndex;
match section.name.as_str() {
".init" => {
init_section_index = Some(idx);

View File

@ -274,7 +274,6 @@ pub enum AttributeKind {
Prototyped = 0x0270 | (FormKind::String as u16),
Public = 0x0280 | (FormKind::String as u16),
PureVirtual = 0x0290 | (FormKind::String as u16),
PureVirtualBlock2 = 0x0290 | (FormKind::Block2 as u16),
ReturnAddr = 0x02a0 | (FormKind::Block2 as u16),
Specification = 0x02b0 | (FormKind::Ref as u16),
StartScope = 0x02c0 | (FormKind::Data4 as u16),
@ -284,7 +283,6 @@ pub enum AttributeKind {
UpperBoundData4 = 0x02f0 | (FormKind::Data4 as u16),
UpperBoundData8 = 0x02f0 | (FormKind::Data8 as u16),
Virtual = 0x0300 | (FormKind::String as u16),
VirtualBlock2 = 0x0300 | (FormKind::Block2 as u16),
LoUser = 0x2000,
HiUser = 0x3ff0,
// User types

View File

@ -12,7 +12,7 @@ use indexmap::IndexMap;
use objdiff_core::obj::split_meta::{SplitMeta, SHT_SPLITMETA, SPLITMETA_SECTION};
use object::{
elf,
elf::{SHF_ALLOC, SHF_EXECINSTR, SHF_WRITE, SHT_LOUSER, SHT_NOBITS, SHT_PROGBITS},
elf::{SHF_ALLOC, SHF_EXECINSTR, SHF_WRITE, SHT_NOBITS, SHT_PROGBITS},
write::{
elf::{ProgramHeader, Rel, SectionHeader, SectionIndex, SymbolIndex, Writer},
StringId,
@ -20,24 +20,20 @@ use object::{
Architecture, Endianness, Object, ObjectKind, ObjectSection, ObjectSymbol, Relocation,
RelocationFlags, RelocationTarget, SectionKind, Symbol, SymbolKind, SymbolScope, SymbolSection,
};
use typed_path::Utf8NativePath;
use crate::{
array_ref,
obj::{
ObjArchitecture, ObjInfo, ObjKind, ObjReloc, ObjRelocKind, ObjSection, ObjSectionKind,
ObjSplit, ObjSymbol, ObjSymbolFlagSet, ObjSymbolFlags, ObjSymbolKind, ObjUnit,
SectionIndex as ObjSectionIndex, SymbolIndex as ObjSymbolIndex,
},
util::{
comment::{CommentSym, MWComment},
file::map_file,
reader::{Endian, FromReader, ToWriter},
},
vfs::open_file,
};
pub const SHT_MWCATS: u32 = SHT_LOUSER + 0x4A2A82C2;
enum BoundaryState {
/// Looking for a file symbol, any section symbols are queued
LookForFile(Vec<(u64, String)>),
@ -47,9 +43,10 @@ enum BoundaryState {
FilesEnded,
}
pub fn process_elf(path: &Utf8NativePath) -> Result<ObjInfo> {
let mut file = open_file(path, true)?;
let obj_file = object::read::File::parse(file.map()?)?;
pub fn process_elf<P>(path: P) -> Result<ObjInfo>
where P: AsRef<Path> {
let file = map_file(path)?;
let obj_file = object::read::File::parse(file.as_slice())?;
let architecture = match obj_file.architecture() {
Architecture::PowerPc => ObjArchitecture::PowerPc,
arch => bail!("Unexpected architecture: {arch:?}"),
@ -71,7 +68,7 @@ pub fn process_elf(path: &Utf8NativePath) -> Result<ObjInfo> {
let mut sda2_base: Option<u32> = None;
let mut sections: Vec<ObjSection> = vec![];
let mut section_indexes: Vec<Option<usize>> = vec![None /* ELF null section */];
let mut section_indexes: Vec<Option<usize>> = vec![];
for section in obj_file.sections() {
if section.size() == 0 {
section_indexes.push(None);
@ -97,7 +94,7 @@ pub fn process_elf(path: &Utf8NativePath) -> Result<ObjInfo> {
size: section.size(),
data: section.uncompressed_data()?.to_vec(),
align: section.align(),
elf_index: section.index().0 as ObjSectionIndex,
elf_index: section.index().0,
relocations: Default::default(),
virtual_address: None, // Loaded from section symbol
file_offset: section.file_range().map(|(v, _)| v).unwrap_or_default(),
@ -116,7 +113,6 @@ pub fn process_elf(path: &Utf8NativePath) -> Result<ObjInfo> {
.context("While reading .comment section")?;
log::debug!("Loaded .comment section header {:?}", header);
let mut comment_syms = Vec::with_capacity(obj_file.symbols().count());
comment_syms.push(CommentSym::from_reader(&mut reader, Endian::Big)?); // ELF null symbol
for symbol in obj_file.symbols() {
let comment_sym = CommentSym::from_reader(&mut reader, Endian::Big)?;
log::debug!("Symbol {:?} -> Comment {:?}", symbol, comment_sym);
@ -151,7 +147,7 @@ pub fn process_elf(path: &Utf8NativePath) -> Result<ObjInfo> {
};
let mut symbols: Vec<ObjSymbol> = vec![];
let mut symbol_indexes: Vec<Option<ObjSymbolIndex>> = vec![None /* ELF null symbol */];
let mut symbol_indexes: Vec<Option<usize>> = vec![];
let mut section_starts = IndexMap::<String, Vec<(u64, String)>>::new();
let mut name_to_index = HashMap::<String, usize>::new(); // for resolving duplicate names
let mut boundary_state = BoundaryState::LookForFile(Default::default());
@ -239,9 +235,7 @@ pub fn process_elf(path: &Utf8NativePath) -> Result<ObjInfo> {
.and_then(|m| m.virtual_addresses.as_ref())
.and_then(|v| v.get(symbol.index().0).cloned())
{
if let Some(section_index) = section_indexes[section_index.0] {
sections[section_index].virtual_address = Some(addr);
}
sections[section_index.0].virtual_address = Some(addr);
}
let section = obj_file.section_by_index(section_index)?;
@ -303,13 +297,13 @@ pub fn process_elf(path: &Utf8NativePath) -> Result<ObjInfo> {
}
// Generate symbols
if matches!(symbol.kind(), SymbolKind::File)
if matches!(symbol.kind(), SymbolKind::Null | SymbolKind::File)
|| matches!(symbol.section_index(), Some(idx) if section_indexes[idx.0].is_none())
{
symbol_indexes.push(None);
continue;
}
symbol_indexes.push(Some(symbols.len() as ObjSymbolIndex));
symbol_indexes.push(Some(symbols.len()));
let align = mw_comment.as_ref().map(|(_, vec)| vec[symbol.index().0].align);
symbols.push(to_obj_symbol(&obj_file, &symbol, &section_indexes, align)?);
}
@ -322,7 +316,6 @@ pub fn process_elf(path: &Utf8NativePath) -> Result<ObjInfo> {
name: file_name.clone(),
autogenerated: false,
comment_version: None,
order: None,
});
}
@ -352,12 +345,11 @@ pub fn process_elf(path: &Utf8NativePath) -> Result<ObjInfo> {
// TODO rebuild common symbols
}
for section in obj_file.sections() {
let out_section =
match section_indexes[section.index().0].and_then(|idx| sections.get_mut(idx)) {
Some(s) => s,
None => continue,
};
for (section_idx, section) in obj_file.sections().enumerate() {
let out_section = match section_indexes[section_idx].and_then(|idx| sections.get_mut(idx)) {
Some(s) => s,
None => continue,
};
// Generate relocations
for (address, reloc) in section.relocations() {
let Some(reloc) =
@ -404,7 +396,7 @@ pub fn write_elf(obj: &ObjInfo, export_all: bool) -> Result<Vec<u8>> {
}
writer.reserve_null_section_index();
let mut out_sections: Vec<OutSection> = Vec::with_capacity(obj.sections.len() as usize);
let mut out_sections: Vec<OutSection> = Vec::with_capacity(obj.sections.len());
for (_, section) in obj.sections.iter() {
let name = writer.add_section_name(section.name.as_bytes());
let index = writer.reserve_section_index();
@ -419,7 +411,7 @@ pub fn write_elf(obj: &ObjInfo, export_all: bool) -> Result<Vec<u8>> {
});
}
let mut rela_names: Vec<String> = vec![Default::default(); obj.sections.len() as usize];
let mut rela_names: Vec<String> = vec![Default::default(); obj.sections.len()];
for (((_, section), out_section), rela_name) in
obj.sections.iter().zip(&mut out_sections).zip(&mut rela_names)
{
@ -452,7 +444,7 @@ pub fn write_elf(obj: &ObjInfo, export_all: bool) -> Result<Vec<u8>> {
});
// Generate .comment data
let mut comment_data = Vec::<u8>::with_capacity(0x2C + obj.symbols.count() as usize * 8);
let mut comment_data = Vec::<u8>::with_capacity(0x2C + obj.symbols.count() * 8);
mw_comment.to_writer_static(&mut comment_data, Endian::Big)?;
// Null symbol
CommentSym { align: 0, vis_flags: 0, active_flags: 0 }
@ -463,7 +455,7 @@ pub fn write_elf(obj: &ObjInfo, export_all: bool) -> Result<Vec<u8>> {
};
// Generate .note.split section
let mut split_meta = if let (Some(metadata), Some(_)) = (&obj.split_meta, &obj.mw_comment) {
let mut split_meta = if let Some(metadata) = &obj.split_meta {
// Reserve section
let name = writer.add_section_name(SPLITMETA_SECTION.as_bytes());
let index = writer.reserve_section_index();
@ -488,8 +480,8 @@ pub fn write_elf(obj: &ObjInfo, export_all: bool) -> Result<Vec<u8>> {
None
};
let mut out_symbols: Vec<OutSymbol> = Vec::with_capacity(obj.symbols.count() as usize);
let mut symbol_map = vec![None; obj.symbols.count() as usize];
let mut out_symbols: Vec<OutSymbol> = Vec::with_capacity(obj.symbols.count());
let mut symbol_map = vec![None; obj.symbols.count()];
let mut section_symbol_offset = 0;
let mut num_local = 0;
@ -535,7 +527,7 @@ pub fn write_elf(obj: &ObjInfo, export_all: bool) -> Result<Vec<u8>> {
// Add section symbols for relocatable objects
if obj.kind == ObjKind::Relocatable {
for (section_index, section) in obj.sections.iter() {
let out_section_index = out_sections.get(section_index as usize).map(|s| s.index);
let out_section_index = out_sections.get(section_index).map(|s| s.index);
let index = writer.reserve_symbol_index(out_section_index);
let sym = object::write::elf::Sym {
name: None,
@ -564,19 +556,19 @@ pub fn write_elf(obj: &ObjInfo, export_all: bool) -> Result<Vec<u8>> {
for (symbol_index, symbol) in obj
.symbols
.iter()
.enumerate()
.filter(|&(_, s)| s.flags.is_local())
.chain(obj.symbols.iter().filter(|&(_, s)| !s.flags.is_local()))
.chain(obj.symbols.iter().enumerate().filter(|&(_, s)| !s.flags.is_local()))
{
if obj.kind == ObjKind::Relocatable && symbol.kind == ObjSymbolKind::Section {
// We wrote section symbols above, so skip them here
let section_index =
symbol.section.ok_or_else(|| anyhow!("section symbol without section index"))?;
symbol_map[symbol_index as usize] =
Some(section_symbol_offset as ObjSectionIndex + section_index);
symbol_map[symbol_index] = Some(section_symbol_offset + section_index as u32);
continue;
}
let section = symbol.section.and_then(|idx| out_sections.get(idx as usize));
let section = symbol.section.and_then(|idx| out_sections.get(idx));
let section_index = section.map(|s| s.index);
let index = writer.reserve_symbol_index(section_index);
let name_index = if symbol.name.is_empty() {
@ -620,7 +612,7 @@ pub fn write_elf(obj: &ObjInfo, export_all: bool) -> Result<Vec<u8>> {
num_local = writer.symbol_count();
}
out_symbols.push(OutSymbol { index, sym });
symbol_map[symbol_index as usize] = Some(index.0);
symbol_map[symbol_index] = Some(index.0);
if let Some((comment_data, _)) = &mut comment_data {
CommentSym::from(symbol, export_all).to_writer_static(comment_data, Endian::Big)?;
}
@ -638,7 +630,7 @@ pub fn write_elf(obj: &ObjInfo, export_all: bool) -> Result<Vec<u8>> {
writer.reserve_file_header();
if obj.kind == ObjKind::Executable {
writer.reserve_program_headers(obj.sections.len());
writer.reserve_program_headers(obj.sections.len() as u32);
}
for ((_, section), out_section) in obj.sections.iter().zip(&mut out_sections) {
@ -737,7 +729,7 @@ pub fn write_elf(obj: &ObjInfo, export_all: bool) -> Result<Vec<u8>> {
ensure!(writer.len() == out_section.rela_offset);
for (addr, reloc) in section.relocations.iter() {
let (r_offset, r_type) = reloc.to_elf(addr);
let r_sym = symbol_map[reloc.target_symbol as usize]
let r_sym = symbol_map[reloc.target_symbol]
.ok_or_else(|| anyhow!("Relocation against stripped symbol"))?;
writer.write_relocation(true, &Rel { r_offset, r_sym, r_type, r_addend: reloc.addend });
}
@ -832,16 +824,9 @@ pub fn write_elf(obj: &ObjInfo, export_all: bool) -> Result<Vec<u8>> {
// Write .note.split section header
if let Some((metadata, idx)) = &split_meta {
let out_section = &out_sections[*idx];
let mut sh_type = SHT_SPLITMETA;
if matches!(&obj.mw_comment, Some(comment) if comment.version < 14) {
// Prior to mwld GC 3.0a3, the linker doesn't support ELF .note sections
// properly. With GC 2.7, it crashes if the section type is SHT_NOTE.
// Use the same section type as .mwcats.* so the linker ignores it.
sh_type = SHT_MWCATS;
}
writer.write_section_header(&SectionHeader {
name: Some(out_section.name),
sh_type,
sh_type: SHT_SPLITMETA,
sh_flags: 0,
sh_addr: 0,
sh_offset: out_section.offset as u64,
@ -896,7 +881,7 @@ fn to_obj_symbol(
name: name.to_string(),
demangled_name: demangle(name, &Default::default()),
address: symbol.address(),
section: section_idx.map(|s| s as ObjSectionIndex),
section: section_idx,
size: symbol.size(),
size_known: true,
flags,
@ -930,7 +915,7 @@ pub fn to_obj_reloc_kind(flags: RelocationFlags) -> Result<ObjRelocKind> {
fn to_obj_reloc(
obj_file: &object::File<'_>,
symbol_indexes: &[Option<ObjSymbolIndex>],
symbol_indexes: &[Option<usize>],
section_data: &[u8],
address: u64,
reloc: Relocation,

View File

@ -1,48 +1,234 @@
use std::{
fs,
ffi::OsStr,
fs::{DirBuilder, File, OpenOptions},
io,
io::{BufRead, BufWriter, Read, Seek, SeekFrom, Write},
io::{BufRead, BufReader, BufWriter, Cursor, Read, Seek, SeekFrom},
path::{Component, Path, PathBuf},
};
use anyhow::{anyhow, Context, Result};
use anyhow::{anyhow, bail, Context, Result};
use filetime::{set_file_mtime, FileTime};
use memmap2::{Mmap, MmapOptions};
use path_slash::PathBufExt;
use rarc::RarcReader;
use sha1::{Digest, Sha1};
use typed_path::{Utf8NativePath, Utf8NativePathBuf, Utf8UnixPathBuf};
use xxhash_rust::xxh3::xxh3_64;
use crate::{
array_ref,
util::{
ncompress::{decompress_yay0, decompress_yaz0, YAY0_MAGIC, YAZ0_MAGIC},
path::check_path_buf,
rarc,
rarc::{Node, RARC_MAGIC},
take_seek::{TakeSeek, TakeSeekExt},
u8_arc::{U8View, U8_MAGIC},
Bytes,
},
vfs::{open_file, VfsFile},
};
pub struct MappedFile {
mmap: Mmap,
mtime: FileTime,
offset: u64,
len: u64,
}
impl MappedFile {
pub fn as_reader(&self) -> Cursor<&[u8]> { Cursor::new(self.as_slice()) }
pub fn as_slice(&self) -> &[u8] {
&self.mmap[self.offset as usize..self.offset as usize + self.len as usize]
}
pub fn len(&self) -> u64 { self.len }
pub fn is_empty(&self) -> bool { self.len == 0 }
pub fn into_inner(self) -> Mmap { self.mmap }
}
pub fn split_path<P>(path: P) -> Result<(PathBuf, Option<PathBuf>)>
where P: AsRef<Path> {
let mut base_path = PathBuf::new();
let mut sub_path: Option<PathBuf> = None;
for component in path.as_ref().components() {
if let Component::Normal(str) = component {
let str = str.to_str().ok_or(anyhow!("Path is not valid UTF-8"))?;
if let Some((a, b)) = str.split_once(':') {
base_path.push(a);
sub_path = Some(PathBuf::from(b));
continue;
}
}
if let Some(sub_path) = &mut sub_path {
sub_path.push(component);
} else {
base_path.push(component);
}
}
Ok((base_path, sub_path))
}
/// Opens a memory mapped file, and decompresses it if needed.
pub fn map_file<P>(path: P) -> Result<FileEntry>
where P: AsRef<Path> {
let (base_path, sub_path) = split_path(path.as_ref())?;
let file = File::open(&base_path)
.with_context(|| format!("Failed to open file '{}'", base_path.display()))?;
let mtime = FileTime::from_last_modification_time(&file.metadata()?);
let mmap = unsafe { MmapOptions::new().map(&file) }
.with_context(|| format!("Failed to mmap file: '{}'", base_path.display()))?;
let (offset, len) = if let Some(sub_path) = sub_path {
if sub_path.as_os_str() == OsStr::new("nlzss") {
return Ok(FileEntry::Buffer(
nintendo_lz::decompress(&mut mmap.as_ref())
.map_err(|e| {
anyhow!(
"Failed to decompress '{}' with NLZSS: {}",
path.as_ref().display(),
e
)
})?
.into_boxed_slice(),
mtime,
));
} else if sub_path.as_os_str() == OsStr::new("yaz0") {
return Ok(FileEntry::Buffer(
decompress_yaz0(mmap.as_ref()).with_context(|| {
format!("Failed to decompress '{}' with Yaz0", path.as_ref().display())
})?,
mtime,
));
} else if sub_path.as_os_str() == OsStr::new("yay0") {
return Ok(FileEntry::Buffer(
decompress_yay0(mmap.as_ref()).with_context(|| {
format!("Failed to decompress '{}' with Yay0", path.as_ref().display())
})?,
mtime,
));
}
let buf = mmap.as_ref();
match *array_ref!(buf, 0, 4) {
RARC_MAGIC => {
let rarc = RarcReader::new(&mut Cursor::new(mmap.as_ref())).with_context(|| {
format!("Failed to open '{}' as RARC archive", base_path.display())
})?;
let (offset, size) = rarc.find_file(&sub_path)?.ok_or_else(|| {
anyhow!("File '{}' not found in '{}'", sub_path.display(), base_path.display())
})?;
(offset, size as u64)
}
U8_MAGIC => {
let arc = U8View::new(buf).map_err(|e| {
anyhow!("Failed to open '{}' as U8 archive: {}", base_path.display(), e)
})?;
let (_, node) = arc.find(sub_path.to_slash_lossy().as_ref()).ok_or_else(|| {
anyhow!("File '{}' not found in '{}'", sub_path.display(), base_path.display())
})?;
(node.offset() as u64, node.length() as u64)
}
_ => bail!("Couldn't detect archive type for '{}'", path.as_ref().display()),
}
} else {
(0, mmap.len() as u64)
};
let map = MappedFile { mmap, mtime, offset, len };
let buf = map.as_slice();
// Auto-detect compression if there's a magic number.
if buf.len() > 4 {
match *array_ref!(buf, 0, 4) {
YAZ0_MAGIC => {
return Ok(FileEntry::Buffer(
decompress_yaz0(buf).with_context(|| {
format!("Failed to decompress '{}' with Yaz0", path.as_ref().display())
})?,
mtime,
));
}
YAY0_MAGIC => {
return Ok(FileEntry::Buffer(
decompress_yay0(buf).with_context(|| {
format!("Failed to decompress '{}' with Yay0", path.as_ref().display())
})?,
mtime,
));
}
_ => {}
}
}
Ok(FileEntry::MappedFile(map))
}
/// Opens a memory mapped file without decompression or archive handling.
pub fn map_file_basic<P>(path: P) -> Result<FileEntry>
where P: AsRef<Path> {
let path = path.as_ref();
let file =
File::open(path).with_context(|| format!("Failed to open file '{}'", path.display()))?;
let mtime = FileTime::from_last_modification_time(&file.metadata()?);
let mmap = unsafe { MmapOptions::new().map(&file) }
.with_context(|| format!("Failed to mmap file: '{}'", path.display()))?;
let len = mmap.len() as u64;
Ok(FileEntry::MappedFile(MappedFile { mmap, mtime, offset: 0, len }))
}
pub type OpenedFile = TakeSeek<File>;
/// Opens a file (not memory mapped). No decompression is performed.
pub fn open_file<P>(path: P) -> Result<OpenedFile>
where P: AsRef<Path> {
let (base_path, sub_path) = split_path(path)?;
let mut file = File::open(&base_path)
.with_context(|| format!("Failed to open file '{}'", base_path.display()))?;
let (offset, size) = if let Some(sub_path) = sub_path {
let rarc = RarcReader::new(&mut BufReader::new(&file))
.with_context(|| format!("Failed to read RARC '{}'", base_path.display()))?;
rarc.find_file(&sub_path)?.map(|(o, s)| (o, s as u64)).ok_or_else(|| {
anyhow!("File '{}' not found in '{}'", sub_path.display(), base_path.display())
})?
} else {
(0, file.seek(SeekFrom::End(0))?)
};
file.seek(SeekFrom::Start(offset))?;
Ok(file.take_seek(size))
}
pub trait Reader: BufRead + Seek {}
impl Reader for Cursor<&[u8]> {}
/// Creates a buffered reader around a file (not memory mapped).
pub fn buf_reader<P>(path: P) -> Result<BufReader<File>>
where P: AsRef<Path> {
let file = File::open(&path)
.with_context(|| format!("Failed to open file '{}'", path.as_ref().display()))?;
Ok(BufReader::new(file))
}
/// Creates a buffered writer around a file (not memory mapped).
pub fn buf_writer(path: &Utf8NativePath) -> Result<BufWriter<File>> {
if let Some(parent) = path.parent() {
pub fn buf_writer<P>(path: P) -> Result<BufWriter<File>>
where P: AsRef<Path> {
if let Some(parent) = path.as_ref().parent() {
DirBuilder::new().recursive(true).create(parent)?;
}
let file = File::create(path).with_context(|| format!("Failed to create file '{}'", path))?;
let file = File::create(&path)
.with_context(|| format!("Failed to create file '{}'", path.as_ref().display()))?;
Ok(BufWriter::new(file))
}
/// Reads a string with known size at the specified offset.
pub fn read_string<R>(reader: &mut R, off: u64, size: usize) -> io::Result<String>
pub fn read_string<R>(reader: &mut R, off: u64, size: usize) -> Result<String>
where R: Read + Seek + ?Sized {
let mut data = vec![0u8; size];
let pos = reader.stream_position()?;
reader.seek(SeekFrom::Start(off))?;
reader.read_exact(&mut data)?;
reader.seek(SeekFrom::Start(pos))?;
String::from_utf8(data).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
Ok(String::from_utf8(data)?)
}
/// Reads a zero-terminated string at the specified offset.
pub fn read_c_string<R>(reader: &mut R, off: u64) -> io::Result<String>
pub fn read_c_string<R>(reader: &mut R, off: u64) -> Result<String>
where R: Read + Seek + ?Sized {
let pos = reader.stream_position()?;
reader.seek(SeekFrom::Start(off))?;
@ -60,21 +246,22 @@ where R: Read + Seek + ?Sized {
}
/// Process response files (starting with '@') and glob patterns (*).
pub fn process_rsp(files: &[Utf8NativePathBuf]) -> Result<Vec<Utf8NativePathBuf>> {
let mut out = Vec::<Utf8NativePathBuf>::with_capacity(files.len());
pub fn process_rsp(files: &[PathBuf]) -> Result<Vec<PathBuf>> {
let mut out = Vec::with_capacity(files.len());
for path in files {
if let Some(rsp_file) = path.as_str().strip_prefix('@') {
let file = open_file(Utf8NativePath::new(rsp_file), true)?;
for result in file.lines() {
let path_str =
path.to_str().ok_or_else(|| anyhow!("'{}' is not valid UTF-8", path.display()))?;
if let Some(rsp_file) = path_str.strip_prefix('@') {
let reader = buf_reader(rsp_file)?;
for result in reader.lines() {
let line = result?;
if !line.is_empty() {
out.push(Utf8UnixPathBuf::from(line).with_encoding());
out.push(PathBuf::from_slash(line));
}
}
} else if path.as_str().contains('*') {
for entry in glob::glob(path.as_str())? {
let path = check_path_buf(entry?)?;
out.push(path.with_encoding());
} else if path_str.contains('*') {
for entry in glob::glob(path_str)? {
out.push(entry?);
}
} else {
out.push(path.clone());
@ -83,20 +270,120 @@ pub fn process_rsp(files: &[Utf8NativePathBuf]) -> Result<Vec<Utf8NativePathBuf>
Ok(out)
}
/// Iterator over files in a RARC archive.
struct RarcIterator {
file: MappedFile,
base_path: PathBuf,
paths: Vec<(PathBuf, u64, u32)>,
index: usize,
}
impl RarcIterator {
pub fn new(file: MappedFile, base_path: &Path) -> Result<Self> {
let reader = RarcReader::new(&mut file.as_reader())?;
let paths = Self::collect_paths(&reader, base_path);
Ok(Self { file, base_path: base_path.to_owned(), paths, index: 0 })
}
fn collect_paths(reader: &RarcReader, base_path: &Path) -> Vec<(PathBuf, u64, u32)> {
let mut current_path = PathBuf::new();
let mut paths = vec![];
for node in reader.nodes() {
match node {
Node::DirectoryBegin { name } => {
current_path.push(name.name);
}
Node::DirectoryEnd { name: _ } => {
current_path.pop();
}
Node::File { name, offset, size } => {
let path = base_path.join(&current_path).join(name.name);
paths.push((path, offset, size));
}
Node::CurrentDirectory => {}
Node::ParentDirectory => {}
}
}
paths
}
}
impl Iterator for RarcIterator {
type Item = Result<(PathBuf, Box<[u8]>)>;
fn next(&mut self) -> Option<Self::Item> {
if self.index >= self.paths.len() {
return None;
}
let (path, off, size) = self.paths[self.index].clone();
self.index += 1;
let slice = &self.file.as_slice()[off as usize..off as usize + size as usize];
match decompress_if_needed(slice) {
Ok(buf) => Some(Ok((path, buf.into_owned()))),
Err(e) => Some(Err(e)),
}
}
}
/// A file entry, either a memory mapped file or an owned buffer.
pub enum FileEntry {
MappedFile(MappedFile),
Buffer(Box<[u8]>, FileTime),
}
impl FileEntry {
/// Creates a reader for the file.
pub fn as_reader(&self) -> Cursor<&[u8]> {
match self {
Self::MappedFile(file) => file.as_reader(),
Self::Buffer(slice, _) => Cursor::new(slice),
}
}
pub fn as_slice(&self) -> &[u8] {
match self {
Self::MappedFile(file) => file.as_slice(),
Self::Buffer(slice, _) => slice,
}
}
pub fn len(&self) -> u64 {
match self {
Self::MappedFile(file) => file.len(),
Self::Buffer(slice, _) => slice.len() as u64,
}
}
pub fn is_empty(&self) -> bool {
match self {
Self::MappedFile(file) => file.is_empty(),
Self::Buffer(slice, _) => slice.is_empty(),
}
}
pub fn mtime(&self) -> FileTime {
match self {
Self::MappedFile(file) => file.mtime,
Self::Buffer(_, mtime) => *mtime,
}
}
}
/// Information about a file when it was read.
/// Used to determine if a file has changed since it was read (mtime)
/// and if it needs to be written (hash).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FileReadInfo {
pub mtime: Option<FileTime>,
pub mtime: FileTime,
pub hash: u64,
}
impl FileReadInfo {
pub fn new(entry: &mut dyn VfsFile) -> Result<Self> {
let hash = xxh3_64(entry.map()?);
let metadata = entry.metadata()?;
Ok(Self { mtime: metadata.mtime, hash })
pub fn new(entry: &FileEntry) -> Result<Self> {
let hash = xxh3_64(entry.as_slice());
Ok(Self { mtime: entry.mtime(), hash })
}
}
@ -104,37 +391,108 @@ impl FileReadInfo {
/// If a file is a RARC archive, iterate over its contents.
/// If a file is a Yaz0 compressed file, decompress it.
pub struct FileIterator {
paths: Vec<Utf8NativePathBuf>,
paths: Vec<PathBuf>,
index: usize,
rarc: Option<RarcIterator>,
}
impl FileIterator {
pub fn new(paths: &[Utf8NativePathBuf]) -> Result<Self> {
Ok(Self { paths: process_rsp(paths)?, index: 0 })
pub fn new(paths: &[PathBuf]) -> Result<Self> {
Ok(Self { paths: process_rsp(paths)?, index: 0, rarc: None })
}
fn next_path(&mut self) -> Option<Result<(Utf8NativePathBuf, Box<dyn VfsFile>)>> {
fn next_rarc(&mut self) -> Option<Result<(PathBuf, FileEntry)>> {
if let Some(rarc) = &mut self.rarc {
match rarc.next() {
Some(Ok((path, buf))) => {
let mut path_str = rarc.base_path.as_os_str().to_os_string();
path_str.push(OsStr::new(":"));
path_str.push(path.as_os_str());
return Some(Ok((path, FileEntry::Buffer(buf, rarc.file.mtime))));
}
Some(Err(err)) => return Some(Err(err)),
None => self.rarc = None,
}
}
None
}
fn next_path(&mut self) -> Option<Result<(PathBuf, FileEntry)>> {
if self.index >= self.paths.len() {
return None;
}
let path = self.paths[self.index].clone();
self.index += 1;
match open_file(&path, true) {
Ok(file) => Some(Ok((path, file))),
Err(e) => Some(Err(e)),
match map_file(&path) {
Ok(FileEntry::MappedFile(map)) => self.handle_file(map, path),
Ok(FileEntry::Buffer(_, _)) => todo!(),
Err(err) => Some(Err(err)),
}
}
fn handle_file(
&mut self,
file: MappedFile,
path: PathBuf,
) -> Option<Result<(PathBuf, FileEntry)>> {
let buf = file.as_slice();
if buf.len() <= 4 {
return Some(Ok((path, FileEntry::MappedFile(file))));
}
match *array_ref!(buf, 0, 4) {
YAZ0_MAGIC => self.handle_yaz0(file, path),
YAY0_MAGIC => self.handle_yay0(file, path),
RARC_MAGIC => self.handle_rarc(file, path),
_ => Some(Ok((path, FileEntry::MappedFile(file)))),
}
}
fn handle_yaz0(
&mut self,
file: MappedFile,
path: PathBuf,
) -> Option<Result<(PathBuf, FileEntry)>> {
Some(match decompress_yaz0(file.as_slice()) {
Ok(buf) => Ok((path, FileEntry::Buffer(buf, file.mtime))),
Err(e) => Err(e),
})
}
fn handle_yay0(
&mut self,
file: MappedFile,
path: PathBuf,
) -> Option<Result<(PathBuf, FileEntry)>> {
Some(match decompress_yay0(file.as_slice()) {
Ok(buf) => Ok((path, FileEntry::Buffer(buf, file.mtime))),
Err(e) => Err(e),
})
}
fn handle_rarc(
&mut self,
file: MappedFile,
path: PathBuf,
) -> Option<Result<(PathBuf, FileEntry)>> {
self.rarc = match RarcIterator::new(file, &path) {
Ok(iter) => Some(iter),
Err(e) => return Some(Err(e)),
};
self.next()
}
}
impl Iterator for FileIterator {
type Item = Result<(Utf8NativePathBuf, Box<dyn VfsFile>)>;
type Item = Result<(PathBuf, FileEntry)>;
fn next(&mut self) -> Option<Self::Item> { self.next_path() }
fn next(&mut self) -> Option<Self::Item> { self.next_rarc().or_else(|| self.next_path()) }
}
pub fn touch(path: &Utf8NativePath) -> io::Result<()> {
if fs::exists(path)? {
pub fn touch<P>(path: P) -> std::io::Result<()>
where P: AsRef<Path> {
if path.as_ref().exists() {
set_file_mtime(path, FileTime::now())
} else {
match OpenOptions::new().create(true).truncate(true).write(true).open(path) {
@ -156,16 +514,13 @@ pub fn decompress_if_needed(buf: &[u8]) -> Result<Bytes> {
}
pub fn verify_hash(buf: &[u8], expected_str: &str) -> Result<()> {
let mut hasher = Sha1::new();
hasher.update(buf);
check_hash_str(hasher.finalize().into(), expected_str)
}
pub fn check_hash_str(hash_bytes: [u8; 20], expected_str: &str) -> Result<()> {
let mut expected_bytes = [0u8; 20];
hex::decode_to_slice(expected_str, &mut expected_bytes)
.with_context(|| format!("Invalid SHA-1 '{expected_str}'"))?;
if hash_bytes == expected_bytes {
let mut hasher = Sha1::new();
hasher.update(buf);
let hash_bytes = hasher.finalize();
if hash_bytes.as_ref() == expected_bytes {
Ok(())
} else {
Err(anyhow!(
@ -175,44 +530,3 @@ pub fn check_hash_str(hash_bytes: [u8; 20], expected_str: &str) -> Result<()> {
))
}
}
/// Copies from a buffered reader to a writer without extra allocations.
pub fn buf_copy<R, W>(reader: &mut R, writer: &mut W) -> io::Result<u64>
where
R: BufRead + ?Sized,
W: Write + ?Sized,
{
let mut copied = 0;
loop {
let buf = reader.fill_buf()?;
let len = buf.len();
if len == 0 {
break;
}
writer.write_all(buf)?;
reader.consume(len);
copied += len as u64;
}
Ok(copied)
}
/// Copies from a buffered reader to a writer without extra allocations.
/// Generates an SHA-1 hash of the data as it is copied.
pub fn buf_copy_with_hash<R, W>(reader: &mut R, writer: &mut W) -> io::Result<[u8; 20]>
where
R: BufRead + ?Sized,
W: Write + ?Sized,
{
let mut hasher = Sha1::new();
loop {
let buf = reader.fill_buf()?;
let len = buf.len();
if len == 0 {
break;
}
hasher.update(buf);
writer.write_all(buf)?;
reader.consume(len);
}
Ok(hasher.finalize().into())
}

View File

@ -1,6 +1,8 @@
use std::path::PathBuf;
use anyhow::Result;
use itertools::Itertools;
use typed_path::{Utf8NativePathBuf, Utf8UnixPath};
use path_slash::PathBufExt;
use crate::obj::{ObjInfo, ObjKind};
@ -31,11 +33,11 @@ pub fn generate_ldscript(
let mut force_files = Vec::with_capacity(obj.link_order.len());
for unit in &obj.link_order {
let obj_path = obj_path_for_unit(&unit.name);
force_files.push(obj_path.file_name().unwrap().to_string());
force_files.push(obj_path.file_name().unwrap().to_str().unwrap().to_string());
}
let mut force_active = force_active.to_vec();
for (_, symbol) in obj.symbols.iter() {
for symbol in obj.symbols.iter() {
if symbol.flags.is_exported() && symbol.flags.is_global() && !symbol.flags.is_no_write() {
force_active.push(symbol.name.clone());
}
@ -80,11 +82,11 @@ pub fn generate_ldscript_partial(
let mut force_files = Vec::with_capacity(obj.link_order.len());
for unit in &obj.link_order {
let obj_path = obj_path_for_unit(&unit.name);
force_files.push(obj_path.file_name().unwrap().to_string());
force_files.push(obj_path.file_name().unwrap().to_str().unwrap().to_string());
}
let mut force_active = force_active.to_vec();
for (_, symbol) in obj.symbols.iter() {
for symbol in obj.symbols.iter() {
if symbol.flags.is_exported() && symbol.flags.is_global() && !symbol.flags.is_no_write() {
force_active.push(symbol.name.clone());
}
@ -97,10 +99,6 @@ pub fn generate_ldscript_partial(
Ok(out)
}
pub fn obj_path_for_unit(unit: &str) -> Utf8NativePathBuf {
Utf8UnixPath::new(unit).with_encoding().with_extension("o")
}
pub fn obj_path_for_unit(unit: &str) -> PathBuf { PathBuf::from_slash(unit).with_extension("o") }
pub fn asm_path_for_unit(unit: &str) -> Utf8NativePathBuf {
Utf8UnixPath::new(unit).with_encoding().with_extension("s")
}
pub fn asm_path_for_unit(unit: &str) -> PathBuf { PathBuf::from_slash(unit).with_extension("s") }

View File

@ -5,26 +5,23 @@ use std::{
hash::Hash,
io::BufRead,
mem::{replace, take},
path::Path,
};
use anyhow::{anyhow, bail, Error, Result};
use cwdemangle::{demangle, DemangleOptions};
use flagset::FlagSet;
use indexmap::IndexMap;
use itertools::Itertools;
use multimap::MultiMap;
use once_cell::sync::Lazy;
use regex::{Captures, Regex};
use typed_path::Utf8NativePath;
use crate::{
obj::{
section_kind_for_section, ObjArchitecture, ObjInfo, ObjKind, ObjSection, ObjSectionKind,
ObjSections, ObjSplit, ObjSymbol, ObjSymbolFlagSet, ObjSymbolFlags, ObjSymbolKind,
ObjSymbols, ObjUnit, SectionIndex,
ObjInfo, ObjKind, ObjSplit, ObjSymbol, ObjSymbolFlagSet, ObjSymbolFlags, ObjSymbolKind,
ObjUnit,
},
util::nested::NestedVec,
vfs::open_file,
util::{file::map_file, nested::NestedVec},
};
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
@ -130,7 +127,7 @@ pub struct MapInfo {
pub unit_references: MultiMap<SymbolRef, String>,
pub sections: Vec<SectionInfo>,
pub link_map_symbols: HashMap<SymbolRef, SymbolEntry>,
pub section_symbols: IndexMap<String, BTreeMap<u32, Vec<SymbolEntry>>>,
pub section_symbols: HashMap<String, BTreeMap<u32, Vec<SymbolEntry>>>,
pub section_units: HashMap<String, Vec<(u32, String)>>,
// For common BSS inflation correction
pub common_bss_start: Option<u32>,
@ -714,61 +711,26 @@ where
Ok(sm.result)
}
pub fn apply_map_file(
path: &Utf8NativePath,
pub fn apply_map_file<P>(
path: P,
obj: &mut ObjInfo,
common_bss_start: Option<u32>,
mw_comment_version: Option<u8>,
) -> Result<()> {
let mut file = open_file(path, true)?;
let info = process_map(file.as_mut(), common_bss_start, mw_comment_version)?;
apply_map(info, obj)
) -> Result<()>
where
P: AsRef<Path>,
{
let file = map_file(&path)?;
let info = process_map(&mut file.as_reader(), common_bss_start, mw_comment_version)?;
apply_map(&info, obj)
}
const DEFAULT_REL_SECTIONS: &[&str] =
&[".init", ".text", ".ctors", ".dtors", ".rodata", ".data", ".bss"];
fn normalize_section_name(name: &str) -> &str {
match name {
".extabindex" => "extabindex",
".extab" => "extab",
_ => name,
}
}
pub fn apply_map(mut result: MapInfo, obj: &mut ObjInfo) -> Result<()> {
if result.sections.is_empty() && obj.kind == ObjKind::Executable {
log::warn!("Memory map section missing, attempting to recreate");
for (section_name, symbol_map) in &result.section_symbols {
let mut address = u32::MAX;
let mut size = 0;
for symbol_entry in symbol_map.values().flatten() {
if symbol_entry.address < address {
address = symbol_entry.address;
}
if symbol_entry.address + symbol_entry.size > address + size {
size = symbol_entry.address + symbol_entry.size - address;
}
}
log::info!("Recreated section {} @ {:#010X} ({:#X})", section_name, address, size);
result.sections.push(SectionInfo {
name: normalize_section_name(section_name).to_string(),
address,
size,
file_offset: 0,
});
}
}
pub fn apply_map(result: &MapInfo, obj: &mut ObjInfo) -> Result<()> {
for (section_index, section) in obj.sections.iter_mut() {
let opt = if obj.kind == ObjKind::Executable {
result.sections.iter().find(|s| {
// Slightly fuzzy match for postprocess/broken maps (TP, SMG)
s.address >= section.address as u32
&& (s.address + s.size) <= (section.address + section.size) as u32
})
result.sections.iter().find(|s| s.address == section.address as u32)
} else {
result.sections.iter().filter(|s| s.size > 0).nth(section_index as usize)
result.sections.iter().filter(|s| s.size > 0).nth(section_index)
};
if let Some(info) = opt {
if section.section_known && section.name != info.name {
@ -791,61 +753,17 @@ pub fn apply_map(mut result: MapInfo, obj: &mut ObjInfo) -> Result<()> {
section.rename(info.name.clone())?;
} else {
log::warn!("Section {} @ {:#010X} not found in map", section.name, section.address);
if obj.kind == ObjKind::Relocatable {
let new_name = match section.kind {
ObjSectionKind::Code => {
if section.elf_index == 0 {
".init"
} else {
".text"
}
}
ObjSectionKind::Data | ObjSectionKind::ReadOnlyData => {
if section.elf_index == 4 {
if result
.section_symbols
.get(".rodata")
.map_or(false, |m| !m.is_empty())
{
".rodata"
} else {
".data"
}
} else if let Some(section_name) =
DEFAULT_REL_SECTIONS.get(section.elf_index as usize)
{
section_name
} else {
".data"
}
}
ObjSectionKind::Bss => ".bss",
};
log::warn!("Defaulting to {}", new_name);
section.rename(new_name.to_string())?;
}
}
}
// If every symbol the map has alignment 4, it's likely bogus
let bogus_alignment =
result.section_symbols.values().flatten().flat_map(|(_, m)| m).all(|s| s.align == Some(4));
if bogus_alignment {
log::warn!("Bogus alignment detected, ignoring");
}
// Add section symbols
for (section_name, symbol_map) in &result.section_symbols {
if section_name == ".dead" {
continue;
}
let section_name = normalize_section_name(section_name);
let (section_index, _) = obj
.sections
.by_name(section_name)?
.ok_or_else(|| anyhow!("Failed to locate section {section_name} from map"))?;
for symbol_entry in symbol_map.values().flatten() {
add_symbol(obj, symbol_entry, Some(section_index), bogus_alignment)?;
add_symbol(obj, symbol_entry, Some(section_index))?;
}
}
@ -857,10 +775,6 @@ pub fn apply_map(mut result: MapInfo, obj: &mut ObjInfo) -> Result<()> {
// Add splits
for (section_name, unit_order) in &result.section_units {
if section_name == ".dead" {
continue;
}
let section_name = normalize_section_name(section_name);
let (_, section) = obj
.sections
.iter_mut()
@ -882,7 +796,6 @@ pub fn apply_map(mut result: MapInfo, obj: &mut ObjInfo) -> Result<()> {
name: unit.clone(),
autogenerated: false,
comment_version: Some(0),
order: None,
});
}
@ -900,121 +813,7 @@ pub fn apply_map(mut result: MapInfo, obj: &mut ObjInfo) -> Result<()> {
Ok(())
}
pub fn create_obj(result: &MapInfo) -> Result<ObjInfo> {
let sections = result
.sections
.iter()
.map(|s| {
let name = s.name.clone();
let address = s.address as u64;
let size = s.size as u64;
let file_offset = s.file_offset as u64;
let kind = section_kind_for_section(&name).unwrap_or(ObjSectionKind::ReadOnlyData);
ObjSection {
name,
kind,
address,
size,
data: vec![],
align: 0,
elf_index: 0,
relocations: Default::default(),
virtual_address: None,
file_offset,
section_known: true,
splits: Default::default(),
}
})
.collect();
let mut obj = ObjInfo {
kind: ObjKind::Executable,
architecture: ObjArchitecture::PowerPc,
name: "".to_string(),
symbols: ObjSymbols::new(ObjKind::Executable, vec![]),
sections: ObjSections::new(ObjKind::Executable, sections),
entry: None, // TODO result.entry_point
mw_comment: None,
split_meta: None,
sda2_base: None,
sda_base: None,
stack_address: None,
stack_end: None,
db_stack_addr: None,
arena_lo: None,
arena_hi: None,
link_order: vec![],
blocked_relocation_sources: Default::default(),
blocked_relocation_targets: Default::default(),
known_functions: Default::default(),
module_id: 0,
unresolved_relocations: vec![],
};
// If every symbol the map has alignment 4, it's likely bogus
let bogus_alignment =
result.section_symbols.values().flatten().flat_map(|(_, m)| m).all(|s| s.align == Some(4));
if bogus_alignment {
log::warn!("Bogus alignment detected, ignoring");
}
// Add section symbols
for (section_name, symbol_map) in &result.section_symbols {
let (section_index, _) = obj
.sections
.by_name(section_name)?
.ok_or_else(|| anyhow!("Failed to locate section {section_name} from map"))?;
for symbol_entry in symbol_map.values().flatten() {
add_symbol(&mut obj, symbol_entry, Some(section_index), bogus_alignment)?;
}
}
// Add splits
for (section_name, unit_order) in &result.section_units {
let (_, section) = obj
.sections
.iter_mut()
.find(|(_, s)| s.name == *section_name)
.ok_or_else(|| anyhow!("Failed to locate section '{}'", section_name))?;
let mut iter = unit_order.iter().peekable();
while let Some((addr, unit)) = iter.next() {
let next = iter
.peek()
.map(|(addr, _)| *addr)
.unwrap_or_else(|| (section.address + section.size) as u32);
let common = section_name == ".bss"
&& matches!(result.common_bss_start, Some(start) if *addr >= start);
let unit = unit.replace(' ', "/");
// Disable mw_comment_version for assembly units
if unit.ends_with(".s") && !obj.link_order.iter().any(|u| u.name == unit) {
obj.link_order.push(ObjUnit {
name: unit.clone(),
autogenerated: false,
comment_version: Some(0),
order: None,
});
}
section.splits.push(*addr, ObjSplit {
unit,
end: next,
align: None,
common,
autogenerated: false,
skip: false,
rename: None,
});
}
}
Ok(obj)
}
fn add_symbol(
obj: &mut ObjInfo,
symbol_entry: &SymbolEntry,
section: Option<SectionIndex>,
ignore_alignment: bool,
) -> Result<()> {
fn add_symbol(obj: &mut ObjInfo, symbol_entry: &SymbolEntry, section: Option<usize>) -> Result<()> {
let demangled_name = demangle(&symbol_entry.name, &DemangleOptions::default());
let mut flags: FlagSet<ObjSymbolFlags> = match symbol_entry.visibility {
SymbolVisibility::Unknown => Default::default(),
@ -1044,7 +843,7 @@ fn add_symbol(
SymbolKind::Section => ObjSymbolKind::Section,
SymbolKind::NoType => ObjSymbolKind::Unknown,
},
align: if ignore_alignment { None } else { symbol_entry.align },
align: symbol_entry.align,
..Default::default()
},
true,

View File

@ -15,8 +15,6 @@ pub mod lcf;
pub mod map;
pub mod ncompress;
pub mod nested;
pub mod nlzss;
pub mod path;
pub mod rarc;
pub mod reader;
pub mod rel;

View File

@ -1,305 +0,0 @@
// BSD 2-Clause License
//
// Copyright (c) 2018, Charlotte D
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// Source: https://gitlab.com/DarkKirb/nintendo-lz
// Modified to compile with latest edition, use anyhow::Error, and fix various issues.
use std::io::{Cursor, Read, Write};
use anyhow::{bail, ensure, Result};
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
/// Decompresses an LZ10/LZ11 compressed file. It returns an error when:
///
/// - The file is not a valid LZ10/LZ11 file
/// - The file is truncated (More data was expected than present)
///
/// # Example
///
/// ```rust,ignore
/// let mut f = File::open("Archive.bin.cmp");
/// let mut decompressed = nintendo_lz::decompress(&mut f).unwrap();
/// ```
pub fn decompress<R>(inp: &mut R) -> Result<Vec<u8>>
where R: Read + ?Sized {
let mut length = inp.read_u32::<LittleEndian>()? as usize;
let ver = match length & 0xFF {
0x10 => 0,
0x11 => 1,
_ => bail!("Invalid magic number"),
};
length >>= 8;
if length == 0 && ver == 1 {
length = inp.read_u32::<LittleEndian>()? as usize;
}
let mut out = Vec::<u8>::with_capacity(length);
while out.len() < length {
let byte = inp.read_u8()?;
for bit_no in (0..8).rev() {
if out.len() >= length {
break;
}
if ((byte >> bit_no) & 1) == 0 {
let data = inp.read_u8()?;
out.push(data);
} else {
let lenmsb = inp.read_u8()? as usize;
let lsb = inp.read_u8()? as usize;
let mut length: usize = lenmsb >> 4;
let mut disp: usize = ((lenmsb & 15) << 8) + lsb;
if ver == 0 {
length += 3;
} else if length > 1 {
length += 1;
} else if length == 0 {
length = (lenmsb & 15) << 4;
length += lsb >> 4;
length += 0x11;
let msb = inp.read_u8()? as usize;
disp = ((lsb & 15) << 8) + msb;
} else {
length = (lenmsb & 15) << 12;
length += lsb << 4;
let byte1 = inp.read_u8()? as usize;
let byte2 = inp.read_u8()? as usize;
length += byte1 >> 4;
length += 0x111;
disp = ((byte1 & 15) << 8) + byte2;
}
let start: usize = out.len() - disp - 1;
for i in 0..length {
let val = out[start + i];
out.push(val);
}
}
}
}
Ok(out)
}
/// This function is a convenience wrapper around `decompress` for decompressing slices, arrays or
/// vectors.
pub fn decompress_arr(input: &[u8]) -> Result<Vec<u8>> {
let mut reader = Cursor::new(input);
decompress(&mut reader)
}
/// This enum contains the possible compression levels for LZ compression.
pub enum CompressionLevel {
/// LZ10 compression. Maximum repeat size: 18 bytes
LZ10,
/// LZ11 compression. Maximum repeat size: 65809 bytes
///
/// Argument: Maximum repeat size (0..65810), lower means worse compression but higher speed.
/// for values < 3 compression is disabled
LZ11(u32),
}
fn find_longest_match(data: &[u8], off: usize, max: usize) -> Option<(usize, usize)> {
if off < 4 || data.len() - off < 4 {
return None;
}
let mut longest_pos: usize = 0;
let mut longest_len: usize = 0;
let mut start = 0;
if off > 0x1000 {
start = off - 0x1000;
}
for pos in search(&data[start..off + 2], &data[off..off + 3]) {
let mut length = 0;
for (i, p) in (off..data.len()).enumerate() {
if length == max {
return Some((start + pos, length));
}
if data[p] != data[start + pos + i] {
break;
}
length += 1;
}
if length > longest_len {
longest_pos = pos;
longest_len = length;
}
}
if longest_len < 3 {
return None;
}
Some((start + longest_pos, longest_len))
}
/// Compresses data to LZ10/LZ11. It returns an error when:
///
/// - The input is too large for the selected LZ version (LZ10 supports at most 16MiB)
/// - The maximum repeat length is out of range (for LZ11, has to be in the range (0..65810)
/// - Writing to the output file failed
///
/// # Example
///
/// ```rust,ignore
/// let mut f = File::create("Archive.bin.cmp");
/// let data = b"This is an example text. This is an example text";
/// nintendo_lz::compress(&data, &mut f, nintendo_lz::CompressionLevel::LZ11(65809)).unwrap();
/// ```
pub fn compress<W>(inp: &[u8], out: &mut W, level: CompressionLevel) -> Result<()>
where W: Write + ?Sized {
let ver = match level {
CompressionLevel::LZ10 => 0,
CompressionLevel::LZ11(_) => 1,
};
if ver == 0 && inp.len() > 16777216 {
bail!("Input data too large for LZ10");
}
if ver == 1 && inp.len() as u64 > 0xFFFFFFFF {
bail!("Input data too large for LZ11");
}
let repeat_size = match level {
CompressionLevel::LZ10 => 18,
CompressionLevel::LZ11(max) => max,
};
ensure!(repeat_size < 65810, "Maximum repeat size out of range. (0..65810)");
let size: usize = inp.len();
if size < 16777216 && (size != 0 || ver == 0) {
let header = 0x10 + ver + ((size as u32) << 8);
out.write_u32::<LittleEndian>(header)?;
} else {
out.write_u32::<LittleEndian>(0x11)?;
out.write_u32::<LittleEndian>(size as u32)?;
}
let mut off: usize = 0;
let mut byte: u8 = 0;
let mut index = 7;
let mut cmpbuf: Vec<u8> = Vec::new();
while off < size {
match find_longest_match(inp, off, repeat_size as usize) {
None => {
index -= 1;
cmpbuf.push(inp[off]);
off += 1;
}
Some((pos, len)) => {
let lz_off: usize = off - pos - 1;
byte |= 1 << index;
index -= 1;
if ver == 0 {
let l = len - 3;
let cmp: [u8; 2] = [((lz_off >> 8) as u8) + ((l << 4) as u8), lz_off as u8];
cmpbuf.extend_from_slice(&cmp);
} else if len < 0x11 {
let l = len - 1;
let cmp: [u8; 2] = [((lz_off >> 8) as u8) + ((l << 4) as u8), lz_off as u8];
cmpbuf.extend_from_slice(&cmp);
} else if len < 0x111 {
let l = len - 0x11;
let cmp: [u8; 3] =
[(l >> 4) as u8, ((lz_off >> 8) as u8) + ((l << 4) as u8), lz_off as u8];
cmpbuf.extend_from_slice(&cmp);
} else {
let l = len - 0x111;
let cmp: [u8; 4] = [
(l >> 12) as u8 + 0x10,
(l >> 4) as u8,
((lz_off >> 8) as u8) + ((l << 4) as u8),
lz_off as u8,
];
cmpbuf.extend_from_slice(&cmp);
}
off += len;
}
};
if index < 0 {
out.write_u8(byte)?;
out.write_all(&cmpbuf)?;
byte = 0;
index = 7;
cmpbuf.clear();
}
}
if !cmpbuf.is_empty() {
out.write_u8(byte)?;
out.write_all(&cmpbuf)?;
}
out.write_u8(0xFF)?;
Ok(())
}
/// This function is a convenience wrapper around `compress` for compressing to a Vec<u8>.
/// Additionally, it uses LZ11 as compression algorithm by default.
pub fn compress_arr(input: &[u8]) -> Result<Vec<u8>> {
let mut out: Vec<u8> = Vec::new();
{
let mut writer = Cursor::new(&mut out);
compress(input, &mut writer, CompressionLevel::LZ11(65809))?;
}
Ok(out)
}
fn get_needle_table(needle: &[u8]) -> [usize; 256] {
let mut needle_table = [needle.len(); 256];
for (i, c) in needle.iter().enumerate() {
needle_table[*c as usize] = needle.len() - i;
}
needle_table
}
pub fn search_one(haystack: &[u8], needle: &[u8], needle_table: &[usize; 256]) -> Option<usize> {
let mut cur = 0;
while haystack.len() - cur >= needle.len() {
let mut output = None;
for i in (0..needle.len()).rev() {
if haystack[cur + i] == needle[i] {
output = Some(cur);
break;
}
}
if output.is_some() {
return output;
}
cur += needle_table[haystack[cur + needle.len() - 1] as usize];
}
None
}
fn search(haystack: &[u8], needle: &[u8]) -> Vec<usize> {
let needle_table = get_needle_table(needle);
let mut cur = 0usize;
let mut positions = Vec::new();
while cur + needle.len() < haystack.len() {
let found_pos = search_one(&haystack[cur..], needle, &needle_table);
if let Some(pos) = found_pos {
positions.push(pos);
cur += pos + needle.len() + 1;
} else {
return positions;
}
}
positions
}

View File

@ -1,26 +0,0 @@
use std::{
path::{Path, PathBuf},
str::Utf8Error,
string::FromUtf8Error,
};
use typed_path::{NativePath, NativePathBuf, Utf8NativePath, Utf8NativePathBuf};
// For argp::FromArgs
pub fn native_path(value: &str) -> Result<Utf8NativePathBuf, String> {
Ok(Utf8NativePathBuf::from(value))
}
/// Checks if the path is valid UTF-8 and returns it as a [`Utf8NativePath`].
#[inline]
pub fn check_path(path: &Path) -> Result<&Utf8NativePath, Utf8Error> {
Utf8NativePath::from_bytes_path(NativePath::new(path.as_os_str().as_encoded_bytes()))
}
/// Checks if the path is valid UTF-8 and returns it as a [`Utf8NativePathBuf`].
#[inline]
pub fn check_path_buf(path: PathBuf) -> Result<Utf8NativePathBuf, FromUtf8Error> {
Utf8NativePathBuf::from_bytes_path_buf(NativePathBuf::from(
path.into_os_string().into_encoded_bytes(),
))
}

View File

@ -1,283 +1,425 @@
use std::{borrow::Cow, ffi::CStr};
// Source: https://github.com/Julgodis/picori/blob/650da9f4fe6050b39b80d5360416591c748058d5/src/rarc.rs
// License: MIT
// Modified to use `std::io::Cursor<&[u8]>` and project's FromReader trait
use std::{
collections::HashMap,
fmt::Display,
hash::{Hash, Hasher},
io,
io::{Read, Seek, SeekFrom},
path::{Component, Path, PathBuf},
};
use typed_path::Utf8UnixPath;
use zerocopy::{big_endian::*, FromBytes, Immutable, IntoBytes, KnownLayout};
use anyhow::{anyhow, bail, ensure, Result};
use crate::{static_assert, vfs::next_non_empty};
use crate::util::{
file::read_c_string,
reader::{struct_size, Endian, FromReader},
};
#[derive(Debug, Clone)]
pub struct NamedHash {
pub name: String,
pub hash: u16,
}
impl Display for NamedHash {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.name)
}
}
impl Hash for NamedHash {
fn hash<H>(&self, state: &mut H)
where H: Hasher {
self.hash.hash(state);
}
}
impl PartialEq for NamedHash {
fn eq(&self, other: &Self) -> bool {
if self.hash == other.hash {
self.name == other.name
} else {
false
}
}
}
impl Eq for NamedHash {}
#[derive(Debug, Clone)]
enum RarcDirectory {
File {
/// Name of the file.
name: NamedHash,
/// Offset of the file in the RARC file. This offset is relative to the start of the RARC file.
offset: u64,
/// Size of the file.
size: u32,
},
Folder {
/// Name of the folder.
name: NamedHash,
},
CurrentFolder,
ParentFolder,
}
#[derive(Debug, Clone)]
struct RarcNode {
/// Index of first directory.
pub index: u32,
/// Number of directories.
pub count: u32,
}
pub struct RarcReader {
directories: Vec<RarcDirectory>,
nodes: HashMap<NamedHash, RarcNode>,
root_node: NamedHash,
}
pub const RARC_MAGIC: [u8; 4] = *b"RARC";
#[derive(Copy, Clone, Debug, PartialEq, FromBytes, IntoBytes, Immutable, KnownLayout)]
#[repr(C, align(4))]
pub struct RarcHeader {
/// Magic identifier. (Always "RARC")
struct RarcHeader {
magic: [u8; 4],
/// Length of the RARC file.
file_len: U32,
/// Length of the header. (Always 32)
header_len: U32,
/// Start of the file data, relative to the end of the file header.
data_offset: U32,
/// Length of the file data.
data_len: U32,
_unk1: U32,
_unk2: U32,
_unk3: U32,
_file_length: u32,
header_length: u32,
file_offset: u32,
_file_length_2: u32,
_unk0: u32,
_unk1: u32,
_unk2: u32,
node_count: u32,
node_offset: u32,
directory_count: u32,
directory_offset: u32,
string_table_length: u32,
string_table_offset: u32,
_file_count: u16,
_unk3: u16,
_unk4: u32,
}
static_assert!(size_of::<RarcHeader>() == 0x20);
impl FromReader for RarcHeader {
type Args = ();
impl RarcHeader {
/// Length of the RARC file.
pub fn file_len(&self) -> u32 { self.file_len.get() }
const STATIC_SIZE: usize = struct_size([
4, // magic
u32::STATIC_SIZE, // file_length
u32::STATIC_SIZE, // header_length
u32::STATIC_SIZE, // file_offset
u32::STATIC_SIZE, // file_length
u32::STATIC_SIZE, // unk0
u32::STATIC_SIZE, // unk1
u32::STATIC_SIZE, // unk2
u32::STATIC_SIZE, // node_count
u32::STATIC_SIZE, // node_offset
u32::STATIC_SIZE, // directory_count
u32::STATIC_SIZE, // directory_offset
u32::STATIC_SIZE, // string_table_length
u32::STATIC_SIZE, // string_table_offset
u16::STATIC_SIZE, // file_count
u16::STATIC_SIZE, // unk3
u32::STATIC_SIZE, // unk4
]);
/// Length of the header.
pub fn header_len(&self) -> u32 { self.header_len.get() }
/// Start of the file data, relative to the end of the file header.
pub fn data_offset(&self) -> u32 { self.data_offset.get() }
/// Length of the file data.
pub fn data_len(&self) -> u32 { self.data_len.get() }
}
#[derive(Copy, Clone, Debug, PartialEq, FromBytes, IntoBytes, Immutable, KnownLayout)]
#[repr(C, align(4))]
struct RarcInfo {
/// Number of directories in the directory table.
directory_count: U32,
/// Offset to the start of the directory table, relative to the end of the file header.
directory_offset: U32,
/// Number of nodes in the node table.
node_count: U32,
/// Offset to the start of the node table, relative to the end of the file header.
node_offset: U32,
/// Length of the string table.
string_table_len: U32,
/// Offset to the start of the string table, relative to the end of the file header.
string_table_offset: U32,
/// Number of files in the node table.
_file_count: U16,
_unk4: U16,
_unk5: U32,
}
static_assert!(size_of::<RarcInfo>() == 0x20);
#[derive(Copy, Clone, Debug, PartialEq, FromBytes, IntoBytes, Immutable, KnownLayout)]
#[repr(C, align(4))]
pub struct RarcNode {
/// Index of the node. (0xFFFF for directories)
index: U16,
/// Hash of the node name.
name_hash: U16,
/// Unknown. (0x200 for folders, 0x1100 for files)
_unk0: U16,
/// Offset in the string table to the node name.
name_offset: U16,
/// Files: Offset in the data to the file data.
/// Directories: Index of the directory in the directory table.
data_offset: U32,
/// Files: Length of the data.
/// Directories: Unknown. Always 16.
data_length: U32,
_unk1: U32,
}
static_assert!(size_of::<RarcNode>() == 0x14);
impl RarcNode {
/// Whether the node is a file.
pub fn is_file(&self) -> bool { self.index.get() != 0xFFFF }
/// Whether the node is a directory.
pub fn is_dir(&self) -> bool { self.index.get() == 0xFFFF }
/// Offset in the string table to the node name.
pub fn name_offset(&self) -> u32 { self.name_offset.get() as u32 }
/// Files: Offset in the data to the file data.
/// Directories: Index of the directory in the directory table.
pub fn data_offset(&self) -> u32 { self.data_offset.get() }
/// Files: Length of the data.
/// Directories: Unknown. Always 16.
pub fn data_length(&self) -> u32 { self.data_length.get() }
}
#[derive(Copy, Clone, Debug, PartialEq, FromBytes, IntoBytes, Immutable, KnownLayout)]
#[repr(C, align(4))]
pub struct RarcDirectory {
/// Identifier of the directory.
identifier: [u8; 4],
/// Offset in the string table to the directory name.
name_offset: U32,
/// Hash of the directory name.
name_hash: U16,
/// Number of nodes in the directory.
count: U16,
/// Index of the first node in the directory.
index: U32,
}
static_assert!(size_of::<RarcDirectory>() == 0x10);
impl RarcDirectory {
/// Offset in the string table to the directory name.
pub fn name_offset(&self) -> u32 { self.name_offset.get() }
/// Index of the first node in the directory.
pub fn node_index(&self) -> u32 { self.index.get() }
/// Number of nodes in the directory.
pub fn node_count(&self) -> u16 { self.count.get() }
}
/// A view into a RARC archive.
pub struct RarcView<'a> {
/// The RARC archive header.
pub header: &'a RarcHeader,
/// The directories in the RARC archive.
pub directories: &'a [RarcDirectory],
/// The nodes in the RARC archive.
pub nodes: &'a [RarcNode],
/// The string table containing all file and directory names.
pub string_table: &'a [u8],
/// The file data.
pub data: &'a [u8],
}
impl<'a> RarcView<'a> {
/// Create a new RARC view from a buffer.
pub fn new(buf: &'a [u8]) -> Result<Self, &'static str> {
let Ok((header, remaining)) = RarcHeader::ref_from_prefix(buf) else {
return Err("Buffer not large enough for RARC header");
fn from_reader_args<R>(reader: &mut R, e: Endian, _args: Self::Args) -> io::Result<Self>
where R: Read + Seek + ?Sized {
let header = Self {
magic: <[u8; 4]>::from_reader(reader, e)?,
_file_length: u32::from_reader(reader, e)?,
header_length: u32::from_reader(reader, e)?,
file_offset: u32::from_reader(reader, e)?,
_file_length_2: u32::from_reader(reader, e)?,
_unk0: u32::from_reader(reader, e)?,
_unk1: u32::from_reader(reader, e)?,
_unk2: u32::from_reader(reader, e)?,
node_count: u32::from_reader(reader, e)?,
node_offset: u32::from_reader(reader, e)?,
directory_count: u32::from_reader(reader, e)?,
directory_offset: u32::from_reader(reader, e)?,
string_table_length: u32::from_reader(reader, e)?,
string_table_offset: u32::from_reader(reader, e)?,
_file_count: u16::from_reader(reader, e)?,
_unk3: u16::from_reader(reader, e)?,
_unk4: u32::from_reader(reader, e)?,
};
if header.magic != RARC_MAGIC {
return Err("RARC magic mismatch");
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("invalid RARC magic: {:?}", header.magic),
));
}
if header.header_len.get() as usize != size_of::<RarcHeader>() {
return Err("RARC header size mismatch");
if header.node_count >= 0x10000 {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("invalid node count: {}", header.node_count),
));
}
// All offsets are relative to the _end_ of the header, so we can
// just trim the header from the buffer and use the offsets as is.
let Ok((info, _)) = RarcInfo::ref_from_prefix(remaining) else {
return Err("Buffer not large enough for RARC info");
};
let directory_table_offset = info.directory_offset.get() as usize;
let directory_table_size = info.directory_count.get() as usize * size_of::<RarcDirectory>();
let directories_buf = remaining
.get(directory_table_offset..directory_table_offset + directory_table_size)
.ok_or("RARC directory table out of bounds")?;
let directories = <[RarcDirectory]>::ref_from_bytes(directories_buf)
.map_err(|_| "RARC directory table not aligned")?;
if directories.is_empty() || directories[0].identifier != *b"ROOT" {
return Err("RARC root directory not found");
if header.directory_count >= 0x10000 {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("invalid directory count: {}", header.directory_count),
));
}
let node_table_offset = info.node_offset.get() as usize;
let node_table_size = info.node_count.get() as usize * size_of::<RarcNode>();
let nodes_buf = remaining
.get(node_table_offset..node_table_offset + node_table_size)
.ok_or("RARC node table out of bounds")?;
let nodes =
<[RarcNode]>::ref_from_bytes(nodes_buf).map_err(|_| "RARC node table not aligned")?;
let string_table_offset = info.string_table_offset.get() as usize;
let string_table_size = info.string_table_len.get() as usize;
let string_table = remaining
.get(string_table_offset..string_table_offset + string_table_size)
.ok_or("RARC string table out of bounds")?;
let data_offset = header.data_offset.get() as usize;
let data_size = header.data_len.get() as usize;
let data =
buf.get(data_offset..data_offset + data_size).ok_or("RARC file data out of bounds")?;
Ok(Self { header, directories, nodes, string_table, data })
Ok(header)
}
}
/// Get a string from the string table at the given offset.
pub fn get_string(&self, offset: u32) -> Result<Cow<str>, String> {
let name_buf = self.string_table.get(offset as usize..).ok_or_else(|| {
format!(
"RARC: name offset {} out of bounds (string table size: {})",
offset,
self.string_table.len()
)
})?;
let c_string = CStr::from_bytes_until_nul(name_buf)
.map_err(|_| format!("RARC: name at offset {} not null-terminated", offset))?;
Ok(c_string.to_string_lossy())
struct RarcFileNode {
index: u16,
name_hash: u16,
_unk0: u16, // 0x200 for folders, 0x1100 for files
name_offset: u16,
data_offset: u32,
data_length: u32,
_unk1: u32,
}
impl FromReader for RarcFileNode {
type Args = ();
const STATIC_SIZE: usize = struct_size([
u16::STATIC_SIZE, // index
u16::STATIC_SIZE, // name_hash
u16::STATIC_SIZE, // unk0
u16::STATIC_SIZE, // name_offset
u32::STATIC_SIZE, // data_offset
u32::STATIC_SIZE, // data_length
u32::STATIC_SIZE, // unk1
]);
fn from_reader_args<R>(reader: &mut R, e: Endian, _args: Self::Args) -> io::Result<Self>
where R: Read + Seek + ?Sized {
Ok(Self {
index: u16::from_reader(reader, e)?,
name_hash: u16::from_reader(reader, e)?,
_unk0: u16::from_reader(reader, e)?,
name_offset: u16::from_reader(reader, e)?,
data_offset: u32::from_reader(reader, e)?,
data_length: u32::from_reader(reader, e)?,
_unk1: u32::from_reader(reader, e)?,
})
}
}
/// Get the data for a file node.
pub fn get_data(&self, node: RarcNode) -> Result<&[u8], &'static str> {
if node.is_dir() {
return Err("Cannot get data for a directory node");
}
let offset = node.data_offset.get() as usize;
let size = node.data_length.get() as usize;
self.data.get(offset..offset + size).ok_or("RARC file data out of bounds")
struct RarcDirectoryNode {
_identifier: u32,
name_offset: u32,
name_hash: u16,
count: u16,
index: u32,
}
impl FromReader for RarcDirectoryNode {
type Args = ();
const STATIC_SIZE: usize = struct_size([
u32::STATIC_SIZE, // identifier
u32::STATIC_SIZE, // name_offset
u16::STATIC_SIZE, // name_hash
u16::STATIC_SIZE, // count
u32::STATIC_SIZE, // index
]);
fn from_reader_args<R>(reader: &mut R, e: Endian, _args: Self::Args) -> io::Result<Self>
where R: Read + Seek + ?Sized {
Ok(Self {
_identifier: u32::from_reader(reader, e)?,
name_offset: u32::from_reader(reader, e)?,
name_hash: u16::from_reader(reader, e)?,
count: u16::from_reader(reader, e)?,
index: u32::from_reader(reader, e)?,
})
}
}
/// Finds a particular file or directory by path.
pub fn find(&self, path: &Utf8UnixPath) -> Option<RarcNodeKind> {
let mut split = path.as_str().split('/');
let mut current = next_non_empty(&mut split);
impl RarcReader {
/// Creates a new RARC reader.
pub fn new<R>(reader: &mut R) -> Result<Self>
where R: Read + Seek + ?Sized {
let base = reader.stream_position()?;
let header = RarcHeader::from_reader(reader, Endian::Big)?;
let mut dir_idx = 0;
let mut dir = self.directories[dir_idx];
// Allow matching the root directory by name optionally
if let Ok(root_name) = self.get_string(dir.name_offset()) {
if root_name.eq_ignore_ascii_case(current) {
current = next_non_empty(&mut split);
let base = base + header.header_length as u64;
let directory_base = base + header.directory_offset as u64;
let data_base = base + header.file_offset as u64;
let mut directories = Vec::with_capacity(header.directory_count as usize);
for i in 0..header.directory_count {
reader.seek(SeekFrom::Start(directory_base + 20 * i as u64))?;
let node = RarcFileNode::from_reader(reader, Endian::Big)?;
let name = {
let offset = header.string_table_offset as u64;
let offset = offset + node.name_offset as u64;
ensure!(
(node.name_offset as u32) < header.string_table_length,
"invalid string table offset"
);
read_c_string(reader, base + offset)
}?;
if node.index == 0xFFFF {
if name == "." {
directories.push(RarcDirectory::CurrentFolder);
} else if name == ".." {
directories.push(RarcDirectory::ParentFolder);
} else {
directories.push(RarcDirectory::Folder {
name: NamedHash { name, hash: node.name_hash },
});
}
} else {
directories.push(RarcDirectory::File {
name: NamedHash { name, hash: node.name_hash },
offset: data_base + node.data_offset as u64,
size: node.data_length,
});
}
}
if current.is_empty() {
return Some(RarcNodeKind::Directory(dir_idx, dir));
let node_base = base + header.node_offset as u64;
let mut root_node: Option<NamedHash> = None;
let mut nodes = HashMap::with_capacity(header.node_count as usize);
for i in 0..header.node_count {
reader.seek(SeekFrom::Start(node_base + 16 * i as u64))?;
let node = RarcDirectoryNode::from_reader(reader, Endian::Big)?;
ensure!(node.index < header.directory_count, "first directory index out of bounds");
let last_index = node.index.checked_add(node.count as u32);
ensure!(
last_index.is_some() && last_index.unwrap() <= header.directory_count,
"last directory index out of bounds"
);
let name = {
let offset = header.string_table_offset as u64;
let offset = offset + node.name_offset as u64;
ensure!(
node.name_offset < header.string_table_length,
"invalid string table offset"
);
read_c_string(reader, base + offset)
}?;
// FIXME: this assumes that the root node is the first node in the list
if root_node.is_none() {
root_node = Some(NamedHash { name: name.clone(), hash: node.name_hash });
}
let name = NamedHash { name, hash: node.name_hash };
nodes.insert(name.clone(), RarcNode { index: node.index, count: node.count as u32 });
}
let mut idx = dir.index.get() as usize;
while idx < dir.index.get() as usize + dir.count.get() as usize {
let node = self.nodes.get(idx).copied()?;
let Ok(name) = self.get_string(node.name_offset()) else {
idx += 1;
continue;
};
if name.eq_ignore_ascii_case(current) {
current = next_non_empty(&mut split);
if node.is_dir() {
dir_idx = node.data_offset.get() as usize;
dir = self.directories.get(dir_idx).cloned()?;
idx = dir.index.get() as usize;
if current.is_empty() {
return Some(RarcNodeKind::Directory(dir_idx, dir));
if let Some(root_node) = root_node {
Ok(Self { directories, nodes, root_node })
} else {
Err(anyhow!("no root node"))
}
}
/// Get a iterator over the nodes in the RARC file.
pub fn nodes(&self) -> Nodes<'_> {
let root_node = self.root_node.clone();
Nodes { parent: self, stack: vec![NodeState::Begin(root_node)] }
}
/// Find a file in the RARC file.
pub fn find_file<P>(&self, path: P) -> Result<Option<(u64, u32)>>
where P: AsRef<Path> {
let mut cmp_path = PathBuf::new();
for component in path.as_ref().components() {
match component {
Component::Normal(name) => cmp_path.push(name.to_ascii_lowercase()),
Component::RootDir => {}
component => bail!("Invalid path component: {:?}", component),
}
}
let mut current_path = PathBuf::new();
for node in self.nodes() {
match node {
Node::DirectoryBegin { name } => {
current_path.push(name.name.to_ascii_lowercase());
}
Node::DirectoryEnd { name: _ } => {
current_path.pop();
}
Node::File { name, offset, size } => {
if current_path.join(name.name.to_ascii_lowercase()) == cmp_path {
return Ok(Some((offset, size)));
}
}
Node::CurrentDirectory => {}
Node::ParentDirectory => {}
}
}
Ok(None)
}
}
/// A node in an RARC file.
pub enum Node {
/// A directory that has been entered.
DirectoryBegin { name: NamedHash },
/// A directory that has been exited.
DirectoryEnd { name: NamedHash },
/// A file in the current directory.
File { name: NamedHash, offset: u64, size: u32 },
/// The current directory. This is equivalent to ".".
CurrentDirectory,
/// The parent directory. This is equivalent to "..".
ParentDirectory,
}
enum NodeState {
Begin(NamedHash),
End(NamedHash),
File(NamedHash, u32),
}
/// An iterator over the nodes in an RARC file.
pub struct Nodes<'parent> {
parent: &'parent RarcReader,
stack: Vec<NodeState>,
}
impl<'parent> Iterator for Nodes<'parent> {
type Item = Node;
fn next(&mut self) -> Option<Self::Item> {
match self.stack.pop()? {
NodeState::Begin(name) => {
self.stack.push(NodeState::File(name.clone(), 0));
Some(Node::DirectoryBegin { name })
}
NodeState::End(name) => Some(Node::DirectoryEnd { name }),
NodeState::File(name, index) => {
if let Some(node) = self.parent.nodes.get(&name) {
if index + 1 >= node.count {
self.stack.push(NodeState::End(name.clone()));
} else {
continue;
self.stack.push(NodeState::File(name.clone(), index + 1));
}
let directory = &self.parent.directories[(node.index + index) as usize];
match directory {
RarcDirectory::CurrentFolder => Some(Node::CurrentDirectory),
RarcDirectory::ParentFolder => Some(Node::ParentDirectory),
RarcDirectory::Folder { name } => {
self.stack.push(NodeState::Begin(name.clone()));
self.next()
}
RarcDirectory::File { name, offset, size } => {
Some(Node::File { name: name.clone(), offset: *offset, size: *size })
}
}
} else {
return Some(RarcNodeKind::File(idx, node));
None
}
}
idx += 1;
}
None
}
/// Get the children of a directory.
pub fn children(&self, dir: RarcDirectory) -> &[RarcNode] {
let start = dir.node_index() as usize;
let end = start + dir.node_count() as usize;
self.nodes.get(start..end).unwrap_or(&[])
}
}
#[derive(Debug)]
pub enum RarcNodeKind {
File(usize, RarcNode),
Directory(usize, RarcDirectory),
}

View File

@ -13,7 +13,7 @@ use crate::{
array_ref_mut,
obj::{
ObjArchitecture, ObjInfo, ObjKind, ObjRelocKind, ObjSection, ObjSectionKind, ObjSymbol,
ObjSymbolFlagSet, ObjSymbolFlags, ObjSymbolKind, SectionIndex,
ObjSymbolFlagSet, ObjSymbolFlags, ObjSymbolKind,
},
util::{
align_up,
@ -418,7 +418,7 @@ where R: Read + Seek + ?Sized {
_ => None, // determined later
}
.unwrap_or_default() as u64,
elf_index: idx as SectionIndex,
elf_index: idx,
relocations: Default::default(),
virtual_address: None, // TODO option to set?
file_offset: offset as u64,
@ -440,7 +440,7 @@ where R: Read + Seek + ?Sized {
let (section_index, _) = sections
.iter()
.enumerate()
.find(|&(_, section)| section.elf_index == rel_section_idx as SectionIndex)
.find(|&(_, section)| section.elf_index == rel_section_idx as usize)
.ok_or_else(|| anyhow!("Failed to locate {name} section {rel_section_idx}"))?;
log::debug!("Adding {name} section {rel_section_idx} offset {offset:#X}");
let mut flags = ObjSymbolFlagSet(ObjSymbolFlags::Global.into());
@ -450,7 +450,7 @@ where R: Read + Seek + ?Sized {
symbols.push(ObjSymbol {
name: name.to_string(),
address: offset as u64,
section: Some(section_index as SectionIndex),
section: Some(section_index),
flags,
kind: ObjSymbolKind::Function,
..Default::default()
@ -853,10 +853,8 @@ where
offset = (offset + align) & !align;
offset += section.size() as u32;
}
if info.version >= 3 {
// Align to 4 after section data
offset = (offset + 3) & !3;
}
// Align to 4 after section data
offset = (offset + 3) & !3;
fn do_relocation_layout(
relocations: &[RelReloc],
@ -1049,8 +1047,8 @@ where
}
w.write_all(&section_data)?;
}
if info.version >= 3 {
// Align to 4 after section data
// Align to 4 after section data
{
let position = w.stream_position()?;
w.write_all(&vec![0u8; calculate_padding(position, 4) as usize])?;
}

View File

@ -9,7 +9,7 @@ use cwdemangle::{demangle, DemangleOptions};
use crate::{
obj::{
ObjArchitecture, ObjInfo, ObjKind, ObjSection, ObjSectionKind, ObjSymbol, ObjSymbolFlagSet,
ObjSymbolFlags, ObjSymbolKind, SectionIndex,
ObjSymbolFlags, ObjSymbolKind,
},
util::{
file::{read_c_string, read_string},
@ -34,16 +34,11 @@ pub const DOL_SECTION_NAMES: [Option<&str>; 14] = [
Some(".sbss2"),
None, // s_zero2
];
pub const RSO_SECTION_NAMES: [&str; 7] =
[".init", ".text", ".ctors", ".dtors", ".rodata", ".data", ".bss"];
/// extabindex section index.
pub const DOL_SECTION_ETI: u32 = 241;
/// ABS symbol section index.
pub const DOL_SECTION_ABS: u32 = 65521;
#[derive(Default)]
pub struct RsoHeader {
// Pointer to the next module, forming a linked list. Always 0, filled in at runtime.
// pub next: u32,
@ -106,10 +101,6 @@ pub struct RsoHeader {
pub import_table_name_offset: u32,
}
impl RsoHeader {
pub fn new() -> Self { Self { version: 1, ..Default::default() } }
}
impl FromReader for RsoHeader {
type Args = ();
@ -214,46 +205,13 @@ impl FromReader for RsoHeader {
}
}
impl ToWriter for RsoHeader {
fn to_writer<W>(&self, writer: &mut W, e: Endian) -> io::Result<()>
where W: Write + ?Sized {
(0u64).to_writer(writer, e)?; // next and prev
self.num_sections.to_writer(writer, e)?;
self.section_info_offset.to_writer(writer, e)?;
self.name_offset.to_writer(writer, e)?;
self.name_size.to_writer(writer, e)?;
self.version.to_writer(writer, e)?;
self.bss_size.to_writer(writer, e)?;
self.prolog_section.to_writer(writer, e)?;
self.epilog_section.to_writer(writer, e)?;
self.unresolved_section.to_writer(writer, e)?;
(0u8).to_writer(writer, e)?; // bss_section
self.prolog_offset.to_writer(writer, e)?;
self.epilog_offset.to_writer(writer, e)?;
self.unresolved_offset.to_writer(writer, e)?;
self.internal_rel_offset.to_writer(writer, e)?;
self.internal_rel_size.to_writer(writer, e)?;
self.external_rel_offset.to_writer(writer, e)?;
self.external_rel_size.to_writer(writer, e)?;
self.export_table_offset.to_writer(writer, e)?;
self.export_table_size.to_writer(writer, e)?;
self.export_table_name_offset.to_writer(writer, e)?;
self.import_table_offset.to_writer(writer, e)?;
self.import_table_size.to_writer(writer, e)?;
self.import_table_name_offset.to_writer(writer, e)?;
Ok(())
}
fn write_size(&self) -> usize { Self::STATIC_SIZE }
}
#[derive(Copy, Clone, Debug, Default)]
#[derive(Copy, Clone, Debug)]
pub struct RsoSectionHeader {
/// Absolute offset of the section.
/// The lowest bit is set if the section is executable.
pub offset_and_flags: u32,
offset_and_flags: u32,
/// Size of the section.
pub size: u32,
size: u32,
}
impl FromReader for RsoSectionHeader {
@ -297,17 +255,17 @@ impl RsoSectionHeader {
pub fn exec(&self) -> bool { self.offset_and_flags & 1 != 0 }
}
pub struct RsoRelocation {
struct RsoRelocation {
/// Absolute offset of this relocation (relative to the start of the RSO file).
pub offset: u32,
offset: u32,
/// For internal relocations, this is the section index of the symbol being patched to.
/// For external relocations, this is the index of the symbol within the import symbol table.
/// The lowest 8 bits are the relocation type.
pub id_and_type: u32,
id_and_type: u32,
/// For internal relocations, this is the section-relative offset of the target symbol.
/// For external relocations, this is unused and always 0 (the offset is calculated using the
/// import symbol table).
pub target_offset: u32,
target_offset: u32,
}
impl FromReader for RsoRelocation {
@ -357,23 +315,22 @@ impl RsoRelocation {
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum RsoSymbolKind {
enum RsoSymbolKind {
Import,
Export,
}
#[derive(Debug)]
pub struct RsoSymbol {
struct RsoSymbol {
/// Relative offset into the name table pointed to in the header,
/// which points to the name of this symbol.
pub name_offset: u32,
name_offset: u32,
/// The section-relative offset to the symbol. This is always 0 for imports.
pub offset: u32,
offset: u32,
/// For exports, index of the section that contains this symbol.
/// For imports, offset of the first relocation that use this symbol
pub section_index: u32,
/// For imports, appears to be an offset?
section_index: u32,
/// A hash of the symbol name. Only present for exports.
pub hash: Option<u32>,
hash: Option<u32>,
}
impl FromReader for RsoSymbol {
@ -403,8 +360,7 @@ impl ToWriter for RsoSymbol {
self.offset.to_writer(writer, e)?;
self.section_index.to_writer(writer, e)?;
if let Some(hash) = self.hash {
// Since the nature of the value is not numeric, we must preserve the order of the bytes
writer.write_all(&hash.to_ne_bytes())?;
hash.to_writer(writer, e)?;
}
Ok(())
}
@ -452,7 +408,7 @@ where R: Read + Seek + ?Sized {
size: size as u64,
data,
align: 0,
elf_index: idx as SectionIndex,
elf_index: idx as usize,
relocations: Default::default(),
virtual_address: None, // TODO option to set?
file_offset: offset as u64,
@ -476,13 +432,13 @@ where R: Read + Seek + ?Sized {
let (section_index, _) = sections
.iter()
.enumerate()
.find(|&(_, section)| section.elf_index == rel_section_idx as SectionIndex)
.find(|&(_, section)| section.elf_index == rel_section_idx as usize)
.ok_or_else(|| anyhow!("Failed to locate {name} section {rel_section_idx}"))?;
log::debug!("Adding {name} section {rel_section_idx} offset {offset:#X}");
symbols.push(ObjSymbol {
name: name.to_string(),
address: offset as u64,
section: Some(section_index as SectionIndex),
section: Some(section_index),
flags: ObjSymbolFlagSet(ObjSymbolFlags::Global.into()),
kind: ObjSymbolKind::Function,
..Default::default()
@ -526,7 +482,7 @@ where R: Read + Seek + ?Sized {
let section = sections
.iter()
.enumerate()
.find(|&(_, section)| section.elf_index == symbol.section_index as SectionIndex)
.find(|&(_, section)| section.elf_index == symbol.section_index as usize)
.map(|(idx, _)| idx)
// HACK: selfiles won't have any sections
.unwrap_or(symbol.section_index as usize);
@ -541,7 +497,7 @@ where R: Read + Seek + ?Sized {
name,
demangled_name,
address: symbol.offset as u64,
section: Some(section as SectionIndex),
section: Some(section),
..Default::default()
});
}
@ -568,7 +524,7 @@ where R: Read + Seek + ?Sized {
Ok(obj)
}
pub fn symbol_hash(s: &str) -> u32 {
fn symbol_hash(s: &str) -> u32 {
s.bytes().fold(0u32, |hash, c| {
let mut m = (hash << 4).wrapping_add(c as u32);
let n = m & 0xF0000000;

View File

@ -1,11 +1,13 @@
use std::collections::{btree_map, BTreeMap};
use std::{
collections::{btree_map, BTreeMap},
path::Path,
};
use anyhow::{anyhow, bail, ensure, Result};
use base64::{engine::general_purpose::STANDARD, Engine};
use cwdemangle::{demangle, DemangleOptions};
use serde::{Deserialize, Serialize};
use sha1::{Digest, Sha1};
use typed_path::Utf8NativePath;
use crate::{
analysis::{
@ -16,7 +18,7 @@ use crate::{
array_ref,
obj::{
ObjInfo, ObjKind, ObjReloc, ObjRelocKind, ObjSection, ObjSymbol, ObjSymbolFlagSet,
ObjSymbolKind, SectionIndex, SymbolIndex,
ObjSymbolKind,
},
util::elf::process_elf,
};
@ -34,13 +36,13 @@ pub struct OutSymbol {
pub struct OutReloc {
pub offset: u32,
pub kind: ObjRelocKind,
pub symbol: u32,
pub symbol: usize,
pub addend: i32,
}
#[derive(Debug, Clone, Hash, Eq, PartialEq, Serialize, Deserialize)]
pub struct FunctionSignature {
pub symbol: u32,
pub symbol: usize,
pub hash: String,
pub signature: String,
pub symbols: Vec<OutSymbol>,
@ -90,12 +92,12 @@ pub fn check_signatures(
let mut name = None;
for signature in signatures {
if name.is_none() {
name = Some(signature.symbols[signature.symbol as usize].name.clone());
name = Some(signature.symbols[signature.symbol].name.clone());
}
if check_signature(data, signature)? {
log::debug!(
"Found {} @ {:#010X} (hash {})",
signature.symbols[signature.symbol as usize].name,
signature.symbols[signature.symbol].name,
addr,
signature.hash
);
@ -112,9 +114,9 @@ pub fn apply_symbol(
obj: &mut ObjInfo,
target: SectionAddress,
sig_symbol: &OutSymbol,
) -> Result<SymbolIndex> {
) -> Result<usize> {
let mut target_section_index =
if target.section == SectionIndex::MAX { None } else { Some(target.section) };
if target.section == usize::MAX { None } else { Some(target.section) };
if let Some(target_section_index) = target_section_index {
let target_section = &mut obj.sections[target_section_index];
if !target_section.section_known {
@ -152,7 +154,7 @@ pub fn apply_signature(
addr: SectionAddress,
signature: &FunctionSignature,
) -> Result<()> {
let in_symbol = &signature.symbols[signature.symbol as usize];
let in_symbol = &signature.symbols[signature.symbol];
let symbol_idx = apply_symbol(obj, addr, in_symbol)?;
let mut tracker = Tracker::new(obj);
for reloc in &signature.relocations {
@ -183,7 +185,7 @@ pub fn apply_signature(
}
_ => bail!("Relocation mismatch: {:?} != {:?}", reloc, sig_reloc.kind),
};
let sig_symbol = &signature.symbols[sig_reloc.symbol as usize];
let sig_symbol = &signature.symbols[sig_reloc.symbol];
// log::info!("Processing relocation {:#010X} {:?} -> {:#010X} {:?}", reloc_addr, reloc, target, sig_symbol);
let target_symbol_idx = apply_symbol(obj, target, sig_symbol)?;
let obj_reloc = ObjReloc {
@ -198,7 +200,7 @@ pub fn apply_signature(
for reloc in &signature.relocations {
let addr = addr + reloc.offset;
if !tracker.relocations.contains_key(&addr) {
let sig_symbol = &signature.symbols[reloc.symbol as usize];
let sig_symbol = &signature.symbols[reloc.symbol];
bail!("Missing relocation @ {:#010X}: {:?} -> {:?}", addr, reloc, sig_symbol);
}
}
@ -244,13 +246,11 @@ pub fn compare_signature(existing: &mut FunctionSignature, new: &FunctionSignatu
Ok(())
}
pub fn generate_signature(
path: &Utf8NativePath,
symbol_name: &str,
) -> Result<Option<FunctionSignature>> {
pub fn generate_signature<P>(path: P, symbol_name: &str) -> Result<Option<FunctionSignature>>
where P: AsRef<Path> {
let mut out_symbols: Vec<OutSymbol> = Vec::new();
let mut out_relocs: Vec<OutReloc> = Vec::new();
let mut symbol_map: BTreeMap<SymbolIndex, u32> = BTreeMap::new();
let mut symbol_map: BTreeMap<usize, usize> = BTreeMap::new();
let mut obj = process_elf(path)?;
if obj.kind == ObjKind::Executable
@ -312,7 +312,7 @@ pub fn generate_signature(
let symbol_idx = match symbol_map.entry(reloc.target_symbol) {
btree_map::Entry::Vacant(e) => {
let target = &obj.symbols[reloc.target_symbol];
let symbol_idx = out_symbols.len() as u32;
let symbol_idx = out_symbols.len();
e.insert(symbol_idx);
out_symbols.push(OutSymbol {
kind: target.kind,

View File

@ -1,6 +1,6 @@
use std::{
cmp::{max, min, Ordering},
collections::{btree_map, BTreeMap, HashMap, HashSet},
collections::{BTreeMap, HashMap, HashSet},
};
use anyhow::{anyhow, bail, ensure, Context, Result};
@ -15,7 +15,7 @@ use crate::{
obj::{
ObjArchitecture, ObjInfo, ObjKind, ObjReloc, ObjRelocations, ObjSection, ObjSectionKind,
ObjSplit, ObjSymbol, ObjSymbolFlagSet, ObjSymbolFlags, ObjSymbolKind, ObjSymbolScope,
ObjUnit, SectionIndex, SymbolIndex,
ObjUnit,
},
util::{align_up, comment::MWComment},
};
@ -444,8 +444,8 @@ fn create_gap_splits(obj: &mut ObjInfo) -> Result<()> {
/// Ensures that all .bss splits following a common split are also marked as common.
fn update_common_splits(obj: &mut ObjInfo, common_start: Option<u32>) -> Result<()> {
let Some(common_bss_start) = (match common_start {
Some(addr) => Some(SectionAddress::new(
let Some((bss_section_index, common_bss_start)) = (match common_start {
Some(addr) => Some((
obj.sections.by_name(".bss")?.ok_or_else(|| anyhow!("Failed to find .bss section"))?.0,
addr,
)),
@ -454,8 +454,8 @@ fn update_common_splits(obj: &mut ObjInfo, common_start: Option<u32>) -> Result<
return Ok(());
};
log::debug!("Found common BSS start at {:#010X}", common_bss_start);
let bss_section = &mut obj.sections[common_bss_start.section];
for (addr, split) in bss_section.splits.for_range_mut(common_bss_start.address..) {
let bss_section = &mut obj.sections[bss_section_index];
for (addr, split) in bss_section.splits.for_range_mut(common_bss_start..) {
if !split.common {
split.common = true;
log::debug!("Added common flag to split {} at {:#010X}", split.unit, addr);
@ -593,8 +593,8 @@ fn add_padding_symbols(obj: &mut ObjInfo) -> Result<()> {
.peekable();
while let Some((_, symbol)) = iter.next() {
// Common BSS is allowed to have gaps and overlaps to accurately match the common BSS inflation bug
if matches!(common_bss, Some(addr) if
section_index == addr.section && symbol.address as u32 >= addr.address)
if matches!(common_bss, Some((idx, addr)) if
section_index == idx && symbol.address as u32 >= addr)
{
continue;
}
@ -758,32 +758,26 @@ fn trim_linker_generated_symbols(obj: &mut ObjInfo) -> Result<()> {
pub fn update_splits(obj: &mut ObjInfo, common_start: Option<u32>, fill_gaps: bool) -> Result<()> {
// Create splits for extab and extabindex entries
if let Some((section_index, section)) = obj.sections.by_name("extabindex")? {
if !section.data.is_empty() {
let start = SectionAddress::new(section_index, section.address as u32);
split_extabindex(obj, start)?;
}
let start = SectionAddress::new(section_index, section.address as u32);
split_extabindex(obj, start)?;
}
// Create splits for .ctors entries
if let Some((section_index, section)) = obj.sections.by_name(".ctors")? {
if !section.data.is_empty() {
let start = SectionAddress::new(section_index, section.address as u32);
let end = start + (section.size as u32 - 4);
split_ctors_dtors(obj, start, end)?;
}
let start = SectionAddress::new(section_index, section.address as u32);
let end = start + (section.size as u32 - 4);
split_ctors_dtors(obj, start, end)?;
}
// Create splits for .dtors entries
if let Some((section_index, section)) = obj.sections.by_name(".dtors")? {
if !section.data.is_empty() {
let mut start = SectionAddress::new(section_index, section.address as u32);
let end = start + (section.size as u32 - 4);
if obj.kind == ObjKind::Executable {
// Skip __destroy_global_chain_reference
start += 4;
}
split_ctors_dtors(obj, start, end)?;
let mut start = SectionAddress::new(section_index, section.address as u32);
let end = start + (section.size as u32 - 4);
if obj.kind == ObjKind::Executable {
// Skip __destroy_global_chain_reference
start += 4;
}
split_ctors_dtors(obj, start, end)?;
}
// Remove linker generated symbols from splits
@ -822,8 +816,8 @@ fn resolve_link_order(obj: &ObjInfo) -> Result<Vec<ObjUnit>> {
#[allow(dead_code)]
#[derive(Debug, Copy, Clone)]
struct SplitEdge {
from: i64,
to: i64,
from: u32,
to: u32,
}
let mut graph = Graph::<String, SplitEdge>::new();
@ -858,36 +852,11 @@ fn resolve_link_order(obj: &ObjInfo) -> Result<Vec<ObjUnit>> {
);
let a_index = *unit_to_index_map.get(&a.unit).unwrap();
let b_index = *unit_to_index_map.get(&b.unit).unwrap();
graph.add_edge(a_index, b_index, SplitEdge {
from: a_addr as i64,
to: b_addr as i64,
});
graph.add_edge(a_index, b_index, SplitEdge { from: a_addr, to: b_addr });
}
}
}
// Apply link order constraints provided by the user
let mut ordered_units = BTreeMap::<i32, String>::new();
for unit in &obj.link_order {
if let Some(order) = unit.order {
match ordered_units.entry(order) {
btree_map::Entry::Vacant(entry) => {
entry.insert(unit.name.clone());
}
btree_map::Entry::Occupied(entry) => {
bail!("Duplicate order {} for units {} and {}", order, entry.get(), unit.name);
}
}
}
}
let mut iter = ordered_units
.into_iter()
.filter_map(|(order, unit)| unit_to_index_map.get(&unit).map(|&index| (order, index)))
.peekable();
while let (Some((a_order, a_index)), Some((b_order, b_index))) = (iter.next(), iter.peek()) {
graph.add_edge(a_index, *b_index, SplitEdge { from: a_order as i64, to: *b_order as i64 });
}
// use petgraph::{
// dot::{Config, Dot},
// graph::EdgeReference,
@ -917,7 +886,6 @@ fn resolve_link_order(obj: &ObjInfo) -> Result<Vec<ObjUnit>> {
name: name.clone(),
autogenerated: obj.is_unit_autogenerated(name),
comment_version: None,
order: None,
}
}
})
@ -933,11 +901,11 @@ fn resolve_link_order(obj: &ObjInfo) -> Result<Vec<ObjUnit>> {
#[instrument(level = "debug", skip(obj))]
pub fn split_obj(obj: &ObjInfo, module_name: Option<&str>) -> Result<Vec<ObjInfo>> {
let mut objects: Vec<ObjInfo> = vec![];
let mut object_symbols: Vec<Vec<Option<SymbolIndex>>> = vec![];
let mut object_symbols: Vec<Vec<Option<usize>>> = vec![];
let mut name_to_obj: HashMap<String, usize> = HashMap::new();
for unit in &obj.link_order {
name_to_obj.insert(unit.name.clone(), objects.len());
object_symbols.push(vec![None; obj.symbols.count() as usize]);
object_symbols.push(vec![None; obj.symbols.count()]);
let mut split_obj = ObjInfo::new(
ObjKind::Relocatable,
ObjArchitecture::PowerPc,
@ -1079,7 +1047,7 @@ pub fn split_obj(obj: &ObjInfo, module_name: Option<&str>) -> Result<Vec<ObjInfo
s.section == Some(section_index) && !is_linker_generated_label(&s.name)
})
{
if symbol_idxs[symbol_idx as usize].is_some() {
if symbol_idxs[symbol_idx].is_some() {
continue; // should never happen?
}
@ -1092,7 +1060,7 @@ pub fn split_obj(obj: &ObjInfo, module_name: Option<&str>) -> Result<Vec<ObjInfo
continue;
}
let new_index = split_obj.symbols.add_direct(ObjSymbol {
symbol_idxs[symbol_idx] = Some(split_obj.symbols.add_direct(ObjSymbol {
name: symbol.name.clone(),
demangled_name: symbol.demangled_name.clone(),
address: if split.common {
@ -1113,8 +1081,7 @@ pub fn split_obj(obj: &ObjInfo, module_name: Option<&str>) -> Result<Vec<ObjInfo
data_kind: symbol.data_kind,
name_hash: symbol.name_hash,
demangled_name_hash: symbol.demangled_name_hash,
})?;
symbol_idxs[symbol_idx as usize] = Some(new_index);
})?);
}
// For mwldeppc 2.7 and above, a .comment section is required to link without error
@ -1157,7 +1124,7 @@ pub fn split_obj(obj: &ObjInfo, module_name: Option<&str>) -> Result<Vec<ObjInfo
let symbol_idxs = &mut object_symbols[obj_idx];
for (_section_index, section) in out_obj.sections.iter_mut() {
for (reloc_address, reloc) in section.relocations.iter_mut() {
match symbol_idxs[reloc.target_symbol as usize] {
match symbol_idxs[reloc.target_symbol] {
Some(out_sym_idx) => {
reloc.target_symbol = out_sym_idx;
}
@ -1190,7 +1157,7 @@ pub fn split_obj(obj: &ObjInfo, module_name: Option<&str>) -> Result<Vec<ObjInfo
globalize_symbols.push((reloc.target_symbol, new_name));
}
symbol_idxs[reloc.target_symbol as usize] = Some(out_sym_idx);
symbol_idxs[reloc.target_symbol] = Some(out_sym_idx);
out_obj.symbols.add_direct(ObjSymbol {
name: target_sym.name.clone(),
demangled_name: target_sym.demangled_name.clone(),
@ -1234,7 +1201,7 @@ pub fn split_obj(obj: &ObjInfo, module_name: Option<&str>) -> Result<Vec<ObjInfo
// Upgrade local symbols to global if necessary
for (obj, symbol_map) in objects.iter_mut().zip(&object_symbols) {
for (globalize_idx, new_name) in &globalize_symbols {
if let Some(symbol_idx) = symbol_map[*globalize_idx as usize] {
if let Some(symbol_idx) = symbol_map[*globalize_idx] {
let mut symbol = obj.symbols[symbol_idx].clone();
symbol.name.clone_from(new_name);
if symbol.flags.is_local() {
@ -1249,7 +1216,7 @@ pub fn split_obj(obj: &ObjInfo, module_name: Option<&str>) -> Result<Vec<ObjInfo
// Extern linker generated symbols
for obj in &mut objects {
let mut replace_symbols = vec![];
for (symbol_idx, symbol) in obj.symbols.iter() {
for (symbol_idx, symbol) in obj.symbols.iter().enumerate() {
if is_linker_generated_label(&symbol.name) && symbol.section.is_some() {
log::debug!("Externing {:?} in {}", symbol, obj.name);
replace_symbols.push((symbol_idx, ObjSymbol {
@ -1353,15 +1320,12 @@ pub fn is_linker_generated_object(name: &str) -> bool {
}
/// Locate the end address of a section when excluding linker generated objects
pub fn end_for_section(obj: &ObjInfo, section_index: SectionIndex) -> Result<SectionAddress> {
pub fn end_for_section(obj: &ObjInfo, section_index: usize) -> Result<SectionAddress> {
let section = obj
.sections
.get(section_index)
.ok_or_else(|| anyhow!("Invalid section index: {}", section_index))?;
let mut section_end = (section.address + section.size) as u32;
if section.data.is_empty() {
return Ok(SectionAddress::new(section_index, section_end));
}
// .ctors and .dtors end with a linker-generated null pointer,
// adjust section size appropriately
if matches!(section.name.as_str(), ".ctors" | ".dtors")

View File

@ -1,15 +1,14 @@
use std::{borrow::Cow, ffi::CStr, mem::size_of};
use anyhow::Result;
use typed_path::Utf8UnixPath;
use zerocopy::{big_endian::U32, FromBytes, Immutable, IntoBytes, KnownLayout};
use zerocopy::{big_endian::U32, AsBytes, FromBytes, FromZeroes};
use crate::{static_assert, vfs::next_non_empty};
use crate::static_assert;
pub const U8_MAGIC: [u8; 4] = [0x55, 0xAA, 0x38, 0x2D];
/// U8 archive header.
#[derive(Clone, Debug, PartialEq, FromBytes, IntoBytes, Immutable, KnownLayout)]
#[derive(Clone, Debug, PartialEq, FromBytes, FromZeroes, AsBytes)]
#[repr(C, align(4))]
pub struct U8Header {
magic: [u8; 4],
@ -33,7 +32,7 @@ pub enum U8NodeKind {
}
/// An individual file system node.
#[derive(Copy, Clone, Debug, PartialEq, FromBytes, IntoBytes, Immutable, KnownLayout)]
#[derive(Clone, Debug, PartialEq, FromBytes, FromZeroes, AsBytes)]
#[repr(C, align(4))]
pub struct U8Node {
kind: u8,
@ -92,7 +91,7 @@ pub struct U8View<'a> {
impl<'a> U8View<'a> {
/// Create a new U8 view from a buffer.
pub fn new(buf: &'a [u8]) -> Result<Self, &'static str> {
let Ok((header, _)) = U8Header::ref_from_prefix(buf) else {
let Some(header) = U8Header::ref_from_prefix(buf) else {
return Err("Buffer not large enough for U8 header");
};
if header.magic != U8_MAGIC {
@ -102,8 +101,7 @@ impl<'a> U8View<'a> {
let nodes_buf = buf
.get(node_table_offset..node_table_offset + header.node_table_size.get() as usize)
.ok_or("U8 node table out of bounds")?;
let (root_node, _) =
U8Node::ref_from_prefix(nodes_buf).map_err(|_| "U8 root node not aligned")?;
let root_node = U8Node::ref_from_prefix(nodes_buf).ok_or("U8 root node not aligned")?;
if root_node.kind() != U8NodeKind::Directory {
return Err("U8 root node is not a directory");
}
@ -115,8 +113,7 @@ impl<'a> U8View<'a> {
return Err("U8 node table size mismatch");
}
let (nodes_buf, string_table) = nodes_buf.split_at(node_count * size_of::<U8Node>());
let nodes =
<[U8Node]>::ref_from_bytes(nodes_buf).map_err(|_| "U8 node table not aligned")?;
let nodes = U8Node::slice_from(nodes_buf).ok_or("U8 node table not aligned")?;
Ok(Self { header, nodes, string_table })
}
@ -124,7 +121,7 @@ impl<'a> U8View<'a> {
pub fn iter(&self) -> U8Iter { U8Iter { inner: self, idx: 1 } }
/// Get the name of a node.
pub fn get_name(&self, node: U8Node) -> Result<Cow<str>, String> {
pub fn get_name(&self, node: &U8Node) -> Result<Cow<str>, String> {
let name_buf = self.string_table.get(node.name_offset() as usize..).ok_or_else(|| {
format!(
"U8: name offset {} out of bounds (string table size: {})",
@ -139,29 +136,22 @@ impl<'a> U8View<'a> {
}
/// Finds a particular file or directory by path.
pub fn find(&self, path: &Utf8UnixPath) -> Option<(usize, U8Node)> {
let mut split = path.as_str().split('/');
let mut current = next_non_empty(&mut split);
if current.is_empty() {
return Some((0, self.nodes[0]));
}
pub fn find(&self, path: &str) -> Option<(usize, &U8Node)> {
let mut split = path.trim_matches('/').split('/');
let mut current = split.next()?;
let mut idx = 1;
let mut stop_at = None;
while let Some(node) = self.nodes.get(idx).copied() {
if self.get_name(node).map_or(false, |name| name.eq_ignore_ascii_case(current)) {
current = next_non_empty(&mut split);
if current.is_empty() {
while let Some(node) = self.nodes.get(idx) {
if self.get_name(node).as_ref().map_or(false, |name| name.eq_ignore_ascii_case(current))
{
if let Some(next) = split.next() {
current = next;
} else {
return Some((idx, node));
}
if node.is_dir() {
// Descend into directory
idx += 1;
stop_at = Some(node.length() as usize + idx);
} else {
// Not a directory
break;
}
// Descend into directory
idx += 1;
stop_at = Some(node.length() as usize + idx);
} else if node.is_dir() {
// Skip directory
idx = node.length() as usize;
@ -186,11 +176,11 @@ pub struct U8Iter<'a> {
}
impl<'a> Iterator for U8Iter<'a> {
type Item = (usize, U8Node, Result<Cow<'a, str>, String>);
type Item = (usize, &'a U8Node, Result<Cow<'a, str>, String>);
fn next(&mut self) -> Option<Self::Item> {
let idx = self.idx;
let node = self.inner.nodes.get(idx).copied()?;
let node = self.inner.nodes.get(idx)?;
let name = self.inner.get_name(node);
self.idx += 1;
Some((idx, node, name))

View File

@ -1,130 +0,0 @@
use std::{
io,
io::{BufRead, Cursor, Read, Seek, SeekFrom},
sync::Arc,
};
use filetime::FileTime;
use super::{DiscStream, VfsFileType, VfsMetadata};
use crate::vfs::VfsFile;
#[derive(Clone)]
pub struct StaticFile {
inner: Cursor<Arc<[u8]>>,
mtime: Option<FileTime>,
}
impl StaticFile {
pub fn new(data: Arc<[u8]>, mtime: Option<FileTime>) -> Self {
Self { inner: Cursor::new(data), mtime }
}
}
impl BufRead for StaticFile {
fn fill_buf(&mut self) -> io::Result<&[u8]> { self.inner.fill_buf() }
fn consume(&mut self, amt: usize) { self.inner.consume(amt) }
}
impl Read for StaticFile {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { self.inner.read(buf) }
}
impl Seek for StaticFile {
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> { self.inner.seek(pos) }
}
impl VfsFile for StaticFile {
fn map(&mut self) -> io::Result<&[u8]> { Ok(self.inner.get_ref()) }
fn metadata(&mut self) -> io::Result<VfsMetadata> {
Ok(VfsMetadata {
file_type: VfsFileType::File,
len: self.inner.get_ref().len() as u64,
mtime: self.mtime,
})
}
fn into_disc_stream(self: Box<Self>) -> Box<dyn DiscStream> { self }
}
#[derive(Clone)]
pub struct WindowedFile {
base: Box<dyn VfsFile>,
pos: u64,
begin: u64,
end: u64,
}
impl WindowedFile {
pub fn new(mut base: Box<dyn VfsFile>, offset: u64, size: u64) -> io::Result<Self> {
base.seek(SeekFrom::Start(offset))?;
Ok(Self { base, pos: offset, begin: offset, end: offset + size })
}
#[inline]
pub fn len(&self) -> u64 { self.end - self.begin }
}
impl BufRead for WindowedFile {
fn fill_buf(&mut self) -> io::Result<&[u8]> {
let buf = self.base.fill_buf()?;
let remaining = self.end.saturating_sub(self.pos);
Ok(&buf[..buf.len().min(remaining as usize)])
}
fn consume(&mut self, amt: usize) {
let remaining = self.end.saturating_sub(self.pos);
let amt = amt.min(remaining as usize);
self.base.consume(amt);
self.pos += amt as u64;
}
}
impl Read for WindowedFile {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let remaining = self.end.saturating_sub(self.pos);
if remaining == 0 {
return Ok(0);
}
let len = buf.len().min(remaining as usize);
self.base.read(&mut buf[..len])
}
}
impl Seek for WindowedFile {
#[inline]
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
let mut pos = match pos {
SeekFrom::Start(p) => self.begin + p,
SeekFrom::End(p) => self.end.saturating_add_signed(p),
SeekFrom::Current(p) => self.pos.saturating_add_signed(p),
};
if pos < self.begin {
pos = self.begin;
} else if pos > self.end {
pos = self.end;
}
let result = self.base.seek(SeekFrom::Start(pos))?;
self.pos = result;
Ok(result - self.begin)
}
#[inline]
fn stream_position(&mut self) -> io::Result<u64> { Ok(self.pos) }
}
impl VfsFile for WindowedFile {
fn map(&mut self) -> io::Result<&[u8]> {
let buf = self.base.map()?;
Ok(&buf[self.pos as usize..self.end as usize])
}
fn metadata(&mut self) -> io::Result<VfsMetadata> {
let metadata = self.base.metadata()?;
Ok(VfsMetadata { file_type: VfsFileType::File, len: self.len(), mtime: metadata.mtime })
}
fn into_disc_stream(self: Box<Self>) -> Box<dyn DiscStream> { self }
}

View File

@ -1,337 +0,0 @@
use std::{
io,
io::{BufRead, Cursor, Read, Seek, SeekFrom},
sync::Arc,
};
use filetime::FileTime;
use nodtool::{
nod,
nod::{Disc, DiscStream, Fst, NodeKind, OwnedFileStream, PartitionBase, PartitionMeta},
};
use typed_path::Utf8UnixPath;
use zerocopy::IntoBytes;
use super::{
next_non_empty, StaticFile, Vfs, VfsError, VfsFile, VfsFileType, VfsMetadata, VfsResult,
};
#[derive(Clone)]
pub struct DiscFs {
disc: Arc<Disc>,
base: Box<dyn PartitionBase>,
meta: Box<PartitionMeta>,
mtime: Option<FileTime>,
}
enum SpecialDir {
Root,
Sys,
Disc,
}
enum DiscNode<'a> {
None,
Special(SpecialDir),
Node(Fst<'a>, usize, nod::Node),
Static(&'a [u8]),
}
impl DiscFs {
pub fn new(
disc: Arc<Disc>,
mut base: Box<dyn PartitionBase>,
mtime: Option<FileTime>,
) -> io::Result<Self> {
let meta = base.meta().map_err(nod_to_io_error)?;
Ok(Self { disc, base, meta, mtime })
}
fn find(&self, path: &Utf8UnixPath) -> VfsResult<DiscNode> {
let path = path.as_str().trim_matches('/');
let mut split = path.split('/');
let mut segment = next_non_empty(&mut split);
match segment.to_ascii_lowercase().as_str() {
"" => Ok(DiscNode::Special(SpecialDir::Root)),
"files" => {
let fst = Fst::new(&self.meta.raw_fst)?;
if next_non_empty(&mut split).is_empty() {
let root = fst.nodes[0];
return Ok(DiscNode::Node(fst, 0, root));
}
let remainder = &path[segment.len() + 1..];
match fst.find(remainder) {
Some((idx, node)) => Ok(DiscNode::Node(fst, idx, node)),
None => Ok(DiscNode::None),
}
}
"sys" => {
segment = next_non_empty(&mut split);
// No directories in sys
if !next_non_empty(&mut split).is_empty() {
return Ok(DiscNode::None);
}
match segment.to_ascii_lowercase().as_str() {
"" => Ok(DiscNode::Special(SpecialDir::Sys)),
"boot.bin" => Ok(DiscNode::Static(self.meta.raw_boot.as_slice())),
"bi2.bin" => Ok(DiscNode::Static(self.meta.raw_bi2.as_slice())),
"apploader.img" => Ok(DiscNode::Static(self.meta.raw_apploader.as_ref())),
"fst.bin" => Ok(DiscNode::Static(self.meta.raw_fst.as_ref())),
"main.dol" => Ok(DiscNode::Static(self.meta.raw_dol.as_ref())),
_ => Ok(DiscNode::None),
}
}
"disc" => {
if !self.disc.header().is_wii() {
return Ok(DiscNode::None);
}
segment = next_non_empty(&mut split);
// No directories in disc
if !next_non_empty(&mut split).is_empty() {
return Ok(DiscNode::None);
}
match segment.to_ascii_lowercase().as_str() {
"" => Ok(DiscNode::Special(SpecialDir::Disc)),
"header.bin" => Ok(DiscNode::Static(&self.disc.header().as_bytes()[..0x100])),
"region.bin" => {
Ok(DiscNode::Static(self.disc.region().ok_or(VfsError::NotFound)?))
}
_ => Ok(DiscNode::None),
}
}
"ticket.bin" => {
Ok(DiscNode::Static(self.meta.raw_ticket.as_deref().ok_or(VfsError::NotFound)?))
}
"tmd.bin" => {
Ok(DiscNode::Static(self.meta.raw_tmd.as_deref().ok_or(VfsError::NotFound)?))
}
"cert.bin" => {
Ok(DiscNode::Static(self.meta.raw_cert_chain.as_deref().ok_or(VfsError::NotFound)?))
}
"h3.bin" => {
Ok(DiscNode::Static(self.meta.raw_h3_table.as_deref().ok_or(VfsError::NotFound)?))
}
_ => Ok(DiscNode::None),
}
}
}
impl Vfs for DiscFs {
fn open(&mut self, path: &Utf8UnixPath) -> VfsResult<Box<dyn VfsFile>> {
match self.find(path)? {
DiscNode::None => Err(VfsError::NotFound),
DiscNode::Special(_) => Err(VfsError::IsADirectory),
DiscNode::Node(_, _, node) => match node.kind() {
NodeKind::File => {
if node.length() > 2048 {
let file = self.base.clone().into_open_file(node)?;
Ok(Box::new(DiscFile::new(file, self.mtime)))
} else {
let len = node.length() as usize;
let mut file = self.base.open_file(node)?;
let mut data = vec![0u8; len];
file.read_exact(&mut data)?;
Ok(Box::new(StaticFile::new(Arc::from(data.as_slice()), self.mtime)))
}
}
NodeKind::Directory => Err(VfsError::IsADirectory),
NodeKind::Invalid => Err(VfsError::from("FST: Invalid node kind")),
},
DiscNode::Static(data) => Ok(Box::new(StaticFile::new(Arc::from(data), self.mtime))),
}
}
fn exists(&mut self, path: &Utf8UnixPath) -> VfsResult<bool> {
Ok(!matches!(self.find(path)?, DiscNode::None))
}
fn read_dir(&mut self, path: &Utf8UnixPath) -> VfsResult<Vec<String>> {
match self.find(path)? {
DiscNode::None => Err(VfsError::NotFound),
DiscNode::Special(SpecialDir::Root) => {
let mut entries = vec!["files".to_string(), "sys".to_string()];
if self.disc.header().is_wii() {
entries.push("disc".to_string());
}
if self.meta.raw_cert_chain.is_some() {
entries.push("cert.bin".to_string());
}
if self.meta.raw_h3_table.is_some() {
entries.push("h3.bin".to_string());
}
if self.meta.raw_ticket.is_some() {
entries.push("ticket.bin".to_string());
}
if self.meta.raw_tmd.is_some() {
entries.push("tmd.bin".to_string());
}
Ok(entries)
}
DiscNode::Special(SpecialDir::Sys) => Ok(vec![
"boot.bin".to_string(),
"bi2.bin".to_string(),
"apploader.img".to_string(),
"fst.bin".to_string(),
"main.dol".to_string(),
]),
DiscNode::Special(SpecialDir::Disc) => {
let mut entries = Vec::new();
if self.disc.header().is_wii() {
entries.push("header.bin".to_string());
if self.disc.region().is_some() {
entries.push("region.bin".to_string());
}
}
Ok(entries)
}
DiscNode::Node(fst, idx, node) => {
match node.kind() {
NodeKind::File => return Err(VfsError::NotADirectory),
NodeKind::Directory => {}
NodeKind::Invalid => return Err(VfsError::from("FST: Invalid node kind")),
}
let mut entries = Vec::new();
let mut idx = idx + 1;
let end = node.length() as usize;
while idx < end {
let child = fst
.nodes
.get(idx)
.copied()
.ok_or_else(|| VfsError::from("FST: Node index out of bounds"))?;
entries.push(fst.get_name(child)?.to_string());
if child.is_dir() {
idx = child.length() as usize;
} else {
idx += 1;
}
}
Ok(entries)
}
DiscNode::Static(_) => Err(VfsError::NotADirectory),
}
}
fn metadata(&mut self, path: &Utf8UnixPath) -> VfsResult<VfsMetadata> {
match self.find(path)? {
DiscNode::None => Err(VfsError::NotFound),
DiscNode::Special(_) => {
Ok(VfsMetadata { file_type: VfsFileType::Directory, len: 0, mtime: self.mtime })
}
DiscNode::Node(_, _, node) => {
let (file_type, len) = match node.kind() {
NodeKind::File => (VfsFileType::File, node.length()),
NodeKind::Directory => (VfsFileType::Directory, 0),
NodeKind::Invalid => return Err(VfsError::from("FST: Invalid node kind")),
};
Ok(VfsMetadata { file_type, len, mtime: self.mtime })
}
DiscNode::Static(data) => Ok(VfsMetadata {
file_type: VfsFileType::File,
len: data.len() as u64,
mtime: self.mtime,
}),
}
}
}
#[derive(Clone)]
enum DiscFileInner {
Stream(OwnedFileStream),
Mapped(Cursor<Arc<[u8]>>),
}
#[derive(Clone)]
struct DiscFile {
inner: DiscFileInner,
mtime: Option<FileTime>,
}
impl DiscFile {
pub fn new(file: OwnedFileStream, mtime: Option<FileTime>) -> Self {
Self { inner: DiscFileInner::Stream(file), mtime }
}
fn convert_to_mapped(&mut self) {
match &mut self.inner {
DiscFileInner::Stream(stream) => {
let pos = stream.stream_position().unwrap();
stream.seek(SeekFrom::Start(0)).unwrap();
let mut data = vec![0u8; stream.len() as usize];
stream.read_exact(&mut data).unwrap();
let mut cursor = Cursor::new(Arc::from(data.as_slice()));
cursor.set_position(pos);
self.inner = DiscFileInner::Mapped(cursor);
}
DiscFileInner::Mapped(_) => {}
};
}
}
impl BufRead for DiscFile {
fn fill_buf(&mut self) -> io::Result<&[u8]> {
match &mut self.inner {
DiscFileInner::Stream(stream) => stream.fill_buf(),
DiscFileInner::Mapped(data) => data.fill_buf(),
}
}
fn consume(&mut self, amt: usize) {
match &mut self.inner {
DiscFileInner::Stream(stream) => stream.consume(amt),
DiscFileInner::Mapped(data) => data.consume(amt),
}
}
}
impl Read for DiscFile {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
match &mut self.inner {
DiscFileInner::Stream(stream) => stream.read(buf),
DiscFileInner::Mapped(data) => data.read(buf),
}
}
}
impl Seek for DiscFile {
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
match &mut self.inner {
DiscFileInner::Stream(stream) => stream.seek(pos),
DiscFileInner::Mapped(data) => data.seek(pos),
}
}
}
impl VfsFile for DiscFile {
fn map(&mut self) -> io::Result<&[u8]> {
self.convert_to_mapped();
match &mut self.inner {
DiscFileInner::Stream(_) => unreachable!(),
DiscFileInner::Mapped(data) => Ok(data.get_ref()),
}
}
fn metadata(&mut self) -> io::Result<VfsMetadata> {
match &mut self.inner {
DiscFileInner::Stream(stream) => Ok(VfsMetadata {
file_type: VfsFileType::File,
len: stream.len(),
mtime: self.mtime,
}),
DiscFileInner::Mapped(data) => Ok(VfsMetadata {
file_type: VfsFileType::File,
len: data.get_ref().len() as u64,
mtime: self.mtime,
}),
}
}
fn into_disc_stream(self: Box<Self>) -> Box<dyn DiscStream> { self }
}
pub fn nod_to_io_error(e: nod::Error) -> io::Error {
match e {
nod::Error::Io(msg, e) => io::Error::new(e.kind(), format!("{}: {}", msg, e)),
e => io::Error::new(io::ErrorKind::InvalidData, e),
}
}

View File

@ -1,373 +0,0 @@
mod common;
mod disc;
mod rarc;
mod std_fs;
mod u8_arc;
use std::{
error::Error,
fmt::{Debug, Display, Formatter},
io,
io::{BufRead, Read, Seek, SeekFrom},
sync::Arc,
};
use anyhow::{anyhow, Context};
use common::{StaticFile, WindowedFile};
use disc::{nod_to_io_error, DiscFs};
use dyn_clone::DynClone;
use filetime::FileTime;
use nodtool::{nod, nod::DiscStream};
use rarc::RarcFs;
pub use std_fs::StdFs;
use typed_path::{Utf8NativePath, Utf8UnixPath, Utf8UnixPathBuf};
use u8_arc::U8Fs;
use crate::util::{
ncompress::{YAY0_MAGIC, YAZ0_MAGIC},
nlzss,
rarc::RARC_MAGIC,
u8_arc::U8_MAGIC,
};
pub trait Vfs: DynClone + Send + Sync {
fn open(&mut self, path: &Utf8UnixPath) -> VfsResult<Box<dyn VfsFile>>;
fn exists(&mut self, path: &Utf8UnixPath) -> VfsResult<bool>;
fn read_dir(&mut self, path: &Utf8UnixPath) -> VfsResult<Vec<String>>;
fn metadata(&mut self, path: &Utf8UnixPath) -> VfsResult<VfsMetadata>;
}
dyn_clone::clone_trait_object!(Vfs);
pub trait VfsFile: DiscStream + BufRead {
fn map(&mut self) -> io::Result<&[u8]>;
fn metadata(&mut self) -> io::Result<VfsMetadata>;
fn into_disc_stream(self: Box<Self>) -> Box<dyn DiscStream>;
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum VfsFileType {
File,
Directory,
}
pub struct VfsMetadata {
pub file_type: VfsFileType,
pub len: u64,
pub mtime: Option<FileTime>,
}
impl VfsMetadata {
pub fn is_file(&self) -> bool { self.file_type == VfsFileType::File }
pub fn is_dir(&self) -> bool { self.file_type == VfsFileType::Directory }
}
dyn_clone::clone_trait_object!(VfsFile);
#[derive(Debug)]
pub enum VfsError {
NotFound,
NotADirectory,
IsADirectory,
IoError(io::Error),
Other(String),
}
pub type VfsResult<T, E = VfsError> = Result<T, E>;
impl From<io::Error> for VfsError {
fn from(e: io::Error) -> Self {
match e.kind() {
io::ErrorKind::NotFound => VfsError::NotFound,
// TODO: stabilized in Rust 1.83
// io::ErrorKind::NotADirectory => VfsError::NotADirectory,
// io::ErrorKind::IsADirectory => VfsError::IsADirectory,
_ => VfsError::IoError(e),
}
}
}
impl From<String> for VfsError {
fn from(e: String) -> Self { VfsError::Other(e) }
}
impl From<&str> for VfsError {
fn from(e: &str) -> Self { VfsError::Other(e.to_string()) }
}
impl Display for VfsError {
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
match self {
VfsError::NotFound => write!(f, "File or directory not found"),
VfsError::IoError(e) => write!(f, "{}", e),
VfsError::Other(e) => write!(f, "{}", e),
VfsError::NotADirectory => write!(f, "Path is a file, not a directory"),
VfsError::IsADirectory => write!(f, "Path is a directory, not a file"),
}
}
}
impl Error for VfsError {}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum FileFormat {
Regular,
Compressed(CompressionKind),
Archive(ArchiveKind),
}
impl Display for FileFormat {
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
match self {
FileFormat::Regular => write!(f, "File"),
FileFormat::Compressed(kind) => write!(f, "Compressed: {}", kind),
FileFormat::Archive(kind) => write!(f, "Archive: {}", kind),
}
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum CompressionKind {
Yay0,
Yaz0,
Nlzss,
}
impl Display for CompressionKind {
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
match self {
CompressionKind::Yay0 => write!(f, "Yay0"),
CompressionKind::Yaz0 => write!(f, "Yaz0"),
CompressionKind::Nlzss => write!(f, "NLZSS"),
}
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum ArchiveKind {
Rarc,
U8,
Disc(nod::Format),
}
impl Display for ArchiveKind {
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
match self {
ArchiveKind::Rarc => write!(f, "RARC"),
ArchiveKind::U8 => write!(f, "U8"),
ArchiveKind::Disc(format) => write!(f, "Disc ({})", format),
}
}
}
pub fn detect<R>(file: &mut R) -> io::Result<FileFormat>
where R: Read + Seek + ?Sized {
file.seek(SeekFrom::Start(0))?;
let mut magic = [0u8; 4];
match file.read_exact(&mut magic) {
Ok(_) => {}
Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => return Ok(FileFormat::Regular),
Err(e) => return Err(e),
}
file.seek_relative(-4)?;
match magic {
YAY0_MAGIC => Ok(FileFormat::Compressed(CompressionKind::Yay0)),
YAZ0_MAGIC => Ok(FileFormat::Compressed(CompressionKind::Yaz0)),
RARC_MAGIC => Ok(FileFormat::Archive(ArchiveKind::Rarc)),
U8_MAGIC => Ok(FileFormat::Archive(ArchiveKind::U8)),
_ => {
let format = nod::Disc::detect(file)?;
file.seek(SeekFrom::Start(0))?;
match format {
Some(format) => Ok(FileFormat::Archive(ArchiveKind::Disc(format))),
None => Ok(FileFormat::Regular),
}
}
}
}
pub enum OpenResult {
File(Box<dyn VfsFile>, Utf8UnixPathBuf),
Directory(Box<dyn Vfs>, Utf8UnixPathBuf),
}
pub fn open_path(path: &Utf8NativePath, auto_decompress: bool) -> anyhow::Result<OpenResult> {
open_path_with_fs(Box::new(StdFs), path, auto_decompress)
}
pub fn open_path_with_fs(
mut fs: Box<dyn Vfs>,
path: &Utf8NativePath,
auto_decompress: bool,
) -> anyhow::Result<OpenResult> {
let path = path.with_unix_encoding();
let mut split = path.as_str().split(':').peekable();
let mut current_path = String::new();
let mut file: Option<Box<dyn VfsFile>> = None;
let mut segment = Utf8UnixPath::new("");
loop {
// Open the next segment if necessary
if file.is_none() {
segment = Utf8UnixPath::new(split.next().unwrap());
if !current_path.is_empty() {
current_path.push(':');
}
current_path.push_str(segment.as_str());
let file_type = match fs.metadata(segment) {
Ok(metadata) => metadata.file_type,
Err(VfsError::NotFound) => return Err(anyhow!("{} not found", current_path)),
Err(e) => return Err(e).context(format!("Failed to open {}", current_path)),
};
match file_type {
VfsFileType::File => {
file = Some(
fs.open(segment)
.with_context(|| format!("Failed to open {}", current_path))?,
);
}
VfsFileType::Directory => {
return if split.peek().is_some() {
Err(anyhow!("{} is not a file", current_path))
} else {
Ok(OpenResult::Directory(fs, segment.to_path_buf()))
}
}
}
}
let mut current_file = file.take().unwrap();
let format = detect(current_file.as_mut())
.with_context(|| format!("Failed to detect file type for {}", current_path))?;
if let Some(&next) = split.peek() {
match next {
"nlzss" => {
split.next();
file = Some(
decompress_file(current_file.as_mut(), CompressionKind::Nlzss)
.with_context(|| {
format!("Failed to decompress {} with NLZSS", current_path)
})?,
);
}
"yay0" => {
split.next();
file = Some(
decompress_file(current_file.as_mut(), CompressionKind::Yay0)
.with_context(|| {
format!("Failed to decompress {} with Yay0", current_path)
})?,
);
}
"yaz0" => {
split.next();
file = Some(
decompress_file(current_file.as_mut(), CompressionKind::Yaz0)
.with_context(|| {
format!("Failed to decompress {} with Yaz0", current_path)
})?,
);
}
_ => match format {
FileFormat::Regular => {
return Err(anyhow!("{} is not an archive", current_path))
}
FileFormat::Compressed(kind) => {
file =
Some(decompress_file(current_file.as_mut(), kind).with_context(
|| format!("Failed to decompress {}", current_path),
)?);
// Continue the loop to detect the new format
}
FileFormat::Archive(kind) => {
fs = open_fs(current_file, kind).with_context(|| {
format!("Failed to open container {}", current_path)
})?;
// Continue the loop to open the next segment
}
},
}
} else {
// No more segments, return as-is
return match format {
FileFormat::Compressed(kind) if auto_decompress => Ok(OpenResult::File(
decompress_file(current_file.as_mut(), kind)
.with_context(|| format!("Failed to decompress {}", current_path))?,
segment.to_path_buf(),
)),
_ => Ok(OpenResult::File(current_file, segment.to_path_buf())),
};
}
}
}
pub fn open_file(path: &Utf8NativePath, auto_decompress: bool) -> anyhow::Result<Box<dyn VfsFile>> {
open_file_with_fs(Box::new(StdFs), path, auto_decompress)
}
pub fn open_file_with_fs(
fs: Box<dyn Vfs>,
path: &Utf8NativePath,
auto_decompress: bool,
) -> anyhow::Result<Box<dyn VfsFile>> {
match open_path_with_fs(fs, path, auto_decompress)? {
OpenResult::File(file, _) => Ok(file),
OpenResult::Directory(_, _) => Err(VfsError::IsADirectory.into()),
}
}
pub fn open_fs(mut file: Box<dyn VfsFile>, kind: ArchiveKind) -> io::Result<Box<dyn Vfs>> {
let metadata = file.metadata()?;
match kind {
ArchiveKind::Rarc => Ok(Box::new(RarcFs::new(file)?)),
ArchiveKind::U8 => Ok(Box::new(U8Fs::new(file)?)),
ArchiveKind::Disc(_) => {
let disc =
Arc::new(nod::Disc::new_stream(file.into_disc_stream()).map_err(nod_to_io_error)?);
let partition =
disc.open_partition_kind(nod::PartitionKind::Data).map_err(nod_to_io_error)?;
Ok(Box::new(DiscFs::new(disc, partition, metadata.mtime)?))
}
}
}
pub fn decompress_file(
file: &mut dyn VfsFile,
kind: CompressionKind,
) -> io::Result<Box<dyn VfsFile>> {
let metadata = file.metadata()?;
match kind {
CompressionKind::Yay0 => {
let data = file.map()?;
let result = orthrus_ncompress::yay0::Yay0::decompress_from(data)
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e.to_string()))?;
Ok(Box::new(StaticFile::new(Arc::from(result), metadata.mtime)))
}
CompressionKind::Yaz0 => {
let data = file.map()?;
let result = orthrus_ncompress::yaz0::Yaz0::decompress_from(data)
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e.to_string()))?;
Ok(Box::new(StaticFile::new(Arc::from(result), metadata.mtime)))
}
CompressionKind::Nlzss => {
let result = nlzss::decompress(file)
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e.to_string()))?;
Ok(Box::new(StaticFile::new(Arc::from(result.as_slice()), metadata.mtime)))
}
}
}
#[inline]
pub fn next_non_empty<'a>(iter: &mut impl Iterator<Item = &'a str>) -> &'a str {
loop {
match iter.next() {
Some("") => continue,
Some(next) => break next,
None => break "",
}
}
}

View File

@ -1,78 +0,0 @@
use std::io;
use typed_path::Utf8UnixPath;
use super::{Vfs, VfsError, VfsFile, VfsFileType, VfsMetadata, VfsResult, WindowedFile};
use crate::util::rarc::{RarcNodeKind, RarcView};
#[derive(Clone)]
pub struct RarcFs {
file: Box<dyn VfsFile>,
}
impl RarcFs {
pub fn new(file: Box<dyn VfsFile>) -> io::Result<Self> { Ok(Self { file }) }
fn view(&mut self) -> io::Result<RarcView> {
let data = self.file.map()?;
RarcView::new(data).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
}
}
impl Vfs for RarcFs {
fn open(&mut self, path: &Utf8UnixPath) -> VfsResult<Box<dyn VfsFile>> {
let view = self.view()?;
match view.find(path) {
Some(RarcNodeKind::File(_, node)) => {
let offset = view.header.header_len() as u64
+ view.header.data_offset() as u64
+ node.data_offset() as u64;
let len = node.data_length() as u64;
let file = WindowedFile::new(self.file.clone(), offset, len)?;
Ok(Box::new(file))
}
Some(RarcNodeKind::Directory(_, _)) => Err(VfsError::IsADirectory),
None => Err(VfsError::NotFound),
}
}
fn exists(&mut self, path: &Utf8UnixPath) -> VfsResult<bool> {
let view = self.view()?;
Ok(view.find(path).is_some())
}
fn read_dir(&mut self, path: &Utf8UnixPath) -> VfsResult<Vec<String>> {
let view = self.view()?;
match view.find(path) {
Some(RarcNodeKind::Directory(_, dir)) => {
let mut entries = Vec::new();
for node in view.children(dir) {
let name = view.get_string(node.name_offset())?;
if name == "." || name == ".." {
continue;
}
entries.push(name.to_string());
}
Ok(entries)
}
Some(RarcNodeKind::File(_, _)) => Err(VfsError::NotADirectory),
None => Err(VfsError::NotFound),
}
}
fn metadata(&mut self, path: &Utf8UnixPath) -> VfsResult<VfsMetadata> {
let metadata = self.file.metadata()?;
let view = self.view()?;
match view.find(path) {
Some(RarcNodeKind::File(_, node)) => Ok(VfsMetadata {
file_type: VfsFileType::File,
len: node.data_length() as u64,
mtime: metadata.mtime,
}),
Some(RarcNodeKind::Directory(_, _)) => {
Ok(VfsMetadata { file_type: VfsFileType::Directory, len: 0, mtime: metadata.mtime })
}
None => Err(VfsError::NotFound),
}
}
}

View File

@ -1,108 +0,0 @@
use std::{
fs, io,
io::{BufRead, BufReader, Read, Seek, SeekFrom},
};
use filetime::FileTime;
use typed_path::{Utf8NativePathBuf, Utf8UnixPath};
use super::{DiscStream, Vfs, VfsFile, VfsFileType, VfsMetadata, VfsResult};
#[derive(Clone)]
pub struct StdFs;
impl Vfs for StdFs {
fn open(&mut self, path: &Utf8UnixPath) -> VfsResult<Box<dyn VfsFile>> {
let mut file = StdFile::new(path.with_encoding());
file.file()?; // Open the file now to check for errors
Ok(Box::new(file))
}
fn exists(&mut self, path: &Utf8UnixPath) -> VfsResult<bool> {
Ok(fs::exists(path.with_encoding())?)
}
fn read_dir(&mut self, path: &Utf8UnixPath) -> VfsResult<Vec<String>> {
let entries = fs::read_dir(path.with_encoding())?
.map(|entry| entry.map(|e| e.file_name().to_string_lossy().into_owned()))
.collect::<Result<Vec<_>, _>>()?;
Ok(entries)
}
fn metadata(&mut self, path: &Utf8UnixPath) -> VfsResult<VfsMetadata> {
let metadata = fs::metadata(path.with_encoding())?;
Ok(VfsMetadata {
file_type: if metadata.is_dir() { VfsFileType::Directory } else { VfsFileType::File },
len: metadata.len(),
mtime: Some(FileTime::from_last_modification_time(&metadata)),
})
}
}
pub struct StdFile {
path: Utf8NativePathBuf,
file: Option<BufReader<fs::File>>,
mmap: Option<memmap2::Mmap>,
}
impl Clone for StdFile {
#[inline]
fn clone(&self) -> Self { Self { path: self.path.clone(), file: None, mmap: None } }
}
impl StdFile {
#[inline]
pub fn new(path: Utf8NativePathBuf) -> Self { StdFile { path, file: None, mmap: None } }
pub fn file(&mut self) -> io::Result<&mut BufReader<fs::File>> {
if self.file.is_none() {
self.file = Some(BufReader::new(fs::File::open(&self.path)?));
}
Ok(self.file.as_mut().unwrap())
}
}
impl BufRead for StdFile {
#[inline]
fn fill_buf(&mut self) -> io::Result<&[u8]> { self.file()?.fill_buf() }
#[inline]
fn consume(&mut self, amt: usize) {
if let Ok(file) = self.file() {
file.consume(amt);
}
}
}
impl Read for StdFile {
#[inline]
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { self.file()?.read(buf) }
}
impl Seek for StdFile {
#[inline]
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> { self.file()?.seek(pos) }
}
impl VfsFile for StdFile {
fn map(&mut self) -> io::Result<&[u8]> {
if self.file.is_none() {
self.file = Some(BufReader::new(fs::File::open(&self.path)?));
}
if self.mmap.is_none() {
self.mmap = Some(unsafe { memmap2::Mmap::map(self.file.as_ref().unwrap().get_ref())? });
}
Ok(self.mmap.as_ref().unwrap())
}
fn metadata(&mut self) -> io::Result<VfsMetadata> {
let metadata = fs::metadata(&self.path)?;
Ok(VfsMetadata {
file_type: if metadata.is_dir() { VfsFileType::Directory } else { VfsFileType::File },
len: metadata.len(),
mtime: Some(FileTime::from_last_modification_time(&metadata)),
})
}
fn into_disc_stream(self: Box<Self>) -> Box<dyn DiscStream> { self }
}

View File

@ -1,91 +0,0 @@
use std::io;
use typed_path::Utf8UnixPath;
use super::{Vfs, VfsError, VfsFile, VfsFileType, VfsMetadata, VfsResult, WindowedFile};
use crate::util::u8_arc::{U8NodeKind, U8View};
#[derive(Clone)]
pub struct U8Fs {
file: Box<dyn VfsFile>,
}
impl U8Fs {
pub fn new(file: Box<dyn VfsFile>) -> io::Result<Self> { Ok(Self { file }) }
fn view(&mut self) -> io::Result<U8View> {
let data = self.file.map()?;
U8View::new(data).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
}
}
impl Vfs for U8Fs {
fn open(&mut self, path: &Utf8UnixPath) -> VfsResult<Box<dyn VfsFile>> {
let view = self.view()?;
match view.find(path) {
Some((_, node)) => match node.kind() {
U8NodeKind::File => {
let offset = node.offset() as u64;
let len = node.length() as u64;
let file = WindowedFile::new(self.file.clone(), offset, len)?;
Ok(Box::new(file))
}
U8NodeKind::Directory => Err(VfsError::IsADirectory),
U8NodeKind::Invalid => Err(VfsError::from("U8: Invalid node kind")),
},
None => Err(VfsError::NotFound),
}
}
fn exists(&mut self, path: &Utf8UnixPath) -> VfsResult<bool> {
let view = self.view()?;
Ok(view.find(path).is_some())
}
fn read_dir(&mut self, path: &Utf8UnixPath) -> VfsResult<Vec<String>> {
let view = self.view()?;
match view.find(path) {
Some((idx, node)) => match node.kind() {
U8NodeKind::File => Err(VfsError::NotADirectory),
U8NodeKind::Directory => {
let mut entries = Vec::new();
let mut idx = idx + 1;
let end = node.length() as usize;
while idx < end {
let child = view.nodes.get(idx).copied().ok_or(VfsError::NotFound)?;
entries.push(view.get_name(child)?.to_string());
if child.is_dir() {
idx = child.length() as usize;
} else {
idx += 1;
}
}
Ok(entries)
}
U8NodeKind::Invalid => Err(VfsError::from("U8: Invalid node kind")),
},
None => Err(VfsError::NotFound),
}
}
fn metadata(&mut self, path: &Utf8UnixPath) -> VfsResult<VfsMetadata> {
let metdata = self.file.metadata()?;
let view = self.view()?;
match view.find(path) {
Some((_, node)) => match node.kind() {
U8NodeKind::File => Ok(VfsMetadata {
file_type: VfsFileType::File,
len: node.length() as u64,
mtime: metdata.mtime,
}),
U8NodeKind::Directory => Ok(VfsMetadata {
file_type: VfsFileType::Directory,
len: 0,
mtime: metdata.mtime,
}),
U8NodeKind::Invalid => Err(VfsError::from("U8: Invalid node kind")),
},
None => Err(VfsError::NotFound),
}
}
}