Skip to content

Commit 5437f8c

Browse files
committed
feat: Texture groups information support
1 parent 1e29709 commit 5437f8c

File tree

3 files changed

+396
-0
lines changed

3 files changed

+396
-0
lines changed
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
2+
3+
4+
using System;
5+
using System.IO;
6+
using System.Text;
7+
using System.Text.Json;
8+
using System.Linq;
9+
using System.Threading.Tasks;
10+
using System.Collections.Generic;
11+
using UndertaleModLib;
12+
using UndertaleModLib.Models;
13+
14+
15+
16+
17+
string OutputDirectory = "";
18+
19+
20+
21+
22+
void PrintLine(string s) => Console.WriteLine(s);
23+
24+
string SafeName(string name)
25+
{
26+
var invalid = Path.GetInvalidFileNameChars();
27+
var sb = new StringBuilder(name.Length);
28+
foreach (var ch in name) sb.Append(invalid.Contains(ch) ? '_' : ch);
29+
return sb.ToString();
30+
}
31+
32+
string ResolveOutputDirectory()
33+
{
34+
if (!string.IsNullOrEmpty(OutputDirectory) && Directory.Exists(OutputDirectory))
35+
return OutputDirectory;
36+
37+
if (string.IsNullOrEmpty(FilePath))
38+
throw new ScriptException("No data.win file loaded. Please load a game data file first.");
39+
40+
string dataWinDir = Path.GetDirectoryName(FilePath);
41+
string outputDir = Path.Combine(dataWinDir, "Objects", "TextureGroups");
42+
Directory.CreateDirectory(outputDir);
43+
return outputDir;
44+
}
45+
46+
47+
48+
49+
EnsureDataLoaded();
50+
51+
if (Data.TextureGroupInfo == null || Data.TextureGroupInfo.Count == 0)
52+
{
53+
PrintLine("[ExportTextureGroupInfo] No texture group information found in this game. This feature requires GameMaker 2.2.1+ (Bytecode 17+).");
54+
return;
55+
}
56+
57+
string textureGroupsOut = ResolveOutputDirectory();
58+
PrintLine($"[ExportTextureGroupInfo] Exporting to: {textureGroupsOut}");
59+
60+
List<UndertaleTextureGroupInfo> allTextureGroups = Data.TextureGroupInfo?.ToList() ?? new List<UndertaleTextureGroupInfo>();
61+
PrintLine($"[ExportTextureGroupInfo] Found {allTextureGroups.Count} texture group info entries to export.");
62+
63+
SetProgressBar(null, "Exporting Texture Group Info", 0, allTextureGroups.Count);
64+
StartProgressBarUpdater();
65+
66+
await Task.Run(() => Parallel.ForEach(allTextureGroups, tg => ExportTextureGroup(tg, textureGroupsOut)));
67+
68+
void ExportTextureGroup(UndertaleTextureGroupInfo textureGroup, string outputDir)
69+
{
70+
if (textureGroup?.Name?.Content == null)
71+
{
72+
IncrementProgressParallel();
73+
return;
74+
}
75+
76+
try
77+
{
78+
string name = SafeName(textureGroup.Name.Content);
79+
string jsonPath = Path.Combine(outputDir, name + ".json");
80+
81+
using (var stream = new FileStream(jsonPath, FileMode.Create, FileAccess.Write))
82+
using (var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { Indented = true }))
83+
{
84+
writer.WriteStartObject();
85+
86+
writer.WriteString("name", textureGroup.Name.Content);
87+
88+
if (Data.IsVersionAtLeast(2022, 9))
89+
{
90+
if (textureGroup.Directory != null)
91+
writer.WriteString("directory", textureGroup.Directory.Content ?? "");
92+
if (textureGroup.Extension != null)
93+
writer.WriteString("extension", textureGroup.Extension.Content ?? "");
94+
writer.WriteNumber("loadType", (int)textureGroup.LoadType);
95+
writer.WriteString("loadTypeDescription", textureGroup.LoadType.ToString());
96+
}
97+
98+
writer.WriteStartArray("texturePages");
99+
if (textureGroup.TexturePages != null)
100+
{
101+
foreach (var texPage in textureGroup.TexturePages)
102+
{
103+
if (texPage?.Name?.Content != null)
104+
writer.WriteStringValue(texPage.Name.Content);
105+
}
106+
}
107+
writer.WriteEndArray();
108+
109+
writer.WriteStartArray("sprites");
110+
if (textureGroup.Sprites != null)
111+
{
112+
foreach (var sprite in textureGroup.Sprites)
113+
{
114+
if (sprite?.Name?.Content != null)
115+
writer.WriteStringValue(sprite.Name.Content);
116+
}
117+
}
118+
writer.WriteEndArray();
119+
120+
if (!Data.IsNonLTSVersionAtLeast(2023, 1))
121+
{
122+
writer.WriteStartArray("spineSprites");
123+
if (textureGroup.SpineSprites != null)
124+
{
125+
foreach (var spineSprite in textureGroup.SpineSprites)
126+
{
127+
if (spineSprite?.Name?.Content != null)
128+
writer.WriteStringValue(spineSprite.Name.Content);
129+
}
130+
}
131+
writer.WriteEndArray();
132+
}
133+
134+
writer.WriteStartArray("fonts");
135+
if (textureGroup.Fonts != null)
136+
{
137+
foreach (var font in textureGroup.Fonts)
138+
{
139+
if (font?.Name?.Content != null)
140+
writer.WriteStringValue(font.Name.Content);
141+
}
142+
}
143+
writer.WriteEndArray();
144+
145+
writer.WriteStartArray("tilesets");
146+
if (textureGroup.Tilesets != null)
147+
{
148+
foreach (var tileset in textureGroup.Tilesets)
149+
{
150+
if (tileset?.Name?.Content != null)
151+
writer.WriteStringValue(tileset.Name.Content);
152+
}
153+
}
154+
writer.WriteEndArray();
155+
156+
writer.WriteEndObject();
157+
}
158+
}
159+
catch (Exception ex)
160+
{
161+
PrintLine($"[ExportTextureGroupInfo] Failed to export texture group info {textureGroup.Name?.Content}: {ex.Message}");
162+
}
163+
164+
IncrementProgressParallel();
165+
}
166+
167+
await StopProgressBarUpdater();
168+
HideProgressBar();
169+
170+
PrintLine($"[ExportTextureGroupInfo] Export complete. {allTextureGroups.Count} texture group info entries exported to {textureGroupsOut}");
Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
2+
3+
4+
using System;
5+
using System.IO;
6+
using System.Text;
7+
using System.Linq;
8+
using System.Collections.Generic;
9+
using System.Text.Json;
10+
using UndertaleModLib;
11+
using UndertaleModLib.Models;
12+
13+
14+
15+
16+
string InputDirectory = "";
17+
18+
19+
20+
21+
void PrintLine(string s) => Console.WriteLine(s);
22+
23+
string ResolveInputDirectory()
24+
{
25+
if (!string.IsNullOrEmpty(InputDirectory) && Directory.Exists(InputDirectory))
26+
return InputDirectory;
27+
28+
if (string.IsNullOrEmpty(FilePath))
29+
throw new ScriptException("No data.win file loaded. Please load a game data file first.");
30+
31+
string dataWinDir = Path.GetDirectoryName(FilePath);
32+
string textureGroupsDir = Path.Combine(dataWinDir, "Objects", "TextureGroups");
33+
34+
if (Directory.Exists(textureGroupsDir))
35+
return textureGroupsDir;
36+
37+
throw new ScriptException($"TextureGroups directory not found at: {textureGroupsDir}\nPlease specify InputDirectory or place texture groups in an 'Objects/TextureGroups' folder next to data.win.");
38+
}
39+
40+
41+
42+
43+
EnsureDataLoaded();
44+
45+
if (Data.TextureGroupInfo == null)
46+
{
47+
PrintLine("[ImportTextureGroupInfo] This game does not support texture group information. This feature requires GameMaker 2.2.1+ (Bytecode 17+).");
48+
return;
49+
}
50+
51+
string textureGroupsDir = ResolveInputDirectory();
52+
PrintLine($"[ImportTextureGroupInfo] Importing from: {textureGroupsDir}");
53+
54+
string[] textureGroupFiles = Directory.GetFiles(textureGroupsDir, "*.json");
55+
if (textureGroupFiles.Length == 0)
56+
{
57+
PrintLine("[ImportTextureGroupInfo] No texture group info JSON files found, skipping import.");
58+
return;
59+
}
60+
61+
PrintLine($"[ImportTextureGroupInfo] Found {textureGroupFiles.Length} texture group info file(s) to import.");
62+
63+
SetProgressBar(null, "Importing Texture Group Info", 0, textureGroupFiles.Length);
64+
StartProgressBarUpdater();
65+
66+
foreach (string textureGroupFile in textureGroupFiles)
67+
{
68+
try
69+
{
70+
string jsonContent = File.ReadAllText(textureGroupFile, Encoding.UTF8);
71+
string textureGroupName = Path.GetFileNameWithoutExtension(textureGroupFile);
72+
73+
JsonDocument jsonDoc = JsonDocument.Parse(jsonContent);
74+
JsonElement root = jsonDoc.RootElement;
75+
76+
UndertaleTextureGroupInfo textureGroup = Data.TextureGroupInfo.FirstOrDefault(tg => tg.Name?.Content == textureGroupName);
77+
if (textureGroup == null)
78+
{
79+
textureGroup = new UndertaleTextureGroupInfo();
80+
textureGroup.Name = Data.Strings.MakeString(textureGroupName);
81+
Data.TextureGroupInfo.Add(textureGroup);
82+
PrintLine($"[ImportTextureGroupInfo] Created new texture group info: {textureGroupName}");
83+
}
84+
else
85+
{
86+
PrintLine($"[ImportTextureGroupInfo] Updating existing texture group info: {textureGroupName}");
87+
}
88+
89+
UpdateTextureGroupFromJson(textureGroup, root);
90+
91+
jsonDoc.Dispose();
92+
IncrementProgress();
93+
}
94+
catch (Exception ex)
95+
{
96+
PrintLine($"[ImportTextureGroupInfo] Error importing texture group info {Path.GetFileName(textureGroupFile)}: {ex.Message}");
97+
}
98+
}
99+
100+
await StopProgressBarUpdater();
101+
HideProgressBar();
102+
103+
PrintLine("[ImportTextureGroupInfo] Texture group info import completed.");
104+
105+
void UpdateTextureGroupFromJson(UndertaleTextureGroupInfo textureGroup, JsonElement data)
106+
{
107+
if (data.TryGetProperty("name", out JsonElement nameElm) && nameElm.ValueKind == JsonValueKind.String)
108+
textureGroup.Name = Data.Strings.MakeString(nameElm.GetString());
109+
110+
if (Data.IsVersionAtLeast(2022, 9))
111+
{
112+
if (data.TryGetProperty("directory", out JsonElement dirElm) && dirElm.ValueKind == JsonValueKind.String)
113+
textureGroup.Directory = Data.Strings.MakeString(dirElm.GetString());
114+
115+
if (data.TryGetProperty("extension", out JsonElement extElm) && extElm.ValueKind == JsonValueKind.String)
116+
textureGroup.Extension = Data.Strings.MakeString(extElm.GetString());
117+
118+
if (data.TryGetProperty("loadType", out JsonElement loadTypeElm) && loadTypeElm.ValueKind == JsonValueKind.Number)
119+
textureGroup.LoadType = (UndertaleTextureGroupInfo.TextureGroupLoadType)loadTypeElm.GetInt32();
120+
}
121+
122+
if (data.TryGetProperty("texturePages", out JsonElement texPagesElm) && texPagesElm.ValueKind == JsonValueKind.Array)
123+
{
124+
textureGroup.TexturePages.Clear();
125+
foreach (JsonElement texPageElm in texPagesElm.EnumerateArray())
126+
{
127+
if (texPageElm.ValueKind == JsonValueKind.String)
128+
{
129+
string texPageName = texPageElm.GetString();
130+
if (!string.IsNullOrEmpty(texPageName))
131+
{
132+
var texPage = Data.EmbeddedTextures.FirstOrDefault(t => t.Name?.Content == texPageName);
133+
if (texPage != null)
134+
textureGroup.TexturePages.Add(texPage);
135+
else
136+
PrintLine($"[ImportTextureGroupInfo] Warning: Texture page '{texPageName}' not found in game data.");
137+
}
138+
}
139+
}
140+
}
141+
142+
if (data.TryGetProperty("sprites", out JsonElement spritesElm) && spritesElm.ValueKind == JsonValueKind.Array)
143+
{
144+
textureGroup.Sprites.Clear();
145+
foreach (JsonElement spriteElm in spritesElm.EnumerateArray())
146+
{
147+
if (spriteElm.ValueKind == JsonValueKind.String)
148+
{
149+
string spriteName = spriteElm.GetString();
150+
if (!string.IsNullOrEmpty(spriteName))
151+
{
152+
var sprite = Data.Sprites.ByName(spriteName);
153+
if (sprite != null)
154+
textureGroup.Sprites.Add(sprite);
155+
else
156+
PrintLine($"[ImportTextureGroupInfo] Warning: Sprite '{spriteName}' not found in game data.");
157+
}
158+
}
159+
}
160+
}
161+
162+
if (!Data.IsNonLTSVersionAtLeast(2023, 1))
163+
{
164+
if (data.TryGetProperty("spineSprites", out JsonElement spineSpritesElm) && spineSpritesElm.ValueKind == JsonValueKind.Array)
165+
{
166+
textureGroup.SpineSprites.Clear();
167+
foreach (JsonElement spineSpriteElm in spineSpritesElm.EnumerateArray())
168+
{
169+
if (spineSpriteElm.ValueKind == JsonValueKind.String)
170+
{
171+
string spineSpriteName = spineSpriteElm.GetString();
172+
if (!string.IsNullOrEmpty(spineSpriteName))
173+
{
174+
var spineSprite = Data.Sprites.ByName(spineSpriteName);
175+
if (spineSprite != null)
176+
textureGroup.SpineSprites.Add(spineSprite);
177+
else
178+
PrintLine($"[ImportTextureGroupInfo] Warning: Spine sprite '{spineSpriteName}' not found in game data.");
179+
}
180+
}
181+
}
182+
}
183+
}
184+
185+
if (data.TryGetProperty("fonts", out JsonElement fontsElm) && fontsElm.ValueKind == JsonValueKind.Array)
186+
{
187+
textureGroup.Fonts.Clear();
188+
foreach (JsonElement fontElm in fontsElm.EnumerateArray())
189+
{
190+
if (fontElm.ValueKind == JsonValueKind.String)
191+
{
192+
string fontName = fontElm.GetString();
193+
if (!string.IsNullOrEmpty(fontName))
194+
{
195+
var font = Data.Fonts.ByName(fontName);
196+
if (font != null)
197+
textureGroup.Fonts.Add(font);
198+
else
199+
PrintLine($"[ImportTextureGroupInfo] Warning: Font '{fontName}' not found in game data.");
200+
}
201+
}
202+
}
203+
}
204+
205+
if (data.TryGetProperty("tilesets", out JsonElement tilesetsElm) && tilesetsElm.ValueKind == JsonValueKind.Array)
206+
{
207+
textureGroup.Tilesets.Clear();
208+
foreach (JsonElement tilesetElm in tilesetsElm.EnumerateArray())
209+
{
210+
if (tilesetElm.ValueKind == JsonValueKind.String)
211+
{
212+
string tilesetName = tilesetElm.GetString();
213+
if (!string.IsNullOrEmpty(tilesetName))
214+
{
215+
var tileset = Data.Backgrounds.ByName(tilesetName);
216+
if (tileset != null)
217+
textureGroup.Tilesets.Add(tileset);
218+
else
219+
PrintLine($"[ImportTextureGroupInfo] Warning: Tileset '{tilesetName}' not found in game data.");
220+
}
221+
}
222+
}
223+
}
224+
}

0 commit comments

Comments
 (0)