11import argparse
2- import os , io , json
2+ import os , io , json , importlib
33from zlib import crc32
44import UnityPy
55from typing import TypeVar
2121# TypeTreeHelper.read_typetree_boost = False
2222logger = getLogger ("UnityPyLive2DExtractor" )
2323
24+ from sssekai .fmt .motion3 import to_motion3
25+ from sssekai .fmt .moc3 import read_moc3
26+ from sssekai .unity .AnimationClip import AnimationHelper
27+
2428from UnityPyLive2DExtractor import __version__
25- from UnityPyLive2DExtractor .generated import TYPETREE_DEFS
29+
30+ # These are here ONLY for the sake of type hints
2631from UnityPyLive2DExtractor .generated .Live2D .Cubism .Core import CubismModel
2732from UnityPyLive2DExtractor .generated .Live2D .Cubism .Rendering import CubismRenderer
2833from UnityPyLive2DExtractor .generated .Live2D .Cubism .Framework .Physics import (
3540 CubismPhysicsRig ,
3641 CubismPhysicsController ,
3742)
38- from sssekai .fmt .motion3 import to_motion3
39- from sssekai .fmt .moc3 import read_moc3
40- from sssekai .unity .AnimationClip import AnimationHelper
41-
4243
43- def monkey_patch (cls ):
44- """ooh ooh aah aah"""
45-
46- def wrapper (func ):
47- setattr (cls , func .__name__ , func )
48- return func
44+ from dataclasses import dataclass
4945
50- return wrapper
5146
47+ @dataclass
48+ class ExtractorConfig :
49+ generated_module : str = "UnityPyLive2DExtractor.generated"
5250
53- @monkey_patch (CubismPhysicsNormalizationTuplet )
54- def dump (self : CubismPhysicsNormalizationTuplet ):
55- return {
56- "Maximum" : self .Maximum ,
57- "Minimum" : self .Minimum ,
58- "Default" : self .Default ,
59- }
51+ @property
52+ def module (self ):
53+ return __import__ (self .generated_module )
6054
55+ def import_path (self , fullname : str ):
56+ namespace = "." .join (fullname .split ("." )[:- 1 ])
57+ classname = fullname .split ("." )[- 1 ]
58+ cls = importlib .import_module (f".{ namespace } " , package = self .generated_module )
59+ cls = getattr (cls , classname )
60+ return cls
6161
62- @monkey_patch (CubismPhysicsNormalization )
63- def dump (self : CubismPhysicsNormalization ):
64- return {"Position" : self .Position .dump (), "Angle" : self .Angle .dump ()}
6562
63+ CFG = ExtractorConfig ()
6664
67- @monkey_patch (CubismPhysicsParticle )
68- def dump (self : CubismPhysicsParticle ):
69- return {
70- "Position" : {"X" : self .InitialPosition .x , "Y" : self .InitialPosition .y },
71- "Mobility" : self .Mobility ,
72- "Delay" : self .Delay ,
73- "Acceleration" : self .Acceleration ,
74- "Radius" : self .Radius ,
75- }
7665
66+ def patch_all ():
67+ def monkey_patch (fullname ):
68+ """ooh ooh aah aah"""
69+ cls = CFG .import_path (fullname )
7770
78- @monkey_patch (CubismPhysicsOutput )
79- def dump (self : CubismPhysicsOutput ):
80- return {
81- "Destination" : {"Target" : "Parameter" , "Id" : self .DestinationId },
82- "VertexIndex" : self .ParticleIndex ,
83- "Scale" : self .AngleScale ,
84- "Weight" : self .Weight ,
85- "Type" : ["X" , "Y" , "Angle" ][self .SourceComponent ],
86- "Reflect" : self .IsInverted ,
87- }
71+ def wrapper (func ):
72+ setattr (cls , func .__name__ , func )
73+ return func
8874
75+ return wrapper
8976
90- @monkey_patch (CubismPhysicsInput )
91- def dump (self : CubismPhysicsInput ):
92- return {
93- "Source" : {"Target" : "Parameter" , "Id" : self .SourceId },
94- "Weight" : self .Weight ,
95- "Type" : ["X" , "Y" , "Angle" ][self .SourceComponent ],
96- "Reflect" : self .IsInverted ,
97- }
77+ @monkey_patch ("Live2D.Cubism.Framework.Physics.CubismPhysicsNormalizationTuplet" )
78+ def dump (self : CubismPhysicsNormalizationTuplet ):
79+ return {
80+ "Maximum" : self .Maximum ,
81+ "Minimum" : self .Minimum ,
82+ "Default" : self .Default ,
83+ }
9884
85+ @monkey_patch ("Live2D.Cubism.Framework.Physics.CubismPhysicsNormalization" )
86+ def dump (self : CubismPhysicsNormalization ):
87+ return {"Position" : self .Position .dump (), "Angle" : self .Angle .dump ()}
88+
89+ @monkey_patch ("Live2D.Cubism.Framework.Physics.CubismPhysicsParticle" )
90+ def dump (self : CubismPhysicsParticle ):
91+ return {
92+ "Position" : {"X" : self .InitialPosition .x , "Y" : self .InitialPosition .y },
93+ "Mobility" : self .Mobility ,
94+ "Delay" : self .Delay ,
95+ "Acceleration" : self .Acceleration ,
96+ "Radius" : self .Radius ,
97+ }
9998
100- @monkey_patch (CubismPhysicsSubRig )
101- def dump (self : CubismPhysicsSubRig ):
102- return {
103- "Input" : [x .dump () for x in self .Input ],
104- "Output" : [x .dump () for x in self .Output ],
105- "Vertices" : [x .dump () for x in self .Particles ],
106- "Normalization" : self .Normalization .dump (),
107- }
99+ @monkey_patch ("Live2D.Cubism.Framework.Physics.CubismPhysicsOutput" )
100+ def dump (self : CubismPhysicsOutput ):
101+ return {
102+ "Destination" : {"Target" : "Parameter" , "Id" : self .DestinationId },
103+ "VertexIndex" : self .ParticleIndex ,
104+ "Scale" : self .AngleScale ,
105+ "Weight" : self .Weight ,
106+ "Type" : ["X" , "Y" , "Angle" ][self .SourceComponent ],
107+ "Reflect" : self .IsInverted ,
108+ }
108109
110+ @monkey_patch ("Live2D.Cubism.Framework.Physics.CubismPhysicsInput" )
111+ def dump (self : CubismPhysicsInput ):
112+ return {
113+ "Source" : {"Target" : "Parameter" , "Id" : self .SourceId },
114+ "Weight" : self .Weight ,
115+ "Type" : ["X" , "Y" , "Angle" ][self .SourceComponent ],
116+ "Reflect" : self .IsInverted ,
117+ }
109118
110- @monkey_patch (CubismPhysicsRig )
111- def dump (self : CubismPhysicsRig ):
112- return [
113- {"Id" : "PhysicsSetting%d" % (i + 1 ), ** rig .dump ()}
114- for i , rig in enumerate (self .SubRigs )
115- ]
119+ @monkey_patch ("Live2D.Cubism.Framework.Physics.CubismPhysicsSubRig" )
120+ def dump (self : CubismPhysicsSubRig ):
121+ return {
122+ "Input" : [x .dump () for x in self .Input ],
123+ "Output" : [x .dump () for x in self .Output ],
124+ "Vertices" : [x .dump () for x in self .Particles ],
125+ "Normalization" : self .Normalization .dump (),
126+ }
116127
128+ @monkey_patch ("Live2D.Cubism.Framework.Physics.CubismPhysicsRig" )
129+ def dump (self : CubismPhysicsRig ):
130+ return [
131+ {"Id" : "PhysicsSetting%d" % (i + 1 ), ** rig .dump ()}
132+ for i , rig in enumerate (self .SubRigs )
133+ ]
117134
118- @monkey_patch (CubismPhysicsController )
119- def dump (self : CubismPhysicsController ):
120- return {
121- "Version" : 3 ,
122- "Meta" : {
123- "PhysicsSettingCount" : len (self ._rig .SubRigs ),
124- "TotalInputCount" : sum ((len (x .Input ) for x in self ._rig .SubRigs )),
125- "TotalOutputCount" : sum ((len (x .Output ) for x in self ._rig .SubRigs )),
126- "VertexCount" : sum ((len (x .Particles ) for x in self ._rig .SubRigs )),
127- "Fps" : 60 ,
128- "EffectiveForces" : {
129- "Gravity" : {"X" : 0 , "Y" : - 1 },
130- "Wind" : {"X" : 0 , "Y" : 0 },
135+ @monkey_patch ("Live2D.Cubism.Framework.Physics.CubismPhysicsController" )
136+ def dump (self : CubismPhysicsController ):
137+ return {
138+ "Version" : 3 ,
139+ "Meta" : {
140+ "PhysicsSettingCount" : len (self ._rig .SubRigs ),
141+ "TotalInputCount" : sum ((len (x .Input ) for x in self ._rig .SubRigs )),
142+ "TotalOutputCount" : sum ((len (x .Output ) for x in self ._rig .SubRigs )),
143+ "VertexCount" : sum ((len (x .Particles ) for x in self ._rig .SubRigs )),
144+ "Fps" : 60 ,
145+ "EffectiveForces" : {
146+ "Gravity" : {"X" : 0 , "Y" : - 1 },
147+ "Wind" : {"X" : 0 , "Y" : 0 },
148+ },
149+ "PhysicsDictionary" : [
150+ {"Id" : "PhysicsSetting%d" % (i + 1 ), "Name" : "%d" % (i + 1 )}
151+ for i , _ in enumerate (self ._rig .SubRigs )
152+ ],
131153 },
132- "PhysicsDictionary" : [
133- {"Id" : "PhysicsSetting%d" % (i + 1 ), "Name" : "%d" % (i + 1 )}
134- for i , _ in enumerate (self ._rig .SubRigs )
135- ],
136- },
137- "PhysicsSettings" : self ._rig .dump (),
138- }
139-
140-
141- @monkey_patch (CubismRenderer )
142- def __hash__ (self : CubismRenderer ):
143- return self ._mainTexture .path_id
144-
145-
146- @monkey_patch (CubismRenderer )
147- def __eq__ (self : CubismRenderer , other : CubismRenderer ):
148- return self .__hash__ () == other .__hash__ ()
149-
150-
151- from dataclasses import dataclass
152-
153-
154- @dataclass
155- class ExtractorFlags :
156- live2d_variant : str = "cubism"
154+ "PhysicsSettings" : self ._rig .dump (),
155+ }
157156
157+ @monkey_patch ("Live2D.Cubism.Rendering.CubismRenderer" )
158+ def __hash__ (self : CubismRenderer ):
159+ return self ._mainTexture .path_id
158160
159- FLAGS = ExtractorFlags ()
161+ @monkey_patch ("Live2D.Cubism.Rendering.CubismRenderer" )
162+ def __eq__ (self : CubismRenderer , other : CubismRenderer ):
163+ return self .__hash__ () == other .__hash__ ()
160164
161165
162166# XXX: Is monkey patching this into UnityPy a good idea?
163167def read_from (reader : ObjectReader , ** kwargs ):
164168 """Import generated classes by MonoBehavior script class type and read from reader"""
165- import importlib
166-
169+ TYPETREE_DEFS = CFG .import_path ("TYPETREE_DEFS" )
167170 match reader .type :
168171 case ClassIDType .MonoBehaviour :
169172 mono : MonoBehaviour = reader .read (check_read = False )
@@ -178,10 +181,7 @@ def read_from(reader: ObjectReader, **kwargs):
178181
179182 if typetree :
180183 result = reader .read_typetree (typetree )
181- nameSpace = importlib .import_module (
182- f".generated.{ nameSpace } " , package = "UnityPyLive2DExtractor"
183- )
184- clazz = getattr (nameSpace , className , None )
184+ clazz = CFG .import_path (fullName )
185185 instance = clazz (object_reader = reader , ** result )
186186 return instance
187187 else :
@@ -201,6 +201,11 @@ def __main__():
201201 )
202202 parser .add_argument ("infile" , help = "Input file/directory to extract from" )
203203 parser .add_argument ("outdir" , help = "Output directory to extract to" )
204+ parser .add_argument (
205+ "--module" ,
206+ help = "Generated Live2D module from TypeTree dump. Read the README for info." ,
207+ default = "UnityPyLive2DExtractor.generated" ,
208+ )
204209 parser .add_argument (
205210 "--log-level" ,
206211 help = "Set logging level" ,
@@ -216,8 +221,10 @@ def __main__():
216221 fmt = "%(asctime)s %(name)s [%(levelname).4s] %(message)s" ,
217222 isatty = True ,
218223 )
219- os .makedirs (args .outdir , exist_ok = True )
220224 logger .info ("UnityPyLive2D Extractor v%d.%d.%d" % __version__ )
225+ CFG .generated_module = args .module
226+ patch_all ()
227+ os .makedirs (args .outdir , exist_ok = True )
221228 logger .info ("Loading %s" % args .infile )
222229 env = UnityPy .load (args .infile )
223230 objs = [
0 commit comments