Skip to content

Commit

Permalink
Merge pull request #4 from mos9527/unitypy-1.20
Browse files Browse the repository at this point in the history
Migrate to UnityPy 1.20.4+
  • Loading branch information
mos9527 authored Oct 6, 2024
2 parents 7f39195 + c0008bf commit bc2cea3
Show file tree
Hide file tree
Showing 18 changed files with 613 additions and 25 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,6 @@ logs_bin
build
dist
*.egg-info
*.test*
*.test*
.temp
test.py
10 changes: 10 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -169,5 +169,15 @@
],
"justMyCode": true
},
{
"name": "Python: Test File",
"type": "python",
"request": "launch",
"program": "${workspaceFolder}/tests/tests_main.py",
"console": "integratedTerminal",
"justMyCode": false,
"args": [
]
},
]
}
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
install_requires=[
"msgpack",
"pycryptodome",
"unitypy >= 1.10.14, < 1.20",
"unitypy >= 1.20.4",
"wannacri",
"python-json-logger",
"tqdm",
Expand Down
4 changes: 2 additions & 2 deletions sssekai/entrypoint/apphash.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,6 @@ def main_apphash(args):
env.load_file(stream)
for obj in env.objects:
obj = obj.read()
hashStr = HASHREGEX.finditer(obj.raw_data)
hashStr = HASHREGEX.finditer(obj.object_reader.get_raw_data())
for m in hashStr:
print(m.group().decode(), obj.name)
print(m.group().decode(), obj.m_Name)
14 changes: 7 additions & 7 deletions sssekai/entrypoint/live2dextract.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,22 @@ def main_live2dextract(args):
for obj in env.objects:
data = obj.read()
if obj.type in {ClassIDType.MonoBehaviour}:
monobehaviors[data.name] = data
monobehaviors[data.m_Name] = data
if obj.type in {ClassIDType.Texture2D}:
textures[data.name] = data
textures[data.m_Name] = data
if obj.type in {ClassIDType.AnimationClip}:
animations[data.name] = data
animations[data.m_Name] = data
modelData = monobehaviors.get("BuildModelData", None)
if not modelData:
logger.warning("BuildModelData absent. Not extracting Live2D models!")
else:
modelData = modelData.read_typetree()
# modelData = modelData.read_typetree()
# TextAssets are directly extracted
# Usually there are *.moc3, *.model3, *.physics3; the last two should be renamed to *.*.json
for obj in env.objects:
if obj.type == ClassIDType.TextAsset:
data = obj.read()
out_name: str = data.name
out_name: str = data.m_Name
if (
out_name.endswith(".moc3")
or out_name.endswith(".model3")
Expand All @@ -45,9 +45,9 @@ def main_live2dextract(args):
out_name += ".json"
with open(path.join(args.outdir, out_name), "wb") as fout:
logger.info("Extracting Live2D Asset %s" % out_name)
fout.write(data.script)
fout.write(data.m_Script.encode("utf-8", "surrogateescape"))
# Textures always needs conversion and is placed under specific folders
for texture in modelData["TextureNames"]:
for texture in modelData.TextureNames:
name = path.basename(texture)
folder = path.dirname(texture)
out_folder = path.join(args.outdir, folder)
Expand Down
4 changes: 3 additions & 1 deletion sssekai/entrypoint/moc3paths.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ def main_moc3paths(args):
out_name: str = data.name
if out_name.endswith(".moc3"):
parts, parameters = read_moc3(
BytesIO(data.script.tobytes())
BytesIO(
data.m_Script.encode("utf-8", "surrogateescape")
)
)
ParameterNames.update(parameters)
PartNames.update(parts)
Expand Down
2 changes: 1 addition & 1 deletion sssekai/entrypoint/mvdata.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def main_mvdata(args):
data = obj.read()
if data.name == "data":
index = len(mvdata_items)
mvdata_items.append(data.read_typetree())
mvdata_items.append(data) # .read_typetree())
mvdata_lut[str(data.name)] = index
mvdata_lut[str(data.id)] = index
break
Expand Down
2 changes: 1 addition & 1 deletion sssekai/entrypoint/rla2json.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def main_rla2json(args):
for obj in rla_env.objects:
if obj.type in {ClassIDType.TextAsset}:
data = obj.read()
datas[data.name] = data.script.tobytes()
datas[data.m_Name] = data.script.tobytes()
header = datas.get("sekai.rlh", None)
assert header, "RLH Header file not found!"
makedirs(args.outdir, exist_ok=True)
Expand Down
8 changes: 4 additions & 4 deletions sssekai/entrypoint/spineextract.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ def main_spineextract(args):
env = load_assetbundle(f)
objects = [pobj.read() for pobj in env.objects]
binaries = {
obj.name: obj
obj.m_Name: obj
for obj in objects
if getattr(obj, "type", None) in {ClassIDType.TextAsset}
}
textures = {
obj.name: obj
obj.m_Name: obj
for obj in objects
if getattr(obj, "type", None) in {ClassIDType.Texture2D}
}
Expand All @@ -34,7 +34,7 @@ def main_spineextract(args):
if atlas:
logger.info("...has Atlas %s" % spine)
with open(os.path.join(outdir, spine, spine + ".atlas.txt"), "wb") as f:
f.write(atlas.script)
f.write(atlas.m_Script.encode("utf-8", "surrogateescape"))
texfiles = [line.strip() for line in atlas.text.split("\n")]
texfiles = [
".".join(line.split(".")[:-1])
Expand All @@ -59,6 +59,6 @@ def main_spineextract(args):
with open(
os.path.join(outdir, spine, spine + ".skel.bytes"), "wb"
) as f:
f.write(skel.script)
f.write(skel.m_Script.encode("utf-8", "surrogateescape"))
else:
logger.warning("No skel found for %s" % spine)
6 changes: 3 additions & 3 deletions sssekai/entrypoint/usmdemux.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ def main_usmdemux(args):
for obj in env.objects:
if obj.type in {ClassIDType.MonoBehaviour, ClassIDType.TextAsset}:
data = obj.read()
datas[data.name] = data
datas[data.m_Name] = data
movieInfo = datas.get("MovieBundleBuildData", None)
assert movieInfo, "Invalid AssetBundle. No MovieBundleBuildData found!"
movieInfo = movieInfo.read_typetree()
# movieInfo = movieInfo.read_typetree()
usm_name = movieInfo["movieBundleDatas"][0]["usmFileName"][: -len(".bytes")]
logger.info("USM: %s" % usm_name)
usm_folder = path.join(args.outdir, usm_name)
Expand All @@ -27,7 +27,7 @@ def main_usmdemux(args):
for data in movieInfo["movieBundleDatas"]:
usm = data["usmFileName"][: -len(".bytes")]
usm = datas[usm]
usmstream.write(usm.script)
usmstream.write(usm.m_Script.encode("utf-8", "surrogateescape"))
usm = Usm.open(usm_temp, encoding="shift-jis")
usm.demux(path.join(args.outdir, usm_name), usm_name)
remove(usm_temp)
Expand Down
94 changes: 90 additions & 4 deletions sssekai/unity/AnimationClip.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from collections import OrderedDict
from typing import Dict, List, Tuple
from UnityPy.enums import ClassIDType
from UnityPy.classes import AnimationClip
from UnityPy.math import Matrix4x4, Quaternion, Vector3
from UnityPy.classes import AnimationClip, StreamedClip, AnimationClipBindingConstant
from UnityPy.classes.math import Vector3f as Vector3, Quaternionf as Quaternion
from UnityPy.streams.EndianBinaryReader import EndianBinaryReader
from dataclasses import dataclass
from enum import IntEnum
from logging import getLogger
Expand Down Expand Up @@ -67,6 +68,89 @@ def __init__(self) -> None:
self.TransformTracks[TransformType.Scaling] = dict()


# Backported from 47b1bde027fd79d78af3de4d5e3bebd05f8ceeb8
class StreamedCurveKey:
def __init__(self, reader):
self.index = reader.read_int()
self.coeff = reader.read_float_array(4)

self.outSlope = self.coeff[2]
self.value = self.coeff[3]

def CalculateNextInSlope(self, dx: float, rhs):
"""
:param dx: float
:param rhs: StreamedCurvedKey
:return:
"""
# Stepped
if self.coeff[0] == 0 and self.coeff[1] == 0 and self.coeff[2] == 0:
return float("inf")

dx = max(dx, 0.0001)
dy = rhs.value - self.value
length = 1.0 / (dx * dx)
d1 = self.outSlope * dx
d2 = dy + dy + dy - d1 - d1 - self.coeff[1] / length
return d2 / dx


class StreamedFrame:
def __init__(self, reader):
self.time = reader.read_float()
numKeys = reader.read_int()
self.keyList = [StreamedCurveKey(reader) for _ in range(numKeys)]


def StreamedClipReadData(self):
frameList = []
buffer = b"".join(val.to_bytes(4, "big") for val in self.data)
reader = EndianBinaryReader(buffer)
while reader.Position < reader.Length:
frameList.append(StreamedFrame(reader))

for frameIndex in range(2, len(frameList) - 1):
frame = frameList[frameIndex]
for curveKey in frame.keyList:
i = frameIndex - 1
while i >= 0:
preFrame = frameList[i]
try:
preCurveKey = [
x for x in preFrame.keyList if x.index == curveKey.index
][0]
curveKey.inSlope = preCurveKey.CalculateNextInSlope(
frame.time - preFrame.time, curveKey
)
break
except IndexError:
pass
i -= 1
return frameList
StreamedClip.ReadData = StreamedClipReadData

def AnimationClipBindingConstantFindBinding(self, index):
curves = 0
for b in self.genericBindings:
if b.typeID == ClassIDType.Transform: #
switch = b.attribute

if switch in [1, 3, 4]:
# case 1: #kBindTransformPosition
# case 3: #kBindTransformScale
# case 4: #kBindTransformEuler
curves += 3
elif switch == 2: # kBindTransformRotation
curves += 4
else:
curves += 1
else:
curves += 1
if curves > index:
return b
return None
AnimationClipBindingConstant.FindBinding = AnimationClipBindingConstantFindBinding

def read_animation(animationClip: AnimationClip) -> Animation:
"""Reads AnimationClip data and converts it to a list of Tracks
Expand All @@ -76,7 +160,7 @@ def read_animation(animationClip: AnimationClip) -> Animation:
Returns:
List[Track]: List of Tracks
"""
m_Clip = animationClip.m_MuscleClip.m_Clip
m_Clip = animationClip.m_MuscleClip.m_Clip.data
streamedFrames = m_Clip.m_StreamedClip.ReadData()
m_ClipBindingConstant = animationClip.m_ClipBindingConstant
animationTracks = Animation()
Expand Down Expand Up @@ -195,7 +279,9 @@ def get_next_curve_index(current, keyList):
curveIndex = 0
while curveIndex < len(m_ConstantClip.data):
index = streamCount + denseCount + curveIndex
binding = m_ClipBindingConstant.FindBinding(index)
binding = AnimationClipBindingConstant.FindBinding(
m_ClipBindingConstant, index
)
if binding.typeID == ClassIDType.Transform:
transformType = TransformType(binding.attribute)
dimension = DimensionOfTransformType(transformType)
Expand Down
Loading

0 comments on commit bc2cea3

Please sign in to comment.