From cb3c6062c7105411d16275ce715cd212a8f902ae Mon Sep 17 00:00:00 2001 From: Luke Street Date: Thu, 8 Sep 2022 17:19:20 -0400 Subject: [PATCH] Initial commit --- .github/workflows/build.yaml | 61 + .gitignore | 24 + Cargo.lock | 2445 ++++++++++++++++++++++++++++++++++ Cargo.toml | 36 + README.md | 28 + assets/screen-diff.png | Bin 0 -> 135811 bytes deny.toml | 210 +++ rustfmt.toml | 8 + src/app.rs | 266 ++++ src/diff.rs | 396 ++++++ src/editops.rs | 253 ++++ src/elf.rs | 242 ++++ src/jobs/build.rs | 107 ++ src/jobs/mod.rs | 104 ++ src/lib.rs | 11 + src/main.rs | 31 + src/obj.rs | 132 ++ src/views/config.rs | 87 ++ src/views/function_diff.rs | 338 +++++ src/views/jobs.rs | 38 + src/views/mod.rs | 4 + src/views/symbol_diff.rs | 240 ++++ 22 files changed, 5061 insertions(+) create mode 100644 .github/workflows/build.yaml create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 README.md create mode 100644 assets/screen-diff.png create mode 100644 deny.toml create mode 100644 rustfmt.toml create mode 100644 src/app.rs create mode 100644 src/diff.rs create mode 100644 src/editops.rs create mode 100644 src/elf.rs create mode 100644 src/jobs/build.rs create mode 100644 src/jobs/mod.rs create mode 100644 src/lib.rs create mode 100644 src/main.rs create mode 100644 src/obj.rs create mode 100644 src/views/config.rs create mode 100644 src/views/function_diff.rs create mode 100644 src/views/jobs.rs create mode 100644 src/views/mod.rs create mode 100644 src/views/symbol_diff.rs 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 0000000000000000000000000000000000000000..de775eb96edae9a608dea46ef36a9e7c7b670ae5 GIT binary patch literal 135811 zcma&OcRZK<|28g)hET4Ej7p_p@2#yuDtn7i_LkjNSxG3fLb9_r$tp91>@u_W%DRuU z>*{;ozx(&k{eFBt*L4~1^L?JL*K-`t<2YWPT)8Z>b35a95)zV~7cZQ>MnbYVm4t-s z{8n;&ht!YB1^?P?d-|f%R=ixd-gu0!8SKuf*(q8Y**RXfF(fgzurxR1v^B6XG_

WhZA`byIUgvzxPS9jWlB!HC)YN|ZNG5U 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 literal 0 HcmV?d00001 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); + } + }); + }); + }); + }, + ); + } +}