diff --git a/.github/workflows/modification.yml b/.github/workflows/modification.yml new file mode 100644 index 00000000..2e2f38d9 --- /dev/null +++ b/.github/workflows/modification.yml @@ -0,0 +1,71 @@ +name: Branch modification + +on: + push: + branches: + - modification + workflow_dispatch: + +env: + CARGO_TERM_COLOR: always + +jobs: + build: + runs-on: windows-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # For $commitCount + lfs: true + + - uses: actions/cache@v4 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + target/ + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + + - name: Extract git-rev + run: | + $commitCount = git rev-list --count HEAD + $shortHash = git rev-parse --short HEAD + "GIT_REV=r$commitCount.$shortHash" | Out-File -FilePath $env:GITHUB_ENV -Append + + - name: Setup Toolchain + run: rustup default nightly-msvc + + - name: Set version in Cargo.toml + run: | + $files = @( + './Cargo.toml' + './yas/Cargo.toml' + './yas-genshin/Cargo.toml' + './yas-starrail/Cargo.toml' + ) + [regex]$pattern = '(?<=version = ").*(?=")' + foreach ($file in $files) { + $pattern.Replace((Get-Content -Raw $file), "0.0.0-$env:GIT_REV", 1) | Out-File -FilePath $file + } + + - name: Build (Release) + run: cargo build --release + + - name: Rename Outputs + run: | + Move-Item ./target/release/yas_scanner_genshin.exe "yas_scanner_genshin_$env:GIT_REV.exe" + Move-Item ./target/release/yas_scanner_starrail.exe "yas_scanner_starrail_$env:GIT_REV.exe" + + - name: Upload yas_scanner_genshin + uses: actions/upload-artifact@v4 + with: + name: yas_scanner_genshin_${{ env.GIT_REV }} + path: yas_scanner_genshin_${{ env.GIT_REV }}.exe + + - name: Upload yas_scanner_starrail + uses: actions/upload-artifact@v4 + with: + name: yas_scanner_starrail_${{ env.GIT_REV }} + path: yas_scanner_starrail_${{ env.GIT_REV }}.exe diff --git a/.gitignore b/.gitignore index 864c4920..736b9722 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,10 @@ -/target -*.png -data/ target/ -mona.json -.idea/ captures/ -.vscode/ \ No newline at end of file +dumps/ +.vscode/ +.idea/ +/*.json +**/*.png +**tmp** +**/.DS_Store +data/ diff --git a/Cargo.lock b/Cargo.lock index 23cb0e9e..089789b4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + [[package]] name = "adler" version = "1.0.2" @@ -9,43 +18,79 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] -name = "adler32" -version = "1.2.0" +name = "ahash" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] [[package]] name = "aho-corasick" -version = "0.7.20" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +checksum = "0c378d78423fdad8089616f827526ee33c19f2fddbd5de1629152c9593ba4783" dependencies = [ "memchr", ] [[package]] -name = "android_system_properties" -version = "0.1.5" +name = "anstream" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +checksum = "b1f58811cfac344940f1a400b6e6231ce35171f614f26439e80f8c1465c5cc0c" dependencies = [ - "libc", + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", ] [[package]] -name = "ansi_term" -version = "0.12.1" +name = "anstyle" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15c4c2c83f81532e5845a733998b6971faca23490340a418e9b72a3ec9de12ea" + +[[package]] +name = "anstyle-parse" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333" dependencies = [ - "winapi", + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +dependencies = [ + "windows-sys 0.48.0", +] + +[[package]] +name = "anstyle-wincon" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58f54d10c6dfa51283a066ceab3ec1ab78d13fae00aa49243a45e4571fb79dfd" +dependencies = [ + "anstyle", + "windows-sys 0.48.0", ] [[package]] name = "anyhow" -version = "1.0.70" +version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" [[package]] name = "anymap2" @@ -53,28 +98,32 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d301b3b94cb4b2f23d7917810addbbaff90738e0ca2be692bd027e70d7e0330c" -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi 0.1.19", - "libc", - "winapi", -] - [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + [[package]] name = "base64" -version = "0.13.1" +version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +checksum = "414dcefbc63d77c526a76b3afcf6fbb9b5e2791c19c3aa2297733208750c6e53" [[package]] name = "bit-set" @@ -91,12 +140,24 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" +[[package]] +name = "bit_field" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" + [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" + [[package]] name = "block" version = "0.1.6" @@ -105,24 +166,24 @@ checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" [[package]] name = "block-buffer" -version = "0.10.3" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ "generic-array", ] [[package]] name = "bumpalo" -version = "3.11.1" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" +checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" [[package]] name = "bytemuck" -version = "1.12.3" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aaa3a8d9a1ca92e282c96a32d6511b695d7d994d1d102ba85d279f9b2756947f" +checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6" [[package]] name = "byteorder" @@ -132,15 +193,27 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" + +[[package]] +name = "bytesize" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" +checksum = "a3e368af43e418a04d52505cf3dbc23dda4e3407ae2fa99fd0e4f308ce546acc" +dependencies = [ + "serde", +] [[package]] name = "cc" -version = "1.0.78" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] [[package]] name = "cfg-if" @@ -149,47 +222,57 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] -name = "chrono" -version = "0.4.23" +name = "clap" +version = "4.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f" +checksum = "6a13b88d2c62ff462f88e4a121f17a82c1af05693a2f192b5c38d14de73c19f6" dependencies = [ - "iana-time-zone", - "js-sys", - "num-integer", - "num-traits", - "time", - "wasm-bindgen", - "winapi", + "clap_builder", + "clap_derive", ] [[package]] -name = "clap" -version = "2.34.0" +name = "clap_builder" +version = "4.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" +checksum = "2bb9faaa7c2ef94b2743a21f5a29e6f0010dff4caa69ac8e9d6cf8b6fa74da08" dependencies = [ - "ansi_term", - "atty", - "bitflags", + "anstream", + "anstyle", + "clap_lex", "strsim", - "textwrap", - "unicode-width", - "vec_map", ] +[[package]] +name = "clap_derive" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0862016ff20d69b84ef8247369fabf5c008a7417002411897d40ee1f4532b873" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.52", +] + +[[package]] +name = "clap_lex" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961" + [[package]] name = "cocoa" -version = "0.24.1" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f425db7937052c684daec3bd6375c8abe2d146dca4b8b143d6db777c39138f3a" +checksum = "f6140449f97a6e97f9511815c5632d84c8aacf8ac271ad77c559218161a1373c" dependencies = [ - "bitflags", + "bitflags 1.3.2", "block", "cocoa-foundation", - "core-foundation 0.9.3", - "core-graphics 0.22.3", - "foreign-types", + "core-foundation", + "core-graphics 0.23.1", + "foreign-types 0.5.0", "libc", "objc", ] @@ -200,25 +283,15 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "931d3837c286f56e3c58423ce4eba12d08db2374461a785c86f672b08b5650d6" dependencies = [ - "bitflags", + "bitflags 1.3.2", "block", - "core-foundation 0.9.3", + "core-foundation", "core-graphics-types", - "foreign-types", + "foreign-types 0.3.2", "libc", "objc", ] -[[package]] -name = "codespan-reporting" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" -dependencies = [ - "termcolor", - "unicode-width", -] - [[package]] name = "color_quant" version = "1.1.0" @@ -226,13 +299,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" [[package]] -name = "core-foundation" -version = "0.6.4" +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + +[[package]] +name = "console" +version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25b9e03f145fd4f2bf705e07b900cd41fc636598fe5dc452fd0db1441c3f496d" +checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8" dependencies = [ - "core-foundation-sys 0.6.2", + "encode_unicode 0.3.6", + "lazy_static", "libc", + "unicode-width", + "windows-sys 0.45.0", ] [[package]] @@ -241,64 +323,58 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" dependencies = [ - "core-foundation-sys 0.8.3", + "core-foundation-sys", "libc", ] [[package]] name = "core-foundation-sys" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7ca8a5221364ef15ce201e8ed2f609fc312682a8f4e0e3d4aa5879764e0fa3b" - -[[package]] -name = "core-foundation-sys" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" [[package]] name = "core-graphics" -version = "0.18.0" +version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f15b3cb55687886a6b66953123621e5a1529a91a01666d646fb64baa13f900f0" +checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb" dependencies = [ - "bitflags", - "core-foundation 0.6.4", - "foreign-types", + "bitflags 1.3.2", + "core-foundation", + "core-graphics-types", + "foreign-types 0.3.2", "libc", ] [[package]] name = "core-graphics" -version = "0.22.3" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb" +checksum = "970a29baf4110c26fedbc7f82107d42c23f7e88e404c4577ed73fe99ff85a212" dependencies = [ - "bitflags", - "core-foundation 0.9.3", + "bitflags 1.3.2", + "core-foundation", "core-graphics-types", - "foreign-types", + "foreign-types 0.5.0", "libc", ] [[package]] name = "core-graphics-types" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a68b68b3446082644c91ac778bf50cd4104bfb002b5a6a7c44cca5a2c70788b" +checksum = "2bb142d41022986c1d8ff29103a1411c8a3dfad3552f87a4f8dc50d61d4f4e33" dependencies = [ - "bitflags", - "core-foundation 0.9.3", - "foreign-types", + "bitflags 1.3.2", + "core-foundation", "libc", ] [[package]] name = "cpufeatures" -version = "0.2.5" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" dependencies = [ "libc", ] @@ -312,21 +388,11 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "crossbeam-channel" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" -dependencies = [ - "cfg-if", - "crossbeam-utils", -] - [[package]] name = "crossbeam-deque" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc" +checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" dependencies = [ "cfg-if", "crossbeam-epoch", @@ -335,26 +401,32 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.13" +version = "0.9.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01a9af1f4c2ef74bb8aa1f7e19706bc72d03598c8a570bb5de72243c7a9d9d5a" +checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" dependencies = [ "autocfg", "cfg-if", "crossbeam-utils", - "memoffset", + "memoffset 0.9.0", "scopeguard", ] [[package]] name = "crossbeam-utils" -version = "0.8.14" +version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f" +checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" dependencies = [ "cfg-if", ] +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + [[package]] name = "crypto-common" version = "0.1.6" @@ -366,47 +438,24 @@ dependencies = [ ] [[package]] -name = "cxx" -version = "1.0.86" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d1075c37807dcf850c379432f0df05ba52cc30f279c5cfc43cc221ce7f8579" -dependencies = [ - "cc", - "cxxbridge-flags", - "cxxbridge-macro", - "link-cplusplus", -] - -[[package]] -name = "cxx-build" -version = "1.0.86" +name = "csv" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5044281f61b27bc598f2f6647d480aed48d2bf52d6eb0b627d84c0361b17aa70" +checksum = "ac574ff4d437a7b5ad237ef331c17ccca63c46479e5b5453eb8e10bb99a759fe" dependencies = [ - "cc", - "codespan-reporting", - "once_cell", - "proc-macro2", - "quote", - "scratch", - "syn", + "csv-core", + "itoa", + "ryu", + "serde", ] [[package]] -name = "cxxbridge-flags" -version = "1.0.86" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61b50bc93ba22c27b0d31128d2d130a0a6b3d267ae27ef7e4fae2167dfe8781c" - -[[package]] -name = "cxxbridge-macro" -version = "1.0.86" +name = "csv-core" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39e61fda7e62115119469c7b3591fd913ecca96fb766cfd3f2e2502ab7bc87a5" +checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70" dependencies = [ - "proc-macro2", - "quote", - "syn", + "memchr", ] [[package]] @@ -421,14 +470,10 @@ dependencies = [ ] [[package]] -name = "deflate" -version = "0.8.6" +name = "deranged" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73770f8e1fe7d64df17ca66ad28994a0a623ea497fa69486e14984e715c5d174" -dependencies = [ - "adler32", - "byteorder", -] +checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946" [[package]] name = "derive-new" @@ -438,33 +483,63 @@ checksum = "3418329ca0ad70234b9735dc4ceed10af4df60eff9c8e7b06cb5e520d92c3535" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "digest" -version = "0.10.6" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", ] +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + [[package]] name = "display-info" -version = "0.4.1" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15453e90755c09fc70a6dbc9a307b0d4ec0fa65c5e36fb7cf8246109c442c331" +checksum = "4f9fb6a73233755f827129d80a80a6a16448122040537c881248a09d5c80ab6b" dependencies = [ "anyhow", - "core-graphics 0.22.3", + "core-graphics 0.23.1", "fxhash", "widestring", - "windows", + "windows 0.48.0", "xcb", ] +[[package]] +name = "dlib" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" +dependencies = [ + "libloading", +] + [[package]] name = "doc-comment" version = "0.3.3" @@ -479,9 +554,9 @@ checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" [[package]] name = "dyn-clone" -version = "1.0.10" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9b0705efd4599c15a38151f4721f7bc388306f61084d3bfd50bd07fbca5cb60" +checksum = "bbfc4744c1b8f2a09adc0e55242f60b1af195d88596bd8700be74418c056c555" [[package]] name = "edit-distance" @@ -490,82 +565,107 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbbaaaf38131deb9ca518a274a45bfdb8771f139517b073b16c2d3d32ae5037b" [[package]] -name = "educe" -version = "0.4.20" +name = "either" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb0188e3c3ba8df5753894d54461f0e39bc91741dc5b22e1c46999ec2c71f4e4" -dependencies = [ - "enum-ordinalize", - "proc-macro2", - "quote", - "syn", -] +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" [[package]] -name = "either" -version = "1.8.0" +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + +[[package]] +name = "encode_unicode" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" [[package]] name = "encoding_rs" -version = "0.8.31" +version = "0.8.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" +checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" dependencies = [ "cfg-if", ] [[package]] name = "enigo" -version = "0.0.14" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95ce8d7672e87b3155fd5e8a9226276da5c833e15bc879c7b98a78f743b67814" +checksum = "802e4b2ae123615659085369b453cba87c5562e46ed8050a909fee18a9bc3157" dependencies = [ - "core-graphics 0.18.0", + "core-graphics 0.23.1", "libc", "objc", "pkg-config", - "unicode-segmentation", - "winapi", -] - -[[package]] -name = "enum-ordinalize" -version = "3.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a62bb1df8b45ecb7ffa78dca1c17a438fb193eb083db0b1b494d2a61bcb5096a" -dependencies = [ - "num-bigint", - "num-traits", - "proc-macro2", - "quote", - "rustc_version", - "syn", + "windows 0.51.1", ] [[package]] name = "env_logger" -version = "0.9.3" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7" +checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" dependencies = [ - "atty", "humantime", + "is-terminal", "log", "regex", "termcolor", ] [[package]] -name = "fastrand" -version = "1.8.0" +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" +checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd" dependencies = [ - "instant", + "errno-dragonfly", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "exr" +version = "1.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "279d3efcc55e19917fff7ab3ddd6c14afb6a90881a0078465196fe2f99d08c56" +dependencies = [ + "bit_field", + "flume", + "half", + "lebe", + "miniz_oxide", + "rayon-core", + "smallvec", + "zune-inflate", ] +[[package]] +name = "fastrand" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" + [[package]] name = "fdeflate" version = "0.3.0" @@ -577,30 +677,37 @@ dependencies = [ [[package]] name = "filetime" -version = "0.2.19" +version = "0.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e884668cd0c7480504233e951174ddc3b382f7c2666e3b7310b5c4e7b0c37f9" +checksum = "d4029edd3e734da6fe05b6cd7bd2960760a616bd2ddd0d59a0124746d6272af0" dependencies = [ "cfg-if", "libc", - "redox_syscall", - "windows-sys 0.42.0", + "redox_syscall 0.3.5", + "windows-sys 0.48.0", ] [[package]] -name = "fixedbitset" -version = "0.4.2" +name = "flate2" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" +checksum = "c6c98ee8095e9d1dcbf2fcc6d95acccb90d1c81db1e44725c6a984b1dbdfb010" +dependencies = [ + "crc32fast", + "miniz_oxide", +] [[package]] -name = "flate2" -version = "1.0.25" +name = "flume" +version = "0.10.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841" +checksum = "1657b4441c3403d9f7b3409e47575237dac27b1b5726df654a6ecbf92f0f7577" dependencies = [ - "crc32fast", - "miniz_oxide 0.6.2", + "futures-core", + "futures-sink", + "nanorand", + "pin-project", + "spin", ] [[package]] @@ -615,7 +722,28 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" dependencies = [ - "foreign-types-shared", + "foreign-types-shared 0.1.1", +] + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared 0.3.1", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", ] [[package]] @@ -624,53 +752,59 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + [[package]] name = "form_urlencoded" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" dependencies = [ "percent-encoding", ] [[package]] name = "futures-channel" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" dependencies = [ "futures-core", ] [[package]] name = "futures-core" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" [[package]] name = "futures-io" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" [[package]] name = "futures-sink" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" [[package]] name = "futures-task" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" [[package]] name = "futures-util" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" dependencies = [ "futures-core", "futures-io", @@ -692,9 +826,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.6" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", @@ -702,30 +836,38 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.8" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" dependencies = [ "cfg-if", + "js-sys", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", + "wasm-bindgen", ] [[package]] name = "gif" -version = "0.11.4" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3edd93c6756b4dfaf2709eafcc345ba2636565295c198a9cfbf75fa5e3e00b06" +checksum = "80792593675e051cf94a4b111980da2ba60d4a83e43e0048c5693baab3977045" dependencies = [ "color_quant", "weezl", ] [[package]] -name = "h2" -version = "0.3.15" +name = "gimli" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4" +checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" + +[[package]] +name = "h2" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91fc23aa11be92976ef4729127f1a74adf36d8436f7816b185d18df956790833" dependencies = [ "bytes", "fnv", @@ -733,7 +875,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap", + "indexmap 1.9.3", "slab", "tokio", "tokio-util", @@ -742,9 +884,14 @@ dependencies = [ [[package]] name = "half" -version = "1.8.2" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" +checksum = "bc52e53916c08643f1b56ec082790d1e86a32e58dc5268f897f313fbae7b4872" +dependencies = [ + "cfg-if", + "crunchy", + "num-traits", +] [[package]] name = "hashbrown" @@ -753,43 +900,31 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] -name = "heck" -version = "0.3.3" +name = "hashbrown" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" dependencies = [ - "unicode-segmentation", + "ahash", ] [[package]] name = "heck" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" - -[[package]] -name = "hermit-abi" -version = "0.1.19" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" -version = "0.2.6" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" -dependencies = [ - "libc", -] +checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" [[package]] name = "http" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" +checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" dependencies = [ "bytes", "fnv", @@ -815,9 +950,9 @@ checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" [[package]] name = "httpdate" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "humantime" @@ -827,9 +962,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.23" +version = "0.14.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "034711faac9d2166cb1baf1a2fb0b60b1f277f8492fd72176c17f3515e1abd3c" +checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" dependencies = [ "bytes", "futures-channel", @@ -842,7 +977,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2", + "socket2 0.4.9", "tokio", "tower-service", "tracing", @@ -862,35 +997,11 @@ dependencies = [ "tokio-native-tls", ] -[[package]] -name = "iana-time-zone" -version = "0.1.53" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765" -dependencies = [ - "android_system_properties", - "core-foundation-sys 0.8.3", - "iana-time-zone-haiku", - "js-sys", - "wasm-bindgen", - "winapi", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" -dependencies = [ - "cxx", - "cxx-build", -] - [[package]] name = "idna" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" dependencies = [ "unicode-bidi", "unicode-normalization", @@ -898,31 +1009,64 @@ dependencies = [ [[package]] name = "image" -version = "0.23.14" +version = "0.24.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24ffcb7e7244a9bf19d35bf2883b9c080c4ced3c07a9895572178cdb8f13f6a1" +checksum = "6f3dfdbdd72063086ff443e297b61695500514b1e41095b6fb9a5ab48a70a711" dependencies = [ "bytemuck", "byteorder", "color_quant", + "exr", "gif", "jpeg-decoder", - "num-iter", "num-rational", "num-traits", - "png 0.16.8", - "scoped_threadpool", + "png", + "qoi", "tiff", ] [[package]] name = "indexmap" -version = "1.9.2" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", - "hashbrown", + "hashbrown 0.12.3", +] + +[[package]] +name = "indexmap" +version = "2.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4" +dependencies = [ + "equivalent", + "hashbrown 0.14.3", +] + +[[package]] +name = "indicatif" +version = "0.17.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b297dc40733f23a0e52728a58fa9489a5b7638a324932de16b41adc3ef80730" +dependencies = [ + "console", + "instant", + "number_prefix", + "portable-atomic", + "unicode-width", +] + +[[package]] +name = "indicatif-log-bridge" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2963046f28a204e3e3fd7e754fd90a6235da05b5378f24707ff0ec9513725ce3" +dependencies = [ + "indicatif", + "log", ] [[package]] @@ -934,11 +1078,33 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "io-lifetimes" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.48.0", +] + [[package]] name = "ipnet" -version = "2.7.1" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30e22bd8629359895450b59ea7a776c850561b96a3b1d31321c1949d9e6c9146" +checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6" + +[[package]] +name = "is-terminal" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" +dependencies = [ + "hermit-abi", + "rustix", + "windows-sys 0.48.0", +] [[package]] name = "itertools" @@ -949,37 +1115,47 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "itoa" -version = "1.0.5" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "jpeg-decoder" -version = "0.1.22" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "229d53d58899083193af11e15917b5640cd40b29ff475a1fe4ef725deb02d0f2" +checksum = "bc0000e42512c92e31c2252315bda326620a4e034105e900c98ec492fa077b3e" dependencies = [ "rayon", ] [[package]] name = "js-sys" -version = "0.3.60" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" dependencies = [ "wasm-bindgen", ] [[package]] name = "kstring" -version = "1.0.6" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b310ccceade8121d7d77fee406160e457c2f4e7c7982d589da3499bc7ea4526" +checksum = "ec3066350882a1cd6d950d055997f379ac37fd39f81cd4d8ed186032eb3c5747" dependencies = [ "serde", + "static_assertions", ] [[package]] @@ -988,38 +1164,84 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "lebe" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" + [[package]] name = "libc" -version = "0.2.139" +version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" [[package]] name = "libdbus-sys" -version = "0.2.3" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2264f9d90a9b4e60a2dc722ad899ea0374f03c2e96e755fe22a8f551d4d5fb3c" +checksum = "06085512b750d640299b79be4bad3d2fa90a9c00b1fd9e1b46364f66f0485c72" dependencies = [ + "cc", "pkg-config", ] [[package]] -name = "link-cplusplus" -version = "1.0.8" +name = "libloading" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d580318f95776505201b28cf98eb1fa5e4be3b689633ba6a3e6cd880ff22d8cb" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "libm" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" + +[[package]] +name = "libredox" +version = "0.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5" +checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" dependencies = [ - "cc", + "bitflags 2.4.0", + "libc", + "redox_syscall 0.4.1", +] + +[[package]] +name = "libwayshot" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "896d0e594158b7f5188034836a6c4886492078352c39760786e54f1b796caaea" +dependencies = [ + "image", + "log", + "memmap2 0.7.1", + "nix", + "thiserror", + "wayland-client", + "wayland-protocols", + "wayland-protocols-wlr", ] +[[package]] +name = "linux-raw-sys" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" + [[package]] name = "liquid" -version = "0.23.1" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26e930310cf4334c4936ae18737500a57739c69442b5c42bae114d619af54b82" +checksum = "69f68ae1011499ae2ef879f631891f21c78e309755f4a5e483c4a8f12e10b609" dependencies = [ "doc-comment", - "kstring", "liquid-core", "liquid-derive", "liquid-lib", @@ -1028,57 +1250,64 @@ dependencies = [ [[package]] name = "liquid-core" -version = "0.23.2" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eae470f061bfc53607283906de925ab67ed57a341e827146e3b241699a1dcde" +checksum = "79e0724dfcaad5cfb7965ea0f178ca0870b8d7315178f4a7179f5696f7f04d5f" dependencies = [ "anymap2", - "chrono", - "itertools", + "itertools 0.10.5", "kstring", "liquid-derive", "num-traits", "pest", "pest_derive", + "regex", "serde", + "time", ] [[package]] name = "liquid-derive" -version = "0.23.1" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6510e456700da1afe07603913b0da5a2595f2482656ade07abf719aae7501f0a" +checksum = "fc2fb41a9bb4257a3803154bdf7e2df7d45197d1941c9b1a90ad815231630721" dependencies = [ "proc-macro2", - "proc-quote", - "syn", + "quote", + "syn 2.0.52", ] [[package]] name = "liquid-lib" -version = "0.23.1" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6341259f779ff663bdf1fc478bddb2ca51fda25414006dc69395eddfac07e0a4" +checksum = "e2a17e273a6fb1fb6268f7a5867ddfd0bd4683c7e19b51084f3d567fad4348c0" dependencies = [ - "chrono", - "itertools", - "kstring", + "itertools 0.10.5", "liquid-core", "once_cell", "percent-encoding", "regex", + "time", "unicode-segmentation", ] [[package]] -name = "log" -version = "0.4.17" +name = "lock_api" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" dependencies = [ - "cfg-if", + "autocfg", + "scopeguard", ] +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + [[package]] name = "malloc_buf" version = "0.0.6" @@ -1094,79 +1323,69 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" -[[package]] -name = "mapr" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46a28a55dbc005b2f6f123c4058933d57add373d362f6fd3a76aab4fe6973500" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "matrixmultiply" -version = "0.3.2" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "add85d4dd35074e6fedc608f8c8f513a3548619a9024b751949ef0e8e45a4d84" +checksum = "090126dc04f95dc0d1c1c91f61bdd474b3930ca064c1edc8a849da2c6cbe1e77" dependencies = [ + "autocfg", "rawpointer", ] [[package]] name = "memchr" -version = "2.5.0" +version = "2.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" [[package]] -name = "memoffset" +name = "memmap2" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +checksum = "f49388d20533534cd19360ad3d6a7dadc885944aa802ba3995040c5ec11288c6" dependencies = [ - "autocfg", + "libc", ] [[package]] -name = "mime" -version = "0.3.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" - -[[package]] -name = "minimal-lexical" -version = "0.2.1" +name = "memmap2" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +checksum = "fe751422e4a8caa417e13c3ea66452215d7d63e19e604f4980461212f3ae1322" +dependencies = [ + "libc", +] [[package]] -name = "miniz_oxide" -version = "0.3.7" +name = "memoffset" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" dependencies = [ - "adler32", + "autocfg", ] [[package]] -name = "miniz_oxide" -version = "0.4.4" +name = "memoffset" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" +checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" dependencies = [ - "adler", "autocfg", ] [[package]] -name = "miniz_oxide" -version = "0.6.2" +name = "mime" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" -dependencies = [ - "adler", -] +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" @@ -1180,21 +1399,23 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.5" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" +checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" dependencies = [ "libc", - "log", - "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.42.0", + "wasi", + "windows-sys 0.48.0", ] [[package]] -name = "multimap" -version = "0.8.3" +name = "nanorand" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" +checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" +dependencies = [ + "getrandom", +] [[package]] name = "native-tls" @@ -1227,32 +1448,43 @@ dependencies = [ "rawpointer", ] +[[package]] +name = "nix" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "libc", + "memoffset 0.7.1", + "pin-utils", +] + [[package]] name = "nom" -version = "7.1.2" +version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5507769c4919c998e69e49c839d9dc6e693ede4cc4290d6ad8b41d4f09c548c" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" dependencies = [ "memchr", "minimal-lexical", ] [[package]] -name = "num-bigint" -version = "0.4.3" +name = "ntapi" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" +checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" dependencies = [ - "autocfg", - "num-integer", - "num-traits", + "winapi", ] [[package]] name = "num-complex" -version = "0.4.2" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ae39348c8bc5fbd7f40c727a9925f03517afd2ab27d46702108b6a7e5414c19" +checksum = "1ba157ca0885411de85d6ca030ba7e2a83a28636056c7c699b07c8b6f7383214" dependencies = [ "num-traits", ] @@ -1267,22 +1499,11 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-iter" -version = "0.1.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - [[package]] name = "num-rational" -version = "0.3.2" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" dependencies = [ "autocfg", "num-integer", @@ -1291,23 +1512,30 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" dependencies = [ "autocfg", + "libm", ] [[package]] name = "num_cpus" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi 0.2.6", + "hermit-abi", "libc", ] +[[package]] +name = "number_prefix" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" + [[package]] name = "objc" version = "0.2.7" @@ -1317,21 +1545,30 @@ dependencies = [ "malloc_buf", ] +[[package]] +name = "object" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +dependencies = [ + "memchr", +] + [[package]] name = "once_cell" -version = "1.17.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "openssl" -version = "0.10.45" +version = "0.10.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b102428fd03bc5edf97f62620f7298614c45cedf287c271e7ed450bbaf83f2e1" +checksum = "bac25ee399abb46215765b1cb35bc0212377e58a061560d8b29b024fd0430e7c" dependencies = [ - "bitflags", + "bitflags 2.4.0", "cfg-if", - "foreign-types", + "foreign-types 0.3.2", "libc", "once_cell", "openssl-macros", @@ -1340,13 +1577,13 @@ dependencies = [ [[package]] name = "openssl-macros" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.52", ] [[package]] @@ -1357,11 +1594,10 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.80" +version = "0.9.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23bbbf7854cd45b83958ebe919f0e8e516793727652e27fda10a8384cfc790b7" +checksum = "db4d56a4c0478783083cfafcc42493dd4a981d41669da64b4572a2a089b51b1d" dependencies = [ - "autocfg", "cc", "libc", "pkg-config", @@ -1370,42 +1606,66 @@ dependencies = [ [[package]] name = "os_info" -version = "3.5.1" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4750134fb6a5d49afc80777394ad5d95b04bc12068c6abb92fae8f43817270f" +checksum = "006e42d5b888366f1880eda20371fedde764ed2213dc8496f49622fa0c99cd5e" dependencies = [ "log", "serde", "winapi", ] +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.4.1", + "smallvec", + "windows-targets 0.48.5", +] + [[package]] name = "paste" -version = "1.0.11" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d01a5bd0424d00070b0098dd17ebca6f961a959dead1dbcbbbc1d1cd8d3deeba" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" [[package]] name = "percent-encoding" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" +checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" [[package]] name = "pest" -version = "2.5.2" +version = "2.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f6e86fb9e7026527a0d46bc308b841d73170ef8f443e1807f6ef88526a816d4" +checksum = "d7a4d085fd991ac8d5b05a147b437791b4260b76326baf0fc60cf7c9c27ecd33" dependencies = [ + "memchr", "thiserror", "ucd-trie", ] [[package]] name = "pest_derive" -version = "2.5.2" +version = "2.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96504449aa860c8dcde14f9fba5c58dc6658688ca1fe363589d6327b8662c603" +checksum = "a2bee7be22ce7918f641a33f08e3f43388c7656772244e2bbb2477f44cc9021a" dependencies = [ "pest", "pest_generator", @@ -1413,80 +1673,84 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.5.2" +version = "2.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "798e0220d1111ae63d66cb66a5dcb3fc2d986d520b98e49e1852bfdb11d7c5e7" +checksum = "d1511785c5e98d79a05e8a6bc34b4ac2168a0e3e92161862030ad84daa223141" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn", + "syn 2.0.52", ] [[package]] name = "pest_meta" -version = "2.5.2" +version = "2.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "984298b75898e30a843e278a9f2452c31e349a073a0ce6fd950a12a74464e065" +checksum = "b42f0394d3123e33353ca5e1e89092e533d2cc490389f2bd6131c43c634ebc5f" dependencies = [ "once_cell", "pest", - "sha1", + "sha2", ] [[package]] -name = "petgraph" -version = "0.6.2" +name = "pin-project" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d5014253a1331579ce62aa67443b4a658c5e7dd03d4bc6d302b94474888143" +checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" dependencies = [ - "fixedbitset", - "indexmap", + "pin-project-internal", ] [[package]] -name = "pin-project-lite" -version = "0.2.9" +name = "pin-project-internal" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" - -[[package]] -name = "pin-utils" +checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "pin-utils" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.26" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] name = "png" -version = "0.16.8" +version = "0.17.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c3287920cb847dee3de33d301c463fba14dda99db24214ddf93f83d3021f4c6" +checksum = "dd75bf2d8dd3702b9707cdbc56a5b9ef42cec752eb8b3bafc01234558442aa64" dependencies = [ - "bitflags", + "bitflags 1.3.2", "crc32fast", - "deflate", - "miniz_oxide 0.3.7", + "fdeflate", + "flate2", + "miniz_oxide", ] [[package]] -name = "png" -version = "0.17.8" +name = "portable-atomic" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aaeebc51f9e7d2c150d3f3bfeb667f2aa985db5ef1e3d212847bdedb488beeaa" -dependencies = [ - "bitflags", - "crc32fast", - "fdeflate", - "flate2", - "miniz_oxide 0.7.1", -] +checksum = "31114a898e107c51bb1609ffaf55a0e011cf6a4d7f1170d0015a165082c0338b" [[package]] name = "ppv-lite86" @@ -1495,111 +1759,92 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] -name = "proc-macro-hack" -version = "0.5.20+deprecated" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" - -[[package]] -name = "proc-macro2" -version = "1.0.70" +name = "prettytable-rs" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" +checksum = "eea25e07510aa6ab6547308ebe3c036016d162b8da920dbb079e3ba8acf3d95a" dependencies = [ - "unicode-ident", + "csv", + "encode_unicode 1.0.0", + "is-terminal", + "lazy_static", + "term", + "unicode-width", ] [[package]] -name = "proc-quote" -version = "0.4.0" +name = "primal-check" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e84ab161de78c915302ca325a19bee6df272800e2ae1a43fe3ef430bab2a100" +checksum = "9df7f93fd637f083201473dab4fee2db4c429d32e55e3299980ab3957ab916a0" dependencies = [ - "proc-macro-hack", - "proc-macro2", - "proc-quote-impl", - "quote", - "syn", + "num-integer", ] [[package]] -name = "proc-quote-impl" -version = "0.3.2" +name = "proc-macro2" +version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fb3ec628b063cdbcf316e06a8b8c1a541d28fa6c0a8eacd2bfb2b7f49e88aa0" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" dependencies = [ - "proc-macro-hack", - "proc-macro2", - "quote", + "unicode-ident", ] [[package]] name = "prost" -version = "0.9.0" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "444879275cb4fd84958b1a1d5420d15e6fcf7c235fe47f053c9c2a80aceb6001" +checksum = "0b82eaa1d779e9a4bc1c3217db8ffbeabaae1dca241bf70183242128d48681cd" dependencies = [ "bytes", "prost-derive", ] [[package]] -name = "prost-build" -version = "0.9.0" +name = "prost-derive" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62941722fb675d463659e49c4f3fe1fe792ff24fe5bbaa9c08cd3b98a1c354f5" +checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4" dependencies = [ - "bytes", - "heck 0.3.3", - "itertools", - "lazy_static", - "log", - "multimap", - "petgraph", - "prost", - "prost-types", - "regex", - "tempfile", - "which", + "anyhow", + "itertools 0.10.5", + "proc-macro2", + "quote", + "syn 1.0.109", ] [[package]] -name = "prost-derive" -version = "0.9.0" +name = "qoi" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9cc1a3263e07e0bf68e96268f37665207b49560d98739662cdfaae215c720fe" +checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" dependencies = [ - "anyhow", - "itertools", - "proc-macro2", - "quote", - "syn", + "bytemuck", ] [[package]] -name = "prost-types" -version = "0.9.0" +name = "quick-xml" +version = "0.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534b7a0e836e3c482d2693070f982e39e7611da9695d4d1f5a4b186b51faef0a" +checksum = "0ce5e73202a820a31f8a0ee32ada5e21029c81fd9e3ebf668a40832e4219d9d1" dependencies = [ - "bytes", - "prost", + "memchr", ] [[package]] name = "quick-xml" -version = "0.22.0" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8533f14c8382aaad0d592c812ac3b826162128b65662331e1127b45c3d18536b" +checksum = "eff6510e86862b57b210fd8cbe8ed3f0d7d600b9c2863cd4549a2e033c66e956" dependencies = [ "memchr", ] [[package]] name = "quote" -version = "1.0.23" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] @@ -1634,6 +1879,16 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rand_distr" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31" +dependencies = [ + "num-traits", + "rand", +] + [[package]] name = "rawpointer" version = "0.2.1" @@ -1642,9 +1897,9 @@ checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" [[package]] name = "rayon" -version = "1.6.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db3a213adf02b3bcfd2d3846bb41cb22857d131789e01df434fb7e7bc0759b7" +checksum = "e4963ed1bc86e4f3ee217022bd855b297cef07fb9eac5dfa1f788b220b49b3bd" dependencies = [ "either", "rayon-core", @@ -1652,56 +1907,77 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.10.1" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cac410af5d00ab6884528b4ab69d1e8e146e8d471201800fa1b4524126de6ad3" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" dependencies = [ - "crossbeam-channel", "crossbeam-deque", "crossbeam-utils", - "num_cpus", ] [[package]] name = "redox_syscall" -version = "0.2.16" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_users" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" dependencies = [ - "bitflags", + "getrandom", + "libredox", + "thiserror", ] [[package]] name = "regex" -version = "1.7.0" +version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" +checksum = "697061221ea1b4a94a624f67d0ae2bfe4e22b8a17b6a192afb11046542cc8c47" dependencies = [ "aho-corasick", "memchr", + "regex-automata", "regex-syntax", ] [[package]] -name = "regex-syntax" -version = "0.6.28" +name = "regex-automata" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" +checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] [[package]] -name = "remove_dir_all" -version = "0.5.3" +name = "regex-syntax" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" -dependencies = [ - "winapi", -] +checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" [[package]] name = "reqwest" -version = "0.11.13" +version = "0.11.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68cc60575865c7831548863cc02356512e3f1dc2f3f82cb837d7fc4cc8f3c97c" +checksum = "3e9ad3fe7488d7e34558a2033d45a0c90b72d97b4f80705666fea71472e2e6a1" dependencies = [ "base64", "bytes", @@ -1735,25 +2011,50 @@ dependencies = [ ] [[package]] -name = "rustc_version" -version = "0.4.0" +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustfft" +version = "6.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +checksum = "43806561bc506d0c5d160643ad742e3161049ac01027b5e6d7524091fd401d86" dependencies = [ - "semver", + "num-complex", + "num-integer", + "num-traits", + "primal-check", + "strength_reduce", + "transpose", + "version_check", +] + +[[package]] +name = "rustix" +version = "0.38.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0c3dde1fc030af041adc40e79c0e7fbcf431dd24870053d187d7c66e4b87453" +dependencies = [ + "bitflags 2.4.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.48.0", ] [[package]] name = "rustversion" -version = "1.0.11" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5583e89e108996506031660fe09baa5011b9dd0341b89029313006d1fb508d70" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" [[package]] name = "ryu" -version = "1.0.12" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[package]] name = "same-file" @@ -1765,104 +2066,108 @@ dependencies = [ ] [[package]] -name = "schannel" -version = "0.1.20" +name = "scan_fmt" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" +checksum = "0b53b0a5db882a8e2fdaae0a43f7b39e7e9082389e978398bdf223a55b581248" dependencies = [ - "lazy_static", - "windows-sys 0.36.1", + "regex", ] [[package]] -name = "scoped_threadpool" -version = "0.1.9" +name = "schannel" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8" +checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" +dependencies = [ + "windows-sys 0.48.0", +] [[package]] -name = "scopeguard" -version = "1.1.0" +name = "scoped-tls" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" [[package]] -name = "scratch" -version = "1.0.3" +name = "scopeguard" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddccb15bcce173023b3fedd9436f882a0739b8dfb45e4f6b6002bee5929f61b2" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "screenshots" -version = "0.5.3" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0af08077596f1a9a4058fc4fcc95f523c5dbac873e6ef4b921d6e4bf36bc1048" +checksum = "7187581a91266356e1a99500061ce6b5c5e7a925ae6244b3e71a7b807046bad3" dependencies = [ "anyhow", "core-graphics 0.22.3", "dbus", "display-info", "fxhash", - "png 0.17.8", + "image", + "libwayshot", + "percent-encoding", "widestring", - "windows", + "windows 0.48.0", "xcb", ] [[package]] name = "security-framework" -version = "2.7.0" +version = "2.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bc1bb97804af6631813c55739f771071e0f2ed33ee20b68c86ec505d906356c" +checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" dependencies = [ - "bitflags", - "core-foundation 0.9.3", - "core-foundation-sys 0.8.3", + "bitflags 1.3.2", + "core-foundation", + "core-foundation-sys", "libc", "security-framework-sys", ] [[package]] name = "security-framework-sys" -version = "2.6.1" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" +checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" dependencies = [ - "core-foundation-sys 0.8.3", + "core-foundation-sys", "libc", ] [[package]] name = "semver" -version = "1.0.16" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58bc9567378fc7690d6b2addae4e60ac2eeea07becb2c64b9f218b53865cba2a" +checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" [[package]] name = "serde" -version = "1.0.152" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.152" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" +checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.52", ] [[package]] name = "serde_json" -version = "1.0.91" +version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883" +checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360" dependencies = [ "itoa", "ryu", @@ -1882,10 +2187,23 @@ dependencies = [ ] [[package]] -name = "sha1" -version = "0.10.5" +name = "serde_yaml" +version = "0.9.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a15e0ef66bf939a7c890a0bf6d5a733c70202225f9888a89ed5c62298b019129" +dependencies = [ + "indexmap 2.2.5", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[package]] +name = "sha2" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" dependencies = [ "cfg-if", "cpufeatures", @@ -1894,76 +2212,144 @@ dependencies = [ [[package]] name = "simd-adler32" -version = "0.3.5" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "238abfbb77c1915110ad968465608b68e869e0772622c9656714e73e5a1a522f" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" [[package]] name = "slab" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ "autocfg", ] [[package]] name = "smallvec" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" +checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" [[package]] name = "socket2" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" dependencies = [ "libc", "winapi", ] +[[package]] +name = "socket2" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877" +dependencies = [ + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strength_reduce" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe895eb47f22e2ddd4dabc02bce419d2e643c8e3b585c78158b349195bc24d82" + +[[package]] +name = "string-interner" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07f9fdfdd31a0ff38b59deb401be81b73913d76c9cc5b1aed4e1330a223420b9" +dependencies = [ + "cfg-if", + "hashbrown 0.14.3", + "serde", +] + [[package]] name = "strsim" -version = "0.8.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "strum" -version = "0.24.1" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" +checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" [[package]] name = "strum_macros" -version = "0.24.3" +version = "0.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" +checksum = "ad8d03b598d3d0fff69bf533ee3ef19b8eeb342729596df84bcc7e1f96ec4059" dependencies = [ - "heck 0.4.0", + "heck", "proc-macro2", "quote", "rustversion", - "syn", + "syn 2.0.52", ] [[package]] name = "syn" -version = "1.0.107" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "syn" +version = "2.0.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sysinfo" +version = "0.30.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6746919caf9f2a85bff759535664c060109f21975c5ac2e8652e60102bd4d196" +dependencies = [ + "cfg-if", + "core-foundation-sys", + "libc", + "ntapi", + "once_cell", + "rayon", + "windows 0.52.0", +] + [[package]] name = "tar" -version = "0.4.38" +version = "0.4.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b55807c0344e1e6c04d7c965f5289c39a8d94ae23ed5c0b57aabac549f871c6" +checksum = "b16afcea1f22891c49a00c751c7b63b2233284064f11a200fc624137c51e2ddb" dependencies = [ "filetime", "libc", @@ -1972,76 +2358,94 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.3.0" +version = "3.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" dependencies = [ "cfg-if", "fastrand", - "libc", - "redox_syscall", - "remove_dir_all", - "winapi", + "redox_syscall 0.3.5", + "rustix", + "windows-sys 0.48.0", ] [[package]] -name = "termcolor" -version = "1.1.3" +name = "term" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" dependencies = [ - "winapi-util", + "dirs-next", + "rustversion", + "winapi", ] [[package]] -name = "textwrap" -version = "0.11.0" +name = "termcolor" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" dependencies = [ - "unicode-width", + "winapi-util", ] [[package]] name = "thiserror" -version = "1.0.38" +version = "1.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" +checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.38" +version = "1.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" +checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.52", ] [[package]] name = "tiff" -version = "0.6.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a53f4706d65497df0c4349241deddf35f84cee19c87ed86ea8ca590f4464437" +checksum = "6d172b0f4d3fba17ba89811858b9d3d97f928aece846475bbda076ca46736211" dependencies = [ + "flate2", "jpeg-decoder", - "miniz_oxide 0.4.4", "weezl", ] [[package]] name = "time" -version = "0.1.45" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" +checksum = "17f6bb557fd245c28e6411aa56b6403c689ad95061f50e4be16c274e70a17e48" dependencies = [ - "libc", - "wasi 0.10.0+wasi-snapshot-preview1", - "winapi", + "deranged", + "itoa", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" + +[[package]] +name = "time-macros" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a942f44339478ef67935ab2bbaec2fb0322496cf3cbe84b261e06ac3814c572" +dependencies = [ + "time-core", ] [[package]] @@ -2055,32 +2459,31 @@ dependencies = [ [[package]] name = "tinyvec_macros" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.24.1" +version = "1.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d9f76183f91ecfb55e1d7d5602bd1d979e38a3a522fe900241cf195624d67ae" +checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9" dependencies = [ - "autocfg", + "backtrace", "bytes", "libc", - "memchr", "mio", "num_cpus", "pin-project-lite", - "socket2", - "windows-sys 0.42.0", + "socket2 0.5.3", + "windows-sys 0.48.0", ] [[package]] name = "tokio-native-tls" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" dependencies = [ "native-tls", "tokio", @@ -2088,9 +2491,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.4" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" +checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d" dependencies = [ "bytes", "futures-core", @@ -2102,9 +2505,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.5.10" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1333c76748e868a4d9d1017b5ab53171dfd095f70c712fdb4653a406547f598f" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" dependencies = [ "serde", ] @@ -2128,32 +2531,33 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.30" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" dependencies = [ "once_cell", ] [[package]] name = "tract-core" -version = "0.15.8" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ec224e77668b0857a0b93b21994323672e53479057cfa8fb4a6987086c9c879" +checksum = "b61f2cff6b36a1385cffa0419daf8578cc9c7d3d3ec5d4f62082d5b2beb18699" dependencies = [ "anyhow", "bit-set", "derive-new", "downcast-rs", "dyn-clone", - "educe", - "half", "lazy_static", "log", "maplit", "ndarray", + "num-complex", "num-integer", "num-traits", + "paste", + "rustfft", "smallvec", "tract-data", "tract-linalg", @@ -2161,54 +2565,55 @@ dependencies = [ [[package]] name = "tract-data" -version = "0.15.8" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1609c2b286a347d3040c7234e204de170920ec3709b15521defe098c0f8a5a67" +checksum = "60d273009dc7ec8101fb8fdfc4a905eabfd48c5ea49d4cf30c20de7ada6aae2c" dependencies = [ "anyhow", - "educe", "half", - "itertools", + "itertools 0.12.1", "lazy_static", "maplit", "ndarray", - "num-complex", + "nom", "num-integer", "num-traits", + "scan_fmt", "smallvec", + "string-interner", ] [[package]] name = "tract-hir" -version = "0.15.8" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b579830105a172704676ca4da670931010577070e869724be6740ec43d4cc3eb" +checksum = "b54e67b476006d4422101f413459b842f2470d7ae594bd24d669d7433d9613c5" dependencies = [ "derive-new", - "educe", "log", "tract-core", ] [[package]] name = "tract-linalg" -version = "0.15.8" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f51d85987d90f5e6479dbe203cbf259eb9208ff4e1c94870ab0ca61492171bb" +checksum = "4eebd82a81656eebc479eea84cee391be259b7bbd9719f96c4b4c6678748f2d6" dependencies = [ "cc", "derive-new", "downcast-rs", "dyn-clone", - "educe", "half", "lazy_static", - "libc", "liquid", + "liquid-core", "log", "num-traits", "paste", + "scan_fmt", "smallvec", + "time", "tract-data", "unicode-normalization", "walkdir", @@ -2216,9 +2621,9 @@ dependencies = [ [[package]] name = "tract-nnef" -version = "0.15.8" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6b15a11fea45a9f69c595cf386c87e4c3cd45d1323e7b5f430004a8cd11d2c4" +checksum = "8e08aa7b7b1f0e5317aa4832f220a847eb3177cfeefb94a089aae2bde867c2d2" dependencies = [ "byteorder", "flate2", @@ -2231,18 +2636,16 @@ dependencies = [ [[package]] name = "tract-onnx" -version = "0.15.8" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a9a6eb2116cdab535b5b9f1771e82acebea419f2571bd688e267451de3506ce" +checksum = "a123632dae75ed9b1281a11e8454c4cb6cfbdf01dc774c53a5fcebcd689ca509" dependencies = [ "bytes", "derive-new", - "educe", "log", - "mapr", + "memmap2 0.9.4", "num-integer", "prost", - "prost-build", "smallvec", "tract-hir", "tract-nnef", @@ -2251,14 +2654,28 @@ dependencies = [ [[package]] name = "tract-onnx-opl" -version = "0.15.8" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a45ed1d95f1deef2a6240d296fa0eaf97dc4b15df32be7a5220ee825e0a3fc4d" +checksum = "241339a78880a8e5dfcc107df5943459eaa38bc3118cef64261e133746cc6621" dependencies = [ - "educe", + "getrandom", + "log", + "rand", + "rand_distr", + "rustfft", "tract-nnef", ] +[[package]] +name = "transpose" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad61aed86bc3faea4300c7aee358b4c6d0c8d6ccc36524c96e4c92ccf26e77e" +dependencies = [ + "num-integer", + "strength_reduce", +] + [[package]] name = "try-lock" version = "0.2.4" @@ -2273,21 +2690,21 @@ checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" [[package]] name = "ucd-trie" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" +checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" [[package]] name = "unicode-bidi" -version = "0.3.8" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-ident" -version = "1.0.6" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" +checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" [[package]] name = "unicode-normalization" @@ -2300,9 +2717,9 @@ dependencies = [ [[package]] name = "unicode-segmentation" -version = "1.10.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" [[package]] name = "unicode-width" @@ -2310,11 +2727,17 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" +[[package]] +name = "unsafe-libyaml" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab4c90930b95a82d00dc9e9ac071b4991924390d46cbd0dfe566148667605e4b" + [[package]] name = "url" -version = "2.3.1" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" dependencies = [ "form_urlencoded", "idna", @@ -2322,16 +2745,16 @@ dependencies = [ ] [[package]] -name = "vcpkg" -version = "0.2.15" +name = "utf8parse" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] -name = "vec_map" -version = "0.8.2" +name = "vcpkg" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "version_check" @@ -2341,31 +2764,23 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "walkdir" -version = "2.3.2" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" dependencies = [ "same-file", - "winapi", "winapi-util", ] [[package]] name = "want" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" dependencies = [ - "log", "try-lock", ] -[[package]] -name = "wasi" -version = "0.10.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -2374,9 +2789,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.83" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -2384,24 +2799,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.83" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn", + "syn 2.0.52", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.33" +version = "0.4.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d" +checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" dependencies = [ "cfg-if", "js-sys", @@ -2411,9 +2826,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.83" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2421,28 +2836,102 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.83" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.52", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.83" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" + +[[package]] +name = "wayland-backend" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41b48e27457e8da3b2260ac60d0a94512f5cba36448679f3747c0865b7893ed8" +dependencies = [ + "cc", + "downcast-rs", + "io-lifetimes", + "nix", + "scoped-tls", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-client" +version = "0.30.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" +checksum = "489c9654770f674fc7e266b3c579f4053d7551df0ceb392f153adb1f9ed06ac8" +dependencies = [ + "bitflags 1.3.2", + "nix", + "wayland-backend", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b28101e5ca94f70461a6c2d610f76d85ad223d042dd76585ab23d3422dd9b4d" +dependencies = [ + "bitflags 1.3.2", + "wayland-backend", + "wayland-client", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-wlr" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fce991093320e4a6a525876e6b629ab24da25f9baef0c2e0080ad173ec89588a" +dependencies = [ + "bitflags 1.3.2", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9b873b257fbc32ec909c0eb80dea312076a67014e65e245f5eb69a6b8ab330e" +dependencies = [ + "proc-macro2", + "quick-xml 0.28.2", + "quote", +] + +[[package]] +name = "wayland-sys" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b2a02ac608e07132978689a6f9bf4214949c85998c247abadd4f4129b1aa06" +dependencies = [ + "dlib", + "log", + "pkg-config", +] [[package]] name = "web-sys" -version = "0.3.60" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" +checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" dependencies = [ "js-sys", "wasm-bindgen", @@ -2454,17 +2943,6 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb" -[[package]] -name = "which" -version = "4.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c831fbbee9e129a8cf93e7747a82da9d95ba8e16621cae60ec2cdc849bacb7b" -dependencies = [ - "either", - "libc", - "once_cell", -] - [[package]] name = "widestring" version = "1.0.2" @@ -2504,39 +2982,108 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows" -version = "0.44.0" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows" +version = "0.51.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca229916c5ee38c2f2bc1e9d8f04df975b4bd93f9955dc69fabb5d91270045c9" +dependencies = [ + "windows-core 0.51.1", + "windows-targets 0.48.5", +] + +[[package]] +name = "windows" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" +dependencies = [ + "windows-core 0.52.0", + "windows-targets 0.52.4", +] + +[[package]] +name = "windows" +version = "0.54.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9252e5725dbed82865af151df558e754e4a3c2c30818359eb17465f1346a1b49" +dependencies = [ + "windows-core 0.54.0", + "windows-targets 0.52.4", +] + +[[package]] +name = "windows-capture" +version = "1.0.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e745dab35a0c4c77aa3ce42d595e13d2003d6902d6b08c9ef5fc326d08da12b" +checksum = "33d819f3fbf3fe142be4c26c21374d9629c1681be346fbee371778d8c1555f46" dependencies = [ - "windows-targets", + "parking_lot", + "rayon", + "thiserror", + "windows 0.54.0", +] + +[[package]] +name = "windows-core" +version = "0.51.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.4", +] + +[[package]] +name = "windows-core" +version = "0.54.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12661b9c89351d684a50a8a643ce5f608e20243b9fb84687800163429f161d65" +dependencies = [ + "windows-result", + "windows-targets 0.52.4", +] + +[[package]] +name = "windows-result" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd19df78e5168dfb0aedc343d1d1b8d422ab2db6756d2dc3fef75035402a3f64" +dependencies = [ + "windows-targets 0.52.4", ] [[package]] name = "windows-sys" -version = "0.36.1" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" dependencies = [ - "windows_aarch64_msvc 0.36.1", - "windows_i686_gnu 0.36.1", - "windows_i686_msvc 0.36.1", - "windows_x86_64_gnu 0.36.1", - "windows_x86_64_msvc 0.36.1", + "windows-targets 0.42.2", ] [[package]] name = "windows-sys" -version = "0.42.0" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc 0.42.2", + "windows-targets 0.48.5", ] [[package]] @@ -2545,15 +3092,45 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" dependencies = [ - "windows_aarch64_gnullvm", + "windows_aarch64_gnullvm 0.42.2", "windows_aarch64_msvc 0.42.2", "windows_i686_gnu 0.42.2", "windows_i686_msvc 0.42.2", "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm", + "windows_x86_64_gnullvm 0.42.2", "windows_x86_64_msvc 0.42.2", ] +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" +dependencies = [ + "windows_aarch64_gnullvm 0.52.4", + "windows_aarch64_msvc 0.52.4", + "windows_i686_gnu 0.52.4", + "windows_i686_msvc 0.52.4", + "windows_x86_64_gnu 0.52.4", + "windows_x86_64_gnullvm 0.52.4", + "windows_x86_64_msvc 0.52.4", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" @@ -2561,10 +3138,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" [[package]] -name = "windows_aarch64_msvc" -version = "0.36.1" +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" +checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" [[package]] name = "windows_aarch64_msvc" @@ -2573,10 +3156,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" [[package]] -name = "windows_i686_gnu" -version = "0.36.1" +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" +checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" [[package]] name = "windows_i686_gnu" @@ -2585,10 +3174,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" [[package]] -name = "windows_i686_msvc" -version = "0.36.1" +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" +checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" [[package]] name = "windows_i686_msvc" @@ -2597,10 +3192,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" [[package]] -name = "windows_x86_64_gnu" -version = "0.36.1" +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" +checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" [[package]] name = "windows_x86_64_gnu" @@ -2608,6 +3209,18 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" @@ -2615,10 +3228,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" [[package]] -name = "windows_x86_64_msvc" -version = "0.36.1" +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" +checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" [[package]] name = "windows_x86_64_msvc" @@ -2626,13 +3245,26 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" + [[package]] name = "winreg" -version = "0.10.1" +version = "0.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" dependencies = [ - "winapi", + "cfg-if", + "windows-sys 0.48.0", ] [[package]] @@ -2646,40 +3278,74 @@ dependencies = [ [[package]] name = "xattr" -version = "0.2.3" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d1526bbe5aaeb5eb06885f4d987bcdfa5e23187055de9b83fe00156a821fabc" +checksum = "f4686009f71ff3e5c4dbcf1a282d0a44db3f021ba69350cd42086b3e5f1c6985" dependencies = [ "libc", ] +[[package]] +name = "xcap" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6776c1b371ea7c73bc98a40ae5c6a69c648669ff2e6d8a03fc8b80ddc58a0498" +dependencies = [ + "core-foundation", + "core-graphics 0.23.1", + "dbus", + "image", + "log", + "percent-encoding", + "sysinfo", + "thiserror", + "windows 0.52.0", + "xcb", +] + [[package]] name = "xcb" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0faeb4d7e2d54fff4a0584f61297e86b106914af2029778de7b427f72564d6c5" +checksum = "5d27b37e69b8c05bfadcd968eb1a4fe27c9c52565b727f88512f43b89567e262" dependencies = [ - "bitflags", + "bitflags 1.3.2", "libc", - "quick-xml", + "quick-xml 0.30.0", +] + +[[package]] +name = "yas_derive" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", ] [[package]] name = "yas_scanner" -version = "0.1.15" +version = "0.1.14" dependencies = [ + "anyhow", + "bytesize", "clap", "cocoa", - "core-foundation 0.9.3", - "core-graphics 0.22.3", + "console", + "core-foundation", + "core-graphics 0.23.1", "edit-distance", "enigo", - "env_logger", "image", + "indicatif", + "indicatif-log-bridge", "lazy_static", "log", + "once_cell", "os_info", - "png 0.17.8", + "paste", + "png", + "prettytable-rs", "rand", "regex", "reqwest", @@ -2691,5 +3357,78 @@ dependencies = [ "strum_macros", "tract-onnx", "winapi", + "windows-capture", + "xcap", +] + +[[package]] +name = "yas_scanner_genshin" +version = "0.1.16" +dependencies = [ + "anyhow", + "clap", + "edit-distance", + "env_logger", + "image", + "lazy_static", + "log", + "regex", + "serde", + "serde_json", + "serde_yaml", + "strum", + "strum_macros", + "windows-capture", "winres", + "yas_derive", + "yas_scanner", +] + +[[package]] +name = "yas_scanner_starrail" +version = "0.1.14" +dependencies = [ + "anyhow", + "clap", + "edit-distance", + "env_logger", + "image", + "lazy_static", + "log", + "regex", + "serde", + "serde_json", + "strum", + "strum_macros", + "winres", + "yas_scanner", +] + +[[package]] +name = "zerocopy" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", +] + +[[package]] +name = "zune-inflate" +version = "0.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" +dependencies = [ + "simd-adler32", ] diff --git a/Cargo.toml b/Cargo.toml index 45658e82..274c8dee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,62 +1,13 @@ -[package] -name = "yas_scanner" -version = "0.1.15" -edition = "2018" -build = "build.rs" -description = "Genshin Impact item scanner" -repository = "https://github.com/wormtql/yas" -keywords = ["GenshinImpact", "artifacts", "scanner", "ocr"] -license = "GPL-2.0-or-later" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -image = "0.23.14" -enigo = "0.0.14" -tract-onnx = "0.15.3" -serde_json = "1.0.68" -serde = { version = "1.0.130", features = ["derive"] } -regex = "1.5.4" -log = "0.4.14" -env_logger = "0.9.0" -edit-distance = "2.1.0" -clap = "2.33.3" -os_info = "3.0.7" -strum = "0.24" -strum_macros = "0.24" -rand = "0.8.5" -reqwest = { version = "0.11", features = ["blocking", "json"] } -semver = "1.0.7" -lazy_static = "1.4.0" -screenshots = "0.5.3" -png = "0.17.8" - -[target.'cfg(windows)'.dependencies] -winapi = { version = "0.3", features = [ - "winuser", - "wingdi", - "winnt", - "securitybaseapi", - "libloaderapi", -] } - -[target.'cfg(macos)'.dependencies] -core-graphics = "0.22.3" -core-foundation = "0.9.3" -cocoa = "0.24.1" - -[build-dependencies] -winres = "0.1" +[workspace] +resolver = "2" +members = [ + "yas", + "yas-genshin", + "yas-starrail", + "yas-derive" +] [profile.release] lto = true panic = "abort" strip = true - -[[bin]] -name = "yas_scanner" -path = "src/main.rs" - -[[bin]] -name = "yas_scanner_starrail" -path = "src/main_starrail.rs" \ No newline at end of file diff --git a/README.md b/README.md index 0fefbcdd..2f836ce0 100644 --- a/README.md +++ b/README.md @@ -1,73 +1,94 @@
# Yas -Yet Another Genshin Impact Scanner + +Yet Another Genshin Impact Scanner 又一个原神圣遗物导出器
## 介绍 -基于SVTR(基本上是MobileNetV3_Small + Transformer)字符识别模型,使用原神字体对原神中会出现的字符串进行训练,达到更高的速度和更精确的结果。相比CRNN,SVTR可以达到更小的体积及更好的识别率 -导出结果可以导入分析工具(例如 [莫娜占卜铺](https://mona-uranai.com/) )进行配装或者其他计算 -由于使用了 [Rust](https://www.rust-lang.org/) 进行编写,运行效率和文件体积都得到了很大的提升 + +基于 SVTR(基本上是 MobileNetV3_Small + Transformer)字符识别模型,使用原神字体对原神中会出现的字符串进行训练,达到更高的速度和更精确的结果。相比 CRNN,SVTR 可以达到更小的体积及更好的识别率 +导出结果可以导入分析工具(例如 [莫娜占卜铺](https://mona-uranai.com/) )进行配装或者其他计算 +由于使用了 [Rust](https://www.rust-lang.org/) 进行编写,运行效率和文件体积都得到了很大的提升 + ### 相关资料 + - [MobileNetV3](https://arxiv.org/pdf/1905.02244.pdf) - [CRNN](https://arxiv.org/pdf/1507.05717.pdf) - [SVTR](https://arxiv.org/pdf/2205.00159.pdf) - [Transformer](https://proceedings.neurips.cc/paper/2017/file/3f5ee243547dee91fbd053c1c4a845aa-Paper.pdf) ### 识别模型 -SVTR原文使用了多个Local/Global Mixing,其中Global Mixing就是Transformer层,而根据*PaddleOCR*的代码,其SVTR识别模型也并未完全遵照SVTR原模型,而是骨干网络 + Transformer的结构 -*Yas*同样采用PaddleOCR的做法,即MobileNetV3_Small + Global Mixing,相当于将原RNN替换为Transformer。 + +SVTR 原文使用了多个 Local/Global Mixing,其中 Global Mixing 就是 Transformer 层,而根据*PaddleOCR*的代码,其 SVTR 识别模型也并未完全遵照 SVTR 原模型,而是骨干网络 + Transformer 的结构 +*Yas*同样采用 PaddleOCR 的做法,即 MobileNetV3_Small + Global Mixing,相当于将原 RNN 替换为 Transformer。 ## 使用 + ### Windows + - 打开原神,并切换到背包页面,将背包拉到最上面 -- 下载单exe可执行文件,右键管理员运行 +- 下载单 exe 可执行文件,右键管理员运行 - 扫描过程中,鼠标右键终止 + ### Linux -- 首先请确保自己在x11下或者GNOME/Wayland下(其他wayland de下[会有很坏的性能](https://github.com/poly000/screenshots-rs/blob/d96dff76c5f5cbd849d80451f0df8f415f8e5f4b/src/linux/wayland_screenshot.rs#L109)) -- 用wine窗口化运行原神(或者全屏+虚拟桌面),打开圣遗物界面,拉到最顶 -- 启动yas -- Alt+Tab切换到原神窗口,并且在鼠标变为十字后点击一下(还没做窗口聚焦),注意保证原神窗口整体在屏幕内 + +- 首先请确保自己在 x11 下或者 GNOME/Wayland 下(其他 wayland de 下[会有很坏的性能](https://github.com/poly000/screenshots-rs/blob/d96dff76c5f5cbd849d80451f0df8f415f8e5f4b/src/linux/wayland_screenshot.rs#L109)) +- 用 wine 窗口化运行原神(或者全屏+虚拟桌面),打开圣遗物界面,拉到最顶 +- 启动 yas +- Alt+Tab 切换到原神窗口,并且在鼠标变为十字后点击一下(还没做窗口聚焦),注意保证原神窗口整体在屏幕内 - 等待扫描结束。右键中止还没做 + ### 注意 -- 默认4星以下圣遗物不扫描 -- 不是所有窗口比例都支持,推荐16:9的分辨率(如1600x900, 1920x1080, 3840x2160) + +- 默认 4 星以下圣遗物不扫描 +- 不是所有窗口比例都支持,推荐 16:9 的分辨率(如 1600x900, 1920x1080, 3840x2160) - 扫描过程中不要对鼠标做任何操作 -- 当前仅支持中文环境,若默认系统为非中文,请前往游戏设置界面修改Language为“简体中文”,否则无法读取原神窗口 +- 当前仅支持中文环境,若默认系统为非中文,请前往游戏设置界面修改 Language 为“简体中文”,否则无法读取原神窗口 - 当前仅支持键鼠作为控制设备,暂不支持手柄。 ### 命令行使用 -假设你知道如何使用命令行工具 + +假设你知道如何使用命令行工具 查看选项 + ```shell yas --help ``` + 只扫描五星圣遗物 + ```shell yas --min-star=5 ``` + 只扫描一行 + ```shell yas --max-row=1 ``` ## 编译 -在构建前,请确保安装`Git LFS`,并运行`git lfs pull`。否则[yas在运行时会使用错误的模型](https://github.com/wormtql/yas/pull/102#issuecomment-1375503803)。 +在构建前,请确保安装`Git LFS`,并运行`git lfs pull`。否则[yas 在运行时会使用错误的模型](https://github.com/wormtql/yas/pull/102#issuecomment-1375503803)。 ```shell -# Linux下需要首先安装rustup以及mingw-w64,然后再安装对应的rust target, +# Linux 下需要首先安装 rustup 以及 mingw-w64 ,然后再安装对应的 rust target, # 构建到Linux需要 `libxdo` 和 `libxcb` rustup default stable rustup target add x86_64-pc-windows-gnu cargo build --release --locked --target=x86_64-pc-windows-gnu ``` +如果使用 macOS,为了保证正常捕捉窗口,需要在编译后运行 `codesign.sh` 对二进制文件进行签名 + ## 训练 + [yas-train](https://github.com/wormtql/yas-train) ## 反馈 + - Issue -- QQ群:801106595 +- QQ 群:801106595 diff --git a/assets/app.entitlements b/assets/app.entitlements new file mode 100644 index 00000000..6177c2bc --- /dev/null +++ b/assets/app.entitlements @@ -0,0 +1,14 @@ + + + + com.apple.application-identifier + io.yas.scanner + com.apple.private.screencapture.allow + + com.apple.private.tcc.allow + + kTCCServiceScreenCapture + kTCCServiceAccessibility + + + diff --git a/manifest.xml b/assets/manifest.xml similarity index 100% rename from manifest.xml rename to assets/manifest.xml diff --git a/build.rs b/build.rs deleted file mode 100644 index 42a7e95e..00000000 --- a/build.rs +++ /dev/null @@ -1,7 +0,0 @@ -fn main() { - if std::env::var("CARGO_CFG_TARGET_OS").unwrap().as_str() == "windows" { - let mut res = winres::WindowsResource::new(); - res.set_manifest_file("manifest.xml"); - res.compile().unwrap(); - } -} diff --git a/codesign.sh b/codesign.sh new file mode 100755 index 00000000..f34fa505 --- /dev/null +++ b/codesign.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +# This is bad, but post build scripts still not implemented yet for cargo +# https://github.com/rust-lang/cargo/issues/545 + +codesign -f -s - --timestamp=none --entitlements assets/app.entitlements ./target/*/yas_scanner* diff --git a/models/index_2_word_starrail.json b/models/index_2_word_starrail.json deleted file mode 100644 index 4ba5d4b8..00000000 --- a/models/index_2_word_starrail.json +++ /dev/null @@ -1,371 +0,0 @@ -{ - "0": "-", - "1": " ", - "2": "%", - "3": "'", - "4": "+", - "5": ",", - "6": ".", - "7": "/", - "8": "0", - "9": "1", - "10": "2", - "11": "3", - "12": "4", - "13": "5", - "14": "6", - "15": "7", - "16": "8", - "17": "9", - "18": "「", - "19": "」", - "20": "七", - "21": "三", - "22": "丝", - "23": "中", - "24": "丹", - "25": "义", - "26": "之", - "27": "乐", - "28": "云", - "29": "亚", - "30": "人", - "31": "仙", - "32": "伊", - "33": "伤", - "34": "伯", - "35": "佩", - "36": "使", - "37": "信", - "38": "修", - "39": "值", - "40": "假", - "41": "偏", - "42": "停", - "43": "儿", - "44": "元", - "45": "光", - "46": "克", - "47": "兜", - "48": "全", - "49": "公", - "50": "兰", - "51": "军", - "52": "冠", - "53": "冥", - "54": "冰", - "55": "击", - "56": "刃", - "57": "利", - "58": "制", - "59": "刹", - "60": "刺", - "61": "力", - "62": "加", - "63": "动", - "64": "包", - "65": "匠", - "66": "千", - "67": "博", - "68": "卡", - "69": "卢", - "70": "卫", - "71": "卿", - "72": "历", - "73": "变", - "74": "司", - "75": "合", - "76": "吸", - "77": "呼", - "78": "命", - "79": "喙", - "80": "器", - "81": "围", - "82": "图", - "83": "土", - "84": "圣", - "85": "场", - "86": "坼", - "87": "垒", - "88": "城", - "89": "域", - "90": "堡", - "91": "塔", - "92": "墨", - "93": "士", - "94": "壳", - "95": "备", - "96": "复", - "97": "外", - "98": "天", - "99": "头", - "100": "套", - "101": "妮", - "102": "妲", - "103": "姬", - "104": "娅", - "105": "娜", - "106": "子", - "107": "孔", - "108": "存", - "109": "客", - "110": "害", - "111": "宽", - "112": "密", - "113": "射", - "114": "小", - "115": "尔", - "116": "属", - "117": "履", - "118": "岛", - "119": "岸", - "120": "巡", - "121": "巧", - "122": "巨", - "123": "已", - "124": "市", - "125": "布", - "126": "希", - "127": "帕", - "128": "带", - "129": "帽", - "130": "序", - "131": "废", - "132": "度", - "133": "建", - "134": "引", - "135": "弧", - "136": "彦", - "137": "御", - "138": "德", - "139": "快", - "140": "性", - "141": "怪", - "142": "总", - "143": "恒", - "144": "恕", - "145": "恢", - "146": "息", - "147": "感", - "148": "成", - "149": "戒", - "150": "战", - "151": "手", - "152": "才", - "153": "承", - "154": "抗", - "155": "护", - "156": "披", - "157": "抵", - "158": "拉", - "159": "拳", - "160": "指", - "161": "挎", - "162": "捕", - "163": "提", - "164": "攻", - "165": "效", - "166": "数", - "167": "旧", - "168": "明", - "169": "昏", - "170": "易", - "171": "星", - "172": "春", - "173": "晨", - "174": "景", - "175": "暴", - "176": "月", - "177": "服", - "178": "木", - "179": "机", - "180": "束", - "181": "杰", - "182": "板", - "183": "构", - "184": "果", - "185": "枝", - "186": "枪", - "187": "格", - "188": "桑", - "189": "械", - "190": "楼", - "191": "步", - "192": "残", - "193": "毡", - "194": "沉", - "195": "治", - "196": "泰", - "197": "洛", - "198": "洲", - "199": "流", - "200": "浮", - "201": "海", - "202": "深", - "203": "游", - "204": "演", - "205": "漠", - "206": "漫", - "207": "潜", - "208": "火", - "209": "炮", - "210": "点", - "211": "烈", - "212": "燃", - "213": "爪", - "214": "物", - "215": "特", - "216": "狼", - "217": "猎", - "218": "率", - "219": "王", - "220": "环", - "221": "球", - "222": "理", - "223": "瓦", - "224": "生", - "225": "甲", - "226": "电", - "227": "界", - "228": "疗", - "229": "白", - "230": "百", - "231": "的", - "232": "皮", - "233": "盔", - "234": "盗", - "235": "目", - "236": "眼", - "237": "短", - "238": "破", - "239": "磨", - "240": "神", - "241": "科", - "242": "秩", - "243": "移", - "244": "穆", - "245": "穗", - "246": "空", - "247": "站", - "248": "端", - "249": "簪", - "250": "粗", - "251": "素", - "252": "索", - "253": "纤", - "254": "纹", - "255": "线", - "256": "终", - "257": "绑", - "258": "绒", - "259": "绘", - "260": "绣", - "261": "绳", - "262": "缆", - "263": "罗", - "264": "罩", - "265": "羽", - "266": "翁", - "267": "翔", - "268": "翼", - "269": "耀", - "270": "者", - "271": "肃", - "272": "肢", - "273": "肩", - "274": "胫", - "275": "胸", - "276": "能", - "277": "腿", - "278": "臂", - "279": "舟", - "280": "航", - "281": "船", - "282": "艾", - "283": "芙", - "284": "荒", - "285": "莎", - "286": "莳", - "287": "萨", - "288": "落", - "289": "蔓", - "290": "虎", - "291": "虚", - "292": "蜥", - "293": "螺", - "294": "衣", - "295": "袍", - "296": "裂", - "297": "装", - "298": "裙", - "299": "裳", - "300": "裸", - "301": "誓", - "302": "诞", - "303": "贝", - "304": "贴", - "305": "贸", - "306": "赛", - "307": "超", - "308": "跑", - "309": "距", - "310": "身", - "311": "轨", - "312": "软", - "313": "过", - "314": "迹", - "315": "途", - "316": "速", - "317": "逢", - "318": "道", - "319": "遗", - "320": "遥", - "321": "部", - "322": "酷", - "323": "重", - "324": "野", - "325": "量", - "326": "金", - "327": "钉", - "328": "钢", - "329": "钩", - "330": "铁", - "331": "铆", - "332": "铵", - "333": "银", - "334": "铸", - "335": "镇", - "336": "镜", - "337": "镭", - "338": "长", - "339": "间", - "340": "队", - "341": "防", - "342": "阳", - "343": "阻", - "344": "阿", - "345": "雀", - "346": "雪", - "347": "雷", - "348": "露", - "349": "青", - "350": "面", - "351": "革", - "352": "靴", - "353": "鞋", - "354": "鞲", - "355": "须", - "356": "频", - "357": "风", - "358": "马", - "359": "驭", - "360": "骑", - "361": "高", - "362": "鲸", - "363": "鳞", - "364": "鹰", - "365": "鹿", - "366": "黑", - "367": "默", - "368": "龙" -} \ No newline at end of file diff --git a/models/model_training_starrail.onnx b/models/model_training_starrail.onnx deleted file mode 100644 index 0b001b27..00000000 --- a/models/model_training_starrail.onnx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6226ad47e62e49c8fabfb09db7439bf8099e77132096ef64aef5ba94961500b3 -size 4630951 diff --git a/src/artifact/mod.rs b/src/artifact/mod.rs deleted file mode 100644 index 421697ed..00000000 --- a/src/artifact/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod internal_artifact; -pub mod internal_relic; diff --git a/src/capture/mod.rs b/src/capture/mod.rs deleted file mode 100644 index 13daad87..00000000 --- a/src/capture/mod.rs +++ /dev/null @@ -1,87 +0,0 @@ -#[cfg(target_os = "macos")] -use std::os::macos::raw; - -use image::{ - buffer::ConvertBuffer, imageops::resize, imageops::FilterType::Triangle, RgbImage, RgbaImage, -}; - -use crate::common::color::Color; -use crate::common::PixelRect; - -use png::Decoder; - -/// retures Ok(buf) on success -/// buf contains pixels in [b:u8, g:u8, r:u8, a:u8] format, as an `[[i32;width];height]`. -pub fn capture_absolute( - PixelRect { - left, - top, - width, - height, - }: &PixelRect, -) -> Result { - let screen = screenshots::Screen::all().expect("cannot get DisplayInfo")[0]; - let png_img = screen - .capture_area(*left, *top, *width as u32, *height as u32) - .expect("capture failed"); - let mut rgb_img = png_decode(png_img).unwrap(); - if rgb_img.width() as i32 > *width && rgb_img.height() as i32 > *height { - rgb_img = resize(&rgb_img, (*width) as u32, (*height) as u32, Triangle); - } - Ok(rgb_img) -} - -fn png_decode(png_img: screenshots::Image) -> Result { - let png_decoder = Decoder::new(png_img.buffer().as_slice()); - let mut png_reader = png_decoder.read_info().unwrap(); - - let mut png_data_buf = vec![0; png_reader.output_buffer_size()]; - - let info = png_reader.next_frame(&mut png_data_buf).unwrap(); - - assert!( - info.color_type == png::ColorType::Rgba, - "Not rgba format image" - ); - - let _buffer = png_data_buf[..info.buffer_size()].to_vec(); - - let rgba_img = RgbaImage::from_raw(png_img.width(), png_img.height(), png_data_buf).unwrap(); - let rgb_img: RgbImage = rgba_img.convert(); - Ok(rgb_img) -} - -pub fn capture_absolute_image( - PixelRect { - left, - top, - width, - height, - }: &PixelRect, -) -> Result { - // simply use the first screen. - // todo: multi-screen support - let screen = screenshots::Screen::all().expect("cannot get DisplayInfo")[0]; - let image = screen - .capture_area(*left, *top, *width as u32, *height as u32) - .expect("capture failed"); - - let mut buffer = png_decode(image).unwrap(); - - if buffer.width() as i32 > *width && buffer.height() as i32 > *height { - buffer = resize(&buffer, (*width) as u32, (*height) as u32, Triangle); - } - Ok(buffer) -} - -pub fn get_color(x: u32, y: u32) -> Color { - let im = capture_absolute(&PixelRect { - left: x as i32, - top: y as i32, - width: 1, - height: 1, - }) - .unwrap(); - let pixel = im.get_pixel(0, 0); - Color::from(pixel[0], pixel[1], pixel[2]) -} diff --git a/src/common/buffer.rs b/src/common/buffer.rs deleted file mode 100644 index adf44d3b..00000000 --- a/src/common/buffer.rs +++ /dev/null @@ -1,11 +0,0 @@ -pub struct Buffer { - pub data: Vec>, -} - -impl Buffer { - fn new_zeroed(width: usize, height: usize) -> Buffer { - Buffer { - data: vec![vec![0.0; width]; height], - } - } -} diff --git a/src/common/color.rs b/src/common/color.rs deleted file mode 100644 index 511fdae8..00000000 --- a/src/common/color.rs +++ /dev/null @@ -1,25 +0,0 @@ -#[derive(Debug)] -pub struct Color(pub u8, pub u8, pub u8); - -impl Color { - pub fn is_same(&self, other: &Color) -> bool { - let dis = self.dis_2(other); - - dis < 20 - } - - pub fn dis_2(&self, other: &Color) -> u32 { - let dis = (self.0 as i32 - other.0 as i32) * (self.0 as i32 - other.0 as i32) - + (self.1 as i32 - other.1 as i32) * (self.1 as i32 - other.1 as i32) - + (self.2 as i32 - other.2 as i32) * (self.2 as i32 - other.2 as i32); - dis as u32 - } - - pub fn new() -> Color { - Color(0, 0, 0) - } - - pub fn from(r: u8, g: u8, b: u8) -> Color { - Color(r, g, b) - } -} diff --git a/src/common/mod.rs b/src/common/mod.rs deleted file mode 100644 index 4e4eb833..00000000 --- a/src/common/mod.rs +++ /dev/null @@ -1,185 +0,0 @@ -use crate::capture; -use crate::inference::pre_process::{ - pre_process, raw_to_img, to_gray, uint8_raw_to_img, GrayImageFloat, -}; -use crate::info::info::ScanInfo; -use image::{GrayImage, ImageBuffer, RgbImage}; -use log::info; -use std::time::SystemTime; - -pub mod buffer; -pub mod character_name; -pub mod color; -pub mod utils; - -#[derive(Debug)] -pub struct PixelRect { - pub left: i32, - pub top: i32, - pub width: i32, - pub height: i32, -} - -impl PixelRect { - pub fn scale(&mut self, ratio: f64) { - self.left = (self.left as f64 * ratio).round() as i32; - self.top = (self.top as f64 * ratio).round() as i32; - self.width = (self.width as f64 * ratio).round() as i32; - self.height = (self.height as f64 * ratio).round() as i32; - } -} - -#[derive(Clone, Debug)] -pub struct PixelRectBound { - pub left: i32, - pub top: i32, - pub right: i32, - pub bottom: i32, -} - -impl PixelRectBound { - pub fn capture_absolute(&self) -> Result { - let w = self.right - self.left; - let h = self.bottom - self.top; - let rect = PixelRect { - left: self.left, - top: self.top, - width: w, - height: h, - }; - let raw_u8 = capture::capture_absolute(&rect).unwrap(); - let raw_gray = to_gray(&raw_u8); - let raw_after_pp = pre_process(raw_gray); - - match raw_after_pp { - Some(im) => Ok(im), - None => Err(String::from("capture error")), - } - } - - pub fn capture_relative( - &self, - left: i32, - top: i32, - use_pre_process: bool, - ) -> Result { - let w = self.right - self.left; - let h = self.bottom - self.top; - let rect = PixelRect { - left: self.left + left, - top: self.top + top, - width: w, - height: h, - }; - let now = SystemTime::now(); - let raw_u8 = capture::capture_absolute(&rect).unwrap(); - info!("capture raw time: {}ms", now.elapsed().unwrap().as_millis()); - let raw_gray = to_gray(&raw_u8); - let raw_after_pp = if use_pre_process { - pre_process(raw_gray) - } else { - Some(raw_gray) - }; - - info!("preprocess time: {}ms", now.elapsed().unwrap().as_millis()); - - match raw_after_pp { - Some(im) => Ok(im), - None => Err(String::from("capture error")), - } - } - - pub fn capture_relative_image(&self, left: i32, top: i32) -> Result { - let w = self.right - self.left; - let h = self.bottom - self.top; - let rect = PixelRect { - left: self.left + left, - top: self.top + top, - width: w, - height: h, - }; - - capture::capture_absolute_image(&rect) - } -} - -pub struct RawImage { - pub data: Vec, - pub w: u32, - pub h: u32, -} - -pub struct RawCaptureImage { - pub data: Vec, - pub w: u32, - pub h: u32, -} - -impl RawImage { - pub fn to_gray_image(&self) -> GrayImage { - raw_to_img(&self) - } - - pub fn grayscale_to_gray_image(&self) -> GrayImage { - uint8_raw_to_img(&self) - } -} - -impl RawCaptureImage { - pub fn save(&self, path: &str) { - let width = self.w; - let height = self.h; - let data = &self.data; - - let img = ImageBuffer::from_fn(width, height, |x, y| { - let index = (y * self.w + x) as usize; - - let b = data[index * 4]; - let g = data[index * 4 + 1]; - let r = data[index * 4 + 2]; - - image::Rgb([r, g, b]) - // image::Luma([pixel]) - }); - - img.save(path).unwrap(); - } - - pub fn crop_to_raw_img(&self, rect: &PixelRect) -> RawImage { - // let now = SystemTime::now(); - let vol = rect.width * rect.height; - let mut data = vec![0.0; vol as usize]; - for i in rect.left..rect.left + rect.width { - for j in rect.top..rect.top + rect.height { - let x = i; - let y = j; - let b: u8 = self.data[((y * self.w as i32 + x) * 4) as usize]; - let g: u8 = self.data[((y * self.w as i32 + x) * 4 + 1) as usize]; - let r: u8 = self.data[((y * self.w as i32 + x) * 4 + 2) as usize]; - - let gray = r as f32 * 0.2989 + g as f32 * 0.5870 + b as f32 * 0.1140; - let new_index = ((j - rect.top) * rect.width + i - rect.left) as usize; - data[new_index] = gray; - } - } - - let im = RawImage { - data, - w: rect.width as u32, - h: rect.height as u32, - }; - // let im = pre_process(im); - // No preprocess! - - // info!("preprocess time: {}ms", now.elapsed().unwrap().as_millis()); - // im.to_gray_image().save("test.png"); - im - } -} - -// pub struct - -pub enum UI { - Desktop, - Mobile, -} diff --git a/src/common/utils.rs b/src/common/utils.rs deleted file mode 100644 index 6b525ecc..00000000 --- a/src/common/utils.rs +++ /dev/null @@ -1,74 +0,0 @@ -use std::fs; -use std::io::stdin; -use std::process; -use std::thread; -use std::time::Duration; - -use log::error; -use reqwest::blocking::Client; -use reqwest::header::{HeaderValue, USER_AGENT}; - -#[cfg(target_os = "macos")] -pub use mac::*; -#[cfg(windows)] -pub use windows::*; - -use crate::dto::GithubTag; - -#[cfg(target_os = "macos")] -mod mac; -#[cfg(windows)] -mod windows; - -pub fn sleep(ms: u32) { - let time = Duration::from_millis(ms as u64); - thread::sleep(time); -} - -pub fn read_file_to_string(path: String) -> String { - let content = fs::read_to_string(path).unwrap(); - content -} - -pub fn error_and_quit(msg: &str) -> ! { - error!("{}, 按Enter退出", msg); - let mut s: String = String::new(); - stdin().read_line(&mut s).unwrap(); - process::exit(0); -} - -#[cfg(not(windows))] -pub fn is_rmb_down() -> bool { - false -} - -pub const VERSION: &str = env!("CARGO_PKG_VERSION"); - -pub fn check_update() -> Option { - let client = Client::new(); - - let resp = client - .get("https://api.github.com/repos/wormtql/yas/tags") - .timeout(Duration::from_secs(5)) - .header(USER_AGENT, HeaderValue::from_static("reqwest")) - .send() - .ok()? - .json::>() - .ok()?; - - let latest = if resp.len() == 0 { - return None; - } else { - resp[0].name.clone() - }; - let latest = &latest[1..]; - - let latest_sem: semver::Version = semver::Version::parse(&latest).unwrap(); - let current_sem: semver::Version = semver::Version::parse(VERSION).unwrap(); - - if latest_sem > current_sem { - Some(String::from(latest)) - } else { - None - } -} diff --git a/src/dto/mod.rs b/src/dto/mod.rs deleted file mode 100644 index d96041a6..00000000 --- a/src/dto/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ -use serde::Deserialize; - -#[derive(Deserialize)] -pub struct GithubTag { - pub name: String, -} diff --git a/src/expo/mod.rs b/src/expo/mod.rs deleted file mode 100644 index 17bcaa13..00000000 --- a/src/expo/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod good; -pub mod mingyu_lab; -pub mod mona_uranai; -pub mod march7th; diff --git a/src/inference/mod.rs b/src/inference/mod.rs deleted file mode 100644 index 4ba6e9f2..00000000 --- a/src/inference/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod inference; -pub mod pre_process; diff --git a/src/info/info.rs b/src/info/info.rs deleted file mode 100644 index e931b1e2..00000000 --- a/src/info/info.rs +++ /dev/null @@ -1,77 +0,0 @@ -use crate::common::{PixelRect, PixelRectBound}; -use crate::info::window_info::{ - WINDOW_16_9, WINDOW_43_18, WINDOW_4_3, WINDOW_7_3, WINDOW_8_5, WINDOW_MAC_8_5, -}; - -#[derive(Clone, Debug)] -pub struct ScanInfo { - // pub panel_height: u32, - // pub panel_width: u32, - - // pub panel_position: PixelRectBound, - pub title_position: PixelRectBound, - pub main_stat_name_position: PixelRectBound, - pub main_stat_value_position: PixelRectBound, - pub level_position: PixelRectBound, - pub panel_position: PixelRectBound, - - pub sub_stat1_position: PixelRectBound, - pub sub_stat2_position: PixelRectBound, - pub sub_stat3_position: PixelRectBound, - pub sub_stat4_position: PixelRectBound, - - pub equip_position: PixelRectBound, - pub art_count_position: PixelRectBound, - - pub art_width: u32, - pub art_height: u32, - pub art_gap_x: u32, - pub art_gap_y: u32, - - pub art_row: u32, - pub art_col: u32, - - pub left_margin: u32, - pub top_margin: u32, - - pub width: u32, - pub height: u32, - pub left: i32, - pub top: i32, - - pub flag_x: u32, - pub flag_y: u32, - - pub star_x: u32, - pub star_y: u32, - - pub pool_position: PixelRectBound, -} - -impl ScanInfo { - pub fn from_pc(width: u32, height: u32, left: i32, top: i32) -> ScanInfo { - if height * 43 == width * 18 { - WINDOW_43_18.to_scan_info(height as f64, width as f64, left, top) - } else if height * 16 == width * 9 { - WINDOW_16_9.to_scan_info(height as f64, width as f64, left, top) - } else if height * 8 == width * 5 { - WINDOW_8_5.to_scan_info(height as f64, width as f64, left, top) - } else if height * 4 == width * 3 { - WINDOW_4_3.to_scan_info(height as f64, width as f64, left, top) - } else if height * 7 == width * 3 { - WINDOW_7_3.to_scan_info(height as f64, width as f64, left, top) - } else { - // 不支持的分辨率 - panic!("不支持的分辨率"); - } - } - pub fn from_mobile(width: u32, height: u32, left: i32, top: i32) -> ScanInfo { - if (height as i32 * 8 - width as i32 * 5).abs() < 20 { - // 窗口状态下的playcover分辨率长宽无法整除 - WINDOW_MAC_8_5.to_scan_info(height as f64, width as f64, left, top) - } else { - // 不支持的分辨率 - panic!("不支持的分辨率"); - } - } -} diff --git a/src/info/info_starrail.rs b/src/info/info_starrail.rs deleted file mode 100644 index a0cf4d39..00000000 --- a/src/info/info_starrail.rs +++ /dev/null @@ -1,81 +0,0 @@ -use crate::common::{PixelRect, PixelRectBound}; -use crate::info::window_info_starrail::{ - WINDOW_43_18, WINDOW_16_9, WINDOW_8_5, WINDOW_4_3, WINDOW_7_3, WINDOW_MAC_8_5 -}; - -#[derive(Clone, Debug)] -pub struct ScanInfoStarRail { - // pub panel_height: u32, - // pub panel_width: u32, - - // pub panel_position: PixelRectBound, - pub title_position: PixelRectBound, - pub main_stat_name_position: PixelRectBound, - pub main_stat_value_position: PixelRectBound, - pub level_position: PixelRectBound, - pub panel_position: PixelRectBound, - - pub sub_stat1_name_pos: PixelRectBound, - pub sub_stat1_value_pos: PixelRectBound, - pub sub_stat2_name_pos: PixelRectBound, - pub sub_stat2_value_pos: PixelRectBound, - pub sub_stat3_name_pos: PixelRectBound, - pub sub_stat3_value_pos: PixelRectBound, - pub sub_stat4_name_pos: PixelRectBound, - pub sub_stat4_value_pos: PixelRectBound, - - pub equip_position: PixelRectBound, - pub art_count_position: PixelRectBound, - - pub art_width: u32, - pub art_height: u32, - pub art_gap_x: u32, - pub art_gap_y: u32, - - pub art_row: u32, - pub art_col: u32, - - pub left_margin: u32, - pub top_margin: u32, - - pub width: u32, - pub height: u32, - pub left: i32, - pub top: i32, - - pub flag_x: u32, - pub flag_y: u32, - - pub star_x: u32, - pub star_y: u32, - - pub pool_position: PixelRectBound, -} - -impl ScanInfoStarRail { - pub fn from_pc(width: u32, height: u32, left: i32, top: i32) -> ScanInfoStarRail { - if height * 43 == width * 18 { - WINDOW_43_18.to_scan_info(height as f64, width as f64, left, top) - } else if height * 16 == width * 9 { - WINDOW_16_9.to_scan_info(height as f64, width as f64, left, top) - } else if height * 8 == width * 5 { - WINDOW_8_5.to_scan_info(height as f64, width as f64, left, top) - } else if height * 4 == width * 3 { - WINDOW_4_3.to_scan_info(height as f64, width as f64, left, top) - } else if height * 7 == width * 3 { - WINDOW_7_3.to_scan_info(height as f64, width as f64, left, top) - } else { - // 不支持的分辨率 - panic!("不支持的分辨率"); - } - } - pub fn from_mobile(width: u32, height: u32, left: i32, top: i32) -> ScanInfoStarRail { - if (height as i32 * 8 - width as i32 * 5).abs() < 20 { - // 窗口状态下的playcover分辨率长宽无法整除 - WINDOW_MAC_8_5.to_scan_info(height as f64, width as f64, left, top) - } else { - // 不支持的分辨率 - panic!("不支持的分辨率"); - } - } -} diff --git a/src/info/mod.rs b/src/info/mod.rs deleted file mode 100644 index 26878aca..00000000 --- a/src/info/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod info; -pub mod info_starrail; -pub mod window_info; -pub mod window_info_starrail; diff --git a/src/info/window_info.rs b/src/info/window_info.rs deleted file mode 100644 index b87023db..00000000 --- a/src/info/window_info.rs +++ /dev/null @@ -1,296 +0,0 @@ -use crate::common::PixelRectBound; -use crate::info::info::ScanInfo; - -pub struct Rect(f64, f64, f64, f64); // top, right, bottom, left - -pub struct WindowInfo { - pub width: f64, - pub height: f64, - - pub title_pos: Rect, - pub main_stat_name_pos: Rect, - pub main_stat_value_pos: Rect, - pub level_pos: Rect, - pub panel_pos: Rect, - - pub sub_stat1_pos: Rect, - pub sub_stat2_pos: Rect, - pub sub_stat3_pos: Rect, - pub sub_stat4_pos: Rect, - - pub equip_pos: Rect, - pub art_count_pos: Rect, - - pub art_width: f64, - pub art_height: f64, - pub art_gap_x: f64, - pub art_gap_y: f64, - - pub art_row: usize, - pub art_col: usize, - - pub left_margin: f64, - pub top_margin: f64, - - pub flag_x: f64, - pub flag_y: f64, - - pub star_x: f64, - pub star_y: f64, - - pub pool_pos: Rect, -} - -impl WindowInfo { - pub fn to_scan_info(&self, h: f64, w: f64, left: i32, top: i32) -> ScanInfo { - let convert_rect = |rect: &Rect| { - let top = rect.0 / self.height * h; - let right = rect.1 / self.width * w; - let bottom = rect.2 / self.height * h; - let left = rect.3 / self.width * w; - - PixelRectBound { - left: left as i32, - top: top as i32, - right: right as i32, - bottom: bottom as i32, - } - }; - - let convert_x = |x: f64| x / self.width * w; - - let convert_y = |y: f64| y / self.height * h; - - ScanInfo { - title_position: convert_rect(&self.title_pos), - main_stat_name_position: convert_rect(&self.main_stat_name_pos), - main_stat_value_position: convert_rect(&self.main_stat_value_pos), - level_position: convert_rect(&self.level_pos), - panel_position: convert_rect(&self.panel_pos), - sub_stat1_position: convert_rect(&self.sub_stat1_pos), - sub_stat2_position: convert_rect(&self.sub_stat2_pos), - sub_stat3_position: convert_rect(&self.sub_stat3_pos), - sub_stat4_position: convert_rect(&self.sub_stat4_pos), - equip_position: convert_rect(&self.equip_pos), - art_count_position: convert_rect(&self.art_count_pos), - art_width: convert_x(self.art_width) as u32, - art_height: convert_y(self.art_height) as u32, - art_gap_x: convert_x(self.art_gap_x) as u32, - art_gap_y: convert_y(self.art_gap_y) as u32, - art_row: self.art_row as u32, - art_col: self.art_col as u32, - left_margin: convert_x(self.left_margin) as u32, - top_margin: convert_y(self.top_margin) as u32, - width: w as u32, - height: h as u32, - left, - top, - flag_x: convert_x(self.flag_x) as u32, - flag_y: convert_y(self.flag_y) as u32, - star_x: convert_x(self.star_x) as u32, - star_y: convert_y(self.star_y) as u32, - pool_position: convert_rect(&self.pool_pos), - } - } -} - -pub const WINDOW_43_18: WindowInfo = WindowInfo { - width: 3440.0, - height: 1440.0, - - title_pos: Rect(170.0, 3140.0, 220.0, 2560.0), - main_stat_name_pos: Rect(360.0, 2850.0, 400.0, 2560.0), - main_stat_value_pos: Rect(400.0, 2850.0, 460.0, 2560.0), - level_pos: Rect(575.0, 2640.0, 605.0, 2568.0), - panel_pos: Rect(160.0, 3185.0, 1280.0, 2528.0), - - sub_stat1_pos: Rect(640.0, 3080.0, 680.0, 2590.0), - sub_stat2_pos: Rect(690.0, 3080.0, 730.0, 2590.0), - sub_stat3_pos: Rect(742.0, 3080.0, 782.0, 2590.0), - sub_stat4_pos: Rect(795.0, 3080.0, 835.0, 2590.0), - - equip_pos: Rect(1220.0, 5630.0, 1260.0, 3140.0), - art_count_pos: Rect(50.0, 3185.0, 85.0, 2750.0), - - art_width: 2421.0 - 2257.0, - art_height: 598.0 - 394.0, - art_gap_x: 2257.0 - 2225.0, - art_gap_y: 394.0 - 363.0, - - art_row: 5, - art_col: 11, - - left_margin: 305.0, - top_margin: 161.0, - - flag_x: 580.0, - flag_y: 145.0, - - star_x: 3130.0, - star_y: 200.0, - - pool_pos: Rect(170.0, 2610.0 + 30.0, 900.0, 2610.0), -}; - -pub const WINDOW_7_3: WindowInfo = WindowInfo { - width: 2100.0, - height: 900.0, - - title_pos: Rect(106.6, 1800.0, 139.6, 1550.0), - main_stat_name_pos: Rect(224.3, 1690.0, 248.0, 1550.0), - main_stat_value_pos: Rect(248.4, 1690.0, 286.8, 1550.0), - level_pos: Rect(360.0, 1600.0, 378.0, 1557.0), - panel_pos: Rect(100.0, 1941.0, 800.0, 1531.0), - - sub_stat1_pos: Rect(398.1, 1780.0, 427.3, 1570.0), - sub_stat2_pos: Rect(427.3, 1780.0, 458.2, 1570.0), - sub_stat3_pos: Rect(458.2, 1780.0, 490.9, 1570.0), - sub_stat4_pos: Rect(490.9, 1780.0, 523.0, 1570.0), - - equip_pos: Rect(762.6, 1850.0, 787.8, 1598.0), - art_count_pos: Rect(27.1, 1945.0, 52.9, 1785.0), - - art_width: 1055.0 - 953.0, - art_height: 373.0 - 247.0, - art_gap_x: 953.0 - 933.0, - art_gap_y: 247.0 - 227.0, - - art_row: 5, - art_col: 11, - - left_margin: 166.0, - top_margin: 101.0, - - flag_x: 340.0, - flag_y: 89.8, - - star_x: 1900.0, - star_y: 123.9, - pool_pos: Rect(118.2, 1584.0 + 15.0, 510.3, 1584.0), -}; - -pub const WINDOW_16_9: WindowInfo = WindowInfo { - width: 1600.0, - height: 900.0, - - title_pos: Rect(106.6, 1417.7, 139.6, 1111.8), - main_stat_name_pos: Rect(224.3, 1253.9, 248.0, 1110.0), - main_stat_value_pos: Rect(248.4, 1246.8, 286.8, 1110.0), - level_pos: Rect(360.0, 1160.0, 378.0, 1117.0), - panel_pos: Rect(100.0, 1500.0, 800.0, 1090.0), - - sub_stat1_pos: Rect(398.1, 1343.0, 427.3, 1130.2), - sub_stat2_pos: Rect(427.3, 1343.0, 458.2, 1130.2), - sub_stat3_pos: Rect(458.2, 1343.0, 490.9, 1130.2), - sub_stat4_pos: Rect(490.9, 1343.0, 523.0, 1130.2), - - equip_pos: Rect(762.6, 1389.4, 787.8, 1154.9), - art_count_pos: Rect(27.1, 1504.7, 52.9, 1314.9), - - art_width: 1055.0 - 953.0, - art_height: 373.0 - 247.0, - art_gap_x: 953.0 - 933.0, - art_gap_y: 247.0 - 227.0, - - art_row: 5, - art_col: 8, - - left_margin: 99.0, - top_margin: 101.0, - - flag_x: 271.1, - flag_y: 89.8, - - star_x: 1469.4, - star_y: 123.9, - - pool_pos: Rect(118.2, 1144.7 + 15.0, 510.3, 1144.7), -}; - -pub const WINDOW_8_5: WindowInfo = WindowInfo { - width: 1440.0, - height: 900.0, - title_pos: Rect(96.0, 1268.9, 126.1, 1000.9), - main_stat_name_pos: Rect(201.6, 1128.1, 223.9, 1000.3), - main_stat_value_pos: Rect(225.5, 1128.1, 262.8, 1000.3), - level_pos: Rect(324.0, 1043.0, 340.0, 1006.0), - panel_pos: Rect(90.0, 1350.0, 810.0, 981.0), - sub_stat1_pos: Rect(358.0, 1224.1, 384.1, 1016.2), - sub_stat2_pos: Rect(384.1, 1224.1, 412.6, 1016.2), - sub_stat3_pos: Rect(412.6, 1224.1, 440.5, 1016.2), - sub_stat4_pos: Rect(440.5, 1224.1, 467.1, 1016.2), - equip_pos: Rect(776.0, 1247.3, 800.6, 1041.3), - art_count_pos: Rect(25.0, 1353.1, 46.8, 1182.8), - art_width: 950.0 - 857.0, - art_height: 204.0 - 91.0, - art_gap_x: 857.0 - 840.0, - art_gap_y: 222.0 - 204.0, - art_row: 6, - art_col: 8, - left_margin: 89.0, - top_margin: 91.0, - flag_x: 245.9, - flag_y: 82.1, - star_x: 1321.3, - star_y: 111.3, - pool_pos: Rect(103.6, 1025.8 + 15.0, 460.7, 1028.5), -}; - -pub const WINDOW_4_3: WindowInfo = WindowInfo { - width: 1280.0, - height: 960.0, - title_pos: Rect(85.0, 1094.8, 111.7, 889.5), - main_stat_name_pos: Rect(181.0, 998.0, 199.8, 889.5), - main_stat_value_pos: Rect(199.8, 998.0, 233.4, 889.5), - level_pos: Rect(288.0, 927.0, 302.0, 894.0), - panel_pos: Rect(80.0, 1200.0, 880.0, 872.0), - sub_stat1_pos: Rect(318.2, 1100.5, 342.3, 904.3), - sub_stat2_pos: Rect(342.3, 1100.5, 369.4, 904.3), - sub_stat3_pos: Rect(369.4, 1100.5, 395.3, 904.3), - sub_stat4_pos: Rect(395.3, 1100.5, 420.6, 904.3), - equip_pos: Rect(849.8, 1090.8, 870.1, 924.4), - art_count_pos: Rect(22.9, 1202.3, 41.4, 1058.6), - art_width: 844.0 - 762.0, - art_height: 182.0 - 81.0, - art_gap_x: 762.0 - 747.0, - art_gap_y: 197.0 - 182.0, - art_row: 7, - art_col: 8, - left_margin: 79.0, - top_margin: 81.0, - flag_x: 218.1, - flag_y: 72.1, - star_x: 1175.4, - star_y: 95.8, - pool_pos: Rect(93.2, 912.7 + 15.0, 412.4, 912.7), -}; - -//top, right, bottom, left -pub const WINDOW_MAC_8_5: WindowInfo = WindowInfo { - width: 1164.0, - height: 755.0 - 28., - title_pos: Rect(122.0 - 28., 1090.0, 157.0 - 28., 770.0), - main_stat_name_pos: Rect(230. - 28., 925., 254. - 28., 765.), - main_stat_value_pos: Rect(253. - 28., 911., 294. - 28., 767.), - level_pos: Rect(353. - 28., 813., 372. - 28., 781.), - panel_pos: Rect(117. - 28., 1127., 666. - 28., 756.), - sub_stat1_pos: Rect(387. - 28., 1050., 417. - 28., 791.), - sub_stat2_pos: Rect(417. - 28., 1050., 446. - 28., 791.), - sub_stat3_pos: Rect(446. - 28., 1050., 475. - 28., 791.), - sub_stat4_pos: Rect(475. - 28., 1050., 504. - 28., 791.), - equip_pos: Rect(627. - 28., 1090., 659. - 28., 815.), - art_count_pos: Rect(51. - 28., 1076., 80. - 28., 924.), - art_width: 250. - 155., - art_height: 234. - 118., - art_gap_x: 266. - 250., - art_gap_y: 250. - 234., - art_row: 4, - art_col: 5, - left_margin: 155., - top_margin: 118. - 28., - flag_x: 170., //检测颜色出现重复,则判定换行完成 - flag_y: 223. - 28., - star_x: 1060., - star_y: 140. - 28., - pool_pos: Rect(390. - 28., 1010., 504. - 28., 792.), //检测平均颜色是否相同,判断圣遗物有没有切换 -}; diff --git a/src/info/window_info_starrail.rs b/src/info/window_info_starrail.rs deleted file mode 100644 index ae19539a..00000000 --- a/src/info/window_info_starrail.rs +++ /dev/null @@ -1,335 +0,0 @@ -use crate::common::PixelRectBound; -use crate::info::info_starrail::ScanInfoStarRail; - -pub struct Rect(f64, f64, f64, f64); // top, right, bottom, left - -pub struct WindowInfoStarRail { - pub width: f64, - pub height: f64, - - pub title_pos: Rect, - pub main_stat_name_pos: Rect, - pub main_stat_value_pos: Rect, - pub level_pos: Rect, - pub panel_pos: Rect, - - pub sub_stat1_name_pos: Rect, - pub sub_stat1_value_pos: Rect, - pub sub_stat2_name_pos: Rect, - pub sub_stat2_value_pos: Rect, - pub sub_stat3_name_pos: Rect, - pub sub_stat3_value_pos: Rect, - pub sub_stat4_name_pos: Rect, - pub sub_stat4_value_pos: Rect, - - pub equip_pos: Rect, - pub art_count_pos: Rect, - - pub art_width: f64, - pub art_height: f64, - pub art_gap_x: f64, - pub art_gap_y: f64, - - pub art_row: usize, - pub art_col: usize, - - pub left_margin: f64, - pub top_margin: f64, - - pub flag_x: f64, - pub flag_y: f64, - - pub star_x: f64, - pub star_y: f64, - - pub pool_pos: Rect, -} - -impl WindowInfoStarRail { - pub fn to_scan_info(&self, h: f64, w: f64, left: i32, top: i32) -> ScanInfoStarRail { - let convert_rect = |rect: &Rect| { - let top = rect.0 / self.height * h; - let right = rect.1 / self.width * w; - let bottom = rect.2 / self.height * h; - let left = rect.3 / self.width * w; - - PixelRectBound { - left: left as i32, - top: top as i32, - right: right as i32, - bottom: bottom as i32, - } - }; - - let convert_x = |x: f64| x / self.width * w; - - let convert_y = |y: f64| y / self.height * h; - - ScanInfoStarRail { - title_position: convert_rect(&self.title_pos), - main_stat_name_position: convert_rect(&self.main_stat_name_pos), - main_stat_value_position: convert_rect(&self.main_stat_value_pos), - level_position: convert_rect(&self.level_pos), - panel_position: convert_rect(&self.panel_pos), - sub_stat1_name_pos: convert_rect(&self.sub_stat1_name_pos), - sub_stat1_value_pos: convert_rect(&self.sub_stat1_value_pos), - sub_stat2_name_pos: convert_rect(&self.sub_stat2_name_pos), - sub_stat2_value_pos: convert_rect(&self.sub_stat2_value_pos), - sub_stat3_name_pos: convert_rect(&self.sub_stat3_name_pos), - sub_stat3_value_pos: convert_rect(&self.sub_stat3_value_pos), - sub_stat4_name_pos: convert_rect(&self.sub_stat4_name_pos), - sub_stat4_value_pos: convert_rect(&self.sub_stat4_value_pos), - equip_position: convert_rect(&self.equip_pos), - art_count_position: convert_rect(&self.art_count_pos), - art_width: convert_x(self.art_width) as u32, - art_height: convert_y(self.art_height) as u32, - art_gap_x: convert_x(self.art_gap_x) as u32, - art_gap_y: convert_y(self.art_gap_y) as u32, - art_row: self.art_row as u32, - art_col: self.art_col as u32, - left_margin: convert_x(self.left_margin) as u32, - top_margin: convert_y(self.top_margin) as u32, - width: w as u32, - height: h as u32, - left, - top, - flag_x: convert_x(self.flag_x) as u32, - flag_y: convert_y(self.flag_y) as u32, - star_x: convert_x(self.star_x) as u32, - star_y: convert_y(self.star_y) as u32, - pool_position: convert_rect(&self.pool_pos), - } - } -} - -pub const WINDOW_43_18: WindowInfoStarRail = WindowInfoStarRail { - width: 3440.0, - height: 1440.0, - - title_pos: Rect(170.0, 3140.0, 220.0, 2560.0), - main_stat_name_pos: Rect(360.0, 2850.0, 400.0, 2560.0), - main_stat_value_pos: Rect(400.0, 2850.0, 460.0, 2560.0), - level_pos: Rect(575.0, 2640.0, 605.0, 2568.0), - panel_pos: Rect(160.0, 3185.0, 1280.0, 2528.0), - - // 凑数用的 - sub_stat1_name_pos: Rect(370.0, 1534.0, 390.0, 1204.0), - sub_stat1_value_pos: Rect(370.0, 1534.0, 390.0, 1204.0), - sub_stat2_name_pos: Rect(402.0, 1534.0, 423.0, 1204.0), - sub_stat2_value_pos: Rect(402.0, 1534.0, 423.0, 1204.0), - sub_stat3_name_pos: Rect(435.0, 1534.0, 456.0, 1204.0), - sub_stat3_value_pos: Rect(435.0, 1534.0, 456.0, 1204.0), - sub_stat4_name_pos: Rect(467.0, 1534.0, 487.0, 1204.0), - sub_stat4_value_pos: Rect(467.0, 1534.0, 487.0, 1204.0), - - equip_pos: Rect(1220.0, 5630.0, 1260.0, 3140.0), - art_count_pos: Rect(50.0, 3185.0, 85.0, 2750.0), - - art_width: 2421.0 - 2257.0, - art_height: 598.0 - 394.0, - art_gap_x: 2257.0 - 2225.0, - art_gap_y: 394.0 - 363.0, - - art_row: 5, - art_col: 11, - - left_margin: 305.0, - top_margin: 161.0, - - flag_x: 580.0, - flag_y: 145.0, - - star_x: 3130.0, - star_y: 200.0, - - pool_pos: Rect(170.0, 2610.0 + 30.0, 900.0, 2610.0), -}; - -pub const WINDOW_7_3: WindowInfoStarRail = WindowInfoStarRail { - width: 2100.0, - height: 900.0, - - title_pos: Rect(106.6, 1800.0, 139.6, 1550.0), - main_stat_name_pos: Rect(224.3, 1690.0, 248.0, 1550.0), - main_stat_value_pos: Rect(248.4, 1690.0, 286.8, 1550.0), - level_pos: Rect(360.0, 1600.0, 378.0, 1557.0), - panel_pos: Rect(100.0, 1941.0, 800.0, 1531.0), - - // 凑数用的 - sub_stat1_name_pos: Rect(370.0, 1534.0, 390.0, 1204.0), - sub_stat1_value_pos: Rect(370.0, 1534.0, 390.0, 1204.0), - sub_stat2_name_pos: Rect(402.0, 1534.0, 423.0, 1204.0), - sub_stat2_value_pos: Rect(402.0, 1534.0, 423.0, 1204.0), - sub_stat3_name_pos: Rect(435.0, 1534.0, 456.0, 1204.0), - sub_stat3_value_pos: Rect(435.0, 1534.0, 456.0, 1204.0), - sub_stat4_name_pos: Rect(467.0, 1534.0, 487.0, 1204.0), - sub_stat4_value_pos: Rect(467.0, 1534.0, 487.0, 1204.0), - - equip_pos: Rect(762.6, 1850.0, 787.8, 1598.0), - art_count_pos: Rect(27.1, 1945.0, 52.9, 1785.0), - - art_width: 1055.0 - 953.0, - art_height: 373.0 - 247.0, - art_gap_x: 953.0 - 933.0, - art_gap_y: 247.0 - 227.0, - - art_row: 5, - art_col: 11, - - left_margin: 166.0, - top_margin: 101.0, - - flag_x: 340.0, - flag_y: 89.8, - - star_x: 1900.0, - star_y: 123.9, - pool_pos: Rect(118.2, 1584.0 + 15.0, 510.3, 1584.0), -}; - -pub const WINDOW_16_9: WindowInfoStarRail = WindowInfoStarRail { - width: 1600.0, - height: 900.0, - - title_pos: Rect(111.0, 1400.0, 132.0, 1169.0), - main_stat_name_pos: Rect(335.0, 1379.0, 355.0, 1207.0), - main_stat_value_pos: Rect(335.0, 1535.0, 355.0, 1465.0), - level_pos: Rect(258.0, 1240.0, 285.0, 1170.0), - panel_pos: Rect(100.0, 1550.0, 800.0, 1150.0), - - sub_stat1_name_pos: Rect(370.0, 1369.0, 390.0, 1204.0), - sub_stat1_value_pos: Rect(370.0, 1534.0, 390.0, 1369.0), - sub_stat2_name_pos: Rect(402.0, 1369.0, 423.0, 1204.0), - sub_stat2_value_pos: Rect(402.0, 1534.0, 423.0, 1369.0), - sub_stat3_name_pos: Rect(435.0, 1369.0, 456.0, 1204.0), - sub_stat3_value_pos: Rect(435.0, 1534.0, 456.0, 1369.0), - sub_stat4_name_pos: Rect(467.0, 1369.0, 487.0, 1204.0), - sub_stat4_value_pos: Rect(467.0, 1534.0, 487.0, 1369.0), - - equip_pos: Rect(762.6, 1389.4, 787.8, 1154.9), - art_count_pos: Rect(813.0, 960.0, 836.0, 753.0), - - art_width: 197.5 - 112.5, - art_height: 379.2 - 295.8, - art_gap_x: 217.5 - 197.5, - art_gap_y: 420.0 - 379.2, - - art_row: 5, - art_col: 9, - - left_margin: 113.0, - top_margin: 172.0, - - flag_x: 271.1, - flag_y: 158.0, - - star_x: 1500.0, - star_y: 208.0, - - pool_pos: Rect(118.2, 1218.7 + 15.0, 510.3, 1218.7), -}; - - -pub const WINDOW_8_5: WindowInfoStarRail = WindowInfoStarRail { - width: 1440.0, - height: 900.0, - title_pos: Rect(96.0, 1268.9, 126.1, 1000.9), - main_stat_name_pos: Rect(201.6, 1128.1, 223.9, 1000.3), - main_stat_value_pos: Rect(225.5, 1128.1, 262.8, 1000.3), - level_pos: Rect(324.0, 1043.0, 340.0, 1006.0), - panel_pos: Rect(90.0, 1350.0, 810.0, 981.0), - // 凑数用的 - sub_stat1_name_pos: Rect(370.0, 1534.0, 390.0, 1204.0), - sub_stat1_value_pos: Rect(370.0, 1534.0, 390.0, 1204.0), - sub_stat2_name_pos: Rect(402.0, 1534.0, 423.0, 1204.0), - sub_stat2_value_pos: Rect(402.0, 1534.0, 423.0, 1204.0), - sub_stat3_name_pos: Rect(435.0, 1534.0, 456.0, 1204.0), - sub_stat3_value_pos: Rect(435.0, 1534.0, 456.0, 1204.0), - sub_stat4_name_pos: Rect(467.0, 1534.0, 487.0, 1204.0), - sub_stat4_value_pos: Rect(467.0, 1534.0, 487.0, 1204.0), - equip_pos: Rect(776.0, 1247.3, 800.6, 1041.3), - art_count_pos: Rect(25.0, 1353.1, 46.8, 1182.8), - art_width: 950.0 - 857.0, - art_height: 204.0 - 91.0, - art_gap_x: 857.0 - 840.0, - art_gap_y: 222.0 - 204.0, - art_row: 6, - art_col: 8, - left_margin: 89.0, - top_margin: 91.0, - flag_x: 245.9, - flag_y: 82.1, - star_x: 1321.3, - star_y: 111.3, - pool_pos: Rect(103.6, 1025.8 + 15.0, 460.7, 1028.5), -}; - - -pub const WINDOW_4_3: WindowInfoStarRail = WindowInfoStarRail { - width: 1280.0, - height: 960.0, - title_pos: Rect(85.0, 1094.8, 111.7, 889.5), - main_stat_name_pos: Rect(181.0, 998.0, 199.8, 889.5), - main_stat_value_pos: Rect(199.8, 998.0, 233.4, 889.5), - level_pos: Rect(288.0, 927.0, 302.0, 894.0), - panel_pos: Rect(80.0, 1200.0, 880.0, 872.0), - // 凑数用的 - sub_stat1_name_pos: Rect(370.0, 1534.0, 390.0, 1204.0), - sub_stat1_value_pos: Rect(370.0, 1534.0, 390.0, 1204.0), - sub_stat2_name_pos: Rect(402.0, 1534.0, 423.0, 1204.0), - sub_stat2_value_pos: Rect(402.0, 1534.0, 423.0, 1204.0), - sub_stat3_name_pos: Rect(435.0, 1534.0, 456.0, 1204.0), - sub_stat3_value_pos: Rect(435.0, 1534.0, 456.0, 1204.0), - sub_stat4_name_pos: Rect(467.0, 1534.0, 487.0, 1204.0), - sub_stat4_value_pos: Rect(467.0, 1534.0, 487.0, 1204.0), - equip_pos: Rect(849.8, 1090.8, 870.1, 924.4), - art_count_pos: Rect(22.9, 1202.3, 41.4, 1058.6), - art_width: 844.0 - 762.0, - art_height: 182.0 - 81.0, - art_gap_x: 762.0 - 747.0, - art_gap_y: 197.0 - 182.0, - art_row: 7, - art_col: 8, - left_margin: 79.0, - top_margin: 81.0, - flag_x: 218.1, - flag_y: 72.1, - star_x: 1175.4, - star_y: 95.8, - pool_pos: Rect(93.2, 912.7 + 15.0, 412.4, 912.7), -}; - - -pub const WINDOW_MAC_8_5: WindowInfoStarRail = WindowInfoStarRail { - width: 1164.0, - height: 755.0 - 28., - title_pos: Rect(122.0 - 28., 1090.0, 157.0 - 28., 770.0), - main_stat_name_pos: Rect(230. - 28., 925., 254. - 28., 765.), - main_stat_value_pos: Rect(253. - 28., 911., 294. - 28., 767.), - level_pos: Rect(353. - 28., 813., 372. - 28., 781.), - panel_pos: Rect(117. - 28., 1127., 666. - 28., 756.), - // 凑数用的 - sub_stat1_name_pos: Rect(370.0, 1534.0, 390.0, 1204.0), - sub_stat1_value_pos: Rect(370.0, 1534.0, 390.0, 1204.0), - sub_stat2_name_pos: Rect(402.0, 1534.0, 423.0, 1204.0), - sub_stat2_value_pos: Rect(402.0, 1534.0, 423.0, 1204.0), - sub_stat3_name_pos: Rect(435.0, 1534.0, 456.0, 1204.0), - sub_stat3_value_pos: Rect(435.0, 1534.0, 456.0, 1204.0), - sub_stat4_name_pos: Rect(467.0, 1534.0, 487.0, 1204.0), - sub_stat4_value_pos: Rect(467.0, 1534.0, 487.0, 1204.0), - equip_pos: Rect(627. - 28., 1090., 659. - 28., 815.), - art_count_pos: Rect(51. - 28., 1076., 80. - 28., 924.), - art_width: 250. - 155., - art_height: 234. - 118., - art_gap_x: 266. - 250., - art_gap_y: 250. - 234., - art_row: 4, - art_col: 5, - left_margin: 155., - top_margin: 118. - 28., - flag_x: 170., //检测颜色出现重复,则判定换行完成 - flag_y: 223. - 28., - star_x: 1060., - star_y: 140. - 28., - pool_pos: Rect(390. - 28., 1010., 504. - 28., 792.), //检测平均颜色是否相同,判断圣遗物有没有切换 -}; diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index 2714f4f7..00000000 --- a/src/lib.rs +++ /dev/null @@ -1,8 +0,0 @@ -pub mod artifact; -pub mod capture; -pub mod common; -pub mod dto; -pub mod expo; -pub mod inference; -pub mod info; -pub mod scanner; diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index 4e3dae18..00000000 --- a/src/main.rs +++ /dev/null @@ -1,311 +0,0 @@ -use std::io::stdin; -use std::path::Path; -use std::time::SystemTime; - -#[cfg(target_os = "macos")] -use yas_scanner::common::utils::get_pid_and_ui; -use yas_scanner::common::{utils, UI}; -use yas_scanner::common::{PixelRect, RawImage}; -use yas_scanner::expo::good::GOODFormat; -use yas_scanner::expo::mingyu_lab::MingyuLabFormat; -use yas_scanner::expo::mona_uranai::MonaFormat; - -use yas_scanner::inference::pre_process::image_to_raw; -use yas_scanner::info::info; -use yas_scanner::scanner::yas_scanner::{YasScanner, YasScannerConfig}; - -use clap::{App, Arg}; -use env_logger::Builder; -use image::imageops::grayscale; - -use log::{info, warn, LevelFilter}; - -fn open_local(path: String) -> RawImage { - let img = image::open(path).unwrap(); - let img = grayscale(&img); - let raw_img = image_to_raw(img); - - raw_img -} - -fn main() { - Builder::new().filter_level(LevelFilter::Info).init(); - - #[cfg(windows)] - if !utils::is_admin() { - utils::error_and_quit("请以管理员身份运行该程序") - } - - if let Some(v) = utils::check_update() { - warn!("检测到新版本,请手动更新:{}", v); - } - - let matches = App::new("YAS - 原神圣遗物导出器") - .version(utils::VERSION) - .author("wormtql <584130248@qq.com>") - .about("Genshin Impact Artifact Exporter") - .arg( - Arg::with_name("max-row") - .long("max-row") - .takes_value(true) - .help("最大扫描行数"), - ) - .arg( - Arg::with_name("dump") - .long("dump") - .required(false) - .takes_value(false) - .help("输出模型预测结果、二值化图像和灰度图像,debug专用"), - ) - .arg( - Arg::with_name("capture-only") - .long("capture-only") - .required(false) - .takes_value(false) - .help("只保存截图,不进行扫描,debug专用"), - ) - .arg( - Arg::with_name("min-star") - .long("min-star") - .takes_value(true) - .help("最小星级"), - ) - .arg( - Arg::with_name("min-level") - .long("min-level") - .takes_value(true) - .help("最小等级"), - ) - .arg( - Arg::with_name("max-wait-switch-artifact") - .long("max-wait-switch-artifact") - .takes_value(true) - .help("切换圣遗物最大等待时间(ms)"), - ) - .arg( - Arg::with_name("output-dir") - .long("output-dir") - .short("o") - .takes_value(true) - .help("输出目录") - .default_value("."), - ) - .arg( - Arg::with_name("scroll-stop") - .long("scroll-stop") - .takes_value(true) - .help("翻页时滚轮停顿时间(ms)(翻页不正确可以考虑加大该选项,默认为80)"), - ) - .arg( - Arg::with_name("number") - .long("number") - .takes_value(true) - .help("指定圣遗物数量(在自动识别数量不准确时使用)"), - ) - .arg( - Arg::with_name("verbose") - .long("verbose") - .help("显示详细信息"), - ) - .arg( - Arg::with_name("offset-x") - .long("offset-x") - .takes_value(true) - .help("人为指定横坐标偏移(截图有偏移时可用该选项校正)"), - ) - .arg( - Arg::with_name("offset-y") - .long("offset-y") - .takes_value(true) - .help("人为指定纵坐标偏移(截图有偏移时可用该选项校正)"), - ) - .arg( - Arg::with_name("output-format") - .long("output-format") - .short("f") - .takes_value(true) - .help("输出格式") - .possible_values(&["mona", "mingyulab", "good"]) - .default_value("mona"), - ) - .arg( - Arg::with_name("cloud-wait-switch-artifact") - .long("cloud-wait-switch-artifact") - .takes_value(true) - .help("指定云·原神切换圣遗物等待时间(ms)"), - ) - .get_matches(); - let config = YasScannerConfig::from_match(&matches); - - let rect: PixelRect; - let is_cloud: bool; - let ui: UI; - - #[cfg(windows)] - { - use winapi::um::winuser::{SetForegroundWindow, ShowWindow, SW_RESTORE}; - // use winapi::um::shellscalingapi::{SetProcessDpiAwareness, PROCESS_PER_MONITOR_DPI_AWARE}; - - utils::set_dpi_awareness(); - - let hwnd; - - (hwnd, is_cloud) = utils::find_window_local("原神") - .or_else(|_| utils::find_window_local("Genshin Impact")) - .map(|hwnd| (hwnd, false)) - .unwrap_or_else(|_| { - let Ok(hwnd) = utils::find_window_cloud() else { - utils::error_and_quit("未找到原神窗口,请确认原神已经开启") - }; - (hwnd, true) - }); - - unsafe { - ShowWindow(hwnd, SW_RESTORE); - } - // utils::sleep(1000); - unsafe { - SetForegroundWindow(hwnd); - } - utils::sleep(1000); - - rect = utils::get_client_rect(hwnd).unwrap(); - ui = UI::Desktop; - } - - #[cfg(all(target_os = "linux"))] - { - let window_id = unsafe { - String::from_utf8_unchecked( - std::process::Command::new("sh") - .arg("-c") - .arg(r#" xwininfo|grep "Window id"|cut -d " " -f 4 "#) - .output() - .unwrap() - .stdout, - ) - }; - let window_id = window_id.trim_end_matches("\n"); - - let position_size = unsafe { - String::from_utf8_unchecked( - std::process::Command::new("sh") - .arg("-c") - .arg(&format!(r#" xwininfo -id {window_id}|cut -f 2 -d :|tr -cd "0-9\n"|grep -v "^$"|sed -n "1,2p;5,6p" "#)) - .output() - .unwrap() - .stdout, - ) - }; - - let mut info = position_size.split("\n"); - - let left = info.next().unwrap().parse().unwrap(); - let top = info.next().unwrap().parse().unwrap(); - let width = info.next().unwrap().parse().unwrap(); - let height = info.next().unwrap().parse().unwrap(); - - rect = PixelRect { - left, - top, - width, - height, - }; - is_cloud = false; // todo: detect cloud genshin by title - ui = UI::Desktop; - } - - #[cfg(target_os = "macos")] - { - let (pid, ui_) = get_pid_and_ui(); - let window_title: String; - (rect, window_title) = unsafe { utils::find_window_by_pid(pid).unwrap() }; - info!("Found genshin pid:{}, window name:{}", pid, window_title); - is_cloud = false; // todo: detect cloud genshin by title - ui = ui_; - } - - // rect.scale(1.25); - info!( - "left = {}, top = {}, width = {}, height = {}", - rect.left, rect.top, rect.width, rect.height - ); - - let mut info: info::ScanInfo; - - // desktop ui or mobile ui - match ui { - UI::Desktop => { - info!("desktop ui"); - info = info::ScanInfo::from_pc( - rect.width as u32, - rect.height as u32, - rect.left, - rect.top, - ); - }, - UI::Mobile => { - info!("mobile ui"); - info = info::ScanInfo::from_mobile( - rect.width as u32, - rect.height as u32, - rect.left, - rect.top, - ); - }, - } - - let offset_x = matches - .value_of("offset-x") - .unwrap_or("0") - .parse::() - .unwrap(); - let offset_y = matches - .value_of("offset-y") - .unwrap_or("0") - .parse::() - .unwrap(); - info.left += offset_x; - info.top += offset_y; - - let mut scanner = YasScanner::new(info.clone(), config, is_cloud); - - let now = SystemTime::now(); - #[cfg(target_os = "macos")] - { - info!("初始化完成,请切换到原神窗口,yas将在10s后开始扫描圣遗物"); - utils::sleep(10000); - } - let results = scanner.start(); - let t = now.elapsed().unwrap().as_secs_f64(); - info!("time: {}s", t); - - let output_dir = Path::new(matches.value_of("output-dir").unwrap()); - match matches.value_of("output-format") { - Some("mona") => { - let output_filename = output_dir.join("mona.json"); - let mona = MonaFormat::new(&results); - mona.save(String::from(output_filename.to_str().unwrap())); - }, - Some("mingyulab") => { - let output_filename = output_dir.join("mingyulab.json"); - let mingyulab = MingyuLabFormat::new(&results); - mingyulab.save(String::from(output_filename.to_str().unwrap())); - }, - Some("good") => { - let output_filename = output_dir.join("good.json"); - let good = GOODFormat::new(&results); - good.save(String::from(output_filename.to_str().unwrap())); - }, - _ => unreachable!(), - } - // let info = info; - // let img = info.art_count_position.capture_relative(&info).unwrap(); - - // let mut inference = CRNNModel::new(String::from("model_training.onnx"), String::from("index_2_word.json")); - // let s = inference.inference_string(&img); - // println!("{}", s); - info!("识别结束,请按Enter退出"); - let mut s = String::new(); - stdin().read_line(&mut s).unwrap(); -} diff --git a/src/main_starrail.rs b/src/main_starrail.rs deleted file mode 100644 index 47b54b28..00000000 --- a/src/main_starrail.rs +++ /dev/null @@ -1,299 +0,0 @@ -use std::io::stdin; -use std::path::Path; -use std::time::SystemTime; - -#[cfg(target_os = "macos")] -use yas_scanner::common::utils::get_pid_and_ui; -use yas_scanner::common::{utils, UI}; -use yas_scanner::common::{PixelRect, RawImage}; -use yas_scanner::expo::march7th::March7thFormat; - -use yas_scanner::inference::pre_process::image_to_raw; -use yas_scanner::info::info_starrail; -use yas_scanner::scanner::yas_scanner_starrail::{YasScanner, YasScannerConfig}; - -use clap::{App, Arg}; -use env_logger::Builder; -use image::imageops::grayscale; - -use log::{info, warn, LevelFilter}; - -fn open_local(path: String) -> RawImage { - let img = image::open(path).unwrap(); - let img = grayscale(&img); - let raw_img = image_to_raw(img); - - raw_img -} - -fn main() { - Builder::new().filter_level(LevelFilter::Info).init(); - - #[cfg(windows)] - if !utils::is_admin() { - utils::error_and_quit("请以管理员身份运行该程序") - } - - if let Some(v) = utils::check_update() { - warn!("检测到新版本,请手动更新:{}", v); - } - - let matches = App::new("YAS - 崩坏:星穹铁道遗器导出器") - .version(utils::VERSION) - .author("wormtql <584130248@qq.com>") - .about("Honkai: Star Rail Relic Exporter") - .arg( - Arg::with_name("max-row") - .long("max-row") - .takes_value(true) - .help("最大扫描行数"), - ) - .arg( - Arg::with_name("dump") - .long("dump") - .required(false) - .takes_value(false) - .help("输出模型预测结果、二值化图像和灰度图像,debug专用"), - ) - .arg( - Arg::with_name("capture-only") - .long("capture-only") - .required(false) - .takes_value(false) - .help("只保存截图,不进行扫描,debug专用"), - ) - .arg( - Arg::with_name("min-star") - .long("min-star") - .takes_value(true) - .help("最小星级"), - ) - .arg( - Arg::with_name("min-level") - .long("min-level") - .takes_value(true) - .help("最小等级"), - ) - .arg( - Arg::with_name("max-wait-switch-relic") - .long("max-wait-switch-relic") - .takes_value(true) - .help("切换遗器最大等待时间(ms)"), - ) - .arg( - Arg::with_name("output-dir") - .long("output-dir") - .short("o") - .takes_value(true) - .help("输出目录") - .default_value("."), - ) - .arg( - Arg::with_name("scroll-stop") - .long("scroll-stop") - .takes_value(true) - .help("翻页时滚轮停顿时间(ms)(翻页不正确可以考虑加大该选项,默认为80)"), - ) - .arg( - Arg::with_name("number") - .long("number") - .takes_value(true) - .help("指定遗器数量(在自动识别数量不准确时使用)"), - ) - .arg( - Arg::with_name("verbose") - .long("verbose") - .help("显示详细信息"), - ) - .arg( - Arg::with_name("offset-x") - .long("offset-x") - .takes_value(true) - .help("人为指定横坐标偏移(截图有偏移时可用该选项校正)"), - ) - .arg( - Arg::with_name("offset-y") - .long("offset-y") - .takes_value(true) - .help("人为指定纵坐标偏移(截图有偏移时可用该选项校正)"), - ) - .arg( - Arg::with_name("output-format") - .long("output-format") - .short("f") - .takes_value(true) - .help("输出格式") - .possible_values(&["march7th"]) - .default_value("march7th"), - ) - .arg( - Arg::with_name("cloud-wait-switch-relic") - .long("cloud-wait-switch-relic") - .takes_value(true) - .help("指定云·崩坏:星穹铁道切换遗器等待时间(ms)"), - ) - .get_matches(); - let config = YasScannerConfig::from_match(&matches); - - let rect: PixelRect; - let is_cloud: bool; - let ui: UI; - - #[cfg(windows)] - { - use winapi::um::winuser::{SetForegroundWindow, ShowWindow, SW_RESTORE}; - // use winapi::um::shellscalingapi::{SetProcessDpiAwareness, PROCESS_PER_MONITOR_DPI_AWARE}; - - utils::set_dpi_awareness(); - - let hwnd; - - (hwnd, is_cloud) = utils::find_window_local("崩坏:星穹铁道") - .or_else(|_| utils::find_window_local("Honkai: Star Rail")) - .map(|hwnd| (hwnd, false)) - .unwrap_or_else(|_| { - let Ok(hwnd) = utils::find_window_cloud() else { - utils::error_and_quit("未找到崩坏:星穹铁道窗口,请确认崩坏:星穹铁道已经开启") - }; - (hwnd, true) - }); - - unsafe { - ShowWindow(hwnd, SW_RESTORE); - } - // utils::sleep(1000); - unsafe { - SetForegroundWindow(hwnd); - } - utils::sleep(1000); - - rect = utils::get_client_rect(hwnd).unwrap(); - ui = UI::Desktop; - } - - #[cfg(all(target_os = "linux"))] - { - let window_id = unsafe { - String::from_utf8_unchecked( - std::process::Command::new("sh") - .arg("-c") - .arg(r#" xwininfo|grep "Window id"|cut -d " " -f 4 "#) - .output() - .unwrap() - .stdout, - ) - }; - let window_id = window_id.trim_end_matches("\n"); - - let position_size = unsafe { - String::from_utf8_unchecked( - std::process::Command::new("sh") - .arg("-c") - .arg(&format!(r#" xwininfo -id {window_id}|cut -f 2 -d :|tr -cd "0-9\n"|grep -v "^$"|sed -n "1,2p;5,6p" "#)) - .output() - .unwrap() - .stdout, - ) - }; - - let mut info = position_size.split("\n"); - - let left = info.next().unwrap().parse().unwrap(); - let top = info.next().unwrap().parse().unwrap(); - let width = info.next().unwrap().parse().unwrap(); - let height = info.next().unwrap().parse().unwrap(); - - rect = PixelRect { - left, - top, - width, - height, - }; - is_cloud = false; // todo: detect cloud starrail by title - ui = UI::Desktop; - } - - #[cfg(target_os = "macos")] - { - let (pid, ui_) = get_pid_and_ui(); - let window_title: String; - (rect, window_title) = unsafe { utils::find_window_by_pid(pid).unwrap() }; - info!("Found starrail pid:{}, window name:{}", pid, window_title); - is_cloud = false; // todo: detect cloud starrail by title - ui = ui_; - } - - // rect.scale(1.25); - info!( - "left = {}, top = {}, width = {}, height = {}", - rect.left, rect.top, rect.width, rect.height - ); - - let mut info: info_starrail::ScanInfoStarRail; - - // desktop ui or mobile ui - match ui { - UI::Desktop => { - info!("desktop ui"); - info = info_starrail::ScanInfoStarRail::from_pc( - rect.width as u32, - rect.height as u32, - rect.left, - rect.top, - ); - }, - UI::Mobile => { - info!("mobile ui"); - info = info_starrail::ScanInfoStarRail::from_mobile( - rect.width as u32, - rect.height as u32, - rect.left, - rect.top, - ); - }, - } - - let offset_x = matches - .value_of("offset-x") - .unwrap_or("0") - .parse::() - .unwrap(); - let offset_y = matches - .value_of("offset-y") - .unwrap_or("0") - .parse::() - .unwrap(); - info.left += offset_x; - info.top += offset_y; - - let mut scanner = YasScanner::new(info.clone(), config, is_cloud); - - let now = SystemTime::now(); - #[cfg(target_os = "macos")] - { - info!("初始化完成,请切换到崩坏:星穹铁道窗口,yas将在10s后开始扫描遗器"); - utils::sleep(10000); - } - let results = scanner.start(); - let t = now.elapsed().unwrap().as_secs_f64(); - info!("time: {}s", t); - - let output_dir = Path::new(matches.value_of("output-dir").unwrap()); - match matches.value_of("output-format") { - Some("march7th") => { - let output_filename = output_dir.join("march7th.json"); - let march7th = March7thFormat::new(&results); - march7th.save(String::from(output_filename.to_str().unwrap())); - }, - _ => unreachable!(), - } - // let info = info; - // let img = info.relic_count_position.capture_relative(&info).unwrap(); - - // let mut inference = CRNNModel::new(String::from("model_training.onnx"), String::from("index_2_word.json")); - // let s = inference.inference_string(&img); - // println!("{}", s); - info!("识别结束,请按Enter退出"); - let mut s = String::new(); - stdin().read_line(&mut s).unwrap(); -} diff --git a/src/scanner/mod.rs b/src/scanner/mod.rs deleted file mode 100644 index 903c3b8f..00000000 --- a/src/scanner/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod yas_scanner; -pub mod yas_scanner_starrail; diff --git a/src/scanner/yas_scanner.rs b/src/scanner/yas_scanner.rs deleted file mode 100644 index 5b79e05e..00000000 --- a/src/scanner/yas_scanner.rs +++ /dev/null @@ -1,937 +0,0 @@ -use std::collections::HashSet; -use std::convert::From; -use std::fs; - -use std::sync::mpsc; -use std::thread; -use std::time::SystemTime; - -use clap::ArgMatches; -use enigo::*; -use image::{GenericImageView, RgbImage}; -use log::{error, info, warn}; -use tract_onnx::prelude::tract_itertools::Itertools; - -use crate::artifact::internal_artifact::{ - ArtifactSetName, ArtifactSlot, ArtifactStat, InternalArtifact, -}; -use crate::capture::{self}; -use crate::common::character_name::CHARACTER_NAMES; -use crate::common::color::Color; -#[cfg(target_os = "macos")] -use crate::common::utils::get_pid_and_ui; -use crate::common::{utils, PixelRect, PixelRectBound}; -use crate::inference::inference::CRNNModel; -use crate::inference::pre_process::{pre_process, to_gray, ImageConvExt}; -use crate::info::info::ScanInfo; - -// Playcover only, wine should not need this. -#[cfg(all(target_os = "macos", target_arch = "aarch64"))] -use crate::common::utils::mac_scroll; - -pub struct YasScannerConfig { - max_row: u32, - capture_only: bool, - min_star: u32, - min_level: u32, - max_wait_switch_artifact: u32, - scroll_stop: u32, - number: u32, - verbose: bool, - dump_mode: bool, - cloud_wait_switch_artifact: u32, - // offset_x: i32, - // offset_y: i32, -} - -impl YasScannerConfig { - pub fn from_match(matches: &ArgMatches) -> YasScannerConfig { - YasScannerConfig { - max_row: matches - .value_of("max-row") - .unwrap_or("1000") - .parse::() - .unwrap(), - capture_only: matches.is_present("capture-only"), - dump_mode: matches.is_present("dump"), - min_star: matches - .value_of("min-star") - .unwrap_or("4") - .parse::() - .unwrap(), - min_level: matches - .value_of("min-level") - .unwrap_or("0") - .parse::() - .unwrap(), - max_wait_switch_artifact: matches - .value_of("max-wait-switch-artifact") - .unwrap_or("800") - .parse::() - .unwrap(), - scroll_stop: matches - .value_of("scroll-stop") - .unwrap_or("80") - .parse::() - .unwrap(), - number: matches - .value_of("number") - .unwrap_or("0") - .parse::() - .unwrap(), - verbose: matches.is_present("verbose"), - cloud_wait_switch_artifact: matches - .value_of("cloud-wait-switch-artifact") - .unwrap_or("300") - .parse::() - .unwrap(), - // offset_x: matches.value_of("offset-x").unwrap_or("0").parse::().unwrap(), - // offset_y: matches.value_of("offset-y").unwrap_or("0").parse::().unwrap(), - } - } -} - -pub struct YasScanner { - model: CRNNModel, - enigo: Enigo, - - info: ScanInfo, - config: YasScannerConfig, - - row: u32, - col: u32, - - pool: f64, - - initial_color: Color, - - // for scrolls - scrolled_rows: u32, - avg_scroll_one_row: f64, - - avg_switch_time: f64, - scanned_count: u32, - - is_cloud: bool, -} - -enum ScrollResult { - TLE, - // time limit exceeded - Interrupt, - Success, - Skip, -} - -#[derive(Debug)] -pub struct YasScanResult { - name: String, - main_stat_name: String, - main_stat_value: String, - sub_stat_1: String, - sub_stat_2: String, - sub_stat_3: String, - sub_stat_4: String, - level: String, - equip: String, - star: u32, -} - -impl YasScanResult { - pub fn to_internal_artifact(&self) -> Option { - let set_name = ArtifactSetName::from_zh_cn(&self.name)?; - let slot = ArtifactSlot::from_zh_cn(&self.name)?; - let star = self.star; - if !self.level.contains("+") { - return None; - } - let level = self - .level - .chars() - .skip(1) - .collect::() - .parse::() - .ok()?; - let main_stat = ArtifactStat::from_zh_cn_raw( - (self.main_stat_name.clone() + "+" + self.main_stat_value.as_str()).as_str(), - )?; - let sub1 = ArtifactStat::from_zh_cn_raw(&self.sub_stat_1); - let sub2 = ArtifactStat::from_zh_cn_raw(&self.sub_stat_2); - let sub3 = ArtifactStat::from_zh_cn_raw(&self.sub_stat_3); - let sub4 = ArtifactStat::from_zh_cn_raw(&self.sub_stat_4); - - let equip = if self.equip.contains("已装备") { - //let equip_name = self.equip.clone(); - //equip_name.remove_matches("已装备"); - //let equip_name = &self.equip[..self.equip.len()-9]; - let chars = self.equip.chars().collect_vec(); - let chars2 = &chars[..chars.len() - 3]; - let equip_name = chars2.iter().collect::(); - if CHARACTER_NAMES.contains(equip_name.as_str()) { - Some(equip_name) - } else { - None - } - } else { - None - }; - - let art = InternalArtifact { - set_name, - slot, - star, - level, - main_stat, - sub_stat_1: sub1, - sub_stat_2: sub2, - sub_stat_3: sub3, - sub_stat_4: sub4, - equip, - }; - Some(art) - } -} - -fn calc_pool(row: &Vec) -> f32 { - let len = row.len() / 3; - let mut pool: f32 = 0.0; - - for i in 0..len { - pool += row[i * 3] as f32; - } - // pool /= len as f64; - pool -} - -impl YasScanner { - pub fn new(info: ScanInfo, config: YasScannerConfig, is_cloud: bool) -> YasScanner { - let row = info.art_row; - let col = info.art_col; - - YasScanner { - model: CRNNModel::new( - String::from("model_training.onnx"), - String::from("index_2_word.json"), - ), - enigo: Enigo::new(), - info, - config, - - row, - col, - - pool: -1.0, - initial_color: Color::new(), - scrolled_rows: 0, - avg_scroll_one_row: 0.0, - - avg_switch_time: 0.0, - scanned_count: 0, - - is_cloud, - } - } -} - -impl YasScanner { - fn align_row(&mut self) -> bool { - #[cfg(target_os = "macos")] - let (_, ui) = get_pid_and_ui(); - let mut count = 0; - while count < 10 { - let color = self.get_flag_color(); - if color.is_same(&self.initial_color) { - return true; - } - - #[cfg(windows)] - self.enigo.mouse_scroll_y(-1); - #[cfg(any(target_os = "linux"))] - self.enigo.mouse_scroll_y(1); - #[cfg(target_os = "macos")] - { - match ui { - crate::common::UI::Desktop => { - // mac_scroll(&mut self.enigo, 1); - self.enigo.mouse_scroll_y(-1); - utils::sleep(20); - }, - crate::common::UI::Mobile => { - mac_scroll(&mut self.enigo, 1); - }, - } - } - - utils::sleep(self.config.scroll_stop); - count += 1; - } - - false - } - - /* - pub fn panel_down(&mut self) { - let info = &self.info; - let max_scroll = 20; - let mut count = 0; - self.enigo.mouse_move_to( - info.left + info.star_x as i32, - info.top + info.star_y as i32, - ); - let level_color = Color::from(57, 67, 79); - let mut color = capture::get_color( - info.level_position.left as u32, - info.level_position.bottom as u32, - ); - while !level_color.is_same(&color) && count < max_scroll { - #[cfg(windows)] - self.enigo.mouse_scroll_y(5); - #[cfg(target_os = "linux")] - self.enigo.mouse_scroll_y(-1); - - utils::sleep(self.config.scroll_stop); - color = capture::get_color( - info.level_position.left as u32, - info.level_position.bottom as u32, - ); - count += 1; - } - } - */ - - fn capture_panel(&mut self) -> Result { - let _now = SystemTime::now(); - let w = self.info.panel_position.right - self.info.panel_position.left; - let h = self.info.panel_position.bottom - self.info.panel_position.top; - let rect: PixelRect = PixelRect { - left: self.info.left as i32 + self.info.panel_position.left, - top: self.info.top as i32 + self.info.panel_position.top, - width: w, - height: h, - }; - let u8_arr = capture::capture_absolute(&rect)?; - // info!("capture time: {}ms", now.elapsed().unwrap().as_millis()); - Ok(u8_arr) - } - - fn get_art_count(&mut self) -> Result { - let count = self.config.number; - if let 0 = count { - let info = &self.info; - let raw_after_pp = self - .info - .art_count_position - .capture_relative(info.left, info.top, true) - .unwrap(); - let s = self.model.inference_string(&raw_after_pp); - info!("raw count string: {}", s); - if s.starts_with("圣遗物") { - let chars = s.chars().collect::>(); - let count_str = (&chars[4..chars.len() - 5]).iter().collect::(); - let count = match count_str.parse::() { - Ok(v) => v, - Err(_) => { - return Err(String::from("无法识别圣遗物数量")); - }, - }; - return Ok(count); - } - Err(String::from("无法识别圣遗物数量")) - } else { - return Ok(count); - } - } - - fn get_flag_color(&self) -> Color { - let flag_x = self.info.flag_x as i32 + self.info.left; - let flag_y = self.info.flag_y as i32 + self.info.top; - let color = capture::get_color(flag_x as u32, flag_y as u32); - - color - } - - fn get_star(&self) -> u32 { - let color = capture::get_color( - (self.info.star_x as i32 + self.info.left) as u32, - (self.info.star_y as i32 + self.info.top) as u32, - ); - - let color_1 = Color::from(113, 119, 139); - let color_2 = Color::from(42, 143, 114); - let color_3 = Color::from(81, 127, 203); - let color_4 = Color::from(161, 86, 224); - let color_5 = Color::from(188, 105, 50); - - let min_dis: u32 = color_1.dis_2(&color); - let mut star = 1_u32; - if color_2.dis_2(&color) < min_dis { - star = 2; - } - if color_3.dis_2(&color) < min_dis { - star = 3; - } - if color_4.dis_2(&color) < min_dis { - star = 4; - } - if color_5.dis_2(&color) < min_dis { - star = 5; - } - - star - } - - pub fn move_to(&mut self, row: u32, col: u32) { - let info = &self.info; - let left = info.left - + (info.left_margin + (info.art_width + info.art_gap_x) * col + info.art_width / 2) - as i32; - let top = info.top - + (info.top_margin + (info.art_height + info.art_gap_y) * row + info.art_height / 4) - as i32; - self.enigo.mouse_move_to(left as i32, top as i32); - #[cfg(target_os = "macos")] - utils::sleep(20); - } - - fn sample_initial_color(&mut self) { - self.initial_color = self.get_flag_color(); - } - - fn scroll_one_row(&mut self) -> ScrollResult { - #[cfg(target_os = "macos")] - let (_, ui) = get_pid_and_ui(); - let mut state = 0; - let mut count = 0; - let max_scroll = 20; - while count < max_scroll { - if utils::is_rmb_down() { - return ScrollResult::Interrupt; - } - - #[cfg(windows)] - self.enigo.mouse_scroll_y(-5); - #[cfg(any(target_os = "linux"))] - self.enigo.mouse_scroll_y(1); - #[cfg(target_os = "macos")] - { - match ui { - crate::common::UI::Desktop => { - // mac_scroll(&mut self.enigo, 1); - self.enigo.mouse_scroll_y(-1); - utils::sleep(20); - }, - crate::common::UI::Mobile => { - mac_scroll(&mut self.enigo, 1); - }, - } - } - utils::sleep(self.config.scroll_stop); - count += 1; - let color: Color = self.get_flag_color(); - if state == 0 && !color.is_same(&self.initial_color) { - state = 1; - } else if state == 1 && self.initial_color.is_same(&color) { - self.avg_scroll_one_row = (self.avg_scroll_one_row * self.scrolled_rows as f64 - + count as f64) - / (self.scrolled_rows as f64 + 1.0); - info!("avg scroll/row: {}", self.avg_scroll_one_row); - self.scrolled_rows += 1; - return ScrollResult::Success; - } - } - - ScrollResult::TLE - } - - fn scroll_rows(&mut self, count: u32) -> ScrollResult { - #[cfg(target_os = "macos")] - let (_, ui) = get_pid_and_ui(); - if self.scrolled_rows >= 5 { - let scroll = ((self.avg_scroll_one_row * count as f64 - 3.0).round() as u32).max(0); - for _ in 0..scroll { - #[cfg(windows)] - self.enigo.mouse_scroll_y(-1); - #[cfg(target_os = "linux")] - self.enigo.mouse_scroll_y(1); - #[cfg(target_os = "macos")] - { - match ui { - crate::common::UI::Desktop => { - // mac_scroll(&mut self.enigo, 1); - self.enigo.mouse_scroll_y(-1); - utils::sleep(20); - }, - crate::common::UI::Mobile => { - mac_scroll(&mut self.enigo, 1); - }, - } - } - } - utils::sleep(400); - self.align_row(); - return ScrollResult::Skip; - } - - for _ in 0..count { - match self.scroll_one_row() { - ScrollResult::TLE => return ScrollResult::TLE, - ScrollResult::Interrupt => return ScrollResult::Interrupt, - _ => (), - } - } - - ScrollResult::Success - } - - /* - fn start_capture_only(&mut self) { - fs::create_dir("captures"); - let info = &self.info.clone(); - - println!("capture l:{}, t:{}, w:{},h:{}", info.left, info.top, info.width, info.height); - let raw_im_rect = PixelRectBound { - left: info.left as i32, - top: info.top as i32, - right: info.left + info.width as i32, - bottom: info.top + info.height as i32, - }; - - let raw_im = raw_im_rect.capture_relative(info, false).unwrap(); - raw_im.grayscale_to_gray_image().save("captures/raw_im.png"); - println!("Finish capture raw"); - panic!(); - - let count = self.info.art_count_position.capture_relative(info, false).unwrap(); - count.to_gray_image().save("captures/count.png"); - - let convert_rect = |rect: &PixelRectBound| PixelRect { - left: rect.left - info.panel_position.left, - top: rect.top - info.panel_position.top, - width: rect.right - rect.left, - height: rect.bottom - rect.top, - }; - - let panel = self.capture_panel().unwrap(); - let im_title = pre_process(panel.crop_to_raw_img(&convert_rect(&info.title_position))); - if let Some(im) = im_title { - im.to_gray_image().save("captures/title.png").expect("Err"); - } - - let im_main_stat_name = - pre_process(panel.crop_to_raw_img(&convert_rect(&info.main_stat_name_position))); - if let Some(im) = im_main_stat_name { - im.to_gray_image() - .save("captures/main_stat_name.png") - .expect("Err"); - } - - let im_main_stat_value = - pre_process(panel.crop_to_raw_img(&convert_rect(&info.main_stat_value_position))); - if let Some(im) = im_main_stat_value { - im.to_gray_image() - .save("captures/main_stat_value.png") - .expect("Err"); - } - - let im_sub_stat_1 = - pre_process(panel.crop_to_raw_img(&convert_rect(&info.sub_stat1_position))); - if let Some(im) = im_sub_stat_1 { - im.to_gray_image() - .save("captures/sub_stat_1.png") - .expect("Err"); - } - - let im_sub_stat_2 = - pre_process(panel.crop_to_raw_img(&convert_rect(&info.sub_stat2_position))); - if let Some(im) = im_sub_stat_2 { - im.to_gray_image() - .save("captures/sub_stat_2.png") - .expect("Err"); - } - - let im_sub_stat_3 = - pre_process(panel.crop_to_raw_img(&convert_rect(&info.sub_stat3_position))); - if let Some(im) = im_sub_stat_3 { - im.to_gray_image() - .save("captures/sub_stat_3.png") - .expect("Err"); - } - - let im_sub_stat_4 = - pre_process(panel.crop_to_raw_img(&convert_rect(&info.sub_stat4_position))); - if let Some(im) = im_sub_stat_4 { - im.to_gray_image() - .save("captures/sub_stat_4.png") - .expect("Err"); - } - - let im_level = pre_process(panel.crop_to_raw_img(&convert_rect(&info.level_position))); - if let Some(im) = im_level { - im.to_gray_image().save("captures/level.png").expect("Err"); - } - - let im_equip = pre_process(panel.crop_to_raw_img(&convert_rect(&info.equip_position))); - if let Some(im) = im_equip { - im.to_gray_image().save("captures/equip.png").expect("Err"); - } - } - */ - pub fn start(&mut self) -> Vec { - //self.panel_down(); - /* if self.config.capture_only { - self.start_capture_only(); - return Vec::new(); - } */ - - let count = match self.get_art_count() { - Ok(v) => v, - Err(_) => 1500, - }; - - let total_row = (count + self.col - 1) / self.col; - let last_row_col = if count % self.col == 0 { - self.col - } else { - count % self.col - }; - - // println!("检测到圣遗物数量:{},若无误请按回车,否则输入正确的圣遗物数量:", count); - // let mut s: String = String::new(); - // stdin().read_line(&mut s); - // if s.trim() != "" { - // count = s.trim().parse::().unwrap(); - // } - - info!("detected count: {}", count); - info!("total row: {}", total_row); - info!("last column: {}", last_row_col); - - let (tx, rx) = mpsc::channel::>(); - let info_2 = self.info.clone(); - // v bvvmnvbm - let is_verbose = self.config.verbose; - let is_dump_mode = self.config.dump_mode; - let min_level = self.config.min_level; - let handle = thread::spawn(move || { - let mut results: Vec = Vec::new(); - let model = CRNNModel::new( - String::from("model_training.onnx"), - String::from("index_2_word.json"), - ); - let mut error_count = 0; - let mut dup_count = 0; - let mut hash = HashSet::new(); - let mut consecutive_dup_count = 0; - let info = info_2; - - let mut cnt = 0; - if is_dump_mode { - fs::create_dir("dumps").unwrap(); - } - - let convert_rect = |rect: &PixelRectBound| PixelRect { - left: rect.left - info.panel_position.left, - top: rect.top - info.panel_position.top, - width: rect.right - rect.left, - height: rect.bottom - rect.top, - }; - - for i in rx { - let (capture, star) = match i { - Some(v) => v, - None => break, - }; - //info!("raw capture image: width = {}, height = {}", capture.width(), capture.height()); - let _now = SystemTime::now(); - - let model_inference = |pos: &PixelRectBound, - name: &str, - captured_img: &RgbImage, - cnt: i32| - -> String { - let rect = convert_rect(pos); - let raw_img = to_gray(captured_img) - .view( - rect.left as u32, - rect.top as u32, - rect.width as u32, - rect.height as u32, - ) - .to_image(); - //info!("raw_img: width = {}, height = {}", raw_img.width(), raw_img.height()); - - if is_dump_mode { - raw_img - .to_common_grayscale() - .save(format!("dumps/{}_{}.png", name, cnt)) - .expect("Err"); - } - - let processed_img = match pre_process(raw_img) { - Some(im) => im, - None => { - return String::new(); - }, - }; - if is_dump_mode { - processed_img - .to_common_grayscale() - .save(format!("dumps/p_{}_{}.png", name, cnt)) - .expect("Err"); - } - - let inference_result = model.inference_string(&processed_img); - if is_dump_mode { - fs::write(format!("dumps/{}_{}.txt", name, cnt), &inference_result) - .expect("Err"); - } - - inference_result - }; - - let str_title = model_inference(&info.title_position, "title", &capture, cnt); - let str_main_stat_name = model_inference( - &info.main_stat_name_position, - "main_stat_name", - &capture, - cnt, - ); - let str_main_stat_value = model_inference( - &info.main_stat_value_position, - "main_stat_value", - &capture, - cnt, - ); - - let str_sub_stat_1 = - model_inference(&info.sub_stat1_position, "sub_stat_1", &capture, cnt); - let str_sub_stat_2 = - model_inference(&info.sub_stat2_position, "sub_stat_2", &capture, cnt); - let str_sub_stat_3 = - model_inference(&info.sub_stat3_position, "sub_stat_3", &capture, cnt); - let str_sub_stat_4 = - model_inference(&info.sub_stat4_position, "sub_stat_4", &capture, cnt); - - let str_level = model_inference(&info.level_position, "level", &capture, cnt); - let str_equip = model_inference(&info.equip_position, "equip", &capture, cnt); - - cnt += 1; - - // let predict_time = now.elapsed().unwrap().as_millis(); - // println!("predict time: {}ms", predict_time); - - let result = YasScanResult { - name: str_title, - main_stat_name: str_main_stat_name, - main_stat_value: str_main_stat_value, - sub_stat_1: str_sub_stat_1, - sub_stat_2: str_sub_stat_2, - sub_stat_3: str_sub_stat_3, - sub_stat_4: str_sub_stat_4, - level: str_level, - equip: str_equip, - star, - }; - if is_verbose { - info!("{:?}", result); - } - // println!("{:?}", result); - let art = result.to_internal_artifact(); - if let Some(a) = art { - if hash.contains(&a) { - dup_count += 1; - consecutive_dup_count += 1; - warn!("dup artifact detected: {:?}", result); - } else { - consecutive_dup_count = 0; - hash.insert(a.clone()); - results.push(a); - } - } else { - error!("wrong detection: {:?}", result); - error_count += 1; - // println!("error parsing results"); - } - if consecutive_dup_count >= info.art_row { - error!("检测到连续多个重复圣遗物,可能为翻页错误,或者为非背包顶部开始扫描"); - break; - } - } - - info!("error count: {}", error_count); - info!("dup count: {}", dup_count); - - if min_level > 0 { - results - .into_iter() - .filter(|result| result.level >= min_level) - .collect::>() - } else { - results - } - }); - - let mut scanned_row = 0_u32; - let mut scanned_count = 0_u32; - let mut start_row = 0_u32; - self.move_to(0, 0); - #[cfg(target_os = "macos")] - utils::sleep(20); - self.enigo.mouse_click(MouseButton::Left); - utils::sleep(1000); - // self.wait_until_switched(); - self.sample_initial_color(); - - 'outer: while scanned_count < count { - 'row: for row in start_row..self.row { - let c = if scanned_row == total_row - 1 { - last_row_col - } else { - self.col - }; - 'col: for col in 0..c { - // 大于最大数量则退出 - if scanned_count > count { - break 'outer; - } - - // 右键终止 - if utils::is_rmb_down() { - break 'outer; - } - - self.move_to(row, col); - self.enigo.mouse_click(MouseButton::Left); - #[cfg(target_os = "macos")] - utils::sleep(20); - - self.wait_until_switched(); - let capture = self.capture_panel().unwrap(); - let star = self.get_star(); - if star < self.config.min_star { - break 'outer; - } - tx.send(Some((capture, star))).unwrap(); - - scanned_count += 1; - } // end 'col - - scanned_row += 1; - - if scanned_row >= self.config.max_row { - info!("max row reached, quiting..."); - break 'outer; - } - } // end 'row - - let remain = count - scanned_count; - let remain_row = (remain + self.col - 1) / self.col; - let scroll_row = remain_row.min(self.row); - start_row = self.row - scroll_row; - match self.scroll_rows(scroll_row) { - ScrollResult::TLE => { - error!("翻页出现问题"); - break 'outer; - }, - ScrollResult::Interrupt => break 'outer, - _ => (), - } - - utils::sleep(100); - } - - tx.send(None).unwrap(); - - info!("扫描结束,等待识别线程结束,请勿关闭程序"); - let results: Vec = handle.join().unwrap(); - info!("count: {}", results.len()); - results - } - fn wait_until_switched(&mut self) -> bool { - if self.is_cloud { - utils::sleep(self.config.cloud_wait_switch_artifact); - return true; - } - let now = SystemTime::now(); - - let mut consecutive_time = 0; - let mut diff_flag = false; - while now.elapsed().unwrap().as_millis() < self.config.max_wait_switch_artifact as u128 { - // let pool_start = SystemTime::now(); - let rect = PixelRect { - left: self.info.left as i32 + self.info.pool_position.left, - top: self.info.top as i32 + self.info.pool_position.top, - width: self.info.pool_position.right - self.info.pool_position.left, - height: self.info.pool_position.bottom - self.info.pool_position.top, - }; - let im = capture::capture_absolute(&rect).unwrap(); - let pool = calc_pool(im.as_raw()) as f64; - // info!("pool: {}", pool); - // println!("pool time: {}ms", pool_start.elapsed().unwrap().as_millis()); - - if (pool - self.pool).abs() > 0.000001 { - // info!("pool: {}", pool); - // let raw = RawCaptureImage { - // data: im, - // w: rect.width as u32, - // h: rect.height as u32, - // }; - // let raw = raw.to_raw_image(); - // println!("{:?}", &raw.data[..10]); - // raw.save(&format!("captures/{}.png", rand::thread_rng().gen::())); - - self.pool = pool; - diff_flag = true; - consecutive_time = 0; - // info!("avg switch time: {}ms", self.avg_switch_time); - } else { - if diff_flag { - consecutive_time += 1; - if consecutive_time == 1 { - self.avg_switch_time = (self.avg_switch_time * self.scanned_count as f64 - + now.elapsed().unwrap().as_millis() as f64) - / (self.scanned_count as f64 + 1.0); - self.scanned_count += 1; - return true; - } - } - } - } - - false - } -} - -impl YasScanner { - // pub fn start_from_scratch(config: YasScannerConfig) -> Result, String> { - // set_dpi_awareness(); - // let mut is_cloud = false; - // let hwnd = match find_window_local() { - // Ok(v) => {is_cloud = true; v}, - // Err(s) => { - // match find_window_cloud() { - // Ok(v) => v, - // Err(s) => return Err(String::from("未找到原神窗口")) - // } - // } - // }; - // - // show_window_and_set_foreground(hwnd); - // sleep(1000); - // - // let mut rect = match get_client_rect(hwnd) { - // Ok(v) => v, - // Err(_) => return Err(String::from("未能获取窗口大小")) - // }; - // - // let info = match ScanInfo::from_rect(&rect) { - // Ok(v) => v, - // Err(e) => return Err(e) - // }; - // - // let mut scanner = YasScanner::new(info, config, is_cloud); - // let result = scanner.start(); - // - // Ok(result) - // } -} diff --git a/src/scanner/yas_scanner_starrail.rs b/src/scanner/yas_scanner_starrail.rs deleted file mode 100644 index 4d3ce705..00000000 --- a/src/scanner/yas_scanner_starrail.rs +++ /dev/null @@ -1,946 +0,0 @@ -use std::collections::HashSet; -use std::convert::From; -use std::fs; - -use std::sync::mpsc; -use std::thread; -use std::time::SystemTime; - -use clap::ArgMatches; -use enigo::*; -use image::{GenericImageView, RgbImage}; -use log::{error, info, warn}; -use tract_onnx::prelude::tract_itertools::Itertools; - -use crate::artifact::internal_relic::{ - RelicSetName, RelicSlot, RelicStat, InternalRelic, -}; -use crate::capture::{self}; -use crate::common::character_name::CHARACTER_NAMES; -use crate::common::color::Color; -#[cfg(target_os = "macos")] -use crate::common::utils::get_pid_and_ui; -use crate::common::{utils, PixelRect, PixelRectBound}; -use crate::inference::inference::CRNNModel; -use crate::inference::pre_process::{pre_process, to_gray, ImageConvExt}; -use crate::info::info_starrail::ScanInfoStarRail; - -// Playcover only, wine should not need this. -#[cfg(all(target_os = "macos", target_arch = "aarch64"))] -use crate::common::utils::mac_scroll; - -pub struct YasScannerConfig { - max_row: u32, - capture_only: bool, - min_star: u32, - min_level: u32, - max_wait_switch_relic: u32, - scroll_stop: u32, - number: u32, - verbose: bool, - dump_mode: bool, - cloud_wait_switch_relic: u32, - // offset_x: i32, - // offset_y: i32, -} - -impl YasScannerConfig { - pub fn from_match(matches: &ArgMatches) -> YasScannerConfig { - YasScannerConfig { - max_row: matches - .value_of("max-row") - .unwrap_or("1000") - .parse::() - .unwrap(), - capture_only: matches.is_present("capture-only"), - dump_mode: matches.is_present("dump"), - min_star: matches - .value_of("min-star") - .unwrap_or("4") - .parse::() - .unwrap(), - min_level: matches - .value_of("min-level") - .unwrap_or("0") - .parse::() - .unwrap(), - max_wait_switch_relic: matches - .value_of("max-wait-switch-relic") - .unwrap_or("800") - .parse::() - .unwrap(), - scroll_stop: matches - .value_of("scroll-stop") - .unwrap_or("80") - .parse::() - .unwrap(), - number: matches - .value_of("number") - .unwrap_or("0") - .parse::() - .unwrap(), - verbose: matches.is_present("verbose"), - cloud_wait_switch_relic: matches - .value_of("cloud-wait-switch-relic") - .unwrap_or("300") - .parse::() - .unwrap(), - // offset_x: matches.value_of("offset-x").unwrap_or("0").parse::().unwrap(), - // offset_y: matches.value_of("offset-y").unwrap_or("0").parse::().unwrap(), - } - } -} - -pub struct YasScanner { - model: CRNNModel, - enigo: Enigo, - - info: ScanInfoStarRail, - config: YasScannerConfig, - - row: u32, - col: u32, - - pool: f64, - - initial_color: Color, - - // for scrolls - scrolled_rows: u32, - avg_scroll_one_row: f64, - - avg_switch_time: f64, - scanned_count: u32, - - is_cloud: bool, -} - -enum ScrollResult { - TLE, - // time limit exceeded - Interrupt, - Success, - Skip, -} - -#[derive(Debug)] -pub struct YasScanResult { - name: String, - main_stat_name: String, - main_stat_value: String, - sub_stat_1: String, - sub_stat_2: String, - sub_stat_3: String, - sub_stat_4: String, - level: String, - equip: String, - star: u32, -} - -impl YasScanResult { - pub fn to_internal_relic(&self) -> Option { - let set_name = RelicSetName::from_zh_cn(&self.name)?; - let slot = RelicSlot::from_zh_cn(&self.name)?; - let star = self.star; - if !self.level.contains("+") { - return None; - } - let level = self - .level - .chars() - .skip(1) - .collect::() - .parse::() - .ok()?; - let main_stat = RelicStat::from_zh_cn_raw( - (self.main_stat_name.clone() + "+" + self.main_stat_value.as_str()).as_str(), - )?; - let sub1 = RelicStat::from_zh_cn_raw(&self.sub_stat_1); - let sub2 = RelicStat::from_zh_cn_raw(&self.sub_stat_2); - let sub3 = RelicStat::from_zh_cn_raw(&self.sub_stat_3); - let sub4 = RelicStat::from_zh_cn_raw(&self.sub_stat_4); - - let equip = None; -/* let equip = if self.equip.contains("已装备") { - //let equip_name = self.equip.clone(); - //equip_name.remove_matches("已装备"); - //let equip_name = &self.equip[..self.equip.len()-9]; - let chars = self.equip.chars().collect_vec(); - let chars2 = &chars[..chars.len() - 3]; - let equip_name = chars2.iter().collect::(); - if CHARACTER_NAMES.contains(equip_name.as_str()) { - Some(equip_name) - } else { - None - } - } else { - None - }; */ - - let relic = InternalRelic { - set_name, - slot, - star, - level, - main_stat, - sub_stat_1: sub1, - sub_stat_2: sub2, - sub_stat_3: sub3, - sub_stat_4: sub4, - equip, - }; - Some(relic) - } -} - -fn calc_pool(row: &Vec) -> f32 { - let len = row.len() / 3; - let mut pool: f32 = 0.0; - - for i in 0..len { - pool += row[i * 3] as f32; - } - // pool /= len as f64; - pool -} - -impl YasScanner { - pub fn new(info: ScanInfoStarRail, config: YasScannerConfig, is_cloud: bool) -> YasScanner { - let row = info.art_row; - let col = info.art_col; - - YasScanner { - model: CRNNModel::new( - String::from("model_training_starrail.onnx"), - String::from("index_2_word_starrail.json"), - ), - enigo: Enigo::new(), - info, - config, - - row, - col, - - pool: -1.0, - initial_color: Color::new(), - scrolled_rows: 0, - avg_scroll_one_row: 0.0, - - avg_switch_time: 0.0, - scanned_count: 0, - - is_cloud, - } - } -} - -impl YasScanner { - fn align_row(&mut self) -> bool { - #[cfg(target_os = "macos")] - let (_, ui) = get_pid_and_ui(); - let mut count = 0; - while count < 10 { - let color = self.get_flag_color(); - if color.is_same(&self.initial_color) { - return true; - } - - #[cfg(windows)] - self.enigo.mouse_scroll_y(-1); - #[cfg(any(target_os = "linux"))] - self.enigo.mouse_scroll_y(1); - #[cfg(target_os = "macos")] - { - match ui { - crate::common::UI::Desktop => { - // mac_scroll(&mut self.enigo, 1); - self.enigo.mouse_scroll_y(-1); - utils::sleep(20); - }, - crate::common::UI::Mobile => { - mac_scroll(&mut self.enigo, 1); - }, - } - } - - utils::sleep(self.config.scroll_stop); - count += 1; - } - - false - } - - /* - pub fn panel_down(&mut self) { - let info = &self.info; - let max_scroll = 20; - let mut count = 0; - self.enigo.mouse_move_to( - info.left + info.star_x as i32, - info.top + info.star_y as i32, - ); - let level_color = Color::from(57, 67, 79); - let mut color = capture::get_color( - info.level_position.left as u32, - info.level_position.bottom as u32, - ); - while !level_color.is_same(&color) && count < max_scroll { - #[cfg(windows)] - self.enigo.mouse_scroll_y(5); - #[cfg(target_os = "linux")] - self.enigo.mouse_scroll_y(-1); - - utils::sleep(self.config.scroll_stop); - color = capture::get_color( - info.level_position.left as u32, - info.level_position.bottom as u32, - ); - count += 1; - } - } - */ - - fn capture_panel(&mut self) -> Result { - let _now = SystemTime::now(); - let w = self.info.panel_position.right - self.info.panel_position.left; - let h = self.info.panel_position.bottom - self.info.panel_position.top; - let rect: PixelRect = PixelRect { - left: self.info.left as i32 + self.info.panel_position.left, - top: self.info.top as i32 + self.info.panel_position.top, - width: w, - height: h, - }; - let u8_arr = capture::capture_absolute(&rect)?; - // info!("capture time: {}ms", now.elapsed().unwrap().as_millis()); - Ok(u8_arr) - } - - fn get_relic_count(&mut self) -> Result { - let count = self.config.number; - if let 0 = count { - let info = &self.info; - let raw_after_pp = self - .info - .art_count_position - .capture_relative(info.left, info.top, true) - .unwrap(); - let s = self.model.inference_string(&raw_after_pp); - info!("raw count string: {}", s); - if s.starts_with("遗器数量") { - let chars = s.chars().collect::>(); - let count_str = (&chars[4..chars.len() - 5]).iter().collect::(); - let count = match count_str.parse::() { - Ok(v) => v, - Err(_) => { - return Err(String::from("无法识别遗器数量")); - } - }; - return Ok(count); - } - Err(String::from("无法识别遗器数量")) - } else { - return Ok(count); - } - } - - fn get_flag_color(&self) -> Color { - let flag_x = self.info.flag_x as i32 + self.info.left; - let flag_y = self.info.flag_y as i32 + self.info.top; - let color = capture::get_color(flag_x as u32, flag_y as u32); - - color - } - - fn get_star(&self) -> u32 { - let color = capture::get_color( - (self.info.star_x as i32 + self.info.left) as u32, - (self.info.star_y as i32 + self.info.top) as u32, - ); - - let color_1 = Color::from(113, 119, 139); // 未核实 - let color_2 = Color::from(42, 143, 114); // 未核实 - let color_3 = Color::from(81, 127, 203); // 未核实 - let color_4 = Color::from(155, 117, 206); - let color_5 = Color::from(194, 159, 112); - - let min_dis: u32 = color_1.dis_2(&color); - let mut star = 1_u32; - if color_2.dis_2(&color) < min_dis { - star = 2; - } - if color_3.dis_2(&color) < min_dis { - star = 3; - } - if color_4.dis_2(&color) < min_dis { - star = 4; - } - if color_5.dis_2(&color) < min_dis { - star = 5; - } - - star - } - - pub fn move_to(&mut self, row: u32, col: u32) { - let info = &self.info; - let left = info.left - + (info.left_margin + (info.art_width + info.art_gap_x) * col + info.art_width / 2) - as i32; - let top = info.top - + (info.top_margin + (info.art_height + info.art_gap_y) * row + info.art_height / 4) - as i32; - self.enigo.mouse_move_to(left as i32, top as i32); - #[cfg(target_os = "macos")] - utils::sleep(20); - } - - fn sample_initial_color(&mut self) { - self.initial_color = self.get_flag_color(); - } - - fn scroll_one_row(&mut self) -> ScrollResult { - #[cfg(target_os = "macos")] - let (_, ui) = get_pid_and_ui(); - let mut state = 0; - let mut count = 0; - let max_scroll = 25; - while count < max_scroll { - if utils::is_rmb_down() { - return ScrollResult::Interrupt; - } - - #[cfg(windows)] - self.enigo.mouse_scroll_y(-5); - #[cfg(any(target_os = "linux"))] - self.enigo.mouse_scroll_y(1); - #[cfg(target_os = "macos")] - { - match ui { - crate::common::UI::Desktop => { - // mac_scroll(&mut self.enigo, 1); - self.enigo.mouse_scroll_y(-1); - utils::sleep(20); - }, - crate::common::UI::Mobile => { - mac_scroll(&mut self.enigo, 1); - }, - } - } - utils::sleep(self.config.scroll_stop); - count += 1; - let color: Color = self.get_flag_color(); - if state == 0 && !color.is_same(&self.initial_color) { - state = 1; - } else if state == 1 && self.initial_color.is_same(&color) { - self.avg_scroll_one_row = (self.avg_scroll_one_row * self.scrolled_rows as f64 - + count as f64) - / (self.scrolled_rows as f64 + 1.0); - info!("avg scroll/row: {}", self.avg_scroll_one_row); - self.scrolled_rows += 1; - return ScrollResult::Success; - } - } - - ScrollResult::TLE - } - - fn scroll_rows(&mut self, count: u32) -> ScrollResult { - #[cfg(target_os = "macos")] - let (_, ui) = get_pid_and_ui(); - if self.scrolled_rows >= 5 { - let scroll = ((self.avg_scroll_one_row * count as f64 - 3.0).round() as u32).max(0); - for _ in 0..scroll { - #[cfg(windows)] - self.enigo.mouse_scroll_y(-1); - #[cfg(target_os = "linux")] - self.enigo.mouse_scroll_y(1); - #[cfg(target_os = "macos")] - { - match ui { - crate::common::UI::Desktop => { - // mac_scroll(&mut self.enigo, 1); - self.enigo.mouse_scroll_y(-1); - utils::sleep(20); - }, - crate::common::UI::Mobile => { - mac_scroll(&mut self.enigo, 1); - }, - } - } - } - utils::sleep(400); - self.align_row(); - return ScrollResult::Skip; - } - - for _ in 0..count { - match self.scroll_one_row() { - ScrollResult::TLE => return ScrollResult::TLE, - ScrollResult::Interrupt => return ScrollResult::Interrupt, - _ => (), - } - } - - ScrollResult::Success - } - - /* - fn start_capture_only(&mut self) { - fs::create_dir("captures"); - let info = &self.info.clone(); - - println!("capture l:{}, t:{}, w:{},h:{}", info.left, info.top, info.width, info.height); - let raw_im_rect = PixelRectBound { - left: info.left as i32, - top: info.top as i32, - right: info.left + info.width as i32, - bottom: info.top + info.height as i32, - }; - - let raw_im = raw_im_rect.capture_relative(info, false).unwrap(); - raw_im.grayscale_to_gray_image().save("captures/raw_im.png"); - println!("Finish capture raw"); - panic!(); - - let count = self.info.art_count_position.capture_relative(info, false).unwrap(); - count.to_gray_image().save("captures/count.png"); - - let convert_rect = |rect: &PixelRectBound| PixelRect { - left: rect.left - info.panel_position.left, - top: rect.top - info.panel_position.top, - width: rect.right - rect.left, - height: rect.bottom - rect.top, - }; - - let panel = self.capture_panel().unwrap(); - let im_title = pre_process(panel.crop_to_raw_img(&convert_rect(&info.title_position))); - if let Some(im) = im_title { - im.to_gray_image().save("captures/title.png").expect("Err"); - } - - let im_main_stat_name = - pre_process(panel.crop_to_raw_img(&convert_rect(&info.main_stat_name_position))); - if let Some(im) = im_main_stat_name { - im.to_gray_image() - .save("captures/main_stat_name.png") - .expect("Err"); - } - - let im_main_stat_value = - pre_process(panel.crop_to_raw_img(&convert_rect(&info.main_stat_value_position))); - if let Some(im) = im_main_stat_value { - im.to_gray_image() - .save("captures/main_stat_value.png") - .expect("Err"); - } - - let im_sub_stat_1 = - pre_process(panel.crop_to_raw_img(&convert_rect(&info.sub_stat1_position))); - if let Some(im) = im_sub_stat_1 { - im.to_gray_image() - .save("captures/sub_stat_1.png") - .expect("Err"); - } - - let im_sub_stat_2 = - pre_process(panel.crop_to_raw_img(&convert_rect(&info.sub_stat2_position))); - if let Some(im) = im_sub_stat_2 { - im.to_gray_image() - .save("captures/sub_stat_2.png") - .expect("Err"); - } - - let im_sub_stat_3 = - pre_process(panel.crop_to_raw_img(&convert_rect(&info.sub_stat3_position))); - if let Some(im) = im_sub_stat_3 { - im.to_gray_image() - .save("captures/sub_stat_3.png") - .expect("Err"); - } - - let im_sub_stat_4 = - pre_process(panel.crop_to_raw_img(&convert_rect(&info.sub_stat4_position))); - if let Some(im) = im_sub_stat_4 { - im.to_gray_image() - .save("captures/sub_stat_4.png") - .expect("Err"); - } - - let im_level = pre_process(panel.crop_to_raw_img(&convert_rect(&info.level_position))); - if let Some(im) = im_level { - im.to_gray_image().save("captures/level.png").expect("Err"); - } - - let im_equip = pre_process(panel.crop_to_raw_img(&convert_rect(&info.equip_position))); - if let Some(im) = im_equip { - im.to_gray_image().save("captures/equip.png").expect("Err"); - } - } - */ - pub fn start(&mut self) -> Vec { - //self.panel_down(); - /* if self.config.capture_only { - self.start_capture_only(); - return Vec::new(); - } */ - - let count = match self.get_relic_count() { - Ok(v) => v, - Err(_) => 1500, - }; - - let total_row = (count + self.col - 1) / self.col; - let last_row_col = if count % self.col == 0 { - self.col - } else { - count % self.col - }; - - // println!("检测到圣遗物数量:{},若无误请按回车,否则输入正确的圣遗物数量:", count); - // let mut s: String = String::new(); - // stdin().read_line(&mut s); - // if s.trim() != "" { - // count = s.trim().parse::().unwrap(); - // } - - info!("detected count: {}", count); - info!("total row: {}", total_row); - info!("last column: {}", last_row_col); - - let (tx, rx) = mpsc::channel::>(); - let info_2 = self.info.clone(); - // v bvvmnvbm - let is_verbose = self.config.verbose; - let is_dump_mode = self.config.dump_mode; - let min_level = self.config.min_level; - let handle = thread::spawn(move || { - let mut results: Vec = Vec::new(); - let model = CRNNModel::new( - String::from("model_training_starrail.onnx"), - String::from("index_2_word_starrail.json"), - ); - let mut error_count = 0; - let mut dup_count = 0; - let mut hash = HashSet::new(); - let mut consecutive_dup_count = 0; - let info = info_2; - - let mut cnt = 0; - if is_dump_mode { - fs::create_dir("dumps").unwrap(); - } - - let convert_rect = |rect: &PixelRectBound| PixelRect { - left: rect.left - info.panel_position.left, - top: rect.top - info.panel_position.top, - width: rect.right - rect.left, - height: rect.bottom - rect.top, - }; - - for i in rx { - let (capture, star) = match i { - Some(v) => v, - None => break, - }; - //info!("raw capture image: width = {}, height = {}", capture.width(), capture.height()); - let _now = SystemTime::now(); - - let model_inference = |pos: &PixelRectBound, - name: &str, - captured_img: &RgbImage, - cnt: i32| - -> String { - let rect = convert_rect(pos); - let raw_img = to_gray(captured_img) - .view( - rect.left as u32, - rect.top as u32, - rect.width as u32, - rect.height as u32, - ) - .to_image(); - //info!("raw_img: width = {}, height = {}", raw_img.width(), raw_img.height()); - - if is_dump_mode { - raw_img - .to_common_grayscale() - .save(format!("dumps/{}_{}.png", name, cnt)) - .expect("Err"); - } - - let processed_img = match pre_process(raw_img) { - Some(im) => im, - None => { - return String::new(); - }, - }; - if is_dump_mode { - processed_img - .to_common_grayscale() - .save(format!("dumps/p_{}_{}.png", name, cnt)) - .expect("Err"); - } - - let inference_result = model.inference_string(&processed_img); - if is_dump_mode { - fs::write(format!("dumps/{}_{}.txt", name, cnt), &inference_result) - .expect("Err"); - } - - inference_result - }; - - let str_title = model_inference(&info.title_position, "title", &capture, cnt); - let str_main_stat_name = model_inference( - &info.main_stat_name_position, - "main_stat_name", - &capture, - cnt, - ); - let str_main_stat_value = model_inference( - &info.main_stat_value_position, - "main_stat_value", - &capture, - cnt, - ); - - let str_sub_stat_1_name = model_inference( - &info.sub_stat1_name_pos, "sub_stat_1_name", &capture, cnt); - let str_sub_stat_1_value = model_inference( - &info.sub_stat1_value_pos, "sub_stat_1_value", &capture, cnt); - let str_sub_stat_2_name = model_inference( - &info.sub_stat2_name_pos, "sub_stat_2_name", &capture, cnt); - let str_sub_stat_2_value = model_inference( - &info.sub_stat2_value_pos, "sub_stat_2_value", &capture, cnt); - let str_sub_stat_3_name = model_inference( - &info.sub_stat3_name_pos, "sub_stat_3_name", &capture, cnt); - let str_sub_stat_3_value = model_inference( - &info.sub_stat3_value_pos, "sub_stat_3_value", &capture, cnt); - let str_sub_stat_4_name = model_inference( - &info.sub_stat4_name_pos, "sub_stat_4_name", &capture, cnt); - let str_sub_stat_4_value = model_inference( - &info.sub_stat4_value_pos, "sub_stat_4_value", &capture, cnt); - - let str_level = model_inference(&info.level_position, "level", &capture, cnt); - // let str_equip = model_inference(&info.equip_position, "equip", &capture, cnt); - - cnt += 1; - - // let predict_time = now.elapsed().unwrap().as_millis(); - // println!("predict time: {}ms", predict_time); - - let result = YasScanResult { - name: str_title, - main_stat_name: str_main_stat_name, - main_stat_value: str_main_stat_value, - sub_stat_1: str_sub_stat_1_name + "+" + &str_sub_stat_1_value, - sub_stat_2: str_sub_stat_2_name + "+" + &str_sub_stat_2_value, - sub_stat_3: str_sub_stat_3_name + "+" + &str_sub_stat_3_value, - sub_stat_4: str_sub_stat_4_name + "+" + &str_sub_stat_4_value, - level: str_level, - equip: String::new(), - star, - }; - if is_verbose { - info!("{:?}", result); - } - // println!("{:?}", result); - let relic = result.to_internal_relic(); - if let Some(a) = relic { - if hash.contains(&a) { - dup_count += 1; - consecutive_dup_count += 1; - warn!("dup relic detected: {:?}", result); - } else { - consecutive_dup_count = 0; - hash.insert(a.clone()); - results.push(a); - } - } else { - error!("wrong detection: {:?}", result); - error_count += 1; - // println!("error parsing results"); - } - if consecutive_dup_count >= info.art_row { - error!("检测到连续多个重复遗器,可能为翻页错误,或者为非背包顶部开始扫描"); - break; - } - } - - info!("error count: {}", error_count); - info!("dup count: {}", dup_count); - - if min_level > 0 { - results - .into_iter() - .filter(|result| result.level >= min_level) - .collect::>() - } else { - results - } - }); - - let mut scanned_row = 0_u32; - let mut scanned_count = 0_u32; - let mut start_row = 0_u32; - self.move_to(0, 0); - #[cfg(target_os = "macos")] - utils::sleep(20); - self.enigo.mouse_click(MouseButton::Left); - utils::sleep(1000); - // self.wait_until_switched(); - self.sample_initial_color(); - - 'outer: while scanned_count < count { - 'row: for row in start_row..self.row { - let c = if scanned_row == total_row - 1 { - last_row_col - } else { - self.col - }; - 'col: for col in 0..c { - // 大于最大数量则退出 - if scanned_count > count { - break 'outer; - } - - // 右键终止 - if utils::is_rmb_down() { - break 'outer; - } - - self.move_to(row, col); - self.enigo.mouse_click(MouseButton::Left); - #[cfg(target_os = "macos")] - utils::sleep(20); - - self.wait_until_switched(); - let capture = self.capture_panel().unwrap(); - let star = self.get_star(); - if star < self.config.min_star { - break 'outer; - } - tx.send(Some((capture, star))).unwrap(); - - scanned_count += 1; - } // end 'col - - scanned_row += 1; - - if scanned_row >= self.config.max_row { - info!("max row reached, quiting..."); - break 'outer; - } - } // end 'row - - let remain = count - scanned_count; - let remain_row = (remain + self.col - 1) / self.col; - let scroll_row = remain_row.min(self.row); - start_row = self.row - scroll_row; - match self.scroll_rows(scroll_row) { - ScrollResult::TLE => { - error!("翻页出现问题"); - break 'outer; - }, - ScrollResult::Interrupt => break 'outer, - _ => (), - } - - utils::sleep(100); - } - - tx.send(None).unwrap(); - - info!("扫描结束,等待识别线程结束,请勿关闭程序"); - let results: Vec = handle.join().unwrap(); - info!("count: {}", results.len()); - results - } - fn wait_until_switched(&mut self) -> bool { - if self.is_cloud { - utils::sleep(self.config.cloud_wait_switch_relic); - return true; - } - let now = SystemTime::now(); - - let mut consecutive_time = 0; - let mut diff_flag = false; - while now.elapsed().unwrap().as_millis() < self.config.max_wait_switch_relic as u128 { - // let pool_start = SystemTime::now(); - let rect = PixelRect { - left: self.info.left as i32 + self.info.pool_position.left, - top: self.info.top as i32 + self.info.pool_position.top, - width: self.info.pool_position.right - self.info.pool_position.left, - height: self.info.pool_position.bottom - self.info.pool_position.top, - }; - let im = capture::capture_absolute(&rect).unwrap(); - let pool = calc_pool(im.as_raw()) as f64; - // info!("pool: {}", pool); - // println!("pool time: {}ms", pool_start.elapsed().unwrap().as_millis()); - - if (pool - self.pool).abs() > 0.000001 { - // info!("pool: {}", pool); - // let raw = RawCaptureImage { - // data: im, - // w: rect.width as u32, - // h: rect.height as u32, - // }; - // let raw = raw.to_raw_image(); - // println!("{:?}", &raw.data[..10]); - // raw.save(&format!("captures/{}.png", rand::thread_rng().gen::())); - - self.pool = pool; - diff_flag = true; - consecutive_time = 0; - // info!("avg switch time: {}ms", self.avg_switch_time); - } else { - if diff_flag { - consecutive_time += 1; - if consecutive_time == 1 { - self.avg_switch_time = (self.avg_switch_time * self.scanned_count as f64 - + now.elapsed().unwrap().as_millis() as f64) - / (self.scanned_count as f64 + 1.0); - self.scanned_count += 1; - return true; - } - } - } - } - - false - } -} - -impl YasScanner { - // pub fn start_from_scratch(config: YasScannerConfig) -> Result, String> { - // set_dpi_awareness(); - // let mut is_cloud = false; - // let hwnd = match find_window_local() { - // Ok(v) => {is_cloud = true; v}, - // Err(s) => { - // match find_window_cloud() { - // Ok(v) => v, - // Err(s) => return Err(String::from("未找到原神窗口")) - // } - // } - // }; - // - // show_window_and_set_foreground(hwnd); - // sleep(1000); - // - // let mut rect = match get_client_rect(hwnd) { - // Ok(v) => v, - // Err(_) => return Err(String::from("未能获取窗口大小")) - // }; - // - // let info = match ScanInfo::from_rect(&rect) { - // Ok(v) => v, - // Err(e) => return Err(e) - // }; - // - // let mut scanner = YasScanner::new(info, config, is_cloud); - // let result = scanner.start(); - // - // Ok(result) - // } -} diff --git a/yas-derive/Cargo.toml b/yas-derive/Cargo.toml new file mode 100644 index 00000000..5d83c02c --- /dev/null +++ b/yas-derive/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "yas_derive" +version = "0.1.0" +edition = "2021" +description = "Yas proc macro" +repository = "https://github.com/wormtql/yas" +authors = ["wormtql <584130248@qq.com>"] +license = "GPL-2.0-or-later" + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = "1.0" +syn = { version = "2.0", features = ["parsing"] } +quote = "1.0" diff --git a/yas-derive/src/lib.rs b/yas-derive/src/lib.rs new file mode 100644 index 00000000..728acd82 --- /dev/null +++ b/yas-derive/src/lib.rs @@ -0,0 +1,6 @@ +extern crate proc_macro; +mod window_info; +#[proc_macro_derive(YasWindowInfo, attributes(window_info))] +pub fn yas_window_info(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + window_info::yas_window_info(input) +} diff --git a/yas-derive/src/window_info/derive_macro.rs b/yas-derive/src/window_info/derive_macro.rs new file mode 100644 index 00000000..c25ca0cf --- /dev/null +++ b/yas-derive/src/window_info/derive_macro.rs @@ -0,0 +1,53 @@ +use quote::quote; +use syn::{parse_macro_input, DeriveInput}; +use crate::window_info::WindowInfoNestedAttributes; + +pub fn yas_window_info(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = parse_macro_input!(input as DeriveInput); + let struct_name = &input.ident; + + if let syn::Data::Struct(data_struct) = &input.data { + let mut fields = Vec::new(); + for field in data_struct.fields.iter() { + let name = field.ident.as_ref().unwrap(); + + let mut window_info_key: String = name.to_string(); + for attr in field.attrs.iter() { + if attr.path().is_ident("window_info") { + let nested_attributes = WindowInfoNestedAttributes::from_attr(attr).unwrap(); + if nested_attributes.rename.is_some() { + window_info_key = nested_attributes.rename.clone().unwrap().value(); + } + } + } + + fields.push(quote! { + #name: match repo.get_auto_scale(#window_info_key, window_size, ui, platform) { + None => { + return Err(anyhow::anyhow!("cannot find window info key \"{}\"", #window_info_key)); + }, + Some(value) => value + } + }); + } + + let trait_impl = quote! { + impl yas::window_info::FromWindowInfoRepository for #struct_name { + fn from_window_info_repository( + window_size: yas::positioning::Size, + ui: yas::game_info::UI, + platform: yas::game_info::Platform, + repo: &yas::window_info::WindowInfoRepository + ) -> anyhow::Result { + Ok(Self { + #(#fields),* + }) + } + } + }; + + return trait_impl.into(); + } + + proc_macro::TokenStream::new() +} \ No newline at end of file diff --git a/yas-derive/src/window_info/mod.rs b/yas-derive/src/window_info/mod.rs new file mode 100644 index 00000000..6ff94251 --- /dev/null +++ b/yas-derive/src/window_info/mod.rs @@ -0,0 +1,5 @@ +mod nested_attributes; +mod derive_macro; + +pub use derive_macro::yas_window_info; +pub use nested_attributes::WindowInfoNestedAttributes; diff --git a/yas-derive/src/window_info/nested_attributes.rs b/yas-derive/src/window_info/nested_attributes.rs new file mode 100644 index 00000000..25e8aa82 --- /dev/null +++ b/yas-derive/src/window_info/nested_attributes.rs @@ -0,0 +1,35 @@ +use syn::{token, Token}; + +pub struct WindowInfoNestedAttributes { + pub rename: Option, +} + +impl Default for WindowInfoNestedAttributes { + fn default() -> Self { + Self { + rename: None + } + } +} + +impl WindowInfoNestedAttributes { + pub fn from_attr(attr: &syn::Attribute) -> syn::parse::Result { + let mut result: WindowInfoNestedAttributes = Default::default(); + attr.parse_nested_meta( |meta| { + if meta.path.is_ident("rename") { + if meta.input.peek(token::Eq) { + let _eq: Token![=] = meta.input.parse()?; + let expr: syn::LitStr = meta.input.parse()?; + result.rename = Some(expr); + return Ok(()); + } + result.rename = None; + return Ok(()); + } + + Err(meta.error("unrecognized window_info")) + })?; + + Ok(result) + } +} \ No newline at end of file diff --git a/yas-genshin/Cargo.toml b/yas-genshin/Cargo.toml new file mode 100644 index 00000000..577736cb --- /dev/null +++ b/yas-genshin/Cargo.toml @@ -0,0 +1,42 @@ +[package] +name = "yas_scanner_genshin" +version = "0.1.16" +edition = "2021" +build = "build.rs" +description = "Genshin Impact item scanner" +repository = "https://github.com/wormtql/yas" +keywords = ["GenshinImpact", "artifacts", "scanner", "ocr"] +license = "GPL-2.0-or-later" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +yas = { path = "../yas", package="yas_scanner" } +yas_derive = { path = "../yas-derive", package = "yas_derive" } +anyhow = "1.0" +log = "0.4" +clap = { version = "4.4", features = ["derive", "cargo"] } +image = "0.24" +serde_json = "1.0" +edit-distance = "2.1" +regex = "1.5" +strum = "0.25" +strum_macros = "0.25" +lazy_static = "1.4" +serde = { version = "1.0", features = ["derive"] } +env_logger = "0.10.0" +serde_yaml = "0.9" + +[target.'cfg(target_os = "windows")'.dependencies] +windows-capture = "1.0.65" + +[build-dependencies] +winres = "0.1" + +[[bin]] +name = "yas_artifact" +path = "src/bin/artifact_scanner.rs" + +[[bin]] +name = "playground" +path = "src/bin/playground.rs" diff --git a/yas-genshin/build.rs b/yas-genshin/build.rs new file mode 100644 index 00000000..b64054ab --- /dev/null +++ b/yas-genshin/build.rs @@ -0,0 +1,14 @@ +fn main() { + if std::env::var("CARGO_CFG_TARGET_OS").unwrap().as_str() == "windows" { + let mut res = winres::WindowsResource::new(); + // https://github.com/mxre/winres/pull/24 + // https://github.com/mxre/winres/issues/42 + #[cfg(not(target_os = "windows"))] + if std::env::var("CARGO_CFG_TARGET_ENV").unwrap().as_str() == "gnu" { + res.set_ar_path("x86_64-w64-mingw32-ar"); + res.set_windres_path("x86_64-w64-mingw32-windres"); + } + res.set_manifest_file("../assets/manifest.xml"); + res.compile().unwrap(); + } +} diff --git a/yas-genshin/src/application/artifact_scanner.rs b/yas-genshin/src/application/artifact_scanner.rs new file mode 100644 index 00000000..c9ccb2d4 --- /dev/null +++ b/yas-genshin/src/application/artifact_scanner.rs @@ -0,0 +1,104 @@ +use anyhow::{anyhow, Result}; +use clap::{Args, command}; +use log::info; + +use yas::export::{AssetEmitter, ExportAssets}; +use yas::game_info::{GameInfo, GameInfoBuilder}; +use yas::window_info::{load_window_info_repo, WindowInfoRepository}; + +use crate::artifact::GenshinArtifact; +use crate::export::artifact::{ExportArtifactConfig, GenshinArtifactExporter}; +use crate::scanner::{GenshinArtifactScanner, GenshinArtifactScannerConfig}; +use crate::scanner_controller::repository_layout::GenshinRepositoryScannerLogicConfig; + +pub struct ArtifactScannerApplication; + +impl ArtifactScannerApplication { + pub fn new() -> Self { + ArtifactScannerApplication + } + + fn build_command() -> clap::Command { + let mut cmd = command!(); + cmd = ::augment_args_for_update(cmd); + cmd = ::augment_args_for_update(cmd); + cmd = ::augment_args_for_update(cmd); + cmd + } + + fn get_window_info_repository() -> WindowInfoRepository { + load_window_info_repo!( + "../../window_info/windows1600x900.json", + "../../window_info/windows1280x960.json", + "../../window_info/windows1440x900.json", + "../../window_info/windows2100x900.json", + "../../window_info/windows3440x1440.json", + ) + } + + fn init() { + env_logger::Builder::new() + .filter_level(log::LevelFilter::Info) + .init(); + } + + fn get_game_info() -> Result { + let game_info = GameInfoBuilder::new() + .add_local_window_name("原神") + .add_local_window_name("Genshin Impact") + .add_cloud_window_name("云·原神") + .build(); + game_info + } +} + +impl ArtifactScannerApplication { + pub fn run(&self) -> Result<()> { + Self::init(); + let arg_matches = Self::build_command().get_matches(); + let window_info_repository = Self::get_window_info_repository(); + let game_info = Self::get_game_info()?; + + info!("window: {:?}", game_info.window); + info!("ui: {:?}", game_info.ui); + info!("cloud: {}", game_info.is_cloud); + info!("resolution family: {:?}", game_info.resolution_family); + + #[cfg(target_os = "windows")] + { + // assure admin + if !yas::utils::is_admin() { + return Err(anyhow!("请使用管理员运行")); + } + } + + let mut scanner = GenshinArtifactScanner::from_arg_matches( + &window_info_repository, + &arg_matches, + game_info.clone() + )?; + + let result = scanner.scan()?; + let artifacts = result + .iter() + .map(|x| GenshinArtifact::try_from(x)) + .filter(|x| x.is_ok()) + .map(|x| x.unwrap()) + .collect::>(); + + let exporter = GenshinArtifactExporter::new(&arg_matches, &artifacts)?; + let mut export_assets = ExportAssets::new(); + exporter.emit(&mut export_assets); + + let stats = export_assets.save(); + info!("保存结果:"); + let table = format!("{}", stats); + // print multiline + for line in table.lines() { + info!("{}", line); + } + info!("Yas 识别结束,共识别到 {} 件圣遗物。", result.len()); + + Ok(()) + } +} diff --git a/yas-genshin/src/application/mod.rs b/yas-genshin/src/application/mod.rs new file mode 100644 index 00000000..5ed8560e --- /dev/null +++ b/yas-genshin/src/application/mod.rs @@ -0,0 +1,4 @@ +pub use artifact_scanner::ArtifactScannerApplication; + +mod artifact_scanner; + diff --git a/src/artifact/internal_artifact.rs b/yas-genshin/src/artifact/artifact.rs similarity index 84% rename from src/artifact/internal_artifact.rs rename to yas-genshin/src/artifact/artifact.rs index 4641d84c..353b8054 100644 --- a/src/artifact/internal_artifact.rs +++ b/yas-genshin/src/artifact/artifact.rs @@ -1,9 +1,12 @@ -use edit_distance; +use std::hash::{Hash, Hasher}; + use log::error; use regex::Regex; -use std::hash::{Hash, Hasher}; use strum_macros::Display; +use crate::character::CHARACTER_NAMES; +use crate::scanner::GenshinArtifactScanResult; + #[derive(Debug, Hash, Clone, PartialEq, Eq)] pub enum ArtifactStatName { HealingBonus, @@ -87,6 +90,8 @@ pub enum ArtifactSetName { GoldenTroupe, SongOfDaysPast, NighttimeWhispersInTheEchoingWoods, + FragmentOfHarmonicWhimsy, + UnfinishedReverie, } #[derive(Debug, Clone)] @@ -96,11 +101,11 @@ pub struct ArtifactStat { } #[derive(Debug, Hash, Clone, PartialEq, Eq)] -pub struct InternalArtifact { +pub struct GenshinArtifact { pub set_name: ArtifactSetName, pub slot: ArtifactSlot, - pub star: u32, - pub level: u32, + pub star: i32, + pub level: i32, pub main_stat: ArtifactStat, pub sub_stat_1: Option, pub sub_stat_2: Option, @@ -160,12 +165,12 @@ impl ArtifactStatName { impl ArtifactStat { // e.g "生命值+4,123", "暴击率+10%" pub fn from_zh_cn_raw(s: &str) -> Option { - let temp: Vec<&str> = s.split("+").collect(); + let temp: Vec<&str> = s.split('+').collect(); if temp.len() != 2 { return None; } - let is_percentage = temp[1].contains("%"); + let is_percentage = temp[1].contains('%'); let stat_name = match ArtifactStatName::from_zh_cn(temp[0], is_percentage) { Some(v) => v, None => return None, @@ -190,86 +195,55 @@ impl ArtifactStat { } } -pub fn get_real_artifact_name_chs(raw: &str) -> Option { - #[rustfmt::skip] - let all_artifact_chs = [ - "磐陀裂生之花", "嵯峨群峰之翼", "星罗圭壁之晷", "星罗圭璧之晷", "巉岩琢塑之樽", "不动玄石之相", - "历经风雪的思念", "摧冰而行的执望", "冰雪故园的终期", "遍结寒霜的傲骨", "破冰踏雪的回音", - "染血的铁之心", "染血的黑之羽", "骑士染血之时", "染血骑士之杯", "染血的铁假面", - "魔女的炎之花", "魔女常燃之羽", "魔女破灭之时", "魔女的心之火", "焦灼的魔女帽", - "角斗士的留恋", "角斗士的归宿", "角斗士的希冀", "角斗士的酣醉", "角斗士的凯旋", - "饰金胸花", "追忆之风", "坚铜罗盘", "沉波之盏", "酒渍船帽", - "渡火者的决绝", "渡火者的解脱", "渡火者的煎熬", "渡火者的醒悟", "渡火者的智慧", - "远方的少女之心", "少女飘摇的思念", "少女苦短的良辰", "少女片刻的闲暇", "少女易逝的芳颜", - "宗室之花", "宗室之翎", "宗室时计", "宗室银瓮", "宗室面具", - "夏祭之花", "夏祭终末", "夏祭之刻", "夏祭水玉", "夏祭之面", - "平雷之心", "平雷之羽", "平雷之刻", "平雷之器", "平雷之冠", - "雷鸟的怜悯", "雷灾的孑遗", "雷霆的时计", "降雷的凶兆", "唤雷的头冠", - "野花记忆的绿野", "猎人青翠的箭羽", "翠绿猎人的笃定", "翠绿猎人的容器", "翠绿的猎人之冠", - "乐团的晨光", "琴师的箭羽", "终幕的时计", "终末的时计", "吟游者之壶", "指挥的礼帽", - "战狂的蔷薇", "战狂的翎羽", "战狂的时计", "战狂的骨杯", "战狂的鬼面", - "勇士的勋章", "勇士的期许", "勇士的坚毅", "勇士的壮行", "勇士的冠冕", - "守护之花", "守护徽印", "守护座钟", "守护之皿", "守护束带", - "流放者之花", "流放者之羽", "流放者怀表", "流放者之杯", "流放者头冠", - "赌徒的胸花", "赌徒的羽饰", "赌徒的怀表", "赌徒的骰盅", "赌徒的耳环", - "教官的胸花", "教官的羽饰", "教官的怀表", "教官的茶杯", "教官的帽子", - "武人的红花", "武人的羽饰", "武人的水漏", "武人的酒杯", "武人的头巾", - "祭水礼冠", "祭火礼冠", "祭雷礼冠", "祭冰礼冠", - "故人之心", "归乡之羽", "逐光之石", "异国之盏", "感别之冠", - "学士的书签", "学士的羽笔", "学士的时钟", "学士的墨杯", "学士的镜片", - "奇迹之花", "奇迹之羽", "奇迹之沙", "奇迹之杯", "奇迹耳坠", - "冒险家之花", "冒险家尾羽", "冒险家怀表", "冒险家金杯", "冒险家头带", - "幸运儿绿花", "幸运儿鹰羽", "幸运儿沙漏", "幸运儿之杯", "幸运儿银冠", - "游医的银莲", "游医的枭羽", "游医的怀钟", "游医的药壶", "游医的方巾", - "勋绩之花", "昭武翎羽", "金铜时晷", "盟誓金爵", "将帅兜鍪", - "无垢之花", "贤医之羽", "停摆之刻", "超越之盏", "嗤笑之面", - "明威之镡", "切落之羽", "雷云之笼", "绯花之壶", "华饰之兜", - "羁缠之花", "思忆之矢", "朝露之时", "祈望之心", "无常之面", - "荣花之期", "华馆之羽", "众生之谣", "梦醒之瓢", "形骸之笠", - "海染之花", "渊宫之羽", "离别之贝", "真珠之笼", "海祇之冠", - "生灵之华", "阳辔之遗", "潜光片羽", "结契之刻", "虺雷之姿", - "魂香之花", "祝祀之凭", "垂玉之叶", "涌泉之盏", "浮溯之珏", - "迷宫的游人", "翠蔓的智者", "贤智的定期", "迷误者之灯", "月桂的宝冠", - "梦中的铁花", "裁断的翎羽", "沉金的岁月", "如蜜的终宴", "沙王的投影", - "月女的华彩", "谢落的筵席", "凝结的时刻", "守秘的魔瓶", "紫晶的花冠", - "众王之都的开端", "黄金邦国的结末", "失落迷途的机芯", "迷醉长梦的守护", "流沙贵嗣的遗宝", - "恶龙的单片镜", "坏巫师的羽杖", "旅途中的鲜花", "水仙的时时刻刻", "勇者们的茶会", - "灵光明烁之心", "琦色灵彩之羽", "灵光源起之蕊", "久远花落之时", "无边酣乐之筵", - "老兵的容颜", "杰作的序曲", "猎人的胸花", "裁判的时刻", "遗忘的容器", - "黄金剧团的奖赏", "黄金飞鸟的落羽", "黄金乐曲的变奏", "黄金时代的先声", "黄金之夜的喧嚣", - "昔时传奏之诗", "昔时浮想之思", "昔时遗落之誓", "昔时回映之音", "昔时应许之梦", - "慈爱的淑女帽", "诚恳的蘸水笔", "无私的妆饰花", "忠实的砂时计", "慷慨的墨水瓶", - ]; +impl TryFrom<&GenshinArtifactScanResult> for GenshinArtifact { + type Error = (); - let mut min_index = 0; - let mut min_dis = edit_distance::edit_distance(raw, all_artifact_chs[0]); - let mut same_flag = false; - for (i, &val) in all_artifact_chs.iter().enumerate().skip(1) { - let dis = edit_distance::edit_distance(val, raw); - if dis < min_dis { - min_dis = dis; - min_index = i; - same_flag = false; - } else if dis == min_dis { - same_flag = true; - } - } + fn try_from(value: &GenshinArtifactScanResult) -> Result { + let set_name = ArtifactSetName::from_zh_cn(&value.name).ok_or(())?; + let slot = ArtifactSlot::from_zh_cn(&value.name).ok_or(())?; + let star = value.star; + + let main_stat = ArtifactStat::from_zh_cn_raw( + (value.main_stat_name.clone() + "+" + value.main_stat_value.as_str()).as_str(), + ) + .ok_or(())?; + + let sub1 = ArtifactStat::from_zh_cn_raw(&value.sub_stat[0]); + let sub2 = ArtifactStat::from_zh_cn_raw(&value.sub_stat[1]); + let sub3 = ArtifactStat::from_zh_cn_raw(&value.sub_stat[2]); + let sub4 = ArtifactStat::from_zh_cn_raw(&value.sub_stat[3]); + + let equip = if value.equip.ends_with("已装备") { + let chars = value.equip.chars().collect::>(); + let equip_name = chars[..chars.len() - 3].iter().collect::(); + + if CHARACTER_NAMES.contains(equip_name.as_str()) { + Some(equip_name) + } else { + None + } + } else { + None + }; - if same_flag { - None - } else { - Some(String::from(all_artifact_chs[min_index])) + Ok(GenshinArtifact { + set_name, + slot, + star, + level: value.level, + main_stat, + sub_stat_1: sub1, + sub_stat_2: sub2, + sub_stat_3: sub3, + sub_stat_4: sub4, + equip, + }) } } impl ArtifactSetName { #[rustfmt::skip] pub fn from_zh_cn(s: &str) -> Option { - // let s = match get_real_artifact_name_chs(s) { - // Some(v) => v, - // None => return None, - // }; - // println!("name: {}", s); match s { "磐陀裂生之花" => Some(ArtifactSetName::ArchaicPetra), "嵯峨群峰之翼" => Some(ArtifactSetName::ArchaicPetra), @@ -455,6 +429,8 @@ impl ArtifactSetName { "黄金乐曲的变奏" | "黄金飞鸟的落羽" | "黄金时代的先声" | "黄金之夜的喧嚣" | "黄金剧团的奖赏" => Some(ArtifactSetName::GoldenTroupe), "昔时传奏之诗" | "昔时浮想之思" | "昔时遗落之誓" | "昔时回映之音" | "昔时应许之梦" => Some(ArtifactSetName::SongOfDaysPast), "慈爱的淑女帽" | "诚恳的蘸水笔" | "无私的妆饰花" | "忠实的砂时计" | "慷慨的墨水瓶" => Some(ArtifactSetName::NighttimeWhispersInTheEchoingWoods), + "异想零落的圆舞" | "古海玄幽的夜想" | "谐律交响的前奏" | "命途轮转的谐谑" | "灵露倾洒的狂诗" => Some(ArtifactSetName::FragmentOfHarmonicWhimsy), + "失冕的宝冠" | "褪光的翠尾" | "暗结的明花" | "举业的识刻" | "筹谋的共樽" => Some(ArtifactSetName::UnfinishedReverie), _ => None, } } @@ -462,10 +438,6 @@ impl ArtifactSetName { impl ArtifactSlot { pub fn from_zh_cn(s: &str) -> Option { - // let s = match get_real_artifact_name_chs(s) { - // Some(v) => v, - // None => return None, - // }; match s { "磐陀裂生之花" => Some(ArtifactSlot::Flower), "嵯峨群峰之翼" => Some(ArtifactSlot::Feather), @@ -688,17 +660,27 @@ impl ArtifactSlot { "黄金时代的先声" => Some(ArtifactSlot::Sand), "黄金之夜的喧嚣" => Some(ArtifactSlot::Goblet), "黄金剧团的奖赏" => Some(ArtifactSlot::Head), - "昔时传奏之诗" => Some(ArtifactSlot::Head), - "昔时浮想之思" => Some(ArtifactSlot::Feather), "昔时遗落之誓" => Some(ArtifactSlot::Flower), + "昔时浮想之思" => Some(ArtifactSlot::Feather), "昔时回映之音" => Some(ArtifactSlot::Sand), "昔时应许之梦" => Some(ArtifactSlot::Goblet), - "慈爱的淑女帽" => Some(ArtifactSlot::Head), - "诚恳的蘸水笔" => Some(ArtifactSlot::Feather), + "昔时传奏之诗" => Some(ArtifactSlot::Head), "无私的妆饰花" => Some(ArtifactSlot::Flower), + "诚恳的蘸水笔" => Some(ArtifactSlot::Feather), "忠实的砂时计" => Some(ArtifactSlot::Sand), "慷慨的墨水瓶" => Some(ArtifactSlot::Goblet), + "慈爱的淑女帽" => Some(ArtifactSlot::Head), + "异想零落的圆舞" => Some(ArtifactSlot::Head), + "古海玄幽的夜想" => Some(ArtifactSlot::Feather), + "谐律交响的前奏" => Some(ArtifactSlot::Flower), + "命途轮转的谐谑" => Some(ArtifactSlot::Sand), + "灵露倾洒的狂诗" => Some(ArtifactSlot::Goblet), + "失冕的宝冠" => Some(ArtifactSlot::Head), + "褪光的翠尾" => Some(ArtifactSlot::Feather), + "暗结的明花" => Some(ArtifactSlot::Flower), + "举业的识刻" => Some(ArtifactSlot::Sand), + "筹谋的共樽" => Some(ArtifactSlot::Goblet), _ => None, } } -} +} \ No newline at end of file diff --git a/yas-genshin/src/artifact/mod.rs b/yas-genshin/src/artifact/mod.rs new file mode 100644 index 00000000..a3caa679 --- /dev/null +++ b/yas-genshin/src/artifact/mod.rs @@ -0,0 +1,8 @@ +pub use artifact::ArtifactSetName; +pub use artifact::ArtifactSlot; +pub use artifact::ArtifactStat; +pub use artifact::ArtifactStatName; +pub use artifact::GenshinArtifact; + +mod artifact; + diff --git a/yas-genshin/src/bin/artifact_scanner.rs b/yas-genshin/src/bin/artifact_scanner.rs new file mode 100644 index 00000000..5518e093 --- /dev/null +++ b/yas-genshin/src/bin/artifact_scanner.rs @@ -0,0 +1,16 @@ +use std::io::Read; +use log::error; + +use yas_scanner_genshin::application::ArtifactScannerApplication; + +pub fn main() { + let application = ArtifactScannerApplication::new(); + match application.run() { + Err(e) => { + error!("error: {}", e); + // press any key to continue + let _ = std::io::stdin().read(&mut [0u8]).unwrap(); + }, + _ => {} + } +} diff --git a/yas-genshin/src/bin/playground.rs b/yas-genshin/src/bin/playground.rs new file mode 100644 index 00000000..145556d4 --- /dev/null +++ b/yas-genshin/src/bin/playground.rs @@ -0,0 +1,14 @@ +use anyhow::Result; +use image::RgbImage; +use yas::ocr::{ImageToText, PPOCRChV4RecInfer, ppocr_model}; +use image::io::Reader as ImageReader; + +fn main() -> Result<()> { + let model: Box> = Box::new(PPOCRChV4RecInfer::new()?); + let image = ImageReader::open(r"E:\rust\yas\item_count.png")?.decode()?; + let rgb_image = image.to_rgb8(); + let result = model.image_to_text(&rgb_image, false)?; + println!("{}", result); + + Ok(()) +} diff --git a/src/common/character_name.rs b/yas-genshin/src/character/character_names.rs similarity index 99% rename from src/common/character_name.rs rename to yas-genshin/src/character/character_names.rs index 55f3edb0..b8c586db 100644 --- a/src/common/character_name.rs +++ b/yas-genshin/src/character/character_names.rs @@ -78,5 +78,4 @@ lazy_static! { "那维莱特", "娜维娅", ]); - -} +} \ No newline at end of file diff --git a/yas-genshin/src/character/mod.rs b/yas-genshin/src/character/mod.rs new file mode 100644 index 00000000..5f5bc37c --- /dev/null +++ b/yas-genshin/src/character/mod.rs @@ -0,0 +1,4 @@ +pub use character_names::CHARACTER_NAMES; + +mod character_names; + diff --git a/yas-genshin/src/export/artifact/config.rs b/yas-genshin/src/export/artifact/config.rs new file mode 100644 index 00000000..3c1fb116 --- /dev/null +++ b/yas-genshin/src/export/artifact/config.rs @@ -0,0 +1,11 @@ +use crate::export::artifact::GenshinArtifactExportFormat; + +#[derive(clap::Args)] +pub struct ExportArtifactConfig { + #[arg(id = "format", long = "format", short = 'f', default_value_t = GenshinArtifactExportFormat::Mona, help = "输出格式")] + #[arg(value_enum)] + pub format: GenshinArtifactExportFormat, + + #[arg(id = "output-dir", long = "output-dir", short, default_value_t = String::from("."), help = "输出目录")] + pub output_dir: String, +} diff --git a/yas-genshin/src/export/artifact/export_format.rs b/yas-genshin/src/export/artifact/export_format.rs new file mode 100644 index 00000000..52cd4fa0 --- /dev/null +++ b/yas-genshin/src/export/artifact/export_format.rs @@ -0,0 +1,14 @@ +use clap::ValueEnum; + +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)] +pub enum GenshinArtifactExportFormat { + Mona, + MingyuLab, + Good, +} + +impl Default for GenshinArtifactExportFormat { + fn default() -> Self { + Self::Mona + } +} diff --git a/yas-genshin/src/export/artifact/exporter.rs b/yas-genshin/src/export/artifact/exporter.rs new file mode 100644 index 00000000..4ec07f05 --- /dev/null +++ b/yas-genshin/src/export/artifact/exporter.rs @@ -0,0 +1,76 @@ +use std::path::PathBuf; + +use anyhow::Result; +use clap::FromArgMatches; + +use yas::export::{AssetEmitter, ExportAssets}; + +use crate::artifact::GenshinArtifact; +use crate::export::artifact::{ExportArtifactConfig, GenshinArtifactExportFormat}; + +use super::good::GOODFormat; +use super::mingyu_lab::MingyuLabFormat; +use super::mona_uranai::MonaFormat; + +pub struct GenshinArtifactExporter<'a> { + pub format: GenshinArtifactExportFormat, + pub results: Option<&'a [GenshinArtifact]>, + pub output_dir: PathBuf, +} + +impl <'a> GenshinArtifactExporter<'a> { + pub fn new(arg_matches: &clap::ArgMatches, results: &'a [GenshinArtifact]) -> Result { + let config = ExportArtifactConfig::from_arg_matches(arg_matches)?; + Ok(Self { + format: config.format, + results: Some(results), + output_dir: PathBuf::from(&config.output_dir) + }) + } +} + +impl<'a> AssetEmitter for GenshinArtifactExporter<'a> { + fn emit(&self, export_assets: &mut ExportAssets) { + if self.results.is_none() { + return; + } + + let results = self.results.unwrap(); + + match self.format { + GenshinArtifactExportFormat::Mona => { + let path = self.output_dir.join("mona.json"); + let value = MonaFormat::new(results); + let contents = serde_json::to_string(&value).unwrap(); + + export_assets.add_asset( + Some(String::from("artifacts")), + path, + contents.into_bytes(), + Some(String::from("莫娜圣遗物格式"))); + }, + GenshinArtifactExportFormat::MingyuLab => { + let path = self.output_dir.join("mingyulab.json"); + let value = MingyuLabFormat::new(results); + let contents = serde_json::to_string(&value).unwrap(); + + export_assets.add_asset( + Some(String::from("artifacts")), + path, + contents.into_bytes(), + Some(String::from("原魔计算器圣遗物格式"))); + }, + GenshinArtifactExportFormat::Good => { + let path = self.output_dir.join("good.json"); + let value = GOODFormat::new(results); + let contents = serde_json::to_string(&value).unwrap(); + + export_assets.add_asset( + Some(String::from("artifacts")), + path, + contents.into_bytes(), + Some(String::from("GOOD圣遗物格式"))); + }, + }; + } +} diff --git a/src/expo/good.rs b/yas-genshin/src/export/artifact/good.rs similarity index 91% rename from src/expo/good.rs rename to yas-genshin/src/export/artifact/good.rs index 9bcda058..b9139f10 100644 --- a/src/expo/good.rs +++ b/yas-genshin/src/export/artifact/good.rs @@ -1,13 +1,12 @@ -use crate::artifact::internal_artifact::{ - ArtifactSetName, ArtifactSlot, ArtifactStat, ArtifactStatName, InternalArtifact, -}; use serde::ser::{SerializeMap, Serializer}; use serde::Serialize; -use std::fs::File; -use std::io::prelude::*; + +use crate::artifact::{ + ArtifactSetName, ArtifactSlot, ArtifactStat, ArtifactStatName, GenshinArtifact, +}; struct GOODArtifact<'a> { - artifact: &'a InternalArtifact, + artifact: &'a GenshinArtifact, } impl<'a> Serialize for GOODArtifact<'a> { @@ -159,6 +158,8 @@ impl ArtifactSetName { ArtifactSetName::GoldenTroupe => "GoldenTroupe", ArtifactSetName::SongOfDaysPast => "SongOfDaysPast", ArtifactSetName::NighttimeWhispersInTheEchoingWoods => "NighttimeWhispersInTheEchoingWoods", + ArtifactSetName::FragmentOfHarmonicWhimsy => "FragmentOfHarmonicWhimsy", + ArtifactSetName::UnfinishedReverie => "UnfinishedReverie", } } } @@ -172,9 +173,9 @@ pub struct GOODFormat<'a> { } impl<'a> GOODFormat<'a> { - pub fn new(results: &'a Vec) -> GOODFormat { + pub fn new(results: &'a [GenshinArtifact]) -> GOODFormat { let artifacts: Vec> = results - .into_iter() + .iter() .map(|artifact| GOODArtifact { artifact }) .collect(); GOODFormat { @@ -184,16 +185,4 @@ impl<'a> GOODFormat<'a> { artifacts, } } - - pub fn save(&self, path: String) { - let mut file = match File::create(&path) { - Err(why) => panic!("couldn't create {}: {}", path, why), - Ok(file) => file, - }; - let s = serde_json::to_string(&self).unwrap(); - match file.write_all(s.as_bytes()) { - Err(why) => panic!("couldn't write to {}: {}", path, why), - _ => {}, - } - } } diff --git a/src/expo/mingyu_lab.rs b/yas-genshin/src/export/artifact/mingyu_lab.rs similarity index 92% rename from src/expo/mingyu_lab.rs rename to yas-genshin/src/export/artifact/mingyu_lab.rs index 967529a9..a40ed37f 100644 --- a/src/expo/mingyu_lab.rs +++ b/yas-genshin/src/export/artifact/mingyu_lab.rs @@ -1,12 +1,11 @@ -use crate::artifact::internal_artifact::{ - ArtifactSetName, ArtifactSlot, ArtifactStat, ArtifactStatName, InternalArtifact, -}; use serde::ser::{Serialize, SerializeMap, Serializer}; -use std::fs::File; -use std::io::prelude::*; + +use crate::artifact::{ + ArtifactSetName, ArtifactSlot, ArtifactStat, ArtifactStatName, GenshinArtifact, +}; struct MingyuLabArtifact<'a> { - artifact: &'a InternalArtifact, + artifact: &'a GenshinArtifact, } impl<'a> Serialize for MingyuLabArtifact<'a> { @@ -136,6 +135,8 @@ impl ArtifactSetName { ArtifactSetName::GoldenTroupe => "golden_troupe", ArtifactSetName::SongOfDaysPast => "song_of_days_past", ArtifactSetName::NighttimeWhispersInTheEchoingWoods => "nighttime_whispers_in_the_echoing_woods", + ArtifactSetName::FragmentOfHarmonicWhimsy => "fragment_of_harmonic_whimsy", + ArtifactSetName::UnfinishedReverie => "unfinished_reverie", // Not supported by Mingyulab ArtifactSetName::Adventurer => unreachable!(), @@ -150,9 +151,9 @@ pub struct MingyuLabFormat<'a> { } impl<'a> MingyuLabFormat<'a> { - pub fn new(results: &'a Vec) -> MingyuLabFormat { + pub fn new(results: &'a [GenshinArtifact]) -> MingyuLabFormat { let artifacts: Vec> = results - .into_iter() + .iter() .filter(|artifact| { artifact.set_name != ArtifactSetName::Adventurer && artifact.set_name != ArtifactSetName::LuckyDog @@ -162,16 +163,13 @@ impl<'a> MingyuLabFormat<'a> { .collect(); MingyuLabFormat { artifacts } } +} - pub fn save(&self, path: String) { - let mut file = match File::create(&path) { - Err(why) => panic!("couldn't create {}: {}", path, why), - Ok(file) => file, - }; - let s = serde_json::to_string(&self.artifacts).unwrap(); - match file.write_all(s.as_bytes()) { - Err(why) => panic!("couldn't write to {}: {}", path, why), - _ => {}, - } +impl Serialize for MingyuLabFormat<'_> { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.artifacts.serialize(serializer) } } diff --git a/yas-genshin/src/export/artifact/mod.rs b/yas-genshin/src/export/artifact/mod.rs new file mode 100644 index 00000000..22c2f918 --- /dev/null +++ b/yas-genshin/src/export/artifact/mod.rs @@ -0,0 +1,11 @@ +pub use config::ExportArtifactConfig; +pub use export_format::GenshinArtifactExportFormat; +pub use exporter::GenshinArtifactExporter; + +mod good; +mod mingyu_lab; +mod mona_uranai; +mod exporter; +mod export_format; +mod config; + diff --git a/src/expo/mona_uranai.rs b/yas-genshin/src/export/artifact/mona_uranai.rs similarity index 92% rename from src/expo/mona_uranai.rs rename to yas-genshin/src/export/artifact/mona_uranai.rs index 5fcc88fd..c7a7843d 100644 --- a/src/expo/mona_uranai.rs +++ b/yas-genshin/src/export/artifact/mona_uranai.rs @@ -1,14 +1,12 @@ use std::convert::From; -use std::fs::File; -use std::io::prelude::*; use serde::ser::{Serialize, SerializeMap, Serializer}; -use crate::artifact::internal_artifact::{ - ArtifactSetName, ArtifactSlot, ArtifactStat, ArtifactStatName, InternalArtifact, +use crate::artifact::{ + ArtifactSetName, ArtifactSlot, ArtifactStat, ArtifactStatName, GenshinArtifact, }; -type MonaArtifact = InternalArtifact; +type MonaArtifact = GenshinArtifact; impl ArtifactStatName { pub fn to_mona(&self) -> String { @@ -181,7 +179,7 @@ impl<'a> Serialize for MonaFormat<'a> { } impl<'a> MonaFormat<'a> { - pub fn new(results: &Vec) -> MonaFormat { + pub fn new(results: &[GenshinArtifact]) -> MonaFormat { let mut flower: Vec<&MonaArtifact> = Vec::new(); let mut feather: Vec<&MonaArtifact> = Vec::new(); let mut cup: Vec<&MonaArtifact> = Vec::new(); @@ -204,21 +202,7 @@ impl<'a> MonaFormat<'a> { cup, sand, head, - version: String::from("1"), } } - - pub fn save(&self, path: String) { - let mut file = match File::create(&path) { - Err(why) => panic!("couldn't create {}: {}", path, why), - Ok(file) => file, - }; - let s = serde_json::to_string(&self).unwrap(); - - match file.write_all(s.as_bytes()) { - Err(why) => panic!("couldn't write to {}: {}", path, why), - _ => {}, - } - } } diff --git a/yas-genshin/src/export/mod.rs b/yas-genshin/src/export/mod.rs new file mode 100644 index 00000000..3ee85eee --- /dev/null +++ b/yas-genshin/src/export/mod.rs @@ -0,0 +1 @@ +pub mod artifact; diff --git a/yas-genshin/src/lib.rs b/yas-genshin/src/lib.rs new file mode 100644 index 00000000..7e08cb8b --- /dev/null +++ b/yas-genshin/src/lib.rs @@ -0,0 +1,9 @@ +#![feature(coroutines, coroutine_trait)] +#![feature(fn_traits)] + +pub mod scanner_controller; +pub mod export; +pub mod scanner; +pub mod artifact; +pub mod character; +pub mod application; diff --git a/yas-genshin/src/main.rs b/yas-genshin/src/main.rs new file mode 100644 index 00000000..008846cf --- /dev/null +++ b/yas-genshin/src/main.rs @@ -0,0 +1,86 @@ +#[macro_use] +extern crate log; + +use anyhow::Result; +use clap::{Command, FromArgMatches}; + +use yas::export::YasExporter; +use yas::utils; + +fn main() -> Result<()> { + env_logger::Builder::new() + .filter_level(log::LevelFilter::Info) + .init(); + + let mut builder = ArgumentsBuilder::new(); + // setup arguments + ::modify_arguments(&mut builder); + ::modify_arguments(&mut builder); + let cmd = Command::new("yas-genshin-artifact") + .version("0.1.14") // todo + .author("wormtql <584130248@qq.com>") + .about("Genshin Impact Artifact Scanner"); + let cmd = builder.build(cmd); + let matches = cmd.get_matches(); + + // get game info + let game_info = GameInfoBuilder::new() + .add_local_window_name("原神") + .add_local_window_name("Genshin Impact") + .add_cloud_window_name("云·原神") + .build(); + let game_info = match game_info { + Err(e) => { + error!("{}", e); + utils::quit() + }, + Ok(v) => v + }; + info!("{:?}", game_info); + + // setup window info + let window_info = { + let mut window_info_builder = WindowInfoBuilder::new(); + ::require_window_info(&mut window_info_builder); + + let mut window_info_prototypes = WindowInfoPrototypes::new(); + window_info_prototypes.insert(load_window_info!("../window_info/windows16x9.json")); + + let resolution = game_info.window.size(); + let mut wi = window_info_builder.build(&window_info_prototypes, resolution).unwrap(); + wi.add_pos("window_origin_pos", game_info.window.origin()); + + wi + }; + + // setup config + let config = GenshinArtifactScannerConfig::from_arg_matches(&matches).unwrap(); + + // setup scanner + let mut scanner = GenshinArtifactScanner::new(config, &window_info, game_info.clone()); + + // run + #[cfg(target_os = "macos")] + { + info!("初始化完成,请切换到原神窗口,Yas 将在 5s 后开始扫描"); + utils::sleep(5000); + } + let results = scanner.scan()?; + + // export + let mut exporter = GenshinArtifactExporter::from_arg_matches(&matches)?; + let genshin_artifacts = results.iter() + .map(|x| GenshinArtifact::try_from(x)) + .filter(|x| x.is_ok()) + .map(|x| x.unwrap()) + .collect::>(); + exporter.results = Some(&genshin_artifacts); + let mut export_assets = ExportAssets::new(); + exporter.export(&mut export_assets); + let export_stats = export_assets.save(); + info!("{}", export_stats); + + info!("Yas 识别结束,共识别到 {} 件圣遗物。", results.len()); + + Ok(()) +} \ No newline at end of file diff --git a/yas-genshin/src/scanner/artifact_scanner/artifact_scanner.rs b/yas-genshin/src/scanner/artifact_scanner/artifact_scanner.rs new file mode 100644 index 00000000..0a0c3410 --- /dev/null +++ b/yas-genshin/src/scanner/artifact_scanner/artifact_scanner.rs @@ -0,0 +1,242 @@ +use std::{cell::RefCell, ops::{Coroutine, CoroutineState}, pin::Pin, rc::Rc, sync::{mpsc::{self, Sender}}, time::SystemTime}; + +use anyhow::Result; +use clap::FromArgMatches; +use image::RgbImage; +use log::{error, info}; + +use yas::capture::{Capturer, GenericCapturer}; +use yas::game_info::GameInfo; +use yas::ocr::{ImageToText, yas_ocr_model}; +use yas::positioning::Pos; +use yas::window_info::FromWindowInfoRepository; +use yas::window_info::WindowInfoRepository; + +use crate::scanner::artifact_scanner::artifact_scanner_worker::ArtifactScannerWorker; +use crate::scanner::artifact_scanner::message_items::SendItem; +use crate::scanner::artifact_scanner::scan_result::GenshinArtifactScanResult; +use crate::scanner_controller::repository_layout::{ + GenshinRepositoryScanController, + GenshinRepositoryScannerLogicConfig, + ReturnResult as GenshinRepositoryControllerReturnResult +}; + +use super::artifact_scanner_config::GenshinArtifactScannerConfig; +use super::ArtifactScannerWindowInfo; + +fn color_distance(c1: &image::Rgb, c2: &image::Rgb) -> usize { + let x = c1.0[0] as i32 - c2.0[0] as i32; + let y = c1.0[1] as i32 - c2.0[1] as i32; + let z = c1.0[2] as i32 - c2.0[2] as i32; + return (x * x + y * y + z * z) as usize; +} + +pub struct GenshinArtifactScanner { + scanner_config: GenshinArtifactScannerConfig, + window_info: ArtifactScannerWindowInfo, + game_info: GameInfo, + image_to_text: Box + Send>, + controller: Rc>, + capturer: Rc>, +} + +// constructor +impl GenshinArtifactScanner { + fn get_image_to_text() -> Result + Send>> { + let model: Box + Send> = Box::new( + yas_ocr_model!("./models/model_training.onnx", "./models/index_2_word.json")? + ); + Ok(model) + } + + fn get_capturer() -> Result>> { + Ok(Rc::new(GenericCapturer::new()?)) + } + + pub fn new( + window_info_repo: &WindowInfoRepository, + config: GenshinArtifactScannerConfig, + controller_config: GenshinRepositoryScannerLogicConfig, + game_info: GameInfo, + ) -> Result { + Ok(Self { + scanner_config: config, + window_info: ArtifactScannerWindowInfo::from_window_info_repository( + game_info.window.to_rect_usize().size(), + game_info.ui, + game_info.platform, + window_info_repo + )?, + controller: Rc::new(RefCell::new( + GenshinRepositoryScanController::new(window_info_repo, controller_config, game_info.clone())? + )), + game_info, + image_to_text: Self::get_image_to_text()?, + // item count will be set later, once the scan starts + capturer: Self::get_capturer()?, + }) + } + + pub fn from_arg_matches( + window_info_repo: &WindowInfoRepository, + arg_matches: &clap::ArgMatches, + game_info: GameInfo, + ) -> Result { + let window_info = ArtifactScannerWindowInfo::from_window_info_repository( + game_info.window.to_rect_usize().size(), + game_info.ui, + game_info.platform, + window_info_repo + )?; + Ok(GenshinArtifactScanner { + scanner_config: GenshinArtifactScannerConfig::from_arg_matches(arg_matches)?, + window_info, + controller: Rc::new(RefCell::new( + GenshinRepositoryScanController::from_arg_matches(window_info_repo, arg_matches, game_info.clone())? + )), + game_info, + image_to_text: Self::get_image_to_text()?, + capturer: Self::get_capturer()? + }) + } +} + +impl GenshinArtifactScanner { + pub fn capture_panel(&self) -> Result { + self.capturer.capture_relative_to( + self.window_info.panel_rect.to_rect_i32(), + self.game_info.window.origin() + ) + } + + pub fn get_star(&self) -> Result { + let pos: Pos = Pos { + x: self.game_info.window.left + self.window_info.star_pos.x as i32, + y: self.game_info.window.top + self.window_info.star_pos.y as i32, + }; + let color = self.capturer.capture_color(pos)?; + + let match_colors = [ + image::Rgb([113, 119, 139]), + image::Rgb([42, 143, 114]), + image::Rgb([81, 127, 203]), + image::Rgb([161, 86, 224]), + image::Rgb([188, 105, 50]), + ]; + + let mut min_dis: usize = 0xdeadbeef; + let mut ret: usize = 1; + for (i, match_color) in match_colors.iter().enumerate() { + let dis2 = color_distance(&match_color, &color); + if dis2 < min_dis { + min_dis = dis2; + ret = i + 1; + } + } + + anyhow::Ok(ret) + } + + pub fn get_item_count(&self) -> Result { + let count = self.scanner_config.number; + let item_name = "圣遗物"; + + let max_count = 1800; + if count > 0 { + return Ok(max_count.min(count)); + } + + let im = self.capturer.capture_relative_to( + self.window_info.item_count_rect.to_rect_i32(), + self.game_info.window.origin() + )?; + im.save("item_count.png")?; + let s = self.image_to_text.image_to_text(&im, false)?; + + info!("物品信息: {}", s); + + if s.starts_with(item_name) { + let chars = s.chars().collect::>(); + let count_str = chars[4..chars.len() - 5].iter().collect::(); + Ok(match count_str.parse::() { + Ok(v) => (v as i32).min(max_count), + Err(_) => max_count, + }) + } else { + Ok(max_count) + } + } + + pub fn scan(&mut self) -> Result> { + info!("开始扫描,使用鼠标右键中断扫描"); + + let now = SystemTime::now(); + let (tx, rx) = mpsc::channel::>(); + // let token = self.cancellation_token.clone(); + let count = self.get_item_count()?; + let worker = ArtifactScannerWorker::new( + self.window_info.clone(), + self.scanner_config.clone(), + )?; + + let join_handle = worker.run(rx); + info!("Worker created"); + + self.send(&tx, count); + + match tx.send(None) { + Ok(_) => info!("扫描结束,等待识别线程结束,请勿关闭程序"), + Err(_) => info!("扫描结束,识别已完成"), + } + + match join_handle.join() { + Ok(v) => { + info!("识别耗时: {:?}", now.elapsed()?); + Ok(v) + } + Err(_) => Err(anyhow::anyhow!("识别线程出现错误")), + } + } + + fn send(&mut self, tx: &Sender>, count: i32) { + let mut generator = GenshinRepositoryScanController::get_generator(self.controller.clone(), count as usize); + + loop { + let pinned_generator = Pin::new(&mut generator); + match pinned_generator.resume(()) { + CoroutineState::Yielded(_) => { + let image = self.capture_panel().unwrap(); + let star = self.get_star().unwrap(); + + // todo normalize types + if (star as i32) < self.scanner_config.min_star { + info!( + "找到满足最低星级要求 {} 的物品,准备退出……", + self.scanner_config.min_star + ); + break; + } + + if tx.send(Some(SendItem { panel_image: image, star: star })).is_err() { + break; + } + + // scanned_count += 1; + } + CoroutineState::Complete(result) => { + match result { + Err(e) => error!("扫描发生错误:{}", e), + Ok(value) => { + match value { + GenshinRepositoryControllerReturnResult::Interrupted => info!("用户中断"), + GenshinRepositoryControllerReturnResult::Finished => () + } + } + } + + break; + } + } + } + } +} \ No newline at end of file diff --git a/yas-genshin/src/scanner/artifact_scanner/artifact_scanner_config.rs b/yas-genshin/src/scanner/artifact_scanner/artifact_scanner_config.rs new file mode 100644 index 00000000..88638c19 --- /dev/null +++ b/yas-genshin/src/scanner/artifact_scanner/artifact_scanner_config.rs @@ -0,0 +1,22 @@ +#[derive(Clone, clap::Args)] +pub struct GenshinArtifactScannerConfig { + /// Items with stars less than this will be ignored + #[arg(id = "min-star", long = "min-star", help = "最小星级", value_name = "MIN_STAR", default_value_t = 4)] + pub min_star: i32, + + /// Items with level less than this will be ignored + #[arg(id = "min-level", long = "min-level", help = "最小等级", value_name = "MIN_LEVEL", default_value_t = 0)] + pub min_level: i32, + + /// Ignore duplicated items + #[arg(id = "ignore-dup", long = "ignore-dup", help = "忽略重复物品")] + pub ignore_dup: bool, + + /// it will output very verbose messages + #[arg(id = "verbose", long, help = "显示详细信息")] + pub verbose: bool, + + /// the exact amount to scan + #[arg(id = "number", long, help = "指定圣遗物数量", value_name = "NUMBER", default_value_t = -1)] + pub number: i32, +} diff --git a/yas-genshin/src/scanner/artifact_scanner/artifact_scanner_window_info.rs b/yas-genshin/src/scanner/artifact_scanner/artifact_scanner_window_info.rs new file mode 100644 index 00000000..1bf3bf9d --- /dev/null +++ b/yas-genshin/src/scanner/artifact_scanner/artifact_scanner_window_info.rs @@ -0,0 +1,50 @@ +use yas::positioning::{Pos, Rect}; + +#[derive(Clone, yas_derive::YasWindowInfo, Debug)] +pub struct ArtifactScannerWindowInfo { + /// the position of artifact title relative to window + #[window_info(rename = "genshin_artifact_title_rect")] + pub title_rect: Rect, + + /// the main stat name position of artifact relative to window + #[window_info(rename = "genshin_artifact_main_stat_name_rect")] + pub main_stat_name_rect: Rect, + + /// the main stat value position of artifact relative to window + #[window_info(rename = "genshin_artifact_main_stat_value_rect")] + pub main_stat_value_rect: Rect, + + /// the sub stats positions relative to window + #[window_info(rename = "genshin_artifact_sub_stat1_rect")] + pub sub_stat_1: Rect, + #[window_info(rename = "genshin_artifact_sub_stat2_rect")] + pub sub_stat_2: Rect, + #[window_info(rename = "genshin_artifact_sub_stat3_rect")] + pub sub_stat_3: Rect, + #[window_info(rename = "genshin_artifact_sub_stat4_rect")] + pub sub_stat_4: Rect, + + /// the level of the artifact relative to window + #[window_info(rename = "genshin_artifact_level_rect")] + pub level_rect: Rect, + + /// equip status of the artifact relative to window + #[window_info(rename = "genshin_artifact_item_equip_rect")] + pub item_equip_rect: Rect, + + /// the count of artifacts relative to window + #[window_info(rename = "genshin_artifact_item_count_rect")] + pub item_count_rect: Rect, + + /// the sample position of star, relative to window + #[window_info(rename = "genshin_artifact_star_pos")] + pub star_pos: Pos, + + /// the whole panel of the artifact, relative to window + #[window_info(rename = "genshin_repository_panel_rect")] + pub panel_rect: Rect, + + /// how many columns in this layout + #[window_info(rename = "genshin_repository_item_col")] + pub col: i32, +} diff --git a/yas-genshin/src/scanner/artifact_scanner/artifact_scanner_worker.rs b/yas-genshin/src/scanner/artifact_scanner/artifact_scanner_worker.rs new file mode 100644 index 00000000..9e703717 --- /dev/null +++ b/yas-genshin/src/scanner/artifact_scanner/artifact_scanner_worker.rs @@ -0,0 +1,174 @@ +use std::collections::HashSet; +use std::sync::mpsc::Receiver; +use std::thread::JoinHandle; + +use anyhow::Result; +use image::{GenericImageView, RgbImage}; +use log::{error, info, warn}; + +use yas::ocr::ImageToText; +use yas::ocr::yas_ocr_model; +use yas::positioning::{Pos, Rect}; + +use crate::scanner::artifact_scanner::artifact_scanner_window_info::ArtifactScannerWindowInfo; +use crate::scanner::artifact_scanner::GenshinArtifactScannerConfig; +use crate::scanner::artifact_scanner::message_items::SendItem; +use crate::scanner::artifact_scanner::scan_result::GenshinArtifactScanResult; + +fn parse_level(s: &str) -> Result { + let pos = s.find('+'); + + if pos.is_none() { + let level = s.parse::()?; + return anyhow::Ok(level); + } + + let level = s[pos.unwrap()..].parse::()?; + return anyhow::Ok(level); +} + +fn get_image_to_text() -> Result + Send>> { + let model: Box + Send> = Box::new( + yas_ocr_model!("./models/model_training.onnx", "./models/index_2_word.json")? + ); + Ok(model) +} + +/// run in a separate thread, accept captured image and get an artifact +pub struct ArtifactScannerWorker { + model: Box + Send>, + window_info: ArtifactScannerWindowInfo, + config: GenshinArtifactScannerConfig, +} + +impl ArtifactScannerWorker { + pub fn new( + window_info: ArtifactScannerWindowInfo, + config: GenshinArtifactScannerConfig, + ) -> Result { + Ok(ArtifactScannerWorker { + model: get_image_to_text()?, + window_info, + config, + }) + } + + /// the captured_img is a panel of the artifact, the rect is a region of the panel + fn model_inference(&self, rect: Rect, captured_img: &RgbImage) -> Result { + let relative_rect = rect.translate(Pos { + x: -self.window_info.panel_rect.left, + y: -self.window_info.panel_rect.top, + }); + + let raw_img = captured_img.view( + relative_rect.left as u32, relative_rect.top as u32, relative_rect.width as u32, relative_rect.height as u32, + ).to_image(); + + let inference_result = self.model.image_to_text(&raw_img, false); + + inference_result + } + + /// Parse the captured result (of type SendItem) to a scanned artifact + fn scan_item_image(&self, item: SendItem) -> Result { + let image = &item.panel_image; + + let str_title = self.model_inference(self.window_info.title_rect, &image)?; + let str_main_stat_name = self.model_inference(self.window_info.main_stat_name_rect, &image)?; + let str_main_stat_value = self.model_inference(self.window_info.main_stat_value_rect, &image)?; + + let str_sub_stat0 = self.model_inference(self.window_info.sub_stat_1, &image)?; + let str_sub_stat1 = self.model_inference(self.window_info.sub_stat_2, &image)?; + let str_sub_stat2 = self.model_inference(self.window_info.sub_stat_3, &image)?; + let str_sub_stat3 = self.model_inference(self.window_info.sub_stat_4, &image)?; + + let str_level = self.model_inference(self.window_info.level_rect, &image)?; + let str_equip = self.model_inference(self.window_info.item_equip_rect, &image)?; + + anyhow::Ok(GenshinArtifactScanResult { + name: str_title, + main_stat_name: str_main_stat_name, + main_stat_value: str_main_stat_value, + sub_stat: [ + str_sub_stat0, + str_sub_stat1, + str_sub_stat2, + str_sub_stat3, + ], + level: parse_level(&str_level)?, + equip: str_equip, + star: item.star as i32, + }) + } + + pub fn run(self, rx: Receiver>) -> JoinHandle> { + std::thread::spawn(move || { + let mut results = Vec::new(); + let mut hash: HashSet = HashSet::new(); + // if too many artifacts are same in consecutive, then an error has occurred + let mut consecutive_dup_count = 0; + + let is_verbose = self.config.verbose; + let min_level = self.config.min_level; + let info = self.window_info.clone(); + // todo remove dump mode to another scanner + // let dump_mode = false; + // let model = self.model.clone(); + // let panel_origin = Pos { x: self.window_info.panel_rect.left, y: self.window_info.panel_rect.top }; + + for (_cnt, item) in rx.into_iter().enumerate() { + let item = match item { + Some(v) => v, + None => break, + }; + + let result = match self.scan_item_image(item) { + Ok(v) => v, + Err(e) => { + error!("识别错误: {}", e); + continue; + } + }; + + if is_verbose { + info!("{:?}", result); + } + + if result.level < min_level { + info!( + "找到满足最低等级要求 {} 的物品({}),准备退出……", + min_level, result.level + ); + break; + } + + if hash.contains(&result) { + consecutive_dup_count += 1; + warn!("识别到重复物品: {:#?}", result); + } else { + consecutive_dup_count = 0; + hash.insert(result.clone()); + results.push(result); + } + + if consecutive_dup_count >= info.col && !self.config.ignore_dup { + error!("识别到连续多个重复物品,可能为翻页错误,或者为非背包顶部开始扫描"); + // token.cancel(); + break; + } + + // if token.cancelled() { + // error!("扫描任务被取消"); + // break; + // } + } + + info!("识别结束,非重复物品数量: {}", hash.len()); + + // progress_bar.finish(); + // MULTI_PROGRESS.remove(&progress_bar); + + results + }) + } +} \ No newline at end of file diff --git a/yas-genshin/src/scanner/artifact_scanner/message_items.rs b/yas-genshin/src/scanner/artifact_scanner/message_items.rs new file mode 100644 index 00000000..d9ca3c5d --- /dev/null +++ b/yas-genshin/src/scanner/artifact_scanner/message_items.rs @@ -0,0 +1,7 @@ +use image::RgbImage; + +/// this is constructed by the capturing thread, and sent to the worker thread +pub struct SendItem { + pub panel_image: RgbImage, + pub star: usize, +} diff --git a/yas-genshin/src/scanner/artifact_scanner/mod.rs b/yas-genshin/src/scanner/artifact_scanner/mod.rs new file mode 100644 index 00000000..67c9ec57 --- /dev/null +++ b/yas-genshin/src/scanner/artifact_scanner/mod.rs @@ -0,0 +1,11 @@ +pub use artifact_scanner::GenshinArtifactScanner; +pub use artifact_scanner_config::GenshinArtifactScannerConfig; +pub use artifact_scanner_window_info::ArtifactScannerWindowInfo; +pub use scan_result::GenshinArtifactScanResult; + +mod artifact_scanner; +mod artifact_scanner_config; +mod scan_result; +mod artifact_scanner_worker; +mod artifact_scanner_window_info; +mod message_items; diff --git a/yas-genshin/src/scanner/artifact_scanner/models/index_2_word.json b/yas-genshin/src/scanner/artifact_scanner/models/index_2_word.json new file mode 100644 index 00000000..9d33ded3 --- /dev/null +++ b/yas-genshin/src/scanner/artifact_scanner/models/index_2_word.json @@ -0,0 +1,602 @@ +{ + "0": "-", + "1": " ", + "2": "%", + "3": "'", + "4": "+", + "5": ",", + "6": ".", + "7": "/", + "8": "0", + "9": "1", + "10": "2", + "11": "3", + "12": "4", + "13": "5", + "14": "6", + "15": "7", + "16": "8", + "17": "9", + "18": "一", + "19": "七", + "20": "万", + "21": "不", + "22": "业", + "23": "丝", + "24": "中", + "25": "丽", + "26": "举", + "27": "久", + "28": "之", + "29": "乐", + "30": "九", + "31": "乡", + "32": "书", + "33": "云", + "34": "五", + "35": "亚", + "36": "交", + "37": "人", + "38": "仙", + "39": "代", + "40": "们", + "41": "伊", + "42": "众", + "43": "优", + "44": "会", + "45": "传", + "46": "伤", + "47": "作", + "48": "依", + "49": "值", + "50": "倾", + "51": "假", + "52": "停", + "53": "傲", + "54": "儿", + "55": "元", + "56": "充", + "57": "兆", + "58": "先", + "59": "光", + "60": "克", + "61": "兜", + "62": "八", + "63": "兰", + "64": "共", + "65": "兵", + "66": "具", + "67": "冀", + "68": "冒", + "69": "冕", + "70": "军", + "71": "冠", + "72": "冰", + "73": "决", + "74": "凝", + "75": "凭", + "76": "凯", + "77": "凶", + "78": "击", + "79": "切", + "80": "判", + "81": "利", + "82": "别", + "83": "刻", + "84": "前", + "85": "剧", + "86": "力", + "87": "加", + "88": "动", + "89": "勇", + "90": "勋", + "91": "北", + "92": "医", + "93": "千", + "94": "华", + "95": "单", + "96": "卡", + "97": "卢", + "98": "印", + "99": "历", + "100": "原", + "101": "变", + "102": "古", + "103": "可", + "104": "叶", + "105": "吟", + "106": "命", + "107": "响", + "108": "唤", + "109": "喧", + "110": "嗣", + "111": "嗤", + "112": "嘉", + "113": "器", + "114": "嚣", + "115": "回", + "116": "团", + "117": "园", + "118": "国", + "119": "圆", + "120": "圣", + "121": "圭", + "122": "坎", + "123": "坏", + "124": "坚", + "125": "坠", + "126": "垂", + "127": "垢", + "128": "埃", + "129": "堇", + "130": "塑", + "131": "墨", + "132": "士", + "133": "壮", + "134": "声", + "135": "壶", + "136": "备", + "137": "夏", + "138": "多", + "139": "夜", + "140": "失", + "141": "头", + "142": "奇", + "143": "奏", + "144": "契", + "145": "奖", + "146": "奥", + "147": "女", + "148": "如", + "149": "妆", + "150": "妮", + "151": "妲", + "152": "姿", + "153": "威", + "154": "娅", + "155": "娜", + "156": "子", + "157": "孑", + "158": "学", + "159": "宁", + "160": "守", + "161": "安", + "162": "宗", + "163": "官", + "164": "定", + "165": "宝", + "166": "实", + "167": "室", + "168": "宫", + "169": "害", + "170": "宴", + "171": "宵", + "172": "家", + "173": "容", + "174": "宿", + "175": "寒", + "176": "将", + "177": "少", + "178": "尔", + "179": "尼", + "180": "尾", + "181": "岁", + "182": "岐", + "183": "岩", + "184": "峨", + "185": "峰", + "186": "嵯", + "187": "巉", + "188": "巫", + "189": "已", + "190": "巾", + "191": "帅", + "192": "师", + "193": "希", + "194": "带", + "195": "席", + "196": "常", + "197": "帽", + "198": "幕", + "199": "平", + "200": "幸", + "201": "幽", + "202": "序", + "203": "应", + "204": "座", + "205": "开", + "206": "异", + "207": "归", + "208": "形", + "209": "彩", + "210": "影", + "211": "律", + "212": "徒", + "213": "御", + "214": "徽", + "215": "心", + "216": "忆", + "217": "忍", + "218": "忘", + "219": "忠", + "220": "念", + "221": "怀", + "222": "怜", + "223": "思", + "224": "恋", + "225": "恳", + "226": "恶", + "227": "悟", + "228": "悯", + "229": "想", + "230": "感", + "231": "慈", + "232": "慧", + "233": "慨", + "234": "慷", + "235": "成", + "236": "战", + "237": "托", + "238": "执", + "239": "投", + "240": "护", + "241": "拉", + "242": "指", + "243": "挥", + "244": "提", + "245": "摆", + "246": "摇", + "247": "摧", + "248": "攻", + "249": "放", + "250": "故", + "251": "效", + "252": "教", + "253": "斗", + "254": "断", + "255": "斯", + "256": "方", + "257": "旅", + "258": "旋", + "259": "无", + "260": "早", + "261": "时", + "262": "明", + "263": "易", + "264": "昔", + "265": "星", + "266": "映", + "267": "昭", + "268": "晨", + "269": "晴", + "270": "晶", + "271": "晷", + "272": "智", + "273": "暇", + "274": "暗", + "275": "暴", + "276": "曲", + "277": "月", + "278": "望", + "279": "朝", + "280": "期", + "281": "末", + "282": "术", + "283": "机", + "284": "杖", + "285": "束", + "286": "条", + "287": "杯", + "288": "杰", + "289": "林", + "290": "枫", + "291": "枭", + "292": "柏", + "293": "染", + "294": "柚", + "295": "柯", + "296": "桂", + "297": "桃", + "298": "梦", + "299": "森", + "300": "樽", + "301": "欧", + "302": "武", + "303": "毅", + "304": "水", + "305": "沃", + "306": "沉", + "307": "沙", + "308": "治", + "309": "泉", + "310": "波", + "311": "泷", + "312": "泽", + "313": "洒", + "314": "洛", + "315": "流", + "316": "浪", + "317": "浮", + "318": "海", + "319": "涌", + "320": "淑", + "321": "渊", + "322": "渍", + "323": "渡", + "324": "温", + "325": "游", + "326": "源", + "327": "溯", + "328": "漏", + "329": "潜", + "330": "火", + "331": "灭", + "332": "灯", + "333": "灵", + "334": "灼", + "335": "灾", + "336": "炎", + "337": "烁", + "338": "烟", + "339": "焦", + "340": "焱", + "341": "煎", + "342": "熬", + "343": "燃", + "344": "爱", + "345": "爵", + "346": "片", + "347": "物", + "348": "特", + "349": "狂", + "350": "猎", + "351": "玄", + "352": "率", + "353": "玉", + "354": "王", + "355": "环", + "356": "珊", + "357": "珏", + "358": "珐", + "359": "珠", + "360": "班", + "361": "理", + "362": "琢", + "363": "琦", + "364": "琳", + "365": "琴", + "366": "瑚", + "367": "瑶", + "368": "璧", + "369": "瓢", + "370": "瓮", + "371": "瓶", + "372": "甘", + "373": "生", + "374": "申", + "375": "电", + "376": "留", + "377": "疗", + "378": "白", + "379": "的", + "380": "皿", + "381": "盅", + "382": "盏", + "383": "盘", + "384": "盟", + "385": "相", + "386": "真", + "387": "矢", + "388": "短", + "389": "石", + "390": "砂", + "391": "破", + "392": "磐", + "393": "礼", + "394": "祀", + "395": "祇", + "396": "祈", + "397": "祝", + "398": "神", + "399": "祭", + "400": "离", + "401": "私", + "402": "秋", + "403": "秘", + "404": "章", + "405": "端", + "406": "笃", + "407": "笑", + "408": "笔", + "409": "笠", + "410": "笼", + "411": "筵", + "412": "筹", + "413": "签", + "414": "箭", + "415": "米", + "416": "精", + "417": "糖", + "418": "素", + "419": "紫", + "420": "红", + "421": "纳", + "422": "织", + "423": "终", + "424": "经", + "425": "结", + "426": "绝", + "427": "绩", + "428": "绫", + "429": "绯", + "430": "维", + "431": "绿", + "432": "缠", + "433": "罗", + "434": "羁", + "435": "群", + "436": "羽", + "437": "翎", + "438": "翠", + "439": "翼", + "440": "老", + "441": "者", + "442": "而", + "443": "耳", + "444": "胡", + "445": "胸", + "446": "能", + "447": "脱", + "448": "舞", + "449": "船", + "450": "良", + "451": "色", + "452": "艾", + "453": "芙", + "454": "芭", + "455": "芯", + "456": "花", + "457": "芳", + "458": "苦", + "459": "茶", + "460": "草", + "461": "荒", + "462": "荣", + "463": "药", + "464": "莉", + "465": "莎", + "466": "莫", + "467": "莱", + "468": "莲", + "469": "菈", + "470": "菱", + "471": "菲", + "472": "落", + "473": "蒂", + "474": "蔓", + "475": "蔷", + "476": "蕊", + "477": "蕾", + "478": "薇", + "479": "藏", + "480": "蘸", + "481": "虺", + "482": "蜜", + "483": "血", + "484": "行", + "485": "表", + "486": "裁", + "487": "裂", + "488": "装", + "489": "裟", + "490": "褪", + "491": "西", + "492": "角", + "493": "解", + "494": "誓", + "495": "计", + "496": "记", + "497": "许", + "498": "识", + "499": "诗", + "500": "诚", + "501": "误", + "502": "诺", + "503": "谋", + "504": "谐", + "505": "谑", + "506": "谢", + "507": "谣", + "508": "贝", + "509": "贤", + "510": "贵", + "511": "赌", + "512": "赏", + "513": "赛", + "514": "起", + "515": "超", + "516": "越", + "517": "踏", + "518": "转", + "519": "轮", + "520": "辔", + "521": "辛", + "522": "辰", + "523": "边", + "524": "达", + "525": "运", + "526": "远", + "527": "迪", + "528": "迷", + "529": "迹", + "530": "追", + "531": "逐", + "532": "途", + "533": "通", + "534": "逝", + "535": "遍", + "536": "遗", + "537": "那", + "538": "邦", + "539": "郎", + "540": "都", + "541": "酒", + "542": "酣", + "543": "醉", + "544": "醒", + "545": "里", + "546": "重", + "547": "野", + "548": "金", + "549": "鍪", + "550": "钟", + "551": "铁", + "552": "铜", + "553": "银", + "554": "镜", + "555": "镡", + "556": "长", + "557": "闲", + "558": "防", + "559": "阳", + "560": "阿", + "561": "陀", + "562": "降", + "563": "院", + "564": "险", + "565": "雅", + "566": "雨", + "567": "雪", + "568": "零", + "569": "雷", + "570": "霆", + "571": "霜", + "572": "露", + "573": "青", + "574": "面", + "575": "音", + "576": "颜", + "577": "风", + "578": "飘", + "579": "飞", + "580": "饰", + "581": "馆", + "582": "香", + "583": "马", + "584": "骑", + "585": "骨", + "586": "骰", + "587": "骸", + "588": "鬼", + "589": "魂", + "590": "魈", + "591": "魔", + "592": "鲜", + "593": "鸟", + "594": "鹤", + "595": "鹰", + "596": "鹿", + "597": "黄", + "598": "黑", + "599": "龙" +} \ No newline at end of file diff --git a/yas-genshin/src/scanner/artifact_scanner/models/model_training.onnx b/yas-genshin/src/scanner/artifact_scanner/models/model_training.onnx new file mode 100644 index 00000000..9fdf2501 --- /dev/null +++ b/yas-genshin/src/scanner/artifact_scanner/models/model_training.onnx @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:58fb8084a9b33bc190c1fbe0a2bdb1050a102e302ebc934fc1fda6a873937e5d +size 4719959 diff --git a/yas-genshin/src/scanner/artifact_scanner/scan_result.rs b/yas-genshin/src/scanner/artifact_scanner/scan_result.rs new file mode 100644 index 00000000..5362e112 --- /dev/null +++ b/yas-genshin/src/scanner/artifact_scanner/scan_result.rs @@ -0,0 +1,10 @@ +#[derive(Debug, Hash, Clone, PartialEq, Eq)] +pub struct GenshinArtifactScanResult { + pub name: String, + pub main_stat_name: String, + pub main_stat_value: String, + pub sub_stat: [String; 4], + pub equip: String, + pub level: i32, + pub star: i32, +} diff --git a/yas-genshin/src/scanner/item_scanner/item_scanner.rs b/yas-genshin/src/scanner/item_scanner/item_scanner.rs new file mode 100644 index 00000000..12284704 --- /dev/null +++ b/yas-genshin/src/scanner/item_scanner/item_scanner.rs @@ -0,0 +1,365 @@ +use std::{cell::RefCell, collections::HashSet, ops::{Generator, GeneratorState}, pin::Pin, rc::Rc, sync::mpsc::{self, Receiver, Sender}, thread::JoinHandle, time::SystemTime}; + +use anyhow::Result; +use image::{GenericImageView, RgbImage}; +use log::{error, info, warn}; +use yas::{capture::capture::{self, RelativeCapturable}, common::{color::Color, positioning::{Pos, Rect}}, game_info::GameInfo, inference::{model::OCRModel, pre_process::{pre_process, to_gray}}, window_info::{require_window_info::RequireWindowInfo, window_info_repository::WindowInfoRepository}}; + +use crate::scanner_controller::repository_layout::controller::{GenshinRepositoryScanController, ReturnResult}; + +use super::item_scanner_config::ItemScannerConfig; + +#[derive(Debug, Hash, Clone, PartialEq, Eq)] +pub struct ItemScanResult { + pub name: String, + pub amount: i32 +} + +struct ItemScannerWorker { + model: OCRModel, + window_info: ItemScannerWindowInfo, + config: ItemScannerConfig, +} + +impl ItemScannerWorker { + pub fn new( + model: OCRModel, + window_info: ItemScannerWindowInfo, + config: ItemScannerConfig, + ) -> Self { + ItemScannerWorker { + model, + window_info, + config, + } + } + + fn model_inference(&self, pos: Rect, captured_img: &RgbImage) -> Result { + // todo move dump mode into a scanner + // if dump_mode { + // captured_img.save(Path::new("dumps").join(format!("{}_{}.rgb.png", name, cnt)))?; + // } + + let relative_rect = pos.translate(Pos { + x: -self.window_info.panel_pos.left, + y: -self.window_info.panel_pos.top, + }); + + let raw_img = captured_img.view( + relative_rect.left as u32, relative_rect.top as u32, relative_rect.width as u32, relative_rect.height as u32 + ).to_image(); + let raw_img_grayed = to_gray(&raw_img); + + let (processed_img, process_flag) = pre_process(raw_img_grayed); + if !process_flag { + return Ok(String::new()); + } + + let inference_result = self.model.inference_string(&processed_img)?; + + Ok(inference_result) + } + + fn scan_item_image(&self, item: SendItem) -> Result { + let image = &item.panel_image; + + let str_title = self.model_inference(self.window_info.title_pos, &image)?; + + anyhow::Ok(ItemScanResult { + name: str_title, + amount: 0, + }) + } + + pub fn run(self, rx: Receiver>) -> JoinHandle> { + std::thread::spawn(move || { + let mut results = Vec::new(); + let mut hash = HashSet::new(); + let mut consecutive_dup_count = 0; + + let is_verbose = self.config.verbose; + let info = self.window_info.clone(); + + for (_cnt, item) in rx.into_iter().enumerate() { + let item = match item { + Some(v) => v, + None => break, + }; + + let result = match self.scan_item_image(item) { + Ok(v) => v, + Err(e) => { + error!("识别错误: {}", e); + continue; + }, + }; + + if is_verbose { + info!("{:?}", result); + } + + if hash.contains(&result) { + consecutive_dup_count += 1; + warn!("识别到重复物品: {:#?}", result); + } else { + consecutive_dup_count = 0; + hash.insert(result.clone()); + results.push(result); + } + + if consecutive_dup_count >= info.col { + error!("识别到连续多个重复物品,可能为翻页错误,或者为非背包顶部开始扫描"); + // token.cancel(); + break; + } + } + + info!("识别结束,非重复物品数量: {}", hash.len()); + + results + }) + } +} + +#[derive(Clone)] +struct ItemScannerWindowInfo { + // window origin + pub origin: Pos, + + pub title_pos: Rect, + + pub star: Pos, + + pub panel_pos: Rect, + + pub col: i32, +} + +impl From<&WindowInfoRepository> for ItemScannerWindowInfo { + fn from(value: &WindowInfoRepository) -> Self { + ItemScannerWindowInfo { + origin: value.get("window_origin").unwrap(), + title_pos: value.get("genshin_artifact_title_pos").unwrap(), + star: value.get("genshin_artifact_star").unwrap(), + + panel_pos: value.get("genshin_repository_panel_pos").unwrap(), + col: value.get("genshin_repository_item_col").unwrap(), + } + } +} + +pub struct GenshinItemScanner { + scanner_config: ItemScannerConfig, + + window_info: ItemScannerWindowInfo, + window_info_clone: WindowInfoRepository, + + game_info: GameInfo, +} + +impl RequireWindowInfo for GenshinItemScanner { + fn require_window_info(window_info_builder: &mut yas::window_info::window_info_builder::WindowInfoBuilder) { + ::require_window_info(window_info_builder); + + // window_info_builder.add_required_key("window_origin"); + window_info_builder.add_required_key("genshin_artifact_title_pos"); + window_info_builder.add_required_key("genshin_artifact_main_stat_name_pos"); + window_info_builder.add_required_key("genshin_artifact_main_stat_value_pos"); + window_info_builder.add_required_key("genshin_artifact_level_pos"); + window_info_builder.add_required_key("genshin_artifact_item_equip_pos"); + window_info_builder.add_required_key("genshin_artifact_item_count_pos"); + window_info_builder.add_required_key("genshin_artifact_star"); + window_info_builder.add_required_key("genshin_repository_item_col"); + window_info_builder.add_required_key("genshin_repository_panel_pos"); + window_info_builder.add_required_key("genshin_artifact_sub_stat0"); + window_info_builder.add_required_key("genshin_artifact_sub_stat1"); + window_info_builder.add_required_key("genshin_artifact_sub_stat2"); + window_info_builder.add_required_key("genshin_artifact_sub_stat3"); + } +} + +struct SendItem { + panel_image: RgbImage, + star: usize, +} + +// constructor +impl GenshinItemScanner { + pub fn new(config: ItemScannerConfig, window_info: &WindowInfoRepository, game_info: GameInfo) -> Self { + GenshinItemScanner { + scanner_config: config, + window_info: ItemScannerWindowInfo::from(window_info), + window_info_clone: window_info.clone(), + game_info + } + } +} + +impl GenshinItemScanner { + pub fn get_star(&self) -> Result { + let pos = self.window_info.origin + self.window_info.star; + let color = capture::get_color(pos)?; + + let match_colors = [ + Color::new(113, 119, 139), + Color::new(42, 143, 114), + Color::new(81, 127, 203), + Color::new(161, 86, 224), + Color::new(188, 105, 50), + ]; + + let mut min_dis: u32 = 0xdeadbeef; + let mut ret: usize = 1; + for (i, match_color) in match_colors.iter().enumerate() { + let dis = match_color.distance(&color); + if dis < min_dis { + min_dis = dis; + ret = i + 1; + } + } + + anyhow::Ok(ret) + } + + pub fn get_item_count(&self, ocr_model: &OCRModel) -> Result { + let count = self.scanner_config.number; + let item_name = "圣遗物"; + + let max_count = 1800; + if count > 0 { + return Ok(max_count.min(count)); + } + + let im = match self.window_info.item_count_pos + .capture_relative(self.window_info.origin) + { + Ok(im) => im, + Err(e) => { + error!("Error when capturing item count: {}", e); + return Ok(max_count); + }, + }; + + // todo use better preprocess function set + let im_grayed = to_gray(&im); + let (im_preprocessed, preprocess_flag) = pre_process(im_grayed); + assert!(preprocess_flag); + + let s = match ocr_model.inference_string(&im_preprocessed) { + Ok(s) => s, + Err(e) => { + error!("Error when inferring item count: {}", e); + return Ok(max_count); + }, + }; + + info!("物品信息: {}", s); + + if s.starts_with(item_name) { + let chars = s.chars().collect::>(); + let count_str = chars[4..chars.len() - 5].iter().collect::(); + Ok(match count_str.parse::() { + Ok(v) => (v as i32).min(max_count), + Err(_) => max_count, + }) + } else { + Ok(max_count) + } + } + + pub fn scan(&mut self) -> Result> { + info!("开始扫描,使用鼠标右键中断扫描"); + + let now = SystemTime::now(); + + let (tx, rx) = mpsc::channel::>(); + // let token = self.cancellation_token.clone(); + + let model = { + let model_bytes = include_bytes!("./models/model_training.onnx"); + let index_to_world = include_str!("./models/index_2_word.json"); + + OCRModel::new( + model_bytes, index_to_world + ) + }?; + let count = self.get_item_count(&model)?; + + let worker = ArtifactScannerWorker::new( + model, + self.window_info.clone(), + self.scanner_config.clone() + ); + + let join_handle = worker.run(rx); + + // let worker = self.worker(rx, count, token); + + self.send(&tx, count); + + match tx.send(None) { + Ok(_) => info!("扫描结束,等待识别线程结束,请勿关闭程序"), + Err(_) => info!("扫描结束,识别已完成"), + } + + match join_handle.join() { + Ok(v) => { + info!("识别耗时: {:?}", now.elapsed()?); + Ok(v) + }, + Err(_) => Err(anyhow::anyhow!("识别线程出现错误")), + } + } + + fn send(&mut self, tx: &Sender>, count: i32) { + let controller = Rc::new(RefCell::new(GenshinRepositoryScanController::new( + self.scanner_config.genshin_repo_scan_logic_config.clone(), + &self.window_info_clone, + // todo normalize types + count as usize, + self.game_info.clone(), + ))); + let mut generator = GenshinRepositoryScanController::get_generator(controller.clone()); + + loop { + let pinned_generator = Pin::new(&mut generator); + match pinned_generator.resume(()) { + GeneratorState::Yielded(_) => { + // let image = self.capture_panel().unwrap(); + let image = controller.borrow().capture_panel().unwrap(); + let star = self.get_star().unwrap(); + + // todo normalize types + if (star as i32) < self.scanner_config.min_star { + info!( + "找到满足最低星级要求 {} 的物品,准备退出……", + self.scanner_config.min_star + ); + break; + } + + if tx.send(Some(SendItem { panel_image: image, star: star })).is_err() { + break; + } + + // scanned_count += 1; + }, + GeneratorState::Complete(result) => { + match result { + Err(e) => error!("扫描发生错误:{}", e), + Ok(value) => { + match value { + ReturnResult::Interrupted => info!("用户中断"), + ReturnResult::Finished => () + } + } + } + + break; + } + } + } + + + } +} \ No newline at end of file diff --git a/yas-genshin/src/scanner/item_scanner/item_scanner_config.rs b/yas-genshin/src/scanner/item_scanner/item_scanner_config.rs new file mode 100644 index 00000000..87aa7696 --- /dev/null +++ b/yas-genshin/src/scanner/item_scanner/item_scanner_config.rs @@ -0,0 +1,52 @@ +use clap::{Arg, ArgAction, FromArgMatches}; +use yas::arguments_builder::arguments_builder::ArgumentsModifier; + +use crate::scanner_controller::repository_layout::config::GenshinRepositoryScannerLogicConfig; + +pub struct ItemScannerConfig { + pub verbose: bool, + + pub genshin_repo_scan_logic_config: GenshinRepositoryScannerLogicConfig, +} + +impl Default for ItemScannerConfig { + fn default() -> Self { + ItemScannerConfig { + verbose: false, + genshin_repo_scan_logic_config: Default::default() + } + } +} + +impl ArgumentsModifier for ItemScannerConfig { + fn modify_arguments(builder: &mut yas::arguments_builder::arguments_builder::ArgumentsBuilder) { + builder + .arg( + Arg::new("verbose") + .long("verbose") + .help("显示详细信息") + .num_args(0) + .action(ArgAction::SetTrue) + ); + + ::modify_arguments(builder); + } +} + +impl FromArgMatches for ItemScannerConfig { + fn from_arg_matches(matches: &clap::ArgMatches) -> Result { + let scanner_controller_config = GenshinRepositoryScannerLogicConfig::from_arg_matches(matches)?; + + let result = ItemScannerConfig { + genshin_repo_scan_logic_config: scanner_controller_config, + verbose: matches.get_flag("verbose"), + }; + + Ok(result) + } + + fn update_from_arg_matches(&mut self, _matches: &clap::ArgMatches) -> Result<(), clap::Error> { + // todo + unimplemented!() + } +} diff --git a/yas-genshin/src/scanner/item_scanner/mod.rs b/yas-genshin/src/scanner/item_scanner/mod.rs new file mode 100644 index 00000000..7630ea14 --- /dev/null +++ b/yas-genshin/src/scanner/item_scanner/mod.rs @@ -0,0 +1,2 @@ +pub mod item_scanner_config; +pub mod item_scanner; diff --git a/yas-genshin/src/scanner/mod.rs b/yas-genshin/src/scanner/mod.rs new file mode 100644 index 00000000..7309bcf1 --- /dev/null +++ b/yas-genshin/src/scanner/mod.rs @@ -0,0 +1,7 @@ +pub use artifact_scanner::GenshinArtifactScanner; +pub use artifact_scanner::GenshinArtifactScannerConfig; +pub use artifact_scanner::GenshinArtifactScanResult; + +mod artifact_scanner; +// mod item_scanner; + diff --git a/yas-genshin/src/scanner_controller/mod.rs b/yas-genshin/src/scanner_controller/mod.rs new file mode 100644 index 00000000..76acb972 --- /dev/null +++ b/yas-genshin/src/scanner_controller/mod.rs @@ -0,0 +1,2 @@ +pub mod repository_layout; + diff --git a/yas-genshin/src/scanner_controller/repository_layout/config.rs b/yas-genshin/src/scanner_controller/repository_layout/config.rs new file mode 100644 index 00000000..3e83178a --- /dev/null +++ b/yas-genshin/src/scanner_controller/repository_layout/config.rs @@ -0,0 +1,41 @@ +use clap::arg; + +#[derive(Clone, clap::Args)] +pub struct GenshinRepositoryScannerLogicConfig { + /// Max rows to scan + #[arg(id = "max-row", long = "max-row", help = "最大扫描行数", default_value_t = -1)] + pub max_row: i32, + + // todo move to another scanner + /// Will the scanner capture only? + // pub capture_only: bool, + + /// The time to wait for scrolling. Consider increasing this value if the scrolling is not correct + #[arg(id = "scroll-delay", long = "scroll-delay", help = "翻页时滚轮停顿时间(ms)(翻页不正确可以考虑加大该选项)", default_value_t = 80)] + pub scroll_delay: i32, + + /// Dump the captured image + // pub dump_mode: bool, + + /// The maximum time to wait for switching to the next item + #[arg(id = "max-wait-switch-item", long = "max-wait-switch-item", help = "切换物品最大等待时间(ms)", default_value_t = 800)] + pub max_wait_switch_item: i32, + + /// The time to wait for switching to the next item in cloud game + #[arg(id = "cloud-wait-switch-item", long = "cloud-wait-switch-item", help = "云游戏切换物品等待时间(ms)", default_value_t = 300)] + pub cloud_wait_switch_item: i32, +} + +impl Default for GenshinRepositoryScannerLogicConfig { + fn default() -> Self { + GenshinRepositoryScannerLogicConfig { + max_row: -1, + // capture_only: false, + scroll_delay: 80, + // number: -1, + // dump_mode: false, + max_wait_switch_item: 800, + cloud_wait_switch_item: 300, + } + } +} diff --git a/yas-genshin/src/scanner_controller/repository_layout/controller.rs b/yas-genshin/src/scanner_controller/repository_layout/controller.rs new file mode 100644 index 00000000..f4db4b28 --- /dev/null +++ b/yas-genshin/src/scanner_controller/repository_layout/controller.rs @@ -0,0 +1,416 @@ +use std::cell::RefCell; +use std::ops::Coroutine; +use std::rc::Rc; +use std::time::SystemTime; + +use anyhow::{anyhow, Result}; +use clap::{ArgMatches, FromArgMatches}; +use image::RgbImage; +use log::{error, info}; + +use yas::capture::{Capturer, GenericCapturer}; +use yas::game_info::GameInfo; +use yas::positioning::Pos; +use yas::system_control::SystemControl; +use yas::utils; +use yas::window_info::{FromWindowInfoRepository, WindowInfoRepository}; + +use crate::scanner_controller::repository_layout::{GenshinRepositoryScanControllerWindowInfo, GenshinRepositoryScannerLogicConfig, ScrollResult}; + +pub struct GenshinRepositoryScanController { + // to detect whether an item changes + pool: f64, + + initial_color: image::Rgb, + + // for scrolls + scrolled_rows: u32, + avg_scroll_one_row: f64, + + avg_switch_time: f64, + scanned_count: usize, + + game_info: GameInfo, + + // row and column in one page + row: usize, + col: usize, + + config: GenshinRepositoryScannerLogicConfig, + window_info: GenshinRepositoryScanControllerWindowInfo, + system_control: SystemControl, + capturer: Rc>, +} + +fn calc_pool(row: &Vec) -> f32 { + let len = row.len() / 3; + let mut pool: f32 = 0.0; + + for i in 0..len { + pool += row[i * 3] as f32; + } + pool +} + +fn get_capturer() -> Result>> { + Ok(Rc::new(GenericCapturer::new()?)) +} + +fn color_distance(c1: &image::Rgb, c2: &image::Rgb) -> usize { + let x = c1.0[0] as i32 - c2.0[0] as i32; + let y = c1.0[1] as i32 - c2.0[1] as i32; + let z = c1.0[2] as i32 - c2.0[2] as i32; + return (x * x + y * y + z * z) as usize; +} + +// constructor +impl GenshinRepositoryScanController { + pub fn new( + window_info_repo: &WindowInfoRepository, + config: GenshinRepositoryScannerLogicConfig, + game_info: GameInfo + ) -> Result { + let window_info = GenshinRepositoryScanControllerWindowInfo::from_window_info_repository( + game_info.window.to_rect_usize().size(), + game_info.ui, + game_info.platform, + window_info_repo + )?; + let row = window_info.genshin_repository_item_row; + let col = window_info.genshin_repository_item_col; + + Ok(GenshinRepositoryScanController { + system_control: SystemControl::new(), + + row: row as usize, + col: col as usize, + + window_info, + config, + + pool: 0.0, + + initial_color: image::Rgb([0, 0, 0]), + + scrolled_rows: 0, + avg_scroll_one_row: 0.0, + + avg_switch_time: 0.0, + // scanned_count: 0, + + game_info, + scanned_count: 0, + + capturer: get_capturer()?, + }) + } + + pub fn from_arg_matches( + window_info_repo: &WindowInfoRepository, + arg_matches: &ArgMatches, + game_info: GameInfo + ) -> Result { + Self::new( + window_info_repo, + GenshinRepositoryScannerLogicConfig::from_arg_matches(arg_matches)?, + game_info + ) + } +} + +pub enum ReturnResult { + Interrupted, + Finished, +} + +impl GenshinRepositoryScanController { + pub fn get_generator(object: Rc>, item_count: usize) -> impl Coroutine> { + let generator = move || { + let mut scanned_row = 0; + let mut scanned_count = 0; + let mut start_row = 0; + + let total_row = (item_count + object.borrow().col - 1) / object.borrow().col; + let last_row_col = if item_count % object.borrow().col == 0 { + object.borrow().col + } else { + item_count % object.borrow().col + }; + + info!( + "扫描任务共 {} 个物品,共计 {} 行,尾行 {} 个", + item_count, total_row, last_row_col + ); + + object.borrow_mut().move_to(0, 0); + + #[cfg(target_os = "macos")] + utils::sleep(20); + + // todo remove unwrap + object.borrow_mut().system_control.mouse_click().unwrap(); + utils::sleep(1000); + + object.borrow_mut().sample_initial_color().unwrap(); + + let row = object.borrow().row.min(total_row); + + 'outer: while scanned_count < item_count { + '_row: for row in start_row..row { + let row_item_count = if scanned_row == total_row - 1 { + last_row_col + } else { + object.borrow().col + }; + + '_col: for col in 0..row_item_count { + // 大于最大数量 或者 取消 或者 鼠标右键按下 + if utils::is_rmb_down() { + return Ok(ReturnResult::Interrupted); + } + if scanned_count > item_count { + return Ok(ReturnResult::Finished); + } + + object.borrow_mut().move_to(row, col); + object.borrow_mut().system_control.mouse_click().unwrap(); + + #[cfg(target_os = "macos")] + utils::sleep(20); + + let _ = object.borrow_mut().wait_until_switched(); + + // have to make sure at this point no mut ref exists + yield; + + scanned_count += 1; + object.borrow_mut().scanned_count = scanned_count; + } // end '_col + + scanned_row += 1; + + // todo this is dangerous, use uniform integer type instead + if scanned_row >= object.borrow().config.max_row as usize { + info!("到达最大行数,准备退出……"); + break 'outer; + } + } // end '_row + + let remain = item_count - scanned_count; + let remain_row = (remain + object.borrow().col - 1) / object.borrow().col; + let scroll_row = remain_row.min(object.borrow().row); + start_row = object.borrow().row - scroll_row; + + match object.borrow_mut().scroll_rows(scroll_row as i32) { + ScrollResult::TimeLimitExceeded => { + // error!(""); + return Err(anyhow!("翻页超时,扫描终止……")); + }, + ScrollResult::Interrupt => { + return Ok(ReturnResult::Interrupted); + }, + _ => (), + } + + utils::sleep(100); + } + + Ok(ReturnResult::Finished) + }; + + generator + } + + #[inline(always)] + pub fn get_flag_color(&self) -> Result> { + let pos = Pos { + x: self.window_info.flag_pos.x as i32 + self.game_info.window.left, + y: self.window_info.flag_pos.y as i32 + self.game_info.window.top + }; + self.capturer.capture_color(pos) + } + + #[inline(always)] + pub fn sample_initial_color(&mut self) -> Result<()> { + self.initial_color = self.get_flag_color()?; + anyhow::Ok(()) + } + + pub fn align_row(&mut self) { + for _ in 0..10 { + let color = match self.get_flag_color() { + Ok(color) => color, + Err(_) => return, + }; + + if color_distance(&self.initial_color, &color) > 10 { + self.mouse_scroll(1, false); + utils::sleep(self.config.scroll_delay.try_into().unwrap()); + } else { + break; + } + } + } + + pub fn move_to(&mut self, row: usize, col: usize) { + let (row, col) = (row as u32, col as u32); + let origin = self.game_info.window.to_rect_f64().origin(); + + let gap = self.window_info.item_gap_size; + let margin = self.window_info.scan_margin_pos; + let size = self.window_info.item_size; + + let left = origin.x + margin.x + (gap.width + size.width) * (col as f64) + size.width / 2.0; + let top = origin.y + margin.y + (gap.height + size.height) * (row as f64) + size.height / 2.0; + + self.system_control.mouse_move_to(left as i32, top as i32).unwrap(); + + #[cfg(target_os = "macos")] + utils::sleep(20); + } + + pub fn scroll_one_row(&mut self) -> ScrollResult { + let mut state = 0; + let mut count = 0; + let max_scroll = 25; + + while count < max_scroll { + if utils::is_rmb_down() { + return ScrollResult::Interrupt; + } + + #[cfg(windows)] + let _ = self.system_control.mouse_scroll(1, false); + + // self.mouse_scroll(1, count < 1); + + utils::sleep(self.config.scroll_delay.try_into().unwrap()); + count += 1; + + let color = match self.get_flag_color() { + Ok(color) => color, + Err(_) => return ScrollResult::Failed, + }; + + if state == 0 && color_distance(&self.initial_color, &color) > 10 { + state = 1; + } else if state == 1 && color_distance(&self.initial_color, &color) <= 10 { + self.update_avg_row(count); + return ScrollResult::Success; + } + } + + ScrollResult::TimeLimitExceeded + } + + pub fn scroll_rows(&mut self, count: i32) -> ScrollResult { + if cfg!(not(target_os = "macos")) && self.scrolled_rows >= 5 { + let length = self.estimate_scroll_length(count); + + for _ in 0..length { + // todo remove unwrap + self.system_control.mouse_scroll(1, false).unwrap(); + } + + // self.mouse_scroll(length, false); + + utils::sleep(self.config.scroll_delay.try_into().unwrap()); + + self.align_row(); + return ScrollResult::Skip; + } + + for _ in 0..count { + match self.scroll_one_row() { + ScrollResult::Success | ScrollResult::Skip => continue, + ScrollResult::Interrupt => return ScrollResult::Interrupt, + v => { + error!("Scrolling failed: {:?}", v); + return v; + }, + } + } + + ScrollResult::Success + } + + pub fn wait_until_switched(&mut self) -> Result<()> { + if self.game_info.is_cloud { + utils::sleep(self.config.cloud_wait_switch_item.try_into()?); + return anyhow::Ok(()); + } + + let now = SystemTime::now(); + + let mut consecutive_time = 0; + let mut diff_flag = false; + while now.elapsed().unwrap().as_millis() < self.config.max_wait_switch_item as u128 { + let im = self.capturer.capture_relative_to( + self.window_info.pool_rect.to_rect_i32(), + self.game_info.window.origin() + )?; + + let pool = calc_pool(im.as_raw()) as f64; + + if (pool - self.pool).abs() > 0.000001 { + self.pool = pool; + diff_flag = true; + consecutive_time = 0; + } else if diff_flag { + consecutive_time += 1; + if consecutive_time == 1 { + self.avg_switch_time = (self.avg_switch_time * self.scanned_count as f64 + + now.elapsed().unwrap().as_millis() as f64) + / (self.scanned_count as f64 + 1.0); + self.scanned_count += 1; + return anyhow::Ok(()); + } + } + } + + Err(anyhow!("Wait until switched failed")) + } + + #[inline(always)] + pub fn mouse_scroll(&mut self, length: i32, try_find: bool) { + #[cfg(windows)] + self.system_control.mouse_scroll(length, try_find).unwrap(); + + #[cfg(target_os = "linux")] + self.system_control.mouse_scroll(length, try_find); + + #[cfg(target_os = "macos")] + { + match self.game_info.ui { + crate::common::UI::Desktop => { + self.system_control.mouse_scroll(length); + utils::sleep(20); + }, + crate::common::UI::Mobile => { + if try_find { + self.system_control.mac_scroll_fast(length); + } else { + self.system_control.mac_scroll_slow(length); + } + }, + } + } + } + + #[inline(always)] + fn update_avg_row(&mut self, count: i32) { + let current = self.avg_scroll_one_row * self.scrolled_rows as f64 + count as f64; + self.scrolled_rows += 1; + self.avg_scroll_one_row = current / self.scrolled_rows as f64; + + info!( + "avg scroll one row: {} ({})", + self.avg_scroll_one_row, self.scrolled_rows + ); + } + + #[inline(always)] + fn estimate_scroll_length(&self, count: i32) -> i32 { + ((self.avg_scroll_one_row * count as f64 - 2.0).round() as i32).max(0) + } +} \ No newline at end of file diff --git a/yas-genshin/src/scanner_controller/repository_layout/mod.rs b/yas-genshin/src/scanner_controller/repository_layout/mod.rs new file mode 100644 index 00000000..a8a55b35 --- /dev/null +++ b/yas-genshin/src/scanner_controller/repository_layout/mod.rs @@ -0,0 +1,12 @@ +pub use config::GenshinRepositoryScannerLogicConfig; +pub use controller::GenshinRepositoryScanController; +pub use controller::ReturnResult; +pub use scroll_result::ScrollResult; +pub use window_info::GenshinRepositoryScanControllerWindowInfo; + +mod config; +mod controller; + +mod scroll_result; +mod window_info; + diff --git a/yas-genshin/src/scanner_controller/repository_layout/scroll_result.rs b/yas-genshin/src/scanner_controller/repository_layout/scroll_result.rs new file mode 100644 index 00000000..63d17c21 --- /dev/null +++ b/yas-genshin/src/scanner_controller/repository_layout/scroll_result.rs @@ -0,0 +1,8 @@ +#[derive(Debug)] +pub enum ScrollResult { + TimeLimitExceeded, + Interrupt, + Success, + Failed, + Skip, +} diff --git a/yas-genshin/src/scanner_controller/repository_layout/window_info.rs b/yas-genshin/src/scanner_controller/repository_layout/window_info.rs new file mode 100644 index 00000000..6442b7dd --- /dev/null +++ b/yas-genshin/src/scanner_controller/repository_layout/window_info.rs @@ -0,0 +1,26 @@ +use yas::positioning::{Pos, Rect, Size}; +use yas_derive::YasWindowInfo; + +#[derive(YasWindowInfo)] +pub struct GenshinRepositoryScanControllerWindowInfo { + #[window_info(rename = "genshin_repository_panel_rect")] + pub panel_rect: Rect, + + #[window_info(rename = "genshin_repository_flag_pos")] + pub flag_pos: Pos, + + #[window_info(rename = "genshin_repository_item_gap_size")] + pub item_gap_size: Size, + + #[window_info(rename = "genshin_repository_item_size")] + pub item_size: Size, + + #[window_info(rename = "genshin_repository_scan_margin_pos")] + pub scan_margin_pos: Pos, + + #[window_info(rename = "genshin_repository_pool_rect")] + pub pool_rect: Rect, + + pub genshin_repository_item_row: i32, + pub genshin_repository_item_col: i32, +} diff --git a/yas-genshin/window_info/windows1280x960.json b/yas-genshin/window_info/windows1280x960.json new file mode 100644 index 00000000..0486bbd9 --- /dev/null +++ b/yas-genshin/window_info/windows1280x960.json @@ -0,0 +1,143 @@ +{ + "current_resolution": { + "width": 1280, + "height": 960 + }, + "platform": "Windows", + "ui": "Desktop", + "data": { + "genshin_repository_panel_rect": { + "Rect": { + "top": 80, + "left": 872, + "height": 800, + "width": 328 + } + }, + "genshin_repository_flag_pos": { + "Pos": { + "x": 218.1, + "y": 72.1 + } + }, + "genshin_repository_item_gap_size": { + "Size": { + "width": 15, + "height": 15 + } + }, + "genshin_repository_item_size": { + "Size": { + "width": 82, + "height": 101 + } + }, + "genshin_repository_scan_margin_pos": { + "Pos": { + "x": 79, + "y": 81 + } + }, + "genshin_repository_pool_rect": { + "Rect": { + "top": 93.2, + "left": 912.7, + "height": 319.2, + "width": 15 + } + }, + "genshin_repository_item_row": { + "InvariantInt": 7 + }, + "genshin_repository_item_col": { + "InvariantInt": 8 + }, + + "genshin_artifact_item_count_rect": { + "Rect": { + "top": 22.9, + "left": 1058.6, + "height": 18.5, + "width": 143.7 + } + }, + "genshin_artifact_star_pos": { + "Pos": { + "x": 1175.4, + "y": 95.8 + } + }, + "genshin_artifact_main_stat_name_rect": { + "Rect": { + "top": 181.0, + "left": 889.5, + "height": 18.8, + "width": 108.5 + } + }, + "genshin_artifact_main_stat_value_rect": { + "Rect": { + "top": 199.8, + "left": 889.5, + "height": 33.6, + "width": 108.5 + } + }, + "genshin_artifact_level_rect": { + "Rect": { + "top": 288, + "left": 894, + "height": 14, + "width": 33 + } + }, + "genshin_artifact_item_equip_rect": { + "Rect": { + "top": 849.8, + "left": 924.4, + "height": 20.3, + "width": 166.4 + } + }, + "genshin_artifact_sub_stat1_rect": { + "Rect": { + "top": 318.2, + "left": 904.3, + "height": 24.1, + "width": 196.2 + } + }, + "genshin_artifact_sub_stat2_rect": { + "Rect": { + "top": 342.3, + "left": 904.3, + "height": 27.1, + "width": 196.2 + } + }, + "genshin_artifact_sub_stat3_rect": { + "Rect": { + "top": 369.4, + "left": 904.3, + "height": 25.9, + "width": 196.2 + } + }, + "genshin_artifact_sub_stat4_rect": { + "Rect": { + "top": 395.3, + "left": 904.3, + "height": 25.3, + "width": 196.2 + } + }, + "genshin_artifact_title_rect": { + "Rect": { + "top": 85, + "left": 889.5, + "height": 26.7, + "width": 205.3 + } + } + } +} \ No newline at end of file diff --git a/yas-genshin/window_info/windows1440x900.json b/yas-genshin/window_info/windows1440x900.json new file mode 100644 index 00000000..2aead950 --- /dev/null +++ b/yas-genshin/window_info/windows1440x900.json @@ -0,0 +1,143 @@ +{ + "current_resolution": { + "width": 1440, + "height": 900 + }, + "platform": "Windows", + "ui": "Desktop", + "data": { + "genshin_repository_panel_rect": { + "Rect": { + "top": 90, + "left": 981, + "height": 720, + "width": 369 + } + }, + "genshin_repository_flag_pos": { + "Pos": { + "x": 245.9, + "y": 82.1 + } + }, + "genshin_repository_item_gap_size": { + "Size": { + "width": 17, + "height": 18 + } + }, + "genshin_repository_item_size": { + "Size": { + "width": 93, + "height": 113 + } + }, + "genshin_repository_scan_margin_pos": { + "Pos": { + "x": 89, + "y": 91 + } + }, + "genshin_repository_pool_rect": { + "Rect": { + "top": 103.6, + "left": 1028.5, + "height": 357.1, + "width": 15 + } + }, + "genshin_repository_item_row": { + "InvariantInt": 6 + }, + "genshin_repository_item_col": { + "InvariantInt": 8 + }, + + "genshin_artifact_item_count_rect": { + "Rect": { + "top": 25, + "left": 1182.8, + "height": 21.8, + "width": 170.3 + } + }, + "genshin_artifact_star_pos": { + "Pos": { + "x": 1321.3, + "y": 111.3 + } + }, + "genshin_artifact_main_stat_name_rect": { + "Rect": { + "top": 201.6, + "left": 1000.3, + "height": 22.3, + "width": 127.8 + } + }, + "genshin_artifact_main_stat_value_rect": { + "Rect": { + "top": 225.5, + "left": 1000.3, + "height": 37.3, + "width": 127.8 + } + }, + "genshin_artifact_level_rect": { + "Rect": { + "top": 324, + "left": 1006, + "height": 16, + "width": 37 + } + }, + "genshin_artifact_item_equip_rect": { + "Rect": { + "top": 776, + "left": 1041.3, + "height": 24.6, + "width": 206 + } + }, + "genshin_artifact_sub_stat1_rect": { + "Rect": { + "top": 358, + "left": 1016.2, + "height": 26.1, + "width": 207.9 + } + }, + "genshin_artifact_sub_stat2_rect": { + "Rect": { + "top": 384.1, + "left": 1016.2, + "height": 28.5, + "width": 207.9 + } + }, + "genshin_artifact_sub_stat3_rect": { + "Rect": { + "top": 412.6, + "left": 1016.2, + "height": 27.9, + "width": 207.9 + } + }, + "genshin_artifact_sub_stat4_rect": { + "Rect": { + "top": 440.5, + "left": 1016.2, + "height": 26.6, + "width": 207.9 + } + }, + "genshin_artifact_title_rect": { + "Rect": { + "top": 96, + "left": 1000.9, + "height": 30.1, + "width": 268 + } + } + } +} \ No newline at end of file diff --git a/yas-genshin/window_info/windows1600x900.json b/yas-genshin/window_info/windows1600x900.json new file mode 100644 index 00000000..5363c70a --- /dev/null +++ b/yas-genshin/window_info/windows1600x900.json @@ -0,0 +1,143 @@ +{ + "current_resolution": { + "width": 1600, + "height": 900 + }, + "platform": "Windows", + "ui": "Desktop", + "data": { + "genshin_repository_panel_rect": { + "Rect": { + "top": 100.0, + "left": 1090.0, + "width": 410.0, + "height": 700.0 + } + }, + "genshin_repository_flag_pos": { + "Pos": { + "x": 271.1, + "y": 89.8 + } + }, + "genshin_repository_item_gap_size": { + "Size": { + "width": 20, + "height": 20 + } + }, + "genshin_repository_item_size": { + "Size": { + "width": 102, + "height": 126 + } + }, + "genshin_repository_scan_margin_pos": { + "Pos": { + "x": 99, + "y": 101 + } + }, + "genshin_repository_pool_rect": { + "Rect": { + "top": 118.2, + "left": 1144.7, + "width": 15, + "height": 392.1 + } + }, + "genshin_repository_item_row": { + "InvariantInt": 5 + }, + "genshin_repository_item_col": { + "InvariantInt": 8 + }, + + "genshin_artifact_item_count_rect": { + "Rect": { + "top": 27.1, + "left": 1314.9, + "height": 25.8, + "width": 189.8 + } + }, + "genshin_artifact_star_pos": { + "Pos": { + "x": 1469.4, + "y": 123.9 + } + }, + "genshin_artifact_main_stat_name_rect": { + "Rect": { + "top": 224.3, + "left": 1110.0, + "height": 24, + "width": 143.9 + } + }, + "genshin_artifact_main_stat_value_rect": { + "Rect": { + "top": 248.4, + "left": 1110.0, + "height": 38.4, + "width": 136.8 + } + }, + "genshin_artifact_level_rect": { + "Rect": { + "top": 360, + "left": 1117, + "height": 18, + "width": 43 + } + }, + "genshin_artifact_item_equip_rect": { + "Rect": { + "top": 762.6, + "left": 1154.9, + "height": 25.2, + "width": 243.5 + } + }, + "genshin_artifact_sub_stat1_rect": { + "Rect": { + "top": 398.1, + "left": 1130.2, + "height": 29.2, + "width": 212.8 + } + }, + "genshin_artifact_sub_stat2_rect": { + "Rect": { + "top": 427.3, + "left": 1130.2, + "height": 30.9, + "width": 212.8 + } + }, + "genshin_artifact_sub_stat3_rect": { + "Rect": { + "top": 458.2, + "left": 1130.2, + "height": 32.7, + "width": 212.8 + } + }, + "genshin_artifact_sub_stat4_rect": { + "Rect": { + "top": 490.9, + "left": 1130.2, + "height": 32.1, + "width": 212.8 + } + }, + "genshin_artifact_title_rect": { + "Rect": { + "top": 106.6, + "left": 1111.8, + "height": 33, + "width": 305.9 + } + } + } +} \ No newline at end of file diff --git a/yas-genshin/window_info/windows2100x900.json b/yas-genshin/window_info/windows2100x900.json new file mode 100644 index 00000000..09ce03d0 --- /dev/null +++ b/yas-genshin/window_info/windows2100x900.json @@ -0,0 +1,143 @@ +{ + "current_resolution": { + "width": 2100, + "height": 900 + }, + "platform": "Windows", + "ui": "Desktop", + "data": { + "genshin_repository_panel_rect": { + "Rect": { + "top": 100, + "left": 1531, + "height": 700, + "width": 410 + } + }, + "genshin_repository_flag_pos": { + "Pos": { + "x": 340, + "y": 89.8 + } + }, + "genshin_repository_item_gap_size": { + "Size": { + "width": 20, + "height": 20 + } + }, + "genshin_repository_item_size": { + "Size": { + "width": 102, + "height": 126 + } + }, + "genshin_repository_scan_margin_pos": { + "Pos": { + "x": 166, + "y": 101 + } + }, + "genshin_repository_pool_rect": { + "Rect": { + "top": 118.2, + "left": 1584, + "height": 392.1, + "width": 15 + } + }, + "genshin_repository_item_row": { + "InvariantInt": 5 + }, + "genshin_repository_item_col": { + "InvariantInt": 11 + }, + + "genshin_artifact_item_count_rect": { + "Rect": { + "top": 27.1, + "left": 1785, + "height": 25.8, + "width": 160 + } + }, + "genshin_artifact_star_pos": { + "Pos": { + "x": 1900, + "y": 123.9 + } + }, + "genshin_artifact_main_stat_name_rect": { + "Rect": { + "top": 224.3, + "left": 1550, + "height": 23.7, + "width": 140 + } + }, + "genshin_artifact_main_stat_value_rect": { + "Rect": { + "top": 248.4, + "left": 1550, + "height": 38.4, + "width": 140 + } + }, + "genshin_artifact_level_rect": { + "Rect": { + "top": 360, + "left": 1557, + "height": 18, + "width": 43 + } + }, + "genshin_artifact_item_equip_rect": { + "Rect": { + "top": 762.6, + "left": 1598, + "height": 25.2, + "width": 252 + } + }, + "genshin_artifact_sub_stat1_rect": { + "Rect": { + "top": 398.1, + "left": 1570, + "height": 29.2, + "width": 210 + } + }, + "genshin_artifact_sub_stat2_rect": { + "Rect": { + "top": 427.3, + "left": 1570, + "height": 30.9, + "width": 210 + } + }, + "genshin_artifact_sub_stat3_rect": { + "Rect": { + "top": 458.2, + "left": 1570, + "height": 32.7, + "width": 210 + } + }, + "genshin_artifact_sub_stat4_rect": { + "Rect": { + "top": 490.9, + "left": 1570, + "height": 32.1, + "width": 210 + } + }, + "genshin_artifact_title_rect": { + "Rect": { + "top": 106.6, + "left": 1550, + "height": 33, + "width": 150 + } + } + } +} \ No newline at end of file diff --git a/yas-genshin/window_info/windows3440x1440.json b/yas-genshin/window_info/windows3440x1440.json new file mode 100644 index 00000000..48556993 --- /dev/null +++ b/yas-genshin/window_info/windows3440x1440.json @@ -0,0 +1,143 @@ +{ + "current_resolution": { + "width": 3440, + "height": 1440 + }, + "platform": "Windows", + "ui": "Desktop", + "data": { + "genshin_repository_panel_rect": { + "Rect": { + "top": 160, + "left": 2528, + "height": 1120, + "width": 657 + } + }, + "genshin_repository_flag_pos": { + "Pos": { + "x": 580, + "y": 145 + } + }, + "genshin_repository_item_gap_size": { + "Size": { + "width": 32, + "height": 31 + } + }, + "genshin_repository_item_size": { + "Size": { + "width": 164, + "height": 204 + } + }, + "genshin_repository_scan_margin_pos": { + "Pos": { + "x": 305, + "y": 161 + } + }, + "genshin_repository_pool_rect": { + "Rect": { + "top": 170, + "left": 2610, + "height": 730, + "width": 30 + } + }, + "genshin_repository_item_row": { + "InvariantInt": 5 + }, + "genshin_repository_item_col": { + "InvariantInt": 11 + }, + + "genshin_artifact_item_count_rect": { + "Rect": { + "top": 50, + "left": 2750, + "height": 35, + "width": 435 + } + }, + "genshin_artifact_star_pos": { + "Pos": { + "x": 3130, + "y": 200 + } + }, + "genshin_artifact_main_stat_name_rect": { + "Rect": { + "top": 360, + "left": 2560, + "height": 40, + "width": 290 + } + }, + "genshin_artifact_main_stat_value_rect": { + "Rect": { + "top": 400, + "left": 2560, + "height": 60, + "width": 290 + } + }, + "genshin_artifact_level_rect": { + "Rect": { + "top": 575, + "left": 2568, + "height": 30, + "width": 72 + } + }, + "genshin_artifact_item_equip_rect": { + "Rect": { + "top": 1220, + "left": 3140, + "height": 40, + "width": 2490 + } + }, + "genshin_artifact_sub_stat1_rect": { + "Rect": { + "top": 640, + "left": 2590, + "height": 40, + "width": 490 + } + }, + "genshin_artifact_sub_stat2_rect": { + "Rect": { + "top": 690, + "left": 2590, + "height": 40, + "width": 490 + } + }, + "genshin_artifact_sub_stat3_rect": { + "Rect": { + "top": 742, + "left": 2590, + "height": 40, + "width": 490 + } + }, + "genshin_artifact_sub_stat4_rect": { + "Rect": { + "top": 795, + "left": 2590, + "height": 40, + "width": 490 + } + }, + "genshin_artifact_title_rect": { + "Rect": { + "top": 170, + "left": 2560, + "height": 50, + "width": 580 + } + } + } +} \ No newline at end of file diff --git a/yas-starrail/Cargo.toml b/yas-starrail/Cargo.toml new file mode 100644 index 00000000..0bb3cda7 --- /dev/null +++ b/yas-starrail/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "yas_scanner_starrail" +version = "0.1.14" +edition = "2021" +build = "build.rs" +description = "Honkai: Star Rail item scanner" +repository = "https://github.com/wormtql/yas" +keywords = ["HonkaiStarRail", "relic", "scanner", "ocr"] +license = "GPL-2.0-or-later" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +yas = { path = "../yas", package="yas_scanner" } +anyhow = "1.0" +log = "0.4" +clap = { version = "4.4", features = ["derive"] } +image = "0.24" +serde_json = "1.0" +edit-distance = "2.1" +regex = "1.5" +strum = "0.25" +strum_macros = "0.25" +lazy_static = "1.4" +serde = { version = "1.0", features = ["derive"] } +env_logger = "0.10.0" + +[build-dependencies] +winres = "0.1" diff --git a/yas-starrail/build.rs b/yas-starrail/build.rs new file mode 100644 index 00000000..b64054ab --- /dev/null +++ b/yas-starrail/build.rs @@ -0,0 +1,14 @@ +fn main() { + if std::env::var("CARGO_CFG_TARGET_OS").unwrap().as_str() == "windows" { + let mut res = winres::WindowsResource::new(); + // https://github.com/mxre/winres/pull/24 + // https://github.com/mxre/winres/issues/42 + #[cfg(not(target_os = "windows"))] + if std::env::var("CARGO_CFG_TARGET_ENV").unwrap().as_str() == "gnu" { + res.set_ar_path("x86_64-w64-mingw32-ar"); + res.set_windres_path("x86_64-w64-mingw32-windres"); + } + res.set_manifest_file("../assets/manifest.xml"); + res.compile().unwrap(); + } +} diff --git a/yas-starrail/src/export/export_format.rs b/yas-starrail/src/export/export_format.rs new file mode 100644 index 00000000..4b7d0fde --- /dev/null +++ b/yas-starrail/src/export/export_format.rs @@ -0,0 +1,12 @@ +use clap::ValueEnum; + +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)] +pub enum StarRailRelicExportFormat { + March7th, +} + +impl Default for StarRailRelicExportFormat { + fn default() -> Self { + Self::March7th + } +} \ No newline at end of file diff --git a/yas-starrail/src/export/mod.rs b/yas-starrail/src/export/mod.rs new file mode 100644 index 00000000..4b416d48 --- /dev/null +++ b/yas-starrail/src/export/mod.rs @@ -0,0 +1,2 @@ +pub mod export_format; +pub mod relic; \ No newline at end of file diff --git a/yas-starrail/src/export/relic/exporter.rs b/yas-starrail/src/export/relic/exporter.rs new file mode 100644 index 00000000..4d7d0922 --- /dev/null +++ b/yas-starrail/src/export/relic/exporter.rs @@ -0,0 +1,79 @@ +use std::path::{Path, PathBuf}; + +use clap::{Arg, FromArgMatches}; +use yas::arguments_builder::arguments_builder::{ArgumentsModifier, ArgumentsBuilder}; +use yas::export::{YasExporter, ExportAssets}; + +use crate::relic::StarRailRelic; + +use crate::export::export_format::StarRailRelicExportFormat; + +use super::march7th::March7thFormat; + +pub struct StarRailRelicExporter<'a> { + pub format: StarRailRelicExportFormat, + pub results: Option<&'a [StarRailRelic]>, + pub output_dir: PathBuf, +} + +impl<'a> YasExporter for StarRailRelicExporter<'a> { + fn export(&self, export_assets: &mut ExportAssets) { + if self.results.is_none() { + return; + } + + let results = self.results.unwrap(); + + match self.format { + StarRailRelicExportFormat::March7th => { + let path = self.output_dir.join("march7th.json"); + let value = March7thFormat::new(results); + let contents = serde_json::to_string(&value).unwrap(); + + export_assets.add_asset(path, contents.into_bytes()); + }, + }; + } +} + +impl<'a> ArgumentsModifier for StarRailRelicExporter<'a> { + fn modify_arguments(builder: &mut ArgumentsBuilder) { + builder.arg( + Arg::new("output-format") + .long("output-format") + .short('f') + .help("输出格式") + .value_parser(clap::builder::EnumValueParser::::new()) + .default_value("march7th") + ) + .arg( + Arg::new("output-dir") + .long("output-dir") + .short('o') + .help("输出目录") + .default_value(".") + ); + } +} + +impl<'a> FromArgMatches for StarRailRelicExporter<'a> { + fn from_arg_matches(matches: &clap::ArgMatches) -> Result { + let output_dir = matches.get_one::("output-dir").unwrap(); + + // todo error propogation + let path = PathBuf::try_from(output_dir).unwrap(); + + let value = StarRailRelicExporter { + format: *matches.get_one("output-format").unwrap(), + results: None, + output_dir: path + }; + + Ok(value) + } + + fn update_from_arg_matches(&mut self, matches: &clap::ArgMatches) -> Result<(), clap::Error> { + // todo + unimplemented!() + } +} \ No newline at end of file diff --git a/src/expo/march7th.rs b/yas-starrail/src/export/relic/march7th.rs similarity index 84% rename from src/expo/march7th.rs rename to yas-starrail/src/export/relic/march7th.rs index 773dabf3..a04de13e 100644 --- a/src/expo/march7th.rs +++ b/yas-starrail/src/export/relic/march7th.rs @@ -1,15 +1,11 @@ -use std::convert::From; -use std::fs::File; -use std::io::prelude::*; - use serde::ser::{Serialize, SerializeMap, Serializer}; +use std::convert::From; -use crate::artifact::internal_relic::{ - RelicSetName, RelicSlot, RelicStat, RelicStatName, InternalRelic, +use crate::relic::{ + RelicSetName, RelicSlot, RelicStat, RelicStatName, StarRailRelic, }; - -type March7thRelic = InternalRelic; +type March7thRelic = StarRailRelic; impl RelicStatName { pub fn to_march7th(&self) -> String { @@ -42,7 +38,6 @@ impl RelicStatName { impl RelicSetName { pub fn to_march7th(&self) -> String { - let same = self.to_string(); let temp = match self { RelicSetName::PasserbyofWanderingCloud => "PasserbyofWanderingCloud", RelicSetName::MusketeerofWildWheat => "MusketeerofWildWheat", @@ -56,9 +51,16 @@ impl RelicSetName { RelicSetName::EagleofTwilightLine => "EagleofTwilightLine", RelicSetName::ThiefofShootingMeteor => "ThiefofShootingMeteor", RelicSetName::WastelanderofBanditryDesert => "WastelanderofBanditryDesert", + RelicSetName::LongevousDisciple => "LongevousDisciple", + RelicSetName::MessengerTraversingHackerspace => "MessengerTraversingHackerspace", + RelicSetName::TheAshblazingGrandDuke => "TheAshblazingGrandDuke", + RelicSetName::PrisonerinDeepConfinement => "PrisonerinDeepConfinement", + RelicSetName::PioneerDiverofDeadWaters => "PioneerDiverofDeadWaters", + RelicSetName::WatchmakerMasterofDreamMachinations => "WatchmakerMasterofDreamMachinations", + RelicSetName::SpaceSealingStation => "SpaceSealingStation", RelicSetName::FleetoftheAgeless => "FleetoftheAgeless", - RelicSetName::PanGalacticCommercialEnterprise => "PanGalacticCommercialEnterprise", + RelicSetName::PanCosmicCommercialEnterprise => "PanCosmicCommercialEnterprise", RelicSetName::BelobogoftheArchitects => "BelobogoftheArchitects", RelicSetName::CelestialDifferentiator => "CelestialDifferentiator", RelicSetName::InertSalsotto => "InertSalsotto", @@ -66,9 +68,8 @@ impl RelicSetName { RelicSetName::SprightlyVonwacq => "SprightlyVonwacq", RelicSetName::RutilantArena => "RutilantArena", RelicSetName::BrokenKeel => "BrokenKeel", - RelicSetName::LongevousDisciple => "LongevousDisciple", - RelicSetName::MessengerTraversingHackerspace => "MessengerTraversingHackerspace", - _ => same.as_str(), + RelicSetName::FirmamentFrontlineGlamoth => "FirmamentFrontlineGlamoth", + RelicSetName::PenaconyLandoftheDreams => "PenaconyLandoftheDreams", }; String::from(temp) } @@ -124,21 +125,13 @@ impl Serialize for March7thRelic { if let Some(ref s) = self.sub_stat_4 { sub_stats.push(s); } - // let mut subs = serializer.serialize_seq(Some(sub_stats.len()))?; - // - // for i in sub_stats { - // subs.serialize_element(i); - // } - // subs.end(); - // subs. root.serialize_entry("normalTags", &sub_stats)?; - root.serialize_entry("omit", &false)?; root.serialize_entry("level", &self.level)?; root.serialize_entry("star", &self.star)?; root.serialize_entry("equip", &self.equip)?; - // let random_id = thread_rng().gen::(); - // root.serialize_entry("id", &random_id); + root.serialize_entry("lock", &self.lock)?; + root. serialize_entry("discard", &self.discard)?; root.end() } @@ -172,12 +165,11 @@ impl<'a> Serialize for March7thFormat<'a> { } impl<'a> March7thFormat<'a> { - pub fn new(results: &Vec) -> March7thFormat { + pub fn new(results: &'a [StarRailRelic]) -> March7thFormat { let mut head: Vec<&March7thRelic> = Vec::new(); let mut hands: Vec<&March7thRelic> = Vec::new(); let mut body: Vec<&March7thRelic> = Vec::new(); let mut feet: Vec<&March7thRelic> = Vec::new(); - let mut head: Vec<&March7thRelic> = Vec::new(); let mut sphere: Vec<&March7thRelic> = Vec::new(); let mut rope: Vec<&March7thRelic> = Vec::new(); @@ -199,21 +191,7 @@ impl<'a> March7thFormat<'a> { feet, sphere, rope, - version: String::from("1"), } } - - pub fn save(&self, path: String) { - let mut file = match File::create(&path) { - Err(why) => panic!("couldn't create {}: {}", path, why), - Ok(file) => file, - }; - let s = serde_json::to_string(&self).unwrap(); - - match file.write_all(s.as_bytes()) { - Err(why) => panic!("couldn't write to {}: {}", path, why), - _ => {}, - } - } -} +} \ No newline at end of file diff --git a/yas-starrail/src/export/relic/mod.rs b/yas-starrail/src/export/relic/mod.rs new file mode 100644 index 00000000..2da5ced5 --- /dev/null +++ b/yas-starrail/src/export/relic/mod.rs @@ -0,0 +1,4 @@ +mod march7th; +mod exporter; + +pub use exporter::StarRailRelicExporter; \ No newline at end of file diff --git a/yas-starrail/src/lib.rs b/yas-starrail/src/lib.rs new file mode 100644 index 00000000..d93a5c72 --- /dev/null +++ b/yas-starrail/src/lib.rs @@ -0,0 +1,6 @@ +#![feature(coroutines, coroutine_trait)] + +pub mod scanner_controller; +pub mod export; +pub mod scanner; +pub mod relic; \ No newline at end of file diff --git a/yas-starrail/src/main.rs b/yas-starrail/src/main.rs new file mode 100644 index 00000000..0a17c827 --- /dev/null +++ b/yas-starrail/src/main.rs @@ -0,0 +1,88 @@ +#[macro_use] +extern crate log; + +use anyhow::Result; +use clap::{Command, FromArgMatches}; +use yas::{arguments_builder::arguments_builder::{ArgumentsModifier, ArgumentsBuilder}, window_info::{require_window_info::RequireWindowInfo, window_info_builder::WindowInfoBuilder, window_info_prototypes::WindowInfoPrototypes}, load_window_info, game_info::GameInfoBuilder, export::ExportAssets}; +use yas_scanner_starrail::{scanner::relic_scanner::{StarRailRelicScanner, StarRailRelicScannerConfig}, export::relic::StarRailRelicExporter, relic::StarRailRelic}; +use yas::export::YasExporter; +use yas::window_info::window_info_repository::WindowInfoRepository; +use yas::utils; + +fn main() -> Result<()> { + env_logger::Builder::new() + .filter_level(log::LevelFilter::Info) + .init(); + + let mut builder = ArgumentsBuilder::new(); + // setup arguments + ::modify_arguments(&mut builder); + ::modify_arguments(&mut builder); + let cmd = Command::new("yas-starrail-relic") + .version("0.1.14") // todo + .author("wormtql <584130248@qq.com>") + .about("Honkai: Star Rail Relic Scanner"); + let cmd = builder.build(cmd); + let matches = cmd.get_matches(); + + // get game info + let game_info = GameInfoBuilder::new() + .add_local_window_name("崩坏:星穹铁道") + .add_local_window_name("Honkai: Star Rail") + .add_cloud_window_name("云·星穹铁道") + .build(); + let game_info = match game_info { + Err(e) => { + error!("{}", e); + utils::quit() + }, + Ok(v) => v + }; + info!("{:?}", game_info); + + // setup window info + let window_info = { + let mut window_info_builder = WindowInfoBuilder::new(); + ::require_window_info(&mut window_info_builder); + + let mut window_info_prototypes = WindowInfoPrototypes::new(); + window_info_prototypes.insert(load_window_info!("../window_info/windows16x9.json")); + + let resolution = game_info.window.size(); + let mut wi = window_info_builder.build(&window_info_prototypes, resolution).unwrap(); + wi.add_pos("window_origin_pos", game_info.window.origin()); + + wi + }; + + // setup config + let config = StarRailRelicScannerConfig::from_arg_matches(&matches).unwrap(); + + // setup scanner + let mut scanner = StarRailRelicScanner::new(config, &window_info, game_info.clone()); + + // run + #[cfg(target_os = "macos")] + { + info!("初始化完成,请切换到崩坏:星穹铁道窗口,Yas 将在 5s 后开始扫描"); + utils::sleep(5000); + } + let results = scanner.scan()?; + + // export + let mut exporter = StarRailRelicExporter::from_arg_matches(&matches)?; + let starrail_relics = results.iter() + .map(|x| StarRailRelic::try_from(x)) + .filter(|x| x.is_ok()) + .map(|x| x.unwrap()) + .collect::>(); + exporter.results = Some(&starrail_relics); + let mut export_assets = ExportAssets::new(); + exporter.export(&mut export_assets); + let export_stats = export_assets.save(); + info!("{}", export_stats); + + info!("Yas 识别结束,共识别到 {} 件遗器。", results.len()); + + Ok(()) +} \ No newline at end of file diff --git a/yas-starrail/src/relic/mod.rs b/yas-starrail/src/relic/mod.rs new file mode 100644 index 00000000..e7738e61 --- /dev/null +++ b/yas-starrail/src/relic/mod.rs @@ -0,0 +1,7 @@ +mod relic; + +pub use relic::RelicSlot; +pub use relic::RelicSetName; +pub use relic::RelicStat; +pub use relic::RelicStatName; +pub use relic::StarRailRelic; \ No newline at end of file diff --git a/src/artifact/internal_relic.rs b/yas-starrail/src/relic/relic.rs similarity index 69% rename from src/artifact/internal_relic.rs rename to yas-starrail/src/relic/relic.rs index 14dcab89..4d725c60 100644 --- a/src/artifact/internal_relic.rs +++ b/yas-starrail/src/relic/relic.rs @@ -1,8 +1,8 @@ +use log::error; use regex::Regex; use std::hash::{Hash, Hasher}; -use edit_distance; -use log::error; use strum_macros::Display; +use crate::scanner::relic_scanner::StarRailRelicScanResult; #[derive(Debug, Hash, Clone, PartialEq, Eq)] pub enum RelicStatName { @@ -39,8 +39,7 @@ pub enum RelicSlot { LinkRope, } -#[derive(Debug, Hash, Clone, PartialEq, Eq)] -#[derive(Display)] +#[derive(Debug, Hash, Clone, PartialEq, Eq, Display)] pub enum RelicSetName { PasserbyofWanderingCloud, MusketeerofWildWheat, @@ -54,9 +53,16 @@ pub enum RelicSetName { EagleofTwilightLine, ThiefofShootingMeteor, WastelanderofBanditryDesert, + LongevousDisciple, + MessengerTraversingHackerspace, + TheAshblazingGrandDuke, + PrisonerinDeepConfinement, + PioneerDiverofDeadWaters, + WatchmakerMasterofDreamMachinations, + SpaceSealingStation, FleetoftheAgeless, - PanGalacticCommercialEnterprise, + PanCosmicCommercialEnterprise, BelobogoftheArchitects, CelestialDifferentiator, InertSalsotto, @@ -64,8 +70,8 @@ pub enum RelicSetName { SprightlyVonwacq, RutilantArena, BrokenKeel, - LongevousDisciple, - MessengerTraversingHackerspace, + FirmamentFrontlineGlamoth, + PenaconyLandoftheDreams, } #[derive(Debug, Clone)] @@ -75,17 +81,19 @@ pub struct RelicStat { } #[derive(Debug, Hash, Clone, PartialEq, Eq)] -pub struct InternalRelic { +pub struct StarRailRelic { pub set_name: RelicSetName, pub slot: RelicSlot, - pub star: u32, - pub level: u32, + pub star: i32, + pub level: i32, pub main_stat: RelicStat, pub sub_stat_1: Option, pub sub_stat_2: Option, pub sub_stat_3: Option, pub sub_stat_4: Option, pub equip: Option, + pub lock: bool, + pub discard: bool, } impl Hash for RelicStat { @@ -112,6 +120,7 @@ impl PartialEq for RelicStat { impl Eq for RelicStat {} impl RelicStatName { + #[rustfmt::skip] pub fn from_zh_cn(name: &str, is_percentage: bool) -> Option { match name { "生命值" => if is_percentage { Some(RelicStatName::HPPercentage) } else { Some(RelicStatName::HP) }, @@ -140,12 +149,12 @@ impl RelicStatName { impl RelicStat { // e.g "生命值+4,123", "暴击率+10%" pub fn from_zh_cn_raw(s: &str) -> Option { - let temp: Vec<&str> = s.split("+").collect(); + let temp: Vec<&str> = s.split('+').collect(); if temp.len() != 2 { return None; } - let is_percentage = temp[1].contains("%"); + let is_percentage = temp[1].contains('%'); let stat_name = match RelicStatName::from_zh_cn(temp[0], is_percentage) { Some(v) => v, None => return None, @@ -170,62 +179,50 @@ impl RelicStat { } } -pub fn get_real_relic_name_chs(raw: &str) -> Option { - let all_relic_chs = [ - "过客的逢春木簪", "过客的游龙臂鞲", "过客的残绣风衣", "过客的冥途游履", - "快枪手的野穗毡帽", "快枪手的粗革手套", "快枪手的猎风披肩", "快枪手的铆钉马靴", - "圣骑的宽恕盔面", "圣骑的沉默誓环", "圣骑的肃穆胸甲", "圣骑的秩序铁靴", - "雪猎的荒神兜帽", "雪猎的巨蜥手套", "雪猎的冰龙披风", "雪猎的鹿皮软靴", - "拳王的冠军护头", "拳王的重炮拳套", "拳王的贴身护胸", "拳王的弧步战靴", - "铁卫的铸铁面盔", "铁卫的银鳞手甲", "铁卫的旧制军服", "铁卫的白银护胫", - "火匠的黑耀目镜", "火匠的御火戒指", "火匠的阻燃围裙", "火匠的合金义肢", - "天才的超距遥感", "天才的频变捕手", "天才的元域深潜", "天才的引力漫步", - "乐队的偏光墨镜", "乐队的巡演手绳", "乐队的钉刺皮衣", "乐队的铆钉短靴", - "翔鹰的长喙头盔", "翔鹰的鹰击指环", "翔鹰的翼装束带", "翔鹰的绒羽绑带", - "怪盗的千人假面", "怪盗的绘纹手套", "怪盗的纤钢爪钩", "怪盗的流星快靴", - "废土客的呼吸面罩", "废土客的荒漠终端", "废土客的修士长袍", "废土客的动力腿甲", - "「黑塔」的空间站点", "「黑塔」的漫历轨迹", - "罗浮仙舟的天外楼船", "罗浮仙舟的建木枝蔓", - "公司的巨构总部", "公司的贸易航道", - "贝洛伯格的存护堡垒", "贝洛伯格的铁卫防线", - "螺丝星的机械烈阳", "螺丝星的环星孔带", - "萨尔索图的移动城市", "萨尔索图的晨昏界线", - "塔利亚的钉壳小镇", "塔利亚的裸皮电线", - "翁瓦克的诞生之岛", "翁瓦克的环岛海岸", - "泰科铵的镭射球场", "泰科铵的弧光赛道", - "伊须磨洲的残船鲸落", "伊须磨洲的坼裂缆索", - "莳者的复明义眼", "莳者的机巧木手", "莳者的承露羽衣", "莳者的天人丝履", - "信使的全息目镜", "信使的百变义手", "信使的密信挎包", "信使的酷跑板鞋", - ]; +impl TryFrom<&StarRailRelicScanResult> for StarRailRelic { + type Error = (); - let mut min_index = 0; - let mut min_dis = edit_distance::edit_distance(raw, all_relic_chs[0]); - let mut same_flag = false; - for (i, &val) in all_relic_chs.iter().enumerate().skip(1) { - let dis = edit_distance::edit_distance(val, raw); - if dis < min_dis { - min_dis = dis; - min_index = i; - same_flag = false; - } else if dis == min_dis { - same_flag = true; - } - } + fn try_from(value: &StarRailRelicScanResult) -> Result { + let set_name = RelicSetName::from_zh_cn(&value.name).ok_or(())?; + let slot = RelicSlot::from_zh_cn(&value.name).ok_or(())?; + + let main_stat = RelicStat::from_zh_cn_raw( + (value.main_stat_name.clone() + "+" + value.main_stat_value.as_str()).as_str(), + ) + .ok_or(())?; + let sub1 = RelicStat::from_zh_cn_raw(&(value.sub_stat_name[0].clone() + "+" + value.sub_stat_value[0].as_str())); + let sub2 = RelicStat::from_zh_cn_raw(&(value.sub_stat_name[1].clone() + "+" + value.sub_stat_value[1].as_str())); + let sub3 = RelicStat::from_zh_cn_raw(&(value.sub_stat_name[2].clone() + "+" + value.sub_stat_value[2].as_str())); + let sub4 = RelicStat::from_zh_cn_raw(&(value.sub_stat_name[3].clone() + "+" + value.sub_stat_value[3].as_str())); - if same_flag { - None - } else { - Some(String::from(all_relic_chs[min_index])) + let equip = if value.equip.ends_with("装备中") { + let chars = value.equip.chars().collect::>(); + let equip_name = chars[..chars.len() - 3].iter().collect::(); + Some(equip_name) + } else { + None + }; + + Ok(StarRailRelic { + set_name, + slot, + star: value.star, + level: value.level, + main_stat, + sub_stat_1: sub1, + sub_stat_2: sub2, + sub_stat_3: sub3, + sub_stat_4: sub4, + equip, + lock: value.lock, + discard: value.discard, + }) } } impl RelicSetName { + #[rustfmt::skip] pub fn from_zh_cn(s: &str) -> Option { - // let s = match get_real_relic_name_chs(s) { - // Some(v) => v, - // None => return None, - // }; - // println!("name: {}", s); match s { "过客的逢春木簪" | "过客的游龙臂鞲" | "过客的残绣风衣" | "过客的冥途游履" => Some(RelicSetName::PasserbyofWanderingCloud), "快枪手的野穗毡帽" | "快枪手的粗革手套" | "快枪手的猎风披肩" | "快枪手的铆钉马靴" => Some(RelicSetName::MusketeerofWildWheat), @@ -239,38 +236,41 @@ impl RelicSetName { "翔鹰的长喙头盔" | "翔鹰的鹰击指环" | "翔鹰的翼装束带" | "翔鹰的绒羽绑带" => Some(RelicSetName::EagleofTwilightLine), "怪盗的千人假面" | "怪盗的绘纹手套" | "怪盗的纤钢爪钩" | "怪盗的流星快靴" => Some(RelicSetName::ThiefofShootingMeteor), "废土客的呼吸面罩" | "废土客的荒漠终端" | "废土客的修士长袍" | "废土客的动力腿甲" => Some(RelicSetName::WastelanderofBanditryDesert), + "莳者的复明义眼" | "莳者的机巧木手" | "莳者的承露羽衣" | "莳者的天人丝履" => Some(RelicSetName::LongevousDisciple), + "信使的全息目镜" | "信使的百变义手" | "信使的密信挎包" | "信使的酷跑板鞋" => Some(RelicSetName::MessengerTraversingHackerspace), + "大公的冥焰冠冕" | "大公的绒火指套" | "大公的蒙恩长袍" | "大公的绅雅礼靴" => Some(RelicSetName::TheAshblazingGrandDuke), + "系囚的合啮拘笼" | "系囚的铅石梏铐" | "系囚的幽闭缚束" | "系囚的绝足锁桎" => Some(RelicSetName::PrisonerinDeepConfinement), + "先驱的绝热围壳" | "先驱的虚极罗盘" | "先驱的密合铅衣" | "先驱的泊星桩锚" => Some(RelicSetName::PioneerDiverofDeadWaters), + "钟表匠的极目透镜" | "钟表匠的交运腕表" | "钟表匠的空幻礼服" | "钟表匠的隐梦革履" => Some(RelicSetName::WatchmakerMasterofDreamMachinations), + "「黑塔」的空间站点" | "「黑塔」的漫历轨迹" => Some(RelicSetName::SpaceSealingStation), "罗浮仙舟的天外楼船" | "罗浮仙舟的建木枝蔓" => Some(RelicSetName::FleetoftheAgeless), - "公司的巨构总部" | "公司的贸易航道" => Some(RelicSetName::PanGalacticCommercialEnterprise), + "公司的巨构总部" | "公司的贸易航道" => Some(RelicSetName::PanCosmicCommercialEnterprise), "贝洛伯格的存护堡垒" | "贝洛伯格的铁卫防线" => Some(RelicSetName::BelobogoftheArchitects), "螺丝星的机械烈阳" | "螺丝星的环星孔带" => Some(RelicSetName::CelestialDifferentiator), "萨尔索图的移动城市" | "萨尔索图的晨昏界线" => Some(RelicSetName::InertSalsotto), "塔利亚的钉壳小镇" | "塔利亚的裸皮电线" => Some(RelicSetName::TaliaKingdomofBanditry), - "翁瓦克的诞生之岛" | "翁瓦克的环岛海岸" => Some(RelicSetName::FleetoftheAgeless), + "翁瓦克的诞生之岛" | "翁瓦克的环岛海岸" => Some(RelicSetName::SprightlyVonwacq), "泰科铵的镭射球场" | "泰科铵的弧光赛道" => Some(RelicSetName::RutilantArena), "伊须磨洲的残船鲸落" | "伊须磨洲的坼裂缆索" => Some(RelicSetName::BrokenKeel), - "莳者的复明义眼" | "莳者的机巧木手" | "莳者的承露羽衣" | "莳者的天人丝履" => Some(RelicSetName::LongevousDisciple), - "信使的全息目镜" | "信使的百变义手" | "信使的密信挎包" | "信使的酷跑板鞋" => Some(RelicSetName::MessengerTraversingHackerspace), + "格拉默的铁骑兵团" | "格拉默的寂静坟碑" => Some(RelicSetName::FirmamentFrontlineGlamoth), + "匹诺康尼的堂皇酒店" | "匹诺康尼的逐梦轨道" => Some(RelicSetName::PenaconyLandoftheDreams), _ => None, } } } impl RelicSlot { + #[rustfmt::skip] pub fn from_zh_cn(s: &str) -> Option { - // let s = match get_real_relic_name_chs(s) { - // Some(v) => v, - // None => return None, - // }; match s { - "过客的逢春木簪" | "快枪手的野穗毡帽" | "圣骑的宽恕盔面" | "雪猎的荒神兜帽" | "拳王的冠军护头" | "铁卫的铸铁面盔" | "火匠的黑耀目镜" | "天才的超距遥感" | "乐队的偏光墨镜" | "翔鹰的长喙头盔" | "怪盗的千人假面" | "废土客的呼吸面罩" | "莳者的复明义眼" | "信使的全息目镜" => Some(RelicSlot::Head), - "过客的游龙臂鞲" | "快枪手的粗革手套" | "圣骑的沉默誓环" | "雪猎的巨蜥手套" | "拳王的重炮拳套" | "铁卫的银鳞手甲" | "火匠的御火戒指" | "天才的频变捕手" | "乐队的巡演手绳" | "翔鹰的鹰击指环" | "怪盗的绘纹手套" | "废土客的荒漠终端" | "莳者的机巧木手" | "信使的百变义手" => Some(RelicSlot::Hands), - "过客的残绣风衣" | "快枪手的猎风披肩" | "圣骑的肃穆胸甲" | "雪猎的冰龙披风" | "拳王的贴身护胸" | "铁卫的旧制军服" | "火匠的阻燃围裙" | "天才的元域深潜" | "乐队的钉刺皮衣" | "翔鹰的翼装束带" | "怪盗的纤钢爪钩" | "废土客的修士长袍" | "莳者的承露羽衣" | "信使的密信挎包" => Some(RelicSlot::Body), - "过客的冥途游履" | "快枪手的铆钉马靴" | "圣骑的秩序铁靴" | "雪猎的鹿皮软靴" | "拳王的弧步战靴" | "铁卫的白银护胫" | "火匠的合金义肢" | "天才的引力漫步" | "乐队的铆钉短靴" | "翔鹰的绒羽绑带" | "怪盗的流星快靴" | "废土客的动力腿甲" | "莳者的天人丝履" | "信使的酷跑板鞋" => Some(RelicSlot::Feet), - "「黑塔」的空间站点" | "罗浮仙舟的天外楼船" | "公司的巨构总部" | "贝洛伯格的存护堡垒" | "螺丝星的机械烈阳" | "萨尔索图的移动城市" | "塔利亚的钉壳小镇" | "翁瓦克的诞生之岛" | "泰科铵的镭射球场" | "伊须磨洲的残船鲸落" => Some(RelicSlot::PlanarSphere), - "「黑塔」的漫历轨迹" | "罗浮仙舟的建木枝蔓" | "公司的贸易航道" | "贝洛伯格的铁卫防线" | "螺丝星的环星孔带" | "萨尔索图的晨昏界线" | "塔利亚的裸皮电线" | "翁瓦克的环岛海岸" | "泰科铵的弧光赛道" | "伊须磨洲的坼裂缆索" => Some(RelicSlot::LinkRope), + "过客的逢春木簪" | "快枪手的野穗毡帽" | "圣骑的宽恕盔面" | "雪猎的荒神兜帽" | "拳王的冠军护头" | "铁卫的铸铁面盔" | "火匠的黑耀目镜" | "天才的超距遥感" | "乐队的偏光墨镜" | "翔鹰的长喙头盔" | "怪盗的千人假面" | "废土客的呼吸面罩" | "莳者的复明义眼" | "信使的全息目镜" | "大公的冥焰冠冕" | "系囚的合啮拘笼" | "先驱的绝热围壳" | "钟表匠的极目透镜" => Some(RelicSlot::Head), + "过客的游龙臂鞲" | "快枪手的粗革手套" | "圣骑的沉默誓环" | "雪猎的巨蜥手套" | "拳王的重炮拳套" | "铁卫的银鳞手甲" | "火匠的御火戒指" | "天才的频变捕手" | "乐队的巡演手绳" | "翔鹰的鹰击指环" | "怪盗的绘纹手套" | "废土客的荒漠终端" | "莳者的机巧木手" | "信使的百变义手" | "大公的绒火指套" | "系囚的铅石梏铐" | "先驱的虚极罗盘" | "钟表匠的交运腕表" => Some(RelicSlot::Hands), + "过客的残绣风衣" | "快枪手的猎风披肩" | "圣骑的肃穆胸甲" | "雪猎的冰龙披风" | "拳王的贴身护胸" | "铁卫的旧制军服" | "火匠的阻燃围裙" | "天才的元域深潜" | "乐队的钉刺皮衣" | "翔鹰的翼装束带" | "怪盗的纤钢爪钩" | "废土客的修士长袍" | "莳者的承露羽衣" | "信使的密信挎包" | "大公的蒙恩长袍" | "系囚的幽闭缚束" | "先驱的密合铅衣" | "钟表匠的空幻礼服" => Some(RelicSlot::Body), + "过客的冥途游履" | "快枪手的铆钉马靴" | "圣骑的秩序铁靴" | "雪猎的鹿皮软靴" | "拳王的弧步战靴" | "铁卫的白银护胫" | "火匠的合金义肢" | "天才的引力漫步" | "乐队的铆钉短靴" | "翔鹰的绒羽绑带" | "怪盗的流星快靴" | "废土客的动力腿甲" | "莳者的天人丝履" | "信使的酷跑板鞋" | "大公的绅雅礼靴" | "系囚的绝足锁桎" | "先驱的泊星桩锚" | "钟表匠的隐梦革履" => Some(RelicSlot::Feet), + "「黑塔」的空间站点" | "罗浮仙舟的天外楼船" | "公司的巨构总部" | "贝洛伯格的存护堡垒" | "螺丝星的机械烈阳" | "萨尔索图的移动城市" | "塔利亚的钉壳小镇" | "翁瓦克的诞生之岛" | "泰科铵的镭射球场" | "伊须磨洲的残船鲸落" | "格拉默的铁骑兵团" | "匹诺康尼的堂皇酒店" => Some(RelicSlot::PlanarSphere), + "「黑塔」的漫历轨迹" | "罗浮仙舟的建木枝蔓" | "公司的贸易航道" | "贝洛伯格的铁卫防线" | "螺丝星的环星孔带" | "萨尔索图的晨昏界线" | "塔利亚的裸皮电线" | "翁瓦克的环岛海岸" | "泰科铵的弧光赛道" | "伊须磨洲的坼裂缆索" | "格拉默的寂静坟碑" | "匹诺康尼的逐梦轨道" => Some(RelicSlot::LinkRope), _ => None, } } -} - +} \ No newline at end of file diff --git a/yas-starrail/src/scanner/mod.rs b/yas-starrail/src/scanner/mod.rs new file mode 100644 index 00000000..327972b5 --- /dev/null +++ b/yas-starrail/src/scanner/mod.rs @@ -0,0 +1 @@ +pub mod relic_scanner; \ No newline at end of file diff --git a/yas-starrail/src/scanner/relic_scanner/mod.rs b/yas-starrail/src/scanner/relic_scanner/mod.rs new file mode 100644 index 00000000..88a99fa6 --- /dev/null +++ b/yas-starrail/src/scanner/relic_scanner/mod.rs @@ -0,0 +1,5 @@ +mod relic_scanner; +mod relic_scanner_config; + +pub use relic_scanner::{StarRailRelicScanResult, StarRailRelicScanner}; +pub use relic_scanner_config::StarRailRelicScannerConfig; \ No newline at end of file diff --git a/yas-starrail/src/scanner/relic_scanner/models/index_2_word.json b/yas-starrail/src/scanner/relic_scanner/models/index_2_word.json new file mode 100644 index 00000000..1b39e379 --- /dev/null +++ b/yas-starrail/src/scanner/relic_scanner/models/index_2_word.json @@ -0,0 +1,449 @@ +{ + "0": "-", + "1": " ", + "2": "%", + "3": "&", + "4": "'", + "5": "+", + "6": ",", + "7": ".", + "8": "/", + "9": "0", + "10": "1", + "11": "2", + "12": "3", + "13": "4", + "14": "5", + "15": "6", + "16": "7", + "17": "8", + "18": "9", + "19": "•", + "20": "「", + "21": "」", + "22": "七", + "23": "三", + "24": "丝", + "25": "中", + "26": "丹", + "27": "乃", + "28": "义", + "29": "之", + "30": "乐", + "31": "云", + "32": "亚", + "33": "交", + "34": "人", + "35": "仙", + "36": "伊", + "37": "伤", + "38": "伯", + "39": "佩", + "40": "使", + "41": "信", + "42": "修", + "43": "值", + "44": "假", + "45": "偏", + "46": "停", + "47": "儿", + "48": "元", + "49": "先", + "50": "光", + "51": "克", + "52": "兜", + "53": "全", + "54": "公", + "55": "兰", + "56": "兵", + "57": "冕", + "58": "军", + "59": "冠", + "60": "冥", + "61": "冰", + "62": "击", + "63": "刃", + "64": "利", + "65": "制", + "66": "刹", + "67": "刺", + "68": "力", + "69": "加", + "70": "动", + "71": "包", + "72": "匠", + "73": "匹", + "74": "医", + "75": "千", + "76": "博", + "77": "卡", + "78": "卢", + "79": "卫", + "80": "卿", + "81": "历", + "82": "变", + "83": "可", + "84": "司", + "85": "合", + "86": "吸", + "87": "呼", + "88": "命", + "89": "啮", + "90": "喙", + "91": "器", + "92": "囚", + "93": "团", + "94": "围", + "95": "图", + "96": "土", + "97": "圣", + "98": "场", + "99": "坟", + "100": "坼", + "101": "垒", + "102": "城", + "103": "域", + "104": "堂", + "105": "堡", + "106": "塔", + "107": "墨", + "108": "士", + "109": "壳", + "110": "备", + "111": "复", + "112": "外", + "113": "大", + "114": "天", + "115": "头", + "116": "套", + "117": "妮", + "118": "妲", + "119": "姬", + "120": "娅", + "121": "娜", + "122": "子", + "123": "孔", + "124": "存", + "125": "客", + "126": "害", + "127": "宽", + "128": "寂", + "129": "密", + "130": "寒", + "131": "射", + "132": "小", + "133": "尔", + "134": "尼", + "135": "属", + "136": "履", + "137": "岛", + "138": "岸", + "139": "巡", + "140": "巧", + "141": "巨", + "142": "市", + "143": "布", + "144": "希", + "145": "帕", + "146": "带", + "147": "帽", + "148": "幻", + "149": "幽", + "150": "序", + "151": "店", + "152": "废", + "153": "度", + "154": "康", + "155": "建", + "156": "引", + "157": "弧", + "158": "彦", + "159": "御", + "160": "德", + "161": "快", + "162": "性", + "163": "怪", + "164": "总", + "165": "恒", + "166": "恕", + "167": "恢", + "168": "恩", + "169": "息", + "170": "感", + "171": "成", + "172": "戒", + "173": "战", + "174": "手", + "175": "才", + "176": "托", + "177": "承", + "178": "抗", + "179": "护", + "180": "披", + "181": "抵", + "182": "拉", + "183": "拘", + "184": "拳", + "185": "指", + "186": "挎", + "187": "捕", + "188": "提", + "189": "攻", + "190": "效", + "191": "数", + "192": "旧", + "193": "明", + "194": "昏", + "195": "易", + "196": "星", + "197": "春", + "198": "晨", + "199": "景", + "200": "暴", + "201": "月", + "202": "服", + "203": "木", + "204": "机", + "205": "束", + "206": "杰", + "207": "板", + "208": "极", + "209": "构", + "210": "果", + "211": "枝", + "212": "枪", + "213": "格", + "214": "桂", + "215": "桎", + "216": "桑", + "217": "桩", + "218": "梅", + "219": "梏", + "220": "梦", + "221": "械", + "222": "楼", + "223": "步", + "224": "残", + "225": "毡", + "226": "沉", + "227": "沙", + "228": "治", + "229": "泊", + "230": "泰", + "231": "洛", + "232": "洲", + "233": "流", + "234": "浮", + "235": "海", + "236": "深", + "237": "游", + "238": "演", + "239": "漠", + "240": "漫", + "241": "潜", + "242": "火", + "243": "炮", + "244": "点", + "245": "烈", + "246": "热", + "247": "焰", + "248": "燃", + "249": "爪", + "250": "物", + "251": "特", + "252": "狼", + "253": "猎", + "254": "玄", + "255": "率", + "256": "王", + "257": "环", + "258": "玲", + "259": "球", + "260": "理", + "261": "瓦", + "262": "生", + "263": "甲", + "264": "电", + "265": "界", + "266": "疗", + "267": "白", + "268": "百", + "269": "的", + "270": "皇", + "271": "皮", + "272": "盔", + "273": "盗", + "274": "盘", + "275": "目", + "276": "真", + "277": "眼", + "278": "短", + "279": "石", + "280": "破", + "281": "碑", + "282": "磨", + "283": "礼", + "284": "神", + "285": "科", + "286": "秩", + "287": "移", + "288": "穆", + "289": "穗", + "290": "空", + "291": "站", + "292": "端", + "293": "符", + "294": "笼", + "295": "簪", + "296": "米", + "297": "粗", + "298": "系", + "299": "素", + "300": "索", + "301": "纤", + "302": "纹", + "303": "线", + "304": "绅", + "305": "终", + "306": "绑", + "307": "绒", + "308": "绘", + "309": "绝", + "310": "绣", + "311": "绳", + "312": "缆", + "313": "缚", + "314": "罗", + "315": "罩", + "316": "羽", + "317": "翁", + "318": "翔", + "319": "翼", + "320": "耀", + "321": "者", + "322": "肃", + "323": "肢", + "324": "肩", + "325": "胫", + "326": "胸", + "327": "能", + "328": "腕", + "329": "腿", + "330": "臂", + "331": "舟", + "332": "航", + "333": "船", + "334": "艾", + "335": "芙", + "336": "芬", + "337": "花", + "338": "荒", + "339": "莎", + "340": "莳", + "341": "萨", + "342": "落", + "343": "蒙", + "344": "蔓", + "345": "藿", + "346": "虎", + "347": "虚", + "348": "蜥", + "349": "螺", + "350": "衣", + "351": "表", + "352": "袍", + "353": "裂", + "354": "装", + "355": "裙", + "356": "裳", + "357": "裸", + "358": "誓", + "359": "诞", + "360": "诺", + "361": "贝", + "362": "账", + "363": "贴", + "364": "贸", + "365": "赛", + "366": "超", + "367": "足", + "368": "跑", + "369": "距", + "370": "身", + "371": "轨", + "372": "软", + "373": "过", + "374": "运", + "375": "迹", + "376": "透", + "377": "逐", + "378": "途", + "379": "速", + "380": "逢", + "381": "道", + "382": "遗", + "383": "遥", + "384": "部", + "385": "酒", + "386": "酷", + "387": "重", + "388": "野", + "389": "量", + "390": "金", + "391": "钉", + "392": "钟", + "393": "钢", + "394": "钩", + "395": "铁", + "396": "铅", + "397": "铆", + "398": "铐", + "399": "铵", + "400": "银", + "401": "铸", + "402": "锁", + "403": "锚", + "404": "镇", + "405": "镜", + "406": "镭", + "407": "长", + "408": "闭", + "409": "间", + "410": "队", + "411": "阮", + "412": "防", + "413": "阳", + "414": "阻", + "415": "阿", + "416": "隐", + "417": "雀", + "418": "雅", + "419": "雪", + "420": "雷", + "421": "露", + "422": "青", + "423": "静", + "424": "面", + "425": "革", + "426": "靴", + "427": "鞋", + "428": "鞲", + "429": "须", + "430": "频", + "431": "风", + "432": "饮", + "433": "马", + "434": "驭", + "435": "驱", + "436": "骑", + "437": "高", + "438": "鲸", + "439": "鳞", + "440": "鸦", + "441": "鹅", + "442": "鹰", + "443": "鹿", + "444": "黑", + "445": "默", + "446": "龙" +} \ No newline at end of file diff --git a/yas-starrail/src/scanner/relic_scanner/models/model_training.onnx b/yas-starrail/src/scanner/relic_scanner/models/model_training.onnx new file mode 100644 index 00000000..9d93ed4c --- /dev/null +++ b/yas-starrail/src/scanner/relic_scanner/models/model_training.onnx @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:88247b0abfdc032af4f353c3b0f4933d01343f5c385c4e635a27493c8ca99007 +size 4668704 diff --git a/yas-starrail/src/scanner/relic_scanner/relic_scanner.rs b/yas-starrail/src/scanner/relic_scanner/relic_scanner.rs new file mode 100644 index 00000000..a5178b01 --- /dev/null +++ b/yas-starrail/src/scanner/relic_scanner/relic_scanner.rs @@ -0,0 +1,611 @@ +use image::{RgbImage, GenericImageView}; +use log::{error, info, warn}; +use yas::{capture::capture::{self, RelativeCapturable}, common::{color::Color, positioning::{Rect, Pos}}, window_info::{require_window_info::RequireWindowInfo, window_info_repository::WindowInfoRepository}, inference::{model::OCRModel, pre_process::{pre_process, to_gray, ImageConvExt}}, game_info::GameInfo}; +use std::{ops::{Coroutine, CoroutineState}, pin::Pin, rc::Rc, cell::RefCell, sync::{mpsc::{Receiver, Sender, self}, Arc}, thread::JoinHandle, os::windows::thread, collections::HashSet, time::SystemTime}; + +use crate::scanner_controller::repository_layout::{scan_logic::{StarRailRepositoryScanController, ReturnResult}, config::StarRailRepositoryScannerLogicConfig}; + +use super::relic_scanner_config::StarRailRelicScannerConfig; +use anyhow::Result; + +#[derive(Debug, Hash, Clone, PartialEq, Eq)] +pub struct StarRailRelicScanResult { + pub name: String, + pub main_stat_name: String, + pub main_stat_value: String, + pub sub_stat_name: [String; 4], + pub sub_stat_value: [String; 4], + pub equip: String, + pub level: i32, + pub star: i32, + pub lock: bool, + pub discard: bool, +} + +struct RelicScannerWorker { + model: OCRModel, + window_info: RelicScannerWindowInfo, + config: StarRailRelicScannerConfig, +} + +fn parse_level(s: &str) -> Result { + let pos = s.find('+'); + + if pos.is_none() { + let level = s.parse::()?; + return Ok(level); + } + + let level = s[pos.unwrap()..].parse::()?; + return Ok(level); +} + +impl RelicScannerWorker { + pub fn new( + model: OCRModel, + window_info: RelicScannerWindowInfo, + config: StarRailRelicScannerConfig, + ) -> Self { + RelicScannerWorker { + model, + window_info, + config, + } + } + + fn model_inference(&self, rect: Rect, captured_img: &RgbImage) -> Result { + // todo move dump mode into a scanner + // if dump_mode { + // captured_img.save(Path::new("dumps").join(format!("{}_{}.rgb.png", name, cnt)))?; + // } + + let relative_rect = rect.translate(Pos { + x: -self.window_info.panel_rect.left, + y: -self.window_info.panel_rect.top, + }); + + let raw_img = captured_img.view( + relative_rect.left as u32, relative_rect.top as u32, relative_rect.width as u32, relative_rect.height as u32 + ).to_image(); + let raw_img_grayed = to_gray(&raw_img); + + // let raw_img = to_gray(captured_img) + // .view( + // relative_rect.left, + // relative_rect.top, + // rect.size.width, + // rect.size.height, + // ) + // .to_image(); + + // if dump_mode { + // raw_img + // .to_common_grayscale() + // .save(Path::new("dumps").join(format!("{}_{}.rgb.png", name, cnt)))?; + // } + + let (processed_img, process_flag) = pre_process(raw_img_grayed); + if !process_flag { + return Ok(String::new()); + } + + // if dump_mode { + // processed_img + // .to_common_grayscale() + // .save(Path::new("dumps").join(format!("{}_{}.pp.png", name, cnt)))?; + // } + + let inference_result = self.model.inference_string(&processed_img)?; + + // if dump_mode { + // dump_text( + // &inference_result, + // Path::new("dumps").join(format!("{}_{}.txt", name, cnt)), + // ); + // } + + Ok(inference_result) + } + + fn scan_item_image(&self, item: SendItem) -> Result { + let image = &item.panel_image; + + let str_title = self.model_inference(self.window_info.title_rect, &image)?; + let str_main_stat_name = self.model_inference(self.window_info.main_stat_name_rect, &image)?; + let str_main_stat_value = self.model_inference(self.window_info.main_stat_value_rect, &image)?; + + let str_sub_stat0_name = self.model_inference(self.window_info.sub_stat_name_rect[0], &image)?; + let str_sub_stat1_name = self.model_inference(self.window_info.sub_stat_name_rect[1], &image)?; + let str_sub_stat2_name = self.model_inference(self.window_info.sub_stat_name_rect[2], &image)?; + let str_sub_stat3_name = self.model_inference(self.window_info.sub_stat_name_rect[3], &image)?; + let str_sub_stat0_value = self.model_inference(self.window_info.sub_stat_value_rect[0], &image)?; + let str_sub_stat1_value = self.model_inference(self.window_info.sub_stat_value_rect[1], &image)?; + let str_sub_stat2_value = self.model_inference(self.window_info.sub_stat_value_rect[2], &image)?; + let str_sub_stat3_value = self.model_inference(self.window_info.sub_stat_value_rect[3], &image)?; + + let str_level = self.model_inference(self.window_info.level_rect, &image)?; + let str_equip = self.model_inference(self.window_info.equip_rect, &image)?; + + Ok(StarRailRelicScanResult { + name: str_title, + main_stat_name: str_main_stat_name, + main_stat_value: str_main_stat_value, + sub_stat_name: [ + str_sub_stat0_name, + str_sub_stat1_name, + str_sub_stat2_name, + str_sub_stat3_name, + ], + sub_stat_value: [ + str_sub_stat0_value, + str_sub_stat1_value, + str_sub_stat2_value, + str_sub_stat3_value, + ], + level: parse_level(&str_level)?, + equip: item.equip + &str_equip, + star: item.star as i32, + lock: item.lock, + discard: item.discard, + }) + } + + pub fn run(self, rx: Receiver>) -> JoinHandle> { + std::thread::spawn(move || { + let mut results = Vec::new(); + let mut hash = HashSet::new(); + let mut consecutive_dup_count = 0; + + let is_verbose = self.config.verbose; + let min_level = self.config.min_level; + let info = self.window_info.clone(); + // todo remove dump mode to another scanner + // let dump_mode = false; + // let model = self.model.clone(); + // let panel_origin = Pos { x: self.window_info.panel_rect.left, y: self.window_info.panel_rect.top }; + + for (_cnt, item) in rx.into_iter().enumerate() { + let item = match item { + Some(v) => v, + None => break, + }; + + let result = match self.scan_item_image(item) { + Ok(v) => v, + Err(e) => { + error!("识别错误: {}", e); + continue; + }, + }; + + if is_verbose { + info!("{:?}", result); + } + + // progress_bar.inc(1); + // progress_bar.set_message(format!( + // "{}{}: {}", + // style(&result.name).bold().cyan(), + // style(format!("({})", result.level)).yellow(), + // style(&result.main_stat_name).dim() + // )); + + if result.level < min_level { + info!( + "找到满足最低等级要求 {} 的物品({}),准备退出……", + min_level, result.level + ); + // token.cancel(); + break; + } + + if hash.contains(&result) { + consecutive_dup_count += 1; + warn!("识别到重复物品: {:#?}", result); + } else { + consecutive_dup_count = 0; + hash.insert(result.clone()); + results.push(result); + } + + if consecutive_dup_count >= info.col && !self.config.ignore_dup { + error!("识别到连续多个重复物品,可能为翻页错误,或者为非背包顶部开始扫描"); + // token.cancel(); + break; + } + + // if token.cancelled() { + // error!("扫描任务被取消"); + // break; + // } + } + + info!("识别结束,非重复物品数量: {}", hash.len()); + + // progress_bar.finish(); + // MULTI_PROGRESS.remove(&progress_bar); + + results + }) + } +} + +#[derive(Clone)] +struct RelicScannerWindowInfo { + pub origin_pos: Pos, + + pub title_rect: Rect, + pub main_stat_name_rect: Rect, + pub main_stat_value_rect: Rect, + pub sub_stat_name_rect: [Rect; 4], + pub sub_stat_value_rect: [Rect; 4], + + pub level_rect: Rect, + + pub equip_rect: Rect, + pub equipper_pos: Pos, + pub item_count_rect: Rect, + + pub star_pos: Pos, + pub lock_pos: Pos, + pub discard_pos: Pos, + + pub panel_rect: Rect, + + pub col: i32, +} + +impl From<&WindowInfoRepository> for RelicScannerWindowInfo { + fn from(value: &WindowInfoRepository) -> Self { + RelicScannerWindowInfo { + origin_pos: value.get("window_origin_pos").unwrap(), + title_rect: value.get("starrail_relic_title_rect").unwrap(), + main_stat_name_rect: value.get("starrail_relic_main_stat_name_rect").unwrap(), + main_stat_value_rect: value.get("starrail_relic_main_stat_value_rect").unwrap(), + level_rect: value.get("starrail_relic_level_rect").unwrap(), + equip_rect: value.get("starrail_relic_equip_rect").unwrap(), + equipper_pos: value.get("starrail_relic_equipper_pos").unwrap(), + item_count_rect: value.get("starrail_relic_item_count_rect").unwrap(), + star_pos: value.get("starrail_relic_star_pos").unwrap(), + lock_pos: value.get("starrail_relic_lock_pos").unwrap(), + discard_pos: value.get("starrail_relic_discard_pos").unwrap(), + + panel_rect: value.get("starrail_repository_panel_rect").unwrap(), + col: value.get("starrail_repository_item_col").unwrap(), + + sub_stat_name_rect: [ + value.get("starrail_relic_sub_stat0_name_rect").unwrap(), + value.get("starrail_relic_sub_stat1_name_rect").unwrap(), + value.get("starrail_relic_sub_stat2_name_rect").unwrap(), + value.get("starrail_relic_sub_stat3_name_rect").unwrap(), + ], + sub_stat_value_rect: [ + value.get("starrail_relic_sub_stat0_value_rect").unwrap(), + value.get("starrail_relic_sub_stat1_value_rect").unwrap(), + value.get("starrail_relic_sub_stat2_value_rect").unwrap(), + value.get("starrail_relic_sub_stat3_value_rect").unwrap(), + ], + } + } +} + +pub struct StarRailRelicScanner { + scanner_config: StarRailRelicScannerConfig, + + window_info: RelicScannerWindowInfo, + window_info_clone: WindowInfoRepository, + + game_info: GameInfo, + + match_colors_star: [Color; 5], + match_colors_lock: [Color; 3], + match_colors_discard: [Color; 3], + match_colors_equipper: [(&'static str, Color); 43], +} + +impl RequireWindowInfo for StarRailRelicScanner { + fn require_window_info(window_info_builder: &mut yas::window_info::window_info_builder::WindowInfoBuilder) { + ::require_window_info(window_info_builder); + + // window_info_builder.add_required_key("window_origin_pos"); + window_info_builder.add_required_key("starrail_relic_title_rect"); + window_info_builder.add_required_key("starrail_relic_main_stat_name_rect"); + window_info_builder.add_required_key("starrail_relic_main_stat_value_rect"); + window_info_builder.add_required_key("starrail_relic_level_rect"); + window_info_builder.add_required_key("starrail_relic_equip_rect"); + window_info_builder.add_required_key("starrail_relic_equipper_pos"); + window_info_builder.add_required_key("starrail_relic_item_count_rect"); + window_info_builder.add_required_key("starrail_relic_star_pos"); + window_info_builder.add_required_key("starrail_relic_lock_pos"); + window_info_builder.add_required_key("starrail_relic_discard_pos"); + window_info_builder.add_required_key("starrail_repository_item_col"); + window_info_builder.add_required_key("starrail_repository_panel_rect"); + window_info_builder.add_required_key("starrail_relic_sub_stat0_name_rect"); + window_info_builder.add_required_key("starrail_relic_sub_stat1_name_rect"); + window_info_builder.add_required_key("starrail_relic_sub_stat2_name_rect"); + window_info_builder.add_required_key("starrail_relic_sub_stat3_name_rect"); + window_info_builder.add_required_key("starrail_relic_sub_stat0_value_rect"); + window_info_builder.add_required_key("starrail_relic_sub_stat1_value_rect"); + window_info_builder.add_required_key("starrail_relic_sub_stat2_value_rect"); + window_info_builder.add_required_key("starrail_relic_sub_stat3_value_rect"); + } +} + +struct SendItem { + panel_image: RgbImage, + equip: String, + star: usize, + lock: bool, + discard: bool, +} + +// constructor +impl StarRailRelicScanner { + pub fn new(config: StarRailRelicScannerConfig, window_info: &WindowInfoRepository, game_info: GameInfo) -> Self { + StarRailRelicScanner { + scanner_config: config, + window_info: RelicScannerWindowInfo::from(window_info), + window_info_clone: window_info.clone(), + game_info, + match_colors_star: [ + Color::new(113, 119, 139), // todo + Color::new(42, 143, 114), // todo + Color::new(96, 142, 197), // 3 + Color::new(157, 117, 206), // 4 + Color::new(193, 158, 112), // 5 + ], + match_colors_lock: [ + Color::new(18, 18, 18), // locked + Color::new(249, 249, 249), // unlocked + Color::new(116, 108, 99), // discard + ], + match_colors_discard: [ + Color::new(235, 77, 61), // discard + Color::new(249, 249, 249), // not discard + Color::new(115, 108, 98), // locked + ], + // todo use better match color set + match_colors_equipper: [ + ("Argenti", Color::new(216, 174, 161)), + ("Arlan", Color::new(146, 134, 124)), + ("Asta", Color::new(188, 130, 117)), + ("Bailu", Color::new(160, 127, 174)), + ("BlackSwan", Color::new(252, 242, 239)), + ("Blade", Color::new(191, 162, 162)), + ("Bronya", Color::new(83, 66, 83)), + ("Clara", Color::new(181, 107, 129)), + ("DanHeng", Color::new(124, 100, 100)), + ("DanHengImbibitorLunae", Color::new(181, 169, 163)), + ("DrRatio", Color::new(134, 120, 143)), + ("FuXuan", Color::new(231, 166, 145)), + ("Gepard", Color::new(192, 199, 223)), + ("Guinaifen", Color::new(219, 137, 111)), + ("Hanya", Color::new(247, 238, 232)), + ("Herta", Color::new(246, 239, 227)), + ("Himeko", Color::new(177, 92, 85)), + ("Hook", Color::new(190, 161, 86)), + ("Huohuo", Color::new(230, 250, 250)), + ("Jingliu", Color::new(193, 194, 218)), + ("JingYuan", Color::new(169, 154, 147)), + ("Kafka", Color::new(126, 50, 80)), + ("Luka", Color::new(218, 198, 183)), + ("Luocha", Color::new(191, 160, 116)), + ("Lynx", Color::new(247, 213, 197)), + ("March7th", Color::new(251, 243, 243)), + ("Misha", Color::new(234, 215, 213)), + ("Natasha", Color::new(238, 208, 196)), + ("Pela", Color::new(241, 217, 217)), + ("Qingque", Color::new(18, 27, 11)), + ("RuanMei", Color::new(129, 101, 101)), + ("Sampo", Color::new(241, 217, 213)), + ("Seele", Color::new(91, 65, 111)), + ("Serval", Color::new(158, 141, 150)), + ("SilverWolf", Color::new(222, 210, 210)), + ("Sushang", Color::new(101, 65, 58)), + ("Tingyun", Color::new(127, 116, 57)), + ("TopazNumby", Color::new(254, 250, 246)), + ("Trailblazer_Preservation", Color::new(153, 125, 111)), + ("Welt", Color::new(158, 114, 99)), + ("Xueyi", Color::new(250, 242, 230)), + ("Yanqing", Color::new(255, 242, 232)), + ("Yukong", Color::new(174, 167, 174)) + ], + } + } +} + +impl StarRailRelicScanner { + pub fn get_star(&self) -> Result { + let pos = self.window_info.origin_pos + self.window_info.star_pos; + let color = capture::get_color(pos)?; + + let (index, _) = self.match_colors_star + .iter() + .enumerate() + .min_by_key(|&(_, match_color)| match_color.distance(&color)) + .unwrap(); + + Ok(index + 1) + } + + pub fn get_lock(&self) -> Result { + let pos = self.window_info.origin_pos + self.window_info.lock_pos; + let color = capture::get_color(pos)?; + + let (index, _) = self.match_colors_lock + .iter() + .enumerate() + .min_by_key(|&(_, match_color)| match_color.distance(&color)) + .unwrap(); + + Ok(index == 0) + } + + pub fn get_discard(&self) -> Result { + let pos = self.window_info.origin_pos + self.window_info.discard_pos; + let color = capture::get_color(pos)?; + + let (index, _) = self.match_colors_discard + .iter() + .enumerate() + .min_by_key(|&(_, match_color)| match_color.distance(&color)) + .unwrap(); + + Ok(index == 0) + } + + pub fn get_equipper(&self) -> Result { + let pos = self.window_info.origin_pos + self.window_info.equipper_pos; + let color = capture::get_color(pos)?; + + let (name, _) = self.match_colors_equipper + .iter() + .min_by_key(|&(_, match_color)| match_color.distance(&color)) + .unwrap(); + + Ok(name.to_string()) + } + + pub fn get_item_count(&self, ocr_model: &OCRModel) -> Result { + let count = self.scanner_config.number; + let item_name = "遗器"; + + let max_count = 1500; + if count > 0 { + return Ok(max_count.min(count)); + } + + let im = match self.window_info.item_count_rect + .capture_relative(self.window_info.origin_pos) + { + Ok(im) => im, + Err(e) => { + error!("Error when capturing item count: {}", e); + return Ok(max_count); + }, + }; + + // todo use better preprocess function set + let im_grayed = to_gray(&im); + let (im_preprocessed, preprocess_flag) = pre_process(im_grayed); + assert!(preprocess_flag); + + let s = match ocr_model.inference_string(&im_preprocessed) { + Ok(s) => s, + Err(e) => { + error!("Error when inferring item count: {}", e); + return Ok(max_count); + }, + }; + + info!("物品信息: {}", s); + + if s.starts_with(item_name) { + let chars = s.chars().collect::>(); + let count_str = chars[4..chars.len() - 5].iter().collect::(); + Ok(match count_str.parse::() { + Ok(v) => (v as i32).min(max_count), + Err(_) => max_count, + }) + } else { + Ok(max_count) + } + } + + pub fn scan(&mut self) -> Result> { + info!("开始扫描,使用鼠标右键中断扫描"); + + let now = SystemTime::now(); + + let (tx, rx) = mpsc::channel::>(); + // let token = self.cancellation_token.clone(); + + let model = { + let model_bytes = include_bytes!("./models/model_training.onnx"); + let index_to_world = include_str!("./models/index_2_word.json"); + + OCRModel::new( + model_bytes, index_to_world + ) + }?; + let count = self.get_item_count(&model)?; + + let worker = RelicScannerWorker::new( + model, + self.window_info.clone(), + self.scanner_config.clone() + ); + + let join_handle = worker.run(rx); + + // let worker = self.worker(rx, count, token); + + self.send(&tx, count); + + match tx.send(None) { + Ok(_) => info!("扫描结束,等待识别线程结束,请勿关闭程序"), + Err(_) => info!("扫描结束,识别已完成"), + } + + match join_handle.join() { + Ok(v) => { + info!("识别耗时: {:?}", now.elapsed()?); + Ok(v) + }, + Err(_) => Err(anyhow::anyhow!("识别线程出现错误")), + } + } + + fn send(&mut self, tx: &Sender>, count: i32) { + let controller = Rc::new(RefCell::new(StarRailRepositoryScanController::new( + self.scanner_config.starrail_repo_scan_logic_config.clone(), + &self.window_info_clone, + // todo normalize types + count as usize, + self.game_info.clone(), + ))); + let mut generator = StarRailRepositoryScanController::into_generator(controller.clone()); + + loop { + let pinned_generator = Pin::new(&mut generator); + match pinned_generator.resume(()) { + CoroutineState::Yielded(_) => { + // let image = self.capture_panel().unwrap(); + let panel_image = controller.borrow().capture_panel().unwrap(); + let equip = self.get_equipper().unwrap(); + let star = self.get_star().unwrap(); + let lock = self.get_lock().unwrap(); + let discard = self.get_discard().unwrap(); + + // todo normalize types + if (star as i32) < self.scanner_config.min_star { + info!( + "找到满足最低星级要求 {} 的物品,准备退出……", + self.scanner_config.min_star + ); + break; + } + + if tx.send(Some(SendItem { panel_image, equip, star, lock, discard })).is_err() { + break; + } + + // scanned_count += 1; + }, + CoroutineState::Complete(result) => { + match result { + Err(e) => error!("扫描发生错误:{}", e), + Ok(value) => { + match value { + ReturnResult::Interrupted => info!("用户中断"), + ReturnResult::Finished => () + } + } + } + + break; + } + } + } + } +} \ No newline at end of file diff --git a/yas-starrail/src/scanner/relic_scanner/relic_scanner_config.rs b/yas-starrail/src/scanner/relic_scanner/relic_scanner_config.rs new file mode 100644 index 00000000..398ff63e --- /dev/null +++ b/yas-starrail/src/scanner/relic_scanner/relic_scanner_config.rs @@ -0,0 +1,97 @@ +use log::info; +use yas::arguments_builder::arguments_builder::{ArgumentsModifier, ArgumentsBuilder}; +use clap::{Arg, Command, FromArgMatches, ArgAction}; + +use crate::{export::export_format::StarRailRelicExportFormat, scanner_controller::repository_layout::config::StarRailRepositoryScannerLogicConfig}; + +#[derive(Default, Clone)] +pub struct StarRailRelicScannerConfig { + /// Items with stars less than this will be ignored + pub min_star: i32, + + /// Items with level less than this will be ignored + pub min_level: i32, + + /// Ignore duplicated items + pub ignore_dup: bool, + + pub verbose: bool, + + pub number: i32, + + pub starrail_repo_scan_logic_config: StarRailRepositoryScannerLogicConfig, +} + +impl ArgumentsModifier for StarRailRelicScannerConfig { + fn modify_arguments(builder: &mut ArgumentsBuilder) { + // todo use custom command builder + // todo add more configs + builder + .arg( + Arg::new("ignore-dup") + .long("ignore-dup") + .help("忽略重复物品") + // .num_args(0) + .action(ArgAction::SetTrue) + ) + .arg( + Arg::new("verbose") + .long("verbose") + .help("显示详细信息") + // .num_args(0) + .action(ArgAction::SetTrue) + ) + .arg( + Arg::new("min-star") + .long("min-star") + .help("最小星级") + .value_name("MIN_STAR") + .default_value("4") + .value_parser(clap::value_parser!(i32)) + ) + .arg( + Arg::new("min-level") + .long("min-level") + .help("最小等级") + .value_name("MIN_LEVEL") + .default_value("0") + .value_parser(clap::value_parser!(i32)) + ).arg( + Arg::new("number") + .long("number") + .help("指定遗器数量") + .value_name("NUMBER") + .value_parser(clap::value_parser!(i32)) + ); + + ::modify_arguments(builder); + } +} + +impl FromArgMatches for StarRailRelicScannerConfig { + fn from_arg_matches(matches: &clap::ArgMatches) -> Result { + let scanner_controller_config = StarRailRepositoryScannerLogicConfig::from_arg_matches(matches)?; + + // todo + let result = StarRailRelicScannerConfig { + min_star: *matches.get_one::("min-star").unwrap(), + min_level: *matches.get_one::("min-level").unwrap(), + number: if matches.contains_id("number") { + *matches.get_one::("number").unwrap() + } else { + -1 + }, + ignore_dup: matches.get_flag("ignore-dup"), + verbose: matches.get_flag("verbose"), + + starrail_repo_scan_logic_config: scanner_controller_config + }; + + Ok(result) + } + + fn update_from_arg_matches(&mut self, matches: &clap::ArgMatches) -> Result<(), clap::Error> { + // todo + unimplemented!() + } +} \ No newline at end of file diff --git a/yas-starrail/src/scanner_controller/mod.rs b/yas-starrail/src/scanner_controller/mod.rs new file mode 100644 index 00000000..6888aae1 --- /dev/null +++ b/yas-starrail/src/scanner_controller/mod.rs @@ -0,0 +1 @@ +pub mod repository_layout; \ No newline at end of file diff --git a/yas-starrail/src/scanner_controller/repository_layout/config.rs b/yas-starrail/src/scanner_controller/repository_layout/config.rs new file mode 100644 index 00000000..9b286a90 --- /dev/null +++ b/yas-starrail/src/scanner_controller/repository_layout/config.rs @@ -0,0 +1,108 @@ +use clap::{Arg, arg, FromArgMatches}; + +// todo add all the cmd arguments +#[derive(Clone)] +pub struct StarRailRepositoryScannerLogicConfig { + /// Max rows to scan + pub max_row: i32, + + // todo move to another scanner + /// Will the scanner capture only? + // pub capture_only: bool, + + /// The time to wait for scrolling. Consider increasing this value if the scrolling is not correct + pub scroll_delay: i32, + + /// Dump the captured image + // pub dump_mode: bool, + + /// The maximum time to wait for switching to the next item + pub max_wait_switch_item: i32, + + /// The time to wait for switching to the next item in cloud game + pub cloud_wait_switch_item: i32, +} + +impl Default for StarRailRepositoryScannerLogicConfig { + fn default() -> Self { + StarRailRepositoryScannerLogicConfig { + max_row: -1, + // capture_only: false, + scroll_delay: 80, + // number: -1, + // dump_mode: false, + max_wait_switch_item: 800, + cloud_wait_switch_item: 300, + } + } +} + +impl FromArgMatches for StarRailRepositoryScannerLogicConfig { + fn from_arg_matches(matches: &clap::ArgMatches) -> Result { + let mut result = StarRailRepositoryScannerLogicConfig::default(); + if matches.contains_id("max-row") { + result.max_row = *matches.get_one::("max-row").unwrap(); + } + if matches.contains_id("scroll-delay") { + result.scroll_delay = *matches.get_one::("scroll-delay").unwrap(); + } + if matches.contains_id("max-wait-switch-item") { + result.max_wait_switch_item = *matches.get_one::("max-wait-switch-item").unwrap(); + } + if matches.contains_id("cloud-wait-switch-item") { + result.cloud_wait_switch_item = *matches.get_one::("cloud-wait-switch-item").unwrap(); + } + + Ok(result) + } + + fn update_from_arg_matches(&mut self, matches: &clap::ArgMatches) -> Result<(), clap::Error> { + // todo + Ok(()) + } +} + +impl ArgumentsModifier for StarRailRepositoryScannerLogicConfig { + fn modify_arguments(builder: &mut ArgumentsBuilder) { + builder + .arg( + Arg::new("max-row") + .long("max-row") + .help("最大扫描行数") + .value_parser(clap::value_parser!(i32).range(1..)) + ) + .arg( + Arg::new("scroll-delay") + .long("scroll-delay") + .help("翻页时滚轮停顿时间(ms)(翻页不正确可以考虑加大该选项)") + .value_parser(clap::value_parser!(i32).range(1..)) + ) + // todo remove offset x and y + .arg( + Arg::new("offset-x") + .long("offset-x") + .help("人为指定横坐标偏移(截图有偏移时可用该选项校正)") + .value_parser(clap::value_parser!(i32)) + .hide(true) + ) + .arg( + Arg::new("offset-y") + .long("offset-y") + .help("人为指定纵坐标偏移(截图有偏移时可用该选项校正)") + .value_parser(clap::value_parser!(i32)) + .hide(true) + ) + .arg( + Arg::new("max-wait-switch-item") + .long("max-wait-switch-item") + .help("切换物品最大等待时间(ms)") + .value_parser(clap::value_parser!(i32).range(1..)) + ) + .arg( + Arg::new("cloud-wait-switch-item") + .long("cloud-wait-switch-item") + .help("指定云游戏切换物品等待时间(ms)") + .value_parser(clap::value_parser!(i32).range(1..)) + ); + } +} \ No newline at end of file diff --git a/yas-starrail/src/scanner_controller/repository_layout/mod.rs b/yas-starrail/src/scanner_controller/repository_layout/mod.rs new file mode 100644 index 00000000..7adcfdba --- /dev/null +++ b/yas-starrail/src/scanner_controller/repository_layout/mod.rs @@ -0,0 +1,2 @@ +pub mod config; +pub mod scan_logic; \ No newline at end of file diff --git a/yas-starrail/src/scanner_controller/repository_layout/scan_logic.rs b/yas-starrail/src/scanner_controller/repository_layout/scan_logic.rs new file mode 100644 index 00000000..cfe9277c --- /dev/null +++ b/yas-starrail/src/scanner_controller/repository_layout/scan_logic.rs @@ -0,0 +1,442 @@ +use std::cell::RefCell; +use std::ops::Coroutine; +use std::rc::Rc; +use image::RgbImage; +use yas::common::color::Color; +use yas::game_info::GameInfo; +use yas::window_info::require_window_info::RequireWindowInfo; +use yas::window_info::window_info_repository::WindowInfoRepository; +use crate::scanner_controller::repository_layout::config::StarRailRepositoryScannerLogicConfig; +use anyhow::{Result, anyhow}; +use yas::common::positioning::{Pos, Rect, Size}; +use yas::utils; +use log::{info, error}; +use std::time::SystemTime; +use yas::capture::capture::RelativeCapturable; +use yas::system_control::SystemControl; + +#[derive(Debug)] +pub enum ScrollResult { + TimeLimitExceeded, + Interrupt, + Success, + Failed, + Skip, +} + +// todo use macros +struct StarRailRepositoryScanControllerWindowInfo { + pub window_origin_pos: Pos, + pub panel_rect: Rect, + pub flag_rect: Rect, + pub item_gap_size: Size, + pub item_size: Size, + pub scan_margin_pos: Pos, + pub pool_rect: Rect, +} + +impl From<&WindowInfoRepository> for StarRailRepositoryScanControllerWindowInfo { + fn from(value: &WindowInfoRepository) -> Self { + StarRailRepositoryScanControllerWindowInfo { + window_origin_pos: value.get::("window_origin_pos").unwrap(), + panel_rect: value.get("starrail_repository_panel_rect").unwrap(), + flag_rect: value.get("starrail_repository_flag_rect").unwrap(), + item_gap_size: value.get("starrail_repository_item_gap_size").unwrap(), + item_size: value.get("starrail_repository_item_size").unwrap(), + scan_margin_pos: value.get("starrail_repository_scan_margin_pos").unwrap(), + pool_rect: value.get("starrail_repository_pool_rect").unwrap(), + } + } +} + +pub struct StarRailRepositoryScanController { + // to detect whether an item changes + pool: f64, + + // Stores initial gap colors for line gap detection + initial_flag: [Color; 50], + + // for scrolls + scrolled_rows: u32, + avg_scroll_one_row: f64, + + avg_switch_time: f64, + scanned_count: usize, + + game_info: GameInfo, + + row: usize, + col: usize, + item_count: usize, + + config: StarRailRepositoryScannerLogicConfig, + window_info: StarRailRepositoryScanControllerWindowInfo, + system_control: SystemControl, +} + +impl RequireWindowInfo for StarRailRepositoryScanController { + fn require_window_info(window_info_builder: &mut yas::window_info::window_info_builder::WindowInfoBuilder) { + window_info_builder + // .add_required_key("window_origin_pos") + .add_required_key("starrail_repository_panel_rect") + .add_required_key("starrail_repository_flag_rect") + .add_required_key("starrail_repository_item_gap_size") + .add_required_key("starrail_repository_item_size") + .add_required_key("starrail_repository_scan_margin_pos") + .add_required_key("starrail_repository_pool_rect") + .add_required_key("starrail_repository_item_row") + .add_required_key("starrail_repository_item_col"); + } +} + +pub fn calc_pool(row: &Vec) -> f32 { + let len = row.len() / 3; + let mut pool: f32 = 0.0; + + for i in 0..len { + pool += row[i * 3] as f32; + } + pool +} + +// constructor +impl StarRailRepositoryScanController { + pub fn new(config: StarRailRepositoryScannerLogicConfig, window_info: &WindowInfoRepository, item_count: usize, game_info: GameInfo) -> Self { + let item_row = window_info.get::("starrail_repository_item_row").unwrap(); + let item_col = window_info.get::("starrail_repository_item_col").unwrap(); + + StarRailRepositoryScanController { + system_control: SystemControl::new(), + + row: item_row as usize, + col: item_col as usize, + + window_info: StarRailRepositoryScanControllerWindowInfo::from(window_info), + config, + + pool: 0.0, + + initial_flag: [Color::new(0, 0, 0); 50], + + scrolled_rows: 0, + avg_scroll_one_row: 0.0, + + avg_switch_time: 0.0, + // scanned_count: 0, + + game_info, + item_count, + scanned_count: 0, + } + } +} + +pub enum ReturnResult { + Interrupted, + Finished, +} + +impl StarRailRepositoryScanController { + pub fn into_generator(object: Rc>) -> impl Coroutine> { + let generator = move || { + let mut scanned_row = 0; + let mut scanned_count = 0; + let mut start_row = 0; + + let count = object.borrow().item_count; + + let total_row = (object.borrow().item_count + object.borrow().col - 1) / object.borrow().col; + let last_row_col = if object.borrow().item_count % object.borrow().col == 0 { + object.borrow().col + } else { + count % object.borrow().col + }; + + info!( + "扫描任务共 {} 个物品,共计 {} 行,尾行 {} 个", + count, total_row, last_row_col + ); + + object.borrow_mut().move_to(0, 0); + + #[cfg(target_os = "macos")] + utils::sleep(20); + + if let Err(e) = object.borrow_mut().system_control.mouse_click() { + error!("Mouse click failed: {:?}", e); + return Err(anyhow!("Mouse click failed")); + } + utils::sleep(1000); + + object.borrow_mut().sample_initial_color().unwrap(); + + let row = object.borrow().row; + + 'outer: while scanned_count < count { + '_row: for row in start_row..row { + let row_item_count = if scanned_row == total_row - 1 { + last_row_col + } else { + object.borrow().col + }; + + '_col: for col in 0..row_item_count { + // Exit if right mouse button is down, or if we've scanned more than the maximum count + if utils::is_rmb_down() { + return Ok(ReturnResult::Interrupted); + } + if scanned_count > count { + return Ok(ReturnResult::Finished); + } + + object.borrow_mut().move_to(row, col); + object.borrow_mut().system_control.mouse_click().unwrap(); + + #[cfg(target_os = "macos")] + utils::sleep(20); + + // do not unwrap + object.borrow_mut().wait_until_switched(); + + // have to make sure at this point no mut ref exists + yield; + + scanned_count += 1; + object.borrow_mut().scanned_count = scanned_count; + } // end '_col + + scanned_row += 1; + + // todo this is dangerous, use uniform integer type instead + if scanned_row >= object.borrow().config.max_row as usize { + info!("到达最大行数,准备退出……"); + break 'outer; + } + } // end '_row + + let remain = count - scanned_count; + let remain_row = (remain + object.borrow().col - 1) / object.borrow().col; + let scroll_row = remain_row.min(object.borrow().row); + start_row = object.borrow().row - scroll_row; + + match object.borrow_mut().scroll_rows(scroll_row as i32) { + ScrollResult::TimeLimitExceeded => { + return Err(anyhow!("翻页超时,扫描终止……")); + }, + ScrollResult::Interrupt => { + return Ok(ReturnResult::Interrupted); + }, + _ => (), + } + + utils::sleep(100); + } + + Ok(ReturnResult::Finished) + }; + + generator + } + + pub fn capture_panel(&self) -> Result { + self.window_info.panel_rect.capture_relative(self.window_info.window_origin_pos) + } + + #[inline(always)] + pub fn sample_initial_color(&mut self) -> Result<()> { + self.initial_flag = self.capture_flag()?; + Ok(()) + } + + #[inline(always)] + pub fn capture_flag(&self) -> Result<[Color; 50]> { + let mut flag = [Color::new(0, 0, 0); 50]; + let im: RgbImage = self.window_info.flag_rect.capture_relative(self.window_info.window_origin_pos)?; + + // Gap size between warehouse top and first item row varies with resolution. + // At 1920x1080, it's 20 pixels. + for y in 0..self.window_info.flag_rect.height as usize { + let color = im.get_pixel(0, y as u32); + flag[y] = Color::new(color.0[0], color.0[1], color.0[2]); + } + + Ok(flag) + } + + #[inline(always)] + pub fn check_flag(&self) -> Result<()> { + let flag = self.capture_flag()?; + for y in 0..self.window_info.flag_rect.height as usize { + if self.initial_flag[y].distance(&flag[y]) < 10 { + return Ok(()); + } + } + Err(anyhow!("Flag changed")) + } + + pub fn align_row(&mut self) { + for _ in 0..10 { + if self.check_flag().is_err() { + self.mouse_scroll(1, false); + utils::sleep(self.config.scroll_delay.try_into().unwrap()); + } else { + break; + } + } + } + + pub fn move_to(&mut self, row: usize, col: usize) { + let (row, col) = (row as u32, col as u32); + let origin = self.window_info.window_origin_pos; + + let gap = self.window_info.item_gap_size; + let margin = self.window_info.scan_margin_pos; + let size = self.window_info.item_size; + + let left = origin.x + margin.x + (gap.width + size.width) * (col as f64) + size.width / 2.0; + let top = origin.y + margin.y + (gap.height + size.height) * (row as f64) + size.height / 2.0; + + self.system_control.mouse_move_to(left as i32, top as i32).unwrap(); + + #[cfg(target_os = "macos")] + utils::sleep(20); + } + + pub fn scroll_one_row(&mut self) -> ScrollResult { + let mut state = 0; + let mut count = 0; + let max_scroll = 25; + + while count < max_scroll { + if utils::is_rmb_down() { + return ScrollResult::Interrupt; + } + + #[cfg(windows)] + self.system_control.mouse_scroll(1, false); + + utils::sleep(self.config.scroll_delay.try_into().unwrap()); + count += 1; + + match (state, self.check_flag()) { + (0, Err(_)) => state = 1, + (1, Ok(_)) => { + self.update_avg_row(count); + return ScrollResult::Success; + } + _ => {} + } + } + + ScrollResult::TimeLimitExceeded + } + + pub fn scroll_rows(&mut self, count: i32) -> ScrollResult { + if cfg!(not(target_os = "macos")) && self.scrolled_rows >= 5 { + let length = self.estimate_scroll_length(count); + + for _ in 0..length { + if let Err(e) = self.system_control.mouse_scroll(1, false) { + error!("Scrolling failed: {:?}", e); + return ScrollResult::Interrupt; + } + } + + utils::sleep(self.config.scroll_delay.try_into().unwrap()); + + self.align_row(); + return ScrollResult::Skip; + } + + for _ in 0..count { + match self.scroll_one_row() { + ScrollResult::Success | ScrollResult::Skip => continue, + ScrollResult::Interrupt => return ScrollResult::Interrupt, + v => { + error!("Scrolling failed: {:?}", v); + return v; + }, + } + } + + ScrollResult::Success + } + + pub fn wait_until_switched(&mut self) -> Result<()> { + if self.game_info.is_cloud { + utils::sleep(self.config.cloud_wait_switch_item.try_into()?); + return Ok(()); + } + + let now = SystemTime::now(); + + let mut consecutive_time = 0; + let mut diff_flag = false; + while now.elapsed()?.as_millis() < self.config.max_wait_switch_item as u128 { + let im: RgbImage = self.window_info.pool_rect + .capture_relative(self.window_info.window_origin_pos)?; + + let pool = calc_pool(im.as_raw()) as f64; + + if (pool - self.pool).abs() > 0.000001 { + self.pool = pool; + diff_flag = true; + consecutive_time = 0; + } else if diff_flag { + consecutive_time += 1; + if consecutive_time == 1 { + self.avg_switch_time = (self.avg_switch_time * self.scanned_count as f64 + + now.elapsed().unwrap().as_millis() as f64) + / (self.scanned_count as f64 + 1.0); + self.scanned_count += 1; + return anyhow::Ok(()); + } + } + } + + Err(anyhow!("Wait until switched failed")) + } + + #[inline(always)] + pub fn mouse_scroll(&mut self, length: i32, try_find: bool) { + #[cfg(windows)] + self.system_control.mouse_scroll(length, try_find).unwrap(); + + #[cfg(target_os = "linux")] + self.system_control.mouse_scroll(length, try_find); + + #[cfg(target_os = "macos")] + { + match self.game_info.ui { + crate::common::UI::Desktop => { + self.system_control.mouse_scroll(length); + utils::sleep(20); + }, + crate::common::UI::Mobile => { + if try_find { + self.system_control.mac_scroll_fast(length); + } else { + self.system_control.mac_scroll_slow(length); + } + }, + } + } + } + + #[inline(always)] + fn update_avg_row(&mut self, count: i32) { + let current = self.avg_scroll_one_row * self.scrolled_rows as f64 + count as f64; + self.scrolled_rows += 1; + self.avg_scroll_one_row = current / self.scrolled_rows as f64; + + info!( + "avg scroll one row: {} ({})", + self.avg_scroll_one_row, self.scrolled_rows + ); + } + + #[inline(always)] + fn estimate_scroll_length(&self, count: i32) -> i32 { + ((self.avg_scroll_one_row * count as f64 - 2.0).round() as i32).max(0) + } +} \ No newline at end of file diff --git a/yas-starrail/window_info/windows16x9.json b/yas-starrail/window_info/windows16x9.json new file mode 100644 index 00000000..947f5b9e --- /dev/null +++ b/yas-starrail/window_info/windows16x9.json @@ -0,0 +1 @@ +{"current_resolution": {"width": 1920, "height": 1080}, "resolution_family": "Windows16x9", "data": {"starrail_relic_discard_pos": {"Pos": {"x": 1808, "y": 323}}, "starrail_relic_equip_rect": {"Rect": {"top": 884, "left": 1610, "width": 76, "height": 23}}, "starrail_relic_equipper_pos": {"Pos": {"x": 1582, "y": 888}}, "starrail_relic_item_count_rect": {"Rect": {"top": 973, "left": 895, "width": 258, "height": 34}}, "starrail_relic_level_rect": {"Rect": {"top": 309, "left": 1413, "width": 86, "height": 38}}, "starrail_relic_lock_pos": {"Pos": {"x": 1808, "y": 274}}, "starrail_relic_main_stat_name_rect": {"Rect": {"top": 398, "left": 1440, "width": 251, "height": 33}}, "starrail_relic_main_stat_value_rect": {"Rect": {"top": 398, "left": 1729, "width": 113, "height": 33}}, "starrail_relic_star_pos": {"Pos": {"x": 1800, "y": 250}}, "starrail_relic_sub_stat0_name_rect": {"Rect": {"top": 442, "left": 1440, "width": 197, "height": 33}}, "starrail_relic_sub_stat0_value_rect": {"Rect": {"top": 442, "left": 1680, "width": 166, "height": 33}}, "starrail_relic_sub_stat1_name_rect": {"Rect": {"top": 480, "left": 1440, "width": 197, "height": 35}}, "starrail_relic_sub_stat1_value_rect": {"Rect": {"top": 480, "left": 1680, "width": 166, "height": 33}}, "starrail_relic_sub_stat2_name_rect": {"Rect": {"top": 519, "left": 1440, "width": 197, "height": 34}}, "starrail_relic_sub_stat2_value_rect": {"Rect": {"top": 519, "left": 1680, "width": 166, "height": 33}}, "starrail_relic_sub_stat3_name_rect": {"Rect": {"top": 557, "left": 1440, "width": 197, "height": 34}}, "starrail_relic_sub_stat3_value_rect": {"Rect": {"top": 557, "left": 1680, "width": 166, "height": 34}}, "starrail_relic_title_rect": {"Rect": {"top": 129, "left": 1398, "width": 450, "height": 33}}, "starrail_repository_flag_rect": {"Rect": {"top": 179, "left": 1064, "width": 1, "height": 20}}, "starrail_repository_item_col": {"InvariantInt": 9}, "starrail_repository_item_gap_size": {"Size": {"width": 9, "height": 14}}, "starrail_repository_item_row": {"InvariantInt": 5}, "starrail_repository_item_size": {"Size": {"width": 115, "height": 135}}, "starrail_repository_panel_rect": {"Rect": {"top": 124, "left": 1395, "width": 456, "height": 817}}, "starrail_repository_pool_rect": {"Rect": {"top": 132, "left": 1446, "width": 63, "height": 456}}, "starrail_repository_scan_margin_pos": {"Pos": {"x": 136, "y": 206}}}} \ No newline at end of file diff --git a/yas/Cargo.toml b/yas/Cargo.toml new file mode 100644 index 00000000..79eb1e2f --- /dev/null +++ b/yas/Cargo.toml @@ -0,0 +1,57 @@ +[package] +name = "yas_scanner" +version = "0.1.14" +edition = "2021" +description = "Mihoyo game item scanner library" +repository = "https://github.com/wormtql/yas" +authors = ["wormtql <584130248@qq.com>", "GZTime ", "YCR160 <3342711246@qq.com>"] +keywords = ["GenshinImpact", "HonkaiStarRail", "scanner", "ocr"] +license = "GPL-2.0-or-later" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +clap = { version = "4.4", features = ["derive"] } +image = "0.24" +enigo = "0.1" +# ref: https://github.com/sonos/tract#tract-opl-version-compatibility +tract-onnx = "0.21.1" +serde_json = "1.0" +serde = { version = "1.0", features = ["derive"] } +regex = "1.5" +log = "0.4" +edit-distance = "2.1" +os_info = "3.0" +strum = "0.25" +strum_macros = "0.25" +rand = "0.8" +reqwest = { version = "0.11", features = ["blocking", "json"] } +semver = "1.0" +lazy_static = "1.4" +screenshots = "0.8" +png = "0.17" +anyhow = "1.0" +once_cell = "1.18" +indicatif-log-bridge = "0.2" +indicatif = "0.17" +console = "0.15" +xcap = "0.0.4" +paste = "1.0" +prettytable-rs = "^0.10" +bytesize = {version = "1.2.0", features = ["serde"]} + +[target.'cfg(target_os = "windows")'.dependencies] +winapi = { version = "0.3", features = [ + "winuser", + "wingdi", + "winnt", + "securitybaseapi", + "libloaderapi", + "shellscalingapi" +] } +windows-capture = "1.0.65" + +[target.'cfg(target_os = "macos")'.dependencies] +core-graphics = "0.23" +core-foundation = "0.9" +cocoa = "0.25" diff --git a/yas/src/capture/capturer.rs b/yas/src/capture/capturer.rs new file mode 100644 index 00000000..f590a93d --- /dev/null +++ b/yas/src/capture/capturer.rs @@ -0,0 +1,27 @@ +use crate::positioning::{Pos, Rect}; +use anyhow::Result; + +pub trait Capturer { + // it's necessary to use signed int, because capture region may be out of the screen + fn capture_rect(&self, rect: Rect) -> Result; + + fn capture_color(&self, pos: Pos) -> Result { + let image = self.capture_rect(Rect { + left: pos.x, + top: pos.y, + width: 1, + height: 1, + })?; + Ok(image.get_pixel(0, 0)) + } + + fn capture_relative_to(&self, rect: Rect, relative_to: Pos) -> Result { + let new_rect = Rect { + left: rect.left + relative_to.x, + top: rect.top + relative_to.y, + width: rect.width, + height: rect.height + }; + self.capture_rect(new_rect) + } +} diff --git a/yas/src/capture/generic_capturer.rs b/yas/src/capture/generic_capturer.rs new file mode 100644 index 00000000..716633f3 --- /dev/null +++ b/yas/src/capture/generic_capturer.rs @@ -0,0 +1,35 @@ +use crate::capture::{Capturer, ScreenshotsCapturer, WinapiCapturer}; +use anyhow::Result; +use image::RgbImage; +use crate::positioning::Rect; + +pub struct GenericCapturer { + #[cfg(target_os = "windows")] + pub windows_capturer: WinapiCapturer, + pub fallback_capturer: ScreenshotsCapturer, +} + +impl GenericCapturer { + pub fn new() -> Result { + Ok(Self { + #[cfg(target_os = "windows")] + windows_capturer: WinapiCapturer::new(), + fallback_capturer: ScreenshotsCapturer::new()?, + }) + } +} + +impl Capturer for GenericCapturer { + fn capture_rect(&self, rect: Rect) -> Result { + #[cfg(target_os = "windows")] + { + let result = self.windows_capturer.capture_rect(rect); + if result.is_ok() { + return result + } + } + + let result = self.fallback_capturer.capture_rect(rect); + return result; + } +} diff --git a/yas/src/capture/mod.rs b/yas/src/capture/mod.rs new file mode 100644 index 00000000..da29e9b6 --- /dev/null +++ b/yas/src/capture/mod.rs @@ -0,0 +1,13 @@ +mod capturer; +mod screenshots_capturer; +mod xcap_capturer; +#[cfg(target_os = "windows")] +mod winapi_capturer; +mod generic_capturer; +// #[cfg(target_os = "windows")] +// mod window_capture_capturer; + +pub use capturer::Capturer; +pub use screenshots_capturer::ScreenshotsCapturer; +pub use winapi_capturer::WinapiCapturer; +pub use generic_capturer::GenericCapturer; diff --git a/yas/src/capture/screenshots_capturer.rs b/yas/src/capture/screenshots_capturer.rs new file mode 100644 index 00000000..3bd25bd9 --- /dev/null +++ b/yas/src/capture/screenshots_capturer.rs @@ -0,0 +1,37 @@ +use anyhow::Result; +use image::{RgbaImage, RgbImage}; +use image::buffer::ConvertBuffer; +use crate::capture::Capturer; +use crate::positioning::Rect; + +pub struct ScreenshotsCapturer { + screens: Vec, +} + +impl ScreenshotsCapturer { + pub fn new() -> Result { + Ok(Self { + screens: screenshots::Screen::all()?, + }) + } +} + +impl Capturer for ScreenshotsCapturer { + fn capture_rect(&self, rect: Rect) -> Result { + let screen = &self.screens[0]; + let capture_result = screen.capture_area( + rect.left, + rect.top, + rect.width as u32, + rect.height as u32 + ); + capture_result + } +} + +impl Capturer for ScreenshotsCapturer { + fn capture_rect(&self, rect: Rect) -> Result { + let rgba_result: RgbaImage = self.capture_rect(rect)?; + Ok(rgba_result.convert()) + } +} diff --git a/yas/src/capture/winapi_capturer.rs b/yas/src/capture/winapi_capturer.rs new file mode 100644 index 00000000..dbe63d31 --- /dev/null +++ b/yas/src/capture/winapi_capturer.rs @@ -0,0 +1,156 @@ +use std::mem::size_of; +use std::ptr::null_mut; + +use anyhow::{anyhow, Result}; +use image::{ImageBuffer, RgbImage}; +use winapi::shared::windef::{HBITMAP, HDC}; +use winapi::um::wingdi::{ + BI_RGB, + BitBlt, + BITMAP, + BITMAPINFO, + BITMAPINFOHEADER, + CreateCompatibleBitmap, + CreateCompatibleDC, + DeleteObject, + DIB_RGB_COLORS, + GetDIBits, + GetObjectW, + SelectObject, + SRCCOPY, +}; +use winapi::um::winuser::{GetDC, ReleaseDC}; + +use crate::capture::Capturer; +use crate::positioning::{Pos, Rect}; + +// BGRA +unsafe fn unsafe_capture(rect: Rect) -> Result> { + let dc_window: HDC = GetDC(null_mut()); + + let dc_mem: HDC = CreateCompatibleDC(dc_window); + if dc_mem.is_null() { + return Err(anyhow!("CreateCompatibleDC failed")); + } + + let hbm: HBITMAP = CreateCompatibleBitmap(dc_window, rect.width, rect.height); + if hbm.is_null() { + return Err(anyhow!("CreateCompatibleBitmap failed")); + } + + SelectObject(dc_mem, hbm as *mut winapi::ctypes::c_void); + + let result = BitBlt( + dc_mem, + 0, + 0, + rect.width, + rect.height, + dc_window, + rect.left, + rect.top, + SRCCOPY + ); + if result == 0 { + return Err(anyhow!("BitBlt failed")); + } + + let mut bitmap: BITMAP = BITMAP { + bmBits: 0 as *mut winapi::ctypes::c_void, + bmBitsPixel: 0, + bmPlanes: 0, + bmWidthBytes: 0, + bmHeight: 0, + bmWidth: 0, + bmType: 0, + }; + GetObjectW( + hbm as *mut winapi::ctypes::c_void, + size_of::() as i32, + (&mut bitmap) as *mut BITMAP as *mut winapi::ctypes::c_void + ); + + let mut bi: BITMAPINFOHEADER = BITMAPINFOHEADER { + biSize: size_of::() as u32, + biWidth: bitmap.bmWidth, + biHeight: bitmap.bmHeight, + biPlanes: 1, + biBitCount: 32, + biCompression: BI_RGB, + biSizeImage: 0, + biXPelsPerMeter: 0, + biYPelsPerMeter: 0, + biClrUsed: 0, + biClrImportant: 0, + }; + + let bitmap_size: usize = (((bitmap.bmWidth * 32 + 31) / 32) * 4 * bitmap.bmHeight) as usize; + let mut buffer: Vec = vec![0; bitmap_size]; + + GetDIBits( + dc_window, + hbm, + 0, + bitmap.bmHeight as u32, + // lpbitmap, + buffer.as_mut_ptr() as *mut winapi::ctypes::c_void, + (&mut bi) as *mut BITMAPINFOHEADER as *mut BITMAPINFO, + DIB_RGB_COLORS + ); + + DeleteObject(hbm as *mut winapi::ctypes::c_void); + DeleteObject(dc_mem as *mut winapi::ctypes::c_void); + ReleaseDC(null_mut(), dc_window); + + Ok(buffer) +} + +pub struct WinapiCapturer; + +impl WinapiCapturer { + pub fn new() -> Self { + // todo maybe we can explicitly account for windows scale, and remove the call + // crate::utils::set_dpi_awareness(); + Self + } +} + +impl Capturer for WinapiCapturer { + fn capture_rect(&self, rect: Rect) -> Result { + let raw: Vec = unsafe { + unsafe_capture(rect)? + }; + + let height = rect.height as u32; + let width = rect.width as u32; + + let img = ImageBuffer::from_fn( + rect.width as u32, + rect.height as u32, + move |x, y| { + let y = height - y - 1; + let b = raw[((y * width + x) * 4 + 0) as usize]; + let g = raw[((y * width + x) * 4 + 1) as usize]; + let r = raw[((y * width + x) * 4 + 2) as usize]; + image::Rgb([r, g, b]) + } + ); + + Ok(img) + } + + fn capture_color(&self, pos: Pos) -> Result> { + let raw: Vec = unsafe { + unsafe_capture(Rect { + left: pos.x, + top: pos.y, + width: 1, + height: 1 + })? + }; + let r = raw[2]; + let g = raw[1]; + let b = raw[0]; + Ok(image::Rgb([r, g, b])) + } +} diff --git a/yas/src/capture/xcap_capturer.rs b/yas/src/capture/xcap_capturer.rs new file mode 100644 index 00000000..0a97a472 --- /dev/null +++ b/yas/src/capture/xcap_capturer.rs @@ -0,0 +1,13 @@ +// use anyhow::Result; + +// todo +// pub struct XCapCapturer { +// pub monitors: Vec, +// } +// +// impl XCapCapturer { +// pub fn new() -> Result { +// let monitors = xcap::Monitor::all()?; +// +// } +// } \ No newline at end of file diff --git a/yas/src/common/cancel.rs b/yas/src/common/cancel.rs new file mode 100644 index 00000000..f2fab414 --- /dev/null +++ b/yas/src/common/cancel.rs @@ -0,0 +1,33 @@ +use std::sync::{atomic::AtomicBool, Arc}; + +#[derive(Clone)] +pub struct CancellationToken { + cancelled: Arc, +} + +impl std::panic::UnwindSafe for CancellationToken {} +impl std::panic::RefUnwindSafe for CancellationToken {} + +impl CancellationToken { + /// Creates a new CancellationToken in the non-cancelled state. + pub fn new() -> CancellationToken { + CancellationToken { + cancelled: Arc::new(AtomicBool::new(false)), + } + } + + pub fn cancel(&self) { + self.cancelled + .store(true, std::sync::atomic::Ordering::SeqCst); + } + + pub fn cancelled(&self) -> bool { + self.cancelled.load(std::sync::atomic::Ordering::SeqCst) + } +} + +impl Default for CancellationToken { + fn default() -> Self { + Self::new() + } +} diff --git a/yas/src/common/color.rs b/yas/src/common/color.rs new file mode 100644 index 00000000..c138571f --- /dev/null +++ b/yas/src/common/color.rs @@ -0,0 +1,17 @@ +#[derive(Debug, PartialEq, Default, Clone, Copy)] +pub struct Color(pub u8, pub u8, pub u8); + +impl Color { + pub fn distance(&self, other: &Color) -> u32 { + let r = self.0 as i32 - other.0 as i32; + let g = self.1 as i32 - other.1 as i32; + let b = self.2 as i32 - other.2 as i32; + + let dis = r * r + g * g + b * b; + dis as u32 + } + + pub fn new(r: u8, g: u8, b: u8) -> Color { + Color(r, g, b) + } +} \ No newline at end of file diff --git a/yas/src/common/image_ext.rs b/yas/src/common/image_ext.rs new file mode 100644 index 00000000..e76cd3b3 --- /dev/null +++ b/yas/src/common/image_ext.rs @@ -0,0 +1,14 @@ +use image::{GrayImage, ImageBuffer, Luma}; + +pub trait ToF32GrayImage { + fn to_f32_gray_image(&self) -> ImageBuffer, Vec>; +} + +impl ToF32GrayImage for GrayImage { + fn to_f32_gray_image(&self) -> ImageBuffer, Vec> { + ImageBuffer::from_fn(self.width(), self.height(), |x, y| { + let pv = self.get_pixel(x, y)[0]; + Luma([pv as f32 / 255.0_f32]) + }) + } +} diff --git a/yas/src/common/mod.rs b/yas/src/common/mod.rs new file mode 100644 index 00000000..066d821c --- /dev/null +++ b/yas/src/common/mod.rs @@ -0,0 +1,3 @@ +pub mod cancel; +pub mod color; +pub mod image_ext; diff --git a/yas/src/draw_capture_region/draw_capture_region.rs b/yas/src/draw_capture_region/draw_capture_region.rs new file mode 100644 index 00000000..e85aa2d4 --- /dev/null +++ b/yas/src/draw_capture_region/draw_capture_region.rs @@ -0,0 +1,55 @@ +use crate::positioning::{Pos, Rect}; + + +pub trait DrawCaptureRegion { + fn draw_capture_region(&self, image: &mut image::RgbImage); +} + +// todo other types +impl DrawCaptureRegion for Pos { + fn draw_capture_region(&self, image: &mut image::RgbImage) { + let blue = image::Rgb([0, 0, 255]); + + let x = self.x as u32; + let y = self.y as u32; + + for i in x - 1..=x + 1 { + for j in y - 1..=y + 1 { + image.put_pixel(i, j, blue); + } + } + + for i in x - 5..=x + 5 { + image.put_pixel(i, y + 5, blue); + image.put_pixel(i, y - 5, blue); + } + + for j in y - 5..=y + 5 { + image.put_pixel(x + 5, j, blue); + image.put_pixel(x - 5, j, blue); + } + } +} + +impl DrawCaptureRegion for Rect { + fn draw_capture_region(&self, image: &mut image::RgbImage) { + let red = image::Rgb([255, 0, 0]); + + let left = self.left as u32; + let top = self.top as u32; + let width = self.width as u32; + let height = self.height as u32; + let bottom = top + height; + let right = left + width; + + for x in left..right { + image.put_pixel(x, top, red); + image.put_pixel(x, bottom, red); + } + + for y in top..bottom { + image.put_pixel(left, y, red); + image.put_pixel(right, y, red); + } + } +} \ No newline at end of file diff --git a/yas/src/draw_capture_region/mod.rs b/yas/src/draw_capture_region/mod.rs new file mode 100644 index 00000000..c8915eac --- /dev/null +++ b/yas/src/draw_capture_region/mod.rs @@ -0,0 +1 @@ +pub mod draw_capture_region; diff --git a/yas/src/export/asset_emitter.rs b/yas/src/export/asset_emitter.rs new file mode 100644 index 00000000..10b0a744 --- /dev/null +++ b/yas/src/export/asset_emitter.rs @@ -0,0 +1,5 @@ +use crate::export::ExportAssets; + +pub trait AssetEmitter { + fn emit(&self, asset_bundle: &mut ExportAssets); +} \ No newline at end of file diff --git a/yas/src/export/export_item.rs b/yas/src/export/export_item.rs new file mode 100644 index 00000000..046fa248 --- /dev/null +++ b/yas/src/export/export_item.rs @@ -0,0 +1,29 @@ +use std::path::PathBuf; + +#[derive(Clone, Debug)] +pub struct ExportItem { + // bytes + pub contents: Vec, + pub filename: PathBuf, + pub name: Option, + pub description: Option, +} + +#[derive(Clone, Debug)] +pub struct StatisticItem { + pub size_in_bytes: usize, + pub filename: PathBuf, + pub name: Option, + pub description: Option, +} + +impl StatisticItem { + pub fn from_export_item(export_item: &ExportItem) -> Self { + StatisticItem { + size_in_bytes: export_item.contents.len(), + filename: export_item.filename.clone(), + name: export_item.name.clone(), + description: export_item.description.clone() + } + } +} diff --git a/yas/src/export/export_statistics.rs b/yas/src/export/export_statistics.rs new file mode 100644 index 00000000..e2f44701 --- /dev/null +++ b/yas/src/export/export_statistics.rs @@ -0,0 +1,45 @@ +use std::fmt; + +use bytesize::ByteSize; +use prettytable::{row, Row, Table}; + +use crate::export::StatisticItem; + +pub struct ExportStatistics { + pub exported_assets: Vec, + pub failed_items: Vec, +} + +impl ExportStatistics { + pub fn get_table(&self) -> Table { + let mut table = Table::new(); + + table.add_row(row!["Name", "Description", "File", "Size"]); + for item in self.exported_assets.iter() { + table.add_row(Row::new(vec![ + prettytable::Cell::new(item.name.as_ref().unwrap_or(&Default::default())), + prettytable::Cell::new(item.description.as_ref().unwrap_or(&Default::default())), + prettytable::Cell::new(&format!("{:?}", item.filename)), + prettytable::Cell::new(&format!("{}", ByteSize(item.size_in_bytes as u64))), + ])); + } + + table + } +} + +impl fmt::Display for ExportStatistics { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let table = self.get_table(); + write!(f, "{}", table) + } +} + +impl ExportStatistics { + pub fn new() -> Self { + ExportStatistics { + exported_assets: Vec::new(), + failed_items: Vec::new() + } + } +} \ No newline at end of file diff --git a/yas/src/export/exporter.rs b/yas/src/export/exporter.rs new file mode 100644 index 00000000..2c9ce49f --- /dev/null +++ b/yas/src/export/exporter.rs @@ -0,0 +1,56 @@ +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +use log::error; + +use crate::export::{ExportItem, ExportStatistics, StatisticItem}; + +pub struct ExportAssets { + pub assets: Vec +} + +impl ExportAssets { + pub fn new() -> Self { + ExportAssets { assets: Vec::new() } + } + + pub fn add_asset(&mut self, name: Option, filename: PathBuf, contents: Vec, description: Option) { + self.assets.push(ExportItem { + contents, + filename, + name, + description, + }) + } + + pub fn save(&self) -> ExportStatistics { + let mut stat = ExportStatistics::new(); + + for item in self.assets.iter() { + let mut file = match File::create(&item.filename) { + Err(why) => { + stat.failed_items.push(StatisticItem::from_export_item(item)); + error!("无法创建文件 {:?}: {}", &item.filename, why); + continue; + }, + Ok(file) => { + file + }, + }; + + match file.write_all(&item.contents) { + Err(why) => { + stat.failed_items.push(StatisticItem::from_export_item(item)); + error!("无法写入文件 {:?}: {}", &item.filename, why); + continue; + }, + Ok(_) => (), + } + + stat.exported_assets.push(StatisticItem::from_export_item(item)); + } + + stat + } +} diff --git a/yas/src/export/mod.rs b/yas/src/export/mod.rs new file mode 100644 index 00000000..63168831 --- /dev/null +++ b/yas/src/export/mod.rs @@ -0,0 +1,10 @@ +pub use asset_emitter::AssetEmitter; +pub use export_item::{ExportItem, StatisticItem}; +pub use export_statistics::ExportStatistics; +pub use exporter::ExportAssets; + +mod exporter; +mod export_item; +mod export_statistics; +mod asset_emitter; + diff --git a/yas/src/game_info/game_info.rs b/yas/src/game_info/game_info.rs new file mode 100644 index 00000000..d5e72e48 --- /dev/null +++ b/yas/src/game_info/game_info.rs @@ -0,0 +1,12 @@ +use crate::game_info::{ResolutionFamily, UI}; +use crate::game_info::ui::Platform; +use crate::positioning::Rect; + +#[derive(Clone, Debug)] +pub struct GameInfo { + pub window: Rect, + pub resolution_family: ResolutionFamily, + pub is_cloud: bool, + pub ui: UI, + pub platform: Platform, +} diff --git a/yas/src/game_info/game_info_builder.rs b/yas/src/game_info/game_info_builder.rs new file mode 100644 index 00000000..ea47df39 --- /dev/null +++ b/yas/src/game_info/game_info_builder.rs @@ -0,0 +1,43 @@ +use super::game_info::GameInfo; +use anyhow::Result; + +pub struct GameInfoBuilder { + pub local_window_names: Vec, + pub cloud_window_names: Vec, +} + +impl GameInfoBuilder { + pub fn new() -> Self { + GameInfoBuilder { + local_window_names: Vec::new(), + cloud_window_names: Vec::new(), + } + } + + pub fn add_local_window_name(&mut self, name: &str) -> &mut Self { + self.local_window_names.push(String::from(name)); + self + } + + pub fn add_cloud_window_name(&mut self, name: &str) -> &mut Self { + self.cloud_window_names.push(String::from(name)); + self + } + + pub fn build(&self) -> Result { + #[cfg(windows)] + { + let mut window_names = Vec::new(); + for name in self.local_window_names.iter() { + window_names.push(name.as_str()); + } + for name in self.cloud_window_names.iter() { + window_names.push(name.as_str()); + } + crate::game_info::os::get_game_info(&window_names) + // crate::game_info::os::get_game_info(&["原神", "Genshin Impact", "云·原神"]) + } + + // todo other platforms + } +} diff --git a/yas/src/game_info/mod.rs b/yas/src/game_info/mod.rs new file mode 100644 index 00000000..f2193209 --- /dev/null +++ b/yas/src/game_info/mod.rs @@ -0,0 +1,10 @@ +mod game_info; +mod os; +mod game_info_builder; +mod ui; +mod resolution_family; + +pub use game_info_builder::GameInfoBuilder; +pub use ui::{UI, Platform}; +pub use resolution_family::ResolutionFamily; +pub use game_info::GameInfo; diff --git a/yas/src/game_info/os/linux.rs b/yas/src/game_info/os/linux.rs new file mode 100644 index 00000000..e0d3eadb --- /dev/null +++ b/yas/src/game_info/os/linux.rs @@ -0,0 +1,43 @@ +use crate::game_info::{GameInfo, Platform}; + +pub fn get_game_info() -> GameInfo { + let window_id = unsafe { + String::from_utf8_unchecked( + std::process::Command::new("sh") + .arg("-c") + .arg(r#" xwininfo|grep "Window id"|cut -d " " -f 4 "#) + .output() + .unwrap() + .stdout, + ) + }; + let window_id = window_id.trim_end_matches("\n"); + + let position_size = unsafe { + String::from_utf8_unchecked( + std::process::Command::new("sh") + .arg("-c") + .arg(&format!(r#" xwininfo -id {window_id}|cut -f 2 -d :|tr -cd "0-9\n"|grep -v "^$"|sed -n "1,2p;5,6p" "#)) + .output() + .unwrap() + .stdout, + ) + }; + + let mut info = position_size.split("\n"); + + let left = info.next().unwrap().parse().unwrap(); + let top = info.next().unwrap().parse().unwrap(); + let width = info.next().unwrap().parse().unwrap(); + let height = info.next().unwrap().parse().unwrap(); + + let rect = Rect::new(left, top, width, height); + + GameInfo { + window: rect, + resolution_family: Resolution::new(rect.size), + is_cloud: false, + ui: UI::Desktop, + platform: Platform::Linux + } +} diff --git a/yas/src/game_info/os/macos.rs b/yas/src/game_info/os/macos.rs new file mode 100644 index 00000000..4a527dac --- /dev/null +++ b/yas/src/game_info/os/macos.rs @@ -0,0 +1,18 @@ +use crate::{common::utils::*, core::ui::Resolution}; +use crate::game_info::{GameInfo, Platform}; + +pub fn get_game_info() -> GameInfo { + let (pid, ui) = get_pid_and_ui(); + + let (rect, window_title) = unsafe { find_window_by_pid(pid).unwrap() }; + + info!("找到游戏窗口:{} (PID: {})", window_title, pid); + + GameInfo { + window: rect, + resolution_family: Resolution::new(rect.size), + is_cloud: false, + ui, + platform: Platform::MacOS + } +} diff --git a/yas/src/game_info/os/mod.rs b/yas/src/game_info/os/mod.rs new file mode 100644 index 00000000..cdf31c15 --- /dev/null +++ b/yas/src/game_info/os/mod.rs @@ -0,0 +1,14 @@ +#[cfg(windows)] +mod winodws; +#[cfg(windows)] +pub use winodws::*; + +#[cfg(target_os = "macos")] +mod macos; +#[cfg(target_os = "macos")] +pub use macos::*; + +#[cfg(target_os = "linux")] +mod linux; +#[cfg(target_os = "linux")] +pub use linux::*; diff --git a/yas/src/game_info/os/winodws.rs b/yas/src/game_info/os/winodws.rs new file mode 100644 index 00000000..d102c9d7 --- /dev/null +++ b/yas/src/game_info/os/winodws.rs @@ -0,0 +1,54 @@ +use crate::game_info::{GameInfo, ResolutionFamily, UI, Platform}; +use crate::utils; +use winapi::shared::windef::HWND; +use anyhow::{Result, anyhow}; + +fn get_window(window_names: &[&str]) -> Result<(HWND, bool)> { + // local game names + // let local_game_names = ["原神", "Genshin Impact"]; + for name in window_names.iter() { + let hwnd = utils::find_window_local(name); + if let Ok(hwnd) = hwnd { + return Ok((hwnd, false)); + } + } + + // cloud games + // let cloud_game_names = [""] + // for name in get_cloud_window_name() { + // let hwnd = utils::find_window_local(name); + // if let Ok(hwnd) = hwnd { + // return (hwnd, true); + // } + // } + + Err(anyhow!("未找到游戏窗口,请确认{:?}已经开启", window_names)) +} + +pub fn get_game_info(window_names: &[&str]) -> Result { + use winapi::um::winuser::{SetForegroundWindow, ShowWindow, SW_RESTORE}; + + utils::set_dpi_awareness(); + + let (hwnd, is_cloud) = get_window(window_names)?; + + unsafe { + ShowWindow(hwnd, SW_RESTORE); + } + + unsafe { + SetForegroundWindow(hwnd); + } + + utils::sleep(1000); + + let rect = utils::get_client_rect(hwnd)?; + + Ok(GameInfo { + window: rect, + resolution_family: ResolutionFamily::new(rect.to_rect_usize().size()).unwrap(), + is_cloud, + ui: UI::Desktop, + platform: Platform::Windows + }) +} diff --git a/yas/src/game_info/resolution_family.rs b/yas/src/game_info/resolution_family.rs new file mode 100644 index 00000000..ef739e8f --- /dev/null +++ b/yas/src/game_info/resolution_family.rs @@ -0,0 +1,39 @@ +use serde::{Deserialize, Serialize}; +use crate::positioning::Size; + +#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] +pub enum ResolutionFamily { + // PC + Windows43x18, + Windows7x3, + Windows16x9, + Windows8x5, + Windows4x3, + // Mobile + MacOS8x5, +} + +impl ResolutionFamily { + pub fn new(size: Size) -> Option { + // todo get OS at run time + + let height = size.height as u32; + let width = size.width as u32; + + if height * 43 == width * 18 { + Some(ResolutionFamily::Windows43x18) + } else if height * 16 == width * 9 { + Some(ResolutionFamily::Windows16x9) + } else if height * 8 == width * 5 { + Some(ResolutionFamily::Windows8x5) + } else if height * 4 == width * 3 { + Some(ResolutionFamily::Windows4x3) + } else if height * 7 == width * 3 { + Some(ResolutionFamily::Windows7x3) + } else if (height as i32 * 8 - width as i32 * 5).abs() < 20 { + Some(ResolutionFamily::MacOS8x5) + } else { + None + } + } +} diff --git a/yas/src/game_info/ui.rs b/yas/src/game_info/ui.rs new file mode 100644 index 00000000..e6026f74 --- /dev/null +++ b/yas/src/game_info/ui.rs @@ -0,0 +1,27 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize)] +pub enum UI { + Desktop, + Mobile, +} + +#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize)] +pub enum Platform { + Windows, + MacOS, + Linux, +} + +impl Platform { + pub fn current() -> Self { + #[cfg(target_os = "windows")] + return Self::Windows; + + #[cfg(target_os = "macos")] + return Self::MacOS; + + #[cfg(target_os = "linux")] + return Self::Linux; + } +} diff --git a/yas/src/lib.rs b/yas/src/lib.rs new file mode 100644 index 00000000..11090702 --- /dev/null +++ b/yas/src/lib.rs @@ -0,0 +1,17 @@ +#![feature(decl_macro)] +#![feature(concat_idents)] +#![allow(unused_imports)] + +extern crate log; +extern crate lazy_static; + +pub mod common; +pub mod export; +pub mod draw_capture_region; +pub mod capture; +pub mod utils; +pub mod game_info; +pub mod window_info; +pub mod system_control; +pub mod ocr; +pub mod positioning; diff --git a/yas/src/ocr/mod.rs b/yas/src/ocr/mod.rs new file mode 100644 index 00000000..306c6cc9 --- /dev/null +++ b/yas/src/ocr/mod.rs @@ -0,0 +1,10 @@ +mod traits; +mod yas_model; +mod paddle_paddle_model; + +pub use yas_model::yas_ocr_model::YasOCRModel; +pub use yas_model::yas_ocr_model::yas_ocr_model; +pub use traits::ImageToText; +pub use paddle_paddle_model::PPOCRModel; +pub use paddle_paddle_model::PPOCRChV4RecInfer; +pub use paddle_paddle_model::ppocr_model; diff --git a/yas/src/ocr/paddle_paddle_model/ch_PP-OCRv4_rec_infer.onnx b/yas/src/ocr/paddle_paddle_model/ch_PP-OCRv4_rec_infer.onnx new file mode 100644 index 00000000..750d2a21 --- /dev/null +++ b/yas/src/ocr/paddle_paddle_model/ch_PP-OCRv4_rec_infer.onnx @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:902666a62eb7ff8a6b954a8b706cdd52e224b4f32584f30eeb03202cf6a32aea +size 10826466 diff --git a/yas/src/ocr/paddle_paddle_model/chinese_cht_dict.txt b/yas/src/ocr/paddle_paddle_model/chinese_cht_dict.txt new file mode 100644 index 00000000..cc1aa472 --- /dev/null +++ b/yas/src/ocr/paddle_paddle_model/chinese_cht_dict.txt @@ -0,0 +1,8421 @@ +! +" +# +$ +% +& +' +( +) +* ++ +, +- +. +/ +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +: +; +< += +> +? +@ +A +B +C +D +E +F +G +H +I +J +K +L +M +N +O +P +Q +R +S +T +U +V +W +X +Y +Z +[ +\ +] +^ +_ +` +a +b +c +d +e +f +g +h +i +j +k +l +m +n +o +p +q +r +s +t +u +v +w +x +y +z +{ +| +} +~ +¥ +® +° +± +² +´ +· +» +É +Ë +Ó +× +Ü +à +á +ä +è +é +ì +í +ò +ó +÷ +ú +ü +ā +ē +ī +ō +ū +ǐ +ǒ +ɔ +ɡ +ʌ +ˋ +Λ +Ο +Φ +Ω +α +β +ε +θ +μ +π +З +И +Й +П +Я +г +— +‖ +‘ +’ +“ +” +• +… +‧ +′ +″ +※ +℃ +№ +™ +Ⅱ +Ⅲ +Ⅳ +← +↑ +→ +↓ +⇋ +∈ +∑ +√ +∞ +∣ +∧ +∩ +∫ +∶ +≈ +≠ +≤ +≥ +⊙ +⊥ +① +② +③ +④ +⑧ +⑴ +⑵ +⑶ +─ +│ +┅ +┌ +├ +█ +▎ +▏ +▕ +■ +□ +▪ +▲ +△ +▼ +◆ +◇ +○ +◎ +● +◥ +★ +☆ +❋ +❤ +  +、 +。 +〇 +〉 +《 +》 +「 +」 +『 +』 +【 +】 +〔 +〕 +〖 +〗 +の +サ +シ +ジ +マ +ㄱ +ㆍ +㎏ +㎡ +㐂 +㐱 +㙟 +㴪 +㸃 +䖝 +䝉 +䰾 +䲁 +一 +丁 +七 +丄 +丈 +三 +上 +下 +丌 +不 +与 +丏 +丐 +丑 +且 +丕 +世 +丘 +丙 +丞 +丟 +両 +並 +丨 +丫 +中 +丰 +串 +丶 +丸 +丹 +主 +丼 +丿 +乂 +乃 +久 +么 +之 +乍 +乎 +乏 +乒 +乓 +乖 +乗 +乘 +乙 +乚 +乜 +九 +乞 +也 +乩 +乭 +乳 +乸 +乹 +乾 +亀 +亂 +亅 +了 +予 +亊 +事 +二 +亍 +云 +互 +亓 +五 +井 +亘 +些 +亜 +亞 +亟 +亠 +亡 +亢 +交 +亥 +亦 +亨 +享 +京 +亭 +亮 +亰 +亳 +亶 +亹 +人 +亻 +什 +仁 +仂 +仃 +仄 +仇 +仉 +今 +介 +仍 +仏 +仔 +仕 +他 +仗 +付 +仙 +仛 +仝 +仞 +仟 +仡 +代 +令 +以 +仨 +仫 +仮 +仰 +仲 +仳 +仵 +件 +仺 +任 +仼 +份 +仿 +企 +伃 +伈 +伉 +伊 +伋 +伍 +伎 +伏 +伐 +休 +伕 +伙 +伝 +伢 +伯 +估 +伱 +伴 +伶 +伷 +伸 +伺 +似 +伽 +伾 +佀 +佁 +佃 +但 +佇 +佈 +佉 +佋 +位 +低 +住 +佐 +佑 +体 +佔 +何 +佗 +佘 +余 +佚 +佛 +作 +佝 +佞 +佟 +你 +佣 +佤 +佧 +佩 +佬 +佯 +佰 +佳 +併 +佶 +佹 +佺 +佼 +佾 +使 +侁 +侃 +侄 +侅 +來 +侈 +侊 +例 +侍 +侏 +侑 +侖 +侗 +侘 +侚 +供 +依 +侞 +価 +侮 +侯 +侵 +侶 +侷 +侹 +便 +俁 +係 +促 +俄 +俅 +俊 +俋 +俌 +俍 +俎 +俏 +俐 +俑 +俗 +俘 +俚 +俛 +保 +俞 +俟 +俠 +信 +俬 +修 +俯 +俱 +俳 +俴 +俵 +俶 +俸 +俺 +俽 +俾 +倆 +倈 +倉 +個 +倌 +倍 +們 +倒 +倓 +倔 +倖 +倗 +倘 +候 +倚 +倜 +倞 +借 +倡 +倢 +倣 +値 +倦 +倧 +倩 +倪 +倫 +倬 +倭 +倮 +倻 +值 +偁 +偃 +假 +偈 +偉 +偊 +偌 +偍 +偎 +偏 +偓 +偕 +做 +停 +健 +偪 +偲 +側 +偵 +偶 +偷 +偸 +偽 +傀 +傃 +傅 +傈 +傉 +傍 +傑 +傒 +傕 +傖 +傘 +備 +傜 +傢 +傣 +催 +傭 +傲 +傳 +債 +傷 +傻 +傾 +僅 +僉 +僊 +働 +像 +僑 +僔 +僕 +僖 +僙 +僚 +僜 +僡 +僧 +僩 +僭 +僮 +僰 +僱 +僳 +僴 +僵 +價 +僻 +儀 +儁 +儂 +億 +儆 +儇 +儈 +儉 +儋 +儐 +儒 +儔 +儕 +儘 +儚 +儞 +償 +儡 +儥 +儦 +優 +儫 +儱 +儲 +儷 +儺 +儻 +儼 +兀 +允 +元 +兄 +充 +兆 +先 +光 +克 +兌 +免 +児 +兒 +兔 +兕 +兗 +兜 +入 +內 +全 +兩 +兪 +八 +公 +六 +兮 +共 +兵 +其 +具 +典 +兼 +兿 +冀 +冂 +円 +冇 +冉 +冊 +再 +冏 +冑 +冒 +冕 +冖 +冗 +冚 +冠 +冢 +冤 +冥 +冧 +冨 +冪 +冫 +冬 +冮 +冰 +冴 +冶 +冷 +冼 +冽 +凃 +凄 +准 +凈 +凋 +凌 +凍 +凖 +凜 +凝 +凞 +几 +凡 +処 +凪 +凬 +凰 +凱 +凳 +凵 +凶 +凸 +凹 +出 +函 +刀 +刁 +刂 +刃 +刄 +分 +切 +刈 +刊 +刎 +刑 +划 +列 +初 +判 +別 +刦 +刧 +刨 +利 +刪 +刮 +到 +制 +刷 +券 +刺 +刻 +刼 +剁 +剃 +則 +削 +剋 +剌 +前 +剎 +剏 +剔 +剖 +剛 +剝 +剡 +剣 +剩 +剪 +剮 +副 +割 +創 +剿 +劃 +劄 +劇 +劈 +劉 +劊 +劌 +劍 +劑 +劔 +力 +功 +加 +劣 +助 +努 +劫 +劬 +劭 +劵 +効 +劼 +劾 +勁 +勃 +勅 +勇 +勉 +勐 +勑 +勒 +勔 +動 +勖 +勗 +勘 +務 +勛 +勝 +勞 +募 +勢 +勣 +勤 +勦 +勰 +勱 +勲 +勳 +勵 +勷 +勸 +勺 +勻 +勾 +勿 +匂 +匄 +包 +匆 +匈 +匋 +匍 +匏 +匐 +匕 +化 +北 +匙 +匚 +匝 +匠 +匡 +匣 +匪 +匯 +匱 +匸 +匹 +匾 +匿 +區 +十 +千 +卅 +升 +午 +卉 +半 +卋 +卍 +卐 +卑 +卒 +卓 +協 +南 +博 +卜 +卞 +卟 +占 +卡 +卣 +卦 +卧 +卩 +卬 +卮 +卯 +印 +危 +卲 +即 +卵 +卷 +卸 +卹 +卺 +卻 +卽 +卿 +厄 +厓 +厔 +厙 +厚 +厝 +原 +厥 +厭 +厰 +厲 +厴 +厶 +去 +參 +叄 +又 +叉 +及 +友 +反 +収 +叔 +叕 +取 +受 +叛 +叟 +叡 +叢 +口 +古 +句 +另 +叨 +叩 +只 +叫 +召 +叭 +叮 +可 +台 +叱 +史 +右 +叵 +司 +叻 +叼 +吁 +吃 +各 +吆 +合 +吉 +吊 +吋 +同 +名 +后 +吏 +吐 +向 +吒 +吔 +吖 +君 +吝 +吞 +吟 +吠 +吡 +吥 +否 +吧 +吩 +含 +吮 +吱 +吲 +吳 +吵 +吶 +吸 +吹 +吻 +吼 +吾 +呀 +呂 +呃 +呈 +呉 +告 +呋 +呎 +呢 +呤 +呦 +周 +呱 +味 +呵 +呷 +呸 +呼 +命 +呾 +咀 +咁 +咂 +咄 +咅 +咆 +咋 +和 +咎 +咑 +咒 +咔 +咕 +咖 +咗 +咘 +咚 +咟 +咤 +咥 +咧 +咨 +咩 +咪 +咫 +咬 +咭 +咯 +咱 +咲 +咳 +咸 +咻 +咼 +咽 +咾 +咿 +哀 +品 +哂 +哄 +哆 +哇 +哈 +哉 +哌 +哎 +哏 +哐 +哖 +哚 +哞 +員 +哥 +哦 +哨 +哩 +哪 +哭 +哮 +哱 +哲 +哺 +哼 +唃 +唄 +唆 +唇 +唉 +唏 +唐 +唑 +唔 +唘 +唧 +唫 +唬 +唭 +售 +唯 +唱 +唳 +唵 +唷 +唸 +唻 +唾 +啁 +啃 +啄 +商 +啉 +啊 +啍 +問 +啓 +啖 +啚 +啜 +啞 +啟 +啡 +啣 +啤 +啥 +啦 +啪 +啫 +啯 +啰 +啱 +啲 +啵 +啶 +啷 +啻 +啼 +啾 +喀 +喂 +喃 +善 +喆 +喇 +喈 +喉 +喊 +喋 +喏 +喔 +喘 +喙 +喚 +喜 +喝 +喢 +喦 +喧 +喪 +喫 +喬 +單 +喰 +喱 +喲 +喳 +喵 +喹 +喻 +喼 +嗄 +嗅 +嗆 +嗇 +嗊 +嗎 +嗑 +嗒 +嗓 +嗔 +嗖 +嗚 +嗜 +嗝 +嗞 +嗡 +嗢 +嗣 +嗦 +嗨 +嗩 +嗪 +嗮 +嗯 +嗲 +嗶 +嗹 +嗽 +嘀 +嘅 +嘆 +嘉 +嘌 +嘍 +嘎 +嘏 +嘔 +嘗 +嘚 +嘛 +嘜 +嘞 +嘟 +嘢 +嘣 +嘥 +嘧 +嘩 +嘬 +嘮 +嘯 +嘰 +嘲 +嘴 +嘶 +嘸 +嘹 +嘻 +嘿 +噁 +噌 +噍 +噏 +噓 +噗 +噝 +噠 +噢 +噤 +噥 +噦 +器 +噩 +噪 +噬 +噯 +噰 +噲 +噴 +噶 +噸 +噹 +噻 +嚇 +嚈 +嚎 +嚏 +嚐 +嚒 +嚓 +嚕 +嚗 +嚙 +嚞 +嚟 +嚤 +嚦 +嚧 +嚨 +嚩 +嚮 +嚳 +嚴 +嚶 +嚷 +嚼 +嚿 +囀 +囂 +囃 +囉 +囊 +囍 +囑 +囒 +囓 +囗 +囚 +四 +囝 +回 +因 +囡 +団 +囤 +囧 +囪 +囮 +囯 +困 +囲 +図 +囶 +囷 +囹 +固 +囿 +圂 +圃 +圄 +圈 +圉 +國 +圍 +圏 +園 +圓 +圖 +圗 +團 +圜 +土 +圧 +在 +圩 +圪 +圭 +圯 +地 +圳 +圻 +圾 +址 +均 +坊 +坋 +坌 +坍 +坎 +坐 +坑 +坖 +坡 +坣 +坤 +坦 +坨 +坩 +坪 +坫 +坬 +坭 +坮 +坯 +坳 +坵 +坶 +坷 +坻 +垂 +垃 +垈 +型 +垍 +垓 +垕 +垚 +垛 +垞 +垟 +垠 +垢 +垣 +垮 +垯 +垰 +垵 +垸 +垻 +垿 +埃 +埅 +埇 +埈 +埋 +埌 +城 +埏 +埒 +埔 +埕 +埗 +埜 +域 +埠 +埡 +埤 +埧 +埨 +埪 +埭 +埮 +埴 +埵 +執 +培 +基 +埻 +埼 +堀 +堂 +堃 +堅 +堆 +堇 +堈 +堉 +堊 +堍 +堖 +堝 +堡 +堤 +堦 +堪 +堮 +堯 +堰 +報 +場 +堵 +堷 +堺 +塀 +塅 +塆 +塊 +塋 +塌 +塍 +塏 +塑 +塔 +塗 +塘 +塙 +塜 +塞 +塡 +塢 +塤 +塨 +塩 +填 +塬 +塭 +塰 +塱 +塲 +塵 +塹 +塽 +塾 +墀 +境 +墅 +墉 +墊 +墎 +墓 +増 +墘 +墜 +增 +墟 +墡 +墣 +墨 +墩 +墫 +墬 +墮 +墱 +墳 +墺 +墼 +墾 +壁 +壄 +壆 +壇 +壋 +壌 +壎 +壐 +壑 +壓 +壔 +壕 +壘 +壙 +壞 +壟 +壠 +壢 +壤 +壩 +士 +壬 +壯 +壱 +壴 +壹 +壺 +壽 +夀 +夆 +変 +夊 +夋 +夌 +夏 +夔 +夕 +外 +夙 +多 +夜 +夠 +夢 +夤 +夥 +大 +天 +太 +夫 +夬 +夭 +央 +夯 +失 +夷 +夾 +奀 +奄 +奇 +奈 +奉 +奎 +奏 +奐 +契 +奓 +奔 +奕 +套 +奘 +奚 +奠 +奢 +奣 +奧 +奩 +奪 +奫 +奭 +奮 +女 +奴 +奶 +她 +好 +妀 +妁 +如 +妃 +妄 +妊 +妍 +妏 +妑 +妒 +妓 +妖 +妙 +妝 +妞 +妠 +妤 +妥 +妧 +妨 +妭 +妮 +妯 +妲 +妳 +妸 +妹 +妺 +妻 +妾 +姀 +姁 +姃 +姆 +姈 +姉 +姊 +始 +姌 +姍 +姐 +姑 +姒 +姓 +委 +姚 +姜 +姝 +姣 +姥 +姦 +姨 +姪 +姫 +姬 +姮 +姵 +姶 +姸 +姻 +姿 +威 +娃 +娉 +娋 +娌 +娍 +娎 +娑 +娖 +娘 +娛 +娜 +娟 +娠 +娣 +娥 +娩 +娫 +娳 +娶 +娸 +娼 +娽 +婀 +婁 +婆 +婉 +婊 +婑 +婕 +婚 +婢 +婦 +婧 +婪 +婭 +婯 +婷 +婺 +婻 +婼 +婿 +媃 +媄 +媊 +媐 +媒 +媓 +媖 +媗 +媚 +媛 +媜 +媞 +媧 +媭 +媯 +媲 +媳 +媺 +媼 +媽 +媾 +媿 +嫁 +嫂 +嫄 +嫈 +嫉 +嫌 +嫖 +嫘 +嫚 +嫡 +嫣 +嫦 +嫩 +嫪 +嫲 +嫳 +嫵 +嫺 +嫻 +嬅 +嬈 +嬉 +嬋 +嬌 +嬗 +嬛 +嬝 +嬡 +嬤 +嬨 +嬪 +嬬 +嬭 +嬰 +嬴 +嬸 +嬾 +嬿 +孀 +孃 +孆 +孋 +孌 +子 +孑 +孔 +孕 +孖 +字 +存 +孚 +孛 +孜 +孝 +孟 +孢 +季 +孤 +孩 +孫 +孬 +孮 +孰 +孳 +孵 +學 +孺 +孻 +孽 +孿 +宀 +它 +宅 +宇 +守 +安 +宋 +完 +宍 +宏 +宓 +宕 +宗 +官 +宙 +定 +宛 +宜 +実 +客 +宣 +室 +宥 +宦 +宧 +宮 +宰 +害 +宴 +宵 +家 +宸 +容 +宿 +寀 +寁 +寂 +寄 +寅 +密 +寇 +寈 +寊 +富 +寐 +寒 +寓 +寔 +寕 +寖 +寗 +寘 +寛 +寜 +寞 +察 +寡 +寢 +寤 +寥 +實 +寧 +寨 +審 +寫 +寬 +寮 +寯 +寰 +寳 +寵 +寶 +寸 +寺 +対 +封 +専 +尃 +射 +將 +專 +尉 +尊 +尋 +對 +導 +小 +尐 +少 +尓 +尕 +尖 +尗 +尙 +尚 +尢 +尤 +尨 +尪 +尬 +就 +尷 +尹 +尺 +尻 +尼 +尾 +尿 +局 +屁 +屄 +居 +屆 +屇 +屈 +屋 +屌 +屍 +屎 +屏 +屐 +屑 +屓 +展 +屚 +屜 +屠 +屢 +層 +履 +屬 +屭 +屯 +山 +屹 +屺 +屻 +岀 +岈 +岌 +岐 +岑 +岔 +岡 +岢 +岣 +岧 +岩 +岪 +岫 +岬 +岰 +岱 +岳 +岵 +岷 +岸 +岻 +峁 +峅 +峇 +峋 +峍 +峒 +峘 +峙 +峚 +峠 +峨 +峩 +峪 +峭 +峯 +峰 +峴 +島 +峻 +峼 +峽 +崁 +崆 +崇 +崈 +崋 +崍 +崎 +崐 +崑 +崒 +崔 +崖 +崗 +崘 +崙 +崚 +崛 +崞 +崟 +崠 +崢 +崤 +崧 +崩 +崬 +崮 +崱 +崴 +崵 +崶 +崽 +嵇 +嵊 +嵋 +嵌 +嵎 +嵐 +嵒 +嵕 +嵖 +嵗 +嵙 +嵛 +嵜 +嵨 +嵩 +嵬 +嵮 +嵯 +嵰 +嵴 +嵻 +嵿 +嶁 +嶂 +嶃 +嶄 +嶇 +嶋 +嶌 +嶍 +嶒 +嶔 +嶗 +嶝 +嶠 +嶢 +嶦 +嶧 +嶪 +嶬 +嶰 +嶲 +嶴 +嶷 +嶸 +嶺 +嶼 +嶽 +巂 +巄 +巆 +巋 +巌 +巍 +巎 +巑 +巒 +巔 +巖 +巘 +巛 +川 +州 +巡 +巢 +工 +左 +巧 +巨 +巫 +差 +巰 +己 +已 +巳 +巴 +巶 +巷 +巻 +巽 +巾 +巿 +市 +布 +帆 +希 +帑 +帔 +帕 +帖 +帘 +帙 +帚 +帛 +帝 +帡 +帢 +帥 +師 +席 +帯 +帰 +帳 +帶 +帷 +常 +帽 +幀 +幃 +幄 +幅 +幌 +幔 +幕 +幗 +幚 +幛 +幟 +幡 +幢 +幣 +幪 +幫 +干 +平 +年 +幵 +幷 +幸 +幹 +幺 +幻 +幼 +幽 +幾 +庀 +庁 +広 +庇 +床 +序 +底 +庖 +店 +庚 +府 +庠 +庢 +庥 +度 +座 +庫 +庭 +庲 +庵 +庶 +康 +庸 +庹 +庼 +庾 +廁 +廂 +廄 +廆 +廈 +廉 +廊 +廋 +廌 +廍 +廑 +廓 +廔 +廕 +廖 +廙 +廚 +廝 +廞 +廟 +廠 +廡 +廢 +廣 +廧 +廨 +廩 +廬 +廰 +廱 +廳 +延 +廷 +廸 +建 +廻 +廼 +廿 +弁 +弄 +弅 +弇 +弈 +弉 +弊 +弋 +弍 +式 +弐 +弒 +弓 +弔 +引 +弖 +弗 +弘 +弛 +弟 +弢 +弦 +弧 +弨 +弩 +弭 +弱 +張 +強 +弸 +弼 +弾 +彀 +彄 +彅 +彆 +彈 +彊 +彌 +彎 +彐 +彔 +彖 +彗 +彘 +彙 +彜 +彞 +彠 +彡 +形 +彣 +彤 +彥 +彧 +彩 +彪 +彫 +彬 +彭 +彰 +影 +彳 +彷 +役 +彼 +彿 +往 +征 +徂 +待 +徇 +很 +徉 +徊 +律 +後 +徐 +徑 +徒 +得 +徘 +徙 +徜 +從 +徠 +御 +徧 +徨 +復 +循 +徫 +徬 +徭 +微 +徳 +徴 +徵 +德 +徸 +徹 +徽 +心 +忄 +必 +忉 +忌 +忍 +忐 +忑 +忒 +志 +忘 +忙 +応 +忝 +忞 +忠 +快 +忬 +忯 +忱 +忳 +念 +忻 +忽 +忿 +怍 +怎 +怒 +怕 +怖 +怙 +怛 +思 +怠 +怡 +急 +怦 +性 +怨 +怪 +怯 +怵 +恁 +恂 +恃 +恆 +恊 +恍 +恐 +恕 +恙 +恢 +恣 +恤 +恥 +恨 +恩 +恪 +恬 +恭 +息 +恰 +恵 +恿 +悄 +悅 +悆 +悉 +悌 +悍 +悔 +悖 +悚 +悛 +悝 +悞 +悟 +悠 +患 +悧 +您 +悪 +悰 +悲 +悳 +悵 +悶 +悸 +悼 +情 +惆 +惇 +惑 +惔 +惕 +惘 +惚 +惜 +惟 +惠 +惡 +惣 +惦 +惰 +惱 +惲 +想 +惶 +惹 +惺 +愁 +愃 +愆 +愈 +愉 +愍 +意 +愐 +愒 +愔 +愕 +愚 +愛 +愜 +感 +愣 +愧 +愨 +愫 +愭 +愴 +愷 +愼 +愾 +愿 +慄 +慈 +態 +慌 +慎 +慕 +慘 +慚 +慜 +慟 +慢 +慣 +慥 +慧 +慨 +慮 +慰 +慳 +慵 +慶 +慷 +慾 +憂 +憊 +憋 +憍 +憎 +憐 +憑 +憓 +憕 +憙 +憚 +憤 +憧 +憨 +憩 +憫 +憬 +憲 +憶 +憺 +憻 +憾 +懂 +懃 +懇 +懈 +應 +懋 +懌 +懍 +懐 +懣 +懦 +懮 +懲 +懵 +懶 +懷 +懸 +懺 +懼 +懽 +懾 +懿 +戀 +戇 +戈 +戊 +戌 +戍 +戎 +成 +我 +戒 +戔 +戕 +或 +戙 +戚 +戛 +戟 +戡 +戢 +戥 +戦 +戩 +截 +戮 +戰 +戱 +戲 +戳 +戴 +戶 +戸 +戻 +戽 +戾 +房 +所 +扁 +扆 +扇 +扈 +扉 +手 +扌 +才 +扎 +扒 +打 +扔 +托 +扙 +扛 +扞 +扣 +扥 +扦 +扭 +扮 +扯 +扳 +扶 +批 +扼 +找 +承 +技 +抃 +抄 +抇 +抉 +把 +抑 +抒 +抓 +投 +抖 +抗 +折 +抦 +披 +抬 +抱 +抵 +抹 +抻 +押 +抽 +抿 +拂 +拆 +拇 +拈 +拉 +拋 +拌 +拍 +拎 +拏 +拐 +拒 +拓 +拔 +拖 +拗 +拘 +拙 +拚 +招 +拜 +拝 +拡 +括 +拭 +拮 +拯 +拱 +拳 +拴 +拷 +拺 +拼 +拽 +拾 +拿 +持 +指 +按 +挎 +挑 +挖 +挙 +挨 +挪 +挫 +振 +挲 +挵 +挹 +挺 +挻 +挾 +捂 +捆 +捉 +捌 +捍 +捎 +捏 +捐 +捒 +捕 +捜 +捦 +捧 +捨 +捩 +捫 +捭 +捱 +捲 +捶 +捷 +捺 +捻 +掀 +掂 +掃 +掄 +掇 +授 +掉 +掌 +掏 +掐 +排 +掖 +掘 +掙 +掛 +掞 +掟 +掠 +採 +探 +掣 +接 +控 +推 +掩 +措 +掬 +掰 +掾 +揀 +揄 +揆 +揉 +揍 +描 +提 +插 +揔 +揖 +揚 +換 +握 +揪 +揭 +揮 +援 +揸 +揺 +損 +搏 +搐 +搓 +搔 +搖 +搗 +搜 +搞 +搠 +搢 +搪 +搬 +搭 +搳 +搴 +搵 +搶 +搽 +搾 +摂 +摒 +摔 +摘 +摜 +摞 +摟 +摠 +摧 +摩 +摭 +摯 +摳 +摴 +摵 +摶 +摸 +摹 +摺 +摻 +摽 +撃 +撇 +撈 +撐 +撒 +撓 +撕 +撖 +撙 +撚 +撞 +撣 +撤 +撥 +撩 +撫 +撬 +播 +撮 +撰 +撲 +撳 +撻 +撼 +撾 +撿 +擀 +擁 +擂 +擅 +擇 +擊 +擋 +操 +擎 +擒 +擔 +擘 +據 +擠 +擢 +擥 +擦 +擬 +擯 +擰 +擱 +擲 +擴 +擷 +擺 +擼 +擾 +攀 +攏 +攔 +攖 +攘 +攜 +攝 +攞 +攢 +攣 +攤 +攪 +攫 +攬 +支 +攴 +攵 +收 +攷 +攸 +改 +攻 +攽 +放 +政 +故 +效 +敍 +敎 +敏 +救 +敔 +敕 +敖 +敗 +敘 +教 +敝 +敞 +敟 +敢 +散 +敦 +敫 +敬 +敭 +敲 +整 +敵 +敷 +數 +敻 +敾 +斂 +斃 +文 +斌 +斎 +斐 +斑 +斕 +斖 +斗 +料 +斛 +斜 +斝 +斟 +斡 +斤 +斥 +斧 +斬 +斯 +新 +斷 +方 +於 +施 +斿 +旁 +旂 +旃 +旄 +旅 +旉 +旋 +旌 +旎 +族 +旖 +旗 +旙 +旛 +旡 +既 +日 +旦 +旨 +早 +旬 +旭 +旱 +旲 +旳 +旺 +旻 +旼 +旽 +旾 +旿 +昀 +昂 +昃 +昆 +昇 +昉 +昊 +昌 +昍 +明 +昏 +昐 +易 +昔 +昕 +昚 +昛 +昜 +昝 +昞 +星 +映 +昡 +昣 +昤 +春 +昧 +昨 +昪 +昫 +昭 +是 +昰 +昱 +昴 +昵 +昶 +昺 +晁 +時 +晃 +晈 +晉 +晊 +晏 +晗 +晙 +晚 +晛 +晝 +晞 +晟 +晤 +晦 +晧 +晨 +晩 +晪 +晫 +晭 +普 +景 +晰 +晳 +晴 +晶 +晷 +晸 +智 +晾 +暃 +暄 +暅 +暇 +暈 +暉 +暊 +暌 +暎 +暏 +暐 +暑 +暕 +暖 +暗 +暘 +暝 +暟 +暠 +暢 +暦 +暨 +暫 +暮 +暱 +暲 +暴 +暸 +暹 +暻 +暾 +曄 +曅 +曆 +曇 +曉 +曌 +曔 +曖 +曙 +曜 +曝 +曠 +曦 +曧 +曨 +曩 +曬 +曮 +曰 +曲 +曳 +更 +曶 +曷 +書 +曹 +曺 +曼 +曽 +曾 +替 +最 +會 +月 +有 +朊 +朋 +服 +朏 +朐 +朓 +朔 +朕 +朖 +朗 +望 +朝 +期 +朦 +朧 +木 +未 +末 +本 +札 +朱 +朴 +朵 +朶 +朽 +朿 +杁 +杉 +杋 +杌 +李 +杏 +材 +村 +杓 +杖 +杙 +杜 +杞 +束 +杠 +杣 +杤 +杧 +杬 +杭 +杯 +東 +杲 +杳 +杴 +杵 +杷 +杻 +杼 +松 +板 +极 +枇 +枉 +枋 +枏 +析 +枕 +枖 +林 +枚 +枛 +果 +枝 +枠 +枡 +枯 +枰 +枱 +枲 +枳 +架 +枷 +枸 +枹 +枼 +柁 +柃 +柄 +柉 +柊 +柎 +柏 +某 +柑 +柒 +染 +柔 +柘 +柚 +柜 +柝 +柞 +柟 +查 +柩 +柬 +柯 +柰 +柱 +柳 +柴 +柵 +柶 +柷 +査 +柾 +柿 +栃 +栄 +栐 +栒 +栓 +栜 +栝 +栞 +校 +栢 +栨 +栩 +株 +栲 +栴 +核 +根 +栻 +格 +栽 +桀 +桁 +桂 +桃 +桄 +桅 +框 +案 +桉 +桌 +桎 +桐 +桑 +桓 +桔 +桕 +桖 +桙 +桜 +桝 +桫 +桱 +桲 +桴 +桶 +桷 +桼 +桿 +梀 +梁 +梂 +梃 +梅 +梆 +梉 +梏 +梓 +梔 +梗 +梘 +條 +梟 +梠 +梢 +梣 +梧 +梨 +梫 +梭 +梯 +械 +梱 +梳 +梵 +梶 +梽 +棄 +棆 +棉 +棋 +棍 +棐 +棒 +棓 +棕 +棖 +棗 +棘 +棚 +棛 +棟 +棠 +棡 +棣 +棧 +棨 +棩 +棪 +棫 +森 +棱 +棲 +棵 +棶 +棹 +棺 +棻 +棼 +棽 +椅 +椆 +椇 +椋 +植 +椎 +椏 +椒 +椙 +椥 +椪 +椰 +椲 +椴 +椵 +椹 +椽 +椿 +楂 +楊 +楓 +楔 +楗 +楙 +楚 +楝 +楞 +楠 +楡 +楢 +楣 +楤 +楦 +楧 +楨 +楫 +業 +楮 +楯 +楳 +極 +楷 +楸 +楹 +楽 +楿 +概 +榆 +榊 +榍 +榎 +榑 +榔 +榕 +榖 +榗 +榘 +榛 +榜 +榞 +榢 +榣 +榤 +榦 +榧 +榨 +榫 +榭 +榮 +榲 +榴 +榷 +榻 +榿 +槀 +槁 +槃 +槊 +構 +槌 +槍 +槎 +槐 +槓 +槔 +槗 +様 +槙 +槤 +槩 +槭 +槰 +槱 +槲 +槳 +槺 +槻 +槼 +槽 +槿 +樀 +樁 +樂 +樅 +樆 +樊 +樋 +樑 +樓 +樗 +樘 +標 +樞 +樟 +模 +樣 +樨 +権 +樫 +樵 +樸 +樹 +樺 +樻 +樽 +樾 +橄 +橇 +橈 +橋 +橐 +橒 +橓 +橘 +橙 +橚 +機 +橡 +橢 +橪 +橫 +橿 +檀 +檄 +檇 +檉 +檊 +檎 +檐 +檔 +檗 +檜 +檞 +檠 +檡 +檢 +檣 +檦 +檨 +檫 +檬 +檯 +檳 +檵 +檸 +檻 +檽 +櫂 +櫃 +櫆 +櫈 +櫓 +櫚 +櫛 +櫞 +櫟 +櫥 +櫨 +櫪 +櫱 +櫸 +櫻 +櫾 +櫿 +欄 +欉 +權 +欏 +欒 +欖 +欞 +欠 +次 +欣 +欥 +欲 +欸 +欹 +欺 +欽 +款 +歆 +歇 +歉 +歊 +歌 +歎 +歐 +歓 +歙 +歛 +歡 +止 +正 +此 +步 +武 +歧 +歩 +歪 +歲 +歳 +歴 +歷 +歸 +歹 +死 +歿 +殂 +殃 +殄 +殆 +殉 +殊 +殑 +殖 +殘 +殛 +殞 +殟 +殤 +殭 +殮 +殯 +殲 +殳 +段 +殷 +殺 +殻 +殼 +殿 +毀 +毅 +毆 +毉 +毋 +毌 +母 +毎 +每 +毐 +毒 +毓 +比 +毖 +毗 +毘 +毛 +毫 +毬 +毯 +毴 +毸 +毽 +毿 +氂 +氈 +氍 +氏 +氐 +民 +氓 +氖 +気 +氘 +氙 +氚 +氛 +氟 +氣 +氦 +氧 +氨 +氪 +氫 +氬 +氮 +氯 +氰 +水 +氵 +氷 +永 +氹 +氻 +氽 +氾 +汀 +汁 +求 +汊 +汎 +汐 +汕 +汗 +汛 +汜 +汝 +汞 +江 +池 +污 +汧 +汨 +汩 +汪 +汭 +汰 +汲 +汴 +汶 +決 +汽 +汾 +沁 +沂 +沃 +沄 +沅 +沆 +沇 +沈 +沉 +沌 +沍 +沏 +沐 +沒 +沓 +沔 +沖 +沘 +沙 +沚 +沛 +沜 +沢 +沨 +沫 +沭 +沮 +沯 +沱 +河 +沸 +油 +沺 +治 +沼 +沽 +沾 +沿 +況 +泂 +泄 +泆 +泇 +泉 +泊 +泌 +泐 +泓 +泔 +法 +泖 +泗 +泚 +泛 +泠 +泡 +波 +泣 +泥 +泩 +泫 +泮 +泯 +泰 +泱 +泳 +泵 +洄 +洋 +洌 +洎 +洗 +洙 +洛 +洞 +洢 +洣 +洤 +津 +洨 +洩 +洪 +洮 +洱 +洲 +洳 +洵 +洸 +洹 +洺 +活 +洽 +派 +流 +浄 +浙 +浚 +浛 +浜 +浞 +浟 +浠 +浡 +浣 +浤 +浥 +浦 +浩 +浪 +浮 +浯 +浴 +浵 +海 +浸 +浹 +涅 +涇 +消 +涉 +涌 +涎 +涑 +涓 +涔 +涕 +涙 +涪 +涫 +涮 +涯 +液 +涵 +涸 +涼 +涿 +淄 +淅 +淆 +淇 +淋 +淌 +淍 +淎 +淏 +淑 +淓 +淖 +淘 +淙 +淚 +淛 +淝 +淞 +淠 +淡 +淤 +淥 +淦 +淨 +淩 +淪 +淫 +淬 +淮 +淯 +淰 +深 +淳 +淵 +淶 +混 +淸 +淹 +淺 +添 +淼 +淽 +渃 +清 +済 +渉 +渋 +渕 +渙 +渚 +減 +渝 +渟 +渠 +渡 +渣 +渤 +渥 +渦 +渫 +測 +渭 +港 +渲 +渴 +游 +渺 +渼 +渽 +渾 +湃 +湄 +湉 +湊 +湍 +湓 +湔 +湖 +湘 +湛 +湜 +湞 +湟 +湣 +湥 +湧 +湫 +湮 +湯 +湳 +湴 +湼 +満 +溁 +溇 +溈 +溉 +溋 +溎 +溏 +源 +準 +溙 +溜 +溝 +溟 +溢 +溥 +溦 +溧 +溪 +溫 +溯 +溱 +溲 +溴 +溵 +溶 +溺 +溼 +滀 +滁 +滂 +滄 +滅 +滇 +滈 +滉 +滋 +滌 +滎 +滏 +滑 +滓 +滔 +滕 +滘 +滙 +滝 +滬 +滯 +滲 +滴 +滷 +滸 +滹 +滻 +滽 +滾 +滿 +漁 +漂 +漆 +漇 +漈 +漎 +漏 +漓 +演 +漕 +漚 +漠 +漢 +漣 +漩 +漪 +漫 +漬 +漯 +漱 +漲 +漳 +漴 +漵 +漷 +漸 +漼 +漾 +漿 +潁 +潑 +潔 +潘 +潛 +潞 +潟 +潢 +潤 +潭 +潮 +潯 +潰 +潲 +潺 +潼 +潽 +潾 +潿 +澀 +澁 +澂 +澄 +澆 +澇 +澈 +澉 +澋 +澌 +澍 +澎 +澔 +澗 +澠 +澡 +澣 +澤 +澥 +澧 +澪 +澮 +澯 +澱 +澳 +澶 +澹 +澻 +激 +濁 +濂 +濃 +濉 +濊 +濋 +濕 +濘 +濙 +濛 +濞 +濟 +濠 +濡 +濤 +濫 +濬 +濮 +濯 +濰 +濱 +濲 +濶 +濺 +濼 +濾 +瀁 +瀅 +瀆 +瀉 +瀍 +瀏 +瀑 +瀔 +瀕 +瀘 +瀚 +瀛 +瀝 +瀞 +瀟 +瀠 +瀣 +瀦 +瀧 +瀨 +瀬 +瀰 +瀲 +瀴 +瀶 +瀹 +瀾 +灃 +灊 +灌 +灑 +灘 +灝 +灞 +灡 +灣 +灤 +灧 +火 +灰 +灴 +灸 +灼 +災 +炁 +炅 +炆 +炊 +炎 +炒 +炔 +炕 +炘 +炙 +炟 +炣 +炤 +炫 +炬 +炭 +炮 +炯 +炱 +炲 +炳 +炷 +炸 +為 +炻 +烈 +烉 +烊 +烋 +烏 +烒 +烔 +烘 +烙 +烜 +烝 +烤 +烯 +烱 +烴 +烷 +烹 +烺 +烽 +焃 +焄 +焉 +焊 +焌 +焓 +焗 +焙 +焚 +焜 +焞 +無 +焦 +焯 +焰 +焱 +焴 +然 +焻 +焼 +焿 +煇 +煉 +煊 +煌 +煎 +煐 +煒 +煔 +煕 +煖 +煙 +煚 +煜 +煞 +煠 +煤 +煥 +煦 +照 +煨 +煩 +煬 +煮 +煲 +煳 +煵 +煶 +煸 +煽 +熄 +熅 +熇 +熈 +熊 +熏 +熒 +熔 +熖 +熗 +熘 +熙 +熜 +熟 +熠 +熤 +熥 +熨 +熬 +熯 +熱 +熲 +熳 +熵 +熹 +熺 +熼 +熾 +熿 +燁 +燃 +燄 +燈 +燉 +燊 +燎 +燏 +燐 +燒 +燔 +燕 +燘 +燙 +燚 +燜 +燝 +營 +燥 +燦 +燧 +燫 +燬 +燭 +燮 +燴 +燹 +燻 +燼 +燾 +燿 +爀 +爆 +爌 +爍 +爐 +爔 +爚 +爛 +爝 +爨 +爪 +爬 +爭 +爯 +爰 +爲 +爵 +父 +爸 +爹 +爺 +爻 +爽 +爾 +爿 +牁 +牂 +牆 +片 +版 +牌 +牒 +牕 +牖 +牘 +牙 +牛 +牝 +牟 +牠 +牡 +牢 +牧 +物 +牯 +牲 +特 +牻 +牼 +牽 +犀 +犁 +犂 +犇 +犍 +犎 +犖 +犛 +犢 +犧 +犨 +犬 +犯 +犰 +犴 +犽 +狀 +狂 +狄 +狍 +狎 +狐 +狒 +狓 +狗 +狙 +狛 +狟 +狠 +狡 +狦 +狨 +狩 +狳 +狶 +狷 +狸 +狹 +狻 +狼 +猁 +猄 +猇 +猊 +猗 +猙 +猛 +猜 +猝 +猞 +猢 +猥 +猨 +猩 +猳 +猴 +猶 +猷 +猺 +猻 +猾 +猿 +獁 +獃 +獄 +獅 +獇 +獎 +獏 +獐 +獒 +獠 +獢 +獣 +獨 +獬 +獮 +獯 +獰 +獲 +獴 +獵 +獷 +獸 +獺 +獻 +獼 +獾 +玀 +玄 +玆 +率 +玉 +王 +玎 +玏 +玓 +玕 +玖 +玗 +玘 +玙 +玟 +玠 +玡 +玢 +玥 +玧 +玨 +玩 +玫 +玭 +玲 +玳 +玶 +玷 +玹 +玻 +玾 +珀 +珂 +珅 +珈 +珉 +珊 +珌 +珍 +珎 +珏 +珖 +珙 +珝 +珞 +珠 +珡 +珣 +珤 +珥 +珦 +珧 +珩 +珪 +班 +珮 +珵 +珹 +珺 +珽 +現 +琁 +球 +琄 +琅 +理 +琇 +琉 +琊 +琍 +琎 +琚 +琛 +琡 +琢 +琤 +琥 +琦 +琨 +琪 +琬 +琮 +琯 +琰 +琱 +琳 +琴 +琵 +琶 +琹 +琺 +琿 +瑀 +瑁 +瑂 +瑄 +瑅 +瑆 +瑈 +瑊 +瑋 +瑑 +瑒 +瑕 +瑗 +瑙 +瑚 +瑛 +瑜 +瑝 +瑞 +瑟 +瑠 +瑢 +瑣 +瑤 +瑥 +瑧 +瑨 +瑩 +瑪 +瑭 +瑯 +瑰 +瑱 +瑳 +瑴 +瑺 +瑾 +璀 +璁 +璃 +璄 +璆 +璇 +璈 +璉 +璋 +璌 +璐 +璕 +璘 +璙 +璚 +璜 +璞 +璟 +璠 +璡 +璣 +璥 +璦 +璧 +璨 +璩 +璪 +璫 +璬 +璮 +環 +璱 +璵 +璸 +璹 +璽 +璿 +瓈 +瓊 +瓌 +瓏 +瓑 +瓔 +瓖 +瓘 +瓚 +瓛 +瓜 +瓞 +瓠 +瓢 +瓣 +瓤 +瓦 +瓮 +瓴 +瓶 +瓷 +瓿 +甂 +甄 +甌 +甍 +甑 +甕 +甘 +甙 +甚 +甜 +生 +甡 +產 +産 +甥 +甦 +用 +甩 +甪 +甫 +甬 +甯 +田 +由 +甲 +申 +男 +甸 +甹 +町 +甾 +畀 +畇 +畈 +畊 +畋 +界 +畎 +畏 +畐 +畑 +畔 +留 +畜 +畝 +畠 +畢 +略 +畦 +畧 +番 +畫 +畬 +畯 +異 +畲 +畳 +畵 +當 +畷 +畸 +畹 +畿 +疃 +疆 +疇 +疊 +疋 +疌 +疍 +疏 +疑 +疒 +疕 +疙 +疚 +疝 +疣 +疤 +疥 +疫 +疲 +疳 +疵 +疸 +疹 +疼 +疽 +疾 +痂 +病 +症 +痊 +痍 +痔 +痕 +痘 +痙 +痛 +痞 +痟 +痠 +痢 +痣 +痤 +痧 +痩 +痰 +痱 +痲 +痴 +痹 +痺 +痿 +瘀 +瘁 +瘊 +瘋 +瘍 +瘓 +瘙 +瘜 +瘞 +瘟 +瘠 +瘡 +瘢 +瘤 +瘦 +瘧 +瘩 +瘰 +瘴 +瘺 +癀 +療 +癆 +癇 +癌 +癒 +癖 +癘 +癜 +癟 +癡 +癢 +癤 +癥 +癩 +癬 +癭 +癮 +癯 +癰 +癱 +癲 +癸 +発 +登 +發 +白 +百 +皂 +的 +皆 +皇 +皈 +皋 +皎 +皐 +皓 +皖 +皙 +皚 +皛 +皝 +皞 +皮 +皰 +皴 +皷 +皸 +皺 +皿 +盂 +盃 +盅 +盆 +盈 +益 +盋 +盌 +盎 +盒 +盔 +盛 +盜 +盞 +盟 +盡 +監 +盤 +盥 +盦 +盧 +盨 +盩 +盪 +盫 +目 +盯 +盱 +盲 +直 +盷 +相 +盹 +盺 +盼 +盾 +眀 +省 +眉 +看 +県 +眙 +眛 +眜 +眞 +真 +眠 +眥 +眨 +眩 +眭 +眯 +眵 +眶 +眷 +眸 +眺 +眼 +眾 +着 +睇 +睛 +睜 +睞 +睡 +睢 +督 +睥 +睦 +睨 +睪 +睫 +睭 +睹 +睺 +睽 +睾 +睿 +瞄 +瞅 +瞋 +瞌 +瞎 +瞑 +瞓 +瞞 +瞢 +瞥 +瞧 +瞪 +瞫 +瞬 +瞭 +瞰 +瞳 +瞻 +瞼 +瞽 +瞿 +矇 +矍 +矗 +矚 +矛 +矜 +矞 +矢 +矣 +知 +矧 +矩 +短 +矮 +矯 +石 +矸 +矽 +砂 +砋 +砌 +砍 +砒 +研 +砝 +砢 +砥 +砦 +砧 +砩 +砫 +砭 +砮 +砯 +砰 +砲 +砳 +破 +砵 +砷 +砸 +砼 +硂 +硃 +硅 +硇 +硏 +硐 +硒 +硓 +硚 +硜 +硝 +硤 +硨 +硫 +硬 +硭 +硯 +硼 +碁 +碇 +碉 +碌 +碎 +碑 +碓 +碕 +碗 +碘 +碚 +碟 +碡 +碣 +碧 +碩 +碪 +碭 +碰 +碲 +碳 +碴 +碶 +碸 +確 +碻 +碼 +碽 +碾 +磁 +磅 +磊 +磋 +磐 +磔 +磕 +磘 +磙 +磚 +磜 +磡 +磨 +磪 +磬 +磯 +磱 +磲 +磵 +磷 +磺 +磻 +磾 +礁 +礄 +礎 +礐 +礑 +礒 +礙 +礠 +礦 +礪 +礫 +礬 +礮 +礱 +礴 +示 +礻 +礽 +社 +祀 +祁 +祂 +祆 +祇 +祈 +祉 +祋 +祏 +祐 +祓 +祕 +祖 +祗 +祙 +祚 +祛 +祜 +祝 +神 +祟 +祠 +祥 +祧 +票 +祭 +祹 +祺 +祼 +祿 +禁 +禃 +禇 +禍 +禎 +福 +禑 +禓 +禔 +禕 +禘 +禛 +禟 +禠 +禤 +禦 +禧 +禨 +禩 +禪 +禮 +禰 +禱 +禵 +禹 +禺 +禼 +禽 +禾 +禿 +秀 +私 +秈 +秉 +秋 +科 +秒 +秕 +秘 +租 +秠 +秣 +秤 +秦 +秧 +秩 +秭 +秳 +秸 +移 +稀 +稅 +稈 +稉 +程 +稍 +稑 +稔 +稗 +稘 +稙 +稚 +稜 +稞 +稟 +稠 +種 +稱 +稲 +稷 +稹 +稺 +稻 +稼 +稽 +稾 +稿 +穀 +穂 +穆 +穈 +穉 +穌 +積 +穎 +穗 +穟 +穠 +穡 +穢 +穣 +穩 +穫 +穰 +穴 +穵 +究 +穹 +空 +穿 +突 +窄 +窅 +窈 +窋 +窒 +窕 +窖 +窗 +窘 +窟 +窠 +窣 +窨 +窩 +窪 +窮 +窯 +窰 +窶 +窺 +窿 +竄 +竅 +竇 +竈 +竊 +立 +竑 +站 +竜 +竟 +章 +竣 +童 +竦 +竩 +竭 +端 +競 +竹 +竺 +竻 +竿 +笄 +笆 +笈 +笏 +笑 +笘 +笙 +笛 +笞 +笠 +笥 +符 +笨 +笩 +笪 +第 +笭 +笮 +笯 +笱 +笳 +笹 +筅 +筆 +等 +筊 +筋 +筌 +筍 +筏 +筐 +筒 +答 +策 +筘 +筠 +筥 +筦 +筧 +筬 +筭 +筱 +筲 +筳 +筵 +筶 +筷 +筻 +箆 +箇 +箋 +箍 +箏 +箐 +箑 +箒 +箔 +箕 +算 +箜 +管 +箬 +箭 +箱 +箴 +箸 +節 +篁 +範 +篆 +篇 +築 +篊 +篋 +篌 +篔 +篙 +篝 +篠 +篡 +篤 +篥 +篦 +篩 +篪 +篭 +篯 +篳 +篷 +簀 +簃 +簇 +簉 +簋 +簍 +簑 +簕 +簗 +簞 +簠 +簡 +簧 +簪 +簫 +簷 +簸 +簹 +簺 +簽 +簾 +簿 +籀 +籃 +籌 +籍 +籐 +籙 +籛 +籜 +籝 +籟 +籠 +籣 +籤 +籥 +籪 +籬 +籮 +籲 +米 +籽 +籾 +粄 +粉 +粍 +粑 +粒 +粕 +粗 +粘 +粟 +粢 +粥 +粦 +粧 +粩 +粱 +粲 +粳 +粵 +粹 +粼 +粽 +精 +粿 +糀 +糅 +糊 +糌 +糍 +糎 +糕 +糖 +糙 +糜 +糝 +糞 +糟 +糠 +糢 +糧 +糬 +糯 +糰 +糴 +糶 +糸 +糹 +糺 +系 +糾 +紀 +紂 +約 +紅 +紆 +紇 +紈 +紉 +紊 +紋 +納 +紐 +紑 +紓 +純 +紕 +紗 +紘 +紙 +級 +紛 +紜 +紝 +紞 +素 +紡 +索 +紫 +紮 +累 +細 +紱 +紲 +紳 +紵 +紹 +紺 +紿 +終 +絃 +組 +絆 +経 +絎 +結 +絕 +絛 +絜 +絞 +絡 +絢 +給 +絨 +絪 +絮 +統 +絲 +絳 +絵 +絶 +絹 +絺 +綁 +綃 +綈 +綉 +綎 +綏 +經 +綖 +継 +続 +綜 +綝 +綞 +綠 +綢 +綣 +綦 +綧 +綫 +綬 +維 +綮 +綰 +綱 +網 +綳 +綴 +綸 +綺 +綻 +綽 +綾 +綿 +緁 +緃 +緄 +緈 +緊 +緋 +総 +緑 +緒 +緖 +緘 +線 +緜 +緝 +緞 +締 +緡 +緣 +緤 +編 +緩 +緬 +緯 +緱 +緲 +練 +緹 +緻 +縂 +縄 +縈 +縉 +縊 +縕 +縛 +縝 +縞 +縠 +縡 +縣 +縤 +縫 +縮 +縯 +縱 +縴 +縵 +縷 +縹 +縻 +總 +績 +繁 +繃 +繆 +繇 +繒 +織 +繕 +繖 +繙 +繚 +繞 +繡 +繩 +繪 +繫 +繭 +繰 +繳 +繹 +繻 +繼 +繽 +繾 +纁 +纂 +纈 +續 +纍 +纏 +纓 +纔 +纕 +纖 +纘 +纛 +纜 +缐 +缶 +缸 +缺 +缽 +罃 +罄 +罅 +罈 +罉 +罌 +罍 +罐 +罔 +罕 +罘 +罟 +罡 +罨 +罩 +罪 +置 +罰 +罱 +署 +罳 +罵 +罶 +罷 +罹 +罽 +羂 +羅 +羆 +羈 +羊 +羋 +羌 +美 +羔 +羕 +羗 +羙 +羚 +羞 +羡 +羣 +群 +羥 +羧 +羨 +義 +羯 +羰 +羱 +羲 +羸 +羹 +羽 +羿 +翀 +翁 +翂 +翃 +翅 +翊 +翌 +翎 +翏 +習 +翔 +翕 +翙 +翜 +翟 +翠 +翡 +翥 +翦 +翩 +翬 +翮 +翰 +翱 +翳 +翹 +翻 +翼 +耀 +老 +考 +耄 +者 +耆 +而 +耍 +耎 +耐 +耑 +耒 +耔 +耕 +耗 +耘 +耙 +耜 +耦 +耨 +耬 +耳 +耵 +耶 +耷 +耽 +耿 +聃 +聆 +聊 +聒 +聖 +聘 +聚 +聞 +聟 +聨 +聯 +聰 +聱 +聲 +聳 +聴 +聶 +職 +聽 +聾 +聿 +肄 +肅 +肆 +肇 +肉 +肋 +肌 +肏 +肖 +肘 +肚 +肛 +肜 +肝 +肟 +股 +肢 +肥 +肩 +肪 +肫 +肯 +肱 +育 +肸 +肹 +肺 +肼 +肽 +胂 +胃 +胄 +胅 +胇 +胊 +背 +胍 +胎 +胖 +胗 +胙 +胚 +胛 +胝 +胞 +胡 +胤 +胥 +胬 +胭 +胰 +胱 +胳 +胴 +胸 +胺 +胼 +能 +脂 +脅 +脆 +脇 +脈 +脊 +脒 +脖 +脘 +脛 +脣 +脩 +脫 +脬 +脭 +脯 +脲 +脳 +脷 +脹 +脾 +腆 +腈 +腊 +腋 +腌 +腎 +腐 +腑 +腓 +腔 +腕 +腥 +腦 +腧 +腩 +腫 +腮 +腰 +腱 +腳 +腴 +腸 +腹 +腺 +腿 +膀 +膂 +膈 +膊 +膏 +膚 +膛 +膜 +膝 +膠 +膣 +膥 +膦 +膨 +膩 +膮 +膳 +膺 +膽 +膾 +膿 +臀 +臂 +臃 +臆 +臉 +臊 +臍 +臏 +臘 +臚 +臞 +臟 +臠 +臣 +臧 +臨 +自 +臭 +臯 +至 +致 +臺 +臻 +臼 +臾 +舂 +舅 +與 +興 +舉 +舊 +舌 +舍 +舎 +舒 +舔 +舖 +舘 +舛 +舜 +舞 +舟 +舢 +舥 +舨 +舩 +航 +舫 +般 +舲 +舵 +舶 +舷 +舸 +船 +舺 +艅 +艇 +艉 +艋 +艎 +艏 +艔 +艘 +艙 +艚 +艦 +艮 +良 +艱 +色 +艶 +艷 +艸 +艽 +艾 +艿 +芃 +芊 +芋 +芍 +芎 +芑 +芒 +芘 +芙 +芛 +芝 +芡 +芥 +芨 +芩 +芪 +芫 +芬 +芭 +芮 +芯 +花 +芳 +芴 +芷 +芸 +芹 +芻 +芽 +芾 +苄 +苅 +苑 +苒 +苓 +苔 +苕 +苗 +苛 +苜 +苝 +苞 +苟 +苡 +苣 +苤 +若 +苦 +苧 +苪 +苫 +苯 +英 +苳 +苴 +苷 +苺 +苻 +苼 +苾 +茀 +茁 +茂 +范 +茄 +茅 +茆 +茇 +茈 +茉 +茌 +茗 +茘 +茚 +茛 +茜 +茝 +茨 +茫 +茬 +茭 +茮 +茯 +茱 +茲 +茴 +茵 +茶 +茷 +茸 +茹 +茺 +茼 +荀 +荃 +荅 +荇 +草 +荊 +荎 +荏 +荒 +荔 +荖 +荘 +荳 +荷 +荸 +荻 +荼 +荽 +莆 +莉 +莊 +莎 +莒 +莓 +莕 +莖 +莘 +莙 +莛 +莜 +莞 +莠 +莢 +莧 +莨 +莩 +莪 +莫 +莽 +莿 +菀 +菁 +菅 +菇 +菈 +菉 +菊 +菌 +菍 +菏 +菑 +菓 +菔 +菖 +菘 +菜 +菝 +菟 +菠 +菡 +菥 +菩 +菪 +菫 +華 +菰 +菱 +菲 +菴 +菶 +菸 +菹 +菺 +菼 +菽 +菾 +萁 +萃 +萄 +萇 +萊 +萌 +萍 +萎 +萐 +萘 +萜 +萠 +萡 +萣 +萩 +萬 +萭 +萱 +萵 +萸 +萹 +萼 +落 +葃 +葆 +葉 +葊 +葎 +葑 +葒 +著 +葙 +葚 +葛 +葜 +葝 +葡 +董 +葦 +葩 +葫 +葬 +葭 +葯 +葰 +葳 +葵 +葶 +葷 +葺 +蒂 +蒄 +蒍 +蒎 +蒐 +蒓 +蒔 +蒗 +蒙 +蒜 +蒞 +蒟 +蒡 +蒢 +蒤 +蒧 +蒨 +蒭 +蒯 +蒲 +蒴 +蒸 +蒹 +蒺 +蒻 +蒼 +蒽 +蒾 +蒿 +蓀 +蓁 +蓂 +蓄 +蓆 +蓉 +蓋 +蓍 +蓑 +蓓 +蓖 +蓘 +蓚 +蓧 +蓨 +蓪 +蓬 +蓭 +蓮 +蓯 +蓳 +蓼 +蓽 +蓿 +蔆 +蔎 +蔑 +蔓 +蔔 +蔕 +蔗 +蔘 +蔚 +蔝 +蔞 +蔡 +蔣 +蔥 +蔦 +蔬 +蔭 +蔴 +蔵 +蔻 +蔽 +蕁 +蕃 +蕅 +蕈 +蕉 +蕊 +蕎 +蕑 +蕒 +蕖 +蕘 +蕙 +蕚 +蕟 +蕡 +蕢 +蕤 +蕨 +蕩 +蕪 +蕭 +蕷 +蕹 +蕺 +蕻 +蕾 +薀 +薄 +薆 +薇 +薈 +薊 +薌 +薏 +薐 +薑 +薔 +薗 +薘 +薙 +薛 +薜 +薞 +薟 +薡 +薦 +薨 +薩 +薪 +薫 +薬 +薯 +薰 +薲 +薷 +薸 +薹 +薺 +薾 +薿 +藁 +藉 +藍 +藎 +藏 +藐 +藔 +藕 +藜 +藝 +藟 +藤 +藥 +藦 +藨 +藩 +藪 +藶 +藸 +藹 +藺 +藻 +藿 +蘂 +蘄 +蘅 +蘆 +蘇 +蘊 +蘋 +蘐 +蘑 +蘓 +蘗 +蘘 +蘚 +蘞 +蘢 +蘧 +蘩 +蘭 +蘵 +蘶 +蘸 +蘼 +蘿 +虉 +虎 +虐 +虓 +虔 +處 +虖 +虛 +虜 +虞 +號 +虢 +虧 +虨 +虯 +虱 +虵 +虹 +虺 +虻 +蚆 +蚊 +蚋 +蚌 +蚍 +蚓 +蚖 +蚜 +蚝 +蚡 +蚢 +蚣 +蚤 +蚧 +蚨 +蚩 +蚪 +蚯 +蚱 +蚴 +蚵 +蚶 +蚺 +蚼 +蛀 +蛄 +蛇 +蛉 +蛋 +蛍 +蛐 +蛑 +蛔 +蛙 +蛛 +蛞 +蛟 +蛤 +蛭 +蛯 +蛸 +蛹 +蛺 +蛻 +蛾 +蜀 +蜂 +蜃 +蜆 +蜇 +蜈 +蜉 +蜊 +蜍 +蜑 +蜒 +蜓 +蜘 +蜚 +蜛 +蜜 +蜞 +蜢 +蜣 +蜥 +蜨 +蜮 +蜯 +蜱 +蜴 +蜷 +蜻 +蜾 +蜿 +蝀 +蝌 +蝍 +蝎 +蝓 +蝕 +蝗 +蝘 +蝙 +蝚 +蝟 +蝠 +蝣 +蝤 +蝦 +蝨 +蝮 +蝯 +蝰 +蝲 +蝴 +蝶 +蝸 +蝽 +螂 +螃 +螄 +螅 +螈 +螋 +融 +螐 +螔 +螞 +螟 +螠 +螢 +螣 +螥 +螫 +螭 +螯 +螳 +螶 +螺 +螻 +螽 +螾 +蟀 +蟄 +蟅 +蟆 +蟊 +蟋 +蟌 +蟎 +蟑 +蟒 +蟜 +蟠 +蟥 +蟪 +蟫 +蟬 +蟯 +蟲 +蟳 +蟴 +蟶 +蟹 +蟻 +蟾 +蠂 +蠃 +蠄 +蠅 +蠆 +蠊 +蠋 +蠍 +蠐 +蠑 +蠓 +蠔 +蠕 +蠖 +蠘 +蠙 +蠟 +蠡 +蠢 +蠣 +蠱 +蠲 +蠵 +蠶 +蠷 +蠹 +蠻 +血 +衂 +衆 +行 +衍 +衎 +術 +衕 +衖 +街 +衙 +衚 +衛 +衜 +衝 +衞 +衡 +衢 +衣 +表 +衩 +衫 +衰 +衲 +衷 +衽 +衾 +衿 +袁 +袂 +袈 +袋 +袍 +袓 +袖 +袛 +袞 +袤 +袪 +被 +袱 +袴 +袾 +裁 +裂 +裊 +裎 +裒 +裔 +裕 +裖 +裘 +裙 +補 +裝 +裟 +裡 +裨 +裬 +裱 +裳 +裴 +裵 +裸 +裹 +製 +裾 +裿 +褀 +褂 +複 +褌 +褍 +褎 +褐 +褒 +褓 +褔 +褘 +褙 +褚 +褞 +褥 +褧 +褪 +褫 +褭 +褲 +褶 +褸 +褻 +襄 +襌 +襖 +襞 +襟 +襠 +襤 +襦 +襪 +襯 +襲 +襴 +襶 +襻 +襾 +西 +要 +覃 +覆 +覇 +覈 +見 +覌 +規 +覓 +視 +覚 +覡 +覦 +覧 +親 +覬 +覲 +観 +覺 +覽 +覿 +觀 +角 +觔 +觙 +觚 +觜 +解 +觭 +觱 +觴 +觶 +觸 +觿 +言 +訁 +訂 +訃 +訇 +計 +訊 +訌 +討 +訏 +訐 +訒 +訓 +訔 +訕 +訖 +託 +記 +訛 +訝 +訟 +訣 +訥 +訪 +設 +許 +訴 +訶 +診 +註 +証 +訾 +詁 +詆 +詈 +詐 +詒 +詔 +評 +詛 +詞 +詠 +詡 +詢 +詣 +詥 +試 +詧 +詩 +詫 +詭 +詮 +詰 +話 +該 +詳 +詵 +詹 +詼 +誄 +誅 +誇 +誌 +認 +誒 +誓 +誕 +誘 +語 +誠 +誡 +誣 +誤 +誥 +誦 +誨 +說 +説 +読 +誰 +課 +誴 +誹 +誼 +誾 +調 +談 +請 +諍 +諏 +諒 +論 +諗 +諜 +諟 +諠 +諡 +諤 +諦 +諧 +諪 +諫 +諭 +諮 +諱 +諲 +諳 +諴 +諶 +諷 +諸 +諺 +諼 +諾 +謀 +謁 +謂 +謄 +謇 +謊 +謌 +謎 +謏 +謐 +謔 +謖 +謗 +謙 +謚 +講 +謜 +謝 +謠 +謢 +謤 +謨 +謩 +謫 +謬 +謳 +謹 +謾 +證 +譏 +譓 +譔 +識 +譙 +譚 +譜 +譞 +警 +譫 +譬 +譭 +譯 +議 +譲 +譳 +譴 +護 +譽 +譿 +讀 +讃 +變 +讌 +讎 +讓 +讖 +讙 +讚 +讜 +讞 +谷 +谿 +豁 +豆 +豇 +豈 +豉 +豊 +豌 +豎 +豐 +豔 +豕 +豚 +象 +豢 +豨 +豪 +豫 +豬 +豳 +豸 +豹 +豺 +豿 +貂 +貅 +貉 +貊 +貌 +貐 +貒 +貓 +貔 +貘 +貝 +貞 +負 +財 +貢 +貤 +貧 +貨 +販 +貪 +貫 +責 +貭 +貮 +貯 +貲 +貳 +貴 +貶 +買 +貸 +貺 +費 +貼 +貽 +貿 +賀 +賁 +賂 +賃 +賄 +資 +賈 +賊 +賑 +賒 +賓 +賔 +賕 +賚 +賜 +賞 +賠 +賡 +賢 +賣 +賤 +賦 +賨 +質 +賬 +賭 +賴 +賹 +賺 +賻 +購 +賽 +賾 +贄 +贅 +贇 +贈 +贊 +贌 +贍 +贏 +贓 +贔 +贖 +贛 +赤 +赦 +赧 +赫 +赬 +赭 +走 +赳 +赴 +起 +趁 +超 +越 +趐 +趕 +趖 +趙 +趟 +趣 +趨 +足 +趴 +趵 +趺 +趼 +趾 +跅 +跆 +跋 +跌 +跏 +跑 +跖 +跗 +跛 +距 +跟 +跡 +跣 +跤 +跨 +跩 +跪 +路 +跳 +踎 +踏 +踐 +踝 +踞 +踢 +踩 +踰 +踴 +踹 +踺 +蹂 +蹄 +蹇 +蹈 +蹉 +蹊 +蹋 +蹕 +蹙 +蹟 +蹠 +蹤 +蹦 +蹬 +蹭 +蹯 +蹲 +蹴 +蹶 +蹺 +蹻 +蹼 +躁 +躂 +躄 +躉 +躋 +躍 +躑 +躒 +躔 +躝 +躪 +身 +躬 +躰 +躲 +躺 +軀 +車 +軋 +軌 +軍 +軎 +軒 +軔 +軛 +軟 +転 +軫 +軲 +軸 +軹 +軺 +軻 +軼 +軽 +軾 +較 +輄 +輅 +載 +輋 +輒 +輓 +輔 +輕 +輛 +輝 +輞 +輟 +輥 +輦 +輩 +輪 +輬 +輭 +輯 +輶 +輸 +輻 +輾 +輿 +轀 +轂 +轄 +轅 +轆 +轉 +轍 +轎 +轘 +轝 +轟 +轤 +辛 +辜 +辟 +辣 +辦 +辧 +辨 +辭 +辮 +辯 +辰 +辱 +農 +辵 +辺 +辻 +込 +迂 +迄 +迅 +迎 +近 +返 +迢 +迤 +迥 +迦 +迪 +迫 +迭 +迮 +述 +迴 +迵 +迷 +迸 +迺 +追 +退 +送 +逃 +逄 +逅 +逆 +逈 +逋 +逌 +逍 +逎 +透 +逐 +逑 +途 +逕 +逖 +逗 +這 +通 +逛 +逝 +逞 +速 +造 +逢 +連 +逤 +逨 +逮 +逯 +進 +逴 +逵 +逸 +逹 +逺 +逼 +逾 +遁 +遂 +遄 +遇 +遊 +運 +遍 +過 +遏 +遐 +遒 +道 +達 +違 +遘 +遙 +遛 +遜 +遞 +遠 +遢 +遣 +遨 +適 +遭 +遮 +遯 +遲 +遴 +遵 +遶 +遷 +選 +遹 +遺 +遼 +避 +邀 +邁 +邂 +邃 +還 +邇 +邈 +邉 +邊 +邋 +邏 +邑 +邕 +邗 +邙 +邛 +邠 +邡 +邢 +那 +邦 +邨 +邪 +邯 +邰 +邱 +邲 +邳 +邴 +邵 +邸 +邽 +邾 +郁 +郃 +郄 +郅 +郇 +郊 +郋 +郎 +郗 +郛 +郜 +郝 +郞 +郟 +郡 +郢 +郤 +部 +郪 +郫 +郭 +郯 +郳 +郴 +郵 +郷 +都 +郾 +郿 +鄂 +鄃 +鄄 +鄆 +鄉 +鄋 +鄑 +鄒 +鄔 +鄖 +鄗 +鄘 +鄙 +鄚 +鄜 +鄞 +鄠 +鄢 +鄣 +鄤 +鄧 +鄩 +鄫 +鄭 +鄯 +鄰 +鄱 +鄲 +鄳 +鄴 +鄺 +酃 +酆 +酈 +酉 +酊 +酋 +酌 +配 +酎 +酏 +酐 +酒 +酔 +酗 +酚 +酞 +酡 +酢 +酣 +酥 +酩 +酪 +酬 +酮 +酯 +酰 +酴 +酵 +酶 +酷 +酸 +酺 +酼 +醁 +醂 +醃 +醅 +醇 +醉 +醋 +醌 +醍 +醐 +醒 +醚 +醛 +醜 +醞 +醢 +醣 +醪 +醫 +醬 +醮 +醯 +醴 +醺 +醾 +醿 +釀 +釁 +釆 +采 +釉 +釋 +里 +重 +野 +量 +釐 +金 +釒 +釓 +釔 +釕 +釗 +釘 +釙 +釚 +釜 +針 +釣 +釤 +釦 +釧 +釩 +釪 +釭 +釴 +釵 +釷 +釹 +釺 +鈀 +鈁 +鈄 +鈇 +鈈 +鈉 +鈊 +鈍 +鈏 +鈐 +鈑 +鈔 +鈕 +鈖 +鈞 +鈢 +鈣 +鈥 +鈦 +鈫 +鈮 +鈰 +鈳 +鈴 +鈷 +鈸 +鈹 +鈺 +鈾 +鈿 +鉀 +鉄 +鉅 +鉆 +鉈 +鉉 +鉋 +鉌 +鉍 +鉏 +鉑 +鉓 +鉗 +鉚 +鉛 +鉞 +鉟 +鉤 +鉦 +鉬 +鉭 +鉲 +鉶 +鉷 +鉸 +鉻 +鉾 +鉿 +銀 +銂 +銃 +銅 +銋 +銍 +銑 +銓 +銕 +銖 +銘 +銚 +銜 +銠 +銣 +銥 +銦 +銨 +銩 +銪 +銫 +銬 +銭 +銱 +銲 +銳 +銶 +銷 +銹 +銻 +銼 +銾 +鋁 +鋅 +鋆 +鋇 +鋌 +鋏 +鋐 +鋒 +鋕 +鋗 +鋙 +鋡 +鋤 +鋥 +鋦 +鋨 +鋪 +鋮 +鋯 +鋰 +鋱 +鋳 +鋶 +鋸 +鋹 +鋼 +錀 +錄 +錏 +錐 +錒 +錕 +錘 +錚 +錞 +錟 +錠 +錡 +錢 +錦 +錨 +錫 +錬 +錮 +錯 +錳 +錶 +錸 +錻 +鍀 +鍇 +鍈 +鍉 +鍊 +鍋 +鍍 +鍏 +鍔 +鍘 +鍛 +鍝 +鍟 +鍠 +鍥 +鍩 +鍬 +鍱 +鍳 +鍵 +鍶 +鍷 +鍺 +鍼 +鍾 +鎂 +鎅 +鎊 +鎌 +鎏 +鎓 +鎔 +鎖 +鎗 +鎘 +鎚 +鎛 +鎢 +鎣 +鎦 +鎧 +鎪 +鎬 +鎭 +鎮 +鎰 +鎳 +鎵 +鎻 +鏃 +鏇 +鏈 +鏊 +鏌 +鏐 +鏑 +鏓 +鏖 +鏗 +鏘 +鏜 +鏝 +鏞 +鏟 +鏡 +鏢 +鏤 +鏦 +鏳 +鏴 +鏵 +鏷 +鏻 +鏽 +鐃 +鐇 +鐈 +鐓 +鐔 +鐘 +鐙 +鐠 +鐡 +鐤 +鐦 +鐧 +鐫 +鐬 +鐭 +鐮 +鐲 +鐳 +鐵 +鐸 +鐺 +鐽 +鐿 +鑀 +鑁 +鑂 +鑄 +鑅 +鑊 +鑌 +鑑 +鑒 +鑛 +鑠 +鑣 +鑨 +鑪 +鑫 +鑭 +鑰 +鑲 +鑴 +鑷 +鑼 +鑽 +鑾 +鑿 +長 +門 +閂 +閃 +閆 +閉 +開 +閎 +閏 +閑 +閒 +間 +閔 +閘 +閜 +閞 +閟 +関 +閣 +閥 +閦 +閨 +閩 +閬 +閭 +閰 +閱 +閶 +閹 +閻 +閼 +閾 +閿 +闆 +闇 +闈 +闊 +闋 +闌 +闍 +闐 +闓 +闔 +闕 +闖 +闘 +關 +闞 +闡 +闢 +闥 +阜 +阝 +阡 +阪 +阭 +阮 +阯 +阱 +防 +阻 +阿 +陀 +陁 +陂 +附 +陋 +陌 +降 +限 +陔 +陘 +陛 +陜 +陝 +陞 +陟 +陡 +院 +陣 +除 +陪 +陬 +陰 +陲 +陳 +陵 +陶 +陷 +陸 +険 +陽 +隄 +隅 +隆 +隈 +隊 +隋 +隍 +階 +隔 +隕 +隗 +隘 +隙 +際 +障 +隣 +隧 +隨 +險 +隰 +隱 +隲 +隳 +隴 +隷 +隸 +隹 +隻 +隼 +雀 +雁 +雄 +雅 +集 +雇 +雉 +雋 +雌 +雍 +雎 +雑 +雒 +雕 +雖 +雙 +雛 +雜 +雝 +雞 +離 +難 +雨 +雩 +雪 +雫 +雯 +雱 +雲 +零 +雷 +雹 +電 +需 +霄 +霅 +霆 +震 +霈 +霉 +霊 +霍 +霎 +霏 +霑 +霓 +霖 +霙 +霜 +霞 +霤 +霧 +霨 +霰 +露 +霶 +霸 +霹 +霽 +霾 +靁 +靂 +靄 +靈 +靉 +靑 +青 +靖 +靚 +靛 +靜 +非 +靠 +靡 +面 +革 +靫 +靬 +靭 +靳 +靴 +靶 +靺 +靼 +鞅 +鞆 +鞋 +鞍 +鞏 +鞘 +鞞 +鞠 +鞣 +鞥 +鞦 +鞨 +鞭 +鞮 +鞴 +韁 +韃 +韆 +韋 +韌 +韑 +韓 +韙 +韜 +韞 +韠 +韡 +韭 +韮 +音 +韶 +韺 +韻 +韾 +響 +頁 +頂 +頃 +項 +順 +須 +頊 +頌 +頍 +頎 +頏 +預 +頑 +頒 +頓 +頔 +頗 +領 +頜 +頠 +頡 +頤 +頦 +頫 +頭 +頰 +頴 +頵 +頷 +頸 +頹 +頻 +頼 +顆 +題 +額 +顎 +顏 +顒 +顓 +顔 +顕 +顗 +願 +顙 +顛 +類 +顥 +顧 +顫 +顯 +顰 +顱 +顳 +顴 +風 +颮 +颯 +颱 +颶 +颺 +颼 +飄 +飆 +飈 +飛 +食 +飠 +飡 +飢 +飥 +飩 +飪 +飫 +飬 +飭 +飮 +飯 +飲 +飴 +飼 +飽 +飾 +餃 +餄 +餅 +餉 +養 +餌 +餎 +餐 +餒 +餓 +餗 +餘 +餚 +餛 +餞 +餠 +餡 +館 +餮 +餵 +餺 +餾 +餿 +饃 +饅 +饋 +饌 +饑 +饒 +饕 +饗 +饞 +饟 +饢 +首 +馗 +馘 +香 +馛 +馥 +馦 +馨 +馬 +馭 +馮 +馯 +馱 +馳 +馴 +馼 +駁 +駄 +駅 +駆 +駐 +駑 +駒 +駔 +駕 +駘 +駙 +駛 +駝 +駟 +駢 +駭 +駰 +駱 +駿 +騁 +騂 +騄 +騅 +騋 +騎 +騏 +験 +騖 +騙 +騤 +騨 +騫 +騭 +騮 +騰 +騶 +騷 +騾 +驁 +驃 +驄 +驅 +驊 +驌 +驍 +驎 +驒 +驕 +驗 +驚 +驛 +驟 +驢 +驤 +驥 +驩 +驪 +骨 +骯 +骰 +骶 +骷 +骸 +骼 +髀 +髂 +髎 +髏 +髑 +髒 +髓 +體 +高 +髙 +髡 +髦 +髪 +髭 +髮 +髯 +髲 +髷 +髹 +髻 +鬃 +鬄 +鬅 +鬆 +鬍 +鬚 +鬟 +鬢 +鬣 +鬥 +鬧 +鬨 +鬩 +鬪 +鬬 +鬮 +鬯 +鬱 +鬲 +鬹 +鬻 +鬼 +魁 +魂 +魃 +魄 +魅 +魈 +魋 +魍 +魎 +魏 +魔 +魕 +魘 +魚 +魛 +魞 +魟 +魣 +魨 +魩 +魮 +魯 +魴 +魷 +鮀 +鮁 +鮃 +鮄 +鮊 +鮋 +鮍 +鮐 +鮑 +鮒 +鮓 +鮗 +鮜 +鮟 +鮠 +鮡 +鮣 +鮨 +鮪 +鮫 +鮭 +鮮 +鮰 +鮸 +鮹 +鮻 +鯀 +鯁 +鯃 +鯇 +鯉 +鯊 +鯏 +鯒 +鯓 +鯔 +鯕 +鯖 +鯗 +鯙 +鯛 +鯡 +鯢 +鯤 +鯧 +鯨 +鯪 +鯭 +鯮 +鯰 +鯶 +鯷 +鯻 +鯽 +鯿 +鰂 +鰃 +鰆 +鰈 +鰉 +鰍 +鰏 +鰒 +鰓 +鰕 +鰗 +鰛 +鰜 +鰟 +鰣 +鰤 +鰧 +鰨 +鰩 +鰭 +鰮 +鰱 +鰲 +鰳 +鰶 +鰷 +鰹 +鰺 +鰻 +鰼 +鰾 +鱀 +鱂 +鱅 +鱇 +鱈 +鱉 +鱊 +鱒 +鱓 +鱔 +鱖 +鱗 +鱘 +鱚 +鱝 +鱟 +鱠 +鱣 +鱥 +鱧 +鱨 +鱬 +鱮 +鱰 +鱲 +鱵 +鱷 +鱸 +鱺 +鱻 +鳥 +鳧 +鳩 +鳯 +鳰 +鳳 +鳴 +鳶 +鳽 +鴆 +鴇 +鴉 +鴒 +鴓 +鴕 +鴗 +鴛 +鴝 +鴞 +鴟 +鴡 +鴣 +鴦 +鴨 +鴫 +鴯 +鴰 +鴴 +鴻 +鴿 +鵂 +鵄 +鵎 +鵐 +鵑 +鵒 +鵓 +鵙 +鵜 +鵝 +鵞 +鵟 +鵠 +鵡 +鵪 +鵬 +鵯 +鵰 +鵲 +鵵 +鵼 +鵾 +鶆 +鶇 +鶉 +鶏 +鶒 +鶓 +鶘 +鶚 +鶡 +鶥 +鶩 +鶬 +鶯 +鶲 +鶴 +鶹 +鶺 +鶻 +鶼 +鶿 +鷂 +鷄 +鷉 +鷎 +鷓 +鷗 +鷙 +鷚 +鷟 +鷥 +鷦 +鷫 +鷯 +鷲 +鷳 +鷸 +鷹 +鷺 +鸊 +鸌 +鸐 +鸑 +鸕 +鸘 +鸚 +鸛 +鸜 +鸝 +鸞 +鹮 +鹵 +鹹 +鹼 +鹽 +鹿 +麂 +麅 +麇 +麈 +麊 +麋 +麐 +麒 +麓 +麗 +麝 +麞 +麟 +麥 +麩 +麪 +麯 +麴 +麵 +麹 +麺 +麻 +麼 +麽 +麾 +麿 +黁 +黃 +黇 +黌 +黍 +黎 +黏 +黐 +黑 +黒 +黔 +默 +黙 +黛 +黜 +黝 +點 +黟 +黥 +黧 +黨 +黯 +黴 +黶 +黻 +黼 +黽 +黿 +鼂 +鼇 +鼈 +鼉 +鼎 +鼐 +鼒 +鼓 +鼕 +鼙 +鼠 +鼢 +鼩 +鼬 +鼯 +鼱 +鼴 +鼷 +鼻 +鼽 +鼾 +齊 +齋 +齒 +齕 +齡 +齣 +齦 +齧 +齲 +齶 +龍 +龎 +龐 +龑 +龔 +龕 +龜 +龝 +龠 +龢 +郎 +凉 +﹑ +﹗ +﹝ +﹞ +﹢ +! +" +# +$ +% +& +' +( +) +* ++ +, +- +. +/ +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +: +; +< += +> +? +A +B +C +D +E +F +G +H +I +K +L +M +N +O +P +R +S +T +U +V +W +Y +Z +[ +] +` +a +b +c +d +e +f +g +h +i +j +k +l +m +n +o +p +r +s +t +u +z +{ +| +} +~ +¥ +𣇉 + diff --git a/yas/src/ocr/paddle_paddle_model/mod.rs b/yas/src/ocr/paddle_paddle_model/mod.rs new file mode 100644 index 00000000..dd211c2d --- /dev/null +++ b/yas/src/ocr/paddle_paddle_model/mod.rs @@ -0,0 +1,5 @@ +mod model; + +pub use model::PPOCRModel; +pub use model::ppocr_model; +pub use model::PPOCRChV4RecInfer; diff --git a/yas/src/ocr/paddle_paddle_model/model.rs b/yas/src/ocr/paddle_paddle_model/model.rs new file mode 100644 index 00000000..c2a63d5b --- /dev/null +++ b/yas/src/ocr/paddle_paddle_model/model.rs @@ -0,0 +1,102 @@ +use tract_onnx::prelude::*; +use anyhow::Result; +use image::{EncodableLayout, RgbImage}; +use tract_onnx::tract_hir::infer::InferenceOp; +use tract_onnx::tract_hir::shapefactoid; +use crate::ocr::ImageToText; + +// type ModelType = RunnableModel, Graph>>; +type ModelType = RunnableModel, Graph>>; + +pub struct PPOCRModel { + index_to_word: Vec, + model: ModelType, +} + +impl PPOCRModel { + pub fn new(onnx: &[u8], index_to_word: Vec) -> Result { + let fact = InferenceFact::new().with_datum_type(DatumType::F32) + .with_shape(shapefactoid!(_, 3, _, _)); + println!("{}", index_to_word.len()); + let model = tract_onnx::onnx() + .model_for_read(&mut onnx.as_bytes())? + .with_input_fact(0, fact)? + // .into_optimized()? + .into_runnable()?; + Ok(Self { + index_to_word, + model + }) + } +} + +impl ImageToText for PPOCRModel { + fn image_to_text(&self, image: &RgbImage, is_preprocessed: bool) -> Result { + let tensor: Tensor = tract_ndarray::Array4::from_shape_fn((1, 3, image.height() as usize, image.width() as usize), |(_, c, y, x)| { + let pix = image.get_pixel(x as u32, y as u32)[c]; + let v = pix as f32 / 255.0_f32; + (v - 0.5) / 0.5 + }).into(); + + let result = self.model.run(tvec!(tensor.into()))?; + let arr = result[0].to_array_view::()?; + let shape = arr.shape(); + + let mut s = String::new(); + for i in 0..shape[1] { + let mut max_index = 0; + let mut max_value = -1.0; + for j in 0..shape[2] { + let value = arr[[0, i, j]]; + if value > max_value { + max_value = value; + max_index = j; + } + } + let word = &self.index_to_word[max_index]; + s.push_str(word.as_str()); + // if *word != last_word && word != "-" { + // ans = ans + word; + // } + + // last_word = word.clone(); + } + + // let s = format!("{:?}", shape); + Ok(s) + } +} + +pub macro ppocr_model($onnx:literal, $index_to_word:literal) { + { + let model_bytes = include_bytes!($onnx); + let index_to_word_str = include_str!($index_to_word); + + let mut index_to_word_vec: Vec = Vec::new(); + for line in index_to_word_str.lines() { + index_to_word_vec.push(String::from(line)); + } + + PPOCRModel::new( + model_bytes, index_to_word_vec, + ) + } +} + +pub struct PPOCRChV4RecInfer { + model: PPOCRModel, +} + +impl PPOCRChV4RecInfer { + pub fn new() -> Result { + Ok(Self { + model: ppocr_model!("./ch_PP-OCRv4_rec_infer.onnx", "./chinese_cht_dict.txt")? + }) + } +} + +impl ImageToText for PPOCRChV4RecInfer { + fn image_to_text(&self, image: &RgbImage, is_preprocessed: bool) -> Result { + self.model.image_to_text(image, is_preprocessed) + } +} diff --git a/yas/src/ocr/traits.rs b/yas/src/ocr/traits.rs new file mode 100644 index 00000000..08ccd889 --- /dev/null +++ b/yas/src/ocr/traits.rs @@ -0,0 +1,9 @@ +use anyhow::Result; + +pub trait ImageToText { + fn image_to_text(&self, image: &ImageType, is_preprocessed: bool) -> Result; +} + +// pub trait ImageTextDetection { +// +// } diff --git a/yas/src/ocr/yas_model/mod.rs b/yas/src/ocr/yas_model/mod.rs new file mode 100644 index 00000000..a4bae36c --- /dev/null +++ b/yas/src/ocr/yas_model/mod.rs @@ -0,0 +1,6 @@ +pub mod preprocess; +pub mod yas_ocr_model; + +// pub use preprocess::to_gray; +// pub use preprocess::pre_process; +// pub use yas_ocr_model::YasOCRModel; diff --git a/src/inference/pre_process.rs b/yas/src/ocr/yas_model/preprocess.rs similarity index 53% rename from src/inference/pre_process.rs rename to yas/src/ocr/yas_model/preprocess.rs index 9abf1d91..d1c768d6 100644 --- a/src/inference/pre_process.rs +++ b/yas/src/ocr/yas_model/preprocess.rs @@ -1,32 +1,9 @@ -use image::imageops::{overlay, resize}; -use image::{GenericImageView, GrayImage, ImageBuffer, Luma, RgbImage}; +use image::{ImageBuffer, Luma, RgbImage, GenericImageView}; +use image::imageops; -use crate::common::RawImage; -pub type GrayImageFloat = ImageBuffer, Vec>; - -pub trait ImageConvExt { - fn to_common_grayscale(&self) -> GrayImage; -} - -impl ImageConvExt for GrayImageFloat { - fn to_common_grayscale(&self) -> GrayImage { - let img = ImageBuffer::from_fn(self.width(), self.height(), |x, y| { - let pv = self.get_pixel(x, y)[0]; - let pixel = (pv * 255.0) as u32; - let pixel: u8 = if pixel > 255 { 255 } else { pixel as _ }; - image::Luma([pixel]) - }); - img - } -} - -#[inline] -fn get_index(width: u32, x: u32, y: u32) -> usize { - (y * width + x) as usize -} - -pub fn to_gray(raw: &RgbImage) -> GrayImageFloat { - let mut new_gray = GrayImageFloat::new(raw.width(), raw.height()); +/// convert rgb image to f32 gray image +pub fn to_gray(raw: &RgbImage) -> ImageBuffer, Vec> { + let mut new_gray: ImageBuffer, Vec> = ImageBuffer::new(raw.width(), raw.height()); for x in 0..raw.width() { for y in 0..raw.height() { let rgb = raw.get_pixel(x, y); @@ -43,7 +20,9 @@ pub fn to_gray(raw: &RgbImage) -> GrayImageFloat { new_gray } -pub fn normalize(im: &mut GrayImageFloat, auto_inverse: bool) -> bool { +/// normalize an f32 gray image +/// which makes the bright pixel more bright, the dark pixels more dark +fn normalize(im: &mut ImageBuffer, Vec>, auto_inverse: bool) -> bool { let width = im.width(); let height = im.height(); @@ -51,14 +30,12 @@ pub fn normalize(im: &mut GrayImageFloat, auto_inverse: bool) -> bool { println!("wrong width or height"); return false; } - // info!("in normalize: width = {}, height = {}", width, height); let mut max: f32 = 0.0; let mut min: f32 = 256.0; for i in 0..width { for j in 0..height { - // info!("i = {}, j = {}, width = {}, index = {}", i, j, width, index); let p = im.get_pixel(i, j)[0]; if p > max { max = p; @@ -82,20 +59,17 @@ pub fn normalize(im: &mut GrayImageFloat, auto_inverse: bool) -> bool { let pv = p[0]; let mut new_pv = (pv - min) / (max - min); if auto_inverse && flag_pixel > 0.5 { - // println!("123"); new_pv = 1.0 - new_pv; } p[0] = new_pv; - // if data[index] < 0.6 { - // data[index] = 0.0; - // } } } true } -pub fn crop(im: &GrayImageFloat) -> GrayImageFloat { +/// crop an f32 gray image to only where there is text +fn crop(im: &ImageBuffer, Vec>) -> ImageBuffer, Vec> { let width = im.width(); let height = im.height(); @@ -143,50 +117,8 @@ pub fn crop(im: &GrayImageFloat) -> GrayImageFloat { cropped_im } -pub fn raw_to_img(im: &RawImage) -> GrayImage { - let width = im.w; - let height = im.h; - let data = &im.data; - - let img = ImageBuffer::from_fn(width, height, |x, y| { - let index = get_index(width, x, y); - let p = data[index]; - let pixel = (p * 255.0) as u32; - let pixel: u8 = if pixel > 255 { - 255 - } else if pixel < 0 { - 0 - } else { - pixel as u8 - }; - Luma([pixel]) - }); - - img -} - -pub fn uint8_raw_to_img(im: &RawImage) -> GrayImage { - let width = im.w; - let height = im.h; - let data = &im.data; - - let img = ImageBuffer::from_fn(width, height, |x, y| { - let index = get_index(width, x, y); - let pixel = data[index] as u32; - let pixel: u8 = if pixel > 255 { - 255 - } else if pixel < 0 { - 0 - } else { - pixel as u8 - }; - Luma([pixel]) - }); - - img -} - -pub fn resize_and_pad(im: &GrayImageFloat) -> GrayImageFloat { +/// resize an f32 gray image to 384 * 32, if not wide enough, then pad with background +fn resize_and_pad(im: &ImageBuffer, Vec>) -> ImageBuffer, Vec> { let w = im.width(); let h = im.height(); @@ -198,10 +130,7 @@ pub fn resize_and_pad(im: &GrayImageFloat) -> GrayImageFloat { let new_height = std::cmp::min((384.0 / w as f64 * h as f64) as u32, 32); - //let img = raw_to_img(&im); - //let img = resize(&img, new_width, 32, image::imageops::FilterType::Triangle); - - let img = resize( + let img = imageops::resize( im, new_width, new_height, @@ -210,14 +139,16 @@ pub fn resize_and_pad(im: &GrayImageFloat) -> GrayImageFloat { let data: Vec = vec![0.0; 32 * 384]; let mut padded_im = ImageBuffer::from_vec(384, 32, data).unwrap(); - overlay(&mut padded_im, &img, 0, 0); + imageops::overlay(&mut padded_im, &img, 0, 0); padded_im } -pub fn pre_process(im: GrayImageFloat) -> Option { +/// transform an f32 gray image to a preprocessed image +/// if the image has only one color, then return false, but this is not an error +pub fn pre_process(im: ImageBuffer, Vec>) -> (ImageBuffer, Vec>, bool) { let mut im = im; if !normalize(&mut im, true) { - return None; + return (im, false); } let mut im = crop(&im); @@ -237,21 +168,5 @@ pub fn pre_process(im: GrayImageFloat) -> Option { } } - Some(im) -} - -pub fn image_to_raw(im: GrayImage) -> RawImage { - let w = im.width(); - let h = im.height(); - - let mut data: Vec = vec![0.0; (w * h) as usize]; - for i in 0..w { - for j in 0..h { - let pixel = im.get_pixel(i, j).0[0] as f32 / 255.0; - let index = get_index(w, i, j); - data[index] = pixel; - } - } - - RawImage { data, w, h } + (im, true) } diff --git a/yas/src/ocr/yas_model/yas_ocr_model.rs b/yas/src/ocr/yas_model/yas_ocr_model.rs new file mode 100644 index 00000000..314a23ca --- /dev/null +++ b/yas/src/ocr/yas_model/yas_ocr_model.rs @@ -0,0 +1,155 @@ +use std::cell::RefCell; +use std::time::SystemTime; +use image::{EncodableLayout, GrayImage, ImageBuffer, Luma, RgbImage}; +use tract_onnx::prelude::*; +use crate::ocr::traits::ImageToText; +use super::preprocess; +use anyhow::Result; +use crate::common::image_ext::*; + +type ModelType = RunnableModel, Graph>>; + +pub struct YasOCRModel { + model: ModelType, + index_to_word: Vec, + + inference_time: RefCell, // in seconds + invoke_count: RefCell, +} + +impl YasOCRModel { + fn inc_statistics(&self, time: f64) { + let mut count_handle = self.invoke_count.borrow_mut(); + *count_handle += 1; + + let mut time_handle = self.inference_time.borrow_mut(); + *time_handle += time; + } + + pub fn get_average_inference_time(&self) -> f64 { + let count = *self.invoke_count.borrow(); + let total_time = *self.inference_time.borrow(); + total_time / count as f64 + } + + pub fn new(model: &[u8], content: &str) -> Result { + let model = tract_onnx::onnx() + .model_for_read(&mut model.as_bytes())? + .with_input_fact(0, f32::fact([1, 1, 32, 384]).into())? + .into_optimized()? + .into_runnable()?; + + let json = serde_json::from_str::(content)?; + + let mut index_to_word = json + .as_object() + .unwrap() + .iter() + .map(|(k, v)| (k.parse::().unwrap(), v.as_str().unwrap().to_string())) + .collect::>(); + + index_to_word.sort_by(|(k1, _), (k2, _)| k1.cmp(k2)); + + let index_to_word = index_to_word.into_iter().map(|(_, v)| v).collect(); + + Ok(YasOCRModel { + model, + index_to_word, + inference_time: RefCell::new(0.0), + invoke_count: RefCell::new(0), + }) + } + + pub fn inference_string(&self, img: &ImageBuffer, Vec>) -> Result { + let now = SystemTime::now(); + + let tensor: Tensor = + tract_ndarray::Array4::from_shape_fn((1, 1, 32, 384), |(_, _, y, x)| { + img.get_pixel(x as u32, y as u32)[0] + }).into(); + + let result = self.model.run(tvec!(tensor.into()))?; + let arr = result[0].to_array_view::()?; + + let shape = arr.shape(); + + let mut ans = String::new(); + let mut last_word = String::new(); + for i in 0..shape[0] { + let mut max_index = 0; + let mut max_value = -1.0; + for j in 0..self.index_to_word.len() { + let value = arr[[i, 0, j]]; + if value > max_value { + max_value = value; + max_index = j; + } + } + let word = &self.index_to_word[max_index]; + if *word != last_word && word != "-" { + ans = ans + word; + } + + last_word = word.clone(); + } + + let time = now.elapsed()?.as_secs_f64(); + self.inc_statistics(time); + + Ok(ans) + } +} + +impl ImageToText for YasOCRModel { + fn image_to_text(&self, image: &RgbImage, is_preprocessed: bool) -> Result { + assert_eq!(is_preprocessed, false); + + let gray_image_float = preprocess::to_gray(image); + let (result, non_mono) = preprocess::pre_process(gray_image_float); + + if !non_mono { + return Ok(String::new()); + } + + let string_result = self.inference_string(&result)?; + + Ok(string_result) + } +} + +impl ImageToText, Vec>> for YasOCRModel { + fn image_to_text(&self, image: &ImageBuffer, Vec>, is_preprocessed: bool) -> Result { + if is_preprocessed { + let string_result = self.inference_string(image)?; + Ok(string_result) + } else { + let im = image.clone(); + let (preprocess_result, non_mono) = preprocess::pre_process(im); + + if !non_mono { + return Ok(String::new()); + } + + let string_result = self.inference_string(&preprocess_result)?; + Ok(string_result) + } + } +} + +impl ImageToText for YasOCRModel { + fn image_to_text(&self, im: &GrayImage, is_preprocessed: bool) -> Result { + let gray_f32_image: ImageBuffer, Vec> = im.to_f32_gray_image(); + self.image_to_text(&gray_f32_image, is_preprocessed) + } +} + +pub macro yas_ocr_model($model_name:literal, $index_to_word:literal) { + { + let model_bytes = include_bytes!($model_name); + let index_to_word = include_str!($index_to_word); + + YasOCRModel::new( + model_bytes, index_to_word, + ) + } +} diff --git a/yas/src/positioning/mod.rs b/yas/src/positioning/mod.rs new file mode 100644 index 00000000..a4a42410 --- /dev/null +++ b/yas/src/positioning/mod.rs @@ -0,0 +1,9 @@ +mod size; +mod scalable; +mod rect; +mod pos; + +pub use scalable::Scalable; +pub use size::Size; +pub use pos::Pos; +pub use rect::Rect; diff --git a/yas/src/positioning/pos.rs b/yas/src/positioning/pos.rs new file mode 100644 index 00000000..f24d6afc --- /dev/null +++ b/yas/src/positioning/pos.rs @@ -0,0 +1,72 @@ +use std::fmt::Display; +use std::ops::{Add, Sub}; + +use serde::{Deserialize, Serialize}; + +use crate::positioning::Scalable; + +#[derive(Debug, Clone, PartialEq, Default, Copy, Serialize, Deserialize)] +pub struct Pos { + pub x: T, + pub y: T, +} + +impl Add> for Pos where T: Add { + type Output = Self; + + fn add(self, rhs: Pos) -> Self::Output { + Pos { + x: self.x + rhs.x, + y: self.y + rhs.y, + } + } +} + +impl Sub> for Pos where T: Sub { + type Output = Self; + + fn sub(self, rhs: Pos) -> Self::Output { + Pos { + x: self.x - rhs.x, + y: self.y - rhs.y, + } + } +} + +impl Pos { + pub fn new(x: T, y: T) -> Pos { + Pos { + x, y + } + } +} + +impl Display for Pos where T: Display { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "({}, {})", self.x, self.y) + } +} + +impl Scalable for Pos { + fn scale(&self, factor: f64) -> Pos { + Pos { + x: self.x * factor, + y: self.y * factor, + } + } +} + +macro impl_int_pos($t:ty) { + impl Scalable for Pos<$t> { + fn scale(&self, factor: f64) -> Pos<$t> { + Pos { + x: ((self.x as f64) * factor) as $t, + y: ((self.y as f64) * factor) as $t + } + } + } +} + +impl_int_pos!(i32); +impl_int_pos!(usize); +impl_int_pos!(u32); diff --git a/yas/src/positioning/rect.rs b/yas/src/positioning/rect.rs new file mode 100644 index 00000000..86ff5a2f --- /dev/null +++ b/yas/src/positioning/rect.rs @@ -0,0 +1,88 @@ +use std::fmt::Display; +use std::ops::Add; +use serde::{Deserialize, Serialize}; +use crate::positioning::{Pos, Scalable, Size}; +use paste::paste; + +#[derive(Debug, Clone, PartialEq, Default, Copy, Serialize, Deserialize)] +pub struct Rect { + pub left: T, + pub top: T, + pub width: T, + pub height: T, +} + +impl Rect where T: Copy { + pub fn new(left: T, top: T, width: T, height: T) -> Rect { + Rect { + left, top, width, height + } + } + + pub fn origin(&self) -> Pos { + Pos { + x: self.left, + y: self.top + } + } + + pub fn size(&self) -> Size { + Size { + width: self.width, + height: self.height + } + } +} + +impl Rect where T: Add + Copy { + pub fn translate(&self, pos: Pos) -> Rect { + Rect { + left: self.left + pos.x, + top: self.top + pos.y, + width: self.width, + height: self.height + } + } +} + +impl Display for Rect where T: Display + Copy { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Rect {} -> {}", self.origin(), self.size()) + } +} + +impl Scalable for Rect { + fn scale(&self, factor: f64) -> Self { + Rect { + left: self.left * factor, + top: self.top * factor, + width: self.width * factor, + height: self.height * factor, + } + } +} + +macro_rules! convert_rect_type { + ($t1:ty, $t2:ty) => { + impl Rect<$t1> { + paste!{ + pub fn [](&self) -> Rect<$t2> { + Rect { + left: self.left as $t2, + top: self.top as $t2, + width: self.width as $t2, + height: self.height as $t2, + } + } + } + } + } +} + +convert_rect_type!(f64, i32); +convert_rect_type!(f64, usize); +convert_rect_type!(f64, u32); +convert_rect_type!(u32, usize); +convert_rect_type!(i32, usize); +convert_rect_type!(i32, f64); +convert_rect_type!(i32, u32); diff --git a/yas/src/positioning/scalable.rs b/yas/src/positioning/scalable.rs new file mode 100644 index 00000000..fb5e7413 --- /dev/null +++ b/yas/src/positioning/scalable.rs @@ -0,0 +1,21 @@ +pub trait Scalable { + fn scale(&self, factor: f64) -> Self; +} + +impl Scalable for f64 { + fn scale(&self, factor: f64) -> Self { + *self * factor + } +} + +macro impl_int_scale($t:ty) { + impl Scalable for $t { + fn scale(&self, factor: f64) -> Self { + ((*self as f64) * factor) as $t + } + } +} + +impl_int_scale!(i32); +impl_int_scale!(usize); +impl_int_scale!(u32); diff --git a/yas/src/positioning/size.rs b/yas/src/positioning/size.rs new file mode 100644 index 00000000..96273d41 --- /dev/null +++ b/yas/src/positioning/size.rs @@ -0,0 +1,63 @@ +use std::fmt::{Display, Formatter}; +use std::hash::{Hash, Hasher}; + +use serde::{Deserialize, Serialize}; + +use crate::positioning::Scalable; + +#[derive(Debug, Clone, PartialEq, Eq, Default, Copy, Serialize, Deserialize)] +pub struct Size { + pub height: T, + pub width: T, +} + +impl Size { + pub fn new(width: T, height: T) -> Size { + Size { + width, height + } + } +} + +impl Display for Size where T: Display { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "Size({}, {})", self.height, self.width) + } +} + +macro impl_int_size($t:ty) { + impl Scalable for Size<$t> { + fn scale(&self, factor: f64) -> Self { + Size { + height: ((self.height as f64) * factor) as $t, + width: ((self.width as f64) * factor) as $t, + } + } + } +} + +impl Scalable for Size { + fn scale(&self, factor: f64) -> Self { + Size { + height: self.height * factor, + width: self.width * factor + } + } +} + +impl_int_size!(i32); +impl_int_size!(usize); +impl_int_size!(u32); + +macro impl_int_hash($t:ty) { + impl Hash for Size<$t> { + fn hash(&self, state: &mut H) { + self.width.hash(state); + self.height.hash(state); + } + } +} + +impl_int_hash!(i32); +impl_int_hash!(usize); +impl_int_hash!(u32); diff --git a/yas/src/system_control/linux/linux_control.rs b/yas/src/system_control/linux/linux_control.rs new file mode 100644 index 00000000..a402bef1 --- /dev/null +++ b/yas/src/system_control/linux/linux_control.rs @@ -0,0 +1,31 @@ +use enigo::Enigo; + +pub struct LinuxControl { + enigo: Enigo, +} + +impl LinuxControl { + pub fn new() -> LinuxControl { + LinuxControl { + enigo: Enigo::new(), + } + } + + pub fn mouse_move_to(&mut self, x: i32, y: i32) -> anyhow::Result<()> { + self.enigo.mouse_move_to(x, y); + + anyhow::Ok(()) + } + + pub fn mouse_click(&mut self) -> anyhow::Result<()> { + self.enigo.mouse_click(MouseButton::Left); + + anyhow::Ok(()) + } + + pub fn mouse_scroll(&mut self, amount: i32, _try_find: bool) -> anyhow::Result<()> { + self.enigo.mouse_scroll_y(amount); + + anyhow::Ok(()) + } +} diff --git a/yas/src/system_control/linux/mod.rs b/yas/src/system_control/linux/mod.rs new file mode 100644 index 00000000..d95586d8 --- /dev/null +++ b/yas/src/system_control/linux/mod.rs @@ -0,0 +1 @@ +pub mod linux_control; diff --git a/yas/src/system_control/macos/macos_control.rs b/yas/src/system_control/macos/macos_control.rs new file mode 100644 index 00000000..553bd26d --- /dev/null +++ b/yas/src/system_control/macos/macos_control.rs @@ -0,0 +1,65 @@ +use enigo::{Enigo, MouseControllable}; + +use crate::system_control::system_control::SystemControl; +use crate::utils; + +pub struct MacOSControl { + enigo: Enigo +} + +impl MacOSControl { + pub fn new() -> MacOSControl { + MacOSControl { + enigo: Enigo::new() + } + } + + pub fn mouse_move_to(&mut self, x: i32, y: i32) -> anyhow::Result<()> { + self.enigo.mouse_move_to(x, y); + + anyhow::Ok(()) + } + + pub fn mouse_click(&mut self) -> anyhow::Result<()> { + self.enigo.mouse_click(MouseButton::Left); + + anyhow::Ok(()) + } + + pub fn mouse_scroll(&mut self, amount: i32) -> anyhow::Result<()> { + self.enigo.mouse_scroll_y(-amount); + + anyhow::Ok(()) + } + + pub fn mac_scroll(&mut self, length: i32, delta: i32, times: i32) { + let enigo = &mut self.enigo; + + for _j in 0..length { + enigo.mouse_down(MouseButton::Left); + for _i in 0..times { + enigo.mouse_move_relative(0, -delta); + utils::sleep(10); + } + + enigo.mouse_up(MouseButton::Left); + utils::sleep(10); + + enigo.mouse_down(MouseButton::Left); + utils::sleep(5); + enigo.mouse_up(MouseButton::Left); + utils::sleep(5); + + enigo.mouse_move_relative(0, times * delta); + utils::sleep(20); + } + } + + pub fn mac_scroll_fast(length: i32) { + mac_scroll(length, 4, 30); + } + + pub fn mac_scroll_slow(length: i32) { + mac_scroll(length, 4, 5); + } +} \ No newline at end of file diff --git a/yas/src/system_control/macos/mod.rs b/yas/src/system_control/macos/mod.rs new file mode 100644 index 00000000..69562820 --- /dev/null +++ b/yas/src/system_control/macos/mod.rs @@ -0,0 +1 @@ +pub mod macos_control; diff --git a/yas/src/system_control/mod.rs b/yas/src/system_control/mod.rs new file mode 100644 index 00000000..4b901516 --- /dev/null +++ b/yas/src/system_control/mod.rs @@ -0,0 +1,13 @@ +#[cfg(target_os = "windows")] +pub mod windows; +#[cfg(target_os = "macos")] +pub mod macos; +#[cfg(target_os = "linux")] +pub mod linux; + +#[cfg(target_os = "windows")] +pub use windows::windows_control::WindowsSystemControl as SystemControl; +#[cfg(target_os = "macos")] +pub use macos::macos_control::MacOSControl as SystemControl; +#[cfg(target_os = "linux")] +pub use linux::linux_control::LinuxControl as SystemControl; diff --git a/yas/src/system_control/windows/mod.rs b/yas/src/system_control/windows/mod.rs new file mode 100644 index 00000000..a8511eb0 --- /dev/null +++ b/yas/src/system_control/windows/mod.rs @@ -0,0 +1 @@ +pub mod windows_control; diff --git a/yas/src/system_control/windows/windows_control.rs b/yas/src/system_control/windows/windows_control.rs new file mode 100644 index 00000000..8554d06c --- /dev/null +++ b/yas/src/system_control/windows/windows_control.rs @@ -0,0 +1,29 @@ +use enigo::{Enigo, MouseControllable, MouseButton}; + +pub struct WindowsSystemControl { + enigo: Enigo, +} + +impl WindowsSystemControl { + pub fn new() -> WindowsSystemControl { + WindowsSystemControl { enigo: Enigo::new() } + } + + pub fn mouse_move_to(&mut self, x: i32, y: i32) -> anyhow::Result<()> { + self.enigo.mouse_move_to(x, y); + + anyhow::Ok(()) + } + + pub fn mouse_click(&mut self) -> anyhow::Result<()> { + self.enigo.mouse_click(MouseButton::Left); + + anyhow::Ok(()) + } + + pub fn mouse_scroll(&mut self, amount: i32, _try_find: bool) -> anyhow::Result<()> { + self.enigo.mouse_scroll_y(amount); + + anyhow::Ok(()) + } +} diff --git a/src/common/utils/mac.rs b/yas/src/utils/macos.rs similarity index 67% rename from src/common/utils/mac.rs rename to yas/src/utils/macos.rs index f5513a75..a347f7c3 100644 --- a/src/common/utils/mac.rs +++ b/yas/src/utils/macos.rs @@ -1,4 +1,4 @@ -use crate::common::{utils, PixelRect, UI}; +use crate::common::*; use cocoa::{ appkit::CGFloat, base::NO, @@ -6,23 +6,37 @@ use cocoa::{ }; use enigo::*; -pub fn mac_scroll(enigo: &mut Enigo, count: i32) { - utils::sleep(10); - for j in 0..count { +pub fn mac_scroll(enigo: &mut Enigo, length: i32, delta: i32, times: i32) { + for _j in 0..length { enigo.mouse_down(MouseButton::Left); - for i in 0..5 { - enigo.mouse_move_relative(0, -2); + for _i in 0..times { + enigo.mouse_move_relative(0, -delta); utils::sleep(10); } + enigo.mouse_up(MouseButton::Left); utils::sleep(10); - enigo.mouse_move_relative(0, 10); + + enigo.mouse_down(MouseButton::Left); + utils::sleep(5); + enigo.mouse_up(MouseButton::Left); + utils::sleep(5); + + enigo.mouse_move_relative(0, times * delta); utils::sleep(20); } } +pub fn mac_scroll_fast(enigo: &mut Enigo, length: i32) { + mac_scroll(enigo, length, 4, 30); +} + +pub fn mac_scroll_slow(enigo: &mut Enigo, length: i32) { + mac_scroll(enigo, length, 4, 5); +} + pub fn get_titlebar_height() -> f64 { - use cocoa::appkit::{NSBackingStoreBuffered, NSImage, NSWindow, NSWindowStyleMask}; + use cocoa::appkit::{NSBackingStoreBuffered, NSWindow, NSWindowStyleMask}; use cocoa::base::nil; use cocoa::foundation::NSRect; let ns_point = NSPoint::new(100 as CGFloat, 100 as CGFloat); @@ -50,20 +64,18 @@ pub fn get_pid_and_ui() -> (i32, UI) { String::from_utf8_unchecked( std::process::Command::new("sh") .arg("-c") - .arg(&format!(r#"ps -Aj | grep [Y]uanshen | cut -f 2 -w"#)) + .arg(r#"ps -Aj | grep "PlayCover/" | cut -f 2 -w | head -n 1"#) .output() .unwrap() .stdout, ) }; - let pid_str_genshin_wine = unsafe { + let pid_str_wine = unsafe { String::from_utf8_unchecked( std::process::Command::new("sh") .arg("-c") - .arg(&format!( - r#"top -l 1 -o mem | grep wine64-preloader | head -n 1 | sed 's/^[ ]*//' | cut -d ' ' -f 1"# - )) + .arg(r#"top -l 1 -o mem | grep wine64-preloader | head -n 1 | sed 's/^[ ]*//' | cut -d ' ' -f 1"#) .output() .unwrap() .stdout, @@ -72,17 +84,23 @@ pub fn get_pid_and_ui() -> (i32, UI) { match pid_str_playcover.trim().parse::() { Ok(pid) => (pid, UI::Mobile), - Err(_) => match pid_str_genshin_wine.trim().parse::() { + Err(_) => match pid_str_wine.trim().parse::() { Ok(pid) => (pid, UI::Desktop), - Err(_) => panic!("No genshin program found"), + Err(_) => crate::error_and_quit!("No game program found"), }, } } -pub unsafe fn find_window_by_pid(pid: i32) -> Result<(PixelRect, String), String> { - use cocoa::appkit::{NSBackingStoreBuffered, NSImage, NSWindow, NSWindowStyleMask}; - use cocoa::base::nil; - use cocoa::foundation::NSRect; +#[allow(clippy::default_constructed_unit_structs)] +pub fn request_capture_access() -> bool { + use core_graphics::access::ScreenCaptureAccess; + + let access = ScreenCaptureAccess::default(); + + access.preflight() || access.request() +} + +pub unsafe fn find_window_by_pid(pid: i32) -> Result<(Rect, String), String> { use core_foundation::array::{CFArrayGetCount, CFArrayGetValueAtIndex}; use core_foundation::base::TCFType; use core_foundation::dictionary::{ @@ -101,16 +119,12 @@ pub unsafe fn find_window_by_pid(pid: i32) -> Result<(PixelRect, String), String let cf_win_array = CGWindowListCopyWindowInfo(kCGWindowListOptionExcludeDesktopElements, kCGNullWindowID); let count = CFArrayGetCount(cf_win_array); + if count == 0 { - return Err("No genshin window found".to_string()); + return Err("No game window found".to_string()); } - let mut mrect = PixelRect { - left: 0, - top: 0, - width: 0, - height: 0, - }; + let mut mrect = Rect::default(); let mut window_count = 0; let mut title: String = String::new(); @@ -146,25 +160,13 @@ pub unsafe fn find_window_by_pid(pid: i32) -> Result<(PixelRect, String), String ); let cg_title = CFString::wrap_under_get_rule(cg_title_ref as CFStringRef); title = cg_title.to_string(); - // mac app cg_rect.size.x = 0 when full screen if cg_rect.size.height > 200. { - if cg_rect.origin.y > 0. { + mrect = if cg_rect.origin.y > 0. { // Window Mode - let titlebar_height = get_titlebar_height(); - mrect = PixelRect { - left: cg_rect.origin.x as i32, - top: cg_rect.origin.y as i32 + titlebar_height as i32, // The titlebar appears in window mode - width: cg_rect.size.width as i32, - height: cg_rect.size.height as i32 - titlebar_height as i32, // The titlebar appears in window mode - }; + Rect::from(cg_rect).with_titlebar(get_titlebar_height() as u32) } else { - mrect = PixelRect { - left: cg_rect.origin.x as i32, - top: cg_rect.origin.y as i32, - width: cg_rect.size.width as i32, - height: cg_rect.size.height as i32, - }; - } + Rect::from(cg_rect) + }; window_count += 1 } } diff --git a/yas/src/utils/mod.rs b/yas/src/utils/mod.rs new file mode 100644 index 00000000..626080d1 --- /dev/null +++ b/yas/src/utils/mod.rs @@ -0,0 +1,59 @@ +use std::fmt::Arguments; +use std::fs; +use std::thread; +use std::time::Duration; +use serde::Deserialize; +use std::io::stdin; +use std::process; + +#[cfg(target_os = "macos")] +mod macos; +#[cfg(target_os = "macos")] +pub use macos::*; + +#[cfg(windows)] +mod windows; +#[cfg(windows)] +pub use windows::*; + +pub fn sleep(ms: u32) { + thread::sleep(Duration::from_millis(ms as u64)); +} + +pub fn read_file_to_string(path: String) -> String { + fs::read_to_string(path).unwrap() +} + +pub fn quit() -> ! { + let mut s: String = String::new(); + stdin().read_line(&mut s).unwrap(); + process::exit(0); +} + +#[doc(hidden)] +pub fn error_and_quit_internal(args: Arguments) -> ! { + panic!("Error: {}", args); +} + +#[macro_export] +macro_rules! error_and_quit { + ($($arg:tt)*) => ( + $crate::utils::error_and_quit_internal(format_args!($($arg)*)) + ); +} + +#[cfg(not(windows))] +pub fn is_rmb_down() -> bool { + false +} + +#[derive(Deserialize)] +pub struct GithubTag { + pub name: String, +} + +pub fn ensure_dir(path: &str) { + if !std::path::Path::new(path).exists() { + fs::create_dir_all(path).unwrap(); + } +} \ No newline at end of file diff --git a/src/common/utils/windows.rs b/yas/src/utils/windows.rs similarity index 67% rename from src/common/utils/windows.rs rename to yas/src/utils/windows.rs index f192d747..d65cf86e 100644 --- a/src/common/utils/windows.rs +++ b/yas/src/utils/windows.rs @@ -1,26 +1,26 @@ use std::ffi::OsStr; use std::iter::once; use std::mem::transmute; +use std::os::windows::ffi::OsStrExt; use std::ptr::null_mut; -use crate::common::PixelRect; +use anyhow::{anyhow, Result}; use log::{info, warn}; - -pub use winapi::shared::minwindef::{BOOL, HINSTANCE}; -pub use winapi::shared::windef::{HWND, POINT as WinPoint, RECT as WinRect}; -pub use winapi::um::libloaderapi::{FreeLibrary, GetProcAddress, LoadLibraryA, LoadLibraryW}; -pub use winapi::um::securitybaseapi::{AllocateAndInitializeSid, CheckTokenMembership, FreeSid}; -pub use winapi::um::winnt::{ - CHAR, DOMAIN_ALIAS_RID_ADMINS, PSID, SECURITY_BUILTIN_DOMAIN_RID, SECURITY_NT_AUTHORITY, +use winapi::shared::minwindef::BOOL; +use winapi::shared::windef::{HWND, POINT as WinPoint, RECT as WinRect}; +use winapi::um::libloaderapi::{FreeLibrary, GetProcAddress, LoadLibraryA}; +use winapi::um::securitybaseapi::{AllocateAndInitializeSid, CheckTokenMembership, FreeSid}; +use winapi::um::winnt::{ + DOMAIN_ALIAS_RID_ADMINS, PSID, SECURITY_BUILTIN_DOMAIN_RID, SECURITY_NT_AUTHORITY, SID_IDENTIFIER_AUTHORITY, }; -pub use winapi::um::winuser::{ +use winapi::um::winuser::{ ClientToScreen, FindWindowExW, FindWindowW, GetAsyncKeyState, GetClientRect, GetWindowLongPtrW, - SetForegroundWindow, SetProcessDPIAware, ShowWindow, GWL_EXSTYLE, GWL_STYLE, SW_RESTORE, + GWL_EXSTYLE, GWL_STYLE, SetForegroundWindow, SetProcessDPIAware, ShowWindow, SW_RESTORE, VK_RBUTTON, }; -use std::os::windows::ffi::OsStrExt; +use crate::positioning::Rect; pub fn encode_lpcstr(s: &str) -> Vec { let mut arr: Vec = s.bytes().map(|x| x as i8).collect(); @@ -29,22 +29,25 @@ pub fn encode_lpcstr(s: &str) -> Vec { } fn encode_wide_with_null(s: impl AsRef) -> Vec { - let wide: Vec = OsStr::new(s.as_ref()).encode_wide().chain(once(0)).collect(); + let wide: Vec = OsStr::new(s.as_ref()) + .encode_wide() + .chain(once(0)) + .collect(); wide } -pub fn find_window_local(title: impl AsRef) -> Result { +pub fn find_window_local(title: impl AsRef) -> Result { let title = encode_wide_with_null(title); let class = encode_wide_with_null("UnityWndClass"); let result: HWND = unsafe { FindWindowW(class.as_ptr(), title.as_ptr()) }; if result.is_null() { - Err(String::from("cannot find window")) + Err(anyhow!("cannot find window")) } else { Ok(result) } } -pub fn find_window_cloud() -> Result { +pub fn find_window_cloud() -> Result { let title = encode_wide_with_null(String::from("云·原神")); //let class = encode_wide(String::from("Qt5152QWindowIcon")); unsafe { @@ -60,10 +63,10 @@ pub fn find_window_cloud() -> Result { } } } - Err(String::from("cannot find window")) + Err(anyhow!("cannot find window")) } -unsafe fn get_client_rect_unsafe(hwnd: HWND) -> Result { +unsafe fn get_client_rect_unsafe(hwnd: HWND) -> Result> { let mut rect: WinRect = WinRect { left: 0, top: 0, @@ -79,15 +82,15 @@ unsafe fn get_client_rect_unsafe(hwnd: HWND) -> Result { let left: i32 = point.x; let top: i32 = point.y; - Ok(PixelRect { + Ok(Rect { left, top, width, - height, + height }) } -pub fn get_client_rect(hwnd: HWND) -> Result { +pub fn get_client_rect(hwnd: HWND) -> Result> { unsafe { get_client_rect_unsafe(hwnd) } } @@ -134,51 +137,31 @@ pub fn is_rmb_down() -> bool { state & 1 > 0 } } -pub fn set_dpi_awareness() { - // let os = os_info::get(); +pub fn set_dpi_awareness() { let h_lib = unsafe { - // let names = ["SHCore.dll"] - LoadLibraryA(encode_lpcstr("Shcore.dll").as_ptr()) }; if h_lib.is_null() { - info!("`Shcore.dll` not found"); unsafe { SetProcessDPIAware(); } } else { - info!("`Shcore.dll` found"); unsafe { let addr = GetProcAddress(h_lib, encode_lpcstr("SetProcessDpiAwareness").as_ptr()); if addr.is_null() { warn!("cannot find process `SetProcessDpiAwareness`, but `Shcore.dll` exists"); SetProcessDPIAware(); } else { - // func(PROCESS_DPI_AWARENESS) -> HRESULT let func = transmute::<*const (), fn(u32) -> i32>(addr as *const ()); - // SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE); func(2); } FreeLibrary(h_lib); } } - - // if os.version() >= &os_info::Version::from_string("8.1") { - // info!("Windows version >= 8.1"); - // unsafe { - // SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE); - // } - // } else { - // info!("Windows version < 8.1"); - // unsafe { - // SetProcessDPIAware(); - // } - // } } -#[cfg(windows)] pub fn show_window_and_set_foreground(hwnd: HWND) { unsafe { ShowWindow(hwnd, SW_RESTORE); diff --git a/yas/src/window_info/from_window_info_repository.rs b/yas/src/window_info/from_window_info_repository.rs new file mode 100644 index 00000000..6bc00832 --- /dev/null +++ b/yas/src/window_info/from_window_info_repository.rs @@ -0,0 +1,12 @@ +use crate::game_info::{Platform, UI}; +use crate::positioning::Size; +use crate::window_info::WindowInfoRepository; + +pub trait FromWindowInfoRepository: Sized { + fn from_window_info_repository( + window_size: Size, + ui: UI, + platform: Platform, + repo: &WindowInfoRepository + ) -> anyhow::Result; +} diff --git a/yas/src/window_info/load_window_info.rs b/yas/src/window_info/load_window_info.rs new file mode 100644 index 00000000..e6f95c28 --- /dev/null +++ b/yas/src/window_info/load_window_info.rs @@ -0,0 +1,38 @@ +use std::collections::HashMap; +use serde::{Deserialize, Serialize}; +use crate::game_info::{Platform, UI}; +use crate::positioning::Size; +use crate::window_info::WindowInfoType; +use crate::window_info::WindowInfoRepository; + +/// Which is a format, where the whole file are recorded under a certain resolution +#[derive(Serialize, Deserialize)] +pub struct WindowInfoTemplatePerSize { + pub current_resolution: Size, + pub platform: Platform, + pub ui: UI, + pub data: HashMap +} + +impl WindowInfoTemplatePerSize { + pub fn inject_into_window_info_repo(&self, repo: &mut WindowInfoRepository) { + for (name, value) in self.data.iter() { + repo.add(&name, self.current_resolution, self.ui, self.platform, *value); + } + } +} + +pub macro load_window_info_repo($($filename:literal),+ $(,)?) { + { + let mut result = WindowInfoRepository::new(); + $( + { + let s = include_str!($filename); + let f: WindowInfoTemplatePerSize = serde_json::from_str(&s).unwrap(); + f.inject_into_window_info_repo(&mut result); + } + )* + result + } +} + diff --git a/yas/src/window_info/mod.rs b/yas/src/window_info/mod.rs new file mode 100644 index 00000000..08a1eb74 --- /dev/null +++ b/yas/src/window_info/mod.rs @@ -0,0 +1,9 @@ +mod window_info_repository; +mod window_info_type; +mod load_window_info; +mod from_window_info_repository; + +pub use from_window_info_repository::FromWindowInfoRepository; +pub use window_info_repository::WindowInfoRepository; +pub use window_info_type::WindowInfoType; +pub use load_window_info::load_window_info_repo; \ No newline at end of file diff --git a/yas/src/window_info/window_info_repository.rs b/yas/src/window_info/window_info_repository.rs new file mode 100644 index 00000000..8b84464b --- /dev/null +++ b/yas/src/window_info/window_info_repository.rs @@ -0,0 +1,91 @@ +use std::collections::HashMap; + +use serde::{Deserialize, Serialize}; +use crate::game_info::{Platform, UI}; +use crate::positioning::{Pos, Scalable, Size}; + +use crate::window_info::WindowInfoType; + +/// Maps a window-info-key to a list of entries +/// where entries consist of a size where the value is recorded, and accordingly a value +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct WindowInfoRepository { + /// window info key -> (window size, ui, platform) + pub data: HashMap, UI, Platform), WindowInfoType>>, +} + +impl WindowInfoRepository { + pub fn new() -> WindowInfoRepository { + WindowInfoRepository { + data: HashMap::new(), + } + } + + pub fn add(&mut self, name: &str, size: Size, ui: UI, platform: Platform, value: WindowInfoType) { + self.data + .entry(String::from(name)) + .or_insert(HashMap::new()) + .insert((size, ui, platform), value); + } + + pub fn add_pos(&mut self, name: &str, size: Size, ui: UI, platform: Platform, value: Pos) { + self.data + .entry(String::from(name)) + .or_insert(HashMap::new()) + .insert((size, ui, platform), WindowInfoType::Pos(value)); + } + + pub fn merge_inplace(&mut self, other: &WindowInfoRepository) { + for (key, data) in other.data.iter() { + if self.data.contains_key(key) { + for (resolution, value) in data.iter() { + self.data.get_mut(key).unwrap().insert(resolution.clone(), value.clone()); + } + } else { + self.data.insert(key.clone(), data.clone()); + } + } + } + + pub fn merge(&self, other: &WindowInfoRepository) -> WindowInfoRepository { + let mut result = self.clone(); + result.merge_inplace(other); + result + } + + /// Get window info by name and size + /// if name or resolution does not exist, then return None + pub fn get_exact(&self, name: &str, window_size: Size, ui: UI, platform: Platform) -> Option where WindowInfoType: TryInto { + if self.data.contains_key(name) { + if self.data[name].contains_key(&(window_size, ui, platform)) { + return self.data[name][&(window_size, ui, platform)].try_into().ok(); + } + } + + None + } + + /// Get window info by name and size + /// if window size does not exists exactly, this function will search for the same resolution family and scale the result + pub fn get_auto_scale(&self, name: &str, window_size: Size, ui: UI, platform: Platform) -> Option where WindowInfoType: TryInto { + if self.data.contains_key(name) { + if self.data[name].contains_key(&(window_size, ui, platform)) { + return self.data[name][&(window_size, ui, platform)].try_into().ok(); + } else { + // todo find a biggest size which can be scaled, this will reduce error + // find if a resolution can be scaled + for (k, value) in self.data[name].iter() { + let size = &k.0; + if size.width * window_size.height == size.height * window_size.width + && k.1 == ui && k.2 == platform + { + let factor: f64 = window_size.width as f64 / size.width as f64; + return value.scale(factor).try_into().ok(); + } + } + } + } + + None + } +} diff --git a/yas/src/window_info/window_info_type.rs b/yas/src/window_info/window_info_type.rs new file mode 100644 index 00000000..0f7dec9a --- /dev/null +++ b/yas/src/window_info/window_info_type.rs @@ -0,0 +1,85 @@ +use serde::{Deserialize, Serialize}; +use crate::positioning::{Pos, Rect, Scalable, Size}; +use anyhow::anyhow; + +#[derive(Copy, Clone, Debug, Serialize, Deserialize)] +pub enum WindowInfoType { + Rect(Rect), + Pos(Pos), + Size(Size), + Float(f64), + /// when window size scales, these amount will not scale + InvariantInt(i32), + InvariantFloat(f64), +} + +// due to orphan rule, we implement TryInto instead of TryFrom +impl TryInto for WindowInfoType { + type Error = anyhow::Error; + + fn try_into(self) -> Result { + match self { + WindowInfoType::InvariantInt(v) => Ok(v), + _ => Err(anyhow!(String::from("not an i32 type"))) + } + } +} + +impl TryInto> for WindowInfoType { + type Error = anyhow::Error; + + fn try_into(self) -> Result, Self::Error> { + match self { + WindowInfoType::Rect(rect) => Ok(rect), + _ => Err(anyhow!(String::from("not a rect type"))), + } + } +} + +impl TryInto> for WindowInfoType { + type Error = anyhow::Error; + + fn try_into(self) -> Result, Self::Error> { + match self { + WindowInfoType::Pos(pos) => Ok(pos), + _ => Err(anyhow!(String::from("not a pos type"))), + } + } +} + +impl TryInto for WindowInfoType { + type Error = anyhow::Error; + + fn try_into(self) -> std::result::Result { + match self { + WindowInfoType::Float(f) => Ok(f), + WindowInfoType::InvariantFloat(f) => Ok(f), + _ => Err(anyhow!(String::from("not a float type"))), + } + } +} + +impl TryInto> for WindowInfoType { + type Error = anyhow::Error; + + fn try_into(self) -> Result, Self::Error> { + match self { + WindowInfoType::Size(size) => Ok(size), + _ => Err(anyhow!(String::from("not a size type"))), + } + } +} + +impl Scalable for WindowInfoType { + fn scale(&self, factor: f64) -> Self { + let result = match *self { + WindowInfoType::Rect(rect) => WindowInfoType::Rect(rect.scale(factor)), + WindowInfoType::Pos(pos) => WindowInfoType::Pos(pos.scale(factor)), + WindowInfoType::Size(size) => WindowInfoType::Size(size.scale(factor)), + WindowInfoType::Float(v) => WindowInfoType::Float(v.scale(factor)), + WindowInfoType::InvariantInt(v) => WindowInfoType::InvariantInt(v), + WindowInfoType::InvariantFloat(v) => WindowInfoType::InvariantFloat(v), + }; + result + } +} \ No newline at end of file