init
This commit is contained in:
commit
56d0ac2064
5 changed files with 886 additions and 0 deletions
22
LICENSE
Normal file
22
LICENSE
Normal 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
18
README.md
Normal 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
590
main.ipynb
Normal 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
253
memorylib.py
Normal 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
3
requirements.txt
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
numpy
|
||||||
|
tqdm
|
||||||
|
jupyter
|
Reference in a new issue