├── .rustfmt.toml ├── yas-genshin ├── src │ ├── export │ │ ├── mod.rs │ │ └── artifact │ │ │ ├── mod.rs │ │ │ ├── export_format.rs │ │ │ ├── config.rs │ │ │ ├── csv.rs │ │ │ └── exporter.rs │ ├── scanner_controller │ │ ├── mod.rs │ │ └── repository_layout │ │ │ ├── scroll_result.rs │ │ │ ├── mod.rs │ │ │ ├── window_info.rs │ │ │ └── config.rs │ ├── scanner │ │ ├── item_scanner │ │ │ ├── mod.rs │ │ │ └── item_scanner_config.rs │ │ ├── artifact_scanner │ │ │ ├── models │ │ │ │ └── model_training.onnx │ │ │ ├── message_items.rs │ │ │ ├── scan_result.rs │ │ │ ├── mod.rs │ │ │ ├── artifact_scanner_config.rs │ │ │ └── artifact_scanner_window_info.rs │ │ └── mod.rs │ ├── character │ │ ├── mod.rs │ │ └── character_names.rs │ ├── application │ │ ├── mod.rs │ │ └── artifact_scanner.rs │ ├── artifact │ │ ├── mod.rs │ │ └── zh_cn.rs │ ├── lib.rs │ └── bin │ │ └── playground.rs ├── Cargo.toml └── window_info │ ├── windows3440x1440.json │ ├── windows2100x900.json │ ├── windows1280x960.json │ ├── windows1440x900.json │ └── windows1600x900.json ├── yas-starrail ├── src │ ├── scanner │ │ ├── mod.rs │ │ └── relic_scanner │ │ │ ├── models │ │ │ └── model_training.onnx │ │ │ ├── message_items.rs │ │ │ ├── scan_result.rs │ │ │ ├── mod.rs │ │ │ ├── relic_scanner_config.rs │ │ │ ├── relic_scanner_window_info.rs │ │ │ ├── match_colors.rs │ │ │ └── relic_scanner_worker.rs │ ├── export │ │ ├── mod.rs │ │ └── relic │ │ │ ├── mod.rs │ │ │ ├── export_format.rs │ │ │ ├── config.rs │ │ │ └── exporter.rs │ ├── scanner_controller │ │ ├── mod.rs │ │ └── repository_layout │ │ │ ├── scroll_result.rs │ │ │ ├── mod.rs │ │ │ ├── window_info.rs │ │ │ └── config.rs │ ├── application │ │ ├── mod.rs │ │ └── relic_scanner.rs │ ├── relic │ │ └── mod.rs │ └── lib.rs ├── Cargo.toml └── window_info │ └── windows1920x1080.json ├── yas-wutheringwaves ├── src │ ├── export │ │ ├── mod.rs │ │ └── echo │ │ │ ├── mod.rs │ │ │ ├── export_format.rs │ │ │ ├── config.rs │ │ │ └── hsi.rs │ ├── application │ │ ├── mod.rs │ │ └── ww_echo_scanner.rs │ ├── scanner │ │ ├── mod.rs │ │ └── echo_scanner │ │ │ ├── message_item.rs │ │ │ ├── models │ │ │ ├── model_training.onnx │ │ │ └── index_2_word.json │ │ │ ├── mod.rs │ │ │ ├── scan_result.rs │ │ │ ├── echo_scanner_config.rs │ │ │ └── echo_scanner_window_info.rs │ ├── scanner_controller │ │ ├── mod.rs │ │ └── repository │ │ │ ├── mod.rs │ │ │ ├── repository_layout_window_info.rs │ │ │ └── config.rs │ ├── echo │ │ ├── mod.rs │ │ ├── echo_name.rs │ │ ├── echo.rs │ │ └── stats.rs │ └── lib.rs ├── Cargo.toml └── window_info │ └── windows2560x1440.json ├── .gitattributes ├── yas ├── src │ ├── system_control │ │ ├── linux │ │ │ ├── mod.rs │ │ │ └── linux_control.rs │ │ ├── macos │ │ │ ├── mod.rs │ │ │ └── macos_control.rs │ │ ├── windows │ │ │ ├── mod.rs │ │ │ └── windows_control.rs │ │ └── mod.rs │ ├── draw_capture_region │ │ ├── mod.rs │ │ └── draw_capture_region.rs │ ├── profiler │ │ ├── mod.rs │ │ └── profiler.rs │ ├── common │ │ ├── mod.rs │ │ ├── image_ext.rs │ │ ├── color.rs │ │ └── cancel.rs │ ├── export │ │ ├── asset_emitter.rs │ │ ├── mod.rs │ │ ├── export_item.rs │ │ ├── export_statistics.rs │ │ └── exporter.rs │ ├── ocr │ │ ├── paddle_paddle_model │ │ │ ├── mod.rs │ │ │ ├── ch_PP-OCRv4_rec_infer.onnx │ │ │ └── preprocess.rs │ │ ├── yas_model │ │ │ ├── mod.rs │ │ │ └── preprocess.rs │ │ ├── mod.rs │ │ └── traits.rs │ ├── positioning │ │ ├── mod.rs │ │ ├── shape.rs │ │ ├── scalable.rs │ │ ├── size.rs │ │ ├── pos.rs │ │ └── rect.rs │ ├── game_info │ │ ├── mod.rs │ │ ├── os │ │ │ ├── mod.rs │ │ │ ├── macos.rs │ │ │ ├── linux.rs │ │ │ └── winodws.rs │ │ ├── game_info.rs │ │ ├── ui.rs │ │ ├── resolution_family.rs │ │ └── game_info_builder.rs │ ├── window_info │ │ ├── mod.rs │ │ ├── from_window_info_repository.rs │ │ ├── load_window_info.rs │ │ ├── window_info_type.rs │ │ └── window_info_repository.rs │ ├── capture │ │ ├── generic_capturer.rs │ │ ├── mod.rs │ │ ├── capturer.rs │ │ ├── windows_capturer.rs │ │ ├── screenshots_capturer.rs │ │ ├── libwayshot_capturer.rs │ │ ├── stream_capturer.rs │ │ └── winapi_capturer.rs │ ├── utils │ │ ├── misc.rs │ │ ├── mod.rs │ │ ├── windows.rs │ │ └── macos.rs │ └── lib.rs └── Cargo.toml ├── yas-derive-wuthering-waves ├── src │ ├── echoes │ │ ├── mod.rs │ │ └── echo_item.rs │ └── lib.rs └── Cargo.toml ├── .gitignore ├── yas-derive ├── src │ ├── window_info │ │ ├── mod.rs │ │ ├── nested_attributes.rs │ │ └── derive_macro.rs │ └── lib.rs └── Cargo.toml ├── codesign.sh ├── Cargo.toml ├── assets ├── manifest.xml └── app.entitlements ├── yas-application ├── build.rs ├── src │ └── bin │ │ ├── yas_relic.rs │ │ ├── yas_ww_echo.rs │ │ ├── yas_artifact.rs │ │ └── yas.rs └── Cargo.toml ├── .github └── workflows │ └── rust.yml └── README.md /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | match_block_trailing_comma = true 2 | -------------------------------------------------------------------------------- /yas-genshin/src/export/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod artifact; 2 | -------------------------------------------------------------------------------- /yas-starrail/src/scanner/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod relic_scanner; -------------------------------------------------------------------------------- /yas-wutheringwaves/src/export/mod.rs: -------------------------------------------------------------------------------- 1 | mod echo; 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.onnx filter=lfs diff=lfs merge=lfs -text 2 | -------------------------------------------------------------------------------- /yas/src/system_control/linux/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod linux_control; 2 | -------------------------------------------------------------------------------- /yas/src/system_control/macos/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod macos_control; 2 | -------------------------------------------------------------------------------- /yas-starrail/src/export/mod.rs: -------------------------------------------------------------------------------- 1 | pub use relic::*; 2 | 3 | mod relic; -------------------------------------------------------------------------------- /yas-starrail/src/scanner_controller/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod repository_layout; -------------------------------------------------------------------------------- /yas/src/draw_capture_region/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod draw_capture_region; 2 | -------------------------------------------------------------------------------- /yas/src/system_control/windows/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod windows_control; 2 | -------------------------------------------------------------------------------- /yas-genshin/src/scanner_controller/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod repository_layout; 2 | 3 | -------------------------------------------------------------------------------- /yas/src/profiler/mod.rs: -------------------------------------------------------------------------------- 1 | pub use profiler::Profiler; 2 | 3 | mod profiler; 4 | -------------------------------------------------------------------------------- /yas/src/common/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod cancel; 2 | pub mod color; 3 | pub mod image_ext; 4 | -------------------------------------------------------------------------------- /yas-wutheringwaves/src/export/echo/mod.rs: -------------------------------------------------------------------------------- 1 | mod config; 2 | mod export_format; 3 | mod hsi; 4 | -------------------------------------------------------------------------------- /yas-derive-wuthering-waves/src/echoes/mod.rs: -------------------------------------------------------------------------------- 1 | pub use echo_item::EchoDataItem; 2 | 3 | mod echo_item; -------------------------------------------------------------------------------- /yas-genshin/src/scanner/item_scanner/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod item_scanner_config; 2 | pub mod item_scanner; 3 | -------------------------------------------------------------------------------- /yas-genshin/src/character/mod.rs: -------------------------------------------------------------------------------- 1 | pub use character_names::CHARACTER_NAMES; 2 | 3 | mod character_names; 4 | 5 | -------------------------------------------------------------------------------- /yas-starrail/src/application/mod.rs: -------------------------------------------------------------------------------- 1 | pub use relic_scanner::RelicScannerApplication; 2 | 3 | mod relic_scanner; 4 | -------------------------------------------------------------------------------- /yas-genshin/src/application/mod.rs: -------------------------------------------------------------------------------- 1 | pub use artifact_scanner::ArtifactScannerApplication; 2 | 3 | mod artifact_scanner; 4 | 5 | -------------------------------------------------------------------------------- /yas-wutheringwaves/src/application/mod.rs: -------------------------------------------------------------------------------- 1 | pub use ww_echo_scanner::WWEchoScannerApplication; 2 | 3 | mod ww_echo_scanner; 4 | -------------------------------------------------------------------------------- /yas-wutheringwaves/src/scanner/mod.rs: -------------------------------------------------------------------------------- 1 | pub use echo_scanner::{WWEchoScanner, WWEchoScannerConfig}; 2 | 3 | mod echo_scanner; 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | captures/ 3 | dumps/ 4 | .vscode/ 5 | .idea/ 6 | /*.json 7 | **/*.png 8 | **tmp** 9 | **/.DS_Store 10 | data/ 11 | -------------------------------------------------------------------------------- /yas-wutheringwaves/src/scanner/echo_scanner/message_item.rs: -------------------------------------------------------------------------------- 1 | use image::RgbImage; 2 | 3 | pub struct SendItem { 4 | pub panel_image: RgbImage, 5 | } -------------------------------------------------------------------------------- /yas/src/export/asset_emitter.rs: -------------------------------------------------------------------------------- 1 | use crate::export::ExportAssets; 2 | 3 | pub trait AssetEmitter { 4 | fn emit(&self, asset_bundle: &mut ExportAssets); 5 | } -------------------------------------------------------------------------------- /yas-wutheringwaves/src/scanner_controller/mod.rs: -------------------------------------------------------------------------------- 1 | pub use repository::{WWRepositoryLayoutScanController, WWRepositoryLayoutConfig, ReturnResult}; 2 | 3 | mod repository; 4 | -------------------------------------------------------------------------------- /yas/src/ocr/paddle_paddle_model/mod.rs: -------------------------------------------------------------------------------- 1 | mod model; 2 | mod preprocess; 3 | 4 | pub use model::PPOCRModel; 5 | pub use model::ppocr_model; 6 | pub use model::PPOCRChV4RecInfer; 7 | -------------------------------------------------------------------------------- /yas-derive/src/window_info/mod.rs: -------------------------------------------------------------------------------- 1 | mod nested_attributes; 2 | mod derive_macro; 3 | 4 | pub use derive_macro::yas_window_info; 5 | pub use nested_attributes::WindowInfoNestedAttributes; 6 | -------------------------------------------------------------------------------- /yas-wutheringwaves/src/echo/mod.rs: -------------------------------------------------------------------------------- 1 | pub use echo_name::WWEchoName; 2 | pub use stats::{WWStat, WWStatName}; 3 | pub use echo::WWEcho; 4 | 5 | mod echo; 6 | mod echo_name; 7 | mod stats; 8 | -------------------------------------------------------------------------------- /yas/src/ocr/paddle_paddle_model/ch_PP-OCRv4_rec_infer.onnx: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:902666a62eb7ff8a6b954a8b706cdd52e224b4f32584f30eeb03202cf6a32aea 3 | size 10826466 4 | -------------------------------------------------------------------------------- /yas/src/ocr/yas_model/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod preprocess; 2 | pub mod yas_ocr_model; 3 | 4 | // pub use preprocess::to_gray; 5 | // pub use preprocess::pre_process; 6 | // pub use yas_ocr_model::YasOCRModel; 7 | -------------------------------------------------------------------------------- /yas-genshin/src/scanner/artifact_scanner/models/model_training.onnx: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:89664e170052241c8c845bb83a15b4510f40271df61226bb0ebbb7aca9fdbbb8 3 | size 4761632 4 | -------------------------------------------------------------------------------- /yas-starrail/src/relic/mod.rs: -------------------------------------------------------------------------------- 1 | pub use relic::RelicSlot; 2 | pub use relic::RelicSetName; 3 | pub use relic::RelicStat; 4 | pub use relic::RelicStatName; 5 | pub use relic::StarRailRelic; 6 | 7 | mod relic; 8 | -------------------------------------------------------------------------------- /yas-starrail/src/scanner/relic_scanner/models/model_training.onnx: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:7815c2bf52e356cf81e03b184a904226a8a5be25353e439684e7acf2b4aa7b51 3 | size 4702100 4 | -------------------------------------------------------------------------------- /yas-genshin/src/scanner_controller/repository_layout/scroll_result.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug)] 2 | pub enum ScrollResult { 3 | TimeLimitExceeded, 4 | Interrupt, 5 | Success, 6 | Failed, 7 | Skip, 8 | } 9 | -------------------------------------------------------------------------------- /yas-starrail/src/scanner_controller/repository_layout/scroll_result.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug)] 2 | pub enum ScrollResult { 3 | TimeLimitExceeded, 4 | Interrupt, 5 | Success, 6 | Failed, 7 | Skip, 8 | } 9 | -------------------------------------------------------------------------------- /yas-wutheringwaves/src/echo/echo_name.rs: -------------------------------------------------------------------------------- 1 | // use yas_derive_wuthering_waves::yas_wuthering_waves_echoes; 2 | 3 | yas_derive_wuthering_waves::yas_wuthering_waves_echoes!("yas-wutheringwaves/data/echoes.json"); 4 | 5 | -------------------------------------------------------------------------------- /yas-wutheringwaves/src/scanner/echo_scanner/models/model_training.onnx: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:b551296e592ef33d3e735c5e6853b6fc1c2a6e68d8c5475a5dfc2b0334993258 3 | size 4543832 4 | -------------------------------------------------------------------------------- /yas-derive-wuthering-waves/src/echoes/echo_item.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize}; 2 | 3 | #[derive(Deserialize)] 4 | pub struct EchoDataItem { 5 | pub name: String, 6 | // pub cost: usize, 7 | pub name_chs: String, 8 | } 9 | -------------------------------------------------------------------------------- /yas/src/positioning/mod.rs: -------------------------------------------------------------------------------- 1 | mod size; 2 | mod scalable; 3 | mod rect; 4 | mod pos; 5 | mod shape; 6 | 7 | pub use scalable::Scalable; 8 | pub use size::Size; 9 | pub use pos::Pos; 10 | pub use rect::Rect; 11 | pub use shape::Shape3D; 12 | -------------------------------------------------------------------------------- /yas-starrail/src/scanner/relic_scanner/message_items.rs: -------------------------------------------------------------------------------- 1 | use image::RgbImage; 2 | 3 | pub struct SendItem { 4 | pub panel_image: RgbImage, 5 | pub equip: String, 6 | pub star: usize, 7 | pub lock: bool, 8 | pub discard: bool, 9 | } -------------------------------------------------------------------------------- /yas-genshin/src/artifact/mod.rs: -------------------------------------------------------------------------------- 1 | pub use artifact::ArtifactSetName; 2 | pub use artifact::ArtifactSlot; 3 | pub use artifact::ArtifactStat; 4 | pub use artifact::ArtifactStatName; 5 | pub use artifact::GenshinArtifact; 6 | 7 | mod artifact; 8 | mod zh_cn; 9 | -------------------------------------------------------------------------------- /yas-genshin/src/scanner/mod.rs: -------------------------------------------------------------------------------- 1 | pub use artifact_scanner::GenshinArtifactScanner; 2 | pub use artifact_scanner::GenshinArtifactScannerConfig; 3 | pub use artifact_scanner::GenshinArtifactScanResult; 4 | 5 | mod artifact_scanner; 6 | // mod item_scanner; 7 | 8 | -------------------------------------------------------------------------------- /yas-starrail/src/export/relic/mod.rs: -------------------------------------------------------------------------------- 1 | pub use exporter::StarRailRelicExporter; 2 | pub use config::ExportRelicConfig; 3 | pub use export_format::StarRailRelicExportFormat; 4 | mod march7th; 5 | mod exporter; 6 | mod export_format; 7 | mod config; 8 | mod hsr; 9 | -------------------------------------------------------------------------------- /codesign.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # This is bad, but post build scripts still not implemented yet for cargo 4 | # https://github.com/rust-lang/cargo/issues/545 5 | 6 | codesign -f -s - --timestamp=none --entitlements assets/app.entitlements ./target/*/yas_scanner* 7 | -------------------------------------------------------------------------------- /yas-starrail/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(coroutines, coroutine_trait)] 2 | #![feature(stmt_expr_attributes)] 3 | #![allow(unused_imports)] 4 | 5 | pub mod scanner_controller; 6 | pub mod export; 7 | pub mod scanner; 8 | pub mod relic; 9 | pub mod application; 10 | -------------------------------------------------------------------------------- /yas-derive/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate proc_macro; 2 | mod window_info; 3 | #[proc_macro_derive(YasWindowInfo, attributes(window_info))] 4 | pub fn yas_window_info(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 5 | window_info::yas_window_info(input) 6 | } 7 | -------------------------------------------------------------------------------- /yas-wutheringwaves/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(coroutine_trait, coroutines)] 2 | #![feature(stmt_expr_attributes)] 3 | 4 | #[allow(unused_imports)] 5 | 6 | pub mod echo; 7 | pub mod scanner_controller; 8 | pub mod scanner; 9 | pub mod application; 10 | pub mod export; 11 | -------------------------------------------------------------------------------- /yas-genshin/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(coroutines, coroutine_trait)] 2 | #![feature(fn_traits)] 3 | #![feature(stmt_expr_attributes)] 4 | 5 | pub mod scanner_controller; 6 | pub mod export; 7 | pub mod scanner; 8 | pub mod artifact; 9 | pub mod character; 10 | pub mod application; 11 | -------------------------------------------------------------------------------- /yas/src/positioning/shape.rs: -------------------------------------------------------------------------------- 1 | pub struct Shape3D { 2 | pub x: T, 3 | pub y: T, 4 | pub z: T, 5 | } 6 | 7 | impl Shape3D { 8 | pub fn new(x: T, y: T, z: T) -> Shape3D { 9 | Shape3D { 10 | x, y, z 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /yas/src/game_info/mod.rs: -------------------------------------------------------------------------------- 1 | mod game_info; 2 | mod os; 3 | mod game_info_builder; 4 | mod ui; 5 | mod resolution_family; 6 | 7 | pub use game_info_builder::GameInfoBuilder; 8 | pub use ui::{UI, Platform}; 9 | pub use resolution_family::ResolutionFamily; 10 | pub use game_info::GameInfo; 11 | -------------------------------------------------------------------------------- /yas-genshin/src/scanner/artifact_scanner/message_items.rs: -------------------------------------------------------------------------------- 1 | use image::RgbImage; 2 | 3 | /// this is constructed by the capturing thread, and sent to the worker thread 4 | pub struct SendItem { 5 | pub panel_image: RgbImage, 6 | pub star: usize, 7 | pub list_image: Option, 8 | } 9 | -------------------------------------------------------------------------------- /yas/src/export/mod.rs: -------------------------------------------------------------------------------- 1 | pub use asset_emitter::AssetEmitter; 2 | pub use export_item::{ExportItem, StatisticItem}; 3 | pub use export_statistics::ExportStatistics; 4 | pub use exporter::ExportAssets; 5 | 6 | mod exporter; 7 | mod export_item; 8 | mod export_statistics; 9 | mod asset_emitter; 10 | 11 | -------------------------------------------------------------------------------- /yas-wutheringwaves/src/scanner/echo_scanner/mod.rs: -------------------------------------------------------------------------------- 1 | pub use echo_scanner_config::WWEchoScannerConfig; 2 | pub use echo_scanner::WWEchoScanner; 3 | 4 | mod message_item; 5 | mod echo_scanner_config; 6 | mod echo_scanner_window_info; 7 | mod scan_result; 8 | mod echo_scanner_worker; 9 | mod echo_scanner; 10 | -------------------------------------------------------------------------------- /yas-genshin/src/export/artifact/mod.rs: -------------------------------------------------------------------------------- 1 | pub use config::ExportArtifactConfig; 2 | pub use export_format::GenshinArtifactExportFormat; 3 | pub use exporter::GenshinArtifactExporter; 4 | 5 | mod good; 6 | mod mingyu_lab; 7 | mod mona_uranai; 8 | mod exporter; 9 | mod export_format; 10 | mod config; 11 | mod csv; 12 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = [ 4 | "yas", 5 | "yas-genshin", 6 | "yas-starrail", 7 | "yas-derive", 8 | "yas-application", 9 | "yas-wutheringwaves" 10 | , "yas-derive-wuthering-waves"] 11 | 12 | [profile.release] 13 | lto = true 14 | panic = "abort" 15 | strip = true 16 | -------------------------------------------------------------------------------- /yas-wutheringwaves/src/echo/echo.rs: -------------------------------------------------------------------------------- 1 | use crate::echo::{WWEchoName, WWStat}; 2 | 3 | pub struct WWEcho { 4 | pub name: WWEchoName, 5 | pub main_stat1: WWStat, 6 | pub main_stat2: WWStat, 7 | pub sub_stats: Vec, 8 | pub level: usize, 9 | pub star: usize, 10 | pub lock: bool, 11 | } 12 | -------------------------------------------------------------------------------- /yas-wutheringwaves/src/scanner_controller/repository/mod.rs: -------------------------------------------------------------------------------- 1 | pub use config::WWRepositoryLayoutConfig; 2 | pub use repository_layout_window_info::WWRepositoryLayoutWindowinfo; 3 | pub use scan_logic::{ReturnResult, WWRepositoryLayoutScanController}; 4 | 5 | mod repository_layout_window_info; 6 | mod config; 7 | mod scan_logic; 8 | -------------------------------------------------------------------------------- /yas/src/game_info/os/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(windows)] 2 | mod winodws; 3 | #[cfg(windows)] 4 | pub use winodws::*; 5 | 6 | #[cfg(target_os = "macos")] 7 | mod macos; 8 | #[cfg(target_os = "macos")] 9 | pub use macos::*; 10 | 11 | #[cfg(target_os = "linux")] 12 | mod linux; 13 | #[cfg(target_os = "linux")] 14 | pub use linux::*; 15 | -------------------------------------------------------------------------------- /yas-wutheringwaves/src/export/echo/export_format.rs: -------------------------------------------------------------------------------- 1 | use clap::ValueEnum; 2 | 3 | #[derive(ValueEnum, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] 4 | pub enum WWEchoExportFormat { 5 | Hsi 6 | } 7 | 8 | impl Default for WWEchoExportFormat { 9 | fn default() -> Self { 10 | Self::Hsi 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /yas-starrail/src/export/relic/export_format.rs: -------------------------------------------------------------------------------- 1 | use clap::ValueEnum; 2 | 3 | #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)] 4 | pub enum StarRailRelicExportFormat { 5 | March7th, 6 | HSR, 7 | } 8 | 9 | impl Default for StarRailRelicExportFormat { 10 | fn default() -> Self { 11 | Self::HSR 12 | } 13 | } -------------------------------------------------------------------------------- /yas/src/window_info/mod.rs: -------------------------------------------------------------------------------- 1 | mod window_info_repository; 2 | mod window_info_type; 3 | mod load_window_info; 4 | mod from_window_info_repository; 5 | 6 | pub use from_window_info_repository::FromWindowInfoRepository; 7 | pub use window_info_repository::WindowInfoRepository; 8 | pub use window_info_type::WindowInfoType; 9 | pub use load_window_info::load_window_info_repo; -------------------------------------------------------------------------------- /yas-derive-wuthering-waves/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "yas-derive-wuthering-waves" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | proc-macro = true 8 | 9 | [dependencies] 10 | proc-macro2 = "1.0" 11 | syn = { version = "2.0", features = ["parsing"] } 12 | quote = "1.0" 13 | serde = { version = "1", features = ["derive"] } 14 | serde_json = "1" -------------------------------------------------------------------------------- /yas/src/ocr/mod.rs: -------------------------------------------------------------------------------- 1 | mod traits; 2 | mod yas_model; 3 | mod paddle_paddle_model; 4 | 5 | pub use yas_model::yas_ocr_model::YasOCRModel; 6 | pub use yas_model::yas_ocr_model::yas_ocr_model; 7 | pub use traits::ImageToText; 8 | pub use paddle_paddle_model::PPOCRModel; 9 | pub use paddle_paddle_model::PPOCRChV4RecInfer; 10 | pub use paddle_paddle_model::ppocr_model; 11 | -------------------------------------------------------------------------------- /yas/src/ocr/traits.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use anyhow::Result; 4 | 5 | pub trait ImageToText { 6 | fn image_to_text(&self, image: &ImageType, is_preprocessed: bool) -> Result; 7 | 8 | fn get_average_inference_time(&self) -> Option; 9 | } 10 | 11 | // pub trait ImageTextDetection { 12 | // 13 | // } 14 | -------------------------------------------------------------------------------- /yas/src/game_info/game_info.rs: -------------------------------------------------------------------------------- 1 | use crate::game_info::{ResolutionFamily, UI}; 2 | use crate::game_info::ui::Platform; 3 | use crate::positioning::Rect; 4 | 5 | #[derive(Clone, Debug)] 6 | pub struct GameInfo { 7 | pub window: Rect, 8 | pub resolution_family: ResolutionFamily, 9 | pub is_cloud: bool, 10 | pub ui: UI, 11 | pub platform: Platform, 12 | } 13 | -------------------------------------------------------------------------------- /yas-genshin/src/scanner/artifact_scanner/scan_result.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Hash, Clone, PartialEq, Eq)] 2 | pub struct GenshinArtifactScanResult { 3 | pub name: String, 4 | pub main_stat_name: String, 5 | pub main_stat_value: String, 6 | pub sub_stat: [String; 4], 7 | pub equip: String, 8 | pub level: i32, 9 | pub star: i32, 10 | pub lock: bool, 11 | } 12 | -------------------------------------------------------------------------------- /assets/manifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /yas-genshin/src/scanner_controller/repository_layout/mod.rs: -------------------------------------------------------------------------------- 1 | pub use config::GenshinRepositoryScannerLogicConfig; 2 | pub use controller::GenshinRepositoryScanController; 3 | pub use controller::ReturnResult; 4 | pub use scroll_result::ScrollResult; 5 | pub use window_info::GenshinRepositoryScanControllerWindowInfo; 6 | 7 | mod config; 8 | mod controller; 9 | 10 | mod scroll_result; 11 | mod window_info; 12 | -------------------------------------------------------------------------------- /yas-starrail/src/scanner_controller/repository_layout/mod.rs: -------------------------------------------------------------------------------- 1 | pub use scan_logic::StarRailRepositoryScanController; 2 | pub use scan_logic::ReturnResult; 3 | pub use config::StarRailRepositoryScannerLogicConfig; 4 | pub use window_info::StarRailRepositoryScanControllerWindowInfo; 5 | // pub use scroll_result::ScrollResult; 6 | 7 | mod config; 8 | mod scan_logic; 9 | mod window_info; 10 | mod scroll_result; 11 | -------------------------------------------------------------------------------- /yas/src/capture/generic_capturer.rs: -------------------------------------------------------------------------------- 1 | #[cfg(target_os = "windows")] 2 | use crate::capture::WindowsCapturer; 3 | #[cfg(target_os = "windows")] 4 | pub type GenericCapturer = WindowsCapturer; 5 | 6 | #[cfg(target_os = "linux")] 7 | use crate::capture::LibwayshotCapturer; 8 | #[cfg(target_os = "linux")] 9 | pub type GenericCapturer = LibwayshotCapturer; 10 | 11 | // #[cfg(target_os = "macos")] 12 | // pub type GenericCapturer = -------------------------------------------------------------------------------- /yas-derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "yas_derive" 3 | version = "0.1.0" 4 | edition = "2021" 5 | description = "Yas proc macro" 6 | repository = "https://github.com/wormtql/yas" 7 | authors = ["wormtql <584130248@qq.com>"] 8 | license = "GPL-2.0-or-later" 9 | 10 | [lib] 11 | proc-macro = true 12 | 13 | [dependencies] 14 | proc-macro2 = "1.0" 15 | syn = { version = "2.0", features = ["parsing"] } 16 | quote = "1.0" 17 | -------------------------------------------------------------------------------- /yas-genshin/src/artifact/zh_cn.rs: -------------------------------------------------------------------------------- 1 | use crate::artifact::ArtifactSlot; 2 | 3 | impl ArtifactSlot { 4 | pub fn to_zh_cn(&self) -> &'static str { 5 | match *self { 6 | ArtifactSlot::Flower => "生之花", 7 | ArtifactSlot::Feather => "死之羽", 8 | ArtifactSlot::Sand => "时之沙", 9 | ArtifactSlot::Goblet => "空之杯", 10 | ArtifactSlot::Head => "理之冠", 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /yas-genshin/src/export/artifact/export_format.rs: -------------------------------------------------------------------------------- 1 | use clap::ValueEnum; 2 | 3 | #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)] 4 | pub enum GenshinArtifactExportFormat { 5 | Mona, 6 | MingyuLab, 7 | Good, 8 | CSV, 9 | /// Export all formats 10 | All, 11 | } 12 | 13 | impl Default for GenshinArtifactExportFormat { 14 | fn default() -> Self { 15 | Self::Mona 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /yas/src/window_info/from_window_info_repository.rs: -------------------------------------------------------------------------------- 1 | use crate::game_info::{Platform, UI}; 2 | use crate::positioning::Size; 3 | use crate::window_info::WindowInfoRepository; 4 | 5 | pub trait FromWindowInfoRepository: Sized { 6 | fn from_window_info_repository( 7 | window_size: Size, 8 | ui: UI, 9 | platform: Platform, 10 | repo: &WindowInfoRepository 11 | ) -> anyhow::Result; 12 | } 13 | -------------------------------------------------------------------------------- /yas-starrail/src/scanner/relic_scanner/scan_result.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Hash, Clone, PartialEq, Eq)] 2 | pub struct StarRailRelicScanResult { 3 | pub name: String, 4 | pub main_stat_name: String, 5 | pub main_stat_value: String, 6 | pub sub_stat_name: [String; 4], 7 | pub sub_stat_value: [String; 4], 8 | pub equip: String, 9 | pub level: i32, 10 | pub star: i32, 11 | pub lock: bool, 12 | pub discard: bool, 13 | } 14 | -------------------------------------------------------------------------------- /yas-starrail/src/scanner/relic_scanner/mod.rs: -------------------------------------------------------------------------------- 1 | pub use relic_scanner::{StarRailRelicScanner}; 2 | pub use relic_scanner_config::StarRailRelicScannerConfig; 3 | pub use scan_result::StarRailRelicScanResult; 4 | // pub use relic_scanner_window_info::RelicScannerWindowInfo; 5 | 6 | mod relic_scanner; 7 | mod relic_scanner_config; 8 | mod relic_scanner_window_info; 9 | mod scan_result; 10 | mod relic_scanner_worker; 11 | mod message_items; 12 | mod match_colors; 13 | -------------------------------------------------------------------------------- /yas-genshin/src/scanner/artifact_scanner/mod.rs: -------------------------------------------------------------------------------- 1 | pub use artifact_scanner::GenshinArtifactScanner; 2 | pub use artifact_scanner_config::GenshinArtifactScannerConfig; 3 | pub use artifact_scanner_window_info::ArtifactScannerWindowInfo; 4 | pub use scan_result::GenshinArtifactScanResult; 5 | 6 | mod artifact_scanner; 7 | mod artifact_scanner_config; 8 | mod scan_result; 9 | mod artifact_scanner_worker; 10 | mod artifact_scanner_window_info; 11 | mod message_items; 12 | -------------------------------------------------------------------------------- /yas-wutheringwaves/src/scanner/echo_scanner/scan_result.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Hash, Clone, PartialEq, Eq)] 2 | pub struct WWEchoScanResult { 3 | pub name: String, 4 | pub main_stat1_name: String, 5 | pub main_stat1_value: String, 6 | pub main_stat2_name: String, 7 | pub main_stat2_value: String, 8 | pub sub_stat_names: [String; 5], 9 | pub sub_stat_values: [String; 5], 10 | // pub equip: String, 11 | pub level: usize, 12 | pub star: usize, 13 | } 14 | -------------------------------------------------------------------------------- /yas/src/system_control/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(target_os = "windows")] 2 | pub mod windows; 3 | #[cfg(target_os = "macos")] 4 | pub mod macos; 5 | #[cfg(target_os = "linux")] 6 | pub mod linux; 7 | 8 | #[cfg(target_os = "windows")] 9 | pub use windows::windows_control::WindowsSystemControl as SystemControl; 10 | #[cfg(target_os = "macos")] 11 | pub use macos::macos_control::MacOSControl as SystemControl; 12 | #[cfg(target_os = "linux")] 13 | pub use linux::linux_control::LinuxControl as SystemControl; 14 | -------------------------------------------------------------------------------- /yas/src/common/image_ext.rs: -------------------------------------------------------------------------------- 1 | use image::{GrayImage, ImageBuffer, Luma}; 2 | 3 | pub trait ToF32GrayImage { 4 | fn to_f32_gray_image(&self) -> ImageBuffer, Vec>; 5 | } 6 | 7 | impl ToF32GrayImage for GrayImage { 8 | fn to_f32_gray_image(&self) -> ImageBuffer, Vec> { 9 | ImageBuffer::from_fn(self.width(), self.height(), |x, y| { 10 | let pv = self.get_pixel(x, y)[0]; 11 | Luma([pv as f32 / 255.0_f32]) 12 | }) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /yas/src/utils/misc.rs: -------------------------------------------------------------------------------- 1 | use std::io::Read; 2 | 3 | /// Calculate the square of the distance (not the distance itself) 4 | pub fn color_distance(c1: &image::Rgb, c2: &image::Rgb) -> usize { 5 | let x = c1.0[0] as i32 - c2.0[0] as i32; 6 | let y = c1.0[1] as i32 - c2.0[1] as i32; 7 | let z = c1.0[2] as i32 - c2.0[2] as i32; 8 | (x * x + y * y + z * z) as usize 9 | } 10 | 11 | pub fn press_any_key_to_continue() { 12 | let _ = std::io::stdin().read(&mut [0u8]).unwrap(); 13 | } 14 | -------------------------------------------------------------------------------- /yas-wutheringwaves/src/export/echo/config.rs: -------------------------------------------------------------------------------- 1 | use crate::export::echo::export_format::WWEchoExportFormat; 2 | 3 | #[derive(clap::Args)] 4 | pub struct WWExportEchoConfig { 5 | #[arg(id = "format", long = "format", short = 'f', default_value_t = WWEchoExportFormat::Hsi, help = "输出格式")] 6 | #[arg(value_enum)] 7 | pub format: WWEchoExportFormat, 8 | 9 | #[arg(id = "output-dir", long = "output-dir", short, default_value_t = String::from("."), help = "输出目录")] 10 | pub output_dir: String, 11 | } 12 | -------------------------------------------------------------------------------- /yas/src/positioning/scalable.rs: -------------------------------------------------------------------------------- 1 | pub trait Scalable { 2 | fn scale(&self, factor: f64) -> Self; 3 | } 4 | 5 | impl Scalable for f64 { 6 | fn scale(&self, factor: f64) -> Self { 7 | *self * factor 8 | } 9 | } 10 | 11 | macro impl_int_scale($t:ty) { 12 | impl Scalable for $t { 13 | fn scale(&self, factor: f64) -> Self { 14 | ((*self as f64) * factor) as $t 15 | } 16 | } 17 | } 18 | 19 | impl_int_scale!(i32); 20 | impl_int_scale!(usize); 21 | impl_int_scale!(u32); 22 | -------------------------------------------------------------------------------- /yas-genshin/src/export/artifact/config.rs: -------------------------------------------------------------------------------- 1 | use crate::export::artifact::GenshinArtifactExportFormat; 2 | 3 | #[derive(clap::Args)] 4 | pub struct ExportArtifactConfig { 5 | #[arg(id = "format", long = "format", short = 'f', default_value_t = GenshinArtifactExportFormat::Mona, help = "输出格式")] 6 | #[arg(value_enum)] 7 | pub format: GenshinArtifactExportFormat, 8 | 9 | #[arg(id = "output-dir", long = "output-dir", short, default_value_t = String::from("."), help = "输出目录")] 10 | pub output_dir: String, 11 | } 12 | -------------------------------------------------------------------------------- /yas-starrail/src/export/relic/config.rs: -------------------------------------------------------------------------------- 1 | use crate::export::relic::export_format::StarRailRelicExportFormat; 2 | 3 | #[derive(clap::Args)] 4 | pub struct ExportRelicConfig { 5 | #[arg(id = "format", long = "format", short = 'f', default_value_t = StarRailRelicExportFormat::HSR, help = "输出格式")] 6 | #[arg(value_enum)] 7 | pub format: StarRailRelicExportFormat, 8 | 9 | #[arg(id = "output-dir", long = "output-dir", short, default_value_t = String::from("."), help = "输出目录")] 10 | pub output_dir: String, 11 | } 12 | -------------------------------------------------------------------------------- /yas/src/common/color.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, PartialEq, Default, Clone, Copy)] 2 | pub struct Color(pub u8, pub u8, pub u8); 3 | 4 | impl Color { 5 | pub fn distance(&self, other: &Color) -> u32 { 6 | let r = self.0 as i32 - other.0 as i32; 7 | let g = self.1 as i32 - other.1 as i32; 8 | let b = self.2 as i32 - other.2 as i32; 9 | 10 | let dis = r * r + g * g + b * b; 11 | dis as u32 12 | } 13 | 14 | pub fn new(r: u8, g: u8, b: u8) -> Color { 15 | Color(r, g, b) 16 | } 17 | } -------------------------------------------------------------------------------- /yas-genshin/src/bin/playground.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use image::RgbImage; 3 | use yas::ocr::{ImageToText, PPOCRChV4RecInfer}; 4 | use image::io::Reader as ImageReader; 5 | 6 | fn main() -> Result<()> { 7 | let model: Box> = Box::new(PPOCRChV4RecInfer::new()?); 8 | let image = ImageReader::open(r"E:\rust\yas\item_count.png")?.decode()?; 9 | let rgb_image = image.to_rgb8(); 10 | let result = model.image_to_text(&rgb_image, false)?; 11 | println!("{}", result); 12 | 13 | Ok(()) 14 | } 15 | -------------------------------------------------------------------------------- /yas/src/game_info/os/macos.rs: -------------------------------------------------------------------------------- 1 | use crate::{common::utils::*, core::ui::Resolution}; 2 | use crate::game_info::{GameInfo, Platform}; 3 | 4 | pub fn get_game_info() -> GameInfo { 5 | let (pid, ui) = get_pid_and_ui(); 6 | 7 | let (rect, window_title) = unsafe { find_window_by_pid(pid).unwrap() }; 8 | 9 | info!("找到游戏窗口:{} (PID: {})", window_title, pid); 10 | 11 | GameInfo { 12 | window: rect, 13 | resolution_family: Resolution::new(rect.size), 14 | is_cloud: false, 15 | ui, 16 | platform: Platform::MacOS 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /yas/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(decl_macro)] 2 | #![feature(concat_idents)] 3 | #![allow(unused_imports)] 4 | 5 | #[cfg(all(feature = "ort", feature = "tract_onnx"))] 6 | compile_error!("feature \"ort\" and \"tract_onnx\" cannot be enabled at the same time"); 7 | 8 | extern crate log; 9 | extern crate lazy_static; 10 | 11 | pub mod common; 12 | pub mod export; 13 | pub mod draw_capture_region; 14 | pub mod capture; 15 | pub mod utils; 16 | pub mod game_info; 17 | pub mod window_info; 18 | pub mod system_control; 19 | pub mod ocr; 20 | pub mod positioning; 21 | pub mod profiler; 22 | -------------------------------------------------------------------------------- /assets/app.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | com.apple.application-identifier 5 | io.yas.scanner 6 | com.apple.private.screencapture.allow 7 | 8 | com.apple.private.tcc.allow 9 | 10 | kTCCServiceScreenCapture 11 | kTCCServiceAccessibility 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /yas-application/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | if std::env::var("CARGO_CFG_TARGET_OS").unwrap().as_str() == "windows" { 3 | let mut res = winres::WindowsResource::new(); 4 | // https://github.com/mxre/winres/pull/24 5 | // https://github.com/mxre/winres/issues/42 6 | #[cfg(not(target_os = "windows"))] 7 | if std::env::var("CARGO_CFG_TARGET_ENV").unwrap().as_str() == "gnu" { 8 | res.set_ar_path("x86_64-w64-mingw32-ar"); 9 | res.set_windres_path("x86_64-w64-mingw32-windres"); 10 | } 11 | res.set_manifest_file("../assets/manifest.xml"); 12 | res.compile().unwrap(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /yas/src/game_info/ui.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | #[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize)] 4 | pub enum UI { 5 | Desktop, 6 | Mobile, 7 | } 8 | 9 | #[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize)] 10 | pub enum Platform { 11 | Windows, 12 | MacOS, 13 | Linux, 14 | } 15 | 16 | impl Platform { 17 | pub fn current() -> Self { 18 | #[cfg(target_os = "windows")] 19 | return Self::Windows; 20 | 21 | #[cfg(target_os = "macos")] 22 | return Self::MacOS; 23 | 24 | #[cfg(target_os = "linux")] 25 | return Self::Linux; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /yas-application/src/bin/yas_relic.rs: -------------------------------------------------------------------------------- 1 | use yas::utils::press_any_key_to_continue; 2 | use yas_starrail::application::RelicScannerApplication; 3 | use log::error; 4 | 5 | pub fn main() { 6 | env_logger::Builder::new() 7 | .filter_level(log::LevelFilter::Info) 8 | .init(); 9 | let matches = RelicScannerApplication::build_command().get_matches(); 10 | 11 | let application = RelicScannerApplication::new(matches); 12 | match application.run() { 13 | Err(e) => { 14 | error!("error: {}", e); 15 | press_any_key_to_continue(); 16 | }, 17 | _ => { 18 | press_any_key_to_continue(); 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /yas-application/src/bin/yas_ww_echo.rs: -------------------------------------------------------------------------------- 1 | use yas::utils::press_any_key_to_continue; 2 | use log::error; 3 | use yas_wutheringwaves::application::WWEchoScannerApplication; 4 | 5 | pub fn main() { 6 | env_logger::Builder::new() 7 | .filter_level(log::LevelFilter::Info) 8 | .init(); 9 | let matches = WWEchoScannerApplication::build_command().get_matches(); 10 | 11 | let application = WWEchoScannerApplication::new(matches); 12 | match application.run() { 13 | Err(e) => { 14 | error!("error: {}", e); 15 | press_any_key_to_continue(); 16 | }, 17 | _ => { 18 | press_any_key_to_continue(); 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /yas-wutheringwaves/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "yas-wutheringwaves" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | yas-derive-wuthering-waves = { path = "../yas-derive-wuthering-waves" } 8 | yas_derive = { path = "../yas-derive" } 9 | yas = { path = "../yas", package="yas_core", features = ["ort"] } 10 | anyhow = "1.0" 11 | log = "0.4" 12 | clap = { version = "4.4", features = ["derive"] } 13 | image = "0.24" 14 | serde_json = "1.0" 15 | edit-distance = "2.1" 16 | regex = "1.10" 17 | strum = "0.26" 18 | strum_macros = "0.26" 19 | lazy_static = "1.4" 20 | serde = { version = "1.0", features = ["derive"] } 21 | env_logger = "0.11" 22 | nanoid = "0.4" 23 | rayon = "1.10.0" 24 | -------------------------------------------------------------------------------- /yas-application/src/bin/yas_artifact.rs: -------------------------------------------------------------------------------- 1 | use yas::utils::press_any_key_to_continue; 2 | use yas_genshin::application::ArtifactScannerApplication; 3 | use log::error; 4 | 5 | pub fn main() { 6 | env_logger::Builder::new() 7 | .filter_level(log::LevelFilter::Info) 8 | .init(); 9 | 10 | let command = ArtifactScannerApplication::build_command(); 11 | let matches = command.get_matches(); 12 | 13 | let application = ArtifactScannerApplication::new(matches); 14 | match application.run() { 15 | Err(e) => { 16 | error!("error: {}", e); 17 | press_any_key_to_continue(); 18 | }, 19 | _ => { 20 | press_any_key_to_continue(); 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /yas/src/system_control/linux/linux_control.rs: -------------------------------------------------------------------------------- 1 | use enigo::{Enigo, MouseButton, MouseControllable}; 2 | 3 | pub struct LinuxControl { 4 | enigo: Enigo, 5 | } 6 | 7 | impl LinuxControl { 8 | pub fn new() -> LinuxControl { 9 | LinuxControl { 10 | enigo: Enigo::new(), 11 | } 12 | } 13 | 14 | pub fn mouse_move_to(&mut self, x: i32, y: i32) -> anyhow::Result<()> { 15 | self.enigo.mouse_move_to(x, y); 16 | 17 | anyhow::Ok(()) 18 | } 19 | 20 | pub fn mouse_click(&mut self) -> anyhow::Result<()> { 21 | self.enigo.mouse_click(MouseButton::Left); 22 | 23 | anyhow::Ok(()) 24 | } 25 | 26 | pub fn mouse_scroll(&mut self, amount: i32, _try_find: bool) -> anyhow::Result<()> { 27 | self.enigo.mouse_scroll_y(amount); 28 | 29 | anyhow::Ok(()) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /yas/src/capture/mod.rs: -------------------------------------------------------------------------------- 1 | pub use stream_capturer::StreamingCapturer; 2 | pub use capturer::Capturer; 3 | pub use generic_capturer::GenericCapturer; 4 | 5 | mod capturer; 6 | mod generic_capturer; 7 | mod stream_capturer; 8 | 9 | // windows 10 | 11 | #[cfg(target_os = "windows")] 12 | mod screenshots_capturer; 13 | #[cfg(target_os = "windows")] 14 | mod winapi_capturer; 15 | #[cfg(target_os = "windows")] 16 | mod windows_capturer; 17 | 18 | #[cfg(target_os = "windows")] 19 | pub use screenshots_capturer::ScreenshotsCapturer; 20 | #[cfg(target_os = "windows")] 21 | pub use winapi_capturer::WinapiCapturer; 22 | #[cfg(target_os = "windows")] 23 | pub use windows_capturer::WindowsCapturer; 24 | 25 | // linux 26 | #[cfg(target_os = "linux")] 27 | mod libwayshot_capturer; 28 | 29 | #[cfg(target_os = "linux")] 30 | pub use libwayshot_capturer::LibwayshotCapturer; 31 | -------------------------------------------------------------------------------- /yas/src/system_control/windows/windows_control.rs: -------------------------------------------------------------------------------- 1 | use enigo::{Enigo, MouseControllable, MouseButton}; 2 | 3 | pub struct WindowsSystemControl { 4 | enigo: Enigo, 5 | } 6 | 7 | impl WindowsSystemControl { 8 | pub fn new() -> WindowsSystemControl { 9 | WindowsSystemControl { enigo: Enigo::new() } 10 | } 11 | 12 | pub fn mouse_move_to(&mut self, x: i32, y: i32) -> anyhow::Result<()> { 13 | self.enigo.mouse_move_to(x, y); 14 | 15 | anyhow::Ok(()) 16 | } 17 | 18 | pub fn mouse_click(&mut self) -> anyhow::Result<()> { 19 | self.enigo.mouse_click(MouseButton::Left); 20 | 21 | anyhow::Ok(()) 22 | } 23 | 24 | pub fn mouse_scroll(&mut self, amount: i32, _try_find: bool) -> anyhow::Result<()> { 25 | self.enigo.mouse_scroll_y(amount); 26 | 27 | anyhow::Ok(()) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /yas/src/export/export_item.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | #[derive(Clone, Debug)] 4 | pub struct ExportItem { 5 | // bytes 6 | pub contents: Vec, 7 | pub filename: PathBuf, 8 | pub name: Option, 9 | pub description: Option, 10 | } 11 | 12 | #[derive(Clone, Debug)] 13 | pub struct StatisticItem { 14 | pub size_in_bytes: usize, 15 | pub filename: PathBuf, 16 | pub name: Option, 17 | pub description: Option, 18 | } 19 | 20 | impl StatisticItem { 21 | pub fn from_export_item(export_item: &ExportItem) -> Self { 22 | StatisticItem { 23 | size_in_bytes: export_item.contents.len(), 24 | filename: export_item.filename.clone(), 25 | name: export_item.name.clone(), 26 | description: export_item.description.clone() 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /yas-starrail/src/scanner/relic_scanner/relic_scanner_config.rs: -------------------------------------------------------------------------------- 1 | #[derive(Clone, clap::Args)] 2 | pub struct StarRailRelicScannerConfig { 3 | /// Items with stars less than this will be ignored 4 | #[arg(id = "min-star", long = "min-star", help = "最小星级", value_name = "MIN_STAR", default_value_t = 4)] 5 | pub min_star: i32, 6 | 7 | /// Items with level less than this will be ignored 8 | #[arg(id = "min-level", long = "min-level", help = "最小等级", value_name = "MIN_LEVEL", default_value_t = 0)] 9 | pub min_level: i32, 10 | 11 | /// Ignore duplicated items 12 | #[arg(id = "ignore-dup", long = "ignore-dup", help = "忽略重复物品")] 13 | pub ignore_dup: bool, 14 | 15 | #[arg(id = "verbose", long, help = "显示详细信息")] 16 | pub verbose: bool, 17 | 18 | #[arg(id = "number", long, help = "指定遗器数量", value_name = "NUMBER", default_value_t = -1)] 19 | pub number: i32, 20 | } 21 | -------------------------------------------------------------------------------- /yas-wutheringwaves/src/scanner/echo_scanner/echo_scanner_config.rs: -------------------------------------------------------------------------------- 1 | use clap::Args; 2 | 3 | #[derive(clap::Args, Clone)] 4 | pub struct WWEchoScannerConfig { 5 | /// Items with stars less than this will be ignored 6 | #[arg(id = "min-star", long = "min-star", help = "最小星级", value_name = "MIN_STAR", default_value_t = 4)] 7 | pub min_star: i32, 8 | 9 | /// Items with level less than this will be ignored 10 | #[arg(id = "min-level", long = "min-level", help = "最小等级", value_name = "MIN_LEVEL", default_value_t = 0)] 11 | pub min_level: i32, 12 | 13 | /// Ignore duplicated items 14 | #[arg(id = "ignore-dup", long = "ignore-dup", help = "忽略重复物品")] 15 | pub ignore_dup: bool, 16 | 17 | #[arg(id = "verbose", long, help = "显示详细信息")] 18 | pub verbose: bool, 19 | 20 | #[arg(id = "number", long, help = "指定声骸数量", value_name = "NUMBER")] 21 | pub number: Option, 22 | } 23 | -------------------------------------------------------------------------------- /yas-starrail/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "yas_scanner_starrail" 3 | version = "0.1.14" 4 | edition = "2021" 5 | description = "Honkai: Star Rail item scanner" 6 | repository = "https://github.com/wormtql/yas" 7 | keywords = ["HonkaiStarRail", "relic", "scanner", "ocr"] 8 | license = "GPL-2.0-or-later" 9 | 10 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 11 | 12 | [dependencies] 13 | yas = { path = "../yas", package="yas_core", features = ["ort"] } 14 | yas_derive = { path = "../yas-derive", package = "yas_derive" } 15 | anyhow = "1.0" 16 | log = "0.4" 17 | clap = { version = "4.4", features = ["derive"] } 18 | image = "0.24" 19 | serde_json = "1.0" 20 | edit-distance = "2.1" 21 | regex = "1.5" 22 | strum = "0.26" 23 | strum_macros = "0.26" 24 | lazy_static = "1.4" 25 | serde = { version = "1.0", features = ["derive"] } 26 | env_logger = "0.11" 27 | nanoid = "0.4" 28 | -------------------------------------------------------------------------------- /yas-wutheringwaves/src/scanner_controller/repository/repository_layout_window_info.rs: -------------------------------------------------------------------------------- 1 | use yas::positioning::{Pos, Rect, Size}; 2 | use yas_derive::YasWindowInfo; 3 | 4 | #[derive(YasWindowInfo)] 5 | pub struct WWRepositoryLayoutWindowinfo { 6 | #[window_info(rename = "ww_repository_panel_rect")] 7 | pub panel_rect: Rect, 8 | 9 | #[window_info(rename = "ww_repository_flag_pos")] 10 | pub flag_pos: Pos, 11 | 12 | #[window_info(rename = "ww_repository_item_gap_size")] 13 | pub item_gap_size: Size, 14 | 15 | #[window_info(rename = "ww_repository_item_size")] 16 | pub item_size: Size, 17 | 18 | #[window_info(rename = "ww_repository_scan_margin_pos")] 19 | pub scan_margin_pos: Pos, 20 | 21 | #[window_info(rename = "ww_repository_pool_rect")] 22 | pub pool_rect: Rect, 23 | 24 | pub ww_repository_item_row: i32, 25 | pub ww_repository_item_col: i32, 26 | } 27 | -------------------------------------------------------------------------------- /yas/src/common/cancel.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{atomic::AtomicBool, Arc}; 2 | 3 | #[derive(Clone)] 4 | pub struct CancellationToken { 5 | cancelled: Arc, 6 | } 7 | 8 | impl std::panic::UnwindSafe for CancellationToken {} 9 | impl std::panic::RefUnwindSafe for CancellationToken {} 10 | 11 | impl CancellationToken { 12 | /// Creates a new CancellationToken in the non-cancelled state. 13 | pub fn new() -> CancellationToken { 14 | CancellationToken { 15 | cancelled: Arc::new(AtomicBool::new(false)), 16 | } 17 | } 18 | 19 | pub fn cancel(&self) { 20 | self.cancelled 21 | .store(true, std::sync::atomic::Ordering::SeqCst); 22 | } 23 | 24 | pub fn cancelled(&self) -> bool { 25 | self.cancelled.load(std::sync::atomic::Ordering::SeqCst) 26 | } 27 | } 28 | 29 | impl Default for CancellationToken { 30 | fn default() -> Self { 31 | Self::new() 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /yas/src/capture/capturer.rs: -------------------------------------------------------------------------------- 1 | use crate::positioning::{Pos, Rect}; 2 | use anyhow::Result; 3 | 4 | pub trait Capturer { 5 | // it's necessary to use signed int, because capture region may be out of the screen 6 | fn capture_rect(&self, rect: Rect) -> Result; 7 | 8 | fn capture_color(&self, pos: Pos) -> Result { 9 | let image = self.capture_rect(Rect { 10 | left: pos.x, 11 | top: pos.y, 12 | width: 1, 13 | height: 1, 14 | })?; 15 | Ok(image.get_pixel(0, 0)) 16 | } 17 | 18 | fn capture_relative_to(&self, rect: Rect, relative_to: Pos) -> Result { 19 | let new_rect = Rect { 20 | left: rect.left + relative_to.x, 21 | top: rect.top + relative_to.y, 22 | width: rect.width, 23 | height: rect.height 24 | }; 25 | self.capture_rect(new_rect) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /yas-starrail/src/scanner_controller/repository_layout/window_info.rs: -------------------------------------------------------------------------------- 1 | use yas::positioning::{Pos, Rect, Size}; 2 | use yas_derive::YasWindowInfo; 3 | 4 | #[derive(YasWindowInfo)] 5 | pub struct StarRailRepositoryScanControllerWindowInfo { 6 | #[window_info(rename = "starrail_repository_panel_rect")] 7 | pub panel_rect: Rect, 8 | 9 | #[window_info(rename = "starrail_repository_flag_rect")] 10 | pub flag_rect: Rect, 11 | 12 | #[window_info(rename = "starrail_repository_item_gap_size")] 13 | pub item_gap_size: Size, 14 | 15 | #[window_info(rename = "starrail_repository_item_size")] 16 | pub item_size: Size, 17 | 18 | #[window_info(rename = "starrail_repository_scan_margin_pos")] 19 | pub scan_margin_pos: Pos, 20 | 21 | #[window_info(rename = "starrail_repository_pool_rect")] 22 | pub pool_rect: Rect, 23 | 24 | pub starrail_repository_item_row: i32, 25 | pub starrail_repository_item_col: i32, 26 | } 27 | -------------------------------------------------------------------------------- /yas-genshin/src/scanner/artifact_scanner/artifact_scanner_config.rs: -------------------------------------------------------------------------------- 1 | #[derive(Clone, clap::Args)] 2 | pub struct GenshinArtifactScannerConfig { 3 | /// Items with stars less than this will be ignored 4 | #[arg(id = "min-star", long = "min-star", help = "最小星级", value_name = "MIN_STAR", default_value_t = 4)] 5 | pub min_star: i32, 6 | 7 | /// Items with level less than this will be ignored 8 | #[arg(id = "min-level", long = "min-level", help = "最小等级", value_name = "MIN_LEVEL", default_value_t = 0)] 9 | pub min_level: i32, 10 | 11 | /// Ignore duplicated items 12 | #[arg(id = "ignore-dup", long = "ignore-dup", help = "忽略重复物品")] 13 | pub ignore_dup: bool, 14 | 15 | /// it will output very verbose messages 16 | #[arg(id = "verbose", long, help = "显示详细信息")] 17 | pub verbose: bool, 18 | 19 | /// the exact amount to scan 20 | #[arg(id = "number", long, help = "指定圣遗物数量", value_name = "NUMBER", default_value_t = -1)] 21 | pub number: i32, 22 | } 23 | -------------------------------------------------------------------------------- /yas-derive/src/window_info/nested_attributes.rs: -------------------------------------------------------------------------------- 1 | use syn::{token, Token}; 2 | 3 | #[derive(Default)] 4 | pub struct WindowInfoNestedAttributes { 5 | pub rename: Option, 6 | } 7 | 8 | impl WindowInfoNestedAttributes { 9 | pub fn from_attr(attr: &syn::Attribute) -> syn::parse::Result { 10 | let mut result: WindowInfoNestedAttributes = Default::default(); 11 | attr.parse_nested_meta( |meta| { 12 | if meta.path.is_ident("rename") { 13 | if meta.input.peek(token::Eq) { 14 | let _eq: Token![=] = meta.input.parse()?; 15 | let expr: syn::LitStr = meta.input.parse()?; 16 | result.rename = Some(expr); 17 | return Ok(()); 18 | } 19 | result.rename = None; 20 | return Ok(()); 21 | } 22 | 23 | Err(meta.error("unrecognized window_info")) 24 | })?; 25 | 26 | Ok(result) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /yas-application/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "yas-application" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | anyhow = "1.0" 8 | yas = { path = "../yas" , package = "yas_core" } 9 | yas_genshin = { path = "../yas-genshin", package = "yas_scanner_genshin" } 10 | yas_starrail = { path = "../yas-starrail", package = "yas_scanner_starrail" } 11 | yas-wutheringwaves = { path = "../yas-wutheringwaves" } 12 | clap = { version = "4.4", features = ["derive"] } 13 | log = "0.4" 14 | env_logger = "0.11" 15 | 16 | [build-dependencies] 17 | winres = "0.1" 18 | 19 | # [profile.release] 20 | # lto = true 21 | # panic = "abort" 22 | # strip = true 23 | 24 | [[bin]] 25 | name = "yas" 26 | path = "src/bin/yas.rs" 27 | # build = "build.rs" 28 | 29 | [[bin]] 30 | name = "yas_artifact" 31 | path = "src/bin/yas_artifact.rs" 32 | # build = "build.rs" 33 | 34 | [[bin]] 35 | name = "yas_relic" 36 | path = "src/bin/yas_relic.rs" 37 | 38 | [[bin]] 39 | name = "yas_ww_echo" 40 | path = "src/bin/yas_ww_echo.rs" 41 | -------------------------------------------------------------------------------- /yas-genshin/src/scanner_controller/repository_layout/window_info.rs: -------------------------------------------------------------------------------- 1 | use yas::positioning::{Pos, Rect, Size}; 2 | use yas_derive::YasWindowInfo; 3 | 4 | #[derive(Clone, YasWindowInfo)] 5 | pub struct GenshinRepositoryScanControllerWindowInfo { 6 | #[window_info(rename = "genshin_repository_panel_rect")] 7 | pub panel_rect: Rect, 8 | 9 | #[window_info(rename = "genshin_repository_flag_pos")] 10 | pub flag_pos: Pos, 11 | 12 | #[window_info(rename = "genshin_repository_item_gap_size")] 13 | pub item_gap_size: Size, 14 | 15 | #[window_info(rename = "genshin_repository_item_size")] 16 | pub item_size: Size, 17 | 18 | #[window_info(rename = "genshin_repository_scan_margin_pos")] 19 | pub scan_margin_pos: Pos, 20 | 21 | #[window_info(rename = "genshin_repository_pool_rect")] 22 | pub pool_rect: Rect, 23 | 24 | #[window_info(rename = "genshin_artifact_offset")] 25 | pub artifact_panel_offset: Size, 26 | 27 | pub genshin_repository_item_row: i32, 28 | pub genshin_repository_item_col: i32, 29 | } 30 | -------------------------------------------------------------------------------- /yas/src/capture/windows_capturer.rs: -------------------------------------------------------------------------------- 1 | use crate::capture::WinapiCapturer; 2 | use crate::capture::ScreenshotsCapturer; 3 | use crate::capture::Capturer; 4 | use crate::positioning::Rect; 5 | use image::RgbImage; 6 | use anyhow::Result; 7 | use anyhow::anyhow; 8 | 9 | pub struct WindowsCapturer { 10 | windows_capturer: WinapiCapturer, 11 | fallback_capturer: ScreenshotsCapturer, 12 | } 13 | 14 | impl WindowsCapturer { 15 | pub fn new() -> Result { 16 | Ok(Self { 17 | windows_capturer: WinapiCapturer::new(), 18 | fallback_capturer: ScreenshotsCapturer::new()?, 19 | }) 20 | } 21 | } 22 | 23 | impl Capturer for WindowsCapturer { 24 | fn capture_rect(&self, rect: Rect) -> Result { 25 | { 26 | let result = self.windows_capturer.capture_rect(rect); 27 | if result.is_ok() { 28 | return result 29 | } 30 | } 31 | 32 | { 33 | let result = self.fallback_capturer.capture_rect(rect); 34 | return result; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /yas/src/capture/screenshots_capturer.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use image::{RgbaImage, RgbImage}; 3 | use image::buffer::ConvertBuffer; 4 | use crate::capture::Capturer; 5 | use crate::positioning::Rect; 6 | 7 | pub struct ScreenshotsCapturer { 8 | screens: Vec, 9 | } 10 | 11 | impl ScreenshotsCapturer { 12 | pub fn new() -> Result { 13 | Ok(Self { 14 | screens: screenshots::Screen::all()?, 15 | }) 16 | } 17 | } 18 | 19 | impl Capturer for ScreenshotsCapturer { 20 | fn capture_rect(&self, rect: Rect) -> Result { 21 | let screen = &self.screens[0]; 22 | let capture_result = screen.capture_area( 23 | rect.left, 24 | rect.top, 25 | rect.width as u32, 26 | rect.height as u32 27 | ); 28 | capture_result 29 | } 30 | } 31 | 32 | impl Capturer for ScreenshotsCapturer { 33 | fn capture_rect(&self, rect: Rect) -> Result { 34 | let rgba_result: RgbaImage = self.capture_rect(rect)?; 35 | Ok(rgba_result.convert()) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /yas-genshin/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "yas_scanner_genshin" 3 | version = "0.1.16" 4 | edition = "2021" 5 | description = "Genshin Impact item scanner" 6 | repository = "https://github.com/wormtql/yas" 7 | keywords = ["GenshinImpact", "artifacts", "scanner", "ocr"] 8 | license = "GPL-2.0-or-later" 9 | 10 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 11 | 12 | [dependencies] 13 | yas = { path = "../yas", package="yas_core", features = ["ort"] } 14 | yas_derive = { path = "../yas-derive", package = "yas_derive" } 15 | anyhow = "1.0" 16 | log = "0.4" 17 | clap = { version = "4.4", features = ["derive", "cargo"] } 18 | image = "0.24" 19 | serde_json = "1.0" 20 | edit-distance = "2.1" 21 | regex = "1.5" 22 | strum = "0.26" 23 | strum_macros = "0.26" 24 | lazy_static = "1.4" 25 | serde = { version = "1.0", features = ["derive"] } 26 | env_logger = "0.11" 27 | serde_yaml = "0.9" 28 | csv = "1.3.0" 29 | 30 | [target.'cfg(target_os = "windows")'.dependencies] 31 | windows-capture = "1.0.65" 32 | 33 | # [profile.release] 34 | # lto = true 35 | # panic = "abort" 36 | # strip = true 37 | 38 | [[bin]] 39 | name = "yas_genshin_playground" 40 | path = "src/bin/playground.rs" 41 | -------------------------------------------------------------------------------- /yas/src/capture/libwayshot_capturer.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use image::{RgbaImage, RgbImage}; 3 | use image::buffer::ConvertBuffer; 4 | use libwayshot::{WayshotConnection, CaptureRegion}; 5 | use crate::capture::Capturer; 6 | use crate::positioning::Rect; 7 | 8 | pub struct LibwayshotCapturer { 9 | conn: WayshotConnection, 10 | } 11 | 12 | impl LibwayshotCapturer { 13 | pub fn new() -> Result { 14 | Ok(Self { 15 | conn: WayshotConnection::new()?, 16 | }) 17 | } 18 | } 19 | 20 | impl Capturer for LibwayshotCapturer { 21 | fn capture_rect(&self, rect: Rect) -> Result { 22 | let region = CaptureRegion { 23 | x_coordinate: rect.left, 24 | y_coordinate: rect.top, 25 | width: rect.width, 26 | height: rect.height, 27 | }; 28 | let capture_result = self.conn.screenshot(region, false)?; 29 | Ok(capture_result) 30 | } 31 | } 32 | 33 | impl Capturer for LibwayshotCapturer { 34 | fn capture_rect(&self, rect: Rect) -> Result { 35 | let rgba_result: RgbaImage = self.capture_rect(rect)?; 36 | Ok(rgba_result.convert()) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /yas/src/game_info/resolution_family.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use crate::positioning::Size; 3 | 4 | #[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] 5 | pub enum ResolutionFamily { 6 | // PC 7 | Windows43x18, 8 | Windows7x3, 9 | Windows16x9, 10 | Windows8x5, 11 | Windows4x3, 12 | // Mobile 13 | MacOS8x5, 14 | } 15 | 16 | impl ResolutionFamily { 17 | pub fn new(size: Size) -> Option { 18 | // todo get OS at run time 19 | 20 | let height = size.height as u32; 21 | let width = size.width as u32; 22 | 23 | if height * 43 == width * 18 { 24 | Some(ResolutionFamily::Windows43x18) 25 | } else if height * 16 == width * 9 { 26 | Some(ResolutionFamily::Windows16x9) 27 | } else if height * 8 == width * 5 { 28 | Some(ResolutionFamily::Windows8x5) 29 | } else if height * 4 == width * 3 { 30 | Some(ResolutionFamily::Windows4x3) 31 | } else if height * 7 == width * 3 { 32 | Some(ResolutionFamily::Windows7x3) 33 | } else if (height as i32 * 8 - width as i32 * 5).abs() < 20 { 34 | Some(ResolutionFamily::MacOS8x5) 35 | } else { 36 | None 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /yas/src/window_info/load_window_info.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use serde::{Deserialize, Serialize}; 3 | use crate::game_info::{Platform, UI}; 4 | use crate::positioning::Size; 5 | use crate::window_info::WindowInfoType; 6 | use crate::window_info::WindowInfoRepository; 7 | 8 | /// Which is a format, where the whole file are recorded under a certain resolution 9 | #[derive(Serialize, Deserialize)] 10 | pub struct WindowInfoTemplatePerSize { 11 | pub current_resolution: Size, 12 | pub platform: Platform, 13 | pub ui: UI, 14 | pub data: HashMap 15 | } 16 | 17 | impl WindowInfoTemplatePerSize { 18 | pub fn inject_into_window_info_repo(&self, repo: &mut WindowInfoRepository) { 19 | for (name, value) in self.data.iter() { 20 | repo.add(name, self.current_resolution, self.ui, self.platform, *value); 21 | } 22 | } 23 | } 24 | 25 | pub macro load_window_info_repo($($filename:literal),+ $(,)?) { 26 | { 27 | let mut result = WindowInfoRepository::new(); 28 | $( 29 | { 30 | let s = include_str!($filename); 31 | let f: WindowInfoTemplatePerSize = serde_json::from_str(&s).unwrap(); 32 | f.inject_into_window_info_repo(&mut result); 33 | } 34 | )* 35 | result 36 | } 37 | } 38 | 39 | -------------------------------------------------------------------------------- /yas-wutheringwaves/src/scanner_controller/repository/config.rs: -------------------------------------------------------------------------------- 1 | use clap::arg; 2 | 3 | #[derive(clap::Args, Clone)] 4 | pub struct WWRepositoryLayoutConfig { 5 | /// Max rows to scan 6 | #[arg(id = "max-row", long = "max-row", help = "最大扫描行数")] 7 | pub max_row: Option, 8 | 9 | /// The time to wait for scrolling. Consider increasing this value if the scrolling is not correct 10 | #[arg(id = "scroll-delay", long = "scroll-delay", help = "翻页时滚轮停顿时间(ms)(翻页不正确可以考虑加大该选项)", default_value_t = 80)] 11 | pub scroll_delay: i32, 12 | 13 | /// Dump the captured image 14 | // pub dump_mode: bool, 15 | 16 | /// The maximum time to wait for switching to the next item 17 | #[arg(id = "max-wait-switch-item", long = "max-wait-switch-item", help = "切换物品最大等待时间(ms)", default_value_t = 800)] 18 | pub max_wait_switch_item: i32, 19 | 20 | /// The time to wait for switching to the next item in cloud game 21 | #[arg(id = "cloud-wait-switch-item", long = "cloud-wait-switch-item", help = "云游戏切换物品等待时间(ms)", default_value_t = 300)] 22 | pub cloud_wait_switch_item: i32, 23 | } 24 | 25 | impl Default for WWRepositoryLayoutConfig { 26 | fn default() -> Self { 27 | Self { 28 | max_row: None, 29 | scroll_delay: 80, 30 | max_wait_switch_item: 800, 31 | cloud_wait_switch_item: 800, 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /yas/src/utils/mod.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Arguments; 2 | use std::fs; 3 | use std::path::Path; 4 | use std::thread; 5 | use std::time::Duration; 6 | use serde::Deserialize; 7 | use std::io::stdin; 8 | use std::process; 9 | pub use misc::*; 10 | 11 | #[cfg(target_os = "macos")] 12 | mod macos; 13 | #[cfg(target_os = "macos")] 14 | pub use macos::*; 15 | 16 | #[cfg(windows)] 17 | mod windows; 18 | #[cfg(windows)] 19 | pub use windows::*; 20 | 21 | mod misc; 22 | 23 | pub fn sleep(ms: u32) { 24 | thread::sleep(Duration::from_millis(ms as u64)); 25 | } 26 | 27 | pub fn read_file_to_string>(path: P) -> String { 28 | fs::read_to_string(path).unwrap() 29 | } 30 | 31 | pub fn quit() -> ! { 32 | let mut s: String = String::new(); 33 | stdin().read_line(&mut s).unwrap(); 34 | process::exit(0); 35 | } 36 | 37 | #[doc(hidden)] 38 | pub fn error_and_quit_internal(args: Arguments) -> ! { 39 | panic!("Error: {}", args); 40 | } 41 | 42 | #[macro_export] 43 | macro_rules! error_and_quit { 44 | ($($arg:tt)*) => ( 45 | $crate::utils::error_and_quit_internal(format_args!($($arg)*)) 46 | ); 47 | } 48 | 49 | #[cfg(not(windows))] 50 | pub fn is_rmb_down() -> bool { 51 | false 52 | } 53 | 54 | #[derive(Deserialize)] 55 | pub struct GithubTag { 56 | pub name: String, 57 | } 58 | 59 | pub fn ensure_dir(path: &str) { 60 | if !std::path::Path::new(path).exists() { 61 | fs::create_dir_all(path).unwrap(); 62 | } 63 | } -------------------------------------------------------------------------------- /yas/src/export/export_statistics.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use bytesize::ByteSize; 4 | use prettytable::{row, Row, Table}; 5 | 6 | use crate::export::StatisticItem; 7 | 8 | pub struct ExportStatistics { 9 | pub exported_assets: Vec, 10 | pub failed_items: Vec, 11 | } 12 | 13 | impl ExportStatistics { 14 | pub fn get_table(&self) -> Table { 15 | let mut table = Table::new(); 16 | 17 | table.add_row(row!["Name", "Description", "File", "Size"]); 18 | for item in self.exported_assets.iter() { 19 | table.add_row(Row::new(vec![ 20 | prettytable::Cell::new(item.name.as_ref().unwrap_or(&Default::default())), 21 | prettytable::Cell::new(item.description.as_ref().unwrap_or(&Default::default())), 22 | prettytable::Cell::new(&format!("{:?}", item.filename)), 23 | prettytable::Cell::new(&format!("{}", ByteSize(item.size_in_bytes as u64))), 24 | ])); 25 | } 26 | 27 | table 28 | } 29 | } 30 | 31 | impl fmt::Display for ExportStatistics { 32 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 33 | let table = self.get_table(); 34 | write!(f, "{}", table) 35 | } 36 | } 37 | 38 | impl ExportStatistics { 39 | pub fn new() -> Self { 40 | ExportStatistics { 41 | exported_assets: Vec::new(), 42 | failed_items: Vec::new() 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /yas/src/game_info/game_info_builder.rs: -------------------------------------------------------------------------------- 1 | use super::game_info::GameInfo; 2 | use anyhow::Result; 3 | 4 | pub struct GameInfoBuilder { 5 | pub local_window_names: Vec, 6 | pub cloud_window_names: Vec, 7 | } 8 | 9 | impl GameInfoBuilder { 10 | pub fn new() -> Self { 11 | GameInfoBuilder { 12 | local_window_names: Vec::new(), 13 | cloud_window_names: Vec::new(), 14 | } 15 | } 16 | 17 | pub fn add_local_window_name(&mut self, name: &str) -> &mut Self { 18 | self.local_window_names.push(String::from(name)); 19 | self 20 | } 21 | 22 | pub fn add_cloud_window_name(&mut self, name: &str) -> &mut Self { 23 | self.cloud_window_names.push(String::from(name)); 24 | self 25 | } 26 | 27 | pub fn build(&self) -> Result { 28 | #[cfg(windows)] 29 | { 30 | let mut window_names = Vec::new(); 31 | for name in self.local_window_names.iter() { 32 | window_names.push(name.as_str()); 33 | } 34 | for name in self.cloud_window_names.iter() { 35 | window_names.push(name.as_str()); 36 | } 37 | crate::game_info::os::get_game_info(&window_names) 38 | // crate::game_info::os::get_game_info(&["原神", "Genshin Impact", "云·原神"]) 39 | } 40 | 41 | #[cfg(target_os = "linux")] 42 | crate::game_info::os::get_game_info() 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /yas-starrail/src/scanner_controller/repository_layout/config.rs: -------------------------------------------------------------------------------- 1 | use clap::{arg}; 2 | 3 | // todo add all the cmd arguments 4 | #[derive(Clone, clap::Args)] 5 | pub struct StarRailRepositoryScannerLogicConfig { 6 | /// Max rows to scan 7 | #[arg(id = "max-row", long = "max-row", help = "最大扫描行数", default_value_t = -1)] 8 | pub max_row: i32, 9 | 10 | /// The time to wait for scrolling. Consider increasing this value if the scrolling is not correct 11 | #[arg(id = "scroll-delay", long = "scroll-delay", help = "翻页时滚轮停顿时间(ms)(翻页不正确可以考虑加大该选项)", default_value_t = 80)] 12 | pub scroll_delay: i32, 13 | 14 | /// Dump the captured image 15 | // pub dump_mode: bool, 16 | 17 | /// The maximum time to wait for switching to the next item 18 | #[arg(id = "max-wait-switch-item", long = "max-wait-switch-item", help = "切换物品最大等待时间(ms)", default_value_t = 800)] 19 | pub max_wait_switch_item: i32, 20 | 21 | /// The time to wait for switching to the next item in cloud game 22 | #[arg(id = "cloud-wait-switch-item", long = "cloud-wait-switch-item", help = "云游戏切换物品等待时间(ms)", default_value_t = 300)] 23 | pub cloud_wait_switch_item: i32, 24 | } 25 | 26 | impl Default for StarRailRepositoryScannerLogicConfig { 27 | fn default() -> Self { 28 | StarRailRepositoryScannerLogicConfig { 29 | max_row: -1, 30 | scroll_delay: 80, 31 | max_wait_switch_item: 800, 32 | cloud_wait_switch_item: 300, 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /yas/src/game_info/os/linux.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Result, anyhow}; 2 | 3 | use crate::game_info::{GameInfo, Platform, UI, ResolutionFamily}; 4 | use crate::positioning::Rect; 5 | 6 | pub fn get_game_info() -> Result { 7 | let window_id = String::from_utf8( 8 | std::process::Command::new("sh") 9 | .arg("-c") 10 | .arg(r#" xwininfo|grep "Window id"|cut -d " " -f 4 "#) 11 | .output() 12 | .unwrap() 13 | .stdout, 14 | )?; 15 | let window_id = window_id.trim_end_matches("\n"); 16 | 17 | let position_size = String::from_utf8( 18 | std::process::Command::new("sh") 19 | .arg("-c") 20 | .arg(&format!(r#" xwininfo -id {window_id}|cut -f 2 -d :|tr -cd "0-9\n"|grep -v "^$"|sed -n "1,2p;5,6p" "#)) 21 | .output() 22 | .unwrap() 23 | .stdout, 24 | )?; 25 | 26 | let mut info = position_size.split("\n"); 27 | 28 | let left = info.next().unwrap().parse().unwrap(); 29 | let top = info.next().unwrap().parse().unwrap(); 30 | let width = info.next().unwrap().parse().unwrap(); 31 | let height = info.next().unwrap().parse().unwrap(); 32 | 33 | let rect = Rect::new(left, top, width, height); 34 | let rf = ResolutionFamily::new(rect.size()).ok_or(anyhow!("unknown resolution family"))?; 35 | 36 | Ok(GameInfo { 37 | window: rect.to_rect_i32(), 38 | resolution_family: rf, 39 | is_cloud: false, 40 | ui: UI::Desktop, 41 | platform: Platform::Linux, 42 | }) 43 | } 44 | -------------------------------------------------------------------------------- /yas-application/src/bin/yas.rs: -------------------------------------------------------------------------------- 1 | use clap::{command, Command}; 2 | use yas::utils::press_any_key_to_continue; 3 | use yas_genshin::application::ArtifactScannerApplication; 4 | use yas_starrail::application::RelicScannerApplication; 5 | 6 | fn get_genshin_command() -> Command { 7 | let cmd = ArtifactScannerApplication::build_command(); 8 | cmd.name("genshin") 9 | } 10 | 11 | fn get_starrail_command() -> Command { 12 | let cmd = RelicScannerApplication::build_command(); 13 | cmd.name("starrail") 14 | } 15 | 16 | fn init() { 17 | env_logger::Builder::new() 18 | .filter_level(log::LevelFilter::Info) 19 | .init(); 20 | } 21 | 22 | pub fn main() { 23 | init(); 24 | let cmd = command!() 25 | .subcommand(get_genshin_command()) 26 | .subcommand(get_starrail_command()); 27 | let arg_matches = cmd.get_matches(); 28 | 29 | let res = if let Some((subcommand_name, matches)) = arg_matches.subcommand() { 30 | if subcommand_name == "genshin" { 31 | let application = ArtifactScannerApplication::new(matches.clone()); 32 | application.run() 33 | } else if subcommand_name == "starrail" { 34 | let application = RelicScannerApplication::new(matches.clone()); 35 | application.run() 36 | } else { 37 | Ok(()) 38 | } 39 | } else { 40 | Ok(()) 41 | }; 42 | 43 | match res { 44 | Ok(_) => { 45 | press_any_key_to_continue(); 46 | }, 47 | Err(e) => { 48 | log::error!("error: {}", e); 49 | press_any_key_to_continue(); 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /yas/src/positioning/size.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{Display, Formatter}; 2 | use std::hash::{Hash, Hasher}; 3 | 4 | use serde::{Deserialize, Serialize}; 5 | 6 | use crate::positioning::Scalable; 7 | 8 | #[derive(Debug, Clone, PartialEq, Eq, Default, Copy, Serialize, Deserialize)] 9 | pub struct Size { 10 | pub height: T, 11 | pub width: T, 12 | } 13 | 14 | impl Size { 15 | pub fn new(width: T, height: T) -> Size { 16 | Size { 17 | width, height 18 | } 19 | } 20 | } 21 | 22 | impl Display for Size where T: Display { 23 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 24 | write!(f, "Size({}, {})", self.height, self.width) 25 | } 26 | } 27 | 28 | macro impl_int_size($t:ty) { 29 | impl Scalable for Size<$t> { 30 | fn scale(&self, factor: f64) -> Self { 31 | Size { 32 | height: ((self.height as f64) * factor) as $t, 33 | width: ((self.width as f64) * factor) as $t, 34 | } 35 | } 36 | } 37 | } 38 | 39 | impl Scalable for Size { 40 | fn scale(&self, factor: f64) -> Self { 41 | Size { 42 | height: self.height * factor, 43 | width: self.width * factor 44 | } 45 | } 46 | } 47 | 48 | impl_int_size!(i32); 49 | impl_int_size!(usize); 50 | impl_int_size!(u32); 51 | 52 | macro impl_int_hash($t:ty) { 53 | impl Hash for Size<$t> { 54 | fn hash(&self, state: &mut H) { 55 | self.width.hash(state); 56 | self.height.hash(state); 57 | } 58 | } 59 | } 60 | 61 | impl_int_hash!(i32); 62 | impl_int_hash!(usize); 63 | impl_int_hash!(u32); 64 | -------------------------------------------------------------------------------- /yas/src/draw_capture_region/draw_capture_region.rs: -------------------------------------------------------------------------------- 1 | use crate::positioning::{Pos, Rect}; 2 | 3 | 4 | pub trait DrawCaptureRegion { 5 | fn draw_capture_region(&self, image: &mut image::RgbImage); 6 | } 7 | 8 | // todo other types 9 | impl DrawCaptureRegion for Pos { 10 | fn draw_capture_region(&self, image: &mut image::RgbImage) { 11 | let blue = image::Rgb([0, 0, 255]); 12 | 13 | let x = self.x as u32; 14 | let y = self.y as u32; 15 | 16 | for i in x - 1..=x + 1 { 17 | for j in y - 1..=y + 1 { 18 | image.put_pixel(i, j, blue); 19 | } 20 | } 21 | 22 | for i in x - 5..=x + 5 { 23 | image.put_pixel(i, y + 5, blue); 24 | image.put_pixel(i, y - 5, blue); 25 | } 26 | 27 | for j in y - 5..=y + 5 { 28 | image.put_pixel(x + 5, j, blue); 29 | image.put_pixel(x - 5, j, blue); 30 | } 31 | } 32 | } 33 | 34 | impl DrawCaptureRegion for Rect { 35 | fn draw_capture_region(&self, image: &mut image::RgbImage) { 36 | let red = image::Rgb([255, 0, 0]); 37 | 38 | let left = self.left as u32; 39 | let top = self.top as u32; 40 | let width = self.width as u32; 41 | let height = self.height as u32; 42 | let bottom = top + height; 43 | let right = left + width; 44 | 45 | for x in left..right { 46 | image.put_pixel(x, top, red); 47 | image.put_pixel(x, bottom, red); 48 | } 49 | 50 | for y in top..bottom { 51 | image.put_pixel(left, y, red); 52 | image.put_pixel(right, y, red); 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /yas-genshin/src/scanner_controller/repository_layout/config.rs: -------------------------------------------------------------------------------- 1 | use clap::arg; 2 | 3 | #[derive(Clone, clap::Args)] 4 | pub struct GenshinRepositoryScannerLogicConfig { 5 | /// Max rows to scan 6 | #[arg(id = "max-row", long = "max-row", help = "最大扫描行数", default_value_t = -1)] 7 | pub max_row: i32, 8 | 9 | // todo move to another scanner 10 | /// Will the scanner capture only? 11 | // pub capture_only: bool, 12 | 13 | /// The time to wait for scrolling. Consider increasing this value if the scrolling is not correct 14 | #[arg(id = "scroll-delay", long = "scroll-delay", help = "翻页时滚轮停顿时间(ms)(翻页不正确可以考虑加大该选项)", default_value_t = 80)] 15 | pub scroll_delay: i32, 16 | 17 | /// Dump the captured image 18 | // pub dump_mode: bool, 19 | 20 | /// The maximum time to wait for switching to the next item 21 | #[arg(id = "max-wait-switch-item", long = "max-wait-switch-item", help = "切换物品最大等待时间(ms)", default_value_t = 800)] 22 | pub max_wait_switch_item: i32, 23 | 24 | /// The time to wait for switching to the next item in cloud game 25 | #[arg(id = "cloud-wait-switch-item", long = "cloud-wait-switch-item", help = "云游戏切换物品等待时间(ms)", default_value_t = 300)] 26 | pub cloud_wait_switch_item: i32, 27 | } 28 | 29 | impl Default for GenshinRepositoryScannerLogicConfig { 30 | fn default() -> Self { 31 | GenshinRepositoryScannerLogicConfig { 32 | max_row: -1, 33 | // capture_only: false, 34 | scroll_delay: 80, 35 | // number: -1, 36 | // dump_mode: false, 37 | max_wait_switch_item: 800, 38 | cloud_wait_switch_item: 300, 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /yas/src/export/exporter.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use std::io::Write; 3 | use std::path::PathBuf; 4 | 5 | use log::error; 6 | 7 | use crate::export::{ExportItem, ExportStatistics, StatisticItem}; 8 | 9 | pub struct ExportAssets { 10 | pub assets: Vec 11 | } 12 | 13 | impl ExportAssets { 14 | pub fn new() -> Self { 15 | ExportAssets { assets: Vec::new() } 16 | } 17 | 18 | pub fn add_asset(&mut self, name: Option, filename: PathBuf, contents: Vec, description: Option) { 19 | self.assets.push(ExportItem { 20 | contents, 21 | filename, 22 | name, 23 | description, 24 | }) 25 | } 26 | 27 | pub fn save(&self) -> ExportStatistics { 28 | let mut stat = ExportStatistics::new(); 29 | 30 | for item in self.assets.iter() { 31 | let mut file = match File::create(&item.filename) { 32 | Err(why) => { 33 | stat.failed_items.push(StatisticItem::from_export_item(item)); 34 | error!("无法创建文件 {:?}: {}", &item.filename, why); 35 | continue; 36 | }, 37 | Ok(file) => { 38 | file 39 | }, 40 | }; 41 | 42 | match file.write_all(&item.contents) { 43 | Err(why) => { 44 | stat.failed_items.push(StatisticItem::from_export_item(item)); 45 | error!("无法写入文件 {:?}: {}", &item.filename, why); 46 | continue; 47 | }, 48 | Ok(_) => (), 49 | } 50 | 51 | stat.exported_assets.push(StatisticItem::from_export_item(item)); 52 | } 53 | 54 | stat 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /yas-wutheringwaves/src/echo/stats.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Copy, Clone, Eq, PartialEq, strum_macros::Display)] 2 | pub enum WWStatName { 3 | CriticalDamage, 4 | CriticalRate, 5 | GlacioBonus, 6 | AeroBonus, 7 | FusionBonus, 8 | ElectroBonus, 9 | HavocBonus, 10 | SpectroBonus, 11 | EnergyRegeneration, 12 | ATK, 13 | ATKPercentage, 14 | HP, 15 | HPPercentage, 16 | DEF, 17 | DEFPercentage, 18 | HealingBonus, 19 | BasicAttackBonus, 20 | HeavyAttackBonus, 21 | ResonanceSkillBonus, 22 | ResonanceLiberationBonus, 23 | } 24 | 25 | impl WWStatName { 26 | pub fn from_chs(chs: &str, is_percentage: bool) -> Option { 27 | let ret = match chs { 28 | "暴击伤害" => Self::CriticalDamage, 29 | "暴击率" => Self::CriticalRate, 30 | "冷凝伤害加成" => Self::GlacioBonus, 31 | "气动伤害加成" => Self::AeroBonus, 32 | "热熔伤害加成" => Self::FusionBonus, 33 | "导电伤害加成" => Self::ElectroBonus, 34 | "湮灭伤害加成" => Self::HavocBonus, 35 | "衍射伤害加成" => Self::SpectroBonus, 36 | "共鸣效率" => Self::EnergyRegeneration, 37 | "攻击" => if is_percentage { Self::ATKPercentage } else { Self::ATK }, 38 | "防御" => if is_percentage { Self::DEFPercentage } else { Self::DEF }, 39 | "生命" => if is_percentage { Self::HPPercentage } else { Self::HP }, 40 | "治疗效果加成" => Self::HealingBonus, 41 | "普攻伤害加成" => Self::BasicAttackBonus, 42 | "重击伤害加成" => Self::HeavyAttackBonus, 43 | "共鸣技能伤害加成" => Self::ResonanceSkillBonus, 44 | "共鸣解放伤害加成" => Self::ResonanceLiberationBonus, 45 | _ => return None, 46 | }; 47 | 48 | Some(ret) 49 | } 50 | } 51 | 52 | pub struct WWStat { 53 | pub name: WWStatName, 54 | pub value: f64, 55 | } 56 | -------------------------------------------------------------------------------- /yas/src/system_control/macos/macos_control.rs: -------------------------------------------------------------------------------- 1 | use enigo::{Enigo, MouseControllable}; 2 | 3 | use crate::system_control::system_control::SystemControl; 4 | use crate::utils; 5 | 6 | pub struct MacOSControl { 7 | enigo: Enigo 8 | } 9 | 10 | impl MacOSControl { 11 | pub fn new() -> MacOSControl { 12 | MacOSControl { 13 | enigo: Enigo::new() 14 | } 15 | } 16 | 17 | pub fn mouse_move_to(&mut self, x: i32, y: i32) -> anyhow::Result<()> { 18 | self.enigo.mouse_move_to(x, y); 19 | 20 | anyhow::Ok(()) 21 | } 22 | 23 | pub fn mouse_click(&mut self) -> anyhow::Result<()> { 24 | self.enigo.mouse_click(MouseButton::Left); 25 | 26 | anyhow::Ok(()) 27 | } 28 | 29 | pub fn mouse_scroll(&mut self, amount: i32) -> anyhow::Result<()> { 30 | self.enigo.mouse_scroll_y(-amount); 31 | 32 | anyhow::Ok(()) 33 | } 34 | 35 | pub fn mac_scroll(&mut self, length: i32, delta: i32, times: i32) { 36 | let enigo = &mut self.enigo; 37 | 38 | for _j in 0..length { 39 | enigo.mouse_down(MouseButton::Left); 40 | for _i in 0..times { 41 | enigo.mouse_move_relative(0, -delta); 42 | utils::sleep(10); 43 | } 44 | 45 | enigo.mouse_up(MouseButton::Left); 46 | utils::sleep(10); 47 | 48 | enigo.mouse_down(MouseButton::Left); 49 | utils::sleep(5); 50 | enigo.mouse_up(MouseButton::Left); 51 | utils::sleep(5); 52 | 53 | enigo.mouse_move_relative(0, times * delta); 54 | utils::sleep(20); 55 | } 56 | } 57 | 58 | pub fn mac_scroll_fast(length: i32) { 59 | mac_scroll(length, 4, 30); 60 | } 61 | 62 | pub fn mac_scroll_slow(length: i32) { 63 | mac_scroll(length, 4, 5); 64 | } 65 | } -------------------------------------------------------------------------------- /yas-genshin/src/scanner/item_scanner/item_scanner_config.rs: -------------------------------------------------------------------------------- 1 | use clap::{Arg, ArgAction, FromArgMatches}; 2 | use yas::arguments_builder::arguments_builder::ArgumentsModifier; 3 | 4 | use crate::scanner_controller::repository_layout::config::GenshinRepositoryScannerLogicConfig; 5 | 6 | pub struct ItemScannerConfig { 7 | pub verbose: bool, 8 | 9 | pub genshin_repo_scan_logic_config: GenshinRepositoryScannerLogicConfig, 10 | } 11 | 12 | impl Default for ItemScannerConfig { 13 | fn default() -> Self { 14 | ItemScannerConfig { 15 | verbose: false, 16 | genshin_repo_scan_logic_config: Default::default() 17 | } 18 | } 19 | } 20 | 21 | impl ArgumentsModifier for ItemScannerConfig { 22 | fn modify_arguments(builder: &mut yas::arguments_builder::arguments_builder::ArgumentsBuilder) { 23 | builder 24 | .arg( 25 | Arg::new("verbose") 26 | .long("verbose") 27 | .help("显示详细信息") 28 | .num_args(0) 29 | .action(ArgAction::SetTrue) 30 | ); 31 | 32 | ::modify_arguments(builder); 33 | } 34 | } 35 | 36 | impl FromArgMatches for ItemScannerConfig { 37 | fn from_arg_matches(matches: &clap::ArgMatches) -> Result { 38 | let scanner_controller_config = GenshinRepositoryScannerLogicConfig::from_arg_matches(matches)?; 39 | 40 | let result = ItemScannerConfig { 41 | genshin_repo_scan_logic_config: scanner_controller_config, 42 | verbose: matches.get_flag("verbose"), 43 | }; 44 | 45 | Ok(result) 46 | } 47 | 48 | fn update_from_arg_matches(&mut self, _matches: &clap::ArgMatches) -> Result<(), clap::Error> { 49 | // todo 50 | unimplemented!() 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /yas/src/capture/stream_capturer.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{Arc, atomic}; 2 | use std::sync::atomic::AtomicBool; 3 | use std::sync::mpsc::{Receiver, Sender}; 4 | use std::thread::JoinHandle; 5 | use std::thread; 6 | use image::{GenericImage, RgbImage}; 7 | use crate::capture::{Capturer, GenericCapturer}; 8 | use crate::positioning::Rect; 9 | use anyhow::Result; 10 | 11 | pub struct StreamingCapturer { 12 | region: Rect, 13 | capturer: Box + Send>, 14 | 15 | is_cancelled: Arc 16 | } 17 | 18 | impl StreamingCapturer where { 19 | pub fn new(region: Rect) -> Self { 20 | Self { 21 | region, 22 | capturer: Box::new(GenericCapturer::new().unwrap()), 23 | is_cancelled: Arc::new(AtomicBool::new(false)), 24 | } 25 | } 26 | 27 | pub fn start_transform(self, tx: Sender, transform: F) -> (JoinHandle>, impl Fn()) 28 | where 29 | F: Fn(RgbImage) -> S + Send + Sync + 'static, 30 | S: Send + Sync + 'static 31 | { 32 | let is_cancelled = self.is_cancelled.clone(); 33 | 34 | let handle = thread::spawn(move || -> Result<()> { 35 | let mut it = 0; 36 | loop { 37 | if self.is_cancelled.load(atomic::Ordering::Relaxed) { 38 | break; 39 | } 40 | 41 | // println!("capture image {}", it); 42 | 43 | let image = self.capturer.capture_rect(self.region); 44 | if let Ok(im) = image { 45 | tx.send(transform(im))? 46 | } 47 | 48 | it += 1; 49 | } 50 | 51 | Ok(()) 52 | }); 53 | 54 | let cancel = move || { 55 | println!("cancel capture"); 56 | is_cancelled.store(true, atomic::Ordering::Relaxed); 57 | }; 58 | 59 | (handle, cancel) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /yas-genshin/src/character/character_names.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | 3 | use lazy_static::lazy_static; 4 | 5 | lazy_static! { 6 | pub static ref CHARACTER_NAMES: HashSet<&'static str> = HashSet::from([ 7 | "迪卢克", 8 | "可莉", 9 | "胡桃", 10 | "宵宫", 11 | "安柏", 12 | "班尼特", 13 | "香菱", 14 | "辛焱", 15 | "烟绯", 16 | "托马", 17 | "莫娜", 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 | -------------------------------------------------------------------------------- /yas/src/profiler/profiler.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::time::{Duration, SystemTime}; 3 | use anyhow::Result; 4 | 5 | pub struct Profiler { 6 | scope: Vec, 7 | 8 | begin_time: Vec, 9 | time_table: HashMap, 10 | } 11 | 12 | impl Profiler { 13 | pub fn new() -> Self { 14 | Self { 15 | scope: Vec::new(), 16 | begin_time: Vec::new(), 17 | time_table: HashMap::new(), 18 | } 19 | } 20 | 21 | fn get_key(&self) -> String { 22 | let mut ret = String::new(); 23 | for item in self.scope.iter() { 24 | ret = ret + item; 25 | } 26 | ret 27 | } 28 | 29 | pub fn begin(&mut self, name: &str) { 30 | self.scope.push(String::from(name)); 31 | self.begin_time.push(SystemTime::now()); 32 | } 33 | 34 | pub fn end(&mut self, name: &str) -> Result<()> { 35 | if self.scope.len() == 0 { 36 | panic!("Profiler called end without begin"); 37 | } 38 | let len = self.scope.len(); 39 | let top = self.scope[len - 1].as_str(); 40 | if top == name { 41 | let key = self.get_key(); 42 | let entry = self.time_table.entry(key).or_insert((0, Duration::new(0, 0))); 43 | let elapsed = self.begin_time[len - 1].elapsed()?; 44 | 45 | entry.0 += 1; 46 | entry.1 += elapsed; 47 | 48 | self.scope.pop(); 49 | self.begin_time.pop(); 50 | } else { 51 | panic!("Profiler end {} and begin {} not match", name, top); 52 | } 53 | 54 | Ok(()) 55 | } 56 | 57 | pub fn print(&self) { 58 | println!("Profile:"); 59 | for (k, v) in self.time_table.iter() { 60 | let ms = v.1.as_millis() as f64 / (v.0 as f64); 61 | println!("{}: avg {}ms, execution count: {}", k, ms, v.0); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /yas/src/positioning/pos.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Display; 2 | use std::ops::{Add, Sub}; 3 | 4 | use serde::{Deserialize, Serialize}; 5 | 6 | use crate::positioning::{Scalable, Size}; 7 | 8 | #[derive(Debug, Clone, PartialEq, Default, Copy, Serialize, Deserialize)] 9 | pub struct Pos { 10 | pub x: T, 11 | pub y: T, 12 | } 13 | 14 | impl Add> for Pos where T: Add { 15 | type Output = Self; 16 | 17 | fn add(self, rhs: Pos) -> Self::Output { 18 | Pos { 19 | x: self.x + rhs.x, 20 | y: self.y + rhs.y, 21 | } 22 | } 23 | } 24 | 25 | impl Add> for Pos where T: Add { 26 | type Output = Self; 27 | 28 | fn add(self, rhs: Size) -> Self::Output { 29 | Pos { 30 | x: self.x + rhs.width, 31 | y: self.y + rhs.height 32 | } 33 | } 34 | } 35 | 36 | impl Sub> for Pos where T: Sub { 37 | type Output = Self; 38 | 39 | fn sub(self, rhs: Pos) -> Self::Output { 40 | Pos { 41 | x: self.x - rhs.x, 42 | y: self.y - rhs.y, 43 | } 44 | } 45 | } 46 | 47 | impl Pos { 48 | pub fn new(x: T, y: T) -> Pos { 49 | Pos { 50 | x, y 51 | } 52 | } 53 | } 54 | 55 | impl Display for Pos where T: Display { 56 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 57 | write!(f, "({}, {})", self.x, self.y) 58 | } 59 | } 60 | 61 | impl Scalable for Pos { 62 | fn scale(&self, factor: f64) -> Pos { 63 | Pos { 64 | x: self.x * factor, 65 | y: self.y * factor, 66 | } 67 | } 68 | } 69 | 70 | macro impl_int_pos($t:ty) { 71 | impl Scalable for Pos<$t> { 72 | fn scale(&self, factor: f64) -> Pos<$t> { 73 | Pos { 74 | x: ((self.x as f64) * factor) as $t, 75 | y: ((self.y as f64) * factor) as $t 76 | } 77 | } 78 | } 79 | } 80 | 81 | impl_int_pos!(i32); 82 | impl_int_pos!(usize); 83 | impl_int_pos!(u32); 84 | -------------------------------------------------------------------------------- /yas/src/ocr/paddle_paddle_model/preprocess.rs: -------------------------------------------------------------------------------- 1 | use image::{ImageBuffer, RgbImage}; 2 | use crate::positioning::Shape3D; 3 | use anyhow::Result; 4 | use image::imageops::{FilterType, resize}; 5 | #[cfg(feature = "tract_onnx")] 6 | use tract_onnx::prelude::{Tensor, tract_ndarray}; 7 | 8 | /// Resize an image to the expected height, but the width can vary 9 | /// rec_image_shape: the expected shape to feed into the onnx model. CHW 10 | pub fn resize_img(rec_image_shape: Shape3D, img: &RgbImage) -> RgbImage { 11 | let image_width = img.width(); 12 | let image_height = img.height(); 13 | let wh_ratio = image_width as f64 / image_height as f64; 14 | 15 | assert_eq!(rec_image_shape.x, 3); 16 | 17 | let resized_width = (wh_ratio * rec_image_shape.y as f64) as u32; 18 | 19 | let resized_image = resize(img, resized_width, rec_image_shape.y, FilterType::Triangle); 20 | resized_image 21 | } 22 | 23 | #[cfg(feature = "ort")] 24 | pub fn normalize_image_to_ndarray(img: &RgbImage) -> ndarray::Array4 { 25 | let height = img.height() as usize; 26 | let width = img.width() as usize; 27 | // let tensor: Tensor = tract_ndarray::Array4::from_shape_fn((1, 3, height, width), |(_, c, y, x)| { 28 | // let pix = img.get_pixel(x as u32, y as u32)[c]; 29 | // let v = pix as f32 / 255.0_f32; 30 | // (v - 0.5) / 0.5 31 | // }).into(); 32 | // tensor 33 | 34 | let arr = ndarray::Array4::from_shape_fn((1, 3, height, width), |(_, c, y, x)| { 35 | let pix = img.get_pixel(x as u32, y as u32)[c]; 36 | let v = pix as f32 / 255.0_f32; 37 | (v - 0.5) / 0.5 38 | }); 39 | arr 40 | } 41 | 42 | #[cfg(feature = "tract_onnx")] 43 | pub fn normalize_image_to_tensor(img: &RgbImage) -> Tensor { 44 | let height = img.height() as usize; 45 | let width = img.width() as usize; 46 | let tensor: Tensor = tract_ndarray::Array4::from_shape_fn((1, 3, height, width), |(_, c, y, x)| { 47 | let pix = img.get_pixel(x as u32, y as u32)[c]; 48 | let v = pix as f32 / 255.0_f32; 49 | (v - 0.5) / 0.5 50 | }).into(); 51 | tensor 52 | } -------------------------------------------------------------------------------- /yas-derive/src/window_info/derive_macro.rs: -------------------------------------------------------------------------------- 1 | use quote::quote; 2 | use syn::{parse_macro_input, DeriveInput}; 3 | use crate::window_info::WindowInfoNestedAttributes; 4 | 5 | pub fn yas_window_info(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 6 | let input = parse_macro_input!(input as DeriveInput); 7 | let struct_name = &input.ident; 8 | 9 | if let syn::Data::Struct(data_struct) = &input.data { 10 | let mut fields = Vec::new(); 11 | for field in data_struct.fields.iter() { 12 | let name = field.ident.as_ref().unwrap(); 13 | 14 | let mut window_info_key: String = name.to_string(); 15 | for attr in field.attrs.iter() { 16 | if attr.path().is_ident("window_info") { 17 | let nested_attributes = WindowInfoNestedAttributes::from_attr(attr).unwrap(); 18 | if nested_attributes.rename.is_some() { 19 | window_info_key = nested_attributes.rename.clone().unwrap().value(); 20 | } 21 | } 22 | } 23 | 24 | fields.push(quote! { 25 | #name: match repo.get_auto_scale(#window_info_key, window_size, ui, platform) { 26 | None => { 27 | return Err(anyhow::anyhow!("cannot find window info key \"{}\"", #window_info_key)); 28 | }, 29 | Some(value) => value 30 | } 31 | }); 32 | } 33 | 34 | let trait_impl = quote! { 35 | impl yas::window_info::FromWindowInfoRepository for #struct_name { 36 | fn from_window_info_repository( 37 | window_size: yas::positioning::Size, 38 | ui: yas::game_info::UI, 39 | platform: yas::game_info::Platform, 40 | repo: &yas::window_info::WindowInfoRepository 41 | ) -> anyhow::Result { 42 | Ok(Self { 43 | #(#fields),* 44 | }) 45 | } 46 | } 47 | }; 48 | 49 | return trait_impl.into(); 50 | } 51 | 52 | proc_macro::TokenStream::new() 53 | } -------------------------------------------------------------------------------- /yas-derive-wuthering-waves/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate proc_macro2; 2 | 3 | use proc_macro::TokenStream; 4 | use crate::echoes::EchoDataItem; 5 | use quote::quote; 6 | 7 | mod echoes; 8 | 9 | fn get_echo_names(data: &[EchoDataItem]) -> Vec { 10 | let mut result = Vec::new(); 11 | 12 | for item in data.iter() { 13 | result.push(item.name.parse().unwrap()); 14 | } 15 | 16 | result 17 | } 18 | 19 | fn echo_name_from_chs(data: &[EchoDataItem], echo_names: &[proc_macro2::TokenStream]) -> proc_macro2::TokenStream { 20 | let chs_names: Vec<_> = data.iter().map(|x| x.name_chs.clone()).collect(); 21 | 22 | let mut temp = Vec::new(); 23 | for i in 0..echo_names.len() { 24 | let name = &chs_names[i]; 25 | let echo_name = &echo_names[i]; 26 | temp.push(quote! { 27 | #name => Some(Self:: #echo_name), 28 | }); 29 | } 30 | 31 | quote! { 32 | impl WWEchoName { 33 | pub fn from_chs(chs: &str) -> Option { 34 | match chs { 35 | #(#temp)* 36 | // It's weird that this will not compile 37 | // #(#chs_names => Some(Self::#echo_names)),* 38 | _ => return None, 39 | } 40 | } 41 | } 42 | } 43 | } 44 | 45 | #[proc_macro] 46 | pub fn yas_wuthering_waves_echoes(input: TokenStream) -> TokenStream { 47 | let ast: syn::LitStr = syn::parse(input).unwrap(); 48 | 49 | let filename = ast.value(); 50 | 51 | let content = std::fs::read_to_string(filename).unwrap(); 52 | let echo_data: Vec = serde_json::from_str(&content).unwrap(); 53 | 54 | let echo_names = get_echo_names(&echo_data); 55 | 56 | let echo_name_enum = quote! { 57 | #[derive(Debug, Copy, Clone, Eq, PartialEq, strum_macros::Display)] 58 | pub enum WWEchoName { 59 | #(#echo_names),* 60 | } 61 | }; 62 | let echo_name_from_chs_impl = echo_name_from_chs(&echo_data, &echo_names); 63 | 64 | let result = quote! { 65 | #echo_name_enum 66 | #echo_name_from_chs_impl 67 | }; 68 | 69 | // println!("{:?}", result.to_string()); 70 | 71 | result.into() 72 | } 73 | -------------------------------------------------------------------------------- /yas-wutheringwaves/src/export/echo/hsi.rs: -------------------------------------------------------------------------------- 1 | use std::ops::Deref; 2 | use serde::{Serialize, Serializer}; 3 | use serde::ser::SerializeMap; 4 | use crate::echo::{WWEcho, WWStat}; 5 | 6 | struct HsiStat<'a>(&'a WWStat); 7 | 8 | impl<'a> Deref for HsiStat<'a> { 9 | type Target = WWStat; 10 | 11 | fn deref(&self) -> &Self::Target { 12 | &self.0 13 | } 14 | } 15 | 16 | impl<'a> Serialize for HsiStat<'a> { 17 | fn serialize(&self, serializer: S) -> Result 18 | where 19 | S: Serializer, 20 | { 21 | let mut root = serializer.serialize_map(Some(2))?; 22 | 23 | root.serialize_entry("name", &self.name.to_string())?; 24 | root.serialize_entry("value", &self.value)?; 25 | 26 | root.end() 27 | } 28 | } 29 | 30 | struct HsiEcho<'a>(&'a WWEcho); 31 | 32 | impl<'a> Deref for HsiEcho<'a> { 33 | type Target = WWEcho; 34 | 35 | fn deref(&self) -> &Self::Target { 36 | &self.0 37 | } 38 | } 39 | 40 | impl<'a> Serialize for HsiEcho<'a> { 41 | fn serialize(&self, serializer: S) -> Result 42 | where 43 | S: Serializer, 44 | { 45 | let mut root = serializer.serialize_map(None)?; 46 | 47 | let mut hsi_sub_stats = Vec::new(); 48 | for item in self.sub_stats.iter() { 49 | hsi_sub_stats.push(HsiStat(item)); 50 | } 51 | 52 | root.serialize_entry("name", &self.name.to_string())?; 53 | root.serialize_entry("main_stat1", &HsiStat(&self.main_stat1))?; 54 | root.serialize_entry("main_stat2", &HsiStat(&self.main_stat2))?; 55 | root.serialize_entry("sub_stats", &hsi_sub_stats)?; 56 | root.serialize_entry("star", &self.star)?; 57 | root.serialize_entry("level", &self.level)?; 58 | // root.serialize_entry("lock", &self.lock)?; 59 | 60 | root.end() 61 | } 62 | } 63 | 64 | pub struct WWHsiFormat<'a> { 65 | pub echoes: &'a [HsiEcho<'a>], 66 | pub version: usize, 67 | } 68 | 69 | impl<'a> Serialize for WWHsiFormat<'a> { 70 | fn serialize(&self, serializer: S) -> Result 71 | where 72 | S: Serializer, 73 | { 74 | let mut map = serializer.serialize_map(None)?; 75 | 76 | map.serialize_entry("echoes", self.echoes)?; 77 | map.serialize_entry("version", &self.version)?; 78 | map.end() 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /yas-starrail/src/export/relic/exporter.rs: -------------------------------------------------------------------------------- 1 | use std::path::{PathBuf}; 2 | 3 | use clap::{FromArgMatches}; 4 | 5 | use crate::relic::StarRailRelic; 6 | 7 | use crate::export::relic::{ExportRelicConfig, StarRailRelicExportFormat}; 8 | use anyhow::Result; 9 | use yas::export::{AssetEmitter, ExportAssets}; 10 | use crate::export::relic::hsr::StarRailHSRFormat; 11 | 12 | use super::march7th::March7thFormat; 13 | 14 | pub struct StarRailRelicExporter<'a> { 15 | pub format: StarRailRelicExportFormat, 16 | pub results: Option<&'a [StarRailRelic]>, 17 | pub output_dir: PathBuf, 18 | } 19 | 20 | impl<'a> StarRailRelicExporter<'a> { 21 | pub fn new(arg_matches: &clap::ArgMatches, results: &'a [StarRailRelic]) -> Result { 22 | let config = ExportRelicConfig::from_arg_matches(arg_matches)?; 23 | Ok(Self { 24 | format: config.format, 25 | results: Some(results), 26 | output_dir: PathBuf::from(&config.output_dir) 27 | }) 28 | } 29 | } 30 | 31 | impl<'a> AssetEmitter for StarRailRelicExporter<'a> { 32 | fn emit(&self, asset_bundle: &mut ExportAssets) { 33 | if self.results.is_none() { 34 | return; 35 | } 36 | 37 | let results = self.results.unwrap(); 38 | 39 | match self.format { 40 | StarRailRelicExportFormat::March7th => { 41 | let path = self.output_dir.join("march7th.json"); 42 | let format = March7thFormat::new(results); 43 | let contents = serde_json::to_string(&format).unwrap(); 44 | 45 | asset_bundle.add_asset( 46 | Some(String::from("relics")), 47 | path, 48 | contents.into_bytes(), 49 | Some(String::from("三月七遗器格式")) 50 | ); 51 | }, 52 | StarRailRelicExportFormat::HSR => { 53 | let path = self.output_dir.join("hsr.json"); 54 | let format = StarRailHSRFormat::new_version3(results); 55 | let contents = serde_json::to_string(&format).unwrap(); 56 | 57 | asset_bundle.add_asset( 58 | Some(String::from("relics")), 59 | path, 60 | contents.into_bytes(), 61 | Some(String::from("HSR遗器格式")) 62 | ); 63 | } 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /yas/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "yas_core" 3 | version = "0.1.14" 4 | edition = "2021" 5 | description = "Mihoyo game item scanner library" 6 | repository = "https://github.com/wormtql/yas" 7 | authors = ["wormtql <584130248@qq.com>", "GZTime ", "YCR160 <3342711246@qq.com>"] 8 | keywords = ["GenshinImpact", "HonkaiStarRail", "scanner", "ocr"] 9 | license = "GPL-2.0-or-later" 10 | 11 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 12 | 13 | [dependencies] 14 | clap = { version = "4.4", features = ["derive"] } 15 | image = "0.24" 16 | enigo = "0.1" 17 | serde_json = "1.0" 18 | serde = { version = "1.0", features = ["derive"] } 19 | regex = "1.5" 20 | log = "0.4" 21 | edit-distance = "2.1" 22 | os_info = "3.0" 23 | strum = "0.25" 24 | strum_macros = "0.25" 25 | rand = "0.8" 26 | reqwest = { version = "0.11", features = ["blocking", "json"] } 27 | semver = "1.0" 28 | lazy_static = "1.4" 29 | # screenshots = { version = "0.8", optional = true } 30 | png = "0.17" 31 | anyhow = "1.0" 32 | once_cell = "1.18" 33 | indicatif-log-bridge = "0.2" 34 | indicatif = "0.17" 35 | console = "0.15" 36 | paste = "1.0" 37 | prettytable-rs = "^0.10" 38 | bytesize = {version = "1.2.0", features = ["serde"]} 39 | ort = { version = "2.0.0-rc.2", optional = true } 40 | ndarray = { version = "0.15", optional = true } 41 | tract-onnx = { version = "0.21.5", optional = true } 42 | 43 | [target.'cfg(target_os = "linux")'.dependencies] 44 | libwayshot = { version = "0.3.0", optional = true } 45 | screenshots = { version = "0.8", optional = true } 46 | 47 | [target.'cfg(target_os = "windows")'.dependencies] 48 | windows-capture = "1.0.65" 49 | screenshots = { version = "0.8" } 50 | 51 | [target.'cfg(target_os = "windows")'.dependencies.windows-sys] 52 | version = "0.59.0" 53 | features = [ 54 | "Win32_UI_WindowsAndMessaging", 55 | "Win32_Graphics_Gdi", 56 | "Win32_Security", 57 | "Win32_UI_Input_KeyboardAndMouse", 58 | "Win32_System_SystemServices", 59 | "Win32_System_LibraryLoader", 60 | ] 61 | 62 | [target.'cfg(target_os = "macos")'.dependencies] 63 | core-graphics = "0.23" 64 | core-foundation = "0.9" 65 | cocoa = "0.25" 66 | 67 | [build-dependencies] 68 | cc = "1.1.7" 69 | 70 | [features] 71 | # default = ["tract_onnx"] 72 | ort = ["dep:ort", "dep:ndarray"] 73 | tract_onnx = ["dep:tract-onnx"] 74 | 75 | capturer_screenshots = ["dep:screenshots"] 76 | capturer_libwayshot = ["dep:libwayshot"] 77 | 78 | -------------------------------------------------------------------------------- /yas-starrail/src/scanner/relic_scanner/relic_scanner_window_info.rs: -------------------------------------------------------------------------------- 1 | use yas::positioning::{Pos, Rect}; 2 | 3 | #[derive(Clone, yas_derive::YasWindowInfo, Debug)] 4 | pub struct RelicScannerWindowInfo { 5 | #[window_info(rename = "starrail_relic_title_rect")] 6 | pub title_rect: Rect, 7 | 8 | #[window_info(rename = "starrail_relic_main_stat_name_rect")] 9 | pub main_stat_name_rect: Rect, 10 | 11 | #[window_info(rename = "starrail_relic_main_stat_value_rect")] 12 | pub main_stat_value_rect: Rect, 13 | 14 | /// the sub stat name positions relative to window 15 | #[window_info(rename = "starrail_relic_sub_stat0_name_rect")] 16 | pub sub_stat_name_1: Rect, 17 | #[window_info(rename = "starrail_relic_sub_stat1_name_rect")] 18 | pub sub_stat_name_2: Rect, 19 | #[window_info(rename = "starrail_relic_sub_stat2_name_rect")] 20 | pub sub_stat_name_3: Rect, 21 | #[window_info(rename = "starrail_relic_sub_stat3_name_rect")] 22 | pub sub_stat_name_4: Rect, 23 | 24 | /// the sub stat value positions relative to window 25 | #[window_info(rename = "starrail_relic_sub_stat0_value_rect")] 26 | pub sub_stat_value_1: Rect, 27 | #[window_info(rename = "starrail_relic_sub_stat1_value_rect")] 28 | pub sub_stat_value_2: Rect, 29 | #[window_info(rename = "starrail_relic_sub_stat2_value_rect")] 30 | pub sub_stat_value_3: Rect, 31 | #[window_info(rename = "starrail_relic_sub_stat3_value_rect")] 32 | pub sub_stat_value_4: Rect, 33 | 34 | #[window_info(rename = "starrail_relic_level_rect")] 35 | pub level_rect: Rect, 36 | 37 | #[window_info(rename = "starrail_relic_equip_rect")] 38 | pub equip_rect: Rect, 39 | 40 | #[window_info(rename = "starrail_relic_equipper_pos")] 41 | pub equipper_pos: Pos, 42 | 43 | #[window_info(rename = "starrail_relic_item_count_rect")] 44 | pub item_count_rect: Rect, 45 | 46 | #[window_info(rename = "starrail_relic_star_pos")] 47 | pub star_pos: Pos, 48 | 49 | #[window_info(rename = "starrail_relic_lock_pos")] 50 | pub lock_pos: Pos, 51 | 52 | #[window_info(rename = "starrail_relic_discard_pos")] 53 | pub discard_pos: Pos, 54 | 55 | #[window_info(rename = "starrail_repository_panel_rect")] 56 | pub panel_rect: Rect, 57 | 58 | #[window_info(rename = "starrail_repository_item_col")] 59 | pub col: i32, 60 | } 61 | -------------------------------------------------------------------------------- /yas/src/positioning/rect.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Display; 2 | use std::ops::Add; 3 | use serde::{Deserialize, Serialize}; 4 | use crate::positioning::{Pos, Scalable, Size}; 5 | use paste::paste; 6 | 7 | #[derive(Debug, Clone, PartialEq, Default, Copy, Serialize, Deserialize)] 8 | pub struct Rect { 9 | pub left: T, 10 | pub top: T, 11 | pub width: T, 12 | pub height: T, 13 | } 14 | 15 | impl Rect where T: Copy { 16 | pub fn new(left: T, top: T, width: T, height: T) -> Rect { 17 | Rect { 18 | left, top, width, height 19 | } 20 | } 21 | 22 | pub fn origin(&self) -> Pos { 23 | Pos { 24 | x: self.left, 25 | y: self.top 26 | } 27 | } 28 | 29 | pub fn size(&self) -> Size { 30 | Size { 31 | width: self.width, 32 | height: self.height 33 | } 34 | } 35 | } 36 | 37 | impl Rect where T: Add + Copy { 38 | pub fn translate(&self, pos: Pos) -> Rect { 39 | Rect { 40 | left: self.left + pos.x, 41 | top: self.top + pos.y, 42 | width: self.width, 43 | height: self.height 44 | } 45 | } 46 | } 47 | 48 | impl Display for Rect where T: Display + Copy { 49 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 50 | write!(f, "Rect {} -> {}", self.origin(), self.size()) 51 | } 52 | } 53 | 54 | impl Scalable for Rect { 55 | fn scale(&self, factor: f64) -> Self { 56 | Rect { 57 | left: self.left * factor, 58 | top: self.top * factor, 59 | width: self.width * factor, 60 | height: self.height * factor, 61 | } 62 | } 63 | } 64 | 65 | macro_rules! convert_rect_type { 66 | ($t1:ty, $t2:ty) => { 67 | impl Rect<$t1> { 68 | paste!{ 69 | pub fn [](&self) -> Rect<$t2> { 70 | Rect { 71 | left: self.left as $t2, 72 | top: self.top as $t2, 73 | width: self.width as $t2, 74 | height: self.height as $t2, 75 | } 76 | } 77 | } 78 | } 79 | } 80 | } 81 | 82 | convert_rect_type!(f64, i32); 83 | convert_rect_type!(f64, usize); 84 | convert_rect_type!(f64, u32); 85 | convert_rect_type!(u32, usize); 86 | convert_rect_type!(i32, usize); 87 | convert_rect_type!(i32, f64); 88 | convert_rect_type!(i32, u32); 89 | convert_rect_type!(usize, i32); 90 | -------------------------------------------------------------------------------- /yas-wutheringwaves/src/scanner/echo_scanner/echo_scanner_window_info.rs: -------------------------------------------------------------------------------- 1 | use yas::positioning::{Pos, Rect}; 2 | use yas_derive::YasWindowInfo; 3 | 4 | #[derive(YasWindowInfo, Debug, Clone)] 5 | pub struct EchoScannerWindowInfo { 6 | #[window_info(rename = "ww_echo_title_rect")] 7 | pub title_rect: Rect, 8 | 9 | #[window_info(rename = "ww_echo_main_stat1_name_rect")] 10 | pub main_stat1_name_rect: Rect, 11 | #[window_info(rename = "ww_echo_main_stat1_value_rect")] 12 | pub main_stat1_value_rect: Rect, 13 | #[window_info(rename = "ww_echo_main_stat2_name_rect")] 14 | pub main_stat2_name_rect: Rect, 15 | #[window_info(rename = "ww_echo_main_stat2_value_rect")] 16 | pub main_stat2_value_rect: Rect, 17 | 18 | // the sub stat name positions relative to window 19 | #[window_info(rename = "ww_echo_sub_stat0_name_rect")] 20 | pub sub_stat_name_1: Rect, 21 | #[window_info(rename = "ww_echo_sub_stat1_name_rect")] 22 | pub sub_stat_name_2: Rect, 23 | #[window_info(rename = "ww_echo_sub_stat2_name_rect")] 24 | pub sub_stat_name_3: Rect, 25 | #[window_info(rename = "ww_echo_sub_stat3_name_rect")] 26 | pub sub_stat_name_4: Rect, 27 | #[window_info(rename = "ww_echo_sub_stat4_name_rect")] 28 | pub sub_stat_name_5: Rect, 29 | 30 | // the sub stat value positions relative to window 31 | #[window_info(rename = "ww_echo_sub_stat0_value_rect")] 32 | pub sub_stat_value_1: Rect, 33 | #[window_info(rename = "ww_echo_sub_stat1_value_rect")] 34 | pub sub_stat_value_2: Rect, 35 | #[window_info(rename = "ww_echo_sub_stat2_value_rect")] 36 | pub sub_stat_value_3: Rect, 37 | #[window_info(rename = "ww_echo_sub_stat3_value_rect")] 38 | pub sub_stat_value_4: Rect, 39 | #[window_info(rename = "ww_echo_sub_stat4_value_rect")] 40 | pub sub_stat_value_5: Rect, 41 | 42 | #[window_info(rename = "ww_echo_level_rect")] 43 | pub level_rect: Rect, 44 | 45 | // #[window_info(rename = "ww_echo_equip_rect")] 46 | // pub equip_rect: Rect, 47 | 48 | #[window_info(rename = "ww_echo_item_count_rect")] 49 | pub item_count_rect: Rect, 50 | 51 | #[window_info(rename = "ww_echo_star_pos")] 52 | pub star_pos: Pos, 53 | 54 | // #[window_info(rename = "ww_echo_lock_pos")] 55 | // pub lock_pos: Pos, 56 | 57 | #[window_info(rename = "ww_repository_panel_rect")] 58 | pub panel_rect: Rect, 59 | 60 | #[window_info(rename = "ww_repository_item_col")] 61 | pub col: i32, 62 | } 63 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Build executables 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | workflow_dispatch: 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | runs-on: windows-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | with: 18 | fetch-depth: 0 # For $commitCount 19 | lfs: true 20 | 21 | # - uses: actions/cache@v4 22 | # with: 23 | # path: | 24 | # ~/.cargo/bin/ 25 | # ~/.cargo/registry/index/ 26 | # ~/.cargo/registry/cache/ 27 | # ~/.cargo/git/db/ 28 | # target/ 29 | # key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 30 | 31 | - name: Extract git-rev 32 | run: | 33 | $commitCount = git rev-list --count HEAD 34 | $shortHash = git rev-parse --short HEAD 35 | "GIT_REV=r$commitCount.$shortHash" | Out-File -FilePath $env:GITHUB_ENV -Append 36 | 37 | - name: Setup Toolchain 38 | run: rustup default nightly-msvc 39 | 40 | - name: Set version in Cargo.toml 41 | run: | 42 | $files = @( 43 | './Cargo.toml' 44 | './yas/Cargo.toml' 45 | './yas-genshin/Cargo.toml' 46 | './yas-starrail/Cargo.toml' 47 | './yas-application/Cargo.toml' 48 | ) 49 | [regex]$pattern = '(?<=version = ").*(?=")' 50 | foreach ($file in $files) { 51 | $pattern.Replace((Get-Content -Raw $file), "0.0.0-$env:GIT_REV", 1) | Out-File -FilePath $file 52 | } 53 | 54 | - name: Build (Release) 55 | run: cargo build --release 56 | 57 | - name: Rename Outputs 58 | run: | 59 | Move-Item ./target/release/yas_artifact.exe "yas_artifact_$env:GIT_REV.exe" 60 | Move-Item ./target/release/yas_relic.exe "yas_relic_$env:GIT_REV.exe" 61 | Move-Item ./target/release/yas.exe "yas_$env:GIT_REV.exe" 62 | 63 | - name: Upload yas_artifact 64 | uses: actions/upload-artifact@v4 65 | with: 66 | name: yas_artifact_${{ env.GIT_REV }} 67 | path: yas_artifact_${{ env.GIT_REV }}.exe 68 | 69 | - name: Upload yas_relic 70 | uses: actions/upload-artifact@v4 71 | with: 72 | name: yas_relic_${{ env.GIT_REV }} 73 | path: yas_relic_${{ env.GIT_REV }}.exe 74 | 75 | - name: Upload yas 76 | uses: actions/upload-artifact@v4 77 | with: 78 | name: yas_${{ env.GIT_REV }} 79 | path: yas_${{ env.GIT_REV }}.exe -------------------------------------------------------------------------------- /yas-genshin/src/export/artifact/csv.rs: -------------------------------------------------------------------------------- 1 | use serde::{Serialize, Serializer}; 2 | use crate::artifact::GenshinArtifact; 3 | 4 | pub struct GenshinArtifactCSVFormat<'a> { 5 | artifacts: &'a [GenshinArtifact], 6 | } 7 | 8 | /// CSV format: 9 | /// set name, slot, star, level, main stat name, main stat value, [sub state name, sub state value]*4, equip 10 | fn single_artifact_to_string(artifact: &GenshinArtifact) -> String { 11 | let mut s = String::new(); 12 | s = s + &artifact.set_name.to_string(); 13 | s = s + "," + &artifact.slot.to_string(); 14 | s = s + "," + &format!("{}", artifact.star); 15 | s = s + "," + &format!("{}", artifact.level); 16 | s = s + "," + &artifact.main_stat.name.to_string(); 17 | s = s + "," + &format!("{}", artifact.main_stat.value); 18 | if let Some(sub) = &artifact.sub_stat_1 { 19 | s = s + "," + &sub.name.to_string(); 20 | s = s + "," + &format!("{}", sub.value); 21 | } else { 22 | s += ",,"; 23 | } 24 | if let Some(sub) = &artifact.sub_stat_2 { 25 | s = s + "," + &sub.name.to_string(); 26 | s = s + "," + &format!("{}", sub.value); 27 | } else { 28 | s += ",,"; 29 | } 30 | if let Some(sub) = &artifact.sub_stat_3 { 31 | s = s + "," + &sub.name.to_string(); 32 | s = s + "," + &format!("{}", sub.value); 33 | } else { 34 | s += ",,"; 35 | } 36 | if let Some(sub) = &artifact.sub_stat_4 { 37 | s = s + "," + &sub.name.to_string(); 38 | s = s + "," + &format!("{}", sub.value); 39 | } else { 40 | s += ",,"; 41 | } 42 | if let Some(e) = &artifact.equip { 43 | s = s + "," + e; 44 | } else { 45 | s += "," 46 | } 47 | 48 | s 49 | } 50 | 51 | impl<'a> GenshinArtifactCSVFormat<'a> { 52 | pub fn new(artifacts: &'a [GenshinArtifact]) -> Self { 53 | Self { 54 | artifacts 55 | } 56 | } 57 | 58 | pub fn to_csv_string(&self) -> String { 59 | let header = "套装,部位,星级,等级,主词条名,主词条值,副词条名1,副词条值1,副词条名2,副词条值2,副词条名3,副词条值3,副词条名4,副词条值4,装备"; 60 | let mut result = String::from(header) + "\n"; 61 | 62 | for artifact in self.artifacts.iter() { 63 | let line = single_artifact_to_string(artifact); 64 | result = result + &line + "\n"; 65 | } 66 | 67 | result 68 | } 69 | } 70 | 71 | impl<'a> Serialize for GenshinArtifactCSVFormat<'a> { 72 | fn serialize(&self, serializer: S) -> Result where S: Serializer { 73 | let s = self.to_csv_string(); 74 | serializer.serialize_str(&s) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /yas-wutheringwaves/src/application/ww_echo_scanner.rs: -------------------------------------------------------------------------------- 1 | use anyhow::anyhow; 2 | use clap::{ArgMatches, Args, command}; 3 | use log::info; 4 | use yas::export::ExportAssets; 5 | use yas::game_info::{GameInfo, GameInfoBuilder}; 6 | use yas::window_info::{load_window_info_repo, WindowInfoRepository}; 7 | use crate::scanner::{WWEchoScanner, WWEchoScannerConfig}; 8 | use crate::scanner_controller::WWRepositoryLayoutConfig; 9 | use anyhow::Result; 10 | 11 | pub struct WWEchoScannerApplication { 12 | arg_matches: ArgMatches 13 | } 14 | 15 | impl WWEchoScannerApplication { 16 | pub fn new(args: ArgMatches) -> Self { 17 | Self { 18 | arg_matches: args 19 | } 20 | } 21 | 22 | pub fn build_command() -> clap::Command { 23 | let mut cmd = command!(); 24 | cmd = ::augment_args_for_update(cmd); 25 | cmd = ::augment_args_for_update(cmd); 26 | // cmd = ::augment_args_for_update(cmd); 27 | cmd 28 | } 29 | 30 | fn get_window_info_repository() -> WindowInfoRepository { 31 | load_window_info_repo!( 32 | "../../window_info/windows2560x1440.json" 33 | ) 34 | } 35 | 36 | fn get_game_info() -> anyhow::Result { 37 | let game_info = GameInfoBuilder::new() 38 | .add_local_window_name("鸣潮") 39 | .add_local_window_name("Wuthering Waves") 40 | // .add_cloud_window_name("云·星穹铁道") 41 | .build(); 42 | game_info 43 | } 44 | } 45 | 46 | impl WWEchoScannerApplication { 47 | pub fn run(&self) -> Result<()> { 48 | println!("START"); 49 | // Self::init(); 50 | let arg_matches = &self.arg_matches; 51 | let window_info_repository = Self::get_window_info_repository(); 52 | let game_info = Self::get_game_info()?; 53 | 54 | info!("window: {:?}", game_info.window); 55 | info!("ui: {:?}", game_info.ui); 56 | info!("cloud: {}", game_info.is_cloud); 57 | info!("resolution family: {:?}", game_info.resolution_family); 58 | 59 | #[cfg(target_os = "windows")] 60 | { 61 | // assure admin 62 | if !yas::utils::is_admin() { 63 | return Err(anyhow!("请使用管理员运行")); 64 | } 65 | } 66 | 67 | let mut scanner = WWEchoScanner::from_arg_matches( 68 | &window_info_repository, 69 | &arg_matches, 70 | game_info.clone() 71 | )?; 72 | 73 | let results = scanner.scan()?; 74 | 75 | for item in results.iter() { 76 | println!("{:?}", item); 77 | } 78 | 79 | Ok(()) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /yas-genshin/src/scanner/artifact_scanner/artifact_scanner_window_info.rs: -------------------------------------------------------------------------------- 1 | use yas::positioning::{Pos, Rect, Size}; 2 | 3 | #[derive(Clone, yas_derive::YasWindowInfo, Debug)] 4 | pub struct ArtifactScannerWindowInfo { 5 | /// the position of artifact title relative to window 6 | #[window_info(rename = "genshin_artifact_title_rect")] 7 | pub title_rect: Rect, 8 | 9 | /// the main stat name position of artifact relative to window 10 | #[window_info(rename = "genshin_artifact_main_stat_name_rect")] 11 | pub main_stat_name_rect: Rect, 12 | 13 | /// the main stat value position of artifact relative to window 14 | #[window_info(rename = "genshin_artifact_main_stat_value_rect")] 15 | pub main_stat_value_rect: Rect, 16 | 17 | /// the sub stats positions relative to window 18 | #[window_info(rename = "genshin_artifact_sub_stat1_rect")] 19 | pub sub_stat_1: Rect, 20 | #[window_info(rename = "genshin_artifact_sub_stat2_rect")] 21 | pub sub_stat_2: Rect, 22 | #[window_info(rename = "genshin_artifact_sub_stat3_rect")] 23 | pub sub_stat_3: Rect, 24 | #[window_info(rename = "genshin_artifact_sub_stat4_rect")] 25 | pub sub_stat_4: Rect, 26 | 27 | /// the level of the artifact relative to window 28 | #[window_info(rename = "genshin_artifact_level_rect")] 29 | pub level_rect: Rect, 30 | 31 | /// equip status of the artifact relative to window 32 | #[window_info(rename = "genshin_artifact_item_equip_rect")] 33 | pub item_equip_rect: Rect, 34 | 35 | /// the count of artifacts relative to window 36 | #[window_info(rename = "genshin_artifact_item_count_rect")] 37 | pub item_count_rect: Rect, 38 | 39 | /// the sample position of star, relative to window 40 | #[window_info(rename = "genshin_artifact_star_pos")] 41 | pub star_pos: Pos, 42 | 43 | /// the whole panel of the artifact, relative to window 44 | #[window_info(rename = "genshin_repository_panel_rect")] 45 | pub panel_rect: Rect, 46 | 47 | /// how many columns in this layout 48 | #[window_info(rename = "genshin_repository_item_col")] 49 | pub col: i32, 50 | 51 | /// how many rows in this layout 52 | #[window_info(rename = "genshin_repository_item_row")] 53 | pub row: i32, 54 | 55 | #[window_info(rename = "genshin_repository_item_gap_size")] 56 | pub item_gap_size: Size, 57 | 58 | #[window_info(rename = "genshin_repository_item_size")] 59 | pub item_size: Size, 60 | 61 | #[window_info(rename = "genshin_repository_scan_margin_pos")] 62 | pub scan_margin_pos: Pos, 63 | 64 | #[window_info(rename = "genshin_repository_lock_pos")] 65 | pub lock_pos: Pos, 66 | } 67 | -------------------------------------------------------------------------------- /yas/src/game_info/os/winodws.rs: -------------------------------------------------------------------------------- 1 | use std::io::stdin; 2 | use crate::game_info::{GameInfo, ResolutionFamily, UI, Platform}; 3 | use crate::utils; 4 | use anyhow::{Result, anyhow}; 5 | use windows_sys::Win32::Foundation::HWND; 6 | use windows_sys::Win32::UI::WindowsAndMessaging::*; 7 | 8 | fn is_window_cloud(title: &str) -> bool { 9 | title.starts_with("云") 10 | } 11 | 12 | fn get_window(window_names: &[&str]) -> Result<(HWND, bool)> { 13 | let handles = utils::iterate_window(); 14 | let mut viable_handles = Vec::new(); 15 | for hwnd in handles.iter() { 16 | let title = utils::get_window_title(*hwnd); 17 | if let Some(t) = title { 18 | let trimmed = t.trim(); 19 | 20 | for name in window_names.iter() { 21 | if trimmed == *name { 22 | // return Ok((*hwnd, false)); 23 | viable_handles.push((*hwnd, String::from(trimmed))); 24 | } 25 | } 26 | } 27 | } 28 | 29 | // cloud games 30 | // let cloud_game_names = [""] 31 | // for name in get_cloud_window_name() { 32 | // let hwnd = utils::find_window_local(name); 33 | // if let Ok(hwnd) = hwnd { 34 | // return (hwnd, true); 35 | // } 36 | // } 37 | 38 | if viable_handles.len() == 1 { 39 | return Ok((viable_handles[0].0, is_window_cloud(&viable_handles[0].1))); 40 | } else if viable_handles.len() == 0 { 41 | return Err(anyhow!("未找到游戏窗口,请确认{:?}已经开启", window_names)); 42 | } 43 | 44 | println!("找到多个符合名称的窗口,请手动选择窗口:"); 45 | for (i, (hwnd, title)) in viable_handles.iter().enumerate() { 46 | println!("{}: {}", i, title); 47 | } 48 | let mut index = String::new(); 49 | stdin().read_line(&mut index); 50 | 51 | let idx = index.trim().parse::()?; 52 | if idx >= 0 && idx < viable_handles.len() { 53 | let is_cloud = is_window_cloud(&viable_handles[idx].1); 54 | Ok((viable_handles[idx].0, is_cloud)) 55 | } else { 56 | Err(anyhow!("索引{}超出范围", idx)) 57 | } 58 | } 59 | 60 | pub fn get_game_info(window_names: &[&str]) -> Result { 61 | utils::set_dpi_awareness(); 62 | 63 | let (hwnd, is_cloud) = get_window(window_names)?; 64 | 65 | unsafe { 66 | ShowWindow(hwnd, SW_RESTORE); 67 | } 68 | 69 | unsafe { 70 | SetForegroundWindow(hwnd); 71 | } 72 | 73 | utils::sleep(1000); 74 | 75 | let rect = utils::get_client_rect(hwnd)?; 76 | let resolution_family = ResolutionFamily::new(rect.to_rect_usize().size()); 77 | if resolution_family.is_none() { 78 | return Err(anyhow!("Resolution not supported: {}x{}", rect.width, rect.height)); 79 | } 80 | 81 | Ok(GameInfo { 82 | window: rect, 83 | resolution_family: resolution_family.unwrap(), 84 | is_cloud, 85 | ui: UI::Desktop, 86 | platform: Platform::Windows 87 | }) 88 | } 89 | -------------------------------------------------------------------------------- /yas/src/window_info/window_info_type.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use crate::positioning::{Pos, Rect, Scalable, Size}; 3 | use anyhow::anyhow; 4 | 5 | #[derive(Copy, Clone, Debug, Serialize, Deserialize)] 6 | pub enum WindowInfoType { 7 | Rect(Rect), 8 | Pos(Pos), 9 | Size(Size), 10 | Float(f64), 11 | /// when window size scales, these amount will not scale 12 | InvariantInt(i32), 13 | InvariantFloat(f64), 14 | } 15 | 16 | // due to orphan rule, we implement TryInto instead of TryFrom 17 | impl TryInto for WindowInfoType { 18 | type Error = anyhow::Error; 19 | 20 | fn try_into(self) -> Result { 21 | match self { 22 | WindowInfoType::InvariantInt(v) => Ok(v), 23 | _ => Err(anyhow!(String::from("not an i32 type"))) 24 | } 25 | } 26 | } 27 | 28 | impl TryInto> for WindowInfoType { 29 | type Error = anyhow::Error; 30 | 31 | fn try_into(self) -> Result, Self::Error> { 32 | match self { 33 | WindowInfoType::Rect(rect) => Ok(rect), 34 | _ => Err(anyhow!(String::from("not a rect type"))), 35 | } 36 | } 37 | } 38 | 39 | impl TryInto> for WindowInfoType { 40 | type Error = anyhow::Error; 41 | 42 | fn try_into(self) -> Result, Self::Error> { 43 | match self { 44 | WindowInfoType::Pos(pos) => Ok(pos), 45 | _ => Err(anyhow!(String::from("not a pos type"))), 46 | } 47 | } 48 | } 49 | 50 | impl TryInto for WindowInfoType { 51 | type Error = anyhow::Error; 52 | 53 | fn try_into(self) -> std::result::Result { 54 | match self { 55 | WindowInfoType::Float(f) => Ok(f), 56 | WindowInfoType::InvariantFloat(f) => Ok(f), 57 | _ => Err(anyhow!(String::from("not a float type"))), 58 | } 59 | } 60 | } 61 | 62 | impl TryInto> for WindowInfoType { 63 | type Error = anyhow::Error; 64 | 65 | fn try_into(self) -> Result, Self::Error> { 66 | match self { 67 | WindowInfoType::Size(size) => Ok(size), 68 | _ => Err(anyhow!(String::from("not a size type"))), 69 | } 70 | } 71 | } 72 | 73 | impl Scalable for WindowInfoType { 74 | fn scale(&self, factor: f64) -> Self { 75 | let result = match *self { 76 | WindowInfoType::Rect(rect) => WindowInfoType::Rect(rect.scale(factor)), 77 | WindowInfoType::Pos(pos) => WindowInfoType::Pos(pos.scale(factor)), 78 | WindowInfoType::Size(size) => WindowInfoType::Size(size.scale(factor)), 79 | WindowInfoType::Float(v) => WindowInfoType::Float(v.scale(factor)), 80 | WindowInfoType::InvariantInt(v) => WindowInfoType::InvariantInt(v), 81 | WindowInfoType::InvariantFloat(v) => WindowInfoType::InvariantFloat(v), 82 | }; 83 | result 84 | } 85 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | # Yas 4 | 5 | Yet Another Scanner 6 | 又一个原神圣遗物导出器 7 | 8 |
9 | 10 | ## 介绍 11 | 12 | 基于 SVTR(基本上是 MobileNetV3_Small + Transformer)字符识别模型,使用原神字体对原神中会出现的字符串进行训练,达到更高的速度和更精确的结果。相比 CRNN(旧版Yas使用的模型),SVTR 可以达到更小的体积及更好的识别率。导出结果可以导入分析工具(例如 [莫娜占卜铺](https://mona-uranai.com/) )进行配装或者其他计算。由于使用了 [Rust](https://www.rust-lang.org/) 进行编写,运行效率和文件体积都得到了很大的提升。 13 | 14 | ### 相关资料 15 | 16 | - [MobileNetV3](https://arxiv.org/pdf/1905.02244.pdf) 17 | - [CRNN](https://arxiv.org/pdf/1507.05717.pdf) 18 | - [SVTR](https://arxiv.org/pdf/2205.00159.pdf) 19 | - [Transformer](https://proceedings.neurips.cc/paper/2017/file/3f5ee243547dee91fbd053c1c4a845aa-Paper.pdf) 20 | 21 | ### 识别模型 22 | 23 | SVTR 原文使用了多个 Local/Global Mixing,其中 Global Mixing 就是 Transformer 层,而根据*PaddleOCR*的代码,其 SVTR 识别模型也并未完全遵照 SVTR 原模型,而是骨干网络 + Transformer 的结构。*Yas*同样采用 PaddleOCR 的做法,使用 MobileNetV3_Small + Global Mixing,相当于将 CRNN 的 RNN 替换为 Transformer。由于训练集更加定制化,模型输入张量更小,网络结构简单,Yas模型相比PaddleOCR的V4轻量级模型,推理速度提升了6倍(仅在作者个人电脑上测试)。 24 | 25 | ## 使用 26 | `yas.exe`把不同游戏的功能都集成到了一个exe中,因此需要使用命令行指定游戏,例如: 27 | ```shell 28 | yas.exe genshin 29 | ``` 30 | 运行`yas.exe --help`查看所有指令,运行`yas.exe help genshin`查看游戏特定的指令。 31 | 32 | 也可以下载特定游戏的版本,例如`yas_artifact.exe`只能用于扫描原神的圣遗物。 33 | 34 | ### Windows 35 | 36 | - 打开原神/星铁,并切换到背包页面,将背包拉到最上面 37 | - 如果是`yas.exe`,需要用命令行运行`yas.exe genshin`,如果是`yas_artifact.exe`,直接运行即可 38 | - 扫描过程中,鼠标右键终止 39 | 40 | ### Linux 41 | - 还没有经过详细测试 42 | - 首先请确保自己在 x11 下或者 GNOME/Wayland 下(其他 wayland de 下[会有很坏的性能](https://github.com/poly000/screenshots-rs/blob/d96dff76c5f5cbd849d80451f0df8f415f8e5f4b/src/linux/wayland_screenshot.rs#L109)) 43 | - 用 wine 窗口化运行原神(或者全屏+虚拟桌面),打开圣遗物界面,拉到最顶 44 | - 启动 yas 45 | - Alt+Tab 切换到原神窗口,并且在鼠标变为十字后点击一下(还没做窗口聚焦),注意保证原神窗口整体在屏幕内 46 | - 等待扫描结束。 47 | 48 | ### 注意 49 | 50 | - 默认 4 星以下圣遗物不扫描 51 | - 不是所有窗口比例都支持,推荐 16:9 的分辨率(如 1600x900, 1920x1080, 3840x2160) 52 | - 扫描过程中不要对鼠标做任何操作 53 | - 当前仅支持中文环境,若默认系统为非中文,请前往游戏设置界面修改 Language 为“简体中文”,否则无法读取原神窗口 54 | - 当前仅支持键鼠作为控制设备,暂不支持手柄。 55 | 56 | ### 命令行使用 57 | 58 | 假设你知道如何使用命令行工具。 59 | 60 | 61 | 查看选项: 62 | ```shell 63 | yas --help 64 | yas help genshin 65 | yas help starrail 66 | ``` 67 | 68 | 只扫描五星圣遗物: 69 | ```shell 70 | yas genshin --min-star=5 71 | ``` 72 | 73 | 只扫描一行: 74 | ```shell 75 | yas genshin --max-row=1 76 | ``` 77 | 78 | ## 编译 79 | 80 | 在构建前,请确保安装`Git LFS`,并运行`git lfs pull`。否则[yas 在运行时会使用错误的模型](https://github.com/wormtql/yas/pull/102#issuecomment-1375503803)。 81 | 82 | ```shell 83 | # Linux 下需要首先安装 rustup 以及 mingw-w64 ,然后再安装对应的 rust target, 84 | # 构建到Linux需要 `libxdo` 和 `libxcb` 85 | rustup default stable 86 | rustup target add x86_64-pc-windows-gnu 87 | cargo build --release --locked --target=x86_64-pc-windows-gnu 88 | ``` 89 | 90 | 如果使用 macOS,为了保证正常捕捉窗口,需要在编译后运行 `codesign.sh` 对二进制文件进行签名 91 | 92 | ## 训练 93 | 94 | [yas-train](https://github.com/wormtql/yas-train) 95 | 96 | ## 反馈 97 | 98 | - Issue 99 | - QQ 群:801106595 100 | -------------------------------------------------------------------------------- /yas-starrail/src/scanner/relic_scanner/match_colors.rs: -------------------------------------------------------------------------------- 1 | use image::Rgb; 2 | 3 | pub struct MatchColors { 4 | pub match_colors_star: [Rgb; 5], 5 | pub match_colors_lock: [Rgb; 3], 6 | pub match_colors_discard: [Rgb; 3], 7 | pub match_colors_equipper: [(&'static str, Rgb); 50], 8 | } 9 | 10 | pub const MATCH_COLORS: MatchColors = MatchColors { 11 | match_colors_star: [ 12 | Rgb([144, 144, 154]), // 1 13 | Rgb([75, 146, 146]), // 2 14 | Rgb([96, 142, 197]), // 3 15 | Rgb([157, 117, 206]), // 4 16 | Rgb([193, 158, 112]), // 5 17 | ], 18 | match_colors_lock: [ 19 | Rgb([18, 18, 18]), // locked 20 | Rgb([249, 249, 249]), // unlocked 21 | Rgb([116, 108, 99]), // discard 22 | ], 23 | match_colors_discard: [ 24 | Rgb([235, 77, 61]), // discard 25 | Rgb([249, 249, 249]), // not discard 26 | Rgb([115, 108, 98]), // locked 27 | ], 28 | match_colors_equipper: [ 29 | ("Acheron", Rgb([249, 246, 235])), 30 | ("Argenti", Rgb([216, 174, 161])), 31 | ("Arlan", Rgb([146, 134, 124])), 32 | ("Asta", Rgb([188, 130, 117])), 33 | ("Aventurine", Rgb([221, 206, 189])), 34 | ("Bailu", Rgb([160, 127, 174])), 35 | ("BlackSwan", Rgb([252, 242, 239])), 36 | ("Blade", Rgb([191, 162, 162])), 37 | ("Boothill", Rgb([127, 95, 94])), 38 | ("Bronya", Rgb([83, 66, 83])), 39 | ("Clara", Rgb([181, 107, 129])), 40 | ("DanHeng", Rgb([124, 100, 100])), 41 | ("DanHengImbibitorLunae", Rgb([181, 169, 163])), 42 | ("DrRatio", Rgb([134, 120, 143])), 43 | ("Feixiao", Rgb([69, 47, 47])), 44 | ("Firefly", Rgb([100, 100, 120])), 45 | ("FuXuan", Rgb([231, 166, 145])), 46 | ("Gallagher", Rgb([117, 77, 60])), 47 | ("Gepard", Rgb([192, 199, 223])), 48 | ("Guinaifen", Rgb([219, 137, 111])), 49 | ("Hanya", Rgb([247, 238, 232])), 50 | ("Herta", Rgb([246, 239, 227])), 51 | ("Himeko", Rgb([177, 92, 85])), 52 | ("Hook", Rgb([190, 161, 86])), 53 | ("Huohuo", Rgb([230, 250, 250])), 54 | ("Jingliu", Rgb([193, 194, 218])), 55 | ("JingYuan", Rgb([169, 154, 147])), 56 | ("Kafka", Rgb([126, 50, 80])), 57 | ("Luka", Rgb([218, 198, 183])), 58 | ("Luocha", Rgb([191, 160, 116])), 59 | ("Lynx", Rgb([247, 213, 197])), 60 | ("Misha", Rgb([234, 215, 213])), 61 | ("Moze", Rgb([243, 239, 234])), 62 | ("Natasha", Rgb([238, 208, 196])), 63 | ("Pela", Rgb([241, 217, 217])), 64 | ("Qingque", Rgb([18, 27, 11])), 65 | ("Robin", Rgb([247, 236, 232])), 66 | ("RuanMei", Rgb([129, 101, 101])), 67 | ("Sampo", Rgb([241, 217, 213])), 68 | ("Seele", Rgb([91, 65, 111])), 69 | ("Serval", Rgb([158, 141, 150])), 70 | ("SilverWolf", Rgb([222, 210, 210])), 71 | ("Sparkle", Rgb([227, 164, 196])), 72 | ("Sushang", Rgb([101, 65, 58])), 73 | ("Tingyun", Rgb([127, 116, 57])), 74 | ("TopazNumby", Rgb([254, 250, 246])), 75 | ("Welt", Rgb([158, 114, 99])), 76 | ("Xueyi", Rgb([250, 242, 230])), 77 | ("Yanqing", Rgb([255, 242, 232])), 78 | ("Yukong", Rgb([174, 167, 174])) 79 | ] 80 | }; 81 | -------------------------------------------------------------------------------- /yas-genshin/window_info/windows3440x1440.json: -------------------------------------------------------------------------------- 1 | { 2 | "current_resolution": { 3 | "width": 3440, 4 | "height": 1440 5 | }, 6 | "platform": "Windows", 7 | "ui": "Desktop", 8 | "data": { 9 | "genshin_repository_panel_rect": { 10 | "Rect": { 11 | "top": 160, 12 | "left": 2528, 13 | "height": 1120, 14 | "width": 657 15 | } 16 | }, 17 | "genshin_repository_flag_pos": { 18 | "Pos": { 19 | "x": 580, 20 | "y": 145 21 | } 22 | }, 23 | "genshin_repository_item_gap_size": { 24 | "Size": { 25 | "width": 32, 26 | "height": 31 27 | } 28 | }, 29 | "genshin_repository_item_size": { 30 | "Size": { 31 | "width": 164, 32 | "height": 204 33 | } 34 | }, 35 | "genshin_repository_scan_margin_pos": { 36 | "Pos": { 37 | "x": 305, 38 | "y": 161 39 | } 40 | }, 41 | "genshin_repository_pool_rect": { 42 | "Rect": { 43 | "top": 170, 44 | "left": 2610, 45 | "height": 730, 46 | "width": 30 47 | } 48 | }, 49 | "genshin_repository_item_row": { 50 | "InvariantInt": 5 51 | }, 52 | "genshin_repository_item_col": { 53 | "InvariantInt": 11 54 | }, 55 | "genshin_repository_lock_pos": { 56 | "Pos": { 57 | "x": 15, 58 | "y": 20 59 | } 60 | }, 61 | "genshin_artifact_item_count_rect": { 62 | "Rect": { 63 | "top": 50, 64 | "left": 2750, 65 | "height": 35, 66 | "width": 435 67 | } 68 | }, 69 | "genshin_artifact_star_pos": { 70 | "Pos": { 71 | "x": 3130, 72 | "y": 200 73 | } 74 | }, 75 | "genshin_artifact_lock_pos": { 76 | "Pos": { 77 | "x": 3114, 78 | "y": 592 79 | } 80 | }, 81 | "genshin_artifact_main_stat_name_rect": { 82 | "Rect": { 83 | "top": 360, 84 | "left": 2560, 85 | "height": 40, 86 | "width": 290 87 | } 88 | }, 89 | "genshin_artifact_main_stat_value_rect": { 90 | "Rect": { 91 | "top": 400, 92 | "left": 2560, 93 | "height": 60, 94 | "width": 290 95 | } 96 | }, 97 | "genshin_artifact_level_rect": { 98 | "Rect": { 99 | "top": 575, 100 | "left": 2568, 101 | "height": 30, 102 | "width": 72 103 | } 104 | }, 105 | "genshin_artifact_item_equip_rect": { 106 | "Rect": { 107 | "top": 1220, 108 | "left": 3140, 109 | "height": 40, 110 | "width": 2490 111 | } 112 | }, 113 | "genshin_artifact_sub_stat1_rect": { 114 | "Rect": { 115 | "top": 640, 116 | "left": 2590, 117 | "height": 40, 118 | "width": 490 119 | } 120 | }, 121 | "genshin_artifact_sub_stat2_rect": { 122 | "Rect": { 123 | "top": 690, 124 | "left": 2590, 125 | "height": 40, 126 | "width": 490 127 | } 128 | }, 129 | "genshin_artifact_sub_stat3_rect": { 130 | "Rect": { 131 | "top": 742, 132 | "left": 2590, 133 | "height": 40, 134 | "width": 490 135 | } 136 | }, 137 | "genshin_artifact_sub_stat4_rect": { 138 | "Rect": { 139 | "top": 795, 140 | "left": 2590, 141 | "height": 40, 142 | "width": 490 143 | } 144 | }, 145 | "genshin_artifact_title_rect": { 146 | "Rect": { 147 | "top": 170, 148 | "left": 2560, 149 | "height": 50, 150 | "width": 580 151 | } 152 | } 153 | } 154 | } -------------------------------------------------------------------------------- /yas-starrail/src/application/relic_scanner.rs: -------------------------------------------------------------------------------- 1 | use clap::{command, ArgMatches, Args}; 2 | use yas::game_info::{GameInfo, GameInfoBuilder}; 3 | use yas::window_info::{load_window_info_repo, WindowInfoRepository}; 4 | use crate::export::{ExportRelicConfig, StarRailRelicExporter}; 5 | use crate::scanner::relic_scanner::{StarRailRelicScanner, StarRailRelicScannerConfig}; 6 | use crate::scanner_controller::repository_layout::StarRailRepositoryScannerLogicConfig; 7 | use anyhow::{anyhow, Result}; 8 | use log::info; 9 | use yas::export::{AssetEmitter, ExportAssets}; 10 | use crate::relic::StarRailRelic; 11 | 12 | pub struct RelicScannerApplication { 13 | arg_matches: ArgMatches, 14 | } 15 | 16 | impl RelicScannerApplication { 17 | pub fn new(args: ArgMatches) -> Self { 18 | RelicScannerApplication { 19 | arg_matches: args 20 | } 21 | } 22 | 23 | pub fn build_command() -> clap::Command { 24 | let mut cmd = command!(); 25 | cmd = ::augment_args_for_update(cmd); 26 | cmd = ::augment_args_for_update(cmd); 27 | cmd = ::augment_args_for_update(cmd); 28 | cmd 29 | } 30 | 31 | fn get_window_info_repository() -> WindowInfoRepository { 32 | load_window_info_repo!( 33 | "../../window_info/windows1920x1080.json" 34 | ) 35 | } 36 | 37 | // fn init() { 38 | // env_logger::Builder::new() 39 | // .filter_level(log::LevelFilter::Info) 40 | // .init(); 41 | // } 42 | 43 | fn get_game_info() -> Result { 44 | let game_info = GameInfoBuilder::new() 45 | .add_local_window_name("崩坏:星穹铁道") 46 | .add_local_window_name("Honkai: Star Rail") 47 | .add_cloud_window_name("云·星穹铁道") 48 | .build(); 49 | game_info 50 | } 51 | } 52 | 53 | impl RelicScannerApplication { 54 | pub fn run(&self) -> Result<()> { 55 | // Self::init(); 56 | let arg_matches = &self.arg_matches; 57 | let window_info_repository = Self::get_window_info_repository(); 58 | let game_info = Self::get_game_info()?; 59 | 60 | info!("window: {:?}", game_info.window); 61 | info!("ui: {:?}", game_info.ui); 62 | info!("cloud: {}", game_info.is_cloud); 63 | info!("resolution family: {:?}", game_info.resolution_family); 64 | 65 | #[cfg(target_os = "windows")] 66 | { 67 | // assure admin 68 | if !yas::utils::is_admin() { 69 | return Err(anyhow!("请使用管理员运行")); 70 | } 71 | } 72 | 73 | let mut scanner = StarRailRelicScanner::from_arg_matches( 74 | &window_info_repository, 75 | &arg_matches, 76 | game_info.clone() 77 | )?; 78 | 79 | let results = scanner.scan()?; 80 | let starrail_relics = results.iter() 81 | .map(|x| StarRailRelic::try_from(x)) 82 | .filter(|x| x.is_ok()) 83 | .map(|x| x.unwrap()) 84 | .collect::>(); 85 | let exporter = StarRailRelicExporter::new(&arg_matches, &starrail_relics)?; 86 | let mut export_assets = ExportAssets::new(); 87 | exporter.emit(&mut export_assets); 88 | 89 | let stats = export_assets.save(); 90 | info!("保存结果:"); 91 | let table = format!("{}", stats); 92 | // print multiline 93 | for line in table.lines() { 94 | info!("{}", line); 95 | } 96 | info!("Yas 识别结束,共识别到 {} 件圣遗物。", results.len()); 97 | 98 | Ok(()) 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /yas/src/window_info/window_info_repository.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use serde::{Deserialize, Serialize}; 4 | use crate::game_info::{Platform, UI}; 5 | use crate::positioning::{Pos, Scalable, Size}; 6 | 7 | use crate::window_info::WindowInfoType; 8 | 9 | /// Maps a window-info-key to a list of entries 10 | /// where entries consist of a size where the value is recorded, and accordingly a value 11 | #[derive(Serialize, Deserialize, Clone, Debug)] 12 | pub struct WindowInfoRepository { 13 | /// window info key -> (window size, ui, platform) 14 | pub data: HashMap, UI, Platform), WindowInfoType>>, 15 | } 16 | 17 | impl WindowInfoRepository { 18 | pub fn new() -> WindowInfoRepository { 19 | WindowInfoRepository { 20 | data: HashMap::new(), 21 | } 22 | } 23 | 24 | pub fn add(&mut self, name: &str, size: Size, ui: UI, platform: Platform, value: WindowInfoType) { 25 | self.data 26 | .entry(String::from(name)) 27 | .or_insert(HashMap::new()) 28 | .insert((size, ui, platform), value); 29 | } 30 | 31 | pub fn add_pos(&mut self, name: &str, size: Size, ui: UI, platform: Platform, value: Pos) { 32 | self.data 33 | .entry(String::from(name)) 34 | .or_insert(HashMap::new()) 35 | .insert((size, ui, platform), WindowInfoType::Pos(value)); 36 | } 37 | 38 | pub fn merge_inplace(&mut self, other: &WindowInfoRepository) { 39 | for (key, data) in other.data.iter() { 40 | if self.data.contains_key(key) { 41 | for (resolution, value) in data.iter() { 42 | self.data.get_mut(key).unwrap().insert(resolution.clone(), value.clone()); 43 | } 44 | } else { 45 | self.data.insert(key.clone(), data.clone()); 46 | } 47 | } 48 | } 49 | 50 | pub fn merge(&self, other: &WindowInfoRepository) -> WindowInfoRepository { 51 | let mut result = self.clone(); 52 | result.merge_inplace(other); 53 | result 54 | } 55 | 56 | /// Get window info by name and size 57 | /// if name or resolution does not exist, then return None 58 | pub fn get_exact(&self, name: &str, window_size: Size, ui: UI, platform: Platform) -> Option where WindowInfoType: TryInto { 59 | if self.data.contains_key(name) && 60 | self.data[name].contains_key(&(window_size, ui, platform)) { 61 | return self.data[name][&(window_size, ui, platform)].try_into().ok(); 62 | } 63 | 64 | None 65 | } 66 | 67 | /// Get window info by name and size 68 | /// if window size does not exists exactly, this function will search for the same resolution family and scale the result 69 | pub fn get_auto_scale(&self, name: &str, window_size: Size, ui: UI, platform: Platform) -> Option where WindowInfoType: TryInto { 70 | if self.data.contains_key(name) { 71 | if self.data[name].contains_key(&(window_size, ui, platform)) { 72 | return self.data[name][&(window_size, ui, platform)].try_into().ok(); 73 | } else { 74 | // todo find a biggest size which can be scaled, this will reduce error 75 | // find if a resolution can be scaled 76 | for (k, value) in self.data[name].iter() { 77 | let size = &k.0; 78 | if size.width * window_size.height == size.height * window_size.width 79 | && k.1 == ui && k.2 == platform 80 | { 81 | let factor: f64 = window_size.width as f64 / size.width as f64; 82 | return value.scale(factor).try_into().ok(); 83 | } 84 | } 85 | } 86 | } 87 | 88 | None 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /yas-genshin/window_info/windows2100x900.json: -------------------------------------------------------------------------------- 1 | { 2 | "current_resolution": { 3 | "width": 2100, 4 | "height": 900 5 | }, 6 | "platform": "Windows", 7 | "ui": "Desktop", 8 | "data": { 9 | "genshin_artifact_offset": { 10 | "Size": { 11 | "width": 0, 12 | "height": 39.0 13 | } 14 | }, 15 | "genshin_repository_panel_rect": { 16 | "Rect": { 17 | "top": 100, 18 | "left": 1531, 19 | "height": 700, 20 | "width": 410 21 | } 22 | }, 23 | "genshin_repository_flag_pos": { 24 | "Pos": { 25 | "x": 340, 26 | "y": 89.8 27 | } 28 | }, 29 | "genshin_repository_item_gap_size": { 30 | "Size": { 31 | "width": 20, 32 | "height": 20 33 | } 34 | }, 35 | "genshin_repository_item_size": { 36 | "Size": { 37 | "width": 102, 38 | "height": 126 39 | } 40 | }, 41 | "genshin_repository_scan_margin_pos": { 42 | "Pos": { 43 | "x": 166, 44 | "y": 101 45 | } 46 | }, 47 | "genshin_repository_pool_rect": { 48 | "Rect": { 49 | "top": 118.2, 50 | "left": 1584, 51 | "height": 392.1, 52 | "width": 15 53 | } 54 | }, 55 | "genshin_repository_item_row": { 56 | "InvariantInt": 5 57 | }, 58 | "genshin_repository_item_col": { 59 | "InvariantInt": 11 60 | }, 61 | "genshin_repository_lock_pos": { 62 | "Pos": { 63 | "x": 10, 64 | "y": 12 65 | } 66 | }, 67 | "genshin_artifact_item_count_rect": { 68 | "Rect": { 69 | "top": 27.1, 70 | "left": 1785, 71 | "height": 25.8, 72 | "width": 160 73 | } 74 | }, 75 | "genshin_artifact_star_pos": { 76 | "Pos": { 77 | "x": 1900, 78 | "y": 123.9 79 | } 80 | }, 81 | "genshin_artifact_lock_pos": { 82 | "Pos": { 83 | "x": 1896, 84 | "y": 371 85 | } 86 | }, 87 | "genshin_artifact_main_stat_name_rect": { 88 | "Rect": { 89 | "top": 224.3, 90 | "left": 1550, 91 | "height": 23.7, 92 | "width": 140 93 | } 94 | }, 95 | "genshin_artifact_main_stat_value_rect": { 96 | "Rect": { 97 | "top": 248.4, 98 | "left": 1550, 99 | "height": 38.4, 100 | "width": 140 101 | } 102 | }, 103 | "genshin_artifact_level_rect": { 104 | "Rect": { 105 | "top": 360, 106 | "left": 1557, 107 | "height": 18, 108 | "width": 43 109 | } 110 | }, 111 | "genshin_artifact_item_equip_rect": { 112 | "Rect": { 113 | "top": 762.6, 114 | "left": 1598, 115 | "height": 25.2, 116 | "width": 252 117 | } 118 | }, 119 | "genshin_artifact_sub_stat1_rect": { 120 | "Rect": { 121 | "top": 398.1, 122 | "left": 1570, 123 | "height": 29.2, 124 | "width": 210 125 | } 126 | }, 127 | "genshin_artifact_sub_stat2_rect": { 128 | "Rect": { 129 | "top": 427.3, 130 | "left": 1570, 131 | "height": 30.9, 132 | "width": 210 133 | } 134 | }, 135 | "genshin_artifact_sub_stat3_rect": { 136 | "Rect": { 137 | "top": 458.2, 138 | "left": 1570, 139 | "height": 32.7, 140 | "width": 210 141 | } 142 | }, 143 | "genshin_artifact_sub_stat4_rect": { 144 | "Rect": { 145 | "top": 490.9, 146 | "left": 1570, 147 | "height": 32.1, 148 | "width": 210 149 | } 150 | }, 151 | "genshin_artifact_title_rect": { 152 | "Rect": { 153 | "top": 106.6, 154 | "left": 1550, 155 | "height": 33, 156 | "width": 150 157 | } 158 | } 159 | } 160 | } -------------------------------------------------------------------------------- /yas-genshin/src/application/artifact_scanner.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use anyhow::anyhow; 3 | use clap::{command, ArgMatches, Args}; 4 | use log::info; 5 | 6 | use yas::export::{AssetEmitter, ExportAssets}; 7 | use yas::game_info::{GameInfo, GameInfoBuilder}; 8 | use yas::window_info::{load_window_info_repo, WindowInfoRepository}; 9 | 10 | use crate::artifact::GenshinArtifact; 11 | use crate::export::artifact::{ExportArtifactConfig, GenshinArtifactExporter}; 12 | use crate::scanner::{GenshinArtifactScanner, GenshinArtifactScannerConfig}; 13 | use crate::scanner_controller::repository_layout::GenshinRepositoryScannerLogicConfig; 14 | 15 | pub struct ArtifactScannerApplication { 16 | arg_matches: ArgMatches, 17 | } 18 | 19 | impl ArtifactScannerApplication { 20 | pub fn new(matches: ArgMatches) -> Self { 21 | ArtifactScannerApplication { 22 | arg_matches: matches 23 | } 24 | } 25 | 26 | pub fn build_command() -> clap::Command { 27 | let mut cmd = command!(); 28 | cmd = ::augment_args_for_update(cmd); 29 | cmd = ::augment_args_for_update(cmd); 30 | cmd = ::augment_args_for_update(cmd); 31 | cmd 32 | } 33 | 34 | fn get_window_info_repository() -> WindowInfoRepository { 35 | load_window_info_repo!( 36 | "../../window_info/windows1600x900.json", 37 | "../../window_info/windows1280x960.json", 38 | "../../window_info/windows1440x900.json", 39 | "../../window_info/windows2100x900.json", 40 | "../../window_info/windows3440x1440.json", 41 | ) 42 | } 43 | 44 | // fn init() { 45 | // env_logger::Builder::new() 46 | // .filter_level(log::LevelFilter::Info) 47 | // .init(); 48 | // } 49 | 50 | fn get_game_info() -> Result { 51 | let game_info = GameInfoBuilder::new() 52 | .add_local_window_name("原神") 53 | .add_local_window_name("Genshin Impact") 54 | .add_cloud_window_name("云·原神") 55 | .build(); 56 | game_info 57 | } 58 | } 59 | 60 | impl ArtifactScannerApplication { 61 | pub fn run(&self) -> Result<()> { 62 | let arg_matches = &self.arg_matches; 63 | let window_info_repository = Self::get_window_info_repository(); 64 | let game_info = Self::get_game_info()?; 65 | 66 | info!("window: {:?}", game_info.window); 67 | info!("ui: {:?}", game_info.ui); 68 | info!("cloud: {}", game_info.is_cloud); 69 | info!("resolution family: {:?}", game_info.resolution_family); 70 | 71 | #[cfg(target_os = "windows")] 72 | { 73 | // assure admin 74 | if !yas::utils::is_admin() { 75 | return Err(anyhow!("请使用管理员运行")); 76 | } 77 | } 78 | 79 | let mut scanner = GenshinArtifactScanner::from_arg_matches( 80 | &window_info_repository, 81 | arg_matches, 82 | game_info.clone() 83 | )?; 84 | 85 | let result = scanner.scan()?; 86 | let artifacts = result 87 | .iter() 88 | .flat_map(GenshinArtifact::try_from) 89 | .collect::>(); 90 | 91 | let exporter = GenshinArtifactExporter::new(arg_matches, &artifacts)?; 92 | let mut export_assets = ExportAssets::new(); 93 | exporter.emit(&mut export_assets); 94 | 95 | let stats = export_assets.save(); 96 | info!("保存结果:"); 97 | let table = format!("{}", stats); 98 | // print multiline 99 | for line in table.lines() { 100 | info!("{}", line); 101 | } 102 | info!("Yas 识别结束,共识别到 {} 件圣遗物。", result.len()); 103 | 104 | Ok(()) 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /yas-genshin/window_info/windows1280x960.json: -------------------------------------------------------------------------------- 1 | { 2 | "current_resolution": { 3 | "width": 1280, 4 | "height": 960 5 | }, 6 | "platform": "Windows", 7 | "ui": "Desktop", 8 | "data": { 9 | "genshin_artifact_offset": { 10 | "Size": { 11 | "width": 0, 12 | "height": 39.0 13 | } 14 | }, 15 | "genshin_repository_panel_rect": { 16 | "Rect": { 17 | "top": 80, 18 | "left": 872, 19 | "height": 800, 20 | "width": 328 21 | } 22 | }, 23 | "genshin_repository_flag_pos": { 24 | "Pos": { 25 | "x": 218.1, 26 | "y": 72.1 27 | } 28 | }, 29 | "genshin_repository_item_gap_size": { 30 | "Size": { 31 | "width": 15, 32 | "height": 15 33 | } 34 | }, 35 | "genshin_repository_item_size": { 36 | "Size": { 37 | "width": 82, 38 | "height": 101 39 | } 40 | }, 41 | "genshin_repository_scan_margin_pos": { 42 | "Pos": { 43 | "x": 79, 44 | "y": 81 45 | } 46 | }, 47 | "genshin_repository_pool_rect": { 48 | "Rect": { 49 | "top": 93.2, 50 | "left": 912.7, 51 | "height": 319.2, 52 | "width": 15 53 | } 54 | }, 55 | "genshin_repository_item_row": { 56 | "InvariantInt": 7 57 | }, 58 | "genshin_repository_item_col": { 59 | "InvariantInt": 8 60 | }, 61 | "genshin_repository_lock_pos": { 62 | "Pos": { 63 | "x": 9, 64 | "y": 10 65 | } 66 | }, 67 | "genshin_artifact_item_count_rect": { 68 | "Rect": { 69 | "top": 22.9, 70 | "left": 1058.6, 71 | "height": 18.5, 72 | "width": 143.7 73 | } 74 | }, 75 | "genshin_artifact_star_pos": { 76 | "Pos": { 77 | "x": 1175.4, 78 | "y": 95.8 79 | } 80 | }, 81 | "genshin_artifact_lock_pos": { 82 | "Pos": { 83 | "x": 1160, 84 | "y": 286 85 | } 86 | }, 87 | "genshin_artifact_main_stat_name_rect": { 88 | "Rect": { 89 | "top": 181.0, 90 | "left": 889.5, 91 | "height": 18.8, 92 | "width": 108.5 93 | } 94 | }, 95 | "genshin_artifact_main_stat_value_rect": { 96 | "Rect": { 97 | "top": 199.8, 98 | "left": 889.5, 99 | "height": 33.6, 100 | "width": 108.5 101 | } 102 | }, 103 | "genshin_artifact_level_rect": { 104 | "Rect": { 105 | "top": 288, 106 | "left": 894, 107 | "height": 14, 108 | "width": 33 109 | } 110 | }, 111 | "genshin_artifact_item_equip_rect": { 112 | "Rect": { 113 | "top": 849.8, 114 | "left": 924.4, 115 | "height": 20.3, 116 | "width": 166.4 117 | } 118 | }, 119 | "genshin_artifact_sub_stat1_rect": { 120 | "Rect": { 121 | "top": 318.2, 122 | "left": 904.3, 123 | "height": 24.1, 124 | "width": 196.2 125 | } 126 | }, 127 | "genshin_artifact_sub_stat2_rect": { 128 | "Rect": { 129 | "top": 342.3, 130 | "left": 904.3, 131 | "height": 27.1, 132 | "width": 196.2 133 | } 134 | }, 135 | "genshin_artifact_sub_stat3_rect": { 136 | "Rect": { 137 | "top": 369.4, 138 | "left": 904.3, 139 | "height": 25.9, 140 | "width": 196.2 141 | } 142 | }, 143 | "genshin_artifact_sub_stat4_rect": { 144 | "Rect": { 145 | "top": 395.3, 146 | "left": 904.3, 147 | "height": 25.3, 148 | "width": 196.2 149 | } 150 | }, 151 | "genshin_artifact_title_rect": { 152 | "Rect": { 153 | "top": 85, 154 | "left": 889.5, 155 | "height": 26.7, 156 | "width": 205.3 157 | } 158 | } 159 | } 160 | } -------------------------------------------------------------------------------- /yas-genshin/window_info/windows1440x900.json: -------------------------------------------------------------------------------- 1 | { 2 | "current_resolution": { 3 | "width": 1440, 4 | "height": 900 5 | }, 6 | "platform": "Windows", 7 | "ui": "Desktop", 8 | "data": { 9 | "genshin_artifact_offset": { 10 | "Size": { 11 | "width": 0, 12 | "height": 44 13 | } 14 | }, 15 | "genshin_repository_panel_rect": { 16 | "Rect": { 17 | "top": 90, 18 | "left": 981, 19 | "height": 720, 20 | "width": 369 21 | } 22 | }, 23 | "genshin_repository_flag_pos": { 24 | "Pos": { 25 | "x": 245.9, 26 | "y": 82.1 27 | } 28 | }, 29 | "genshin_repository_item_gap_size": { 30 | "Size": { 31 | "width": 17, 32 | "height": 18 33 | } 34 | }, 35 | "genshin_repository_item_size": { 36 | "Size": { 37 | "width": 93, 38 | "height": 113 39 | } 40 | }, 41 | "genshin_repository_scan_margin_pos": { 42 | "Pos": { 43 | "x": 89, 44 | "y": 91 45 | } 46 | }, 47 | "genshin_repository_pool_rect": { 48 | "Rect": { 49 | "top": 103.6, 50 | "left": 1028.5, 51 | "height": 357.1, 52 | "width": 15 53 | } 54 | }, 55 | "genshin_repository_item_row": { 56 | "InvariantInt": 6 57 | }, 58 | "genshin_repository_item_col": { 59 | "InvariantInt": 8 60 | }, 61 | "genshin_repository_lock_pos": { 62 | "Pos": { 63 | "x": 10, 64 | "y": 12 65 | } 66 | }, 67 | "genshin_artifact_item_count_rect": { 68 | "Rect": { 69 | "top": 25, 70 | "left": 1182.8, 71 | "height": 21.8, 72 | "width": 170.3 73 | } 74 | }, 75 | "genshin_artifact_star_pos": { 76 | "Pos": { 77 | "x": 1321.3, 78 | "y": 111.3 79 | } 80 | }, 81 | "genshin_artifact_lock_pos": { 82 | "Pos": { 83 | "x": 1305, 84 | "y": 322 85 | } 86 | }, 87 | "genshin_artifact_main_stat_name_rect": { 88 | "Rect": { 89 | "top": 201.6, 90 | "left": 1000.3, 91 | "height": 22.3, 92 | "width": 127.8 93 | } 94 | }, 95 | "genshin_artifact_main_stat_value_rect": { 96 | "Rect": { 97 | "top": 225.5, 98 | "left": 1000.3, 99 | "height": 37.3, 100 | "width": 127.8 101 | } 102 | }, 103 | "genshin_artifact_level_rect": { 104 | "Rect": { 105 | "top": 324, 106 | "left": 1006, 107 | "height": 16, 108 | "width": 37 109 | } 110 | }, 111 | "genshin_artifact_item_equip_rect": { 112 | "Rect": { 113 | "top": 776, 114 | "left": 1041.3, 115 | "height": 24.6, 116 | "width": 206 117 | } 118 | }, 119 | "genshin_artifact_sub_stat1_rect": { 120 | "Rect": { 121 | "top": 358, 122 | "left": 1016.2, 123 | "height": 26.1, 124 | "width": 207.9 125 | } 126 | }, 127 | "genshin_artifact_sub_stat2_rect": { 128 | "Rect": { 129 | "top": 384.1, 130 | "left": 1016.2, 131 | "height": 28.5, 132 | "width": 207.9 133 | } 134 | }, 135 | "genshin_artifact_sub_stat3_rect": { 136 | "Rect": { 137 | "top": 412.6, 138 | "left": 1016.2, 139 | "height": 27.9, 140 | "width": 207.9 141 | } 142 | }, 143 | "genshin_artifact_sub_stat4_rect": { 144 | "Rect": { 145 | "top": 440.5, 146 | "left": 1016.2, 147 | "height": 26.6, 148 | "width": 207.9 149 | } 150 | }, 151 | "genshin_artifact_title_rect": { 152 | "Rect": { 153 | "top": 96, 154 | "left": 1000.9, 155 | "height": 30.1, 156 | "width": 268 157 | } 158 | } 159 | } 160 | } -------------------------------------------------------------------------------- /yas-wutheringwaves/src/scanner/echo_scanner/models/index_2_word.json: -------------------------------------------------------------------------------- 1 | { 2 | "0": "-", 3 | "1": " ", 4 | "2": "%", 5 | "3": "+", 6 | "4": ".", 7 | "5": "/", 8 | "6": "0", 9 | "7": "1", 10 | "8": "2", 11 | "9": "3", 12 | "10": "4", 13 | "11": "5", 14 | "12": "6", 15 | "13": "7", 16 | "14": "8", 17 | "15": "9", 18 | "16": "·", 19 | "17": "之", 20 | "18": "乐", 21 | "19": "云", 22 | "20": "伤", 23 | "21": "侏", 24 | "22": "偶", 25 | "23": "傀", 26 | "24": "先", 27 | "25": "光", 28 | "26": "共", 29 | "27": "兽", 30 | "28": "军", 31 | "29": "冠", 32 | "30": "冥", 33 | "31": "冷", 34 | "32": "凝", 35 | "33": "凶", 36 | "34": "击", 37 | "35": "刃", 38 | "36": "判", 39 | "37": "刺", 40 | "38": "加", 41 | "39": "动", 42 | "40": "势", 43 | "41": "卫", 44 | "42": "变", 45 | "43": "叮", 46 | "44": "呜", 47 | "45": "呼", 48 | "46": "命", 49 | "47": "咔", 50 | "48": "咕", 51 | "49": "咚", 52 | "50": "咻", 53 | "51": "哀", 54 | "52": "哨", 55 | "53": "啾", 56 | "54": "嗞", 57 | "55": "嚓", 58 | "56": "地", 59 | "57": "坚", 60 | "58": "士", 61 | "59": "声", 62 | "60": "奏", 63 | "61": "妄", 64 | "62": "守", 65 | "63": "审", 66 | "64": "害", 67 | "65": "寒", 68 | "66": "导", 69 | "67": "射", 70 | "68": "岩", 71 | "69": "巡", 72 | "70": "师", 73 | "71": "常", 74 | "72": "幼", 75 | "73": "廉", 76 | "74": "异", 77 | "75": "弋", 78 | "76": "形", 79 | "77": "徊", 80 | "78": "御", 81 | "79": "惊", 82 | "80": "戏", 83 | "81": "成", 84 | "82": "战", 85 | "83": "手", 86 | "84": "技", 87 | "85": "振", 88 | "86": "攻", 89 | "87": "放", 90 | "88": "效", 91 | "89": "斗", 92 | "90": "无", 93 | "91": "普", 94 | "92": "晶", 95 | "93": "暗", 96 | "94": "暴", 97 | "95": "朔", 98 | "96": "机", 99 | "97": "果", 100 | "98": "枢", 101 | "99": "械", 102 | "100": "棱", 103 | "101": "气", 104 | "102": "河", 105 | "103": "治", 106 | "104": "泣", 107 | "105": "渊", 108 | "106": "游", 109 | "107": "湮", 110 | "108": "火", 111 | "109": "灭", 112 | "110": "灯", 113 | "111": "热", 114 | "112": "照", 115 | "113": "熊", 116 | "114": "熔", 117 | "115": "燎", 118 | "116": "狼", 119 | "117": "猎", 120 | "118": "猩", 121 | "119": "猪", 122 | "120": "猿", 123 | "121": "獠", 124 | "122": "率", 125 | "123": "玫", 126 | "124": "生", 127 | "125": "电", 128 | "126": "疗", 129 | "127": "相", 130 | "128": "石", 131 | "129": "破", 132 | "130": "碎", 133 | "131": "磐", 134 | "132": "稚", 135 | "133": "箭", 136 | "134": "簇", 137 | "135": "紫", 138 | "136": "绿", 139 | "137": "羽", 140 | "138": "者", 141 | "139": "聚", 142 | "140": "能", 143 | "141": "菇", 144 | "142": "萤", 145 | "143": "虫", 146 | "144": "蛰", 147 | "145": "蜥", 148 | "146": "蝎", 149 | "147": "蝶", 150 | "148": "融", 151 | "149": "螯", 152 | "150": "行", 153 | "151": "衍", 154 | "152": "裂", 155 | "153": "角", 156 | "154": "解", 157 | "155": "谕", 158 | "156": "豚", 159 | "157": "踏", 160 | "158": "车", 161 | "159": "辉", 162 | "160": "通", 163 | "161": "遁", 164 | "162": "重", 165 | "163": "钟", 166 | "164": "铎", 167 | "165": "锋", 168 | "166": "镜", 169 | "167": "镰", 170 | "168": "闪", 171 | "169": "防", 172 | "170": "阿", 173 | "171": "陆", 174 | "172": "雪", 175 | "173": "雷", 176 | "174": "霜", 177 | "175": "青", 178 | "176": "飞", 179 | "177": "骑", 180 | "178": "骸", 181 | "179": "鬃", 182 | "180": "鳞", 183 | "181": "鸣", 184 | "182": "鸵", 185 | "183": "鸷", 186 | "184": "鹭", 187 | "185": "鼠", 188 | "186": "龟", 189 | "187": "(", 190 | "188": ")" 191 | } -------------------------------------------------------------------------------- /yas/src/capture/winapi_capturer.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::c_void; 2 | use std::mem::size_of; 3 | use std::ptr::null_mut; 4 | 5 | use anyhow::{anyhow, Result}; 6 | use image::{ImageBuffer, RgbImage}; 7 | use windows_sys::Win32::Graphics::Gdi::*; 8 | 9 | use crate::capture::Capturer; 10 | use crate::positioning::{Pos, Rect}; 11 | 12 | // BGRA 13 | unsafe fn unsafe_capture(rect: Rect) -> Result> { 14 | let dc_window: HDC = GetDC(null_mut()); 15 | 16 | let dc_mem: HDC = CreateCompatibleDC(dc_window); 17 | if dc_mem.is_null() { 18 | return Err(anyhow!("CreateCompatibleDC failed")); 19 | } 20 | 21 | let hbm: HBITMAP = CreateCompatibleBitmap(dc_window, rect.width, rect.height); 22 | if hbm.is_null() { 23 | return Err(anyhow!("CreateCompatibleBitmap failed")); 24 | } 25 | 26 | SelectObject(dc_mem, hbm as *mut c_void); 27 | 28 | let result = BitBlt( 29 | dc_mem, 30 | 0, 31 | 0, 32 | rect.width, 33 | rect.height, 34 | dc_window, 35 | rect.left, 36 | rect.top, 37 | SRCCOPY 38 | ); 39 | if result == 0 { 40 | return Err(anyhow!("BitBlt failed")); 41 | } 42 | 43 | let mut bitmap: BITMAP = BITMAP { 44 | bmBits: 0 as *mut c_void, 45 | bmBitsPixel: 0, 46 | bmPlanes: 0, 47 | bmWidthBytes: 0, 48 | bmHeight: 0, 49 | bmWidth: 0, 50 | bmType: 0, 51 | }; 52 | GetObjectW( 53 | hbm as *mut c_void, 54 | size_of::() as i32, 55 | (&mut bitmap) as *mut BITMAP as *mut c_void 56 | ); 57 | 58 | let mut bi: BITMAPINFOHEADER = BITMAPINFOHEADER { 59 | biSize: size_of::() as u32, 60 | biWidth: bitmap.bmWidth, 61 | biHeight: bitmap.bmHeight, 62 | biPlanes: 1, 63 | biBitCount: 32, 64 | biCompression: BI_RGB, 65 | biSizeImage: 0, 66 | biXPelsPerMeter: 0, 67 | biYPelsPerMeter: 0, 68 | biClrUsed: 0, 69 | biClrImportant: 0, 70 | }; 71 | 72 | let bitmap_size: usize = (((bitmap.bmWidth * 32 + 31) / 32) * 4 * bitmap.bmHeight) as usize; 73 | let mut buffer: Vec = vec![0; bitmap_size]; 74 | 75 | GetDIBits( 76 | dc_window, 77 | hbm, 78 | 0, 79 | bitmap.bmHeight as u32, 80 | // lpbitmap, 81 | buffer.as_mut_ptr() as *mut c_void, 82 | (&mut bi) as *mut BITMAPINFOHEADER as *mut BITMAPINFO, 83 | DIB_RGB_COLORS 84 | ); 85 | 86 | DeleteObject(hbm as *mut c_void); 87 | DeleteObject(dc_mem as *mut c_void); 88 | ReleaseDC(null_mut(), dc_window); 89 | 90 | Ok(buffer) 91 | } 92 | 93 | pub struct WinapiCapturer; 94 | 95 | impl WinapiCapturer { 96 | pub fn new() -> Self { 97 | // todo maybe we can explicitly account for windows scale, and remove the call 98 | // crate::utils::set_dpi_awareness(); 99 | Self 100 | } 101 | } 102 | 103 | impl Capturer for WinapiCapturer { 104 | fn capture_rect(&self, rect: Rect) -> Result { 105 | let raw: Vec = unsafe { 106 | unsafe_capture(rect)? 107 | }; 108 | 109 | let height = rect.height as u32; 110 | let width = rect.width as u32; 111 | 112 | let img = ImageBuffer::from_fn( 113 | rect.width as u32, 114 | rect.height as u32, 115 | move |x, y| { 116 | let y = height - y - 1; 117 | let b = raw[((y * width + x) * 4 + 0) as usize]; 118 | let g = raw[((y * width + x) * 4 + 1) as usize]; 119 | let r = raw[((y * width + x) * 4 + 2) as usize]; 120 | image::Rgb([r, g, b]) 121 | } 122 | ); 123 | 124 | Ok(img) 125 | } 126 | 127 | fn capture_color(&self, pos: Pos) -> Result> { 128 | let raw: Vec = unsafe { 129 | unsafe_capture(Rect { 130 | left: pos.x, 131 | top: pos.y, 132 | width: 1, 133 | height: 1 134 | })? 135 | }; 136 | let r = raw[2]; 137 | let g = raw[1]; 138 | let b = raw[0]; 139 | Ok(image::Rgb([r, g, b])) 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /yas-starrail/window_info/windows1920x1080.json: -------------------------------------------------------------------------------- 1 | { 2 | "current_resolution": { 3 | "width": 1920, 4 | "height": 1080 5 | }, 6 | "platform": "Windows", 7 | "ui": "Desktop", 8 | "data": { 9 | "starrail_relic_discard_pos": { 10 | "Pos": { 11 | "x": 1808, 12 | "y": 323 13 | } 14 | }, 15 | "starrail_relic_equip_rect": { 16 | "Rect": { 17 | "top": 884, 18 | "left": 1610, 19 | "width": 76, 20 | "height": 23 21 | } 22 | }, 23 | "starrail_relic_equipper_pos": { 24 | "Pos": { 25 | "x": 1582, 26 | "y": 888 27 | } 28 | }, 29 | "starrail_relic_item_count_rect": { 30 | "Rect": { 31 | "top": 45, 32 | "left": 1503, 33 | "width": 260, 34 | "height": 40 35 | } 36 | }, 37 | "starrail_relic_level_rect": { 38 | "Rect": { 39 | "top": 309, 40 | "left": 1413, 41 | "width": 86, 42 | "height": 38 43 | } 44 | }, 45 | "starrail_relic_lock_pos": { 46 | "Pos": { 47 | "x": 1808, 48 | "y": 274 49 | } 50 | }, 51 | "starrail_relic_main_stat_name_rect": { 52 | "Rect": { 53 | "top": 398, 54 | "left": 1440, 55 | "width": 251, 56 | "height": 33 57 | } 58 | }, 59 | "starrail_relic_main_stat_value_rect": { 60 | "Rect": { 61 | "top": 398, 62 | "left": 1729, 63 | "width": 113, 64 | "height": 33 65 | } 66 | }, 67 | "starrail_relic_star_pos": { 68 | "Pos": { 69 | "x": 1800, 70 | "y": 250 71 | } 72 | }, 73 | "starrail_relic_sub_stat0_name_rect": { 74 | "Rect": { 75 | "top": 442, 76 | "left": 1440, 77 | "width": 197, 78 | "height": 33 79 | } 80 | }, 81 | "starrail_relic_sub_stat0_value_rect": { 82 | "Rect": { 83 | "top": 442, 84 | "left": 1680, 85 | "width": 166, 86 | "height": 33 87 | } 88 | }, 89 | "starrail_relic_sub_stat1_name_rect": { 90 | "Rect": { 91 | "top": 480, 92 | "left": 1440, 93 | "width": 197, 94 | "height": 35 95 | } 96 | }, 97 | "starrail_relic_sub_stat1_value_rect": { 98 | "Rect": { 99 | "top": 480, 100 | "left": 1680, 101 | "width": 166, 102 | "height": 33 103 | } 104 | }, 105 | "starrail_relic_sub_stat2_name_rect": { 106 | "Rect": { 107 | "top": 519, 108 | "left": 1440, 109 | "width": 197, 110 | "height": 34 111 | } 112 | }, 113 | "starrail_relic_sub_stat2_value_rect": { 114 | "Rect": { 115 | "top": 519, 116 | "left": 1680, 117 | "width": 166, 118 | "height": 33 119 | } 120 | }, 121 | "starrail_relic_sub_stat3_name_rect": { 122 | "Rect": { 123 | "top": 557, 124 | "left": 1440, 125 | "width": 197, 126 | "height": 34 127 | } 128 | }, 129 | "starrail_relic_sub_stat3_value_rect": { 130 | "Rect": { 131 | "top": 557, 132 | "left": 1680, 133 | "width": 166, 134 | "height": 34 135 | } 136 | }, 137 | "starrail_relic_title_rect": { 138 | "Rect": { 139 | "top": 129, 140 | "left": 1398, 141 | "width": 450, 142 | "height": 33 143 | } 144 | }, 145 | "starrail_repository_flag_rect": { 146 | "Rect": { 147 | "top": 179, 148 | "left": 1064, 149 | "width": 1, 150 | "height": 20 151 | } 152 | }, 153 | "starrail_repository_item_col": { 154 | "InvariantInt": 9 155 | }, 156 | "starrail_repository_item_gap_size": { 157 | "Size": { 158 | "width": 9, 159 | "height": 14 160 | } 161 | }, 162 | "starrail_repository_item_row": { 163 | "InvariantInt": 5 164 | }, 165 | "starrail_repository_item_size": { 166 | "Size": { 167 | "width": 115, 168 | "height": 135 169 | } 170 | }, 171 | "starrail_repository_panel_rect": { 172 | "Rect": { 173 | "top": 124, 174 | "left": 1395, 175 | "width": 456, 176 | "height": 817 177 | } 178 | }, 179 | "starrail_repository_pool_rect": { 180 | "Rect": { 181 | "top": 132, 182 | "left": 1446, 183 | "width": 63, 184 | "height": 456 185 | } 186 | }, 187 | "starrail_repository_scan_margin_pos": { 188 | "Pos": { 189 | "x": 136, 190 | "y": 206 191 | } 192 | } 193 | } 194 | } -------------------------------------------------------------------------------- /yas-genshin/window_info/windows1600x900.json: -------------------------------------------------------------------------------- 1 | { 2 | "current_resolution": { 3 | "width": 1600, 4 | "height": 900 5 | }, 6 | "platform": "Windows", 7 | "ui": "Desktop", 8 | "data": { 9 | "genshin_artifact_offset": { 10 | "Size": { 11 | "width": 0, 12 | "height": 48.5 13 | } 14 | }, 15 | "genshin_repository_panel_rect": { 16 | "Rect": { 17 | "top": 100.0, 18 | "left": 1090.0, 19 | "width": 410.0, 20 | "height": 700.0 21 | } 22 | }, 23 | "genshin_repository_flag_pos": { 24 | "Pos": { 25 | "x": 271.1, 26 | "y": 89.8 27 | } 28 | }, 29 | "genshin_repository_item_gap_size": { 30 | "Size": { 31 | "width": 20, 32 | "height": 20 33 | } 34 | }, 35 | "genshin_repository_item_size": { 36 | "Size": { 37 | "width": 102, 38 | "height": 126 39 | } 40 | }, 41 | "genshin_repository_scan_margin_pos": { 42 | "Pos": { 43 | "x": 99, 44 | "y": 101 45 | } 46 | }, 47 | "genshin_repository_pool_rect": { 48 | "Rect": { 49 | "top": 118.2, 50 | "left": 1144.7, 51 | "width": 15, 52 | "height": 392.1 53 | } 54 | }, 55 | "genshin_repository_item_row": { 56 | "InvariantInt": 5 57 | }, 58 | "genshin_repository_item_col": { 59 | "InvariantInt": 8 60 | }, 61 | "genshin_repository_lock_pos": { 62 | "Pos": { 63 | "x": 12, 64 | "y": 14 65 | } 66 | }, 67 | "genshin_artifact_item_count_rect": { 68 | "Rect": { 69 | "top": 27.1, 70 | "left": 1314.9, 71 | "height": 25.8, 72 | "width": 189.8 73 | } 74 | }, 75 | "genshin_artifact_star_pos": { 76 | "Pos": { 77 | "x": 1469.4, 78 | "y": 123.9 79 | } 80 | }, 81 | "genshin_artifact_lock_pos": { 82 | "Pos": { 83 | "x": 1450, 84 | "y": 357 85 | } 86 | }, 87 | "genshin_artifact_main_stat_name_rect": { 88 | "Rect": { 89 | "top": 224.3, 90 | "left": 1110.0, 91 | "height": 24, 92 | "width": 143.9 93 | } 94 | }, 95 | "genshin_artifact_main_stat_value_rect": { 96 | "Rect": { 97 | "top": 248.4, 98 | "left": 1110.0, 99 | "height": 38.4, 100 | "width": 136.8 101 | } 102 | }, 103 | "genshin_artifact_level_rect": { 104 | "Rect": { 105 | "top": 360, 106 | "left": 1117, 107 | "height": 18, 108 | "width": 43 109 | } 110 | }, 111 | "genshin_artifact_item_equip_rect": { 112 | "Rect": { 113 | "top": 762.6, 114 | "left": 1154.9, 115 | "height": 25.2, 116 | "width": 243.5 117 | } 118 | }, 119 | "genshin_artifact_sub_stat1_rect": { 120 | "Rect": { 121 | "top": 398.1, 122 | "left": 1130.2, 123 | "height": 29.2, 124 | "width": 212.8 125 | } 126 | }, 127 | "genshin_artifact_sub_stat2_rect": { 128 | "Rect": { 129 | "top": 427.3, 130 | "left": 1130.2, 131 | "height": 30.9, 132 | "width": 212.8 133 | } 134 | }, 135 | "genshin_artifact_sub_stat3_rect": { 136 | "Rect": { 137 | "top": 458.2, 138 | "left": 1130.2, 139 | "height": 32.7, 140 | "width": 212.8 141 | } 142 | }, 143 | "genshin_artifact_sub_stat4_rect": { 144 | "Rect": { 145 | "top": 490.9, 146 | "left": 1130.2, 147 | "height": 32.1, 148 | "width": 212.8 149 | } 150 | }, 151 | "genshin_artifact_title_rect": { 152 | "Rect": { 153 | "top": 106.6, 154 | "left": 1111.8, 155 | "height": 33, 156 | "width": 305.9 157 | } 158 | } 159 | } 160 | } -------------------------------------------------------------------------------- /yas-wutheringwaves/window_info/windows2560x1440.json: -------------------------------------------------------------------------------- 1 | { 2 | "current_resolution": { 3 | "width": 2560, 4 | "height": 1440 5 | }, 6 | "platform": "Windows", 7 | "ui": "Desktop", 8 | "data": { 9 | "ww_echo_equip_rect": { 10 | "Rect": { 11 | "top": 1168, 12 | "left": 1800, 13 | "width": 181, 14 | "height": 40 15 | } 16 | }, 17 | "ww_echo_item_count_rect": { 18 | "Rect": { 19 | "top": 68, 20 | "left": 138, 21 | "width": 302, 22 | "height": 47 23 | } 24 | }, 25 | "ww_echo_level_rect": { 26 | "Rect": { 27 | "top": 331, 28 | "left": 2352, 29 | "width": 91, 30 | "height": 44 31 | } 32 | }, 33 | "ww_echo_main_stat1_name_rect": { 34 | "Rect": { 35 | "top": 572, 36 | "left": 1921, 37 | "width": 352, 38 | "height": 45 39 | } 40 | }, 41 | "ww_echo_main_stat1_value_rect": { 42 | "Rect": { 43 | "top": 572, 44 | "left": 2280, 45 | "width": 165, 46 | "height": 45 47 | } 48 | }, 49 | "ww_echo_main_stat2_name_rect": { 50 | "Rect": { 51 | "top": 627, 52 | "left": 1921, 53 | "width": 352, 54 | "height": 46 55 | } 56 | }, 57 | "ww_echo_main_stat2_value_rect": { 58 | "Rect": { 59 | "top": 627, 60 | "left": 2280, 61 | "width": 165, 62 | "height": 46 63 | } 64 | }, 65 | "ww_echo_star_pos": { 66 | "Pos": { 67 | "x": 2453, 68 | "y": 506 69 | } 70 | }, 71 | "ww_echo_sub_stat0_name_rect": { 72 | "Rect": { 73 | "top": 684, 74 | "left": 1921, 75 | "width": 352, 76 | "height": 46 77 | } 78 | }, 79 | "ww_echo_sub_stat0_value_rect": { 80 | "Rect": { 81 | "top": 684, 82 | "left": 2280, 83 | "width": 165, 84 | "height": 46 85 | } 86 | }, 87 | "ww_echo_sub_stat1_name_rect": { 88 | "Rect": { 89 | "top": 742, 90 | "left": 1921, 91 | "width": 352, 92 | "height": 46 93 | } 94 | }, 95 | "ww_echo_sub_stat1_value_rect": { 96 | "Rect": { 97 | "top": 742, 98 | "left": 2280, 99 | "width": 165, 100 | "height": 46 101 | } 102 | }, 103 | "ww_echo_sub_stat2_name_rect": { 104 | "Rect": { 105 | "top": 795, 106 | "left": 1921, 107 | "width": 352, 108 | "height": 50 109 | } 110 | }, 111 | "ww_echo_sub_stat2_value_rect": { 112 | "Rect": { 113 | "top": 795, 114 | "left": 2280, 115 | "width": 165, 116 | "height": 50 117 | } 118 | }, 119 | "ww_echo_sub_stat3_name_rect": { 120 | "Rect": { 121 | "top": 853, 122 | "left": 1921, 123 | "width": 352, 124 | "height": 48 125 | } 126 | }, 127 | "ww_echo_sub_stat3_value_rect": { 128 | "Rect": { 129 | "top": 853, 130 | "left": 2280, 131 | "width": 165, 132 | "height": 48 133 | } 134 | }, 135 | "ww_echo_sub_stat4_name_rect": { 136 | "Rect": { 137 | "top": 911, 138 | "left": 1921, 139 | "width": 352, 140 | "height": 48 141 | } 142 | }, 143 | "ww_echo_sub_stat4_value_rect": { 144 | "Rect": { 145 | "top": 911, 146 | "left": 2280, 147 | "width": 165, 148 | "height": 48 149 | } 150 | }, 151 | "ww_echo_title_rect": { 152 | "Rect": { 153 | "top": 169, 154 | "left": 1745, 155 | "width": 388, 156 | "height": 51 157 | } 158 | }, 159 | "ww_repository_flag_pos": { 160 | "Pos": { 161 | "y": 154, 162 | "x": 1048 163 | } 164 | }, 165 | "ww_repository_item_col": { 166 | "InvariantInt": 6 167 | }, 168 | "ww_repository_item_gap_size": { 169 | "Size": { 170 | "width": 30, 171 | "height": 40 172 | } 173 | }, 174 | "ww_repository_item_row": { 175 | "InvariantInt": 4 176 | }, 177 | "ww_repository_item_size": { 178 | "Size": { 179 | "width": 192, 180 | "height": 233 181 | } 182 | }, 183 | "ww_repository_panel_rect": { 184 | "Rect": { 185 | "top": 154, 186 | "left": 1728, 187 | "width": 744, 188 | "height": 1090 189 | } 190 | }, 191 | "ww_repository_pool_rect": { 192 | "Rect": { 193 | "top": 563, 194 | "left": 1774, 195 | "width": 64, 196 | "height": 578 197 | } 198 | }, 199 | "ww_repository_scan_margin_pos": { 200 | "Pos": { 201 | "x": 279, 202 | "y": 167 203 | } 204 | } 205 | } 206 | } -------------------------------------------------------------------------------- /yas/src/ocr/yas_model/preprocess.rs: -------------------------------------------------------------------------------- 1 | use image::{ImageBuffer, Luma, RgbImage, GenericImageView}; 2 | use image::imageops; 3 | 4 | /// convert rgb image to f32 gray image 5 | pub fn to_gray(raw: &RgbImage) -> ImageBuffer, Vec> { 6 | let mut new_gray: ImageBuffer, Vec> = ImageBuffer::new(raw.width(), raw.height()); 7 | for x in 0..raw.width() { 8 | for y in 0..raw.height() { 9 | let rgb = raw.get_pixel(x, y); 10 | 11 | let r = rgb[0] as f32 / 255.0; 12 | let g = rgb[1] as f32 / 255.0; 13 | let b = rgb[2] as f32 / 255.0; 14 | 15 | let gray = r * 0.2989 + g * 0.5870 + b * 0.1140; 16 | let grayp = new_gray.get_pixel_mut(x, y); 17 | grayp[0] = gray; 18 | } 19 | } 20 | new_gray 21 | } 22 | 23 | /// normalize an f32 gray image 24 | /// which makes the bright pixel brighter, the dark pixels darker 25 | fn normalize(im: &mut ImageBuffer, Vec>, auto_inverse: bool) -> bool { 26 | let width = im.width(); 27 | let height = im.height(); 28 | 29 | if width == 0 || height == 0 { 30 | println!("wrong width or height"); 31 | return false; 32 | } 33 | 34 | let mut max: f32 = 0.0; 35 | let mut min: f32 = 256.0; 36 | 37 | for i in 0..width { 38 | for j in 0..height { 39 | let p = im.get_pixel(i, j)[0]; 40 | if p > max { 41 | max = p; 42 | } 43 | if p < min { 44 | min = p; 45 | } 46 | } 47 | } 48 | 49 | if max == min { 50 | return false; 51 | } 52 | 53 | let flag_pixel = if width >= 2 { 54 | im.get_pixel(width - 2, height - 1)[0] 55 | } else { 56 | im.get_pixel(width - 1, height - 1)[0] 57 | }; 58 | let flag_pixel = (flag_pixel - min) / (max - min); 59 | 60 | for i in 0..width { 61 | for j in 0..height { 62 | let p = im.get_pixel_mut(i, j); 63 | let pv = p[0]; 64 | let mut new_pv = (pv - min) / (max - min); 65 | if auto_inverse && flag_pixel > 0.5 { 66 | new_pv = 1.0 - new_pv; 67 | } 68 | p[0] = new_pv; 69 | } 70 | } 71 | 72 | true 73 | } 74 | 75 | /// crop an f32 gray image to only where there is text 76 | fn crop(im: &ImageBuffer, Vec>) -> ImageBuffer, Vec> { 77 | let width = im.width(); 78 | let height = im.height(); 79 | 80 | let mut min_col = width - 1; 81 | let mut max_col = 0; 82 | let mut min_row = height - 1; 83 | let mut max_row = 0_u32; 84 | 85 | for i in 0..width { 86 | for j in 0..height { 87 | let p = im.get_pixel(i, j)[0]; 88 | if p > 0.7 { 89 | if i < min_col { 90 | min_col = i; 91 | } 92 | if i > max_col { 93 | max_col = i; 94 | } 95 | break; 96 | } 97 | } 98 | } 99 | 100 | for j in 0..height { 101 | for i in 0..width { 102 | let p = im.get_pixel(i, j)[0]; 103 | if p > 0.7 { 104 | if j < min_row { 105 | min_row = j; 106 | } 107 | if j > max_row { 108 | max_row = j; 109 | } 110 | break; 111 | } 112 | } 113 | } 114 | 115 | if min_col > max_col || min_row > max_row { 116 | return im.clone(); 117 | } 118 | 119 | let new_height = max_row - min_row + 1; 120 | let new_width = max_col - min_col + 1; 121 | 122 | // let _ans: Vec = vec![0.0; (new_width * new_height) as usize]; 123 | let cropped_im = im.view(min_col, min_row, new_width, new_height).to_image(); 124 | 125 | cropped_im 126 | } 127 | 128 | /// resize an f32 gray image to 384 * 32, if not wide enough, then pad with background 129 | fn resize_and_pad(im: &ImageBuffer, Vec>) -> ImageBuffer, Vec> { 130 | let w = im.width(); 131 | let h = im.height(); 132 | 133 | let new_width = if w as f64 / (h as f64) > 384.0 / 32.0 { 134 | 384 135 | } else { 136 | std::cmp::min((32.0 / h as f64 * w as f64) as u32, 384) 137 | }; 138 | 139 | let new_height = std::cmp::min((384.0 / w as f64 * h as f64) as u32, 32); 140 | 141 | let img = imageops::resize( 142 | im, 143 | new_width, 144 | new_height, 145 | image::imageops::FilterType::Triangle, 146 | ); 147 | 148 | let data: Vec = vec![0.0; 32 * 384]; 149 | let mut padded_im = ImageBuffer::from_vec(384, 32, data).unwrap(); 150 | imageops::overlay(&mut padded_im, &img, 0, 0); 151 | padded_im 152 | } 153 | 154 | /// transform an f32 gray image to a preprocessed image 155 | /// if the image has only one color, then return false, but this is not an error 156 | pub fn pre_process(im: ImageBuffer, Vec>) -> (ImageBuffer, Vec>, bool) { 157 | let mut im = im; 158 | if !normalize(&mut im, true) { 159 | return (im, false); 160 | } 161 | let mut im = crop(&im); 162 | 163 | normalize(&mut im, false); 164 | 165 | let mut im = resize_and_pad(&im); 166 | 167 | for i in 0..im.width() { 168 | for j in 0..im.height() { 169 | let p = im.get_pixel_mut(i, j); 170 | let pv = p[0]; 171 | if pv < 0.53 { 172 | p[0] = 0.0; 173 | } else { 174 | p[0] = 1.0; 175 | } 176 | } 177 | } 178 | 179 | (im, true) 180 | } 181 | -------------------------------------------------------------------------------- /yas-genshin/src/export/artifact/exporter.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use anyhow::Result; 4 | use clap::FromArgMatches; 5 | 6 | use yas::export::{AssetEmitter, ExportAssets}; 7 | 8 | use crate::artifact::GenshinArtifact; 9 | use crate::export::artifact::{ExportArtifactConfig, GenshinArtifactExportFormat}; 10 | use crate::export::artifact::csv::GenshinArtifactCSVFormat; 11 | 12 | use super::good::GOODFormat; 13 | use super::mingyu_lab::MingyuLabFormat; 14 | use super::mona_uranai::MonaFormat; 15 | 16 | pub struct GenshinArtifactExporter<'a> { 17 | pub format: GenshinArtifactExportFormat, 18 | pub results: Option<&'a [GenshinArtifact]>, 19 | pub output_dir: PathBuf, 20 | } 21 | 22 | impl <'a> GenshinArtifactExporter<'a> { 23 | pub fn new(arg_matches: &clap::ArgMatches, results: &'a [GenshinArtifact]) -> Result { 24 | let config = ExportArtifactConfig::from_arg_matches(arg_matches)?; 25 | Ok(Self { 26 | format: config.format, 27 | results: Some(results), 28 | output_dir: PathBuf::from(&config.output_dir) 29 | }) 30 | } 31 | } 32 | 33 | impl<'a> AssetEmitter for GenshinArtifactExporter<'a> { 34 | fn emit(&self, export_assets: &mut ExportAssets) { 35 | if self.results.is_none() { 36 | return; 37 | } 38 | 39 | let results = self.results.unwrap(); 40 | 41 | match self.format { 42 | GenshinArtifactExportFormat::Mona => { 43 | let path = self.output_dir.join("mona.json"); 44 | let value = MonaFormat::new(results); 45 | let contents = serde_json::to_string(&value).unwrap(); 46 | 47 | export_assets.add_asset( 48 | Some(String::from("artifacts")), 49 | path, 50 | contents.into_bytes(), 51 | Some(String::from("莫娜圣遗物格式"))); 52 | }, 53 | GenshinArtifactExportFormat::MingyuLab => { 54 | let path = self.output_dir.join("mingyulab.json"); 55 | let value = MingyuLabFormat::new(results); 56 | let contents = serde_json::to_string(&value).unwrap(); 57 | 58 | export_assets.add_asset( 59 | Some(String::from("artifacts")), 60 | path, 61 | contents.into_bytes(), 62 | Some(String::from("原魔计算器圣遗物格式"))); 63 | }, 64 | GenshinArtifactExportFormat::Good => { 65 | let path = self.output_dir.join("good.json"); 66 | let value = GOODFormat::new(results); 67 | let contents = serde_json::to_string(&value).unwrap(); 68 | 69 | export_assets.add_asset( 70 | Some(String::from("artifacts")), 71 | path, 72 | contents.into_bytes(), 73 | Some(String::from("GOOD圣遗物格式"))); 74 | }, 75 | GenshinArtifactExportFormat::CSV => { 76 | let path = self.output_dir.join("artifacts.csv"); 77 | let value = GenshinArtifactCSVFormat::new(results); 78 | let contents = value.to_csv_string(); 79 | export_assets.add_asset( 80 | Some(String::from("artifacts csv format")), 81 | path, 82 | contents.into_bytes(), 83 | Some(String::from("CSV格式圣遗物")) 84 | ); 85 | }, 86 | GenshinArtifactExportFormat::All => { 87 | // mona 88 | { 89 | let path = self.output_dir.join("mona.json"); 90 | let value = MonaFormat::new(results); 91 | let contents = serde_json::to_string(&value).unwrap(); 92 | 93 | export_assets.add_asset( 94 | Some(String::from("mona")), 95 | path, 96 | contents.into_bytes(), 97 | Some(String::from("莫娜圣遗物格式"))); 98 | } 99 | // mingyulab 100 | { 101 | let path = self.output_dir.join("mingyulab.json"); 102 | let value = MingyuLabFormat::new(results); 103 | let contents = serde_json::to_string(&value).unwrap(); 104 | 105 | export_assets.add_asset( 106 | Some(String::from("mingyulab")), 107 | path, 108 | contents.into_bytes(), 109 | Some(String::from("原魔计算器圣遗物格式"))); 110 | } 111 | // good 112 | { 113 | let path = self.output_dir.join("good.json"); 114 | let value = GOODFormat::new(results); 115 | let contents = serde_json::to_string(&value).unwrap(); 116 | 117 | export_assets.add_asset( 118 | Some(String::from("GOOD")), 119 | path, 120 | contents.into_bytes(), 121 | Some(String::from("GOOD圣遗物格式"))); 122 | } 123 | // csv 124 | { 125 | let path = self.output_dir.join("artifacts.csv"); 126 | let value = GenshinArtifactCSVFormat::new(results); 127 | let contents = value.to_csv_string(); 128 | export_assets.add_asset( 129 | Some(String::from("csv")), 130 | path, 131 | contents.into_bytes(), 132 | Some(String::from("CSV格式圣遗物")) 133 | ); 134 | } 135 | } 136 | }; 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /yas/src/utils/windows.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::{OsStr, OsString}; 2 | use std::iter::once; 3 | use std::marker::PhantomPinned; 4 | use std::mem::transmute; 5 | use std::os::windows::ffi::{OsStringExt, OsStrExt}; 6 | use std::pin::{Pin, pin}; 7 | use std::ptr::{null, null_mut, slice_from_raw_parts_mut}; 8 | 9 | use anyhow::{anyhow, Result}; 10 | use log::{info, warn}; 11 | use windows_sys::Win32::Foundation::*; 12 | use windows_sys::Win32::Graphics::Gdi::ClientToScreen; 13 | use windows_sys::Win32::Security::*; 14 | use windows_sys::Win32::UI::Input::KeyboardAndMouse::*; 15 | use windows_sys::Win32::UI::WindowsAndMessaging::*; 16 | use windows_sys::Win32::System::SystemServices::*; 17 | use windows_sys::Win32::System::LibraryLoader::*; 18 | use crate::positioning::Rect; 19 | 20 | pub fn encode_lpcstr(s: &str) -> Vec { 21 | let mut arr: Vec = s.bytes().map(|x| x as u8).collect(); 22 | arr.push(0); 23 | arr 24 | } 25 | 26 | fn encode_wide_with_null(s: impl AsRef) -> Vec { 27 | let wide: Vec = OsStr::new(s.as_ref()) 28 | .encode_wide() 29 | .chain(once(0)) 30 | .collect(); 31 | wide 32 | } 33 | 34 | pub fn find_window_local(title: impl AsRef) -> Result { 35 | let title = encode_wide_with_null(title); 36 | let class = encode_wide_with_null("UnityWndClass"); 37 | let result: HWND = unsafe { FindWindowW(class.as_ptr(), title.as_ptr()) }; 38 | if result.is_null() { 39 | Err(anyhow!("cannot find window")) 40 | } else { 41 | Ok(result) 42 | } 43 | } 44 | 45 | pub fn find_window_cloud() -> Result { 46 | let title = encode_wide_with_null(String::from("云·原神")); 47 | //let class = encode_wide(String::from("Qt5152QWindowIcon")); 48 | unsafe { 49 | let mut result: HWND = null_mut(); 50 | for _ in 0..3 { 51 | result = FindWindowExW(null_mut(), result, null_mut(), title.as_ptr()); 52 | let exstyle = GetWindowLongPtrW(result, GWL_EXSTYLE); 53 | let style = GetWindowLongPtrW(result, GWL_STYLE); 54 | if exstyle == 0x0 && style == 0x96080000 { 55 | return Ok(result); //全屏 56 | } else if exstyle == 0x100 && style == 0x96CE0000 { 57 | return Ok(result); //窗口 58 | } 59 | } 60 | } 61 | Err(anyhow!("cannot find window")) 62 | } 63 | 64 | unsafe fn get_client_rect_unsafe(hwnd: HWND) -> Result> { 65 | let mut rect: RECT = RECT { 66 | left: 0, 67 | top: 0, 68 | right: 0, 69 | bottom: 0, 70 | }; 71 | GetClientRect(hwnd, &mut rect); 72 | let width: i32 = rect.right; 73 | let height: i32 = rect.bottom; 74 | 75 | let mut point: POINT = POINT { x: 0, y: 0 }; 76 | ClientToScreen(hwnd, &mut point as *mut POINT); 77 | let left: i32 = point.x; 78 | let top: i32 = point.y; 79 | 80 | Ok(Rect { 81 | left, 82 | top, 83 | width, 84 | height 85 | }) 86 | } 87 | 88 | pub fn get_client_rect(hwnd: HWND) -> Result> { 89 | unsafe { get_client_rect_unsafe(hwnd) } 90 | } 91 | 92 | unsafe fn is_admin_unsafe() -> bool { 93 | let mut authority: SID_IDENTIFIER_AUTHORITY = SID_IDENTIFIER_AUTHORITY { 94 | Value: [0, 0, 0, 0, 0, 5], 95 | }; 96 | let mut group: PSID = null_mut(); 97 | let mut b = AllocateAndInitializeSid( 98 | &mut authority as *mut SID_IDENTIFIER_AUTHORITY, 99 | 2, 100 | SECURITY_BUILTIN_DOMAIN_RID as u32, 101 | DOMAIN_ALIAS_RID_ADMINS as u32, 102 | 0, 103 | 0, 104 | 0, 105 | 0, 106 | 0, 107 | 0, 108 | &mut group as *mut PSID, 109 | ); 110 | if b != 0 { 111 | let r = CheckTokenMembership(null_mut(), group, &mut b as *mut BOOL); 112 | if r == 0 { 113 | b = 0; 114 | } 115 | FreeSid(group); 116 | } 117 | 118 | b != 0 119 | } 120 | 121 | pub fn is_admin() -> bool { 122 | unsafe { is_admin_unsafe() } 123 | } 124 | 125 | pub fn is_rmb_down() -> bool { 126 | unsafe { 127 | let state = GetAsyncKeyState(VK_RBUTTON as i32); 128 | if state == 0 { 129 | return false; 130 | } 131 | 132 | state & 1 > 0 133 | } 134 | } 135 | 136 | pub fn set_dpi_awareness() { 137 | let h_lib = unsafe { 138 | let utf16 = encode_lpcstr("Shcore.dll"); 139 | LoadLibraryA(utf16.as_ptr()) 140 | }; 141 | println!("{:?}", h_lib); 142 | if h_lib.is_null() { 143 | unsafe { 144 | SetProcessDPIAware(); 145 | } 146 | } else { 147 | unsafe { 148 | let addr = GetProcAddress(h_lib, encode_lpcstr("SetProcessDpiAwareness").as_ptr()); 149 | println!("{:?}", addr); 150 | if addr.is_none() { 151 | warn!("cannot find process `SetProcessDpiAwareness`, but `Shcore.dll` exists"); 152 | SetProcessDPIAware(); 153 | } else { 154 | let proc = addr.unwrap(); 155 | let func = transmute:: isize, unsafe extern "system" fn(usize) -> isize>(proc); 156 | func(2); 157 | } 158 | 159 | FreeLibrary(h_lib); 160 | } 161 | } 162 | } 163 | 164 | pub fn show_window_and_set_foreground(hwnd: HWND) { 165 | unsafe { 166 | ShowWindow(hwnd, SW_RESTORE); 167 | SetForegroundWindow(hwnd); 168 | } 169 | } 170 | 171 | unsafe fn iterate_window_unsafe() -> Vec { 172 | static mut ALL_HANDLES: Vec = Vec::new(); 173 | 174 | extern "system" fn callback(hwnd: HWND, vec_ptr: LPARAM) -> BOOL { 175 | unsafe { 176 | ALL_HANDLES.push(hwnd); 177 | } 178 | 1 179 | } 180 | 181 | ALL_HANDLES.clear(); 182 | EnumWindows(Some(callback), 0); 183 | 184 | ALL_HANDLES.clone() 185 | } 186 | 187 | pub fn iterate_window() -> Vec { 188 | unsafe { 189 | iterate_window_unsafe() 190 | } 191 | } 192 | 193 | unsafe fn get_window_title_unsafe(hwnd: HWND) -> Option { 194 | let mut buffer: Vec = vec![0; 100]; 195 | GetWindowTextW(hwnd, buffer.as_mut_ptr(), 100); 196 | 197 | let s = OsString::from_wide(&buffer); 198 | 199 | if let Some(ss) = s.into_string().ok() { 200 | let ss = ss.trim_matches(char::from(0)); 201 | Some(String::from(ss)) 202 | } else { 203 | None 204 | } 205 | } 206 | 207 | pub fn get_window_title(hwnd: HWND) -> Option { 208 | unsafe { 209 | get_window_title_unsafe(hwnd) 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /yas/src/utils/macos.rs: -------------------------------------------------------------------------------- 1 | use crate::common::*; 2 | use cocoa::{ 3 | appkit::CGFloat, 4 | base::NO, 5 | foundation::{NSAutoreleasePool, NSPoint, NSSize}, 6 | }; 7 | use enigo::*; 8 | 9 | pub fn mac_scroll(enigo: &mut Enigo, length: i32, delta: i32, times: i32) { 10 | for _j in 0..length { 11 | enigo.mouse_down(MouseButton::Left); 12 | for _i in 0..times { 13 | enigo.mouse_move_relative(0, -delta); 14 | utils::sleep(10); 15 | } 16 | 17 | enigo.mouse_up(MouseButton::Left); 18 | utils::sleep(10); 19 | 20 | enigo.mouse_down(MouseButton::Left); 21 | utils::sleep(5); 22 | enigo.mouse_up(MouseButton::Left); 23 | utils::sleep(5); 24 | 25 | enigo.mouse_move_relative(0, times * delta); 26 | utils::sleep(20); 27 | } 28 | } 29 | 30 | pub fn mac_scroll_fast(enigo: &mut Enigo, length: i32) { 31 | mac_scroll(enigo, length, 4, 30); 32 | } 33 | 34 | pub fn mac_scroll_slow(enigo: &mut Enigo, length: i32) { 35 | mac_scroll(enigo, length, 4, 5); 36 | } 37 | 38 | pub fn get_titlebar_height() -> f64 { 39 | use cocoa::appkit::{NSBackingStoreBuffered, NSWindow, NSWindowStyleMask}; 40 | use cocoa::base::nil; 41 | use cocoa::foundation::NSRect; 42 | let ns_point = NSPoint::new(100 as CGFloat, 100 as CGFloat); 43 | let ns_size = NSSize::new(100 as CGFloat, 100 as CGFloat); 44 | // create NSWindow 45 | let ns_rect = NSRect::new(ns_point, ns_size); 46 | let ns_window = unsafe { 47 | NSWindow::alloc(nil) 48 | .initWithContentRect_styleMask_backing_defer_( 49 | ns_rect, 50 | NSWindowStyleMask::NSTitledWindowMask, 51 | NSBackingStoreBuffered, 52 | NO, 53 | ) 54 | .autorelease() 55 | }; 56 | println!("{}", unsafe { 57 | ns_size.height - ns_window.contentRectForFrameRect_(ns_rect).size.height 58 | }); 59 | unsafe { ns_size.height - ns_window.contentRectForFrameRect_(ns_rect).size.height } 60 | } 61 | 62 | pub fn get_pid_and_ui() -> (i32, UI) { 63 | let pid_str_playcover = unsafe { 64 | String::from_utf8_unchecked( 65 | std::process::Command::new("sh") 66 | .arg("-c") 67 | .arg(r#"ps -Aj | grep "PlayCover/" | cut -f 2 -w | head -n 1"#) 68 | .output() 69 | .unwrap() 70 | .stdout, 71 | ) 72 | }; 73 | 74 | let pid_str_wine = unsafe { 75 | String::from_utf8_unchecked( 76 | std::process::Command::new("sh") 77 | .arg("-c") 78 | .arg(r#"top -l 1 -o mem | grep wine64-preloader | head -n 1 | sed 's/^[ ]*//' | cut -d ' ' -f 1"#) 79 | .output() 80 | .unwrap() 81 | .stdout, 82 | ) 83 | }; 84 | 85 | match pid_str_playcover.trim().parse::() { 86 | Ok(pid) => (pid, UI::Mobile), 87 | Err(_) => match pid_str_wine.trim().parse::() { 88 | Ok(pid) => (pid, UI::Desktop), 89 | Err(_) => crate::error_and_quit!("No game program found"), 90 | }, 91 | } 92 | } 93 | 94 | #[allow(clippy::default_constructed_unit_structs)] 95 | pub fn request_capture_access() -> bool { 96 | use core_graphics::access::ScreenCaptureAccess; 97 | 98 | let access = ScreenCaptureAccess::default(); 99 | 100 | access.preflight() || access.request() 101 | } 102 | 103 | pub unsafe fn find_window_by_pid(pid: i32) -> Result<(Rect, String), String> { 104 | use core_foundation::array::{CFArrayGetCount, CFArrayGetValueAtIndex}; 105 | use core_foundation::base::TCFType; 106 | use core_foundation::dictionary::{ 107 | CFDictionary, CFDictionaryGetValueIfPresent, CFDictionaryRef, 108 | }; 109 | use core_foundation::number::{CFNumber, CFNumberRef}; 110 | use core_foundation::string::{CFString, CFStringRef}; 111 | use core_graphics::geometry::CGRect; 112 | use core_graphics::window::{ 113 | kCGNullWindowID, kCGWindowBounds, kCGWindowListOptionExcludeDesktopElements, 114 | kCGWindowOwnerName, kCGWindowOwnerPID, CGWindowListCopyWindowInfo, 115 | }; 116 | 117 | use std::ffi::c_void; 118 | 119 | let cf_win_array = 120 | CGWindowListCopyWindowInfo(kCGWindowListOptionExcludeDesktopElements, kCGNullWindowID); 121 | let count = CFArrayGetCount(cf_win_array); 122 | 123 | if count == 0 { 124 | return Err("No game window found".to_string()); 125 | } 126 | 127 | let mut mrect = Rect::default(); 128 | let mut window_count = 0; 129 | let mut title: String = String::new(); 130 | 131 | for i in 0..count { 132 | let win_info_ref: CFDictionaryRef = 133 | CFArrayGetValueAtIndex(cf_win_array, i) as CFDictionaryRef; 134 | let mut test_pid_ref: *const c_void = std::ptr::null_mut(); 135 | assert!( 136 | CFDictionaryGetValueIfPresent( 137 | win_info_ref, 138 | kCGWindowOwnerPID as *const c_void, 139 | &mut test_pid_ref 140 | ) != 0 141 | ); 142 | let test_pid = CFNumber::wrap_under_get_rule(test_pid_ref as CFNumberRef); 143 | 144 | if pid == test_pid.to_i32().unwrap() { 145 | let mut cg_bounds_dict_ref: *const c_void = std::ptr::null_mut(); 146 | CFDictionaryGetValueIfPresent( 147 | win_info_ref, 148 | kCGWindowBounds as *const c_void, 149 | &mut cg_bounds_dict_ref, 150 | ); 151 | let cg_bounds_dict = 152 | CFDictionary::wrap_under_get_rule(cg_bounds_dict_ref as CFDictionaryRef); 153 | let cg_rect = CGRect::from_dict_representation(&cg_bounds_dict).unwrap(); 154 | 155 | let mut cg_title_ref: *const c_void = std::ptr::null_mut(); 156 | CFDictionaryGetValueIfPresent( 157 | win_info_ref, 158 | kCGWindowOwnerName as *const c_void, 159 | &mut cg_title_ref, 160 | ); 161 | let cg_title = CFString::wrap_under_get_rule(cg_title_ref as CFStringRef); 162 | title = cg_title.to_string(); 163 | if cg_rect.size.height > 200. { 164 | mrect = if cg_rect.origin.y > 0. { 165 | // Window Mode 166 | Rect::from(cg_rect).with_titlebar(get_titlebar_height() as u32) 167 | } else { 168 | Rect::from(cg_rect) 169 | }; 170 | window_count += 1 171 | } 172 | } 173 | } 174 | if window_count > 0 { 175 | Ok((mrect, title)) 176 | } else { 177 | Err("No genshin window found".to_string()) 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /yas-starrail/src/scanner/relic_scanner/relic_scanner_worker.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | use std::sync::mpsc::Receiver; 3 | use std::thread::JoinHandle; 4 | 5 | use anyhow::Result; 6 | use image::{GenericImageView, RgbImage}; 7 | use log::{error, info, warn}; 8 | 9 | use yas::ocr::{yas_ocr_model, ImageToText}; 10 | use yas::positioning::{Pos, Rect}; 11 | 12 | use crate::scanner::relic_scanner::message_items::SendItem; 13 | use crate::scanner::relic_scanner::relic_scanner_window_info::RelicScannerWindowInfo; 14 | use crate::scanner::relic_scanner::scan_result::StarRailRelicScanResult; 15 | use crate::scanner::relic_scanner::StarRailRelicScannerConfig; 16 | 17 | pub struct RelicScannerWorker { 18 | model: Box + Send>, 19 | window_info: RelicScannerWindowInfo, 20 | config: StarRailRelicScannerConfig, 21 | } 22 | 23 | fn parse_level(s: &str) -> Result { 24 | let pos = s.find('+'); 25 | 26 | if pos.is_none() { 27 | let level = s.parse::()?; 28 | return Ok(level); 29 | } 30 | 31 | let level = s[pos.unwrap()..].parse::()?; 32 | return Ok(level); 33 | } 34 | 35 | fn get_image_to_text() -> Result + Send>> { 36 | let model: Box + Send> = Box::new( 37 | yas_ocr_model!("./models/model_training.onnx", "./models/index_2_word.json")? 38 | ); 39 | // let model: Box + Send> = Box::new(PPOCRChV4RecInfer::new()?); 40 | Ok(model) 41 | } 42 | 43 | impl RelicScannerWorker { 44 | pub fn new( 45 | window_info: RelicScannerWindowInfo, 46 | config: StarRailRelicScannerConfig, 47 | ) -> Result { 48 | Ok(RelicScannerWorker { 49 | model: get_image_to_text()?, 50 | window_info, 51 | config, 52 | }) 53 | } 54 | 55 | fn model_inference(&self, rect: Rect, captured_img: &RgbImage) -> Result { 56 | let relative_rect = rect.translate(Pos { 57 | x: -self.window_info.panel_rect.left, 58 | y: -self.window_info.panel_rect.top, 59 | }); 60 | 61 | let raw_img = captured_img.view( 62 | relative_rect.left as u32, relative_rect.top as u32, relative_rect.width as u32, relative_rect.height as u32 63 | ).to_image(); 64 | 65 | let inference_result = self.model.image_to_text(&raw_img, false); 66 | 67 | inference_result 68 | } 69 | 70 | fn scan_item_image(&self, item: SendItem) -> Result { 71 | let image = &item.panel_image; 72 | 73 | let str_title = self.model_inference(self.window_info.title_rect, &image)?; 74 | let str_main_stat_name = self.model_inference(self.window_info.main_stat_name_rect, &image)?; 75 | let str_main_stat_value = self.model_inference(self.window_info.main_stat_value_rect, &image)?; 76 | 77 | let str_sub_stat0_name = self.model_inference(self.window_info.sub_stat_name_1, &image)?; 78 | let str_sub_stat1_name = self.model_inference(self.window_info.sub_stat_name_2, &image)?; 79 | let str_sub_stat2_name = self.model_inference(self.window_info.sub_stat_name_3, &image)?; 80 | let str_sub_stat3_name = self.model_inference(self.window_info.sub_stat_name_4, &image)?; 81 | let str_sub_stat0_value = self.model_inference(self.window_info.sub_stat_value_1, &image)?; 82 | let str_sub_stat1_value = self.model_inference(self.window_info.sub_stat_value_2, &image)?; 83 | let str_sub_stat2_value = self.model_inference(self.window_info.sub_stat_value_3, &image)?; 84 | let str_sub_stat3_value = self.model_inference(self.window_info.sub_stat_value_4, &image)?; 85 | 86 | let str_level = self.model_inference(self.window_info.level_rect, &image)?; 87 | let str_equip = self.model_inference(self.window_info.equip_rect, &image)?; 88 | 89 | Ok(StarRailRelicScanResult { 90 | name: str_title, 91 | main_stat_name: str_main_stat_name, 92 | main_stat_value: str_main_stat_value, 93 | sub_stat_name: [ 94 | str_sub_stat0_name, 95 | str_sub_stat1_name, 96 | str_sub_stat2_name, 97 | str_sub_stat3_name, 98 | ], 99 | sub_stat_value: [ 100 | str_sub_stat0_value, 101 | str_sub_stat1_value, 102 | str_sub_stat2_value, 103 | str_sub_stat3_value, 104 | ], 105 | level: parse_level(&str_level)?, 106 | equip: item.equip + &str_equip, 107 | star: item.star as i32, 108 | lock: item.lock, 109 | discard: item.discard, 110 | }) 111 | } 112 | 113 | pub fn run(self, rx: Receiver>) -> JoinHandle> { 114 | std::thread::spawn(move || { 115 | let mut results = Vec::new(); 116 | let mut hash = HashSet::new(); 117 | let mut consecutive_dup_count = 0; 118 | 119 | let is_verbose = self.config.verbose; 120 | let min_level = self.config.min_level; 121 | let info = self.window_info.clone(); 122 | 123 | for (_cnt, item) in rx.into_iter().enumerate() { 124 | let item = match item { 125 | Some(v) => v, 126 | None => break, 127 | }; 128 | 129 | let result = match self.scan_item_image(item) { 130 | Ok(v) => v, 131 | Err(e) => { 132 | error!("识别错误: {}", e); 133 | continue; 134 | }, 135 | }; 136 | 137 | if is_verbose { 138 | info!("{:?}", result); 139 | } 140 | 141 | if result.level < min_level { 142 | info!( 143 | "找到满足最低等级要求 {} 的物品({}),准备退出……", 144 | min_level, result.level 145 | ); 146 | // token.cancel(); 147 | break; 148 | } 149 | 150 | if hash.contains(&result) { 151 | consecutive_dup_count += 1; 152 | warn!("识别到重复物品: {:#?}", result); 153 | } else { 154 | consecutive_dup_count = 0; 155 | hash.insert(result.clone()); 156 | results.push(result); 157 | } 158 | 159 | if consecutive_dup_count >= info.col && !self.config.ignore_dup { 160 | error!("识别到连续多个重复物品,可能为翻页错误,或者为非背包顶部开始扫描"); 161 | break; 162 | } 163 | } 164 | 165 | info!("识别结束,非重复物品数量: {}", hash.len()); 166 | 167 | results 168 | }) 169 | } 170 | } --------------------------------------------------------------------------------