Compare commits
10 Commits
e46c6a72bc
...
4dacf2f39a
Author | SHA1 | Date |
---|---|---|
Luke Street | 4dacf2f39a | |
Luke Street | 9452ca8b8c | |
Luke Street | 963425793d | |
Luke Street | c45f37eb10 | |
Luke Street | c1c4373e53 | |
Luke Street | dc7e307c44 | |
Luke Street | dac2dcfc9e | |
Luke Street | fdafe59e13 | |
Luke Street | bfa926ebbf | |
Luke Street | 989293a477 |
|
@ -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",
|
||||
]
|
||||
|
|
34
Cargo.toml
34
Cargo.toml
|
@ -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"
|
||||
|
|
93
README.md
93
README.md
|
@ -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
|
||||
```
|
||||
|
|
|
@ -2,13 +2,7 @@ SECTIONS
|
|||
{
|
||||
GROUP:
|
||||
{
|
||||
.init :{}
|
||||
.text :{}
|
||||
.ctors :{}
|
||||
.dtors :{}
|
||||
.rodata :{}
|
||||
.data :{ *(.data) *(extabindex) *(extab) }
|
||||
.bss :{}
|
||||
$SECTIONS
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 => {
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)) =
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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) }
|
|
@ -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!(
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)?;
|
||||
|
|
|
@ -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(())
|
||||
}
|
|
@ -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()
|
||||
|
|
20
src/main.rs
20
src/main.rs
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(§ion.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(§ion.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 => {
|
||||
|
|
|
@ -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 => {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
use std::{
|
||||
cmp::max,
|
||||
collections::BTreeMap,
|
||||
convert::TryFrom,
|
||||
fmt::{Display, Formatter, Write},
|
||||
io::{BufRead, Cursor, Seek, SeekFrom},
|
||||
num::NonZeroU32,
|
||||
|
|
|
@ -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)
|
||||
|
|
124
src/util/file.rs
124
src/util/file.rs
|
@ -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<()> {
|
||||
|
|
|
@ -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", §ion_defs)
|
||||
.replace("$FORCEACTIVE", &force_active.join("\n "));
|
||||
Ok(out)
|
||||
}
|
||||
|
|
|
@ -478,7 +478,7 @@ impl StateMachine {
|
|||
return false;
|
||||
}
|
||||
if !e.unused {
|
||||
last_unit = e.unit.clone();
|
||||
last_unit.clone_from(&e.unit);
|
||||
}
|
||||
true
|
||||
});
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
|
@ -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);
|
||||
|
|
101
src/util/yaz0.rs
101
src/util/yaz0.rs
|
@ -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(())
|
||||
}
|
Loading…
Reference in New Issue