[v0.1.0-beta.3] Added support for older Dolphin and MEM2

This commit is contained in:
sup39 2023-07-25 21:13:50 +09:00
parent 393a9fcd2f
commit 444618a42d
No known key found for this signature in database
GPG key ID: 14D2E0D21140D260
24 changed files with 1140 additions and 763 deletions

View file

@ -1,10 +1,15 @@
# Changelog
## v0.1.0-beta.3 (2023/07/25)
- Added support for older Dolphin (e.g. Dolphin 5.0-114)
- Added support for MEM2
## v0.1.0-beta.2 (2023/07/24)
- Added api.getVersion()
- Added static variables
- Improved UI
- Added buttons to show/hide UI elements
- Added button to reload managers
## v0.1.0-beta.1 (2023/07/23)
- Implemented ObjectViewer
- load/reload `ObjectParameters/*.json`

2
Cargo.lock generated
View file

@ -854,7 +854,7 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "sup-smsac"
version = "0.1.0-beta.2"
version = "0.1.0-beta.3"
dependencies = [
"clap",
"encoding_rs",

View file

@ -1,6 +1,6 @@
[package]
name = "sup-smsac"
version = "0.1.0-beta.2"
version = "0.1.0-beta.3"
edition = "2021"
license = "MIT OR Apache-2.0"
authors = ["sup39 <sms@sup39.dev>"]
@ -13,7 +13,9 @@ encoding_rs = "0.8.32"
windows = {version = "0.48.0", features = [
"Win32_Foundation",
"Win32_System_Diagnostics_ToolHelp",
"Win32_System_Diagnostics_Debug",
"Win32_System_Memory",
"Win32_System_Threading",
]}
futures-util = "0.3.28"
hyper = { version = "0.14.27", features = ["full"] }

View file

@ -1,4 +1,10 @@
# SPDX-FileCopyrightText: 2023 sup39 <sms@sup39.dev>
# SPDX-License-Identifier: MIT OR Apache-2.0
Some of the code regarding finding dolphin is based on
aldelaro5's Dolphin memory engine
(https://github.com/aldelaro5/Dolphin-memory-engine)
# SPDX-FileCopyrightText: 2017 aldelaro5
# SPDX-License-Identifier: MIT
See "www/LICENSE.html" for the full license text.

View file

@ -13,6 +13,10 @@ Download the binary from the [releases page](https://github.com/sup39/sup-smsac/
Requirements:
- [cargo](https://www.rust-lang.org/tools/install)
- [Git Bash](https://git-scm.com/download/win)
- [cargo-about](https://github.com/EmbarkStudios/cargo-about)
```
cargo install --locked cargo-about
```
```sh
# Clone the repository

120
about.hbs
View file

@ -1,70 +1,70 @@
<html>
<head>
<style>
@media (prefers-color-scheme: dark) {
body {
background: #333;
color: white;
}
a {
color: skyblue;
}
}
.container {
font-family: sans-serif;
max-width: 800px;
margin: 0 auto;
}
.intro {
text-align: center;
}
.licenses-list {
list-style-type: none;
margin: 0;
padding: 0;
}
.license-used-by {
margin-top: -10px;
}
.license-text {
max-height: 200px;
overflow-y: scroll;
white-space: pre-wrap;
}
</style>
<meta charset="utf8">
<style>
@media (prefers-color-scheme: dark) {
body {
background: #333;
color: white;
}
a {
color: skyblue;
}
}
.container {
font-family: sans-serif;
max-width: 800px;
margin: 0 auto;
}
.intro {
text-align: center;
}
.licenses-list {
list-style-type: none;
margin: 0;
padding: 0;
}
.license-used-by {
margin-top: -10px;
}
.license-text {
max-height: 200px;
overflow-y: scroll;
white-space: pre-wrap;
}
</style>
</head>
<body>
<main class="container">
<div class="intro">
<h1>Third Party Licenses</h1>
<p>This page lists the licenses of the projects used in cargo-about.</p>
</div>
<h2>Overview of licenses:</h2>
<ul class="licenses-overview">
{{#each overview}}
<li><a href="#{{id}}">{{name}}</a> ({{count}})</li>
{{/each}}
</ul>
<main class="container">
<div class="intro">
<h1>Third Party Licenses</h1>
<p>This page lists the licenses of the projects used in cargo-about.</p>
</div>
<h2>All license text:</h2>
<ul class="licenses-list">
{{#each licenses}}
<li class="license">
<h3 id="{{id}}">{{name}}</h3>
<h4>Used by:</h4>
<ul class="license-used-by">
{{#each used_by}}
<li><a href="{{#if crate.repository}} {{crate.repository}} {{else}} https://crates.io/crates/{{crate.name}} {{/if}}">{{crate.name}} {{crate.version}}</a></li>
{{/each}}
</ul>
<pre class="license-text">{{text}}</pre>
</li>
{{/each}}
<h2>Overview of licenses:</h2>
<ul class="licenses-overview">
{{#each overview}}
<li><a href="#{{id}}">{{name}}</a> ({{count}})</li>
{{/each}}
</ul>
<h2>All license text:</h2>
<ul class="licenses-list">
{{#each licenses}}
<li class="license">
<h3 id="{{id}}">{{name}}</h3>
<h4>Used by:</h4>
<ul class="license-used-by">
{{#each used_by}}
<li><a href="{{#if crate.repository}} {{crate.repository}} {{else}} https://crates.io/crates/{{crate.name}} {{/if}}">{{crate.name}} {{crate.version}}</a></li>
{{/each}}
</ul>
</main>
<pre class="license-text">{{text}}</pre>
</li>
{{/each}}
</ul>
</main>
</body>
</html>

View file

@ -6,5 +6,6 @@ outDir=out/sup-smsac-$version
mkdir -p "$outDir"
cargo build --release
cargo-about generate about.hbs -o www/LICENSE.html
cp ./target/release/sup-smsac.exe "$outDir/"
cp -r www res README.md LICENSE.txt CHANGELOG.md "$outDir/"

View file

@ -0,0 +1,88 @@
{
"TBGCheckData": {
"size": "72",
"offsets": [
{
"offset": "0",
"type": "u16",
"format": "hex",
"name": "Surface Type",
"notes": ""
},
{
"offset": "2",
"type": "s16",
"name": "Surface Parameter",
"notes": ""
},
{
"offset": "4",
"type": "u16",
"format": "hex",
"hidden": true,
"name": "Surface Flag",
"notes": "0x0008: XWALL(0) ZWALL(1)\n0x0010: isIllegal(1)"
},
{
"offset": "6",
"type": "u8",
"name": "Sound effect id",
"hidden": true,
"notes": ""
},
{
"offset": "8",
"type": "float",
"name": "Min Y",
"notes": ""
},
{
"offset": "c",
"type": "float",
"name": "Max Y",
"notes": ""
},
{
"offset": "10",
"type": "JGeometry::TVec3<float>",
"name": "Vertex0.*",
"hidden": true,
"notes": ""
},
{
"offset": "1c",
"type": "JGeometry::TVec3<float>",
"name": "Vertex1.*",
"hidden": true,
"notes": ""
},
{
"offset": "28",
"type": "JGeometry::TVec3<float>",
"name": "Vertex2.*",
"hidden": true,
"notes": ""
},
{
"offset": "34",
"type": "JGeometry::TVec3<float>",
"name": "Normal.*",
"notes": ""
},
{
"offset": "40",
"type": "float",
"name": "-Vertex0 dot Normal",
"hidden": true,
"notes": ""
},
{
"offset": "44",
"type": "void*",
"name": "Owner",
"hidden": true,
"notes": ""
}
]
}
}

View file

@ -80,24 +80,32 @@
"offset": "e0",
"type": "TBGCheckData*",
"name": "Floor triangle under Mario",
"hidden": true,
"notes": ""
},
{
"offset": ["e0", "0"],
"type": "TBGCheckData",
"name": "Ground *",
"hidden": false,
"notes": ""
},
{
"offset": "e8",
"type": "float",
"name": "Height of the ceiling above Mario",
"name": "Ceiling height",
"notes": "9,999,999 if none"
},
{
"offset": "ec",
"type": "float",
"name": "Height of the floor below Mario",
"name": "Ground height",
"notes": "-32,767 if none"
},
{
"offset": "f0",
"type": "float",
"name": "Height of the water surface at Marios position",
"name": "Water surface height",
"notes": "Y position if none"
},
{

View file

@ -1,94 +0,0 @@
/// SPDX-FileCopyrightText: 2023 sup39 <sms@sup39.dev>
/// SPDX-License-Identifier: MIT OR Apache-2.0
use crate::sys::process::PidType;
use crate::sys::shared_memory::{SharedMemory, SharedMemoryOpenError};
use crate::addr::Addr;
use crate::big_endian::DecodeBE;
use crate::sys::process::{Process32Iterator, ProcessInfo};
use encoding_rs::SHIFT_JIS;
pub const MEM1_START_ADDR: Addr = Addr(0x8000_0000);
pub const MEM1_END_ADDR: Addr = Addr(0x8180_0000);
pub trait Dolphin {
/// # Safety
///
/// The offset must be smaller than the size of the memory region
unsafe fn mem<T: Into<isize>>(&self, offset: T) -> *mut u8;
fn get_ptr_mut(&self, addr: Addr, size: usize) -> Option<*mut u8> {
if MEM1_START_ADDR <= addr && addr < MEM1_END_ADDR - size as u32 {
Some(unsafe {self.mem(addr - MEM1_START_ADDR)})
} else {
None
}
}
#[inline]
fn read_bytes(&self, addr: Addr, size: usize) -> Option<&[u8]> {
self.get_ptr_mut(addr, size)
.map(|ptr| unsafe {std::slice::from_raw_parts(ptr, size)})
}
#[inline]
fn read<T: DecodeBE>(&self, addr: Addr) -> Option<T> {
let size = std::mem::size_of::<T>();
self.get_ptr_mut(addr, size)
.map(|ptr| unsafe {T::decode_be(ptr)})
}
fn read_str(&self, addr: Addr) -> Option<String> {
if MEM1_START_ADDR <= addr && addr < MEM1_END_ADDR {
let ptr = unsafe {self.mem(addr - MEM1_START_ADDR)};
const MAX_LENGTH: u32 = 256; // TODO
let max_length = MAX_LENGTH; // TODO
let maxlen = std::cmp::min(max_length as usize, (MEM1_END_ADDR - addr) as usize);
// let maxlen = (MEM1_END_ADDR - addr) as usize;
let mut len = 0usize;
while len < maxlen {
if unsafe{*ptr.add(len) == 0} {break;}
len += 1;
}
SHIFT_JIS.decode_without_bom_handling_and_without_replacement(unsafe{std::slice::from_raw_parts(ptr, len)}).map(|x| x.into_owned())
} else {
None
}
}
#[inline]
fn write_bytes(&self, addr: Addr, payload: &[u8]) -> Option<()> {
let size = payload.len();
self.get_ptr_mut(addr, size)
.map(|ptr| unsafe {std::ptr::copy(payload.as_ptr(), ptr, size)})
}
}
pub struct DolphinMemory {
shared_memory: SharedMemory,
pid: PidType,
}
impl Dolphin for DolphinMemory {
#[inline]
unsafe fn mem<T: Into<isize>>(&self, offset: T) -> *mut u8 {
self.shared_memory.get_ptr().offset(offset.into())
}
}
impl DolphinMemory {
#[inline]
pub fn pid(&self) -> PidType {
self.pid
}
pub fn open_pid<T: Into<PidType>>(pid: T) -> Result<DolphinMemory, SharedMemoryOpenError> {
let pid: usize = pid.into();
let shared_memory = SharedMemory::open(&format!("dolphin-emu.{}", pid))?;
Ok(DolphinMemory {shared_memory, pid})
}
pub fn list_dolphin() -> impl Iterator<Item = (usize, Result<DolphinMemory, SharedMemoryOpenError>)> {
Process32Iterator::new().filter_map(|p| p.get_name().to_str().and_then(|name|
match name {
"Dolphin.exe" => Some((p.pid(), DolphinMemory::open_pid(p.pid()))),
_ => None,
}
))
}
}

33
src/dolphin/addr.rs Normal file
View file

@ -0,0 +1,33 @@
use crate::dolphin::Addr;
pub const MEM1_START_ADDR: Addr = Addr(0x8000_0000);
pub const MEM1_END_ADDR: Addr = Addr(0x8180_0000);
pub const MEM1_SIZE: u32 = MEM1_END_ADDR.0 - MEM1_START_ADDR.0;
pub const MEM2_START_ADDR: Addr = Addr(0x9000_0000);
pub const MEM2_END_ADDR: Addr = Addr(0x9400_0000);
pub const MEM2_SIZE: u32 = MEM2_END_ADDR.0 - MEM2_START_ADDR.0;
pub enum DolphinMemAddr {
MEM1(u32),
MEM2(u32),
}
impl DolphinMemAddr {
pub fn space(&self) -> u32 {
match self {
DolphinMemAddr::MEM1(off) => MEM1_SIZE - off,
DolphinMemAddr::MEM2(off) => MEM2_SIZE - off,
}
}
}
impl TryFrom<Addr> for DolphinMemAddr {
type Error = ();
fn try_from(addr: Addr) -> Result<Self, Self::Error> {
if (MEM1_START_ADDR..MEM1_END_ADDR).contains(&addr) {
Ok(DolphinMemAddr::MEM1(addr.0 - MEM1_START_ADDR.0))
} else if (MEM2_START_ADDR..MEM2_END_ADDR).contains(&addr) {
Ok(DolphinMemAddr::MEM2(addr.0 - MEM2_START_ADDR.0))
} else {
Err(())
}
}
}

118
src/dolphin/mod.rs Normal file
View file

@ -0,0 +1,118 @@
pub use crate::big_endian::DecodeBE;
pub use crate::addr::Addr;
pub use crate::sys::process::PidType;
use crate::sys::process::{Process32Iterator, ProcessInfo};
use encoding_rs::SHIFT_JIS;
pub mod addr;
mod shared_memory;
mod process_memory;
pub use addr::DolphinMemAddr;
use shared_memory::DolphinSharedMemory;
use process_memory::DolphinProcessMemory;
pub trait Dolphin {
/// # Safety
/// `maddr + size` must be in bound
unsafe fn operate_memory_unchecked<T, F>(
&self, maddr: DolphinMemAddr, size: usize, operator: F,
) -> Option<T>
where F: FnOnce(*mut u8) -> T;
#[inline]
fn operate_memory<T, F>(&self, addr: Addr, size: usize, operator: F) -> Option<T>
where F: FnOnce(*mut u8) -> T
{
DolphinMemAddr::try_from(addr).ok().and_then(|maddr| {
if (maddr.space() as usize) < size {return None}
unsafe {self.operate_memory_unchecked(maddr, size, operator)}
})
}
#[inline]
fn operate_memory_truncated<T, F>(&self, addr: Addr, max_size: usize, operator: F) -> Option<T>
where F: FnOnce(*mut u8, usize) -> T
{
DolphinMemAddr::try_from(addr).ok().and_then(|maddr| {
let size = std::cmp::min(maddr.space() as usize, max_size);
unsafe {self.operate_memory_unchecked(maddr, size, |ptr| operator(ptr, size))}
})
}
fn read<T: DecodeBE>(&self, addr: Addr) -> Option<T> {
let size = std::mem::size_of::<T>();
self.operate_memory(addr, size, |ptr| unsafe {T::decode_be(ptr)})
}
fn read_str(&self, addr: Addr) -> Option<String> {
let maxlen = 256; // TODO
self.operate_memory_truncated(addr, maxlen, |ptr, maxlen| {
let mut len = 0usize;
while len < maxlen && unsafe{*ptr.add(len)} != 0 {
len += 1;
}
SHIFT_JIS.decode_without_bom_handling_and_without_replacement(
unsafe{std::slice::from_raw_parts(ptr, len)}
).map(|s| s.into_owned())
}).unwrap_or(None)
}
fn dump_hex(&self, addr: Addr, size: usize) -> Option<String> {
self.operate_memory(addr, size, |ptr| {
(0..size)
.map(|i| format!("{:02X}", unsafe {*ptr.add(i)}))
.collect()
})
}
fn write_bytes(&self, addr: Addr, payload: &[u8]) -> Option<()> {
let size = payload.len();
self.operate_memory(addr, size, |ptr| unsafe {
std::ptr::copy(payload.as_ptr(), ptr, size);
})
}
}
pub enum DolphinMemory {
SharedMemory(DolphinSharedMemory),
ProcessMemory(DolphinProcessMemory),
}
impl Dolphin for DolphinMemory {
unsafe fn operate_memory_unchecked<T, F>(&self, maddr: DolphinMemAddr, size: usize, operator: F) -> Option<T>
where F: FnOnce(*mut u8) -> T
{
match self {
DolphinMemory::SharedMemory(m) => m.operate_memory_unchecked(maddr, size, operator),
DolphinMemory::ProcessMemory(m) => m.operate_memory_unchecked(maddr, size, operator),
}
}
}
impl From<DolphinSharedMemory> for DolphinMemory {
fn from(x: DolphinSharedMemory) -> Self {
Self::SharedMemory(x)
}
}
impl From<DolphinProcessMemory> for DolphinMemory {
fn from(x: DolphinProcessMemory) -> Self {
Self::ProcessMemory(x)
}
}
impl DolphinMemory {
pub fn list() -> impl Iterator<Item = (PidType, Option<DolphinMemory>)> {
Process32Iterator::new().filter_map(|p| p.get_name().to_str().and_then(|name|
match name {
"Dolphin.exe" | "DolphinQt2.exe" | "DolphinWx.exe" => {
let pid = p.pid();
Some((pid, {
DolphinSharedMemory::open_pid(pid).ok()
.map(DolphinMemory::SharedMemory)
.or_else(|| {
DolphinProcessMemory::open_pid(pid).ok()
.map(DolphinMemory::ProcessMemory)
})
}))
},
_ => None,
}
))
}
}

View file

@ -0,0 +1,93 @@
/// SPDX-FileCopyrightText: 2023 sup39 <sms@sup39.dev>
/// SPDX-License-Identifier: MIT OR Apache-2.0
///
/// The `DolphinProcessMemory::open_pid` function is based on
/// `WindowsDolphinProcess::obtainEmuRAMInformations()`
/// (https://github.com/aldelaro5/Dolphin-memory-engine/blob/master/Source/DolphinProcess/Windows/WindowsDolphinProcess.cpp#L47)
/// from aldelaro5's Dolphin memory engine
/// (https://github.com/aldelaro5/Dolphin-memory-engine)
/// SPDX-FileCopyrightText: 2017 aldelaro5
/// SPDX-License-Identifier: MIT
use super::{Dolphin, DolphinMemAddr, PidType};
use crate::sys::process_memory::ProcessMemoryIterator;
use core::ffi::c_void;
use windows::Win32::Foundation::{HANDLE, CloseHandle};
use windows::Win32::System::{
Threading::{PROCESS_QUERY_INFORMATION, PROCESS_VM_READ, PROCESS_VM_WRITE, OpenProcess},
Memory::MEM_MAPPED,
Diagnostics::Debug::ReadProcessMemory,
};
pub struct DolphinProcessMemory {
h_proc: HANDLE,
base_addr_mem1: usize,
base_addr_mem2: Option<usize>,
}
impl Drop for DolphinProcessMemory {
fn drop(&mut self) {
unsafe {
CloseHandle(self.h_proc);
}
}
}
impl Dolphin for DolphinProcessMemory {
unsafe fn operate_memory_unchecked<T, F>(&self, maddr: DolphinMemAddr, size: usize, operator: F) -> Option<T>
where F: FnOnce(*mut u8) -> T
{
match maddr {
DolphinMemAddr::MEM1(offset) => Some(self.base_addr_mem1 + (offset as usize)),
DolphinMemAddr::MEM2(offset) => self.base_addr_mem2.map(|base_addr| base_addr + (offset as usize)),
}.and_then(|base_addr| {
let mut buf = Vec::with_capacity(size);
let ptr = buf.as_mut_ptr();
match unsafe {ReadProcessMemory(
self.h_proc, base_addr as *const c_void,
ptr as *mut c_void, size, None,
).as_bool()} {
true => Some(operator(ptr)),
false => None,
}
})
}
}
pub enum DolphinProcessMemoryFindError {
OpenError(windows::core::Error),
MemoryNotFound,
}
impl From<windows::core::Error> for DolphinProcessMemoryFindError {
fn from(e: windows::core::Error) -> Self {
Self::OpenError(e)
}
}
impl DolphinProcessMemory {
pub fn open_pid(pid: PidType) -> Result<DolphinProcessMemory, DolphinProcessMemoryFindError> {
unsafe {
let h_proc = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ | PROCESS_VM_WRITE, false, pid)?;
let mut itr = ProcessMemoryIterator::with_handle(h_proc);
// find MEM1
let Some(base_addr_mem1) =
itr.find(|meminfo| meminfo.RegionSize == 0x2000000 && meminfo.Type == MEM_MAPPED)
.map(|meminfo| meminfo.BaseAddress)
else {
CloseHandle(h_proc);
return Err(DolphinProcessMemoryFindError::MemoryNotFound);
};
// find MEM2
let base_addr_mem2_check = base_addr_mem1.add(0x10000000);
let base_addr_mem2 =
itr.find(|meminfo| {
meminfo.BaseAddress == base_addr_mem2_check
&& meminfo.RegionSize == 0x4000000
&& meminfo.Type == MEM_MAPPED
}).map(|_| base_addr_mem2_check as usize);
Ok(DolphinProcessMemory {
h_proc, base_addr_mem1: base_addr_mem1 as usize, base_addr_mem2,
})
}
}
}

View file

@ -0,0 +1,38 @@
/// SPDX-FileCopyrightText: 2023 sup39 <sms@sup39.dev>
/// SPDX-License-Identifier: MIT OR Apache-2.0
use crate::sys::process::PidType;
use crate::sys::shared_memory::{SharedMemory, SharedMemoryOpenError};
use crate::dolphin::{Dolphin, DolphinMemAddr, addr::MEM2_SIZE};
pub struct DolphinSharedMemory {
shared_memory: SharedMemory,
has_mem2: bool,
}
pub const MEM2_OFFSET: u32 = 0x4040000;
impl Dolphin for DolphinSharedMemory {
/// # Safety
/// `maddr + size` must be in bound
unsafe fn operate_memory_unchecked<T, F>(&self, maddr: DolphinMemAddr, _size: usize, operator: F) -> Option<T>
where F: FnOnce(*mut u8) -> T
{
match maddr {
DolphinMemAddr::MEM1(offset) => Some(offset),
DolphinMemAddr::MEM2(offset) => match self.has_mem2 {
true => Some(MEM2_OFFSET + offset),
false => None,
},
}.map(|offset| {
operator(self.shared_memory.get_ptr().add(offset as usize))
})
}
}
impl DolphinSharedMemory {
pub fn open_pid(pid: PidType) -> Result<DolphinSharedMemory, SharedMemoryOpenError> {
let shared_memory = SharedMemory::open(&format!("dolphin-emu.{}", pid))?;
let has_mem2 = shared_memory.size() >= MEM2_OFFSET + MEM2_SIZE;
Ok(DolphinSharedMemory {shared_memory, has_mem2})
}
}

View file

@ -70,7 +70,6 @@ impl FieldReader<SMSDolphin, String> for ClassNameReader {
pub struct HexFieldReader(pub usize);
impl<D: Dolphin> FieldReader<D, String> for HexFieldReader {
fn read(&self, d: &D, addr: Addr) -> Option<String> {
d.read_bytes(addr, self.0)
.map(|bytes| bytes.iter().map(|x| format!("{x:02X}")).collect())
d.dump_hex(addr, self.0)
}
}

View file

@ -63,7 +63,6 @@ impl<T: Dolphin> DolphinMemoryJsExt for T {
}
}
pub async fn handle_command(
env: &HttpEnv,
dolphin: &mut Option<SMSDolphin>,
@ -181,8 +180,8 @@ pub async fn handle_command(
let Some(size) = size.as_u64().map(|x| x as usize) else {
return_err!("\"size\" must be a positive integer");
};
d.read_bytes(addr, size)
.map(|bytes| json!(bytes.iter().map(|x| format!("{:02X}", x)).collect::<String>()))
d.dump_hex(addr, size)
.map(|s| json!(s))
.unwrap_or_else(|| json!(null))
},
None => {

View file

@ -1,6 +1,9 @@
/// SPDX-FileCopyrightText: 2023 sup39 <sms@sup39.dev>
/// SPDX-License-Identifier: MIT OR Apache-2.0
use crate::addr::Addr;
use crate::dolphin::{DolphinMemory, Dolphin, DolphinMemAddr, PidType};
#[derive(Debug, Clone, Copy)]
pub enum SMSVersion {
GMSJ01, GMSE01, GMSP01, GMSJ0A,
@ -11,31 +14,27 @@ impl std::fmt::Display for SMSVersion {
}
}
use crate::addr::Addr;
use crate::dolphin::{DolphinMemory, Dolphin};
pub struct SMSDolphin {
d: DolphinMemory,
pid: PidType,
ver: SMSVersion,
}
impl Dolphin for SMSDolphin {
#[inline]
unsafe fn mem<T: Into<isize>>(&self, offset: T) -> *mut u8 {
self.d.mem(offset)
unsafe fn operate_memory_unchecked<T, F>(&self, maddr: DolphinMemAddr, size: usize, operator: F) -> Option<T>
where F: FnOnce(*mut u8) -> T
{
self.d.operate_memory_unchecked(maddr, size, operator)
}
}
pub mod vt;
impl SMSDolphin {
#[inline]
pub fn pid(&self) -> usize {
self.d.pid()
}
#[inline]
pub fn ver(&self) -> SMSVersion {
self.ver
}
pub fn from_dolphin_memory(d: DolphinMemory) -> Result<SMSDolphin, Option<[u8; 8]>> {
pub fn from_dolphin_memory(d: DolphinMemory, pid: PidType) -> Result<SMSDolphin, Option<[u8; 8]>> {
match d.read::<&[u8; 8]>(Addr(0x80000000)) {
None => Err(None),
Some(rver) => match rver {
@ -44,7 +43,7 @@ impl SMSDolphin {
b"GMSP01\x00\x00" => Ok(SMSVersion::GMSP01),
b"GMSJ01\x00\x01" => Ok(SMSVersion::GMSJ0A),
_ => Err(Some(rver.to_owned())),
}.map(|ver| SMSDolphin {d, ver}),
}.map(|ver| SMSDolphin {d, ver, pid}),
}
}
pub fn get_class(&self, addr: Addr) -> Option<&'static str> {
@ -72,18 +71,22 @@ impl std::fmt::Display for SMSDolphinFindOneError {
}
impl SMSDolphin {
pub fn pid(&self) -> PidType {
self.pid
}
pub fn find_one() -> Result<SMSDolphin, SMSDolphinFindOneError> {
let mut dolphin_running = false;
let mut game_running = false;
for (_pid, d) in DolphinMemory::list_dolphin() {
for (pid, d) in DolphinMemory::list() {
match d {
Ok(d) => {
if let Ok(o) = SMSDolphin::from_dolphin_memory(d) {
Some(d) => {
if let Ok(o) = SMSDolphin::from_dolphin_memory(d, pid) {
return Ok(o)
}
game_running = true;
},
Err(_) => dolphin_running = true,
None => dolphin_running = true,
}
}
Err(if game_running {

View file

@ -3,3 +3,4 @@
pub mod process;
pub mod shared_memory;
pub mod process_memory;

View file

@ -3,7 +3,7 @@
use std::ffi::OsString;
use std::os::windows::ffi::OsStringExt;
use windows::Win32::Foundation::{HANDLE, BOOL};
use windows::Win32::Foundation::{HANDLE, BOOL, CloseHandle};
use windows::Win32::System::Diagnostics::ToolHelp::{
CreateToolhelp32Snapshot,
TH32CS_SNAPPROCESS,
@ -17,7 +17,6 @@ pub struct Process32Iterator {
fn_next: unsafe fn(HANDLE, *mut PROCESSENTRY32W) -> BOOL,
}
// https://learn.microsoft.com/en-us/windows/win32/api/tlhelp32/nf-tlhelp32-createtoolhelp32snapshot
impl Process32Iterator {
pub fn new() -> Self {
Process32Iterator {
@ -31,6 +30,13 @@ impl Default for Process32Iterator {
Self::new()
}
}
impl Drop for Process32Iterator {
fn drop(&mut self) {
unsafe {
CloseHandle(self.hsnapshot);
}
}
}
impl Iterator for Process32Iterator {
type Item = PROCESSENTRY32W;
@ -49,7 +55,7 @@ impl Iterator for Process32Iterator {
}
}
pub type PidType = usize;
pub type PidType = u32;
pub trait ProcessInfo {
fn pid(&self) -> PidType;
fn get_name(&self) -> OsString;

50
src/sys/process_memory.rs Normal file
View file

@ -0,0 +1,50 @@
/// SPDX-FileCopyrightText: 2023 sup39 <sms@sup39.dev>
/// SPDX-License-Identifier: MIT OR Apache-2.0
use core::ffi::c_void;
use windows::Win32::Foundation::{HANDLE, CloseHandle};
use windows::Win32::System::{
Threading::{PROCESS_QUERY_INFORMATION, OpenProcess},
Memory::{MEMORY_BASIC_INFORMATION, VirtualQueryEx},
};
pub struct ProcessMemoryIterator {
h_proc: HANDLE,
addr: *const c_void,
should_drop: bool,
}
impl ProcessMemoryIterator {
pub fn with_handle(h_proc: HANDLE) -> Self {
Self {h_proc, addr: std::ptr::null(), should_drop: false}
}
pub fn try_new(pid: u32) -> Result<Self, windows::core::Error> {
let h_proc = unsafe {
OpenProcess(PROCESS_QUERY_INFORMATION, false, pid)?
};
Ok(Self {h_proc, addr: std::ptr::null(), should_drop: true})
}
}
impl Drop for ProcessMemoryIterator {
fn drop(&mut self) {
unsafe {
if self.should_drop {
CloseHandle(self.h_proc);
}
}
}
}
impl Iterator for ProcessMemoryIterator {
type Item = MEMORY_BASIC_INFORMATION;
fn next(&mut self) -> Option<MEMORY_BASIC_INFORMATION> {
let mut meminfo = MEMORY_BASIC_INFORMATION::default();
unsafe {match {
VirtualQueryEx(self.h_proc, Some(self.addr), &mut meminfo, std::mem::size_of::<MEMORY_BASIC_INFORMATION>()) > 0
} {
true => {
self.addr = self.addr.add(meminfo.RegionSize);
Some(meminfo)
},
false => None,
}}
}
}

View file

@ -1,6 +1,7 @@
/// SPDX-FileCopyrightText: 2023 sup39 <sms@sup39.dev>
/// SPDX-License-Identifier: MIT OR Apache-2.0
use core::ffi::c_void;
use windows::core::PCSTR;
use windows::Win32::Foundation::{HANDLE, CloseHandle};
use windows::Win32::System::Memory::{
@ -9,6 +10,8 @@ use windows::Win32::System::Memory::{
MapViewOfFile,
UnmapViewOfFile,
MEMORYMAPPEDVIEW_HANDLE,
MEMORY_BASIC_INFORMATION,
VirtualQuery,
};
#[derive(Debug)]
@ -21,12 +24,17 @@ pub enum SharedMemoryOpenError {
pub struct SharedMemory {
h_file_mapping: HANDLE,
h_map_view: MEMORYMAPPEDVIEW_HANDLE,
size: u32,
}
impl SharedMemory {
#[inline]
pub fn get_ptr(&self) -> *mut u8 {
self.h_map_view.0 as *mut u8
}
#[inline]
pub fn size(&self) -> u32 {
self.size
}
}
impl SharedMemory {
@ -51,8 +59,14 @@ impl SharedMemory {
return Err(SharedMemoryOpenError::MemoryUninitialized);
}
let mut meminfo = MEMORY_BASIC_INFORMATION::default();
unsafe {VirtualQuery(
Some(h_map_view.0 as *mut c_void), &mut meminfo, std::mem::size_of::<MEMORY_BASIC_INFORMATION>(),
)};
let size = meminfo.RegionSize as u32;
// create SharedMemory successfully
Ok(Self {h_file_mapping, h_map_view})
Ok(Self {h_file_mapping, h_map_view, size})
}
}

File diff suppressed because it is too large Load diff

View file

@ -52,6 +52,9 @@ table.list td {
#fieldsViewer td:nth-child(3) {
text-align: right;
}
#fieldsViewer td:nth-child(3) {
min-width: 8em;
}
#fieldsViewer td:nth-child(4) {
display: none;
}

View file

@ -1,14 +1,14 @@
<!DOCTYPE html>
<html>
<head>
<title>SMS Web Object Viewer (v0.1.0-beta.2)</title>
<title>SMS Web Object Viewer</title>
<link rel="stylesheet" type="text/css" href="index.css">
<link rel="icon" type="image/svg+xml" href="icon.svg">
<script src="api.js"></script>
<script src="index.js"></script>
</head>
<body>
<h1>SMS Web Object Viewer (v0.1.0-beta.2)</h1>
<h1>SMS Web Object Viewer (v0.1.0-beta.3) (2023/07/25)</h1>
<header>
<div id="msg"></div>
<details>