[v0.1.3] Added Older Dolphin support (e.g. Lua-Core) on windows
This commit is contained in:
parent
0ddaa379e5
commit
cb366e0cdb
5 changed files with 142 additions and 4 deletions
30
3RD-PARTY-LICENSE.txt
Normal file
30
3RD-PARTY-LICENSE.txt
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
3RD PARTY LICENSES
|
||||||
|
|
||||||
|
* dolphin-memory-engine (modified)
|
||||||
|
(https://github.com/aldelaro5/Dolphin-memory-engine)
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
# Copyright (c) 2017 aldelaro5
|
||||||
|
|
||||||
|
#############################################################################
|
||||||
|
LICENSE TEXTS
|
||||||
|
#############################################################################
|
||||||
|
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
|
@ -1,4 +1,6 @@
|
||||||
# Change Log
|
# Change Log
|
||||||
|
## 0.1.3 (2023/04/16)
|
||||||
|
- Added Older Dolphin support (e.g. Lua-Core) on windows
|
||||||
## 0.1.2 (2023/03/27)
|
## 0.1.2 (2023/03/27)
|
||||||
- Fixed MEM2 range
|
- Fixed MEM2 range
|
||||||
## 0.1.1 (2023/03/27)
|
## 0.1.1 (2023/03/27)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[metadata]
|
[metadata]
|
||||||
name = supDolphinWS-server
|
name = supDolphinWS-server
|
||||||
version = 0.1.2
|
version = 0.1.3
|
||||||
author = sup39
|
author = sup39
|
||||||
author_email = sms@sup39.dev
|
author_email = sms@sup39.dev
|
||||||
description = A WebSocket server for accessing memory of emulated games in Dolphin
|
description = A WebSocket server for accessing memory of emulated games in Dolphin
|
||||||
|
|
|
@ -5,6 +5,11 @@ import psutil
|
||||||
import struct
|
import struct
|
||||||
from multiprocessing.shared_memory import SharedMemory
|
from multiprocessing.shared_memory import SharedMemory
|
||||||
|
|
||||||
|
if os.name == 'nt':
|
||||||
|
from .memory_windows import try_get_memory as try_get_memory_direct
|
||||||
|
else:
|
||||||
|
try_get_memory_direct = None
|
||||||
|
|
||||||
MEM1_START = 0x80000000
|
MEM1_START = 0x80000000
|
||||||
MEM1_END = 0x81800000
|
MEM1_END = 0x81800000
|
||||||
MEM2_START = 0x90000000
|
MEM2_START = 0x90000000
|
||||||
|
@ -16,14 +21,23 @@ dolphinProcNames = \
|
||||||
else {'dolphin-emu', 'dolphin-emu-qt2', 'dolphin-emu-wx'}
|
else {'dolphin-emu', 'dolphin-emu-qt2', 'dolphin-emu-wx'}
|
||||||
|
|
||||||
def try_get_memory(pid):
|
def try_get_memory(pid):
|
||||||
try: return SharedMemory('dolphin-emu.'+str(pid))
|
# newer Dolphin => SharedMemory
|
||||||
except FileNotFoundError: return None
|
try:
|
||||||
|
return SharedMemory('dolphin-emu.'+str(pid))
|
||||||
|
except FileNotFoundError: pass
|
||||||
|
# old Dolphin (on windows) => direct memory access via win32 api
|
||||||
|
if try_get_memory_direct:
|
||||||
|
try:
|
||||||
|
return try_get_memory_direct(pid)
|
||||||
|
except:
|
||||||
|
import traceback
|
||||||
|
print(traceback.format_exc())
|
||||||
|
|
||||||
def find_memory(exclude_pid={}):
|
def find_memory(exclude_pid={}):
|
||||||
try: return next(
|
try: return next(
|
||||||
(p.pid, m)
|
(p.pid, m)
|
||||||
for p in psutil.process_iter(['pid', 'name'])
|
for p in psutil.process_iter(['pid', 'name'])
|
||||||
if p.pid not in exclude_pid and p.name() in dolphinProcNames
|
if p.pid not in exclude_pid and p.name() in dolphinProcNames and p.status() == 'running'
|
||||||
for m in [try_get_memory(p.pid)]
|
for m in [try_get_memory(p.pid)]
|
||||||
if m is not None
|
if m is not None
|
||||||
)
|
)
|
||||||
|
|
92
src/supDolphinWS/server/memory_windows.py
Normal file
92
src/supDolphinWS/server/memory_windows.py
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
# Copyright (c) 2023 sup39
|
||||||
|
'''
|
||||||
|
The `try_get_memory` 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-License-Identifier: MIT
|
||||||
|
# Copyright (c) 2017 aldelaro5
|
||||||
|
'''
|
||||||
|
|
||||||
|
import ctypes
|
||||||
|
from ctypes import wintypes
|
||||||
|
wintypes.SIZE_T = ctypes.c_uint32 if ctypes.sizeof(ctypes.c_void_p) == 4 else ctypes.c_uint64
|
||||||
|
|
||||||
|
PROCESS_QUERY_INFORMATION = 0x0400
|
||||||
|
PROCESS_VM_READ = 0x0010
|
||||||
|
PROCESS_VM_WRITE = 0x0020
|
||||||
|
MEM_MAPPED = 0x40000
|
||||||
|
|
||||||
|
class MEMORY_BASIC_INFORMATION(ctypes.Structure):
|
||||||
|
_fields_ = [
|
||||||
|
("BaseAddress", wintypes.LPVOID),
|
||||||
|
("AllocationBase", wintypes.LPVOID),
|
||||||
|
("AllocationProtect", wintypes.DWORD),
|
||||||
|
("RegionSize", wintypes.SIZE_T),
|
||||||
|
("State", wintypes.DWORD),
|
||||||
|
("Protect", wintypes.DWORD),
|
||||||
|
("Type", wintypes.DWORD)
|
||||||
|
]
|
||||||
|
|
||||||
|
kernel32 = ctypes.WinDLL("kernel32.dll")
|
||||||
|
virtual_query_ex = kernel32.VirtualQueryEx
|
||||||
|
virtual_query_ex.argtypes = [wintypes.HANDLE, wintypes.LPCVOID, ctypes.POINTER(MEMORY_BASIC_INFORMATION), wintypes.SIZE_T]
|
||||||
|
virtual_query_ex.restype = wintypes.SIZE_T
|
||||||
|
|
||||||
|
class FakeMemorySlice(bytes):
|
||||||
|
def tobytes(self): return self
|
||||||
|
|
||||||
|
class ProcessMemoryView:
|
||||||
|
def __init__(self, hProcess, baseAddr):
|
||||||
|
self.hProcess = hProcess
|
||||||
|
self.baseAddr = baseAddr
|
||||||
|
def __getitem__(self, rg):
|
||||||
|
if type(rg) is not slice: raise TypeError('Index must be `slice`')
|
||||||
|
assert rg.start is not None and rg.stop is not None, 'Start and stop of the slice must be given'
|
||||||
|
addr = rg.start
|
||||||
|
size = rg.stop - rg.start
|
||||||
|
buf = ctypes.create_string_buffer(size)
|
||||||
|
nbyte = ctypes.c_size_t(0)
|
||||||
|
assert kernel32.ReadProcessMemory(
|
||||||
|
self.hProcess,
|
||||||
|
ctypes.c_void_p(self.baseAddr+addr),
|
||||||
|
buf,
|
||||||
|
ctypes.c_size_t(size),
|
||||||
|
ctypes.byref(nbyte),
|
||||||
|
), f'Failed to read process memory. Error code: {kernel32.GetLastError()}'
|
||||||
|
return FakeMemorySlice(buf[slice(None, None, rg.step)])
|
||||||
|
def __setitem__(self, rg, payload):
|
||||||
|
if type(rg) is not slice: raise TypeError('Index must be `slice`')
|
||||||
|
assert rg.start is not None, 'Start of the slice must be given'
|
||||||
|
addr = rg.start
|
||||||
|
nbyte = ctypes.c_size_t(0)
|
||||||
|
assert kernel32.WriteProcessMemory(
|
||||||
|
self.hProcess,
|
||||||
|
ctypes.c_void_p(self.baseAddr+addr),
|
||||||
|
bytes(payload),
|
||||||
|
ctypes.c_size_t(len(payload)),
|
||||||
|
ctypes.byref(nbyte)
|
||||||
|
), f'Failed to write process memory. Error code: {kernel32.GetLastError()}'
|
||||||
|
return nbyte.value
|
||||||
|
def __len__(self):
|
||||||
|
return 0x2000000 # TODO
|
||||||
|
|
||||||
|
class FakeSharedMemory:
|
||||||
|
def __init__(self, hProcess, baseAddr):
|
||||||
|
self.hProcess = hProcess
|
||||||
|
self.buf = ProcessMemoryView(hProcess, baseAddr)
|
||||||
|
def __del__(self):
|
||||||
|
self.close()
|
||||||
|
def close(self):
|
||||||
|
kernel32.CloseHandle(self.hProcess)
|
||||||
|
|
||||||
|
def try_get_memory(pid): # TODO MEM2
|
||||||
|
hProcess = kernel32.OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ | PROCESS_VM_WRITE, False, pid)
|
||||||
|
baseAddr = 0
|
||||||
|
memory_info = MEMORY_BASIC_INFORMATION()
|
||||||
|
while virtual_query_ex(hProcess, baseAddr, ctypes.byref(memory_info), ctypes.sizeof(memory_info)):
|
||||||
|
if memory_info.RegionSize == 0x2000000 and memory_info.Type == MEM_MAPPED:
|
||||||
|
return FakeSharedMemory(hProcess, baseAddr)
|
||||||
|
baseAddr += memory_info.RegionSize
|
Reference in a new issue