├── .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 | #
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