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 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ⅈO--8$7c{oI4suTS6Dm~2`0 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

?KOW)q4{5gBsFeBdPIbX>d1M~Kt^L&#m>Xfcc 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, + #[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); + } + }); + }); + }); + }, + ); + } +}