Compare commits
39 Commits
c484952912
...
f984bc3fb2
Author | SHA1 | Date |
---|---|---|
Luke Street | f984bc3fb2 | |
cadmic | 8823c2225e | |
Luke Street | fa5068fd6d | |
Luke Street | 3ada073da1 | |
Luke Street | 5c7560bcea | |
Luke Street | 8d8d801b2f | |
Luke Street | bee4570a4c | |
Luke Street | 18bd608fe8 | |
Luke Street | 1a9736f8d9 | |
Luke Street | 4fe2608e07 | |
Luke Street | 601c8e1a5e | |
Luke Street | 2e524e6806 | |
Luke Street | 64d0491256 | |
Luke Street | 4611a4b501 | |
Luke Street | b184fee73f | |
Luke Street | 1f4b452bd5 | |
Luke Street | f346239b81 | |
Luke Street | ef7e0db095 | |
Luke Street | 281b0f7104 | |
Luke Street | 71701b5667 | |
Luke Street | f91c2a1474 | |
Amber Brault | 26f52f65b7 | |
Luke Street | c106123877 | |
Luke Street | dfda3d5ea3 | |
Luke Street | 68f4552e44 | |
Luke Street | ac45676770 | |
Luke Street | e430cb56f5 | |
Luke Street | 0719c73ef8 | |
Luke Street | cfcd146dfa | |
Luke Street | d4f695ffc7 | |
Luke Street | 8b793b5616 | |
Luke Street | 9dfdbb9301 | |
Luke Street | c403931f0f | |
Luke Street | d9817f63d5 | |
Chippy | a112eb1829 | |
Luke Street | b6a29fa910 | |
Wesley Moret | da6a514fac | |
riidefi | cfeacd2c3a | |
First Last | c3c7c2b062 |
|
@ -1,16 +1,17 @@
|
|||
name: Build
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
paths-ignore:
|
||||
- '*.md'
|
||||
- 'LICENSE*'
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
BUILD_PROFILE: release-lto
|
||||
CARGO_BIN_NAME: dtk
|
||||
CARGO_TARGET_DIR: target
|
||||
CARGO_INCREMENTAL: 0
|
||||
|
||||
jobs:
|
||||
check:
|
||||
|
@ -25,6 +26,8 @@ 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
|
||||
|
@ -74,11 +77,15 @@ 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
|
||||
name: Build dtk
|
||||
env:
|
||||
CARGO_BIN_NAME: dtk
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
|
@ -94,10 +101,10 @@ jobs:
|
|||
target: aarch64-unknown-linux-musl
|
||||
name: linux-aarch64
|
||||
build: zigbuild
|
||||
- platform: ubuntu-latest
|
||||
target: armv7-unknown-linux-musleabi
|
||||
name: linux-armv7l
|
||||
build: zigbuild
|
||||
- platform: windows-latest
|
||||
target: i686-pc-windows-msvc
|
||||
name: windows-x86
|
||||
build: build
|
||||
- platform: windows-latest
|
||||
target: x86_64-pc-windows-msvc
|
||||
name: windows-x86_64
|
||||
|
@ -119,19 +126,6 @@ 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: |
|
||||
|
@ -139,20 +133,28 @@ jobs:
|
|||
sudo apt-get -y install ${{ matrix.packages }}
|
||||
- name: Install cargo-zigbuild
|
||||
if: matrix.build == 'zigbuild'
|
||||
run: pip install ziglang==0.12.0 cargo-zigbuild==0.18.4
|
||||
run: |
|
||||
python3 -m venv .venv
|
||||
. .venv/bin/activate
|
||||
echo PATH=$PATH >> $GITHUB_ENV
|
||||
pip install ziglang==0.13.0 cargo-zigbuild==0.19.1
|
||||
- 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 }} --all-features --target ${{ matrix.target }} --bin ${{ env.CARGO_BIN_NAME }}
|
||||
run: >
|
||||
cargo ${{ matrix.build }} --profile ${{ env.BUILD_PROFILE }} --target ${{ matrix.target }}
|
||||
--bin ${{ env.CARGO_BIN_NAME }}
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.name }}
|
||||
name: ${{ env.CARGO_BIN_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
|
||||
|
@ -162,7 +164,23 @@ 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:
|
||||
|
@ -170,12 +188,28 @@ jobs:
|
|||
- name: Rename artifacts
|
||||
working-directory: artifacts
|
||||
run: |
|
||||
set -euo pipefail
|
||||
mkdir ../out
|
||||
for i in */*/$BUILD_PROFILE/$CARGO_BIN_NAME*; do
|
||||
mv "$i" "../out/$(sed -E "s/([^/]+)\/[^/]+\/$BUILD_PROFILE\/($CARGO_BIN_NAME)/\2-\1/" <<< "$i")"
|
||||
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
|
||||
done
|
||||
ls -R ../out
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: out/*
|
||||
draft: true
|
||||
generate_release_notes: true
|
||||
|
|
File diff suppressed because it is too large
Load Diff
105
Cargo.toml
105
Cargo.toml
|
@ -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 = "0.9.3"
|
||||
version = "1.1.4"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
repository = "https://github.com/encounter/decomp-toolkit"
|
||||
readme = "README.md"
|
||||
categories = ["command-line-utilities"]
|
||||
rust-version = "1.73.0"
|
||||
rust-version = "1.81"
|
||||
|
||||
[[bin]]
|
||||
name = "dtk"
|
||||
|
@ -20,56 +20,63 @@ panic = "abort"
|
|||
|
||||
[profile.release-lto]
|
||||
inherits = "release"
|
||||
lto = "thin"
|
||||
lto = "fat"
|
||||
strip = "debuginfo"
|
||||
codegen-units = 1
|
||||
|
||||
[dependencies]
|
||||
anyhow = { version = "1.0.82", features = ["backtrace"] }
|
||||
anyhow = { version = "1.0", features = ["backtrace"] }
|
||||
ar = { git = "https://github.com/bjorn3/rust-ar.git", branch = "write_symbol_table" }
|
||||
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" }
|
||||
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"
|
||||
#nodtool = { path = "../nod-rs/nodtool" }
|
||||
num_enum = "0.7.2"
|
||||
objdiff-core = { git = "https://github.com/encounter/objdiff", rev = "a5a6a3928e392d5af5d92826e73b77e074b8788c", features = ["ppc"] }
|
||||
num_enum = "0.7"
|
||||
objdiff-core = { version = "2.2", features = ["ppc"] }
|
||||
#objdiff-core = { path = "../objdiff/objdiff-core", features = ["ppc"] }
|
||||
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"] }
|
||||
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"
|
||||
|
|
127
README.md
127
README.md
|
@ -39,12 +39,15 @@ 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)
|
||||
|
@ -88,8 +91,6 @@ 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**
|
||||
|
@ -125,11 +126,6 @@ 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
|
||||
|
@ -180,6 +176,8 @@ 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)
|
||||
|
@ -188,6 +186,7 @@ Supported disc image formats:
|
|||
- CISO (+ NKit 2 lossless)
|
||||
- NFS (Wii U VC)
|
||||
- GCZ
|
||||
- TGC
|
||||
|
||||
```shell
|
||||
$ dtk disc info /path/to/game.iso
|
||||
|
@ -199,6 +198,9 @@ 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]
|
||||
```
|
||||
|
@ -233,21 +235,23 @@ $ 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
|
||||
|
||||
> [!NOTE]
|
||||
> This command is a work-in-progress.
|
||||
> [!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).
|
||||
|
||||
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
|
||||
```
|
||||
|
@ -321,6 +325,8 @@ 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
|
||||
|
@ -344,8 +350,12 @@ $ 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
|
||||
|
@ -368,6 +378,22 @@ 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.
|
||||
|
@ -392,6 +418,10 @@ $ 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
|
||||
|
@ -400,6 +430,10 @@ $ 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
|
||||
|
@ -408,6 +442,10 @@ $ 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
|
||||
|
@ -416,12 +454,75 @@ $ 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
182
deny.toml
|
@ -9,6 +9,11 @@
|
|||
# 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
|
||||
|
@ -20,53 +25,67 @@
|
|||
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
|
||||
#{ triple = "x86_64-unknown-linux-musl" },
|
||||
#"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 database is cloned/fetched into
|
||||
db-path = "~/.cargo/advisory-db"
|
||||
# The path where the advisory databases are cloned/fetched into
|
||||
#db-path = "$CARGO_HOME/advisory-dbs"
|
||||
# The url(s) of the advisory databases to use
|
||||
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"
|
||||
#db-urls = ["https://github.com/rustsec/advisory-db"]
|
||||
# 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" },
|
||||
]
|
||||
# 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 =
|
||||
# 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
|
||||
|
||||
# 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]
|
||||
# The lint level for crates which do not have a detectable license
|
||||
unlicensed = "deny"
|
||||
# List of explictly allowed licenses
|
||||
# List of explicitly allowed licenses
|
||||
# See https://spdx.org/licenses/ for list of possible licenses
|
||||
# [possible values: any SPDX 3.11 short identifier (+ optional exception)].
|
||||
allow = [
|
||||
|
@ -77,28 +96,10 @@ 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.
|
||||
|
@ -109,32 +110,32 @@ confidence-threshold = 0.8
|
|||
exceptions = [
|
||||
# Each entry is the crate and version constraint, and its specific allow
|
||||
# list
|
||||
#{ allow = ["Zlib"], name = "adler32", version = "*" },
|
||||
#{ allow = ["Zlib"], crate = "adler32" },
|
||||
]
|
||||
|
||||
# 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 name of the crate the clarification applies to
|
||||
name = "encoding_rs"
|
||||
# The optional version constraint for the crate
|
||||
#version = "*"
|
||||
#[[licenses.clarify]]
|
||||
# The package spec the clarification applies to
|
||||
#crate = "ring"
|
||||
# The SPDX expression for the license requirements of the crate
|
||||
expression = "(Apache-2.0 OR MIT) AND BSD-3-Clause"
|
||||
#expression = "MIT AND ISC AND OpenSSL"
|
||||
# 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 = "COPYRIGHT", hash = 0x39f8ad31 }
|
||||
]
|
||||
#license-files = [
|
||||
# Each entry is a crate relative path, and the (opaque) hash of its contents
|
||||
#{ path = "LICENSE", hash = 0xbd0eed23 }
|
||||
#]
|
||||
|
||||
[licenses.private]
|
||||
# If true, ignores workspace crates that aren't published, or are only
|
||||
# published to private registries
|
||||
# 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.
|
||||
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
|
||||
|
@ -148,7 +149,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 = "warn"
|
||||
multiple-versions = "allow"
|
||||
# Lint level for when a crate version requirement is `*`
|
||||
wildcards = "allow"
|
||||
# The graph highlighting used when creating dotgraphs for crates
|
||||
|
@ -157,30 +158,63 @@ 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 = [
|
||||
#{ name = "ansi_term", version = "=0.11.0" },
|
||||
#"ansi_term@0.11.0",
|
||||
#{ crate = "ansi_term@0.11.0", reason = "you can specify a reason it is allowed" },
|
||||
]
|
||||
# List of crates to deny
|
||||
deny = [
|
||||
# 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" },
|
||||
#
|
||||
#"ansi_term@0.11.0",
|
||||
#{ crate = "ansi_term@0.11.0", reason = "you can specify a reason it is banned" },
|
||||
# Wrapper crates can optionally be specified to allow the crate when it
|
||||
# is a direct dependency of the otherwise banned crate
|
||||
#{ name = "ansi_term", version = "=0.11.0", wrappers = [] },
|
||||
#{ crate = "ansi_term@0.11.0", wrappers = ["this-crate-directly-depends-on-ansi_term"] },
|
||||
]
|
||||
|
||||
# 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 = [
|
||||
#{ name = "ansi_term", version = "=0.11.0" },
|
||||
#"ansi_term@0.11.0",
|
||||
#{ crate = "ansi_term@0.11.0", reason = "you can specify a reason why it can't be updated/removed" },
|
||||
]
|
||||
# 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 = [
|
||||
#{ name = "ansi_term", version = "=0.11.0", depth = 20 },
|
||||
#"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 },
|
||||
]
|
||||
|
||||
# This section is considered when running `cargo deny check sources`.
|
||||
|
@ -200,9 +234,9 @@ allow-registry = ["https://github.com/rust-lang/crates.io-index"]
|
|||
allow-git = []
|
||||
|
||||
[sources.allow-org]
|
||||
# 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 = [""]
|
||||
# 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 = []
|
||||
|
|
|
@ -16,12 +16,15 @@ use crate::{
|
|||
vm::{BranchTarget, GprValue, StepResult, VM},
|
||||
RelocationTarget,
|
||||
},
|
||||
obj::{ObjInfo, ObjSectionKind, ObjSymbol, ObjSymbolFlagSet, ObjSymbolFlags, ObjSymbolKind},
|
||||
obj::{
|
||||
ObjInfo, ObjSectionKind, ObjSymbol, ObjSymbolFlagSet, ObjSymbolFlags, ObjSymbolKind,
|
||||
SectionIndex,
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct SectionAddress {
|
||||
pub section: usize,
|
||||
pub section: SectionIndex,
|
||||
pub address: u32,
|
||||
}
|
||||
|
||||
|
@ -38,7 +41,7 @@ impl Display for SectionAddress {
|
|||
}
|
||||
|
||||
impl SectionAddress {
|
||||
pub fn new(section: usize, address: u32) -> Self { Self { section, address } }
|
||||
pub fn new(section: SectionIndex, address: u32) -> Self { Self { section, address } }
|
||||
|
||||
pub fn offset(self, offset: i32) -> Self {
|
||||
Self { section: self.section, address: self.address.wrapping_add_signed(offset) }
|
||||
|
@ -53,6 +56,10 @@ 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 {
|
||||
|
@ -116,7 +123,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<usize, String>,
|
||||
pub known_sections: BTreeMap<SectionIndex, String>,
|
||||
}
|
||||
|
||||
impl AnalyzerState {
|
||||
|
@ -597,19 +604,27 @@ pub fn locate_bss_memsets(obj: &mut ObjInfo) -> Result<Vec<(u32, u32)>> {
|
|||
StepResult::Branch(branches) => {
|
||||
for branch in branches {
|
||||
if branch.link {
|
||||
// ProDG bug? Registers are supposed to start at r3
|
||||
// Some ProDG crt0.s versions use the wrong registers, some don't
|
||||
if let (
|
||||
GprValue::Constant(addr),
|
||||
GprValue::Constant(value),
|
||||
GprValue::Constant(size),
|
||||
) = (vm.gpr_value(4), vm.gpr_value(5), vm.gpr_value(6))
|
||||
{
|
||||
) = {
|
||||
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))
|
||||
}
|
||||
} {
|
||||
if value == 0 && size > 0 {
|
||||
bss_sections.push((addr, size));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if bss_sections.len() >= 2 {
|
||||
return Ok(ExecCbResult::End(()));
|
||||
}
|
||||
Ok(ExecCbResult::Continue)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ struct VisitedAddresses {
|
|||
|
||||
impl VisitedAddresses {
|
||||
pub fn new(obj: &ObjInfo) -> Self {
|
||||
let mut inner = Vec::with_capacity(obj.sections.len());
|
||||
let mut inner = Vec::with_capacity(obj.sections.len() as usize);
|
||||
for (_, section) in obj.sections.iter() {
|
||||
if section.kind == ObjSectionKind::Code {
|
||||
let size = (section.size / 4) as usize;
|
||||
|
@ -32,11 +32,13 @@ impl VisitedAddresses {
|
|||
}
|
||||
|
||||
pub fn contains(&self, section_address: u32, address: SectionAddress) -> bool {
|
||||
self.inner[address.section].contains(Self::bit_for(section_address, address.address))
|
||||
self.inner[address.section as usize]
|
||||
.contains(Self::bit_for(section_address, address.address))
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, section_address: u32, address: SectionAddress) {
|
||||
self.inner[address.section].insert(Self::bit_for(section_address, address.address));
|
||||
self.inner[address.section as usize]
|
||||
.insert(Self::bit_for(section_address, address.address));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
|
|
@ -6,7 +6,9 @@ use ppc750cl::Ins;
|
|||
use crate::{
|
||||
analysis::cfa::SectionAddress,
|
||||
array_ref,
|
||||
obj::{ObjInfo, ObjKind, ObjRelocKind, ObjSection, ObjSectionKind, ObjSymbolKind},
|
||||
obj::{
|
||||
ObjInfo, ObjKind, ObjRelocKind, ObjSection, ObjSectionKind, ObjSymbolKind, SectionIndex,
|
||||
},
|
||||
};
|
||||
|
||||
pub mod cfa;
|
||||
|
@ -36,11 +38,9 @@ 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 usize == section.elf_index && reloc.address == address)
|
||||
{
|
||||
if let Some(reloc) = obj.unresolved_relocations.iter().find(|reloc| {
|
||||
reloc.section as SectionIndex == 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 usize).ok_or_else(|| {
|
||||
obj.sections.get_elf_index(reloc.target_section as SectionIndex).ok_or_else(|| {
|
||||
anyhow!(
|
||||
"Failed to find target section {} for unresolved relocation",
|
||||
reloc.target_section
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use anyhow::Result;
|
||||
|
||||
use crate::{
|
||||
obj::{ObjDataKind, ObjInfo, ObjSectionKind, ObjSymbolKind},
|
||||
obj::{ObjDataKind, ObjInfo, ObjSectionKind, ObjSymbolKind, SymbolIndex},
|
||||
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::<(usize, ObjDataKind, usize)>::new();
|
||||
let mut symbols_set = Vec::<(SymbolIndex, ObjDataKind, usize)>::new();
|
||||
for (section_index, section) in obj
|
||||
.sections
|
||||
.iter()
|
||||
|
|
|
@ -7,7 +7,7 @@ use crate::{
|
|||
analysis::cfa::{AnalyzerState, FunctionInfo, SectionAddress},
|
||||
obj::{
|
||||
ObjInfo, ObjKind, ObjRelocKind, ObjSectionKind, ObjSymbol, ObjSymbolFlagSet,
|
||||
ObjSymbolFlags, ObjSymbolKind,
|
||||
ObjSymbolFlags, ObjSymbolKind, SectionIndex,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -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 == section.elf_index as u8
|
||||
&& reloc.section as SectionIndex == section.elf_index
|
||||
&& 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 usize)
|
||||
let Some((target_section_index, target_section)) =
|
||||
obj.sections.iter().find(|(_, section)| {
|
||||
section.elf_index == reloc.target_section as SectionIndex
|
||||
})
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
|
|
|
@ -4,6 +4,7 @@ use std::{
|
|||
};
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use cwextab::decode_extab;
|
||||
use ppc750cl::Opcode;
|
||||
use tracing::{debug_span, info_span};
|
||||
use tracing_attributes::instrument;
|
||||
|
@ -18,7 +19,7 @@ use crate::{
|
|||
},
|
||||
obj::{
|
||||
ObjDataKind, ObjInfo, ObjKind, ObjReloc, ObjRelocKind, ObjSection, ObjSectionKind,
|
||||
ObjSymbol, ObjSymbolFlagSet, ObjSymbolFlags, ObjSymbolKind,
|
||||
ObjSymbol, ObjSymbolFlagSet, ObjSymbolFlags, ObjSymbolKind, SectionIndex, SymbolIndex,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -120,6 +121,7 @@ impl Tracker {
|
|||
self.process_data(obj, section_index, section)?;
|
||||
}
|
||||
}
|
||||
self.check_extab_relocations(obj)?;
|
||||
self.reject_invalid_relocations(obj)?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -147,6 +149,62 @@ 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)?;
|
||||
|
@ -240,7 +298,7 @@ impl Tracker {
|
|||
debug_assert_ne!(
|
||||
value,
|
||||
RelocationTarget::Address(SectionAddress::new(
|
||||
usize::MAX,
|
||||
SectionIndex::MAX,
|
||||
0
|
||||
))
|
||||
);
|
||||
|
@ -301,7 +359,7 @@ impl Tracker {
|
|||
debug_assert_ne!(
|
||||
address,
|
||||
RelocationTarget::Address(SectionAddress::new(
|
||||
usize::MAX,
|
||||
SectionIndex::MAX,
|
||||
0
|
||||
))
|
||||
);
|
||||
|
@ -322,7 +380,7 @@ impl Tracker {
|
|||
debug_assert_ne!(
|
||||
address,
|
||||
RelocationTarget::Address(SectionAddress::new(
|
||||
usize::MAX,
|
||||
SectionIndex::MAX,
|
||||
0
|
||||
))
|
||||
);
|
||||
|
@ -406,7 +464,7 @@ impl Tracker {
|
|||
{
|
||||
(addr, is_function_addr(addr))
|
||||
} else {
|
||||
(SectionAddress::new(usize::MAX, 0), false)
|
||||
(SectionAddress::new(SectionIndex::MAX, 0), false)
|
||||
};
|
||||
if branch.link || !is_fn_addr {
|
||||
self.relocations.insert(ins_addr, match ins.op {
|
||||
|
@ -491,7 +549,7 @@ impl Tracker {
|
|||
fn process_data(
|
||||
&mut self,
|
||||
obj: &ObjInfo,
|
||||
section_index: usize,
|
||||
section_index: SectionIndex,
|
||||
section: &ObjSection,
|
||||
) -> Result<()> {
|
||||
let mut addr = SectionAddress::new(section_index, section.address as u32);
|
||||
|
@ -544,7 +602,7 @@ impl Tracker {
|
|||
} else {
|
||||
// Check known relocations (function signature matching)
|
||||
if self.known_relocations.contains(&from) {
|
||||
return Some(SectionAddress::new(usize::MAX, addr));
|
||||
return Some(SectionAddress::new(SectionIndex::MAX, addr));
|
||||
}
|
||||
// Check special symbols
|
||||
if self.stack_address == Some(addr)
|
||||
|
@ -555,7 +613,7 @@ impl Tracker {
|
|||
|| self.sda2_base == Some(addr)
|
||||
|| self.sda_base == Some(addr)
|
||||
{
|
||||
return Some(SectionAddress::new(usize::MAX, addr));
|
||||
return Some(SectionAddress::new(SectionIndex::MAX, addr));
|
||||
}
|
||||
// Not valid
|
||||
None
|
||||
|
@ -567,7 +625,7 @@ impl Tracker {
|
|||
obj: &mut ObjInfo,
|
||||
addr: u32,
|
||||
reloc_kind: ObjRelocKind,
|
||||
) -> Option<usize> {
|
||||
) -> Option<SymbolIndex> {
|
||||
if !matches!(
|
||||
reloc_kind,
|
||||
ObjRelocKind::PpcAddr16Ha | ObjRelocKind::PpcAddr16Lo
|
||||
|
@ -583,7 +641,7 @@ impl Tracker {
|
|||
// return generate_special_symbol(obj, addr, &name).ok();
|
||||
// }
|
||||
// }
|
||||
let mut check_symbol = |opt: Option<u32>, name: &str| -> Option<usize> {
|
||||
let mut check_symbol = |opt: Option<u32>, name: &str| -> Option<SymbolIndex> {
|
||||
if let Some(value) = opt {
|
||||
if addr == value {
|
||||
return generate_special_symbol(obj, value, name).ok();
|
||||
|
@ -808,7 +866,7 @@ fn data_kind_from_op(op: Opcode) -> DataKind {
|
|||
}
|
||||
}
|
||||
|
||||
fn generate_special_symbol(obj: &mut ObjInfo, addr: u32, name: &str) -> Result<usize> {
|
||||
fn generate_special_symbol(obj: &mut ObjInfo, addr: u32, name: &str) -> Result<SymbolIndex> {
|
||||
obj.add_symbol(
|
||||
ObjSymbol {
|
||||
name: name.to_string(),
|
||||
|
|
|
@ -208,11 +208,11 @@ impl VM {
|
|||
(
|
||||
GprValue::Address(RelocationTarget::Address(left)),
|
||||
GprValue::Constant(right),
|
||||
) => GprValue::Address(RelocationTarget::Address(left + right)),
|
||||
) => GprValue::Address(RelocationTarget::Address(left.wrapping_add(right))),
|
||||
(
|
||||
GprValue::Constant(left),
|
||||
GprValue::Address(RelocationTarget::Address(right)),
|
||||
) => GprValue::Address(RelocationTarget::Address(right + left)),
|
||||
) => GprValue::Address(RelocationTarget::Address(right.wrapping_add(left))),
|
||||
_ => GprValue::Unknown,
|
||||
};
|
||||
self.gpr[ins.field_rd() as usize].set_direct(value);
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
use std::{
|
||||
io::{stdout, Write},
|
||||
path::PathBuf,
|
||||
};
|
||||
use std::io::{stdout, Write};
|
||||
|
||||
use anyhow::Result;
|
||||
use argp::FromArgs;
|
||||
use typed_path::Utf8NativePathBuf;
|
||||
|
||||
use crate::{
|
||||
cmd,
|
||||
util::{
|
||||
alf::AlfFile,
|
||||
file::{buf_writer, map_file},
|
||||
file::buf_writer,
|
||||
path::native_path,
|
||||
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)]
|
||||
#[argp(positional, from_str_fn(native_path))]
|
||||
/// alf file
|
||||
file: PathBuf,
|
||||
file: Utf8NativePathBuf,
|
||||
}
|
||||
|
||||
#[derive(FromArgs, PartialEq, Debug)]
|
||||
/// Extracts symbol hashes from an alf file.
|
||||
#[argp(subcommand, name = "hashes")]
|
||||
pub struct HashesArgs {
|
||||
#[argp(positional)]
|
||||
#[argp(positional, from_str_fn(native_path))]
|
||||
/// alf file
|
||||
alf_file: PathBuf,
|
||||
#[argp(positional)]
|
||||
alf_file: Utf8NativePathBuf,
|
||||
#[argp(positional, from_str_fn(native_path))]
|
||||
/// output file
|
||||
output: Option<PathBuf>,
|
||||
output: Option<Utf8NativePathBuf>,
|
||||
}
|
||||
|
||||
pub fn run(args: Args) -> Result<()> {
|
||||
|
@ -60,11 +60,10 @@ pub fn run(args: Args) -> Result<()> {
|
|||
|
||||
fn hashes(args: HashesArgs) -> Result<()> {
|
||||
let alf_file = {
|
||||
let file = map_file(&args.alf_file)?;
|
||||
let mut reader = file.as_reader();
|
||||
AlfFile::from_reader(&mut reader, Endian::Little)?
|
||||
let mut file = open_file(&args.alf_file, true)?;
|
||||
AlfFile::from_reader(file.as_mut(), 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())
|
||||
|
|
|
@ -2,14 +2,20 @@ 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, map_file, map_file_basic, process_rsp};
|
||||
use crate::{
|
||||
util::{
|
||||
file::{buf_writer, process_rsp},
|
||||
path::native_path,
|
||||
},
|
||||
vfs::open_file,
|
||||
};
|
||||
|
||||
#[derive(FromArgs, PartialEq, Debug)]
|
||||
/// Commands for processing static libraries.
|
||||
|
@ -30,24 +36,24 @@ enum SubCommand {
|
|||
/// Creates a static library.
|
||||
#[argp(subcommand, name = "create")]
|
||||
pub struct CreateArgs {
|
||||
#[argp(positional)]
|
||||
#[argp(positional, from_str_fn(native_path))]
|
||||
/// output file
|
||||
out: PathBuf,
|
||||
#[argp(positional)]
|
||||
out: Utf8NativePathBuf,
|
||||
#[argp(positional, from_str_fn(native_path))]
|
||||
/// input files
|
||||
files: Vec<PathBuf>,
|
||||
files: Vec<Utf8NativePathBuf>,
|
||||
}
|
||||
|
||||
#[derive(FromArgs, PartialEq, Eq, Debug)]
|
||||
/// Extracts a static library.
|
||||
#[argp(subcommand, name = "extract")]
|
||||
pub struct ExtractArgs {
|
||||
#[argp(positional)]
|
||||
#[argp(positional, from_str_fn(native_path))]
|
||||
/// input files
|
||||
files: Vec<PathBuf>,
|
||||
#[argp(option, short = 'o')]
|
||||
files: Vec<Utf8NativePathBuf>,
|
||||
#[argp(option, short = 'o', from_str_fn(native_path))]
|
||||
/// output directory
|
||||
out: Option<PathBuf>,
|
||||
out: Option<Utf8NativePathBuf>,
|
||||
#[argp(switch, short = 'q')]
|
||||
/// quiet output
|
||||
quiet: bool,
|
||||
|
@ -71,17 +77,16 @@ fn create(args: CreateArgs) -> Result<()> {
|
|||
let mut identifiers = Vec::with_capacity(files.len());
|
||||
let mut symbol_table = BTreeMap::new();
|
||||
for path in &files {
|
||||
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();
|
||||
let unix_path = path.with_unix_encoding();
|
||||
let identifier = unix_path.as_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 '{path_str}'"),
|
||||
Entry::Occupied(_) => bail!("Duplicate file name '{unix_path}'"),
|
||||
};
|
||||
let file = map_file_basic(path)?;
|
||||
let obj = object::File::parse(file.as_slice())?;
|
||||
let mut file = open_file(path, false)?;
|
||||
let obj = object::File::parse(file.map()?)?;
|
||||
for symbol in obj.symbols() {
|
||||
if symbol.scope() == SymbolScope::Dynamic {
|
||||
entries.push(symbol.name_bytes()?.to_vec());
|
||||
|
@ -99,10 +104,8 @@ 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_str.as_bytes(), &mut file)?;
|
||||
builder.append_file(path.as_str().as_bytes(), &mut file)?;
|
||||
}
|
||||
builder.into_inner()?.flush()?;
|
||||
Ok(())
|
||||
|
@ -115,7 +118,8 @@ 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 { PathBuf::new() };
|
||||
let mut out_dir =
|
||||
if let Some(out) = &args.out { out.clone() } else { Utf8NativePathBuf::new() };
|
||||
// If there are multiple files, extract to separate directories
|
||||
if files.len() > 1 {
|
||||
out_dir
|
||||
|
@ -123,14 +127,13 @@ fn extract(args: ExtractArgs) -> Result<()> {
|
|||
}
|
||||
std::fs::create_dir_all(&out_dir)?;
|
||||
if !args.quiet {
|
||||
println!("Extracting {} to {}", path.display(), out_dir.display());
|
||||
println!("Extracting {} to {}", path, out_dir);
|
||||
}
|
||||
|
||||
let file = map_file(path)?;
|
||||
let mut archive = ar::Archive::new(file.as_slice());
|
||||
let mut file = open_file(path, false)?;
|
||||
let mut archive = ar::Archive::new(file.map()?);
|
||||
while let Some(entry) = archive.next_entry() {
|
||||
let mut entry =
|
||||
entry.with_context(|| format!("Processing entry in {}", path.display()))?;
|
||||
let mut entry = entry.with_context(|| format!("Processing entry in {}", path))?;
|
||||
let file_name = std::str::from_utf8(entry.header().identifier())?;
|
||||
if !args.quiet && args.verbose {
|
||||
println!("\t{}", file_name);
|
||||
|
@ -143,7 +146,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.display()))?;
|
||||
.with_context(|| format!("Failed to create file {}", file_path))?;
|
||||
std::io::copy(&mut entry, &mut file)?;
|
||||
file.flush()?;
|
||||
|
||||
|
|
621
src/cmd/dol.rs
621
src/cmd/dol.rs
File diff suppressed because it is too large
Load Diff
|
@ -2,7 +2,6 @@ use std::{
|
|||
collections::{btree_map, BTreeMap},
|
||||
io::{stdout, Cursor, Read, Write},
|
||||
ops::Bound::{Excluded, Unbounded},
|
||||
path::PathBuf,
|
||||
str::from_utf8,
|
||||
};
|
||||
|
||||
|
@ -15,13 +14,18 @@ use syntect::{
|
|||
highlighting::{Color, HighlightIterator, HighlightState, Highlighter, Theme, ThemeSet},
|
||||
parsing::{ParseState, ScopeStack, SyntaxReference, SyntaxSet},
|
||||
};
|
||||
use typed_path::Utf8NativePathBuf;
|
||||
|
||||
use crate::util::{
|
||||
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, map_file},
|
||||
file::buf_writer,
|
||||
path::native_path,
|
||||
},
|
||||
vfs::open_file,
|
||||
};
|
||||
|
||||
#[derive(FromArgs, PartialEq, Debug)]
|
||||
|
@ -42,12 +46,12 @@ enum SubCommand {
|
|||
/// Dumps DWARF 1.1 info from an object or archive.
|
||||
#[argp(subcommand, name = "dump")]
|
||||
pub struct DumpArgs {
|
||||
#[argp(positional)]
|
||||
#[argp(positional, from_str_fn(native_path))]
|
||||
/// Input object. (ELF or archive)
|
||||
in_file: PathBuf,
|
||||
#[argp(option, short = 'o')]
|
||||
in_file: Utf8NativePathBuf,
|
||||
#[argp(option, short = 'o', from_str_fn(native_path))]
|
||||
/// Output file. (Or directory, for archive)
|
||||
out: Option<PathBuf>,
|
||||
out: Option<Utf8NativePathBuf>,
|
||||
#[argp(switch)]
|
||||
/// Disable color output.
|
||||
no_color: bool,
|
||||
|
@ -73,8 +77,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 file = map_file(&args.in_file)?;
|
||||
let buf = file.as_slice();
|
||||
let mut file = open_file(&args.in_file, true)?;
|
||||
let buf = file.map()?;
|
||||
if buf.starts_with(b"!<arch>\n") {
|
||||
let mut archive = ar::Archive::new(buf);
|
||||
while let Some(result) = archive.next_entry() {
|
||||
|
@ -101,7 +105,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 {
|
||||
|
|
|
@ -3,7 +3,6 @@ use std::{
|
|||
fs,
|
||||
fs::DirBuilder,
|
||||
io::{Cursor, Write},
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
use anyhow::{anyhow, bail, ensure, Context, Result};
|
||||
|
@ -15,6 +14,7 @@ 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,6 +24,7 @@ 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,
|
||||
|
@ -54,72 +55,72 @@ enum SubCommand {
|
|||
/// Disassembles an ELF file.
|
||||
#[argp(subcommand, name = "disasm")]
|
||||
pub struct DisasmArgs {
|
||||
#[argp(positional)]
|
||||
#[argp(positional, from_str_fn(native_path))]
|
||||
/// input file
|
||||
elf_file: PathBuf,
|
||||
#[argp(positional)]
|
||||
elf_file: Utf8NativePathBuf,
|
||||
#[argp(positional, from_str_fn(native_path))]
|
||||
/// output file (.o) or directory (.elf)
|
||||
out: PathBuf,
|
||||
out: Utf8NativePathBuf,
|
||||
}
|
||||
|
||||
#[derive(FromArgs, PartialEq, Eq, Debug)]
|
||||
/// Fixes issues with GNU assembler built object files.
|
||||
#[argp(subcommand, name = "fixup")]
|
||||
pub struct FixupArgs {
|
||||
#[argp(positional)]
|
||||
#[argp(positional, from_str_fn(native_path))]
|
||||
/// input file
|
||||
in_file: PathBuf,
|
||||
#[argp(positional)]
|
||||
in_file: Utf8NativePathBuf,
|
||||
#[argp(positional, from_str_fn(native_path))]
|
||||
/// output file
|
||||
out_file: PathBuf,
|
||||
out_file: Utf8NativePathBuf,
|
||||
}
|
||||
|
||||
#[derive(FromArgs, PartialEq, Eq, Debug)]
|
||||
/// Splits an executable ELF into relocatable objects.
|
||||
#[argp(subcommand, name = "split")]
|
||||
pub struct SplitArgs {
|
||||
#[argp(positional)]
|
||||
#[argp(positional, from_str_fn(native_path))]
|
||||
/// input file
|
||||
in_file: PathBuf,
|
||||
#[argp(positional)]
|
||||
in_file: Utf8NativePathBuf,
|
||||
#[argp(positional, from_str_fn(native_path))]
|
||||
/// output directory
|
||||
out_dir: PathBuf,
|
||||
out_dir: Utf8NativePathBuf,
|
||||
}
|
||||
|
||||
#[derive(FromArgs, PartialEq, Eq, Debug)]
|
||||
/// Generates configuration files from an executable ELF.
|
||||
#[argp(subcommand, name = "config")]
|
||||
pub struct ConfigArgs {
|
||||
#[argp(positional)]
|
||||
#[argp(positional, from_str_fn(native_path))]
|
||||
/// input file
|
||||
in_file: PathBuf,
|
||||
#[argp(positional)]
|
||||
in_file: Utf8NativePathBuf,
|
||||
#[argp(positional, from_str_fn(native_path))]
|
||||
/// output directory
|
||||
out_dir: PathBuf,
|
||||
out_dir: Utf8NativePathBuf,
|
||||
}
|
||||
|
||||
#[derive(FromArgs, PartialEq, Eq, Debug)]
|
||||
/// Builds function signatures from an ELF file.
|
||||
#[argp(subcommand, name = "sigs")]
|
||||
pub struct SignaturesArgs {
|
||||
#[argp(positional)]
|
||||
#[argp(positional, from_str_fn(native_path))]
|
||||
/// input file(s)
|
||||
files: Vec<PathBuf>,
|
||||
files: Vec<Utf8NativePathBuf>,
|
||||
#[argp(option, short = 's')]
|
||||
/// symbol name
|
||||
symbol: String,
|
||||
#[argp(option, short = 'o')]
|
||||
#[argp(option, short = 'o', from_str_fn(native_path))]
|
||||
/// output yml
|
||||
out_file: PathBuf,
|
||||
out_file: Utf8NativePathBuf,
|
||||
}
|
||||
|
||||
#[derive(FromArgs, PartialEq, Eq, Debug)]
|
||||
/// Prints information about an ELF file.
|
||||
#[argp(subcommand, name = "info")]
|
||||
pub struct InfoArgs {
|
||||
#[argp(positional)]
|
||||
#[argp(positional, from_str_fn(native_path))]
|
||||
/// input file
|
||||
input: PathBuf,
|
||||
input: Utf8NativePathBuf,
|
||||
}
|
||||
|
||||
pub fn run(args: Args) -> Result<()> {
|
||||
|
@ -134,17 +135,17 @@ pub fn run(args: Args) -> Result<()> {
|
|||
}
|
||||
|
||||
fn config(args: ConfigArgs) -> Result<()> {
|
||||
log::info!("Loading {}", args.in_file.display());
|
||||
log::info!("Loading {}", args.in_file);
|
||||
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.display());
|
||||
log::info!("Loading {}", args.elf_file);
|
||||
let obj = process_elf(&args.elf_file)?;
|
||||
match obj.kind {
|
||||
ObjKind::Executable => {
|
||||
|
@ -156,12 +157,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.display());
|
||||
log::info!("Writing {}", out_path);
|
||||
|
||||
let mut w = buf_writer(out_path)?;
|
||||
let mut w = buf_writer(&out_path)?;
|
||||
write_asm(&mut w, split_obj)?;
|
||||
w.flush()?;
|
||||
|
||||
|
@ -170,7 +171,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()?;
|
||||
}
|
||||
|
@ -193,18 +194,17 @@ fn split(args: SplitArgs) -> Result<()> {
|
|||
};
|
||||
}
|
||||
|
||||
let mut rsp_file = buf_writer("rsp")?;
|
||||
let mut rsp_file = buf_writer(Utf8NativePath::new("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.display())?;
|
||||
writeln!(rsp_file, "{}", out_path)?;
|
||||
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.display()))?;
|
||||
fs::write(&out_path, object).with_context(|| format!("Failed to write '{}'", out_path))?;
|
||||
}
|
||||
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.display()))?;
|
||||
.with_context(|| format!("Failed to open input file: '{}'", args.in_file))?;
|
||||
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,10 +262,7 @@ 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.display()))?;
|
||||
let file_name = file_name
|
||||
.to_str()
|
||||
.ok_or_else(|| anyhow!("'{}' is not valid UTF-8", file_name.to_string_lossy()))?;
|
||||
.ok_or_else(|| anyhow!("'{}' is not a file path", args.in_file))?;
|
||||
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 {
|
||||
|
@ -281,7 +278,7 @@ fn fixup(args: FixupArgs) -> Result<()> {
|
|||
}
|
||||
|
||||
// Write section symbols & sections
|
||||
let mut section_ids: Vec<Option<SectionId>> = vec![];
|
||||
let mut section_ids: Vec<Option<SectionId>> = vec![None /* ELF null section */];
|
||||
for section in in_file.sections() {
|
||||
// Skip empty sections or metadata sections
|
||||
if section.size() == 0 || section.kind() == SectionKind::Metadata {
|
||||
|
@ -304,11 +301,11 @@ fn fixup(args: FixupArgs) -> Result<()> {
|
|||
}
|
||||
|
||||
// Write symbols
|
||||
let mut symbol_ids: Vec<Option<SymbolId>> = vec![];
|
||||
let mut symbol_ids: Vec<Option<SymbolId>> = vec![None /* ELF null symbol */];
|
||||
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 | SymbolKind::Null) {
|
||||
if matches!(symbol.kind(), SymbolKind::Section | SymbolKind::File) {
|
||||
symbol_ids.push(None);
|
||||
continue;
|
||||
}
|
||||
|
@ -445,7 +442,7 @@ fn signatures(args: SignaturesArgs) -> Result<()> {
|
|||
|
||||
let mut signatures: HashMap<String, FunctionSignature> = HashMap::new();
|
||||
for path in files {
|
||||
log::info!("Processing {}", path.display());
|
||||
log::info!("Processing {}", path);
|
||||
let signature = match generate_signature(&path, &args.symbol) {
|
||||
Ok(Some(signature)) => signature,
|
||||
Ok(None) => continue,
|
||||
|
@ -472,7 +469,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.display()))?;
|
||||
.with_context(|| format!("Failed to open input file: '{}'", args.input))?;
|
||||
let in_file = object::read::File::parse(&*in_buf).context("Failed to parse input ELF")?;
|
||||
|
||||
println!("ELF type: {:?}", in_file.kind());
|
||||
|
@ -488,7 +485,7 @@ fn info(args: InfoArgs) -> Result<()> {
|
|||
"{: >15} | {: <10} | {: <10} | {: <10} | {: <10}",
|
||||
"Name", "Type", "Size", "File Off", "Index"
|
||||
);
|
||||
for section in in_file.sections().skip(1) {
|
||||
for section in in_file.sections() {
|
||||
let kind_str = match section.kind() {
|
||||
SectionKind::Text => "code".to_cow(),
|
||||
SectionKind::Data => "data".to_cow(),
|
||||
|
@ -565,6 +562,7 @@ 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() {
|
||||
|
@ -603,7 +601,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) {
|
||||
for (symbol, addr) in in_file.symbols().zip(virtual_addresses.iter().skip(1)) {
|
||||
if symbol.is_definition() {
|
||||
println!("\t{: >10} | {: <10}", format!("{:#X}", addr), symbol.name()?);
|
||||
}
|
||||
|
|
|
@ -1,24 +1,28 @@
|
|||
use std::{
|
||||
io::{Seek, SeekFrom, Write},
|
||||
path::PathBuf,
|
||||
};
|
||||
use std::io::{Seek, SeekFrom, Write};
|
||||
|
||||
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, map_file};
|
||||
use crate::{
|
||||
util::{file::buf_writer, path::native_path},
|
||||
vfs::open_file,
|
||||
};
|
||||
|
||||
#[derive(FromArgs, PartialEq, Eq, Debug)]
|
||||
/// Converts an ELF file to a DOL file.
|
||||
#[argp(subcommand, name = "elf2dol")]
|
||||
pub struct Args {
|
||||
#[argp(positional)]
|
||||
#[argp(positional, from_str_fn(native_path))]
|
||||
/// path to input ELF
|
||||
elf_file: PathBuf,
|
||||
#[argp(positional)]
|
||||
elf_file: Utf8NativePathBuf,
|
||||
#[argp(positional, from_str_fn(native_path))]
|
||||
/// path to output DOL
|
||||
dol_file: PathBuf,
|
||||
dol_file: Utf8NativePathBuf,
|
||||
/// sections (by name) to ignore
|
||||
#[argp(option, long = "ignore")]
|
||||
deny_sections: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
|
@ -43,8 +47,8 @@ const MAX_TEXT_SECTIONS: usize = 7;
|
|||
const MAX_DATA_SECTIONS: usize = 11;
|
||||
|
||||
pub fn run(args: Args) -> Result<()> {
|
||||
let file = map_file(&args.elf_file)?;
|
||||
let obj_file = object::read::File::parse(file.as_slice())?;
|
||||
let mut file = open_file(&args.elf_file, true)?;
|
||||
let obj_file = object::read::File::parse(file.map()?)?;
|
||||
match obj_file.architecture() {
|
||||
Architecture::PowerPc => {}
|
||||
arch => bail!("Unexpected architecture: {arch:?}"),
|
||||
|
@ -61,9 +65,11 @@ 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()))
|
||||
{
|
||||
for section in obj_file.sections().filter(|s| {
|
||||
section_kind(s) == SectionKind::Text
|
||||
&& is_alloc(s.flags())
|
||||
&& is_name_allowed(s, &args.deny_sections)
|
||||
}) {
|
||||
log::debug!("Processing text section '{}'", section.name().unwrap_or("[error]"));
|
||||
let address = section.address() as u32;
|
||||
let size = align32(section.size() as u32);
|
||||
|
@ -79,9 +85,11 @@ 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()))
|
||||
{
|
||||
for section in obj_file.sections().filter(|s| {
|
||||
section_kind(s) == SectionKind::Data
|
||||
&& is_alloc(s.flags())
|
||||
&& is_name_allowed(s, &args.deny_sections)
|
||||
}) {
|
||||
log::debug!("Processing data section '{}'", section.name().unwrap_or("[error]"));
|
||||
let address = section.address() as u32;
|
||||
let size = align32(section.size() as u32);
|
||||
|
@ -97,10 +105,11 @@ 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()))
|
||||
{
|
||||
for section in obj_file.sections().filter(|s| {
|
||||
section_kind(s) == SectionKind::UninitializedData
|
||||
&& is_alloc(s.flags())
|
||||
&& is_name_allowed(s, &args.deny_sections)
|
||||
}) {
|
||||
let address = section.address() as u32;
|
||||
let size = section.size() as u32;
|
||||
if header.bss_address == 0 {
|
||||
|
@ -184,3 +193,8 @@ 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())
|
||||
}
|
||||
|
|
|
@ -1,12 +1,19 @@
|
|||
use std::path::PathBuf;
|
||||
use std::fs::DirBuilder;
|
||||
|
||||
use anyhow::{bail, ensure, Result};
|
||||
use argp::FromArgs;
|
||||
use cwdemangle::{demangle, DemangleOptions};
|
||||
use tracing::error;
|
||||
use typed_path::Utf8NativePathBuf;
|
||||
|
||||
use crate::util::{
|
||||
file::map_file,
|
||||
map::{process_map, SymbolEntry, SymbolRef},
|
||||
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,
|
||||
};
|
||||
|
||||
#[derive(FromArgs, PartialEq, Debug)]
|
||||
|
@ -22,15 +29,16 @@ 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)]
|
||||
#[argp(positional, from_str_fn(native_path))]
|
||||
/// path to input map
|
||||
map_file: PathBuf,
|
||||
map_file: Utf8NativePathBuf,
|
||||
#[argp(positional)]
|
||||
/// TU to display entries for
|
||||
unit: String,
|
||||
|
@ -40,24 +48,37 @@ pub struct EntriesArgs {
|
|||
/// Displays all references to a symbol.
|
||||
#[argp(subcommand, name = "symbol")]
|
||||
pub struct SymbolArgs {
|
||||
#[argp(positional)]
|
||||
#[argp(positional, from_str_fn(native_path))]
|
||||
/// path to input map
|
||||
map_file: PathBuf,
|
||||
map_file: Utf8NativePathBuf,
|
||||
#[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 file = map_file(&args.map_file)?;
|
||||
let entries = process_map(&mut file.as_reader(), None, None)?;
|
||||
let mut file = open_file(&args.map_file, true)?;
|
||||
let entries = process_map(file.as_mut(), None, None)?;
|
||||
match entries.unit_entries.get_vec(&args.unit) {
|
||||
Some(vec) => {
|
||||
println!("Entries for {}:", args.unit);
|
||||
|
@ -87,9 +108,9 @@ fn entries(args: EntriesArgs) -> Result<()> {
|
|||
}
|
||||
|
||||
fn symbol(args: SymbolArgs) -> Result<()> {
|
||||
let file = map_file(&args.map_file)?;
|
||||
let mut file = open_file(&args.map_file, true)?;
|
||||
log::info!("Processing map...");
|
||||
let entries = process_map(&mut file.as_reader(), None, None)?;
|
||||
let entries = process_map(file.as_mut(), None, None)?;
|
||||
log::info!("Done!");
|
||||
let mut opt_ref: Option<(String, SymbolEntry)> = None;
|
||||
|
||||
|
@ -160,3 +181,18 @@ 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(())
|
||||
}
|
||||
|
|
|
@ -1,48 +0,0 @@
|
|||
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(())
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
use std::{fs, path::PathBuf};
|
||||
use std::fs;
|
||||
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use argp::FromArgs;
|
||||
use typed_path::Utf8NativePathBuf;
|
||||
|
||||
use crate::util::{
|
||||
file::{open_file, process_rsp},
|
||||
IntoCow, ToCow,
|
||||
use crate::{
|
||||
util::{file::process_rsp, nlzss, path::native_path, IntoCow, ToCow},
|
||||
vfs::open_file,
|
||||
};
|
||||
|
||||
#[derive(FromArgs, PartialEq, Debug)]
|
||||
|
@ -26,13 +27,13 @@ enum SubCommand {
|
|||
/// Decompresses NLZSS-compressed files.
|
||||
#[argp(subcommand, name = "decompress")]
|
||||
pub struct DecompressArgs {
|
||||
#[argp(positional)]
|
||||
#[argp(positional, from_str_fn(native_path))]
|
||||
/// NLZSS-compressed file(s)
|
||||
files: Vec<PathBuf>,
|
||||
#[argp(option, short = 'o')]
|
||||
files: Vec<Utf8NativePathBuf>,
|
||||
#[argp(option, short = 'o', from_str_fn(native_path))]
|
||||
/// Output file (or directory, if multiple files are specified).
|
||||
/// If not specified, decompresses in-place.
|
||||
output: Option<PathBuf>,
|
||||
output: Option<Utf8NativePathBuf>,
|
||||
}
|
||||
|
||||
pub fn run(args: Args) -> Result<()> {
|
||||
|
@ -45,8 +46,9 @@ fn decompress(args: DecompressArgs) -> Result<()> {
|
|||
let files = process_rsp(&args.files)?;
|
||||
let single_file = files.len() == 1;
|
||||
for path in files {
|
||||
let data = nintendo_lz::decompress(&mut open_file(&path)?)
|
||||
.map_err(|e| anyhow!("Failed to decompress '{}' with NLZSS: {}", path.display(), e))?;
|
||||
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 out_path = if let Some(output) = &args.output {
|
||||
if single_file {
|
||||
output.as_path().to_cow()
|
||||
|
@ -57,7 +59,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.display()))?;
|
||||
.with_context(|| format!("Failed to write '{}'", out_path))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
104
src/cmd/rarc.rs
104
src/cmd/rarc.rs
|
@ -1,12 +1,9 @@
|
|||
use std::{fs, fs::DirBuilder, path::PathBuf};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use anyhow::Result;
|
||||
use argp::FromArgs;
|
||||
use typed_path::Utf8NativePathBuf;
|
||||
|
||||
use crate::util::{
|
||||
file::{decompress_if_needed, map_file},
|
||||
rarc::{Node, RarcReader},
|
||||
};
|
||||
use super::vfs;
|
||||
use crate::util::path::native_path;
|
||||
|
||||
#[derive(FromArgs, PartialEq, Debug)]
|
||||
/// Commands for processing RSO files.
|
||||
|
@ -27,23 +24,29 @@ enum SubCommand {
|
|||
/// Views RARC file information.
|
||||
#[argp(subcommand, name = "list")]
|
||||
pub struct ListArgs {
|
||||
#[argp(positional)]
|
||||
#[argp(positional, from_str_fn(native_path))]
|
||||
/// RARC file
|
||||
file: PathBuf,
|
||||
file: Utf8NativePathBuf,
|
||||
#[argp(switch, short = 's')]
|
||||
/// Only print filenames.
|
||||
short: bool,
|
||||
}
|
||||
|
||||
#[derive(FromArgs, PartialEq, Eq, Debug)]
|
||||
/// Extracts RARC file contents.
|
||||
#[argp(subcommand, name = "extract")]
|
||||
pub struct ExtractArgs {
|
||||
#[argp(positional)]
|
||||
#[argp(positional, from_str_fn(native_path))]
|
||||
/// RARC file
|
||||
file: PathBuf,
|
||||
#[argp(option, short = 'o')]
|
||||
file: Utf8NativePathBuf,
|
||||
#[argp(option, short = 'o', from_str_fn(native_path))]
|
||||
/// output directory
|
||||
output: Option<PathBuf>,
|
||||
output: Option<Utf8NativePathBuf>,
|
||||
#[argp(switch)]
|
||||
/// Do not decompress files when copying.
|
||||
no_decompress: bool,
|
||||
#[argp(switch, short = 'q')]
|
||||
/// quiet output
|
||||
/// Quiet output. Don't print anything except errors.
|
||||
quiet: bool,
|
||||
}
|
||||
|
||||
|
@ -55,71 +58,16 @@ pub fn run(args: Args) -> Result<()> {
|
|||
}
|
||||
|
||||
fn list(args: ListArgs) -> Result<()> {
|
||||
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(())
|
||||
let path = Utf8NativePathBuf::from(format!("{}:", args.file));
|
||||
vfs::ls(vfs::LsArgs { path, short: args.short, recursive: true })
|
||||
}
|
||||
|
||||
fn extract(args: ExtractArgs) -> Result<()> {
|
||||
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(())
|
||||
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,
|
||||
})
|
||||
}
|
||||
|
|
115
src/cmd/rel.rs
115
src/cmd/rel.rs
|
@ -1,8 +1,7 @@
|
|||
use std::{
|
||||
collections::{btree_map, BTreeMap},
|
||||
fs,
|
||||
io::Write,
|
||||
path::PathBuf,
|
||||
io::{Cursor, Write},
|
||||
time::Instant,
|
||||
};
|
||||
|
||||
|
@ -15,6 +14,7 @@ use object::{
|
|||
use rayon::prelude::*;
|
||||
use rustc_hash::FxHashMap;
|
||||
use tracing::{info, info_span};
|
||||
use typed_path::Utf8NativePathBuf;
|
||||
|
||||
use crate::{
|
||||
analysis::{
|
||||
|
@ -27,20 +27,25 @@ use crate::{
|
|||
tracker::Tracker,
|
||||
},
|
||||
array_ref_mut,
|
||||
cmd::dol::{ModuleConfig, ProjectConfig},
|
||||
obj::{ObjInfo, ObjReloc, ObjRelocKind, ObjSection, ObjSectionKind, ObjSymbol},
|
||||
cmd::dol::{find_object_base, ModuleConfig, ObjectBase, ProjectConfig},
|
||||
obj::{
|
||||
ObjInfo, ObjReloc, ObjRelocKind, ObjSection, ObjSectionKind, ObjSymbol,
|
||||
SectionIndex as ObjSectionIndex,
|
||||
},
|
||||
util::{
|
||||
config::{is_auto_symbol, read_splits_sections, SectionDef},
|
||||
dol::process_dol,
|
||||
elf::{to_obj_reloc_kind, write_elf},
|
||||
file::{buf_reader, buf_writer, map_file, process_rsp, verify_hash, FileIterator},
|
||||
file::{buf_writer, 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)]
|
||||
|
@ -63,9 +68,9 @@ enum SubCommand {
|
|||
/// Views REL file information.
|
||||
#[argp(subcommand, name = "info")]
|
||||
pub struct InfoArgs {
|
||||
#[argp(positional)]
|
||||
#[argp(positional, from_str_fn(native_path))]
|
||||
/// REL file
|
||||
rel_file: PathBuf,
|
||||
rel_file: Utf8NativePathBuf,
|
||||
#[argp(switch, short = 'r')]
|
||||
/// print relocations
|
||||
relocations: bool,
|
||||
|
@ -75,27 +80,27 @@ pub struct InfoArgs {
|
|||
/// Merges a DOL + REL(s) into an ELF.
|
||||
#[argp(subcommand, name = "merge")]
|
||||
pub struct MergeArgs {
|
||||
#[argp(positional)]
|
||||
#[argp(positional, from_str_fn(native_path))]
|
||||
/// DOL file
|
||||
dol_file: PathBuf,
|
||||
#[argp(positional)]
|
||||
dol_file: Utf8NativePathBuf,
|
||||
#[argp(positional, from_str_fn(native_path))]
|
||||
/// REL file(s)
|
||||
rel_files: Vec<PathBuf>,
|
||||
#[argp(option, short = 'o')]
|
||||
rel_files: Vec<Utf8NativePathBuf>,
|
||||
#[argp(option, short = 'o', from_str_fn(native_path))]
|
||||
/// output ELF
|
||||
out_file: PathBuf,
|
||||
out_file: Utf8NativePathBuf,
|
||||
}
|
||||
|
||||
#[derive(FromArgs, PartialEq, Eq, Debug)]
|
||||
/// Creates RELs from an ELF + PLF(s).
|
||||
#[argp(subcommand, name = "make")]
|
||||
pub struct MakeArgs {
|
||||
#[argp(positional)]
|
||||
#[argp(positional, from_str_fn(native_path))]
|
||||
/// input file(s)
|
||||
files: Vec<PathBuf>,
|
||||
#[argp(option, short = 'c')]
|
||||
files: Vec<Utf8NativePathBuf>,
|
||||
#[argp(option, short = 'c', from_str_fn(native_path))]
|
||||
/// (optional) project configuration file
|
||||
config: Option<PathBuf>,
|
||||
config: Option<Utf8NativePathBuf>,
|
||||
#[argp(option, short = 'n')]
|
||||
/// (optional) module names
|
||||
names: Vec<String>,
|
||||
|
@ -162,16 +167,17 @@ fn match_section_index(
|
|||
// })
|
||||
}
|
||||
|
||||
fn load_rel(module_config: &ModuleConfig) -> Result<RelInfo> {
|
||||
let file = map_file(&module_config.object)?;
|
||||
fn load_rel(module_config: &ModuleConfig, object_base: &ObjectBase) -> Result<RelInfo> {
|
||||
let mut file = object_base.open(&module_config.object)?;
|
||||
let data = file.map()?;
|
||||
if let Some(hash_str) = &module_config.hash {
|
||||
verify_hash(file.as_slice(), hash_str)?;
|
||||
verify_hash(data, hash_str)?;
|
||||
}
|
||||
let mut reader = file.as_reader();
|
||||
let mut reader = Cursor::new(data);
|
||||
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)?
|
||||
read_splits_sections(&splits_path.with_encoding())?
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
@ -181,7 +187,7 @@ fn load_rel(module_config: &ModuleConfig) -> Result<RelInfo> {
|
|||
struct LoadedModule<'a> {
|
||||
module_id: u32,
|
||||
file: File<'a>,
|
||||
path: PathBuf,
|
||||
path: Utf8NativePathBuf,
|
||||
}
|
||||
|
||||
fn resolve_relocations(
|
||||
|
@ -261,15 +267,19 @@ 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 = serde_yaml::from_reader(&mut buf_reader(config_path)?)?;
|
||||
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)?;
|
||||
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).with_context(|| {
|
||||
format!("While loading REL '{}'", module_config.object.display())
|
||||
let info = load_rel(module_config, &object_base).with_context(|| {
|
||||
format!("While loading REL '{}'", object_base.join(&module_config.object))
|
||||
})?;
|
||||
name_to_module_id.insert(module_name.to_string(), info.0.module_id);
|
||||
match existing_headers.entry(info.0.module_id) {
|
||||
|
@ -287,9 +297,9 @@ fn make(args: MakeArgs) -> Result<()> {
|
|||
}
|
||||
|
||||
// Load all modules
|
||||
let files = paths.iter().map(map_file).collect::<Result<Vec<_>>>()?;
|
||||
let mut files = paths.iter().map(|p| open_file(p, true)).collect::<Result<Vec<_>>>()?;
|
||||
let modules = files
|
||||
.par_iter()
|
||||
.par_iter_mut()
|
||||
.enumerate()
|
||||
.zip(&paths)
|
||||
.map(|((idx, file), path)| {
|
||||
|
@ -301,9 +311,9 @@ fn make(args: MakeArgs) -> Result<()> {
|
|||
.and_then(|n| name_to_module_id.get(n))
|
||||
.copied()
|
||||
.unwrap_or(idx as u32);
|
||||
load_obj(file.as_slice())
|
||||
load_obj(file.map()?)
|
||||
.map(|o| LoadedModule { module_id, file: o, path: path.clone() })
|
||||
.with_context(|| format!("Failed to load '{}'", path.display()))
|
||||
.with_context(|| format!("Failed to load '{}'", path))
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
|
||||
|
@ -311,7 +321,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.display()).entered();
|
||||
let _span = info_span!("file", path = %module_info.path).entered();
|
||||
for symbol in module_info.file.symbols() {
|
||||
if symbol.scope() == object::SymbolScope::Dynamic {
|
||||
symbol_map
|
||||
|
@ -326,7 +336,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.display()).entered();
|
||||
let _span = info_span!("file", path = %module_info.path).entered();
|
||||
resolved += resolve_relocations(
|
||||
&module_info.file,
|
||||
&existing_headers,
|
||||
|
@ -335,9 +345,7 @@ fn make(args: MakeArgs) -> Result<()> {
|
|||
&modules,
|
||||
relocations,
|
||||
)
|
||||
.with_context(|| {
|
||||
format!("While resolving relocations in '{}'", module_info.path.display())
|
||||
})?;
|
||||
.with_context(|| format!("While resolving relocations in '{}'", module_info.path))?;
|
||||
}
|
||||
|
||||
if !args.quiet {
|
||||
|
@ -353,7 +361,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.display()).entered();
|
||||
let _span = info_span!("file", path = %module_info.path).entered();
|
||||
let mut info = RelWriteInfo {
|
||||
module_id: module_info.module_id,
|
||||
version: 3,
|
||||
|
@ -384,7 +392,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.display()))?;
|
||||
.with_context(|| format!("Failed to write '{}'", rel_path))?;
|
||||
w.flush()?;
|
||||
}
|
||||
|
||||
|
@ -399,8 +407,8 @@ fn make(args: MakeArgs) -> Result<()> {
|
|||
}
|
||||
|
||||
fn info(args: InfoArgs) -> Result<()> {
|
||||
let file = map_file(args.rel_file)?;
|
||||
let (header, mut module_obj) = process_rel(&mut file.as_reader(), "")?;
|
||||
let mut file = open_file(&args.rel_file, true)?;
|
||||
let (header, mut module_obj) = process_rel(file.as_mut(), "")?;
|
||||
|
||||
let mut state = AnalyzerState::default();
|
||||
state.detect_functions(&module_obj)?;
|
||||
|
@ -458,7 +466,7 @@ fn info(args: InfoArgs) -> Result<()> {
|
|||
if args.relocations {
|
||||
println!("\nRelocations:");
|
||||
println!(" [Source] section:address RelocType -> [Target] module:section:address");
|
||||
print_relocations(&mut file.as_reader(), &header)?;
|
||||
print_relocations(file.as_mut(), &header)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -467,11 +475,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.display());
|
||||
log::info!("Loading {}", args.dol_file);
|
||||
let mut obj = {
|
||||
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())?
|
||||
let mut file = open_file(&args.dol_file, true)?;
|
||||
let name = args.dol_file.file_stem().unwrap_or_default();
|
||||
process_dol(file.map()?, name)?
|
||||
};
|
||||
|
||||
log::info!("Performing signature analysis");
|
||||
|
@ -481,10 +489,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, 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())?;
|
||||
let (path, mut entry) = result?;
|
||||
log::info!("Loading {}", path);
|
||||
let name = path.file_stem().unwrap_or_default();
|
||||
let (_, obj) = process_rel(&mut entry, name)?;
|
||||
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),
|
||||
|
@ -493,7 +501,7 @@ fn merge(args: MergeArgs) -> Result<()> {
|
|||
}
|
||||
|
||||
log::info!("Merging {} REL(s)", processed);
|
||||
let mut section_map: BTreeMap<u32, BTreeMap<u32, u32>> = BTreeMap::new();
|
||||
let mut section_map: BTreeMap<u32, BTreeMap<ObjSectionIndex, 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() {
|
||||
|
@ -512,7 +520,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 as u32, offset)?;
|
||||
section_map.nested_insert(module.module_id, mod_section.elf_index, offset)?;
|
||||
for (_, mod_symbol) in module.symbols.for_section(mod_section_index) {
|
||||
obj.symbols.add_direct(ObjSymbol {
|
||||
name: mod_symbol.name.clone(),
|
||||
|
@ -536,7 +544,8 @@ 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 u32)]
|
||||
let source_addr = (section_map[&module.module_id]
|
||||
[&(rel_reloc.section as ObjSectionIndex)]
|
||||
+ rel_reloc.address)
|
||||
& !3;
|
||||
let target_addr = if rel_reloc.module_id == 0 {
|
||||
|
@ -600,7 +609,7 @@ fn merge(args: MergeArgs) -> Result<()> {
|
|||
tracker.apply(&mut obj, false)?;
|
||||
|
||||
// Write ELF
|
||||
log::info!("Writing {}", args.out_file.display());
|
||||
log::info!("Writing {}", args.out_file);
|
||||
fs::write(&args.out_file, write_elf(&obj, false)?)?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
491
src/cmd/rso.rs
491
src/cmd/rso.rs
|
@ -1,9 +1,26 @@
|
|||
use std::path::PathBuf;
|
||||
use std::io::{BufRead, Seek, Write};
|
||||
|
||||
use anyhow::Result;
|
||||
use anyhow::{bail, ensure, Context, 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::map_file, rso::process_rso};
|
||||
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,
|
||||
};
|
||||
|
||||
#[derive(FromArgs, PartialEq, Debug)]
|
||||
/// Commands for processing RSO files.
|
||||
|
@ -17,30 +34,486 @@ 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)]
|
||||
#[argp(positional, from_str_fn(native_path))]
|
||||
/// RSO file
|
||||
rso_file: PathBuf,
|
||||
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>,
|
||||
}
|
||||
|
||||
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 file = map_file(args.rso_file)?;
|
||||
let obj = process_rso(&mut file.as_reader())?;
|
||||
#[allow(clippy::let_and_return)]
|
||||
obj
|
||||
let mut file = open_file(&args.rso_file, true)?;
|
||||
process_rso(file.as_mut())?
|
||||
};
|
||||
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(())
|
||||
}
|
||||
|
|
|
@ -1,16 +1,21 @@
|
|||
use std::{
|
||||
fs::File,
|
||||
io::{stdout, BufRead, BufReader, Read, Write},
|
||||
path::{Path, PathBuf},
|
||||
io::{stdout, BufRead, Read, Write},
|
||||
};
|
||||
|
||||
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, open_file, process_rsp, touch};
|
||||
use crate::{
|
||||
util::{
|
||||
file::{buf_writer, process_rsp, touch},
|
||||
path::native_path,
|
||||
},
|
||||
vfs::open_file,
|
||||
};
|
||||
|
||||
#[derive(FromArgs, PartialEq, Eq, Debug)]
|
||||
/// Print or check SHA1 (160-bit) checksums.
|
||||
|
@ -19,13 +24,13 @@ pub struct Args {
|
|||
#[argp(switch, short = 'c')]
|
||||
/// check SHA sums against given list
|
||||
check: bool,
|
||||
#[argp(positional)]
|
||||
#[argp(positional, from_str_fn(native_path))]
|
||||
/// path to input file(s)
|
||||
files: Vec<PathBuf>,
|
||||
#[argp(option, short = 'o')]
|
||||
files: Vec<Utf8NativePathBuf>,
|
||||
#[argp(option, short = 'o', from_str_fn(native_path))]
|
||||
/// (check) touch output file on successful check
|
||||
/// (hash) write hash(es) to output file
|
||||
output: Option<PathBuf>,
|
||||
output: Option<Utf8NativePathBuf>,
|
||||
#[argp(switch, short = 'q')]
|
||||
/// only print failures and a summary
|
||||
quiet: bool,
|
||||
|
@ -36,25 +41,25 @@ const DEFAULT_BUF_SIZE: usize = 8192;
|
|||
pub fn run(args: Args) -> Result<()> {
|
||||
if args.check {
|
||||
for path in process_rsp(&args.files)? {
|
||||
let file = open_file(&path)?;
|
||||
check(&args, &mut BufReader::new(file))?;
|
||||
let mut file = open_file(&path, false)?;
|
||||
check(&args, file.as_mut())?;
|
||||
}
|
||||
if let Some(out_path) = &args.output {
|
||||
touch(out_path)
|
||||
.with_context(|| format!("Failed to touch output file '{}'", out_path.display()))?;
|
||||
.with_context(|| format!("Failed to touch output file '{}'", out_path))?;
|
||||
}
|
||||
} 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.display())
|
||||
})?)
|
||||
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())
|
||||
};
|
||||
for path in process_rsp(&args.files)? {
|
||||
let mut file = open_file(&path)?;
|
||||
hash(w.as_mut(), &mut file, &path)?
|
||||
let mut file = open_file(&path, false)?;
|
||||
hash(w.as_mut(), file.as_mut(), &path)?
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
@ -111,7 +116,7 @@ where R: BufRead + ?Sized {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn hash<R, W>(w: &mut W, reader: &mut R, path: &Path) -> Result<()>
|
||||
fn hash<R, W>(w: &mut W, reader: &mut R, path: &Utf8NativePath) -> Result<()>
|
||||
where
|
||||
R: Read + ?Sized,
|
||||
W: Write + ?Sized,
|
||||
|
@ -120,7 +125,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.to_slash_lossy())?;
|
||||
writeln!(w, "{} {}", hash_str, path.with_unix_encoding())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
@ -1,13 +1,9 @@
|
|||
use std::{borrow::Cow, fs, fs::DirBuilder, path::PathBuf};
|
||||
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use anyhow::Result;
|
||||
use argp::FromArgs;
|
||||
use itertools::Itertools;
|
||||
use typed_path::Utf8NativePathBuf;
|
||||
|
||||
use crate::util::{
|
||||
file::{decompress_if_needed, map_file},
|
||||
u8_arc::{U8Node, U8View},
|
||||
};
|
||||
use super::vfs;
|
||||
use crate::util::path::native_path;
|
||||
|
||||
#[derive(FromArgs, PartialEq, Debug)]
|
||||
/// Commands for processing U8 (arc) files.
|
||||
|
@ -28,23 +24,29 @@ enum SubCommand {
|
|||
/// Views U8 (arc) file information.
|
||||
#[argp(subcommand, name = "list")]
|
||||
pub struct ListArgs {
|
||||
#[argp(positional)]
|
||||
#[argp(positional, from_str_fn(native_path))]
|
||||
/// U8 (arc) file
|
||||
file: PathBuf,
|
||||
file: Utf8NativePathBuf,
|
||||
#[argp(switch, short = 's')]
|
||||
/// Only print filenames.
|
||||
short: bool,
|
||||
}
|
||||
|
||||
#[derive(FromArgs, PartialEq, Eq, Debug)]
|
||||
/// Extracts U8 (arc) file contents.
|
||||
#[argp(subcommand, name = "extract")]
|
||||
pub struct ExtractArgs {
|
||||
#[argp(positional)]
|
||||
#[argp(positional, from_str_fn(native_path))]
|
||||
/// U8 (arc) file
|
||||
file: PathBuf,
|
||||
#[argp(option, short = 'o')]
|
||||
file: Utf8NativePathBuf,
|
||||
#[argp(option, short = 'o', from_str_fn(native_path))]
|
||||
/// output directory
|
||||
output: Option<PathBuf>,
|
||||
output: Option<Utf8NativePathBuf>,
|
||||
#[argp(switch)]
|
||||
/// Do not decompress files when copying.
|
||||
no_decompress: bool,
|
||||
#[argp(switch, short = 'q')]
|
||||
/// quiet output
|
||||
/// Quiet output. Don't print anything except errors.
|
||||
quiet: bool,
|
||||
}
|
||||
|
||||
|
@ -56,66 +58,16 @@ pub fn run(args: Args) -> Result<()> {
|
|||
}
|
||||
|
||||
fn list(args: ListArgs) -> Result<()> {
|
||||
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(())
|
||||
})
|
||||
let path = Utf8NativePathBuf::from(format!("{}:", args.file));
|
||||
vfs::ls(vfs::LsArgs { path, short: args.short, recursive: true })
|
||||
}
|
||||
|
||||
fn extract(args: ExtractArgs) -> Result<()> {
|
||||
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(())
|
||||
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,
|
||||
})
|
||||
}
|
||||
|
||||
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(())
|
||||
}
|
||||
|
|
|
@ -0,0 +1,286 @@
|
|||
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(())
|
||||
}
|
|
@ -1,12 +1,17 @@
|
|||
use std::{fs, path::PathBuf};
|
||||
use std::fs;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use argp::FromArgs;
|
||||
use typed_path::Utf8NativePathBuf;
|
||||
|
||||
use crate::util::{
|
||||
file::{map_file_basic, process_rsp},
|
||||
use crate::{
|
||||
util::{
|
||||
file::process_rsp,
|
||||
ncompress::{compress_yay0, decompress_yay0},
|
||||
path::native_path,
|
||||
IntoCow, ToCow,
|
||||
},
|
||||
vfs::open_file,
|
||||
};
|
||||
|
||||
#[derive(FromArgs, PartialEq, Debug)]
|
||||
|
@ -28,26 +33,26 @@ enum SubCommand {
|
|||
/// Compresses files using YAY0.
|
||||
#[argp(subcommand, name = "compress")]
|
||||
pub struct CompressArgs {
|
||||
#[argp(positional)]
|
||||
#[argp(positional, from_str_fn(native_path))]
|
||||
/// Files to compress
|
||||
files: Vec<PathBuf>,
|
||||
#[argp(option, short = 'o')]
|
||||
files: Vec<Utf8NativePathBuf>,
|
||||
#[argp(option, short = 'o', from_str_fn(native_path))]
|
||||
/// Output file (or directory, if multiple files are specified).
|
||||
/// If not specified, compresses in-place.
|
||||
output: Option<PathBuf>,
|
||||
output: Option<Utf8NativePathBuf>,
|
||||
}
|
||||
|
||||
#[derive(FromArgs, PartialEq, Eq, Debug)]
|
||||
/// Decompresses YAY0-compressed files.
|
||||
#[argp(subcommand, name = "decompress")]
|
||||
pub struct DecompressArgs {
|
||||
#[argp(positional)]
|
||||
#[argp(positional, from_str_fn(native_path))]
|
||||
/// YAY0-compressed files
|
||||
files: Vec<PathBuf>,
|
||||
#[argp(option, short = 'o')]
|
||||
files: Vec<Utf8NativePathBuf>,
|
||||
#[argp(option, short = 'o', from_str_fn(native_path))]
|
||||
/// Output file (or directory, if multiple files are specified).
|
||||
/// If not specified, decompresses in-place.
|
||||
output: Option<PathBuf>,
|
||||
output: Option<Utf8NativePathBuf>,
|
||||
}
|
||||
|
||||
pub fn run(args: Args) -> Result<()> {
|
||||
|
@ -62,8 +67,8 @@ fn compress(args: CompressArgs) -> Result<()> {
|
|||
let single_file = files.len() == 1;
|
||||
for path in files {
|
||||
let data = {
|
||||
let file = map_file_basic(&path)?;
|
||||
compress_yay0(file.as_slice())
|
||||
let mut file = open_file(&path, false)?;
|
||||
compress_yay0(file.map()?)
|
||||
};
|
||||
let out_path = if let Some(output) = &args.output {
|
||||
if single_file {
|
||||
|
@ -75,7 +80,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.display()))?;
|
||||
.with_context(|| format!("Failed to write '{}'", out_path))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -85,9 +90,9 @@ fn decompress(args: DecompressArgs) -> Result<()> {
|
|||
let single_file = files.len() == 1;
|
||||
for path in files {
|
||||
let data = {
|
||||
let file = map_file_basic(&path)?;
|
||||
decompress_yay0(file.as_slice())
|
||||
.with_context(|| format!("Failed to decompress '{}' using Yay0", path.display()))?
|
||||
let mut file = open_file(&path, true)?;
|
||||
decompress_yay0(file.map()?)
|
||||
.with_context(|| format!("Failed to decompress '{}' using Yay0", path))?
|
||||
};
|
||||
let out_path = if let Some(output) = &args.output {
|
||||
if single_file {
|
||||
|
@ -99,7 +104,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.display()))?;
|
||||
.with_context(|| format!("Failed to write '{}'", out_path))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -1,12 +1,17 @@
|
|||
use std::{fs, path::PathBuf};
|
||||
use std::fs;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use argp::FromArgs;
|
||||
use typed_path::Utf8NativePathBuf;
|
||||
|
||||
use crate::util::{
|
||||
file::{map_file_basic, process_rsp},
|
||||
use crate::{
|
||||
util::{
|
||||
file::process_rsp,
|
||||
ncompress::{compress_yaz0, decompress_yaz0},
|
||||
path::native_path,
|
||||
IntoCow, ToCow,
|
||||
},
|
||||
vfs::open_file,
|
||||
};
|
||||
|
||||
#[derive(FromArgs, PartialEq, Debug)]
|
||||
|
@ -28,26 +33,26 @@ enum SubCommand {
|
|||
/// Compresses files using YAZ0.
|
||||
#[argp(subcommand, name = "compress")]
|
||||
pub struct CompressArgs {
|
||||
#[argp(positional)]
|
||||
#[argp(positional, from_str_fn(native_path))]
|
||||
/// Files to compress
|
||||
files: Vec<PathBuf>,
|
||||
#[argp(option, short = 'o')]
|
||||
files: Vec<Utf8NativePathBuf>,
|
||||
#[argp(option, short = 'o', from_str_fn(native_path))]
|
||||
/// Output file (or directory, if multiple files are specified).
|
||||
/// If not specified, compresses in-place.
|
||||
output: Option<PathBuf>,
|
||||
output: Option<Utf8NativePathBuf>,
|
||||
}
|
||||
|
||||
#[derive(FromArgs, PartialEq, Eq, Debug)]
|
||||
/// Decompresses YAZ0-compressed files.
|
||||
#[argp(subcommand, name = "decompress")]
|
||||
pub struct DecompressArgs {
|
||||
#[argp(positional)]
|
||||
#[argp(positional, from_str_fn(native_path))]
|
||||
/// YAZ0-compressed files
|
||||
files: Vec<PathBuf>,
|
||||
#[argp(option, short = 'o')]
|
||||
files: Vec<Utf8NativePathBuf>,
|
||||
#[argp(option, short = 'o', from_str_fn(native_path))]
|
||||
/// Output file (or directory, if multiple files are specified).
|
||||
/// If not specified, decompresses in-place.
|
||||
output: Option<PathBuf>,
|
||||
output: Option<Utf8NativePathBuf>,
|
||||
}
|
||||
|
||||
pub fn run(args: Args) -> Result<()> {
|
||||
|
@ -62,8 +67,8 @@ fn compress(args: CompressArgs) -> Result<()> {
|
|||
let single_file = files.len() == 1;
|
||||
for path in files {
|
||||
let data = {
|
||||
let file = map_file_basic(&path)?;
|
||||
compress_yaz0(file.as_slice())
|
||||
let mut file = open_file(&path, false)?;
|
||||
compress_yaz0(file.map()?)
|
||||
};
|
||||
let out_path = if let Some(output) = &args.output {
|
||||
if single_file {
|
||||
|
@ -75,7 +80,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.display()))?;
|
||||
.with_context(|| format!("Failed to write '{}'", out_path))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -85,9 +90,9 @@ fn decompress(args: DecompressArgs) -> Result<()> {
|
|||
let single_file = files.len() == 1;
|
||||
for path in files {
|
||||
let data = {
|
||||
let file = map_file_basic(&path)?;
|
||||
decompress_yaz0(file.as_slice())
|
||||
.with_context(|| format!("Failed to decompress '{}' using Yaz0", path.display()))?
|
||||
let mut file = open_file(&path, false)?;
|
||||
decompress_yaz0(file.map()?)
|
||||
.with_context(|| format!("Failed to decompress '{}' using Yaz0", path))?
|
||||
};
|
||||
let out_path = if let Some(output) = &args.output {
|
||||
if single_file {
|
||||
|
@ -99,7 +104,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.display()))?;
|
||||
.with_context(|| format!("Failed to write '{}'", out_path))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
12
src/main.rs
12
src/main.rs
|
@ -1,3 +1,4 @@
|
|||
#![deny(unused_crate_dependencies)]
|
||||
use std::{env, ffi::OsStr, fmt::Display, path::PathBuf, process::exit, str::FromStr};
|
||||
|
||||
use anyhow::Error;
|
||||
|
@ -12,6 +13,13 @@ 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 {
|
||||
|
@ -89,13 +97,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),
|
||||
}
|
||||
|
@ -164,13 +172,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),
|
||||
});
|
||||
|
|
|
@ -13,7 +13,9 @@ use std::{
|
|||
use anyhow::{anyhow, bail, ensure, Result};
|
||||
use objdiff_core::obj::split_meta::SplitMeta;
|
||||
pub use relocations::{ObjReloc, ObjRelocKind, ObjRelocations};
|
||||
pub use sections::{ObjSection, ObjSectionKind, ObjSections};
|
||||
pub use sections::{
|
||||
section_kind_for_section, ObjSection, ObjSectionKind, ObjSections, SectionIndex,
|
||||
};
|
||||
pub use splits::{ObjSplit, ObjSplits};
|
||||
pub use symbols::{
|
||||
best_match_for_reloc, ObjDataKind, ObjSymbol, ObjSymbolFlagSet, ObjSymbolFlags, ObjSymbolKind,
|
||||
|
@ -47,6 +49,8 @@ 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)]
|
||||
|
@ -130,7 +134,12 @@ impl ObjInfo {
|
|||
self.symbols.add(in_symbol, replace)
|
||||
}
|
||||
|
||||
pub fn add_split(&mut self, section_index: usize, address: u32, split: ObjSplit) -> Result<()> {
|
||||
pub fn add_split(
|
||||
&mut self,
|
||||
section_index: SectionIndex,
|
||||
address: u32,
|
||||
split: ObjSplit,
|
||||
) -> Result<()> {
|
||||
let section = self
|
||||
.sections
|
||||
.get_mut(section_index)
|
||||
|
@ -320,8 +329,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()
|
||||
}
|
||||
|
|
|
@ -7,7 +7,10 @@ use std::{
|
|||
use anyhow::{anyhow, bail, ensure, Result};
|
||||
use itertools::Itertools;
|
||||
|
||||
use crate::obj::{ObjKind, ObjRelocations, ObjSplit, ObjSplits, ObjSymbol};
|
||||
use crate::{
|
||||
analysis::cfa::SectionAddress,
|
||||
obj::{ObjKind, ObjRelocations, ObjSplit, ObjSplits, ObjSymbol},
|
||||
};
|
||||
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub enum ObjSectionKind {
|
||||
|
@ -26,7 +29,7 @@ pub struct ObjSection {
|
|||
pub data: Vec<u8>,
|
||||
pub align: u64,
|
||||
/// REL files reference the original ELF section indices
|
||||
pub elf_index: usize,
|
||||
pub elf_index: SectionIndex,
|
||||
pub relocations: ObjRelocations,
|
||||
pub virtual_address: Option<u64>,
|
||||
pub file_offset: u64,
|
||||
|
@ -40,38 +43,45 @@ 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 = (usize, &ObjSection)> {
|
||||
self.sections.iter().enumerate()
|
||||
pub fn iter(&self) -> impl DoubleEndedIterator<Item = (SectionIndex, &ObjSection)> {
|
||||
self.sections.iter().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 iter_mut(&mut self) -> impl DoubleEndedIterator<Item = (SectionIndex, &mut ObjSection)> {
|
||||
self.sections.iter_mut().enumerate().map(|(i, s)| (i as SectionIndex, s))
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize { self.sections.len() }
|
||||
pub fn len(&self) -> SectionIndex { self.sections.len() as SectionIndex }
|
||||
|
||||
pub fn is_empty(&self) -> bool { self.sections.is_empty() }
|
||||
|
||||
pub fn next_section_index(&self) -> usize { self.sections.len() }
|
||||
pub fn next_section_index(&self) -> SectionIndex { self.sections.len() as SectionIndex }
|
||||
|
||||
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(&self, index: SectionIndex) -> Option<&ObjSection> {
|
||||
self.sections.get(index as usize)
|
||||
}
|
||||
|
||||
pub fn get_elf_index(&self, elf_index: usize) -> Option<(usize, &ObjSection)> {
|
||||
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)> {
|
||||
self.iter().find(|&(_, s)| s.elf_index == elf_index)
|
||||
}
|
||||
|
||||
pub fn get_elf_index_mut(&mut self, elf_index: usize) -> Option<(usize, &mut ObjSection)> {
|
||||
pub fn get_elf_index_mut(
|
||||
&mut self,
|
||||
elf_index: SectionIndex,
|
||||
) -> Option<(SectionIndex, &mut ObjSection)> {
|
||||
self.iter_mut().find(|(_, s)| s.elf_index == elf_index)
|
||||
}
|
||||
|
||||
pub fn at_address(&self, addr: u32) -> Result<(usize, &ObjSection)> {
|
||||
pub fn at_address(&self, addr: u32) -> Result<(SectionIndex, &ObjSection)> {
|
||||
ensure!(
|
||||
self.obj_kind == ObjKind::Executable,
|
||||
"Use of ObjSections::at_address in relocatable object"
|
||||
|
@ -81,7 +91,7 @@ impl ObjSections {
|
|||
.ok_or_else(|| anyhow!("Failed to locate section @ {:#010X}", addr))
|
||||
}
|
||||
|
||||
pub fn at_address_mut(&mut self, addr: u32) -> Result<(usize, &mut ObjSection)> {
|
||||
pub fn at_address_mut(&mut self, addr: u32) -> Result<(SectionIndex, &mut ObjSection)> {
|
||||
ensure!(
|
||||
self.obj_kind == ObjKind::Executable,
|
||||
"Use of ObjSections::at_address_mut in relocatable object"
|
||||
|
@ -91,7 +101,7 @@ impl ObjSections {
|
|||
.ok_or_else(|| anyhow!("Failed to locate section @ {:#010X}", addr))
|
||||
}
|
||||
|
||||
pub fn with_range(&self, range: Range<u32>) -> Result<(usize, &ObjSection)> {
|
||||
pub fn with_range(&self, range: Range<u32>) -> Result<(SectionIndex, &ObjSection)> {
|
||||
ensure!(
|
||||
self.obj_kind == ObjKind::Executable,
|
||||
"Use of ObjSections::with_range in relocatable object"
|
||||
|
@ -104,46 +114,52 @@ impl ObjSections {
|
|||
pub fn by_kind(
|
||||
&self,
|
||||
kind: ObjSectionKind,
|
||||
) -> impl DoubleEndedIterator<Item = (usize, &ObjSection)> {
|
||||
) -> impl DoubleEndedIterator<Item = (SectionIndex, &ObjSection)> {
|
||||
self.iter().filter(move |(_, s)| s.kind == kind)
|
||||
}
|
||||
|
||||
pub fn by_name(&self, name: &str) -> Result<Option<(usize, &ObjSection)>> {
|
||||
pub fn by_name(&self, name: &str) -> Result<Option<(SectionIndex, &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) -> usize {
|
||||
pub fn push(&mut self, section: ObjSection) -> SectionIndex {
|
||||
let index = self.sections.len();
|
||||
self.sections.push(section);
|
||||
index
|
||||
index as SectionIndex
|
||||
}
|
||||
|
||||
pub fn all_splits(
|
||||
&self,
|
||||
) -> impl DoubleEndedIterator<Item = (usize, &ObjSection, u32, &ObjSplit)> {
|
||||
) -> impl DoubleEndedIterator<Item = (SectionIndex, &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<(usize, u32)> {
|
||||
pub fn common_bss_start(&self) -> Option<SectionAddress> {
|
||||
let Ok(Some((section_index, section))) = self.by_name(".bss") else {
|
||||
return None;
|
||||
};
|
||||
section.splits.iter().find(|(_, split)| split.common).map(|(addr, _)| (section_index, addr))
|
||||
section
|
||||
.splits
|
||||
.iter()
|
||||
.find(|(_, split)| split.common)
|
||||
.map(|(addr, _)| SectionAddress::new(section_index, addr))
|
||||
}
|
||||
}
|
||||
|
||||
impl Index<usize> for ObjSections {
|
||||
impl Index<SectionIndex> for ObjSections {
|
||||
type Output = ObjSection;
|
||||
|
||||
fn index(&self, index: usize) -> &Self::Output { &self.sections[index] }
|
||||
fn index(&self, index: SectionIndex) -> &Self::Output { &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 IndexMut<SectionIndex> for ObjSections {
|
||||
fn index_mut(&mut self, index: SectionIndex) -> &mut Self::Output {
|
||||
&mut self.sections[index as usize]
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjSection {
|
||||
|
@ -218,7 +234,7 @@ impl ObjSection {
|
|||
}
|
||||
}
|
||||
|
||||
fn section_kind_for_section(section_name: &str) -> Result<ObjSectionKind> {
|
||||
pub 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" => {
|
||||
|
|
|
@ -4,7 +4,7 @@ use anyhow::{anyhow, Result};
|
|||
use itertools::Itertools;
|
||||
|
||||
use crate::{
|
||||
obj::{ObjInfo, ObjSection},
|
||||
obj::{ObjInfo, ObjSection, SectionIndex},
|
||||
util::{nested::NestedVec, split::default_section_align},
|
||||
};
|
||||
|
||||
|
@ -28,7 +28,7 @@ impl ObjSplit {
|
|||
pub fn alignment(
|
||||
&self,
|
||||
obj: &ObjInfo,
|
||||
section_index: usize,
|
||||
section_index: SectionIndex,
|
||||
section: &ObjSection,
|
||||
split_addr: u32,
|
||||
) -> u32 {
|
||||
|
|
|
@ -12,7 +12,7 @@ use serde_repr::{Deserialize_repr, Serialize_repr};
|
|||
|
||||
use crate::{
|
||||
analysis::cfa::SectionAddress,
|
||||
obj::{ObjKind, ObjRelocKind, ObjSections},
|
||||
obj::{sections::SectionIndex, 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<usize>,
|
||||
pub section: Option<SectionIndex>,
|
||||
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 = usize;
|
||||
pub type SymbolIndex = u32;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ObjSymbols {
|
||||
|
@ -216,8 +216,10 @@ 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);
|
||||
}
|
||||
|
@ -312,7 +314,7 @@ impl ObjSymbols {
|
|||
}
|
||||
symbol_idx
|
||||
} else {
|
||||
let target_symbol_idx = self.symbols.len();
|
||||
let target_symbol_idx = self.symbols.len() as SymbolIndex;
|
||||
self.add_direct(ObjSymbol {
|
||||
name: in_symbol.name,
|
||||
demangled_name: in_symbol.demangled_name,
|
||||
|
@ -333,9 +335,10 @@ impl ObjSymbols {
|
|||
}
|
||||
|
||||
pub fn add_direct(&mut self, in_symbol: ObjSymbol) -> Result<SymbolIndex> {
|
||||
let symbol_idx = self.symbols.len();
|
||||
let symbol_idx = self.symbols.len() as SymbolIndex;
|
||||
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);
|
||||
}
|
||||
|
@ -355,28 +358,30 @@ impl ObjSymbols {
|
|||
Ok(symbol_idx)
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl DoubleEndedIterator<Item = &ObjSymbol> { self.symbols.iter() }
|
||||
pub fn iter(&self) -> impl DoubleEndedIterator<Item = (SymbolIndex, &ObjSymbol)> {
|
||||
self.symbols.iter().enumerate().map(|(i, s)| (i as SymbolIndex, s))
|
||||
}
|
||||
|
||||
pub fn count(&self) -> usize { self.symbols.len() }
|
||||
pub fn count(&self) -> SymbolIndex { self.symbols.len() as SymbolIndex }
|
||||
|
||||
pub fn at_section_address(
|
||||
&self,
|
||||
section_idx: usize,
|
||||
section_idx: SectionIndex,
|
||||
addr: u32,
|
||||
) -> impl DoubleEndedIterator<Item = (SymbolIndex, &ObjSymbol)> {
|
||||
self.symbols_by_section
|
||||
.get(section_idx)
|
||||
.get(section_idx as usize)
|
||||
.and_then(|v| v.get(&addr))
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.map(move |&idx| (idx, &self.symbols[idx]))
|
||||
.map(move |&idx| (idx, &self.symbols[idx as usize]))
|
||||
// "Stripped" symbols don't actually exist at the address
|
||||
.filter(|(_, sym)| !sym.flags.is_stripped())
|
||||
}
|
||||
|
||||
pub fn kind_at_section_address(
|
||||
&self,
|
||||
section_idx: usize,
|
||||
section_idx: SectionIndex,
|
||||
addr: u32,
|
||||
kind: ObjSymbolKind,
|
||||
) -> Result<Option<(SymbolIndex, &ObjSymbol)>> {
|
||||
|
@ -397,7 +402,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])))
|
||||
.flat_map(move |v| v.iter().map(move |u| (*u, &self.symbols[*u as usize])))
|
||||
}
|
||||
|
||||
// Iterate over all ABS symbols
|
||||
|
@ -405,24 +410,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])))
|
||||
.flat_map(|(_, v)| v.iter().map(|&u| (u, &self.symbols[u as usize])))
|
||||
.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: usize,
|
||||
section_index: SectionIndex,
|
||||
range: R,
|
||||
) -> impl DoubleEndedIterator<Item = (SymbolIndex, &ObjSymbol)>
|
||||
where
|
||||
R: RangeBounds<u32> + Clone,
|
||||
{
|
||||
self.symbols_by_section
|
||||
.get(section_index)
|
||||
.get(section_index as usize)
|
||||
.into_iter()
|
||||
.flat_map(move |v| v.range(range.clone()))
|
||||
.flat_map(move |(_, v)| v.iter().map(move |u| (*u, &self.symbols[*u])))
|
||||
.flat_map(move |(_, v)| v.iter().map(move |u| (*u, &self.symbols[*u as usize])))
|
||||
}
|
||||
|
||||
pub fn indexes_for_range<R>(
|
||||
|
@ -438,13 +443,13 @@ impl ObjSymbols {
|
|||
|
||||
pub fn for_section(
|
||||
&self,
|
||||
section_idx: usize,
|
||||
section_idx: SectionIndex,
|
||||
) -> impl DoubleEndedIterator<Item = (SymbolIndex, &ObjSymbol)> {
|
||||
self.symbols_by_section
|
||||
.get(section_idx)
|
||||
.get(section_idx as usize)
|
||||
.into_iter()
|
||||
.flat_map(|v| v.iter().map(|(_, v)| v))
|
||||
.flat_map(move |v| v.iter().map(move |u| (*u, &self.symbols[*u])))
|
||||
.flat_map(move |v| v.iter().map(move |u| (*u, &self.symbols[*u as usize])))
|
||||
}
|
||||
|
||||
pub fn for_name(
|
||||
|
@ -454,7 +459,7 @@ impl ObjSymbols {
|
|||
self.symbols_by_name
|
||||
.get(name)
|
||||
.into_iter()
|
||||
.flat_map(move |v| v.iter().map(move |u| (*u, &self.symbols[*u])))
|
||||
.flat_map(move |v| v.iter().map(move |u| (*u, &self.symbols[*u as usize])))
|
||||
}
|
||||
|
||||
pub fn by_name(&self, name: &str) -> Result<Option<(SymbolIndex, &ObjSymbol)>> {
|
||||
|
@ -515,11 +520,11 @@ impl ObjSymbols {
|
|||
&self,
|
||||
kind: ObjSymbolKind,
|
||||
) -> impl DoubleEndedIterator<Item = (SymbolIndex, &ObjSymbol)> {
|
||||
self.symbols.iter().enumerate().filter(move |(_, sym)| sym.kind == kind)
|
||||
self.iter().filter(move |(_, sym)| sym.kind == kind)
|
||||
}
|
||||
|
||||
pub fn replace(&mut self, index: SymbolIndex, symbol: ObjSymbol) -> Result<()> {
|
||||
let symbol_ref = &mut self.symbols[index];
|
||||
let symbol_ref = &mut self.symbols[index as usize];
|
||||
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 {
|
||||
|
@ -545,7 +550,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]))
|
||||
.map(|&idx| (idx, &self.symbols[idx as usize]))
|
||||
.filter(|(_, sym)| {
|
||||
(sym.section.is_none() || sym.section == Some(target_addr.section))
|
||||
&& sym.referenced_by(reloc_kind)
|
||||
|
@ -570,14 +575,14 @@ impl ObjSymbols {
|
|||
|
||||
#[inline]
|
||||
pub fn flags(&mut self, idx: SymbolIndex) -> &mut ObjSymbolFlagSet {
|
||||
&mut self.symbols[idx].flags
|
||||
&mut self.symbols[idx as usize].flags
|
||||
}
|
||||
}
|
||||
|
||||
impl Index<SymbolIndex> for ObjSymbols {
|
||||
type Output = ObjSymbol;
|
||||
|
||||
fn index(&self, index: usize) -> &Self::Output { &self.symbols[index] }
|
||||
fn index(&self, index: SymbolIndex) -> &Self::Output { &self.symbols[index as usize] }
|
||||
}
|
||||
|
||||
impl ObjSymbol {
|
||||
|
|
|
@ -7,7 +7,7 @@ use anyhow::Result;
|
|||
use io::{Error, ErrorKind};
|
||||
|
||||
use crate::{
|
||||
obj::{ObjSymbol, ObjSymbolKind},
|
||||
obj::{ObjSymbol, ObjSymbolKind, SectionIndex},
|
||||
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(),
|
||||
index: sections.len() as SectionIndex,
|
||||
});
|
||||
}
|
||||
for sym in &symtab.symbols {
|
||||
|
@ -230,7 +230,7 @@ impl AlfSymbol {
|
|||
name,
|
||||
demangled_name,
|
||||
address: self.address as u64,
|
||||
section: Some(self.section as usize - 1),
|
||||
section: Some(self.section as SectionIndex - 1),
|
||||
size: self.size as u64,
|
||||
size_known: true,
|
||||
flags: Default::default(),
|
||||
|
|
|
@ -11,7 +11,7 @@ use ppc750cl::{Argument, Ins, InsIter, Opcode};
|
|||
use crate::{
|
||||
obj::{
|
||||
ObjDataKind, ObjInfo, ObjReloc, ObjRelocKind, ObjSection, ObjSectionKind, ObjSymbol,
|
||||
ObjSymbolKind,
|
||||
ObjSymbolKind, SymbolIndex,
|
||||
},
|
||||
util::nested::NestedVec,
|
||||
};
|
||||
|
@ -25,7 +25,7 @@ enum SymbolEntryKind {
|
|||
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
struct SymbolEntry {
|
||||
index: usize,
|
||||
index: SymbolIndex,
|
||||
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().cloned().collect();
|
||||
let mut symbols: Vec<ObjSymbol> = obj.symbols.iter().map(|(_, s)| s.clone()).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();
|
||||
let symbol_idx = symbols.len() as SymbolIndex;
|
||||
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];
|
||||
let target = &symbols[reloc.target_symbol as usize];
|
||||
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].entry(address as u32) {
|
||||
let vec = match section_entries[target_section_idx as usize].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();
|
||||
let symbol_idx = symbols.len() as SymbolIndex;
|
||||
symbols.push(ObjSymbol {
|
||||
name: format!(".L_{display_address:08X}"),
|
||||
address: display_address,
|
||||
|
@ -181,13 +181,17 @@ where W: Write + ?Sized {
|
|||
}
|
||||
|
||||
for (section_index, section) in obj.sections.iter() {
|
||||
let entries = §ion_entries[section_index];
|
||||
let relocations = §ion_relocations[section_index];
|
||||
let entries = §ion_entries[section_index as usize];
|
||||
let relocations = §ion_relocations[section_index as usize];
|
||||
|
||||
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).filter(|(_, s)| s.name == section.name).count();
|
||||
let subsection = obj
|
||||
.sections
|
||||
.iter()
|
||||
.take(section_index as usize)
|
||||
.filter(|(_, s)| s.name == section.name)
|
||||
.count();
|
||||
|
||||
loop {
|
||||
if current_address >= section_end {
|
||||
|
@ -369,7 +373,7 @@ fn write_symbol_entry<W>(
|
|||
where
|
||||
W: Write + ?Sized,
|
||||
{
|
||||
let symbol = &symbols[entry.index];
|
||||
let symbol = &symbols[entry.index as usize];
|
||||
|
||||
// Skip writing certain symbols
|
||||
if symbol.kind == ObjSymbolKind::Section {
|
||||
|
@ -432,9 +436,37 @@ 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,
|
||||
|
@ -476,7 +508,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]).collect_vec();
|
||||
let dbg_symbols = vec.iter().map(|e| &symbols[e.index as usize]).collect_vec();
|
||||
bail!(
|
||||
"Unaligned symbol entry @ {:#010X}:\n\t{:?}",
|
||||
section.virtual_address.unwrap_or(0) as u32 + sym_addr,
|
||||
|
@ -557,7 +589,7 @@ fn find_symbol_kind(
|
|||
for entry in entries {
|
||||
match entry.kind {
|
||||
SymbolEntryKind::Start => {
|
||||
let new_kind = symbols[entry.index].kind;
|
||||
let new_kind = symbols[entry.index as usize].kind;
|
||||
if !matches!(new_kind, ObjSymbolKind::Unknown | ObjSymbolKind::Section) {
|
||||
ensure!(
|
||||
!found || new_kind == kind,
|
||||
|
@ -583,11 +615,11 @@ fn find_data_kind(
|
|||
for entry in entries {
|
||||
match entry.kind {
|
||||
SymbolEntryKind::Start => {
|
||||
let new_kind = symbols[entry.index].data_kind;
|
||||
let new_kind = symbols[entry.index as usize].data_kind;
|
||||
if !matches!(new_kind, ObjDataKind::Unknown) {
|
||||
if found && new_kind != kind {
|
||||
for entry in entries {
|
||||
log::error!("Symbol {:?}", symbols[entry.index]);
|
||||
log::error!("Symbol {:?}", symbols[entry.index as usize]);
|
||||
}
|
||||
bail!(
|
||||
"Conflicting data kinds found: {kind:?} and {new_kind:?}",
|
||||
|
@ -778,14 +810,14 @@ where
|
|||
ObjRelocKind::Absolute => {
|
||||
// Attempt to use .rel macro for relative relocations
|
||||
if reloc.addend != 0 {
|
||||
let target = &symbols[reloc.target_symbol];
|
||||
let target = &symbols[reloc.target_symbol as usize];
|
||||
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].get(&target_addr))
|
||||
.and_then(|section_idx| section_entries[section_idx as usize].get(&target_addr))
|
||||
.and_then(|entries| entries.iter().find(|e| e.kind == SymbolEntryKind::Label))
|
||||
{
|
||||
let symbol = &symbols[entry.index];
|
||||
let symbol = &symbols[entry.index as usize];
|
||||
write!(w, "\t.rel ")?;
|
||||
write_symbol_name(w, &target.name)?;
|
||||
write!(w, ", ")?;
|
||||
|
@ -931,7 +963,7 @@ fn write_reloc_symbol<W>(
|
|||
where
|
||||
W: Write + ?Sized,
|
||||
{
|
||||
write_symbol_name(w, &symbols[reloc.target_symbol].name)?;
|
||||
write_symbol_name(w, &symbols[reloc.target_symbol as usize].name)?;
|
||||
match reloc.addend.cmp(&0i64) {
|
||||
Ordering::Greater => write!(w, "+{:#X}", reloc.addend),
|
||||
Ordering::Less => write!(w, "-{:#X}", -reloc.addend),
|
||||
|
|
|
@ -2,7 +2,6 @@ use std::{
|
|||
fs,
|
||||
io::{BufRead, Write},
|
||||
num::ParseIntError,
|
||||
path::Path,
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
|
@ -12,18 +11,20 @@ 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,
|
||||
ObjSymbolFlags, ObjSymbolKind, ObjUnit, SectionIndex,
|
||||
},
|
||||
util::{
|
||||
file::{buf_writer, map_file, FileReadInfo},
|
||||
file::{buf_writer, FileReadInfo},
|
||||
split::default_section_align,
|
||||
},
|
||||
vfs::open_file,
|
||||
};
|
||||
|
||||
pub fn parse_u32(s: &str) -> Result<u32, ParseIntError> {
|
||||
|
@ -34,12 +35,24 @@ pub fn parse_u32(s: &str) -> Result<u32, ParseIntError> {
|
|||
}
|
||||
}
|
||||
|
||||
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() {
|
||||
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() {
|
||||
let line = match result {
|
||||
Ok(line) => line,
|
||||
Err(e) => bail!("Failed to process symbols file: {e:?}"),
|
||||
|
@ -187,19 +200,21 @@ 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<P, Cb>(path: P, cb: Cb, cached_file: Option<FileReadInfo>) -> Result<()>
|
||||
fn write_if_unchanged<Cb>(
|
||||
path: &Utf8NativePath,
|
||||
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) = new_mtime {
|
||||
if new_mtime != cached_file.mtime {
|
||||
if let (Some(new_mtime), Some(old_mtime)) = (new_mtime, cached_file.mtime) {
|
||||
if new_mtime != old_mtime {
|
||||
// File changed, don't write
|
||||
warn!(path = %path.display(), "File changed since read, not updating");
|
||||
warn!(path = %path, "File changed since read, not updating");
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
@ -209,12 +224,12 @@ where
|
|||
cb(&mut buf)?;
|
||||
if xxh3_64(&buf) == cached_file.hash {
|
||||
// No changes
|
||||
debug!(path = %path.display(), "File unchanged");
|
||||
debug!(path = %path, "File unchanged");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Write to file
|
||||
info!("Writing updated {}", path.display());
|
||||
info!("Writing updated {}", path);
|
||||
fs::write(path, &buf)?;
|
||||
} else {
|
||||
// Write directly
|
||||
|
@ -226,14 +241,11 @@ where
|
|||
}
|
||||
|
||||
#[inline]
|
||||
pub fn write_symbols_file<P>(
|
||||
path: P,
|
||||
pub fn write_symbols_file(
|
||||
path: &Utf8NativePath,
|
||||
obj: &ObjInfo,
|
||||
cached_file: Option<FileReadInfo>,
|
||||
) -> Result<()>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
) -> Result<()> {
|
||||
write_if_unchanged(path, |w| write_symbols(w, obj), cached_file)
|
||||
}
|
||||
|
||||
|
@ -401,15 +413,12 @@ fn section_kind_to_str(kind: ObjSectionKind) -> &'static str {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
pub fn write_splits_file<P>(
|
||||
path: P,
|
||||
pub fn write_splits_file(
|
||||
path: &Utf8NativePath,
|
||||
obj: &ObjInfo,
|
||||
all: bool,
|
||||
cached_file: Option<FileReadInfo>,
|
||||
) -> Result<()>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
) -> Result<()> {
|
||||
write_if_unchanged(path, |w| write_splits(w, obj, all), cached_file)
|
||||
}
|
||||
|
||||
|
@ -428,6 +437,9 @@ 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() {
|
||||
|
@ -475,6 +487,8 @@ 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 {
|
||||
|
@ -515,12 +529,13 @@ 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 };
|
||||
let mut unit = SplitUnit { name: name.to_string(), comment_version: None, order: 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 {
|
||||
|
@ -603,16 +618,15 @@ fn parse_section_line(captures: Captures, state: &SplitState) -> Result<SplitLin
|
|||
|
||||
enum SplitState {
|
||||
None,
|
||||
Sections(usize),
|
||||
Sections(SectionIndex),
|
||||
Unit(String),
|
||||
}
|
||||
|
||||
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)?;
|
||||
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)?;
|
||||
Some(cached)
|
||||
} else {
|
||||
None
|
||||
|
@ -631,12 +645,13 @@ where R: BufRead + ?Sized {
|
|||
match (&mut state, split_line) {
|
||||
(
|
||||
SplitState::None | SplitState::Unit(_) | SplitState::Sections(_),
|
||||
SplitLine::Unit(SplitUnit { name, comment_version }),
|
||||
SplitLine::Unit(SplitUnit { name, comment_version, order }),
|
||||
) => {
|
||||
obj.link_order.push(ObjUnit {
|
||||
name: name.clone(),
|
||||
autogenerated: false,
|
||||
comment_version,
|
||||
order,
|
||||
});
|
||||
state = SplitState::Unit(name);
|
||||
}
|
||||
|
@ -718,16 +733,14 @@ where R: BufRead + ?Sized {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn read_splits_sections<P>(path: P) -> Result<Option<Vec<SectionDef>>>
|
||||
where P: AsRef<Path> {
|
||||
if !path.as_ref().is_file() {
|
||||
pub fn read_splits_sections(path: &Utf8NativePath) -> Result<Option<Vec<SectionDef>>> {
|
||||
if !fs::metadata(path).is_ok_and(|m| m.is_file()) {
|
||||
return Ok(None);
|
||||
}
|
||||
let file = map_file(path)?;
|
||||
let r = file.as_reader();
|
||||
let file = open_file(path, true)?;
|
||||
let mut sections = Vec::new();
|
||||
let mut state = SplitState::None;
|
||||
for result in r.lines() {
|
||||
for result in file.lines() {
|
||||
let line = match result {
|
||||
Ok(line) => line,
|
||||
Err(e) => return Err(e.into()),
|
||||
|
|
|
@ -1,40 +1,39 @@
|
|||
use std::{
|
||||
io::Write,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
use std::io::Write;
|
||||
|
||||
use itertools::Itertools;
|
||||
use path_slash::PathBufExt;
|
||||
|
||||
use crate::util::file::split_path;
|
||||
use typed_path::{Utf8NativePath, Utf8NativePathBuf, Utf8UnixPathBuf};
|
||||
|
||||
pub struct DepFile {
|
||||
pub name: PathBuf,
|
||||
pub dependencies: Vec<PathBuf>,
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
impl DepFile {
|
||||
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 new(name: Utf8NativePathBuf) -> Self {
|
||||
Self { name: name.with_unix_encoding(), dependencies: vec![] }
|
||||
}
|
||||
|
||||
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 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 write<W>(&self, w: &mut W) -> std::io::Result<()>
|
||||
where W: Write + ?Sized {
|
||||
write!(w, "{}:", self.name.to_slash_lossy())?;
|
||||
write!(w, "{}:", self.name)?;
|
||||
for dep in self.dependencies.iter().unique() {
|
||||
write!(w, " \\\n {}", dep.to_slash_lossy().replace(' ', "\\ "))?;
|
||||
write!(w, " \\\n {}", dep.as_str().replace(' ', "\\ "))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
//! 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::{
|
||||
|
@ -28,7 +29,7 @@ pub fn process_code(
|
|||
section: &ObjSection,
|
||||
config: &DiffObjConfig,
|
||||
) -> Result<ProcessCodeResult> {
|
||||
let arch = objdiff_core::arch::ppc::ObjArchPpc {};
|
||||
let arch = objdiff_core::arch::ppc::ObjArchPpc { extab: None };
|
||||
let orig_relocs = section
|
||||
.relocations
|
||||
.range(symbol.address as u32..symbol.address as u32 + symbol.size as u32)
|
||||
|
@ -39,7 +40,7 @@ pub fn process_code(
|
|||
arch.process_code(
|
||||
symbol.address,
|
||||
orig_data,
|
||||
section.elf_index,
|
||||
section.elf_index as usize,
|
||||
&orig_relocs,
|
||||
&Default::default(),
|
||||
config,
|
||||
|
@ -224,8 +225,11 @@ fn print_line(ins_diff: &ObjInsDiff, base_addr: u64) -> Vec<Span> {
|
|||
base_color = COLOR_ROTATION[diff.idx % COLOR_ROTATION.len()]
|
||||
}
|
||||
}
|
||||
DiffText::BranchDest(addr) => {
|
||||
DiffText::BranchDest(addr, diff) => {
|
||||
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);
|
||||
|
@ -287,6 +291,7 @@ 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(),
|
||||
|
@ -297,6 +302,8 @@ fn to_objdiff_symbol(
|
|||
flags,
|
||||
addend,
|
||||
virtual_address: None,
|
||||
original_index: None,
|
||||
bytes,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ use crate::{
|
|||
array_ref,
|
||||
obj::{
|
||||
ObjArchitecture, ObjInfo, ObjKind, ObjSection, ObjSectionKind, ObjSymbol, ObjSymbolFlagSet,
|
||||
ObjSymbolFlags, ObjSymbolKind,
|
||||
ObjSymbolFlags, ObjSymbolKind, SectionIndex,
|
||||
},
|
||||
util::{
|
||||
alf::{AlfFile, AlfSymbol, ALF_MAGIC},
|
||||
|
@ -75,7 +75,7 @@ pub struct DolSection {
|
|||
pub size: u32,
|
||||
pub kind: DolSectionKind,
|
||||
// TODO remove
|
||||
pub index: usize,
|
||||
pub index: SectionIndex,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
@ -103,7 +103,7 @@ impl FromReader for DolFile {
|
|||
data_size: size,
|
||||
size,
|
||||
kind: DolSectionKind::Text,
|
||||
index: sections.len(),
|
||||
index: sections.len() as SectionIndex,
|
||||
});
|
||||
}
|
||||
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(),
|
||||
index: sections.len() as SectionIndex,
|
||||
});
|
||||
}
|
||||
sections.push(DolSection {
|
||||
|
@ -125,7 +125,7 @@ impl FromReader for DolFile {
|
|||
data_size: 0,
|
||||
size: header.bss_size,
|
||||
kind: DolSectionKind::Bss,
|
||||
index: sections.len(),
|
||||
index: sections.len() as SectionIndex,
|
||||
});
|
||||
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<usize> = None;
|
||||
let mut extabindex_section: Option<usize> = None;
|
||||
let mut extab_section: Option<SectionIndex> = None;
|
||||
let mut extabindex_section: Option<SectionIndex> = None;
|
||||
'outer: for dol_section in
|
||||
dol.sections().iter().filter(|section| section.kind == DolSectionKind::Data)
|
||||
{
|
||||
|
@ -537,8 +537,9 @@ pub fn process_dol(buf: &[u8], name: &str) -> Result<ObjInfo> {
|
|||
}
|
||||
|
||||
// Apply section indices
|
||||
let mut init_section_index = None;
|
||||
let mut init_section_index: Option<SectionIndex> = 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);
|
||||
|
|
|
@ -274,6 +274,7 @@ 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),
|
||||
|
@ -283,6 +284,7 @@ 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
|
||||
|
|
|
@ -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_NOBITS, SHT_PROGBITS},
|
||||
elf::{SHF_ALLOC, SHF_EXECINSTR, SHF_WRITE, SHT_LOUSER, SHT_NOBITS, SHT_PROGBITS},
|
||||
write::{
|
||||
elf::{ProgramHeader, Rel, SectionHeader, SectionIndex, SymbolIndex, Writer},
|
||||
StringId,
|
||||
|
@ -20,20 +20,24 @@ 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)>),
|
||||
|
@ -43,10 +47,9 @@ enum BoundaryState {
|
|||
FilesEnded,
|
||||
}
|
||||
|
||||
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())?;
|
||||
pub fn process_elf(path: &Utf8NativePath) -> Result<ObjInfo> {
|
||||
let mut file = open_file(path, true)?;
|
||||
let obj_file = object::read::File::parse(file.map()?)?;
|
||||
let architecture = match obj_file.architecture() {
|
||||
Architecture::PowerPc => ObjArchitecture::PowerPc,
|
||||
arch => bail!("Unexpected architecture: {arch:?}"),
|
||||
|
@ -68,7 +71,7 @@ where P: AsRef<Path> {
|
|||
let mut sda2_base: Option<u32> = None;
|
||||
|
||||
let mut sections: Vec<ObjSection> = vec![];
|
||||
let mut section_indexes: Vec<Option<usize>> = vec![];
|
||||
let mut section_indexes: Vec<Option<usize>> = vec![None /* ELF null section */];
|
||||
for section in obj_file.sections() {
|
||||
if section.size() == 0 {
|
||||
section_indexes.push(None);
|
||||
|
@ -94,7 +97,7 @@ where P: AsRef<Path> {
|
|||
size: section.size(),
|
||||
data: section.uncompressed_data()?.to_vec(),
|
||||
align: section.align(),
|
||||
elf_index: section.index().0,
|
||||
elf_index: section.index().0 as ObjSectionIndex,
|
||||
relocations: Default::default(),
|
||||
virtual_address: None, // Loaded from section symbol
|
||||
file_offset: section.file_range().map(|(v, _)| v).unwrap_or_default(),
|
||||
|
@ -113,6 +116,7 @@ where P: AsRef<Path> {
|
|||
.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);
|
||||
|
@ -147,7 +151,7 @@ where P: AsRef<Path> {
|
|||
};
|
||||
|
||||
let mut symbols: Vec<ObjSymbol> = vec![];
|
||||
let mut symbol_indexes: Vec<Option<usize>> = vec![];
|
||||
let mut symbol_indexes: Vec<Option<ObjSymbolIndex>> = vec![None /* ELF null symbol */];
|
||||
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());
|
||||
|
@ -235,7 +239,9 @@ where P: AsRef<Path> {
|
|||
.and_then(|m| m.virtual_addresses.as_ref())
|
||||
.and_then(|v| v.get(symbol.index().0).cloned())
|
||||
{
|
||||
sections[section_index.0].virtual_address = Some(addr);
|
||||
if let Some(section_index) = section_indexes[section_index.0] {
|
||||
sections[section_index].virtual_address = Some(addr);
|
||||
}
|
||||
}
|
||||
|
||||
let section = obj_file.section_by_index(section_index)?;
|
||||
|
@ -297,13 +303,13 @@ where P: AsRef<Path> {
|
|||
}
|
||||
|
||||
// Generate symbols
|
||||
if matches!(symbol.kind(), SymbolKind::Null | SymbolKind::File)
|
||||
if matches!(symbol.kind(), 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()));
|
||||
symbol_indexes.push(Some(symbols.len() as ObjSymbolIndex));
|
||||
let align = mw_comment.as_ref().map(|(_, vec)| vec[symbol.index().0].align);
|
||||
symbols.push(to_obj_symbol(&obj_file, &symbol, §ion_indexes, align)?);
|
||||
}
|
||||
|
@ -316,6 +322,7 @@ where P: AsRef<Path> {
|
|||
name: file_name.clone(),
|
||||
autogenerated: false,
|
||||
comment_version: None,
|
||||
order: None,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -345,8 +352,9 @@ where P: AsRef<Path> {
|
|||
// TODO rebuild common symbols
|
||||
}
|
||||
|
||||
for (section_idx, section) in obj_file.sections().enumerate() {
|
||||
let out_section = match section_indexes[section_idx].and_then(|idx| sections.get_mut(idx)) {
|
||||
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,
|
||||
};
|
||||
|
@ -396,7 +404,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());
|
||||
let mut out_sections: Vec<OutSection> = Vec::with_capacity(obj.sections.len() as usize);
|
||||
for (_, section) in obj.sections.iter() {
|
||||
let name = writer.add_section_name(section.name.as_bytes());
|
||||
let index = writer.reserve_section_index();
|
||||
|
@ -411,7 +419,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()];
|
||||
let mut rela_names: Vec<String> = vec![Default::default(); obj.sections.len() as usize];
|
||||
for (((_, section), out_section), rela_name) in
|
||||
obj.sections.iter().zip(&mut out_sections).zip(&mut rela_names)
|
||||
{
|
||||
|
@ -444,7 +452,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() * 8);
|
||||
let mut comment_data = Vec::<u8>::with_capacity(0x2C + obj.symbols.count() as usize * 8);
|
||||
mw_comment.to_writer_static(&mut comment_data, Endian::Big)?;
|
||||
// Null symbol
|
||||
CommentSym { align: 0, vis_flags: 0, active_flags: 0 }
|
||||
|
@ -455,7 +463,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) = &obj.split_meta {
|
||||
let mut split_meta = if let (Some(metadata), Some(_)) = (&obj.split_meta, &obj.mw_comment) {
|
||||
// Reserve section
|
||||
let name = writer.add_section_name(SPLITMETA_SECTION.as_bytes());
|
||||
let index = writer.reserve_section_index();
|
||||
|
@ -480,8 +488,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());
|
||||
let mut symbol_map = vec![None; obj.symbols.count()];
|
||||
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 section_symbol_offset = 0;
|
||||
let mut num_local = 0;
|
||||
|
||||
|
@ -527,7 +535,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).map(|s| s.index);
|
||||
let out_section_index = out_sections.get(section_index as usize).map(|s| s.index);
|
||||
let index = writer.reserve_symbol_index(out_section_index);
|
||||
let sym = object::write::elf::Sym {
|
||||
name: None,
|
||||
|
@ -556,19 +564,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().enumerate().filter(|&(_, s)| !s.flags.is_local()))
|
||||
.chain(obj.symbols.iter().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] = Some(section_symbol_offset + section_index as u32);
|
||||
symbol_map[symbol_index as usize] =
|
||||
Some(section_symbol_offset as ObjSectionIndex + section_index);
|
||||
continue;
|
||||
}
|
||||
|
||||
let section = symbol.section.and_then(|idx| out_sections.get(idx));
|
||||
let section = symbol.section.and_then(|idx| out_sections.get(idx as usize));
|
||||
let section_index = section.map(|s| s.index);
|
||||
let index = writer.reserve_symbol_index(section_index);
|
||||
let name_index = if symbol.name.is_empty() {
|
||||
|
@ -612,7 +620,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] = Some(index.0);
|
||||
symbol_map[symbol_index as usize] = Some(index.0);
|
||||
if let Some((comment_data, _)) = &mut comment_data {
|
||||
CommentSym::from(symbol, export_all).to_writer_static(comment_data, Endian::Big)?;
|
||||
}
|
||||
|
@ -630,7 +638,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() as u32);
|
||||
writer.reserve_program_headers(obj.sections.len());
|
||||
}
|
||||
|
||||
for ((_, section), out_section) in obj.sections.iter().zip(&mut out_sections) {
|
||||
|
@ -729,7 +737,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]
|
||||
let r_sym = symbol_map[reloc.target_symbol as usize]
|
||||
.ok_or_else(|| anyhow!("Relocation against stripped symbol"))?;
|
||||
writer.write_relocation(true, &Rel { r_offset, r_sym, r_type, r_addend: reloc.addend });
|
||||
}
|
||||
|
@ -824,9 +832,16 @@ 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: SHT_SPLITMETA,
|
||||
sh_type,
|
||||
sh_flags: 0,
|
||||
sh_addr: 0,
|
||||
sh_offset: out_section.offset as u64,
|
||||
|
@ -881,7 +896,7 @@ fn to_obj_symbol(
|
|||
name: name.to_string(),
|
||||
demangled_name: demangle(name, &Default::default()),
|
||||
address: symbol.address(),
|
||||
section: section_idx,
|
||||
section: section_idx.map(|s| s as ObjSectionIndex),
|
||||
size: symbol.size(),
|
||||
size_known: true,
|
||||
flags,
|
||||
|
@ -915,7 +930,7 @@ pub fn to_obj_reloc_kind(flags: RelocationFlags) -> Result<ObjRelocKind> {
|
|||
|
||||
fn to_obj_reloc(
|
||||
obj_file: &object::File<'_>,
|
||||
symbol_indexes: &[Option<usize>],
|
||||
symbol_indexes: &[Option<ObjSymbolIndex>],
|
||||
section_data: &[u8],
|
||||
address: u64,
|
||||
reloc: Relocation,
|
||||
|
|
488
src/util/file.rs
488
src/util/file.rs
|
@ -1,234 +1,48 @@
|
|||
use std::{
|
||||
ffi::OsStr,
|
||||
fs,
|
||||
fs::{DirBuilder, File, OpenOptions},
|
||||
io::{BufRead, BufReader, BufWriter, Cursor, Read, Seek, SeekFrom},
|
||||
path::{Component, Path, PathBuf},
|
||||
io,
|
||||
io::{BufRead, BufWriter, Read, Seek, SeekFrom, Write},
|
||||
};
|
||||
|
||||
use anyhow::{anyhow, bail, Context, Result};
|
||||
use anyhow::{anyhow, 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},
|
||||
rarc,
|
||||
rarc::{Node, RARC_MAGIC},
|
||||
take_seek::{TakeSeek, TakeSeekExt},
|
||||
u8_arc::{U8View, U8_MAGIC},
|
||||
path::check_path_buf,
|
||||
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<P>(path: P) -> Result<BufWriter<File>>
|
||||
where P: AsRef<Path> {
|
||||
if let Some(parent) = path.as_ref().parent() {
|
||||
pub fn buf_writer(path: &Utf8NativePath) -> Result<BufWriter<File>> {
|
||||
if let Some(parent) = path.parent() {
|
||||
DirBuilder::new().recursive(true).create(parent)?;
|
||||
}
|
||||
let file = File::create(&path)
|
||||
.with_context(|| format!("Failed to create file '{}'", path.as_ref().display()))?;
|
||||
let file = File::create(path).with_context(|| format!("Failed to create file '{}'", path))?;
|
||||
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) -> Result<String>
|
||||
pub fn read_string<R>(reader: &mut R, off: u64, size: usize) -> io::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))?;
|
||||
Ok(String::from_utf8(data)?)
|
||||
String::from_utf8(data).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
|
||||
}
|
||||
|
||||
/// Reads a zero-terminated string at the specified offset.
|
||||
pub fn read_c_string<R>(reader: &mut R, off: u64) -> Result<String>
|
||||
pub fn read_c_string<R>(reader: &mut R, off: u64) -> io::Result<String>
|
||||
where R: Read + Seek + ?Sized {
|
||||
let pos = reader.stream_position()?;
|
||||
reader.seek(SeekFrom::Start(off))?;
|
||||
|
@ -246,22 +60,21 @@ where R: Read + Seek + ?Sized {
|
|||
}
|
||||
|
||||
/// Process response files (starting with '@') and glob patterns (*).
|
||||
pub fn process_rsp(files: &[PathBuf]) -> Result<Vec<PathBuf>> {
|
||||
let mut out = Vec::with_capacity(files.len());
|
||||
pub fn process_rsp(files: &[Utf8NativePathBuf]) -> Result<Vec<Utf8NativePathBuf>> {
|
||||
let mut out = Vec::<Utf8NativePathBuf>::with_capacity(files.len());
|
||||
for path in files {
|
||||
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() {
|
||||
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 line = result?;
|
||||
if !line.is_empty() {
|
||||
out.push(PathBuf::from_slash(line));
|
||||
out.push(Utf8UnixPathBuf::from(line).with_encoding());
|
||||
}
|
||||
}
|
||||
} else if path_str.contains('*') {
|
||||
for entry in glob::glob(path_str)? {
|
||||
out.push(entry?);
|
||||
} 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 {
|
||||
out.push(path.clone());
|
||||
|
@ -270,120 +83,20 @@ pub fn process_rsp(files: &[PathBuf]) -> Result<Vec<PathBuf>> {
|
|||
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(¤t_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: FileTime,
|
||||
pub mtime: Option<FileTime>,
|
||||
pub hash: u64,
|
||||
}
|
||||
|
||||
impl FileReadInfo {
|
||||
pub fn new(entry: &FileEntry) -> Result<Self> {
|
||||
let hash = xxh3_64(entry.as_slice());
|
||||
Ok(Self { mtime: entry.mtime(), hash })
|
||||
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 })
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -391,108 +104,37 @@ 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<PathBuf>,
|
||||
paths: Vec<Utf8NativePathBuf>,
|
||||
index: usize,
|
||||
rarc: Option<RarcIterator>,
|
||||
}
|
||||
|
||||
impl FileIterator {
|
||||
pub fn new(paths: &[PathBuf]) -> Result<Self> {
|
||||
Ok(Self { paths: process_rsp(paths)?, index: 0, rarc: None })
|
||||
pub fn new(paths: &[Utf8NativePathBuf]) -> Result<Self> {
|
||||
Ok(Self { paths: process_rsp(paths)?, index: 0 })
|
||||
}
|
||||
|
||||
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)>> {
|
||||
fn next_path(&mut self) -> Option<Result<(Utf8NativePathBuf, Box<dyn VfsFile>)>> {
|
||||
if self.index >= self.paths.len() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let path = self.paths[self.index].clone();
|
||||
self.index += 1;
|
||||
match map_file(&path) {
|
||||
Ok(FileEntry::MappedFile(map)) => self.handle_file(map, path),
|
||||
Ok(FileEntry::Buffer(_, _)) => todo!(),
|
||||
Err(err) => Some(Err(err)),
|
||||
match open_file(&path, true) {
|
||||
Ok(file) => Some(Ok((path, file))),
|
||||
Err(e) => Some(Err(e)),
|
||||
}
|
||||
}
|
||||
|
||||
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<(PathBuf, FileEntry)>;
|
||||
type Item = Result<(Utf8NativePathBuf, Box<dyn VfsFile>)>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> { self.next_rarc().or_else(|| self.next_path()) }
|
||||
fn next(&mut self) -> Option<Self::Item> { self.next_path() }
|
||||
}
|
||||
|
||||
pub fn touch<P>(path: P) -> std::io::Result<()>
|
||||
where P: AsRef<Path> {
|
||||
if path.as_ref().exists() {
|
||||
pub fn touch(path: &Utf8NativePath) -> io::Result<()> {
|
||||
if fs::exists(path)? {
|
||||
set_file_mtime(path, FileTime::now())
|
||||
} else {
|
||||
match OpenOptions::new().create(true).truncate(true).write(true).open(path) {
|
||||
|
@ -514,13 +156,16 @@ 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}'"))?;
|
||||
let mut hasher = Sha1::new();
|
||||
hasher.update(buf);
|
||||
let hash_bytes = hasher.finalize();
|
||||
if hash_bytes.as_ref() == expected_bytes {
|
||||
if hash_bytes == expected_bytes {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(anyhow!(
|
||||
|
@ -530,3 +175,44 @@ pub fn verify_hash(buf: &[u8], 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())
|
||||
}
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::Result;
|
||||
use itertools::Itertools;
|
||||
use path_slash::PathBufExt;
|
||||
use typed_path::{Utf8NativePathBuf, Utf8UnixPath};
|
||||
|
||||
use crate::obj::{ObjInfo, ObjKind};
|
||||
|
||||
|
@ -33,11 +31,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_str().unwrap().to_string());
|
||||
force_files.push(obj_path.file_name().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());
|
||||
}
|
||||
|
@ -82,11 +80,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_str().unwrap().to_string());
|
||||
force_files.push(obj_path.file_name().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());
|
||||
}
|
||||
|
@ -99,6 +97,10 @@ pub fn generate_ldscript_partial(
|
|||
Ok(out)
|
||||
}
|
||||
|
||||
pub fn obj_path_for_unit(unit: &str) -> PathBuf { PathBuf::from_slash(unit).with_extension("o") }
|
||||
pub fn obj_path_for_unit(unit: &str) -> Utf8NativePathBuf {
|
||||
Utf8UnixPath::new(unit).with_encoding().with_extension("o")
|
||||
}
|
||||
|
||||
pub fn asm_path_for_unit(unit: &str) -> PathBuf { PathBuf::from_slash(unit).with_extension("s") }
|
||||
pub fn asm_path_for_unit(unit: &str) -> Utf8NativePathBuf {
|
||||
Utf8UnixPath::new(unit).with_encoding().with_extension("s")
|
||||
}
|
||||
|
|
255
src/util/map.rs
255
src/util/map.rs
|
@ -5,23 +5,26 @@ 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::{
|
||||
ObjInfo, ObjKind, ObjSplit, ObjSymbol, ObjSymbolFlagSet, ObjSymbolFlags, ObjSymbolKind,
|
||||
ObjUnit,
|
||||
section_kind_for_section, ObjArchitecture, ObjInfo, ObjKind, ObjSection, ObjSectionKind,
|
||||
ObjSections, ObjSplit, ObjSymbol, ObjSymbolFlagSet, ObjSymbolFlags, ObjSymbolKind,
|
||||
ObjSymbols, ObjUnit, SectionIndex,
|
||||
},
|
||||
util::{file::map_file, nested::NestedVec},
|
||||
util::nested::NestedVec,
|
||||
vfs::open_file,
|
||||
};
|
||||
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
|
@ -127,7 +130,7 @@ pub struct MapInfo {
|
|||
pub unit_references: MultiMap<SymbolRef, String>,
|
||||
pub sections: Vec<SectionInfo>,
|
||||
pub link_map_symbols: HashMap<SymbolRef, SymbolEntry>,
|
||||
pub section_symbols: HashMap<String, BTreeMap<u32, Vec<SymbolEntry>>>,
|
||||
pub section_symbols: IndexMap<String, BTreeMap<u32, Vec<SymbolEntry>>>,
|
||||
pub section_units: HashMap<String, Vec<(u32, String)>>,
|
||||
// For common BSS inflation correction
|
||||
pub common_bss_start: Option<u32>,
|
||||
|
@ -711,26 +714,61 @@ where
|
|||
Ok(sm.result)
|
||||
}
|
||||
|
||||
pub fn apply_map_file<P>(
|
||||
path: P,
|
||||
pub fn apply_map_file(
|
||||
path: &Utf8NativePath,
|
||||
obj: &mut ObjInfo,
|
||||
common_bss_start: Option<u32>,
|
||||
mw_comment_version: Option<u8>,
|
||||
) -> 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)
|
||||
) -> 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)
|
||||
}
|
||||
|
||||
pub fn apply_map(result: &MapInfo, obj: &mut ObjInfo) -> Result<()> {
|
||||
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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for (section_index, section) in obj.sections.iter_mut() {
|
||||
let opt = if obj.kind == ObjKind::Executable {
|
||||
result.sections.iter().find(|s| s.address == section.address as u32)
|
||||
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
|
||||
})
|
||||
} else {
|
||||
result.sections.iter().filter(|s| s.size > 0).nth(section_index)
|
||||
result.sections.iter().filter(|s| s.size > 0).nth(section_index as usize)
|
||||
};
|
||||
if let Some(info) = opt {
|
||||
if section.section_known && section.name != info.name {
|
||||
|
@ -753,8 +791,171 @@ pub fn apply_map(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 absolute symbols
|
||||
// TODO
|
||||
// for symbol_entry in result.link_map_symbols.values().filter(|s| s.unit.is_none()) {
|
||||
// add_symbol(obj, symbol_entry, None)?;
|
||||
// }
|
||||
|
||||
// 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()
|
||||
.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(())
|
||||
}
|
||||
|
||||
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 {
|
||||
|
@ -763,16 +964,10 @@ pub fn apply_map(result: &MapInfo, obj: &mut ObjInfo) -> Result<()> {
|
|||
.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))?;
|
||||
add_symbol(&mut obj, symbol_entry, Some(section_index), bogus_alignment)?;
|
||||
}
|
||||
}
|
||||
|
||||
// Add absolute symbols
|
||||
// TODO
|
||||
// for symbol_entry in result.link_map_symbols.values().filter(|s| s.unit.is_none()) {
|
||||
// add_symbol(obj, symbol_entry, None)?;
|
||||
// }
|
||||
|
||||
// Add splits
|
||||
for (section_name, unit_order) in &result.section_units {
|
||||
let (_, section) = obj
|
||||
|
@ -796,6 +991,7 @@ pub fn apply_map(result: &MapInfo, obj: &mut ObjInfo) -> Result<()> {
|
|||
name: unit.clone(),
|
||||
autogenerated: false,
|
||||
comment_version: Some(0),
|
||||
order: None,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -810,10 +1006,15 @@ pub fn apply_map(result: &MapInfo, obj: &mut ObjInfo) -> Result<()> {
|
|||
});
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
Ok(obj)
|
||||
}
|
||||
|
||||
fn add_symbol(obj: &mut ObjInfo, symbol_entry: &SymbolEntry, section: Option<usize>) -> Result<()> {
|
||||
fn add_symbol(
|
||||
obj: &mut ObjInfo,
|
||||
symbol_entry: &SymbolEntry,
|
||||
section: Option<SectionIndex>,
|
||||
ignore_alignment: bool,
|
||||
) -> Result<()> {
|
||||
let demangled_name = demangle(&symbol_entry.name, &DemangleOptions::default());
|
||||
let mut flags: FlagSet<ObjSymbolFlags> = match symbol_entry.visibility {
|
||||
SymbolVisibility::Unknown => Default::default(),
|
||||
|
@ -843,7 +1044,7 @@ fn add_symbol(obj: &mut ObjInfo, symbol_entry: &SymbolEntry, section: Option<usi
|
|||
SymbolKind::Section => ObjSymbolKind::Section,
|
||||
SymbolKind::NoType => ObjSymbolKind::Unknown,
|
||||
},
|
||||
align: symbol_entry.align,
|
||||
align: if ignore_alignment { None } else { symbol_entry.align },
|
||||
..Default::default()
|
||||
},
|
||||
true,
|
||||
|
|
|
@ -15,6 +15,8 @@ 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;
|
||||
|
|
|
@ -0,0 +1,305 @@
|
|||
// 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
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
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(),
|
||||
))
|
||||
}
|
642
src/util/rarc.rs
642
src/util/rarc.rs
|
@ -1,425 +1,283 @@
|
|||
// 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 std::{borrow::Cow, ffi::CStr};
|
||||
|
||||
use anyhow::{anyhow, bail, ensure, Result};
|
||||
use typed_path::Utf8UnixPath;
|
||||
use zerocopy::{big_endian::*, FromBytes, Immutable, IntoBytes, KnownLayout};
|
||||
|
||||
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,
|
||||
}
|
||||
use crate::{static_assert, vfs::next_non_empty};
|
||||
|
||||
pub const RARC_MAGIC: [u8; 4] = *b"RARC";
|
||||
|
||||
struct RarcHeader {
|
||||
#[derive(Copy, Clone, Debug, PartialEq, FromBytes, IntoBytes, Immutable, KnownLayout)]
|
||||
#[repr(C, align(4))]
|
||||
pub struct RarcHeader {
|
||||
/// Magic identifier. (Always "RARC")
|
||||
magic: [u8; 4],
|
||||
_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,
|
||||
/// 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,
|
||||
}
|
||||
|
||||
impl FromReader for RarcHeader {
|
||||
type Args = ();
|
||||
static_assert!(size_of::<RarcHeader>() == 0x20);
|
||||
|
||||
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
|
||||
]);
|
||||
impl RarcHeader {
|
||||
/// Length of the RARC file.
|
||||
pub fn file_len(&self) -> u32 { self.file_len.get() }
|
||||
|
||||
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)?,
|
||||
/// 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");
|
||||
};
|
||||
if header.magic != RARC_MAGIC {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
format!("invalid RARC magic: {:?}", header.magic),
|
||||
));
|
||||
return Err("RARC magic mismatch");
|
||||
}
|
||||
if header.node_count >= 0x10000 {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
format!("invalid node count: {}", header.node_count),
|
||||
));
|
||||
if header.header_len.get() as usize != size_of::<RarcHeader>() {
|
||||
return Err("RARC header size mismatch");
|
||||
}
|
||||
if header.directory_count >= 0x10000 {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
format!("invalid directory count: {}", header.directory_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");
|
||||
}
|
||||
Ok(header)
|
||||
|
||||
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 })
|
||||
}
|
||||
}
|
||||
|
||||
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 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 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)?,
|
||||
})
|
||||
/// 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")
|
||||
}
|
||||
}
|
||||
|
||||
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)?;
|
||||
/// 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);
|
||||
|
||||
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 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);
|
||||
}
|
||||
}
|
||||
if current.is_empty() {
|
||||
return Some(RarcNodeKind::Directory(dir_idx, dir));
|
||||
}
|
||||
|
||||
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);
|
||||
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));
|
||||
} else {
|
||||
directories.push(RarcDirectory::Folder {
|
||||
name: NamedHash { name, hash: node.name_hash },
|
||||
});
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
directories.push(RarcDirectory::File {
|
||||
name: NamedHash { name, hash: node.name_hash },
|
||||
offset: data_base + node.data_offset as u64,
|
||||
size: node.data_length,
|
||||
});
|
||||
return Some(RarcNodeKind::File(idx, node));
|
||||
}
|
||||
}
|
||||
idx += 1;
|
||||
}
|
||||
|
||||
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 });
|
||||
}
|
||||
|
||||
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 {
|
||||
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 {
|
||||
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),
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ use crate::{
|
|||
array_ref_mut,
|
||||
obj::{
|
||||
ObjArchitecture, ObjInfo, ObjKind, ObjRelocKind, ObjSection, ObjSectionKind, ObjSymbol,
|
||||
ObjSymbolFlagSet, ObjSymbolFlags, ObjSymbolKind,
|
||||
ObjSymbolFlagSet, ObjSymbolFlags, ObjSymbolKind, SectionIndex,
|
||||
},
|
||||
util::{
|
||||
align_up,
|
||||
|
@ -418,7 +418,7 @@ where R: Read + Seek + ?Sized {
|
|||
_ => None, // determined later
|
||||
}
|
||||
.unwrap_or_default() as u64,
|
||||
elf_index: idx,
|
||||
elf_index: idx as SectionIndex,
|
||||
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 usize)
|
||||
.find(|&(_, section)| section.elf_index == rel_section_idx as SectionIndex)
|
||||
.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),
|
||||
section: Some(section_index as SectionIndex),
|
||||
flags,
|
||||
kind: ObjSymbolKind::Function,
|
||||
..Default::default()
|
||||
|
@ -853,8 +853,10 @@ where
|
|||
offset = (offset + align) & !align;
|
||||
offset += section.size() as u32;
|
||||
}
|
||||
if info.version >= 3 {
|
||||
// Align to 4 after section data
|
||||
offset = (offset + 3) & !3;
|
||||
}
|
||||
|
||||
fn do_relocation_layout(
|
||||
relocations: &[RelReloc],
|
||||
|
@ -1047,8 +1049,8 @@ where
|
|||
}
|
||||
w.write_all(§ion_data)?;
|
||||
}
|
||||
if info.version >= 3 {
|
||||
// Align to 4 after section data
|
||||
{
|
||||
let position = w.stream_position()?;
|
||||
w.write_all(&vec![0u8; calculate_padding(position, 4) as usize])?;
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ use cwdemangle::{demangle, DemangleOptions};
|
|||
use crate::{
|
||||
obj::{
|
||||
ObjArchitecture, ObjInfo, ObjKind, ObjSection, ObjSectionKind, ObjSymbol, ObjSymbolFlagSet,
|
||||
ObjSymbolFlags, ObjSymbolKind,
|
||||
ObjSymbolFlags, ObjSymbolKind, SectionIndex,
|
||||
},
|
||||
util::{
|
||||
file::{read_c_string, read_string},
|
||||
|
@ -34,11 +34,16 @@ 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,
|
||||
|
@ -101,6 +106,10 @@ 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 = ();
|
||||
|
||||
|
@ -205,13 +214,46 @@ impl FromReader for RsoHeader {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
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)]
|
||||
pub struct RsoSectionHeader {
|
||||
/// Absolute offset of the section.
|
||||
/// The lowest bit is set if the section is executable.
|
||||
offset_and_flags: u32,
|
||||
pub offset_and_flags: u32,
|
||||
/// Size of the section.
|
||||
size: u32,
|
||||
pub size: u32,
|
||||
}
|
||||
|
||||
impl FromReader for RsoSectionHeader {
|
||||
|
@ -255,17 +297,17 @@ impl RsoSectionHeader {
|
|||
pub fn exec(&self) -> bool { self.offset_and_flags & 1 != 0 }
|
||||
}
|
||||
|
||||
struct RsoRelocation {
|
||||
pub struct RsoRelocation {
|
||||
/// Absolute offset of this relocation (relative to the start of the RSO file).
|
||||
offset: u32,
|
||||
pub 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.
|
||||
id_and_type: u32,
|
||||
pub 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).
|
||||
target_offset: u32,
|
||||
pub target_offset: u32,
|
||||
}
|
||||
|
||||
impl FromReader for RsoRelocation {
|
||||
|
@ -315,22 +357,23 @@ impl RsoRelocation {
|
|||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
enum RsoSymbolKind {
|
||||
pub enum RsoSymbolKind {
|
||||
Import,
|
||||
Export,
|
||||
}
|
||||
|
||||
struct RsoSymbol {
|
||||
#[derive(Debug)]
|
||||
pub struct RsoSymbol {
|
||||
/// Relative offset into the name table pointed to in the header,
|
||||
/// which points to the name of this symbol.
|
||||
name_offset: u32,
|
||||
pub name_offset: u32,
|
||||
/// The section-relative offset to the symbol. This is always 0 for imports.
|
||||
offset: u32,
|
||||
pub offset: u32,
|
||||
/// For exports, index of the section that contains this symbol.
|
||||
/// For imports, appears to be an offset?
|
||||
section_index: u32,
|
||||
/// For imports, offset of the first relocation that use this symbol
|
||||
pub section_index: u32,
|
||||
/// A hash of the symbol name. Only present for exports.
|
||||
hash: Option<u32>,
|
||||
pub hash: Option<u32>,
|
||||
}
|
||||
|
||||
impl FromReader for RsoSymbol {
|
||||
|
@ -360,7 +403,8 @@ impl ToWriter for RsoSymbol {
|
|||
self.offset.to_writer(writer, e)?;
|
||||
self.section_index.to_writer(writer, e)?;
|
||||
if let Some(hash) = self.hash {
|
||||
hash.to_writer(writer, e)?;
|
||||
// Since the nature of the value is not numeric, we must preserve the order of the bytes
|
||||
writer.write_all(&hash.to_ne_bytes())?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -408,7 +452,7 @@ where R: Read + Seek + ?Sized {
|
|||
size: size as u64,
|
||||
data,
|
||||
align: 0,
|
||||
elf_index: idx as usize,
|
||||
elf_index: idx as SectionIndex,
|
||||
relocations: Default::default(),
|
||||
virtual_address: None, // TODO option to set?
|
||||
file_offset: offset as u64,
|
||||
|
@ -432,13 +476,13 @@ where R: Read + Seek + ?Sized {
|
|||
let (section_index, _) = sections
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|&(_, section)| section.elf_index == rel_section_idx as usize)
|
||||
.find(|&(_, section)| section.elf_index == rel_section_idx as SectionIndex)
|
||||
.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),
|
||||
section: Some(section_index as SectionIndex),
|
||||
flags: ObjSymbolFlagSet(ObjSymbolFlags::Global.into()),
|
||||
kind: ObjSymbolKind::Function,
|
||||
..Default::default()
|
||||
|
@ -482,7 +526,7 @@ where R: Read + Seek + ?Sized {
|
|||
let section = sections
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|&(_, section)| section.elf_index == symbol.section_index as usize)
|
||||
.find(|&(_, section)| section.elf_index == symbol.section_index as SectionIndex)
|
||||
.map(|(idx, _)| idx)
|
||||
// HACK: selfiles won't have any sections
|
||||
.unwrap_or(symbol.section_index as usize);
|
||||
|
@ -497,7 +541,7 @@ where R: Read + Seek + ?Sized {
|
|||
name,
|
||||
demangled_name,
|
||||
address: symbol.offset as u64,
|
||||
section: Some(section),
|
||||
section: Some(section as SectionIndex),
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
|
@ -524,7 +568,7 @@ where R: Read + Seek + ?Sized {
|
|||
Ok(obj)
|
||||
}
|
||||
|
||||
fn symbol_hash(s: &str) -> u32 {
|
||||
pub 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;
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
use std::{
|
||||
collections::{btree_map, BTreeMap},
|
||||
path::Path,
|
||||
};
|
||||
use std::collections::{btree_map, BTreeMap};
|
||||
|
||||
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::{
|
||||
|
@ -18,7 +16,7 @@ use crate::{
|
|||
array_ref,
|
||||
obj::{
|
||||
ObjInfo, ObjKind, ObjReloc, ObjRelocKind, ObjSection, ObjSymbol, ObjSymbolFlagSet,
|
||||
ObjSymbolKind,
|
||||
ObjSymbolKind, SectionIndex, SymbolIndex,
|
||||
},
|
||||
util::elf::process_elf,
|
||||
};
|
||||
|
@ -36,13 +34,13 @@ pub struct OutSymbol {
|
|||
pub struct OutReloc {
|
||||
pub offset: u32,
|
||||
pub kind: ObjRelocKind,
|
||||
pub symbol: usize,
|
||||
pub symbol: u32,
|
||||
pub addend: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Hash, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub struct FunctionSignature {
|
||||
pub symbol: usize,
|
||||
pub symbol: u32,
|
||||
pub hash: String,
|
||||
pub signature: String,
|
||||
pub symbols: Vec<OutSymbol>,
|
||||
|
@ -92,12 +90,12 @@ pub fn check_signatures(
|
|||
let mut name = None;
|
||||
for signature in signatures {
|
||||
if name.is_none() {
|
||||
name = Some(signature.symbols[signature.symbol].name.clone());
|
||||
name = Some(signature.symbols[signature.symbol as usize].name.clone());
|
||||
}
|
||||
if check_signature(data, signature)? {
|
||||
log::debug!(
|
||||
"Found {} @ {:#010X} (hash {})",
|
||||
signature.symbols[signature.symbol].name,
|
||||
signature.symbols[signature.symbol as usize].name,
|
||||
addr,
|
||||
signature.hash
|
||||
);
|
||||
|
@ -114,9 +112,9 @@ pub fn apply_symbol(
|
|||
obj: &mut ObjInfo,
|
||||
target: SectionAddress,
|
||||
sig_symbol: &OutSymbol,
|
||||
) -> Result<usize> {
|
||||
) -> Result<SymbolIndex> {
|
||||
let mut target_section_index =
|
||||
if target.section == usize::MAX { None } else { Some(target.section) };
|
||||
if target.section == SectionIndex::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 {
|
||||
|
@ -154,7 +152,7 @@ pub fn apply_signature(
|
|||
addr: SectionAddress,
|
||||
signature: &FunctionSignature,
|
||||
) -> Result<()> {
|
||||
let in_symbol = &signature.symbols[signature.symbol];
|
||||
let in_symbol = &signature.symbols[signature.symbol as usize];
|
||||
let symbol_idx = apply_symbol(obj, addr, in_symbol)?;
|
||||
let mut tracker = Tracker::new(obj);
|
||||
for reloc in &signature.relocations {
|
||||
|
@ -185,7 +183,7 @@ pub fn apply_signature(
|
|||
}
|
||||
_ => bail!("Relocation mismatch: {:?} != {:?}", reloc, sig_reloc.kind),
|
||||
};
|
||||
let sig_symbol = &signature.symbols[sig_reloc.symbol];
|
||||
let sig_symbol = &signature.symbols[sig_reloc.symbol as usize];
|
||||
// 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 {
|
||||
|
@ -200,7 +198,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];
|
||||
let sig_symbol = &signature.symbols[reloc.symbol as usize];
|
||||
bail!("Missing relocation @ {:#010X}: {:?} -> {:?}", addr, reloc, sig_symbol);
|
||||
}
|
||||
}
|
||||
|
@ -246,11 +244,13 @@ pub fn compare_signature(existing: &mut FunctionSignature, new: &FunctionSignatu
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn generate_signature<P>(path: P, symbol_name: &str) -> Result<Option<FunctionSignature>>
|
||||
where P: AsRef<Path> {
|
||||
pub fn generate_signature(
|
||||
path: &Utf8NativePath,
|
||||
symbol_name: &str,
|
||||
) -> Result<Option<FunctionSignature>> {
|
||||
let mut out_symbols: Vec<OutSymbol> = Vec::new();
|
||||
let mut out_relocs: Vec<OutReloc> = Vec::new();
|
||||
let mut symbol_map: BTreeMap<usize, usize> = BTreeMap::new();
|
||||
let mut symbol_map: BTreeMap<SymbolIndex, u32> = BTreeMap::new();
|
||||
|
||||
let mut obj = process_elf(path)?;
|
||||
if obj.kind == ObjKind::Executable
|
||||
|
@ -312,7 +312,7 @@ where P: AsRef<Path> {
|
|||
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();
|
||||
let symbol_idx = out_symbols.len() as u32;
|
||||
e.insert(symbol_idx);
|
||||
out_symbols.push(OutSymbol {
|
||||
kind: target.kind,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use std::{
|
||||
cmp::{max, min, Ordering},
|
||||
collections::{BTreeMap, HashMap, HashSet},
|
||||
collections::{btree_map, 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,
|
||||
ObjUnit, SectionIndex, SymbolIndex,
|
||||
},
|
||||
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((bss_section_index, common_bss_start)) = (match common_start {
|
||||
Some(addr) => Some((
|
||||
let Some(common_bss_start) = (match common_start {
|
||||
Some(addr) => Some(SectionAddress::new(
|
||||
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[bss_section_index];
|
||||
for (addr, split) in bss_section.splits.for_range_mut(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..) {
|
||||
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((idx, addr)) if
|
||||
section_index == idx && symbol.address as u32 >= addr)
|
||||
if matches!(common_bss, Some(addr) if
|
||||
section_index == addr.section && symbol.address as u32 >= addr.address)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
@ -758,19 +758,24 @@ 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)?;
|
||||
}
|
||||
}
|
||||
|
||||
// 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)?;
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
|
@ -779,6 +784,7 @@ pub fn update_splits(obj: &mut ObjInfo, common_start: Option<u32>, fill_gaps: bo
|
|||
}
|
||||
split_ctors_dtors(obj, start, end)?;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove linker generated symbols from splits
|
||||
trim_linker_generated_symbols(obj)?;
|
||||
|
@ -816,8 +822,8 @@ fn resolve_link_order(obj: &ObjInfo) -> Result<Vec<ObjUnit>> {
|
|||
#[allow(dead_code)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
struct SplitEdge {
|
||||
from: u32,
|
||||
to: u32,
|
||||
from: i64,
|
||||
to: i64,
|
||||
}
|
||||
|
||||
let mut graph = Graph::<String, SplitEdge>::new();
|
||||
|
@ -852,11 +858,36 @@ 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, to: b_addr });
|
||||
graph.add_edge(a_index, b_index, SplitEdge {
|
||||
from: a_addr as i64,
|
||||
to: b_addr as i64,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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,
|
||||
|
@ -886,6 +917,7 @@ fn resolve_link_order(obj: &ObjInfo) -> Result<Vec<ObjUnit>> {
|
|||
name: name.clone(),
|
||||
autogenerated: obj.is_unit_autogenerated(name),
|
||||
comment_version: None,
|
||||
order: None,
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -901,11 +933,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<usize>>> = vec![];
|
||||
let mut object_symbols: Vec<Vec<Option<SymbolIndex>>> = 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()]);
|
||||
object_symbols.push(vec![None; obj.symbols.count() as usize]);
|
||||
let mut split_obj = ObjInfo::new(
|
||||
ObjKind::Relocatable,
|
||||
ObjArchitecture::PowerPc,
|
||||
|
@ -1047,7 +1079,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].is_some() {
|
||||
if symbol_idxs[symbol_idx as usize].is_some() {
|
||||
continue; // should never happen?
|
||||
}
|
||||
|
||||
|
@ -1060,7 +1092,7 @@ pub fn split_obj(obj: &ObjInfo, module_name: Option<&str>) -> Result<Vec<ObjInfo
|
|||
continue;
|
||||
}
|
||||
|
||||
symbol_idxs[symbol_idx] = Some(split_obj.symbols.add_direct(ObjSymbol {
|
||||
let new_index = split_obj.symbols.add_direct(ObjSymbol {
|
||||
name: symbol.name.clone(),
|
||||
demangled_name: symbol.demangled_name.clone(),
|
||||
address: if split.common {
|
||||
|
@ -1081,7 +1113,8 @@ 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
|
||||
|
@ -1124,7 +1157,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] {
|
||||
match symbol_idxs[reloc.target_symbol as usize] {
|
||||
Some(out_sym_idx) => {
|
||||
reloc.target_symbol = out_sym_idx;
|
||||
}
|
||||
|
@ -1157,7 +1190,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] = Some(out_sym_idx);
|
||||
symbol_idxs[reloc.target_symbol as usize] = Some(out_sym_idx);
|
||||
out_obj.symbols.add_direct(ObjSymbol {
|
||||
name: target_sym.name.clone(),
|
||||
demangled_name: target_sym.demangled_name.clone(),
|
||||
|
@ -1201,7 +1234,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] {
|
||||
if let Some(symbol_idx) = symbol_map[*globalize_idx as usize] {
|
||||
let mut symbol = obj.symbols[symbol_idx].clone();
|
||||
symbol.name.clone_from(new_name);
|
||||
if symbol.flags.is_local() {
|
||||
|
@ -1216,7 +1249,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().enumerate() {
|
||||
for (symbol_idx, symbol) in obj.symbols.iter() {
|
||||
if is_linker_generated_label(&symbol.name) && symbol.section.is_some() {
|
||||
log::debug!("Externing {:?} in {}", symbol, obj.name);
|
||||
replace_symbols.push((symbol_idx, ObjSymbol {
|
||||
|
@ -1320,12 +1353,15 @@ 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: usize) -> Result<SectionAddress> {
|
||||
pub fn end_for_section(obj: &ObjInfo, section_index: SectionIndex) -> 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")
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
use std::{borrow::Cow, ffi::CStr, mem::size_of};
|
||||
|
||||
use anyhow::Result;
|
||||
use zerocopy::{big_endian::U32, AsBytes, FromBytes, FromZeroes};
|
||||
use typed_path::Utf8UnixPath;
|
||||
use zerocopy::{big_endian::U32, FromBytes, Immutable, IntoBytes, KnownLayout};
|
||||
|
||||
use crate::static_assert;
|
||||
use crate::{static_assert, vfs::next_non_empty};
|
||||
|
||||
pub const U8_MAGIC: [u8; 4] = [0x55, 0xAA, 0x38, 0x2D];
|
||||
|
||||
/// U8 archive header.
|
||||
#[derive(Clone, Debug, PartialEq, FromBytes, FromZeroes, AsBytes)]
|
||||
#[derive(Clone, Debug, PartialEq, FromBytes, IntoBytes, Immutable, KnownLayout)]
|
||||
#[repr(C, align(4))]
|
||||
pub struct U8Header {
|
||||
magic: [u8; 4],
|
||||
|
@ -32,7 +33,7 @@ pub enum U8NodeKind {
|
|||
}
|
||||
|
||||
/// An individual file system node.
|
||||
#[derive(Clone, Debug, PartialEq, FromBytes, FromZeroes, AsBytes)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq, FromBytes, IntoBytes, Immutable, KnownLayout)]
|
||||
#[repr(C, align(4))]
|
||||
pub struct U8Node {
|
||||
kind: u8,
|
||||
|
@ -91,7 +92,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 Some(header) = U8Header::ref_from_prefix(buf) else {
|
||||
let Ok((header, _)) = U8Header::ref_from_prefix(buf) else {
|
||||
return Err("Buffer not large enough for U8 header");
|
||||
};
|
||||
if header.magic != U8_MAGIC {
|
||||
|
@ -101,7 +102,8 @@ 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).ok_or("U8 root node not aligned")?;
|
||||
let (root_node, _) =
|
||||
U8Node::ref_from_prefix(nodes_buf).map_err(|_| "U8 root node not aligned")?;
|
||||
if root_node.kind() != U8NodeKind::Directory {
|
||||
return Err("U8 root node is not a directory");
|
||||
}
|
||||
|
@ -113,7 +115,8 @@ 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::slice_from(nodes_buf).ok_or("U8 node table not aligned")?;
|
||||
let nodes =
|
||||
<[U8Node]>::ref_from_bytes(nodes_buf).map_err(|_| "U8 node table not aligned")?;
|
||||
Ok(Self { header, nodes, string_table })
|
||||
}
|
||||
|
||||
|
@ -121,7 +124,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: {})",
|
||||
|
@ -136,22 +139,29 @@ impl<'a> U8View<'a> {
|
|||
}
|
||||
|
||||
/// Finds a particular file or directory by path.
|
||||
pub fn find(&self, path: &str) -> Option<(usize, &U8Node)> {
|
||||
let mut split = path.trim_matches('/').split('/');
|
||||
let mut current = split.next()?;
|
||||
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]));
|
||||
}
|
||||
|
||||
let mut idx = 1;
|
||||
let mut stop_at = None;
|
||||
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 {
|
||||
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() {
|
||||
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;
|
||||
}
|
||||
} else if node.is_dir() {
|
||||
// Skip directory
|
||||
idx = node.length() as usize;
|
||||
|
@ -176,11 +186,11 @@ pub struct U8Iter<'a> {
|
|||
}
|
||||
|
||||
impl<'a> Iterator for U8Iter<'a> {
|
||||
type Item = (usize, &'a U8Node, Result<Cow<'a, str>, String>);
|
||||
type Item = (usize, U8Node, Result<Cow<'a, str>, String>);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let idx = self.idx;
|
||||
let node = self.inner.nodes.get(idx)?;
|
||||
let node = self.inner.nodes.get(idx).copied()?;
|
||||
let name = self.inner.get_name(node);
|
||||
self.idx += 1;
|
||||
Some((idx, node, name))
|
||||
|
|
|
@ -0,0 +1,130 @@
|
|||
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 }
|
||||
}
|
|
@ -0,0 +1,337 @@
|
|||
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),
|
||||
}
|
||||
}
|
|
@ -0,0 +1,373 @@
|
|||
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 "",
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
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),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
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 }
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
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),
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue