Rewrite TMPAssetConverter
Now supports more game file types, and converting between two game files (hopefully)
This commit is contained in:
@@ -2,14 +2,6 @@ import sys
|
|||||||
import os
|
import os
|
||||||
import struct
|
import struct
|
||||||
|
|
||||||
if len(sys.argv) < 5:
|
|
||||||
print("Usage: " + sys.argv[0] + " newAtlas.dat newMonoBehaviour.dat originalMonoBehaviour.dat outputFolder")
|
|
||||||
exit()
|
|
||||||
|
|
||||||
if not os.path.isdir(sys.argv[4]):
|
|
||||||
print("Output folder " + sys.argv[4] + " must be a directory!")
|
|
||||||
exit()
|
|
||||||
|
|
||||||
class DataScanner:
|
class DataScanner:
|
||||||
def __init__(self, data):
|
def __init__(self, data):
|
||||||
self.offset = 0
|
self.offset = 0
|
||||||
@@ -27,103 +19,182 @@ class DataScanner:
|
|||||||
return self.data[self.offset:(self.offset + length)]
|
return self.data[self.offset:(self.offset + length)]
|
||||||
|
|
||||||
def readString(self):
|
def readString(self):
|
||||||
length = int.from_bytes(self.peek(4), byteorder='little')
|
length = int.from_bytes(self.peek(4), byteorder="little")
|
||||||
length += (4 - length) % 4
|
length += (4 - length) % 4
|
||||||
return self.read(length + 4)
|
return self.read(length + 4)
|
||||||
|
|
||||||
def rest(self):
|
def rest(self):
|
||||||
return self.data[self.offset:]
|
return self.data[self.offset:]
|
||||||
|
|
||||||
with open(sys.argv[1], "rb") as atlasFile:
|
def readString(str):
|
||||||
atlas = DataScanner(atlasFile.read())
|
length = int.from_bytes(str[0:4], byteorder="little")
|
||||||
with open(sys.argv[2], "rb") as behaviourFile:
|
return str[4:(4+length)].decode("utf-8")
|
||||||
behaviour = DataScanner(behaviourFile.read())
|
|
||||||
|
|
||||||
# Atlas Editing
|
class FontFile:
|
||||||
atlasOut = b""
|
def __init__(self, data, filename):
|
||||||
atlas.advance(4 * 7)
|
stringLength = int.from_bytes(data[(4 * 15):(4 * 16)], byteorder="little")
|
||||||
name = atlas.readString()
|
if stringLength in range(4, 26): # Assumes filenames are between 4 and 25 chars
|
||||||
atlasOut += name
|
print(f"Detected {filename} as coming from TextMesh Pro")
|
||||||
name = name[4:].decode('utf-8')
|
self.type = "TMP"
|
||||||
atlas.advance(4 * 4)
|
self._fromAssetCreator(data, filename)
|
||||||
atlasOut += atlas.rest()
|
else:
|
||||||
with open(sys.argv[4] + "/" + name + "_Texture2D.dat", "wb") as outFile:
|
stringLength = int.from_bytes(data[(4*12):(4*13)], byteorder="little")
|
||||||
outFile.write(atlasOut)
|
if stringLength == 1:
|
||||||
|
print(f"Detected {filename} as coming from a newer Higurashi game")
|
||||||
|
self.type = "Hima"
|
||||||
|
else:
|
||||||
|
print(f"Detected {filename} as coming from an older Higurashi game")
|
||||||
|
self.type = "Oni"
|
||||||
|
self._fromGame(data, filename)
|
||||||
|
self.filename = readString(self.Filename)
|
||||||
|
|
||||||
# MonoBehaviour editing
|
def _readArray(self, data, itemLength):
|
||||||
behaviourOut = b""
|
self.Array = data.read(4)
|
||||||
with open(sys.argv[3], "rb") as originalFile:
|
length = int.from_bytes(self.Array, byteorder="little")
|
||||||
original = DataScanner(originalFile.read())
|
atlasWidth = struct.unpack("<f", self.AtlasWidth)[0]
|
||||||
behaviourOut += original.read(4 * 7) # Unknown
|
atlasHeight = struct.unpack("<f", self.AtlasHeight)[0]
|
||||||
behaviour.advance(4 * 15) # Unneeded stuff
|
for _ in range(length):
|
||||||
originalName = original.readString()
|
info = bytearray(data.read(4 * 8))
|
||||||
newName = behaviour.readString()
|
if itemLength > 8:
|
||||||
if originalName != newName: # If these names don't match then the atlas's won't either, and we didn't ask for the original atlas file
|
data.advance((itemLength - 8) * 4)
|
||||||
print("Asset names " + originalName[4:].decode("utf-8") + " and " + newName[4:].decode("utf-8") + " don't match, aborting!")
|
x, y = struct.unpack("<ff", info[4:12])
|
||||||
|
if x > atlasWidth:
|
||||||
|
info[4:8] = self.AtlasWidth
|
||||||
|
if y > atlasHeight:
|
||||||
|
info[8:12] = self.AtlasHeight
|
||||||
|
self.Array += info
|
||||||
|
|
||||||
|
def _fromGame(self, data, filename):
|
||||||
|
data = DataScanner(data)
|
||||||
|
self.Header = data.read(4 * 7)
|
||||||
|
self.Filename = data.readString()
|
||||||
|
if self.type == "Hima":
|
||||||
|
self.BeforeFontName = data.read(4)
|
||||||
|
else:
|
||||||
|
self.BeforeFontName = bytes()
|
||||||
|
self.FontName = data.readString()
|
||||||
|
# Font face data
|
||||||
|
self.PointSize = data.read(4)
|
||||||
|
self.Padding = data.read(4)
|
||||||
|
self.LineHeight = data.read(4)
|
||||||
|
self.Baseline = data.read(4)
|
||||||
|
self.Ascender = data.read(4)
|
||||||
|
self.Descender = data.read(4)
|
||||||
|
self.CenterLine = data.read(4)
|
||||||
|
self.SuperscriptOffset = data.read(4)
|
||||||
|
self.SubscriptOffest = data.read(4)
|
||||||
|
self.SubSize = data.read(4)
|
||||||
|
self.Underline = data.read(4)
|
||||||
|
self.UnderlineThickness = data.read(4)
|
||||||
|
self.TabWidth = data.read(4)
|
||||||
|
self.CharacterCount = data.read(4)
|
||||||
|
self.AtlasWidth = data.read(4)
|
||||||
|
self.AtlasHeight = data.read(4)
|
||||||
|
|
||||||
|
self.AfterFontFace = data.read(4 * 8)
|
||||||
|
self._readArray(data, 8)
|
||||||
|
self.Footer = data.rest()
|
||||||
|
|
||||||
|
def _fromAssetCreator(self, data, filename):
|
||||||
|
data = DataScanner(data)
|
||||||
|
self.Header = data.read(4 * 15)
|
||||||
|
self.Filename = data.readString()
|
||||||
|
self.BeforeFontName = data.read(4 * 7)
|
||||||
|
self.FontName = data.readString()
|
||||||
|
# Font face data
|
||||||
|
self.PointSize = data.read(4)
|
||||||
|
self.Scale = data.read(4)
|
||||||
|
self.CharacterCount = data.read(4)
|
||||||
|
self.LineHeight = data.read(4)
|
||||||
|
self.Baseline = data.read(4)
|
||||||
|
self.Ascender = data.read(4)
|
||||||
|
self.CapHeight = data.read(4)
|
||||||
|
self.Descender = data.read(4)
|
||||||
|
self.CenterLine = data.read(4)
|
||||||
|
self.SuperscriptOffset = data.read(4)
|
||||||
|
self.SubscriptOffest = data.read(4)
|
||||||
|
self.SubSize = data.read(4)
|
||||||
|
self.Underline = data.read(4)
|
||||||
|
self.UnderlineThickness = data.read(4)
|
||||||
|
self.strikethrough = data.read(4)
|
||||||
|
self.strikethroughThickness = data.read(4)
|
||||||
|
self.TabWidth = data.read(4)
|
||||||
|
self.Padding = data.read(4)
|
||||||
|
self.AtlasWidth = data.read(4)
|
||||||
|
self.AtlasHeight = data.read(4)
|
||||||
|
|
||||||
|
self.AfterFontFace = data.read(4 * 3)
|
||||||
|
self._readArray(data, 9)
|
||||||
|
self.Footer = data.rest()
|
||||||
|
|
||||||
|
def combineFonts(original: FontFile, new: FontFile):
|
||||||
|
out = bytes()
|
||||||
|
out += original.Header
|
||||||
|
out += original.Filename
|
||||||
|
out += original.BeforeFontName
|
||||||
|
out += new.FontName
|
||||||
|
|
||||||
|
out += new.PointSize
|
||||||
|
out += new.Padding
|
||||||
|
out += new.LineHeight
|
||||||
|
out += new.Baseline
|
||||||
|
out += new.Ascender
|
||||||
|
out += new.Descender
|
||||||
|
out += new.CenterLine
|
||||||
|
out += new.SuperscriptOffset
|
||||||
|
out += new.SubscriptOffest
|
||||||
|
out += new.SubSize
|
||||||
|
out += new.Underline
|
||||||
|
out += new.UnderlineThickness
|
||||||
|
out += new.TabWidth
|
||||||
|
out += new.CharacterCount
|
||||||
|
out += new.AtlasWidth
|
||||||
|
out += new.AtlasHeight
|
||||||
|
|
||||||
|
out += original.AfterFontFace
|
||||||
|
out += new.Array
|
||||||
|
out += original.Footer
|
||||||
|
|
||||||
|
return out
|
||||||
|
|
||||||
|
if len(sys.argv) > 4:
|
||||||
|
atlasFN = sys.argv[1]
|
||||||
|
behaviourFN = sys.argv[2]
|
||||||
|
originalFN = sys.argv[3]
|
||||||
|
outFN = sys.argv[4]
|
||||||
|
else:
|
||||||
|
if len(sys.argv) < 4:
|
||||||
|
print("Usage: " + sys.argv[0] + " [newAtlas.dat] newMonoBehaviour.dat originalMonoBehaviour.dat outputFolder")
|
||||||
|
exit()
|
||||||
|
|
||||||
|
behaviourFN = sys.argv[1]
|
||||||
|
originalFN = sys.argv[2]
|
||||||
|
outFN = sys.argv[3]
|
||||||
|
|
||||||
|
if not os.path.isdir(outFN):
|
||||||
|
print("Output folder " + outFN + " must be a directory!")
|
||||||
exit()
|
exit()
|
||||||
behaviourOut += newName
|
|
||||||
behaviour.advance(4 * 6) # Unneeded stuff
|
with open(originalFN, "rb") as originalFile:
|
||||||
original.advance(4) # Will be read from behaviour
|
original = FontFile(originalFile.read(), originalFN)
|
||||||
behaviourOut += behaviour.read(4)
|
with open(behaviourFN, "rb") as behaviourFile:
|
||||||
original.readString() # Font name, will be gotten from behaviour
|
behaviour = FontFile(behaviourFile.read(), behaviourFN)
|
||||||
behaviourOut += behaviour.readString() # Font name
|
if len(sys.argv) > 4:
|
||||||
original.advance(4 * 16) # Font face data, will be read and converted from behaviour
|
with open(atlasFN, "rb") as atlasFile:
|
||||||
# Read font face data, is in a completely different order from the target
|
atlas = DataScanner(atlasFile.read())
|
||||||
PointSize = behaviour.read(4)
|
atlasOut = b""
|
||||||
Scale = behaviour.read(4)
|
if int.from_bytes(atlas.peek(4), byteorder="little") not in range(8, 32):
|
||||||
CharacterCount = behaviour.read(4)
|
atlas.advance(4 * 7)
|
||||||
LineHeight = behaviour.read(4)
|
atlasName = atlas.readString()
|
||||||
Baseline = behaviour.read(4)
|
atlas.advance(4 * 4)
|
||||||
Ascender = behaviour.read(4)
|
else:
|
||||||
CapHeight = behaviour.read(4)
|
atlasName = atlas.readString()
|
||||||
Descender = behaviour.read(4)
|
atlasOut += atlasName
|
||||||
CenterLine = behaviour.read(4)
|
atlasOut += atlas.rest()
|
||||||
SuperscriptOffset = behaviour.read(4)
|
|
||||||
SubscriptOffest = behaviour.read(4)
|
atlasName = readString(atlasName)
|
||||||
SubSize = behaviour.read(4)
|
with open(outFN + "/" + atlasName + "_Texture2D.dat", "wb") as outFile:
|
||||||
Underline = behaviour.read(4)
|
outFile.write(atlasOut)
|
||||||
UnderlineThickness = behaviour.read(4)
|
with open(outFN + "/" + original.filename + "_TextMeshProFont.dat", "wb") as outFile:
|
||||||
strikethrough = behaviour.read(4)
|
outFile.write(combineFonts(original=original, new=behaviour))
|
||||||
strikethroughThickness = behaviour.read(4)
|
|
||||||
TabWidth = behaviour.read(4)
|
|
||||||
Padding = behaviour.read(4)
|
|
||||||
AtlasWidth = behaviour.read(4)
|
|
||||||
AtlasHeight = behaviour.read(4)
|
|
||||||
# Write font face data
|
|
||||||
behaviourOut += PointSize
|
|
||||||
behaviourOut += Padding
|
|
||||||
behaviourOut += LineHeight
|
|
||||||
behaviourOut += Baseline
|
|
||||||
behaviourOut += Ascender
|
|
||||||
behaviourOut += Descender
|
|
||||||
behaviourOut += CenterLine
|
|
||||||
behaviourOut += SuperscriptOffset
|
|
||||||
behaviourOut += SubscriptOffest
|
|
||||||
behaviourOut += SubSize
|
|
||||||
behaviourOut += Underline
|
|
||||||
behaviourOut += UnderlineThickness
|
|
||||||
behaviourOut += TabWidth
|
|
||||||
behaviourOut += CharacterCount
|
|
||||||
behaviourOut += AtlasWidth
|
|
||||||
behaviourOut += AtlasHeight
|
|
||||||
behaviour.advance(4 * 3) # Will read this from original
|
|
||||||
behaviourOut += original.read(4 * 8) # Other data
|
|
||||||
behaviourOut += behaviour.peek(4) # Char info array length
|
|
||||||
originalArrayLength = int.from_bytes(original.read(4), byteorder='little')
|
|
||||||
newArrayLength = int.from_bytes(behaviour.read(4), byteorder='little')
|
|
||||||
original.advance(originalArrayLength * 4 * 8) # We don't need this data
|
|
||||||
atlasWidth = struct.unpack("<f", AtlasWidth)[0]
|
|
||||||
atlasHeight = struct.unpack("<f", AtlasHeight)[0]
|
|
||||||
for i in range(newArrayLength):
|
|
||||||
info = bytearray(behaviour.read(4 * 8))
|
|
||||||
x, y = struct.unpack("<ff", info[4:12])
|
|
||||||
if x > atlasWidth:
|
|
||||||
info[4:8] = AtlasWidth
|
|
||||||
if y > atlasHeight:
|
|
||||||
info[8:12] = AtlasHeight
|
|
||||||
behaviourOut += info
|
|
||||||
behaviour.advance(4) # One field is only in the new data so we need to remove it
|
|
||||||
behaviourOut += original.rest() # Rest of file can be copied from the original
|
|
||||||
with open(sys.argv[4] + "/" + os.path.basename(sys.argv[3]), "wb") as outFile:
|
|
||||||
outFile.write(behaviourOut)
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user