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

Fix edge case with 2024.6+ masks #2034

Merged
merged 1 commit into from
Feb 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
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
60 changes: 38 additions & 22 deletions UndertaleModLib/Models/UndertaleSprite.cs
Original file line number Diff line number Diff line change
Expand Up @@ -277,11 +277,27 @@ public void Dispose()
V3NineSlice = null;
}

public MaskEntry NewMaskEntry()
/// <summary>
/// Creates a new mask entry for this sprite.
/// </summary>
/// <param name="data">Data that this sprite is part of, for checking the GameMaker version.</param>
/// <returns>The new mask entry.</returns>
public MaskEntry NewMaskEntry(UndertaleData data = null)
{
uint len = (Width + 7) / 8 * Height;
MaskEntry newEntry = new MaskEntry(new byte[len], Width, Height);
return newEntry;
int width, height;
if (data is not null)
{
// Support for 2024.6+ (modern code path)
(width, height) = CalculateMaskDimensions(data);
}
else
{
// Legacy code path (for scripts that haven't been updated to support 2024.6+ yet)
(width, height) = ((int)Width, (int)Height);
}

uint len = (uint)((width + 7) / 8 * height);
return new MaskEntry(new byte[len], width, height);
}

/// <summary>
Expand Down Expand Up @@ -350,17 +366,17 @@ public class MaskEntry : IDisposable
/// <summary>
/// Width of this sprite mask. UTMT only.
/// </summary>
public uint Width { get; set; }
public int Width { get; set; }
/// <summary>
/// Height of this sprite mask. UTMT only.
/// </summary>
public uint Height { get; set; }
public int Height { get; set; }

public MaskEntry()
{
}

public MaskEntry(byte[] data, uint width, uint height)
public MaskEntry(byte[] data, int width, int height)
{
this.Data = data;
this.Width = width;
Expand Down Expand Up @@ -525,7 +541,7 @@ private void WriteMaskData(UndertaleWriter writer)
total++;
}

(uint width, uint height) = CalculateMaskDimensions(writer.undertaleData);
(int width, int height) = CalculateMaskDimensions(writer.undertaleData);
Util.DebugUtil.Assert(total == CalculateMaskDataSize(width, height, (uint)CollisionMasks.Count), "Invalid mask data for sprite");
}

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

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

return count;
Expand All @@ -821,22 +837,22 @@ public static uint UnserializeChildObjectCount(UndertaleReader reader)
/// <summary>
/// Returns the width and height of the collision mask for this sprite, which changes depending on GameMaker version.
/// </summary>
public (uint Width, uint Height) CalculateMaskDimensions(UndertaleData data)
public (int Width, int Height) CalculateMaskDimensions(UndertaleData data)
{
if (data.IsVersionAtLeast(2024, 6))
{
return CalculateBboxMaskDimensions(MarginRight, MarginLeft, MarginBottom, MarginTop);
}
return CalculateFullMaskDimensions(Width, Height);
return CalculateFullMaskDimensions((int)Width, (int)Height);
}

/// <summary>
/// Calculates the width and height of a collision mask from the given margin/bounding box.
/// This method is used to calculate collision mask dimensions in GameMaker 2024.6 and above.
/// </summary>
public static (uint Width, uint Height) CalculateBboxMaskDimensions(int marginRight, int marginLeft, int marginBottom, int marginTop)
public static (int Width, int Height) CalculateBboxMaskDimensions(int marginRight, int marginLeft, int marginBottom, int marginTop)
{
return ((uint)(marginRight - marginLeft + 1), (uint)(marginBottom - marginTop + 1));
return (marginRight - marginLeft + 1, marginBottom - marginTop + 1);
}

/// <summary>
Expand All @@ -846,7 +862,7 @@ public static (uint Width, uint Height) CalculateBboxMaskDimensions(int marginRi
/// <remarks>
/// This simply returns the width and height supplied, but is intended for clarity in the code.
/// </remarks>
public static (uint Width, uint Height) CalculateFullMaskDimensions(uint width, uint height)
public static (int Width, int Height) CalculateFullMaskDimensions(int width, int height)
{
return (width, height);
}
Expand All @@ -858,8 +874,8 @@ private void ReadMaskData(UndertaleReader reader)
List<MaskEntry> newMasks = new((int)maskCount);

// Read in mask data
(uint width, uint height) = CalculateMaskDimensions(reader.undertaleData);
uint len = (width + 7) / 8 * height;
(int width, int height) = CalculateMaskDimensions(reader.undertaleData);
uint len = (uint)((width + 7) / 8 * height);
uint total = 0;
for (uint i = 0; i < maskCount; i++)
{
Expand All @@ -884,7 +900,7 @@ private void ReadMaskData(UndertaleReader reader)
CollisionMasks = new(newMasks);
}

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

uint total = 0;
for (uint i = 0; i < maskCount; i++)
Expand All @@ -914,10 +930,10 @@ private static void SkipMaskData(UndertaleReader reader, uint width, uint height
reader.Position += skipSize;
}

public uint CalculateMaskDataSize(uint width, uint height, uint maskcount)
private uint CalculateMaskDataSize(int width, int height, uint maskCount)
{
uint roundedWidth = (width + 7) / 8 * 8; // round to multiple of 8
uint dataBits = roundedWidth * height * maskcount;
uint roundedWidth = (uint)((width + 7) / 8 * 8); // round to multiple of 8
uint dataBits = (uint)(roundedWidth * height * maskCount);
uint dataBytes = ((dataBits + 31) / 32 * 32) / 8; // round to multiple of 4 bytes
return dataBytes;
}
Expand Down
8 changes: 4 additions & 4 deletions UndertaleModLib/UndertaleChunks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -462,8 +462,8 @@ private void CheckForGM2024_6(UndertaleReader reader)
int marginRight = reader.ReadInt32();
int marginBottom = reader.ReadInt32();
int marginTop = reader.ReadInt32();
(uint bboxWidth, uint bboxHeight) = UndertaleSprite.CalculateBboxMaskDimensions(marginRight, marginLeft, marginBottom, marginTop);
(uint normalWidth, uint normalHeight) = UndertaleSprite.CalculateFullMaskDimensions(width, height);
(int bboxWidth, int bboxHeight) = UndertaleSprite.CalculateBboxMaskDimensions(marginRight, marginLeft, marginBottom, marginTop);
(int normalWidth, int normalHeight) = UndertaleSprite.CalculateFullMaskDimensions((int)width, (int)height);
if (bboxWidth == normalWidth && bboxHeight == normalHeight)
{
// We can't determine anything from this sprite
Expand Down Expand Up @@ -506,11 +506,11 @@ private void CheckForGM2024_6(UndertaleReader reader)
// We can't determine anything from this sprite
continue;
}
uint fullLength = (normalWidth + 7) / 8 * normalHeight;
uint fullLength = (uint)((normalWidth + 7) / 8 * normalHeight);
fullLength *= maskCount;
if ((fullLength % 4) != 0)
fullLength += (4 - (fullLength % 4));
uint bboxLength = (bboxWidth + 7) / 8 * bboxHeight;
uint bboxLength = (uint)((bboxWidth + 7) / 8 * bboxHeight);
bboxLength *= maskCount;
if ((bboxLength % 4) != 0)
bboxLength += (4 - (bboxLength % 4));
Expand Down
8 changes: 4 additions & 4 deletions UndertaleModTool/Converters/MaskImageConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@ public object Convert(object[] values, Type targetType, object parameter, System
return null;
}

uint width = (uint)values[0];
uint height = (uint)values[1];
int width = (int)values[0];
int height = (int)values[1];
byte[] data = (byte[])values[2];
if (data == null || data.Length != (width + 7) / 8 * height || width == 0 || height == 0)
if (data == null || data.Length != (width + 7) / 8 * height || width <= 0 || height <= 0)
return null;
return BitmapSource.Create((int)width, (int)height, 96, 96, PixelFormats.BlackWhite, null, data, (int)((width + 7) / 8));
return BitmapSource.Create(width, height, 96, 96, PixelFormats.BlackWhite, null, data, ((width + 7) / 8));
}

public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
Expand Down
10 changes: 5 additions & 5 deletions UndertaleModTool/Editors/UndertaleSpriteEditor.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ private void ExportAll_Click(object sender, RoutedEventArgs e)

private void MaskList_AddingNewItem(object sender, AddingNewItemEventArgs e)
{
e.NewItem = (this.DataContext as UndertaleSprite).NewMaskEntry();
e.NewItem = (this.DataContext as UndertaleSprite).NewMaskEntry(mainWindow.Data);
}

private void MaskImport_Click(object sender, RoutedEventArgs e)
Expand All @@ -153,8 +153,8 @@ private void MaskImport_Click(object sender, RoutedEventArgs e)
{
try
{
(uint maskWidth, uint maskHeight) = sprite.CalculateMaskDimensions(mainWindow.Data);
target.Data = TextureWorker.ReadMaskData(dlg.FileName, (int)maskWidth, (int)maskHeight);
(int maskWidth, int maskHeight) = sprite.CalculateMaskDimensions(mainWindow.Data);
target.Data = TextureWorker.ReadMaskData(dlg.FileName, maskWidth, maskHeight);
target.Width = maskWidth;
target.Height = maskHeight;
}
Expand All @@ -179,8 +179,8 @@ private void MaskExport_Click(object sender, RoutedEventArgs e)
{
try
{
(uint maskWidth, uint maskHeight) = sprite.CalculateMaskDimensions(mainWindow.Data);
TextureWorker.ExportCollisionMaskPNG(target, dlg.FileName, (int)maskWidth, (int)maskHeight);
(int maskWidth, int maskHeight) = sprite.CalculateMaskDimensions(mainWindow.Data);
TextureWorker.ExportCollisionMaskPNG(target, dlg.FileName, maskWidth, maskHeight);
}
catch (Exception ex)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,9 @@ foreach (Atlas atlas in packer.Atlasses)
for (int i = 0; i < frame; i++)
newSprite.Textures.Add(null);
}

// FIXME: this needs support for 2024.6+ collision masks, which only use bounding box
// (should use newSprite.CalculateMaskDimensions(Data) as well as newSprite.NewMaskEntry(Data))
newSprite.CollisionMasks.Add(newSprite.NewMaskEntry());

int width = ((n.Bounds.Width + 7) / 8) * 8;
Expand Down
6 changes: 3 additions & 3 deletions UndertaleModTool/Scripts/Resource Repackers/ImportMasks.csx
Original file line number Diff line number Diff line change
Expand Up @@ -101,13 +101,13 @@ await Task.Run(() =>
int collision_mask_count = sprite.CollisionMasks.Count;
while (collision_mask_count <= frame)
{
sprite.CollisionMasks.Add(sprite.NewMaskEntry());
sprite.CollisionMasks.Add(sprite.NewMaskEntry(Data));
collision_mask_count += 1;
}
try
{
(uint maskWidth, uint maskHeight) = sprite.CalculateMaskDimensions(Data);
sprite.CollisionMasks[frame].Data = TextureWorker.ReadMaskData(file, (int)maskWidth, (int)maskHeight);
(int maskWidth, int maskHeight) = sprite.CalculateMaskDimensions(Data);
sprite.CollisionMasks[frame].Data = TextureWorker.ReadMaskData(file, maskWidth, maskHeight);
}
catch
{
Expand Down
4 changes: 2 additions & 2 deletions UndertaleModTool/Scripts/Resource Unpackers/ExportMasks.csx
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ void DumpSprite(UndertaleSprite sprite)
{
if (sprite.CollisionMasks[i]?.Data is not null)
{
(uint maskWidth, uint maskHeight) = sprite.CalculateMaskDimensions(Data);
TextureWorker.ExportCollisionMaskPNG(sprite.CollisionMasks[i], Path.Combine(texFolder, $"{sprite.Name.Content}_{i}.png"), (int)maskWidth, (int)maskHeight);
(int maskWidth, int maskHeight) = sprite.CalculateMaskDimensions(Data);
TextureWorker.ExportCollisionMaskPNG(sprite.CollisionMasks[i], Path.Combine(texFolder, $"{sprite.Name.Content}_{i}.png"), maskWidth, maskHeight);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,9 @@ foreach (Atlas atlas in packer.Atlasses)
for (int i = 0; i < frame; i++)
newSprite.Textures.Add(null);
}

// FIXME: this needs support for 2024.6+ collision masks, which only use bounding box
// (should use newSprite.CalculateMaskDimensions(Data) as well as newSprite.NewMaskEntry(Data))
newSprite.CollisionMasks.Add(newSprite.NewMaskEntry());

int width = ((n.Bounds.Width + 7) / 8) * 8;
Expand Down