Hey, guys! So with PS4 games starting to get some exposure, I've decided to start ripping models from Gravity Rush 2. I've had some help over on Xentax, and I've managed to write a Python script that does a pretty good job with Kat/Kitten's many outfits:
(Unfortunately, I don't have the files for the 2B outfit.)
The script works for Raven/Crow too, but I think most models are quite different from the playable characters.
kit00.gfx-kit21.gfx for Kat
cro00.gfx-cro03.gfx for Raven
The script works with Model Researcher Pro. I want to fine-tune it so that there aren't any errors or missing faces, but I also want to try to get the bones, which are in separate files (.mot) and must be assigned weights when the models are loaded in-game. The only problem with that is that I'm not entirely sure how the bone data looks in a hex file, or if I can use Model Researcher to visualize them. I may have to write a Blender script for this sooner than I'd hoped, haha! If you guys know a thing or two about ripping bones and weights, please do tell.
(Unfortunately, I don't have the files for the 2B outfit.)
The script works for Raven/Crow too, but I think most models are quite different from the playable characters.
kit00.gfx-kit21.gfx for Kat
cro00.gfx-cro03.gfx for Raven
The script works with Model Researcher Pro. I want to fine-tune it so that there aren't any errors or missing faces, but I also want to try to get the bones, which are in separate files (.mot) and must be assigned weights when the models are loaded in-game. The only problem with that is that I'm not entirely sure how the bone data looks in a hex file, or if I can use Model Researcher to visualize them. I may have to write a Blender script for this sooner than I'd hoped, haha! If you guys know a thing or two about ripping bones and weights, please do tell.
Code:
import mrp
import random
file = mrp.get_bfile()
vertStart = 0
subMeshes = []
#we gotta create the meshes.
#this should be done as we collect
#the vertices, faces, and uvs.
fileComplete = False
meshCounter = 1
#there are some instances where
#vertex blocks are 36 bytes, not 32.
#vertexType will tell me
#what size I'm processing.
vertexType = 32
while True:
file.seek(30,1)
checkedByte = file.readHalfFloat()
if checkedByte == 1.0 or checkedByte == -1.0:
file.seek(-32,1)
vertStart = file.tell()
break
while fileComplete == False:
print(" ")
print("Begin Mesh" + str(meshCounter).zfill(2))
print("vertexType: " + str(vertexType))
mesh = mrp.create_mesh("Mesh" + str(meshCounter).zfill(2))
verts = []
faces = []
uvs = []
dp = 0x400 #(1024)
meshComplete = False
#set vertex and uv data simultaneously
file.seek(vertStart)
vertCount = 0
print("Vertices: " + file.tellHex())
while meshComplete == False:
#first, confirm that this is a vertex
file.seek(vertexType - 1,1)
checkedByte = file.readByte()
file.seek(-vertexType,1)
file.seek(16,1)
byte40 = file.readShort()
file.seek(20,1)
altByte40 = file.readHalfFloat()
file.seek(-40,1)
if vertexType == 40 and byte40 != 65535 and altByte40 != 0.0:
meshComplete = True
continue
elif vertexType != 40 and checkedByte != 60 and checkedByte != 188:
meshComplete = True
continue
#read vertex
v1,v2,v3 = file.read3Float()
verts.append((v1,v2,v3))
file.seek(8,1) #seek to uv data
u1,u2 = file.read2short()
u1 /= dp
u2 /= dp
uvs.append((u1,-u2))
file.seek(vertexType - 24,1) #seek next vertex
vertCount = vertCount + 1
#end vertex & uv data
print(file.tellHex() + " Count: " + str(vertCount))
padding = True
#loop to reach face data
while padding == True:
#print(file.tellHex())
checkedByte = file.readByte()
if checkedByte != 0:
file.seek(-1,1)
padding = False
#while file.tell() % 32 != 0:
#file.seek(-1,1) #let's hope this doesn't corrupt good face data
#end loop
#now we need to confirm tht we're
#starting at the right place.
while file.tell() % 32 != 0:
file.seek(-1,1)
meshComplete = False
print("Faces: " + file.tellHex())
#face data
#peek 31 (0x1f) bytes ahead.
#if the block is not the end of a vertex,
#step back 31 bytes and read 16 times.
showData = True
finalSubFaces = []
subMeshStart40 = False
simpleSubMesh = False
while meshComplete == False:
#if meshCounter == 78:
#print("Squad")
subMeshStart = False
foundVertex = False
file.seek(30,1)
checkedByte = file.readHalfFloat()
file.seek(-32,1)
file.seek(34,1)
altByte36 = file.readHalfFloat()
file.seek(-36,1)
file.seek(3,1)
vertCheck1 = file.readByte()
file.seek(3,1)
vertCheck2 = file.readByte()
file.seek(3,1)
vertCheck3 = file.readByte()
file.seek(2,1) #14 bytes from start
checkedByte2 = file.readShort()
byte40 = file.readShort()
file.seek(20,1)
altByte40 = file.readHalfFloat()
file.seek(-40,1)
file.seek(17,1)
minorByte28 = file.readByte()
file.seek(8,1) #26 bytes from start
altByte28 = file.readHalfFloat()
file.seek(-28,1)
file.seek(1,1)
faceConfirm1 = file.readByte()
file.seek(1,1)
faceConfirm2 = file.readByte()
file.seek(1,1)
faceConfirm3 = file.readByte()
file.seek(-6,1)
faceConfirmFinal = False
subMeshStartFace = 0
if faceConfirm1 == 0x3c:
if faceConfirm1 == faceConfirm2 and faceConfirm2 == faceConfirm3:
faceConfirmFinal = True
if faceConfirmFinal == False and (file.tell() % 32 == 0) and vertCheck1 >= 0x00 and vertCheck1 <= 0xbf and vertCheck2 >= 0x3b and vertCheck2 <= 0xbf and vertCheck3 >= 0x3b and vertCheck3 <= 0xbf:
if byte40 == 65535 and altByte40 == 0.0:
foundVertex = True
vertexType = 40
#print("vertexType: " + str(vertexType))
elif (altByte36 == 1.0 or altByte36 == -1.0) and (byte40 >= 65278 and byte40 <= 65535 or byte40 == 255):
foundVertex = True
vertexType = 36
#print("vertexType: " + str(vertexType))
elif (altByte28 == 1.0 or altByte28 == -1.0) and (minorByte28 >= 1 and minorByte28 <= 3):
foundVertex = True
vertexType = 28
print("vertexType: " + str(vertexType))
elif (checkedByte == 1.0 or checkedByte == -1.0) and (byte40 >= 255 and byte40 <= 65535 or byte40 == 12): #previously 65278 - 65535
foundVertex = True
vertexType = 32
#print("vertexType: " + str(vertexType))
elif checkedByte2 == 32768 and checkedByte == 0.0 or checkedByte2 == 32768 and checkedByte == 1.875:
#end of compatible mesh data
fileComplete = True
elif subMeshStart40 == True:
#this will be used to make
#subMeshStart true.
subMeshes.append(file.tell())
subMeshStart40 = False
elif simpleSubMesh == True:
subMeshes.append(file.tell())
simpleSubMesh = False
for f in subMeshes:
if file.tell() == f:
subMeshStart = True
subMeshStartFace = f
if subMeshStart == True:
subMeshes.remove(subMeshStartFace)
if subMeshStart == False and foundVertex == False and fileComplete == False:
for i in range(1):
f1,f2,f3 = file.read3Short()
if f1 == 0 and f2 == 0 and f3 == 0:
padding = True
padCount = 0
while padding == True:
#skip 0-bytes
checkedByte = file.readByte()
if checkedByte != 0:
file.seek(-1,1)
padding = False
elif file.tell() % 32 == 0:
#if the first vertex is 0, I don't need to do a full
#type32-vertex check. Instead, I can simply check
#the last half-float. That's enough info to continue the loop.
file.seek(30,1)
checkedByte = file.readHalfFloat()
file.seek(-32,1)
if (checkedByte == 1.0 or checkedByte == -1.0):
#print("possible vert at" + file.tellHex())
break
else:
padCount = padCount + 1
if padCount == 32 - 8:
simpleSubMesh = True
#print(file.tellHex())
if vertexType == 40: #we've found a lowpoly model. submeshes are separated by zeros.
subMeshStart40 = True
#if file.tell() % 32 == 0:
#simpleSubMesh = True
break
elif f1 == 0 and f2 == 0 and file.tell() % 32 != 0:
file.seek(-2,1)
break
faces.append((f1,f2,f3))
#if showData == True and meshCounter == 42:
#print(str(f1) + ", " + str(f2) + ", " + str(f3))
#print(file.tellHex())
#print(i)
else:
meshComplete = True
vertStart = file.tell()
if fileComplete == True:
print("File Complete at Mesh" + str(meshCounter).zfill(2))
elif subMeshStart == True:
#we've encountered a submesh!
meshComplete = False
faceCount = int((len(faces)) / 3)
for i in range(faceCount):
finalSubFaces.append(faces[i])
faces = []
print("Count: " + str(faceCount) + "- Submesh at " + file.tellHex())
#end face data
#make sure to use only the first third of the faces array.
#considering the values involved,
#you divide by 18, not 3
finalFaces = finalSubFaces[:]
faceCount = int((len(faces)) / 3)
for i in range(faceCount):
finalFaces.append(faces[i])
print(file.tellHex() + " Count: " + str(faceCount))
#material
material = mrp.create_material("Material" + str(meshCounter).zfill(2))
material.set_color(random.randint(0, 256), random.randint(0, 256), random.randint(0, 256))
material.set_texture("C:\\Users\\freez\\Pictures\\GR2\\kit01_face_00.bmp", "RGB", True)
#end material
mesh.set_vertices(verts,"ZYX","z","Float")
mesh.set_faces(finalFaces, tp="Short")
mesh.set_uvs(uvs)
mesh.set_material(material)
mrp.view_uvs("Mesh" + str(meshCounter).zfill(2))
mrp.render("Mesh" + str(meshCounter).zfill(2))
#mrp.print_mesh("Mesh" + str(meshCounter).zfill(2))
print("Mesh" + str(meshCounter).zfill(2) + " complete")
meshCounter = meshCounter + 1
#failsafe for infinite loops
if meshCounter == 90:
fileComplete = True
mrp.render("All")