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