Compare commits

...

39 Commits

Author SHA1 Message Date
Luke Street f984bc3fb2 Cleanup find_object_base & better error handling 2024-10-18 00:41:17 -06:00
cadmic 8823c2225e
Follow symlinks when looking for disc images (#78) 2024-10-18 00:37:54 -06:00
Luke Street fa5068fd6d Fix ELF relocation processing
Another bug caused by object removing ELF null section / symbol
from their iterators.
2024-10-18 00:24:34 -06:00
Luke Street 3ada073da1 Update dependencies 2024-10-18 00:19:06 -06:00
Luke Street 5c7560bcea Fix address analysis with negative `add` operands
Resolves #76
2024-10-14 09:40:46 -06:00
Luke Street 8d8d801b2f A couple process_elf fixes 2024-10-13 14:15:13 -06:00
Luke Street bee4570a4c Always check for extracted files in object resolution
Fixes an issue where extracted files would not be found after
removing the disc image from the orig dir.
2024-10-13 13:36:01 -06:00
Luke Street 18bd608fe8 Extract files from disc image to `object_base` by default
When `extract_objects` is enabled, objects will be extracted once
from a disc image into `object_base`, and then used directly from
`object_base` going forward.

This allows users to delete the disc image from their `orig` dir
once the initial build completes.
2024-10-13 00:53:45 -06:00
Luke Street 1a9736f8d9 ci: Add Rust workspace cache 2024-10-12 23:32:51 -06:00
Luke Street 4fe2608e07 Make `selfile` relative to `object_base` 2024-10-12 23:31:41 -06:00
Luke Street 601c8e1a5e ci: Setup python venv for cargo-zigbuild 2024-10-10 22:37:55 -06:00
Luke Street 2e524e6806 Use typed-path in place of std Path/PathBuf
This allows handling path conversions in a more structured way,
as well as avoiding needless UTF-8 checks. All argument inputs
use `Utf8NativePathBuf`, while all config entries use
`Utf8UnixPathBuf`, ensuring that we deserialize/serialize using
forward slashes. We can omit `.display()` and lossy UTF-8
conversions since all paths are known valid UTF-8.
2024-10-04 23:38:15 -06:00
Luke Street 64d0491256 Remove unused metroidbuildinfo command 2024-10-04 21:08:34 -06:00
Luke Street 4611a4b501 Vendor nintendo-lz crate to fix issues & avoid old deps 2024-10-04 21:02:04 -06:00
Luke Street b184fee73f Migrate SectionIndex/SymbolIndex to u32
This halves the size of structs like SectionAddress.
2024-10-04 20:40:50 -06:00
Luke Street 1f4b452bd5 Fix disc VFS layout & update nod 2024-10-04 20:33:46 -06:00
Luke Street f346239b81 Update README.md 2024-10-04 19:17:46 -06:00
Luke Street ef7e0db095 VFS fixes and improvements, update README.md
`vfs ls`: Now displays size, detected file format, and decompressed
size (if applicable). `-r`/`--recursive` lists files recursively.
`-s`/`--short` prints only filenames.

`vfs cp`: Copies files recursively when the source is a directory.
`--no-decompress` disables automatic decompression for Yay0/Yaz0.
`-q` disables printing copied files.

`rarc` and `u8` commands are now thin wrappers over `vfs ls` and
`vfs cp`. For example, `rarc list file.arc` is now equivalent to
`vfs ls file.arc:`. `rarc extract file.arc -o out` is equivalent
to `vfs cp file.arc: out`.
2024-10-04 18:15:24 -06:00
Luke Street 281b0f7104 Decode extab entries as comment in assembly output 2024-10-03 22:33:21 -06:00
Luke Street 71701b5667 Update all dependencies 2024-10-03 22:24:54 -06:00
Luke Street f91c2a1474 Load objects from disc image & `vfs` module
Revamps support for container paths and centralizes logic into a VFS (virtual file system) module.
The new VFS architecture supports disc images and any layer of nesting.

For example, the following command works:
`dtk dol info 'Interactive Multi-Game Demo Disc - July 2002 (USA).rvz:files/zz_StarFox051702_e3.tgc:files/default.dol'`
This opens a TGC file inside an RVZ disc image, then reads `default.dol` in the FST.

Another example:
`dtk rel info 'Legend of Zelda, The - The Wind Waker (USA).rvz:files/RELS.arc:mmem/f_pc_profile_lst.rel'`
This opens a RARC archive inside an RVZ disc image, loads the Yaz0-compressed REL and
decompresses it on the fly.

This all operates in memory with minimal overhead, with no need to extract temporary files.

Supported container formats:
- Disc images (ISO/GCM, WIA/RVZ, WBFS, CISO, NFS, GCZ, TGC)
- RARC/SZS and U8 (.arc)

Supported compression formats:
- Yaz0 (SZS)
- Yay0 (SZP)
- NLZSS (.lz)

Additionally, projects can utilize a new configuration key `object_base`:
```
object: orig/GZLE01/sys/main.dol
modules:
- object: orig/GZLE01/files/RELS.arc:rels/mmem/f_pc_profile_lst.rel
```
becomes
```
object_base: orig/GZLE01
object: sys/main.dol
modules:
- object: files/RELS.arc:mmem/f_pc_profile_lst.rel
```
When loading the objects, decomp-toolkit will automatically check the `object_base`
directory for any disc images. (They can be named anything, but must be in the folder
root) If one is found, all objects will be fetched from the disc image itself, rather
than having to extract the files manually.

While still a work in progress, two new `vfs` commands were added: `vfs ls` and `vfs cp`.
These commands are very barebones currently, but allow listing directory contents and
extracting files from decomp-toolkit's vfs representation:
```
❯ dtk vfs ls disc.rvz:
files
sys

❯ dtk vfs ls disc.rvz:sys
boot.bin
bi2.bin
apploader.bin
fst.bin
main.dol

❯ dtk vfs cp disc.rvz:sys/main.dol .
```
2024-10-03 21:50:35 -06:00
Amber Brault 26f52f65b7
Automatically check for invalid extab relocations (#75)
* Begin work on extab reloc analysis code

* Refactoring

* Make format happy

* Address feedback + improvements
2024-10-03 01:13:23 -06:00
Luke Street c106123877 Use mimalloc when targeting musl
Also removes the armv7 linux build.
If you were using this, let me know!

Updates all dependencies
2024-09-29 12:20:28 -06:00
Luke Street dfda3d5ea3 Fix todo! in FileIterator::next_path 2024-09-29 12:02:55 -06:00
Luke Street 68f4552e44 Better support for SMG/TP maps
These maps are post-processed and have
various issues. This includes some hacks
to work around those issues as much as
possible.
2024-09-29 12:02:26 -06:00
Luke Street ac45676770 Fixes for updated object crate
object stopped including the ELF null
symbol and null section in the respective
iterators. We relied on this behavior for
building certain vectors in order of
symbol index. Adjust this logic to
restore the correct behavior.
2024-09-29 12:00:44 -06:00
Luke Street e430cb56f5 Remove features from CI 2024-09-09 22:05:33 -06:00
Luke Street 0719c73ef8 Resolve clippy issue 2024-09-09 20:39:54 -06:00
Luke Street cfcd146dfa Add map config for generating symbols/splits
Useful for extracting information from
map files that aren't fully well-formed,
such as ones from existing decompilation
projects.
2024-09-09 20:38:25 -06:00
Luke Street d4f695ffc7 dol diff: Loosen @ symbol match criteria 2024-09-09 20:36:46 -06:00
Luke Street 8b793b5616 Update CI workflow & all dependencies 2024-09-09 20:36:18 -06:00
Luke Street 9dfdbb9301 Fix v1-2 REL alignment regression
Alignment after section data and
before relocations / import table
is exclusive to REL v3.
2024-09-05 00:26:14 -06:00
Luke Street c403931f0f Update nod for TGC disc support 2024-09-04 20:09:56 -06:00
Luke Street d9817f63d5 Fix .note.split warnings for older mwld versions
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.
2024-09-04 19:56:22 -06:00
Chippy a112eb1829
DWARF: Add PureVirtual/Virtual Block2 attributes (#70) 2024-08-26 17:41:25 -07:00
Luke Street b6a29fa910 Add split `order` attribute for manual reordering
Example in splits.txt:
```
file1.cpp: order:0
  ...

file2.cpp: order:1
  ...

file3.cpp: order:2
  ...
```

This ensures that file2.cpp is always
anchored in between 1 and 3 when resolving
the final link order.
2024-08-11 20:38:11 -06:00
Wesley Moret da6a514fac
RSO: `make` command (#67)
Allow to create rso file from relocatable elf

No sel file support yet
2024-08-06 21:15:03 -06:00
riidefi cfeacd2c3a
elf2dol: Support section name denylist (#64) 2024-07-17 20:02:34 -06:00
First Last c3c7c2b062
Properly locate ProDG .bss sections (partial addressing of #62) (#63)
* Locate ProDG .bss sections (partial addressing of #62)

* Support both correct and incorrect memset calls
2024-07-16 23:14:46 -06:00
61 changed files with 5121 additions and 2419 deletions

View File

@ -1,16 +1,17 @@
name: Build name: Build
on: on:
pull_request:
push: push:
paths-ignore: paths-ignore:
- '*.md' - '*.md'
- 'LICENSE*' - 'LICENSE*'
pull_request: workflow_dispatch:
env: env:
BUILD_PROFILE: release-lto BUILD_PROFILE: release-lto
CARGO_BIN_NAME: dtk
CARGO_TARGET_DIR: target CARGO_TARGET_DIR: target
CARGO_INCREMENTAL: 0
jobs: jobs:
check: check:
@ -25,6 +26,8 @@ jobs:
uses: dtolnay/rust-toolchain@stable uses: dtolnay/rust-toolchain@stable
with: with:
components: clippy components: clippy
- name: Cache Rust workspace
uses: Swatinem/rust-cache@v2
- name: Cargo check - name: Cargo check
run: cargo check --all-features --all-targets run: cargo check --all-features --all-targets
- name: Cargo clippy - name: Cargo clippy
@ -74,11 +77,15 @@ jobs:
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Setup Rust toolchain - name: Setup Rust toolchain
uses: dtolnay/rust-toolchain@stable uses: dtolnay/rust-toolchain@stable
- name: Cache Rust workspace
uses: Swatinem/rust-cache@v2
- name: Cargo test - name: Cargo test
run: cargo test --release --all-features run: cargo test --release --all-features
build: build:
name: Build name: Build dtk
env:
CARGO_BIN_NAME: dtk
strategy: strategy:
matrix: matrix:
include: include:
@ -94,10 +101,10 @@ jobs:
target: aarch64-unknown-linux-musl target: aarch64-unknown-linux-musl
name: linux-aarch64 name: linux-aarch64
build: zigbuild build: zigbuild
- platform: ubuntu-latest - platform: windows-latest
target: armv7-unknown-linux-musleabi target: i686-pc-windows-msvc
name: linux-armv7l name: windows-x86
build: zigbuild build: build
- platform: windows-latest - platform: windows-latest
target: x86_64-pc-windows-msvc target: x86_64-pc-windows-msvc
name: windows-x86_64 name: windows-x86_64
@ -119,19 +126,6 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 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 - name: Install dependencies
if: matrix.packages != '' if: matrix.packages != ''
run: | run: |
@ -139,20 +133,28 @@ jobs:
sudo apt-get -y install ${{ matrix.packages }} sudo apt-get -y install ${{ matrix.packages }}
- name: Install cargo-zigbuild - name: Install cargo-zigbuild
if: matrix.build == '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 - name: Setup Rust toolchain
uses: dtolnay/rust-toolchain@stable uses: dtolnay/rust-toolchain@stable
with: with:
targets: ${{ matrix.target }} targets: ${{ matrix.target }}
- name: Cache Rust workspace
uses: Swatinem/rust-cache@v2
with:
key: ${{ matrix.target }}
- name: Cargo build - 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 - name: Upload artifacts
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: ${{ matrix.name }} name: ${{ env.CARGO_BIN_NAME }}-${{ matrix.name }}
path: | 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 }}
${{ env.CARGO_TARGET_DIR }}/${{ matrix.target }}/${{ env.BUILD_PROFILE }}/${{ env.CARGO_BIN_NAME }}.exe ${{ env.CARGO_TARGET_DIR }}/${{ matrix.target }}/${{ env.BUILD_PROFILE }}/${{ env.CARGO_BIN_NAME }}.exe
if-no-files-found: error if-no-files-found: error
@ -162,7 +164,23 @@ jobs:
if: startsWith(github.ref, 'refs/tags/') if: startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: [ build ] needs: [ build ]
permissions:
contents: write
steps: 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 - name: Download artifacts
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4
with: with:
@ -170,12 +188,28 @@ jobs:
- name: Rename artifacts - name: Rename artifacts
working-directory: artifacts working-directory: artifacts
run: | run: |
set -euo pipefail
mkdir ../out mkdir ../out
for i in */*/$BUILD_PROFILE/$CARGO_BIN_NAME*; do for dir in */; do
mv "$i" "../out/$(sed -E "s/([^/]+)\/[^/]+\/$BUILD_PROFILE\/($CARGO_BIN_NAME)/\2-\1/" <<< "$i")" 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 done
ls -R ../out ls -R ../out
- name: Release - name: Release
uses: softprops/action-gh-release@v2 uses: softprops/action-gh-release@v2
with: with:
files: out/* files: out/*
draft: true
generate_release_notes: true

983
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

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

127
README.md
View File

@ -39,12 +39,15 @@ project structure and build system that uses decomp-toolkit under the hood.
- [rel info](#rel-info) - [rel info](#rel-info)
- [rel merge](#rel-merge) - [rel merge](#rel-merge)
- [rso info](#rso-info) - [rso info](#rso-info)
- [rso make](#rso-make)
- [shasum](#shasum) - [shasum](#shasum)
- [nlzss decompress](#nlzss-decompress) - [nlzss decompress](#nlzss-decompress)
- [rarc list](#rarc-list) - [rarc list](#rarc-list)
- [rarc extract](#rarc-extract) - [rarc extract](#rarc-extract)
- [u8 list](#u8-list) - [u8 list](#u8-list)
- [u8 extract](#u8-extract) - [u8 extract](#u8-extract)
- [vfs ls](#vfs-ls)
- [vfs cp](#vfs-cp)
- [yay0 decompress](#yay0-decompress) - [yay0 decompress](#yay0-decompress)
- [yay0 compress](#yay0-compress) - [yay0 compress](#yay0-compress)
- [yaz0 decompress](#yaz0-decompress) - [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 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. as generating the linker script and other files needed to link the decompiled code.
## Analyzer features ## Analyzer features
**Function boundary analysis** **Function boundary analysis**
@ -125,11 +126,6 @@ If desired, optionally writes GNU assembler-compatible files alongside the objec
**Linker script generation** **Linker script generation**
Generates `ldscript.lcf` for `mwldeppc.exe`. Generates `ldscript.lcf` for `mwldeppc.exe`.
**Future work**
- Support RSO files
- Add more signatures
## Commands ## Commands
### ar create ### ar create
@ -180,6 +176,8 @@ and its `nodtool` command line tool._
Displays information about disc images. Displays information about disc images.
To list the contents of a disc image, use [vfs ls](#vfs-ls).
Supported disc image formats: Supported disc image formats:
- ISO (GCM) - ISO (GCM)
@ -188,6 +186,7 @@ Supported disc image formats:
- CISO (+ NKit 2 lossless) - CISO (+ NKit 2 lossless)
- NFS (Wii U VC) - NFS (Wii U VC)
- GCZ - GCZ
- TGC
```shell ```shell
$ dtk disc info /path/to/game.iso $ 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. See [disc info](#disc-info) for supported formats.
> [!NOTE]
> [vfs cp](#vfs-cp) is more flexible and supports disc images.
```shell ```shell
$ dtk disc extract /path/to/game.iso [outdir] $ 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. Analyzes a DOL file and outputs information section and symbol information.
See [vfs ls](#vfs-ls) for information on the VFS abstraction.
```shell ```shell
$ dtk dol info input.dol $ dtk dol info input.dol
# or, directly from a disc image
$ dtk dol info 'disc.rvz:sys/main.dol'
``` ```
### dol split ### dol split
> [!NOTE] > [!IMPORTANT]
> This command is a work-in-progress. > **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. 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 ```shell
$ dtk dol split config.yml target $ dtk dol split config.yml target
``` ```
@ -321,6 +325,8 @@ Creates a DOL file from the provided ELF file.
```shell ```shell
$ dtk elf2dol input.elf output.dol $ dtk elf2dol input.elf output.dol
# or, to ignore certain sections
$ dtk elf2dol input.elf output.dol --ignore debug_section1 --ignore debug_section2
``` ```
### map ### map
@ -344,8 +350,12 @@ $ dtk map symbol Game.MAP 'Function__5ClassFv'
Prints information about a REL file. Prints information about a REL file.
See [vfs ls](#vfs-ls) for information on the VFS abstraction.
```shell ```shell
$ dtk rel info input.rel $ 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 ### rel merge
@ -368,6 +378,22 @@ Prints information about an RSO file.
$ dtk rso info input.rso $ 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 ### shasum
Calculate and verify SHA-1 hashes. Calculate and verify SHA-1 hashes.
@ -392,6 +418,10 @@ $ dtk nlzss decompress rels/*.lz -o rels
### rarc list ### 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. Lists the contents of an RARC (older .arc) archive.
```shell ```shell
@ -400,6 +430,10 @@ $ dtk rarc list input.arc
### rarc extract ### 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. Extracts the contents of an RARC (older .arc) archive.
```shell ```shell
@ -408,6 +442,10 @@ $ dtk rarc extract input.arc -o output_dir
### u8 list ### 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. Extracts the contents of a U8 (newer .arc) archive.
```shell ```shell
@ -416,12 +454,75 @@ $ dtk u8 list input.arc
### u8 extract ### 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. Extracts the contents of a U8 (newer .arc) archive.
```shell ```shell
$ dtk u8 extract input.arc -o output_dir $ 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 ### yay0 decompress
Decompresses Yay0-compressed files. Decompresses Yay0-compressed files.

182
deny.toml
View File

@ -9,6 +9,11 @@
# The values provided in this template are the default values that will be used # 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 # 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, # If 1 or more target triples (and optionally, target_features) are specified,
# only the specified targets will be checked when running `cargo deny check`. # 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 # This means, if a particular package is only ever used as a target specific
@ -20,53 +25,67 @@
targets = [ targets = [
# The triple can be any string, but only the target triples built in to # 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 # 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 # You can also specify which target_features you promise are enabled for a
# particular target. target_features are currently not validated against # particular target. target_features are currently not validated against
# the actual valid features supported by the target architecture. # the actual valid features supported by the target architecture.
#{ triple = "wasm32-unknown-unknown", features = ["atomics"] }, #{ 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` # This section is considered when running `cargo deny check advisories`
# More documentation for the advisories section can be found here: # More documentation for the advisories section can be found here:
# https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html # https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html
[advisories] [advisories]
# The path where the advisory database is cloned/fetched into # The path where the advisory databases are cloned/fetched into
db-path = "~/.cargo/advisory-db" #db-path = "$CARGO_HOME/advisory-dbs"
# The url(s) of the advisory databases to use # The url(s) of the advisory databases to use
db-urls = ["https://github.com/rustsec/advisory-db"] #db-urls = ["https://github.com/rustsec/advisory-db"]
# The lint level for security vulnerabilities
vulnerability = "deny"
# The lint level for unmaintained crates
unmaintained = "warn"
# The lint level for crates that have been yanked from their source registry
yanked = "warn"
# The lint level for crates with security notices. Note that as of
# 2019-12-17 there are no security notice advisories in
# https://github.com/rustsec/advisory-db
notice = "warn"
# A list of advisory IDs to ignore. Note that ignored advisories will still # A list of advisory IDs to ignore. Note that ignored advisories will still
# output a note when they are encountered. # output a note when they are encountered.
ignore = [ ignore = [
#"RUSTSEC-0000-0000", #"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 # If this is true, then cargo deny will use the git executable to fetch advisory database.
# lower than the range specified will be ignored. Note that ignored advisories # If this is false, then it uses a built-in git library.
# will still output a note when they are encountered. # Setting this to true can be helpful if you have special authentication requirements that cargo-deny does not support.
# * None - CVSS Score 0.0 # See Git Authentication for more information about setting up git authentication.
# * Low - CVSS Score 0.1 - 3.9 #git-fetch-with-cli = true
# * Medium - CVSS Score 4.0 - 6.9
# * High - CVSS Score 7.0 - 8.9
# * Critical - CVSS Score 9.0 - 10.0
#severity-threshold =
# This section is considered when running `cargo deny check licenses` # This section is considered when running `cargo deny check licenses`
# More documentation for the licenses section can be found here: # More documentation for the licenses section can be found here:
# https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html # https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html
[licenses] [licenses]
# The lint level for crates which do not have a detectable license # List of explicitly allowed licenses
unlicensed = "deny"
# List of explictly allowed licenses
# See https://spdx.org/licenses/ for list of possible licenses # See https://spdx.org/licenses/ for list of possible licenses
# [possible values: any SPDX 3.11 short identifier (+ optional exception)]. # [possible values: any SPDX 3.11 short identifier (+ optional exception)].
allow = [ allow = [
@ -77,28 +96,10 @@ allow = [
"BSL-1.0", "BSL-1.0",
"ISC", "ISC",
"MIT", "MIT",
"MPL-2.0",
"Unicode-DFS-2016", "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 confidence threshold for detecting a license from license text.
# The higher the value, the more closely the license text must be to the # The higher the value, the more closely the license text must be to the
# canonical license text of a valid SPDX license file. # canonical license text of a valid SPDX license file.
@ -109,32 +110,32 @@ confidence-threshold = 0.8
exceptions = [ exceptions = [
# Each entry is the crate and version constraint, and its specific allow # Each entry is the crate and version constraint, and its specific allow
# list # list
#{ allow = ["Zlib"], name = "adler32", version = "*" }, #{ allow = ["Zlib"], crate = "adler32" },
] ]
# Some crates don't have (easily) machine readable licensing information, # Some crates don't have (easily) machine readable licensing information,
# adding a clarification entry for it allows you to manually specify the # adding a clarification entry for it allows you to manually specify the
# licensing information # licensing information
[[licenses.clarify]] #[[licenses.clarify]]
# The name of the crate the clarification applies to # The package spec the clarification applies to
name = "encoding_rs" #crate = "ring"
# The optional version constraint for the crate
#version = "*"
# The SPDX expression for the license requirements of the crate # 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 # 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 # the license expression. If the contents match, the clarification will be used
# when running the license check, otherwise the clarification will be ignored # when running the license check, otherwise the clarification will be ignored
# and the crate will be checked normally, which may produce warnings or errors # and the crate will be checked normally, which may produce warnings or errors
# depending on the rest of your configuration # depending on the rest of your configuration
license-files = [ #license-files = [
# Each entry is a crate relative path, and the (opaque) hash of its contents # Each entry is a crate relative path, and the (opaque) hash of its contents
{ path = "COPYRIGHT", hash = 0x39f8ad31 } #{ path = "LICENSE", hash = 0xbd0eed23 }
] #]
[licenses.private] [licenses.private]
# If true, ignores workspace crates that aren't published, or are only # 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 ignore = false
# One or more private registries that you might publish crates to, if a crate # 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 # 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 # https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html
[bans] [bans]
# Lint level for when multiple versions of the same crate are detected # 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 `*` # Lint level for when a crate version requirement is `*`
wildcards = "allow" wildcards = "allow"
# The graph highlighting used when creating dotgraphs for crates # 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 # * simplest-path - The path to the version with the fewest edges is highlighted
# * all - Both lowest-version and simplest-path are used # * all - Both lowest-version and simplest-path are used
highlight = "all" 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! # List of crates that are allowed. Use with care!
allow = [ 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 # List of crates to deny
deny = [ deny = [
# Each entry the name of a crate and a version range. If version is #"ansi_term@0.11.0",
# not specified, all versions will be matched. #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason it is banned" },
#{ name = "ansi_term", version = "=0.11.0" },
#
# Wrapper crates can optionally be specified to allow the crate when it # Wrapper crates can optionally be specified to allow the crate when it
# is a direct dependency of the otherwise banned crate # 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. # Certain crates/versions that will be skipped when doing duplicate detection.
skip = [ 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 # Similarly to `skip` allows you to skip certain crates during duplicate
# detection. Unlike skip, it also includes the entire tree of transitive # detection. Unlike skip, it also includes the entire tree of transitive
# dependencies starting at the specified crate, up to a certain depth, which is # dependencies starting at the specified crate, up to a certain depth, which is
# by default infinite # by default infinite.
skip-tree = [ 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`. # 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 = [] allow-git = []
[sources.allow-org] [sources.allow-org]
# 1 or more github.com organizations to allow git sources for # github.com organizations to allow git sources for
#github = [""] github = ["bjorn3"]
# 1 or more gitlab.com organizations to allow git sources for # gitlab.com organizations to allow git sources for
#gitlab = [""] gitlab = []
# 1 or more bitbucket.org organizations to allow git sources for # bitbucket.org organizations to allow git sources for
#bitbucket = [""] bitbucket = []

View File

@ -16,12 +16,15 @@ use crate::{
vm::{BranchTarget, GprValue, StepResult, VM}, vm::{BranchTarget, GprValue, StepResult, VM},
RelocationTarget, RelocationTarget,
}, },
obj::{ObjInfo, ObjSectionKind, ObjSymbol, ObjSymbolFlagSet, ObjSymbolFlags, ObjSymbolKind}, obj::{
ObjInfo, ObjSectionKind, ObjSymbol, ObjSymbolFlagSet, ObjSymbolFlags, ObjSymbolKind,
SectionIndex,
},
}; };
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct SectionAddress { pub struct SectionAddress {
pub section: usize, pub section: SectionIndex,
pub address: u32, pub address: u32,
} }
@ -38,7 +41,7 @@ impl Display for SectionAddress {
} }
impl 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 { pub fn offset(self, offset: i32) -> Self {
Self { section: self.section, address: self.address.wrapping_add_signed(offset) } 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 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 { impl Add<u32> for SectionAddress {
@ -116,7 +123,7 @@ pub struct AnalyzerState {
pub functions: BTreeMap<SectionAddress, FunctionInfo>, pub functions: BTreeMap<SectionAddress, FunctionInfo>,
pub jump_tables: BTreeMap<SectionAddress, u32>, pub jump_tables: BTreeMap<SectionAddress, u32>,
pub known_symbols: BTreeMap<SectionAddress, Vec<ObjSymbol>>, pub known_symbols: BTreeMap<SectionAddress, Vec<ObjSymbol>>,
pub known_sections: BTreeMap<usize, String>, pub known_sections: BTreeMap<SectionIndex, String>,
} }
impl AnalyzerState { impl AnalyzerState {
@ -597,19 +604,27 @@ pub fn locate_bss_memsets(obj: &mut ObjInfo) -> Result<Vec<(u32, u32)>> {
StepResult::Branch(branches) => { StepResult::Branch(branches) => {
for branch in branches { for branch in branches {
if branch.link { 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 ( if let (
GprValue::Constant(addr), GprValue::Constant(addr),
GprValue::Constant(value), GprValue::Constant(value),
GprValue::Constant(size), 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 { if value == 0 && size > 0 {
bss_sections.push((addr, size)); bss_sections.push((addr, size));
} }
} }
} }
} }
if bss_sections.len() >= 2 {
return Ok(ExecCbResult::End(()));
}
Ok(ExecCbResult::Continue) Ok(ExecCbResult::Continue)
} }
} }

View File

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

View File

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

View File

@ -1,7 +1,7 @@
use anyhow::Result; use anyhow::Result;
use crate::{ use crate::{
obj::{ObjDataKind, ObjInfo, ObjSectionKind, ObjSymbolKind}, obj::{ObjDataKind, ObjInfo, ObjSectionKind, ObjSymbolKind, SymbolIndex},
util::split::is_linker_generated_label, 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<()> { 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 for (section_index, section) in obj
.sections .sections
.iter() .iter()

View File

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

View File

@ -4,6 +4,7 @@ use std::{
}; };
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use cwextab::decode_extab;
use ppc750cl::Opcode; use ppc750cl::Opcode;
use tracing::{debug_span, info_span}; use tracing::{debug_span, info_span};
use tracing_attributes::instrument; use tracing_attributes::instrument;
@ -18,7 +19,7 @@ use crate::{
}, },
obj::{ obj::{
ObjDataKind, ObjInfo, ObjKind, ObjReloc, ObjRelocKind, ObjSection, ObjSectionKind, 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.process_data(obj, section_index, section)?;
} }
} }
self.check_extab_relocations(obj)?;
self.reject_invalid_relocations(obj)?; self.reject_invalid_relocations(obj)?;
Ok(()) Ok(())
} }
@ -147,6 +149,62 @@ impl Tracker {
Ok(()) 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<()> { fn process_code(&mut self, obj: &ObjInfo) -> Result<()> {
if let Some(entry) = obj.entry { if let Some(entry) = obj.entry {
let (section_index, _) = obj.sections.at_address(entry as u32)?; let (section_index, _) = obj.sections.at_address(entry as u32)?;
@ -240,7 +298,7 @@ impl Tracker {
debug_assert_ne!( debug_assert_ne!(
value, value,
RelocationTarget::Address(SectionAddress::new( RelocationTarget::Address(SectionAddress::new(
usize::MAX, SectionIndex::MAX,
0 0
)) ))
); );
@ -301,7 +359,7 @@ impl Tracker {
debug_assert_ne!( debug_assert_ne!(
address, address,
RelocationTarget::Address(SectionAddress::new( RelocationTarget::Address(SectionAddress::new(
usize::MAX, SectionIndex::MAX,
0 0
)) ))
); );
@ -322,7 +380,7 @@ impl Tracker {
debug_assert_ne!( debug_assert_ne!(
address, address,
RelocationTarget::Address(SectionAddress::new( RelocationTarget::Address(SectionAddress::new(
usize::MAX, SectionIndex::MAX,
0 0
)) ))
); );
@ -406,7 +464,7 @@ impl Tracker {
{ {
(addr, is_function_addr(addr)) (addr, is_function_addr(addr))
} else { } else {
(SectionAddress::new(usize::MAX, 0), false) (SectionAddress::new(SectionIndex::MAX, 0), false)
}; };
if branch.link || !is_fn_addr { if branch.link || !is_fn_addr {
self.relocations.insert(ins_addr, match ins.op { self.relocations.insert(ins_addr, match ins.op {
@ -491,7 +549,7 @@ impl Tracker {
fn process_data( fn process_data(
&mut self, &mut self,
obj: &ObjInfo, obj: &ObjInfo,
section_index: usize, section_index: SectionIndex,
section: &ObjSection, section: &ObjSection,
) -> Result<()> { ) -> Result<()> {
let mut addr = SectionAddress::new(section_index, section.address as u32); let mut addr = SectionAddress::new(section_index, section.address as u32);
@ -544,7 +602,7 @@ impl Tracker {
} else { } else {
// Check known relocations (function signature matching) // Check known relocations (function signature matching)
if self.known_relocations.contains(&from) { if self.known_relocations.contains(&from) {
return Some(SectionAddress::new(usize::MAX, addr)); return Some(SectionAddress::new(SectionIndex::MAX, addr));
} }
// Check special symbols // Check special symbols
if self.stack_address == Some(addr) if self.stack_address == Some(addr)
@ -555,7 +613,7 @@ impl Tracker {
|| self.sda2_base == Some(addr) || self.sda2_base == Some(addr)
|| self.sda_base == Some(addr) || self.sda_base == Some(addr)
{ {
return Some(SectionAddress::new(usize::MAX, addr)); return Some(SectionAddress::new(SectionIndex::MAX, addr));
} }
// Not valid // Not valid
None None
@ -567,7 +625,7 @@ impl Tracker {
obj: &mut ObjInfo, obj: &mut ObjInfo,
addr: u32, addr: u32,
reloc_kind: ObjRelocKind, reloc_kind: ObjRelocKind,
) -> Option<usize> { ) -> Option<SymbolIndex> {
if !matches!( if !matches!(
reloc_kind, reloc_kind,
ObjRelocKind::PpcAddr16Ha | ObjRelocKind::PpcAddr16Lo ObjRelocKind::PpcAddr16Ha | ObjRelocKind::PpcAddr16Lo
@ -583,7 +641,7 @@ impl Tracker {
// return generate_special_symbol(obj, addr, &name).ok(); // 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 let Some(value) = opt {
if addr == value { if addr == value {
return generate_special_symbol(obj, value, name).ok(); 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( obj.add_symbol(
ObjSymbol { ObjSymbol {
name: name.to_string(), name: name.to_string(),

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

@ -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(())
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

286
src/cmd/vfs.rs Normal file
View File

@ -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(())
}

View File

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

View File

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

View File

@ -1,3 +1,4 @@
#![deny(unused_crate_dependencies)]
use std::{env, ffi::OsStr, fmt::Display, path::PathBuf, process::exit, str::FromStr}; use std::{env, ffi::OsStr, fmt::Display, path::PathBuf, process::exit, str::FromStr};
use anyhow::Error; use anyhow::Error;
@ -12,6 +13,13 @@ pub mod argp_version;
pub mod cmd; pub mod cmd;
pub mod obj; pub mod obj;
pub mod util; 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)] #[derive(Debug, Eq, PartialEq, Copy, Clone)]
enum LogLevel { enum LogLevel {
@ -89,13 +97,13 @@ enum SubCommand {
Elf(cmd::elf::Args), Elf(cmd::elf::Args),
Elf2Dol(cmd::elf2dol::Args), Elf2Dol(cmd::elf2dol::Args),
Map(cmd::map::Args), Map(cmd::map::Args),
MetroidBuildInfo(cmd::metroidbuildinfo::Args),
Nlzss(cmd::nlzss::Args), Nlzss(cmd::nlzss::Args),
Rarc(cmd::rarc::Args), Rarc(cmd::rarc::Args),
Rel(cmd::rel::Args), Rel(cmd::rel::Args),
Rso(cmd::rso::Args), Rso(cmd::rso::Args),
Shasum(cmd::shasum::Args), Shasum(cmd::shasum::Args),
U8(cmd::u8_arc::Args), U8(cmd::u8_arc::Args),
Vfs(cmd::vfs::Args),
Yay0(cmd::yay0::Args), Yay0(cmd::yay0::Args),
Yaz0(cmd::yaz0::Args), Yaz0(cmd::yaz0::Args),
} }
@ -164,13 +172,13 @@ fn main() {
SubCommand::Elf(c_args) => cmd::elf::run(c_args), SubCommand::Elf(c_args) => cmd::elf::run(c_args),
SubCommand::Elf2Dol(c_args) => cmd::elf2dol::run(c_args), SubCommand::Elf2Dol(c_args) => cmd::elf2dol::run(c_args),
SubCommand::Map(c_args) => cmd::map::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::Nlzss(c_args) => cmd::nlzss::run(c_args),
SubCommand::Rarc(c_args) => cmd::rarc::run(c_args), SubCommand::Rarc(c_args) => cmd::rarc::run(c_args),
SubCommand::Rel(c_args) => cmd::rel::run(c_args), SubCommand::Rel(c_args) => cmd::rel::run(c_args),
SubCommand::Rso(c_args) => cmd::rso::run(c_args), SubCommand::Rso(c_args) => cmd::rso::run(c_args),
SubCommand::Shasum(c_args) => cmd::shasum::run(c_args), SubCommand::Shasum(c_args) => cmd::shasum::run(c_args),
SubCommand::U8(c_args) => cmd::u8_arc::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::Yay0(c_args) => cmd::yay0::run(c_args),
SubCommand::Yaz0(c_args) => cmd::yaz0::run(c_args), SubCommand::Yaz0(c_args) => cmd::yaz0::run(c_args),
}); });

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,234 +1,48 @@
use std::{ use std::{
ffi::OsStr, fs,
fs::{DirBuilder, File, OpenOptions}, fs::{DirBuilder, File, OpenOptions},
io::{BufRead, BufReader, BufWriter, Cursor, Read, Seek, SeekFrom}, io,
path::{Component, Path, PathBuf}, 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 filetime::{set_file_mtime, FileTime};
use memmap2::{Mmap, MmapOptions};
use path_slash::PathBufExt;
use rarc::RarcReader;
use sha1::{Digest, Sha1}; use sha1::{Digest, Sha1};
use typed_path::{Utf8NativePath, Utf8NativePathBuf, Utf8UnixPathBuf};
use xxhash_rust::xxh3::xxh3_64; use xxhash_rust::xxh3::xxh3_64;
use crate::{ use crate::{
array_ref, array_ref,
util::{ util::{
ncompress::{decompress_yay0, decompress_yaz0, YAY0_MAGIC, YAZ0_MAGIC}, ncompress::{decompress_yay0, decompress_yaz0, YAY0_MAGIC, YAZ0_MAGIC},
rarc, path::check_path_buf,
rarc::{Node, RARC_MAGIC},
take_seek::{TakeSeek, TakeSeekExt},
u8_arc::{U8View, U8_MAGIC},
Bytes, 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). /// Creates a buffered writer around a file (not memory mapped).
pub fn buf_writer<P>(path: P) -> Result<BufWriter<File>> pub fn buf_writer(path: &Utf8NativePath) -> Result<BufWriter<File>> {
where P: AsRef<Path> { if let Some(parent) = path.parent() {
if let Some(parent) = path.as_ref().parent() {
DirBuilder::new().recursive(true).create(parent)?; DirBuilder::new().recursive(true).create(parent)?;
} }
let file = File::create(&path) let file = File::create(path).with_context(|| format!("Failed to create file '{}'", path))?;
.with_context(|| format!("Failed to create file '{}'", path.as_ref().display()))?;
Ok(BufWriter::new(file)) Ok(BufWriter::new(file))
} }
/// Reads a string with known size at the specified offset. /// 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 { where R: Read + Seek + ?Sized {
let mut data = vec![0u8; size]; let mut data = vec![0u8; size];
let pos = reader.stream_position()?; let pos = reader.stream_position()?;
reader.seek(SeekFrom::Start(off))?; reader.seek(SeekFrom::Start(off))?;
reader.read_exact(&mut data)?; reader.read_exact(&mut data)?;
reader.seek(SeekFrom::Start(pos))?; 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. /// 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 { where R: Read + Seek + ?Sized {
let pos = reader.stream_position()?; let pos = reader.stream_position()?;
reader.seek(SeekFrom::Start(off))?; reader.seek(SeekFrom::Start(off))?;
@ -246,22 +60,21 @@ where R: Read + Seek + ?Sized {
} }
/// Process response files (starting with '@') and glob patterns (*). /// Process response files (starting with '@') and glob patterns (*).
pub fn process_rsp(files: &[PathBuf]) -> Result<Vec<PathBuf>> { pub fn process_rsp(files: &[Utf8NativePathBuf]) -> Result<Vec<Utf8NativePathBuf>> {
let mut out = Vec::with_capacity(files.len()); let mut out = Vec::<Utf8NativePathBuf>::with_capacity(files.len());
for path in files { for path in files {
let path_str = if let Some(rsp_file) = path.as_str().strip_prefix('@') {
path.to_str().ok_or_else(|| anyhow!("'{}' is not valid UTF-8", path.display()))?; let file = open_file(Utf8NativePath::new(rsp_file), true)?;
if let Some(rsp_file) = path_str.strip_prefix('@') { for result in file.lines() {
let reader = buf_reader(rsp_file)?;
for result in reader.lines() {
let line = result?; let line = result?;
if !line.is_empty() { if !line.is_empty() {
out.push(PathBuf::from_slash(line)); out.push(Utf8UnixPathBuf::from(line).with_encoding());
} }
} }
} else if path_str.contains('*') { } else if path.as_str().contains('*') {
for entry in glob::glob(path_str)? { for entry in glob::glob(path.as_str())? {
out.push(entry?); let path = check_path_buf(entry?)?;
out.push(path.with_encoding());
} }
} else { } else {
out.push(path.clone()); out.push(path.clone());
@ -270,120 +83,20 @@ pub fn process_rsp(files: &[PathBuf]) -> Result<Vec<PathBuf>> {
Ok(out) Ok(out)
} }
/// Iterator over files in a RARC archive.
struct RarcIterator {
file: MappedFile,
base_path: PathBuf,
paths: Vec<(PathBuf, u64, u32)>,
index: usize,
}
impl RarcIterator {
pub fn new(file: MappedFile, base_path: &Path) -> Result<Self> {
let reader = RarcReader::new(&mut file.as_reader())?;
let paths = Self::collect_paths(&reader, base_path);
Ok(Self { file, base_path: base_path.to_owned(), paths, index: 0 })
}
fn collect_paths(reader: &RarcReader, base_path: &Path) -> Vec<(PathBuf, u64, u32)> {
let mut current_path = PathBuf::new();
let mut paths = vec![];
for node in reader.nodes() {
match node {
Node::DirectoryBegin { name } => {
current_path.push(name.name);
}
Node::DirectoryEnd { name: _ } => {
current_path.pop();
}
Node::File { name, offset, size } => {
let path = base_path.join(&current_path).join(name.name);
paths.push((path, offset, size));
}
Node::CurrentDirectory => {}
Node::ParentDirectory => {}
}
}
paths
}
}
impl Iterator for RarcIterator {
type Item = Result<(PathBuf, Box<[u8]>)>;
fn next(&mut self) -> Option<Self::Item> {
if self.index >= self.paths.len() {
return None;
}
let (path, off, size) = self.paths[self.index].clone();
self.index += 1;
let slice = &self.file.as_slice()[off as usize..off as usize + size as usize];
match decompress_if_needed(slice) {
Ok(buf) => Some(Ok((path, buf.into_owned()))),
Err(e) => Some(Err(e)),
}
}
}
/// A file entry, either a memory mapped file or an owned buffer.
pub enum FileEntry {
MappedFile(MappedFile),
Buffer(Box<[u8]>, FileTime),
}
impl FileEntry {
/// Creates a reader for the file.
pub fn as_reader(&self) -> Cursor<&[u8]> {
match self {
Self::MappedFile(file) => file.as_reader(),
Self::Buffer(slice, _) => Cursor::new(slice),
}
}
pub fn as_slice(&self) -> &[u8] {
match self {
Self::MappedFile(file) => file.as_slice(),
Self::Buffer(slice, _) => slice,
}
}
pub fn len(&self) -> u64 {
match self {
Self::MappedFile(file) => file.len(),
Self::Buffer(slice, _) => slice.len() as u64,
}
}
pub fn is_empty(&self) -> bool {
match self {
Self::MappedFile(file) => file.is_empty(),
Self::Buffer(slice, _) => slice.is_empty(),
}
}
pub fn mtime(&self) -> FileTime {
match self {
Self::MappedFile(file) => file.mtime,
Self::Buffer(_, mtime) => *mtime,
}
}
}
/// Information about a file when it was read. /// Information about a file when it was read.
/// Used to determine if a file has changed since it was read (mtime) /// Used to determine if a file has changed since it was read (mtime)
/// and if it needs to be written (hash). /// and if it needs to be written (hash).
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FileReadInfo { pub struct FileReadInfo {
pub mtime: FileTime, pub mtime: Option<FileTime>,
pub hash: u64, pub hash: u64,
} }
impl FileReadInfo { impl FileReadInfo {
pub fn new(entry: &FileEntry) -> Result<Self> { pub fn new(entry: &mut dyn VfsFile) -> Result<Self> {
let hash = xxh3_64(entry.as_slice()); let hash = xxh3_64(entry.map()?);
Ok(Self { mtime: entry.mtime(), hash }) 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 RARC archive, iterate over its contents.
/// If a file is a Yaz0 compressed file, decompress it. /// If a file is a Yaz0 compressed file, decompress it.
pub struct FileIterator { pub struct FileIterator {
paths: Vec<PathBuf>, paths: Vec<Utf8NativePathBuf>,
index: usize, index: usize,
rarc: Option<RarcIterator>,
} }
impl FileIterator { impl FileIterator {
pub fn new(paths: &[PathBuf]) -> Result<Self> { pub fn new(paths: &[Utf8NativePathBuf]) -> Result<Self> {
Ok(Self { paths: process_rsp(paths)?, index: 0, rarc: None }) Ok(Self { paths: process_rsp(paths)?, index: 0 })
} }
fn next_rarc(&mut self) -> Option<Result<(PathBuf, FileEntry)>> { fn next_path(&mut self) -> Option<Result<(Utf8NativePathBuf, Box<dyn VfsFile>)>> {
if let Some(rarc) = &mut self.rarc {
match rarc.next() {
Some(Ok((path, buf))) => {
let mut path_str = rarc.base_path.as_os_str().to_os_string();
path_str.push(OsStr::new(":"));
path_str.push(path.as_os_str());
return Some(Ok((path, FileEntry::Buffer(buf, rarc.file.mtime))));
}
Some(Err(err)) => return Some(Err(err)),
None => self.rarc = None,
}
}
None
}
fn next_path(&mut self) -> Option<Result<(PathBuf, FileEntry)>> {
if self.index >= self.paths.len() { if self.index >= self.paths.len() {
return None; return None;
} }
let path = self.paths[self.index].clone(); let path = self.paths[self.index].clone();
self.index += 1; self.index += 1;
match map_file(&path) { match open_file(&path, true) {
Ok(FileEntry::MappedFile(map)) => self.handle_file(map, path), Ok(file) => Some(Ok((path, file))),
Ok(FileEntry::Buffer(_, _)) => todo!(), Err(e) => Some(Err(e)),
Err(err) => Some(Err(err)),
} }
} }
fn handle_file(
&mut self,
file: MappedFile,
path: PathBuf,
) -> Option<Result<(PathBuf, FileEntry)>> {
let buf = file.as_slice();
if buf.len() <= 4 {
return Some(Ok((path, FileEntry::MappedFile(file))));
}
match *array_ref!(buf, 0, 4) {
YAZ0_MAGIC => self.handle_yaz0(file, path),
YAY0_MAGIC => self.handle_yay0(file, path),
RARC_MAGIC => self.handle_rarc(file, path),
_ => Some(Ok((path, FileEntry::MappedFile(file)))),
}
}
fn handle_yaz0(
&mut self,
file: MappedFile,
path: PathBuf,
) -> Option<Result<(PathBuf, FileEntry)>> {
Some(match decompress_yaz0(file.as_slice()) {
Ok(buf) => Ok((path, FileEntry::Buffer(buf, file.mtime))),
Err(e) => Err(e),
})
}
fn handle_yay0(
&mut self,
file: MappedFile,
path: PathBuf,
) -> Option<Result<(PathBuf, FileEntry)>> {
Some(match decompress_yay0(file.as_slice()) {
Ok(buf) => Ok((path, FileEntry::Buffer(buf, file.mtime))),
Err(e) => Err(e),
})
}
fn handle_rarc(
&mut self,
file: MappedFile,
path: PathBuf,
) -> Option<Result<(PathBuf, FileEntry)>> {
self.rarc = match RarcIterator::new(file, &path) {
Ok(iter) => Some(iter),
Err(e) => return Some(Err(e)),
};
self.next()
}
} }
impl Iterator for FileIterator { 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<()> pub fn touch(path: &Utf8NativePath) -> io::Result<()> {
where P: AsRef<Path> { if fs::exists(path)? {
if path.as_ref().exists() {
set_file_mtime(path, FileTime::now()) set_file_mtime(path, FileTime::now())
} else { } else {
match OpenOptions::new().create(true).truncate(true).write(true).open(path) { 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<()> { 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]; let mut expected_bytes = [0u8; 20];
hex::decode_to_slice(expected_str, &mut expected_bytes) hex::decode_to_slice(expected_str, &mut expected_bytes)
.with_context(|| format!("Invalid SHA-1 '{expected_str}'"))?; .with_context(|| format!("Invalid SHA-1 '{expected_str}'"))?;
let mut hasher = Sha1::new(); if hash_bytes == expected_bytes {
hasher.update(buf);
let hash_bytes = hasher.finalize();
if hash_bytes.as_ref() == expected_bytes {
Ok(()) Ok(())
} else { } else {
Err(anyhow!( 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())
}

View File

@ -1,8 +1,6 @@
use std::path::PathBuf;
use anyhow::Result; use anyhow::Result;
use itertools::Itertools; use itertools::Itertools;
use path_slash::PathBufExt; use typed_path::{Utf8NativePathBuf, Utf8UnixPath};
use crate::obj::{ObjInfo, ObjKind}; use crate::obj::{ObjInfo, ObjKind};
@ -33,11 +31,11 @@ pub fn generate_ldscript(
let mut force_files = Vec::with_capacity(obj.link_order.len()); let mut force_files = Vec::with_capacity(obj.link_order.len());
for unit in &obj.link_order { for unit in &obj.link_order {
let obj_path = obj_path_for_unit(&unit.name); 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(); 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() { if symbol.flags.is_exported() && symbol.flags.is_global() && !symbol.flags.is_no_write() {
force_active.push(symbol.name.clone()); 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()); let mut force_files = Vec::with_capacity(obj.link_order.len());
for unit in &obj.link_order { for unit in &obj.link_order {
let obj_path = obj_path_for_unit(&unit.name); 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(); 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() { if symbol.flags.is_exported() && symbol.flags.is_global() && !symbol.flags.is_no_write() {
force_active.push(symbol.name.clone()); force_active.push(symbol.name.clone());
} }
@ -99,6 +97,10 @@ pub fn generate_ldscript_partial(
Ok(out) 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")
}

View File

@ -5,23 +5,26 @@ use std::{
hash::Hash, hash::Hash,
io::BufRead, io::BufRead,
mem::{replace, take}, mem::{replace, take},
path::Path,
}; };
use anyhow::{anyhow, bail, Error, Result}; use anyhow::{anyhow, bail, Error, Result};
use cwdemangle::{demangle, DemangleOptions}; use cwdemangle::{demangle, DemangleOptions};
use flagset::FlagSet; use flagset::FlagSet;
use indexmap::IndexMap;
use itertools::Itertools; use itertools::Itertools;
use multimap::MultiMap; use multimap::MultiMap;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use regex::{Captures, Regex}; use regex::{Captures, Regex};
use typed_path::Utf8NativePath;
use crate::{ use crate::{
obj::{ obj::{
ObjInfo, ObjKind, ObjSplit, ObjSymbol, ObjSymbolFlagSet, ObjSymbolFlags, ObjSymbolKind, section_kind_for_section, ObjArchitecture, ObjInfo, ObjKind, ObjSection, ObjSectionKind,
ObjUnit, 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)] #[derive(Debug, Copy, Clone, Eq, PartialEq)]
@ -127,7 +130,7 @@ pub struct MapInfo {
pub unit_references: MultiMap<SymbolRef, String>, pub unit_references: MultiMap<SymbolRef, String>,
pub sections: Vec<SectionInfo>, pub sections: Vec<SectionInfo>,
pub link_map_symbols: HashMap<SymbolRef, SymbolEntry>, 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)>>, pub section_units: HashMap<String, Vec<(u32, String)>>,
// For common BSS inflation correction // For common BSS inflation correction
pub common_bss_start: Option<u32>, pub common_bss_start: Option<u32>,
@ -711,26 +714,61 @@ where
Ok(sm.result) Ok(sm.result)
} }
pub fn apply_map_file<P>( pub fn apply_map_file(
path: P, path: &Utf8NativePath,
obj: &mut ObjInfo, obj: &mut ObjInfo,
common_bss_start: Option<u32>, common_bss_start: Option<u32>,
mw_comment_version: Option<u8>, mw_comment_version: Option<u8>,
) -> Result<()> ) -> Result<()> {
where let mut file = open_file(path, true)?;
P: AsRef<Path>, let info = process_map(file.as_mut(), common_bss_start, mw_comment_version)?;
{ apply_map(info, obj)
let file = map_file(&path)?;
let info = process_map(&mut file.as_reader(), 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() { for (section_index, section) in obj.sections.iter_mut() {
let opt = if obj.kind == ObjKind::Executable { 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 { } 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 let Some(info) = opt {
if section.section_known && section.name != info.name { if section.section_known && section.name != info.name {
@ -753,9 +791,172 @@ pub fn apply_map(result: &MapInfo, obj: &mut ObjInfo) -> Result<()> {
section.rename(info.name.clone())?; section.rename(info.name.clone())?;
} else { } else {
log::warn!("Section {} @ {:#010X} not found in map", section.name, section.address); 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 // Add section symbols
for (section_name, symbol_map) in &result.section_symbols { for (section_name, symbol_map) in &result.section_symbols {
let (section_index, _) = obj let (section_index, _) = obj
@ -763,16 +964,10 @@ pub fn apply_map(result: &MapInfo, obj: &mut ObjInfo) -> Result<()> {
.by_name(section_name)? .by_name(section_name)?
.ok_or_else(|| anyhow!("Failed to locate section {section_name} from map"))?; .ok_or_else(|| anyhow!("Failed to locate section {section_name} from map"))?;
for symbol_entry in symbol_map.values().flatten() { 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 // Add splits
for (section_name, unit_order) in &result.section_units { for (section_name, unit_order) in &result.section_units {
let (_, section) = obj let (_, section) = obj
@ -796,6 +991,7 @@ pub fn apply_map(result: &MapInfo, obj: &mut ObjInfo) -> Result<()> {
name: unit.clone(), name: unit.clone(),
autogenerated: false, autogenerated: false,
comment_version: Some(0), 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 demangled_name = demangle(&symbol_entry.name, &DemangleOptions::default());
let mut flags: FlagSet<ObjSymbolFlags> = match symbol_entry.visibility { let mut flags: FlagSet<ObjSymbolFlags> = match symbol_entry.visibility {
SymbolVisibility::Unknown => Default::default(), 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::Section => ObjSymbolKind::Section,
SymbolKind::NoType => ObjSymbolKind::Unknown, SymbolKind::NoType => ObjSymbolKind::Unknown,
}, },
align: symbol_entry.align, align: if ignore_alignment { None } else { symbol_entry.align },
..Default::default() ..Default::default()
}, },
true, true,

View File

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

305
src/util/nlzss.rs Normal file
View File

@ -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
}

26
src/util/path.rs Normal file
View File

@ -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(),
))
}

View File

@ -1,425 +1,283 @@
// Source: https://github.com/Julgodis/picori/blob/650da9f4fe6050b39b80d5360416591c748058d5/src/rarc.rs use std::{borrow::Cow, ffi::CStr};
// 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 anyhow::{anyhow, bail, ensure, Result}; use typed_path::Utf8UnixPath;
use zerocopy::{big_endian::*, FromBytes, Immutable, IntoBytes, KnownLayout};
use crate::util::{ use crate::{static_assert, vfs::next_non_empty};
file::read_c_string,
reader::{struct_size, Endian, FromReader},
};
#[derive(Debug, Clone)]
pub struct NamedHash {
pub name: String,
pub hash: u16,
}
impl Display for NamedHash {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.name)
}
}
impl Hash for NamedHash {
fn hash<H>(&self, state: &mut H)
where H: Hasher {
self.hash.hash(state);
}
}
impl PartialEq for NamedHash {
fn eq(&self, other: &Self) -> bool {
if self.hash == other.hash {
self.name == other.name
} else {
false
}
}
}
impl Eq for NamedHash {}
#[derive(Debug, Clone)]
enum RarcDirectory {
File {
/// Name of the file.
name: NamedHash,
/// Offset of the file in the RARC file. This offset is relative to the start of the RARC file.
offset: u64,
/// Size of the file.
size: u32,
},
Folder {
/// Name of the folder.
name: NamedHash,
},
CurrentFolder,
ParentFolder,
}
#[derive(Debug, Clone)]
struct RarcNode {
/// Index of first directory.
pub index: u32,
/// Number of directories.
pub count: u32,
}
pub struct RarcReader {
directories: Vec<RarcDirectory>,
nodes: HashMap<NamedHash, RarcNode>,
root_node: NamedHash,
}
pub const RARC_MAGIC: [u8; 4] = *b"RARC"; 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], magic: [u8; 4],
_file_length: u32, /// Length of the RARC file.
header_length: u32, file_len: U32,
file_offset: u32, /// Length of the header. (Always 32)
_file_length_2: u32, header_len: U32,
_unk0: u32, /// Start of the file data, relative to the end of the file header.
_unk1: u32, data_offset: U32,
_unk2: u32, /// Length of the file data.
node_count: u32, data_len: U32,
node_offset: u32, _unk1: U32,
directory_count: u32, _unk2: U32,
directory_offset: u32, _unk3: U32,
string_table_length: u32,
string_table_offset: u32,
_file_count: u16,
_unk3: u16,
_unk4: u32,
} }
impl FromReader for RarcHeader { static_assert!(size_of::<RarcHeader>() == 0x20);
type Args = ();
const STATIC_SIZE: usize = struct_size([ impl RarcHeader {
4, // magic /// Length of the RARC file.
u32::STATIC_SIZE, // file_length pub fn file_len(&self) -> u32 { self.file_len.get() }
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
]);
fn from_reader_args<R>(reader: &mut R, e: Endian, _args: Self::Args) -> io::Result<Self> /// Length of the header.
where R: Read + Seek + ?Sized { pub fn header_len(&self) -> u32 { self.header_len.get() }
let header = Self {
magic: <[u8; 4]>::from_reader(reader, e)?, /// Start of the file data, relative to the end of the file header.
_file_length: u32::from_reader(reader, e)?, pub fn data_offset(&self) -> u32 { self.data_offset.get() }
header_length: u32::from_reader(reader, e)?,
file_offset: u32::from_reader(reader, e)?, /// Length of the file data.
_file_length_2: u32::from_reader(reader, e)?, pub fn data_len(&self) -> u32 { self.data_len.get() }
_unk0: u32::from_reader(reader, e)?, }
_unk1: u32::from_reader(reader, e)?,
_unk2: u32::from_reader(reader, e)?, #[derive(Copy, Clone, Debug, PartialEq, FromBytes, IntoBytes, Immutable, KnownLayout)]
node_count: u32::from_reader(reader, e)?, #[repr(C, align(4))]
node_offset: u32::from_reader(reader, e)?, struct RarcInfo {
directory_count: u32::from_reader(reader, e)?, /// Number of directories in the directory table.
directory_offset: u32::from_reader(reader, e)?, directory_count: U32,
string_table_length: u32::from_reader(reader, e)?, /// Offset to the start of the directory table, relative to the end of the file header.
string_table_offset: u32::from_reader(reader, e)?, directory_offset: U32,
_file_count: u16::from_reader(reader, e)?, /// Number of nodes in the node table.
_unk3: u16::from_reader(reader, e)?, node_count: U32,
_unk4: u32::from_reader(reader, e)?, /// 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 { if header.magic != RARC_MAGIC {
return Err(io::Error::new( return Err("RARC magic mismatch");
io::ErrorKind::InvalidData,
format!("invalid RARC magic: {:?}", header.magic),
));
} }
if header.node_count >= 0x10000 { if header.header_len.get() as usize != size_of::<RarcHeader>() {
return Err(io::Error::new( return Err("RARC header size mismatch");
io::ErrorKind::InvalidData,
format!("invalid node count: {}", header.node_count),
));
} }
if header.directory_count >= 0x10000 {
return Err(io::Error::new( // All offsets are relative to the _end_ of the header, so we can
io::ErrorKind::InvalidData, // just trim the header from the buffer and use the offsets as is.
format!("invalid directory count: {}", header.directory_count), 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 { /// Get a string from the string table at the given offset.
index: u16, pub fn get_string(&self, offset: u32) -> Result<Cow<str>, String> {
name_hash: u16, let name_buf = self.string_table.get(offset as usize..).ok_or_else(|| {
_unk0: u16, // 0x200 for folders, 0x1100 for files format!(
name_offset: u16, "RARC: name offset {} out of bounds (string table size: {})",
data_offset: u32, offset,
data_length: u32, self.string_table.len()
_unk1: u32, )
} })?;
let c_string = CStr::from_bytes_until_nul(name_buf)
impl FromReader for RarcFileNode { .map_err(|_| format!("RARC: name at offset {} not null-terminated", offset))?;
type Args = (); Ok(c_string.to_string_lossy())
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)?,
})
} }
}
struct RarcDirectoryNode { /// Get the data for a file node.
_identifier: u32, pub fn get_data(&self, node: RarcNode) -> Result<&[u8], &'static str> {
name_offset: u32, if node.is_dir() {
name_hash: u16, return Err("Cannot get data for a directory node");
count: u16, }
index: u32, 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 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)?,
})
} }
}
impl RarcReader { /// Finds a particular file or directory by path.
/// Creates a new RARC reader. pub fn find(&self, path: &Utf8UnixPath) -> Option<RarcNodeKind> {
pub fn new<R>(reader: &mut R) -> Result<Self> let mut split = path.as_str().split('/');
where R: Read + Seek + ?Sized { let mut current = next_non_empty(&mut split);
let base = reader.stream_position()?;
let header = RarcHeader::from_reader(reader, Endian::Big)?;
let base = base + header.header_length as u64; let mut dir_idx = 0;
let directory_base = base + header.directory_offset as u64; let mut dir = self.directories[dir_idx];
let data_base = base + header.file_offset as u64; // Allow matching the root directory by name optionally
let mut directories = Vec::with_capacity(header.directory_count as usize); if let Ok(root_name) = self.get_string(dir.name_offset()) {
for i in 0..header.directory_count { if root_name.eq_ignore_ascii_case(current) {
reader.seek(SeekFrom::Start(directory_base + 20 * i as u64))?; current = next_non_empty(&mut split);
let node = RarcFileNode::from_reader(reader, Endian::Big)?;
let name = {
let offset = header.string_table_offset as u64;
let offset = offset + node.name_offset as u64;
ensure!(
(node.name_offset as u32) < header.string_table_length,
"invalid string table offset"
);
read_c_string(reader, base + offset)
}?;
if node.index == 0xFFFF {
if name == "." {
directories.push(RarcDirectory::CurrentFolder);
} else if name == ".." {
directories.push(RarcDirectory::ParentFolder);
} else {
directories.push(RarcDirectory::Folder {
name: NamedHash { name, hash: node.name_hash },
});
}
} else {
directories.push(RarcDirectory::File {
name: NamedHash { name, hash: node.name_hash },
offset: data_base + node.data_offset as u64,
size: node.data_length,
});
} }
} }
if current.is_empty() {
let node_base = base + header.node_offset as u64; return Some(RarcNodeKind::Directory(dir_idx, dir));
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 { let mut idx = dir.index.get() as usize;
Ok(Self { directories, nodes, root_node }) while idx < dir.index.get() as usize + dir.count.get() as usize {
} else { let node = self.nodes.get(idx).copied()?;
Err(anyhow!("no root node")) let Ok(name) = self.get_string(node.name_offset()) else {
} idx += 1;
} continue;
};
/// Get a iterator over the nodes in the RARC file. if name.eq_ignore_ascii_case(current) {
pub fn nodes(&self) -> Nodes<'_> { current = next_non_empty(&mut split);
let root_node = self.root_node.clone(); if node.is_dir() {
Nodes { parent: self, stack: vec![NodeState::Begin(root_node)] } dir_idx = node.data_offset.get() as usize;
} dir = self.directories.get(dir_idx).cloned()?;
idx = dir.index.get() as usize;
/// Find a file in the RARC file. if current.is_empty() {
pub fn find_file<P>(&self, path: P) -> Result<Option<(u64, u32)>> return Some(RarcNodeKind::Directory(dir_idx, dir));
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 { } else {
self.stack.push(NodeState::File(name.clone(), index + 1)); continue;
}
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 { } else {
None return Some(RarcNodeKind::File(idx, node));
} }
} }
idx += 1;
} }
None
}
/// Get the children of a directory.
pub fn children(&self, dir: RarcDirectory) -> &[RarcNode] {
let start = dir.node_index() as usize;
let end = start + dir.node_count() as usize;
self.nodes.get(start..end).unwrap_or(&[])
} }
} }
#[derive(Debug)]
pub enum RarcNodeKind {
File(usize, RarcNode),
Directory(usize, RarcDirectory),
}

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
use std::{ use std::{
cmp::{max, min, Ordering}, cmp::{max, min, Ordering},
collections::{BTreeMap, HashMap, HashSet}, collections::{btree_map, BTreeMap, HashMap, HashSet},
}; };
use anyhow::{anyhow, bail, ensure, Context, Result}; use anyhow::{anyhow, bail, ensure, Context, Result};
@ -15,7 +15,7 @@ use crate::{
obj::{ obj::{
ObjArchitecture, ObjInfo, ObjKind, ObjReloc, ObjRelocations, ObjSection, ObjSectionKind, ObjArchitecture, ObjInfo, ObjKind, ObjReloc, ObjRelocations, ObjSection, ObjSectionKind,
ObjSplit, ObjSymbol, ObjSymbolFlagSet, ObjSymbolFlags, ObjSymbolKind, ObjSymbolScope, ObjSplit, ObjSymbol, ObjSymbolFlagSet, ObjSymbolFlags, ObjSymbolKind, ObjSymbolScope,
ObjUnit, ObjUnit, SectionIndex, SymbolIndex,
}, },
util::{align_up, comment::MWComment}, 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. /// 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<()> { fn update_common_splits(obj: &mut ObjInfo, common_start: Option<u32>) -> Result<()> {
let Some((bss_section_index, common_bss_start)) = (match common_start { let Some(common_bss_start) = (match common_start {
Some(addr) => Some(( Some(addr) => Some(SectionAddress::new(
obj.sections.by_name(".bss")?.ok_or_else(|| anyhow!("Failed to find .bss section"))?.0, obj.sections.by_name(".bss")?.ok_or_else(|| anyhow!("Failed to find .bss section"))?.0,
addr, addr,
)), )),
@ -454,8 +454,8 @@ fn update_common_splits(obj: &mut ObjInfo, common_start: Option<u32>) -> Result<
return Ok(()); return Ok(());
}; };
log::debug!("Found common BSS start at {:#010X}", common_bss_start); log::debug!("Found common BSS start at {:#010X}", common_bss_start);
let bss_section = &mut obj.sections[bss_section_index]; let bss_section = &mut obj.sections[common_bss_start.section];
for (addr, split) in bss_section.splits.for_range_mut(common_bss_start..) { for (addr, split) in bss_section.splits.for_range_mut(common_bss_start.address..) {
if !split.common { if !split.common {
split.common = true; split.common = true;
log::debug!("Added common flag to split {} at {:#010X}", split.unit, addr); 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(); .peekable();
while let Some((_, symbol)) = iter.next() { while let Some((_, symbol)) = iter.next() {
// Common BSS is allowed to have gaps and overlaps to accurately match the common BSS inflation bug // 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 if matches!(common_bss, Some(addr) if
section_index == idx && symbol.address as u32 >= addr) section_index == addr.section && symbol.address as u32 >= addr.address)
{ {
continue; continue;
} }
@ -758,26 +758,32 @@ fn trim_linker_generated_symbols(obj: &mut ObjInfo) -> Result<()> {
pub fn update_splits(obj: &mut ObjInfo, common_start: Option<u32>, fill_gaps: bool) -> Result<()> { pub fn update_splits(obj: &mut ObjInfo, common_start: Option<u32>, fill_gaps: bool) -> Result<()> {
// Create splits for extab and extabindex entries // Create splits for extab and extabindex entries
if let Some((section_index, section)) = obj.sections.by_name("extabindex")? { if let Some((section_index, section)) = obj.sections.by_name("extabindex")? {
let start = SectionAddress::new(section_index, section.address as u32); if !section.data.is_empty() {
split_extabindex(obj, start)?; let start = SectionAddress::new(section_index, section.address as u32);
split_extabindex(obj, start)?;
}
} }
// Create splits for .ctors entries // Create splits for .ctors entries
if let Some((section_index, section)) = obj.sections.by_name(".ctors")? { if let Some((section_index, section)) = obj.sections.by_name(".ctors")? {
let start = SectionAddress::new(section_index, section.address as u32); if !section.data.is_empty() {
let end = start + (section.size as u32 - 4); let start = SectionAddress::new(section_index, section.address as u32);
split_ctors_dtors(obj, start, end)?; let end = start + (section.size as u32 - 4);
split_ctors_dtors(obj, start, end)?;
}
} }
// Create splits for .dtors entries // Create splits for .dtors entries
if let Some((section_index, section)) = obj.sections.by_name(".dtors")? { if let Some((section_index, section)) = obj.sections.by_name(".dtors")? {
let mut start = SectionAddress::new(section_index, section.address as u32); if !section.data.is_empty() {
let end = start + (section.size as u32 - 4); let mut start = SectionAddress::new(section_index, section.address as u32);
if obj.kind == ObjKind::Executable { let end = start + (section.size as u32 - 4);
// Skip __destroy_global_chain_reference if obj.kind == ObjKind::Executable {
start += 4; // Skip __destroy_global_chain_reference
start += 4;
}
split_ctors_dtors(obj, start, end)?;
} }
split_ctors_dtors(obj, start, end)?;
} }
// Remove linker generated symbols from splits // Remove linker generated symbols from splits
@ -816,8 +822,8 @@ fn resolve_link_order(obj: &ObjInfo) -> Result<Vec<ObjUnit>> {
#[allow(dead_code)] #[allow(dead_code)]
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
struct SplitEdge { struct SplitEdge {
from: u32, from: i64,
to: u32, to: i64,
} }
let mut graph = Graph::<String, SplitEdge>::new(); 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 a_index = *unit_to_index_map.get(&a.unit).unwrap();
let b_index = *unit_to_index_map.get(&b.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::{ // use petgraph::{
// dot::{Config, Dot}, // dot::{Config, Dot},
// graph::EdgeReference, // graph::EdgeReference,
@ -886,6 +917,7 @@ fn resolve_link_order(obj: &ObjInfo) -> Result<Vec<ObjUnit>> {
name: name.clone(), name: name.clone(),
autogenerated: obj.is_unit_autogenerated(name), autogenerated: obj.is_unit_autogenerated(name),
comment_version: None, comment_version: None,
order: None,
} }
} }
}) })
@ -901,11 +933,11 @@ fn resolve_link_order(obj: &ObjInfo) -> Result<Vec<ObjUnit>> {
#[instrument(level = "debug", skip(obj))] #[instrument(level = "debug", skip(obj))]
pub fn split_obj(obj: &ObjInfo, module_name: Option<&str>) -> Result<Vec<ObjInfo>> { pub fn split_obj(obj: &ObjInfo, module_name: Option<&str>) -> Result<Vec<ObjInfo>> {
let mut objects: Vec<ObjInfo> = vec![]; 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(); let mut name_to_obj: HashMap<String, usize> = HashMap::new();
for unit in &obj.link_order { for unit in &obj.link_order {
name_to_obj.insert(unit.name.clone(), objects.len()); 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( let mut split_obj = ObjInfo::new(
ObjKind::Relocatable, ObjKind::Relocatable,
ObjArchitecture::PowerPc, 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) 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? continue; // should never happen?
} }
@ -1060,7 +1092,7 @@ pub fn split_obj(obj: &ObjInfo, module_name: Option<&str>) -> Result<Vec<ObjInfo
continue; 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(), name: symbol.name.clone(),
demangled_name: symbol.demangled_name.clone(), demangled_name: symbol.demangled_name.clone(),
address: if split.common { 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, data_kind: symbol.data_kind,
name_hash: symbol.name_hash, name_hash: symbol.name_hash,
demangled_name_hash: symbol.demangled_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 // 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]; let symbol_idxs = &mut object_symbols[obj_idx];
for (_section_index, section) in out_obj.sections.iter_mut() { for (_section_index, section) in out_obj.sections.iter_mut() {
for (reloc_address, reloc) in section.relocations.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) => { Some(out_sym_idx) => {
reloc.target_symbol = 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)); 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 { out_obj.symbols.add_direct(ObjSymbol {
name: target_sym.name.clone(), name: target_sym.name.clone(),
demangled_name: target_sym.demangled_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 // Upgrade local symbols to global if necessary
for (obj, symbol_map) in objects.iter_mut().zip(&object_symbols) { for (obj, symbol_map) in objects.iter_mut().zip(&object_symbols) {
for (globalize_idx, new_name) in &globalize_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(); let mut symbol = obj.symbols[symbol_idx].clone();
symbol.name.clone_from(new_name); symbol.name.clone_from(new_name);
if symbol.flags.is_local() { 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 // Extern linker generated symbols
for obj in &mut objects { for obj in &mut objects {
let mut replace_symbols = vec![]; 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() { if is_linker_generated_label(&symbol.name) && symbol.section.is_some() {
log::debug!("Externing {:?} in {}", symbol, obj.name); log::debug!("Externing {:?} in {}", symbol, obj.name);
replace_symbols.push((symbol_idx, ObjSymbol { 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 /// 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 let section = obj
.sections .sections
.get(section_index) .get(section_index)
.ok_or_else(|| anyhow!("Invalid section index: {}", section_index))?; .ok_or_else(|| anyhow!("Invalid section index: {}", section_index))?;
let mut section_end = (section.address + section.size) as u32; 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, // .ctors and .dtors end with a linker-generated null pointer,
// adjust section size appropriately // adjust section size appropriately
if matches!(section.name.as_str(), ".ctors" | ".dtors") if matches!(section.name.as_str(), ".ctors" | ".dtors")

View File

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

130
src/vfs/common.rs Normal file
View File

@ -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 }
}

337
src/vfs/disc.rs Normal file
View File

@ -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),
}
}

373
src/vfs/mod.rs Normal file
View File

@ -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 "",
}
}
}

78
src/vfs/rarc.rs Normal file
View File

@ -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),
}
}
}

108
src/vfs/std_fs.rs Normal file
View File

@ -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 }
}

91
src/vfs/u8_arc.rs Normal file
View File

@ -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),
}
}
}