diff --git a/.github/workflows/harness.yml b/.github/workflows/harness.yml new file mode 100644 index 00000000..7a212136 --- /dev/null +++ b/.github/workflows/harness.yml @@ -0,0 +1,51 @@ +name: Harness + +on: + pull_request: + push: + branches: + - "master" + workflow_dispatch: + +jobs: + run: + name: Run (${{ matrix.os }}) + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [windows-latest, ubuntu-latest] + steps: + - name: Checkout Commit + uses: actions/checkout@v6 + + - name: Install Rust + uses: hecrj/setup-rust-action@v2 + with: + targets: wasm32-unknown-unknown + + # The players are pinned to one commit of the fixtures repo, so the + # cache is keyed on that commit and only a new corpus downloads again. + - name: Read the fixtures commit + id: fixtures + shell: bash + run: | + commit=$(grep -o 'FIXTURES: &str = "[0-9a-f]*' harness/src/main.rs | cut -d '"' -f 2) + echo "commit=$commit" >> "$GITHUB_OUTPUT" + + - name: Cache the players + uses: actions/cache@v4 + with: + path: harness/target/fixtures/*.zip + key: players-${{ runner.os }}-${{ steps.fixtures.outputs.commit }} + + - name: Run the players + if: runner.os == 'Windows' + run: cargo run --release --manifest-path harness/Cargo.toml + + # Gives the Linux run a virtual display because Unity 6000.0 to 6000.5 + # Linux players crash a few seconds after start without one, even with + # -nographics. + - name: Run the players + if: runner.os == 'Linux' + run: xvfb-run -a cargo run --release --manifest-path harness/Cargo.toml diff --git a/harness/.gitignore b/harness/.gitignore new file mode 100644 index 00000000..ea8c4bf7 --- /dev/null +++ b/harness/.gitignore @@ -0,0 +1 @@ +/target diff --git a/harness/Cargo.lock b/harness/Cargo.lock new file mode 100644 index 00000000..114d717c --- /dev/null +++ b/harness/Cargo.lock @@ -0,0 +1,2833 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59317f77929f0e679d39364702289274de2f0f0b22cbf50b2b8cff2169a0b27a" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aho-corasick" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "ambient-authority" +version = "0.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9d4ee0d472d1cd2e28c97dfa124b3d8d992e10eb0a035f33f5d12e3a177ba3b" + +[[package]] +name = "android_system_properties" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" + +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" +dependencies = [ + "derive_arbitrary", +] + +[[package]] +name = "arc-swap" +version = "1.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c049c0be4daef0b145cb3555416b3b8ef5b7888a38aea1a3a155801fe7b0810b" +dependencies = [ + "rustversion", +] + +[[package]] +name = "arrayvec" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" + +[[package]] +name = "asr" +version = "0.1.0" +dependencies = [ + "arrayvec", + "asr-derive", + "bytemuck", + "itoa", + "ryu", + "time", + "wasi", +] + +[[package]] +name = "asr-derive" +version = "0.1.0" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "async-trait" +version = "0.1.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "base64" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5" + +[[package]] +name = "bindgen" +version = "0.72.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "itertools 0.13.0", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex 1.3.0", + "syn 2.0.119", +] + +[[package]] +name = "bitflags" +version = "2.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bstr" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bb31b46c14244e20ee9984b11bf5c992b91fb6939fea616e3512c8baecdbe5f" +dependencies = [ + "memchr", + "regex-automata", + "serde_core", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" +dependencies = [ + "allocator-api2", +] + +[[package]] +name = "bytemuck" +version = "1.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a1f896587b6f2c069c73d2f0913e2d590c3990285cd2f0b6aa02b786b4c679c" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "bytes" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" + +[[package]] +name = "cap-fs-ext" +version = "3.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "476f0d0003a760918ed4b1e039a59e11769030416f79c8222551d22785f7f70d" +dependencies = [ + "cap-primitives", + "cap-std", + "io-lifetimes", + "windows-sys 0.52.0", +] + +[[package]] +name = "cap-net-ext" +version = "3.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "150941cefd3df4de2fea24604ba4949371576f62e527410298333f7d431a1bc6" +dependencies = [ + "cap-primitives", + "cap-std", + "rustix", + "smallvec", +] + +[[package]] +name = "cap-primitives" +version = "3.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e0bf07d379916947be6c4a07f43684153d710a2896c31f9e97781362895596c" +dependencies = [ + "ambient-authority", + "fs-set-times", + "io-extras", + "io-lifetimes", + "ipnet", + "maybe-owned", + "rustix", + "rustix-linux-procfs", + "windows-sys 0.52.0", + "winx", +] + +[[package]] +name = "cap-std" +version = "3.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a59e59fa26472d29680ece6a9f8ee8b0551a719a33df2f5240bde065ecbddfd7" +dependencies = [ + "cap-primitives", + "io-extras", + "io-lifetimes", + "rustix", +] + +[[package]] +name = "cap-time-ext" +version = "3.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b54c289326c70f1c697ebf0a31842a480932e5942b5fac92fcc46e87286b48e2" +dependencies = [ + "ambient-authority", + "cap-primitives", + "iana-time-zone", + "once_cell", + "rustix", + "winx", +] + +[[package]] +name = "cc" +version = "1.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54413ede23c2daf518f35156dfde027feb2374004d63bd497f983c8db9c0e313" +dependencies = [ + "find-msvc-tools", + "shlex 2.0.1", +] + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600" + +[[package]] +name = "chacha20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.1", + "rand_core", +] + +[[package]] +name = "clang-sys" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "157a8ba7b480713b56f4c09fd13fc3e0a22a5dfab8097ba61cbc5feef950788a" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "cobs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" +dependencies = [ + "thiserror 2.0.20", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core_detect" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f8f80099a98041a3d1622845c271458a2d73e688351bf3cb999266764b81d48" + +[[package]] +name = "cpp_demangle" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2bb79cb74d735044c972aae58ed0aaa9a837e85b01106a54c39e42e97f62253" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566" +dependencies = [ + "libc", +] + +[[package]] +name = "cranelift-assembler-x64" +version = "0.132.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d521bdbc6098937af83ef4ab6d5c07398126bc71878f7ef4ea9499977978ef5" +dependencies = [ + "cranelift-assembler-x64-meta", +] + +[[package]] +name = "cranelift-assembler-x64-meta" +version = "0.132.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dde0b83164d4a497860af4236271178bc640512b067101e0853e4f74eaa4df5" +dependencies = [ + "cranelift-srcgen", +] + +[[package]] +name = "cranelift-bforest" +version = "0.132.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0111d110b72b4efad69a372e29e21628652fd0bcab66967e5c8350ab679affd5" +dependencies = [ + "cranelift-entity", + "wasmtime-internal-core", +] + +[[package]] +name = "cranelift-bitset" +version = "0.132.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf01ecc92fc5499789d79c3b817299d5a8ddd828d31dcf0f9cc3cc66f38dbb36" +dependencies = [ + "serde", + "serde_derive", + "wasmtime-internal-core", +] + +[[package]] +name = "cranelift-codegen" +version = "0.132.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cd2563bead0090c3879a7ff7327f7550c9d59bb5d05ecc8cd7886977aa3125a" +dependencies = [ + "bumpalo", + "cranelift-assembler-x64", + "cranelift-bforest", + "cranelift-bitset", + "cranelift-codegen-meta", + "cranelift-codegen-shared", + "cranelift-control", + "cranelift-entity", + "cranelift-isle", + "gimli", + "hashbrown 0.17.1", + "libm", + "log", + "pulley-interpreter", + "regalloc2", + "rustc-hash", + "serde", + "smallvec", + "target-lexicon", + "wasmtime-internal-core", +] + +[[package]] +name = "cranelift-codegen-meta" +version = "0.132.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9640d250d26f9381a73dc7f9862b27928d4809119aec73be0e556612e67b6a99" +dependencies = [ + "cranelift-assembler-x64-meta", + "cranelift-codegen-shared", + "cranelift-srcgen", + "heck 0.5.0", + "pulley-interpreter", +] + +[[package]] +name = "cranelift-codegen-shared" +version = "0.132.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a07f156b90efc94371ddb3536f76e4ab671ad2e093bfa5511e10198cadbb0c47" + +[[package]] +name = "cranelift-control" +version = "0.132.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d75a76fd9dd37dcbc3d2d2e00abe3281a7e88adc035fba3b8114cc69981576ec" +dependencies = [ + "arbitrary", +] + +[[package]] +name = "cranelift-entity" +version = "0.132.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eea22522144ba08c7e7ef94bfc25d474ef016c2771974b8ab1986734ac85965" +dependencies = [ + "cranelift-bitset", + "serde", + "serde_derive", + "wasmtime-internal-core", +] + +[[package]] +name = "cranelift-frontend" +version = "0.132.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efa2826c80dff1d93b19b3cfbcaf9fae44c78d739a98d146dd5bd89c51e14367" +dependencies = [ + "cranelift-codegen", + "log", + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cranelift-isle" +version = "0.132.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e238a69b95c5415456f22189494a12db94f313bb72b6ec9cc88ddf2f1056e28e" + +[[package]] +name = "cranelift-native" +version = "0.132.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eceb0ebd8d6aef6bb287e8d532a164b0db1c0062961211e9c22a91f67521c098" +dependencies = [ + "cranelift-codegen", + "libc", + "target-lexicon", +] + +[[package]] +name = "cranelift-srcgen" +version = "0.132.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "004643f39a7bec553de5263d650db30e5b9caec1d5cbe065fd732b7ec9ae40d0" + +[[package]] +name = "crc32fast" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01a7799fd6b852db0e61728dde9a204c423b44d689dbd432522543614b490e78" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622f3fc73690be383c7214310406f28a90e6edeadc3cea882f9d71e495b9711a" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc74980687109a3b14c72fd458107bf0baa1da1a1a805e178d15501ba9b86d9d" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" + +[[package]] +name = "derive_arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "displaydoc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "either" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34" + +[[package]] +name = "embedded-io" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" + +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + +[[package]] +name = "encoding_rs" +version = "0.8.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5ef0006ac9ab233c38522f5ae99cae3625151de8f706cacee1cba4b8e2832a" +dependencies = [ + "cfg-if", + "core_detect", + "multiversion", + "multiversion_no_op", + "rustversion", + "scopeguard", + "simdutf8", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25905e51abafe4dcea6c15fec58c57b601cdbd0ee53d22ea1d3016c587d39b" + +[[package]] +name = "flate2" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb" +dependencies = [ + "crc32fast", + "miniz_oxide", + "zlib-rs", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fs-set-times" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94e7099f6313ecacbe1256e8ff9d617b75d1bcb16a6fddef94866d225a01a14a" +dependencies = [ + "io-lifetimes", + "rustix", + "windows-sys 0.52.0", +] + +[[package]] +name = "futures" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" + +[[package]] +name = "futures-io" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" + +[[package]] +name = "futures-sink" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" + +[[package]] +name = "futures-task" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" + +[[package]] +name = "futures-util" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" +dependencies = [ + "futures-core", + "futures-sink", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "rand_core", +] + +[[package]] +name = "gimli" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf7f043f89559805f8c7cacc432749b2fa0d0a0a9ee46ce47164ed5ba7f126c" +dependencies = [ + "fnv", + "hashbrown 0.16.1", + "indexmap", + "stable_deref_trait", +] + +[[package]] +name = "glob" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" + +[[package]] +name = "harness" +version = "0.1.0" +dependencies = [ + "livesplit-auto-splitting", + "serde", + "serde_json", + "sha2", + "time", + "ureq", + "zip", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" +dependencies = [ + "foldhash", + "serde", + "serde_core", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "http" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0" + +[[package]] +name = "icu_properties" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa" + +[[package]] +name = "icu_provider" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", + "serde", + "serde_core", +] + +[[package]] +name = "io-extras" +version = "0.18.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2285ddfe3054097ef4b2fe909ef8c3bcd1ea52a8f0d274416caebeef39f04a65" +dependencies = [ + "io-lifetimes", + "windows-sys 0.52.0", +] + +[[package]] +name = "io-lifetimes" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06432fb54d3be7964ecd3649233cddf80db2832f47fec34c01f65b3d9d774983" + +[[package]] +name = "ipnet" +version = "2.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791930b43c0d5973160d90a8f3894509f2b273430f5c5c73b668636d0287c5c0" + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "js-sys" +version = "0.3.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce57d20d1ea864ce2ac172ab472d409214f4fd359f0b2a2775abdf522e2af99e" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "leb128" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c83bff1d572d6b9aeef67ddfc8448e4a3737909cb28e81f97c791b9018703e52" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "libproc" +version = "0.14.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a54ad7278b8bc5301d5ffd2a94251c004feb971feba96c971ea4063645990757" +dependencies = [ + "bindgen", + "errno", + "libc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" + +[[package]] +name = "livesplit-auto-splitting" +version = "0.1.0" +source = "git+https://github.com/LiveSplit/livesplit-core#a065a8d66c2c5151769cc7625f760724b5ea2413" +dependencies = [ + "arc-swap", + "async-trait", + "bstr", + "bytemuck", + "bytes", + "indexmap", + "proc-maps", + "read-process-memory", + "slotmap", + "snafu", + "sysinfo", + "time", + "tokio", + "wasmtime", + "wasmtime-wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "log" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" + +[[package]] +name = "mach2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44" +dependencies = [ + "libc", +] + +[[package]] +name = "mach2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dae608c151f68243f2b000364e1f7b186d9c29845f7d2d85bd31b9ad77ad552b" + +[[package]] +name = "maybe-owned" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4facc753ae494aeb6e3c22f839b158aebd4f9270f55cd3c79906c45476c47ab4" + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "memfd" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57804b2c9b69967f1536a56f86297e367a33b19e98852ed624b84551cdbc0d90" +dependencies = [ + "rustix", +] + +[[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.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b63fbc4a50860e98e7b2aa7804ded1db5cbc3aff9193adaff57a6931bf7c4b4c" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b18443e9c262bfe8fa82f51666e2642c53393f7e5c27b3e1aeab922cff5b9d8" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "multiversion" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ca4bea16ffc3f443cf7d866912118196bfef4c6a1556ca00f9f9b00bb43f7c" +dependencies = [ + "multiversion-macros", +] + +[[package]] +name = "multiversion-macros" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d416831a7317ef4b08bee00b69cbbb9c8763da7959a7026244d6266869f9c83" +dependencies = [ + "proc-macro2", + "quote", + "rustversion", + "syn 3.0.6", +] + +[[package]] +name = "multiversion_no_op" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743fb55ba31b18fb1ecef6bdc9aa2743314978ac084044301a7eee33fb99a20d" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "ntapi" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3b335231dfd352ffb0f8017f3b6027a4917f7df785ea2143d8af2adc66980ae" +dependencies = [ + "winapi", +] + +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags", +] + +[[package]] +name = "objc2-io-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33fafba39597d6dc1fb709123dfa8289d39406734be322956a69f0931c73bb15" +dependencies = [ + "libc", + "objc2-core-foundation", +] + +[[package]] +name = "object" +version = "0.39.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e5a6c098c7a3b6547378093f5cc30bc54fd361ce711e05293a5cc589562739b" +dependencies = [ + "crc32fast", + "hashbrown 0.17.1", + "indexmap", + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "postcard" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24" +dependencies = [ + "cobs", + "embedded-io 0.4.0", + "embedded-io 0.6.1", + "serde", +] + +[[package]] +name = "potential_utf" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proc-maps" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db44c5aa60e193a25fcd93bb9ed27423827e8f118897866f946e2cf936c44fb" +dependencies = [ + "anyhow", + "libc", + "libproc", + "mach2 0.4.3", + "winapi", +] + +[[package]] +name = "pulley-interpreter" +version = "45.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b04adf8f93264685f2c70a3edb8f828c791e71b22659253319c33f29d1165aef" +dependencies = [ + "cranelift-bitset", + "log", + "pulley-macros", + "wasmtime-internal-core", +] + +[[package]] +name = "pulley-macros" +version = "45.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c120a6af1a574a42efcd9df2ad27cb78bc04118c5e0c69e90599f17301a1c7a" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65c9fb96cbc91e3478eaae79a69fcd3f1ae4ad052e471fe6732fff548984b4af" +dependencies = [ + "chacha20", + "getrandom 0.4.3", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + +[[package]] +name = "rayon" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "read-process-memory" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98379bd2cb26a9ad7d0af215816348a41279285fd15e40b398af97108207a9d3" +dependencies = [ + "libc", + "log", + "mach2 0.6.0", + "windows-sys 0.61.2", +] + +[[package]] +name = "regalloc2" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "757712e8e61590d6d4f5d563483755538b5aa13467837a3b41cd9832509a7f85" +dependencies = [ + "allocator-api2", + "bumpalo", + "hashbrown 0.17.1", + "log", + "rustc-hash", + "smallvec", +] + +[[package]] +name = "regex" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b74b56ffa8bb2830709a538c2cbcae9aa062db0d2a42563bfb09bdaae44020eb" + +[[package]] +name = "rustc-hash" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" + +[[package]] +name = "rustix" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891efababe418670775f199f0d233d84843c227a0949a883ce15b37c78d6629d" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustix-linux-procfs" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fc84bf7e9aa16c4f2c758f27412dc9841341e16aa682d9c7ac308fe3ee12056" +dependencies = [ + "once_cell", + "rustix", +] + +[[package]] +name = "rustls" +version = "0.23.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d41d731c7d2f962d1ccc364cec258de3c0e93b38c2fb3ba97ac74513048d634" +dependencies = [ + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "serde_json" +version = "1.0.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "simd-adler32" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "slotmap" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038" +dependencies = [ + "version_check", +] + +[[package]] +name = "smallvec" +version = "1.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba467056f1b547ed52077911161fc86985becbc60e8e1857c8a144dab0def891" +dependencies = [ + "serde", +] + +[[package]] +name = "snafu" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e45cb604038abb7b926b679887b3226d8d0f23874b66623625a0454be425a4b7" +dependencies = [ + "snafu-derive", +] + +[[package]] +name = "snafu-derive" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "287f59010008f0d7cf5e3b03196d666c1acc46c8d3e9cf34c28a1a7157601e72" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "socket2" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "splitter" +version = "0.1.0" +dependencies = [ + "asr", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "901704edd0dfe137f1987838ee4f259e4e063c31371bdb423f7ae38ec6f77f02" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "sysinfo" +version = "0.39.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2071df9448915b71c4fe6d25deaf1c22f12bd234f01540b77312bb8e41361e6" +dependencies = [ + "libc", + "memchr", + "ntapi", + "objc2-core-foundation", + "objc2-io-kit", + "rayon", + "windows", +] + +[[package]] +name = "target-lexicon" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca" + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" +dependencies = [ + "thiserror-impl 2.0.20", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "time" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde_core", + "time-core", +] + +[[package]] +name = "time-core" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" + +[[package]] +name = "tinystr" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tokio" +version = "1.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" +dependencies = [ + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "windows-sys 0.61.2", +] + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "unicode-ident" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "ureq" +version = "3.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a7ac20be9b7726e0bbdbf974c059676d9acb1cd414961f570a4e8231cacd7fc" +dependencies = [ + "base64", + "flate2", + "log", + "percent-encoding", + "rustls", + "rustls-pki-types", + "ureq-proto", + "utf8-zero", + "webpki-roots", +] + +[[package]] +name = "ureq-proto" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f86fd172ccca569e458f61b6bdd6220965a9ef36e672a6852953b51a0e1583be" +dependencies = [ + "base64", + "http", + "httparse", + "log", +] + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8-zero" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8c0a043c9540bae7c578c88f91dda8bd82e59ae27c21baca69c8b191aaf5a6e" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aecb87a33d3b0c5e3b7aa46336eaf486cffafbd281b195e4c8b80d50df2351bf" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a690d511e3c1a8b3a55e33511e3c2c00c78415cd23650f32b808627f5696b9ed" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "411e4887f0071ef2d2164a9d5fdf2d20efbef78fccd3a78b0c10a1dc5295e48a" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 3.0.6", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81941cd78d0c92026c33e5e01312845a4cb1e9af3407f9134b100dd03144103e" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.248.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac92cf547bc18d27ecc521015c08c353b4f18b84ab388bb6d1b6b682c620d9b6" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.248.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa4439c5eee9df71ee0c6efb37f63b1fcb1fec38f85f5142c54e7ed05d33091a" +dependencies = [ + "bitflags", + "hashbrown 0.17.1", + "indexmap", + "semver", + "serde", +] + +[[package]] +name = "wasmprinter" +version = "0.248.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b264a5410b008d4d199a92bf536eae703cbd614482fc1ec53831cf19e1c183" +dependencies = [ + "anyhow", + "termcolor", + "wasmparser", +] + +[[package]] +name = "wasmtime" +version = "45.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a83ef84ac8bab735c89e27b0268b0586099fbe81281ae45be2b8e4f407b2daa" +dependencies = [ + "addr2line", + "async-trait", + "bitflags", + "bumpalo", + "cc", + "cfg-if", + "encoding_rs", + "gimli", + "libc", + "log", + "mach2 0.4.3", + "memfd", + "object", + "once_cell", + "postcard", + "pulley-interpreter", + "rayon", + "rustix", + "semver", + "serde", + "serde_derive", + "smallvec", + "target-lexicon", + "wasmparser", + "wasmtime-environ", + "wasmtime-internal-component-macro", + "wasmtime-internal-component-util", + "wasmtime-internal-core", + "wasmtime-internal-cranelift", + "wasmtime-internal-fiber", + "wasmtime-internal-jit-debug", + "wasmtime-internal-jit-icache-coherence", + "wasmtime-internal-unwinder", + "wasmtime-internal-versioned-export-macros", + "wasmtime-internal-winch", + "windows-sys 0.61.2", +] + +[[package]] +name = "wasmtime-environ" +version = "45.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439686d3deb38525c86b88bbc90f7ba669f70c8edc7d6b35147c738bbef5ff76" +dependencies = [ + "anyhow", + "cpp_demangle", + "cranelift-bforest", + "cranelift-bitset", + "cranelift-entity", + "gimli", + "hashbrown 0.17.1", + "indexmap", + "log", + "object", + "postcard", + "rustc-demangle", + "semver", + "serde", + "serde_derive", + "sha2", + "smallvec", + "target-lexicon", + "wasm-encoder", + "wasmparser", + "wasmprinter", + "wasmtime-internal-component-util", + "wasmtime-internal-core", +] + +[[package]] +name = "wasmtime-internal-component-macro" +version = "45.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81d2c5850fb8c448792453765d97f6f01096a32708f3c36798e86220763c90c0" +dependencies = [ + "anyhow", + "proc-macro2", + "quote", + "syn 2.0.119", + "wasmtime-internal-component-util", + "wasmtime-internal-wit-bindgen", + "wit-parser", +] + +[[package]] +name = "wasmtime-internal-component-util" +version = "45.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ed79c4e9ab416dcc5e46b9d1ec9b7c2e3c730deab525996b0de45a35fc8b2c3" + +[[package]] +name = "wasmtime-internal-core" +version = "45.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3073c03f97f871fe6400e68621c863b93ba79296f8e570285231952d23fcc804" +dependencies = [ + "anyhow", + "hashbrown 0.17.1", + "libm", + "serde", +] + +[[package]] +name = "wasmtime-internal-cranelift" +version = "45.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a15981261d361f9c93b42929f446b4009840853ceea181ad6e17730f4016a0b" +dependencies = [ + "cfg-if", + "cranelift-codegen", + "cranelift-control", + "cranelift-entity", + "cranelift-frontend", + "cranelift-native", + "gimli", + "itertools 0.14.0", + "log", + "object", + "pulley-interpreter", + "smallvec", + "target-lexicon", + "thiserror 2.0.20", + "wasmparser", + "wasmtime-environ", + "wasmtime-internal-core", + "wasmtime-internal-unwinder", + "wasmtime-internal-versioned-export-macros", +] + +[[package]] +name = "wasmtime-internal-fiber" +version = "45.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33aed9d91d54c9f861ba7e3c3130bded8a7a63e01fe58c3a9a36b45d6ee0fb57" +dependencies = [ + "cc", + "cfg-if", + "libc", + "rustix", + "wasmtime-environ", + "wasmtime-internal-versioned-export-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "wasmtime-internal-jit-debug" +version = "45.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "944942118aab67e7b4febfe88a65d0af4cfd10af8ed0e79fd6cf39d75359ab7e" +dependencies = [ + "cc", + "wasmtime-internal-versioned-export-macros", +] + +[[package]] +name = "wasmtime-internal-jit-icache-coherence" +version = "45.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37dee05e8c35759826f6b926cd949c51f771b6a58619d8e60c63c3f3d3e5e59b" +dependencies = [ + "cfg-if", + "libc", + "wasmtime-internal-core", + "windows-sys 0.61.2", +] + +[[package]] +name = "wasmtime-internal-unwinder" +version = "45.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a83f7ebe911a6f1605ff0d3a678472ddb1fcc57c3e2f6bae5dd4d170b625b3ed" +dependencies = [ + "cfg-if", + "cranelift-codegen", + "log", + "object", + "wasmtime-environ", +] + +[[package]] +name = "wasmtime-internal-versioned-export-macros" +version = "45.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d75980644456dc003238766254b0e5f002704630237ccd0728747991fe2739d7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "wasmtime-internal-winch" +version = "45.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81af36995b4720b32e3d667541b548a27184a859d09a6ee745eaba095f5a6f6e" +dependencies = [ + "cranelift-codegen", + "gimli", + "log", + "object", + "target-lexicon", + "wasmparser", + "wasmtime-environ", + "wasmtime-internal-cranelift", + "winch-codegen", +] + +[[package]] +name = "wasmtime-internal-wit-bindgen" +version = "45.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ada06667b7948892703fcc9f0b9b337c2e78c334d74d4fa62e2f75f07fbb3659" +dependencies = [ + "anyhow", + "bitflags", + "heck 0.5.0", + "indexmap", + "wit-parser", +] + +[[package]] +name = "wasmtime-wasi" +version = "45.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd8e9db77f7b60f4c5f6f72847f55eb646ed7ed2f68e362559ee07dc543fb408" +dependencies = [ + "async-trait", + "bitflags", + "bytes", + "cap-fs-ext", + "cap-net-ext", + "cap-std", + "cap-time-ext", + "cfg-if", + "fs-set-times", + "futures", + "io-extras", + "io-lifetimes", + "rand", + "rustix", + "thiserror 2.0.20", + "tokio", + "tracing", + "url", + "wasmtime", + "wasmtime-wasi-io", + "wiggle", + "windows-sys 0.61.2", +] + +[[package]] +name = "wasmtime-wasi-io" +version = "45.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3805f02be91374a4fd02c0f947daf07bbaa6fbebb5909de5305fa5b0e9568f30" +dependencies = [ + "async-trait", + "bytes", + "futures", + "tracing", + "wasmtime", +] + +[[package]] +name = "wast" +version = "35.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ef140f1b49946586078353a453a1d28ba90adfc54dde75710bc1931de204d68" +dependencies = [ + "leb128", +] + +[[package]] +name = "webpki-roots" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dcd9d09a39985f5344844e66b0c530a33843579125f23e21e9f0f220850f22a" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "wiggle" +version = "45.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4aca130e25a57e29bbb541441b6e261970a8469580bffebe904d96f0df67e41d" +dependencies = [ + "bitflags", + "thiserror 2.0.20", + "tracing", + "wasmtime", + "wasmtime-environ", + "wiggle-macro", +] + +[[package]] +name = "wiggle-generate" +version = "45.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f0a45b47e893521cad81819f2e0db18a8b05086fd4bfb35369ed8830a8f0148" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.119", + "wasmtime-environ", + "witx", +] + +[[package]] +name = "wiggle-macro" +version = "45.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e297e0325cffa6d368386b6e672e9d6c6a7ad34bdb8a5f49319db4decef2a990" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "wiggle-generate", +] + +[[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.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.52.0", +] + +[[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 = "winch-codegen" +version = "45.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d73bc25d27222248f9bafc7e9945f61e74275360c3ea0d34008810d436140a53" +dependencies = [ + "cranelift-assembler-x64", + "cranelift-codegen", + "gimli", + "regalloc2", + "smallvec", + "target-lexicon", + "thiserror 2.0.20", + "wasmparser", + "wasmtime-environ", + "wasmtime-internal-core", + "wasmtime-internal-cranelift", +] + +[[package]] +name = "windows" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" +dependencies = [ + "windows-collections", + "windows-core", + "windows-future", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" +dependencies = [ + "windows-core", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-future" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" +dependencies = [ + "windows-core", + "windows-link", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-numerics" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" +dependencies = [ + "windows-core", + "windows-link", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows-threading" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winx" +version = "0.36.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f3fd376f71958b862e7afb20cfe5a22830e1963462f3a17f49d82a6c1d1f42d" +dependencies = [ + "bitflags", + "windows-sys 0.52.0", +] + +[[package]] +name = "wit-parser" +version = "0.248.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "247ad505da2915a082fe13204c5ba8788425aea1de54f43b284818cf82637856" +dependencies = [ + "anyhow", + "hashbrown 0.17.1", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "witx" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e366f27a5cabcddb2706a78296a40b8fcc451e1a6aba2fc1d94b4a01bdaaef4b" +dependencies = [ + "anyhow", + "log", + "thiserror 1.0.69", + "wast", +] + +[[package]] +name = "writeable" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" + +[[package]] +name = "yoke" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33811428bee40dbceb6d545e95754741d17a6aef9a4849f0fd62e2ba4f412a78" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", + "synstructure", +] + +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f75b4683f6c7f45248d4d64056a24298c6281e0993356d7d1b4a1a962ef10d4a" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" + +[[package]] +name = "zerotrie" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "zip" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fabe6324e908f85a1c52063ce7aa26b68dcb7eb6dbc83a2d148403c9bc3eba50" +dependencies = [ + "arbitrary", + "crc32fast", + "crossbeam-utils", + "displaydoc", + "flate2", + "indexmap", + "memchr", + "thiserror 2.0.20", + "zopfli", +] + +[[package]] +name = "zlib-rs" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b268e58e7c693d7c271f93ffc4ba3b380412554231c85bf61ca7af91042a4112" + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" + +[[package]] +name = "zopfli" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249" +dependencies = [ + "bumpalo", + "crc32fast", + "log", + "simd-adler32", +] diff --git a/harness/Cargo.toml b/harness/Cargo.toml new file mode 100644 index 00000000..3da609db --- /dev/null +++ b/harness/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "harness" +version = "0.1.0" +edition = "2021" +publish = false + +[dependencies] +livesplit-auto-splitting = { git = "https://github.com/LiveSplit/livesplit-core" } +serde = { version = "1", features = ["derive"] } +serde_json = "1" +sha2 = "0.10" +time = "0.3" +ureq = "3" +zip = { version = "2", default-features = false, features = ["deflate"] } + +[workspace] +members = ["splitter"] diff --git a/harness/README.md b/harness/README.md new file mode 100644 index 00000000..8d2af25d --- /dev/null +++ b/harness/README.md @@ -0,0 +1,18 @@ +# Harness + +Runs the Unity players from [auto-splitting-test-fixtures](https://github.com/LiveSplit/auto-splitting-test-fixtures) against an auto splitter built on asr and checks what the auto splitter reads against `unity-fixture.json`, the file that says what every player holds. + +``` +cargo run --release +``` + +The first run fetches the players for this machine's platform from the fixtures releases, checks each zip against the manifest and unpacks it under `target/fixtures`. Set `ASR_FIXTURES` to keep them somewhere else. Every run after that starts from the cache. The players are pinned to one commit of the fixtures repo, `FIXTURES` in `src/main.rs`, so a new corpus is a bump of that constant. + +Each player starts headless with `-batchmode -nographics`. The auto splitter in `splitter/` attaches to it, reads the scenes, the objects, and the `FixtureData` statics and fields, and reports every value through `timer::set_variable` under its path in the contract, such as `scenes.0.roots.0.name`. The run prints one line per player and, for a failing one, what was wrong and what the auto splitter said. A player fails when a reported value differs from the contract, when a required path is missing, or when the auto splitter has not said it is done after 90 seconds. + +An argument narrows the run to the players whose version or variant contains it: + +``` +cargo run --release -- 6000.5.10f1 +cargo run --release -- win-x86-il2cpp +``` diff --git a/harness/splitter/Cargo.toml b/harness/splitter/Cargo.toml new file mode 100644 index 00000000..fabb0e27 --- /dev/null +++ b/harness/splitter/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "splitter" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +crate-type = ["cdylib"] + +[dependencies] +asr = { path = "../..", features = ["unity", "alloc", "float-vars", "integer-vars"] } diff --git a/harness/splitter/src/lib.rs b/harness/splitter/src/lib.rs new file mode 100644 index 00000000..52e9dbde --- /dev/null +++ b/harness/splitter/src/lib.rs @@ -0,0 +1,382 @@ +//! The auto splitter the harness runs against every fixture player. It +//! reads what the public API can read of the fixture and reports each +//! value under its path in `unity-fixture.json`, such as +//! `scenes.0.roots.0.name` or `classes.FixtureData.fields.hero.level`, +//! then `done`. + +use asr::{ + arrayvec::ArrayVec, + future::{next_tick, retry}, + game_engine::unity::{il2cpp, mono, scene_manager::SceneManager}, + timer, Address, PointerSize, Process, +}; + +asr::async_main!(stable); + +/// How many UTF-16 units or elements a read holds at most. The fixture's +/// strings and collections are far below it. +const CAP: usize = 64; + +async fn main() { + loop { + let process = retry(|| { + ["fixture.exe", "fixture"] + .iter() + .find_map(|name| Process::attach(name)) + }) + .await; + asr::print_message("attached to the player"); + process + .until_closes(async { + report(&process).await; + loop { + next_tick().await; + } + }) + .await; + } +} + +fn set(key: &str, value: impl AsRef) { + timer::set_variable(key, value.as_ref()); +} + +/// The scripting backend a player runs, told by the runtime library it +/// loads. The library comes in some time after the process, so the choice +/// waits for one of them. +enum Backend { + Mono, + Il2cpp, +} + +async fn report(process: &Process) { + let backend = retry(|| { + if has_module( + process, + &["GameAssembly.dll", "GameAssembly.so", "GameAssembly.dylib"], + ) { + Some(Backend::Il2cpp) + } else if has_module( + process, + &[ + "mono.dll", + "mono-2.0-bdwgc.dll", + "libmono.so", + "libmonobdwgc-2.0.so", + "libmonobdwgc-2.0.dylib", + ], + ) { + Some(Backend::Mono) + } else { + None + } + }) + .await; + match backend { + Backend::Il2cpp => { + set("runtime", "il2cpp"); + let module = il2cpp::Module::wait_attach_auto_detect(process).await; + asr::print_message("attached to il2cpp"); + report_classes(process, &Il2cpp(module)).await; + } + Backend::Mono => { + set("runtime", "mono"); + let module = mono::Module::wait_attach_auto_detect(process).await; + asr::print_message("attached to mono"); + report_classes(process, &Mono(module)).await; + } + } + report_scenes(process).await; + set("done", "1"); +} + +fn has_module(process: &Process, names: &[&str]) -> bool { + names + .iter() + .any(|name| process.get_module_address(name).is_ok()) +} + +/// Reports the scene list, the Boot scene's hierarchy under `Foo`, and +/// the root under DontDestroyOnLoad. The second scene is loaded by the +/// fixture's own `Awake`, so the list is read once it holds both. +async fn report_scenes(process: &Process) { + let scenes = SceneManager::wait_attach(process).await; + asr::print_message("attached to the scene manager"); + + // A scene still loading sits in the list with no path and a build + // index of -1, so the list is read whole until both scenes are in it + // for real. + let loaded = retry(|| { + let loaded: Vec<(String, i32)> = scenes + .scenes(process) + .map(|scene| { + let path = scene.path::<128>(process, &scenes).ok()?; + let path = String::from(path.validate_utf8().ok()?); + let index = scene.index(process, &scenes).ok()?; + (!path.is_empty() && index >= 0).then_some((path, index)) + }) + .collect::>()?; + (loaded.len() == 2).then_some(loaded) + }) + .await; + asr::print_message("both scenes are loaded"); + for (index, (path, build_index)) in loaded.iter().enumerate() { + set(&format!("scenes.{index}.path"), path); + timer::set_variable_int(&format!("scenes.{index}.buildIndex"), *build_index); + } + + let root = retry(|| scenes.get_root_game_object(process, "Foo").ok()).await; + report_transform(process, &scenes, &root, "scenes.0.roots.0"); + + let fixture = retry(|| { + scenes + .get_game_object_from_dont_destroy_on_load(process, "Fixture") + .ok() + }) + .await; + report_transform(process, &scenes, &fixture, "dontDestroyOnLoad.roots.0"); +} + +/// Reports a transform's name and, in order, its children under the +/// path's `children` list. +fn report_transform( + process: &Process, + scenes: &SceneManager, + transform: &asr::game_engine::unity::scene_manager::Transform, + path: &str, +) { + if let Ok(name) = transform.get_name::<128>(process, scenes) { + set(&format!("{path}.name"), name.validate_utf8().unwrap_or("")); + } + if let Ok(children) = transform.children(process, scenes) { + for (index, child) in children.enumerate() { + report_transform(process, scenes, &child, &format!("{path}.children.{index}")); + } + } +} + +/// The reads the harness needs from a scripting backend, the same on +/// both. +trait Managed { + type Class; + fn pointer_size(&self) -> PointerSize; + fn class(&self, process: &Process, name: &str) -> Option; + fn field_offset(&self, process: &Process, class: &Self::Class, field: &str) -> Option; + fn static_table(&self, process: &Process, class: &Self::Class) -> Option
; + fn string(&self, process: &Process, at: Address) -> Option; + fn ints(&self, process: &Process, at: Address) -> Option>; + fn int_list(&self, process: &Process, at: Address) -> Option>; + fn string_list(&self, process: &Process, at: Address) -> Option>; + fn string_int_dictionary(&self, process: &Process, at: Address) -> Option>; +} + +struct Mono(mono::Module); +struct Il2cpp(il2cpp::Module); + +macro_rules! managed { + ($wrapper:ident, $class:path) => { + impl Managed for $wrapper { + type Class = $class; + + fn pointer_size(&self) -> PointerSize { + self.0.get_pointer_size() + } + + fn class(&self, process: &Process, name: &str) -> Option<$class> { + let image = self.0.get_image(process, "Assembly-CSharp")?; + image.get_class(process, &self.0, name) + } + + fn field_offset(&self, process: &Process, class: &$class, field: &str) -> Option { + class.get_field_offset(process, &self.0, field) + } + + fn static_table(&self, process: &Process, class: &$class) -> Option
{ + class.get_static_table(process, &self.0) + } + + fn string(&self, process: &Process, at: Address) -> Option { + let string = self.0.read_string::(process, at).ok()?; + Some(String::from_utf16_lossy(&string)) + } + + fn ints(&self, process: &Process, at: Address) -> Option> { + self.0.read_array::(process, at).ok() + } + + fn int_list(&self, process: &Process, at: Address) -> Option> { + let offsets = self.0.get_list_offsets(process, at)?; + self.0.read_list::(process, offsets, at).ok() + } + + fn string_list(&self, process: &Process, at: Address) -> Option> { + let offsets = self.0.get_list_offsets(process, at)?; + let objects = self + .0 + .read_reference_list::(process, offsets, at) + .ok()?; + objects + .iter() + .map(|object| { + let string = self.0.read_string_object::(process, *object).ok()?; + Some(String::from_utf16_lossy(&string)) + }) + .collect() + } + + fn string_int_dictionary( + &self, + process: &Process, + at: Address, + ) -> Option> { + let offsets = self.0.get_dictionary_offsets(process, at)?; + let pairs: Vec<(u64, i32)> = match self.pointer_size() { + PointerSize::Bit64 => self + .0 + .read_dictionary::(process, offsets, at) + .ok()? + .into_iter() + .collect(), + _ => self + .0 + .read_dictionary::(process, offsets, at) + .ok()? + .into_iter() + .map(|(key, value)| (key as u64, value)) + .collect(), + }; + pairs + .into_iter() + .map(|(key, value)| { + let string = self + .0 + .read_string_object::(process, Address::new(key)) + .ok()?; + Some((String::from_utf16_lossy(&string), value)) + }) + .collect() + } + } + }; +} + +managed!(Mono, mono::Class); +managed!(Il2cpp, il2cpp::Class); + +/// Reports `FixtureData`'s statics and the fields of its instance under +/// `classes.FixtureData`. `Awake` sets `Instance` first and `dict` last, so +/// the reads wait for both. +async fn report_classes(process: &Process, module: &impl Managed) { + let class = retry(|| module.class(process, "FixtureData")).await; + asr::print_message("found FixtureData"); + let statics = retry(|| module.static_table(process, &class)).await; + let field = |name: &str| module.field_offset(process, &class, name); + let pointer = module.pointer_size(); + + let instance = retry(|| { + let at = statics + field("Instance")?; + process + .read_pointer(at, pointer) + .ok() + .filter(|address| !address.is_null()) + }) + .await; + asr::print_message("FixtureData.Instance is set"); + retry(|| { + process + .read_pointer(instance + field("dict")?, pointer) + .ok() + .filter(|address| !address.is_null()) + }) + .await; + + let prefix = "classes.FixtureData"; + if let Some(offset) = field("StaticInt") { + if let Ok(value) = process.read::(statics + offset) { + timer::set_variable_int(&format!("{prefix}.statics.StaticInt"), value); + } + } + if let Some(value) = + field("StaticString").and_then(|offset| module.string(process, statics + offset)) + { + set(&format!("{prefix}.statics.StaticString"), value); + } + + let at = |name: &str| field(name).map(|offset| instance + offset); + if let Some(at) = at("intValue") { + if let Ok(value) = process.read::(at) { + timer::set_variable_int(&format!("{prefix}.fields.intValue"), value); + } + } + if let Some(at) = at("longValue") { + if let Ok(value) = process.read::(at) { + timer::set_variable_int(&format!("{prefix}.fields.longValue"), value); + } + } + if let Some(at) = at("floatValue") { + if let Ok(value) = process.read::(at) { + timer::set_variable_float(&format!("{prefix}.fields.floatValue"), value); + } + } + if let Some(at) = at("doubleValue") { + if let Ok(value) = process.read::(at) { + timer::set_variable_float(&format!("{prefix}.fields.doubleValue"), value); + } + } + if let Some(at) = at("boolValue") { + if let Ok(value) = process.read::(at) { + set( + &format!("{prefix}.fields.boolValue"), + if value != 0 { "true" } else { "false" }, + ); + } + } + if let Some(value) = at("stringValue").and_then(|at| module.string(process, at)) { + set(&format!("{prefix}.fields.stringValue"), value); + } + + // `hero` is a `Hero`, a class of its own, so the field holds a + // reference and the object's fields sit at Hero's offsets. + let hero = at("hero").and_then(|at| process.read_pointer(at, pointer).ok()); + if let (Some(hero), Some(hero_class)) = (hero, module.class(process, "Hero")) { + let hero_field = |name: &str| { + module + .field_offset(process, &hero_class, name) + .map(|offset| hero + offset) + }; + if let Some(at) = hero_field("id") { + if let Ok(value) = process.read::(at) { + timer::set_variable_int(&format!("{prefix}.fields.hero.id"), value); + } + } + if let Some(at) = hero_field("level") { + if let Ok(value) = process.read::(at) { + timer::set_variable_int(&format!("{prefix}.fields.hero.level"), value); + } + } + if let Some(value) = hero_field("title").and_then(|at| module.string(process, at)) { + set(&format!("{prefix}.fields.hero.title"), value); + } + } + + if let Some(values) = at("intArray").and_then(|at| module.ints(process, at)) { + for (index, value) in values.iter().enumerate() { + timer::set_variable_int(&format!("{prefix}.fields.intArray.{index}"), *value); + } + } + if let Some(values) = at("intList").and_then(|at| module.int_list(process, at)) { + for (index, value) in values.iter().enumerate() { + timer::set_variable_int(&format!("{prefix}.fields.intList.{index}"), *value); + } + } + if let Some(values) = at("stringList").and_then(|at| module.string_list(process, at)) { + for (index, value) in values.iter().enumerate() { + set(&format!("{prefix}.fields.stringList.{index}"), value); + } + } + if let Some(pairs) = at("dict").and_then(|at| module.string_int_dictionary(process, at)) { + for (key, value) in pairs { + timer::set_variable_int(&format!("{prefix}.fields.dict.{key}"), value); + } + } +} diff --git a/harness/src/main.rs b/harness/src/main.rs new file mode 100644 index 00000000..c70bbfcf --- /dev/null +++ b/harness/src/main.rs @@ -0,0 +1,619 @@ +//! Runs the Unity fixture players against an auto splitter built on asr +//! and checks what it reads against the fixtures repo's contract. + +use std::{ + collections::BTreeMap, + fmt, fs, + path::{Path, PathBuf}, + process::{self, Command, Stdio}, + sync::{ + mpsc::{self, RecvTimeoutError}, + Arc, Mutex, + }, + thread, + time::{Duration, Instant}, +}; + +use livesplit_auto_splitting::{ + settings, CompiledAutoSplitter, Config, LogLevel, Runtime, Timer, TimerState, +}; +use serde::Deserialize; +use sha2::{Digest, Sha256}; + +/// The commit of the fixtures repo whose manifest and contract are used. +const FIXTURES: &str = "fe04cdfcb2b614e11b7378d4a3de9d82a0ffc404"; + +/// One asset of the fixtures repo: a player for one Unity version and +/// variant, with what the manifest says about the zip. +#[derive(Deserialize)] +struct Entry { + version: String, + variant: String, + asset: String, + size: u64, + sha256: String, +} + +#[derive(Deserialize)] +struct Manifest { + fixtures: Vec, +} + +/// The name of the zip an entry points at. +fn zip_name(entry: &Entry) -> &str { + entry.asset.rsplit('/').next().unwrap_or(&entry.asset) +} + +/// The entries whose players run on a platform, such as `win` or `linux`. +fn on<'a>(platform: &'a str, entries: &'a [Entry]) -> impl Iterator { + entries.iter().filter(move |entry| { + entry.variant.starts_with(platform) && entry.variant[platform.len()..].starts_with('-') + }) +} + +/// The platform this harness runs on, in the words the variants use. +fn host() -> &'static str { + match std::env::consts::OS { + "windows" => "win", + "macos" => "mac", + os => os, + } +} + +/// Checks a zip against the size and digest the manifest holds for it. +fn verify(bytes: &[u8], entry: &Entry) -> Result<(), String> { + if bytes.len() as u64 != entry.size { + return Err(format!( + "{} is {} bytes, the manifest says {}", + zip_name(entry), + bytes.len(), + entry.size + )); + } + let digest = format!("{:x}", Sha256::digest(bytes)); + if digest != entry.sha256 { + return Err(format!( + "{} hashes {digest}, the manifest says {}", + zip_name(entry), + entry.sha256 + )); + } + Ok(()) +} + +fn fail(message: &str) -> ! { + eprintln!("{message}"); + process::exit(1); +} + +/// Downloads one URL whole. +fn fetch(url: &str) -> Vec { + let mut response = ureq::get(url) + .call() + .unwrap_or_else(|error| fail(&format!("fetching {url}: {error}"))); + response + .body_mut() + .with_config() + .limit(4 << 30) + .read_to_vec() + .unwrap_or_else(|error| fail(&format!("reading {url}: {error}"))) +} + +/// The directory the zips and the unpacked players are kept in, next to +/// the build output unless `ASR_FIXTURES` says otherwise. +fn cache() -> PathBuf { + match std::env::var_os("ASR_FIXTURES") { + Some(dir) => PathBuf::from(dir), + None => Path::new(env!("CARGO_MANIFEST_DIR")) + .join("target") + .join("fixtures"), + } +} + +/// The directory an entry's player is unpacked into, fetching and checking +/// the zip on the way when the cache has no good copy of it. +fn player(entry: &Entry, cache: &Path) -> PathBuf { + let name = zip_name(entry); + let zip = cache.join(name); + let unpacked = cache.join(name.strip_suffix(".zip").unwrap_or(name)); + if unpacked.is_dir() { + return unpacked; + } + + let bytes = match fs::read(&zip) + .ok() + .filter(|bytes| verify(bytes, entry).is_ok()) + { + Some(bytes) => bytes, + None => { + println!("fetching {name}"); + let bytes = fetch(&entry.asset); + verify(&bytes, entry).unwrap_or_else(|error| fail(&error)); + fs::write(&zip, &bytes).expect("writing the zip"); + bytes + } + }; + + // The player is unpacked beside its final place and moved there whole, + // so a run that dies halfway leaves no directory that passes as done. + let part = cache.join(format!("{}.part", zip_name(entry))); + let mut archive = zip::ZipArchive::new(std::io::Cursor::new(bytes)).expect("opening the zip"); + archive.extract(&part).expect("unpacking the player"); + fs::rename(&part, &unpacked).expect("moving the player into place"); + unpacked +} + +/// Every value in the contract under its path: object keys and array +/// indices joined by dots, such as `scenes.0.roots.0.name`. +fn leaves(contract: &serde_json::Value) -> BTreeMap { + fn walk(value: &serde_json::Value, path: &str, into: &mut BTreeMap) { + let child = |key: &str| match path { + "" => key.to_string(), + _ => format!("{path}.{key}"), + }; + match value { + serde_json::Value::Object(fields) => { + for (key, value) in fields { + walk(value, &child(key), into); + } + } + serde_json::Value::Array(items) => { + for (index, value) in items.iter().enumerate() { + walk(value, &child(&index.to_string()), into); + } + } + serde_json::Value::String(text) => { + into.insert(path.to_string(), text.clone()); + } + _ => { + into.insert(path.to_string(), value.to_string()); + } + } + } + let mut into = BTreeMap::new(); + walk(contract, "", &mut into); + into +} + +/// The paths the auto splitter reads today. A player passes when every +/// one is reported and everything reported matches the contract. +const REQUIRED: &[&str] = &[ + "scenes.0.path", + "scenes.0.buildIndex", + "scenes.1.path", + "scenes.1.buildIndex", + "scenes.0.roots.0.name", + "scenes.0.roots.0.children.0.name", + "scenes.0.roots.0.children.0.children.0.name", + "scenes.0.roots.0.children.0.children.0.children.0.name", + "scenes.0.roots.0.children.0.children.0.children.1.name", + "scenes.0.roots.0.children.0.children.0.children.1.children.0.name", + "dontDestroyOnLoad.roots.0.name", + "classes.FixtureData.statics.StaticInt", + "classes.FixtureData.statics.StaticString", + "classes.FixtureData.fields.intValue", + "classes.FixtureData.fields.longValue", + "classes.FixtureData.fields.floatValue", + "classes.FixtureData.fields.doubleValue", + "classes.FixtureData.fields.boolValue", + "classes.FixtureData.fields.stringValue", + "classes.FixtureData.fields.hero.id", + "classes.FixtureData.fields.hero.level", + "classes.FixtureData.fields.hero.title", + "classes.FixtureData.fields.intArray.0", + "classes.FixtureData.fields.intArray.1", + "classes.FixtureData.fields.intArray.2", + "classes.FixtureData.fields.intList.0", + "classes.FixtureData.fields.intList.1", + "classes.FixtureData.fields.intList.2", + "classes.FixtureData.fields.stringList.0", + "classes.FixtureData.fields.stringList.1", + "classes.FixtureData.fields.stringList.2", + "classes.FixtureData.fields.dict.one", + "classes.FixtureData.fields.dict.two", + "classes.FixtureData.fields.dict.three", +]; + +/// Returns the paths a player of the Unity version has to report. No +/// scene manager build goes below 5.6, so older players only report the +/// classes. Unity 5.6.0 and 5.6.1 have no `DontDestroyOnLoad` scene, so +/// those players report no `dontDestroyOnLoad` paths. +fn required(version: &str) -> impl Iterator { + // The patch part starts with digits and goes on with letters, as in `0f1`. + let number = |part: &str| { + let digits = part.split(|c: char| !c.is_ascii_digit()).next()?; + digits.parse::().ok() + }; + let mut parts = version.split('.').map(number); + let unity = ( + parts.next().flatten().unwrap_or(0), + parts.next().flatten().unwrap_or(0), + parts.next().flatten().unwrap_or(0), + ); + REQUIRED.iter().copied().filter(move |path| { + if unity < (5, 6, 0) { + path.starts_with("classes.") + } else if unity < (5, 6, 2) { + !path.starts_with("dontDestroyOnLoad.") + } else { + true + } + }) +} + +/// The variables the auto splitter sets about the run itself, not about +/// the contract. +const ABOUT_THE_RUN: &[&str] = &["runtime", "done"]; + +/// Compares what the auto splitter reported with the contract. Numbers +/// compare as numbers, so `2.50` matches `2.5`. +fn check( + reported: &BTreeMap, + contract: &BTreeMap, + required: &[&str], +) -> Vec { + let same = |a: &str, b: &str| { + a == b || matches!((a.parse::(), b.parse::()), (Ok(a), Ok(b)) if a == b) + }; + let mut problems = Vec::new(); + for (path, value) in reported { + if ABOUT_THE_RUN.contains(&path.as_str()) { + continue; + } + match contract.get(path) { + Some(expected) if same(value, expected) => {} + Some(expected) => { + problems.push(format!("{path} is {value}, the contract says {expected}")) + } + None => problems.push(format!("{path} is not in the contract")), + } + } + for path in required { + if !reported.contains_key(*path) { + problems.push(format!("{path} is missing")); + } + } + problems +} + +/// What the auto splitter told the timer: the variables it set and the +/// messages it printed. +#[derive(Default)] +struct Reported { + variables: BTreeMap, + messages: Vec, +} + +/// A timer that only remembers. The auto splitter never starts or splits; +/// it reports through variables. +struct Report(Arc>); + +impl Timer for Report { + fn state(&self) -> TimerState { + TimerState::NotRunning + } + fn current_split_index(&self) -> Option { + None + } + fn segment_splitted(&self, _index: usize) -> Option { + None + } + fn start(&mut self) {} + fn split(&mut self) {} + fn skip_split(&mut self) {} + fn undo_split(&mut self) {} + fn reset(&mut self) {} + fn set_game_time(&mut self, _time: time::Duration) {} + fn pause_game_time(&mut self) {} + fn resume_game_time(&mut self) {} + fn set_variable(&mut self, key: &str, value: &str) { + self.0 + .lock() + .unwrap() + .variables + .insert(key.into(), value.into()); + } + fn log_auto_splitter(&mut self, message: fmt::Arguments) { + self.0.lock().unwrap().messages.push(message.to_string()); + } + fn log_runtime(&mut self, message: fmt::Arguments, _level: LogLevel) { + self.0.lock().unwrap().messages.push(message.to_string()); + } +} + +/// Builds the auto splitter for wasm and reads the module back. The build +/// goes to `target` next to the harness even when `CARGO_TARGET_DIR` points +/// elsewhere, so the module read back is always the one just built. +fn splitter() -> Vec { + let root = Path::new(env!("CARGO_MANIFEST_DIR")); + let target = root.join("target"); + let status = Command::new(env!("CARGO")) + .args([ + "build", + "--release", + "-p", + "splitter", + "--target", + "wasm32-unknown-unknown", + ]) + .arg("--target-dir") + .arg(&target) + .current_dir(root) + .status() + .expect("running cargo"); + if !status.success() { + fail("the auto splitter did not build"); + } + fs::read(target.join("wasm32-unknown-unknown/release/splitter.wasm")) + .expect("reading the auto splitter") +} + +/// How long a player gets to start and be read before the run counts as +/// stuck. +const PATIENCE: Duration = Duration::from_secs(90); + +/// Starts a player headless, runs the auto splitter against it until it +/// says it is done or the patience runs out, and stops the player. +fn run(player: &Path, splitter: &CompiledAutoSplitter, log: &Path) -> Reported { + let executable = player.join(if cfg!(windows) { + "fixture.exe" + } else { + "fixture" + }); + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + fs::set_permissions(&executable, fs::Permissions::from_mode(0o755)) + .expect("marking the player executable"); + } + let mut child = Command::new(&executable) + .args(["-batchmode", "-nographics", "-logFile"]) + .arg(log) + .current_dir(player) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn() + .unwrap_or_else(|error| fail(&format!("starting {}: {error}", executable.display()))); + + let reported = Arc::new(Mutex::new(Reported::default())); + let auto_splitter = splitter + .instantiate(Report(reported.clone()), None::, None) + .unwrap_or_else(|error| fail(&format!("instantiating the auto splitter: {error}"))); + // An update that never returns would never see the patience run out, so + // a second thread interrupts it once the time is up. + let (finished, waiting) = mpsc::channel::<()>(); + let interrupt = auto_splitter.interrupt_handle(); + let timeout = thread::spawn(move || { + if waiting.recv_timeout(PATIENCE) == Err(RecvTimeoutError::Timeout) { + interrupt.interrupt(); + } + }); + let started = Instant::now(); + while started.elapsed() < PATIENCE { + if let Err(error) = auto_splitter.lock().update() { + let message = if started.elapsed() >= PATIENCE { + format!("the auto splitter was still running after {PATIENCE:?}") + } else { + format!("the auto splitter trapped: {error}") + }; + reported.lock().unwrap().messages.push(message); + break; + } + if reported.lock().unwrap().variables.contains_key("done") { + break; + } + thread::sleep(auto_splitter.tick_rate()); + } + drop(finished); + let _ = timeout.join(); + drop(auto_splitter); + let _ = child.kill(); + let _ = child.wait(); + Arc::try_unwrap(reported) + .ok() + .expect("the auto splitter is gone") + .into_inner() + .unwrap() +} + +fn main() { + let only = std::env::args().nth(1).unwrap_or_default(); + let raw = format!( + "https://raw.githubusercontent.com/LiveSplit/auto-splitting-test-fixtures/{FIXTURES}" + ); + let manifest: Manifest = serde_json::from_slice(&fetch(&format!("{raw}/manifest.json"))) + .unwrap_or_else(|error| fail(&format!("manifest.json: {error}"))); + let contract: serde_json::Value = + serde_json::from_slice(&fetch(&format!("{raw}/unity-fixture.json"))) + .unwrap_or_else(|error| fail(&format!("unity-fixture.json: {error}"))); + let contract = leaves(&contract); + for path in REQUIRED { + if !contract.contains_key(*path) { + fail(&format!("{path} is required but not in the contract")); + } + } + let cache = cache(); + fs::create_dir_all(&cache).expect("creating the cache"); + + let runtime = Runtime::new(Config::default()).expect("creating the runtime"); + let splitter = runtime + .compile(&splitter()) + .expect("compiling the auto splitter"); + + let mut failed = 0; + for entry in on(host(), &manifest.fixtures) { + let name = format!("{} {}", entry.version, entry.variant); + if !name.contains(&only) { + continue; + } + let dir = player(entry, &cache); + let log = cache.join(format!("{}.log", zip_name(entry).trim_end_matches(".zip"))); + let reported = run(&dir, &splitter, &log); + let required: Vec<_> = required(&entry.version).collect(); + let problems = check(&reported.variables, &contract, &required); + let runtime = reported + .variables + .get("runtime") + .map_or("?", String::as_str); + let verdict = if problems.is_empty() { "ok" } else { "FAIL" }; + println!("{name:40} {runtime:7} {verdict}"); + if !problems.is_empty() { + failed += 1; + for problem in &problems { + println!(" {problem}"); + } + for message in &reported.messages { + println!(" > {message}"); + } + for (path, value) in &reported.variables { + println!(" = {path}: {value}"); + } + // The player's own log says whether it started at all. + let tail = fs::read_to_string(&log).unwrap_or_default(); + for line in tail + .lines() + .rev() + .take(20) + .collect::>() + .into_iter() + .rev() + { + println!(" | {line}"); + } + } + } + if failed > 0 { + fail(&format!("{failed} players failed")); + } +} + +#[cfg(test)] +mod tests { + use super::{on, verify, Entry}; + use std::collections::BTreeMap; + + fn entry(variant: &str) -> Entry { + Entry { + version: "6000.5.10f1".into(), + variant: variant.into(), + asset: format!("https://example.test/unity-6000.5.10f1-{variant}.zip"), + size: 3, + sha256: "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad".into(), + } + } + + #[test] + fn picks_the_variants_of_the_host_platform() { + let entries = [ + entry("win-x64-mono-bdwgc"), + entry("win-x86-il2cpp-release"), + entry("linux-x64-mono-bdwgc"), + entry("mac-mono-bdwgc"), + ]; + let names = |platform| { + on(platform, &entries) + .map(|entry| entry.variant.as_str()) + .collect::>() + }; + assert_eq!( + names("win"), + ["win-x64-mono-bdwgc", "win-x86-il2cpp-release"] + ); + assert_eq!(names("linux"), ["linux-x64-mono-bdwgc"]); + assert_eq!(names("mac"), ["mac-mono-bdwgc"]); + } + + #[test] + fn flattens_the_contract_into_paths() { + let contract = serde_json::json!({ + "scenes": [{ "path": "Assets/Scenes/Boot.unity", "buildIndex": 0, "roots": [{ "name": "Foo" }] }], + "classes": { "FixtureData": { "fields": { "floatValue": 3.5, "boolValue": true, "dict": { "one": 1 } } } } + }); + let leaves = super::leaves(&contract); + let pairs: Vec<_> = leaves + .iter() + .map(|(path, value)| (path.as_str(), value.as_str())) + .collect(); + assert_eq!( + pairs, + [ + ("classes.FixtureData.fields.boolValue", "true"), + ("classes.FixtureData.fields.dict.one", "1"), + ("classes.FixtureData.fields.floatValue", "3.5"), + ("scenes.0.buildIndex", "0"), + ("scenes.0.path", "Assets/Scenes/Boot.unity"), + ("scenes.0.roots.0.name", "Foo"), + ] + ); + } + + #[test] + fn a_reported_value_has_to_match_the_contract() { + let contract = super::leaves(&serde_json::json!({ "a": 1, "b": 2.5, "c": "x" })); + let reported = |pairs: &[(&str, &str)]| { + pairs + .iter() + .map(|(k, v)| (k.to_string(), v.to_string())) + .collect::>() + }; + let required = ["a", "b"]; + assert_eq!( + super::check(&reported(&[("a", "1"), ("b", "2.5")]), &contract, &required), + Vec::::new() + ); + assert_eq!( + super::check( + &reported(&[("a", "1"), ("b", "2.50")]), + &contract, + &required + ), + Vec::::new() + ); + assert_eq!( + super::check(&reported(&[("a", "2"), ("b", "2.5")]), &contract, &required), + ["a is 2, the contract says 1"] + ); + assert_eq!( + super::check(&reported(&[("a", "1")]), &contract, &required), + ["b is missing"] + ); + assert_eq!( + super::check( + &reported(&[("a", "1"), ("b", "2.5"), ("d", "4")]), + &contract, + &required + ), + ["d is not in the contract"] + ); + } + + #[test] + fn a_player_below_5_6_needs_no_scenes() { + let required = |version| super::required(version).collect::>(); + let below = required("5.5.6f1"); + assert!(!below.is_empty()); + assert!(below.iter().all(|path| path.starts_with("classes."))); + assert_eq!(required("2017.1.0f3"), super::REQUIRED); + } + + #[test] + fn a_player_below_5_6_2_needs_no_dont_destroy_on_load() { + let required = |version| super::required(version).collect::>(); + for version in ["5.6.0f1", "5.6.1f1"] { + let paths = required(version); + assert!(paths.contains(&"scenes.0.path")); + assert!(!paths + .iter() + .any(|path| path.starts_with("dontDestroyOnLoad."))); + } + assert_eq!(required("5.6.2f1"), super::REQUIRED); + } + + #[test] + fn refuses_a_zip_the_manifest_does_not_describe() { + let entry = entry("win-x64-mono-bdwgc"); + assert_eq!(verify(b"abc", &entry), Ok(())); + assert!(verify(b"abcd", &entry).is_err()); + assert!(verify(b"abd", &entry).is_err()); + } +}