From 8b0a235bca5d0b35803b9729ef8ee842d8630bb9 Mon Sep 17 00:00:00 2001 From: colinator27 Date: Thu, 15 Nov 2018 16:05:29 -0500 Subject: [PATCH 1/9] Fix EMBI, add TGIN internal support, fix EXTN potential crash --- .../Models/UndertaleEmbeddedImage.cs | 32 ++++ .../Models/UndertaleTextureGroupInfo.cs | 146 ++++++++++++++++++ UndertaleModLib/Models/UndertaleUnused.cs | 17 -- UndertaleModLib/UndertaleChunks.cs | 35 ++++- UndertaleModLib/UndertaleModLib.csproj | 2 + 5 files changed, 211 insertions(+), 21 deletions(-) create mode 100644 UndertaleModLib/Models/UndertaleEmbeddedImage.cs create mode 100644 UndertaleModLib/Models/UndertaleTextureGroupInfo.cs diff --git a/UndertaleModLib/Models/UndertaleEmbeddedImage.cs b/UndertaleModLib/Models/UndertaleEmbeddedImage.cs new file mode 100644 index 000000000..add02aa8e --- /dev/null +++ b/UndertaleModLib/Models/UndertaleEmbeddedImage.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace UndertaleModLib.Models +{ + // Not to be confused with the other "embedded" resources, this is a bit separate. + // GMS2 only, see https://github.com/krzys-h/UndertaleModTool/issues/4#issuecomment-421844420 for rough structure, but doesn't appear commonly used + public class UndertaleEmbeddedImage : UndertaleObject + { + public UndertaleString Name; + public UndertaleTexturePageItem TextureEntry; + + public UndertaleEmbeddedImage() + { + } + + public void Serialize(UndertaleWriter writer) + { + writer.WriteUndertaleString(Name); + writer.WriteUndertaleObjectPointer(TextureEntry); + } + + public void Unserialize(UndertaleReader reader) + { + Name = reader.ReadUndertaleString(); + TextureEntry = reader.ReadUndertaleObjectPointer(); + } + } +} diff --git a/UndertaleModLib/Models/UndertaleTextureGroupInfo.cs b/UndertaleModLib/Models/UndertaleTextureGroupInfo.cs new file mode 100644 index 000000000..1571a112f --- /dev/null +++ b/UndertaleModLib/Models/UndertaleTextureGroupInfo.cs @@ -0,0 +1,146 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace UndertaleModLib.Models +{ + public class UndertaleTextureGroupInfo : UndertaleObject + { + public UndertaleString GroupName; + public List> TexturePages; + public List> Sprites; + public List> SpineSprites; + public List> Fonts; + public List> Tilesets; + + public UndertaleTextureGroupInfo() + { + TexturePages = new List>(); + Sprites = new List>(); + SpineSprites = new List>(); + Fonts = new List>(); + Tilesets = new List>(); + } + + public void Serialize(UndertaleWriter writer) + { + writer.WriteUndertaleString(GroupName); + uint pointer1 = writer.Position; writer.Write(0); + uint pointer2 = writer.Position; writer.Write(0); + uint pointer3 = writer.Position; writer.Write(0); + uint pointer4 = writer.Position; writer.Write(0); + uint pointer5 = writer.Position; writer.Write(0); + + SeekWritePointer(writer, pointer1); + foreach (UndertaleResourceById r in TexturePages) + { + writer.Write(r.Serialize(writer)); + } + + SeekWritePointer(writer, pointer2); + foreach (UndertaleResourceById r in Sprites) + { + writer.Write(r.Serialize(writer)); + } + + SeekWritePointer(writer, pointer3); + foreach (UndertaleResourceById r in SpineSprites) + { + writer.Write(r.Serialize(writer)); + } + + SeekWritePointer(writer, pointer4); + foreach (UndertaleResourceById r in Fonts) + { + writer.Write(r.Serialize(writer)); + } + + SeekWritePointer(writer, pointer5); + foreach (UndertaleResourceById r in Tilesets) + { + writer.Write(r.Serialize(writer)); + } + } + + private void SeekWritePointer(UndertaleWriter writer, uint pointer) + { + uint returnTo = writer.Position; + writer.Position = pointer; + writer.Write(returnTo); + writer.Position = returnTo; + } + + public void Unserialize(UndertaleReader reader) + { + GroupName = reader.ReadUndertaleString(); + uint pointer1 = reader.ReadUInt32(); + uint pointer2 = reader.ReadUInt32(); + uint pointer3 = reader.ReadUInt32(); + uint pointer4 = reader.ReadUInt32(); + uint pointer5 = reader.ReadUInt32(); + + EnsurePointer(reader, pointer1); + { + int count = reader.ReadInt32(); + for (int i = 0; i < count; i++) + { + UndertaleResourceById r = new UndertaleResourceById("TXTR"); + r.Unserialize(reader, reader.ReadInt32()); + TexturePages.Add(r); + } + } + + EnsurePointer(reader, pointer2); + { + int count = reader.ReadInt32(); + for (int i = 0; i < count; i++) + { + UndertaleResourceById r = new UndertaleResourceById("SPRT"); + r.Unserialize(reader, reader.ReadInt32()); + Sprites.Add(r); + } + } + + EnsurePointer(reader, pointer3); + { + int count = reader.ReadInt32(); + for (int i = 0; i < count; i++) + { + UndertaleResourceById r = new UndertaleResourceById("SPRT"); + r.Unserialize(reader, reader.ReadInt32()); + SpineSprites.Add(r); + } + } + + EnsurePointer(reader, pointer4); + { + int count = reader.ReadInt32(); + for (int i = 0; i < count; i++) + { + UndertaleResourceById r = new UndertaleResourceById("FONT"); + r.Unserialize(reader, reader.ReadInt32()); + Fonts.Add(r); + } + } + + EnsurePointer(reader, pointer5); + { + int count = reader.ReadInt32(); + for (int i = 0; i < count; i++) + { + UndertaleResourceById r = new UndertaleResourceById("BGND"); + r.Unserialize(reader, reader.ReadInt32()); + Tilesets.Add(r); + } + } + } + + private void EnsurePointer(UndertaleReader reader, uint pointer) + { + Debug.Assert(reader.Position == pointer, "Invalid pointer in TGIN entry"); + } + } +} diff --git a/UndertaleModLib/Models/UndertaleUnused.cs b/UndertaleModLib/Models/UndertaleUnused.cs index 6e2201b25..6a131e347 100644 --- a/UndertaleModLib/Models/UndertaleUnused.cs +++ b/UndertaleModLib/Models/UndertaleUnused.cs @@ -7,22 +7,5 @@ namespace UndertaleModLib.Models { - // GMS2 only, see https://github.com/krzys-h/UndertaleModTool/issues/4#issuecomment-421844420 for rough structure, but doesn't appear commonly used - public class UndertaleEmbeddedISomething : UndertaleObject - { - public UndertaleEmbeddedISomething() - { - throw new NotImplementedException(); - } - public void Serialize(UndertaleWriter writer) - { - throw new NotImplementedException(); - } - - public void Unserialize(UndertaleReader reader) - { - throw new NotImplementedException(); - } - } } diff --git a/UndertaleModLib/UndertaleChunks.cs b/UndertaleModLib/UndertaleChunks.cs index 755a1ab66..5499aaf6d 100644 --- a/UndertaleModLib/UndertaleChunks.cs +++ b/UndertaleModLib/UndertaleChunks.cs @@ -35,6 +35,7 @@ public class UndertaleChunkFORM : UndertaleChunk public UndertaleChunkROOM ROOM => Chunks.GetValueOrDefault("ROOM") as UndertaleChunkROOM; public UndertaleChunkDAFL DAFL => Chunks.GetValueOrDefault("DAFL") as UndertaleChunkDAFL; public UndertaleChunkTPAG TPAG => Chunks.GetValueOrDefault("TPAG") as UndertaleChunkTPAG; + public UndertaleChunkTGIN TGIN => Chunks.GetValueOrDefault("TGIN") as UndertaleChunkTGIN; public UndertaleChunkCODE CODE => Chunks.GetValueOrDefault("CODE") as UndertaleChunkCODE; public UndertaleChunkVARI VARI => Chunks.GetValueOrDefault("VARI") as UndertaleChunkVARI; public UndertaleChunkFUNC FUNC => Chunks.GetValueOrDefault("FUNC") as UndertaleChunkFUNC; @@ -107,9 +108,12 @@ internal override void UnserializeChunk(UndertaleReader reader) // Strange data for each extension, some kind of unique identifier based on // the product ID for each of them productIdData = new List(); - for (int i = 0; i < List.Count; i++) + if (reader.undertaleData.GeneralInfo?.Major >= 2 || (reader.undertaleData.GeneralInfo?.Major == 1 && reader.undertaleData.GeneralInfo?.Build >= 9999)) { - productIdData.Add(reader.ReadBytes(16)); + for (int i = 0; i < List.Count; i++) + { + productIdData.Add(reader.ReadBytes(16)); + } } } @@ -403,9 +407,9 @@ public class UndertaleChunkAUDO : UndertaleListChunk { public override string Name => "AUDO"; } - + // GMS2 only - public class UndertaleChunkEMBI : UndertaleSimpleListChunk + public class UndertaleChunkEMBI : UndertaleSimpleListChunk { public override string Name => "EMBI"; @@ -426,4 +430,27 @@ internal override void UnserializeChunk(UndertaleReader reader) base.UnserializeChunk(reader); } } + + // GMS2.2.1+ only + public class UndertaleChunkTGIN : UndertaleSimpleListChunk + { + public override string Name => "TGIN"; + + internal override void SerializeChunk(UndertaleWriter writer) + { + if (writer.undertaleData.GeneralInfo.Major < 2) + throw new InvalidOperationException(); + writer.Write((uint)1); // Hardcoded 1 + base.SerializeChunk(writer); + } + + internal override void UnserializeChunk(UndertaleReader reader) + { + if (reader.undertaleData.GeneralInfo.Major < 2) + throw new InvalidOperationException(); + if (reader.ReadUInt32() != 1) + throw new Exception("Should be hardcoded 1"); + base.UnserializeChunk(reader); + } + } } diff --git a/UndertaleModLib/UndertaleModLib.csproj b/UndertaleModLib/UndertaleModLib.csproj index 92d50ad3a..dd11dbaea 100644 --- a/UndertaleModLib/UndertaleModLib.csproj +++ b/UndertaleModLib/UndertaleModLib.csproj @@ -44,6 +44,7 @@ + @@ -61,6 +62,7 @@ + From c6b10af99cbb865a4a07b1b24588fc6f5b9f0abc Mon Sep 17 00:00:00 2001 From: colinator27 Date: Sat, 17 Nov 2018 09:06:47 -0500 Subject: [PATCH 2/9] Fixes to bytecode 17 changes, fix a few minor issues, name some more variables --- .../Models/UndertaleEmbeddedTexture.cs | 16 +- UndertaleModLib/Models/UndertaleFont.cs | 54 ++++++- .../Models/UndertaleGeneralInfo.cs | 2 +- UndertaleModLib/Models/UndertaleRoom.cs | 5 +- .../Models/UndertaleTextureGroupInfo.cs | 149 ++++-------------- UndertaleModLib/UndertaleChunks.cs | 3 +- UndertaleModLib/UndertaleData.cs | 4 + UndertaleModLib/UndertaleLists.cs | 50 +++++- .../UndertaleEmbeddedTextureEditor.xaml | 8 +- .../Editors/UndertaleFontEditor.xaml | 2 +- UndertaleModTool/MainWindow.xaml.cs | 6 +- 11 files changed, 159 insertions(+), 140 deletions(-) diff --git a/UndertaleModLib/Models/UndertaleEmbeddedTexture.cs b/UndertaleModLib/Models/UndertaleEmbeddedTexture.cs index b0bd59cf7..22066d590 100644 --- a/UndertaleModLib/Models/UndertaleEmbeddedTexture.cs +++ b/UndertaleModLib/Models/UndertaleEmbeddedTexture.cs @@ -10,29 +10,29 @@ namespace UndertaleModLib.Models { public class UndertaleEmbeddedTexture : UndertaleResource, INotifyPropertyChanged { - private uint _GMS2Unknown; - private uint _UnknownAlwaysZero = 0; + private uint _GeneratedMips; + private uint _Scaled = 0; private TexData _TextureData = new TexData(); - public uint GMS2Unknown { get => _GMS2Unknown; set { _GMS2Unknown = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("GMS2Unknown")); } } - public uint UnknownAlwaysZero { get => _UnknownAlwaysZero; set { _UnknownAlwaysZero = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("UnknownAlwaysZero")); } } + public uint Scaled { get => _Scaled; set { _Scaled = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Scaled")); } } + public uint GeneratedMips { get => _GeneratedMips; set { _GeneratedMips = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("GeneratedMips")); } } public TexData TextureData { get => _TextureData; set { _TextureData = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("TextureData")); } } public event PropertyChangedEventHandler PropertyChanged; public void Serialize(UndertaleWriter writer) { + writer.Write(Scaled); if (writer.undertaleData.GeneralInfo.Major >= 2) - writer.Write(GMS2Unknown); - writer.Write(UnknownAlwaysZero); + writer.Write(GeneratedMips); writer.WriteUndertaleObjectPointer(TextureData); } public void Unserialize(UndertaleReader reader) { + Scaled = reader.ReadUInt32(); if (reader.undertaleData.GeneralInfo.Major >= 2) - GMS2Unknown = reader.ReadUInt32(); - UnknownAlwaysZero = reader.ReadUInt32(); + GeneratedMips = reader.ReadUInt32(); TextureData = reader.ReadUndertaleObjectPointer(); } diff --git a/UndertaleModLib/Models/UndertaleFont.cs b/UndertaleModLib/Models/UndertaleFont.cs index a2cedc6b2..a06498575 100644 --- a/UndertaleModLib/Models/UndertaleFont.cs +++ b/UndertaleModLib/Models/UndertaleFont.cs @@ -21,6 +21,7 @@ public class UndertaleFont : UndertaleNamedResource, INotifyPropertyChanged private UndertaleTexturePageItem _Texture; private float _ScaleX; private float _ScaleY; + private int _AscenderOffset; public UndertaleString Name { get => _Name; set { _Name = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Name")); } } public UndertaleString DisplayName { get => _DisplayName; set { _DisplayName = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("DisplayName")); } } @@ -35,6 +36,7 @@ public class UndertaleFont : UndertaleNamedResource, INotifyPropertyChanged public float ScaleX { get => _ScaleX; set { _ScaleX = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("ScaleX")); } } public float ScaleY { get => _ScaleY; set { _ScaleY = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("ScaleY")); } } public UndertalePointerList Glyphs { get; private set; } = new UndertalePointerList(); + public int AscenderOffset { get => _AscenderOffset; set { _AscenderOffset = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("AscenderOffset")); } } public event PropertyChangedEventHandler PropertyChanged; @@ -45,16 +47,18 @@ public class Glyph : UndertaleObject, INotifyPropertyChanged private ushort _SourceY; private ushort _SourceWidth; private ushort _SourceHeight; - private ushort _Shift; - private uint _Offset; // TODO: probably signed + private short _Shift; + private int _Offset; + private UndertaleSimpleListShort _Kerning; public ushort Character { get => _Character; set { _Character = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Character")); } } public ushort SourceX { get => _SourceX; set { _SourceX = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("SourceX")); } } public ushort SourceY { get => _SourceY; set { _SourceY = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("SourceY")); } } public ushort SourceWidth { get => _SourceWidth; set { _SourceWidth = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("SourceWidth")); } } public ushort SourceHeight { get => _SourceHeight; set { _SourceHeight = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("SourceHeight")); } } - public ushort Shift { get => _Shift; set { _Shift = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Shift")); } } - public uint Offset { get => _Offset; set { _Offset = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Offset")); } } + public short Shift { get => _Shift; set { _Shift = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Shift")); } } + public int Offset { get => _Offset; set { _Offset = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Offset")); } } + public UndertaleSimpleListShort Kerning { get => _Kerning; set { _Kerning = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Kerning")); } } public event PropertyChangedEventHandler PropertyChanged; @@ -66,7 +70,14 @@ public void Serialize(UndertaleWriter writer) writer.Write(SourceWidth); writer.Write(SourceHeight); writer.Write(Shift); - writer.Write(Offset); + if (writer.undertaleData.GeneralInfo?.Major >= 2 || (writer.undertaleData.GeneralInfo?.Major == 1 && writer.undertaleData.GeneralInfo?.Build == 9999)) + { + writer.Write((short)Offset); + writer.WriteUndertaleObject(Kerning); + } else + { + writer.Write(Offset); + } } public void Unserialize(UndertaleReader reader) @@ -76,8 +87,33 @@ public void Unserialize(UndertaleReader reader) SourceY = reader.ReadUInt16(); SourceWidth = reader.ReadUInt16(); SourceHeight = reader.ReadUInt16(); - Shift = reader.ReadUInt16(); - Offset = reader.ReadUInt32(); + Shift = reader.ReadInt16(); + if (reader.undertaleData.GeneralInfo?.Major >= 2 || (reader.undertaleData.GeneralInfo?.Major == 1 && reader.undertaleData.GeneralInfo?.Build == 9999)) + { + Offset = reader.ReadInt16(); + Kerning = reader.ReadUndertaleObject>(); + } else + { + Offset = reader.ReadInt32(); // Maybe? I don't really know, but this definitely used to work + } + } + + public class GlyphKerning : UndertaleObject + { + public short Other; + public short Amount; + + public void Serialize(UndertaleWriter writer) + { + writer.Write(Other); + writer.Write(Amount); + } + + public void Unserialize(UndertaleReader reader) + { + Other = reader.ReadInt16(); + Amount = reader.ReadInt16(); + } } } @@ -95,6 +131,8 @@ public void Serialize(UndertaleWriter writer) writer.WriteUndertaleObjectPointer(Texture); writer.Write(ScaleX); writer.Write(ScaleY); + if (writer.undertaleData.GeneralInfo?.Major >= 2 || (writer.undertaleData.GeneralInfo?.Major == 1 && writer.undertaleData.GeneralInfo?.Build == 9999)) + writer.Write(AscenderOffset); writer.WriteUndertaleObject(Glyphs); } @@ -112,6 +150,8 @@ public void Unserialize(UndertaleReader reader) Texture = reader.ReadUndertaleObjectPointer(); ScaleX = reader.ReadSingle(); ScaleY = reader.ReadSingle(); + if (reader.undertaleData.GeneralInfo?.Major >= 2 && reader.undertaleData.GeneralInfo?.BytecodeVersion >= 17) + AscenderOffset = reader.ReadInt32(); Glyphs = reader.ReadUndertaleObject>(); } diff --git a/UndertaleModLib/Models/UndertaleGeneralInfo.cs b/UndertaleModLib/Models/UndertaleGeneralInfo.cs index e164f8c01..ba1444eef 100644 --- a/UndertaleModLib/Models/UndertaleGeneralInfo.cs +++ b/UndertaleModLib/Models/UndertaleGeneralInfo.cs @@ -159,7 +159,7 @@ public void Unserialize(UndertaleReader reader) GMS2AllowStatistics = reader.ReadBoolean(); GMS2GameGUID = reader.ReadBytes(16); } - reader.undertaleData.UnsupportedBytecodeVersion = (BytecodeVersion != 15 && BytecodeVersion != 16); + reader.undertaleData.UnsupportedBytecodeVersion = (BytecodeVersion != 15 && BytecodeVersion != 16 && BytecodeVersion != 17); } public override string ToString() diff --git a/UndertaleModLib/Models/UndertaleRoom.cs b/UndertaleModLib/Models/UndertaleRoom.cs index 2523cab33..dd0c7a23b 100644 --- a/UndertaleModLib/Models/UndertaleRoom.cs +++ b/UndertaleModLib/Models/UndertaleRoom.cs @@ -628,7 +628,7 @@ public class LayerTilesData : LayerData, INotifyPropertyChanged public void Serialize(UndertaleWriter writer) { - writer.WriteUndertaleObject(_Background); + _Background.Serialize(writer); // see comment below writer.Write(TilesX); writer.Write(TilesY); if (TileData.Length != TilesY) @@ -644,7 +644,8 @@ public void Serialize(UndertaleWriter writer) public void Unserialize(UndertaleReader reader) { - _Background = reader.ReadUndertaleObject>(); + _Background = new UndertaleResourceById(); // see comment in UndertaleGlobalInit.Unserialize + _Background.Unserialize(reader); _TileData = null; // prevent unnecessary resizes TilesX = reader.ReadUInt32(); TilesY = reader.ReadUInt32(); diff --git a/UndertaleModLib/Models/UndertaleTextureGroupInfo.cs b/UndertaleModLib/Models/UndertaleTextureGroupInfo.cs index 1571a112f..452a1be40 100644 --- a/UndertaleModLib/Models/UndertaleTextureGroupInfo.cs +++ b/UndertaleModLib/Models/UndertaleTextureGroupInfo.cs @@ -10,137 +10,58 @@ namespace UndertaleModLib.Models public class UndertaleTextureGroupInfo : UndertaleObject { public UndertaleString GroupName; - public List> TexturePages; - public List> Sprites; - public List> SpineSprites; - public List> Fonts; - public List> Tilesets; + public UndertaleSimpleResourcesList TexturePages; + public UndertaleSimpleResourcesList Sprites; + public UndertaleSimpleResourcesList SpineSprites; + public UndertaleSimpleResourcesList Fonts; + public UndertaleSimpleResourcesList Tilesets; public UndertaleTextureGroupInfo() { - TexturePages = new List>(); - Sprites = new List>(); - SpineSprites = new List>(); - Fonts = new List>(); - Tilesets = new List>(); + TexturePages = new UndertaleSimpleResourcesList(); + Sprites = new UndertaleSimpleResourcesList(); + SpineSprites = new UndertaleSimpleResourcesList(); + Fonts = new UndertaleSimpleResourcesList(); + Tilesets = new UndertaleSimpleResourcesList(); } public void Serialize(UndertaleWriter writer) { writer.WriteUndertaleString(GroupName); - uint pointer1 = writer.Position; writer.Write(0); - uint pointer2 = writer.Position; writer.Write(0); - uint pointer3 = writer.Position; writer.Write(0); - uint pointer4 = writer.Position; writer.Write(0); - uint pointer5 = writer.Position; writer.Write(0); - SeekWritePointer(writer, pointer1); - foreach (UndertaleResourceById r in TexturePages) - { - writer.Write(r.Serialize(writer)); - } - - SeekWritePointer(writer, pointer2); - foreach (UndertaleResourceById r in Sprites) - { - writer.Write(r.Serialize(writer)); - } - - SeekWritePointer(writer, pointer3); - foreach (UndertaleResourceById r in SpineSprites) - { - writer.Write(r.Serialize(writer)); - } - - SeekWritePointer(writer, pointer4); - foreach (UndertaleResourceById r in Fonts) - { - writer.Write(r.Serialize(writer)); - } - - SeekWritePointer(writer, pointer5); - foreach (UndertaleResourceById r in Tilesets) - { - writer.Write(r.Serialize(writer)); - } - } - - private void SeekWritePointer(UndertaleWriter writer, uint pointer) - { - uint returnTo = writer.Position; - writer.Position = pointer; - writer.Write(returnTo); - writer.Position = returnTo; + writer.WriteUndertaleObjectPointer(TexturePages); + writer.WriteUndertaleObjectPointer(Sprites); + writer.WriteUndertaleObjectPointer(SpineSprites); + writer.WriteUndertaleObjectPointer(Fonts); + writer.WriteUndertaleObjectPointer(Tilesets); + + writer.WriteUndertaleObject(TexturePages); + writer.WriteUndertaleObject(Sprites); + writer.WriteUndertaleObject(SpineSprites); + writer.WriteUndertaleObject(Fonts); + writer.WriteUndertaleObject(Tilesets); } public void Unserialize(UndertaleReader reader) { GroupName = reader.ReadUndertaleString(); - uint pointer1 = reader.ReadUInt32(); - uint pointer2 = reader.ReadUInt32(); - uint pointer3 = reader.ReadUInt32(); - uint pointer4 = reader.ReadUInt32(); - uint pointer5 = reader.ReadUInt32(); - - EnsurePointer(reader, pointer1); - { - int count = reader.ReadInt32(); - for (int i = 0; i < count; i++) - { - UndertaleResourceById r = new UndertaleResourceById("TXTR"); - r.Unserialize(reader, reader.ReadInt32()); - TexturePages.Add(r); - } - } - - EnsurePointer(reader, pointer2); - { - int count = reader.ReadInt32(); - for (int i = 0; i < count; i++) - { - UndertaleResourceById r = new UndertaleResourceById("SPRT"); - r.Unserialize(reader, reader.ReadInt32()); - Sprites.Add(r); - } - } - - EnsurePointer(reader, pointer3); - { - int count = reader.ReadInt32(); - for (int i = 0; i < count; i++) - { - UndertaleResourceById r = new UndertaleResourceById("SPRT"); - r.Unserialize(reader, reader.ReadInt32()); - SpineSprites.Add(r); - } - } - - EnsurePointer(reader, pointer4); - { - int count = reader.ReadInt32(); - for (int i = 0; i < count; i++) - { - UndertaleResourceById r = new UndertaleResourceById("FONT"); - r.Unserialize(reader, reader.ReadInt32()); - Fonts.Add(r); - } - } - EnsurePointer(reader, pointer5); + // Read the pointers + TexturePages = reader.ReadUndertaleObjectPointer>(); + Sprites = reader.ReadUndertaleObjectPointer>(); + SpineSprites = reader.ReadUndertaleObjectPointer>(); + Fonts = reader.ReadUndertaleObjectPointer>(); + Tilesets = reader.ReadUndertaleObjectPointer>(); + + // Read the objects, throwing an error if the pointers are invalid + if (reader.ReadUndertaleObject>() != TexturePages || + reader.ReadUndertaleObject>() != Sprites || + reader.ReadUndertaleObject>() != SpineSprites || + reader.ReadUndertaleObject>() != Fonts || + reader.ReadUndertaleObject>() != Tilesets) { - int count = reader.ReadInt32(); - for (int i = 0; i < count; i++) - { - UndertaleResourceById r = new UndertaleResourceById("BGND"); - r.Unserialize(reader, reader.ReadInt32()); - Tilesets.Add(r); - } + throw new UndertaleSerializationException("Invalid pointer to SimpleResourcesList"); } } - - private void EnsurePointer(UndertaleReader reader, uint pointer) - { - Debug.Assert(reader.Position == pointer, "Invalid pointer in TGIN entry"); - } } } diff --git a/UndertaleModLib/UndertaleChunks.cs b/UndertaleModLib/UndertaleChunks.cs index 5499aaf6d..71f2c6929 100644 --- a/UndertaleModLib/UndertaleChunks.cs +++ b/UndertaleModLib/UndertaleChunks.cs @@ -34,6 +34,7 @@ public class UndertaleChunkFORM : UndertaleChunk public UndertaleChunkOBJT OBJT => Chunks.GetValueOrDefault("OBJT") as UndertaleChunkOBJT; public UndertaleChunkROOM ROOM => Chunks.GetValueOrDefault("ROOM") as UndertaleChunkROOM; public UndertaleChunkDAFL DAFL => Chunks.GetValueOrDefault("DAFL") as UndertaleChunkDAFL; + public UndertaleChunkEMBI EMBI => Chunks.GetValueOrDefault("EMBI") as UndertaleChunkEMBI; public UndertaleChunkTPAG TPAG => Chunks.GetValueOrDefault("TPAG") as UndertaleChunkTPAG; public UndertaleChunkTGIN TGIN => Chunks.GetValueOrDefault("TGIN") as UndertaleChunkTGIN; public UndertaleChunkCODE CODE => Chunks.GetValueOrDefault("CODE") as UndertaleChunkCODE; @@ -432,7 +433,7 @@ internal override void UnserializeChunk(UndertaleReader reader) } // GMS2.2.1+ only - public class UndertaleChunkTGIN : UndertaleSimpleListChunk + public class UndertaleChunkTGIN : UndertaleListChunk { public override string Name => "TGIN"; diff --git a/UndertaleModLib/UndertaleData.cs b/UndertaleModLib/UndertaleData.cs index 0d4aac548..62f43dcd6 100644 --- a/UndertaleModLib/UndertaleData.cs +++ b/UndertaleModLib/UndertaleData.cs @@ -39,7 +39,9 @@ public class UndertaleData public IList CodeLocals => FORM.FUNC?.CodeLocals; public IList Strings => FORM.STRG?.List; public IList EmbeddedTextures => FORM.TXTR?.List; + public IList EmbeddedImages => FORM.EMBI?.List; public IList EmbeddedAudio => FORM.AUDO?.List; + public IList TextureGroupInfo => FORM.TGIN?.List; public bool UnsupportedBytecodeVersion = false; @@ -78,7 +80,9 @@ public static UndertaleData CreateNew() data.FORM.Chunks["OBJT"] = new UndertaleChunkOBJT(); data.FORM.Chunks["ROOM"] = new UndertaleChunkROOM(); data.FORM.Chunks["DAFL"] = new UndertaleChunkDAFL(); + data.FORM.Chunks["EMBI"] = new UndertaleChunkEMBI(); data.FORM.Chunks["TPAG"] = new UndertaleChunkTPAG(); + data.FORM.Chunks["TGIN"] = new UndertaleChunkTGIN(); // Maybe a studio 2 option may come in handy data.FORM.Chunks["CODE"] = new UndertaleChunkCODE(); data.FORM.Chunks["VARI"] = new UndertaleChunkVARI(); data.FORM.Chunks["FUNC"] = new UndertaleChunkFUNC(); diff --git a/UndertaleModLib/UndertaleLists.cs b/UndertaleModLib/UndertaleLists.cs index ed396ad44..684e9f263 100644 --- a/UndertaleModLib/UndertaleLists.cs +++ b/UndertaleModLib/UndertaleLists.cs @@ -2,6 +2,7 @@ using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Collections.Specialized; using System.IO; using System.Linq; using System.Text; @@ -39,7 +40,54 @@ public void Unserialize(UndertaleReader reader) } catch (UndertaleSerializationException e) { - throw new UndertaleSerializationException(e.Message + "\nwhile reading item " + (i+1) + " of " + count + " in a list of " + typeof(T).FullName, e); + throw new UndertaleSerializationException(e.Message + "\nwhile reading item " + (i + 1) + " of " + count + " in a list of " + typeof(T).FullName, e); + } + } + } + } + + public class UndertaleSimpleListShort : ObservableCollection, UndertaleObject where T : UndertaleObject, new() + { + public UndertaleSimpleListShort() + { + base.CollectionChanged += EnsureShortCount; + } + + private void EnsureShortCount(object sender, NotifyCollectionChangedEventArgs e) + { + if (e.NewItems != null && e.NewItems.Count > Int16.MaxValue) + throw new InvalidOperationException("Count of short SimpleList exceeds maximum number allowed."); + } + + public void Serialize(UndertaleWriter writer) + { + writer.Write((ushort)Count); + for (int i = 0; i < Count; i++) + { + try + { + writer.WriteUndertaleObject(this[i]); + } + catch (UndertaleSerializationException e) + { + throw new UndertaleSerializationException(e.Message + "\nwhile writing item " + (i + 1) + " of " + Count + " in a list of " + typeof(T).FullName, e); + } + } + } + + public void Unserialize(UndertaleReader reader) + { + ushort count = reader.ReadUInt16(); + Clear(); + for (ushort i = 0; i < count; i++) + { + try + { + Add(reader.ReadUndertaleObject()); + } + catch (UndertaleSerializationException e) + { + throw new UndertaleSerializationException(e.Message + "\nwhile reading item " + (i + 1) + " of " + count + " in a list of " + typeof(T).FullName, e); } } } diff --git a/UndertaleModTool/Editors/UndertaleEmbeddedTextureEditor.xaml b/UndertaleModTool/Editors/UndertaleEmbeddedTextureEditor.xaml index 13d7e21df..3d3469964 100644 --- a/UndertaleModTool/Editors/UndertaleEmbeddedTextureEditor.xaml +++ b/UndertaleModTool/Editors/UndertaleEmbeddedTextureEditor.xaml @@ -18,11 +18,11 @@ - Unknown (added in GMS2) - + Scaled + - Unknown - + Generated mips + diff --git a/UndertaleModTool/Editors/UndertaleFontEditor.xaml b/UndertaleModTool/Editors/UndertaleFontEditor.xaml index d89fe5591..37c0720af 100644 --- a/UndertaleModTool/Editors/UndertaleFontEditor.xaml +++ b/UndertaleModTool/Editors/UndertaleFontEditor.xaml @@ -161,7 +161,7 @@ - Note that the glyphs need to be specified in ascending order. Press the button below to sort them appropariately. + Note that the glyphs need to be specified in ascending order. Press the button below to sort them appropriately. diff --git a/UndertaleModTool/MainWindow.xaml.cs b/UndertaleModTool/MainWindow.xaml.cs index b3083d6e9..1943f8998 100644 --- a/UndertaleModTool/MainWindow.xaml.cs +++ b/UndertaleModTool/MainWindow.xaml.cs @@ -237,7 +237,7 @@ private async Task LoadFile(string filename) { if (data.UnsupportedBytecodeVersion) { - MessageBox.Show("Only bytecode versions 15 and 16 are supported for now, you are trying to load " + data.GeneralInfo.BytecodeVersion + ". A lot of code is disabled and will likely break something. Saving/exporting is disabled.", "Unsupported bytecode version", MessageBoxButton.OK, MessageBoxImage.Warning); + MessageBox.Show("Only bytecode versions 15, 16, and (partially) 17 are supported for now, you are trying to load " + data.GeneralInfo.BytecodeVersion + ". A lot of code is disabled and will likely break something. Saving/exporting is disabled.", "Unsupported bytecode version", MessageBoxButton.OK, MessageBoxImage.Warning); CanSave = false; } else if (hadWarnings) @@ -253,6 +253,10 @@ private async Task LoadFile(string filename) { MessageBox.Show("Game Maker: Studio 2 game loaded! I just hacked this together quickly for the Nintendo Switch release of Undertale. Most things work, but some editors don't display all the data.", "GMS2 game loaded", MessageBoxButton.OK, MessageBoxImage.Warning); } + if (data.GeneralInfo?.BytecodeVersion == 17) + { + MessageBox.Show("Bytecode version 17 has been loaded. There may be some problems remaining, as thorough research into the changes are yet to be done.", "Bytecode version 17", MessageBoxButton.OK, MessageBoxImage.Warning); + } if (System.IO.Path.GetDirectoryName(FilePath) != System.IO.Path.GetDirectoryName(filename)) CloseChildFiles(); this.Data = data; From 427b1a18a9b997867ed1fb86f98daf6f5049a34c Mon Sep 17 00:00:00 2001 From: colinator27 Date: Sat, 17 Nov 2018 09:26:07 -0500 Subject: [PATCH 3/9] Change error messages to be about version numbers, because that is likely what the values actually are --- UndertaleModLib/UndertaleChunks.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/UndertaleModLib/UndertaleChunks.cs b/UndertaleModLib/UndertaleChunks.cs index 71f2c6929..0dfa7d93b 100644 --- a/UndertaleModLib/UndertaleChunks.cs +++ b/UndertaleModLib/UndertaleChunks.cs @@ -427,7 +427,7 @@ internal override void UnserializeChunk(UndertaleReader reader) if (reader.undertaleData.GeneralInfo.Major < 2) throw new InvalidOperationException(); if (reader.ReadUInt32() != 1) - throw new Exception("Should be hardcoded 1"); + throw new Exception("Expected EMBI version 1"); base.UnserializeChunk(reader); } } @@ -450,7 +450,7 @@ internal override void UnserializeChunk(UndertaleReader reader) if (reader.undertaleData.GeneralInfo.Major < 2) throw new InvalidOperationException(); if (reader.ReadUInt32() != 1) - throw new Exception("Should be hardcoded 1"); + throw new Exception("Expected TGIN version 1"); base.UnserializeChunk(reader); } } From 544eec3ccff24b55d0d2309fdc1f725d23ac72ab Mon Sep 17 00:00:00 2001 From: colinator27 Date: Sat, 17 Nov 2018 11:17:11 -0500 Subject: [PATCH 4/9] Fixed one line that broke everything --- UndertaleModLib/Models/UndertaleFont.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UndertaleModLib/Models/UndertaleFont.cs b/UndertaleModLib/Models/UndertaleFont.cs index a06498575..2bea96bf0 100644 --- a/UndertaleModLib/Models/UndertaleFont.cs +++ b/UndertaleModLib/Models/UndertaleFont.cs @@ -131,7 +131,7 @@ public void Serialize(UndertaleWriter writer) writer.WriteUndertaleObjectPointer(Texture); writer.Write(ScaleX); writer.Write(ScaleY); - if (writer.undertaleData.GeneralInfo?.Major >= 2 || (writer.undertaleData.GeneralInfo?.Major == 1 && writer.undertaleData.GeneralInfo?.Build == 9999)) + if (writer.undertaleData.GeneralInfo?.Major >= 2 && writer.undertaleData.GeneralInfo?.BytecodeVersion >= 17) writer.Write(AscenderOffset); writer.WriteUndertaleObject(Glyphs); } From b63a470d3da49864db8fcbd463bb6fcc861af076 Mon Sep 17 00:00:00 2001 From: colinator27 Date: Sat, 17 Nov 2018 13:49:03 -0500 Subject: [PATCH 5/9] Fix pre-padded object unserialization in pointer lists --- UndertaleModLib/UndertaleLists.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/UndertaleModLib/UndertaleLists.cs b/UndertaleModLib/UndertaleLists.cs index 684e9f263..882f836c3 100644 --- a/UndertaleModLib/UndertaleLists.cs +++ b/UndertaleModLib/UndertaleLists.cs @@ -160,6 +160,7 @@ public void Unserialize(UndertaleReader reader) { try { + (this[(int)i] as PrePaddedObject)?.UnserializePrePadding(reader); T obj = reader.ReadUndertaleObject(); if (!obj.Equals(this[(int)i])) throw new UndertaleSerializationException("Something got misaligned..."); From 1f28c8e45a2c0197eb7d3f952b3019dc801d3085 Mon Sep 17 00:00:00 2001 From: krzys_h Date: Sat, 17 Nov 2018 17:23:34 -0500 Subject: [PATCH 6/9] Get rid of unnecessary GMS2 check #1 Co-Authored-By: colinator27 <17358554+colinator27@users.noreply.github.com> --- UndertaleModLib/Models/UndertaleFont.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UndertaleModLib/Models/UndertaleFont.cs b/UndertaleModLib/Models/UndertaleFont.cs index 2bea96bf0..a3221cf9c 100644 --- a/UndertaleModLib/Models/UndertaleFont.cs +++ b/UndertaleModLib/Models/UndertaleFont.cs @@ -131,7 +131,7 @@ public void Serialize(UndertaleWriter writer) writer.WriteUndertaleObjectPointer(Texture); writer.Write(ScaleX); writer.Write(ScaleY); - if (writer.undertaleData.GeneralInfo?.Major >= 2 && writer.undertaleData.GeneralInfo?.BytecodeVersion >= 17) + if (writer.undertaleData.GeneralInfo?.BytecodeVersion >= 17) writer.Write(AscenderOffset); writer.WriteUndertaleObject(Glyphs); } From 481bcb48d32d4503ae3f65b0e3f5718dbc9eb766 Mon Sep 17 00:00:00 2001 From: krzys_h Date: Sat, 17 Nov 2018 17:24:06 -0500 Subject: [PATCH 7/9] Get rid of unnecessary GMS2 check #2 Co-Authored-By: colinator27 <17358554+colinator27@users.noreply.github.com> --- UndertaleModLib/Models/UndertaleFont.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UndertaleModLib/Models/UndertaleFont.cs b/UndertaleModLib/Models/UndertaleFont.cs index a3221cf9c..f80a245f2 100644 --- a/UndertaleModLib/Models/UndertaleFont.cs +++ b/UndertaleModLib/Models/UndertaleFont.cs @@ -150,7 +150,7 @@ public void Unserialize(UndertaleReader reader) Texture = reader.ReadUndertaleObjectPointer(); ScaleX = reader.ReadSingle(); ScaleY = reader.ReadSingle(); - if (reader.undertaleData.GeneralInfo?.Major >= 2 && reader.undertaleData.GeneralInfo?.BytecodeVersion >= 17) + if (reader.undertaleData.GeneralInfo?.BytecodeVersion >= 17) AscenderOffset = reader.ReadInt32(); Glyphs = reader.ReadUndertaleObject>(); } From 86ba3225832123cb19571f8100ac0e2cc29af85a Mon Sep 17 00:00:00 2001 From: krzys_h Date: Sat, 17 Nov 2018 17:58:03 -0500 Subject: [PATCH 8/9] Remove unnecessary chunk from default list #1 Sorry for making these several commits, but I'm far too lazy to do it the proper way. Co-Authored-By: colinator27 <17358554+colinator27@users.noreply.github.com> --- UndertaleModLib/UndertaleData.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/UndertaleModLib/UndertaleData.cs b/UndertaleModLib/UndertaleData.cs index 62f43dcd6..3bbe10346 100644 --- a/UndertaleModLib/UndertaleData.cs +++ b/UndertaleModLib/UndertaleData.cs @@ -82,7 +82,6 @@ public static UndertaleData CreateNew() data.FORM.Chunks["DAFL"] = new UndertaleChunkDAFL(); data.FORM.Chunks["EMBI"] = new UndertaleChunkEMBI(); data.FORM.Chunks["TPAG"] = new UndertaleChunkTPAG(); - data.FORM.Chunks["TGIN"] = new UndertaleChunkTGIN(); // Maybe a studio 2 option may come in handy data.FORM.Chunks["CODE"] = new UndertaleChunkCODE(); data.FORM.Chunks["VARI"] = new UndertaleChunkVARI(); data.FORM.Chunks["FUNC"] = new UndertaleChunkFUNC(); From 06530286274bfca913d9880bf5df180c14e36710 Mon Sep 17 00:00:00 2001 From: krzys_h Date: Sat, 17 Nov 2018 17:58:14 -0500 Subject: [PATCH 9/9] Remove unnecessary chunk from default list #2 Co-Authored-By: colinator27 <17358554+colinator27@users.noreply.github.com> --- UndertaleModLib/UndertaleData.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/UndertaleModLib/UndertaleData.cs b/UndertaleModLib/UndertaleData.cs index 3bbe10346..8fbfaa186 100644 --- a/UndertaleModLib/UndertaleData.cs +++ b/UndertaleModLib/UndertaleData.cs @@ -80,7 +80,6 @@ public static UndertaleData CreateNew() data.FORM.Chunks["OBJT"] = new UndertaleChunkOBJT(); data.FORM.Chunks["ROOM"] = new UndertaleChunkROOM(); data.FORM.Chunks["DAFL"] = new UndertaleChunkDAFL(); - data.FORM.Chunks["EMBI"] = new UndertaleChunkEMBI(); data.FORM.Chunks["TPAG"] = new UndertaleChunkTPAG(); data.FORM.Chunks["CODE"] = new UndertaleChunkCODE(); data.FORM.Chunks["VARI"] = new UndertaleChunkVARI();