UndertaleModTool-ExportToPr.../ExportToProject.csx
2019-11-03 00:11:42 +08:00

533 lines
No EOL
22 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Linq;
using System.Drawing;
using System.Reflection;
using UndertaleModLib.Models;
using UndertaleModLib.Util;
using UndertaleModLib.Decompiler;
int progress = 0;
string projFolder = GetFolder(FilePath) + "Export_Project" + Path.DirectorySeparatorChar;
var context = new DecompileContext(Data, true);
TextureWorker worker = new TextureWorker();
ThreadLocal<DecompileContext> DECOMPILE_CONTEXT = new ThreadLocal<DecompileContext>(() => new DecompileContext(Data, false));
string gmxDeclaration = "This Document is generated by GameMaker, if you edit it by hand then you do so at your own risk!";
if (Directory.Exists(projFolder))
{
ScriptError("A project export already exists. Please remove it.", "Error");
return;
}
Directory.CreateDirectory(projFolder);
// --------------- Start exporting ---------------
// Export sprites
UpdateProgressBar(null, "Exporting sprites...", progress++, 8);
await ExportSprites();
// Export backgrounds
UpdateProgressBar(null, "Exporting backgrounds...", progress++, 8);
await ExportBackground();
// Export objects
UpdateProgressBar(null, "Exporting objects...", progress++, 8);
await ExportGameObjects();
// Export rooms
UpdateProgressBar(null, "Exporting rooms...", progress++, 8);
await ExportRooms();
// Export sounds
UpdateProgressBar(null, "Exporting sounds...", progress++, 8);
await ExportSounds();
// Export scripts
UpdateProgressBar(null, "Exporting scripts...", progress++, 8);
await ExportScripts();
// Export fonts
UpdateProgressBar(null, "Exporting fonts...", progress++, 8);
await ExportFonts();
// Generate project file
UpdateProgressBar(null, "Generating project file...", progress++, 8);
ExportProjectFile();
// --------------- Export completed ---------------
worker.Cleanup();
HideProgressBar();
ScriptMessage("Export Complete.\n\nLocation: " + projFolder);
string GetFolder(string path)
{
return Path.GetDirectoryName(path) + Path.DirectorySeparatorChar;
}
string BoolToString(bool value)
{
// In the GMX file, -1 is true and 0 is false.
return value ? "-1" : "0";
}
// --------------- Export Sprite ---------------
async Task ExportSprites()
{
Directory.CreateDirectory(projFolder + "/sprites/images");
await Task.Run(() => Parallel.ForEach(Data.Sprites, ExportSprite));
}
void ExportSprite(UndertaleSprite sprite)
{
// Save the sprite GMX
var gmx = new XDocument(
new XComment(gmxDeclaration),
new XElement("sprite",
new XElement("type", "0"),
new XElement("xorig", sprite.OriginX.ToString()),
new XElement("yorigin", sprite.OriginY.ToString()),
new XElement("colkind", sprite.BBoxMode.ToString()),
new XElement("coltolerance", "0"),
new XElement("sepmasks", sprite.SepMasks.ToString("D")),
new XElement("bboxmode", sprite.BBoxMode.ToString()),
new XElement("bbox_left", sprite.MarginLeft.ToString()),
new XElement("bbox_right", sprite.MarginRight.ToString()),
new XElement("bbox_top", sprite.MarginTop.ToString()),
new XElement("bbox_bottom", sprite.MarginBottom.ToString()),
new XElement("HTile", "0"),
new XElement("VTile", "0"),
new XElement("TextureGroups",
new XElement("TextureGroup0", "0")
),
new XElement("For3D", "0"),
new XElement("width", sprite.Width.ToString()),
new XElement("height", sprite.Height.ToString()),
new XElement("frames"),
new XElement("bbox_right", sprite.MarginRight.ToString())
)
);
for (int i = 0; i < sprite.Textures.Count; i++)
{
if (sprite.Textures[i]?.Texture != null)
{
gmx.Element("sprite").Element("frames").Add(
new XElement(
"frame",
new XAttribute("index", i.ToString()),
"images\\" + sprite.Name.Content + "_" + i + ".png"
)
);
}
}
File.WriteAllText(projFolder + "/sprites/" + sprite.Name.Content + ".sprite.gmx", gmx.ToString());
// Save sprite images
for (int i = 0; i < sprite.Textures.Count; i++)
{
if (sprite.Textures[i]?.Texture != null)
{
// Fix sprite size
var bitmapNew = new Bitmap((int)sprite.Width, (int)sprite.Height);
var bitmapOrigin = worker.GetTextureFor(sprite.Textures[i].Texture, Path.GetFileNameWithoutExtension(projFolder + "/sprites/images/" + sprite.Name.Content + "_" + i + ".png"));
//worker.ExportAsPNG(sprite.Textures[i].Texture, projFolder + "/sprites/images/" + sprite.Name.Content + "_" + i + ".png");
var g = Graphics.FromImage(bitmapNew);
g.DrawImage(bitmapOrigin, (int)sprite.Textures[i].Texture.TargetX, (int)sprite.Textures[i].Texture.TargetY);
bitmapNew.Save(projFolder + "/sprites/images/" + sprite.Name.Content + "_" + i + ".png");
bitmapNew.Dispose();
bitmapOrigin.Dispose();
}
}
}
// --------------- Export Background ---------------
async Task ExportBackground()
{
Directory.CreateDirectory(projFolder + "/background/images");
await Task.Run(() => Parallel.ForEach(Data.Backgrounds, ExportBackground));
}
void ExportBackground(UndertaleBackground background)
{
// Save the backgound GMX
var gmx = new XDocument(
new XComment(gmxDeclaration),
new XElement("background",
new XElement("istileset", "-1"),
new XElement("tilewidth", background.Texture.BoundingWidth.ToString()),
new XElement("tileheight", background.Texture.BoundingHeight.ToString()),
new XElement("tilexoff", "0"),
new XElement("tileyoff", "0"),
new XElement("tilehsep", "0"),
new XElement("tilevsep", "0"),
new XElement("HTile", "-1"),
new XElement("VTile", "-1"),
new XElement("TextureGroups",
new XElement("TextureGroup0", "0")
),
new XElement("For3D", "0"),
new XElement("width", background.Texture.BoundingWidth.ToString()),
new XElement("height", background.Texture.BoundingHeight.ToString()),
new XElement("data", "images\\" + background.Name.Content + ".png")
)
);
File.WriteAllText(projFolder + "/background/" + background.Name.Content + ".background.gmx", gmx.ToString());
// Save background images
worker.ExportAsPNG(background.Texture, projFolder + "/background/images/" + background.Name.Content + ".png");
}
// --------------- Export Object ---------------
async Task ExportGameObjects()
{
Directory.CreateDirectory(projFolder + "/objects");
await Task.Run(() => Parallel.ForEach(Data.GameObjects, ExportGameObject));
}
void ExportGameObject(UndertaleGameObject gameObject)
{
// Save the object GMX
var gmx = new XDocument(
new XComment(gmxDeclaration),
new XElement("object",
new XElement("spriteName", gameObject.Sprite is null ? "<undefined>" : gameObject.Sprite.Name.Content),
new XElement("solid", BoolToString(gameObject.Solid)),
new XElement("visible", BoolToString(gameObject.Visible)),
new XElement("depth", gameObject.Depth.ToString()),
new XElement("persistent", BoolToString(gameObject.Persistent)),
new XElement("parentName", gameObject.ParentId is null ? "<undefined>" : gameObject.ParentId.Name.Content),
new XElement("maskName", gameObject.TextureMaskId is null ? "<undefined>" : gameObject.TextureMaskId.Name.Content),
new XElement("events")
)
);
// Traversing the event type list
for (int i = 0; i < gameObject.Events.Count; i++)
{
// Determine if an event is empty
if (gameObject.Events[i].Count > 0)
{
// Traversing event list
foreach (var j in gameObject.Events[i])
{
var eventsNode = gmx.Element("object").Element("events");
var eventNode = new XElement("event",
new XAttribute("eventtype", i.ToString())
);
if (j.EventSubtype == 4)
{
// To get the actual name of the collision object when the event type is a collision event
eventNode.Add(new XAttribute("ename", Data.GameObjects[(int)j.EventSubtype].Name.Content));
}
else
{
// Get the sub-event number directly
eventNode.Add(new XAttribute("enumb", j.EventSubtype.ToString()));
}
// Save action
var actionNode = new XElement("action");
// Traversing the action list
foreach (var k in j.Actions)
{
actionNode.Add(
new XElement("libid", k.LibID.ToString()),
new XElement("id", k.ID.ToString()),
new XElement("kind", k.Kind.ToString()),
new XElement("userelative", k.LibID.ToString()),
new XElement("libid", BoolToString(k.UseRelative)),
new XElement("isquestion", BoolToString(k.IsQuestion)),
new XElement("useapplyto", BoolToString(k.UseApplyTo)),
new XElement("exetype", k.ExeType.ToString()),
new XElement("functionname", k.ActionName.Content),
new XElement("codestring", ""),
new XElement("whoName", "self"),
new XElement("relative", BoolToString(k.Relative)),
new XElement("isnot", BoolToString(k.IsNot)),
new XElement("arguments",
new XElement("argument",
new XElement("kind", "1"),
new XElement("string", k.CodeId != null ? Decompiler.Decompile(k.CodeId, DECOMPILE_CONTEXT.Value) : "")
)
)
);
}
eventNode.Add(actionNode);
eventsNode.Add(eventNode);
// TODOPhysics
}
}
}
File.WriteAllText(projFolder + "/objects/" + gameObject.Name.Content + ".object.gmx", gmx.ToString());
}
// --------------- Export Room ---------------
async Task ExportRooms()
{
Directory.CreateDirectory(projFolder + "/rooms");
await Task.Run(() => Parallel.ForEach(Data.Rooms, ExportRoom));
}
void ExportRoom(UndertaleRoom room)
{
// Save the room GMX
var gmx = new XDocument(
new XComment(gmxDeclaration),
new XElement("room",
new XElement("caption", room.Caption.Content),
new XElement("width", room.Width.ToString()),
new XElement("height", room.Height.ToString()),
new XElement("vsnap", "32"),
new XElement("hsnap", "32"),
new XElement("isometric", "0"),
new XElement("speed", room.Speed.ToString()),
new XElement("persistent", BoolToString(room.Persistent)),
new XElement("colour", room.BackgroundColor.ToString()),
new XElement("code", room.CreationCodeId is null ? "" : Decompiler.Decompile(room.CreationCodeId, context)),
new XElement("enableViews", BoolToString(room.Flags.HasFlag(UndertaleRoom.RoomEntryFlags.EnableViews))),
new XElement("clearViewBackground", BoolToString(room.Flags.HasFlag(UndertaleRoom.RoomEntryFlags.ShowColor))),
new XElement("clearDisplayBuffer", BoolToString(room.Flags.HasFlag(UndertaleRoom.RoomEntryFlags.ClearDisplayBuffer)))
)
);
// TODOMakerSettings
// Room backgrounds
var backgroundsNode = new XElement("backgrounds");
foreach (var i in room.Backgrounds)
{
var backgroundNode = new XElement("background",
new XAttribute("visible", BoolToString(i.Enabled)),
new XAttribute("foreground", BoolToString(i.Foreground)),
new XAttribute("name", i.BackgroundDefinition is null ? "" : i.BackgroundDefinition.Name.Content),
new XAttribute("x", i.X.ToString()),
new XAttribute("y", i.Y.ToString()),
new XAttribute("htiled", i.TileX.ToString()),
new XAttribute("vtiled", i.TileY.ToString()),
new XAttribute("hspeed", i.SpeedX.ToString()),
new XAttribute("vspeed", i.SpeedY.ToString()),
new XAttribute("stretch", "0")
);
backgroundsNode.Add(backgroundNode);
}
gmx.Element("room").Add(backgroundsNode);
// Room views
var viewsNode = new XElement("views");
foreach (var i in room.Views)
{
var viewNode = new XElement("view",
new XAttribute("visible", BoolToString(i.Enabled)),
new XAttribute("objName", i.ObjectId is null ? "<undefined>" : i.ObjectId.Name.Content),
new XAttribute("xview", i.ViewX.ToString()),
new XAttribute("yview", i.ViewY.ToString()),
new XAttribute("wview", i.ViewHeight.ToString()),
new XAttribute("xport", i.PortX.ToString()),
new XAttribute("yport", i.PortY.ToString()),
new XAttribute("wport", i.PortWidth.ToString()),
new XAttribute("hport", i.PortHeight.ToString()),
new XAttribute("hborder", i.BorderX.ToString()),
new XAttribute("vborder", i.BorderY.ToString()),
new XAttribute("hspeed", i.SpeedX.ToString()),
new XAttribute("vspeed", i.SpeedY.ToString())
);
viewsNode.Add(viewNode);
}
gmx.Element("room").Add(viewsNode);
// Room instances
var instancesNode = new XElement("instances");
foreach (var i in room.GameObjects)
{
var instanceNode = new XElement("instance",
new XAttribute("objName", i.ObjectDefinition.Name.Content),
new XAttribute("x", i.X.ToString()),
new XAttribute("y", i.Y.ToString()),
new XAttribute("name", "inst_" + i.InstanceID.ToString("X")),
new XAttribute("locked", "0"),
new XAttribute("code", i.CreationCode != null ? Decompiler.Decompile(i.CreationCode, DECOMPILE_CONTEXT.Value) : ""),
new XAttribute("scaleX", i.ScaleX.ToString()),
new XAttribute("scaleY", i.ScaleY.ToString()),
new XAttribute("colour", i.Color.ToString()),
new XAttribute("rotation", i.Rotation.ToString())
);
instancesNode.Add(instanceNode);
}
gmx.Element("room").Add(instancesNode);
// Room tiles
var tilesNode = new XElement("tiles");
foreach (var i in room.Tiles)
{
var tileNode = new XElement("tile",
new XAttribute("bgName", i.BackgroundDefinition.Name.Content),
new XAttribute("x", i.X.ToString()),
new XAttribute("y", i.Y.ToString()),
new XAttribute("w", i.Width.ToString()),
new XAttribute("h", i.Height.ToString()),
new XAttribute("xo", i.SourceX.ToString()),
new XAttribute("yo", i.SourceY.ToString()),
new XAttribute("id", i.InstanceID.ToString()),
new XAttribute("name", "inst_" + i.InstanceID.ToString("X")),
new XAttribute("depth", i.TileDepth.ToString()),
new XAttribute("locked", "0"),
new XAttribute("colour", i.Color.ToString()),
new XAttribute("scaleX", i.ScaleX.ToString()),
new XAttribute("scaleY", i.ScaleY.ToString())
);
tilesNode.Add(tileNode);
}
gmx.Element("room").Add(tilesNode);
// TODORoom physics
File.WriteAllText(projFolder + "/rooms/" + room.Name.Content + ".room.gmx", gmx.ToString());
}
// --------------- Export Sound ---------------
async Task ExportSounds()
{
Directory.CreateDirectory(projFolder + "/sound/audio");
await Task.Run(() => Parallel.ForEach(Data.Sounds, ExportSound));
}
void ExportSound(UndertaleSound sound)
{
// Save the sound GMX
var gmx = new XDocument(
new XComment(gmxDeclaration),
new XElement("sound",
new XElement("kind", Path.GetExtension(sound.File.Content) == ".ogg" ? "3" : "0"),
new XElement("extension", Path.GetExtension(sound.File.Content)),
new XElement("origname", "sound\\audio\\" + sound.File.Content),
new XElement("effects", sound.Effects.ToString()),
new XElement("volume", sound.Volume.ToString()),
new XElement("pan", "0"),
new XElement("bitRates", "192"),
new XElement("sampleRates",
new XElement("sampleRate", "44100")
),
new XElement("types",
new XElement("type", "1")
),
new XElement("bitDepths",
new XElement("bitDepth", "16")
),
new XElement("preload", "-1"),
new XElement("data", Path.GetFileName(sound.File.Content)),
new XElement("compressed", Path.GetExtension(sound.File.Content) == ".ogg" ? "1" : "0"),
new XElement("streamed", Path.GetExtension(sound.File.Content) == ".ogg" ? "1" : "0"),
new XElement("uncompressOnLoad", "0"),
new XElement("audioGroup", "0")
)
);
File.WriteAllText(projFolder + "/sound/" + sound.Name.Content + ".sound.gmx", gmx.ToString());
// Save sound files
if (sound.AudioFile != null)
File.WriteAllBytes(projFolder + "/sound/audio/" + sound.File.Content, sound.AudioFile.Data);
}
// --------------- Export Script ---------------
async Task ExportScripts()
{
Directory.CreateDirectory(projFolder + "/scripts/");
await Task.Run(() => Parallel.ForEach(Data.Scripts, ExportScript));
}
void ExportScript(UndertaleScript script)
{
// Save GML files
File.WriteAllText(projFolder + "/scripts/" + script.Name.Content + ".gml", (script.Code != null ? Decompiler.Decompile(script.Code, DECOMPILE_CONTEXT.Value) : ""));
}
// --------------- Export Font ---------------
async Task ExportFonts()
{
Directory.CreateDirectory(projFolder + "/fonts/");
await Task.Run(() => Parallel.ForEach(Data.Fonts, ExportFont));
}
void ExportFont(UndertaleFont font)
{
// Save the font GMX
var gmx = new XDocument(
new XComment(gmxDeclaration),
new XElement("font",
new XElement("name", font.Name.Content),
new XElement("size", font.EmSize.ToString()),
new XElement("bold", BoolToString(font.Bold)),
new XElement("renderhq", "-1"),
new XElement("italic", BoolToString(font.Italic)),
new XElement("charset", font.Charset.ToString()),
new XElement("aa", font.AntiAliasing.ToString()),
new XElement("includeTTF", "0"),
new XElement("TTFName", ""),
new XElement("texgroups",
new XElement("texgroup", "0")
),
new XElement("ranges",
new XElement("range0", font.RangeStart.ToString() + "," + font.RangeEnd.ToString())
),
new XElement("glyphs"),
new XElement("kerningPairs"),
new XElement("image", font.Name.Content + ".png")
)
);
var glyphsNode = gmx.Element("font").Element("glyphs");
foreach (var i in font.Glyphs)
{
var glyphNode = new XElement("glyph");
glyphNode.Add(new XElement("character", i.Character.ToString()));
glyphNode.Add(new XElement("x", i.SourceX.ToString()));
glyphNode.Add(new XElement("y", i.SourceY.ToString()));
glyphNode.Add(new XElement("w", i.SourceWidth.ToString()));
glyphNode.Add(new XElement("h", i.SourceHeight.ToString()));
glyphNode.Add(new XElement("shift", i.Shift.ToString()));
glyphNode.Add(new XElement("offset", i.Offset.ToString()));
glyphsNode.Add(glyphNode);
}
File.WriteAllText(projFolder + "/fonts/" + font.Name.Content + ".font.gmx", gmx.ToString());
// Save font textures
worker.ExportAsPNG(font.Texture, projFolder + "/fonts/" + font.Name.Content + ".png");
}
// --------------- Generate project file ---------------
void ExportProjectFile()
{
var gmx = new XDocument(
new XComment(gmxDeclaration),
new XElement("assets")
);
// Write all resource indexes to project.gmx
WriteIndexes<UndertaleSound>(gmx.Element("assets"), "sounds", "sound", Data.Sounds, "sound", "sound\\");
WriteIndexes<UndertaleSprite>(gmx.Element("assets"), "sprites", "sprites", Data.Sprites, "sprite", "sprites\\");
WriteIndexes<UndertaleBackground>(gmx.Element("assets"), "backgrounds", "background", Data.Backgrounds, "background", "background\\");
WriteIndexes<UndertaleScript>(gmx.Element("assets"), "scripts", "scripts", Data.Scripts, "script", "scripts\\", ".gml");
WriteIndexes<UndertaleFont>(gmx.Element("assets"), "fonts", "fonts", Data.Fonts, "font", "fonts\\");
WriteIndexes<UndertaleGameObject>(gmx.Element("assets"), "objects", "objects", Data.GameObjects, "object", "objects\\");
WriteIndexes<UndertaleRoom>(gmx.Element("assets"), "rooms", "rooms", Data.Rooms, "room", "rooms\\");
File.WriteAllText(projFolder + "Export_Project.project.gmx", gmx.ToString());
}
void WriteIndexes<T>(XElement rootNode, string elementName, string attributeName, IList<T> dataList, string oneName, string resourcePath, string fileExtension = "")
{
var resourcesNode = new XElement(elementName,
new XAttribute("name", attributeName)
);
foreach (UndertaleNamedResource i in dataList)
{
var resourceNode = new XElement(oneName, resourcePath + i.Name.Content + fileExtension);
resourcesNode.Add(resourceNode);
}
rootNode.Add(resourcesNode);
}