Move scripts
This commit is contained in:
121
scripts/UnityTextModifier.py
Normal file
121
scripts/UnityTextModifier.py
Normal file
@@ -0,0 +1,121 @@
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
import unitypack
|
||||
from unitypack.asset import Asset
|
||||
|
||||
if len(sys.argv) < 4:
|
||||
print("Usage: " + sys.argv[0] + " assetfile.assets edits.json outputfolder\nEdits.json should be an array of objects with the fields 'CurrentEnglish', 'CurrentJapanese', 'NewEnglish', and 'NewJapanese'. An optional 'Discriminator' field can be added if multiple texts have the same English and Japanese values.");
|
||||
exit()
|
||||
|
||||
if not os.path.isdir(sys.argv[3]):
|
||||
print("Output folder " + sys.argv[3] + " must be a directory!")
|
||||
exit()
|
||||
|
||||
class ScriptEdit:
|
||||
def __init__(self, currentEnglish, currentJapanese, newEnglish, newJapanese, discriminator=None):
|
||||
self.currentEnglish = currentEnglish
|
||||
self.currentJapanese = currentJapanese
|
||||
self.newEnglish = newEnglish
|
||||
self.newJapanese = newJapanese
|
||||
self.discriminator = discriminator
|
||||
|
||||
@staticmethod
|
||||
def fromJSON(json):
|
||||
if "Discriminator" in json:
|
||||
discriminator = json["Discriminator"]
|
||||
else:
|
||||
discriminator = None
|
||||
return ScriptEdit(json["CurrentEnglish"], json["CurrentJapanese"], json["NewEnglish"], json["NewJapanese"], discriminator)
|
||||
|
||||
@staticmethod
|
||||
def bytesFromString(string):
|
||||
strBytes = string.encode('utf-8')
|
||||
out = len(strBytes).to_bytes(4, byteorder='little')
|
||||
out += strBytes
|
||||
out += b"\0" * ((4 - len(strBytes)) % 4)
|
||||
return out
|
||||
|
||||
@property
|
||||
def expectedBytes(self):
|
||||
return self.bytesFromString(self.currentEnglish) + self.bytesFromString(self.currentJapanese)
|
||||
|
||||
@property
|
||||
def newBytes(self):
|
||||
return self.bytesFromString(self.newEnglish) + self.bytesFromString(self.newJapanese)
|
||||
|
||||
def findInAssetBundle(self, bundle):
|
||||
search = self.expectedBytes
|
||||
offsets = []
|
||||
start = 0
|
||||
while True:
|
||||
offset = bundle.find(search, start)
|
||||
if offset == -1:
|
||||
break
|
||||
offsets.append(offset)
|
||||
start = offset + 1
|
||||
if len(offsets) == 0:
|
||||
raise IndexError(f"No asset found for {self.currentEnglish} / {self.currentJapanese}")
|
||||
if self.discriminator == None:
|
||||
if len(offsets) > 1:
|
||||
raise IndexError(f"Multiple assets found for {self.currentEnglish} / {self.currentJapanese}, candidates are " + ", ".join(f"{index}: 0x{offset:x}" for index, offset in enumerate(offsets)) + ". Please select one and add a Discriminator tag for it.")
|
||||
self.offset = offsets[0]
|
||||
else:
|
||||
if len(offsets) <= self.discriminator:
|
||||
raise IndexError(f"Not enough offsets found for ${self.currentEnglish} / {self.currentJapanese} to meet request for #{self.discriminator}, there were only {len(offsets)}")
|
||||
self.offset = offsets[self.discriminator]
|
||||
|
||||
def checkObject(self, id, object, bundle):
|
||||
if obj.data_offset <= self.offset and obj.data_offset + obj.size >= self.offset:
|
||||
self.id = id
|
||||
self.currentData = bundle[obj.data_offset:(obj.data_offset + obj.size)]
|
||||
expectedBytes = self.expectedBytes
|
||||
smallOffset = self.currentData.find(expectedBytes)
|
||||
self.newData = self.currentData[:smallOffset] + self.newBytes + self.currentData[(smallOffset + len(expectedBytes)):]
|
||||
print(f"Found {self.currentEnglish} / {self.currentJapanese} in object #{id}")
|
||||
|
||||
def write(self, folder):
|
||||
try:
|
||||
self.newData
|
||||
except:
|
||||
print(f"Failed to find object id for {self.currentEnglish} / {self.currentJapanese}!")
|
||||
return
|
||||
filename = folder + "/" + str(self.id) + ".dat"
|
||||
with open(filename, "wb") as outputFile:
|
||||
outputFile.write(self.newData)
|
||||
|
||||
def __repr__(self):
|
||||
string = f"ScriptEdit(currentEnglish: {self.currentEnglish}, currentJapanese: {self.currentJapanese}, newEnglish: {self.newEnglish}, newJapanese: {self.newJapanese}"
|
||||
if self.discriminator != None:
|
||||
string += f", discriminator: {self.discriminator}"
|
||||
try: string += f", offset: 0x{self.offset:x}"
|
||||
except: pass
|
||||
return string + ")"
|
||||
|
||||
def __str__(self):
|
||||
try: return f"<ScriptEdit for position 0x{self.offset:x}>"
|
||||
except: return "<ScriptEdit for unknown position>"
|
||||
|
||||
|
||||
with open(sys.argv[2], encoding="utf-8") as jsonFile:
|
||||
edits = [ScriptEdit.fromJSON(x) for x in json.load(jsonFile)]
|
||||
|
||||
with open(sys.argv[1], "rb") as assetsFile:
|
||||
bundle = assetsFile.read()
|
||||
newEdits = []
|
||||
for edit in edits:
|
||||
try:
|
||||
edit.findInAssetBundle(bundle)
|
||||
newEdits.append(edit)
|
||||
print(f"Found {edit.currentEnglish} / {edit.currentJapanese} at offset 0x{edit.offset:x}")
|
||||
except IndexError as e:
|
||||
print(e)
|
||||
edits = newEdits
|
||||
|
||||
assetsFile.seek(0)
|
||||
assets = Asset.from_file(assetsFile)
|
||||
for id, obj in assets.objects.items():
|
||||
for edit in edits:
|
||||
edit.checkObject(id, obj, bundle)
|
||||
for edit in edits:
|
||||
edit.write(sys.argv[3])
|
||||
Reference in New Issue
Block a user