├── .github └── workflows │ ├── build.yml │ └── docs.yml ├── .gitignore ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── asr-derive ├── Cargo.toml └── src │ ├── lib.rs │ └── unity.rs └── src ├── deep_pointer.rs ├── emulator ├── gba │ ├── emuhawk.rs │ ├── mednafen.rs │ ├── mgba.rs │ ├── mod.rs │ ├── nocashgba.rs │ ├── retroarch.rs │ └── vba.rs ├── gcn │ ├── dolphin.rs │ ├── mod.rs │ └── retroarch.rs ├── genesis │ ├── blastem.rs │ ├── fusion.rs │ ├── gens.rs │ ├── mod.rs │ ├── retroarch.rs │ └── segaclassics.rs ├── mod.rs ├── ps1 │ ├── duckstation.rs │ ├── epsxe.rs │ ├── mednafen.rs │ ├── mod.rs │ ├── pcsx_redux.rs │ ├── psxfin.rs │ ├── retroarch.rs │ └── xebra.rs ├── ps2 │ ├── mod.rs │ ├── pcsx2.rs │ └── retroarch.rs ├── sms │ ├── blastem.rs │ ├── fusion.rs │ ├── mednafen.rs │ ├── mod.rs │ └── retroarch.rs └── wii │ ├── dolphin.rs │ ├── mod.rs │ └── retroarch.rs ├── file_format ├── elf.rs ├── mod.rs └── pe.rs ├── future ├── mod.rs ├── task.rs └── time.rs ├── game_engine ├── godot │ ├── core │ │ ├── mod.rs │ │ ├── object │ │ │ ├── mod.rs │ │ │ ├── object.rs │ │ │ ├── script_instance.rs │ │ │ └── script_language.rs │ │ ├── os │ │ │ ├── main_loop.rs │ │ │ └── mod.rs │ │ ├── string │ │ │ ├── mod.rs │ │ │ ├── string_name.rs │ │ │ └── ustring.rs │ │ ├── templates │ │ │ ├── cowdata.rs │ │ │ ├── hash_map.rs │ │ │ ├── hash_set.rs │ │ │ ├── hashfuncs.rs │ │ │ ├── list.rs │ │ │ ├── mod.rs │ │ │ └── vector.rs │ │ └── variant │ │ │ ├── mod.rs │ │ │ └── variant.rs │ ├── cpp │ │ ├── mod.rs │ │ ├── ptr.rs │ │ ├── type_info.rs │ │ └── vtable.rs │ ├── mod.rs │ ├── modules │ │ ├── gdscript │ │ │ ├── gdscript.rs │ │ │ └── mod.rs │ │ ├── mod.rs │ │ └── mono │ │ │ ├── csharp_script.rs │ │ │ └── mod.rs │ └── scene │ │ ├── main │ │ ├── canvas_item.rs │ │ ├── mod.rs │ │ ├── node.rs │ │ ├── scene_tree.rs │ │ ├── viewport.rs │ │ └── window.rs │ │ ├── mod.rs │ │ ├── three_d │ │ ├── collision_object_3d.rs │ │ ├── mod.rs │ │ ├── node_3d.rs │ │ └── physics_body_3d.rs │ │ └── two_d │ │ ├── collision_object_2d.rs │ │ ├── mod.rs │ │ ├── node_2d.rs │ │ └── physics_body_2d.rs ├── mod.rs ├── unity │ ├── il2cpp.rs │ ├── mod.rs │ ├── mono.rs │ └── scene.rs └── unreal │ └── mod.rs ├── lib.rs ├── panic.rs ├── primitives ├── address.rs ├── endian.rs └── mod.rs ├── runtime ├── memory_range.rs ├── mod.rs ├── process.rs ├── settings │ ├── gui.rs │ ├── list.rs │ ├── map.rs │ ├── mod.rs │ └── value.rs ├── sys.rs └── timer.rs ├── signature.rs ├── string.rs ├── sync.rs ├── time_util.rs ├── wasi_no_std.rs └── watcher.rs /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - 'master' 8 | tags: 9 | - '*' 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | strategy: 15 | fail-fast: false 16 | matrix: 17 | target: [wasm32-unknown-unknown, wasm32-wasip1] 18 | toolchain: [stable, nightly] 19 | steps: 20 | - name: Checkout Commit 21 | uses: actions/checkout@v4 22 | 23 | - name: Install Rust 24 | uses: hecrj/setup-rust-action@v2 25 | with: 26 | targets: ${{ matrix.target }} 27 | rust-version: ${{ matrix.toolchain }} 28 | 29 | - name: Build (No Default Features) 30 | run: | 31 | cargo build --no-default-features --target ${{ matrix.target }} 32 | 33 | - name: Build (Default Features) 34 | run: | 35 | cargo build --target ${{ matrix.target }} 36 | 37 | - name: Build (All Features) 38 | run: | 39 | cargo build --all-features --target ${{ matrix.target }} 40 | 41 | test: 42 | name: Test (Host) 43 | runs-on: ubuntu-latest 44 | steps: 45 | - name: Checkout Commit 46 | uses: actions/checkout@v4 47 | 48 | - name: Install Rust 49 | uses: hecrj/setup-rust-action@v2 50 | 51 | - name: Test (All Features) 52 | run: | 53 | cargo test --all-features 54 | 55 | clippy: 56 | name: Check clippy lints 57 | runs-on: ubuntu-latest 58 | steps: 59 | - name: Checkout Commit 60 | uses: actions/checkout@v4 61 | 62 | - name: Install Rust 63 | uses: hecrj/setup-rust-action@v2 64 | with: 65 | components: clippy 66 | 67 | - name: Run Clippy 68 | run: cargo clippy --all-features 69 | 70 | format: 71 | name: Check formatting 72 | runs-on: ubuntu-latest 73 | steps: 74 | - name: Checkout Commit 75 | uses: actions/checkout@v4 76 | 77 | - name: Install Rust 78 | uses: hecrj/setup-rust-action@v2 79 | with: 80 | components: rustfmt 81 | 82 | - name: Run cargo fmt 83 | run: cargo fmt -- --check || true 84 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | # Simple workflow for deploying static content to GitHub Pages 2 | name: Deploy docs 3 | 4 | on: 5 | # Runs on pushes targeting the default branch 6 | push: 7 | branches: ["master"] 8 | 9 | # Allows you to run this workflow manually from the Actions tab 10 | workflow_dispatch: 11 | 12 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 13 | permissions: 14 | contents: read 15 | pages: write 16 | id-token: write 17 | 18 | # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. 19 | # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. 20 | concurrency: 21 | group: "pages" 22 | cancel-in-progress: false 23 | 24 | jobs: 25 | # Single deploy job since we're just deploying 26 | deploy: 27 | environment: 28 | name: github-pages 29 | url: ${{ steps.deployment.outputs.page_url }} 30 | runs-on: ubuntu-latest 31 | steps: 32 | - name: Checkout 33 | uses: actions/checkout@v3 34 | 35 | - name: Install Rust 36 | uses: hecrj/setup-rust-action@v1 37 | with: 38 | components: rust-docs 39 | rust-version: nightly 40 | targets: wasm32-wasip1 41 | 42 | - name: Build docs 43 | run: RUSTDOCFLAGS="--cfg doc_cfg" cargo doc --all-features --target wasm32-wasip1 44 | 45 | - name: Setup Pages 46 | uses: actions/configure-pages@v3 47 | 48 | - name: Fix file permissions 49 | shell: sh 50 | if: runner.os == 'Linux' 51 | run: | 52 | chmod -c -R +rX "$INPUT_PATH" | 53 | while read line; do 54 | echo "::warning title=Invalid file permissions automatically fixed::$line" 55 | done 56 | tar \ 57 | --dereference --hard-dereference \ 58 | --directory "$INPUT_PATH" \ 59 | -cvf "$RUNNER_TEMP/artifact.tar" \ 60 | --exclude=.git \ 61 | --exclude=.github \ 62 | . 63 | env: 64 | INPUT_PATH: target/wasm32-wasip1/doc 65 | 66 | - name: Upload artifact 67 | uses: actions/upload-pages-artifact@v1 68 | with: 69 | path: target/wasm32-wasip1/doc 70 | 71 | - name: Deploy to GitHub Pages 72 | id: deployment 73 | uses: actions/deploy-pages@v2 74 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "asr" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | arrayvec = { version = "0.7.2", default-features = false } 10 | asr-derive = { path = "asr-derive", optional = true } 11 | bitflags = { version = "2.2.1", optional = true } 12 | bytemuck = { version = "1.13.1", features = ["derive", "min_const_generics"] } 13 | itoa = { version = "1.0.1", default-features = false, optional = true } 14 | memchr = { version = "2.5.0", default-features = false, optional = true } 15 | ryu = { version = "1.0.11", default-features = false, optional = true } 16 | time = { version = "0.3.5", default-features = false } 17 | 18 | [target.'cfg(target_os = "wasi")'.dependencies] 19 | libm = { version = "0.2.7", optional = true } 20 | wasi = { version = "0.11.0+wasi-snapshot-preview1", default-features = false } 21 | 22 | [features] 23 | alloc = [] 24 | derive = ["asr-derive"] 25 | flags = ["bitflags"] 26 | float-vars = ["ryu"] 27 | float-vars-small = ["float-vars", "ryu/small"] 28 | integer-vars = ["itoa"] 29 | signature = ["memchr"] 30 | wasi-no-std = ["libm"] 31 | 32 | # Game Engines 33 | godot = [] 34 | unity = ["signature", "asr-derive?/unity"] 35 | unreal = ["signature"] 36 | 37 | # Emulators 38 | gba = ["flags", "signature"] 39 | gcn = ["flags"] 40 | genesis = ["flags", "signature"] 41 | ps1 = ["flags", "signature"] 42 | ps2 = ["flags", "signature"] 43 | sms = ["flags", "signature"] 44 | wii = ["flags"] 45 | 46 | [lints.rust] 47 | unexpected_cfgs = { level = "allow", check-cfg = ['cfg(doc_cfg)'] } 48 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Christopher Serr 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LiveSplit asr 2 | 3 | 4 | Helper crate to write auto splitters for LiveSplit One's auto splitting 5 | runtime. 6 | 7 | [API Documentation](https://livesplit.org/asr/asr/) 8 | 9 | There are two ways of defining an auto splitter. 10 | 11 | ## Defining an `update` function 12 | 13 | You can define an `update` function that will be called every frame. This is 14 | the simplest way to define an auto splitter. The function must have the 15 | following signature: 16 | ```rust 17 | #[no_mangle] 18 | pub extern "C" fn update() {} 19 | ``` 20 | 21 | The advantage of this approach is that you have full control over what 22 | happens on every tick of the runtime. However, it's much harder to keep 23 | state around as you need to store all state in global variables as you need 24 | to return out of the function on every tick. 25 | 26 | ### Example 27 | 28 | ```rust 29 | #[no_mangle] 30 | pub extern "C" fn update() { 31 | if let Some(process) = Process::attach("explorer.exe") { 32 | asr::print_message("Hello World!"); 33 | if let Ok(address) = process.get_module_address("explorer.exe") { 34 | if let Ok(value) = process.read::(address) { 35 | if value > 0 { 36 | asr::timer::start(); 37 | } 38 | } 39 | } 40 | } 41 | } 42 | ``` 43 | 44 | ## Defining an asynchronous `main` function 45 | 46 | You can use the `async_main` macro to define an asynchronous `main` 47 | function. 48 | 49 | Similar to using an `update` function, it is important to constantly yield 50 | back to the runtime to communicate that the auto splitter is still alive. 51 | All asynchronous code that you await automatically yields back to the 52 | runtime. However, if you want to write synchronous code, such as the main 53 | loop handling of a process on every tick, you can use the 54 | `next_tick` function to yield back to the runtime and 55 | continue on the next tick. 56 | 57 | The main low level abstraction is the `retry` function, which wraps any code 58 | that you want to retry until it succeeds, yielding back to the runtime between 59 | each try. 60 | 61 | So if you wanted to attach to a Process you could for example write: 62 | 63 | ```rust 64 | let process = retry(|| Process::attach("MyGame.exe")).await; 65 | ``` 66 | 67 | This will try to attach to the process every tick until it succeeds. This 68 | specific example is exactly how the `Process::wait_attach` method is 69 | implemented. So if you wanted to attach to any of multiple processes, you could 70 | for example write: 71 | 72 | ```rust 73 | let process = retry(|| { 74 | ["a.exe", "b.exe"].into_iter().find_map(Process::attach) 75 | }).await; 76 | ``` 77 | 78 | ### Example 79 | 80 | Here is a full example of how an auto splitter could look like using the 81 | `async_main` macro: 82 | 83 | Usage on stable Rust: 84 | ```rust 85 | async_main!(stable); 86 | ``` 87 | 88 | Usage on nightly Rust: 89 | ```rust 90 | #![feature(type_alias_impl_trait, const_async_blocks)] 91 | 92 | async_main!(nightly); 93 | ``` 94 | 95 | The asynchronous main function itself: 96 | ```rust 97 | async fn main() { 98 | // TODO: Set up some general state and settings. 99 | loop { 100 | let process = Process::wait_attach("explorer.exe").await; 101 | process.until_closes(async { 102 | // TODO: Load some initial information from the process. 103 | loop { 104 | // TODO: Do something on every tick. 105 | next_tick().await; 106 | } 107 | }).await; 108 | } 109 | } 110 | ``` 111 | 112 | ## License 113 | 114 | Licensed under either of 115 | * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or 116 | http://www.apache.org/licenses/LICENSE-2.0) 117 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or 118 | http://opensource.org/licenses/MIT) at your option. 119 | 120 | ### Contribution 121 | 122 | Unless you explicitly state otherwise, any contribution intentionally submitted 123 | for inclusion in the work by you shall be dual licensed as above, without any 124 | additional terms or conditions. 125 | -------------------------------------------------------------------------------- /asr-derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "asr-derive" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | heck = "0.4.0" 10 | proc-macro2 = "1.0.70" 11 | quote = "1.0.18" 12 | syn = { version = "2.0.41", features = ["full"] } 13 | 14 | [lib] 15 | proc-macro = true 16 | 17 | [features] 18 | unity = [] 19 | -------------------------------------------------------------------------------- /asr-derive/src/unity.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | use quote::{quote, quote_spanned, ToTokens}; 3 | use syn::{Data, DeriveInput, Expr, ExprLit, Ident, Lit, Meta}; 4 | 5 | pub fn process(input: TokenStream, mono_module: impl ToTokens) -> TokenStream { 6 | let ast: DeriveInput = syn::parse(input).unwrap(); 7 | 8 | let vis = ast.vis; 9 | 10 | let struct_data = match ast.data { 11 | Data::Struct(s) => s, 12 | _ => panic!("Only structs are supported"), 13 | }; 14 | 15 | let struct_name = ast.ident; 16 | let stuct_name_string = struct_name.to_string(); 17 | 18 | let binding_name = Ident::new(&format!("{struct_name}Binding"), struct_name.span()); 19 | 20 | let mut has_static = false; 21 | let mut is_fully_static = true; 22 | 23 | let mut field_names = Vec::new(); 24 | let mut lookup_names = Vec::new(); 25 | let mut field_types = Vec::new(); 26 | let mut field_reads = Vec::new(); 27 | for field in struct_data.fields { 28 | let field_name = field.ident.clone().unwrap(); 29 | let span = field_name.span(); 30 | let is_static = field.attrs.iter().any(|x| { 31 | let Meta::Path(path) = &x.meta else { 32 | return false; 33 | }; 34 | path.is_ident("static_field") 35 | }); 36 | field_reads.push(if is_static { 37 | quote_spanned! { span => 38 | process.read(self.static_table + self.#field_name).map_err(drop)? 39 | } 40 | } else { 41 | quote_spanned! { span => 42 | process.read(instance + self.#field_name).map_err(drop)? 43 | } 44 | }); 45 | let lookup_name = field 46 | .attrs 47 | .iter() 48 | .find_map(|x| { 49 | let Meta::NameValue(name_value) = &x.meta else { 50 | return None; 51 | }; 52 | if !name_value.path.is_ident("rename") { 53 | return None; 54 | } 55 | let Expr::Lit(ExprLit { 56 | lit: Lit::Str(name), 57 | .. 58 | }) = &name_value.value 59 | else { 60 | return None; 61 | }; 62 | Some(name.value()) 63 | }) 64 | .unwrap_or_else(|| field.ident.clone().unwrap().to_string()); 65 | has_static |= is_static; 66 | is_fully_static &= is_static; 67 | field_names.push(field_name); 68 | lookup_names.push(lookup_name); 69 | field_types.push(field.ty); 70 | } 71 | 72 | let static_table_field = if has_static { 73 | quote! { 74 | static_table: asr::Address, 75 | } 76 | } else { 77 | quote! {} 78 | }; 79 | 80 | let static_table_init = if has_static { 81 | quote! { 82 | static_table: class.wait_get_static_table(process, module).await, 83 | } 84 | } else { 85 | quote! {} 86 | }; 87 | 88 | let maybe_instance_param = if is_fully_static { 89 | quote! {} 90 | } else { 91 | quote! { , instance: asr::Address } 92 | }; 93 | 94 | quote! { 95 | #vis struct #binding_name { 96 | class: #mono_module::Class, 97 | #static_table_field 98 | #(#field_names: u32,)* 99 | } 100 | 101 | impl #struct_name { 102 | #vis async fn bind( 103 | process: &asr::Process, 104 | module: &#mono_module::Module, 105 | image: &#mono_module::Image, 106 | ) -> #binding_name { 107 | let class = image.wait_get_class(process, module, #stuct_name_string).await; 108 | 109 | #( 110 | let #field_names = class.wait_get_field_offset(process, module, #lookup_names).await; 111 | )* 112 | 113 | #binding_name { 114 | #static_table_init 115 | class, 116 | #(#field_names,)* 117 | } 118 | } 119 | } 120 | 121 | impl #binding_name { 122 | #vis fn class(&self) -> &#mono_module::Class { 123 | &self.class 124 | } 125 | 126 | #vis fn read(&self, process: &asr::Process #maybe_instance_param) -> Result<#struct_name, ()> { 127 | Ok(#struct_name {#( 128 | #field_names: #field_reads, 129 | )*}) 130 | } 131 | } 132 | } 133 | .into() 134 | } 135 | -------------------------------------------------------------------------------- /src/deep_pointer.rs: -------------------------------------------------------------------------------- 1 | //! Support for storing pointer paths for easy dereferencing inside the autosplitter logic. 2 | 3 | use core::array; 4 | 5 | use bytemuck::CheckedBitPattern; 6 | 7 | use crate::{Address, Error, PointerSize, Process}; 8 | 9 | /// An abstraction of a pointer path, usable for easy dereferencing inside an autosplitter logic. 10 | /// 11 | /// The maximum depth of the pointer path is given by the generic parameter `CAP`. 12 | /// 13 | /// `CAP` should be higher or equal to the number of offsets provided in `path`. 14 | /// If a higher number of offsets is provided, the pointer path will be truncated 15 | /// according to the value of `CAP`. 16 | #[derive(Copy, Clone)] 17 | pub struct DeepPointer { 18 | base_address: Address, 19 | path: [u64; CAP], 20 | depth: usize, 21 | pointer_size: PointerSize, 22 | } 23 | 24 | impl Default for DeepPointer { 25 | /// Creates a new empty DeepPointer. 26 | #[inline] 27 | fn default() -> Self { 28 | Self { 29 | base_address: Address::default(), 30 | path: [u64::default(); CAP], 31 | depth: usize::default(), 32 | pointer_size: PointerSize::Bit64, 33 | } 34 | } 35 | } 36 | 37 | impl DeepPointer { 38 | /// Creates a new DeepPointer and specify the pointer size dereferencing 39 | #[inline] 40 | pub fn new(base_address: impl Into
, pointer_size: PointerSize, path: &[u64]) -> Self { 41 | let this_path = { 42 | let mut iter = path.iter(); 43 | array::from_fn(|_| iter.next().copied().unwrap_or_default()) 44 | }; 45 | 46 | Self { 47 | base_address: base_address.into(), 48 | path: this_path, 49 | depth: path.len().min(CAP), 50 | pointer_size, 51 | } 52 | } 53 | 54 | /// Creates a new DeepPointer with 32bit pointer size dereferencing 55 | pub fn new_32bit(base_address: impl Into
, path: &[u64]) -> Self { 56 | Self::new(base_address, PointerSize::Bit32, path) 57 | } 58 | 59 | /// Creates a new DeepPointer with 64bit pointer size dereferencing 60 | pub fn new_64bit(base_address: impl Into
, path: &[u64]) -> Self { 61 | Self::new(base_address, PointerSize::Bit64, path) 62 | } 63 | 64 | /// Dereferences the pointer path, returning the memory address of the value of interest 65 | pub fn deref_offsets(&self, process: &Process) -> Result { 66 | let mut address = self.base_address; 67 | let (&last, path) = self.path[..self.depth].split_last().ok_or(Error {})?; 68 | for &offset in path { 69 | address = process.read_pointer(address + offset, self.pointer_size)?; 70 | } 71 | Ok(address + last) 72 | } 73 | 74 | /// Dereferences the pointer path, returning the value stored at the final memory address 75 | pub fn deref(&self, process: &Process) -> Result { 76 | process.read_pointer_path( 77 | self.base_address, 78 | self.pointer_size, 79 | &self.path[..self.depth], 80 | ) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/emulator/gba/emuhawk.rs: -------------------------------------------------------------------------------- 1 | use crate::{Address, MemoryRangeFlags, Process}; 2 | 3 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 4 | pub struct State { 5 | base_addr: Address, 6 | } 7 | 8 | impl State { 9 | pub fn find_ram(&mut self, game: &Process) -> Option<[Address; 2]> { 10 | self.base_addr = game.get_module_address("mgba.dll").ok()?; 11 | 12 | let addr = game 13 | .memory_ranges() 14 | .find(|range| { 15 | range.size().is_ok_and(|size| size == 0x48000) 16 | && range.flags().is_ok_and(|flag| { 17 | flag.contains(MemoryRangeFlags::WRITE | MemoryRangeFlags::READ) 18 | }) 19 | })? 20 | .address() 21 | .ok()?; 22 | 23 | Some([addr, addr + 0x40000]) 24 | } 25 | 26 | pub fn keep_alive(&self, game: &Process, ram_base: &Option<[Address; 2]>) -> bool { 27 | ram_base.is_some_and(|[ewram, _]| game.read::(ewram).is_ok()) 28 | && game.read::(self.base_addr).is_ok() 29 | } 30 | 31 | pub const fn new() -> Self { 32 | Self { 33 | base_addr: Address::NULL, 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/emulator/gba/mednafen.rs: -------------------------------------------------------------------------------- 1 | use crate::{file_format::pe, signature::Signature, Address, Address32, Address64, Error, Process}; 2 | 3 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 4 | pub struct State { 5 | cached_ewram_pointer: Address, 6 | cached_iwram_pointer: Address, 7 | is_64_bit: bool, 8 | } 9 | 10 | impl State { 11 | pub fn find_ram(&mut self, game: &Process) -> Option<[Address; 2]> { 12 | let main_module_range = super::PROCESS_NAMES 13 | .iter() 14 | .filter(|(_, state)| matches!(state, super::State::Mednafen(_))) 15 | .find_map(|(name, _)| game.get_module_range(name).ok())?; 16 | 17 | self.is_64_bit = 18 | pe::MachineType::read(game, main_module_range.0) == Some(pe::MachineType::X86_64); 19 | 20 | if self.is_64_bit { 21 | self.cached_ewram_pointer = { 22 | const SIG: Signature<13> = Signature::new("48 8B 05 ?? ?? ?? ?? 81 E1 FF FF 03 00"); 23 | let ptr: Address = SIG.scan_process_range(game, main_module_range)? + 3; 24 | let mut addr: Address = ptr + 0x4 + game.read::(ptr).ok()?; 25 | 26 | if game.read::(ptr + 10).ok()? == 0x48 { 27 | addr = game.read::(addr).ok()?.into(); 28 | if addr.is_null() { 29 | return None; 30 | } 31 | } 32 | 33 | addr 34 | }; 35 | 36 | self.cached_iwram_pointer = { 37 | const SIG2: Signature<13> = 38 | Signature::new("48 8B 05 ?? ?? ?? ?? 81 E1 FF 7F 00 00"); 39 | let ptr: Address = SIG2.scan_process_range(game, main_module_range)? + 3; 40 | let mut addr: Address = ptr + 0x4 + game.read::(ptr).ok()?; 41 | 42 | if game.read::(ptr + 10).ok()? == 0x48 { 43 | addr = game.read::(addr).ok()?.into(); 44 | if addr.is_null() { 45 | return None; 46 | } 47 | } 48 | 49 | addr 50 | }; 51 | 52 | let ewram = game.read::(self.cached_ewram_pointer).ok()?; 53 | let iwram = game.read::(self.cached_iwram_pointer).ok()?; 54 | 55 | Some([ewram.into(), iwram.into()]) 56 | } else { 57 | self.cached_ewram_pointer = { 58 | const SIG: Signature<11> = Signature::new("A1 ?? ?? ?? ?? 81 ?? FF FF 03 00"); 59 | let ptr = SIG.scan_process_range(game, main_module_range)?; 60 | game.read::(ptr + 1).ok()?.into() 61 | }; 62 | 63 | self.cached_iwram_pointer = { 64 | const SIG2: Signature<11> = Signature::new("A1 ?? ?? ?? ?? 81 ?? FF 7F 00 00"); 65 | let ptr = SIG2.scan_process_range(game, main_module_range)?; 66 | game.read::(ptr + 1).ok()?.into() 67 | }; 68 | 69 | let ewram = game.read::(self.cached_ewram_pointer).ok()?; 70 | let iwram = game.read::(self.cached_iwram_pointer).ok()?; 71 | 72 | Some([ewram.into(), iwram.into()]) 73 | } 74 | } 75 | 76 | fn read_pointer(&self, game: &Process, address: Address) -> Result { 77 | Ok(match self.is_64_bit { 78 | true => game.read::(address)?.into(), 79 | false => game.read::(address)?.into(), 80 | }) 81 | } 82 | 83 | pub fn keep_alive(&self, game: &Process, ram: &mut Option<[Address; 2]>) -> bool { 84 | let ewram = match self.read_pointer(game, self.cached_ewram_pointer) { 85 | Ok(x) => x, 86 | _ => return false, 87 | }; 88 | 89 | let iwram = match self.read_pointer(game, self.cached_iwram_pointer) { 90 | Ok(x) => x, 91 | _ => return false, 92 | }; 93 | 94 | *ram = Some([ewram, iwram]); 95 | true 96 | } 97 | 98 | pub const fn new() -> Self { 99 | Self { 100 | cached_ewram_pointer: Address::NULL, 101 | cached_iwram_pointer: Address::NULL, 102 | is_64_bit: false, 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/emulator/gba/mgba.rs: -------------------------------------------------------------------------------- 1 | use crate::{Address, MemoryRangeFlags, Process}; 2 | 3 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 4 | pub struct State; 5 | 6 | impl State { 7 | pub fn find_ram(&self, game: &Process) -> Option<[Address; 2]> { 8 | // Latest version tested: 0.10.2 (September 2023) 9 | let addr = game 10 | .memory_ranges() 11 | .find(|range| { 12 | range.size().is_ok_and(|size| size == 0x48000) 13 | && range.flags().is_ok_and(|flag| { 14 | flag.contains(MemoryRangeFlags::WRITE | MemoryRangeFlags::READ) 15 | }) 16 | })? 17 | .address() 18 | .ok()?; 19 | Some([addr, addr + 0x40000]) 20 | } 21 | 22 | pub fn keep_alive(&self, game: &Process, ram_base: &Option<[Address; 2]>) -> bool { 23 | ram_base.is_some_and(|[ewram, _]| game.read::(ewram).is_ok()) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/emulator/gba/nocashgba.rs: -------------------------------------------------------------------------------- 1 | use crate::{signature::Signature, Address, Address32, Process}; 2 | 3 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 4 | pub struct State { 5 | base_addr: Address, 6 | } 7 | 8 | impl State { 9 | pub fn find_ram(&mut self, game: &Process) -> Option<[Address; 2]> { 10 | // Tested and working on NO$GBA 3.2 and 3.05 11 | const SIG: Signature<7> = Signature::new("FF 35 ?? ?? ?? ?? 55"); 12 | 13 | let main_module_range = super::PROCESS_NAMES 14 | .iter() 15 | .filter(|(_, state)| matches!(state, super::State::NoCashGba(_))) 16 | .find_map(|(name, _)| game.get_module_range(name).ok())?; 17 | 18 | self.base_addr = game 19 | .read::(SIG.scan_process_range(game, main_module_range)? + 0x2) 20 | .ok()? 21 | .into(); 22 | 23 | let addr: Address = game.read::(self.base_addr).ok()?.into(); 24 | 25 | let ewram_pointer = addr.add(0x938C).add(0x8); 26 | let iwram_pointer = addr.add(0x95D4); 27 | 28 | Some([ 29 | game.read::(ewram_pointer).ok()?.into(), 30 | game.read::(iwram_pointer).ok()?.into(), 31 | ]) 32 | } 33 | 34 | pub fn keep_alive(&self, game: &Process, ram: &mut Option<[Address; 2]>) -> bool { 35 | let Ok(addr) = game.read::(self.base_addr) else { 36 | return false; 37 | }; 38 | let ewram_pointer = addr.add(0x938C).add(0x8); 39 | let iwram_pointer = addr.add(0x95D4); 40 | 41 | let Ok(ewram) = game.read::(ewram_pointer) else { 42 | return false; 43 | }; 44 | let Ok(iwram) = game.read::(iwram_pointer) else { 45 | return false; 46 | }; 47 | 48 | *ram = Some([ewram.into(), iwram.into()]); 49 | true 50 | } 51 | 52 | pub const fn new() -> Self { 53 | Self { 54 | base_addr: Address::NULL, 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/emulator/gba/retroarch.rs: -------------------------------------------------------------------------------- 1 | use crate::{file_format::pe, signature::Signature, Address, Address32, Address64, Process}; 2 | 3 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 4 | pub struct State { 5 | core_base: Address, 6 | } 7 | 8 | impl State { 9 | pub fn find_ram(&mut self, game: &Process) -> Option<[Address; 2]> { 10 | const SUPPORTED_CORES: &[&str] = &[ 11 | "vbam_libretro.dll", 12 | "mednafen_gba_libretro.dll", 13 | "vba_next_libretro.dll", 14 | "mgba_libretro.dll", 15 | "gpsp_libretro.dll", 16 | ]; 17 | 18 | let main_module_address = super::PROCESS_NAMES 19 | .iter() 20 | .filter(|(_, state)| matches!(state, super::State::Retroarch(_))) 21 | .find_map(|(name, _)| game.get_module_address(name).ok())?; 22 | 23 | let is_64_bit = 24 | pe::MachineType::read(game, main_module_address) == Some(pe::MachineType::X86_64); 25 | 26 | let (core_name, core_address) = SUPPORTED_CORES 27 | .iter() 28 | .find_map(|&m| Some((m, game.get_module_address(m).ok()?)))?; 29 | 30 | self.core_base = core_address; 31 | 32 | match core_name { 33 | "vbam_libretro.dll" | "vba_next_libretro.dll" | "mednafen_gba_libretro.dll" => { 34 | self.vba(game, is_64_bit, core_name) 35 | } 36 | "mgba_libretro.dll" => super::mgba::State::find_ram(&super::mgba::State, game), 37 | "gpsp_libretro.dll" => self.gpsp(game, is_64_bit, core_name), 38 | _ => None, 39 | } 40 | } 41 | 42 | fn vba(&self, game: &Process, is_64_bit: bool, core_name: &str) -> Option<[Address; 2]> { 43 | let module_range = (self.core_base, game.get_module_size(core_name).ok()?); 44 | 45 | if is_64_bit { 46 | const SIG: Signature<13> = Signature::new("48 8B 05 ?? ?? ?? ?? 81 E1 FF FF 03 00"); 47 | const SIG2: Signature<13> = Signature::new("48 8B 05 ?? ?? ?? ?? 81 E1 FF 7F 00 00"); 48 | 49 | let ewram_pointer = { 50 | let ptr: Address = SIG.scan_process_range(game, module_range)? + 3; 51 | let mut addr: Address = ptr + 0x4 + game.read::(ptr).ok()?; 52 | 53 | if game.read::(ptr + 10).ok()? == 0x48 { 54 | addr = game.read::(addr).ok()?.into(); 55 | if addr.is_null() { 56 | return None; 57 | } 58 | } 59 | 60 | addr 61 | }; 62 | 63 | let iwram_pointer = { 64 | let ptr: Address = SIG2.scan_process_range(game, module_range)? + 3; 65 | let mut addr: Address = ptr + 0x4 + game.read::(ptr).ok()?; 66 | 67 | if game.read::(ptr + 10).ok()? == 0x48 { 68 | addr = game.read::(addr).ok()?.into(); 69 | if addr.is_null() { 70 | return None; 71 | } 72 | } 73 | 74 | addr 75 | }; 76 | 77 | let ewram = game.read::(ewram_pointer).ok()?; 78 | let iwram = game.read::(iwram_pointer).ok()?; 79 | 80 | if ewram.is_null() || iwram.is_null() { 81 | None 82 | } else { 83 | Some([ewram.into(), iwram.into()]) 84 | } 85 | } else { 86 | let ewram_pointer: Address = { 87 | const SIG: Signature<11> = Signature::new("A1 ?? ?? ?? ?? 81 ?? FF FF 03 00"); 88 | let ptr = SIG.scan_process_range(game, module_range)?; 89 | game.read::(ptr + 1).ok()?.into() 90 | }; 91 | let iwram_pointer: Address = { 92 | const SIG2: Signature<11> = Signature::new("A1 ?? ?? ?? ?? 81 ?? FF 7F 00 00"); 93 | let ptr = SIG2.scan_process_range(game, module_range)?; 94 | game.read::(ptr + 1).ok()?.into() 95 | }; 96 | 97 | let ewram = game.read::(ewram_pointer).ok()?; 98 | let iwram = game.read::(iwram_pointer).ok()?; 99 | 100 | if ewram.is_null() || iwram.is_null() { 101 | None 102 | } else { 103 | Some([ewram.into(), iwram.into()]) 104 | } 105 | } 106 | } 107 | 108 | fn gpsp(&self, game: &Process, is_64_bit: bool, core_name: &str) -> Option<[Address; 2]> { 109 | const SIG_EWRAM: Signature<8> = Signature::new("25 FF FF 03 00 88 94 03"); 110 | const SIG_IWRAM: Signature<9> = Signature::new("25 FE 7F 00 00 66 89 94 03"); 111 | 112 | let module_size = game.get_module_size(core_name).ok()?; 113 | 114 | let base_addr: Address = match is_64_bit { 115 | true => { 116 | const SIG: Signature<10> = Signature::new("48 8B 15 ?? ?? ?? ?? 8B 42 40"); 117 | let ptr = SIG.scan_process_range(game, (self.core_base, module_size))? + 3; 118 | let ptr: Address = ptr + 0x4 + game.read::(ptr).ok()?; 119 | game.read::(ptr).ok()?.into() 120 | } 121 | false => { 122 | const SIG: Signature<11> = Signature::new("A3 ?? ?? ?? ?? F7 C5 02 00 00 00"); 123 | let ptr = SIG.scan_process_range(game, (self.core_base, module_size))? + 1; 124 | game.read::(ptr).ok()?.into() 125 | } 126 | }; 127 | 128 | let ewram = { 129 | let offset = SIG_EWRAM.scan_process_range(game, (self.core_base, module_size))? + 8; 130 | base_addr + game.read::(offset).ok()? 131 | }; 132 | 133 | let iwram = { 134 | let offset = SIG_IWRAM.scan_process_range(game, (self.core_base, module_size))? + 9; 135 | base_addr + game.read::(offset).ok()? 136 | }; 137 | 138 | Some([ewram, iwram]) 139 | } 140 | 141 | pub fn keep_alive(&self, game: &Process) -> bool { 142 | game.read::(self.core_base).is_ok() 143 | } 144 | 145 | pub const fn new() -> Self { 146 | Self { 147 | core_base: Address::NULL, 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/emulator/gba/vba.rs: -------------------------------------------------------------------------------- 1 | use crate::{file_format::pe, signature::Signature, Address, Address32, Address64, Error, Process}; 2 | 3 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 4 | pub struct State { 5 | cached_ewram_pointer: Address, 6 | cached_iwram_pointer: Address, 7 | is_emulating: Address, 8 | is_64_bit: bool, 9 | } 10 | 11 | impl State { 12 | pub fn find_ram(&mut self, game: &Process) -> Option<[Address; 2]> { 13 | // Latest version tested: 2.1.7 14 | 15 | let main_module_range = super::PROCESS_NAMES 16 | .iter() 17 | .filter(|(_, state)| matches!(state, super::State::VisualBoyAdvance(_))) 18 | .find_map(|(name, _)| game.get_module_range(name).ok())?; 19 | 20 | self.is_64_bit = 21 | pe::MachineType::read(game, main_module_range.0) == Some(pe::MachineType::X86_64); 22 | 23 | if self.is_64_bit { 24 | const SIG: Signature<13> = Signature::new("48 8B 05 ?? ?? ?? ?? 81 E3 FF FF 03 00"); 25 | const SIG2: Signature<13> = Signature::new("48 8B 05 ?? ?? ?? ?? 81 E3 FF 7F 00 00"); 26 | 27 | self.cached_ewram_pointer = { 28 | let ptr: Address = SIG.scan_process_range(game, main_module_range)? + 3; 29 | let mut addr: Address = ptr + 0x4 + game.read::(ptr).ok()?; 30 | 31 | if game.read::(ptr + 10).ok()? == 0x48 { 32 | addr = game.read::(addr).ok()?.into(); 33 | if addr.is_null() { 34 | return None; 35 | } 36 | } 37 | 38 | addr 39 | }; 40 | 41 | self.cached_iwram_pointer = { 42 | let ptr: Address = SIG2.scan_process_range(game, main_module_range)? + 3; 43 | let mut addr: Address = ptr + 0x4 + game.read::(ptr).ok()?; 44 | 45 | if game.read::(ptr + 10).ok()? == 0x48 { 46 | addr = game.read::(addr).ok()?.into(); 47 | if addr.is_null() { 48 | return None; 49 | } 50 | } 51 | 52 | addr 53 | }; 54 | 55 | self.is_emulating = { 56 | const SIG_RUNNING: Signature<19> = 57 | Signature::new("83 3D ?? ?? ?? ?? 00 74 ?? 80 3D ?? ?? ?? ?? 00 75 ?? 66"); 58 | const SIG_RUNNING2: Signature<16> = 59 | Signature::new("48 8B 15 ?? ?? ?? ?? 31 C0 8B 12 85 D2 74 ?? 48"); 60 | 61 | if let Some(ptr) = SIG_RUNNING.scan_process_range(game, main_module_range) { 62 | let ptr = ptr + 2; 63 | ptr + 0x4 + game.read::(ptr).ok()? + 0x1 64 | } else { 65 | let ptr = SIG_RUNNING2.scan_process_range(game, main_module_range)? + 3; 66 | let ptr = ptr + 0x4 + game.read::(ptr).ok()?; 67 | game.read::(ptr).ok()?.into() 68 | } 69 | }; 70 | 71 | let ewram = game.read::(self.cached_ewram_pointer).ok()?; 72 | let iwram = game.read::(self.cached_iwram_pointer).ok()?; 73 | 74 | Some([ewram.into(), iwram.into()]) 75 | } else { 76 | const SIG: Signature<11> = Signature::new("A1 ?? ?? ?? ?? 81 ?? FF FF 03 00"); 77 | const SIG_OLD: Signature<12> = Signature::new("81 E6 FF FF 03 00 8B 15 ?? ?? ?? ??"); 78 | 79 | if let Some(ptr) = SIG.scan_process_range(game, main_module_range) { 80 | self.cached_ewram_pointer = game.read::(ptr + 1).ok()?.into(); 81 | self.cached_iwram_pointer = { 82 | const SIG2: Signature<11> = Signature::new("A1 ?? ?? ?? ?? 81 ?? FF 7F 00 00"); 83 | let ptr = SIG2.scan_process_range(game, main_module_range)?; 84 | game.read::(ptr + 1).ok()?.into() 85 | }; 86 | 87 | self.is_emulating = { 88 | const SIG: Signature<19> = 89 | Signature::new("83 3D ?? ?? ?? ?? 00 74 ?? 80 3D ?? ?? ?? ?? 00 75 ?? 66"); 90 | const SIG_OLD: Signature<13> = 91 | Signature::new("8B 15 ?? ?? ?? ?? 31 C0 85 D2 74 ?? 0F"); 92 | 93 | let ptr = SIG 94 | .scan_process_range(game, main_module_range) 95 | .or_else(|| SIG_OLD.scan_process_range(game, main_module_range))?; 96 | 97 | game.read::(ptr + 2).ok()?.into() 98 | }; 99 | 100 | let ewram = game.read::(self.cached_ewram_pointer).ok()?; 101 | let iwram = game.read::(self.cached_iwram_pointer).ok()?; 102 | 103 | Some([ewram.into(), iwram.into()]) 104 | } else if let Some(ptr) = SIG_OLD.scan_process_range(game, main_module_range) { 105 | // This code is for very old versions of VisualBoyAdvance (1.8.0-beta 3) 106 | self.cached_ewram_pointer = game.read::(ptr + 8).ok()?.into(); 107 | self.cached_iwram_pointer = self.cached_ewram_pointer.add_signed(0x4); 108 | 109 | self.is_emulating = { 110 | const SIG_RUNNING: Signature<11> = 111 | Signature::new("8B 0D ?? ?? ?? ?? 85 C9 74 ?? 8A"); 112 | let ptr = SIG_RUNNING.scan_process_range(game, main_module_range)? + 2; 113 | game.read::(ptr).ok()?.into() 114 | }; 115 | 116 | let ewram = game.read::(self.cached_ewram_pointer).ok()?; 117 | let iwram = game.read::(self.cached_iwram_pointer).ok()?; 118 | 119 | Some([ewram.into(), iwram.into()]) 120 | } else { 121 | None 122 | } 123 | } 124 | } 125 | 126 | fn read_pointer(&self, game: &Process, address: Address) -> Result { 127 | Ok(match self.is_64_bit { 128 | true => game.read::(address)?.into(), 129 | false => game.read::(address)?.into(), 130 | }) 131 | } 132 | 133 | pub fn keep_alive(&self, game: &Process, ram: &mut Option<[Address; 2]>) -> bool { 134 | match game.read::(self.is_emulating) { 135 | Ok(false) => { 136 | *ram = Some([Address::NULL; 2]); 137 | } 138 | Ok(true) => { 139 | let Ok(ewram) = self.read_pointer(game, self.cached_ewram_pointer) else { 140 | return false; 141 | }; 142 | let Ok(iwram) = self.read_pointer(game, self.cached_iwram_pointer) else { 143 | return false; 144 | }; 145 | *ram = Some([ewram, iwram]); 146 | } 147 | _ => return false, 148 | }; 149 | true 150 | } 151 | 152 | pub const fn new() -> Self { 153 | Self { 154 | cached_ewram_pointer: Address::NULL, 155 | cached_iwram_pointer: Address::NULL, 156 | is_emulating: Address::NULL, 157 | is_64_bit: false, 158 | } 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/emulator/gcn/dolphin.rs: -------------------------------------------------------------------------------- 1 | use crate::{Address, Endian, FromEndian, MemoryRangeFlags, Process}; 2 | 3 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 4 | pub struct State; 5 | 6 | impl State { 7 | pub fn find_ram(&self, game: &Process, endian: &mut Endian) -> Option
{ 8 | *endian = Endian::Big; 9 | 10 | // Main logic: finding the address for the GCN main memory by looking for 11 | // memory ranges with the READ and WRITE flags and a size of 0x2000000. 12 | // In order to verify we found the correct memory range, we take advantage 13 | // of a small 'hack', by checking if the offset 0x1C contains a "magic number" 14 | // fixed for all Gamecube games. 15 | 16 | game.memory_ranges() 17 | .find(|range| { 18 | range 19 | .flags() 20 | .is_ok_and(|r| r.contains(MemoryRangeFlags::WRITE | MemoryRangeFlags::READ)) 21 | && range.size().is_ok_and(|size| size == 0x2000000) 22 | && range.address().is_ok_and(|addr| { 23 | game.read::(addr + 0x1C) 24 | .is_ok_and(|magic| magic.from_endian(Endian::Big) == 0xC2339F3D) 25 | }) 26 | })? 27 | .address() 28 | .ok() 29 | } 30 | 31 | pub fn keep_alive(&self, game: &Process, ram_base: &Option
) -> bool { 32 | ram_base.is_some_and(|addr| game.read::(addr).is_ok()) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/emulator/gcn/retroarch.rs: -------------------------------------------------------------------------------- 1 | use crate::{file_format::pe, Address, Endian, Process}; 2 | 3 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 4 | pub struct State { 5 | core_base: Address, 6 | } 7 | 8 | impl State { 9 | pub fn find_ram(&mut self, game: &Process, endian: &mut Endian) -> Option
{ 10 | const SUPPORTED_CORES: [&str; 1] = ["dolphin_libretro.dll"]; 11 | 12 | let main_module_address = super::PROCESS_NAMES 13 | .iter() 14 | .filter(|(_, state)| matches!(state, super::State::Retroarch(_))) 15 | .find_map(|(name, _)| game.get_module_address(name).ok())?; 16 | 17 | let is_64_bit = 18 | pe::MachineType::read(game, main_module_address) == Some(pe::MachineType::X86_64); 19 | 20 | if !is_64_bit { 21 | // The Dolphin core, the only one available for retroarch at 22 | // the time of writing (Sep 19th, 2023), only supports 64-bit 23 | return None; 24 | } 25 | 26 | self.core_base = SUPPORTED_CORES 27 | .iter() 28 | .find_map(|&m| game.get_module_address(m).ok())?; 29 | 30 | // The following code is essentially the same used for Dolphin 31 | *endian = Endian::Big; 32 | 33 | super::dolphin::State::find_ram(&super::dolphin::State, game, endian) 34 | } 35 | 36 | pub fn keep_alive(&self, game: &Process, ram_base: &Option
) -> bool { 37 | game.read::(self.core_base).is_ok() 38 | && ram_base.is_some_and(|ram| game.read::(ram).is_ok()) 39 | } 40 | 41 | pub const fn new() -> Self { 42 | Self { 43 | core_base: Address::NULL, 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/emulator/genesis/blastem.rs: -------------------------------------------------------------------------------- 1 | use crate::{runtime::MemoryRangeFlags, signature::Signature, Address, Address32, Endian, Process}; 2 | 3 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 4 | pub struct State; 5 | 6 | impl State { 7 | pub fn find_wram(&mut self, game: &Process, endian: &mut Endian) -> Option
{ 8 | const SIG: Signature<16> = 9 | Signature::new("72 0E 81 E1 FF FF 00 00 66 8B 89 ?? ?? ?? ?? C3"); 10 | 11 | *endian = Endian::Little; 12 | 13 | let scanned_address = game 14 | .memory_ranges() 15 | .filter(|m| { 16 | m.flags() 17 | .unwrap_or_default() 18 | .contains(MemoryRangeFlags::WRITE) 19 | && m.size().unwrap_or_default() == 0x101000 20 | }) 21 | .find_map(|m| SIG.scan_process_range(game, m.range().ok()?))? 22 | + 11; 23 | 24 | let wram = game.read::(scanned_address).ok()?; 25 | 26 | Some(wram.into()) 27 | } 28 | 29 | pub const fn keep_alive(&self) -> bool { 30 | true 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/emulator/genesis/fusion.rs: -------------------------------------------------------------------------------- 1 | use crate::{signature::Signature, Address, Address32, Endian, Process}; 2 | 3 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 4 | pub struct State { 5 | addr: Address, 6 | } 7 | 8 | impl State { 9 | pub fn find_wram(&mut self, game: &Process, endian: &mut Endian) -> Option
{ 10 | const SIG: Signature<4> = Signature::new("75 2F 6A 01"); 11 | 12 | let main_module = super::PROCESS_NAMES 13 | .iter() 14 | .filter(|(_, state)| matches!(state, super::State::Fusion(_))) 15 | .find_map(|(name, _)| game.get_module_range(name).ok())?; 16 | 17 | let ptr = SIG.scan_process_range(game, main_module)? + 1; 18 | 19 | let addr = ptr + game.read::(ptr).ok()? as u64 + 3; 20 | let addr = game.read::(addr).ok()?; 21 | 22 | self.addr = addr.into(); 23 | 24 | let addr = game.read::(self.addr).ok()?; 25 | 26 | *endian = Endian::Big; 27 | 28 | Some(addr.into()) 29 | } 30 | 31 | pub fn keep_alive(&self, game: &Process, wram_base: &mut Option
) -> bool { 32 | if let Ok(addr) = game.read::(self.addr) { 33 | *wram_base = Some(addr.into()); 34 | true 35 | } else { 36 | false 37 | } 38 | } 39 | 40 | pub const fn new() -> Self { 41 | Self { 42 | addr: Address::NULL, 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/emulator/genesis/gens.rs: -------------------------------------------------------------------------------- 1 | use crate::{signature::Signature, Address, Address32, Endian, Process}; 2 | 3 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 4 | pub struct State; 5 | 6 | impl State { 7 | pub fn find_wram(&mut self, game: &Process, endian: &mut Endian) -> Option
{ 8 | const SIG: Signature<10> = Signature::new("72 ?? 81 ?? FF FF 00 00 66 8B"); 9 | 10 | let main_module = super::PROCESS_NAMES 11 | .iter() 12 | .filter(|(_, state)| matches!(state, super::State::Gens(_))) 13 | .find_map(|(name, _)| game.get_module_range(name).ok())?; 14 | 15 | let ptr = SIG.scan_process_range(game, main_module)? + 11; 16 | 17 | *endian = if game.read::(ptr + 4).ok()? == 0x86 { 18 | Endian::Big 19 | } else { 20 | Endian::Little 21 | }; 22 | 23 | let wram = game.read::(ptr).ok()?; 24 | 25 | Some(wram.into()) 26 | } 27 | 28 | pub const fn keep_alive(&self) -> bool { 29 | true 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/emulator/genesis/retroarch.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | file_format::pe, signature::Signature, Address, Address32, Endian, MemoryRangeFlags, Process, 3 | }; 4 | 5 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 6 | pub struct State { 7 | core_base: Address, 8 | } 9 | 10 | impl State { 11 | pub fn find_wram(&mut self, game: &Process, endian: &mut Endian) -> Option
{ 12 | const SUPPORTED_CORES: [&str; 4] = [ 13 | "blastem_libretro.dll", 14 | "genesis_plus_gx_libretro.dll", 15 | "genesis_plus_gx_wide_libretro.dll", 16 | "picodrive_libretro.dll", 17 | ]; 18 | 19 | let main_module_address = super::PROCESS_NAMES 20 | .iter() 21 | .filter(|(_, state)| matches!(state, super::State::Retroarch(_))) 22 | .find_map(|(name, _)| game.get_module_address(name).ok())?; 23 | 24 | let is_x86_64 = 25 | pe::MachineType::read(game, main_module_address) == Some(pe::MachineType::X86_64); 26 | 27 | let (core_name, core_address) = SUPPORTED_CORES 28 | .iter() 29 | .find_map(|&m| Some((m, game.get_module_address(m).ok()?)))?; 30 | 31 | self.core_base = core_address; 32 | 33 | if core_name == SUPPORTED_CORES[0] { 34 | *endian = Endian::Little; 35 | 36 | // BlastEm 37 | const SIG: Signature<16> = 38 | Signature::new("72 0E 81 E1 FF FF 00 00 66 8B 89 ?? ?? ?? ?? C3"); 39 | 40 | let scanned_address = game 41 | .memory_ranges() 42 | .filter(|m| { 43 | m.flags() 44 | .unwrap_or_default() 45 | .contains(MemoryRangeFlags::WRITE) 46 | && m.size().unwrap_or_default() == 0x101000 47 | }) 48 | .find_map(|m| SIG.scan_process_range(game, m.range().ok()?))? 49 | + 11; 50 | 51 | let wram = game.read::(scanned_address).ok()?; 52 | 53 | Some(wram.into()) 54 | } else if core_name == SUPPORTED_CORES[1] || core_name == SUPPORTED_CORES[2] { 55 | *endian = Endian::Little; 56 | 57 | // Genesis plus GX 58 | if is_x86_64 { 59 | const SIG_64: Signature<10> = Signature::new("48 8D 0D ?? ?? ?? ?? 4C 8B 2D"); 60 | 61 | let addr = SIG_64.scan_process_range( 62 | game, 63 | (core_address, game.get_module_size(core_name).ok()?), 64 | )? + 3; 65 | 66 | let wram = addr + 0x4 + game.read::(addr).ok()?; 67 | 68 | Some(wram) 69 | } else { 70 | const SIG_32: Signature<7> = Signature::new("A3 ?? ?? ?? ?? 29 F9"); 71 | 72 | let ptr = SIG_32.scan_process_range( 73 | game, 74 | (core_address, game.get_module_size(core_name).ok()?), 75 | )? + 1; 76 | 77 | let wram = game.read::(ptr).ok()?; 78 | 79 | Some(wram.into()) 80 | } 81 | } else if core_name == SUPPORTED_CORES[3] { 82 | *endian = Endian::Little; 83 | 84 | // Picodrive 85 | if is_x86_64 { 86 | const SIG_64: Signature<9> = Signature::new("48 8D 0D ?? ?? ?? ?? 41 B8"); 87 | 88 | let addr = SIG_64.scan_process_range( 89 | game, 90 | (core_address, game.get_module_size(core_name).ok()?), 91 | )? + 3; 92 | 93 | let wram = addr + 0x4 + game.read::(addr).ok()?; 94 | 95 | Some(wram) 96 | } else { 97 | const SIG_32: Signature<8> = Signature::new("B9 ?? ?? ?? ?? C1 EF 10"); 98 | 99 | let ptr = SIG_32.scan_process_range( 100 | game, 101 | (core_address, game.get_module_size(core_name).ok()?), 102 | )? + 1; 103 | 104 | let wram = game.read::(ptr).ok()?; 105 | 106 | Some(wram.into()) 107 | } 108 | } else { 109 | None 110 | } 111 | } 112 | 113 | pub fn keep_alive(&self, game: &Process) -> bool { 114 | game.read::(self.core_base).is_ok() 115 | } 116 | 117 | pub const fn new() -> Self { 118 | Self { 119 | core_base: Address::NULL, 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/emulator/genesis/segaclassics.rs: -------------------------------------------------------------------------------- 1 | use crate::{signature::Signature, Address, Address32, Endian, Process}; 2 | 3 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 4 | pub struct State { 5 | addr: Address, 6 | } 7 | 8 | impl State { 9 | pub fn find_wram(&mut self, game: &Process, endian: &mut Endian) -> Option
{ 10 | const SIG_GAMEROOM: Signature<16> = 11 | Signature::new("C7 05 ???????? ???????? A3 ???????? A3"); 12 | const SIG_SEGACLASSICS: Signature<8> = Signature::new("89 2D ???????? 89 0D"); 13 | const GENESISWRAPPERDLL: &str = "GenesisEmuWrapper.dll"; 14 | 15 | let mut ptr = if let Ok(module) = game.get_module_range(GENESISWRAPPERDLL) { 16 | SIG_GAMEROOM.scan_process_range(game, module)? + 2 17 | } else { 18 | let main_module = super::PROCESS_NAMES 19 | .iter() 20 | .filter(|(_, state)| matches!(state, super::State::SegaClassics(_))) 21 | .find_map(|(name, _)| game.get_module_range(name).ok())?; 22 | 23 | SIG_SEGACLASSICS.scan_process_range(game, main_module)? + 8 24 | }; 25 | 26 | ptr = game.read::(ptr).ok()?.into(); 27 | 28 | self.addr = ptr; 29 | *endian = Endian::Little; 30 | 31 | ptr = game.read::(self.addr).ok()?.into(); 32 | 33 | Some(ptr) 34 | } 35 | 36 | pub fn keep_alive(&self, game: &Process, wram_base: &mut Option
) -> bool { 37 | if let Ok(addr) = game.read::(self.addr) { 38 | *wram_base = Some(addr.into()); 39 | true 40 | } else { 41 | false 42 | } 43 | } 44 | 45 | pub const fn new() -> Self { 46 | Self { 47 | addr: Address::NULL, 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/emulator/mod.rs: -------------------------------------------------------------------------------- 1 | //! Support for attaching to various emulators. 2 | 3 | #[cfg(feature = "gba")] 4 | pub mod gba; 5 | #[cfg(feature = "gcn")] 6 | pub mod gcn; 7 | #[cfg(feature = "genesis")] 8 | pub mod genesis; 9 | #[cfg(feature = "ps1")] 10 | pub mod ps1; 11 | #[cfg(feature = "ps2")] 12 | pub mod ps2; 13 | #[cfg(feature = "sms")] 14 | pub mod sms; 15 | #[cfg(feature = "wii")] 16 | pub mod wii; 17 | -------------------------------------------------------------------------------- /src/emulator/ps1/duckstation.rs: -------------------------------------------------------------------------------- 1 | use crate::{file_format::pe, signature::Signature, Address, Address64, Process}; 2 | 3 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 4 | pub struct State { 5 | addr: Address, 6 | } 7 | 8 | impl State { 9 | pub fn find_ram(&mut self, game: &Process) -> Option
{ 10 | const SIG: Signature<8> = Signature::new("48 89 0D ?? ?? ?? ?? B8"); 11 | 12 | let main_module_range = super::PROCESS_NAMES 13 | .iter() 14 | .filter(|(_, state)| matches!(state, super::State::Duckstation(_))) 15 | .find_map(|(name, _)| game.get_module_range(name).ok())?; 16 | 17 | // Recent Duckstation releases include a debug symbol that can be used to easily retrieve the address of the emulated RAM 18 | // Info: https://github.com/stenzek/duckstation/commit/c98e0bd0969abdd82589bfc565aea52119fd0f19 19 | if let Some(debug_symbol) = pe::symbols(game, main_module_range.0).find(|symbol| { 20 | symbol 21 | .get_name::<4>(game) 22 | .is_ok_and(|name| name.matches(b"RAM")) 23 | }) { 24 | self.addr = debug_symbol.address; 25 | } else { 26 | // For older versions of Duckstation, we fall back to regular sigscanning 27 | let addr = SIG.scan_process_range(game, main_module_range)? + 3; 28 | self.addr = addr + 0x4 + game.read::(addr).ok()?; 29 | } 30 | 31 | Some(game.read::(self.addr).ok()?.into()) 32 | } 33 | 34 | pub fn keep_alive(&self, game: &Process, wram_base: &mut Option
) -> bool { 35 | if let Ok(addr) = game.read::(self.addr) { 36 | *wram_base = Some(addr.into()); 37 | true 38 | } else { 39 | false 40 | } 41 | } 42 | 43 | pub const fn new() -> Self { 44 | Self { 45 | addr: Address::NULL, 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/emulator/ps1/epsxe.rs: -------------------------------------------------------------------------------- 1 | use crate::{signature::Signature, Address, Address32, Process}; 2 | 3 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 4 | pub struct State; 5 | 6 | impl State { 7 | pub fn find_ram(&self, game: &Process) -> Option
{ 8 | const SIG: Signature<5> = Signature::new("C1 E1 10 8D 89"); 9 | 10 | let main_module_range = super::PROCESS_NAMES 11 | .iter() 12 | .filter(|(_, state)| matches!(state, super::State::Epsxe(_))) 13 | .find_map(|(name, _)| game.get_module_range(name).ok())?; 14 | 15 | let ptr = SIG.scan_process_range(game, main_module_range)? + 5; 16 | 17 | Some(game.read::(ptr).ok()?.into()) 18 | } 19 | 20 | pub const fn keep_alive(&self) -> bool { 21 | true 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/emulator/ps1/mednafen.rs: -------------------------------------------------------------------------------- 1 | use crate::{file_format::pe, signature::Signature, Address, Address32, Process}; 2 | 3 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 4 | pub struct State; 5 | 6 | impl State { 7 | pub fn find_ram(&self, game: &Process) -> Option
{ 8 | const SIG_32: Signature<10> = Signature::new("89 01 0F B6 82 ?? ?? ?? ?? C3"); 9 | const SIG_64: Signature<5> = Signature::new("89 01 0F B6 82"); 10 | 11 | let main_module_range = super::PROCESS_NAMES 12 | .iter() 13 | .filter(|(_, state)| matches!(state, super::State::Mednafen(_))) 14 | .find_map(|(name, _)| game.get_module_range(name).ok())?; 15 | 16 | let is_64_bit = 17 | pe::MachineType::read(game, main_module_range.0) == Some(pe::MachineType::X86_64); 18 | 19 | let ptr = match is_64_bit { 20 | true => SIG_64.scan_process_range(game, main_module_range)?, 21 | false => SIG_32.scan_process_range(game, main_module_range)?, 22 | } + 0x5; 23 | 24 | Some(game.read::(ptr).ok()?.into()) 25 | } 26 | 27 | pub const fn keep_alive(&self) -> bool { 28 | true 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/emulator/ps1/pcsx_redux.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | file_format::pe, signature::Signature, Address, Address32, Address64, MemoryRangeFlags, Process, 3 | }; 4 | 5 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 6 | pub struct State { 7 | is_64_bit: bool, 8 | addr_base: Address, 9 | addr: Address, 10 | } 11 | 12 | impl State { 13 | pub fn find_ram(&mut self, game: &Process) -> Option
{ 14 | let main_module_range = super::PROCESS_NAMES 15 | .iter() 16 | .filter(|(_, state)| matches!(state, super::State::PcsxRedux(_))) 17 | .find_map(|(name, _)| game.get_module_range(name).ok())?; 18 | 19 | self.is_64_bit = 20 | pe::MachineType::read(game, main_module_range.0) == Some(pe::MachineType::X86_64); 21 | 22 | if self.is_64_bit { 23 | const SIG_BASE: Signature<25> = Signature::new( 24 | "48 B9 ?? ?? ?? ?? ?? ?? ?? ?? E8 ?? ?? ?? ?? C7 85 ?? ?? ?? ?? 00 00 00 00", 25 | ); 26 | const SIG_OFFSET: Signature<9> = Signature::new("89 D1 C1 E9 10 48 8B ?? ??"); 27 | 28 | self.addr_base = SIG_BASE.scan_process_range(game, main_module_range)? + 2; 29 | self.addr = game.read::(self.addr_base).ok()?.into(); 30 | 31 | let offset = SIG_OFFSET.scan_process_range(game, main_module_range)? + 8; 32 | let offset = game.read::(offset).ok()? as u64; 33 | 34 | let addr = game.read::(self.addr + offset).ok()?; 35 | 36 | Some(game.read::(addr).ok()?.into()) 37 | } else { 38 | const SIG: Signature<18> = 39 | Signature::new("8B 3D 20 ?? ?? ?? 0F B7 D3 8B 04 95 ?? ?? ?? ?? 21 05"); 40 | 41 | self.addr_base = game 42 | .memory_ranges() 43 | .filter(|m| { 44 | m.flags() 45 | .unwrap_or_default() 46 | .contains(MemoryRangeFlags::WRITE) 47 | }) 48 | .find_map(|m| SIG.scan_process_range(game, m.range().ok()?))? 49 | + 2; 50 | 51 | self.addr = game.read::(self.addr_base).ok()?.into(); 52 | Some(self.addr) 53 | } 54 | } 55 | 56 | pub fn keep_alive(&self, game: &Process) -> bool { 57 | if self.is_64_bit { 58 | game.read::(self.addr_base) 59 | .is_ok_and(|addr| self.addr == addr.into()) 60 | } else { 61 | game.read::(self.addr_base) 62 | .is_ok_and(|addr| self.addr == addr.into()) 63 | } 64 | } 65 | 66 | pub const fn new() -> Self { 67 | Self { 68 | is_64_bit: true, 69 | addr_base: Address::NULL, 70 | addr: Address::NULL, 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/emulator/ps1/psxfin.rs: -------------------------------------------------------------------------------- 1 | use crate::{signature::Signature, Address, Address32, Process}; 2 | 3 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 4 | pub struct State; 5 | 6 | impl State { 7 | pub fn find_ram(&self, game: &Process) -> Option
{ 8 | const SIG: Signature<9> = Signature::new("8B 15 ?? ?? ?? ?? 8D 34 1A"); // v1.13 9 | const SIG_0: Signature<8> = Signature::new("A1 ?? ?? ?? ?? 8D 34 18"); // v1.12 10 | const SIG_1: Signature<9> = Signature::new("A1 ?? ?? ?? ?? 8B 7C 24 14"); // v1.5 through v1.11 11 | const SIG_2: Signature<8> = Signature::new("A1 ?? ?? ?? ?? 8B 6C 24"); // v1.0 through v1.4 12 | 13 | let main_module_range = super::PROCESS_NAMES 14 | .iter() 15 | .filter(|(_, state)| matches!(state, super::State::PsxFin(_))) 16 | .find_map(|(name, _)| game.get_module_range(name).ok())?; 17 | 18 | let mut ptr: Address32 = if let Some(sig) = SIG.scan_process_range(game, main_module_range) 19 | { 20 | game.read(sig + 2).ok()? 21 | } else if let Some(sig) = SIG_0.scan_process_range(game, main_module_range) { 22 | game.read(sig + 1).ok()? 23 | } else if let Some(sig) = SIG_1.scan_process_range(game, main_module_range) { 24 | game.read(sig + 1).ok()? 25 | } else if let Some(sig) = SIG_2.scan_process_range(game, main_module_range) { 26 | game.read(sig + 1).ok()? 27 | } else { 28 | return None; 29 | }; 30 | 31 | ptr = game.read::(ptr).ok()?; 32 | 33 | if ptr.is_null() { 34 | None 35 | } else { 36 | Some(ptr.into()) 37 | } 38 | } 39 | 40 | pub const fn keep_alive(&self) -> bool { 41 | true 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/emulator/ps1/retroarch.rs: -------------------------------------------------------------------------------- 1 | use crate::{file_format::pe, signature::Signature, Address, Address32, Address64, Process}; 2 | 3 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 4 | pub struct State { 5 | core_base: Address, 6 | } 7 | 8 | impl State { 9 | pub fn find_ram(&mut self, game: &Process) -> Option
{ 10 | const SUPPORTED_CORES: [&str; 4] = [ 11 | "mednafen_psx_hw_libretro.dll", 12 | "mednafen_psx_libretro.dll", 13 | "swanstation_libretro.dll", 14 | "pcsx_rearmed_libretro.dll", 15 | ]; 16 | 17 | let main_module_address = super::PROCESS_NAMES 18 | .iter() 19 | .filter(|(_, state)| matches!(state, super::State::Retroarch(_))) 20 | .find_map(|(name, _)| game.get_module_address(name).ok())?; 21 | 22 | let is_64_bit = 23 | pe::MachineType::read(game, main_module_address) == Some(pe::MachineType::X86_64); 24 | 25 | let (core_name, core_address) = SUPPORTED_CORES 26 | .iter() 27 | .find_map(|&m| Some((m, game.get_module_address(m).ok()?)))?; 28 | 29 | self.core_base = core_address; 30 | 31 | if core_name == SUPPORTED_CORES[0] || core_name == SUPPORTED_CORES[1] { 32 | // Mednafen 33 | if is_64_bit { 34 | const SIG: Signature<14> = 35 | Signature::new("48 8B 05 ?? ?? ?? ?? 41 81 E4 FF FF 1F 00"); 36 | let ptr = SIG.scan_process_range( 37 | game, 38 | (core_address, game.get_module_size(core_name).ok()?), 39 | )? + 3; 40 | let ptr = ptr + 0x4 + game.read::(ptr).ok()?; 41 | Some(game.read::(ptr).ok()?.into()) 42 | } else { 43 | const SIG: Signature<11> = Signature::new("A1 ?? ?? ?? ?? 81 E3 FF FF 1F 00"); 44 | let ptr = SIG.scan_process_range( 45 | game, 46 | (core_address, game.get_module_size(core_name).ok()?), 47 | )? + 1; 48 | let ptr = game.read::(ptr).ok()?; 49 | Some(game.read::(ptr).ok()?.into()) 50 | } 51 | } else if core_name == SUPPORTED_CORES[2] { 52 | // Swanstation 53 | if is_64_bit { 54 | const SIG: Signature<15> = 55 | Signature::new("48 89 0D ?? ?? ?? ?? 89 35 ?? ?? ?? ?? 89 3D"); 56 | let addr = SIG.scan_process_range( 57 | game, 58 | (core_address, game.get_module_size(core_name).ok()?), 59 | )? + 3; 60 | let ptr = addr + 0x4 + game.read::(addr).ok()?; 61 | Some(game.read::(ptr).ok()?.into()) 62 | } else { 63 | const SIG: Signature<8> = Signature::new("A1 ?? ?? ?? ?? 23 CB 8B"); 64 | let ptr = SIG.scan_process_range( 65 | game, 66 | (core_address, game.get_module_size(core_name).ok()?), 67 | )? + 1; 68 | let ptr = game.read::(ptr).ok()?; 69 | Some(game.read::(ptr).ok()?.into()) 70 | } 71 | } else if core_name == SUPPORTED_CORES[3] { 72 | // PCSX ReARMed 73 | if is_64_bit { 74 | const SIG: Signature<9> = Signature::new("48 8B 35 ?? ?? ?? ?? 81 E2"); 75 | let addr = SIG.scan_process_range( 76 | game, 77 | (core_address, game.get_module_size(core_name).ok()?), 78 | )? + 3; 79 | let ptr = addr + 0x4 + game.read::(addr).ok()?; 80 | let ptr = game.read::(ptr).ok()?; 81 | Some(game.read::(ptr).ok()?.into()) 82 | } else { 83 | const SIG: Signature<9> = Signature::new("FF FF 1F 00 89 ?? ?? ?? A1"); 84 | let ptr = SIG.scan_process_range( 85 | game, 86 | (core_address, game.get_module_size(core_name).ok()?), 87 | )? + 9; 88 | let ptr = game.read::(ptr).ok()?; 89 | Some(game.read::(ptr).ok()?.into()) 90 | } 91 | } else { 92 | None 93 | } 94 | } 95 | 96 | pub fn keep_alive(&self, game: &Process) -> bool { 97 | game.read::(self.core_base).is_ok() 98 | } 99 | 100 | pub const fn new() -> Self { 101 | Self { 102 | core_base: Address::NULL, 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/emulator/ps1/xebra.rs: -------------------------------------------------------------------------------- 1 | use crate::{signature::Signature, Address, Address32, Process}; 2 | 3 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 4 | pub struct State; 5 | 6 | impl State { 7 | pub fn find_ram(&self, game: &Process) -> Option
{ 8 | const SIG: Signature<15> = Signature::new("E8 ?? ?? ?? ?? E9 ?? ?? ?? ?? 89 C8 C1 F8 10"); 9 | 10 | let main_module_range = super::PROCESS_NAMES 11 | .iter() 12 | .filter(|(_, state)| matches!(state, super::State::Xebra(_))) 13 | .find_map(|(name, _)| game.get_module_range(name).ok())?; 14 | 15 | let ptr = SIG.scan_process_range(game, main_module_range)? + 1; 16 | let addr = ptr + 0x4 + game.read::(ptr).ok()?; 17 | let addr = game.read::(addr + 0x16A).ok()?; 18 | let addr = game.read::(addr).ok()?; 19 | Some(addr.into()) 20 | } 21 | 22 | pub const fn keep_alive(&self) -> bool { 23 | true 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/emulator/ps2/pcsx2.rs: -------------------------------------------------------------------------------- 1 | use crate::{file_format::pe, signature::Signature, Address, Address32, Address64, Error, Process}; 2 | 3 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 4 | pub struct State { 5 | is_64_bit: bool, 6 | addr_base: Address, 7 | } 8 | 9 | impl State { 10 | pub fn find_ram(&mut self, game: &Process) -> Option
{ 11 | let main_module_range = super::PROCESS_NAMES 12 | .iter() 13 | .filter(|(_, state)| matches!(state, super::State::Pcsx2(_))) 14 | .find_map(|(name, _)| game.get_module_range(name).ok())?; 15 | 16 | self.is_64_bit = 17 | pe::MachineType::read(game, main_module_range.0) == Some(pe::MachineType::X86_64); 18 | 19 | self.addr_base = if self.is_64_bit { 20 | const SIG: Signature<12> = Signature::new("48 8B ?? ?? ?? ?? ?? 25 F0 3F 00 00"); 21 | let ptr = SIG.scan_process_range(game, main_module_range)? + 3; 22 | ptr + 0x4 + game.read::(ptr).ok()? 23 | } else { 24 | const SIG: Signature<11> = Signature::new("8B ?? ?? ?? ?? ?? 25 F0 3F 00 00"); 25 | const SIG_ALT: Signature<12> = Signature::new("8B ?? ?? ?? ?? ?? 81 ?? F0 3F 00 00"); 26 | let ptr = if let Some(addr) = SIG.scan_process_range(game, main_module_range) { 27 | addr + 2 28 | } else { 29 | SIG_ALT.scan_process_range(game, main_module_range)? + 2 30 | }; 31 | self.read_pointer(game, ptr).ok()? 32 | }; 33 | 34 | self.read_pointer(game, self.addr_base).ok() 35 | } 36 | 37 | pub fn keep_alive(&self, game: &Process, ram_base: &mut Option
) -> bool { 38 | *ram_base = Some(match self.read_pointer(game, self.addr_base) { 39 | Ok(x) => x, 40 | Err(_) => return false, 41 | }); 42 | true 43 | } 44 | 45 | fn read_pointer(&self, game: &Process, address: Address) -> Result { 46 | Ok(match self.is_64_bit { 47 | true => game.read::(address)?.into(), 48 | false => game.read::(address)?.into(), 49 | }) 50 | } 51 | 52 | pub const fn new() -> Self { 53 | Self { 54 | is_64_bit: true, 55 | addr_base: Address::NULL, 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/emulator/ps2/retroarch.rs: -------------------------------------------------------------------------------- 1 | use crate::{file_format::pe, signature::Signature, Address, Address64, Process}; 2 | 3 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 4 | pub struct State { 5 | core_base: Address, 6 | } 7 | 8 | impl State { 9 | pub fn find_ram(&mut self, game: &Process) -> Option
{ 10 | const SUPPORTED_CORES: [&str; 1] = ["pcsx2_libretro.dll"]; 11 | 12 | let main_module_address = super::PROCESS_NAMES 13 | .iter() 14 | .filter(|(_, state)| matches!(state, super::State::Retroarch(_))) 15 | .find_map(|(name, _)| game.get_module_address(name).ok())?; 16 | 17 | let is_64_bit = 18 | pe::MachineType::read(game, main_module_address) == Some(pe::MachineType::X86_64); 19 | 20 | if !is_64_bit { 21 | // The LRPS2 core, the only one available for retroarch at 22 | // the time of writing (Sep 14th, 2023), only supports 64-bit 23 | return None; 24 | } 25 | 26 | let (core_name, core_address) = SUPPORTED_CORES 27 | .iter() 28 | .find_map(|&m| Some((m, game.get_module_address(m).ok()?)))?; 29 | 30 | self.core_base = core_address; 31 | 32 | let base_addr = { 33 | const SIG: Signature<13> = Signature::new("48 8B ?? ?? ?? ?? ?? 81 ?? F0 3F 00 00"); 34 | let ptr = SIG 35 | .scan_process_range(game, (core_address, game.get_module_size(core_name).ok()?))? 36 | + 3; 37 | ptr + 0x4 + game.read::(ptr).ok()? 38 | }; 39 | 40 | match game.read::(base_addr) { 41 | Ok(Address64::NULL) => None, 42 | Ok(x) => Some(x.into()), 43 | _ => None, 44 | } 45 | } 46 | 47 | pub fn keep_alive(&self, game: &Process) -> bool { 48 | game.read::(self.core_base).is_ok() 49 | } 50 | 51 | pub const fn new() -> Self { 52 | Self { 53 | core_base: Address::NULL, 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/emulator/sms/blastem.rs: -------------------------------------------------------------------------------- 1 | use crate::{runtime::MemoryRangeFlags, signature::Signature, Address, Address32, Process}; 2 | 3 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 4 | pub struct State; 5 | 6 | impl State { 7 | pub fn find_ram(&self, game: &Process) -> Option
{ 8 | const SIG: Signature<15> = Signature::new("66 81 E1 FF 1F 0F B7 C9 8A 89 ?? ?? ?? ?? C3"); 9 | 10 | let scanned_address = game 11 | .memory_ranges() 12 | .filter(|m| { 13 | m.flags() 14 | .unwrap_or_default() 15 | .contains(MemoryRangeFlags::WRITE) 16 | && m.size().unwrap_or_default() == 0x101000 17 | }) 18 | .find_map(|m| SIG.scan_process_range(game, m.range().ok()?))? 19 | + 10; 20 | 21 | let wram: Address = game.read::(scanned_address).ok()?.into(); 22 | 23 | if wram.is_null() { 24 | None 25 | } else { 26 | Some(wram) 27 | } 28 | } 29 | 30 | pub const fn keep_alive(&self) -> bool { 31 | true 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/emulator/sms/fusion.rs: -------------------------------------------------------------------------------- 1 | use crate::{signature::Signature, Address, Address32, Process}; 2 | 3 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 4 | pub struct State { 5 | addr: Address, 6 | } 7 | 8 | impl State { 9 | pub fn find_ram(&mut self, game: &Process) -> Option
{ 10 | const SIG: Signature<4> = Signature::new("74 C8 83 3D"); 11 | 12 | let main_module = super::PROCESS_NAMES 13 | .iter() 14 | .filter(|(_, state)| matches!(state, super::State::Fusion(_))) 15 | .find_map(|(name, _)| game.get_module_range(name).ok())?; 16 | 17 | let ptr = SIG.scan_process_range(game, main_module)? + 4; 18 | self.addr = game.read::(ptr).ok()?.into(); 19 | 20 | Some(game.read::(self.addr).ok()?.add(0xC000).into()) 21 | } 22 | 23 | pub fn keep_alive(&self, game: &Process, wram_base: &mut Option
) -> bool { 24 | *wram_base = Some(match game.read::(self.addr) { 25 | Ok(Address32::NULL) => Address::NULL, 26 | Ok(x) => x.add(0xC000).into(), 27 | _ => return false, 28 | }); 29 | true 30 | } 31 | 32 | pub const fn new() -> Self { 33 | Self { 34 | addr: Address::NULL, 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/emulator/sms/mednafen.rs: -------------------------------------------------------------------------------- 1 | use crate::{file_format::pe, signature::Signature, Address, Address32, Process}; 2 | 3 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 4 | pub struct State; 5 | 6 | impl State { 7 | pub fn find_ram(&self, game: &Process) -> Option
{ 8 | const SIG_32: Signature<8> = Signature::new("25 FF 1F 00 00 0F B6 80"); 9 | const SIG_64: Signature<7> = Signature::new("25 FF 1F 00 00 88 90"); 10 | 11 | let main_module_range = super::PROCESS_NAMES 12 | .iter() 13 | .filter(|(_, state)| matches!(state, super::State::Mednafen(_))) 14 | .find_map(|(name, _)| game.get_module_range(name).ok())?; 15 | 16 | let is_64_bit = 17 | pe::MachineType::read(game, main_module_range.0) == Some(pe::MachineType::X86_64); 18 | 19 | let ptr = match is_64_bit { 20 | true => SIG_64.scan_process_range(game, main_module_range)? + 8, 21 | false => SIG_32.scan_process_range(game, main_module_range)? + 7, 22 | }; 23 | 24 | Some(game.read::(ptr).ok()?.into()) 25 | } 26 | 27 | pub const fn keep_alive(&self) -> bool { 28 | true 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/emulator/sms/mod.rs: -------------------------------------------------------------------------------- 1 | //! Support for attaching to SEGA Master System / SEGA GameGear emulators. 2 | 3 | use core::{ 4 | cell::Cell, 5 | future::Future, 6 | mem::size_of, 7 | ops::Sub, 8 | pin::Pin, 9 | task::{Context, Poll}, 10 | }; 11 | 12 | use crate::{future::retry, Address, Error, Process}; 13 | use bytemuck::CheckedBitPattern; 14 | 15 | mod blastem; 16 | mod fusion; 17 | mod mednafen; 18 | mod retroarch; 19 | 20 | /// A SEGA Master System / GameGear emulator that the auto splitter is attached to. 21 | pub struct Emulator { 22 | /// The attached emulator process 23 | process: Process, 24 | /// An enum stating which emulator is currently attached 25 | state: Cell, 26 | /// The memory address of the emulated RAM 27 | ram_base: Cell>, 28 | } 29 | 30 | impl Emulator { 31 | /// Attaches to the emulator process 32 | /// 33 | /// Returns `Option` if successful, `None` otherwise. 34 | /// 35 | /// Supported emulators are: 36 | /// - Retroarch, with one of the following cores: `genesis_plus_gx_libretro.dll`, 37 | /// `genesis_plus_gx_wide_libretro.dll`, `picodrive_libretro.dll`, `smsplus_libretro.dll`, `gearsystem_libretro.dll` 38 | /// - Fusion 39 | /// - BlastEm 40 | pub fn attach() -> Option { 41 | let (&state, process) = PROCESS_NAMES 42 | .iter() 43 | .find_map(|(name, state)| Some((state, Process::attach(name)?)))?; 44 | 45 | Some(Self { 46 | process, 47 | state: Cell::new(state), 48 | ram_base: Cell::new(None), 49 | }) 50 | } 51 | 52 | /// Asynchronously awaits attaching to a target emulator, 53 | /// yielding back to the runtime between each try. 54 | /// 55 | /// Supported emulators are: 56 | /// - Retroarch, with one of the following cores: `genesis_plus_gx_libretro.dll`, 57 | /// `genesis_plus_gx_wide_libretro.dll`, `picodrive_libretro.dll`, `smsplus_libretro.dll`, `gearsystem_libretro.dll` 58 | /// - Fusion 59 | /// - BlastEm 60 | pub async fn wait_attach() -> Self { 61 | retry(Self::attach).await 62 | } 63 | 64 | /// Checks whether the emulator is still open. If it is not open anymore, 65 | /// you should drop the emulator. 66 | pub fn is_open(&self) -> bool { 67 | self.process.is_open() 68 | } 69 | 70 | /// Executes a future until the emulator process closes. 71 | pub const fn until_closes(&self, future: F) -> UntilEmulatorCloses<'_, F> { 72 | UntilEmulatorCloses { 73 | emulator: self, 74 | future, 75 | } 76 | } 77 | 78 | /// Calls the internal routines needed in order to find (and update, if 79 | /// needed) the address of the emulated RAM. 80 | /// 81 | /// Returns true if successful, false otherwise. 82 | pub fn update(&self) -> bool { 83 | let mut ram_base = self.ram_base.get(); 84 | let mut state = self.state.get(); 85 | 86 | if ram_base.is_none() { 87 | ram_base = match match &mut state { 88 | State::Retroarch(x) => x.find_ram(&self.process), 89 | State::Fusion(x) => x.find_ram(&self.process), 90 | State::BlastEm(x) => x.find_ram(&self.process), 91 | State::Mednafen(x) => x.find_ram(&self.process), 92 | } { 93 | None => return false, 94 | something => something, 95 | }; 96 | } 97 | 98 | let success = match &state { 99 | State::Retroarch(x) => x.keep_alive(&self.process), 100 | State::Fusion(x) => x.keep_alive(&self.process, &mut ram_base), 101 | State::BlastEm(x) => x.keep_alive(), 102 | State::Mednafen(x) => x.keep_alive(), 103 | }; 104 | 105 | if success { 106 | self.ram_base.set(ram_base); 107 | true 108 | } else { 109 | self.ram_base.set(None); 110 | false 111 | } 112 | } 113 | 114 | /// Converts a SEGA Master System memory address to a real memory address in the emulator process' virtual memory space 115 | /// 116 | /// Valid addresses for the SMS range from `0xC000` to `0xDFFF`. 117 | pub fn get_address(&self, offset: u32) -> Result { 118 | match offset { 119 | (0xC000..=0xDFFF) => Ok(self.ram_base.get().ok_or(Error {})? + offset.sub(0xC000)), 120 | _ => Err(Error {}), 121 | } 122 | } 123 | 124 | /// Checks if a memory reading operation would exceed the memory bounds of the emulated system. 125 | /// 126 | /// Returns `true` if the read operation can be performed safely, `false` otherwise. 127 | const fn check_bounds(&self, offset: u32) -> bool { 128 | match offset { 129 | (0xC000..=0xDFFF) => offset + size_of::() as u32 <= 0xE000, 130 | _ => false, 131 | } 132 | } 133 | 134 | /// Reads any value from the emulated RAM. 135 | /// 136 | /// The offset provided is meant to be the same used on the original hardware. 137 | /// 138 | /// The SEGA Master System has 8KB of RAM, mapped from address 139 | /// `0xC000` to `0xDFFF`. 140 | /// 141 | /// Providing any offset outside this range will return `Err()`. 142 | pub fn read(&self, offset: u32) -> Result { 143 | match self.check_bounds::(offset) { 144 | true => self.process.read(self.get_address(offset)?), 145 | false => Err(Error {}), 146 | } 147 | } 148 | } 149 | 150 | /// A future that executes a future until the emulator closes. 151 | #[must_use = "You need to await this future."] 152 | pub struct UntilEmulatorCloses<'a, F> { 153 | emulator: &'a Emulator, 154 | future: F, 155 | } 156 | 157 | impl> Future for UntilEmulatorCloses<'_, F> { 158 | type Output = Option; 159 | 160 | fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 161 | if !self.emulator.is_open() { 162 | return Poll::Ready(None); 163 | } 164 | self.emulator.update(); 165 | // SAFETY: We are simply projecting the Pin. 166 | unsafe { 167 | Pin::new_unchecked(&mut self.get_unchecked_mut().future) 168 | .poll(cx) 169 | .map(Some) 170 | } 171 | } 172 | } 173 | 174 | #[doc(hidden)] 175 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 176 | pub enum State { 177 | Retroarch(retroarch::State), 178 | Fusion(fusion::State), 179 | BlastEm(blastem::State), 180 | Mednafen(mednafen::State), 181 | } 182 | 183 | static PROCESS_NAMES: &[(&str, State)] = &[ 184 | ("retroarch.exe", State::Retroarch(retroarch::State::new())), 185 | ("Fusion.exe", State::Fusion(fusion::State::new())), 186 | ("blastem.exe", State::BlastEm(blastem::State)), 187 | ("mednafen.exe", State::Mednafen(mednafen::State)), 188 | ]; 189 | -------------------------------------------------------------------------------- /src/emulator/sms/retroarch.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | file_format::pe, signature::Signature, Address, Address32, Address64, PointerSize, Process, 3 | }; 4 | 5 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 6 | pub struct State { 7 | core_base: Address, 8 | } 9 | 10 | impl State { 11 | pub fn find_ram(&mut self, game: &Process) -> Option
{ 12 | const SUPPORTED_CORES: &[&str] = &[ 13 | "genesis_plus_gx_libretro.dll", 14 | "genesis_plus_gx_wide_libretro.dll", 15 | "picodrive_libretro.dll", 16 | "smsplus_libretro.dll", 17 | "gearsystem_libretro.dll", 18 | ]; 19 | 20 | let main_module_address = super::PROCESS_NAMES 21 | .iter() 22 | .filter(|(_, state)| matches!(state, super::State::Retroarch(_))) 23 | .find_map(|(name, _)| game.get_module_address(name).ok())?; 24 | 25 | let is_64_bit = 26 | pe::MachineType::read(game, main_module_address) == Some(pe::MachineType::X86_64); 27 | 28 | let (core_name, core_address) = SUPPORTED_CORES 29 | .iter() 30 | .find_map(|&m| Some((m, game.get_module_address(m).ok()?)))?; 31 | 32 | self.core_base = core_address; 33 | 34 | match core_name { 35 | "genesis_plus_gx_libretro.dll" | "genesis_plus_gx_wide_libretro.dll" => { 36 | self.genesis_plus(game, is_64_bit, core_name) 37 | } 38 | "picodrive_libretro.dll" => self.picodrive(game, is_64_bit, core_name), 39 | "smsplus_libretro.dll" => self.sms_plus(game, is_64_bit, core_name), 40 | "gearsystem_libretro.dll" => self.gearsystem(game, is_64_bit, core_name), 41 | _ => None, 42 | } 43 | } 44 | 45 | fn picodrive(&self, game: &Process, is_64_bit: bool, core_name: &str) -> Option
{ 46 | let module_size = game.get_module_size(core_name).ok()?; 47 | 48 | Some( 49 | if is_64_bit { 50 | const SIG: Signature<9> = Signature::new("48 8D 0D ?? ?? ?? ?? 41 B8"); 51 | let ptr = SIG.scan_process_range(game, (self.core_base, module_size))? + 3; 52 | ptr + 0x4 + game.read::(ptr).ok()? 53 | } else { 54 | const SIG: Signature<8> = Signature::new("B9 ?? ?? ?? ?? C1 EF 10"); 55 | let ptr = SIG.scan_process_range(game, (self.core_base, module_size))? + 1; 56 | game.read::(ptr).ok()?.into() 57 | } + 0x20000, 58 | ) 59 | } 60 | 61 | fn genesis_plus(&self, game: &Process, is_64_bit: bool, core_name: &str) -> Option
{ 62 | let module_size = game.get_module_size(core_name).ok()?; 63 | 64 | Some(if is_64_bit { 65 | const SIG: Signature<10> = Signature::new("48 8D 0D ?? ?? ?? ?? 4C 8B 2D"); 66 | let ptr = SIG.scan_process_range(game, (self.core_base, module_size))? + 3; 67 | ptr + 0x4 + game.read::(ptr).ok()? 68 | } else { 69 | const SIG: Signature<7> = Signature::new("A3 ?? ?? ?? ?? 29 F9"); 70 | let ptr = SIG.scan_process_range(game, (self.core_base, module_size))? + 1; 71 | game.read::(ptr).ok()?.into() 72 | }) 73 | } 74 | 75 | fn sms_plus(&self, game: &Process, is_64_bit: bool, core_name: &str) -> Option
{ 76 | let module_size = game.get_module_size(core_name).ok()?; 77 | 78 | Some(if is_64_bit { 79 | const SIG: Signature<5> = Signature::new("31 F6 48 C7 05"); 80 | let ptr = SIG.scan_process_range(game, (self.core_base, module_size))? + 5; 81 | ptr + 0x8 + game.read::(ptr).ok()? 82 | } else { 83 | const SIG: Signature<4> = Signature::new("83 FA 02 B8"); 84 | let ptr = SIG.scan_process_range(game, (self.core_base, module_size))? + 4; 85 | game.read::(ptr).ok()?.into() 86 | }) 87 | } 88 | 89 | fn gearsystem(&self, game: &Process, is_64_bit: bool, core_name: &str) -> Option
{ 90 | let module_size = game.get_module_size(core_name).ok()?; 91 | 92 | Some(if is_64_bit { 93 | const SIG: Signature<13> = Signature::new("83 ?? 02 75 ?? 48 8B 0D ?? ?? ?? ?? E8"); 94 | let ptr = SIG.scan_process_range(game, (self.core_base, module_size))? + 8; 95 | let offset = game 96 | .read::(ptr + 13 + 0x4 + game.read::(ptr + 13).ok()? + 3) 97 | .ok()?; 98 | let addr = game 99 | .read_pointer_path::( 100 | ptr + 0x4 + game.read::(ptr).ok()?, 101 | PointerSize::Bit64, 102 | &[0x0, 0x0, offset as _], 103 | ) 104 | .ok()?; 105 | if addr.is_null() { 106 | return None; 107 | } else { 108 | addr.add(0xC000).into() 109 | } 110 | } else { 111 | const SIG: Signature<12> = Signature::new("83 ?? 02 75 ?? 8B ?? ?? ?? ?? ?? E8"); 112 | let ptr = SIG.scan_process_range(game, (self.core_base, module_size))? + 7; 113 | let offset = game 114 | .read::(ptr + 12 + 0x4 + game.read::(ptr + 12).ok()? + 2) 115 | .ok()?; 116 | let addr = game 117 | .read_pointer_path::( 118 | ptr, 119 | PointerSize::Bit32, 120 | &[0x0, 0x0, 0x0, offset as _], 121 | ) 122 | .ok()?; 123 | if addr.is_null() { 124 | return None; 125 | } else { 126 | addr.add(0xC000).into() 127 | } 128 | }) 129 | } 130 | 131 | pub fn keep_alive(&self, game: &Process) -> bool { 132 | game.read::(self.core_base).is_ok() 133 | } 134 | 135 | pub const fn new() -> Self { 136 | Self { 137 | core_base: Address::NULL, 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /src/emulator/wii/dolphin.rs: -------------------------------------------------------------------------------- 1 | use crate::{Address, Endian, FromEndian, MemoryRangeFlags, Process}; 2 | 3 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 4 | pub struct State; 5 | 6 | impl State { 7 | pub fn find_ram(&self, game: &Process, endian: &mut Endian) -> Option<[Address; 2]> { 8 | let mem_1 = game 9 | .memory_ranges() 10 | .find(|range| { 11 | range 12 | .flags() 13 | .is_ok_and(|r| r.contains(MemoryRangeFlags::WRITE | MemoryRangeFlags::READ)) 14 | && range.size().is_ok_and(|size| size == 0x2000000) 15 | && range.address().is_ok_and(|addr| { 16 | game.read::<[u32; 2]>(addr + 0x3118) 17 | .is_ok_and(|val| val.from_endian(Endian::Big) == [0x4000000; 2]) 18 | }) 19 | })? 20 | .address() 21 | .ok()?; 22 | 23 | let mem_2 = game 24 | .memory_ranges() 25 | .find(|range| { 26 | range 27 | .flags() 28 | .is_ok_and(|r| r.contains(MemoryRangeFlags::WRITE | MemoryRangeFlags::READ)) 29 | && range.size().is_ok_and(|size| size == 0x4000000) 30 | && range 31 | .address() 32 | .is_ok_and(|addr| addr > mem_1 && addr < mem_1 + 0x10000000) 33 | })? 34 | .address() 35 | .ok()?; 36 | 37 | *endian = Endian::Big; 38 | Some([mem_1, mem_2]) 39 | } 40 | 41 | pub fn keep_alive(&self, game: &Process, ram_base: &Option<[Address; 2]>) -> bool { 42 | ram_base.is_some_and(|[mem1, mem2]| { 43 | game.read::(mem1).is_ok() && game.read::(mem2).is_ok() 44 | }) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/emulator/wii/retroarch.rs: -------------------------------------------------------------------------------- 1 | use crate::{file_format::pe, Address, Endian, Process}; 2 | 3 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 4 | pub struct State { 5 | core_base: Address, 6 | } 7 | 8 | impl State { 9 | pub fn find_ram(&mut self, game: &Process, endian: &mut Endian) -> Option<[Address; 2]> { 10 | const SUPPORTED_CORES: [&str; 1] = ["dolphin_libretro.dll"]; 11 | 12 | let main_module_address = super::PROCESS_NAMES 13 | .iter() 14 | .filter(|(_, state)| matches!(state, super::State::Retroarch(_))) 15 | .find_map(|(name, _)| game.get_module_address(name).ok())?; 16 | 17 | let is_64_bit = 18 | pe::MachineType::read(game, main_module_address) == Some(pe::MachineType::X86_64); 19 | 20 | if !is_64_bit { 21 | // The Dolphin core, the only one available for retroarch, only supports 64-bit 22 | return None; 23 | } 24 | 25 | self.core_base = SUPPORTED_CORES 26 | .iter() 27 | .find_map(|&m| game.get_module_address(m).ok())?; 28 | 29 | *endian = Endian::Big; 30 | super::dolphin::State::find_ram(&super::dolphin::State, game, endian) 31 | } 32 | 33 | pub fn keep_alive(&self, game: &Process, ram_base: &Option<[Address; 2]>) -> bool { 34 | game.read::(self.core_base).is_ok() 35 | && ram_base.is_some_and(|[mem1, mem2]| { 36 | game.read::(mem1).is_ok() && game.read::(mem2).is_ok() 37 | }) 38 | } 39 | 40 | pub const fn new() -> Self { 41 | Self { 42 | core_base: Address::NULL, 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/file_format/mod.rs: -------------------------------------------------------------------------------- 1 | //! Support for parsing various file formats. 2 | 3 | pub mod elf; 4 | pub mod pe; 5 | -------------------------------------------------------------------------------- /src/future/task.rs: -------------------------------------------------------------------------------- 1 | use core::{ 2 | cell::RefCell, 3 | future::Future, 4 | pin::Pin, 5 | task::{Context, Poll}, 6 | }; 7 | 8 | use alloc::{ 9 | boxed::Box, 10 | rc::{Rc, Weak}, 11 | vec::Vec, 12 | }; 13 | 14 | type TaskList<'fut> = RefCell + 'fut>>>>; 15 | 16 | /// Manages a list of tasks that can be spawned and run concurrently to each 17 | /// other. 18 | /// 19 | /// # Example 20 | /// 21 | /// ```no_run 22 | /// # use asr::future::Tasks; 23 | /// # async fn example() { 24 | /// let tasks = Tasks::new(); 25 | /// 26 | /// tasks.spawn(async { 27 | /// // do some work 28 | /// }); 29 | /// 30 | /// tasks.spawn_recursive(|tasks| async move { 31 | /// // do some work 32 | /// tasks.spawn(async { 33 | /// // do some work 34 | /// }); 35 | /// }); 36 | /// 37 | /// tasks.run().await; 38 | /// # } 39 | /// ``` 40 | pub struct Tasks<'fut> { 41 | // This type is explicitly not clone to ensure that you don't create an Rc 42 | // cycle (Task list owns futures who own the task list and so on). 43 | tasks: Rc>, 44 | } 45 | 46 | impl<'fut> Tasks<'fut> { 47 | /// Creates a new list of tasks to execute. 48 | pub fn new() -> Self { 49 | Self { 50 | tasks: Rc::new(RefCell::new(Vec::new())), 51 | } 52 | } 53 | 54 | /// Runs all tasks in the list to completion. While the tasks are running, 55 | /// further tasks can be spawned. 56 | pub fn run<'tasks>(&'tasks self) -> RunTasks<'fut, 'tasks> { 57 | RunTasks { 58 | tasks: Vec::new(), 59 | freshly_added: &self.tasks, 60 | } 61 | } 62 | 63 | /// Spawns a new task to be run concurrently to the other tasks. 64 | pub fn spawn(&self, f: impl Future + 'fut) { 65 | self.tasks.borrow_mut().push(Box::pin(f)); 66 | } 67 | 68 | /// Spawns a new task to be run concurrently to the other tasks. The 69 | /// provided closure is passed a [`TaskSpawner`] that can be used to spawn 70 | /// further tasks. 71 | pub fn spawn_recursive(&self, f: impl FnOnce(TaskSpawner<'fut>) -> F) 72 | where 73 | F: Future + 'fut, 74 | { 75 | self.spawn(f(self.spawner())); 76 | } 77 | 78 | /// Returns a [`TaskSpawner`] that can be used to spawn tasks. 79 | pub fn spawner(&self) -> TaskSpawner<'fut> { 80 | TaskSpawner { 81 | tasks: Rc::downgrade(&self.tasks), 82 | } 83 | } 84 | } 85 | 86 | impl<'fut> Default for Tasks<'fut> { 87 | fn default() -> Self { 88 | Self::new() 89 | } 90 | } 91 | 92 | /// A type that can be used to spawn tasks. 93 | #[derive(Clone)] 94 | pub struct TaskSpawner<'fut> { 95 | tasks: Weak>, 96 | } 97 | 98 | impl<'fut> TaskSpawner<'fut> { 99 | /// Spawns a new task to be run concurrently to the other tasks. 100 | pub fn spawn(&self, f: impl Future + 'fut) { 101 | if let Some(tasks) = self.tasks.upgrade() { 102 | tasks.borrow_mut().push(Box::pin(f)); 103 | } 104 | } 105 | 106 | /// Spawns a new task to be run concurrently to the other tasks. The 107 | /// provided closure is passed a [`TaskSpawner`] that can be used to spawn 108 | /// further tasks. 109 | pub fn spawn_recursive(&self, f: impl FnOnce(Self) -> F) 110 | where 111 | F: Future + 'fut, 112 | { 113 | self.spawn(f(self.clone())); 114 | } 115 | } 116 | 117 | /// A future that runs all tasks in the list to completion. 118 | #[must_use = "You need to await this future."] 119 | pub struct RunTasks<'fut, 'tasks> { 120 | tasks: Vec + 'fut>>>, 121 | freshly_added: &'tasks TaskList<'fut>, 122 | } 123 | 124 | impl Future for RunTasks<'_, '_> { 125 | type Output = (); 126 | 127 | fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 128 | let this = self.get_mut(); 129 | 130 | this.tasks.extend(this.freshly_added.borrow_mut().drain(..)); 131 | this.tasks.retain_mut(|f| f.as_mut().poll(cx).is_pending()); 132 | 133 | if this.tasks.is_empty() && this.freshly_added.borrow().is_empty() { 134 | Poll::Ready(()) 135 | } else { 136 | Poll::Pending 137 | } 138 | } 139 | } 140 | 141 | /// Wraps a future and allows it to spawn tasks that get run concurrently to the 142 | /// wrapped future. The future completes once all tasks are complete. 143 | /// 144 | /// Alternatively you can create a [`Tasks`] list for more control. 145 | /// 146 | /// # Example 147 | /// 148 | /// ```no_run 149 | /// # use asr::future::run_tasks; 150 | /// # async fn example() { 151 | /// run_tasks(|tasks| async move { 152 | /// // do some work 153 | /// 154 | /// tasks.spawn(async { 155 | /// // do some background work 156 | /// }); 157 | /// 158 | /// // use spawn_recursive to spawn tasks that can spawn further tasks 159 | /// tasks.spawn_recursive(|tasks| async move { 160 | /// tasks.spawn(async { 161 | /// // do some background work 162 | /// }); 163 | /// }); 164 | /// 165 | /// // do some work 166 | /// }).await; 167 | /// # } 168 | /// ``` 169 | pub async fn run_tasks<'fut, F>(f: impl FnOnce(TaskSpawner<'fut>) -> F) 170 | where 171 | F: Future + 'fut, 172 | { 173 | let tasks = Tasks::new(); 174 | tasks.spawn_recursive(f); 175 | tasks.run().await; 176 | } 177 | -------------------------------------------------------------------------------- /src/future/time.rs: -------------------------------------------------------------------------------- 1 | use core::{ 2 | future::Future, 3 | pin::Pin, 4 | task::{Context, Poll}, 5 | time::Duration, 6 | }; 7 | 8 | use crate::time_util::Instant; 9 | 10 | /// A type that provides futures that resolve in fixed intervals. 11 | /// 12 | /// # Example 13 | /// 14 | /// ```no_run 15 | /// let mut interval = interval(Duration::from_secs(1)); 16 | /// loop { 17 | /// interval.tick().await; 18 | /// print_message("A second has passed!"); 19 | /// } 20 | /// ``` 21 | pub struct Interval { 22 | next: u64, 23 | duration: u64, 24 | } 25 | 26 | impl Interval { 27 | /// Returns a future that resolves in the next interval. 28 | /// 29 | /// # Example 30 | /// 31 | /// ```no_run 32 | /// let mut interval = interval(Duration::from_secs(1)); 33 | /// loop { 34 | /// interval.tick().await; 35 | /// print_message("A second has passed!"); 36 | /// } 37 | /// ``` 38 | pub fn tick(&mut self) -> Sleep { 39 | let next = self.next; 40 | self.next += self.duration; 41 | Sleep(next) 42 | } 43 | } 44 | 45 | /// A type that provides futures that resolve in fixed intervals. 46 | /// 47 | /// # Example 48 | /// 49 | /// ```no_run 50 | /// let mut interval = interval(Duration::from_secs(1)); 51 | /// loop { 52 | /// interval.tick().await; 53 | /// print_message("A second has passed!"); 54 | /// } 55 | /// ``` 56 | pub fn interval(duration: Duration) -> Interval { 57 | let duration = duration.as_nanos() as u64; 58 | Interval { 59 | next: Instant::now().0 + duration, 60 | duration, 61 | } 62 | } 63 | 64 | /// A future that yields back to the runtime for a certain amount of time and 65 | /// then resolves once the time has passed. 66 | /// 67 | /// # Example 68 | /// 69 | /// ```no_run 70 | /// sleep(Duration::from_secs(1)).await; 71 | /// print_message("A second has passed!"); 72 | /// ``` 73 | #[must_use = "You need to await this future."] 74 | pub struct Sleep(u64); 75 | 76 | impl Future for Sleep { 77 | type Output = (); 78 | 79 | fn poll(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll { 80 | if Instant::now().0 < self.0 { 81 | Poll::Pending 82 | } else { 83 | Poll::Ready(()) 84 | } 85 | } 86 | } 87 | 88 | /// A future that yields back to the runtime for a certain amount of time and 89 | /// then resolves once the time has passed. 90 | /// 91 | /// # Example 92 | /// 93 | /// ```no_run 94 | /// sleep(Duration::from_secs(1)).await; 95 | /// print_message("A second has passed!"); 96 | /// ``` 97 | pub fn sleep(duration: Duration) -> Sleep { 98 | Sleep(Instant::now().0 + duration.as_nanos() as u64) 99 | } 100 | 101 | /// A future that resolves to [`None`] after a certain amount of time, if the 102 | /// provided future has not resolved yet. 103 | /// 104 | /// # Example 105 | /// 106 | /// ```no_run 107 | /// let future = async { 108 | /// // do some work 109 | /// }; 110 | /// 111 | /// let result = timeout(Duration::from_secs(1), future).await; 112 | /// if let Some(result) = result { 113 | /// // do something with the result 114 | /// } else { 115 | /// // the future timed out 116 | /// } 117 | /// ``` 118 | #[must_use = "You need to await this future."] 119 | pub struct Timeout { 120 | sleep: Sleep, 121 | future: F, 122 | } 123 | 124 | impl Future for Timeout { 125 | type Output = Option; 126 | 127 | fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 128 | // SAFETY: We are simply projecting the Pin to the inner futures. 129 | unsafe { 130 | let this = self.get_unchecked_mut(); 131 | if let Poll::Ready(()) = Pin::new_unchecked(&mut this.sleep).poll(cx) { 132 | return Poll::Ready(None); 133 | } 134 | Pin::new_unchecked(&mut this.future).poll(cx).map(Some) 135 | } 136 | } 137 | } 138 | 139 | /// A future that resolves to [`None`] after a certain amount of time, if the 140 | /// provided future has not resolved yet. 141 | /// 142 | /// # Example 143 | /// 144 | /// ```no_run 145 | /// let future = async { 146 | /// // do some work 147 | /// }; 148 | /// 149 | /// let result = timeout(Duration::from_secs(1), future).await; 150 | /// if let Some(result) = result { 151 | /// // do something with the result 152 | /// } else { 153 | /// // the future timed out 154 | /// } 155 | /// ``` 156 | pub fn timeout(duration: Duration, future: F) -> Timeout { 157 | Timeout { 158 | sleep: sleep(duration), 159 | future, 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /src/game_engine/godot/core/mod.rs: -------------------------------------------------------------------------------- 1 | mod object; 2 | mod os; 3 | mod string; 4 | mod templates; 5 | mod variant; 6 | 7 | pub use object::*; 8 | pub use os::*; 9 | pub use string::*; 10 | pub use templates::*; 11 | pub use variant::*; 12 | -------------------------------------------------------------------------------- /src/game_engine/godot/core/object/mod.rs: -------------------------------------------------------------------------------- 1 | mod object; 2 | mod script_instance; 3 | mod script_language; 4 | 5 | pub use object::*; 6 | pub use script_instance::*; 7 | pub use script_language::*; 8 | -------------------------------------------------------------------------------- /src/game_engine/godot/core/object/object.rs: -------------------------------------------------------------------------------- 1 | //! 2 | 3 | use crate::{ 4 | game_engine::godot::{Ptr, VTable, VariantType}, 5 | Error, Process, 6 | }; 7 | 8 | use super::ScriptInstance; 9 | 10 | #[allow(unused)] 11 | mod offsets { 12 | // *const VTable 13 | pub const VTABLE_PTR: u64 = 0x0; 14 | // *const ObjectGDExtension 15 | pub const EXTENSION: u64 = 0x8; 16 | // GDExtensionClassInstancePtr 17 | pub const EXTENSION_INSTANCE: u64 = 0x10; 18 | // HashMap 19 | pub const SIGNAL_MAP: u64 = 0x18; 20 | // List 21 | pub const CONNECTIONS: u64 = 0x48; 22 | // bool 23 | pub const BLOCK_SIGNALS: u64 = 0x50; 24 | // i32 25 | pub const PREDELETE_OK: u64 = 0x54; 26 | // ObjectID 27 | pub const INSTANCE_ID: u64 = 0x58; 28 | // bool 29 | pub const CAN_TRANSLATE: u64 = 0x60; 30 | // bool 31 | pub const EMITTING: u64 = 0x61; 32 | // *const ScriptInstance 33 | pub const SCRIPT_INSTANCE: u64 = 0x68; 34 | // Variant 35 | pub const SCRIPT: u64 = 0x70; 36 | // HashMap 37 | pub const METADATA: u64 = 0x88; 38 | // HashMap 39 | pub const METADATA_PROPERTIES: u64 = 0xb8; 40 | // *const StringName 41 | pub const CLASS_NAME_PTR: u64 = 0xe8; 42 | } 43 | 44 | /// Information about a property of a script. This is not publicly exposed in 45 | /// Godot. 46 | /// 47 | /// Check the [`Ptr`] documentation to see all the methods you can 48 | /// call on it. 49 | #[derive(Debug, Copy, Clone)] 50 | #[repr(transparent)] 51 | pub struct PropertyInfo; 52 | 53 | impl Ptr { 54 | /// Returns the type of the property as a [`VariantType`]. 55 | pub fn get_variant_type(self, process: &Process) -> Result { 56 | self.read_at_byte_offset(0x0, process) 57 | } 58 | } 59 | 60 | /// Base class for all other classes in the engine. 61 | /// 62 | /// [`Object`](https://docs.godotengine.org/en/4.2/classes/class_object.html) 63 | /// 64 | /// Check the [`Ptr`] documentation to see all the methods you can call 65 | /// on it. 66 | #[derive(Debug, Copy, Clone)] 67 | #[repr(transparent)] 68 | pub struct Object; 69 | 70 | impl Ptr { 71 | /// Returns a pointer to the object's virtual method table. 72 | pub fn get_vtable(self, process: &Process) -> Result, Error> { 73 | self.read_at_byte_offset(offsets::VTABLE_PTR, process) 74 | } 75 | 76 | /// Returns the object's Script instance, or [`None`] if no script is 77 | /// attached. 78 | /// 79 | /// [`Object.get_script`](https://docs.godotengine.org/en/4.2/classes/class_object.html#class-object-method-get-script) 80 | pub fn get_script_instance( 81 | self, 82 | process: &Process, 83 | ) -> Result>, Error> { 84 | let ptr: Ptr = 85 | self.read_at_byte_offset(offsets::SCRIPT_INSTANCE, process)?; 86 | Ok(if ptr.is_null() { None } else { Some(ptr) }) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/game_engine/godot/core/object/script_instance.rs: -------------------------------------------------------------------------------- 1 | //! 2 | 3 | /// An instance of a [`Script`](super::Script). 4 | /// 5 | /// You need to cast this to a 6 | /// [`GDScriptInstance`](crate::game_engine::godot::GDScriptInstance) or 7 | /// [`CSharpScriptInstance`](crate::game_engine::godot::CSharpScriptInstance) to 8 | /// do anything meaningful with it. Make sure to verify the script language 9 | /// before casting. 10 | #[derive(Debug, Copy, Clone)] 11 | #[repr(transparent)] 12 | pub struct ScriptInstance; 13 | -------------------------------------------------------------------------------- /src/game_engine/godot/core/object/script_language.rs: -------------------------------------------------------------------------------- 1 | //! 2 | 3 | /// A class stored as a resource. 4 | /// 5 | /// [`Script`](https://docs.godotengine.org/en/4.2/classes/class_script.html) 6 | /// 7 | /// You need to cast this to a [`GDScript`](crate::game_engine::godot::GDScript) 8 | /// or [`CSharpScript`](crate::game_engine::godot::CSharpScript) to do anything 9 | /// meaningful with it. Make sure to verify the script language before casting. 10 | #[derive(Debug, Copy, Clone)] 11 | #[repr(transparent)] 12 | pub struct Script; 13 | -------------------------------------------------------------------------------- /src/game_engine/godot/core/os/main_loop.rs: -------------------------------------------------------------------------------- 1 | //! 2 | 3 | use crate::game_engine::godot::Object; 4 | 5 | /// Abstract base class for the game's main loop. 6 | /// 7 | /// [`MainLoop`](https://docs.godotengine.org/en/4.2/classes/class_mainloop.html) 8 | #[derive(Debug, Copy, Clone)] 9 | #[repr(transparent)] 10 | pub struct MainLoop; 11 | extends!(MainLoop: Object); 12 | -------------------------------------------------------------------------------- /src/game_engine/godot/core/os/mod.rs: -------------------------------------------------------------------------------- 1 | mod main_loop; 2 | 3 | pub use main_loop::*; 4 | -------------------------------------------------------------------------------- /src/game_engine/godot/core/string/mod.rs: -------------------------------------------------------------------------------- 1 | mod string_name; 2 | mod ustring; 3 | 4 | pub use string_name::*; 5 | pub use ustring::*; 6 | -------------------------------------------------------------------------------- /src/game_engine/godot/core/string/string_name.rs: -------------------------------------------------------------------------------- 1 | //! 2 | 3 | use core::mem::MaybeUninit; 4 | 5 | use arrayvec::ArrayVec; 6 | use bytemuck::{Pod, Zeroable}; 7 | 8 | use crate::{ 9 | game_engine::godot::{Hash, Ptr, SizeInTargetProcess}, 10 | Address64, Error, Process, 11 | }; 12 | 13 | use super::String; 14 | 15 | #[allow(unused)] 16 | mod offsets { 17 | pub mod data { 18 | use super::super::{SizeInTargetProcess, String}; 19 | 20 | pub const REFCOUNT: u64 = 0x00; 21 | pub const STATIC_COUNT: u64 = 0x04; 22 | pub const CNAME: u64 = 0x08; 23 | pub const NAME: u64 = 0x10; 24 | pub const IDX: u64 = NAME + String::<0>::SIZE; 25 | pub const HASH: u64 = IDX + 0x4; 26 | } 27 | } 28 | 29 | /// A built-in type for unique strings. 30 | /// 31 | /// [`StringName`](https://docs.godotengine.org/en/4.2/classes/class_stringname.html) 32 | #[derive(Debug, Copy, Clone, Pod, Zeroable)] 33 | #[repr(transparent)] 34 | pub struct StringName { 35 | data: Ptr, 36 | } 37 | 38 | impl Hash<[u8; N]> for StringName { 39 | fn hash_of_lookup_key(lookup_key: &[u8; N]) -> u32 { 40 | // String::hash 41 | let mut hashv: u32 = 5381; 42 | 43 | for c in lossy_chars(lookup_key) { 44 | hashv = hashv.wrapping_mul(33).wrapping_add(c as u32); 45 | } 46 | 47 | hashv 48 | } 49 | 50 | fn eq(&self, lookup_key: &[u8; N], process: &Process) -> bool { 51 | let Ok(name) = self.read::(process) else { 52 | return false; 53 | }; 54 | name.chars().eq(lossy_chars(lookup_key)) 55 | } 56 | } 57 | 58 | fn lossy_chars(lookup_key: &[u8]) -> impl Iterator + '_ { 59 | lookup_key.utf8_chunks().flat_map(|chunk| { 60 | chunk.valid().chars().chain(if chunk.invalid().is_empty() { 61 | None 62 | } else { 63 | Some(char::REPLACEMENT_CHARACTER) 64 | }) 65 | }) 66 | } 67 | 68 | impl SizeInTargetProcess for StringName { 69 | const SIZE: u64 = 0x8; 70 | } 71 | 72 | #[derive(Debug, Copy, Clone, Pod, Zeroable)] 73 | #[repr(transparent)] 74 | struct Data(Address64); 75 | 76 | impl StringName { 77 | /// Reads the string from the target process. 78 | pub fn read(self, process: &Process) -> Result, Error> { 79 | // FIXME: This skips cname entirely atm. 80 | 81 | // FIXME: Use CowData 82 | let cow_data: Address64 = self 83 | .data 84 | .read_at_byte_offset(offsets::data::NAME, process)?; 85 | 86 | // Only on 4.2 or before. 87 | let len = process 88 | .read::(cow_data + -0x4)? 89 | .checked_sub(1) 90 | .ok_or(Error {})?; 91 | let mut buf = [MaybeUninit::uninit(); N]; 92 | let buf = buf.get_mut(..len as usize).ok_or(Error {})?; 93 | let buf = process.read_into_uninit_slice(cow_data, buf)?; 94 | 95 | let mut out = ArrayVec::new(); 96 | out.extend(buf.iter().copied()); 97 | 98 | Ok(String(out)) 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/game_engine/godot/core/string/ustring.rs: -------------------------------------------------------------------------------- 1 | //! 2 | 3 | use arrayvec::{ArrayString, ArrayVec}; 4 | 5 | use crate::game_engine::godot::SizeInTargetProcess; 6 | 7 | /// A built-in type for strings. 8 | /// 9 | /// [`String`](https://docs.godotengine.org/en/4.2/classes/class_string.html) 10 | #[derive(Clone)] 11 | pub struct String(pub(super) ArrayVec); 12 | 13 | impl SizeInTargetProcess for String { 14 | const SIZE: u64 = 0x8; 15 | } 16 | 17 | impl String { 18 | /// Returns an iterator over the characters in this string. 19 | pub fn chars(&self) -> impl Iterator + '_ { 20 | self.0 21 | .iter() 22 | .copied() 23 | .map(|c| char::from_u32(c).unwrap_or(char::REPLACEMENT_CHARACTER)) 24 | } 25 | 26 | /// Converts this string to an [`ArrayString`]. If the string is too long to 27 | /// fit in the array, the excess characters are truncated. 28 | pub fn to_array_string(&self) -> ArrayString { 29 | let mut buf = ArrayString::::new(); 30 | for c in self.chars() { 31 | let _ = buf.try_push(c); 32 | } 33 | buf 34 | } 35 | 36 | /// Checks if this string matches the given string. 37 | pub fn matches_str(&self, text: &str) -> bool { 38 | self.chars().eq(text.chars()) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/game_engine/godot/core/templates/cowdata.rs: -------------------------------------------------------------------------------- 1 | //! 2 | 3 | use bytemuck::{Pod, Zeroable}; 4 | 5 | use crate::game_engine::godot::Ptr; 6 | 7 | /// A copy-on-write data type. This is not publicly exposed in Godot. 8 | #[repr(transparent)] 9 | pub struct CowData(Ptr); 10 | 11 | impl Copy for CowData {} 12 | 13 | impl Clone for CowData { 14 | fn clone(&self) -> Self { 15 | *self 16 | } 17 | } 18 | 19 | // SAFETY: The type is transparent over a `Ptr`, which is `Pod`. 20 | unsafe impl Pod for CowData {} 21 | 22 | // SAFETY: The type is transparent over a `Ptr`, which is `Zeroable`. 23 | unsafe impl Zeroable for CowData {} 24 | 25 | impl CowData { 26 | /// Returns the pointer to the underlying data. 27 | pub fn ptr(self) -> Ptr { 28 | self.0 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/game_engine/godot/core/templates/hash_set.rs: -------------------------------------------------------------------------------- 1 | //! 2 | 3 | use core::marker::PhantomData; 4 | 5 | use crate::game_engine::godot::SizeInTargetProcess; 6 | 7 | impl SizeInTargetProcess for HashSet { 8 | const SIZE: u64 = 40; 9 | } 10 | 11 | /// A hash set that uniquely stores each element. This is not publicly exposed 12 | /// in Godot. 13 | #[derive(Debug, Copy, Clone)] 14 | #[repr(transparent)] 15 | pub struct HashSet(PhantomData K>); 16 | -------------------------------------------------------------------------------- /src/game_engine/godot/core/templates/hashfuncs.rs: -------------------------------------------------------------------------------- 1 | //! 2 | 3 | use core::num::NonZeroU32; 4 | 5 | use bytemuck::CheckedBitPattern; 6 | 7 | use crate::Process; 8 | 9 | /// A trait for looking up a key in a hash table. The type of the key to look up 10 | /// does not need to match the type in the target process. However, it needs to 11 | /// hash and compare equally in the same way. 12 | pub trait Hash: CheckedBitPattern { 13 | /// Hashes the lookup key. 14 | fn hash_of_lookup_key(lookup_key: &Q) -> u32; 15 | /// Compares the lookup key with the key in the target process. Errors are 16 | /// meant to be ignored and instead treated as comparing unequal. 17 | fn eq(&self, lookup_key: &Q, process: &Process) -> bool; 18 | } 19 | 20 | const HASH_TABLE_SIZE_MAX: usize = 29; 21 | 22 | #[track_caller] 23 | pub(super) const fn n32(x: u32) -> NonZeroU32 { 24 | match NonZeroU32::new(x) { 25 | Some(x) => x, 26 | None => panic!(), 27 | } 28 | } 29 | 30 | pub(super) const HASH_TABLE_SIZE_PRIMES: [NonZeroU32; HASH_TABLE_SIZE_MAX] = [ 31 | n32(5), 32 | n32(13), 33 | n32(23), 34 | n32(47), 35 | n32(97), 36 | n32(193), 37 | n32(389), 38 | n32(769), 39 | n32(1543), 40 | n32(3079), 41 | n32(6151), 42 | n32(12289), 43 | n32(24593), 44 | n32(49157), 45 | n32(98317), 46 | n32(196613), 47 | n32(393241), 48 | n32(786433), 49 | n32(1572869), 50 | n32(3145739), 51 | n32(6291469), 52 | n32(12582917), 53 | n32(25165843), 54 | n32(50331653), 55 | n32(100663319), 56 | n32(201326611), 57 | n32(402653189), 58 | n32(805306457), 59 | n32(1610612741), 60 | ]; 61 | 62 | pub(super) const HASH_TABLE_SIZE_PRIMES_INV: [u64; HASH_TABLE_SIZE_MAX] = [ 63 | 3689348814741910324, 64 | 1418980313362273202, 65 | 802032351030850071, 66 | 392483916461905354, 67 | 190172619316593316, 68 | 95578984837873325, 69 | 47420935922132524, 70 | 23987963684927896, 71 | 11955116055547344, 72 | 5991147799191151, 73 | 2998982941588287, 74 | 1501077717772769, 75 | 750081082979285, 76 | 375261795343686, 77 | 187625172388393, 78 | 93822606204624, 79 | 46909513691883, 80 | 23456218233098, 81 | 11728086747027, 82 | 5864041509391, 83 | 2932024948977, 84 | 1466014921160, 85 | 733007198436, 86 | 366503839517, 87 | 183251896093, 88 | 91625960335, 89 | 45812983922, 90 | 22906489714, 91 | 11453246088, 92 | ]; 93 | 94 | pub(super) fn fastmod(n: u32, _c: u64, d: NonZeroU32) -> u32 { 95 | #[cfg(not(target_family = "wasm"))] 96 | { 97 | let lowbits = _c.wrapping_mul(n as u64); 98 | // TODO: `widening_mul` 99 | ((lowbits as u128 * d.get() as u128) >> 64) as u32 100 | } 101 | #[cfg(target_family = "wasm")] 102 | { 103 | n % d.get() 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/game_engine/godot/core/templates/list.rs: -------------------------------------------------------------------------------- 1 | //! 2 | 3 | use core::marker::PhantomData; 4 | 5 | use crate::game_engine::godot::SizeInTargetProcess; 6 | 7 | impl SizeInTargetProcess for List { 8 | const SIZE: u64 = 0x8; 9 | } 10 | 11 | /// A linked list of elements. This is not publicly exposed in Godot. 12 | #[derive(Debug, Copy, Clone)] 13 | #[repr(transparent)] 14 | pub struct List(PhantomData T>); 15 | -------------------------------------------------------------------------------- /src/game_engine/godot/core/templates/mod.rs: -------------------------------------------------------------------------------- 1 | mod cowdata; 2 | mod hash_map; 3 | mod hash_set; 4 | mod hashfuncs; 5 | mod list; 6 | mod vector; 7 | 8 | pub use cowdata::*; 9 | pub use hash_map::*; 10 | pub use hash_set::*; 11 | pub use hashfuncs::*; 12 | pub use list::*; 13 | pub use vector::*; 14 | -------------------------------------------------------------------------------- /src/game_engine/godot/core/templates/vector.rs: -------------------------------------------------------------------------------- 1 | //! 2 | 3 | use core::mem::size_of; 4 | 5 | use bytemuck::{Pod, Zeroable}; 6 | 7 | use crate::game_engine::godot::{Ptr, SizeInTargetProcess}; 8 | 9 | use super::CowData; 10 | 11 | /// A contiguous vector of elements. This is not publicly exposed in Godot. 12 | #[repr(C)] 13 | pub struct Vector { 14 | // lol this is pure padding, they messed up 15 | write_proxy: [u8; 0x8], 16 | cowdata: CowData, 17 | } 18 | 19 | impl SizeInTargetProcess for Vector { 20 | const SIZE: u64 = size_of::>() as u64; 21 | } 22 | 23 | impl Copy for Vector {} 24 | 25 | impl Clone for Vector { 26 | fn clone(&self) -> Self { 27 | *self 28 | } 29 | } 30 | 31 | // SAFETY: The type is transparent over a `CowData` and a byte array, which is `Pod`. 32 | unsafe impl Pod for Vector {} 33 | 34 | // SAFETY: The type is transparent over a `CowData` and a byte array, which is `Zeroable`. 35 | unsafe impl Zeroable for Vector {} 36 | 37 | impl Vector { 38 | /// Returns the pointer to the underlying data at the given index. This does 39 | /// not perform bounds checking. 40 | pub fn unchecked_at(&self, index: u64) -> Ptr { 41 | Ptr::new(self.cowdata.ptr().addr() + index.wrapping_mul(T::SIZE)) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/game_engine/godot/core/variant/mod.rs: -------------------------------------------------------------------------------- 1 | mod variant; 2 | 3 | pub use variant::*; 4 | -------------------------------------------------------------------------------- /src/game_engine/godot/core/variant/variant.rs: -------------------------------------------------------------------------------- 1 | //! 2 | 3 | use core::{fmt, mem::size_of}; 4 | 5 | use bytemuck::{Pod, Zeroable}; 6 | 7 | use crate::game_engine::godot::SizeInTargetProcess; 8 | 9 | /// The type of a [`Variant`]. 10 | #[derive(Copy, Clone, PartialEq, Eq, Hash, Pod, Zeroable)] 11 | #[repr(transparent)] 12 | pub struct VariantType(u8); 13 | 14 | impl fmt::Debug for VariantType { 15 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 16 | f.write_str(match *self { 17 | Self::NIL => "NIL", 18 | Self::BOOL => "BOOL", 19 | Self::INT => "INT", 20 | Self::FLOAT => "FLOAT", 21 | Self::STRING => "STRING", 22 | Self::VECTOR2 => "VECTOR2", 23 | Self::VECTOR2I => "VECTOR2I", 24 | Self::RECT2 => "RECT2", 25 | Self::RECT2I => "RECT2I", 26 | Self::VECTOR3 => "VECTOR3", 27 | Self::VECTOR3I => "VECTOR3I", 28 | Self::TRANSFORM2D => "TRANSFORM2D", 29 | Self::VECTOR4 => "VECTOR4", 30 | Self::VECTOR4I => "VECTOR4I", 31 | Self::PLANE => "PLANE", 32 | Self::QUATERNION => "QUATERNION", 33 | Self::AABB => "AABB", 34 | Self::BASIS => "BASIS", 35 | Self::TRANSFORM3D => "TRANSFORM3D", 36 | Self::PROJECTION => "PROJECTION", 37 | Self::COLOR => "COLOR", 38 | Self::STRING_NAME => "STRING_NAME", 39 | Self::NODE_PATH => "NODE_PATH", 40 | Self::RID => "RID", 41 | Self::OBJECT => "OBJECT", 42 | Self::CALLABLE => "CALLABLE", 43 | Self::SIGNAL => "SIGNAL", 44 | Self::DICTIONARY => "DICTIONARY", 45 | Self::ARRAY => "ARRAY", 46 | Self::PACKED_BYTE_ARRAY => "PACKED_BYTE_ARRAY", 47 | Self::PACKED_INT32_ARRAY => "PACKED_INT32_ARRAY", 48 | Self::PACKED_INT64_ARRAY => "PACKED_INT64_ARRAY", 49 | Self::PACKED_FLOAT32_ARRAY => "PACKED_FLOAT32_ARRAY", 50 | Self::PACKED_FLOAT64_ARRAY => "PACKED_FLOAT64_ARRAY", 51 | Self::PACKED_STRING_ARRAY => "PACKED_STRING_ARRAY", 52 | Self::PACKED_VECTOR2_ARRAY => "PACKED_VECTOR2_ARRAY", 53 | Self::PACKED_VECTOR3_ARRAY => "PACKED_VECTOR3_ARRAY", 54 | Self::PACKED_COLOR_ARRAY => "PACKED_COLOR_ARRAY", 55 | _ => "", 56 | }) 57 | } 58 | } 59 | 60 | #[allow(missing_docs)] 61 | impl VariantType { 62 | pub const NIL: Self = Self(0); 63 | 64 | // atomic types 65 | pub const BOOL: Self = Self(1); 66 | pub const INT: Self = Self(2); 67 | pub const FLOAT: Self = Self(3); 68 | pub const STRING: Self = Self(4); 69 | 70 | // math types 71 | pub const VECTOR2: Self = Self(5); 72 | pub const VECTOR2I: Self = Self(6); 73 | pub const RECT2: Self = Self(7); 74 | pub const RECT2I: Self = Self(8); 75 | pub const VECTOR3: Self = Self(9); 76 | pub const VECTOR3I: Self = Self(10); 77 | pub const TRANSFORM2D: Self = Self(11); 78 | pub const VECTOR4: Self = Self(12); 79 | pub const VECTOR4I: Self = Self(13); 80 | pub const PLANE: Self = Self(14); 81 | pub const QUATERNION: Self = Self(15); 82 | pub const AABB: Self = Self(16); 83 | pub const BASIS: Self = Self(17); 84 | pub const TRANSFORM3D: Self = Self(18); 85 | pub const PROJECTION: Self = Self(19); 86 | 87 | // misc types 88 | pub const COLOR: Self = Self(20); 89 | pub const STRING_NAME: Self = Self(21); 90 | pub const NODE_PATH: Self = Self(22); 91 | pub const RID: Self = Self(23); 92 | pub const OBJECT: Self = Self(24); 93 | pub const CALLABLE: Self = Self(25); 94 | pub const SIGNAL: Self = Self(26); 95 | pub const DICTIONARY: Self = Self(27); 96 | pub const ARRAY: Self = Self(28); 97 | 98 | // typed arrays 99 | pub const PACKED_BYTE_ARRAY: Self = Self(29); 100 | pub const PACKED_INT32_ARRAY: Self = Self(30); 101 | pub const PACKED_INT64_ARRAY: Self = Self(31); 102 | pub const PACKED_FLOAT32_ARRAY: Self = Self(32); 103 | pub const PACKED_FLOAT64_ARRAY: Self = Self(33); 104 | pub const PACKED_STRING_ARRAY: Self = Self(34); 105 | pub const PACKED_VECTOR2_ARRAY: Self = Self(35); 106 | pub const PACKED_VECTOR3_ARRAY: Self = Self(36); 107 | pub const PACKED_COLOR_ARRAY: Self = Self(37); 108 | } 109 | 110 | /// The most important data type in Godot. 111 | /// 112 | /// [`Variant`](https://docs.godotengine.org/en/4.2/classes/class_variant.html) 113 | #[derive(Copy, Clone, PartialEq, Eq, Hash, Pod, Zeroable)] 114 | #[repr(C)] 115 | pub struct Variant { 116 | /// The type of the variant. 117 | pub ty: VariantType, 118 | _padding: [u8; 7], 119 | /// The data of the variant. Use one of the accessors to get the data, based 120 | /// on the type. 121 | pub data: [u8; 16], 122 | } 123 | 124 | impl Variant { 125 | /// Assume the variant is a boolean and returns its value. Make sure this is 126 | /// the correct type beforehand. 127 | pub fn get_bool(&self) -> bool { 128 | self.data[0] != 0 129 | } 130 | 131 | /// Assume the variant is an integer and returns its value. Make sure this 132 | /// is the correct type beforehand. 133 | pub fn get_int(&self) -> i32 { 134 | let [i, _, _, _]: &[i32; 4] = bytemuck::cast_ref(&self.data); 135 | *i 136 | } 137 | 138 | /// Assume the variant is a float and returns its value. Make sure this is 139 | /// the correct type beforehand. 140 | pub fn get_float(&self) -> f32 { 141 | let [f, _, _, _]: &[f32; 4] = bytemuck::cast_ref(&self.data); 142 | *f 143 | } 144 | } 145 | 146 | impl fmt::Debug for Variant { 147 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 148 | match self.ty { 149 | VariantType::NIL => write!(f, "Variant::NIL"), 150 | VariantType::BOOL => write!(f, "Variant::BOOL({})", self.get_bool()), 151 | VariantType::INT => write!(f, "Variant::INT({})", self.get_int()), 152 | VariantType::FLOAT => write!(f, "Variant::FLOAT({})", self.get_float()), 153 | _ => f 154 | .debug_struct("Variant") 155 | .field("ty", &self.ty) 156 | .field("data", &self.data) 157 | .finish(), 158 | } 159 | } 160 | } 161 | 162 | impl SizeInTargetProcess for Variant { 163 | const SIZE: u64 = size_of::() as u64; 164 | } 165 | -------------------------------------------------------------------------------- /src/game_engine/godot/cpp/mod.rs: -------------------------------------------------------------------------------- 1 | //! This module is not Godot specific and instead provides generic utilities for 2 | //! working with processes written in C++. It could be moved outside at some 3 | //! point in the future. 4 | 5 | mod ptr; 6 | mod type_info; 7 | mod vtable; 8 | 9 | pub use ptr::*; 10 | pub use type_info::*; 11 | pub use vtable::*; 12 | 13 | /// The size of a type in the target process. 14 | pub trait SizeInTargetProcess { 15 | /// The size of the type in the target process. 16 | const SIZE: u64; 17 | } 18 | -------------------------------------------------------------------------------- /src/game_engine/godot/cpp/ptr.rs: -------------------------------------------------------------------------------- 1 | use core::{any::type_name, fmt, marker::PhantomData, ops::Add}; 2 | 3 | use bytemuck::{CheckedBitPattern, Pod, Zeroable}; 4 | 5 | use crate::{Address64, Error, Process}; 6 | 7 | /// A pointer is an address in the target process that knows the type that it's 8 | /// targeting. 9 | #[repr(transparent)] 10 | pub struct Ptr(Address64, PhantomData T>); 11 | 12 | impl fmt::Debug for Ptr { 13 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 14 | write!(f, "{}*: {}", type_name::(), self.0) 15 | } 16 | } 17 | 18 | impl Copy for Ptr {} 19 | 20 | impl Clone for Ptr { 21 | fn clone(&self) -> Self { 22 | *self 23 | } 24 | } 25 | 26 | impl Default for Ptr { 27 | fn default() -> Self { 28 | Self(Address64::NULL, PhantomData) 29 | } 30 | } 31 | 32 | // SAFETY: The type is transparent over an `Address64`, which is `Pod`. 33 | unsafe impl Pod for Ptr {} 34 | 35 | // SAFETY: The type is transparent over an `Address64`, which is `Zeroable`. 36 | unsafe impl Zeroable for Ptr {} 37 | 38 | impl Ptr { 39 | /// Creates a new pointer from the given address. 40 | pub fn new(addr: Address64) -> Self { 41 | Self(addr, PhantomData) 42 | } 43 | 44 | /// Checks whether the pointer is null. 45 | pub fn is_null(self) -> bool { 46 | self.0.is_null() 47 | } 48 | 49 | /// Reads the value that this pointer points to from the target process. 50 | pub fn deref(self, process: &Process) -> Result 51 | where 52 | T: CheckedBitPattern, 53 | { 54 | process.read(self.0) 55 | } 56 | 57 | /// Reads the value that this pointer points to from the target process at 58 | /// the given offset. 59 | pub fn read_at_byte_offset(self, offset: O, process: &Process) -> Result 60 | where 61 | U: CheckedBitPattern, 62 | Address64: Add, 63 | { 64 | process.read(self.0 + offset) 65 | } 66 | 67 | /// Casts this pointer to a pointer of a different type without any checks. 68 | pub fn unchecked_cast(self) -> Ptr { 69 | Ptr::new(self.0) 70 | } 71 | 72 | /// Returns the address that this pointer points to. 73 | pub fn addr(self) -> Address64 { 74 | self.0 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/game_engine/godot/cpp/type_info.rs: -------------------------------------------------------------------------------- 1 | use crate::{string::ArrayCString, Address64, Error, Process}; 2 | 3 | use super::Ptr; 4 | 5 | /// The class `TypeInfo` holds implementation-specific information about a 6 | /// type, including the name of the type and means to compare two types for 7 | /// equality or collating order. This is the class returned by 8 | /// [`Ptr::get_type_info`]. 9 | /// 10 | /// [`std::type_info`](https://en.cppreference.com/w/cpp/types/type_info) 11 | #[derive(Debug, Copy, Clone)] 12 | #[repr(transparent)] 13 | pub struct TypeInfo; 14 | 15 | impl Ptr { 16 | /// Returns a GCC/Clang mangled null-terminated character string containing 17 | /// the name of the type. No guarantees are given; in particular, the 18 | /// returned string can be identical for several types. 19 | /// 20 | /// [`std::type_info::name`](https://en.cppreference.com/w/cpp/types/type_info/name) 21 | pub fn get_mangled_name( 22 | self, 23 | process: &Process, 24 | ) -> Result, Error> { 25 | let name_ptr: Address64 = self.read_at_byte_offset(0x8, process)?; 26 | process.read(name_ptr) 27 | } 28 | 29 | /// Checks if the mangled name of the type matches the given string. 30 | pub fn matches_mangled_name( 31 | self, 32 | mangled_name: &[u8; N], 33 | process: &Process, 34 | ) -> Result { 35 | Ok(self.get_mangled_name::(process)?.matches(mangled_name)) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/game_engine/godot/cpp/vtable.rs: -------------------------------------------------------------------------------- 1 | use crate::{Error, Process}; 2 | 3 | use super::{Ptr, TypeInfo}; 4 | 5 | /// A C++ virtual method table. 6 | /// 7 | /// This can be used to look up virtual functions and type information for the 8 | /// object. A pointer to a vtable is unique for each type, so comparing pointers 9 | /// is enough to check for type equality. 10 | /// 11 | /// [Wikipedia](https://en.wikipedia.org/wiki/Virtual_method_table) 12 | #[derive(Debug, Copy, Clone)] 13 | #[repr(transparent)] 14 | pub struct VTable; 15 | 16 | impl Ptr { 17 | /// Queries information of a type. Used where the dynamic type of a 18 | /// polymorphic object must be known and for static type identification. 19 | /// 20 | /// [`typeid`](https://en.cppreference.com/w/cpp/language/typeid) 21 | pub fn get_type_info(self, process: &Process) -> Result, Error> { 22 | self.read_at_byte_offset(-8, process) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/game_engine/godot/mod.rs: -------------------------------------------------------------------------------- 1 | //! Support for games using the Godot engine. 2 | //! 3 | //! The support is still very experimental. Currently only games using Godot 4.2 4 | //! without any debug symbols are supported. 5 | //! 6 | //! The main entry point is [`SceneTree::locate`], which locates the 7 | //! [`SceneTree`] instance in the game's memory. From there you can find the 8 | //! root node and all its child nodes. Nodes may also have attached scripts, 9 | //! which can also be accessed and queried for their members. 10 | //! 11 | //! # Example 12 | //! 13 | //! ```no_run 14 | //! # async fn example(process: asr::Process, main_module_address: asr::Address) { 15 | //! use asr::game_engine::godot::SceneTree; 16 | //! 17 | //! // We first locate the SceneTree instance. 18 | //! let scene_tree = SceneTree::wait_locate(&process, main_module_address).await; 19 | //! 20 | //! // We access the root node of the SceneTree. 21 | //! let root = scene_tree.wait_get_root(&process).await; 22 | //! 23 | //! // We print the tree of nodes starting from the root. 24 | //! asr::print_limited::<4096>(&root.print_tree::<64>(&process)); 25 | //! # } 26 | //! ``` 27 | //! 28 | //! # Extensibility 29 | //! 30 | //! The types and the code are closely matching the Godot source code. If there 31 | //! is anything missing, chances are that it can easily be added. Feel free to 32 | //! open an issue or contribute the missing parts yourself. 33 | //! 34 | //! # Copyright Notice 35 | //! 36 | //! Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). 37 | //! 38 | //! Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. 39 | //! 40 | //! Permission is hereby granted, free of charge, to any person obtaining a copy 41 | //! of this software and associated documentation files (the "Software"), to 42 | //! deal in the Software without restriction, including without limitation the 43 | //! rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 44 | //! sell copies of the Software, and to permit persons to whom the Software is 45 | //! furnished to do so, subject to the following conditions: 46 | //! 47 | //! The above copyright notice and this permission notice shall be included in 48 | //! all copies or substantial portions of the Software. 49 | //! 50 | //! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 51 | //! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 52 | //! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 53 | //! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 54 | //! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 55 | //! FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 56 | //! IN THE SOFTWARE. 57 | 58 | macro_rules! extends { 59 | ($Sub:ident: $Base:ident) => { 60 | impl core::ops::Deref for crate::game_engine::godot::Ptr<$Sub> { 61 | type Target = crate::game_engine::godot::Ptr<$Base>; 62 | 63 | fn deref(&self) -> &Self::Target { 64 | bytemuck::cast_ref(self) 65 | } 66 | } 67 | }; 68 | } 69 | 70 | mod core; 71 | mod modules; 72 | mod scene; 73 | 74 | pub use core::*; 75 | pub use modules::*; 76 | pub use scene::*; 77 | 78 | mod cpp; 79 | pub use cpp::*; 80 | -------------------------------------------------------------------------------- /src/game_engine/godot/modules/gdscript/gdscript.rs: -------------------------------------------------------------------------------- 1 | //! 2 | 3 | use crate::{ 4 | game_engine::godot::{HashMap, Ptr, Script, ScriptInstance, StringName, Variant, Vector}, 5 | Error, Process, 6 | }; 7 | 8 | #[allow(unused)] 9 | mod offsets { 10 | pub mod script_instance { 11 | // ObjectId 12 | pub const OWNER_ID: u64 = 0x8; 13 | // *const Object 14 | pub const OWNER: u64 = 0x10; 15 | // Ref 16 | pub const SCRIPT: u64 = 0x18; 17 | // Vector 18 | pub const MEMBERS: u64 = 0x20; 19 | } 20 | 21 | pub mod script { 22 | // bool 23 | pub const TOOL: u64 = 0x178; 24 | // bool 25 | pub const VALID: u64 = 0x179; 26 | // bool 27 | pub const RELOADING: u64 = 0x17A; 28 | // Ref 29 | pub const NATIVE: u64 = 0x180; 30 | // Ref 31 | pub const BASE: u64 = 0x188; 32 | // *const GDScript 33 | pub const BASE_PTR: u64 = 0x190; 34 | // *const GDScript 35 | pub const OWNER_PTR: u64 = 0x198; 36 | // HashMap 37 | pub const MEMBER_INDICES: u64 = 0x1A0; 38 | } 39 | 40 | pub mod member_info { 41 | // i32 42 | pub const INDEX: u64 = 0x0; 43 | // StringName 44 | pub const SETTER: u64 = 0x8; 45 | // StringName 46 | pub const GETTER: u64 = 0x10; 47 | // GDScriptDataType 48 | pub const DATA_TYPE: u64 = 0x18; 49 | } 50 | } 51 | 52 | /// A script implemented in the GDScript programming language. 53 | /// 54 | /// [`GDScript`](https://docs.godotengine.org/en/4.2/classes/class_gdscript.html) 55 | /// 56 | /// Check the [`Ptr`] documentation to see all the methods you can 57 | /// call on it. 58 | #[derive(Debug, Copy, Clone)] 59 | #[repr(transparent)] 60 | pub struct GDScript; 61 | extends!(GDScript: Script); 62 | 63 | impl Ptr { 64 | /// Returns a [`HashMap`] that maps the name of each member to a 65 | /// [`MemberInfo`] object. This object contains information about the 66 | /// member, such as the index it occupies in the `members` array of a 67 | /// [`GDScriptInstance`]. This can then be used to read the actual values of 68 | /// the members, by indexing into the `members` array returned by 69 | /// [`Ptr::get_members`]. 70 | pub fn get_member_indices(self) -> Ptr> { 71 | Ptr::new(self.addr() + offsets::script::MEMBER_INDICES) 72 | } 73 | } 74 | 75 | /// An instance of a script implemented in the GDScript programming language. 76 | /// This is not publicly exposed in Godot. 77 | /// 78 | /// Check the [`Ptr`] documentation to see all the methods you 79 | /// can call on it. 80 | #[derive(Debug, Copy, Clone)] 81 | #[repr(transparent)] 82 | pub struct GDScriptInstance; 83 | extends!(GDScriptInstance: ScriptInstance); 84 | 85 | impl Ptr { 86 | /// Returns the [`GDScript`] that this instance is an instance of. This can 87 | /// be used to query information about the script, such as the names of its 88 | /// members and their indices. 89 | pub fn get_script(self, process: &Process) -> Result, Error> { 90 | self.read_at_byte_offset(offsets::script_instance::SCRIPT, process) 91 | } 92 | 93 | /// Returns the values of all the members of this script instance. To figure 94 | /// out the index of a member, use [`Ptr::get_member_indices`]. 95 | pub fn get_members(self, process: &Process) -> Result, Error> { 96 | self.read_at_byte_offset(offsets::script_instance::MEMBERS, process) 97 | } 98 | } 99 | 100 | /// Information about a member of a script implemented in the GDScript 101 | /// programming language. This is not publicly exposed in Godot. 102 | /// 103 | /// Check the [`Ptr`] documentation to see all the methods you can 104 | /// call on it. 105 | pub struct MemberInfo; 106 | 107 | impl Ptr { 108 | /// Returns the index of the member in the `members` array of a 109 | /// [`GDScriptInstance`]. This can then be used to read the actual values of 110 | /// the members, by indexing into the `members` array returned by 111 | /// [`Ptr::get_members`]. 112 | pub fn get_index(self, process: &Process) -> Result { 113 | self.read_at_byte_offset(offsets::member_info::INDEX, process) 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/game_engine/godot/modules/gdscript/mod.rs: -------------------------------------------------------------------------------- 1 | mod gdscript; 2 | 3 | pub use gdscript::*; 4 | -------------------------------------------------------------------------------- /src/game_engine/godot/modules/mod.rs: -------------------------------------------------------------------------------- 1 | mod gdscript; 2 | mod mono; 3 | 4 | pub use gdscript::*; 5 | pub use mono::*; 6 | -------------------------------------------------------------------------------- /src/game_engine/godot/modules/mono/csharp_script.rs: -------------------------------------------------------------------------------- 1 | //! 2 | 3 | use crate::{ 4 | game_engine::godot::{HashMap, PropertyInfo, Ptr, Script, ScriptInstance, StringName}, 5 | Error, Process, 6 | }; 7 | 8 | #[allow(unused)] 9 | mod offsets { 10 | pub mod script_instance { 11 | // *const Object 12 | pub const OWNER: u64 = 0x8; 13 | // bool 14 | pub const BASE_REF_COUNTED: u64 = 0x10; 15 | // bool 16 | pub const REF_DYING: u64 = 0x11; 17 | // bool 18 | pub const UNSAFE_REFERENCED: u64 = 0x12; 19 | // bool 20 | pub const PREDELETE_NOTIFIED: u64 = 0x13; 21 | // Ref 22 | pub const SCRIPT: u64 = 0x18; 23 | // MonoGCHandleData 24 | pub const GCHANDLE: u64 = 0x20; 25 | } 26 | 27 | pub mod script { 28 | use crate::game_engine::godot::{HashSet, SizeInTargetProcess, String, Vector}; 29 | 30 | // bool 31 | pub const TOOL: u64 = 0x178; 32 | // bool 33 | pub const GLOBAL_CLASS: u64 = 0x179; 34 | // bool 35 | pub const ABSTRACT_CLASS: u64 = 0x17A; 36 | // bool 37 | pub const VALID: u64 = 0x17B; 38 | // bool 39 | pub const RELOAD_INVALIDATED: u64 = 0x17C; 40 | // Ref 41 | pub const BASE_SCRIPT: u64 = 0x180; 42 | // HashSet<*const Object> 43 | pub const INSTANCES: u64 = 0x188; 44 | // String 45 | pub const SOURCE: u64 = (INSTANCES + HashSet::<()>::SIZE).next_multiple_of(8); 46 | // String 47 | pub const CLASS_NAME: u64 = (SOURCE + String::<0>::SIZE).next_multiple_of(8); 48 | // String 49 | pub const ICON_PATH: u64 = (CLASS_NAME + String::<0>::SIZE).next_multiple_of(8); 50 | // SelfList (4 pointers) 51 | pub const SCRIPT_LIST: u64 = (ICON_PATH + String::<0>::SIZE).next_multiple_of(8); 52 | // Dictionary (1 pointer) 53 | pub const RPC_CONFIG: u64 = (SCRIPT_LIST + 4 * 8).next_multiple_of(8); 54 | // Vector 55 | pub const EVENT_SIGNALS: u64 = (RPC_CONFIG + 8).next_multiple_of(8); 56 | // Vector 57 | pub const METHODS: u64 = (EVENT_SIGNALS + Vector::<()>::SIZE).next_multiple_of(8); 58 | // HashMap 59 | pub const MEMBER_INFO: u64 = (METHODS + Vector::<()>::SIZE).next_multiple_of(8); 60 | } 61 | } 62 | 63 | /// A script implemented in the C# programming language, saved with the `.cs` 64 | /// extension (Mono-enabled builds only). 65 | /// 66 | /// [`CSharpScript`](https://docs.godotengine.org/en/4.2/classes/class_csharpscript.html) 67 | /// 68 | /// Check the [`Ptr`] documentation to see all the methods you can 69 | /// call on it. 70 | #[derive(Debug, Copy, Clone)] 71 | #[repr(transparent)] 72 | pub struct CSharpScript; 73 | extends!(CSharpScript: Script); 74 | 75 | impl Ptr { 76 | /// Returns a [`HashMap`] that maps the name of each member to a 77 | /// [`PropertyInfo`] object. This object contains information about the 78 | /// member, such as its type. Notably this is not the type on the C# side, 79 | /// but a [`VariantType`](crate::game_engine::godot::VariantType). Unlike 80 | /// with [`GDScript`](crate::game_engine::godot::GDScript), there is 81 | /// currently no way to figure out where the member is stored in memory. 82 | pub fn get_member_info(self) -> Ptr> { 83 | Ptr::new(self.addr() + offsets::script::MEMBER_INFO) 84 | } 85 | } 86 | 87 | /// An instance of a script implemented in the C# programming language. This is 88 | /// not publicly exposed in Godot. 89 | /// 90 | /// Check the [`Ptr`] documentation to see all the methods 91 | /// you can call on it. 92 | #[derive(Debug, Copy, Clone)] 93 | #[repr(transparent)] 94 | pub struct CSharpScriptInstance; 95 | extends!(CSharpScriptInstance: ScriptInstance); 96 | 97 | impl Ptr { 98 | /// Returns the [`CSharpScript`] that this instance is an instance of. This 99 | /// can be used to query information about the script, such as the names of 100 | /// its members and their 101 | /// [`VariantType`](crate::game_engine::godot::VariantType)s. 102 | pub fn get_script(self, process: &Process) -> Result, Error> { 103 | self.read_at_byte_offset(offsets::script_instance::SCRIPT, process) 104 | } 105 | 106 | /// Returns the [`CSharpGCHandle`], which allows you to access the members of 107 | /// the script instance. 108 | pub fn get_gc_handle(self, process: &Process) -> Result, Error> { 109 | self.read_at_byte_offset(offsets::script_instance::GCHANDLE, process) 110 | } 111 | } 112 | 113 | /// A handle to a C# object. This is not publicly exposed in Godot. 114 | /// 115 | /// Check the [`Ptr`] documentation to see all the methods you 116 | /// can call on it. 117 | #[derive(Debug, Copy, Clone)] 118 | #[repr(transparent)] 119 | pub struct CSharpGCHandle; 120 | 121 | impl Ptr { 122 | /// Returns a pointer to the start of the raw data of the instance. This is 123 | /// where all the members are stored. You can use the `.Net Info` 124 | /// functionality in Cheat Engine to figure out the offset of a member from 125 | /// this pointer. Note that the garbage collector can move objects around in 126 | /// memory, so this pointer should be queried in each tick of the auto 127 | /// splitter. 128 | pub fn get_instance_data(self, process: &Process) -> Result, Error> { 129 | self.read_at_byte_offset(0x0, process) 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/game_engine/godot/modules/mono/mod.rs: -------------------------------------------------------------------------------- 1 | mod csharp_script; 2 | 3 | pub use csharp_script::*; 4 | -------------------------------------------------------------------------------- /src/game_engine/godot/scene/main/canvas_item.rs: -------------------------------------------------------------------------------- 1 | //! 2 | 3 | use crate::{game_engine::godot::Ptr, Error, Process}; 4 | 5 | use super::Node; 6 | 7 | /// Abstract base class for everything in 2D space. 8 | /// 9 | /// [`CanvasItem`](https://docs.godotengine.org/en/4.2/classes/class_canvasitem.html) 10 | /// 11 | /// Check the [`Ptr`] documentation to see all the methods you can 12 | /// call on it. 13 | #[derive(Debug, Copy, Clone)] 14 | #[repr(transparent)] 15 | pub struct CanvasItem; 16 | extends!(CanvasItem: Node); 17 | 18 | impl Ptr { 19 | /// Returns the global transform matrix of this item, i.e. the combined 20 | /// transform up to the topmost **CanvasItem** node. The topmost item is a 21 | /// **CanvasItem** that either has no parent, has non-**CanvasItem** parent 22 | /// or it has `top_level` enabled. 23 | /// 24 | /// [`CanvasItem.get_global_transform`](https://docs.godotengine.org/en/4.2/classes/class_canvasitem.html#class-canvasitem-method-get-global-transform) 25 | pub fn get_global_transform(self, process: &Process) -> Result<[[f32; 2]; 3], Error> { 26 | self.read_at_byte_offset(0x450, process) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/game_engine/godot/scene/main/mod.rs: -------------------------------------------------------------------------------- 1 | mod canvas_item; 2 | mod node; 3 | mod scene_tree; 4 | mod viewport; 5 | mod window; 6 | 7 | pub use canvas_item::*; 8 | pub use node::*; 9 | pub use scene_tree::*; 10 | pub use viewport::*; 11 | pub use window::*; 12 | -------------------------------------------------------------------------------- /src/game_engine/godot/scene/main/scene_tree.rs: -------------------------------------------------------------------------------- 1 | //! 2 | 3 | use crate::{ 4 | future::retry, 5 | game_engine::godot::{MainLoop, Ptr}, 6 | Address, Address64, Error, Process, 7 | }; 8 | 9 | use super::{Node, Window}; 10 | 11 | #[allow(unused)] 12 | mod offsets { 13 | use crate::{ 14 | game_engine::godot::{HashMap, HashSet, List, SizeInTargetProcess}, 15 | Address64, 16 | }; 17 | 18 | // *const Window 19 | pub const ROOT: u64 = 0x2B0; 20 | // i64 21 | pub const CURRENT_FRAME: u64 = 0x330; 22 | // i32 23 | pub const NODES_IN_TREE_COUNT: u64 = 0x338; 24 | // bool 25 | pub const PROCESSING: u64 = 0x33C; 26 | // i32 27 | pub const NODES_REMOVED_ON_GROUP_CALL_LOCK: u64 = 0x340; 28 | // HashSet<*const Node> 29 | pub const NODES_REMOVED_ON_GROUP_CALL: u64 = 0x348; 30 | // List 31 | pub const DELETE_QUEUE: u64 = 32 | (NODES_REMOVED_ON_GROUP_CALL + HashSet::<()>::SIZE).next_multiple_of(8); 33 | /// HashMap, UGCall> 34 | pub const UNIQUE_GROUP_CALLS: u64 = (DELETE_QUEUE + List::<()>::SIZE).next_multiple_of(8); 35 | // bool 36 | pub const UGC_LOCKED: u64 = UNIQUE_GROUP_CALLS + HashMap::<(), ()>::SIZE; 37 | // *const Node 38 | pub const CURRENT_SCENE: u64 = (UGC_LOCKED + 1).next_multiple_of(8); 39 | // *const Node 40 | pub const PREV_SCENE: u64 = CURRENT_SCENE + 8; 41 | // *const Node 42 | pub const PENDING_NEW_SCENE: u64 = PREV_SCENE + 8; 43 | } 44 | 45 | /// Manages the game loop via a hierarchy of nodes. 46 | /// 47 | /// [`SceneTree`](https://docs.godotengine.org/en/4.2/classes/class_scenetree.html) 48 | /// 49 | /// Check the [`Ptr`] documentation to see all the methods you can call 50 | /// on it. 51 | #[derive(Debug, Copy, Clone)] 52 | #[repr(transparent)] 53 | pub struct SceneTree; 54 | extends!(SceneTree: MainLoop); 55 | 56 | impl SceneTree { 57 | /// Locates the `SceneTree` instance in the given process. 58 | pub fn locate(process: &Process, main_module: Address) -> Result, Error> { 59 | let addr: Address64 = process.read(main_module + 0x0424BE40)?; 60 | if addr.is_null() { 61 | return Err(Error {}); 62 | } 63 | Ok(Ptr::new(addr)) 64 | } 65 | 66 | /// Waits for the `SceneTree` instance to be located in the given process. 67 | pub async fn wait_locate(process: &Process, main_module: Address) -> Ptr { 68 | retry(|| Self::locate(process, main_module)).await 69 | } 70 | } 71 | 72 | impl Ptr { 73 | /// The `SceneTree`'s root [`Window`]. 74 | /// 75 | /// [`SceneTree.get_root`](https://docs.godotengine.org/en/4.2/classes/class_scenetree.html#class-scenetree-property-root) 76 | pub fn get_root(self, process: &Process) -> Result, Error> { 77 | self.read_at_byte_offset(offsets::ROOT, process) 78 | } 79 | 80 | /// Waits for the `SceneTree`'s root [`Window`] to be available. 81 | pub async fn wait_get_root(self, process: &Process) -> Ptr { 82 | retry(|| self.get_root(process)).await 83 | } 84 | 85 | /// Returns the current frame number, i.e. the total frame count since the 86 | /// application started. 87 | /// 88 | /// [`SceneTree.get_frame`](https://docs.godotengine.org/en/4.2/classes/class_scenetree.html#class-scenetree-method-get-frame) 89 | pub fn get_frame(self, process: &Process) -> Result { 90 | self.read_at_byte_offset(offsets::CURRENT_FRAME, process) 91 | } 92 | 93 | /// Returns the root node of the currently running scene, regardless of its 94 | /// structure. 95 | /// 96 | /// [`SceneTree.get_current_scene`](https://docs.godotengine.org/en/4.2/classes/class_scenetree.html#class-scenetree-property-current-scene) 97 | pub fn get_current_scene(self, process: &Process) -> Result>, Error> { 98 | let scene: Ptr = self.read_at_byte_offset(offsets::CURRENT_SCENE, process)?; 99 | Ok(if scene.is_null() { None } else { Some(scene) }) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/game_engine/godot/scene/main/viewport.rs: -------------------------------------------------------------------------------- 1 | //! 2 | 3 | use super::Node; 4 | 5 | /// Abstract base class for viewports. Encapsulates drawing and interaction with 6 | /// a game world. 7 | /// 8 | /// [`Viewport`](https://docs.godotengine.org/en/4.2/classes/class_viewport.html) 9 | #[derive(Debug, Copy, Clone)] 10 | #[repr(transparent)] 11 | pub struct Viewport; 12 | extends!(Viewport: Node); 13 | -------------------------------------------------------------------------------- /src/game_engine/godot/scene/main/window.rs: -------------------------------------------------------------------------------- 1 | //! 2 | 3 | use super::Viewport; 4 | 5 | /// Base class for all windows, dialogs, and popups. 6 | /// 7 | /// [`Window`](https://docs.godotengine.org/en/4.2/classes/class_window.html) 8 | #[derive(Debug, Copy, Clone)] 9 | #[repr(transparent)] 10 | pub struct Window; 11 | extends!(Window: Viewport); 12 | -------------------------------------------------------------------------------- /src/game_engine/godot/scene/mod.rs: -------------------------------------------------------------------------------- 1 | mod main; 2 | mod three_d; 3 | mod two_d; 4 | 5 | pub use main::*; 6 | pub use three_d::*; 7 | pub use two_d::*; 8 | -------------------------------------------------------------------------------- /src/game_engine/godot/scene/three_d/collision_object_3d.rs: -------------------------------------------------------------------------------- 1 | //! 2 | 3 | use super::Node3D; 4 | 5 | /// Abstract base class for 3D physics objects. 6 | /// 7 | /// [`CollisionObject3D`](https://docs.godotengine.org/en/4.2/classes/class_collisionobject3d.html) 8 | #[derive(Debug, Copy, Clone)] 9 | #[repr(transparent)] 10 | pub struct CollisionObject3D; 11 | extends!(CollisionObject3D: Node3D); 12 | -------------------------------------------------------------------------------- /src/game_engine/godot/scene/three_d/mod.rs: -------------------------------------------------------------------------------- 1 | mod collision_object_3d; 2 | mod node_3d; 3 | mod physics_body_3d; 4 | 5 | pub use collision_object_3d::*; 6 | pub use node_3d::*; 7 | pub use physics_body_3d::*; 8 | -------------------------------------------------------------------------------- /src/game_engine/godot/scene/three_d/node_3d.rs: -------------------------------------------------------------------------------- 1 | //! 2 | 3 | use crate::{ 4 | game_engine::godot::{Node, Ptr}, 5 | Error, Process, 6 | }; 7 | 8 | mod offsets { 9 | // Transform3D 10 | pub const GLOBAL_TRANSFORM: u64 = 0x3C8; 11 | // Transform3D 12 | pub const LOCAL_TRANSFORM: u64 = 0x3F8; 13 | } 14 | 15 | /// Most basic 3D game object, parent of all 3D-related nodes. 16 | /// 17 | /// [`Node3D`](https://docs.godotengine.org/en/4.2/classes/class_node3d.html) 18 | /// 19 | /// Check the [`Ptr`] documentation to see all the methods you can call 20 | /// on it. 21 | #[derive(Debug, Copy, Clone)] 22 | #[repr(transparent)] 23 | pub struct Node3D; 24 | extends!(Node3D: Node); 25 | 26 | impl Ptr { 27 | /// World3D space (global) Transform3D of this node. 28 | /// 29 | /// [`Node3D.global_transform`](https://docs.godotengine.org/en/4.2/classes/class_node3d.html#class-node3d-property-global-transform) 30 | pub fn get_global_transform(self, process: &Process) -> Result<[[f32; 3]; 4], Error> { 31 | self.read_at_byte_offset(offsets::GLOBAL_TRANSFORM, process) 32 | } 33 | 34 | /// Local Transform3D of this node. This is not exposed in Godot. 35 | pub fn get_local_transform(self, process: &Process) -> Result<[[f32; 3]; 4], Error> { 36 | self.read_at_byte_offset(offsets::LOCAL_TRANSFORM, process) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/game_engine/godot/scene/three_d/physics_body_3d.rs: -------------------------------------------------------------------------------- 1 | //! 2 | 3 | use crate::{game_engine::godot::Ptr, Error, Process}; 4 | 5 | use super::CollisionObject3D; 6 | 7 | mod offsets { 8 | // Vector3 9 | pub const VELOCITY: u64 = 0x5D8; 10 | } 11 | 12 | /// Abstract base class for 3D game objects affected by physics. 13 | /// 14 | /// [`PhysicsBody3D`](https://docs.godotengine.org/en/4.2/classes/class_physicsbody3d.html) 15 | #[derive(Debug, Copy, Clone)] 16 | #[repr(transparent)] 17 | pub struct PhysicsBody3D; 18 | extends!(PhysicsBody3D: CollisionObject3D); 19 | 20 | /// A 3D physics body specialized for characters moved by script. 21 | /// 22 | /// [`CharacterBody3D`](https://docs.godotengine.org/en/4.2/classes/class_characterbody3d.html) 23 | /// 24 | /// Check the [`Ptr`] documentation to see all the methods you 25 | /// can call on it. 26 | #[derive(Debug, Copy, Clone)] 27 | #[repr(transparent)] 28 | pub struct CharacterBody3D; 29 | extends!(CharacterBody3D: PhysicsBody3D); 30 | 31 | impl Ptr { 32 | /// Current velocity vector (typically meters per second), used and modified 33 | /// during calls to `move_and_slide`. 34 | /// 35 | /// [`CharacterBody3D.velocity`](https://docs.godotengine.org/en/4.2/classes/class_characterbody3d.html#class-characterbody3d-property-velocity) 36 | pub fn get_velocity(self, process: &Process) -> Result<[f32; 3], Error> { 37 | self.read_at_byte_offset(offsets::VELOCITY, process) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/game_engine/godot/scene/two_d/collision_object_2d.rs: -------------------------------------------------------------------------------- 1 | //! 2 | 3 | use super::Node2D; 4 | 5 | /// Abstract base class for 2D physics objects. 6 | /// 7 | /// [`CollisionObject2D`](https://docs.godotengine.org/en/4.2/classes/class_collisionobject2d.html) 8 | #[derive(Debug, Copy, Clone)] 9 | #[repr(transparent)] 10 | pub struct CollisionObject2D; 11 | extends!(CollisionObject2D: Node2D); 12 | -------------------------------------------------------------------------------- /src/game_engine/godot/scene/two_d/mod.rs: -------------------------------------------------------------------------------- 1 | mod collision_object_2d; 2 | mod node_2d; 3 | mod physics_body_2d; 4 | 5 | pub use collision_object_2d::*; 6 | pub use node_2d::*; 7 | pub use physics_body_2d::*; 8 | -------------------------------------------------------------------------------- /src/game_engine/godot/scene/two_d/node_2d.rs: -------------------------------------------------------------------------------- 1 | //! 2 | 3 | use crate::{ 4 | game_engine::godot::{CanvasItem, Ptr}, 5 | Error, Process, 6 | }; 7 | 8 | /// A 2D game object, inherited by all 2D-related nodes. Has a position, 9 | /// rotation, scale, and Z index. 10 | /// 11 | /// [`Node2D`](https://docs.godotengine.org/en/4.2/classes/class_node2d.html) 12 | /// 13 | /// Check the [`Ptr`] documentation to see all the methods you can call 14 | /// on it. 15 | #[derive(Debug, Copy, Clone)] 16 | #[repr(transparent)] 17 | pub struct Node2D; 18 | extends!(Node2D: CanvasItem); 19 | 20 | impl Ptr { 21 | /// Position, relative to the node's parent. 22 | /// 23 | /// [`Node2D.get_position`](https://docs.godotengine.org/en/4.2/classes/class_node2d.html#class-node2d-property-position) 24 | pub fn get_position(self, process: &Process) -> Result<[f32; 2], Error> { 25 | self.read_at_byte_offset(0x48C, process) 26 | } 27 | 28 | /// Rotation in radians, relative to the node's parent. 29 | /// 30 | /// [`Node2D.get_rotation`](https://docs.godotengine.org/en/4.2/classes/class_node2d.html#class-node2d-property-rotation) 31 | pub fn get_rotation(self, process: &Process) -> Result { 32 | self.read_at_byte_offset(0x494, process) 33 | } 34 | 35 | /// The node's scale. Unscaled value: `[1.0, 1.0]`. 36 | /// 37 | /// [`Node2D.get_scale`](https://docs.godotengine.org/en/4.2/classes/class_node2d.html#class-node2d-property-scale) 38 | pub fn get_scale(self, process: &Process) -> Result<[f32; 2], Error> { 39 | self.read_at_byte_offset(0x498, process) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/game_engine/godot/scene/two_d/physics_body_2d.rs: -------------------------------------------------------------------------------- 1 | //! 2 | 3 | use crate::{game_engine::godot::Ptr, Error, Process}; 4 | 5 | use super::CollisionObject2D; 6 | 7 | mod offsets { 8 | // Vector2 9 | pub const VELOCITY: u64 = 0x5C4; 10 | } 11 | 12 | /// Abstract base class for 2D game objects affected by physics. 13 | /// 14 | /// [`PhysicsBody2D`](https://docs.godotengine.org/en/4.2/classes/class_physicsbody2d.html) 15 | #[derive(Debug, Copy, Clone)] 16 | #[repr(transparent)] 17 | pub struct PhysicsBody2D; 18 | extends!(PhysicsBody2D: CollisionObject2D); 19 | 20 | /// A 2D physics body specialized for characters moved by script. 21 | /// 22 | /// [`CharacterBody2D`](https://docs.godotengine.org/en/4.2/classes/class_characterbody2d.html) 23 | /// 24 | /// Check the [`Ptr`] documentation to see all the methods you 25 | /// can call on it. 26 | #[derive(Debug, Copy, Clone)] 27 | #[repr(transparent)] 28 | pub struct CharacterBody2D; 29 | extends!(CharacterBody2D: PhysicsBody2D); 30 | 31 | impl Ptr { 32 | /// Current velocity vector in pixels per second, used and modified during 33 | /// calls to `move_and_slide`. 34 | /// 35 | /// [`CharacterBody2D.velocity`](https://docs.godotengine.org/en/4.2/classes/class_characterbody2d.html#class-characterbody2d-property-velocity) 36 | pub fn get_velocity(self, process: &Process) -> Result<[f32; 2], Error> { 37 | self.read_at_byte_offset(offsets::VELOCITY, process) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/game_engine/mod.rs: -------------------------------------------------------------------------------- 1 | //! Support for attaching to various game engines. 2 | 3 | #[cfg(feature = "godot")] 4 | pub mod godot; 5 | #[cfg(feature = "unity")] 6 | pub mod unity; 7 | #[cfg(feature = "unreal")] 8 | pub mod unreal; 9 | -------------------------------------------------------------------------------- /src/game_engine/unity/mod.rs: -------------------------------------------------------------------------------- 1 | //! Support for games using the Unity engine. 2 | //! 3 | //! # Example 4 | //! 5 | //! ```no_run 6 | //! # async fn example(process: asr::Process) { 7 | //! use asr::{ 8 | //! future::retry, 9 | //! game_engine::unity::il2cpp::{Module, Version}, 10 | //! Address, Address64, 11 | //! }; 12 | //! 13 | //! // We first attach to the Mono module. Here we know that the game is using IL2CPP 2020. 14 | //! let module = Module::wait_attach(&process, Version::V2020).await; 15 | //! // We access the .NET DLL that the game code is in. 16 | //! let image = module.wait_get_default_image(&process).await; 17 | //! 18 | //! // We access a class called "Timer" in that DLL. 19 | //! let timer_class = image.wait_get_class(&process, &module, "Timer").await; 20 | //! // We access a static field called "_instance" representing the singleton 21 | //! // instance of the class. 22 | //! let instance = timer_class.wait_get_static_instance(&process, &module, "_instance").await; 23 | //! 24 | //! // Once we have the address of the instance, we want to access one of its 25 | //! // fields, so we get the offset of the "currentTime" field. 26 | //! let current_time_offset = timer_class.wait_get_field_offset(&process, &module, "currentTime").await; 27 | //! 28 | //! // Now we can add it to the address of the instance and read the current time. 29 | //! if let Ok(current_time) = process.read::(instance + current_time_offset) { 30 | //! // Use the current time. 31 | //! } 32 | //! # } 33 | //! ``` 34 | //! Alternatively you can use the `Class` derive macro to generate the bindings 35 | //! for you. This allows reading the contents of an instance of the class 36 | //! described by the struct from a process. Each field must match the name of 37 | //! the field in the class exactly (or alternatively renamed with the `#[rename 38 | //! = "..."]` attribute) and needs to be of a type that can be read from a 39 | //! process. Fields can be marked as static with the `#[static_field]` 40 | //! attribute. 41 | //! 42 | //! ```ignore 43 | //! #[derive(Class)] 44 | //! struct Timer { 45 | //! #[rename = "currentLevelTime"] 46 | //! level_time: f32, 47 | //! #[static_field] 48 | //! foo: bool, 49 | //! } 50 | //! ``` 51 | //! 52 | //! This will bind to a .NET class of the following shape: 53 | //! 54 | //! ```csharp 55 | //! class Timer 56 | //! { 57 | //! float currentLevelTime; 58 | //! static bool foo; 59 | //! // ... 60 | //! } 61 | //! ``` 62 | //! 63 | //! The class can then be bound to the process like so: 64 | //! 65 | //! ```ignore 66 | //! let timer_class = Timer::bind(&process, &module, &image).await; 67 | //! ``` 68 | //! 69 | //! Once you have an instance, you can read the instance from the process like 70 | //! so: 71 | //! 72 | //! ```ignore 73 | //! if let Ok(timer) = timer_class.read(&process, timer_instance) { 74 | //! // Do something with the instance. 75 | //! } 76 | //! ``` 77 | //! 78 | //! If only static fields are present, the `read` method does not take an 79 | //! instance argument. 80 | 81 | // References: 82 | // https://github.com/just-ero/asl-help/tree/4c87822df0125b027d1af75e8e348c485817592d/src/Unity 83 | // https://github.com/Unity-Technologies/mono 84 | // https://github.com/CryZe/lunistice-auto-splitter/blob/b8c01031991783f7b41044099ee69edd54514dba/asr-dotnet/src/lib.rs 85 | 86 | pub mod il2cpp; 87 | pub mod mono; 88 | 89 | mod scene; 90 | pub use self::scene::*; 91 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![warn( 3 | clippy::complexity, 4 | clippy::correctness, 5 | clippy::perf, 6 | clippy::style, 7 | clippy::missing_const_for_fn, 8 | clippy::undocumented_unsafe_blocks, 9 | missing_docs, 10 | rust_2018_idioms 11 | )] 12 | #![cfg_attr(doc_cfg, feature(doc_auto_cfg))] 13 | 14 | //! Helper crate to write auto splitters for LiveSplit One's auto splitting 15 | //! runtime. 16 | //! 17 | //! There are two ways of defining an auto splitter. 18 | //! 19 | //! # Defining an `update` function 20 | //! 21 | //! You can define an `update` function that will be called every frame. This is 22 | //! the simplest way to define an auto splitter. The function must have the 23 | //! following signature: 24 | //! ```no_run 25 | //! #[no_mangle] 26 | //! pub extern "C" fn update() {} 27 | //! ``` 28 | //! 29 | //! The advantage of this approach is that you have full control over what 30 | //! happens on every tick of the runtime. However, it's much harder to keep 31 | //! state around as you need to store all state in global variables as you need 32 | //! to return out of the function on every tick. 33 | //! 34 | //! ## Example 35 | //! 36 | //! ```no_run 37 | //! # use asr::Process; 38 | //! #[no_mangle] 39 | //! pub extern "C" fn update() { 40 | //! if let Some(process) = Process::attach("explorer.exe") { 41 | //! asr::print_message("Hello World!"); 42 | //! if let Ok(address) = process.get_module_address("explorer.exe") { 43 | //! if let Ok(value) = process.read::(address) { 44 | //! if value > 0 { 45 | //! asr::timer::start(); 46 | //! } 47 | //! } 48 | //! } 49 | //! } 50 | //! } 51 | //! ``` 52 | //! 53 | //! # Defining an asynchronous `main` function 54 | //! 55 | //! You can use the [`async_main`] macro to define an asynchronous `main` 56 | //! function. 57 | //! 58 | //! Similar to using an `update` function, it is important to constantly yield 59 | //! back to the runtime to communicate that the auto splitter is still alive. 60 | //! All asynchronous code that you await automatically yields back to the 61 | //! runtime. However, if you want to write synchronous code, such as the main 62 | //! loop handling of a process on every tick, you can use the 63 | //! [`next_tick`](future::next_tick) function to yield back to the runtime and 64 | //! continue on the next tick. 65 | //! 66 | //! The main low level abstraction is the [`retry`](future::retry) function, 67 | //! which wraps any code that you want to retry until it succeeds, yielding back 68 | //! to the runtime between each try. 69 | //! 70 | //! So if you wanted to attach to a Process you could for example write: 71 | //! 72 | //! ```no_run 73 | //! # use asr::{Process, future::retry}; 74 | //! # async fn example() { 75 | //! let process = retry(|| Process::attach("MyGame.exe")).await; 76 | //! # } 77 | //! ``` 78 | //! 79 | //! This will try to attach to the process every tick until it succeeds. This 80 | //! specific example is exactly how the [`Process::wait_attach`] method is 81 | //! implemented. So if you wanted to attach to any of multiple processes, you 82 | //! could for example write: 83 | //! 84 | //! ```no_run 85 | //! # use asr::{Process, future::retry}; 86 | //! # async fn example() { 87 | //! let process = retry(|| { 88 | //! ["a.exe", "b.exe"].into_iter().find_map(Process::attach) 89 | //! }).await; 90 | //! # } 91 | //! ``` 92 | //! 93 | //! ## Example 94 | //! 95 | //! Here is a full example of how an auto splitter could look like using the 96 | //! [`async_main`] macro: 97 | //! 98 | //! Usage on stable Rust: 99 | //! ```ignore 100 | //! async_main!(stable); 101 | //! ``` 102 | //! 103 | //! Usage on nightly Rust: 104 | //! ```ignore 105 | //! #![feature(type_alias_impl_trait, const_async_blocks)] 106 | //! 107 | //! async_main!(nightly); 108 | //! ``` 109 | //! 110 | //! The asynchronous main function itself: 111 | //! ```ignore 112 | //! async fn main() { 113 | //! // TODO: Set up some general state and settings. 114 | //! loop { 115 | //! let process = Process::wait_attach("explorer.exe").await; 116 | //! process.until_closes(async { 117 | //! // TODO: Load some initial information from the process. 118 | //! loop { 119 | //! // TODO: Do something on every tick. 120 | //! next_tick().await; 121 | //! } 122 | //! }).await; 123 | //! } 124 | //! } 125 | //! ``` 126 | 127 | #[cfg(feature = "alloc")] 128 | extern crate alloc; 129 | 130 | mod primitives; 131 | mod runtime; 132 | 133 | pub mod deep_pointer; 134 | pub mod emulator; 135 | #[macro_use] 136 | pub mod future; 137 | pub mod file_format; 138 | pub mod game_engine; 139 | #[cfg(feature = "signature")] 140 | pub mod signature; 141 | pub mod string; 142 | pub mod sync; 143 | pub mod time_util; 144 | #[cfg(all(feature = "wasi-no-std", target_os = "wasi"))] 145 | mod wasi_no_std; 146 | pub mod watcher; 147 | 148 | pub use self::{primitives::*, runtime::*}; 149 | pub use arrayvec; 150 | pub use time; 151 | 152 | #[cfg(feature = "itoa")] 153 | pub use itoa; 154 | 155 | #[cfg(feature = "ryu")] 156 | pub use ryu; 157 | 158 | #[macro_use] 159 | mod panic; 160 | -------------------------------------------------------------------------------- /src/panic.rs: -------------------------------------------------------------------------------- 1 | /// Defines a panic handler for the auto splitter that aborts execution. By 2 | /// default it will only print the panic message in debug builds. A stack based 3 | /// buffer of 1024 bytes is used by default. If the message is too long, it will 4 | /// be truncated. All of this can be configured. 5 | /// 6 | /// # Usage 7 | /// 8 | /// ```ignore 9 | /// asr::panic_handler! { 10 | /// /// When to print the panic message. 11 | /// /// Default: debug 12 | /// print: never | debug | always, 13 | /// 14 | /// /// The size of the stack based buffer in bytes. 15 | /// /// Default: 1024 16 | /// buffer: , 17 | /// } 18 | /// ``` 19 | /// 20 | /// # Example 21 | /// 22 | /// The default configuration will print a message in debug builds: 23 | /// ```no_run 24 | /// asr::panic_handler!(); 25 | /// ``` 26 | /// 27 | /// A message will always be printed with a buffer size of 512 bytes: 28 | /// ```no_run 29 | /// asr::panic_handler! { 30 | /// print: always, 31 | /// buffer: 512, 32 | /// } 33 | /// ``` 34 | /// 35 | /// A message will always be printed with the default buffer size: 36 | /// ```no_run 37 | /// asr::panic_handler! { 38 | /// print: always, 39 | /// } 40 | /// ``` 41 | /// 42 | /// A message will never be printed: 43 | /// ```no_run 44 | /// asr::panic_handler! { 45 | /// print: never, 46 | /// } 47 | /// ``` 48 | /// 49 | /// A message will be printed in debug builds, with a buffer size of 512 bytes: 50 | /// ```no_run 51 | /// asr::panic_handler! { 52 | /// buffer: 512, 53 | /// } 54 | /// ``` 55 | #[macro_export] 56 | macro_rules! panic_handler { 57 | () => { $crate::panic_handler!(print: debug); }; 58 | (print: never $(,)?) => { 59 | #[cfg(all(not(test), target_family = "wasm"))] 60 | #[panic_handler] 61 | fn panic(_: &core::panic::PanicInfo) -> ! { 62 | #[cfg(target_arch = "wasm32")] 63 | core::arch::wasm32::unreachable(); 64 | #[cfg(target_arch = "wasm64")] 65 | core::arch::wasm64::unreachable(); 66 | } 67 | }; 68 | (buffer: $N:expr $(,)?) => { $crate::panic_handler!(print: debug, buffer: $N); }; 69 | (print: always $(,)?) => { $crate::panic_handler!(print: always, buffer: 1024); }; 70 | (print: always, buffer: $N:expr $(,)?) => { 71 | #[cfg(all(not(test), target_family = "wasm"))] 72 | #[panic_handler] 73 | fn panic(info: &core::panic::PanicInfo) -> ! { 74 | asr::print_limited::<$N>(info); 75 | #[cfg(target_arch = "wasm32")] 76 | core::arch::wasm32::unreachable(); 77 | #[cfg(target_arch = "wasm64")] 78 | core::arch::wasm64::unreachable(); 79 | } 80 | }; 81 | (print: debug $(,)?) => { $crate::panic_handler!(print: debug, buffer: 1024); }; 82 | (print: debug, buffer: $N:expr $(,)?) => { 83 | #[cfg(all(not(test), target_family = "wasm"))] 84 | #[panic_handler] 85 | fn panic(_info: &core::panic::PanicInfo) -> ! { 86 | #[cfg(debug_assertions)] 87 | asr::print_limited::<$N>(_info); 88 | #[cfg(target_arch = "wasm32")] 89 | core::arch::wasm32::unreachable(); 90 | #[cfg(target_arch = "wasm64")] 91 | core::arch::wasm64::unreachable(); 92 | } 93 | }; 94 | } 95 | -------------------------------------------------------------------------------- /src/primitives/address.rs: -------------------------------------------------------------------------------- 1 | use core::{fmt, ops::Add}; 2 | 3 | use bytemuck::{Pod, Zeroable}; 4 | 5 | macro_rules! define_addr { 6 | (#[$name_in_doc:meta] $name:ident => $inner_u:ident => $inner_i:ident) => { 7 | #[derive(Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] 8 | #[repr(transparent)] 9 | /// A 10 | #[$name_in_doc] 11 | pub struct $name($inner_u); 12 | 13 | impl $name { 14 | /// The null pointer pointing to address 0. 15 | pub const NULL: Self = Self(0); 16 | 17 | /// Creates a new address from the given value. 18 | #[inline] 19 | pub const fn new(value: $inner_u) -> Self { 20 | Self(value) 21 | } 22 | 23 | /// Returns the underlying address as an integer. 24 | #[inline] 25 | pub const fn value(self) -> $inner_u { 26 | self.0 27 | } 28 | 29 | /// Checks whether the address is null. 30 | #[inline] 31 | pub const fn is_null(self) -> bool { 32 | self.0 == 0 33 | } 34 | 35 | /// Offsets the address by the given number of bytes. 36 | #[inline] 37 | pub const fn add(self, bytes: $inner_u) -> Self { 38 | Self(self.0.wrapping_add(bytes)) 39 | } 40 | 41 | /// Offsets the address by the given number of bytes. 42 | #[inline] 43 | pub const fn add_signed(self, bytes: $inner_i) -> Self { 44 | Self(self.0.wrapping_add_signed(bytes)) 45 | } 46 | } 47 | 48 | impl fmt::Debug for $name { 49 | #[inline] 50 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 51 | fmt::Pointer::fmt(self, f) 52 | } 53 | } 54 | 55 | impl fmt::Display for $name { 56 | #[inline] 57 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 58 | fmt::Pointer::fmt(self, f) 59 | } 60 | } 61 | 62 | impl fmt::Pointer for $name { 63 | #[inline] 64 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 65 | fmt::LowerHex::fmt(&self.0, f) 66 | } 67 | } 68 | }; 69 | } 70 | 71 | macro_rules! define_pod_addr { 72 | (#[$name_in_doc:meta] $name:ident => $inner_u:ident => $inner_i:ident) => { 73 | define_addr!(#[$name_in_doc] $name => $inner_u => $inner_i); 74 | 75 | impl From<$name> for Address { 76 | #[inline] 77 | fn from(addr: $name) -> Self { 78 | Self(addr.0 as _) 79 | } 80 | } 81 | 82 | impl From<$inner_u> for Address { 83 | #[inline] 84 | fn from(addr: $inner_u) -> Self { 85 | Self(addr as _) 86 | } 87 | } 88 | 89 | impl Add<$inner_u> for Address { 90 | type Output = Self; 91 | 92 | #[inline] 93 | fn add(self, bytes: $inner_u) -> Self { 94 | Self(self.0.wrapping_add(bytes as _)) 95 | } 96 | } 97 | 98 | impl Add<$inner_i> for Address { 99 | type Output = Self; 100 | 101 | #[inline] 102 | fn add(self, bytes: $inner_i) -> Self { 103 | Self(self.0.wrapping_add_signed(bytes as _)) 104 | } 105 | } 106 | 107 | impl From<$inner_u> for $name { 108 | #[inline] 109 | fn from(addr: $inner_u) -> Self { 110 | Self(addr as _) 111 | } 112 | } 113 | 114 | impl Add<$inner_u> for $name { 115 | type Output = Self; 116 | 117 | #[inline] 118 | fn add(self, bytes: $inner_u) -> Self { 119 | Self(self.0.wrapping_add(bytes as _)) 120 | } 121 | } 122 | 123 | impl Add<$inner_i> for $name { 124 | type Output = Self; 125 | 126 | #[inline] 127 | fn add(self, bytes: $inner_i) -> Self { 128 | Self(self.0.wrapping_add_signed(bytes as _)) 129 | } 130 | } 131 | 132 | // SAFETY: The type is transparent over an integer, which is `Pod`. 133 | unsafe impl Pod for $name {} 134 | // SAFETY: The type is transparent over an integer, which is `Zeroable`. 135 | unsafe impl Zeroable for $name {} 136 | }; 137 | } 138 | 139 | define_pod_addr!(#[doc = "16-bit address that can be read from a process's memory."] Address16 => u16 => i16); 140 | define_pod_addr!(#[doc = "32-bit address that can be read from a process's memory."] Address32 => u32 => i32); 141 | define_pod_addr!(#[doc = "64-bit address that can be read from a process's memory."] Address64 => u64 => i64); 142 | define_addr!(#[doc = "general purpose address."] Address => u64 => i64); 143 | 144 | impl Add for Address { 145 | type Output = Self; 146 | 147 | #[inline] 148 | fn add(self, bytes: u8) -> Self { 149 | Self(self.0.wrapping_add(bytes as _)) 150 | } 151 | } 152 | 153 | impl Add for Address { 154 | type Output = Self; 155 | 156 | #[inline] 157 | fn add(self, bytes: i8) -> Self { 158 | Self(self.0.wrapping_add_signed(bytes as _)) 159 | } 160 | } 161 | 162 | impl Add for Address64 { 163 | type Output = Self; 164 | 165 | #[inline] 166 | fn add(self, bytes: i32) -> Self { 167 | Self(self.0.wrapping_add(bytes as _)) 168 | } 169 | } 170 | 171 | impl Add for Address64 { 172 | type Output = Self; 173 | 174 | #[inline] 175 | fn add(self, bytes: u32) -> Self { 176 | Self(self.0.wrapping_add(bytes as _)) 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /src/primitives/endian.rs: -------------------------------------------------------------------------------- 1 | use core::array; 2 | 3 | use crate::{Address16, Address32, Address64}; 4 | 5 | /// The endianness of a value. 6 | #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] 7 | pub enum Endian { 8 | /// Big endian. 9 | Big, 10 | /// Little endian. 11 | Little, 12 | } 13 | 14 | #[cfg(feature = "derive")] 15 | pub use asr_derive::FromEndian; 16 | 17 | /// A trait for converting from big or little endian. 18 | #[allow(clippy::wrong_self_convention)] 19 | pub trait FromEndian: Sized { 20 | /// Converts the value from big endian. 21 | fn from_be(&self) -> Self; 22 | /// Converts the value from little endian. 23 | fn from_le(&self) -> Self; 24 | /// Converts the value from the given endian. 25 | fn from_endian(&self, endian: Endian) -> Self { 26 | match endian { 27 | Endian::Big => self.from_be(), 28 | Endian::Little => self.from_le(), 29 | } 30 | } 31 | } 32 | 33 | macro_rules! define { 34 | ($name:ident) => { 35 | impl FromEndian for $name { 36 | fn from_be(&self) -> Self { 37 | Self::from_be_bytes(bytemuck::cast(*self)) 38 | } 39 | fn from_le(&self) -> Self { 40 | Self::from_le_bytes(bytemuck::cast(*self)) 41 | } 42 | } 43 | }; 44 | } 45 | 46 | macro_rules! define_addr { 47 | ($name:ident) => { 48 | impl FromEndian for $name { 49 | fn from_be(&self) -> Self { 50 | Self::new(self.value().from_be()) 51 | } 52 | fn from_le(&self) -> Self { 53 | Self::new(self.value().from_le()) 54 | } 55 | } 56 | }; 57 | } 58 | 59 | define!(u8); 60 | define!(u16); 61 | define!(u32); 62 | define!(u64); 63 | define!(u128); 64 | define!(i8); 65 | define!(i16); 66 | define!(i32); 67 | define!(i64); 68 | define!(i128); 69 | define!(f32); 70 | define!(f64); 71 | 72 | define_addr!(Address16); 73 | define_addr!(Address32); 74 | define_addr!(Address64); 75 | 76 | impl FromEndian for bool { 77 | fn from_be(&self) -> Self { 78 | *self 79 | } 80 | fn from_le(&self) -> Self { 81 | *self 82 | } 83 | } 84 | 85 | impl FromEndian for [T; N] { 86 | fn from_be(&self) -> Self { 87 | let mut iter = self.iter(); 88 | array::from_fn(|_| iter.next().unwrap().from_be()) 89 | } 90 | fn from_le(&self) -> Self { 91 | let mut iter = self.iter(); 92 | array::from_fn(|_| iter.next().unwrap().from_le()) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/primitives/mod.rs: -------------------------------------------------------------------------------- 1 | mod address; 2 | mod endian; 3 | 4 | pub use self::{address::*, endian::*}; 5 | 6 | /// Pointer size represents the width (in bytes) of memory addresses used 7 | /// in a certain process. 8 | #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] 9 | #[repr(u8)] 10 | pub enum PointerSize { 11 | /// A 16-bit (2 byte wide) pointer size 12 | Bit16 = 0x2, 13 | /// A 32-bit (4 byte wide) pointer size 14 | Bit32 = 0x4, 15 | /// A 64-bit (8 byte wide) pointer size 16 | Bit64 = 0x8, 17 | } 18 | -------------------------------------------------------------------------------- /src/runtime/memory_range.rs: -------------------------------------------------------------------------------- 1 | use crate::Address; 2 | 3 | use super::{sys, Error, Process}; 4 | 5 | #[cfg(feature = "flags")] 6 | bitflags::bitflags! { 7 | /// Describes various flags of a memory range. 8 | #[derive(Clone, Copy, Debug, Default)] 9 | pub struct MemoryRangeFlags: u64 { 10 | /// The memory range is readable. 11 | const READ = 1 << 1; 12 | /// The memory range is writable. 13 | const WRITE = 1 << 2; 14 | /// The memory range is executable. 15 | const EXECUTE = 1 << 3; 16 | /// The memory range has a file path. 17 | const PATH = 1 << 4; 18 | } 19 | } 20 | 21 | /// A memory range of a process. All information is queried lazily. 22 | #[derive(Copy, Clone)] 23 | pub struct MemoryRange<'a> { 24 | pub(crate) process: &'a Process, 25 | pub(crate) index: u64, 26 | } 27 | 28 | impl MemoryRange<'_> { 29 | /// Queries the starting address of the memory range. 30 | #[inline] 31 | pub fn address(&self) -> Result { 32 | // SAFETY: The process is guaranteed to be valid because we borrow an 33 | // owned Process object. 34 | unsafe { 35 | let address = sys::process_get_memory_range_address(self.process.0, self.index); 36 | if let Some(address) = address { 37 | Ok(Address::new(address.0.get())) 38 | } else { 39 | Err(Error {}) 40 | } 41 | } 42 | } 43 | 44 | /// Queries the size of the memory range. 45 | #[inline] 46 | pub fn size(&self) -> Result { 47 | // SAFETY: The process is guaranteed to be valid because we borrow an 48 | // owned Process object. 49 | unsafe { 50 | let size = sys::process_get_memory_range_size(self.process.0, self.index); 51 | if let Some(size) = size { 52 | Ok(size.get()) 53 | } else { 54 | Err(Error {}) 55 | } 56 | } 57 | } 58 | 59 | /// Queries the address and size of the memory range. 60 | #[inline] 61 | pub fn range(&self) -> Result<(Address, u64), Error> { 62 | Ok((self.address()?, self.size()?)) 63 | } 64 | 65 | /// Queries the flags of the memory range. 66 | #[cfg(feature = "flags")] 67 | #[inline] 68 | pub fn flags(&self) -> Result { 69 | // SAFETY: The process is guaranteed to be valid because we borrow an 70 | // owned Process object. 71 | unsafe { 72 | let flags = sys::process_get_memory_range_flags(self.process.0, self.index); 73 | if let Some(flags) = flags { 74 | Ok(MemoryRangeFlags::from_bits_truncate(flags.get())) 75 | } else { 76 | Err(Error {}) 77 | } 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/runtime/mod.rs: -------------------------------------------------------------------------------- 1 | pub use memory_range::*; 2 | pub use process::*; 3 | 4 | mod memory_range; 5 | mod process; 6 | mod sys; 7 | 8 | pub mod settings; 9 | pub mod timer; 10 | 11 | /// An error returned by a runtime function. 12 | #[derive(Debug)] 13 | #[non_exhaustive] 14 | pub struct Error {} 15 | 16 | /// Sets the tick rate of the runtime. This influences how many times per second 17 | /// the `update` function is called. The default tick rate is 120 ticks per 18 | /// second. 19 | #[inline] 20 | pub fn set_tick_rate(ticks_per_second: f64) { 21 | // SAFETY: It is always safe to call this function. 22 | unsafe { sys::runtime_set_tick_rate(ticks_per_second) } 23 | } 24 | 25 | /// Prints a log message for debugging purposes. 26 | #[inline] 27 | pub fn print_message(text: &str) { 28 | // SAFETY: We provide a valid pointer and length to text that is UTF-8 encoded. 29 | unsafe { sys::runtime_print_message(text.as_ptr(), text.len()) } 30 | } 31 | 32 | /// Prints a log message for debugging purposes by formatting the given message 33 | /// into a stack allocated buffer with the given capacity. This is useful for 34 | /// printing dynamic messages without needing an allocator. However the message 35 | /// may be truncated if it is too long. 36 | /// 37 | /// # Example 38 | /// 39 | /// ```no_run 40 | /// asr::print_limited::<128>(&format_args!("Hello, {}!", "world")); 41 | /// ``` 42 | #[inline(never)] 43 | pub fn print_limited(message: &dyn core::fmt::Display) { 44 | let mut buf = arrayvec::ArrayString::::new(); 45 | let _ = core::fmt::Write::write_fmt(&mut buf, format_args!("{message}")); 46 | print_message(&buf); 47 | } 48 | 49 | /// Queries the name of the operating system that the runtime is running on. Due 50 | /// to emulation this may not be the same as the operating system that an 51 | /// individual process is targeting. 52 | /// 53 | /// Example values: `windows`, `linux`, `macos` 54 | #[inline] 55 | pub fn get_os() -> Result, Error> { 56 | let mut buf = arrayvec::ArrayString::<16>::new(); 57 | // SAFETY: We provide a valid pointer and length to the buffer. We check 58 | // whether the buffer was successfully filled and set the length of the 59 | // buffer accordingly. The buffer is guaranteed to be valid UTF-8. 60 | unsafe { 61 | let mut len = buf.capacity(); 62 | let success = sys::runtime_get_os(buf.as_mut_ptr(), &mut len); 63 | if !success { 64 | return Err(Error {}); 65 | } 66 | buf.set_len(len); 67 | } 68 | Ok(buf) 69 | } 70 | 71 | /// Queries the architecture that the runtime is running on. Due to emulation 72 | /// this may not be the same as the architecture that an individual process is 73 | /// targeting. For example 64-bit operating systems usually can run 32-bit 74 | /// processes. Also modern operating systems running on aarch64 often have 75 | /// backwards compatibility with x86_64 processes. 76 | /// 77 | /// Example values: `x86`, `x86_64`, `arm`, `aarch64` 78 | #[inline] 79 | pub fn get_arch() -> Result, Error> { 80 | let mut buf = arrayvec::ArrayString::<16>::new(); 81 | // SAFETY: We provide a valid pointer and length to the buffer. We check 82 | // whether the buffer was successfully filled and set the length of the 83 | // buffer accordingly. The buffer is guaranteed to be valid UTF-8. 84 | unsafe { 85 | let mut len = buf.capacity(); 86 | let success = sys::runtime_get_arch(buf.as_mut_ptr(), &mut len); 87 | if !success { 88 | return Err(Error {}); 89 | } 90 | buf.set_len(len); 91 | } 92 | Ok(buf) 93 | } 94 | -------------------------------------------------------------------------------- /src/runtime/settings/list.rs: -------------------------------------------------------------------------------- 1 | use core::{borrow::Borrow, fmt}; 2 | 3 | use crate::{runtime::sys, Error}; 4 | 5 | use super::{AsValue, Value}; 6 | 7 | /// A list of [`Value`]s that can itself be a [`Value`] and thus be stored in a 8 | /// [`Map`](super::Map). 9 | #[repr(transparent)] 10 | pub struct List(pub(super) sys::SettingsList); 11 | 12 | impl fmt::Debug for List { 13 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 14 | f.debug_list().entries(self.iter()).finish() 15 | } 16 | } 17 | 18 | impl Drop for List { 19 | #[inline] 20 | fn drop(&mut self) { 21 | // SAFETY: The handle is valid and we own it, so it's our responsibility 22 | // to free it. 23 | unsafe { sys::settings_list_free(self.0) } 24 | } 25 | } 26 | 27 | impl Clone for List { 28 | #[inline] 29 | fn clone(&self) -> Self { 30 | // SAFETY: The handle is valid, so we can safely copy it. 31 | Self(unsafe { sys::settings_list_copy(self.0) }) 32 | } 33 | } 34 | 35 | impl Default for List { 36 | #[inline] 37 | fn default() -> Self { 38 | Self::new() 39 | } 40 | } 41 | 42 | impl List { 43 | /// Creates a new empty settings list. 44 | #[inline] 45 | pub fn new() -> Self { 46 | // SAFETY: This is always safe to call. 47 | Self(unsafe { sys::settings_list_new() }) 48 | } 49 | 50 | /// Returns the number of values in the list. 51 | #[inline] 52 | pub fn len(&self) -> u64 { 53 | // SAFETY: The handle is valid, so we can safely call this function. 54 | unsafe { sys::settings_list_len(self.0) } 55 | } 56 | 57 | /// Returns [`true`] if the list has a length of 0. 58 | #[inline] 59 | pub fn is_empty(&self) -> bool { 60 | self.len() == 0 61 | } 62 | 63 | /// Returns a copy of the value at the given index. Returns [`None`] if the 64 | /// index is out of bounds. Any changes to it are only perceived if it's 65 | /// stored back. 66 | #[inline] 67 | pub fn get(&self, index: u64) -> Option { 68 | // SAFETY: The settings list handle is valid. 69 | unsafe { sys::settings_list_get(self.0, index).map(Value) } 70 | } 71 | 72 | /// Pushes a copy of the value to the end of the list. 73 | #[inline] 74 | pub fn push(&self, value: impl AsValue) { 75 | // SAFETY: The settings list handle is valid and the value handle is 76 | // valid. 77 | unsafe { sys::settings_list_push(self.0, value.as_value().borrow().0) } 78 | } 79 | 80 | /// Inserts a copy of the value at the given index, pushing all values at 81 | /// and after the index one position further. Returns an error if the index 82 | /// is out of bounds. You may specify an index that is equal to the length 83 | /// of the list to append the value to the end of the list. 84 | #[inline] 85 | pub fn insert(&self, index: u64, value: impl AsValue) -> Result<(), Error> { 86 | // SAFETY: The settings list handle is valid and the value handle is 87 | // valid. 88 | unsafe { 89 | if sys::settings_list_insert(self.0, index, value.as_value().borrow().0) { 90 | Ok(()) 91 | } else { 92 | Err(Error {}) 93 | } 94 | } 95 | } 96 | 97 | /// Returns an iterator over the values in the list. Every value is a copy, 98 | /// so any changes to them are only perceived if they are stored back. The 99 | /// iterator is double-ended, so it can be iterated backwards as well. While 100 | /// it's possible to modify the list while iterating over it, it's not 101 | /// recommended to do so, as the iterator might skip values or return 102 | /// duplicate values. In that case it's better to clone the list before and 103 | /// iterate over the clone or use [`get`](Self::get) to manually handle the 104 | /// iteration. 105 | #[inline] 106 | pub fn iter(&self) -> impl DoubleEndedIterator + '_ { 107 | (0..self.len()).flat_map(|i| self.get(i)) 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/runtime/settings/mod.rs: -------------------------------------------------------------------------------- 1 | //! Support for interacting with the settings of the auto splitter. 2 | //! 3 | //! # Overview 4 | //! 5 | //! Settings consist of two parts. One part is the settings [`Gui`], that is 6 | //! used to let the user configure the settings. The other part is the settings 7 | //! values that are actually stored in the splits file. Those settings don't 8 | //! necessarily correlate entirely with the settings [`Gui`], because the stored 9 | //! splits might either be from a different version of the auto splitter or 10 | //! contain additional information such as the version of the settings, that the 11 | //! user doesn't necessarily directly interact with. These stored settings are 12 | //! available as the global settings [`Map`], which can be loaded, modified and 13 | //! stored freely. The keys used for the settings widgets directly correlate 14 | //! with the keys used in the settings [`Map`]. Any changes in the settings 15 | //! [`Gui`] will automatically be reflected in the global settings [`Map`] and 16 | //! vice versa. 17 | //! 18 | //! # Defining a GUI 19 | //! 20 | //! ```ignore 21 | //! #[derive(Gui)] 22 | //! struct Settings { 23 | //! /// General Settings 24 | //! _general_settings: Title, 25 | //! /// Use Game Time 26 | //! /// 27 | //! /// This is the tooltip. 28 | //! use_game_time: bool, 29 | //! } 30 | //! ``` 31 | //! 32 | //! The type can then be used like so: 33 | //! 34 | //! ```ignore 35 | //! let mut settings = Settings::register(); 36 | //! 37 | //! loop { 38 | //! settings.update(); 39 | //! // Do something with the settings. 40 | //! } 41 | //! ``` 42 | //! 43 | //! Check the [`Gui`](macro@Gui) derive macro and the [`Gui`](trait@Gui) trait 44 | //! for more information. 45 | //! 46 | //! # Modifying the global settings map 47 | //! 48 | //! ```no_run 49 | //! # use asr::settings; 50 | //! let mut map = settings::Map::load(); 51 | //! map.insert("key", true); 52 | //! map.store(); 53 | //! ``` 54 | //! 55 | //! Check the [`Map`](struct@Map) struct for more information. 56 | 57 | pub mod gui; 58 | mod list; 59 | mod map; 60 | mod value; 61 | 62 | pub use gui::Gui; 63 | pub use list::*; 64 | pub use map::*; 65 | pub use value::*; 66 | -------------------------------------------------------------------------------- /src/runtime/timer.rs: -------------------------------------------------------------------------------- 1 | //! This module provides functions for interacting with the timer. 2 | 3 | use super::sys; 4 | 5 | /// The state of the timer. 6 | #[derive(Debug, Copy, Clone, PartialEq, Eq)] 7 | #[non_exhaustive] 8 | pub enum TimerState { 9 | /// The timer is not running. 10 | NotRunning, 11 | /// The timer is running. 12 | Running, 13 | /// The timer started but got paused. This is separate from the game time 14 | /// being paused. Game time may even always be paused. 15 | Paused, 16 | /// The timer has ended, but didn't get reset yet. 17 | Ended, 18 | /// The timer is in an unknown state. 19 | Unknown, 20 | } 21 | 22 | /// Starts the timer. 23 | #[inline] 24 | pub fn start() { 25 | // SAFETY: It is always safe to call this function. 26 | unsafe { sys::timer_start() } 27 | } 28 | 29 | /// Splits the current segment. 30 | #[inline] 31 | pub fn split() { 32 | // SAFETY: It is always safe to call this function. 33 | unsafe { sys::timer_split() } 34 | } 35 | 36 | /// Skips the current split. 37 | #[inline] 38 | pub fn skip_split() { 39 | // SAFETY: It is always safe to call this function. 40 | unsafe { sys::timer_skip_split() } 41 | } 42 | 43 | /// Undoes the previous split. 44 | #[inline] 45 | pub fn undo_split() { 46 | // SAFETY: It is always safe to call this function. 47 | unsafe { sys::timer_undo_split() } 48 | } 49 | 50 | /// Resets the timer. 51 | #[inline] 52 | pub fn reset() { 53 | // SAFETY: It is always safe to call this function. 54 | unsafe { sys::timer_reset() } 55 | } 56 | 57 | /// Pauses the game time. This does not pause the timer, only the 58 | /// automatic flow of time for the game time. 59 | #[inline] 60 | pub fn pause_game_time() { 61 | // SAFETY: It is always safe to call this function. 62 | unsafe { sys::timer_pause_game_time() } 63 | } 64 | 65 | /// Resumes the game time. This does not resume the timer, only the 66 | /// automatic flow of time for the game time. 67 | #[inline] 68 | pub fn resume_game_time() { 69 | // SAFETY: It is always safe to call this function. 70 | unsafe { sys::timer_resume_game_time() } 71 | } 72 | 73 | /// Sets a custom key value pair. This may be arbitrary information that the 74 | /// auto splitter wants to provide for visualization. 75 | #[inline] 76 | pub fn set_variable(key: &str, value: &str) { 77 | // SAFETY: We provide a valid pointer and length to both the key and value 78 | // that are both UTF-8 encoded. 79 | unsafe { sys::timer_set_variable(key.as_ptr(), key.len(), value.as_ptr(), value.len()) } 80 | } 81 | 82 | /// Sets a custom key value pair where the value is an integer. This may be 83 | /// arbitrary information that the auto splitter wants to provide for 84 | /// visualization. 85 | #[cfg(feature = "integer-vars")] 86 | pub fn set_variable_int(key: &str, value: impl itoa::Integer) { 87 | let mut buf = itoa::Buffer::new(); 88 | set_variable(key, buf.format(value)); 89 | } 90 | 91 | /// Sets a custom key value pair where the value is a floating point number. 92 | /// This may be arbitrary information that the auto splitter wants to provide 93 | /// for visualization. 94 | #[cfg(feature = "float-vars")] 95 | pub fn set_variable_float(key: &str, value: impl ryu::Float) { 96 | let mut buf = ryu::Buffer::new(); 97 | set_variable(key, buf.format(value)); 98 | } 99 | 100 | /// Gets the state that the timer currently is in. 101 | #[inline] 102 | pub fn state() -> TimerState { 103 | // SAFETY: It is always safe to call this function. 104 | unsafe { 105 | match sys::timer_get_state() { 106 | sys::TimerState::NOT_RUNNING => TimerState::NotRunning, 107 | sys::TimerState::PAUSED => TimerState::Paused, 108 | sys::TimerState::RUNNING => TimerState::Running, 109 | sys::TimerState::ENDED => TimerState::Ended, 110 | _ => TimerState::Unknown, 111 | } 112 | } 113 | } 114 | 115 | /// Sets the game time. 116 | #[inline] 117 | pub fn set_game_time(time: time::Duration) { 118 | // SAFETY: It is always safe to call this function. 119 | unsafe { sys::timer_set_game_time(time.whole_seconds(), time.subsec_nanoseconds()) } 120 | } 121 | -------------------------------------------------------------------------------- /src/string.rs: -------------------------------------------------------------------------------- 1 | //! Support for string types that can be read from a process's memory. 2 | 3 | use core::{ops, slice, str}; 4 | 5 | use bytemuck::{Pod, Zeroable}; 6 | 7 | pub use arrayvec::ArrayString; 8 | 9 | use crate::FromEndian; 10 | 11 | /// A nul-terminated string that is stored in an array of a fixed size `N`. This 12 | /// can be read from a process's memory. 13 | #[derive(Copy, Clone)] 14 | #[repr(transparent)] 15 | pub struct ArrayCString([u8; N]); 16 | 17 | impl ArrayCString { 18 | /// Creates a new empty nul-terminated string. 19 | pub const fn new() -> Self { 20 | Self([0; N]) 21 | } 22 | 23 | /// Returns the bytes of the string up until (but excluding) the 24 | /// nul-terminator. If there is no nul-terminator, all bytes are returned. 25 | pub fn as_bytes(&self) -> &[u8] { 26 | let len = self.0.iter().position(|&b| b == 0).unwrap_or(N); 27 | &self.0[..len] 28 | } 29 | 30 | /// Returns the string as a string slice if it is valid UTF-8. 31 | pub fn validate_utf8(&self) -> Result<&str, str::Utf8Error> { 32 | str::from_utf8(self.as_bytes()) 33 | } 34 | 35 | /// Checks whether the string matches the given text. This is faster than 36 | /// calling [`as_bytes`](Self::as_bytes) and then comparing, because it can 37 | /// use the length information of the parameter. 38 | pub fn matches(&self, text: impl AsRef<[u8]>) -> bool { 39 | let bytes = text.as_ref(); 40 | !self.0.get(bytes.len()).is_some_and(|&b| b != 0) 41 | && self.0.get(..bytes.len()).is_some_and(|s| s == bytes) 42 | } 43 | 44 | /// Reduces the size of the string contained inside the ArrayString 45 | /// to the value provided by `len`. If a value higher than the size of the ArrayString 46 | /// is provided, no action is performed. 47 | /// This function might be useful for dealing with strings devoid of the null terminator byte. 48 | pub fn set_len(&mut self, len: usize) { 49 | if self.len() > len { 50 | // SAFETY: We checked the length of the u8 array beforehand 51 | unsafe { 52 | let ptr = slice::from_raw_parts_mut(self.as_ptr() as *mut u8, self.len()); 53 | ptr[len] = 0; 54 | } 55 | } 56 | } 57 | } 58 | 59 | impl Default for ArrayCString { 60 | fn default() -> Self { 61 | Self::new() 62 | } 63 | } 64 | 65 | impl ops::Deref for ArrayCString { 66 | type Target = [u8]; 67 | 68 | fn deref(&self) -> &Self::Target { 69 | self.as_bytes() 70 | } 71 | } 72 | 73 | impl PartialEq for ArrayCString { 74 | fn eq(&self, other: &Self) -> bool { 75 | self.matches(&**other) 76 | } 77 | } 78 | 79 | impl Eq for ArrayCString {} 80 | 81 | /// SAFETY: The type is transparent over an array of `N` bytes, which is `Pod`. 82 | unsafe impl Pod for ArrayCString {} 83 | /// SAFETY: The type is transparent over an array of `N` bytes, which is `Zeroable`. 84 | unsafe impl Zeroable for ArrayCString {} 85 | 86 | impl FromEndian for ArrayCString { 87 | fn from_be(&self) -> Self { 88 | *self 89 | } 90 | fn from_le(&self) -> Self { 91 | *self 92 | } 93 | } 94 | 95 | /// A nul-terminated wide string (16-bit characters) that is stored in an array 96 | /// of a fixed size of `N` characters. This can be read from a process's memory. 97 | #[derive(Copy, Clone)] 98 | #[repr(transparent)] 99 | pub struct ArrayWString([u16; N]); 100 | 101 | impl ArrayWString { 102 | /// Creates a new empty nul-terminated wide string. 103 | pub const fn new() -> Self { 104 | Self([0; N]) 105 | } 106 | 107 | /// Returns the 16-bit characters of the string up until (but excluding) the 108 | /// nul-terminator. If there is no nul-terminator, all characters are 109 | /// returned. 110 | pub fn as_slice(&self) -> &[u16] { 111 | let len = self.0.iter().position(|&b| b == 0).unwrap_or(N); 112 | &self.0[..len] 113 | } 114 | 115 | /// Checks whether the string matches the given text. This is faster than 116 | /// calling [`as_slice`](Self::as_slice) and then comparing, because it can 117 | /// use the length information of the parameter. 118 | pub fn matches(&self, text: impl AsRef<[u16]>) -> bool { 119 | let chars = text.as_ref(); 120 | !self.0.get(chars.len()).is_some_and(|&b| b != 0) 121 | && self.0.get(..chars.len()).is_some_and(|s| s == chars) 122 | } 123 | 124 | /// Checks whether the string matches the given text. This dynamically 125 | /// re-encodes the passed in text to UTF-16, which is not as fast as 126 | /// [`matches`](Self::matches). 127 | pub fn matches_str(&self, text: &str) -> bool { 128 | self.as_slice().iter().copied().eq(text.encode_utf16()) 129 | } 130 | } 131 | 132 | impl Default for ArrayWString { 133 | fn default() -> Self { 134 | Self::new() 135 | } 136 | } 137 | 138 | impl ops::Deref for ArrayWString { 139 | type Target = [u16]; 140 | 141 | fn deref(&self) -> &Self::Target { 142 | self.as_slice() 143 | } 144 | } 145 | 146 | impl PartialEq for ArrayWString { 147 | fn eq(&self, other: &Self) -> bool { 148 | self.matches(&**other) 149 | } 150 | } 151 | 152 | impl Eq for ArrayWString {} 153 | 154 | /// SAFETY: The type is transparent over an array of `N` u16s, which is `Pod`. 155 | unsafe impl Pod for ArrayWString {} 156 | /// SAFETY: The type is transparent over an array of `N` u16s, which is `Zeroable`. 157 | unsafe impl Zeroable for ArrayWString {} 158 | 159 | impl FromEndian for ArrayWString { 160 | fn from_be(&self) -> Self { 161 | Self(self.0.map(|x| x.from_be())) 162 | } 163 | fn from_le(&self) -> Self { 164 | Self(self.0.map(|x| x.from_le())) 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/sync.rs: -------------------------------------------------------------------------------- 1 | //! Useful synchronization primitives. 2 | 3 | use core::{ 4 | cell::{RefCell, RefMut, UnsafeCell}, 5 | marker::PhantomData, 6 | ops::{Deref, DerefMut}, 7 | }; 8 | 9 | /// A mutual exclusion primitive useful for protecting shared data. This mutex 10 | /// is specifically for single-threaded WebAssembly. 11 | pub struct Mutex(RefCell); 12 | 13 | /// An RAII implementation of a “scoped lock” of a mutex. When this structure is 14 | /// dropped (falls out of scope), the lock will be unlocked. 15 | /// 16 | /// The data protected by the mutex can be accessed through this guard via its 17 | /// [`Deref`] and [`DerefMut`] implementations. 18 | /// 19 | /// This structure is created by the [`lock`](Mutex::::lock) and 20 | /// [`try_lock`](Mutex::::try_lock) methods on Mutex. 21 | pub struct MutexGuard<'a, T: ?Sized>(RefMut<'a, T>); 22 | 23 | /// A type alias for the result of a nonblocking locking method. 24 | pub type TryLockResult = Result>; 25 | 26 | /// An enumeration of possible errors associated with a [`TryLockResult`] which 27 | /// can occur while trying to acquire a lock, from the 28 | /// [`try_lock`](Mutex::::try_lock) method on a Mutex. 29 | pub struct TryLockError { 30 | _private: PhantomData, 31 | } 32 | 33 | impl Mutex { 34 | /// Creates a new mutex in an unlocked state ready for use. 35 | #[inline] 36 | pub const fn new(value: T) -> Self { 37 | Self(RefCell::new(value)) 38 | } 39 | } 40 | 41 | impl Mutex { 42 | /// Acquires a mutex, panics if it is unable to do so. 43 | #[track_caller] 44 | #[inline] 45 | pub fn lock(&self) -> MutexGuard<'_, T> { 46 | MutexGuard(self.0.borrow_mut()) 47 | } 48 | 49 | /// Attempts to acquire this lock. 50 | /// 51 | /// If the lock could not be acquired at this time, then Err is returned. 52 | /// Otherwise, an RAII guard is returned. The lock will be unlocked when the 53 | /// guard is dropped. 54 | // 55 | /// This function does not block. 56 | #[inline] 57 | pub fn try_lock(&self) -> TryLockResult> { 58 | Ok(MutexGuard(self.0.try_borrow_mut().map_err(|_| { 59 | TryLockError { 60 | _private: PhantomData, 61 | } 62 | })?)) 63 | } 64 | 65 | /// Consumes this mutex, returning the underlying data. 66 | #[inline] 67 | pub fn into_inner(self) -> T 68 | where 69 | T: Sized, 70 | { 71 | self.0.into_inner() 72 | } 73 | 74 | /// Returns a mutable reference to the underlying data. 75 | #[inline] 76 | pub fn get_mut(&mut self) -> &mut T { 77 | self.0.get_mut() 78 | } 79 | } 80 | 81 | impl Deref for MutexGuard<'_, T> { 82 | type Target = T; 83 | 84 | #[inline] 85 | fn deref(&self) -> &Self::Target { 86 | &self.0 87 | } 88 | } 89 | 90 | impl DerefMut for MutexGuard<'_, T> { 91 | #[inline] 92 | fn deref_mut(&mut self) -> &mut Self::Target { 93 | &mut self.0 94 | } 95 | } 96 | 97 | #[cfg(not(target_feature = "atomics"))] 98 | // SAFETY: This is the same as std's Mutex, but it can only be safe in 99 | // single-threaded WASM, because we use RefCell underneath. 100 | unsafe impl Send for Mutex {} 101 | 102 | #[cfg(not(target_feature = "atomics"))] 103 | // SAFETY: This is the same as std's Mutex, but it can only be safe in 104 | // single-threaded WASM, because we use RefCell underneath. 105 | unsafe impl Sync for Mutex {} 106 | 107 | // TODO: Currently not possible in stable Rust. 108 | // impl !Send for MutexGuard<'_, T> 109 | #[cfg(not(target_feature = "atomics"))] 110 | // SAFETY: This is the same as std's MutexGuard, but it can only be safe in 111 | // single-threaded WASM, because we use RefMut underneath. 112 | unsafe impl Sync for MutexGuard<'_, T> {} 113 | 114 | /// A wrapper type that can be used for creating mutable global variables. It 115 | /// does not by itself provide any thread safety. 116 | #[repr(transparent)] 117 | pub struct RacyCell(UnsafeCell); 118 | 119 | // SAFETY: The thread unsafety is delegated to the user of this type. 120 | unsafe impl Sync for RacyCell {} 121 | // SAFETY: The thread unsafety is delegated to the user of this type. 122 | unsafe impl Send for RacyCell {} 123 | 124 | impl RacyCell { 125 | /// Creates a new `RacyCell` containing the given value. 126 | #[inline(always)] 127 | pub const fn new(value: T) -> Self { 128 | RacyCell(UnsafeCell::new(value)) 129 | } 130 | 131 | /// Accesses the inner value as mutable pointer. There is no synchronization 132 | /// provided by this type, so it is up to the user to ensure that no other 133 | /// references to the value are used while this pointer is alive. 134 | /// 135 | /// # Safety 136 | /// 137 | /// You need to ensure that no other references to the value are used while 138 | /// this pointer is alive. 139 | #[inline(always)] 140 | pub const unsafe fn get_mut(&self) -> *mut T { 141 | self.0.get() 142 | } 143 | 144 | /// Accesses the inner value as const pointer. There is no synchronization 145 | /// provided by this type, so it is up to the user to ensure that no other 146 | /// references to the value are used while this pointer is alive. 147 | /// 148 | /// # Safety 149 | /// 150 | /// You need to ensure that no other references to the value are used while 151 | /// this pointer is alive. 152 | #[inline(always)] 153 | pub const unsafe fn get(&self) -> *const T { 154 | self.0.get() 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/time_util.rs: -------------------------------------------------------------------------------- 1 | //! This module provides utilities for creating durations. 2 | 3 | /// From a frame count and a fixed frame rate, returns an accurate duration. 4 | pub fn frame_count(frame_count: u64) -> time::Duration { 5 | let secs = frame_count / FRAME_RATE; 6 | let nanos = (frame_count % FRAME_RATE) * 1_000_000_000 / FRAME_RATE; 7 | time::Duration::new(secs as _, nanos as _) 8 | } 9 | 10 | #[cfg(target_os = "wasi")] 11 | mod instant { 12 | use core::{mem::MaybeUninit, ops::Add, time::Duration}; 13 | 14 | use wasi::Timestamp; 15 | 16 | fn current_time() -> Timestamp { 17 | // SAFETY: This is copied from std, so it should be fine. 18 | // https://github.com/rust-lang/rust/blob/dd5d7c729d4e8a59708df64002e09dbcbc4005ba/library/std/src/sys/wasi/time.rs#L15 19 | unsafe { 20 | let mut rp0 = MaybeUninit::::uninit(); 21 | let ret = wasi::wasi_snapshot_preview1::clock_time_get( 22 | wasi::CLOCKID_MONOTONIC.raw() as _, 23 | 1, // precision... seems ignored though? 24 | rp0.as_mut_ptr() as _, 25 | ); 26 | assert_eq!(ret, wasi::ERRNO_SUCCESS.raw() as i32); 27 | rp0.assume_init() 28 | } 29 | } 30 | 31 | /// A version of the standard library's `Instant` using WASI that doesn't 32 | /// need the standard library. 33 | #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] 34 | #[repr(transparent)] 35 | pub struct Instant(pub(crate) Timestamp); 36 | 37 | impl Instant { 38 | /// Returns an instant corresponding to "now". 39 | /// 40 | /// # Examples 41 | /// 42 | /// ```no_run 43 | /// use asr::time_util::Instant; 44 | /// 45 | /// let now = Instant::now(); 46 | /// ``` 47 | pub fn now() -> Self { 48 | Self(current_time()) 49 | } 50 | 51 | /// Returns the amount of time elapsed from another instant to this one, 52 | /// or zero duration if that instant is later than this one. 53 | pub const fn duration_since(&self, other: Self) -> Duration { 54 | let nanos = self.0.saturating_sub(other.0); 55 | Duration::new(nanos / 1_000_000_000, (nanos % 1_000_000_000) as _) 56 | } 57 | 58 | /// Returns the amount of time elapsed since this instant. 59 | pub fn elapsed(&self) -> Duration { 60 | Self::now().duration_since(*self) 61 | } 62 | } 63 | 64 | impl Add for Instant { 65 | type Output = Self; 66 | 67 | fn add(self, rhs: Duration) -> Self::Output { 68 | Self(self.0 + rhs.as_nanos() as u64) 69 | } 70 | } 71 | } 72 | #[cfg(target_os = "wasi")] 73 | pub use self::instant::Instant; 74 | -------------------------------------------------------------------------------- /src/watcher.rs: -------------------------------------------------------------------------------- 1 | //! Support for watching values and tracking changes between them. 2 | 3 | use core::{mem, ops}; 4 | 5 | use bytemuck::{bytes_of, NoUninit}; 6 | 7 | /// A watcher keeps a pair of values and allows you to track changes between 8 | /// them. 9 | #[derive(Copy, Clone)] 10 | pub struct Watcher { 11 | /// The pair of values. 12 | pub pair: Option>, 13 | } 14 | 15 | impl Default for Watcher { 16 | #[inline] 17 | fn default() -> Self { 18 | Self { pair: None } 19 | } 20 | } 21 | 22 | impl Watcher { 23 | /// Creates a new empty watcher. 24 | #[inline] 25 | pub const fn new() -> Self { 26 | Self { pair: None } 27 | } 28 | } 29 | 30 | impl Watcher { 31 | /// Updates the watcher with a new value. Returns the pair if the value 32 | /// provided is not [`None`]. 33 | pub fn update(&mut self, value: Option) -> Option<&Pair> { 34 | match (&mut self.pair, value) { 35 | (None, Some(value)) => { 36 | self.pair = Some(Pair { 37 | old: value.clone(), 38 | current: value, 39 | }); 40 | } 41 | (Some(pair), Some(value)) => pair.old = mem::replace(&mut pair.current, value), 42 | _ => { 43 | self.pair = None; 44 | } 45 | } 46 | self.pair.as_ref() 47 | } 48 | 49 | /// Updates the watcher with a new value that always exists. The pair is 50 | /// then returned. 51 | pub fn update_infallible(&mut self, value: T) -> &Pair { 52 | match &mut self.pair { 53 | None => { 54 | self.pair = Some(Pair { 55 | old: value.clone(), 56 | current: value, 57 | }); 58 | } 59 | Some(pair) => pair.old = mem::replace(&mut pair.current, value), 60 | } 61 | self.pair.as_ref().unwrap() 62 | } 63 | } 64 | 65 | /// A pair consisting of an old and a current value that can be used for 66 | /// tracking changes between them. 67 | #[derive(Copy, Clone, Default)] 68 | pub struct Pair { 69 | /// The old value. 70 | pub old: T, 71 | /// The current value. 72 | pub current: T, 73 | } 74 | 75 | impl ops::Deref for Pair { 76 | type Target = T; 77 | 78 | /// Accesses the current value. 79 | fn deref(&self) -> &Self::Target { 80 | &self.current 81 | } 82 | } 83 | 84 | impl Pair { 85 | /// Checks if a condition is true for the current value but false for the 86 | /// old value. 87 | pub fn check(&self, mut f: impl FnMut(&T) -> bool) -> bool { 88 | !f(&self.old) && f(&self.current) 89 | } 90 | 91 | /// Maps the pair to a new pair with a different type. 92 | pub fn map(self, mut f: impl FnMut(T) -> U) -> Pair { 93 | Pair { 94 | old: f(self.old), 95 | current: f(self.current), 96 | } 97 | } 98 | } 99 | 100 | impl Pair { 101 | /// Checks if the value changed. 102 | pub fn changed(&self) -> bool { 103 | self.old != self.current 104 | } 105 | 106 | /// Checks if the value did not change. 107 | pub fn unchanged(&self) -> bool { 108 | self.old == self.current 109 | } 110 | 111 | /// Checks if the value changed to a specific value that it was not before. 112 | pub fn changed_to(&self, value: &T) -> bool { 113 | self.check(|v| v == value) 114 | } 115 | 116 | /// Checks if the value changed from a specific value that it is not now. 117 | pub fn changed_from(&self, value: &T) -> bool { 118 | self.check(|v| v != value) 119 | } 120 | 121 | /// Checks if the value changed from a specific value to another specific value. 122 | pub fn changed_from_to(&self, old: &T, current: &T) -> bool { 123 | &self.old == old && &self.current == current 124 | } 125 | } 126 | 127 | impl Pair { 128 | /// Checks if the bytes of the value changed. 129 | pub fn bytes_changed(&self) -> bool { 130 | bytes_of(&self.old) != bytes_of(&self.current) 131 | } 132 | 133 | /// Checks if the bytes of the value did not change. 134 | pub fn bytes_unchanged(&self) -> bool { 135 | bytes_of(&self.old) == bytes_of(&self.current) 136 | } 137 | 138 | /// Checks if the bytes of the value changed to a specific value that it was 139 | /// not before. 140 | pub fn bytes_changed_to(&self, value: &T) -> bool { 141 | self.check(|v| bytes_of(v) == bytes_of(value)) 142 | } 143 | 144 | /// Checks if the bytes of the value changed from a specific value that it 145 | /// is not now. 146 | pub fn bytes_changed_from(&self, value: &T) -> bool { 147 | self.check(|v| bytes_of(v) != bytes_of(value)) 148 | } 149 | 150 | /// Checks if the bytes of the value changed from a specific value to 151 | /// another specific value. 152 | pub fn bytes_changed_from_to(&self, old: &T, current: &T) -> bool { 153 | bytes_of(&self.old) == bytes_of(old) && bytes_of(&self.current) == bytes_of(current) 154 | } 155 | } 156 | 157 | impl Pair { 158 | /// Checks if the value increased. 159 | pub fn increased(&self) -> bool { 160 | self.old < self.current 161 | } 162 | 163 | /// Checks if the value decreased. 164 | pub fn decreased(&self) -> bool { 165 | self.old > self.current 166 | } 167 | } 168 | --------------------------------------------------------------------------------