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();
+ }
}
///