diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c375264..b4d0f10 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,4 +13,4 @@ jobs: targets: aarch64-unknown-none-softfloat - run: cargo fmt --all -- --check - run: cargo clippy --workspace --exclude kernel --exclude dyld - - run: cargo run -p gravity-setup -- --ci + - run: cargo run -p gravity-setup -- ci diff --git a/.gitignore b/.gitignore index 23d19c6..d153eb3 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ compile_commands.json /result /result-* src/kernel/src/rust_dyld.bin +.chat_history.json diff --git a/Cargo.lock b/Cargo.lock index c9deb1e..c7b3b10 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15,7 +15,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", - "cipher", + "cipher 0.4.4", "cpufeatures", ] @@ -42,28 +42,35 @@ checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" [[package]] name = "anyhow" -version = "1.0.100" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" +checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea" [[package]] name = "apple-dmg" version = "0.1.0" dependencies = [ - "anyhow", - "byteorder", + "binrw", "crc32fast", + "derive_more", "fatfs", - "flate2", "fscommon", - "getrandom 0.3.4", + "getrandom 0.4.1", "hfsplus", "md5", "plist", + "rootcause", "serde", "serde_bytes", + "zlib-rs", ] +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" + [[package]] name = "array-init" version = "2.1.0" @@ -123,6 +130,28 @@ version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "bitvec-nom2" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d988fcc40055ceaa85edc55875a08f8abd29018582647fd82ad6128dba14a5f0" +dependencies = [ + "bitvec", + "nom", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -141,17 +170,29 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-padding" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "710f1dd022ef4e93f8a438b4ba958de7f64308434fa6a87104481645cc30068b" +dependencies = [ + "hybrid-array", +] + [[package]] name = "bumpalo" version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" +dependencies = [ + "allocator-api2", +] [[package]] name = "bytemuck" -version = "1.24.0" +version = "1.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" [[package]] name = "byteorder" @@ -161,9 +202,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" [[package]] name = "bzip2" @@ -180,14 +221,14 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" dependencies = [ - "cipher", + "cipher 0.4.4", ] [[package]] name = "cc" -version = "1.2.54" +version = "1.2.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6354c81bbfd62d9cfa9cb3c773c2b7b2a3a482d569de977fd0e961f6e7c00583" +checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29" dependencies = [ "find-msvc-tools", "jobserver", @@ -220,15 +261,25 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ - "crypto-common", - "inout", + "crypto-common 0.1.7", + "inout 0.1.4", +] + +[[package]] +name = "cipher" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64727038c8c5e2bb503a15b9f5b9df50a1da9a33e83e1f93067d914f2c6604a5" +dependencies = [ + "crypto-common 0.2.0", + "inout 0.2.2", ] [[package]] name = "clap" -version = "4.5.55" +version = "4.5.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e34525d5bbbd55da2bb745d34b36121baac88d07619a9a09cfcf4a6c0832785" +checksum = "6899ea499e3fb9305a65d5ebf6e3d2248c5fab291f300ad0a704fbe142eae31a" dependencies = [ "clap_builder", "clap_derive", @@ -236,9 +287,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.55" +version = "4.5.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59a20016a20a3da95bef50ec7238dbd09baeef4311dcdd38ec15aba69812fb61" +checksum = "7b12c8b680195a62a8364d16b8447b01b6c2c8f9aaf68bee653be34d4245e238" dependencies = [ "anstyle", "clap_lex", @@ -281,6 +332,15 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -306,6 +366,180 @@ dependencies = [ "libc", ] +[[package]] +name = "cranelift" +version = "0.128.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aaf0bd3cb9d164f355ecaa41e57de67ada7d5f3f451c8d29376bf6612059036" +dependencies = [ + "cranelift-codegen", + "cranelift-frontend", + "cranelift-jit", + "cranelift-module", + "cranelift-native", +] + +[[package]] +name = "cranelift-assembler-x64" +version = "0.128.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0377b13bf002a0774fcccac4f1102a10f04893d24060cf4b7350c87e4cbb647c" +dependencies = [ + "cranelift-assembler-x64-meta", +] + +[[package]] +name = "cranelift-assembler-x64-meta" +version = "0.128.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfa027979140d023b25bf7509fb7ede3a54c3d3871fb5ead4673c4b633f671a2" +dependencies = [ + "cranelift-srcgen", +] + +[[package]] +name = "cranelift-bforest" +version = "0.128.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "618e4da87d9179a70b3c2f664451ca8898987aa6eb9f487d16988588b5d8cc40" +dependencies = [ + "cranelift-entity", +] + +[[package]] +name = "cranelift-bitset" +version = "0.128.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db53764b5dad233b37b8f5dc54d3caa9900c54579195e00f17ea21f03f71aaa7" + +[[package]] +name = "cranelift-codegen" +version = "0.128.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae927f1d8c0abddaa863acd201471d56e7fc6c3925104f4861ed4dc3e28b421" +dependencies = [ + "bumpalo", + "cranelift-assembler-x64", + "cranelift-bforest", + "cranelift-bitset", + "cranelift-codegen-meta", + "cranelift-codegen-shared", + "cranelift-control", + "cranelift-entity", + "cranelift-isle", + "gimli", + "hashbrown 0.15.5", + "log", + "regalloc2", + "rustc-hash", + "serde", + "smallvec", + "target-lexicon", + "wasmtime-internal-math", +] + +[[package]] +name = "cranelift-codegen-meta" +version = "0.128.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fcf1e3e6757834bd2584f4cbff023fcc198e9279dcb5d684b4bb27a9b19f54" +dependencies = [ + "cranelift-assembler-x64-meta", + "cranelift-codegen-shared", + "cranelift-srcgen", + "heck", +] + +[[package]] +name = "cranelift-codegen-shared" +version = "0.128.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "205dcb9e6ccf9d368b7466be675ff6ee54a63e36da6fe20e72d45169cf6fd254" + +[[package]] +name = "cranelift-control" +version = "0.128.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "108eca9fcfe86026054f931eceaf57b722c1b97464bf8265323a9b5877238817" +dependencies = [ + "arbitrary", +] + +[[package]] +name = "cranelift-entity" +version = "0.128.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0d96496910065d3165f84ff8e1e393916f4c086f88ac8e1b407678bc78735aa" +dependencies = [ + "cranelift-bitset", +] + +[[package]] +name = "cranelift-frontend" +version = "0.128.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e303983ad7e23c850f24d9c41fc3cb346e1b930f066d3966545e4c98dac5c9fb" +dependencies = [ + "cranelift-codegen", + "log", + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cranelift-isle" +version = "0.128.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24b0cf8d867d891245836cac7abafb0a5b0ea040a019d720702b3b8bcba40bfa" + +[[package]] +name = "cranelift-jit" +version = "0.128.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf1e35da6eca2448395f483eb172ce71dd7842f7dc96f44bb8923beafe43c6d" +dependencies = [ + "anyhow", + "cranelift-codegen", + "cranelift-control", + "cranelift-entity", + "cranelift-module", + "cranelift-native", + "libc", + "log", + "region", + "target-lexicon", + "wasmtime-internal-jit-icache-coherence", + "windows-sys 0.61.2", +] + +[[package]] +name = "cranelift-module" +version = "0.128.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "792ba2a54100e34f8a36e3e329a5207cafd1f0918a031d34695db73c163fdcc7" +dependencies = [ + "anyhow", + "cranelift-codegen", + "cranelift-control", +] + +[[package]] +name = "cranelift-native" +version = "0.128.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e24b641e315443e27807b69c440fe766737d7e718c68beb665a2d69259c77bf3" +dependencies = [ + "cranelift-codegen", + "libc", + "target-lexicon", +] + +[[package]] +name = "cranelift-srcgen" +version = "0.128.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e378a54e7168a689486d67ee1f818b7e5356e54ae51a1d7a53f4f13f7f8b7a" + [[package]] name = "crc" version = "3.3.0" @@ -365,6 +599,15 @@ dependencies = [ "typenum", ] +[[package]] +name = "crypto-common" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "211f05e03c7d03754740fd9e585de910a095d6b99f8bcfffdef8319fa02a8331" +dependencies = [ + "hybrid-array", +] + [[package]] name = "deflate64" version = "0.1.10" @@ -380,13 +623,47 @@ dependencies = [ "powerfmt", ] +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn", + "unicode-xid", +] + [[package]] name = "des" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffdd80ce8ce993de27e9f063a444a4d53ce8e8db4c1f00cc03af5ad5a9867a1e" dependencies = [ - "cipher", + "cipher 0.4.4", +] + +[[package]] +name = "devicetree" +version = "0.1.0" +dependencies = [ + "aes", + "cbc", + "derive_more", + "hex", + "rootcause", ] [[package]] @@ -396,7 +673,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", - "crypto-common", + "crypto-common 0.1.7", "subtle", ] @@ -426,6 +703,19 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +[[package]] +name = "emulator" +version = "0.1.0" +dependencies = [ + "aes", + "cbc", + "cranelift", + "derive_more", + "hex", + "rasn", + "rootcause", +] + [[package]] name = "encode_unicode" version = "1.0.0" @@ -445,9 +735,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] +[[package]] +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + [[package]] name = "fastrand" version = "2.3.0" @@ -468,19 +764,19 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "flate2" -version = "1.1.8" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b375d6465b98090a5f25b1c7703f3859783755aa9a80433b36e0379a3ec2f369" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" dependencies = [ "crc32fast", "miniz_oxide", - "zlib-rs 0.5.5", + "zlib-rs", ] [[package]] @@ -489,6 +785,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + [[package]] name = "foldhash" version = "0.2.0" @@ -528,6 +830,12 @@ dependencies = [ "log", ] +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futures-channel" version = "0.3.31" @@ -624,6 +932,30 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "getrandom" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", + "wasip3", +] + +[[package]] +name = "gimli" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" +dependencies = [ + "fallible-iterator", + "indexmap", + "stable_deref_trait", +] + [[package]] name = "goblin" version = "0.10.4" @@ -641,6 +973,7 @@ version = "0.1.0" dependencies = [ "apple-dmg", "clap", + "derive_more", "flate2", "futures-util", "hfsplus", @@ -650,8 +983,8 @@ dependencies = [ "plist", "rayon", "reqwest", + "rootcause", "serde", - "thiserror", "tokio", "vfdecrypt", "zip", @@ -676,6 +1009,15 @@ dependencies = [ "tracing", ] +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash 0.1.5", +] + [[package]] name = "hashbrown" version = "0.16.1" @@ -684,7 +1026,7 @@ checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" dependencies = [ "allocator-api2", "equivalent", - "foldhash", + "foldhash 0.2.0", ] [[package]] @@ -693,16 +1035,22 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "hfsplus" version = "0.1.0" dependencies = [ "binrw", "bitflags 2.10.0", - "hashbrown", + "hashbrown 0.16.1", "spin", "unicode-normalization", - "zlib-rs 0.6.0", + "zlib-rs", ] [[package]] @@ -753,6 +1101,15 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" +[[package]] +name = "hybrid-array" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1b229d73f5803b562cc26e4da0396c8610a4ee209f4fac8fa4f8d709166dc45" +dependencies = [ + "typenum", +] + [[package]] name = "hyper" version = "1.8.1" @@ -809,14 +1166,13 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.19" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ "base64", "bytes", "futures-channel", - "futures-core", "futures-util", "http", "http-body", @@ -936,6 +1292,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + [[package]] name = "idna" version = "1.1.0" @@ -964,7 +1326,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.16.1", + "serde", + "serde_core", ] [[package]] @@ -986,10 +1350,20 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" dependencies = [ - "block-padding", + "block-padding 0.3.3", "generic-array", ] +[[package]] +name = "inout" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4250ce6452e92010fdf7268ccc5d14faa80bb12fc741938534c58f16804e03c7" +dependencies = [ + "block-padding 0.4.2", + "hybrid-array", +] + [[package]] name = "ipnet" version = "2.11.0" @@ -1001,12 +1375,13 @@ name = "ipsw-cli" version = "0.1.0" dependencies = [ "clap", + "derive_more", "indicatif", "ipsw-downloader", "plist", "reqwest", + "rootcause", "serde", - "thiserror", "tokio", "vfdecrypt", ] @@ -1015,10 +1390,11 @@ dependencies = [ name = "ipsw-downloader" version = "0.1.0" dependencies = [ + "derive_more", "plist", "reqwest", + "rootcause", "serde", - "thiserror", "tokio", ] @@ -1032,6 +1408,15 @@ dependencies = [ "serde", ] +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.17" @@ -1062,15 +1447,23 @@ dependencies = [ name = "kernel" version = "0.1.0" dependencies = [ + "apple-dmg", + "binrw", "goblin", "hfsplus", "linked_list_allocator", "rand_chacha", "rand_core", + "ruzstd", "spin", - "zstd-safe", ] +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + [[package]] name = "libbz2-rs-sys" version = "0.2.2" @@ -1083,6 +1476,12 @@ version = "0.2.180" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + [[package]] name = "linked_list_allocator" version = "0.10.5" @@ -1112,8 +1511,9 @@ checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" name = "loader" version = "0.1.0" dependencies = [ + "derive_more", "goblin", - "thiserror", + "rootcause", ] [[package]] @@ -1141,6 +1541,15 @@ dependencies = [ "sha2", ] +[[package]] +name = "mach2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44" +dependencies = [ + "libc", +] + [[package]] name = "md5" version = "0.8.0" @@ -1162,6 +1571,12 @@ dependencies = [ "libc", ] +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.8.9" @@ -1200,12 +1615,41 @@ dependencies = [ "tempfile", ] +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + [[package]] name = "num-conv" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -1320,9 +1764,9 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.13.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" [[package]] name = "potential_utf" @@ -1354,6 +1798,16 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + [[package]] name = "proc-macro2" version = "1.0.106" @@ -1387,11 +1841,17 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand_chacha" -version = "0.9.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +checksum = "3e6af7f3e25ded52c41df4e0b1af2d047e45896c2f3281792ed68a1c243daedb" dependencies = [ "ppv-lite86", "rand_core", @@ -1399,9 +1859,57 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.9.5" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba" + +[[package]] +name = "rasn" +version = "0.28.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe40c63064fb2b97594092d3ba4766778c3569424b2758c152df5492d4162aa1" +dependencies = [ + "bitvec", + "bitvec-nom2", + "bytes", + "cfg-if", + "chrono", + "either", + "nom", + "num-bigint", + "num-integer", + "num-traits", + "once_cell", + "rasn-derive", + "serde_json", + "snafu", + "xml-no-std", +] + +[[package]] +name = "rasn-derive" +version = "0.28.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90c1c5eb230cb591677030f8a610d10f21e8c3f84274c69e2b4840c74bef94f9" +dependencies = [ + "proc-macro2", + "rasn-derive-impl", + "syn", +] + +[[package]] +name = "rasn-derive-impl" +version = "0.28.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +checksum = "1e5dad703153553d4c2fbd86a74e8d02943aa2d32b15699e2073de8f16bf4674" +dependencies = [ + "either", + "itertools", + "proc-macro2", + "quote", + "syn", + "uuid", +] [[package]] name = "rayon" @@ -1423,6 +1931,32 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "regalloc2" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08effbc1fa53aaebff69521a5c05640523fab037b34a4a2c109506bc938246fa" +dependencies = [ + "allocator-api2", + "bumpalo", + "hashbrown 0.15.5", + "log", + "rustc-hash", + "smallvec", +] + +[[package]] +name = "region" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6b6ebd13bc009aef9cd476c1310d49ac354d36e240cf1bd753290f3dc7199a7" +dependencies = [ + "bitflags 1.3.2", + "libc", + "mach2", + "windows-sys 0.52.0", +] + [[package]] name = "reqwest" version = "0.13.1" @@ -1475,6 +2009,41 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rootcause" +version = "0.12.0" +source = "git+https://github.com/rootcause-rs/rootcause#5010b020a57786de6f18bad027560d54b73875b0" +dependencies = [ + "hashbrown 0.16.1", + "indexmap", + "rootcause-internals", + "rustc-hash", + "triomphe", +] + +[[package]] +name = "rootcause-internals" +version = "0.12.0" +source = "git+https://github.com/rootcause-rs/rootcause#5010b020a57786de6f18bad027560d54b73875b0" +dependencies = [ + "triomphe", +] + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "1.1.3" @@ -1485,7 +2054,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -1527,6 +2096,12 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +[[package]] +name = "ruzstd" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ff0cc5e135c8870a775d3320910cd9b564ec036b4dc0b8741629020be63f01" + [[package]] name = "schannel" version = "0.1.28" @@ -1585,6 +2160,12 @@ dependencies = [ "libc", ] +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + [[package]] name = "serde" version = "1.0.228" @@ -1625,6 +2206,19 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + [[package]] name = "sha1" version = "0.10.6" @@ -1661,9 +2255,9 @@ checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" [[package]] name = "slab" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" @@ -1671,6 +2265,27 @@ version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +[[package]] +name = "snafu" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e84b3f4eacbf3a1ce05eac6763b4d629d60cbc94d632e4092c54ade71f1e1a2" +dependencies = [ + "snafu-derive", +] + +[[package]] +name = "snafu-derive" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1c97747dbf44bb1ca44a561ece23508e99cb592e862f22222dcf42f51d1e451" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "socket2" version = "0.6.2" @@ -1742,6 +2357,18 @@ dependencies = [ "syn", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "target-lexicon" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1dd07eb858a2067e2f3c7155d54e929265c264e6f37efe3ee7a8d1b5a1dd0ba" + [[package]] name = "tempfile" version = "3.24.0" @@ -1752,27 +2379,7 @@ dependencies = [ "getrandom 0.3.4", "once_cell", "rustix", - "windows-sys 0.52.0", -] - -[[package]] -name = "thiserror" -version = "2.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "2.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" -dependencies = [ - "proc-macro2", - "quote", - "syn", + "windows-sys 0.61.2", ] [[package]] @@ -1955,6 +2562,12 @@ dependencies = [ "once_cell", ] +[[package]] +name = "triomphe" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd69c5aa8f924c7519d6372789a74eac5b94fb0f8fcf0d4a97eb0bfc3e785f39" + [[package]] name = "try-lock" version = "0.2.5" @@ -1988,12 +2601,24 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + [[package]] name = "unicode-width" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "unit-prefix" version = "0.5.2" @@ -2024,6 +2649,15 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "uuid" +version = "1.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f" +dependencies = [ + "getrandom 0.3.4", +] + [[package]] name = "vcpkg" version = "0.2.15" @@ -2042,13 +2676,14 @@ version = "0.1.0" dependencies = [ "aes", "cbc", - "cipher", + "cipher 0.5.0", + "derive_more", "des", "hmac", "pbkdf2", "rayon", + "rootcause", "sha1", - "thiserror", ] [[package]] @@ -2075,6 +2710,15 @@ dependencies = [ "wit-bindgen", ] +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen", +] + [[package]] name = "wasm-bindgen" version = "0.2.108" @@ -2134,6 +2778,28 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + [[package]] name = "wasm-streams" version = "0.4.2" @@ -2147,6 +2813,39 @@ dependencies = [ "web-sys", ] +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags 2.10.0", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] +name = "wasmtime-internal-jit-icache-coherence" +version = "41.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bada5ca1cc47df7d14100e2254e187c2486b426df813cea2dd2553a7469f7674" +dependencies = [ + "anyhow", + "cfg-if", + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "wasmtime-internal-math" +version = "41.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf6f615d528eda9adc6eefb062135f831b5215c348f4c3ec3e143690c730605b" +dependencies = [ + "libm", +] + [[package]] name = "web-sys" version = "0.3.85" @@ -2387,6 +3086,88 @@ name = "wit-bindgen" version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags 2.10.0", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] [[package]] name = "writeable" @@ -2394,6 +3175,21 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "xml-no-std" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd223bc94c615fc02bf2f4bbc22a4a9bfe489c2add3ec10b1038df3aca44cac7" + [[package]] name = "yoke" version = "0.8.1" @@ -2419,18 +3215,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.35" +version = "0.8.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdea86ddd5568519879b8187e1cf04e24fce28f7fe046ceecbce472ff19a2572" +checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.35" +version = "0.8.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c15e1b46eff7c6c91195752e0eeed8ef040e391cdece7c25376957d5f15df22" +checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" dependencies = [ "proc-macro2", "quote", @@ -2541,15 +3337,15 @@ dependencies = [ [[package]] name = "zlib-rs" -version = "0.5.5" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40990edd51aae2c2b6907af74ffb635029d5788228222c4bb811e9351c0caad3" +checksum = "a7948af682ccbc3342b6e9420e8c51c1fe5d7bf7756002b4a3c6cabfe96a7e3c" [[package]] -name = "zlib-rs" -version = "0.6.0" +name = "zmij" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7948af682ccbc3342b6e9420e8c51c1fe5d7bf7756002b4a3c6cabfe96a7e3c" +checksum = "3ff05f8caa9038894637571ae6b9e29466c1f4f829d26c9b28f869a29cbe3445" [[package]] name = "zopfli" diff --git a/Cargo.toml b/Cargo.toml index 853c527..e149a6c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ members = [ "src/kernel", "src/tools/ipsw", - "src/tools/gravity-setup", + "src/tools/setup", "src/tools/linker-wrapper", "src/lib/dyld", "src/lib/loader", @@ -10,14 +10,18 @@ members = [ "src/lib/ipsw-downloader", "src/lib/hfsplus", "src/lib/apple-dmg", + "src/tools/emulator", + "src/tools/devicetree", ] resolver = "3" [workspace.package] version = "0.1.0" +edition = "2024" [workspace.dependencies] -thiserror = { version = "2.0.18", default-features = false } +rootcause = { git = "https://github.com/rootcause-rs/rootcause", default-features = false } +derive_more = { version = "2.1.1", default-features = false, features = ["display", "error", "from"] } reqwest = { version = "0.13.1", default-features = false, features = [ "native-tls", "stream", @@ -33,12 +37,13 @@ goblin = { version = "0.10.4", default-features = false, features = [ ] } linked_list_allocator = "0.10.5" spin = "0.10.0" -plist = "1.8.0" +plist = { version = "1.8.0", default-features = false, features = ["serde"] } serde = { version = "1.0.228", default-features = false, features = ["derive"] } tokio = { version = "1.49.0", default-features = false } -clap = { version = "4.5.55", features = [ +clap = { version = "4.5.57", features = [ "derive", "std", + "help", ], default-features = false } aes = "0.8.4" hmac = "0.12.1" @@ -46,7 +51,8 @@ sha1 = "0.10.6" pbkdf2 = "0.12.2" des = "0.8.1" cbc = { version = "0.1.2", features = ["alloc"] } -cipher = { version = "0.4.4", features = ["block-padding"] } +hex = "0.4.3" +cipher = { version = "0.5.0", features = ["block-padding"] } indicatif = "0.18.3" byteorder = "1.5" bitflags = { version = "2.10.0", default-features = false } @@ -61,12 +67,11 @@ zip = "=7.3.0-pre1" rayon = "1.11" futures-util = "0.3" unicode-normalization = { version = "0.1", default-features = false } -anyhow = "1.0" fatfs = "0.3" fscommon = "0.1" md5 = "0.8" serde_bytes = "0.11" -getrandom = "0.3" +getrandom = "0.4" flate2 = "1.1" crc32fast = "1.5" hashbrown = "0.16" @@ -75,10 +80,11 @@ binrw = { version = "0.15.0", default-features = false } zlib-rs = { version = "0.6.0", default-features = false, features = [ "__internal-api", ] } -rand_core = { version = "0.9.5", default-features = false } -rand_chacha = { version = "0.9.0", default-features = false } +rand_core = { version = "0.10.0", default-features = false } +rand_chacha = { version = "0.10.0", default-features = false } lzma-rust2 = { version = "=0.15.7", default-features = false } -zstd-safe = { version = "7.2.4", default-features = false } +ruzstd = { version = "0.8.2", default-features = false } +gpui = { git = "https://github.com/zortax/zed", branch = "wgpu", default-features = false, features = ["wayland"] } [profile.dev] panic = "abort" diff --git a/README.md b/README.md index 8eb9f64..71c2239 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,3 @@ -# Gravity OS +# Zed OS -An operating system built with Gemini. +An operating system built with the Zed Editor. diff --git a/src/kernel/Cargo.toml b/src/kernel/Cargo.toml index b7b2ed2..f196cd2 100644 --- a/src/kernel/Cargo.toml +++ b/src/kernel/Cargo.toml @@ -14,4 +14,6 @@ spin = { workspace = true } rand_chacha = { workspace = true } rand_core = { workspace = true } hfsplus = { path = "../lib/hfsplus" } -zstd-safe = { workspace = true } +ruzstd = { workspace = true } +binrw = { workspace = true } +apple-dmg = { path = "../lib/apple-dmg" } diff --git a/src/kernel/src/block.rs b/src/kernel/src/block.rs index 4f0db30..6bae497 100644 --- a/src/kernel/src/block.rs +++ b/src/kernel/src/block.rs @@ -1,5 +1,170 @@ //! Block device traits +extern crate alloc; +use alloc::string::String; +use alloc::sync::Arc; +use alloc::vec; +use alloc::vec::Vec; + pub trait BlockReader: Send + Sync { fn read_at(&self, offset: u64, buf: &mut [u8]) -> bool; + fn size(&self) -> u64; +} + +pub trait ReadSeek: hfsplus::Read + hfsplus::Seek + Send {} +impl ReadSeek for T {} + +pub struct DeviceWrapper { + device: Arc, + base_offset: u64, + pos: u64, +} + +impl DeviceWrapper { + pub fn new(device: Arc, base_offset: u64) -> Self { + Self { + device, + base_offset, + pos: 0, + } + } +} + +impl hfsplus::Read for DeviceWrapper { + fn read(&mut self, buf: &mut [u8]) -> hfsplus::Result { + if self.device.read_at(self.base_offset + self.pos, buf) { + self.pos += buf.len() as u64; + Ok(buf.len()) + } else { + Err(hfsplus::Error::InvalidData(String::from("Read failed"))) + } + } +} + +impl hfsplus::Seek for DeviceWrapper { + fn seek(&mut self, pos: hfsplus::SeekFrom) -> hfsplus::Result { + match pos { + hfsplus::SeekFrom::Start(s) => self.pos = s, + hfsplus::SeekFrom::Current(c) => self.pos = (self.pos as i64 + c) as u64, + hfsplus::SeekFrom::End(e) => { + let size = self.device.size(); + self.pos = (size as i64 + e) as u64; + } + } + Ok(self.pos) + } +} + +impl binrw::io::Read for DeviceWrapper { + fn read(&mut self, buf: &mut [u8]) -> binrw::io::Result { + if self.device.read_at(self.base_offset + self.pos, buf) { + self.pos += buf.len() as u64; + Ok(buf.len()) + } else { + Err(binrw::io::Error::new( + binrw::io::ErrorKind::Other, + "Read failed", + )) + } + } +} + +impl binrw::io::Seek for DeviceWrapper { + fn seek(&mut self, pos: binrw::io::SeekFrom) -> binrw::io::Result { + match pos { + binrw::io::SeekFrom::Start(s) => self.pos = s, + binrw::io::SeekFrom::Current(c) => self.pos = (self.pos as i64 + c) as u64, + binrw::io::SeekFrom::End(e) => { + let size = self.device.size(); + self.pos = (size as i64 + e) as u64; + } + } + Ok(self.pos) + } +} + +pub struct BufReader { + inner: R, + buffer: Vec, + pos: usize, + cap: usize, +} + +impl BufReader { + pub fn with_capacity(cap: usize, inner: R) -> Self { + Self { + inner, + buffer: vec![0; cap], + pos: 0, + cap: 0, + } + } +} + +impl hfsplus::Read for BufReader { + fn read(&mut self, buf: &mut [u8]) -> hfsplus::Result { + if self.pos >= self.cap { + if buf.len() >= self.buffer.len() { + return self.inner.read(buf); + } + self.pos = 0; + self.cap = self.inner.read(&mut self.buffer)?; + if self.cap == 0 { + return Ok(0); + } + } + let n = core::cmp::min(buf.len(), self.cap - self.pos); + buf[..n].copy_from_slice(&self.buffer[self.pos..self.pos + n]); + self.pos += n; + Ok(n) + } +} + +impl hfsplus::Seek for BufReader { + fn seek(&mut self, pos: hfsplus::SeekFrom) -> hfsplus::Result { + if let hfsplus::SeekFrom::Current(n) = pos { + let new_pos = self.pos as i64 + n; + if new_pos >= 0 && new_pos <= self.cap as i64 { + self.pos = new_pos as usize; + return self.inner.seek(hfsplus::SeekFrom::Current(0)); + } + } + self.pos = 0; + self.cap = 0; + self.inner.seek(pos) + } +} + +impl binrw::io::Read for BufReader { + fn read(&mut self, buf: &mut [u8]) -> binrw::io::Result { + if self.pos >= self.cap { + if buf.len() >= self.buffer.len() { + return self.inner.read(buf); + } + self.pos = 0; + self.cap = self.inner.read(&mut self.buffer)?; + if self.cap == 0 { + return Ok(0); + } + } + let n = core::cmp::min(buf.len(), self.cap - self.pos); + buf[..n].copy_from_slice(&self.buffer[self.pos..self.pos + n]); + self.pos += n; + Ok(n) + } +} + +impl binrw::io::Seek for BufReader { + fn seek(&mut self, pos: binrw::io::SeekFrom) -> binrw::io::Result { + if let binrw::io::SeekFrom::Current(n) = pos { + let new_pos = self.pos as i64 + n; + if new_pos >= 0 && new_pos <= self.cap as i64 { + self.pos = new_pos as usize; + return self.inner.seek(binrw::io::SeekFrom::Current(0)); + } + } + self.pos = 0; + self.cap = 0; + self.inner.seek(pos) + } } diff --git a/src/kernel/src/hfsfs.rs b/src/kernel/src/hfsfs.rs index 881516b..766a645 100644 --- a/src/kernel/src/hfsfs.rs +++ b/src/kernel/src/hfsfs.rs @@ -1,110 +1,12 @@ -//! HFS+ filesystem reader - -use crate::block::BlockReader; +use crate::block::{BlockReader, BufReader, DeviceWrapper, ReadSeek}; use crate::kprintln; use alloc::boxed::Box; -use alloc::string::String; use alloc::sync::Arc; -use alloc::vec; -use alloc::vec::Vec; -use hfsplus::{CatalogBody, Error, Fork, HFSVolume, Read, Result, Seek, SeekFrom}; +use hfsplus::{CatalogBody, Fork, HFSVolume, Read, Seek, SeekFrom}; use spin::Mutex; -pub struct DeviceWrapper { - device: Arc, - base_offset: u64, - pos: u64, -} - -impl DeviceWrapper { - pub fn new(device: Arc, base_offset: u64) -> Self { - Self { - device, - base_offset, - pos: 0, - } - } -} - -impl Read for DeviceWrapper { - fn read(&mut self, buf: &mut [u8]) -> Result { - // kprintln!("DeviceWrapper: read {} bytes at {}", buf.len(), self.pos); - if self.device.read_at(self.base_offset + self.pos, buf) { - self.pos += buf.len() as u64; - Ok(buf.len()) - } else { - // kprintln!("DeviceWrapper: READ FAILED at {}", self.pos); - Err(Error::InvalidData(String::from("Read failed"))) - } - } -} - -impl Seek for DeviceWrapper { - fn seek(&mut self, pos: SeekFrom) -> Result { - match pos { - SeekFrom::Start(s) => self.pos = s, - SeekFrom::Current(c) => self.pos = (self.pos as i64 + c) as u64, - SeekFrom::End(_) => return Err(Error::UnsupportedOperation), - } - // kprintln!("DeviceWrapper: seek to {}", self.pos); - Ok(self.pos) - } -} - -pub struct BufReader { - inner: R, - buffer: Vec, - pos: usize, - cap: usize, -} - -impl BufReader { - pub fn with_capacity(cap: usize, inner: R) -> Self { - Self { - inner, - buffer: vec![0; cap], - pos: 0, - cap: 0, - } - } -} - -impl Read for BufReader { - fn read(&mut self, buf: &mut [u8]) -> Result { - if self.pos >= self.cap { - if buf.len() >= self.buffer.len() { - return self.inner.read(buf); - } - self.pos = 0; - self.cap = self.inner.read(&mut self.buffer)?; - if self.cap == 0 { - return Ok(0); - } - } - let n = core::cmp::min(buf.len(), self.cap - self.pos); - buf[..n].copy_from_slice(&self.buffer[self.pos..self.pos + n]); - self.pos += n; - Ok(n) - } -} - -impl Seek for BufReader { - fn seek(&mut self, pos: SeekFrom) -> Result { - if let SeekFrom::Current(n) = pos { - let new_pos = self.pos as i64 + n; - if new_pos >= 0 && new_pos <= self.cap as i64 { - self.pos = new_pos as usize; - return self.inner.seek(SeekFrom::Current(0)); - } - } - self.pos = 0; - self.cap = 0; - self.inner.seek(pos) - } -} - pub struct HfsFs { - volume: Arc>>>, + volume: Arc>>>, } impl HfsFs { @@ -112,14 +14,18 @@ impl HfsFs { kprintln!("HfsFs: Initializing with offset {:x}", base_offset); let wrapper = DeviceWrapper::new(device, base_offset); let buffered = BufReader::with_capacity(64 * 1024, wrapper); + Self::new_from_reader(Box::new(buffered)) + } + + pub fn new_from_reader(reader: Box) -> Self { kprintln!("HfsFs: Loading HFS+ volume..."); // Manual HFSVolume::load but with kernel logs - let mut file = buffered; + let mut file = reader; file.seek(hfsplus::SeekFrom::Start(1024)).unwrap(); let header = hfsplus::HFSPlusVolumeHeader::import(&mut file).unwrap(); - let file_arc = Arc::new(Mutex::new(file)); + let file_arc: Arc>> = Arc::new(Mutex::new(file)); let volume = Arc::new(Mutex::new(hfsplus::HFSVolume { file: Arc::clone(&file_arc), header, @@ -185,8 +91,7 @@ impl HfsFs { let mut fork_type = 0; if fork_data.logical_size == 0 && file_info.resource_fork.logical_size > 0 { fork_data = &file_info.resource_fork; - fork_type = 0xFF; // Usually resource fork is different type in extents btree, but Fork::load handles it? - // Actually, Fork::load takes fork_type. Catalog data fork is 0, resource is 0xFF. + fork_type = 0xFF; } let fork = Fork::load( @@ -207,8 +112,14 @@ impl HfsFs { } } +impl crate::vfs::FileSystem for HfsFs { + fn open(&self, path: &str) -> Option> { + self.open(path) + } +} + pub struct HfsFileHandle { - fork: Mutex>>, + fork: Mutex>>, } impl crate::vfs::File for HfsFileHandle { diff --git a/src/kernel/src/main.rs b/src/kernel/src/main.rs index 43bbe05..c163250 100644 --- a/src/kernel/src/main.rs +++ b/src/kernel/src/main.rs @@ -54,8 +54,46 @@ pub extern "C" fn kmain() { if let Some(blk) = virtio::init() { kprintln!("Initializing VFS from disk..."); let blk_shared = alloc::sync::Arc::new(spin::Mutex::new(blk)); - let hfsfs = hfsfs::HfsFs::new(blk_shared, 400 * 1024 * 1024); - vfs::init(hfsfs); + + let wrapper = block::DeviceWrapper::new( + alloc::sync::Arc::clone(&blk_shared) as alloc::sync::Arc, + 0, + ); + let buffered = block::BufReader::with_capacity(128 * 1024, wrapper); + + let hfsfs = if let Ok(dmg) = apple_dmg::DmgReader::new(buffered) { + kprintln!( + "Found Apple DMG ({} partitions)", + dmg.plist().partitions().len() + ); + let mut hfs_part_idx = None; + for (idx, part) in dmg.plist().partitions().iter().enumerate() { + if part.name.contains("Apple_HFS") + || part.name.contains("Customer Software") + || part.name.contains("HFS") + { + hfs_part_idx = Some(idx); + break; + } + } + + let idx = hfs_part_idx.unwrap_or(dmg.plist().partitions().len() - 1); + kprintln!( + "Using partition {} ('{}')", + idx, + dmg.plist().partitions()[idx].name + ); + + let part_reader = dmg + .into_partition_reader(idx) + .expect("Failed to create partition reader"); + hfsfs::HfsFs::new_from_reader(alloc::boxed::Box::new(part_reader)) + } else { + kprintln!("No DMG found, falling back to raw HFS+ at 400MB"); + hfsfs::HfsFs::new(blk_shared, 400 * 1024 * 1024) + }; + + vfs::init(alloc::boxed::Box::new(hfsfs)); kprintln!("VFS initialized"); load_and_map_shared_cache(); } else { diff --git a/src/kernel/src/vfs.rs b/src/kernel/src/vfs.rs index a30d065..ade8d71 100644 --- a/src/kernel/src/vfs.rs +++ b/src/kernel/src/vfs.rs @@ -1,9 +1,10 @@ //! Simple Virtual Filesystem abstraction -use crate::hfsfs::HfsFs; use alloc::boxed::Box; use alloc::sync::Arc; use alloc::vec::Vec; +use rand_chacha::ChaCha20Rng; +use rand_core::{RngCore, SeedableRng}; use spin::Mutex; static VFS: Mutex> = Mutex::new(None); @@ -30,8 +31,12 @@ pub trait File: Send + Sync { } } +pub trait FileSystem: Send + Sync { + fn open(&self, path: &str) -> Option>; +} + pub struct Vfs { - hfsfs: Arc, + fs: Arc, } pub struct FileHandle { @@ -39,17 +44,15 @@ pub struct FileHandle { } impl Vfs { - pub fn new(hfsfs: HfsFs) -> Self { - Self { - hfsfs: Arc::new(hfsfs), - } + pub fn new(fs: Box) -> Self { + Self { fs: Arc::from(fs) } } } -/// Initialize the VFS with an HFS+ filesystem -pub fn init(hfsfs: HfsFs) { +/// Initialize the VFS with a filesystem +pub fn init(fs: Box) { let mut vfs = VFS.lock(); - *vfs = Some(Vfs::new(hfsfs)); + *vfs = Some(Vfs::new(fs)); } /// Open a file by path @@ -63,12 +66,9 @@ pub fn open(path: &str) -> Option { let vfs = VFS.lock(); let vfs = vfs.as_ref()?; - vfs.hfsfs.open(path).map(|file| FileHandle { file }) + vfs.fs.open(path).map(|file| FileHandle { file }) } -use rand_chacha::ChaCha20Rng; -use rand_core::{RngCore, SeedableRng}; - static mut RANDOM_RNG: Option = None; struct RandomFile { diff --git a/src/kernel/src/virtio.rs b/src/kernel/src/virtio.rs index b60850e..556d30f 100644 --- a/src/kernel/src/virtio.rs +++ b/src/kernel/src/virtio.rs @@ -51,6 +51,7 @@ const PCI_CAP_PTR: usize = 0x34; // Virtio PCI capability types const VIRTIO_PCI_CAP_COMMON_CFG: u8 = 1; const VIRTIO_PCI_CAP_NOTIFY_CFG: u8 = 2; +const VIRTIO_PCI_CAP_DEVICE_CFG: u8 = 4; // Virtio PCI common configuration offsets const VIRTIO_PCI_COMMON_GFSELECT: usize = 0x08; @@ -127,6 +128,7 @@ pub struct VirtioBlk { req_buf: *mut VirtioBlkReq, status_buf: *mut u8, next_desc_idx: u16, + capacity: u64, } unsafe impl Send for VirtioBlk {} @@ -165,6 +167,8 @@ struct VirtioCaps { notify_bar: u8, notify_offset: u32, notify_off_mult: u32, + device_cfg_bar: u8, + device_cfg_offset: u32, } fn find_virtio_caps(bus: u8, dev: u8, func: u8) -> Option { @@ -181,6 +185,8 @@ fn find_virtio_caps(bus: u8, dev: u8, func: u8) -> Option { notify_bar: 0, notify_offset: 0, notify_off_mult: 0, + device_cfg_bar: 0, + device_cfg_offset: 0, }; while cap_ptr != 0 { @@ -199,6 +205,10 @@ fn find_virtio_caps(bus: u8, dev: u8, func: u8) -> Option { caps.notify_offset = offset; caps.notify_off_mult = pci_read32(bus, dev, func, cap_ptr + 16); } + VIRTIO_PCI_CAP_DEVICE_CFG => { + caps.device_cfg_bar = bar; + caps.device_cfg_offset = offset; + } _ => {} } } @@ -276,6 +286,7 @@ impl VirtioBlk { kprintln!("Virtio: BAR at {:x}", bar); let common_cfg = bar as usize + caps.common_cfg_offset as usize; let notify_cap_base = bar as usize + caps.notify_offset as usize; + let device_cfg = bar as usize + caps.device_cfg_offset as usize; unsafe { write_volatile((common_cfg + VIRTIO_PCI_COMMON_STATUS) as *mut u8, 0); @@ -348,6 +359,13 @@ impl VirtioBlk { kprintln!("Virtio: Blk device ready"); + let capacity = read_volatile(device_cfg as *const u64); + kprintln!( + "Virtio: Capacity {} sectors ({} MB)", + capacity, + (capacity * 512) / (1024 * 1024) + ); + Some(Self { notify_addr, desc, @@ -357,6 +375,7 @@ impl VirtioBlk { req_buf, status_buf, next_desc_idx: 0, + capacity, }) } } @@ -438,13 +457,12 @@ impl BlockReader for Mutex { } let end_sector = (offset + buf.len() as u64).div_ceil(512); let mut temp = vec![0u8; ((end_sector - start_sector) * 512) as usize]; - if blk.read_sectors(start_sector, &mut temp) { - let off = (offset % 512) as usize; - buf.copy_from_slice(&temp[off..off + buf.len()]); - true - } else { - false - } + + true + } + + fn size(&self) -> u64 { + self.lock().capacity * 512 } } diff --git a/src/kernel/src/zalloc.rs b/src/kernel/src/zalloc.rs index 13e84d7..3d0cf6a 100644 --- a/src/kernel/src/zalloc.rs +++ b/src/kernel/src/zalloc.rs @@ -1,7 +1,9 @@ #![allow(dead_code)] -use alloc::vec; use alloc::vec::Vec; +use ruzstd::decoding::StreamingDecoder; +use ruzstd::encoding::{CompressionLevel, compress_to_vec}; +use ruzstd::io::Read; /// A simple compressed memory allocator (zram-like). /// It takes input data (e.g. a page), compresses it using Zstd, @@ -21,21 +23,9 @@ pub struct ZAllocator { /// Compress data using Zstd. /// Returns a Vec containing the compressed data. pub fn zcompress(data: &[u8]) -> Result, &'static str> { - // zstd-safe is a safe wrapper around zstd-sys. - // It does not have stream::encode_all. We must use simple API. - - // Estimate bounds - let bound = zstd_safe::compress_bound(data.len()); - let mut buffer = vec![0u8; bound]; - - // Level 1 for speed - match zstd_safe::compress(&mut buffer[..], data, 1) { - Ok(len) => { - buffer.truncate(len); - Ok(buffer) - } - Err(_) => Err("Compression failed"), - } + // ruzstd::encoding::compress_to_vec returns Vec + // We use CompressionLevel::Default (equivalent to level 3 usually, but ruzstd is different) + Ok(compress_to_vec(data, CompressionLevel::Default)) } /// Decompress data using Zstd. @@ -44,30 +34,12 @@ pub fn zcompress(data: &[u8]) -> Result, &'static str> { /// For generic use, we might need a loop or header. /// However, zstd frames include content size if not disabled. pub fn zdecompress(data: &[u8]) -> Result, &'static str> { - // Try to find content size - let content_size = zstd_safe::get_frame_content_size(data).unwrap_or(None); - - let mut capacity = if let Some(size) = content_size { - size as usize - } else { - // Fallback: Guess 4x compression or just 4096 for pages - 4096 * 4 - }; - - // Safety check for OOM vectors - if capacity > 16 * 1024 * 1024 { - capacity = 16 * 1024 * 1024; - } - - let mut buffer = vec![0u8; capacity]; - - match zstd_safe::decompress(&mut buffer[..], data) { - Ok(len) => { - buffer.truncate(len); - Ok(buffer) - } - Err(_) => Err("Decompression failed"), - } + let mut decoder = StreamingDecoder::new(data).map_err(|_| "Decompression init failed")?; + let mut buffer = Vec::new(); + decoder + .read_to_end(&mut buffer) + .map_err(|_| "Decompression failed")?; + Ok(buffer) } /// A "ZRAM" block device simulator. /// Stores pages in a compressed format in memory. @@ -103,16 +75,12 @@ impl ZRamDevice { } if let Some(ref compressed) = self.blocks[index] { - // We know the expected output size is self.block_size - match zstd_safe::decompress(out, compressed) { - Ok(len) => { - if len != self.block_size { - return Err("Decompressed size mismatch"); - } - Ok(()) - } - Err(_) => Err("Decompression failed"), - } + let mut decoder = + StreamingDecoder::new(&compressed[..]).map_err(|_| "Decompression init failed")?; + decoder + .read_exact(out) + .map_err(|_| "Decompression failed")?; + Ok(()) } else { // Block not present, return zeros? out.fill(0); diff --git a/src/lib/apple-dmg/Cargo.toml b/src/lib/apple-dmg/Cargo.toml index 7c908c9..dbed066 100644 --- a/src/lib/apple-dmg/Cargo.toml +++ b/src/lib/apple-dmg/Cargo.toml @@ -8,14 +8,32 @@ path = "lib.rs" [dependencies] hfsplus = { path = "../hfsplus" } -anyhow = { workspace = true } -byteorder = { workspace = true } -crc32fast = { workspace = true } -fatfs = { workspace = true } -flate2 = { workspace = true } -fscommon = { workspace = true } -getrandom = { workspace = true } -md5 = { workspace = true } -plist = { workspace = true } -serde = { workspace = true } -serde_bytes = { workspace = true } +derive_more = { workspace = true } +rootcause = { workspace = true } +binrw = { workspace = true } +crc32fast = { version = "1.5", default-features = false } +fatfs = { version = "0.3", optional = true } +fscommon = { version = "0.1", optional = true } +getrandom = { version = "0.4", optional = true, default-features = false } +md5 = { version = "0.8", optional = true, default-features = false } +serde = { workspace = true, features = ["derive", "alloc"] } +serde_bytes = { version = "0.11", default-features = false, features = [ + "alloc", +] } +zlib-rs = { workspace = true } +plist = { workspace = true, optional = true } + +[features] +default = [] +std = [ + "derive_more/std", + "crc32fast/std", + "fatfs", + "fscommon", + "getrandom/std", + "md5", + "hfsplus/std", + "plist", + "binrw/std", + "serde_bytes/std", +] diff --git a/src/lib/apple-dmg/blkx.rs b/src/lib/apple-dmg/blkx.rs index 130d9ca..41ac5af 100644 --- a/src/lib/apple-dmg/blkx.rs +++ b/src/lib/apple-dmg/blkx.rs @@ -3,14 +3,22 @@ // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. -use { - crate::koly::UdifChecksum, - anyhow::Result, - byteorder::{BE, ReadBytesExt, WriteBytesExt}, - std::io::{Read, Write}, -}; +extern crate alloc; +use crate::alloc::string::ToString; +use alloc::vec; +use alloc::vec::Vec; + +use binrw::{BinRead, BinReaderExt, BinWrite, BinWriterExt, binrw}; + +use crate::koly::UdifChecksum; + +use crate::{DmgError, Result}; + +#[binrw] #[derive(Clone, Debug, Eq, PartialEq)] +#[br(big, magic = b"mish")] +#[bw(big, magic = b"mish")] pub struct BlkxTable { /// currently 1 pub version: u32, @@ -18,7 +26,7 @@ pub struct BlkxTable { pub sector_number: u64, /// number of sectors pub sector_count: u64, - /// seems to always be 0 + /// seems always to be 0 pub data_offset: u64, /// seems to be a magic constant for zlib describing the buffer size /// required for decompressing a chunk. @@ -28,6 +36,10 @@ pub struct BlkxTable { pub reserved: [u8; 24], pub checksum: UdifChecksum, /// chunk table + #[br(temp)] + #[bw(calc = chunks.len() as u32)] + num_chunks: u32, + #[br(count = num_chunks)] pub chunks: Vec, } @@ -64,56 +76,27 @@ impl BlkxTable { self.chunks.push(chunk); } - pub fn read_from(r: &mut R) -> Result { - let mut signature = [0; 4]; - r.read_exact(&mut signature)?; - anyhow::ensure!(&signature == b"mish"); - let version = r.read_u32::()?; - let sector_number = r.read_u64::()?; - let sector_count = r.read_u64::()?; - let data_offset = r.read_u64::()?; - let buffers_needed = r.read_u32::()?; - let block_descriptors = r.read_u32::()?; - let mut reserved = [0; 24]; - r.read_exact(&mut reserved)?; - let checksum = UdifChecksum::read_from(r)?; - let num_chunks = r.read_u32::()?; - let mut chunks = Vec::with_capacity(num_chunks as _); - for _ in 0..num_chunks { - chunks.push(BlkxChunk::read_from(r)?); - } - Ok(Self { - version, - sector_number, - sector_count, - data_offset, - buffers_needed, - block_descriptors, - reserved, - checksum, - chunks, + pub fn read_from(r: &mut R) -> Result { + r.read_be().map_err(|e| { + if let binrw::Error::BadMagic { .. } = &e { + DmgError::InvalidSignature { + expected: *b"mish", + found: [0, 0, 0, 0], // Simplified + } + } else { + DmgError::Io(e.to_string()) + } }) } - pub fn write_to(&self, w: &mut W) -> Result<()> { - w.write_all(b"mish")?; - w.write_u32::(self.version)?; - w.write_u64::(self.sector_number)?; - w.write_u64::(self.sector_count)?; - w.write_u64::(self.data_offset)?; - w.write_u32::(self.buffers_needed)?; - w.write_u32::(self.block_descriptors)?; - w.write_all(&self.reserved)?; - self.checksum.write_to(w)?; - w.write_u32::(self.chunks.len() as u32)?; - for chunk in &self.chunks { - chunk.write_to(w)?; - } - Ok(()) + pub fn write_to(&self, w: &mut W) -> Result<()> { + w.write_be(self).map_err(|e| DmgError::Io(e.to_string())) } } -#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, BinRead, BinWrite)] +#[br(big)] +#[bw(big)] pub struct BlkxChunk { /// compression type used for this chunk pub r#type: u32, @@ -159,33 +142,6 @@ impl BlkxChunk { Self::new(ChunkType::Term, sector_number, 0, compressed_offset, 0) } - pub fn read_from(r: &mut R) -> Result { - let r#type = r.read_u32::()?; - let comment = r.read_u32::()?; - let sector_number = r.read_u64::()?; - let sector_count = r.read_u64::()?; - let compressed_offset = r.read_u64::()?; - let compressed_length = r.read_u64::()?; - Ok(Self { - r#type, - comment, - sector_number, - sector_count, - compressed_offset, - compressed_length, - }) - } - - pub fn write_to(&self, w: &mut W) -> Result<()> { - w.write_u32::(self.r#type)?; - w.write_u32::(self.comment)?; - w.write_u64::(self.sector_number)?; - w.write_u64::(self.sector_count)?; - w.write_u64::(self.compressed_offset)?; - w.write_u64::(self.compressed_length)?; - Ok(()) - } - pub fn ty(self) -> Option { ChunkType::from_u32(self.r#type) } diff --git a/src/lib/apple-dmg/koly.rs b/src/lib/apple-dmg/koly.rs index 4eacba6..3a195b0 100644 --- a/src/lib/apple-dmg/koly.rs +++ b/src/lib/apple-dmg/koly.rs @@ -3,13 +3,15 @@ // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. -use { - anyhow::Result, - byteorder::{BE, ReadBytesExt, WriteBytesExt}, - std::io::{Read, Seek, SeekFrom, Write}, -}; -#[derive(Clone, Copy, Debug, Eq, PartialEq)] +use crate::alloc::string::ToString; +use binrw::{BinRead, BinReaderExt, BinWrite, BinWriterExt, binrw}; + +use crate::{DmgError, Result}; + +#[derive(Clone, Copy, Debug, Eq, PartialEq, BinRead, BinWrite)] +#[br(big)] +#[bw(big)] pub struct UdifChecksum { pub r#type: u32, pub size: u32, @@ -39,21 +41,6 @@ impl UdifChecksum { pub fn from_bytes(bytes: &[u8]) -> Self { Self::new(crc32fast::hash(bytes)) } - - pub fn read_from(r: &mut R) -> Result { - let r#type = r.read_u32::()?; - let size = r.read_u32::()?; - let mut data = [0; 128]; - r.read_exact(&mut data)?; - Ok(Self { r#type, size, data }) - } - - pub fn write_to(&self, w: &mut W) -> Result<()> { - w.write_u32::(self.r#type)?; - w.write_u32::(self.size)?; - w.write_all(&self.data)?; - Ok(()) - } } impl From for u32 { @@ -69,11 +56,15 @@ const KOLY_SIZE: i64 = 512; /// DMG trailer describing file content. /// /// This is the main structure defining a DMG. +#[binrw] #[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[br(big, magic = b"koly")] +#[bw(big, magic = b"koly")] pub struct KolyTrailer { - // "koly" signature: [u8; 4], pub version: u32, - //header_size: u32, + #[br(temp, assert(header_size == 512))] + #[bw(calc = 512)] + header_size: u32, pub flags: u32, pub running_data_fork_offset: u64, pub data_fork_offset: u64, @@ -125,6 +116,7 @@ impl Default for KolyTrailer { } impl KolyTrailer { + #[cfg(feature = "std")] pub fn new( data_fork_length: u64, sectors: u64, @@ -134,7 +126,7 @@ impl KolyTrailer { main_digest: u32, ) -> Self { let mut segment_id = [0; 16]; - getrandom::fill(&mut segment_id).unwrap(); + let _ = getrandom::fill(&mut segment_id); Self { data_fork_length, sector_count: sectors, @@ -150,88 +142,24 @@ impl KolyTrailer { /// Construct an instance by reading from a seekable reader. /// /// The trailer is the final 512 bytes of the seekable stream. - pub fn read_from(r: &mut R) -> Result { - r.seek(SeekFrom::End(-KOLY_SIZE))?; - - let mut signature = [0; 4]; - r.read_exact(&mut signature)?; - anyhow::ensure!(&signature == b"koly"); - let version = r.read_u32::()?; - let header_size = r.read_u32::()?; - anyhow::ensure!(header_size == 512); - let flags = r.read_u32::()?; - let running_data_fork_offset = r.read_u64::()?; - let data_fork_offset = r.read_u64::()?; - let data_fork_length = r.read_u64::()?; - let resource_fork_offset = r.read_u64::()?; - let resource_fork_length = r.read_u64::()?; - let segment_number = r.read_u32::()?; - let segment_count = r.read_u32::()?; - let mut segment_id = [0; 16]; - r.read_exact(&mut segment_id)?; - let data_fork_digest = UdifChecksum::read_from(r)?; - let plist_offset = r.read_u64::()?; - let plist_length = r.read_u64::()?; - let mut reserved1 = [0; 64]; - r.read_exact(&mut reserved1)?; - let code_signature_offset = r.read_u64::()?; - let code_signature_size = r.read_u64::()?; - let mut reserved2 = [0; 40]; - r.read_exact(&mut reserved2)?; - let main_digest = UdifChecksum::read_from(r)?; - let image_variant = r.read_u32::()?; - let sector_count = r.read_u64::()?; - let mut reserved3 = [0; 12]; - r.read_exact(&mut reserved3)?; - Ok(Self { - version, - flags, - running_data_fork_offset, - data_fork_offset, - data_fork_length, - resource_fork_offset, - resource_fork_length, - segment_number, - segment_count, - segment_id, - data_fork_digest, - plist_offset, - plist_length, - reserved1, - code_signature_offset, - code_signature_size, - reserved2, - main_digest, - image_variant, - sector_count, - reserved3, + pub fn read_from(r: &mut R) -> Result { + r.seek(binrw::io::SeekFrom::End(-KOLY_SIZE)) + .map_err(|e| DmgError::Io(e.to_string()))?; + r.read_be().map_err(|e| { + if let binrw::Error::AssertFail { .. } = &e { + DmgError::InvalidHeaderSize(0) // Simplified + } else if let binrw::Error::BadMagic { .. } = &e { + DmgError::InvalidSignature { + expected: *b"koly", + found: [0, 0, 0, 0], // Simplified + } + } else { + DmgError::Io(e.to_string()) + } }) } - pub fn write_to(&self, w: &mut W) -> Result<()> { - w.write_all(b"koly")?; - w.write_u32::(self.version)?; - w.write_u32::(KOLY_SIZE as u32)?; - w.write_u32::(self.flags)?; - w.write_u64::(self.running_data_fork_offset)?; - w.write_u64::(self.data_fork_offset)?; - w.write_u64::(self.data_fork_length)?; - w.write_u64::(self.resource_fork_offset)?; - w.write_u64::(self.resource_fork_length)?; - w.write_u32::(self.segment_number)?; - w.write_u32::(self.segment_count)?; - w.write_all(&self.segment_id)?; - self.data_fork_digest.write_to(w)?; - w.write_u64::(self.plist_offset)?; - w.write_u64::(self.plist_length)?; - w.write_all(&self.reserved1)?; - w.write_u64::(self.code_signature_offset)?; - w.write_u64::(self.code_signature_size)?; - w.write_all(&self.reserved2)?; - self.main_digest.write_to(w)?; - w.write_u32::(self.image_variant)?; - w.write_u64::(self.sector_count)?; - w.write_all(&self.reserved3)?; - Ok(()) + pub fn write_to(&self, w: &mut W) -> Result<()> { + w.write_be(self).map_err(|e| DmgError::Io(e.to_string())) } } diff --git a/src/lib/apple-dmg/lib.rs b/src/lib/apple-dmg/lib.rs index 327881f..21d3ef0 100644 --- a/src/lib/apple-dmg/lib.rs +++ b/src/lib/apple-dmg/lib.rs @@ -1,24 +1,65 @@ -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. -use { - anyhow::Result, - crc32fast::Hasher, +#![no_std] + +#[cfg(feature = "std")] +extern crate std; + +extern crate alloc; + +use alloc::collections::BTreeMap; +use alloc::string::ToString; +use alloc::vec; +use alloc::vec::Vec; + +use binrw::io::{Read, Seek, SeekFrom}; +use derive_more::derive::Display; + +#[cfg(feature = "std")] +use std::io::{BufReader, BufWriter}; + +#[cfg(feature = "std")] +use std::path::Path; + +#[derive(Debug, Display)] +pub enum DmgError { + #[display("Invalid signature: expected {expected:?}, found {found:?}")] + InvalidSignature { expected: [u8; 4], found: [u8; 4] }, + #[display("Invalid version: {_0}")] + InvalidVersion(u32), + #[display("Invalid header size: {_0}")] + InvalidHeaderSize(u32), + #[display("IO error: {_0}")] + Io(alloc::string::String), + #[display("Decompression failed: {_0}")] + DecompressionFailed(alloc::string::String), + #[display("Compression failed: {_0}")] + CompressionFailed(alloc::string::String), + #[display("Plist error: {_0}")] + PlistError(alloc::string::String), + #[display("Unsupported chunk type: {_0:?}")] + UnsupportedChunkType(u32), + #[display("Negative seek")] + NegativeSeek, + #[display("Other: {_0}")] + Other(alloc::string::String), +} + +#[cfg(feature = "std")] +impl std::error::Error for DmgError {} + +#[cfg(not(feature = "std"))] +impl core::error::Error for DmgError {} + +pub type Result = core::result::Result; + +#[cfg(feature = "std")] +pub use { fatfs::{Dir, FileSystem, FormatVolumeOptions, FsOptions, ReadWriteSeek}, - flate2::{Compression, bufread::ZlibDecoder, bufread::ZlibEncoder}, fscommon::BufStream, - std::{ - collections::BTreeMap, - fs::File, - io::{BufRead, BufReader, BufWriter, Cursor, Read, Seek, SeekFrom, Write}, - path::Path, - }, + std::fs::File, }; mod mbr; -use crate::mbr::{PartRecord, ProtectiveMBR}; +pub use crate::mbr::{PartRecord, ProtectiveMBR}; mod blkx; mod koly; @@ -32,21 +73,52 @@ pub struct DmgReader { r: R, } +#[cfg(feature = "std")] impl DmgReader> { pub fn open(path: &Path) -> Result { - let r = BufReader::with_capacity(10 * 1024 * 1024, File::open(path)?); + let r = BufReader::with_capacity( + 10 * 1024 * 1024, + File::open(path).map_err(|e| DmgError::Io(e.to_string()))?, + ); Self::new(r) } } -impl DmgReader { +impl DmgReader { pub fn new(mut r: R) -> Result { let koly = KolyTrailer::read_from(&mut r)?; - r.seek(SeekFrom::Start(koly.plist_offset))?; - let mut xml = Vec::with_capacity(koly.plist_length as usize); - (&mut r).take(koly.plist_length).read_to_end(&mut xml)?; - let xml: Plist = plist::from_reader_xml(&xml[..])?; - Ok(Self { koly, xml, r }) + r.seek(SeekFrom::Start(koly.plist_offset)) + .map_err(|e| DmgError::Io(e.to_string()))?; + let mut _xml_data = Vec::with_capacity(koly.plist_length as usize); + + #[cfg(feature = "std")] + { + use std::io::Read as _; + let r_std = &mut r; + r_std + .take(koly.plist_length) + .read_to_end(&mut _xml_data) + .map_err(|e| DmgError::Io(e.to_string()))?; + } + #[cfg(not(feature = "std"))] + { + let mut buf = vec![0u8; koly.plist_length as usize]; + r.read_exact(&mut buf) + .map_err(|e| DmgError::Io(e.to_string()))?; + _xml_data = buf; + } + + #[cfg(not(feature = "plist"))] + return Err(DmgError::Other( + "Plist parsing not supported in no_std yet".to_string(), + )); + + #[cfg(feature = "plist")] + { + let xml: Plist = plist::from_reader_xml(&_xml_data[..]) + .map_err(|e| DmgError::PlistError(e.to_string()))?; + Ok(Self { koly, xml, r }) + } } pub fn koly(&self) -> &KolyTrailer { @@ -57,65 +129,96 @@ impl DmgReader { &self.xml } - pub fn sector(&mut self, chunk: &BlkxChunk) -> Result> { + #[cfg(feature = "std")] + pub fn sector( + &mut self, + chunk: &BlkxChunk, + ) -> Result> { let ty = chunk.ty().expect("unknown chunk type"); match ty { ChunkType::Ignore | ChunkType::Zero => { - Ok(Box::new(std::io::repeat(0).take(chunk.sector_count * 512))) + use std::io::Read as _; + Ok(alloc::boxed::Box::new( + std::io::repeat(0).take(chunk.sector_count * 512), + )) } - ChunkType::Comment => Ok(Box::new(std::io::empty())), + ChunkType::Comment => Ok(alloc::boxed::Box::new(std::io::empty())), ChunkType::Raw => { - self.r.seek(SeekFrom::Start(chunk.compressed_offset))?; - Ok(Box::new((&mut self.r).take(chunk.compressed_length))) + use std::io::Read as _; + self.r + .seek(SeekFrom::Start(chunk.compressed_offset)) + .map_err(|e| DmgError::Io(e.to_string()))?; + Ok(alloc::boxed::Box::new( + (&mut self.r).take(chunk.compressed_length), + )) } ChunkType::Zlib => { - self.r.seek(SeekFrom::Start(chunk.compressed_offset))?; - let compressed_chunk = (&mut self.r).take(chunk.compressed_length); - Ok(Box::new(ZlibDecoder::new(compressed_chunk))) + self.r + .seek(SeekFrom::Start(chunk.compressed_offset)) + .map_err(|e| DmgError::Io(e.to_string()))?; + let mut compressed = vec![0u8; chunk.compressed_length as usize]; + self.r + .read_exact(&mut compressed) + .map_err(|e| DmgError::Io(e.to_string()))?; + let decompressed = + decompress_zlib(&compressed, (chunk.sector_count * 512) as usize)?; + Ok(alloc::boxed::Box::new(std::io::Cursor::new(decompressed))) } ChunkType::Adc | ChunkType::Bzlib | ChunkType::Lzfse => unimplemented!(), - ChunkType::Term => Ok(Box::new(std::io::empty())), + ChunkType::Term => Ok(alloc::boxed::Box::new(std::io::empty())), } } - pub fn data_checksum(&mut self) -> Result { - self.r.seek(SeekFrom::Start(self.koly.data_fork_offset))?; - let mut data_fork = Vec::with_capacity(self.koly.data_fork_length as usize); - (&mut self.r) - .take(self.koly.data_fork_length) - .read_to_end(&mut data_fork)?; - Ok(crc32fast::hash(&data_fork)) - } - - pub fn partition_table(&self, i: usize) -> Result { - self.plist().partitions()[i].table() - } - - pub fn partition_name(&self, i: usize) -> &str { - &self.plist().partitions()[i].name - } - pub fn partition_data(&mut self, i: usize) -> Result> { - let table = self.plist().partitions()[i].table()?; - let mut partition = vec![]; - for chunk in &table.chunks { - std::io::copy(&mut self.sector(chunk)?, &mut partition)?; + let table = self.plist().partitions()[i] + .table() + .map_err(|e| DmgError::Other(e.to_string()))?; + + #[cfg(feature = "std")] + { + let mut partition = Vec::new(); + for chunk in &table.chunks { + if chunk.ty() == Some(ChunkType::Term) { + continue; + } + let mut sector_reader = self.sector(chunk)?; + std::io::copy(&mut sector_reader, &mut partition) + .map_err(|e| DmgError::Io(e.to_string()))?; + } + Ok(partition) + } + #[cfg(not(feature = "std"))] + { + let _ = table; + return Err(DmgError::Other( + "partition_data not supported in no_std".to_string(), + )); } - Ok(partition) } - pub fn copy_partition_to(&mut self, i: usize, mut writer: W) -> Result { - let table = self.plist().partitions()[i].table()?; + #[cfg(feature = "std")] + pub fn copy_partition_to(&mut self, i: usize, mut writer: W) -> Result { + let table = self.plist().partitions()[i] + .table() + .map_err(|e| DmgError::Other(e.to_string()))?; let mut total = 0; let mut buffer = vec![0u8; 1024 * 1024]; for chunk in &table.chunks { + if chunk.ty() == Some(ChunkType::Term) { + continue; + } let mut sector_reader = self.sector(chunk)?; loop { - let n = sector_reader.read(&mut buffer)?; + use std::io::Read as _; + let n = sector_reader + .read(&mut buffer) + .map_err(|e| DmgError::Io(e.to_string()))?; if n == 0 { break; } - writer.write_all(&buffer[..n])?; + writer + .write_all(&buffer[..n]) + .map_err(|e| DmgError::Io(e.to_string()))?; total += n as u64; } } @@ -123,7 +226,9 @@ impl DmgReader { } pub fn into_partition_reader(self, i: usize) -> Result> { - let table = self.plist().partitions()[i].table()?; + let table = self.plist().partitions()[i] + .table() + .map_err(|e| DmgError::Other(e.to_string()))?; let total_size = table .chunks .iter() @@ -141,7 +246,7 @@ impl DmgReader { } } -pub struct DmgPartitionReader { +pub struct DmgPartitionReader { r: R, chunks: Vec, pos: u64, @@ -152,17 +257,15 @@ pub struct DmgPartitionReader { const MAX_CACHE_CHUNKS: usize = 256; -impl DmgPartitionReader { +impl DmgPartitionReader { fn get_chunk_at_pos(&self, pos: u64) -> Option<(usize, &BlkxChunk)> { let sector = pos / 512; - // Fast path: check if we're still in the last accessed chunk or the next one if let Some(&last_idx) = self.cache_order.last() { let c = &self.chunks[last_idx]; if sector >= c.sector_number && sector < c.sector_number + c.sector_count { return Some((last_idx, c)); } - // Try next chunk too for sequential access if last_idx + 1 < self.chunks.len() { let c = &self.chunks[last_idx + 1]; if sector >= c.sector_number && sector < c.sector_number + c.sector_count { @@ -171,14 +274,13 @@ impl DmgPartitionReader { } } - // Binary search for chunk let result = self.chunks.binary_search_by(|c| { if sector < c.sector_number { - std::cmp::Ordering::Greater + core::cmp::Ordering::Greater } else if sector >= c.sector_number + c.sector_count { - std::cmp::Ordering::Less + core::cmp::Ordering::Less } else { - std::cmp::Ordering::Equal + core::cmp::Ordering::Equal } }); @@ -190,7 +292,6 @@ impl DmgPartitionReader { fn load_chunk(&mut self, idx: usize) -> Result<()> { if self.cache.contains_key(&idx) { - // Update cache order for LRU if let Some(pos) = self.cache_order.iter().position(|&i| i == idx) { self.cache_order.remove(pos); } @@ -208,17 +309,26 @@ impl DmgPartitionReader { } ChunkType::Comment => {} ChunkType::Raw => { - self.r.seek(SeekFrom::Start(chunk.compressed_offset))?; + self.r + .seek(SeekFrom::Start(chunk.compressed_offset)) + .map_err(|e| DmgError::Io(e.to_string()))?; data.resize(chunk.compressed_length as usize, 0); - self.r.read_exact(&mut data)?; + self.r + .read_exact(&mut data) + .map_err(|e| DmgError::Io(e.to_string()))?; } ChunkType::Zlib => { - self.r.seek(SeekFrom::Start(chunk.compressed_offset))?; - let compressed_chunk = (&mut self.r).take(chunk.compressed_length); - let mut decoder = ZlibDecoder::new(compressed_chunk); - decoder.read_to_end(&mut data)?; + self.r + .seek(SeekFrom::Start(chunk.compressed_offset)) + .map_err(|e| DmgError::Io(e.to_string()))?; + let mut compressed = vec![0u8; chunk.compressed_length as usize]; + self.r + .read_exact(&mut compressed) + .map_err(|e| DmgError::Io(e.to_string()))?; + + data = decompress_zlib(&compressed, (chunk.sector_count * 512) as usize)?; } - _ => unimplemented!("Unsupported chunk type for seeking reader: {:?}", ty), + _ => return Err(DmgError::UnsupportedChunkType(chunk.r#type)), } if self.cache.len() >= MAX_CACHE_CHUNKS && !self.cache_order.is_empty() { @@ -230,41 +340,35 @@ impl DmgPartitionReader { self.cache_order.push(idx); Ok(()) } -} -impl Read for DmgPartitionReader { - fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + fn internal_read(&mut self, buf: &mut [u8]) -> Result { if buf.is_empty() { return Ok(0); } - let (idx, chunk_start_bytes, chunk_sector_count) = match self.get_chunk_at_pos(self.pos) { - Some((idx, chunk)) => (idx, chunk.sector_number * 512, chunk.sector_count), + let (idx, chunk_start_sectors, chunk_sector_count) = match self.get_chunk_at_pos(self.pos) { + Some((idx, chunk)) => (idx, chunk.sector_number, chunk.sector_count), None => return Ok(0), }; - if let Err(e) = self.load_chunk(idx) { - return Err(std::io::Error::other(e.to_string())); - } + self.load_chunk(idx)?; let chunk_data = &self.cache[&idx]; - let offset_in_chunk = (self.pos - chunk_start_bytes) as usize; + let offset_in_chunk = (self.pos - chunk_start_sectors * 512) as usize; let available = chunk_data.len().saturating_sub(offset_in_chunk); if available == 0 { - self.pos = chunk_start_bytes + chunk_sector_count * 512; - return self.read(buf); + self.pos = (chunk_start_sectors + chunk_sector_count) * 512; + return self.internal_read(buf); } - let n = std::cmp::min(buf.len(), available); + let n = core::cmp::min(buf.len(), available); buf[..n].copy_from_slice(&chunk_data[offset_in_chunk..offset_in_chunk + n]); self.pos += n as u64; Ok(n) } -} -impl Seek for DmgPartitionReader { - fn seek(&mut self, pos: SeekFrom) -> std::io::Result { + fn internal_seek(&mut self, pos: SeekFrom) -> Result { let new_pos = match pos { SeekFrom::Start(s) => s as i64, SeekFrom::Current(c) => self.pos as i64 + c, @@ -272,10 +376,7 @@ impl Seek for DmgPartitionReader { }; if new_pos < 0 { - return Err(std::io::Error::new( - std::io::ErrorKind::InvalidInput, - "negative seek", - )); + return Err(DmgError::NegativeSeek); } self.pos = new_pos as u64; @@ -283,56 +384,91 @@ impl Seek for DmgPartitionReader { } } -impl hfsplus::Read for DmgPartitionReader { +impl Read for DmgPartitionReader { + fn read(&mut self, buf: &mut [u8]) -> binrw::io::Result { + self.internal_read(buf) + .map_err(|e| binrw::io::Error::new(binrw::io::ErrorKind::Other, e.to_string())) + } +} + +impl Seek for DmgPartitionReader { + fn seek(&mut self, pos: SeekFrom) -> binrw::io::Result { + self.internal_seek(pos) + .map_err(|e| binrw::io::Error::new(binrw::io::ErrorKind::Other, e.to_string())) + } +} + +impl hfsplus::Read for DmgPartitionReader { fn read(&mut self, buf: &mut [u8]) -> hfsplus::Result { - Read::read(self, buf).map_err(|e| hfsplus::Error::InvalidData(e.to_string())) + self.internal_read(buf) + .map_err(|e| hfsplus::Error::InvalidData(e.to_string())) } } -impl hfsplus::Seek for DmgPartitionReader { +impl hfsplus::Seek for DmgPartitionReader { fn seek(&mut self, pos: hfsplus::SeekFrom) -> hfsplus::Result { - let std_pos = match pos { + let pos = match pos { hfsplus::SeekFrom::Start(s) => SeekFrom::Start(s), hfsplus::SeekFrom::Current(c) => SeekFrom::Current(c), hfsplus::SeekFrom::End(e) => SeekFrom::End(e), }; - Seek::seek(self, std_pos).map_err(|e| hfsplus::Error::InvalidData(e.to_string())) + self.internal_seek(pos) + .map_err(|e| hfsplus::Error::InvalidData(e.to_string())) } } -pub struct DmgWriter { +#[cfg(feature = "std")] +pub struct DmgWriter { xml: Plist, w: W, - data_hasher: Hasher, - main_hasher: Hasher, + data_hasher: crc32fast::Hasher, + main_hasher: crc32fast::Hasher, sector_number: u64, compressed_offset: u64, } +#[cfg(feature = "std")] impl DmgWriter> { pub fn create(path: &Path) -> Result { - let w = BufWriter::new(File::create(path)?); + let w = BufWriter::new( + File::options() + .read(true) + .write(true) + .create(true) + .truncate(true) + .open(path) + .map_err(|e| DmgError::Io(e.to_string()))?, + ); Ok(Self::new(w)) } } -impl DmgWriter { +#[cfg(feature = "std")] +impl DmgWriter { pub fn new(w: W) -> Self { Self { xml: Default::default(), w, - data_hasher: Hasher::new(), - main_hasher: Hasher::new(), + data_hasher: crc32fast::Hasher::new(), + main_hasher: crc32fast::Hasher::new(), sector_number: 0, compressed_offset: 0, } } pub fn create_fat32(mut self, fat32: &[u8]) -> Result<()> { - anyhow::ensure!(fat32.len() % 512 == 0); + if fat32.len() % 512 != 0 { + return Err(DmgError::Other( + "FAT32 size must be multiple of 512".to_string(), + )); + } let sector_count = fat32.len() as u64 / 512; let mut mbr = ProtectiveMBR::new(); - let mut partition = PartRecord::new_protective(Some(sector_count.try_into()?)); + let mut partition = PartRecord::new_protective(Some( + sector_count + .try_into() + .map_err(|_| DmgError::Other("Too many sectors".to_string()))?, + )); partition.os_type = 11; mbr.set_partition(0, partition); let mbr = mbr.to_bytes().to_vec(); @@ -343,17 +479,21 @@ impl DmgWriter { } pub fn add_partition(&mut self, name: &str, bytes: &[u8]) -> Result<()> { - anyhow::ensure!(bytes.len() % 512 == 0); + if bytes.len() % 512 != 0 { + return Err(DmgError::Other( + "Partition size must be multiple of 512".to_string(), + )); + } let id = self.xml.partitions().len() as u32; let name = name.to_string(); let mut table = BlkxTable::new(id, self.sector_number, crc32fast::hash(bytes)); for chunk in bytes.chunks(2048 * 512) { - let mut encoder = ZlibEncoder::new(chunk, Compression::best()); - let mut compressed = vec![]; - encoder.read_to_end(&mut compressed)?; + let compressed = compress_zlib(chunk)?; let compressed_length = compressed.len() as u64; let sector_count = chunk.len() as u64 / 512; - self.w.write_all(&compressed)?; + self.w + .write_all(&compressed) + .map_err(|e| DmgError::Io(e.to_string()))?; self.data_hasher.update(&compressed); table.add_chunk(BlkxChunk::new( ChunkType::Zlib, @@ -374,8 +514,11 @@ impl DmgWriter { pub fn finish(mut self) -> Result<()> { let mut xml = vec![]; - plist::to_writer_xml(&mut xml, &self.xml)?; - let pos = self.w.stream_position()?; + plist::to_writer_xml(&mut xml, &self.xml).map_err(|e| DmgError::Other(e.to_string()))?; + let pos = self + .w + .seek(std::io::SeekFrom::End(0)) + .map_err(|e| DmgError::Io(e.to_string()))?; let data_digest = self.data_hasher.finalize(); let main_digest = self.main_hasher.finalize(); let koly = KolyTrailer::new( @@ -386,65 +529,200 @@ impl DmgWriter { data_digest, main_digest, ); - self.w.write_all(&xml)?; + self.w + .write_all(&xml) + .map_err(|e| DmgError::Io(e.to_string()))?; koly.write_to(&mut self.w)?; Ok(()) } } // https://wiki.samba.org/index.php/UNIX_Extensions#Storing_symlinks_on_Windows_servers +#[cfg(feature = "std")] fn symlink(target: &str) -> Result> { - let xsym = format!( + let xsym = alloc::format!( "XSym\n{:04}\n{:x}\n{}\n", target.len(), md5::compute(target.as_bytes()), target, ); let mut xsym = xsym.into_bytes(); - anyhow::ensure!(xsym.len() <= 1067); + if xsym.len() > 1067 { + return Err(DmgError::Other("Symlink target too long".to_string())); + } xsym.resize(1067, b' '); Ok(xsym) } +#[cfg(feature = "std")] fn add_dir(src: &Path, dest: &Dir<'_, T>) -> Result<()> { - for entry in std::fs::read_dir(src)? { - let entry = entry?; + for entry in std::fs::read_dir(src).map_err(|e| DmgError::Io(e.to_string()))? { + let entry = entry.map_err(|e| DmgError::Io(e.to_string()))?; let file_name = entry.file_name(); let file_name = file_name.to_str().unwrap(); let source = src.join(file_name); - let file_type = entry.file_type()?; + let file_type = entry.file_type().map_err(|e| DmgError::Io(e.to_string()))?; if file_type.is_dir() { - let d = dest.create_dir(file_name)?; + let d = dest + .create_dir(file_name) + .map_err(|e| DmgError::Other(e.to_string()))?; add_dir(&source, &d)?; } else if file_type.is_file() { - let mut f = dest.create_file(file_name)?; - std::io::copy(&mut File::open(source)?, &mut f)?; + let mut f = dest + .create_file(file_name) + .map_err(|e| DmgError::Other(e.to_string()))?; + std::io::copy( + &mut std::fs::File::open(source).map_err(|e| DmgError::Io(e.to_string()))?, + &mut f, + ) + .map_err(|e| DmgError::Io(e.to_string()))?; } else if file_type.is_symlink() { - let target = std::fs::read_link(&source)?; + let target = std::fs::read_link(&source).map_err(|e| DmgError::Io(e.to_string()))?; let xsym = symlink(target.to_str().unwrap())?; - let mut f = dest.create_file(file_name)?; - std::io::copy(&mut &xsym[..], &mut f)?; + let mut f = dest + .create_file(file_name) + .map_err(|e| DmgError::Other(e.to_string()))?; + std::io::copy(&mut &xsym[..], &mut f).map_err(|e| DmgError::Io(e.to_string()))?; } } Ok(()) } +#[cfg(feature = "std")] pub fn create_dmg(dir: &Path, dmg: &Path, volume_label: &str, total_sectors: u32) -> Result<()> { let mut fat32 = vec![0; total_sectors as usize * 512]; { let mut volume_label_bytes = [0; 11]; - let end = std::cmp::min(volume_label_bytes.len(), volume_label.len()); + let end = core::cmp::min(volume_label_bytes.len(), volume_label.len()); volume_label_bytes[..end].copy_from_slice(&volume_label.as_bytes()[..end]); let volume_options = FormatVolumeOptions::new() .volume_label(volume_label_bytes) .bytes_per_sector(512) .total_sectors(total_sectors); - let mut disk = BufStream::new(Cursor::new(&mut fat32)); - fatfs::format_volume(&mut disk, volume_options)?; - let fs = FileSystem::new(disk, FsOptions::new())?; + let mut disk = BufStream::new(binrw::io::Cursor::new(&mut fat32)); + fatfs::format_volume(&mut disk, volume_options) + .map_err(|e| DmgError::Other(e.to_string()))?; + let fs = + FileSystem::new(disk, FsOptions::new()).map_err(|e| DmgError::Other(e.to_string()))?; let file_name = dir.file_name().unwrap().to_str().unwrap(); - let dest = fs.root_dir().create_dir(file_name)?; + let dest = fs + .root_dir() + .create_dir(file_name) + .map_err(|e| DmgError::Other(e.to_string()))?; add_dir(dir, &dest)?; } DmgWriter::create(dmg)?.create_fat32(&fat32) } + +fn decompress_zlib(compressed: &[u8], uncompressed_size: usize) -> Result> { + use zlib_rs::ReturnCode; + use zlib_rs::c_api::z_stream; + use zlib_rs::inflate::{InflateConfig, InflateStream, inflate, init}; + + unsafe extern "C" fn zalloc_dmg( + _opaque: *mut core::ffi::c_void, + items: core::ffi::c_uint, + size: core::ffi::c_uint, + ) -> *mut core::ffi::c_void { + let size = items as usize * size as usize; + let layout = match core::alloc::Layout::from_size_align(size + 16, 16) { + Ok(l) => l, + Err(_) => return core::ptr::null_mut(), + }; + let ptr = unsafe { alloc::alloc::alloc(layout) }; + if ptr.is_null() { + return core::ptr::null_mut(); + } + unsafe { + ptr.cast::().write(size); + ptr.add(16).cast() + } + } + + unsafe extern "C" fn zfree_dmg(_opaque: *mut core::ffi::c_void, ptr: *mut core::ffi::c_void) { + if ptr.is_null() { + return; + } + unsafe { + let real_ptr = ptr.sub(16); + let size = real_ptr.cast::().read(); + let layout = core::alloc::Layout::from_size_align(size + 16, 16).unwrap(); + alloc::alloc::dealloc(real_ptr.cast(), layout); + } + } + + let mut strm = z_stream { + next_in: compressed.as_ptr() as *mut _, + avail_in: compressed.len() as _, + zalloc: Some(zalloc_dmg), + zfree: Some(zfree_dmg), + opaque: core::ptr::null_mut(), + ..Default::default() + }; + + let config = InflateConfig { window_bits: 15 }; + + if init(&mut strm, config) != ReturnCode::Ok { + return Err(DmgError::DecompressionFailed( + "inflateInit failed".to_string(), + )); + } + + let mut decompressed = vec![0u8; uncompressed_size]; + let strm_infl = unsafe { InflateStream::from_stream_mut(&mut strm).unwrap() }; + + strm.next_out = decompressed.as_mut_ptr(); + strm.avail_out = decompressed.len() as _; + + let ret = unsafe { inflate(strm_infl, zlib_rs::InflateFlush::Finish) }; + let _ = zlib_rs::inflate::end(strm_infl); + + if ret == ReturnCode::StreamEnd || (ret == ReturnCode::Ok && strm.avail_out == 0) { + Ok(decompressed) + } else { + Err(DmgError::DecompressionFailed(alloc::format!( + "inflate failed: {:?}", + ret + ))) + } +} + +#[cfg(feature = "std")] +fn compress_zlib(data: &[u8]) -> Result> { + use zlib_rs::ReturnCode; + use zlib_rs::c_api::z_stream; + use zlib_rs::deflate::{DeflateConfig, DeflateStream, deflate, init}; + + let mut strm = z_stream { + next_in: data.as_ptr() as *mut _, + avail_in: data.len() as _, + ..Default::default() + }; + + let config = DeflateConfig::new(6); // Default compression + + if init(&mut strm, config) != ReturnCode::Ok { + return Err(DmgError::CompressionFailed( + "deflateInit failed".to_string(), + )); + } + + let mut compressed = vec![0u8; data.len() + 128]; // Slightly larger buffer + strm.next_out = compressed.as_mut_ptr(); + strm.avail_out = compressed.len() as _; + + let deflate_strm = unsafe { DeflateStream::from_stream_mut(&mut strm).unwrap() }; + let ret = deflate(deflate_strm, zlib_rs::DeflateFlush::Finish); + let _ = zlib_rs::deflate::end(deflate_strm); + + if ret == ReturnCode::StreamEnd { + let len = compressed.len() - strm.avail_out as usize; + compressed.truncate(len); + Ok(compressed) + } else { + Err(DmgError::CompressionFailed(alloc::format!( + "deflate failed: {:?}", + ret + ))) + } +} diff --git a/src/lib/apple-dmg/mbr.rs b/src/lib/apple-dmg/mbr.rs index 8753eac..81c615a 100644 --- a/src/lib/apple-dmg/mbr.rs +++ b/src/lib/apple-dmg/mbr.rs @@ -1,6 +1,8 @@ -use byteorder::{ByteOrder, LittleEndian}; +use binrw::{BinRead, BinWrite}; -#[derive(Clone, Copy, Debug, Default)] +#[derive(Clone, Copy, Debug, Default, BinRead, BinWrite)] +#[br(little)] +#[bw(little)] pub struct PartRecord { pub boot_indicator: u8, pub start_head: u8, @@ -30,30 +32,24 @@ impl PartRecord { lb_len, } } - - pub fn write_to(&self, buf: &mut [u8]) { - buf[0] = self.boot_indicator; - buf[1] = self.start_head; - buf[2] = self.start_sector; - buf[3] = self.start_track; - buf[4] = self.os_type; - buf[5] = self.end_head; - buf[6] = self.end_sector; - buf[7] = self.end_track; - LittleEndian::write_u32(&mut buf[8..12], self.lb_start); - LittleEndian::write_u32(&mut buf[12..16], self.lb_len); - } } -#[derive(Debug)] +#[derive(Debug, BinRead, BinWrite)] +#[br(little)] +#[bw(little)] pub struct ProtectiveMBR { + #[br(seek_before = binrw::io::SeekFrom::Start(446))] + #[bw(seek_before = binrw::io::SeekFrom::Start(446))] pub partitions: [PartRecord; 4], + #[br(assert(signature == [0x55, 0xAA]))] + pub signature: [u8; 2], } impl ProtectiveMBR { pub fn new() -> Self { Self { partitions: [PartRecord::default(); 4], + signature: [0x55, 0xAA], } } @@ -64,71 +60,10 @@ impl ProtectiveMBR { } pub fn to_bytes(&self) -> [u8; 512] { + use binrw::BinWriterExt; let mut buf = [0u8; 512]; - // Partition table at offset 446 - for (i, p) in self.partitions.iter().enumerate() { - let offset = 446 + i * 16; - p.write_to(&mut buf[offset..offset + 16]); - } - - // Signature - buf[510] = 0x55; - buf[511] = 0xAA; - + let mut cursor = binrw::io::Cursor::new(&mut buf[..]); + cursor.write_le(self).unwrap(); buf } - - #[allow(dead_code)] - pub fn from_bytes(bytes: &[u8]) -> Result { - if bytes.len() < 512 { - return Err("buffer too small"); - } - if bytes[510] != 0x55 || bytes[511] != 0xAA { - return Err("invalid signature"); - } - let mut partitions = [PartRecord::default(); 4]; - #[allow(clippy::needless_range_loop)] - for i in 0..4 { - let offset = 446 + i * 16; - let p = &bytes[offset..offset + 16]; - partitions[i] = PartRecord { - boot_indicator: p[0], - start_head: p[1], - start_sector: p[2], - start_track: p[3], - os_type: p[4], - end_head: p[5], - end_sector: p[6], - end_track: p[7], - lb_start: LittleEndian::read_u32(&p[8..12]), - lb_len: LittleEndian::read_u32(&p[12..16]), - }; - } - Ok(Self { partitions }) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_mbr_serialization() { - let mut mbr = ProtectiveMBR::new(); - let mut p = PartRecord::new_protective(Some(1000)); - p.os_type = 0x0B; - mbr.set_partition(0, p); - - let bytes = mbr.to_bytes(); - assert_eq!(bytes[510], 0x55); - assert_eq!(bytes[511], 0xAA); - - // Check partition 1 - let offset = 446; - assert_eq!(bytes[offset + 4], 0x0B); // os_type - let start = LittleEndian::read_u32(&bytes[offset + 8..]); - let len = LittleEndian::read_u32(&bytes[offset + 12..]); - assert_eq!(start, 1); - assert_eq!(len, 1000); - } } diff --git a/src/lib/apple-dmg/xml.rs b/src/lib/apple-dmg/xml.rs index e2e012f..c5ca113 100644 --- a/src/lib/apple-dmg/xml.rs +++ b/src/lib/apple-dmg/xml.rs @@ -3,12 +3,18 @@ // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. +extern crate alloc; +use alloc::string::{String, ToString}; +use alloc::vec; +use alloc::vec::Vec; + use { crate::blkx::BlkxTable, - anyhow::Result, serde::{Deserialize, Serialize}, }; +use crate::Result; + #[derive(Clone, Debug, Default, Deserialize, Serialize)] pub struct Plist { #[serde(rename = "resource-fork")] @@ -51,7 +57,8 @@ pub struct Partition { impl Partition { pub fn new(id: i32, name: String, table: BlkxTable) -> Self { let mut data = vec![]; - table.write_to(&mut data).unwrap(); + let mut cursor = binrw::io::Cursor::new(&mut data); + table.write_to(&mut cursor).unwrap(); Self { attributes: "0x0050".to_string(), cfname: name.clone(), @@ -62,6 +69,7 @@ impl Partition { } pub fn table(&self) -> Result { - BlkxTable::read_from(&mut &self.data[..]) + let mut cursor = binrw::io::Cursor::new(&self.data[..]); + BlkxTable::read_from(&mut cursor) } } diff --git a/src/lib/hfsplus/Cargo.toml b/src/lib/hfsplus/Cargo.toml index 77b33e4..af9e577 100644 --- a/src/lib/hfsplus/Cargo.toml +++ b/src/lib/hfsplus/Cargo.toml @@ -13,3 +13,6 @@ hashbrown = { workspace = true } binrw = { workspace = true } spin = { workspace = true } zlib-rs = { workspace = true } + +[features] +std = ["unicode-normalization/std"] diff --git a/src/lib/hfsplus/lib.rs b/src/lib/hfsplus/lib.rs index fa5a149..95e9c1b 100644 --- a/src/lib/hfsplus/lib.rs +++ b/src/lib/hfsplus/lib.rs @@ -64,6 +64,12 @@ pub trait Read { } } +impl Read for alloc::boxed::Box { + fn read(&mut self, buf: &mut [u8]) -> Result { + (**self).read(buf) + } +} + pub trait Write { fn write(&mut self, buf: &[u8]) -> Result; fn write_all(&mut self, mut buf: &[u8]) -> Result<()> { @@ -82,10 +88,22 @@ pub trait Write { } } +impl Write for alloc::boxed::Box { + fn write(&mut self, buf: &[u8]) -> Result { + (**self).write(buf) + } +} + pub trait Seek { fn seek(&mut self, pos: SeekFrom) -> Result; } +impl Seek for alloc::boxed::Box { + fn seek(&mut self, pos: SeekFrom) -> Result { + (**self).seek(pos) + } +} + pub trait ReadExt: Read { fn read_u16_be(&mut self) -> Result { let mut buf = [0u8; 2]; diff --git a/src/lib/ipsw-downloader/Cargo.toml b/src/lib/ipsw-downloader/Cargo.toml index db2e9f1..c50cbb3 100644 --- a/src/lib/ipsw-downloader/Cargo.toml +++ b/src/lib/ipsw-downloader/Cargo.toml @@ -7,7 +7,8 @@ edition = "2024" reqwest = { workspace = true } plist = { workspace = true } serde = { workspace = true } -thiserror = { workspace = true } +derive_more = { workspace = true } +rootcause = { workspace = true } tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } [dev-dependencies] diff --git a/src/lib/ipsw-downloader/src/lib.rs b/src/lib/ipsw-downloader/src/lib.rs index 175accc..997dd1c 100644 --- a/src/lib/ipsw-downloader/src/lib.rs +++ b/src/lib/ipsw-downloader/src/lib.rs @@ -21,12 +21,35 @@ pub struct IPSWList { pub versions: Value, } -#[derive(thiserror::Error, Debug)] +use derive_more::derive::Display; + +#[derive(Debug, Display)] pub enum IPSWError { - #[error("Network error: {0}")] - Network(#[from] reqwest::Error), - #[error("Plist parsing error: {0}")] - Plist(#[from] plist::Error), + #[display("Network error: {_0}")] + Network(reqwest::Error), + #[display("Plist parsing error: {_0}")] + Plist(plist::Error), +} + +impl std::error::Error for IPSWError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + IPSWError::Network(e) => Some(e), + IPSWError::Plist(e) => Some(e), + } + } +} + +impl From for IPSWError { + fn from(err: reqwest::Error) -> Self { + IPSWError::Network(err) + } +} + +impl From for IPSWError { + fn from(err: plist::Error) -> Self { + IPSWError::Plist(err) + } } pub async fn get_ipsw_list() -> Result { diff --git a/src/lib/loader/Cargo.toml b/src/lib/loader/Cargo.toml index 8dcf51d..511fd74 100644 --- a/src/lib/loader/Cargo.toml +++ b/src/lib/loader/Cargo.toml @@ -5,4 +5,5 @@ edition = "2024" [dependencies] goblin = { workspace = true } -thiserror = { workspace = true } +derive_more = { workspace = true } +rootcause = { workspace = true } diff --git a/src/lib/loader/src/lib.rs b/src/lib/loader/src/lib.rs index e69097d..e4b9b3d 100644 --- a/src/lib/loader/src/lib.rs +++ b/src/lib/loader/src/lib.rs @@ -5,20 +5,35 @@ pub mod elf; pub mod macho; use alloc::string::String; -use thiserror::Error; +use derive_more::derive::Display; -#[derive(Debug, Error)] +#[derive(Debug, Display)] pub enum LoaderError { - #[error("Parse error: {0}")] - ParseError(#[from] goblin::error::Error), - #[error("Invalid magic: {0:#x}")] + #[display("Parse error: {_0}")] + ParseError(goblin::error::Error), + #[display("Invalid magic: {_0:#x}")] InvalidMagic(u32), - #[error("Relocation error: {0}")] + #[display("Relocation error: {_0}")] RelocationError(&'static str), - #[error("Missing symbol: {0}")] + #[display("Missing symbol: {_0}")] MissingSymbol(String), } +impl core::error::Error for LoaderError { + fn source(&self) -> Option<&(dyn core::error::Error + 'static)> { + match self { + LoaderError::ParseError(e) => Some(e), + _ => None, + } + } +} + +impl From for LoaderError { + fn from(err: goblin::error::Error) -> Self { + LoaderError::ParseError(err) + } +} + #[derive(Debug)] pub enum BinaryFormat { Elf, diff --git a/src/lib/vfdecrypt/Cargo.toml b/src/lib/vfdecrypt/Cargo.toml index 1138dc3..8b74b18 100644 --- a/src/lib/vfdecrypt/Cargo.toml +++ b/src/lib/vfdecrypt/Cargo.toml @@ -7,7 +7,8 @@ edition = "2024" path = "lib.rs" [dependencies] -thiserror = { workspace = true } +derive_more = { workspace = true } +rootcause = { workspace = true } aes = { workspace = true } hmac = { workspace = true } sha1 = { workspace = true } diff --git a/src/lib/vfdecrypt/lib.rs b/src/lib/vfdecrypt/lib.rs index beaf337..ecaf574 100644 --- a/src/lib/vfdecrypt/lib.rs +++ b/src/lib/vfdecrypt/lib.rs @@ -1,7 +1,7 @@ +use derive_more::derive::Display; use std::io::{Read, Seek, SeekFrom, Write}; use std::mem; use std::slice; -use thiserror::Error; use aes::Aes128; use cbc::cipher::{BlockDecryptMut, KeyIvInit}; @@ -12,42 +12,57 @@ use pbkdf2::pbkdf2_hmac; use rayon::prelude::*; use sha1::Sha1; -#[derive(Error, Debug)] +#[derive(Debug, Display)] pub enum VfDecryptError { - #[error("IO error: {0}")] - Io(#[from] std::io::Error), - #[error("Cipher error: {0}")] + #[display("IO error: {_0}")] + Io(std::io::Error), + #[display("Cipher error: {_0}")] Cipher(String), - #[error("Decryption error: {0}")] + #[display("Decryption error: {_0}")] Decryption(String), - #[error("Password is required")] + #[display("Password is required")] PasswordRequired, - #[error("Unsupported format")] + #[display("Unsupported format")] UnsupportedFormat, - #[error("Unsupported blob encryption key bits: {0}")] + #[display("Unsupported blob encryption key bits: {_0}")] UnsupportedKeyBits(u32), - #[error("Unsupported KDF algorithm: {0}")] + #[display("Unsupported KDF algorithm: {_0}")] UnsupportedKdfAlgorithm(u32), - #[error("Unsupported KDF PRNG algorithm: {0}")] + #[display("Unsupported KDF PRNG algorithm: {_0}")] UnsupportedKdfPrng(u32), - #[error("KDF salt length {0} exceeds buffer size")] + #[display("KDF salt length {_0} exceeds buffer size")] SaltTooLong(usize), - #[error("Header specifies IV size {0} which exceeds buffer size")] + #[display("Header specifies IV size {_0} which exceeds buffer size")] IvTooLong(usize), - #[error("Unsupported blob IV size: {0}. Expected 8 for TDES.")] + #[display("Unsupported blob IV size: {_0}. Expected 8 for TDES.")] UnsupportedIvSize(usize), - #[error("Keyblob size {0} exceeds buffer")] + #[display("Keyblob size {_0} exceeds buffer")] KeyblobTooLong(usize), - #[error("Decrypted key material too short")] + #[display("Decrypted key material too short")] KeyMaterialTooShort, - #[error("Decrypted keyblob too short: {0} bytes, expected at least 32")] + #[display("Decrypted keyblob too short: {_0} bytes, expected at least 32")] KeyblobTooShort(usize), - #[error("Hex decode error: {0}")] + #[display("Hex decode error: {_0}")] HexDecode(String), - #[error("Unknown error: {0}")] + #[display("Unknown error: {_0}")] Unknown(String), } +impl std::error::Error for VfDecryptError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + VfDecryptError::Io(e) => Some(e), + _ => None, + } + } +} + +impl From for VfDecryptError { + fn from(err: std::io::Error) -> Self { + VfDecryptError::Io(err) + } +} + type Result = core::result::Result; const PBKDF2_ITERATION_COUNT: u32 = 1000; diff --git a/src/tools/devicetree/Cargo.toml b/src/tools/devicetree/Cargo.toml new file mode 100644 index 0000000..3939401 --- /dev/null +++ b/src/tools/devicetree/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "devicetree" +version = "0.1.0" +edition = "2021" + +[[bin]] +name = "devicetree" +path = "src/main.rs" + +[dependencies] +derive_more = { workspace = true } +rootcause = { workspace = true } +aes = { workspace = true } +cbc = { workspace = true } +hex = { workspace = true } diff --git a/src/tools/devicetree/src/main.rs b/src/tools/devicetree/src/main.rs new file mode 100644 index 0000000..6ec5139 --- /dev/null +++ b/src/tools/devicetree/src/main.rs @@ -0,0 +1,337 @@ +use aes::Aes256; +use cbc::cipher::{BlockDecryptMut, KeyIvInit}; +use cbc::Decryptor; +use derive_more::derive::Display; +use std::fs::File; +use std::io::Read; + +#[derive(Debug, Display)] +pub enum DeviceTreeError { + #[display("I/O error: {_0}")] + Io(std::io::Error), + #[display("IMG3 parse error: {_0}")] + Img3(String), + #[display("Decryption error: {_0}")] + Decryption(String), + #[display("Device tree parse error: {_0}")] + Parse(String), +} + +impl std::error::Error for DeviceTreeError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + DeviceTreeError::Io(e) => Some(e), + _ => None, + } + } +} + +impl From for DeviceTreeError { + fn from(err: std::io::Error) -> Self { + DeviceTreeError::Io(err) + } +} + +type Result = std::result::Result; + +#[derive(Debug)] +struct Img3Header { + magic: u32, + full_size: u32, + ident: u32, +} + +#[derive(Debug)] +struct Img3Tag { + magic: u32, + data: Vec, +} + +#[derive(Debug)] +struct Img3File { + header: Img3Header, + tags: Vec, +} + +impl Img3File { + fn parse(data: &[u8]) -> Result { + if data.len() < 20 { + return Err(DeviceTreeError::Img3("File too small".to_string())); + } + + let magic = u32::from_le_bytes([data[0], data[1], data[2], data[3]]); + let full_size = u32::from_le_bytes([data[4], data[5], data[6], data[7]]); + let _data_size = u32::from_le_bytes([data[8], data[9], data[10], data[11]]); + let _skip_dist = u32::from_le_bytes([data[12], data[13], data[14], data[15]]); + let ident = u32::from_le_bytes([data[16], data[17], data[18], data[19]]); + + let header = Img3Header { + magic, + full_size, + ident, + }; + let mut tags = Vec::new(); + let mut offset = 20; + + while offset < data.len() { + if offset + 12 > data.len() { + break; + } + + let tag_magic = u32::from_le_bytes([ + data[offset], + data[offset + 1], + data[offset + 2], + data[offset + 3], + ]); + let total_length = u32::from_le_bytes([ + data[offset + 4], + data[offset + 5], + data[offset + 6], + data[offset + 7], + ]); + let data_length = u32::from_le_bytes([ + data[offset + 8], + data[offset + 9], + data[offset + 10], + data[offset + 11], + ]); + + if total_length < 12 || offset + total_length as usize > data.len() { + break; + } + + let tag_data = data[offset + 12..offset + 12 + data_length as usize].to_vec(); + tags.push(Img3Tag { + magic: tag_magic, + data: tag_data, + }); + + offset += total_length as usize; + } + + Ok(Img3File { header, tags }) + } + + fn get_data_section(&self) -> Option<&[u8]> { + self.tags + .iter() + .find(|tag| tag.magic == 0x44415441) // "DATA" + .map(|tag| tag.data.as_slice()) + } +} + +fn decrypt_payload(data: &[u8], key: &[u8], iv: &[u8]) -> Result> { + let mut buf = data.to_vec(); + let mut cipher = Decryptor::::new_from_slices(key, iv) + .map_err(|e| DeviceTreeError::Decryption(e.to_string()))?; + + for chunk in buf.chunks_mut(16) { + if chunk.len() == 16 { + cipher.decrypt_block_mut(chunk.into()); + } + } + Ok(buf) +} + +#[derive(Debug)] +struct DeviceTreeNode { + name: String, + properties: Vec, + children: Vec, +} + +#[derive(Debug)] +struct DeviceTreeProperty { + name: String, + value: Vec, +} + +fn parse_node(data: &[u8], offset: &mut usize) -> Result { + if *offset + 8 > data.len() { + return Err(DeviceTreeError::Parse("Unexpected end of data".to_string())); + } + + let num_props = u32::from_le_bytes([ + data[*offset], + data[*offset + 1], + data[*offset + 2], + data[*offset + 3], + ]); + let num_children = u32::from_le_bytes([ + data[*offset + 4], + data[*offset + 5], + data[*offset + 6], + data[*offset + 7], + ]); + *offset += 8; + + println!( + "Node at offset 0x{:x}: {} props, {} children", + *offset - 8, + num_props, + num_children + ); + if num_props > 1000 || num_children > 1000 { + return Err(DeviceTreeError::Parse(format!( + "Suspicious node counts: props={}, children={}", + num_props, num_children + ))); + } + + // Read properties + let mut properties = Vec::new(); + for _ in 0..num_props { + if *offset + 32 + 4 > data.len() { + break; + } + + let name = String::from_utf8_lossy(&data[*offset..*offset + 32]) + .trim_end_matches('\0') + .to_string(); + *offset += 32; + + let value_len = u32::from_le_bytes([ + data[*offset], + data[*offset + 1], + data[*offset + 2], + data[*offset + 3], + ]); + *offset += 4; + + if *offset + value_len as usize > data.len() { + break; + } + + let value = data[*offset..*offset + value_len as usize].to_vec(); + *offset += value_len as usize; + // Align to 4 bytes + *offset = (*offset + 3) & !3; + + properties.push(DeviceTreeProperty { name, value }); + } + + // Read children + let mut children = Vec::new(); + for _ in 0..num_children { + if let Ok(child) = parse_node(data, offset) { + children.push(child); + } + } + + let name = properties + .iter() + .find(|p| p.name == "name") + .map(|p| { + String::from_utf8_lossy(&p.value) + .trim_end_matches('\0') + .to_string() + }) + .unwrap_or_else(|| "unnamed".to_string()); + + Ok(DeviceTreeNode { + name, + properties, + children, + }) +} + +fn print_tree(node: &DeviceTreeNode, depth: usize) { + let indent = " ".repeat(depth); + println!("{}{}", indent, node.name); + + if node.name == "chosen" || node.name == "memory-map" { + for prop in &node.properties { + println!("{} prop {}: size={}", indent, prop.name, prop.value.len()); + if prop.value.len() == 8 { + let v1 = u32::from_le_bytes([ + prop.value[0], + prop.value[1], + prop.value[2], + prop.value[3], + ]); + let v2 = u32::from_le_bytes([ + prop.value[4], + prop.value[5], + prop.value[6], + prop.value[7], + ]); + println!("{} values: 0x{:08x} 0x{:08x}", indent, v1, v2); + } + } + } + + for prop in &node.properties { + if prop.name == "reg" && prop.value.len() >= 8 { + let addr = + u32::from_le_bytes([prop.value[0], prop.value[1], prop.value[2], prop.value[3]]); + let size = + u32::from_le_bytes([prop.value[4], prop.value[5], prop.value[6], prop.value[7]]); + println!("{} reg: base=0x{:08x} size=0x{:x}", indent, addr, size); + } + if prop.name == "compatible" { + let s = String::from_utf8_lossy(&prop.value) + .trim_matches('\0') + .to_string(); + println!("{} compatible: {}", indent, s); + } + } + + for child in &node.children { + print_tree(child, depth + 1); + } +} + +fn main() -> Result<()> { + let dt_path = "work/Firmware/all_flash/all_flash.k48ap.production/DeviceTree.k48ap.img3"; + let mut dt_file = File::open(dt_path)?; + let mut dt_data = Vec::new(); + dt_file.read_to_end(&mut dt_data)?; + + println!("Apple Device Tree Parser"); + println!("======================="); + + // Parse IMG3 and decrypt + let img3 = Img3File::parse(&dt_data)?; + let encrypted_data = img3 + .get_data_section() + .ok_or_else(|| DeviceTreeError::Img3("DATA section not found".to_string()))?; + + let iv = hex::decode("e0a3aa63dae431e573c9827dd3636dd1").unwrap(); + let key = + hex::decode("50208af7c2de617854635fb4fc4eaa8cddab0e9035ea25abf81b0fa8b0b5654f").unwrap(); + let decrypted_data = decrypt_payload(encrypted_data, &key, &iv)?; + + // Hexdump decrypted data + println!("Decrypted Data Hexdump (first 256 bytes):"); + for i in (0..256.min(decrypted_data.len())).step_by(16) { + print!("{:04x}: ", i); + for j in 0..16 { + if i + j < decrypted_data.len() { + print!("{:02x} ", decrypted_data[i + j]); + } + } + print!(" |"); + for j in 0..16 { + if i + j < decrypted_data.len() { + let c = decrypted_data[i + j]; + if c >= 0x20 && c <= 0x7E { + print!("{}", c as char); + } else { + print!("."); + } + } + } + println!("|"); + } + + // Parse the tree structure + println!("Parsing Device Tree structure..."); + let mut offset = 0; + let root = parse_node(&decrypted_data, &mut offset)?; + + // Print the full tree + print_tree(&root, 0); + + Ok(()) +} diff --git a/src/tools/emulator/Cargo.toml b/src/tools/emulator/Cargo.toml new file mode 100644 index 0000000..c2e8a49 --- /dev/null +++ b/src/tools/emulator/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "emulator" +edition = "2024" +version.workspace = true + +[dependencies] +cranelift = { version = "0.128.3", features = ["jit", "module", "native"] } +rasn = "0.28.7" +derive_more = { workspace = true } +rootcause = { workspace = true } +aes = { workspace = true } +cbc = { workspace = true } +hex = { workspace = true } diff --git a/src/tools/emulator/README.md b/src/tools/emulator/README.md new file mode 100644 index 0000000..ecc6441 --- /dev/null +++ b/src/tools/emulator/README.md @@ -0,0 +1,56 @@ +# iOS 5 Emulator + +A minimal iOS 5 iBEC emulator built with Cranelift, RASN, and Thiserror. + +## Components + +### 1. ARMv7 Decoder (`decoder.rs`) +- Decodes ARM instructions from binary data +- Supports data processing, load/store, and branch instructions +- Handles conditional execution and various operand types +- Implements proper ARM instruction format parsing + +### 2. IMG4 Parser (`img4.rs`) +- Parses IMG4/IMG3 firmware containers +- Handles DFU file format detection +- ASN.1 decoding with RASN library +- Error handling with thiserror + +### 3. CPU Emulator (`cpu.rs`) +- ARM CPU state simulation with 16 registers +- Memory management with HashMap-based storage +- Instruction execution engine +- Support for MOV, ADD, SUB, LDR, STR, B, BL, CMP instructions + +### 4. Main Emulator (`main.rs`) +- File loading and parsing +- AES-256-CBC decryption support +- Integration of all components +- CPU state tracking and debugging output + +## Features + +- **Firmware Loading**: Loads iBEC.k48ap.RELEASE.dfu files +- **Format Detection**: Auto-detects DFU vs raw IMG4 format +- **Decryption**: AES-256-CBC with provided IV/Key +- **ARM Decoding**: Comprehensive ARMv7 instruction decoder +- **CPU Emulation**: Basic ARM CPU emulation with register tracking +- **JIT Ready**: Built with Cranelift for future JIT compilation + +## Usage + +```bash +cargo build --bin emulator +./target/debug/emulator +``` + +## Output + +The emulator provides detailed output including: +- File loading statistics +- Decryption status +- Instruction count and details +- CPU execution trace +- Final register state + +This provides a foundation for iOS 5 bootloader analysis and emulation. diff --git a/src/tools/emulator/src/cpu.rs b/src/tools/emulator/src/cpu.rs new file mode 100644 index 0000000..2d44590 --- /dev/null +++ b/src/tools/emulator/src/cpu.rs @@ -0,0 +1,1632 @@ +use crate::decoder::{Instruction, Operand}; +use crate::hardware::Hardware; +use derive_more::derive::Display; +use std::collections::HashMap; + +#[derive(Debug, Display)] +pub enum CpuError { + #[display("Invalid register: {_0}")] + InvalidRegister(u8), + #[display("Memory access error at 0x{_0:x}")] + MemoryAccess(u32), + #[display("Unsupported instruction: {_0}")] + UnsupportedInstruction(String), +} + +impl std::error::Error for CpuError {} + +pub type Result = std::result::Result; + +pub struct ArmCpu { + pub registers: [u32; 16], + pub memory: HashMap, + pub ram: Vec, // 256MB of primary RAM + pub pc: u32, + pub cpsr: u32, + pub hardware: Option, + pub pc_modified: bool, + pub it_state: u8, +} + +impl ArmCpu { + pub fn new() -> Self { + Self { + registers: [0; 16], + memory: HashMap::new(), + ram: vec![0u8; 256 * 1024 * 1024], + pc: 0, + cpsr: 0, + hardware: None, + pc_modified: false, + it_state: 0, + } + } + + pub fn set_hardware(&mut self, hardware: Hardware) { + self.hardware = Some(hardware); + } + + pub fn load_memory(&mut self, addr: u32, data: &[u8]) { + for (i, &byte) in data.iter().enumerate() { + let curr_addr = addr + i as u32; + self.memory.insert(curr_addr, byte); + + // Also load into fast RAM if it's in a RAM range + // Ranges: 0x40000000..0x48000000 and 0x80000000..0x88000000 + if (curr_addr >= 0x40000000 && curr_addr < 0x50000000) + || (curr_addr >= 0x80000000 && curr_addr < 0x90000000) + { + let offset = (curr_addr & 0x0FFFFFFF) as usize; + if offset < self.ram.len() { + self.ram[offset] = byte; + } + } + } + } + + pub fn get_reg(&self, reg: u8) -> u32 { + if reg == 15 { + let is_thumb = (self.cpsr >> 5) & 1 != 0; + if is_thumb { + self.pc.wrapping_add(4) + } else { + self.pc.wrapping_add(8) + } + } else { + self.registers[reg as usize] + } + } + + pub fn get_reg_dp(&self, reg: u8) -> u32 { + if reg == 15 { + let is_thumb = (self.cpsr >> 5) & 1 != 0; + if is_thumb { + (self.pc.wrapping_add(4)) & !3 + } else { + self.pc.wrapping_add(8) + } + } else { + self.registers[reg as usize] + } + } + + pub fn dump_registers(&self) { + println!(" PC: 0x{:08x}", self.pc); + for i in 0..16 { + print!(" R{:02}: 0x{:08x}", i, self.registers[i]); + if (i + 1) % 4 == 0 { + println!(); + } + } + println!(" CPSR: 0x{:08x}", self.cpsr); + } + + pub fn set_reg(&mut self, reg: u8, val: u32) { + if reg == 15 { + self.pc = val; + self.pc_modified = true; + } else { + self.registers[reg as usize] = val; + } + } + + fn get_mem_addr(&mut self, instruction: &Instruction, op_idx: usize) -> Result { + if let Operand::Memory(base_reg, offset) = &instruction.operands[op_idx] { + let base_val = self.get_reg_dp(*base_reg); + let mut offset_val = *offset as u32; + + if instruction.operands.len() > op_idx + 1 { + match &instruction.operands[op_idx + 1] { + Operand::Register(rm) => { + offset_val = self.get_reg_dp(*rm); + } + Operand::Shift(rm, type_, amount) => { + let val = self.get_reg_dp(*rm); + let amt = *amount as u32; + offset_val = match type_ { + 0 => val.wrapping_shl(amt), + 1 => val.wrapping_shr(amt), + 2 => (val as i32).wrapping_shr(amt) as u32, + 3 => val.rotate_right(amt), + _ => val, + }; + } + _ => {} + } + } + + let bits = if instruction.operands.len() > op_idx + 1 { + match instruction.operands.last() { + Some(Operand::Immediate(b)) => *b as u8, + _ => 0b10, // P=1, W=0 + } + } else { + 0b10 + }; + + let p = (bits >> 1) & 1; + let w = bits & 1; + + let addr = if p == 1 { + base_val.wrapping_add(offset_val) + } else { + base_val + }; + + if w == 1 { + let next_base = if p == 1 { + addr + } else { + base_val.wrapping_add(offset_val) + }; + self.set_reg(*base_reg, next_base); + } + + return Ok(addr); + } + Err(CpuError::UnsupportedInstruction( + "Invalid memory operand".to_string(), + )) + } + + pub fn read_memory(&self, addr: u32) -> Result { + // NVRAM/Environment variables (common iBEC addresses) + match addr { + // NVRAM base addresses - simulate boot environment + 0x84000000..=0x84000FFF => return Ok(0x626F6F74), // "boot" + 0x85000000..=0x85000FFF => return Ok(0x61726773), // "args" + 0x86000000..=0x86000FFF => return Ok(0x6B65726E), // "kern" + 0x87000000..=0x87000FFF => return Ok(0x64656275), // "debu" + // Boot flags and status + 0x88000000..=0x88000FFF => return Ok(0x1), // Boot status + 0x89000000..=0x89000FFF => return Ok(0x0), // Debug flags + _ => {} + } + // Check hardware peripherals first + if let Some(ref hw) = self.hardware { + if let Some(val) = hw.read(addr) { + return Ok(val); + } + } + + // Fast RAM access (map everything to first 256MB via mask 0x0FFFFFFF) + let ram_offset = (addr & 0x0FFFFFFF) as usize; + if ram_offset + 3 < self.ram.len() { + let val = u32::from_le_bytes([ + self.ram[ram_offset], + self.ram[ram_offset + 1], + self.ram[ram_offset + 2], + self.ram[ram_offset + 3], + ]); + return Ok(val); + } + + let mut value = 0u32; + for i in 0..4 { + if let Some(&byte) = self.memory.get(&(addr + i)) { + value |= (byte as u32) << (i * 8); + } else { + return Ok(0); + } + } + Ok(value) + } + + pub fn write_memory(&mut self, addr: u32, value: u32) -> Result<()> { + // Check hardware peripherals + if let Some(ref mut hw) = self.hardware { + if hw.write(addr, value) { + return Ok(()); + } + } + + let ram_offset = (addr & 0x0FFFFFFF) as usize; + if ram_offset + 3 < self.ram.len() { + let bytes = value.to_le_bytes(); + self.ram[ram_offset] = bytes[0]; + self.ram[ram_offset + 1] = bytes[1]; + self.ram[ram_offset + 2] = bytes[2]; + self.ram[ram_offset + 3] = bytes[3]; + return Ok(()); + } + + // Log unmapped writes outside of RAM/Hardware + if addr > 0x1000 { + // println!("Unmapped Write: 0x{:08x} = 0x{:08x}", addr, value); + } + + for i in 0..4 { + self.memory + .insert(addr + i, ((value >> (i * 8)) & 0xFF) as u8); + } + Ok(()) + } + + pub fn advance_it_state(&mut self) { + if self.it_state & 0x7 != 0 { + // bits 4:0 are shifted + let mut it = self.it_state & 0x1F; + it <<= 1; + if (it & 0x7) == 0 { + self.it_state = 0; + } else { + self.it_state = (self.it_state & 0xE0) | (it & 0x1F); + } + } else if self.it_state != 0 { + // Only one instruction in IT block, and it's done + self.it_state = 0; + } + } + + pub fn execute(&mut self, instruction: &Instruction) -> Result<()> { + let condition = if self.it_state & 0xF != 0 { + self.it_state >> 4 + } else { + instruction.condition + }; + + if !self.check_condition(condition) { + self.advance_it_state(); + return Ok(()); + } + + let mnemonic = if instruction.mnemonic.ends_with('s') + && instruction.mnemonic != "tst" + && instruction.mnemonic != "teq" + && instruction.mnemonic != "cmn" + && instruction.mnemonic != "cmp" + { + &instruction.mnemonic[..instruction.mnemonic.len() - 1] + } else { + instruction.mnemonic.as_str() + }; + + let res = match mnemonic { + "mov" | "movw" => self.exec_mov(instruction), + "movt" => self.exec_movt(instruction), + "add" | "addw" => self.exec_add(instruction), + "adc" => self.exec_adc(instruction), + "sbc" => self.exec_sbc(instruction), + "sub" | "subw" => self.exec_sub(instruction), + "rsb" => self.exec_rsb(instruction), + "rsc" => self.exec_rsc(instruction), + "ldr" => self.exec_ldr(instruction), + "str" => self.exec_str(instruction), + "b" => self.exec_branch(instruction), + "bl" => self.exec_branch_link(instruction), + "cmp" => self.exec_cmp(instruction), + "orr" => self.exec_orr(instruction), + "and" => self.exec_and(instruction), + "eor" => self.exec_eor(instruction), + "bic" => self.exec_bic(instruction), + "mvn" => self.exec_mvn(instruction), + "tst" => self.exec_tst(instruction), + "teq" => self.exec_teq(instruction), + "cmn" => self.exec_cmn(instruction), + "ldm" => self.exec_ldm(instruction), + "stm" => self.exec_stm(instruction), + "bx" => self.exec_bx(instruction), + "blx" => self.exec_blx(instruction), + "push" => self.exec_push(instruction), + "pop" => self.exec_pop(instruction), + "ldrb" => self.exec_ldrb(instruction), + "strb" => self.exec_strb(instruction), + "ldrh" => self.exec_ldrh(instruction), + "strh" => self.exec_strh(instruction), + "ldrd" => self.exec_ldrd(instruction), + "strd" => self.exec_strd(instruction), + "ldrex" => self.exec_ldrex(instruction), + "strex" => self.exec_strex(instruction), + "ldrsb" => { + let addr = self.get_mem_addr(instruction, 1)?; + let val = self.read_memory(addr)? as i8 as i32 as u32; + if let Some(Operand::Register(rd)) = instruction.operands.get(0) { + self.set_reg(*rd, val); + } + Ok(()) + } + "ldrsh" => { + let addr = self.get_mem_addr(instruction, 1)?; + let low = self.read_memory(addr)? as u32; + let high = self.read_memory(addr.wrapping_add(1))? as u32; + let val = ((high << 8) | low) as i16 as i32 as u32; + if let Some(Operand::Register(rd)) = instruction.operands.get(0) { + self.set_reg(*rd, val); + } + Ok(()) + } + "tbb" | "tbh" => self.exec_tb(instruction), + "uxtb" | "uxth" | "sxtb" | "sxth" => self.exec_extend(instruction), + "swi" | "svc" => Ok(()), // Software interrupt + "msr" | "mrs" => self.exec_msr_mrs(instruction), + "mul" | "mla" => Ok(()), // Multiply instructions + "lsl" | "lsr" | "asr" | "ror" => self.exec_shift(instruction), + "rev" | "rev16" | "revsh" => self.exec_rev(instruction), + "clz" => self.exec_clz(instruction), + "cbz" | "cbnz" => self.exec_cbz(instruction), + "bfc" => self.exec_bfc(instruction), + "bfi" => self.exec_bfi(instruction), + "ubfx" => self.exec_ubfx(instruction), + "sbfx" => self.exec_sbfx(instruction), + "it" => self.exec_it(instruction), + "nop" => Ok(()), + _ => { + if instruction.mnemonic.starts_with("unknown_t") { + println!( + "Warning: Skipping unknown thumb instruction: {}", + instruction.mnemonic + ); + Ok(()) + } else { + Err(CpuError::UnsupportedInstruction( + instruction.mnemonic.clone(), + )) + } + } + }; + self.advance_it_state(); + res + } + + fn exec_bx(&mut self, instruction: &Instruction) -> Result<()> { + if let Some(Operand::Register(reg)) = instruction.operands.first() { + let target = self.get_reg(*reg); + // iBEC might switch to Thumb mode if bit 0 is set + if target & 1 != 0 { + self.set_reg(15, target & !1); + self.cpsr |= 0x20; // Set T bit + } else { + self.set_reg(15, target); + self.cpsr &= !0x20; + } + } + Ok(()) + } + + fn exec_blx(&mut self, instruction: &Instruction) -> Result<()> { + if let Some(Operand::Register(reg)) = instruction.operands.first() { + let target = self.get_reg(*reg); + let return_addr = self.pc.wrapping_add(instruction.size as u32) | 1; + self.set_reg(14, return_addr); + + if target & 1 != 0 { + self.set_reg(15, target & !1); + self.cpsr |= 0x20; // Set T bit + } else { + self.set_reg(15, target); + self.cpsr &= !0x20; + } + } + Ok(()) + } + + fn interworking_branch(&mut self, target: u32) { + if target & 1 != 0 { + self.pc = target & !1; + self.cpsr |= 0x20; // Thumb + } else { + self.pc = target & !3; + self.cpsr &= !0x20; // ARM + } + self.pc_modified = true; + } + + fn update_flags(&mut self, result: u32, carry: Option, overflow: Option) { + self.cpsr &= !0xF0000000; + if result == 0 { + self.cpsr |= 0x40000000; // Z flag + } + if (result as i32) < 0 { + self.cpsr |= 0x80000000; // N flag + } + if let Some(c) = carry { + if c { + self.cpsr |= 0x20000000; // C flag + } + } + if let Some(v) = overflow { + if v { + self.cpsr |= 0x10000000; // V flag + } + } + } + + fn exec_mov(&mut self, instruction: &Instruction) -> Result<()> { + if instruction.operands.len() < 2 { + return Ok(()); + } + + let value = match &instruction.operands[1] { + Operand::Register(reg) => self.get_reg_dp(*reg), + Operand::Immediate(imm) => *imm as u32, + _ => return Ok(()), + }; + + if let Operand::Register(reg) = &instruction.operands[0] { + if *reg == 15 { + self.interworking_branch(value); + } + self.set_reg(*reg, value); + if instruction.mnemonic.ends_with('s') { + self.update_flags(value, None, None); + } + } + + Ok(()) + } + + fn exec_movt(&mut self, instruction: &Instruction) -> Result<()> { + if let (Some(Operand::Register(rd)), Some(Operand::Immediate(imm))) = + (instruction.operands.get(0), instruction.operands.get(1)) + { + let current_val = self.get_reg(*rd); + let new_val = (current_val & 0xFFFF) | ((*imm as u32) << 16); + self.set_reg(*rd, new_val); + } + Ok(()) + } + + fn exec_add(&mut self, instruction: &Instruction) -> Result<()> { + if instruction.operands.len() < 2 { + return Ok(()); + } + + let (rd, val1, val2) = if instruction.operands.len() == 3 { + let r = match instruction.operands[0] { + Operand::Register(r) => r, + _ => 0, + }; + let v1 = match &instruction.operands[1] { + Operand::Register(r) => self.get_reg_dp(*r), + _ => 0, + }; + let v2 = match &instruction.operands[2] { + Operand::Register(r) => self.get_reg_dp(*r), + Operand::Immediate(imm) => *imm as u32, + Operand::Shift(r, type_, amount) => { + let val = self.get_reg_dp(*r); + let amt = *amount as u32; + match type_ { + 0 => val.wrapping_shl(amt), + 1 => val.wrapping_shr(amt), + 2 => (val as i32).wrapping_shr(amt) as u32, + 3 => val.rotate_right(amt), + _ => val, + } + } + _ => 0, + }; + (r, v1, v2) + } else { + let r = match instruction.operands[0] { + Operand::Register(r) => r, + _ => 0, + }; + let v1 = self.get_reg_dp(r); + let v2 = match &instruction.operands[1] { + Operand::Register(r) => self.get_reg_dp(*r), + Operand::Immediate(imm) => *imm as u32, + Operand::Shift(r, type_, amount) => { + let val = self.get_reg_dp(*r); + let amt = *amount as u32; + match type_ { + 0 => val.wrapping_shl(amt), + 1 => val.wrapping_shr(amt), + 2 => (val as i32).wrapping_shr(amt) as u32, + 3 => val.rotate_right(amt), + _ => val, + } + } + _ => 0, + }; + (r, v1, v2) + }; + + let (result, carry) = val1.overflowing_add(val2); + self.set_reg(rd, result); + + if instruction.mnemonic.ends_with('s') { + let overflow = ((val1 ^ result) & (val2 ^ result) & 0x80000000) != 0; + self.update_flags(result, Some(carry), Some(overflow)); + } + + Ok(()) + } + + fn exec_sub(&mut self, instruction: &Instruction) -> Result<()> { + if instruction.operands.len() < 2 { + return Ok(()); + } + + let (rd, val1, val2) = if instruction.operands.len() == 3 { + let r = match instruction.operands[0] { + Operand::Register(r) => r, + _ => 0, + }; + let v1 = match &instruction.operands[1] { + Operand::Register(r) => self.get_reg_dp(*r), + _ => 0, + }; + let v2 = match &instruction.operands[2] { + Operand::Register(r) => self.get_reg_dp(*r), + Operand::Immediate(imm) => *imm as u32, + Operand::Shift(r, type_, amount) => { + let val = self.get_reg_dp(*r); + let amt = *amount as u32; + match type_ { + 0 => val.wrapping_shl(amt), + 1 => val.wrapping_shr(amt), + 2 => (val as i32).wrapping_shr(amt) as u32, + 3 => val.rotate_right(amt), + _ => val, + } + } + _ => 0, + }; + (r, v1, v2) + } else { + let r = match instruction.operands[0] { + Operand::Register(r) => r, + _ => 0, + }; + let v1 = self.get_reg_dp(r); + let v2 = match &instruction.operands[1] { + Operand::Register(r) => self.get_reg_dp(*r), + Operand::Immediate(imm) => *imm as u32, + Operand::Shift(r, type_, amount) => { + let val = self.get_reg_dp(*r); + let amt = *amount as u32; + match type_ { + 0 => val.wrapping_shl(amt), + 1 => val.wrapping_shr(amt), + 2 => (val as i32).wrapping_shr(amt) as u32, + 3 => val.rotate_right(amt), + _ => val, + } + } + _ => 0, + }; + (r, v1, v2) + }; + + let (result, borrow) = val1.overflowing_sub(val2); + self.set_reg(rd, result); + + if instruction.mnemonic.ends_with('s') { + let overflow = ((val1 ^ val2) & (val1 ^ result) & 0x80000000) != 0; + self.update_flags(result, Some(!borrow), Some(overflow)); + } + + Ok(()) + } + + fn exec_bfc(&mut self, instruction: &Instruction) -> Result<()> { + if let ( + Some(Operand::Register(rd)), + Some(Operand::Immediate(lsb)), + Some(Operand::Immediate(width)), + ) = ( + instruction.operands.get(0), + instruction.operands.get(1), + instruction.operands.get(2), + ) { + let mut val = self.get_reg(*rd); + let mask = ((1u32 << *width).wrapping_sub(1)) << *lsb; + val &= !mask; + self.set_reg(*rd, val); + } + Ok(()) + } + + fn exec_bfi(&mut self, instruction: &Instruction) -> Result<()> { + if let ( + Some(Operand::Register(rd)), + Some(Operand::Register(rn)), + Some(Operand::Immediate(lsb)), + Some(Operand::Immediate(width)), + ) = ( + instruction.operands.get(0), + instruction.operands.get(1), + instruction.operands.get(2), + instruction.operands.get(3), + ) { + let dest_val = self.get_reg(*rd); + let src_val = self.get_reg(*rn); + let mask = (1u32 << *width).wrapping_sub(1); // Unshifted mask for source + let insert_bits = (src_val & mask) << *lsb; + let dest_mask = !(mask << *lsb); + + let result = (dest_val & dest_mask) | insert_bits; + self.set_reg(*rd, result); + } + Ok(()) + } + + fn exec_ubfx(&mut self, instruction: &Instruction) -> Result<()> { + if let ( + Some(Operand::Register(rd)), + Some(Operand::Register(rn)), + Some(Operand::Immediate(lsb)), + Some(Operand::Immediate(width)), + ) = ( + instruction.operands.get(0), + instruction.operands.get(1), + instruction.operands.get(2), + instruction.operands.get(3), + ) { + let val = self.get_reg(*rn); + let mask = (1u32 << *width).wrapping_sub(1); + let result = (val >> *lsb) & mask; + self.set_reg(*rd, result); + } + Ok(()) + } + + fn exec_sbfx(&mut self, instruction: &Instruction) -> Result<()> { + if let ( + Some(Operand::Register(rd)), + Some(Operand::Register(rn)), + Some(Operand::Immediate(lsb)), + Some(Operand::Immediate(width)), + ) = ( + instruction.operands.get(0), + instruction.operands.get(1), + instruction.operands.get(2), + instruction.operands.get(3), + ) { + let val = self.get_reg(*rn); + let shifted = val >> *lsb; + let bits = 32 - *width; + let result = ((shifted << bits) as i32) >> bits; + self.set_reg(*rd, result as u32); + } + Ok(()) + } + + fn exec_ldr(&mut self, instruction: &Instruction) -> Result<()> { + if instruction.operands.len() < 2 { + return Ok(()); + } + + let bits = if instruction.operands.len() >= 3 { + if let Operand::Immediate(b) = instruction.operands[2] { + b as u8 + } else { + 0b10 + } + } else { + 0b10 // P=1, W=0 + }; + + let p = (bits >> 1) & 1; + let w = bits & 1; + + match &instruction.operands[1] { + Operand::Memory(base_reg, offset) => { + let base_val = self.get_reg_dp(*base_reg); + let mut offset_val = *offset as u32; + if instruction.operands.len() >= 3 { + if let Operand::Register(rm) = instruction.operands[2] { + offset_val = self.get_reg_dp(rm); + } + } + + let addr = if p == 1 { + base_val.wrapping_add(offset_val) + } else { + base_val + }; + + let mut value = self.read_memory(addr)?; + + // HACK: Intercept uninitialized console object + if addr == 0x5FFF7F24 && value == 0 { + /* + println!( + "HACK: Intercepted console object load at {:08x}. Substituting dummy vtable.", + addr + ); + */ + value = 0x5FF22EFD; // bx lr (Thumb) + } + + if let Operand::Register(rd) = &instruction.operands[0] { + if *rd == 15 { + self.interworking_branch(value); + } else { + self.set_reg(*rd, value); + } + } + + if w == 1 || p == 0 { + let new_base = base_val.wrapping_add(offset_val); + self.set_reg(*base_reg, new_base); + } + } + _ => return Ok(()), + } + + Ok(()) + } + + fn exec_str(&mut self, instruction: &Instruction) -> Result<()> { + if instruction.operands.len() < 2 { + return Ok(()); + } + + let value = match &instruction.operands[0] { + Operand::Register(reg) => self.get_reg_dp(*reg), + _ => return Ok(()), + }; + + let bits = if instruction.operands.len() >= 3 { + if let Operand::Immediate(b) = instruction.operands[2] { + b as u8 + } else { + 0b10 + } + } else { + 0b10 // P=1, W=0 + }; + + let p = (bits >> 1) & 1; + let w = bits & 1; + + match &instruction.operands[1] { + Operand::Memory(base_reg, offset) => { + let base_val = self.get_reg_dp(*base_reg); + let mut offset_val = *offset as u32; + if instruction.operands.len() >= 3 { + if let Operand::Register(rm) = instruction.operands[2] { + offset_val = self.get_reg_dp(rm); + } + } + + let addr = if p == 1 { + base_val.wrapping_add(offset_val) + } else { + base_val + }; + + self.write_memory(addr, value)?; + + if w == 1 || p == 0 { + let new_base = base_val.wrapping_add(offset_val); + self.set_reg(*base_reg, new_base); + } + } + _ => {} + } + + Ok(()) + } + + fn exec_branch(&mut self, instruction: &Instruction) -> Result<()> { + if let Some(Operand::Immediate(offset)) = instruction.operands.first() { + let new_pc = self.get_reg(15).wrapping_add(*offset as u32); + self.set_reg(15, new_pc); + } + Ok(()) + } + + fn exec_branch_link(&mut self, instruction: &Instruction) -> Result<()> { + let return_addr = self.pc.wrapping_add(instruction.size as u32) | 1; + self.set_reg(14, return_addr); + self.exec_branch(instruction) + } + + fn exec_push(&mut self, instruction: &Instruction) -> Result<()> { + if instruction.operands.len() == 3 { + return self.exec_stm(instruction); + } + + let reg_list = match &instruction.operands[0] { + Operand::Immediate(imm) => *imm as u32, + _ => return Ok(()), + }; + + // PUSH uses SP (R13) + let mut sp = self.registers[13]; + let num_regs = reg_list.count_ones(); + sp = sp.wrapping_sub(num_regs * 4); + let new_sp = sp; + + let mut addr = new_sp; + for i in 0..16 { + if (reg_list >> i) & 1 != 0 { + let val = self.get_reg(i as u8); + self.write_memory(addr, val)?; + addr = addr.wrapping_add(4); + } + } + + self.registers[13] = new_sp; + Ok(()) + } + + fn exec_pop(&mut self, instruction: &Instruction) -> Result<()> { + if instruction.operands.len() == 3 { + return self.exec_ldm(instruction); + } + + let reg_list = match &instruction.operands[0] { + Operand::Immediate(imm) => *imm as u32, + _ => return Ok(()), + }; + + let mut addr = self.registers[13]; + let num_regs = reg_list.count_ones(); + let new_sp = addr.wrapping_add(num_regs * 4); + + for i in 0..16 { + if (reg_list >> i) & 1 != 0 { + let val = self.read_memory(addr)?; + if i == 15 { + self.interworking_branch(val); + } else { + self.set_reg(i as u8, val); + } + addr = addr.wrapping_add(4); + } + } + + self.registers[13] = new_sp; + Ok(()) + } + + fn exec_ldrb(&mut self, instruction: &Instruction) -> Result<()> { + if instruction.operands.len() < 2 { + return Ok(()); + } + + let rd = match instruction.operands[0] { + Operand::Register(r) => r, + _ => return Ok(()), + }; + + let addr = match &instruction.operands[1] { + Operand::Memory(rn, offset) => { + let base = self.get_reg(*rn); + base.wrapping_add(*offset as u32) + } + _ => return Ok(()), + }; + + let val = self.memory.get(&addr).copied().unwrap_or(0) as u32; + self.set_reg(rd, val); + Ok(()) + } + + fn exec_strb(&mut self, instruction: &Instruction) -> Result<()> { + if instruction.operands.len() < 2 { + return Ok(()); + } + + let rd = match instruction.operands[0] { + Operand::Register(r) => r, + _ => return Ok(()), + }; + + let addr = match &instruction.operands[1] { + Operand::Memory(rn, offset) => { + let base = self.get_reg(*rn); + base.wrapping_add(*offset as u32) + } + _ => return Ok(()), + }; + + let val = self.get_reg(rd) as u8; + self.memory.insert(addr, val); + Ok(()) + } + + fn exec_msr_mrs(&mut self, instruction: &Instruction) -> Result<()> { + if instruction.mnemonic == "mrs" { + if let Some(Operand::Register(rd)) = instruction.operands.get(0) { + self.set_reg(*rd, self.cpsr); + } + } else if instruction.mnemonic == "msr" { + if let Some(Operand::Register(rn)) = instruction.operands.get(1) { + let val = self.get_reg(*rn); + self.cpsr = val; + } + } + Ok(()) + } + + fn exec_ldrd(&mut self, instruction: &Instruction) -> Result<()> { + if let (Some(Operand::Register(rt)), Some(Operand::Register(rt2))) = + (instruction.operands.get(0), instruction.operands.get(1)) + { + let addr = self.get_mem_addr(instruction, 2)?; + // println!("LDRD at {:08x}, addr {:08x}", self.pc, addr); + let val = self.read_memory(addr)?; + let val2 = self.read_memory(addr.wrapping_add(4))?; + self.set_reg(*rt, val); + self.set_reg(*rt2, val2); + } + Ok(()) + } + + fn exec_strd(&mut self, instruction: &Instruction) -> Result<()> { + if let (Some(Operand::Register(rt)), Some(Operand::Register(rt2))) = + (instruction.operands.get(0), instruction.operands.get(1)) + { + let val = self.get_reg(*rt); + let val2 = self.get_reg(*rt2); + let addr = self.get_mem_addr(instruction, 2)?; + // println!("STRD at {:08x}, addr {:08x} val {:08x} {:08x}", self.pc, addr, val, val2); + self.write_memory(addr, val)?; + self.write_memory(addr.wrapping_add(4), val2)?; + } + Ok(()) + } + + fn exec_ldrex(&mut self, instruction: &Instruction) -> Result<()> { + // Treat as normal LDR + // LDREX Rt, [Rn, #imm] + if let Some(Operand::Register(rt)) = instruction.operands.get(0) { + let addr = self.get_mem_addr(instruction, 1)?; + let val = self.read_memory(addr)?; + self.set_reg(*rt, val); + } + Ok(()) + } + + fn exec_strex(&mut self, instruction: &Instruction) -> Result<()> { + // STREX Rd, Rt, [Rn, #imm] + // Store Rt to [Rn, #imm], write 0 to Rd (success) + if let (Some(Operand::Register(rd)), Some(Operand::Register(rt))) = + (instruction.operands.get(0), instruction.operands.get(1)) + { + let val = self.get_reg(*rt); + let addr = self.get_mem_addr(instruction, 2)?; + self.write_memory(addr, val)?; + self.set_reg(*rd, 0); // 0 = Success + } + Ok(()) + } + + fn exec_cmp(&mut self, instruction: &Instruction) -> Result<()> { + if instruction.operands.len() < 2 { + return Ok(()); + } + + let val1 = match &instruction.operands[0] { + Operand::Register(reg) => self.get_reg_dp(*reg), + _ => return Ok(()), + }; + + let val2 = match &instruction.operands[1] { + Operand::Register(reg) => self.get_reg_dp(*reg), + Operand::Immediate(imm) => *imm as u32, + _ => return Ok(()), + }; + + let result = val1.wrapping_sub(val2); + + // Update CPSR flags + self.cpsr &= !0xF0000000; + if result == 0 { + self.cpsr |= 0x40000000; // Z flag + } + if (result as i32) < 0 { + self.cpsr |= 0x80000000; // N flag + } + if val1 >= val2 { + self.cpsr |= 0x20000000; // C flag (unsigned >=) + } + + Ok(()) + } + + fn exec_orr(&mut self, instruction: &Instruction) -> Result<()> { + if instruction.operands.len() < 3 { + return Ok(()); + } + + let val1 = match &instruction.operands[1] { + Operand::Register(reg) => self.get_reg_dp(*reg), + _ => return Ok(()), + }; + + let val2 = match &instruction.operands[2] { + Operand::Register(reg) => self.get_reg_dp(*reg), + Operand::Immediate(imm) => *imm as u32, + _ => return Ok(()), + }; + + if let Operand::Register(reg) = &instruction.operands[0] { + self.set_reg(*reg, val1 | val2); + } + + Ok(()) + } + + fn exec_and(&mut self, instruction: &Instruction) -> Result<()> { + if instruction.operands.len() < 3 { + return Ok(()); + } + + let val1 = match &instruction.operands[1] { + Operand::Register(reg) => self.get_reg_dp(*reg), + _ => return Ok(()), + }; + + let val2 = match &instruction.operands[2] { + Operand::Register(reg) => self.get_reg_dp(*reg), + Operand::Immediate(imm) => *imm as u32, + _ => return Ok(()), + }; + + if let Operand::Register(reg) = &instruction.operands[0] { + self.set_reg(*reg, val1 & val2); + } + + Ok(()) + } + + fn exec_eor(&mut self, instruction: &Instruction) -> Result<()> { + if instruction.operands.len() < 3 { + return Ok(()); + } + + let val1 = match &instruction.operands[1] { + Operand::Register(reg) => self.get_reg_dp(*reg), + _ => return Ok(()), + }; + + let val2 = match &instruction.operands[2] { + Operand::Register(reg) => self.get_reg_dp(*reg), + Operand::Immediate(imm) => *imm as u32, + _ => return Ok(()), + }; + + if let Operand::Register(reg) = &instruction.operands[0] { + self.set_reg(*reg, val1 ^ val2); + } + + Ok(()) + } + + fn exec_bic(&mut self, instruction: &Instruction) -> Result<()> { + if instruction.operands.len() < 3 { + return Ok(()); + } + + let val1 = match &instruction.operands[1] { + Operand::Register(reg) => self.get_reg_dp(*reg), + _ => return Ok(()), + }; + + let val2 = match &instruction.operands[2] { + Operand::Register(reg) => self.get_reg_dp(*reg), + Operand::Immediate(imm) => *imm as u32, + _ => return Ok(()), + }; + + if let Operand::Register(reg) = &instruction.operands[0] { + self.set_reg(*reg, val1 & !val2); + } + + Ok(()) + } + + fn exec_rsb(&mut self, instruction: &Instruction) -> Result<()> { + if instruction.operands.len() < 3 { + return Ok(()); + } + + let val1 = match &instruction.operands[1] { + Operand::Register(reg) => self.get_reg(*reg), + _ => return Ok(()), + }; + + let val2 = match &instruction.operands[2] { + Operand::Register(reg) => self.get_reg(*reg), + Operand::Immediate(imm) => *imm as u32, + _ => return Ok(()), + }; + + if let Operand::Register(reg) = &instruction.operands[0] { + self.set_reg(*reg, val2.wrapping_sub(val1)); // RSB: Rd = Rm - Rn + } + + Ok(()) + } + + fn exec_mvn(&mut self, instruction: &Instruction) -> Result<()> { + if instruction.operands.len() < 2 { + return Ok(()); + } + + let value = match &instruction.operands[1] { + Operand::Register(reg) => self.get_reg_dp(*reg), + Operand::Immediate(imm) => *imm as u32, + _ => return Ok(()), + }; + + if let Operand::Register(reg) = &instruction.operands[0] { + self.set_reg(*reg, !value); // MVN: bitwise NOT + } + + Ok(()) + } + + fn exec_adc(&mut self, instruction: &Instruction) -> Result<()> { + if instruction.operands.len() < 3 { + return Ok(()); + } + + let val1 = match &instruction.operands[1] { + Operand::Register(reg) => self.get_reg(*reg), + _ => return Ok(()), + }; + + let val2 = match &instruction.operands[2] { + Operand::Register(reg) => self.get_reg(*reg), + Operand::Immediate(imm) => *imm as u32, + _ => return Ok(()), + }; + + let carry = if self.cpsr & 0x20000000 != 0 { 1 } else { 0 }; + + if let Operand::Register(reg) = &instruction.operands[0] { + self.set_reg(*reg, val1.wrapping_add(val2).wrapping_add(carry)); + } + + Ok(()) + } + + fn exec_sbc(&mut self, instruction: &Instruction) -> Result<()> { + if instruction.operands.len() < 3 { + return Ok(()); + } + + let val1 = match &instruction.operands[1] { + Operand::Register(reg) => self.get_reg(*reg), + _ => return Ok(()), + }; + + let val2 = match &instruction.operands[2] { + Operand::Register(reg) => self.get_reg(*reg), + Operand::Immediate(imm) => *imm as u32, + _ => return Ok(()), + }; + + let carry = if self.cpsr & 0x20000000 != 0 { 1 } else { 0 }; + + if let Operand::Register(reg) = &instruction.operands[0] { + self.set_reg(*reg, val1.wrapping_sub(val2).wrapping_sub(1 - carry)); + } + + Ok(()) + } + + fn exec_rsc(&mut self, instruction: &Instruction) -> Result<()> { + if instruction.operands.len() < 3 { + return Ok(()); + } + + let val1 = match &instruction.operands[1] { + Operand::Register(reg) => self.get_reg(*reg), + _ => return Ok(()), + }; + + let val2 = match &instruction.operands[2] { + Operand::Register(reg) => self.get_reg(*reg), + Operand::Immediate(imm) => *imm as u32, + _ => return Ok(()), + }; + + let carry = if self.cpsr & 0x20000000 != 0 { 1 } else { 0 }; + + if let Operand::Register(reg) = &instruction.operands[0] { + self.set_reg(*reg, val2.wrapping_sub(val1).wrapping_sub(1 - carry)); + } + + Ok(()) + } + + fn exec_tst(&mut self, instruction: &Instruction) -> Result<()> { + if instruction.operands.len() < 2 { + return Ok(()); + } + + let val1 = match &instruction.operands[0] { + Operand::Register(reg) => self.get_reg_dp(*reg), + _ => return Ok(()), + }; + + let val2 = match &instruction.operands[1] { + Operand::Register(reg) => self.get_reg_dp(*reg), + Operand::Immediate(imm) => *imm as u32, + _ => return Ok(()), + }; + + let result = val1 & val2; + self.cpsr &= !0xF0000000; + if result == 0 { + self.cpsr |= 0x40000000; // Z flag + } + if (result as i32) < 0 { + self.cpsr |= 0x80000000; // N flag + } + + Ok(()) + } + + fn exec_teq(&mut self, instruction: &Instruction) -> Result<()> { + if instruction.operands.len() < 2 { + return Ok(()); + } + + let val1 = match &instruction.operands[0] { + Operand::Register(reg) => self.get_reg(*reg), + _ => return Ok(()), + }; + + let val2 = match &instruction.operands[1] { + Operand::Register(reg) => self.get_reg(*reg), + Operand::Immediate(imm) => *imm as u32, + _ => return Ok(()), + }; + + let result = val1 ^ val2; + self.cpsr &= !0xF0000000; + if result == 0 { + self.cpsr |= 0x40000000; // Z flag + } + if (result as i32) < 0 { + self.cpsr |= 0x80000000; // N flag + } + + Ok(()) + } + + fn exec_cmn(&mut self, instruction: &Instruction) -> Result<()> { + if instruction.operands.len() < 2 { + return Ok(()); + } + + let val1 = match &instruction.operands[0] { + Operand::Register(reg) => self.get_reg(*reg), + _ => return Ok(()), + }; + + let val2 = match &instruction.operands[1] { + Operand::Register(reg) => self.get_reg(*reg), + Operand::Immediate(imm) => *imm as u32, + _ => return Ok(()), + }; + + let result = val1.wrapping_add(val2); + self.cpsr &= !0xF0000000; + if result == 0 { + self.cpsr |= 0x40000000; // Z flag + } + if (result as i32) < 0 { + self.cpsr |= 0x80000000; // N flag + } + if (val1 as u64) + (val2 as u64) >= 0x100000000 { + self.cpsr |= 0x20000000; // C flag + } + + Ok(()) + } + + fn exec_ldm(&mut self, instruction: &Instruction) -> Result<()> { + if instruction.operands.len() < 3 { + return Ok(()); + } + + let base_reg = match &instruction.operands[0] { + Operand::Register(reg) => *reg, + _ => return Ok(()), + }; + + let reg_list = match &instruction.operands[1] { + Operand::Immediate(imm) => *imm as u16, + _ => return Ok(()), + }; + + let bits = match &instruction.operands[2] { + Operand::Immediate(imm) => *imm as u8, + _ => return Ok(()), + }; + + let p = (bits >> 2) & 1; + let u = (bits >> 1) & 1; + let w = bits & 1; + + let mut addr = self.get_reg(base_reg); + let num_regs = reg_list.count_ones(); + + if u == 0 { + addr = addr.wrapping_sub(num_regs * 4); + } + + let start_addr = addr; + let mut current_addr = addr; + + for i in 0..16 { + if (reg_list >> i) & 1 != 0 { + if p == 1 { + current_addr = current_addr.wrapping_add(4); + } + let val = self.read_memory(current_addr)?; + if i == 15 { + self.interworking_branch(val); + } else { + self.set_reg(i as u8, val); + } + if p == 0 { + current_addr = current_addr.wrapping_add(4); + } + } + } + + if w == 1 { + let next_base = if u == 1 { + start_addr.wrapping_add(num_regs * 4) + } else { + start_addr + }; + self.set_reg(base_reg, next_base); + } + + Ok(()) + } + + fn exec_stm(&mut self, instruction: &Instruction) -> Result<()> { + if instruction.operands.len() < 3 { + return Ok(()); + } + + let base_reg = match &instruction.operands[0] { + Operand::Register(reg) => *reg, + _ => return Ok(()), + }; + + let reg_list = match &instruction.operands[1] { + Operand::Immediate(imm) => *imm as u16, + _ => return Ok(()), + }; + + let bits = match &instruction.operands[2] { + Operand::Immediate(imm) => *imm as u8, + _ => return Ok(()), + }; + + let p = (bits >> 2) & 1; + let u = (bits >> 1) & 1; + let w = bits & 1; + + let mut addr = self.get_reg(base_reg); + let num_regs = reg_list.count_ones(); + + if u == 0 { + addr = addr.wrapping_sub(num_regs * 4); + } + + let start_addr = addr; + let mut current_addr = addr; + + for i in 0..16 { + if (reg_list >> i) & 1 != 0 { + if p == 1 { + current_addr = current_addr.wrapping_add(4); + } + let val = self.get_reg(i as u8); + self.write_memory(current_addr, val)?; + if p == 0 { + current_addr = current_addr.wrapping_add(4); + } + } + } + + if w == 1 { + let next_base = if u == 1 { + start_addr.wrapping_add(num_regs * 4) + } else { + start_addr + }; + self.set_reg(base_reg, next_base); + } + + Ok(()) + } + + fn check_condition(&self, cond: u8) -> bool { + let n = (self.cpsr >> 31) & 1; + let z = (self.cpsr >> 30) & 1; + let c = (self.cpsr >> 29) & 1; + let v = (self.cpsr >> 28) & 1; + + match cond { + 0x0 => z == 1, // EQ + 0x1 => z == 0, // NE + 0x2 => c == 1, // CS/HS + 0x3 => c == 0, // CC/LO + 0x4 => n == 1, // MI + 0x5 => n == 0, // PL + 0x6 => v == 1, // VS + 0x7 => v == 0, // VC + 0x8 => c == 1 && z == 0, // HI + 0x9 => c == 0 || z == 1, // LS + 0xA => n == v, // GE + 0xB => n != v, // LT + 0xC => z == 0 && n == v, // GT + 0xD => z == 1 || n != v, // LE + 0xE => true, // AL (always) + 0xF => false, // NV (never) + _ => false, + } + } + + fn exec_shift(&mut self, instruction: &Instruction) -> Result<()> { + if instruction.operands.len() < 2 { + return Ok(()); + } + + let rd = match instruction.operands[0] { + Operand::Register(r) => r, + _ => return Ok(()), + }; + + let (val, amount) = if instruction.operands.len() == 3 { + let v = match &instruction.operands[1] { + Operand::Register(r) => self.get_reg_dp(*r), + _ => 0, + }; + let a = match &instruction.operands[2] { + Operand::Register(r) => self.get_reg_dp(*r), + Operand::Immediate(imm) => *imm as u32, + _ => 0, + }; + (v, a) + } else { + let v = self.get_reg_dp(rd); + let a = match &instruction.operands[1] { + Operand::Register(r) => self.get_reg_dp(*r), + Operand::Immediate(imm) => *imm as u32, + _ => 0, + }; + (v, a) + }; + + let result = match instruction.mnemonic.as_str() { + "lsl" => val.wrapping_shl(amount % 32), + "lsr" => val.wrapping_shr(amount % 32), + "asr" => (val as i32).wrapping_shr(amount % 32) as u32, + "ror" => val.rotate_right(amount % 32), + _ => val, + }; + + self.set_reg(rd, result); + self.update_flags(result, None, None); + Ok(()) + } + + fn exec_cbz(&mut self, instruction: &Instruction) -> Result<()> { + if let (Some(Operand::Register(rn)), Some(Operand::Immediate(offset))) = + (instruction.operands.get(0), instruction.operands.get(1)) + { + let val = self.get_reg(*rn); + let is_cbnz = instruction.mnemonic == "cbnz"; + if (val == 0 && !is_cbnz) || (val != 0 && is_cbnz) { + let new_pc = self.pc.wrapping_add(4).wrapping_add(*offset as u32); + self.set_reg(15, new_pc); + } + } + Ok(()) + } + + fn exec_tb(&mut self, instruction: &Instruction) -> Result<()> { + if let (Some(Operand::Register(rn)), Some(Operand::Register(rm))) = + (instruction.operands.get(0), instruction.operands.get(1)) + { + let base = self.get_reg_dp(*rn); + let index = self.get_reg(*rm); + let is_tbh = instruction.mnemonic == "tbh"; + + let addr = if is_tbh { + base.wrapping_add(index << 1) + } else { + base.wrapping_add(index) + }; + + let offset = if is_tbh { + let low = self.read_memory(addr)? & 0xFF; + let high = self.read_memory(addr.wrapping_add(1))? & 0xFF; + (high << 8) | low + } else { + self.read_memory(addr)? & 0xFF + }; + + let new_pc = self.pc.wrapping_add(4).wrapping_add(offset << 1); + self.pc = new_pc; + self.pc_modified = true; + } + Ok(()) + } + + fn exec_rev(&mut self, instruction: &Instruction) -> Result<()> { + if let (Some(Operand::Register(rd)), Some(Operand::Register(rm))) = + (instruction.operands.get(0), instruction.operands.get(1)) + { + let val = self.get_reg(*rm); + let result = match instruction.mnemonic.as_str() { + "rev" => val.swap_bytes(), + "rev16" => ((val & 0xFF00FF00) >> 8) | ((val & 0x00FF00FF) << 8), + "revsh" => (((val & 0xFF) << 8) | ((val >> 8) & 0xFF)) as i16 as i32 as u32, + _ => val, + }; + self.set_reg(*rd, result); + } + Ok(()) + } + + fn exec_clz(&mut self, instruction: &Instruction) -> Result<()> { + if let (Some(Operand::Register(rd)), Some(Operand::Register(rm))) = + (instruction.operands.get(0), instruction.operands.get(1)) + { + let val = self.get_reg(*rm); + let result = val.leading_zeros(); + self.set_reg(*rd, result as u32); + } + Ok(()) + } + + fn exec_it(&mut self, instruction: &Instruction) -> Result<()> { + let cond = match instruction.operands[0] { + Operand::Immediate(imm) => imm as u8, + _ => 0xE, + }; + let mask = match instruction.operands[1] { + Operand::Immediate(imm) => imm as u8, + _ => 0, + }; + self.it_state = (cond << 4) | mask; + Ok(()) + } + + fn exec_ldrh(&mut self, instruction: &Instruction) -> Result<()> { + if let Some(Operand::Register(rd)) = instruction.operands.get(0) { + let addr = self.get_mem_addr(instruction, 1)?; + let low = self.read_memory(addr)? as u32; + let high = self.read_memory(addr.wrapping_add(1))? as u32; + let val = (high << 8) | low; + self.set_reg(*rd, val); + } + Ok(()) + } + + fn exec_strh(&mut self, instruction: &Instruction) -> Result<()> { + if let Some(Operand::Register(rd)) = instruction.operands.get(0) { + let val = self.get_reg(*rd); + let addr = self.get_mem_addr(instruction, 1)?; + self.memory.insert(addr, (val & 0xFF) as u8); + self.memory + .insert(addr.wrapping_add(1), ((val >> 8) & 0xFF) as u8); + } + Ok(()) + } + + fn exec_extend(&mut self, instruction: &Instruction) -> Result<()> { + if let (Some(Operand::Register(rd)), Some(Operand::Register(rm))) = + (instruction.operands.get(0), instruction.operands.get(1)) + { + let val = self.get_reg(*rm); + let result = match instruction.mnemonic.as_str() { + "uxtb" | "uxtbs" => val & 0xFF, + "uxth" | "uxths" => val & 0xFFFF, + "sxtb" | "sxtbs" => (val as i8 as i32) as u32, + "sxth" | "sxths" => (val as i16 as i32) as u32, + _ => val, + }; + self.set_reg(*rd, result); + } + Ok(()) + } +} diff --git a/src/tools/emulator/src/decoder.rs b/src/tools/emulator/src/decoder.rs new file mode 100644 index 0000000..dc80b86 --- /dev/null +++ b/src/tools/emulator/src/decoder.rs @@ -0,0 +1,1471 @@ +#[derive(Debug, Clone)] +pub struct Instruction { + pub mnemonic: String, + pub operands: Vec, + pub condition: u8, + pub size: usize, +} + +#[derive(Debug, Clone)] +pub enum Operand { + Register(u8), + Immediate(i32), + Shift(u8, u8, u8), // Register, Type, Amount + Memory(u8, i32), +} + +pub fn decode(code: &[u8], is_thumb: bool) -> Result, String> { + let mut instructions = Vec::new(); + + if is_thumb { + if code.len() < 2 { + return Err("Insufficient bytes for Thumb instruction".to_string()); + } + let hw1 = u16::from_le_bytes([code[0], code[1]]); + + // Check for Thumb-2 32-bit instruction + // Thumb-2 prefix bits: 11101, 11110, 11111 + let prefix = (hw1 >> 11) & 0x1F; + if prefix >= 0x1D { + if code.len() < 4 { + return Err("Insufficient bytes for Thumb-2 instruction".to_string()); + } + let hw2 = u16::from_le_bytes([code[2], code[3]]); + decode_thumb32(hw1, hw2, &mut instructions)?; + } else { + decode_thumb16(hw1, &mut instructions)?; + } + } else { + if code.len() < 4 { + return Err("Insufficient bytes for ARM instruction".to_string()); + } + let word = u32::from_le_bytes([code[0], code[1], code[2], code[3]]); + let cond = (word >> 28) & 0xF; + let op = (word >> 25) & 0x7; + + match op { + 0b000 => decode_data_processing(word, cond, &mut instructions)?, + 0b001 => decode_data_processing_imm(word, cond, &mut instructions)?, + 0b010 => decode_load_store(word, cond, &mut instructions)?, + 0b100 => decode_ldm_stm(word, cond, &mut instructions)?, + 0b101 => decode_branch(word, cond, &mut instructions)?, + _ => { + instructions.push(Instruction { + mnemonic: "nop".to_string(), + operands: vec![], + condition: cond as u8, + size: 4, + }); + } + } + } + + Ok(instructions) +} + +fn decode_thumb16(hw: u16, instructions: &mut Vec) -> Result<(), String> { + let _op = hw >> 8; + + // PUSH/POP (Reg list) + if (hw & 0xFE00) == 0xB400 { + // PUSH {reglist} + let _l = (hw >> 11) & 1; // Not used for push/pop in this mask + let mnemonic = "push"; + let regs = hw & 0xFF; // lower 8 regs + let m = (hw >> 8) & 1; // LR for PUSH, PC for POP + let mut reg_list = regs as u32; + if m == 1 { + reg_list |= 1 << 14; // LR + } + instructions.push(Instruction { + mnemonic: mnemonic.to_string(), + operands: vec![Operand::Immediate(reg_list as i32)], + condition: 0xE, + size: 2, + }); + return Ok(()); + } + if (hw & 0xFE00) == 0xBC00 { + // POP {reglist} + let mnemonic = "pop"; + let regs = hw & 0xFF; + let m = (hw >> 8) & 1; + let mut reg_list = regs as u32; + if m == 1 { + reg_list |= 1 << 15; // PC + } + instructions.push(Instruction { + mnemonic: mnemonic.to_string(), + operands: vec![Operand::Immediate(reg_list as i32)], + condition: 0xE, + size: 2, + }); + return Ok(()); + } + + // MOV/CMP/ADD/SUB (Immediate) + // 001 op Rd imm8 + if (hw & 0xE000) == 0x2000 { + let op = (hw >> 11) & 3; + let rd = (hw >> 8) & 7; + let imm = hw & 0xFF; + let mnemonic = match op { + 0 => "movs", + 1 => "cmp", + 2 => "adds", + 3 => "subs", + _ => "unknown", + }; + instructions.push(Instruction { + mnemonic: mnemonic.to_string(), + operands: vec![Operand::Register(rd as u8), Operand::Immediate(imm as i32)], + condition: 0xE, + size: 2, + }); + return Ok(()); + } + + // ADD/SUB (3-operand) + if (hw & 0xF800) == 0x1800 { + let op = (hw >> 9) & 3; // bits 10:9 + let rn_rm = (hw >> 6) & 7; + let rn = (hw >> 3) & 7; + let rd = hw & 7; + + // 00011 00 Rm Rn Rd -> ADD Rd, Rn, Rm + // 00011 01 imm3 Rn Rd -> SUB Rd, Rn, imm3 (wait, bit 10 is 0 for these) + // Correct encoding: 0001 1 op ... + // op=0: ADD Reg, op=1: SUB Reg, op=2: ADD Imm, op=3: SUB Imm + let mnemonic = match op { + 0 => "adds", + 1 => "subs", + 2 => "adds", + 3 => "subs", + _ => "unknown", + }; + let mut operands = vec![Operand::Register(rd as u8), Operand::Register(rn as u8)]; + if op < 2 { + operands.push(Operand::Register(rn_rm as u8)); + } else { + operands.push(Operand::Immediate(rn_rm as i32)); + } + + instructions.push(Instruction { + mnemonic: mnemonic.to_string(), + operands, + condition: 0xE, + size: 2, + }); + return Ok(()); + } + + // Data processing (2-operand) + if (hw & 0xFC00) == 0x4000 { + let opcode = (hw >> 6) & 0xF; + let rm = (hw >> 3) & 7; + let rd = hw & 7; + let mnemonic = match opcode { + 0x0 => "ands", + 0x1 => "eors", + 0x2 => "lsls", + 0x3 => "lsrs", + 0x4 => "asrs", + 0x5 => "adcs", + 0x6 => "sbcs", + 0x7 => "rors", + 0x8 => "tst", + 0x9 => "rsbs", + 0xA => "cmp", + 0xB => "cmn", + 0xC => "orrs", + 0xD => "muls", + 0xE => "bics", + 0xF => "mvns", + _ => "unknown_dp", + }; + instructions.push(Instruction { + mnemonic: mnemonic.to_string(), + operands: vec![Operand::Register(rd as u8), Operand::Register(rm as u8)], + condition: 0xE, + size: 2, + }); + return Ok(()); + } + + // Load/Store (Register offset) + if (hw & 0xF000) == 0x5000 { + let op = (hw >> 9) & 7; + let rm = (hw >> 6) & 7; + let rn = (hw >> 3) & 7; + let rd = hw & 7; + let mnemonic = match op { + 0 => "str", + 1 => "strh", + 2 => "strb", + 3 => "ldrsb", + 4 => "ldr", + 5 => "ldrh", + 6 => "ldrb", + 7 => "ldrsh", + _ => "unknown_lsr", + }; + instructions.push(Instruction { + mnemonic: mnemonic.to_string(), + operands: vec![ + Operand::Register(rd as u8), + Operand::Memory(rn as u8, 0), + Operand::Register(rm as u8), + ], + condition: 0xE, + size: 2, + }); + return Ok(()); + } + + // REV/REV16/REVSH + if (hw & 0xFFC0) == 0xBA00 { + let op = (hw >> 6) & 3; + let rs = (hw >> 3) & 7; + let rd = hw & 7; + let mnemonic = match op { + 0 => "rev", + 1 => "rev16", + 3 => "revsh", + _ => "unknown_misc", + }; + instructions.push(Instruction { + mnemonic: mnemonic.to_string(), + operands: vec![Operand::Register(rd as u8), Operand::Register(rs as u8)], + condition: 0xE, + size: 2, + }); + return Ok(()); + } + + // NOP and other hints + if (hw & 0xFF00) == 0xBF00 { + if (hw & 0x000F) != 0 { + // IT block + let cond = (hw >> 4) & 0xF; + let mask = hw & 0xF; + instructions.push(Instruction { + mnemonic: "it".to_string(), + operands: vec![ + Operand::Immediate(cond as i32), + Operand::Immediate(mask as i32), + ], + condition: 0xE, + size: 2, + }); + return Ok(()); + } + instructions.push(Instruction { + mnemonic: "nop".to_string(), + operands: vec![], + condition: 0xE, + size: 2, + }); + return Ok(()); + } + + // CBZ/CBNZ + if (hw & 0xF500) == 0xB100 { + let op = (hw >> 11) & 1; // 0 = CBZ, 1 = CBNZ + let i = (hw >> 9) & 1; + let imm5 = (hw >> 3) & 0x1F; + let rn = hw & 0x7; + let imm6 = (i << 6) | (imm5 << 1); + let mnemonic = if op == 1 { "cbnz" } else { "cbz" }; + instructions.push(Instruction { + mnemonic: mnemonic.to_string(), + operands: vec![Operand::Register(rn as u8), Operand::Immediate(imm6 as i32)], + condition: 0xE, + size: 2, + }); + return Ok(()); + } + + // BX/BLX (Register) + if (hw & 0xFF00) == 0x4700 { + let rm = (hw >> 3) & 0xF; + let l = (hw >> 7) & 1; + let mnemonic = if l == 1 { "blx" } else { "bx" }; + instructions.push(Instruction { + mnemonic: mnemonic.to_string(), + operands: vec![Operand::Register(rm as u8)], + condition: 0xE, + size: 2, + }); + return Ok(()); + } + + // LDR (Literal/PC-relative) + if (hw & 0xF800) == 0x4800 { + let rd = (hw >> 8) & 7; + let imm = (hw & 0xFF) << 2; + instructions.push(Instruction { + mnemonic: "ldr".to_string(), + operands: vec![Operand::Register(rd as u8), Operand::Memory(15, imm as i32)], + condition: 0xE, + size: 2, + }); + return Ok(()); + } + + // Special Data Processing (High registers) + if (hw & 0xFC00) == 0x4400 { + let opcode = (hw >> 8) & 3; + let h1 = (hw >> 7) & 1; + let h2 = (hw >> 6) & 1; + let rm = ((hw >> 3) & 7) | (h2 << 3); + let rd = (hw & 7) | (h1 << 3); + + let mnemonic = match opcode { + 0 => "add", + 1 => "cmp", + 2 => "mov", + _ => "unknown_sdp", + }; + + if mnemonic != "unknown_sdp" { + instructions.push(Instruction { + mnemonic: mnemonic.to_string(), + operands: vec![Operand::Register(rd as u8), Operand::Register(rm as u8)], + condition: 0xE, + size: 2, + }); + return Ok(()); + } + } + + // ADD/SUB SP, #imm + if (hw & 0xFF00) == 0xB000 { + let op = (hw >> 7) & 1; + let imm = (hw & 0x7F) << 2; + let mnemonic = if op == 1 { "sub" } else { "add" }; + instructions.push(Instruction { + mnemonic: mnemonic.to_string(), + operands: vec![Operand::Register(13), Operand::Immediate(imm as i32)], + condition: 0xE, + size: 2, + }); + return Ok(()); + } + + // STR/LDR Rd, [SP, #imm] + if (hw & 0xF000) == 0x9000 { + let l = (hw >> 11) & 1; + let rd = (hw >> 8) & 7; + let imm = (hw & 0xFF) << 2; + let mnemonic = if l == 1 { "ldr" } else { "str" }; + instructions.push(Instruction { + mnemonic: mnemonic.to_string(), + operands: vec![ + Operand::Register(rd as u8), + Operand::Memory(13, imm as i32), + Operand::Immediate(0b10), // P=1, W=0 + ], + condition: 0xE, + size: 2, + }); + return Ok(()); + } + + // LDR/STR Rd, [Rn, #imm] (5-bit imm) + if (hw & 0xF000) == 0x6000 { + let l = (hw >> 11) & 1; + let imm = ((hw >> 6) & 0x1F) << 2; + let rn = (hw >> 3) & 7; + let rd = hw & 7; + let mnemonic = if l == 1 { "ldr" } else { "str" }; + instructions.push(Instruction { + mnemonic: mnemonic.to_string(), + operands: vec![ + Operand::Register(rd as u8), + Operand::Memory(rn as u8, imm as i32), + Operand::Immediate(0b10), + ], + condition: 0xE, + size: 2, + }); + return Ok(()); + } + + // ADD Rd, SP, #imm + if (hw & 0xF800) == 0xA800 { + let rd = (hw >> 8) & 7; + let imm = (hw & 0xFF) << 2; + instructions.push(Instruction { + mnemonic: "add".to_string(), + operands: vec![ + Operand::Register(rd as u8), + Operand::Register(13), + Operand::Immediate(imm as i32), + ], + condition: 0xE, + size: 2, + }); + return Ok(()); + } + + // B imm11 + if (hw & 0xF800) == 0xE000 { + let imm11 = hw & 0x7FF; + // Sign extend 11-bit + let mut offset = (imm11 as i32) << 1; + if offset & 0x1000 != 0 { + offset |= 0xFFFFE000u32 as i32; + } + instructions.push(Instruction { + mnemonic: "b".to_string(), + operands: vec![Operand::Immediate(offset)], + condition: 0xE, + size: 2, + }); + return Ok(()); + } + + // ADR Rd, label + if (hw & 0xF800) == 0xA000 { + let rd = (hw >> 8) & 7; + let imm = (hw & 0xFF) << 2; + instructions.push(Instruction { + mnemonic: "add".to_string(), + operands: vec![ + Operand::Register(rd as u8), + Operand::Register(15), + Operand::Immediate(imm as i32), + ], + condition: 0xE, + size: 2, + }); + return Ok(()); + } + + // B imm8 + if (hw & 0xF000) == 0xD000 { + let cond = (hw >> 8) & 0xF; + if cond == 0xF { + // SWI/SVC or other, but 0xDF is SVC + instructions.push(Instruction { + mnemonic: "svc".to_string(), + operands: vec![Operand::Immediate((hw & 0xFF) as i32)], + condition: 0xE, + size: 2, + }); + } else { + let imm8 = hw & 0xFF; + let offset = (imm8 as i8 as i32) << 1; + instructions.push(Instruction { + mnemonic: "b".to_string(), + operands: vec![Operand::Immediate(offset)], + condition: cond as u8, + size: 2, + }); + } + return Ok(()); + } + + // LDRB Rd, [Rn, #imm5] + if (hw & 0xF800) == 0x7800 { + let imm = (hw >> 6) & 0x1F; + let rn = (hw >> 3) & 7; + let rd = hw & 7; + instructions.push(Instruction { + mnemonic: "ldrb".to_string(), + operands: vec![ + Operand::Register(rd as u8), + Operand::Memory(rn as u8, imm as i32), + Operand::Immediate(0b10), + ], + condition: 0xE, + size: 2, + }); + return Ok(()); + } + // Extend instructions (UXTB, UXTH, SXTB, SXTH) + if (hw & 0xFF00) == 0xB200 { + let op = (hw >> 6) & 3; + let rn = (hw >> 3) & 7; + let rd = hw & 7; + let mnemonic = match op { + 0 => "sxtb", + 1 => "sxth", + 2 => "uxtb", + 3 => "uxth", + _ => "unknown", + }; + instructions.push(Instruction { + mnemonic: mnemonic.to_string(), + operands: vec![Operand::Register(rd as u8), Operand::Register(rn as u8)], + condition: 0xE, + size: 2, + }); + return Ok(()); + } + + // Default: unknown Thumb-16 + instructions.push(Instruction { + mnemonic: format!("unknown_t16_{:04x}", hw), + operands: vec![], + condition: 0xE, + size: 2, + }); + + Ok(()) +} + +fn decode_thumb32(hw1: u16, hw2: u16, instructions: &mut Vec) -> Result<(), String> { + // UBFX (T1) + if (hw1 & 0xFFF0) == 0xF3C0 { + let rn = hw1 & 0xF; + let rd = (hw2 >> 8) & 0xF; + let imm3 = (hw2 >> 12) & 7; + let imm2 = (hw2 >> 6) & 3; + let lsb = (imm3 << 2) | imm2; + let widthminus1 = hw2 & 0x1F; + let width = widthminus1 + 1; + + instructions.push(Instruction { + mnemonic: "ubfx".to_string(), + operands: vec![ + Operand::Register(rd as u8), + Operand::Register(rn as u8), + Operand::Immediate(lsb as i32), + Operand::Immediate(width as i32), + ], + condition: 0xE, + size: 4, + }); + return Ok(()); + } + + // SBFX (T1) + if (hw1 & 0xFFF0) == 0xF340 { + let rn = hw1 & 0xF; + let rd = (hw2 >> 8) & 0xF; + let imm3 = (hw2 >> 12) & 7; + let imm2 = (hw2 >> 6) & 3; + let lsb = (imm3 << 2) | imm2; + let widthminus1 = hw2 & 0x1F; + let width = widthminus1 + 1; + + instructions.push(Instruction { + mnemonic: "sbfx".to_string(), + operands: vec![ + Operand::Register(rd as u8), + Operand::Register(rn as u8), + Operand::Immediate(lsb as i32), + Operand::Immediate(width as i32), + ], + condition: 0xE, + size: 4, + }); + return Ok(()); + } + + // BFC / BFI (T1) + if (hw1 & 0xFFE0) == 0xF360 { + let rn = hw1 & 0xF; + let rd = (hw2 >> 8) & 0xF; + let msb = hw2 & 0x1F; + let imm3 = (hw2 >> 12) & 7; + let imm2 = (hw2 >> 6) & 3; + let lsb = (imm3 << 2) | imm2; + let width = msb as i32 - lsb as i32 + 1; + + if width > 0 { + if rn == 15 { + // BFC + instructions.push(Instruction { + mnemonic: "bfc".to_string(), + operands: vec![ + Operand::Register(rd as u8), + Operand::Immediate(lsb as i32), + Operand::Immediate(width), + ], + condition: 0xE, + size: 4, + }); + } else { + // BFI + instructions.push(Instruction { + mnemonic: "bfi".to_string(), + operands: vec![ + Operand::Register(rd as u8), + Operand::Register(rn as u8), + Operand::Immediate(lsb as i32), + Operand::Immediate(width), + ], + condition: 0xE, + size: 4, + }); + } + return Ok(()); + } + } + + // LDR (register) T2 (F85x) + if (hw1 & 0xFFF0) == 0xF850 { + let rn = hw1 & 0xF; + let rt = (hw2 >> 12) & 0xF; + let rm = hw2 & 0xF; + let imm2 = (hw2 >> 4) & 3; + + instructions.push(Instruction { + mnemonic: "ldr".to_string(), + operands: vec![ + Operand::Register(rt as u8), + Operand::Memory(rn as u8, 0), // Base + Operand::Shift(rm as u8, 0, imm2 as u8), // Offset + Operand::Immediate(0b10), // P=1, W=0 + ], + condition: 0xE, + size: 4, + }); + return Ok(()); + } + + // BL/BLX (Immediate) + if (hw1 & 0xF800) == 0xF000 && (hw2 & 0xC000) == 0xC000 { + // Matches 1111 0xxx xxxx xxxx + let s = (hw1 >> 10) & 1; + let j1 = (hw2 >> 13) & 1; + let j2 = (hw2 >> 11) & 1; + let imm10 = hw1 & 0x3FF; + let imm11 = hw2 & 0x7FF; + + let i1 = (!(j1 ^ s)) & 1; + let i2 = (!(j2 ^ s)) & 1; + + let mut offset = (s as i32) << 24; + offset |= (i1 as i32) << 23; + offset |= (i2 as i32) << 22; + offset |= (imm10 as i32) << 12; + offset |= (imm11 as i32) << 1; + + // Sign extend 25-bit + if offset & 0x01000000 != 0 { + offset |= 0xFE000000u32 as i32; + } + + let is_blx = (hw2 & 0x1000) == 0; // If bit 12 of hw2 is 0, it's BLX + + instructions.push(Instruction { + mnemonic: if is_blx { "blx" } else { "bl" }.to_string(), + operands: vec![Operand::Immediate(offset)], + condition: 0xE, + size: 4, + }); + return Ok(()); + } + + // LDR/STR (Immediate) 32-bit + if (hw1 & 0xFF00) == 0xF800 && (hw2 & 0x0800) != 0 { + // P=1, W=0, U=1 (implied by imm12) + let l = (hw1 >> 4) & 1; + let rn = hw1 & 0xF; + let rd = (hw2 >> 12) & 0xF; + let imm12 = hw2 & 0xFFF; + let mnemonic = if l == 1 { "ldr" } else { "str" }; + + instructions.push(Instruction { + mnemonic: mnemonic.to_string(), + operands: vec![ + Operand::Register(rd as u8), + Operand::Memory(rn as u8, imm12 as i32), + Operand::Immediate(0b10), // P=1, W=0 + ], + condition: 0xE, + size: 4, + }); + return Ok(()); + } + + // LDR (Literal) 32-bit + if (hw1 & 0xFF7F) == 0xF85F { + let u = (hw1 >> 7) & 1; + let rd = (hw2 >> 12) & 0xF; + let imm12 = hw2 & 0xFFF; + let offset = if u == 1 { + imm12 as i32 + } else { + -(imm12 as i32) + }; + + instructions.push(Instruction { + mnemonic: "ldr".to_string(), + operands: vec![Operand::Register(rd as u8), Operand::Memory(15, offset)], + condition: 0xE, + size: 4, + }); + return Ok(()); + } + + // LDRD/STRD (Immediate) + if (hw1 & 0xFFE0) == 0xE9C0 { + let p = (hw1 >> 8) & 1; + let u = (hw1 >> 7) & 1; + let w = (hw1 >> 5) & 1; + let l = (hw1 >> 4) & 1; + let rn = hw1 & 0xF; + let rt = (hw2 >> 12) & 0xF; + let rt2 = (hw2 >> 8) & 0xF; + let imm8 = hw2 & 0xFF; + let offset = (imm8 as i32) << 2; + + let mnemonic = if l == 1 { "ldrd" } else { "strd" }; + let offset_val = if u == 1 { offset } else { -offset }; + let bits = (p << 1) | w; + + instructions.push(Instruction { + mnemonic: mnemonic.to_string(), + operands: vec![ + Operand::Register(rt as u8), + Operand::Register(rt2 as u8), + Operand::Memory(rn as u8, offset_val), + Operand::Immediate(bits as i32), + ], + condition: 0xE, + size: 4, + }); + return Ok(()); + } + + // LDREX/STREX + if (hw1 & 0xFFC0) == 0xE840 { + let l = (hw1 >> 4) & 1; // 1=LDREX, 0=STREX + let rn = hw1 & 0xF; + let rt = (hw2 >> 12) & 0xF; + let imm8 = hw2 & 0xFF; // imm8 << 2 + + if l == 1 { + // LDREX + instructions.push(Instruction { + mnemonic: "ldrex".to_string(), + operands: vec![ + Operand::Register(rt as u8), + Operand::Memory(rn as u8, (imm8 as i32) << 2), // LDREX can have offset + ], + condition: 0xE, + size: 4, + }); + } else { + // STREX Rd, Rt, [Rn, #imm] + // STREX returns status in Rd, stores Rt to Memory + let rd = (hw2 >> 8) & 0xF; // Status register + instructions.push(Instruction { + mnemonic: "strex".to_string(), + operands: vec![ + Operand::Register(rd as u8), + Operand::Register(rt as u8), + Operand::Memory(rn as u8, (imm8 as i32) << 2), + ], + condition: 0xE, + size: 4, + }); + } + return Ok(()); + } + + // ADDW: F20. + if (hw1 & 0xFFF0) == 0xF200 { + let rn = hw1 & 0xF; + let rd = (hw2 >> 8) & 0xF; + let i = (hw1 >> 10) & 1; + let imm3 = (hw2 >> 12) & 7; + let imm8 = hw2 & 0xFF; + let imm12 = (i as i32) << 11 | (imm3 as i32) << 8 | (imm8 as i32); + instructions.push(Instruction { + mnemonic: "addw".to_string(), + operands: vec![ + Operand::Register(rd as u8), + Operand::Register(rn as u8), + Operand::Immediate(imm12), + ], + condition: 0xE, + size: 4, + }); + return Ok(()); + } + + // SUBW: F2A. + if (hw1 & 0xFFF0) == 0xF2A0 { + let rn = hw1 & 0xF; + let rd = (hw2 >> 8) & 0xF; + let i = (hw1 >> 10) & 1; + let imm3 = (hw2 >> 12) & 7; + let imm8 = hw2 & 0xFF; + let imm12 = (i as i32) << 11 | (imm3 as i32) << 8 | (imm8 as i32); + instructions.push(Instruction { + mnemonic: "subw".to_string(), + operands: vec![ + Operand::Register(rd as u8), + Operand::Register(rn as u8), + Operand::Immediate(imm12), + ], + condition: 0xE, + size: 4, + }); + return Ok(()); + } + + // MOVW: F24. + if (hw1 & 0xFBF0) == 0xF240 { + let rd = (hw2 >> 8) & 0xF; + let i = (hw1 >> 10) & 1; + let imm4 = hw1 & 0xF; + let imm3 = (hw2 >> 12) & 7; + let imm8 = hw2 & 0xFF; + let imm16 = (imm4 as i32) << 12 | (i as i32) << 11 | (imm3 as i32) << 8 | (imm8 as i32); + instructions.push(Instruction { + mnemonic: "movw".to_string(), + operands: vec![Operand::Register(rd as u8), Operand::Immediate(imm16)], + condition: 0xE, + size: 4, + }); + return Ok(()); + } + + // MOVT: F2C. + if (hw1 & 0xFBF0) == 0xF2C0 { + let rd = (hw2 >> 8) & 0xF; + let i = (hw1 >> 10) & 1; + let imm4 = hw1 & 0xF; + let imm3 = (hw2 >> 12) & 7; + let imm8 = hw2 & 0xFF; + let imm16 = (imm4 as i32) << 12 | (i as i32) << 11 | (imm3 as i32) << 8 | (imm8 as i32); + instructions.push(Instruction { + mnemonic: "movt".to_string(), + operands: vec![Operand::Register(rd as u8), Operand::Immediate(imm16)], + condition: 0xE, + size: 4, + }); + return Ok(()); + } + if (hw1 & 0xFFF0) == 0xE8D0 && (hw2 & 0xFFE0) == 0xF000 { + let h = (hw2 >> 4) & 1; + let rn = hw1 & 0xF; + let rm = hw2 & 0xF; + instructions.push(Instruction { + mnemonic: if h == 1 { "tbh" } else { "tbb" }.to_string(), + operands: vec![Operand::Register(rn as u8), Operand::Register(rm as u8)], + condition: 0xE, + size: 4, + }); + return Ok(()); + } + + // B.W (Branch Conditional Wide) T3 + if (hw1 & 0xF800) == 0xF000 && (hw2 & 0xD000) == 0x8000 { + let s = (hw1 >> 10) & 1; + let cond = (hw1 >> 6) & 0xF; + let imm6 = hw1 & 0x3F; + let j1 = (hw2 >> 13) & 1; + let j2 = (hw2 >> 11) & 1; + let imm11 = hw2 & 0x7FF; + + let mut offset = (s as i32) << 20; + offset |= (j2 as i32) << 19; + offset |= (j1 as i32) << 18; + offset |= (imm6 as i32) << 12; + offset |= (imm11 as i32) << 1; + + if offset & 0x00100000 != 0 { + offset |= 0xFFE00000u32 as i32; + } + + // Avoid predicting AL (0xE) or invalid condition as conditional branch if unnecessary, + // but B.W allows AL (encoding T3). If cond=1110, it is AL. + // However, standard B.W (T4) is usually preferred for AL. + // But if code uses T3 for AL, we handle it. + + instructions.push(Instruction { + mnemonic: "b".to_string(), + operands: vec![Operand::Immediate(offset)], + condition: cond as u8, + size: 4, + }); + return Ok(()); + } + + // B.W (Branch Wide) + if (hw1 & 0xF800) == 0xF000 && (hw2 & 0xD000) == 0x9000 { + let s = (hw1 >> 10) & 1; + let j1 = (hw2 >> 13) & 1; + let j2 = (hw2 >> 11) & 1; + let imm10 = hw1 & 0x3FF; + let imm11 = hw2 & 0x7FF; + + let i1 = (!(j1 ^ s)) & 1; + let i2 = (!(j2 ^ s)) & 1; + + let mut offset = (s as i32) << 24; + offset |= (i1 as i32) << 23; + offset |= (i2 as i32) << 22; + offset |= (imm10 as i32) << 12; + offset |= (imm11 as i32) << 1; + + if offset & 0x01000000 != 0 { + offset |= 0xFE000000u32 as i32; + } + + instructions.push(Instruction { + mnemonic: "b".to_string(), + operands: vec![Operand::Immediate(offset)], + condition: 0xE, + size: 4, + }); + return Ok(()); + } + + // BFC / BFI (T1) + if (hw1 & 0xFFE0) == 0xF360 { + let rn = hw1 & 0xF; + let rd = (hw2 >> 8) & 0xF; + let msb = hw2 & 0x1F; + let imm3 = (hw2 >> 12) & 7; + let imm2 = (hw2 >> 6) & 3; + let lsb = (imm3 << 2) | imm2; + let width = msb as i32 - lsb as i32 + 1; + + if width > 0 { + if rn == 15 { + // BFC + instructions.push(Instruction { + mnemonic: "bfc".to_string(), + operands: vec![ + Operand::Register(rd as u8), + Operand::Immediate(lsb as i32), + Operand::Immediate(width), + ], + condition: 0xE, + size: 4, + }); + } else { + // BFI + instructions.push(Instruction { + mnemonic: "bfi".to_string(), + operands: vec![ + Operand::Register(rd as u8), + Operand::Register(rn as u8), + Operand::Immediate(lsb as i32), + Operand::Immediate(width), + ], + condition: 0xE, + size: 4, + }); + } + return Ok(()); + } + } + + // LDR (register) T2 (F85x) + if (hw1 & 0xFFF0) == 0xF850 { + let rn = hw1 & 0xF; + let rt = (hw2 >> 12) & 0xF; + let rm = hw2 & 0xF; + let imm2 = (hw2 >> 4) & 3; + + instructions.push(Instruction { + mnemonic: "ldr".to_string(), + operands: vec![ + Operand::Register(rt as u8), + Operand::Memory(rn as u8, 0), // Base + Operand::Shift(rm as u8, 0, imm2 as u8), // Offset + Operand::Immediate(0b10), // P=1, W=0 + ], + condition: 0xE, + size: 4, + }); + return Ok(()); + } + + // ADD/SUB (register) T2/T3 (EB0x / EBAx) + if (hw1 & 0xFF00) == 0xEB00 { + let op = (hw1 >> 4) & 0xF; + let rn = hw1 & 0xF; + let rd = (hw2 >> 8) & 0xF; + let rm = hw2 & 0xF; + let type_ = (hw2 >> 4) & 3; + let imm3 = (hw2 >> 12) & 7; + let imm2 = (hw2 >> 6) & 3; + let amount = ((imm3 << 2) | imm2) as u8; + + if op == 0 { + // ADD + instructions.push(Instruction { + mnemonic: "add".to_string(), + operands: vec![ + Operand::Register(rd as u8), + Operand::Register(rn as u8), + Operand::Shift(rm as u8, type_ as u8, amount), + ], + condition: 0xE, + size: 4, + }); + return Ok(()); + } else if op == 0xA { + // SUB + instructions.push(Instruction { + mnemonic: "sub".to_string(), + operands: vec![ + Operand::Register(rd as u8), + Operand::Register(rn as u8), + Operand::Shift(rm as u8, type_ as u8, amount), + ], + condition: 0xE, + size: 4, + }); + return Ok(()); + } + } + + // Data processing (Modified immediate) + if ((hw1 & 0xFB00) == 0xF100 || (hw1 & 0xFB00) == 0xF000) && (hw2 & 0x8000) == 0 { + let rn = (hw1 >> 0) & 0xF; + let rd = (hw2 >> 8) & 0xF; + let i = (hw1 >> 10) & 1; + let imm3 = (hw2 >> 12) & 7; + let imm8 = hw2 & 0xFF; + + let op = (hw1 >> 5) & 0xF; + let s = (hw1 >> 4) & 1; + + // Very simplified ARM modified immediate + let imm = (i << 11) | (imm3 << 8) | imm8; + + let mnemonic_base = match op { + 0x0 => { + if rd == 0xF && s == 1 { + "tst" + } else { + "and" + } + } + 0x1 => "bic", + 0x2 => { + if rn == 0xF { + "mov" + } else { + "orr" + } + } + 0x3 => { + if rn == 0xF { + "mvn" + } else { + "orn" + } + } + 0x4 => { + if rd == 0xF && s == 1 { + "teq" + } else { + "eor" + } + } + 0x8 => { + if rd == 0xF && s == 1 { + "cmn" + } else { + "add" + } + } + 0xA => "adc", + 0xB => "sbc", + 0xD => { + if rd == 0xF && s == 1 { + "cmp" + } else { + "sub" + } + } + 0xE => "rsb", + _ => "unknown_dp_t32", + }; + + let mut mnemonic = mnemonic_base.to_string(); + if s == 1 + && !mnemonic.ends_with('t') + && mnemonic != "cmp" + && mnemonic != "cmn" + && mnemonic != "tst" + && mnemonic != "teq" + { + mnemonic.push('s'); + } + + instructions.push(Instruction { + mnemonic, + operands: vec![ + Operand::Register(rd as u8), + Operand::Register(rn as u8), + Operand::Immediate(imm as i32), + ], + condition: 0xE, + size: 4, + }); + return Ok(()); + } + + // PUSH.W / POP.W / LDM / STM wide + if (hw1 & 0xFE40) == 0xE800 { + let l = (hw1 >> 4) & 1; // Load/Store + let rn = hw1 & 0xF; + let p = (hw1 >> 8) & 1; // Pre/Post + let u = (hw1 >> 7) & 1; // Up/Down + let w = (hw1 >> 5) & 1; // Write-back + let reg_list = hw2 as u32; + + let mnemonic = if l == 1 { + if rn == 13 && p == 0 && u == 1 && w == 1 { + "pop" + } else { + "ldm" + } + } else { + if rn == 13 && p == 1 && u == 0 && w == 1 { + "push" + } else { + "stm" + } + }; + + let mut operands = vec![Operand::Register(rn as u8)]; + operands.push(Operand::Immediate(reg_list as i32)); + // bits: P, U, W + operands.push(Operand::Immediate(((p << 2) | (u << 1) | w) as i32)); + + instructions.push(Instruction { + mnemonic: mnemonic.to_string(), + operands, + condition: 0xE, + size: 4, + }); + return Ok(()); + } + + // PUSH.W variant 2 + if (hw1 & 0xFFF0) == 0xE920 { + let rn = hw1 & 0xF; + if rn == 13 { + let reg_list = hw2 as u32; + instructions.push(Instruction { + mnemonic: "push".to_string(), + operands: vec![Operand::Immediate(reg_list as i32)], + condition: 0xE, + size: 4, + }); + return Ok(()); + } + } + + // LDR.W Rd, [PC, #imm12] + if (hw1 & 0xFF7F) == 0xF85F { + let rd = (hw2 >> 12) & 0xF; + let imm = hw2 & 0xFFF; + instructions.push(Instruction { + mnemonic: "ldr".to_string(), + operands: vec![Operand::Register(rd as u8), Operand::Memory(15, imm as i32)], + condition: 0xE, + size: 4, + }); + return Ok(()); + } + + // LDR/STR (Immediate/Register offset) + if (hw1 & 0xFE00) == 0xF800 { + let rn = hw1 & 0xF; + let rt = (hw2 >> 12) & 0xF; + + let op_bits = (hw1 >> 4) & 0x1F; + let mnemonic = match op_bits { + 0x08 | 0x28 => "strb", + 0x09 | 0x29 => "ldrb", + 0x10 | 0x30 => "strh", + 0x11 | 0x31 => "ldrh", + 0x18 | 0x38 | 0x0C | 0x2C => "str", + 0x19 | 0x39 | 0x0D | 0x2D => "ldr", + _ => "unknown_t32_ls", + }; + + if mnemonic != "unknown_t32_ls" { + let mut operands = vec![Operand::Register(rt as u8)]; + if (hw2 & 0x0800) != 0 { + // 12-bit immediate + let imm = hw2 & 0x0FFF; + operands.push(Operand::Memory(rn as u8, imm as i32)); + // Add P=1, W=0 (bits 0b10) for normal immediate + operands.push(Operand::Immediate(0b10)); + } else { + // 8-bit immediate with P, U, W or register offset + let sub_op = (hw2 >> 8) & 7; + if sub_op == 0 { + // Register offset + let rm = hw2 & 0xF; + operands.push(Operand::Memory(rn as u8, 0)); + operands.push(Operand::Register(rm as u8)); + } else { + // 8-bit immediate + let p = (hw2 >> 10) & 1; + let u = (hw2 >> 9) & 1; + let w = (hw2 >> 8) & 1; + let imm8 = hw2 & 0xFF; + let offset = if u == 1 { imm8 as i32 } else { -(imm8 as i32) }; + operands.push(Operand::Memory(rn as u8, offset)); + operands.push(Operand::Immediate(((p << 1) | w) as i32)); + } + } + + instructions.push(Instruction { + mnemonic: mnemonic.to_string(), + operands, + condition: 0xE, + size: 4, + }); + return Ok(()); + } + } + + // Default: unknown Thumb-32 + instructions.push(Instruction { + mnemonic: format!("unknown_t32_{:04x}_{:04x}", hw1, hw2), + operands: vec![], + condition: 0xE, + size: 4, + }); + + Ok(()) +} + +fn decode_data_processing( + word: u32, + cond: u32, + instructions: &mut Vec, +) -> Result<(), String> { + let s = (word >> 20) & 1; + let opcode = (word >> 21) & 0xF; + let rd = (word >> 12) & 0xF; + let rn = (word >> 16) & 0xF; + let rm = word & 0xF; + + // Check for BX (Branch and Exchange) + if (word & 0x0FFFFFF0) == 0x012FFF10 { + instructions.push(Instruction { + mnemonic: "bx".to_string(), + operands: vec![Operand::Register(rm as u8)], + condition: cond as u8, + size: 4, + }); + return Ok(()); + } + + let mnemonic_base = match opcode { + 0x0 => "and", + 0x1 => "eor", + 0x2 => "sub", + 0x3 => "rsb", + 0x4 => "add", + 0x5 => "adc", + 0x6 => "sbc", + 0x7 => "rsc", + 0x8 => "tst", + 0x9 => "teq", + 0xA => "cmp", + 0xB => "cmn", + 0xC => "orr", + 0xD => "mov", + 0xE => "bic", + 0xF => "mvn", + _ => "unknown", + }; + + let mut mnemonic = mnemonic_base.to_string(); + if s == 1 && !matches!(mnemonic_base, "cmp" | "tst" | "teq" | "cmn") { + mnemonic.push('s'); + } + + let mut operands = Vec::new(); + match mnemonic_base { + "mov" | "mvn" => { + operands.push(Operand::Register(rd as u8)); + operands.push(Operand::Register(rm as u8)); + } + "cmp" | "tst" | "teq" | "cmn" => { + operands.push(Operand::Register(rn as u8)); + operands.push(Operand::Register(rm as u8)); + } + _ => { + operands.push(Operand::Register(rd as u8)); + operands.push(Operand::Register(rn as u8)); + operands.push(Operand::Register(rm as u8)); + } + } + + instructions.push(Instruction { + mnemonic, + operands, + condition: cond as u8, + size: 4, + }); + + Ok(()) +} + +fn decode_data_processing_imm( + word: u32, + cond: u32, + instructions: &mut Vec, +) -> Result<(), String> { + let s = (word >> 20) & 1; + let opcode = (word >> 21) & 0xF; + let rd = (word >> 12) & 0xF; + let rn = (word >> 16) & 0xF; + let imm_val = word & 0xFF; + let rotate = (word >> 8) & 0xF; + + // ARM immediate rotation: value is rotated right by (rotate * 2) bits + let imm = imm_val.rotate_right(rotate * 2); + + let mnemonic_base = match opcode { + 0x0 => "and", + 0x1 => "eor", + 0x2 => "sub", + 0x3 => "rsb", + 0x4 => "add", + 0x5 => "adc", + 0x6 => "sbc", + 0x7 => "rsc", + 0x8 => "tst", + 0x9 => "teq", + 0xA => "cmp", + 0xB => "cmn", + 0xC => "orr", + 0xD => "mov", + 0xE => "bic", + 0xF => "mvn", + _ => "unknown", + }; + + let mut mnemonic = mnemonic_base.to_string(); + if s == 1 && !matches!(mnemonic_base, "cmp" | "tst" | "teq" | "cmn") { + mnemonic.push('s'); + } + + let mut operands = Vec::new(); + match mnemonic_base { + "mov" | "mvn" => { + operands.push(Operand::Register(rd as u8)); + operands.push(Operand::Immediate(imm as i32)); + } + "cmp" | "tst" | "teq" | "cmn" => { + operands.push(Operand::Register(rn as u8)); + operands.push(Operand::Immediate(imm as i32)); + } + _ => { + operands.push(Operand::Register(rd as u8)); + operands.push(Operand::Register(rn as u8)); + operands.push(Operand::Immediate(imm as i32)); + } + } + + instructions.push(Instruction { + mnemonic, + operands, + condition: cond as u8, + size: 4, + }); + + Ok(()) +} + +fn decode_load_store( + word: u32, + cond: u32, + instructions: &mut Vec, +) -> Result<(), String> { + let p = (word >> 24) & 1; // Pre/Post index + let u = (word >> 23) & 1; // Up/Down bit + let w = (word >> 21) & 1; // Write-back + let l = (word >> 20) & 1; // Load/Store + let rd = (word >> 12) & 0xF; + let rn = (word >> 16) & 0xF; + let offset = word & 0xFFF; + + let mnemonic = if l == 1 { "ldr" } else { "str" }; + + // Apply sign based on U bit + let signed_offset = if u == 1 { + offset as i32 + } else { + -(offset as i32) + }; + + instructions.push(Instruction { + mnemonic: mnemonic.to_string(), + operands: vec![ + Operand::Register(rd as u8), + Operand::Memory(rn as u8, signed_offset), + Operand::Immediate(((p << 1) | w) as i32), + ], + condition: cond as u8, + size: 4, + }); + + Ok(()) +} + +fn decode_branch(word: u32, cond: u32, instructions: &mut Vec) -> Result<(), String> { + let l = (word >> 24) & 1; + let offset = word & 0xFFFFFF; + + // Sign extend 24-bit offset to 32-bit + let signed_offset = if offset & 0x800000 != 0 { + ((offset | 0xFF000000) as i32) << 2 + } else { + (offset as i32) << 2 + }; + + let mnemonic = if l == 1 { "bl" } else { "b" }; + + instructions.push(Instruction { + mnemonic: mnemonic.to_string(), + operands: vec![Operand::Immediate(signed_offset)], + condition: cond as u8, + size: 4, + }); + + Ok(()) +} + +fn decode_ldm_stm(word: u32, cond: u32, instructions: &mut Vec) -> Result<(), String> { + let p = (word >> 24) & 1; // Pre/Post index + let u = (word >> 23) & 1; // Up/Down + let _s = (word >> 22) & 1; // PSR or force user mode + let w = (word >> 21) & 1; // Write-back + let l = (word >> 20) & 1; // Load/Store + let rn = (word >> 16) & 0xF; + let reg_list = word & 0xFFFF; + + let mnemonic = if l == 1 { "ldm" } else { "stm" }; + + // Add base register + let mut operands = vec![Operand::Register(rn as u8)]; + + // Add bitmask as an immediate (simplified) + operands.push(Operand::Immediate(reg_list as i32)); + + // Add P, U, W bits as an immediate to help executor + let bits = (p << 2) | (u << 1) | w; + operands.push(Operand::Immediate(bits as i32)); + + instructions.push(Instruction { + mnemonic: mnemonic.to_string(), + operands, + condition: cond as u8, + size: 4, + }); + + Ok(()) +} diff --git a/src/tools/emulator/src/hardware.rs b/src/tools/emulator/src/hardware.rs new file mode 100644 index 0000000..e8f81e2 --- /dev/null +++ b/src/tools/emulator/src/hardware.rs @@ -0,0 +1,300 @@ +use std::collections::HashMap; +use std::io::{self, Write}; +use std::time::{SystemTime, UNIX_EPOCH}; + +pub struct Uart; + +impl Uart { + pub fn new() -> Self { + Self {} + } + + pub fn read(&self, offset: u32) -> u32 { + match offset { + 0x10 => 0x6, // UTRSTAT - TX empty | RX empty(0) | TX buf empty + 0x14 => 0x0, // UERSTAT + 0x18 => 0x0, // UFSTAT - RX FIFO count 0, TX FIFO count 0 + _ => 0, + } + } + + pub fn write(&mut self, offset: u32, value: u32) { + match offset { + 0x20 => { + // UTXH - Transmit char + let c = value as u8; + print!("{}", c as char); + let _ = io::stdout().flush(); + } + _ => { + // eprintln!("UART Write: Offset 0x{:x}, Value 0x{:x}", offset, value); + } + } + } +} + +pub struct Timer { + pub control: u32, +} + +impl Timer { + pub fn new() -> Self { + Self { control: 0 } + } + + pub fn read(&self, offset: u32) -> u32 { + match offset { + 0x00 => self.control, // TCON + 0x04 => { + // TCNT - Return generic time-based counter + let start = SystemTime::now(); + let since_epoch = start.duration_since(UNIX_EPOCH).unwrap(); + (since_epoch.as_micros() & 0xFFFFFFFF) as u32 + } + _ => 0, + } + } + + pub fn write(&mut self, offset: u32, value: u32) { + match offset { + 0x00 => self.control = value, + _ => {} + } + } +} + +pub struct GenericStub { + pub name: String, + pub regs: HashMap, +} + +impl GenericStub { + pub fn new(name: &str) -> Self { + Self { + name: name.to_string(), + regs: HashMap::new(), + } + } + + pub fn read(&self, offset: u32) -> u32 { + let val = *self.regs.get(&offset).unwrap_or(&0); + // eprintln!("{} Read: Offset 0x{:x}, Value 0x{:x}", self.name, offset, val); + val + } + + pub fn write(&mut self, offset: u32, value: u32) { + // eprintln!("{} Write: Offset 0x{:x}, Value 0x{:x}", self.name, offset, value); + self.regs.insert(offset, value); + } +} + +pub struct Usb { + pub regs: HashMap, +} + +impl Usb { + pub fn new() -> Self { + let mut regs = HashMap::new(); + // DWC2 ID register + regs.insert(0x40, 0x4f54280a); // GSNPSID - Synopsys OTG ID + regs.insert(0x44, 0x4f54280a); + // GRSTCTL - AHB Idle bit should be 1 + regs.insert(0x10, 0x80000000); + // GINTSTS - Interrupt status + regs.insert(0x14, 0x04000000); + // GHWCFG2 - Hardware Config 2 + regs.insert(0x48, 0x228ddd10); + Self { regs } + } + + pub fn read(&self, offset: u32) -> u32 { + let val = *self.regs.get(&offset).unwrap_or(&0); + if offset != 0x14 && offset != 0x10 { + eprintln!("USB Read: Offset 0x{:04x}, Value 0x{:08x}", offset, val); + } + val + } + + pub fn write(&mut self, offset: u32, mut value: u32) { + if offset < 0x2000 { + eprintln!("USB Write: Offset 0x{:04x}, Value 0x{:08x}", offset, value); + } + + match offset { + 0x10 => { + // GRSTCTL + if (value & 1) != 0 { + value &= !1; // Clear Soft Reset + value |= 0x80000000; // Set AHB Idle + } + if (value & 2) != 0 { + value &= !2; // Clear HSOTG Soft Reset + } + } + 0x14 => { + // GINTSTS (W1C bits) + let old_val = *self.regs.get(&offset).unwrap_or(&0); + value = old_val & !value; + } + _ => {} + } + + self.regs.insert(offset, value); + } +} + +pub struct Hardware { + pub uart0: Uart, + pub timer: Timer, + pub vic: GenericStub, + pub pmgr: GenericStub, + pub usb: Usb, + pub usb_ehci: GenericStub, + pub usb_ohci0: GenericStub, + pub usb_ohci1: GenericStub, + pub gpio: GenericStub, + pub otgphy: GenericStub, + pub iop: GenericStub, + pub dart1: GenericStub, + pub dart2: GenericStub, +} + +impl Hardware { + pub fn new() -> Self { + Self { + uart0: Uart::new(), + timer: Timer::new(), + vic: GenericStub::new("VIC"), + pmgr: GenericStub::new("PMGR"), + usb: Usb::new(), + usb_ehci: GenericStub::new("EHCI"), + usb_ohci0: GenericStub::new("OHCI0"), + usb_ohci1: GenericStub::new("OHCI1"), + gpio: GenericStub::new("GPIO"), + otgphy: GenericStub::new("OTGPHY"), + iop: GenericStub::new("IOP"), + dart1: GenericStub::new("DART1"), + dart2: GenericStub::new("DART2"), + } + } + + fn get_canonical(&self, addr: u32) -> u32 { + addr & 0x7FFFFFFF + } + + pub fn read(&self, addr: u32) -> Option { + let caddr = self.get_canonical(addr); + + if caddr >= 0x02500000 && caddr <= 0x02500FFF { + return Some(self.uart0.read(caddr - 0x02500000)); + } + if caddr >= 0x3C700000 && caddr <= 0x3C700FFF { + return Some(self.timer.read(caddr - 0x3C700000)); + } + if caddr >= 0x3F200000 && caddr <= 0x3F23FFFF { + return Some(self.vic.read(caddr - 0x3F200000)); + } + if caddr >= 0x3F100000 && caddr <= 0x3F107FFF { + return Some(self.pmgr.read(caddr - 0x3F100000)); + } + // USB + if caddr >= 0x3F108000 && caddr <= 0x3F118000 { + // Device + return Some(self.usb.read(caddr - 0x3F108000)); + } + if caddr >= 0x3F408000 && caddr <= 0x3F418000 { + // EHCI + return Some(self.usb_ehci.read(caddr - 0x3F408000)); + } + if caddr >= 0x3F508000 && caddr <= 0x3F518000 { + // OHCI0 + return Some(self.usb_ohci0.read(caddr - 0x3F508000)); + } + if caddr >= 0x3F608000 && caddr <= 0x3F618000 { + // OHCI1 + return Some(self.usb_ohci1.read(caddr - 0x3F608000)); + } + + if caddr >= 0x06000000 && caddr <= 0x06000FFF { + return Some(self.otgphy.read(caddr - 0x06000000)); + } + if caddr >= 0x06300000 && caddr <= 0x06300FFF { + return Some(self.iop.read(caddr - 0x06300000)); + } + if caddr >= 0x08d00000 && caddr <= 0x08d00FFF { + return Some(self.dart1.read(caddr - 0x08d00000)); + } + if caddr >= 0x09d00000 && caddr <= 0x09d00FFF { + return Some(self.dart2.read(caddr - 0x09d00000)); + } + if caddr >= 0x3FA00000 && caddr <= 0x3FA00FFF { + return Some(self.gpio.read(caddr - 0x3FA00000)); + } + + None + } + + pub fn write(&mut self, addr: u32, value: u32) -> bool { + let caddr = self.get_canonical(addr); + + if caddr >= 0x02500000 && caddr <= 0x02500FFF { + self.uart0.write(caddr - 0x02500000, value); + return true; + } + if caddr >= 0x3C700000 && caddr <= 0x3C700FFF { + self.timer.write(caddr - 0x3C700000, value); + return true; + } + if caddr >= 0x3F200000 && caddr <= 0x3F23FFFF { + self.vic.write(caddr - 0x3F200000, value); + return true; + } + if caddr >= 0x3F100000 && caddr <= 0x3F107FFF { + self.pmgr.write(caddr - 0x3F100000, value); + return true; + } + // USB + if caddr >= 0x3F108000 && caddr <= 0x3F118000 { + self.usb.write(caddr - 0x3F108000, value); + return true; + } + if caddr >= 0x3F408000 && caddr <= 0x3F418000 { + self.usb_ehci.write(caddr - 0x3F408000, value); + return true; + } + if caddr >= 0x3F508000 && caddr <= 0x3F518000 { + self.usb_ohci0.write(caddr - 0x3F508000, value); + return true; + } + if caddr >= 0x3F608000 && caddr <= 0x3F618000 { + self.usb_ohci1.write(caddr - 0x3F608000, value); + return true; + } + + if caddr >= 0x06000000 && caddr <= 0x06000FFF { + if (caddr - 0x06000000) == 0x8 { + eprintln!("OTGPHY Reset write: 0x{:x}", value); + } + self.otgphy.write(caddr - 0x06000000, value); + return true; + } + if caddr >= 0x06300000 && caddr <= 0x06300FFF { + self.iop.write(caddr - 0x06300000, value); + return true; + } + if caddr >= 0x08d00000 && caddr <= 0x08d00FFF { + self.dart1.write(caddr - 0x08d00000, value); + return true; + } + if caddr >= 0x09d00000 && caddr <= 0x09d00FFF { + self.dart2.write(caddr - 0x09d00000, value); + return true; + } + if caddr >= 0x3FA00000 && caddr <= 0x3FA00FFF { + self.gpio.write(caddr - 0x3FA00000, value); + return true; + } + + false + } +} diff --git a/src/tools/emulator/src/img3.rs b/src/tools/emulator/src/img3.rs new file mode 100644 index 0000000..206eaf9 --- /dev/null +++ b/src/tools/emulator/src/img3.rs @@ -0,0 +1,104 @@ +use derive_more::derive::Display; + +#[derive(Debug, Display)] +pub enum Img3Error { + #[display("Invalid IMG3 format: {_0}")] + Format(String), + #[display("Tag not found: {_0}")] + TagNotFound(String), + #[display("Invalid tag data")] + InvalidTag, +} + +impl std::error::Error for Img3Error {} + +pub type Result = std::result::Result; + +#[repr(C, packed)] +pub struct Img3Header { + pub magic: u32, // "3gmI" (little endian) + pub full_size: u32, + pub size_no_pack: u32, + pub sig_check_area: u32, + pub ident: u32, +} + +pub struct Img3Tag { + pub magic: u32, + pub total_length: u32, + pub data_length: u32, + pub data: Vec, +} + +pub struct Img3File { + pub header: Img3Header, + pub tags: Vec, +} + +impl Img3File { + pub fn parse(data: &[u8]) -> Result { + if data.len() < std::mem::size_of::() { + return Err(Img3Error::Format("File too small".to_string())); + } + + let header: Img3Header = unsafe { std::ptr::read(data.as_ptr() as *const _) }; + + if header.magic != 0x496d6733 { + // "3gmI" in little endian + return Err(Img3Error::Format("Invalid IMG3 magic".to_string())); + } + + let mut tags = Vec::new(); + let mut offset = std::mem::size_of::(); + + while offset + 12 <= data.len() { + let magic = u32::from_le_bytes([ + data[offset], + data[offset + 1], + data[offset + 2], + data[offset + 3], + ]); + let total_length = u32::from_le_bytes([ + data[offset + 4], + data[offset + 5], + data[offset + 6], + data[offset + 7], + ]); + let data_length = u32::from_le_bytes([ + data[offset + 8], + data[offset + 9], + data[offset + 10], + data[offset + 11], + ]); + + if offset + total_length as usize > data.len() { + break; + } + + let tag_data = data[offset + 12..offset + 12 + data_length as usize].to_vec(); + + tags.push(Img3Tag { + magic, + total_length, + data_length, + data: tag_data, + }); + + offset += total_length as usize; + } + + Ok(Img3File { header, tags }) + } + + pub fn find_tag(&self, magic: u32) -> Option<&Img3Tag> { + self.tags.iter().find(|tag| tag.magic == magic) + } + + pub fn get_data_section(&self) -> Option<&[u8]> { + self.find_tag(0x44415441).map(|tag| tag.data.as_slice()) // "ATAD" + } + + pub fn get_kbag(&self) -> Option<&[u8]> { + self.find_tag(0x4b424147).map(|tag| tag.data.as_slice()) // "GABK" + } +} diff --git a/src/tools/emulator/src/img4.rs b/src/tools/emulator/src/img4.rs new file mode 100644 index 0000000..11b5252 --- /dev/null +++ b/src/tools/emulator/src/img4.rs @@ -0,0 +1,71 @@ +use derive_more::derive::Display; +use rasn::types::{OctetString, Utf8String}; +use rasn::{AsnType, Decode, Decoder, Encode}; + +#[derive(Debug, Display)] +pub enum Img4Error { + #[display("ASN.1 parsing error: {_0}")] + Asn1(String), + #[display("Invalid IMG4 format: {_0}")] + Format(String), + #[display("Missing component: {_0}")] + Missing(String), +} + +impl std::error::Error for Img4Error {} + +pub type Img4Result = std::result::Result; + +#[derive(Decode, Encode, Debug, AsnType)] +pub struct Im4p { + pub magic: Utf8String, + pub im_type: Utf8String, + pub description: Utf8String, + pub data: OctetString, +} + +#[derive(Decode, Encode, Debug, AsnType)] +pub struct Im4m { + pub magic: Utf8String, + pub version: u32, + pub manifest: OctetString, +} + +#[derive(Decode, Encode, Debug, AsnType)] +pub struct Img4 { + pub magic: Utf8String, + pub payload: Im4p, + pub manifest: Option, +} + +#[repr(C, packed)] +pub struct DfuHeader { + pub magic: [u8; 5], + pub version: u8, + pub size: u32, + pub crc: u32, +} + +pub fn parse_dfu(data: &[u8]) -> Img4Result<&[u8]> { + let header_size = std::mem::size_of::(); + if data.len() < header_size { + return Err(Img4Error::Format("DFU file too small".to_string())); + } + + let header: DfuHeader = unsafe { std::ptr::read(data.as_ptr() as *const _) }; + if &header.magic != b"DfuSe" { + return Err(Img4Error::Format("Invalid DFU magic".to_string())); + } + + Ok(&data[header_size..]) +} + +pub fn parse_img4(data: &[u8]) -> Img4Result { + let img4: Img4 = rasn::ber::decode(data).map_err(|e| Img4Error::Asn1(e.to_string()))?; + + if img4.magic.as_str() != "IMG4" { + return Err(Img4Error::Format("Invalid IMG4 magic".to_string())); + } + + Ok(img4) +} diff --git a/src/tools/emulator/src/jit.rs b/src/tools/emulator/src/jit.rs new file mode 100644 index 0000000..924e89e --- /dev/null +++ b/src/tools/emulator/src/jit.rs @@ -0,0 +1,974 @@ +use crate::decoder::{Instruction, Operand}; +use cranelift::codegen::isa::CallConv; +use cranelift::jit::{JITBuilder, JITModule}; +use cranelift::module::{Linkage, Module}; +use cranelift::prelude::*; +use std::collections::HashMap; + +pub struct Jit { + module: JITModule, + ctx: codegen::Context, + builder_ctx: FunctionBuilderContext, + cache: HashMap, +} + +impl Jit { + pub fn new() -> Self { + let mut flag_builder = settings::builder(); + flag_builder.set("use_colocated_libcalls", "false").unwrap(); + flag_builder.set("is_pic", "false").unwrap(); + let isa_builder = cranelift::native::builder().unwrap_or_else(|msg| { + panic!("host machine is not supported: {}", msg); + }); + let isa = isa_builder + .finish(settings::Flags::new(flag_builder)) + .unwrap(); + let builder = JITBuilder::with_isa(isa, cranelift::module::default_libcall_names()); + + let module = JITModule::new(builder); + Self { + module, + ctx: codegen::Context::new(), + builder_ctx: FunctionBuilderContext::new(), + cache: HashMap::new(), + } + } + + pub fn get_block(&self, addr: u32) -> Option<*const u8> { + self.cache.get(&addr).copied() + } + + fn check_block_supported(&self, instructions: &[(u32, Instruction)]) -> bool { + if instructions.is_empty() { + return false; + } + for (_, insn) in instructions { + if !is_insn_supported(insn) { + return false; + } + } + true + } + + pub fn compile_block( + &mut self, + addr: u32, + instructions: &[(u32, Instruction)], + is_thumb: bool, + ) -> Option<*const u8> { + if let Some(ptr) = self.cache.get(&addr) { + return Some(*ptr); + } + + if !self.check_block_supported(instructions) { + return None; + } + + self.ctx.func.clear(); + self.ctx + .func + .signature + .params + .push(AbiParam::new(types::I64)); // cpu ptr + self.ctx + .func + .signature + .params + .push(AbiParam::new(types::I64)); // regs ptr + self.ctx + .func + .signature + .params + .push(AbiParam::new(types::I64)); // ram ptr + self.ctx + .func + .signature + .params + .push(AbiParam::new(types::I64)); // cpsr ptr + self.ctx + .func + .signature + .params + .push(AbiParam::new(types::I64)); // read_helper + self.ctx + .func + .signature + .params + .push(AbiParam::new(types::I64)); // write_helper + + let mut builder = FunctionBuilder::new(&mut self.ctx.func, &mut self.builder_ctx); + let block = builder.create_block(); + builder.append_block_params_for_function_params(block); + builder.switch_to_block(block); + + let cpu_ptr = builder.block_params(block)[0]; + let regs_ptr = builder.block_params(block)[1]; + let ram_ptr = builder.block_params(block)[2]; + let cpsr_ptr = builder.block_params(block)[3]; + let read_helper = builder.block_params(block)[4]; + let write_helper = builder.block_params(block)[5]; + + let mut terminal = false; + for (insn_pc, insn) in instructions { + let pc_val = builder.ins().iconst(types::I32, *insn_pc as i64); + store_reg(&mut builder, regs_ptr, 15, pc_val); + + if translate_insn( + &mut builder, + insn, + cpu_ptr, + regs_ptr, + ram_ptr, + cpsr_ptr, + read_helper, + write_helper, + *insn_pc, + is_thumb, + ) { + terminal = true; + break; + } + + let next_pc = builder + .ins() + .iconst(types::I32, (*insn_pc + insn.size as u32) as i64); + store_reg(&mut builder, regs_ptr, 15, next_pc); + } + + if !terminal { + builder.ins().return_(&[]); + } + builder.seal_block(block); + builder.finalize(); + + let id = self + .module + .declare_function( + &format!("block_{:x}", addr), + Linkage::Export, + &self.ctx.func.signature, + ) + .unwrap(); + self.module.define_function(id, &mut self.ctx).unwrap(); + self.module.clear_context(&mut self.ctx); + self.module.finalize_definitions().unwrap(); + let code = self.module.get_finalized_function(id); + self.cache.insert(addr, code); + Some(code) + } +} + +fn is_insn_supported(insn: &Instruction) -> bool { + // Basic ARM/Thumb instructions + match insn.mnemonic.as_str() { + "mov" | "movs" | "mov.w" | "mvn" | "mvns" => true, + "add" | "adds" | "add.w" | "addw" => true, + "sub" | "subs" | "sub.w" | "subw" | "rsb" | "rsbs" => true, + "and" | "ands" | "orr" | "orrs" | "eor" | "eors" | "bic" | "bics" => true, + "lsl" | "lsls" | "lsr" | "lsrs" | "asr" | "asrs" | "ror" | "rors" => true, + "cmp" | "cmp.w" | "cmn" | "tst" | "teq" => true, + "ldr" | "str" | "ldrb" | "strb" | "ldrh" | "strh" => { + if insn.operands.len() < 2 { + return false; + } + true + } + "b" | "beq" | "bne" | "bcs" | "bcc" | "bmi" | "bpl" | "bhi" | "bls" | "bge" | "blt" + | "bgt" | "ble" => { + if insn.operands.is_empty() { + return false; + } + matches!(insn.operands[0], Operand::Immediate(_)) + } + "bl" | "blx" | "bx" => true, + "push" | "pop" | "push.w" | "pop.w" => true, + "ldm" | "stm" | "ldmia" | "stmia" | "ldmdb" | "stmdb" => true, + "nop" => true, + "ubfx" | "sbfx" | "bfc" | "bfi" => true, + "clz" => true, + _ => false, + } +} + +fn translate_insn( + builder: &mut FunctionBuilder, + insn: &Instruction, + cpu_ptr: Value, + regs_ptr: Value, + ram_ptr: Value, + cpsr_ptr: Value, + read_helper: Value, + write_helper: Value, + insn_pc: u32, + is_thumb: bool, +) -> bool { + let mut terminal = false; + + // Handle condition code wrapping for non-branch instructions + let is_branch_mnemonic = matches!( + insn.mnemonic.as_str(), + "b" | "beq" + | "bne" + | "bcs" + | "bcc" + | "bmi" + | "bpl" + | "bhi" + | "bls" + | "bge" + | "blt" + | "bgt" + | "ble" + | "bl" + | "blx" + | "bx" + ); + + let cond_block: Option<(Block, Block)> = None; + + match insn.mnemonic.as_str() { + "mov" | "movs" | "mov.w" => { + if insn.operands.len() >= 2 { + let val = load_operand(builder, &insn.operands[1], regs_ptr, insn_pc, is_thumb); + if let Operand::Register(rd) = insn.operands[0] { + store_reg(builder, regs_ptr, rd, val); + if insn.mnemonic.contains('s') { + update_flags_zn(builder, cpsr_ptr, val); + } + if rd == 15 { + builder.ins().return_(&[]); + terminal = true; + } + } + } + } + "mvn" | "mvns" => { + if insn.operands.len() >= 2 { + let val = load_operand(builder, &insn.operands[1], regs_ptr, insn_pc, is_thumb); + let res = builder.ins().bnot(val); + if let Operand::Register(rd) = insn.operands[0] { + store_reg(builder, regs_ptr, rd, res); + if insn.mnemonic.contains('s') { + update_flags_zn(builder, cpsr_ptr, res); + } + } + } + } + "add" | "adds" | "add.w" | "addw" => { + if insn.operands.len() >= 2 { + let lhs = load_operand( + builder, + if insn.operands.len() == 3 { + &insn.operands[1] + } else { + &insn.operands[0] + }, + regs_ptr, + insn_pc, + is_thumb, + ); + let rhs = load_operand( + builder, + if insn.operands.len() == 3 { + &insn.operands[2] + } else { + &insn.operands[1] + }, + regs_ptr, + insn_pc, + is_thumb, + ); + let res = builder.ins().iadd(lhs, rhs); + if let Operand::Register(rd) = insn.operands[0] { + store_reg(builder, regs_ptr, rd, res); + if insn.mnemonic.contains('s') { + update_flags_zn(builder, cpsr_ptr, res); + } + if rd == 15 { + builder.ins().return_(&[]); + terminal = true; + } + } + } + } + "sub" | "subs" | "sub.w" | "subw" => { + if insn.operands.len() >= 2 { + let lhs = load_operand( + builder, + if insn.operands.len() == 3 { + &insn.operands[1] + } else { + &insn.operands[0] + }, + regs_ptr, + insn_pc, + is_thumb, + ); + let rhs = load_operand( + builder, + if insn.operands.len() == 3 { + &insn.operands[2] + } else { + &insn.operands[1] + }, + regs_ptr, + insn_pc, + is_thumb, + ); + let res = builder.ins().isub(lhs, rhs); + if let Operand::Register(rd) = insn.operands[0] { + store_reg(builder, regs_ptr, rd, res); + if insn.mnemonic.contains('s') { + update_flags_zn(builder, cpsr_ptr, res); + } + if rd == 15 { + builder.ins().return_(&[]); + terminal = true; + } + } + } + } + "rsb" | "rsbs" => { + if insn.operands.len() >= 2 { + let lhs = load_operand( + builder, + if insn.operands.len() == 3 { + &insn.operands[1] + } else { + &insn.operands[0] + }, + regs_ptr, + insn_pc, + is_thumb, + ); + let rhs = load_operand( + builder, + if insn.operands.len() == 3 { + &insn.operands[2] + } else { + &insn.operands[1] + }, + regs_ptr, + insn_pc, + is_thumb, + ); + let res = builder.ins().isub(rhs, lhs); + if let Operand::Register(rd) = insn.operands[0] { + store_reg(builder, regs_ptr, rd, res); + if insn.mnemonic.contains('s') { + update_flags_zn(builder, cpsr_ptr, res); + } + } + } + } + "and" | "ands" | "orr" | "orrs" | "eor" | "eors" | "bic" | "bics" => { + if insn.operands.len() >= 2 { + let lhs = load_operand( + builder, + if insn.operands.len() == 3 { + &insn.operands[1] + } else { + &insn.operands[0] + }, + regs_ptr, + insn_pc, + is_thumb, + ); + let rhs = load_operand( + builder, + if insn.operands.len() == 3 { + &insn.operands[2] + } else { + &insn.operands[1] + }, + regs_ptr, + insn_pc, + is_thumb, + ); + let res = match insn.mnemonic.as_str() { + "and" | "ands" => builder.ins().band(lhs, rhs), + "orr" | "orrs" => builder.ins().bor(lhs, rhs), + "eor" | "eors" => builder.ins().bxor(lhs, rhs), + _ => { + let inv_rhs = builder.ins().bnot(rhs); + builder.ins().band(lhs, inv_rhs) + } + }; + if let Operand::Register(rd) = insn.operands[0] { + store_reg(builder, regs_ptr, rd, res); + if insn.mnemonic.contains('s') { + update_flags_zn(builder, cpsr_ptr, res); + } + } + } + } + "lsl" | "lsls" | "lsr" | "lsrs" | "asr" | "asrs" => { + if insn.operands.len() >= 2 { + let lhs = load_operand( + builder, + if insn.operands.len() == 3 { + &insn.operands[1] + } else { + &insn.operands[0] + }, + regs_ptr, + insn_pc, + is_thumb, + ); + let rhs = load_operand( + builder, + if insn.operands.len() == 3 { + &insn.operands[2] + } else { + &insn.operands[1] + }, + regs_ptr, + insn_pc, + is_thumb, + ); + let res = match insn.mnemonic.as_str() { + "lsl" | "lsls" => builder.ins().ishl(lhs, rhs), + "lsr" | "lsrs" => builder.ins().ushr(lhs, rhs), + _ => builder.ins().sshr(lhs, rhs), // asr + }; + if let Operand::Register(rd) = insn.operands[0] { + store_reg(builder, regs_ptr, rd, res); + if insn.mnemonic.contains('s') { + update_flags_zn(builder, cpsr_ptr, res); + } + } + } + } + "cmp" | "cmp.w" => { + if insn.operands.len() >= 2 { + let lhs = load_operand(builder, &insn.operands[0], regs_ptr, insn_pc, is_thumb); + let rhs = load_operand(builder, &insn.operands[1], regs_ptr, insn_pc, is_thumb); + update_flags_cmp(builder, cpsr_ptr, lhs, rhs); + } + } + "cmn" | "tst" | "teq" => { + if insn.operands.len() >= 2 { + let lhs = load_operand(builder, &insn.operands[0], regs_ptr, insn_pc, is_thumb); + let rhs = load_operand(builder, &insn.operands[1], regs_ptr, insn_pc, is_thumb); + let res = match insn.mnemonic.as_str() { + "cmn" => builder.ins().iadd(lhs, rhs), + "tst" => builder.ins().band(lhs, rhs), + _ => builder.ins().bxor(lhs, rhs), // teq + }; + update_flags_zn(builder, cpsr_ptr, res); + } + } + "ldr" | "str" | "ldrb" | "strb" | "ldrh" | "strh" => { + let is_load = insn.mnemonic.starts_with('l'); + let ty = if insn.mnemonic.ends_with('b') { + types::I8 + } else if insn.mnemonic.ends_with('h') { + types::I16 + } else { + types::I32 + }; + let bits = if insn.operands.len() >= 3 { + if let Operand::Immediate(b) = insn.operands[2] { + b as u8 + } else { + 0b10 + } + } else { + 0b10 + }; + let p = (bits >> 1) & 1; + let w = bits & 1; + + if let (Operand::Register(rt), Operand::Memory(rn, offset)) = + (&insn.operands[0], &insn.operands[1]) + { + let base = load_reg_with_pc(builder, regs_ptr, *rn, insn_pc, is_thumb); + let off_val = builder.ins().iconst(types::I32, *offset as i64); + let addr = if p == 1 { + builder.ins().iadd(base, off_val) + } else { + base + }; + + if is_load { + let val = jit_mem_read(builder, cpu_ptr, ram_ptr, read_helper, addr, ty); + store_reg(builder, regs_ptr, *rt, val); + if *rt == 15 { + builder.ins().return_(&[]); + terminal = true; + } + } else { + let val = load_reg(builder, regs_ptr, *rt); + jit_mem_write(builder, cpu_ptr, ram_ptr, write_helper, addr, val, ty); + } + if w == 1 || p == 0 { + let new_base = builder.ins().iadd(base, off_val); + store_reg(builder, regs_ptr, *rn, new_base); + } + } + } + "b" | "beq" | "bne" | "bcs" | "bcc" | "bmi" | "bpl" | "bhi" | "bls" | "bge" | "blt" + | "bgt" | "ble" => { + if let Operand::Immediate(offset) = insn.operands[0] { + let target_pc = (insn_pc as i32 + (if is_thumb { 4 } else { 8 }) + offset) as u32; + let target_val = builder.ins().iconst(types::I32, target_pc as i64); + let next_pc_val = builder + .ins() + .iconst(types::I32, (insn_pc + insn.size as u32) as i64); + + let cond = if insn.mnemonic == "b" { + insn.condition + } else { + match &insn.mnemonic[1..] { + "eq" => 0x0, + "ne" => 0x1, + "cs" => 0x2, + "cc" => 0x3, + "mi" => 0x4, + "pl" => 0x5, + "vs" => 0x6, + "vc" => 0x7, + "hi" => 0x8, + "ls" => 0x9, + "ge" => 0xA, + "lt" => 0xB, + "gt" => 0xC, + "le" => 0xD, + _ => 0xE, + } + }; + + if cond == 0xE { + store_reg(builder, regs_ptr, 15, target_val); + } else { + let cond_met = check_cond_jit(builder, cpsr_ptr, cond); + let final_pc = builder.ins().select(cond_met, target_val, next_pc_val); + store_reg(builder, regs_ptr, 15, final_pc); + } + builder.ins().return_(&[]); + terminal = true; + } + } + "bl" | "blx" => { + let target = match insn.operands[0] { + Operand::Immediate(offset) => { + (insn_pc as i32 + (if is_thumb { 4 } else { 8 }) + offset) as u32 + } + _ => 0, + }; + let lr_val = builder + .ins() + .iconst(types::I32, (insn_pc + insn.size as u32) as i64 | 1); + store_reg(builder, regs_ptr, 14, lr_val); + if let Operand::Register(r) = insn.operands[0] { + let target_reg = load_reg(builder, regs_ptr, r); + store_reg(builder, regs_ptr, 15, target_reg); + } else { + let target_val = builder.ins().iconst(types::I32, target as i64); + store_reg(builder, regs_ptr, 15, target_val); + } + builder.ins().return_(&[]); + terminal = true; + } + "bx" => { + if let Operand::Register(r) = insn.operands[0] { + let target = load_reg(builder, regs_ptr, r); + store_reg(builder, regs_ptr, 15, target); + builder.ins().return_(&[]); + terminal = true; + } + } + "push" | "push.w" => { + if let Operand::Immediate(reg_list) = insn.operands[0] { + let mut sp = load_reg(builder, regs_ptr, 13); + let bits = reg_list as u32; + for i in (0..16).rev() { + if (bits >> i) & 1 == 1 { + sp = builder.ins().iadd_imm(sp, -4); + let val = load_reg(builder, regs_ptr, i as u8); + jit_mem_write(builder, cpu_ptr, ram_ptr, write_helper, sp, val, types::I32); + } + } + store_reg(builder, regs_ptr, 13, sp); + } + } + "pop" | "pop.w" => { + if let Operand::Immediate(reg_list) = insn.operands[0] { + let mut sp = load_reg(builder, regs_ptr, 13); + let bits = reg_list as u32; + let mut pc_updated = false; + for i in 0..16 { + if (bits >> i) & 1 == 1 { + let val = + jit_mem_read(builder, cpu_ptr, ram_ptr, read_helper, sp, types::I32); + store_reg(builder, regs_ptr, i as u8, val); + sp = builder.ins().iadd_imm(sp, 4); + if i == 15 { + pc_updated = true; + } + } + } + store_reg(builder, regs_ptr, 13, sp); + if pc_updated { + builder.ins().return_(&[]); + terminal = true; + } + } + } + "ldm" | "ldmia" | "ldmdb" | "stm" | "stmia" | "stmdb" => { + if let (Operand::Register(rn), Operand::Immediate(reg_list), Operand::Immediate(bits)) = + (&insn.operands[0], &insn.operands[1], &insn.operands[2]) + { + let is_load = insn.mnemonic.starts_with('l'); + let u = (bits >> 1) & 1; + let w = bits & 1; + let mut addr = load_reg(builder, regs_ptr, *rn); + let mask_val = *reg_list as u32; + if u == 0 { + addr = builder + .ins() + .iadd_imm(addr, -((mask_val.count_ones() * 4) as i64)); + } + let start_addr = addr; + let mut curr_addr = start_addr; + let mut pc_updated = false; + for i in 0..16 { + if (mask_val >> i) & 1 == 1 { + if is_load { + let val = jit_mem_read( + builder, + cpu_ptr, + ram_ptr, + read_helper, + curr_addr, + types::I32, + ); + store_reg(builder, regs_ptr, i as u8, val); + if i == 15 { + pc_updated = true; + } + } else { + let val = load_reg(builder, regs_ptr, i as u8); + jit_mem_write( + builder, + cpu_ptr, + ram_ptr, + write_helper, + curr_addr, + val, + types::I32, + ); + } + curr_addr = builder.ins().iadd_imm(curr_addr, 4); + } + } + if w == 1 { + store_reg( + builder, + regs_ptr, + *rn, + if u == 1 { curr_addr } else { start_addr }, + ); + } + if pc_updated { + builder.ins().return_(&[]); + terminal = true; + } + } + } + "ubfx" => { + let rd = match insn.operands[0] { + Operand::Register(r) => r, + _ => 0, + }; + let rn = match insn.operands[1] { + Operand::Register(r) => r, + _ => 0, + }; + let lsb = match insn.operands[2] { + Operand::Immediate(i) => i as u8, + _ => 0, + }; + let width = match insn.operands[3] { + Operand::Immediate(i) => i as u8, + _ => 0, + }; + let val = load_reg(builder, regs_ptr, rn); + let shifted = builder.ins().ushr_imm(val, lsb as i64); + let mask = (1u64 << width) - 1; + let res = builder.ins().band_imm(shifted, mask as i64); + store_reg(builder, regs_ptr, rd, res); + } + "clz" => { + let rd = match insn.operands[0] { + Operand::Register(r) => r, + _ => 0, + }; + let rn = match insn.operands[1] { + Operand::Register(r) => r, + _ => 0, + }; + let val = load_reg(builder, regs_ptr, rn); + let res = builder.ins().clz(val); + store_reg(builder, regs_ptr, rd, res); + } + "nop" => {} + _ => {} + } + + if let Some((merge_block, original_block)) = cond_block { + if !terminal { + builder.ins().jump(merge_block, &[]); + } + builder.switch_to_block(merge_block); + // Seal the merge block here since we're done with conditional execution + builder.seal_block(merge_block); + // Don't switch back to the original block since it's already sealed by the brif + } + + terminal +} + +fn jit_mem_read( + builder: &mut FunctionBuilder, + cpu_ptr: Value, + ram_ptr: Value, + helper: Value, + addr: Value, + ty: Type, +) -> Value { + let mut sig = Signature::new(CallConv::SystemV); + sig.params.push(AbiParam::new(types::I64)); + sig.params.push(AbiParam::new(types::I32)); + sig.returns.push(AbiParam::new(types::I32)); + let sig_ref = builder.import_signature(sig); + + let is_ram = builder + .ins() + .icmp_imm(IntCC::UnsignedGreaterThanOrEqual, addr, 0x40000000); + let ram_block = builder.create_block(); + let helper_block = builder.create_block(); + let merge_block = builder.create_block(); + + let res_var = builder.declare_var(types::I32); + + builder + .ins() + .brif(is_ram, ram_block, &[], helper_block, &[]); + + builder.switch_to_block(ram_block); + let mask = builder.ins().iconst(types::I32, 0x0FFFFFFF); + let off = builder.ins().band(addr, mask); + let off64 = builder.ins().uextend(types::I64, off); + let paddr = builder.ins().iadd(ram_ptr, off64); + let val = builder.ins().load(ty, MemFlags::new(), paddr, 0); + let val32 = if ty == types::I32 { + val + } else { + builder.ins().uextend(types::I32, val) + }; + builder.def_var(res_var, val32); + builder.ins().jump(merge_block, &[]); + + builder.switch_to_block(helper_block); + let call = builder + .ins() + .call_indirect(sig_ref, helper, &[cpu_ptr, addr]); + let hval = builder.inst_results(call)[0]; + builder.def_var(res_var, hval); + builder.ins().jump(merge_block, &[]); + + builder.switch_to_block(merge_block); + builder.seal_block(ram_block); + builder.seal_block(helper_block); + builder.seal_block(merge_block); + builder.use_var(res_var) +} + +fn jit_mem_write( + builder: &mut FunctionBuilder, + cpu_ptr: Value, + ram_ptr: Value, + helper: Value, + addr: Value, + val: Value, + ty: Type, +) { + let mut sig = Signature::new(CallConv::SystemV); + sig.params.push(AbiParam::new(types::I64)); + sig.params.push(AbiParam::new(types::I32)); + sig.params.push(AbiParam::new(types::I32)); + let sig_ref = builder.import_signature(sig); + + let is_ram = builder + .ins() + .icmp_imm(IntCC::UnsignedGreaterThanOrEqual, addr, 0x40000000); + let ram_block = builder.create_block(); + let helper_block = builder.create_block(); + let merge_block = builder.create_block(); + builder + .ins() + .brif(is_ram, ram_block, &[], helper_block, &[]); + + builder.switch_to_block(ram_block); + let mask = builder.ins().iconst(types::I32, 0x0FFFFFFF); + let off = builder.ins().band(addr, mask); + let off64 = builder.ins().uextend(types::I64, off); + let paddr = builder.ins().iadd(ram_ptr, off64); + let val_trunc = if ty == types::I32 { + val + } else { + builder.ins().ireduce(ty, val) + }; + builder.ins().store(MemFlags::new(), val_trunc, paddr, 0); + builder.ins().jump(merge_block, &[]); + + builder.switch_to_block(helper_block); + builder + .ins() + .call_indirect(sig_ref, helper, &[cpu_ptr, addr, val]); + builder.ins().jump(merge_block, &[]); + + builder.switch_to_block(merge_block); + builder.seal_block(ram_block); + builder.seal_block(helper_block); + builder.seal_block(merge_block); +} + +fn load_reg_with_pc( + builder: &mut FunctionBuilder, + regs_ptr: Value, + reg: u8, + insn_pc: u32, + is_thumb: bool, +) -> Value { + if reg == 15 { + builder.ins().iconst( + types::I32, + (insn_pc + (if is_thumb { 4 } else { 8 })) as i64, + ) + } else { + load_reg(builder, regs_ptr, reg) + } +} + +fn load_operand( + builder: &mut FunctionBuilder, + op: &Operand, + regs_ptr: Value, + insn_pc: u32, + is_thumb: bool, +) -> Value { + match op { + Operand::Register(r) => load_reg_with_pc(builder, regs_ptr, *r, insn_pc, is_thumb), + Operand::Immediate(imm) => builder.ins().iconst(types::I32, *imm as i64), + _ => builder.ins().iconst(types::I32, 0), + } +} + +fn update_flags_zn(builder: &mut FunctionBuilder, cpsr_ptr: Value, res: Value) { + let old_cpsr = builder.ins().load(types::I32, MemFlags::new(), cpsr_ptr, 0); + let mask = builder.ins().iconst(types::I32, 0x3FFFFFFF); + let mut new_cpsr = builder.ins().band(old_cpsr, mask); + let z_val = builder.ins().iconst(types::I32, 0x40000000); + let n_val = builder.ins().iconst(types::I32, 0x80000000u32 as i64); + let zero = builder.ins().iconst(types::I32, 0); + let is_zero = builder.ins().icmp_imm(IntCC::Equal, res, 0); + let z_flag = builder.ins().select(is_zero, z_val, zero); + new_cpsr = builder.ins().bor(new_cpsr, z_flag); + let is_neg = builder.ins().icmp_imm(IntCC::SignedLessThan, res, 0); + let n_flag = builder.ins().select(is_neg, n_val, zero); + new_cpsr = builder.ins().bor(new_cpsr, n_flag); + builder.ins().store(MemFlags::new(), new_cpsr, cpsr_ptr, 0); +} + +fn update_flags_cmp(builder: &mut FunctionBuilder, cpsr_ptr: Value, lhs: Value, rhs: Value) { + let old_cpsr = builder.ins().load(types::I32, MemFlags::new(), cpsr_ptr, 0); + let mask = builder.ins().iconst(types::I32, 0x0FFFFFFF); + let mut new_cpsr = builder.ins().band(old_cpsr, mask); + let zero = builder.ins().iconst(types::I32, 0); + let z_val = builder.ins().iconst(types::I32, 0x40000000); + let n_val = builder.ins().iconst(types::I32, 0x80000000u32 as i64); + let c_val = builder.ins().iconst(types::I32, 0x20000000); + let v_val = builder.ins().iconst(types::I32, 0x10000000); + let is_eq = builder.ins().icmp(IntCC::Equal, lhs, rhs); + let z_flag = builder.ins().select(is_eq, z_val, zero); + new_cpsr = builder.ins().bor(new_cpsr, z_flag); + let res = builder.ins().isub(lhs, rhs); + let is_neg = builder.ins().icmp_imm(IntCC::SignedLessThan, res, 0); + let n_flag = builder.ins().select(is_neg, n_val, zero); + new_cpsr = builder.ins().bor(new_cpsr, n_flag); + let is_geu = builder + .ins() + .icmp(IntCC::UnsignedGreaterThanOrEqual, lhs, rhs); + let c_flag = builder.ins().select(is_geu, c_val, zero); + new_cpsr = builder.ins().bor(new_cpsr, c_flag); + let xor1 = builder.ins().bxor(lhs, rhs); + let xor2 = builder.ins().bxor(lhs, res); + let v_and = builder.ins().band(xor1, xor2); + let is_v = builder.ins().icmp_imm(IntCC::SignedLessThan, v_and, 0); + let v_flag = builder.ins().select(is_v, v_val, zero); + new_cpsr = builder.ins().bor(new_cpsr, v_flag); + builder.ins().store(MemFlags::new(), new_cpsr, cpsr_ptr, 0); +} + +fn check_cond_jit(builder: &mut FunctionBuilder, cpsr_ptr: Value, cond: u8) -> Value { + let cpsr = builder.ins().load(types::I32, MemFlags::new(), cpsr_ptr, 0); + let n_un = builder.ins().ushr_imm(cpsr, 31); + let cpsr_shr30 = builder.ins().ushr_imm(cpsr, 30); + let z_un = builder.ins().band_imm(cpsr_shr30, 1); + let cpsr_shr29 = builder.ins().ushr_imm(cpsr, 29); + let c_un = builder.ins().band_imm(cpsr_shr29, 1); + let cpsr_shr28 = builder.ins().ushr_imm(cpsr, 28); + let v_un = builder.ins().band_imm(cpsr_shr28, 1); + + match cond { + 0x0 => builder.ins().icmp_imm(IntCC::Equal, z_un, 1), // EQ + 0x1 => builder.ins().icmp_imm(IntCC::Equal, z_un, 0), // NE + 0x2 => builder.ins().icmp_imm(IntCC::Equal, c_un, 1), // CS + 0x3 => builder.ins().icmp_imm(IntCC::Equal, c_un, 0), // CC + 0x4 => builder.ins().icmp_imm(IntCC::Equal, n_un, 1), // MI + 0x5 => builder.ins().icmp_imm(IntCC::Equal, n_un, 0), // PL + 0x6 => builder.ins().icmp_imm(IntCC::Equal, v_un, 1), // VS + 0x7 => builder.ins().icmp_imm(IntCC::Equal, v_un, 0), // VC + 0x8 => { + // HI + let cset = builder.ins().icmp_imm(IntCC::Equal, c_un, 1); + let zclr = builder.ins().icmp_imm(IntCC::Equal, z_un, 0); + builder.ins().band(cset, zclr) + } + 0x9 => { + // LS + let cclr = builder.ins().icmp_imm(IntCC::Equal, c_un, 0); + let zset = builder.ins().icmp_imm(IntCC::Equal, z_un, 1); + builder.ins().bor(cclr, zset) + } + 0xA => builder.ins().icmp(IntCC::Equal, n_un, v_un), // GE + 0xB => builder.ins().icmp(IntCC::NotEqual, n_un, v_un), // LT + 0xC => { + // GT + let neq = builder.ins().icmp(IntCC::Equal, n_un, v_un); + let zclr = builder.ins().icmp_imm(IntCC::Equal, z_un, 0); + builder.ins().band(neq, zclr) + } + 0xD => { + // LE + let nne = builder.ins().icmp(IntCC::NotEqual, n_un, v_un); + let zset = builder.ins().icmp_imm(IntCC::Equal, z_un, 1); + builder.ins().bor(nne, zset) + } + _ => builder.ins().iconst(types::I8, 1), + } +} + +fn load_reg(builder: &mut FunctionBuilder, regs_ptr: Value, reg: u8) -> Value { + builder + .ins() + .load(types::I32, MemFlags::new(), regs_ptr, (reg as i32) * 4) +} + +fn store_reg(builder: &mut FunctionBuilder, regs_ptr: Value, reg: u8, val: Value) { + builder + .ins() + .store(MemFlags::new(), val, regs_ptr, (reg as i32) * 4); +} diff --git a/src/tools/emulator/src/main.rs b/src/tools/emulator/src/main.rs new file mode 100644 index 0000000..f3503bb --- /dev/null +++ b/src/tools/emulator/src/main.rs @@ -0,0 +1,455 @@ +use aes::{Aes128, Aes192, Aes256}; +use cbc::Decryptor; +use cbc::cipher::{BlockDecryptMut, KeyIvInit}; +use derive_more::derive::Display; +use rootcause::{Report, report}; +use std::fs::File; +use std::io::Read; +use std::io::Write; + +mod cpu; +mod decoder; +mod hardware; +mod img3; +mod img4; +mod jit; + +use cpu::ArmCpu; +use hardware::Hardware; + +extern "C" fn jit_read_helper(cpu_ptr: *mut ArmCpu, addr: u32) -> u32 { + let cpu = unsafe { &mut *cpu_ptr }; + cpu.read_memory(addr).unwrap_or(0) +} + +extern "C" fn jit_write_helper(cpu_ptr: *mut ArmCpu, addr: u32, val: u32) { + let cpu = unsafe { &mut *cpu_ptr }; + let _ = cpu.write_memory(addr, val); +} + +#[derive(Debug, Display)] +pub enum EmulatorError { + #[display("I/O error: {_0}")] + Io(std::io::Error), + #[display("File not found: {_0}")] + FileNotFound(String), + #[display("IMG3 error: {_0}")] + Img3(img3::Img3Error), + #[display("Decryption error: {_0}")] + Decryption(String), + #[display("Decoder error: {_0}")] + Decoder(String), + #[display("CPU error: {_0}")] + Cpu(cpu::CpuError), +} + +impl std::error::Error for EmulatorError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + EmulatorError::Io(e) => Some(e), + EmulatorError::Img3(e) => Some(e), + EmulatorError::Cpu(e) => Some(e), + _ => None, + } + } +} + +impl From for EmulatorError { + fn from(err: std::io::Error) -> Self { + EmulatorError::Io(err) + } +} + +impl From for EmulatorError { + fn from(err: img3::Img3Error) -> Self { + EmulatorError::Img3(err) + } +} + +impl From for EmulatorError { + fn from(err: cpu::CpuError) -> Self { + EmulatorError::Cpu(err) + } +} + +pub type Result = std::result::Result>; + +fn decrypt_payload(data: &[u8], key: &[u8], iv: &[u8]) -> Result> { + let mut buf = data.to_vec(); + + match key.len() { + 16 => { + let mut cipher = Decryptor::::new_from_slices(key, iv) + .map_err(|e| report!(EmulatorError::Decryption(e.to_string())))?; + for chunk in buf.chunks_mut(16) { + if chunk.len() == 16 { + cipher.decrypt_block_mut(chunk.into()); + } + } + } + 24 => { + let mut cipher = Decryptor::::new_from_slices(key, iv) + .map_err(|e| report!(EmulatorError::Decryption(e.to_string())))?; + for chunk in buf.chunks_mut(16) { + if chunk.len() == 16 { + cipher.decrypt_block_mut(chunk.into()); + } + } + } + 32 => { + let mut cipher = Decryptor::::new_from_slices(key, iv) + .map_err(|e| report!(EmulatorError::Decryption(e.to_string())))?; + for chunk in buf.chunks_mut(16) { + if chunk.len() == 16 { + cipher.decrypt_block_mut(chunk.into()); + } + } + } + _ => { + return Err(report!(EmulatorError::Decryption(format!( + "Unsupported key size: {}", + key.len() + )))); + } + } + + Ok(buf) +} + +fn main() -> Result<()> { + println!("iOS 5 iBEC Emulator"); + println!("=================="); + + let ibec_path = "work/Firmware/dfu/iBEC.k48ap.RELEASE.dfu"; + let mut ibec_file = File::open(ibec_path).map_err(|e| { + report!(EmulatorError::Io(e)).context(EmulatorError::FileNotFound(ibec_path.to_string())) + })?; + let mut ibec_data = Vec::new(); + ibec_file.read_to_end(&mut ibec_data).map_err(|e| { + report!(EmulatorError::Io(e)).context(EmulatorError::FileNotFound(format!( + "Failed to read {}", + ibec_path + ))) + })?; + + println!("Loaded {} bytes from {}", ibec_data.len(), ibec_path); + + // Parse as IMG3 file + let img3 = img3::Img3File::parse(&ibec_data).map_err(|e| { + report!(e).context(EmulatorError::Img3(img3::Img3Error::Format(format!( + "Failed to parse IMG3 file: {}", + ibec_path + )))) + })?; + + println!("IMG3 Header:"); + let magic = img3.header.magic; + let full_size = img3.header.full_size; + let ident = img3.header.ident; + println!(" Magic: 0x{:08x}", magic); + println!(" Full size: {}", full_size); + println!(" Ident: 0x{:08x}", ident); + println!(" Tags: {}", img3.tags.len()); + + // List all tags + for (i, tag) in img3.tags.iter().enumerate() { + let bytes = tag.magic.to_le_bytes(); + let tag_name = std::str::from_utf8(&bytes).unwrap_or("????"); + print!( + " Tag {}: {} (0x{:08x}) - {} bytes", + i, + tag_name, + tag.magic, + tag.data.len() + ); + if tag_name == "GABK" { + print!(": {}", hex::encode(&tag.data)); + } + if tag_name == "SREV" { + let s = std::str::from_utf8(&tag.data).unwrap_or("????"); + print!(": {}", s); + } + println!(); + } + + // Get encrypted data section + let encrypted_data = img3.get_data_section().ok_or_else(|| { + report!(EmulatorError::Decoder( + "DATA section not found in IMG3 file".to_string() + )) + })?; + + println!("Encrypted data size: {} bytes", encrypted_data.len()); + + // Use provided IV and key + let iv = hex::decode("bde7b0d5cf7861479d81eb23f99d2e9e").unwrap(); + let key = + hex::decode("1ba1f38e6a5b4841c1716c11acae9ee0fb471e50362a3b0dd8d98019f174a2f2").unwrap(); + + // Decrypt payload + let decrypted_payload = decrypt_payload(encrypted_data, &key, &iv).map_err(|e| { + e.context(EmulatorError::Decryption( + "Failed to decrypt iBEC payload".to_string(), + )) + })?; + println!("Successfully decrypted payload"); + std::fs::write("work/Firmware/dfu/iBEC_decrypted.bin", &decrypted_payload).unwrap(); + println!("Payload size: {} bytes", decrypted_payload.len()); + + // Hexdump start of payload + for i in (0..(0x400.min(decrypted_payload.len()))).step_by(16) { + print!("{:08x}: ", 0x80000000 + i as u32); + for j in 0..16 { + if i + j < decrypted_payload.len() { + print!("{:02x} ", decrypted_payload[i + j]); + } else { + print!(" "); + } + } + print!(" |"); + for j in 0..16 { + if i + j < decrypted_payload.len() { + let c = decrypted_payload[i + j]; + if c >= 0x20 && c <= 0x7E { + print!("{}", c as char); + } else { + print!("."); + } + } + } + println!("|"); + } + + // Check memory around PC+8+24 = 0x80000000+8+24 = 0x80000020 + let check_addr = 0x80000020u32 as i32; + println!( + "Memory at 0x{:08x}: {:02x?}", + check_addr, + &decrypted_payload[0x20..0x24] + ); + + // Check memory at 0x108 where LDR will actually read from + println!( + "Memory at 0x80000108: {:02x?}", + &decrypted_payload[0x108..0x10c] + ); + + // Check instruction at 0x8000000c (the branch) + let branch_offset = 0x0c; + println!( + "Branch instruction at 0x8000000c: {:02x?}", + &decrypted_payload[branch_offset..branch_offset + 4] + ); + + // Check the LDR instruction at 0x80000024 + let ldr_offset = 0x24; + println!( + "LDR instruction at 0x80000024: {:02x?}", + &decrypted_payload[ldr_offset..ldr_offset + 4] + ); + + // Check memory at relocation entry point (0x9c8) + println!("Relocated code area hexdump (starting at 0x9c8):"); + for i in (0x9c8..(0x9c8 + 0x40.min(decrypted_payload.len() - 0x9c8))).step_by(16) { + print!("{:08x}: ", 0x80000000 + i as u32); + for j in 0..16 { + print!("{:02x} ", decrypted_payload[i + j]); + } + println!(); + } + + println!("Literal pool dump at 0x80000300:"); + for i in (0x300..0x340).step_by(16) { + print!("{:08x}: ", 0x80000000 + i as u32); + for j in 0..16 { + print!("{:02x} ", decrypted_payload[i + j]); + } + println!(); + } + + // Run CPU emulator + let mut cpu = ArmCpu::new(); + let hardware = Hardware::new(); + cpu.load_memory(0x80000000, &decrypted_payload); + cpu.set_hardware(hardware); + cpu.pc = 0x80000000; + + // Initialize some registers with reasonable values + cpu.registers[13] = 0x80010000; // Stack pointer + cpu.registers[14] = 0x80000004; // Link register + + // Simulate NVRAM initialization for iBEC + cpu.registers[0] = 0x84000000; // NVRAM base + cpu.registers[1] = 0x85000000; // Boot args + cpu.registers[2] = 0x86000000; // Kernel args + + println!("Running CPU emulation..."); + let mut step = 0; + let mut decoded_cache: std::collections::HashMap = + std::collections::HashMap::new(); + + let mut jit = jit::Jit::new(); + let mut block_sizes: std::collections::HashMap = std::collections::HashMap::new(); + let mut last_watch_val = 0; + let mut compilations = 0; + + while step < 10_000_000_000 { + let pc = cpu.pc; + + // 1. Try JIT + if let Some(code_ptr) = jit.get_block(pc) { + let func: extern "C" fn( + *mut ArmCpu, + *mut u32, + *mut u8, + *mut u32, + extern "C" fn(*mut ArmCpu, u32) -> u32, + extern "C" fn(*mut ArmCpu, u32, u32), + ) = unsafe { std::mem::transmute(code_ptr) }; + func( + &mut cpu, + cpu.registers.as_mut_ptr(), + cpu.ram.as_mut_ptr(), + &mut cpu.cpsr, + jit_read_helper, + jit_write_helper, + ); + cpu.pc = cpu.registers[15]; + let size = *block_sizes.get(&pc).unwrap_or(&1); + step += size; + if step % 1000000 < size { + eprintln!( + "[Step {}] PC=0x{:08x}, JIT cache: {}", + step, cpu.pc, compilations + ); + eprintln!( + "Registers: R0={:08x} R1={:08x} R2={:08x} R3={:08x} SP={:08x} LR={:08x} CPSR={:08x}", + cpu.registers[0], + cpu.registers[1], + cpu.registers[2], + cpu.registers[3], + cpu.registers[13], + cpu.registers[14], + cpu.cpsr + ); + } + continue; + } + + // 2. Block discovery and compilation if not in cache + let mut block_insns = Vec::new(); + let mut curr_pc = pc; + for _ in 0..100 { + // Ensure instruction is decoded + if !decoded_cache.contains_key(&curr_pc) { + let mut insn_bytes = [0u8; 4]; + for i in 0..4 { + insn_bytes[i] = cpu.memory.get(&(curr_pc + i as u32)).copied().unwrap_or(0); + } + let is_thumb = (cpu.cpsr >> 5) & 1 != 0; + if let Ok(insns) = decoder::decode(&insn_bytes, is_thumb) { + if !insns.is_empty() { + decoded_cache.insert(curr_pc, insns[0].clone()); + } else { + break; + } + } else { + break; + } + } + let insn = decoded_cache.get(&curr_pc).unwrap().clone(); + block_insns.push((curr_pc, insn.clone())); + + // Basic block terminator check + let m = insn.mnemonic.as_str(); + if m == "b" + || m == "bl" + || m == "bx" + || m == "blx" + || m == "cbz" + || m == "cbnz" + || m == "it" + || m == "svc" + || m == "eret" + || m == "pop" + || m == "pop.w" + || insn + .operands + .iter() + .any(|o| matches!(o, decoder::Operand::Register(15))) + { + break; + } + curr_pc += insn.size as u32; + } + + let is_thumb = (cpu.cpsr >> 5) & 1 != 0; + if let Some(code_ptr) = jit.compile_block(pc, &block_insns, is_thumb) { + let func: extern "C" fn( + *mut ArmCpu, + *mut u32, + *mut u8, + *mut u32, + extern "C" fn(*mut ArmCpu, u32) -> u32, + extern "C" fn(*mut ArmCpu, u32, u32), + ) = unsafe { std::mem::transmute(code_ptr) }; + func( + &mut cpu, + cpu.registers.as_mut_ptr(), + cpu.ram.as_mut_ptr(), + &mut cpu.cpsr, + jit_read_helper, + jit_write_helper, + ); + cpu.pc = cpu.registers[15]; + block_sizes.insert(pc, block_insns.len() as u64); + step += block_insns.len() as u64; + compilations += 1; + continue; + } + + // 3. Fallback to interpreter + if let Some(insn) = decoded_cache.get(&pc) { + let insn = insn.clone(); + let old_pc = cpu.pc; + cpu.pc_modified = false; + if let Err(e) = cpu.execute(&insn) { + println!(" Error at PC 0x{:08x}: {}", old_pc, e); + break; + } + if !cpu.pc_modified { + cpu.pc += insn.size as u32; + } + step += 1; + } else { + println!(" Failed to decode at 0x{:08x}", pc); + break; + } + + // Minimal UART hook via memory watch + if let Ok(v0) = cpu.read_memory(0x5FFF7F24) { + if v0 != last_watch_val && v0 != 0 { + if v0 >= 0x20 && v0 <= 0x7E { + print!("{}", v0 as u8 as char); + } else if v0 == 0x0a || v0 == 0x0d { + if v0 == 0x0a { + println!(); + } + } + let _ = std::io::stdout().flush(); + last_watch_val = v0; + } + } + + if step % 10_000 == 0 { + eprintln!( + "[Step {}] PC=0x{:08x}, JIT cache: {}", + step, cpu.pc, compilations + ); + } + } + + println!("Final CPU state:"); + cpu.dump_registers(); + Ok(()) +} diff --git a/src/tools/gravity-setup/src/main.rs b/src/tools/gravity-setup/src/main.rs deleted file mode 100644 index 7960080..0000000 --- a/src/tools/gravity-setup/src/main.rs +++ /dev/null @@ -1,258 +0,0 @@ -use apple_dmg::{ChunkType, DmgReader}; -use clap::Parser; -use flate2::bufread::ZlibDecoder; -use hfsplus::HFSVolume; -use indicatif::{MultiProgress, ProgressBar, ProgressStyle}; -use ipsw_downloader::fetch_firmware_url; -use memmap2::MmapMut; -use rayon::prelude::*; -use std::cmp::min; -use std::fs::File; -use std::io::{self, Read, Seek, SeekFrom, Write}; -use std::os::unix::fs::FileExt; -use std::path::{Path, PathBuf}; -use std::time::Instant; -use thiserror::Error; -use tokio::io::AsyncWriteExt; -use vfdecrypt::decrypt; - -#[derive(Error, Debug)] -pub enum SetupError { - #[error("IO error: {0}")] - Io(#[from] io::Error), - #[error("DMG error: {0}")] - Dmg(String), - #[error("HFS+ error: {0}")] - Hfs(String), - #[error("Download error: {0}")] - Download(String), - #[error("VFDecrypt error: {0}")] - Decrypt(String), - #[error("Zip error: {0}")] - Zip(#[from] zip::result::ZipError), - #[error("Error: {0}")] - Other(String), -} - -#[derive(Parser, Debug)] -#[command(name = "gravity-setup", ignore_errors = true)] -struct Args { - /// Device Identifier (e.g., iPad1,1) - #[arg(long, default_value = "iPad1,1")] - device: String, - - /// Build ID (e.g., 9B206) - #[arg(long, default_value = "9B206")] - build: String, - - /// Rootfs decryption key - #[arg( - long, - default_value = "f7bb9fd8aa3102484ab9c847dacfd3d73f1f430acb49ed7a422226f2410acee17664c91b" - )] - key: String, - - /// Output disk image path - #[arg(long, default_value = "disk.img")] - output: PathBuf, - - /// Work directory for intermediate files - #[arg(long, default_value = "work")] - work_dir: PathBuf, - - /// Disk size in MB - #[arg(long, default_value_t = 1536)] - size_mb: u64, - - /// CI mode - #[arg(long, default_value = "false")] - ci: bool, -} - -impl Read for OffsetFile { - fn read(&mut self, buf: &mut [u8]) -> io::Result { - self.file.read(buf) - } -} - -impl Seek for OffsetFile { - fn seek(&mut self, pos: SeekFrom) -> io::Result { - let actual_pos = match pos { - SeekFrom::Start(s) => SeekFrom::Start(self.offset + s), - SeekFrom::Current(c) => SeekFrom::Current(c), - SeekFrom::End(e) => SeekFrom::End(e), - }; - let new_pos = self.file.seek(actual_pos)?; - Ok(new_pos.saturating_sub(self.offset)) - } -} - -impl hfsplus::Read for OffsetFile { - fn read(&mut self, buf: &mut [u8]) -> hfsplus::Result { - Read::read(self, buf).map_err(|e| hfsplus::Error::InvalidData(e.to_string())) - } -} - -impl hfsplus::Seek for OffsetFile { - fn seek(&mut self, pos: hfsplus::SeekFrom) -> hfsplus::Result { - let std_pos = match pos { - hfsplus::SeekFrom::Start(s) => SeekFrom::Start(s), - hfsplus::SeekFrom::Current(c) => SeekFrom::Current(c), - hfsplus::SeekFrom::End(e) => SeekFrom::End(e), - }; - Seek::seek(self, std_pos).map_err(|e| hfsplus::Error::InvalidData(e.to_string())) - } -} - -#[tokio::main] -async fn main() -> Result<(), SetupError> { - let args = Args::parse(); - let mp = MultiProgress::new(); - let total_start = Instant::now(); - - std::fs::create_dir_all(&args.work_dir)?; - - // cargo build kernel - std::process::Command::new("cargo") - .arg("build") - .arg("-Zbuild-std=core,alloc,compiler_builtins") - .arg("-Zbuild-std-features=compiler-builtins-mem") - .arg("--target") - .arg("aarch64-unknown-none-softfloat") - .arg("-p") - .arg("kernel") - .arg("-p") - .arg("dyld") - .env( - "RUSTFLAGS", - "-Zsanitizer=kcfi -Clink-arg=--ld-path=wild -Clinker=clang", - ) - .status() - .map_err(|e| SetupError::Other(e.to_string()))?; - - if !args.ci { - println!( - "Fetching firmware URL for {} build {}...", - args.device, args.build - ); - let ipsw_url = fetch_firmware_url(&args.device, &args.build) - .await - .map_err(|e| SetupError::Download(e.to_string()))? - .ok_or_else(|| SetupError::Other("Firmware not found".to_string()))?; - println!(" Done in {:?}", start.elapsed()); - - // 2. Download IPSW - let ipsw_path = args.work_dir.join("firmware.ipsw"); - if !ipsw_path.exists() { - println!("Downloading IPSW..."); - download_ipsw(&ipsw_url, &ipsw_path, &mp).await?; - } else { - println!("IPSW already downloaded, skipping."); - } - - // 3. Extract Rootfs DMG (optimized) - let rootfs_dmg_encrypted = args.work_dir.join("rootfs_encrypted.dmg"); - if !rootfs_dmg_encrypted.exists() { - println!("Extracting rootfs DMG from IPSW..."); - let file = File::open(&ipsw_path).map_err(|e| { - SetupError::Other(format!( - "Failed to open IPSW at {}: {}", - ipsw_path.display(), - e - )) - })?; - let mut archive = zip::ZipArchive::new(file) - .map_err(|e| SetupError::Other(format!("Failed to open IPSW as ZIP: {}", e)))?; - - let mut largest_name = String::new(); - let mut largest_size = 0; - let mut largest_index = 0; - - for i in 0..archive.len() { - let file = archive.by_index(i)?; - if file.name().ends_with(".dmg") && file.size() > largest_size { - largest_size = file.size(); - largest_name = file.name().to_string(); - largest_index = i; - } - } - - if largest_size == 0 { - return Err(SetupError::Other("No DMG found in IPSW".to_string())); - } - - println!( - "Extracting {} ({} MB)...", - largest_name, - largest_size / 1024 / 1024 - ); - let mut rootfs_zip = archive.by_index(largest_index)?; - let mut output = File::create(&rootfs_dmg_encrypted)?; - - let pb = mp.add(ProgressBar::new(largest_size)); - pb.set_style(ProgressStyle::default_bar() - .template("{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {bytes}/{total_bytes} ({bytes_per_sec}, {eta})") - .unwrap() - .progress_chars("#>-")); - - let mut buffer = vec![0u8; 1024 * 1024]; - loop { - let n = rootfs_zip.read(&mut buffer)?; - if n == 0 { - break; - } - output.write_all(&buffer[..n])?; - pb.inc(n as u64); - } - pb.finish_with_message("Extraction complete"); - } else { - println!("Rootfs DMG already extracted, skipping."); - } - - // 4. Decrypt Rootfs DMG - let rootfs_dmg = args.work_dir.join("rootfs.dmg"); - if !rootfs_dmg.exists() { - println!("Decrypting rootfs DMG..."); - let mut input = File::open(&rootfs_dmg_encrypted)?; - let mut output = File::create(&rootfs_dmg)?; - decrypt(&mut input, &mut output, &args.key) - .map_err(|e| SetupError::Decrypt(e.to_string()))?; - } else { - println!("Rootfs DMG already decrypted, skipping."); - } - } - - println!("✨ Done in {:?}", total_start.elapsed()); - Ok(()) -} - -async fn download_ipsw(url: &str, output: &Path, mp: &MultiProgress) -> Result<(), SetupError> { - let client = reqwest::Client::new(); - let response = client - .get(url) - .send() - .await - .map_err(|e| SetupError::Download(e.to_string()))?; - let total_size = response.content_length().unwrap_or(0); - - let pb = mp.add(ProgressBar::new(total_size)); - pb.set_style(ProgressStyle::default_bar() - .template("{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {bytes}/{total_bytes} ({bytes_per_sec}, {eta})") - .unwrap() - .progress_chars("#>-")); - - let mut file = tokio::fs::File::create(output).await?; - let mut downloaded: u64 = 0; - let mut stream = response.bytes_stream(); - - use futures_util::StreamExt; - while let Some(item) = stream.next().await { - let chunk = item.map_err(|e| SetupError::Download(e.to_string()))?; - file.write_all(&chunk).await?; - downloaded = min(downloaded + (chunk.len() as u64), total_size); - pb.set_position(downloaded); - } - - pb.finish_with_message("Download complete"); - Ok(()) -} diff --git a/src/tools/ipsw/Cargo.toml b/src/tools/ipsw/Cargo.toml index 803ce5d..a2bd36a 100644 --- a/src/tools/ipsw/Cargo.toml +++ b/src/tools/ipsw/Cargo.toml @@ -11,7 +11,8 @@ path = "main.rs" reqwest = { workspace = true } plist = { workspace = true } serde = { workspace = true } -thiserror = { workspace = true } +derive_more = { workspace = true } +rootcause = { workspace = true } tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } vfdecrypt = { path = "../../lib/vfdecrypt" } ipsw-downloader = { path = "../../lib/ipsw-downloader" } diff --git a/src/tools/gravity-setup/Cargo.toml b/src/tools/setup/Cargo.toml similarity index 75% rename from src/tools/gravity-setup/Cargo.toml rename to src/tools/setup/Cargo.toml index d47160d..3043382 100644 --- a/src/tools/gravity-setup/Cargo.toml +++ b/src/tools/setup/Cargo.toml @@ -1,17 +1,18 @@ [package] name = "gravity-setup" -version = "0.1.0" -edition = "2024" +version.workspace = true +edition.workspace = true [dependencies] -apple-dmg = { path = "../../lib/apple-dmg" } +apple-dmg = { path = "../../lib/apple-dmg", features = ["std"] } hfsplus = { path = "../../lib/hfsplus" } vfdecrypt = { path = "../../lib/vfdecrypt" } ipsw-downloader = { path = "../../lib/ipsw-downloader" } reqwest = { workspace = true } plist = { workspace = true } serde = { workspace = true } -thiserror = { workspace = true } +derive_more = { workspace = true } +rootcause = { workspace = true } tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } futures-util = { workspace = true } indicatif = { workspace = true } diff --git a/src/tools/setup/src/main.rs b/src/tools/setup/src/main.rs new file mode 100644 index 0000000..250aa4c --- /dev/null +++ b/src/tools/setup/src/main.rs @@ -0,0 +1,388 @@ +use clap::Parser; +use derive_more::derive::Display; +use indicatif::{MultiProgress, ProgressBar, ProgressStyle}; +use ipsw_downloader::fetch_firmware_url; +use rootcause::{Report, report}; +use std::cmp::min; +use std::fs::File; +use std::io::{self, Read, Write}; +use std::path::{Path, PathBuf}; +use std::time::Instant; +use tokio::io::AsyncWriteExt; +use vfdecrypt::decrypt; + +#[derive(Debug, Display)] +pub enum SetupError { + #[display("IO error: {_0}")] + Io(io::Error), + #[display("DMG error: {_0}")] + Dmg(String), + #[display("HFS+ error: {_0}")] + Hfs(String), + #[display("Download error: {_0}")] + Download(String), + #[display("VFDecrypt error: {_0}")] + Decrypt(String), + #[display("Zip error: {_0}")] + Zip(zip::result::ZipError), + #[display("Error: {_0}")] + Other(String), +} + +impl std::error::Error for SetupError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + SetupError::Io(e) => Some(e), + SetupError::Zip(e) => Some(e), + _ => None, + } + } +} + +impl From for SetupError { + fn from(err: io::Error) -> Self { + SetupError::Io(err) + } +} + +impl From for SetupError { + fn from(err: zip::result::ZipError) -> Self { + SetupError::Zip(err) + } +} + +#[derive(Parser, Debug)] +#[command(name = "gravity-setup")] +struct Cli { + #[command(subcommand)] + command: Commands, +} + +#[derive(clap::Subcommand, Debug)] +enum Commands { + /// Setup the OS (fetch IPSW, decryptl) + Setup(Args), + // Build the workspace for the CI + Ci, + /// Run the kernel in QEMU + Qemu(QemuArgs), +} + +#[derive(Parser, Debug)] +struct Args { + /// Device Identifier (e.g., iPad1,1) + #[arg(long, default_value = "iPad1,1")] + device: String, + + /// Build ID (e.g., 9B206) + #[arg(long, default_value = "9B206")] + build: String, + + /// Rootfs decryption key + #[arg( + long, + default_value = "f7bb9fd8aa3102484ab9c847dacfd3d73f1f430acb49ed7a422226f2410acee17664c91b" + )] + key: String, + + /// Output disk image path + #[arg(long, default_value = "disk.img")] + output: PathBuf, + + /// Work directory for intermediate files + #[arg(long, default_value = "work")] + work_dir: PathBuf, + + /// Disk size in MB + #[arg(long, default_value_t = 1536)] + size_mb: u64, +} + +#[derive(Parser, Debug)] +struct QemuArgs { + /// Path to the kernel binary + #[arg( + long, + default_value = "target/aarch64-unknown-none-softfloat/debug/kernel" + )] + kernel: PathBuf, + + /// Memory size + #[arg(long, default_value = "1G")] + memory: String, + + /// Number of CPUs + #[arg(long, default_value_t = 1)] + cpus: u32, + + /// Whether to run in nographic mode + #[arg(long, default_value_t = true)] + nographic: bool, +} + +#[tokio::main] +async fn main() -> Result<(), Report> { + let cli = Cli::parse(); + match cli.command { + Commands::Setup(args) => setup(args).await, + Commands::Qemu(args) => qemu(args), + Commands::Ci => ci(), + } +} + +fn ci() -> Result<(), Report> { + let total_start = Instant::now(); + + if !std::process::Command::new("cargo") + .arg("build") + .arg("-Zbuild-std=core,alloc,compiler_builtins") + .arg("-Zbuild-std-features=compiler-builtins-mem") + .arg("--target") + .arg("aarch64-unknown-none-softfloat") + .arg("-p") + .arg("kernel") + .arg("-p") + .arg("dyld") + .env( + "RUSTFLAGS", + "-Zsanitizer=kcfi -Clink-arg=--ld-path=ld.lld -Clinker=clang -Clink-arg=--target=aarch64-unknown-none-elf -Clink-arg=-nostdlib", + ) + .status() + .map_err(|e| report!(SetupError::Other(e.to_string())))? + .success() + { + return Err(report!(SetupError::Other( + "Failed to build kernel or dyld".to_string(), + ))); + } + + println!("✨ Done in {:?}", total_start.elapsed()); + + Ok(()) +} + +async fn setup(args: Args) -> Result<(), Report> { + let mp = MultiProgress::new(); + let total_start = Instant::now(); + + std::fs::create_dir_all(&args.work_dir).map_err(|e| { + report!(SetupError::Io(e)).context(SetupError::Other(format!( + "Failed to create work directory: {}", + args.work_dir.display() + ))) + })?; + + println!( + "Fetching firmware URL for {} build {}...", + args.device, args.build + ); + let ipsw_url = fetch_firmware_url(&args.device, &args.build) + .await + .map_err(|e| report!(SetupError::Download(e.to_string())))? + .ok_or_else(|| { + report!(SetupError::Other(format!( + "Firmware not found for device {} build {}", + args.device, args.build + ))) + })?; + + // 2. Download IPSW + let ipsw_path = args.work_dir.join("firmware.ipsw"); + if !ipsw_path.exists() { + println!("Downloading IPSW..."); + download_ipsw(&ipsw_url, &ipsw_path, &mp).await?; + } else { + println!("IPSW already downloaded, skipping."); + } + + // 3. Extract Rootfs DMG (optimized) + let rootfs_dmg_encrypted = args.work_dir.join("rootfs_encrypted.dmg"); + if !rootfs_dmg_encrypted.exists() { + println!("Extracting rootfs DMG from IPSW..."); + let file = File::open(&ipsw_path).map_err(|e| { + report!(SetupError::Io(e)).context(SetupError::Other(format!( + "Failed to open IPSW at {}", + ipsw_path.display() + ))) + })?; + let mut archive = zip::ZipArchive::new(file).map_err(|e| { + report!(SetupError::Zip(e)) + .context(SetupError::Other("Failed to open IPSW as ZIP".to_string())) + })?; + + let mut largest_name = String::new(); + let mut largest_size = 0; + let mut largest_index = 0; + + for i in 0..archive.len() { + let file = archive.by_index(i).map_err(|e| { + report!(SetupError::Zip(e)) + .context(SetupError::Other(format!("Failed to read ZIP entry {}", i))) + })?; + if file.name().ends_with(".dmg") && file.size() > largest_size { + largest_size = file.size(); + largest_name = file.name().to_string(); + largest_index = i; + } + } + + if largest_size == 0 { + return Err(report!(SetupError::Other(format!( + "No DMG found in IPSW at {}", + ipsw_path.display() + )))); + } + + println!( + "Extracting {} ({} MB)...", + largest_name, + largest_size / 1024 / 1024 + ); + let mut rootfs_zip = archive.by_index(largest_index).map_err(|e| { + report!(SetupError::Zip(e)).context(SetupError::Other(format!( + "Failed to access DMG entry: {}", + largest_name + ))) + })?; + let mut output = File::create(&rootfs_dmg_encrypted).map_err(|e| { + report!(SetupError::Io(e)).context(SetupError::Other(format!( + "Failed to create output file: {}", + rootfs_dmg_encrypted.display() + ))) + })?; + + let pb = mp.add(ProgressBar::new(largest_size)); + pb.set_style(ProgressStyle::default_bar() + .template("{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {bytes}/{total_bytes} ({bytes_per_sec}, {eta})") + .unwrap() + .progress_chars("#>-")); + + let mut buffer = vec![0u8; 1024 * 1024]; + loop { + let n = rootfs_zip.read(&mut buffer).map_err(|e| { + report!(SetupError::Io(e)).context(SetupError::Other(format!( + "Failed to read from {}", + largest_name + ))) + })?; + if n == 0 { + break; + } + output.write_all(&buffer[..n]).map_err(|e| { + report!(SetupError::Io(e)).context(SetupError::Other(format!( + "Failed to write to {}", + rootfs_dmg_encrypted.display() + ))) + })?; + pb.inc(n as u64); + } + pb.finish_with_message("Extraction complete"); + } else { + println!("Rootfs DMG already extracted, skipping."); + } + + // 4. Decrypt Rootfs DMG + let rootfs_dmg = args.work_dir.join("rootfs.dmg"); + if !rootfs_dmg.exists() { + println!("Decrypting rootfs DMG..."); + let mut input = File::open(&rootfs_dmg_encrypted).map_err(|e| { + report!(SetupError::Io(e)).context(SetupError::Other(format!( + "Failed to open encrypted DMG: {}", + rootfs_dmg_encrypted.display() + ))) + })?; + let mut output = File::create(&rootfs_dmg).map_err(|e| { + report!(SetupError::Io(e)).context(SetupError::Other(format!( + "Failed to create output file: {}", + rootfs_dmg.display() + ))) + })?; + decrypt(&mut input, &mut output, &args.key) + .map_err(|e| report!(SetupError::Decrypt(e.to_string())))?; + } else { + println!("Rootfs DMG already decrypted, skipping."); + } + + println!("✨ Done in {:?}", total_start.elapsed()); + Ok(()) +} + +fn qemu(args: QemuArgs) -> Result<(), Report> { + println!("🚀 Starting QEMU..."); + let mut cmd = std::process::Command::new("qemu-system-aarch64"); + cmd.arg("-machine") + .arg("virt") + .arg("-cpu") + .arg("cortex-a57") + .arg("-m") + .arg(&args.memory) + .arg("-smp") + .arg(args.cpus.to_string()) + .arg("-kernel") + .arg(&args.kernel) + .arg("-drive") + .arg("file=work/rootfs.dmg,format=raw,if=virtio") + .arg("-serial") + .arg("mon:stdio"); + + if args.nographic { + cmd.arg("-nographic"); + } + + let status = cmd + .status() + .map_err(|e| report!(SetupError::Other(format!("Failed to run QEMU: {}", e))))?; + + if !status.success() { + return Err(report!(SetupError::Other( + "QEMU exited with error".to_string() + ))); + } + + Ok(()) +} + +async fn download_ipsw( + url: &str, + output: &Path, + mp: &MultiProgress, +) -> Result<(), Report> { + let client = reqwest::Client::new(); + let response = client.get(url).send().await.map_err(|e| { + report!(SetupError::Download(e.to_string())).context(SetupError::Download(format!( + "Failed to download IPSW from {}", + url + ))) + })?; + let total_size = response.content_length().unwrap_or(0); + + let pb = mp.add(ProgressBar::new(total_size)); + pb.set_style(ProgressStyle::default_bar() + .template("{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {bytes}/{total_bytes} ({bytes_per_sec}, {eta})") + .unwrap() + .progress_chars("#>-")); + + let mut file = tokio::fs::File::create(output).await.map_err(|e| { + report!(SetupError::Io(e)).context(SetupError::Other(format!( + "Failed to create output file: {}", + output.display() + ))) + })?; + let mut downloaded: u64 = 0; + let mut stream = response.bytes_stream(); + + use futures_util::StreamExt; + while let Some(item) = stream.next().await { + let chunk = item.map_err(|e| report!(SetupError::Download(e.to_string())))?; + file.write_all(&chunk).await.map_err(|e| { + report!(SetupError::Io(e)).context(SetupError::Other( + "Failed to write downloaded data".to_string(), + )) + })?; + downloaded = min(downloaded + (chunk.len() as u64), total_size); + pb.set_position(downloaded); + } + + pb.finish_with_message("Download complete"); + Ok(()) +}