From fe9696b17e171d0c9334cfa9136998ed39b59bf7 Mon Sep 17 00:00:00 2001 From: Liu Wenyuan <15816141883@163.com> Date: Thu, 9 Jan 2025 14:40:43 +0800 Subject: [PATCH 01/29] 2024.11 compatibility: Horrible hack to ignore null entries in list chunks --- UndertaleModLib/UndertaleChunks.cs | 38 +++++++++++------ UndertaleModLib/UndertaleData.cs | 2 +- UndertaleModLib/UndertaleIO.cs | 2 + UndertaleModLib/UndertaleLists.cs | 65 +++++++++++++++++++----------- 4 files changed, 71 insertions(+), 36 deletions(-) diff --git a/UndertaleModLib/UndertaleChunks.cs b/UndertaleModLib/UndertaleChunks.cs index f52dc77f8..d88348787 100644 --- a/UndertaleModLib/UndertaleChunks.cs +++ b/UndertaleModLib/UndertaleChunks.cs @@ -371,24 +371,33 @@ private void CheckForGM2024_6(UndertaleReader reader) long returnTo = reader.Position; - uint soundCount = reader.ReadUInt32(); - if (soundCount >= 2) + uint possibleSoundCount = reader.ReadUInt32(); + List soundPtrs = new(); + if (possibleSoundCount > 0) + { + soundPtrs.Capacity = (int)possibleSoundCount; + for (int i = 0; i < possibleSoundCount; i++) + { + uint soundPtr = reader.ReadUInt32(); + if (soundPtr == 0) + continue; + soundPtrs.Add(soundPtr); + } + } + if (soundPtrs.Count >= 2) { // If first sound's theoretical (old) end offset is below the start offset of // the next sound by exactly 4 bytes, then this is 2024.6. - uint firstSoundPtr = reader.ReadUInt32(); - uint secondSoundPtr = reader.ReadUInt32(); - if ((firstSoundPtr + (4 * 9)) == (secondSoundPtr - 4)) + if ((soundPtrs[0] + (4 * 9)) == (soundPtrs[1] - 4)) { reader.undertaleData.SetGMS2Version(2024, 6); } } - else if (soundCount == 1) + else if (soundPtrs.Count == 1) { // If there's a nonzero value where padding should be at the // end of the sound, then this is 2024.6. - uint firstSoundPtr = reader.ReadUInt32(); - reader.AbsPosition = firstSoundPtr + (4 * 9); + reader.AbsPosition = soundPtrs[0] + (4 * 9); if ((reader.AbsPosition % 16) != 4) { // If this occurs, then something weird has happened at the start of the chunk? @@ -639,9 +648,7 @@ internal override void SerializeChunk(UndertaleWriter writer) internal override void UnserializeChunk(UndertaleReader reader) { - reader.Position -= 4; - int chunkLength = reader.ReadInt32(); - long chunkEnd = reader.AbsPosition + chunkLength; + long chunkEnd = reader.AbsPosition + Length; long beginPosition = reader.Position; @@ -650,7 +657,14 @@ internal override void UnserializeChunk(UndertaleReader reader) uint[] objectLocations = new uint[count + 1]; for (int i = 0; i < count; i++) { - objectLocations[i] = (uint)reader.ReadInt32(); + uint objectLocation = reader.ReadUInt32(); + if (objectLocation == 0) + { + i--; + count--; + continue; + } + objectLocations[i] = objectLocation; } objectLocations[count] = (uint)chunkEnd; diff --git a/UndertaleModLib/UndertaleData.cs b/UndertaleModLib/UndertaleData.cs index a293e2c5e..77eb96f60 100644 --- a/UndertaleModLib/UndertaleData.cs +++ b/UndertaleModLib/UndertaleData.cs @@ -690,7 +690,7 @@ public void Dispose() if (disposableType.IsAssignableFrom(list.GetType().GetGenericArguments()[0])) { foreach (IDisposable disposable in list) - disposable.Dispose(); + disposable?.Dispose(); } list.Clear(); diff --git a/UndertaleModLib/UndertaleIO.cs b/UndertaleModLib/UndertaleIO.cs index 3f5a83d0f..7bc5bbce7 100644 --- a/UndertaleModLib/UndertaleIO.cs +++ b/UndertaleModLib/UndertaleIO.cs @@ -572,6 +572,8 @@ public uint GetAddressForUndertaleObject(UndertaleObject obj) try { var expectedAddress = GetAddressForUndertaleObject(obj); + if (expectedAddress == 0) + return; if (expectedAddress != AbsPosition) { SubmitWarning("Reading misaligned at " + AbsPosition.ToString("X8") + ", realigning back to " + expectedAddress.ToString("X8") + "\nHIGH RISK OF DATA LOSS! The file is probably corrupted, or uses unsupported features\nProceed at your own risk"); diff --git a/UndertaleModLib/UndertaleLists.cs b/UndertaleModLib/UndertaleLists.cs index a191ea259..36aa0bfd4 100644 --- a/UndertaleModLib/UndertaleLists.cs +++ b/UndertaleModLib/UndertaleLists.cs @@ -332,25 +332,29 @@ public override void Unserialize(UndertaleReader reader) throw new UndertaleSerializationException(e.Message + "\nwhile reading pointer to item " + (i + 1) + " of " + count + " in a list of " + typeof(T).FullName, e); } } - if (Count > 0) - { - uint pos = reader.GetAddressForUndertaleObject(this[0]); - if (reader.AbsPosition != pos) - { - long skip = pos - reader.AbsPosition; - if (skip > 0) - { - //Console.WriteLine("Skip " + skip + " bytes of blobs"); - reader.AbsPosition += skip; - } - else - throw new IOException("First list item starts inside the pointer list?!?!"); - } - } + + bool firstItemHit = false; for (uint i = 0; i < count; i++) { try { + uint pos = reader.GetAddressForUndertaleObject(this[(int)i]); + if (pos == 0) + continue; + + if (!firstItemHit && reader.AbsPosition != pos) + { + long skip = pos - reader.AbsPosition; + if (skip > 0) + { + //Console.WriteLine("Skip " + skip + " bytes of blobs"); + reader.AbsPosition += skip; + } + else + throw new IOException("First list item starts inside the pointer list?!?!"); + } + firstItemHit = true; + T obj = this[(int)i]; (obj as PrePaddedObject)?.UnserializePrePadding(reader); @@ -392,16 +396,31 @@ public static uint UnserializeChildObjectCount(UndertaleReader reader) uint[] pointers = reader.utListPtrsPool.Rent((int)count); for (uint i = 0; i < count; i++) - pointers[i] = reader.ReadUInt32(); + { + uint pos = reader.ReadUInt32(); + if (pos == 0) + { + i--; + count--; + continue; + } + pointers[i] = pos; + } - uint pos = pointers[0]; - if (reader.AbsPosition != pos) + for (uint i = 0; i < count; i++) { - long skip = pos - reader.AbsPosition; - if (skip > 0) - reader.AbsPosition += skip; - else - throw new IOException("First list item starts inside the pointer list?!?!"); + uint pos = pointers[i]; + if (pos == 0) + continue; + if (reader.AbsPosition != pos) + { + long skip = pos - reader.AbsPosition; + if (skip > 0) + reader.AbsPosition += skip; + else + throw new IOException("First list item starts inside the pointer list?!?!"); + } + break; } var unserializeFunc = reader.GetUnserializeCountFunc(t); From 9bed1ff9100c2bb8084ba3d4f1d6e0f247b485d5 Mon Sep 17 00:00:00 2001 From: Liu Wenyuan <15816141883@163.com> Date: Wed, 15 Jan 2025 23:51:41 +0800 Subject: [PATCH 02/29] Assume GM 2024.11 if null pointers exist, add unknown property to UTFont --- UndertaleModLib/Models/UndertaleFont.cs | 12 ++++++++++++ UndertaleModLib/UndertaleIO.cs | 2 +- UndertaleModLib/UndertaleLists.cs | 4 ++++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/UndertaleModLib/Models/UndertaleFont.cs b/UndertaleModLib/Models/UndertaleFont.cs index e8d5a57d2..de42ace43 100644 --- a/UndertaleModLib/Models/UndertaleFont.cs +++ b/UndertaleModLib/Models/UndertaleFont.cs @@ -158,6 +158,14 @@ public class Glyph : UndertaleObject, IDisposable /// public UndertaleSimpleListShort Kerning { get; set; } = new UndertaleSimpleListShort(); + /// + /// Purpose unknown, always 0. + /// + /// + /// Was introduced in GM 2024.11. + /// + public short UnknownAlwaysZero { get; set; } + /// public void Serialize(UndertaleWriter writer) { @@ -168,6 +176,8 @@ public void Serialize(UndertaleWriter writer) writer.Write(SourceHeight); writer.Write(Shift); writer.Write(Offset); + if (writer.undertaleData.IsVersionAtLeast(2024, 11)) + writer.Write(UnknownAlwaysZero); writer.WriteUndertaleObject(Kerning); } @@ -181,6 +191,8 @@ public void Unserialize(UndertaleReader reader) SourceHeight = reader.ReadUInt16(); Shift = reader.ReadInt16(); Offset = reader.ReadInt16(); // Potential assumption, see the conversation at https://github.com/UnderminersTeam/UndertaleModTool/issues/40#issuecomment-440208912 + if (reader.undertaleData.IsVersionAtLeast(2024, 11)) + UnknownAlwaysZero = reader.ReadInt16(); Kerning = reader.ReadUndertaleObject>(); } diff --git a/UndertaleModLib/UndertaleIO.cs b/UndertaleModLib/UndertaleIO.cs index 7bc5bbce7..26ce54e0a 100644 --- a/UndertaleModLib/UndertaleIO.cs +++ b/UndertaleModLib/UndertaleIO.cs @@ -576,7 +576,7 @@ public uint GetAddressForUndertaleObject(UndertaleObject obj) return; if (expectedAddress != AbsPosition) { - SubmitWarning("Reading misaligned at " + AbsPosition.ToString("X8") + ", realigning back to " + expectedAddress.ToString("X8") + "\nHIGH RISK OF DATA LOSS! The file is probably corrupted, or uses unsupported features\nProceed at your own risk"); + SubmitWarning("Reading misaligned at " + AbsPosition.ToString("X8") + ", realigning back to " + expectedAddress.ToString("X8") + "\nHIGH RISK OF DATA LOSS! The file is probably corrupted, or uses unsupported features\nProceed at your own risk\n" + Environment.StackTrace); AbsPosition = expectedAddress; } unreadObjects.Remove((uint)AbsPosition); diff --git a/UndertaleModLib/UndertaleLists.cs b/UndertaleModLib/UndertaleLists.cs index 36aa0bfd4..270cd2983 100644 --- a/UndertaleModLib/UndertaleLists.cs +++ b/UndertaleModLib/UndertaleLists.cs @@ -400,6 +400,10 @@ public static uint UnserializeChildObjectCount(UndertaleReader reader) uint pos = reader.ReadUInt32(); if (pos == 0) { + // Naturally this can only happen with 2024.11 data files. + // FIXME: Is this a good idea? + if (!reader.undertaleData.IsVersionAtLeast(2024, 11)) + reader.undertaleData.SetGMS2Version(2024, 11); i--; count--; continue; From 447f33e92901ee9428c50e3cfca861b7e7c11e70 Mon Sep 17 00:00:00 2001 From: Liu Wenyuan <15816141883@163.com> Date: Wed, 15 Jan 2025 23:53:24 +0800 Subject: [PATCH 03/29] Remove accidentally included debugging --- UndertaleModLib/UndertaleIO.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UndertaleModLib/UndertaleIO.cs b/UndertaleModLib/UndertaleIO.cs index 26ce54e0a..7bc5bbce7 100644 --- a/UndertaleModLib/UndertaleIO.cs +++ b/UndertaleModLib/UndertaleIO.cs @@ -576,7 +576,7 @@ public uint GetAddressForUndertaleObject(UndertaleObject obj) return; if (expectedAddress != AbsPosition) { - SubmitWarning("Reading misaligned at " + AbsPosition.ToString("X8") + ", realigning back to " + expectedAddress.ToString("X8") + "\nHIGH RISK OF DATA LOSS! The file is probably corrupted, or uses unsupported features\nProceed at your own risk\n" + Environment.StackTrace); + SubmitWarning("Reading misaligned at " + AbsPosition.ToString("X8") + ", realigning back to " + expectedAddress.ToString("X8") + "\nHIGH RISK OF DATA LOSS! The file is probably corrupted, or uses unsupported features\nProceed at your own risk"); AbsPosition = expectedAddress; } unreadObjects.Remove((uint)AbsPosition); From 6b110e800799f5b2a069ea69b0193c80496a73f5 Mon Sep 17 00:00:00 2001 From: Liu Wenyuan <15816141883@163.com> Date: Thu, 16 Jan 2025 00:17:41 +0800 Subject: [PATCH 04/29] **TEMP** Hack to make finding references work --- .../UndertaleResourceReferenceMethodsMap.cs | 88 +++++++++---------- 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/UndertaleModTool/Windows/FindReferencesTypesDialog/UndertaleResourceReferenceMethodsMap.cs b/UndertaleModTool/Windows/FindReferencesTypesDialog/UndertaleResourceReferenceMethodsMap.cs index e3efc43a7..31296cffe 100644 --- a/UndertaleModTool/Windows/FindReferencesTypesDialog/UndertaleResourceReferenceMethodsMap.cs +++ b/UndertaleModTool/Windows/FindReferencesTypesDialog/UndertaleResourceReferenceMethodsMap.cs @@ -61,7 +61,7 @@ public static class UndertaleResourceReferenceMethodsMap if (objSrc is not UndertaleSprite obj) return null; - var gameObjects = data.GameObjects.Where(x => x.Sprite == obj); + var gameObjects = data.GameObjects.Where(x => x is not null).Where(x => x.Sprite == obj); if (gameObjects.Any()) return new() { { "Game objects", checkOne ? gameObjects.ToEmptyArray() : gameObjects.ToArray() } }; else @@ -158,7 +158,7 @@ IEnumerable GetBgLayers() if (objSrc is not UndertaleSprite obj) return null; - var textGroups = data.TextureGroupInfo.Where(x => x.Sprites.Any(s => s.Resource == obj) + var textGroups = data.TextureGroupInfo.Where(x => x is not null).Where(x => x.Sprites.Any(s => s.Resource == obj) || (x.SpineSprites?.Any(s => s.Resource == obj) == true)); if (textGroups.Any()) return new() { { "Texture groups", checkOne ? textGroups.ToEmptyArray() : textGroups.ToArray() } }; @@ -177,7 +177,7 @@ IEnumerable GetBgLayers() if (objSrc is not UndertaleSprite obj) return null; - var partSysEmitters = data.ParticleSystemEmitters.Where(x => x.Sprite == obj); + var partSysEmitters = data.ParticleSystemEmitters.Where(x => x is not null).Where(x => x.Sprite == obj); if (partSysEmitters.Any()) return new() { { "Particle system emitters", checkOne ? partSysEmitters.ToEmptyArray() : partSysEmitters.ToArray() } }; else @@ -284,7 +284,7 @@ IEnumerable GetTileLayers() if (objSrc is not UndertaleBackground obj) return null; - var textGroups = data.TextureGroupInfo.Where(x => x.Tilesets.Any(s => s.Resource == obj)); + var textGroups = data.TextureGroupInfo.Where(x => x is not null).Where(x => x.Tilesets.Any(s => s.Resource == obj)); if (textGroups.Any()) return new() { { "Texture groups", checkOne ? textGroups.ToEmptyArray() : textGroups.ToArray() } }; else @@ -305,7 +305,7 @@ IEnumerable GetTileLayers() if (objSrc is not UndertaleEmbeddedTexture obj) return null; - var pageItems = data.TexturePageItems.Where(x => x.TexturePage == obj); + var pageItems = data.TexturePageItems.Where(x => x is not null).Where(x => x.TexturePage == obj); if (pageItems.Any()) return new() { { "Texture page items", checkOne ? pageItems.ToEmptyArray() : pageItems.ToArray() } }; else @@ -323,7 +323,7 @@ IEnumerable GetTileLayers() if (objSrc is not UndertaleEmbeddedTexture obj) return null; - var textGroups = data.TextureGroupInfo.Where(x => x.TexturePages.Any(s => s.Resource == obj)); + var textGroups = data.TextureGroupInfo.Where(x => x is not null).Where(x => x.TexturePages.Any(s => s.Resource == obj)); if (textGroups.Any()) return new() { { "Texture groups", checkOne ? textGroups.ToEmptyArray() : textGroups.ToArray() } }; else @@ -348,21 +348,21 @@ IEnumerable GetTileLayers() if (types.Contains(typeof(UndertaleSprite))) { - var sprites = data.Sprites.Where(x => x.Textures.Any(t => t.Texture == obj)); + var sprites = data.Sprites.Where(x => x is not null).Where(x => x.Textures.Any(t => t.Texture == obj)); if (sprites.Any()) outDict["Sprites"] = checkOne ? sprites.ToEmptyArray() : sprites.ToArray(); } if (types.Contains(typeof(UndertaleBackground))) { - var backgrounds = data.Backgrounds.Where(x => x.Texture == obj); + var backgrounds = data.Backgrounds.Where(x => x is not null).Where(x => x.Texture == obj); if (backgrounds.Any()) outDict[data.IsGameMaker2() ? "Tile sets" : "Backgrounds"] = checkOne ? backgrounds.ToEmptyArray() : backgrounds.ToArray(); } if (types.Contains(typeof(UndertaleFont))) { - var fonts = data.Fonts.Where(x => x.Texture == obj); + var fonts = data.Fonts.Where(x => x is not null).Where(x => x.Texture == obj); if (fonts.Any()) outDict["Fonts"] = checkOne ? fonts.ToEmptyArray() : fonts.ToArray(); } @@ -383,7 +383,7 @@ IEnumerable GetTileLayers() if (objSrc is not UndertaleTexturePageItem obj) return null; - var embImages = data.EmbeddedImages.Where(x => x.TextureEntry == obj); + var embImages = data.EmbeddedImages.Where(x => x is not null).Where(x => x.TextureEntry == obj); if (embImages.Any()) return new() { { "Embedded images", checkOne ? embImages.ToEmptyArray() : embImages.ToArray() } }; else @@ -408,20 +408,20 @@ IEnumerable GetTileLayers() if (types.Contains(typeof(UndertaleBackground))) { - var backgrounds = data.Backgrounds.Where(x => x.Name == obj); + var backgrounds = data.Backgrounds.Where(x => x is not null).Where(x => x.Name == obj); if (backgrounds.Any()) outDict[data.IsGameMaker2() ? "Tile sets" : "Backgrounds"] = checkOne ? backgrounds.ToEmptyArray() : backgrounds.ToArray(); } if (types.Contains(typeof(UndertaleCode))) { - var codeEntries = data.Code.Where(x => x.Name == obj); + var codeEntries = data.Code.Where(x => x is not null).Where(x => x.Name == obj); IEnumerable stringRefs; if (stringReferences is not null) stringRefs = stringReferences.Where(x => x.Value.Contains(obj)) .Select(x => x.Key); else - stringRefs = data.Code.Where(x => x.Instructions.Any( + stringRefs = data.Code.Where(x => x is not null).Where(x => x.Instructions.Any( i => i.Value is UndertaleResourceById strPtr && strPtr.Resource == obj)); @@ -432,38 +432,38 @@ IEnumerable GetTileLayers() } if (types.Contains(typeof(UndertaleFunction))) { - var functions = data.Functions.Where(x => x.Name == obj); + var functions = data.Functions.Where(x => x is not null).Where(x => x.Name == obj); if (functions.Any()) outDict["Functions"] = checkOne ? functions.ToEmptyArray() : functions.ToArray(); } if (types.Contains(typeof(UndertaleVariable))) { - var variables = data.Variables.Where(x => x.Name == obj); + var variables = data.Variables.Where(x => x is not null).Where(x => x.Name == obj); if (variables.Any()) outDict["Variables"] = checkOne ? variables.ToEmptyArray() : variables.ToArray(); } if (types.Contains(typeof(UndertaleSound))) { - var sounds = data.Sounds.Where(x => x.Name == obj || x.Type == obj || x.File == obj); + var sounds = data.Sounds.Where(x => x is not null).Where(x => x.Name == obj || x.Type == obj || x.File == obj); if (sounds.Any()) outDict["Sounds"] = checkOne ? sounds.ToEmptyArray() : sounds.ToArray(); } if (types.Contains(typeof(UndertaleAudioGroup))) { - var audioGroups = data.AudioGroups.Where(x => x.Name == obj); + var audioGroups = data.AudioGroups.Where(x => x is not null).Where(x => x.Name == obj); if (audioGroups.Any()) outDict["Audio groups"] = checkOne ? audioGroups.ToEmptyArray() : audioGroups.ToArray(); } if (types.Contains(typeof(UndertaleSprite))) { - var sprites = data.Sprites.Where(x => x.Name == obj); + var sprites = data.Sprites.Where(x => x is not null).Where(x => x.Name == obj); if (data.IsVersionAtLeast(2, 3, 0)) { - sprites = sprites.Concat(data.Sprites.Where(x => x.V2Sequence is not null + sprites = sprites.Concat(data.Sprites.Where(x => x is not null).Where(x => x.V2Sequence is not null && x.V2Sequence.Tracks.Count != 0 && (x.V2Sequence.Tracks[0].Name == obj || x.V2Sequence.Tracks[0].ModelName == obj))); @@ -476,7 +476,7 @@ IEnumerable GetTileLayers() if (types.Contains(typeof(UndertaleExtension))) { - var extensions = data.Extensions.Where(x => x.Name == obj || x.ClassName == obj || x.FolderName == obj); + var extensions = data.Extensions.Where(x => x is not null).Where(x => x.Name == obj || x.ClassName == obj || x.FolderName == obj); if (extensions.Any()) outDict["Extensions"] = checkOne ? extensions.ToEmptyArray() : extensions.ToArray(); } @@ -534,14 +534,14 @@ IEnumerable GetExtnFunctions() if (types.Contains(typeof(UndertaleFont))) { - var fonts = data.Fonts.Where(x => x.Name == obj || x.DisplayName == obj); + var fonts = data.Fonts.Where(x => x is not null).Where(x => x.Name == obj || x.DisplayName == obj); if (fonts.Any()) outDict["Fonts"] = checkOne ? fonts.ToEmptyArray() : fonts.ToArray(); } if (types.Contains(typeof(UndertaleGameObject))) { - var gameObjects = data.GameObjects.Where(x => x.Name == obj); + var gameObjects = data.GameObjects.Where(x => x is not null).Where(x => x.Name == obj); if (gameObjects.Any()) outDict["Game objects"] = checkOne ? gameObjects.ToEmptyArray() : gameObjects.ToArray(); } @@ -563,28 +563,28 @@ IEnumerable GetExtnFunctions() if (types.Contains(typeof(UndertalePath))) { - var paths = data.Paths.Where(x => x.Name == obj); + var paths = data.Paths.Where(x => x is not null).Where(x => x.Name == obj); if (paths.Any()) outDict["Paths"] = checkOne ? paths.ToEmptyArray() : paths.ToArray(); } if (types.Contains(typeof(UndertaleRoom))) { - var rooms = data.Rooms.Where(x => x.Name == obj || x.Caption == obj); + var rooms = data.Rooms.Where(x => x is not null).Where(x => x.Name == obj || x.Caption == obj); if (rooms.Any()) outDict["Rooms"] = checkOne ? rooms.ToEmptyArray() : rooms.ToArray(); } if (types.Contains(typeof(UndertaleScript))) { - var scripts = data.Scripts.Where(x => x.Name == obj); + var scripts = data.Scripts.Where(x => x is not null).Where(x => x.Name == obj); if (scripts.Any()) outDict["Scripts"] = checkOne ? scripts.ToEmptyArray() : scripts.ToArray(); } if (types.Contains(typeof(UndertaleShader))) { - var shaders = data.Shaders.Where(x => x.Name == obj + var shaders = data.Shaders.Where(x => x is not null).Where(x => x.Name == obj || x.GLSL_ES_Vertex == obj || x.GLSL_Vertex == obj || x.HLSL9_Vertex == obj || x.GLSL_ES_Fragment == obj || x.GLSL_Fragment == obj || x.HLSL9_Fragment == obj || x.VertexShaderAttributes.Any(a => a.Name == obj)); @@ -594,7 +594,7 @@ IEnumerable GetExtnFunctions() if (types.Contains(typeof(UndertaleTimeline))) { - var timelines = data.Timelines.Where(x => x.Name == obj); + var timelines = data.Timelines.Where(x => x is not null).Where(x => x.Name == obj); if (timelines.Any()) outDict["Timelines"] = checkOne ? timelines.ToEmptyArray() : timelines.ToArray(); } @@ -617,7 +617,7 @@ IEnumerable GetExtnFunctions() if (objSrc is not UndertaleString obj) return null; - var codeLocals = data.CodeLocals.Where(x => x.Name == obj || x.Locals.Any(l => l.Name == obj)); + var codeLocals = data.CodeLocals.Where(x => x is not null).Where(x => x.Name == obj || x.Locals.Any(l => l.Name == obj)); if (codeLocals.Any()) return new() { { "Code locals", checkOne ? codeLocals.ToEmptyArray() : codeLocals.ToArray() } }; else @@ -658,7 +658,7 @@ IEnumerable GetExtnFunctions() if (types.Contains(typeof(UndertaleEmbeddedImage))) { - var embImages = data.EmbeddedImages.Where(x => x.Name == obj); + var embImages = data.EmbeddedImages.Where(x => x is not null).Where(x => x.Name == obj); if (embImages.Any()) outDict["Embedded images"] = checkOne ? embImages.ToEmptyArray() : embImages.ToArray(); } @@ -716,7 +716,7 @@ IEnumerable GetSprInstances() if (objSrc is not UndertaleString obj) return null; - var textGroups = data.TextureGroupInfo.Where(x => x.Name == obj); + var textGroups = data.TextureGroupInfo.Where(x => x is not null).Where(x => x.Name == obj); if (textGroups.Any()) return new() { { "Texture groups", checkOne ? textGroups.ToEmptyArray() : textGroups.ToArray() } }; else @@ -735,7 +735,7 @@ IEnumerable GetSprInstances() if (types.Contains(typeof(UndertaleAnimationCurve))) { - var animCurves = data.AnimationCurves.Where(x => x.Name == obj); + var animCurves = data.AnimationCurves.Where(x => x is not null).Where(x => x.Name == obj); if (animCurves.Any()) outDict["Animation curves"] = checkOne ? animCurves.ToEmptyArray() : animCurves.ToArray(); } @@ -781,7 +781,7 @@ IEnumerable GetSeqInstances() if (types.Contains(typeof(UndertaleSequence))) { - var sequences = data.Sequences.Where(x => x.Name == obj + var sequences = data.Sequences.Where(x => x is not null).Where(x => x.Name == obj || x.FunctionIDs.ContainsValue(obj)); if (sequences.Any()) outDict["Sequences"] = checkOne ? sequences.ToEmptyArray() : sequences.ToArray(); @@ -886,7 +886,7 @@ IEnumerable GetSequenceMoments() if (objSrc is not UndertaleString obj) return null; - var filterEffects = data.FilterEffects.Where(x => x.Name == obj || x.Value == obj); + var filterEffects = data.FilterEffects.Where(x => x is not null).Where(x => x.Name == obj || x.Value == obj); if (filterEffects.Any()) return new() { { "Filter effects", checkOne ? filterEffects.ToEmptyArray() : filterEffects.ToArray() } }; else @@ -982,14 +982,14 @@ void ProcessTrack(UndertaleSequence seq, Track track, List trackChain) if (types.Contains(typeof(UndertaleParticleSystem))) { - var partSystems = data.ParticleSystems.Where(x => x.Name == obj); + var partSystems = data.ParticleSystems.Where(x => x is not null).Where(x => x.Name == obj); if (partSystems.Any()) outDict["Particle systems"] = checkOne ? partSystems.ToEmptyArray() : partSystems.ToArray(); } if (types.Contains(typeof(UndertaleParticleSystemEmitter))) { - var partSysEmitters = data.ParticleSystemEmitters.Where(x => x.Name == obj); + var partSysEmitters = data.ParticleSystemEmitters.Where(x => x is not null).Where(x => x.Name == obj); if (partSysEmitters.Any()) outDict["Particle system emitters"] = checkOne ? partSysEmitters.ToEmptyArray() : partSysEmitters.ToArray(); } @@ -1138,7 +1138,7 @@ void ProcessTrack(UndertaleSequence seq, Track track, List trackChain) if (types.Contains(typeof(UndertaleGameObject))) { - var gameObjects = data.GameObjects.Where(x => x.Events.Any( + var gameObjects = data.GameObjects.Where(x => x is not null).Where(x => x.Events.Any( e => e.Any(se => se.Actions.Any( a => a.CodeId == obj)))); if (gameObjects.Any()) @@ -1147,7 +1147,7 @@ void ProcessTrack(UndertaleSequence seq, Track track, List trackChain) if (types.Contains(typeof(UndertaleRoom))) { - var rooms = data.Rooms.Where(x => x.CreationCodeId == obj); + var rooms = data.Rooms.Where(x => x is not null).Where(x => x.CreationCodeId == obj); if (rooms.Any()) outDict["Rooms"] = checkOne ? rooms.ToEmptyArray() : rooms.ToArray(); } @@ -1165,7 +1165,7 @@ void ProcessTrack(UndertaleSequence seq, Track track, List trackChain) if (types.Contains(typeof(UndertaleScript))) { - var scripts = data.Scripts.Where(x => x.Code == obj); + var scripts = data.Scripts.Where(x => x is not null).Where(x => x.Code == obj); if (scripts.Any()) outDict["Scripts"] = checkOne ? scripts.ToEmptyArray() : scripts.ToArray(); } @@ -1192,7 +1192,7 @@ void ProcessTrack(UndertaleSequence seq, Track track, List trackChain) if (objSrc is not UndertaleEmbeddedAudio obj) return null; - var sounds = data.Sounds.Where(x => x.AudioFile == obj); + var sounds = data.Sounds.Where(x => x is not null).Where(x => x.AudioFile == obj); if (sounds.Any()) return new() { { "Sounds", checkOne ? sounds.ToEmptyArray() : sounds.ToArray() } }; else @@ -1216,7 +1216,7 @@ void ProcessTrack(UndertaleSequence seq, Track track, List trackChain) if (objSrc is not UndertaleAudioGroup obj) return null; - var sounds = data.Sounds.Where(x => x.AudioGroup == obj); + var sounds = data.Sounds.Where(x => x is not null).Where(x => x.AudioGroup == obj); if (sounds.Any()) return new() { { "Sounds", checkOne ? sounds.ToEmptyArray() : sounds.ToArray() } }; else @@ -1245,7 +1245,7 @@ void ProcessTrack(UndertaleSequence seq, Track track, List trackChain) funcRefs = funcReferences.Where(x => x.Value.Contains(obj)) .Select(x => x.Key); else - funcRefs = data.Code.Where(x => x.Instructions.Any( + funcRefs = data.Code.Where(x => x is not null).Where(x => x.Instructions.Any( i => i.Function?.Target == obj || i.Value is UndertaleInstruction.Reference funcRef && funcRef.Target == obj)); @@ -1277,7 +1277,7 @@ void ProcessTrack(UndertaleSequence seq, Track track, List trackChain) variRefs = variReferences.Where(x => x.Value.Contains(obj)) .Select(x => x.Key); else - variRefs = data.Code.Where(x => x.Instructions.Any( + variRefs = data.Code.Where(x => x is not null).Where(x => x.Instructions.Any( i => i.Destination?.Target == obj || i.Value is UndertaleInstruction.Reference varRef && varRef.Target == obj)); @@ -1393,14 +1393,14 @@ IEnumerable GetPartSysInstances() if (types.Contains(typeof(UndertaleParticleSystem))) { - var partSystems = data.ParticleSystems.Where(x => x.Emitters.Any(e => e.Resource == obj)); + var partSystems = data.ParticleSystems.Where(x => x is not null).Where(x => x.Emitters.Any(e => e.Resource == obj)); if (partSystems.Any()) outDict["Particle systems"] = checkOne ? partSystems.ToEmptyArray() : partSystems.ToArray(); } if (types.Contains(typeof(UndertaleParticleSystemEmitter))) { - var partSysEmitters = data.ParticleSystemEmitters.Where(x => x.SpawnOnDeath == obj || x.SpawnOnUpdate == obj); + var partSysEmitters = data.ParticleSystemEmitters.Where(x => x is not null).Where(x => x.SpawnOnDeath == obj || x.SpawnOnUpdate == obj); if (partSysEmitters.Any()) outDict["Particle system emitters"] = checkOne ? partSysEmitters.ToEmptyArray() : partSysEmitters.ToArray(); } From 9bc4c5620f5fb637cddddf51445b3e8863346a84 Mon Sep 17 00:00:00 2001 From: Liu Wenyuan <15816141883@163.com> Date: Thu, 16 Jan 2025 19:58:48 +0800 Subject: [PATCH 05/29] Make it possible to save with nulls in list chunk --- UndertaleModLib/UndertaleChunkTypes.cs | 5 ++++- UndertaleModLib/UndertaleIO.cs | 11 ++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/UndertaleModLib/UndertaleChunkTypes.cs b/UndertaleModLib/UndertaleChunkTypes.cs index 5289f0499..6729b7d6e 100644 --- a/UndertaleModLib/UndertaleChunkTypes.cs +++ b/UndertaleModLib/UndertaleChunkTypes.cs @@ -238,7 +238,10 @@ public void GenerateIndexDict() IndexDict = new(); for (int i = 0; i < List.Count; i++) - IndexDict[List[i]] = i; + { + if (List[i] is not null) + IndexDict[List[i]] = i; + } } public void ClearIndexDict() { diff --git a/UndertaleModLib/UndertaleIO.cs b/UndertaleModLib/UndertaleIO.cs index 7bc5bbce7..090804803 100644 --- a/UndertaleModLib/UndertaleIO.cs +++ b/UndertaleModLib/UndertaleIO.cs @@ -777,11 +777,20 @@ public uint GetAddressForUndertaleObject(UndertaleObject obj) public void WriteUndertaleObject(T obj) where T : UndertaleObject, new() { + if (obj is null) + { + // We simply shouldn't write anything. + // Pointers to this "object" are simply written as 0, and we don't need to + // put it in the pool + return; + } + try { // This isn't a major issue, and this is a performance waster //if (objectPool.ContainsKey(obj)) // throw new IOException("Writing object twice"); + uint objectAddr = Position; if (obj.GetType() == typeof(UndertaleString)) { @@ -795,7 +804,7 @@ public uint GetAddressForUndertaleObject(UndertaleObject obj) } else objectPool.Add(obj, objectAddr); // strings come later in the file, so no need to add them to the pool - + obj.Serialize(this); if (pendingWrites.ContainsKey(obj)) From fe1623eaa95ac773df73d4a043505675fd5ab0b7 Mon Sep 17 00:00:00 2001 From: Liu Wenyuan <15816141883@163.com> Date: Fri, 17 Jan 2025 01:52:55 +0800 Subject: [PATCH 06/29] Add ResourceListTreeViewItem with special handling for null items --- .../Controls/ResourceListTreeViewItem.xaml | 49 +++++ .../Controls/ResourceListTreeViewItem.xaml.cs | 127 +++++++++++ UndertaleModTool/MainWindow.xaml | 208 +++++++++--------- UndertaleModTool/MainWindow.xaml.cs | 57 +---- 4 files changed, 279 insertions(+), 162 deletions(-) create mode 100644 UndertaleModTool/Controls/ResourceListTreeViewItem.xaml create mode 100644 UndertaleModTool/Controls/ResourceListTreeViewItem.xaml.cs diff --git a/UndertaleModTool/Controls/ResourceListTreeViewItem.xaml b/UndertaleModTool/Controls/ResourceListTreeViewItem.xaml new file mode 100644 index 000000000..a4b30a88a --- /dev/null +++ b/UndertaleModTool/Controls/ResourceListTreeViewItem.xaml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/UndertaleModTool/Controls/ResourceListTreeViewItem.xaml.cs b/UndertaleModTool/Controls/ResourceListTreeViewItem.xaml.cs new file mode 100644 index 000000000..fecafe37a --- /dev/null +++ b/UndertaleModTool/Controls/ResourceListTreeViewItem.xaml.cs @@ -0,0 +1,127 @@ +using System; +using System.Collections.Generic; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using UndertaleModLib; +using UndertaleModTool.Windows; + +namespace UndertaleModTool +{ + public partial class ResourceListTreeViewItem : TreeViewItem + { + public static readonly DependencyProperty DefaultTemplateProperty = DependencyProperty.Register( + "DefaultTemplate", typeof(DataTemplate), typeof(ResourceListTreeViewItem), + new FrameworkPropertyMetadata(default(DataTemplate), FrameworkPropertyMetadataOptions.AffectsRender, OnDefaultTemplateChanged)); + + public DataTemplate DefaultTemplate + { + get { return (DataTemplate)GetValue(DefaultTemplateProperty); } + set { SetValue(DefaultTemplateProperty, value); } + } + + public ResourceListTreeViewItem() + { + InitializeComponent(); + + Binding visibilityBinding = new("ItemsSource") + { + Converter = new NullToVisibilityConverter() { + nullValue = Visibility.Collapsed, + notNullValue = Visibility.Visible + }, + RelativeSource = RelativeSource.Self, + UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged + }; + SetBinding(VisibilityProperty, visibilityBinding); + + ItemTemplateSelector = new ResourceItemTemplateSelector() + { + DefaultTemplate = DefaultTemplate, + NullTemplate = FindResource("NullResourceItemTemplate") as DataTemplate + }; + } + + private static void OnDefaultTemplateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + ResourceListTreeViewItem item = d as ResourceListTreeViewItem; + if (item is not null) + (item.ItemTemplateSelector as ResourceItemTemplateSelector).DefaultTemplate = item.DefaultTemplate; + } + + private void MenuItem_ContextMenuOpened(object sender, RoutedEventArgs e) + { + var menu = sender as ContextMenu; + foreach (var item in menu.Items) + { + var menuItem = item as MenuItem; + if ((menuItem.Header as string) == "Find all references") + { + menuItem.Visibility = UndertaleResourceReferenceMap.IsTypeReferenceable(menu.DataContext?.GetType()) + ? Visibility.Visible : Visibility.Collapsed; + + break; + } + } + } + + private void MenuItem_OpenInNewTab_Click(object sender, RoutedEventArgs e) + { + MainWindow window = (MainWindow)Application.Current.MainWindow; + window.OpenInTab(window.Highlighted, true); + } + + private void MenuItem_FindAllReferences_Click(object sender, RoutedEventArgs e) + { + MainWindow window = (MainWindow)Application.Current.MainWindow; + var obj = (sender as FrameworkElement)?.DataContext as UndertaleResource; + if (obj is null) + { + window.ShowError("The selected object is not an \"UndertaleResource\"."); + return; + } + + FindReferencesTypesDialog dialog = null; + try + { + dialog = new(obj, window.Data); + dialog.ShowDialog(); + } + catch (Exception ex) + { + window.ShowError("An error occured in the object references related window.\n" + + $"Please report this on GitHub.\n\n{ex}"); + } + finally + { + dialog?.Close(); + } + } + + private void MenuItem_CopyName_Click(object sender, RoutedEventArgs e) + { + MainWindow window = (MainWindow)Application.Current.MainWindow; + window.CopyItemName(window.Highlighted); + } + + private void MenuItem_Delete_Click(object sender, RoutedEventArgs e) + { + MainWindow window = (MainWindow)Application.Current.MainWindow; + if (window.Highlighted is UndertaleObject obj) + window.DeleteItem(obj); + } + } + + public class ResourceItemTemplateSelector : DataTemplateSelector + { + public DataTemplate DefaultTemplate { get; set; } + public DataTemplate NullTemplate { get; set; } + + public override DataTemplate SelectTemplate(object item, DependencyObject container) + { + if (item is null) + return NullTemplate; + return DefaultTemplate; + } + } +} \ No newline at end of file diff --git a/UndertaleModTool/MainWindow.xaml b/UndertaleModTool/MainWindow.xaml index 0969b0784..e49d54610 100644 --- a/UndertaleModTool/MainWindow.xaml +++ b/UndertaleModTool/MainWindow.xaml @@ -248,12 +248,6 @@ - - - - - - @@ -263,9 +257,6 @@ - - - @@ -291,101 +282,100 @@ - - + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - - - - + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - + + diff --git a/UndertaleModTool/MainWindow.xaml.cs b/UndertaleModTool/MainWindow.xaml.cs index 2201d54d6..ed1237a1f 100644 --- a/UndertaleModTool/MainWindow.xaml.cs +++ b/UndertaleModTool/MainWindow.xaml.cs @@ -1839,7 +1839,7 @@ private TreeViewItem GetTreeViewItemFor(UndertaleObject obj) return null; } - private void DeleteItem(UndertaleObject obj) + internal void DeleteItem(UndertaleObject obj) { TreeViewItem container = GetNearestParent(GetTreeViewItemFor(obj)); object source = container.ItemsSource; @@ -1905,7 +1905,7 @@ private void DeleteItem(UndertaleObject obj) } } } - private void CopyItemName(object obj) + internal void CopyItemName(object obj) { string name = null; @@ -2024,60 +2024,11 @@ private void MenuItem_FindUnreferencedAssets_Click(object sender, RoutedEventArg dialog?.Close(); } } - - private void MenuItem_ContextMenuOpened(object sender, RoutedEventArgs e) - { - var menu = sender as ContextMenu; - foreach (var item in menu.Items) - { - var menuItem = item as MenuItem; - if ((menuItem.Header as string) == "Find all references") - { - menuItem.Visibility = UndertaleResourceReferenceMap.IsTypeReferenceable(menu.DataContext?.GetType()) - ? Visibility.Visible : Visibility.Collapsed; - - break; - } - } - } + private void MenuItem_OpenInNewTab_Click(object sender, RoutedEventArgs e) { OpenInTab(Highlighted, true); } - private void MenuItem_FindAllReferences_Click(object sender, RoutedEventArgs e) - { - var obj = (sender as FrameworkElement)?.DataContext as UndertaleResource; - if (obj is null) - { - this.ShowError("The selected object is not an \"UndertaleResource\"."); - return; - } - - FindReferencesTypesDialog dialog = null; - try - { - dialog = new(obj, Data); - dialog.ShowDialog(); - } - catch (Exception ex) - { - this.ShowError("An error occured in the object references related window.\n" + - $"Please report this on GitHub.\n\n{ex}"); - } - finally - { - dialog?.Close(); - } - } - private void MenuItem_CopyName_Click(object sender, RoutedEventArgs e) - { - CopyItemName(Highlighted); - } - private void MenuItem_Delete_Click(object sender, RoutedEventArgs e) - { - if (Highlighted is UndertaleObject obj) - DeleteItem(obj); - } private void MenuItem_Add_Click(object sender, RoutedEventArgs e) { @@ -3629,7 +3580,7 @@ private async void MenuItem_OffsetMap_Click(object sender, RoutedEventArgs e) } } - private void OpenInTab(object obj, bool isNewTab = false, string tabTitle = null) + internal void OpenInTab(object obj, bool isNewTab = false, string tabTitle = null) { if (obj is null) return; From 01ad4ae3b2fc807e660fd987b3feadc856873d82 Mon Sep 17 00:00:00 2001 From: Liu Wenyuan <15816141883@163.com> Date: Sat, 18 Jan 2025 12:42:42 +0800 Subject: [PATCH 07/29] Random refactors to ResourceListTreeViewItem --- .../Controls/ResourceListTreeViewItem.xaml | 14 +-- .../Controls/ResourceListTreeViewItem.xaml.cs | 78 ++++++++++----- UndertaleModTool/MainWindow.xaml | 98 +++++++++---------- .../NullConditionalDataTemplateSelector.cs | 32 ++++++ 4 files changed, 140 insertions(+), 82 deletions(-) create mode 100644 UndertaleModTool/NullConditionalDataTemplateSelector.cs diff --git a/UndertaleModTool/Controls/ResourceListTreeViewItem.xaml b/UndertaleModTool/Controls/ResourceListTreeViewItem.xaml index a4b30a88a..c4b3361e3 100644 --- a/UndertaleModTool/Controls/ResourceListTreeViewItem.xaml +++ b/UndertaleModTool/Controls/ResourceListTreeViewItem.xaml @@ -8,12 +8,12 @@ mc:Ignorable="d"> - - - + + + @@ -22,28 +22,28 @@ - - + \ No newline at end of file diff --git a/UndertaleModTool/Controls/ResourceListTreeViewItem.xaml.cs b/UndertaleModTool/Controls/ResourceListTreeViewItem.xaml.cs index fecafe37a..b7c213b82 100644 --- a/UndertaleModTool/Controls/ResourceListTreeViewItem.xaml.cs +++ b/UndertaleModTool/Controls/ResourceListTreeViewItem.xaml.cs @@ -1,5 +1,5 @@ using System; -using System.Collections.Generic; +using System.ComponentModel; using System.Windows; using System.Windows.Controls; using System.Windows.Data; @@ -10,14 +10,46 @@ namespace UndertaleModTool { public partial class ResourceListTreeViewItem : TreeViewItem { - public static readonly DependencyProperty DefaultTemplateProperty = DependencyProperty.Register( - "DefaultTemplate", typeof(DataTemplate), typeof(ResourceListTreeViewItem), - new FrameworkPropertyMetadata(default(DataTemplate), FrameworkPropertyMetadataOptions.AffectsRender, OnDefaultTemplateChanged)); + // + // TreeViewItem for representing UndertaleResources in the MainWindow data hirearchy + // - public DataTemplate DefaultTemplate + // We can't just use the stock ItemTemplate property, else our Selector will not run + public static readonly DependencyProperty DefaultItemTemplateProperty = DependencyProperty.Register( + "DefaultItemTemplate", typeof(DataTemplate), typeof(ResourceListTreeViewItem), + new FrameworkPropertyMetadata(default(DataTemplate), FrameworkPropertyMetadataOptions.AffectsRender, OnDefaultItemTemplateChanged)); + + // + // Template to use if a resource is not null + // + [Bindable(true), Category("Content")] + public DataTemplate DefaultItemTemplate { - get { return (DataTemplate)GetValue(DefaultTemplateProperty); } - set { SetValue(DefaultTemplateProperty, value); } + get { return (DataTemplate)GetValue(DefaultItemTemplateProperty); } + set { SetValue(DefaultItemTemplateProperty, value); } + } + + public static readonly DependencyProperty NullItemTemplateProperty = DependencyProperty.Register( + "NullItemTemplate", typeof(DataTemplate), typeof(ResourceListTreeViewItem), + new FrameworkPropertyMetadata(default(DataTemplate), FrameworkPropertyMetadataOptions.AffectsRender, OnNullItemTemplateChanged)); + + private DataTemplate _defaultNullItemTemplateCache; + + // + // Template to use if a resource is null + // + [Bindable(true), Category("Content")] + public DataTemplate NullItemTemplate + { + get + { + // HACK: I don't want to implement the default template in code, so I'm getting it from + // the resource dict; unfortunately it means I have to do this, since I can't just + // specify it in the metadata + _defaultNullItemTemplateCache ??= FindResource("DefaultNullItemTemplate") as DataTemplate; + return (DataTemplate)GetValue(NullItemTemplateProperty) ?? _defaultNullItemTemplateCache; + } + set { SetValue(NullItemTemplateProperty, value); } } public ResourceListTreeViewItem() @@ -26,27 +58,34 @@ public ResourceListTreeViewItem() Binding visibilityBinding = new("ItemsSource") { + RelativeSource = RelativeSource.Self, Converter = new NullToVisibilityConverter() { nullValue = Visibility.Collapsed, notNullValue = Visibility.Visible }, - RelativeSource = RelativeSource.Self, UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged }; SetBinding(VisibilityProperty, visibilityBinding); - ItemTemplateSelector = new ResourceItemTemplateSelector() + ItemTemplateSelector = new NullConditionalDataTemplateSelector() { - DefaultTemplate = DefaultTemplate, - NullTemplate = FindResource("NullResourceItemTemplate") as DataTemplate + NonNullTemplate = DefaultItemTemplate, + NullTemplate = NullItemTemplate }; } - private static void OnDefaultTemplateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + private static void OnDefaultItemTemplateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { ResourceListTreeViewItem item = d as ResourceListTreeViewItem; if (item is not null) - (item.ItemTemplateSelector as ResourceItemTemplateSelector).DefaultTemplate = item.DefaultTemplate; + (item.ItemTemplateSelector as NullConditionalDataTemplateSelector).NonNullTemplate = item.DefaultItemTemplate; + } + + private static void OnNullItemTemplateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + ResourceListTreeViewItem item = d as ResourceListTreeViewItem; + if (item is not null) + (item.ItemTemplateSelector as NullConditionalDataTemplateSelector).NullTemplate = item.NullItemTemplate; } private void MenuItem_ContextMenuOpened(object sender, RoutedEventArgs e) @@ -111,17 +150,4 @@ private void MenuItem_Delete_Click(object sender, RoutedEventArgs e) window.DeleteItem(obj); } } - - public class ResourceItemTemplateSelector : DataTemplateSelector - { - public DataTemplate DefaultTemplate { get; set; } - public DataTemplate NullTemplate { get; set; } - - public override DataTemplate SelectTemplate(object item, DependencyObject container) - { - if (item is null) - return NullTemplate; - return DefaultTemplate; - } - } } \ No newline at end of file diff --git a/UndertaleModTool/MainWindow.xaml b/UndertaleModTool/MainWindow.xaml index e49d54610..36f938d4b 100644 --- a/UndertaleModTool/MainWindow.xaml +++ b/UndertaleModTool/MainWindow.xaml @@ -283,99 +283,99 @@ - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + diff --git a/UndertaleModTool/NullConditionalDataTemplateSelector.cs b/UndertaleModTool/NullConditionalDataTemplateSelector.cs new file mode 100644 index 000000000..7bbfe3cce --- /dev/null +++ b/UndertaleModTool/NullConditionalDataTemplateSelector.cs @@ -0,0 +1,32 @@ +using System.Windows; +using System.Windows.Controls; + +namespace UndertaleModTool +{ + public class NullConditionalDataTemplateSelector : DataTemplateSelector + { + // + // Selects a DataTemplate based on whether the item is null or not. + // + // + // Used by to select the appropriate template for an item. + // + + // + // The template to use if the item is not null. + // + public DataTemplate NonNullTemplate { get; set; } + + // + // The template to use if the item is null. + // + public DataTemplate NullTemplate { get; set; } + + public override DataTemplate SelectTemplate(object item, DependencyObject container) + { + if (item is null) + return NullTemplate; + return NonNullTemplate; + } + } +} \ No newline at end of file From 82ea608e9f71e59e26ac48716e00e510f73844dd Mon Sep 17 00:00:00 2001 From: Liu Wenyuan <15816141883@163.com> Date: Sat, 18 Jan 2025 13:04:23 +0800 Subject: [PATCH 08/29] Convert resource list header hiding logic to style and restore old behavior --- .../Controls/ResourceListTreeViewItem.xaml | 17 +++++++++++++++++ .../Controls/ResourceListTreeViewItem.xaml.cs | 11 ----------- UndertaleModTool/MainWindow.xaml | 1 - 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/UndertaleModTool/Controls/ResourceListTreeViewItem.xaml b/UndertaleModTool/Controls/ResourceListTreeViewItem.xaml index c4b3361e3..bf77c5661 100644 --- a/UndertaleModTool/Controls/ResourceListTreeViewItem.xaml +++ b/UndertaleModTool/Controls/ResourceListTreeViewItem.xaml @@ -42,6 +42,23 @@ + + + + diff --git a/UndertaleModTool/Controls/ResourceListTreeViewItem.xaml.cs b/UndertaleModTool/Controls/ResourceListTreeViewItem.xaml.cs index b7c213b82..75f1e5e55 100644 --- a/UndertaleModTool/Controls/ResourceListTreeViewItem.xaml.cs +++ b/UndertaleModTool/Controls/ResourceListTreeViewItem.xaml.cs @@ -56,17 +56,6 @@ public ResourceListTreeViewItem() { InitializeComponent(); - Binding visibilityBinding = new("ItemsSource") - { - RelativeSource = RelativeSource.Self, - Converter = new NullToVisibilityConverter() { - nullValue = Visibility.Collapsed, - notNullValue = Visibility.Visible - }, - UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged - }; - SetBinding(VisibilityProperty, visibilityBinding); - ItemTemplateSelector = new NullConditionalDataTemplateSelector() { NonNullTemplate = DefaultItemTemplate, diff --git a/UndertaleModTool/MainWindow.xaml b/UndertaleModTool/MainWindow.xaml index 36f938d4b..e3ed6ae72 100644 --- a/UndertaleModTool/MainWindow.xaml +++ b/UndertaleModTool/MainWindow.xaml @@ -23,7 +23,6 @@ - From 3c2176f5b736909cd47858549c857b52213123ad Mon Sep 17 00:00:00 2001 From: Liu Wenyuan <15816141883@163.com> Date: Sat, 18 Jan 2025 13:05:56 +0800 Subject: [PATCH 09/29] Restore accidentally removed VisibleIfNotNull converter in MainWindow --- UndertaleModTool/MainWindow.xaml | 1 + 1 file changed, 1 insertion(+) diff --git a/UndertaleModTool/MainWindow.xaml b/UndertaleModTool/MainWindow.xaml index e3ed6ae72..36f938d4b 100644 --- a/UndertaleModTool/MainWindow.xaml +++ b/UndertaleModTool/MainWindow.xaml @@ -23,6 +23,7 @@ + From d33c31f9ab8e82a83b15b290f4a60161d0d220a1 Mon Sep 17 00:00:00 2001 From: Liu Wenyuan <15816141883@163.com> Date: Sat, 18 Jan 2025 17:59:10 +0800 Subject: [PATCH 10/29] Fix BGND reading & AGRP child object count miscounting --- UndertaleModLib/UndertaleChunkTypes.cs | 35 ++++++++++-- UndertaleModLib/UndertaleLists.cs | 74 ++++++++++++++++---------- 2 files changed, 77 insertions(+), 32 deletions(-) diff --git a/UndertaleModLib/UndertaleChunkTypes.cs b/UndertaleModLib/UndertaleChunkTypes.cs index 6729b7d6e..17af3b252 100644 --- a/UndertaleModLib/UndertaleChunkTypes.cs +++ b/UndertaleModLib/UndertaleChunkTypes.cs @@ -268,6 +268,8 @@ internal override void SerializeChunk(UndertaleWriter writer) while (writer.Position % Alignment != 0) writer.Write((byte)0); } + if (List[i] is null) + continue; uint returnTo = writer.Position; writer.Position = baseAddr + ((uint)i * 4); writer.Write(returnTo); @@ -281,6 +283,9 @@ internal override void UnserializeChunk(UndertaleReader reader) uint count = reader.ReadUInt32(); List.SetCapacity(count); uint realCount = count; + bool[] gm2024_11_WhatToSkip = null; + if (reader.undertaleData.IsVersionAtLeast(2024, 11) && count > 0) + gm2024_11_WhatToSkip = new bool[count]; for (int i = 0; i < count; i++) { @@ -288,15 +293,24 @@ internal override void UnserializeChunk(UndertaleReader reader) Align &= (readValue % Alignment == 0); if (readValue != 0) continue; - if (reader.undertaleData.GeneralInfo.BytecodeVersion >= 13) + if (reader.undertaleData.IsVersionAtLeast(2024, 11) && gm2024_11_WhatToSkip is not null) { - reader.SubmitWarning("Zero values in an AlignUpdatedListChunk encountered on Bytecode 13 or higher!"); + // This is "normal" and is likely a object removed by GMAC. + gm2024_11_WhatToSkip[i] = true; + continue; } + realCount--; } for (int i = 0; i < realCount; i++) { + if (gm2024_11_WhatToSkip is not null && gm2024_11_WhatToSkip[i]) + { + List.InternalAdd(default); + continue; + } + if (Align) { while (reader.AbsPosition % Alignment != 0) @@ -309,7 +323,22 @@ internal override void UnserializeChunk(UndertaleReader reader) internal override uint UnserializeObjectCount(UndertaleReader reader) { - uint count = reader.ReadUInt32(); + uint claimedCount = reader.ReadUInt32(), count = claimedCount; + if (count == 0) + return 0; + + for (int i = 0; i < claimedCount; i++) + { + uint readValue = reader.ReadUInt32(); + Align &= (readValue % Alignment == 0); + if (readValue != 0) continue; + + if (reader.undertaleData.GeneralInfo.BytecodeVersion >= 13 && !reader.undertaleData.IsVersionAtLeast(2024, 11)) + { + reader.SubmitWarning("Zero values in an AlignUpdatedListChunk encountered on potential pre-2024.11 Bytecode 13+!"); + } + count--; + } if (count == 0) return 0; diff --git a/UndertaleModLib/UndertaleLists.cs b/UndertaleModLib/UndertaleLists.cs index 270cd2983..a9438a60a 100644 --- a/UndertaleModLib/UndertaleLists.cs +++ b/UndertaleModLib/UndertaleLists.cs @@ -2,8 +2,10 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.IO; +using System.Linq; using System.Reflection; using UndertaleModLib.Models; +using UndertaleModLib.Util; namespace UndertaleModLib { @@ -378,6 +380,36 @@ public static uint UnserializeChildObjectCount(UndertaleReader reader) if (count == 0) return 0; + long pointersStart = reader.Position; + uint[] pointers = reader.utListPtrsPool.Rent((int)count); + for (uint i = 0; i < count; i++) + { + uint pos = reader.ReadUInt32(); + if (pos == 0) + { + // Naturally this can only happen with 2024.11 data files. + // FIXME: Is this a good idea? + if (reader.undertaleData.GeneralInfo.BytecodeVersion >= 17) + { + if (!reader.undertaleData.IsVersionAtLeast(2024, 11)) + reader.undertaleData.SetGMS2Version(2024, 11); + } + else + { + reader.SubmitWarning("Null pointers found in pointer list on bytecode version pre-17!"); + } + i--; + count--; + continue; + } + pointers[i] = pos; + } + if (count == 0) + { + reader.utListPtrsPool.Return(pointers); + return 0; + } + uint totalCount = 0; Type t = typeof(T); @@ -389,42 +421,26 @@ public static uint UnserializeChildObjectCount(UndertaleReader reader) if (t.IsAssignableTo(typeof(IStaticChildObjCount))) subCount = reader.GetStaticChildCount(t); - reader.Position += count * 4 + count * subSize; + reader.Position = pointersStart + count * 4; + reader.Position += count * subSize; + reader.utListPtrsPool.Return(pointers); return count + count * subCount; } - uint[] pointers = reader.utListPtrsPool.Rent((int)count); - for (uint i = 0; i < count; i++) + uint firstItem = pointers.Where(x => x != 0).FirstOrDefault(); + if (firstItem == 0) { - uint pos = reader.ReadUInt32(); - if (pos == 0) - { - // Naturally this can only happen with 2024.11 data files. - // FIXME: Is this a good idea? - if (!reader.undertaleData.IsVersionAtLeast(2024, 11)) - reader.undertaleData.SetGMS2Version(2024, 11); - i--; - count--; - continue; - } - pointers[i] = pos; + reader.utListPtrsPool.Return(pointers); + return 0; } - - for (uint i = 0; i < count; i++) + if (reader.AbsPosition != firstItem) { - uint pos = pointers[i]; - if (pos == 0) - continue; - if (reader.AbsPosition != pos) - { - long skip = pos - reader.AbsPosition; - if (skip > 0) - reader.AbsPosition += skip; - else - throw new IOException("First list item starts inside the pointer list?!?!"); - } - break; + long skip = firstItem - reader.AbsPosition; + if (skip > 0) + reader.AbsPosition += skip; + else + throw new IOException("First list item starts inside the pointer list?!?!"); } var unserializeFunc = reader.GetUnserializeCountFunc(t); From c1fe7da0c9c20cb72ac5f893e519dd716955169a Mon Sep 17 00:00:00 2001 From: Liu Wenyuan <15816141883@163.com> Date: Sat, 18 Jan 2025 18:54:02 +0800 Subject: [PATCH 11/29] Improve UndertaleResourceById handling regarding deleted objects I'm not going to guess 2024.11 in AlignUpdatedListChunk because it's more likely to be bogus there --- UndertaleModLib/UndertaleIO.cs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/UndertaleModLib/UndertaleIO.cs b/UndertaleModLib/UndertaleIO.cs index 090804803..c9396c980 100644 --- a/UndertaleModLib/UndertaleIO.cs +++ b/UndertaleModLib/UndertaleIO.cs @@ -78,6 +78,13 @@ public int SerializeById(UndertaleWriter writer) } else { + // FIXME + if (CachedId > 0 || (typeof(ChunkT) != typeof(UndertaleChunkAGRP) && CachedId == 0)) + { + if (!writer.undertaleData.IsVersionAtLeast(2024, 11)) + throw new IOException("Tried to write an ID reference to a null object, which is abnormal before 2024.11"); + writer.SubmitMessage("Writing -1 as the ID reference to a null (likely deleted by GMAC) object, as we can't assure what its position in the relevant chunk is (DATA LOSS)"); + } if (typeof(ChunkT) == typeof(UndertaleChunkAGRP)) CachedId = 0; else @@ -111,6 +118,20 @@ public void PostUnserialize(UndertaleReader reader) return; } Resource = CachedId >= 0 ? list[CachedId] : default; + if (Resource == null && CachedId >= 0) + { + // Naturally this can only happen with 2024.11 data files. + // FIXME: Is this a good idea? + if (reader.undertaleData.GeneralInfo.BytecodeVersion >= 17) + { + if (!reader.undertaleData.IsVersionAtLeast(2024, 11)) + reader.undertaleData.SetGMS2Version(2024, 11); + } + else + { + reader.SubmitWarning("ID reference to null object found on bytecode version pre-17! File is likely corrupt and you won't be able to save."); + } + } } } From 475643ed39a587b7dcbf8a31ce00c5b72fa6bc84 Mon Sep 17 00:00:00 2001 From: Liu Wenyuan <15816141883@163.com> Date: Sat, 18 Jan 2025 19:33:34 +0800 Subject: [PATCH 12/29] Fix UTFont estimated child object count in 2024.11+ --- UndertaleModLib/Models/UndertaleFont.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/UndertaleModLib/Models/UndertaleFont.cs b/UndertaleModLib/Models/UndertaleFont.cs index de42ace43..ea63e786b 100644 --- a/UndertaleModLib/Models/UndertaleFont.cs +++ b/UndertaleModLib/Models/UndertaleFont.cs @@ -200,6 +200,8 @@ public void Unserialize(UndertaleReader reader) public static uint UnserializeChildObjectCount(UndertaleReader reader) { reader.Position += 14; + if (reader.undertaleData.IsVersionAtLeast(2024, 11)) + reader.Position += 2; return 1 + UndertaleSimpleListShort.UnserializeChildObjectCount(reader); } From 97e52850a53e8c84207b29bb3066049ec84c60d8 Mon Sep 17 00:00:00 2001 From: Liu Wenyuan <15816141883@163.com> Date: Sat, 18 Jan 2025 21:37:13 +0800 Subject: [PATCH 13/29] Add hidden setting ShowNullEntriesInDataHierarchy (disabled by default) Because: - It's not too useful by default - The nasty bug can make the tool nearly unusable Will add UI later --- .../DataHierarchyFilteredViewConverter.cs | 18 ++++++++++++++++++ .../Converters/FilteredViewConverter.cs | 15 ++++++++++----- UndertaleModTool/MainWindow.xaml | 2 +- UndertaleModTool/Settings.cs | 2 ++ 4 files changed, 31 insertions(+), 6 deletions(-) create mode 100644 UndertaleModTool/Converters/DataHierarchyFilteredViewConverter.cs diff --git a/UndertaleModTool/Converters/DataHierarchyFilteredViewConverter.cs b/UndertaleModTool/Converters/DataHierarchyFilteredViewConverter.cs new file mode 100644 index 000000000..720dbdcab --- /dev/null +++ b/UndertaleModTool/Converters/DataHierarchyFilteredViewConverter.cs @@ -0,0 +1,18 @@ +using System; + +namespace UndertaleModTool +{ + public partial class DataHierarchyFilteredViewConverter : FilteredViewConverter + { + internal override Predicate CreateFilter() + { + Predicate baseFilter = base.CreateFilter(); + return (obj) => + { + if (!Settings.Instance.ShowNullEntriesInDataHierarchy && obj is null) + return false; + return baseFilter(obj); + }; + } + } +} \ No newline at end of file diff --git a/UndertaleModTool/Converters/FilteredViewConverter.cs b/UndertaleModTool/Converters/FilteredViewConverter.cs index fa6bed3b0..a7cb48452 100644 --- a/UndertaleModTool/Converters/FilteredViewConverter.cs +++ b/UndertaleModTool/Converters/FilteredViewConverter.cs @@ -26,12 +26,9 @@ public string Filter set { SetValue(FilterProperty, value); } } - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + internal virtual Predicate CreateFilter() { - if (value == null) - return null; - ICollectionView filteredView = CollectionViewSource.GetDefaultView(value); - filteredView.Filter = (obj) => + return (obj) => { if (String.IsNullOrEmpty(Filter)) return true; @@ -44,6 +41,14 @@ public object Convert(object value, Type targetType, object parameter, CultureIn .Any(x => (x?.IndexOf(Filter, StringComparison.OrdinalIgnoreCase) ?? -1) >= 0); return true; }; + } + + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value == null) + return null; + ICollectionView filteredView = CollectionViewSource.GetDefaultView(value); + filteredView.Filter = CreateFilter(); return filteredView; } diff --git a/UndertaleModTool/MainWindow.xaml b/UndertaleModTool/MainWindow.xaml index 36f938d4b..2aa8bb33e 100644 --- a/UndertaleModTool/MainWindow.xaml +++ b/UndertaleModTool/MainWindow.xaml @@ -22,7 +22,7 @@ - + diff --git a/UndertaleModTool/Settings.cs b/UndertaleModTool/Settings.cs index 43df39e91..a38ba3e39 100644 --- a/UndertaleModTool/Settings.cs +++ b/UndertaleModTool/Settings.cs @@ -62,6 +62,8 @@ public class Settings public DecompilerSettings DecompilerSettings { get; set; } public string InstanceIdPrefix { get; set; } = "inst_"; + public bool ShowNullEntriesInDataHierarchy { get; set; } = false; + public static Settings Instance; public static JsonSerializerOptions JsonOptions = new() From f61b170d11438d7194f8b28d4c4cc232ae6f6c63 Mon Sep 17 00:00:00 2001 From: Liu Wenyuan <15816141883@163.com> Date: Sun, 19 Jan 2025 17:39:13 +0800 Subject: [PATCH 14/29] Clarify ResourceById resource == null && cachedid >= 0 error --- UndertaleModLib/UndertaleIO.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/UndertaleModLib/UndertaleIO.cs b/UndertaleModLib/UndertaleIO.cs index c9396c980..cb4c92ee6 100644 --- a/UndertaleModLib/UndertaleIO.cs +++ b/UndertaleModLib/UndertaleIO.cs @@ -82,8 +82,8 @@ public int SerializeById(UndertaleWriter writer) if (CachedId > 0 || (typeof(ChunkT) != typeof(UndertaleChunkAGRP) && CachedId == 0)) { if (!writer.undertaleData.IsVersionAtLeast(2024, 11)) - throw new IOException("Tried to write an ID reference to a null object, which is abnormal before 2024.11"); - writer.SubmitMessage("Writing -1 as the ID reference to a null (likely deleted by GMAC) object, as we can't assure what its position in the relevant chunk is (DATA LOSS)"); + throw new IOException("Tried to write an ID reference to a null object which had a cached ID, which is abnormal before 2024.11"); + writer.SubmitMessage("Writing -1 as the ID reference to a null object which had a cached ID (likely meaning it was deleted by GMAC), as we can't assure what its position in the relevant chunk is (DATA LOSS)"); } if (typeof(ChunkT) == typeof(UndertaleChunkAGRP)) CachedId = 0; From 60caf58fb1472c0e7c8ae882eeaebf3db36c3704 Mon Sep 17 00:00:00 2001 From: Liu Wenyuan <15816141883@163.com> Date: Tue, 21 Jan 2025 21:24:20 +0800 Subject: [PATCH 15/29] Raise 2024.11 detection requirement to GMS2, better approach for null ResById saving --- UndertaleModLib/UndertaleIO.cs | 24 ++++++++++++++---------- UndertaleModLib/UndertaleLists.cs | 4 ++-- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/UndertaleModLib/UndertaleIO.cs b/UndertaleModLib/UndertaleIO.cs index cb4c92ee6..874dc46b2 100644 --- a/UndertaleModLib/UndertaleIO.cs +++ b/UndertaleModLib/UndertaleIO.cs @@ -78,17 +78,21 @@ public int SerializeById(UndertaleWriter writer) } else { - // FIXME + int newCachedId; + if (typeof(ChunkT) == typeof(UndertaleChunkAGRP)) + newCachedId = 0; + else + newCachedId = -1; if (CachedId > 0 || (typeof(ChunkT) != typeof(UndertaleChunkAGRP) && CachedId == 0)) { - if (!writer.undertaleData.IsVersionAtLeast(2024, 11)) - throw new IOException("Tried to write an ID reference to a null object which had a cached ID, which is abnormal before 2024.11"); - writer.SubmitMessage("Writing -1 as the ID reference to a null object which had a cached ID (likely meaning it was deleted by GMAC), as we can't assure what its position in the relevant chunk is (DATA LOSS)"); + if (chunk.List[CachedId] is not null) + { + int firstNullOccurence = chunk.List.IndexOf(default(T)); + if (firstNullOccurence != -1) + newCachedId = firstNullOccurence; + } } - if (typeof(ChunkT) == typeof(UndertaleChunkAGRP)) - CachedId = 0; - else - CachedId = -1; + CachedId = newCachedId; } } return CachedId; @@ -122,14 +126,14 @@ public void PostUnserialize(UndertaleReader reader) { // Naturally this can only happen with 2024.11 data files. // FIXME: Is this a good idea? - if (reader.undertaleData.GeneralInfo.BytecodeVersion >= 17) + if (reader.undertaleData.IsGameMaker2()) { if (!reader.undertaleData.IsVersionAtLeast(2024, 11)) reader.undertaleData.SetGMS2Version(2024, 11); } else { - reader.SubmitWarning("ID reference to null object found on bytecode version pre-17! File is likely corrupt and you won't be able to save."); + reader.SubmitWarning("ID reference to null object found on file built with GMS pre-2!"); } } } diff --git a/UndertaleModLib/UndertaleLists.cs b/UndertaleModLib/UndertaleLists.cs index a9438a60a..a6e6523db 100644 --- a/UndertaleModLib/UndertaleLists.cs +++ b/UndertaleModLib/UndertaleLists.cs @@ -389,14 +389,14 @@ public static uint UnserializeChildObjectCount(UndertaleReader reader) { // Naturally this can only happen with 2024.11 data files. // FIXME: Is this a good idea? - if (reader.undertaleData.GeneralInfo.BytecodeVersion >= 17) + if (reader.undertaleData.IsGameMaker2()) { if (!reader.undertaleData.IsVersionAtLeast(2024, 11)) reader.undertaleData.SetGMS2Version(2024, 11); } else { - reader.SubmitWarning("Null pointers found in pointer list on bytecode version pre-17!"); + reader.SubmitWarning("Null pointers found in pointer list on file built with GMS pre-2!"); } i--; count--; From d1da607fba08d2f341d339cd1c3ac8b673066605 Mon Sep 17 00:00:00 2001 From: Liu Wenyuan <15816141883@163.com> Date: Tue, 21 Jan 2025 21:27:35 +0800 Subject: [PATCH 16/29] Forgot to use old CachedId if the resource ocuppying the slot is still null --- UndertaleModLib/UndertaleIO.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/UndertaleModLib/UndertaleIO.cs b/UndertaleModLib/UndertaleIO.cs index 874dc46b2..25fe0b6a0 100644 --- a/UndertaleModLib/UndertaleIO.cs +++ b/UndertaleModLib/UndertaleIO.cs @@ -87,10 +87,14 @@ public int SerializeById(UndertaleWriter writer) { if (chunk.List[CachedId] is not null) { - int firstNullOccurence = chunk.List.IndexOf(default(T)); + int firstNullOccurence = chunk.List.IndexOf(default); if (firstNullOccurence != -1) newCachedId = firstNullOccurence; } + else + { + newCachedId = CachedId; + } } CachedId = newCachedId; } From a1fbca9cecdcd55802d7ebef00d5abbbcc7fd5c2 Mon Sep 17 00:00:00 2001 From: Liu Wenyuan <15816141883@163.com> Date: Tue, 21 Jan 2025 21:34:18 +0800 Subject: [PATCH 17/29] Check list boundary and fix typo --- UndertaleModLib/UndertaleIO.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/UndertaleModLib/UndertaleIO.cs b/UndertaleModLib/UndertaleIO.cs index 25fe0b6a0..ea5bbdfeb 100644 --- a/UndertaleModLib/UndertaleIO.cs +++ b/UndertaleModLib/UndertaleIO.cs @@ -85,11 +85,11 @@ public int SerializeById(UndertaleWriter writer) newCachedId = -1; if (CachedId > 0 || (typeof(ChunkT) != typeof(UndertaleChunkAGRP) && CachedId == 0)) { - if (chunk.List[CachedId] is not null) + if (chunk.List.Count > CachedId && chunk.List[CachedId] is not null) { - int firstNullOccurence = chunk.List.IndexOf(default); - if (firstNullOccurence != -1) - newCachedId = firstNullOccurence; + int firstNullOccurrence = chunk.List.IndexOf(default); + if (firstNullOccurrence != -1) + newCachedId = firstNullOccurrence; } else { From 3633d1ef6817afc1a6c8b1f5c7923fc1e19a4b60 Mon Sep 17 00:00:00 2001 From: Liu Wenyuan <15816141883@163.com> Date: Wed, 5 Feb 2025 15:58:57 +0800 Subject: [PATCH 18/29] Try handling null in AddAssetsFromList --- UndertaleModLib/Compiler/Compiler.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/UndertaleModLib/Compiler/Compiler.cs b/UndertaleModLib/Compiler/Compiler.cs index 064746782..8022ff40e 100644 --- a/UndertaleModLib/Compiler/Compiler.cs +++ b/UndertaleModLib/Compiler/Compiler.cs @@ -205,7 +205,10 @@ private void AddAssetsFromList(IList list, RefType type) where T : Underta { for (int i = 0; i < list.Count; i++) { - string name = list[i].Name?.Content; + T item = list[i]; + if (item is null) + continue; + string name = item.Name?.Content; if (name != null) { // Typed asset refs pack their type into the ID @@ -217,7 +220,10 @@ private void AddAssetsFromList(IList list, RefType type) where T : Underta { for (int i = 0; i < list.Count; i++) { - string name = list[i].Name?.Content; + T item = list[i]; + if (item is null) + continue; + string name = item.Name?.Content; if (name != null) assetIds[name] = i; } From 6db95879985e79efadd873d96becf21a493ee408 Mon Sep 17 00:00:00 2001 From: Liu Wenyuan <15816141883@163.com> Date: Wed, 5 Feb 2025 16:17:58 +0800 Subject: [PATCH 19/29] Try handling null in GlobalDecompileContext --- .../Decompiler/GlobalDecompileContext.cs | 49 +++++++++++++------ 1 file changed, 35 insertions(+), 14 deletions(-) diff --git a/UndertaleModLib/Decompiler/GlobalDecompileContext.cs b/UndertaleModLib/Decompiler/GlobalDecompileContext.cs index 8699ab860..f51389854 100644 --- a/UndertaleModLib/Decompiler/GlobalDecompileContext.cs +++ b/UndertaleModLib/Decompiler/GlobalDecompileContext.cs @@ -86,6 +86,9 @@ private static IEnumerable GetGlobalScriptCodeEntries(UndertaleData dat { foreach (UndertaleGlobalInit script in data.GlobalInitScripts) { + // TODO: is this correct? + if (script is null || script.Code is null) + continue; yield return script.Code; } } @@ -98,6 +101,7 @@ public string GetAssetName(AssetType assetType, int assetIndex) return null; } + UndertaleNamedResource asset = null; switch (assetType) { case AssetType.Object: @@ -105,79 +109,92 @@ public string GetAssetName(AssetType assetType, int assetIndex) { return null; } - return Data.GameObjects[assetIndex].Name?.Content; + asset = Data.GameObjects[assetIndex]; + break; case AssetType.Sprite: if (assetIndex >= Data.Sprites.Count) { return null; } - return Data.Sprites[assetIndex].Name?.Content; + asset = Data.Sprites[assetIndex]; + break; case AssetType.Sound: if (assetIndex >= Data.Sounds.Count) { return null; } - return Data.Sounds[assetIndex].Name?.Content; + asset = Data.Sounds[assetIndex]; + break; case AssetType.Room: if (assetIndex >= Data.Rooms.Count) { return null; } - return Data.Rooms[assetIndex].Name?.Content; + asset = Data.Rooms[assetIndex]; + break; case AssetType.Background: if (assetIndex >= Data.Backgrounds.Count) { return null; } - return Data.Backgrounds[assetIndex].Name?.Content; + asset = Data.Backgrounds[assetIndex]; + break; case AssetType.Path: if (assetIndex >= Data.Paths.Count) { return null; } - return Data.Paths[assetIndex].Name?.Content; + asset = Data.Paths[assetIndex]; + break; case AssetType.Script: if (assetIndex >= Data.Scripts.Count) { return null; } - return Data.Scripts[assetIndex].Name?.Content; + asset = Data.Scripts[assetIndex]; + break; case AssetType.Font: if (assetIndex >= Data.Fonts.Count) { return null; } - return Data.Fonts[assetIndex].Name?.Content; + asset = Data.Fonts[assetIndex]; + break; case AssetType.Timeline: if (assetIndex >= Data.Timelines.Count) { return null; } - return Data.Timelines[assetIndex].Name?.Content; + asset = Data.Timelines[assetIndex]; + break; case AssetType.Shader: if (assetIndex >= Data.Shaders.Count) { return null; } - return Data.Shaders[assetIndex].Name?.Content; + asset = Data.Shaders[assetIndex]; + break; case AssetType.Sequence: if (assetIndex >= Data.Sequences.Count) { return null; } - return Data.Sequences[assetIndex].Name?.Content; + asset = Data.Sequences[assetIndex]; + break; case AssetType.AnimCurve: if (assetIndex >= Data.AnimationCurves.Count) { return null; } - return Data.AnimationCurves[assetIndex].Name?.Content; + asset = Data.AnimationCurves[assetIndex]; + break; case AssetType.ParticleSystem: if (assetIndex >= Data.ParticleSystems.Count) { return null; } - return Data.ParticleSystems[assetIndex].Name?.Content; + asset = Data.ParticleSystems[assetIndex]; + break; case AssetType.RoomInstance: if (assetIndex < 100000) { @@ -186,7 +203,11 @@ public string GetAssetName(AssetType assetType, int assetIndex) return $"{Data.ToolInfo.InstanceIdPrefix()}{assetIndex}"; } - return null; + if (asset is null) + { + return null; + } + return asset.Name?.Content; } public bool GetAssetId(string assetName, out int assetId) From 05cb9e71ee73c5ffa61f751bec7a97af60d75ecc Mon Sep 17 00:00:00 2001 From: Liu Wenyuan <15816141883@163.com> Date: Sun, 16 Feb 2025 13:37:53 +0800 Subject: [PATCH 20/29] Properly detect UnknownAlwaysZero in UTFont --- UndertaleModLib/Models/UndertaleFont.cs | 2 +- UndertaleModLib/UndertaleChunks.cs | 207 +++++++++++++++++------- 2 files changed, 150 insertions(+), 59 deletions(-) diff --git a/UndertaleModLib/Models/UndertaleFont.cs b/UndertaleModLib/Models/UndertaleFont.cs index ea63e786b..1f04bc8e1 100644 --- a/UndertaleModLib/Models/UndertaleFont.cs +++ b/UndertaleModLib/Models/UndertaleFont.cs @@ -201,7 +201,7 @@ public static uint UnserializeChildObjectCount(UndertaleReader reader) { reader.Position += 14; if (reader.undertaleData.IsVersionAtLeast(2024, 11)) - reader.Position += 2; + reader.Position += 2; // UnknownAlwaysZero return 1 + UndertaleSimpleListShort.UnserializeChildObjectCount(reader); } diff --git a/UndertaleModLib/UndertaleChunks.cs b/UndertaleModLib/UndertaleChunks.cs index d88348787..5d11340cb 100644 --- a/UndertaleModLib/UndertaleChunks.cs +++ b/UndertaleModLib/UndertaleChunks.cs @@ -691,7 +691,8 @@ public class UndertaleChunkFONT : UndertaleListChunk public byte[] Padding; private static bool checkedFor2022_2; - private static bool checkedFor2023_6; + private static bool checkedFor2023_6And2024_11; + private void CheckForGM2022_2(UndertaleReader reader) { /* This code performs four checks to identify GM2022.2. @@ -713,35 +714,52 @@ private void CheckForGM2022_2(UndertaleReader reader) long positionToReturn = reader.Position; bool GMS2022_2 = false; - if (reader.ReadUInt32() > 0) // Font count + uint possibleFontCount = reader.ReadUInt32(); + if (possibleFontCount > 0) { - uint firstFontPointer = reader.ReadUInt32(); - reader.AbsPosition = firstFontPointer + 48; // There are 48 bytes of existing metadata. - uint glyphsLength = reader.ReadUInt32(); - GMS2022_2 = true; - if ((glyphsLength * 4) > this.Length) + uint firstFontPointer = 0; + for (int i = 0; i < possibleFontCount; i++) { - GMS2022_2 = false; + uint fontPointer = reader.ReadUInt32(); + if (fontPointer != 0) + { + firstFontPointer = fontPointer; + break; + } } - else if (glyphsLength != 0) + if (firstFontPointer != 0) { - List glyphPointers = new List((int)glyphsLength); - for (uint i = 0; i < glyphsLength; i++) - glyphPointers.Add(reader.ReadUInt32()); - foreach (uint pointer in glyphPointers) + reader.AbsPosition = firstFontPointer + 48; // There are 48 bytes of existing metadata. + uint glyphsLength = reader.ReadUInt32(); + GMS2022_2 = true; + if ((glyphsLength * 4) > this.Length) + { + GMS2022_2 = false; + } + else if (glyphsLength != 0) { - if (reader.AbsPosition != pointer) + List glyphPointers = new List((int)glyphsLength); + for (uint i = 0; i < glyphsLength; i++) { - GMS2022_2 = false; - break; + uint glyphPointer = reader.ReadUInt32(); + if (glyphPointer == 0) + throw new IOException("One of the glyph pointers is null?"); + glyphPointers.Add(glyphPointer); } + foreach (uint pointer in glyphPointers) + { + if (reader.AbsPosition != pointer) + { + GMS2022_2 = false; + break; + } - reader.Position += 14; - ushort kerningLength = reader.ReadUInt16(); - reader.Position += (uint)4 * kerningLength; // combining read/write would apparently break + reader.Position += 14; + ushort kerningLength = reader.ReadUInt16(); + reader.Position += (uint)4 * kerningLength; // combining read/write would apparently break + } } } - } if (GMS2022_2) reader.undertaleData.SetGMS2Version(2022, 2); @@ -750,59 +768,132 @@ private void CheckForGM2022_2(UndertaleReader reader) checkedFor2022_2 = true; } - private void CheckForGM2023_6(UndertaleReader reader) - { - // This is basically the same as the 2022.2 check, but adapted for the LineHeight value instead of Ascender. - - // We already know whether the version is more or less than 2022.8 due to FEAT. Checking a shorter range narrows possibility of error. - // PSEM (2023.2) is not used, as it would return a false negative on LTS (2022.9+ equivalent with no particles). - if (!reader.undertaleData.IsVersionAtLeast(2022, 8) || reader.undertaleData.IsVersionAtLeast(2023, 6)) + private void CheckForGM2023_6AndGM2024_11(UndertaleReader reader) + { + /* + We already know whether the version is more or less than 2022.8 due to FEAT. + Checking a shorter range narrows possibility of error. + PSEM (2023.2) is not used, as it would return a false negative on LTS (2022.9+ equivalent with no particles). + Taking advantage of that, this is basically the same as the 2022.2 check, but it: + - Checks for the LineHeight value instead of Ascender (added in 2023.6) + - Checks for UnknownAlwaysZero in Glyphs (added in 2024.11) + It's possible for the null pointer check planted in UTPointerList deserialisation to not be triggered: + for example, if SDF is enabled for any fonts, the shaders related to SDF will not be stripped; + it's also possible to prevent audiogroup_default from being stripped by doing + audio_group_name(audiogroup_default) + So we check for the presence of UnknownAlwaysZero as a last resort. + */ + if (!reader.undertaleData.IsVersionAtLeast(2022, 8) || + (reader.undertaleData.IsVersionAtLeast(2023, 6) && !reader.undertaleData.IsVersionAtLeast(2024, 6)) || + reader.undertaleData.IsVersionAtLeast(2024, 11)) { - checkedFor2023_6 = true; + checkedFor2023_6And2024_11 = true; return; } - long positionToReturn = reader.Position; + long positionToReturn = reader.Position, startAbsPosition = reader.AbsPosition; bool GMS2023_6 = false; + bool GMS2024_11 = false; + bool GMS2024_11_Failed = false; - if (reader.ReadUInt32() > 0) // Font count + uint possibleFontCount = reader.ReadUInt32(); + if (possibleFontCount > 0) { - uint firstFontPointer = reader.ReadUInt32(); - reader.AbsPosition = firstFontPointer + 52; // Also the LineHeight value. 48 + 4 = 52. - if (reader.undertaleData.IsNonLTSVersionAtLeast(2023, 2)) // SDFSpread is present from 2023.2 non-LTS onward - reader.AbsPosition += 4; // (detected by PSEM/PSYS chunk existence) - - uint glyphsLength = reader.ReadUInt32(); - GMS2023_6 = true; - if ((glyphsLength * 4) > this.Length) + List firstAndNextFontPointers = new(2); + for (int i = 0; i < possibleFontCount; i++) { - GMS2023_6 = false; + uint fontPointer = reader.ReadUInt32(); + if (fontPointer != 0) + { + firstAndNextFontPointers.Add(fontPointer); + if (firstAndNextFontPointers.Count == 2) + break; + } + } + if (firstAndNextFontPointers.Count == 1) + { + // Add in the position of the padding + firstAndNextFontPointers.Add(startAbsPosition + Length - 512); } - else if (glyphsLength != 0) + if (firstAndNextFontPointers.Count > 0) { - List glyphPointers = new List((int)glyphsLength); - for (uint i = 0; i < glyphsLength; i++) - glyphPointers.Add(reader.ReadUInt32()); - foreach (uint pointer in glyphPointers) + reader.AbsPosition = firstAndNextFontPointers[0] + 52; // Also the LineHeight value. 48 + 4 = 52. + if (reader.undertaleData.IsNonLTSVersionAtLeast(2023, 2)) // SDFSpread is present from 2023.2 non-LTS onward + reader.AbsPosition += 4; // (detected by PSEM/PSYS chunk existence) + + uint glyphsLength = reader.ReadUInt32(); + GMS2023_6 = true; + if (glyphsLength * 4 > Length) { - if (reader.AbsPosition != pointer) + GMS2023_6 = false; + } + else if (glyphsLength != 0) + { + List glyphPointers = new((int)glyphsLength); + for (uint i = 0; i < glyphsLength; i++) { - GMS2023_6 = false; - break; + uint glyphPointer = reader.ReadUInt32(); + if (glyphPointer == 0) + throw new IOException("One of the glyph pointers is null?"); + glyphPointers.Add(glyphPointer); } + // Hopefully the last thing in a UTFont is the glyph list + long pointerAfterFirstGlyph = glyphPointers.Count > 1 ? glyphPointers[1] : firstAndNextFontPointers[1]; + foreach (uint pointer in glyphPointers) + { + if (reader.AbsPosition != pointer) + { + GMS2023_6 = false; + GMS2024_11 = false; + break; + } - reader.Position += 14; - ushort kerningLength = reader.ReadUInt16(); - reader.Position += (uint)4 * kerningLength; // combining read/write would apparently break + reader.Position += 14; + long kerningReadAttemptPosition = reader.Position; + ushort kerningLength = reader.ReadUInt16(); + if (!GMS2024_11_Failed) + { + if (!GMS2024_11) + { + // And hopefully the last thing in a glyph is the kerning list + long pointerAfterKerningList = reader.AbsPosition + (uint)4 * kerningLength; + // If we won't land into the next glyph just right + if (pointerAfterKerningList != pointerAfterFirstGlyph) + { + // Could be 2024.11... + kerningLength = reader.ReadUInt16(); // Discard last read + pointerAfterKerningList = reader.AbsPosition + (uint)4 * kerningLength; + if (pointerAfterKerningList != pointerAfterFirstGlyph) + throw new IOException("Detected that there are more/less values than just UnknownAlwaysZero before the kerning list"); + GMS2024_11 = true; + } + else + { + reader.Position = kerningReadAttemptPosition; + GMS2024_11_Failed = true; + } + } + else + { + // Discard last read + kerningLength = reader.ReadUInt16(); + } + } + reader.Position += (uint)4 * kerningLength; // combining read/write would apparently break + } } } - } - if (GMS2023_6) - reader.undertaleData.SetGMS2Version(2023, 6); + if (GMS2024_11) + reader.undertaleData.SetGMS2Version(2024, 11); + else if (GMS2023_6) + { + if (!reader.undertaleData.IsVersionAtLeast(2023, 6)) + reader.undertaleData.SetGMS2Version(2023, 6); + } reader.Position = positionToReturn; - checkedFor2023_6 = true; + checkedFor2023_6And2024_11 = true; } internal override void SerializeChunk(UndertaleWriter writer) @@ -824,8 +915,8 @@ internal override void UnserializeChunk(UndertaleReader reader) if (!checkedFor2022_2) CheckForGM2022_2(reader); - if (!checkedFor2023_6) - CheckForGM2023_6(reader); + if (!checkedFor2023_6And2024_11) + CheckForGM2023_6AndGM2024_11(reader); base.UnserializeChunk(reader); @@ -835,10 +926,10 @@ internal override void UnserializeChunk(UndertaleReader reader) internal override uint UnserializeObjectCount(UndertaleReader reader) { checkedFor2022_2 = false; - checkedFor2023_6 = false; + checkedFor2023_6And2024_11 = false; CheckForGM2022_2(reader); - CheckForGM2023_6(reader); + CheckForGM2023_6AndGM2024_11(reader); return base.UnserializeObjectCount(reader); } From aff55170d76c9997cdfdc70aa24a0db2d3c322d1 Mon Sep 17 00:00:00 2001 From: Liu Wenyuan <15816141883@163.com> Date: Sun, 16 Feb 2025 13:42:08 +0800 Subject: [PATCH 21/29] Cleanup --- UndertaleModLib/UndertaleChunks.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/UndertaleModLib/UndertaleChunks.cs b/UndertaleModLib/UndertaleChunks.cs index 5d11340cb..69c07757e 100644 --- a/UndertaleModLib/UndertaleChunks.cs +++ b/UndertaleModLib/UndertaleChunks.cs @@ -849,27 +849,24 @@ Checking a shorter range narrows possibility of error. } reader.Position += 14; - long kerningReadAttemptPosition = reader.Position; ushort kerningLength = reader.ReadUInt16(); if (!GMS2024_11_Failed) { if (!GMS2024_11) { // And hopefully the last thing in a glyph is the kerning list - long pointerAfterKerningList = reader.AbsPosition + (uint)4 * kerningLength; + long pointerAfterKerningList = reader.AbsPosition + 4 * kerningLength; // If we won't land into the next glyph just right if (pointerAfterKerningList != pointerAfterFirstGlyph) { - // Could be 2024.11... kerningLength = reader.ReadUInt16(); // Discard last read - pointerAfterKerningList = reader.AbsPosition + (uint)4 * kerningLength; + pointerAfterKerningList = reader.AbsPosition + 4 * kerningLength; if (pointerAfterKerningList != pointerAfterFirstGlyph) - throw new IOException("Detected that there are more/less values than just UnknownAlwaysZero before the kerning list"); + throw new IOException("Detected that there are more/less values than UnknownAlwaysZero before the kerning list"); GMS2024_11 = true; } else { - reader.Position = kerningReadAttemptPosition; GMS2024_11_Failed = true; } } @@ -879,11 +876,12 @@ Checking a shorter range narrows possibility of error. kerningLength = reader.ReadUInt16(); } } - reader.Position += (uint)4 * kerningLength; // combining read/write would apparently break + reader.Position += 4 * kerningLength; // combining read/write would apparently break } } } } + if (GMS2024_11) reader.undertaleData.SetGMS2Version(2024, 11); else if (GMS2023_6) From 612b98c1c9333fdbdb1990aa7193178d2155c9c1 Mon Sep 17 00:00:00 2001 From: Liu Wenyuan <15816141883@163.com> Date: Sun, 16 Feb 2025 14:03:01 +0800 Subject: [PATCH 22/29] Cleanup, properly calculate next glyph position, better safe than sorry --- UndertaleModLib/UndertaleChunks.cs | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/UndertaleModLib/UndertaleChunks.cs b/UndertaleModLib/UndertaleChunks.cs index 69c07757e..e61b0b4d9 100644 --- a/UndertaleModLib/UndertaleChunks.cs +++ b/UndertaleModLib/UndertaleChunks.cs @@ -791,9 +791,10 @@ Checking a shorter range narrows possibility of error. return; } - long positionToReturn = reader.Position, startAbsPosition = reader.AbsPosition; + long positionToReturn = reader.AbsPosition; bool GMS2023_6 = false; bool GMS2024_11 = false; + // When this is set to true the detection logic will not run again bool GMS2024_11_Failed = false; uint possibleFontCount = reader.ReadUInt32(); @@ -812,9 +813,10 @@ Checking a shorter range narrows possibility of error. } if (firstAndNextFontPointers.Count == 1) { - // Add in the position of the padding - firstAndNextFontPointers.Add(startAbsPosition + Length - 512); + // Add in the position of the padding i.e. the end of the font list + firstAndNextFontPointers.Add(positionToReturn + Length - 512); } + if (firstAndNextFontPointers.Count > 0) { reader.AbsPosition = firstAndNextFontPointers[0] + 52; // Also the LineHeight value. 48 + 4 = 52. @@ -823,7 +825,7 @@ Checking a shorter range narrows possibility of error. uint glyphsLength = reader.ReadUInt32(); GMS2023_6 = true; - if (glyphsLength * 4 > Length) + if (glyphsLength * 4 > (firstAndNextFontPointers[1] - reader.AbsPosition)) { GMS2023_6 = false; } @@ -837,10 +839,9 @@ Checking a shorter range narrows possibility of error. throw new IOException("One of the glyph pointers is null?"); glyphPointers.Add(glyphPointer); } - // Hopefully the last thing in a UTFont is the glyph list - long pointerAfterFirstGlyph = glyphPointers.Count > 1 ? glyphPointers[1] : firstAndNextFontPointers[1]; - foreach (uint pointer in glyphPointers) + for (int i = 0; i < glyphPointers.Count; i++) { + uint pointer = glyphPointers[i]; if (reader.AbsPosition != pointer) { GMS2023_6 = false; @@ -854,15 +855,17 @@ Checking a shorter range narrows possibility of error. { if (!GMS2024_11) { + // Hopefully the last thing in a UTFont is the glyph list + long pointerNextGlyph = i < (glyphPointers.Count - 1) ? glyphPointers[i + 1] : firstAndNextFontPointers[1]; // And hopefully the last thing in a glyph is the kerning list long pointerAfterKerningList = reader.AbsPosition + 4 * kerningLength; // If we won't land into the next glyph just right - if (pointerAfterKerningList != pointerAfterFirstGlyph) + if (pointerAfterKerningList != pointerNextGlyph) { kerningLength = reader.ReadUInt16(); // Discard last read pointerAfterKerningList = reader.AbsPosition + 4 * kerningLength; - if (pointerAfterKerningList != pointerAfterFirstGlyph) - throw new IOException("Detected that there are more/less values than UnknownAlwaysZero before the kerning list"); + if (pointerAfterKerningList != pointerNextGlyph) + reader.SubmitWarning("There appears to be more/less values than UnknownAlwaysZero before the kerning list in a UTFont.Glyph - potential data loss"); GMS2024_11 = true; } else @@ -889,7 +892,7 @@ Checking a shorter range narrows possibility of error. if (!reader.undertaleData.IsVersionAtLeast(2023, 6)) reader.undertaleData.SetGMS2Version(2023, 6); } - reader.Position = positionToReturn; + reader.AbsPosition = positionToReturn; checkedFor2023_6And2024_11 = true; } From 9e18dd6d78822f3ce5b0317431a1664650f929b1 Mon Sep 17 00:00:00 2001 From: Liu Wenyuan <15816141883@163.com> Date: Sun, 16 Feb 2025 14:05:15 +0800 Subject: [PATCH 23/29] Make the comment more coherent --- UndertaleModLib/UndertaleChunks.cs | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/UndertaleModLib/UndertaleChunks.cs b/UndertaleModLib/UndertaleChunks.cs index e61b0b4d9..93a975867 100644 --- a/UndertaleModLib/UndertaleChunks.cs +++ b/UndertaleModLib/UndertaleChunks.cs @@ -771,17 +771,16 @@ private void CheckForGM2022_2(UndertaleReader reader) private void CheckForGM2023_6AndGM2024_11(UndertaleReader reader) { /* - We already know whether the version is more or less than 2022.8 due to FEAT. - Checking a shorter range narrows possibility of error. - PSEM (2023.2) is not used, as it would return a false negative on LTS (2022.9+ equivalent with no particles). - Taking advantage of that, this is basically the same as the 2022.2 check, but it: - - Checks for the LineHeight value instead of Ascender (added in 2023.6) - - Checks for UnknownAlwaysZero in Glyphs (added in 2024.11) - It's possible for the null pointer check planted in UTPointerList deserialisation to not be triggered: - for example, if SDF is enabled for any fonts, the shaders related to SDF will not be stripped; - it's also possible to prevent audiogroup_default from being stripped by doing - audio_group_name(audiogroup_default) - So we check for the presence of UnknownAlwaysZero as a last resort. + We already know whether the version is more or less than 2022.8 due to the FEAT chunk being present. + Taking advantage of that, this is basically the same as the 2022.2 check, but it: + - Checks for the LineHeight value instead of Ascender (added in 2023.6) + PSEM (2023.2) is not used, as it would return a false negative on LTS (2022.9+ equivalent with no particles). + - Checks for UnknownAlwaysZero in Glyphs (added in 2024.11) + It's possible for the null pointer check planted in UTPointerList deserialisation to not be triggered: + for example, if SDF is enabled for any fonts, the shaders related to SDF will not be stripped; + it's also possible to prevent audiogroup_default from being stripped by doing + audio_group_name(audiogroup_default) + So we check for the presence of UnknownAlwaysZero as a last resort. */ if (!reader.undertaleData.IsVersionAtLeast(2022, 8) || (reader.undertaleData.IsVersionAtLeast(2023, 6) && !reader.undertaleData.IsVersionAtLeast(2024, 6)) || From e89d79415de36bc502647e27a9558ee06701dd58 Mon Sep 17 00:00:00 2001 From: Liu Wenyuan <15816141883@163.com> Date: Sun, 16 Feb 2025 14:38:19 +0800 Subject: [PATCH 24/29] I promise this is the last cleanup --- UndertaleModLib/UndertaleChunks.cs | 156 ++++++++++++++++------------- 1 file changed, 87 insertions(+), 69 deletions(-) diff --git a/UndertaleModLib/UndertaleChunks.cs b/UndertaleModLib/UndertaleChunks.cs index 93a975867..4b22797b7 100644 --- a/UndertaleModLib/UndertaleChunks.cs +++ b/UndertaleModLib/UndertaleChunks.cs @@ -793,106 +793,124 @@ We already know whether the version is more or less than 2022.8 due to the FEAT long positionToReturn = reader.AbsPosition; bool GMS2023_6 = false; bool GMS2024_11 = false; - // When this is set to true the detection logic will not run again - bool GMS2024_11_Failed = false; uint possibleFontCount = reader.ReadUInt32(); - if (possibleFontCount > 0) + if (possibleFontCount <= 0) { - List firstAndNextFontPointers = new(2); - for (int i = 0; i < possibleFontCount; i++) + // No way to know anything + reader.AbsPosition = positionToReturn; + checkedFor2023_6And2024_11 = true; + return; + } + + List firstAndNextFontPointers = new(2); + for (int i = 0; i < possibleFontCount; i++) + { + uint fontPointer = reader.ReadUInt32(); + if (fontPointer != 0) { - uint fontPointer = reader.ReadUInt32(); - if (fontPointer != 0) - { - firstAndNextFontPointers.Add(fontPointer); - if (firstAndNextFontPointers.Count == 2) - break; - } + firstAndNextFontPointers.Add(fontPointer); + if (firstAndNextFontPointers.Count == 2) + break; } - if (firstAndNextFontPointers.Count == 1) + } + + if (firstAndNextFontPointers.Count == 0) + { + // No way to know anything + reader.AbsPosition = positionToReturn; + checkedFor2023_6And2024_11 = true; + return; + } + + if (firstAndNextFontPointers.Count == 1) + { + // Add in the position of the padding i.e. the end of the font list + firstAndNextFontPointers.Add(positionToReturn + Length - 512); + } + + reader.AbsPosition = firstAndNextFontPointers[0] + 52; // Also the LineHeight value. 48 + 4 = 52. + if (reader.undertaleData.IsNonLTSVersionAtLeast(2023, 2)) // SDFSpread is present from 2023.2 non-LTS onward + reader.AbsPosition += 4; // (detected by PSEM/PSYS chunk existence) + + uint glyphsLength = reader.ReadUInt32(); + GMS2023_6 = true; + if (glyphsLength * 4 > firstAndNextFontPointers[1] - reader.AbsPosition) + { + GMS2023_6 = false; + } + else if (glyphsLength != 0) + { + List glyphPointers = new((int)glyphsLength); + for (uint i = 0; i < glyphsLength; i++) { - // Add in the position of the padding i.e. the end of the font list - firstAndNextFontPointers.Add(positionToReturn + Length - 512); + uint glyphPointer = reader.ReadUInt32(); + if (glyphPointer == 0) + throw new IOException("One of the glyph pointers is null?"); + glyphPointers.Add(glyphPointer); } - if (firstAndNextFontPointers.Count > 0) + // When this is set to true the detection logic will not run again + bool GMS2024_11_Failed = false; + for (int i = 0; i < glyphPointers.Count; i++) { - reader.AbsPosition = firstAndNextFontPointers[0] + 52; // Also the LineHeight value. 48 + 4 = 52. - if (reader.undertaleData.IsNonLTSVersionAtLeast(2023, 2)) // SDFSpread is present from 2023.2 non-LTS onward - reader.AbsPosition += 4; // (detected by PSEM/PSYS chunk existence) - - uint glyphsLength = reader.ReadUInt32(); - GMS2023_6 = true; - if (glyphsLength * 4 > (firstAndNextFontPointers[1] - reader.AbsPosition)) + if (reader.AbsPosition != glyphPointers[i]) { GMS2023_6 = false; + GMS2024_11 = false; + break; } - else if (glyphsLength != 0) + + reader.Position += 14; + ushort kerningLength = reader.ReadUInt16(); + if (!GMS2024_11_Failed) { - List glyphPointers = new((int)glyphsLength); - for (uint i = 0; i < glyphsLength; i++) - { - uint glyphPointer = reader.ReadUInt32(); - if (glyphPointer == 0) - throw new IOException("One of the glyph pointers is null?"); - glyphPointers.Add(glyphPointer); - } - for (int i = 0; i < glyphPointers.Count; i++) + if (!GMS2024_11) { - uint pointer = glyphPointers[i]; - if (reader.AbsPosition != pointer) + // Hopefully the last thing in a UTFont is the glyph list + long pointerNextGlyph = i < (glyphPointers.Count - 1) ? glyphPointers[i + 1] : firstAndNextFontPointers[1]; + // And hopefully the last thing in a glyph is the kerning list + // Note that we're actually skipping all items of the Glyph.Kerning SimpleList here; + // 4 is supposed to be the size of a GlyphKerning object + long pointerAfterKerningList = reader.AbsPosition + 4 * kerningLength; + // If we don't land on the next glyph/font after skipping the Kerning list, + // kerningLength is probably bogus and UnknownAlwaysZero may be present + if (pointerAfterKerningList != pointerNextGlyph) { - GMS2023_6 = false; - GMS2024_11 = false; - break; + // Discard last read, which would be of UnknownAlwaysZero + kerningLength = reader.ReadUInt16(); + pointerAfterKerningList = reader.AbsPosition + 4 * kerningLength; + if (pointerAfterKerningList != pointerNextGlyph) + reader.SubmitWarning("There appears to be more/less values than UnknownAlwaysZero before " + + "the kerning list in a UTFont.Glyph - potential data loss"); + GMS2024_11 = true; } - - reader.Position += 14; - ushort kerningLength = reader.ReadUInt16(); - if (!GMS2024_11_Failed) + else { - if (!GMS2024_11) - { - // Hopefully the last thing in a UTFont is the glyph list - long pointerNextGlyph = i < (glyphPointers.Count - 1) ? glyphPointers[i + 1] : firstAndNextFontPointers[1]; - // And hopefully the last thing in a glyph is the kerning list - long pointerAfterKerningList = reader.AbsPosition + 4 * kerningLength; - // If we won't land into the next glyph just right - if (pointerAfterKerningList != pointerNextGlyph) - { - kerningLength = reader.ReadUInt16(); // Discard last read - pointerAfterKerningList = reader.AbsPosition + 4 * kerningLength; - if (pointerAfterKerningList != pointerNextGlyph) - reader.SubmitWarning("There appears to be more/less values than UnknownAlwaysZero before the kerning list in a UTFont.Glyph - potential data loss"); - GMS2024_11 = true; - } - else - { - GMS2024_11_Failed = true; - } - } - else - { - // Discard last read - kerningLength = reader.ReadUInt16(); - } + GMS2024_11_Failed = true; } - reader.Position += 4 * kerningLength; // combining read/write would apparently break + } + else + { + // Discard last read, which would be of UnknownAlwaysZero + kerningLength = reader.ReadUInt16(); } } + reader.Position += 4 * kerningLength; // combining read/write would apparently break } } if (GMS2024_11) + { reader.undertaleData.SetGMS2Version(2024, 11); + } else if (GMS2023_6) { if (!reader.undertaleData.IsVersionAtLeast(2023, 6)) reader.undertaleData.SetGMS2Version(2023, 6); } - reader.AbsPosition = positionToReturn; + reader.AbsPosition = positionToReturn; checkedFor2023_6And2024_11 = true; } From 392ae25ea388432269eede84bde82065082a638b Mon Sep 17 00:00:00 2001 From: Liu Wenyuan <15816141883@163.com> Date: Sun, 16 Feb 2025 15:06:37 +0800 Subject: [PATCH 25/29] Fix my broken XML comments --- .../Controls/ResourceListTreeViewItem.xaml.cs | 19 +++++++------- .../NullConditionalDataTemplateSelector.cs | 25 +++++++++---------- 2 files changed, 21 insertions(+), 23 deletions(-) diff --git a/UndertaleModTool/Controls/ResourceListTreeViewItem.xaml.cs b/UndertaleModTool/Controls/ResourceListTreeViewItem.xaml.cs index 75f1e5e55..a93788956 100644 --- a/UndertaleModTool/Controls/ResourceListTreeViewItem.xaml.cs +++ b/UndertaleModTool/Controls/ResourceListTreeViewItem.xaml.cs @@ -8,20 +8,19 @@ namespace UndertaleModTool { + /// + /// TreeViewItem for representing UndertaleResources in the MainWindow data hirearchy + /// public partial class ResourceListTreeViewItem : TreeViewItem { - // - // TreeViewItem for representing UndertaleResources in the MainWindow data hirearchy - // - // We can't just use the stock ItemTemplate property, else our Selector will not run public static readonly DependencyProperty DefaultItemTemplateProperty = DependencyProperty.Register( "DefaultItemTemplate", typeof(DataTemplate), typeof(ResourceListTreeViewItem), new FrameworkPropertyMetadata(default(DataTemplate), FrameworkPropertyMetadataOptions.AffectsRender, OnDefaultItemTemplateChanged)); - // - // Template to use if a resource is not null - // + /// + /// Template to use if a resource is not null + /// [Bindable(true), Category("Content")] public DataTemplate DefaultItemTemplate { @@ -35,9 +34,9 @@ public DataTemplate DefaultItemTemplate private DataTemplate _defaultNullItemTemplateCache; - // - // Template to use if a resource is null - // + /// + /// Template to use if a resource is null + /// [Bindable(true), Category("Content")] public DataTemplate NullItemTemplate { diff --git a/UndertaleModTool/NullConditionalDataTemplateSelector.cs b/UndertaleModTool/NullConditionalDataTemplateSelector.cs index 7bbfe3cce..e83d6507a 100644 --- a/UndertaleModTool/NullConditionalDataTemplateSelector.cs +++ b/UndertaleModTool/NullConditionalDataTemplateSelector.cs @@ -3,23 +3,22 @@ namespace UndertaleModTool { + /// + /// Selects a DataTemplate based on whether the item is null or not. + /// + /// + /// Used by to select the appropriate template for an item. + /// public class NullConditionalDataTemplateSelector : DataTemplateSelector { - // - // Selects a DataTemplate based on whether the item is null or not. - // - // - // Used by to select the appropriate template for an item. - // - - // - // The template to use if the item is not null. - // + /// + /// The template to use if the item is not null. + /// public DataTemplate NonNullTemplate { get; set; } - // - // The template to use if the item is null. - // + /// + /// The template to use if the item is null. + /// public DataTemplate NullTemplate { get; set; } public override DataTemplate SelectTemplate(object item, DependencyObject container) From 4723e12212e3945429905dd29bac895a0c7981be Mon Sep 17 00:00:00 2001 From: Liu Wenyuan <15816141883@163.com> Date: Sun, 16 Feb 2025 15:18:40 +0800 Subject: [PATCH 26/29] Transform gm2024_11_WhatToSkip to BitArray --- UndertaleModLib/UndertaleChunkTypes.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/UndertaleModLib/UndertaleChunkTypes.cs b/UndertaleModLib/UndertaleChunkTypes.cs index 17af3b252..9e159d3c2 100644 --- a/UndertaleModLib/UndertaleChunkTypes.cs +++ b/UndertaleModLib/UndertaleChunkTypes.cs @@ -283,9 +283,9 @@ internal override void UnserializeChunk(UndertaleReader reader) uint count = reader.ReadUInt32(); List.SetCapacity(count); uint realCount = count; - bool[] gm2024_11_WhatToSkip = null; + BitArray gm2024_11_WhatToSkip = null; if (reader.undertaleData.IsVersionAtLeast(2024, 11) && count > 0) - gm2024_11_WhatToSkip = new bool[count]; + gm2024_11_WhatToSkip = new((int)count, false); for (int i = 0; i < count; i++) { @@ -296,7 +296,7 @@ internal override void UnserializeChunk(UndertaleReader reader) if (reader.undertaleData.IsVersionAtLeast(2024, 11) && gm2024_11_WhatToSkip is not null) { // This is "normal" and is likely a object removed by GMAC. - gm2024_11_WhatToSkip[i] = true; + gm2024_11_WhatToSkip.Set(i, true); continue; } @@ -305,7 +305,7 @@ internal override void UnserializeChunk(UndertaleReader reader) for (int i = 0; i < realCount; i++) { - if (gm2024_11_WhatToSkip is not null && gm2024_11_WhatToSkip[i]) + if (gm2024_11_WhatToSkip is not null && gm2024_11_WhatToSkip.Get(i)) { List.InternalAdd(default); continue; From 3958ec8e1aee881df93655dc4aabba7351a7eccd Mon Sep 17 00:00:00 2001 From: Liu Wenyuan <15816141883@163.com> Date: Sun, 16 Feb 2025 15:59:40 +0800 Subject: [PATCH 27/29] Null detection attempt Import/ExportAllStrings and find all references are too scary. Bytecode conversion scripts are ignored. --- UndertaleModLib/Compiler/Compiler.cs | 26 +++--- .../Models/UndertaleEmbeddedTexture.cs | 11 ++- .../UndertaleRoomEditor.xaml.cs | 2 + .../Scripts/Builtin Scripts/DeltaHATE.csx | 2 + .../Scripts/Builtin Scripts/DeltaMILK.csx | 2 + .../Builtin Scripts/FindAndReplace.csx | 5 ++ .../Scripts/Builtin Scripts/SearchLimited.csx | 4 + .../UndertaleDialogSimulator.csx | 2 + .../Community Scripts/ExternalizeAllOGGs.csx | 17 +++- .../Community Scripts/FancyRoomSelect.csx | 2 + .../Scripts/Community Scripts/FontEditor.csx | 4 + .../Community Scripts/ScaleAllTextures.csx | 4 + .../Community Scripts/VariableFixer.csx | 2 + .../Resource Repackers/CopySoundInternal.csx | 2 + .../Resource Repackers/CopySpriteBgFont.csx | 12 +++ .../CopySpriteBgFontInternal.csx | 12 +++ .../ImportAllEmbeddedTextures.csx | 8 +- .../Resource Repackers/ImportAssetOrder.csx | 10 ++- .../Resource Repackers/NewTextureRepacker.csx | 2 + .../Resource Repackers/RemoveUnusedSounds.csx | 5 +- .../Resource Repackers/SpriteOriginCopy.csx | 2 + .../Resource Unpackers/DumpSpecificCode.csx | 8 +- .../Resource Unpackers/ExportAllSounds.csx | 11 ++- .../Resource Unpackers/ExportAssetOrder.csx | 80 ++++++------------- .../Resource Unpackers/ExportFontData.csx | 3 +- .../Resource Unpackers/ExportShaderData.csx | 4 +- .../ExportSpecificSprites.csx | 2 + .../ExportTextureGroups.csx | 2 + .../Technical Scripts/ExecutionOrder.csx | 7 ++ .../ExportAndConvert_2_3_ASM.csx | 6 ++ .../FindUnknownFunctions.csx | 8 ++ .../Technical Scripts/FindUnusedStrings.csx | 44 ++++++++++ .../GoToRoom_AutoLocatePersistentObj.csx | 2 + .../Scripts/Technical Scripts/Profiler.csx | 9 +++ .../RestoreMissingCodeLocals.csx | 4 +- .../Technical Scripts/TestExportAllCode.csx | 5 ++ 36 files changed, 248 insertions(+), 83 deletions(-) diff --git a/UndertaleModLib/Compiler/Compiler.cs b/UndertaleModLib/Compiler/Compiler.cs index 8022ff40e..446d60d31 100644 --- a/UndertaleModLib/Compiler/Compiler.cs +++ b/UndertaleModLib/Compiler/Compiler.cs @@ -175,23 +175,25 @@ private void MakeAssetDictionary() AddAssetsFromList(Data.Sequences, RefType.Sequence); AddAssetsFromList(Data.ParticleSystems, RefType.ParticleSystem); - if (Data.Scripts is not null) + foreach (UndertaleScript s in Data.Scripts) { - foreach (UndertaleScript s in Data.Scripts) - { - scripts.Add(s.Name.Content); - } + if (s is null) + continue; + scripts.Add(s.Name.Content); } - if (Data.Extensions is not null) + foreach (UndertaleExtension e in Data.Extensions) { - foreach (UndertaleExtension e in Data.Extensions) + if (e is null) + continue; + foreach (UndertaleExtensionFile file in e.Files) { - foreach (UndertaleExtensionFile file in e.Files) + if (file is null) + continue; + foreach (UndertaleExtensionFunction func in file.Functions?) { - foreach (UndertaleExtensionFunction func in file.Functions) - { - scripts.Add(func.Name.Content); - } + if (func is null) + continue; + scripts.Add(func.Name.Content); } } } diff --git a/UndertaleModLib/Models/UndertaleEmbeddedTexture.cs b/UndertaleModLib/Models/UndertaleEmbeddedTexture.cs index 019562ebe..3246e3719 100644 --- a/UndertaleModLib/Models/UndertaleEmbeddedTexture.cs +++ b/UndertaleModLib/Models/UndertaleEmbeddedTexture.cs @@ -218,12 +218,15 @@ public static uint UnserializeChildObjectCount(UndertaleReader reader) /// public static void FindAllTextureInfo(UndertaleData data) { - if (data.TextureGroupInfo != null) + foreach (var info in data.TextureGroupInfo) { - foreach (var info in data.TextureGroupInfo) + if (info is null) + continue; + foreach (var tex in info.TexturePages) { - foreach (var tex in info.TexturePages) - tex.Resource.TextureInfo = info; + if (tex is null) + continue; + tex.Resource.TextureInfo = info; } } } diff --git a/UndertaleModTool/Editors/UndertaleRoomEditor/UndertaleRoomEditor.xaml.cs b/UndertaleModTool/Editors/UndertaleRoomEditor/UndertaleRoomEditor.xaml.cs index 1383c3fdf..de943a33f 100644 --- a/UndertaleModTool/Editors/UndertaleRoomEditor/UndertaleRoomEditor.xaml.cs +++ b/UndertaleModTool/Editors/UndertaleRoomEditor/UndertaleRoomEditor.xaml.cs @@ -1306,6 +1306,8 @@ public void Command_Paste(object sender, ExecutedRoutedEventArgs e) // See #355 foreach (UndertaleRoom Room in data.Rooms) { + if (Room is null) + continue; foreach (Layer Layer in Room.Layers) { if (Layer.LayerId > largest_layerid) diff --git a/UndertaleModTool/Scripts/Builtin Scripts/DeltaHATE.csx b/UndertaleModTool/Scripts/Builtin Scripts/DeltaHATE.csx index a2fd7b267..122b7ba1a 100644 --- a/UndertaleModTool/Scripts/Builtin Scripts/DeltaHATE.csx +++ b/UndertaleModTool/Scripts/Builtin Scripts/DeltaHATE.csx @@ -236,6 +236,8 @@ else foreach (var obj in Data.GameObjects) { + if (obj is null) + continue; if (!obj.Visible) continue; if (obj._sprite.CachedId >= 0) diff --git a/UndertaleModTool/Scripts/Builtin Scripts/DeltaMILK.csx b/UndertaleModTool/Scripts/Builtin Scripts/DeltaMILK.csx index aafb59b9f..fb1e9acc5 100644 --- a/UndertaleModTool/Scripts/Builtin Scripts/DeltaMILK.csx +++ b/UndertaleModTool/Scripts/Builtin Scripts/DeltaMILK.csx @@ -5,6 +5,8 @@ ScriptMessage("Select the MILK that you prefer\nReplace every non-background spr var milk = Data.Sprites.ByName("spr_checkers_milk").Textures[0].Texture; foreach (var sprite in Data.Sprites) { + if (sprite is null) + continue; if (sprite.Name.Content.StartsWith("bg_")) continue; foreach (var tex in sprite.Textures) diff --git a/UndertaleModTool/Scripts/Builtin Scripts/FindAndReplace.csx b/UndertaleModTool/Scripts/Builtin Scripts/FindAndReplace.csx index 4555216af..e40432757 100644 --- a/UndertaleModTool/Scripts/Builtin Scripts/FindAndReplace.csx +++ b/UndertaleModTool/Scripts/Builtin Scripts/FindAndReplace.csx @@ -37,6 +37,11 @@ await Task.Run(() => GlobalDecompileContext globalDecompileContext = new(Data); foreach (UndertaleCode code in Data.Code) { + if (code is null) + { + IncrementProgress(); + continue; + } ReplaceTextInGML(code.Name.Content, keyword, replacement, case_sensitive, isRegex, globalDecompileContext); IncrementProgress(); } diff --git a/UndertaleModTool/Scripts/Builtin Scripts/SearchLimited.csx b/UndertaleModTool/Scripts/Builtin Scripts/SearchLimited.csx index 62233005c..6d501f9d9 100644 --- a/UndertaleModTool/Scripts/Builtin Scripts/SearchLimited.csx +++ b/UndertaleModTool/Scripts/Builtin Scripts/SearchLimited.csx @@ -68,6 +68,8 @@ for (var j = 0; j < searchNamesList.Length; j++) { foreach (UndertaleGameObject obj in Data.GameObjects) { + if (obj is null) + continue; if (searchNamesList[j].ToLower() == obj.Name.Content.ToLower()) { gameObjectCandidates.Add(obj.Name.Content); @@ -75,6 +77,8 @@ for (var j = 0; j < searchNamesList.Length; j++) } foreach (UndertaleCode code in Data.Code) { + if (code is null) + continue; if (searchNamesList[j].ToLower() == code.Name.Content.ToLower()) { codeToDump.Add(code.Name.Content); diff --git a/UndertaleModTool/Scripts/Builtin Scripts/UndertaleDialogSimulator.csx b/UndertaleModTool/Scripts/Builtin Scripts/UndertaleDialogSimulator.csx index 56a6d021c..4643a18c4 100644 --- a/UndertaleModTool/Scripts/Builtin Scripts/UndertaleDialogSimulator.csx +++ b/UndertaleModTool/Scripts/Builtin Scripts/UndertaleDialogSimulator.csx @@ -342,6 +342,8 @@ public uint GetLastLayerID() uint a_last_layer_id = 0; foreach (UndertaleRoom Room in Data.Rooms) { + if (Room is null) + continue; foreach (UndertaleRoom.Layer Layer in Room.Layers) { if (Layer.LayerId > a_last_layer_id) diff --git a/UndertaleModTool/Scripts/Community Scripts/ExternalizeAllOGGs.csx b/UndertaleModTool/Scripts/Community Scripts/ExternalizeAllOGGs.csx index 9bc35a1cc..4a542afc4 100644 --- a/UndertaleModTool/Scripts/Community Scripts/ExternalizeAllOGGs.csx +++ b/UndertaleModTool/Scripts/Community Scripts/ExternalizeAllOGGs.csx @@ -74,7 +74,17 @@ ScriptMessage("Externalization Complete.\nExternalized " + sounds.ToString() + " void ExternalizeSounds() { foreach (UndertaleSound sound in Data.Sounds) - ExternalizeSound(sound); + { + if (sound is not null) + { + ExternalizeSound(sound); + } + else + { + sounds++; + IncrementProgress(); + } + } } string GetFolder(string path) @@ -197,7 +207,10 @@ byte[] GetSoundData(UndertaleSound sound) void DumpSounds() { foreach (UndertaleSound sound in Data.Sounds) - DumpSound(sound); + { + if (sound is not null) + DumpSound(sound); + } } void DumpSound(UndertaleSound sound) diff --git a/UndertaleModTool/Scripts/Community Scripts/FancyRoomSelect.csx b/UndertaleModTool/Scripts/Community Scripts/FancyRoomSelect.csx index a404f4c70..c8d39a048 100644 --- a/UndertaleModTool/Scripts/Community Scripts/FancyRoomSelect.csx +++ b/UndertaleModTool/Scripts/Community Scripts/FancyRoomSelect.csx @@ -46,6 +46,8 @@ if(gms2) { if(target_layer == null) { uint layer_id = 0; foreach(var room in Data.Rooms) { + if (room is null) + continue; foreach(var layer in room.Layers) { if(layer.LayerId > layer_id) { layer_id = (uint)layer.LayerId; diff --git a/UndertaleModTool/Scripts/Community Scripts/FontEditor.csx b/UndertaleModTool/Scripts/Community Scripts/FontEditor.csx index 1438fd0d4..4e92a8ce2 100644 --- a/UndertaleModTool/Scripts/Community Scripts/FontEditor.csx +++ b/UndertaleModTool/Scripts/Community Scripts/FontEditor.csx @@ -1250,7 +1250,11 @@ UndertaleFont FontPickerResult() comboBox.Size = new Size(form.Size.Width - 25, comboBox.Size.Height); comboBox.Anchor = AnchorStyles.Left | AnchorStyles.Top | AnchorStyles.Right; foreach (UndertaleFont font in Data.Fonts) + { + if (font is null || font.Name?.Content? is null) + continue; comboBox.Items.Add(font.Name.Content); + } int defaultSelection = comboBox.Items.IndexOf("fnt_maintext"); comboBox.SelectedIndex = defaultSelection == -1 ? 0 : defaultSelection; form.Controls.Add(comboBox); diff --git a/UndertaleModTool/Scripts/Community Scripts/ScaleAllTextures.csx b/UndertaleModTool/Scripts/Community Scripts/ScaleAllTextures.csx index 71f5d23df..84c66ced2 100644 --- a/UndertaleModTool/Scripts/Community Scripts/ScaleAllTextures.csx +++ b/UndertaleModTool/Scripts/Community Scripts/ScaleAllTextures.csx @@ -61,6 +61,8 @@ using (TextureWorker worker = new()) } foreach (UndertaleFont fnt in Data.Fonts) { + if (fnt is null) + continue; //fnt.ScaleX = scale; //fnt.ScaleY = scale; foreach (UndertaleFont.Glyph glyph in fnt.Glyphs) @@ -79,6 +81,8 @@ using (TextureWorker worker = new()) } foreach (UndertaleRoom room in Data.Rooms) { + if (room is null) + continue; foreach (UndertaleRoom.Background background in room.Backgrounds) { if (background.Enabled) diff --git a/UndertaleModTool/Scripts/Community Scripts/VariableFixer.csx b/UndertaleModTool/Scripts/Community Scripts/VariableFixer.csx index c5c85fe35..e61f60fe9 100644 --- a/UndertaleModTool/Scripts/Community Scripts/VariableFixer.csx +++ b/UndertaleModTool/Scripts/Community Scripts/VariableFixer.csx @@ -17,6 +17,8 @@ int globalNum = 0; int selfNum = 0; foreach(var vari in Data.Variables) { + if (vari is null) + continue; if (vari.InstanceType == UndertaleInstruction.InstanceType.Global) { vari.VarID = globalNum++; diff --git a/UndertaleModTool/Scripts/Resource Repackers/CopySoundInternal.csx b/UndertaleModTool/Scripts/Resource Repackers/CopySoundInternal.csx index b59de3300..0a58b52e9 100644 --- a/UndertaleModTool/Scripts/Resource Repackers/CopySoundInternal.csx +++ b/UndertaleModTool/Scripts/Resource Repackers/CopySoundInternal.csx @@ -169,6 +169,8 @@ List GetSoundsList(List splitStringsList) { foreach (UndertaleSound snd in Data.Sounds) { + if (snd is null) + continue; if (splitStringsList[j].ToLower() == snd.Name.Content.ToLower()) { soundsList.Add(snd); diff --git a/UndertaleModTool/Scripts/Resource Repackers/CopySpriteBgFont.csx b/UndertaleModTool/Scripts/Resource Repackers/CopySpriteBgFont.csx index fca4df4f9..235c5cdb6 100644 --- a/UndertaleModTool/Scripts/Resource Repackers/CopySpriteBgFont.csx +++ b/UndertaleModTool/Scripts/Resource Repackers/CopySpriteBgFont.csx @@ -383,6 +383,8 @@ void TexturePageItemsUsedUpdate() } foreach(UndertaleSprite sprite in Data.Sprites) { + if (sprite is null) + continue; for (int i = 0; i < sprite.Textures.Count; i++) { if (sprite.Textures[i]?.Texture != null) @@ -393,6 +395,8 @@ void TexturePageItemsUsedUpdate() } foreach (UndertaleBackground bg in Data.Backgrounds) { + if (bg is null) + continue; if (bg.Texture != null) { TexturePageItemsUsed[Data.TexturePageItems.IndexOf(bg.Texture)] = true; @@ -400,6 +404,8 @@ void TexturePageItemsUsedUpdate() } foreach (UndertaleFont fnt in Data.Fonts) { + if (fnt is null) + continue; if (fnt.Texture != null) { TexturePageItemsUsed[Data.TexturePageItems.IndexOf(fnt.Texture)] = true; @@ -410,6 +416,8 @@ void SpriteSheetsUsedUpdate() { foreach(UndertaleSprite sprite in Data.Sprites) { + if (sprite is null) + continue; for (int i = 0; i < sprite.Textures.Count; i++) { if (sprite.Textures[i]?.Texture != null) @@ -420,6 +428,8 @@ void SpriteSheetsUsedUpdate() } foreach (UndertaleBackground bg in Data.Backgrounds) { + if (bg is null) + continue; if (bg.Texture != null) { SpriteSheetsUsed[Data.EmbeddedTextures.IndexOf(bg.Texture.TexturePage)] = true; @@ -427,6 +437,8 @@ void SpriteSheetsUsedUpdate() } foreach (UndertaleFont fnt in Data.Fonts) { + if (fnt is null) + continue; if (fnt.Texture != null) { SpriteSheetsUsed[Data.EmbeddedTextures.IndexOf(fnt.Texture.TexturePage)] = true; diff --git a/UndertaleModTool/Scripts/Resource Repackers/CopySpriteBgFontInternal.csx b/UndertaleModTool/Scripts/Resource Repackers/CopySpriteBgFontInternal.csx index 4c417364c..5175ceba1 100644 --- a/UndertaleModTool/Scripts/Resource Repackers/CopySpriteBgFontInternal.csx +++ b/UndertaleModTool/Scripts/Resource Repackers/CopySpriteBgFontInternal.csx @@ -357,6 +357,8 @@ void TexturePageItemsUsedUpdate() } foreach(UndertaleSprite sprite in Data.Sprites) { + if (sprite is null) + continue; for (int i = 0; i < sprite.Textures.Count; i++) { if (sprite.Textures[i]?.Texture != null) @@ -367,6 +369,8 @@ void TexturePageItemsUsedUpdate() } foreach (UndertaleBackground bg in Data.Backgrounds) { + if (bg is null) + continue; if (bg.Texture != null) { TexturePageItemsUsed[Data.TexturePageItems.IndexOf(bg.Texture)] = true; @@ -374,6 +378,8 @@ void TexturePageItemsUsedUpdate() } foreach (UndertaleFont fnt in Data.Fonts) { + if (fnt is null) + continue; if (fnt.Texture != null) { TexturePageItemsUsed[Data.TexturePageItems.IndexOf(fnt.Texture)] = true; @@ -384,6 +390,8 @@ void SpriteSheetsUsedUpdate() { foreach(UndertaleSprite sprite in Data.Sprites) { + if (sprite is null) + continue; for (int i = 0; i < sprite.Textures.Count; i++) { if (sprite.Textures[i]?.Texture != null) @@ -394,6 +402,8 @@ void SpriteSheetsUsedUpdate() } foreach (UndertaleBackground bg in Data.Backgrounds) { + if (bg is null) + continue; if (bg.Texture != null) { SpriteSheetsUsed[Data.EmbeddedTextures.IndexOf(bg.Texture.TexturePage)] = true; @@ -401,6 +411,8 @@ void SpriteSheetsUsedUpdate() } foreach (UndertaleFont fnt in Data.Fonts) { + if (fnt is null) + continue; if (fnt.Texture != null) { SpriteSheetsUsed[Data.EmbeddedTextures.IndexOf(fnt.Texture.TexturePage)] = true; diff --git a/UndertaleModTool/Scripts/Resource Repackers/ImportAllEmbeddedTextures.csx b/UndertaleModTool/Scripts/Resource Repackers/ImportAllEmbeddedTextures.csx index 86297f082..8b570f0de 100644 --- a/UndertaleModTool/Scripts/Resource Repackers/ImportAllEmbeddedTextures.csx +++ b/UndertaleModTool/Scripts/Resource Repackers/ImportAllEmbeddedTextures.csx @@ -20,9 +20,13 @@ if (!Directory.Exists(embeddedTexturesPath)) string subPath = embeddedTexturesPath; int i = 0; -foreach (var txtr in Data.EmbeddedTextures) +foreach (UndertaleEmbeddedTexture target in Data.EmbeddedTextures) { - UndertaleEmbeddedTexture target = txtr as UndertaleEmbeddedTexture; + if (target is null) + { + i++; + continue; + } string filename = $"{i}.png"; try { diff --git a/UndertaleModTool/Scripts/Resource Repackers/ImportAssetOrder.csx b/UndertaleModTool/Scripts/Resource Repackers/ImportAssetOrder.csx index a8a4d5d61..b875662a8 100644 --- a/UndertaleModTool/Scripts/Resource Repackers/ImportAssetOrder.csx +++ b/UndertaleModTool/Scripts/Resource Repackers/ImportAssetOrder.csx @@ -11,6 +11,9 @@ using UndertaleModLib.Util; EnsureDataLoaded(); +if (Data.IsVersionAtLeast(2024, 11)) + ScriptWarning("This script may act erroneusly on GM version 2024.11 and later."); + string assetNamePath = PromptLoadFile("txt", "Text files (.txt)|*.txt|All files|*"); if (assetNamePath == null) throw new ScriptException("The asset name text file was not chosen!"); @@ -34,7 +37,12 @@ void Reorganize(IList list, List order) where T : UndertaleNamedRe T asset; try { - asset = temp[order[i]]; + if (order[i] == "(null)") + asset = default(T); + else if (int.TryParse(order[i], out int index)) + asset = list[index]; + else + asset = temp[order[i]]; } catch (Exception e) { diff --git a/UndertaleModTool/Scripts/Resource Repackers/NewTextureRepacker.csx b/UndertaleModTool/Scripts/Resource Repackers/NewTextureRepacker.csx index 63a25d052..55cbbf744 100644 --- a/UndertaleModTool/Scripts/Resource Repackers/NewTextureRepacker.csx +++ b/UndertaleModTool/Scripts/Resource Repackers/NewTextureRepacker.csx @@ -384,6 +384,8 @@ if (Data.TextureGroupInfo != null) { foreach (var texInfo in Data.TextureGroupInfo) { + if (texInfo is null) + continue; texInfo.TexturePages.Clear(); } } diff --git a/UndertaleModTool/Scripts/Resource Repackers/RemoveUnusedSounds.csx b/UndertaleModTool/Scripts/Resource Repackers/RemoveUnusedSounds.csx index 58e699d51..d94b360ee 100644 --- a/UndertaleModTool/Scripts/Resource Repackers/RemoveUnusedSounds.csx +++ b/UndertaleModTool/Scripts/Resource Repackers/RemoveUnusedSounds.csx @@ -211,5 +211,8 @@ byte[] GetSoundData(UndertaleSound sound) void ProcessSounds() { foreach (UndertaleSound sound in Data.Sounds) - GetSoundData(sound); + { + if (sound is not null) + GetSoundData(sound); + } } diff --git a/UndertaleModTool/Scripts/Resource Repackers/SpriteOriginCopy.csx b/UndertaleModTool/Scripts/Resource Repackers/SpriteOriginCopy.csx index 90986bd22..881729660 100644 --- a/UndertaleModTool/Scripts/Resource Repackers/SpriteOriginCopy.csx +++ b/UndertaleModTool/Scripts/Resource Repackers/SpriteOriginCopy.csx @@ -22,6 +22,8 @@ using (var stream = new FileStream(donorDataPath, FileMode.Open, FileAccess.Read foreach (var sprite in Data.Sprites) { + if (sprite is null) + continue; var donorSpr = donorData.Sprites.ByName(sprite.Name.Content); if (donorSpr is not null) { diff --git a/UndertaleModTool/Scripts/Resource Unpackers/DumpSpecificCode.csx b/UndertaleModTool/Scripts/Resource Unpackers/DumpSpecificCode.csx index 61acb7e4b..72707dc53 100644 --- a/UndertaleModTool/Scripts/Resource Unpackers/DumpSpecificCode.csx +++ b/UndertaleModTool/Scripts/Resource Unpackers/DumpSpecificCode.csx @@ -39,6 +39,8 @@ for (var j = 0; j < splitStringsList.Count; j++) { foreach (UndertaleGameObject obj in Data.GameObjects) { + if (obj is null) + continue; if (splitStringsList[j].ToLower() == obj.Name.Content.ToLower()) { gameObjectCandidates.Add(obj.Name.Content); @@ -46,7 +48,7 @@ for (var j = 0; j < splitStringsList.Count; j++) } foreach (UndertaleScript scr in Data.Scripts) { - if (scr.Code == null) + if (scr is null || scr.Code == null) continue; if (splitStringsList[j].ToLower() == scr.Name.Content.ToLower()) { @@ -55,7 +57,7 @@ for (var j = 0; j < splitStringsList.Count; j++) } foreach (UndertaleGlobalInit globalInit in Data.GlobalInitScripts) { - if (globalInit.Code == null) + if (globalInit is null || globalInit.Code == null) continue; if (splitStringsList[j].ToLower() == globalInit.Code.Name.Content.ToLower()) { @@ -64,6 +66,8 @@ for (var j = 0; j < splitStringsList.Count; j++) } foreach (UndertaleCode code in Data.Code) { + if (code is null) + continue; if (splitStringsList[j].ToLower() == code.Name.Content.ToLower()) { codeToDump.Add(code.Name.Content); diff --git a/UndertaleModTool/Scripts/Resource Unpackers/ExportAllSounds.csx b/UndertaleModTool/Scripts/Resource Unpackers/ExportAllSounds.csx index 7bd3708c7..98bcd3473 100644 --- a/UndertaleModTool/Scripts/Resource Unpackers/ExportAllSounds.csx +++ b/UndertaleModTool/Scripts/Resource Unpackers/ExportAllSounds.csx @@ -142,7 +142,16 @@ void DumpSounds() { //MakeFolder("Exported_Sounds"); foreach (UndertaleSound sound in Data.Sounds) - DumpSound(sound); + { + if (sound is not null) + { + DumpSound(sound); + } + else + { + IncProgressLocal(); + } + } } void DumpSound(UndertaleSound sound) diff --git a/UndertaleModTool/Scripts/Resource Unpackers/ExportAssetOrder.csx b/UndertaleModTool/Scripts/Resource Unpackers/ExportAssetOrder.csx index c90908219..baa7fbc31 100644 --- a/UndertaleModTool/Scripts/Resource Unpackers/ExportAssetOrder.csx +++ b/UndertaleModTool/Scripts/Resource Unpackers/ExportAssetOrder.csx @@ -22,94 +22,64 @@ Would you like to overwrite it?"); } } +void WriteAssetNames(StreamWriter writer, IList assets) +{ + if (assets.Count == 0) + return; + foreach (var asset in assets) + { + if (asset is not null) + writer.WriteLine(asset.Name?.Content ?? assets.IndexOf(asset).ToString()); + else + writer.WriteLine("(null)"); + } +} + using (StreamWriter writer = new StreamWriter(outputPath)) { // Write Sounds. writer.WriteLine("@@sounds@@"); - if (Data.Sounds.Count > 0) - { - foreach (UndertaleSound sound in Data.Sounds) - writer.WriteLine(sound.Name.Content); - } + WriteAssetNames(writer, Data.Sounds); + // Write Sprites. writer.WriteLine("@@sprites@@"); - if (Data.Sprites.Count > 0) - { - foreach (var sprite in Data.Sprites) - writer.WriteLine(sprite.Name.Content); - } + WriteAssetNames(writer, Data.Sprites); // Write Backgrounds. writer.WriteLine("@@backgrounds@@"); - if (Data.Backgrounds.Count > 0) - { - foreach (var background in Data.Backgrounds) - writer.WriteLine(background.Name.Content); - } + WriteAssetNames(writer, Data.Backgrounds); // Write Paths. writer.WriteLine("@@paths@@"); - if (Data.Paths.Count > 0) - { - foreach (UndertalePath path in Data.Paths) - writer.WriteLine(path.Name.Content); - } + WriteAssetNames(writer, Data.Paths); // Write Scripts. writer.WriteLine("@@scripts@@"); - if (Data.Scripts.Count > 0) - { - foreach (UndertaleScript script in Data.Scripts) - writer.WriteLine(script.Name.Content); - } + WriteAssetNames(writer, Data.Scripts); // Write Fonts. writer.WriteLine("@@fonts@@"); - if (Data.Fonts.Count > 0) - { - foreach (UndertaleFont font in Data.Fonts) - writer.WriteLine(font.Name.Content); - } + WriteAssetNames(writer, Data.Fonts); // Write Objects. writer.WriteLine("@@objects@@"); - if (Data.GameObjects.Count > 0) - { - foreach (UndertaleGameObject gameObject in Data.GameObjects) - writer.WriteLine(gameObject.Name.Content); - } + WriteAssetNames(writer, Data.GameObjects); // Write Timelines. writer.WriteLine("@@timelines@@"); - if (Data.Timelines.Count > 0) - { - foreach (UndertaleTimeline timeline in Data.Timelines) - writer.WriteLine(timeline.Name.Content); - } + WriteAssetNames(writer, Data.Timelines); // Write Rooms. writer.WriteLine("@@rooms@@"); - if (Data.Rooms.Count > 0) - { - foreach (UndertaleRoom room in Data.Rooms) - writer.WriteLine(room.Name.Content); - } + WriteAssetNames(writer, Data.Rooms); // Write Shaders. writer.WriteLine("@@shaders@@"); - if (Data.Shaders.Count > 0) - { - foreach (UndertaleShader shader in Data.Shaders) - writer.WriteLine(shader.Name.Content); - } + WriteAssetNames(writer, Data.Shaders); // Write Extensions. writer.WriteLine("@@extensions@@"); - if (Data.Extensions.Count > 0) - { - foreach (UndertaleExtension extension in Data.Extensions) - writer.WriteLine(extension.Name.Content); - } + WriteAssetNames(writer, Data.Extensions); // TODO: Perhaps detect GMS2.3, export those asset names as well. } diff --git a/UndertaleModTool/Scripts/Resource Unpackers/ExportFontData.csx b/UndertaleModTool/Scripts/Resource Unpackers/ExportFontData.csx index 21f803b71..45a5a3e42 100644 --- a/UndertaleModTool/Scripts/Resource Unpackers/ExportFontData.csx +++ b/UndertaleModTool/Scripts/Resource Unpackers/ExportFontData.csx @@ -71,7 +71,8 @@ private DialogResult ShowInputDialog() //fonts_list.Items.Add("All"); foreach (var x in Data.Fonts) { - fonts_list.Items.Add(x.Name.ToString().Replace("\"", "")); + if (x is not null) + fonts_list.Items.Add(x.Name.ToString().Replace("\"", "")); } fonts_list.Size = new System.Drawing.Size(size.Width - 10, size.Height - 50); diff --git a/UndertaleModTool/Scripts/Resource Unpackers/ExportShaderData.csx b/UndertaleModTool/Scripts/Resource Unpackers/ExportShaderData.csx index e72b38ed1..88465aeb9 100644 --- a/UndertaleModTool/Scripts/Resource Unpackers/ExportShaderData.csx +++ b/UndertaleModTool/Scripts/Resource Unpackers/ExportShaderData.csx @@ -12,8 +12,10 @@ if (exportFolder == null) Directory.CreateDirectory(exportFolder + "/Shader_Data/"); -foreach(UndertaleShader shader in Data.Shaders) +foreach (UndertaleShader shader in Data.Shaders) { + if (shader is null) + continue; string exportBase = (exportFolder + "/Shader_Data/" + shader.Name.Content + "/"); Directory.CreateDirectory(exportBase); File.WriteAllText(exportBase + "Type.txt", shader.Type.ToString()); diff --git a/UndertaleModTool/Scripts/Resource Unpackers/ExportSpecificSprites.csx b/UndertaleModTool/Scripts/Resource Unpackers/ExportSpecificSprites.csx index 8f1b39747..741eeb51a 100644 --- a/UndertaleModTool/Scripts/Resource Unpackers/ExportSpecificSprites.csx +++ b/UndertaleModTool/Scripts/Resource Unpackers/ExportSpecificSprites.csx @@ -30,6 +30,8 @@ foreach (string listElement in splitStringsList) { foreach (UndertaleSprite spr in Data.Sprites) { + if (spr is null) + continue; if (listElement.Equals(spr.Name.Content, StringComparison.InvariantCultureIgnoreCase)) { spritesToDump.Add(spr); diff --git a/UndertaleModTool/Scripts/Resource Unpackers/ExportTextureGroups.csx b/UndertaleModTool/Scripts/Resource Unpackers/ExportTextureGroups.csx index 53cc30efb..abe7254e0 100644 --- a/UndertaleModTool/Scripts/Resource Unpackers/ExportTextureGroups.csx +++ b/UndertaleModTool/Scripts/Resource Unpackers/ExportTextureGroups.csx @@ -31,6 +31,8 @@ using (worker = new()) { foreach (UndertaleTextureGroupInfo tgin in Data.TextureGroupInfo) { + if (tgin is null) + continue; int progress = 0; int sum = 0; if (tgin.TexturePages != null) diff --git a/UndertaleModTool/Scripts/Technical Scripts/ExecutionOrder.csx b/UndertaleModTool/Scripts/Technical Scripts/ExecutionOrder.csx index d2d98591b..ffe306922 100644 --- a/UndertaleModTool/Scripts/Technical Scripts/ExecutionOrder.csx +++ b/UndertaleModTool/Scripts/Technical Scripts/ExecutionOrder.csx @@ -285,6 +285,11 @@ void ProfileModeOperations() Underanalyzer.Decompiler.IDecompileSettings decompilerSettings = new Underanalyzer.Decompiler.DecompileSettings(); foreach (UndertaleCode c in Data.Code) { + if (c is null) + { + progress++; + continue; + } UpdateProgressBar(null, "Code entries processed", progress++, Data.Code.Count); string gmlCode = GetDecompiledText(c.Name.Content, globalDecompileContext, decompilerSettings); gmlCode = gmlCode.Replace("\r\n", "\n"); @@ -304,6 +309,8 @@ void ProfileModeExempt() // Process bytecode, patching in script calls where needed foreach (UndertaleCode c in Data.Code) { + if (c is null) + continue; // global.interact get/set patches for (int i = 0; i < c.Instructions.Count; i++) { diff --git a/UndertaleModTool/Scripts/Technical Scripts/ExportAndConvert_2_3_ASM.csx b/UndertaleModTool/Scripts/Technical Scripts/ExportAndConvert_2_3_ASM.csx index 0009b0503..d01f9adcb 100644 --- a/UndertaleModTool/Scripts/Technical Scripts/ExportAndConvert_2_3_ASM.csx +++ b/UndertaleModTool/Scripts/Technical Scripts/ExportAndConvert_2_3_ASM.csx @@ -51,6 +51,12 @@ void DumpCode() { foreach (UndertaleCode code_orig in Data.Code) { + if (code_orig is null) + { + IncrementProgress(); + continue; + } + code_orig.Offset = 0; if (Data.CodeLocals is not null && Data.CodeLocals.ByName(code_orig.Name.Content) == null) { diff --git a/UndertaleModTool/Scripts/Technical Scripts/FindUnknownFunctions.csx b/UndertaleModTool/Scripts/Technical Scripts/FindUnknownFunctions.csx index ed4feffd2..af039f5d7 100644 --- a/UndertaleModTool/Scripts/Technical Scripts/FindUnknownFunctions.csx +++ b/UndertaleModTool/Scripts/Technical Scripts/FindUnknownFunctions.csx @@ -35,10 +35,16 @@ List unknownFunctions = new List(); List unknownFunctions2 = new List(); foreach (UndertaleExtension extension in Data.Extensions) { + if (extension is null) + continue; foreach (UndertaleExtensionFile exFile in extension.Files) { + if (exFile is null) + continue; foreach (UndertaleExtensionFunction exFunc in exFile.Functions) { + if (exFunc is null) + continue; extensionFunctions.Add(exFunc.Name.Content); } } @@ -48,6 +54,8 @@ using (StreamWriter writer = new StreamWriter(exportFolder + "unknown_functions. { foreach (var func in Data.Functions) { + if (func is null) + continue; if (func.Name.Content.Contains("\n") || func.Name.Content.Contains("\r")) { continue; diff --git a/UndertaleModTool/Scripts/Technical Scripts/FindUnusedStrings.csx b/UndertaleModTool/Scripts/Technical Scripts/FindUnusedStrings.csx index 7e30e1d71..14d187653 100644 --- a/UndertaleModTool/Scripts/Technical Scripts/FindUnusedStrings.csx +++ b/UndertaleModTool/Scripts/Technical Scripts/FindUnusedStrings.csx @@ -84,6 +84,8 @@ uint[] GetStringUsageCount() { foreach (UndertaleAnimationCurve obj in Data.AnimationCurves) { + if (obj is null) + continue; if (obj.Name != null) { stringsUsageCountArray[Data.Strings.IndexOf(obj.Name)] += 1; @@ -102,6 +104,8 @@ uint[] GetStringUsageCount() { foreach (UndertaleAudioGroup obj in Data.AudioGroups) { + if (obj is null) + continue; if (obj.Name != null) { stringsUsageCountArray[Data.Strings.IndexOf(obj.Name)] += 1; @@ -113,6 +117,8 @@ uint[] GetStringUsageCount() { foreach (UndertaleBackground obj in Data.Backgrounds) { + if (obj is null) + continue; if (obj.Name != null) { stringsUsageCountArray[Data.Strings.IndexOf(obj.Name)] += 1; @@ -124,6 +130,8 @@ uint[] GetStringUsageCount() { foreach (UndertaleCode obj in Data.Code) { + if (obj is null) + continue; if (obj.Name != null) { stringsUsageCountArray[Data.Strings.IndexOf(obj.Name)] += 1; @@ -146,6 +154,8 @@ uint[] GetStringUsageCount() { foreach (UndertaleCodeLocals obj in Data.CodeLocals) { + if (obj is null) + continue; if (obj.Name != null) { stringsUsageCountArray[Data.Strings.IndexOf(obj.Name)] += 1; @@ -164,6 +174,8 @@ uint[] GetStringUsageCount() { foreach (UndertaleExtension obj in Data.Extensions) { + if (obj is null) + continue; if (obj.Name != null) { stringsUsageCountArray[Data.Strings.IndexOf(obj.Name)] += 1; @@ -213,6 +225,8 @@ uint[] GetStringUsageCount() { foreach (UndertaleFont obj in Data.Fonts) { + if (obj is null) + continue; if (obj.Name != null) { stringsUsageCountArray[Data.Strings.IndexOf(obj.Name)] += 1; @@ -228,6 +242,8 @@ uint[] GetStringUsageCount() { foreach (UndertaleFunction obj in Data.Functions) { + if (obj is null) + continue; if (obj.Name != null) { stringsUsageCountArray[Data.Strings.IndexOf(obj.Name)] += 1; @@ -239,6 +255,8 @@ uint[] GetStringUsageCount() { foreach (UndertaleGameObject obj in Data.GameObjects) { + if (obj is null) + continue; if (obj.Name != null) { stringsUsageCountArray[Data.Strings.IndexOf(obj.Name)] += 1; @@ -313,6 +331,8 @@ uint[] GetStringUsageCount() { foreach (UndertaleOptions.Constant constant in Data.Options.Constants) { + if (constant is null) + continue; if (constant.Name != null) { stringsUsageCountArray[Data.Strings.IndexOf(constant.Name)] += 1; @@ -328,6 +348,8 @@ uint[] GetStringUsageCount() { foreach (UndertalePath obj in Data.Paths) { + if (obj is null) + continue; if (obj.Name != null) { stringsUsageCountArray[Data.Strings.IndexOf(obj.Name)] += 1; @@ -339,6 +361,8 @@ uint[] GetStringUsageCount() { foreach (UndertaleRoom obj in Data.Rooms) { + if (obj is null) + continue; if (obj.Name != null) { stringsUsageCountArray[Data.Strings.IndexOf(obj.Name)] += 1; @@ -397,6 +421,8 @@ uint[] GetStringUsageCount() { foreach (UndertaleScript obj in Data.Scripts) { + if (obj is null) + continue; if (obj.Name != null) { stringsUsageCountArray[Data.Strings.IndexOf(obj.Name)] += 1; @@ -408,6 +434,8 @@ uint[] GetStringUsageCount() { foreach (UndertaleSequence obj in Data.Sequences) { + if (obj is null) + continue; if ((obj as UndertaleSequence).Name != null) { stringsUsageCountArray[Data.Strings.IndexOf((obj as UndertaleSequence).Name)] += 1; @@ -433,6 +461,8 @@ uint[] GetStringUsageCount() { foreach (UndertaleShader obj in Data.Shaders) { + if (obj is null) + continue; if (obj.Name != null) { stringsUsageCountArray[Data.Strings.IndexOf(obj.Name)] += 1; @@ -475,6 +505,8 @@ uint[] GetStringUsageCount() { foreach (UndertaleSound obj in Data.Sounds) { + if (obj is null) + continue; if (obj.Name != null) { stringsUsageCountArray[Data.Strings.IndexOf(obj.Name)] += 1; @@ -494,6 +526,8 @@ uint[] GetStringUsageCount() { foreach (UndertaleSprite obj in Data.Sprites) { + if (obj is null) + continue; if (obj.Name != null) { stringsUsageCountArray[Data.Strings.IndexOf(obj.Name)] += 1; @@ -516,6 +550,8 @@ uint[] GetStringUsageCount() { foreach (UndertaleTextureGroupInfo obj in Data.TextureGroupInfo) { + if (obj is null) + continue; if (obj.Name != null) { stringsUsageCountArray[Data.Strings.IndexOf(obj.Name)] += 1; @@ -527,6 +563,8 @@ uint[] GetStringUsageCount() { foreach (UndertaleTimeline obj in Data.Timelines) { + if (obj is null) + continue; if (obj.Name != null) { stringsUsageCountArray[Data.Strings.IndexOf(obj.Name)] += 1; @@ -538,6 +576,8 @@ uint[] GetStringUsageCount() { foreach (UndertaleVariable obj in Data.Variables) { + if (obj is null) + continue; if (obj.Name != null) { stringsUsageCountArray[Data.Strings.IndexOf(obj.Name)] += 1; @@ -615,6 +655,8 @@ Dictionary> CollectReferencesVar() UpdateProgress("Searching For Unused Variables"); foreach (UndertaleCode code in Data.Code) { + if (code is null) + continue; if (code.Offset != 0) // GMS 2.3, skip duplicates continue; foreach (UndertaleInstruction instr in code.Instructions) @@ -636,6 +678,8 @@ Dictionary> CollectReferencesFunc( UpdateProgress("Searching For Unused Functions"); foreach (UndertaleCode code in Data.Code) { + if (code is null) + continue; if (code.Offset != 0) // GMS 2.3, skip duplicates continue; foreach (UndertaleInstruction instr in code.Instructions) diff --git a/UndertaleModTool/Scripts/Technical Scripts/GoToRoom_AutoLocatePersistentObj.csx b/UndertaleModTool/Scripts/Technical Scripts/GoToRoom_AutoLocatePersistentObj.csx index afa1ece75..d61e6233d 100644 --- a/UndertaleModTool/Scripts/Technical Scripts/GoToRoom_AutoLocatePersistentObj.csx +++ b/UndertaleModTool/Scripts/Technical Scripts/GoToRoom_AutoLocatePersistentObj.csx @@ -8,6 +8,8 @@ bool pers = false; UndertaleGameObject obj_pers = null; foreach (UndertaleGameObject obj in Data.GameObjects) { + if (obj is null) + continue; if (obj.Persistent) { pers = true; diff --git a/UndertaleModTool/Scripts/Technical Scripts/Profiler.csx b/UndertaleModTool/Scripts/Technical Scripts/Profiler.csx index a5f810dc9..3be6471b3 100644 --- a/UndertaleModTool/Scripts/Technical Scripts/Profiler.csx +++ b/UndertaleModTool/Scripts/Technical Scripts/Profiler.csx @@ -487,6 +487,11 @@ void ProfileModeOperations() Underanalyzer.Decompiler.IDecompileSettings decompilerSettings = new Underanalyzer.Decompiler.DecompileSettings(); foreach (UndertaleCode c in Data.Code) { + if (c is null) + { + progress++; + continue; + } UpdateProgressBar(null, "Code entries processed", progress++, Data.Code.Count); string gmlCode = GetDecompiledText(c.Name.Content, globalDecompileContext, decompilerSettings); gmlCode = gmlCode.Replace("\r\n", "\n"); @@ -506,6 +511,8 @@ void ProfileModeExempt() // Process bytecode, patching in script calls where needed foreach (UndertaleCode c in Data.Code) { + if (c is null) + continue; // global.interact get/set patches for (int i = 0; i < c.Instructions.Count; i++) { @@ -689,6 +696,8 @@ void PersistentObjectSetup(string objectName) uint layer_id = 0; foreach (var room in Data.Rooms) { + if (room is null) + continue; foreach (var layer in room.Layers) { if (layer.LayerId > layer_id) diff --git a/UndertaleModTool/Scripts/Technical Scripts/RestoreMissingCodeLocals.csx b/UndertaleModTool/Scripts/Technical Scripts/RestoreMissingCodeLocals.csx index 1f00a6672..49c02af89 100644 --- a/UndertaleModTool/Scripts/Technical Scripts/RestoreMissingCodeLocals.csx +++ b/UndertaleModTool/Scripts/Technical Scripts/RestoreMissingCodeLocals.csx @@ -1,12 +1,14 @@ EnsureDataLoaded(); if (Data.CodeLocals is null) { - ScriptMessage("Cannot run on this game, bytecode >= 15 and GM <= 2024.8 required!"); + ScriptMessage("Cannot run on this game, bytecode >= 15 and GM < 2024.8 required!"); return; } int newCount = 0; foreach (UndertaleCode code in Data.Code) { + if (code is null) + continue; if (Data.CodeLocals.ByName(code.Name.Content) == null) { UndertaleCodeLocals locals = new UndertaleCodeLocals(); diff --git a/UndertaleModTool/Scripts/Technical Scripts/TestExportAllCode.csx b/UndertaleModTool/Scripts/Technical Scripts/TestExportAllCode.csx index a4e5dbd0f..b3f08e59e 100644 --- a/UndertaleModTool/Scripts/Technical Scripts/TestExportAllCode.csx +++ b/UndertaleModTool/Scripts/Technical Scripts/TestExportAllCode.csx @@ -88,6 +88,11 @@ await Task.Run(() => { foreach (UndertaleCode code in Data.Code) { + if (code is null) + { + IncrementProgress(); + continue; + } if (write) { try From d5f76381f2a9e95199f3ca714ada2fd1c5ed04d6 Mon Sep 17 00:00:00 2001 From: Liu Wenyuan <15816141883@163.com> Date: Sun, 16 Feb 2025 16:01:34 +0800 Subject: [PATCH 28/29] Forgot about this --- UndertaleModLib/Compiler/Compiler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UndertaleModLib/Compiler/Compiler.cs b/UndertaleModLib/Compiler/Compiler.cs index 446d60d31..7f903ebd8 100644 --- a/UndertaleModLib/Compiler/Compiler.cs +++ b/UndertaleModLib/Compiler/Compiler.cs @@ -189,7 +189,7 @@ private void MakeAssetDictionary() { if (file is null) continue; - foreach (UndertaleExtensionFunction func in file.Functions?) + foreach (UndertaleExtensionFunction func in file.Functions) { if (func is null) continue; From c057ef91f93773b91578075f9bfcd45fe05d1fac Mon Sep 17 00:00:00 2001 From: Liu Wenyuan <15816141883@163.com> Date: Sun, 16 Feb 2025 16:10:53 +0800 Subject: [PATCH 29/29] Null adaptation 2: Parallel.ForEach flavor --- UndertaleModTests/GameLoadingTests.cs | 4 ++++ .../FindObjectsWithSprite.csx | 18 ++++++++++----- .../Helper Scripts/ExportAllSoundsOld.csx | 2 +- .../Resource Repackers/ImportAllTilesets.csx | 23 +++++++++++-------- .../ReduceEmbeddedTexturePages.csx | 19 ++++++++++----- .../Scripts/Resource Unpackers/ExportASM.csx | 17 ++++++++------ .../Resource Unpackers/ExportAllSprites.csx | 21 +++++++++-------- .../Resource Unpackers/ExportAllTextures.csx | 15 ++++++++---- .../ExportAllTexturesGrouped.csx | 19 ++++++++++----- .../Resource Unpackers/ExportAllTilesets.csx | 2 +- .../Resource Unpackers/ExportFontData.csx | 2 +- .../Resource Unpackers/ExportMasks.csx | 11 +++++---- .../ImportGraphics_Full_Repack.csx | 19 ++++++++++----- 13 files changed, 111 insertions(+), 61 deletions(-) diff --git a/UndertaleModTests/GameLoadingTests.cs b/UndertaleModTests/GameLoadingTests.cs index 9c4a159f2..fd3c7081e 100644 --- a/UndertaleModTests/GameLoadingTests.cs +++ b/UndertaleModTests/GameLoadingTests.cs @@ -37,6 +37,8 @@ public void DecompileAllScripts() GlobalDecompileContext context = new GlobalDecompileContext(data); Parallel.ForEach(data.Code, (code) => { + if (code is null) + return; //Console.WriteLine(code.Name.Content); try { @@ -54,6 +56,8 @@ public void DisassembleAndReassembleAllScripts() { Parallel.ForEach(data.Code, (code) => { + if (code is null) + return; //Console.WriteLine(code.Name.Content); bool knownBug = false; diff --git a/UndertaleModTool/Scripts/Community Scripts/FindObjectsWithSprite.csx b/UndertaleModTool/Scripts/Community Scripts/FindObjectsWithSprite.csx index e0238c8e4..24bcc9434 100644 --- a/UndertaleModTool/Scripts/Community Scripts/FindObjectsWithSprite.csx +++ b/UndertaleModTool/Scripts/Community Scripts/FindObjectsWithSprite.csx @@ -71,19 +71,25 @@ EnableUI(); void CheckObject(UndertaleGameObject obj) { - string sprName = obj.Sprite?.Name?.Content; + if (obj is not null) + { + string sprName = obj.Sprite?.Name?.Content; - if (sprName is not null && spriteNames.Contains(sprName)) - resultList.Add(obj.Name.Content); + if (sprName is not null && spriteNames.Contains(sprName)) + resultList.Add(obj.Name.Content); + } IncProgressP(); } void CheckObjectRegex(UndertaleGameObject obj) { - string sprName = obj.Sprite?.Name?.Content; + if (obj is not null) + { + string sprName = obj.Sprite?.Name?.Content; - if (sprName is not null && searchRegex.Match(sprName).Success) - resultList.Add(obj.Name.Content); + if (sprName is not null && searchRegex.Match(sprName).Success) + resultList.Add(obj.Name.Content); + } IncProgressP(); } \ No newline at end of file diff --git a/UndertaleModTool/Scripts/Helper Scripts/ExportAllSoundsOld.csx b/UndertaleModTool/Scripts/Helper Scripts/ExportAllSoundsOld.csx index 1d79f1534..a180f7c7b 100644 --- a/UndertaleModTool/Scripts/Helper Scripts/ExportAllSoundsOld.csx +++ b/UndertaleModTool/Scripts/Helper Scripts/ExportAllSoundsOld.csx @@ -38,7 +38,7 @@ async Task DumpSounds() void DumpSound(UndertaleSound sound) { - if (sound.AudioFile != null && !File.Exists(sndFolder + sound.File.Content)) + if (sound is not null && sound.AudioFile != null && !File.Exists(sndFolder + sound.File.Content)) File.WriteAllBytes(sndFolder + sound.File.Content, sound.AudioFile.Data); IncrementProgress(); diff --git a/UndertaleModTool/Scripts/Resource Repackers/ImportAllTilesets.csx b/UndertaleModTool/Scripts/Resource Repackers/ImportAllTilesets.csx index a2d5956d5..a437b5285 100644 --- a/UndertaleModTool/Scripts/Resource Repackers/ImportAllTilesets.csx +++ b/UndertaleModTool/Scripts/Resource Repackers/ImportAllTilesets.csx @@ -38,19 +38,22 @@ async Task ImportTilesets() void ImportTileset(UndertaleBackground tileset) { - string filename = $"{tileset.Name.Content}.png"; - try + if (tileset is not null) { - string path = Path.Combine(subPath, filename); - if (File.Exists(path)) + string filename = $"{tileset.Name.Content}.png"; + try { - using MagickImage img = TextureWorker.ReadBGRAImageFromFile(path); - tileset.Texture.ReplaceTexture(img); + string path = Path.Combine(subPath, filename); + if (File.Exists(path)) + { + using MagickImage img = TextureWorker.ReadBGRAImageFromFile(path); + tileset.Texture.ReplaceTexture(img); + } + } + catch (Exception ex) + { + ScriptMessage($"Failed to import {filename} (index {Data.Backgrounds.IndexOf(tileset)}): {ex.Message}"); } - } - catch (Exception ex) - { - ScriptMessage($"Failed to import {filename} (index {Data.Backgrounds.IndexOf(tileset)}): {ex.Message}"); } IncrementProgress(); diff --git a/UndertaleModTool/Scripts/Resource Repackers/ReduceEmbeddedTexturePages.csx b/UndertaleModTool/Scripts/Resource Repackers/ReduceEmbeddedTexturePages.csx index 5cf218be8..fed4cebec 100644 --- a/UndertaleModTool/Scripts/Resource Repackers/ReduceEmbeddedTexturePages.csx +++ b/UndertaleModTool/Scripts/Resource Repackers/ReduceEmbeddedTexturePages.csx @@ -62,14 +62,17 @@ async Task DumpFonts() void DumpSprite(UndertaleSprite sprite) { - for (int i = 0; i < sprite.Textures.Count; i++) + if (sprite is not null) { - if (sprite.Textures[i]?.Texture != null) + for (int i = 0; i < sprite.Textures.Count; i++) { - UndertaleTexturePageItem tex = sprite.Textures[i].Texture; - worker.ExportAsPNG(tex, Path.Combine(exportedTexturesFolder, $"{sprite.Name.Content}_{i}.png")); - assetCoordinateDict.Add($"{sprite.Name.Content}_{i}", new int[] { tex.TargetX, tex.TargetY, tex.SourceWidth, tex.SourceHeight, tex.TargetWidth, tex.TargetHeight, tex.BoundingWidth, tex.BoundingHeight }); - assetTypeDict.Add($"{sprite.Name.Content}_{i}", "spr"); + if (sprite.Textures[i]?.Texture != null) + { + UndertaleTexturePageItem tex = sprite.Textures[i].Texture; + worker.ExportAsPNG(tex, Path.Combine(exportedTexturesFolder, $"{sprite.Name.Content}_{i}.png")); + assetCoordinateDict.Add($"{sprite.Name.Content}_{i}", new int[] { tex.TargetX, tex.TargetY, tex.SourceWidth, tex.SourceHeight, tex.TargetWidth, tex.TargetHeight, tex.BoundingWidth, tex.BoundingHeight }); + assetTypeDict.Add($"{sprite.Name.Content}_{i}", "spr"); + } } } @@ -78,6 +81,8 @@ void DumpSprite(UndertaleSprite sprite) void DumpFont(UndertaleFont font) { + if (font is null) + return; if (font.Texture != null) { UndertaleTexturePageItem tex = font.Texture; @@ -91,6 +96,8 @@ void DumpFont(UndertaleFont font) void DumpBackground(UndertaleBackground background) { + if (background is null) + return; if (background.Texture != null) { UndertaleTexturePageItem tex = background.Texture; diff --git a/UndertaleModTool/Scripts/Resource Unpackers/ExportASM.csx b/UndertaleModTool/Scripts/Resource Unpackers/ExportASM.csx index d25e605bb..bddc370aa 100644 --- a/UndertaleModTool/Scripts/Resource Unpackers/ExportASM.csx +++ b/UndertaleModTool/Scripts/Resource Unpackers/ExportASM.csx @@ -36,14 +36,17 @@ async Task DumpCode() void DumpCode(UndertaleCode code) { - string path = Path.Combine(codeFolder, code.Name.Content + ".asm"); - try + if (code is not null) { - File.WriteAllText(path, (code != null ? code.Disassemble(Data.Variables, Data.CodeLocals?.For(code)) : "")); - } - catch (Exception e) - { - File.WriteAllText(path, "/*\nDISASSEMBLY FAILED!\n\n" + e.ToString() + "\n*/"); // Please don't + string path = Path.Combine(codeFolder, code.Name.Content + ".asm"); + try + { + File.WriteAllText(path, (code != null ? code.Disassemble(Data.Variables, Data.CodeLocals?.For(code)) : "")); + } + catch (Exception e) + { + File.WriteAllText(path, "/*\nDISASSEMBLY FAILED!\n\n" + e.ToString() + "\n*/"); // Please don't + } } IncrementProgressParallel(); diff --git a/UndertaleModTool/Scripts/Resource Unpackers/ExportAllSprites.csx b/UndertaleModTool/Scripts/Resource Unpackers/ExportAllSprites.csx index eecd5b557..6c5d81156 100644 --- a/UndertaleModTool/Scripts/Resource Unpackers/ExportAllSprites.csx +++ b/UndertaleModTool/Scripts/Resource Unpackers/ExportAllSprites.csx @@ -47,16 +47,19 @@ async Task DumpSprites() void DumpSprite(UndertaleSprite sprite) { - string outputFolder = texFolder; - if (useSubDirectories) - outputFolder = Path.Combine(outputFolder, sprite.Name.Content); - if (sprite.Textures.Count > 0) - Directory.CreateDirectory(outputFolder); - - for (int i = 0; i < sprite.Textures.Count; i++) + if (sprite is not null) { - if (sprite.Textures[i]?.Texture != null) - worker.ExportAsPNG(sprite.Textures[i].Texture, Path.Combine(outputFolder, $"{sprite.Name.Content}_{i}.png"), null, padded); // Include padding to make sprites look neat! + string outputFolder = texFolder; + if (useSubDirectories) + outputFolder = Path.Combine(outputFolder, sprite.Name.Content); + if (sprite.Textures.Count > 0) + Directory.CreateDirectory(outputFolder); + + for (int i = 0; i < sprite.Textures.Count; i++) + { + if (sprite.Textures[i]?.Texture != null) + worker.ExportAsPNG(sprite.Textures[i].Texture, Path.Combine(outputFolder, $"{sprite.Name.Content}_{i}.png"), null, padded); // Include padding to make sprites look neat! + } } IncrementProgressParallel(); diff --git a/UndertaleModTool/Scripts/Resource Unpackers/ExportAllTextures.csx b/UndertaleModTool/Scripts/Resource Unpackers/ExportAllTextures.csx index b98542bc9..80851c556 100644 --- a/UndertaleModTool/Scripts/Resource Unpackers/ExportAllTextures.csx +++ b/UndertaleModTool/Scripts/Resource Unpackers/ExportAllTextures.csx @@ -60,12 +60,15 @@ async Task DumpFonts() void DumpSprite(UndertaleSprite sprite) { - for (int i = 0; i < sprite.Textures.Count; i++) + if (sprite is not null) { - if (sprite.Textures[i]?.Texture != null) + for (int i = 0; i < sprite.Textures.Count; i++) { - UndertaleTexturePageItem tex = sprite.Textures[i].Texture; - worker.ExportAsPNG(tex, Path.Combine(sprFolder, $"{sprite.Name.Content}_{i}.png")); + if (sprite.Textures[i]?.Texture != null) + { + UndertaleTexturePageItem tex = sprite.Textures[i].Texture; + worker.ExportAsPNG(tex, Path.Combine(sprFolder, $"{sprite.Name.Content}_{i}.png")); + } } } @@ -74,6 +77,8 @@ void DumpSprite(UndertaleSprite sprite) void DumpFont(UndertaleFont font) { + if (font is null) + return; if (font.Texture != null) { UndertaleTexturePageItem tex = font.Texture; @@ -85,6 +90,8 @@ void DumpFont(UndertaleFont font) void DumpBackground(UndertaleBackground background) { + if (background is null) + return; if (background.Texture != null) { UndertaleTexturePageItem tex = background.Texture; diff --git a/UndertaleModTool/Scripts/Resource Unpackers/ExportAllTexturesGrouped.csx b/UndertaleModTool/Scripts/Resource Unpackers/ExportAllTexturesGrouped.csx index e04ab85e3..c9c2473e1 100644 --- a/UndertaleModTool/Scripts/Resource Unpackers/ExportAllTexturesGrouped.csx +++ b/UndertaleModTool/Scripts/Resource Unpackers/ExportAllTexturesGrouped.csx @@ -59,14 +59,17 @@ async Task DumpFonts() void DumpSprite(UndertaleSprite sprite) { - for (int i = 0; i < sprite.Textures.Count; i++) + if (sprite is not null) { - if (sprite.Textures[i]?.Texture != null) + for (int i = 0; i < sprite.Textures.Count; i++) { - UndertaleTexturePageItem tex = sprite.Textures[i].Texture; - string sprFolder2 = Path.Combine(sprFolder, sprite.Name.Content); - Directory.CreateDirectory(sprFolder2); - worker.ExportAsPNG(tex, Path.Combine(sprFolder2, $"{sprite.Name.Content}_{i}.png")); + if (sprite.Textures[i]?.Texture != null) + { + UndertaleTexturePageItem tex = sprite.Textures[i].Texture; + string sprFolder2 = Path.Combine(sprFolder, sprite.Name.Content); + Directory.CreateDirectory(sprFolder2); + worker.ExportAsPNG(tex, Path.Combine(sprFolder2, $"{sprite.Name.Content}_{i}.png")); + } } } @@ -75,6 +78,8 @@ void DumpSprite(UndertaleSprite sprite) void DumpFont(UndertaleFont font) { + if (font is null) + return; if (font.Texture != null) { UndertaleTexturePageItem tex = font.Texture; @@ -87,6 +92,8 @@ void DumpFont(UndertaleFont font) void DumpBackground(UndertaleBackground background) { + if (background is null) + return; if (background.Texture != null) { UndertaleTexturePageItem tex = background.Texture; diff --git a/UndertaleModTool/Scripts/Resource Unpackers/ExportAllTilesets.csx b/UndertaleModTool/Scripts/Resource Unpackers/ExportAllTilesets.csx index aaf7ae24c..b3d7132d0 100644 --- a/UndertaleModTool/Scripts/Resource Unpackers/ExportAllTilesets.csx +++ b/UndertaleModTool/Scripts/Resource Unpackers/ExportAllTilesets.csx @@ -36,7 +36,7 @@ async Task DumpTilesets() void DumpTileset(UndertaleBackground tileset) { - if (tileset.Texture != null) + if (tileset is not null && tileset.Texture != null) worker.ExportAsPNG(tileset.Texture, Path.Combine(texFolder, $"{tileset.Name.Content}.png")); IncrementProgressParallel(); diff --git a/UndertaleModTool/Scripts/Resource Unpackers/ExportFontData.csx b/UndertaleModTool/Scripts/Resource Unpackers/ExportFontData.csx index 45a5a3e42..fbcc04455 100644 --- a/UndertaleModTool/Scripts/Resource Unpackers/ExportFontData.csx +++ b/UndertaleModTool/Scripts/Resource Unpackers/ExportFontData.csx @@ -40,7 +40,7 @@ async Task DumpFonts() void DumpFont(UndertaleFont font) { - if (arrayString.Contains(font.Name.ToString().Replace("\"", ""))) + if (font is not null && arrayString.Contains(font.Name.ToString().Replace("\"", ""))) { worker.ExportAsPNG(font.Texture, Path.Combine(fntFolder, $"{font.Name.Content}.png")); using (StreamWriter writer = new(Path.Combine(fntFolder, $"glyphs_{font.Name.Content}.csv"))) diff --git a/UndertaleModTool/Scripts/Resource Unpackers/ExportMasks.csx b/UndertaleModTool/Scripts/Resource Unpackers/ExportMasks.csx index 605b63048..c61c1d99d 100644 --- a/UndertaleModTool/Scripts/Resource Unpackers/ExportMasks.csx +++ b/UndertaleModTool/Scripts/Resource Unpackers/ExportMasks.csx @@ -38,12 +38,15 @@ async Task DumpSprites() void DumpSprite(UndertaleSprite sprite) { - for (int i = 0; i < sprite.CollisionMasks.Count; i++) + if (sprite is not null) { - if (sprite.CollisionMasks[i]?.Data is not null) + for (int i = 0; i < sprite.CollisionMasks.Count; i++) { - (int maskWidth, int maskHeight) = sprite.CalculateMaskDimensions(Data); - TextureWorker.ExportCollisionMaskPNG(sprite.CollisionMasks[i], Path.Combine(texFolder, $"{sprite.Name.Content}_{i}.png"), maskWidth, maskHeight); + if (sprite.CollisionMasks[i]?.Data is not null) + { + (int maskWidth, int maskHeight) = sprite.CalculateMaskDimensions(Data); + TextureWorker.ExportCollisionMaskPNG(sprite.CollisionMasks[i], Path.Combine(texFolder, $"{sprite.Name.Content}_{i}.png"), maskWidth, maskHeight); + } } } diff --git a/UndertaleModTool/Scripts/Technical Scripts/ImportGraphics_Full_Repack.csx b/UndertaleModTool/Scripts/Technical Scripts/ImportGraphics_Full_Repack.csx index 9efd8db7a..88f33b611 100644 --- a/UndertaleModTool/Scripts/Technical Scripts/ImportGraphics_Full_Repack.csx +++ b/UndertaleModTool/Scripts/Technical Scripts/ImportGraphics_Full_Repack.csx @@ -128,14 +128,17 @@ async Task DumpFonts() void DumpSprite(UndertaleSprite sprite) { - for (int i = 0; i < sprite.Textures.Count; i++) + if (sprite is not null) { - if (sprite.Textures[i]?.Texture != null) + for (int i = 0; i < sprite.Textures.Count; i++) { - UndertaleTexturePageItem tex = sprite.Textures[i].Texture; - worker.ExportAsPNG(tex, Path.Combine(exportedTexturesFolder, $"{sprite.Name.Content}_{i}.png")); - assetCoordinateDict.Add($"{sprite.Name.Content}_{i}", new int[] { tex.TargetX, tex.TargetY, tex.TargetWidth, tex.TargetHeight, tex.BoundingWidth, tex.BoundingHeight }); - assetTypeDict.Add($"{sprite.Name.Content}_{i}", "spr"); + if (sprite.Textures[i]?.Texture != null) + { + UndertaleTexturePageItem tex = sprite.Textures[i].Texture; + worker.ExportAsPNG(tex, Path.Combine(exportedTexturesFolder, $"{sprite.Name.Content}_{i}.png")); + assetCoordinateDict.Add($"{sprite.Name.Content}_{i}", new int[] { tex.TargetX, tex.TargetY, tex.TargetWidth, tex.TargetHeight, tex.BoundingWidth, tex.BoundingHeight }); + assetTypeDict.Add($"{sprite.Name.Content}_{i}", "spr"); + } } } @@ -144,6 +147,8 @@ void DumpSprite(UndertaleSprite sprite) void DumpFont(UndertaleFont font) { + if (font is null) + return; if (font.Texture != null) { UndertaleTexturePageItem tex = font.Texture; @@ -157,6 +162,8 @@ void DumpFont(UndertaleFont font) void DumpBackground(UndertaleBackground background) { + if (background is null) + return; if (background.Texture != null) { UndertaleTexturePageItem tex = background.Texture;