Archived
1
0
Fork 0
This commit is contained in:
sup39 2021-11-18 19:44:40 +09:00
commit 56d0ac2064
5 changed files with 886 additions and 0 deletions

22
LICENSE Normal file
View file

@ -0,0 +1,22 @@
Copyright (c) 2021 sup39[サポミク]
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.

18
README.md Normal file
View file

@ -0,0 +1,18 @@
# SMS-CM-gap
Brute forcing lava surface gap in Corona Mountain with Dolphin and Jupyter notebook.
## Installation
Install Python 3.9 or higher, and use `pip` to install all packages in `requirements.txt`:
```
pip install -r requirements.txt
```
## Usage
Start Jupyter notebook/lab and browse `main.ipynb`:
```
jupyter notebook
```
## Credit
The file `memorylib.py` in this repo is a copy from
[QbeRoot/sms-livecol](https://github.com/QbeRoot/sms-livecol).

590
main.ipynb Normal file
View file

@ -0,0 +1,590 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "b20e1b96-585c-4cfe-899a-a12198b076f7",
"metadata": {},
"source": [
"# Brute forcing lava surface gap in Corona Mountain\n",
"[su(@ykpin64)'s demo 1](https://twitter.com/ykpin64/status/1439228134088331273) \n",
"[su(@ykpin64)'s demo 2](https://twitter.com/ykpin64/status/1439867660955623426) \n",
"[Coordinates within the gap near z=13260.290](https://docs.google.com/spreadsheets/d/117ut2qKKVrpSavebtknhrhqDuvPu3A9dRuK7i47dGSM/edit#gid=1250557014)\n",
"(by sup39)"
]
},
{
"cell_type": "markdown",
"id": "d5f2ea9b-5259-446e-9a1d-8fc31700fe67",
"metadata": {},
"source": [
"## Prerequisite\n",
"Make sure `memorylib.py` is in the same directory as this Jupyter notebook.\n",
"If you don't have one, [download it](https://raw.githubusercontent.com/QbeRoot/sms-livecol/main/memorylib.py) from\n",
"[QbeRoot/sms-livecol](https://github.com/QbeRoot/sms-livecol)\n",
"and put it in the same directory as this Jupyter notebook."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "09f159c9-77ad-41e0-b8ef-5ad35ab3afc0",
"metadata": {},
"outputs": [],
"source": [
"# Well, I don't know how to use wget in Windows\n",
"!wget https://raw.githubusercontent.com/QbeRoot/sms-livecol/main/memorylib.py"
]
},
{
"cell_type": "markdown",
"id": "8c595984-b034-4a0d-9e65-1ed443a0b562",
"metadata": {
"tags": []
},
"source": [
"## Preparation"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "d5037b8e-3916-4c33-8670-3c1c22e80b78",
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"from numpy import array, float32\n",
"import time\n",
"# float32 contants\n",
"inf = np.float32(np.inf)\n",
"minf = -inf\n",
"nan = np.float32(np.nan)\n",
"# Make sure memorylib.py is in the same directory\n",
"from memorylib import Dolphin"
]
},
{
"cell_type": "markdown",
"id": "20d8563d-ecdb-40af-95b8-5aee1b412db2",
"metadata": {},
"source": [
"### Initialize Dolphin\n",
"Open Dolphin and start Super Mario Sunshine, and then execute the following code:"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "ac8f997d-e4e6-49e7-8b5f-7523eb1bce0d",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"1813322042992 0x1a6326a8e70\n"
]
}
],
"source": [
"dolphin = Dolphin()\n",
"if not dolphin.find_dolphin():\n",
" print('Dolphin not found')\n",
"if not dolphin.init_shared_memory():\n",
" print('MEM1 not found')\n",
"if dolphin.read_ram(0, 3).tobytes() != b'GMS':\n",
" print('Current game is not Sunshine')"
]
},
{
"cell_type": "markdown",
"id": "846fc1f2-b921-4f58-8958-a48d5a8c3ae5",
"metadata": {},
"source": [
"If it says `MEM1 not found`, make sure your Dolphin is dev or beta version. **You can not use the stable 5.0 version**."
]
},
{
"cell_type": "markdown",
"id": "c6185fac-4925-46f9-8fd4-c4c1575d2c5f",
"metadata": {},
"source": [
"If no error occurs, proceed to enter Corona Mountain in Dolphin."
]
},
{
"cell_type": "markdown",
"id": "9dcb8308-bab5-41d4-9642-4224639d807f",
"metadata": {},
"source": [
"### Prevent Mario from dying when touching lava \n",
"The lava surface in Corona Mountain consists of two triangles. One is at memory `80EE82C8`, and the other is at `80EE8310`. For convenience, change the water(floor) type of these triangles to `0x104`, which Mario can stand on it without dying.\n",
"\n",
"Make sure to overwrite the water type **after** entering Corona Mountain."
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "5bd25bb7-c761-42fd-ae35-e837e2da5cbd",
"metadata": {},
"outputs": [],
"source": [
"dolphin.write_uint16(0x80EE82C8, 0x104)\n",
"dolphin.write_uint16(0x80EE8310, 0x104)"
]
},
{
"cell_type": "markdown",
"id": "d88e28b8-ee15-43cd-a1a3-12ac4402b166",
"metadata": {},
"source": [
"You can save state after setting the floor type,\n",
"and next time you just need to reload the state you saved\n",
"without setting the floor type again."
]
},
{
"cell_type": "markdown",
"id": "7455bb2d-ea35-4d5d-8ca4-cea10a36cb02",
"metadata": {},
"source": [
"### Prepare Memory Address\n",
"We need to set Mario's position, so we need to know the address of his X, Y, Z coordinate.\n",
"Also, in order to detect if the water surface is under Mario, we can check if \n",
"`TMario+0xEC`(Height of the floor below Mario) is 0(water surface) or -500(no water surface).\n",
"#### References\n",
"[Version magic number](https://github.com/QbeRoot/sms-livecol/blob/main/collision.py#L292) \n",
"[Absolute address of *gpMarioOriginal](https://docs.google.com/spreadsheets/d/1ElTW-akaTUF9OC2pIFR9-7aVPwpJ54AdEVJyJ_jvg0E/edit#gid=1727422135) \n",
"[RAM Map of TMario](https://docs.google.com/spreadsheets/d/1ElTW-akaTUF9OC2pIFR9-7aVPwpJ54AdEVJyJ_jvg0E/edit#gid=1550544746)"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "0d3832ab-4ba9-4786-bea3-8941379bb677",
"metadata": {},
"outputs": [],
"source": [
"# TMario**\n",
"ptrPtrMario = {\n",
" 0x23: 0x8040A378, # JP 1.0\n",
" 0xA3: 0x8040E0E8, # NA / KOR\n",
" 0x41: 0x804057B0, # PAL\n",
" 0x80: 0x8040A378, # JP 1.1 (Not sure)\n",
" # 0x4D: ????????, # 3DAS\n",
"}.get(dolphin.read_uint8(0x80365DDD))\n",
"\n",
"# TMario*\n",
"ptrMario = dolphin.read_uint32(ptrPtrMario)\n",
"ptrX, ptrY, ptrZ = (ptrMario+i for i in range(0x10, 0x18+1, 4))\n",
"ptrFloorHeight = ptrMario+0xec"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "e2eb5f72-250d-460b-9c80-e7e9f673a549",
"metadata": {},
"outputs": [],
"source": [
"def write_position(x, y, z):\n",
" dolphin.write_float(ptrX, x)\n",
" dolphin.write_float(ptrY, y)\n",
" dolphin.write_float(ptrZ, z)"
]
},
{
"cell_type": "markdown",
"id": "0695afa9-3105-4026-8aa0-c02fa1d2e022",
"metadata": {},
"source": [
"Now, you can use `write_position(x, y, z)` to move Mario to any point you like.\n"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "90a0ba5e-d858-4b27-891b-35129c9866c7",
"metadata": {},
"outputs": [],
"source": [
"write_position(float32(1302.07495), 100, float32(5962.14697))"
]
},
{
"cell_type": "markdown",
"id": "9fa16dca-6de4-4fba-a362-3e98b8c7ed75",
"metadata": {},
"source": [
"### Other utility functions\n",
"You may want to find the coordinate one by one,\n",
"and need to know the nearest previous/next float32.\n",
"To do that, you can simply use `numpy.nextafter(x, toward)`."
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "dd09e269-3ad9-4677-b7a0-d571763c29f4",
"metadata": {},
"outputs": [],
"source": [
"nextf = lambda x: np.nextafter(float32(x), inf)\n",
"prevf = lambda x: np.nextafter(float32(x), minf)"
]
},
{
"cell_type": "markdown",
"id": "24a574ee-8988-4246-9abd-f307aa75707b",
"metadata": {},
"source": [
"For instance, the next float32 after `1` should be `1+2^-23`,\n",
"and the previous float32 before `1` should be `1-2^-24`.\n",
"\n",
"Note that you would better cast\n",
"python's `float`(which is handled as `float64`) into `numpy.float32` explicitly\n",
"to prevent unexpected type casting."
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "72c7d34b-b473-4f8d-ac16-0afc2ddb9853",
"metadata": {},
"outputs": [],
"source": [
"assert nextf(1)-float32(1) == 2**-23\n",
"assert float32(1)-prevf(1) == 2**-24"
]
},
{
"cell_type": "markdown",
"id": "e4c51d0c-f894-469f-87c2-e99283687a84",
"metadata": {},
"source": [
"## Find coordinates within the gap\n",
"As mentioned above, we can test if a coordinate is within the gap by\n",
"moving Mario to the coordinate and check the floor height under Mario."
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "b3c7e4eb-92c6-42ee-969b-2dc29e4c3c5a",
"metadata": {},
"outputs": [],
"source": [
"# sleep 1/29.97 second, approximately 1 frame\n",
"# you can increase this variable if sometimes the return value is wrong\n",
"dt_sleep = 1/29.97\n",
"def test_xz(x, z):\n",
" write_position(x, 0, z)\n",
" time.sleep(dt_sleep)\n",
" return dolphin.read_float(ptrFloorHeight)<0 # True if no surface under Mario"
]
},
{
"cell_type": "markdown",
"id": "47661431-a351-4168-a2e3-d4dbbb541e80",
"metadata": {},
"source": [
"Now you can test any coordinate with `test_xz(x, z)`.\n",
"Note that float32 has low precision.\n",
"For example, for numbers between 4096 and 8192,\n",
"the smallest distance between two 2 floats is $4096\\times2^{-23}\\approx4.88\\times10^{-4}$.\n",
"i.e. only 4 digits after decimal point is meaningful."
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "9088b2c7-0a14-49d9-a6f1-370da9808c0c",
"metadata": {},
"outputs": [],
"source": [
"## ref. https://twitter.com/sup39x1207/status/1460915545595736072\n",
"assert test_xz(float32(1302.07495), float32(5962.14648))\n",
"assert test_xz(float32(1302.07495), float32(5962.14697))\n",
"assert not test_xz(float32(1302.07495), prevf(float32(5962.14648)))\n",
"assert not test_xz(float32(1302.07495), nextf(float32(5962.14697)))"
]
},
{
"cell_type": "markdown",
"id": "17c0c714-52fe-4f8e-841a-9b70d396f102",
"metadata": {},
"source": [
"To find the coordinates within the gap efficiently,\n",
"we can first calculate the theoretical boundary,\n",
"and then find coordinates near the boundary."
]
},
{
"cell_type": "markdown",
"id": "e65b874f-676d-4907-becc-35ed028d35fa",
"metadata": {},
"source": [
"For your information, the boundary between two lava surface triangles\n",
"(`80EE82C8` and `80EE8310`) is a segment from\n",
"`(-6000, 0, -33900)` to `(6200, 0, 32700)`\n",
"([Reference Image](https://twitter.com/ykpin64/status/1439233002677047299)).\n",
"\n",
"That is, given $x$, the z coordinate of the boundary should be\n",
"$z=-33900+\\frac{x+6000}{6200+6000}$. \n",
"Also, given $z$, the x coordinate should be $x=-6000+\\frac{z+33900}{32700+33900}$."
]
},
{
"cell_type": "markdown",
"id": "e0a5d07f-0472-490b-87c7-2fe329967c63",
"metadata": {},
"source": [
"Therefore, given z, we can use the following function to find the x range within the gap:"
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "b01878bc-74f4-4a40-bbf5-aa3d67664888",
"metadata": {},
"outputs": [],
"source": [
"def find_x(z):\n",
" # theoretical x\n",
" x = float32((z+33900)/(32700+33900)*(6200+6000)-6000)\n",
" # prev/next x to test\n",
" xp1, xn1 = (prevf(x), nextf(x))\n",
" # the actual x_min/x_max within the gap\n",
" xp, xn = (x, x) if test_xz(x, z) else (xn1, xp1)\n",
" # find x_min. test at least 2 floats\n",
" if test_xz(xp1, z): xp = xp1\n",
" xp1 = prevf(xp1)\n",
" while test_xz(xp1, z): xp, xp1 = xp1, prevf(xp1)\n",
" # find x_max. test at least 2 floats\n",
" if test_xz(xn1, z): xn = xn1\n",
" xn1 = nextf(xn1)\n",
" while test_xz(xn1, z): xn, xn1 = xn1, nextf(xn1)\n",
" # return z and x range. if xp<=xn, it means no x is valid\n",
" return (z, xp, xn) if xp <= xn else (z, None, None)"
]
},
{
"cell_type": "markdown",
"id": "686b5d7f-c7f2-4684-bd58-3f65857f86fd",
"metadata": {},
"source": [
"Note that if the actual gap is too far away from its theoretical value,\n",
"this function may cause a false negative\n",
"(and that's why I test at least 2 floats in each direction).\n",
"You can test more floats near the theoretical value if you want."
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "904e2e7e-a255-46c5-a6b1-2cbfded90e6f",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"(5962.1, 1302.0662, 1302.0667)"
]
},
"execution_count": 12,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"find_x(float32(5962.1))"
]
},
{
"cell_type": "code",
"execution_count": 13,
"id": "eb4c216d-6f48-4c02-bd03-607301c49f33",
"metadata": {},
"outputs": [],
"source": [
"assert not test_xz(prevf(float32(1302.0662)), float32(5962.1))\n",
"assert test_xz(float32(1302.0662), float32(5962.1))\n",
"assert test_xz(float32(1302.0664), float32(5962.1))\n",
"assert test_xz(float32(1302.0667), float32(5962.1))\n",
"assert not test_xz(nextf(float32(1302.0667)), float32(5962.1))"
]
},
{
"cell_type": "markdown",
"id": "2ac1a4f9-1722-4ae7-85a0-301ea52822df",
"metadata": {},
"source": [
"Finally, you may want to make a loop of z to find more points automatically.\n",
"I recommend use `tqdm` to track progress and estimate time."
]
},
{
"cell_type": "code",
"execution_count": 14,
"id": "1986e131-60fa-4176-bc74-b26f3f6094c5",
"metadata": {},
"outputs": [],
"source": [
"from tqdm.notebook import tqdm"
]
},
{
"cell_type": "markdown",
"id": "4ef6eab2-1740-4a1e-8134-d33c4265c0ff",
"metadata": {},
"source": [
"There are many ways to do a loop. The following code is the one sup39 used."
]
},
{
"cell_type": "code",
"execution_count": 15,
"id": "b65d4ccf-9ef3-48ff-b7e4-5e59d77f21be",
"metadata": {},
"outputs": [],
"source": [
"z0 = np.float32(13260.29) # Initial z value\n",
"zp = zn = z0\n",
"result = [find_x(z0)]"
]
},
{
"cell_type": "code",
"execution_count": 16,
"id": "4ac3ab10-7c1f-4e0b-b470-7fa75594e980",
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "bdfc6d7b562d43619394c49b1370bb17",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
" 0%| | 0/50 [00:00<?, ?it/s]"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"loop_count = 50 # change to any number you like\n",
"\n",
"# write the result to result.csv\n",
"with open('result.csv', 'w') as fw:\n",
" # utility functions\n",
" write_row = lambda r: print(\n",
" *('' if x is None else x for x in r),\n",
" '' if r[1] is None else r[2]-r[1]+(nextf(r[2])-r[2])/2+(r[1]-prevf(r[1]))/2,\n",
" sep=',', file=fw,\n",
" )\n",
" def append_row(r):\n",
" result.append(r)\n",
" write_row(r)\n",
" # header\n",
" print('z', 'x Min', 'x Max', 'x Range', sep=',', file=fw)\n",
" # write existing result\n",
" for r in result: write_row(r)\n",
" # loop z\n",
" for _ in tqdm(range(loop_count)):\n",
" zp, zn = prevf(zp), nextf(zn)\n",
" append_row(find_x(zp))\n",
" append_row(find_x(zn))"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "15b04d6c-818f-4b6f-8d66-7bda9f5473d0",
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"id": "3499a4d0-bd7d-4b5b-b145-adf5fe5ca6d3",
"metadata": {},
"source": [
"## LICENSE"
]
},
{
"cell_type": "raw",
"id": "29af27c1-7a24-4528-a590-6047155dee98",
"metadata": {},
"source": [
"Copyright (c) 2021 sup39[サポミク]\n",
"\n",
"Permission is hereby granted, free of charge, to any person\n",
"obtaining a copy of this software and associated documentation\n",
"files (the \"Software\"), to deal in the Software without\n",
"restriction, including without limitation the rights to use,\n",
"copy, modify, merge, publish, distribute, sublicense, and/or sell\n",
"copies of the Software, and to permit persons to whom the\n",
"Software is furnished to do so, subject to the following\n",
"conditions:\n",
"\n",
"The above copyright notice and this permission notice shall be\n",
"included in all copies or substantial portions of the Software.\n",
"\n",
"THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n",
"EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\n",
"OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n",
"NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n",
"HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n",
"WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n",
"FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\n",
"OTHER DEALINGS IN THE SOFTWARE."
]
},
{
"cell_type": "markdown",
"id": "0ce175be-7508-4b42-a573-8f8a92c72baa",
"metadata": {},
"source": [
"This Jupyter notebook is made by [sup39\\[サポミク\\]](https://sup39.dev).\n",
"If you have any question, feel free to ask me (via\n",
"[Twitter](https://twitter.com/sup39x1207),\n",
"[Github](https://github.com/sup39/SMS-CM-gap/issues),\n",
"etc.). Thanks for using this Jupyter notebook!"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "26ca4690-e660-4f39-8bfc-fe35f0323c31",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.1"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

253
memorylib.py Normal file
View file

@ -0,0 +1,253 @@
##################################################
# This file is a copy from [QbeRoot/sms-livecol] #
# (https://github.com/QbeRoot/sms-livecol) #
##################################################
import ctypes
import struct
from struct import pack, unpack
from ctypes import wintypes, sizeof, addressof, POINTER, pointer
from ctypes.wintypes import DWORD, ULONG, LONG, WORD
from multiprocessing import shared_memory
# Various Windows structs/enums needed for operation
NULL = 0
TH32CS_SNAPHEAPLIST = 0x00000001
TH32CS_SNAPPROCESS = 0x00000002
TH32CS_SNAPTHREAD = 0x00000004
TH32CS_SNAPMODULE = 0x00000008
TH32CS_SNAPALL = TH32CS_SNAPHEAPLIST | TH32CS_SNAPPROCESS | TH32CS_SNAPTHREAD | TH32CS_SNAPMODULE
assert TH32CS_SNAPALL == 0xF
PROCESS_QUERY_INFORMATION = 0x0400
PROCESS_VM_OPERATION = 0x0008
PROCESS_VM_READ = 0x0010
PROCESS_VM_WRITE = 0x0020
MEM_MAPPED = 0x40000
ULONG_PTR = ctypes.c_ulonglong
class PROCESSENTRY32(ctypes.Structure):
_fields_ = [ ( 'dwSize' , DWORD ) ,
( 'cntUsage' , DWORD) ,
( 'th32ProcessID' , DWORD) ,
( 'th32DefaultHeapID' , ctypes.POINTER(ULONG)) ,
( 'th32ModuleID' , DWORD) ,
( 'cntThreads' , DWORD) ,
( 'th32ParentProcessID' , DWORD) ,
( 'pcPriClassBase' , LONG) ,
( 'dwFlags' , DWORD) ,
( 'szExeFile' , ctypes.c_char * 260 ) ]
class MEMORY_BASIC_INFORMATION(ctypes.Structure):
_fields_ = [ ( 'BaseAddress' , ctypes.c_void_p),
( 'AllocationBase' , ctypes.c_void_p),
( 'AllocationProtect' , DWORD),
( 'PartitionID' , WORD),
( 'RegionSize' , ctypes.c_size_t),
( 'State' , DWORD),
( 'Protect' , DWORD),
( 'Type' , DWORD)]
class PSAPI_WORKING_SET_EX_BLOCK(ctypes.Structure):
_fields_ = [ ( 'Flags', ULONG_PTR),
( 'Valid', ULONG_PTR),
( 'ShareCount', ULONG_PTR),
( 'Win32Protection', ULONG_PTR),
( 'Shared', ULONG_PTR),
( 'Node', ULONG_PTR),
( 'Locked', ULONG_PTR),
( 'LargePage', ULONG_PTR),
( 'Reserved', ULONG_PTR),
( 'Bad', ULONG_PTR),
( 'ReservedUlong', ULONG_PTR)]
#class PSAPI_WORKING_SET_EX_INFORMATION(ctypes.Structure):
# _fields_ = [ ( 'VirtualAddress' , ctypes.c_void_p),
# ( 'VirtualAttributes' , PSAPI_WORKING_SET_EX_BLOCK)]
class PSAPI_WORKING_SET_EX_INFORMATION(ctypes.Structure):
_fields_ = [ ( 'VirtualAddress' , ctypes.c_void_p),
#( 'Flags', ULONG_PTR),
( 'Valid', ULONG_PTR, 1)]
#( 'ShareCount', ULONG_PTR),
#( 'Win32Protection', ULONG_PTR),
#( 'Shared', ULONG_PTR),
#( 'Node', ULONG_PTR),
#( 'Locked', ULONG_PTR),
#( 'LargePage', ULONG_PTR),
#( 'Reserved', ULONG_PTR),
#( 'Bad', ULONG_PTR),
#( 'ReservedUlong', ULONG_PTR)]
#def print_values(self):
# for i,v in self._fields_:
# print(i, getattr(self, i))
# The find_dolphin function is based on WindowsDolphinProcess::findPID() from
# aldelaro5's Dolphin memory engine
# https://github.com/aldelaro5/Dolphin-memory-engine
"""
MIT License
Copyright (c) 2017 aldelaro5
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."""
class Dolphin(object):
def __init__(self):
self.pid = -1
self.memory = None
def reset(self):
self.pid = -1
self.memory = None
def find_dolphin(self, skip_pids=[]):
entry = PROCESSENTRY32()
entry.dwSize = sizeof(PROCESSENTRY32)
snapshot = ctypes.windll.kernel32.CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL)
print(addressof(entry), hex(addressof(entry)))
a = ULONG(addressof(entry))
self.pid = -1
if ctypes.windll.kernel32.Process32First(snapshot, pointer(entry)):
if entry.th32ProcessID not in skip_pids and entry.szExeFile in (b"Dolphin.exe", b"DolphinQt2.exe", b"DolphinWx.exe"):
self.pid = entry.th32ProcessID
else:
while ctypes.windll.kernel32.Process32Next(snapshot, pointer(entry)):
if entry.th32ProcessID in skip_pids:
continue
if entry.szExeFile in (b"Dolphin.exe", b"DolphinQt2.exe", b"DolphinWx.exe"):
self.pid = entry.th32ProcessID
ctypes.windll.kernel32.CloseHandle(snapshot)
if self.pid == -1:
return False
return True
def init_shared_memory(self):
try:
self.memory = shared_memory.SharedMemory('dolphin-emu.'+str(self.pid))
return True
except FileNotFoundError:
return False
def read_ram(self, offset, size):
return self.memory.buf[offset:offset+size]
def write_ram(self, offset, data):
self.memory.buf[offset:offset+len(data)] = data
def read_uint8(self, addr):
assert addr >= 0x80000000
value = self.read_ram(addr-0x80000000, 1)
return unpack(">B", value)[0]
def write_uint8(self, addr, val):
assert addr >= 0x80000000
return self.write_ram(addr - 0x80000000, pack(">B", val))
def read_uint16(self, addr):
assert addr >= 0x80000000
value = self.read_ram(addr-0x80000000, 2)
return unpack(">H", value)[0]
def write_uint16(self, addr, val):
assert addr >= 0x80000000
return self.write_ram(addr - 0x80000000, pack(">H", val))
def read_uint32(self, addr):
assert addr >= 0x80000000
value = self.read_ram(addr-0x80000000, 4)
return unpack(">I", value)[0]
def write_uint32(self, addr, val):
assert addr >= 0x80000000
return self.write_ram(addr - 0x80000000, pack(">I", val))
def read_float(self, addr):
assert addr >= 0x80000000
value = self.read_ram(addr - 0x80000000, 4)
return unpack(">f", value)[0]
def write_float(self, addr, val):
assert addr >= 0x80000000
return self.write_ram(addr - 0x80000000, pack(">f", val))
"""with open("ctypes.txt", "w") as f:
for a in ctypes.__dict__:
f.write(str(a))
f.write("\n")"""
if __name__ == "__main__":
dolphin = Dolphin()
import multiprocessing
if dolphin.find_dolphin():
print("Found Dolphin!")
else:
print("Didn't find Dolphin")
print(dolphin.pid)
dolphin.init_shared_memory()
if dolphin.init_shared_memory():
print("We found MEM1 and/or MEM2!")
else:
print("We didn't find it...")
import random
randint = random.randint
from timeit import default_timer
start = default_timer()
print("Testing Shared Memory Method")
start = default_timer()
count = 500000
for i in range(count):
value = randint(0, 2**32-1)
dolphin.write_uint32(0x80000000, value)
result = dolphin.read_uint32(0x80000000)
assert result == value
diff = default_timer()-start
print(count/diff, "per sec")
print("time: ", diff)

3
requirements.txt Normal file
View file

@ -0,0 +1,3 @@
numpy
tqdm
jupyter