From 60567d3c6e7a91b9976ca58dc966d54a6aedba25 Mon Sep 17 00:00:00 2001 From: CST1229 <68464103+CST1229@users.noreply.github.com> Date: Wed, 3 Apr 2024 12:52:19 +0200 Subject: [PATCH 1/8] Add support for GM2024.2 tile compression --- UndertaleModLib/Models/UndertaleRoom.cs | 195 ++++++++++++++++++++++-- UndertaleModLib/UndertaleChunks.cs | 68 +++++++++ UndertaleModLib/UndertaleData.cs | 2 +- 3 files changed, 254 insertions(+), 11 deletions(-) diff --git a/UndertaleModLib/Models/UndertaleRoom.cs b/UndertaleModLib/Models/UndertaleRoom.cs index e460ff316..5f85cfad2 100644 --- a/UndertaleModLib/Models/UndertaleRoom.cs +++ b/UndertaleModLib/Models/UndertaleRoom.cs @@ -1637,12 +1637,17 @@ public void Serialize(UndertaleWriter writer) writer.Write(TilesY); if (TileData.Length != TilesY) throw new Exception("Invalid TileData row length"); - foreach (var row in TileData) + if (writer.undertaleData.IsVersionAtLeast(2024, 2)) + WriteCompressedTileData(writer); + else { - if (row.Length != TilesX) - throw new Exception("Invalid TileData column length"); - foreach (var tile in row) - writer.Write(tile); + foreach (var row in TileData) + { + if (row.Length != TilesX) + throw new Exception("Invalid TileData column length"); + foreach (var tile in row) + writer.Write(tile); + } } } @@ -1655,12 +1660,15 @@ public void Unserialize(UndertaleReader reader) TilesX = reader.ReadUInt32(); TilesY = reader.ReadUInt32(); TileData = new uint[TilesY][]; - for (uint y = 0; y < TilesY; y++) + if (reader.undertaleData.IsVersionAtLeast(2024, 2)) + ReadCompressedTileData(reader); + else { - TileData[y] = new uint[TilesX]; - for (uint x = 0; x < TilesX; x++) + for (uint y = 0; y < TilesY; y++) { - TileData[y][x] = reader.ReadUInt32(); + TileData[y] = new uint[TilesX]; + for (uint x = 0; x < TilesX; x++) + TileData[y][x] = reader.ReadUInt32(); } } } @@ -1674,11 +1682,178 @@ public static uint UnserializeChildObjectCount(UndertaleReader reader) uint tilesX = reader.ReadUInt32(); uint tilesY = reader.ReadUInt32(); - reader.Position += tilesX * tilesY * 4; + if (reader.undertaleData.IsVersionAtLeast(2024, 2)) + { + uint tileCount = tilesX * tilesY; + int tiles = 0; + while (tiles < tileCount) + { + byte opcode = reader.ReadByte(); + if (opcode >= 128) + { + // Repeat run + int length = opcode - 127; + reader.ReadUInt32(); + tiles += length; + } + else + { + // Verbatim run + int length = opcode; + for (int i = 0; i < length; i++) + reader.ReadUInt32(); + tiles += length; + } + } + } + else + reader.Position += tilesX * tilesY * 4; return count; } + /// + /// Reads 2024.2+ compressed RLE tile data. + /// + /// Where to deserialize from. + public void ReadCompressedTileData(UndertaleReader reader) + { + int x = 0; + int y = 0; + if (TilesY > 0) + TileData[y] = new uint[TilesX]; + + Func NextTile = () => + { + x++; + if (x >= TilesX) + { + x = 0; + y++; + if (y >= TilesY) + return true; + TileData[y] = new uint[TilesX]; + } + return false; + }; + + while (true) + { + byte length = reader.ReadByte(); + if (length >= 128) + { + // Repeat run + int runLength = length - 128 + 1; + uint tile = reader.ReadUInt32(); + for (int i = 0; i < runLength; i++) + { + TileData[y][x] = tile; + if (NextTile()) + break; + } + } + else + { + // Verbatim run + int runLength = length; + for (int i = 0; i < runLength; i++) + { + TileData[y][x] = reader.ReadUInt32(); + if (NextTile()) + break; + } + } + if (y >= TilesY) + break; + } + } + + /// + /// Writes 2024.2+ compressed RLE tile data. + /// + /// Where to serialize to. + public void WriteCompressedTileData(UndertaleWriter writer) + { + List run = new(); + run.EnsureCapacity(128); + bool runIsVerbatim = false; + Action EndRun = () => + { + if (run.Count == 0) + return; + + if (runIsVerbatim || run.Count == 1) + { + if (run.Count > 127) + throw new IndexOutOfRangeException("Attempted to encode verbatim tile run size " + run.Count + " larger than maximum 127"); + writer.Write((byte)run.Count); + foreach (uint tile in run) + writer.Write(tile); + } + else + { + if (run.Count > 128) + throw new IndexOutOfRangeException("Attempted to encode repeat tile run size " + run.Count + " larger than maximum 128"); + writer.Write((byte)(run.Count + 127)); + writer.Write(run[0]); + } + run.Clear(); + }; + + for (int y = 0; y < TileData.Length; y++) + { + uint[] row = TileData[y]; + if (row.Length != TilesX) + throw new Exception("Invalid TileData row length"); + for (int x = 0; x < row.Length; x++) + { + uint tile = row[x]; + if (!runIsVerbatim) + { + if (run.Count > 0 && tile != run[0]) + { + if (run.Count == 1) + { + runIsVerbatim = true; + run.Add(tile); + continue; + } + EndRun(); + } + else if (run.Count >= 128) + // Split the run + EndRun(); + run.Add(tile); + } + else + { + + if ((x + 1) <= TilesX || (y + 1) <= TilesY) + { + // Check the next tile for repeat runs + int nextX = x + 1; + int nextY = y; + if (nextX >= TilesX) + { + nextX = 0; + nextY++; + } + if (TileData[nextY][nextX] == tile) + { + EndRun(); + runIsVerbatim = false; + } + } + if (run.Count >= 127) + // Split the run + EndRun(); + run.Add(tile); + } + } + } + EndRun(); + } + /// public void Dispose() { diff --git a/UndertaleModLib/UndertaleChunks.cs b/UndertaleModLib/UndertaleChunks.cs index 3708f52f7..7c2148476 100644 --- a/UndertaleModLib/UndertaleChunks.cs +++ b/UndertaleModLib/UndertaleChunks.cs @@ -682,6 +682,9 @@ public class UndertaleChunkROOM : UndertaleListChunk internal override void UnserializeChunk(UndertaleReader reader) { + if (!checkedFor2024_2) + CheckForTileCompression(reader); + if (!checkedFor2022_1) CheckForEffectData(reader); @@ -694,8 +697,10 @@ internal override void UnserializeChunk(UndertaleReader reader) internal override uint UnserializeObjectCount(UndertaleReader reader) { checkedFor2022_1 = false; + checkedFor2024_2 = false; checkedForGMS2_2_2_302 = false; + CheckForTileCompression(reader); CheckForEffectData(reader); CheckForImageSpeed(reader); @@ -857,6 +862,69 @@ private void CheckForImageSpeed(UndertaleReader reader) checkedForGMS2_2_2_302 = true; } + + private static bool checkedFor2024_2; + private void CheckForTileCompression(UndertaleReader reader) + { + if (!reader.undertaleData.IsVersionAtLeast(2023, 2) || reader.undertaleData.IsVersionAtLeast(2024, 2)) + { + checkedFor2024_2 = true; + return; + } + + // Do a length check on room layers to see if this is 2024.2 or higher + long returnTo = reader.Position; + + // Iterate over all rooms until a length check is performed + int roomCount = reader.ReadInt32(); + for (uint roomIndex = 0; roomIndex < roomCount; roomIndex++) + { + // Advance to room data we're interested in (and grab pointer for next room) + reader.Position = returnTo + 4 + (4 * roomIndex); + uint roomPtr = (uint)reader.ReadInt32(); + reader.AbsPosition = roomPtr + (22 * 4); + + // Get the pointer for this room's layer list, as well as pointer to sequence list + uint layerListPtr = (uint)reader.ReadInt32(); + int seqnPtr = reader.ReadInt32(); + reader.AbsPosition = layerListPtr; + int layerCount = reader.ReadInt32(); + if (layerCount <= 0) + { + // No layers, try the next room + continue; + } + // Get pointer into the individual layer data (plus 8 bytes) for the first layer in the room + int jumpOffset = reader.ReadInt32() + 8; + + // Find the offset for the end of this layer + int nextOffset; + if (layerCount == 1) + nextOffset = seqnPtr; + else + nextOffset = reader.ReadInt32(); // (pointer to next element in the layer list) + + // Actually perform the length checks + reader.AbsPosition = jumpOffset; + + LayerType layerType = (LayerType)reader.ReadInt32(); + // This is the only way to repeat the loop, because each successful switch case terminates the loop + if (layerType != LayerType.Tiles) + continue; + + reader.Position += 10 * 4; + int tileMapWidth = reader.ReadInt32(); + int tileMapHeight = reader.ReadInt32(); + if (nextOffset - reader.AbsPosition != (tileMapWidth * tileMapHeight * 4)) + reader.undertaleData.SetGMS2Version(2024, 2); + // Check complete, found and tested a layer. + break; + } + + reader.Position = returnTo; + + checkedFor2024_2 = true; + } } public class UndertaleChunkDAFL : UndertaleEmptyChunk // DataFiles diff --git a/UndertaleModLib/UndertaleData.cs b/UndertaleModLib/UndertaleData.cs index b1013f55e..ae9ab8094 100644 --- a/UndertaleModLib/UndertaleData.cs +++ b/UndertaleModLib/UndertaleData.cs @@ -469,7 +469,7 @@ private bool TestGMS1Version(uint stableBuild, uint betaBuild, bool allowGMS2 = /// The build version. public void SetGMS2Version(uint major, uint minor = 0, uint release = 0, uint build = 0) { - if (major != 2 && major != 2022 && major != 2023) + if (major != 2 && major != 2022 && major != 2023 && major != 2024) throw new NotSupportedException("Attempted to set a version of GameMaker " + major + " using SetGMS2Version"); GeneralInfo.Major = major; From f27dd0fb82cf96eb7ad86d6485e3020a5ae4ffdb Mon Sep 17 00:00:00 2001 From: CST1229 <68464103+CST1229@users.noreply.github.com> Date: Wed, 3 Apr 2024 20:54:39 +0200 Subject: [PATCH 2/8] increment reader.Position instead of reading --- UndertaleModLib/Models/UndertaleRoom.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/UndertaleModLib/Models/UndertaleRoom.cs b/UndertaleModLib/Models/UndertaleRoom.cs index 5f85cfad2..8dd22c712 100644 --- a/UndertaleModLib/Models/UndertaleRoom.cs +++ b/UndertaleModLib/Models/UndertaleRoom.cs @@ -1693,7 +1693,7 @@ public static uint UnserializeChildObjectCount(UndertaleReader reader) { // Repeat run int length = opcode - 127; - reader.ReadUInt32(); + reader.Position += 4; tiles += length; } else @@ -1701,7 +1701,7 @@ public static uint UnserializeChildObjectCount(UndertaleReader reader) // Verbatim run int length = opcode; for (int i = 0; i < length; i++) - reader.ReadUInt32(); + reader.Position += 4; tiles += length; } } From 4d85a6e609db517c93f4e4f000a48d4051968d49 Mon Sep 17 00:00:00 2001 From: CST1229 <68464103+CST1229@users.noreply.github.com> Date: Sun, 7 Apr 2024 12:51:11 +0200 Subject: [PATCH 3/8] i'm dumb --- UndertaleModLib/Models/UndertaleRoom.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/UndertaleModLib/Models/UndertaleRoom.cs b/UndertaleModLib/Models/UndertaleRoom.cs index 8dd22c712..a712fd844 100644 --- a/UndertaleModLib/Models/UndertaleRoom.cs +++ b/UndertaleModLib/Models/UndertaleRoom.cs @@ -1700,8 +1700,7 @@ public static uint UnserializeChildObjectCount(UndertaleReader reader) { // Verbatim run int length = opcode; - for (int i = 0; i < length; i++) - reader.Position += 4; + reader.Position += length * 4; tiles += length; } } From 25c5830864a8c06c2539930a6073f21497748281 Mon Sep 17 00:00:00 2001 From: Benjamin Urquhart Date: Thu, 25 Apr 2024 20:30:36 -0400 Subject: [PATCH 4/8] Account for dummy tiles in some cases There are still problems loading certain games (see: TetherGeist) but it gets rid of the load warnings --- UndertaleModLib/Models/UndertaleRoom.cs | 44 +++++++++++++++++++-- UndertaleModLib/UndertaleChunks.cs | 52 ++++++++++++++----------- UndertaleModTool/MainWindow.xaml.cs | 9 ++++- 3 files changed, 79 insertions(+), 26 deletions(-) diff --git a/UndertaleModLib/Models/UndertaleRoom.cs b/UndertaleModLib/Models/UndertaleRoom.cs index a712fd844..6b7b7611b 100644 --- a/UndertaleModLib/Models/UndertaleRoom.cs +++ b/UndertaleModLib/Models/UndertaleRoom.cs @@ -4,6 +4,7 @@ using System.ComponentModel; using System.Diagnostics; using System.Drawing; +using System.IO; using System.Linq; using System.Runtime.CompilerServices; @@ -1736,14 +1737,16 @@ public void ReadCompressedTileData(UndertaleReader reader) return false; }; + byte length; + uint tile; while (true) { - byte length = reader.ReadByte(); + length = reader.ReadByte(); if (length >= 128) { // Repeat run - int runLength = length - 128 + 1; - uint tile = reader.ReadUInt32(); + int runLength = (length & 0x7f) + 1; + tile = reader.ReadUInt32(); for (int i = 0; i < runLength; i++) { TileData[y][x] = tile; @@ -1765,6 +1768,38 @@ public void ReadCompressedTileData(UndertaleReader reader) if (y >= TilesY) break; } + + if (TilesX == 0 && TilesY == 0) + return; + + // Due to a GMAC bug, 2 blank tiles are inserted into the layer + // if the last 2 tiles in the layer are different. + // This is a certified YoyoGames moment right here. + x = (int)(TilesX - 1); + y = (int)(TilesY - 1); + uint lastTile = TileData[y][x]; + + x--; + if (x < 0) + { + x = (int)(TilesX - 1); + y--; + } + if (y < 0) + y = 0; // most likely only 1 tile on the layer in which case the blank tiles exist + + bool hasPadding = TileData[y][x] != lastTile; + if (hasPadding) + { + length = reader.ReadByte(); + tile = reader.ReadUInt32(); + + // sanity check: run of 2 empty tiles + if (length != 0x81) + throw new IOException("Expected 0x81, got " + length.ToString("X2")); + if (tile != unchecked((uint)-1)) + throw new IOException("Expected -1, got " + tile + " (0x" + tile.ToString("X8") + ")"); + } } /// @@ -1851,6 +1886,9 @@ public void WriteCompressedTileData(UndertaleWriter writer) } } EndRun(); + + // TODO: insert 2 blank tiles if the last 2 tiles on the layer don't match. + // This is important for writing an identical file. } /// diff --git a/UndertaleModLib/UndertaleChunks.cs b/UndertaleModLib/UndertaleChunks.cs index 7c2148476..626d8432c 100644 --- a/UndertaleModLib/UndertaleChunks.cs +++ b/UndertaleModLib/UndertaleChunks.cs @@ -894,35 +894,43 @@ private void CheckForTileCompression(UndertaleReader reader) // No layers, try the next room continue; } - // Get pointer into the individual layer data (plus 8 bytes) for the first layer in the room - int jumpOffset = reader.ReadInt32() + 8; - // Find the offset for the end of this layer - int nextOffset; - if (layerCount == 1) - nextOffset = seqnPtr; - else - nextOffset = reader.ReadInt32(); // (pointer to next element in the layer list) + for (int layerNum = 0; layerNum < layerCount; layerNum++) + { + reader.AbsPosition = layerListPtr + (4 * layerNum) + 4; - // Actually perform the length checks - reader.AbsPosition = jumpOffset; + // Get pointer into the individual layer data (plus 8 bytes) for the first layer in the room + int jumpOffset = reader.ReadInt32() + 8; - LayerType layerType = (LayerType)reader.ReadInt32(); - // This is the only way to repeat the loop, because each successful switch case terminates the loop - if (layerType != LayerType.Tiles) - continue; + // Find the offset for the end of this layer + int nextOffset; + if (layerNum == layerCount - 1) + nextOffset = seqnPtr; + else + nextOffset = reader.ReadInt32(); // (pointer to next element in the layer list) - reader.Position += 10 * 4; - int tileMapWidth = reader.ReadInt32(); - int tileMapHeight = reader.ReadInt32(); - if (nextOffset - reader.AbsPosition != (tileMapWidth * tileMapHeight * 4)) - reader.undertaleData.SetGMS2Version(2024, 2); - // Check complete, found and tested a layer. - break; + // Actually perform the length checks + reader.AbsPosition = jumpOffset; + + LayerType layerType = (LayerType)reader.ReadInt32(); + if (layerType != LayerType.Tiles) + continue; + + reader.Position += 10 * 4; + int tileMapWidth = reader.ReadInt32(); + int tileMapHeight = reader.ReadInt32(); + if (nextOffset - reader.AbsPosition != (tileMapWidth * tileMapHeight * 4)) + { + // Check complete, found and tested a layer. + reader.undertaleData.SetGMS2Version(2024, 2); + reader.Position = returnTo; + checkedFor2024_2 = true; + return; + } + } } reader.Position = returnTo; - checkedFor2024_2 = true; } } diff --git a/UndertaleModTool/MainWindow.xaml.cs b/UndertaleModTool/MainWindow.xaml.cs index f9bed75e7..c0add7334 100644 --- a/UndertaleModTool/MainWindow.xaml.cs +++ b/UndertaleModTool/MainWindow.xaml.cs @@ -956,8 +956,12 @@ private async Task LoadFile(string filename, bool preventClose = false, bool onl { data = UndertaleIO.Read(stream, warning => { +#if DEBUG + Debug.WriteLine("[WARNING]: " + warning); + Debug.WriteLine(""); +#else this.ShowWarning(warning, "Loading warning"); - +#endif if (warning.Contains("unserializeCountError.txt") || warning.Contains("object pool size")) return; @@ -973,6 +977,9 @@ private async Task LoadFile(string filename, bool preventClose = false, bool onl } catch (Exception e) { +#if DEBUG + Debug.WriteLine(e); +#endif this.ShowError("An error occured while trying to load:\n" + e.Message, "Load error"); } From a358607c24a3abb0b9bf161cef4e596d8df4188a Mon Sep 17 00:00:00 2001 From: Benjamin Urquhart Date: Thu, 25 Apr 2024 22:18:25 -0400 Subject: [PATCH 5/8] Handle single tile layers correctly --- UndertaleModLib/Models/UndertaleRoom.cs | 8 ++++++-- UndertaleModTool/MainWindow.xaml.cs | 15 ++++++--------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/UndertaleModLib/Models/UndertaleRoom.cs b/UndertaleModLib/Models/UndertaleRoom.cs index 6b7b7611b..0c763698e 100644 --- a/UndertaleModLib/Models/UndertaleRoom.cs +++ b/UndertaleModLib/Models/UndertaleRoom.cs @@ -1777,18 +1777,22 @@ public void ReadCompressedTileData(UndertaleReader reader) // This is a certified YoyoGames moment right here. x = (int)(TilesX - 1); y = (int)(TilesY - 1); + bool hasPadding = false; uint lastTile = TileData[y][x]; + // Go back 1 tile x--; if (x < 0) { x = (int)(TilesX - 1); y--; } + if (y < 0) - y = 0; // most likely only 1 tile on the layer in which case the blank tiles exist + hasPadding = true; // most likely only 1 tile on the layer in which case the blank tiles exist + else + hasPadding = TileData[y][x] != lastTile; - bool hasPadding = TileData[y][x] != lastTile; if (hasPadding) { length = reader.ReadByte(); diff --git a/UndertaleModTool/MainWindow.xaml.cs b/UndertaleModTool/MainWindow.xaml.cs index c0add7334..f91ee3efa 100644 --- a/UndertaleModTool/MainWindow.xaml.cs +++ b/UndertaleModTool/MainWindow.xaml.cs @@ -1,7 +1,8 @@ -using Microsoft.CodeAnalysis; +#define WARNING_POPUP + +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Scripting; using Microsoft.CodeAnalysis.Scripting; -using Microsoft.CodeAnalysis.Scripting.Hosting; using Microsoft.Win32; using Newtonsoft.Json; using System; @@ -33,11 +34,8 @@ using System.IO.Pipes; using Ookii.Dialogs.Wpf; -using ColorConvert = System.Windows.Media.ColorConverter; using System.Text.RegularExpressions; using System.Windows.Data; -using System.Reflection.Metadata.Ecma335; -using System.Windows.Media.Imaging; using System.Security.Cryptography; using System.Collections.Concurrent; using System.Runtime; @@ -49,7 +47,6 @@ using System.Globalization; using System.Windows.Controls.Primitives; using System.Runtime.CompilerServices; -using System.Diagnostics.Metrics; using System.Windows.Interop; namespace UndertaleModTool @@ -956,11 +953,11 @@ private async Task LoadFile(string filename, bool preventClose = false, bool onl { data = UndertaleIO.Read(stream, warning => { -#if DEBUG +#if WARNING_POPUP + this.ShowWarning(warning, "Loading warning"); +#else Debug.WriteLine("[WARNING]: " + warning); Debug.WriteLine(""); -#else - this.ShowWarning(warning, "Loading warning"); #endif if (warning.Contains("unserializeCountError.txt") || warning.Contains("object pool size")) From c3bafea86f6ba4859db39c3e69b9819d1b9db64c Mon Sep 17 00:00:00 2001 From: Benjamin Urquhart Date: Tue, 30 Apr 2024 00:00:07 -0400 Subject: [PATCH 6/8] Fix empty layers I think --- UndertaleModLib/Models/UndertaleRoom.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/UndertaleModLib/Models/UndertaleRoom.cs b/UndertaleModLib/Models/UndertaleRoom.cs index 0c763698e..b705b4e91 100644 --- a/UndertaleModLib/Models/UndertaleRoom.cs +++ b/UndertaleModLib/Models/UndertaleRoom.cs @@ -1718,11 +1718,13 @@ public static uint UnserializeChildObjectCount(UndertaleReader reader) /// Where to deserialize from. public void ReadCompressedTileData(UndertaleReader reader) { + if (TilesX == 0 && TilesY == 0) + return; + int x = 0; int y = 0; if (TilesY > 0) TileData[y] = new uint[TilesX]; - Func NextTile = () => { x++; @@ -1769,9 +1771,6 @@ public void ReadCompressedTileData(UndertaleReader reader) break; } - if (TilesX == 0 && TilesY == 0) - return; - // Due to a GMAC bug, 2 blank tiles are inserted into the layer // if the last 2 tiles in the layer are different. // This is a certified YoyoGames moment right here. From 150bbfd7aa801db07e6184a704aafe5311877645 Mon Sep 17 00:00:00 2001 From: Benjamin Urquhart Date: Tue, 30 Apr 2024 00:04:22 -0400 Subject: [PATCH 7/8] Remove leftover debugging code --- UndertaleModTool/MainWindow.xaml.cs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/UndertaleModTool/MainWindow.xaml.cs b/UndertaleModTool/MainWindow.xaml.cs index f91ee3efa..fe91d0cbe 100644 --- a/UndertaleModTool/MainWindow.xaml.cs +++ b/UndertaleModTool/MainWindow.xaml.cs @@ -1,6 +1,4 @@ -#define WARNING_POPUP - -using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Scripting; using Microsoft.CodeAnalysis.Scripting; using Microsoft.Win32; @@ -953,12 +951,7 @@ private async Task LoadFile(string filename, bool preventClose = false, bool onl { data = UndertaleIO.Read(stream, warning => { -#if WARNING_POPUP this.ShowWarning(warning, "Loading warning"); -#else - Debug.WriteLine("[WARNING]: " + warning); - Debug.WriteLine(""); -#endif if (warning.Contains("unserializeCountError.txt") || warning.Contains("object pool size")) return; From 2d6f8fce86f33691dfe18d53d00529907824d823 Mon Sep 17 00:00:00 2001 From: Benjamin Urquhart Date: Tue, 30 Apr 2024 01:08:26 -0400 Subject: [PATCH 8/8] Append blank tiles on save when needed This works with my simple test project, the only actual 2024.2 game I have on hand has other issues so I can't test it more. --- UndertaleModLib/Models/UndertaleRoom.cs | 42 +++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/UndertaleModLib/Models/UndertaleRoom.cs b/UndertaleModLib/Models/UndertaleRoom.cs index b705b4e91..4b0c1a972 100644 --- a/UndertaleModLib/Models/UndertaleRoom.cs +++ b/UndertaleModLib/Models/UndertaleRoom.cs @@ -1875,7 +1875,7 @@ public void WriteCompressedTileData(UndertaleWriter writer) nextX = 0; nextY++; } - if (TileData[nextY][nextX] == tile) + if (nextY < TilesY && TileData[nextY][nextX] == tile) { EndRun(); runIsVerbatim = false; @@ -1888,10 +1888,46 @@ public void WriteCompressedTileData(UndertaleWriter writer) } } } + EndRun(); - // TODO: insert 2 blank tiles if the last 2 tiles on the layer don't match. - // This is important for writing an identical file. + // Append 2 blank tiles if the last 2 tiles on the layer don't match. + // This is important for writing an identical file as the Gamemaker IDE + // does it at compile time to work around a GMAC bug. + + // As far as I know empty layers are not affected + if (TilesX == 0 && TilesY == 0) + return; + + int prevX = (int)TilesX - 2; + int prevY = (int)TilesY - 1; + + if (prevX < 0) + { + prevY--; + prevX = (int)TilesX - 1; + } + bool writeBlanks = false; + + + if (prevY < 0) + writeBlanks = true; // Single tile on layer, affected + else + { + // Run of 1 with blank tile (-1) is considered as 2 matching tiles + // so we shouldn't need to append blanks in that case (I think). + int lastX = (int)TilesX - 1; + int lastY = (int)TilesY - 1; + writeBlanks = TileData[lastY][lastX] != TileData[prevY][prevX]; + } + + if (writeBlanks) + { + runIsVerbatim = false; + run.Add(0xffffffff); + run.Add(0xffffffff); + EndRun(); + } } ///