-
Notifications
You must be signed in to change notification settings - Fork 239
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
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -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) | ||||||
|
@@ -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); | ||||||
|
@@ -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)) | ||||||
|
@@ -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+ | ||||||
if (reader.undertaleData.IsVersionAtLeast(2024, 6)) | ||||||
TextItems = reader.ReadUndertaleObjectPointer<UndertalePointerList<TextItemInstance>>(); | ||||||
} | ||||||
reader.ReadUndertaleObject(LegacyTiles); | ||||||
reader.ReadUndertaleObject(Sprites); | ||||||
|
@@ -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); | ||||||
} | ||||||
} | ||||||
|
||||||
|
@@ -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+ | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
if (reader.undertaleData.IsVersionAtLeast(2024, 6)) | ||||||
textItemsPtr = reader.ReadUInt32(); | ||||||
} | ||||||
|
||||||
reader.AbsPosition = legacyTilesPtr; | ||||||
|
@@ -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; | ||||||
|
@@ -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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 ?? "?") + "\""; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Were these fields always there? why did we never read them before? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||
{ | ||
|
@@ -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: | ||
|
@@ -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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.