commit cb3c6062c7105411d16275ce715cd212a8f902ae Author: Luke Street Date: Thu Sep 8 17:19:20 2022 -0400 Initial commit diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 0000000..8702adb --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,61 @@ +name: Build + +on: [ push, pull_request ] + +jobs: + check: + name: Check + runs-on: ubuntu-latest + strategy: + matrix: + toolchain: [ stable, 1.61.0, nightly ] + fail-fast: false + env: + RUSTFLAGS: -D warnings + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: ${{ matrix.toolchain }} + override: true + components: rustfmt, clippy + - uses: EmbarkStudios/cargo-deny-action@v1 + - uses: actions-rs/cargo@v1 + with: + command: check + args: --all-features + - uses: actions-rs/cargo@v1 + with: + command: clippy + args: --all-features + + build: + name: Build + strategy: + matrix: + platform: [ ubuntu-latest, macos-latest, windows-latest ] + toolchain: [ stable, 1.61.0, nightly ] + fail-fast: false + runs-on: ${{ matrix.platform }} + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: ${{ matrix.toolchain }} + override: true + - uses: actions-rs/cargo@v1 + with: + command: test + args: --release --all-features + - uses: actions-rs/cargo@v1 + with: + command: build + args: --release --all-features + - uses: actions/upload-artifact@v2 + with: + name: ${{ matrix.platform }}-${{ matrix.toolchain }} + path: | + target/release/objdiff + target/release/objdiff.exe diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e365361 --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +# Rust +target/ +**/*.rs.bk +generated/ + +# cargo-mobile +.cargo/ +/gen + +# macOS +.DS_Store + +# JetBrains +.idea + +# Generated SPIR-V +*.spv + +# project +textures/ +android.keystore +*.frag +*.vert +*.metal diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..5db4211 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,2445 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "ab_glyph" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04a9283dace1c41c265496614998d5b9c4a97b3eb770e804f007c5144bf03f2b" +dependencies = [ + "ab_glyph_rasterizer", + "owned_ttf_parser", +] + +[[package]] +name = "ab_glyph_rasterizer" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "363b9b88fad3af3be80bc8f762c9a3f9dfe906fd0327b8e92f1c12e5ae1b8bbb" + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "adler32" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" + +[[package]] +name = "ahash" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57e6e951cfbb2db8de1828d49073a113a29fd7117b1596caa781a258c7e38d72" +dependencies = [ + "cfg-if", + "getrandom", + "once_cell", + "serde", + "version_check", +] + +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "anyhow" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26fa4d7e3f2eebadf743988fc8aec9fa9a9e82611acafd77c1462ed6262440a" + +[[package]] +name = "arboard" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc120354d1b5ec6d7aaf4876b602def75595937b5e15d356eb554ab5177e08bb" +dependencies = [ + "clipboard-win", + "log", + "objc", + "objc-foundation", + "objc_id", + "parking_lot", + "thiserror", + "winapi", + "x11rb", +] + +[[package]] +name = "argh" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7e7e4aa7e40747e023c0761dafcb42333a9517575bbf1241747f68dd3177a62" +dependencies = [ + "argh_derive", + "argh_shared", +] + +[[package]] +name = "argh_derive" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69f2bd7ff6ed6414f4e5521bd509bae46454bbd513801767ced3f21a751ab4bc" +dependencies = [ + "argh_shared", + "heck 0.3.3", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "argh_shared" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47253b98986dafc7a3e1cf3259194f1f47ac61abb57a57f46ec09e48d004ecda" + +[[package]] +name = "arrayref" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" + +[[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + +[[package]] +name = "atk-sys" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58aeb089fb698e06db8089971c7ee317ab9644bade33383f63631437b03aafb6" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "atomic_refcell" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73b5e5f48b927f04e952dedc932f31995a65a0bf65ec971c74436e51bf6e970d" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + +[[package]] +name = "bumpalo" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d" + +[[package]] +name = "bytemuck" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f5715e491b5a1598fc2bef5a606847b5dc1d48ea625bd3c02c00de8285591da" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9e1f5fa78f69496407a27ae9ed989e3c3b072310286f5ef385525e4cbc24a9" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "bytes" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" + +[[package]] +name = "cairo-sys-rs" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c55d429bef56ac9172d25fecb85dc8068307d17acd74b377866b7a1ef25d3c8" +dependencies = [ + "libc", + "system-deps", +] + +[[package]] +name = "calloop" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a22a6a8f622f797120d452c630b0ab12e1331a1a753e2039ce7868d4ac77b4ee" +dependencies = [ + "log", + "nix 0.24.2", + "slotmap", + "thiserror", + "vec_map", +] + +[[package]] +name = "cc" +version = "1.0.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cfg-expr" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aacacf4d96c24b2ad6eb8ee6df040e4f27b0d0b39a5710c30091baa830485db" +dependencies = [ + "smallvec", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cgl" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ced0551234e87afee12411d535648dd89d2e7f34c78b753395567aff3d447ff" +dependencies = [ + "libc", +] + +[[package]] +name = "clipboard-win" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4ab1b92798304eedc095b53942963240037c0516452cb11aeba709d420b2219" +dependencies = [ + "error-code", + "str-buf", + "winapi", +] + +[[package]] +name = "cmake" +version = "0.1.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8ad8cef104ac57b68b89df3208164d228503abbdce70f6880ffa3d970e7443a" +dependencies = [ + "cc", +] + +[[package]] +name = "cocoa" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f63902e9223530efb4e26ccd0cf55ec30d592d3b42e21a28defc42a9586e832" +dependencies = [ + "bitflags", + "block", + "cocoa-foundation", + "core-foundation", + "core-graphics", + "foreign-types 0.3.2", + "libc", + "objc", +] + +[[package]] +name = "cocoa-foundation" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ade49b65d560ca58c403a479bb396592b155c0185eada742ee323d1d68d6318" +dependencies = [ + "bitflags", + "block", + "core-foundation", + "core-graphics-types", + "foreign-types 0.3.2", + "libc", + "objc", +] + +[[package]] +name = "combine" +version = "4.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" + +[[package]] +name = "core-graphics" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb" +dependencies = [ + "bitflags", + "core-foundation", + "core-graphics-types", + "foreign-types 0.3.2", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a68b68b3446082644c91ac778bf50cd4104bfb002b5a6a7c44cca5a2c70788b" +dependencies = [ + "bitflags", + "core-foundation", + "foreign-types 0.3.2", + "libc", +] + +[[package]] +name = "core-text" +version = "19.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d74ada66e07c1cefa18f8abfba765b486f250de2e4a999e5727fc0dd4b4a25" +dependencies = [ + "core-foundation", + "core-graphics", + "foreign-types 0.3.2", + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51887d4adc7b564537b15adcfb307936f8075dfcd5f00dde9a9f1d29383682bc" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "crossfont" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f66b1c1979c4362323f03ab6bf7fb522902bfc418e0c37319ab347f9561d980f" +dependencies = [ + "cocoa", + "core-foundation", + "core-foundation-sys", + "core-graphics", + "core-text", + "dwrote", + "foreign-types 0.5.0", + "freetype-rs", + "libc", + "log", + "objc", + "once_cell", + "pkg-config", + "servo-fontconfig", + "winapi", +] + +[[package]] +name = "cty" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35" + +[[package]] +name = "cwdemangle" +version = "0.1.0" +dependencies = [ + "argh", +] + +[[package]] +name = "darling" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "deflate" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c86f7e25f518f4b81808a2cf1c50996a61f5c2eb394b2393bd87f2a4780a432f" +dependencies = [ + "adler32", +] + +[[package]] +name = "directories-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339ee130d97a610ea5a5872d2bbb130fdf68884ff09d3028b81bec8a1ac23bbc" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "dispatch" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" + +[[package]] +name = "dlib" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac1b7517328c04c2aa68422fc60a41b92208182142ed04a25879c26c8f878794" +dependencies = [ + "libloading", +] + +[[package]] +name = "downcast-rs" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" + +[[package]] +name = "dwrote" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439a1c2ba5611ad3ed731280541d36d2e9c4ac5e7fb818a27b604bdc5a6aa65b" +dependencies = [ + "lazy_static", + "libc", + "serde", + "serde_derive", + "winapi", + "wio", +] + +[[package]] +name = "eframe" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0d49426c3e72a6728b0c790d22db8bf7bbcff10d83b8b6f3a01295be982302e" +dependencies = [ + "bytemuck", + "directories-next", + "egui", + "egui-winit", + "egui_glow", + "getrandom", + "glow", + "glutin", + "js-sys", + "percent-encoding", + "ron", + "serde", + "tracing", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winit", +] + +[[package]] +name = "egui" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc9fcd393c3daaaf5909008a1d948319d538b79c51871e4df0993260260a94e4" +dependencies = [ + "ahash", + "epaint", + "nohash-hasher", + "ron", + "serde", + "tracing", +] + +[[package]] +name = "egui-winit" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07ddc525334c416e11580123e147b970f738507f427c9fb1cd09ea2dd7416a3a" +dependencies = [ + "arboard", + "egui", + "instant", + "serde", + "smithay-clipboard", + "tracing", + "webbrowser", + "winit", +] + +[[package]] +name = "egui_extras" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f698f685bb0ad39e87109e2f695ded0bccde77d5d40bbf7590cb5561c1e3039d" +dependencies = [ + "egui", +] + +[[package]] +name = "egui_glow" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad77d4a00402bae9658ee64be148f4b2a0b38e4fc7874970575ca01ed1c5b75d" +dependencies = [ + "bytemuck", + "egui", + "glow", + "memoffset", + "tracing", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "emath" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9542a40106fdba943a055f418d1746a050e1a903a049b030c2b097d4686a33cf" +dependencies = [ + "bytemuck", + "serde", +] + +[[package]] +name = "epaint" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ba04741be7f6602b1a1b28f1082cce45948a7032961c52814f8946b28493300" +dependencies = [ + "ab_glyph", + "ahash", + "atomic_refcell", + "bytemuck", + "emath", + "nohash-hasher", + "parking_lot", + "serde", +] + +[[package]] +name = "error-code" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64f18991e7bf11e7ffee451b5318b5c1a73c52d0d0ada6e5a3017c8c1ced6a21" +dependencies = [ + "libc", + "str-buf", +] + +[[package]] +name = "expat-sys" +version = "2.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "658f19728920138342f68408b7cf7644d90d4784353d8ebc32e7e8663dbe45fa" +dependencies = [ + "cmake", + "pkg-config", +] + +[[package]] +name = "filetime" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e94a7bbaa59354bc20dd75b67f23e2797b4490e9d6928203fb105c79e448c86c" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "windows-sys", +] + +[[package]] +name = "flagset" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda653ca797810c02f7ca4b804b40b8b95ae046eb989d356bce17919a8c25499" + +[[package]] +name = "flate2" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared 0.1.1", +] + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared 0.3.1", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8469d0d40519bc608ec6863f1cc88f3f1deee15913f2f3b3e573d81ed38cccc" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + +[[package]] +name = "form_urlencoded" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +dependencies = [ + "matches", + "percent-encoding", +] + +[[package]] +name = "freetype-rs" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74eadec9d0a5c28c54bb9882e54787275152a4e36ce206b45d7451384e5bf5fb" +dependencies = [ + "bitflags", + "freetype-sys", + "libc", +] + +[[package]] +name = "freetype-sys" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a37d4011c0cc628dfa766fcc195454f4b068d7afdc2adfd28861191d866e731a" +dependencies = [ + "cmake", + "libc", + "pkg-config", +] + +[[package]] +name = "fsevent-sys" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" +dependencies = [ + "libc", +] + +[[package]] +name = "gdk-pixbuf-sys" +version = "0.15.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "140b2f5378256527150350a8346dbdb08fadc13453a7a2d73aecd5fab3c402a7" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gdk-sys" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e7a08c1e8f06f4177fb7e51a777b8c1689f743a7bc11ea91d44d2226073a88" +dependencies = [ + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "pkg-config", + "system-deps", +] + +[[package]] +name = "gethostname" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1ebd34e35c46e00bb73e81363248d627782724609fe1b6396f553f68fe3862e" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "getrandom" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "gio-sys" +version = "0.15.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32157a475271e2c4a023382e9cab31c4584ee30a97da41d3c4e9fdd605abcf8d" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", + "winapi", +] + +[[package]] +name = "gl_generator" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" +dependencies = [ + "khronos_api", + "log", + "xml-rs", +] + +[[package]] +name = "glib-sys" +version = "0.15.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef4b192f8e65e9cf76cbf4ea71fa8e3be4a0e18ffe3d68b8da6836974cc5bad4" +dependencies = [ + "libc", + "system-deps", +] + +[[package]] +name = "glow" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8bd5877156a19b8ac83a29b2306fe20537429d318f3ff0a1a2119f8d9c61919" +dependencies = [ + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "glutin" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "444c9ad294fdcaf20ccf6726b78f380b5450275540c9b68ab62f49726ad1c713" +dependencies = [ + "cgl", + "cocoa", + "core-foundation", + "glutin_egl_sys", + "glutin_gles2_sys", + "glutin_glx_sys", + "glutin_wgl_sys", + "libloading", + "log", + "objc", + "once_cell", + "osmesa-sys", + "parking_lot", + "raw-window-handle 0.5.0", + "wayland-client", + "wayland-egl", + "winapi", + "winit", +] + +[[package]] +name = "glutin_egl_sys" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68900f84b471f31ea1d1355567eb865a2cf446294f06cef8d653ed7bcf5f013d" +dependencies = [ + "gl_generator", + "winapi", +] + +[[package]] +name = "glutin_gles2_sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8094e708b730a7c8a1954f4f8a31880af00eb8a1c5b5bf85d28a0a3c6d69103" +dependencies = [ + "gl_generator", + "objc", +] + +[[package]] +name = "glutin_glx_sys" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d93d0575865098580c5b3a423188cd959419912ea60b1e48e8b3b526f6d02468" +dependencies = [ + "gl_generator", + "x11-dl", +] + +[[package]] +name = "glutin_wgl_sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3da5951a1569dbab865c6f2a863efafff193a93caf05538d193e9e3816d21696" +dependencies = [ + "gl_generator", +] + +[[package]] +name = "gobject-sys" +version = "0.15.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d57ce44246becd17153bd035ab4d32cfee096a657fc01f2231c9278378d1e0a" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gtk-sys" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5bc2f0587cba247f60246a0ca11fe25fb733eabc3de12d1965fc07efab87c84" +dependencies = [ + "atk-sys", + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "system-deps", +] + +[[package]] +name = "heck" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "heck" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "inotify" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff" +dependencies = [ + "bitflags", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" +dependencies = [ + "libc", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "jni" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6df18c2e3db7e453d3c6ac5b3e9d5182664d28788126d39b91f2d1e22b017ec" +dependencies = [ + "cesu8", + "combine", + "jni-sys", + "log", + "thiserror", + "walkdir", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "js-sys" +version = "0.3.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "258451ab10b34f8af53416d1fdab72c22e805f0c92a1136d59470ec0b11138b2" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "khronos_api" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" + +[[package]] +name = "kqueue" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d6112e8f37b59803ac47a42d14f1f3a59bbf72fc6857ffc5be455e28a691f8e" +dependencies = [ + "kqueue-sys", + "libc", +] + +[[package]] +name = "kqueue-sys" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8367585489f01bc55dd27404dcf56b95e6da061a256a666ab23be9ba96a2e587" +dependencies = [ + "bitflags", + "libc", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.132" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5" + +[[package]] +name = "libloading" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "lock_api" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f80bf5aacaf25cbfc8210d1cfb718f2bf3b11c4c54e5afe36c236853a8ec390" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + +[[package]] +name = "matches" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "memmap2" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95af15f345b17af2efc8ead6080fb8bc376f8cec1b35277b935637595fe77498" +dependencies = [ + "libc", +] + +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + +[[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.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys", +] + +[[package]] +name = "ndk" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2032c77e030ddee34a6787a64166008da93f6a352b629261d0fee232b8742dd4" +dependencies = [ + "bitflags", + "jni-sys", + "ndk-sys 0.3.0", + "num_enum", + "thiserror", +] + +[[package]] +name = "ndk" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "451422b7e4718271c8b5b3aadf5adedba43dc76312454b387e98fae0fc951aa0" +dependencies = [ + "bitflags", + "jni-sys", + "ndk-sys 0.4.0", + "num_enum", + "raw-window-handle 0.5.0", + "thiserror", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-glue" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d0c4a7b83860226e6b4183edac21851f05d5a51756e97a1144b7f5a6b63e65f" +dependencies = [ + "lazy_static", + "libc", + "log", + "ndk 0.6.0", + "ndk-context", + "ndk-macro", + "ndk-sys 0.3.0", +] + +[[package]] +name = "ndk-glue" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0434fabdd2c15e0aab768ca31d5b7b333717f03cf02037d5a0a3ff3c278ed67f" +dependencies = [ + "libc", + "log", + "ndk 0.7.0", + "ndk-context", + "ndk-macro", + "ndk-sys 0.4.0", + "once_cell", + "parking_lot", +] + +[[package]] +name = "ndk-macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0df7ac00c4672f9d5aece54ee3347520b7e20f158656c7db2e6de01902eb7a6c" +dependencies = [ + "darling", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "ndk-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e5a6ae77c8ee183dcbbba6150e2e6b9f3f4196a7666c02a715a95692ec1fa97" +dependencies = [ + "jni-sys", +] + +[[package]] +name = "ndk-sys" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21d83ec9c63ec5bf950200a8e508bdad6659972187b625469f58ef8c08e29046" +dependencies = [ + "jni-sys", +] + +[[package]] +name = "nix" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4916f159ed8e5de0082076562152a76b7a1f64a01fd9d1e0fea002c37624faf" +dependencies = [ + "bitflags", + "cc", + "cfg-if", + "libc", + "memoffset", +] + +[[package]] +name = "nix" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "195cdbc1741b8134346d515b3a56a1c94b0912758009cfd53f99ea0f57b065fc" +dependencies = [ + "bitflags", + "cfg-if", + "libc", + "memoffset", +] + +[[package]] +name = "nohash-hasher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" + +[[package]] +name = "nom" +version = "7.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "notify" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2c66da08abae1c024c01d635253e402341b4060a12e99b31c7594063bf490a" +dependencies = [ + "bitflags", + "crossbeam-channel", + "filetime", + "fsevent-sys", + "inotify", + "kqueue", + "libc", + "mio", + "walkdir", + "winapi", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_enum" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf5395665662ef45796a4ff5486c5d41d29e0c09640af4c5f17fd94ee2c119c9" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0498641e53dd6ac1a4f22547548caa6864cc4933784319cd1775271c5a46ce" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", +] + +[[package]] +name = "objc-foundation" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" +dependencies = [ + "block", + "objc", + "objc_id", +] + +[[package]] +name = "objc_id" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" +dependencies = [ + "objc", +] + +[[package]] +name = "objdiff" +version = "0.1.0" +dependencies = [ + "anyhow", + "console_error_panic_hook", + "cwdemangle", + "eframe", + "egui", + "egui_extras", + "flagset", + "log", + "notify", + "object", + "ppc750cl", + "rfd", + "serde", + "thiserror", + "tracing-subscriber", + "tracing-wasm", +] + +[[package]] +name = "object" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53" +dependencies = [ + "flate2", + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f7254b99e31cad77da24b08ebf628882739a608578bb1bcdfc1f9c21260d7c0" + +[[package]] +name = "osmesa-sys" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88cfece6e95d2e717e0872a7f53a8684712ad13822a7979bc760b9c77ec0013b" +dependencies = [ + "shared_library", +] + +[[package]] +name = "owned_ttf_parser" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05e6affeb1632d6ff6a23d2cd40ffed138e82f1532571a26f527c8a284bb2fbb" +dependencies = [ + "ttf-parser", +] + +[[package]] +name = "pango-sys" +version = "0.15.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2a00081cde4661982ed91d80ef437c20eacaf6aa1a5962c0279ae194662c3aa" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-sys", +] + +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "pkg-config" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" + +[[package]] +name = "png" +version = "0.17.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc38c0ad57efb786dd57b9864e5b18bae478c00c824dc55a38bbc9da95dde3ba" +dependencies = [ + "bitflags", + "crc32fast", + "deflate", + "miniz_oxide", +] + +[[package]] +name = "ppc750cl" +version = "0.2.0" +source = "git+https://github.com/terorie/ppc750cl#6a3476639ae677023e67b468e511c68123584807" +dependencies = [ + "num-traits", + "serde", +] + +[[package]] +name = "proc-macro-crate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eda0fc3b0fb7c975631757e14d9049da17374063edb6ebbcbc54d880d4fe94e9" +dependencies = [ + "once_cell", + "thiserror", + "toml", +] + +[[package]] +name = "proc-macro2" +version = "1.0.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "raw-window-handle" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b800beb9b6e7d2df1fe337c9e3d04e3af22a124460fb4c30fcc22c9117cefb41" +dependencies = [ + "cty", +] + +[[package]] +name = "raw-window-handle" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed7e3d950b66e19e0c372f3fa3fbbcf85b1746b571f74e0c2af6042a5c93420a" +dependencies = [ + "cty", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +dependencies = [ + "getrandom", + "redox_syscall", + "thiserror", +] + +[[package]] +name = "rfd" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0149778bd99b6959285b0933288206090c50e2327f47a9c463bfdbf45c8823ea" +dependencies = [ + "block", + "dispatch", + "glib-sys", + "gobject-sys", + "gtk-sys", + "js-sys", + "lazy_static", + "log", + "objc", + "objc-foundation", + "objc_id", + "raw-window-handle 0.5.0", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows", +] + +[[package]] +name = "ron" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300a51053b1cb55c80b7a9fde4120726ddf25ca241a1cbb926626f62fb136bff" +dependencies = [ + "base64", + "bitflags", + "serde", +] + +[[package]] +name = "safe_arch" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1ff3d6d9696af502cc3110dacce942840fb06ff4514cad92236ecc455f2ce05" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scoped-tls" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "sctk-adwaita" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04b7c47a572f73de28bee5b5060d085b42b6ce1e4ee2b49c956ea7b25e94b6f0" +dependencies = [ + "crossfont", + "log", + "smithay-client-toolkit", + "tiny-skia", +] + +[[package]] +name = "serde" +version = "1.0.144" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f747710de3dcd43b88c9168773254e809d8ddbdf9653b84e2554ab219f17860" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.144" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94ed3a816fb1d101812f83e789f888322c34e291f894f19590dc310963e87a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "servo-fontconfig" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7e3e22fe5fd73d04ebf0daa049d3efe3eae55369ce38ab16d07ddd9ac5c217c" +dependencies = [ + "libc", + "servo-fontconfig-sys", +] + +[[package]] +name = "servo-fontconfig-sys" +version = "5.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e36b879db9892dfa40f95da1c38a835d41634b825fbd8c4c418093d53c24b388" +dependencies = [ + "expat-sys", + "freetype-sys", + "pkg-config", +] + +[[package]] +name = "sharded-slab" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shared_library" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a9e7e0f2bfae24d8a5b5a66c5b257a83c7412304311512a0c054cd5e619da11" +dependencies = [ + "lazy_static", + "libc", +] + +[[package]] +name = "slotmap" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1e08e261d0e8f5c43123b7adf3e4ca1690d655377ac93a03b2c9d3e98de1342" +dependencies = [ + "version_check", +] + +[[package]] +name = "smallvec" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" + +[[package]] +name = "smithay-client-toolkit" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f307c47d32d2715eb2e0ece5589057820e0e5e70d07c247d1063e844e107f454" +dependencies = [ + "bitflags", + "calloop", + "dlib", + "lazy_static", + "log", + "memmap2", + "nix 0.24.2", + "pkg-config", + "wayland-client", + "wayland-cursor", + "wayland-protocols", +] + +[[package]] +name = "smithay-clipboard" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a345c870a1fae0b1b779085e81b51e614767c239e93503588e54c5b17f4b0e8" +dependencies = [ + "smithay-client-toolkit", + "wayland-client", +] + +[[package]] +name = "str-buf" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e08d8363704e6c71fc928674353e6b7c23dcea9d82d7012c8faf2a3a025f8d0" + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "1.0.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "system-deps" +version = "6.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a45a1c4c9015217e12347f2a411b57ce2c4fc543913b14b6fe40483328e709" +dependencies = [ + "cfg-expr", + "heck 0.4.0", + "pkg-config", + "toml", + "version-compare", +] + +[[package]] +name = "thiserror" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d0a539a918745651435ac7db7a18761589a94cd7e94cd56999f828bf73c8a57" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c251e90f708e16c49a16f4917dc2131e75222b72edfa9cb7f7c58ae56aae0c09" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" +dependencies = [ + "once_cell", +] + +[[package]] +name = "tiny-skia" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "642680569bb895b16e4b9d181c60be1ed136fa0c9c7f11d004daf053ba89bf82" +dependencies = [ + "arrayref", + "arrayvec", + "bytemuck", + "cfg-if", + "png", + "safe_arch", + "tiny-skia-path", +] + +[[package]] +name = "tiny-skia-path" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c114d32f0c2ee43d585367cb013dfaba967ab9f62b90d9af0d696e955e70fa6c" +dependencies = [ + "arrayref", + "bytemuck", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "toml" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" +dependencies = [ + "serde", +] + +[[package]] +name = "tracing" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fce9567bd60a67d08a16488756721ba392f24f29006402881e43b19aac64307" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11c75893af559bc8e10716548bdef5cb2b983f8e637db9d0e15126b61b484ee2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aeea4303076558a00714b823f9ad67d58a3bbda1df83d8827d21193156e22f7" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +dependencies = [ + "lazy_static", + "log", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60db860322da191b40952ad9affe65ea23e7dd6a5c442c2c42865810c6ab8e6b" +dependencies = [ + "ansi_term", + "sharded-slab", + "smallvec", + "thread_local", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "tracing-wasm" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4575c663a174420fa2d78f4108ff68f65bf2fbb7dd89f33749b6e826b3626e07" +dependencies = [ + "tracing", + "tracing-subscriber", + "wasm-bindgen", +] + +[[package]] +name = "ttf-parser" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b3e06c9b9d80ed6b745c7159c40b311ad2916abb34a49e9be2653b90db0d8dd" + +[[package]] +name = "unicode-bidi" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" + +[[package]] +name = "unicode-ident" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf" + +[[package]] +name = "unicode-normalization" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "854cbdc4f7bc6ae19c820d44abdc3277ac3e1b2b93db20a636825d9322fb60e6" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" + +[[package]] +name = "url" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +dependencies = [ + "form_urlencoded", + "idna", + "matches", + "percent-encoding", +] + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + +[[package]] +name = "version-compare" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe88247b92c1df6b6de80ddc290f3976dbdf2f5f5d3fd049a9fb598c6dd5ca73" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "walkdir" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +dependencies = [ + "same-file", + "winapi", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7652e3f6c4706c8d9cd54832c4a4ccb9b5336e2c3bd154d5cccfbf1c1f5f7d" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "662cd44805586bd52971b9586b1df85cdbbd9112e4ef4d8f41559c334dc6ac3f" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa76fb221a1f8acddf5b54ace85912606980ad661ac7a503b4570ffd3a624dad" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b260f13d3012071dfb1512849c033b1925038373aea48ced3012c09df952c602" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be8e654bdd9b79216c2929ab90721aa82faf65c48cdf08bdc4e7f51357b80da" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6598dd0bd3c7d51095ff6531a5b23e02acdc81804e30d8f07afb77b7215a140a" + +[[package]] +name = "wayland-client" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f3b068c05a039c9f755f881dc50f01732214f5685e379829759088967c46715" +dependencies = [ + "bitflags", + "downcast-rs", + "libc", + "nix 0.24.2", + "scoped-tls", + "wayland-commons", + "wayland-scanner", + "wayland-sys", +] + +[[package]] +name = "wayland-commons" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8691f134d584a33a6606d9d717b95c4fa20065605f798a3f350d78dced02a902" +dependencies = [ + "nix 0.24.2", + "once_cell", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-cursor" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6865c6b66f13d6257bef1cd40cbfe8ef2f150fb8ebbdb1e8e873455931377661" +dependencies = [ + "nix 0.24.2", + "wayland-client", + "xcursor", +] + +[[package]] +name = "wayland-egl" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "402de949f81a012926d821a2d659f930694257e76dd92b6e0042ceb27be4107d" +dependencies = [ + "wayland-client", + "wayland-sys", +] + +[[package]] +name = "wayland-protocols" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b950621f9354b322ee817a23474e479b34be96c2e909c14f7bc0100e9a970bc6" +dependencies = [ + "bitflags", + "wayland-client", + "wayland-commons", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f4303d8fa22ab852f789e75a967f0a2cdc430a607751c0499bada3e451cbd53" +dependencies = [ + "proc-macro2", + "quote", + "xml-rs", +] + +[[package]] +name = "wayland-sys" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be12ce1a3c39ec7dba25594b97b42cb3195d54953ddb9d3d95a7c3902bc6e9d4" +dependencies = [ + "dlib", + "lazy_static", + "pkg-config", +] + +[[package]] +name = "web-sys" +version = "0.3.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed055ab27f941423197eb86b2035720b1a3ce40504df082cac2ecc6ed73335a1" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webbrowser" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a3cffdb686fbb24d9fb8f03a213803277ed2300f11026a3afe1f108dc021b" +dependencies = [ + "jni", + "ndk-glue 0.6.2", + "url", + "web-sys", + "widestring", + "winapi", +] + +[[package]] +name = "widestring" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17882f045410753661207383517a6f62ec3dbeb6a4ed2acce01f0728238d1983" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-wsapoll" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c17110f57155602a80dca10be03852116403c9ff3cd25b079d666f2aa3df6e" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57b543186b344cc61c85b5aab0d2e3adf4e0f99bc076eff9aa5927bcc0b8a647" +dependencies = [ + "windows_aarch64_msvc 0.37.0", + "windows_i686_gnu 0.37.0", + "windows_i686_msvc 0.37.0", + "windows_x86_64_gnu 0.37.0", + "windows_x86_64_msvc 0.37.0", +] + +[[package]] +name = "windows-sys" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +dependencies = [ + "windows_aarch64_msvc 0.36.1", + "windows_i686_gnu 0.36.1", + "windows_i686_msvc 0.36.1", + "windows_x86_64_gnu 0.36.1", + "windows_x86_64_msvc 0.36.1", +] + +[[package]] +name = "windows_aarch64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2623277cb2d1c216ba3b578c0f3cf9cdebeddb6e66b1b218bb33596ea7769c3a" + +[[package]] +name = "windows_i686_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" + +[[package]] +name = "windows_i686_gnu" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3925fd0b0b804730d44d4b6278c50f9699703ec49bcd628020f46f4ba07d9e1" + +[[package]] +name = "windows_i686_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" + +[[package]] +name = "windows_i686_msvc" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce907ac74fe331b524c1298683efbf598bb031bc84d5e274db2083696d07c57c" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2babfba0828f2e6b32457d5341427dcbb577ceef556273229959ac23a10af33d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4dd6dc7df2d84cf7b33822ed5b86318fb1781948e9663bacd047fc9dd52259d" + +[[package]] +name = "winit" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83a8f3e9d742401efcfe833b8f84960397482ff049cb7bf59a112e14a4be97f7" +dependencies = [ + "bitflags", + "cocoa", + "core-foundation", + "core-graphics", + "dispatch", + "instant", + "libc", + "log", + "mio", + "ndk 0.7.0", + "ndk-glue 0.7.0", + "objc", + "once_cell", + "parking_lot", + "percent-encoding", + "raw-window-handle 0.4.3", + "raw-window-handle 0.5.0", + "sctk-adwaita", + "smithay-client-toolkit", + "wasm-bindgen", + "wayland-client", + "wayland-protocols", + "web-sys", + "windows-sys", + "x11-dl", +] + +[[package]] +name = "wio" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d129932f4644ac2396cb456385cbf9e63b5b30c6e8dc4820bdca4eb082037a5" +dependencies = [ + "winapi", +] + +[[package]] +name = "x11-dl" +version = "2.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c83627bc137605acc00bb399c7b908ef460b621fc37c953db2b09f88c449ea6" +dependencies = [ + "lazy_static", + "libc", + "pkg-config", +] + +[[package]] +name = "x11rb" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e99be55648b3ae2a52342f9a870c0e138709a3493261ce9b469afe6e4df6d8a" +dependencies = [ + "gethostname", + "nix 0.22.3", + "winapi", + "winapi-wsapoll", +] + +[[package]] +name = "xcursor" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "463705a63313cd4301184381c5e8042f0a7e9b4bb63653f216311d4ae74690b7" +dependencies = [ + "nom", +] + +[[package]] +name = "xml-rs" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..6b62280 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "objdiff" +version = "0.1.0" +edition = "2021" +rust-version = "1.61" +authors = ["Luke Street "] +license = "MIT OR Apache-2.0" +repository = "https://github.com/encounter/objdiff" +readme = "README.md" +description = """ +A tool for decompilation projects. +""" + +[dependencies] +egui = "0.19.0" +eframe = { version = "0.19.0", features = ["persistence"] } # , "wgpu" +serde = { version = "1", features = ["derive"] } +anyhow = "1.0.63" +thiserror = "1.0.33" +flagset = "0.4.3" +object = "0.29.0" +notify = "5.0.0" +cwdemangle = "0.1.1" +log = "0.4.17" +rfd = { version = "0.10.0" } # , default-features = false, features = ['xdg-portal'] +egui_extras = "0.19.0" +ppc750cl = { git = "https://github.com/terorie/ppc750cl" } + +# native: +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +tracing-subscriber = "0.3" + +# web: +[target.'cfg(target_arch = "wasm32")'.dependencies] +console_error_panic_hook = "0.1.6" +tracing-wasm = "0.2" diff --git a/README.md b/README.md new file mode 100644 index 0000000..4028ae2 --- /dev/null +++ b/README.md @@ -0,0 +1,28 @@ +# objdiff [![Build Status]][actions] + +[Build Status]: https://github.com/encounter/objdiff/actions/workflows/build.yaml/badge.svg +[actions]: https://github.com/encounter/objdiff/actions + +A tool for decompilation projects. + +Currently supports: +- PowerPC 750CL (GameCube & Wii) + +**WARNING:** Very early & unstable. + +![Screenshot](assets/screen-diff.png) + +### License + +Licensed under either of + +* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) +* MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any +additional terms or conditions. diff --git a/assets/screen-diff.png b/assets/screen-diff.png new file mode 100644 index 0000000..de775eb Binary files /dev/null and b/assets/screen-diff.png differ diff --git a/deny.toml b/deny.toml new file mode 100644 index 0000000..a1806d7 --- /dev/null +++ b/deny.toml @@ -0,0 +1,210 @@ +# This template contains all of the possible sections and their default values + +# Note that all fields that take a lint level have these possible values: +# * deny - An error will be produced and the check will fail +# * warn - A warning will be produced, but the check will not fail +# * allow - No warning or error will be produced, though in some cases a note +# will be + +# The values provided in this template are the default values that will be used +# when any section or field is not specified in your own configuration + +# If 1 or more target triples (and optionally, target_features) are specified, +# only the specified targets will be checked when running `cargo deny check`. +# This means, if a particular package is only ever used as a target specific +# dependency, such as, for example, the `nix` crate only being used via the +# `target_family = "unix"` configuration, that only having windows targets in +# this list would mean the nix crate, as well as any of its exclusive +# dependencies not shared by any other crates, would be ignored, as the target +# list here is effectively saying which targets you are building for. +targets = [ + # The triple can be any string, but only the target triples built in to + # rustc (as of 1.40) can be checked against actual config expressions + #{ triple = "x86_64-unknown-linux-musl" }, + # You can also specify which target_features you promise are enabled for a + # particular target. target_features are currently not validated against + # the actual valid features supported by the target architecture. + #{ triple = "wasm32-unknown-unknown", features = ["atomics"] }, +] + +# This section is considered when running `cargo deny check advisories` +# More documentation for the advisories section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html +[advisories] +# The path where the advisory database is cloned/fetched into +db-path = "~/.cargo/advisory-db" +# The url(s) of the advisory databases to use +db-urls = ["https://github.com/rustsec/advisory-db"] +# The lint level for security vulnerabilities +vulnerability = "deny" +# The lint level for unmaintained crates +unmaintained = "warn" +# The lint level for crates that have been yanked from their source registry +yanked = "warn" +# The lint level for crates with security notices. Note that as of +# 2019-12-17 there are no security notice advisories in +# https://github.com/rustsec/advisory-db +notice = "warn" +# A list of advisory IDs to ignore. Note that ignored advisories will still +# output a note when they are encountered. +ignore = [ + #"RUSTSEC-0000-0000", +] +# Threshold for security vulnerabilities, any vulnerability with a CVSS score +# lower than the range specified will be ignored. Note that ignored advisories +# will still output a note when they are encountered. +# * None - CVSS Score 0.0 +# * Low - CVSS Score 0.1 - 3.9 +# * Medium - CVSS Score 4.0 - 6.9 +# * High - CVSS Score 7.0 - 8.9 +# * Critical - CVSS Score 9.0 - 10.0 +#severity-threshold = + +# This section is considered when running `cargo deny check licenses` +# More documentation for the licenses section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html +[licenses] +# The lint level for crates which do not have a detectable license +unlicensed = "deny" +# List of explictly allowed licenses +# See https://spdx.org/licenses/ for list of possible licenses +# [possible values: any SPDX 3.11 short identifier (+ optional exception)]. +allow = [ + "MIT", + "Apache-2.0", + "ISC", + "BSD-2-Clause", + "BSD-3-Clause", + "BSL-1.0", + "CC0-1.0", + "MPL-2.0", + "Unicode-DFS-2016", + "Zlib", +] +# List of explictly disallowed licenses +# See https://spdx.org/licenses/ for list of possible licenses +# [possible values: any SPDX 3.11 short identifier (+ optional exception)]. +deny = [ + #"Nokia", +] +# Lint level for licenses considered copyleft +copyleft = "warn" +# Blanket approval or denial for OSI-approved or FSF Free/Libre licenses +# * both - The license will be approved if it is both OSI-approved *AND* FSF +# * either - The license will be approved if it is either OSI-approved *OR* FSF +# * osi-only - The license will be approved if is OSI-approved *AND NOT* FSF +# * fsf-only - The license will be approved if is FSF *AND NOT* OSI-approved +# * neither - This predicate is ignored and the default lint level is used +allow-osi-fsf-free = "neither" +# Lint level used when no other predicates are matched +# 1. License isn't in the allow or deny lists +# 2. License isn't copyleft +# 3. License isn't OSI/FSF, or allow-osi-fsf-free = "neither" +default = "deny" +# The confidence threshold for detecting a license from license text. +# The higher the value, the more closely the license text must be to the +# canonical license text of a valid SPDX license file. +# [possible values: any between 0.0 and 1.0]. +confidence-threshold = 0.8 +# Allow 1 or more licenses on a per-crate basis, so that particular licenses +# aren't accepted for every possible crate as with the normal allow list +exceptions = [ + # Each entry is the crate and version constraint, and its specific allow + # list + #{ allow = ["Zlib"], name = "adler32", version = "*" }, +] + +# Some crates don't have (easily) machine readable licensing information, +# adding a clarification entry for it allows you to manually specify the +# licensing information +#[[licenses.clarify]] +# The name of the crate the clarification applies to +#name = "ring" +# The optional version constraint for the crate +#version = "*" +# The SPDX expression for the license requirements of the crate +#expression = "MIT AND ISC AND OpenSSL" +# One or more files in the crate's source used as the "source of truth" for +# the license expression. If the contents match, the clarification will be used +# when running the license check, otherwise the clarification will be ignored +# and the crate will be checked normally, which may produce warnings or errors +# depending on the rest of your configuration +#license-files = [ + # Each entry is a crate relative path, and the (opaque) hash of its contents + #{ path = "LICENSE", hash = 0xbd0eed23 } +#] + +[licenses.private] +# If true, ignores workspace crates that aren't published, or are only +# published to private registries +ignore = false +# One or more private registries that you might publish crates to, if a crate +# is only published to private registries, and ignore is true, the crate will +# not have its license(s) checked +registries = [ + #"https://sekretz.com/registry +] + +# This section is considered when running `cargo deny check bans`. +# More documentation about the 'bans' section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html +[bans] +# Lint level for when multiple versions of the same crate are detected +multiple-versions = "warn" +# Lint level for when a crate version requirement is `*` +wildcards = "allow" +# The graph highlighting used when creating dotgraphs for crates +# with multiple versions +# * lowest-version - The path to the lowest versioned duplicate is highlighted +# * simplest-path - The path to the version with the fewest edges is highlighted +# * all - Both lowest-version and simplest-path are used +highlight = "all" +# List of crates that are allowed. Use with care! +allow = [ + #{ name = "ansi_term", version = "=0.11.0" }, +] +# List of crates to deny +deny = [ + # Each entry the name of a crate and a version range. If version is + # not specified, all versions will be matched. + #{ name = "ansi_term", version = "=0.11.0" }, + # + # Wrapper crates can optionally be specified to allow the crate when it + # is a direct dependency of the otherwise banned crate + #{ name = "ansi_term", version = "=0.11.0", wrappers = [] }, +] +# Certain crates/versions that will be skipped when doing duplicate detection. +skip = [ + #{ name = "ansi_term", version = "=0.11.0" }, +] +# Similarly to `skip` allows you to skip certain crates during duplicate +# detection. Unlike skip, it also includes the entire tree of transitive +# dependencies starting at the specified crate, up to a certain depth, which is +# by default infinite +skip-tree = [ + #{ name = "ansi_term", version = "=0.11.0", depth = 20 }, +] + +# This section is considered when running `cargo deny check sources`. +# More documentation about the 'sources' section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html +[sources] +# Lint level for what to happen when a crate from a crate registry that is not +# in the allow list is encountered +unknown-registry = "warn" +# Lint level for what to happen when a crate from a git repository that is not +# in the allow list is encountered +unknown-git = "warn" +# List of URLs for allowed crate registries. Defaults to the crates.io index +# if not specified. If it is specified but empty, no registries are allowed. +allow-registry = ["https://github.com/rust-lang/crates.io-index"] +# List of URLs for allowed Git repositories +allow-git = [] + +[sources.allow-org] +# 1 or more github.com organizations to allow git sources for +github = ["encounter", "terorie"] +# 1 or more gitlab.com organizations to allow git sources for +#gitlab = [""] +# 1 or more bitbucket.org organizations to allow git sources for +#bitbucket = [""] diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..0a9eda5 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,8 @@ +fn_single_line = true +group_imports = "StdExternalCrate" +imports_granularity = "Crate" +overflow_delimited_expr = true +reorder_impl_items = true +use_field_init_shorthand = true +use_small_heuristics = "Max" +where_single_line = true diff --git a/src/app.rs b/src/app.rs new file mode 100644 index 0000000..24617d0 --- /dev/null +++ b/src/app.rs @@ -0,0 +1,266 @@ +use std::{ + default::Default, + ffi::OsStr, + path::{Path, PathBuf}, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, RwLock, + }, + time::Duration, +}; + +use eframe::Frame; +use notify::{RecursiveMode, Watcher}; + +use crate::{ + jobs::{ + build::{queue_build, BuildResult}, + Job, JobResult, JobState, + }, + views::{ + config::config_ui, function_diff::function_diff_ui, jobs::jobs_ui, + symbol_diff::symbol_diff_ui, + }, +}; + +#[derive(Default, Eq, PartialEq)] +pub enum View { + #[default] + SymbolDiff, + FunctionDiff, +} + +#[derive(Default, serde::Deserialize, serde::Serialize)] +#[serde(default)] +pub struct ViewState { + #[serde(skip)] + pub jobs: Vec, + #[serde(skip)] + pub build: Option>, + #[serde(skip)] + pub highlighted_symbol: Option, + #[serde(skip)] + pub selected_symbol: Option, + #[serde(skip)] + pub current_view: View, + // Config + pub reverse_fn_order: bool, +} + +#[derive(Default, Clone, serde::Deserialize, serde::Serialize)] +#[serde(default)] +pub struct AppConfig { + pub project_dir: Option, + pub build_asm_dir: Option, + pub build_src_dir: Option, + pub build_obj: Option, + #[serde(skip)] + pub project_dir_change: bool, +} + +/// We derive Deserialize/Serialize so we can persist app state on shutdown. +#[derive(serde::Deserialize, serde::Serialize)] +#[serde(default)] +pub struct App { + view_state: ViewState, + #[serde(skip)] + config: Arc>, + #[serde(skip)] + modified: Arc, + #[serde(skip)] + watcher: Option, +} + +impl Default for App { + fn default() -> Self { + Self { + view_state: ViewState::default(), + config: Arc::new(Default::default()), + modified: Arc::new(Default::default()), + watcher: None, + } + } +} + +const CONFIG_KEY: &str = "app_config"; + +impl App { + /// Called once before the first frame. + pub fn new(cc: &eframe::CreationContext<'_>) -> Self { + // This is also where you can customized the look at feel of egui using + // `cc.egui_ctx.set_visuals` and `cc.egui_ctx.set_fonts`. + + // Load previous app state (if any). + // Note that you must enable the `persistence` feature for this to work. + if let Some(storage) = cc.storage { + let mut app: App = eframe::get_value(storage, eframe::APP_KEY).unwrap_or_default(); + let mut config: AppConfig = eframe::get_value(storage, CONFIG_KEY).unwrap_or_default(); + if config.project_dir.is_some() { + config.project_dir_change = true; + } + app.config = Arc::new(RwLock::new(config)); + app + } else { + Self::default() + } + } +} + +impl eframe::App for App { + /// Called each time the UI needs repainting, which may be many times per second. + /// Put your widgets into a `SidePanel`, `TopPanel`, `CentralPanel`, `Window` or `Area`. + fn update(&mut self, ctx: &egui::Context, frame: &mut Frame) { + let Self { config, view_state, .. } = self; + + egui::TopBottomPanel::top("top_panel").show(ctx, |ui| { + egui::menu::bar(ui, |ui| { + ui.menu_button("File", |ui| { + if ui.button("Quit").clicked() { + frame.close(); + } + }); + }); + }); + + if view_state.current_view == View::FunctionDiff + && matches!(&view_state.build, Some(b) if b.first_status.success && b.second_status.success) + { + egui::SidePanel::left("side_panel").show(ctx, |ui| { + if ui.button("Back").clicked() { + view_state.current_view = View::SymbolDiff; + } + ui.separator(); + jobs_ui(ui, view_state); + }); + + egui::CentralPanel::default().show(ctx, |ui| { + function_diff_ui(ui, view_state); + }); + } else { + egui::SidePanel::left("side_panel").show(ctx, |ui| { + ui.heading("Config"); + config_ui(ui, config, view_state); + jobs_ui(ui, view_state); + }); + + egui::CentralPanel::default().show(ctx, |ui| { + symbol_diff_ui(ui, view_state); + }); + } + + if view_state.jobs.iter().any(|job| { + if let Some(handle) = &job.handle { + return !handle.is_finished(); + } + false + }) { + ctx.request_repaint(); + } else { + ctx.request_repaint(); + ctx.request_repaint_after(Duration::from_millis(100)); + } + } + + /// Called by the frame work to save state before shutdown. + fn save(&mut self, storage: &mut dyn eframe::Storage) { + if let Ok(config) = self.config.read() { + eframe::set_value(storage, CONFIG_KEY, &*config); + } + eframe::set_value(storage, eframe::APP_KEY, self); + } + + fn post_rendering(&mut self, _window_size_px: [u32; 2], _frame: &Frame) { + for job in &mut self.view_state.jobs { + if let Some(handle) = &job.handle { + if !handle.is_finished() { + continue; + } + match job.handle.take().unwrap().join() { + Ok(result) => { + log::info!("Job {} finished", job.id); + match result { + JobResult::None => { + if let Some(err) = &job.status.read().unwrap().error { + log::error!("{:?}", err); + } + } + JobResult::Build(state) => { + self.view_state.build = Some(state); + } + } + } + Err(e) => { + log::error!("Failed to join job handle: {:?}", e); + } + } + } + } + if self.view_state.jobs.iter().any(|v| v.should_remove) { + let mut i = 0; + while i < self.view_state.jobs.len() { + let job = &self.view_state.jobs[i]; + if job.should_remove && job.handle.is_none() { + self.view_state.jobs.remove(i); + } else { + i += 1; + } + } + } + + if let Ok(mut config) = self.config.write() { + if config.project_dir_change { + drop(self.watcher.take()); + if let Some(project_dir) = &config.project_dir { + match create_watcher(self.modified.clone(), project_dir) { + Ok(watcher) => self.watcher = Some(watcher), + Err(e) => eprintln!("Failed to create watcher: {}", e), + } + config.project_dir_change = false; + self.modified.store(true, Ordering::Relaxed); + } + } + + if let Some(build_obj) = &config.build_obj { + if self.modified.load(Ordering::Relaxed) { + if !self + .view_state + .jobs + .iter() + .any(|j| j.job_type == Job::Build && j.handle.is_some()) + { + self.view_state + .jobs + .push(queue_build(build_obj.clone(), self.config.clone())); + } + self.modified.store(false, Ordering::Relaxed); + } + } + } + } +} + +fn create_watcher( + modified: Arc, + project_dir: &Path, +) -> notify::Result { + let mut watcher = + notify::recommended_watcher(move |res: notify::Result| match res { + Ok(event) => { + if matches!(event.kind, notify::EventKind::Modify(..)) { + let watch_extensions = &[ + Some(OsStr::new("c")), + Some(OsStr::new("cp")), + Some(OsStr::new("cpp")), + Some(OsStr::new("h")), + Some(OsStr::new("hpp")), + ]; + if event.paths.iter().any(|p| watch_extensions.contains(&p.extension())) { + modified.store(true, Ordering::Relaxed); + } + } + } + Err(e) => println!("watch error: {:?}", e), + })?; + watcher.watch(project_dir, RecursiveMode::Recursive)?; + Ok(watcher) +} diff --git a/src/diff.rs b/src/diff.rs new file mode 100644 index 0000000..21980d5 --- /dev/null +++ b/src/diff.rs @@ -0,0 +1,396 @@ +use std::collections::BTreeMap; + +use anyhow::Result; +use ppc750cl::{disasm_iter, Argument}; + +use crate::{ + editops::{editops_find, LevEditType}, + obj::{ + ObjInfo, ObjIns, ObjInsArg, ObjInsArgDiff, ObjInsBranchFrom, ObjInsBranchTo, ObjInsDiff, + ObjInsDiffKind, ObjReloc, ObjRelocKind, ObjSection, ObjSectionKind, ObjSymbol, + ObjSymbolFlags, + }, +}; + +// Relative relocation, can be Simm or BranchDest +fn is_relative_arg(arg: &ObjInsArg) -> bool { + matches!(arg, ObjInsArg::Arg(arg) if matches!(arg, Argument::Simm(_) | Argument::BranchDest(_))) +} + +// Relative or absolute relocation, can be Uimm, Simm or Offset +fn is_rel_abs_arg(arg: &ObjInsArg) -> bool { + matches!(arg, ObjInsArg::Arg(arg) if matches!(arg, Argument::Uimm(_) | Argument::Simm(_) | Argument::Offset(_))) +} + +fn is_offset_arg(arg: &ObjInsArg) -> bool { matches!(arg, ObjInsArg::Arg(Argument::Offset(_))) } + +fn process_code(data: &[u8], address: u64, relocs: &[ObjReloc]) -> Result<(Vec, Vec)> { + let ins_count = data.len() / 4; + let mut ops = Vec::::with_capacity(ins_count); + let mut insts = Vec::::with_capacity(ins_count); + for mut ins in disasm_iter(data, address as u32) { + let reloc = relocs.iter().find(|r| (r.address as u32 & !3) == ins.addr); + if let Some(reloc) = reloc { + // Zero out relocations + ins.code = match reloc.kind { + ObjRelocKind::PpcEmbSda21 => ins.code & !0x1FFFFF, + ObjRelocKind::PpcRel24 => ins.code & !0x3FFFFFC, + ObjRelocKind::PpcRel14 => ins.code & !0xFFFC, + ObjRelocKind::PpcAddr16Hi + | ObjRelocKind::PpcAddr16Ha + | ObjRelocKind::PpcAddr16Lo => ins.code & !0xFFFF, + _ => ins.code, + }; + } + let simplified = ins.simplified(); + let mut args: Vec = + simplified.args.iter().map(|a| ObjInsArg::Arg(a.clone())).collect(); + if let Some(reloc) = reloc { + match reloc.kind { + ObjRelocKind::PpcEmbSda21 => { + args = vec![args[0].clone(), ObjInsArg::Reloc]; + } + ObjRelocKind::PpcRel24 | ObjRelocKind::PpcRel14 => { + let arg = args + .iter_mut() + .rfind(|a| is_relative_arg(a)) + .ok_or_else(|| anyhow::Error::msg("Failed to locate rel arg for reloc"))?; + *arg = ObjInsArg::Reloc; + } + ObjRelocKind::PpcAddr16Hi + | ObjRelocKind::PpcAddr16Ha + | ObjRelocKind::PpcAddr16Lo => { + let arg = args.iter_mut().rfind(|a| is_rel_abs_arg(a)).ok_or_else(|| { + anyhow::Error::msg("Failed to locate rel/abs arg for reloc") + })?; + *arg = + if is_offset_arg(arg) { ObjInsArg::RelocOffset } else { ObjInsArg::Reloc }; + } + _ => {} + } + } + ops.push(simplified.ins.op as u8); + let suffix = simplified.ins.suffix(); + insts.push(ObjIns { + ins: simplified.ins, + mnemonic: format!("{}{}", simplified.mnemonic, suffix), + args, + reloc: reloc.cloned(), + }); + } + Ok((ops, insts)) +} + +pub fn diff_code( + left_data: &[u8], + right_data: &[u8], + left_symbol: &mut ObjSymbol, + right_symbol: &mut ObjSymbol, + left_relocs: &[ObjReloc], + right_relocs: &[ObjReloc], +) -> Result<()> { + let left_code = + &left_data[left_symbol.address as usize..(left_symbol.address + left_symbol.size) as usize]; + let (left_ops, left_insts) = process_code(left_code, left_symbol.address, left_relocs)?; + let right_code = &right_data + [right_symbol.address as usize..(right_symbol.address + right_symbol.size) as usize]; + let (right_ops, right_insts) = process_code(right_code, right_symbol.address, right_relocs)?; + + let mut left_diff = Vec::::new(); + let mut right_diff = Vec::::new(); + let edit_ops = editops_find(&left_ops, &right_ops); + + { + let mut op_iter = edit_ops.iter(); + let mut left_iter = left_insts.iter(); + let mut right_iter = right_insts.iter(); + let mut cur_op = op_iter.next(); + let mut cur_left = left_iter.next(); + let mut cur_right = right_iter.next(); + while let Some(op) = cur_op { + let left_addr = op.first_start as u32 * 4; + let right_addr = op.second_start as u32 * 4; + while let (Some(left), Some(right)) = (cur_left, cur_right) { + if (left.ins.addr - left_symbol.address as u32) < left_addr { + left_diff.push(ObjInsDiff { ins: Some(left.clone()), ..ObjInsDiff::default() }); + right_diff + .push(ObjInsDiff { ins: Some(right.clone()), ..ObjInsDiff::default() }); + } else { + break; + } + cur_left = left_iter.next(); + cur_right = right_iter.next(); + } + if let (Some(left), Some(right)) = (cur_left, cur_right) { + if (left.ins.addr - left_symbol.address as u32) != left_addr { + return Err(anyhow::Error::msg("Instruction address mismatch (left)")); + } + if (right.ins.addr - right_symbol.address as u32) != right_addr { + return Err(anyhow::Error::msg("Instruction address mismatch (right)")); + } + match op.op_type { + LevEditType::Replace => { + left_diff + .push(ObjInsDiff { ins: Some(left.clone()), ..ObjInsDiff::default() }); + right_diff + .push(ObjInsDiff { ins: Some(right.clone()), ..ObjInsDiff::default() }); + cur_left = left_iter.next(); + cur_right = right_iter.next(); + } + LevEditType::Insert => { + left_diff.push(ObjInsDiff::default()); + right_diff + .push(ObjInsDiff { ins: Some(right.clone()), ..ObjInsDiff::default() }); + cur_right = right_iter.next(); + } + LevEditType::Delete => { + left_diff + .push(ObjInsDiff { ins: Some(left.clone()), ..ObjInsDiff::default() }); + right_diff.push(ObjInsDiff::default()); + cur_left = left_iter.next(); + } + LevEditType::Keep => unreachable!(), + } + } else { + break; + } + cur_op = op_iter.next(); + } + // Finalize + while cur_left.is_some() || cur_right.is_some() { + left_diff.push(ObjInsDiff { ins: cur_left.cloned(), ..ObjInsDiff::default() }); + right_diff.push(ObjInsDiff { ins: cur_right.cloned(), ..ObjInsDiff::default() }); + cur_left = left_iter.next(); + cur_right = right_iter.next(); + } + } + + resolve_branches(&mut left_diff); + resolve_branches(&mut right_diff); + + let mut diff_state = InsDiffState::default(); + for (left, right) in left_diff.iter_mut().zip(right_diff.iter_mut()) { + let result = compare_ins(left, right, &mut diff_state)?; + left.kind = result.kind; + right.kind = result.kind; + left.arg_diff = result.left_args_diff; + right.arg_diff = result.right_args_diff; + } + + let total = left_insts.len(); + let percent = ((total - diff_state.diff_count) as f32 / total as f32) * 100.0; + left_symbol.match_percent = percent; + right_symbol.match_percent = percent; + + left_symbol.instructions = left_diff; + right_symbol.instructions = right_diff; + + Ok(()) +} + +fn resolve_branches(vec: &mut [ObjInsDiff]) { + let mut branch_idx = 0usize; + // Map addresses to indices + let mut addr_map = BTreeMap::::new(); + for (i, ins_diff) in vec.iter().enumerate() { + if let Some(ins) = &ins_diff.ins { + addr_map.insert(ins.ins.addr, i); + } + } + // Generate branches + let mut branches = BTreeMap::::new(); + for (i, ins_diff) in vec.iter_mut().enumerate() { + if let Some(ins) = &ins_diff.ins { + if ins.ins.is_blr() || ins.reloc.is_some() { + continue; + } + if let Some(ins_idx) = ins.ins.branch_dest().and_then(|dest| addr_map.get(&dest)) { + if let Some(branch) = branches.get_mut(ins_idx) { + ins_diff.branch_to = + Some(ObjInsBranchTo { ins_idx: *ins_idx, branch_idx: branch.branch_idx }); + branch.ins_idx.push(i); + } else { + ins_diff.branch_to = Some(ObjInsBranchTo { ins_idx: *ins_idx, branch_idx }); + branches.insert(*ins_idx, ObjInsBranchFrom { ins_idx: vec![i], branch_idx }); + branch_idx += 1; + } + } + } + } + // Store branch from + for (i, branch) in branches { + vec[i].branch_from = Some(branch); + } +} + +fn reloc_eq(left_reloc: Option<&ObjReloc>, right_reloc: Option<&ObjReloc>) -> bool { + if let (Some(left), Some(right)) = (left_reloc, right_reloc) { + if left.kind != right.kind { + return false; + } + let name_matches = left.target.name == right.target.name; + match (&left.target_section, &right.target_section) { + (Some(sl), Some(sr)) => { + // Match if section and name or address match + sl == sr && (name_matches || left.target.address == right.target.address) + } + (Some(_), None) => false, + (None, Some(_)) => { + // Match if possibly stripped weak symbol + name_matches && right.target.flags.0.contains(ObjSymbolFlags::Weak) + } + (None, None) => name_matches, + } + } else { + false + } +} + +fn arg_eq( + left: &ObjInsArg, + right: &ObjInsArg, + left_diff: &ObjInsDiff, + right_diff: &ObjInsDiff, +) -> bool { + return match left { + ObjInsArg::Arg(l) => match right { + ObjInsArg::Arg(r) => match r { + Argument::BranchDest(_) => { + // Compare dest instruction idx after diffing + left_diff.branch_to.as_ref().map(|b| b.ins_idx) + == right_diff.branch_to.as_ref().map(|b| b.ins_idx) + } + _ => format!("{}", l) == format!("{}", r), + }, + _ => false, + }, + ObjInsArg::Reloc => { + matches!(right, ObjInsArg::Reloc) + && reloc_eq( + left_diff.ins.as_ref().and_then(|i| i.reloc.as_ref()), + right_diff.ins.as_ref().and_then(|i| i.reloc.as_ref()), + ) + } + ObjInsArg::RelocOffset => { + matches!(right, ObjInsArg::RelocOffset) + && reloc_eq( + left_diff.ins.as_ref().and_then(|i| i.reloc.as_ref()), + right_diff.ins.as_ref().and_then(|i| i.reloc.as_ref()), + ) + } + }; +} + +#[derive(Default)] +struct InsDiffState { + diff_count: usize, + left_arg_idx: usize, + right_arg_idx: usize, + left_args_idx: BTreeMap, + right_args_idx: BTreeMap, +} +#[derive(Default)] +struct InsDiffResult { + kind: ObjInsDiffKind, + left_args_diff: Vec>, + right_args_diff: Vec>, +} + +fn compare_ins( + left: &ObjInsDiff, + right: &ObjInsDiff, + state: &mut InsDiffState, +) -> Result { + let mut result = InsDiffResult::default(); + if let (Some(left_ins), Some(right_ins)) = (&left.ins, &right.ins) { + if left_ins.args.len() != right_ins.args.len() || left_ins.ins.op != right_ins.ins.op { + // Totally different op + result.kind = ObjInsDiffKind::Replace; + state.diff_count += 1; + return Ok(result); + } + if left_ins.mnemonic != right_ins.mnemonic { + // Same op but different mnemonic, still cmp args + result.kind = ObjInsDiffKind::OpMismatch; + state.diff_count += 1; + } + for (a, b) in left_ins.args.iter().zip(&right_ins.args) { + if arg_eq(a, b, left, right) { + result.left_args_diff.push(None); + result.right_args_diff.push(None); + } else { + if result.kind == ObjInsDiffKind::None { + result.kind = ObjInsDiffKind::ArgMismatch; + state.diff_count += 1; + } + let a_str = match a { + ObjInsArg::Arg(arg) => format!("{}", arg), + ObjInsArg::Reloc | ObjInsArg::RelocOffset => String::new(), + }; + let a_diff = if let Some(idx) = state.left_args_idx.get(&a_str) { + ObjInsArgDiff { idx: *idx } + } else { + let idx = state.left_arg_idx; + state.left_args_idx.insert(a_str, idx); + state.left_arg_idx += 1; + ObjInsArgDiff { idx } + }; + let b_str = match b { + ObjInsArg::Arg(arg) => format!("{}", arg), + ObjInsArg::Reloc | ObjInsArg::RelocOffset => String::new(), + }; + let b_diff = if let Some(idx) = state.right_args_idx.get(&b_str) { + ObjInsArgDiff { idx: *idx } + } else { + let idx = state.right_arg_idx; + state.right_args_idx.insert(b_str, idx); + state.right_arg_idx += 1; + ObjInsArgDiff { idx } + }; + result.left_args_diff.push(Some(a_diff)); + result.right_args_diff.push(Some(b_diff)); + } + } + } else if left.ins.is_some() { + result.kind = ObjInsDiffKind::Delete; + state.diff_count += 1; + } else { + result.kind = ObjInsDiffKind::Insert; + state.diff_count += 1; + } + Ok(result) +} + +fn find_section<'a>(obj: &'a mut ObjInfo, name: &str) -> Option<&'a mut ObjSection> { + obj.sections.iter_mut().find(|s| s.name == name) +} + +fn find_symbol<'a>(symbols: &'a mut [ObjSymbol], name: &str) -> Option<&'a mut ObjSymbol> { + symbols.iter_mut().find(|s| s.name == name) +} + +pub fn diff_objs(left: &mut ObjInfo, right: &mut ObjInfo) -> Result<()> { + for left_section in &mut left.sections { + if let Some(right_section) = find_section(right, &left_section.name) { + for left_symbol in &mut left_section.symbols { + if let Some(right_symbol) = + find_symbol(&mut right_section.symbols, &left_symbol.name) + { + left_symbol.diff_symbol = Some(right_symbol.name.clone()); + right_symbol.diff_symbol = Some(left_symbol.name.clone()); + if left_section.kind == ObjSectionKind::Code { + diff_code( + &left_section.data, + &right_section.data, + left_symbol, + right_symbol, + &left_section.relocations, + &right_section.relocations, + )?; + } + } + } + } + } + Ok(()) +} diff --git a/src/editops.rs b/src/editops.rs new file mode 100644 index 0000000..0e55a0b --- /dev/null +++ b/src/editops.rs @@ -0,0 +1,253 @@ +/// Adapted from https://crates.io/crates/rapidfuzz +// Copyright 2020 maxbachmann +// +// Permission is hereby granted, free of charge, to any +// person obtaining a copy of this software and associated +// documentation files (the "Software"), to deal in the +// Software without restriction, including without +// limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software +// is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice +// shall be included in all copies or substantial portions +// of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +#[derive(Debug, PartialEq, Eq, Copy, Clone)] +pub enum LevEditType { + Keep, + Replace, + Insert, + Delete, +} + +#[derive(Debug, PartialEq, Eq)] +pub struct LevEditOp { + pub op_type: LevEditType, /* editing operation type */ + pub first_start: usize, /* source block position */ + pub second_start: usize, /* destination position */ +} + +#[derive(Debug, PartialEq, Eq)] +pub struct LevMatchingBlock { + pub first_start: usize, + pub second_start: usize, + pub len: usize, +} + +pub fn editops_find(query: &[u8], choice: &[u8]) -> Vec { + let string_affix = Affix::find(query, choice); + + let first_string_len = string_affix.first_string_len; + let second_string_len = string_affix.second_string_len; + let prefix_len = string_affix.prefix_len; + let first_string = &query[prefix_len..prefix_len + first_string_len]; + let second_string = &choice[prefix_len..prefix_len + second_string_len]; + + let matrix_columns = first_string_len + 1; + let matrix_rows = second_string_len + 1; + + // TODO maybe use an actual matrix for readability + let mut cache_matrix: Vec = vec![0; matrix_rows * matrix_columns]; + for (i, elem) in cache_matrix.iter_mut().enumerate().take(matrix_rows) { + *elem = i; + } + for i in 1..matrix_columns { + cache_matrix[matrix_rows * i] = i; + } + + for (i, char1) in first_string.iter().enumerate() { + let mut prev = i * matrix_rows; + let current = prev + matrix_rows; + let mut x = i + 1; + for (p, char2p) in second_string.iter().enumerate() { + let mut c3 = cache_matrix[prev] + (char1 != char2p) as usize; + prev += 1; + x += 1; + if x >= c3 { + x = c3; + } + c3 = cache_matrix[prev] + 1; + if x > c3 { + x = c3; + } + cache_matrix[current + 1 + p] = x; + } + } + editops_from_cost_matrix( + first_string, + second_string, + matrix_columns, + matrix_rows, + prefix_len, + cache_matrix, + ) +} + +fn editops_from_cost_matrix( + string1: &[u8], + string2: &[u8], + len1: usize, + len2: usize, + prefix_len: usize, + cache_matrix: Vec, +) -> Vec { + let mut dir = 0; + + let mut ops: Vec = vec![]; + ops.reserve(cache_matrix[len1 * len2 - 1]); + + let mut i = len1 - 1; + let mut j = len2 - 1; + let mut p = len1 * len2 - 1; + + // let string1_chars: Vec = string1.chars().collect(); + // let string2_chars: Vec = string2.chars().collect(); + + //TODO this is still pretty ugly + while i > 0 || j > 0 { + let current_value = cache_matrix[p]; + + let op_type; + + if dir == -1 && j > 0 && current_value == cache_matrix[p - 1] + 1 { + op_type = LevEditType::Insert; + } else if dir == 1 && i > 0 && current_value == cache_matrix[p - len2] + 1 { + op_type = LevEditType::Delete; + } else if i > 0 + && j > 0 + && current_value == cache_matrix[p - len2 - 1] + && string1[i - 1] == string2[j - 1] + { + op_type = LevEditType::Keep; + } else if i > 0 && j > 0 && current_value == cache_matrix[p - len2 - 1] + 1 { + op_type = LevEditType::Replace; + } + /* we can't turn directly from -1 to 1, in this case it would be better + * to go diagonally, but check it (dir == 0) */ + else if dir == 0 && j > 0 && current_value == cache_matrix[p - 1] + 1 { + op_type = LevEditType::Insert; + } else if dir == 0 && i > 0 && current_value == cache_matrix[p - len2] + 1 { + op_type = LevEditType::Delete; + } else { + panic!("something went terribly wrong"); + } + + match op_type { + LevEditType::Insert => { + j -= 1; + p -= 1; + dir = -1; + } + LevEditType::Delete => { + i -= 1; + p -= len2; + dir = 1; + } + LevEditType::Replace => { + i -= 1; + j -= 1; + p -= len2 + 1; + dir = 0; + } + LevEditType::Keep => { + i -= 1; + j -= 1; + p -= len2 + 1; + dir = 0; + /* LevEditKeep does not has to be stored */ + continue; + } + }; + + let edit_op = + LevEditOp { op_type, first_start: i + prefix_len, second_start: j + prefix_len }; + ops.insert(0, edit_op); + } + + ops +} + +pub struct Affix { + pub prefix_len: usize, + pub first_string_len: usize, + pub second_string_len: usize, +} + +impl Affix { + pub fn find(first_string: &[u8], second_string: &[u8]) -> Affix { + // remove common prefix and suffix (linear vs square runtime for levensthein) + let mut first_iter = first_string.iter(); + let mut second_iter = second_string.iter(); + + let mut limit_start = 0; + + let mut first_iter_char = first_iter.next(); + let mut second_iter_char = second_iter.next(); + while first_iter_char.is_some() && first_iter_char == second_iter_char { + first_iter_char = first_iter.next(); + second_iter_char = second_iter.next(); + limit_start += 1; + } + + // save char since the iterator was already consumed + let first_iter_cache = first_iter_char; + let second_iter_cache = second_iter_char; + + if second_iter_char.is_some() && first_iter_char.is_some() { + first_iter_char = first_iter.next_back(); + second_iter_char = second_iter.next_back(); + while first_iter_char.is_some() && first_iter_char == second_iter_char { + first_iter_char = first_iter.next_back(); + second_iter_char = second_iter.next_back(); + } + } + + match (first_iter_char, second_iter_char) { + (None, None) => { + // characters might not match even though they were consumed + let remaining_char = (first_iter_cache != second_iter_cache) as usize; + Affix { + prefix_len: limit_start, + first_string_len: remaining_char, + second_string_len: remaining_char, + } + } + (None, _) => { + let remaining_char = + (first_iter_cache.is_some() && first_iter_cache != second_iter_char) as usize; + Affix { + prefix_len: limit_start, + first_string_len: remaining_char, + second_string_len: second_iter.count() + 1 + remaining_char, + } + } + (_, None) => { + let remaining_char = + (second_iter_cache.is_some() && second_iter_cache != first_iter_char) as usize; + Affix { + prefix_len: limit_start, + first_string_len: first_iter.count() + 1 + remaining_char, + second_string_len: remaining_char, + } + } + _ => Affix { + prefix_len: limit_start, + first_string_len: first_iter.count() + 2, + second_string_len: second_iter.count() + 2, + }, + } + } +} diff --git a/src/elf.rs b/src/elf.rs new file mode 100644 index 0000000..30a8f97 --- /dev/null +++ b/src/elf.rs @@ -0,0 +1,242 @@ +use std::{fs, path::Path}; + +use anyhow::{Context, Result}; +use cwdemangle::demangle; +use flagset::Flags; +use object::{ + Object, ObjectSection, ObjectSymbol, RelocationKind, RelocationTarget, SectionKind, SymbolKind, + SymbolSection, +}; + +use crate::obj::{ + ObjInfo, ObjReloc, ObjRelocKind, ObjSection, ObjSectionKind, ObjSymbol, ObjSymbolFlagSet, + ObjSymbolFlags, +}; + +fn to_obj_section_kind(kind: SectionKind) -> ObjSectionKind { + match kind { + SectionKind::Text => ObjSectionKind::Code, + SectionKind::Data | SectionKind::ReadOnlyData => ObjSectionKind::Data, + SectionKind::UninitializedData => ObjSectionKind::Bss, + _ => panic!("Unhandled section kind {:?}", kind), + } +} + +fn to_obj_symbol(symbol: &object::Symbol<'_, '_>) -> Result { + let mut name = symbol.name().context("Failed to process symbol name")?; + if name.is_empty() { + println!("Found empty sym: {:?}", symbol); + name = "?"; + } + let mut flags = ObjSymbolFlagSet(ObjSymbolFlags::none()); + if symbol.is_global() { + flags = ObjSymbolFlagSet(flags.0 | ObjSymbolFlags::Global); + } + if symbol.is_local() { + flags = ObjSymbolFlagSet(flags.0 | ObjSymbolFlags::Local); + } + if symbol.is_common() { + flags = ObjSymbolFlagSet(flags.0 | ObjSymbolFlags::Common); + } + if symbol.is_weak() { + flags = ObjSymbolFlagSet(flags.0 | ObjSymbolFlags::Weak); + } + Ok(ObjSymbol { + name: name.to_string(), + demangled_name: demangle(name), + address: symbol.address(), + size: symbol.size(), + size_known: symbol.size() != 0, + flags, + diff_symbol: None, + instructions: vec![], + match_percent: 0.0, + }) +} + +const R_PPC_ADDR16_LO: u32 = 4; +const R_PPC_ADDR16_HI: u32 = 5; +const R_PPC_ADDR16_HA: u32 = 6; +const R_PPC_REL24: u32 = 10; +const R_PPC_REL14: u32 = 11; +const R_PPC_EMB_SDA21: u32 = 109; + +fn filter_sections(obj_file: &object::File<'_>) -> Result> { + let mut result = Vec::::new(); + for section in obj_file.sections() { + if section.size() == 0 { + continue; + } + if section.kind() != SectionKind::Text + && section.kind() != SectionKind::Data + && section.kind() != SectionKind::ReadOnlyData + && section.kind() != SectionKind::UninitializedData + { + continue; + } + let name = section.name().context("Failed to process section name")?; + let data = section.data().context("Failed to read section data")?; + result.push(ObjSection { + name: name.to_string(), + kind: to_obj_section_kind(section.kind()), + address: section.address(), + size: section.size(), + data: data.to_vec(), + index: section.index().0, + symbols: Vec::new(), + relocations: Vec::new(), + }); + } + result.sort_by(|a, b| a.name.cmp(&b.name)); + Ok(result) +} + +fn symbols_by_section(obj_file: &object::File<'_>, section: &ObjSection) -> Result> { + let mut result = Vec::::new(); + for symbol in obj_file.symbols() { + if symbol.kind() == SymbolKind::Section { + continue; + } + if let Some(index) = symbol.section().index() { + if index.0 == section.index { + if symbol.is_local() && section.kind == ObjSectionKind::Code { + // TODO strip local syms in diff? + let name = symbol.name().context("Failed to process symbol name")?; + if name.starts_with("lbl_") { + continue; + } + } + result.push(to_obj_symbol(&symbol)?); + } + } + } + result.sort_by_key(|v| v.address); + let mut iter = result.iter_mut().peekable(); + while let Some(symbol) = iter.next() { + if symbol.size == 0 { + if let Some(next_symbol) = iter.peek() { + symbol.size = next_symbol.address - symbol.address; + } else { + symbol.size = (section.address + section.size) - symbol.address; + } + } + } + Ok(result) +} + +fn common_symbols(obj_file: &object::File<'_>) -> Result> { + let mut result = Vec::::new(); + for symbol in obj_file.symbols() { + if symbol.is_common() { + result.push(to_obj_symbol(&symbol)?); + } + } + Ok(result) +} + +fn locate_section_symbol( + obj_file: &object::File<'_>, + target: &object::Symbol<'_, '_>, + address: u64, +) -> Result { + let section_index = + target.section_index().ok_or_else(|| anyhow::Error::msg("Unknown section index"))?; + for symbol in obj_file.symbols() { + if !matches!(symbol.section_index(), Some(idx) if idx == section_index) { + continue; + } + if symbol.kind() == SymbolKind::Section || symbol.address() != address { + continue; + } + return to_obj_symbol(&symbol); + } + Err(anyhow::Error::msg("Failed to locate reloc offset sym")) +} + +fn relocations_by_section( + obj_file: &object::File<'_>, + section: &ObjSection, +) -> Result> { + let obj_section = obj_file + .section_by_name(§ion.name) + .ok_or_else(|| anyhow::Error::msg("Failed to locate section"))?; + let mut relocations = Vec::::new(); + for (address, reloc) in obj_section.relocations() { + let symbol = match reloc.target() { + RelocationTarget::Symbol(idx) => obj_file + .symbol_by_index(idx) + .context("Failed to locate relocation target symbol")?, + _ => { + return Err(anyhow::Error::msg(format!( + "Unhandled relocation target: {:?}", + reloc.target() + ))); + } + }; + let kind = match reloc.kind() { + RelocationKind::Absolute => ObjRelocKind::Absolute, + RelocationKind::Elf(kind) => match kind { + R_PPC_ADDR16_LO => ObjRelocKind::PpcAddr16Lo, + R_PPC_ADDR16_HI => ObjRelocKind::PpcAddr16Hi, + R_PPC_ADDR16_HA => ObjRelocKind::PpcAddr16Ha, + R_PPC_REL24 => ObjRelocKind::PpcRel24, + R_PPC_REL14 => ObjRelocKind::PpcRel14, + R_PPC_EMB_SDA21 => ObjRelocKind::PpcEmbSda21, + _ => { + return Err(anyhow::Error::msg(format!( + "Unhandled ELF relocation type: {}", + kind + ))) + } + }, + _ => { + return Err(anyhow::Error::msg(format!( + "Unhandled relocation type: {:?}", + reloc.kind() + ))) + } + }; + let target_section = match symbol.section() { + SymbolSection::Common => Some(".comm".to_string()), + SymbolSection::Section(idx) => obj_file + .section_by_index(idx) + .map(|s| s.name().map(|s| s.to_string()).ok()) + .ok() + .flatten(), + _ => None, + }; + // println!("Reloc: {:?}", reloc.addend()); + let target = match symbol.kind() { + SymbolKind::Text | SymbolKind::Data | SymbolKind::Unknown => to_obj_symbol(&symbol), + SymbolKind::Section => { + let addend = reloc.addend(); + if addend < 0 { + Err(anyhow::Error::msg(format!("Negative addend in section reloc: {}", addend))) + } else { + locate_section_symbol(obj_file, &symbol, addend as u64) + } + } + _ => Err(anyhow::Error::msg(format!( + "Unhandled relocation symbol type {:?}", + symbol.kind() + ))), + }?; + relocations.push(ObjReloc { kind, address, target, target_section }); + } + Ok(relocations) +} + +pub fn read(obj_path: &Path) -> Result { + let bin_data = fs::read(obj_path)?; + let obj_file = object::File::parse(&*bin_data)?; + let mut result = ObjInfo { + path: obj_path.to_owned(), + sections: filter_sections(&obj_file)?, + common: common_symbols(&obj_file)?, + }; + for section in &mut result.sections { + section.symbols = symbols_by_section(&obj_file, section)?; + section.relocations = relocations_by_section(&obj_file, section)?; + } + Ok(result) +} diff --git a/src/jobs/build.rs b/src/jobs/build.rs new file mode 100644 index 0000000..b4f3f62 --- /dev/null +++ b/src/jobs/build.rs @@ -0,0 +1,107 @@ +use std::{ + path::Path, + process::Command, + str::from_utf8, + sync::{mpsc::Receiver, Arc, RwLock}, +}; + +use anyhow::{Context, Error, Result}; + +use crate::{ + app::AppConfig, + diff::diff_objs, + elf, + jobs::{queue_job, update_status, Job, JobResult, JobState, Status}, + obj::ObjInfo, +}; + +pub struct BuildStatus { + pub success: bool, + pub log: String, +} +pub struct BuildResult { + pub first_status: BuildStatus, + pub second_status: BuildStatus, + pub first_obj: Option, + pub second_obj: Option, +} + +fn run_make(cwd: &Path, arg: &Path) -> BuildStatus { + match (|| -> Result { + let output = Command::new("make") + .current_dir(cwd) + .arg(arg) + .output() + .context("Failed to execute build")?; + let stdout = from_utf8(&output.stdout).context("Failed to process stdout")?; + let stderr = from_utf8(&output.stderr).context("Failed to process stderr")?; + Ok(BuildStatus { + success: output.status.code().unwrap_or(-1) == 0, + log: format!("{}\n{}", stdout, stderr), + }) + })() { + Ok(status) => status, + Err(e) => BuildStatus { success: false, log: e.to_string() }, + } +} + +fn run_build( + status: &Status, + cancel: Receiver<()>, + obj_path: String, + config: Arc>, +) -> Result> { + let config = config.read().map_err(|_| Error::msg("Failed to lock app config"))?.clone(); + let project_dir = + config.project_dir.as_ref().ok_or_else(|| Error::msg("Missing project dir"))?; + let mut asm_path = config + .build_asm_dir + .as_ref() + .ok_or_else(|| Error::msg("Missing build asm dir"))? + .to_owned(); + asm_path.push(&obj_path); + let mut src_path = config + .build_src_dir + .as_ref() + .ok_or_else(|| Error::msg("Missing build src dir"))? + .to_owned(); + src_path.push(&obj_path); + let asm_path_rel = + asm_path.strip_prefix(project_dir).context("Failed to create relative asm obj path")?; + let src_path_rel = + src_path.strip_prefix(project_dir).context("Failed to create relative src obj path")?; + + update_status(status, format!("Building asm {}", obj_path), 0, 5, &cancel)?; + let first_status = run_make(project_dir, asm_path_rel); + + update_status(status, format!("Building src {}", obj_path), 1, 5, &cancel)?; + let second_status = run_make(project_dir, src_path_rel); + + let mut first_obj = if first_status.success { + update_status(status, format!("Loading asm {}", obj_path), 2, 5, &cancel)?; + Some(elf::read(&asm_path)?) + } else { + None + }; + + let mut second_obj = if second_status.success { + update_status(status, format!("Loading src {}", obj_path), 3, 5, &cancel)?; + Some(elf::read(&src_path)?) + } else { + None + }; + + if let (Some(first_obj), Some(second_obj)) = (&mut first_obj, &mut second_obj) { + update_status(status, "Performing diff".to_string(), 4, 5, &cancel)?; + diff_objs(first_obj, second_obj)?; + } + + update_status(status, "Complete".to_string(), 5, 5, &cancel)?; + Ok(Box::new(BuildResult { first_status, second_status, first_obj, second_obj })) +} + +pub fn queue_build(obj_path: String, config: Arc>) -> JobState { + queue_job(Job::Build, move |status, cancel| { + run_build(status, cancel, obj_path, config).map(JobResult::Build) + }) +} diff --git a/src/jobs/mod.rs b/src/jobs/mod.rs new file mode 100644 index 0000000..d44d082 --- /dev/null +++ b/src/jobs/mod.rs @@ -0,0 +1,104 @@ +use std::{ + sync::{ + atomic::{AtomicUsize, Ordering}, + mpsc::{Receiver, Sender, TryRecvError}, + Arc, RwLock, + }, + thread::JoinHandle, +}; + +use anyhow::Result; + +use crate::jobs::build::BuildResult; + +pub mod build; + +#[derive(Debug, Eq, PartialEq, Copy, Clone)] +pub enum Job { + Build, +} +pub static JOB_ID: AtomicUsize = AtomicUsize::new(0); +pub struct JobState { + pub id: usize, + pub job_type: Job, + pub handle: Option>, + pub status: Arc>, + pub cancel: Sender<()>, + pub should_remove: bool, +} +#[derive(Default)] +pub struct JobStatus { + pub title: String, + pub progress_percent: f32, + pub progress_items: Option<[u32; 2]>, + pub status: String, + pub error: Option, +} +pub enum JobResult { + None, + Build(Box), +} + +fn should_cancel(rx: &Receiver<()>) -> bool { + match rx.try_recv() { + Ok(_) | Err(TryRecvError::Disconnected) => true, + Err(_) => false, + } +} + +type Status = Arc>; + +fn queue_job( + job_type: Job, + run: impl FnOnce(&Status, Receiver<()>) -> Result + Send + 'static, +) -> JobState { + let status = Arc::new(RwLock::new(JobStatus { + title: String::new(), + progress_percent: 0.0, + progress_items: None, + status: "".to_string(), + error: None, + })); + let status_clone = status.clone(); + let (tx, rx) = std::sync::mpsc::channel(); + let handle = std::thread::spawn(move || { + return match run(&status, rx) { + Ok(state) => state, + Err(e) => { + if let Ok(mut w) = status.write() { + w.error = Some(e); + } + JobResult::None + } + }; + }); + let id = JOB_ID.fetch_add(1, Ordering::Relaxed); + log::info!("Started job {}", id); + JobState { + id, + job_type, + handle: Some(handle), + status: status_clone, + cancel: tx, + should_remove: true, + } +} + +fn update_status( + status: &Status, + str: String, + count: u32, + total: u32, + cancel: &Receiver<()>, +) -> Result<()> { + let mut w = status.write().map_err(|_| anyhow::Error::msg("Failed to lock job status"))?; + w.progress_items = Some([count, total]); + w.progress_percent = count as f32 / total as f32; + if should_cancel(cancel) { + w.status = "Cancelled".to_string(); + return Err(anyhow::Error::msg("Cancelled")); + } else { + w.status = str; + } + Ok(()) +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..acc522a --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,11 @@ +#![warn(clippy::all, rust_2018_idioms)] + +pub use app::App; + +mod app; +mod diff; +mod editops; +mod elf; +mod jobs; +mod obj; +mod views; diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..79feb36 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,31 @@ +#![warn(clippy::all, rust_2018_idioms)] +#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release + +// When compiling natively: +#[cfg(not(target_arch = "wasm32"))] +fn main() { + // Log to stdout (if you run with `RUST_LOG=debug`). + tracing_subscriber::fmt::init(); + + let native_options = eframe::NativeOptions::default(); + // native_options.renderer = eframe::Renderer::Wgpu; + eframe::run_native("objdiff", native_options, Box::new(|cc| Box::new(objdiff::App::new(cc)))); +} + +// when compiling to web using trunk. +#[cfg(target_arch = "wasm32")] +fn main() { + // Make sure panics are logged using `console.error`. + console_error_panic_hook::set_once(); + + // Redirect tracing to console.log and friends: + tracing_wasm::set_as_global_default(); + + let web_options = eframe::WebOptions::default(); + eframe::start_web( + "the_canvas_id", // hardcode it + web_options, + Box::new(|cc| Box::new(eframe_template::TemplateApp::new(cc))), + ) + .expect("failed to start eframe"); +} diff --git a/src/obj.rs b/src/obj.rs new file mode 100644 index 0000000..a33f50d --- /dev/null +++ b/src/obj.rs @@ -0,0 +1,132 @@ +use std::path::PathBuf; + +use flagset::{flags, FlagSet}; + +#[derive(Debug, Eq, PartialEq, Copy, Clone)] +pub enum ObjSectionKind { + Code, + Data, + Bss, +} +flags! { + pub enum ObjSymbolFlags: u8 { + Global, + Local, + Weak, + Common, + } +} +#[derive(Debug, Copy, Clone, Default)] +pub struct ObjSymbolFlagSet(pub(crate) FlagSet); +#[derive(Debug, Clone)] +pub struct ObjSection { + pub name: String, + pub kind: ObjSectionKind, + pub address: u64, + pub size: u64, + pub data: Vec, + pub index: usize, + pub symbols: Vec, + pub relocations: Vec, +} +#[derive(Debug, Clone)] +pub enum ObjInsArg { + Arg(ppc750cl::Argument), + Reloc, + RelocOffset, +} +#[derive(Debug, Copy, Clone)] +pub struct ObjInsArgDiff { + /// Incrementing index for coloring + pub idx: usize, +} +#[derive(Debug, Clone)] +pub struct ObjInsBranchFrom { + /// Source instruction indices + pub ins_idx: Vec, + /// Incrementing index for coloring + pub branch_idx: usize, +} +#[derive(Debug, Clone)] +pub struct ObjInsBranchTo { + /// Target instruction index + pub ins_idx: usize, + /// Incrementing index for coloring + pub branch_idx: usize, +} +#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)] +pub enum ObjInsDiffKind { + #[default] + None, + OpMismatch, + ArgMismatch, + Replace, + Delete, + Insert, +} +#[derive(Debug, Clone)] +pub struct ObjIns { + pub ins: ppc750cl::Ins, + pub mnemonic: String, + pub args: Vec, + pub reloc: Option, +} +#[derive(Debug, Clone, Default)] +pub struct ObjInsDiff { + pub ins: Option, + /// Diff kind + pub kind: ObjInsDiffKind, + /// Branches from instruction + pub branch_from: Option, + /// Branches to instruction + pub branch_to: Option, + /// Arg diffs + pub arg_diff: Vec>, +} +#[derive(Debug, Clone)] +pub struct ObjSymbol { + pub name: String, + pub demangled_name: Option, + pub address: u64, + pub size: u64, + pub size_known: bool, + pub flags: ObjSymbolFlagSet, + + // Diff + pub diff_symbol: Option, + pub instructions: Vec, + pub match_percent: f32, +} +#[derive(Debug, Clone)] +pub struct ObjInfo { + pub path: PathBuf, + pub sections: Vec, + pub common: Vec, +} +#[derive(Debug, Eq, PartialEq, Copy, Clone)] +pub enum ObjRelocKind { + Absolute, + PpcAddr16Hi, + PpcAddr16Ha, + PpcAddr16Lo, + // PpcAddr32, + // PpcRel32, + // PpcAddr24, + PpcRel24, + // PpcAddr14, + PpcRel14, + PpcEmbSda21, +} +#[derive(Debug, Clone)] +pub struct ObjReloc { + pub kind: ObjRelocKind, + pub address: u64, + pub target: ObjSymbol, + pub target_section: Option, +} +// #[derive(Debug, Clone)] +// pub struct ObjInsDiff { +// pub kind: ObjInsDiffKind, +// pub left: Option, +// pub right: Option, +// } diff --git a/src/views/config.rs b/src/views/config.rs new file mode 100644 index 0000000..c923dc0 --- /dev/null +++ b/src/views/config.rs @@ -0,0 +1,87 @@ +use std::sync::{Arc, RwLock}; + +use crate::{ + app::{AppConfig, ViewState}, + jobs::build::queue_build, +}; + +pub fn config_ui(ui: &mut egui::Ui, config: &Arc>, view_state: &mut ViewState) { + let mut config_guard = config.write().unwrap(); + let AppConfig { project_dir, project_dir_change, build_asm_dir, build_src_dir, build_obj } = + &mut *config_guard; + + if ui.button("Select project dir").clicked() { + if let Some(path) = rfd::FileDialog::new().pick_folder() { + *project_dir = Some(path); + *project_dir_change = true; + *build_asm_dir = None; + *build_src_dir = None; + *build_obj = None; + } + } + if let Some(dir) = project_dir { + ui.label(dir.to_string_lossy()); + } + + ui.separator(); + + if let Some(project_dir) = project_dir { + if ui.button("Select asm build dir").clicked() { + if let Some(path) = rfd::FileDialog::new().set_directory(&project_dir).pick_folder() { + *build_asm_dir = Some(path); + *build_obj = None; + } + } + if let Some(dir) = build_asm_dir { + ui.label(dir.to_string_lossy()); + } + + ui.separator(); + + if ui.button("Select src build dir").clicked() { + if let Some(path) = rfd::FileDialog::new().set_directory(&project_dir).pick_folder() { + *build_src_dir = Some(path); + *build_obj = None; + } + } + if let Some(dir) = build_src_dir { + ui.label(dir.to_string_lossy()); + } + + ui.separator(); + } + + if let Some(build_src_dir) = build_src_dir { + if ui.button("Select obj").clicked() { + if let Some(path) = rfd::FileDialog::new() + .set_directory(&build_src_dir) + .add_filter("Object file", &["o"]) + .pick_file() + { + let mut new_build_obj: Option = None; + if let Ok(obj_path) = path.strip_prefix(&build_src_dir) { + new_build_obj = Some(obj_path.display().to_string()); + } else if let Some(build_asm_dir) = build_asm_dir { + if let Ok(obj_path) = path.strip_prefix(&build_asm_dir) { + new_build_obj = Some(obj_path.display().to_string()); + } + } + if let Some(new_build_obj) = new_build_obj { + *build_obj = Some(new_build_obj.clone()); + view_state.jobs.push(queue_build(new_build_obj, config.clone())); + } + } + } + if let Some(build_obj) = build_obj { + ui.label(&*build_obj); + if ui.button("Build").clicked() { + view_state.jobs.push(queue_build(build_obj.clone(), config.clone())); + } + } + + ui.separator(); + } + + ui.checkbox(&mut view_state.reverse_fn_order, "Reverse function order (deferred)"); + ui.separator(); +} diff --git a/src/views/function_diff.rs b/src/views/function_diff.rs new file mode 100644 index 0000000..0e8929b --- /dev/null +++ b/src/views/function_diff.rs @@ -0,0 +1,338 @@ +use std::default::Default; + +use cwdemangle::demangle; +use egui::{text::LayoutJob, Color32, FontFamily, FontId, Label, Sense, TextFormat}; +use egui_extras::{Size, StripBuilder, TableBuilder}; +use ppc750cl::Argument; + +use crate::{ + app::ViewState, + obj::{ + ObjInfo, ObjIns, ObjInsArg, ObjInsArgDiff, ObjInsDiff, ObjInsDiffKind, ObjReloc, + ObjRelocKind, ObjSymbol, + }, + views::symbol_diff::match_color_for_symbol, +}; + +const FONT_SIZE: f32 = 14.0; +const FONT_ID: FontId = FontId::new(FONT_SIZE, FontFamily::Monospace); + +const COLOR_RED: Color32 = Color32::from_rgb(200, 40, 41); + +fn write_text(str: &str, color: Color32, job: &mut LayoutJob) { + job.append(str, 0.0, TextFormat { font_id: FONT_ID, color, ..Default::default() }); +} + +fn write_reloc(reloc: &ObjReloc, job: &mut LayoutJob) { + let name = reloc.target.demangled_name.as_ref().unwrap_or(&reloc.target.name); + write_text(name, Color32::LIGHT_GRAY, job); + match reloc.kind { + ObjRelocKind::PpcAddr16Lo => write_text("@l", Color32::GRAY, job), + ObjRelocKind::PpcAddr16Hi => write_text("@h", Color32::GRAY, job), + ObjRelocKind::PpcAddr16Ha => write_text("@ha", Color32::GRAY, job), + ObjRelocKind::PpcEmbSda21 => write_text("@sda21", Color32::GRAY, job), + _ => {} + }; +} + +fn write_ins( + ins: &ObjIns, + diff_kind: &ObjInsDiffKind, + args: &[Option], + base_addr: u32, + job: &mut LayoutJob, +) { + let base_color = match diff_kind { + ObjInsDiffKind::None | ObjInsDiffKind::OpMismatch | ObjInsDiffKind::ArgMismatch => { + Color32::GRAY + } + ObjInsDiffKind::Replace => Color32::LIGHT_BLUE, + ObjInsDiffKind::Delete => COLOR_RED, + ObjInsDiffKind::Insert => Color32::GREEN, + }; + write_text( + &ins.mnemonic, + match diff_kind { + ObjInsDiffKind::OpMismatch => Color32::LIGHT_BLUE, + _ => base_color, + }, + job, + ); + let mut writing_offset = false; + for (i, arg) in ins.args.iter().enumerate() { + if i == 0 { + write_text(" ", base_color, job); + } + if i > 0 && !writing_offset { + write_text(", ", base_color, job); + } + let color = if let Some(diff) = args.get(i).and_then(|a| a.as_ref()) { + COLOR_ROTATION[diff.idx % COLOR_ROTATION.len()] + } else { + base_color + }; + match arg { + ObjInsArg::Arg(arg) => match arg { + Argument::Offset(val) => { + write_text(&format!("{}", val), color, job); + write_text("(", base_color, job); + writing_offset = true; + continue; + } + Argument::BranchDest(dest) => { + let addr = dest.0 + ins.ins.addr as i32 - base_addr as i32; + write_text(&format!("{:x}", addr), color, job); + } + Argument::Uimm(_) | Argument::Simm(_) => { + write_text(&format!("{}", arg), color, job); + } + _ => { + write_text(&format!("{}", arg), color, job); + } + }, + ObjInsArg::Reloc => { + write_reloc(ins.reloc.as_ref().unwrap(), job); + } + ObjInsArg::RelocOffset => { + write_reloc(ins.reloc.as_ref().unwrap(), job); + write_text("(", base_color, job); + writing_offset = true; + continue; + } + } + if writing_offset { + write_text(")", base_color, job); + writing_offset = false; + } + } +} + +fn ins_hover_ui(ui: &mut egui::Ui, ins: &ObjIns) { + ui.scope(|ui| { + ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace); + ui.style_mut().wrap = Some(false); + + ui.label(format!("{:02X?}", ins.ins.code.to_be_bytes())); + + for arg in &ins.args { + if let ObjInsArg::Arg(arg) = arg { + match arg { + Argument::Uimm(v) => { + ui.label(format!("{} == {}", v, v.0)); + } + Argument::Simm(v) => { + ui.label(format!("{} == {}", v, v.0)); + } + Argument::Offset(v) => { + ui.label(format!("{} == {}", v, v.0)); + } + _ => {} + } + } + } + + if let Some(reloc) = &ins.reloc { + ui.label(format!("Relocation type: {:?}", reloc.kind)); + ui.colored_label(Color32::WHITE, format!("Name: {}", reloc.target.name)); + if let Some(section) = &reloc.target_section { + ui.colored_label(Color32::WHITE, format!("Section: {}", section)); + ui.colored_label(Color32::WHITE, format!("Address: {:x}", reloc.target.address)); + ui.colored_label(Color32::WHITE, format!("Size: {:x}", reloc.target.size)); + } else { + ui.colored_label(Color32::WHITE, "Extern".to_string()); + } + } + }); +} + +fn ins_context_menu(ui: &mut egui::Ui, ins: &ObjIns) { + ui.scope(|ui| { + ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace); + ui.style_mut().wrap = Some(false); + + // if ui.button("Copy hex").clicked() {} + + for arg in &ins.args { + if let ObjInsArg::Arg(arg) = arg { + match arg { + Argument::Uimm(v) => { + if ui.button(format!("Copy \"{}\"", v)).clicked() { + ui.output().copied_text = format!("{}", v); + ui.close_menu(); + } + if ui.button(format!("Copy \"{}\"", v.0)).clicked() { + ui.output().copied_text = format!("{}", v.0); + ui.close_menu(); + } + } + Argument::Simm(v) => { + if ui.button(format!("Copy \"{}\"", v)).clicked() { + ui.output().copied_text = format!("{}", v); + ui.close_menu(); + } + if ui.button(format!("Copy \"{}\"", v.0)).clicked() { + ui.output().copied_text = format!("{}", v.0); + ui.close_menu(); + } + } + Argument::Offset(v) => { + if ui.button(format!("Copy \"{}\"", v)).clicked() { + ui.output().copied_text = format!("{}", v); + ui.close_menu(); + } + if ui.button(format!("Copy \"{}\"", v.0)).clicked() { + ui.output().copied_text = format!("{}", v.0); + ui.close_menu(); + } + } + _ => {} + } + } + } + if let Some(reloc) = &ins.reloc { + if let Some(name) = &reloc.target.demangled_name { + if ui.button(format!("Copy \"{}\"", name)).clicked() { + ui.output().copied_text = name.clone(); + ui.close_menu(); + } + } + if ui.button(format!("Copy \"{}\"", reloc.target.name)).clicked() { + ui.output().copied_text = reloc.target.name.clone(); + ui.close_menu(); + } + } + }); +} + +const COLOR_ROTATION: [Color32; 9] = [ + Color32::from_rgb(255, 0, 255), + Color32::from_rgb(0, 255, 255), + Color32::from_rgb(0, 128, 0), + Color32::from_rgb(255, 0, 0), + Color32::from_rgb(255, 255, 0), + Color32::from_rgb(255, 192, 203), + Color32::from_rgb(0, 0, 255), + Color32::from_rgb(0, 255, 0), + Color32::from_rgb(128, 128, 128), +]; + +fn find_symbol<'a>(obj: &'a ObjInfo, section_name: &str, name: &str) -> Option<&'a ObjSymbol> { + let section = obj.sections.iter().find(|s| s.name == section_name)?; + section.symbols.iter().find(|s| s.name == name) +} + +fn asm_row_ui(ui: &mut egui::Ui, ins_diff: &ObjInsDiff, symbol: &ObjSymbol) { + if ins_diff.kind != ObjInsDiffKind::None { + ui.painter().rect_filled(ui.available_rect_before_wrap(), 0.0, ui.visuals().faint_bg_color); + } + let mut job = LayoutJob::default(); + if let Some(ins) = &ins_diff.ins { + let base_color = match ins_diff.kind { + ObjInsDiffKind::None | ObjInsDiffKind::OpMismatch | ObjInsDiffKind::ArgMismatch => { + Color32::GRAY + } + ObjInsDiffKind::Replace => Color32::LIGHT_BLUE, + ObjInsDiffKind::Delete => COLOR_RED, + ObjInsDiffKind::Insert => Color32::GREEN, + }; + write_text( + &format!("{:<6}", format!("{:x}:", ins.ins.addr - symbol.address as u32)), + base_color, + &mut job, + ); + if let Some(branch) = &ins_diff.branch_from { + write_text("~> ", COLOR_ROTATION[branch.branch_idx % COLOR_ROTATION.len()], &mut job); + } else { + write_text(" ", base_color, &mut job); + } + write_ins(ins, &ins_diff.kind, &ins_diff.arg_diff, symbol.address as u32, &mut job); + if let Some(branch) = &ins_diff.branch_to { + write_text(" ~>", COLOR_ROTATION[branch.branch_idx % COLOR_ROTATION.len()], &mut job); + } + ui.add(Label::new(job).sense(Sense::click())) + .on_hover_ui_at_pointer(|ui| ins_hover_ui(ui, ins)) + .context_menu(|ui| ins_context_menu(ui, ins)); + } else { + ui.label(""); + } +} + +fn asm_table_ui( + table: TableBuilder<'_>, + left_obj: &ObjInfo, + right_obj: &ObjInfo, + fn_name: &str, +) -> Option<()> { + let left_symbol = find_symbol(left_obj, ".text", fn_name)?; + let right_symbol = find_symbol(right_obj, ".text", fn_name)?; + table.body(|body| { + body.rows(FONT_SIZE, left_symbol.instructions.len(), |row_index, mut row| { + row.col(|ui| { + asm_row_ui(ui, &left_symbol.instructions[row_index], left_symbol); + }); + row.col(|ui| { + asm_row_ui(ui, &right_symbol.instructions[row_index], right_symbol); + }); + }); + }); + Some(()) +} + +pub fn function_diff_ui(ui: &mut egui::Ui, view_state: &mut ViewState) { + if let (Some(result), Some(selected_symbol)) = (&view_state.build, &view_state.selected_symbol) + { + StripBuilder::new(ui).size(Size::exact(40.0)).size(Size::remainder()).vertical( + |mut strip| { + strip.strip(|builder| { + builder.sizes(Size::remainder(), 2).horizontal(|mut strip| { + let demangled = demangle(selected_symbol); + strip.cell(|ui| { + ui.scope(|ui| { + ui.style_mut().override_text_style = + Some(egui::TextStyle::Monospace); + ui.style_mut().wrap = Some(false); + ui.colored_label( + Color32::WHITE, + demangled.as_ref().unwrap_or(selected_symbol), + ); + ui.label("Diff asm:"); + ui.separator(); + }); + }); + strip.cell(|ui| { + ui.scope(|ui| { + ui.style_mut().override_text_style = + Some(egui::TextStyle::Monospace); + ui.style_mut().wrap = Some(false); + if let Some(obj) = &result.second_obj { + if let Some(symbol) = find_symbol(obj, ".text", selected_symbol) + { + ui.colored_label( + match_color_for_symbol(symbol), + &format!("{:.0}%", symbol.match_percent), + ); + } + } + ui.label("Diff src:"); + ui.separator(); + }); + }); + }); + }); + strip.cell(|ui| { + if let (Some(left_obj), Some(right_obj)) = + (&result.first_obj, &result.second_obj) + { + let table = TableBuilder::new(ui) + .striped(false) + .cell_layout(egui::Layout::left_to_right(egui::Align::Min)) + .column(Size::relative(0.5)) + .column(Size::relative(0.5)) + .resizable(false); + asm_table_ui(table, left_obj, right_obj, selected_symbol); + } + }); + }, + ); + } +} diff --git a/src/views/jobs.rs b/src/views/jobs.rs new file mode 100644 index 0000000..377e70d --- /dev/null +++ b/src/views/jobs.rs @@ -0,0 +1,38 @@ +use egui::{Color32, ProgressBar, Widget}; + +use crate::app::ViewState; + +pub fn jobs_ui(ui: &mut egui::Ui, view_state: &ViewState) { + ui.label("Jobs"); + + for job in &view_state.jobs { + if let Ok(status) = job.status.read() { + ui.group(|ui| { + ui.label(&status.title); + let mut bar = ProgressBar::new(status.progress_percent); + if let Some(items) = &status.progress_items { + bar = bar.text(format!("{} / {}", items[0], items[1])); + } + bar.ui(ui); + const STATUS_LENGTH: usize = 80; + if let Some(err) = &status.error { + let err_string = err.to_string(); + ui.colored_label( + Color32::from_rgb(255, 0, 0), + if err_string.len() > STATUS_LENGTH - 10 { + format!("Error: {}...", &err_string[0..STATUS_LENGTH - 10]) + } else { + format!("Error: {:width$}", err_string, width = STATUS_LENGTH - 7) + }, + ); + } else { + ui.label(if status.status.len() > STATUS_LENGTH - 3 { + format!("{}...", &status.status[0..STATUS_LENGTH - 3]) + } else { + format!("{:width$}", &status.status, width = STATUS_LENGTH) + }); + } + }); + } + } +} diff --git a/src/views/mod.rs b/src/views/mod.rs new file mode 100644 index 0000000..1e5696e --- /dev/null +++ b/src/views/mod.rs @@ -0,0 +1,4 @@ +pub(crate) mod config; +pub(crate) mod function_diff; +pub(crate) mod jobs; +pub(crate) mod symbol_diff; diff --git a/src/views/symbol_diff.rs b/src/views/symbol_diff.rs new file mode 100644 index 0000000..862d36c --- /dev/null +++ b/src/views/symbol_diff.rs @@ -0,0 +1,240 @@ +use egui::{ + text::LayoutJob, CollapsingHeader, Color32, FontFamily, FontId, Rgba, ScrollArea, + SelectableLabel, TextFormat, Ui, Widget, +}; +use egui_extras::{Size, StripBuilder}; + +use crate::{ + app::{View, ViewState}, + jobs::build::BuildStatus, + obj::{ObjInfo, ObjSymbol, ObjSymbolFlags}, +}; + +pub fn match_color_for_symbol(symbol: &ObjSymbol) -> Color32 { + if symbol.match_percent == 100.0 { + Color32::GREEN + } else if symbol.match_percent >= 50.0 { + Color32::LIGHT_BLUE + } else { + Color32::RED + } +} + +fn symbol_ui( + ui: &mut Ui, + symbol: &ObjSymbol, + highlighted_symbol: &mut Option, + selected_symbol: &mut Option, + current_view: &mut View, +) { + let mut job = LayoutJob::default(); + let name: &str = + if let Some(demangled) = &symbol.demangled_name { demangled } else { &symbol.name }; + let mut selected = false; + if let Some(sym) = highlighted_symbol { + selected = sym == &symbol.name; + } + let font_id = FontId::new(14.0, FontFamily::Monospace); + job.append("[", 0.0, TextFormat { + font_id: font_id.clone(), + color: Color32::GRAY, + ..Default::default() + }); + if symbol.flags.0.contains(ObjSymbolFlags::Common) { + job.append("c", 0.0, TextFormat { + font_id: font_id.clone(), + color: Color32::from_rgb(0, 255, 255), + ..Default::default() + }); + } else if symbol.flags.0.contains(ObjSymbolFlags::Global) { + job.append("g", 0.0, TextFormat { + font_id: font_id.clone(), + color: Color32::GREEN, + ..Default::default() + }); + } else if symbol.flags.0.contains(ObjSymbolFlags::Local) { + job.append("l", 0.0, TextFormat { + font_id: font_id.clone(), + color: Color32::GRAY, + ..Default::default() + }); + } + if symbol.flags.0.contains(ObjSymbolFlags::Weak) { + job.append("w", 0.0, TextFormat { + font_id: font_id.clone(), + color: Color32::GRAY, + ..Default::default() + }); + } + job.append("] (", 0.0, TextFormat { + font_id: font_id.clone(), + color: Color32::GRAY, + ..Default::default() + }); + job.append(&format!("{:.0}%", symbol.match_percent), 0.0, TextFormat { + font_id: font_id.clone(), + color: match_color_for_symbol(symbol), + ..Default::default() + }); + job.append(") ", 0.0, TextFormat { + font_id: font_id.clone(), + color: Color32::GRAY, + ..Default::default() + }); + job.append(name, 0.0, TextFormat { font_id, color: Color32::WHITE, ..Default::default() }); + let response = SelectableLabel::new(selected, job).ui(ui); + if response.clicked() { + *selected_symbol = Some(symbol.name.clone()); + *current_view = View::FunctionDiff; + } else if response.hovered() { + *highlighted_symbol = Some(symbol.name.clone()); + } +} + +fn symbol_list_ui( + ui: &mut Ui, + obj: &ObjInfo, + highlighted_symbol: &mut Option, + selected_symbol: &mut Option, + current_view: &mut View, + reverse_function_order: bool, +) { + ScrollArea::both().auto_shrink([false, false]).show(ui, |ui| { + ui.scope(|ui| { + ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace); + ui.style_mut().wrap = Some(false); + + if !obj.common.is_empty() { + CollapsingHeader::new(".comm").default_open(true).show(ui, |ui| { + for symbol in &obj.common { + symbol_ui(ui, symbol, highlighted_symbol, selected_symbol, current_view); + } + }); + } + + for section in &obj.sections { + CollapsingHeader::new(format!("{} ({:x})", section.name, section.size)) + .default_open(true) + .show(ui, |ui| { + if section.name == ".text" && reverse_function_order { + for symbol in section.symbols.iter().rev() { + symbol_ui( + ui, + symbol, + highlighted_symbol, + selected_symbol, + current_view, + ); + } + } else { + for symbol in §ion.symbols { + symbol_ui( + ui, + symbol, + highlighted_symbol, + selected_symbol, + current_view, + ); + } + } + }); + } + }); + }); +} + +fn build_log_ui(ui: &mut Ui, status: &BuildStatus) { + ui.scope(|ui| { + ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace); + ui.style_mut().wrap = Some(false); + ui.colored_label(Color32::from_rgb(255, 0, 0), &status.log); + }); +} + +pub fn symbol_diff_ui(ui: &mut Ui, view_state: &mut ViewState) { + if let (Some(result), highlighted_symbol, selected_symbol, current_view) = ( + &view_state.build, + &mut view_state.highlighted_symbol, + &mut view_state.selected_symbol, + &mut view_state.current_view, + ) { + StripBuilder::new(ui).size(Size::exact(40.0)).size(Size::remainder()).vertical( + |mut strip| { + strip.strip(|builder| { + builder.sizes(Size::remainder(), 2).horizontal(|mut strip| { + strip.cell(|ui| { + ui.scope(|ui| { + ui.style_mut().override_text_style = + Some(egui::TextStyle::Monospace); + ui.style_mut().wrap = Some(false); + + ui.label("Build asm:"); + if result.first_status.success { + ui.label("OK"); + } else { + ui.colored_label(Rgba::from_rgb(1.0, 0.0, 0.0), "Fail"); + } + }); + ui.separator(); + }); + strip.cell(|ui| { + ui.scope(|ui| { + ui.style_mut().override_text_style = + Some(egui::TextStyle::Monospace); + ui.style_mut().wrap = Some(false); + + ui.label("Build src:"); + if result.second_status.success { + ui.label("OK"); + } else { + ui.colored_label(Rgba::from_rgb(1.0, 0.0, 0.0), "Fail"); + } + }); + ui.separator(); + }); + }); + }); + strip.strip(|builder| { + builder.sizes(Size::remainder(), 2).horizontal(|mut strip| { + strip.cell(|ui| { + if result.first_status.success { + if let Some(obj) = &result.first_obj { + ui.push_id("left", |ui| { + symbol_list_ui( + ui, + obj, + highlighted_symbol, + selected_symbol, + current_view, + view_state.reverse_fn_order, + ); + }); + } + } else { + build_log_ui(ui, &result.first_status); + } + }); + strip.cell(|ui| { + if result.second_status.success { + if let Some(obj) = &result.second_obj { + ui.push_id("right", |ui| { + symbol_list_ui( + ui, + obj, + highlighted_symbol, + selected_symbol, + current_view, + view_state.reverse_fn_order, + ); + }); + } + } else { + build_log_ui(ui, &result.second_status); + } + }); + }); + }); + }, + ); + } +}