From 0395cbc9424402fab9a8adbdd0f195265ff65062 Mon Sep 17 00:00:00 2001 From: hheik <4469778+hheik@users.noreply.github.com> Date: Sat, 1 Apr 2023 11:23:25 +0300 Subject: [PATCH] added file-watcher for memory dumps --- Cargo.lock | 12 +++++ Cargo.toml | 2 + src/ivpeek.rs | 3 ++ src/ivpeek/battle.rs | 6 +++ src/ivpeek/inspector.rs | 78 ++++++------------------------- src/ivpeek/memory_reader.rs | 91 +++++++++++++++++++++++++++++++++++++ src/ivpeek/ui.rs | 4 +- 7 files changed, 130 insertions(+), 66 deletions(-) create mode 100644 src/ivpeek/battle.rs create mode 100644 src/ivpeek/memory_reader.rs diff --git a/Cargo.lock b/Cargo.lock index b2c75f6..ccce814 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2557,6 +2557,16 @@ dependencies = [ "windows-sys 0.42.0", ] +[[package]] +name = "notify-debouncer-mini" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e23e9fa24f094b143c1eb61f90ac6457de87be6987bc70746e0179f7dbc9007b" +dependencies = [ + "crossbeam-channel", + "notify", +] + [[package]] name = "ntapi" version = "0.4.0" @@ -3293,6 +3303,8 @@ dependencies = [ "bevy_egui", "json", "lazy_static", + "notify", + "notify-debouncer-mini", "reqwest", ] diff --git a/Cargo.toml b/Cargo.toml index 014f25e..1f3e14c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,8 @@ bevy = "0.10.0" bevy_egui = "0.20.1" json = "0.12.4" lazy_static = "1.4.0" +notify = "5.1.0" +notify-debouncer-mini = "0.2.1" reqwest = { version = "0.11.14", features = ["blocking", "json"] } # Enable a small amount of optimization in debug mode diff --git a/src/ivpeek.rs b/src/ivpeek.rs index b622eb0..1513c7c 100644 --- a/src/ivpeek.rs +++ b/src/ivpeek.rs @@ -1,6 +1,8 @@ use bevy::{prelude::*, window::WindowResolution}; +mod battle; mod inspector; +mod memory_reader; mod ui; pub fn run() { @@ -21,5 +23,6 @@ pub fn run() { .add_plugin(crate::database::DatabasePlugin) .add_plugin(inspector::InspectorPlugin) .add_plugin(ui::UiPlugin) + .add_plugin(memory_reader::MemoryReaderPlugin) .run() } diff --git a/src/ivpeek/battle.rs b/src/ivpeek/battle.rs new file mode 100644 index 0000000..6d0688a --- /dev/null +++ b/src/ivpeek/battle.rs @@ -0,0 +1,6 @@ +use super::inspector::Battle; + +pub fn parse_battle_party(data: &Vec) -> Result { + let mut result = Battle::default(); + Ok(result) +} diff --git a/src/ivpeek/inspector.rs b/src/ivpeek/inspector.rs index bd0d2bc..ea0757c 100644 --- a/src/ivpeek/inspector.rs +++ b/src/ivpeek/inspector.rs @@ -3,10 +3,7 @@ use std::collections::HashMap; use bevy::prelude::*; use bevy_egui::EguiContexts; -use crate::{ - database::{load_url_asset, Database}, - pokemon::*, -}; +use crate::{database::load_url_asset, pokemon::*}; use super::ui::{EguiAsset, UiAssets}; @@ -15,25 +12,22 @@ pub struct InspectorPlugin; impl Plugin for InspectorPlugin { fn build(&self, app: &mut App) { app.insert_resource(Inspector::default()) - .add_system(state_changed) - .add_startup_system(test_init); + .add_system(state_changed); } } #[derive(Resource, Default)] pub struct Inspector { - pub allies: Vec, - pub enemies: Vec, + pub battle: Battle, } -impl Inspector { - pub fn clear(&mut self) { - self.allies.clear(); - self.enemies.clear(); - } +#[derive(Default, Clone)] +pub struct Battle { + pub player_party: Vec, + pub enemy_party: Vec, } -#[derive(Default)] +#[derive(Default, Clone)] pub struct PokemonInstance { pub pokemon: Pokemon, pub nature: Option, @@ -41,55 +35,6 @@ pub struct PokemonInstance { pub evs: HashMap, } -fn test_init(mut inspector: ResMut, pokemon_database: Res>) { - for name in vec!["baltoy"].iter() { - inspector.enemies.push(PokemonInstance { - pokemon: pokemon_database.map.get(*name).unwrap().clone(), - ivs: { - let mut result = HashMap::new(); - for stat in BaseStat::all().iter() { - result.insert( - *stat, - match stat { - BaseStat::Hp => 31, - BaseStat::Attack => 27, - BaseStat::Defense => 26, - BaseStat::SpecialAttack => 0, - BaseStat::SpecialDefense => 21, - BaseStat::Speed => 15, - }, - ); - } - result - }, - ..default() - }); - } - for name in vec!["seadra", "sceptile", "ampharos", "shedinja"].iter() { - inspector.allies.push(PokemonInstance { - pokemon: pokemon_database.map.get(*name).unwrap().clone(), - ivs: { - let mut result = HashMap::new(); - for stat in BaseStat::all().iter() { - result.insert( - *stat, - match stat { - BaseStat::Hp => 31, - BaseStat::Attack => 27, - BaseStat::Defense => 26, - BaseStat::SpecialAttack => 0, - BaseStat::SpecialDefense => 21, - BaseStat::Speed => 15, - }, - ); - } - result - }, - ..default() - }); - } -} - fn state_changed( inspector: Res, assets: Res, @@ -97,7 +42,12 @@ fn state_changed( mut contexts: EguiContexts, ) { if inspector.is_changed() { - for instance in inspector.enemies.iter().chain(inspector.allies.iter()) { + for instance in inspector + .battle + .enemy_party + .iter() + .chain(inspector.battle.player_party.iter()) + { let pokemon = instance.pokemon.clone(); if !ui_assets.sprite_map.contains_key(&pokemon.key()) { diff --git a/src/ivpeek/memory_reader.rs b/src/ivpeek/memory_reader.rs new file mode 100644 index 0000000..27c8bc1 --- /dev/null +++ b/src/ivpeek/memory_reader.rs @@ -0,0 +1,91 @@ +use bevy::prelude::*; +use notify_debouncer_mini::new_debouncer; +use std::{ + fs, + sync::{Arc, Mutex}, + thread, + time::Duration, +}; + +use super::{ + battle::parse_battle_party, + inspector::{Battle, Inspector}, +}; + +pub struct MemoryReaderPlugin; + +impl Plugin for MemoryReaderPlugin { + fn build(&self, app: &mut App) { + app.add_event::() + .insert_resource(EventQueue::default()) + .add_startup_system(init_memory_reader) + .add_system(flush_updates) + .add_system(update_inspector); + } +} + +#[derive(Default)] +pub struct ParsedBattleEvent(Battle); + +#[derive(Resource, Default)] +struct EventQueue { + updates: Arc>>, +} + +fn flush_updates(queue: Res, mut events: EventWriter) { + match queue.updates.lock() { + Ok(mut updates) => { + for update in updates.drain(..) { + events.send(update) + } + } + Err(err) => { + error!("{err}"); + } + } +} + +fn update_inspector(mut inspector: ResMut, mut events: EventReader) { + for event in events.iter() { + inspector.battle = event.0.clone(); + } +} + +fn init_memory_reader(queue: Res) { + let updates = queue.updates.clone(); + + thread::spawn(move || { + let watch_path = std::path::Path::new("/tmp/ivpeek/"); + fs::create_dir_all(watch_path).expect("Failed to create path for {watch_path:?}"); + + let (tx, rx) = std::sync::mpsc::channel(); + + let mut debouncer = new_debouncer(Duration::from_millis(1_000), None, tx) + .expect("Could not create notify debouncer"); + + debouncer + .watcher() + .watch(watch_path, notify::RecursiveMode::NonRecursive) + .expect("Could not watch for {watch_path:?}"); + + for result in rx { + match result { + Ok(events) => { + for event in events.iter() { + match fs::read(&event.path) { + Ok(data) => match parse_battle_party(&data) { + Ok(battle) => match updates.lock() { + Ok(mut updates) => updates.push(ParsedBattleEvent(battle)), + Err(err) => error!("{err}"), + }, + Err(err) => error!("Failed to parse party data: ${err}"), + }, + _ => (), + } + } + } + Err(err) => error!("Watcher error: {err:?}"), + } + } + }); +} diff --git a/src/ivpeek/ui.rs b/src/ivpeek/ui.rs index cb69355..6e6f1df 100644 --- a/src/ivpeek/ui.rs +++ b/src/ivpeek/ui.rs @@ -74,7 +74,7 @@ fn ui_system(mut contexts: EguiContexts, inspector: Res, ui_assets: R egui::CentralPanel::default().show(contexts.ctx_mut(), |ui| { egui::ScrollArea::vertical().show(ui, |ui| { ui.heading("Enemy team"); - for instance in inspector.enemies.iter() { + for instance in inspector.battle.enemy_party.iter() { ui.add_space(8.); render_pokemon_instance(ui, &ui_assets, instance); ui.add_space(8.); @@ -83,7 +83,7 @@ fn ui_system(mut contexts: EguiContexts, inspector: Res, ui_assets: R ui.separator(); ui.heading("Player team"); - for instance in inspector.allies.iter() { + for instance in inspector.battle.player_party.iter() { ui.add_space(8.); render_pokemon_instance(ui, &ui_assets, instance); ui.add_space(8.);