Compare commits

...

10 Commits

Author SHA1 Message Date
Luke Street 4dacf2f39a Add disc commands: info, extract, convert, verify
Utilizing https://github.com/encounter/nod-rs
2024-05-01 00:12:20 -06:00
Luke Street 9452ca8b8c Pass ppc750cl::Ins by value 2024-04-30 22:53:32 -06:00
Luke Street 963425793d Remove unused deps 2024-04-30 20:48:53 -06:00
Luke Street c45f37eb10 Update ppc750cl (10x faster!) & upgrade deps 2024-04-30 20:40:14 -06:00
Luke Street c1c4373e53 Prefer references to `_savegpr_14` over `__savegpr`
Same for `__restore_gpr`, `__save_fpr` and `__restore_fpr`.
A common issue that can be solved with a little bit
of special-casing.
2024-04-30 18:04:50 -06:00
Luke Street dc7e307c44 Rework section alignment handling
- Honor splits.txt alignment values when writing ldscript.lcf
- Add alignment values to ldscript_partial.lcf, remove hacky code from rel make
- Guess alignment values in DOL loader

Fixes #27
2024-04-30 18:04:17 -06:00
Luke Street dac2dcfc9e Explicitly check split end >= start
Fixes #48
2024-04-30 18:03:59 -06:00
Luke Street fdafe59e13 Adds `data:int` and `data:short` for asm output
Fixes #41
2024-04-30 18:03:45 -06:00
Luke Street bfa926ebbf Write object address and size in asm comments
Fixes #37
2024-04-30 18:03:31 -06:00
Luke Street 989293a477 Add Yay0/Yaz0 compression & decompression
Uses orthrus-ncompress

Fixes #6
2024-04-30 18:03:00 -06:00
33 changed files with 1085 additions and 492 deletions

471
Cargo.lock generated
View File

@ -17,6 +17,17 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "aes"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
dependencies = [
"cfg-if",
"cipher",
"cpufeatures",
]
[[package]]
name = "ahash"
version = "0.8.10"
@ -49,9 +60,9 @@ dependencies = [
[[package]]
name = "anyhow"
version = "1.0.81"
version = "1.0.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247"
checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519"
dependencies = [
"backtrace",
]
@ -122,9 +133,9 @@ checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf"
[[package]]
name = "base64"
version = "0.22.0"
version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "bincode"
@ -166,13 +177,12 @@ dependencies = [
]
[[package]]
name = "bstr"
version = "1.8.0"
name = "block-padding"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "542f33a8835a0884b006a0c3df3dadd99c0c3f296ed26c2fdc8028e01ad6230c"
checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93"
dependencies = [
"memchr",
"serde",
"generic-array",
]
[[package]]
@ -181,11 +191,45 @@ version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "bzip2"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8"
dependencies = [
"bzip2-sys",
"libc",
]
[[package]]
name = "bzip2-sys"
version = "0.1.11+1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc"
dependencies = [
"cc",
"libc",
"pkg-config",
]
[[package]]
name = "cbc"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6"
dependencies = [
"cipher",
]
[[package]]
name = "cc"
version = "1.0.90"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5"
dependencies = [
"jobserver",
"libc",
]
[[package]]
name = "cfg-if"
@ -193,6 +237,16 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "cipher"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
dependencies = [
"crypto-common",
"inout",
]
[[package]]
name = "clap"
version = "2.34.0"
@ -209,10 +263,23 @@ dependencies = [
]
[[package]]
name = "cpufeatures"
version = "0.2.11"
name = "console"
version = "0.15.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0"
checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb"
dependencies = [
"encode_unicode",
"lazy_static",
"libc",
"unicode-width",
"windows-sys 0.52.0",
]
[[package]]
name = "cpufeatures"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504"
dependencies = [
"libc",
]
@ -277,12 +344,10 @@ dependencies = [
"base16ct",
"base64",
"cwdemangle",
"dol",
"enable-ansi-support",
"filetime",
"fixedbitset 0.5.0",
"fixedbitset 0.5.7",
"flagset",
"flate2",
"glob",
"hex",
"indent",
@ -293,10 +358,12 @@ dependencies = [
"memmap2",
"multimap",
"nintendo-lz",
"nodtool",
"num_enum",
"objdiff-core",
"object 0.34.0",
"object 0.35.0",
"once_cell",
"orthrus-ncompress",
"owo-colors",
"path-slash",
"petgraph",
@ -310,7 +377,6 @@ dependencies = [
"serde_repr",
"serde_yaml",
"sha-1",
"smallvec",
"supports-color 3.0.0",
"syntect",
"tracing",
@ -330,14 +396,10 @@ dependencies = [
]
[[package]]
name = "dol"
version = "0.1.0"
source = "git+https://github.com/encounter/ppc750cl?rev=4a2bbbc6f84dcb76255ab6f3595a8d4a0ce96618#4a2bbbc6f84dcb76255ab6f3595a8d4a0ce96618"
dependencies = [
"bincode",
"serde",
"thiserror",
]
name = "dyn-clone"
version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125"
[[package]]
name = "either"
@ -354,6 +416,21 @@ dependencies = [
"windows-sys 0.42.0",
]
[[package]]
name = "encode_unicode"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
[[package]]
name = "encoding_rs"
version = "0.8.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59"
dependencies = [
"cfg-if",
]
[[package]]
name = "equivalent"
version = "1.0.1"
@ -390,9 +467,9 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "fixedbitset"
version = "0.5.0"
version = "0.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b51ee430d5ff16df7998870eb0b4775383ac5bc450f5a2ed547394fe2d617131"
checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99"
[[package]]
name = "flagset"
@ -405,9 +482,9 @@ dependencies = [
[[package]]
name = "flate2"
version = "1.0.28"
version = "1.0.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e"
checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae"
dependencies = [
"crc32fast",
"miniz_oxide",
@ -450,20 +527,6 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
[[package]]
name = "globset"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1"
dependencies = [
"aho-corasick",
"bstr",
"log",
"regex-automata 0.4.6",
"regex-syntax 0.8.2",
"serde",
]
[[package]]
name = "hashbrown"
version = "0.14.3"
@ -473,6 +536,12 @@ dependencies = [
"ahash",
]
[[package]]
name = "heck"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]]
name = "hermit-abi"
version = "0.1.19"
@ -493,6 +562,9 @@ name = "hex"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
dependencies = [
"serde",
]
[[package]]
name = "indent"
@ -502,14 +574,46 @@ checksum = "d9f1a0777d972970f204fdf8ef319f1f4f8459131636d7e3c96c5d59570d0fa6"
[[package]]
name = "indexmap"
version = "2.2.5"
version = "2.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4"
checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26"
dependencies = [
"equivalent",
"hashbrown",
]
[[package]]
name = "indicatif"
version = "0.17.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "763a5a8f45087d6bcea4222e7b72c291a054edf80e4ef6efd2a4979878c7bea3"
dependencies = [
"console",
"instant",
"number_prefix",
"portable-atomic",
"unicode-width",
]
[[package]]
name = "inout"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5"
dependencies = [
"block-padding",
"generic-array",
]
[[package]]
name = "instant"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
dependencies = [
"cfg-if",
]
[[package]]
name = "is-terminal"
version = "0.4.12"
@ -542,6 +646,15 @@ version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
[[package]]
name = "jobserver"
version = "0.1.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e"
dependencies = [
"libc",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
@ -554,6 +667,26 @@ version = "0.2.153"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
[[package]]
name = "liblzma"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "599133771f99c14ca089a8db3a4565f482ea6eeb66991b262bffc2b72acff69c"
dependencies = [
"liblzma-sys",
]
[[package]]
name = "liblzma-sys"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be9aaba5f9c8f8f615d41570909338b6284fbb1813dc057ecc68563d98a65097"
dependencies = [
"cc",
"libc",
"pkg-config",
]
[[package]]
name = "log"
version = "0.4.21"
@ -570,10 +703,20 @@ dependencies = [
]
[[package]]
name = "memchr"
version = "2.7.1"
name = "md-5"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf"
dependencies = [
"cfg-if",
"digest",
]
[[package]]
name = "memchr"
version = "2.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d"
[[package]]
name = "memmap2"
@ -612,6 +755,58 @@ dependencies = [
"clap",
]
[[package]]
name = "nod"
version = "1.2.0"
source = "git+https://github.com/encounter/nod-rs?rev=03b83484cb17f94408fa0ef8e50d94951464d1b2#03b83484cb17f94408fa0ef8e50d94951464d1b2"
dependencies = [
"adler",
"aes",
"base16ct",
"bzip2",
"cbc",
"digest",
"dyn-clone",
"encoding_rs",
"itertools",
"liblzma",
"log",
"miniz_oxide",
"rayon",
"sha1",
"thiserror",
"zerocopy",
"zstd",
]
[[package]]
name = "nodtool"
version = "1.2.0"
source = "git+https://github.com/encounter/nod-rs?rev=03b83484cb17f94408fa0ef8e50d94951464d1b2#03b83484cb17f94408fa0ef8e50d94951464d1b2"
dependencies = [
"argp",
"base16ct",
"crc32fast",
"digest",
"enable-ansi-support",
"hex",
"indicatif",
"itertools",
"log",
"md-5",
"nod",
"quick-xml",
"serde",
"sha1",
"size",
"supports-color 3.0.0",
"tracing",
"tracing-attributes",
"tracing-subscriber",
"xxhash-rust",
"zerocopy",
]
[[package]]
name = "nu-ansi-term"
version = "0.46.0"
@ -652,26 +847,28 @@ dependencies = [
"syn 2.0.52",
]
[[package]]
name = "number_prefix"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
[[package]]
name = "objdiff-core"
version = "1.0.0"
source = "git+https://github.com/encounter/objdiff?rev=a5668b484b3db9e85d2aa8aeb84b37bff6077df6#a5668b484b3db9e85d2aa8aeb84b37bff6077df6"
source = "git+https://github.com/encounter/objdiff?rev=2c46286affd11fe37f620ba919a3e57c4b80ccdb#2c46286affd11fe37f620ba919a3e57c4b80ccdb"
dependencies = [
"anyhow",
"byteorder",
"cwdemangle",
"filetime",
"flagset",
"globset",
"log",
"memmap2",
"num-traits",
"object 0.34.0",
"object 0.35.0",
"ppc750cl",
"semver",
"serde",
"serde_json",
"serde_yaml",
"similar",
]
@ -686,9 +883,9 @@ dependencies = [
[[package]]
name = "object"
version = "0.34.0"
version = "0.35.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7090bae93f8585aad99e595b7073c5de9ba89fbd6b4e9f0cdd7a10177273ac8"
checksum = "b8ec7ab813848ba4522158d5517a6093db1ded27575b070f4177b8d12b41db5e"
dependencies = [
"crc32fast",
"hashbrown",
@ -702,6 +899,25 @@ version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
name = "orthrus-core"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad3db74dec173db0794aadee37558a5ca1f944ed0bd0c340bbad7103af0dc06a"
dependencies = [
"snafu",
]
[[package]]
name = "orthrus-ncompress"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "770b9c12ef43e3204d9c5e4e77ed5fcd48a08a104b6ba17a3a10a0dc975deb07"
dependencies = [
"orthrus-core",
"snafu",
]
[[package]]
name = "overload"
version = "0.1.1"
@ -739,14 +955,22 @@ version = "0.2.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
[[package]]
name = "pkg-config"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
[[package]]
name = "portable-atomic"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0"
[[package]]
name = "ppc750cl"
version = "0.2.0"
source = "git+https://github.com/encounter/ppc750cl?rev=4a2bbbc6f84dcb76255ab6f3595a8d4a0ce96618#4a2bbbc6f84dcb76255ab6f3595a8d4a0ce96618"
dependencies = [
"num-traits",
"serde",
]
version = "0.3.0"
source = "git+https://github.com/encounter/ppc750cl?rev=6cbd7d888c7082c2c860f66cbb9848d633f753ed#6cbd7d888c7082c2c860f66cbb9848d633f753ed"
[[package]]
name = "proc-macro-crate"
@ -778,6 +1002,16 @@ dependencies = [
"unicase",
]
[[package]]
name = "quick-xml"
version = "0.31.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33"
dependencies = [
"memchr",
"serde",
]
[[package]]
name = "quote"
version = "1.0.35"
@ -789,9 +1023,9 @@ dependencies = [
[[package]]
name = "rayon"
version = "1.9.0"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4963ed1bc86e4f3ee217022bd855b297cef07fb9eac5dfa1f788b220b49b3bd"
checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa"
dependencies = [
"either",
"rayon-core",
@ -818,9 +1052,9 @@ dependencies = [
[[package]]
name = "regex"
version = "1.10.3"
version = "1.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15"
checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c"
dependencies = [
"aho-corasick",
"memchr",
@ -893,26 +1127,20 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19d36299972b96b8ae7e8f04ecbf75fb41a27bf3781af00abcf57609774cb911"
[[package]]
name = "semver"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca"
[[package]]
name = "serde"
version = "1.0.197"
version = "1.0.199"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2"
checksum = "0c9f6e76df036c77cd94996771fb40db98187f096dd0b9af39c6c6e452ba966a"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.197"
version = "1.0.199"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
checksum = "11bd257a6541e141e42ca6d24ae26f7714887b47e89aa739099104c7e4d3b7fc"
dependencies = [
"proc-macro2",
"quote",
@ -921,9 +1149,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.114"
version = "1.0.116"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0"
checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813"
dependencies = [
"itoa",
"ryu",
@ -932,9 +1160,9 @@ dependencies = [
[[package]]
name = "serde_repr"
version = "0.1.18"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b2e6b945e9d3df726b65d6ee24060aff8e3533d431f677a9695db04eff9dfdb"
checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9"
dependencies = [
"proc-macro2",
"quote",
@ -943,9 +1171,9 @@ dependencies = [
[[package]]
name = "serde_yaml"
version = "0.9.32"
version = "0.9.34+deprecated"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fd075d994154d4a774f95b51fb96bdc2832b0ea48425c92546073816cda1f2f"
checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
dependencies = [
"indexmap",
"itoa",
@ -965,6 +1193,17 @@ dependencies = [
"digest",
]
[[package]]
name = "sha1"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
dependencies = [
"cfg-if",
"cpufeatures",
"digest",
]
[[package]]
name = "sharded-slab"
version = "0.1.7"
@ -976,15 +1215,42 @@ dependencies = [
[[package]]
name = "similar"
version = "2.4.0"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32fea41aca09ee824cc9724996433064c89f7777e60762749a4170a14abbfa21"
checksum = "fa42c91313f1d05da9b26f267f931cf178d4aba455b4c4622dd7355eb80c6640"
[[package]]
name = "size"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fed904c7fb2856d868b92464fc8fa597fce366edea1a9cbfaa8cb5fe080bd6d"
[[package]]
name = "smallvec"
version = "1.13.1"
version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7"
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
[[package]]
name = "snafu"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75976f4748ab44f6e5332102be424e7c2dc18daeaf7e725f2040c3ebb133512e"
dependencies = [
"snafu-derive",
]
[[package]]
name = "snafu-derive"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4b19911debfb8c2fb1107bc6cb2d61868aaf53a988449213959bb1b5b1ed95f"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn 2.0.52",
]
[[package]]
name = "strsim"
@ -1199,9 +1465,9 @@ checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
[[package]]
name = "unsafe-libyaml"
version = "0.2.10"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab4c90930b95a82d00dc9e9ac071b4991924390d46cbd0dfe566148667605e4b"
checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
[[package]]
name = "valuable"
@ -1406,6 +1672,7 @@ version = "0.7.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be"
dependencies = [
"byteorder",
"zerocopy-derive",
]
@ -1419,3 +1686,31 @@ dependencies = [
"quote",
"syn 2.0.52",
]
[[package]]
name = "zstd"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d789b1514203a1120ad2429eae43a7bd32b90976a7bb8a05f7ec02fa88cc23a"
dependencies = [
"zstd-safe",
]
[[package]]
name = "zstd-safe"
version = "7.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cd99b45c6bc03a018c8b8a86025678c87e55526064e38f9df301989dce7ec0a"
dependencies = [
"zstd-sys",
]
[[package]]
name = "zstd-sys"
version = "2.0.10+zstd.1.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c253a4914af5bafc8fa8c86ee400827e83cf6ec01195ec1f1ed8441bf00d65aa"
dependencies = [
"cc",
"pkg-config",
]

View File

@ -24,47 +24,47 @@ lto = "thin"
strip = "debuginfo"
[dependencies]
anyhow = { version = "1.0.81", features = ["backtrace"] }
anyhow = { version = "1.0.82", features = ["backtrace"] }
ar = { git = "https://github.com/bjorn3/rust-ar.git", branch = "write_symbol_table" }
argp = "0.3.0"
base16ct = "0.2.0"
base64 = "0.22.0"
base64 = "0.22.1"
cwdemangle = "1.0.0"
dol = { git = "https://github.com/encounter/ppc750cl", rev = "4a2bbbc6f84dcb76255ab6f3595a8d4a0ce96618" }
enable-ansi-support = "0.2.1"
filetime = "0.2.23"
fixedbitset = "0.5.0"
fixedbitset = "0.5.7"
flagset = { version = "0.4.5", features = ["serde"] }
flate2 = "1.0.28"
glob = "0.3.1"
hex = "0.4.3"
indent = "0.1.1"
indexmap = "2.2.5"
indexmap = "2.2.6"
itertools = "0.12.1"
log = "0.4.21"
memchr = "2.7.1"
memchr = "2.7.2"
memmap2 = "0.9.4"
multimap = "0.10.0"
nintendo-lz = "0.1.3"
nodtool = { git = "https://github.com/encounter/nod-rs", rev = "03b83484cb17f94408fa0ef8e50d94951464d1b2" }
#nodtool = { path = "../nod-rs/nodtool" }
num_enum = "0.7.2"
objdiff-core = { git = "https://github.com/encounter/objdiff", rev = "a5668b484b3db9e85d2aa8aeb84b37bff6077df6", features = ["ppc"] }
objdiff-core = { git = "https://github.com/encounter/objdiff", rev = "2c46286affd11fe37f620ba919a3e57c4b80ccdb", features = ["ppc"] }
#objdiff-core = { path = "../objdiff/objdiff-core", features = ["ppc"] }
object = { version = "0.34.0", features = ["read_core", "std", "elf", "write_std"], default-features = false }
object = { version = "0.35.0", features = ["read_core", "std", "elf", "write_std"], default-features = false }
once_cell = "1.19.0"
orthrus-ncompress = "0.2.0"
owo-colors = { version = "4.0.0", features = ["supports-colors"] }
path-slash = "0.2.1"
petgraph = { version = "0.6.4", default-features = false }
ppc750cl = { git = "https://github.com/encounter/ppc750cl", rev = "4a2bbbc6f84dcb76255ab6f3595a8d4a0ce96618" }
rayon = "1.9.0"
regex = "1.10.3"
ppc750cl = { git = "https://github.com/encounter/ppc750cl", rev = "6cbd7d888c7082c2c860f66cbb9848d633f753ed" }
rayon = "1.10.0"
regex = "1.10.4"
rustc-hash = "1.1.0"
sanitise-file-name = "1.0.0"
serde = "1.0.197"
serde_json = "1.0.114"
serde_repr = "0.1.18"
serde_yaml = "0.9.32"
serde = "1.0.199"
serde_json = "1.0.116"
serde_repr = "0.1.19"
serde_yaml = "0.9.34"
sha-1 = "0.10.1"
smallvec = "1.13.1"
supports-color = "3.0.0"
syntect = { version = "5.2.0", features = ["parsing", "regex-fancy", "dump-load"], default-features = false }
tracing = "0.1.40"

View File

@ -22,6 +22,10 @@ project structure and build system that uses decomp-toolkit under the hood.
- [ar create](#ar-create)
- [ar extract](#ar-extract)
- [demangle](#demangle)
- [disc info](#disc-info)
- [disc extract](#disc-extract)
- [disc convert](#disc-convert)
- [disc verify](#disc-verify)
- [dol info](#dol-info)
- [dol split](#dol-split)
- [dol diff](#dol-diff)
@ -39,7 +43,10 @@ project structure and build system that uses decomp-toolkit under the hood.
- [nlzss decompress](#nlzss-decompress)
- [rarc list](#rarc-list)
- [rarc extract](#rarc-extract)
- [yay0 decompress](#yay0-decompress)
- [yay0 compress](#yay0-compress)
- [yaz0 decompress](#yaz0-decompress)
- [yaz0 compress](#yaz0-compress)
## Goals
@ -164,6 +171,62 @@ $ dtk demangle 'BuildLight__9CGuiLightCFv'
CGuiLight::BuildLight() const
```
### disc info
_`disc` commands are wrappers around the [nod](https://github.com/encounter/nod-rs) library
and its `nodtool` command line tool._
Displays information about disc images.
Supported disc image formats:
- ISO (GCM)
- WIA / RVZ
- WBFS (+ NKit 2 lossless)
- CISO (+ NKit 2 lossless)
- NFS (Wii U VC)
- GCZ
```shell
$ dtk disc info /path/to/game.iso
```
### disc extract
Extracts the contents of disc images to a directory.
See [disc info](#disc-info) for supported formats.
```shell
$ dtk disc extract /path/to/game.iso [outdir]
```
By default, only the main **data** partition is extracted.
Use the `-p`/`--partition` option to choose a different partition.
(Options: `all`, `data`, `update`, `channel`, or a partition index)
### disc convert
Converts any supported disc image to raw ISO (GCM).
If the format is lossless, the output will be identical to the original disc image.
See [disc info](#disc-info) for supported formats.
```shell
$ dtk disc convert /path/to/game.wia /path/to/game.iso
```
### disc verify
Hashes the contents of a disc image and verifies it against a built-in [Redump](http://redump.org/) database.
See [disc info](#disc-info) for supported formats.
```shell
$ dtk disc verify /path/to/game.iso
```
### dol info
Analyzes a DOL file and outputs information section and symbol information.
@ -341,6 +404,26 @@ Extracts the contents of an RARC archive.
$ dtk rarc extract input.arc -o output_dir
```
### yay0 decompress
Decompresses Yay0-compressed files.
```shell
$ dtk yay0 decompress input.bin.yay0 -o output.bin
# or, for batch processing
$ dtk yay0 decompress rels/*.yay0 -o rels
```
### yay0 compress
Compresses files using Yay0 compression.
```shell
$ dtk yay0 compress input.bin -o output.bin.yay0
# or, for batch processing
$ dtk yay0 compress rels/* -o rels
```
### yaz0 decompress
Decompresses Yaz0-compressed files.
@ -350,3 +433,13 @@ $ dtk yaz0 decompress input.bin.yaz0 -o output.bin
# or, for batch processing
$ dtk yaz0 decompress rels/*.yaz0 -o rels
```
### yaz0 compress
Compresses files using Yaz0 compression.
```shell
$ dtk yaz0 compress input.bin -o output.bin.yaz0
# or, for batch processing
$ dtk yaz0 compress rels/* -o rels
```

View File

@ -2,13 +2,7 @@ SECTIONS
{
GROUP:
{
.init :{}
.text :{}
.ctors :{}
.dtors :{}
.rodata :{}
.data :{ *(.data) *(extabindex) *(extab) }
.bss :{}
$SECTIONS
}
}

View File

@ -499,12 +499,12 @@ pub fn locate_sda_bases(obj: &mut ObjInfo) -> Result<bool> {
executor.push(entry_addr, VM::new(), false);
let result = executor.run(
obj,
|ExecCbData { executor, vm, result, ins_addr: _, section: _, ins, block_start: _ }| {
|ExecCbData { executor, vm, result, ins_addr, section: _, ins: _, block_start: _ }| {
match result {
StepResult::Continue | StepResult::LoadStore { .. } => {
return Ok(ExecCbResult::Continue);
}
StepResult::Illegal => bail!("Illegal instruction @ {:#010X}", ins.addr),
StepResult::Illegal => bail!("Illegal instruction @ {}", ins_addr),
StepResult::Jump(target) => {
if let BranchTarget::Address(RelocationTarget::Address(addr)) = target {
return Ok(ExecCbResult::Jump(addr));
@ -558,10 +558,10 @@ pub fn locate_bss_memsets(obj: &mut ObjInfo) -> Result<Vec<(u32, u32)>> {
executor.push(entry_addr, VM::new(), false);
executor.run(
obj,
|ExecCbData { executor: _, vm, result, ins_addr: _, section: _, ins, block_start: _ }| {
|ExecCbData { executor: _, vm, result, ins_addr, section: _, ins: _, block_start: _ }| {
match result {
StepResult::Continue | StepResult::LoadStore { .. } => Ok(ExecCbResult::Continue),
StepResult::Illegal => bail!("Illegal instruction @ {:#010X}", ins.addr),
StepResult::Illegal => bail!("Illegal instruction @ {}", ins_addr),
StepResult::Jump(_target) => Ok(ExecCbResult::End(())),
StepResult::Branch(branches) => {
for branch in branches {

View File

@ -62,7 +62,7 @@ pub struct ExecCbData<'a> {
pub result: StepResult,
pub ins_addr: SectionAddress,
pub section: &'a ObjSection,
pub ins: &'a Ins,
pub ins: Ins,
pub block_start: SectionAddress,
}
@ -109,14 +109,14 @@ impl Executor {
Some(ins) => ins,
None => return Ok(None),
};
let result = state.vm.step(obj, state.address, &ins);
let result = state.vm.step(obj, state.address, ins);
match cb(ExecCbData {
executor: self,
vm: &mut state.vm,
result,
ins_addr: state.address,
section,
ins: &ins,
ins,
block_start,
})? {
ExecCbResult::Continue => {

View File

@ -19,7 +19,7 @@ pub mod tracker;
pub mod vm;
pub fn disassemble(section: &ObjSection, address: u32) -> Option<Ins> {
read_u32(section, address).map(|code| Ins::new(code, address))
read_u32(section, address).map(Ins::new)
}
pub fn read_u32(section: &ObjSection, address: u32) -> Option<u32> {

View File

@ -19,8 +19,8 @@ pub fn detect_objects(obj: &mut ObjInfo) -> Result<()> {
}
let expected_size = match symbol.data_kind {
ObjDataKind::Byte => 1,
ObjDataKind::Byte2 => 2,
ObjDataKind::Byte4 | ObjDataKind::Float => 4,
ObjDataKind::Byte2 | ObjDataKind::Short => 2,
ObjDataKind::Byte4 | ObjDataKind::Float | ObjDataKind::Int => 4,
ObjDataKind::Byte8 | ObjDataKind::Double => 8,
_ => 0,
};

View File

@ -43,54 +43,61 @@ pub enum TailCallResult {
type BlockRange = Range<SectionAddress>;
#[inline(always)]
fn next_ins(section: &ObjSection, ins: &Ins) -> Option<Ins> { disassemble(section, ins.addr + 4) }
type InsCheck = dyn Fn(&Ins) -> bool;
type InsCheck = dyn Fn(Ins) -> bool;
#[inline(always)]
fn check_sequence(
section: &ObjSection,
ins: &Ins,
addr: SectionAddress,
ins: Option<Ins>,
sequence: &[(&InsCheck, &InsCheck)],
) -> Result<bool> {
let mut found = false;
for &(first, second) in sequence {
if first(ins) {
if let Some(next) = next_ins(section, ins) {
if second(&next)
// Also check the following instruction, in case the scheduler
// put something in between.
|| (!next.is_branch() && matches!(next_ins(section, &next), Some(ins) if second(&ins)))
{
found = true;
break;
}
}
let Some(ins) = ins.or_else(|| disassemble(section, addr.address)) else {
continue;
};
if !first(ins) {
continue;
}
let Some(next) = disassemble(section, addr.address + 4) else {
continue;
};
if second(next)
// Also check the following instruction, in case the scheduler
// put something in between.
|| (!next.is_branch()
&& matches!(disassemble(section, addr.address + 8), Some(ins) if second(ins)))
{
found = true;
break;
}
}
Ok(found)
}
fn check_prologue_sequence(section: &ObjSection, ins: &Ins) -> Result<bool> {
fn check_prologue_sequence(
section: &ObjSection,
addr: SectionAddress,
ins: Option<Ins>,
) -> Result<bool> {
#[inline(always)]
fn is_mflr(ins: &Ins) -> bool {
fn is_mflr(ins: Ins) -> bool {
// mfspr r0, LR
ins.op == Opcode::Mfspr && ins.field_rD() == 0 && ins.field_spr() == 8
ins.op == Opcode::Mfspr && ins.field_rd() == 0 && ins.field_spr() == 8
}
#[inline(always)]
fn is_stwu(ins: &Ins) -> bool {
fn is_stwu(ins: Ins) -> bool {
// stwu r1, d(r1)
ins.op == Opcode::Stwu && ins.field_rS() == 1 && ins.field_rA() == 1
ins.op == Opcode::Stwu && ins.field_rs() == 1 && ins.field_ra() == 1
}
#[inline(always)]
fn is_stw(ins: &Ins) -> bool {
fn is_stw(ins: Ins) -> bool {
// stw r0, d(r1)
ins.op == Opcode::Stw && ins.field_rS() == 0 && ins.field_rA() == 1
ins.op == Opcode::Stw && ins.field_rs() == 0 && ins.field_ra() == 1
}
check_sequence(section, ins, &[(&is_stwu, &is_mflr), (&is_mflr, &is_stw)])
check_sequence(section, addr, ins, &[(&is_stwu, &is_mflr), (&is_mflr, &is_stw)])
}
impl FunctionSlices {
@ -127,19 +134,19 @@ impl FunctionSlices {
&mut self,
section: &ObjSection,
addr: SectionAddress,
ins: &Ins,
ins: Ins,
) -> Result<()> {
#[inline(always)]
fn is_lwz(ins: &Ins) -> bool {
fn is_lwz(ins: Ins) -> bool {
// lwz r1, d(r)
ins.op == Opcode::Lwz && ins.field_rD() == 1
ins.op == Opcode::Lwz && ins.field_rd() == 1
}
if is_lwz(ins) {
self.has_r1_load = true;
return Ok(()); // Possibly instead of a prologue
}
if check_prologue_sequence(section, ins)? {
if check_prologue_sequence(section, addr, Some(ins))? {
if let Some(prologue) = self.prologue {
if prologue != addr && prologue != addr - 4 {
bail!("Found duplicate prologue: {:#010X} and {:#010X}", prologue, addr)
@ -155,25 +162,25 @@ impl FunctionSlices {
&mut self,
section: &ObjSection,
addr: SectionAddress,
ins: &Ins,
ins: Ins,
) -> Result<()> {
#[inline(always)]
fn is_mtlr(ins: &Ins) -> bool {
fn is_mtlr(ins: Ins) -> bool {
// mtspr LR, r0
ins.op == Opcode::Mtspr && ins.field_rS() == 0 && ins.field_spr() == 8
ins.op == Opcode::Mtspr && ins.field_rs() == 0 && ins.field_spr() == 8
}
#[inline(always)]
fn is_addi(ins: &Ins) -> bool {
fn is_addi(ins: Ins) -> bool {
// addi r1, r1, SIMM
ins.op == Opcode::Addi && ins.field_rD() == 1 && ins.field_rA() == 1
ins.op == Opcode::Addi && ins.field_rd() == 1 && ins.field_ra() == 1
}
#[inline(always)]
fn is_or(ins: &Ins) -> bool {
fn is_or(ins: Ins) -> bool {
// or r1, rA, rB
ins.op == Opcode::Or && ins.field_rD() == 1
ins.op == Opcode::Or && ins.field_rd() == 1
}
if check_sequence(section, ins, &[(&is_mtlr, &is_addi), (&is_or, &is_mtlr)])? {
if check_sequence(section, addr, Some(ins), &[(&is_mtlr, &is_addi), (&is_or, &is_mtlr)])? {
if let Some(epilogue) = self.epilogue {
if epilogue != addr {
bail!("Found duplicate epilogue: {:#010X} and {:#010X}", epilogue, addr)
@ -443,7 +450,7 @@ impl FunctionSlices {
// Skip nops
match disassemble(&obj.sections[end.section], end.address) {
Some(ins) => {
if !is_nop(&ins) {
if !is_nop(ins) {
break;
}
}
@ -513,11 +520,12 @@ impl FunctionSlices {
{
// FIXME this is real bad
if !self.has_conditional_blr {
if let Some(ins) = disassemble(section, end.address - 4) {
let ins_addr = end - 4;
if let Some(ins) = disassemble(section, ins_addr.address) {
if ins.op == Opcode::B {
if let Some(RelocationTarget::Address(target)) = ins
.branch_dest()
.and_then(|addr| section_address_for(obj, end - 4, addr))
.branch_dest(ins_addr.address)
.and_then(|addr| section_address_for(obj, ins_addr, addr))
{
if self.function_references.contains(&target) {
for branches in self.branches.values() {
@ -548,7 +556,7 @@ impl FunctionSlices {
// Some functions with rfi also include a trailing nop
if self.has_rfi
&& matches!(disassemble(section, end.address), Some(ins) if is_nop(&ins))
&& matches!(disassemble(section, end.address), Some(ins) if is_nop(ins))
&& !known_functions.contains_key(&end)
{
log::trace!("Found trailing nop @ {:#010X}, merging with function", end);
@ -617,8 +625,7 @@ impl FunctionSlices {
if self.prologue.is_none() {
let mut current_address = function_start;
while current_address < addr {
let ins = disassemble(target_section, current_address.address).unwrap();
match check_prologue_sequence(target_section, &ins) {
match check_prologue_sequence(target_section, current_address, None) {
Ok(true) => {
log::debug!(
"Prologue discovered @ {}; known tail call: {}",
@ -702,12 +709,12 @@ impl FunctionSlices {
}
#[inline]
fn is_conditional_blr(ins: &Ins) -> bool {
ins.op == Opcode::Bclr && ins.field_BO() & 0b10100 != 0b10100
fn is_conditional_blr(ins: Ins) -> bool {
ins.op == Opcode::Bclr && ins.field_bo() & 0b10100 != 0b10100
}
#[inline]
fn is_nop(ins: &Ins) -> bool {
fn is_nop(ins: Ins) -> bool {
// ori r0, r0, 0
ins.code == 0x60000000
}

View File

@ -219,8 +219,8 @@ impl Tracker {
match ins.op {
// addi rD, rA, SIMM
Opcode::Addi | Opcode::Addic | Opcode::Addic_ => {
let source = ins.field_rA();
let target = ins.field_rD();
let source = ins.field_ra() as usize;
let target = ins.field_rd() as usize;
if let Some(value) = self.gpr_address(obj, ins_addr, &vm.gpr[target].value)
{
if (source == 2
@ -258,7 +258,7 @@ impl Tracker {
}
// ori rA, rS, UIMM
Opcode::Ori => {
let target = ins.field_rA();
let target = ins.field_ra() as usize;
if let Some(value) = self.gpr_address(obj, ins_addr, &vm.gpr[target].value)
{
if let (Some(hi_addr), Some(lo_addr)) =

View File

@ -192,15 +192,15 @@ impl VM {
#[inline]
pub fn clone_all(&self) -> Box<Self> { Box::new(self.clone()) }
pub fn step(&mut self, obj: &ObjInfo, ins_addr: SectionAddress, ins: &Ins) -> StepResult {
pub fn step(&mut self, obj: &ObjInfo, ins_addr: SectionAddress, ins: Ins) -> StepResult {
match ins.op {
Opcode::Illegal => {
return StepResult::Illegal;
}
// add rD, rA, rB
Opcode::Add => {
let left = self.gpr[ins.field_rA()].value;
let right = self.gpr[ins.field_rB()].value;
let left = self.gpr[ins.field_ra() as usize].value;
let right = self.gpr[ins.field_rb() as usize].value;
let value = match (left, right) {
(GprValue::Constant(left), GprValue::Constant(right)) => {
GprValue::Constant(left.wrapping_add(right))
@ -215,20 +215,20 @@ impl VM {
) => GprValue::Address(RelocationTarget::Address(right + left)),
_ => GprValue::Unknown,
};
self.gpr[ins.field_rD()].set_direct(value);
self.gpr[ins.field_rd() as usize].set_direct(value);
}
// addis rD, rA, SIMM
Opcode::Addis => {
if let Some(target) =
relocation_target_for(obj, ins_addr, None /* TODO */).ok().flatten()
{
debug_assert_eq!(ins.field_rA(), 0);
self.gpr[ins.field_rD()].set_hi(GprValue::Address(target), ins_addr);
debug_assert_eq!(ins.field_ra(), 0);
self.gpr[ins.field_rd() as usize].set_hi(GprValue::Address(target), ins_addr);
} else {
let left = if ins.field_rA() == 0 {
let left = if ins.field_ra() == 0 {
GprValue::Constant(0)
} else {
self.gpr[ins.field_rA()].value
self.gpr[ins.field_ra() as usize].value
};
let value = match left {
GprValue::Constant(value) => {
@ -236,11 +236,11 @@ impl VM {
}
_ => GprValue::Unknown,
};
if ins.field_rA() == 0 {
if ins.field_ra() == 0 {
// lis rD, SIMM
self.gpr[ins.field_rD()].set_hi(value, ins_addr);
self.gpr[ins.field_rd() as usize].set_hi(value, ins_addr);
} else {
self.gpr[ins.field_rD()].set_direct(value);
self.gpr[ins.field_rd() as usize].set_direct(value);
}
}
}
@ -251,16 +251,16 @@ impl VM {
if let Some(target) =
relocation_target_for(obj, ins_addr, None /* TODO */).ok().flatten()
{
self.gpr[ins.field_rD()].set_lo(
self.gpr[ins.field_rd() as usize].set_lo(
GprValue::Address(target),
ins_addr,
self.gpr[ins.field_rA()],
self.gpr[ins.field_ra() as usize],
);
} else {
let left = if ins.field_rA() == 0 && ins.op == Opcode::Addi {
let left = if ins.field_ra() == 0 && ins.op == Opcode::Addi {
GprValue::Constant(0)
} else {
self.gpr[ins.field_rA()].value
self.gpr[ins.field_ra() as usize].value
};
let value = match left {
GprValue::Constant(value) => {
@ -271,19 +271,26 @@ impl VM {
),
_ => GprValue::Unknown,
};
if ins.field_rA() == 0 {
if ins.field_ra() == 0 {
// li rD, SIMM
self.gpr[ins.field_rD()].set_direct(value);
self.gpr[ins.field_rd() as usize].set_direct(value);
} else {
self.gpr[ins.field_rD()].set_lo(value, ins_addr, self.gpr[ins.field_rA()]);
self.gpr[ins.field_rd() as usize].set_lo(
value,
ins_addr,
self.gpr[ins.field_ra() as usize],
);
}
}
}
// subf rD, rA, rB
// subfc rD, rA, rB
Opcode::Subf | Opcode::Subfc => {
self.gpr[ins.field_rD()].set_direct(
match (self.gpr[ins.field_rA()].value, self.gpr[ins.field_rB()].value) {
self.gpr[ins.field_rd() as usize].set_direct(
match (
self.gpr[ins.field_ra() as usize].value,
self.gpr[ins.field_rb() as usize].value,
) {
(GprValue::Constant(left), GprValue::Constant(right)) => {
GprValue::Constant((!left).wrapping_add(right).wrapping_add(1))
}
@ -293,48 +300,54 @@ impl VM {
}
// subfic rD, rA, SIMM
Opcode::Subfic => {
self.gpr[ins.field_rD()].set_direct(match self.gpr[ins.field_rA()].value {
GprValue::Constant(value) => GprValue::Constant(
(!value).wrapping_add(ins.field_simm() as u32).wrapping_add(1),
),
_ => GprValue::Unknown,
});
self.gpr[ins.field_rd() as usize].set_direct(
match self.gpr[ins.field_ra() as usize].value {
GprValue::Constant(value) => GprValue::Constant(
(!value).wrapping_add(ins.field_simm() as u32).wrapping_add(1),
),
_ => GprValue::Unknown,
},
);
}
// ori rA, rS, UIMM
Opcode::Ori => {
if let Some(target) =
relocation_target_for(obj, ins_addr, None /* TODO */).ok().flatten()
{
self.gpr[ins.field_rA()].set_lo(
self.gpr[ins.field_ra() as usize].set_lo(
GprValue::Address(target),
ins_addr,
self.gpr[ins.field_rS()],
self.gpr[ins.field_rs() as usize],
);
} else {
let value = match self.gpr[ins.field_rS()].value {
let value = match self.gpr[ins.field_rs() as usize].value {
GprValue::Constant(value) => {
GprValue::Constant(value | ins.field_uimm() as u32)
}
_ => GprValue::Unknown,
};
self.gpr[ins.field_rA()].set_lo(value, ins_addr, self.gpr[ins.field_rS()]);
self.gpr[ins.field_ra() as usize].set_lo(
value,
ins_addr,
self.gpr[ins.field_rs() as usize],
);
}
}
// or rA, rS, rB
Opcode::Or => {
if ins.field_rS() == ins.field_rB() {
if ins.field_rs() == ins.field_rb() {
// Register copy
self.gpr[ins.field_rA()] = self.gpr[ins.field_rS()];
self.gpr[ins.field_ra() as usize] = self.gpr[ins.field_rs() as usize];
} else {
let left = self.gpr[ins.field_rS()].value;
let right = self.gpr[ins.field_rB()].value;
let left = self.gpr[ins.field_rs() as usize].value;
let right = self.gpr[ins.field_rb() as usize].value;
let value = match (left, right) {
(GprValue::Constant(left), GprValue::Constant(right)) => {
GprValue::Constant(left | right)
}
_ => GprValue::Unknown,
};
self.gpr[ins.field_rA()].set_direct(value);
self.gpr[ins.field_ra() as usize].set_direct(value);
}
}
// cmp [crfD], [L], rA, rB
@ -342,34 +355,34 @@ impl VM {
// cmpl [crfD], [L], rA, rB
// cmpli [crfD], [L], rA, UIMM
Opcode::Cmp | Opcode::Cmpi | Opcode::Cmpl | Opcode::Cmpli => {
if ins.field_L() == 0 {
let left_reg = ins.field_rA();
if ins.field_l() == 0 {
let left_reg = ins.field_ra() as usize;
let left = self.gpr[left_reg].value;
let (right, signed) = match ins.op {
Opcode::Cmp => (self.gpr[ins.field_rB()].value, true),
Opcode::Cmpl => (self.gpr[ins.field_rB()].value, false),
Opcode::Cmp => (self.gpr[ins.field_rb() as usize].value, true),
Opcode::Cmpl => (self.gpr[ins.field_rb() as usize].value, false),
Opcode::Cmpi => (GprValue::Constant(ins.field_simm() as u32), true),
Opcode::Cmpli => (GprValue::Constant(ins.field_uimm() as u32), false),
_ => unreachable!(),
};
let crf = ins.field_crfD();
self.cr[crf] = Cr { signed, left, right };
self.gpr[left_reg].value = GprValue::ComparisonResult(crf as u8);
let crf = ins.field_crfd();
self.cr[crf as usize] = Cr { signed, left, right };
self.gpr[left_reg].value = GprValue::ComparisonResult(crf);
}
}
// rlwinm rA, rS, SH, MB, ME
// rlwnm rA, rS, rB, MB, ME
Opcode::Rlwinm | Opcode::Rlwnm => {
let value = if let Some(shift) = match ins.op {
Opcode::Rlwinm => Some(ins.field_SH() as u32),
Opcode::Rlwnm => match self.gpr[ins.field_rB()].value {
Opcode::Rlwinm => Some(ins.field_sh() as u32),
Opcode::Rlwnm => match self.gpr[ins.field_rb() as usize].value {
GprValue::Constant(value) => Some(value),
_ => None,
},
_ => unreachable!(),
} {
let mask = mask_value(ins.field_MB() as u32, ins.field_ME() as u32);
match self.gpr[ins.field_rS()].value {
let mask = mask_value(ins.field_mb() as u32, ins.field_me() as u32);
match self.gpr[ins.field_rs() as usize].value {
GprValue::Constant(value) => {
GprValue::Constant(value.rotate_left(shift) & mask)
}
@ -383,7 +396,7 @@ impl VM {
} else {
GprValue::Unknown
};
self.gpr[ins.field_rA()].set_direct(value);
self.gpr[ins.field_ra() as usize].set_direct(value);
}
// b[l][a] target_addr
// b[c][l][a] BO, BI, target_addr
@ -391,7 +404,7 @@ impl VM {
// b[c]lr[l] BO, BI
Opcode::B | Opcode::Bc | Opcode::Bcctr | Opcode::Bclr => {
// HACK for `bla 0x60` in __OSDBJump
if ins.op == Opcode::B && ins.field_LK() && ins.field_AA() {
if ins.op == Opcode::B && ins.field_lk() && ins.field_aa() {
return StepResult::Jump(BranchTarget::Unknown);
}
@ -409,7 +422,7 @@ impl VM {
GprValue::Address(target) => BranchTarget::Address(target),
GprValue::LoadIndexed { address, max_offset }
// FIXME: avoids treating bctrl indirect calls as jump tables
if !ins.field_LK() => {
if !ins.field_lk() => {
BranchTarget::JumpTable { address, size: max_offset.and_then(|n| n.checked_add(4)) }
}
_ => BranchTarget::Unknown,
@ -417,7 +430,7 @@ impl VM {
}
Opcode::Bclr => BranchTarget::Return,
_ => {
let value = ins.branch_dest().unwrap();
let value = ins.branch_dest(ins_addr.address).unwrap();
if let Some(target) = section_address_for(obj, ins_addr, value) {
BranchTarget::Address(target)
} else {
@ -427,7 +440,7 @@ impl VM {
};
// If branching with link, use function call semantics
if ins.field_LK() {
if ins.field_lk() {
return StepResult::Branch(vec![
Branch {
target: BranchTarget::Address(RelocationTarget::Address(ins_addr + 4)),
@ -439,7 +452,7 @@ impl VM {
}
// Branch always
if ins.op == Opcode::B || ins.field_BO() & 0b10100 == 0b10100 {
if ins.op == Opcode::B || ins.field_bo() & 0b10100 == 0b10100 {
return StepResult::Jump(branch_target);
}
@ -452,19 +465,19 @@ impl VM {
vm: self.clone_all(),
},
// Branch taken
Branch { target: branch_target, link: ins.field_LK(), vm: self.clone_all() },
Branch { target: branch_target, link: ins.field_lk(), vm: self.clone_all() },
];
// Use tracked CR to calculate new register values for branches
let crf = ins.field_BI() >> 2;
let crb = (ins.field_BI() & 3) as u8;
let crf = (ins.field_bi() >> 2) as usize;
let crb = ins.field_bi() & 3;
let (f_val, t_val) =
split_values_by_crb(crb, self.cr[crf].left, self.cr[crf].right);
if ins.field_BO() & 0b11110 == 0b00100 {
if ins.field_bo() & 0b11110 == 0b00100 {
// Branch if false
branches[0].vm.set_comparison_result(t_val, crf);
branches[1].vm.set_comparison_result(f_val, crf);
} else if ins.field_BO() & 0b11110 == 0b01100 {
} else if ins.field_bo() & 0b11110 == 0b01100 {
// Branch if true
branches[0].vm.set_comparison_result(f_val, crf);
branches[1].vm.set_comparison_result(t_val, crf);
@ -474,8 +487,8 @@ impl VM {
}
// lwzx rD, rA, rB
Opcode::Lwzx => {
let left = self.gpr[ins.field_rA()].address(obj, ins_addr);
let right = self.gpr[ins.field_rB()].value;
let left = self.gpr[ins.field_ra() as usize].address(obj, ins_addr);
let right = self.gpr[ins.field_rb() as usize].value;
let value = match (left, right) {
(Some(address), GprValue::Range { min: _, max, .. })
if /*min == 0 &&*/ max < u32::MAX - 4 && max & 3 == 0 =>
@ -492,12 +505,12 @@ impl VM {
}
_ => GprValue::Unknown,
};
self.gpr[ins.field_rD()].set_direct(value);
self.gpr[ins.field_rd() as usize].set_direct(value);
}
// mtspr SPR, rS
Opcode::Mtspr => match ins.field_spr() {
8 => self.lr = self.gpr[ins.field_rS()].value,
9 => self.ctr = self.gpr[ins.field_rS()].value,
8 => self.lr = self.gpr[ins.field_rs() as usize].value,
9 => self.ctr = self.gpr[ins.field_rs() as usize].value,
_ => {}
},
// mfspr rD, SPR
@ -507,14 +520,14 @@ impl VM {
9 => self.ctr,
_ => GprValue::Unknown,
};
self.gpr[ins.field_rD()].set_direct(value);
self.gpr[ins.field_rd() as usize].set_direct(value);
}
// rfi
Opcode::Rfi => {
return StepResult::Jump(BranchTarget::Unknown);
}
op if is_load_store_op(op) => {
let source = ins.field_rA();
let source = ins.field_ra() as usize;
let mut result = StepResult::Continue;
if let GprValue::Address(target) = self.gpr[source].value {
if is_update_op(op) {
@ -549,13 +562,13 @@ impl VM {
self.gpr[source].set_direct(GprValue::Unknown);
}
if is_load_op(op) {
self.gpr[ins.field_rD()].set_direct(GprValue::Unknown);
self.gpr[ins.field_rd() as usize].set_direct(GprValue::Unknown);
}
return result;
}
_ => {
for field in ins.defs() {
if let Some(Argument::GPR(GPR(reg))) = field.argument() {
for argument in ins.defs() {
if let Argument::GPR(GPR(reg)) = argument {
self.gpr[reg as usize].set_direct(GprValue::Unknown);
}
}

View File

@ -9,7 +9,7 @@ use anyhow::{anyhow, bail, Context, Result};
use argp::FromArgs;
use object::{Object, ObjectSymbol, SymbolScope};
use crate::util::file::{buf_writer, map_file, process_rsp};
use crate::util::file::{buf_writer, map_file, map_file_basic, process_rsp};
#[derive(FromArgs, PartialEq, Debug)]
/// Commands for processing static libraries.
@ -80,7 +80,7 @@ fn create(args: CreateArgs) -> Result<()> {
Entry::Vacant(e) => e.insert(Vec::new()),
Entry::Occupied(_) => bail!("Duplicate file name '{path_str}'"),
};
let file = map_file(path)?;
let file = map_file_basic(path)?;
let obj = object::File::parse(file.as_slice())?;
for symbol in obj.symbols() {
if symbol.scope() == SymbolScope::Dynamic {

13
src/cmd/disc.rs Normal file
View File

@ -0,0 +1,13 @@
use anyhow::{Error, Result};
use argp::FromArgs;
use nodtool::SubCommand;
#[derive(FromArgs, Debug)]
/// Commands for processing disc images.
#[argp(subcommand, name = "disc")]
pub struct Args {
#[argp(subcommand)]
command: SubCommand,
}
pub fn run(args: Args) -> Result<()> { nodtool::run(args.command).map_err(Error::new) }

View File

@ -46,7 +46,10 @@ use crate::{
dep::DepFile,
dol::process_dol,
elf::{process_elf, write_elf},
file::{buf_reader, buf_writer, map_file, touch, verify_hash, FileIterator, FileReadInfo},
file::{
buf_reader, buf_writer, map_file, map_file_basic, touch, verify_hash, FileIterator,
FileReadInfo,
},
lcf::{asm_path_for_unit, generate_ldscript, obj_path_for_unit},
map::apply_map_file,
rel::{process_rel, process_rel_header, update_rel_section_alignment},
@ -156,7 +159,7 @@ mod path_slash_serde {
use std::path::PathBuf;
use path_slash::PathBufExt as _;
use serde::{self, Deserialize, Deserializer, Serializer};
use serde::{Deserialize, Deserializer, Serializer};
pub fn serialize<S>(path: &PathBuf, s: S) -> Result<S::Ok, S::Error>
where S: Serializer {
@ -174,7 +177,7 @@ mod path_slash_serde_option {
use std::path::PathBuf;
use path_slash::PathBufExt as _;
use serde::{self, Deserialize, Deserializer, Serializer};
use serde::{Deserialize, Deserializer, Serializer};
pub fn serialize<S>(path: &Option<PathBuf>, s: S) -> Result<S::Ok, S::Error>
where S: Serializer {
@ -938,7 +941,7 @@ fn split_write_obj(
fn write_if_changed(path: &Path, contents: &[u8]) -> Result<()> {
if path.is_file() {
let old_file = map_file(path)?;
let old_file = map_file_basic(path)?;
// If the file is the same size, check if the contents are the same
// Avoid writing if unchanged, since it will update the file's mtime
if old_file.len() == contents.len() as u64
@ -1639,7 +1642,7 @@ fn apply(args: ApplyArgs) -> Result<()> {
orig_sym.kind,
linked_sym.name
);
updated_sym.name = linked_sym.name.clone();
updated_sym.name.clone_from(&linked_sym.name);
}
if linked_sym.size != orig_sym.size {
log::info!(

View File

@ -1,6 +1,7 @@
pub mod alf;
pub mod ar;
pub mod demangle;
pub mod disc;
pub mod dol;
pub mod dwarf;
pub mod elf;
@ -12,4 +13,5 @@ pub mod rarc;
pub mod rel;
pub mod rso;
pub mod shasum;
pub mod yay0;
pub mod yaz0;

View File

@ -30,7 +30,7 @@ use crate::{
cmd::dol::{ModuleConfig, ProjectConfig},
obj::{ObjInfo, ObjReloc, ObjRelocKind, ObjSection, ObjSectionKind, ObjSymbol},
util::{
config::{is_auto_symbol, read_splits_sections, SectionDef},
config::is_auto_symbol,
dol::process_dol,
elf::{to_obj_reloc_kind, write_elf},
file::{buf_reader, buf_writer, map_file, process_rsp, verify_hash, FileIterator},
@ -170,12 +170,7 @@ fn load_rel(module_config: &ModuleConfig) -> Result<RelInfo> {
let mut reader = file.as_reader();
let header = process_rel_header(&mut reader)?;
let sections = process_rel_sections(&mut reader, &header)?;
let section_defs = if let Some(splits_path) = &module_config.splits {
read_splits_sections(splits_path)?
} else {
None
};
Ok((header, sections, section_defs))
Ok((header, sections))
}
fn resolve_relocations(
@ -191,12 +186,11 @@ fn resolve_relocations(
if !matches!(section.name(), Ok(name) if PERMITTED_SECTIONS.contains(&name)) {
continue;
}
let section_index =
if let Some((_, sections, _)) = existing_headers.get(&(module_id as u32)) {
match_section_index(module, section.index(), sections)?
} else {
section.index().0
} as u8;
let section_index = if let Some((_, sections)) = existing_headers.get(&(module_id as u32)) {
match_section_index(module, section.index(), sections)?
} else {
section.index().0
} as u8;
for (address, reloc) in section.relocations() {
let reloc_target = match reloc.target() {
RelocationTarget::Symbol(idx) => {
@ -223,7 +217,7 @@ fn resolve_relocations(
(module_id, reloc_target)
};
let target_section_index = target_symbol.section_index().unwrap();
let target_section = if let Some((_, sections, _)) =
let target_section = if let Some((_, sections)) =
existing_headers.get(&(target_module_id as u32))
{
match_section_index(&modules[target_module_id].0, target_section_index, sections)?
@ -246,7 +240,7 @@ fn resolve_relocations(
Ok(resolved)
}
type RelInfo = (RelHeader, Vec<RelSectionHeader>, Option<Vec<SectionDef>>);
type RelInfo = (RelHeader, Vec<RelSectionHeader>);
fn make(args: MakeArgs) -> Result<()> {
let total = Instant::now();
@ -347,17 +341,13 @@ fn make(args: MakeArgs) -> Result<()> {
quiet: args.no_warn,
section_align: None,
};
if let Some((header, _, section_defs)) = existing_headers.get(&(module_id as u32)) {
if let Some((header, _)) = existing_headers.get(&(module_id as u32)) {
info.version = header.version;
info.name_offset = Some(header.name_offset);
info.name_size = Some(header.name_size);
info.align = header.align;
info.bss_align = header.bss_align;
info.section_count = Some(header.num_sections as usize);
info.section_align = section_defs
.as_ref()
.map(|defs| defs.iter().map(|def| def.align).collect())
.unwrap_or_default();
}
let rel_path = path.with_extension("rel");
let mut w = buf_writer(&rel_path)?;

105
src/cmd/yay0.rs Normal file
View File

@ -0,0 +1,105 @@
use std::{fs, path::PathBuf};
use anyhow::{Context, Result};
use argp::FromArgs;
use crate::util::{
file::{map_file_basic, process_rsp},
ncompress::{compress_yay0, decompress_yay0},
IntoCow, ToCow,
};
#[derive(FromArgs, PartialEq, Debug)]
/// Commands for processing YAY0-compressed files.
#[argp(subcommand, name = "yay0")]
pub struct Args {
#[argp(subcommand)]
command: SubCommand,
}
#[derive(FromArgs, PartialEq, Debug)]
#[argp(subcommand)]
enum SubCommand {
Compress(CompressArgs),
Decompress(DecompressArgs),
}
#[derive(FromArgs, PartialEq, Eq, Debug)]
/// Compresses files using YAY0.
#[argp(subcommand, name = "compress")]
pub struct CompressArgs {
#[argp(positional)]
/// Files to compress
files: Vec<PathBuf>,
#[argp(option, short = 'o')]
/// Output file (or directory, if multiple files are specified).
/// If not specified, compresses in-place.
output: Option<PathBuf>,
}
#[derive(FromArgs, PartialEq, Eq, Debug)]
/// Decompresses YAY0-compressed files.
#[argp(subcommand, name = "decompress")]
pub struct DecompressArgs {
#[argp(positional)]
/// YAY0-compressed files
files: Vec<PathBuf>,
#[argp(option, short = 'o')]
/// Output file (or directory, if multiple files are specified).
/// If not specified, decompresses in-place.
output: Option<PathBuf>,
}
pub fn run(args: Args) -> Result<()> {
match args.command {
SubCommand::Compress(args) => compress(args),
SubCommand::Decompress(args) => decompress(args),
}
}
fn compress(args: CompressArgs) -> Result<()> {
let files = process_rsp(&args.files)?;
let single_file = files.len() == 1;
for path in files {
let data = {
let file = map_file_basic(&path)?;
compress_yay0(file.as_slice())
};
let out_path = if let Some(output) = &args.output {
if single_file {
output.as_path().to_cow()
} else {
output.join(path.file_name().unwrap()).into_cow()
}
} else {
path.as_path().to_cow()
};
fs::write(out_path.as_ref(), data)
.with_context(|| format!("Failed to write '{}'", out_path.display()))?;
}
Ok(())
}
fn decompress(args: DecompressArgs) -> Result<()> {
let files = process_rsp(&args.files)?;
let single_file = files.len() == 1;
for path in files {
let data = {
let file = map_file_basic(&path)?;
decompress_yay0(file.as_slice())
.with_context(|| format!("Failed to decompress '{}' using Yay0", path.display()))?
};
let out_path = if let Some(output) = &args.output {
if single_file {
output.as_path().to_cow()
} else {
output.join(path.file_name().unwrap()).into_cow()
}
} else {
path.as_path().to_cow()
};
fs::write(out_path.as_ref(), data)
.with_context(|| format!("Failed to write '{}'", out_path.display()))?;
}
Ok(())
}

View File

@ -4,7 +4,8 @@ use anyhow::{Context, Result};
use argp::FromArgs;
use crate::util::{
file::{decompress_reader, open_file, process_rsp},
file::{map_file_basic, process_rsp},
ncompress::{compress_yaz0, decompress_yaz0},
IntoCow, ToCow,
};
@ -19,9 +20,23 @@ pub struct Args {
#[derive(FromArgs, PartialEq, Debug)]
#[argp(subcommand)]
enum SubCommand {
Compress(CompressArgs),
Decompress(DecompressArgs),
}
#[derive(FromArgs, PartialEq, Eq, Debug)]
/// Compresses files using YAZ0.
#[argp(subcommand, name = "compress")]
pub struct CompressArgs {
#[argp(positional)]
/// Files to compress
files: Vec<PathBuf>,
#[argp(option, short = 'o')]
/// Output file (or directory, if multiple files are specified).
/// If not specified, compresses in-place.
output: Option<PathBuf>,
}
#[derive(FromArgs, PartialEq, Eq, Debug)]
/// Decompresses YAZ0-compressed files.
#[argp(subcommand, name = "decompress")]
@ -37,15 +52,43 @@ pub struct DecompressArgs {
pub fn run(args: Args) -> Result<()> {
match args.command {
SubCommand::Compress(args) => compress(args),
SubCommand::Decompress(args) => decompress(args),
}
}
fn decompress(args: DecompressArgs) -> Result<()> {
fn compress(args: CompressArgs) -> Result<()> {
let files = process_rsp(&args.files)?;
let single_file = files.len() == 1;
for path in files {
let data = decompress_reader(&mut open_file(&path)?)?;
let data = {
let file = map_file_basic(&path)?;
compress_yaz0(file.as_slice())
};
let out_path = if let Some(output) = &args.output {
if single_file {
output.as_path().to_cow()
} else {
output.join(path.file_name().unwrap()).into_cow()
}
} else {
path.as_path().to_cow()
};
fs::write(out_path.as_ref(), data)
.with_context(|| format!("Failed to write '{}'", out_path.display()))?;
}
Ok(())
}
fn decompress(args: DecompressArgs) -> Result<()> {
let files = process_rsp(&args.files)?;
let single_file = files.len() == 1;
for path in files {
let data = {
let file = map_file_basic(&path)?;
decompress_yaz0(file.as_slice())
.with_context(|| format!("Failed to decompress '{}' using Yaz0", path.display()))?
};
let out_path = if let Some(output) = &args.output {
if single_file {
output.as_path().to_cow()

View File

@ -1,4 +1,4 @@
use std::{env, ffi::OsStr, path::PathBuf, process::exit, str::FromStr};
use std::{env, ffi::OsStr, fmt::Display, path::PathBuf, process::exit, str::FromStr};
use anyhow::Error;
use argp::{FromArgValue, FromArgs};
@ -37,16 +37,15 @@ impl FromStr for LogLevel {
}
}
impl ToString for LogLevel {
fn to_string(&self) -> String {
match self {
impl Display for LogLevel {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
LogLevel::Error => "error",
LogLevel::Warn => "warn",
LogLevel::Info => "info",
LogLevel::Debug => "debug",
LogLevel::Trace => "trace",
}
.to_string()
})
}
}
@ -57,7 +56,7 @@ impl FromArgValue for LogLevel {
}
}
#[derive(FromArgs, PartialEq, Debug)]
#[derive(FromArgs, Debug)]
/// Yet another GameCube/Wii decompilation toolkit.
struct TopLevel {
#[argp(subcommand)]
@ -71,18 +70,20 @@ struct TopLevel {
log_level: Option<LogLevel>,
/// Print version information and exit.
#[argp(switch, short = 'V')]
#[allow(dead_code)]
version: bool,
/// Disable color output. (env: NO_COLOR)
#[argp(switch)]
no_color: bool,
}
#[derive(FromArgs, PartialEq, Debug)]
#[derive(FromArgs, Debug)]
#[argp(subcommand)]
enum SubCommand {
Alf(cmd::alf::Args),
Ar(cmd::ar::Args),
Demangle(cmd::demangle::Args),
Disc(cmd::disc::Args),
Dol(cmd::dol::Args),
Dwarf(cmd::dwarf::Args),
Elf(cmd::elf::Args),
@ -94,6 +95,7 @@ enum SubCommand {
Rel(cmd::rel::Args),
Rso(cmd::rso::Args),
Shasum(cmd::shasum::Args),
Yay0(cmd::yay0::Args),
Yaz0(cmd::yaz0::Args),
}
@ -155,6 +157,7 @@ fn main() {
SubCommand::Alf(c_args) => cmd::alf::run(c_args),
SubCommand::Ar(c_args) => cmd::ar::run(c_args),
SubCommand::Demangle(c_args) => cmd::demangle::run(c_args),
SubCommand::Disc(c_args) => cmd::disc::run(c_args),
SubCommand::Dol(c_args) => cmd::dol::run(c_args),
SubCommand::Dwarf(c_args) => cmd::dwarf::run(c_args),
SubCommand::Elf(c_args) => cmd::elf::run(c_args),
@ -166,6 +169,7 @@ fn main() {
SubCommand::Rel(c_args) => cmd::rel::run(c_args),
SubCommand::Rso(c_args) => cmd::rso::run(c_args),
SubCommand::Shasum(c_args) => cmd::shasum::run(c_args),
SubCommand::Yay0(c_args) => cmd::yay0::run(c_args),
SubCommand::Yaz0(c_args) => cmd::yaz0::run(c_args),
});
if let Err(e) = result {

View File

@ -270,7 +270,7 @@ impl ObjInfo {
existing.end,
split.unit
);
existing.unit = split.unit.clone();
existing.unit.clone_from(&split.unit);
}
}
self.add_split(section_index, new_start, ObjSplit {

View File

@ -172,6 +172,8 @@ pub enum ObjDataKind {
String16,
StringTable,
String16Table,
Int,
Short,
}
#[derive(Debug, Clone, Default, Eq, PartialEq)]
@ -581,15 +583,24 @@ pub fn best_match_for_reloc(
}
symbols.sort_by_key(|&(_, symbol)| {
let mut rank = match symbol.kind {
ObjSymbolKind::Function | ObjSymbolKind::Object => match reloc_kind {
ObjRelocKind::PpcAddr16Hi
| ObjRelocKind::PpcAddr16Ha
| ObjRelocKind::PpcAddr16Lo => 1,
ObjRelocKind::Absolute
| ObjRelocKind::PpcRel24
| ObjRelocKind::PpcRel14
| ObjRelocKind::PpcEmbSda21 => 2,
},
ObjSymbolKind::Function | ObjSymbolKind::Object => {
// HACK: These are generally not referenced directly, so reduce their rank
if matches!(
symbol.name.as_str(),
"__save_gpr" | "__restore_gpr" | "__save_fpr" | "__restore_fpr"
) {
return 0;
}
match reloc_kind {
ObjRelocKind::PpcAddr16Hi
| ObjRelocKind::PpcAddr16Ha
| ObjRelocKind::PpcAddr16Lo => 1,
ObjRelocKind::Absolute
| ObjRelocKind::PpcRel24
| ObjRelocKind::PpcRel14
| ObjRelocKind::PpcEmbSda21 => 2,
}
}
// Label
ObjSymbolKind::Unknown => match reloc_kind {
ObjRelocKind::PpcAddr16Hi

View File

@ -6,7 +6,7 @@ use std::{
use anyhow::{anyhow, bail, ensure, Context, Result};
use itertools::Itertools;
use ppc750cl::{disasm_iter, Argument, Ins, Opcode};
use ppc750cl::{Argument, Ins, InsIter, Opcode};
use crate::{
obj::{
@ -67,15 +67,15 @@ where W: Write + ?Sized {
// Generate local jump labels
if section.kind == ObjSectionKind::Code {
for ins in disasm_iter(&section.data, section.address as u32) {
if let Some(address) = ins.branch_dest() {
if ins.field_AA() || !section.contains(address) {
for (addr, ins) in InsIter::new(&section.data, section.address as u32) {
if let Some(address) = ins.branch_dest(addr) {
if ins.field_aa() || !section.contains(address) {
continue;
}
// Replace section-relative jump relocations (generated by GCC)
// These aren't always possible to express accurately in GNU assembler
if matches!(relocations.get(&ins.addr), Some(reloc) if reloc.addend == 0) {
if matches!(relocations.get(&addr), Some(reloc) if reloc.addend == 0) {
continue;
}
@ -102,7 +102,7 @@ where W: Write + ?Sized {
target_symbol_idx = Some(symbol_idx);
}
if let Some(symbol_idx) = target_symbol_idx {
relocations.insert(ins.addr, ObjReloc {
relocations.insert(addr, ObjReloc {
kind: match ins.op {
Opcode::B => ObjRelocKind::PpcRel24,
Opcode::Bc => ObjRelocKind::PpcRel14,
@ -209,7 +209,7 @@ where W: Write + ?Sized {
)?;
}
ObjSectionKind::Bss => {
write_bss(w, &symbols, entries, current_address, section_end)?;
write_bss(w, &symbols, entries, section, current_address, section_end)?;
}
}
@ -219,7 +219,7 @@ where W: Write + ?Sized {
if entry.kind != SymbolEntryKind::End {
continue;
}
write_symbol_entry(w, &symbols, entry)?;
write_symbol_entry(w, &symbols, entry, section)?;
}
}
@ -243,10 +243,10 @@ fn write_code_chunk<W>(
where
W: Write + ?Sized,
{
for ins in disasm_iter(data, address) {
let reloc = relocations.get(&ins.addr);
let file_offset = section.file_offset + (ins.addr as u64 - section.address);
write_ins(w, symbols, ins, reloc, file_offset, section.virtual_address)?;
for (addr, ins) in InsIter::new(data, address) {
let reloc = relocations.get(&addr);
let file_offset = section.file_offset + (addr as u64 - section.address);
write_ins(w, symbols, addr, ins, reloc, file_offset, section.virtual_address)?;
}
Ok(())
}
@ -254,6 +254,7 @@ where
fn write_ins<W>(
w: &mut W,
symbols: &[ObjSymbol],
addr: u32,
mut ins: Ins,
reloc: Option<&ObjReloc>,
file_offset: u64,
@ -265,7 +266,7 @@ where
write!(
w,
"/* {:08X} {:08X} {:02X} {:02X} {:02X} {:02X} */\t",
ins.addr as u64 + section_vaddr.unwrap_or(0),
addr as u64 + section_vaddr.unwrap_or(0),
file_offset,
(ins.code >> 24) & 0xFF,
(ins.code >> 16) & 0xFF,
@ -290,10 +291,10 @@ where
write!(w, ".4byte {:#010X} /* invalid */", ins.code)?;
} else if is_illegal_instruction(ins.code) {
let sins = ins.simplified();
write!(w, ".4byte {:#010X} /* illegal: {} */", sins.ins.code, sins)?;
write!(w, ".4byte {:#010X} /* illegal: {} */", ins.code, sins)?;
} else {
let sins = ins.simplified();
write!(w, "{}{}", sins.mnemonic, sins.ins.suffix())?;
write!(w, "{}", sins.mnemonic)?;
let mut writing_offset = false;
for (i, arg) in sins.args.iter().enumerate() {
@ -359,8 +360,15 @@ where W: Write + ?Sized {
Ok(())
}
fn write_symbol_entry<W>(w: &mut W, symbols: &[ObjSymbol], entry: &SymbolEntry) -> Result<()>
where W: Write + ?Sized {
fn write_symbol_entry<W>(
w: &mut W,
symbols: &[ObjSymbol],
entry: &SymbolEntry,
section: &ObjSection,
) -> Result<()>
where
W: Write + ?Sized,
{
let symbol = &symbols[entry.index];
// Skip writing certain symbols
@ -398,6 +406,11 @@ where W: Write + ?Sized {
if symbol.kind != ObjSymbolKind::Unknown {
writeln!(w)?;
}
write!(w, "# {}:{:#X}", section.name, symbol.address)?;
if let Some(section_address) = section.virtual_address {
write!(w, " | {:#X}", section_address + symbol.address)?;
}
writeln!(w, " | size: {:#X}", symbol.size)?;
if let Some(name) = &symbol.demangled_name {
writeln!(w, "# {name}")?;
}
@ -456,7 +469,7 @@ where
if entry.kind == SymbolEntryKind::End && begin {
continue;
}
write_symbol_entry(w, symbols, entry)?;
write_symbol_entry(w, symbols, entry, section)?;
}
current_symbol_kind = find_symbol_kind(current_symbol_kind, symbols, vec)?;
current_data_kind = find_data_kind(current_data_kind, symbols, vec)
@ -692,8 +705,8 @@ where W: Write + ?Sized {
_ => {}
}
let chunk_size = match data_kind {
ObjDataKind::Byte2 => 2,
ObjDataKind::Unknown | ObjDataKind::Byte4 | ObjDataKind::Float => 4,
ObjDataKind::Byte2 | ObjDataKind::Short => 2,
ObjDataKind::Unknown | ObjDataKind::Byte4 | ObjDataKind::Float | ObjDataKind::Int => 4,
ObjDataKind::Byte | ObjDataKind::Byte8 | ObjDataKind::Double => 8,
ObjDataKind::String
| ObjDataKind::String16
@ -728,10 +741,18 @@ where W: Write + ?Sized {
writeln!(w, "\t.float {data}")?;
}
}
4 if data_kind == ObjDataKind::Int => {
let data = i32::from_be_bytes(chunk.try_into().unwrap());
writeln!(w, "\t.int {data}")?;
}
4 => {
let data = u32::from_be_bytes(chunk.try_into().unwrap());
writeln!(w, "\t.4byte {data:#010X}")?;
}
2 if data_kind == ObjDataKind::Short => {
let data = i16::from_be_bytes(chunk.try_into().unwrap());
writeln!(w, "\t.short {data}")?;
}
2 => {
writeln!(w, "\t.2byte {:#06X}", u16::from_be_bytes(chunk.try_into().unwrap()))?;
}
@ -790,6 +811,7 @@ fn write_bss<W>(
w: &mut W,
symbols: &[ObjSymbol],
entries: &BTreeMap<u32, Vec<SymbolEntry>>,
section: &ObjSection,
start: u32,
end: u32,
) -> Result<()>
@ -811,7 +833,7 @@ where
if entry.kind == SymbolEntryKind::End && begin {
continue;
}
write_symbol_entry(w, symbols, entry)?;
write_symbol_entry(w, symbols, entry, section)?;
}
entry = entry_iter.next();
}
@ -841,9 +863,10 @@ where
let section_virtual_address = section.virtual_address.unwrap_or(0);
writeln!(
w,
"\n# {:#010X} - {:#010X}",
"\n# {:#010X}..{:#010X} | size: {:#X}",
start as u64 + section_virtual_address,
end as u64 + section_virtual_address
end as u64 + section_virtual_address,
end - start
)?;
match section.name.as_str() {
".text" if subsection == 0 => {

View File

@ -321,6 +321,8 @@ fn symbol_data_kind_to_str(kind: ObjDataKind) -> Option<&'static str> {
ObjDataKind::String16 => Some("wstring"),
ObjDataKind::StringTable => Some("string_table"),
ObjDataKind::String16Table => Some("wstring_table"),
ObjDataKind::Int => Some("int"),
ObjDataKind::Short => Some("short"),
}
}
@ -372,6 +374,8 @@ fn symbol_data_kind_from_str(s: &str) -> Option<ObjDataKind> {
"wstring" => Some(ObjDataKind::String16),
"string_table" => Some(ObjDataKind::StringTable),
"wstring_table" => Some(ObjDataKind::String16Table),
"int" => Some(ObjDataKind::Int),
"short" => Some(ObjDataKind::Short),
_ => None,
}
}
@ -676,6 +680,7 @@ where R: BufRead + ?Sized {
skip,
}),
) => {
ensure!(end >= start, "Invalid split range {:#X}..{:#X}", start, end);
let (section_index, _) = match obj.sections.by_name(&name)? {
Some(v) => Ok(v),
None => {

View File

@ -15,6 +15,7 @@ use crate::{
},
util::{
alf::{AlfFile, AlfSymbol, ALF_MAGIC},
align_up,
reader::{skip_bytes, Endian, FromReader},
},
};
@ -555,6 +556,26 @@ pub fn process_dol(buf: &[u8], name: &str) -> Result<ObjInfo> {
section.elf_index = idx + 1;
}
// Guess section alignment
let mut last_section_end = sections.first().map_or(0, |s| s.address as u32);
for section in &mut sections {
let section_start = section.address as u32;
let mut align = 4;
while align_up(last_section_end, align) < section_start {
align = (align + 1).next_power_of_two();
}
if align_up(last_section_end, align) != section_start {
bail!(
"Couldn't determine alignment for section '{}' ({:#010X} -> {:#010X})",
section.name,
last_section_end,
section_start
);
}
last_section_end = section_start + section.size as u32;
section.align = align as u64;
}
// Create object
let mut obj = ObjInfo::new(
ObjKind::Executable,

View File

@ -1,7 +1,6 @@
use std::{
cmp::max,
collections::BTreeMap,
convert::TryFrom,
fmt::{Display, Formatter, Write},
io::{BufRead, Cursor, Seek, SeekFrom},
num::NonZeroU32,

View File

@ -186,7 +186,7 @@ where P: AsRef<Path> {
continue;
}
if kind == ObjKind::Relocatable {
obj_name = file_name.clone();
obj_name.clone_from(&file_name);
}
let sections = match section_starts.entry(file_name.clone()) {
indexmap::map::Entry::Occupied(_) => {
@ -197,7 +197,7 @@ where P: AsRef<Path> {
*index += 1;
let new_name = format!("{}_{}", file_name, index);
// log::info!("Renaming {} to {}", file_name, new_name);
file_name = new_name.clone();
file_name.clone_from(&new_name);
match section_starts.entry(new_name.clone()) {
indexmap::map::Entry::Occupied(_) => {
bail!("Duplicate filename '{}'", new_name)

View File

@ -1,5 +1,4 @@
use std::{
borrow::Cow,
ffi::OsStr,
fs::{DirBuilder, File, OpenOptions},
io::{BufRead, BufReader, BufWriter, Cursor, Read, Seek, SeekFrom},
@ -16,12 +15,11 @@ use xxhash_rust::xxh3::xxh3_64;
use crate::{
array_ref,
util::{
ncompress::{decompress_yay0, decompress_yaz0, YAY0_MAGIC, YAZ0_MAGIC},
rarc,
rarc::{Node, RARC_MAGIC},
take_seek::{TakeSeek, TakeSeekExt},
yaz0,
yaz0::YAZ0_MAGIC,
IntoCow, ToCow,
Bytes,
},
};
@ -78,24 +76,36 @@ where P: AsRef<Path> {
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 {
let mut reader = Cursor::new(&*mmap);
if sub_path.as_os_str() == OsStr::new("nlzss") {
return Ok(FileEntry::Buffer(
nintendo_lz::decompress(&mut reader).map_err(|e| {
anyhow!("Failed to decompress '{}' with NLZSS: {}", path.as_ref().display(), e)
})?,
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(
yaz0::decompress_file(&mut reader).with_context(|| {
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 rarc = rarc::RarcReader::new(&mut reader)
let rarc = rarc::RarcReader::new(&mut Cursor::new(mmap.as_ref()))
.with_context(|| format!("Failed to open '{}' as RARC archive", 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())
@ -106,17 +116,43 @@ where P: AsRef<Path> {
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 && buf[0..4] == YAZ0_MAGIC {
return Ok(FileEntry::Buffer(
yaz0::decompress_file(&mut map.as_reader()).with_context(|| {
format!("Failed to decompress '{}' with Yaz0", path.as_ref().display())
})?,
mtime,
));
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.
@ -254,7 +290,7 @@ impl RarcIterator {
}
impl Iterator for RarcIterator {
type Item = Result<(PathBuf, Vec<u8>)>;
type Item = Result<(PathBuf, Box<[u8]>)>;
fn next(&mut self) -> Option<Self::Item> {
if self.index >= self.paths.len() {
@ -275,7 +311,7 @@ impl Iterator for RarcIterator {
/// A file entry, either a memory mapped file or an owned buffer.
pub enum FileEntry {
MappedFile(MappedFile),
Buffer(Vec<u8>, FileTime),
Buffer(Box<[u8]>, FileTime),
}
impl FileEntry {
@ -283,14 +319,14 @@ impl FileEntry {
pub fn as_reader(&self) -> Cursor<&[u8]> {
match self {
Self::MappedFile(file) => file.as_reader(),
Self::Buffer(slice, _) => Cursor::new(slice.as_slice()),
Self::Buffer(slice, _) => Cursor::new(slice),
}
}
pub fn as_slice(&self) -> &[u8] {
match self {
Self::MappedFile(file) => file.as_slice(),
Self::Buffer(slice, _) => slice.as_slice(),
Self::Buffer(slice, _) => slice,
}
}
@ -388,6 +424,7 @@ impl FileIterator {
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)))),
}
@ -398,7 +435,18 @@ impl FileIterator {
file: MappedFile,
path: PathBuf,
) -> Option<Result<(PathBuf, FileEntry)>> {
Some(match yaz0::decompress_file(&mut file.as_reader()) {
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),
})
@ -435,31 +483,15 @@ where P: AsRef<Path> {
}
}
pub fn decompress_if_needed(buf: &[u8]) -> Result<Cow<[u8]>> {
Ok(if buf.len() > 4 && buf[0..4] == YAZ0_MAGIC {
yaz0::decompress_file(&mut Cursor::new(buf))?.into_cow()
} else {
buf.to_cow()
})
}
pub fn decompress_reader<R>(reader: &mut R) -> Result<Vec<u8>>
where R: Read + Seek + ?Sized {
let mut magic = [0u8; 4];
if reader.read_exact(&mut magic).is_err() {
reader.seek(SeekFrom::Start(0))?;
let mut buf = vec![];
reader.read_to_end(&mut buf)?;
return Ok(buf);
pub fn decompress_if_needed(buf: &[u8]) -> Result<Bytes> {
if buf.len() > 4 {
match *array_ref!(buf, 0, 4) {
YAZ0_MAGIC => return decompress_yaz0(buf).map(Bytes::Owned),
YAY0_MAGIC => return decompress_yay0(buf).map(Bytes::Owned),
_ => {}
}
}
Ok(if magic == YAZ0_MAGIC {
reader.seek(SeekFrom::Start(0))?;
yaz0::decompress_file(reader)?
} else {
let mut buf = magic.to_vec();
reader.read_to_end(&mut buf)?;
buf
})
Ok(Bytes::Borrowed(buf))
}
pub fn verify_hash(buf: &[u8], expected_str: &str) -> Result<()> {

View File

@ -1,13 +1,10 @@
use std::path::PathBuf;
use anyhow::{bail, Result};
use anyhow::Result;
use itertools::Itertools;
use path_slash::PathBufExt;
use crate::{
obj::{ObjInfo, ObjKind},
util::align_up,
};
use crate::obj::{ObjInfo, ObjKind};
const LCF_TEMPLATE: &str = include_str!("../../assets/ldscript.lcf");
const LCF_PARTIAL_TEMPLATE: &str = include_str!("../../assets/ldscript_partial.lcf");
@ -27,32 +24,10 @@ pub fn generate_ldscript(
_ => 65535, // default
};
// Guess section alignment
let mut alignments = Vec::with_capacity(obj.sections.count());
let mut last_section_end = origin as u32;
for (_, section) in obj.sections.iter() {
let section_start = section.address as u32;
let mut align = 0x20;
while align_up(last_section_end, align) < section_start {
align = (align + 1).next_power_of_two();
}
if align_up(last_section_end, align) != section_start {
bail!(
"Couldn't determine alignment for section '{}' ({:#010X} -> {:#010X})",
section.name,
last_section_end,
section_start
);
}
last_section_end = section_start + section.size as u32;
alignments.push(align);
}
let section_defs = obj
.sections
.iter()
.zip(alignments)
.map(|((_, s), align)| format!("{} ALIGN({:#X}):{{}}", s.name, align))
.map(|(_, s)| format!("{} ALIGN({:#X}):{{}}", s.name, s.align))
.join("\n ");
let mut force_files = Vec::with_capacity(obj.link_order.len());
@ -89,6 +64,15 @@ pub fn generate_ldscript_partial(
template: Option<&str>,
force_active: &[String],
) -> Result<String> {
let section_defs = obj
.sections
.iter()
.map(|(_, s)| {
let inner = if s.name == ".data" { " *(.data) *(extabindex) *(extab) " } else { "" };
format!("{} ALIGN({:#X}):{{{}}}", s.name, s.align, inner)
})
.join("\n ");
let mut force_files = Vec::with_capacity(obj.link_order.len());
for unit in &obj.link_order {
let obj_path = obj_path_for_unit(&unit.name);
@ -104,6 +88,7 @@ pub fn generate_ldscript_partial(
let out = template
.unwrap_or(LCF_PARTIAL_TEMPLATE)
.replace("$SECTIONS", &section_defs)
.replace("$FORCEACTIVE", &force_active.join("\n "));
Ok(out)
}

View File

@ -478,7 +478,7 @@ impl StateMachine {
return false;
}
if !e.unused {
last_unit = e.unit.clone();
last_unit.clone_from(&e.unit);
}
true
});

View File

@ -12,6 +12,7 @@ pub mod elf;
pub mod file;
pub mod lcf;
pub mod map;
pub mod ncompress;
pub mod nested;
pub mod rarc;
pub mod reader;
@ -20,7 +21,6 @@ pub mod rso;
pub mod signatures;
pub mod split;
pub mod take_seek;
pub mod yaz0;
#[inline]
pub const fn align_up(value: u32, align: u32) -> u32 { (value + (align - 1)) & !(align - 1) }
@ -74,3 +74,26 @@ where B: ToOwned + ?Sized
{
fn to_cow(&'a self) -> Cow<'a, B> { Cow::Borrowed(self) }
}
pub enum Bytes<'a> {
Borrowed(&'a [u8]),
Owned(Box<[u8]>),
}
impl<'a> Bytes<'a> {
pub fn into_owned(self) -> Box<[u8]> {
match self {
Bytes::Borrowed(s) => Box::from(s),
Bytes::Owned(b) => b,
}
}
}
impl<'a> AsRef<[u8]> for Bytes<'a> {
fn as_ref(&self) -> &[u8] {
match self {
Bytes::Borrowed(s) => s,
Bytes::Owned(b) => b,
}
}
}

33
src/util/ncompress.rs Normal file
View File

@ -0,0 +1,33 @@
use anyhow::{anyhow, Result};
use orthrus_ncompress::{yay0::Yay0, yaz0::Yaz0};
pub const YAZ0_MAGIC: [u8; 4] = *b"Yaz0";
pub const YAY0_MAGIC: [u8; 4] = *b"Yay0";
/// Compresses the data into a new allocated buffer using Yaz0 compression.
pub fn compress_yaz0(input: &[u8]) -> Box<[u8]> {
let mut output = vec![0u8; Yaz0::worst_possible_size(input.len())];
let size = Yaz0::compress_n64(input, output.as_mut_slice());
output.truncate(size);
output.into_boxed_slice()
}
/// Decompresses the data into a new allocated buffer. Assumes a Yaz0 header followed by
/// compressed data.
pub fn decompress_yaz0(input: &[u8]) -> Result<Box<[u8]>> {
Yaz0::decompress_from(input).map_err(|e| anyhow!(e))
}
/// Compresses the data into a new allocated buffer using Yay0 compression.
pub fn compress_yay0(input: &[u8]) -> Box<[u8]> {
let mut output = vec![0u8; Yay0::worst_possible_size(input.len())];
let size = Yay0::compress_n64(input, output.as_mut_slice());
output.truncate(size);
output.into_boxed_slice()
}
/// Decompresses the data into a new allocated buffer. Assumes a Yay0 header followed by
/// compressed data.
pub fn decompress_yay0(input: &[u8]) -> Result<Box<[u8]>> {
Yay0::decompress_from(input).map_err(|e| anyhow!(e))
}

View File

@ -902,7 +902,7 @@ pub fn split_obj(obj: &ObjInfo, module_name: Option<&str>) -> Result<Vec<ObjInfo
split_obj.mw_comment = Some(MWComment::new(comment_version)?);
}
} else {
split_obj.mw_comment = obj.mw_comment.clone();
split_obj.mw_comment.clone_from(&obj.mw_comment);
}
split_obj.split_meta = Some(SplitMeta {
generator: Some(format!("{} {}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION"))),
@ -1187,7 +1187,7 @@ pub fn split_obj(obj: &ObjInfo, module_name: Option<&str>) -> Result<Vec<ObjInfo
for (globalize_idx, new_name) in &globalize_symbols {
if let Some(symbol_idx) = symbol_map[*globalize_idx] {
let mut symbol = obj.symbols[symbol_idx].clone();
symbol.name = new_name.clone();
symbol.name.clone_from(new_name);
if symbol.flags.is_local() {
log::debug!("Globalizing {} in {}", symbol.name, obj.name);
symbol.flags.set_scope(ObjSymbolScope::Global);

View File

@ -1,101 +0,0 @@
// Source: https://github.com/Julgodis/picori/blob/650da9f4fe6050b39b80d5360416591c748058d5/src/yaz0.rs
// License: MIT
// Modified to use `std::io::Read`/`Seek` and project's FromReader trait.
use std::io::{Read, Seek};
use anyhow::{ensure, Result};
use crate::util::reader::{skip_bytes, struct_size, Endian, FromReader};
pub const YAZ0_MAGIC: [u8; 4] = *b"Yaz0";
/// Yaz0 header.
pub struct Header {
/// Size of decompressed data.
pub decompressed_size: u32,
}
impl FromReader for Header {
type Args = ();
const STATIC_SIZE: usize = struct_size([
u32::STATIC_SIZE, // magic
u32::STATIC_SIZE, // decompressed_size
u32::STATIC_SIZE, // reserved0
u32::STATIC_SIZE, // reserved1
]);
fn from_reader_args<R>(reader: &mut R, e: Endian, _args: Self::Args) -> std::io::Result<Self>
where R: Read + Seek + ?Sized {
let magic = <[u8; 4]>::from_reader(reader, e)?;
if magic != YAZ0_MAGIC {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!("Invalid Yaz0 magic: {:?}", magic),
));
}
let decompressed_size = u32::from_reader(reader, e)?;
skip_bytes::<8, _>(reader)?;
Ok(Self { decompressed_size })
}
}
/// Decompresses the data into a new allocated [`Vec`]. Assumes a Yaz0 header followed by
/// compressed data.
pub fn decompress_file<R>(input: &mut R) -> Result<Vec<u8>>
where R: Read + Seek + ?Sized {
let header = Header::from_reader(input, Endian::Big)?;
decompress(input, header.decompressed_size as usize)
}
/// Decompresses the data into a new allocated [`Vec`]. `decompressed_size` can be determined
/// by looking at the Yaz0 header [`Header`].
pub fn decompress<R>(input: &mut R, decompressed_size: usize) -> Result<Vec<u8>>
where R: Read + Seek + ?Sized {
let mut output = vec![0; decompressed_size];
decompress_into(input, output.as_mut_slice())?;
Ok(output)
}
/// Decompresses the data into the given buffer. The buffer must be large
/// enough to hold the decompressed data.
pub fn decompress_into<R>(input: &mut R, destination: &mut [u8]) -> Result<()>
where R: Read + Seek + ?Sized {
let decompressed_size = destination.len();
let mut dest = 0;
let mut code = 0;
let mut code_bits = 0;
while dest < decompressed_size {
if code_bits == 0 {
code = u8::from_reader(input, Endian::Big)? as u32;
code_bits = 8;
}
if code & 0x80 != 0 {
destination[dest] = u8::from_reader(input, Endian::Big)?;
dest += 1;
} else {
let bytes = <[u8; 2]>::from_reader(input, Endian::Big)?;
let a = (bytes[0] & 0xf) as usize;
let b = (bytes[0] >> 4) as usize;
let offset = (a << 8) | (bytes[1] as usize);
let length = match b {
0 => (u8::from_reader(input, Endian::Big)? as usize) + 0x12,
length => length + 2,
};
ensure!(offset < dest, "Unexpected EOF");
let base = dest - (offset + 1);
for n in 0..length {
destination[dest] = destination[base + n];
dest += 1;
}
}
code <<= 1;
code_bits -= 1;
}
Ok(())
}