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

Calculate length of texture data during serialization instead of writing a set value & Update ImportGMS2FontData #1048

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
b95fc17
Calc length of texture data during serialization
Dobby233Liu Jul 13, 2022
e5da962
Try 2 & Update ImportGMS2FontData
Dobby233Liu Jul 13, 2022
8c4a9f7
Write into blocksize variable
Dobby233Liu Jul 13, 2022
076f12b
Merge branch 'krzys-h:master' into Dobby233Liu-untextureblocksize-1
Dobby233Liu Aug 27, 2022
5c047b5
Merge branch 'krzys-h:master' into Dobby233Liu-untextureblocksize-1
Dobby233Liu Aug 27, 2022
7df75d2
Add GM2022_3 checks
Dobby233Liu Aug 31, 2022
cbaa46f
Fix copypasta
Dobby233Liu Aug 31, 2022
c336330
Slightly redo docs
Dobby233Liu Aug 31, 2022
a7e031e
Merge branch 'krzys-h:master' into Dobby233Liu-untextureblocksize-1
Dobby233Liu Oct 20, 2022
dbdb29f
Apply suggestions from code review
Dobby233Liu Oct 21, 2022
e758cd6
Apply suggestions #2
Dobby233Liu Oct 21, 2022
3f96382
Update ImportGMS2FontData (UNTESTED)
Dobby233Liu Oct 21, 2022
e3fb19b
Merge branch 'master' into Dobby233Liu-untextureblocksize-1
Dobby233Liu Dec 4, 2022
bdd7ce7
Update UndertaleEmbeddedTexture.cs
Dobby233Liu Dec 4, 2022
ca98caf
Merge branch 'master' of https://github.com/krzys-h/UndertaleModTool …
Dobby233Liu Jan 28, 2023
afc3055
Fix ImportGMS2FontData not functioning
Dobby233Liu Jan 28, 2023
2f09f9b
figured i should do this
Dobby233Liu Jan 28, 2023
efbbba7
ditto
Dobby233Liu Jan 28, 2023
cc5357b
the g
Dobby233Liu Jan 28, 2023
7bc8d0d
real
Dobby233Liu Jan 28, 2023
7acac5d
Apply suggestions from code review
Dobby233Liu Jan 29, 2023
40e352c
Add FIXME note for external textures
Dobby233Liu Jan 30, 2023
0208a94
Merge branch 'Dobby233Liu-untextureblocksize-1' of https://github.com…
Dobby233Liu Jan 30, 2023
81f78eb
Write what we have as the texture block size first
Dobby233Liu Jan 31, 2023
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
36 changes: 32 additions & 4 deletions UndertaleModLib/Models/UndertaleEmbeddedTexture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,16 @@ public class UndertaleEmbeddedTexture : UndertaleNamedResource, IDisposable
public uint GeneratedMips { get; set; }

/// <summary>
/// Size of the texture block in bytes. Only appears in later 2022 versions of GameMaker.
/// Size of the texture attached to this texture page in bytes. Only appears in GM 2022.3+.
/// </summary>
public uint TextureBlockSize { get; set; }
private uint _textureBlockSize { get; set; }

/// <summary>
/// The position of the placeholder <see cref="_textureBlockSize">TextureBlockSize</see> value
/// to be overwritten in SerializeBlob. <br/>
/// Only used internally for GM 2022.3+ support.
/// </summary>
private uint _textureBlockSizeLocation { get; set; }

/// <summary>
/// The texture data in the embedded image.
Expand Down Expand Up @@ -90,7 +97,12 @@ public void Serialize(UndertaleWriter writer)
if (writer.undertaleData.GeneralInfo.Major >= 2)
writer.Write(GeneratedMips);
if (writer.undertaleData.GM2022_3)
writer.Write(TextureBlockSize);
{
// We're going to overwrite this later with the actual size
// of our texture block, so save the position
_textureBlockSizeLocation = writer.Position;
writer.Write(_textureBlockSize);
}
if (writer.undertaleData.GM2022_9)
{
writer.Write(TextureWidth);
Expand All @@ -110,7 +122,7 @@ public void Unserialize(UndertaleReader reader)
if (reader.undertaleData.GeneralInfo.Major >= 2)
GeneratedMips = reader.ReadUInt32();
if (reader.undertaleData.GM2022_3)
TextureBlockSize = reader.ReadUInt32();
_textureBlockSize = reader.ReadUInt32();
if (reader.undertaleData.GM2022_9)
{
TextureWidth = reader.ReadInt32();
Expand All @@ -130,14 +142,30 @@ public void SerializeBlob(UndertaleWriter writer)
{
// If external, don't serialize blob
// Has sanity check for data being null as well, although the external flag should be set
// FIXME: Implement external texture writing
// When we implement the above, we should also write the texture's actual size to
// TextureBlockSize because GM does it
// (behavior observed in a VM game built with Runtime 2022.11.1.75)
if (_textureData == null || TextureExternal)
return;

// padding
while (writer.Position % 0x80 != 0)
writer.Write((byte)0);

var texStartPos = writer.Position;
writer.WriteUndertaleObject(_textureData);

if (writer.undertaleData.GM2022_3)
{
_textureBlockSize = texStartPos - writer.Position;
// Write the actual size of the texture block in
// the place of _textureBlockSize
var posBackup = writer.Position;
writer.Position = _textureBlockSizeLocation;
writer.Write(_textureBlockSize);
writer.Position = posBackup;
}
}

/// <summary>
Expand Down
162 changes: 106 additions & 56 deletions UndertaleModTool/Scripts/Community Scripts/ImportGMS2FontData.csx
Original file line number Diff line number Diff line change
Expand Up @@ -7,54 +7,106 @@ using System.Collections;
using System.Collections.Generic;
Dobby233Liu marked this conversation as resolved.
Show resolved Hide resolved
using System.Linq;
using System.Text;
using UndertaleModLib;
using UndertaleModLib.Util;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

EnsureDataLoaded();

ScriptMessage(@"ImportGMS2FontData by Dobby233Liu
This script imports the .yy file the GM IDE generates for font assets to your game
Designed for the data IDE v2022.6.0.23 generates
Select a .yy file in the fonts directory of your project
");
This script can import GM font asset data to your mod
(Designed for the data IDE v2022.6.0.23 generates)
Select the .yy file of the GM font asset you want to import"
);

string importFile = PromptLoadFile("yy", "GameMaker Studio 2 files (.yy)|*.yy|All files|*");
if (importFile == null)
throw new ScriptException("The import folder was not set.");
{
ScriptError("Import cancelled.");
return;
}

JObject fontData = null;
using (StreamReader file = File.OpenText(importFile))
{
using (JsonTextReader reader = new JsonTextReader(file))
{
fontData = (JObject)JToken.ReadFrom(reader);
fontData = JObject.Load(reader);
}
}

string fontPath = Path.GetFullPath(importFile);
string fontName = (string)fontData["name"]; // im gonna trust the yy file lmao
string textureSourcePath = Path.Combine(Path.GetDirectoryName(importFile), fontName + ".png");
if (!File.Exists(textureSourcePath))
throw new ScriptException("The font texture file " + textureSourcePath + " doesn't exist.");
string fontPath = Path.GetDirectoryName(importFile);
string yyFilename = Path.GetFileNameWithoutExtension(importFile);
string fontName = (string)fontData["name"] ?? yyFilename;
string fontTexturePath = Path.Combine(fontPath, yyFilename + ".png");
// Failsafe to use font name
if (!File.Exists(fontTexturePath))
fontTexturePath = Path.Combine(fontPath, fontName + ".png");
// If we still can't find the texture
if (!File.Exists(fontTexturePath))
throw new ScriptException(
$@"Could not find a texture file for the selected font.
Try renaming the correct texture file to
{yyFilename}.png
and putting it in the same directory as the .yy file."
);

/*
If true, the script will attempt to add the new font (if any) and the new font glyph texture that it created to a texture group
This was an attempt to get fonts that this script creates appear in a specific 2022.3 game, but was proved unnecessary, as the problem was caused by something else
*/
bool attemptToFixFontNotAppearing = false; // Data.GM2022_3;
// Default to putting the font into the default texgroup
UndertaleTextureGroupInfo fontTexGroup = Data.TextureGroupInfo.ByName("Default");

UndertaleFont font = Data.Fonts.ByName(fontName);
if (font == null)
{
font = new UndertaleFont() {
font = new UndertaleFont()
{
Name = Data.Strings.MakeString(fontName)
};
Data.Fonts.Add(font);

if (attemptToFixFontNotAppearing)
{
if (fontTexGroup == null)
throw new ScriptException("The default texture group doesn't exist??? (this shouldn't happen)");
fontTexGroup.Fonts.Add(new UndertaleResourceById<UndertaleFont, UndertaleChunkFONT>() { Resource = font });
}
}
else if (attemptToFixFontNotAppearing)
{
// Try to find the texgroup that the font belongs to
// Scariest LINQ query I've ever written (yet)
fontTexGroup = Data.TextureGroupInfo
.Where(t => t.Fonts.Any(f => f.Resource == font))
.DefaultIfEmpty(fontTexGroup)
.FirstOrDefault();
if (fontTexGroup == null)
throw new ScriptException("Existing font doesn't belong to any texture group AND the default texture group doesn't exist??? (this shouldn't happen)");
// Failsafe - put it in Default if it's not in there
if (!fontTexGroup.Fonts.Any(f => f.Resource == font))
fontTexGroup.Fonts.Add(new UndertaleResourceById<UndertaleFont, UndertaleChunkFONT>() { Resource = font });
}

// Prepare font texture
Bitmap textureBitmap = new Bitmap(fontTexturePath);
// Make the DPI exactly 96 for this bitmap
textureBitmap.SetResolution(96.0F, 96.0F);

Bitmap textureBitmap = new Bitmap(textureSourcePath);
UndertaleEmbeddedTexture texture = new UndertaleEmbeddedTexture();
texture.Name = Data.Strings.MakeString("Texture " + Data.EmbeddedTextures.Count); // ???
texture.TextureData.TextureBlob = File.ReadAllBytes(textureSourcePath);
// ??? Why?
texture.Name = new UndertaleString("Texture " + Data.EmbeddedTextures.Count);
texture.TextureData.TextureBlob = File.ReadAllBytes(fontTexturePath);
Data.EmbeddedTextures.Add(texture);
if (attemptToFixFontNotAppearing)
fontTexGroup.TexturePages.Add(new UndertaleResourceById<UndertaleEmbeddedTexture, UndertaleChunkTXTR>() { Resource = texture });

UndertaleTexturePageItem texturePageItem = new UndertaleTexturePageItem();
texturePageItem.Name = Data.Strings.MakeString("PageItem " + Data.TexturePageItems.Count); // ???
// ??? Same as above
texturePageItem.Name = new UndertaleString("PageItem " + Data.TexturePageItems.Count);
texturePageItem.TexturePage = texture;
texturePageItem.SourceX = 0;
texturePageItem.SourceY = 0;
Expand All @@ -68,60 +120,64 @@ texturePageItem.BoundingWidth = (ushort)textureBitmap.Width;
texturePageItem.BoundingHeight = (ushort)textureBitmap.Height;
Data.TexturePageItems.Add(texturePageItem);

font.Texture = texturePageItem;

if (Data.GMS2_3)
font.EmSizeIsFloat = true; // forcbliy save as float lmao

font.Glyphs.Clear();

font.DisplayName = Data.Strings.MakeString((string)fontData["fontName"]);
font.EmSize = (uint)fontData["size"];
font.Texture = texturePageItem;
font.Bold = (bool)fontData["bold"];
font.Italic = (bool)fontData["italic"];
// FIXME: Potentially causes float precision to be lost
font.EmSize = (uint)fontData["size"];
// Save font size as a float in GMS2.3+ (shouldn't UML always save EmSize as a float for GMS2.3+ games??)
font.EmSizeIsFloat = Data.GMS2_3;
font.Charset = (byte)fontData["charset"];
font.AntiAliasing = (byte)fontData["AntiAlias"];
// ???
// FIXME: ??? All YY files I've saw don't contain this
font.ScaleX = 1;
font.ScaleY = 1;
// Ascender is GM2022.2+
if (fontData.ContainsKey("ascender"))
font.Ascender = (uint)fontData["ascender"];
if (fontData.ContainsKey("ascenderOffset"))
font.AscenderOffset = (int)fontData["ascenderOffset"];

font.RangeStart = 0;
font.RangeEnd = 0;
// FIXME: Too complicated?
List<int> charRangesUppersAndLowers = new();
foreach (JObject range in fontData["ranges"].Values<JObject>())
{
var rangeStart = (ushort)range["lower"];
var rangeEnd = (uint)range["upper"];
if (font.RangeStart > rangeStart)
font.RangeStart = rangeStart;
if (font.RangeEnd > rangeEnd)
font.RangeEnd = rangeEnd;
charRangesUppersAndLowers.Add((int)range["upper"]);
charRangesUppersAndLowers.Add((int)range["lower"]);
}

foreach (KeyValuePair<string, JToken> glyphMeta in (JObject)fontData["glyphs"])
charRangesUppersAndLowers.Sort();
// FIXME: Check the range by ourselves if ranges don't have it probably
font.RangeStart = (ushort)charRangesUppersAndLowers.DefaultIfEmpty(0).FirstOrDefault();
font.RangeEnd = (uint)charRangesUppersAndLowers.DefaultIfEmpty(0xFFFF).LastOrDefault();

List<UndertaleFont.Glyph> glyphs = new();
// From what I've seen, the keys of the objects in glyphs is just the character property of the object itself but in string form
foreach (KeyValuePair<string, JToken> glyphKVEntry in (JObject)fontData["glyphs"])
{
var glyph = (JObject)glyphMeta.Value;
font.Glyphs.Add(new UndertaleFont.Glyph()
var glyphData = (JObject)glyphKVEntry.Value;
glyphs.Add(new UndertaleFont.Glyph()
{
Character = (ushort)glyph["character"],
SourceX = (ushort)glyph["x"],
SourceY = (ushort)glyph["y"],
SourceWidth = (ushort)glyph["w"],
SourceHeight = (ushort)glyph["h"],
Shift = (short)glyph["shift"],
Offset = (short)glyph["offset"],
Character = (ushort)glyphData["character"],
SourceX = (ushort)glyphData["x"],
SourceY = (ushort)glyphData["y"],
SourceWidth = (ushort)glyphData["w"],
SourceHeight = (ushort)glyphData["h"],
Shift = (short)glyphData["shift"],
Offset = (short)glyphData["offset"],
});
}
// Sort glyphs like UndertaleFontEditor to be safe
glyphs.Sort((x, y) => x.Character.CompareTo(y.Character));
font.Glyphs.Clear();
foreach (var glyph in glyphs)
font.Glyphs.Add(glyph);

List<UndertaleFont.Glyph> glyphs = font.Glyphs.ToList();

// I'm literally going to LINQ 100000 times
// and you can't stop me
foreach (JObject kerningPair in fontData["kerningPairs"].Values<JObject>())
// TODO: Does this always exist?
glyphs = font.Glyphs.ToList();
foreach (JObject kerningPair in fontData["kerningPairs"]?.Values<JObject>())
{
// Why do I need to do this. Thanks YoYo
var first = (ushort)kerningPair["first"];
var glyph = glyphs.Find(x => x.Character == first);
glyph.Kerning.Add(new UndertaleFont.Glyph.GlyphKerning()
Expand All @@ -131,10 +187,4 @@ foreach (JObject kerningPair in fontData["kerningPairs"].Values<JObject>())
});
}

// Sort glyphs like in UndertaleFontEditor to be safe
glyphs.Sort((x, y) => x.Character.CompareTo(y.Character));
font.Glyphs.Clear();
foreach (UndertaleFont.Glyph glyph in glyphs)
font.Glyphs.Add(glyph);

ScriptMessage("Import complete.");
ScriptMessage("Import complete.");