1+ // Made with the help of QuantumV and The United Modders Of Pizza Tower Team
2+ using System . Text ;
3+ using System ;
4+ using System . IO ;
5+ using System . Threading ;
6+ using System . Threading . Tasks ;
7+ using Newtonsoft . Json ;
8+ using UndertaleModLib . Util ;
9+ using System . Collections . Generic ;
10+
11+ EnsureDataLoaded ( ) ;
12+
13+ string objectFolder = GetFolder ( FilePath ) + "Decompiled" + Path . DirectorySeparatorChar + "objects" + Path . DirectorySeparatorChar ;
14+
15+ if ( Directory . Exists ( objectFolder ) )
16+ {
17+ Directory . Delete ( objectFolder , true ) ;
18+ }
19+
20+ public class AssetReference
21+ {
22+ public string name { get ; set ; }
23+ public string path { get ; set ; }
24+ }
25+ public class GMEvent
26+ {
27+ public string resourceType { get ; set ; } = "GMEvent" ;
28+ public string resourceVersion { get ; set ; } = "1.0" ;
29+ public string name { get ; set ; } = "" ;
30+ public bool isDnD { get ; set ; } = false ;
31+ public uint eventNum { get ; set ; } = 0 ;
32+ public uint eventType { get ; set ; } = 0 ;
33+ public AssetReference collisionObjectId { get ; set ; } = null ;
34+ }
35+ public class GMObjectProperty
36+ {
37+ public string resourceType { get ; set ; } = "GMObjectProperty" ;
38+
39+ public string resourceVersion { get ; set ; } = "1.0" ;
40+
41+ public string name { get ; set ; }
42+
43+ public int varType { get ; set ; } = 0 ;
44+
45+ public string value { get ; set ; }
46+ public bool rangeEnabled { get ; set ; } = false ;
47+ public double rangeMin { get ; set ; } = 0.0 ;
48+ public double rangeMax { get ; set ; } = 10.0 ;
49+ public List < string > listItems { get ; set ; } = new List < string > { } ;
50+ public bool multiselect { get ; set ; } = false ;
51+ public List < string > filters { get ; set ; } = new List < string > { } ;
52+ }
53+
54+ public class ObjectData
55+ {
56+ public string resourceType { get ; set ; } = "GMObject" ;
57+
58+ public string resourceVersion { get ; set ; } = "1.0" ;
59+
60+ public string name { get ; set ; }
61+
62+ public AssetReference spriteId { get ; set ; } = new AssetReference ( ) ;
63+ public AssetReference spriteMaskId { get ; set ; } = new AssetReference ( ) ;
64+ public bool visible { get ; set ; }
65+
66+ public bool solid { get ; set ; }
67+ public bool persistent { get ; set ; }
68+ public bool managed { get ; set ; }
69+ public AssetReference parentObjectId { get ; set ; } = new AssetReference ( ) ;
70+ public List < GMEvent > eventList { get ; set ; } = new List < GMEvent > ( ) ;
71+ public List < GMObjectProperty > properties { get ; set ; } = new List < GMObjectProperty > ( ) ;
72+ public AssetReference parent { get ; set ; } = new AssetReference ( ) ;
73+ }
74+
75+ string GetFolder ( string path )
76+ {
77+ return Path . GetDirectoryName ( path ) + Path . DirectorySeparatorChar ;
78+ }
79+
80+ ThreadLocal < GlobalDecompileContext > DECOMPILE_CONTEXT = new ThreadLocal < GlobalDecompileContext > ( ( ) => new GlobalDecompileContext ( Data ) ) ;
81+
82+ Regex assignmentRegex = new Regex (
83+ @"^(\w+) = (.+)$" ,
84+ RegexOptions . Compiled | RegexOptions . IgnoreCase | RegexOptions . Multiline | RegexOptions . ECMAScript
85+ ) ;
86+ // get variable definitions from a precreate event
87+ List < GMObjectProperty > GetObjectProperties ( UndertalePointerList < UndertaleGameObject . Event > evList )
88+ {
89+ List < GMObjectProperty > list = new List < GMObjectProperty > { } ;
90+ if ( evList == null ) return list ;
91+ foreach ( UndertaleGameObject . Event ev in evList )
92+ {
93+ foreach ( UndertaleGameObject . EventAction action in ev . Actions )
94+ {
95+ UndertaleCode code = action . CodeId ;
96+ if ( code == null ) continue ;
97+ string gml = "" ;
98+ try
99+ {
100+ gml = new Underanalyzer . Decompiler . DecompileContext ( DECOMPILE_CONTEXT . Value , code ) . DecompileToString ( ) ;
101+ }
102+ catch ( Exception e ) { }
103+ foreach ( Match match in assignmentRegex . Matches ( gml ) )
104+ {
105+ list . Add ( new GMObjectProperty
106+ {
107+ varType = 4 , // expression
108+ name = match . Groups [ 1 ] . Captures [ 0 ] . Value ,
109+ value = match . Groups [ 2 ] . Captures [ 0 ] . Value ,
110+ } ) ;
111+ }
112+ }
113+ }
114+ return list ;
115+ }
116+
117+ SetProgressBar ( null , "Objects" , 0 , Data . GameObjects . Count ) ;
118+ StartProgressBarUpdater ( ) ;
119+ await Task . Run ( ( ) => Parallel . ForEach ( Data . GameObjects , ( UndertaleGameObject gameObject ) => {
120+ string objectDir = objectFolder + gameObject . Name . Content + Path . DirectorySeparatorChar ;
121+ Directory . CreateDirectory ( objectDir ) ;
122+ ObjectData objectData = new ObjectData ( )
123+ {
124+ name = gameObject . Name . Content ,
125+ spriteId = gameObject . Sprite != null ? new AssetReference ( )
126+ {
127+ name = gameObject . Sprite . Name . Content ,
128+ path = $ "sprites/{ gameObject . Sprite . Name . Content } /{ gameObject . Sprite . Name . Content } .yy"
129+ } : null ,
130+ spriteMaskId = gameObject . TextureMaskId != null ? new AssetReference ( )
131+ {
132+ name = gameObject . TextureMaskId . Name . Content ,
133+ path = $ "sprites/{ gameObject . TextureMaskId . Name . Content } /{ gameObject . TextureMaskId . Name . Content } .yy"
134+ } : null ,
135+ visible = gameObject . Visible ,
136+ solid = gameObject . Solid ,
137+ persistent = gameObject . Persistent ,
138+ managed = gameObject . Managed ,
139+ parentObjectId = gameObject . ParentId != null ? new AssetReference ( )
140+ {
141+ name = gameObject . ParentId . Name . Content ,
142+ path = $ "objects/{ gameObject . ParentId . Name . Content } /{ gameObject . ParentId . Name . Content } .yy"
143+ } : null ,
144+ parent = new AssetReference ( )
145+ {
146+ name = "Objects" ,
147+ path = "folders/Objects.yy"
148+ } ,
149+ } ;
150+ for ( var i = 0 ; i < gameObject . Events . Count ; i ++ )
151+ {
152+ var evList = gameObject . Events [ i ] ;
153+ // PreCreate is used by variable definitions
154+ if ( ( EventType ) i == EventType . PreCreate )
155+ {
156+ objectData . properties = GetObjectProperties ( evList ) ;
157+ continue ;
158+ }
159+ foreach ( var ev in evList )
160+ {
161+ AssetReference collObjRef = null ;
162+ uint subtype = ev . EventSubtype ;
163+ if ( ( EventType ) i == EventType . Collision )
164+ {
165+ subtype = 0 ;
166+ var collObj = Data . GameObjects [ ( int ) ev . EventSubtype ] ;
167+ if ( collObj != null )
168+ {
169+ collObjRef = new AssetReference ( )
170+ {
171+ name = collObj . Name . Content ,
172+ path = $ "objects/{ collObj . Name . Content } /{ collObj . Name . Content } .yy"
173+ } ;
174+ }
175+ }
176+ objectData . eventList . Add ( new GMEvent ( )
177+ {
178+ eventType = ( uint ) i ,
179+ eventNum = subtype ,
180+ collisionObjectId = collObjRef
181+ } ) ;
182+
183+ if ( ev . Actions . Count > 0 )
184+ {
185+ var action = ev . Actions [ 0 ] ;
186+ var code = action . CodeId ;
187+ var subtypeString = subtype . ToString ( ) ;
188+ if ( ( EventType ) i == EventType . Collision )
189+ {
190+ subtypeString = Data . GameObjects [ ( int ) ev . EventSubtype ] . Name . Content ;
191+ }
192+ var gmlPath = $ "{ objectDir } { ( ( EventType ) i ) . ToString ( ) } _{ subtypeString } .gml";
193+ try
194+ {
195+ File . WriteAllText ( gmlPath , ( code != null ? ConvertEnumToConst ( new Underanalyzer . Decompiler . DecompileContext ( DECOMPILE_CONTEXT . Value , code ) . DecompileToString ( ) ) : "" ) ) ;
196+ }
197+ catch ( Exception e )
198+ {
199+ File . WriteAllText ( gmlPath , "/*\n DECOMPILER FAILED!\n \n " + e . ToString ( ) + "\n */" ) ;
200+ }
201+ }
202+ else
203+ {
204+ var gmlPath = $ "{ objectDir } Create_0.gml";
205+ File . WriteAllText ( gmlPath , "/* Empty Sprite */" ) ;
206+ }
207+ }
208+ }
209+ string json = JsonConvert . SerializeObject ( objectData , Formatting . Indented ) ;
210+ File . WriteAllText ( objectDir + gameObject . Name . Content + ".yy" , json ) ;
211+ IncrementProgressParallel ( ) ;
212+ } ) ) ;
213+
214+ await StopProgressBarUpdater ( ) ;
215+ HideProgressBar ( ) ;
216+
217+ public string ConvertEnumToConst ( string inputCode )
218+ {
219+ // 存储所有枚举定义和替换映射
220+ var enumDefinitions = new Dictionary < string , Dictionary < string , int > > ( ) ;
221+ var replaceDict = new Dictionary < string , string > ( ) ;
222+ var sbDefinitions = new StringBuilder ( ) ;
223+
224+ // 匹配所有枚举定义
225+ var enumRegex = new Regex ( @"enum\s+(\w+)\s*\{(.*?)\}" , RegexOptions . Singleline ) ;
226+ var enumMatches = enumRegex . Matches ( inputCode ) ;
227+
228+ foreach ( Match enumMatch in enumMatches )
229+ {
230+ string enumName = enumMatch . Groups [ 1 ] . Value ;
231+ string enumContent = enumMatch . Groups [ 2 ] . Value ;
232+ var members = new Dictionary < string , int > ( ) ;
233+
234+ // 解析枚举成员(支持显式赋值)
235+ int currentValue = 0 ;
236+ var memberRegex = new Regex ( @"\s*(\w+)\s*(=\s*(\d+))?\s*,?" ) ;
237+ var memberMatches = memberRegex . Matches ( enumContent ) ;
238+
239+ foreach ( Match memberMatch in memberMatches )
240+ {
241+ if ( ! memberMatch . Success ) continue ;
242+
243+ string memberName = memberMatch . Groups [ 1 ] . Value ;
244+ string explicitValue = memberMatch . Groups [ 3 ] . Value ;
245+
246+ if ( ! string . IsNullOrEmpty ( explicitValue ) )
247+ {
248+ currentValue = int . Parse ( explicitValue ) ;
249+ }
250+
251+ members [ memberName ] = currentValue ;
252+
253+ // 添加到替换字典
254+ string fullName = $ "{ enumName } .{ memberName } ";
255+ string replacement = $ "global.{ enumName } __{ memberName } ";
256+ replaceDict [ fullName ] = replacement ;
257+
258+ // 生成定义行
259+ sbDefinitions . AppendLine ( $ "{ replacement } = { currentValue } ;") ;
260+
261+ currentValue ++ ; // 为下一个成员递增
262+ }
263+
264+ enumDefinitions [ enumName ] = members ;
265+ }
266+
267+ // 移除所有枚举定义
268+ string outputCode = enumRegex . Replace ( inputCode , "" ) ;
269+
270+ // 替换所有枚举引用
271+ foreach ( var kvp in replaceDict )
272+ {
273+ outputCode = outputCode . Replace ( kvp . Key , kvp . Value ) ;
274+ }
275+
276+ // 在代码开头插入定义
277+ return sbDefinitions . ToString ( ) + outputCode ;
278+ }
0 commit comments