[v0.1.0-beta.3] Added support for older Dolphin and MEM2
This commit is contained in:
parent
393a9fcd2f
commit
444618a42d
24 changed files with 1140 additions and 763 deletions
|
@ -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
2
Cargo.lock
generated
|
@ -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",
|
||||
|
|
|
@ -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"] }
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
120
about.hbs
|
@ -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>
|
||||
|
|
1
build.sh
1
build.sh
|
@ -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/"
|
||||
|
|
88
res/ObjectParameters/TBGCheckData.json
Normal file
88
res/ObjectParameters/TBGCheckData.json
Normal 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": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -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 Mario’s position",
|
||||
"name": "Water surface height",
|
||||
"notes": "Y position if none"
|
||||
},
|
||||
{
|
||||
|
|
|
@ -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
33
src/dolphin/addr.rs
Normal 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
118
src/dolphin/mod.rs
Normal 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,
|
||||
}
|
||||
))
|
||||
}
|
||||
}
|
93
src/dolphin/process_memory.rs
Normal file
93
src/dolphin/process_memory.rs
Normal 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,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
38
src/dolphin/shared_memory.rs
Normal file
38
src/dolphin/shared_memory.rs
Normal 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})
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 => {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -3,3 +3,4 @@
|
|||
|
||||
pub mod process;
|
||||
pub mod shared_memory;
|
||||
pub mod process_memory;
|
||||
|
|
|
@ -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
50
src/sys/process_memory.rs
Normal 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,
|
||||
}}
|
||||
}
|
||||
}
|
|
@ -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})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
1156
www/LICENSE.html
1156
www/LICENSE.html
File diff suppressed because it is too large
Load diff
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in a new issue