Skip to content

Instantly share code, notes, and snippets.

@uyjulian
Last active January 1, 2020 20:40
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save uyjulian/bc208b17a3e269449ed27dc0041ea1bb to your computer and use it in GitHub Desktop.
Save uyjulian/bc208b17a3e269449ed27dc0041ea1bb to your computer and use it in GitHub Desktop.
fu = require"file_util"
struct = require"struct"
serpent = require"serpent"
bit = require"bit"
ffi = require"ffi"
in1 = fu.readfile(arg[1])
header, b = struct.unpack("c4", in1)
if header == "RYHP"
error("PhyreEngine little endian not supported")
if header ~= "PHYR"
error("not a PhyreEngine serialized file")
tableStructUnpack = (tbl, input, b, extra) ->
ttbl = {}
for i, v in pairs(tbl)
ttbl[v[1]], b = struct.unpack(extra .. v[2], input, b)
return ttbl, b
clusterHeaderGCM_struct = {
{"size", "I4"}
{"packedNamespaceSize", "I4"}
{"platformID", "c4"}
{"instanceListCount", "I4"}
{"arrayFixupSize", "I4"}
{"arrayFixupCount", "I4"}
{"pointerFixupSize", "I4"}
{"pointerFixupCount", "I4"}
{"pointerArrayFixupSize", "I4"}
{"pointerArrayFixupCount", "I4"}
{"pointersInArraysCount", "I4"}
{"userFixupCount", "I4"}
{"userFixupDataSize", "I4"}
{"totalDataSize", "I4"}
{"headerClassInstanceCount", "I4"}
{"headerClassChildCount", "I4"}
{"physicsEngineID", "I4"}
{"vramBufferSize", "I4"}
{"vramBufferAlignment", "I4"}
{"hostBufferSize", "I4"}
{"hostBufferAlignment", "I4"}
}
clusterHeaderGCM, b = tableStructUnpack(clusterHeaderGCM_struct, in1, b, ">")
if clusterHeaderGCM.platformID ~= "GCM\0"
error("PhyreEngine Platform ID " .. clusterHeaderGCM.platformID\gsub("%z", "") .. " not supported")
-- if clusterHeaderGCM.pointersInArraysCount ~= 0
-- error("unsupported feature(s) used")
--PackedNamespace
namespacePacked_struct = {
{"header", "c4"}
{"size", "I4"}
{"typeCount", "I4"}
{"classCount", "I4"}
{"classDataMemberCount", "I4"}
{"stringTableSize", "I4"}
{"defaultBufferCount", "I4"}
{"defaultBufferSize", "I4"}
}
namespacePacked, b = tableStructUnpack(namespacePacked_struct, in1, b, ">")
if namespacePacked.header ~= "\001\002\003\004"
error("wrong NamespacePacked header")
--PackedType
packedType_struct = {
{"nameOffset", "I4"}
}
packedTypes = {}
for i = 1, namespacePacked.typeCount
packedType, b = tableStructUnpack(packedType_struct, in1, b, ">")
table.insert(packedTypes, packedType) --we'll fill these in later from the string table
--PackedClassDescriptors
packedClassDescriptor_struct = {
{"superClassID", "I4"}
--{"sizeInBytesAndAlignment", "I4"} --size
--{"sizeInBytesAndAlignment", "I4"} --alignment
{"sizeInAlignment", "I2"}
{"sizeInBytes", "I2"}
{"nameOffset", "I4"}
{"classDataMemberCount", "I4"}
{"offsetFromParent", "i4"}
{"offsetToBase", "i4"}
{"offsetToBaseInAllocatedBlock", "i4"}
{"flags", "I4"}
{"defaultBufferOffset", "I4"}
}
packedClassDescriptors = {}
for i = 1, namespacePacked.classCount
packedClassDescriptor, b = tableStructUnpack(packedClassDescriptor_struct, in1, b, ">")
table.insert(packedClassDescriptors, packedClassDescriptor)
--PackedDataMember
packedDataMember_sturct = {
{"nameOffset", "I4"}
{"typeID", "I4"}
{"valueOffset", "I4"}
{"sizeInBytes", "I4"}
{"flags", "I4"}
{"fixedArraySize", "I4"}
}
packedDataMembers = {}
for i = 1, namespacePacked.classDataMemberCount
packedDataMember, b = tableStructUnpack(packedDataMember_sturct, in1, b, ">")
table.insert(packedDataMembers, packedDataMember)
--fill in from string table
for i, v in pairs(packedTypes)
v.name = struct.unpack("s", in1, b + v.nameOffset)
for i, v in pairs(packedClassDescriptors)
v.name = struct.unpack("s", in1, b + v.nameOffset)
for i, v in pairs(packedDataMembers)
v.name = struct.unpack("s", in1, b + v.nameOffset)
v.typeID_name = ((#packedTypes < v.typeID) and packedClassDescriptors or packedTypes)[(#packedTypes < v.typeID) and (v.typeID - #packedTypes) or (v.typeID + 1)].name
b += namespacePacked.stringTableSize
--InstanceListHeader
instanceListHeader_struct = {
{"classID", "I4"}
{"count", "I4"}
{"size", "I4"}
{"objectsSize", "I4"}
{"arraysSize", "I4"}
{"pointersInArraysCount", "I4"}
{"arrayFixupCount", "I4"}
{"pointerFixupCount", "I4"}
{"pointerArrayFixupCount", "I4"}
}
instanceListHeaders = {}
for i = 1, clusterHeaderGCM.instanceListCount
instanceListHeader, b = tableStructUnpack(instanceListHeader_struct, in1, b, ">")
table.insert(instanceListHeaders, instanceListHeader)
type2struct_struct = {
PUInt32: "I4"
PChar: "i1"
PUInt8: "I1"
PInt32: "i4"
PUInt16: "I2"
}
for i, v in pairs(packedDataMembers)
if v.flags == 8 or v.flags == 0
if type2struct_struct[v.typeID_name] ~= nil
v.value = (struct.unpack(">" .. type2struct_struct[v.typeID_name], in1, 1 + v.valueOffset))
--read object data
for i, v in pairs(instanceListHeaders)
obj_data = ""
array_data = ""
if v.objectsSize ~= 0
obj_data, b = struct.unpack("c" .. v.objectsSize, in1, b)
if v.arraysSize ~= 0
array_data, b = struct.unpack("c" .. v.arraysSize, in1, b)
v.objdata = obj_data
v.arraydata = array_data
v.classID_name = packedClassDescriptors[v.classID].name
userFixupDataOffs = b
b += clusterHeaderGCM.userFixupDataSize
--user fixup
userFixup_struct = {
{"typeID", "I4"}
{"size", "I4"}
{"offset", "I4"}
}
userFixups = {}
for i = 1, clusterHeaderGCM.userFixupCount
userFixup, b = tableStructUnpack(userFixup_struct, in1, b, ">")
table.insert(userFixups, userFixup)
for i, v in pairs(userFixups)
v.typeID_name = ((#packedTypes < v.typeID) and packedClassDescriptors or packedTypes)[(#packedTypes < v.typeID) and (v.typeID - #packedTypes) or (v.typeID + 1)].name
v.data = struct.unpack("c" .. v.size, in1, userFixupDataOffs + v.offset)
--TODO: header class instance/child counts, pointer array fixup
for i = 1, clusterHeaderGCM.headerClassInstanceCount
b += 4
for i = 1, clusterHeaderGCM.headerClassChildCount
b += 16
b += clusterHeaderGCM.pointerArrayFixupSize --I have no idea how to deal with pointer-array fixups right now
b += clusterHeaderGCM.pointerFixupSize -- I have no idea how to deal with object pointers fixups right now
b += clusterHeaderGCM.arrayFixupSize -- I have no idea how to deal with data pointers fixups right now
--The actual data starts here
--Let's dump this into an actual .dds
format = ""
for i, v in pairs(userFixups)
if v.data == "DXT1\0"
format = "DXT1"
break
elseif v.data == "DXT3\0"
format = "DXT3"
break
elseif v.data == "DXT5\0"
format = "DXT5"
break
elseif v.data == "LA8\0"
format = "LA8"
break
elseif v.data == "L8\0"
format = "L8"
break
elseif v.data == "ARGB8\0"
format = "ARGB8"
break
elseif v.data == "RGBA8\0"
format = "RGBA8"
break
elseif v.data == "ARGB4444\0"
format = "ARGB4444"
break
elseif v.data == "RGB565\0"
format = "RGB565"
break
if format == ""
print(arg[1])
error("unknown format")
height = 0
width = 0
origfilename = ""
for i, v in pairs(instanceListHeaders)
if v.classID_name == "PTexture2D"
if height ~= 0 or width ~= 0
continue
k = 1
k += 28 -- let's get straight to the height width
width, height, k = struct.unpack(">I4I4", v.objdata, k)
elseif v.classID_name == "PAssetReference"
if origfilename ~= ""
continue
origfilename = v.arraydata\gsub("%z", "")
if origfilename == ""
print(arg[1])
error"invalid filename"
if origfilename\sub(-3, -1) ~= "png" and origfilename\sub(-3, -1) ~= "dds" and origfilename\sub(-3, -1) ~= "DDS" and origfilename\sub(-3, -1) ~= "bmp"
print(arg[1])
error"invalid origfilename"
if height == 0 or width == 0
print(arg[1])
error"invalid dims"
ddsHeader = ""
if format == "LA8"
ddsHeader = struct.pack("<c4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4", "DDS\032", 124, bit.bor(0x1, 0x2, 0x4, 0x1000, 0x8), height, width, (width * 16 + 7) / 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, bit.bor(0x1, 0x40), 0, 16, 0x00FF, 0x00FF, 0x00FF, 0xFF00, 0x1000, 0, 0, 0, 0)
elseif format == "L8"
ddsHeader = struct.pack("<c4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4", "DDS\032", 124, bit.bor(0x1, 0x2, 0x4, 0x1000, 0x8), height, width, (width * 8 + 7) / 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, bit.bor(0x40), 0, 8, 0x00FF, 0x00FF, 0x00FF, 0x0000, 0x1000, 0, 0, 0, 0)
elseif format == "ARGB8"
ddsHeader = struct.pack("<c4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4", "DDS\032", 124, bit.bor(0x1, 0x2, 0x4, 0x1000, 0x8), height, width, (width * 32 + 7) / 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, bit.bor(0x1, 0x40), 0, 32, 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000, 0x1000, 0, 0, 0, 0)
elseif format == "RGBA8"
ddsHeader = struct.pack("<c4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4", "DDS\032", 124, bit.bor(0x1, 0x2, 0x4, 0x1000, 0x8), height, width, (width * 32 + 7) / 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, bit.bor(0x1, 0x40), 0, 32, 0xFF000000, 0x00FF0000, 0x0000FF00, 0x000000FF, 0x1000, 0, 0, 0, 0)
elseif format == "RGB565"
ddsHeader = struct.pack("<c4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4", "DDS\032", 124, bit.bor(0x1, 0x2, 0x4, 0x1000, 0x8), height, width, (width * 16 + 7) / 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, bit.bor(0x40), 0, 16, 0x0000F800, 0x000007E0, 0x0000001F, 0x00000000, 0x1000, 0, 0, 0, 0)
elseif format == "ARGB4444"
ddsHeader = struct.pack("<c4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4", "DDS\032", 124, bit.bor(0x1, 0x2, 0x4, 0x1000, 0x8), height, width, (width * 16 + 7) / 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, bit.bor(0x1, 0x40), 0, 16, 0x00000F00, 0x000000F0, 0x0000000F, 0x0000F000, 0x1000, 0, 0, 0, 0)
else
ddsHeader = struct.pack("<c4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4c4I4I4I4I4I4I4I4I4I4I4", "DDS\032", 124, bit.bor(0x1, 0x2, 0x4, 0x1000, 0x80000), height, width, math.max(1, (width + 3) / 4) * ((format == "DXT1") and 8 or 16), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0x4, format, 0, 0, 0, 0, 0, 0x1000, 0, 0, 0, 0)
--unswizzle if needed
isPowerOfTwo = (x) ->
if x == 0
return false
if x == 1 --HACK: 1 is actually a 0th power of 2, but this causes the unswizzle code to be stuck in a loop
return false
return bit.band(x, x - 1) == 0
xy = true
raw_data = in1\sub(b, b + clusterHeaderGCM.vramBufferSize - 1)
if (format\sub(1,3) ~= "DXT") and (isPowerOfTwo(height) and isPowerOfTwo(width))
out_arr = {}
in_arr = {}
raw_data_tbl = {}
out_tbl = {}
readoffset = 1
datawidthm = (((format == "ARGB8") or (format == "RGBA8")) and 4) or ((format == "L8") and 1) or 2
unswizzle_tasks = {}
unswizzle = (outtbl, intbl, writeoffset, segsize, datawidth) ->
if segsize == 2
if xy
outtbl[writeoffset ] = intbl[readoffset]
readoffset += 1
outtbl[writeoffset + 1] = intbl[readoffset]
readoffset += 1
outtbl[writeoffset + datawidth ] = intbl[readoffset]
readoffset += 1
outtbl[writeoffset + datawidth + 1] = intbl[readoffset]
readoffset += 1
else
outtbl[writeoffset ] = intbl[readoffset]
readoffset += 1
outtbl[writeoffset + datawidth ] = intbl[readoffset]
readoffset += 1
outtbl[writeoffset + 1] = intbl[readoffset]
readoffset += 1
outtbl[writeoffset + datawidth + 1] = intbl[readoffset]
readoffset += 1
else
if xy
table.insert(unswizzle_tasks, {outtbl, intbl, writeoffset, segsize / 2, datawidth})
table.insert(unswizzle_tasks, {outtbl, intbl, writeoffset + segsize / 2, segsize / 2, datawidth})
table.insert(unswizzle_tasks, {outtbl, intbl, writeoffset + datawidth * (segsize / 2), segsize / 2, datawidth})
table.insert(unswizzle_tasks, {outtbl, intbl, writeoffset + datawidth * (segsize / 2) + segsize / 2, segsize / 2, datawidth})
else
table.insert(unswizzle_tasks, {outtbl, intbl, writeoffset, segsize / 2, datawidth})
table.insert(unswizzle_tasks, {outtbl, intbl, writeoffset + datawidth * (segsize / 2), segsize / 2, datawidth})
table.insert(unswizzle_tasks, {outtbl, intbl, writeoffset + segsize / 2, segsize / 2, datawidth})
table.insert(unswizzle_tasks, {outtbl, intbl, writeoffset + datawidth * (segsize / 2) + segsize / 2, segsize / 2, datawidth})
gshub = (s) ->
table.insert(raw_data_tbl, s)
return false
raw_data\gsub("."\rep(datawidthm), gshub)
if width == height
table.insert(unswizzle_tasks, {out_tbl, raw_data_tbl, 1, width, width})
elseif width > height
for w = 1, width, height
table.insert(unswizzle_tasks, {out_tbl, raw_data_tbl, w, height, width})
elseif height > width
for h = 1, (height * width), width ^ 2
table.insert(unswizzle_tasks, {out_tbl, raw_data_tbl, h, width, width})
keep_swizzling = true
while keep_swizzling
keep_swizzling = false
to_work = unswizzle_tasks
unswizzle_tasks = {}
for i, v in pairs(to_work)
keep_swizzling = true
unswizzle(unpack(v))
out_data = table.concat(out_tbl)
--deal with mipmaps later
--if #out_data ~= #raw_data
-- print(serpent.block({out_data, raw_data}))
-- print(arg[1])
-- print(#out_data, #raw_data, #out_tbl, #raw_data_tbl, readoffset, height, width, clusterHeaderGCM.vramBufferSize)
-- error("unswizzle mismatch length")
raw_data = out_data
--do we need a byteswap?
if (format == "ARGB4444") or (format == "RGB565")
gshub = (s) ->
return s\sub(2,2) .. s\sub(1,1)
raw_data = raw_data\gsub("..", gshub)
elseif format == "RGBA8"
gshub = (s) ->
return s\sub(3,3) .. s\sub(2,2) .. s\sub(1,1) .. s\sub(4,4)
raw_data = raw_data\gsub("....", gshub)
--now paste the header and content together
os.execute(fu.shellargs("mkdir", "-p", fu.dirname(origfilename)))
fu.writefile(origfilename\sub(1, -5) .. ".dds", ddsHeader .. raw_data)
--Let's convert into .dae! (well, not yet)
print(serpent.block({header, clusterHeaderGCM, namespacePacked, packedTypes, packedClassDescriptors, packedDataMembers, instanceListHeaders, userFixups}))
print(b)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment