Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add (most) save/load support for GameMaker 2024.6 #1827

Merged
merged 2 commits into from
Jul 14, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
133 changes: 133 additions & 0 deletions UndertaleModLib/Models/UndertaleRoom.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2021,6 +2021,7 @@ public class LayerAssetsData : LayerData
public UndertalePointerList<SequenceInstance> Sequences { get; set; }
public UndertalePointerList<SpriteInstance> NineSlices { get; set; } // Removed in 2.3.2, before never used
public UndertalePointerList<ParticleSystemInstance> ParticleSystems { get; set; }
public UndertalePointerList<TextItemInstance> TextItems { get; set; }

/// <inheritdoc />
public void Serialize(UndertaleWriter writer)
Expand All @@ -2034,6 +2035,8 @@ public void Serialize(UndertaleWriter writer)
writer.WriteUndertaleObjectPointer(NineSlices);
if (writer.undertaleData.IsNonLTSVersionAtLeast(2023, 2))
writer.WriteUndertaleObjectPointer(ParticleSystems);
if (writer.undertaleData.IsVersionAtLeast(2024, 6))
writer.WriteUndertaleObjectPointer(TextItems);
}
writer.WriteUndertaleObject(LegacyTiles);
writer.WriteUndertaleObject(Sprites);
Expand All @@ -2044,12 +2047,18 @@ public void Serialize(UndertaleWriter writer)
writer.WriteUndertaleObject(NineSlices);
if (writer.undertaleData.IsNonLTSVersionAtLeast(2023, 2))
writer.WriteUndertaleObject(ParticleSystems);
if (writer.undertaleData.IsVersionAtLeast(2024, 6))
writer.WriteUndertaleObject(TextItems);
}
}

/// <inheritdoc />
public void Unserialize(UndertaleReader reader)
{
// Track first pointer target to detect additional data
long firstPointerTarget = reader.ReadUInt32();
reader.Position -= 4;

LegacyTiles = reader.ReadUndertaleObjectPointer<UndertalePointerList<Tile>>();
Sprites = reader.ReadUndertaleObjectPointer<UndertalePointerList<SpriteInstance>>();
if (reader.undertaleData.IsVersionAtLeast(2, 3))
Expand All @@ -2059,6 +2068,10 @@ public void Unserialize(UndertaleReader reader)
NineSlices = reader.ReadUndertaleObjectPointer<UndertalePointerList<SpriteInstance>>();
if (reader.undertaleData.IsNonLTSVersionAtLeast(2023, 2))
ParticleSystems = reader.ReadUndertaleObjectPointer<UndertalePointerList<ParticleSystemInstance>>();
if (firstPointerTarget > reader.AbsPosition && !reader.undertaleData.IsVersionAtLeast(2024, 6))
reader.undertaleData.SetGMS2Version(2024, 6); // there's more data before legacy tiles, so must be 2024.6+
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
reader.undertaleData.SetGMS2Version(2024, 6); // there's more data before legacy tiles, so must be 2024.6+
reader.undertaleData.SetGMS2Version(2024, 6); // There's more data before legacy tiles, so must be 2024.6+

if (reader.undertaleData.IsVersionAtLeast(2024, 6))
TextItems = reader.ReadUndertaleObjectPointer<UndertalePointerList<TextItemInstance>>();
}
reader.ReadUndertaleObject(LegacyTiles);
reader.ReadUndertaleObject(Sprites);
Expand All @@ -2069,6 +2082,8 @@ public void Unserialize(UndertaleReader reader)
reader.ReadUndertaleObject(NineSlices);
if (reader.undertaleData.IsNonLTSVersionAtLeast(2023, 2))
reader.ReadUndertaleObject(ParticleSystems);
if (reader.undertaleData.IsVersionAtLeast(2024, 6))
reader.ReadUndertaleObject(TextItems);
}
}

Expand All @@ -2082,13 +2097,18 @@ public static uint UnserializeChildObjectCount(UndertaleReader reader)
uint sequencesPtr = 0;
uint nineSlicesPtr = 0;
uint partSystemsPtr = 0;
uint textItemsPtr = 0;
if (reader.undertaleData.IsVersionAtLeast(2, 3))
{
sequencesPtr = reader.ReadUInt32();
if (!reader.undertaleData.IsVersionAtLeast(2, 3, 2))
nineSlicesPtr = reader.ReadUInt32();
if (reader.undertaleData.IsNonLTSVersionAtLeast(2023, 2))
partSystemsPtr = reader.ReadUInt32();
if (legacyTilesPtr > reader.AbsPosition && !reader.undertaleData.IsVersionAtLeast(2024, 6))
reader.undertaleData.SetGMS2Version(2024, 6); // there's more data before legacy tiles, so must be 2024.6+
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
reader.undertaleData.SetGMS2Version(2024, 6); // there's more data before legacy tiles, so must be 2024.6+
reader.undertaleData.SetGMS2Version(2024, 6); // There's more data before legacy tiles, so must be 2024.6+

if (reader.undertaleData.IsVersionAtLeast(2024, 6))
textItemsPtr = reader.ReadUInt32();
}

reader.AbsPosition = legacyTilesPtr;
Expand All @@ -2109,6 +2129,11 @@ public static uint UnserializeChildObjectCount(UndertaleReader reader)
reader.AbsPosition = partSystemsPtr;
count += 1 + UndertalePointerList<ParticleSystemInstance>.UnserializeChildObjectCount(reader);
}
if (reader.undertaleData.IsVersionAtLeast(2024, 6))
{
reader.AbsPosition = textItemsPtr;
count += 1 + UndertalePointerList<TextItemInstance>.UnserializeChildObjectCount(reader);
}
}

return count;
Expand Down Expand Up @@ -2545,6 +2570,114 @@ public void Dispose()
Name = null;
}
}

public class TextItemInstance : UndertaleObject, INotifyPropertyChanged, IStaticChildObjCount, IStaticChildObjectsSize, IDisposable
{
/// <inheritdoc cref="IStaticChildObjCount.ChildObjectCount" />
public static readonly uint ChildObjectCount = 1;

/// <inheritdoc cref="IStaticChildObjectsSize.ChildObjectsSize" />
public static readonly uint ChildObjectsSize = 68;

public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}

private UndertaleResourceById<UndertaleFont, UndertaleChunkFONT> _font = new();

public UndertaleString Name { get; set; }
public int X { get; set; }
public int Y { get; set; }
public UndertaleFont Font
{
get => _font.Resource;
set
{
_font.Resource = value;
OnPropertyChanged();
}
}
public float ScaleX { get; set; }
public float ScaleY { get; set; }
public float Rotation { get; set; }
public uint Color { get; set; }
public float OriginX { get; set; }
public float OriginY { get; set; }
public UndertaleString Text { get; set; }
public int Alignment { get; set; }
public float CharSpacing { get; set; }
public float LineSpacing { get; set; }
public float FrameWidth { get; set; }
public float FrameHeight { get; set; }
public bool Wrap { get; set; }
Comment on lines +2597 to +2621
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing docstrings. Please add at least a TODO if you're not implementing them in this PR (altho would be prferred if you added them)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ended up just adding a TODO because I don't know all the possible values here yet.


/// <inheritdoc />
public void Serialize(UndertaleWriter writer)
{
writer.WriteUndertaleString(Name);
writer.Write(X);
writer.Write(Y);
writer.WriteUndertaleObject(_font);
writer.Write(ScaleX);
writer.Write(ScaleY);
writer.Write(Rotation);
writer.Write(Color);
writer.Write(OriginX);
writer.Write(OriginY);
writer.WriteUndertaleString(Text);
writer.Write(Alignment);
writer.Write(CharSpacing);
writer.Write(LineSpacing);
writer.Write(FrameWidth);
writer.Write(FrameHeight);
writer.Write(Wrap);
}

/// <inheritdoc />
public void Unserialize(UndertaleReader reader)
{
Name = reader.ReadUndertaleString();
X = reader.ReadInt32();
Y = reader.ReadInt32();
_font = reader.ReadUndertaleObject<UndertaleResourceById<UndertaleFont, UndertaleChunkFONT>>();
ScaleX = reader.ReadSingle();
ScaleY = reader.ReadSingle();
Rotation = reader.ReadSingle();
Color = reader.ReadUInt32();
OriginX = reader.ReadSingle();
OriginY = reader.ReadSingle();
Text = reader.ReadUndertaleString();
Alignment = reader.ReadInt32();
CharSpacing = reader.ReadSingle();
LineSpacing = reader.ReadSingle();
FrameWidth = reader.ReadSingle();
FrameHeight = reader.ReadSingle();
Wrap = reader.ReadBoolean();
}

public static UndertaleString GenerateRandomName(UndertaleData data)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Docstring

{
return data.Strings.MakeString("textitem_" + ((uint)Random.Shared.Next(-Int32.MaxValue, Int32.MaxValue)).ToString("X8"));
}

/// <inheritdoc />
public override string ToString()
{
return "Text item " + Name?.Content + " with text \"" + (Text?.Content ?? "?") + "\"";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use string interpolation please

}

/// <inheritdoc/>
public void Dispose()
{
GC.SuppressFinalize(this);

_font.Dispose();
Name = null;
Text = null;
}
}
}

public enum AnimationSpeedType : uint
Expand Down
12 changes: 12 additions & 0 deletions UndertaleModLib/Models/UndertaleSound.cs
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,12 @@ public enum AudioEntryFlags : uint
/// </summary>
public int GroupID { get => _audioGroup.CachedId; set { _audioGroup.CachedId = value; OnPropertyChanged(); } }

/// <summary>
/// The precomputed length of the sound's audio data.
/// </summary>
/// <remarks>Introduced in GameMaker 2024.6.</remarks>
public float AudioLength { get; set; }

/// <inheritdoc />
public event PropertyChangedEventHandler PropertyChanged;

Expand Down Expand Up @@ -174,6 +180,9 @@ public void Serialize(UndertaleWriter writer)
writer.WriteUndertaleObject(_audioFile);
else
writer.Write(_audioFile.CachedId);

if (writer.undertaleData.IsVersionAtLeast(2024, 6))
writer.Write(AudioLength);
}

/// <inheritdoc />
Expand Down Expand Up @@ -207,6 +216,9 @@ public void Unserialize(UndertaleReader reader)
{
_audioFile.CachedId = reader.ReadInt32();
}

if (reader.undertaleData.IsVersionAtLeast(2024, 6))
AudioLength = reader.ReadSingle();
}

/// <inheritdoc cref="UndertaleObject.UnserializeChildObjectCount(UndertaleReader)"/>
Expand Down
60 changes: 51 additions & 9 deletions UndertaleModLib/Models/UndertaleSprite.cs
Original file line number Diff line number Diff line change
Expand Up @@ -697,8 +697,12 @@ public static uint UnserializeChildObjectCount(UndertaleReader reader)
reader.Position += 4; // "Name"
uint width = reader.ReadUInt32();
uint height = reader.ReadUInt32();
int marginLeft = reader.ReadInt32();
int marginRight = reader.ReadInt32();
int marginBottom = reader.ReadInt32();
int marginTop = reader.ReadInt32();
Comment on lines +700 to +703
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Were these fields always there? why did we never read them before?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are required in 2024.6 and above to determine the size of a sprite's collision mask data, which now depends on the size of its bounding box (which is what these values are).


reader.Position += 44;
reader.Position += 28;

if (reader.ReadInt32() == -1)
{
Expand Down Expand Up @@ -727,7 +731,7 @@ public static uint UnserializeChildObjectCount(UndertaleReader reader)
{
case SpriteType.Normal:
count += 1 + UndertaleSimpleList<TextureEntry>.UnserializeChildObjectCount(reader);
SkipMaskData(reader, width, height);
SkipMaskData(reader, width, height, marginRight, marginLeft, marginBottom, marginTop);
break;

case SpriteType.SWF:
Expand Down Expand Up @@ -796,37 +800,75 @@ public static uint UnserializeChildObjectCount(UndertaleReader reader)
{
reader.Position -= 4;
count += 1 + UndertaleSimpleList<TextureEntry>.UnserializeChildObjectCount(reader);
SkipMaskData(reader, width, height);
SkipMaskData(reader, width, height, marginRight, marginLeft, marginBottom, marginTop);
}

return count;
}

public (uint Width, uint Height) CalculateMaskDimensions(UndertaleData data)
{
if (data.IsVersionAtLeast(2024, 6))
{
return CalculateBboxMaskDimensions(MarginRight, MarginLeft, MarginBottom, MarginTop);
}
return CalculateFullMaskDimensions(Width, Height);
}

public static (uint Width, uint Height) CalculateBboxMaskDimensions(int marginRight, int marginLeft, int marginBottom, int marginTop)
{
return ((uint)(marginRight - marginLeft + 1), (uint)(marginBottom - marginTop + 1));
}

public static (uint Width, uint Height) CalculateFullMaskDimensions(uint width, uint height)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

docstrings please :)

{
return (width, height);
}

private void ReadMaskData(UndertaleReader reader)
{
// Initialize mask list
uint maskCount = reader.ReadUInt32();
uint len = (Width + 7) / 8 * Height;
List<MaskEntry> newMasks = new((int)maskCount);

// Read in mask data
(uint width, uint height) = CalculateMaskDimensions(reader.undertaleData);
uint len = (width + 7) / 8 * height;
uint total = 0;
for (uint i = 0; i < maskCount; i++)
{
newMasks.Add(new MaskEntry(reader.ReadBytes((int)len)));
total += len;
}

CollisionMasks = new(newMasks);

while (total % 4 != 0)
while ((total % 4) != 0)
{
if (reader.ReadByte() != 0)
{
throw new IOException("Mask padding");
}
total++;
}
Util.DebugUtil.Assert(total == CalculateMaskDataSize(Width, Height, maskCount));
if (total != CalculateMaskDataSize(width, height, maskCount))
{
throw new IOException("Mask data size incorrect");
}

// Assign masks to sprite
CollisionMasks = new(newMasks);
}
private static void SkipMaskData(UndertaleReader reader, uint width, uint height)

private static void SkipMaskData(UndertaleReader reader, uint width, uint height, int marginRight, int marginLeft, int marginBottom, int marginTop)
{
uint maskCount = reader.ReadUInt32();
if (reader.undertaleData.IsVersionAtLeast(2024, 6))
{
(width, height) = CalculateBboxMaskDimensions(marginRight, marginLeft, marginBottom, marginTop);
}
else
{
(width, height) = CalculateFullMaskDimensions(width, height);
}
uint len = (width + 7) / 8 * height;

uint total = 0;
Expand Down
Loading
Loading