Skip to content

Commit 711383f

Browse files
committed
fix: System.NullReferenceException: Object reference not set to an instance of an object.
1 parent dbbc2af commit 711383f

4 files changed

Lines changed: 524 additions & 18 deletions

File tree

UndertaleModTool/Editors/UndertaleCodeEditor.xaml.cs

Lines changed: 55 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1723,29 +1723,66 @@ private void OpenCompletionWindow(TextEditor editor, bool memberAccess)
17231723
if (items.Count == 0)
17241724
return;
17251725

1726+
// Never create the completion window for an editor that isn't connected to a
1727+
// presentation source (e.g. during tab teardown); showing a Window under such
1728+
// conditions crashes inside WPF window creation.
1729+
if (PresentationSource.FromVisual(editor.TextArea) is null || Window.GetWindow(editor.TextArea) is null)
1730+
return;
1731+
17261732
List<ICompletionData> dataList = new List<ICompletionData>(items.Count);
17271733
foreach (GmlCompletionItem item in items)
17281734
dataList.Add(new GmlCompletionData(item));
17291735

1730-
CompletionWindow window = new(editor.TextArea)
1736+
CompletionWindow window;
1737+
try
17311738
{
1732-
MaxHeight = 320,
1733-
CloseAutomatically = true
1734-
};
1735-
window.CompletionList.IsFiltering = true;
1736-
foreach (ICompletionData completionData in dataList)
1737-
window.CompletionList.CompletionData.Add(completionData);
1738-
1739-
// Show the typed word in gray inside the completion box
1740-
int wordStart = caret;
1741-
string docText = code;
1742-
while (wordStart > 0 && wordStart - 1 < docText.Length && IsWordChar(docText[wordStart - 1]))
1743-
wordStart--;
1744-
window.StartOffset = wordStart;
1745-
1746-
_completionWindow = window;
1747-
window.Closed += (s2, e2) => _completionWindow = null;
1748-
window.Show();
1739+
window = new CompletionWindow(editor.TextArea)
1740+
{
1741+
MaxHeight = 320,
1742+
CloseAutomatically = true
1743+
};
1744+
window.CompletionList.IsFiltering = true;
1745+
foreach (ICompletionData completionData in dataList)
1746+
window.CompletionList.CompletionData.Add(completionData);
1747+
1748+
// Show the typed word in gray inside the completion box
1749+
int wordStart = caret;
1750+
string docText = code;
1751+
while (wordStart > 0 && wordStart - 1 < docText.Length && IsWordChar(docText[wordStart - 1]))
1752+
wordStart--;
1753+
window.StartOffset = wordStart;
1754+
1755+
_completionWindow = window;
1756+
window.Closed += (s2, e2) => _completionWindow = null;
1757+
}
1758+
catch (Exception ex)
1759+
{
1760+
Trace.WriteLine("UndertaleCodeEditor: failed to build completion window: " + ex);
1761+
CloseCompletionWindow();
1762+
return;
1763+
}
1764+
1765+
// Show the window outside of the text-input call stack. Creating the HWND while
1766+
// WPF is still dispatching the WM_CHAR transaction can fail inside
1767+
// Window.CreateSourceWindow (NullReferenceException), taking down the whole app.
1768+
// Deferring the call lets input processing finish first, and any residual failure
1769+
// is contained instead of crashing on every keystroke.
1770+
window.Dispatcher.BeginInvoke(new Action(() =>
1771+
{
1772+
try
1773+
{
1774+
if (!ReferenceEquals(_completionWindow, window))
1775+
return; // superseded or closed meanwhile
1776+
window.Show();
1777+
}
1778+
catch (Exception ex)
1779+
{
1780+
Trace.WriteLine("UndertaleCodeEditor: failed to show completion window: " + ex);
1781+
if (ReferenceEquals(_completionWindow, window))
1782+
_completionWindow = null;
1783+
try { window.Close(); } catch { /* already closing */ }
1784+
}
1785+
}), System.Windows.Threading.DispatcherPriority.Background);
17491786
}
17501787

17511788
private void UpdateFolding()

UndertaleModTool/Scripts/Special Scripts/PLACEHOLDER.csx

Whitespace-only changes.
Lines changed: 278 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
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, "/*\nDECOMPILER 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

Comments
 (0)