1
0
Fork 0

collision viewer

This commit is contained in:
QbeRoot 2021-10-09 05:59:27 +02:00
parent 4e68daeeb3
commit 83f096b1db
4 changed files with 283 additions and 2 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
__pycache__

View file

@ -1,2 +1,2 @@
# dolphin-memory-lib
A Python library for reading and writing the memory of an emulated game in Dolphin.
# sms-livecol
A live collision viewer for Super Mario Sunshine running on Dolphin.

260
collision.py Normal file
View file

@ -0,0 +1,260 @@
import sys, pyrr
from PyQt5 import QtCore
from PyQt5 import QtGui
from PyQt5 import QtWidgets
from OpenGL.GL import *
from OpenGL.GLUT import *
from OpenGL.GLU import *
from OpenGL.arrays import vbo
from OpenGL.GL import shaders
from numpy import array
from memorylib import Dolphin
class CollisionViewer(QtWidgets.QOpenGLWidget):
gpCamera = 0
gpMapCollisionData = 0
def __init__(self, dolphin: Dolphin, parent=None):
self.dolphin = dolphin
self.parent = parent
QtWidgets.QOpenGLWidget.__init__(self, parent)
self.resize(800, 600)
self.frameSwapped.connect(self.update)
def initializeGL(self) -> None:
glEnable(GL_DEPTH_TEST)
glEnable(GL_CULL_FACE)
glClearColor(0.7, 0.7, 1.0, 0.0)
self.shader = shaders.compileProgram(
shaders.compileShader("""#version 330 core
uniform mat4 projMat;
uniform mat4 viewMat;
layout (location = 0) in vec3 position;
layout (location = 1) in float type;
out vec4 vVertexColor;
void main() {
gl_Position = projMat * viewMat * vec4(position, 1.0);
if (type == 0) {
vVertexColor = vec4(0, 0, 1, 1);
} else if (type == 1) {
vVertexColor = vec4(1, 0, 0, 1);
} else if (type == 2) {
vVertexColor = vec4(0, 1, 0, 1);
} else if (type == 3) {
vVertexColor = vec4(0, 0.5, 0, 1);
} else {
vVertexColor = vec4(0.5, 0.5, 0.5, 1);
}
}""", GL_VERTEX_SHADER),
shaders.compileShader("""#version 330 core
layout(triangles) in;
layout(triangle_strip, max_vertices = 3) out;
in vec4 vVertexColor[3];
out vec3 gTriDistance;
out float gTriSize;
out vec4 gVertexColor;
void main() {
gTriSize = max(max(distance(gl_in[0].gl_Position, gl_in[1].gl_Position),
distance(gl_in[0].gl_Position, gl_in[2].gl_Position)),
distance(gl_in[1].gl_Position, gl_in[2].gl_Position));
gTriDistance = vec3(1, 0, 0);
gVertexColor = vVertexColor[0];
gl_Position = gl_in[0].gl_Position;
EmitVertex();
gTriDistance = vec3(0, 1, 0);
gVertexColor = vVertexColor[1];
gl_Position = gl_in[1].gl_Position;
EmitVertex();
gTriDistance = vec3(0, 0, 1);
gVertexColor = vVertexColor[2];
gl_Position = gl_in[2].gl_Position;
EmitVertex();
EndPrimitive();
}""", GL_GEOMETRY_SHADER),
shaders.compileShader("""#version 330 core
in vec3 gTriDistance;
in float gTriSize;
in vec4 gVertexColor;
out vec4 color;
float amplify(float d, float scale, float offset) {
d = scale * d + offset;
d = clamp(d, 0, 1);
d = 1 - exp2(-2*d*d);
return d;
}
void main() {
float d1 = min(min(gTriDistance.x, gTriDistance.y), gTriDistance.z);
color = smoothstep(0, fwidth(d1), d1) * gVertexColor;
}""", GL_FRAGMENT_SHADER))
self.vao = glGenVertexArrays(1)
def getCheckData(self, checkList):
out = set()
while checkList >= 0x80000000:
checkData = self.dolphin.read_uint32(checkList + 0x8)
if checkData >= 0x80000000:
out.add(checkData)
checkList = self.dolphin.read_uint32(checkList + 0x4)
return out
def paintGL(self) -> None:
if self.gpCamera == 0 or self.gpMapCollisionData == 0:
return
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
camera = self.dolphin.read_uint32(self.gpCamera)
if camera == 0:
return
projMat = pyrr.matrix44.create_perspective_projection_matrix(self.dolphin.read_float(camera + 0x48), self.aspect,
self.dolphin.read_float(camera + 0x28), self.dolphin.read_float(camera + 0x2C))
viewMat = pyrr.matrix44.create_look_at(
[self.dolphin.read_float(camera + 0x124), self.dolphin.read_float(camera + 0x128), self.dolphin.read_float(camera + 0x12C)],
[self.dolphin.read_float(camera + 0x148), self.dolphin.read_float(camera + 0x14C), self.dolphin.read_float(camera + 0x150)],
[self.dolphin.read_float(camera + 0x30), self.dolphin.read_float(camera + 0x34), self.dolphin.read_float(camera + 0x38)])
mapColData = self.dolphin.read_uint32(self.gpMapCollisionData)
if mapColData == 0:
return
checkListCount = self.dolphin.read_uint32(mapColData + 0x10)
checkLists1 = self.dolphin.read_uint32(mapColData + 0x14)
checkLists2 = self.dolphin.read_uint32(mapColData + 0x18)
floors = set()
roofs = set()
walls = set()
for i in range(checkListCount):
if checkLists1 != 0:
floors |= self.getCheckData(self.dolphin.read_uint32(checkLists1 + 0x24 * i + 0x4))
roofs |= self.getCheckData(self.dolphin.read_uint32(checkLists1 + 0x24 * i + 0x10))
walls |= self.getCheckData(self.dolphin.read_uint32(checkLists1 + 0x24 * i + 0x1C))
if checkLists2 != 0:
floors |= self.getCheckData(self.dolphin.read_uint32(checkLists2 + 0x24 * i + 0x4))
roofs |= self.getCheckData(self.dolphin.read_uint32(checkLists2 + 0x24 * i + 0x10))
walls |= self.getCheckData(self.dolphin.read_uint32(checkLists2 + 0x24 * i + 0x1C))
buffer = []
for f in floors:
buffer += [
[self.dolphin.read_float(f + 0x10), self.dolphin.read_float(f + 0x14), self.dolphin.read_float(f + 0x18), 0],
[self.dolphin.read_float(f + 0x1C), self.dolphin.read_float(f + 0x20), self.dolphin.read_float(f + 0x24), 0],
[self.dolphin.read_float(f + 0x28), self.dolphin.read_float(f + 0x2C), self.dolphin.read_float(f + 0x30), 0]
]
for r in roofs:
buffer += [
[self.dolphin.read_float(r + 0x10), self.dolphin.read_float(r + 0x14), self.dolphin.read_float(r + 0x18), 1],
[self.dolphin.read_float(r + 0x1C), self.dolphin.read_float(r + 0x20), self.dolphin.read_float(r + 0x24), 1],
[self.dolphin.read_float(r + 0x28), self.dolphin.read_float(r + 0x2C), self.dolphin.read_float(r + 0x30), 1]
]
for w in walls:
proj = 3 if self.dolphin.read_uint16(w + 0x4) & 0x8 else 2
buffer += [
[self.dolphin.read_float(w + 0x10), self.dolphin.read_float(w + 0x14), self.dolphin.read_float(w + 0x18), proj],
[self.dolphin.read_float(w + 0x1C), self.dolphin.read_float(w + 0x20), self.dolphin.read_float(w + 0x24), proj],
[self.dolphin.read_float(w + 0x28), self.dolphin.read_float(w + 0x2C), self.dolphin.read_float(w + 0x30), proj]
]
glUseProgram(self.shader)
glUniformMatrix4fv(glGetUniformLocation(self.shader, 'projMat'), 1, False, projMat)
glUniformMatrix4fv(glGetUniformLocation(self.shader, 'viewMat'), 1, False, viewMat)
glBindVertexArray(self.vao)
vertexBuffer = vbo.VBO(array(buffer, 'f'))
try:
vertexBuffer.bind()
try:
glEnableVertexAttribArray(0)
glVertexAttribPointer(0, 3, GL_FLOAT, False, 16, vertexBuffer)
glEnableVertexAttribArray(1)
glVertexAttribPointer(1, 1, GL_FLOAT, False, 16, vertexBuffer + 12)
glDrawArrays(GL_TRIANGLES, 0, len(buffer))
finally:
vertexBuffer.unbind()
finally:
glBindVertexArray(0)
glUseProgram(0)
def resizeGL(self, w: int, h: int) -> None:
self.width = w
self.height = h or 1
self.aspect = self.width / self.height
glViewport(0, 0, self.width, self.height)
def connect():
if not dolphin.find_dolphin():
status.showMessage('Dolphin not found')
return
if not dolphin.init_shared_memory():
status.showMessage('MEM1 not found')
return
if dolphin.read_ram(0, 3).tobytes() != b'GMS':
status.showMessage('Current game is not Sunshine')
return
viewer.gpCamera, viewer.gpMapCollisionData = {
0x23: (0x8040B370, 0x8040A578), # JP 1.0
0xA3: (0x8040D0A8, 0x8040DEA0), # NA / KOR
0x41: (0x80404808, 0x80405568), # PAL
0x80: (0x803FFA38, 0x803FED40), # JP 1.1
0x4D: (0x80401D08, 0x80402A68), # 3DAS
}.get(dolphin.read_uint8(0x80365DDD))
status.showMessage('Ready')
if __name__ == '__main__':
dolphin = Dolphin()
app = QtWidgets.QApplication(sys.argv)
window = QtWidgets.QWidget()
layout = QtWidgets.QVBoxLayout(window)
viewer = CollisionViewer(dolphin)
button = QtWidgets.QPushButton('Connect to Dolphin')
status = QtWidgets.QStatusBar()
button.clicked.connect(connect)
layout.addWidget(viewer)
layout.addWidget(button)
layout.addWidget(status)
layout.setStretch(0, 1)
window.setWindowTitle('Super Mario Sunshine Live Collision Viewer')
window.resize(800, 600)
window.show()
try:
sys.exit(app.exec())
except SystemExit:
pass

View file

@ -163,6 +163,26 @@ class Dolphin(object):
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)