diff --git a/UndertaleModCli/Program.UMTLibInherited.cs b/UndertaleModCli/Program.UMTLibInherited.cs index d20678554..436dc098c 100644 --- a/UndertaleModCli/Program.UMTLibInherited.cs +++ b/UndertaleModCli/Program.UMTLibInherited.cs @@ -57,7 +57,7 @@ public void EnsureDataLoaded() throw new Exception("No data file is loaded."); } - public async Task Make_New_File() + public async Task MakeNewDataFile() { // This call has no use except to suppress the "method is not doing anything async" warning await Task.Delay(1); @@ -261,17 +261,17 @@ public void AddProgress(int amount) progressValue += amount; } - public void IncProgress() + public void IncrementProgress() { progressValue++; } - public void AddProgressP(int amount) //P - Parallel (multithreaded) + public void AddProgressParallel(int amount) //P - Parallel (multithreaded) { Interlocked.Add(ref progressValue, amount); //thread-safe add operation (not the same as "lock ()") } - public void IncProgressP() + public void IncrementProgressParallel() { Interlocked.Increment(ref progressValue); //thread-safe increment } @@ -334,14 +334,14 @@ public void SyncBinding(string resourceType, bool enable) //there is no UI with any data binding } - public void SyncBinding(bool enable = false) + public void DisableAllSyncBindings() { //there is no UI with any data binding } #endregion - public void StartUpdater() + public void StartProgressBarUpdater() { if (cTokenSource is not null) Console.WriteLine("Warning - there is another progress updater task running (hangs) in the background."); @@ -351,7 +351,7 @@ public void StartUpdater() updater = Task.Run(ProgressUpdater); } - public async Task StopUpdater() //"async" because "Wait()" blocks UI thread + public async Task StopProgressBarUpdater() //"async" because "Wait()" blocks UI thread { if (cTokenSource is null) return; @@ -376,7 +376,7 @@ public void ChangeSelection(object newSelection) Selected = newSelection; } - public string PromptChooseDirectory(string prompt) + public string PromptChooseDirectory() { string path; DirectoryInfo directoryInfo; @@ -504,11 +504,11 @@ public string SimpleTextInput(string title, string label, string defaultValue, b return result; } - public async Task ClickableTextOutput(string title, string query, int resultsCount, IOrderedEnumerable>> resultsDict, bool editorDecompile, IOrderedEnumerable failedList = null) + public async Task ClickableSearchOutput(string title, string query, int resultsCount, IOrderedEnumerable>> resultsDict, bool editorDecompile, IOrderedEnumerable failedList = null) { - await ClickableTextOutput(title, query, resultsCount, resultsDict.ToDictionary(pair => pair.Key, pair => pair.Value), editorDecompile, failedList); + await ClickableSearchOutput(title, query, resultsCount, resultsDict.ToDictionary(pair => pair.Key, pair => pair.Value), editorDecompile, failedList); } - public async Task ClickableTextOutput(string title, string query, int resultsCount, IDictionary> resultsDict, bool editorDecompile, IEnumerable failedList = null) + public async Task ClickableSearchOutput(string title, string query, int resultsCount, IDictionary> resultsDict, bool editorDecompile, IEnumerable failedList = null) { await Task.Delay(1); //dummy await diff --git a/UndertaleModCli/Program.cs b/UndertaleModCli/Program.cs index 905537eb1..33e66cbd8 100644 --- a/UndertaleModCli/Program.cs +++ b/UndertaleModCli/Program.cs @@ -492,6 +492,7 @@ private static UndertaleData ReadDataFile(FileInfo datafile, WarningHandlerDeleg /// A new that can be directly passed onto a FileInfo Constructor //TODO: needs some proper testing on how it behaves on Linux/MacOS and might need to get expanded private static string RemoveQuotes(string s) + { return s.TrimStart('"').TrimEnd('"'); } diff --git a/UndertaleModCli/UndertaleModCli.csproj b/UndertaleModCli/UndertaleModCli.csproj index 399fb0317..adb9f4df8 100644 --- a/UndertaleModCli/UndertaleModCli.csproj +++ b/UndertaleModCli/UndertaleModCli.csproj @@ -8,6 +8,7 @@ annotations LatestMajor 1.0.0 + True diff --git a/UndertaleModLib/AumiIPC.cs b/UndertaleModLib/AumiIPC.cs index 8555bf2b1..0cff656f3 100644 --- a/UndertaleModLib/AumiIPC.cs +++ b/UndertaleModLib/AumiIPC.cs @@ -10,7 +10,39 @@ namespace UndertaleModLib { public struct IpcMessage_t { + /// + /// A numerical value (1 to 4 for the latest AUMI implementation). + /// + /// + /// The following table describes and explains the numerical values: + /// + /// + /// number + /// Explanation + /// + /// + /// 1 + /// Test Communication - Always returns a string that's 128 characters long. + /// + /// + /// 2 + /// Get Function By Index - Returns information about a function at a specified index in the runner. + /// + /// + /// 3 + /// Get Function By Name - Returns information about a function with a specified name. + /// + /// + /// 4 + /// Execute Code - Executes precompiled bytecode in the global context. + /// + /// + /// public short FuncID; + + /// + /// A 512 byte buffer containing information which accompanies . + /// [MarshalAs(UnmanagedType.ByValArray, SizeConst = 512)] public byte[] Buffer; @@ -30,7 +62,15 @@ public byte[] RawBytes() public struct IpcReply_t { + /// + /// A numerical value from 0 to n, where 0 means success. + /// + /// Anything other than 0 means failure, where the number specifies the actual reason. public int AUMIResult; // Always contains a value. + + /// + /// A 128 byte buffer, might not always be filled in. + /// [MarshalAs(UnmanagedType.ByValArray, SizeConst = 128)] public byte[] Buffer; diff --git a/UndertaleModLib/Compiler/BuiltinList.cs b/UndertaleModLib/Compiler/BuiltinList.cs index 20a19b79d..9b41b543e 100644 --- a/UndertaleModLib/Compiler/BuiltinList.cs +++ b/UndertaleModLib/Compiler/BuiltinList.cs @@ -105,9 +105,10 @@ public AccessorInfo(string lFunc, string lPreFunc, string lPostFunc, string rFun } } - // This is a really long list of known constants and variables, taken from code analysis - // This deserves to be in its own file for that reason... - // This will likely need to be updated with every new GameMaker version with new features + /// + /// A really long list of known Game Maker: Studio constants and variables, taken from code analysis.
+ /// Will likely need to be updated on every new Game Maker version with new features. + ///
public class BuiltinList { public Dictionary Constants = null; @@ -132,7 +133,7 @@ public BuiltinList(UndertaleData data) { Initialize(data); } - + public void Initialize(UndertaleData data) { // Functions diff --git a/UndertaleModLib/Models/UndertaleAnimationCurve.cs b/UndertaleModLib/Models/UndertaleAnimationCurve.cs index d49ef287e..55a61de53 100644 --- a/UndertaleModLib/Models/UndertaleAnimationCurve.cs +++ b/UndertaleModLib/Models/UndertaleAnimationCurve.cs @@ -7,6 +7,9 @@ namespace UndertaleModLib.Models { + /// + /// An animation curve entry in a data file. + /// [PropertyChanged.AddINotifyPropertyChangedInterface] public class UndertaleAnimationCurve : UndertaleNamedResource { @@ -16,8 +19,17 @@ public enum GraphTypeEnum : uint Unknown1 = 1 } + /// + /// The name of this animation curve. + /// public UndertaleString Name { get; set; } + + /// + /// The graph type of this animation curve. + /// public GraphTypeEnum GraphType { get; set; } + + public UndertaleSimpleList Channels { get; set; } public void Serialize(UndertaleWriter writer) @@ -25,6 +37,11 @@ public void Serialize(UndertaleWriter writer) Serialize(writer, true); } + /// + /// Serializes the data file into a specified . + /// + /// Where to serialize to. + /// Whether to include in the serialization. public void Serialize(UndertaleWriter writer, bool includeName) { if (includeName) @@ -38,6 +55,11 @@ public void Unserialize(UndertaleReader reader) Unserialize(reader, true); } + /// + /// Deserializes from a specified to the current data file. + /// + /// Where to deserialize from. + /// Whether to include in the deserialization. public void Unserialize(UndertaleReader reader, bool includeName) { if (includeName) diff --git a/UndertaleModLib/Models/UndertaleBackground.cs b/UndertaleModLib/Models/UndertaleBackground.cs index 5433d54cc..02a3eb6ad 100644 --- a/UndertaleModLib/Models/UndertaleBackground.cs +++ b/UndertaleModLib/Models/UndertaleBackground.cs @@ -8,13 +8,24 @@ namespace UndertaleModLib.Models { + /// + /// A background or tileset entry in a data file. + /// + /// For Game Maker Studio: 2, this will only ever be a tileset. For Game Maker Studio: 1, this is usually a background, + /// but is sometimes repurposed as use for a tileset as well. [PropertyChanged.AddINotifyPropertyChangedInterface] public class UndertaleBackground : UndertaleNamedResource { + /// + /// A tile id, which can be used for referencing specific tiles in a tileset. Game Maker Studio 2 only. + /// public class TileID : UndertaleObject, INotifyPropertyChanged { private uint _ID; + /// + /// The id of a specific tile. + /// public uint ID { get => _ID; set { _ID = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ID))); } } public event PropertyChangedEventHandler PropertyChanged; @@ -30,21 +41,85 @@ public void Unserialize(UndertaleReader reader) } } + /// + /// The name of the background. + /// public UndertaleString Name { get; set; } + + /// + /// Whether the background should be transparent. + /// public bool Transparent { get; set; } + + /// + /// Whether the background should get smoothed. + /// public bool Smooth { get; set; } + + /// + /// Whether to preload the background. + /// public bool Preload { get; set; } + + /// + /// The this background uses. + /// public UndertaleTexturePageItem Texture { get; set; } + + + /// + /// TODO: Functionality currently unknown. + /// public uint GMS2UnknownAlways2 { get; set; } = 2; + + /// + /// The tile width of the tileset. Game Maker Studio 2 only. + /// public uint GMS2TileWidth { get; set; } = 32; + + /// + /// The tile height of the tileset. Game Maker Studio 2 only. + /// public uint GMS2TileHeight { get; set; } = 32; + + /// + /// The output border X of the tileset. Game Maker Studio 2 only. + /// public uint GMS2OutputBorderX { get; set; } = 2; + + /// + /// The output Border Y of the tileset. Game Maker Studio 2 only. + /// public uint GMS2OutputBorderY { get; set; } = 2; + + /// + /// The amount of columns this tileset has. + /// public uint GMS2TileColumns { get; set; } = 32; + + /// + /// The number of frames of the tileset animation. + /// public uint GMS2ItemsPerTileCount { get; set; } = 1; + + /// + /// The amount of tiles this tileset has. + /// public uint GMS2TileCount { get; set; } = 1024; + + /// + /// TODO: Functionality currently unknown. + /// public uint GMS2UnknownAlwaysZero { get; set; } = 0; - public long GMS2FrameLength { get; set; } = 66666; // time for each frame (in microseconds seemingly) + + /// + /// The time for each frame in microseconds. + /// + public long GMS2FrameLength { get; set; } = 66666; + + /// + /// All tile ids of this tileset. Game Maker Studio 2 only. + /// public List GMS2TileIds { get; set; } = new List(); public void Serialize(UndertaleWriter writer) diff --git a/UndertaleModLib/Models/UndertaleCode.cs b/UndertaleModLib/Models/UndertaleCode.cs index 9bdbeaad6..02ea9b921 100644 --- a/UndertaleModLib/Models/UndertaleCode.cs +++ b/UndertaleModLib/Models/UndertaleCode.cs @@ -12,8 +12,15 @@ namespace UndertaleModLib.Models { + /// + /// A bytecode instruction. + /// public class UndertaleInstruction : UndertaleObject { + /// + /// Possible opcodes an instruction can use. + /// + //TODO: document all these. i ain't smart enough to understand these. public enum Opcode : byte { Conv = 0x07, // Push((Types.Second)Pop) // DoubleTypeInstruction @@ -51,6 +58,9 @@ public enum Opcode : byte Break = 0xFF, // TODO: Several sub-opcodes in GMS 2.3 } + /// + /// Possible types an instruction can be. + /// public enum InstructionType { SingleTypeInstruction, @@ -63,31 +73,37 @@ public enum InstructionType BreakInstruction } + /// + /// Determines the instruction type of an opcode and returns it. + /// + /// The opcode to get the instruction type of. + /// The instruction type of the supplied opcode. + /// For unknown opcodes. public static InstructionType GetInstructionType(Opcode op) { return op switch { - Opcode.Neg or Opcode.Not or Opcode.Dup or - Opcode.Ret or Opcode.Exit or Opcode.Popz or - Opcode.CallV + Opcode.Neg or Opcode.Not or Opcode.Dup or + Opcode.Ret or Opcode.Exit or Opcode.Popz or + Opcode.CallV => InstructionType.SingleTypeInstruction, - Opcode.Conv or Opcode.Mul or Opcode.Div or - Opcode.Rem or Opcode.Mod or Opcode.Add or - Opcode.Sub or Opcode.And or Opcode.Or or - Opcode.Xor or Opcode.Shl or Opcode.Shr + Opcode.Conv or Opcode.Mul or Opcode.Div or + Opcode.Rem or Opcode.Mod or Opcode.Add or + Opcode.Sub or Opcode.And or Opcode.Or or + Opcode.Xor or Opcode.Shl or Opcode.Shr => InstructionType.DoubleTypeInstruction, Opcode.Cmp => InstructionType.ComparisonInstruction, - Opcode.B or Opcode.Bt or Opcode.Bf or - Opcode.PushEnv or Opcode.PopEnv + Opcode.B or Opcode.Bt or Opcode.Bf or + Opcode.PushEnv or Opcode.PopEnv => InstructionType.GotoInstruction, Opcode.Pop => InstructionType.PopInstruction, - Opcode.Push or Opcode.PushLoc or Opcode.PushGlb or - Opcode.PushBltn or Opcode.PushI + Opcode.Push or Opcode.PushLoc or Opcode.PushGlb or + Opcode.PushBltn or Opcode.PushI => InstructionType.PushInstruction, Opcode.Call => InstructionType.CallInstruction, @@ -97,6 +113,7 @@ Opcode.PushBltn or Opcode.PushI }; } + public enum DataType : byte { Double, @@ -958,18 +975,42 @@ public static UndertaleInstruction.DataType FromOpcodeParam(string type) } } + /// + /// A code entry in a data file. + /// [PropertyChanged.AddINotifyPropertyChangedInterface] public class UndertaleCode : UndertaleNamedResource, UndertaleObjectWithBlobs { + /// + /// The name of the code entry. + /// public UndertaleString Name { get; set; } + + public uint Length { get; set; } - public uint LocalsCount { get; set; } // Warning: Actually a ushort, left this way for compatibility + + + /// + /// The amount of local variables this code entry has.
+ /// Warning: This is actually a ushort internally, it's an uint here for compatibility. + ///
+ public uint LocalsCount { get; set; } + + /// + /// The amount of arguments this code entry accepts. + /// public ushort ArgumentsCount { get; set; } + + public bool WeirdLocalsFlag { get; set; } public uint Offset { get; set; } + + + /// + /// A list of bytecode instructions this code entry has. + /// public List Instructions { get; } = new List(); public bool WeirdLocalFlag { get; set; } - public UndertaleCode ParentEntry { get; set; } = null; public List ChildEntries { get; set; } = new List(); @@ -1117,6 +1158,10 @@ public UndertaleInstruction GetInstructionBeforeAddress(uint address) return null; } + /// + /// Finds and returns a list of all variables this code entry references. + /// + /// A list of all variables this code entry references. public IList FindReferencedVars() { List vars = new List(); @@ -1129,23 +1174,41 @@ public IList FindReferencedVars() return vars; } + /// + /// Finds and returns a list of all local variables this code entry references. + /// + /// A list of all local variables this code entry references. public IList FindReferencedLocalVars() { return FindReferencedVars().Where((x) => x.InstanceType == UndertaleInstruction.InstanceType.Local).ToList(); } + /// + /// Append instructions at the end of this code entry. + /// + /// The instructions to append. public void Append(IList instructions) { Instructions.AddRange(instructions); UpdateAddresses(); } + /// + /// Replaces all instructions currently existing in this code entry with another set of instructions. + /// + /// The new instructions for this code entry. public void Replace(IList instructions) { Instructions.Clear(); Append(instructions); } + /// + /// Append GML instructions at the end of this code entry. + /// + /// The GML code to append. + /// From which data file the GML code is coming from. + /// if the GML code does not compile or if there's an error writing the code to the profile entry. public void AppendGML(string gmlCode, UndertaleData data) { CompileContext context = Compiler.Compiler.CompileGMLText(gmlCode, data, this); @@ -1175,6 +1238,12 @@ public void AppendGML(string gmlCode, UndertaleData data) } } + /// + /// Replaces all instructions currently existing in this code entry with another set of GML instructions. + /// + /// The new GML code for this code entry. + /// From which data file the GML code is coming from. + /// If the GML code does not compile or if there's an error writing the code to the profile entry. public void ReplaceGML(string gmlCode, UndertaleData data) { CompileContext context = Compiler.Compiler.CompileGMLText(gmlCode, data, this); diff --git a/UndertaleModLib/Models/UndertaleEmbeddedAudio.cs b/UndertaleModLib/Models/UndertaleEmbeddedAudio.cs index f7283348b..cafca30e5 100644 --- a/UndertaleModLib/Models/UndertaleEmbeddedAudio.cs +++ b/UndertaleModLib/Models/UndertaleEmbeddedAudio.cs @@ -9,10 +9,20 @@ namespace UndertaleModLib.Models { + /// + /// An embedded audio entry in a data file. + /// [PropertyChanged.AddINotifyPropertyChangedInterface] public class UndertaleEmbeddedAudio : UndertaleNamedResource, PaddedObject { + /// + /// The name of the embedded audio entry. + /// public UndertaleString Name { get; set; } + + /// + /// The audio data of the embedded audio entry. + /// public byte[] Data { get; set; } = Array.Empty(); public void Serialize(UndertaleWriter writer) diff --git a/UndertaleModLib/Models/UndertaleEmbeddedTexture.cs b/UndertaleModLib/Models/UndertaleEmbeddedTexture.cs index f33a5a5ce..ac3fd9c41 100644 --- a/UndertaleModLib/Models/UndertaleEmbeddedTexture.cs +++ b/UndertaleModLib/Models/UndertaleEmbeddedTexture.cs @@ -12,13 +12,33 @@ namespace UndertaleModLib.Models { + /// + /// An embedded texture entry in the data file. + /// [PropertyChanged.AddINotifyPropertyChangedInterface] public class UndertaleEmbeddedTexture : UndertaleNamedResource { + /// + /// The name of the embedded texture entry. + /// public UndertaleString Name { get; set; } + + /// + /// Whether or not this embedded texture is scaled. + /// public uint Scaled { get; set; } = 0; + + /// + /// The amount of generated mipmap levels. + /// public uint GeneratedMips { get; set; } + + public uint TextureBlockSize { get; set; } + + /// + /// The texture data in the embedded image. + /// public TexData TextureData { get; set; } = new TexData(); public void Serialize(UndertaleWriter writer) @@ -41,6 +61,10 @@ public void Unserialize(UndertaleReader reader) TextureData = reader.ReadUndertaleObjectPointer(); } + /// + /// TODO! + /// + /// Where to serialize to. public void SerializeBlob(UndertaleWriter writer) { // padding @@ -50,6 +74,10 @@ public void SerializeBlob(UndertaleWriter writer) writer.WriteUndertaleObject(TextureData); } + /// + /// TODO! + /// + /// Where to deserialize from. public void UnserializeBlob(UndertaleReader reader) { while (reader.Position % 0x80 != 0) @@ -68,10 +96,16 @@ public override string ToString() return Name.Content + " (" + GetType().Name + ")"; } + /// + /// Texture data in an . + /// public class TexData : UndertaleObject, INotifyPropertyChanged { private byte[] _TextureBlob; + /// + /// The image data of the texture. + /// public byte[] TextureBlob { get => _TextureBlob; set { _TextureBlob = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(TextureBlob))); } } public event PropertyChangedEventHandler PropertyChanged; diff --git a/UndertaleModLib/Models/UndertaleFont.cs b/UndertaleModLib/Models/UndertaleFont.cs index b802a85a5..f51eb6b63 100644 --- a/UndertaleModLib/Models/UndertaleFont.cs +++ b/UndertaleModLib/Models/UndertaleFont.cs @@ -7,36 +7,130 @@ namespace UndertaleModLib.Models { + /// + /// A font entry of a data file. + /// [PropertyChanged.AddINotifyPropertyChangedInterface] public class UndertaleFont : UndertaleNamedResource { + /// + /// The name of the font. + /// public UndertaleString Name { get; set; } + + /// + /// The display name of the font. + /// public UndertaleString DisplayName { get; set; } + + /// + /// Whether the Em size is a float. + /// public bool EmSizeIsFloat { get; set; } + + /// + /// The font size in Ems. In Game Maker: Studio 2.3 and above, this is a float instead. + /// public uint EmSize { get; set; } + + /// + /// Whether to display the font in bold. + /// public bool Bold { get; set; } + + /// + /// Whether to display the font in italics + /// public bool Italic { get; set; } + + /// + /// The start of the character range for this font. + /// public ushort RangeStart { get; set; } + + /// + /// TODO: Currently unknown value. + /// public byte Charset { get; set; } + + /// + /// The level of anti-aliasing that is applied. 0 for none, Game Maker: Studio 2 has 1 for on, while + /// Game Maker Studio: 1 and earlier have values 1-3 for different anti-aliasing levels. + /// public byte AntiAliasing { get; set; } + + /// + /// The end of the character range for this font. + /// public uint RangeEnd { get; set; } + + /// + /// The object that contains the texture for this font. + /// public UndertaleTexturePageItem Texture { get; set; } + + /// + /// The x scale this font uses. + /// public float ScaleX { get; set; } + + /// + /// The y scale this font uses. + /// public float ScaleY { get; set; } + + /// + /// TODO: currently unknown, needs investigation. + /// public uint Ascender { get; set; } + + /// + /// The glyphs that this font uses. + /// public UndertalePointerList Glyphs { get; private set; } = new UndertalePointerList(); + + /// + /// TODO: currently unknown, needs investigation. Exists since bytecode 17, but seems to be only get checked since 2022.2+. + /// public int AscenderOffset { get; set; } + + /// + /// Glyphs that a font can use. + /// [PropertyChanged.AddINotifyPropertyChangedInterface] public class Glyph : UndertaleObject { + /// + /// The character for the glyph. + /// public ushort Character { get; set; } + + /// + /// The x position in the where the glyph can be found. + /// public ushort SourceX { get; set; } + + /// + /// The y position in the where the glyph can be found. + /// public ushort SourceY { get; set; } + + /// + /// The width of the glyph. + /// public ushort SourceWidth { get; set; } + + /// + /// The height of the glyph. + /// public ushort SourceHeight { get; set; } + + + //TODO: From here on out is some kerning related stuff I don't know. public short Shift { get; set; } public short Offset { get; set; } + public UndertaleSimpleListShort Kerning { get; set; } = new UndertaleSimpleListShort(); public void Serialize(UndertaleWriter writer) diff --git a/UndertaleModLib/Models/UndertaleGameObject.cs b/UndertaleModLib/Models/UndertaleGameObject.cs index eb39fa19a..ffe9bb03e 100644 --- a/UndertaleModLib/Models/UndertaleGameObject.cs +++ b/UndertaleModLib/Models/UndertaleGameObject.cs @@ -9,39 +9,141 @@ namespace UndertaleModLib.Models { + //TODO: shouldn't this be inside of the UGameObject class? + /// + /// Collision shapes an can use. + /// public enum CollisionShapeFlags : uint { + /// + /// A circular collision shape. + /// Circle = 0, + /// + /// A rectangular collision shape. + /// Box = 1, + /// + /// A custom polygonal collision shape. + /// Custom = 2, } + /// + /// A game object in a data file. + /// public class UndertaleGameObject : UndertaleNamedResource, INotifyPropertyChanged { public UndertaleResourceById _Sprite = new UndertaleResourceById(); public UndertaleResourceById _ParentId = new UndertaleResourceById(); public UndertaleResourceById _TextureMaskId = new UndertaleResourceById(); + /// + /// The name of the game object. + /// public UndertaleString Name { get; set; } + + /// + /// The sprite this game object uses. + /// public UndertaleSprite Sprite { get => _Sprite.Resource; set { _Sprite.Resource = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Sprite))); } } + + /// + /// Whether the game object is visible. + /// public bool Visible { get; set; } = true; + + /// + /// Whether the game object is solid. + /// public bool Solid { get; set; } = false; + + /// + /// The depth level of the game object. + /// public int Depth { get; set; } = 0; + + /// + /// Whether the game object is persistent. + /// public bool Persistent { get; set; } = false; + + /// + /// The parent game object this is inheriting from. + /// public UndertaleGameObject ParentId { get => _ParentId.Resource; set { _ParentId.Resource = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ParentId))); } } + + /// + /// The texture mask this game object is using. + /// public UndertaleSprite TextureMaskId { get => _TextureMaskId.Resource; set { _TextureMaskId.Resource = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(TextureMaskId))); } } + + #region Physics related properties + /// + /// Whether this object uses Game Maker physics. + /// public bool UsesPhysics { get; set; } = false; + + /// + /// Whether this game object should act as a sensor fixture. + /// public bool IsSensor { get; set; } = false; + + /// + /// The collision shape the game object should use. + /// public CollisionShapeFlags CollisionShape { get; set; } = CollisionShapeFlags.Circle; + + /// + /// The physics density of the game object. + /// public float Density { get; set; } = 0.5f; + + /// + /// The physics restitution of the game object. + /// public float Restitution { get; set; } = 0.1f; + + /// + /// The physics collision group this game object belongs to. + /// public uint Group { get; set; } = 0; + + /// + /// The physics linear damping this game object uses. + /// public float LinearDamping { get; set; } = 0.1f; + + /// + /// The physics angular damping this game object uses. + /// public float AngularDamping { get; set; } = 0.1f; + + /// + /// The physics friction this game object uses. + /// public float Friction { get; set; } = 0.2f; + + /// + /// Whether this game object should start awake in the physics simulation. + /// public bool Awake { get; set; } = false; + + /// + /// Whether this game object is kinematic. + /// public bool Kinematic { get; set; } = false; + + /// + /// The vertices used for a of type . + /// public List PhysicsVertices { get; private set; } = new List(); + + #endregion + + /// + /// All the events that this game object has. + /// public UndertalePointerList> Events { get; private set; } = new UndertalePointerList>(); public event PropertyChangedEventHandler PropertyChanged; @@ -133,6 +235,8 @@ public void Unserialize(UndertaleReader reader) Events = reader.ReadUndertaleObject>>(); } + //TODO: what do all these eventhandlers do? can't find any references right now. + public UndertaleCode EventHandlerFor(EventType type, uint subtype, IList strg, IList codelist, IList localslist) { Event subtypeObj = Events[(int)type].Where((x) => x.EventSubtype == subtype).FirstOrDefault(); @@ -250,11 +354,23 @@ public override string ToString() return Name.Content + " (" + GetType().Name + ")"; } + /// + /// Generic events that an uses. + /// [PropertyChanged.AddINotifyPropertyChangedInterface] public class Event : UndertaleObject { - public uint EventSubtype { get; set; } // (the same as the ID at the end of name) - public UndertalePointerList Actions { get; private set; } = new UndertalePointerList(); // seems to always have 1 entry, maybe the games using drag-and-drop code are different + /// + /// The subtype of this event. + /// + /// Game Maker suffixes the action names with this id. + public uint EventSubtype { get; set; } + + /// + /// The available actions that will be performed for this event. + /// + /// This seems to always have 1 entry, it would need testing if maybe the games using drag-and-drop code are different + public UndertalePointerList Actions { get; private set; } = new UndertalePointerList(); public EventSubtypeKey EventSubtypeKey { @@ -305,12 +421,15 @@ public void Unserialize(UndertaleReader reader) } } + /// + /// An action in an event. + /// public class EventAction : UndertaleObject, INotifyPropertyChanged { // All the unknown values seem to be provided for compatibility only - in older versions of GM:S they stored the drag and drop blocks, // but newer versions compile them down to GML bytecode anyway // Possible meaning of values: https://github.com/WarlockD/GMdsam/blob/26aefe3e90a7a7a1891cb83f468079546f32b4b7/GMdsam/GameMaker/ChunkTypes.cs#L466 - + // Note from the future: these aren't always these values... public uint LibID { get; set; } // always 1 @@ -322,6 +441,10 @@ public class EventAction : UndertaleObject, INotifyPropertyChanged public uint ExeType { get; set; } // always 2 public UndertaleString ActionName { get; set; } // always "" private UndertaleResourceById _CodeId = new UndertaleModLib.UndertaleResourceById(); + + /// + /// The code entry that gets executed. + /// public UndertaleCode CodeId { get => _CodeId.Resource; set { _CodeId.Resource = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CodeId))); } } public uint ArgumentCount { get; set; } // always 1 public int Who { get; set; } // always -1 @@ -368,10 +491,20 @@ public void Unserialize(UndertaleReader reader) } } + /// + /// Class representing a physics vertex used for a of type . + /// [PropertyChanged.AddINotifyPropertyChangedInterface] public class UndertalePhysicsVertex : UndertaleObject { + /// + /// The x position of the vertex. + /// public float X { get; set; } + + /// + /// The y position of the vertex. + /// public float Y { get; set; } public void Serialize(UndertaleWriter writer) @@ -388,283 +521,1065 @@ public void Unserialize(UndertaleReader reader) } } + /// + /// The types an from an can be. + /// + /// Note, that subtypes exist as well. public enum EventType : uint { - Create = 0, // no subtypes, always 0 - Destroy = 1, // no subtypes, always 0 - Alarm = 2, // subtype is alarm id (0-11) + /// + /// A creation event type. Has no subtypes, it's always 0 + /// + Create = 0, + /// + /// A destroy event type. Has no subtypes, it's always 0. + /// + Destroy = 1, + /// + /// An alarm event type. The subtype is 0-11, depending on the alarm id. + /// + Alarm = 2, + /// + /// A step event type. The subtype is . + /// Step = 3, // subtype is EventSubtypeStep - Collision = 4, // subtype is other game object ID - Keyboard = 5, // subtype is key ID, see EventSubtypeKey + /// + /// A collision event type. The subtype is the other 's id. + /// + Collision = 4, + /// + /// A key down event type. The subtype is the key id, see . + /// + Keyboard = 5, + /// + /// A mouse event type. The subtype is . + /// Mouse = 6, // subtype is EventSubtypeMouse - Other = 7, // subtype is EventSubtypeOther - Draw = 8, // subtype is EventSubtypeDraw - KeyPress = 9, // subtype is key ID, see EventSubtypeKey - KeyRelease = 10, // subtype is key ID, see EventSubtypeKey + /// + /// A miscellaneous event type. The subtype is . + /// + Other = 7, + /// + /// A draw event type. The subtype is . + /// + Draw = 8, + /// + /// A key pressed event type. The subtype is the key id, see . + /// + KeyPress = 9, + /// + /// A key released event type. The subtype is the key id, see . + /// + KeyRelease = 10, + /// + /// A trigger event type. Only used in Pre- Game Maker: Studio. + /// Trigger = 11, // no subtypes, always 0 - CleanUp = 12, // no subtypes, always 0 - Gesture = 13, // subtype is EventSubtypeGesture + /// + /// A cleanup event type. Has no subtypes, always 0. + /// + CleanUp = 12, + /// + /// A gesture event type. The subtype is . + /// + Gesture = 13, + /// + /// A pre-create event type. Unknown subtype. TODO? + /// PreCreate = 14 } + /// + /// The subtypes for . + /// public enum EventSubtypeStep : uint { + /// + /// Normal step event. + /// Step = 0, + /// + /// The begin step event. + /// BeginStep = 1, + /// + /// The end step event. + /// EndStep = 2, } + /// + /// The subtypes for . + /// public enum EventSubtypeDraw : uint { + /// + /// The draw event. + /// Draw = 0, + /// + /// The draw GUI event. + /// DrawGUI = 64, + /// + /// The resize event. + /// Resize = 65, + /// + /// The draw begin event. + /// DrawBegin = 72, + /// + /// The draw end event. + /// DrawEnd = 73, + /// + /// The draw GUI begin event. + /// DrawGUIBegin = 74, + /// + /// The draw GUI end event. + /// DrawGUIEnd = 75, + /// + /// The pre-draw event. + /// PreDraw = 76, + /// + /// The post-draw event. + /// PostDraw = 77, } + /// + /// The subtypes for , and . + /// public enum EventSubtypeKey : uint { // if doesn't match any of the below, then it's probably just chr(value) + + /// + /// Keycode representing no key. + /// vk_nokey = 0, + /// + /// Keycode representing that any key. + /// vk_anykey = 1, + /// + /// Keycode representing Backspace. + /// vk_backspace = 8, + /// + /// Keycode representing Tab. + /// vk_tab = 9, + /// + /// Keycode representing Return. + /// vk_return = 13, + /// + /// Keycode representing Enter. + /// vk_enter = 13, + /// + /// Keycode representing any Shift key. + /// vk_shift = 16, + /// + /// Keycode representing any Control key. + /// vk_control = 17, + /// + /// Keycode representing any Alt key. + /// vk_alt = 18, + /// + /// Keycode representing the Pause key. + /// vk_pause = 19, + /// + /// Keycode representing the Escape key. + /// vk_escape = 27, + /// + /// Keycode representing the Space key. + /// vk_space = 32, + /// + /// Keycode representing PageUp. + /// vk_pageup = 33, + /// + /// Keycode representing PageDown. + /// vk_pagedown = 34, + /// + /// Keycode representing the End key. + /// vk_end = 35, + /// + /// Keycode representing the Home key. + /// vk_home = 36, + /// + /// Keycode representing the left arrow key. + /// vk_left = 37, + /// + /// Keycode representing the up arrow key. + /// vk_up = 38, + /// + /// Keycode representing the right arrow key. + /// vk_right = 39, + /// + /// Keycode representing the down arrow key. + /// vk_down = 40, + /// + /// Keycode representing the PrintScreen key. + /// vk_printscreen = 44, + /// + /// Keycode representing the Insert key. + /// vk_insert = 45, + /// + /// Keycode representing the Delete key. + /// vk_delete = 46, + /// + /// Keycode representing the 0 key. + /// Digit0 = 48, + /// + /// Keycode representing the 1 key. + /// Digit1 = 49, + /// + /// Keycode representing the 2 key. + /// Digit2 = 50, + /// + /// Keycode representing the 3 key. + /// Digit3 = 51, + /// + /// Keycode representing the 4 key. + /// Digit4 = 52, + /// + /// Keycode representing the 5 key. + /// Digit5 = 53, + /// + /// Keycode representing the 6 key. + /// Digit6 = 54, + /// + /// Keycode representing the 7 key. + /// Digit7 = 55, + /// + /// Keycode representing the 8 key. + /// Digit8 = 56, + /// + /// Keycode representing the 9 key. + /// Digit9 = 57, + /// + /// Keycode representing the A key. + /// A = 65, + /// + /// Keycode representing the B key. + /// B = 66, + /// + /// Keycode representing the C key. + /// C = 67, + /// + /// Keycode representing the D key. + /// D = 68, + /// + /// Keycode representing the E key. + /// E = 69, + /// + /// Keycode representing the F key. + /// F = 70, + /// + /// Keycode representing the G key. + /// G = 71, + /// + /// Keycode representing the H key. + /// H = 72, + /// + /// Keycode representing the I key. + /// I = 73, + /// + /// Keycode representing the J key. + /// J = 74, + /// + /// Keycode representing the K key. + /// K = 75, + /// + /// Keycode representing the L key. + /// L = 76, + /// + /// Keycode representing the M key. + /// M = 77, + /// + /// Keycode representing the N key. + /// N = 78, + /// + /// Keycode representing the O key. + /// O = 79, + /// + /// Keycode representing the P key. + /// P = 80, + /// + /// Keycode representing the Q key. + /// Q = 81, + /// + /// Keycode representing the R key. + /// R = 82, + /// + /// Keycode representing the S key. + /// S = 83, + /// + /// Keycode representing the T key. + /// T = 84, + /// + /// Keycode representing the U key. + /// U = 85, + /// + /// Keycode representing the V key. + /// V = 86, + /// + /// Keycode representing the W key. + /// W = 87, + /// + /// Keycode representing the X key. + /// X = 88, + /// + /// Keycode representing the Y key. + /// Y = 89, + /// + /// Keycode representing the Z key. + /// Z = 90, + /// + /// Keycode representing the 0 key on the numeric keypad. + /// vk_numpad0 = 96, + /// + /// Keycode representing the 1 key on the numeric keypad. + /// vk_numpad1 = 97, + /// + /// Keycode representing the 2 key on the numeric keypad. + /// vk_numpad2 = 98, + /// + /// Keycode representing the 3 key on the numeric keypad. + /// vk_numpad3 = 99, + /// + /// Keycode representing the 4 key on the numeric keypad. + /// vk_numpad4 = 100, + /// + /// Keycode representing the 5 key on the numeric keypad. + /// vk_numpad5 = 101, + /// + /// Keycode representing the 6 key on the numeric keypad. + /// vk_numpad6 = 102, + /// + /// Keycode representing the 7 key on the numeric keypad. + /// vk_numpad7 = 103, + /// + /// Keycode representing the 8 key on the numeric keypad. + /// vk_numpad8 = 104, + /// + /// Keycode representing the 9 key on the numeric keypad. + /// vk_numpad9 = 105, + /// + /// Keycode representing the Multiply key on the numeric keypad. + /// vk_multiply = 106, + /// + /// Keycode representing the Add key on the numeric keypad. + /// vk_add = 107, + /// + /// Keycode representing the Subtract key on the numeric keypad. + /// vk_subtract = 109, + /// + /// Keycode representing the Decimal Dot key on the numeric keypad. + /// vk_decimal = 110, + /// + /// Keycode representing the Divide key on the numeric keypad. + /// vk_divide = 111, + /// + /// Keycode representing the F1 key. + /// vk_f1 = 112, + /// + /// Keycode representing the F2 key. + /// vk_f2 = 113, + /// + /// Keycode representing the F3 key. + /// vk_f3 = 114, + /// + /// Keycode representing the F4 key. + /// vk_f4 = 115, + /// + /// Keycode representing the F5 key. + /// vk_f5 = 116, + /// + /// Keycode representing the F6 key. + /// vk_f6 = 117, + /// + /// Keycode representing the F7 key. + /// vk_f7 = 118, + /// + /// Keycode representing the F8 key. + /// vk_f8 = 119, + /// + /// Keycode representing the F9 key. + /// vk_f9 = 120, + /// + /// Keycode representing the F10 key. + /// vk_f10 = 121, + /// + /// Keycode representing the F11 key. + /// vk_f11 = 122, + /// + /// Keycode representing the F12 key. + /// vk_f12 = 123, + /// + /// Keycode representing the left Shift key. + /// vk_lshift = 160, + /// + /// Keycode representing the right Shift key. + /// vk_rshift = 161, + /// + /// Keycode representing the left Control key. + /// vk_lcontrol = 162, + /// + /// Keycode representing the right Control key. + /// vk_rcontrol = 163, + /// + /// Keycode representing the left Alt key. + /// vk_lalt = 164, + /// + /// Keycode representing the right Alt key. + /// vk_ralt = 165, } - + + /// + /// The subtypes for . + /// public enum EventSubtypeMouse : uint { + /// + /// The left-mouse button down event. + /// LeftButton = 0, + /// + /// The right-mouse button down event. + /// RightButton = 1, + /// + /// The middle-mouse button down event. + /// MiddleButton = 2, + /// + /// The no-mouse input event. + /// NoButton = 3, + /// + /// The left-mouse button pressed event. + /// LeftPressed = 4, + /// + /// The right-mouse button pressed event. + /// RightPressed = 5, + /// + /// The middle-mouse button pressed event. + /// MiddlePressed = 6, + /// + /// The left-mouse button released event. + /// LeftReleased = 7, + /// + /// The right-mouse button released event. + /// RightReleased = 8, + /// + /// The middle-mouse button released event. + /// MiddleReleased = 9, + /// + /// The mouse enter event. + /// MouseEnter = 10, + /// + /// The mouse leave event. + /// MouseLeave = 11, + /// + /// The Joystick1 left event. Is only used in Pre-Game Maker: Studio. + /// Joystick1Left = 16, + /// + /// The Joystick1 right event. Is only used in Pre-Game Maker: Studio. + /// Joystick1Right = 17, + /// + /// The Joystick1 up event. Is only used in Pre-Game Maker: Studio. + /// Joystick1Up = 18, + /// + /// The Joystick1 down event. Is only used in Pre-Game Maker: Studio. + /// Joystick1Down = 19, + /// + /// The Joystick1 button1 event. Is only used in Pre-Game Maker: Studio. + /// Joystick1Button1 = 21, + /// + /// The Joystick1 button2 event. Is only used in Pre-Game Maker: Studio. + /// Joystick1Button2 = 22, + /// + /// The Joystick1 button3 event. Is only used in Pre-Game Maker: Studio. + /// Joystick1Button3 = 23, + /// + /// The Joystick1 button4 event. Is only used in Pre-Game Maker: Studio. + /// Joystick1Button4 = 24, + /// + /// The Joystick1 button5 event. Is only used in Pre-Game Maker: Studio. + /// Joystick1Button5 = 25, + /// + /// The Joystick1 button6 event. Is only used in Pre-Game Maker: Studio. + /// Joystick1Button6 = 26, + /// + /// The Joystick1 button7 event. Is only used in Pre-Game Maker: Studio. + /// Joystick1Button7 = 27, + /// + /// The Joystick1 button8 event. Is only used in Pre-Game Maker: Studio. + /// Joystick1Button8 = 28, + /// + /// The Joystick2 left event. Is only used in Pre-Game Maker: Studio. + /// Joystick2Left = 31, + /// + /// The Joystick2 right event. Is only used in Pre-Game Maker: Studio. + /// Joystick2Right = 32, + /// + /// The Joystick2 up event. Is only used in Pre-Game Maker: Studio. + /// Joystick2Up = 33, + /// + /// The Joystick2 down event. Is only used in Pre-Game Maker: Studio. + /// Joystick2Down = 34, + /// + /// The Joystick2 button1 event. Is only used in Pre-Game Maker: Studio. + /// Joystick2Button1 = 36, + /// + /// The Joystick2 button2 event. Is only used in Pre-Game Maker: Studio. + /// Joystick2Button2 = 37, + /// + /// The Joystick2 button3 event. Is only used in Pre-Game Maker: Studio. + /// Joystick2Button3 = 38, + /// + /// The Joystick2 button4 event. Is only used in Pre-Game Maker: Studio. + /// Joystick2Button4 = 39, + /// + /// The Joystick2 button5 event. Is only used in Pre-Game Maker: Studio. + /// Joystick2Button5 = 40, + /// + /// The Joystick2 button6 event. Is only used in Pre-Game Maker: Studio. + /// Joystick2Button6 = 41, + /// + /// The Joystick2 button7 event. Is only used in Pre-Game Maker: Studio. + /// Joystick2Button7 = 42, + /// + /// The Joystick2 button8 event. Is only used in Pre-Game Maker: Studio. + /// Joystick2Button8 = 43, + /// + /// The global left-mouse button down event. + /// GlobLeftButton = 50, + /// + /// The global right-mouse button down event. + /// GlobRightButton = 51, + /// + /// The global middle-mouse button down event. + /// GlobMiddleButton = 52, + /// + /// The global left-mouse button pressed event. + /// GlobLeftPressed = 53, + /// + /// The global right-mouse button pressed event. + /// GlobRightPressed = 54, + /// + /// The global middle-mouse button pressed event. + /// GlobMiddlePressed = 55, + /// + /// The global left-mouse button released event. + /// GlobLeftReleased = 56, + /// + /// The global right-mouse button released event. + /// GlobRightReleased = 57, + /// + /// The global middle-mouse button released event. + /// GlobMiddleReleased = 58, + /// + /// The mouse-wheel up event. + /// MouseWheelUp = 60, + /// + /// The mouse-wheel down event. + /// MouseWheelDown = 61, } - + + /// + /// The subtypes for . + /// public enum EventSubtypeOther : uint { + /// + /// The outside room event. + /// OutsideRoom = 0, + /// + /// The intersect boundary event. + /// IntersectBoundary = 1, + /// + /// The game start event. + /// GameStart = 2, + /// + /// The game end event. + /// GameEnd = 3, + /// + /// The room start event. + /// RoomStart = 4, + /// + /// The room end event. + /// RoomEnd = 5, + /// + /// The "No More Lives" event. Only used in Game Maker Studio: 1 and earlier. + /// NoMoreLives = 6, + /// + /// The animation end event. + /// AnimationEnd = 7, + /// + /// The path ended event. + /// EndOfPath = 8, + /// + /// The "No More Health" event. Only used in Game Maker Studio: 1 and earlier. + /// NoMoreHealth = 9, + #region User events + /// + /// The User 0 event. + /// User0 = 10, + /// + /// The User 1 event. + /// User1 = 11, + /// + /// The User 2 event. + /// User2 = 12, + /// + /// The User 3 event. + /// User3 = 13, + /// + /// The User 4 event. + /// User4 = 14, + /// + /// The User 5 event. + /// User5 = 15, + /// + /// The User 6 event. + /// User6 = 16, + /// + /// The User 7 event. + /// User7 = 17, + /// + /// The User 8 event. + /// User8 = 18, + /// + /// The User 9 event. + /// User9 = 19, + /// + /// The User 10 event. + /// User10 = 20, + /// + /// The User 11 event. + /// User11 = 21, + /// + /// The User 12 event. + /// User12 = 22, + /// + /// The User 13 event. + /// User13 = 23, + /// + /// The User 14 event. + /// User14 = 24, + /// + /// The User 15 event. + /// User15 = 25, + /// + /// The User 16 event. + /// User16 = 26, + #endregion + #region View events + /// + /// The Outside View 0 event. + /// OutsideView0 = 40, + /// + /// The Outside View 1 event. + /// OutsideView1 = 41, + /// + /// The Outside View 2 event. + /// OutsideView2 = 42, + /// + /// The Outside View 3 event. + /// OutsideView3 = 43, + /// + /// The Outside View 4 event. + /// OutsideView4 = 44, + /// + /// The Outside View 5 event. + /// OutsideView5 = 45, + /// + /// The Outside View 6 event. + /// OutsideView6 = 46, + /// + /// The Outside View 7 event. + /// OutsideView7 = 47, + /// + /// The Intersect View 0 Boundary event. + /// BoundaryView0 = 50, + /// + /// The Intersect View 1 Boundary event. + /// BoundaryView1 = 51, + /// + /// The Intersect View 2 Boundary event. + /// BoundaryView2 = 52, + /// + /// The Intersect View 3 Boundary event. + /// BoundaryView3 = 53, + /// + /// The Intersect View 4 Boundary event. + /// BoundaryView4 = 54, + /// + /// The Intersect View 5 Boundary event. + /// BoundaryView5 = 55, + /// + /// The Intersect View 6 Boundary event. + /// BoundaryView6 = 56, + /// + /// The Intersect View 7 Boundary event. + /// BoundaryView7 = 57, + #endregion + /// + /// The animation Update event for Skeletal Animation functions. + /// AnimationUpdate = 58, + /// + /// The animation event for Skeletal Animation functions. + /// AnimationEvent = 59, + #region Async events + /// + /// The async image loaded event. + /// AsyncImageLoaded = 60, + /// + /// The async sound loaded event. + /// AsyncSoundLoaded = 61, + /// + /// The async http event. + /// AsyncHTTP = 62, + /// + /// The async dialog event. + /// AsyncDialog = 63, + /// + /// The async in-app purchase event. + /// AsyncIAP = 66, + /// + /// The async cloud event. + /// AsyncCloud = 67, + /// + /// The async networking event. + /// AsyncNetworking = 68, + /// + /// The async Steam event. + /// AsyncSteam = 69, + /// + /// The async social event. + /// AsyncSocial = 70, + /// + /// The async push notification event. + /// AsyncPushNotification = 71, + /// + /// The async save/load event. + /// AsyncSaveAndLoad = 72, + /// + /// The async audio recording event. + /// AsyncAudioRecording = 73, + /// + /// The async audio playback event. + /// AsyncAudioPlayback = 74, + /// + /// The async system event. + /// AsyncSystem = 75, + #endregion } + /// + /// The subtypes for . + /// public enum EventSubtypeGesture : uint { + /// + /// The tap event. + /// Tap = 0, + /// + /// The double tap event. + /// DoubleTap = 1, + /// + /// The drag start event. + /// DragStart = 2, + /// + /// The dragging event. + /// DragMove = 3, + /// + /// The drag end event. + /// DragEnd = 4, + /// + /// The flick event. + /// Flick = 5, + /// + /// The pinch start event. + /// PinchStart = 6, + /// + /// The pinch in event. + /// PinchIn = 7, + /// + /// The pinch out event. + /// PinchOut = 8, + /// + /// The pinch end event. + /// PinchEnd = 9, + /// + /// The rotate start event. + /// RotateStart = 10, + /// + /// The rotating event. + /// Rotating = 11, + /// + /// The rotate end event. + /// RotateEnd = 12, + /// + /// The global tap event. + /// GlobalTap = 64, + /// + /// The global double tap event. + /// GlobalDoubleTap = 65, + /// + /// The global drag start event. + /// GlobalDragStart = 66, + /// + /// The global dragging event. + /// GlobalDragMove = 67, + /// + /// The global drag end event. + /// GlobalDragEnd = 68, + /// + /// The global flick event. + /// GlobalFlick = 69, + /// + /// The global pinch start event. + /// GlobalPinchStart = 70, + /// + /// The global pinch in event. + /// GlobalPinchIn = 71, + /// + /// The global pinch out event. + /// GlobalPinchOut = 72, + /// + /// The global pinch end event. + /// GlobalPinchEnd = 73, + /// + /// The global rotate start event. + /// GlobalRotateStart = 74, + /// + /// The global rotating event. + /// GlobalRotating = 75, + /// + /// The global rotate end event. + /// GlobalRotateEnd = 76, } } diff --git a/UndertaleModLib/Models/UndertaleGeneralInfo.cs b/UndertaleModLib/Models/UndertaleGeneralInfo.cs index b6a41b999..6b8171b02 100644 --- a/UndertaleModLib/Models/UndertaleGeneralInfo.cs +++ b/UndertaleModLib/Models/UndertaleGeneralInfo.cs @@ -8,32 +8,82 @@ namespace UndertaleModLib.Models { + /// + /// General info about a data file. + /// [PropertyChanged.AddINotifyPropertyChangedInterface] public class UndertaleGeneralInfo : UndertaleObject { + /// + /// Information flags a data file can use. + /// [Flags] public enum InfoFlags : uint { - Fullscreen = 0x0001, // Start fullscreen - SyncVertex1 = 0x0002, // Use synchronization to avoid tearing + /// + /// Start fullscreen + /// + Fullscreen = 0x0001, + /// + /// Use synchronization to avoid tearing + /// + SyncVertex1 = 0x0002, + /// + /// Use synchronization to avoid tearing + /// SyncVertex2 = 0x0004, - Interpolate = 0x0008, // Interpolate colours between pixels - Scale = 0x0010, // Scaling: Keep aspect - ShowCursor = 0x0020, // Display cursor - Sizeable = 0x0040, // Allow window resize - ScreenKey = 0x0080, // Allow fullscreen switching + /// + /// Interpolate colours between pixels + /// + Interpolate = 0x0008, + /// + /// Scaling: Keep aspect + /// + Scale = 0x0010, + /// + /// Display cursor + /// + ShowCursor = 0x0020, + /// + /// Allow window resize + /// + Sizeable = 0x0040, + /// + /// Allow fullscreen switching + /// + ScreenKey = 0x0080, + SyncVertex3 = 0x0100, StudioVersionB1 = 0x0200, StudioVersionB2 = 0x0400, StudioVersionB3 = 0x0800, - StudioVersionMask = 0x0E00, // studioVersion = (infoFlags & InfoFlags.StudioVersionMask) >> 9 - SteamEnabled = 0x1000, // Enable Steam + + /// + /// studioVersion = (infoFlags & InfoFlags.StudioVersionMask) >> 9 + /// + StudioVersionMask = 0x0E00, + /// + /// Enable Steam + /// + SteamEnabled = 0x1000, + LocalDataEnabled = 0x2000, - BorderlessWindow = 0x4000, // Borderless Window - JavaScriptMode = 0x8000, // Tells the runner to run Javascript code + + /// + /// Borderless Window + /// + BorderlessWindow = 0x4000, + /// + /// Tells the runner to run Javascript code + /// + JavaScriptMode = 0x8000, + LicenseExclusions = 0x10000, } + /// + /// Function classifications a data file can have. + /// [Flags] public enum FunctionClassification : ulong { @@ -106,38 +156,132 @@ public enum FunctionClassification : ulong } public bool DisableDebugger { get; set; } = true; + + /// + /// The bytecode version of the data file. + /// public byte BytecodeVersion { get; set; } = 0x10; + public ushort Unknown { get; set; } = 0; + public UndertaleString Filename { get; set; } public UndertaleString Config { get; set; } + + /// + /// The last object id of the data file. + /// public uint LastObj { get; set; } = 100000; + + /// + /// The last tile id of the data file. + /// public uint LastTile { get; set; } = 10000000; + + /// + /// The game id of the data file. + /// public uint GameID { get; set; } = 13371337; - public Guid DirectPlayGuid { get; set; } = Guid.Empty; // in Studio it's always empty. + + /// + /// The DirectPlay GUID of the data file + /// + /// This is always empty in Game Maker: Studio. + public Guid DirectPlayGuid { get; set; } = Guid.Empty; + + public UndertaleString Name { get; set; } + + /// + /// The major version of the data file. + /// public uint Major { get; set; } = 1; + + /// + /// The minor version of the data file. + /// public uint Minor { get; set; } = 0; + + /// + /// The Release version of the data file. + /// public uint Release { get; set; } = 0; + + /// + /// The build version of the data file. + /// public uint Build { get; set; } = 1337; + + /// + /// The default window width of the game. + /// public uint DefaultWindowWidth { get; set; } = 1024; + + /// + /// The default window height of the game. + /// public uint DefaultWindowHeight { get; set; } = 768; + + /// + /// The info flags of the data file. + /// public InfoFlags Info { get; set; } = InfoFlags.Interpolate | InfoFlags.Scale | InfoFlags.ShowCursor | InfoFlags.ScreenKey | InfoFlags.StudioVersionB3; + + /// + /// The MD5 of the license used to compile the game. + /// public byte[] LicenseMD5 { get; set; } = new byte[16]; + + /// + /// The CRC32 of the license used to compile the game. + /// public uint LicenseCRC32 { get; set; } + + /// + /// The UNIX timestamp the game was compiled. + /// public ulong Timestamp { get; set; } = 0; + + /// + /// The name that gets displayed in the window. + /// public UndertaleString DisplayName { get; set; } + + public ulong ActiveTargets { get; set; } = 0; public FunctionClassification FunctionClassifications { get; set; } = FunctionClassification.None; // Initializing it with None is a very bad idea. + + /// + /// The Steam app id of the game. + /// public int SteamAppID { get; set; } = 0; + + /// + /// The port the data file exposes for the debugger. + /// public uint DebuggerPort { get; set; } = 6502; + + /// + /// The room order of the data file. + /// public UndertaleSimpleResourcesList RoomOrder { get; private set; } = new UndertaleSimpleResourcesList(); public List GMS2RandomUID { get; set; } = new List(); // Some sort of checksum + /// + /// The FPS of the data file. Game Maker Studio: 2 only. + /// public float GMS2FPS { get; set; } = 30.0f; + + /// + /// Whether the data file allows statistics. Game Maker Studio: 2 only. + /// public bool GMS2AllowStatistics { get; set; } = true; + + public byte[] GMS2GameGUID { get; set; } = new byte[16]; // more high entropy data + /// + /// If or has an invalid length. public void Serialize(UndertaleWriter writer) { writer.Write(DisableDebugger ? (byte)1 : (byte)0); @@ -305,20 +449,51 @@ public override string ToString() } } + /// + /// General options about a data file. + /// [PropertyChanged.AddINotifyPropertyChangedInterface] public class UndertaleOptions : UndertaleObject { + /// + /// Option flags a data file can use + /// [Flags] public enum OptionsFlags : ulong { + /// + /// If game should start in fullscreen. + /// FullScreen = 0x1, + /// + /// If pixels should be interpolated. + /// InterpolatePixels = 0x2, + /// + /// If the new audio format should be used. + /// UseNewAudio = 0x4, + /// + /// If borderless window should be used. + /// NoBorder = 0x8, + /// + /// If the mouse cursor should be shown. + /// ShowCursor = 0x10, + /// + /// If the window should be resizable. + /// Sizeable = 0x20, + /// + /// If the window should stay on top. + /// StayOnTop = 0x40, + /// + /// If the resolution can be changed. + /// ChangeResolution = 0x80, + NoButtons = 0x100, ScreenKey = 0x200, HelpKey = 0x400, @@ -344,26 +519,69 @@ public enum OptionsFlags : ulong public uint Unknown1 { get; set; } = 0x80000000; public uint Unknown2 { get; set; } = 0x00000002; + + /// + /// Option flags the data file uses. + /// public OptionsFlags Info { get; set; } = OptionsFlags.InterpolatePixels | OptionsFlags.UseNewAudio | OptionsFlags.ShowCursor | OptionsFlags.ScreenKey | OptionsFlags.QuitKey | OptionsFlags.SaveKey | OptionsFlags.ScreenShotKey | OptionsFlags.CloseSec | OptionsFlags.ScaleProgress | OptionsFlags.DisplayErrors | OptionsFlags.VariableErrors | OptionsFlags.CreationEventOrder; + + /// + /// The window scale. + /// public int Scale { get; set; } = -1; + + /// + /// The window color. + /// public uint WindowColor { get; set; } = 0; + + /// + /// The Color depth. + /// public uint ColorDepth { get; set; } = 0; + + /// + /// The game's resolution. + /// public uint Resolution { get; set; } = 0; + + /// + /// The game's refresh rate. + /// public uint Frequency { get; set; } = 0; + + /// + /// Whether the game uses V-Sync. + /// public uint VertexSync { get; set; } = 0; + public uint Priority { get; set; } = 0; public UndertaleSprite.TextureEntry BackImage { get; set; } = new UndertaleSprite.TextureEntry(); // Apparently these exist, but I can't find any examples of it public UndertaleSprite.TextureEntry FrontImage { get; set; } = new UndertaleSprite.TextureEntry(); public UndertaleSprite.TextureEntry LoadImage { get; set; } = new UndertaleSprite.TextureEntry(); public uint LoadAlpha { get; set; } = 255; + + /// + /// A list of Constants that the game uses. + /// public UndertaleSimpleList Constants { get; private set; } = new UndertaleSimpleList(); public bool NewFormat { get; set; } = true; + /// + /// A class for game constants. + /// [PropertyChanged.AddINotifyPropertyChangedInterface] public class Constant : UndertaleObject { + /// + /// The name of the constant. + /// public UndertaleString Name { get; set; } + + /// + /// The value of the constant. + /// public UndertaleString Value { get; set; } public void Serialize(UndertaleWriter writer) diff --git a/UndertaleModLib/Models/UndertaleGlobalInit.cs b/UndertaleModLib/Models/UndertaleGlobalInit.cs index 5486d6abf..18efb587d 100644 --- a/UndertaleModLib/Models/UndertaleGlobalInit.cs +++ b/UndertaleModLib/Models/UndertaleGlobalInit.cs @@ -7,10 +7,17 @@ namespace UndertaleModLib.Models { - // NOTE: Never seen in GMS1.4 so I'm not sure if the structure was the same + /// + /// A global initialization entry in a data file. + /// + /// Never seen in GMS1.4 so uncertain if the structure was the same. public class UndertaleGlobalInit : UndertaleObject, INotifyPropertyChanged { private UndertaleResourceById _Code = new UndertaleResourceById(); + + /// + /// The object which contains the code. + /// public UndertaleCode Code { get => _Code.Resource; set { _Code.Resource = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Code))); } } public event PropertyChangedEventHandler PropertyChanged; diff --git a/UndertaleModLib/Models/UndertaleRoom.cs b/UndertaleModLib/Models/UndertaleRoom.cs index 645dac05f..bd648362e 100644 --- a/UndertaleModLib/Models/UndertaleRoom.cs +++ b/UndertaleModLib/Models/UndertaleRoom.cs @@ -12,47 +12,158 @@ namespace UndertaleModLib.Models { + /// + /// A room in a data file. + /// public class UndertaleRoom : UndertaleNamedResource, INotifyPropertyChanged { + /// + /// Certain flags a room can have. + /// [Flags] public enum RoomEntryFlags : uint { + /// + /// Whether the room has Views enabled. + /// EnableViews = 1, + /// + /// TODO not exactly sure, probably similar to ? + /// ShowColor = 2, + /// + /// Whether the room should clear the display buffer. + /// ClearDisplayBuffer = 4, + /// + /// Whether the room was made in Game Maker: Studio 2. + /// IsGMS2 = 131072, + /// + /// Whether the room was made in Game Maker: Studio 2.3. + /// IsGMS2_3 = 65536 } + /// + /// The name of the room. + /// public UndertaleString Name { get; set; } + + /// + /// The caption of the room. Legacy variable that's used in pre- Game Maker: Studio. + /// public UndertaleString Caption { get; set; } + + /// + /// The Width of the room. + /// public uint Width { get; set; } = 320; + + /// + /// The height of the room. + /// public uint Height { get; set; } = 240; + + /// + /// The speed of the current room in steps. + /// TODO: GMS1 only? IIRC gms2 deals with it differently. + /// public uint Speed { get; set; } = 30; + + /// + /// Whether this room is persistant. + /// public bool Persistent { get; set; } = false; + + /// + /// The background color of this room. + /// public uint BackgroundColor { get; set; } = 0; + + /// + /// Whether the display buffer should be cleared with Window Color. + /// public bool DrawBackgroundColor { get; set; } = true; private UndertaleResourceById _CreationCodeId = new UndertaleResourceById(); + + /// + /// The creation code of this room. + /// public UndertaleCode CreationCodeId { get => _CreationCodeId.Resource; set { _CreationCodeId.Resource = value; OnPropertyChanged(); } } + + /// + /// The room flags this room has. + /// public RoomEntryFlags Flags { get; set; } = RoomEntryFlags.EnableViews; + + //TODO public bool World { get; set; } = false; public uint Top { get; set; } = 0; public uint Left { get; set; } = 0; public uint Right { get; set; } = 1024; public uint Bottom { get; set; } = 768; + + /// + /// The gravity towards x axis using room physics in m/s. + /// public float GravityX { get; set; } = 0; + + /// + /// The gravity towards y axis using room physics in m/s. + /// public float GravityY { get; set; } = 10; + + /// + /// The meters per pixel value for room physics. + /// public float MetersPerPixel { get; set; } = 0.1f; + + /// + /// The width of the room grid in pixels. + /// public double GridWidth { get; set; } = 16d; + + /// + /// The height of the room grid in pixels. + /// public double GridHeight { get; set; } = 16d; + + /// + /// The thickness of the room grid in pixels. + /// public double GridThicknessPx { get; set; } = 1d; private UndertalePointerList _layers = new(); + + /// + /// The list of backgrounds this room uses. + /// public UndertalePointerList Backgrounds { get; private set; } = new UndertalePointerList(); + + /// + /// The list of views this room uses. + /// public UndertalePointerList Views { get; private set; } = new UndertalePointerList(); + + /// + /// The list of game objects this room uses. + /// public UndertalePointerListLenCheck GameObjects { get; private set; } = new UndertalePointerListLenCheck(); + + /// + /// The list of tiles this room uses. + /// public UndertalePointerList Tiles { get; private set; } = new UndertalePointerList(); + + /// + /// The list of layers this room uses. Used in Game Maker Studio: 2 only, as and are empty there. + /// public UndertalePointerList Layers { get => _layers; private set { _layers = value; UpdateBGColorLayer(); OnPropertyChanged(); } } + + /// + /// The list of sequences this room uses. + /// public UndertaleSimpleList> Sequences { get; private set; } = new UndertaleSimpleList>(); private Layer GetBGColorLayer() @@ -64,6 +175,10 @@ private Layer GetBGColorLayer() .FirstOrDefault(); } public void UpdateBGColorLayer() => OnPropertyChanged("BGColorLayer"); + + /// + /// The layer containing the background color. + /// public Layer BGColorLayer => GetBGColorLayer(); public event PropertyChangedEventHandler PropertyChanged; @@ -135,7 +250,7 @@ public void Serialize(UndertaleWriter writer) if (writer.undertaleData.GeneralInfo.Major >= 2) { writer.WriteUndertaleObject(Layers); - + if (sequences) writer.WriteUndertaleObject(Sequences); } @@ -194,7 +309,7 @@ public void Unserialize(UndertaleReader reader) } } } - + if (sequences) reader.ReadUndertaleObject(Sequences); } @@ -231,7 +346,7 @@ public void SetupRoom(bool calculateGrid = true) tileSizes[new(w, h)] = layer.TilesData.TilesX * layer.TilesData.TilesY; } } - + } else tileList = Tiles; @@ -265,35 +380,98 @@ public override string ToString() return Name.Content + " (" + GetType().Name + ")"; } + /// + /// Interface for objects within rooms. + /// public interface RoomObject { + /// + /// X coordinate of the object. + /// int X { get; } + + /// + /// Y coordinate of the object. + /// int Y { get; } + + /// + /// Instance id of the object. + /// uint InstanceID { get; } } + /// + /// A background with properties as it's used in a room. + /// public class Background : UndertaleObject, INotifyPropertyChanged { private UndertaleRoom _ParentRoom; + + /// + /// The room parent this background belongs to. + /// public UndertaleRoom ParentRoom { get => _ParentRoom; set { _ParentRoom = value; OnPropertyChanged(); UpdateStretch(); } } + + //TODO: public float CalcScaleX { get; set; } = 1; public float CalcScaleY { get; set; } = 1; + + /// + /// Whether this background is enabled. + /// public bool Enabled { get; set; } = false; + + /// + /// Whether this acts as a foreground. + /// public bool Foreground { get; set; } = false; private UndertaleResourceById _BackgroundDefinition = new UndertaleResourceById(); + + /// + /// The background asset this uses. + /// public UndertaleBackground BackgroundDefinition { get => _BackgroundDefinition.Resource; set { _BackgroundDefinition.Resource = value; OnPropertyChanged(); } } private int _X = 0; - private int _Y = 0; + private int _Y = 0; + + /// + /// The x coordinate of the background in the room. + /// public int X { get => _X; set { _X = value; OnPropertyChanged(); UpdateStretch(); } } + + /// + /// The y coordinate of the background in the room. + /// public int Y { get => _Y; set { _Y = value; OnPropertyChanged(); UpdateStretch(); } } public int TileX { get; set; } = 1; public int TileY { get; set; } = 1; + + /// + /// Horizontal speed of the background. + /// public int SpeedX { get; set; } = 0; + + /// + /// Vertical speed of the background. + /// public int SpeedY { get; set; } = 0; private bool _Stretch = false; + + /// + /// Whether this background is stretched + /// public bool Stretch { get => _Stretch; set { _Stretch = value; OnPropertyChanged(); UpdateStretch(); } } + + /// + /// Whether this background is tiled horizontally. + /// public bool TiledHorizontally { get => TileX > 0; set { TileX = value ? 1 : 0; OnPropertyChanged(); } } + + /// + /// Whether this background is tiled vertically. + /// public bool TiledVertically { get => TileY > 0; set { TileY = value ? 1 : 0; OnPropertyChanged(); } } public event PropertyChangedEventHandler PropertyChanged; @@ -338,23 +516,81 @@ public void Unserialize(UndertaleReader reader) } } + /// + /// A view with properties as it's used in a room. + /// public class View : UndertaleObject, INotifyPropertyChanged { + /// + /// Whether this view is enabled. + /// public bool Enabled { get; set; } = false; + + /// + /// The x coordinate of the view in the room. + /// public int ViewX { get; set; } + + /// + /// The y coordinate of the view in the room. + /// public int ViewY { get; set; } + + /// + /// The width of the view. + /// public int ViewWidth { get; set; } = 640; + + /// + /// The height of the view. + /// public int ViewHeight { get; set; } = 480; + + /// + /// The x coordinate of the viewport on the screen. + /// public int PortX { get; set; } + + /// + /// The y coordinate of the viewport on the screen. + /// public int PortY { get; set; } + + /// + /// The width of the viewport on the screen. + /// public int PortWidth { get; set; } = 640; + + /// + /// The height of the viewport on the screen. + /// public int PortHeight { get; set; } = 480; + + /// + /// The horizontal border of the view for view following. + /// public uint BorderX { get; set; } = 32; + + /// + /// The vertical border of the view for view following. + /// public uint BorderY { get; set; } = 32; + + /// + /// The horizontal movement speed of the view. + /// public int SpeedX { get; set; } = -1; + + /// + /// The vertical movement speed of the view. + /// public int SpeedY { get; set; } = -1; private UndertaleResourceById _ObjectId = new UndertaleResourceById(); + + /// + /// The object the view should follow. + /// public UndertaleGameObject ObjectId { get => _ObjectId.Resource; set { _ObjectId.Resource = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ObjectId))); } } public event PropertyChangedEventHandler PropertyChanged; @@ -396,23 +632,71 @@ public void Unserialize(UndertaleReader reader) } } + /// + /// A game object with properties as it's used in a room. + /// public class GameObject : UndertaleObjectLenCheck, RoomObject, INotifyPropertyChanged { private UndertaleResourceById _ObjectDefinition = new UndertaleResourceById(); private UndertaleResourceById _CreationCode = new UndertaleResourceById(); private UndertaleResourceById _PreCreateCode = new UndertaleResourceById(); + /// + /// The x coordinate of this object. + /// public int X { get; set; } + + /// + /// The y coordinate of this object. + /// public int Y { get; set; } + + /// + /// The game object that is used. + /// public UndertaleGameObject ObjectDefinition { get => _ObjectDefinition.Resource; set { _ObjectDefinition.Resource = value; OnPropertyChanged(); } } + + /// + /// The instance id of this object. + /// public uint InstanceID { get; set; } + + /// + /// The creation code for this object. + /// public UndertaleCode CreationCode { get => _CreationCode.Resource; set { _CreationCode.Resource = value; OnPropertyChanged(); } } + + /// + /// The x scale that's applied for this object. + /// public float ScaleX { get; set; } = 1; + + /// + /// The y scale that's applied for this object. + /// public float ScaleY { get; set; } = 1; + + //TODO: no idea public uint Color { get; set; } = 0xFFFFFFFF; + + /// + /// The rotation of this object. + /// public float Rotation { get; set; } + + /// + /// The pre creation code of this object. + /// public UndertaleCode PreCreateCode { get => _PreCreateCode.Resource; set { _PreCreateCode.Resource = value; OnPropertyChanged(); } } + + /// + /// The image speed of this object. Game Maker: Studio 2 only. + /// public float ImageSpeed { get; set; } + + /// + /// The image index of this object. Game Maker: Studio 2 only. + /// public int ImageIndex { get; set; } public event PropertyChangedEventHandler PropertyChanged; @@ -421,7 +705,10 @@ protected void OnPropertyChanged([CallerMemberName] string name = null) PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); } - public float OppositeRotation => 360F - Rotation; + /// + /// The opposite angle of the current rotation. + /// + public float OppositeRotation => 360F - (Rotation % 360); public int XOffset => ObjectDefinition.Sprite != null ? X - ObjectDefinition.Sprite.OriginX + (ObjectDefinition.Sprite.Textures.FirstOrDefault()?.Texture?.TargetX ?? 0) : X; @@ -481,25 +768,85 @@ public override string ToString() } } + /// + /// A tile with properties as it's used in a room. + /// public class Tile : UndertaleObject, RoomObject, INotifyPropertyChanged { + /// + /// Whether this tile is from an asset layer. Game Maker Studio: 2 exclusive. + /// public bool _SpriteMode = false; private UndertaleResourceById _BackgroundDefinition = new UndertaleResourceById(); private UndertaleResourceById _SpriteDefinition = new UndertaleResourceById(); + /// + /// The x coordinate of the tile in the room. + /// public int X { get; set; } + + /// + /// The y coordinate of the tile in the room. + /// public int Y { get; set; } + + /// + /// From which tileset / background the tile stems from. + /// public UndertaleBackground BackgroundDefinition { get => _BackgroundDefinition.Resource; set { _BackgroundDefinition.Resource = value; OnPropertyChanged(); OnPropertyChanged("ObjectDefinition"); } } + + /// + /// From which sprite this tile stems from. + /// public UndertaleSprite SpriteDefinition { get => _SpriteDefinition.Resource; set { _SpriteDefinition.Resource = value; OnPropertyChanged(); OnPropertyChanged("ObjectDefinition"); } } + + /// + /// From which object this tile stems from. + /// Will return a if is disabled, a if it's enabled. + /// public UndertaleNamedResource ObjectDefinition { get => _SpriteMode ? SpriteDefinition : BackgroundDefinition; set { if (_SpriteMode) SpriteDefinition = (UndertaleSprite)value; else BackgroundDefinition = (UndertaleBackground)value; } } + + /// + /// The x coordinate of the tile in . + /// public uint SourceX { get; set; } + + /// + /// The y coordinate of the tile in . + /// public uint SourceY { get; set; } + + /// + /// The width of the tile. + /// public uint Width { get; set; } + + /// + /// The height of the tile. + /// public uint Height { get; set; } + + /// + /// The depth value of this tile. + /// public int TileDepth { get; set; } + + /// + /// The instance id of this tile. + /// public uint InstanceID { get; set; } + + /// + /// The x scale that's applied for this tile. + /// public float ScaleX { get; set; } = 1; + + /// + /// The y scale that's applied for this tile. + /// public float ScaleY { get; set; } = 1; + + //TODO? public uint Color { get; set; } = 0xFFFFFFFF; public UndertaleTexturePageItem Tpag => _SpriteMode ? SpriteDefinition?.Textures?.FirstOrDefault()?.Texture : BackgroundDefinition?.Texture; // TODO: what happens on sprites with multiple textures? @@ -558,6 +905,10 @@ public override string ToString() } // For GMS2, Backgrounds and Tiles are empty and this is used instead + /// + /// The layer type for a specific layer. In Game Maker: Studio 2, , + /// are empty and this is used instead. + /// public enum LayerType { Background = 1, @@ -567,6 +918,10 @@ public enum LayerType Effect = 6 } + /// + /// A layer with properties as it's used in a room. Game Maker: Studio 2 only. + /// + //TODO: everything from here on is mostly gms2 related which i dont have much experience with public class Layer : UndertaleObject, INotifyPropertyChanged { public interface LayerData : UndertaleObject @@ -575,12 +930,32 @@ public interface LayerData : UndertaleObject private UndertaleRoom _ParentRoom; private int _layerDepth; + + /// + /// The room this layer belongs to. + /// public UndertaleRoom ParentRoom { get => _ParentRoom; set { _ParentRoom = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ParentRoom))); UpdateParentRoom(); } } + /// + /// The name of the layer. + /// public UndertaleString LayerName { get; set; } + + /// + /// The id of the layer. + /// public uint LayerId { get; set; } + + /// + /// The type of this layer. + /// public LayerType LayerType { get; set; } + + /// + /// The depth of this layer. + /// public int LayerDepth { get => _layerDepth; set { _layerDepth = value; ParentRoom?.UpdateBGColorLayer(); } } + public float XOffset { get; set; } public float YOffset { get; set; } public float HSpeed { get; set; } @@ -652,7 +1027,7 @@ public void Serialize(UndertaleWriter writer) throw new Exception("Unsupported layer type " + LayerType); } } - + public void Unserialize(UndertaleReader reader) { LayerName = reader.ReadUndertaleString(); diff --git a/UndertaleModLib/Models/UndertaleScript.cs b/UndertaleModLib/Models/UndertaleScript.cs index 6f67e86d0..dfe32884d 100644 --- a/UndertaleModLib/Models/UndertaleScript.cs +++ b/UndertaleModLib/Models/UndertaleScript.cs @@ -7,19 +7,34 @@ namespace UndertaleModLib.Models { + + /// + /// A script entry in a data file. + /// public class UndertaleScript : UndertaleNamedResource, INotifyPropertyChanged { + /// + /// The name of the script entry. + /// public UndertaleString Name { get; set; } private UndertaleResourceById _Code = new UndertaleResourceById(); + + /// + /// The object which contains the code. + /// public UndertaleCode Code { get => _Code.Resource; set { _Code.Resource = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Code))); } } - public bool Constructor { get; set; } = false; + + /// + /// Whether or not this script is a constructor. + /// + public bool IsConstructor { get; set; } = false; public event PropertyChangedEventHandler PropertyChanged; public void Serialize(UndertaleWriter writer) { writer.WriteUndertaleString(Name); - if (Constructor) + if (IsConstructor) writer.Write((uint)_Code.SerializeById(writer) | 2147483648u); else writer.WriteUndertaleObject(_Code); @@ -31,7 +46,7 @@ public void Unserialize(UndertaleReader reader) int id = reader.ReadInt32(); if (id < -1) { - Constructor = true; + IsConstructor = true; id = (int)((uint)id & 2147483647u); } _Code.UnserializeById(reader, id); diff --git a/UndertaleModLib/Models/UndertaleShader.cs b/UndertaleModLib/Models/UndertaleShader.cs index a3014453d..50e23d477 100644 --- a/UndertaleModLib/Models/UndertaleShader.cs +++ b/UndertaleModLib/Models/UndertaleShader.cs @@ -7,12 +7,21 @@ namespace UndertaleModLib.Models { + /// + /// A shader entry for a data file. + /// [PropertyChanged.AddINotifyPropertyChangedInterface] public class UndertaleShader : UndertaleNamedResource { + /// + /// The vertex shader attributes a shader can have. + /// [PropertyChanged.AddINotifyPropertyChangedInterface] public class VertexShaderAttribute : UndertaleObject { + /// + /// The name of the vertex shader attribute. + /// public UndertaleString Name { get; set; } public void Serialize(UndertaleWriter writer) @@ -28,14 +37,44 @@ public void Unserialize(UndertaleReader reader) public uint _EntryEnd; + /// + /// The name of the shader. + /// public UndertaleString Name { get; set; } + + /// + /// The type the shader uses. + /// public ShaderType Type { get; set; } + /// + /// The GLSL ES vertex code this shader uses. + /// public UndertaleString GLSL_ES_Vertex { get; set; } + + /// + /// The GLSL ES fragment code this shader uses. + /// public UndertaleString GLSL_ES_Fragment { get; set; } + + /// + /// The GLSL vertex code this shader uses. + /// public UndertaleString GLSL_Vertex { get; set; } + + /// + /// The GLSL fragment code this shader uses. + /// public UndertaleString GLSL_Fragment { get; set; } + + /// + /// The HLSL9 vertex code this shader uses. + /// public UndertaleString HLSL9_Vertex { get; set; } + + /// + /// The HLSL9 fragment code this shader uses. + /// public UndertaleString HLSL9_Fragment { get; set; } public int Version { get; set; } = 2; @@ -308,17 +347,43 @@ public void Unserialize(UndertaleReader reader) } } - // PSSL is a shading language used only in PS4, based on HLSL11. - // Cg stands for "C for graphics" made by NVIDIA and used in PSVita and PS3 (they have their own variants of Cg), based on HLSL9. - // All console shaders (and HLSL11?) are compiled using confidential SDK tools when GMAssetCompiler builds the game (for PSVita it's psp2cgc shader compiler). + /// + /// Possible shader types a shader can have. + /// + /// All console shaders (and HLSL11?) are compiled using confidential SDK tools when + /// GMAssetCompiler builds the game (for PSVita it's psp2cgc shader compiler). public enum ShaderType : uint { + /// + /// Shader uses GLSL_ES + /// GLSL_ES = 1, + /// + /// Shader uses GLSL + /// GLSL = 2, + /// + /// Shader uses HLSL9 + /// HLSL9 = 3, + /// + /// Shader uses HLSL11 + /// HLSL11 = 4, + /// + /// Shader uses PSSL + /// + /// PSSL is a shading language used only in PS4, based on HLSL11. PSSL = 5, + /// + /// Shader uses for the PSVita + /// + /// Cg stands for "C for graphics" made by NVIDIA and used in PSVita and PS3 (they have their own variants of Cg), based on HLSL9. Cg_PSVita = 6, + /// + /// Shader uses Cg for the PS3 + /// + /// Cg stands for "C for graphics" made by NVIDIA and used in PSVita and PS3 (they have their own variants of Cg), based on HLSL9. Cg_PS3 = 7 } @@ -348,14 +413,14 @@ public void Serialize(UndertaleWriter writer, bool writeLength = true) if (writeLength) writer.Write((Data == null) ? 0 : Data.Length); } - + public void Unserialize(UndertaleReader reader, bool readLength = true) { _PointerLocation = reader.Position; _Position = reader.ReadUInt32(); if (readLength) _Length = reader.ReadUInt32(); - + IsNull = (_Position == 0x00000000u); } diff --git a/UndertaleModLib/Models/UndertaleSound.cs b/UndertaleModLib/Models/UndertaleSound.cs index cd9d11f0d..f8cfbfccf 100644 --- a/UndertaleModLib/Models/UndertaleSound.cs +++ b/UndertaleModLib/Models/UndertaleSound.cs @@ -7,31 +7,100 @@ namespace UndertaleModLib.Models { + /// + /// Sound entry in a data file. + /// public class UndertaleSound : UndertaleNamedResource, INotifyPropertyChanged { + /// + /// Audio entry flags a sound entry can use. + /// [Flags] public enum AudioEntryFlags : uint { + /// + /// Whether the sound is embedded into the data file. + /// IsEmbedded = 0x1, + /// + /// Whether the sound is compressed. + /// IsCompressed = 0x2, + /// + /// Whether the sound is compressed on load. + /// IsDecompressedOnLoad = 0x3, + /// + /// Whether this sound uses the "new audio system". + /// + /// This is default for everything post Game Maker: Studio. + /// The legacy sound system was used in pre Game Maker 8 Regular = 0x64, // also means "Use New Audio System?" Set by default on GMS 2. } + /// + /// The name of the sound entry. + /// public UndertaleString Name { get; set; } + + /// + /// The flags the sound entry uses. + /// public AudioEntryFlags Flags { get; set; } = AudioEntryFlags.IsEmbedded; + + /// + /// The file format of the audio entry. + /// public UndertaleString Type { get; set; } + + /// + /// The file name of the audio entry. + /// public UndertaleString File { get; set; } + + /// + /// A pre- Game Maker: Studio way of having certain effects on a sound effect. + /// + /// The exact way this works is unknown. But following values are possible: + /// Chorus, Echo, Flanger, Reverb, Gargle, all possible to be combined with one another. public uint Effects { get; set; } = 0; + + /// + /// The volume the audio entry is played at. + /// public float Volume { get; set; } = 1; + + /// + /// Whether the audio entry should be preloaded. + /// public bool Preload { get; set; } = true; + + /// + /// The pitch change of the audio entry. + /// public float Pitch { get; set; } = 0; private UndertaleResourceById _AudioGroup = new UndertaleResourceById(); private UndertaleResourceById _AudioFile = new UndertaleResourceById(); + + /// + /// The audio group this audio entry belongs to. + /// public UndertaleAudioGroup AudioGroup { get => _AudioGroup.Resource; set { _AudioGroup.Resource = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(AudioGroup))); } } + + /// + /// The reference to the audio file. + /// public UndertaleEmbeddedAudio AudioFile { get => _AudioFile.Resource; set { _AudioFile.Resource = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(AudioFile))); } } + + /// + /// The id of . + /// public int AudioID { get => _AudioFile.CachedId; set { _AudioFile.CachedId = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(AudioID))); } } + + /// + /// The id of . + /// public int GroupID { get => _AudioGroup.CachedId; set { _AudioGroup.CachedId = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(GroupID))); } } public event PropertyChangedEventHandler PropertyChanged; @@ -78,7 +147,7 @@ public void Unserialize(UndertaleReader reader) Preload = reader.ReadBoolean(); } - if (GroupID == reader.undertaleData.GetBuiltinSoundGroupID()) + if (GroupID == reader.undertaleData.GetBuiltinSoundGroupID()) { _AudioFile = reader.ReadUndertaleObject>(); } @@ -94,9 +163,15 @@ public override string ToString() } } + /// + /// Audio group entry in a data file. + /// [PropertyChanged.AddINotifyPropertyChangedInterface] public class UndertaleAudioGroup : UndertaleNamedResource { + /// + /// The name of the audio group. + /// public UndertaleString Name { get; set; } public void Serialize(UndertaleWriter writer) diff --git a/UndertaleModLib/Models/UndertaleSprite.cs b/UndertaleModLib/Models/UndertaleSprite.cs index c0197ab11..cb8d32d36 100644 --- a/UndertaleModLib/Models/UndertaleSprite.cs +++ b/UndertaleModLib/Models/UndertaleSprite.cs @@ -44,23 +44,84 @@ public override string ToString() } } + /// + /// Sprite entry in the data file. + /// public class UndertaleSprite : UndertaleNamedResource, PrePaddedObject, INotifyPropertyChanged { + /// + /// The name of the sprite. + /// public UndertaleString Name { get; set; } + + /// + /// The width of the sprite. + /// public uint Width { get; set; } + + /// + /// The height of the sprite. + /// public uint Height { get; set; } + + /// + /// The left margin of the sprite. + /// public int MarginLeft { get; set; } + + /// + /// The right margin of the sprite. + /// public int MarginRight { get; set; } + + /// + /// The bottom margin of the sprite. + /// public int MarginBottom { get; set; } + + /// + /// The top margin of the sprite. + /// public int MarginTop { get; set; } + + /// + /// Whether the sprite should be transparent. + /// public bool Transparent { get; set; } + + /// + /// Whether the sprite should get smoothed. + /// public bool Smooth { get; set; } + + /// + /// Whether the sprite should get preloaded. + /// public bool Preload { get; set; } + + public uint BBoxMode { get; set; } public SepMaskType SepMasks { get; set; } + + + /// + /// The x-coordinate of the origin of the sprite. + /// public int OriginX { get; set; } + + /// + /// The y-coordinate of the origin of the sprite. + /// public int OriginY { get; set; } + + /// + /// The frames of the sprite. + /// public UndertaleSimpleList Textures { get; private set; } = new UndertaleSimpleList(); + + /// + /// The collision masks of the sprite. + /// public ObservableCollection CollisionMasks { get; } = new ObservableCollection(); // Special sprite types (always used in GMS2) @@ -103,13 +164,28 @@ public MaskEntry NewMaskEntry() return newEntry; } + /// + /// Different formats a sprite can have. + /// public enum SpriteType : uint { + /// + /// Normal format. + /// Normal = 0, + /// + /// SWF format. + /// SWF = 1, + /// + /// Spine format. + /// Spine = 2 } + /// + /// Different Separation mask types a sprite can have. + /// public enum SepMaskType : uint { AxisAlignedRect = 0, @@ -770,7 +846,7 @@ public void Unserialize(UndertaleReader reader) } [PropertyChanged.AddINotifyPropertyChangedInterface] - public class UndertaleYYSWFGradientRecord : UndertaleObject + public class UndertaleYYSWFGradientRecord : UndertaleObject { public int Ratio { get; set; } public byte Red { get; set; } diff --git a/UndertaleModLib/Models/UndertaleString.cs b/UndertaleModLib/Models/UndertaleString.cs index 5b8b13519..e19529e29 100644 --- a/UndertaleModLib/Models/UndertaleString.cs +++ b/UndertaleModLib/Models/UndertaleString.cs @@ -10,15 +10,28 @@ namespace UndertaleModLib.Models { + /// + /// A string entry a data file can have. + /// [PropertyChanged.AddINotifyPropertyChangedInterface] public class UndertaleString : UndertaleResource, ISearchable { + /// + /// The contents of the string. + /// public string Content { get; set; } + /// + /// Initializes a new empty instance of type . + /// public UndertaleString() { } + /// + /// Initializes a new instance of type with a specified content. + /// + /// The content for the string. public UndertaleString(string content) { this.Content = content; @@ -39,6 +52,11 @@ public override string ToString() return ToString(true); } + /// + /// Returns a string that represents the current object. + /// + /// Whether the string is from a Game Maker: Studio 2 data file or not. + /// A string that represents the current object. public string ToString(bool isGMS2) { if (Content == null) @@ -82,8 +100,12 @@ public bool SearchMatches(string filter) { return Content?.ToLower().Contains(filter.ToLower()) ?? false; } - - // Unescapes text for the assembler + + /// + /// Unescapes text for the assembler. + /// + /// The text to unescape. + /// A string with \n, \r, " and \ being properly escaped. public static string UnescapeText(string text) { return text.Replace("\\r", "\r").Replace("\\n", "\n").Replace("\\\"", "\"").Replace("\\\\", "\\"); diff --git a/UndertaleModLib/Models/UndertaleTextureGroupInfo.cs b/UndertaleModLib/Models/UndertaleTextureGroupInfo.cs index e06be4bb4..4f8e37da6 100644 --- a/UndertaleModLib/Models/UndertaleTextureGroupInfo.cs +++ b/UndertaleModLib/Models/UndertaleTextureGroupInfo.cs @@ -8,14 +8,73 @@ namespace UndertaleModLib.Models { + /// + /// A texture group info entry in a data file. + /// + /// This is a new chunk since Bytecode 17. It's probably related to performance improvements mentioned in the release notes for that runtime. + /// Here is the specification: + /// + /// TGIN structure - likely stands for Texture Group Info + /// Chunk introduced in 2.2.1 with new texture functions + /// --- + /// + /// Int32 - probably chunk format version number, always 1 + /// + /// PointerList<T> structure. Each item represents a texture group: + /// 32-bit string pointer - Name + /// 32-bit pointer #1 + /// 32-bit pointer #2 + /// 32-bit pointer #3 + /// 32-bit pointer #4 + /// 32-bit pointer #5 + /// + /// #1 leads here: + /// SimpleList<int> of texture page IDs the group has + /// + /// #2 leads here: + /// SimpleList<int> of sprite IDs the group has + /// + /// #3 leads here: + /// SimpleList<int> of Spine sprite IDs (normal sprite ID, just this has Spine sprites separated) the group has + /// + /// #4 leads here: + /// SimpleList<int> of font IDs the group has + /// + /// #5 leads here: + /// SimpleList<int> of tileset IDs the group has + /// + /// . [PropertyChanged.AddINotifyPropertyChangedInterface] public class UndertaleTextureGroupInfo : UndertaleNamedResource { + /// + /// The name of the texture group info entry. + /// public UndertaleString Name { get; set; } + + /// + /// A list of texture pages the group has. + /// public UndertaleSimpleResourcesList TexturePages { get; set; } + + /// + /// A list of sprites the group has. + /// public UndertaleSimpleResourcesList Sprites { get; set; } + + /// + /// A list of spine sprites the group has. + /// public UndertaleSimpleResourcesList SpineSprites { get; set; } + + /// + /// A list of fonts the group has. + /// public UndertaleSimpleResourcesList Fonts { get; set; } + + /// + /// A list of tilesets this group has. + /// public UndertaleSimpleResourcesList Tilesets { get; set; } public UndertaleTextureGroupInfo() diff --git a/UndertaleModLib/Models/UndertaleTexturePageItem.cs b/UndertaleModLib/Models/UndertaleTexturePageItem.cs index 6804b7455..f8b056b6f 100644 --- a/UndertaleModLib/Models/UndertaleTexturePageItem.cs +++ b/UndertaleModLib/Models/UndertaleTexturePageItem.cs @@ -5,26 +5,78 @@ namespace UndertaleModLib.Models { - /** - * The way this works is: - * It renders in a box of size BoundingWidth x BoundingHeight at some position. - * TargetX/Y/W/H is relative to the bounding box, anything outside of that is just transparent. - * SourceX/Y/W/H is part of TexturePage that is drawn over TargetX/Y/W/H - */ + /// + /// A texture page item in a data file. + /// + /// The way a texture page item works is:
+ /// It renders in a box of size x at some position.
+ /// , , and are relative to the bounding box, + /// anything outside of that is just transparent.
+ /// , , and are part of the texture page which + /// are drawn over , , , .
public class UndertaleTexturePageItem : UndertaleNamedResource, INotifyPropertyChanged { + /// + /// The name of the texture page item. + /// + //TODO: is not used by game maker, should get repurposed public UndertaleString Name { get; set; } - public ushort SourceX { get; set; } // X/Y of item on the texture page + + /// + /// The x coordinate of the item on the texture page. + /// + public ushort SourceX { get; set; } + + /// + /// The y coordinate of the item on the texture page. + /// public ushort SourceY { get; set; } - public ushort SourceWidth { get; set; } // Width/height of item on the texture page + + /// + /// The width of the item on the texture page. + /// + public ushort SourceWidth { get; set; } + + /// + /// The height of the item on the texture page. + /// public ushort SourceHeight { get; set; } - public ushort TargetX { get; set; } // X/Y of where to place inside of bound width/height + + /// + /// The x coordinate of the item in the bounding rectangle. + /// + public ushort TargetX { get; set; } + + /// + /// The y coordinate of the item in the bounding rectangle. + /// public ushort TargetY { get; set; } - public ushort TargetWidth { get; set; } // Dimensions of where to scale/place inside of bound width/height + + /// + /// The width of the item in the bounding rectangle. + /// + public ushort TargetWidth { get; set; } + + /// + /// The height of the item in the bounding rectangle. + /// public ushort TargetHeight { get; set; } - public ushort BoundingWidth { get; set; } // Source sprite/asset dimensions + + /// + /// The width of the bounding rectangle. + /// + public ushort BoundingWidth { get; set; } + + /// + /// The height of the bounding rectangle. + /// public ushort BoundingHeight { get; set; } + private UndertaleResourceById _TexturePage = new UndertaleResourceById(); + + /// + /// The texture page this item is referencing + /// public UndertaleEmbeddedTexture TexturePage { get => _TexturePage.Resource; set { _TexturePage.Resource = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(TexturePage))); } } public event PropertyChangedEventHandler PropertyChanged; @@ -73,10 +125,15 @@ public override string ToString() return Name.Content + " (" + GetType().Name + ")"; } + /// + /// Replaces the current image of this texture page item to hold a new image. + /// + /// The new image that shall be applied to this texture page item. + /// Whether to dispose afterwards. public void ReplaceTexture(Image replaceImage, bool disposeImage = true) { Image finalImage = TextureWorker.ResizeImage(replaceImage, SourceWidth, SourceHeight); - + // Apply the image to the TexturePage. lock (TexturePage.TextureData) { diff --git a/UndertaleModLib/Models/UndertaleTimeline.cs b/UndertaleModLib/Models/UndertaleTimeline.cs index 368f7b27c..e6b4f96d4 100644 --- a/UndertaleModLib/Models/UndertaleTimeline.cs +++ b/UndertaleModLib/Models/UndertaleTimeline.cs @@ -8,15 +8,31 @@ namespace UndertaleModLib.Models { + /// + /// A timeline in a data file. + /// [PropertyChanged.AddINotifyPropertyChangedInterface] public class UndertaleTimeline : UndertaleNamedResource { + /// + /// A specific moment in a timeline. + /// [PropertyChanged.AddINotifyPropertyChangedInterface] public class UndertaleTimelineMoment : UndertaleObject { + /// + /// After how many steps this moment gets executed. + /// public uint Step { get; set; } + + /// + /// The actions that get executed at this moment. + /// public UndertalePointerList Event { get; set; } + /// + /// Initializes a new empty instance of the class. + /// public UndertaleTimelineMoment() { /* @@ -25,10 +41,15 @@ public UndertaleTimelineMoment() */ } - public UndertaleTimelineMoment(uint step, UndertalePointerList ev) + /// + /// Initializes a new instance of the with the specified step time and event action list. + /// + /// After how many steps the moment shall be executed. + /// A list of events that shall be executed. + public UndertaleTimelineMoment(uint step, UndertalePointerList events) { Step = step; - Event = ev; + Event = events; } public void Serialize(UndertaleWriter writer) @@ -42,7 +63,14 @@ public void Unserialize(UndertaleReader reader) } } + /// + /// The name of the timeline. + /// public UndertaleString Name { get; set; } + + /// + /// The moments this timeline has. Comparable to keyframes. + /// public ObservableCollection Moments { get; set; } = new ObservableCollection(); public override string ToString() diff --git a/UndertaleModLib/Scripting/IScriptInterface.cs b/UndertaleModLib/Scripting/IScriptInterface.cs index 178e54969..1ff5a5048 100644 --- a/UndertaleModLib/Scripting/IScriptInterface.cs +++ b/UndertaleModLib/Scripting/IScriptInterface.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.IO; +using System.IO.Pipes; using System.Linq; using System.Text; using System.Threading; @@ -9,12 +11,24 @@ namespace UndertaleModLib.Scripting { + /// + /// The exception that is thrown when trivial errors happen during runtime of UndertaleModTool scripts.
+ /// This exception does not contain a stacktrace and should more be handled like an error message that stops execution of the currently running script. + ///
+ /// if (Data is null) throw new ScriptException("Please load data.win first!); public class ScriptException : Exception { + /// + /// Initializes a new instance of the IOException class with its message string set to the empty string (""). + /// public ScriptException() { } + /// + /// Initializes a new instance of the IOException class with its message string set to . + /// + /// A that describes the error. The content of is intended to be understood by humans. public ScriptException(string msg) : base(msg) { } @@ -22,84 +36,583 @@ public ScriptException(string msg) : base(msg) public interface IScriptInterface { + /// + /// The data file. + /// UndertaleData Data { get; } + + /// + /// The file path where resides. + /// string FilePath { get; } + + /// + /// The path of the current executed script. + /// string ScriptPath { get; } + + /// + /// The object that's currently highlighted in the GUI. + /// object Highlighted { get; } + + /// + /// The object that's currently selected in the GUI. + /// object Selected { get; } + + /// + /// Indicates whether saving is currently enabled. + /// bool CanSave { get; } + + /// + /// Indicates whether the last script executed successfully or not. + /// bool ScriptExecutionSuccess { get; } + + /// + /// Error message of the last executed script. Will be "" () if no error occured. + /// string ScriptErrorMessage { get; } + + /// + /// Path of the main executable that's currently running. + /// + /// For example C://Users/me/UMT/UMT.exe or /bin/UMTCLI. string ExePath { get; } + + /// + /// A string, detailing the type of the last encountered error. + /// string ScriptErrorType { get; } + /// + /// Indicates whether the user has enabled the setting to use decompiled code cache. + /// bool GMLCacheEnabled { get; } + //TODO: Only GUI and ExportAllRoomsToPng.csx uses this, but nothing should ever need to access this value. + // "somehow Dispatcher.Invoke() in a loop creates executable code queue that doesn't clear on app closing." bool IsAppClosed { get; } - void EnsureDataLoaded(); - Task Make_New_File(); + /// + /// Ensures that a valid data file () is loaded. An exception should be thrown if it isn't. + /// + void EnsureDataLoaded() + { + if (Data is null) + throw new ScriptException("No data file is currently loaded!"); + } + + /// + /// Creates a new Data file asynchronously. + /// + /// if task was successful, if not. + Task MakeNewDataFile(); + + /// Obsolete. Use . + [Obsolete("Use MakeNewDataFile instead!")] + sealed Task Make_New_File() + { + return MakeNewDataFile(); + } + + //TODO: i have absolutely no idea what any of these do. void ReplaceTempWithMain(bool ImAnExpertBTW = false); void ReplaceMainWithTemp(bool ImAnExpertBTW = false); void ReplaceTempWithCorrections(bool ImAnExpertBTW = false); void ReplaceCorrectionsWithTemp(bool ImAnExpertBTW = false); void UpdateCorrections(bool ImAnExpertBTW = false); + /// + /// Used in Scripts in order to show a message to the user. + /// + /// The message to show. void ScriptMessage(string message); + + //TODO: currently should get repurposed/renamed? + /// + /// Sets the message of the variable holding text from the console. Currently only used in GUI. + /// + /// The message to set it to. void SetUMTConsoleText(string message); + + /// + /// Used in Scripts in order to ask a yes/no question to the user which they can answer. + /// + /// The message to ask. + /// if user affirmed the question, if not. bool ScriptQuestion(string message); - void ScriptError(string error, string title = "Error", bool SetConsoleText = true); //TODO: setConsoleText should get a *clearer* name + + /// + /// Used in Scripts in order to show an error to the user. + /// + /// The error message to show. + /// A short-descriptive title. + /// Whether to call with . + //TODO: setConsoleText should get a *clearer* name + void ScriptError(string error, string title = "Error", bool SetConsoleText = true); + + /// + /// Used in Scripts in order to open a URL in the users' browser. + /// + /// The URL to open. void ScriptOpenURL(string url); - bool SendAUMIMessage(IpcMessage_t ipMessage, ref IpcReply_t outReply); + + /// + /// Used for communicating with AUMI (Archie's Undertale Modding Interface). + /// + /// The message to send. + /// The reply from AUMI. + /// if the message couldn't be sent. Otherwise, the function returns . + bool SendAUMIMessage(IpcMessage_t ipMessage, ref IpcReply_t outReply) + { + // By Archie + const int ReplySize = 132; + + // Create the pipe + using var pPipeServer = new NamedPipeServerStream("AUMI-IPC", PipeDirection.InOut, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous); + + // Wait 1/8th of a second for AUMI to connect. + // If it doesn't connect in time (which it should), just return false to avoid a deadlock. + if (!pPipeServer.IsConnected) + { + pPipeServer.WaitForConnectionAsync(); + Thread.Sleep(125); + if (!pPipeServer.IsConnected) + { + pPipeServer.DisposeAsync(); + return false; + } + } + + try + { + //Send the message + pPipeServer.Write(ipMessage.RawBytes()); + pPipeServer.Flush(); + } + catch (Exception e) + { + // Catch any errors that might arise if the connection is broken + ScriptError("Could not write data to the pipe!\nError: " + e.Message); + return false; + } + + // Read the reply, the length of which is always a pre-set amount of bytes. + byte[] bBuffer = new byte[ReplySize]; + pPipeServer.Read(bBuffer, 0, ReplySize); + + outReply = IpcReply_t.FromBytes(bBuffer); + return true; + } + + /// + /// Run a C# UndertaleModLib compatible script file. + /// + /// File path to the script file to execute. + /// A that indicates whether the execution of the script was successful. bool RunUMTScript(string path); + + /// + /// Lint whether a file is C# UndertaleModLib compatible. + /// + /// File path to the script file to lint. + /// A that indicates whether the linting was successful. bool LintUMTScript(string path); + + /// + /// Initializes a Script Dialog with default values + /// void InitializeScriptDialog(); + + //TODO: some profile mod stuff, not quite sure on what its supposed to do. void ReapplyProfileCode(); void NukeProfileGML(string codeName); + + /// + ///Get the decompiled text from a code entry (like gml_Script_moveTo). + /// + /// The name of the code entry from which to get the decompiled code from. + /// The GlobalDecompileContext + /// Decompiled text as a . + /// This will return a string, even if the decompilation failed! Usually commented out and featuring + /// DECOMPILER FAILED! . string GetDecompiledText(string codeName, GlobalDecompileContext context = null); + + /// + /// Get the decompiled text from an object. + /// + /// The object from which to get the decompiled code from. + /// The GlobalDecompileContext + /// Decompiled text as a . + /// This will return a string, even if the decompilation failed! Usually commented out and featuring + /// DECOMPILER FAILED! . string GetDecompiledText(UndertaleCode code, GlobalDecompileContext context = null); + + /// + /// Get the disassembly from a code entry (like gml_Script_moveTo). + /// + /// The name of the code entry from which to get the disassembly from. + /// Disassembly as . + /// This will return a string, even if the disassembly failed! Usually commented out and featuring + /// DISASSEMBLY FAILED! . string GetDisassemblyText(string codeName); + + /// + /// Get the disassembly from an object. + /// + /// The object from which to get the disassembly from. + /// Disassembly as . + /// This will return a string, even if the disassembly failed! Usually commented out and featuring + /// DISASSEMBLY FAILED! . string GetDisassemblyText(UndertaleCode code); - bool AreFilesIdentical(string file1, string file2); - string ScriptInputDialog(string titleText, string labelText, string defaultInputBoxText, string cancelButtonText, string submitButtonText, bool isMultiline, bool preventClose); + + /// + /// Check whether two files are identical. + /// + /// File path to first file. + /// File path to second file. + /// A that indicates whether the files are identical or not. + bool AreFilesIdentical(string file1, string file2) + { + using FileStream fs1 = new FileStream(file1, FileMode.Open, FileAccess.Read, FileShare.Read); + using FileStream fs2 = new FileStream(file2, FileMode.Open, FileAccess.Read, FileShare.Read); + + if (fs1.Length != fs2.Length) return false; // different size, files can't be the same + + while (true) + { + int b1 = fs1.ReadByte(); + int b2 = fs2.ReadByte(); + if (b1 != b2) return false; // different contents, files are not the same + if (b1 == -1) break; // here both bytes are the same. Thus we only need to check if one is at end-of-file. + } + + // identical + return true; + } + + /// + /// Allows the user to input text with the option to cancel it. + /// + /// A short descriptive title. + /// A label describing what the user should input. + /// The default value of the input. + /// The text of the cancel button. + /// The text of the submit button. + /// Whether to allow the input to have multiple lines. + /// Whether the window is allowed to be closed. + /// Should this be set to , then there also won't be a close button. + /// The text that the user inputted. + string ScriptInputDialog(string title, string label, string defaultInput, string cancelText, string submitText, bool isMultiline, bool preventClose); + + /// + /// Allows the user to input text in a simple dialog. + /// + /// A short descriptive title. + /// A label describing what the user should input. + /// The default value of the input. + /// Whether to allow the input to have multiple lines. + /// Whether to block the parent window and only continue after the dialog is cleared. + /// The text that the user inputted. string SimpleTextInput(string title, string label, string defaultValue, bool allowMultiline, bool showDialog = true); - void SimpleTextOutput(string title, string label, string defaultText, bool allowMultiline); - Task ClickableTextOutput(string title, string query, int resultsCount, IOrderedEnumerable>> resultsDict, bool editorDecompile, IOrderedEnumerable failedList = null); - Task ClickableTextOutput(string title, string query, int resultsCount, IDictionary> resultsDict, bool editorDecompile, IEnumerable failedList = null); + + /// + /// Shows simple output to the user. + /// + /// A short descriptive title. + /// A label describing the output. + /// The message to convey to the user. + /// Whether to allow the message to be multiline or not. + /// Should this be false but have multiple lines, then only the first line will be shown. + void SimpleTextOutput(string title, string label, string message, bool allowMultiline); + + /// + /// Shows search output with clickable text to the user. + /// + /// A short descriptive title. + /// The query that was searched for. + /// How many results have been found. + /// An of type , + /// with TKey being the name of the code entry an TValue being a list of matching code lines with their line number prepended. + /// Whether to open the "Decompiled" view or the "Disassembly" view when clicking on an entry name. + /// A list of code entries that encountered an error while searching. + /// A task that represents the search output. + Task ClickableSearchOutput(string title, string query, int resultsCount, IOrderedEnumerable>> resultsDict, bool showInDecompiledView, IOrderedEnumerable failedList = null); + + /// + /// Obsolete. + /// + [Obsolete("Use ClickableSearchOutput instead!")] + sealed Task ClickableTextOutput(string title, string query, int resultsCount, IOrderedEnumerable>> resultsDict, bool showInDecompiledView, IOrderedEnumerable failedList = null) + { + return ClickableSearchOutput(title, query, resultsCount, resultsDict, showInDecompiledView, failedList); + } + + /// + /// Shows search output with clickable text to the user. + /// + /// A short descriptive title. + /// The query that was searched for. + /// How many results have been found. + /// A with TKey being the name of the code entry and + /// TValue being a list of matching code lines with their line number prepended. + /// Whether to open the "Decompiled" view or the "Disassembly" view when clicking on an entry name. + /// A list of code entries that encountered an error while searching. + /// A task that represents the search output. + Task ClickableSearchOutput(string title, string query, int resultsCount, IDictionary> resultsDict, bool showInDecompiledView, IEnumerable failedList = null); + + /// + /// Obsolete. + /// + [Obsolete("Use ClickableSearchOutput instead!")] + sealed Task ClickableTextOutput(string title, string query, int resultsCount, IDictionary> resultsDict, bool showInDecompiledView, IEnumerable failedList = null) + { + return ClickableSearchOutput(title, query, resultsCount, resultsDict, showInDecompiledView, failedList); + } + + /// + /// Sets . + /// + /// The state to set it to. void SetFinishedMessage(bool isFinishedMessageEnabled); + + /// + /// Updates the progress bar. Not to be called directly in scripts! Use instead! + /// + /// + /// + /// + /// void UpdateProgressBar(string message, string status, double progressValue, double maxValue); + + /// + /// Sets the progress bar dialog to a certain value. + /// + /// What the progress bar is describing. + /// What the current status is. For example Decompiling.... + /// The value to set the progress bar to. + /// The max value of the progress bar. void SetProgressBar(string message, string status, double progressValue, double maxValue); + + /// + /// Show the progress bar. + /// void SetProgressBar(); + + /// + /// Updates the value of the current running progress bar dialog. + /// + /// The new value to set the progress bar to. void UpdateProgressValue(double progressValue); + + /// + /// Updates the status of the current running progress bar dialog. + /// + /// The new status. For example Decompiling.... void UpdateProgressStatus(string status); + + //TODO: considering this forces everything that implements this to have their own progressValue, + //why not make that a necessary attribute? + /// + /// Adds a certain amount to the variable holding a progress value. + /// + /// The amount to add. void AddProgress(int amount); - void IncProgress(); - void AddProgressP(int amount); - void IncProgressP(); + + /// + /// Increments the variable holding a progress value by one. + /// + void IncrementProgress(); + + /// + /// Obsolete. + /// + [Obsolete("Use IncrementProgress instead!")] + sealed void IncProgress() + { + IncrementProgress(); + } + + /// + /// Adds a certain amount to the variable holding a progress value in. + /// Used for parallel operations, as it is thread-safe. + /// + /// The amount to add. + void AddProgressParallel(int amount); + + /// + /// Obsolete. + /// + [Obsolete("Use AddProgressParallel instead!")] + sealed void AddProgressP(int amount) + { + AddProgressParallel(amount); + } + + /// + /// Increments the variable holding a progress value by one. + /// Used for parallel operations, as it is thread-safe. + /// + void IncrementProgressParallel(); + + /// + /// Obsolete. + /// + [Obsolete("Use IncrementProgressParallel instead!")] + sealed void IncProgressP() + { + IncrementProgressParallel(); + } + + /// + /// Gets the value of the variable holding a progress value. + /// + /// The value as . int GetProgress(); + + /// + /// Sets the value of the variable holding a progress variable to another value. + /// + /// The new value for the progress variable. void SetProgress(int value); + + /// + /// Hides the progress bar. + /// void HideProgressBar(); + + /// + /// Enables the UI. + /// void EnableUI(); + + /// + /// Allows scripts to modify asset lists from the non-UI thread. + /// If this isn't called before attempting to modify them, a will be thrown. + /// + /// A comma separated list of asset list names. This is case sensitive. + /// Whether to enable or disable the synchronization. + //TODO: Having resourceType as a comma separated list just screams for error. Make it use some array of predefined assets it can use. void SyncBinding(string resourceType, bool enable); - void SyncBinding(bool enable = false); - void StartUpdater(); - Task StopUpdater(); - Task GenerateGMLCache(ThreadLocal decompileContext = null, object dialog = null, bool isSaving = false); + /// + /// Stops the synchronization of all previously enabled assets. + /// + void DisableAllSyncBindings(); - void ChangeSelection(object newsel); + /// + /// Obsolete + /// + /// + [Obsolete("Use DisableAllSyncBindings() instead!")] + sealed void SyncBinding(bool enable = false) + { + DisableAllSyncBindings(); + } + + /// + /// Starts the task that updates a progress bar in parallel. + /// + void StartProgressBarUpdater(); - string PromptChooseDirectory(string prompt); + /// + /// Obsolete. + /// + [Obsolete("Use StartProgressBarUpdater instead!")] + sealed void StartUpdater() + { + StartProgressBarUpdater(); + } + /// + /// Stops the task that updates a progress bar in parallel. + /// + /// A task that represents the stopped progress updater. + Task StopProgressBarUpdater(); + + /// + /// Obsolete. + /// + [Obsolete("Use StopProgressBarUpdater instead!")] + sealed void StopUpdater () + { + StopProgressBarUpdater(); + } + + /// + /// Generates a decompiled code cache to accelerate operations that need to access code often. + /// + /// The GlobalDecompileContext. + /// The dialog that should be shown. If then a new dialog will be automatically created and shown. + /// Whether to clear from . + /// Whether the decompiled GML cache was generated or not. if it was successful, + /// if it wasn't or is disabled. + Task GenerateGMLCache(ThreadLocal decompileContext = null, object dialog = null, bool clearGMLEditedBefore = false); + + /// + /// Changes the currently selected in the GUI. + /// + /// The new object that should now be selected. + void ChangeSelection(object newSelection); + + /// + /// Used to prompt the user for a directory. + /// + /// The directory selected by the user. + string PromptChooseDirectory(); + + /// + /// Obsolete + /// + [Obsolete("Use this parameters, as it is not used.")] + sealed string PromptChooseDirectory(string prompt) + { + return PromptChooseDirectory(); + } + + /// + /// Used to prompt the user for a file. + /// + /// The default extension that should be selected. + /// The filters used for the file select. + /// The file selected by the user. string PromptLoadFile(string defaultExt, string filter); + + //TODO: so much stuff.... void ImportGMLString(string codeName, string gmlCode, bool doParse = true, bool CheckDecompiler = false); void ImportASMString(string codeName, string gmlCode, bool doParse = true, bool destroyASM = true, bool CheckDecompiler = false); void ImportGMLFile(string fileName, bool doParse = true, bool CheckDecompiler = false, bool throwOnError = false); void ImportASMFile(string fileName, bool doParse = true, bool destroyASM = true, bool CheckDecompiler = false, bool throwOnError = false); void ReplaceTextInGML(string codeName, string keyword, string replacement, bool case_sensitive = false, bool isRegex = false, GlobalDecompileContext context = null); void ReplaceTextInGML(UndertaleCode code, string keyword, string replacement, bool case_sensitive = false, bool isRegex = false, GlobalDecompileContext context = null); - bool DummyBool(); - void DummyVoid(); - string DummyString(); + + /// + /// Method returning a dummy boolean value. + /// + /// Returns a dummy boolean value + bool DummyBool() + { + return true; + } + + /// + /// Method doing nothing. + /// + void DummyVoid() + { + + } + + /// + /// Method returning a dummy string value. + /// + /// Returns a dummy string value. + string DummyString() + { + return ""; + } } } diff --git a/UndertaleModLib/UndertaleBaseTypes.cs b/UndertaleModLib/UndertaleBaseTypes.cs index 90a6f152f..7682e37ad 100644 --- a/UndertaleModLib/UndertaleBaseTypes.cs +++ b/UndertaleModLib/UndertaleBaseTypes.cs @@ -9,7 +9,16 @@ namespace UndertaleModLib { public interface UndertaleObject { + /// + /// Serializes the data file into a specified . + /// + /// Where to serialize to. void Serialize(UndertaleWriter writer); + + /// + /// Deserializes from a specified to the current data file. + /// + /// Where to deserialize from. void Unserialize(UndertaleReader reader); } @@ -30,7 +39,16 @@ public interface UndertaleObjectWithBlobs public interface PaddedObject // TODO: use this everywhere { + /// + /// TODO! + /// + /// Where to serialize to. void SerializePadding(UndertaleWriter writer); + + /// + /// TODO! + /// + /// Where to deserialize from. void UnserializePadding(UndertaleReader reader); } @@ -39,7 +57,7 @@ public interface PrePaddedObject void SerializePrePadding(UndertaleWriter writer); void UnserializePrePadding(UndertaleReader reader); } - + public enum ResourceType : int { None = -1, @@ -71,6 +89,12 @@ public interface UndertaleNamedResource : UndertaleResource public interface ISearchable { + /// + /// Returns a value indicating whether a specified substring occurs within this object. + /// + /// The string to seek. Case insensitive. + /// if occurs within this object, or if + /// is the empty string (""); otherwise, false. bool SearchMatches(string filter); } } diff --git a/UndertaleModLib/UndertaleData.cs b/UndertaleModLib/UndertaleData.cs index 9c78acad3..ad1fe3d0c 100644 --- a/UndertaleModLib/UndertaleData.cs +++ b/UndertaleModLib/UndertaleData.cs @@ -1,18 +1,27 @@ using System; -using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; using UndertaleModLib.Compiler; using UndertaleModLib.Models; namespace UndertaleModLib { + /// + /// An object representing a Game Maker: Studio data file. + /// + /// This is basically the heart of the data file, which is usually named data.win, data.unx, + /// data.ios or data.droid, depending for which OS the game was compiled for..
+ /// It includes all the data within it accessible by either the -Chunk attribute, + /// but also via already organized attributes such as or . + /// TODO: add more documentation about how a data file works at one point.
public class UndertaleData { - // access resource list by its name + /// + /// Indexer to access the resource list by its name. + /// + /// The resource name to get. + /// if the data file does not contain a property with that name. public object this[string resourceTypeName] { get @@ -33,7 +42,12 @@ public object this[string resourceTypeName] } } - // access resource list by its items type + /// + /// Indexer to access the resource list by its items type. + /// + /// The resource type to get. + /// if the type is not an . + /// if the data file does not contain a property of that type. public object this[Type resourceType] { get @@ -62,76 +76,306 @@ public object this[Type resourceType] } } + /// + /// The FORM chunk of the data file. + /// public UndertaleChunkFORM FORM; + /// + /// General info of the data file. + /// public UndertaleGeneralInfo GeneralInfo => FORM.GEN8?.Object; + + /// + /// General Options of the data file. + /// public UndertaleOptions Options => FORM.OPTN?.Object; + + /// + /// Languages of the data file. + /// public UndertaleLanguage Language => FORM.LANG?.Object; + + /// + /// The used extensions of the data file. + /// public IList Extensions => FORM.EXTN?.List; + + /// + /// The used sounds of the data file. + /// public IList Sounds => FORM.SOND?.List; + + /// + /// The audio groups of the data file. + /// public IList AudioGroups => FORM.AGRP?.List; + + /// + /// The sprites of the data file. + /// public IList Sprites => FORM.SPRT?.List; + + /// + /// The backgrounds (or Tilesets) of the data file. + /// public IList Backgrounds => FORM.BGND?.List; + + /// + /// The paths of the data file. + /// public IList Paths => FORM.PATH?.List; + + /// + /// The scripts of the data file. + /// public IList Scripts => FORM.SCPT?.List; + + /// + /// The global initialization scripts of the data file. + /// public IList GlobalInitScripts => FORM.GLOB?.List; + + /// + /// The global end scripts of the data file. + /// public IList GameEndScripts => FORM.GMEN?.List; + + /// + /// The used shaders of the data file. + /// public IList Shaders => FORM.SHDR?.List; + + /// + /// The fonts of the data file. + /// public IList Fonts => FORM.FONT?.List; + + /// + /// The Timelines of the data file. + /// public IList Timelines => FORM.TMLN?.List; + + /// + /// The game objects of the data file. + /// public IList GameObjects => FORM.OBJT?.List; + + /// + /// The rooms of the data file. + /// public IList Rooms => FORM.ROOM?.List; //[Obsolete("Unused")] // DataFile + + /// + /// The texture page items from the data file. + /// public IList TexturePageItems => FORM.TPAG?.List; + + /// + /// The code entries of the data file. + /// public IList Code => FORM.CODE?.List; + + /// + /// The used variables of the data file. + /// public IList Variables => FORM.VARI?.List; + + /// + /// TODO: Unknown value, need more research. + /// public uint VarCount1 { get => FORM.VARI.VarCount1; set => FORM.VARI.VarCount1 = value; } + + /// + /// TODO: Unknown value, need more research. + /// public uint VarCount2 { get => FORM.VARI.VarCount2; set => FORM.VARI.VarCount2 = value; } + + /// + /// TODO: Unknown value, need more research. + /// public bool DifferentVarCounts { get => FORM.VARI.DifferentVarCounts; set => FORM.VARI.DifferentVarCounts = value; } [Obsolete] public uint InstanceVarCount { get => VarCount1; set => VarCount1 = value; } [Obsolete] public uint InstanceVarCountAgain { get => VarCount2; set => VarCount2 = value; } + + /// + /// TODO: Unknown value, need more research. + /// public uint MaxLocalVarCount { get => FORM.VARI.MaxLocalVarCount; set => FORM.VARI.MaxLocalVarCount = value; } + + /// + /// The functions of the data file. + /// public IList Functions => FORM.FUNC?.Functions; + + /// + /// The code locals of the data file. + /// public IList CodeLocals => FORM.FUNC?.CodeLocals; + + /// + /// The used strings of the data file. + /// public IList Strings => FORM.STRG?.List; + + /// + /// The embedded images of the data file. This is used to store built-in particle sprites, + /// every time you use part_sprite functions. + /// public IList EmbeddedImages => FORM.EMBI?.List; + + /// + /// The embedded textures of the data file. + /// public IList EmbeddedTextures => FORM.TXTR?.List; + + /// + /// The texture group infos of the data file. + /// public IList TextureGroupInfo => FORM.TGIN?.List; + + /// + /// The embedded audio of the data file. + /// public IList EmbeddedAudio => FORM.AUDO?.List; public UndertaleTags Tags => FORM.TAGS?.Object; + + /// + /// The animation curves of the data file. + /// public IList AnimationCurves => FORM.ACRV?.List; + + /// + /// The sequences of the data file. + /// public IList Sequences => FORM.SEQN?.List; + /// + /// Whether this is an unsupported bytecode version. + /// public bool UnsupportedBytecodeVersion = false; + + /// + /// Whether the Texture Page Items (TPGA) chunk is 4 byte aligned. + /// public bool IsTPAG4ByteAligned = false; + + /// + /// Whether the data file has short circuiting enabled. + /// public bool ShortCircuit = true; + + /// + /// Whether the data file is from version GMS2.2.2.302 + /// public bool GMS2_2_2_302 = false; + + /// + /// Whether the data file is from version GMS2.3 + /// public bool GMS2_3 = false; + + /// + /// Whether the data file is from version GMS2.3.1 + /// public bool GMS2_3_1 = false; + + /// + /// Whether the data file is from version GMS2.3.2 + /// public bool GMS2_3_2 = false; + + /// + /// Whether the data file uses the QOI format for images. + /// public bool UseQoiFormat = false; + + /// + /// Whether the data file uses BZip compression. + /// public bool UseBZipFormat = false; + + /// + /// Whether the data file is from version GMS2022.1. + /// public bool GMS2022_1 = false; + + /// + /// Whether the data file is from version GMS2022.2. + /// public bool GMS2022_2 = false; + + /// + /// Whether the data file is from version GMS2022.3. + /// public bool GM2022_3 = false; - public ToolInfo ToolInfo = new ToolInfo(); + + /// + /// Some info for the editor to store data on. + /// + public readonly ToolInfo ToolInfo = new ToolInfo(); + + /// + /// Shows the current padding value. -1 indicates a pre 1.4.9999 padding, where the default is 16. + /// public int PaddingAlignException = -1; + /// + /// A list of known Game Maker: Studio constants and variables. + /// public BuiltinList BuiltinList; - public Dictionary KnownSubFunctions; // Cache for known 2.3-style function names for compiler speedups. Can be re-built by setting this to null. + /// + /// Cache for known 2.3-style function names for compiler speedups. Can be re-built by setting this to null. + /// + public Dictionary KnownSubFunctions; + + //Profile mode related properties + + //TODO: Why are the functions that deal with the cache in a completely different place than the cache parameters? These have *no* place of being here. + /// + /// A of cached decompiled code, + /// with the code name as the Key and the decompiled code text as the value. + /// public ConcurrentDictionary GMLCache { get; set; } + + /// + /// A list of names of code entries which failed to compile or decompile. + /// public List GMLCacheFailed { get; set; } + + /// + /// A list of names of modified code entries. + /// public ConcurrentBag GMLCacheChanged { get; set; } = new(); + + /// + /// A list of names of code entries that were edited before the "Use decompiled code cache" setting was enabled. + /// public List GMLEditedBefore { get; set; } + + /// + /// Whether the decompiled code cache has been saved to disk with no new cache changes happening since then. + /// public bool GMLCacheWasSaved { get; set; } - public bool GMLCacheIsReady { get; set; } = true; + /// + /// Whether the decompiled code cache is generated. This will be if it's currently generating and + /// otherwise. + /// + public bool GMLCacheIsReady { get; set; } = true; + /// + /// Get a resource from the data file by name. + /// + /// The name of the desired resource. + /// Whether to ignore casing while searching. + /// The . public UndertaleNamedResource ByName(string name, bool ignoreCase = false) { // TODO: Check if those are all possible types @@ -152,6 +396,14 @@ public UndertaleNamedResource ByName(string name, bool ignoreCase = false) (UndertaleNamedResource)null; } + /// + /// Reports the zero-based index of the first occurence of the specified . + /// + /// The object to get the index of. + /// Whether to throw if is not a valid object. + /// The zero-based index position of the parameter if it is found or -2 if it is not. + /// is + /// and could not be found. public int IndexOf(UndertaleNamedResource obj, bool panicIfInvalid = true) { if (obj is UndertaleSound) @@ -199,7 +451,10 @@ internal int IndexOfByName(string line) throw new NotImplementedException(); } - // Test if this data.win was built by GameMaker Studio 2. + /// + /// Reports whether the data file was build by Game Maker Studio 2. + /// + /// if yes, if not. public bool IsGameMaker2() { return IsVersionAtLeast(2, 0, 0, 0); @@ -209,11 +464,19 @@ public bool IsGameMaker2() // Old Versions: https://store.yoyogames.com/downloads/gm-studio/release-notes-studio-old.html // https://web.archive.org/web/20150304025626/https://store.yoyogames.com/downloads/gm-studio/release-notes-studio.html // Early Access: https://web.archive.org/web/20181002232646/http://store.yoyogames.com:80/downloads/gm-studio-ea/release-notes-studio.html - public bool TestGMS1Version(uint stableBuild, uint betaBuild, bool allowGMS2 = false) + private bool TestGMS1Version(uint stableBuild, uint betaBuild, bool allowGMS2 = false) { return (allowGMS2 || !IsGameMaker2()) && (IsVersionAtLeast(1, 0, 0, stableBuild) || (IsVersionAtLeast(1, 0, 0, betaBuild) && !IsVersionAtLeast(1, 0, 0, 1000))); } + /// + /// Reports whether the version of the data file is the same or higher than a specified version. + /// + /// The major version. + /// The minor version. + /// The release version. + /// The build version. + /// Whether the version of the data file is the same or higher than a specified version. public bool IsVersionAtLeast(uint major, uint minor, uint release, uint build) { if (GeneralInfo.Major != major) @@ -231,6 +494,10 @@ public bool IsVersionAtLeast(uint major, uint minor, uint release, uint build) return true; // The version is exactly what supplied. } + /// + /// TODO: needs to be documented on what this does. + /// + /// TODO public int GetBuiltinSoundGroupID() { // It is known it works this way in 1.0.1266. The exact version which changed this is unknown. @@ -238,11 +505,19 @@ public int GetBuiltinSoundGroupID() return TestGMS1Version(1354, 161, true) ? 0 : 1; } + /// + /// Reports whether the data file was compiled with YYC. + /// + /// if yes, if not. public bool IsYYC() { return GeneralInfo != null && Code == null; } + /// + /// TODO: Undocumented helper method. + /// + /// TODO public uint ExtensionFindLastId() { // The reason: @@ -269,6 +544,10 @@ public uint ExtensionFindLastId() return id; } + /// + /// Creates a new empty data file. + /// + /// The newly created data file. public static UndertaleData CreateNew() { UndertaleData data = new UndertaleData(); @@ -421,13 +700,13 @@ public static UndertaleVariable EnsureDefined(this IList list oldId = (uint)id; } else if (!data.DifferentVarCounts) - { + { // Bytecode 16+ data.VarCount1++; data.VarCount2++; } else - { + { // Bytecode 15 if (inst == UndertaleInstruction.InstanceType.Self && !isBuiltin) { @@ -507,11 +786,24 @@ public static UndertaleExtensionFunction DefineExtensionFunction(this IList + /// An info handle for an editor to store data on. + /// public class ToolInfo { - // Info handle for the actual editor to store data on + /// + /// Whether profile mode is enabled. + /// public bool ProfileMode = false; + + /// + /// The location of the profiles folder. + /// public string AppDataProfiles = null; + + /// + /// The MD5 hash of the current file. + /// public string CurrentMD5 = "Unknown"; } } diff --git a/UndertaleModLib/UndertaleModLib.csproj b/UndertaleModLib/UndertaleModLib.csproj index e376b1751..2def49f15 100644 --- a/UndertaleModLib/UndertaleModLib.csproj +++ b/UndertaleModLib/UndertaleModLib.csproj @@ -10,6 +10,7 @@ true + True true @@ -19,6 +20,7 @@ true + true diff --git a/UndertaleModTests/GameScriptTests.cs b/UndertaleModTests/GameScriptTests.cs index cc998cc6c..97f88ad33 100644 --- a/UndertaleModTests/GameScriptTests.cs +++ b/UndertaleModTests/GameScriptTests.cs @@ -36,14 +36,14 @@ public GameScriptTestBase(string path, string md5) : base(path, md5) public bool IsAppClosed => throw new NotImplementedException(); - public void ChangeSelection(object newsel) + public void ChangeSelection(object newSelection) { } public void EnsureDataLoaded() { } - public async Task Make_New_File() + public async Task MakeNewDataFile() { await Task.Delay(1); //dummy await return true; @@ -127,17 +127,17 @@ public void AddProgress(int amount) { Console.WriteLine($"AddProgress(): {amount}"); } - public void IncProgress() + public void IncrementProgress() { - Console.WriteLine("IncProgress()"); + Console.WriteLine("IncrementProgress()"); } - public void AddProgressP(int amount) + public void AddProgressParallel(int amount) { - Console.WriteLine($"AddProgressP(): {amount}"); + Console.WriteLine($"AddProgressParallel(): {amount}"); } - public void IncProgressP() + public void IncrementProgressParallel() { - Console.WriteLine("IncProgressP()"); + Console.WriteLine("IncrementProgressParallel()"); } public int GetProgress() { @@ -149,9 +149,9 @@ public void SetProgress(int value) Console.WriteLine($"SetProgress(): {value}"); } - public string ScriptInputDialog(string titleText, string labelText, string defaultInputBoxText, string cancelButtonText, string submitButtonText, bool isMultiline, bool preventClose) + public string ScriptInputDialog(string title, string label, string defaultInput, string cancelText, string submitText, bool isMultiline, bool preventClose) { - Console.Write(labelText + " "); + Console.Write(label + " "); string ret = Console.ReadLine(); return ret; @@ -160,19 +160,19 @@ public string SimpleTextInput(string titleText, string labelText, string default { return ScriptInputDialog(titleText, labelText, defaultInputBoxText, "Cancel", "Submit", isMultiline, false); } - public void SimpleTextOutput(string titleText, string labelText, string defaultInputBoxText, bool isMultiline) + public void SimpleTextOutput(string titleText, string labelText, string message, bool isMultiline) { - Console.WriteLine($"SimpleTextOutput(): \"{titleText}\", \"{labelText}\", *defaultInputBoxText* (length - {defaultInputBoxText.Length}), {isMultiline}"); + Console.WriteLine($"SimpleTextOutput(): \"{titleText}\", \"{labelText}\", *defaultInputBoxText* (length - {message.Length}), {isMultiline}"); } - public async Task ClickableTextOutput(string title, string query, int resultsCount, IOrderedEnumerable>> resultsDict, bool editorDecompile, IOrderedEnumerable failedList = null) + public async Task ClickableSearchOutput(string title, string query, int resultsCount, IOrderedEnumerable>> resultsDict, bool showInDecompiledView, IOrderedEnumerable failedList = null) { - Console.WriteLine($"ClickableTextOutput(): \"{title}\", \"{query}\", {resultsCount}, *resultsDict* (length - {resultsDict.Count()}), {editorDecompile.ToString().ToLower()}" + Console.WriteLine($"ClickableSearchOutput(): \"{title}\", \"{query}\", {resultsCount}, *resultsDict* (length - {resultsDict.Count()}), {showInDecompiledView.ToString().ToLower()}" + failedList is not null ? $", *failedList* (length - {failedList.Count()})" : string.Empty); await Task.Delay(1); //dummy await } - public async Task ClickableTextOutput(string title, string query, int resultsCount, IDictionary> resultsDict, bool editorDecompile, IEnumerable failedList = null) + public async Task ClickableSearchOutput(string title, string query, int resultsCount, IDictionary> resultsDict, bool showInDecompiledView, IEnumerable failedList = null) { - Console.WriteLine($"ClickableTextOutput(): \"{title}\", \"{query}\", {resultsCount}, *resultsDict* (length - {resultsDict.Count}), {editorDecompile.ToString().ToLower()}" + Console.WriteLine($"ClickableSearchOutput(): \"{title}\", \"{query}\", {resultsCount}, *resultsDict* (length - {resultsDict.Count}), {showInDecompiledView.ToString().ToLower()}" + failedList is not null ? $", *failedList* (length - {failedList.Count()})" : string.Empty); await Task.Delay(1); //dummy await } @@ -224,26 +224,26 @@ public void SyncBinding(string resourceType, bool enable) { Console.WriteLine($"SyncBinding(): \"{resourceType}\", {enable}"); } - public void SyncBinding(bool enable = false) + public void DisableAllSyncBindings() { - Console.WriteLine($"SyncBinding(): {enable}"); + Console.WriteLine($"Disabling all enabled synced bindings."); } - public void StartUpdater() + public void StartProgressBarUpdater() { Console.WriteLine("Starting progress bar updater..."); } - public async Task StopUpdater() + public async Task StopProgressBarUpdater() { Console.WriteLine("Stopping progress bar updater..."); await Task.Delay(1); //dummy await } - public async Task GenerateGMLCache(ThreadLocal decompileContext = null, object dialog = null, bool isSaving = false) + public async Task GenerateGMLCache(ThreadLocal decompileContext = null, object dialog = null, bool clearGMLEditedBefore = false) { Console.WriteLine(string.Format("GenerateGMLCache(): *decompileContext*{0}, *dialog*{1}, {2}", decompileContext is null ? " (null)" : "", dialog is null ? " (null)" : "", - isSaving.ToString().ToLower()) + clearGMLEditedBefore.ToString().ToLower()) ); await Task.Delay(1); //dummy await @@ -272,7 +272,7 @@ public void ScriptError(string error, string title = "Error", bool SetConsoleTex throw new NotImplementedException(); } - public string PromptChooseDirectory(string prompt) + public string PromptChooseDirectory() { throw new NotImplementedException(); } diff --git a/UndertaleModTool/CommunityScripts/ExternalizeAllOGGs.csx b/UndertaleModTool/CommunityScripts/ExternalizeAllOGGs.csx index 51d6d5d92..0561bf088 100644 --- a/UndertaleModTool/CommunityScripts/ExternalizeAllOGGs.csx +++ b/UndertaleModTool/CommunityScripts/ExternalizeAllOGGs.csx @@ -17,7 +17,7 @@ bool usesAGRPs = (Data.AudioGroups.Count > 0); if (Data.AudioGroups.Count > 1) { - bool warningCheck = ScriptQuestion(@"This game uses external audiogroup.dat files. + bool warningCheck = ScriptQuestion(@"This game uses external audiogroup.dat files. In order to externalize the audio files it will clear all data from the external audiogroup.dat files. It is recommended that you make a backup of your game files. If you have already made a backup and wish to continue, select 'Yes'. @@ -30,8 +30,8 @@ Otherwise, select 'No', and make a backup of the game before using this script. //Overwrite Folder Check One if (Directory.Exists(winFolder + "Exported_Sounds\\")) { - bool overwriteCheckOne = ScriptQuestion(@"An 'Exported_Sounds' folder already exists. -Would you like to remove it? This may some time. + bool overwriteCheckOne = ScriptQuestion(@"An 'Exported_Sounds' folder already exists. +Would you like to remove it? This may some time. Note: If an error window stating that 'the directory is not empty' appears, please try again or delete the folder manually. "); @@ -60,16 +60,16 @@ You will have to check the code for these functions and change it accordingly. } SetProgressBar(null, "Externalizing Sounds...", 0, Data.Sounds.Count); -StartUpdater(); +StartProgressBarUpdater(); SyncBinding("Strings", true); await Task.Run(() => { DumpSounds(); // This runs sync, because it has to load audio groups. ExternalizeSounds(); // This runs sync, because it has to load audio groups. }); -SyncBinding(false); +DisableAllSyncBindings(); -await StopUpdater(); +await StopProgressBarUpdater(); HideProgressBar(); ScriptMessage("Externalization Complete.\nExternalized " + sounds.ToString() + " sounds.\n\nNOTE: You will need to convert any external WAV files into OGG files.\nThen replace the WAV file with the OGG file.\nOtherwise the sound will not play.\nA batch conversion tool such as 'LameXP' will help.\nCheck the #faq for more information or message Grossley#2869 on Discord."); @@ -80,12 +80,12 @@ void ExternalizeSounds() ExternalizeSound(sound); } -string GetFolder(string path) +string GetFolder(string path) { return Path.GetDirectoryName(path) + Path.DirectorySeparatorChar; } -void ExternalizeSound(UndertaleSound sound) +void ExternalizeSound(UndertaleSound sound) { bool flagCompressed = sound.Flags.HasFlag(UndertaleSound.AudioEntryFlags.IsCompressed); bool flagEmbedded = sound.Flags.HasFlag(UndertaleSound.AudioEntryFlags.IsEmbedded); @@ -95,8 +95,8 @@ void ExternalizeSound(UndertaleSound sound) string searchName = sound.Name.Content; string searchFilePath = winFolder + "Exported_Sounds\\"; // If it's not an external file already, setup the sound entry such that it is external. - if (flagCompressed == true || flagEmbedded == true) - { + if (flagCompressed == true || flagEmbedded == true) + { // 4. string[] files = Directory.GetFiles(searchFilePath, searchName + ".*", SearchOption.AllDirectories); var path_result = files[0]; @@ -117,7 +117,7 @@ void ExternalizeSound(UndertaleSound sound) //Array.Resize(audioGroupDat.EmbeddedAudio[_audioid].Data, 1); audioGroupDat.EmbeddedAudio[_audioid].Data = new byte[1]; audioGroupDat.EmbeddedAudio[_audioid].Data[0] = 0; - + var audioGroupWriteStream = (new FileStream(winFolder + "audiogroup" + _groupid.ToString() + ".dat", FileMode.Create)); UndertaleIO.Write(audioGroupWriteStream, audioGroupDat); // Write it to the disk audioGroupWriteStream.Dispose(); @@ -137,9 +137,9 @@ void ExternalizeSound(UndertaleSound sound) sound.AudioGroup = Data.AudioGroups[Data.GetBuiltinSoundGroupID()]; } // if it doesn't then we shouldn't care, it's always null. - + sounds++; - IncProgress(); + IncrementProgress(); } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -147,7 +147,7 @@ void ExternalizeSound(UndertaleSound sound) byte[] EMPTY_WAV_FILE_BYTES = System.Convert.FromBase64String("UklGRiQAAABXQVZFZm10IBAAAAABAAIAQB8AAAB9AAAEABAAZGF0YQAAAAA="); string DEFAULT_AUDIOGROUP_NAME = "audiogroup_default"; -void MakeFolder(String folderName) +void MakeFolder(String folderName) { if (!Directory.Exists(winFolder + folderName + "/")) Directory.CreateDirectory(winFolder + folderName + "/"); @@ -155,25 +155,25 @@ void MakeFolder(String folderName) Dictionary> loadedAudioGroups; -IList GetAudioGroupData(UndertaleSound sound) +IList GetAudioGroupData(UndertaleSound sound) { if (loadedAudioGroups == null) loadedAudioGroups = new Dictionary>(); - + string audioGroupName = sound.AudioGroup != null ? sound.AudioGroup.Name.Content : DEFAULT_AUDIOGROUP_NAME; if (loadedAudioGroups.ContainsKey(audioGroupName)) return loadedAudioGroups[audioGroupName]; - + string groupFilePath = winFolder + "audiogroup" + sound.GroupID + ".dat"; if (!File.Exists(groupFilePath)) return null; // Doesn't exist. - - try + + try { UndertaleData data = null; using (var stream = new FileStream(groupFilePath, FileMode.Open, FileAccess.Read)) data = UndertaleIO.Read(stream, warning => ScriptMessage("A warning occured while trying to load " + audioGroupName + ":\n" + warning)); - + loadedAudioGroups[audioGroupName] = data.EmbeddedAudio; return data.EmbeddedAudio; } catch (Exception e) @@ -183,12 +183,12 @@ IList GetAudioGroupData(UndertaleSound sound) } } -byte[] GetSoundData(UndertaleSound sound) +byte[] GetSoundData(UndertaleSound sound) { if (sound.AudioFile != null) return sound.AudioFile.Data; - - if (sound.GroupID > Data.GetBuiltinSoundGroupID()) + + if (sound.GroupID > Data.GetBuiltinSoundGroupID()) { IList audioGroup = GetAudioGroupData(sound); if (audioGroup != null) @@ -197,7 +197,7 @@ byte[] GetSoundData(UndertaleSound sound) return EMPTY_WAV_FILE_BYTES; } -void DumpSounds() +void DumpSounds() { foreach (UndertaleSound sound in Data.Sounds) DumpSound(sound); diff --git a/UndertaleModTool/CommunityScripts/UndertaleWithJSONs.csx b/UndertaleModTool/CommunityScripts/UndertaleWithJSONs.csx index 68b87e542..d47ecd06b 100644 --- a/UndertaleModTool/CommunityScripts/UndertaleWithJSONs.csx +++ b/UndertaleModTool/CommunityScripts/UndertaleWithJSONs.csx @@ -25,7 +25,7 @@ else if (Data?.GeneralInfo?.DisplayName?.Content.ToLower() == "deltarune chapter string langFolder = GetFolder(FilePath) + "lang" + Path.DirectorySeparatorChar; ThreadLocal DECOMPILE_CONTEXT = new ThreadLocal(() => new GlobalDecompileContext(Data, false)); -if (Directory.Exists(langFolder)) +if (Directory.Exists(langFolder)) { ScriptError("The lang files already exist.", "Error"); return; @@ -55,7 +55,7 @@ await Task.Run(() => { void IncProgressLocal() { if (GetProgress() < maxCount) - IncProgress(); + IncrementProgress(); } string GetFolder(string path) { @@ -137,11 +137,11 @@ ScriptMessage("Complete."); void DumpJSON(string language) { var lang_file = Data.Code.ByName("gml_Script_textdata_" + language); - try + try { File.WriteAllText(Path.Combine(langFolder, "lang_" + language + ".json"), (lang_file != null ? Decompiler.Decompile(lang_file, DECOMPILE_CONTEXT.Value) : "")); - } - catch (Exception e) + } + catch (Exception e) { throw new ScriptException("gml_Script_textdata_" + language + " has an error that prevents creation of JSONs."); } @@ -177,11 +177,11 @@ void MakeJSON(string language) pattern = @""" \+ '""'"; replacement = @"\"""""; input = Regex.Replace(input, pattern, replacement); - + pattern = @"'""'"; replacement = @"\"""; input = Regex.Replace(input, pattern, replacement); - + input = input.Replace(@"\"",", @"\"""","); pattern = @"ds_map_add\(global\.text_data_.., ("".*""), ("".*"")\)"; diff --git a/UndertaleModTool/DemoScripts/TestExportAllCode.csx b/UndertaleModTool/DemoScripts/TestExportAllCode.csx index c468930a4..3c745df9c 100644 --- a/UndertaleModTool/DemoScripts/TestExportAllCode.csx +++ b/UndertaleModTool/DemoScripts/TestExportAllCode.csx @@ -24,7 +24,7 @@ bool isErrorCodeEntry = false; ScriptMessage("If UndertaleModTool crashes during code export, or another serious error of that nature occurs, this script will record it. Please reload the game into the tool in the event the tool crashes and re-run this script until it completes successfully without crashing. A full record of code entries with fatal decompilation problems (if they exist) will be recorded by the end in \"Errored_Code_Entries.txt\"."); SetProgressBar(null, "Code Entries", 0, Data.Code.Count); -StartUpdater(); +StartProgressBarUpdater(); if (File.Exists(path_error)) { @@ -132,7 +132,7 @@ await Task.Run(() => { } } - IncProgress(); + IncrementProgress(); } }); if (write) @@ -147,7 +147,7 @@ if (write) } } -await StopUpdater(); +await StopProgressBarUpdater(); HideProgressBar(); ScriptMessage("Export Complete.\n\nLocation: " + codeFolder); if (File.Exists(path_error2)) diff --git a/UndertaleModTool/HelperScripts/ExportAllSoundsOld.csx b/UndertaleModTool/HelperScripts/ExportAllSoundsOld.csx index 95560b842..1d79f1534 100644 --- a/UndertaleModTool/HelperScripts/ExportAllSoundsOld.csx +++ b/UndertaleModTool/HelperScripts/ExportAllSoundsOld.csx @@ -7,7 +7,7 @@ using System.Threading.Tasks; EnsureDataLoaded(); string sndFolder = GetFolder(FilePath) + "Export_Sounds" + Path.DirectorySeparatorChar; -if (Directory.Exists(sndFolder)) +if (Directory.Exists(sndFolder)) { ScriptError("A sound export already exists. Please remove it.", "Error"); return; @@ -16,11 +16,11 @@ if (Directory.Exists(sndFolder)) Directory.CreateDirectory(sndFolder); SetProgressBar(null, "Sounds", 0, Data.Sounds.Count); -StartUpdater(); +StartProgressBarUpdater(); await DumpSounds(); -await StopUpdater(); +await StopProgressBarUpdater(); HideProgressBar(); ScriptMessage("Export Complete.\n\nLocation: " + sndFolder); @@ -31,15 +31,15 @@ string GetFolder(string path) } -async Task DumpSounds() +async Task DumpSounds() { await Task.Run(() => Parallel.ForEach(Data.Sounds, DumpSound)); } -void DumpSound(UndertaleSound sound) +void DumpSound(UndertaleSound sound) { if (sound.AudioFile != null && !File.Exists(sndFolder + sound.File.Content)) File.WriteAllBytes(sndFolder + sound.File.Content, sound.AudioFile.Data); - IncProgressP(); + IncrementProgress(); } diff --git a/UndertaleModTool/MainWindow.xaml.cs b/UndertaleModTool/MainWindow.xaml.cs index 636c0e1b1..6a2b573f0 100644 --- a/UndertaleModTool/MainWindow.xaml.cs +++ b/UndertaleModTool/MainWindow.xaml.cs @@ -592,9 +592,9 @@ public async Task ListenChildConnection(string key) private async void Command_New(object sender, ExecutedRoutedEventArgs e) { - await Make_New_File(); + await MakeNewDataFile(); } - public async Task Make_New_File() + public async Task MakeNewDataFile() { if (Data != null) { @@ -1202,7 +1202,7 @@ await Task.Run(async () => { if (updateCache) { await GenerateGMLCache(null, dialog, true); - await StopUpdater(); + await StopProgressBarUpdater(); } string[] codeNames = Data.Code.Where(x => x.ParentEntry is null).Select(x => x.Name.Content).ToArray(); @@ -1236,7 +1236,7 @@ await Task.Run(async () => { }); } - public async Task GenerateGMLCache(ThreadLocal decompileContext = null, object dialog = null, bool isSaving = false) + public async Task GenerateGMLCache(ThreadLocal decompileContext = null, object dialog = null, bool clearGMLEditedBefore = false) { if (!SettingsWindow.UseGMLCache) return false; @@ -1280,7 +1280,7 @@ public async Task GenerateGMLCache(ThreadLocal dec if (Data.GMLCache.IsEmpty) { SetProgressBar(null, "Generating decompiled code cache...", 0, Data.Code.Count); - StartUpdater(); + StartProgressBarUpdater(); await Task.Run(() => Parallel.ForEach(Data.Code, (code) => { @@ -1296,7 +1296,7 @@ await Task.Run(() => Parallel.ForEach(Data.Code, (code) => } } - IncProgressP(); + IncrementProgressParallel(); })); Data.GMLEditedBefore = new(Data.GMLCacheChanged); @@ -1328,7 +1328,7 @@ await Task.Run(() => Parallel.ForEach(Data.Code, (code) => if (codeToUpdate.Count > 0) { SetProgressBar(null, "Updating decompiled code cache...", 0, codeToUpdate.Count); - StartUpdater(); + StartProgressBarUpdater(); await Task.Run(() => Parallel.ForEach(codeToUpdate.Select(x => Data.Code.ByName(x)), (code) => { @@ -1346,10 +1346,10 @@ await Task.Run(() => Parallel.ForEach(codeToUpdate.Select(x => Data.Code.ByName( } } - IncProgressP(); + IncrementProgressParallel(); })); - if (isSaving) + if (clearGMLEditedBefore) Data.GMLEditedBefore.Clear(); else Data.GMLEditedBefore = Data.GMLEditedBefore.Union(Data.GMLCacheChanged).ToList(); @@ -1358,7 +1358,7 @@ await Task.Run(() => Parallel.ForEach(codeToUpdate.Select(x => Data.Code.ByName( Data.GMLCacheFailed = Data.GMLCacheFailed.Union(failedBag).ToList(); Data.GMLCacheWasSaved = false; } - else if (isSaving) + else if (clearGMLEditedBefore) Data.GMLEditedBefore.Clear(); if (!existedDialog) @@ -1366,7 +1366,7 @@ await Task.Run(() => Parallel.ForEach(codeToUpdate.Select(x => Data.Code.ByName( if (createdDialog) { - await StopUpdater(); + await StopProgressBarUpdater(); HideProgressBar(); } } @@ -1893,15 +1893,15 @@ public void AddProgress(int amount) { progressValue += amount; } - public void IncProgress() + public void IncrementProgress() { progressValue++; } - public void AddProgressP(int amount) //P - Parallel (multithreaded) + public void AddProgressParallel(int amount) //P - Parallel (multithreaded) { Interlocked.Add(ref progressValue, amount); //thread-safe add operation (not the same as "lock ()") } - public void IncProgressP() + public void IncrementProgressParallel() { Interlocked.Increment(ref progressValue); //thread-safe increment } @@ -1972,15 +1972,14 @@ public void SyncBinding(string resourceType, bool enable) } } } - public void SyncBinding(bool enable = false) //disable all sync. bindings + public void DisableAllSyncBindings() //disable all sync. bindings { - if (syncBindings.Count != 0) - { - foreach (string resType in syncBindings) - BindingOperations.DisableCollectionSynchronization(Data[resType] as IEnumerable); + if (syncBindings.Count <= 0) return; - syncBindings.Clear(); - } + foreach (string resType in syncBindings) + BindingOperations.DisableCollectionSynchronization(Data[resType] as IEnumerable); + + syncBindings.Clear(); } private void ProgressUpdater() @@ -2010,7 +2009,7 @@ private void ProgressUpdater() Thread.Sleep(100); //10 times per second } } - public void StartUpdater() + public void StartProgressBarUpdater() { if (cts is not null) ScriptWarning("Warning - there is another progress bar updater task running (hangs) in the background.\nRestart the application to prevent some unexpected behavior."); @@ -2020,22 +2019,22 @@ public void StartUpdater() updater = Task.Run(ProgressUpdater); } - public async Task StopUpdater() //async because "Wait()" blocks UI thread + public async Task StopProgressBarUpdater() //async because "Wait()" blocks UI thread { - if (cts is not null) - { - cts.Cancel(); + if (cts is null) return; - if (await Task.Run(() => !updater.Wait(2000))) //if ProgressUpdater isn't responding - ScriptError("Stopping the progress bar updater task is failed.\nIt's highly recommended to restart the application.", "Script error", false); - else - { - cts.Dispose(); - cts = null; - } + cts.Cancel(); - updater.Dispose(); + if (await Task.Run(() => !updater.Wait(2000))) //if ProgressUpdater isn't responding + ScriptError("Stopping the progress bar updater task is failed.\nIt's highly recommended to restart the application.", + "Script error", false); + else + { + cts.Dispose(); + cts = null; } + + updater.Dispose(); } public void OpenCodeFile(string name, CodeEditorMode editorDecompile) @@ -2200,7 +2199,7 @@ private async Task RunScriptNow(string path) if (!isScriptException) excString = ProcessException(in exc, in scriptText); - await StopUpdater(); + await StopProgressBarUpdater(); Console.WriteLine(exc.ToString()); Dispatcher.Invoke(() => CommandBox.Text = exc.Message); @@ -2221,7 +2220,7 @@ public string PromptLoadFile(string defaultExt, string filter) } #pragma warning disable CA1416 - public string PromptChooseDirectory(string prompt) + public string PromptChooseDirectory() { VistaFolderBrowserDialog folderBrowser = new VistaFolderBrowserDialog(); // vista dialog doesn't suffix the folder name with "/", so we're fixing it here. @@ -2323,16 +2322,16 @@ public string SimpleTextInput(string titleText, string labelText, string default } } - public void SimpleTextOutput(string titleText, string labelText, string defaultText, bool isMultiline) + public void SimpleTextOutput(string titleText, string labelText, string message, bool isMultiline) { - TextInput textOutput = new TextInput(labelText, titleText, defaultText, isMultiline, true); //read-only mode + TextInput textOutput = new TextInput(labelText, titleText, message, isMultiline, true); //read-only mode textOutput.Show(); } - public async Task ClickableTextOutput(string title, string query, int resultsCount, IOrderedEnumerable>> resultsDict, bool editorDecompile, IOrderedEnumerable failedList = null) + public async Task ClickableSearchOutput(string title, string query, int resultsCount, IOrderedEnumerable>> resultsDict, bool showInDecompiledView, IOrderedEnumerable failedList = null) { await Task.Delay(150); //wait until progress bar status is displayed - ClickableTextOutput textOutput = new(title, query, resultsCount, resultsDict, editorDecompile, failedList); + ClickableTextOutput textOutput = new(title, query, resultsCount, resultsDict, showInDecompiledView, failedList); await textOutput.Dispatcher.InvokeAsync(textOutput.GenerateResults); _ = Task.Factory.StartNew(textOutput.FillingNotifier, TaskCreationOptions.LongRunning); //"LongRunning" = prefer creating a new thread @@ -2341,11 +2340,11 @@ public async Task ClickableTextOutput(string title, string query, int resultsCou PlayInformationSound(); } - public async Task ClickableTextOutput(string title, string query, int resultsCount, IDictionary> resultsDict, bool editorDecompile, IEnumerable failedList = null) + public async Task ClickableSearchOutput(string title, string query, int resultsCount, IDictionary> resultsDict, bool showInDecompiledView, IEnumerable failedList = null) { await Task.Delay(150); - ClickableTextOutput textOutput = new(title, query, resultsCount, resultsDict, editorDecompile, failedList); + ClickableTextOutput textOutput = new(title, query, resultsCount, resultsDict, showInDecompiledView, failedList); await textOutput.Dispatcher.InvokeAsync(textOutput.GenerateResults); _ = Task.Factory.StartNew(textOutput.FillingNotifier, TaskCreationOptions.LongRunning); @@ -2360,9 +2359,9 @@ public void ScriptOpenURL(string url) OpenBrowser(url); } - public string ScriptInputDialog(string titleText, string labelText, string defaultInputBoxText, string cancelButtonText, string submitButtonText, bool isMultiline, bool preventClose) + public string ScriptInputDialog(string title, string label, string defaultInput, string cancelText, string submitText, bool isMultiline, bool preventClose) { - TextInputDialog dlg = new TextInputDialog(titleText, labelText, defaultInputBoxText, cancelButtonText, submitButtonText, isMultiline, preventClose); + TextInputDialog dlg = new TextInputDialog(title, label, defaultInput, cancelText, submitText, isMultiline, preventClose); bool? dlgResult = dlg.ShowDialog(); if (!dlgResult.HasValue || dlgResult == false) diff --git a/UndertaleModTool/ProfileSystem.cs b/UndertaleModTool/ProfileSystem.cs index 2d799d0c9..1eb235997 100644 --- a/UndertaleModTool/ProfileSystem.cs +++ b/UndertaleModTool/ProfileSystem.cs @@ -397,7 +397,7 @@ public void DirectoryCopy(string sourceDirName, string destDirName, bool copySub DirectoryInfo[] dirs = dir.GetDirectories(); - // If the destination directory doesn't exist, create it. + // If the destination directory doesn't exist, create it. Directory.CreateDirectory(destDirName); // Get the files in the directory and copy them to the new location. @@ -434,49 +434,5 @@ public void DirectoryCopy(string sourceDirName, string destDirName, bool copySub MessageBox.Show("DirectoryCopy error! Send this to Grossley#2869 and make an issue on Github\n" + exc.ToString()); } } - - public bool AreFilesIdentical(string file1, string file2) - { - int file1byte, file2byte; - FileStream fs1, fs2; - - // Open the two files. - fs1 = new FileStream(file1, FileMode.Open); - fs2 = new FileStream(file2, FileMode.Open); - - // Check the file sizes. If they are not the same, the files - // are not the same. - if (fs1.Length != fs2.Length) - { - // Close the files - fs1.Close(); - fs2.Close(); - - // Return false to indicate files are different - return false; - } - else - { - // Read and compare a byte from each file until either a - // non-matching set of bytes is found or until the end of - // file1 is reached. - do - { - // Read one byte from each file. - file1byte = fs1.ReadByte(); - file2byte = fs2.ReadByte(); - } - while (file1byte == file2byte && file1byte != -1); - - // Close the files. - fs1.Close(); - fs2.Close(); - - // Return the success of the comparison. "file1byte" is - // equal to "file2byte" at this point only if the files are - // the same. - return (file1byte - file2byte) == 0; - } - } } } diff --git a/UndertaleModTool/Repackers/ApplyBasicGraphicsMod.csx b/UndertaleModTool/Repackers/ApplyBasicGraphicsMod.csx index e090aec49..f5d314c95 100644 --- a/UndertaleModTool/Repackers/ApplyBasicGraphicsMod.csx +++ b/UndertaleModTool/Repackers/ApplyBasicGraphicsMod.csx @@ -1,147 +1,147 @@ -using System; -using System.IO; -using System.Drawing; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using UndertaleModLib.Util; - -EnsureDataLoaded(); - -// At this point, this just imports the sprites. -string importFolder = PromptChooseDirectory("Import From Where"); -if (importFolder == null) - throw new ScriptException("The import folder was not set."); - -string[] dirFiles = Directory.GetFiles(importFolder); - -//Stop the script if there's missing sprite entries or w/e. -foreach (string file in dirFiles) -{ - string FileNameWithExtension = Path.GetFileName(file); - if (!FileNameWithExtension.EndsWith(".png")) - continue; // Restarts loop if file is not a valid mask asset. - string stripped = Path.GetFileNameWithoutExtension(file); - int lastUnderscore = stripped.LastIndexOf('_'); - string spriteName = ""; - try - { - spriteName = stripped.Substring(0, lastUnderscore); - } - catch - { - throw new ScriptException("Getting the sprite name of " + FileNameWithExtension + " failed."); - } - if (Data.Sprites.ByName(spriteName) == null) // Reject non-existing sprites - { - throw new ScriptException(FileNameWithExtension + " could not be imported as the sprite " + spriteName + " does not exist."); - } - using (Image img = Image.FromFile(file)) - { - if ((Data.Sprites.ByName(spriteName).Width != (uint)img.Width) || (Data.Sprites.ByName(spriteName).Height != (uint)img.Height)) - throw new ScriptException(FileNameWithExtension + " is not the proper size to be imported! Please correct this before importing! The proper dimensions are width: " + Data.Sprites.ByName(spriteName).Width.ToString() + " px, height: " + Data.Sprites.ByName(spriteName).Height.ToString() + " px."); - } - - Int32 validFrameNumber = 0; - try - { - validFrameNumber = Int32.Parse(stripped.Substring(lastUnderscore + 1)); - } - catch - { - throw new ScriptException("The index of " + FileNameWithExtension + " could not be determined."); - } - int frame = 0; - try - { - frame = Int32.Parse(stripped.Substring(lastUnderscore + 1)); - } - catch - { - throw new ScriptException(FileNameWithExtension + " is using letters instead of numbers. The script has stopped for your own protection."); - } - int prevframe = 0; - if (frame != 0) - { - prevframe = (frame - 1); - } - if (frame < 0) - { - throw new ScriptException(spriteName + " is using an invalid numbering scheme. The script has stopped for your own protection."); - } - var prevFrameName = spriteName + "_" + prevframe.ToString() + ".png"; - string[] previousFrameFiles = Directory.GetFiles(importFolder, prevFrameName); - if (previousFrameFiles.Length < 1) - throw new ScriptException(spriteName + " is missing one or more indexes. The detected missing index is: " + prevFrameName); -} - -SetProgressBar(null, "Files", 0, dirFiles.Length); -StartUpdater(); - -await Task.Run(() => { - foreach (string file in dirFiles) - { - IncProgress(); - - string fileName = Path.GetFileName(file); - if (!fileName.EndsWith(".png") || !fileName.Contains("_")) - continue; // Not an image. - - string stripped = Path.GetFileNameWithoutExtension(file); - - int lastUnderscore = stripped.LastIndexOf('_'); - string spriteName = stripped.Substring(0, lastUnderscore); - int frame = Int32.Parse(stripped.Substring(lastUnderscore + 1)); - - UndertaleSprite sprite = Data.Sprites.ByName(spriteName); - - if (frame < sprite.Textures.Count) - { - try - { - Bitmap bmp; - using (var ms = new MemoryStream(TextureWorker.ReadTextureBlob(file))) - { - bmp = new Bitmap(ms); - } - bmp.SetResolution(96.0F, 96.0F); - var width = (uint)bmp.Width; - var height = (uint)bmp.Height; - var CheckWidth = (uint)(sprite.Textures[frame].Texture.TargetWidth); - var CheckHeight = (uint)(sprite.Textures[frame].Texture.TargetHeight); - if ((width != CheckWidth) || (height != CheckHeight)) - { - string error = "Incorrect dimensions of " + stripped + ". Sprite blurring is very likely in game. Aborting!"; - ScriptError(error, "Unexpected texture dimensions"); - SetUMTConsoleText(error); - SetFinishedMessage(false); - return; - } - sprite.Textures[frame].Texture.ReplaceTexture(bmp); - } - catch - { - string error = file + " encountered an unknown error during import. Contact the Underminers discord with as much information as possible, the file, and this error message. Aborting!"; - ScriptError(error, "Sprite Error"); - SetUMTConsoleText(error); - SetFinishedMessage(false); - return; - } - } - else - { - string error = fileName + ": Index out of range. Index " + frame.ToString() + " exceeds maximum index (" + ((sprite.Textures.Count) - 1).ToString() + ") of " + spriteName + ". Aborting!"; - ScriptError(error, "Sprite Error"); - SetUMTConsoleText(error); - SetFinishedMessage(false); - return; - } - } -}); - -await StopUpdater(); -HideProgressBar(); +using System; +using System.IO; +using System.Drawing; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using UndertaleModLib.Util; + +EnsureDataLoaded(); + +// At this point, this just imports the sprites. +string importFolder = PromptChooseDirectory(); +if (importFolder == null) + throw new ScriptException("The import folder was not set."); + +string[] dirFiles = Directory.GetFiles(importFolder); + +//Stop the script if there's missing sprite entries or w/e. +foreach (string file in dirFiles) +{ + string FileNameWithExtension = Path.GetFileName(file); + if (!FileNameWithExtension.EndsWith(".png")) + continue; // Restarts loop if file is not a valid mask asset. + string stripped = Path.GetFileNameWithoutExtension(file); + int lastUnderscore = stripped.LastIndexOf('_'); + string spriteName = ""; + try + { + spriteName = stripped.Substring(0, lastUnderscore); + } + catch + { + throw new ScriptException("Getting the sprite name of " + FileNameWithExtension + " failed."); + } + if (Data.Sprites.ByName(spriteName) == null) // Reject non-existing sprites + { + throw new ScriptException(FileNameWithExtension + " could not be imported as the sprite " + spriteName + " does not exist."); + } + using (Image img = Image.FromFile(file)) + { + if ((Data.Sprites.ByName(spriteName).Width != (uint)img.Width) || (Data.Sprites.ByName(spriteName).Height != (uint)img.Height)) + throw new ScriptException(FileNameWithExtension + " is not the proper size to be imported! Please correct this before importing! The proper dimensions are width: " + Data.Sprites.ByName(spriteName).Width.ToString() + " px, height: " + Data.Sprites.ByName(spriteName).Height.ToString() + " px."); + } + + Int32 validFrameNumber = 0; + try + { + validFrameNumber = Int32.Parse(stripped.Substring(lastUnderscore + 1)); + } + catch + { + throw new ScriptException("The index of " + FileNameWithExtension + " could not be determined."); + } + int frame = 0; + try + { + frame = Int32.Parse(stripped.Substring(lastUnderscore + 1)); + } + catch + { + throw new ScriptException(FileNameWithExtension + " is using letters instead of numbers. The script has stopped for your own protection."); + } + int prevframe = 0; + if (frame != 0) + { + prevframe = (frame - 1); + } + if (frame < 0) + { + throw new ScriptException(spriteName + " is using an invalid numbering scheme. The script has stopped for your own protection."); + } + var prevFrameName = spriteName + "_" + prevframe.ToString() + ".png"; + string[] previousFrameFiles = Directory.GetFiles(importFolder, prevFrameName); + if (previousFrameFiles.Length < 1) + throw new ScriptException(spriteName + " is missing one or more indexes. The detected missing index is: " + prevFrameName); +} + +SetProgressBar(null, "Files", 0, dirFiles.Length); +StartProgressBarUpdater(); + +await Task.Run(() => { + foreach (string file in dirFiles) + { + IncrementProgress(); + + string fileName = Path.GetFileName(file); + if (!fileName.EndsWith(".png") || !fileName.Contains("_")) + continue; // Not an image. + + string stripped = Path.GetFileNameWithoutExtension(file); + + int lastUnderscore = stripped.LastIndexOf('_'); + string spriteName = stripped.Substring(0, lastUnderscore); + int frame = Int32.Parse(stripped.Substring(lastUnderscore + 1)); + + UndertaleSprite sprite = Data.Sprites.ByName(spriteName); + + if (frame < sprite.Textures.Count) + { + try + { + Bitmap bmp; + using (var ms = new MemoryStream(TextureWorker.ReadTextureBlob(file))) + { + bmp = new Bitmap(ms); + } + bmp.SetResolution(96.0F, 96.0F); + var width = (uint)bmp.Width; + var height = (uint)bmp.Height; + var CheckWidth = (uint)(sprite.Textures[frame].Texture.TargetWidth); + var CheckHeight = (uint)(sprite.Textures[frame].Texture.TargetHeight); + if ((width != CheckWidth) || (height != CheckHeight)) + { + string error = "Incorrect dimensions of " + stripped + ". Sprite blurring is very likely in game. Aborting!"; + ScriptError(error, "Unexpected texture dimensions"); + SetUMTConsoleText(error); + SetFinishedMessage(false); + return; + } + sprite.Textures[frame].Texture.ReplaceTexture(bmp); + } + catch + { + string error = file + " encountered an unknown error during import. Contact the Underminers discord with as much information as possible, the file, and this error message. Aborting!"; + ScriptError(error, "Sprite Error"); + SetUMTConsoleText(error); + SetFinishedMessage(false); + return; + } + } + else + { + string error = fileName + ": Index out of range. Index " + frame.ToString() + " exceeds maximum index (" + ((sprite.Textures.Count) - 1).ToString() + ") of " + spriteName + ". Aborting!"; + ScriptError(error, "Sprite Error"); + SetUMTConsoleText(error); + SetFinishedMessage(false); + return; + } + } +}); + +await StopProgressBarUpdater(); +HideProgressBar(); ScriptMessage("Import Complete!"); \ No newline at end of file diff --git a/UndertaleModTool/Repackers/CopySpriteBgFont.csx b/UndertaleModTool/Repackers/CopySpriteBgFont.csx index 3cd0bdee0..f19528a37 100644 --- a/UndertaleModTool/Repackers/CopySpriteBgFont.csx +++ b/UndertaleModTool/Repackers/CopySpriteBgFont.csx @@ -1,484 +1,484 @@ -//Adapted from original script by Grossley -using System.Text; -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using UndertaleModLib.Util; - -EnsureDataLoaded(); - -// Initialization Start - -var DataEmbeddedTexturesCount = Data.EmbeddedTextures.Count; -List tex_TargetX = new List(); -List tex_TargetY = new List(); -List tex_SourceX = new List(); -List tex_SourceY = new List(); -List tex_SourceWidth = new List(); -List tex_SourceHeight = new List(); -List tex_TargetWidth = new List(); -List tex_TargetHeight = new List(); -List tex_BoundingWidth = new List(); -List tex_BoundingHeight = new List(); -List tex_Frame = new List(); -List tex_EmbeddedTextureID = new List(); -List tex_Name = new List(); -List tex_Type = new List(); -List tex_IsNull = new List(); -List TexturePageItemsUsed = new List(); - -// Initialization End - -ScriptMessage("Select the file to copy from"); - -UndertaleData DonorData; -string DonorDataPath = PromptLoadFile(null, null); -if (DonorDataPath == null) - throw new ScriptException("The donor data path was not set."); - -using (var stream = new FileStream(DonorDataPath, FileMode.Open, FileAccess.Read)) - DonorData = UndertaleIO.Read(stream, warning => ScriptMessage("A warning occured while trying to load " + DonorDataPath + ":\n" + warning)); -var DonorDataEmbeddedTexturesCount = DonorData.EmbeddedTextures.Count; -int copiedSpritesCount = 0; -int copiedBackgroundsCount = 0; -int copiedFontsCount = 0; -int copiedAssetsCount = 0; -List splitStringsList = GetSplitStringsList("sprite(s)/background(s)/font"); -bool[] SpriteSheetsCopyNeeded = new bool[DonorDataEmbeddedTexturesCount]; -bool[] SpriteSheetsUsed = new bool[(DataEmbeddedTexturesCount + DonorDataEmbeddedTexturesCount)]; - -int lastTextPage = Data.EmbeddedTextures.Count - 1; - -SetProgressBar(null, "Textures Exported", 0, DonorData.TexturePageItems.Count); -StartUpdater(); - -SyncBinding("EmbeddedTextures, Strings, Backgrounds, Sprites, Fonts, TexturePageItems", true); -await Task.Run(() => { - for (int i = 0; i < SpriteSheetsCopyNeeded.Length; i++) - { - SpriteSheetsCopyNeeded[i] = false; - } - for (int i = 0; i < SpriteSheetsUsed.Length; i++) - { - SpriteSheetsUsed[i] = false; - } - for (var i = 0; i < DonorDataEmbeddedTexturesCount; i++) - { - UndertaleEmbeddedTexture texture = new UndertaleEmbeddedTexture(); - texture.Name = new UndertaleString("Texture " + ++lastTextPage); - texture.TextureData.TextureBlob = DonorData.EmbeddedTextures[i].TextureData.TextureBlob; - Data.EmbeddedTextures.Add(texture); - } - for (var j = 0; j < splitStringsList.Count; j++) - { - foreach (UndertaleBackground bg in DonorData.Backgrounds) - { - if (splitStringsList[j].ToLower() == bg.Name.Content.ToLower()) - { - UndertaleBackground nativeBG = Data.Backgrounds.ByName(bg.Name.Content); - UndertaleBackground donorBG = DonorData.Backgrounds.ByName(bg.Name.Content); - if (nativeBG == null) - { - nativeBG = new UndertaleBackground(); - nativeBG.Name = Data.Strings.MakeString(bg.Name.Content); - Data.Backgrounds.Add(nativeBG); - } - nativeBG.Transparent = donorBG.Transparent; - nativeBG.Smooth = donorBG.Smooth; - nativeBG.Preload = donorBG.Preload; - nativeBG.GMS2UnknownAlways2 = donorBG.GMS2UnknownAlways2; - nativeBG.GMS2TileWidth = donorBG.GMS2TileWidth; - nativeBG.GMS2TileHeight = donorBG.GMS2TileHeight; - nativeBG.GMS2OutputBorderX = donorBG.GMS2OutputBorderX; - nativeBG.GMS2OutputBorderY = donorBG.GMS2OutputBorderY; - nativeBG.GMS2TileColumns = donorBG.GMS2TileColumns; - nativeBG.GMS2ItemsPerTileCount = donorBG.GMS2ItemsPerTileCount; - nativeBG.GMS2TileCount = donorBG.GMS2TileCount; - nativeBG.GMS2UnknownAlwaysZero = donorBG.GMS2UnknownAlwaysZero; - nativeBG.GMS2FrameLength = donorBG.GMS2FrameLength; - nativeBG.GMS2TileIds = donorBG.GMS2TileIds; - DumpBackground(donorBG); - copiedBackgroundsCount += 1; - } - } - foreach (UndertaleSprite sprite in DonorData.Sprites) - { - if (splitStringsList[j].ToLower() == sprite.Name.Content.ToLower()) - { - UndertaleSprite nativeSPR = Data.Sprites.ByName(sprite.Name.Content); - UndertaleSprite donorSPR = DonorData.Sprites.ByName(sprite.Name.Content); - if (nativeSPR != null) - { - nativeSPR.CollisionMasks.Clear(); - } - else - { - nativeSPR = new UndertaleSprite(); - nativeSPR.Name = Data.Strings.MakeString(sprite.Name.Content); - Data.Sprites.Add(nativeSPR); - } - for (var i = 0; i < donorSPR.CollisionMasks.Count; i++) - nativeSPR.CollisionMasks.Add(new UndertaleSprite.MaskEntry(donorSPR.CollisionMasks[i].Data)); - nativeSPR.Width = donorSPR.Width; - nativeSPR.Height = donorSPR.Height; - nativeSPR.MarginLeft = donorSPR.MarginLeft; - nativeSPR.MarginRight = donorSPR.MarginRight; - nativeSPR.MarginBottom = donorSPR.MarginBottom; - nativeSPR.MarginTop = donorSPR.MarginTop; - nativeSPR.Transparent = donorSPR.Transparent; - nativeSPR.Smooth = donorSPR.Smooth; - nativeSPR.Preload = donorSPR.Preload; - nativeSPR.BBoxMode = donorSPR.BBoxMode; - nativeSPR.OriginX = donorSPR.OriginX; - nativeSPR.OriginY = donorSPR.OriginY; - - // Special sprite types (always used in GMS2) - nativeSPR.SVersion = donorSPR.SVersion; - nativeSPR.GMS2PlaybackSpeed = donorSPR.GMS2PlaybackSpeed; - nativeSPR.IsSpecialType = donorSPR.IsSpecialType; - nativeSPR.SpineVersion = donorSPR.SpineVersion; - nativeSPR.SpineJSON = donorSPR.SpineJSON; - nativeSPR.SpineAtlas = donorSPR.SpineAtlas; - nativeSPR.SWFVersion = donorSPR.SWFVersion; - - //Possibly will break - nativeSPR.SepMasks = donorSPR.SepMasks; - nativeSPR.SSpriteType = donorSPR.SSpriteType; - nativeSPR.GMS2PlaybackSpeedType = donorSPR.GMS2PlaybackSpeedType; - nativeSPR.SpineTextures = donorSPR.SpineTextures; - nativeSPR.YYSWF = donorSPR.YYSWF; - nativeSPR.V2Sequence = donorSPR.V2Sequence; - nativeSPR.V3NineSlice = donorSPR.V3NineSlice; - - DumpSprite(donorSPR); - copiedSpritesCount += 1; - } - } - foreach (UndertaleFont fnt in DonorData.Fonts) - { - if (splitStringsList[j].ToLower() == fnt.Name.Content.ToLower()) - { - UndertaleFont nativeFNT = Data.Fonts.ByName(fnt.Name.Content); - UndertaleFont donorFNT = DonorData.Fonts.ByName(fnt.Name.Content); - if (nativeFNT == null) - { - nativeFNT = new UndertaleFont(); - nativeFNT.Name = Data.Strings.MakeString(fnt.Name.Content); - Data.Fonts.Add(nativeFNT); - } - nativeFNT.Glyphs.Clear(); - nativeFNT.RangeStart = donorFNT.RangeStart; - nativeFNT.DisplayName = Data.Strings.MakeString(donorFNT.DisplayName.Content); - nativeFNT.EmSize = donorFNT.EmSize; - nativeFNT.Bold = donorFNT.Bold; - nativeFNT.Italic = donorFNT.Italic; - nativeFNT.Charset = donorFNT.Charset; - nativeFNT.AntiAliasing = donorFNT.AntiAliasing; - nativeFNT.ScaleX = donorFNT.ScaleX; - nativeFNT.ScaleY = donorFNT.ScaleY; - foreach (UndertaleFont.Glyph glyph in donorFNT.Glyphs) - { - UndertaleFont.Glyph glyph_new = new UndertaleFont.Glyph(); - glyph_new.Character = glyph.Character; - glyph_new.SourceX = glyph.SourceX; - glyph_new.SourceY = glyph.SourceY; - glyph_new.SourceWidth = glyph.SourceWidth; - glyph_new.SourceHeight = glyph.SourceHeight; - glyph_new.Shift = glyph.Shift; - glyph_new.Offset = glyph.Offset; - nativeFNT.Glyphs.Add(glyph_new); - } - nativeFNT.RangeEnd = donorFNT.RangeEnd; - DumpFont(donorFNT); - copiedFontsCount += 1; - } - } - } - for (var i = 0; i < tex_IsNull.Count; i++) - { - if (tex_IsNull[i] == false) - { - UndertaleTexturePageItem texturePageItem = new UndertaleTexturePageItem(); - texturePageItem.TargetX = (ushort)tex_TargetX[i]; - texturePageItem.TargetY = (ushort)tex_TargetY[i]; - texturePageItem.TargetWidth = (ushort)tex_TargetWidth[i]; - texturePageItem.TargetHeight = (ushort)tex_TargetHeight[i]; - texturePageItem.SourceX = (ushort)tex_SourceX[i]; - texturePageItem.SourceY = (ushort)tex_SourceY[i]; - texturePageItem.SourceWidth = (ushort)tex_SourceWidth[i]; - texturePageItem.SourceHeight = (ushort)tex_SourceHeight[i]; - texturePageItem.BoundingWidth = (ushort)tex_BoundingWidth[i]; - texturePageItem.BoundingHeight = (ushort)tex_BoundingHeight[i]; - texturePageItem.TexturePage = Data.EmbeddedTextures[(DataEmbeddedTexturesCount + tex_EmbeddedTextureID[i])]; - Data.TexturePageItems.Add(texturePageItem); - texturePageItem.Name = new UndertaleString("PageItem " + Data.TexturePageItems.IndexOf(texturePageItem).ToString()); - if (tex_Type[i].Equals("bg")) - { - UndertaleBackground background = Data.Backgrounds.ByName(tex_Name[i]); - background.Texture = texturePageItem; - } - else if (tex_Type[i].Equals("fnt")) - { - UndertaleFont font = Data.Fonts.ByName(tex_Name[i]); - font.Texture = texturePageItem; - } - else - { - int frame = tex_Frame[i]; - UndertaleSprite sprite = Data.Sprites.ByName(tex_Name[i]); - UndertaleSprite.TextureEntry texentry = new UndertaleSprite.TextureEntry(); - texentry.Texture = texturePageItem; - if (frame > sprite.Textures.Count - 1) - { - while (frame > sprite.Textures.Count - 1) - { - sprite.Textures.Add(texentry); - } - } - else - { - sprite.Textures[frame] = texentry; - } - } - } - else - { - if (tex_Type[i] == "spr") - { - UndertaleSprite.TextureEntry texentry = new UndertaleSprite.TextureEntry(); - texentry.Texture = null; - Data.Sprites.ByName(tex_Name[i]).Textures.Add(texentry); - } - if (tex_Type[i] == "bg") - { - Data.Backgrounds.ByName(tex_Name[i]).Texture = null; - } - if (tex_Type[i] == "fnt") - { - Data.Fonts.ByName(tex_Name[i]).Texture = null; - } - } - } - SpriteSheetsUsedUpdate(); - RemoveUnusedSpriteSheets(); - TexturePageItemsUsedUpdate(); - RemoveUnusedTexturePageItems(); -}); -SyncBinding(false); - -await StopUpdater(); -HideProgressBar(); -copiedAssetsCount = (copiedFontsCount + copiedBackgroundsCount + copiedSpritesCount); -ScriptMessage(copiedAssetsCount.ToString() + " assets were copied (" + copiedSpritesCount.ToString() + " Sprites, " + copiedBackgroundsCount.ToString() + " Backgrounds, and " + copiedFontsCount.ToString() + " Fonts)"); - - - - -// Functions - -void RemoveUnusedTexturePageItems() -{ - for (int i = (TexturePageItemsUsed.Count - 1); i > -1; i--) - { - if (TexturePageItemsUsed[i] == false) - { - Data.TexturePageItems.Remove(Data.TexturePageItems[i]); - } - } - foreach (UndertaleTexturePageItem texture in Data.TexturePageItems) - { - texture.Name = new UndertaleString("PageItem " + Data.TexturePageItems.IndexOf(texture).ToString()); - } -} -void RemoveUnusedSpriteSheets() -{ - for (int i = (SpriteSheetsUsed.Length - 1); i > -1; i--) - { - if (SpriteSheetsUsed[i] == false) - { - Data.EmbeddedTextures.Remove(Data.EmbeddedTextures[i]); - } - } - foreach (UndertaleEmbeddedTexture texture in Data.EmbeddedTextures) - { - texture.Name = new UndertaleString("Texture " + Data.EmbeddedTextures.IndexOf(texture).ToString()); - } -} -void DumpSprite(UndertaleSprite sprite) -{ - for (int i = 0; i < sprite.Textures.Count; i++) - { - if (sprite.Textures[i]?.Texture != null) - NotNullHandler(sprite.Textures[i].Texture); - else - NullHandler(); - tex_Frame.Add(i); - tex_Name.Add(sprite.Name.Content); - tex_Type.Add("spr"); - } - - AddProgress(sprite.Textures.Count); -} -void DumpFont(UndertaleFont font) -{ - if (font.Texture != null) - NotNullHandler(font.Texture); - else - NullHandler(); - tex_Frame.Add(0); - tex_Name.Add(font.Name.Content); - tex_Type.Add("fnt"); - - IncProgress(); -} -void DumpBackground(UndertaleBackground background) -{ - if (background.Texture != null) - NotNullHandler(background.Texture); - else - NullHandler(); - tex_Frame.Add(0); - tex_Name.Add(background.Name.Content); - tex_Type.Add("bg"); - - IncProgress(); -} -void NullHandler() -{ - tex_TargetX.Add(-16000); - tex_TargetY.Add(-16000); - tex_SourceWidth.Add(-16000); - tex_SourceHeight.Add(-16000); - tex_SourceX.Add(-16000); - tex_SourceY.Add(-16000); - tex_TargetWidth.Add(-16000); - tex_TargetHeight.Add(-16000); - tex_BoundingWidth.Add(-16000); - tex_BoundingHeight.Add(-16000); - tex_EmbeddedTextureID.Add(-16000); - tex_IsNull.Add(true); -} -void NotNullHandler(UndertaleTexturePageItem tex) -{ - tex_TargetX.Add(tex.TargetX); - tex_TargetY.Add(tex.TargetY); - tex_SourceWidth.Add(tex.SourceWidth); - tex_SourceHeight.Add(tex.SourceHeight); - tex_SourceX.Add(tex.SourceX); - tex_SourceY.Add(tex.SourceY); - tex_TargetWidth.Add(tex.TargetWidth); - tex_TargetHeight.Add(tex.TargetHeight); - tex_BoundingWidth.Add(tex.BoundingWidth); - tex_BoundingHeight.Add(tex.BoundingHeight); - tex_EmbeddedTextureID.Add(DonorData.EmbeddedTextures.IndexOf(tex.TexturePage)); - tex_IsNull.Add(false); -} -void TexturePageItemsUsedUpdate() -{ - foreach (UndertaleTexturePageItem texture in Data.TexturePageItems) - { - TexturePageItemsUsed.Add(false); - } - foreach(UndertaleSprite sprite in Data.Sprites) - { - for (int i = 0; i < sprite.Textures.Count; i++) - { - if (sprite.Textures[i]?.Texture != null) - { - TexturePageItemsUsed[Data.TexturePageItems.IndexOf(sprite.Textures[i]?.Texture)] = true; - } - } - } - foreach (UndertaleBackground bg in Data.Backgrounds) - { - if (bg.Texture != null) - { - TexturePageItemsUsed[Data.TexturePageItems.IndexOf(bg.Texture)] = true; - } - } - foreach (UndertaleFont fnt in Data.Fonts) - { - if (fnt.Texture != null) - { - TexturePageItemsUsed[Data.TexturePageItems.IndexOf(fnt.Texture)] = true; - } - } -} -void SpriteSheetsUsedUpdate() -{ - foreach(UndertaleSprite sprite in Data.Sprites) - { - for (int i = 0; i < sprite.Textures.Count; i++) - { - if (sprite.Textures[i]?.Texture != null) - { - SpriteSheetsUsed[Data.EmbeddedTextures.IndexOf(sprite.Textures[i]?.Texture.TexturePage)] = true; - } - } - } - foreach (UndertaleBackground bg in Data.Backgrounds) - { - if (bg.Texture != null) - { - SpriteSheetsUsed[Data.EmbeddedTextures.IndexOf(bg.Texture.TexturePage)] = true; - } - } - foreach (UndertaleFont fnt in Data.Fonts) - { - if (fnt.Texture != null) - { - SpriteSheetsUsed[Data.EmbeddedTextures.IndexOf(fnt.Texture.TexturePage)] = true; - } - } -} -//Unused -/* -void UpdateSpriteSheetsCopyNeeded() -{ - for (var j = 0; j < sprCandidates.Count; j++) - { - UndertaleSprite sprite = Data.Sprites.ByName(sprCandidates[j]); - for (int i = 0; i < sprite.Textures.Count; i++) - { - if (sprite.Textures[i]?.Texture != null) - { - SpriteSheetsCopyNeeded[Data.EmbeddedTextures.IndexOf(sprite.Textures[i]?.Texture.TexturePage)] = true; - } - } - UpdateProgress(); - } - for (var j = 0; j < bgCandidates.Count; j++) - { - UndertaleBackground bg = Data.Backgrounds.ByName(bgCandidates[j]); - if (bg.Texture != null) - { - SpriteSheetsCopyNeeded[Data.EmbeddedTextures.IndexOf(bg.Texture.TexturePage)] = true; - } - UpdateProgress(); - } - for (var j = 0; j < fntCandidates.Count; j++) - { - UndertaleFont fnt = Data.Fonts.ByName(fntCandidates[j]); - if (fnt.Texture != null) - { - SpriteSheetsCopyNeeded[Data.EmbeddedTextures.IndexOf(fnt.Texture.TexturePage)] = true; - } - UpdateProgress(); - } -} -*/ - -List GetSplitStringsList(string assetType) -{ - ScriptMessage("Enter the " + assetType + "(s) to copy"); - List splitStringsList = new List(); - string InputtedText = ""; - InputtedText = SimpleTextInput("Menu", "Enter the name(s) of the " + assetType + "(s)", InputtedText, true); - string[] IndividualLineArray = InputtedText.Split('\n', StringSplitOptions.RemoveEmptyEntries); - foreach (var OneLine in IndividualLineArray) - { - splitStringsList.Add(OneLine.Trim()); - } - return splitStringsList; -} +//Adapted from original script by Grossley +using System.Text; +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using UndertaleModLib.Util; + +EnsureDataLoaded(); + +// Initialization Start + +var DataEmbeddedTexturesCount = Data.EmbeddedTextures.Count; +List tex_TargetX = new List(); +List tex_TargetY = new List(); +List tex_SourceX = new List(); +List tex_SourceY = new List(); +List tex_SourceWidth = new List(); +List tex_SourceHeight = new List(); +List tex_TargetWidth = new List(); +List tex_TargetHeight = new List(); +List tex_BoundingWidth = new List(); +List tex_BoundingHeight = new List(); +List tex_Frame = new List(); +List tex_EmbeddedTextureID = new List(); +List tex_Name = new List(); +List tex_Type = new List(); +List tex_IsNull = new List(); +List TexturePageItemsUsed = new List(); + +// Initialization End + +ScriptMessage("Select the file to copy from"); + +UndertaleData DonorData; +string DonorDataPath = PromptLoadFile(null, null); +if (DonorDataPath == null) + throw new ScriptException("The donor data path was not set."); + +using (var stream = new FileStream(DonorDataPath, FileMode.Open, FileAccess.Read)) + DonorData = UndertaleIO.Read(stream, warning => ScriptMessage("A warning occured while trying to load " + DonorDataPath + ":\n" + warning)); +var DonorDataEmbeddedTexturesCount = DonorData.EmbeddedTextures.Count; +int copiedSpritesCount = 0; +int copiedBackgroundsCount = 0; +int copiedFontsCount = 0; +int copiedAssetsCount = 0; +List splitStringsList = GetSplitStringsList("sprite(s)/background(s)/font"); +bool[] SpriteSheetsCopyNeeded = new bool[DonorDataEmbeddedTexturesCount]; +bool[] SpriteSheetsUsed = new bool[(DataEmbeddedTexturesCount + DonorDataEmbeddedTexturesCount)]; + +int lastTextPage = Data.EmbeddedTextures.Count - 1; + +SetProgressBar(null, "Textures Exported", 0, DonorData.TexturePageItems.Count); +StartProgressBarUpdater(); + +SyncBinding("EmbeddedTextures, Strings, Backgrounds, Sprites, Fonts, TexturePageItems", true); +await Task.Run(() => { + for (int i = 0; i < SpriteSheetsCopyNeeded.Length; i++) + { + SpriteSheetsCopyNeeded[i] = false; + } + for (int i = 0; i < SpriteSheetsUsed.Length; i++) + { + SpriteSheetsUsed[i] = false; + } + for (var i = 0; i < DonorDataEmbeddedTexturesCount; i++) + { + UndertaleEmbeddedTexture texture = new UndertaleEmbeddedTexture(); + texture.Name = new UndertaleString("Texture " + ++lastTextPage); + texture.TextureData.TextureBlob = DonorData.EmbeddedTextures[i].TextureData.TextureBlob; + Data.EmbeddedTextures.Add(texture); + } + for (var j = 0; j < splitStringsList.Count; j++) + { + foreach (UndertaleBackground bg in DonorData.Backgrounds) + { + if (splitStringsList[j].ToLower() == bg.Name.Content.ToLower()) + { + UndertaleBackground nativeBG = Data.Backgrounds.ByName(bg.Name.Content); + UndertaleBackground donorBG = DonorData.Backgrounds.ByName(bg.Name.Content); + if (nativeBG == null) + { + nativeBG = new UndertaleBackground(); + nativeBG.Name = Data.Strings.MakeString(bg.Name.Content); + Data.Backgrounds.Add(nativeBG); + } + nativeBG.Transparent = donorBG.Transparent; + nativeBG.Smooth = donorBG.Smooth; + nativeBG.Preload = donorBG.Preload; + nativeBG.GMS2UnknownAlways2 = donorBG.GMS2UnknownAlways2; + nativeBG.GMS2TileWidth = donorBG.GMS2TileWidth; + nativeBG.GMS2TileHeight = donorBG.GMS2TileHeight; + nativeBG.GMS2OutputBorderX = donorBG.GMS2OutputBorderX; + nativeBG.GMS2OutputBorderY = donorBG.GMS2OutputBorderY; + nativeBG.GMS2TileColumns = donorBG.GMS2TileColumns; + nativeBG.GMS2ItemsPerTileCount = donorBG.GMS2ItemsPerTileCount; + nativeBG.GMS2TileCount = donorBG.GMS2TileCount; + nativeBG.GMS2UnknownAlwaysZero = donorBG.GMS2UnknownAlwaysZero; + nativeBG.GMS2FrameLength = donorBG.GMS2FrameLength; + nativeBG.GMS2TileIds = donorBG.GMS2TileIds; + DumpBackground(donorBG); + copiedBackgroundsCount += 1; + } + } + foreach (UndertaleSprite sprite in DonorData.Sprites) + { + if (splitStringsList[j].ToLower() == sprite.Name.Content.ToLower()) + { + UndertaleSprite nativeSPR = Data.Sprites.ByName(sprite.Name.Content); + UndertaleSprite donorSPR = DonorData.Sprites.ByName(sprite.Name.Content); + if (nativeSPR != null) + { + nativeSPR.CollisionMasks.Clear(); + } + else + { + nativeSPR = new UndertaleSprite(); + nativeSPR.Name = Data.Strings.MakeString(sprite.Name.Content); + Data.Sprites.Add(nativeSPR); + } + for (var i = 0; i < donorSPR.CollisionMasks.Count; i++) + nativeSPR.CollisionMasks.Add(new UndertaleSprite.MaskEntry(donorSPR.CollisionMasks[i].Data)); + nativeSPR.Width = donorSPR.Width; + nativeSPR.Height = donorSPR.Height; + nativeSPR.MarginLeft = donorSPR.MarginLeft; + nativeSPR.MarginRight = donorSPR.MarginRight; + nativeSPR.MarginBottom = donorSPR.MarginBottom; + nativeSPR.MarginTop = donorSPR.MarginTop; + nativeSPR.Transparent = donorSPR.Transparent; + nativeSPR.Smooth = donorSPR.Smooth; + nativeSPR.Preload = donorSPR.Preload; + nativeSPR.BBoxMode = donorSPR.BBoxMode; + nativeSPR.OriginX = donorSPR.OriginX; + nativeSPR.OriginY = donorSPR.OriginY; + + // Special sprite types (always used in GMS2) + nativeSPR.SVersion = donorSPR.SVersion; + nativeSPR.GMS2PlaybackSpeed = donorSPR.GMS2PlaybackSpeed; + nativeSPR.IsSpecialType = donorSPR.IsSpecialType; + nativeSPR.SpineVersion = donorSPR.SpineVersion; + nativeSPR.SpineJSON = donorSPR.SpineJSON; + nativeSPR.SpineAtlas = donorSPR.SpineAtlas; + nativeSPR.SWFVersion = donorSPR.SWFVersion; + + //Possibly will break + nativeSPR.SepMasks = donorSPR.SepMasks; + nativeSPR.SSpriteType = donorSPR.SSpriteType; + nativeSPR.GMS2PlaybackSpeedType = donorSPR.GMS2PlaybackSpeedType; + nativeSPR.SpineTextures = donorSPR.SpineTextures; + nativeSPR.YYSWF = donorSPR.YYSWF; + nativeSPR.V2Sequence = donorSPR.V2Sequence; + nativeSPR.V3NineSlice = donorSPR.V3NineSlice; + + DumpSprite(donorSPR); + copiedSpritesCount += 1; + } + } + foreach (UndertaleFont fnt in DonorData.Fonts) + { + if (splitStringsList[j].ToLower() == fnt.Name.Content.ToLower()) + { + UndertaleFont nativeFNT = Data.Fonts.ByName(fnt.Name.Content); + UndertaleFont donorFNT = DonorData.Fonts.ByName(fnt.Name.Content); + if (nativeFNT == null) + { + nativeFNT = new UndertaleFont(); + nativeFNT.Name = Data.Strings.MakeString(fnt.Name.Content); + Data.Fonts.Add(nativeFNT); + } + nativeFNT.Glyphs.Clear(); + nativeFNT.RangeStart = donorFNT.RangeStart; + nativeFNT.DisplayName = Data.Strings.MakeString(donorFNT.DisplayName.Content); + nativeFNT.EmSize = donorFNT.EmSize; + nativeFNT.Bold = donorFNT.Bold; + nativeFNT.Italic = donorFNT.Italic; + nativeFNT.Charset = donorFNT.Charset; + nativeFNT.AntiAliasing = donorFNT.AntiAliasing; + nativeFNT.ScaleX = donorFNT.ScaleX; + nativeFNT.ScaleY = donorFNT.ScaleY; + foreach (UndertaleFont.Glyph glyph in donorFNT.Glyphs) + { + UndertaleFont.Glyph glyph_new = new UndertaleFont.Glyph(); + glyph_new.Character = glyph.Character; + glyph_new.SourceX = glyph.SourceX; + glyph_new.SourceY = glyph.SourceY; + glyph_new.SourceWidth = glyph.SourceWidth; + glyph_new.SourceHeight = glyph.SourceHeight; + glyph_new.Shift = glyph.Shift; + glyph_new.Offset = glyph.Offset; + nativeFNT.Glyphs.Add(glyph_new); + } + nativeFNT.RangeEnd = donorFNT.RangeEnd; + DumpFont(donorFNT); + copiedFontsCount += 1; + } + } + } + for (var i = 0; i < tex_IsNull.Count; i++) + { + if (tex_IsNull[i] == false) + { + UndertaleTexturePageItem texturePageItem = new UndertaleTexturePageItem(); + texturePageItem.TargetX = (ushort)tex_TargetX[i]; + texturePageItem.TargetY = (ushort)tex_TargetY[i]; + texturePageItem.TargetWidth = (ushort)tex_TargetWidth[i]; + texturePageItem.TargetHeight = (ushort)tex_TargetHeight[i]; + texturePageItem.SourceX = (ushort)tex_SourceX[i]; + texturePageItem.SourceY = (ushort)tex_SourceY[i]; + texturePageItem.SourceWidth = (ushort)tex_SourceWidth[i]; + texturePageItem.SourceHeight = (ushort)tex_SourceHeight[i]; + texturePageItem.BoundingWidth = (ushort)tex_BoundingWidth[i]; + texturePageItem.BoundingHeight = (ushort)tex_BoundingHeight[i]; + texturePageItem.TexturePage = Data.EmbeddedTextures[(DataEmbeddedTexturesCount + tex_EmbeddedTextureID[i])]; + Data.TexturePageItems.Add(texturePageItem); + texturePageItem.Name = new UndertaleString("PageItem " + Data.TexturePageItems.IndexOf(texturePageItem).ToString()); + if (tex_Type[i].Equals("bg")) + { + UndertaleBackground background = Data.Backgrounds.ByName(tex_Name[i]); + background.Texture = texturePageItem; + } + else if (tex_Type[i].Equals("fnt")) + { + UndertaleFont font = Data.Fonts.ByName(tex_Name[i]); + font.Texture = texturePageItem; + } + else + { + int frame = tex_Frame[i]; + UndertaleSprite sprite = Data.Sprites.ByName(tex_Name[i]); + UndertaleSprite.TextureEntry texentry = new UndertaleSprite.TextureEntry(); + texentry.Texture = texturePageItem; + if (frame > sprite.Textures.Count - 1) + { + while (frame > sprite.Textures.Count - 1) + { + sprite.Textures.Add(texentry); + } + } + else + { + sprite.Textures[frame] = texentry; + } + } + } + else + { + if (tex_Type[i] == "spr") + { + UndertaleSprite.TextureEntry texentry = new UndertaleSprite.TextureEntry(); + texentry.Texture = null; + Data.Sprites.ByName(tex_Name[i]).Textures.Add(texentry); + } + if (tex_Type[i] == "bg") + { + Data.Backgrounds.ByName(tex_Name[i]).Texture = null; + } + if (tex_Type[i] == "fnt") + { + Data.Fonts.ByName(tex_Name[i]).Texture = null; + } + } + } + SpriteSheetsUsedUpdate(); + RemoveUnusedSpriteSheets(); + TexturePageItemsUsedUpdate(); + RemoveUnusedTexturePageItems(); +}); +DisableAllSyncBindings(); + +await StopProgressBarUpdater(); +HideProgressBar(); +copiedAssetsCount = (copiedFontsCount + copiedBackgroundsCount + copiedSpritesCount); +ScriptMessage(copiedAssetsCount.ToString() + " assets were copied (" + copiedSpritesCount.ToString() + " Sprites, " + copiedBackgroundsCount.ToString() + " Backgrounds, and " + copiedFontsCount.ToString() + " Fonts)"); + + + + +// Functions + +void RemoveUnusedTexturePageItems() +{ + for (int i = (TexturePageItemsUsed.Count - 1); i > -1; i--) + { + if (TexturePageItemsUsed[i] == false) + { + Data.TexturePageItems.Remove(Data.TexturePageItems[i]); + } + } + foreach (UndertaleTexturePageItem texture in Data.TexturePageItems) + { + texture.Name = new UndertaleString("PageItem " + Data.TexturePageItems.IndexOf(texture).ToString()); + } +} +void RemoveUnusedSpriteSheets() +{ + for (int i = (SpriteSheetsUsed.Length - 1); i > -1; i--) + { + if (SpriteSheetsUsed[i] == false) + { + Data.EmbeddedTextures.Remove(Data.EmbeddedTextures[i]); + } + } + foreach (UndertaleEmbeddedTexture texture in Data.EmbeddedTextures) + { + texture.Name = new UndertaleString("Texture " + Data.EmbeddedTextures.IndexOf(texture).ToString()); + } +} +void DumpSprite(UndertaleSprite sprite) +{ + for (int i = 0; i < sprite.Textures.Count; i++) + { + if (sprite.Textures[i]?.Texture != null) + NotNullHandler(sprite.Textures[i].Texture); + else + NullHandler(); + tex_Frame.Add(i); + tex_Name.Add(sprite.Name.Content); + tex_Type.Add("spr"); + } + + AddProgress(sprite.Textures.Count); +} +void DumpFont(UndertaleFont font) +{ + if (font.Texture != null) + NotNullHandler(font.Texture); + else + NullHandler(); + tex_Frame.Add(0); + tex_Name.Add(font.Name.Content); + tex_Type.Add("fnt"); + + IncrementProgress(); +} +void DumpBackground(UndertaleBackground background) +{ + if (background.Texture != null) + NotNullHandler(background.Texture); + else + NullHandler(); + tex_Frame.Add(0); + tex_Name.Add(background.Name.Content); + tex_Type.Add("bg"); + + IncrementProgress(); +} +void NullHandler() +{ + tex_TargetX.Add(-16000); + tex_TargetY.Add(-16000); + tex_SourceWidth.Add(-16000); + tex_SourceHeight.Add(-16000); + tex_SourceX.Add(-16000); + tex_SourceY.Add(-16000); + tex_TargetWidth.Add(-16000); + tex_TargetHeight.Add(-16000); + tex_BoundingWidth.Add(-16000); + tex_BoundingHeight.Add(-16000); + tex_EmbeddedTextureID.Add(-16000); + tex_IsNull.Add(true); +} +void NotNullHandler(UndertaleTexturePageItem tex) +{ + tex_TargetX.Add(tex.TargetX); + tex_TargetY.Add(tex.TargetY); + tex_SourceWidth.Add(tex.SourceWidth); + tex_SourceHeight.Add(tex.SourceHeight); + tex_SourceX.Add(tex.SourceX); + tex_SourceY.Add(tex.SourceY); + tex_TargetWidth.Add(tex.TargetWidth); + tex_TargetHeight.Add(tex.TargetHeight); + tex_BoundingWidth.Add(tex.BoundingWidth); + tex_BoundingHeight.Add(tex.BoundingHeight); + tex_EmbeddedTextureID.Add(DonorData.EmbeddedTextures.IndexOf(tex.TexturePage)); + tex_IsNull.Add(false); +} +void TexturePageItemsUsedUpdate() +{ + foreach (UndertaleTexturePageItem texture in Data.TexturePageItems) + { + TexturePageItemsUsed.Add(false); + } + foreach(UndertaleSprite sprite in Data.Sprites) + { + for (int i = 0; i < sprite.Textures.Count; i++) + { + if (sprite.Textures[i]?.Texture != null) + { + TexturePageItemsUsed[Data.TexturePageItems.IndexOf(sprite.Textures[i]?.Texture)] = true; + } + } + } + foreach (UndertaleBackground bg in Data.Backgrounds) + { + if (bg.Texture != null) + { + TexturePageItemsUsed[Data.TexturePageItems.IndexOf(bg.Texture)] = true; + } + } + foreach (UndertaleFont fnt in Data.Fonts) + { + if (fnt.Texture != null) + { + TexturePageItemsUsed[Data.TexturePageItems.IndexOf(fnt.Texture)] = true; + } + } +} +void SpriteSheetsUsedUpdate() +{ + foreach(UndertaleSprite sprite in Data.Sprites) + { + for (int i = 0; i < sprite.Textures.Count; i++) + { + if (sprite.Textures[i]?.Texture != null) + { + SpriteSheetsUsed[Data.EmbeddedTextures.IndexOf(sprite.Textures[i]?.Texture.TexturePage)] = true; + } + } + } + foreach (UndertaleBackground bg in Data.Backgrounds) + { + if (bg.Texture != null) + { + SpriteSheetsUsed[Data.EmbeddedTextures.IndexOf(bg.Texture.TexturePage)] = true; + } + } + foreach (UndertaleFont fnt in Data.Fonts) + { + if (fnt.Texture != null) + { + SpriteSheetsUsed[Data.EmbeddedTextures.IndexOf(fnt.Texture.TexturePage)] = true; + } + } +} +//Unused +/* +void UpdateSpriteSheetsCopyNeeded() +{ + for (var j = 0; j < sprCandidates.Count; j++) + { + UndertaleSprite sprite = Data.Sprites.ByName(sprCandidates[j]); + for (int i = 0; i < sprite.Textures.Count; i++) + { + if (sprite.Textures[i]?.Texture != null) + { + SpriteSheetsCopyNeeded[Data.EmbeddedTextures.IndexOf(sprite.Textures[i]?.Texture.TexturePage)] = true; + } + } + UpdateProgress(); + } + for (var j = 0; j < bgCandidates.Count; j++) + { + UndertaleBackground bg = Data.Backgrounds.ByName(bgCandidates[j]); + if (bg.Texture != null) + { + SpriteSheetsCopyNeeded[Data.EmbeddedTextures.IndexOf(bg.Texture.TexturePage)] = true; + } + UpdateProgress(); + } + for (var j = 0; j < fntCandidates.Count; j++) + { + UndertaleFont fnt = Data.Fonts.ByName(fntCandidates[j]); + if (fnt.Texture != null) + { + SpriteSheetsCopyNeeded[Data.EmbeddedTextures.IndexOf(fnt.Texture.TexturePage)] = true; + } + UpdateProgress(); + } +} +*/ + +List GetSplitStringsList(string assetType) +{ + ScriptMessage("Enter the " + assetType + "(s) to copy"); + List splitStringsList = new List(); + string InputtedText = ""; + InputtedText = SimpleTextInput("Menu", "Enter the name(s) of the " + assetType + "(s)", InputtedText, true); + string[] IndividualLineArray = InputtedText.Split('\n', StringSplitOptions.RemoveEmptyEntries); + foreach (var OneLine in IndividualLineArray) + { + splitStringsList.Add(OneLine.Trim()); + } + return splitStringsList; +} \ No newline at end of file diff --git a/UndertaleModTool/Repackers/CopySpriteBgFontInternal.csx b/UndertaleModTool/Repackers/CopySpriteBgFontInternal.csx index abc9e799b..4a1cf50eb 100644 --- a/UndertaleModTool/Repackers/CopySpriteBgFontInternal.csx +++ b/UndertaleModTool/Repackers/CopySpriteBgFontInternal.csx @@ -1,422 +1,422 @@ -//Adapted from original script by Grossley -using System.Text; -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using UndertaleModLib.Util; - -EnsureDataLoaded(); - -// Initialization Start - -var DataEmbeddedTexturesCount = Data.EmbeddedTextures.Count; -List tex_TargetX = new List(); -List tex_TargetY = new List(); -List tex_SourceX = new List(); -List tex_SourceY = new List(); -List tex_SourceWidth = new List(); -List tex_SourceHeight = new List(); -List tex_TargetWidth = new List(); -List tex_TargetHeight = new List(); -List tex_BoundingWidth = new List(); -List tex_BoundingHeight = new List(); -List tex_Frame = new List(); -List tex_EmbeddedTextureID = new List(); -List tex_Name = new List(); -List tex_Type = new List(); -List tex_IsNull = new List(); -List TexturePageItemsUsed = new List(); - -// Initialization End - -int copiedSpritesCount = 0; -int copiedBackgroundsCount = 0; -int copiedFontsCount = 0; -int copiedAssetsCount = 0; -List splitStringsList = GetSplitStringsList("sprite(s)/background(s)/font"); -bool[] SpriteSheetsCopyNeeded = new bool[DataEmbeddedTexturesCount]; -bool[] SpriteSheetsUsed = new bool[(DataEmbeddedTexturesCount + DataEmbeddedTexturesCount)]; - -int lastTextPage = Data.EmbeddedTextures.Count - 1; - -SetProgressBar(null, "Textures Exported", 0, Data.TexturePageItems.Count); -StartUpdater(); - -SyncBinding("EmbeddedTextures, Strings, Backgrounds, Sprites, Fonts, TexturePageItems", true); -await Task.Run(() => { - for (int i = 0; i < SpriteSheetsCopyNeeded.Length; i++) - { - SpriteSheetsCopyNeeded[i] = false; - } - for (int i = 0; i < SpriteSheetsUsed.Length; i++) - { - SpriteSheetsUsed[i] = false; - } - for (var i = 0; i < DataEmbeddedTexturesCount; i++) - { - UndertaleEmbeddedTexture texture = new UndertaleEmbeddedTexture(); - texture.Name = new UndertaleString("Texture " + ++lastTextPage); - texture.TextureData.TextureBlob = Data.EmbeddedTextures[i].TextureData.TextureBlob; - Data.EmbeddedTextures.Add(texture); - } - for (var j = 0; j < splitStringsList.Count; j++) - { - int DataBackgroundsLength = Data.Backgrounds.Count; - for (var i = 0; i < DataBackgroundsLength; i++) - { - UndertaleBackground bg = Data.Backgrounds[i]; - if (splitStringsList[j].ToLower() == bg.Name.Content.ToLower()) - { - UndertaleBackground donorBG = Data.Backgrounds.ByName(bg.Name.Content); - UndertaleBackground nativeBG = new UndertaleBackground(); - nativeBG.Name = Data.Strings.MakeString(bg.Name.Content + "_Copy"); - Data.Backgrounds.Add(nativeBG); - nativeBG.Transparent = donorBG.Transparent; - nativeBG.Smooth = donorBG.Smooth; - nativeBG.Preload = donorBG.Preload; - nativeBG.GMS2UnknownAlways2 = donorBG.GMS2UnknownAlways2; - nativeBG.GMS2TileWidth = donorBG.GMS2TileWidth; - nativeBG.GMS2TileHeight = donorBG.GMS2TileHeight; - nativeBG.GMS2OutputBorderX = donorBG.GMS2OutputBorderX; - nativeBG.GMS2OutputBorderY = donorBG.GMS2OutputBorderY; - nativeBG.GMS2TileColumns = donorBG.GMS2TileColumns; - nativeBG.GMS2ItemsPerTileCount = donorBG.GMS2ItemsPerTileCount; - nativeBG.GMS2TileCount = donorBG.GMS2TileCount; - nativeBG.GMS2UnknownAlwaysZero = donorBG.GMS2UnknownAlwaysZero; - nativeBG.GMS2FrameLength = donorBG.GMS2FrameLength; - nativeBG.GMS2TileIds = donorBG.GMS2TileIds; - DumpBackground(donorBG); - copiedBackgroundsCount += 1; - } - } - int DataSpritesLength = Data.Sprites.Count; - for (var k = 0; k < DataSpritesLength; k++) - { - UndertaleSprite sprite = Data.Sprites[k]; - if (splitStringsList[j].ToLower() == sprite.Name.Content.ToLower()) - { - UndertaleSprite donorSPR = Data.Sprites.ByName(sprite.Name.Content); - UndertaleSprite nativeSPR = new UndertaleSprite(); - nativeSPR.Name = Data.Strings.MakeString(sprite.Name.Content + "_Copy"); - Data.Sprites.Add(nativeSPR); - for (var i = 0; i < donorSPR.CollisionMasks.Count; i++) - nativeSPR.CollisionMasks.Add(new UndertaleSprite.MaskEntry(donorSPR.CollisionMasks[i].Data)); - nativeSPR.Width = donorSPR.Width; - nativeSPR.Height = donorSPR.Height; - nativeSPR.MarginLeft = donorSPR.MarginLeft; - nativeSPR.MarginRight = donorSPR.MarginRight; - nativeSPR.MarginBottom = donorSPR.MarginBottom; - nativeSPR.MarginTop = donorSPR.MarginTop; - nativeSPR.Transparent = donorSPR.Transparent; - nativeSPR.Smooth = donorSPR.Smooth; - nativeSPR.Preload = donorSPR.Preload; - nativeSPR.BBoxMode = donorSPR.BBoxMode; - nativeSPR.OriginX = donorSPR.OriginX; - nativeSPR.OriginY = donorSPR.OriginY; - - // Special sprite types (always used in GMS2) - nativeSPR.SVersion = donorSPR.SVersion; - nativeSPR.GMS2PlaybackSpeed = donorSPR.GMS2PlaybackSpeed; - nativeSPR.IsSpecialType = donorSPR.IsSpecialType; - nativeSPR.SpineVersion = donorSPR.SpineVersion; - nativeSPR.SpineJSON = donorSPR.SpineJSON; - nativeSPR.SpineAtlas = donorSPR.SpineAtlas; - nativeSPR.SWFVersion = donorSPR.SWFVersion; - - //Possibly will break - nativeSPR.SepMasks = donorSPR.SepMasks; - nativeSPR.SSpriteType = donorSPR.SSpriteType; - nativeSPR.GMS2PlaybackSpeedType = donorSPR.GMS2PlaybackSpeedType; - nativeSPR.SpineTextures = donorSPR.SpineTextures; - nativeSPR.YYSWF = donorSPR.YYSWF; - nativeSPR.V2Sequence = donorSPR.V2Sequence; - nativeSPR.V3NineSlice = donorSPR.V3NineSlice; - - DumpSprite(donorSPR); - copiedSpritesCount += 1; - } - } - int DataFontsLength = Data.Fonts.Count; - for (var i = 0; i < DataFontsLength; i++) - { - UndertaleFont fnt = Data.Fonts[i]; - if (splitStringsList[j].ToLower() == fnt.Name.Content.ToLower()) - { - UndertaleFont donorFNT = Data.Fonts.ByName(fnt.Name.Content); - UndertaleFont nativeFNT = new UndertaleFont(); - nativeFNT.Name = Data.Strings.MakeString(fnt.Name.Content + "_Copy"); - Data.Fonts.Add(nativeFNT); - nativeFNT.Glyphs.Clear(); - nativeFNT.RangeStart = donorFNT.RangeStart; - nativeFNT.DisplayName = Data.Strings.MakeString(donorFNT.DisplayName.Content + "_Copy"); - nativeFNT.EmSize = donorFNT.EmSize; - nativeFNT.Bold = donorFNT.Bold; - nativeFNT.Italic = donorFNT.Italic; - nativeFNT.Charset = donorFNT.Charset; - nativeFNT.AntiAliasing = donorFNT.AntiAliasing; - nativeFNT.ScaleX = donorFNT.ScaleX; - nativeFNT.ScaleY = donorFNT.ScaleY; - foreach (UndertaleFont.Glyph glyph in donorFNT.Glyphs) - { - UndertaleFont.Glyph glyph_new = new UndertaleFont.Glyph(); - glyph_new.Character = glyph.Character; - glyph_new.SourceX = glyph.SourceX; - glyph_new.SourceY = glyph.SourceY; - glyph_new.SourceWidth = glyph.SourceWidth; - glyph_new.SourceHeight = glyph.SourceHeight; - glyph_new.Shift = glyph.Shift; - glyph_new.Offset = glyph.Offset; - nativeFNT.Glyphs.Add(glyph_new); - } - nativeFNT.RangeEnd = donorFNT.RangeEnd; - DumpFont(donorFNT); - copiedFontsCount += 1; - } - } - } - for (var i = 0; i < tex_IsNull.Count; i++) - { - if (tex_IsNull[i] == false) - { - UndertaleTexturePageItem texturePageItem = new UndertaleTexturePageItem(); - texturePageItem.TargetX = (ushort)tex_TargetX[i]; - texturePageItem.TargetY = (ushort)tex_TargetY[i]; - texturePageItem.TargetWidth = (ushort)tex_TargetWidth[i]; - texturePageItem.TargetHeight = (ushort)tex_TargetHeight[i]; - texturePageItem.SourceX = (ushort)tex_SourceX[i]; - texturePageItem.SourceY = (ushort)tex_SourceY[i]; - texturePageItem.SourceWidth = (ushort)tex_SourceWidth[i]; - texturePageItem.SourceHeight = (ushort)tex_SourceHeight[i]; - texturePageItem.BoundingWidth = (ushort)tex_BoundingWidth[i]; - texturePageItem.BoundingHeight = (ushort)tex_BoundingHeight[i]; - texturePageItem.TexturePage = Data.EmbeddedTextures[(DataEmbeddedTexturesCount + tex_EmbeddedTextureID[i])]; - Data.TexturePageItems.Add(texturePageItem); - texturePageItem.Name = new UndertaleString("PageItem " + Data.TexturePageItems.IndexOf(texturePageItem).ToString()); - if (tex_Type[i].Equals("bg")) - { - UndertaleBackground background = Data.Backgrounds.ByName(tex_Name[i]); - background.Texture = texturePageItem; - } - else if (tex_Type[i].Equals("fnt")) - { - UndertaleFont font = Data.Fonts.ByName(tex_Name[i]); - font.Texture = texturePageItem; - } - else - { - int frame = tex_Frame[i]; - UndertaleSprite sprite = Data.Sprites.ByName(tex_Name[i]); - UndertaleSprite.TextureEntry texentry = new UndertaleSprite.TextureEntry(); - texentry.Texture = texturePageItem; - if (frame > sprite.Textures.Count - 1) - { - while (frame > sprite.Textures.Count - 1) - { - sprite.Textures.Add(texentry); - } - } - else - { - sprite.Textures[frame] = texentry; - } - } - } - else - { - if (tex_Type[i] == "spr") - { - UndertaleSprite.TextureEntry texentry = new UndertaleSprite.TextureEntry(); - texentry.Texture = null; - Data.Sprites.ByName(tex_Name[i]).Textures.Add(texentry); - } - if (tex_Type[i] == "bg") - { - Data.Backgrounds.ByName(tex_Name[i]).Texture = null; - } - if (tex_Type[i] == "fnt") - { - Data.Fonts.ByName(tex_Name[i]).Texture = null; - } - } - } - SpriteSheetsUsedUpdate(); - RemoveUnusedSpriteSheets(); - TexturePageItemsUsedUpdate(); - RemoveUnusedTexturePageItems(); -}); -SyncBinding(false); - -await StopUpdater(); -HideProgressBar(); -copiedAssetsCount = (copiedFontsCount + copiedBackgroundsCount + copiedSpritesCount); -ScriptMessage(copiedAssetsCount.ToString() + " assets were copied (" + copiedSpritesCount.ToString() + " Sprites, " + copiedBackgroundsCount.ToString() + " Backgrounds, and " + copiedFontsCount.ToString() + " Fonts)"); - -// Functions - -void RemoveUnusedTexturePageItems() -{ - for (int i = (TexturePageItemsUsed.Count - 1); i > -1; i--) - { - if (TexturePageItemsUsed[i] == false) - { - Data.TexturePageItems.Remove(Data.TexturePageItems[i]); - } - } - foreach (UndertaleTexturePageItem texture in Data.TexturePageItems) - { - texture.Name = new UndertaleString("PageItem " + Data.TexturePageItems.IndexOf(texture).ToString()); - } -} -void RemoveUnusedSpriteSheets() -{ - for (int i = (SpriteSheetsUsed.Length - 1); i > -1; i--) - { - if (SpriteSheetsUsed[i] == false) - { - Data.EmbeddedTextures.Remove(Data.EmbeddedTextures[i]); - } - } - foreach (UndertaleEmbeddedTexture texture in Data.EmbeddedTextures) - { - texture.Name = new UndertaleString("Texture " + Data.EmbeddedTextures.IndexOf(texture).ToString()); - } -} -void DumpSprite(UndertaleSprite sprite) -{ - for (int i = 0; i < sprite.Textures.Count; i++) - { - if (sprite.Textures[i]?.Texture != null) - NotNullHandler(sprite.Textures[i].Texture); - else - NullHandler(); - tex_Frame.Add(i); - tex_Name.Add(sprite.Name.Content + "_Copy"); - tex_Type.Add("spr"); - } - AddProgress(sprite.Textures.Count); -} -void DumpFont(UndertaleFont font) -{ - if (font.Texture != null) - NotNullHandler(font.Texture); - else - NullHandler(); - tex_Frame.Add(0); - tex_Name.Add(font.Name.Content + "_Copy"); - tex_Type.Add("fnt"); - IncProgress(); -} -void DumpBackground(UndertaleBackground background) -{ - if (background.Texture != null) - NotNullHandler(background.Texture); - else - NullHandler(); - tex_Frame.Add(0); - tex_Name.Add(background.Name.Content + "_Copy"); - tex_Type.Add("bg"); - IncProgress(); -} -void NullHandler() -{ - tex_TargetX.Add(-16000); - tex_TargetY.Add(-16000); - tex_SourceWidth.Add(-16000); - tex_SourceHeight.Add(-16000); - tex_SourceX.Add(-16000); - tex_SourceY.Add(-16000); - tex_TargetWidth.Add(-16000); - tex_TargetHeight.Add(-16000); - tex_BoundingWidth.Add(-16000); - tex_BoundingHeight.Add(-16000); - tex_EmbeddedTextureID.Add(-16000); - tex_IsNull.Add(true); -} -void NotNullHandler(UndertaleTexturePageItem tex) -{ - tex_TargetX.Add(tex.TargetX); - tex_TargetY.Add(tex.TargetY); - tex_SourceWidth.Add(tex.SourceWidth); - tex_SourceHeight.Add(tex.SourceHeight); - tex_SourceX.Add(tex.SourceX); - tex_SourceY.Add(tex.SourceY); - tex_TargetWidth.Add(tex.TargetWidth); - tex_TargetHeight.Add(tex.TargetHeight); - tex_BoundingWidth.Add(tex.BoundingWidth); - tex_BoundingHeight.Add(tex.BoundingHeight); - tex_EmbeddedTextureID.Add(Data.EmbeddedTextures.IndexOf(tex.TexturePage)); - tex_IsNull.Add(false); -} -void TexturePageItemsUsedUpdate() -{ - foreach (UndertaleTexturePageItem texture in Data.TexturePageItems) - { - TexturePageItemsUsed.Add(false); - } - foreach(UndertaleSprite sprite in Data.Sprites) - { - for (int i = 0; i < sprite.Textures.Count; i++) - { - if (sprite.Textures[i]?.Texture != null) - { - TexturePageItemsUsed[Data.TexturePageItems.IndexOf(sprite.Textures[i]?.Texture)] = true; - } - } - } - foreach (UndertaleBackground bg in Data.Backgrounds) - { - if (bg.Texture != null) - { - TexturePageItemsUsed[Data.TexturePageItems.IndexOf(bg.Texture)] = true; - } - } - foreach (UndertaleFont fnt in Data.Fonts) - { - if (fnt.Texture != null) - { - TexturePageItemsUsed[Data.TexturePageItems.IndexOf(fnt.Texture)] = true; - } - } -} -void SpriteSheetsUsedUpdate() -{ - foreach(UndertaleSprite sprite in Data.Sprites) - { - for (int i = 0; i < sprite.Textures.Count; i++) - { - if (sprite.Textures[i]?.Texture != null) - { - SpriteSheetsUsed[Data.EmbeddedTextures.IndexOf(sprite.Textures[i]?.Texture.TexturePage)] = true; - } - } - } - foreach (UndertaleBackground bg in Data.Backgrounds) - { - if (bg.Texture != null) - { - SpriteSheetsUsed[Data.EmbeddedTextures.IndexOf(bg.Texture.TexturePage)] = true; - } - } - foreach (UndertaleFont fnt in Data.Fonts) - { - if (fnt.Texture != null) - { - SpriteSheetsUsed[Data.EmbeddedTextures.IndexOf(fnt.Texture.TexturePage)] = true; - } - } -} - -List GetSplitStringsList(string assetType) -{ - ScriptMessage("Enter the " + assetType + "(s) to copy"); - List splitStringsList = new List(); - string InputtedText = ""; - InputtedText = SimpleTextInput("Menu", "Enter the name(s) of the " + assetType + "(s)", InputtedText, true); - string[] IndividualLineArray = InputtedText.Split('\n', StringSplitOptions.RemoveEmptyEntries); - foreach (var OneLine in IndividualLineArray) - { - splitStringsList.Add(OneLine.Trim()); - } - return splitStringsList; -} +//Adapted from original script by Grossley +using System.Text; +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using UndertaleModLib.Util; + +EnsureDataLoaded(); + +// Initialization Start + +var DataEmbeddedTexturesCount = Data.EmbeddedTextures.Count; +List tex_TargetX = new List(); +List tex_TargetY = new List(); +List tex_SourceX = new List(); +List tex_SourceY = new List(); +List tex_SourceWidth = new List(); +List tex_SourceHeight = new List(); +List tex_TargetWidth = new List(); +List tex_TargetHeight = new List(); +List tex_BoundingWidth = new List(); +List tex_BoundingHeight = new List(); +List tex_Frame = new List(); +List tex_EmbeddedTextureID = new List(); +List tex_Name = new List(); +List tex_Type = new List(); +List tex_IsNull = new List(); +List TexturePageItemsUsed = new List(); + +// Initialization End + +int copiedSpritesCount = 0; +int copiedBackgroundsCount = 0; +int copiedFontsCount = 0; +int copiedAssetsCount = 0; +List splitStringsList = GetSplitStringsList("sprite(s)/background(s)/font"); +bool[] SpriteSheetsCopyNeeded = new bool[DataEmbeddedTexturesCount]; +bool[] SpriteSheetsUsed = new bool[(DataEmbeddedTexturesCount + DataEmbeddedTexturesCount)]; + +int lastTextPage = Data.EmbeddedTextures.Count - 1; + +SetProgressBar(null, "Textures Exported", 0, Data.TexturePageItems.Count); +StartProgressBarUpdater(); + +SyncBinding("EmbeddedTextures, Strings, Backgrounds, Sprites, Fonts, TexturePageItems", true); +await Task.Run(() => { + for (int i = 0; i < SpriteSheetsCopyNeeded.Length; i++) + { + SpriteSheetsCopyNeeded[i] = false; + } + for (int i = 0; i < SpriteSheetsUsed.Length; i++) + { + SpriteSheetsUsed[i] = false; + } + for (var i = 0; i < DataEmbeddedTexturesCount; i++) + { + UndertaleEmbeddedTexture texture = new UndertaleEmbeddedTexture(); + texture.Name = new UndertaleString("Texture " + ++lastTextPage); + texture.TextureData.TextureBlob = Data.EmbeddedTextures[i].TextureData.TextureBlob; + Data.EmbeddedTextures.Add(texture); + } + for (var j = 0; j < splitStringsList.Count; j++) + { + int DataBackgroundsLength = Data.Backgrounds.Count; + for (var i = 0; i < DataBackgroundsLength; i++) + { + UndertaleBackground bg = Data.Backgrounds[i]; + if (splitStringsList[j].ToLower() == bg.Name.Content.ToLower()) + { + UndertaleBackground donorBG = Data.Backgrounds.ByName(bg.Name.Content); + UndertaleBackground nativeBG = new UndertaleBackground(); + nativeBG.Name = Data.Strings.MakeString(bg.Name.Content + "_Copy"); + Data.Backgrounds.Add(nativeBG); + nativeBG.Transparent = donorBG.Transparent; + nativeBG.Smooth = donorBG.Smooth; + nativeBG.Preload = donorBG.Preload; + nativeBG.GMS2UnknownAlways2 = donorBG.GMS2UnknownAlways2; + nativeBG.GMS2TileWidth = donorBG.GMS2TileWidth; + nativeBG.GMS2TileHeight = donorBG.GMS2TileHeight; + nativeBG.GMS2OutputBorderX = donorBG.GMS2OutputBorderX; + nativeBG.GMS2OutputBorderY = donorBG.GMS2OutputBorderY; + nativeBG.GMS2TileColumns = donorBG.GMS2TileColumns; + nativeBG.GMS2ItemsPerTileCount = donorBG.GMS2ItemsPerTileCount; + nativeBG.GMS2TileCount = donorBG.GMS2TileCount; + nativeBG.GMS2UnknownAlwaysZero = donorBG.GMS2UnknownAlwaysZero; + nativeBG.GMS2FrameLength = donorBG.GMS2FrameLength; + nativeBG.GMS2TileIds = donorBG.GMS2TileIds; + DumpBackground(donorBG); + copiedBackgroundsCount += 1; + } + } + int DataSpritesLength = Data.Sprites.Count; + for (var k = 0; k < DataSpritesLength; k++) + { + UndertaleSprite sprite = Data.Sprites[k]; + if (splitStringsList[j].ToLower() == sprite.Name.Content.ToLower()) + { + UndertaleSprite donorSPR = Data.Sprites.ByName(sprite.Name.Content); + UndertaleSprite nativeSPR = new UndertaleSprite(); + nativeSPR.Name = Data.Strings.MakeString(sprite.Name.Content + "_Copy"); + Data.Sprites.Add(nativeSPR); + for (var i = 0; i < donorSPR.CollisionMasks.Count; i++) + nativeSPR.CollisionMasks.Add(new UndertaleSprite.MaskEntry(donorSPR.CollisionMasks[i].Data)); + nativeSPR.Width = donorSPR.Width; + nativeSPR.Height = donorSPR.Height; + nativeSPR.MarginLeft = donorSPR.MarginLeft; + nativeSPR.MarginRight = donorSPR.MarginRight; + nativeSPR.MarginBottom = donorSPR.MarginBottom; + nativeSPR.MarginTop = donorSPR.MarginTop; + nativeSPR.Transparent = donorSPR.Transparent; + nativeSPR.Smooth = donorSPR.Smooth; + nativeSPR.Preload = donorSPR.Preload; + nativeSPR.BBoxMode = donorSPR.BBoxMode; + nativeSPR.OriginX = donorSPR.OriginX; + nativeSPR.OriginY = donorSPR.OriginY; + + // Special sprite types (always used in GMS2) + nativeSPR.SVersion = donorSPR.SVersion; + nativeSPR.GMS2PlaybackSpeed = donorSPR.GMS2PlaybackSpeed; + nativeSPR.IsSpecialType = donorSPR.IsSpecialType; + nativeSPR.SpineVersion = donorSPR.SpineVersion; + nativeSPR.SpineJSON = donorSPR.SpineJSON; + nativeSPR.SpineAtlas = donorSPR.SpineAtlas; + nativeSPR.SWFVersion = donorSPR.SWFVersion; + + //Possibly will break + nativeSPR.SepMasks = donorSPR.SepMasks; + nativeSPR.SSpriteType = donorSPR.SSpriteType; + nativeSPR.GMS2PlaybackSpeedType = donorSPR.GMS2PlaybackSpeedType; + nativeSPR.SpineTextures = donorSPR.SpineTextures; + nativeSPR.YYSWF = donorSPR.YYSWF; + nativeSPR.V2Sequence = donorSPR.V2Sequence; + nativeSPR.V3NineSlice = donorSPR.V3NineSlice; + + DumpSprite(donorSPR); + copiedSpritesCount += 1; + } + } + int DataFontsLength = Data.Fonts.Count; + for (var i = 0; i < DataFontsLength; i++) + { + UndertaleFont fnt = Data.Fonts[i]; + if (splitStringsList[j].ToLower() == fnt.Name.Content.ToLower()) + { + UndertaleFont donorFNT = Data.Fonts.ByName(fnt.Name.Content); + UndertaleFont nativeFNT = new UndertaleFont(); + nativeFNT.Name = Data.Strings.MakeString(fnt.Name.Content + "_Copy"); + Data.Fonts.Add(nativeFNT); + nativeFNT.Glyphs.Clear(); + nativeFNT.RangeStart = donorFNT.RangeStart; + nativeFNT.DisplayName = Data.Strings.MakeString(donorFNT.DisplayName.Content + "_Copy"); + nativeFNT.EmSize = donorFNT.EmSize; + nativeFNT.Bold = donorFNT.Bold; + nativeFNT.Italic = donorFNT.Italic; + nativeFNT.Charset = donorFNT.Charset; + nativeFNT.AntiAliasing = donorFNT.AntiAliasing; + nativeFNT.ScaleX = donorFNT.ScaleX; + nativeFNT.ScaleY = donorFNT.ScaleY; + foreach (UndertaleFont.Glyph glyph in donorFNT.Glyphs) + { + UndertaleFont.Glyph glyph_new = new UndertaleFont.Glyph(); + glyph_new.Character = glyph.Character; + glyph_new.SourceX = glyph.SourceX; + glyph_new.SourceY = glyph.SourceY; + glyph_new.SourceWidth = glyph.SourceWidth; + glyph_new.SourceHeight = glyph.SourceHeight; + glyph_new.Shift = glyph.Shift; + glyph_new.Offset = glyph.Offset; + nativeFNT.Glyphs.Add(glyph_new); + } + nativeFNT.RangeEnd = donorFNT.RangeEnd; + DumpFont(donorFNT); + copiedFontsCount += 1; + } + } + } + for (var i = 0; i < tex_IsNull.Count; i++) + { + if (tex_IsNull[i] == false) + { + UndertaleTexturePageItem texturePageItem = new UndertaleTexturePageItem(); + texturePageItem.TargetX = (ushort)tex_TargetX[i]; + texturePageItem.TargetY = (ushort)tex_TargetY[i]; + texturePageItem.TargetWidth = (ushort)tex_TargetWidth[i]; + texturePageItem.TargetHeight = (ushort)tex_TargetHeight[i]; + texturePageItem.SourceX = (ushort)tex_SourceX[i]; + texturePageItem.SourceY = (ushort)tex_SourceY[i]; + texturePageItem.SourceWidth = (ushort)tex_SourceWidth[i]; + texturePageItem.SourceHeight = (ushort)tex_SourceHeight[i]; + texturePageItem.BoundingWidth = (ushort)tex_BoundingWidth[i]; + texturePageItem.BoundingHeight = (ushort)tex_BoundingHeight[i]; + texturePageItem.TexturePage = Data.EmbeddedTextures[(DataEmbeddedTexturesCount + tex_EmbeddedTextureID[i])]; + Data.TexturePageItems.Add(texturePageItem); + texturePageItem.Name = new UndertaleString("PageItem " + Data.TexturePageItems.IndexOf(texturePageItem).ToString()); + if (tex_Type[i].Equals("bg")) + { + UndertaleBackground background = Data.Backgrounds.ByName(tex_Name[i]); + background.Texture = texturePageItem; + } + else if (tex_Type[i].Equals("fnt")) + { + UndertaleFont font = Data.Fonts.ByName(tex_Name[i]); + font.Texture = texturePageItem; + } + else + { + int frame = tex_Frame[i]; + UndertaleSprite sprite = Data.Sprites.ByName(tex_Name[i]); + UndertaleSprite.TextureEntry texentry = new UndertaleSprite.TextureEntry(); + texentry.Texture = texturePageItem; + if (frame > sprite.Textures.Count - 1) + { + while (frame > sprite.Textures.Count - 1) + { + sprite.Textures.Add(texentry); + } + } + else + { + sprite.Textures[frame] = texentry; + } + } + } + else + { + if (tex_Type[i] == "spr") + { + UndertaleSprite.TextureEntry texentry = new UndertaleSprite.TextureEntry(); + texentry.Texture = null; + Data.Sprites.ByName(tex_Name[i]).Textures.Add(texentry); + } + if (tex_Type[i] == "bg") + { + Data.Backgrounds.ByName(tex_Name[i]).Texture = null; + } + if (tex_Type[i] == "fnt") + { + Data.Fonts.ByName(tex_Name[i]).Texture = null; + } + } + } + SpriteSheetsUsedUpdate(); + RemoveUnusedSpriteSheets(); + TexturePageItemsUsedUpdate(); + RemoveUnusedTexturePageItems(); +}); +DisableAllSyncBindings(); + +await StopProgressBarUpdater(); +HideProgressBar(); +copiedAssetsCount = (copiedFontsCount + copiedBackgroundsCount + copiedSpritesCount); +ScriptMessage(copiedAssetsCount.ToString() + " assets were copied (" + copiedSpritesCount.ToString() + " Sprites, " + copiedBackgroundsCount.ToString() + " Backgrounds, and " + copiedFontsCount.ToString() + " Fonts)"); + +// Functions + +void RemoveUnusedTexturePageItems() +{ + for (int i = (TexturePageItemsUsed.Count - 1); i > -1; i--) + { + if (TexturePageItemsUsed[i] == false) + { + Data.TexturePageItems.Remove(Data.TexturePageItems[i]); + } + } + foreach (UndertaleTexturePageItem texture in Data.TexturePageItems) + { + texture.Name = new UndertaleString("PageItem " + Data.TexturePageItems.IndexOf(texture).ToString()); + } +} +void RemoveUnusedSpriteSheets() +{ + for (int i = (SpriteSheetsUsed.Length - 1); i > -1; i--) + { + if (SpriteSheetsUsed[i] == false) + { + Data.EmbeddedTextures.Remove(Data.EmbeddedTextures[i]); + } + } + foreach (UndertaleEmbeddedTexture texture in Data.EmbeddedTextures) + { + texture.Name = new UndertaleString("Texture " + Data.EmbeddedTextures.IndexOf(texture).ToString()); + } +} +void DumpSprite(UndertaleSprite sprite) +{ + for (int i = 0; i < sprite.Textures.Count; i++) + { + if (sprite.Textures[i]?.Texture != null) + NotNullHandler(sprite.Textures[i].Texture); + else + NullHandler(); + tex_Frame.Add(i); + tex_Name.Add(sprite.Name.Content + "_Copy"); + tex_Type.Add("spr"); + } + AddProgress(sprite.Textures.Count); +} +void DumpFont(UndertaleFont font) +{ + if (font.Texture != null) + NotNullHandler(font.Texture); + else + NullHandler(); + tex_Frame.Add(0); + tex_Name.Add(font.Name.Content + "_Copy"); + tex_Type.Add("fnt"); + IncrementProgress(); +} +void DumpBackground(UndertaleBackground background) +{ + if (background.Texture != null) + NotNullHandler(background.Texture); + else + NullHandler(); + tex_Frame.Add(0); + tex_Name.Add(background.Name.Content + "_Copy"); + tex_Type.Add("bg"); + IncrementProgress(); +} +void NullHandler() +{ + tex_TargetX.Add(-16000); + tex_TargetY.Add(-16000); + tex_SourceWidth.Add(-16000); + tex_SourceHeight.Add(-16000); + tex_SourceX.Add(-16000); + tex_SourceY.Add(-16000); + tex_TargetWidth.Add(-16000); + tex_TargetHeight.Add(-16000); + tex_BoundingWidth.Add(-16000); + tex_BoundingHeight.Add(-16000); + tex_EmbeddedTextureID.Add(-16000); + tex_IsNull.Add(true); +} +void NotNullHandler(UndertaleTexturePageItem tex) +{ + tex_TargetX.Add(tex.TargetX); + tex_TargetY.Add(tex.TargetY); + tex_SourceWidth.Add(tex.SourceWidth); + tex_SourceHeight.Add(tex.SourceHeight); + tex_SourceX.Add(tex.SourceX); + tex_SourceY.Add(tex.SourceY); + tex_TargetWidth.Add(tex.TargetWidth); + tex_TargetHeight.Add(tex.TargetHeight); + tex_BoundingWidth.Add(tex.BoundingWidth); + tex_BoundingHeight.Add(tex.BoundingHeight); + tex_EmbeddedTextureID.Add(Data.EmbeddedTextures.IndexOf(tex.TexturePage)); + tex_IsNull.Add(false); +} +void TexturePageItemsUsedUpdate() +{ + foreach (UndertaleTexturePageItem texture in Data.TexturePageItems) + { + TexturePageItemsUsed.Add(false); + } + foreach(UndertaleSprite sprite in Data.Sprites) + { + for (int i = 0; i < sprite.Textures.Count; i++) + { + if (sprite.Textures[i]?.Texture != null) + { + TexturePageItemsUsed[Data.TexturePageItems.IndexOf(sprite.Textures[i]?.Texture)] = true; + } + } + } + foreach (UndertaleBackground bg in Data.Backgrounds) + { + if (bg.Texture != null) + { + TexturePageItemsUsed[Data.TexturePageItems.IndexOf(bg.Texture)] = true; + } + } + foreach (UndertaleFont fnt in Data.Fonts) + { + if (fnt.Texture != null) + { + TexturePageItemsUsed[Data.TexturePageItems.IndexOf(fnt.Texture)] = true; + } + } +} +void SpriteSheetsUsedUpdate() +{ + foreach(UndertaleSprite sprite in Data.Sprites) + { + for (int i = 0; i < sprite.Textures.Count; i++) + { + if (sprite.Textures[i]?.Texture != null) + { + SpriteSheetsUsed[Data.EmbeddedTextures.IndexOf(sprite.Textures[i]?.Texture.TexturePage)] = true; + } + } + } + foreach (UndertaleBackground bg in Data.Backgrounds) + { + if (bg.Texture != null) + { + SpriteSheetsUsed[Data.EmbeddedTextures.IndexOf(bg.Texture.TexturePage)] = true; + } + } + foreach (UndertaleFont fnt in Data.Fonts) + { + if (fnt.Texture != null) + { + SpriteSheetsUsed[Data.EmbeddedTextures.IndexOf(fnt.Texture.TexturePage)] = true; + } + } +} + +List GetSplitStringsList(string assetType) +{ + ScriptMessage("Enter the " + assetType + "(s) to copy"); + List splitStringsList = new List(); + string InputtedText = ""; + InputtedText = SimpleTextInput("Menu", "Enter the name(s) of the " + assetType + "(s)", InputtedText, true); + string[] IndividualLineArray = InputtedText.Split('\n', StringSplitOptions.RemoveEmptyEntries); + foreach (var OneLine in IndividualLineArray) + { + splitStringsList.Add(OneLine.Trim()); + } + return splitStringsList; +} diff --git a/UndertaleModTool/Repackers/ImportASM.csx b/UndertaleModTool/Repackers/ImportASM.csx index 5306f11d4..bbe1c107b 100644 --- a/UndertaleModTool/Repackers/ImportASM.csx +++ b/UndertaleModTool/Repackers/ImportASM.csx @@ -1,53 +1,53 @@ -// Script by Jockeholm based off of a script by Kneesnap. -// Major help and edited by Samuel Roy - -using System; -using System.IO; -using System.Threading.Tasks; -using System.Linq; -using UndertaleModLib.Util; - -EnsureDataLoaded(); - -if (Data.ToolInfo.ProfileMode) -{ - if (!ScriptQuestion("This will cause desyncs! As such, your copy of the code(s) you are importing will be cleared, and will be overwritten with a copy decompiled from this ASM. Continue?")) - return; -} - -// Check code directory. -string importFolder = PromptChooseDirectory("Import From Where"); -if (importFolder == null) - throw new ScriptException("The import folder was not set."); - -string[] dirFiles = Directory.GetFiles(importFolder); -if (dirFiles.Length == 0) - throw new ScriptException("The selected folder is empty."); -else if (!dirFiles.Any(x => x.EndsWith(".asm"))) - throw new ScriptException("The selected folder doesn't contain any ASM file."); - -// Ask whether they want to link code. If no, will only generate code entry. -// If yes, will try to add code to objects and scripts depending upon its name -bool doParse = ScriptQuestion("Do you want to automatically attempt to link imported code?"); - -bool stopOnError = ScriptQuestion("Stop importing on error?"); - - - -SetProgressBar(null, "Files", 0, dirFiles.Length); -StartUpdater(); - -SyncBinding("Strings, Code, CodeLocals, Scripts, GlobalInitScripts, GameObjects, Functions, Variables", true); -await Task.Run(() => { - foreach (string file in dirFiles) - { - ImportASMFile(file, doParse, true, false, stopOnError); - - IncProgress(); - } -}); -SyncBinding(false); - -await StopUpdater(); -HideProgressBar(); +// Script by Jockeholm based off of a script by Kneesnap. +// Major help and edited by Samuel Roy + +using System; +using System.IO; +using System.Threading.Tasks; +using System.Linq; +using UndertaleModLib.Util; + +EnsureDataLoaded(); + +if (Data.ToolInfo.ProfileMode) +{ + if (!ScriptQuestion("This will cause desyncs! As such, your copy of the code(s) you are importing will be cleared, and will be overwritten with a copy decompiled from this ASM. Continue?")) + return; +} + +// Check code directory. +string importFolder = PromptChooseDirectory(); +if (importFolder == null) + throw new ScriptException("The import folder was not set."); + +string[] dirFiles = Directory.GetFiles(importFolder); +if (dirFiles.Length == 0) + throw new ScriptException("The selected folder is empty."); +else if (!dirFiles.Any(x => x.EndsWith(".asm"))) + throw new ScriptException("The selected folder doesn't contain any ASM file."); + +// Ask whether they want to link code. If no, will only generate code entry. +// If yes, will try to add code to objects and scripts depending upon its name +bool doParse = ScriptQuestion("Do you want to automatically attempt to link imported code?"); + +bool stopOnError = ScriptQuestion("Stop importing on error?"); + + + +SetProgressBar(null, "Files", 0, dirFiles.Length); +StartProgressBarUpdater(); + +SyncBinding("Strings, Code, CodeLocals, Scripts, GlobalInitScripts, GameObjects, Functions, Variables", true); +await Task.Run(() => { + foreach (string file in dirFiles) + { + ImportASMFile(file, doParse, true, false, stopOnError); + + IncrementProgress(); + } +}); +DisableAllSyncBindings(); + +await StopProgressBarUpdater(); +HideProgressBar(); ScriptMessage("All files successfully imported."); \ No newline at end of file diff --git a/UndertaleModTool/Repackers/ImportASM_2_3.csx b/UndertaleModTool/Repackers/ImportASM_2_3.csx index eea12b0bc..dba62ebf1 100644 --- a/UndertaleModTool/Repackers/ImportASM_2_3.csx +++ b/UndertaleModTool/Repackers/ImportASM_2_3.csx @@ -1,255 +1,255 @@ -// Script by Jockeholm based off of a script by Kneesnap. -// Major help and edited by Samuel Roy - -using System; -using System.IO; -using System.Threading.Tasks; -using UndertaleModLib.Util; - -EnsureDataLoaded(); - -bool is2_3 = false; -if (!((Data.GMS2_3 == false) && (Data.GMS2_3_1 == false) && (Data.GMS2_3_2 == false))) -{ - is2_3 = true; - ScriptMessage("This script is for GMS 2.3 games code from \"ExportAllCode2_3.csx\", because some code names get so long that Windows cannot write them adequately."); -} -else -{ - ScriptError("Use the regular ImportASM please!", "Incompatible"); -} - -enum EventTypes -{ - Create, - Destroy, - Alarm, - Step, - Collision, - Keyboard, - Mouse, - Other, - Draw, - KeyPress, - KeyRelease, - Gesture, - Asynchronous, - PreCreate -} - -// Check code directory. -string importFolder = PromptChooseDirectory("Import From Where"); -if (importFolder == null) - throw new ScriptException("The import folder was not set."); - -List CodeList = new List(); - -if (File.Exists(importFolder + "/LookUpTable.txt")) -{ - int counter = 0; - string line; - System.IO.StreamReader file = new System.IO.StreamReader(importFolder + "/" + "LookUpTable.txt"); - while((line = file.ReadLine()) != null) - { - if (counter > 0) - CodeList.Add(line); - counter++; - } - file.Close(); -} -else -{ - ScriptError("No LookUpTable.txt!", "Error"); - return; -} - -// Ask whether they want to link code. If no, will only generate code entry. -// If yes, will try to add code to objects and scripts depending upon its name -bool doParse = ScriptQuestion("Do you want to automatically attempt to link imported code?"); - -string[] dirFiles = Directory.GetFiles(importFolder); -bool skipGlobalScripts = true; -bool skipGlobalScriptsPrompted = false; - -SetProgressBar(null, "Files", 0, dirFiles.Length); -StartUpdater(); - -SyncBinding("Strings, Code, CodeLocals, Scripts, GlobalInitScripts, GameObjects, Functions, Variables", true); -await Task.Run(() => { - foreach (string file in dirFiles) - { - IncProgress(); - - string fileName = Path.GetFileName(file); - if (!(fileName.EndsWith(".asm"))) - continue; - fileName = Path.GetFileNameWithoutExtension(file); - int number; - bool success = Int32.TryParse(fileName, out number); - string codeName = ""; - if (success) - { - codeName = CodeList[number]; - fileName = codeName + ".asm"; - } - else - { - ScriptError("ASM file not in range of look up table!", "Error"); - return; - } - if (fileName.EndsWith("PreCreate_0.asm") && (Data.GeneralInfo.Major < 2)) - continue; // Restarts loop if file is not a valid code asset. - string asmCode = File.ReadAllText(file); - if (codeName.Substring(0, 17).Equals("gml_GlobalScript_") && is2_3 && (!(skipGlobalScriptsPrompted))) - { - skipGlobalScriptsPrompted = true; - skipGlobalScripts = ScriptQuestion("Skip global scripts parsing?"); - } - if (codeName.Substring(0, 17).Equals("gml_GlobalScript_") && is2_3 && ((skipGlobalScriptsPrompted))) - { - if (skipGlobalScripts) - continue; - } - UndertaleCode code = Data.Code.ByName(codeName); - if (code == null) // Should keep from adding duplicate scripts; haven't tested - { - code = new UndertaleCode(); - code.Name = Data.Strings.MakeString(codeName); - Data.Code.Add(code); - } - if ((Data?.GeneralInfo.BytecodeVersion > 14) && (Data.CodeLocals.ByName(codeName) == null)) - { - UndertaleCodeLocals locals = new UndertaleCodeLocals(); - locals.Name = code.Name; - UndertaleCodeLocals.LocalVar argsLocal = new UndertaleCodeLocals.LocalVar(); - argsLocal.Name = Data.Strings.MakeString("arguments"); - argsLocal.Index = 0; - locals.Locals.Add(argsLocal); - code.LocalsCount = 1; - code.GenerateLocalVarDefinitions(code.FindReferencedLocalVars(), locals); // Dunno if we actually need this line, but it seems to work? - Data.CodeLocals.Add(locals); - } - if (doParse) - { - // This portion links code. - if (codeName.Substring(0, 10).Equals("gml_Script")) - { - // Add code to scripts section. - if (Data.Scripts.ByName(codeName.Substring(11)) == null) - { - UndertaleScript scr = new UndertaleScript(); - scr.Name = Data.Strings.MakeString(codeName.Substring(11)); - scr.Code = code; - Data.Scripts.Add(scr); - } - else - { - UndertaleScript scr = Data.Scripts.ByName(codeName.Substring(11)); - scr.Code = code; - } - } - else if (codeName.Substring(0, 10).Equals("gml_Object")) - { - // Add code to object methods. - string afterPrefix = codeName.Substring(11); - // Dumb substring stuff, don't mess with this. - int underCount = 0; - string methodNumberStr = "", methodName = "", objName = ""; - for (int i = afterPrefix.Length - 1; i >= 0; i--) - { - if (afterPrefix[i] == '_') - { - underCount++; - if (underCount == 1) - { - methodNumberStr = afterPrefix.Substring(i + 1); - } - else if (underCount == 2) - { - objName = afterPrefix.Substring(0, i); - methodName = afterPrefix.Substring(i + 1, afterPrefix.Length - objName.Length - methodNumberStr.Length - 2); - break; - } - } - } - int methodNumber = Int32.Parse(methodNumberStr); - UndertaleGameObject obj = Data.GameObjects.ByName(objName); - if (obj == null) - { - bool doNewObj = ScriptQuestion("Object " + objName + " was not found.\nAdd new object called " + objName + "?"); - if (doNewObj) - { - UndertaleGameObject gameObj = new UndertaleGameObject(); - gameObj.Name = Data.Strings.MakeString(objName); - Data.GameObjects.Add(gameObj); - } - else - { - try - { - var instructions = Assembler.Assemble(asmCode, Data); - code.Replace(instructions); - } - catch (Exception ex) - { - ScriptMessage("Assembler error at file: " + codeName); - return; - } - continue; - } - } - obj = Data.GameObjects.ByName(objName); - int eventIdx = (int)Enum.Parse(typeof(EventTypes), methodName); - - bool duplicate = false; - try - { - foreach (UndertaleGameObject.Event evnt in obj.Events[eventIdx]) - { - foreach (UndertaleGameObject.EventAction action in evnt.Actions) - { - if (action.CodeId.Name.Content == codeName) - duplicate = true; - } - } - } - catch - { - //something went wrong, but probably because it's trying to check something non-existent - //we're gonna make it so - //keep going - } - if (duplicate == false) - { - UndertalePointerList eventList = obj.Events[eventIdx]; - UndertaleGameObject.EventAction action = new UndertaleGameObject.EventAction(); - UndertaleGameObject.Event evnt = new UndertaleGameObject.Event(); - action.ActionName = code.Name; - action.CodeId = code; - evnt.EventSubtype = (uint)methodNumber; - evnt.Actions.Add(action); - eventList.Add(evnt); - } - } - // Code which does not match these criteria cannot link, but are still added to the code section. - } - else - { - try - { - var instructions = Assembler.Assemble(asmCode, Data); - code.Replace(instructions); - } - catch (Exception ex) - { - ScriptMessage("Assembler error at file: " + codeName); - return; - } - } - } -}); -SyncBinding(false); - -await StopUpdater(); -HideProgressBar(); +// Script by Jockeholm based off of a script by Kneesnap. +// Major help and edited by Samuel Roy + +using System; +using System.IO; +using System.Threading.Tasks; +using UndertaleModLib.Util; + +EnsureDataLoaded(); + +bool is2_3 = false; +if (!((Data.GMS2_3 == false) && (Data.GMS2_3_1 == false) && (Data.GMS2_3_2 == false))) +{ + is2_3 = true; + ScriptMessage("This script is for GMS 2.3 games code from \"ExportAllCode2_3.csx\", because some code names get so long that Windows cannot write them adequately."); +} +else +{ + ScriptError("Use the regular ImportASM please!", "Incompatible"); +} + +enum EventTypes +{ + Create, + Destroy, + Alarm, + Step, + Collision, + Keyboard, + Mouse, + Other, + Draw, + KeyPress, + KeyRelease, + Gesture, + Asynchronous, + PreCreate +} + +// Check code directory. +string importFolder = PromptChooseDirectory(); +if (importFolder == null) + throw new ScriptException("The import folder was not set."); + +List CodeList = new List(); + +if (File.Exists(importFolder + "/LookUpTable.txt")) +{ + int counter = 0; + string line; + System.IO.StreamReader file = new System.IO.StreamReader(importFolder + "/" + "LookUpTable.txt"); + while((line = file.ReadLine()) != null) + { + if (counter > 0) + CodeList.Add(line); + counter++; + } + file.Close(); +} +else +{ + ScriptError("No LookUpTable.txt!", "Error"); + return; +} + +// Ask whether they want to link code. If no, will only generate code entry. +// If yes, will try to add code to objects and scripts depending upon its name +bool doParse = ScriptQuestion("Do you want to automatically attempt to link imported code?"); + +string[] dirFiles = Directory.GetFiles(importFolder); +bool skipGlobalScripts = true; +bool skipGlobalScriptsPrompted = false; + +SetProgressBar(null, "Files", 0, dirFiles.Length); +StartProgressBarUpdater(); + +SyncBinding("Strings, Code, CodeLocals, Scripts, GlobalInitScripts, GameObjects, Functions, Variables", true); +await Task.Run(() => { + foreach (string file in dirFiles) + { + IncrementProgress(); + + string fileName = Path.GetFileName(file); + if (!(fileName.EndsWith(".asm"))) + continue; + fileName = Path.GetFileNameWithoutExtension(file); + int number; + bool success = Int32.TryParse(fileName, out number); + string codeName = ""; + if (success) + { + codeName = CodeList[number]; + fileName = codeName + ".asm"; + } + else + { + ScriptError("ASM file not in range of look up table!", "Error"); + return; + } + if (fileName.EndsWith("PreCreate_0.asm") && (Data.GeneralInfo.Major < 2)) + continue; // Restarts loop if file is not a valid code asset. + string asmCode = File.ReadAllText(file); + if (codeName.Substring(0, 17).Equals("gml_GlobalScript_") && is2_3 && (!(skipGlobalScriptsPrompted))) + { + skipGlobalScriptsPrompted = true; + skipGlobalScripts = ScriptQuestion("Skip global scripts parsing?"); + } + if (codeName.Substring(0, 17).Equals("gml_GlobalScript_") && is2_3 && ((skipGlobalScriptsPrompted))) + { + if (skipGlobalScripts) + continue; + } + UndertaleCode code = Data.Code.ByName(codeName); + if (code == null) // Should keep from adding duplicate scripts; haven't tested + { + code = new UndertaleCode(); + code.Name = Data.Strings.MakeString(codeName); + Data.Code.Add(code); + } + if ((Data?.GeneralInfo.BytecodeVersion > 14) && (Data.CodeLocals.ByName(codeName) == null)) + { + UndertaleCodeLocals locals = new UndertaleCodeLocals(); + locals.Name = code.Name; + UndertaleCodeLocals.LocalVar argsLocal = new UndertaleCodeLocals.LocalVar(); + argsLocal.Name = Data.Strings.MakeString("arguments"); + argsLocal.Index = 0; + locals.Locals.Add(argsLocal); + code.LocalsCount = 1; + code.GenerateLocalVarDefinitions(code.FindReferencedLocalVars(), locals); // Dunno if we actually need this line, but it seems to work? + Data.CodeLocals.Add(locals); + } + if (doParse) + { + // This portion links code. + if (codeName.Substring(0, 10).Equals("gml_Script")) + { + // Add code to scripts section. + if (Data.Scripts.ByName(codeName.Substring(11)) == null) + { + UndertaleScript scr = new UndertaleScript(); + scr.Name = Data.Strings.MakeString(codeName.Substring(11)); + scr.Code = code; + Data.Scripts.Add(scr); + } + else + { + UndertaleScript scr = Data.Scripts.ByName(codeName.Substring(11)); + scr.Code = code; + } + } + else if (codeName.Substring(0, 10).Equals("gml_Object")) + { + // Add code to object methods. + string afterPrefix = codeName.Substring(11); + // Dumb substring stuff, don't mess with this. + int underCount = 0; + string methodNumberStr = "", methodName = "", objName = ""; + for (int i = afterPrefix.Length - 1; i >= 0; i--) + { + if (afterPrefix[i] == '_') + { + underCount++; + if (underCount == 1) + { + methodNumberStr = afterPrefix.Substring(i + 1); + } + else if (underCount == 2) + { + objName = afterPrefix.Substring(0, i); + methodName = afterPrefix.Substring(i + 1, afterPrefix.Length - objName.Length - methodNumberStr.Length - 2); + break; + } + } + } + int methodNumber = Int32.Parse(methodNumberStr); + UndertaleGameObject obj = Data.GameObjects.ByName(objName); + if (obj == null) + { + bool doNewObj = ScriptQuestion("Object " + objName + " was not found.\nAdd new object called " + objName + "?"); + if (doNewObj) + { + UndertaleGameObject gameObj = new UndertaleGameObject(); + gameObj.Name = Data.Strings.MakeString(objName); + Data.GameObjects.Add(gameObj); + } + else + { + try + { + var instructions = Assembler.Assemble(asmCode, Data); + code.Replace(instructions); + } + catch (Exception ex) + { + ScriptMessage("Assembler error at file: " + codeName); + return; + } + continue; + } + } + obj = Data.GameObjects.ByName(objName); + int eventIdx = (int)Enum.Parse(typeof(EventTypes), methodName); + + bool duplicate = false; + try + { + foreach (UndertaleGameObject.Event evnt in obj.Events[eventIdx]) + { + foreach (UndertaleGameObject.EventAction action in evnt.Actions) + { + if (action.CodeId.Name.Content == codeName) + duplicate = true; + } + } + } + catch + { + //something went wrong, but probably because it's trying to check something non-existent + //we're gonna make it so + //keep going + } + if (duplicate == false) + { + UndertalePointerList eventList = obj.Events[eventIdx]; + UndertaleGameObject.EventAction action = new UndertaleGameObject.EventAction(); + UndertaleGameObject.Event evnt = new UndertaleGameObject.Event(); + action.ActionName = code.Name; + action.CodeId = code; + evnt.EventSubtype = (uint)methodNumber; + evnt.Actions.Add(action); + eventList.Add(evnt); + } + } + // Code which does not match these criteria cannot link, but are still added to the code section. + } + else + { + try + { + var instructions = Assembler.Assemble(asmCode, Data); + code.Replace(instructions); + } + catch (Exception ex) + { + ScriptMessage("Assembler error at file: " + codeName); + return; + } + } + } +}); +DisableAllSyncBindings(); + +await StopProgressBarUpdater(); +HideProgressBar(); ScriptMessage("All files successfully imported."); \ No newline at end of file diff --git a/UndertaleModTool/Repackers/ImportAllStrings.csx b/UndertaleModTool/Repackers/ImportAllStrings.csx index ad0557c1a..25b161969 100644 --- a/UndertaleModTool/Repackers/ImportAllStrings.csx +++ b/UndertaleModTool/Repackers/ImportAllStrings.csx @@ -1,100 +1,100 @@ -// Adapted from original script by Grossley - -using System.Text; -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using UndertaleModLib.Util; - -EnsureDataLoaded(); - -if (Data.ToolInfo.ProfileMode) -{ - ScriptMessage("This script will not modify your existing edited GML code registered in your profile. Please use GML editing for text editing, or a script like FindAndReplace, for editing strings within these code entries."); -} -else -{ - if (!(ScriptQuestion("This script will recompile all code entries in your profile (if they exist) to the default decompiled output. Continue?"))) - return; - foreach (UndertaleCode c in Data.Code) - NukeProfileGML(c.Name.Content); -} - -string importFolder = PromptChooseDirectory("Import from where"); -if (importFolder == null) - throw new ScriptException("The import folder was not set."); - -//Overwrite Check One -if (!File.Exists(importFolder + "/strings.txt")) -{ - ScriptError("No 'strings.txt' file exists!", "Error"); - return; -} - -int file_length = 0; -string line = ""; -using (StreamReader reader = new StreamReader(importFolder + "/strings.txt")) -{ - while ((line = reader.ReadLine()) != null) - { - file_length += 1; - } -} - -int validStringsCount = 0; -foreach (var str in Data.Strings) -{ - if (str.Content.Contains("\n") || str.Content.Contains("\r")) - continue; - validStringsCount += 1; -} - -if (file_length < validStringsCount) -{ - ScriptError("ERROR 0: Unexpected end of file at line: " + file_length.ToString() + ". Expected file length was: " + validStringsCount.ToString() + ". No changes have been made.", "Error"); - return; -} -else if (file_length > validStringsCount) -{ - ScriptError("ERROR 1: Line count exceeds expected count. Current count: " + file_length.ToString() + ". Expected count: " + validStringsCount.ToString() + ". No changes have been made.", "Error"); - return; -} - -using (StreamReader reader = new StreamReader(importFolder + "/strings.txt")) -{ - int line_no = 1; - line = ""; - foreach (var str in Data.Strings) - { - if (str.Content.Contains("\n") || str.Content.Contains("\r")) - continue; - if (!((line = reader.ReadLine()) != null)) - { - ScriptError("ERROR 2: Unexpected end of file at line: " + line_no.ToString() + ". Expected file length was: " + validStringsCount.ToString() + ". No changes have been made.", "Error"); - return; - } - line_no += 1; - } -} - -using (StreamReader reader = new StreamReader(importFolder + "/strings.txt")) -{ - int line_no = 1; - line = ""; - foreach (var str in Data.Strings) - { - if (str.Content.Contains("\n") || str.Content.Contains("\r")) - continue; - if ((line = reader.ReadLine()) != null) - str.Content = line; - else - { - ScriptError("ERROR 3: Unexpected end of file at line: " + line_no.ToString() + ". Expected file length was: " + validStringsCount.ToString() + ". All lines within the file have been applied. Please check for errors.", "Error"); - return; - } - line_no += 1; - } -} - +// Adapted from original script by Grossley + +using System.Text; +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using UndertaleModLib.Util; + +EnsureDataLoaded(); + +if (Data.ToolInfo.ProfileMode) +{ + ScriptMessage("This script will not modify your existing edited GML code registered in your profile. Please use GML editing for text editing, or a script like FindAndReplace, for editing strings within these code entries."); +} +else +{ + if (!(ScriptQuestion("This script will recompile all code entries in your profile (if they exist) to the default decompiled output. Continue?"))) + return; + foreach (UndertaleCode c in Data.Code) + NukeProfileGML(c.Name.Content); +} + +string importFolder = PromptChooseDirectory(); +if (importFolder == null) + throw new ScriptException("The import folder was not set."); + +//Overwrite Check One +if (!File.Exists(importFolder + "/strings.txt")) +{ + ScriptError("No 'strings.txt' file exists!", "Error"); + return; +} + +int file_length = 0; +string line = ""; +using (StreamReader reader = new StreamReader(importFolder + "/strings.txt")) +{ + while ((line = reader.ReadLine()) != null) + { + file_length += 1; + } +} + +int validStringsCount = 0; +foreach (var str in Data.Strings) +{ + if (str.Content.Contains("\n") || str.Content.Contains("\r")) + continue; + validStringsCount += 1; +} + +if (file_length < validStringsCount) +{ + ScriptError("ERROR 0: Unexpected end of file at line: " + file_length.ToString() + ". Expected file length was: " + validStringsCount.ToString() + ". No changes have been made.", "Error"); + return; +} +else if (file_length > validStringsCount) +{ + ScriptError("ERROR 1: Line count exceeds expected count. Current count: " + file_length.ToString() + ". Expected count: " + validStringsCount.ToString() + ". No changes have been made.", "Error"); + return; +} + +using (StreamReader reader = new StreamReader(importFolder + "/strings.txt")) +{ + int line_no = 1; + line = ""; + foreach (var str in Data.Strings) + { + if (str.Content.Contains("\n") || str.Content.Contains("\r")) + continue; + if (!((line = reader.ReadLine()) != null)) + { + ScriptError("ERROR 2: Unexpected end of file at line: " + line_no.ToString() + ". Expected file length was: " + validStringsCount.ToString() + ". No changes have been made.", "Error"); + return; + } + line_no += 1; + } +} + +using (StreamReader reader = new StreamReader(importFolder + "/strings.txt")) +{ + int line_no = 1; + line = ""; + foreach (var str in Data.Strings) + { + if (str.Content.Contains("\n") || str.Content.Contains("\r")) + continue; + if ((line = reader.ReadLine()) != null) + str.Content = line; + else + { + ScriptError("ERROR 3: Unexpected end of file at line: " + line_no.ToString() + ". Expected file length was: " + validStringsCount.ToString() + ". All lines within the file have been applied. Please check for errors.", "Error"); + return; + } + line_no += 1; + } +} + ReapplyProfileCode(); \ No newline at end of file diff --git a/UndertaleModTool/Repackers/ImportAllTilesets.csx b/UndertaleModTool/Repackers/ImportAllTilesets.csx index 3421ecffb..29deba530 100644 --- a/UndertaleModTool/Repackers/ImportAllTilesets.csx +++ b/UndertaleModTool/Repackers/ImportAllTilesets.csx @@ -1,62 +1,62 @@ -// Adapted from original script by Grossley - -using System.Text; -using System; -using System.IO; -using System.Drawing; -using System.Threading; -using System.Threading.Tasks; - -EnsureDataLoaded(); - -// Setup root export folder. -string winFolder = GetFolder(FilePath); // The folder data.win is located in. - -string subPath = winFolder + "Export_Tilesets"; -int i = 0; - -string GetFolder(string path) -{ - return Path.GetDirectoryName(path) + Path.DirectorySeparatorChar; -} - -// Folder Check One -if (!Directory.Exists(winFolder + "Export_Tilesets\\")) -{ - ScriptError("There is no 'Export_Tilesets' folder to import.", "Error: Nothing to import."); - return; -} - -SetProgressBar(null, "Tilesets", 0, Data.Backgrounds.Count); -StartUpdater(); - -await ImportTilesets(); - -await StopUpdater(); -HideProgressBar(); -ScriptMessage("Import Complete."); - - -async Task ImportTilesets() -{ - await Task.Run(() => Parallel.ForEach(Data.Backgrounds, ImportTileset)); -} - -void ImportTileset(UndertaleBackground tileset) -{ - try - { - string path = subPath + "\\" + tileset.Name.Content + ".png"; - if (File.Exists(path)) - { - Bitmap img = new Bitmap(path); - tileset.Texture.ReplaceTexture((Image)img); - } - } - catch (Exception ex) - { - ScriptMessage($"Failed to import file {tileset.Name} (index - {Data.Backgrounds.IndexOf(tileset)}) due to: " + ex.Message); - } - - IncProgressP(); -} +// Adapted from original script by Grossley + +using System.Text; +using System; +using System.IO; +using System.Drawing; +using System.Threading; +using System.Threading.Tasks; + +EnsureDataLoaded(); + +// Setup root export folder. +string winFolder = GetFolder(FilePath); // The folder data.win is located in. + +string subPath = winFolder + "Export_Tilesets"; +int i = 0; + +string GetFolder(string path) +{ + return Path.GetDirectoryName(path) + Path.DirectorySeparatorChar; +} + +// Folder Check One +if (!Directory.Exists(winFolder + "Export_Tilesets\\")) +{ + ScriptError("There is no 'Export_Tilesets' folder to import.", "Error: Nothing to import."); + return; +} + +SetProgressBar(null, "Tilesets", 0, Data.Backgrounds.Count); +StartProgressBarUpdater(); + +await ImportTilesets(); + +await StopProgressBarUpdater(); +HideProgressBar(); +ScriptMessage("Import Complete."); + + +async Task ImportTilesets() +{ + await Task.Run(() => Parallel.ForEach(Data.Backgrounds, ImportTileset)); +} + +void ImportTileset(UndertaleBackground tileset) +{ + try + { + string path = subPath + "\\" + tileset.Name.Content + ".png"; + if (File.Exists(path)) + { + Bitmap img = new Bitmap(path); + tileset.Texture.ReplaceTexture((Image)img); + } + } + catch (Exception ex) + { + ScriptMessage($"Failed to import file {tileset.Name} (index - {Data.Backgrounds.IndexOf(tileset)}) due to: " + ex.Message); + } + + IncrementProgress(); +} diff --git a/UndertaleModTool/Repackers/ImportFontData.csx b/UndertaleModTool/Repackers/ImportFontData.csx index 2edb720b4..9f1b5d4e2 100644 --- a/UndertaleModTool/Repackers/ImportFontData.csx +++ b/UndertaleModTool/Repackers/ImportFontData.csx @@ -1,456 +1,456 @@ -//Texture packer by Samuel Roy -// Uses code from https://github.com/mfascia/TexturePacker - -using System; -using System.IO; -using System.Drawing; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using UndertaleModLib.Util; - -EnsureDataLoaded(); - -string importFolder = PromptChooseDirectory("Import From Where"); -if (importFolder == null) - throw new ScriptException("The import folder was not set."); - -System.IO.Directory.CreateDirectory("Packager"); -string sourcePath = importFolder; -string searchPattern = "*.png"; -string outName = "Packager/atlas.txt"; -int textureSize = 2048; -int border = 2; -bool debug = false; -Packer packer = new Packer(); -packer.Process(sourcePath, searchPattern, textureSize, border, debug); -packer.SaveAtlasses(outName); - -int lastTextPage = Data.EmbeddedTextures.Count - 1; -int lastTextPageItem = Data.TexturePageItems.Count - 1; - -string prefix = outName.Replace(Path.GetExtension(outName), ""); -int atlasCount = 0; -foreach (Atlas atlas in packer.Atlasses) -{ - string atlasName = String.Format(prefix + "{0:000}" + ".png", atlasCount); - Bitmap atlasBitmap = new Bitmap(atlasName); - UndertaleEmbeddedTexture texture = new UndertaleEmbeddedTexture(); - texture.Name = new UndertaleString("Texture " + ++lastTextPage); - texture.TextureData.TextureBlob = File.ReadAllBytes(atlasName); - Data.EmbeddedTextures.Add(texture); - foreach (Node n in atlas.Nodes) - { - if (n.Texture != null) - { - UndertaleTexturePageItem texturePageItem = new UndertaleTexturePageItem(); - texturePageItem.Name = new UndertaleString("PageItem " + ++lastTextPageItem); - texturePageItem.SourceX = (ushort)n.Bounds.X; - texturePageItem.SourceY = (ushort)n.Bounds.Y; - texturePageItem.SourceWidth = (ushort)n.Bounds.Width; - texturePageItem.SourceHeight = (ushort)n.Bounds.Height; - texturePageItem.TargetX = 0; - texturePageItem.TargetY = 0; - texturePageItem.TargetWidth = (ushort)n.Bounds.Width; - texturePageItem.TargetHeight = (ushort)n.Bounds.Height; - texturePageItem.BoundingWidth = (ushort)n.Bounds.Width; - texturePageItem.BoundingHeight = (ushort)n.Bounds.Height; - texturePageItem.TexturePage = texture; - Data.TexturePageItems.Add(texturePageItem); - string stripped = Path.GetFileNameWithoutExtension(n.Texture.Source); - int lastUnderscore = stripped.LastIndexOf('_'); - string spriteName = stripped.Substring(0, lastUnderscore); - - UndertaleFont font = null; - font = Data.Fonts.ByName(stripped); - - if(font == null) - { - UndertaleString fontUTString = Data.Strings.MakeString(stripped); - UndertaleFont newFont = new UndertaleFont(); - newFont.Name = fontUTString; - - fontUpdate(newFont); - newFont.Texture = texturePageItem; - Data.Fonts.Add(newFont); - continue; - } - - fontUpdate(font); - font.Texture = texturePageItem; - UndertaleSprite.TextureEntry texentry = new UndertaleSprite.TextureEntry(); - texentry.Texture = texturePageItem; - } - } - atlasCount++; -} - - - -HideProgressBar(); -ScriptMessage("Import Complete!"); - -public void fontUpdate(UndertaleFont newFont) -{ - using(StreamReader reader = new StreamReader(sourcePath + "glyphs_" + newFont.Name.Content + ".csv")) - { - newFont.Glyphs.Clear(); - string line; - int head = 0; - while((line = reader.ReadLine()) != null) - { - string[] s = line.Split(';'); - - if (head == 1) - { - newFont.RangeStart = UInt16.Parse(s[0]); - head++; - } - - if (head == 0) - { - String namae = s[0].Replace("\"", ""); - newFont.DisplayName = Data.Strings.MakeString(namae); - newFont.EmSize = UInt16.Parse(s[1]); - newFont.Bold = Boolean.Parse(s[2]); - newFont.Italic = Boolean.Parse(s[3]); - newFont.Charset = Byte.Parse(s[4]); - newFont.AntiAliasing = Byte.Parse(s[5]); - newFont.ScaleX = UInt16.Parse(s[6]); - newFont.ScaleY = UInt16.Parse(s[7]); - head++; - } - - if (head > 1) - { - newFont.Glyphs.Add(new UndertaleFont.Glyph() - { - Character = UInt16.Parse(s[0]), - SourceX = UInt16.Parse(s[1]), - SourceY = UInt16.Parse(s[2]), - SourceWidth = UInt16.Parse(s[3]), - SourceHeight = UInt16.Parse(s[4]), - Shift = Int16.Parse(s[5]), - Offset = Int16.Parse(s[6]), - }); - newFont.RangeEnd = UInt32.Parse(s[0]); - } - } - - } -} - -public class TextureInfo -{ - public string Source; - public int Width; - public int Height; -} - -public enum SplitType -{ - Horizontal, - Vertical, -} - -public enum BestFitHeuristic -{ - Area, - MaxOneAxis, -} - -public class Node -{ - public Rectangle Bounds; - public TextureInfo Texture; - public SplitType SplitType; -} - -public class Atlas -{ - public int Width; - public int Height; - public List Nodes; -} - -public class Packer -{ - public List SourceTextures; - public StringWriter Log; - public StringWriter Error; - public int Padding; - public int AtlasSize; - public bool DebugMode; - public BestFitHeuristic FitHeuristic; - public List Atlasses; - - public Packer() - { - SourceTextures = new List(); - Log = new StringWriter(); - Error = new StringWriter(); - } - - public void Process(string _SourceDir, string _Pattern, int _AtlasSize, int _Padding, bool _DebugMode) - { - Padding = _Padding; - AtlasSize = _AtlasSize; - DebugMode = _DebugMode; - //1: scan for all the textures we need to pack - ScanForTextures(_SourceDir, _Pattern); - List textures = new List(); - textures = SourceTextures.ToList(); - //2: generate as many atlasses as needed (with the latest one as small as possible) - Atlasses = new List(); - while (textures.Count > 0) - { - Atlas atlas = new Atlas(); - atlas.Width = _AtlasSize; - atlas.Height = _AtlasSize; - List leftovers = LayoutAtlas(textures, atlas); - if (leftovers.Count == 0) - { - // we reached the last atlas. Check if this last atlas could have been twice smaller - while (leftovers.Count == 0) - { - atlas.Width /= 2; - atlas.Height /= 2; - leftovers = LayoutAtlas(textures, atlas); - } - // we need to go 1 step larger as we found the first size that is to small - atlas.Width *= 2; - atlas.Height *= 2; - leftovers = LayoutAtlas(textures, atlas); - } - Atlasses.Add(atlas); - textures = leftovers; - } - } - - public void SaveAtlasses(string _Destination) - { - int atlasCount = 0; - string prefix = _Destination.Replace(Path.GetExtension(_Destination), ""); - string descFile = _Destination; - StreamWriter tw = new StreamWriter(_Destination); - tw.WriteLine("source_tex, atlas_tex, x, y, width, height"); - foreach (Atlas atlas in Atlasses) - { - string atlasName = String.Format(prefix + "{0:000}" + ".png", atlasCount); - //1: Save images - Image img = CreateAtlasImage(atlas); - //DPI fix start - Bitmap ResolutionFix = new Bitmap(img); - ResolutionFix.SetResolution(96.0F, 96.0F); - Image img2 = ResolutionFix; - //DPI fix end - img2.Save(atlasName, System.Drawing.Imaging.ImageFormat.Png); - //2: save description in file - foreach (Node n in atlas.Nodes) - { - if (n.Texture != null) - { - tw.Write(n.Texture.Source + ", "); - tw.Write(atlasName + ", "); - tw.Write((n.Bounds.X).ToString() + ", "); - tw.Write((n.Bounds.Y).ToString() + ", "); - tw.Write((n.Bounds.Width).ToString() + ", "); - tw.WriteLine((n.Bounds.Height).ToString()); - } - } - ++atlasCount; - } - tw.Close(); - tw = new StreamWriter(prefix + ".log"); - tw.WriteLine("--- LOG -------------------------------------------"); - tw.WriteLine(Log.ToString()); - tw.WriteLine("--- ERROR -----------------------------------------"); - tw.WriteLine(Error.ToString()); - tw.Close(); - } - - private void ScanForTextures(string _Path, string _Wildcard) - { - DirectoryInfo di = new DirectoryInfo(_Path); - FileInfo[] files = di.GetFiles(_Wildcard, SearchOption.AllDirectories); - foreach (FileInfo fi in files) - { - Image img = Image.FromFile(fi.FullName); - if (img != null) - { - if (img.Width <= AtlasSize && img.Height <= AtlasSize) - { - TextureInfo ti = new TextureInfo(); - - ti.Source = fi.FullName; - ti.Width = img.Width; - ti.Height = img.Height; - - SourceTextures.Add(ti); - - Log.WriteLine("Added " + fi.FullName); - } - else - { - Error.WriteLine(fi.FullName + " is too large to fix in the atlas. Skipping!"); - } - } - } - } - - private void HorizontalSplit(Node _ToSplit, int _Width, int _Height, List _List) - { - Node n1 = new Node(); - n1.Bounds.X = _ToSplit.Bounds.X + _Width + Padding; - n1.Bounds.Y = _ToSplit.Bounds.Y; - n1.Bounds.Width = _ToSplit.Bounds.Width - _Width - Padding; - n1.Bounds.Height = _Height; - n1.SplitType = SplitType.Vertical; - Node n2 = new Node(); - n2.Bounds.X = _ToSplit.Bounds.X; - n2.Bounds.Y = _ToSplit.Bounds.Y + _Height + Padding; - n2.Bounds.Width = _ToSplit.Bounds.Width; - n2.Bounds.Height = _ToSplit.Bounds.Height - _Height - Padding; - n2.SplitType = SplitType.Horizontal; - if (n1.Bounds.Width > 0 && n1.Bounds.Height > 0) - _List.Add(n1); - if (n2.Bounds.Width > 0 && n2.Bounds.Height > 0) - _List.Add(n2); - } - - private void VerticalSplit(Node _ToSplit, int _Width, int _Height, List _List) - { - Node n1 = new Node(); - n1.Bounds.X = _ToSplit.Bounds.X + _Width + Padding; - n1.Bounds.Y = _ToSplit.Bounds.Y; - n1.Bounds.Width = _ToSplit.Bounds.Width - _Width - Padding; - n1.Bounds.Height = _ToSplit.Bounds.Height; - n1.SplitType = SplitType.Vertical; - Node n2 = new Node(); - n2.Bounds.X = _ToSplit.Bounds.X; - n2.Bounds.Y = _ToSplit.Bounds.Y + _Height + Padding; - n2.Bounds.Width = _Width; - n2.Bounds.Height = _ToSplit.Bounds.Height - _Height - Padding; - n2.SplitType = SplitType.Horizontal; - if (n1.Bounds.Width > 0 && n1.Bounds.Height > 0) - _List.Add(n1); - if (n2.Bounds.Width > 0 && n2.Bounds.Height > 0) - _List.Add(n2); - } - - private TextureInfo FindBestFitForNode(Node _Node, List _Textures) - { - TextureInfo bestFit = null; - float nodeArea = _Node.Bounds.Width * _Node.Bounds.Height; - float maxCriteria = 0.0f; - foreach (TextureInfo ti in _Textures) - { - switch (FitHeuristic) - { - // Max of Width and Height ratios - case BestFitHeuristic.MaxOneAxis: - if (ti.Width <= _Node.Bounds.Width && ti.Height <= _Node.Bounds.Height) - { - float wRatio = (float)ti.Width / (float)_Node.Bounds.Width; - float hRatio = (float)ti.Height / (float)_Node.Bounds.Height; - float ratio = wRatio > hRatio ? wRatio : hRatio; - if (ratio > maxCriteria) - { - maxCriteria = ratio; - bestFit = ti; - } - } - break; - // Maximize Area coverage - case BestFitHeuristic.Area: - if (ti.Width <= _Node.Bounds.Width && ti.Height <= _Node.Bounds.Height) - { - float textureArea = ti.Width * ti.Height; - float coverage = textureArea / nodeArea; - if (coverage > maxCriteria) - { - maxCriteria = coverage; - bestFit = ti; - } - } - break; - } - } - return bestFit; - } - - private List LayoutAtlas(List _Textures, Atlas _Atlas) - { - List freeList = new List(); - List textures = new List(); - _Atlas.Nodes = new List(); - textures = _Textures.ToList(); - Node root = new Node(); - root.Bounds.Size = new Size(_Atlas.Width, _Atlas.Height); - root.SplitType = SplitType.Horizontal; - freeList.Add(root); - while (freeList.Count > 0 && textures.Count > 0) - { - Node node = freeList[0]; - freeList.RemoveAt(0); - TextureInfo bestFit = FindBestFitForNode(node, textures); - if (bestFit != null) - { - if (node.SplitType == SplitType.Horizontal) - { - HorizontalSplit(node, bestFit.Width, bestFit.Height, freeList); - } - else - { - VerticalSplit(node, bestFit.Width, bestFit.Height, freeList); - } - node.Texture = bestFit; - node.Bounds.Width = bestFit.Width; - node.Bounds.Height = bestFit.Height; - textures.Remove(bestFit); - } - _Atlas.Nodes.Add(node); - } - return textures; - } - - private Image CreateAtlasImage(Atlas _Atlas) - { - Image img = new Bitmap(_Atlas.Width, _Atlas.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb); - Graphics g = Graphics.FromImage(img); - if (DebugMode) - { - g.FillRectangle(Brushes.Green, new Rectangle(0, 0, _Atlas.Width, _Atlas.Height)); - } - foreach (Node n in _Atlas.Nodes) - { - if (n.Texture != null) - { - Image sourceImg = Image.FromFile(n.Texture.Source); - g.DrawImage(sourceImg, n.Bounds); - if (DebugMode) - { - string label = Path.GetFileNameWithoutExtension(n.Texture.Source); - SizeF labelBox = g.MeasureString(label, SystemFonts.MenuFont, new SizeF(n.Bounds.Size)); - RectangleF rectBounds = new Rectangle(n.Bounds.Location, new Size((int)labelBox.Width, (int)labelBox.Height)); - g.FillRectangle(Brushes.Black, rectBounds); - g.DrawString(label, SystemFonts.MenuFont, Brushes.White, rectBounds); - } - } - else - { - g.FillRectangle(Brushes.DarkMagenta, n.Bounds); - if (DebugMode) - { - string label = n.Bounds.Width.ToString() + "x" + n.Bounds.Height.ToString(); - SizeF labelBox = g.MeasureString(label, SystemFonts.MenuFont, new SizeF(n.Bounds.Size)); - RectangleF rectBounds = new Rectangle(n.Bounds.Location, new Size((int)labelBox.Width, (int)labelBox.Height)); - g.FillRectangle(Brushes.Black, rectBounds); - g.DrawString(label, SystemFonts.MenuFont, Brushes.White, rectBounds); - } - } - } - return img; - } -} +//Texture packer by Samuel Roy +// Uses code from https://github.com/mfascia/TexturePacker + +using System; +using System.IO; +using System.Drawing; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UndertaleModLib.Util; + +EnsureDataLoaded(); + +string importFolder = PromptChooseDirectory(); +if (importFolder == null) + throw new ScriptException("The import folder was not set."); + +System.IO.Directory.CreateDirectory("Packager"); +string sourcePath = importFolder; +string searchPattern = "*.png"; +string outName = "Packager/atlas.txt"; +int textureSize = 2048; +int border = 2; +bool debug = false; +Packer packer = new Packer(); +packer.Process(sourcePath, searchPattern, textureSize, border, debug); +packer.SaveAtlasses(outName); + +int lastTextPage = Data.EmbeddedTextures.Count - 1; +int lastTextPageItem = Data.TexturePageItems.Count - 1; + +string prefix = outName.Replace(Path.GetExtension(outName), ""); +int atlasCount = 0; +foreach (Atlas atlas in packer.Atlasses) +{ + string atlasName = String.Format(prefix + "{0:000}" + ".png", atlasCount); + Bitmap atlasBitmap = new Bitmap(atlasName); + UndertaleEmbeddedTexture texture = new UndertaleEmbeddedTexture(); + texture.Name = new UndertaleString("Texture " + ++lastTextPage); + texture.TextureData.TextureBlob = File.ReadAllBytes(atlasName); + Data.EmbeddedTextures.Add(texture); + foreach (Node n in atlas.Nodes) + { + if (n.Texture != null) + { + UndertaleTexturePageItem texturePageItem = new UndertaleTexturePageItem(); + texturePageItem.Name = new UndertaleString("PageItem " + ++lastTextPageItem); + texturePageItem.SourceX = (ushort)n.Bounds.X; + texturePageItem.SourceY = (ushort)n.Bounds.Y; + texturePageItem.SourceWidth = (ushort)n.Bounds.Width; + texturePageItem.SourceHeight = (ushort)n.Bounds.Height; + texturePageItem.TargetX = 0; + texturePageItem.TargetY = 0; + texturePageItem.TargetWidth = (ushort)n.Bounds.Width; + texturePageItem.TargetHeight = (ushort)n.Bounds.Height; + texturePageItem.BoundingWidth = (ushort)n.Bounds.Width; + texturePageItem.BoundingHeight = (ushort)n.Bounds.Height; + texturePageItem.TexturePage = texture; + Data.TexturePageItems.Add(texturePageItem); + string stripped = Path.GetFileNameWithoutExtension(n.Texture.Source); + int lastUnderscore = stripped.LastIndexOf('_'); + string spriteName = stripped.Substring(0, lastUnderscore); + + UndertaleFont font = null; + font = Data.Fonts.ByName(stripped); + + if(font == null) + { + UndertaleString fontUTString = Data.Strings.MakeString(stripped); + UndertaleFont newFont = new UndertaleFont(); + newFont.Name = fontUTString; + + fontUpdate(newFont); + newFont.Texture = texturePageItem; + Data.Fonts.Add(newFont); + continue; + } + + fontUpdate(font); + font.Texture = texturePageItem; + UndertaleSprite.TextureEntry texentry = new UndertaleSprite.TextureEntry(); + texentry.Texture = texturePageItem; + } + } + atlasCount++; +} + + + +HideProgressBar(); +ScriptMessage("Import Complete!"); + +public void fontUpdate(UndertaleFont newFont) +{ + using(StreamReader reader = new StreamReader(sourcePath + "glyphs_" + newFont.Name.Content + ".csv")) + { + newFont.Glyphs.Clear(); + string line; + int head = 0; + while((line = reader.ReadLine()) != null) + { + string[] s = line.Split(';'); + + if (head == 1) + { + newFont.RangeStart = UInt16.Parse(s[0]); + head++; + } + + if (head == 0) + { + String namae = s[0].Replace("\"", ""); + newFont.DisplayName = Data.Strings.MakeString(namae); + newFont.EmSize = UInt16.Parse(s[1]); + newFont.Bold = Boolean.Parse(s[2]); + newFont.Italic = Boolean.Parse(s[3]); + newFont.Charset = Byte.Parse(s[4]); + newFont.AntiAliasing = Byte.Parse(s[5]); + newFont.ScaleX = UInt16.Parse(s[6]); + newFont.ScaleY = UInt16.Parse(s[7]); + head++; + } + + if (head > 1) + { + newFont.Glyphs.Add(new UndertaleFont.Glyph() + { + Character = UInt16.Parse(s[0]), + SourceX = UInt16.Parse(s[1]), + SourceY = UInt16.Parse(s[2]), + SourceWidth = UInt16.Parse(s[3]), + SourceHeight = UInt16.Parse(s[4]), + Shift = Int16.Parse(s[5]), + Offset = Int16.Parse(s[6]), + }); + newFont.RangeEnd = UInt32.Parse(s[0]); + } + } + + } +} + +public class TextureInfo +{ + public string Source; + public int Width; + public int Height; +} + +public enum SplitType +{ + Horizontal, + Vertical, +} + +public enum BestFitHeuristic +{ + Area, + MaxOneAxis, +} + +public class Node +{ + public Rectangle Bounds; + public TextureInfo Texture; + public SplitType SplitType; +} + +public class Atlas +{ + public int Width; + public int Height; + public List Nodes; +} + +public class Packer +{ + public List SourceTextures; + public StringWriter Log; + public StringWriter Error; + public int Padding; + public int AtlasSize; + public bool DebugMode; + public BestFitHeuristic FitHeuristic; + public List Atlasses; + + public Packer() + { + SourceTextures = new List(); + Log = new StringWriter(); + Error = new StringWriter(); + } + + public void Process(string _SourceDir, string _Pattern, int _AtlasSize, int _Padding, bool _DebugMode) + { + Padding = _Padding; + AtlasSize = _AtlasSize; + DebugMode = _DebugMode; + //1: scan for all the textures we need to pack + ScanForTextures(_SourceDir, _Pattern); + List textures = new List(); + textures = SourceTextures.ToList(); + //2: generate as many atlasses as needed (with the latest one as small as possible) + Atlasses = new List(); + while (textures.Count > 0) + { + Atlas atlas = new Atlas(); + atlas.Width = _AtlasSize; + atlas.Height = _AtlasSize; + List leftovers = LayoutAtlas(textures, atlas); + if (leftovers.Count == 0) + { + // we reached the last atlas. Check if this last atlas could have been twice smaller + while (leftovers.Count == 0) + { + atlas.Width /= 2; + atlas.Height /= 2; + leftovers = LayoutAtlas(textures, atlas); + } + // we need to go 1 step larger as we found the first size that is to small + atlas.Width *= 2; + atlas.Height *= 2; + leftovers = LayoutAtlas(textures, atlas); + } + Atlasses.Add(atlas); + textures = leftovers; + } + } + + public void SaveAtlasses(string _Destination) + { + int atlasCount = 0; + string prefix = _Destination.Replace(Path.GetExtension(_Destination), ""); + string descFile = _Destination; + StreamWriter tw = new StreamWriter(_Destination); + tw.WriteLine("source_tex, atlas_tex, x, y, width, height"); + foreach (Atlas atlas in Atlasses) + { + string atlasName = String.Format(prefix + "{0:000}" + ".png", atlasCount); + //1: Save images + Image img = CreateAtlasImage(atlas); + //DPI fix start + Bitmap ResolutionFix = new Bitmap(img); + ResolutionFix.SetResolution(96.0F, 96.0F); + Image img2 = ResolutionFix; + //DPI fix end + img2.Save(atlasName, System.Drawing.Imaging.ImageFormat.Png); + //2: save description in file + foreach (Node n in atlas.Nodes) + { + if (n.Texture != null) + { + tw.Write(n.Texture.Source + ", "); + tw.Write(atlasName + ", "); + tw.Write((n.Bounds.X).ToString() + ", "); + tw.Write((n.Bounds.Y).ToString() + ", "); + tw.Write((n.Bounds.Width).ToString() + ", "); + tw.WriteLine((n.Bounds.Height).ToString()); + } + } + ++atlasCount; + } + tw.Close(); + tw = new StreamWriter(prefix + ".log"); + tw.WriteLine("--- LOG -------------------------------------------"); + tw.WriteLine(Log.ToString()); + tw.WriteLine("--- ERROR -----------------------------------------"); + tw.WriteLine(Error.ToString()); + tw.Close(); + } + + private void ScanForTextures(string _Path, string _Wildcard) + { + DirectoryInfo di = new DirectoryInfo(_Path); + FileInfo[] files = di.GetFiles(_Wildcard, SearchOption.AllDirectories); + foreach (FileInfo fi in files) + { + Image img = Image.FromFile(fi.FullName); + if (img != null) + { + if (img.Width <= AtlasSize && img.Height <= AtlasSize) + { + TextureInfo ti = new TextureInfo(); + + ti.Source = fi.FullName; + ti.Width = img.Width; + ti.Height = img.Height; + + SourceTextures.Add(ti); + + Log.WriteLine("Added " + fi.FullName); + } + else + { + Error.WriteLine(fi.FullName + " is too large to fix in the atlas. Skipping!"); + } + } + } + } + + private void HorizontalSplit(Node _ToSplit, int _Width, int _Height, List _List) + { + Node n1 = new Node(); + n1.Bounds.X = _ToSplit.Bounds.X + _Width + Padding; + n1.Bounds.Y = _ToSplit.Bounds.Y; + n1.Bounds.Width = _ToSplit.Bounds.Width - _Width - Padding; + n1.Bounds.Height = _Height; + n1.SplitType = SplitType.Vertical; + Node n2 = new Node(); + n2.Bounds.X = _ToSplit.Bounds.X; + n2.Bounds.Y = _ToSplit.Bounds.Y + _Height + Padding; + n2.Bounds.Width = _ToSplit.Bounds.Width; + n2.Bounds.Height = _ToSplit.Bounds.Height - _Height - Padding; + n2.SplitType = SplitType.Horizontal; + if (n1.Bounds.Width > 0 && n1.Bounds.Height > 0) + _List.Add(n1); + if (n2.Bounds.Width > 0 && n2.Bounds.Height > 0) + _List.Add(n2); + } + + private void VerticalSplit(Node _ToSplit, int _Width, int _Height, List _List) + { + Node n1 = new Node(); + n1.Bounds.X = _ToSplit.Bounds.X + _Width + Padding; + n1.Bounds.Y = _ToSplit.Bounds.Y; + n1.Bounds.Width = _ToSplit.Bounds.Width - _Width - Padding; + n1.Bounds.Height = _ToSplit.Bounds.Height; + n1.SplitType = SplitType.Vertical; + Node n2 = new Node(); + n2.Bounds.X = _ToSplit.Bounds.X; + n2.Bounds.Y = _ToSplit.Bounds.Y + _Height + Padding; + n2.Bounds.Width = _Width; + n2.Bounds.Height = _ToSplit.Bounds.Height - _Height - Padding; + n2.SplitType = SplitType.Horizontal; + if (n1.Bounds.Width > 0 && n1.Bounds.Height > 0) + _List.Add(n1); + if (n2.Bounds.Width > 0 && n2.Bounds.Height > 0) + _List.Add(n2); + } + + private TextureInfo FindBestFitForNode(Node _Node, List _Textures) + { + TextureInfo bestFit = null; + float nodeArea = _Node.Bounds.Width * _Node.Bounds.Height; + float maxCriteria = 0.0f; + foreach (TextureInfo ti in _Textures) + { + switch (FitHeuristic) + { + // Max of Width and Height ratios + case BestFitHeuristic.MaxOneAxis: + if (ti.Width <= _Node.Bounds.Width && ti.Height <= _Node.Bounds.Height) + { + float wRatio = (float)ti.Width / (float)_Node.Bounds.Width; + float hRatio = (float)ti.Height / (float)_Node.Bounds.Height; + float ratio = wRatio > hRatio ? wRatio : hRatio; + if (ratio > maxCriteria) + { + maxCriteria = ratio; + bestFit = ti; + } + } + break; + // Maximize Area coverage + case BestFitHeuristic.Area: + if (ti.Width <= _Node.Bounds.Width && ti.Height <= _Node.Bounds.Height) + { + float textureArea = ti.Width * ti.Height; + float coverage = textureArea / nodeArea; + if (coverage > maxCriteria) + { + maxCriteria = coverage; + bestFit = ti; + } + } + break; + } + } + return bestFit; + } + + private List LayoutAtlas(List _Textures, Atlas _Atlas) + { + List freeList = new List(); + List textures = new List(); + _Atlas.Nodes = new List(); + textures = _Textures.ToList(); + Node root = new Node(); + root.Bounds.Size = new Size(_Atlas.Width, _Atlas.Height); + root.SplitType = SplitType.Horizontal; + freeList.Add(root); + while (freeList.Count > 0 && textures.Count > 0) + { + Node node = freeList[0]; + freeList.RemoveAt(0); + TextureInfo bestFit = FindBestFitForNode(node, textures); + if (bestFit != null) + { + if (node.SplitType == SplitType.Horizontal) + { + HorizontalSplit(node, bestFit.Width, bestFit.Height, freeList); + } + else + { + VerticalSplit(node, bestFit.Width, bestFit.Height, freeList); + } + node.Texture = bestFit; + node.Bounds.Width = bestFit.Width; + node.Bounds.Height = bestFit.Height; + textures.Remove(bestFit); + } + _Atlas.Nodes.Add(node); + } + return textures; + } + + private Image CreateAtlasImage(Atlas _Atlas) + { + Image img = new Bitmap(_Atlas.Width, _Atlas.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb); + Graphics g = Graphics.FromImage(img); + if (DebugMode) + { + g.FillRectangle(Brushes.Green, new Rectangle(0, 0, _Atlas.Width, _Atlas.Height)); + } + foreach (Node n in _Atlas.Nodes) + { + if (n.Texture != null) + { + Image sourceImg = Image.FromFile(n.Texture.Source); + g.DrawImage(sourceImg, n.Bounds); + if (DebugMode) + { + string label = Path.GetFileNameWithoutExtension(n.Texture.Source); + SizeF labelBox = g.MeasureString(label, SystemFonts.MenuFont, new SizeF(n.Bounds.Size)); + RectangleF rectBounds = new Rectangle(n.Bounds.Location, new Size((int)labelBox.Width, (int)labelBox.Height)); + g.FillRectangle(Brushes.Black, rectBounds); + g.DrawString(label, SystemFonts.MenuFont, Brushes.White, rectBounds); + } + } + else + { + g.FillRectangle(Brushes.DarkMagenta, n.Bounds); + if (DebugMode) + { + string label = n.Bounds.Width.ToString() + "x" + n.Bounds.Height.ToString(); + SizeF labelBox = g.MeasureString(label, SystemFonts.MenuFont, new SizeF(n.Bounds.Size)); + RectangleF rectBounds = new Rectangle(n.Bounds.Location, new Size((int)labelBox.Width, (int)labelBox.Height)); + g.FillRectangle(Brushes.Black, rectBounds); + g.DrawString(label, SystemFonts.MenuFont, Brushes.White, rectBounds); + } + } + } + return img; + } +} diff --git a/UndertaleModTool/Repackers/ImportGML.csx b/UndertaleModTool/Repackers/ImportGML.csx index 5a30cb17b..9fcd255bc 100644 --- a/UndertaleModTool/Repackers/ImportGML.csx +++ b/UndertaleModTool/Repackers/ImportGML.csx @@ -1,45 +1,45 @@ -// Script by Jockeholm based off of a script by Kneesnap. -// Major help and edited by Samuel Roy - -using System; -using System.IO; -using System.Threading.Tasks; -using System.Linq; -using UndertaleModLib.Util; - -EnsureDataLoaded(); - -// Check code directory. -string importFolder = PromptChooseDirectory("Import From Where"); -if (importFolder == null) - throw new ScriptException("The import folder was not set."); - -string[] dirFiles = Directory.GetFiles(importFolder); -if (dirFiles.Length == 0) - throw new ScriptException("The selected folder is empty."); -else if (!dirFiles.Any(x => x.EndsWith(".gml"))) - throw new ScriptException("The selected folder doesn't contain any GML file."); - -// Ask whether they want to link code. If no, will only generate code entry. -// If yes, will try to add code to objects and scripts depending upon its name -bool doParse = ScriptQuestion("Do you want to automatically attempt to link imported code?"); - -bool stopOnError = ScriptQuestion("Stop importing on error?"); - -SetProgressBar(null, "Files", 0, dirFiles.Length); -StartUpdater(); - -SyncBinding("Strings, Code, CodeLocals, Scripts, GlobalInitScripts, GameObjects, Functions, Variables", true); -await Task.Run(() => { - foreach (string file in dirFiles) - { - IncProgress(); - - ImportGMLFile(file, doParse, false, stopOnError); - } -}); -SyncBinding(false); - -await StopUpdater(); -HideProgressBar(); -ScriptMessage("All files successfully imported."); +// Script by Jockeholm based off of a script by Kneesnap. +// Major help and edited by Samuel Roy + +using System; +using System.IO; +using System.Threading.Tasks; +using System.Linq; +using UndertaleModLib.Util; + +EnsureDataLoaded(); + +// Check code directory. +string importFolder = PromptChooseDirectory(); +if (importFolder == null) + throw new ScriptException("The import folder was not set."); + +string[] dirFiles = Directory.GetFiles(importFolder); +if (dirFiles.Length == 0) + throw new ScriptException("The selected folder is empty."); +else if (!dirFiles.Any(x => x.EndsWith(".gml"))) + throw new ScriptException("The selected folder doesn't contain any GML file."); + +// Ask whether they want to link code. If no, will only generate code entry. +// If yes, will try to add code to objects and scripts depending upon its name +bool doParse = ScriptQuestion("Do you want to automatically attempt to link imported code?"); + +bool stopOnError = ScriptQuestion("Stop importing on error?"); + +SetProgressBar(null, "Files", 0, dirFiles.Length); +StartProgressBarUpdater(); + +SyncBinding("Strings, Code, CodeLocals, Scripts, GlobalInitScripts, GameObjects, Functions, Variables", true); +await Task.Run(() => { + foreach (string file in dirFiles) + { + IncrementProgress(); + + ImportGMLFile(file, doParse, false, stopOnError); + } +}); +DisableAllSyncBindings(); + +await StopProgressBarUpdater(); +HideProgressBar(); +ScriptMessage("All files successfully imported."); diff --git a/UndertaleModTool/Repackers/ImportGML_2_3.csx b/UndertaleModTool/Repackers/ImportGML_2_3.csx index 3df02228d..3457687e3 100644 --- a/UndertaleModTool/Repackers/ImportGML_2_3.csx +++ b/UndertaleModTool/Repackers/ImportGML_2_3.csx @@ -1,265 +1,265 @@ -// Script by Jockeholm based off of a script by Kneesnap. -// Major help and edited by Samuel Roy - -using System; -using System.IO; -using System.Threading.Tasks; -using UndertaleModLib.Util; - -EnsureDataLoaded(); - -bool is2_3 = false; -if (!((Data.GMS2_3 == false) && (Data.GMS2_3_1 == false) && (Data.GMS2_3_2 == false))) -{ - is2_3 = true; - ScriptMessage("This script is for GMS 2.3 games code from \"ExportAllCode2_3.csx\", because some code names get so long that Windows cannot write them adequately."); -} -else -{ - ScriptError("Use the regular ImportGML please!", "Incompatible"); -} - -enum EventTypes -{ - Create, - Destroy, - Alarm, - Step, - Collision, - Keyboard, - Mouse, - Other, - Draw, - KeyPress, - KeyRelease, - Gesture, - Asynchronous, - PreCreate -} - -// Check code directory. -string importFolder = PromptChooseDirectory("Import From Where"); -if (importFolder == null) - throw new ScriptException("The import folder was not set."); - -List CodeList = new List(); - -if (File.Exists(importFolder + "/LookUpTable.txt")) -{ - int counter = 0; - string line; - System.IO.StreamReader file = new System.IO.StreamReader(importFolder + "/" + "LookUpTable.txt"); - while((line = file.ReadLine()) != null) - { - if (counter > 0) - CodeList.Add(line); - counter++; - } - file.Close(); -} -else -{ - ScriptError("No LookUpTable.txt!", "Error"); - return; -} - -// Ask whether they want to link code. If no, will only generate code entry. -// If yes, will try to add code to objects and scripts depending upon its name -bool doParse = ScriptQuestion("Do you want to automatically attempt to link imported code?"); - -string[] dirFiles = Directory.GetFiles(importFolder); -bool skipGlobalScripts = true; -bool skipGlobalScriptsPrompted = false; - -SetProgressBar(null, "Files", 0, dirFiles.Length); -StartUpdater(); - -SyncBinding("Strings, Code, CodeLocals, Scripts, GlobalInitScripts, GameObjects, Functions, Variables", true); -await Task.Run(() => { - foreach (string file in dirFiles) - { - IncProgress(); - - string fileName = Path.GetFileName(file); - if (!(fileName.EndsWith(".gml"))) - continue; - fileName = Path.GetFileNameWithoutExtension(file); - int number; - bool success = Int32.TryParse(fileName, out number); - string codeName; - if (success) - { - codeName = CodeList[number]; - fileName = codeName + ".gml"; - } - else - { - ScriptError("GML file not in range of look up table!", "Error"); - return; - } - if (fileName.EndsWith("PreCreate_0.gml") && (Data.GeneralInfo.Major < 2)) - continue; // Restarts loop if file is not a valid code asset. - string gmlCode = File.ReadAllText(file); - if (codeName.Substring(0, 17).Equals("gml_GlobalScript_") && is2_3 && (!(skipGlobalScriptsPrompted))) - { - skipGlobalScriptsPrompted = true; - skipGlobalScripts = ScriptQuestion("Skip global scripts parsing?"); - } - if (codeName.Substring(0, 17).Equals("gml_GlobalScript_") && is2_3 && ((skipGlobalScriptsPrompted))) - { - if (skipGlobalScripts) - continue; - } - UndertaleCode code = Data.Code.ByName(codeName); - if (code == null) // Should keep from adding duplicate scripts; haven't tested - { - code = new UndertaleCode(); - code.Name = Data.Strings.MakeString(codeName); - Data.Code.Add(code); - } - if ((Data?.GeneralInfo.BytecodeVersion > 14) && (Data.CodeLocals.ByName(codeName) == null)) - { - UndertaleCodeLocals locals = new UndertaleCodeLocals(); - locals.Name = code.Name; - - UndertaleCodeLocals.LocalVar argsLocal = new UndertaleCodeLocals.LocalVar(); - argsLocal.Name = Data.Strings.MakeString("arguments"); - argsLocal.Index = 0; - - locals.Locals.Add(argsLocal); - - code.LocalsCount = 1; - code.GenerateLocalVarDefinitions(code.FindReferencedLocalVars(), locals); // Dunno if we actually need this line, but it seems to work? - Data.CodeLocals.Add(locals); - } - if (doParse) - { - // This portion links code. - if (codeName.Substring(0, 10).Equals("gml_Script")) - { - // Add code to scripts section. - if (Data.Scripts.ByName(codeName.Substring(11)) == null) - { - UndertaleScript scr = new UndertaleScript(); - scr.Name = Data.Strings.MakeString(codeName.Substring(11)); - scr.Code = code; - Data.Scripts.Add(scr); - } - else - { - UndertaleScript scr = Data.Scripts.ByName(codeName.Substring(11)); - scr.Code = code; - } - } - else if (codeName.Substring(0, 10).Equals("gml_Object")) - { - // Add code to object methods. - string afterPrefix = codeName.Substring(11); - // Dumb substring stuff, don't mess with this. - int underCount = 0; - string methodNumberStr = "", methodName = "", objName = ""; - for (int i = afterPrefix.Length - 1; i >= 0; i--) - { - if (afterPrefix[i] == '_') - { - underCount++; - if (underCount == 1) - { - methodNumberStr = afterPrefix.Substring(i + 1); - } - else if (underCount == 2) - { - objName = afterPrefix.Substring(0, i); - methodName = afterPrefix.Substring(i + 1, afterPrefix.Length - objName.Length - methodNumberStr.Length - 2); - break; - } - } - } - - int methodNumber = Int32.Parse(methodNumberStr); - UndertaleGameObject obj = Data.GameObjects.ByName(objName); - if (obj == null) - { - bool doNewObj = ScriptQuestion("Object " + objName + " was not found.\nAdd new object called " + objName + "?"); - if (doNewObj) - { - UndertaleGameObject gameObj = new UndertaleGameObject(); - gameObj.Name = Data.Strings.MakeString(objName); - Data.GameObjects.Add(gameObj); - } - else - { - try - { - code.ReplaceGML(gmlCode, Data); - } - catch (Exception ex) - { - string errorMSG = "Error in " + codeName + ":\r\n" + ex.ToString() + "\r\nAborted"; - ScriptMessage(errorMSG); - SetUMTConsoleText(errorMSG); - SetFinishedMessage(false); - return; - } - continue; - } - } - - obj = Data.GameObjects.ByName(objName); - int eventIdx = (int)Enum.Parse(typeof(EventTypes), methodName); - - bool duplicate = false; - try - { - foreach (UndertaleGameObject.Event evnt in obj.Events[eventIdx]) - { - foreach (UndertaleGameObject.EventAction action in evnt.Actions) - { - if (action.CodeId.Name.Content == codeName) - duplicate = true; - } - } - } - catch - { - //something went wrong, but probably because it's trying to check something non-existent - //we're gonna make it so - //keep going - } - if (duplicate == false) - { - UndertalePointerList eventList = obj.Events[eventIdx]; - UndertaleGameObject.EventAction action = new UndertaleGameObject.EventAction(); - UndertaleGameObject.Event evnt = new UndertaleGameObject.Event(); - - action.ActionName = code.Name; - action.CodeId = code; - evnt.EventSubtype = (uint)methodNumber; - evnt.Actions.Add(action); - eventList.Add(evnt); - } - } - // Code which does not match these criteria cannot link, but are still added to the code section. - } - else - { - try - { - code.ReplaceGML(gmlCode, Data); - } - catch (Exception ex) - { - string errorMSG = "Error in " + codeName + ":\r\n" + ex.ToString() + "\r\nAborted"; - ScriptMessage(errorMSG); - SetUMTConsoleText(errorMSG); - SetFinishedMessage(false); - return; - } - } - } -}); -SyncBinding(false); - -await StopUpdater(); -HideProgressBar(); +// Script by Jockeholm based off of a script by Kneesnap. +// Major help and edited by Samuel Roy + +using System; +using System.IO; +using System.Threading.Tasks; +using UndertaleModLib.Util; + +EnsureDataLoaded(); + +bool is2_3 = false; +if (!((Data.GMS2_3 == false) && (Data.GMS2_3_1 == false) && (Data.GMS2_3_2 == false))) +{ + is2_3 = true; + ScriptMessage("This script is for GMS 2.3 games code from \"ExportAllCode2_3.csx\", because some code names get so long that Windows cannot write them adequately."); +} +else +{ + ScriptError("Use the regular ImportGML please!", "Incompatible"); +} + +enum EventTypes +{ + Create, + Destroy, + Alarm, + Step, + Collision, + Keyboard, + Mouse, + Other, + Draw, + KeyPress, + KeyRelease, + Gesture, + Asynchronous, + PreCreate +} + +// Check code directory. +string importFolder = PromptChooseDirectory(); +if (importFolder == null) + throw new ScriptException("The import folder was not set."); + +List CodeList = new List(); + +if (File.Exists(importFolder + "/LookUpTable.txt")) +{ + int counter = 0; + string line; + System.IO.StreamReader file = new System.IO.StreamReader(importFolder + "/" + "LookUpTable.txt"); + while((line = file.ReadLine()) != null) + { + if (counter > 0) + CodeList.Add(line); + counter++; + } + file.Close(); +} +else +{ + ScriptError("No LookUpTable.txt!", "Error"); + return; +} + +// Ask whether they want to link code. If no, will only generate code entry. +// If yes, will try to add code to objects and scripts depending upon its name +bool doParse = ScriptQuestion("Do you want to automatically attempt to link imported code?"); + +string[] dirFiles = Directory.GetFiles(importFolder); +bool skipGlobalScripts = true; +bool skipGlobalScriptsPrompted = false; + +SetProgressBar(null, "Files", 0, dirFiles.Length); +StartProgressBarUpdater(); + +SyncBinding("Strings, Code, CodeLocals, Scripts, GlobalInitScripts, GameObjects, Functions, Variables", true); +await Task.Run(() => { + foreach (string file in dirFiles) + { + IncrementProgress(); + + string fileName = Path.GetFileName(file); + if (!(fileName.EndsWith(".gml"))) + continue; + fileName = Path.GetFileNameWithoutExtension(file); + int number; + bool success = Int32.TryParse(fileName, out number); + string codeName; + if (success) + { + codeName = CodeList[number]; + fileName = codeName + ".gml"; + } + else + { + ScriptError("GML file not in range of look up table!", "Error"); + return; + } + if (fileName.EndsWith("PreCreate_0.gml") && (Data.GeneralInfo.Major < 2)) + continue; // Restarts loop if file is not a valid code asset. + string gmlCode = File.ReadAllText(file); + if (codeName.Substring(0, 17).Equals("gml_GlobalScript_") && is2_3 && (!(skipGlobalScriptsPrompted))) + { + skipGlobalScriptsPrompted = true; + skipGlobalScripts = ScriptQuestion("Skip global scripts parsing?"); + } + if (codeName.Substring(0, 17).Equals("gml_GlobalScript_") && is2_3 && ((skipGlobalScriptsPrompted))) + { + if (skipGlobalScripts) + continue; + } + UndertaleCode code = Data.Code.ByName(codeName); + if (code == null) // Should keep from adding duplicate scripts; haven't tested + { + code = new UndertaleCode(); + code.Name = Data.Strings.MakeString(codeName); + Data.Code.Add(code); + } + if ((Data?.GeneralInfo.BytecodeVersion > 14) && (Data.CodeLocals.ByName(codeName) == null)) + { + UndertaleCodeLocals locals = new UndertaleCodeLocals(); + locals.Name = code.Name; + + UndertaleCodeLocals.LocalVar argsLocal = new UndertaleCodeLocals.LocalVar(); + argsLocal.Name = Data.Strings.MakeString("arguments"); + argsLocal.Index = 0; + + locals.Locals.Add(argsLocal); + + code.LocalsCount = 1; + code.GenerateLocalVarDefinitions(code.FindReferencedLocalVars(), locals); // Dunno if we actually need this line, but it seems to work? + Data.CodeLocals.Add(locals); + } + if (doParse) + { + // This portion links code. + if (codeName.Substring(0, 10).Equals("gml_Script")) + { + // Add code to scripts section. + if (Data.Scripts.ByName(codeName.Substring(11)) == null) + { + UndertaleScript scr = new UndertaleScript(); + scr.Name = Data.Strings.MakeString(codeName.Substring(11)); + scr.Code = code; + Data.Scripts.Add(scr); + } + else + { + UndertaleScript scr = Data.Scripts.ByName(codeName.Substring(11)); + scr.Code = code; + } + } + else if (codeName.Substring(0, 10).Equals("gml_Object")) + { + // Add code to object methods. + string afterPrefix = codeName.Substring(11); + // Dumb substring stuff, don't mess with this. + int underCount = 0; + string methodNumberStr = "", methodName = "", objName = ""; + for (int i = afterPrefix.Length - 1; i >= 0; i--) + { + if (afterPrefix[i] == '_') + { + underCount++; + if (underCount == 1) + { + methodNumberStr = afterPrefix.Substring(i + 1); + } + else if (underCount == 2) + { + objName = afterPrefix.Substring(0, i); + methodName = afterPrefix.Substring(i + 1, afterPrefix.Length - objName.Length - methodNumberStr.Length - 2); + break; + } + } + } + + int methodNumber = Int32.Parse(methodNumberStr); + UndertaleGameObject obj = Data.GameObjects.ByName(objName); + if (obj == null) + { + bool doNewObj = ScriptQuestion("Object " + objName + " was not found.\nAdd new object called " + objName + "?"); + if (doNewObj) + { + UndertaleGameObject gameObj = new UndertaleGameObject(); + gameObj.Name = Data.Strings.MakeString(objName); + Data.GameObjects.Add(gameObj); + } + else + { + try + { + code.ReplaceGML(gmlCode, Data); + } + catch (Exception ex) + { + string errorMSG = "Error in " + codeName + ":\r\n" + ex.ToString() + "\r\nAborted"; + ScriptMessage(errorMSG); + SetUMTConsoleText(errorMSG); + SetFinishedMessage(false); + return; + } + continue; + } + } + + obj = Data.GameObjects.ByName(objName); + int eventIdx = (int)Enum.Parse(typeof(EventTypes), methodName); + + bool duplicate = false; + try + { + foreach (UndertaleGameObject.Event evnt in obj.Events[eventIdx]) + { + foreach (UndertaleGameObject.EventAction action in evnt.Actions) + { + if (action.CodeId.Name.Content == codeName) + duplicate = true; + } + } + } + catch + { + //something went wrong, but probably because it's trying to check something non-existent + //we're gonna make it so + //keep going + } + if (duplicate == false) + { + UndertalePointerList eventList = obj.Events[eventIdx]; + UndertaleGameObject.EventAction action = new UndertaleGameObject.EventAction(); + UndertaleGameObject.Event evnt = new UndertaleGameObject.Event(); + + action.ActionName = code.Name; + action.CodeId = code; + evnt.EventSubtype = (uint)methodNumber; + evnt.Actions.Add(action); + eventList.Add(evnt); + } + } + // Code which does not match these criteria cannot link, but are still added to the code section. + } + else + { + try + { + code.ReplaceGML(gmlCode, Data); + } + catch (Exception ex) + { + string errorMSG = "Error in " + codeName + ":\r\n" + ex.ToString() + "\r\nAborted"; + ScriptMessage(errorMSG); + SetUMTConsoleText(errorMSG); + SetFinishedMessage(false); + return; + } + } + } +}); +DisableAllSyncBindings(); + +await StopProgressBarUpdater(); +HideProgressBar(); ScriptMessage("All files successfully imported."); \ No newline at end of file diff --git a/UndertaleModTool/Repackers/ImportGraphics.csx b/UndertaleModTool/Repackers/ImportGraphics.csx index c052cba55..087eca90d 100644 --- a/UndertaleModTool/Repackers/ImportGraphics.csx +++ b/UndertaleModTool/Repackers/ImportGraphics.csx @@ -1,620 +1,620 @@ -// Texture packer by Samuel Roy -// Uses code from https://github.com/mfascia/TexturePacker - -using System; -using System.IO; -using System.Drawing; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using UndertaleModLib.Util; - -EnsureDataLoaded(); - -bool importAsSprite = false; - -string importFolder = CheckValidity(); - -string workDirectory = Path.GetDirectoryName(FilePath) + Path.DirectorySeparatorChar; -string packDir = Path.Combine(workDirectory, "Packager"); -Directory.CreateDirectory(packDir); - -string sourcePath = importFolder; -string searchPattern = "*.png"; -string outName = Path.Combine(packDir, "atlas.txt"); -int textureSize = 2048; -int PaddingValue = 2; -bool debug = false; -Packer packer = new Packer(); -packer.Process(sourcePath, searchPattern, textureSize, PaddingValue, debug); -packer.SaveAtlasses(outName); - -int lastTextPage = Data.EmbeddedTextures.Count - 1; -int lastTextPageItem = Data.TexturePageItems.Count - 1; - -// Import everything into UMT -string prefix = outName.Replace(Path.GetExtension(outName), ""); -int atlasCount = 0; -foreach (Atlas atlas in packer.Atlasses) -{ - string atlasName = Path.Combine(packDir, String.Format(prefix + "{0:000}" + ".png", atlasCount)); - Bitmap atlasBitmap = new Bitmap(atlasName); - UndertaleEmbeddedTexture texture = new UndertaleEmbeddedTexture(); - texture.Name = new UndertaleString("Texture " + ++lastTextPage); - texture.TextureData.TextureBlob = File.ReadAllBytes(atlasName); - Data.EmbeddedTextures.Add(texture); - foreach (Node n in atlas.Nodes) - { - if (n.Texture != null) - { - // Initalize values of this texture - UndertaleTexturePageItem texturePageItem = new UndertaleTexturePageItem(); - texturePageItem.Name = new UndertaleString("PageItem " + ++lastTextPageItem); - texturePageItem.SourceX = (ushort)n.Bounds.X; - texturePageItem.SourceY = (ushort)n.Bounds.Y; - texturePageItem.SourceWidth = (ushort)n.Bounds.Width; - texturePageItem.SourceHeight = (ushort)n.Bounds.Height; - texturePageItem.TargetX = 0; - texturePageItem.TargetY = 0; - texturePageItem.TargetWidth = (ushort)n.Bounds.Width; - texturePageItem.TargetHeight = (ushort)n.Bounds.Height; - texturePageItem.BoundingWidth = (ushort)n.Bounds.Width; - texturePageItem.BoundingHeight = (ushort)n.Bounds.Height; - texturePageItem.TexturePage = texture; - - // Add this texture to UMT - Data.TexturePageItems.Add(texturePageItem); - - // String processing - string stripped = Path.GetFileNameWithoutExtension(n.Texture.Source); - - SpriteType spriteType = GetSpriteType(n.Texture.Source); - - if (importAsSprite) - { - if ((spriteType == SpriteType.Unknown) || (spriteType == SpriteType.Font)) - { - spriteType = SpriteType.Sprite; - } - } - - setTextureTargetBounds(texturePageItem, stripped, n); - - - if (spriteType == SpriteType.Background) - { - UndertaleBackground background = Data.Backgrounds.ByName(stripped); - if (background != null) - { - background.Texture = texturePageItem; - } - else - { - // No background found, let's make one - UndertaleString backgroundUTString = Data.Strings.MakeString(stripped); - UndertaleBackground newBackground = new UndertaleBackground(); - newBackground.Name = backgroundUTString; - newBackground.Transparent = false; - newBackground.Preload = false; - newBackground.Texture = texturePageItem; - Data.Backgrounds.Add(newBackground); - } - } - else if (spriteType == SpriteType.Sprite) - { - // Get sprite to add this texture to - string spriteName; - int lastUnderscore, frame; - try - { - lastUnderscore = stripped.LastIndexOf('_'); - spriteName = stripped.Substring(0, lastUnderscore); - frame = Int32.Parse(stripped.Substring(lastUnderscore + 1)); - } - catch (Exception e) - { - ScriptMessage("Error: Image " + stripped + " has an invalid name. Skipping..."); - continue; - } - UndertaleSprite sprite = null; - sprite = Data.Sprites.ByName(spriteName); - - // Create TextureEntry object - UndertaleSprite.TextureEntry texentry = new UndertaleSprite.TextureEntry(); - texentry.Texture = texturePageItem; - - // Set values for new sprites - if (sprite == null) - { - UndertaleString spriteUTString = Data.Strings.MakeString(spriteName); - UndertaleSprite newSprite = new UndertaleSprite(); - newSprite.Name = spriteUTString; - newSprite.Width = (uint)n.Bounds.Width; - newSprite.Height = (uint)n.Bounds.Height; - newSprite.MarginLeft = 0; - newSprite.MarginRight = n.Bounds.Width - 1; - newSprite.MarginTop = 0; - newSprite.MarginBottom = n.Bounds.Height - 1; - newSprite.OriginX = 0; - newSprite.OriginY = 0; - if (frame > 0) - { - for (int i = 0; i < frame; i++) - newSprite.Textures.Add(null); - } - newSprite.CollisionMasks.Add(newSprite.NewMaskEntry()); - Rectangle bmpRect = new Rectangle(n.Bounds.X, n.Bounds.Y, n.Bounds.Width, n.Bounds.Height); - System.Drawing.Imaging.PixelFormat format = atlasBitmap.PixelFormat; - Bitmap cloneBitmap = atlasBitmap.Clone(bmpRect, format); - int width = ((n.Bounds.Width + 7) / 8) * 8; - BitArray maskingBitArray = new BitArray(width * n.Bounds.Height); - for (int y = 0; y < n.Bounds.Height; y++) - { - for (int x = 0; x < n.Bounds.Width; x++) - { - Color pixelColor = cloneBitmap.GetPixel(x, y); - maskingBitArray[y * width + x] = (pixelColor.A > 0); - } - } - BitArray tempBitArray = new BitArray(width * n.Bounds.Height); - for (int i = 0; i < maskingBitArray.Length; i += 8) - { - for (int j = 0; j < 8; j++) - { - tempBitArray[j + i] = maskingBitArray[-(j - 7) + i]; - } - } - int numBytes; - numBytes = maskingBitArray.Length / 8; - byte[] bytes = new byte[numBytes]; - tempBitArray.CopyTo(bytes, 0); - for (int i = 0; i < bytes.Length; i++) - newSprite.CollisionMasks[0].Data[i] = bytes[i]; - newSprite.Textures.Add(texentry); - Data.Sprites.Add(newSprite); - continue; - } - if (frame > sprite.Textures.Count - 1) - { - while (frame > sprite.Textures.Count - 1) - { - sprite.Textures.Add(texentry); - } - continue; - } - sprite.Textures[frame] = texentry; - } - } - } - // Increment atlas - atlasCount++; -} - -HideProgressBar(); -ScriptMessage("Import Complete!"); - -void setTextureTargetBounds(UndertaleTexturePageItem tex, string textureName, Node n) -{ - tex.TargetX = 0; - tex.TargetY = 0; - tex.TargetWidth = (ushort)n.Bounds.Width; - tex.TargetHeight = (ushort)n.Bounds.Height; -} - -public class TextureInfo -{ - public string Source; - public int Width; - public int Height; -} - -public enum SpriteType -{ - Sprite, - Background, - Font, - Unknown -} - - -public enum SplitType -{ - Horizontal, - Vertical, -} - -public enum BestFitHeuristic -{ - Area, - MaxOneAxis, -} - -public class Node -{ - public Rectangle Bounds; - public TextureInfo Texture; - public SplitType SplitType; -} - -public class Atlas -{ - public int Width; - public int Height; - public List Nodes; -} - -public class Packer -{ - public List SourceTextures; - public StringWriter Log; - public StringWriter Error; - public int Padding; - public int AtlasSize; - public bool DebugMode; - public BestFitHeuristic FitHeuristic; - public List Atlasses; - - public Packer() - { - SourceTextures = new List(); - Log = new StringWriter(); - Error = new StringWriter(); - } - - public void Process(string _SourceDir, string _Pattern, int _AtlasSize, int _Padding, bool _DebugMode) - { - Padding = _Padding; - AtlasSize = _AtlasSize; - DebugMode = _DebugMode; - //1: scan for all the textures we need to pack - ScanForTextures(_SourceDir, _Pattern); - List textures = new List(); - textures = SourceTextures.ToList(); - //2: generate as many atlasses as needed (with the latest one as small as possible) - Atlasses = new List(); - while (textures.Count > 0) - { - Atlas atlas = new Atlas(); - atlas.Width = _AtlasSize; - atlas.Height = _AtlasSize; - List leftovers = LayoutAtlas(textures, atlas); - if (leftovers.Count == 0) - { - // we reached the last atlas. Check if this last atlas could have been twice smaller - while (leftovers.Count == 0) - { - atlas.Width /= 2; - atlas.Height /= 2; - leftovers = LayoutAtlas(textures, atlas); - } - // we need to go 1 step larger as we found the first size that is to small - atlas.Width *= 2; - atlas.Height *= 2; - leftovers = LayoutAtlas(textures, atlas); - } - Atlasses.Add(atlas); - textures = leftovers; - } - } - - public void SaveAtlasses(string _Destination) - { - int atlasCount = 0; - string prefix = _Destination.Replace(Path.GetExtension(_Destination), ""); - string descFile = _Destination; - StreamWriter tw = new StreamWriter(_Destination); - tw.WriteLine("source_tex, atlas_tex, x, y, width, height"); - foreach (Atlas atlas in Atlasses) - { - string atlasName = String.Format(prefix + "{0:000}" + ".png", atlasCount); - //1: Save images - Image img = CreateAtlasImage(atlas); - img.Save(atlasName, System.Drawing.Imaging.ImageFormat.Png); - //2: save description in file - foreach (Node n in atlas.Nodes) - { - if (n.Texture != null) - { - tw.Write(n.Texture.Source + ", "); - tw.Write(atlasName + ", "); - tw.Write((n.Bounds.X).ToString() + ", "); - tw.Write((n.Bounds.Y).ToString() + ", "); - tw.Write((n.Bounds.Width).ToString() + ", "); - tw.WriteLine((n.Bounds.Height).ToString()); - } - } - ++atlasCount; - } - tw.Close(); - tw = new StreamWriter(prefix + ".log"); - tw.WriteLine("--- LOG -------------------------------------------"); - tw.WriteLine(Log.ToString()); - tw.WriteLine("--- ERROR -----------------------------------------"); - tw.WriteLine(Error.ToString()); - tw.Close(); - } - - private void ScanForTextures(string _Path, string _Wildcard) - { - DirectoryInfo di = new DirectoryInfo(_Path); - FileInfo[] files = di.GetFiles(_Wildcard, SearchOption.AllDirectories); - foreach (FileInfo fi in files) - { - Image img = Image.FromFile(fi.FullName); - if (img != null) - { - if (img.Width <= AtlasSize && img.Height <= AtlasSize) - { - TextureInfo ti = new TextureInfo(); - - ti.Source = fi.FullName; - ti.Width = img.Width; - ti.Height = img.Height; - - SourceTextures.Add(ti); - - Log.WriteLine("Added " + fi.FullName); - } - else - { - Error.WriteLine(fi.FullName + " is too large to fix in the atlas. Skipping!"); - } - } - } - } - - private void HorizontalSplit(Node _ToSplit, int _Width, int _Height, List _List) - { - Node n1 = new Node(); - n1.Bounds.X = _ToSplit.Bounds.X + _Width + Padding; - n1.Bounds.Y = _ToSplit.Bounds.Y; - n1.Bounds.Width = _ToSplit.Bounds.Width - _Width - Padding; - n1.Bounds.Height = _Height; - n1.SplitType = SplitType.Vertical; - Node n2 = new Node(); - n2.Bounds.X = _ToSplit.Bounds.X; - n2.Bounds.Y = _ToSplit.Bounds.Y + _Height + Padding; - n2.Bounds.Width = _ToSplit.Bounds.Width; - n2.Bounds.Height = _ToSplit.Bounds.Height - _Height - Padding; - n2.SplitType = SplitType.Horizontal; - if (n1.Bounds.Width > 0 && n1.Bounds.Height > 0) - _List.Add(n1); - if (n2.Bounds.Width > 0 && n2.Bounds.Height > 0) - _List.Add(n2); - } - - private void VerticalSplit(Node _ToSplit, int _Width, int _Height, List _List) - { - Node n1 = new Node(); - n1.Bounds.X = _ToSplit.Bounds.X + _Width + Padding; - n1.Bounds.Y = _ToSplit.Bounds.Y; - n1.Bounds.Width = _ToSplit.Bounds.Width - _Width - Padding; - n1.Bounds.Height = _ToSplit.Bounds.Height; - n1.SplitType = SplitType.Vertical; - Node n2 = new Node(); - n2.Bounds.X = _ToSplit.Bounds.X; - n2.Bounds.Y = _ToSplit.Bounds.Y + _Height + Padding; - n2.Bounds.Width = _Width; - n2.Bounds.Height = _ToSplit.Bounds.Height - _Height - Padding; - n2.SplitType = SplitType.Horizontal; - if (n1.Bounds.Width > 0 && n1.Bounds.Height > 0) - _List.Add(n1); - if (n2.Bounds.Width > 0 && n2.Bounds.Height > 0) - _List.Add(n2); - } - - private TextureInfo FindBestFitForNode(Node _Node, List _Textures) - { - TextureInfo bestFit = null; - float nodeArea = _Node.Bounds.Width * _Node.Bounds.Height; - float maxCriteria = 0.0f; - foreach (TextureInfo ti in _Textures) - { - switch (FitHeuristic) - { - // Max of Width and Height ratios - case BestFitHeuristic.MaxOneAxis: - if (ti.Width <= _Node.Bounds.Width && ti.Height <= _Node.Bounds.Height) - { - float wRatio = (float)ti.Width / (float)_Node.Bounds.Width; - float hRatio = (float)ti.Height / (float)_Node.Bounds.Height; - float ratio = wRatio > hRatio ? wRatio : hRatio; - if (ratio > maxCriteria) - { - maxCriteria = ratio; - bestFit = ti; - } - } - break; - // Maximize Area coverage - case BestFitHeuristic.Area: - if (ti.Width <= _Node.Bounds.Width && ti.Height <= _Node.Bounds.Height) - { - float textureArea = ti.Width * ti.Height; - float coverage = textureArea / nodeArea; - if (coverage > maxCriteria) - { - maxCriteria = coverage; - bestFit = ti; - } - } - break; - } - } - return bestFit; - } - - private List LayoutAtlas(List _Textures, Atlas _Atlas) - { - List freeList = new List(); - List textures = new List(); - _Atlas.Nodes = new List(); - textures = _Textures.ToList(); - Node root = new Node(); - root.Bounds.Size = new Size(_Atlas.Width, _Atlas.Height); - root.SplitType = SplitType.Horizontal; - freeList.Add(root); - while (freeList.Count > 0 && textures.Count > 0) - { - Node node = freeList[0]; - freeList.RemoveAt(0); - TextureInfo bestFit = FindBestFitForNode(node, textures); - if (bestFit != null) - { - if (node.SplitType == SplitType.Horizontal) - { - HorizontalSplit(node, bestFit.Width, bestFit.Height, freeList); - } - else - { - VerticalSplit(node, bestFit.Width, bestFit.Height, freeList); - } - node.Texture = bestFit; - node.Bounds.Width = bestFit.Width; - node.Bounds.Height = bestFit.Height; - textures.Remove(bestFit); - } - _Atlas.Nodes.Add(node); - } - return textures; - } - - private Image CreateAtlasImage(Atlas _Atlas) - { - Image img = new Bitmap(_Atlas.Width, _Atlas.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb); - Graphics g = Graphics.FromImage(img); - foreach (Node n in _Atlas.Nodes) - { - if (n.Texture != null) - { - Image sourceImg = Image.FromFile(n.Texture.Source); - g.DrawImage(sourceImg, n.Bounds); - } - } - // DPI FIX START - Bitmap ResolutionFix = new Bitmap(img); - ResolutionFix.SetResolution(96.0F, 96.0F); - Image img2 = ResolutionFix; - return img2; - // DPI FIX END - } -} - -SpriteType GetSpriteType(string path) -{ - string folderPath = Path.GetDirectoryName(path); - string folderName = new DirectoryInfo(folderPath).Name; - string lowerName = folderName.ToLower(); - - if (lowerName == "backgrounds" || lowerName == "background") - { - return SpriteType.Background; - } - else if (lowerName == "fonts" || lowerName == "font") - { - return SpriteType.Font; - } - else if (lowerName == "sprites" || lowerName == "sprite") - { - return SpriteType.Sprite; - } - return SpriteType.Unknown; -} - -string CheckValidity() -{ - bool recursiveCheck = ScriptQuestion(@"This script imports all sprites in all subdirectories recursively. -If an image file is in a folder named ""Backgrounds"", then the image will be imported as a background. -Otherwise, the image will be imported as a sprite. -Do you want to continue?"); - if (!recursiveCheck) - throw new ScriptException("Script cancelled."); - - // Get import folder - string importFolder = PromptChooseDirectory("Import From Where"); - if (importFolder == null) - throw new ScriptException("The import folder was not set."); - - //Stop the script if there's missing sprite entries or w/e. - bool hadMessage = false; - string[] dirFiles = Directory.GetFiles(importFolder, "*.png", SearchOption.AllDirectories); - foreach (string file in dirFiles) - { - string FileNameWithExtension = Path.GetFileName(file); - string stripped = Path.GetFileNameWithoutExtension(file); - int lastUnderscore = stripped.LastIndexOf('_'); - string spriteName = ""; - - SpriteType spriteType = GetSpriteType(file); - - if ((spriteType != SpriteType.Sprite) && (spriteType != SpriteType.Background)) - { - if (!hadMessage) - { - hadMessage = true; - importAsSprite = ScriptQuestion(FileNameWithExtension + @" is in an incorrectly-named folder (valid names being ""Sprites"" and ""Backgrounds""). Would you like to import these images as sprites? -Pressing ""No"" will cause the program to ignore these images."); - } - - if (!importAsSprite) - { - continue; - } - else - { - spriteType = SpriteType.Sprite; - } - } - - // Check for duplicate filenames - string[] dupFiles = Directory.GetFiles(importFolder, FileNameWithExtension, SearchOption.AllDirectories); - if (dupFiles.Length > 1) - throw new ScriptException("Duplicate file detected. There are " + dupFiles.Length + " files named: " + FileNameWithExtension); - - // Sprites can have multiple frames! Do some sprite-specific checking. - if (spriteType == SpriteType.Sprite) - { - try - { - spriteName = stripped.Substring(0, lastUnderscore); - } - catch - { - throw new ScriptException("Getting the sprite name of " + FileNameWithExtension + " failed."); - } - Int32 validFrameNumber = 0; - try - { - validFrameNumber = Int32.Parse(stripped.Substring(lastUnderscore + 1)); - } - catch - { - throw new ScriptException("The index of " + FileNameWithExtension + " could not be determined."); - } - int frame = 0; - try - { - frame = Int32.Parse(stripped.Substring(lastUnderscore + 1)); - } - catch - { - throw new ScriptException(FileNameWithExtension + " is using letters instead of numbers. The script has stopped for your own protection."); - } - int prevframe = 0; - if (frame != 0) - { - prevframe = (frame - 1); - } - if (frame < 0) - { - throw new ScriptException(spriteName + " is using an invalid numbering scheme. The script has stopped for your own protection."); - } - var prevFrameName = spriteName + "_" + prevframe.ToString() + ".png"; - string[] previousFrameFiles = Directory.GetFiles(importFolder, prevFrameName, SearchOption.AllDirectories); - if (previousFrameFiles.Length < 1) - throw new ScriptException(spriteName + " is missing one or more indexes. The detected missing index is: " + prevFrameName); - } - } - return importFolder; -} +// Texture packer by Samuel Roy +// Uses code from https://github.com/mfascia/TexturePacker + +using System; +using System.IO; +using System.Drawing; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UndertaleModLib.Util; + +EnsureDataLoaded(); + +bool importAsSprite = false; + +string importFolder = CheckValidity(); + +string workDirectory = Path.GetDirectoryName(FilePath) + Path.DirectorySeparatorChar; +string packDir = Path.Combine(workDirectory, "Packager"); +Directory.CreateDirectory(packDir); + +string sourcePath = importFolder; +string searchPattern = "*.png"; +string outName = Path.Combine(packDir, "atlas.txt"); +int textureSize = 2048; +int PaddingValue = 2; +bool debug = false; +Packer packer = new Packer(); +packer.Process(sourcePath, searchPattern, textureSize, PaddingValue, debug); +packer.SaveAtlasses(outName); + +int lastTextPage = Data.EmbeddedTextures.Count - 1; +int lastTextPageItem = Data.TexturePageItems.Count - 1; + +// Import everything into UMT +string prefix = outName.Replace(Path.GetExtension(outName), ""); +int atlasCount = 0; +foreach (Atlas atlas in packer.Atlasses) +{ + string atlasName = Path.Combine(packDir, String.Format(prefix + "{0:000}" + ".png", atlasCount)); + Bitmap atlasBitmap = new Bitmap(atlasName); + UndertaleEmbeddedTexture texture = new UndertaleEmbeddedTexture(); + texture.Name = new UndertaleString("Texture " + ++lastTextPage); + texture.TextureData.TextureBlob = File.ReadAllBytes(atlasName); + Data.EmbeddedTextures.Add(texture); + foreach (Node n in atlas.Nodes) + { + if (n.Texture != null) + { + // Initalize values of this texture + UndertaleTexturePageItem texturePageItem = new UndertaleTexturePageItem(); + texturePageItem.Name = new UndertaleString("PageItem " + ++lastTextPageItem); + texturePageItem.SourceX = (ushort)n.Bounds.X; + texturePageItem.SourceY = (ushort)n.Bounds.Y; + texturePageItem.SourceWidth = (ushort)n.Bounds.Width; + texturePageItem.SourceHeight = (ushort)n.Bounds.Height; + texturePageItem.TargetX = 0; + texturePageItem.TargetY = 0; + texturePageItem.TargetWidth = (ushort)n.Bounds.Width; + texturePageItem.TargetHeight = (ushort)n.Bounds.Height; + texturePageItem.BoundingWidth = (ushort)n.Bounds.Width; + texturePageItem.BoundingHeight = (ushort)n.Bounds.Height; + texturePageItem.TexturePage = texture; + + // Add this texture to UMT + Data.TexturePageItems.Add(texturePageItem); + + // String processing + string stripped = Path.GetFileNameWithoutExtension(n.Texture.Source); + + SpriteType spriteType = GetSpriteType(n.Texture.Source); + + if (importAsSprite) + { + if ((spriteType == SpriteType.Unknown) || (spriteType == SpriteType.Font)) + { + spriteType = SpriteType.Sprite; + } + } + + setTextureTargetBounds(texturePageItem, stripped, n); + + + if (spriteType == SpriteType.Background) + { + UndertaleBackground background = Data.Backgrounds.ByName(stripped); + if (background != null) + { + background.Texture = texturePageItem; + } + else + { + // No background found, let's make one + UndertaleString backgroundUTString = Data.Strings.MakeString(stripped); + UndertaleBackground newBackground = new UndertaleBackground(); + newBackground.Name = backgroundUTString; + newBackground.Transparent = false; + newBackground.Preload = false; + newBackground.Texture = texturePageItem; + Data.Backgrounds.Add(newBackground); + } + } + else if (spriteType == SpriteType.Sprite) + { + // Get sprite to add this texture to + string spriteName; + int lastUnderscore, frame; + try + { + lastUnderscore = stripped.LastIndexOf('_'); + spriteName = stripped.Substring(0, lastUnderscore); + frame = Int32.Parse(stripped.Substring(lastUnderscore + 1)); + } + catch (Exception e) + { + ScriptMessage("Error: Image " + stripped + " has an invalid name. Skipping..."); + continue; + } + UndertaleSprite sprite = null; + sprite = Data.Sprites.ByName(spriteName); + + // Create TextureEntry object + UndertaleSprite.TextureEntry texentry = new UndertaleSprite.TextureEntry(); + texentry.Texture = texturePageItem; + + // Set values for new sprites + if (sprite == null) + { + UndertaleString spriteUTString = Data.Strings.MakeString(spriteName); + UndertaleSprite newSprite = new UndertaleSprite(); + newSprite.Name = spriteUTString; + newSprite.Width = (uint)n.Bounds.Width; + newSprite.Height = (uint)n.Bounds.Height; + newSprite.MarginLeft = 0; + newSprite.MarginRight = n.Bounds.Width - 1; + newSprite.MarginTop = 0; + newSprite.MarginBottom = n.Bounds.Height - 1; + newSprite.OriginX = 0; + newSprite.OriginY = 0; + if (frame > 0) + { + for (int i = 0; i < frame; i++) + newSprite.Textures.Add(null); + } + newSprite.CollisionMasks.Add(newSprite.NewMaskEntry()); + Rectangle bmpRect = new Rectangle(n.Bounds.X, n.Bounds.Y, n.Bounds.Width, n.Bounds.Height); + System.Drawing.Imaging.PixelFormat format = atlasBitmap.PixelFormat; + Bitmap cloneBitmap = atlasBitmap.Clone(bmpRect, format); + int width = ((n.Bounds.Width + 7) / 8) * 8; + BitArray maskingBitArray = new BitArray(width * n.Bounds.Height); + for (int y = 0; y < n.Bounds.Height; y++) + { + for (int x = 0; x < n.Bounds.Width; x++) + { + Color pixelColor = cloneBitmap.GetPixel(x, y); + maskingBitArray[y * width + x] = (pixelColor.A > 0); + } + } + BitArray tempBitArray = new BitArray(width * n.Bounds.Height); + for (int i = 0; i < maskingBitArray.Length; i += 8) + { + for (int j = 0; j < 8; j++) + { + tempBitArray[j + i] = maskingBitArray[-(j - 7) + i]; + } + } + int numBytes; + numBytes = maskingBitArray.Length / 8; + byte[] bytes = new byte[numBytes]; + tempBitArray.CopyTo(bytes, 0); + for (int i = 0; i < bytes.Length; i++) + newSprite.CollisionMasks[0].Data[i] = bytes[i]; + newSprite.Textures.Add(texentry); + Data.Sprites.Add(newSprite); + continue; + } + if (frame > sprite.Textures.Count - 1) + { + while (frame > sprite.Textures.Count - 1) + { + sprite.Textures.Add(texentry); + } + continue; + } + sprite.Textures[frame] = texentry; + } + } + } + // Increment atlas + atlasCount++; +} + +HideProgressBar(); +ScriptMessage("Import Complete!"); + +void setTextureTargetBounds(UndertaleTexturePageItem tex, string textureName, Node n) +{ + tex.TargetX = 0; + tex.TargetY = 0; + tex.TargetWidth = (ushort)n.Bounds.Width; + tex.TargetHeight = (ushort)n.Bounds.Height; +} + +public class TextureInfo +{ + public string Source; + public int Width; + public int Height; +} + +public enum SpriteType +{ + Sprite, + Background, + Font, + Unknown +} + + +public enum SplitType +{ + Horizontal, + Vertical, +} + +public enum BestFitHeuristic +{ + Area, + MaxOneAxis, +} + +public class Node +{ + public Rectangle Bounds; + public TextureInfo Texture; + public SplitType SplitType; +} + +public class Atlas +{ + public int Width; + public int Height; + public List Nodes; +} + +public class Packer +{ + public List SourceTextures; + public StringWriter Log; + public StringWriter Error; + public int Padding; + public int AtlasSize; + public bool DebugMode; + public BestFitHeuristic FitHeuristic; + public List Atlasses; + + public Packer() + { + SourceTextures = new List(); + Log = new StringWriter(); + Error = new StringWriter(); + } + + public void Process(string _SourceDir, string _Pattern, int _AtlasSize, int _Padding, bool _DebugMode) + { + Padding = _Padding; + AtlasSize = _AtlasSize; + DebugMode = _DebugMode; + //1: scan for all the textures we need to pack + ScanForTextures(_SourceDir, _Pattern); + List textures = new List(); + textures = SourceTextures.ToList(); + //2: generate as many atlasses as needed (with the latest one as small as possible) + Atlasses = new List(); + while (textures.Count > 0) + { + Atlas atlas = new Atlas(); + atlas.Width = _AtlasSize; + atlas.Height = _AtlasSize; + List leftovers = LayoutAtlas(textures, atlas); + if (leftovers.Count == 0) + { + // we reached the last atlas. Check if this last atlas could have been twice smaller + while (leftovers.Count == 0) + { + atlas.Width /= 2; + atlas.Height /= 2; + leftovers = LayoutAtlas(textures, atlas); + } + // we need to go 1 step larger as we found the first size that is to small + atlas.Width *= 2; + atlas.Height *= 2; + leftovers = LayoutAtlas(textures, atlas); + } + Atlasses.Add(atlas); + textures = leftovers; + } + } + + public void SaveAtlasses(string _Destination) + { + int atlasCount = 0; + string prefix = _Destination.Replace(Path.GetExtension(_Destination), ""); + string descFile = _Destination; + StreamWriter tw = new StreamWriter(_Destination); + tw.WriteLine("source_tex, atlas_tex, x, y, width, height"); + foreach (Atlas atlas in Atlasses) + { + string atlasName = String.Format(prefix + "{0:000}" + ".png", atlasCount); + //1: Save images + Image img = CreateAtlasImage(atlas); + img.Save(atlasName, System.Drawing.Imaging.ImageFormat.Png); + //2: save description in file + foreach (Node n in atlas.Nodes) + { + if (n.Texture != null) + { + tw.Write(n.Texture.Source + ", "); + tw.Write(atlasName + ", "); + tw.Write((n.Bounds.X).ToString() + ", "); + tw.Write((n.Bounds.Y).ToString() + ", "); + tw.Write((n.Bounds.Width).ToString() + ", "); + tw.WriteLine((n.Bounds.Height).ToString()); + } + } + ++atlasCount; + } + tw.Close(); + tw = new StreamWriter(prefix + ".log"); + tw.WriteLine("--- LOG -------------------------------------------"); + tw.WriteLine(Log.ToString()); + tw.WriteLine("--- ERROR -----------------------------------------"); + tw.WriteLine(Error.ToString()); + tw.Close(); + } + + private void ScanForTextures(string _Path, string _Wildcard) + { + DirectoryInfo di = new DirectoryInfo(_Path); + FileInfo[] files = di.GetFiles(_Wildcard, SearchOption.AllDirectories); + foreach (FileInfo fi in files) + { + Image img = Image.FromFile(fi.FullName); + if (img != null) + { + if (img.Width <= AtlasSize && img.Height <= AtlasSize) + { + TextureInfo ti = new TextureInfo(); + + ti.Source = fi.FullName; + ti.Width = img.Width; + ti.Height = img.Height; + + SourceTextures.Add(ti); + + Log.WriteLine("Added " + fi.FullName); + } + else + { + Error.WriteLine(fi.FullName + " is too large to fix in the atlas. Skipping!"); + } + } + } + } + + private void HorizontalSplit(Node _ToSplit, int _Width, int _Height, List _List) + { + Node n1 = new Node(); + n1.Bounds.X = _ToSplit.Bounds.X + _Width + Padding; + n1.Bounds.Y = _ToSplit.Bounds.Y; + n1.Bounds.Width = _ToSplit.Bounds.Width - _Width - Padding; + n1.Bounds.Height = _Height; + n1.SplitType = SplitType.Vertical; + Node n2 = new Node(); + n2.Bounds.X = _ToSplit.Bounds.X; + n2.Bounds.Y = _ToSplit.Bounds.Y + _Height + Padding; + n2.Bounds.Width = _ToSplit.Bounds.Width; + n2.Bounds.Height = _ToSplit.Bounds.Height - _Height - Padding; + n2.SplitType = SplitType.Horizontal; + if (n1.Bounds.Width > 0 && n1.Bounds.Height > 0) + _List.Add(n1); + if (n2.Bounds.Width > 0 && n2.Bounds.Height > 0) + _List.Add(n2); + } + + private void VerticalSplit(Node _ToSplit, int _Width, int _Height, List _List) + { + Node n1 = new Node(); + n1.Bounds.X = _ToSplit.Bounds.X + _Width + Padding; + n1.Bounds.Y = _ToSplit.Bounds.Y; + n1.Bounds.Width = _ToSplit.Bounds.Width - _Width - Padding; + n1.Bounds.Height = _ToSplit.Bounds.Height; + n1.SplitType = SplitType.Vertical; + Node n2 = new Node(); + n2.Bounds.X = _ToSplit.Bounds.X; + n2.Bounds.Y = _ToSplit.Bounds.Y + _Height + Padding; + n2.Bounds.Width = _Width; + n2.Bounds.Height = _ToSplit.Bounds.Height - _Height - Padding; + n2.SplitType = SplitType.Horizontal; + if (n1.Bounds.Width > 0 && n1.Bounds.Height > 0) + _List.Add(n1); + if (n2.Bounds.Width > 0 && n2.Bounds.Height > 0) + _List.Add(n2); + } + + private TextureInfo FindBestFitForNode(Node _Node, List _Textures) + { + TextureInfo bestFit = null; + float nodeArea = _Node.Bounds.Width * _Node.Bounds.Height; + float maxCriteria = 0.0f; + foreach (TextureInfo ti in _Textures) + { + switch (FitHeuristic) + { + // Max of Width and Height ratios + case BestFitHeuristic.MaxOneAxis: + if (ti.Width <= _Node.Bounds.Width && ti.Height <= _Node.Bounds.Height) + { + float wRatio = (float)ti.Width / (float)_Node.Bounds.Width; + float hRatio = (float)ti.Height / (float)_Node.Bounds.Height; + float ratio = wRatio > hRatio ? wRatio : hRatio; + if (ratio > maxCriteria) + { + maxCriteria = ratio; + bestFit = ti; + } + } + break; + // Maximize Area coverage + case BestFitHeuristic.Area: + if (ti.Width <= _Node.Bounds.Width && ti.Height <= _Node.Bounds.Height) + { + float textureArea = ti.Width * ti.Height; + float coverage = textureArea / nodeArea; + if (coverage > maxCriteria) + { + maxCriteria = coverage; + bestFit = ti; + } + } + break; + } + } + return bestFit; + } + + private List LayoutAtlas(List _Textures, Atlas _Atlas) + { + List freeList = new List(); + List textures = new List(); + _Atlas.Nodes = new List(); + textures = _Textures.ToList(); + Node root = new Node(); + root.Bounds.Size = new Size(_Atlas.Width, _Atlas.Height); + root.SplitType = SplitType.Horizontal; + freeList.Add(root); + while (freeList.Count > 0 && textures.Count > 0) + { + Node node = freeList[0]; + freeList.RemoveAt(0); + TextureInfo bestFit = FindBestFitForNode(node, textures); + if (bestFit != null) + { + if (node.SplitType == SplitType.Horizontal) + { + HorizontalSplit(node, bestFit.Width, bestFit.Height, freeList); + } + else + { + VerticalSplit(node, bestFit.Width, bestFit.Height, freeList); + } + node.Texture = bestFit; + node.Bounds.Width = bestFit.Width; + node.Bounds.Height = bestFit.Height; + textures.Remove(bestFit); + } + _Atlas.Nodes.Add(node); + } + return textures; + } + + private Image CreateAtlasImage(Atlas _Atlas) + { + Image img = new Bitmap(_Atlas.Width, _Atlas.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb); + Graphics g = Graphics.FromImage(img); + foreach (Node n in _Atlas.Nodes) + { + if (n.Texture != null) + { + Image sourceImg = Image.FromFile(n.Texture.Source); + g.DrawImage(sourceImg, n.Bounds); + } + } + // DPI FIX START + Bitmap ResolutionFix = new Bitmap(img); + ResolutionFix.SetResolution(96.0F, 96.0F); + Image img2 = ResolutionFix; + return img2; + // DPI FIX END + } +} + +SpriteType GetSpriteType(string path) +{ + string folderPath = Path.GetDirectoryName(path); + string folderName = new DirectoryInfo(folderPath).Name; + string lowerName = folderName.ToLower(); + + if (lowerName == "backgrounds" || lowerName == "background") + { + return SpriteType.Background; + } + else if (lowerName == "fonts" || lowerName == "font") + { + return SpriteType.Font; + } + else if (lowerName == "sprites" || lowerName == "sprite") + { + return SpriteType.Sprite; + } + return SpriteType.Unknown; +} + +string CheckValidity() +{ + bool recursiveCheck = ScriptQuestion(@"This script imports all sprites in all subdirectories recursively. +If an image file is in a folder named ""Backgrounds"", then the image will be imported as a background. +Otherwise, the image will be imported as a sprite. +Do you want to continue?"); + if (!recursiveCheck) + throw new ScriptException("Script cancelled."); + + // Get import folder + string importFolder = PromptChooseDirectory(); + if (importFolder == null) + throw new ScriptException("The import folder was not set."); + + //Stop the script if there's missing sprite entries or w/e. + bool hadMessage = false; + string[] dirFiles = Directory.GetFiles(importFolder, "*.png", SearchOption.AllDirectories); + foreach (string file in dirFiles) + { + string FileNameWithExtension = Path.GetFileName(file); + string stripped = Path.GetFileNameWithoutExtension(file); + int lastUnderscore = stripped.LastIndexOf('_'); + string spriteName = ""; + + SpriteType spriteType = GetSpriteType(file); + + if ((spriteType != SpriteType.Sprite) && (spriteType != SpriteType.Background)) + { + if (!hadMessage) + { + hadMessage = true; + importAsSprite = ScriptQuestion(FileNameWithExtension + @" is in an incorrectly-named folder (valid names being ""Sprites"" and ""Backgrounds""). Would you like to import these images as sprites? +Pressing ""No"" will cause the program to ignore these images."); + } + + if (!importAsSprite) + { + continue; + } + else + { + spriteType = SpriteType.Sprite; + } + } + + // Check for duplicate filenames + string[] dupFiles = Directory.GetFiles(importFolder, FileNameWithExtension, SearchOption.AllDirectories); + if (dupFiles.Length > 1) + throw new ScriptException("Duplicate file detected. There are " + dupFiles.Length + " files named: " + FileNameWithExtension); + + // Sprites can have multiple frames! Do some sprite-specific checking. + if (spriteType == SpriteType.Sprite) + { + try + { + spriteName = stripped.Substring(0, lastUnderscore); + } + catch + { + throw new ScriptException("Getting the sprite name of " + FileNameWithExtension + " failed."); + } + Int32 validFrameNumber = 0; + try + { + validFrameNumber = Int32.Parse(stripped.Substring(lastUnderscore + 1)); + } + catch + { + throw new ScriptException("The index of " + FileNameWithExtension + " could not be determined."); + } + int frame = 0; + try + { + frame = Int32.Parse(stripped.Substring(lastUnderscore + 1)); + } + catch + { + throw new ScriptException(FileNameWithExtension + " is using letters instead of numbers. The script has stopped for your own protection."); + } + int prevframe = 0; + if (frame != 0) + { + prevframe = (frame - 1); + } + if (frame < 0) + { + throw new ScriptException(spriteName + " is using an invalid numbering scheme. The script has stopped for your own protection."); + } + var prevFrameName = spriteName + "_" + prevframe.ToString() + ".png"; + string[] previousFrameFiles = Directory.GetFiles(importFolder, prevFrameName, SearchOption.AllDirectories); + if (previousFrameFiles.Length < 1) + throw new ScriptException(spriteName + " is missing one or more indexes. The detected missing index is: " + prevFrameName); + } + } + return importFolder; +} diff --git a/UndertaleModTool/Repackers/ImportMasks.csx b/UndertaleModTool/Repackers/ImportMasks.csx index 05c3cf09a..a9ac426d8 100644 --- a/UndertaleModTool/Repackers/ImportMasks.csx +++ b/UndertaleModTool/Repackers/ImportMasks.csx @@ -1,119 +1,119 @@ -// Made by Grossley -// Version 1 -// 12/07/2020 - -using System; -using System.IO; -using System.Drawing; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using UndertaleModLib.Util; - -EnsureDataLoaded(); - -// Get import folder -string importFolder = PromptChooseDirectory("Import From Where"); -if (importFolder == null) - throw new ScriptException("The import folder was not set."); - -string[] dirFiles = Directory.GetFiles(importFolder); - -//Stop the script if there's missing sprite entries or w/e. -foreach (string file in dirFiles) -{ - string FileNameWithExtension = Path.GetFileName(file); - if (!FileNameWithExtension.EndsWith(".png")) - continue; // Restarts loop if file is not a valid mask asset. - string stripped = Path.GetFileNameWithoutExtension(file); - int lastUnderscore = stripped.LastIndexOf('_'); - string spriteName = ""; - try - { - spriteName = stripped.Substring(0, lastUnderscore); - } - catch - { - throw new ScriptException("Getting the sprite name of " + FileNameWithExtension + " failed."); - } - if (Data.Sprites.ByName(spriteName) == null) // Reject non-existing sprites - { - throw new ScriptException(FileNameWithExtension + " could not be imported as the sprite " + spriteName + " does not exist."); - } - using (Image img = Image.FromFile(file)) - { - if ((Data.Sprites.ByName(spriteName).Width != (uint)img.Width) || (Data.Sprites.ByName(spriteName).Height != (uint)img.Height)) - throw new ScriptException(FileNameWithExtension + " is not the proper size to be imported! Please correct this before importing! The proper dimensions are width: " + Data.Sprites.ByName(spriteName).Width.ToString() + " px, height: " + Data.Sprites.ByName(spriteName).Height.ToString() + " px."); - } - - Int32 validFrameNumber = 0; - try - { - validFrameNumber = Int32.Parse(stripped.Substring(lastUnderscore + 1)); - } - catch - { - throw new ScriptException("The index of " + FileNameWithExtension + " could not be determined."); - } - int frame = 0; - try - { - frame = Int32.Parse(stripped.Substring(lastUnderscore + 1)); - } - catch - { - throw new ScriptException(FileNameWithExtension + " is using letters instead of numbers. The script has stopped for your own protection."); - } - int prevframe = 0; - if (frame != 0) - { - prevframe = (frame - 1); - } - if (frame < 0) - { - throw new ScriptException(spriteName + " is using an invalid numbering scheme. The script has stopped for your own protection."); - } - var prevFrameName = spriteName + "_" + prevframe.ToString() + ".png"; - string[] previousFrameFiles = Directory.GetFiles(importFolder, prevFrameName); - if (previousFrameFiles.Length < 1) - throw new ScriptException(spriteName + " is missing one or more indexes. The detected missing index is: " + prevFrameName); -} - -SetProgressBar(null, "Files", 0, dirFiles.Length); -StartUpdater(); - -await Task.Run(() => { - foreach (string file in dirFiles) - { - IncProgress(); - - string FileNameWithExtension = Path.GetFileName(file); - if (!FileNameWithExtension.EndsWith(".png")) - continue; // Restarts loop if file is not a valid mask asset. - string stripped = Path.GetFileNameWithoutExtension(file); - int lastUnderscore = stripped.LastIndexOf('_'); - string spriteName = stripped.Substring(0, lastUnderscore); - int frame = Int32.Parse(stripped.Substring(lastUnderscore + 1)); - UndertaleSprite sprite = Data.Sprites.ByName(spriteName); - int collision_mask_count = sprite.CollisionMasks.Count; - while (collision_mask_count <= frame) - { - sprite.CollisionMasks.Add(sprite.NewMaskEntry()); - collision_mask_count += 1; - } - try - { - sprite.CollisionMasks[frame].Data = TextureWorker.ReadMaskData(file); - } - catch - { - throw new ScriptException(FileNameWithExtension + " has an error that prevents its import and so the operation has been aborted! Please correct this before trying again!"); - } - } -}); - -await StopUpdater(); -HideProgressBar(); -ScriptMessage("Import Complete!"); +// Made by Grossley +// Version 1 +// 12/07/2020 + +using System; +using System.IO; +using System.Drawing; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UndertaleModLib.Util; + +EnsureDataLoaded(); + +// Get import folder +string importFolder = PromptChooseDirectory(); +if (importFolder == null) + throw new ScriptException("The import folder was not set."); + +string[] dirFiles = Directory.GetFiles(importFolder); + +//Stop the script if there's missing sprite entries or w/e. +foreach (string file in dirFiles) +{ + string FileNameWithExtension = Path.GetFileName(file); + if (!FileNameWithExtension.EndsWith(".png")) + continue; // Restarts loop if file is not a valid mask asset. + string stripped = Path.GetFileNameWithoutExtension(file); + int lastUnderscore = stripped.LastIndexOf('_'); + string spriteName = ""; + try + { + spriteName = stripped.Substring(0, lastUnderscore); + } + catch + { + throw new ScriptException("Getting the sprite name of " + FileNameWithExtension + " failed."); + } + if (Data.Sprites.ByName(spriteName) == null) // Reject non-existing sprites + { + throw new ScriptException(FileNameWithExtension + " could not be imported as the sprite " + spriteName + " does not exist."); + } + using (Image img = Image.FromFile(file)) + { + if ((Data.Sprites.ByName(spriteName).Width != (uint)img.Width) || (Data.Sprites.ByName(spriteName).Height != (uint)img.Height)) + throw new ScriptException(FileNameWithExtension + " is not the proper size to be imported! Please correct this before importing! The proper dimensions are width: " + Data.Sprites.ByName(spriteName).Width.ToString() + " px, height: " + Data.Sprites.ByName(spriteName).Height.ToString() + " px."); + } + + Int32 validFrameNumber = 0; + try + { + validFrameNumber = Int32.Parse(stripped.Substring(lastUnderscore + 1)); + } + catch + { + throw new ScriptException("The index of " + FileNameWithExtension + " could not be determined."); + } + int frame = 0; + try + { + frame = Int32.Parse(stripped.Substring(lastUnderscore + 1)); + } + catch + { + throw new ScriptException(FileNameWithExtension + " is using letters instead of numbers. The script has stopped for your own protection."); + } + int prevframe = 0; + if (frame != 0) + { + prevframe = (frame - 1); + } + if (frame < 0) + { + throw new ScriptException(spriteName + " is using an invalid numbering scheme. The script has stopped for your own protection."); + } + var prevFrameName = spriteName + "_" + prevframe.ToString() + ".png"; + string[] previousFrameFiles = Directory.GetFiles(importFolder, prevFrameName); + if (previousFrameFiles.Length < 1) + throw new ScriptException(spriteName + " is missing one or more indexes. The detected missing index is: " + prevFrameName); +} + +SetProgressBar(null, "Files", 0, dirFiles.Length); +StartProgressBarUpdater(); + +await Task.Run(() => { + foreach (string file in dirFiles) + { + IncrementProgress(); + + string FileNameWithExtension = Path.GetFileName(file); + if (!FileNameWithExtension.EndsWith(".png")) + continue; // Restarts loop if file is not a valid mask asset. + string stripped = Path.GetFileNameWithoutExtension(file); + int lastUnderscore = stripped.LastIndexOf('_'); + string spriteName = stripped.Substring(0, lastUnderscore); + int frame = Int32.Parse(stripped.Substring(lastUnderscore + 1)); + UndertaleSprite sprite = Data.Sprites.ByName(spriteName); + int collision_mask_count = sprite.CollisionMasks.Count; + while (collision_mask_count <= frame) + { + sprite.CollisionMasks.Add(sprite.NewMaskEntry()); + collision_mask_count += 1; + } + try + { + sprite.CollisionMasks[frame].Data = TextureWorker.ReadMaskData(file); + } + catch + { + throw new ScriptException(FileNameWithExtension + " has an error that prevents its import and so the operation has been aborted! Please correct this before trying again!"); + } + } +}); + +await StopProgressBarUpdater(); +HideProgressBar(); +ScriptMessage("Import Complete!"); diff --git a/UndertaleModTool/Repackers/ImportShaderData.csx b/UndertaleModTool/Repackers/ImportShaderData.csx index 9c2949c4c..f7e5f2731 100644 --- a/UndertaleModTool/Repackers/ImportShaderData.csx +++ b/UndertaleModTool/Repackers/ImportShaderData.csx @@ -1,305 +1,306 @@ -using System.Text; -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; - -EnsureDataLoaded(); - -string importFolder = PromptChooseDirectory("Select 'Import_Loc.txt' file in 'Shader_Data'"); -if (importFolder == null) - throw new ScriptException("The import folder was not set."); - -string[] dirFiles = Directory.GetFiles(importFolder, "*.*", SearchOption.AllDirectories); -List shadersToModify = new List(); -List shadersExisting = new List(); -List shadersNonExist = new List(); -foreach (string file in dirFiles) -{ - if (Path.GetFileName(file) == "Import_Loc.txt") - continue; - else - { - shadersToModify.Add(Path.GetDirectoryName(file).Replace(importFolder, "")); - } -} -List currentList = new List(); -string res = ""; - -for (var i = 0; i < shadersToModify.Count; i++) -{ - currentList.Clear(); - for (int j = 0; j < Data.Shaders.Count; j++) - { - string x = Data.Shaders[j].Name.Content; - res += (x + "\n"); - currentList.Add(x); - } - if (Data.Shaders.ByName(shadersToModify[i]) != null) - { - Data.Shaders.Remove(Data.Shaders.ByName(shadersToModify[i])); - AddShader(shadersToModify[i]); - Reorganize(Data.Shaders, currentList); - } - else - AddShader(shadersToModify[i]); -} - - -void ImportShader(UndertaleShader existing_shader) -{ - string localImportDir = importFolder + "/" + existing_shader.Name.Content + "/"; - if (File.Exists(localImportDir + "Type.txt")) - { - string shader_type = File.ReadAllText(localImportDir + "Type.txt"); - if (shader_type.Contains("GLSL_ES")) - existing_shader.Type = UndertaleShader.ShaderType.GLSL_ES; - else if (shader_type.Contains("GLSL")) - existing_shader.Type = UndertaleShader.ShaderType.GLSL; - else if (shader_type.Contains("HLSL9")) - existing_shader.Type = UndertaleShader.ShaderType.HLSL9; - else if (shader_type.Contains("HLSL11")) - existing_shader.Type = UndertaleShader.ShaderType.HLSL11; - else if (shader_type.Contains("PSSL")) - existing_shader.Type = UndertaleShader.ShaderType.PSSL; - else if (shader_type.Contains("Cg_PSVita")) - existing_shader.Type = UndertaleShader.ShaderType.Cg_PSVita; - else if (shader_type.Contains("Cg_PS3")) - existing_shader.Type = UndertaleShader.ShaderType.Cg_PS3; - } - if (File.Exists(localImportDir + "GLSL_ES_Fragment.txt")) - existing_shader.GLSL_ES_Fragment.Content = File.ReadAllText(localImportDir + "GLSL_ES_Fragment.txt"); - if (File.Exists(localImportDir + "GLSL_ES_Vertex.txt")) - existing_shader.GLSL_ES_Vertex.Content = File.ReadAllText(localImportDir + "GLSL_ES_Vertex.txt"); - if (File.Exists(localImportDir + "GLSL_Fragment.txt")) - existing_shader.GLSL_Fragment.Content = File.ReadAllText(localImportDir + "GLSL_Fragment.txt"); - if (File.Exists(localImportDir + "GLSL_Vertex.txt")) - existing_shader.GLSL_Vertex.Content = File.ReadAllText(localImportDir + "GLSL_Vertex.txt"); - if (File.Exists(localImportDir + "HLSL9_Fragment.txt")) - existing_shader.HLSL9_Fragment.Content = File.ReadAllText(localImportDir + "HLSL9_Fragment.txt"); - if (File.Exists(localImportDir + "HLSL9_Vertex.txt")) - existing_shader.HLSL9_Vertex.Content = File.ReadAllText(localImportDir + "HLSL9_Vertex.txt"); - if (File.Exists(localImportDir + "HLSL11_VertexData.bin")) - { - if (existing_shader.HLSL11_VertexData == null) - { - existing_shader.HLSL11_VertexData = new UndertaleShader.UndertaleRawShaderData(); - } - existing_shader.HLSL11_VertexData.Data = File.ReadAllBytes(localImportDir + "HLSL11_VertexData.bin"); - existing_shader.HLSL11_VertexData.IsNull = false; - } - if (File.Exists(localImportDir + "HLSL11_PixelData.bin")) - { - if (existing_shader.HLSL11_PixelData == null) - { - existing_shader.HLSL11_PixelData = new UndertaleShader.UndertaleRawShaderData(); - } - existing_shader.HLSL11_PixelData.IsNull = false; - existing_shader.HLSL11_PixelData.Data = File.ReadAllBytes(localImportDir + "HLSL11_PixelData.bin"); - } - if (File.Exists(localImportDir + "PSSL_VertexData.bin")) - { - if (existing_shader.PSSL_VertexData == null) - { - existing_shader.PSSL_VertexData = new UndertaleShader.UndertaleRawShaderData(); - } - existing_shader.PSSL_VertexData.IsNull = false; - existing_shader.PSSL_VertexData.Data = File.ReadAllBytes(localImportDir + "PSSL_VertexData.bin"); - } - if (File.Exists(localImportDir + "PSSL_PixelData.bin")) - { - if (existing_shader.PSSL_PixelData == null) - { - existing_shader.PSSL_PixelData = new UndertaleShader.UndertaleRawShaderData(); - } - existing_shader.PSSL_PixelData.IsNull = false; - existing_shader.PSSL_PixelData.Data = File.ReadAllBytes(localImportDir + "PSSL_PixelData.bin"); - } - if (File.Exists(localImportDir + "Cg_PSVita_VertexData.bin")) - { - if (existing_shader.Cg_PSVita_VertexData == null) - { - existing_shader.Cg_PSVita_VertexData = new UndertaleShader.UndertaleRawShaderData(); - } - existing_shader.Cg_PSVita_VertexData.IsNull = false; - existing_shader.Cg_PSVita_VertexData.Data = File.ReadAllBytes(localImportDir + "Cg_PSVita_VertexData.bin"); - } - if (File.Exists(localImportDir + "Cg_PSVita_PixelData.bin")) - { - if (existing_shader.Cg_PSVita_PixelData == null) - { - existing_shader.Cg_PSVita_PixelData = new UndertaleShader.UndertaleRawShaderData(); - } - existing_shader.Cg_PSVita_PixelData.IsNull = false; - existing_shader.Cg_PSVita_PixelData.Data = File.ReadAllBytes(localImportDir + "Cg_PSVita_PixelData.bin"); - } - if (File.Exists(localImportDir + "Cg_PS3_VertexData.bin")) - { - if (existing_shader.Cg_PS3_VertexData == null) - { - existing_shader.Cg_PS3_VertexData = new UndertaleShader.UndertaleRawShaderData(); - } - existing_shader.Cg_PS3_VertexData.IsNull = false; - existing_shader.Cg_PS3_VertexData.Data = File.ReadAllBytes(localImportDir + "Cg_PS3_VertexData.bin"); - } - if (File.Exists(localImportDir + "Cg_PS3_PixelData.bin")) - { - if (existing_shader.Cg_PS3_PixelData == null) - { - existing_shader.Cg_PS3_PixelData = new UndertaleShader.UndertaleRawShaderData(); - } - existing_shader.Cg_PS3_PixelData.IsNull = false; - existing_shader.Cg_PS3_PixelData.Data = File.ReadAllBytes(localImportDir + "Cg_PS3_PixelData.bin"); - } -} - -void AddShader(string shader_name) -{ - UndertaleShader new_shader = new UndertaleShader(); - new_shader.Name = Data.Strings.MakeString(shader_name); - string localImportDir = importFolder + "/" + shader_name + "/"; - if (File.Exists(localImportDir + "Type.txt")) - { - string shader_type = File.ReadAllText(localImportDir + "Type.txt"); - if (shader_type.Contains("GLSL_ES")) - new_shader.Type = UndertaleShader.ShaderType.GLSL_ES; - else if (shader_type.Contains("GLSL")) - new_shader.Type = UndertaleShader.ShaderType.GLSL; - else if (shader_type.Contains("HLSL9")) - new_shader.Type = UndertaleShader.ShaderType.HLSL9; - else if (shader_type.Contains("HLSL11")) - new_shader.Type = UndertaleShader.ShaderType.HLSL11; - else if (shader_type.Contains("PSSL")) - new_shader.Type = UndertaleShader.ShaderType.PSSL; - else if (shader_type.Contains("Cg_PSVita")) - new_shader.Type = UndertaleShader.ShaderType.Cg_PSVita; - else if (shader_type.Contains("Cg_PS3")) - new_shader.Type = UndertaleShader.ShaderType.Cg_PS3; - else - new_shader.Type = UndertaleShader.ShaderType.GLSL_ES; - } - else - new_shader.Type = UndertaleShader.ShaderType.GLSL_ES; - if (File.Exists(localImportDir + "GLSL_ES_Fragment.txt")) - new_shader.GLSL_ES_Fragment = Data.Strings.MakeString(File.ReadAllText(localImportDir + "GLSL_ES_Fragment.txt")); - else - new_shader.GLSL_ES_Fragment = Data.Strings.MakeString(""); - if (File.Exists(localImportDir + "GLSL_ES_Vertex.txt")) - new_shader.GLSL_ES_Vertex = Data.Strings.MakeString(File.ReadAllText(localImportDir + "GLSL_ES_Vertex.txt")); - else - new_shader.GLSL_ES_Vertex = Data.Strings.MakeString(""); - if (File.Exists(localImportDir + "GLSL_Fragment.txt")) - new_shader.GLSL_Fragment = Data.Strings.MakeString(File.ReadAllText(localImportDir + "GLSL_Fragment.txt")); - else - new_shader.GLSL_Fragment = Data.Strings.MakeString(""); - if (File.Exists(localImportDir + "GLSL_Vertex.txt")) - new_shader.GLSL_Vertex = Data.Strings.MakeString(File.ReadAllText(localImportDir + "GLSL_Vertex.txt")); - else - new_shader.GLSL_Vertex = Data.Strings.MakeString(""); - if (File.Exists(localImportDir + "HLSL9_Fragment.txt")) - new_shader.HLSL9_Fragment = Data.Strings.MakeString(File.ReadAllText(localImportDir + "HLSL9_Fragment.txt")); - else - new_shader.HLSL9_Fragment = Data.Strings.MakeString(""); - if (File.Exists(localImportDir + "HLSL9_Vertex.txt")) - new_shader.HLSL9_Vertex = Data.Strings.MakeString(File.ReadAllText(localImportDir + "HLSL9_Vertex.txt")); - else - new_shader.HLSL9_Vertex = Data.Strings.MakeString(""); - if (File.Exists(localImportDir + "HLSL11_VertexData.bin")) - { - new_shader.HLSL11_VertexData = new UndertaleShader.UndertaleRawShaderData(); - new_shader.HLSL11_VertexData.Data = File.ReadAllBytes(localImportDir + "HLSL11_VertexData.bin"); - new_shader.HLSL11_VertexData.IsNull = false; - } - if (File.Exists(localImportDir + "HLSL11_PixelData.bin")) - { - new_shader.HLSL11_PixelData = new UndertaleShader.UndertaleRawShaderData(); - new_shader.HLSL11_PixelData.IsNull = false; - new_shader.HLSL11_PixelData.Data = File.ReadAllBytes(localImportDir + "HLSL11_PixelData.bin"); - } - if (File.Exists(localImportDir + "PSSL_VertexData.bin")) - { - new_shader.PSSL_VertexData = new UndertaleShader.UndertaleRawShaderData(); - new_shader.PSSL_VertexData.IsNull = false; - new_shader.PSSL_VertexData.Data = File.ReadAllBytes(localImportDir + "PSSL_VertexData.bin"); - } - if (File.Exists(localImportDir + "PSSL_PixelData.bin")) - { - new_shader.PSSL_PixelData = new UndertaleShader.UndertaleRawShaderData(); - new_shader.PSSL_PixelData.IsNull = false; - new_shader.PSSL_PixelData.Data = File.ReadAllBytes(localImportDir + "PSSL_PixelData.bin"); - } - if (File.Exists(localImportDir + "Cg_PSVita_VertexData.bin")) - { - new_shader.Cg_PSVita_VertexData = new UndertaleShader.UndertaleRawShaderData(); - new_shader.Cg_PSVita_VertexData.IsNull = false; - new_shader.Cg_PSVita_VertexData.Data = File.ReadAllBytes(localImportDir + "Cg_PSVita_VertexData.bin"); - } - if (File.Exists(localImportDir + "Cg_PSVita_PixelData.bin")) - { - new_shader.Cg_PSVita_PixelData = new UndertaleShader.UndertaleRawShaderData(); - new_shader.Cg_PSVita_PixelData.IsNull = false; - new_shader.Cg_PSVita_PixelData.Data = File.ReadAllBytes(localImportDir + "Cg_PSVita_PixelData.bin"); - } - if (File.Exists(localImportDir + "Cg_PS3_VertexData.bin")) - { - new_shader.Cg_PS3_VertexData = new UndertaleShader.UndertaleRawShaderData(); - new_shader.Cg_PS3_VertexData.IsNull = false; - new_shader.Cg_PS3_VertexData.Data = File.ReadAllBytes(localImportDir + "Cg_PS3_VertexData.bin"); - } - if (File.Exists(localImportDir + "Cg_PS3_PixelData.bin")) - { - new_shader.Cg_PS3_PixelData = new UndertaleShader.UndertaleRawShaderData(); - new_shader.Cg_PS3_PixelData.IsNull = false; - new_shader.Cg_PS3_PixelData.Data = File.ReadAllBytes(localImportDir + "Cg_PS3_PixelData.bin"); - } - if (File.Exists(localImportDir + "VertexShaderAttributes.txt")) - { - string line; - // Read the file and display it line by line. - System.IO.StreamReader file = new System.IO.StreamReader(localImportDir + "VertexShaderAttributes.txt"); - while((line = file.ReadLine()) != null) - { - if (line != "") - { - UndertaleShader.VertexShaderAttribute vertex_x = new UndertaleShader.VertexShaderAttribute(); - vertex_x.Name = Data.Strings.MakeString(line); - new_shader.VertexShaderAttributes.Add(vertex_x); - } - } - file.Close(); - } - Data.Shaders.Add(new_shader); -} - -void Reorganize(IList list, List order) where T : UndertaleNamedResource, new() -{ - Dictionary temp = new Dictionary(); - for (int i = 0; i < list.Count; i++) - { - T asset = list[i]; - string assetName = asset.Name?.Content; - if (order.Contains(assetName)) - { - temp[assetName] = asset; - } - } - - List addOrder = new List(); - for (int i = order.Count - 1; i >= 0; i--) - { - T asset; - try - { - asset = temp[order[i]]; - } catch (Exception e) - { - throw new ScriptException("Missing asset with name \"" + order[i] + "\""); - } - addOrder.Add(asset); - } - - foreach (T asset in addOrder) - list.Remove(asset); - foreach (T asset in addOrder) - list.Insert(0, asset); -} +using System.Text; +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +EnsureDataLoaded(); + +// "Select 'Import_Loc.txt' file in 'Shader_Data'" +string importFolder = PromptChooseDirectory(); +if (importFolder == null) + throw new ScriptException("The import folder was not set."); + +string[] dirFiles = Directory.GetFiles(importFolder, "*.*", SearchOption.AllDirectories); +List shadersToModify = new List(); +List shadersExisting = new List(); +List shadersNonExist = new List(); +foreach (string file in dirFiles) +{ + if (Path.GetFileName(file) == "Import_Loc.txt") + continue; + else + { + shadersToModify.Add(Path.GetDirectoryName(file).Replace(importFolder, "")); + } +} +List currentList = new List(); +string res = ""; + +for (var i = 0; i < shadersToModify.Count; i++) +{ + currentList.Clear(); + for (int j = 0; j < Data.Shaders.Count; j++) + { + string x = Data.Shaders[j].Name.Content; + res += (x + "\n"); + currentList.Add(x); + } + if (Data.Shaders.ByName(shadersToModify[i]) != null) + { + Data.Shaders.Remove(Data.Shaders.ByName(shadersToModify[i])); + AddShader(shadersToModify[i]); + Reorganize(Data.Shaders, currentList); + } + else + AddShader(shadersToModify[i]); +} + + +void ImportShader(UndertaleShader existing_shader) +{ + string localImportDir = importFolder + "/" + existing_shader.Name.Content + "/"; + if (File.Exists(localImportDir + "Type.txt")) + { + string shader_type = File.ReadAllText(localImportDir + "Type.txt"); + if (shader_type.Contains("GLSL_ES")) + existing_shader.Type = UndertaleShader.ShaderType.GLSL_ES; + else if (shader_type.Contains("GLSL")) + existing_shader.Type = UndertaleShader.ShaderType.GLSL; + else if (shader_type.Contains("HLSL9")) + existing_shader.Type = UndertaleShader.ShaderType.HLSL9; + else if (shader_type.Contains("HLSL11")) + existing_shader.Type = UndertaleShader.ShaderType.HLSL11; + else if (shader_type.Contains("PSSL")) + existing_shader.Type = UndertaleShader.ShaderType.PSSL; + else if (shader_type.Contains("Cg_PSVita")) + existing_shader.Type = UndertaleShader.ShaderType.Cg_PSVita; + else if (shader_type.Contains("Cg_PS3")) + existing_shader.Type = UndertaleShader.ShaderType.Cg_PS3; + } + if (File.Exists(localImportDir + "GLSL_ES_Fragment.txt")) + existing_shader.GLSL_ES_Fragment.Content = File.ReadAllText(localImportDir + "GLSL_ES_Fragment.txt"); + if (File.Exists(localImportDir + "GLSL_ES_Vertex.txt")) + existing_shader.GLSL_ES_Vertex.Content = File.ReadAllText(localImportDir + "GLSL_ES_Vertex.txt"); + if (File.Exists(localImportDir + "GLSL_Fragment.txt")) + existing_shader.GLSL_Fragment.Content = File.ReadAllText(localImportDir + "GLSL_Fragment.txt"); + if (File.Exists(localImportDir + "GLSL_Vertex.txt")) + existing_shader.GLSL_Vertex.Content = File.ReadAllText(localImportDir + "GLSL_Vertex.txt"); + if (File.Exists(localImportDir + "HLSL9_Fragment.txt")) + existing_shader.HLSL9_Fragment.Content = File.ReadAllText(localImportDir + "HLSL9_Fragment.txt"); + if (File.Exists(localImportDir + "HLSL9_Vertex.txt")) + existing_shader.HLSL9_Vertex.Content = File.ReadAllText(localImportDir + "HLSL9_Vertex.txt"); + if (File.Exists(localImportDir + "HLSL11_VertexData.bin")) + { + if (existing_shader.HLSL11_VertexData == null) + { + existing_shader.HLSL11_VertexData = new UndertaleShader.UndertaleRawShaderData(); + } + existing_shader.HLSL11_VertexData.Data = File.ReadAllBytes(localImportDir + "HLSL11_VertexData.bin"); + existing_shader.HLSL11_VertexData.IsNull = false; + } + if (File.Exists(localImportDir + "HLSL11_PixelData.bin")) + { + if (existing_shader.HLSL11_PixelData == null) + { + existing_shader.HLSL11_PixelData = new UndertaleShader.UndertaleRawShaderData(); + } + existing_shader.HLSL11_PixelData.IsNull = false; + existing_shader.HLSL11_PixelData.Data = File.ReadAllBytes(localImportDir + "HLSL11_PixelData.bin"); + } + if (File.Exists(localImportDir + "PSSL_VertexData.bin")) + { + if (existing_shader.PSSL_VertexData == null) + { + existing_shader.PSSL_VertexData = new UndertaleShader.UndertaleRawShaderData(); + } + existing_shader.PSSL_VertexData.IsNull = false; + existing_shader.PSSL_VertexData.Data = File.ReadAllBytes(localImportDir + "PSSL_VertexData.bin"); + } + if (File.Exists(localImportDir + "PSSL_PixelData.bin")) + { + if (existing_shader.PSSL_PixelData == null) + { + existing_shader.PSSL_PixelData = new UndertaleShader.UndertaleRawShaderData(); + } + existing_shader.PSSL_PixelData.IsNull = false; + existing_shader.PSSL_PixelData.Data = File.ReadAllBytes(localImportDir + "PSSL_PixelData.bin"); + } + if (File.Exists(localImportDir + "Cg_PSVita_VertexData.bin")) + { + if (existing_shader.Cg_PSVita_VertexData == null) + { + existing_shader.Cg_PSVita_VertexData = new UndertaleShader.UndertaleRawShaderData(); + } + existing_shader.Cg_PSVita_VertexData.IsNull = false; + existing_shader.Cg_PSVita_VertexData.Data = File.ReadAllBytes(localImportDir + "Cg_PSVita_VertexData.bin"); + } + if (File.Exists(localImportDir + "Cg_PSVita_PixelData.bin")) + { + if (existing_shader.Cg_PSVita_PixelData == null) + { + existing_shader.Cg_PSVita_PixelData = new UndertaleShader.UndertaleRawShaderData(); + } + existing_shader.Cg_PSVita_PixelData.IsNull = false; + existing_shader.Cg_PSVita_PixelData.Data = File.ReadAllBytes(localImportDir + "Cg_PSVita_PixelData.bin"); + } + if (File.Exists(localImportDir + "Cg_PS3_VertexData.bin")) + { + if (existing_shader.Cg_PS3_VertexData == null) + { + existing_shader.Cg_PS3_VertexData = new UndertaleShader.UndertaleRawShaderData(); + } + existing_shader.Cg_PS3_VertexData.IsNull = false; + existing_shader.Cg_PS3_VertexData.Data = File.ReadAllBytes(localImportDir + "Cg_PS3_VertexData.bin"); + } + if (File.Exists(localImportDir + "Cg_PS3_PixelData.bin")) + { + if (existing_shader.Cg_PS3_PixelData == null) + { + existing_shader.Cg_PS3_PixelData = new UndertaleShader.UndertaleRawShaderData(); + } + existing_shader.Cg_PS3_PixelData.IsNull = false; + existing_shader.Cg_PS3_PixelData.Data = File.ReadAllBytes(localImportDir + "Cg_PS3_PixelData.bin"); + } +} + +void AddShader(string shader_name) +{ + UndertaleShader new_shader = new UndertaleShader(); + new_shader.Name = Data.Strings.MakeString(shader_name); + string localImportDir = importFolder + "/" + shader_name + "/"; + if (File.Exists(localImportDir + "Type.txt")) + { + string shader_type = File.ReadAllText(localImportDir + "Type.txt"); + if (shader_type.Contains("GLSL_ES")) + new_shader.Type = UndertaleShader.ShaderType.GLSL_ES; + else if (shader_type.Contains("GLSL")) + new_shader.Type = UndertaleShader.ShaderType.GLSL; + else if (shader_type.Contains("HLSL9")) + new_shader.Type = UndertaleShader.ShaderType.HLSL9; + else if (shader_type.Contains("HLSL11")) + new_shader.Type = UndertaleShader.ShaderType.HLSL11; + else if (shader_type.Contains("PSSL")) + new_shader.Type = UndertaleShader.ShaderType.PSSL; + else if (shader_type.Contains("Cg_PSVita")) + new_shader.Type = UndertaleShader.ShaderType.Cg_PSVita; + else if (shader_type.Contains("Cg_PS3")) + new_shader.Type = UndertaleShader.ShaderType.Cg_PS3; + else + new_shader.Type = UndertaleShader.ShaderType.GLSL_ES; + } + else + new_shader.Type = UndertaleShader.ShaderType.GLSL_ES; + if (File.Exists(localImportDir + "GLSL_ES_Fragment.txt")) + new_shader.GLSL_ES_Fragment = Data.Strings.MakeString(File.ReadAllText(localImportDir + "GLSL_ES_Fragment.txt")); + else + new_shader.GLSL_ES_Fragment = Data.Strings.MakeString(""); + if (File.Exists(localImportDir + "GLSL_ES_Vertex.txt")) + new_shader.GLSL_ES_Vertex = Data.Strings.MakeString(File.ReadAllText(localImportDir + "GLSL_ES_Vertex.txt")); + else + new_shader.GLSL_ES_Vertex = Data.Strings.MakeString(""); + if (File.Exists(localImportDir + "GLSL_Fragment.txt")) + new_shader.GLSL_Fragment = Data.Strings.MakeString(File.ReadAllText(localImportDir + "GLSL_Fragment.txt")); + else + new_shader.GLSL_Fragment = Data.Strings.MakeString(""); + if (File.Exists(localImportDir + "GLSL_Vertex.txt")) + new_shader.GLSL_Vertex = Data.Strings.MakeString(File.ReadAllText(localImportDir + "GLSL_Vertex.txt")); + else + new_shader.GLSL_Vertex = Data.Strings.MakeString(""); + if (File.Exists(localImportDir + "HLSL9_Fragment.txt")) + new_shader.HLSL9_Fragment = Data.Strings.MakeString(File.ReadAllText(localImportDir + "HLSL9_Fragment.txt")); + else + new_shader.HLSL9_Fragment = Data.Strings.MakeString(""); + if (File.Exists(localImportDir + "HLSL9_Vertex.txt")) + new_shader.HLSL9_Vertex = Data.Strings.MakeString(File.ReadAllText(localImportDir + "HLSL9_Vertex.txt")); + else + new_shader.HLSL9_Vertex = Data.Strings.MakeString(""); + if (File.Exists(localImportDir + "HLSL11_VertexData.bin")) + { + new_shader.HLSL11_VertexData = new UndertaleShader.UndertaleRawShaderData(); + new_shader.HLSL11_VertexData.Data = File.ReadAllBytes(localImportDir + "HLSL11_VertexData.bin"); + new_shader.HLSL11_VertexData.IsNull = false; + } + if (File.Exists(localImportDir + "HLSL11_PixelData.bin")) + { + new_shader.HLSL11_PixelData = new UndertaleShader.UndertaleRawShaderData(); + new_shader.HLSL11_PixelData.IsNull = false; + new_shader.HLSL11_PixelData.Data = File.ReadAllBytes(localImportDir + "HLSL11_PixelData.bin"); + } + if (File.Exists(localImportDir + "PSSL_VertexData.bin")) + { + new_shader.PSSL_VertexData = new UndertaleShader.UndertaleRawShaderData(); + new_shader.PSSL_VertexData.IsNull = false; + new_shader.PSSL_VertexData.Data = File.ReadAllBytes(localImportDir + "PSSL_VertexData.bin"); + } + if (File.Exists(localImportDir + "PSSL_PixelData.bin")) + { + new_shader.PSSL_PixelData = new UndertaleShader.UndertaleRawShaderData(); + new_shader.PSSL_PixelData.IsNull = false; + new_shader.PSSL_PixelData.Data = File.ReadAllBytes(localImportDir + "PSSL_PixelData.bin"); + } + if (File.Exists(localImportDir + "Cg_PSVita_VertexData.bin")) + { + new_shader.Cg_PSVita_VertexData = new UndertaleShader.UndertaleRawShaderData(); + new_shader.Cg_PSVita_VertexData.IsNull = false; + new_shader.Cg_PSVita_VertexData.Data = File.ReadAllBytes(localImportDir + "Cg_PSVita_VertexData.bin"); + } + if (File.Exists(localImportDir + "Cg_PSVita_PixelData.bin")) + { + new_shader.Cg_PSVita_PixelData = new UndertaleShader.UndertaleRawShaderData(); + new_shader.Cg_PSVita_PixelData.IsNull = false; + new_shader.Cg_PSVita_PixelData.Data = File.ReadAllBytes(localImportDir + "Cg_PSVita_PixelData.bin"); + } + if (File.Exists(localImportDir + "Cg_PS3_VertexData.bin")) + { + new_shader.Cg_PS3_VertexData = new UndertaleShader.UndertaleRawShaderData(); + new_shader.Cg_PS3_VertexData.IsNull = false; + new_shader.Cg_PS3_VertexData.Data = File.ReadAllBytes(localImportDir + "Cg_PS3_VertexData.bin"); + } + if (File.Exists(localImportDir + "Cg_PS3_PixelData.bin")) + { + new_shader.Cg_PS3_PixelData = new UndertaleShader.UndertaleRawShaderData(); + new_shader.Cg_PS3_PixelData.IsNull = false; + new_shader.Cg_PS3_PixelData.Data = File.ReadAllBytes(localImportDir + "Cg_PS3_PixelData.bin"); + } + if (File.Exists(localImportDir + "VertexShaderAttributes.txt")) + { + string line; + // Read the file and display it line by line. + System.IO.StreamReader file = new System.IO.StreamReader(localImportDir + "VertexShaderAttributes.txt"); + while((line = file.ReadLine()) != null) + { + if (line != "") + { + UndertaleShader.VertexShaderAttribute vertex_x = new UndertaleShader.VertexShaderAttribute(); + vertex_x.Name = Data.Strings.MakeString(line); + new_shader.VertexShaderAttributes.Add(vertex_x); + } + } + file.Close(); + } + Data.Shaders.Add(new_shader); +} + +void Reorganize(IList list, List order) where T : UndertaleNamedResource, new() +{ + Dictionary temp = new Dictionary(); + for (int i = 0; i < list.Count; i++) + { + T asset = list[i]; + string assetName = asset.Name?.Content; + if (order.Contains(assetName)) + { + temp[assetName] = asset; + } + } + + List addOrder = new List(); + for (int i = order.Count - 1; i >= 0; i--) + { + T asset; + try + { + asset = temp[order[i]]; + } catch (Exception e) + { + throw new ScriptException("Missing asset with name \"" + order[i] + "\""); + } + addOrder.Add(asset); + } + + foreach (T asset in addOrder) + list.Remove(asset); + foreach (T asset in addOrder) + list.Insert(0, asset); +} diff --git a/UndertaleModTool/Repackers/ImportSoundsBulk.csx b/UndertaleModTool/Repackers/ImportSoundsBulk.csx index 65d574e7e..3e9d1809b 100644 --- a/UndertaleModTool/Repackers/ImportSoundsBulk.csx +++ b/UndertaleModTool/Repackers/ImportSoundsBulk.csx @@ -1,320 +1,320 @@ -// By Grossley - Version 8 - September 15th, 2020 - Makes the sound importer work in bulk. -// By Jockeholm & Nik the Neko & Grossley - Version 7 - April 19th, 2020 - Adds new audiogroups automatically and adds sound to new empty audiogroup if they do not exist yet. -// By Jockeholm & Nik the Neko & Grossley - Version 6 - April 19th, 2020 - Corrected an oversight which caused errors while trying to add sounds to new audiogroups. -// By Jockeholm & Nik the Neko - Version 5 06/03/2020 - Replace existing sounds if needed. -// By Jockeholm & Nik the Neko - Version 4 06/03/2020 - Specify audiogroup name with a folder name. -// By Jockeholm & Nik the Neko - Version 3 03/01/2020 - Massive im_purr_ovements. -// By Jockeholm - Version 2 15/02/2020 - Currently supports embedded WAV files only - -using System.Windows.Forms; -using UndertaleModLib; -using UndertaleModLib.Models; -using static UndertaleModLib.Models.UndertaleSound; -using static UndertaleModLib.UndertaleData; -using System.Threading.Tasks; - -EnsureDataLoaded(); - -int maxCount = 1; - -string GetFolder(string path) -{ - return Path.GetDirectoryName(path) + Path.DirectorySeparatorChar; -} - -UndertaleEmbeddedAudio audioFile = null; -int audioID = -1; -int audioGroupID = -1; -int embAudioID = -1; -bool usesAGRP = (Data.AudioGroups.Count > 0); - -if (!usesAGRP) -{ - ScriptError("ERROR!\nThis game doesn't use audiogroups!\nImporting to external audiogroups is disabled.", "ImportSound"); - //return; -} - -// Check code directory. -string importFolder = PromptChooseDirectory("Import From Where"); -if (importFolder == null) - throw new ScriptException("The import folder was not set."); - -string[] dirFiles = Directory.GetFiles(importFolder); - -bool manuallySpecifyEverySound = false; -bool GeneralSound_embedSound = false; -bool GeneralSound_decodeLoad = false; -bool GeneralSound_needAGRP = false; -string FolderName = new DirectoryInfo(importFolder).Name; - -bool emergencyCancel = ScriptQuestion(@"This script imports sounds in bulk. Do you wish to continue?"); -if (!emergencyCancel) - return; - -bool replaceSoundPropertiesCheck = ScriptQuestion("WARNING!:\nIf a sound already exists in the game, it will be replaced instead of added. Would you like to replace the sound properties as well?"); - -bool autoSpecifyEverySound = ScriptQuestion(@"Would you like to automatically specify the characteristics of each sound? -If you select no you will have to manually specify all sounds."); - -manuallySpecifyEverySound = (autoSpecifyEverySound ? false : true); - -if (manuallySpecifyEverySound == false) -{ - GeneralSound_embedSound = ScriptQuestion("Do you want to keep your OGG files external or internal?\nNo - keep it external\nYes - embed sound into the game (use responsibly!)"); - if (GeneralSound_embedSound) - { - GeneralSound_decodeLoad = ScriptQuestion("Do you want to Uncompress sounds on load? (Higher Memory, low CPU)"); - } - if (GeneralSound_embedSound && (Data.AudioGroups.Count > 0)) - { - GeneralSound_needAGRP = ScriptQuestion("Your last folder name is: " + FolderName + "\nDo you want to treat it as audiogroup's name?\n(Answer No for audiogroup_default)"); - } - ScriptMessage("WARNING!:\nIf a sound already exists in the game, it will be replaced instead of added."); -} - -maxCount = dirFiles.Length; -SetProgressBar(null, "Importing sounds", 0, maxCount); -StartUpdater(); - -SyncBinding("AudioGroups, EmbeddedAudio, Sounds, Strings", true); -await Task.Run(() => { - foreach (string file in dirFiles) - { - IncProgressLocal(); - - string fname = Path.GetFileName(file); - string temp = fname.ToLower(); - if (((temp.EndsWith(".ogg")) || (temp.EndsWith(".wav"))) == false) - { - //ScriptMessage(fname); - //ScriptMessage(Path.GetExtension(fname).ToLower()); - continue; // Restarts loop if file is not a valid sound asset. - } - //else - //{ - // ScriptMessage("You did it"); - //} - string sound_name = Path.GetFileNameWithoutExtension(file); - bool isOGG = Path.GetExtension(fname) == ".ogg"; - bool embedSound = false; - bool decodeLoad = false; - if ((isOGG) && (manuallySpecifyEverySound == true)) - { - embedSound = ScriptQuestion("Your sound appears to be an OGG.\nNo - keep it external\nYes - embed sound into the game (use responsibly!)"); - decodeLoad = false; - if (embedSound) - { - decodeLoad = ScriptQuestion("Do you want to Uncompress this sound on load? (Higher Memory, low CPU)"); - } - } - else if ((isOGG) && (manuallySpecifyEverySound == false)) - { - embedSound = GeneralSound_embedSound; - decodeLoad = GeneralSound_decodeLoad; - } - else - { - // How can a .wav be external? - embedSound = true; - decodeLoad = false; - } - string AGRPname = ""; - string FolderName = new DirectoryInfo(importFolder).Name; - bool needAGRP = false; - bool ifRightAGRP = false; - string[] splitArr = new string[2]; - splitArr[0] = sound_name; - splitArr[1] = FolderName; - - bool soundExists = false; - - UndertaleSound existing_snd = null; - - for (var i = 0; i < Data.Sounds.Count; i++) - { - if (Data.Sounds[i].Name.Content == sound_name) - { - soundExists = true; - existing_snd = Data.Sounds[i]; - if (manuallySpecifyEverySound == true) - ScriptMessage("WARNING!:\nThe sound with this name already exists in the game, it will be replaced instead of added.\n\nsndname: " + existing_snd.Name.Content); - break; - } - } - - if (embedSound && usesAGRP && !soundExists) - { - if (manuallySpecifyEverySound) - needAGRP = ScriptQuestion("Your last folder name is: " + FolderName + "\nDo you want to treat it as audiogroup's name?\n(Answer No for audiogroup_default)"); - else - needAGRP = GeneralSound_needAGRP; - } - - if (needAGRP && usesAGRP && embedSound) - { - AGRPname = splitArr[1]; - ifRightAGRP = (needAGRP && embedSound); - if (ifRightAGRP) - { - while (audioGroupID == -1) - { - // find the agrp we need. - for (int i = 0; i < Data.AudioGroups.Count; i++) - { - string name = Data.AudioGroups[i].Name.Content; - if (name == AGRPname) - { - audioGroupID = i; - break; - } - } - if (audioGroupID == -1) // still -1? o_O - { - File.WriteAllBytes(GetFolder(FilePath) + "audiogroup" + Data.AudioGroups.Count + ".dat", Convert.FromBase64String("Rk9STQwAAABBVURPBAAAAAAAAAA=")); - var newAudioGroup = new UndertaleAudioGroup() - { - Name = Data.Strings.MakeString(FolderName), - }; - Data.AudioGroups.Add(newAudioGroup); - } - } - } - else - { - return; - } - } - - if (soundExists) - { - for (int i = 0; i < Data.Sounds.Count; i++) - { - string name = sound_name; - if (name == Data.Sounds[i].Name.Content) - { - audioGroupID = Data.Sounds[i].GroupID; - break; - } - } - } - if (audioGroupID == 0) //If the audiogroup is zero then - needAGRP = false; - - UndertaleEmbeddedAudio soundData = null; - - if ((embedSound && !needAGRP) || (needAGRP)) - { - soundData = new UndertaleEmbeddedAudio() { Data = File.ReadAllBytes(importFolder + "/" + fname) }; - Data.EmbeddedAudio.Add(soundData); - if (soundExists) - Data.EmbeddedAudio.Remove(existing_snd.AudioFile); - embAudioID = Data.EmbeddedAudio.Count - 1; - //ScriptMessage("len " + Data.EmbeddedAudio[embAudioID].Data.Length.ToString()); - } - - //ScriptMessage("11"); - - if (needAGRP) - { - var audioGroupReadStream = - ( - new FileStream(GetFolder(FilePath) + "audiogroup" + audioGroupID.ToString() + ".dat", FileMode.Open, FileAccess.Read) - ); // Load the audiogroup dat into memory - UndertaleData audioGroupDat = UndertaleIO.Read(audioGroupReadStream); // Load as UndertaleData - audioGroupReadStream.Dispose(); - audioGroupDat.EmbeddedAudio.Add(soundData); // Adds the embeddedaudio entry to the dat data in memory - if (soundExists) - audioGroupDat.EmbeddedAudio.Remove(existing_snd.AudioFile); - audioID = audioGroupDat.EmbeddedAudio.Count - 1; - var audioGroupWriteStream = - ( - new FileStream(GetFolder(FilePath) + "audiogroup" + audioGroupID.ToString() + ".dat", FileMode.Create) - ); - UndertaleIO.Write(audioGroupWriteStream, audioGroupDat); // Write it to the disk - audioGroupWriteStream.Dispose(); - } - - UndertaleSound.AudioEntryFlags flags = UndertaleSound.AudioEntryFlags.Regular; - - if (isOGG && embedSound && decodeLoad) // OGG, embed, decode on load. - flags = UndertaleSound.AudioEntryFlags.IsEmbedded | UndertaleSound.AudioEntryFlags.IsCompressed | UndertaleSound.AudioEntryFlags.Regular; - if (isOGG && embedSound && !decodeLoad) // OGG, embed, not decode on load. - flags = UndertaleSound.AudioEntryFlags.IsCompressed | UndertaleSound.AudioEntryFlags.Regular; - if (!isOGG) // WAV, always embed. - flags = UndertaleSound.AudioEntryFlags.IsEmbedded | UndertaleSound.AudioEntryFlags.Regular; - if (isOGG && !embedSound) // OGG, external. - { - flags = UndertaleSound.AudioEntryFlags.Regular; - audioID = -1; - } - - UndertaleEmbeddedAudio RaudioFile = null; - if (!embedSound) - RaudioFile = null; - if (embedSound && !needAGRP) - RaudioFile = Data.EmbeddedAudio[embAudioID]; - if (embedSound && needAGRP) - RaudioFile = null; - string soundfname = sound_name; - - UndertaleAudioGroup groupID = null; - if (!usesAGRP) - groupID = null; - else - groupID = needAGRP ? Data.AudioGroups[audioGroupID] : Data.AudioGroups[Data.GetBuiltinSoundGroupID()]; - - //ScriptMessage("12"); - - if (!soundExists) - { - var snd_to_add = new UndertaleSound() - { - Name = Data.Strings.MakeString(soundfname), - Flags = flags, - Type = (isOGG ? Data.Strings.MakeString(".ogg") : Data.Strings.MakeString(".wav")), - File = Data.Strings.MakeString(fname), - Effects = 0, - Volume = 1.0F, - Pitch = 1.0F, - AudioID = audioID, - AudioFile = RaudioFile, - AudioGroup = groupID, - GroupID = (needAGRP ? audioGroupID : Data.GetBuiltinSoundGroupID()) - }; - Data.Sounds.Add(snd_to_add); - //ChangeSelection(snd_to_add); - } - else if (replaceSoundPropertiesCheck) - { - var snd_to_add = Data.Sounds.ByName(soundfname); - snd_to_add.Name = Data.Strings.MakeString(soundfname); - snd_to_add.Flags = flags; - snd_to_add.Type = (isOGG ? Data.Strings.MakeString(".ogg") : Data.Strings.MakeString(".wav")); - snd_to_add.File = Data.Strings.MakeString(fname); - snd_to_add.Effects = 0; - snd_to_add.Volume = 1.0F; - snd_to_add.Pitch = 1.0F; - snd_to_add.AudioID = audioID; - snd_to_add.AudioFile = RaudioFile; - snd_to_add.AudioGroup = groupID; - snd_to_add.GroupID = (needAGRP ? audioGroupID : Data.GetBuiltinSoundGroupID()); - } - else - { - existing_snd.AudioFile = RaudioFile; - existing_snd.AudioID = audioID; - //ChangeSelection(existing_snd); - } - } -}); -SyncBinding(false); - -await StopUpdater(); -ScriptMessage("Sound added successfully!\nEnjoy your meowing day!"); - - -void IncProgressLocal() -{ - if (GetProgress() < maxCount) - IncProgress(); -} +// By Grossley - Version 8 - September 15th, 2020 - Makes the sound importer work in bulk. +// By Jockeholm & Nik the Neko & Grossley - Version 7 - April 19th, 2020 - Adds new audiogroups automatically and adds sound to new empty audiogroup if they do not exist yet. +// By Jockeholm & Nik the Neko & Grossley - Version 6 - April 19th, 2020 - Corrected an oversight which caused errors while trying to add sounds to new audiogroups. +// By Jockeholm & Nik the Neko - Version 5 06/03/2020 - Replace existing sounds if needed. +// By Jockeholm & Nik the Neko - Version 4 06/03/2020 - Specify audiogroup name with a folder name. +// By Jockeholm & Nik the Neko - Version 3 03/01/2020 - Massive im_purr_ovements. +// By Jockeholm - Version 2 15/02/2020 - Currently supports embedded WAV files only + +using System.Windows.Forms; +using UndertaleModLib; +using UndertaleModLib.Models; +using static UndertaleModLib.Models.UndertaleSound; +using static UndertaleModLib.UndertaleData; +using System.Threading.Tasks; + +EnsureDataLoaded(); + +int maxCount = 1; + +string GetFolder(string path) +{ + return Path.GetDirectoryName(path) + Path.DirectorySeparatorChar; +} + +UndertaleEmbeddedAudio audioFile = null; +int audioID = -1; +int audioGroupID = -1; +int embAudioID = -1; +bool usesAGRP = (Data.AudioGroups.Count > 0); + +if (!usesAGRP) +{ + ScriptError("ERROR!\nThis game doesn't use audiogroups!\nImporting to external audiogroups is disabled.", "ImportSound"); + //return; +} + +// Check code directory. +string importFolder = PromptChooseDirectory(); +if (importFolder == null) + throw new ScriptException("The import folder was not set."); + +string[] dirFiles = Directory.GetFiles(importFolder); + +bool manuallySpecifyEverySound = false; +bool GeneralSound_embedSound = false; +bool GeneralSound_decodeLoad = false; +bool GeneralSound_needAGRP = false; +string FolderName = new DirectoryInfo(importFolder).Name; + +bool emergencyCancel = ScriptQuestion(@"This script imports sounds in bulk. Do you wish to continue?"); +if (!emergencyCancel) + return; + +bool replaceSoundPropertiesCheck = ScriptQuestion("WARNING!:\nIf a sound already exists in the game, it will be replaced instead of added. Would you like to replace the sound properties as well?"); + +bool autoSpecifyEverySound = ScriptQuestion(@"Would you like to automatically specify the characteristics of each sound? +If you select no you will have to manually specify all sounds."); + +manuallySpecifyEverySound = (autoSpecifyEverySound ? false : true); + +if (manuallySpecifyEverySound == false) +{ + GeneralSound_embedSound = ScriptQuestion("Do you want to keep your OGG files external or internal?\nNo - keep it external\nYes - embed sound into the game (use responsibly!)"); + if (GeneralSound_embedSound) + { + GeneralSound_decodeLoad = ScriptQuestion("Do you want to Uncompress sounds on load? (Higher Memory, low CPU)"); + } + if (GeneralSound_embedSound && (Data.AudioGroups.Count > 0)) + { + GeneralSound_needAGRP = ScriptQuestion("Your last folder name is: " + FolderName + "\nDo you want to treat it as audiogroup's name?\n(Answer No for audiogroup_default)"); + } + ScriptMessage("WARNING!:\nIf a sound already exists in the game, it will be replaced instead of added."); +} + +maxCount = dirFiles.Length; +SetProgressBar(null, "Importing sounds", 0, maxCount); +StartProgressBarUpdater(); + +SyncBinding("AudioGroups, EmbeddedAudio, Sounds, Strings", true); +await Task.Run(() => { + foreach (string file in dirFiles) + { + IncProgressLocal(); + + string fname = Path.GetFileName(file); + string temp = fname.ToLower(); + if (((temp.EndsWith(".ogg")) || (temp.EndsWith(".wav"))) == false) + { + //ScriptMessage(fname); + //ScriptMessage(Path.GetExtension(fname).ToLower()); + continue; // Restarts loop if file is not a valid sound asset. + } + //else + //{ + // ScriptMessage("You did it"); + //} + string sound_name = Path.GetFileNameWithoutExtension(file); + bool isOGG = Path.GetExtension(fname) == ".ogg"; + bool embedSound = false; + bool decodeLoad = false; + if ((isOGG) && (manuallySpecifyEverySound == true)) + { + embedSound = ScriptQuestion("Your sound appears to be an OGG.\nNo - keep it external\nYes - embed sound into the game (use responsibly!)"); + decodeLoad = false; + if (embedSound) + { + decodeLoad = ScriptQuestion("Do you want to Uncompress this sound on load? (Higher Memory, low CPU)"); + } + } + else if ((isOGG) && (manuallySpecifyEverySound == false)) + { + embedSound = GeneralSound_embedSound; + decodeLoad = GeneralSound_decodeLoad; + } + else + { + // How can a .wav be external? + embedSound = true; + decodeLoad = false; + } + string AGRPname = ""; + string FolderName = new DirectoryInfo(importFolder).Name; + bool needAGRP = false; + bool ifRightAGRP = false; + string[] splitArr = new string[2]; + splitArr[0] = sound_name; + splitArr[1] = FolderName; + + bool soundExists = false; + + UndertaleSound existing_snd = null; + + for (var i = 0; i < Data.Sounds.Count; i++) + { + if (Data.Sounds[i].Name.Content == sound_name) + { + soundExists = true; + existing_snd = Data.Sounds[i]; + if (manuallySpecifyEverySound == true) + ScriptMessage("WARNING!:\nThe sound with this name already exists in the game, it will be replaced instead of added.\n\nsndname: " + existing_snd.Name.Content); + break; + } + } + + if (embedSound && usesAGRP && !soundExists) + { + if (manuallySpecifyEverySound) + needAGRP = ScriptQuestion("Your last folder name is: " + FolderName + "\nDo you want to treat it as audiogroup's name?\n(Answer No for audiogroup_default)"); + else + needAGRP = GeneralSound_needAGRP; + } + + if (needAGRP && usesAGRP && embedSound) + { + AGRPname = splitArr[1]; + ifRightAGRP = (needAGRP && embedSound); + if (ifRightAGRP) + { + while (audioGroupID == -1) + { + // find the agrp we need. + for (int i = 0; i < Data.AudioGroups.Count; i++) + { + string name = Data.AudioGroups[i].Name.Content; + if (name == AGRPname) + { + audioGroupID = i; + break; + } + } + if (audioGroupID == -1) // still -1? o_O + { + File.WriteAllBytes(GetFolder(FilePath) + "audiogroup" + Data.AudioGroups.Count + ".dat", Convert.FromBase64String("Rk9STQwAAABBVURPBAAAAAAAAAA=")); + var newAudioGroup = new UndertaleAudioGroup() + { + Name = Data.Strings.MakeString(FolderName), + }; + Data.AudioGroups.Add(newAudioGroup); + } + } + } + else + { + return; + } + } + + if (soundExists) + { + for (int i = 0; i < Data.Sounds.Count; i++) + { + string name = sound_name; + if (name == Data.Sounds[i].Name.Content) + { + audioGroupID = Data.Sounds[i].GroupID; + break; + } + } + } + if (audioGroupID == 0) //If the audiogroup is zero then + needAGRP = false; + + UndertaleEmbeddedAudio soundData = null; + + if ((embedSound && !needAGRP) || (needAGRP)) + { + soundData = new UndertaleEmbeddedAudio() { Data = File.ReadAllBytes(importFolder + "/" + fname) }; + Data.EmbeddedAudio.Add(soundData); + if (soundExists) + Data.EmbeddedAudio.Remove(existing_snd.AudioFile); + embAudioID = Data.EmbeddedAudio.Count - 1; + //ScriptMessage("len " + Data.EmbeddedAudio[embAudioID].Data.Length.ToString()); + } + + //ScriptMessage("11"); + + if (needAGRP) + { + var audioGroupReadStream = + ( + new FileStream(GetFolder(FilePath) + "audiogroup" + audioGroupID.ToString() + ".dat", FileMode.Open, FileAccess.Read) + ); // Load the audiogroup dat into memory + UndertaleData audioGroupDat = UndertaleIO.Read(audioGroupReadStream); // Load as UndertaleData + audioGroupReadStream.Dispose(); + audioGroupDat.EmbeddedAudio.Add(soundData); // Adds the embeddedaudio entry to the dat data in memory + if (soundExists) + audioGroupDat.EmbeddedAudio.Remove(existing_snd.AudioFile); + audioID = audioGroupDat.EmbeddedAudio.Count - 1; + var audioGroupWriteStream = + ( + new FileStream(GetFolder(FilePath) + "audiogroup" + audioGroupID.ToString() + ".dat", FileMode.Create) + ); + UndertaleIO.Write(audioGroupWriteStream, audioGroupDat); // Write it to the disk + audioGroupWriteStream.Dispose(); + } + + UndertaleSound.AudioEntryFlags flags = UndertaleSound.AudioEntryFlags.Regular; + + if (isOGG && embedSound && decodeLoad) // OGG, embed, decode on load. + flags = UndertaleSound.AudioEntryFlags.IsEmbedded | UndertaleSound.AudioEntryFlags.IsCompressed | UndertaleSound.AudioEntryFlags.Regular; + if (isOGG && embedSound && !decodeLoad) // OGG, embed, not decode on load. + flags = UndertaleSound.AudioEntryFlags.IsCompressed | UndertaleSound.AudioEntryFlags.Regular; + if (!isOGG) // WAV, always embed. + flags = UndertaleSound.AudioEntryFlags.IsEmbedded | UndertaleSound.AudioEntryFlags.Regular; + if (isOGG && !embedSound) // OGG, external. + { + flags = UndertaleSound.AudioEntryFlags.Regular; + audioID = -1; + } + + UndertaleEmbeddedAudio RaudioFile = null; + if (!embedSound) + RaudioFile = null; + if (embedSound && !needAGRP) + RaudioFile = Data.EmbeddedAudio[embAudioID]; + if (embedSound && needAGRP) + RaudioFile = null; + string soundfname = sound_name; + + UndertaleAudioGroup groupID = null; + if (!usesAGRP) + groupID = null; + else + groupID = needAGRP ? Data.AudioGroups[audioGroupID] : Data.AudioGroups[Data.GetBuiltinSoundGroupID()]; + + //ScriptMessage("12"); + + if (!soundExists) + { + var snd_to_add = new UndertaleSound() + { + Name = Data.Strings.MakeString(soundfname), + Flags = flags, + Type = (isOGG ? Data.Strings.MakeString(".ogg") : Data.Strings.MakeString(".wav")), + File = Data.Strings.MakeString(fname), + Effects = 0, + Volume = 1.0F, + Pitch = 1.0F, + AudioID = audioID, + AudioFile = RaudioFile, + AudioGroup = groupID, + GroupID = (needAGRP ? audioGroupID : Data.GetBuiltinSoundGroupID()) + }; + Data.Sounds.Add(snd_to_add); + //ChangeSelection(snd_to_add); + } + else if (replaceSoundPropertiesCheck) + { + var snd_to_add = Data.Sounds.ByName(soundfname); + snd_to_add.Name = Data.Strings.MakeString(soundfname); + snd_to_add.Flags = flags; + snd_to_add.Type = (isOGG ? Data.Strings.MakeString(".ogg") : Data.Strings.MakeString(".wav")); + snd_to_add.File = Data.Strings.MakeString(fname); + snd_to_add.Effects = 0; + snd_to_add.Volume = 1.0F; + snd_to_add.Pitch = 1.0F; + snd_to_add.AudioID = audioID; + snd_to_add.AudioFile = RaudioFile; + snd_to_add.AudioGroup = groupID; + snd_to_add.GroupID = (needAGRP ? audioGroupID : Data.GetBuiltinSoundGroupID()); + } + else + { + existing_snd.AudioFile = RaudioFile; + existing_snd.AudioID = audioID; + //ChangeSelection(existing_snd); + } + } +}); +DisableAllSyncBindings(); + +await StopProgressBarUpdater(); +ScriptMessage("Sound added successfully!\nEnjoy your meowing day!"); + + +void IncProgressLocal() +{ + if (GetProgress() < maxCount) + IncrementProgress(); +} diff --git a/UndertaleModTool/Repackers/ReduceEmbeddedTexturePages.csx b/UndertaleModTool/Repackers/ReduceEmbeddedTexturePages.csx index c11287ade..9633745a8 100644 --- a/UndertaleModTool/Repackers/ReduceEmbeddedTexturePages.csx +++ b/UndertaleModTool/Repackers/ReduceEmbeddedTexturePages.csx @@ -1,546 +1,546 @@ -// Texture packer by Samuel Roy -// Uses code from https://github.com/mfascia/TexturePacker -// Uses code from ExportAllTextures.csx - -using System; -using System.IO; -using System.Drawing; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using UndertaleModLib.Util; - -EnsureDataLoaded(); - -// Get directory paths -string workDirectory = Path.GetDirectoryName(FilePath) + Path.DirectorySeparatorChar; -System.IO.DirectoryInfo dir = System.IO.Directory.CreateDirectory(workDirectory + Path.DirectorySeparatorChar + "Packager"); - -// Clear any files if they already exist -foreach (FileInfo file in dir.GetFiles()) - file.Delete(); -foreach (DirectoryInfo di in dir.GetDirectories()) - di.Delete(true); - -// Start export of all existing textures - -string exportedTexturesFolder = dir.FullName + Path.DirectorySeparatorChar + "Textures" + Path.DirectorySeparatorChar; -TextureWorker worker = new TextureWorker(); -Dictionary assetCoordinateDict = new Dictionary(); -Dictionary assetTypeDict = new Dictionary(); - -Directory.CreateDirectory(exportedTexturesFolder); - -SetProgressBar(null, "Existing Textures Exported", 0, Data.TexturePageItems.Count); -StartUpdater(); - -await DumpSprites(); -await DumpFonts(); -await DumpBackgrounds(); -worker.Cleanup(); - -await StopUpdater(); -HideProgressBar(); - -async Task DumpSprites() -{ - await Task.Run(() => Parallel.ForEach(Data.Sprites, DumpSprite)); -} - -async Task DumpBackgrounds() -{ - await Task.Run(() => Parallel.ForEach(Data.Backgrounds, DumpBackground)); -} - -async Task DumpFonts() -{ - await Task.Run(() => Parallel.ForEach(Data.Fonts, DumpFont)); -} - -void DumpSprite(UndertaleSprite sprite) -{ - for (int i = 0; i < sprite.Textures.Count; i++) - { - if (sprite.Textures[i]?.Texture != null) - { - UndertaleTexturePageItem tex = sprite.Textures[i].Texture; - worker.ExportAsPNG(tex, exportedTexturesFolder + sprite.Name.Content + "_" + i + ".png"); - assetCoordinateDict.Add(sprite.Name.Content + "_" + i, new int[] { tex.TargetX, tex.TargetY, tex.SourceWidth, tex.SourceHeight, tex.TargetWidth, tex.TargetHeight, tex.BoundingWidth, tex.BoundingHeight }); - assetTypeDict.Add(sprite.Name.Content + "_" + i, "spr"); - } - } - - AddProgressP(sprite.Textures.Count); -} - -void DumpFont(UndertaleFont font) -{ - if (font.Texture != null) - { - UndertaleTexturePageItem tex = font.Texture; - worker.ExportAsPNG(tex, exportedTexturesFolder + font.Name.Content + ".png"); - assetCoordinateDict.Add(font.Name.Content, new int[] { tex.TargetX, tex.TargetY, tex.SourceWidth, tex.SourceHeight, tex.TargetWidth, tex.TargetHeight, tex.BoundingWidth, tex.BoundingHeight }); - assetTypeDict.Add(font.Name.Content, "fnt"); - - IncProgressP(); - } -} - -void DumpBackground(UndertaleBackground background) -{ - if (background.Texture != null) - { - UndertaleTexturePageItem tex = background.Texture; - worker.ExportAsPNG(tex, exportedTexturesFolder + background.Name.Content + ".png"); - assetCoordinateDict.Add(background.Name.Content, new int[] { tex.TargetX, tex.TargetY, tex.SourceWidth, tex.SourceHeight, tex.TargetWidth, tex.TargetHeight, tex.BoundingWidth, tex.BoundingHeight }); - assetTypeDict.Add(background.Name.Content, "bg"); - IncProgressP(); - } -} - -// End export - -string sourcePath = exportedTexturesFolder; -string searchPattern = "*.png"; -string outName = dir.FullName + Path.DirectorySeparatorChar + "atlas.txt"; -int textureSize = 2048; -int PaddingValue = 2; -bool debug = false; - -// Delete all existing Textures and TextureSheets -Data.TexturePageItems.Clear(); -Data.EmbeddedTextures.Clear(); - -// Run the texture packer using borrowed and slightly modified code from the -// Texture packer sourced above -Packer packer = new Packer(); -packer.Process(sourcePath, searchPattern, textureSize, PaddingValue, debug); -packer.SaveAtlasses(outName); - -int lastTextPage = Data.EmbeddedTextures.Count - 1; -int lastTextPageItem = Data.TexturePageItems.Count - 1; - -// Import everything into UMT -string prefix = outName.Replace(Path.GetExtension(outName), ""); -int atlasCount = 0; -foreach (Atlas atlas in packer.Atlasses) -{ - string atlasName = String.Format(prefix + "{0:000}" + ".png", atlasCount); - Bitmap atlasBitmap = new Bitmap(atlasName); - UndertaleEmbeddedTexture texture = new UndertaleEmbeddedTexture(); - texture.Name = new UndertaleString("Texture " + ++lastTextPage); - texture.TextureData.TextureBlob = File.ReadAllBytes(atlasName); - Data.EmbeddedTextures.Add(texture); - foreach (Node n in atlas.Nodes) - { - if (n.Texture != null) - { - // Initalize values of this texture - UndertaleTexturePageItem texturePageItem = new UndertaleTexturePageItem(); - texturePageItem.Name = new UndertaleString("PageItem " + ++lastTextPageItem); - texturePageItem.SourceX = (ushort)n.Bounds.X; - texturePageItem.SourceY = (ushort)n.Bounds.Y; - texturePageItem.SourceWidth = (ushort)n.Bounds.Width; - texturePageItem.SourceHeight = (ushort)n.Bounds.Height; - texturePageItem.BoundingWidth = (ushort)n.Bounds.Width; - texturePageItem.BoundingHeight = (ushort)n.Bounds.Height; - texturePageItem.TexturePage = texture; - - // Add this texture to UMT - Data.TexturePageItems.Add(texturePageItem); - - // String processing - string stripped = Path.GetFileNameWithoutExtension(n.Texture.Source); - int firstUnderscore = stripped.IndexOf('_'); - string spriteType = ""; - try - { - if (assetTypeDict.ContainsKey(stripped)) - spriteType = assetTypeDict[stripped]; - else - spriteType = stripped.Substring(0, firstUnderscore); - } - catch (Exception e) - { - if (stripped.Equals("background0") || stripped.Equals("background1")) - { - UndertaleBackground background = Data.Backgrounds.ByName(stripped); //Use stripped instead of sprite name or else the last index calculation gives us a bad string. - background.Texture = texturePageItem; - setTextureTargetBounds(texturePageItem, stripped, n); - continue; - } - else - { - ScriptMessage("Error: Image " + stripped + " has an invalid name."); - continue; - } - } - setTextureTargetBounds(texturePageItem, stripped, n); - // Special Cases for backgrounds and fonts - if (spriteType.Equals("bg")) - { - UndertaleBackground background = Data.Backgrounds.ByName(stripped); // Use stripped instead of sprite name or else the last index calculation gives us a bad string. - background.Texture = texturePageItem; - } - else if (spriteType.Equals("fnt")) - { - UndertaleFont font = Data.Fonts.ByName(stripped); // Use stripped instead of sprite name or else the last index calculation gives us a bad string. - font.Texture = texturePageItem; - } - else - { - // Get sprite to add this texture to - string spriteName; - int lastUnderscore, frame; - try - { - lastUnderscore = stripped.LastIndexOf('_'); - spriteName = stripped.Substring(0, lastUnderscore); - frame = Int32.Parse(stripped.Substring(lastUnderscore + 1)); - } - catch (Exception e) - { - ScriptMessage("Error: Image " + stripped + " has an invalid name. Skipping..."); - continue; - } - UndertaleSprite sprite = null; - sprite = Data.Sprites.ByName(spriteName); - - // Create TextureEntry object - UndertaleSprite.TextureEntry texentry = new UndertaleSprite.TextureEntry(); - texentry.Texture = texturePageItem; - - if (frame > sprite.Textures.Count - 1) - { - while (frame > sprite.Textures.Count - 1) - { - sprite.Textures.Add(texentry); - } - continue; - } - sprite.Textures[frame] = texentry; - } - } - } - // Increment atlas - atlasCount++; -} - -void setTextureTargetBounds(UndertaleTexturePageItem tex, string textureName, Node n) -{ - if (assetCoordinateDict.ContainsKey(textureName)) - { - int[] coords = assetCoordinateDict[textureName]; - tex.TargetX = (ushort)coords[0]; - tex.TargetY = (ushort)coords[1]; - tex.SourceWidth = (ushort)coords[2]; - tex.SourceHeight = (ushort)coords[3]; - tex.TargetWidth = (ushort)coords[4]; - tex.TargetHeight = (ushort)coords[5]; - tex.BoundingWidth = (ushort)coords[6]; - tex.BoundingHeight = (ushort)coords[7]; - } - else - { - tex.TargetX = 0; - tex.TargetY = 0; - tex.TargetWidth = (ushort)n.Bounds.Width; - tex.TargetHeight = (ushort)n.Bounds.Height; - } -} - -HideProgressBar(); -ScriptMessage("Import Complete!"); - -public class TextureInfo -{ - public string Source; - public int Width; - public int Height; -} - -public enum SplitType -{ - Horizontal, - Vertical, -} - -public enum BestFitHeuristic -{ - Area, - MaxOneAxis, -} - -public class Node -{ - public Rectangle Bounds; - public TextureInfo Texture; - public SplitType SplitType; -} - -public class Atlas -{ - public int Width; - public int Height; - public List Nodes; -} - -public class Packer -{ - public List SourceTextures; - public StringWriter Log; - public StringWriter Error; - public int Padding; - public int AtlasSize; - public bool DebugMode; - public BestFitHeuristic FitHeuristic; - public List Atlasses; - - public Packer() - { - SourceTextures = new List(); - Log = new StringWriter(); - Error = new StringWriter(); - } - - public void Process(string _SourceDir, string _Pattern, int _AtlasSize, int _Padding, bool _DebugMode) - { - Padding = _Padding; - AtlasSize = _AtlasSize; - DebugMode = _DebugMode; - //1: scan for all the textures we need to pack - ScanForTextures(_SourceDir, _Pattern); - List textures = new List(); - textures = SourceTextures.ToList(); - //2: generate as many atlasses as needed (with the latest one as small as possible) - Atlasses = new List(); - while (textures.Count > 0) - { - Atlas atlas = new Atlas(); - atlas.Width = _AtlasSize; - atlas.Height = _AtlasSize; - List leftovers = LayoutAtlas(textures, atlas); - if (leftovers.Count == 0) - { - // we reached the last atlas. Check if this last atlas could have been twice smaller - while (leftovers.Count == 0) - { - atlas.Width /= 2; - atlas.Height /= 2; - leftovers = LayoutAtlas(textures, atlas); - } - // we need to go 1 step larger as we found the first size that is to small - atlas.Width *= 2; - atlas.Height *= 2; - leftovers = LayoutAtlas(textures, atlas); - } - Atlasses.Add(atlas); - textures = leftovers; - } - } - - public void SaveAtlasses(string _Destination) - { - int atlasCount = 0; - string prefix = _Destination.Replace(Path.GetExtension(_Destination), ""); - string descFile = _Destination; - StreamWriter tw = new StreamWriter(_Destination); - tw.WriteLine("source_tex, atlas_tex, x, y, width, height"); - foreach (Atlas atlas in Atlasses) - { - string atlasName = String.Format(prefix + "{0:000}" + ".png", atlasCount); - //1: Save images - Image img = CreateAtlasImage(atlas); - img.Save(atlasName, System.Drawing.Imaging.ImageFormat.Png); - //2: save description in file - foreach (Node n in atlas.Nodes) - { - if (n.Texture != null) - { - tw.Write(n.Texture.Source + ", "); - tw.Write(atlasName + ", "); - tw.Write((n.Bounds.X).ToString() + ", "); - tw.Write((n.Bounds.Y).ToString() + ", "); - tw.Write((n.Bounds.Width).ToString() + ", "); - tw.WriteLine((n.Bounds.Height).ToString()); - } - } - ++atlasCount; - } - tw.Close(); - tw = new StreamWriter(prefix + ".log"); - tw.WriteLine("--- LOG -------------------------------------------"); - tw.WriteLine(Log.ToString()); - tw.WriteLine("--- ERROR -----------------------------------------"); - tw.WriteLine(Error.ToString()); - tw.Close(); - } - - private void ScanForTextures(string _Path, string _Wildcard) - { - DirectoryInfo di = new DirectoryInfo(_Path); - FileInfo[] files = di.GetFiles(_Wildcard, SearchOption.AllDirectories); - foreach (FileInfo fi in files) - { - Image img = Image.FromFile(fi.FullName); - if (img != null) - { - if (img.Width <= AtlasSize && img.Height <= AtlasSize) - { - TextureInfo ti = new TextureInfo(); - - ti.Source = fi.FullName; - ti.Width = img.Width; - ti.Height = img.Height; - - SourceTextures.Add(ti); - - Log.WriteLine("Added " + fi.FullName); - } - else - { - Error.WriteLine(fi.FullName + " is too large to fix in the atlas. Skipping!"); - } - } - } - } - - private void HorizontalSplit(Node _ToSplit, int _Width, int _Height, List _List) - { - Node n1 = new Node(); - n1.Bounds.X = _ToSplit.Bounds.X + _Width + Padding; - n1.Bounds.Y = _ToSplit.Bounds.Y; - n1.Bounds.Width = _ToSplit.Bounds.Width - _Width - Padding; - n1.Bounds.Height = _Height; - n1.SplitType = SplitType.Vertical; - Node n2 = new Node(); - n2.Bounds.X = _ToSplit.Bounds.X; - n2.Bounds.Y = _ToSplit.Bounds.Y + _Height + Padding; - n2.Bounds.Width = _ToSplit.Bounds.Width; - n2.Bounds.Height = _ToSplit.Bounds.Height - _Height - Padding; - n2.SplitType = SplitType.Horizontal; - if (n1.Bounds.Width > 0 && n1.Bounds.Height > 0) - _List.Add(n1); - if (n2.Bounds.Width > 0 && n2.Bounds.Height > 0) - _List.Add(n2); - } - - private void VerticalSplit(Node _ToSplit, int _Width, int _Height, List _List) - { - Node n1 = new Node(); - n1.Bounds.X = _ToSplit.Bounds.X + _Width + Padding; - n1.Bounds.Y = _ToSplit.Bounds.Y; - n1.Bounds.Width = _ToSplit.Bounds.Width - _Width - Padding; - n1.Bounds.Height = _ToSplit.Bounds.Height; - n1.SplitType = SplitType.Vertical; - Node n2 = new Node(); - n2.Bounds.X = _ToSplit.Bounds.X; - n2.Bounds.Y = _ToSplit.Bounds.Y + _Height + Padding; - n2.Bounds.Width = _Width; - n2.Bounds.Height = _ToSplit.Bounds.Height - _Height - Padding; - n2.SplitType = SplitType.Horizontal; - if (n1.Bounds.Width > 0 && n1.Bounds.Height > 0) - _List.Add(n1); - if (n2.Bounds.Width > 0 && n2.Bounds.Height > 0) - _List.Add(n2); - } - - private TextureInfo FindBestFitForNode(Node _Node, List _Textures) - { - TextureInfo bestFit = null; - float nodeArea = _Node.Bounds.Width * _Node.Bounds.Height; - float maxCriteria = 0.0f; - foreach (TextureInfo ti in _Textures) - { - switch (FitHeuristic) - { - // Max of Width and Height ratios - case BestFitHeuristic.MaxOneAxis: - if (ti.Width <= _Node.Bounds.Width && ti.Height <= _Node.Bounds.Height) - { - float wRatio = (float)ti.Width / (float)_Node.Bounds.Width; - float hRatio = (float)ti.Height / (float)_Node.Bounds.Height; - float ratio = wRatio > hRatio ? wRatio : hRatio; - if (ratio > maxCriteria) - { - maxCriteria = ratio; - bestFit = ti; - } - } - break; - // Maximize Area coverage - case BestFitHeuristic.Area: - if (ti.Width <= _Node.Bounds.Width && ti.Height <= _Node.Bounds.Height) - { - float textureArea = ti.Width * ti.Height; - float coverage = textureArea / nodeArea; - if (coverage > maxCriteria) - { - maxCriteria = coverage; - bestFit = ti; - } - } - break; - } - } - return bestFit; - } - - private List LayoutAtlas(List _Textures, Atlas _Atlas) - { - List freeList = new List(); - List textures = new List(); - _Atlas.Nodes = new List(); - textures = _Textures.ToList(); - Node root = new Node(); - root.Bounds.Size = new Size(_Atlas.Width, _Atlas.Height); - root.SplitType = SplitType.Horizontal; - freeList.Add(root); - while (freeList.Count > 0 && textures.Count > 0) - { - Node node = freeList[0]; - freeList.RemoveAt(0); - TextureInfo bestFit = FindBestFitForNode(node, textures); - if (bestFit != null) - { - if (node.SplitType == SplitType.Horizontal) - { - HorizontalSplit(node, bestFit.Width, bestFit.Height, freeList); - } - else - { - VerticalSplit(node, bestFit.Width, bestFit.Height, freeList); - } - node.Texture = bestFit; - node.Bounds.Width = bestFit.Width; - node.Bounds.Height = bestFit.Height; - textures.Remove(bestFit); - } - _Atlas.Nodes.Add(node); - } - return textures; - } - - private Image CreateAtlasImage(Atlas _Atlas) - { - Image img = new Bitmap(_Atlas.Width, _Atlas.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb); - Graphics g = Graphics.FromImage(img); - foreach (Node n in _Atlas.Nodes) - { - if (n.Texture != null) - { - Image sourceImg = Image.FromFile(n.Texture.Source); - g.DrawImage(sourceImg, n.Bounds); - } - } - // DPI FIX START - Bitmap ResolutionFix = new Bitmap(img); - ResolutionFix.SetResolution(96.0F, 96.0F); - Image img2 = ResolutionFix; - return img2; - // DPI FIX END - } +// Texture packer by Samuel Roy +// Uses code from https://github.com/mfascia/TexturePacker +// Uses code from ExportAllTextures.csx + +using System; +using System.IO; +using System.Drawing; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using UndertaleModLib.Util; + +EnsureDataLoaded(); + +// Get directory paths +string workDirectory = Path.GetDirectoryName(FilePath) + Path.DirectorySeparatorChar; +System.IO.DirectoryInfo dir = System.IO.Directory.CreateDirectory(workDirectory + Path.DirectorySeparatorChar + "Packager"); + +// Clear any files if they already exist +foreach (FileInfo file in dir.GetFiles()) + file.Delete(); +foreach (DirectoryInfo di in dir.GetDirectories()) + di.Delete(true); + +// Start export of all existing textures + +string exportedTexturesFolder = dir.FullName + Path.DirectorySeparatorChar + "Textures" + Path.DirectorySeparatorChar; +TextureWorker worker = new TextureWorker(); +Dictionary assetCoordinateDict = new Dictionary(); +Dictionary assetTypeDict = new Dictionary(); + +Directory.CreateDirectory(exportedTexturesFolder); + +SetProgressBar(null, "Existing Textures Exported", 0, Data.TexturePageItems.Count); +StartProgressBarUpdater(); + +await DumpSprites(); +await DumpFonts(); +await DumpBackgrounds(); +worker.Cleanup(); + +await StopProgressBarUpdater(); +HideProgressBar(); + +async Task DumpSprites() +{ + await Task.Run(() => Parallel.ForEach(Data.Sprites, DumpSprite)); +} + +async Task DumpBackgrounds() +{ + await Task.Run(() => Parallel.ForEach(Data.Backgrounds, DumpBackground)); +} + +async Task DumpFonts() +{ + await Task.Run(() => Parallel.ForEach(Data.Fonts, DumpFont)); +} + +void DumpSprite(UndertaleSprite sprite) +{ + for (int i = 0; i < sprite.Textures.Count; i++) + { + if (sprite.Textures[i]?.Texture != null) + { + UndertaleTexturePageItem tex = sprite.Textures[i].Texture; + worker.ExportAsPNG(tex, exportedTexturesFolder + sprite.Name.Content + "_" + i + ".png"); + assetCoordinateDict.Add(sprite.Name.Content + "_" + i, new int[] { tex.TargetX, tex.TargetY, tex.SourceWidth, tex.SourceHeight, tex.TargetWidth, tex.TargetHeight, tex.BoundingWidth, tex.BoundingHeight }); + assetTypeDict.Add(sprite.Name.Content + "_" + i, "spr"); + } + } + + AddProgressParallel(sprite.Textures.Count); +} + +void DumpFont(UndertaleFont font) +{ + if (font.Texture != null) + { + UndertaleTexturePageItem tex = font.Texture; + worker.ExportAsPNG(tex, exportedTexturesFolder + font.Name.Content + ".png"); + assetCoordinateDict.Add(font.Name.Content, new int[] { tex.TargetX, tex.TargetY, tex.SourceWidth, tex.SourceHeight, tex.TargetWidth, tex.TargetHeight, tex.BoundingWidth, tex.BoundingHeight }); + assetTypeDict.Add(font.Name.Content, "fnt"); + + IncrementProgressParallel(); + } +} + +void DumpBackground(UndertaleBackground background) +{ + if (background.Texture != null) + { + UndertaleTexturePageItem tex = background.Texture; + worker.ExportAsPNG(tex, exportedTexturesFolder + background.Name.Content + ".png"); + assetCoordinateDict.Add(background.Name.Content, new int[] { tex.TargetX, tex.TargetY, tex.SourceWidth, tex.SourceHeight, tex.TargetWidth, tex.TargetHeight, tex.BoundingWidth, tex.BoundingHeight }); + assetTypeDict.Add(background.Name.Content, "bg"); + IncrementProgressParallel(); + } +} + +// End export + +string sourcePath = exportedTexturesFolder; +string searchPattern = "*.png"; +string outName = dir.FullName + Path.DirectorySeparatorChar + "atlas.txt"; +int textureSize = 2048; +int PaddingValue = 2; +bool debug = false; + +// Delete all existing Textures and TextureSheets +Data.TexturePageItems.Clear(); +Data.EmbeddedTextures.Clear(); + +// Run the texture packer using borrowed and slightly modified code from the +// Texture packer sourced above +Packer packer = new Packer(); +packer.Process(sourcePath, searchPattern, textureSize, PaddingValue, debug); +packer.SaveAtlasses(outName); + +int lastTextPage = Data.EmbeddedTextures.Count - 1; +int lastTextPageItem = Data.TexturePageItems.Count - 1; + +// Import everything into UMT +string prefix = outName.Replace(Path.GetExtension(outName), ""); +int atlasCount = 0; +foreach (Atlas atlas in packer.Atlasses) +{ + string atlasName = String.Format(prefix + "{0:000}" + ".png", atlasCount); + Bitmap atlasBitmap = new Bitmap(atlasName); + UndertaleEmbeddedTexture texture = new UndertaleEmbeddedTexture(); + texture.Name = new UndertaleString("Texture " + ++lastTextPage); + texture.TextureData.TextureBlob = File.ReadAllBytes(atlasName); + Data.EmbeddedTextures.Add(texture); + foreach (Node n in atlas.Nodes) + { + if (n.Texture != null) + { + // Initalize values of this texture + UndertaleTexturePageItem texturePageItem = new UndertaleTexturePageItem(); + texturePageItem.Name = new UndertaleString("PageItem " + ++lastTextPageItem); + texturePageItem.SourceX = (ushort)n.Bounds.X; + texturePageItem.SourceY = (ushort)n.Bounds.Y; + texturePageItem.SourceWidth = (ushort)n.Bounds.Width; + texturePageItem.SourceHeight = (ushort)n.Bounds.Height; + texturePageItem.BoundingWidth = (ushort)n.Bounds.Width; + texturePageItem.BoundingHeight = (ushort)n.Bounds.Height; + texturePageItem.TexturePage = texture; + + // Add this texture to UMT + Data.TexturePageItems.Add(texturePageItem); + + // String processing + string stripped = Path.GetFileNameWithoutExtension(n.Texture.Source); + int firstUnderscore = stripped.IndexOf('_'); + string spriteType = ""; + try + { + if (assetTypeDict.ContainsKey(stripped)) + spriteType = assetTypeDict[stripped]; + else + spriteType = stripped.Substring(0, firstUnderscore); + } + catch (Exception e) + { + if (stripped.Equals("background0") || stripped.Equals("background1")) + { + UndertaleBackground background = Data.Backgrounds.ByName(stripped); //Use stripped instead of sprite name or else the last index calculation gives us a bad string. + background.Texture = texturePageItem; + setTextureTargetBounds(texturePageItem, stripped, n); + continue; + } + else + { + ScriptMessage("Error: Image " + stripped + " has an invalid name."); + continue; + } + } + setTextureTargetBounds(texturePageItem, stripped, n); + // Special Cases for backgrounds and fonts + if (spriteType.Equals("bg")) + { + UndertaleBackground background = Data.Backgrounds.ByName(stripped); // Use stripped instead of sprite name or else the last index calculation gives us a bad string. + background.Texture = texturePageItem; + } + else if (spriteType.Equals("fnt")) + { + UndertaleFont font = Data.Fonts.ByName(stripped); // Use stripped instead of sprite name or else the last index calculation gives us a bad string. + font.Texture = texturePageItem; + } + else + { + // Get sprite to add this texture to + string spriteName; + int lastUnderscore, frame; + try + { + lastUnderscore = stripped.LastIndexOf('_'); + spriteName = stripped.Substring(0, lastUnderscore); + frame = Int32.Parse(stripped.Substring(lastUnderscore + 1)); + } + catch (Exception e) + { + ScriptMessage("Error: Image " + stripped + " has an invalid name. Skipping..."); + continue; + } + UndertaleSprite sprite = null; + sprite = Data.Sprites.ByName(spriteName); + + // Create TextureEntry object + UndertaleSprite.TextureEntry texentry = new UndertaleSprite.TextureEntry(); + texentry.Texture = texturePageItem; + + if (frame > sprite.Textures.Count - 1) + { + while (frame > sprite.Textures.Count - 1) + { + sprite.Textures.Add(texentry); + } + continue; + } + sprite.Textures[frame] = texentry; + } + } + } + // Increment atlas + atlasCount++; +} + +void setTextureTargetBounds(UndertaleTexturePageItem tex, string textureName, Node n) +{ + if (assetCoordinateDict.ContainsKey(textureName)) + { + int[] coords = assetCoordinateDict[textureName]; + tex.TargetX = (ushort)coords[0]; + tex.TargetY = (ushort)coords[1]; + tex.SourceWidth = (ushort)coords[2]; + tex.SourceHeight = (ushort)coords[3]; + tex.TargetWidth = (ushort)coords[4]; + tex.TargetHeight = (ushort)coords[5]; + tex.BoundingWidth = (ushort)coords[6]; + tex.BoundingHeight = (ushort)coords[7]; + } + else + { + tex.TargetX = 0; + tex.TargetY = 0; + tex.TargetWidth = (ushort)n.Bounds.Width; + tex.TargetHeight = (ushort)n.Bounds.Height; + } +} + +HideProgressBar(); +ScriptMessage("Import Complete!"); + +public class TextureInfo +{ + public string Source; + public int Width; + public int Height; +} + +public enum SplitType +{ + Horizontal, + Vertical, +} + +public enum BestFitHeuristic +{ + Area, + MaxOneAxis, +} + +public class Node +{ + public Rectangle Bounds; + public TextureInfo Texture; + public SplitType SplitType; +} + +public class Atlas +{ + public int Width; + public int Height; + public List Nodes; +} + +public class Packer +{ + public List SourceTextures; + public StringWriter Log; + public StringWriter Error; + public int Padding; + public int AtlasSize; + public bool DebugMode; + public BestFitHeuristic FitHeuristic; + public List Atlasses; + + public Packer() + { + SourceTextures = new List(); + Log = new StringWriter(); + Error = new StringWriter(); + } + + public void Process(string _SourceDir, string _Pattern, int _AtlasSize, int _Padding, bool _DebugMode) + { + Padding = _Padding; + AtlasSize = _AtlasSize; + DebugMode = _DebugMode; + //1: scan for all the textures we need to pack + ScanForTextures(_SourceDir, _Pattern); + List textures = new List(); + textures = SourceTextures.ToList(); + //2: generate as many atlasses as needed (with the latest one as small as possible) + Atlasses = new List(); + while (textures.Count > 0) + { + Atlas atlas = new Atlas(); + atlas.Width = _AtlasSize; + atlas.Height = _AtlasSize; + List leftovers = LayoutAtlas(textures, atlas); + if (leftovers.Count == 0) + { + // we reached the last atlas. Check if this last atlas could have been twice smaller + while (leftovers.Count == 0) + { + atlas.Width /= 2; + atlas.Height /= 2; + leftovers = LayoutAtlas(textures, atlas); + } + // we need to go 1 step larger as we found the first size that is to small + atlas.Width *= 2; + atlas.Height *= 2; + leftovers = LayoutAtlas(textures, atlas); + } + Atlasses.Add(atlas); + textures = leftovers; + } + } + + public void SaveAtlasses(string _Destination) + { + int atlasCount = 0; + string prefix = _Destination.Replace(Path.GetExtension(_Destination), ""); + string descFile = _Destination; + StreamWriter tw = new StreamWriter(_Destination); + tw.WriteLine("source_tex, atlas_tex, x, y, width, height"); + foreach (Atlas atlas in Atlasses) + { + string atlasName = String.Format(prefix + "{0:000}" + ".png", atlasCount); + //1: Save images + Image img = CreateAtlasImage(atlas); + img.Save(atlasName, System.Drawing.Imaging.ImageFormat.Png); + //2: save description in file + foreach (Node n in atlas.Nodes) + { + if (n.Texture != null) + { + tw.Write(n.Texture.Source + ", "); + tw.Write(atlasName + ", "); + tw.Write((n.Bounds.X).ToString() + ", "); + tw.Write((n.Bounds.Y).ToString() + ", "); + tw.Write((n.Bounds.Width).ToString() + ", "); + tw.WriteLine((n.Bounds.Height).ToString()); + } + } + ++atlasCount; + } + tw.Close(); + tw = new StreamWriter(prefix + ".log"); + tw.WriteLine("--- LOG -------------------------------------------"); + tw.WriteLine(Log.ToString()); + tw.WriteLine("--- ERROR -----------------------------------------"); + tw.WriteLine(Error.ToString()); + tw.Close(); + } + + private void ScanForTextures(string _Path, string _Wildcard) + { + DirectoryInfo di = new DirectoryInfo(_Path); + FileInfo[] files = di.GetFiles(_Wildcard, SearchOption.AllDirectories); + foreach (FileInfo fi in files) + { + Image img = Image.FromFile(fi.FullName); + if (img != null) + { + if (img.Width <= AtlasSize && img.Height <= AtlasSize) + { + TextureInfo ti = new TextureInfo(); + + ti.Source = fi.FullName; + ti.Width = img.Width; + ti.Height = img.Height; + + SourceTextures.Add(ti); + + Log.WriteLine("Added " + fi.FullName); + } + else + { + Error.WriteLine(fi.FullName + " is too large to fix in the atlas. Skipping!"); + } + } + } + } + + private void HorizontalSplit(Node _ToSplit, int _Width, int _Height, List _List) + { + Node n1 = new Node(); + n1.Bounds.X = _ToSplit.Bounds.X + _Width + Padding; + n1.Bounds.Y = _ToSplit.Bounds.Y; + n1.Bounds.Width = _ToSplit.Bounds.Width - _Width - Padding; + n1.Bounds.Height = _Height; + n1.SplitType = SplitType.Vertical; + Node n2 = new Node(); + n2.Bounds.X = _ToSplit.Bounds.X; + n2.Bounds.Y = _ToSplit.Bounds.Y + _Height + Padding; + n2.Bounds.Width = _ToSplit.Bounds.Width; + n2.Bounds.Height = _ToSplit.Bounds.Height - _Height - Padding; + n2.SplitType = SplitType.Horizontal; + if (n1.Bounds.Width > 0 && n1.Bounds.Height > 0) + _List.Add(n1); + if (n2.Bounds.Width > 0 && n2.Bounds.Height > 0) + _List.Add(n2); + } + + private void VerticalSplit(Node _ToSplit, int _Width, int _Height, List _List) + { + Node n1 = new Node(); + n1.Bounds.X = _ToSplit.Bounds.X + _Width + Padding; + n1.Bounds.Y = _ToSplit.Bounds.Y; + n1.Bounds.Width = _ToSplit.Bounds.Width - _Width - Padding; + n1.Bounds.Height = _ToSplit.Bounds.Height; + n1.SplitType = SplitType.Vertical; + Node n2 = new Node(); + n2.Bounds.X = _ToSplit.Bounds.X; + n2.Bounds.Y = _ToSplit.Bounds.Y + _Height + Padding; + n2.Bounds.Width = _Width; + n2.Bounds.Height = _ToSplit.Bounds.Height - _Height - Padding; + n2.SplitType = SplitType.Horizontal; + if (n1.Bounds.Width > 0 && n1.Bounds.Height > 0) + _List.Add(n1); + if (n2.Bounds.Width > 0 && n2.Bounds.Height > 0) + _List.Add(n2); + } + + private TextureInfo FindBestFitForNode(Node _Node, List _Textures) + { + TextureInfo bestFit = null; + float nodeArea = _Node.Bounds.Width * _Node.Bounds.Height; + float maxCriteria = 0.0f; + foreach (TextureInfo ti in _Textures) + { + switch (FitHeuristic) + { + // Max of Width and Height ratios + case BestFitHeuristic.MaxOneAxis: + if (ti.Width <= _Node.Bounds.Width && ti.Height <= _Node.Bounds.Height) + { + float wRatio = (float)ti.Width / (float)_Node.Bounds.Width; + float hRatio = (float)ti.Height / (float)_Node.Bounds.Height; + float ratio = wRatio > hRatio ? wRatio : hRatio; + if (ratio > maxCriteria) + { + maxCriteria = ratio; + bestFit = ti; + } + } + break; + // Maximize Area coverage + case BestFitHeuristic.Area: + if (ti.Width <= _Node.Bounds.Width && ti.Height <= _Node.Bounds.Height) + { + float textureArea = ti.Width * ti.Height; + float coverage = textureArea / nodeArea; + if (coverage > maxCriteria) + { + maxCriteria = coverage; + bestFit = ti; + } + } + break; + } + } + return bestFit; + } + + private List LayoutAtlas(List _Textures, Atlas _Atlas) + { + List freeList = new List(); + List textures = new List(); + _Atlas.Nodes = new List(); + textures = _Textures.ToList(); + Node root = new Node(); + root.Bounds.Size = new Size(_Atlas.Width, _Atlas.Height); + root.SplitType = SplitType.Horizontal; + freeList.Add(root); + while (freeList.Count > 0 && textures.Count > 0) + { + Node node = freeList[0]; + freeList.RemoveAt(0); + TextureInfo bestFit = FindBestFitForNode(node, textures); + if (bestFit != null) + { + if (node.SplitType == SplitType.Horizontal) + { + HorizontalSplit(node, bestFit.Width, bestFit.Height, freeList); + } + else + { + VerticalSplit(node, bestFit.Width, bestFit.Height, freeList); + } + node.Texture = bestFit; + node.Bounds.Width = bestFit.Width; + node.Bounds.Height = bestFit.Height; + textures.Remove(bestFit); + } + _Atlas.Nodes.Add(node); + } + return textures; + } + + private Image CreateAtlasImage(Atlas _Atlas) + { + Image img = new Bitmap(_Atlas.Width, _Atlas.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb); + Graphics g = Graphics.FromImage(img); + foreach (Node n in _Atlas.Nodes) + { + if (n.Texture != null) + { + Image sourceImg = Image.FromFile(n.Texture.Source); + g.DrawImage(sourceImg, n.Bounds); + } + } + // DPI FIX START + Bitmap ResolutionFix = new Bitmap(img); + ResolutionFix.SetResolution(96.0F, 96.0F); + Image img2 = ResolutionFix; + return img2; + // DPI FIX END + } } \ No newline at end of file diff --git a/UndertaleModTool/SampleScripts/FindAndReplace.csx b/UndertaleModTool/SampleScripts/FindAndReplace.csx index c4c297639..7e1cf2e69 100644 --- a/UndertaleModTool/SampleScripts/FindAndReplace.csx +++ b/UndertaleModTool/SampleScripts/FindAndReplace.csx @@ -22,25 +22,25 @@ if (!ScriptQuestion("This will make changes across all of the code! Are you sure { return; } -bool case_sensitive = ScriptQuestion("Case sensitive?"); -bool multiline = ScriptQuestion("Multi-line search?"); +bool case_sensitive = ScriptQuestion("Case sensitive?"); +bool multiline = ScriptQuestion("Multi-line search?"); bool isRegex = ScriptQuestion("Is regex search?"); String keyword = SimpleTextInput("Enter search terms", "Search box below", "", multiline); String replacement = SimpleTextInput("Enter replacement term", "Search box below", "", multiline); SetProgressBar(null, "Code Entries", 0, Data.Code.Count); -StartUpdater(); +StartProgressBarUpdater(); SyncBinding("Strings, Variables, Functions", true); await Task.Run(() => { foreach (UndertaleCode code in Data.Code) { ReplaceTextInGML(code.Name.Content, keyword, replacement, case_sensitive, isRegex); - IncProgress(); + IncrementProgress(); } }); -SyncBinding(false); +DisableAllSyncBindings(); -await StopUpdater(); +await StopProgressBarUpdater(); HideProgressBar(); ScriptMessage("Completed"); \ No newline at end of file diff --git a/UndertaleModTool/SampleScripts/Search.csx b/UndertaleModTool/SampleScripts/Search.csx index 5d6d050e8..1dca580f6 100644 --- a/UndertaleModTool/SampleScripts/Search.csx +++ b/UndertaleModTool/SampleScripts/Search.csx @@ -44,19 +44,19 @@ if (regex_check) } bool cacheGenerated = await GenerateGMLCache(DECOMPILE_CONTEXT); -await StopUpdater(); +await StopProgressBarUpdater(); SetProgressBar(null, "Code Entries", 0, Data.Code.Count); -StartUpdater(); +StartProgressBarUpdater(); await DumpCode(); -await StopUpdater(); +await StopProgressBarUpdater(); await Task.Run(SortResults); UpdateProgressStatus("Generating result list..."); -await ClickableTextOutput("Search results.", keyword, result_count, resultsSorted, true, failedSorted); +await ClickableSearchOutput("Search results.", keyword, result_count, resultsSorted, true, failedSorted); HideProgressBar(); EnableUI(); @@ -132,7 +132,7 @@ void DumpCode(UndertaleCode code) failedList.Add(code.Name.Content); } - IncProgressP(); + IncrementProgressParallel(); } void ScanCode(KeyValuePair code) { @@ -165,5 +165,5 @@ void ScanCode(KeyValuePair code) failedList.Add(code.Key); } - IncProgressP(); + IncrementProgressParallel(); } diff --git a/UndertaleModTool/SampleScripts/SearchASM.csx b/UndertaleModTool/SampleScripts/SearchASM.csx index 4d66b0963..239050cc6 100644 --- a/UndertaleModTool/SampleScripts/SearchASM.csx +++ b/UndertaleModTool/SampleScripts/SearchASM.csx @@ -41,16 +41,16 @@ if (regex_check) } SetProgressBar(null, "Code Entries", 0, Data.Code.Count); -StartUpdater(); +StartProgressBarUpdater(); await DumpCode(); -await StopUpdater(); +await StopProgressBarUpdater(); await Task.Run(SortResults); UpdateProgressStatus("Generating result list..."); -await ClickableTextOutput("Search results.", keyword, result_count, resultsSorted, false, failedSorted); +await ClickableSearchOutput("Search results.", keyword, result_count, resultsSorted, false, failedSorted); HideProgressBar(); EnableUI(); @@ -114,5 +114,5 @@ void DumpCode(UndertaleCode code) } } - IncProgressP(); + IncrementProgressParallel(); } diff --git a/UndertaleModTool/SampleScripts/SearchLimited.csx b/UndertaleModTool/SampleScripts/SearchLimited.csx index 23395effa..316a55917 100644 --- a/UndertaleModTool/SampleScripts/SearchLimited.csx +++ b/UndertaleModTool/SampleScripts/SearchLimited.csx @@ -105,7 +105,7 @@ for (var j = 0; j < gameObjectCandidates.Count; j++) } SetProgressBar(null, "Code Entries", 0, codeToDump.Count); -StartUpdater(); +StartProgressBarUpdater(); await Task.Run(() => { for (var j = 0; j < codeToDump.Count; j++) @@ -114,10 +114,10 @@ await Task.Run(() => { } }); -await StopUpdater(); +await StopProgressBarUpdater(); UpdateProgressStatus("Generating result list..."); -await ClickableTextOutput("Search results.", keyword, result_count, resultsDict, true, failedList); +await ClickableSearchOutput("Search results.", keyword, result_count, resultsDict, true, failedList); HideProgressBar(); EnableUI(); @@ -166,5 +166,5 @@ void DumpCode(UndertaleCode code) failedList.Add(code.Name.Content); } - IncProgress(); + IncrementProgress(); } \ No newline at end of file diff --git a/UndertaleModTool/ScriptingFunctions.cs b/UndertaleModTool/ScriptingFunctions.cs index deb165328..fc0b8f3bf 100644 --- a/UndertaleModTool/ScriptingFunctions.cs +++ b/UndertaleModTool/ScriptingFunctions.cs @@ -21,48 +21,6 @@ namespace UndertaleModTool // Adding misc. scripting functions here public partial class MainWindow : Window, INotifyPropertyChanged, IScriptInterface { - public bool SendAUMIMessage(IpcMessage_t ipMessage, ref IpcReply_t outReply) - { - // By Archie - const int ReplySize = 132; - - // Create the pipe - using var pPipeServer = new NamedPipeServerStream("AUMI-IPC", PipeDirection.InOut, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous); - - // Wait 1/8th of a second for AUMI to connect. - // If it doesn't connect in time (which it should), just return false to avoid a deadlock. - if (!pPipeServer.IsConnected) - { - pPipeServer.WaitForConnectionAsync(); - Thread.Sleep(125); - if (!pPipeServer.IsConnected) - { - pPipeServer.DisposeAsync(); - return false; - } - } - - try - { - //Send the message - pPipeServer.Write(ipMessage.RawBytes()); - pPipeServer.Flush(); - } - catch (Exception e) - { - // Catch any errors that might arise if the connection is broken - ScriptError("Could not write data to the pipe!\nError: " + e.Message); - return false; - } - - // Read the reply, the length of which is always a pre-set amount of bytes. - byte[] bBuffer = new byte[ReplySize]; - pPipeServer.Read(bBuffer, 0, ReplySize); - - outReply = IpcReply_t.FromBytes(bBuffer); - return true; - } - public bool RunUMTScript(string path) { // By Grossley diff --git a/UndertaleModTool/TechnicalScripts/ExportAllCode2_3.csx b/UndertaleModTool/TechnicalScripts/ExportAllCode2_3.csx index 65a810f77..bd9f016ec 100644 --- a/UndertaleModTool/TechnicalScripts/ExportAllCode2_3.csx +++ b/UndertaleModTool/TechnicalScripts/ExportAllCode2_3.csx @@ -1,79 +1,79 @@ -using System.Text; -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; - -EnsureDataLoaded(); - -bool is2_3 = false; -if (!((Data.GMS2_3 == false) && (Data.GMS2_3_1 == false) && (Data.GMS2_3_2 == false))) -{ - is2_3 = true; - ScriptMessage("This script is for GMS 2.3 games, because some code names get so long that Windows cannot write them adequately."); -} -else -{ - ScriptError("Use the regular ExportAllCode please!", "Incompatible"); -} - -string codeFolder = GetFolder(FilePath) + "Export_Code" + Path.DirectorySeparatorChar; -ThreadLocal DECOMPILE_CONTEXT = new ThreadLocal(() => new GlobalDecompileContext(Data, false)); - -if (Directory.Exists(codeFolder)) -{ - ScriptError("A code export already exists. Please remove it.", "Error"); - return; -} - -Directory.CreateDirectory(codeFolder); - -SetProgressBar(null, "Code Entries", 0, Data.Code.Count); -StartUpdater(); - -int failed = 0; -await Task.Run(DumpCode); - -await StopUpdater(); -HideProgressBar(); -ScriptMessage("Export Complete.\n\nLocation: " + codeFolder + " " + failed.ToString() + " failed"); - -string GetFolder(string path) -{ - return Path.GetDirectoryName(path) + Path.DirectorySeparatorChar; -} - -void DumpCode() -{ - //Because 2.3 code names get way too long, we're gonna convert it to an index based system, starting with a lookup system - string index_path = Path.Combine(codeFolder, "LookUpTable.txt"); - string index_text = "This is zero indexed, index 0 starts at line 2."; - for (var i = 0; i < Data.Code.Count; i++) - { - UndertaleCode code = Data.Code[i]; - index_text += "\n"; - index_text += code.Name.Content; - } - File.WriteAllText(index_path, index_text); - for (var i = 0; i < Data.Code.Count; i++) - { - UndertaleCode code = Data.Code[i]; - string path = Path.Combine(codeFolder, i.ToString() + ".gml"); - try - { - File.WriteAllText(path, (code != null ? Decompiler.Decompile(code, DECOMPILE_CONTEXT.Value) : "")); - } - catch (Exception e) - { - if (!(Directory.Exists(codeFolder + "/Failed/"))) - { - Directory.CreateDirectory(codeFolder + "/Failed/"); - } - path = Path.Combine(codeFolder + "/Failed/", i.ToString() + ".gml"); - File.WriteAllText(path, "/*\nDECOMPILER FAILED!\n\n" + e.ToString() + "\n*/"); - failed += 1; - } - - IncProgress(); - } -} +using System.Text; +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +EnsureDataLoaded(); + +bool is2_3 = false; +if (!((Data.GMS2_3 == false) && (Data.GMS2_3_1 == false) && (Data.GMS2_3_2 == false))) +{ + is2_3 = true; + ScriptMessage("This script is for GMS 2.3 games, because some code names get so long that Windows cannot write them adequately."); +} +else +{ + ScriptError("Use the regular ExportAllCode please!", "Incompatible"); +} + +string codeFolder = GetFolder(FilePath) + "Export_Code" + Path.DirectorySeparatorChar; +ThreadLocal DECOMPILE_CONTEXT = new ThreadLocal(() => new GlobalDecompileContext(Data, false)); + +if (Directory.Exists(codeFolder)) +{ + ScriptError("A code export already exists. Please remove it.", "Error"); + return; +} + +Directory.CreateDirectory(codeFolder); + +SetProgressBar(null, "Code Entries", 0, Data.Code.Count); +StartProgressBarUpdater(); + +int failed = 0; +await Task.Run(DumpCode); + +await StopProgressBarUpdater(); +HideProgressBar(); +ScriptMessage("Export Complete.\n\nLocation: " + codeFolder + " " + failed.ToString() + " failed"); + +string GetFolder(string path) +{ + return Path.GetDirectoryName(path) + Path.DirectorySeparatorChar; +} + +void DumpCode() +{ + //Because 2.3 code names get way too long, we're gonna convert it to an index based system, starting with a lookup system + string index_path = Path.Combine(codeFolder, "LookUpTable.txt"); + string index_text = "This is zero indexed, index 0 starts at line 2."; + for (var i = 0; i < Data.Code.Count; i++) + { + UndertaleCode code = Data.Code[i]; + index_text += "\n"; + index_text += code.Name.Content; + } + File.WriteAllText(index_path, index_text); + for (var i = 0; i < Data.Code.Count; i++) + { + UndertaleCode code = Data.Code[i]; + string path = Path.Combine(codeFolder, i.ToString() + ".gml"); + try + { + File.WriteAllText(path, (code != null ? Decompiler.Decompile(code, DECOMPILE_CONTEXT.Value) : "")); + } + catch (Exception e) + { + if (!(Directory.Exists(codeFolder + "/Failed/"))) + { + Directory.CreateDirectory(codeFolder + "/Failed/"); + } + path = Path.Combine(codeFolder + "/Failed/", i.ToString() + ".gml"); + File.WriteAllText(path, "/*\nDECOMPILER FAILED!\n\n" + e.ToString() + "\n*/"); + failed += 1; + } + + IncrementProgress(); + } +} diff --git a/UndertaleModTool/TechnicalScripts/ExportAllCodeSync.csx b/UndertaleModTool/TechnicalScripts/ExportAllCodeSync.csx index f09e32c49..c7b6f5a86 100644 --- a/UndertaleModTool/TechnicalScripts/ExportAllCodeSync.csx +++ b/UndertaleModTool/TechnicalScripts/ExportAllCodeSync.csx @@ -1,83 +1,83 @@ -using System.Text; -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; - -EnsureDataLoaded(); - -string codeFolder = GetFolder(FilePath) + "Export_Code" + Path.DirectorySeparatorChar; -ThreadLocal DECOMPILE_CONTEXT = new ThreadLocal(() => new GlobalDecompileContext(Data, false)); - -if (Directory.Exists(codeFolder)) -{ - codeFolder = GetFolder(FilePath) + "Export_Code_2" + Path.DirectorySeparatorChar; -} - -Directory.CreateDirectory(codeFolder); - -SetProgressBar(null, "Code Entries", 0, Data.Code.Count); -StartUpdater(); - -int failed = 0; -await Task.Run(DumpCode); - -await StopUpdater(); -HideProgressBar(); -ScriptMessage("Export Complete.\n\nLocation: " + codeFolder + " " + failed.ToString() + " failed"); - -string GetFolder(string path) -{ - return Path.GetDirectoryName(path) + Path.DirectorySeparatorChar; -} - - -void DumpCode() -{ - foreach(UndertaleCode code in Data.Code) - { - string path = Path.Combine(codeFolder, code.Name.Content + ".gml"); - if (code.ParentEntry == null) - { - try - { - File.WriteAllText(path, (code != null ? Decompiler.Decompile(code, DECOMPILE_CONTEXT.Value) : "")); - } - catch (Exception e) - { - if (!(Directory.Exists(codeFolder + "/Failed/"))) - { - Directory.CreateDirectory(codeFolder + "/Failed/"); - } - path = Path.Combine(codeFolder + "/Failed/", code.Name.Content + ".gml"); - File.WriteAllText(path, "/*\nDECOMPILER FAILED!\n\n" + e.ToString() + "\n*/"); - failed += 1; - } - } - else - { - if (!(Directory.Exists(codeFolder + "/Duplicates/"))) - { - Directory.CreateDirectory(codeFolder + "/Duplicates/"); - } - try - { - path = Path.Combine(codeFolder + "/Duplicates/", code.Name.Content + ".gml"); - File.WriteAllText(path, (code != null ? Decompiler.Decompile(code, DECOMPILE_CONTEXT.Value) : "")); - } - catch (Exception e) - { - if (!(Directory.Exists(codeFolder + "/Duplicates/Failed/"))) - { - Directory.CreateDirectory(codeFolder + "/Duplicates/Failed/"); - } - path = Path.Combine(codeFolder + "/Duplicates/Failed/", code.Name.Content + ".gml"); - File.WriteAllText(path, "/*\nDECOMPILER FAILED!\n\n" + e.ToString() + "\n*/"); - failed += 1; - } - } - - IncProgress(); - } -} - +using System.Text; +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +EnsureDataLoaded(); + +string codeFolder = GetFolder(FilePath) + "Export_Code" + Path.DirectorySeparatorChar; +ThreadLocal DECOMPILE_CONTEXT = new ThreadLocal(() => new GlobalDecompileContext(Data, false)); + +if (Directory.Exists(codeFolder)) +{ + codeFolder = GetFolder(FilePath) + "Export_Code_2" + Path.DirectorySeparatorChar; +} + +Directory.CreateDirectory(codeFolder); + +SetProgressBar(null, "Code Entries", 0, Data.Code.Count); +StartProgressBarUpdater(); + +int failed = 0; +await Task.Run(DumpCode); + +await StopProgressBarUpdater(); +HideProgressBar(); +ScriptMessage("Export Complete.\n\nLocation: " + codeFolder + " " + failed.ToString() + " failed"); + +string GetFolder(string path) +{ + return Path.GetDirectoryName(path) + Path.DirectorySeparatorChar; +} + + +void DumpCode() +{ + foreach(UndertaleCode code in Data.Code) + { + string path = Path.Combine(codeFolder, code.Name.Content + ".gml"); + if (code.ParentEntry == null) + { + try + { + File.WriteAllText(path, (code != null ? Decompiler.Decompile(code, DECOMPILE_CONTEXT.Value) : "")); + } + catch (Exception e) + { + if (!(Directory.Exists(codeFolder + "/Failed/"))) + { + Directory.CreateDirectory(codeFolder + "/Failed/"); + } + path = Path.Combine(codeFolder + "/Failed/", code.Name.Content + ".gml"); + File.WriteAllText(path, "/*\nDECOMPILER FAILED!\n\n" + e.ToString() + "\n*/"); + failed += 1; + } + } + else + { + if (!(Directory.Exists(codeFolder + "/Duplicates/"))) + { + Directory.CreateDirectory(codeFolder + "/Duplicates/"); + } + try + { + path = Path.Combine(codeFolder + "/Duplicates/", code.Name.Content + ".gml"); + File.WriteAllText(path, (code != null ? Decompiler.Decompile(code, DECOMPILE_CONTEXT.Value) : "")); + } + catch (Exception e) + { + if (!(Directory.Exists(codeFolder + "/Duplicates/Failed/"))) + { + Directory.CreateDirectory(codeFolder + "/Duplicates/Failed/"); + } + path = Path.Combine(codeFolder + "/Duplicates/Failed/", code.Name.Content + ".gml"); + File.WriteAllText(path, "/*\nDECOMPILER FAILED!\n\n" + e.ToString() + "\n*/"); + failed += 1; + } + } + + IncrementProgress(); + } +} + diff --git a/UndertaleModTool/TechnicalScripts/ExportAndConvert_2_3_ASM.csx b/UndertaleModTool/TechnicalScripts/ExportAndConvert_2_3_ASM.csx index 1421a0301..e5c312d69 100644 --- a/UndertaleModTool/TechnicalScripts/ExportAndConvert_2_3_ASM.csx +++ b/UndertaleModTool/TechnicalScripts/ExportAndConvert_2_3_ASM.csx @@ -1,163 +1,163 @@ -using System.Text; -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; - -EnsureDataLoaded(); - -if (Data.ToolInfo.ProfileMode) -{ - ScriptMessage("This script is incompatible with profile mode."); - return; -} - -string codeFolder = GetFolder(FilePath) + "Export_Assembly2" + Path.DirectorySeparatorChar; -ThreadLocal DECOMPILE_CONTEXT = new ThreadLocal(() => new GlobalDecompileContext(Data, false)); - -if (Directory.Exists(codeFolder)) -{ - codeFolder = GetFolder(FilePath) + "Export_Assembly2_2" + Path.DirectorySeparatorChar; -} - -Directory.CreateDirectory(codeFolder); - -SetProgressBar(null, "Code Entries", 0, Data.Code.Count); -StartUpdater(); - -SyncBinding("Strings, CodeLocals, Variables, Functions", true); -await Task.Run(DumpCode); -SyncBinding(false); - -await StopUpdater(); -HideProgressBar(); -ScriptMessage("Conversion Complete.\n\nLocation: " + codeFolder); - -string GetFolder(string path) -{ - return Path.GetDirectoryName(path) + Path.DirectorySeparatorChar; -} - -string ReplaceFirst(string text, string search, string replace) -{ - int pos = text.IndexOf(search); - if (pos < 0) - { - return text; - } - return text.Substring(0, pos) + replace + text.Substring(pos + search.Length); -} - -void DumpCode() -{ - foreach (UndertaleCode code_orig in Data.Code) - { - code_orig.Offset = 0; - if (Data.CodeLocals.ByName(code_orig.Name.Content) == null) - { - UndertaleCodeLocals locals = new UndertaleCodeLocals(); - locals.Name = code_orig.Name; - uint codeLocalsCount = 0; - string equivalentGlobalScript = (code_orig.Name.Content).Replace("gml_Script_", "gml_GlobalScript_"); - if (Data.CodeLocals.ByName(equivalentGlobalScript) != null) - { - foreach (UndertaleCodeLocals.LocalVar localvar in Data.CodeLocals.ByName(equivalentGlobalScript).Locals) - { - UndertaleCodeLocals.LocalVar argsLocal = new UndertaleCodeLocals.LocalVar(); - argsLocal.Name = localvar.Name; - //(localvar.Name != null ? localvar.Name : Data.Strings.MakeString("arguments")); - argsLocal.Index = localvar.Index; - //localvar.Name = Data.Strings.MakeString("arguments"); - //localvar.Index = 0; - locals.Locals.Add(argsLocal); - codeLocalsCount += 1; - } - code_orig.LocalsCount = codeLocalsCount; - code_orig.GenerateLocalVarDefinitions(code_orig.FindReferencedLocalVars(), locals); // Dunno if we actually need this line, but it seems to work? - code_orig.ParentEntry = null; - } - else - { - UndertaleCodeLocals.LocalVar argsLocal = new UndertaleCodeLocals.LocalVar(); - argsLocal.Name = Data.Strings.MakeString("arguments"); - argsLocal.Index = 0; - locals.Locals.Add(argsLocal); - code_orig.LocalsCount = 1; - code_orig.GenerateLocalVarDefinitions(code_orig.FindReferencedLocalVars(), locals); // Dunno if we actually need this line, but it seems to work? - } - Data.CodeLocals.Add(locals); - } - string path = Path.Combine(codeFolder, code_orig.Name.Content + ".asm"); - if (code_orig.ParentEntry == null) - { - string x = ""; - try - { - string disasm_code = code_orig.Disassemble(Data.Variables, Data.CodeLocals.For(code_orig)); - //ScriptMessage(code_orig.Name.Content); - //ScriptMessage("1 " + disasm_code); - int ix = -1; - if (code_orig.Instructions.Count > 0 && code_orig.Instructions[0].Kind == UndertaleInstruction.Opcode.B) - ix = disasm_code.IndexOf("b ["); - string code = ""; - if (ix != -1) - { - code = disasm_code.Substring(ix + 2, disasm_code.IndexOf(']', ix) - (ix + 1)); - //ScriptMessage("2 " + code); - //Console.WriteLine(code); - string toBeSearched2 = ReplaceFirst(disasm_code, "b " + code, ""); - //ScriptMessage("3 " + toBeSearched2); - //Console.WriteLine(toBeSearched2); - ix = toBeSearched2.IndexOf(":" + code); - x = ""; - if (ix != -1) - { - code = toBeSearched2.Substring(ix, toBeSearched2.Length - (ix)); - //Console.WriteLine(code); - x = toBeSearched2.Replace(code, ""); - //ScriptMessage("4 " + x); - //Console.WriteLine(x); - } - code_orig.Replace(Assembler.Assemble(x, Data)); - } - string str_path_to_use = Path.Combine(codeFolder, code_orig.Name.Content + ".asm"); - string code_output = ""; - if (code_orig != null) - code_output = code_orig.Disassemble(Data.Variables, Data.CodeLocals.For(code_orig)); - File.WriteAllText(str_path_to_use, code_output); - } - catch (Exception e) - { - ScriptMessage("Error " + code_orig.Name.Content + ": " + e.ToString()); - SetUMTConsoleText(x); - SetFinishedMessage(false); - return; - } - - IncProgress(); - } - else - { - if (!(Directory.Exists(Path.Combine(codeFolder, "Duplicates")))) - { - Directory.CreateDirectory(Path.Combine(codeFolder, "Duplicates")); - } - try - { - string str_path_to_use = Path.Combine(codeFolder, "Duplicates", code_orig.Name.Content + ".asm"); - string code_output = ""; - if (code_orig != null) - code_output = code_orig.Disassemble(Data.Variables, Data.CodeLocals.For(code_orig)); - File.WriteAllText(str_path_to_use, code_output); - } - catch (Exception e) - { - string str_path_to_use = Path.Combine(codeFolder, "Duplicates", code_orig.Name.Content + ".asm"); - File.WriteAllText(str_path_to_use, "/*\nDISASSEMBLY FAILED!\n\n" + e.ToString() + "\n*/"); // Please don't - } - - IncProgress(); - } - } -} - +using System.Text; +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +EnsureDataLoaded(); + +if (Data.ToolInfo.ProfileMode) +{ + ScriptMessage("This script is incompatible with profile mode."); + return; +} + +string codeFolder = GetFolder(FilePath) + "Export_Assembly2" + Path.DirectorySeparatorChar; +ThreadLocal DECOMPILE_CONTEXT = new ThreadLocal(() => new GlobalDecompileContext(Data, false)); + +if (Directory.Exists(codeFolder)) +{ + codeFolder = GetFolder(FilePath) + "Export_Assembly2_2" + Path.DirectorySeparatorChar; +} + +Directory.CreateDirectory(codeFolder); + +SetProgressBar(null, "Code Entries", 0, Data.Code.Count); +StartProgressBarUpdater(); + +SyncBinding("Strings, CodeLocals, Variables, Functions", true); +await Task.Run(DumpCode); +DisableAllSyncBindings(); + +await StopProgressBarUpdater(); +HideProgressBar(); +ScriptMessage("Conversion Complete.\n\nLocation: " + codeFolder); + +string GetFolder(string path) +{ + return Path.GetDirectoryName(path) + Path.DirectorySeparatorChar; +} + +string ReplaceFirst(string text, string search, string replace) +{ + int pos = text.IndexOf(search); + if (pos < 0) + { + return text; + } + return text.Substring(0, pos) + replace + text.Substring(pos + search.Length); +} + +void DumpCode() +{ + foreach (UndertaleCode code_orig in Data.Code) + { + code_orig.Offset = 0; + if (Data.CodeLocals.ByName(code_orig.Name.Content) == null) + { + UndertaleCodeLocals locals = new UndertaleCodeLocals(); + locals.Name = code_orig.Name; + uint codeLocalsCount = 0; + string equivalentGlobalScript = (code_orig.Name.Content).Replace("gml_Script_", "gml_GlobalScript_"); + if (Data.CodeLocals.ByName(equivalentGlobalScript) != null) + { + foreach (UndertaleCodeLocals.LocalVar localvar in Data.CodeLocals.ByName(equivalentGlobalScript).Locals) + { + UndertaleCodeLocals.LocalVar argsLocal = new UndertaleCodeLocals.LocalVar(); + argsLocal.Name = localvar.Name; + //(localvar.Name != null ? localvar.Name : Data.Strings.MakeString("arguments")); + argsLocal.Index = localvar.Index; + //localvar.Name = Data.Strings.MakeString("arguments"); + //localvar.Index = 0; + locals.Locals.Add(argsLocal); + codeLocalsCount += 1; + } + code_orig.LocalsCount = codeLocalsCount; + code_orig.GenerateLocalVarDefinitions(code_orig.FindReferencedLocalVars(), locals); // Dunno if we actually need this line, but it seems to work? + code_orig.ParentEntry = null; + } + else + { + UndertaleCodeLocals.LocalVar argsLocal = new UndertaleCodeLocals.LocalVar(); + argsLocal.Name = Data.Strings.MakeString("arguments"); + argsLocal.Index = 0; + locals.Locals.Add(argsLocal); + code_orig.LocalsCount = 1; + code_orig.GenerateLocalVarDefinitions(code_orig.FindReferencedLocalVars(), locals); // Dunno if we actually need this line, but it seems to work? + } + Data.CodeLocals.Add(locals); + } + string path = Path.Combine(codeFolder, code_orig.Name.Content + ".asm"); + if (code_orig.ParentEntry == null) + { + string x = ""; + try + { + string disasm_code = code_orig.Disassemble(Data.Variables, Data.CodeLocals.For(code_orig)); + //ScriptMessage(code_orig.Name.Content); + //ScriptMessage("1 " + disasm_code); + int ix = -1; + if (code_orig.Instructions.Count > 0 && code_orig.Instructions[0].Kind == UndertaleInstruction.Opcode.B) + ix = disasm_code.IndexOf("b ["); + string code = ""; + if (ix != -1) + { + code = disasm_code.Substring(ix + 2, disasm_code.IndexOf(']', ix) - (ix + 1)); + //ScriptMessage("2 " + code); + //Console.WriteLine(code); + string toBeSearched2 = ReplaceFirst(disasm_code, "b " + code, ""); + //ScriptMessage("3 " + toBeSearched2); + //Console.WriteLine(toBeSearched2); + ix = toBeSearched2.IndexOf(":" + code); + x = ""; + if (ix != -1) + { + code = toBeSearched2.Substring(ix, toBeSearched2.Length - (ix)); + //Console.WriteLine(code); + x = toBeSearched2.Replace(code, ""); + //ScriptMessage("4 " + x); + //Console.WriteLine(x); + } + code_orig.Replace(Assembler.Assemble(x, Data)); + } + string str_path_to_use = Path.Combine(codeFolder, code_orig.Name.Content + ".asm"); + string code_output = ""; + if (code_orig != null) + code_output = code_orig.Disassemble(Data.Variables, Data.CodeLocals.For(code_orig)); + File.WriteAllText(str_path_to_use, code_output); + } + catch (Exception e) + { + ScriptMessage("Error " + code_orig.Name.Content + ": " + e.ToString()); + SetUMTConsoleText(x); + SetFinishedMessage(false); + return; + } + + IncrementProgress(); + } + else + { + if (!(Directory.Exists(Path.Combine(codeFolder, "Duplicates")))) + { + Directory.CreateDirectory(Path.Combine(codeFolder, "Duplicates")); + } + try + { + string str_path_to_use = Path.Combine(codeFolder, "Duplicates", code_orig.Name.Content + ".asm"); + string code_output = ""; + if (code_orig != null) + code_output = code_orig.Disassemble(Data.Variables, Data.CodeLocals.For(code_orig)); + File.WriteAllText(str_path_to_use, code_output); + } + catch (Exception e) + { + string str_path_to_use = Path.Combine(codeFolder, "Duplicates", code_orig.Name.Content + ".asm"); + File.WriteAllText(str_path_to_use, "/*\nDISASSEMBLY FAILED!\n\n" + e.ToString() + "\n*/"); // Please don't + } + + IncrementProgress(); + } + } +} + diff --git a/UndertaleModTool/TechnicalScripts/FindUnknownFunctions.csx b/UndertaleModTool/TechnicalScripts/FindUnknownFunctions.csx index 3f5f4b6e6..0ad3bf740 100644 --- a/UndertaleModTool/TechnicalScripts/FindUnknownFunctions.csx +++ b/UndertaleModTool/TechnicalScripts/FindUnknownFunctions.csx @@ -1,120 +1,119 @@ -//Adapted from original script by Grossley -using System.Text; -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using UndertaleModLib.Util; - -EnsureDataLoaded(); - -string exportFolder = PromptChooseDirectory("Export to where"); -if (exportFolder == null) - throw new ScriptException("The export folder was not set."); - -//Overwrite Check One -if (File.Exists(exportFolder + "unknown_functions.txt")) -{ - bool overwriteCheckOne = ScriptQuestion(@"A 'unknown_functions.txt' file already exists. -Would you like to overwrite it?"); - if (overwriteCheckOne) - { - File.Delete(exportFolder + "unknown_functions.txt"); - } - else - { - ScriptError("A 'unknown_functions.txt' file already exists. Please remove it and try again.", "Error: Export already exists."); - return; - } -} - -BuiltinList list = new BuiltinList(); -list.Initialize(Data); - -List extensionFunctions = new List(); -List unknownFunctions = new List(); -List unknownFunctions2 = new List(); -foreach (UndertaleExtension extension in Data.Extensions) -{ - foreach (UndertaleExtensionFile exFile in extension.Files) - { - foreach (UndertaleExtensionFunction exFunc in exFile.Functions) - { - extensionFunctions.Add(exFunc.Name.Content); - } - } -} - -using (StreamWriter writer = new StreamWriter(exportFolder + "unknown_functions.txt")) -{ - foreach (var func in Data.Functions) - { - if (func.Name.Content.Contains("\n") || func.Name.Content.Contains("\r")) - { - continue; - } - if ((Data.Scripts.ByName(func.Name.Content) != null) || (Data.Code.ByName(func.Name.Content) != null)) - { - continue; - } - if (list.Functions.ContainsKey(func.Name.Content)) - { - continue; - } - bool continue_var = false; - for (var i = 0; i < extensionFunctions.Count; i++) - { - if (extensionFunctions[i] == func.Name.Content) - { - continue_var = true; - } - } - if (continue_var) - { - continue; - } - unknownFunctions.Add(func.Name.Content); - writer.WriteLine(func.Name.Content); - } -} - -if (unknownFunctions.Count > 0) -{ - if (ScriptQuestion("'unknown_functions.txt' generated. Remove unknown functions now?")) - { - string removed = ""; - string resultsToDisplay = ""; - for (var i = 0; i < unknownFunctions.Count; i++) - { - resultsToDisplay += (unknownFunctions[i] + "\r\n"); - } - resultsToDisplay = SimpleTextInput("Prune Menu", "Delete one or more lines to remove those entries", resultsToDisplay, true); - string[] IndividualLineArray = resultsToDisplay.Split('\n', StringSplitOptions.RemoveEmptyEntries); - foreach (var OneLine in IndividualLineArray) - { - unknownFunctions2.Add(OneLine.Trim()); - } - for (var i = 0; i < unknownFunctions.Count; i++) - { - bool exists = false; - for (var j = 0; j < unknownFunctions2.Count; j++) - { - if (unknownFunctions[i] == unknownFunctions2[j]) - { - exists = true; - } - } - if (!exists) - { - removed += (unknownFunctions[i] + "\r\n"); - Data.Functions.Remove(Data.Functions.ByName(unknownFunctions[i])); - } - } - if (removed.Length < 1) - removed = "No functions "; - else - removed = "The function(s)\r\n" + removed; - ScriptMessage(removed + "were removed."); - } -} - +//Adapted from original script by Grossley +using System.Text; +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using UndertaleModLib.Util; + +EnsureDataLoaded(); + +string exportFolder = PromptChooseDirectory(); +if (exportFolder == null) + throw new ScriptException("The export folder was not set."); + +//Overwrite Check One +if (File.Exists(exportFolder + "unknown_functions.txt")) +{ + bool overwriteCheckOne = ScriptQuestion(@"A 'unknown_functions.txt' file already exists. +Would you like to overwrite it?"); + if (overwriteCheckOne) + { + File.Delete(exportFolder + "unknown_functions.txt"); + } + else + { + ScriptError("A 'unknown_functions.txt' file already exists. Please remove it and try again.", "Error: Export already exists."); + return; + } +} + +BuiltinList list = new BuiltinList(); +list.Initialize(Data); + +List extensionFunctions = new List(); +List unknownFunctions = new List(); +List unknownFunctions2 = new List(); +foreach (UndertaleExtension extension in Data.Extensions) +{ + foreach (UndertaleExtensionFile exFile in extension.Files) + { + foreach (UndertaleExtensionFunction exFunc in exFile.Functions) + { + extensionFunctions.Add(exFunc.Name.Content); + } + } +} + +using (StreamWriter writer = new StreamWriter(exportFolder + "unknown_functions.txt")) +{ + foreach (var func in Data.Functions) + { + if (func.Name.Content.Contains("\n") || func.Name.Content.Contains("\r")) + { + continue; + } + if ((Data.Scripts.ByName(func.Name.Content) != null) || (Data.Code.ByName(func.Name.Content) != null)) + { + continue; + } + if (list.Functions.ContainsKey(func.Name.Content)) + { + continue; + } + bool continue_var = false; + for (var i = 0; i < extensionFunctions.Count; i++) + { + if (extensionFunctions[i] == func.Name.Content) + { + continue_var = true; + } + } + if (continue_var) + { + continue; + } + unknownFunctions.Add(func.Name.Content); + writer.WriteLine(func.Name.Content); + } +} + +if (unknownFunctions.Count > 0) +{ + if (ScriptQuestion("'unknown_functions.txt' generated. Remove unknown functions now?")) + { + string removed = ""; + string resultsToDisplay = ""; + for (var i = 0; i < unknownFunctions.Count; i++) + { + resultsToDisplay += (unknownFunctions[i] + "\r\n"); + } + resultsToDisplay = SimpleTextInput("Prune Menu", "Delete one or more lines to remove those entries", resultsToDisplay, true); + string[] IndividualLineArray = resultsToDisplay.Split('\n'}, StringSplitOptions.RemoveEmptyEntries); + foreach (var OneLine in IndividualLineArray) + { + unknownFunctions2.Add(OneLine.Trim()); + } + for (var i = 0; i < unknownFunctions.Count; i++) + { + bool exists = false; + for (var j = 0; j < unknownFunctions2.Count; j++) + { + if (unknownFunctions[i] == unknownFunctions2[j]) + { + exists = true; + } + } + if (!exists) + { + removed += (unknownFunctions[i] + "\r\n"); + Data.Functions.Remove(Data.Functions.ByName(unknownFunctions[i])); + } + } + if (removed.Length < 1) + removed = "No functions "; + else + removed = "The function(s)\r\n" + removed; + ScriptMessage(removed + "were removed."); + } +} \ No newline at end of file diff --git a/UndertaleModTool/TechnicalScripts/FindUnusedStrings.csx b/UndertaleModTool/TechnicalScripts/FindUnusedStrings.csx index c584d4060..e5c3fc489 100644 --- a/UndertaleModTool/TechnicalScripts/FindUnusedStrings.csx +++ b/UndertaleModTool/TechnicalScripts/FindUnusedStrings.csx @@ -1,646 +1,646 @@ -// Made by Grossley -using System.Text; -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using UndertaleModLib.Util; - -EnsureDataLoaded(); - -string output = ""; -int progress = 0; -bool clearStrings = ScriptQuestion("Clear unused strings?"); -string exportFolder = PromptChooseDirectory("Write unused strings log file to"); -if (exportFolder == null) -{ - ScriptError("The location of the unused strings log file was not set."); - return; -} - -//Overwrite Check One -if (File.Exists(Path.Combine(exportFolder, "unused_strings_log.txt"))) -{ - bool overwriteCheckOne = ScriptQuestion(@"An 'unused_strings_log.txt' file already exists. -Would you like to overwrite it?"); - if (overwriteCheckOne) - { - File.Delete(exportFolder + "unused_strings_log.txt"); - } - else - { - ScriptError("An 'unused_strings_log.txt' file already exists. Please remove it and try again."); - return; - } -} - -RemoveUnusedVariFunc(); - -int stringsCount = Data.Strings.Count; -uint[] stringsUsageCountArray = new uint[stringsCount]; -bool[] stringsUsageMap = new bool[stringsCount]; - -stringsUsageCountArray = GetStringUsageCount(); - -progress = 0; -int found_count = 0; -int removed_count = 0; -for (var i = (stringsUsageCountArray.Length - 1); i >= 0; i--) -{ - if ((i % 1000) == 999) - { - UpdateProgress("Generating output", stringsCount); - progress += 999; - } - if (stringsUsageCountArray[i] == 0) - { - stringsUsageMap[i] = false; - output += ("Data.Strings[" + i.ToString() + "] Exists = " + stringsUsageMap[i].ToString() + ";\r\n" + "Data.Strings[" + i.ToString() + "] UsageCount = " + stringsUsageCountArray[i].ToString() + "; // Data.Strings[" + i.ToString() + "].Content = \"" + Data.Strings[i].Content + "\";\r\n"); - found_count += 1; - if (clearStrings) - { - Data.Strings.Remove(Data.Strings[i]); - removed_count += 1; - } - } -} -File.WriteAllText(Path.Combine(exportFolder, "unused_strings_log.txt"), output); -HideProgressBar(); -ScriptMessage("Complete. " + found_count.ToString() + " unused strings were found and " + removed_count.ToString() + " were removed. Further details have been written to the log at " + Path.Combine(exportFolder, "unused_strings_log.txt")); -return; - -uint[] GetStringUsageCount() -{ - for (var i = 0; i < stringsCount; i++) - { - stringsUsageCountArray[i] = 0; - } - for (var i = 0; i < stringsCount; i++) - { - stringsUsageMap[i] = true; - } - UpdateProgress("Checking strings in AnimationCurves"); - if (Data.AnimationCurves != null) - { - foreach (UndertaleAnimationCurve obj in Data.AnimationCurves) - { - if (obj.Name != null) - { - stringsUsageCountArray[Data.Strings.IndexOf(obj.Name)] += 1; - } - foreach (UndertaleAnimationCurve.Channel chan in obj.Channels) - { - if (chan.Name != null) - { - stringsUsageCountArray[Data.Strings.IndexOf(chan.Name)] += 1; - } - } - } - } - UpdateProgress("Checking strings in AudioGroups"); - if (Data.AudioGroups != null) - { - foreach (UndertaleAudioGroup obj in Data.AudioGroups) - { - if (obj.Name != null) - { - stringsUsageCountArray[Data.Strings.IndexOf(obj.Name)] += 1; - } - } - } - UpdateProgress("Checking strings in Backgrounds"); - if (Data.Backgrounds != null) - { - foreach (UndertaleBackground obj in Data.Backgrounds) - { - if (obj.Name != null) - { - stringsUsageCountArray[Data.Strings.IndexOf(obj.Name)] += 1; - } - } - } - UpdateProgress("Checking strings in Code"); - if (Data.Code != null) - { - foreach (UndertaleCode obj in Data.Code) - { - if (obj.Name != null) - { - stringsUsageCountArray[Data.Strings.IndexOf(obj.Name)] += 1; - } - foreach (UndertaleInstruction Instruction in obj.Instructions) - { - if ((int)Instruction.Kind == 0xC0 && (int)Instruction.Type1 == 6) - { - if (((UndertaleResourceById)Instruction.Value).Resource != null) - { - if ((((UndertaleResourceById)Instruction.Value).Resource) != null) - stringsUsageCountArray[Data.Strings.IndexOf(((UndertaleResourceById)Instruction.Value).Resource)] += 1; - } - } - } - } - } - UpdateProgress("Checking strings in Code Locals"); - if (Data.CodeLocals != null) - { - foreach (UndertaleCodeLocals obj in Data.CodeLocals) - { - if (obj.Name != null) - { - stringsUsageCountArray[Data.Strings.IndexOf(obj.Name)] += 1; - } - foreach (UndertaleCodeLocals.LocalVar locvar in obj.Locals) - { - if (locvar.Name != null) - { - stringsUsageCountArray[Data.Strings.IndexOf(locvar.Name)] += 1; - } - } - } - } - UpdateProgress("Checking strings in Extensions"); - if (Data.Extensions != null) - { - foreach (UndertaleExtension obj in Data.Extensions) - { - if (obj.Name != null) - { - stringsUsageCountArray[Data.Strings.IndexOf(obj.Name)] += 1; - } - if (obj.FolderName != null) - { - stringsUsageCountArray[Data.Strings.IndexOf(obj.FolderName)] += 1; - } - if (obj.Name != null) - { - stringsUsageCountArray[Data.Strings.IndexOf(obj.Name)] += 1; - } - if (obj.ClassName != null) - { - stringsUsageCountArray[Data.Strings.IndexOf(obj.ClassName)] += 1; - } - foreach (UndertaleExtensionFile exFile in obj.Files) - { - if (exFile.Filename != null) - { - stringsUsageCountArray[Data.Strings.IndexOf(exFile.Filename)] += 1; - } - if (exFile.CleanupScript != null) - { - stringsUsageCountArray[Data.Strings.IndexOf(exFile.CleanupScript)] += 1; - } - if (exFile.InitScript != null) - { - stringsUsageCountArray[Data.Strings.IndexOf(exFile.InitScript)] += 1; - } - foreach (UndertaleExtensionFunction exFunc in exFile.Functions) - { - if (exFunc.Name != null) - { - stringsUsageCountArray[Data.Strings.IndexOf(exFunc.Name)] += 1; - } - if (exFunc.ExtName != null) - { - stringsUsageCountArray[Data.Strings.IndexOf(exFunc.ExtName)] += 1; - } - } - } - } - } - UpdateProgress("Checking strings in Fonts"); - if (Data.Fonts != null) - { - foreach (UndertaleFont obj in Data.Fonts) - { - if (obj.Name != null) - { - stringsUsageCountArray[Data.Strings.IndexOf(obj.Name)] += 1; - } - if (obj.DisplayName != null) - { - stringsUsageCountArray[Data.Strings.IndexOf(obj.DisplayName)] += 1; - } - } - } - UpdateProgress("Checking strings in Functions"); - if (Data.Functions != null) - { - foreach (UndertaleFunction obj in Data.Functions) - { - if (obj.Name != null) - { - stringsUsageCountArray[Data.Strings.IndexOf(obj.Name)] += 1; - } - } - } - UpdateProgress("Checking strings in GameObjects"); - if (Data.GameObjects != null) - { - foreach (UndertaleGameObject obj in Data.GameObjects) - { - if (obj.Name != null) - { - stringsUsageCountArray[Data.Strings.IndexOf(obj.Name)] += 1; - } - try - { - for (var i = 0; i < obj.Events.Count; i++) - { - foreach (UndertaleGameObject.Event evnt in obj.Events[i]) - { - foreach (UndertaleGameObject.EventAction action in evnt.Actions) - { - if (action.ActionName != null) - { - stringsUsageCountArray[Data.Strings.IndexOf(action.ActionName)] += 1; - } - } - } - } - } - catch - { - // Something went wrong, but probably because it's trying to check something non-existent - // Just keep going - } - } - } - UpdateProgress("Checking strings in GeneralInfo"); - if (Data.GeneralInfo != null) - { - if (Data.GeneralInfo.Filename != null) - { - stringsUsageCountArray[Data.Strings.IndexOf(Data.GeneralInfo.Filename)] += 1; - } - if (Data.GeneralInfo.Config != null) - { - stringsUsageCountArray[Data.Strings.IndexOf(Data.GeneralInfo.Config)] += 1; - } - if (Data.GeneralInfo.Name != null) - { - stringsUsageCountArray[Data.Strings.IndexOf(Data.GeneralInfo.Name)] += 1; - } - if (Data.GeneralInfo.DisplayName != null) - { - stringsUsageCountArray[Data.Strings.IndexOf(Data.GeneralInfo.DisplayName)] += 1; - } - } - UpdateProgress("Checking strings in LANG"); - if (Data.FORM.LANG != null) - { - for (var i = 0; i < Data.FORM.LANG.Object.EntryIDs.Count; i++) - { - if (Data.FORM.LANG.Object.EntryIDs[i] != null) - { - stringsUsageCountArray[Data.Strings.IndexOf(Data.FORM.LANG.Object.EntryIDs[i])] += 1; - } - } - for (var i = 0; i < Data.FORM.LANG.Object.Languages.Count; i++) - { - if (Data.FORM.LANG.Object.Languages[i].Name != null) - { - stringsUsageCountArray[Data.Strings.IndexOf(Data.FORM.LANG.Object.Languages[i].Name)] += 1; - } - if (Data.FORM.LANG.Object.Languages[i].Region != null) - { - stringsUsageCountArray[Data.Strings.IndexOf(Data.FORM.LANG.Object.Languages[i].Region)] += 1; - } - } - } - UpdateProgress("Checking strings in Options"); - if (Data.Options != null) - { - foreach (UndertaleOptions.Constant constant in Data.Options.Constants) - { - if (constant.Name != null) - { - stringsUsageCountArray[Data.Strings.IndexOf(constant.Name)] += 1; - } - if (constant.Value != null) - { - stringsUsageCountArray[Data.Strings.IndexOf(constant.Value)] += 1; - } - } - } - UpdateProgress("Checking strings in Paths"); - if (Data.Paths != null) - { - foreach (UndertalePath obj in Data.Paths) - { - if (obj.Name != null) - { - stringsUsageCountArray[Data.Strings.IndexOf(obj.Name)] += 1; - } - } - } - UpdateProgress("Checking strings in Rooms"); - if (Data.Rooms != null) - { - foreach (UndertaleRoom obj in Data.Rooms) - { - if (obj.Name != null) - { - stringsUsageCountArray[Data.Strings.IndexOf(obj.Name)] += 1; - } - if (obj.Caption != null) - { - if (obj.Caption != null) - { - stringsUsageCountArray[Data.Strings.IndexOf(obj.Caption)] += 1; - } - } - for (var i = 0; i < obj.Layers.Count; i++) - { - if (obj.Layers[i].LayerName != null) - { - stringsUsageCountArray[Data.Strings.IndexOf(obj.Layers[i].LayerName)] += 1; - } - if (obj.Layers[i].AssetsData != null) - { - if (obj.Layers[i].AssetsData.Sprites != null) - { - for (var j = 0; j < obj.Layers[i].AssetsData.Sprites.Count; j++) - { - if (obj.Layers[i].AssetsData.Sprites[j].Name != null) - { - stringsUsageCountArray[Data.Strings.IndexOf(obj.Layers[i].AssetsData.Sprites[j].Name)] += 1; - } - } - } - if (obj.Layers[i].AssetsData.Sequences != null) - { - for (var j = 0; j < obj.Layers[i].AssetsData.Sequences.Count; j++) - { - if (obj.Layers[i].AssetsData.Sequences[j].Name != null) - { - stringsUsageCountArray[Data.Strings.IndexOf(obj.Layers[i].AssetsData.Sequences[j].Name)] += 1; - } - } - } - if (obj.Layers[i].AssetsData.NineSlices != null) - { - for (var j = 0; j < obj.Layers[i].AssetsData.NineSlices.Count; j++) - { - if (obj.Layers[i].AssetsData.NineSlices[j].Name != null) - { - stringsUsageCountArray[Data.Strings.IndexOf(obj.Layers[i].AssetsData.NineSlices[j].Name)] += 1; - } - } - } - } - } - } - } - UpdateProgress("Checking strings in Scripts"); - if (Data.Scripts != null) - { - foreach (UndertaleScript obj in Data.Scripts) - { - if (obj.Name != null) - { - stringsUsageCountArray[Data.Strings.IndexOf(obj.Name)] += 1; - } - } - } - UpdateProgress("Checking strings in Sequences"); - if (Data.Sequences != null) - { - foreach (UndertaleSequence obj in Data.Sequences) - { - if ((obj as UndertaleSequence).Name != null) - { - stringsUsageCountArray[Data.Strings.IndexOf((obj as UndertaleSequence).Name)] += 1; - } - for (var i = 0; i < obj.Moments.Count; i++) - { - for (var j = 0; j < obj.Moments[i].Channels.Count; j++) - { - if (obj.Moments[i].Channels[j].Event != null) - { - stringsUsageCountArray[Data.Strings.IndexOf(obj.Moments[i].Channels[j].Event)] += 1; - } - } - } - for (var i = 0; i < obj.Tracks.Count; i++) - { - obj.Tracks[i] = RecurseTracks(obj.Tracks[i]); - } - } - } - UpdateProgress("Checking strings in Shaders"); - if (Data.Shaders != null) - { - foreach (UndertaleShader obj in Data.Shaders) - { - if (obj.Name != null) - { - stringsUsageCountArray[Data.Strings.IndexOf(obj.Name)] += 1; - } - if (obj.GLSL_ES_Fragment != null) - { - stringsUsageCountArray[Data.Strings.IndexOf(obj.GLSL_ES_Fragment)] += 1; - } - if (obj.GLSL_ES_Vertex != null) - { - stringsUsageCountArray[Data.Strings.IndexOf(obj.GLSL_ES_Vertex)] += 1; - } - if (obj.GLSL_Fragment != null) - { - stringsUsageCountArray[Data.Strings.IndexOf(obj.GLSL_Fragment)] += 1; - } - if (obj.GLSL_Vertex != null) - { - stringsUsageCountArray[Data.Strings.IndexOf(obj.GLSL_Vertex)] += 1; - } - if (obj.HLSL9_Fragment != null) - { - stringsUsageCountArray[Data.Strings.IndexOf(obj.HLSL9_Fragment)] += 1; - } - if (obj.HLSL9_Vertex != null) - { - stringsUsageCountArray[Data.Strings.IndexOf(obj.HLSL9_Vertex)] += 1; - } - for (var i = 0; i < obj.VertexShaderAttributes.Count; i++) - { - if (obj.VertexShaderAttributes[i].Name != null) - { - stringsUsageCountArray[Data.Strings.IndexOf(obj.VertexShaderAttributes[i].Name)] += 1; - } - } - } - } - UpdateProgress("Checking strings in Sounds"); - if (Data.Sounds != null) - { - foreach (UndertaleSound obj in Data.Sounds) - { - if (obj.Name != null) - { - stringsUsageCountArray[Data.Strings.IndexOf(obj.Name)] += 1; - } - if (obj.Type != null) - { - stringsUsageCountArray[Data.Strings.IndexOf(obj.Type)] += 1; - } - if (obj.File != null) - { - stringsUsageCountArray[Data.Strings.IndexOf(obj.File)] += 1; - } - } - } - UpdateProgress("Checking strings in Sprites"); - if (Data.Sprites != null) - { - foreach (UndertaleSprite obj in Data.Sprites) - { - if (obj.Name != null) - { - stringsUsageCountArray[Data.Strings.IndexOf(obj.Name)] += 1; - } - } - } - UpdateProgress("Checking strings in Texture Group Info"); - if (Data.TextureGroupInfo != null) - { - foreach (UndertaleTextureGroupInfo obj in Data.TextureGroupInfo) - { - if (obj.Name != null) - { - stringsUsageCountArray[Data.Strings.IndexOf(obj.Name)] += 1; - } - } - } - UpdateProgress("Checking strings in Timelines"); - if (Data.Timelines != null) - { - foreach (UndertaleTimeline obj in Data.Timelines) - { - if (obj.Name != null) - { - stringsUsageCountArray[Data.Strings.IndexOf(obj.Name)] += 1; - } - } - } - UpdateProgress("Checking strings in Variables"); - if (Data.Variables != null) - { - foreach (UndertaleVariable obj in Data.Variables) - { - if (obj.Name != null) - { - stringsUsageCountArray[Data.Strings.IndexOf(obj.Name)] += 1; - } - } - } - return stringsUsageCountArray; -} -UndertaleSequence.Track RecurseTracks(UndertaleSequence.Track trackRecurse) -{ - if (trackRecurse.ModelName != null) - { - stringsUsageCountArray[Data.Strings.IndexOf(trackRecurse.ModelName)] += 1; - } - if (trackRecurse.Name != null) - { - stringsUsageCountArray[Data.Strings.IndexOf(trackRecurse.Name)] += 1; - } - if (trackRecurse.GMAnimCurveString != null) - { - stringsUsageCountArray[Data.Strings.IndexOf(trackRecurse.GMAnimCurveString)] += 1; - } - if ((trackRecurse.ModelName.Content) == "GMStringTrack") - { - for (var j = 0; j < (trackRecurse.Keyframes as UndertaleSequence.StringKeyframes).List.Count; j++) - { - for (var k = 0; k < (trackRecurse.Keyframes as UndertaleSequence.StringKeyframes).List[j].Channels.Count; k++) - { - if ((trackRecurse.Keyframes as UndertaleSequence.StringKeyframes).List[j].Channels[k].Value != null) - { - stringsUsageCountArray[Data.Strings.IndexOf((trackRecurse.Keyframes as UndertaleSequence.StringKeyframes).List[j].Channels[k].Value)] += 1; - } - } - } - } - for (var j = 0; j < trackRecurse.Tracks.Count; j++) - { - trackRecurse.Tracks[j] = RecurseTracks(trackRecurse.Tracks[j]); - } - UpdateProgress("RecurseTracks"); - return trackRecurse; -} -void RemoveUnusedVariFunc() -{ - Dictionary> references_vari = CollectReferencesVar(); - Dictionary> references_func = CollectReferencesFunc(); - uint test_variable = 0; - uint test_func = 0; - for (var i = 0; i < Data.Variables.Count; i++) - { - UndertaleVariable vari = Data.Variables[i]; - test_variable = references_vari.ContainsKey(vari) ? (uint)references_vari[vari].Count : 0; - if ((test_variable == 0) && (vari.Name.Content != "arguments") && (vari.Name.Content != "prototype") && (vari.Name.Content != "@@array@@")) - { - output += "Data.Variables[" + i.ToString() + "].Occurrences = " + test_variable.ToString() + ";" + " // Data.Variables[" + i.ToString() + "].Name.Content = \"" + vari.Name.Content + "\";" + "\r\n"; - Data.Variables.Remove(vari); - } - } - UpdateProgress("Removing Unused Variables"); - for (var i = 0; i < Data.Functions.Count; i++) - { - UndertaleFunction func = Data.Functions[i]; - test_func = references_func.ContainsKey(func) ? (uint)references_func[func].Count : 0; - if (test_func == 0) - { - output += "Data.Functions[" + i.ToString() + "].Occurrences = " + test_func.ToString() + ";" + " // Data.Functions[" + i.ToString() + "].Name.Content = \"" + func.Name.Content + "\";" + "\r\n"; - Data.Functions.Remove(func); - } - } - UpdateProgress("Removing Unused Functions"); -} -Dictionary> CollectReferencesVar() -{ - Dictionary> list = new Dictionary>(); - UpdateProgress("Searching For Unused Variables"); - foreach (UndertaleCode code in Data.Code) - { - if (code.Offset != 0) // GMS 2.3, skip duplicates - continue; - foreach (UndertaleInstruction instr in code.Instructions) - { - UndertaleVariable obj = instr.GetReference()?.Target; - if (obj != null) - { - if (!list.ContainsKey(obj)) - list.Add(obj, new List()); - list[obj].Add(instr); - } - } - } - return list; -} -Dictionary> CollectReferencesFunc() -{ - Dictionary> list = new Dictionary>(); - UpdateProgress("Searching For Unused Functions"); - foreach (UndertaleCode code in Data.Code) - { - if (code.Offset != 0) // GMS 2.3, skip duplicates - continue; - foreach (UndertaleInstruction instr in code.Instructions) - { - UndertaleFunction obj = instr.GetReference()?.Target; - if (obj != null) - { - if (!list.ContainsKey(obj)) - list.Add(obj, new List()); - list[obj].Add(instr); - } - } - } - return list; -} -void UpdateProgress(string name, int limit = 1) -{ - UpdateProgressBar(null, name, progress++, limit); -} +// Made by Grossley +using System.Text; +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using UndertaleModLib.Util; + +EnsureDataLoaded(); + +string output = ""; +int progress = 0; +bool clearStrings = ScriptQuestion("Clear unused strings?"); +string exportFolder = PromptChooseDirectory(); +if (exportFolder == null) +{ + ScriptError("The location of the unused strings log file was not set."); + return; +} + +//Overwrite Check One +if (File.Exists(Path.Combine(exportFolder, "unused_strings_log.txt"))) +{ + bool overwriteCheckOne = ScriptQuestion(@"An 'unused_strings_log.txt' file already exists. +Would you like to overwrite it?"); + if (overwriteCheckOne) + { + File.Delete(exportFolder + "unused_strings_log.txt"); + } + else + { + ScriptError("An 'unused_strings_log.txt' file already exists. Please remove it and try again."); + return; + } +} + +RemoveUnusedVariFunc(); + +int stringsCount = Data.Strings.Count; +uint[] stringsUsageCountArray = new uint[stringsCount]; +bool[] stringsUsageMap = new bool[stringsCount]; + +stringsUsageCountArray = GetStringUsageCount(); + +progress = 0; +int found_count = 0; +int removed_count = 0; +for (var i = (stringsUsageCountArray.Length - 1); i >= 0; i--) +{ + if ((i % 1000) == 999) + { + UpdateProgress("Generating output", stringsCount); + progress += 999; + } + if (stringsUsageCountArray[i] == 0) + { + stringsUsageMap[i] = false; + output += ("Data.Strings[" + i.ToString() + "] Exists = " + stringsUsageMap[i].ToString() + ";\r\n" + "Data.Strings[" + i.ToString() + "] UsageCount = " + stringsUsageCountArray[i].ToString() + "; // Data.Strings[" + i.ToString() + "].Content = \"" + Data.Strings[i].Content + "\";\r\n"); + found_count += 1; + if (clearStrings) + { + Data.Strings.Remove(Data.Strings[i]); + removed_count += 1; + } + } +} +File.WriteAllText(Path.Combine(exportFolder, "unused_strings_log.txt"), output); +HideProgressBar(); +ScriptMessage("Complete. " + found_count.ToString() + " unused strings were found and " + removed_count.ToString() + " were removed. Further details have been written to the log at " + Path.Combine(exportFolder, "unused_strings_log.txt")); +return; + +uint[] GetStringUsageCount() +{ + for (var i = 0; i < stringsCount; i++) + { + stringsUsageCountArray[i] = 0; + } + for (var i = 0; i < stringsCount; i++) + { + stringsUsageMap[i] = true; + } + UpdateProgress("Checking strings in AnimationCurves"); + if (Data.AnimationCurves != null) + { + foreach (UndertaleAnimationCurve obj in Data.AnimationCurves) + { + if (obj.Name != null) + { + stringsUsageCountArray[Data.Strings.IndexOf(obj.Name)] += 1; + } + foreach (UndertaleAnimationCurve.Channel chan in obj.Channels) + { + if (chan.Name != null) + { + stringsUsageCountArray[Data.Strings.IndexOf(chan.Name)] += 1; + } + } + } + } + UpdateProgress("Checking strings in AudioGroups"); + if (Data.AudioGroups != null) + { + foreach (UndertaleAudioGroup obj in Data.AudioGroups) + { + if (obj.Name != null) + { + stringsUsageCountArray[Data.Strings.IndexOf(obj.Name)] += 1; + } + } + } + UpdateProgress("Checking strings in Backgrounds"); + if (Data.Backgrounds != null) + { + foreach (UndertaleBackground obj in Data.Backgrounds) + { + if (obj.Name != null) + { + stringsUsageCountArray[Data.Strings.IndexOf(obj.Name)] += 1; + } + } + } + UpdateProgress("Checking strings in Code"); + if (Data.Code != null) + { + foreach (UndertaleCode obj in Data.Code) + { + if (obj.Name != null) + { + stringsUsageCountArray[Data.Strings.IndexOf(obj.Name)] += 1; + } + foreach (UndertaleInstruction Instruction in obj.Instructions) + { + if ((int)Instruction.Kind == 0xC0 && (int)Instruction.Type1 == 6) + { + if (((UndertaleResourceById)Instruction.Value).Resource != null) + { + if ((((UndertaleResourceById)Instruction.Value).Resource) != null) + stringsUsageCountArray[Data.Strings.IndexOf(((UndertaleResourceById)Instruction.Value).Resource)] += 1; + } + } + } + } + } + UpdateProgress("Checking strings in Code Locals"); + if (Data.CodeLocals != null) + { + foreach (UndertaleCodeLocals obj in Data.CodeLocals) + { + if (obj.Name != null) + { + stringsUsageCountArray[Data.Strings.IndexOf(obj.Name)] += 1; + } + foreach (UndertaleCodeLocals.LocalVar locvar in obj.Locals) + { + if (locvar.Name != null) + { + stringsUsageCountArray[Data.Strings.IndexOf(locvar.Name)] += 1; + } + } + } + } + UpdateProgress("Checking strings in Extensions"); + if (Data.Extensions != null) + { + foreach (UndertaleExtension obj in Data.Extensions) + { + if (obj.Name != null) + { + stringsUsageCountArray[Data.Strings.IndexOf(obj.Name)] += 1; + } + if (obj.FolderName != null) + { + stringsUsageCountArray[Data.Strings.IndexOf(obj.FolderName)] += 1; + } + if (obj.Name != null) + { + stringsUsageCountArray[Data.Strings.IndexOf(obj.Name)] += 1; + } + if (obj.ClassName != null) + { + stringsUsageCountArray[Data.Strings.IndexOf(obj.ClassName)] += 1; + } + foreach (UndertaleExtensionFile exFile in obj.Files) + { + if (exFile.Filename != null) + { + stringsUsageCountArray[Data.Strings.IndexOf(exFile.Filename)] += 1; + } + if (exFile.CleanupScript != null) + { + stringsUsageCountArray[Data.Strings.IndexOf(exFile.CleanupScript)] += 1; + } + if (exFile.InitScript != null) + { + stringsUsageCountArray[Data.Strings.IndexOf(exFile.InitScript)] += 1; + } + foreach (UndertaleExtensionFunction exFunc in exFile.Functions) + { + if (exFunc.Name != null) + { + stringsUsageCountArray[Data.Strings.IndexOf(exFunc.Name)] += 1; + } + if (exFunc.ExtName != null) + { + stringsUsageCountArray[Data.Strings.IndexOf(exFunc.ExtName)] += 1; + } + } + } + } + } + UpdateProgress("Checking strings in Fonts"); + if (Data.Fonts != null) + { + foreach (UndertaleFont obj in Data.Fonts) + { + if (obj.Name != null) + { + stringsUsageCountArray[Data.Strings.IndexOf(obj.Name)] += 1; + } + if (obj.DisplayName != null) + { + stringsUsageCountArray[Data.Strings.IndexOf(obj.DisplayName)] += 1; + } + } + } + UpdateProgress("Checking strings in Functions"); + if (Data.Functions != null) + { + foreach (UndertaleFunction obj in Data.Functions) + { + if (obj.Name != null) + { + stringsUsageCountArray[Data.Strings.IndexOf(obj.Name)] += 1; + } + } + } + UpdateProgress("Checking strings in GameObjects"); + if (Data.GameObjects != null) + { + foreach (UndertaleGameObject obj in Data.GameObjects) + { + if (obj.Name != null) + { + stringsUsageCountArray[Data.Strings.IndexOf(obj.Name)] += 1; + } + try + { + for (var i = 0; i < obj.Events.Count; i++) + { + foreach (UndertaleGameObject.Event evnt in obj.Events[i]) + { + foreach (UndertaleGameObject.EventAction action in evnt.Actions) + { + if (action.ActionName != null) + { + stringsUsageCountArray[Data.Strings.IndexOf(action.ActionName)] += 1; + } + } + } + } + } + catch + { + // Something went wrong, but probably because it's trying to check something non-existent + // Just keep going + } + } + } + UpdateProgress("Checking strings in GeneralInfo"); + if (Data.GeneralInfo != null) + { + if (Data.GeneralInfo.Filename != null) + { + stringsUsageCountArray[Data.Strings.IndexOf(Data.GeneralInfo.Filename)] += 1; + } + if (Data.GeneralInfo.Config != null) + { + stringsUsageCountArray[Data.Strings.IndexOf(Data.GeneralInfo.Config)] += 1; + } + if (Data.GeneralInfo.Name != null) + { + stringsUsageCountArray[Data.Strings.IndexOf(Data.GeneralInfo.Name)] += 1; + } + if (Data.GeneralInfo.DisplayName != null) + { + stringsUsageCountArray[Data.Strings.IndexOf(Data.GeneralInfo.DisplayName)] += 1; + } + } + UpdateProgress("Checking strings in LANG"); + if (Data.FORM.LANG != null) + { + for (var i = 0; i < Data.FORM.LANG.Object.EntryIDs.Count; i++) + { + if (Data.FORM.LANG.Object.EntryIDs[i] != null) + { + stringsUsageCountArray[Data.Strings.IndexOf(Data.FORM.LANG.Object.EntryIDs[i])] += 1; + } + } + for (var i = 0; i < Data.FORM.LANG.Object.Languages.Count; i++) + { + if (Data.FORM.LANG.Object.Languages[i].Name != null) + { + stringsUsageCountArray[Data.Strings.IndexOf(Data.FORM.LANG.Object.Languages[i].Name)] += 1; + } + if (Data.FORM.LANG.Object.Languages[i].Region != null) + { + stringsUsageCountArray[Data.Strings.IndexOf(Data.FORM.LANG.Object.Languages[i].Region)] += 1; + } + } + } + UpdateProgress("Checking strings in Options"); + if (Data.Options != null) + { + foreach (UndertaleOptions.Constant constant in Data.Options.Constants) + { + if (constant.Name != null) + { + stringsUsageCountArray[Data.Strings.IndexOf(constant.Name)] += 1; + } + if (constant.Value != null) + { + stringsUsageCountArray[Data.Strings.IndexOf(constant.Value)] += 1; + } + } + } + UpdateProgress("Checking strings in Paths"); + if (Data.Paths != null) + { + foreach (UndertalePath obj in Data.Paths) + { + if (obj.Name != null) + { + stringsUsageCountArray[Data.Strings.IndexOf(obj.Name)] += 1; + } + } + } + UpdateProgress("Checking strings in Rooms"); + if (Data.Rooms != null) + { + foreach (UndertaleRoom obj in Data.Rooms) + { + if (obj.Name != null) + { + stringsUsageCountArray[Data.Strings.IndexOf(obj.Name)] += 1; + } + if (obj.Caption != null) + { + if (obj.Caption != null) + { + stringsUsageCountArray[Data.Strings.IndexOf(obj.Caption)] += 1; + } + } + for (var i = 0; i < obj.Layers.Count; i++) + { + if (obj.Layers[i].LayerName != null) + { + stringsUsageCountArray[Data.Strings.IndexOf(obj.Layers[i].LayerName)] += 1; + } + if (obj.Layers[i].AssetsData != null) + { + if (obj.Layers[i].AssetsData.Sprites != null) + { + for (var j = 0; j < obj.Layers[i].AssetsData.Sprites.Count; j++) + { + if (obj.Layers[i].AssetsData.Sprites[j].Name != null) + { + stringsUsageCountArray[Data.Strings.IndexOf(obj.Layers[i].AssetsData.Sprites[j].Name)] += 1; + } + } + } + if (obj.Layers[i].AssetsData.Sequences != null) + { + for (var j = 0; j < obj.Layers[i].AssetsData.Sequences.Count; j++) + { + if (obj.Layers[i].AssetsData.Sequences[j].Name != null) + { + stringsUsageCountArray[Data.Strings.IndexOf(obj.Layers[i].AssetsData.Sequences[j].Name)] += 1; + } + } + } + if (obj.Layers[i].AssetsData.NineSlices != null) + { + for (var j = 0; j < obj.Layers[i].AssetsData.NineSlices.Count; j++) + { + if (obj.Layers[i].AssetsData.NineSlices[j].Name != null) + { + stringsUsageCountArray[Data.Strings.IndexOf(obj.Layers[i].AssetsData.NineSlices[j].Name)] += 1; + } + } + } + } + } + } + } + UpdateProgress("Checking strings in Scripts"); + if (Data.Scripts != null) + { + foreach (UndertaleScript obj in Data.Scripts) + { + if (obj.Name != null) + { + stringsUsageCountArray[Data.Strings.IndexOf(obj.Name)] += 1; + } + } + } + UpdateProgress("Checking strings in Sequences"); + if (Data.Sequences != null) + { + foreach (UndertaleSequence obj in Data.Sequences) + { + if ((obj as UndertaleSequence).Name != null) + { + stringsUsageCountArray[Data.Strings.IndexOf((obj as UndertaleSequence).Name)] += 1; + } + for (var i = 0; i < obj.Moments.Count; i++) + { + for (var j = 0; j < obj.Moments[i].Channels.Count; j++) + { + if (obj.Moments[i].Channels[j].Event != null) + { + stringsUsageCountArray[Data.Strings.IndexOf(obj.Moments[i].Channels[j].Event)] += 1; + } + } + } + for (var i = 0; i < obj.Tracks.Count; i++) + { + obj.Tracks[i] = RecurseTracks(obj.Tracks[i]); + } + } + } + UpdateProgress("Checking strings in Shaders"); + if (Data.Shaders != null) + { + foreach (UndertaleShader obj in Data.Shaders) + { + if (obj.Name != null) + { + stringsUsageCountArray[Data.Strings.IndexOf(obj.Name)] += 1; + } + if (obj.GLSL_ES_Fragment != null) + { + stringsUsageCountArray[Data.Strings.IndexOf(obj.GLSL_ES_Fragment)] += 1; + } + if (obj.GLSL_ES_Vertex != null) + { + stringsUsageCountArray[Data.Strings.IndexOf(obj.GLSL_ES_Vertex)] += 1; + } + if (obj.GLSL_Fragment != null) + { + stringsUsageCountArray[Data.Strings.IndexOf(obj.GLSL_Fragment)] += 1; + } + if (obj.GLSL_Vertex != null) + { + stringsUsageCountArray[Data.Strings.IndexOf(obj.GLSL_Vertex)] += 1; + } + if (obj.HLSL9_Fragment != null) + { + stringsUsageCountArray[Data.Strings.IndexOf(obj.HLSL9_Fragment)] += 1; + } + if (obj.HLSL9_Vertex != null) + { + stringsUsageCountArray[Data.Strings.IndexOf(obj.HLSL9_Vertex)] += 1; + } + for (var i = 0; i < obj.VertexShaderAttributes.Count; i++) + { + if (obj.VertexShaderAttributes[i].Name != null) + { + stringsUsageCountArray[Data.Strings.IndexOf(obj.VertexShaderAttributes[i].Name)] += 1; + } + } + } + } + UpdateProgress("Checking strings in Sounds"); + if (Data.Sounds != null) + { + foreach (UndertaleSound obj in Data.Sounds) + { + if (obj.Name != null) + { + stringsUsageCountArray[Data.Strings.IndexOf(obj.Name)] += 1; + } + if (obj.Type != null) + { + stringsUsageCountArray[Data.Strings.IndexOf(obj.Type)] += 1; + } + if (obj.File != null) + { + stringsUsageCountArray[Data.Strings.IndexOf(obj.File)] += 1; + } + } + } + UpdateProgress("Checking strings in Sprites"); + if (Data.Sprites != null) + { + foreach (UndertaleSprite obj in Data.Sprites) + { + if (obj.Name != null) + { + stringsUsageCountArray[Data.Strings.IndexOf(obj.Name)] += 1; + } + } + } + UpdateProgress("Checking strings in Texture Group Info"); + if (Data.TextureGroupInfo != null) + { + foreach (UndertaleTextureGroupInfo obj in Data.TextureGroupInfo) + { + if (obj.Name != null) + { + stringsUsageCountArray[Data.Strings.IndexOf(obj.Name)] += 1; + } + } + } + UpdateProgress("Checking strings in Timelines"); + if (Data.Timelines != null) + { + foreach (UndertaleTimeline obj in Data.Timelines) + { + if (obj.Name != null) + { + stringsUsageCountArray[Data.Strings.IndexOf(obj.Name)] += 1; + } + } + } + UpdateProgress("Checking strings in Variables"); + if (Data.Variables != null) + { + foreach (UndertaleVariable obj in Data.Variables) + { + if (obj.Name != null) + { + stringsUsageCountArray[Data.Strings.IndexOf(obj.Name)] += 1; + } + } + } + return stringsUsageCountArray; +} +UndertaleSequence.Track RecurseTracks(UndertaleSequence.Track trackRecurse) +{ + if (trackRecurse.ModelName != null) + { + stringsUsageCountArray[Data.Strings.IndexOf(trackRecurse.ModelName)] += 1; + } + if (trackRecurse.Name != null) + { + stringsUsageCountArray[Data.Strings.IndexOf(trackRecurse.Name)] += 1; + } + if (trackRecurse.GMAnimCurveString != null) + { + stringsUsageCountArray[Data.Strings.IndexOf(trackRecurse.GMAnimCurveString)] += 1; + } + if ((trackRecurse.ModelName.Content) == "GMStringTrack") + { + for (var j = 0; j < (trackRecurse.Keyframes as UndertaleSequence.StringKeyframes).List.Count; j++) + { + for (var k = 0; k < (trackRecurse.Keyframes as UndertaleSequence.StringKeyframes).List[j].Channels.Count; k++) + { + if ((trackRecurse.Keyframes as UndertaleSequence.StringKeyframes).List[j].Channels[k].Value != null) + { + stringsUsageCountArray[Data.Strings.IndexOf((trackRecurse.Keyframes as UndertaleSequence.StringKeyframes).List[j].Channels[k].Value)] += 1; + } + } + } + } + for (var j = 0; j < trackRecurse.Tracks.Count; j++) + { + trackRecurse.Tracks[j] = RecurseTracks(trackRecurse.Tracks[j]); + } + UpdateProgress("RecurseTracks"); + return trackRecurse; +} +void RemoveUnusedVariFunc() +{ + Dictionary> references_vari = CollectReferencesVar(); + Dictionary> references_func = CollectReferencesFunc(); + uint test_variable = 0; + uint test_func = 0; + for (var i = 0; i < Data.Variables.Count; i++) + { + UndertaleVariable vari = Data.Variables[i]; + test_variable = references_vari.ContainsKey(vari) ? (uint)references_vari[vari].Count : 0; + if ((test_variable == 0) && (vari.Name.Content != "arguments") && (vari.Name.Content != "prototype") && (vari.Name.Content != "@@array@@")) + { + output += "Data.Variables[" + i.ToString() + "].Occurrences = " + test_variable.ToString() + ";" + " // Data.Variables[" + i.ToString() + "].Name.Content = \"" + vari.Name.Content + "\";" + "\r\n"; + Data.Variables.Remove(vari); + } + } + UpdateProgress("Removing Unused Variables"); + for (var i = 0; i < Data.Functions.Count; i++) + { + UndertaleFunction func = Data.Functions[i]; + test_func = references_func.ContainsKey(func) ? (uint)references_func[func].Count : 0; + if (test_func == 0) + { + output += "Data.Functions[" + i.ToString() + "].Occurrences = " + test_func.ToString() + ";" + " // Data.Functions[" + i.ToString() + "].Name.Content = \"" + func.Name.Content + "\";" + "\r\n"; + Data.Functions.Remove(func); + } + } + UpdateProgress("Removing Unused Functions"); +} +Dictionary> CollectReferencesVar() +{ + Dictionary> list = new Dictionary>(); + UpdateProgress("Searching For Unused Variables"); + foreach (UndertaleCode code in Data.Code) + { + if (code.Offset != 0) // GMS 2.3, skip duplicates + continue; + foreach (UndertaleInstruction instr in code.Instructions) + { + UndertaleVariable obj = instr.GetReference()?.Target; + if (obj != null) + { + if (!list.ContainsKey(obj)) + list.Add(obj, new List()); + list[obj].Add(instr); + } + } + } + return list; +} +Dictionary> CollectReferencesFunc() +{ + Dictionary> list = new Dictionary>(); + UpdateProgress("Searching For Unused Functions"); + foreach (UndertaleCode code in Data.Code) + { + if (code.Offset != 0) // GMS 2.3, skip duplicates + continue; + foreach (UndertaleInstruction instr in code.Instructions) + { + UndertaleFunction obj = instr.GetReference()?.Target; + if (obj != null) + { + if (!list.ContainsKey(obj)) + list.Add(obj, new List()); + list[obj].Add(instr); + } + } + } + return list; +} +void UpdateProgress(string name, int limit = 1) +{ + UpdateProgressBar(null, name, progress++, limit); +} diff --git a/UndertaleModTool/TechnicalScripts/ImportGraphics_Full_Repack.csx b/UndertaleModTool/TechnicalScripts/ImportGraphics_Full_Repack.csx index 7ce561fca..b3519fa30 100644 --- a/UndertaleModTool/TechnicalScripts/ImportGraphics_Full_Repack.csx +++ b/UndertaleModTool/TechnicalScripts/ImportGraphics_Full_Repack.csx @@ -1,701 +1,701 @@ -// Texture packer by Samuel Roy -// Uses code from https://github.com/mfascia/TexturePacker -// Uses code from ExportAllTextures.csx - -using System; -using System.IO; -using System.Drawing; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using UndertaleModLib.Util; - -EnsureDataLoaded(); - -bool recursiveCheck = ScriptQuestion(@"This script requires will import all valid sprites from all subdirectories. -If you do not want this to occur, please click ""No"" to cancel the script. -Then make sure that the sprites you wish to import are in a separate directory with no subdirectories. -"); -if (!recursiveCheck) - throw new ScriptException("Script cancelled."); - -// Get import folder -string importFolder = PromptChooseDirectory("Import From Where"); -if (importFolder == null) - throw new ScriptException("The import folder was not set."); - -//Stop the script if there's missing sprite entries or w/e. -string[] dirFiles = Directory.GetFiles(importFolder, "*.png", SearchOption.AllDirectories); -foreach (string file in dirFiles) -{ - string FileNameWithExtension = Path.GetFileName(file); - string stripped = Path.GetFileNameWithoutExtension(file); - int lastUnderscore = stripped.LastIndexOf('_'); - string spriteName = ""; - try - { - spriteName = stripped.Substring(0, lastUnderscore); - } - catch - { - throw new ScriptException("Getting the sprite name of " + FileNameWithExtension + " failed."); - } - Int32 validFrameNumber = 0; - try - { - validFrameNumber = Int32.Parse(stripped.Substring(lastUnderscore + 1)); - } - catch - { - throw new ScriptException("The index of " + FileNameWithExtension + " could not be determined."); - } - int frame = 0; - try - { - frame = Int32.Parse(stripped.Substring(lastUnderscore + 1)); - } - catch - { - throw new ScriptException(FileNameWithExtension + " is using letters instead of numbers. The script has stopped for your own protection."); - } - int prevframe = 0; - if (frame != 0) - { - prevframe = (frame - 1); - } - if (frame < 0) - { - throw new ScriptException(spriteName + " is using an invalid numbering scheme. The script has stopped for your own protection."); - } - string[] dupFiles = Directory.GetFiles(importFolder, FileNameWithExtension, SearchOption.AllDirectories); - if (dupFiles.Length > 1) - throw new ScriptException("Duplicate file detected. There are " + dupFiles.Length + " files named: " + FileNameWithExtension); - var prevFrameName = spriteName + "_" + prevframe.ToString() + ".png"; - string[] previousFrameFiles = Directory.GetFiles(importFolder, prevFrameName, SearchOption.AllDirectories); - if (previousFrameFiles.Length < 1) - throw new ScriptException(spriteName + " is missing one or more indexes. The detected missing index is: " + prevFrameName); -} - -// Get directory paths -string workDirectory = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().CodeBase).Substring(6); -System.IO.DirectoryInfo dir = System.IO.Directory.CreateDirectory(workDirectory + Path.DirectorySeparatorChar + "Packager"); - -// Clear any files if they already exist -foreach (FileInfo file in dir.GetFiles()) - file.Delete(); -foreach (DirectoryInfo di in dir.GetDirectories()) - di.Delete(true); - -// Start export of all existing textures - -int progress = 0; -string exportedTexturesFolder = dir.FullName + Path.DirectorySeparatorChar + "Textures" + Path.DirectorySeparatorChar; -TextureWorker worker = new TextureWorker(); -Dictionary assetCoordinateDict = new Dictionary(); -Dictionary assetTypeDict = new Dictionary(); - -Directory.CreateDirectory(exportedTexturesFolder); - -SetProgressBar(null, "Existing Textures Exported", 0, Data.TexturePageItems.Count); -StartUpdater(); - -await DumpSprites(); -await DumpFonts(); -await DumpBackgrounds(); -worker.Cleanup(); - -await StopUpdater(); -HideProgressBar(); - -async Task DumpSprites() -{ - await Task.Run(() => Parallel.ForEach(Data.Sprites, DumpSprite)); -} - -async Task DumpBackgrounds() -{ - await Task.Run(() => Parallel.ForEach(Data.Backgrounds, DumpBackground)); -} - -async Task DumpFonts() -{ - await Task.Run(() => Parallel.ForEach(Data.Fonts, DumpFont)); -} - -void DumpSprite(UndertaleSprite sprite) -{ - for (int i = 0; i < sprite.Textures.Count; i++) - { - if (sprite.Textures[i]?.Texture != null) - { - UndertaleTexturePageItem tex = sprite.Textures[i].Texture; - worker.ExportAsPNG(tex, exportedTexturesFolder + sprite.Name.Content + "_" + i + ".png"); - assetCoordinateDict.Add(sprite.Name.Content + "_" + i, new int[] { tex.TargetX, tex.TargetY, tex.TargetWidth, tex.TargetHeight, tex.BoundingWidth, tex.BoundingHeight }); - assetTypeDict.Add(sprite.Name.Content + "_" + i, "spr"); - } - } - - AddProgress(sprite.Textures.Count); -} - -void DumpFont(UndertaleFont font) -{ - if (font.Texture != null) - { - UndertaleTexturePageItem tex = font.Texture; - worker.ExportAsPNG(tex, exportedTexturesFolder + font.Name.Content + ".png"); - assetCoordinateDict.Add(font.Name.Content, new int[] { tex.TargetX, tex.TargetY, tex.TargetWidth, tex.TargetHeight, tex.BoundingWidth, tex.BoundingHeight }); - assetTypeDict.Add(font.Name.Content, "fnt"); - - AddProgress(1); - } -} - -void DumpBackground(UndertaleBackground background) -{ - if (background.Texture != null) - { - UndertaleTexturePageItem tex = background.Texture; - worker.ExportAsPNG(tex, exportedTexturesFolder + background.Name.Content + ".png"); - assetCoordinateDict.Add(background.Name.Content, new int[] { tex.TargetX, tex.TargetY, tex.TargetWidth, tex.TargetHeight, tex.BoundingWidth, tex.BoundingHeight }); - assetTypeDict.Add(background.Name.Content, "bg"); - - AddProgress(1); - } -} - -// End export - -string sourcePath = exportedTexturesFolder; -string searchPattern = "*.png"; -string outName = dir.FullName + Path.DirectorySeparatorChar + "atlas.txt"; -int textureSize = 2048; -int PaddingValue = 2; -bool debug = false; - -// Add imported textures to existing textures, overwrite those with the same name. -DirectoryInfo textureDirectory = new DirectoryInfo(importFolder); -FileInfo[] files = textureDirectory.GetFiles(searchPattern, SearchOption.AllDirectories); -foreach (FileInfo file in files) -{ - string destFile = System.IO.Path.Combine(exportedTexturesFolder, file.Name); - string sourceFile = System.IO.Path.Combine(importFolder, file.Name); - string stripped = Path.GetFileNameWithoutExtension(sourceFile); - if (assetCoordinateDict.ContainsKey(stripped)) - assetCoordinateDict.Remove(stripped); - System.IO.File.Copy(sourceFile, destFile, true); -} - -try -{ - string[] marginLines = System.IO.File.ReadAllLines(importFolder + Path.DirectorySeparatorChar + "margins.txt"); - foreach (String str in marginLines) - { - string key = str.Substring(0, str.IndexOf(',')); - string tmp = str; - tmp = tmp.Substring(str.IndexOf(',') + 1); - int[] marginValues = new int[6]; - for (int i = 0; i < 5; i++) - { - marginValues[i] = Int32.Parse(tmp.Substring(0, tmp.IndexOf(',')), System.Globalization.NumberStyles.Integer); - tmp = tmp.Substring(tmp.IndexOf(',') + 1); - } - marginValues[5] = Int32.Parse(tmp, System.Globalization.NumberStyles.Integer); - if (assetCoordinateDict.ContainsKey(key)) - assetCoordinateDict[key] = marginValues; - else - assetCoordinateDict.Add(key, marginValues); - } -} -catch (IOException e) -{ - if (!ScriptQuestion("Margin values were not found.\nImport with default values?")) - return; -} - -// Delete all existing Textures and TextureSheets -Data.TexturePageItems.Clear(); -Data.EmbeddedTextures.Clear(); - -// Run the texture packer using borrowed and slightly modified code from the -// Texture packer sourced above -Packer packer = new Packer(); -packer.Process(sourcePath, searchPattern, textureSize, PaddingValue, debug); -packer.SaveAtlasses(outName); - -int lastTextPage = Data.EmbeddedTextures.Count - 1; -int lastTextPageItem = Data.TexturePageItems.Count - 1; - -// Import everything into UMT -string prefix = outName.Replace(Path.GetExtension(outName), ""); -int atlasCount = 0; -foreach (Atlas atlas in packer.Atlasses) -{ - string atlasName = String.Format(prefix + "{0:000}" + ".png", atlasCount); - Bitmap atlasBitmap = new Bitmap(atlasName); - UndertaleEmbeddedTexture texture = new UndertaleEmbeddedTexture(); - texture.Name = new UndertaleString("Texture " + ++lastTextPage); - texture.TextureData.TextureBlob = File.ReadAllBytes(atlasName); - Data.EmbeddedTextures.Add(texture); - foreach (Node n in atlas.Nodes) - { - if (n.Texture != null) - { - // Initalize values of this texture - UndertaleTexturePageItem texturePageItem = new UndertaleTexturePageItem(); - texturePageItem.Name = new UndertaleString("PageItem " + ++lastTextPageItem); - texturePageItem.SourceX = (ushort)n.Bounds.X; - texturePageItem.SourceY = (ushort)n.Bounds.Y; - texturePageItem.SourceWidth = (ushort)n.Bounds.Width; - texturePageItem.SourceHeight = (ushort)n.Bounds.Height; - texturePageItem.BoundingWidth = (ushort)n.Bounds.Width; - texturePageItem.BoundingHeight = (ushort)n.Bounds.Height; - texturePageItem.TexturePage = texture; - - // Add this texture to UMT - Data.TexturePageItems.Add(texturePageItem); - - // String processing - string stripped = Path.GetFileNameWithoutExtension(n.Texture.Source); - int firstUnderscore = stripped.IndexOf('_'); - string spriteType = ""; - try - { - if (assetTypeDict.ContainsKey(stripped)) - spriteType = assetTypeDict[stripped]; - else - spriteType = stripped.Substring(0, firstUnderscore); - } - catch (Exception e) - { - if (stripped.Equals("background0") || stripped.Equals("background1")) - { - UndertaleBackground background = Data.Backgrounds.ByName(stripped); //Use stripped instead of sprite name or else the last index calculation gives us a bad string. - background.Texture = texturePageItem; - setTextureTargetBounds(texturePageItem, stripped, n); - continue; - } - else - { - ScriptMessage("Error: Image " + stripped + " has an invalid name."); - continue; - } - } - setTextureTargetBounds(texturePageItem, stripped, n); - // Special Cases for backgrounds and fonts - if (spriteType.Equals("bg")) - { - UndertaleBackground background = Data.Backgrounds.ByName(stripped); // Use stripped instead of sprite name or else the last index calculation gives us a bad string. - background.Texture = texturePageItem; - } - else if (spriteType.Equals("fnt")) - { - UndertaleFont font = Data.Fonts.ByName(stripped); // Use stripped instead of sprite name or else the last index calculation gives us a bad string. - font.Texture = texturePageItem; - } - else - { - // Get sprite to add this texture to - string spriteName; - int lastUnderscore, frame; - try - { - lastUnderscore = stripped.LastIndexOf('_'); - spriteName = stripped.Substring(0, lastUnderscore); - frame = Int32.Parse(stripped.Substring(lastUnderscore + 1)); - } - catch (Exception e) - { - ScriptMessage("Error: Image " + stripped + " has an invalid name. Skipping..."); - continue; - } - UndertaleSprite sprite = null; - sprite = Data.Sprites.ByName(spriteName); - - // Create TextureEntry object - UndertaleSprite.TextureEntry texentry = new UndertaleSprite.TextureEntry(); - texentry.Texture = texturePageItem; - - // Set values for new sprites - if (sprite == null) - { - UndertaleString spriteUTString = Data.Strings.MakeString(spriteName); - UndertaleSprite newSprite = new UndertaleSprite(); - newSprite.Name = spriteUTString; - newSprite.Width = (uint)n.Bounds.Width; - newSprite.Height = (uint)n.Bounds.Height; - newSprite.MarginLeft = 0; - newSprite.MarginRight = n.Bounds.Width - 1; - newSprite.MarginTop = 0; - newSprite.MarginBottom = n.Bounds.Height - 1; - newSprite.OriginX = 0; - newSprite.OriginY = 0; - if (frame > 0) - { - for (int i = 0; i < frame; i++) - newSprite.Textures.Add(null); - } - newSprite.CollisionMasks.Add(newSprite.NewMaskEntry()); - Rectangle bmpRect = new Rectangle(n.Bounds.X, n.Bounds.Y, n.Bounds.Width, n.Bounds.Height); - System.Drawing.Imaging.PixelFormat format = atlasBitmap.PixelFormat; - Bitmap cloneBitmap = atlasBitmap.Clone(bmpRect, format); - int width = ((n.Bounds.Width + 7) / 8) * 8; - BitArray maskingBitArray = new BitArray(width * n.Bounds.Height); - for (int y = 0; y < n.Bounds.Height; y++) - { - for (int x = 0; x < n.Bounds.Width; x++) - { - Color pixelColor = cloneBitmap.GetPixel(x, y); - maskingBitArray[y * width + x] = (pixelColor.A > 0); - } - } - BitArray tempBitArray = new BitArray(width * n.Bounds.Height); - for (int i = 0; i < maskingBitArray.Length; i += 8) - { - for (int j = 0; j < 8; j++) - { - tempBitArray[j + i] = maskingBitArray[-(j - 7) + i]; - } - } - int numBytes; - numBytes = maskingBitArray.Length / 8; - byte[] bytes = new byte[numBytes]; - tempBitArray.CopyTo(bytes, 0); - for (int i = 0; i < bytes.Length; i++) - newSprite.CollisionMasks[0].Data[i] = bytes[i]; - newSprite.Textures.Add(texentry); - Data.Sprites.Add(newSprite); - continue; - } - if (frame > sprite.Textures.Count - 1) - { - while (frame > sprite.Textures.Count - 1) - { - sprite.Textures.Add(texentry); - } - continue; - } - sprite.Textures[frame] = texentry; - } - } - } - // Increment atlas - atlasCount++; -} - -ScriptMessage("Import Complete!"); - - -void setTextureTargetBounds(UndertaleTexturePageItem tex, string textureName, Node n) -{ - if (assetCoordinateDict.ContainsKey(textureName)) - { - int[] coords = assetCoordinateDict[textureName]; - tex.TargetX = (ushort)coords[0]; - tex.TargetY = (ushort)coords[1]; - tex.TargetWidth = (ushort)coords[2]; - tex.TargetHeight = (ushort)coords[3]; - tex.BoundingWidth = (ushort)coords[4]; - tex.BoundingHeight = (ushort)coords[5]; - } - else - { - tex.TargetX = 0; - tex.TargetY = 0; - tex.TargetWidth = (ushort)n.Bounds.Width; - tex.TargetHeight = (ushort)n.Bounds.Height; - } -} - -public class TextureInfo -{ - public string Source; - public int Width; - public int Height; -} - -public enum SplitType -{ - Horizontal, - Vertical, -} - -public enum BestFitHeuristic -{ - Area, - MaxOneAxis, -} - -public class Node -{ - public Rectangle Bounds; - public TextureInfo Texture; - public SplitType SplitType; -} - -public class Atlas -{ - public int Width; - public int Height; - public List Nodes; -} - -public class Packer -{ - public List SourceTextures; - public StringWriter Log; - public StringWriter Error; - public int Padding; - public int AtlasSize; - public bool DebugMode; - public BestFitHeuristic FitHeuristic; - public List Atlasses; - - public Packer() - { - SourceTextures = new List(); - Log = new StringWriter(); - Error = new StringWriter(); - } - - public void Process(string _SourceDir, string _Pattern, int _AtlasSize, int _Padding, bool _DebugMode) - { - Padding = _Padding; - AtlasSize = _AtlasSize; - DebugMode = _DebugMode; - //1: scan for all the textures we need to pack - ScanForTextures(_SourceDir, _Pattern); - List textures = new List(); - textures = SourceTextures.ToList(); - //2: generate as many atlasses as needed (with the latest one as small as possible) - Atlasses = new List(); - while (textures.Count > 0) - { - Atlas atlas = new Atlas(); - atlas.Width = _AtlasSize; - atlas.Height = _AtlasSize; - List leftovers = LayoutAtlas(textures, atlas); - if (leftovers.Count == 0) - { - // we reached the last atlas. Check if this last atlas could have been twice smaller - while (leftovers.Count == 0) - { - atlas.Width /= 2; - atlas.Height /= 2; - leftovers = LayoutAtlas(textures, atlas); - } - // we need to go 1 step larger as we found the first size that is to small - atlas.Width *= 2; - atlas.Height *= 2; - leftovers = LayoutAtlas(textures, atlas); - } - Atlasses.Add(atlas); - textures = leftovers; - } - } - - public void SaveAtlasses(string _Destination) - { - int atlasCount = 0; - string prefix = _Destination.Replace(Path.GetExtension(_Destination), ""); - string descFile = _Destination; - StreamWriter tw = new StreamWriter(_Destination); - tw.WriteLine("source_tex, atlas_tex, x, y, width, height"); - foreach (Atlas atlas in Atlasses) - { - string atlasName = String.Format(prefix + "{0:000}" + ".png", atlasCount); - //1: Save images - Image img = CreateAtlasImage(atlas); - img.Save(atlasName, System.Drawing.Imaging.ImageFormat.Png); - //2: save description in file - foreach (Node n in atlas.Nodes) - { - if (n.Texture != null) - { - tw.Write(n.Texture.Source + ", "); - tw.Write(atlasName + ", "); - tw.Write((n.Bounds.X).ToString() + ", "); - tw.Write((n.Bounds.Y).ToString() + ", "); - tw.Write((n.Bounds.Width).ToString() + ", "); - tw.WriteLine((n.Bounds.Height).ToString()); - } - } - ++atlasCount; - } - tw.Close(); - tw = new StreamWriter(prefix + ".log"); - tw.WriteLine("--- LOG -------------------------------------------"); - tw.WriteLine(Log.ToString()); - tw.WriteLine("--- ERROR -----------------------------------------"); - tw.WriteLine(Error.ToString()); - tw.Close(); - } - - private void ScanForTextures(string _Path, string _Wildcard) - { - DirectoryInfo di = new DirectoryInfo(_Path); - FileInfo[] files = di.GetFiles(_Wildcard, SearchOption.AllDirectories); - foreach (FileInfo fi in files) - { - Image img = Image.FromFile(fi.FullName); - if (img != null) - { - if (img.Width <= AtlasSize && img.Height <= AtlasSize) - { - TextureInfo ti = new TextureInfo(); - - ti.Source = fi.FullName; - ti.Width = img.Width; - ti.Height = img.Height; - - SourceTextures.Add(ti); - - Log.WriteLine("Added " + fi.FullName); - } - else - { - Error.WriteLine(fi.FullName + " is too large to fix in the atlas. Skipping!"); - } - } - } - } - - private void HorizontalSplit(Node _ToSplit, int _Width, int _Height, List _List) - { - Node n1 = new Node(); - n1.Bounds.X = _ToSplit.Bounds.X + _Width + Padding; - n1.Bounds.Y = _ToSplit.Bounds.Y; - n1.Bounds.Width = _ToSplit.Bounds.Width - _Width - Padding; - n1.Bounds.Height = _Height; - n1.SplitType = SplitType.Vertical; - Node n2 = new Node(); - n2.Bounds.X = _ToSplit.Bounds.X; - n2.Bounds.Y = _ToSplit.Bounds.Y + _Height + Padding; - n2.Bounds.Width = _ToSplit.Bounds.Width; - n2.Bounds.Height = _ToSplit.Bounds.Height - _Height - Padding; - n2.SplitType = SplitType.Horizontal; - if (n1.Bounds.Width > 0 && n1.Bounds.Height > 0) - _List.Add(n1); - if (n2.Bounds.Width > 0 && n2.Bounds.Height > 0) - _List.Add(n2); - } - - private void VerticalSplit(Node _ToSplit, int _Width, int _Height, List _List) - { - Node n1 = new Node(); - n1.Bounds.X = _ToSplit.Bounds.X + _Width + Padding; - n1.Bounds.Y = _ToSplit.Bounds.Y; - n1.Bounds.Width = _ToSplit.Bounds.Width - _Width - Padding; - n1.Bounds.Height = _ToSplit.Bounds.Height; - n1.SplitType = SplitType.Vertical; - Node n2 = new Node(); - n2.Bounds.X = _ToSplit.Bounds.X; - n2.Bounds.Y = _ToSplit.Bounds.Y + _Height + Padding; - n2.Bounds.Width = _Width; - n2.Bounds.Height = _ToSplit.Bounds.Height - _Height - Padding; - n2.SplitType = SplitType.Horizontal; - if (n1.Bounds.Width > 0 && n1.Bounds.Height > 0) - _List.Add(n1); - if (n2.Bounds.Width > 0 && n2.Bounds.Height > 0) - _List.Add(n2); - } - - private TextureInfo FindBestFitForNode(Node _Node, List _Textures) - { - TextureInfo bestFit = null; - float nodeArea = _Node.Bounds.Width * _Node.Bounds.Height; - float maxCriteria = 0.0f; - foreach (TextureInfo ti in _Textures) - { - switch (FitHeuristic) - { - // Max of Width and Height ratios - case BestFitHeuristic.MaxOneAxis: - if (ti.Width <= _Node.Bounds.Width && ti.Height <= _Node.Bounds.Height) - { - float wRatio = (float)ti.Width / (float)_Node.Bounds.Width; - float hRatio = (float)ti.Height / (float)_Node.Bounds.Height; - float ratio = wRatio > hRatio ? wRatio : hRatio; - if (ratio > maxCriteria) - { - maxCriteria = ratio; - bestFit = ti; - } - } - break; - // Maximize Area coverage - case BestFitHeuristic.Area: - if (ti.Width <= _Node.Bounds.Width && ti.Height <= _Node.Bounds.Height) - { - float textureArea = ti.Width * ti.Height; - float coverage = textureArea / nodeArea; - if (coverage > maxCriteria) - { - maxCriteria = coverage; - bestFit = ti; - } - } - break; - } - } - return bestFit; - } - - private List LayoutAtlas(List _Textures, Atlas _Atlas) - { - List freeList = new List(); - List textures = new List(); - _Atlas.Nodes = new List(); - textures = _Textures.ToList(); - Node root = new Node(); - root.Bounds.Size = new Size(_Atlas.Width, _Atlas.Height); - root.SplitType = SplitType.Horizontal; - freeList.Add(root); - while (freeList.Count > 0 && textures.Count > 0) - { - Node node = freeList[0]; - freeList.RemoveAt(0); - TextureInfo bestFit = FindBestFitForNode(node, textures); - if (bestFit != null) - { - if (node.SplitType == SplitType.Horizontal) - { - HorizontalSplit(node, bestFit.Width, bestFit.Height, freeList); - } - else - { - VerticalSplit(node, bestFit.Width, bestFit.Height, freeList); - } - node.Texture = bestFit; - node.Bounds.Width = bestFit.Width; - node.Bounds.Height = bestFit.Height; - textures.Remove(bestFit); - } - _Atlas.Nodes.Add(node); - } - return textures; - } - - private Image CreateAtlasImage(Atlas _Atlas) - { - Image img = new Bitmap(_Atlas.Width, _Atlas.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb); - Graphics g = Graphics.FromImage(img); - foreach (Node n in _Atlas.Nodes) - { - if (n.Texture != null) - { - Image sourceImg = Image.FromFile(n.Texture.Source); - g.DrawImage(sourceImg, n.Bounds); - } - } - // DPI FIX START - Bitmap ResolutionFix = new Bitmap(img); - ResolutionFix.SetResolution(96.0F, 96.0F); - Image img2 = ResolutionFix; - return img2; - // DPI FIX END - } +// Texture packer by Samuel Roy +// Uses code from https://github.com/mfascia/TexturePacker +// Uses code from ExportAllTextures.csx + +using System; +using System.IO; +using System.Drawing; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using UndertaleModLib.Util; + +EnsureDataLoaded(); + +bool recursiveCheck = ScriptQuestion(@"This script requires will import all valid sprites from all subdirectories. +If you do not want this to occur, please click ""No"" to cancel the script. +Then make sure that the sprites you wish to import are in a separate directory with no subdirectories. +"); +if (!recursiveCheck) + throw new ScriptException("Script cancelled."); + +// Get import folder +string importFolder = PromptChooseDirectory(); +if (importFolder == null) + throw new ScriptException("The import folder was not set."); + +//Stop the script if there's missing sprite entries or w/e. +string[] dirFiles = Directory.GetFiles(importFolder, "*.png", SearchOption.AllDirectories); +foreach (string file in dirFiles) +{ + string FileNameWithExtension = Path.GetFileName(file); + string stripped = Path.GetFileNameWithoutExtension(file); + int lastUnderscore = stripped.LastIndexOf('_'); + string spriteName = ""; + try + { + spriteName = stripped.Substring(0, lastUnderscore); + } + catch + { + throw new ScriptException("Getting the sprite name of " + FileNameWithExtension + " failed."); + } + Int32 validFrameNumber = 0; + try + { + validFrameNumber = Int32.Parse(stripped.Substring(lastUnderscore + 1)); + } + catch + { + throw new ScriptException("The index of " + FileNameWithExtension + " could not be determined."); + } + int frame = 0; + try + { + frame = Int32.Parse(stripped.Substring(lastUnderscore + 1)); + } + catch + { + throw new ScriptException(FileNameWithExtension + " is using letters instead of numbers. The script has stopped for your own protection."); + } + int prevframe = 0; + if (frame != 0) + { + prevframe = (frame - 1); + } + if (frame < 0) + { + throw new ScriptException(spriteName + " is using an invalid numbering scheme. The script has stopped for your own protection."); + } + string[] dupFiles = Directory.GetFiles(importFolder, FileNameWithExtension, SearchOption.AllDirectories); + if (dupFiles.Length > 1) + throw new ScriptException("Duplicate file detected. There are " + dupFiles.Length + " files named: " + FileNameWithExtension); + var prevFrameName = spriteName + "_" + prevframe.ToString() + ".png"; + string[] previousFrameFiles = Directory.GetFiles(importFolder, prevFrameName, SearchOption.AllDirectories); + if (previousFrameFiles.Length < 1) + throw new ScriptException(spriteName + " is missing one or more indexes. The detected missing index is: " + prevFrameName); +} + +// Get directory paths +string workDirectory = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().CodeBase).Substring(6); +System.IO.DirectoryInfo dir = System.IO.Directory.CreateDirectory(workDirectory + Path.DirectorySeparatorChar + "Packager"); + +// Clear any files if they already exist +foreach (FileInfo file in dir.GetFiles()) + file.Delete(); +foreach (DirectoryInfo di in dir.GetDirectories()) + di.Delete(true); + +// Start export of all existing textures + +int progress = 0; +string exportedTexturesFolder = dir.FullName + Path.DirectorySeparatorChar + "Textures" + Path.DirectorySeparatorChar; +TextureWorker worker = new TextureWorker(); +Dictionary assetCoordinateDict = new Dictionary(); +Dictionary assetTypeDict = new Dictionary(); + +Directory.CreateDirectory(exportedTexturesFolder); + +SetProgressBar(null, "Existing Textures Exported", 0, Data.TexturePageItems.Count); +StartProgressBarUpdater(); + +await DumpSprites(); +await DumpFonts(); +await DumpBackgrounds(); +worker.Cleanup(); + +await StopProgressBarUpdater(); +HideProgressBar(); + +async Task DumpSprites() +{ + await Task.Run(() => Parallel.ForEach(Data.Sprites, DumpSprite)); +} + +async Task DumpBackgrounds() +{ + await Task.Run(() => Parallel.ForEach(Data.Backgrounds, DumpBackground)); +} + +async Task DumpFonts() +{ + await Task.Run(() => Parallel.ForEach(Data.Fonts, DumpFont)); +} + +void DumpSprite(UndertaleSprite sprite) +{ + for (int i = 0; i < sprite.Textures.Count; i++) + { + if (sprite.Textures[i]?.Texture != null) + { + UndertaleTexturePageItem tex = sprite.Textures[i].Texture; + worker.ExportAsPNG(tex, exportedTexturesFolder + sprite.Name.Content + "_" + i + ".png"); + assetCoordinateDict.Add(sprite.Name.Content + "_" + i, new int[] { tex.TargetX, tex.TargetY, tex.TargetWidth, tex.TargetHeight, tex.BoundingWidth, tex.BoundingHeight }); + assetTypeDict.Add(sprite.Name.Content + "_" + i, "spr"); + } + } + + AddProgress(sprite.Textures.Count); +} + +void DumpFont(UndertaleFont font) +{ + if (font.Texture != null) + { + UndertaleTexturePageItem tex = font.Texture; + worker.ExportAsPNG(tex, exportedTexturesFolder + font.Name.Content + ".png"); + assetCoordinateDict.Add(font.Name.Content, new int[] { tex.TargetX, tex.TargetY, tex.TargetWidth, tex.TargetHeight, tex.BoundingWidth, tex.BoundingHeight }); + assetTypeDict.Add(font.Name.Content, "fnt"); + + AddProgress(1); + } +} + +void DumpBackground(UndertaleBackground background) +{ + if (background.Texture != null) + { + UndertaleTexturePageItem tex = background.Texture; + worker.ExportAsPNG(tex, exportedTexturesFolder + background.Name.Content + ".png"); + assetCoordinateDict.Add(background.Name.Content, new int[] { tex.TargetX, tex.TargetY, tex.TargetWidth, tex.TargetHeight, tex.BoundingWidth, tex.BoundingHeight }); + assetTypeDict.Add(background.Name.Content, "bg"); + + AddProgress(1); + } +} + +// End export + +string sourcePath = exportedTexturesFolder; +string searchPattern = "*.png"; +string outName = dir.FullName + Path.DirectorySeparatorChar + "atlas.txt"; +int textureSize = 2048; +int PaddingValue = 2; +bool debug = false; + +// Add imported textures to existing textures, overwrite those with the same name. +DirectoryInfo textureDirectory = new DirectoryInfo(importFolder); +FileInfo[] files = textureDirectory.GetFiles(searchPattern, SearchOption.AllDirectories); +foreach (FileInfo file in files) +{ + string destFile = System.IO.Path.Combine(exportedTexturesFolder, file.Name); + string sourceFile = System.IO.Path.Combine(importFolder, file.Name); + string stripped = Path.GetFileNameWithoutExtension(sourceFile); + if (assetCoordinateDict.ContainsKey(stripped)) + assetCoordinateDict.Remove(stripped); + System.IO.File.Copy(sourceFile, destFile, true); +} + +try +{ + string[] marginLines = System.IO.File.ReadAllLines(importFolder + Path.DirectorySeparatorChar + "margins.txt"); + foreach (String str in marginLines) + { + string key = str.Substring(0, str.IndexOf(',')); + string tmp = str; + tmp = tmp.Substring(str.IndexOf(',') + 1); + int[] marginValues = new int[6]; + for (int i = 0; i < 5; i++) + { + marginValues[i] = Int32.Parse(tmp.Substring(0, tmp.IndexOf(',')), System.Globalization.NumberStyles.Integer); + tmp = tmp.Substring(tmp.IndexOf(',') + 1); + } + marginValues[5] = Int32.Parse(tmp, System.Globalization.NumberStyles.Integer); + if (assetCoordinateDict.ContainsKey(key)) + assetCoordinateDict[key] = marginValues; + else + assetCoordinateDict.Add(key, marginValues); + } +} +catch (IOException e) +{ + if (!ScriptQuestion("Margin values were not found.\nImport with default values?")) + return; +} + +// Delete all existing Textures and TextureSheets +Data.TexturePageItems.Clear(); +Data.EmbeddedTextures.Clear(); + +// Run the texture packer using borrowed and slightly modified code from the +// Texture packer sourced above +Packer packer = new Packer(); +packer.Process(sourcePath, searchPattern, textureSize, PaddingValue, debug); +packer.SaveAtlasses(outName); + +int lastTextPage = Data.EmbeddedTextures.Count - 1; +int lastTextPageItem = Data.TexturePageItems.Count - 1; + +// Import everything into UMT +string prefix = outName.Replace(Path.GetExtension(outName), ""); +int atlasCount = 0; +foreach (Atlas atlas in packer.Atlasses) +{ + string atlasName = String.Format(prefix + "{0:000}" + ".png", atlasCount); + Bitmap atlasBitmap = new Bitmap(atlasName); + UndertaleEmbeddedTexture texture = new UndertaleEmbeddedTexture(); + texture.Name = new UndertaleString("Texture " + ++lastTextPage); + texture.TextureData.TextureBlob = File.ReadAllBytes(atlasName); + Data.EmbeddedTextures.Add(texture); + foreach (Node n in atlas.Nodes) + { + if (n.Texture != null) + { + // Initalize values of this texture + UndertaleTexturePageItem texturePageItem = new UndertaleTexturePageItem(); + texturePageItem.Name = new UndertaleString("PageItem " + ++lastTextPageItem); + texturePageItem.SourceX = (ushort)n.Bounds.X; + texturePageItem.SourceY = (ushort)n.Bounds.Y; + texturePageItem.SourceWidth = (ushort)n.Bounds.Width; + texturePageItem.SourceHeight = (ushort)n.Bounds.Height; + texturePageItem.BoundingWidth = (ushort)n.Bounds.Width; + texturePageItem.BoundingHeight = (ushort)n.Bounds.Height; + texturePageItem.TexturePage = texture; + + // Add this texture to UMT + Data.TexturePageItems.Add(texturePageItem); + + // String processing + string stripped = Path.GetFileNameWithoutExtension(n.Texture.Source); + int firstUnderscore = stripped.IndexOf('_'); + string spriteType = ""; + try + { + if (assetTypeDict.ContainsKey(stripped)) + spriteType = assetTypeDict[stripped]; + else + spriteType = stripped.Substring(0, firstUnderscore); + } + catch (Exception e) + { + if (stripped.Equals("background0") || stripped.Equals("background1")) + { + UndertaleBackground background = Data.Backgrounds.ByName(stripped); //Use stripped instead of sprite name or else the last index calculation gives us a bad string. + background.Texture = texturePageItem; + setTextureTargetBounds(texturePageItem, stripped, n); + continue; + } + else + { + ScriptMessage("Error: Image " + stripped + " has an invalid name."); + continue; + } + } + setTextureTargetBounds(texturePageItem, stripped, n); + // Special Cases for backgrounds and fonts + if (spriteType.Equals("bg")) + { + UndertaleBackground background = Data.Backgrounds.ByName(stripped); // Use stripped instead of sprite name or else the last index calculation gives us a bad string. + background.Texture = texturePageItem; + } + else if (spriteType.Equals("fnt")) + { + UndertaleFont font = Data.Fonts.ByName(stripped); // Use stripped instead of sprite name or else the last index calculation gives us a bad string. + font.Texture = texturePageItem; + } + else + { + // Get sprite to add this texture to + string spriteName; + int lastUnderscore, frame; + try + { + lastUnderscore = stripped.LastIndexOf('_'); + spriteName = stripped.Substring(0, lastUnderscore); + frame = Int32.Parse(stripped.Substring(lastUnderscore + 1)); + } + catch (Exception e) + { + ScriptMessage("Error: Image " + stripped + " has an invalid name. Skipping..."); + continue; + } + UndertaleSprite sprite = null; + sprite = Data.Sprites.ByName(spriteName); + + // Create TextureEntry object + UndertaleSprite.TextureEntry texentry = new UndertaleSprite.TextureEntry(); + texentry.Texture = texturePageItem; + + // Set values for new sprites + if (sprite == null) + { + UndertaleString spriteUTString = Data.Strings.MakeString(spriteName); + UndertaleSprite newSprite = new UndertaleSprite(); + newSprite.Name = spriteUTString; + newSprite.Width = (uint)n.Bounds.Width; + newSprite.Height = (uint)n.Bounds.Height; + newSprite.MarginLeft = 0; + newSprite.MarginRight = n.Bounds.Width - 1; + newSprite.MarginTop = 0; + newSprite.MarginBottom = n.Bounds.Height - 1; + newSprite.OriginX = 0; + newSprite.OriginY = 0; + if (frame > 0) + { + for (int i = 0; i < frame; i++) + newSprite.Textures.Add(null); + } + newSprite.CollisionMasks.Add(newSprite.NewMaskEntry()); + Rectangle bmpRect = new Rectangle(n.Bounds.X, n.Bounds.Y, n.Bounds.Width, n.Bounds.Height); + System.Drawing.Imaging.PixelFormat format = atlasBitmap.PixelFormat; + Bitmap cloneBitmap = atlasBitmap.Clone(bmpRect, format); + int width = ((n.Bounds.Width + 7) / 8) * 8; + BitArray maskingBitArray = new BitArray(width * n.Bounds.Height); + for (int y = 0; y < n.Bounds.Height; y++) + { + for (int x = 0; x < n.Bounds.Width; x++) + { + Color pixelColor = cloneBitmap.GetPixel(x, y); + maskingBitArray[y * width + x] = (pixelColor.A > 0); + } + } + BitArray tempBitArray = new BitArray(width * n.Bounds.Height); + for (int i = 0; i < maskingBitArray.Length; i += 8) + { + for (int j = 0; j < 8; j++) + { + tempBitArray[j + i] = maskingBitArray[-(j - 7) + i]; + } + } + int numBytes; + numBytes = maskingBitArray.Length / 8; + byte[] bytes = new byte[numBytes]; + tempBitArray.CopyTo(bytes, 0); + for (int i = 0; i < bytes.Length; i++) + newSprite.CollisionMasks[0].Data[i] = bytes[i]; + newSprite.Textures.Add(texentry); + Data.Sprites.Add(newSprite); + continue; + } + if (frame > sprite.Textures.Count - 1) + { + while (frame > sprite.Textures.Count - 1) + { + sprite.Textures.Add(texentry); + } + continue; + } + sprite.Textures[frame] = texentry; + } + } + } + // Increment atlas + atlasCount++; +} + +ScriptMessage("Import Complete!"); + + +void setTextureTargetBounds(UndertaleTexturePageItem tex, string textureName, Node n) +{ + if (assetCoordinateDict.ContainsKey(textureName)) + { + int[] coords = assetCoordinateDict[textureName]; + tex.TargetX = (ushort)coords[0]; + tex.TargetY = (ushort)coords[1]; + tex.TargetWidth = (ushort)coords[2]; + tex.TargetHeight = (ushort)coords[3]; + tex.BoundingWidth = (ushort)coords[4]; + tex.BoundingHeight = (ushort)coords[5]; + } + else + { + tex.TargetX = 0; + tex.TargetY = 0; + tex.TargetWidth = (ushort)n.Bounds.Width; + tex.TargetHeight = (ushort)n.Bounds.Height; + } +} + +public class TextureInfo +{ + public string Source; + public int Width; + public int Height; +} + +public enum SplitType +{ + Horizontal, + Vertical, +} + +public enum BestFitHeuristic +{ + Area, + MaxOneAxis, +} + +public class Node +{ + public Rectangle Bounds; + public TextureInfo Texture; + public SplitType SplitType; +} + +public class Atlas +{ + public int Width; + public int Height; + public List Nodes; +} + +public class Packer +{ + public List SourceTextures; + public StringWriter Log; + public StringWriter Error; + public int Padding; + public int AtlasSize; + public bool DebugMode; + public BestFitHeuristic FitHeuristic; + public List Atlasses; + + public Packer() + { + SourceTextures = new List(); + Log = new StringWriter(); + Error = new StringWriter(); + } + + public void Process(string _SourceDir, string _Pattern, int _AtlasSize, int _Padding, bool _DebugMode) + { + Padding = _Padding; + AtlasSize = _AtlasSize; + DebugMode = _DebugMode; + //1: scan for all the textures we need to pack + ScanForTextures(_SourceDir, _Pattern); + List textures = new List(); + textures = SourceTextures.ToList(); + //2: generate as many atlasses as needed (with the latest one as small as possible) + Atlasses = new List(); + while (textures.Count > 0) + { + Atlas atlas = new Atlas(); + atlas.Width = _AtlasSize; + atlas.Height = _AtlasSize; + List leftovers = LayoutAtlas(textures, atlas); + if (leftovers.Count == 0) + { + // we reached the last atlas. Check if this last atlas could have been twice smaller + while (leftovers.Count == 0) + { + atlas.Width /= 2; + atlas.Height /= 2; + leftovers = LayoutAtlas(textures, atlas); + } + // we need to go 1 step larger as we found the first size that is to small + atlas.Width *= 2; + atlas.Height *= 2; + leftovers = LayoutAtlas(textures, atlas); + } + Atlasses.Add(atlas); + textures = leftovers; + } + } + + public void SaveAtlasses(string _Destination) + { + int atlasCount = 0; + string prefix = _Destination.Replace(Path.GetExtension(_Destination), ""); + string descFile = _Destination; + StreamWriter tw = new StreamWriter(_Destination); + tw.WriteLine("source_tex, atlas_tex, x, y, width, height"); + foreach (Atlas atlas in Atlasses) + { + string atlasName = String.Format(prefix + "{0:000}" + ".png", atlasCount); + //1: Save images + Image img = CreateAtlasImage(atlas); + img.Save(atlasName, System.Drawing.Imaging.ImageFormat.Png); + //2: save description in file + foreach (Node n in atlas.Nodes) + { + if (n.Texture != null) + { + tw.Write(n.Texture.Source + ", "); + tw.Write(atlasName + ", "); + tw.Write((n.Bounds.X).ToString() + ", "); + tw.Write((n.Bounds.Y).ToString() + ", "); + tw.Write((n.Bounds.Width).ToString() + ", "); + tw.WriteLine((n.Bounds.Height).ToString()); + } + } + ++atlasCount; + } + tw.Close(); + tw = new StreamWriter(prefix + ".log"); + tw.WriteLine("--- LOG -------------------------------------------"); + tw.WriteLine(Log.ToString()); + tw.WriteLine("--- ERROR -----------------------------------------"); + tw.WriteLine(Error.ToString()); + tw.Close(); + } + + private void ScanForTextures(string _Path, string _Wildcard) + { + DirectoryInfo di = new DirectoryInfo(_Path); + FileInfo[] files = di.GetFiles(_Wildcard, SearchOption.AllDirectories); + foreach (FileInfo fi in files) + { + Image img = Image.FromFile(fi.FullName); + if (img != null) + { + if (img.Width <= AtlasSize && img.Height <= AtlasSize) + { + TextureInfo ti = new TextureInfo(); + + ti.Source = fi.FullName; + ti.Width = img.Width; + ti.Height = img.Height; + + SourceTextures.Add(ti); + + Log.WriteLine("Added " + fi.FullName); + } + else + { + Error.WriteLine(fi.FullName + " is too large to fix in the atlas. Skipping!"); + } + } + } + } + + private void HorizontalSplit(Node _ToSplit, int _Width, int _Height, List _List) + { + Node n1 = new Node(); + n1.Bounds.X = _ToSplit.Bounds.X + _Width + Padding; + n1.Bounds.Y = _ToSplit.Bounds.Y; + n1.Bounds.Width = _ToSplit.Bounds.Width - _Width - Padding; + n1.Bounds.Height = _Height; + n1.SplitType = SplitType.Vertical; + Node n2 = new Node(); + n2.Bounds.X = _ToSplit.Bounds.X; + n2.Bounds.Y = _ToSplit.Bounds.Y + _Height + Padding; + n2.Bounds.Width = _ToSplit.Bounds.Width; + n2.Bounds.Height = _ToSplit.Bounds.Height - _Height - Padding; + n2.SplitType = SplitType.Horizontal; + if (n1.Bounds.Width > 0 && n1.Bounds.Height > 0) + _List.Add(n1); + if (n2.Bounds.Width > 0 && n2.Bounds.Height > 0) + _List.Add(n2); + } + + private void VerticalSplit(Node _ToSplit, int _Width, int _Height, List _List) + { + Node n1 = new Node(); + n1.Bounds.X = _ToSplit.Bounds.X + _Width + Padding; + n1.Bounds.Y = _ToSplit.Bounds.Y; + n1.Bounds.Width = _ToSplit.Bounds.Width - _Width - Padding; + n1.Bounds.Height = _ToSplit.Bounds.Height; + n1.SplitType = SplitType.Vertical; + Node n2 = new Node(); + n2.Bounds.X = _ToSplit.Bounds.X; + n2.Bounds.Y = _ToSplit.Bounds.Y + _Height + Padding; + n2.Bounds.Width = _Width; + n2.Bounds.Height = _ToSplit.Bounds.Height - _Height - Padding; + n2.SplitType = SplitType.Horizontal; + if (n1.Bounds.Width > 0 && n1.Bounds.Height > 0) + _List.Add(n1); + if (n2.Bounds.Width > 0 && n2.Bounds.Height > 0) + _List.Add(n2); + } + + private TextureInfo FindBestFitForNode(Node _Node, List _Textures) + { + TextureInfo bestFit = null; + float nodeArea = _Node.Bounds.Width * _Node.Bounds.Height; + float maxCriteria = 0.0f; + foreach (TextureInfo ti in _Textures) + { + switch (FitHeuristic) + { + // Max of Width and Height ratios + case BestFitHeuristic.MaxOneAxis: + if (ti.Width <= _Node.Bounds.Width && ti.Height <= _Node.Bounds.Height) + { + float wRatio = (float)ti.Width / (float)_Node.Bounds.Width; + float hRatio = (float)ti.Height / (float)_Node.Bounds.Height; + float ratio = wRatio > hRatio ? wRatio : hRatio; + if (ratio > maxCriteria) + { + maxCriteria = ratio; + bestFit = ti; + } + } + break; + // Maximize Area coverage + case BestFitHeuristic.Area: + if (ti.Width <= _Node.Bounds.Width && ti.Height <= _Node.Bounds.Height) + { + float textureArea = ti.Width * ti.Height; + float coverage = textureArea / nodeArea; + if (coverage > maxCriteria) + { + maxCriteria = coverage; + bestFit = ti; + } + } + break; + } + } + return bestFit; + } + + private List LayoutAtlas(List _Textures, Atlas _Atlas) + { + List freeList = new List(); + List textures = new List(); + _Atlas.Nodes = new List(); + textures = _Textures.ToList(); + Node root = new Node(); + root.Bounds.Size = new Size(_Atlas.Width, _Atlas.Height); + root.SplitType = SplitType.Horizontal; + freeList.Add(root); + while (freeList.Count > 0 && textures.Count > 0) + { + Node node = freeList[0]; + freeList.RemoveAt(0); + TextureInfo bestFit = FindBestFitForNode(node, textures); + if (bestFit != null) + { + if (node.SplitType == SplitType.Horizontal) + { + HorizontalSplit(node, bestFit.Width, bestFit.Height, freeList); + } + else + { + VerticalSplit(node, bestFit.Width, bestFit.Height, freeList); + } + node.Texture = bestFit; + node.Bounds.Width = bestFit.Width; + node.Bounds.Height = bestFit.Height; + textures.Remove(bestFit); + } + _Atlas.Nodes.Add(node); + } + return textures; + } + + private Image CreateAtlasImage(Atlas _Atlas) + { + Image img = new Bitmap(_Atlas.Width, _Atlas.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb); + Graphics g = Graphics.FromImage(img); + foreach (Node n in _Atlas.Nodes) + { + if (n.Texture != null) + { + Image sourceImg = Image.FromFile(n.Texture.Source); + g.DrawImage(sourceImg, n.Bounds); + } + } + // DPI FIX START + Bitmap ResolutionFix = new Bitmap(img); + ResolutionFix.SetResolution(96.0F, 96.0F); + Image img2 = ResolutionFix; + return img2; + // DPI FIX END + } } \ No newline at end of file diff --git a/UndertaleModTool/UndertaleModTool.csproj b/UndertaleModTool/UndertaleModTool.csproj index da4370044..afa7a9bb0 100644 --- a/UndertaleModTool/UndertaleModTool.csproj +++ b/UndertaleModTool/UndertaleModTool.csproj @@ -1,6 +1,7 @@  net5.0-windows + LatestMajor WinExe publish\ true @@ -25,12 +26,14 @@ embedded AnyCPU;x64 win-x64;win-x86 + 9 UndertaleModTool.Program icon.ico + True diff --git a/UndertaleModTool/Unpackers/DumpSpecificCode.csx b/UndertaleModTool/Unpackers/DumpSpecificCode.csx index 62a2ecc3f..488ebb562 100644 --- a/UndertaleModTool/Unpackers/DumpSpecificCode.csx +++ b/UndertaleModTool/Unpackers/DumpSpecificCode.csx @@ -1,152 +1,152 @@ -//Adapted from original script by Grossley -using System.Text; -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using UndertaleModLib.Util; - -EnsureDataLoaded(); - -if (Data.IsYYC()) -{ - ScriptError("You cannot do a code dump of a YYC game! There is no code to dump!"); - return; -} - -ThreadLocal DECOMPILE_CONTEXT = new ThreadLocal(() => new GlobalDecompileContext(Data, false)); - -int failed = 0; - -string codeFolder = PromptChooseDirectory("Export to where"); -if (codeFolder == null) - throw new ScriptException("The export folder was not set."); -Directory.CreateDirectory(Path.Combine(codeFolder, "Code")); -codeFolder = Path.Combine(codeFolder, "Code"); - -List codeToDump = new List(); -List gameObjectCandidates = new List(); -List splitStringsList = new List(); -string InputtedText = ""; -InputtedText = SimpleTextInput("Menu", "Enter object, script, or code entry names", InputtedText, true); -string[] IndividualLineArray = InputtedText.Split('\n', StringSplitOptions.RemoveEmptyEntries); -foreach (var OneLine in IndividualLineArray) -{ - splitStringsList.Add(OneLine.Trim()); -} -for (var j = 0; j < splitStringsList.Count; j++) -{ - foreach (UndertaleGameObject obj in Data.GameObjects) - { - if (splitStringsList[j].ToLower() == obj.Name.Content.ToLower()) - { - gameObjectCandidates.Add(obj.Name.Content); - } - } - foreach (UndertaleScript scr in Data.Scripts) - { - if (scr.Code == null) - continue; - if (splitStringsList[j].ToLower() == scr.Name.Content.ToLower()) - { - codeToDump.Add(scr.Code.Name.Content); - } - } - foreach (UndertaleGlobalInit globalInit in Data.GlobalInitScripts) - { - if (globalInit.Code == null) - continue; - if (splitStringsList[j].ToLower() == globalInit.Code.Name.Content.ToLower()) - { - codeToDump.Add(globalInit.Code.Name.Content); - } - } - foreach (UndertaleCode code in Data.Code) - { - if (splitStringsList[j].ToLower() == code.Name.Content.ToLower()) - { - codeToDump.Add(code.Name.Content); - } - } -} - -for (var j = 0; j < gameObjectCandidates.Count; j++) -{ - try - { - UndertaleGameObject obj = Data.GameObjects.ByName(gameObjectCandidates[j]); - for (var i = 0; i < obj.Events.Count; i++) - { - foreach (UndertaleGameObject.Event evnt in obj.Events[i]) - { - foreach (UndertaleGameObject.EventAction action in evnt.Actions) - { - if (action.CodeId?.Name?.Content != null) - codeToDump.Add(action.CodeId?.Name?.Content); - } - } - } - } - catch - { - // Something went wrong, but probably because it's trying to check something non-existent - // Just keep going - } -} - -SetProgressBar(null, "Code Entries", 0, codeToDump.Count); -StartUpdater(); - -await Task.Run(() => { - for (var j = 0; j < codeToDump.Count; j++) - { - DumpCode(Data.Code.ByName(codeToDump[j])); - } -}); - -await StopUpdater(); - -void DumpCode(UndertaleCode code) -{ - string path = Path.Combine(codeFolder, code.Name.Content + ".gml"); - if (code.ParentEntry == null) - { - try - { - File.WriteAllText(path, (code != null ? Decompiler.Decompile(code, DECOMPILE_CONTEXT.Value) : "")); - } - catch (Exception e) - { - if (!(Directory.Exists(Path.Combine(codeFolder, "Failed")))) - { - Directory.CreateDirectory(Path.Combine(codeFolder, "Failed")); - } - path = Path.Combine(codeFolder, "Failed", code.Name.Content + ".gml"); - File.WriteAllText(path, "/*\nDECOMPILER FAILED!\n\n" + e.ToString() + "\n*/"); - failed += 1; - } - } - else - { - if (!(Directory.Exists(Path.Combine(codeFolder, "Duplicates")))) - { - Directory.CreateDirectory(Path.Combine(codeFolder, "Duplicates")); - } - try - { - path = Path.Combine(codeFolder, "Duplicates", code.Name.Content + ".gml"); - File.WriteAllText(path, (code != null ? Decompiler.Decompile(code, DECOMPILE_CONTEXT.Value).Replace("@@This@@()", "self/*@@This@@()*/") : "")); - } - catch (Exception e) - { - if (!(Directory.Exists(Path.Combine(codeFolder, "Duplicates", "Failed")))) - { - Directory.CreateDirectory(Path.Combine(codeFolder, "Duplicates", "Failed")); - } - path = Path.Combine(codeFolder, "Duplicates", "Failed", code.Name.Content + ".gml"); - File.WriteAllText(path, "/*\nDECOMPILER FAILED!\n\n" + e.ToString() + "\n*/"); - failed += 1; - } - } - IncProgress(); -} +//Adapted from original script by Grossley +using System.Text; +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using UndertaleModLib.Util; + +EnsureDataLoaded(); + +if (Data.IsYYC()) +{ + ScriptError("You cannot do a code dump of a YYC game! There is no code to dump!"); + return; +} + +ThreadLocal DECOMPILE_CONTEXT = new ThreadLocal(() => new GlobalDecompileContext(Data, false)); + +int failed = 0; + +string codeFolder = PromptChooseDirectory(); +if (codeFolder == null) + throw new ScriptException("The export folder was not set."); +Directory.CreateDirectory(Path.Combine(codeFolder, "Code")); +codeFolder = Path.Combine(codeFolder, "Code"); + +List codeToDump = new List(); +List gameObjectCandidates = new List(); +List splitStringsList = new List(); +string InputtedText = ""; +InputtedText = SimpleTextInput("Menu", "Enter object, script, or code entry names", InputtedText, true); +string[] IndividualLineArray = InputtedText.Split('\n'}, StringSplitOptions.RemoveEmptyEntries); +foreach (var OneLine in IndividualLineArray) +{ + splitStringsList.Add(OneLine.Trim()); +} +for (var j = 0; j < splitStringsList.Count; j++) +{ + foreach (UndertaleGameObject obj in Data.GameObjects) + { + if (splitStringsList[j].ToLower() == obj.Name.Content.ToLower()) + { + gameObjectCandidates.Add(obj.Name.Content); + } + } + foreach (UndertaleScript scr in Data.Scripts) + { + if (scr.Code == null) + continue; + if (splitStringsList[j].ToLower() == scr.Name.Content.ToLower()) + { + codeToDump.Add(scr.Code.Name.Content); + } + } + foreach (UndertaleGlobalInit globalInit in Data.GlobalInitScripts) + { + if (globalInit.Code == null) + continue; + if (splitStringsList[j].ToLower() == globalInit.Code.Name.Content.ToLower()) + { + codeToDump.Add(globalInit.Code.Name.Content); + } + } + foreach (UndertaleCode code in Data.Code) + { + if (splitStringsList[j].ToLower() == code.Name.Content.ToLower()) + { + codeToDump.Add(code.Name.Content); + } + } +} + +for (var j = 0; j < gameObjectCandidates.Count; j++) +{ + try + { + UndertaleGameObject obj = Data.GameObjects.ByName(gameObjectCandidates[j]); + for (var i = 0; i < obj.Events.Count; i++) + { + foreach (UndertaleGameObject.Event evnt in obj.Events[i]) + { + foreach (UndertaleGameObject.EventAction action in evnt.Actions) + { + if (action.CodeId?.Name?.Content != null) + codeToDump.Add(action.CodeId?.Name?.Content); + } + } + } + } + catch + { + // Something went wrong, but probably because it's trying to check something non-existent + // Just keep going + } +} + +SetProgressBar(null, "Code Entries", 0, codeToDump.Count); +StartProgressBarUpdater(); + +await Task.Run(() => { + for (var j = 0; j < codeToDump.Count; j++) + { + DumpCode(Data.Code.ByName(codeToDump[j])); + } +}); + +await StopProgressBarUpdater(); + +void DumpCode(UndertaleCode code) +{ + string path = Path.Combine(codeFolder, code.Name.Content + ".gml"); + if (code.ParentEntry == null) + { + try + { + File.WriteAllText(path, (code != null ? Decompiler.Decompile(code, DECOMPILE_CONTEXT.Value) : "")); + } + catch (Exception e) + { + if (!(Directory.Exists(Path.Combine(codeFolder, "Failed")))) + { + Directory.CreateDirectory(Path.Combine(codeFolder, "Failed")); + } + path = Path.Combine(codeFolder, "Failed", code.Name.Content + ".gml"); + File.WriteAllText(path, "/*\nDECOMPILER FAILED!\n\n" + e.ToString() + "\n*/"); + failed += 1; + } + } + else + { + if (!(Directory.Exists(Path.Combine(codeFolder, "Duplicates")))) + { + Directory.CreateDirectory(Path.Combine(codeFolder, "Duplicates")); + } + try + { + path = Path.Combine(codeFolder, "Duplicates", code.Name.Content + ".gml"); + File.WriteAllText(path, (code != null ? Decompiler.Decompile(code, DECOMPILE_CONTEXT.Value).Replace("@@This@@()", "self/*@@This@@()*/") : "")); + } + catch (Exception e) + { + if (!(Directory.Exists(Path.Combine(codeFolder, "Duplicates", "Failed")))) + { + Directory.CreateDirectory(Path.Combine(codeFolder, "Duplicates", "Failed")); + } + path = Path.Combine(codeFolder, "Duplicates", "Failed", code.Name.Content + ".gml"); + File.WriteAllText(path, "/*\nDECOMPILER FAILED!\n\n" + e.ToString() + "\n*/"); + failed += 1; + } + } + IncrementProgress(); +} \ No newline at end of file diff --git a/UndertaleModTool/Unpackers/ExportASM.csx b/UndertaleModTool/Unpackers/ExportASM.csx index 5251b1846..84e4ba618 100644 --- a/UndertaleModTool/Unpackers/ExportASM.csx +++ b/UndertaleModTool/Unpackers/ExportASM.csx @@ -1,53 +1,53 @@ -using System.Text; -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; - -EnsureDataLoaded(); - -string codeFolder = GetFolder(FilePath) + "Export_Assembly" + Path.DirectorySeparatorChar; -ThreadLocal DECOMPILE_CONTEXT = new ThreadLocal(() => new GlobalDecompileContext(Data, false)); -if (Directory.Exists(codeFolder)) -{ - ScriptError("An assembly export already exists. Please remove it.", "Error"); - return; -} - -Directory.CreateDirectory(codeFolder); - -SetProgressBar(null, "Code Entries", 0, Data.Code.Count); -StartUpdater(); - -await DumpCode(); - -await StopUpdater(); -HideProgressBar(); -ScriptMessage("Export Complete.\n\nLocation: " + codeFolder); - - -string GetFolder(string path) -{ - return Path.GetDirectoryName(path) + Path.DirectorySeparatorChar; -} - - -async Task DumpCode() -{ - await Task.Run(() => Parallel.ForEach(Data.Code, DumpCode)); -} - -void DumpCode(UndertaleCode code) -{ - string path = Path.Combine(codeFolder, code.Name.Content + ".asm"); - try - { - File.WriteAllText(path, (code != null ? code.Disassemble(Data.Variables, Data.CodeLocals.For(code)) : "")); - } - catch (Exception e) - { - File.WriteAllText(path, "/*\nDISASSEMBLY FAILED!\n\n" + e.ToString() + "\n*/"); // Please don't - } - - IncProgressP(); +using System.Text; +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +EnsureDataLoaded(); + +string codeFolder = GetFolder(FilePath) + "Export_Assembly" + Path.DirectorySeparatorChar; +ThreadLocal DECOMPILE_CONTEXT = new ThreadLocal(() => new GlobalDecompileContext(Data, false)); +if (Directory.Exists(codeFolder)) +{ + ScriptError("An assembly export already exists. Please remove it.", "Error"); + return; +} + +Directory.CreateDirectory(codeFolder); + +SetProgressBar(null, "Code Entries", 0, Data.Code.Count); +StartProgressBarUpdater(); + +await DumpCode(); + +await StopProgressBarUpdater(); +HideProgressBar(); +ScriptMessage("Export Complete.\n\nLocation: " + codeFolder); + + +string GetFolder(string path) +{ + return Path.GetDirectoryName(path) + Path.DirectorySeparatorChar; +} + + +async Task DumpCode() +{ + await Task.Run(() => Parallel.ForEach(Data.Code, DumpCode)); +} + +void DumpCode(UndertaleCode code) +{ + string path = Path.Combine(codeFolder, code.Name.Content + ".asm"); + try + { + File.WriteAllText(path, (code != null ? code.Disassemble(Data.Variables, Data.CodeLocals.For(code)) : "")); + } + catch (Exception e) + { + File.WriteAllText(path, "/*\nDISASSEMBLY FAILED!\n\n" + e.ToString() + "\n*/"); // Please don't + } + + IncrementProgressParallel(); } \ No newline at end of file diff --git a/UndertaleModTool/Unpackers/ExportAllCode.csx b/UndertaleModTool/Unpackers/ExportAllCode.csx index e1e6c737b..31e8da261 100644 --- a/UndertaleModTool/Unpackers/ExportAllCode.csx +++ b/UndertaleModTool/Unpackers/ExportAllCode.csx @@ -1,106 +1,106 @@ -using System.Text; -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using System.Collections.Generic; - -EnsureDataLoaded(); - -string codeFolder = GetFolder(FilePath) + "Export_Code" + Path.DirectorySeparatorChar; -ThreadLocal DECOMPILE_CONTEXT = new ThreadLocal(() => new GlobalDecompileContext(Data, false)); -if (Directory.Exists(codeFolder)) -{ - ScriptError("A code export already exists. Please remove it.", "Error"); - return; -} - -Directory.CreateDirectory(codeFolder); - -bool exportFromCache = false; -if (GMLCacheEnabled && Data.GMLCache is not null) - exportFromCache = ScriptQuestion("Export from the cache?"); - -List toDump; -if (!exportFromCache) -{ - toDump = new(); - foreach (UndertaleCode code in Data.Code) - { - if (code.ParentEntry != null) - continue; - toDump.Add(code); - } -} - -bool cacheGenerated = false; -if (exportFromCache) -{ - cacheGenerated = await GenerateGMLCache(DECOMPILE_CONTEXT); - await StopUpdater(); -} - -SetProgressBar(null, "Code Entries", 0, exportFromCache ? Data.GMLCache.Count + Data.GMLCacheFailed.Count : toDump.Count); -StartUpdater(); - -await DumpCode(); - -await StopUpdater(); -HideProgressBar(); -ScriptMessage("Export Complete.\n\nLocation: " + codeFolder); - - -string GetFolder(string path) -{ - return Path.GetDirectoryName(path) + Path.DirectorySeparatorChar; -} - - -async Task DumpCode() -{ - if (cacheGenerated) - { - await Task.Run(() => Parallel.ForEach(Data.GMLCache, DumpCachedCode)); - - if (Data.GMLCacheFailed.Count > 0) - { - if (Data.KnownSubFunctions is null) //if we run script before opening any code - Decompiler.BuildSubFunctionCache(Data); - - await Task.Run(() => Parallel.ForEach(Data.GMLCacheFailed, (codeName) => DumpCode(Data.Code.ByName(codeName)))); - } - } - else - { - if (Data.KnownSubFunctions is null) //if we run script before opening any code - Decompiler.BuildSubFunctionCache(Data); - - await Task.Run(() => Parallel.ForEach(toDump, DumpCode)); - } -} - -void DumpCode(UndertaleCode code) -{ - if (code is not null) - { - string path = Path.Combine(codeFolder, code.Name.Content + ".gml"); - try - { - File.WriteAllText(path, (code != null ? Decompiler.Decompile(code, DECOMPILE_CONTEXT.Value) : "")); - } - catch (Exception e) - { - File.WriteAllText(path, "/*\nDECOMPILER FAILED!\n\n" + e.ToString() + "\n*/"); - } - } - - IncProgressP(); -} -void DumpCachedCode(KeyValuePair code) -{ - string path = Path.Combine(codeFolder, code.Key + ".gml"); - - File.WriteAllText(path, code.Value); - - IncProgressP(); +using System.Text; +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using System.Collections.Generic; + +EnsureDataLoaded(); + +string codeFolder = GetFolder(FilePath) + "Export_Code" + Path.DirectorySeparatorChar; +ThreadLocal DECOMPILE_CONTEXT = new ThreadLocal(() => new GlobalDecompileContext(Data, false)); +if (Directory.Exists(codeFolder)) +{ + ScriptError("A code export already exists. Please remove it.", "Error"); + return; +} + +Directory.CreateDirectory(codeFolder); + +bool exportFromCache = false; +if (GMLCacheEnabled && Data.GMLCache is not null) + exportFromCache = ScriptQuestion("Export from the cache?"); + +List toDump; +if (!exportFromCache) +{ + toDump = new(); + foreach (UndertaleCode code in Data.Code) + { + if (code.ParentEntry != null) + continue; + toDump.Add(code); + } +} + +bool cacheGenerated = false; +if (exportFromCache) +{ + cacheGenerated = await GenerateGMLCache(DECOMPILE_CONTEXT); + await StopProgressBarUpdater(); +} + +SetProgressBar(null, "Code Entries", 0, exportFromCache ? Data.GMLCache.Count + Data.GMLCacheFailed.Count : toDump.Count); +StartProgressBarUpdater(); + +await DumpCode(); + +await StopProgressBarUpdater(); +HideProgressBar(); +ScriptMessage("Export Complete.\n\nLocation: " + codeFolder); + + +string GetFolder(string path) +{ + return Path.GetDirectoryName(path) + Path.DirectorySeparatorChar; +} + + +async Task DumpCode() +{ + if (cacheGenerated) + { + await Task.Run(() => Parallel.ForEach(Data.GMLCache, DumpCachedCode)); + + if (Data.GMLCacheFailed.Count > 0) + { + if (Data.KnownSubFunctions is null) //if we run script before opening any code + Decompiler.BuildSubFunctionCache(Data); + + await Task.Run(() => Parallel.ForEach(Data.GMLCacheFailed, (codeName) => DumpCode(Data.Code.ByName(codeName)))); + } + } + else + { + if (Data.KnownSubFunctions is null) //if we run script before opening any code + Decompiler.BuildSubFunctionCache(Data); + + await Task.Run(() => Parallel.ForEach(toDump, DumpCode)); + } +} + +void DumpCode(UndertaleCode code) +{ + if (code is not null) + { + string path = Path.Combine(codeFolder, code.Name.Content + ".gml"); + try + { + File.WriteAllText(path, (code != null ? Decompiler.Decompile(code, DECOMPILE_CONTEXT.Value) : "")); + } + catch (Exception e) + { + File.WriteAllText(path, "/*\nDECOMPILER FAILED!\n\n" + e.ToString() + "\n*/"); + } + } + + IncrementProgressParallel(); +} +void DumpCachedCode(KeyValuePair code) +{ + string path = Path.Combine(codeFolder, code.Key + ".gml"); + + File.WriteAllText(path, code.Value); + + IncrementProgressParallel(); } \ No newline at end of file diff --git a/UndertaleModTool/Unpackers/ExportAllEmbeddedTextures.csx b/UndertaleModTool/Unpackers/ExportAllEmbeddedTextures.csx index 2f940c788..5ccb38244 100644 --- a/UndertaleModTool/Unpackers/ExportAllEmbeddedTextures.csx +++ b/UndertaleModTool/Unpackers/ExportAllEmbeddedTextures.csx @@ -1,70 +1,70 @@ -// Adapted from original script by Grossley -using System.Text; -using System; -using System.IO; -using System.Threading.Tasks; - -EnsureDataLoaded(); - -string winFolder = GetFolder(FilePath); // The folder data.win is located in. -string EmbFolder = Path.Combine(winFolder, "EmbeddedTextures"); // The folder to write the image data to. - -if (!CanOverwrite()) - return; - -MakeFolder("EmbeddedTextures"); - -SetProgressBar(null, "Embedded textures", 0, Data.EmbeddedTextures.Count); -StartUpdater(); - -await Task.Run(() => { - for (var i = 0; i < Data.EmbeddedTextures.Count; i++) - { - try - { - File.WriteAllBytes(Path.Combine(EmbFolder, i + ".png"), Data.EmbeddedTextures[i].TextureData.TextureBlob); - } - catch (Exception ex) - { - ScriptMessage("Failed to export file: " + ex.Message); - } - - IncProgress(); - } -}); - -await StopUpdater(); -HideProgressBar(); -ScriptMessage("Export Complete.\n\nLocation: " + EmbFolder); - -/* Helper functions below. -*/ - -string GetFolder(string path) -{ - return Path.GetDirectoryName(path) + Path.DirectorySeparatorChar; -} - -void MakeFolder(String folderName) -{ - string MakeFolderPath = Path.Combine(winFolder, folderName); - if (!Directory.Exists(MakeFolderPath)) - Directory.CreateDirectory(MakeFolderPath); -} - -bool CanOverwrite() -{ - // Overwrite Folder Check One - if (Directory.Exists(EmbFolder)) - { - bool overwriteCheckOne = ScriptQuestion("An 'EmbeddedTextures' folder already exists.\r\nWould you like to remove it? This may some time.\r\n\r\nNote: If an error window stating that 'the directory is not empty' appears, please try again or delete the folder manually.\r\n"); - if (!overwriteCheckOne) - { - ScriptError("An 'EmbeddedTextures' folder already exists. Please remove it.", "Error: Export already exists."); - return false; - } - Directory.Delete(EmbFolder, true); - return true; - } - return true; +// Adapted from original script by Grossley +using System.Text; +using System; +using System.IO; +using System.Threading.Tasks; + +EnsureDataLoaded(); + +string winFolder = GetFolder(FilePath); // The folder data.win is located in. +string EmbFolder = Path.Combine(winFolder, "EmbeddedTextures"); // The folder to write the image data to. + +if (!CanOverwrite()) + return; + +MakeFolder("EmbeddedTextures"); + +SetProgressBar(null, "Embedded textures", 0, Data.EmbeddedTextures.Count); +StartProgressBarUpdater(); + +await Task.Run(() => { + for (var i = 0; i < Data.EmbeddedTextures.Count; i++) + { + try + { + File.WriteAllBytes(Path.Combine(EmbFolder, i + ".png"), Data.EmbeddedTextures[i].TextureData.TextureBlob); + } + catch (Exception ex) + { + ScriptMessage("Failed to export file: " + ex.Message); + } + + IncrementProgress(); + } +}); + +await StopProgressBarUpdater(); +HideProgressBar(); +ScriptMessage("Export Complete.\n\nLocation: " + EmbFolder); + +/* Helper functions below. +*/ + +string GetFolder(string path) +{ + return Path.GetDirectoryName(path) + Path.DirectorySeparatorChar; +} + +void MakeFolder(String folderName) +{ + string MakeFolderPath = Path.Combine(winFolder, folderName); + if (!Directory.Exists(MakeFolderPath)) + Directory.CreateDirectory(MakeFolderPath); +} + +bool CanOverwrite() +{ + // Overwrite Folder Check One + if (Directory.Exists(EmbFolder)) + { + bool overwriteCheckOne = ScriptQuestion("An 'EmbeddedTextures' folder already exists.\r\nWould you like to remove it? This may some time.\r\n\r\nNote: If an error window stating that 'the directory is not empty' appears, please try again or delete the folder manually.\r\n"); + if (!overwriteCheckOne) + { + ScriptError("An 'EmbeddedTextures' folder already exists. Please remove it.", "Error: Export already exists."); + return false; + } + Directory.Delete(EmbFolder, true); + return true; + } + return true; } \ No newline at end of file diff --git a/UndertaleModTool/Unpackers/ExportAllRoomsToPng.csx b/UndertaleModTool/Unpackers/ExportAllRoomsToPng.csx index 0337c8365..0cf955a0c 100644 --- a/UndertaleModTool/Unpackers/ExportAllRoomsToPng.csx +++ b/UndertaleModTool/Unpackers/ExportAllRoomsToPng.csx @@ -1,106 +1,106 @@ -using System; -using System.Text; -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using System.Collections.Generic; -using System.Linq; -using System.Windows; -using System.Windows.Media; -using System.Windows.Media.Imaging; -using System.Windows.Controls; -using System.Runtime; - -using UndertaleModLib; -using UndertaleModLib.Util; -using UndertaleModLib.Models; - - -EnsureDataLoaded(); - -UndertaleModTool.MainWindow mainWindow = Application.Current.MainWindow as UndertaleModTool.MainWindow; - -ContentControl dataEditor = mainWindow.FindName("DataEditor") as ContentControl; -if (dataEditor is null) - throw new ScriptException("Can't find \"DataEditor\" control."); - -DependencyObject dataEditorChild = VisualTreeHelper.GetChild(dataEditor, 0); -if (dataEditorChild is null) - throw new ScriptException("Can't find \"DataEditor\" child control."); - -UndertaleRoomRenderer roomRenderer; -int roomCount = Data.Rooms.Count; - -string exportedTexturesFolder = PromptChooseDirectory("Choose an export folder"); -if (exportedTexturesFolder == null) - throw new ScriptException("The export folder was not set, stopping script."); - -bool displayGrid = ScriptQuestion("Draw background grid?"); - -if (mainWindow.IsGMS2 == Visibility.Visible) - if (!ScriptQuestion("Use the memory economy mode (uses less RAM, but slower)?")) - TileLayerTemplateSelector.ForcedMode = 1; // render tile layers as whole images - -DirectoryInfo dir = new DirectoryInfo(exportedTexturesFolder); -TextureWorker worker = new TextureWorker(); - -SetProgressBar(null, "Rooms Exported", 0, roomCount); -StartUpdater(); - -await DumpRooms(); -worker.Cleanup(); - -await StopUpdater(); -HideProgressBar(); - -GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce; // force full garbage collection -GC.Collect(); - -ScriptMessage("Exported successfully."); - - -async Task DumpRooms() -{ - for (int i = 0; i < roomCount; i++) { - if (IsAppClosed) - break; - - UndertaleRoom room = Data.Rooms[i]; - - mainWindow.Selected = room; - - if (roomRenderer is null) - { - await Task.Delay(150); - mainWindow.RoomRendererEnabled = true; - await Task.Delay(150); - - DependencyObject obj = VisualTreeHelper.GetChild(dataEditorChild, 0); - if (obj is UndertaleRoomRenderer) - roomRenderer = obj as UndertaleRoomRenderer; - else - throw new ScriptException("Can't find the room renderer object, try again."); - } - - DumpRoom(room.Name.Content, (i == roomCount - 1)); - } - - mainWindow.RoomRendererEnabled = false; -} - -void DumpRoom(string roomName, bool last) -{ - using (var file = File.OpenWrite(exportedTexturesFolder + System.IO.Path.DirectorySeparatorChar + roomName + ".png")) - { - try - { - roomRenderer.SaveImagePNG(file, displayGrid, last); - } - catch (Exception e) - { - throw new ScriptException($"An error occurred while exporting room \"{roomName}\".\n{e}"); - } - } - - IncProgress(); +using System; +using System.Text; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using System.Collections.Generic; +using System.Linq; +using System.Windows; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Controls; +using System.Runtime; + +using UndertaleModLib; +using UndertaleModLib.Util; +using UndertaleModLib.Models; + + +EnsureDataLoaded(); + +UndertaleModTool.MainWindow mainWindow = Application.Current.MainWindow as UndertaleModTool.MainWindow; + +ContentControl dataEditor = mainWindow.FindName("DataEditor") as ContentControl; +if (dataEditor is null) + throw new ScriptException("Can't find \"DataEditor\" control."); + +DependencyObject dataEditorChild = VisualTreeHelper.GetChild(dataEditor, 0); +if (dataEditorChild is null) + throw new ScriptException("Can't find \"DataEditor\" child control."); + +UndertaleRoomRenderer roomRenderer; +int roomCount = Data.Rooms.Count; + +string exportedTexturesFolder = PromptChooseDirectory(); +if (exportedTexturesFolder == null) + throw new ScriptException("The export folder was not set, stopping script."); + +bool displayGrid = ScriptQuestion("Draw background grid?"); + +if (mainWindow.IsGMS2 == Visibility.Visible) + if (!ScriptQuestion("Use the memory economy mode (uses less RAM, but slower)?")) + TileLayerTemplateSelector.ForcedMode = 1; // render tile layers as whole images + +DirectoryInfo dir = new DirectoryInfo(exportedTexturesFolder); +TextureWorker worker = new TextureWorker(); + +SetProgressBar(null, "Rooms Exported", 0, roomCount); +StartProgressBarUpdater(); + +await DumpRooms(); +worker.Cleanup(); + +await StopProgressBarUpdater(); +HideProgressBar(); + +GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce; // force full garbage collection +GC.Collect(); + +ScriptMessage("Exported successfully."); + + +async Task DumpRooms() +{ + for (int i = 0; i < roomCount; i++) { + if (IsAppClosed) + break; + + UndertaleRoom room = Data.Rooms[i]; + + mainWindow.Selected = room; + + if (roomRenderer is null) + { + await Task.Delay(150); + mainWindow.RoomRendererEnabled = true; + await Task.Delay(150); + + DependencyObject obj = VisualTreeHelper.GetChild(dataEditorChild, 0); + if (obj is UndertaleRoomRenderer) + roomRenderer = obj as UndertaleRoomRenderer; + else + throw new ScriptException("Can't find the room renderer object, try again."); + } + + DumpRoom(room.Name.Content, (i == roomCount - 1)); + } + + mainWindow.RoomRendererEnabled = false; +} + +void DumpRoom(string roomName, bool last) +{ + using (var file = File.OpenWrite(exportedTexturesFolder + System.IO.Path.DirectorySeparatorChar + roomName + ".png")) + { + try + { + roomRenderer.SaveImagePNG(file, displayGrid, last); + } + catch (Exception e) + { + throw new ScriptException($"An error occurred while exporting room \"{roomName}\".\n{e}"); + } + } + + IncrementProgress(); } \ No newline at end of file diff --git a/UndertaleModTool/Unpackers/ExportAllSounds.csx b/UndertaleModTool/Unpackers/ExportAllSounds.csx index dcf38f396..38e606a55 100644 --- a/UndertaleModTool/Unpackers/ExportAllSounds.csx +++ b/UndertaleModTool/Unpackers/ExportAllSounds.csx @@ -1,207 +1,207 @@ -// Original script by Kneesnap, updated by Grossley -using System.Text; -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; - -EnsureDataLoaded(); - -int maxCount; - -// Setup root export folder. -string winFolder = GetFolder(FilePath); // The folder data.win is located in. -bool usesAGRP = (Data.AudioGroups.Count > 0); - -//Overwrite Folder Check One -if (Directory.Exists(winFolder + "Exported_Sounds\\")) -{ - bool overwriteCheckOne = ScriptQuestion(@"A 'Exported_Sounds' folder already exists. -Would you like to remove it? This may some time. - -Note: If an error window stating that 'the directory is not empty' appears, please try again or delete the folder manually. -"); - if (overwriteCheckOne) - Directory.Delete(winFolder + "Exported_Sounds\\", true); - if (!overwriteCheckOne) - { - ScriptError("A 'Exported_Sounds' folder already exists. Please remove it.", "Error: Export already exists."); - return; - } -} - -var externalOGG_Copy = 0; - -// EXTERNAL OGG CHECK -bool externalOGGs = ScriptQuestion(@"This script exports embedded sounds. -However, it can also export the external OGGs to a separate folder. -If you would like to export both, select 'YES'. -If you just want the embedded sounds, select 'NO'. -"); - -if (externalOGGs) - externalOGG_Copy = 1; -if (!externalOGGs) - externalOGG_Copy = 0; - -// Overwrite Folder Check Two -if (Directory.Exists(winFolder + "External_Sounds\\") && externalOGG_Copy == 1) -{ - bool overwriteCheckTwo = ScriptQuestion(@"A 'External_Sounds' folder already exists. -Would you like to remove it? This may some time. - -Note: If an error window stating that 'the directory is not empty' appears, please try again or delete the folder manually. -"); - if (overwriteCheckTwo) - Directory.Delete(winFolder + "External_Sounds\\", true); - if (!overwriteCheckTwo) - { - ScriptError("A 'External_Sounds' folder already exists. Please remove it.", "Error: Export already exists."); - return; - } -} - -// Group by audio group check -var groupedExport = 0; -if (usesAGRP) -{ - bool groupedCheck = ScriptQuestion(@"Group sounds by audio group? - "); - if (groupedCheck) - groupedExport = 1; - if (!groupedCheck) - groupedExport = 0; -} - -byte[] EMPTY_WAV_FILE_BYTES = System.Convert.FromBase64String("UklGRiQAAABXQVZFZm10IBAAAAABAAIAQB8AAAB9AAAEABAAZGF0YQAAAAA="); -string DEFAULT_AUDIOGROUP_NAME = "audiogroup_default"; - -maxCount = Data.Sounds.Count; -SetProgressBar(null, "Sound", 0, maxCount); -StartUpdater(); - -await Task.Run(DumpSounds); // This runs sync, because it has to load audio groups. - -await StopUpdater(); -HideProgressBar(); -if (Directory.Exists(winFolder + "External_Sounds\\")) - ScriptMessage("Sounds exported to " + winFolder + " in the 'Exported_Sounds' and 'External_Sounds' folders."); -else - ScriptMessage("Sounds exported to " + winFolder + " in the 'Exported_Sounds' folder."); - -void IncProgressLocal() -{ - if (GetProgress() < maxCount) - IncProgress(); -} - -void MakeFolder(String folderName) -{ - if (!Directory.Exists(winFolder + folderName + "/")) - Directory.CreateDirectory(winFolder + folderName + "/"); -} - -string GetFolder(string path) -{ - return Path.GetDirectoryName(path) + Path.DirectorySeparatorChar; -} - -Dictionary> loadedAudioGroups; -IList GetAudioGroupData(UndertaleSound sound) -{ - if (loadedAudioGroups == null) - loadedAudioGroups = new Dictionary>(); - - string audioGroupName = sound.AudioGroup != null ? sound.AudioGroup.Name.Content : DEFAULT_AUDIOGROUP_NAME; - if (loadedAudioGroups.ContainsKey(audioGroupName)) - return loadedAudioGroups[audioGroupName]; - - string groupFilePath = winFolder + "audiogroup" + sound.GroupID + ".dat"; - if (!File.Exists(groupFilePath)) - return null; // Doesn't exist. - - try - { - UndertaleData data = null; - using (var stream = new FileStream(groupFilePath, FileMode.Open, FileAccess.Read)) - data = UndertaleIO.Read(stream, warning => ScriptMessage("A warning occured while trying to load " + audioGroupName + ":\n" + warning)); - - loadedAudioGroups[audioGroupName] = data.EmbeddedAudio; - return data.EmbeddedAudio; - } catch (Exception e) - { - ScriptMessage("An error occured while trying to load " + audioGroupName + ":\n" + e.Message); - return null; - } -} - -byte[] GetSoundData(UndertaleSound sound) -{ - if (sound.AudioFile != null) - return sound.AudioFile.Data; - - if (sound.GroupID > Data.GetBuiltinSoundGroupID()) - { - IList audioGroup = GetAudioGroupData(sound); - if (audioGroup != null) - return audioGroup[sound.AudioID].Data; - } - return EMPTY_WAV_FILE_BYTES; -} - -void DumpSounds() -{ - //MakeFolder("Exported_Sounds"); - foreach (UndertaleSound sound in Data.Sounds) - DumpSound(sound); -} - -void DumpSound(UndertaleSound sound) -{ - string soundName = sound.Name.Content; - bool flagCompressed = sound.Flags.HasFlag(UndertaleSound.AudioEntryFlags.IsCompressed); - bool flagEmbedded = sound.Flags.HasFlag(UndertaleSound.AudioEntryFlags.IsEmbedded); - // Compression, Streamed, Unpack on Load. - // 1 = 000 = IsEmbedded, Regular. '.wav' type saved in win. - // 2 = 100 = IsCompressed, Regular. '.ogg' type saved in win - // 3 = 101 = IsEmbedded, IsCompressed, Regular. '.ogg' type saved in win. - // 4 = 110 = Regular. '.ogg' type saved outside win. - string audioExt = ".ogg"; - string soundFilePath; - if (groupedExport == 1) - soundFilePath = winFolder + "Exported_Sounds\\" + sound.AudioGroup.Name.Content + "\\" + soundName; - else - soundFilePath = winFolder + "Exported_Sounds\\" + soundName; - MakeFolder("Exported_Sounds"); - if (groupedExport == 1) - MakeFolder("Exported_Sounds\\" + sound.AudioGroup.Name.Content); - bool process = true; - if (flagEmbedded && !flagCompressed) // 1. - audioExt = ".wav"; - else if (flagCompressed && !flagEmbedded) // 2. - audioExt = ".ogg"; - else if (flagCompressed && flagEmbedded) // 3. - audioExt = ".ogg"; - else if (!flagCompressed && !flagEmbedded) - { - process = false; - audioExt = ".ogg"; - string source = winFolder + soundName + audioExt; - string dest = winFolder + "External_Sounds\\" + soundName + audioExt; - if (externalOGG_Copy == 1) - { - if (groupedExport == 1) - { - dest = winFolder + "External_Sounds\\" + sound.AudioGroup.Name.Content + "\\" + soundName + audioExt; - MakeFolder("External_Sounds\\" + sound.AudioGroup.Name.Content); - } - MakeFolder("External_Sounds\\"); - System.IO.File.Copy(source, dest, false); - } - } - if (process && !File.Exists(soundFilePath + audioExt)) - File.WriteAllBytes(soundFilePath + audioExt, GetSoundData(sound)); - - IncProgressLocal(); -} - +// Original script by Kneesnap, updated by Grossley +using System.Text; +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +EnsureDataLoaded(); + +int maxCount; + +// Setup root export folder. +string winFolder = GetFolder(FilePath); // The folder data.win is located in. +bool usesAGRP = (Data.AudioGroups.Count > 0); + +//Overwrite Folder Check One +if (Directory.Exists(winFolder + "Exported_Sounds\\")) +{ + bool overwriteCheckOne = ScriptQuestion(@"A 'Exported_Sounds' folder already exists. +Would you like to remove it? This may some time. + +Note: If an error window stating that 'the directory is not empty' appears, please try again or delete the folder manually. +"); + if (overwriteCheckOne) + Directory.Delete(winFolder + "Exported_Sounds\\", true); + if (!overwriteCheckOne) + { + ScriptError("A 'Exported_Sounds' folder already exists. Please remove it.", "Error: Export already exists."); + return; + } +} + +var externalOGG_Copy = 0; + +// EXTERNAL OGG CHECK +bool externalOGGs = ScriptQuestion(@"This script exports embedded sounds. +However, it can also export the external OGGs to a separate folder. +If you would like to export both, select 'YES'. +If you just want the embedded sounds, select 'NO'. +"); + +if (externalOGGs) + externalOGG_Copy = 1; +if (!externalOGGs) + externalOGG_Copy = 0; + +// Overwrite Folder Check Two +if (Directory.Exists(winFolder + "External_Sounds\\") && externalOGG_Copy == 1) +{ + bool overwriteCheckTwo = ScriptQuestion(@"A 'External_Sounds' folder already exists. +Would you like to remove it? This may some time. + +Note: If an error window stating that 'the directory is not empty' appears, please try again or delete the folder manually. +"); + if (overwriteCheckTwo) + Directory.Delete(winFolder + "External_Sounds\\", true); + if (!overwriteCheckTwo) + { + ScriptError("A 'External_Sounds' folder already exists. Please remove it.", "Error: Export already exists."); + return; + } +} + +// Group by audio group check +var groupedExport = 0; +if (usesAGRP) +{ + bool groupedCheck = ScriptQuestion(@"Group sounds by audio group? + "); + if (groupedCheck) + groupedExport = 1; + if (!groupedCheck) + groupedExport = 0; +} + +byte[] EMPTY_WAV_FILE_BYTES = System.Convert.FromBase64String("UklGRiQAAABXQVZFZm10IBAAAAABAAIAQB8AAAB9AAAEABAAZGF0YQAAAAA="); +string DEFAULT_AUDIOGROUP_NAME = "audiogroup_default"; + +maxCount = Data.Sounds.Count; +SetProgressBar(null, "Sound", 0, maxCount); +StartProgressBarUpdater(); + +await Task.Run(DumpSounds); // This runs sync, because it has to load audio groups. + +await StopProgressBarUpdater(); +HideProgressBar(); +if (Directory.Exists(winFolder + "External_Sounds\\")) + ScriptMessage("Sounds exported to " + winFolder + " in the 'Exported_Sounds' and 'External_Sounds' folders."); +else + ScriptMessage("Sounds exported to " + winFolder + " in the 'Exported_Sounds' folder."); + +void IncProgressLocal() +{ + if (GetProgress() < maxCount) + IncrementProgress(); +} + +void MakeFolder(String folderName) +{ + if (!Directory.Exists(winFolder + folderName + "/")) + Directory.CreateDirectory(winFolder + folderName + "/"); +} + +string GetFolder(string path) +{ + return Path.GetDirectoryName(path) + Path.DirectorySeparatorChar; +} + +Dictionary> loadedAudioGroups; +IList GetAudioGroupData(UndertaleSound sound) +{ + if (loadedAudioGroups == null) + loadedAudioGroups = new Dictionary>(); + + string audioGroupName = sound.AudioGroup != null ? sound.AudioGroup.Name.Content : DEFAULT_AUDIOGROUP_NAME; + if (loadedAudioGroups.ContainsKey(audioGroupName)) + return loadedAudioGroups[audioGroupName]; + + string groupFilePath = winFolder + "audiogroup" + sound.GroupID + ".dat"; + if (!File.Exists(groupFilePath)) + return null; // Doesn't exist. + + try + { + UndertaleData data = null; + using (var stream = new FileStream(groupFilePath, FileMode.Open, FileAccess.Read)) + data = UndertaleIO.Read(stream, warning => ScriptMessage("A warning occured while trying to load " + audioGroupName + ":\n" + warning)); + + loadedAudioGroups[audioGroupName] = data.EmbeddedAudio; + return data.EmbeddedAudio; + } catch (Exception e) + { + ScriptMessage("An error occured while trying to load " + audioGroupName + ":\n" + e.Message); + return null; + } +} + +byte[] GetSoundData(UndertaleSound sound) +{ + if (sound.AudioFile != null) + return sound.AudioFile.Data; + + if (sound.GroupID > Data.GetBuiltinSoundGroupID()) + { + IList audioGroup = GetAudioGroupData(sound); + if (audioGroup != null) + return audioGroup[sound.AudioID].Data; + } + return EMPTY_WAV_FILE_BYTES; +} + +void DumpSounds() +{ + //MakeFolder("Exported_Sounds"); + foreach (UndertaleSound sound in Data.Sounds) + DumpSound(sound); +} + +void DumpSound(UndertaleSound sound) +{ + string soundName = sound.Name.Content; + bool flagCompressed = sound.Flags.HasFlag(UndertaleSound.AudioEntryFlags.IsCompressed); + bool flagEmbedded = sound.Flags.HasFlag(UndertaleSound.AudioEntryFlags.IsEmbedded); + // Compression, Streamed, Unpack on Load. + // 1 = 000 = IsEmbedded, Regular. '.wav' type saved in win. + // 2 = 100 = IsCompressed, Regular. '.ogg' type saved in win + // 3 = 101 = IsEmbedded, IsCompressed, Regular. '.ogg' type saved in win. + // 4 = 110 = Regular. '.ogg' type saved outside win. + string audioExt = ".ogg"; + string soundFilePath; + if (groupedExport == 1) + soundFilePath = winFolder + "Exported_Sounds\\" + sound.AudioGroup.Name.Content + "\\" + soundName; + else + soundFilePath = winFolder + "Exported_Sounds\\" + soundName; + MakeFolder("Exported_Sounds"); + if (groupedExport == 1) + MakeFolder("Exported_Sounds\\" + sound.AudioGroup.Name.Content); + bool process = true; + if (flagEmbedded && !flagCompressed) // 1. + audioExt = ".wav"; + else if (flagCompressed && !flagEmbedded) // 2. + audioExt = ".ogg"; + else if (flagCompressed && flagEmbedded) // 3. + audioExt = ".ogg"; + else if (!flagCompressed && !flagEmbedded) + { + process = false; + audioExt = ".ogg"; + string source = winFolder + soundName + audioExt; + string dest = winFolder + "External_Sounds\\" + soundName + audioExt; + if (externalOGG_Copy == 1) + { + if (groupedExport == 1) + { + dest = winFolder + "External_Sounds\\" + sound.AudioGroup.Name.Content + "\\" + soundName + audioExt; + MakeFolder("External_Sounds\\" + sound.AudioGroup.Name.Content); + } + MakeFolder("External_Sounds\\"); + System.IO.File.Copy(source, dest, false); + } + } + if (process && !File.Exists(soundFilePath + audioExt)) + File.WriteAllBytes(soundFilePath + audioExt, GetSoundData(sound)); + + IncProgressLocal(); +} + diff --git a/UndertaleModTool/Unpackers/ExportAllSprites.csx b/UndertaleModTool/Unpackers/ExportAllSprites.csx index 8d93115f4..37baad3ed 100644 --- a/UndertaleModTool/Unpackers/ExportAllSprites.csx +++ b/UndertaleModTool/Unpackers/ExportAllSprites.csx @@ -1,51 +1,51 @@ -// Modified with the help of Agentalex9 -using System.Text; -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using UndertaleModLib.Util; - -EnsureDataLoaded(); - -bool padded = (!ScriptQuestion("Export all sprites unpadded?")); - -string texFolder = GetFolder(FilePath) + "Export_Sprites" + Path.DirectorySeparatorChar; -TextureWorker worker = new TextureWorker(); -if (Directory.Exists(texFolder)) -{ - ScriptError("A sprites export already exists. Please remove it.", "Error"); - return; -} - -Directory.CreateDirectory(texFolder); - -SetProgressBar(null, "Sprites", 0, Data.Sprites.Count); -StartUpdater(); - -await DumpSprites(); -worker.Cleanup(); - -await StopUpdater(); -HideProgressBar(); -ScriptMessage("Export Complete.\n\nLocation: " + texFolder); - - -string GetFolder(string path) -{ - return Path.GetDirectoryName(path) + Path.DirectorySeparatorChar; -} - -async Task DumpSprites() -{ - await Task.Run(() => Parallel.ForEach(Data.Sprites, DumpSprite)); -} - -void DumpSprite(UndertaleSprite sprite) -{ - for (int i = 0; i < sprite.Textures.Count; i++) - if (sprite.Textures[i]?.Texture != null) - worker.ExportAsPNG(sprite.Textures[i].Texture, texFolder + sprite.Name.Content + "_" + i + ".png", null, padded); // Include padding to make sprites look neat! - - IncProgressP(); +// Modified with the help of Agentalex9 +using System.Text; +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using UndertaleModLib.Util; + +EnsureDataLoaded(); + +bool padded = (!ScriptQuestion("Export all sprites unpadded?")); + +string texFolder = GetFolder(FilePath) + "Export_Sprites" + Path.DirectorySeparatorChar; +TextureWorker worker = new TextureWorker(); +if (Directory.Exists(texFolder)) +{ + ScriptError("A sprites export already exists. Please remove it.", "Error"); + return; +} + +Directory.CreateDirectory(texFolder); + +SetProgressBar(null, "Sprites", 0, Data.Sprites.Count); +StartProgressBarUpdater(); + +await DumpSprites(); +worker.Cleanup(); + +await StopProgressBarUpdater(); +HideProgressBar(); +ScriptMessage("Export Complete.\n\nLocation: " + texFolder); + + +string GetFolder(string path) +{ + return Path.GetDirectoryName(path) + Path.DirectorySeparatorChar; +} + +async Task DumpSprites() +{ + await Task.Run(() => Parallel.ForEach(Data.Sprites, DumpSprite)); +} + +void DumpSprite(UndertaleSprite sprite) +{ + for (int i = 0; i < sprite.Textures.Count; i++) + if (sprite.Textures[i]?.Texture != null) + worker.ExportAsPNG(sprite.Textures[i].Texture, texFolder + sprite.Name.Content + "_" + i + ".png", null, padded); // Include padding to make sprites look neat! + + IncrementProgressParallel(); } \ No newline at end of file diff --git a/UndertaleModTool/Unpackers/ExportAllStrings.csx b/UndertaleModTool/Unpackers/ExportAllStrings.csx index 6a50be727..105939c5f 100644 --- a/UndertaleModTool/Unpackers/ExportAllStrings.csx +++ b/UndertaleModTool/Unpackers/ExportAllStrings.csx @@ -1,37 +1,37 @@ -//Adapted from original script by Grossley -using System.Text; -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using UndertaleModLib.Util; - -EnsureDataLoaded(); - -string exportFolder = PromptChooseDirectory("Export to where"); -if (exportFolder == null) - throw new ScriptException("The export folder was not set."); - -//Overwrite Check One -if (File.Exists(exportFolder + "strings.txt")) -{ - bool overwriteCheckOne = ScriptQuestion(@"A 'strings.txt' file already exists. -Would you like to overwrite it?"); - if (overwriteCheckOne) - File.Delete(exportFolder + "strings.txt"); - if (!overwriteCheckOne) - { - ScriptError("A 'strings.txt' file already exists. Please remove it and try again.", "Error: Export already exists."); - return; - } -} - -using (StreamWriter writer = new StreamWriter(exportFolder + "strings.txt")) -{ - foreach (var str in Data.Strings) - { - if (str.Content.Contains("\n") || str.Content.Contains("\r")) - continue; - writer.WriteLine(str.Content); - } -} +//Adapted from original script by Grossley +using System.Text; +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using UndertaleModLib.Util; + +EnsureDataLoaded(); + +string exportFolder = PromptChooseDirectory(); +if (exportFolder == null) + throw new ScriptException("The export folder was not set."); + +//Overwrite Check One +if (File.Exists(exportFolder + "strings.txt")) +{ + bool overwriteCheckOne = ScriptQuestion(@"A 'strings.txt' file already exists. +Would you like to overwrite it?"); + if (overwriteCheckOne) + File.Delete(exportFolder + "strings.txt"); + if (!overwriteCheckOne) + { + ScriptError("A 'strings.txt' file already exists. Please remove it and try again.", "Error: Export already exists."); + return; + } +} + +using (StreamWriter writer = new StreamWriter(exportFolder + "strings.txt")) +{ + foreach (var str in Data.Strings) + { + if (str.Content.Contains("\n") || str.Content.Contains("\r")) + continue; + writer.WriteLine(str.Content); + } +} diff --git a/UndertaleModTool/Unpackers/ExportAllTextures.csx b/UndertaleModTool/Unpackers/ExportAllTextures.csx index 1f0c3cec0..88ff02a36 100644 --- a/UndertaleModTool/Unpackers/ExportAllTextures.csx +++ b/UndertaleModTool/Unpackers/ExportAllTextures.csx @@ -1,99 +1,99 @@ -using System; -using System.IO; -using System.Drawing; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using UndertaleModLib.Util; - -EnsureDataLoaded(); - -// Start export of all existing textures - -string texFolder = Path.Combine(GetFolder(FilePath), "Export_Textures"); -if (Directory.Exists(texFolder)) -{ - ScriptError("A sprites export already exists. Please remove it.", "Error"); - return; -} - -Directory.CreateDirectory(texFolder); -string sprFolder = Path.Combine(texFolder, "Sprites"); -Directory.CreateDirectory(sprFolder); -string fntFolder = Path.Combine(texFolder, "Fonts"); -Directory.CreateDirectory(fntFolder); -string bgrFolder = Path.Combine(texFolder, "Backgrounds"); -Directory.CreateDirectory(bgrFolder); -TextureWorker worker = new TextureWorker(); - -SetProgressBar(null, "Textures Exported", 0, Data.TexturePageItems.Count); -StartUpdater(); - -await DumpSprites(); -await DumpFonts(); -await DumpBackgrounds(); -worker.Cleanup(); - -await StopUpdater(); -HideProgressBar(); -ScriptMessage("Export Complete.\n\nLocation: " + texFolder); - - -async Task DumpSprites() -{ - await Task.Run(() => Parallel.ForEach(Data.Sprites, DumpSprite)); -} - -async Task DumpBackgrounds() -{ - await Task.Run(() => Parallel.ForEach(Data.Backgrounds, DumpBackground)); -} - -async Task DumpFonts() -{ - await Task.Run(() => Parallel.ForEach(Data.Fonts, DumpFont)); -} - -void DumpSprite(UndertaleSprite sprite) -{ - for (int i = 0; i < sprite.Textures.Count; i++) - { - if (sprite.Textures[i]?.Texture != null) - { - UndertaleTexturePageItem tex = sprite.Textures[i].Texture; - worker.ExportAsPNG(tex, Path.Combine(sprFolder, sprite.Name.Content + "_" + i + ".png")); - } - } - - AddProgressP(sprite.Textures.Count); -} - -void DumpFont(UndertaleFont font) -{ - if (font.Texture != null) - { - UndertaleTexturePageItem tex = font.Texture; - worker.ExportAsPNG(tex, Path.Combine(fntFolder, font.Name.Content + "_0.png")); - - IncProgressP(); - } -} - -void DumpBackground(UndertaleBackground background) -{ - if (background.Texture != null) - { - UndertaleTexturePageItem tex = background.Texture; - worker.ExportAsPNG(tex, Path.Combine(bgrFolder, background.Name.Content + "_0.png")); - - IncProgressP(); - } -} - -string GetFolder(string path) -{ - return Path.GetDirectoryName(path) + Path.DirectorySeparatorChar; -} +using System; +using System.IO; +using System.Drawing; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using UndertaleModLib.Util; + +EnsureDataLoaded(); + +// Start export of all existing textures + +string texFolder = Path.Combine(GetFolder(FilePath), "Export_Textures"); +if (Directory.Exists(texFolder)) +{ + ScriptError("A sprites export already exists. Please remove it.", "Error"); + return; +} + +Directory.CreateDirectory(texFolder); +string sprFolder = Path.Combine(texFolder, "Sprites"); +Directory.CreateDirectory(sprFolder); +string fntFolder = Path.Combine(texFolder, "Fonts"); +Directory.CreateDirectory(fntFolder); +string bgrFolder = Path.Combine(texFolder, "Backgrounds"); +Directory.CreateDirectory(bgrFolder); +TextureWorker worker = new TextureWorker(); + +SetProgressBar(null, "Textures Exported", 0, Data.TexturePageItems.Count); +StartProgressBarUpdater(); + +await DumpSprites(); +await DumpFonts(); +await DumpBackgrounds(); +worker.Cleanup(); + +await StopProgressBarUpdater(); +HideProgressBar(); +ScriptMessage("Export Complete.\n\nLocation: " + texFolder); + + +async Task DumpSprites() +{ + await Task.Run(() => Parallel.ForEach(Data.Sprites, DumpSprite)); +} + +async Task DumpBackgrounds() +{ + await Task.Run(() => Parallel.ForEach(Data.Backgrounds, DumpBackground)); +} + +async Task DumpFonts() +{ + await Task.Run(() => Parallel.ForEach(Data.Fonts, DumpFont)); +} + +void DumpSprite(UndertaleSprite sprite) +{ + for (int i = 0; i < sprite.Textures.Count; i++) + { + if (sprite.Textures[i]?.Texture != null) + { + UndertaleTexturePageItem tex = sprite.Textures[i].Texture; + worker.ExportAsPNG(tex, Path.Combine(sprFolder, sprite.Name.Content + "_" + i + ".png")); + } + } + + AddProgressParallel(sprite.Textures.Count); +} + +void DumpFont(UndertaleFont font) +{ + if (font.Texture != null) + { + UndertaleTexturePageItem tex = font.Texture; + worker.ExportAsPNG(tex, Path.Combine(fntFolder, font.Name.Content + "_0.png")); + + IncrementProgressParallel(); + } +} + +void DumpBackground(UndertaleBackground background) +{ + if (background.Texture != null) + { + UndertaleTexturePageItem tex = background.Texture; + worker.ExportAsPNG(tex, Path.Combine(bgrFolder, background.Name.Content + "_0.png")); + + IncrementProgressParallel(); + } +} + +string GetFolder(string path) +{ + return Path.GetDirectoryName(path) + Path.DirectorySeparatorChar; +} diff --git a/UndertaleModTool/Unpackers/ExportAllTexturesGrouped.csx b/UndertaleModTool/Unpackers/ExportAllTexturesGrouped.csx index 8e22dd9d5..47b399b59 100644 --- a/UndertaleModTool/Unpackers/ExportAllTexturesGrouped.csx +++ b/UndertaleModTool/Unpackers/ExportAllTexturesGrouped.csx @@ -1,102 +1,102 @@ -using System; -using System.IO; -using System.Drawing; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using UndertaleModLib.Util; - -EnsureDataLoaded(); - -// Start export of all existing textures - -string texFolder = Path.Combine(GetFolder(FilePath), "Export_Textures"); -if (Directory.Exists(texFolder)) -{ - ScriptError("A sprites export already exists. Please remove it.", "Error"); - return; -} - -Directory.CreateDirectory(texFolder); -string sprFolder = Path.Combine(texFolder, "Sprites"); -Directory.CreateDirectory(sprFolder); -string fntFolder = Path.Combine(texFolder, "Fonts"); -Directory.CreateDirectory(fntFolder); -string bgrFolder = Path.Combine(texFolder, "Backgrounds"); -Directory.CreateDirectory(bgrFolder); -TextureWorker worker = new TextureWorker(); - -SetProgressBar(null, "Textures Exported", 0, Data.TexturePageItems.Count); -StartUpdater(); - -await DumpSprites(); -await DumpFonts(); -await DumpBackgrounds(); -worker.Cleanup(); - -await StopUpdater(); -HideProgressBar(); -ScriptMessage("Export Complete.\n\nLocation: " + texFolder); - -async Task DumpSprites() -{ - await Task.Run(() => Parallel.ForEach(Data.Sprites, DumpSprite)); -} - -async Task DumpBackgrounds() -{ - await Task.Run(() => Parallel.ForEach(Data.Backgrounds, DumpBackground)); -} - -async Task DumpFonts() -{ - await Task.Run(() => Parallel.ForEach(Data.Fonts, DumpFont)); -} - -void DumpSprite(UndertaleSprite sprite) -{ - for (int i = 0; i < sprite.Textures.Count; i++) - { - if (sprite.Textures[i]?.Texture != null) - { - UndertaleTexturePageItem tex = sprite.Textures[i].Texture; - string sprFolder2 = Path.Combine(sprFolder, sprite.Name.Content); - Directory.CreateDirectory(sprFolder2); - worker.ExportAsPNG(tex, Path.Combine(sprFolder2, sprite.Name.Content + "_" + i + ".png")); - } - } - - AddProgressP(sprite.Textures.Count); -} - -void DumpFont(UndertaleFont font) -{ - if (font.Texture != null) - { - UndertaleTexturePageItem tex = font.Texture; - string fntFolder2 = Path.Combine(fntFolder, font.Name.Content); - Directory.CreateDirectory(fntFolder2); - worker.ExportAsPNG(tex, Path.Combine(fntFolder2, font.Name.Content + "_0.png")); - IncProgressP(); - } -} - -void DumpBackground(UndertaleBackground background) -{ - if (background.Texture != null) - { - UndertaleTexturePageItem tex = background.Texture; - string bgrFolder2 = Path.Combine(bgrFolder, background.Name.Content); - Directory.CreateDirectory(bgrFolder2); - worker.ExportAsPNG(tex, Path.Combine(bgrFolder2, background.Name.Content + "_0.png")); - IncProgressP(); - } -} - -string GetFolder(string path) -{ - return Path.GetDirectoryName(path) + Path.DirectorySeparatorChar; -} +using System; +using System.IO; +using System.Drawing; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using UndertaleModLib.Util; + +EnsureDataLoaded(); + +// Start export of all existing textures + +string texFolder = Path.Combine(GetFolder(FilePath), "Export_Textures"); +if (Directory.Exists(texFolder)) +{ + ScriptError("A sprites export already exists. Please remove it.", "Error"); + return; +} + +Directory.CreateDirectory(texFolder); +string sprFolder = Path.Combine(texFolder, "Sprites"); +Directory.CreateDirectory(sprFolder); +string fntFolder = Path.Combine(texFolder, "Fonts"); +Directory.CreateDirectory(fntFolder); +string bgrFolder = Path.Combine(texFolder, "Backgrounds"); +Directory.CreateDirectory(bgrFolder); +TextureWorker worker = new TextureWorker(); + +SetProgressBar(null, "Textures Exported", 0, Data.TexturePageItems.Count); +StartProgressBarUpdater(); + +await DumpSprites(); +await DumpFonts(); +await DumpBackgrounds(); +worker.Cleanup(); + +await StopProgressBarUpdater(); +HideProgressBar(); +ScriptMessage("Export Complete.\n\nLocation: " + texFolder); + +async Task DumpSprites() +{ + await Task.Run(() => Parallel.ForEach(Data.Sprites, DumpSprite)); +} + +async Task DumpBackgrounds() +{ + await Task.Run(() => Parallel.ForEach(Data.Backgrounds, DumpBackground)); +} + +async Task DumpFonts() +{ + await Task.Run(() => Parallel.ForEach(Data.Fonts, DumpFont)); +} + +void DumpSprite(UndertaleSprite sprite) +{ + for (int i = 0; i < sprite.Textures.Count; i++) + { + if (sprite.Textures[i]?.Texture != null) + { + UndertaleTexturePageItem tex = sprite.Textures[i].Texture; + string sprFolder2 = Path.Combine(sprFolder, sprite.Name.Content); + Directory.CreateDirectory(sprFolder2); + worker.ExportAsPNG(tex, Path.Combine(sprFolder2, sprite.Name.Content + "_" + i + ".png")); + } + } + + AddProgressParallel(sprite.Textures.Count); +} + +void DumpFont(UndertaleFont font) +{ + if (font.Texture != null) + { + UndertaleTexturePageItem tex = font.Texture; + string fntFolder2 = Path.Combine(fntFolder, font.Name.Content); + Directory.CreateDirectory(fntFolder2); + worker.ExportAsPNG(tex, Path.Combine(fntFolder2, font.Name.Content + "_0.png")); + IncrementProgressParallel(); + } +} + +void DumpBackground(UndertaleBackground background) +{ + if (background.Texture != null) + { + UndertaleTexturePageItem tex = background.Texture; + string bgrFolder2 = Path.Combine(bgrFolder, background.Name.Content); + Directory.CreateDirectory(bgrFolder2); + worker.ExportAsPNG(tex, Path.Combine(bgrFolder2, background.Name.Content + "_0.png")); + IncrementProgressParallel(); + } +} + +string GetFolder(string path) +{ + return Path.GetDirectoryName(path) + Path.DirectorySeparatorChar; +} diff --git a/UndertaleModTool/Unpackers/ExportAllTilesets.csx b/UndertaleModTool/Unpackers/ExportAllTilesets.csx index f7849ccc2..f11e718a0 100644 --- a/UndertaleModTool/Unpackers/ExportAllTilesets.csx +++ b/UndertaleModTool/Unpackers/ExportAllTilesets.csx @@ -1,48 +1,48 @@ -using System.Text; -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using UndertaleModLib.Util; - -EnsureDataLoaded(); - -string texFolder = GetFolder(FilePath) + "Export_Tilesets" + Path.DirectorySeparatorChar; -TextureWorker worker = new TextureWorker(); -if (Directory.Exists(texFolder)) -{ - ScriptError("A texture export already exists. Please remove it.", "Error"); - return; -} - -Directory.CreateDirectory(texFolder); - -SetProgressBar(null, "Tilesets", 0, Data.Backgrounds.Count); -StartUpdater(); - -await DumpTilesets(); -worker.Cleanup(); - -await StopUpdater(); -HideProgressBar(); -ScriptMessage("Export Complete.\n\nLocation: " + texFolder); - - -string GetFolder(string path) -{ - return Path.GetDirectoryName(path) + Path.DirectorySeparatorChar; -} - - -async Task DumpTilesets() -{ - await Task.Run(() => Parallel.ForEach(Data.Backgrounds, DumpTileset)); -} - -void DumpTileset(UndertaleBackground tileset) -{ - if (tileset.Texture != null) - worker.ExportAsPNG(tileset.Texture, texFolder + tileset.Name.Content + ".png"); - - IncProgressP(); +using System.Text; +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using UndertaleModLib.Util; + +EnsureDataLoaded(); + +string texFolder = GetFolder(FilePath) + "Export_Tilesets" + Path.DirectorySeparatorChar; +TextureWorker worker = new TextureWorker(); +if (Directory.Exists(texFolder)) +{ + ScriptError("A texture export already exists. Please remove it.", "Error"); + return; +} + +Directory.CreateDirectory(texFolder); + +SetProgressBar(null, "Tilesets", 0, Data.Backgrounds.Count); +StartProgressBarUpdater(); + +await DumpTilesets(); +worker.Cleanup(); + +await StopProgressBarUpdater(); +HideProgressBar(); +ScriptMessage("Export Complete.\n\nLocation: " + texFolder); + + +string GetFolder(string path) +{ + return Path.GetDirectoryName(path) + Path.DirectorySeparatorChar; +} + + +async Task DumpTilesets() +{ + await Task.Run(() => Parallel.ForEach(Data.Backgrounds, DumpTileset)); +} + +void DumpTileset(UndertaleBackground tileset) +{ + if (tileset.Texture != null) + worker.ExportAsPNG(tileset.Texture, texFolder + tileset.Name.Content + ".png"); + + IncrementProgressParallel(); } \ No newline at end of file diff --git a/UndertaleModTool/Unpackers/ExportFontData.csx b/UndertaleModTool/Unpackers/ExportFontData.csx index 484cbdbbd..dfc52b999 100644 --- a/UndertaleModTool/Unpackers/ExportFontData.csx +++ b/UndertaleModTool/Unpackers/ExportFontData.csx @@ -1,137 +1,137 @@ -// Made by mono21400 - -using System.Text; -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using UndertaleModLib.Util; -using System.Linq; -using System.Windows.Forms; - -EnsureDataLoaded(); - -string fntFolder = GetFolder(FilePath) + "Export_Fonts" + Path.DirectorySeparatorChar; -TextureWorker worker = new TextureWorker(); -Directory.CreateDirectory(fntFolder); -List input = new List(); -if (ShowInputDialog() == System.Windows.Forms.DialogResult.Cancel) - return; - -string[] arrayString = input.ToArray(); - -SetProgressBar(null, "Fonts", 0, Data.Fonts.Count); -StartUpdater(); - -await DumpFonts(); -worker.Cleanup(); - -await StopUpdater(); -HideProgressBar(); -ScriptMessage("Export Complete.\n\nLocation: " + fntFolder); - - -string GetFolder(string path) -{ - return Path.GetDirectoryName(path) + Path.DirectorySeparatorChar; -} - -async Task DumpFonts() -{ - await Task.Run(() => Parallel.ForEach(Data.Fonts, DumpFont)); -} - -void DumpFont(UndertaleFont font) -{ - if (arrayString.Contains(font.Name.ToString().Replace("\"", ""))) - { - worker.ExportAsPNG(font.Texture, fntFolder + font.Name.Content + ".png"); - using (StreamWriter writer = new StreamWriter(fntFolder + "glyphs_" + font.Name.Content + ".csv")) - { - writer.WriteLine(font.DisplayName + ";" + font.EmSize + ";" + font.Bold + ";" + font.Italic + ";" + font.Charset + ";" + font.AntiAliasing + ";" + font.ScaleX + ";" + font.ScaleY); - - foreach (var g in font.Glyphs) - { - writer.WriteLine(g.Character + ";" + g.SourceX + ";" + g.SourceY + ";" + g.SourceWidth + ";" + g.SourceHeight + ";" + g.Shift + ";" + g.Offset); - } - } - } - - IncProgressP(); -} - -private DialogResult ShowInputDialog() -{ - System.Drawing.Size size = new System.Drawing.Size(400, 400); - Form inputBox = new Form(); - bool check_all = true; - - inputBox.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; - inputBox.ClientSize = size; - inputBox.Text = "Fonts exporter"; - - System.Windows.Forms.CheckedListBox fonts_list = new CheckedListBox(); - //fonts_list.Items.Add("All"); - foreach (var x in Data.Fonts) - { - fonts_list.Items.Add(x.Name.ToString().Replace("\"", "")); - } - - fonts_list.Size = new System.Drawing.Size(size.Width - 10, size.Height - 50); - fonts_list.Location = new System.Drawing.Point(5, 5); - inputBox.Controls.Add(fonts_list); - - Button okButton = new Button(); - okButton.DialogResult = System.Windows.Forms.DialogResult.OK; - okButton.Name = "okButton"; - okButton.Size = new System.Drawing.Size(75, 23); - okButton.Text = "&OK"; - okButton.Location = new System.Drawing.Point(size.Width - 80 - 80, size.Height - 39); - inputBox.Controls.Add(okButton); - - Button cancelButton = new Button(); - cancelButton.DialogResult = System.Windows.Forms.DialogResult.Cancel; - cancelButton.Name = "cancelButton"; - cancelButton.Size = new System.Drawing.Size(75, 23); - cancelButton.Text = "&Cancel"; - cancelButton.Location = new System.Drawing.Point(size.Width - 85, size.Height - 39); - inputBox.Controls.Add(cancelButton); - - Button toggleSelAllButton = new Button(); - toggleSelAllButton.Name = "toggleSelAllButton"; - toggleSelAllButton.Size = new System.Drawing.Size(80, 23); - toggleSelAllButton.Text = "&Select All"; - toggleSelAllButton.Location = new System.Drawing.Point(size.Width - 160 - 160 - 75, size.Height - 39); - inputBox.Controls.Add(toggleSelAllButton); - toggleSelAllButton.Click += toggleSelAllButton_Click; - - inputBox.AcceptButton = okButton; - inputBox.CancelButton = cancelButton; - - void toggleSelAllButton_Click(object sender, EventArgs e) - { - for (int i = 0; i < fonts_list.Items.Count; i++) - { - fonts_list.SetItemChecked(i, check_all); - } - if (check_all == true) - { - toggleSelAllButton.Text = "&Un-select All"; - check_all = false; - } - else - { - toggleSelAllButton.Text = "&Select All"; - check_all = true; - } - } - - DialogResult result = inputBox.ShowDialog(); - - foreach (var item in fonts_list.CheckedItems) - { - input.Add(item.ToString()); - } - - return result; +// Made by mono21400 + +using System.Text; +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using UndertaleModLib.Util; +using System.Linq; +using System.Windows.Forms; + +EnsureDataLoaded(); + +string fntFolder = GetFolder(FilePath) + "Export_Fonts" + Path.DirectorySeparatorChar; +TextureWorker worker = new TextureWorker(); +Directory.CreateDirectory(fntFolder); +List input = new List(); +if (ShowInputDialog() == System.Windows.Forms.DialogResult.Cancel) + return; + +string[] arrayString = input.ToArray(); + +SetProgressBar(null, "Fonts", 0, Data.Fonts.Count); +StartProgressBarUpdater(); + +await DumpFonts(); +worker.Cleanup(); + +await StopProgressBarUpdater(); +HideProgressBar(); +ScriptMessage("Export Complete.\n\nLocation: " + fntFolder); + + +string GetFolder(string path) +{ + return Path.GetDirectoryName(path) + Path.DirectorySeparatorChar; +} + +async Task DumpFonts() +{ + await Task.Run(() => Parallel.ForEach(Data.Fonts, DumpFont)); +} + +void DumpFont(UndertaleFont font) +{ + if (arrayString.Contains(font.Name.ToString().Replace("\"", ""))) + { + worker.ExportAsPNG(font.Texture, fntFolder + font.Name.Content + ".png"); + using (StreamWriter writer = new StreamWriter(fntFolder + "glyphs_" + font.Name.Content + ".csv")) + { + writer.WriteLine(font.DisplayName + ";" + font.EmSize + ";" + font.Bold + ";" + font.Italic + ";" + font.Charset + ";" + font.AntiAliasing + ";" + font.ScaleX + ";" + font.ScaleY); + + foreach (var g in font.Glyphs) + { + writer.WriteLine(g.Character + ";" + g.SourceX + ";" + g.SourceY + ";" + g.SourceWidth + ";" + g.SourceHeight + ";" + g.Shift + ";" + g.Offset); + } + } + } + + IncrementProgressParallel(); +} + +private DialogResult ShowInputDialog() +{ + System.Drawing.Size size = new System.Drawing.Size(400, 400); + Form inputBox = new Form(); + bool check_all = true; + + inputBox.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + inputBox.ClientSize = size; + inputBox.Text = "Fonts exporter"; + + System.Windows.Forms.CheckedListBox fonts_list = new CheckedListBox(); + //fonts_list.Items.Add("All"); + foreach (var x in Data.Fonts) + { + fonts_list.Items.Add(x.Name.ToString().Replace("\"", "")); + } + + fonts_list.Size = new System.Drawing.Size(size.Width - 10, size.Height - 50); + fonts_list.Location = new System.Drawing.Point(5, 5); + inputBox.Controls.Add(fonts_list); + + Button okButton = new Button(); + okButton.DialogResult = System.Windows.Forms.DialogResult.OK; + okButton.Name = "okButton"; + okButton.Size = new System.Drawing.Size(75, 23); + okButton.Text = "&OK"; + okButton.Location = new System.Drawing.Point(size.Width - 80 - 80, size.Height - 39); + inputBox.Controls.Add(okButton); + + Button cancelButton = new Button(); + cancelButton.DialogResult = System.Windows.Forms.DialogResult.Cancel; + cancelButton.Name = "cancelButton"; + cancelButton.Size = new System.Drawing.Size(75, 23); + cancelButton.Text = "&Cancel"; + cancelButton.Location = new System.Drawing.Point(size.Width - 85, size.Height - 39); + inputBox.Controls.Add(cancelButton); + + Button toggleSelAllButton = new Button(); + toggleSelAllButton.Name = "toggleSelAllButton"; + toggleSelAllButton.Size = new System.Drawing.Size(80, 23); + toggleSelAllButton.Text = "&Select All"; + toggleSelAllButton.Location = new System.Drawing.Point(size.Width - 160 - 160 - 75, size.Height - 39); + inputBox.Controls.Add(toggleSelAllButton); + toggleSelAllButton.Click += toggleSelAllButton_Click; + + inputBox.AcceptButton = okButton; + inputBox.CancelButton = cancelButton; + + void toggleSelAllButton_Click(object sender, EventArgs e) + { + for (int i = 0; i < fonts_list.Items.Count; i++) + { + fonts_list.SetItemChecked(i, check_all); + } + if (check_all == true) + { + toggleSelAllButton.Text = "&Un-select All"; + check_all = false; + } + else + { + toggleSelAllButton.Text = "&Select All"; + check_all = true; + } + } + + DialogResult result = inputBox.ShowDialog(); + + foreach (var item in fonts_list.CheckedItems) + { + input.Add(item.ToString()); + } + + return result; } \ No newline at end of file diff --git a/UndertaleModTool/Unpackers/ExportMasks.csx b/UndertaleModTool/Unpackers/ExportMasks.csx index 499c0e317..71c01699e 100644 --- a/UndertaleModTool/Unpackers/ExportMasks.csx +++ b/UndertaleModTool/Unpackers/ExportMasks.csx @@ -1,56 +1,56 @@ -// Made by Grossley -// Version 1 -// 12/07/2020 - -using System.Text; -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using UndertaleModLib.Util; - -EnsureDataLoaded(); - -string texFolder = GetFolder(FilePath) + "Export_Masks" + Path.DirectorySeparatorChar; -TextureWorker worker = new TextureWorker(); -if (Directory.Exists(texFolder)) -{ - ScriptError("A texture export already exists. Please remove it.", "Error"); - return; -} - -Directory.CreateDirectory(texFolder); - -SetProgressBar(null, "Sprites", 0, Data.Sprites.Count); -StartUpdater(); - -await DumpSprites(); -worker.Cleanup(); - -await StopUpdater(); -HideProgressBar(); -ScriptMessage("Export Complete.\n\nLocation: " + texFolder); - - -string GetFolder(string path) -{ - return Path.GetDirectoryName(path) + Path.DirectorySeparatorChar; -} - -async Task DumpSprites() -{ - await Task.Run(() => Parallel.ForEach(Data.Sprites, DumpSprite)); -} - -void DumpSprite(UndertaleSprite sprite) -{ - for (int i = 0; i < sprite.CollisionMasks.Count; i++) - { - if ((sprite.CollisionMasks[i]?.Data != null)) - { - TextureWorker.ExportCollisionMaskPNG(sprite, sprite.CollisionMasks[i], texFolder + sprite.Name.Content + "_" + i + ".png"); - } - } - - IncProgressP(); -} +// Made by Grossley +// Version 1 +// 12/07/2020 + +using System.Text; +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using UndertaleModLib.Util; + +EnsureDataLoaded(); + +string texFolder = GetFolder(FilePath) + "Export_Masks" + Path.DirectorySeparatorChar; +TextureWorker worker = new TextureWorker(); +if (Directory.Exists(texFolder)) +{ + ScriptError("A texture export already exists. Please remove it.", "Error"); + return; +} + +Directory.CreateDirectory(texFolder); + +SetProgressBar(null, "Sprites", 0, Data.Sprites.Count); +StartProgressBarUpdater(); + +await DumpSprites(); +worker.Cleanup(); + +await StopProgressBarUpdater(); +HideProgressBar(); +ScriptMessage("Export Complete.\n\nLocation: " + texFolder); + + +string GetFolder(string path) +{ + return Path.GetDirectoryName(path) + Path.DirectorySeparatorChar; +} + +async Task DumpSprites() +{ + await Task.Run(() => Parallel.ForEach(Data.Sprites, DumpSprite)); +} + +void DumpSprite(UndertaleSprite sprite) +{ + for (int i = 0; i < sprite.CollisionMasks.Count; i++) + { + if ((sprite.CollisionMasks[i]?.Data != null)) + { + TextureWorker.ExportCollisionMaskPNG(sprite, sprite.CollisionMasks[i], texFolder + sprite.Name.Content + "_" + i + ".png"); + } + } + + IncrementProgressParallel(); +} diff --git a/UndertaleModTool/Unpackers/ExportShaderData.csx b/UndertaleModTool/Unpackers/ExportShaderData.csx index 74e987805..728bbc9dc 100644 --- a/UndertaleModTool/Unpackers/ExportShaderData.csx +++ b/UndertaleModTool/Unpackers/ExportShaderData.csx @@ -1,53 +1,53 @@ -using System.Text; -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; - -EnsureDataLoaded(); - -string exportFolder = PromptChooseDirectory("Export to where"); -if (exportFolder == null) - throw new ScriptException("The export folder was not set."); - -Directory.CreateDirectory(exportFolder + "/Shader_Data/"); -File.WriteAllText(exportFolder + "/Shader_Data/" + "Import_Loc.txt", "Import location"); - -foreach(UndertaleShader shader in Data.Shaders) -{ - string exportBase = (exportFolder + "/Shader_Data/" + shader.Name.Content + "/"); - Directory.CreateDirectory(exportBase); - File.WriteAllText(exportBase + "Type.txt", shader.Type.ToString()); - File.WriteAllText(exportBase + "GLSL_ES_Fragment.txt", shader.GLSL_ES_Fragment.Content); - File.WriteAllText(exportBase + "GLSL_ES_Vertex.txt", shader.GLSL_ES_Vertex.Content); - File.WriteAllText(exportBase + "GLSL_Fragment.txt", shader.GLSL_Fragment.Content); - File.WriteAllText(exportBase + "GLSL_Vertex.txt", shader.GLSL_Vertex.Content); - File.WriteAllText(exportBase + "HLSL9_Fragment.txt", shader.HLSL9_Fragment.Content); - File.WriteAllText(exportBase + "HLSL9_Vertex.txt", shader.HLSL9_Vertex.Content); - if (shader.HLSL11_VertexData.IsNull == false) - File.WriteAllBytes(exportBase + "HLSL11_VertexData.bin", shader.HLSL11_VertexData.Data); - if (shader.HLSL11_PixelData.IsNull == false) - File.WriteAllBytes(exportBase + "HLSL11_PixelData.bin", shader.HLSL11_PixelData.Data); - if (shader.PSSL_VertexData.IsNull == false) - File.WriteAllBytes(exportBase + "PSSL_VertexData.bin", shader.PSSL_VertexData.Data); - if (shader.PSSL_PixelData.IsNull == false) - File.WriteAllBytes(exportBase + "PSSL_PixelData.bin", shader.PSSL_PixelData.Data); - if (shader.Cg_PSVita_VertexData.IsNull == false) - File.WriteAllBytes(exportBase + "Cg_PSVita_VertexData.bin", shader.Cg_PSVita_VertexData.Data); - if (shader.Cg_PSVita_PixelData.IsNull == false) - File.WriteAllBytes(exportBase + "Cg_PSVita_PixelData.bin", shader.Cg_PSVita_PixelData.Data); - if (shader.Cg_PS3_VertexData.IsNull == false) - File.WriteAllBytes(exportBase + "Cg_PS3_VertexData.bin", shader.Cg_PS3_VertexData.Data); - if (shader.Cg_PS3_PixelData.IsNull == false) - File.WriteAllBytes(exportBase + "Cg_PS3_PixelData.bin", shader.Cg_PS3_PixelData.Data); - string vertex = null; - for (var i = 0; i < shader.VertexShaderAttributes.Count; i++) - { - if (vertex == null) - vertex = ""; - vertex += shader.VertexShaderAttributes[i].Name.Content; - vertex += "\n"; - } - File.WriteAllText(exportBase + "VertexShaderAttributes.txt", ((vertex != null) ? vertex : "")); -} - +using System.Text; +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +EnsureDataLoaded(); + +string exportFolder = PromptChooseDirectory(); +if (exportFolder == null) + throw new ScriptException("The export folder was not set."); + +Directory.CreateDirectory(exportFolder + "/Shader_Data/"); +File.WriteAllText(exportFolder + "/Shader_Data/" + "Import_Loc.txt", "Import location"); + +foreach(UndertaleShader shader in Data.Shaders) +{ + string exportBase = (exportFolder + "/Shader_Data/" + shader.Name.Content + "/"); + Directory.CreateDirectory(exportBase); + File.WriteAllText(exportBase + "Type.txt", shader.Type.ToString()); + File.WriteAllText(exportBase + "GLSL_ES_Fragment.txt", shader.GLSL_ES_Fragment.Content); + File.WriteAllText(exportBase + "GLSL_ES_Vertex.txt", shader.GLSL_ES_Vertex.Content); + File.WriteAllText(exportBase + "GLSL_Fragment.txt", shader.GLSL_Fragment.Content); + File.WriteAllText(exportBase + "GLSL_Vertex.txt", shader.GLSL_Vertex.Content); + File.WriteAllText(exportBase + "HLSL9_Fragment.txt", shader.HLSL9_Fragment.Content); + File.WriteAllText(exportBase + "HLSL9_Vertex.txt", shader.HLSL9_Vertex.Content); + if (shader.HLSL11_VertexData.IsNull == false) + File.WriteAllBytes(exportBase + "HLSL11_VertexData.bin", shader.HLSL11_VertexData.Data); + if (shader.HLSL11_PixelData.IsNull == false) + File.WriteAllBytes(exportBase + "HLSL11_PixelData.bin", shader.HLSL11_PixelData.Data); + if (shader.PSSL_VertexData.IsNull == false) + File.WriteAllBytes(exportBase + "PSSL_VertexData.bin", shader.PSSL_VertexData.Data); + if (shader.PSSL_PixelData.IsNull == false) + File.WriteAllBytes(exportBase + "PSSL_PixelData.bin", shader.PSSL_PixelData.Data); + if (shader.Cg_PSVita_VertexData.IsNull == false) + File.WriteAllBytes(exportBase + "Cg_PSVita_VertexData.bin", shader.Cg_PSVita_VertexData.Data); + if (shader.Cg_PSVita_PixelData.IsNull == false) + File.WriteAllBytes(exportBase + "Cg_PSVita_PixelData.bin", shader.Cg_PSVita_PixelData.Data); + if (shader.Cg_PS3_VertexData.IsNull == false) + File.WriteAllBytes(exportBase + "Cg_PS3_VertexData.bin", shader.Cg_PS3_VertexData.Data); + if (shader.Cg_PS3_PixelData.IsNull == false) + File.WriteAllBytes(exportBase + "Cg_PS3_PixelData.bin", shader.Cg_PS3_PixelData.Data); + string vertex = null; + for (var i = 0; i < shader.VertexShaderAttributes.Count; i++) + { + if (vertex == null) + vertex = ""; + vertex += shader.VertexShaderAttributes[i].Name.Content; + vertex += "\n"; + } + File.WriteAllText(exportBase + "VertexShaderAttributes.txt", ((vertex != null) ? vertex : "")); +} + diff --git a/UndertaleModTool/Unpackers/MergeImages.csx b/UndertaleModTool/Unpackers/MergeImages.csx index f5d32f232..66982b35b 100644 --- a/UndertaleModTool/Unpackers/MergeImages.csx +++ b/UndertaleModTool/Unpackers/MergeImages.csx @@ -1,60 +1,60 @@ -using System; -using System.IO; -using System.Drawing; -using System.Drawing.Imaging; -using System.Drawing.Drawing2D; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using UndertaleModLib.Util; - -string importFolderA = PromptChooseDirectory("Import From Where"); -if (importFolderA == null) { - throw new ScriptException("The import folder was not set."); -} - -string importFolderB = PromptChooseDirectory("Merge With Where"); -if (importFolderB == null) { - throw new ScriptException("The import folder was not set."); -} - -string exportFolder = PromptChooseDirectory("Export To Where"); -if (exportFolder == null) { - throw new ScriptException("The export folder was not set."); -} - -string searchPattern = "*.png"; - -DirectoryInfo textureDirectoryA = new DirectoryInfo(importFolderA); -DirectoryInfo textureDirectoryB = new DirectoryInfo(importFolderB); -FileInfo[] filesA = textureDirectoryA.GetFiles(searchPattern, SearchOption.AllDirectories); - -foreach (FileInfo fileA in filesA) { - FileInfo[] fileMatch = textureDirectoryB.GetFiles(fileA.Name); - if (fileMatch == null) { - continue; - } - FileInfo fileB = fileMatch[0]; - Bitmap bitmapA = new Bitmap(System.IO.Path.Combine(importFolderA, fileA.Name)); - Bitmap bitmapB = new Bitmap(System.IO.Path.Combine(importFolderB, fileA.Name)); - int width = bitmapA.Size.Width + bitmapB.Size.Width; - int height = bitmapA.Size.Width; - if (bitmapB.Size.Width > height) { - height = bitmapB.Size.Width; - } - - Bitmap outputBitmap = new Bitmap(width, height); - - outputBitmap = SuperimposeOntoBitmap(bitmapA, outputBitmap, 0); - outputBitmap = SuperimposeOntoBitmap(bitmapB, outputBitmap, bitmapA.Size.Width); - - outputBitmap.Save(System.IO.Path.Combine(exportFolder, fileA.Name), ImageFormat.Png); -} - -Bitmap SuperimposeOntoBitmap(Bitmap bitmapToAdd, Bitmap baseBitmap, int x) { - Graphics g = Graphics.FromImage(baseBitmap); - g.CompositingMode = CompositingMode.SourceOver; - g.DrawImage(bitmapToAdd, new System.Drawing.Point(x, 0)); - return baseBitmap; -} +using System; +using System.IO; +using System.Drawing; +using System.Drawing.Imaging; +using System.Drawing.Drawing2D; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UndertaleModLib.Util; + +string importFolderA = PromptChooseDirectory(); +if (importFolderA == null) { + throw new ScriptException("The import folder was not set."); +} + +string importFolderB = PromptChooseDirectory(); +if (importFolderB == null) { + throw new ScriptException("The import folder was not set."); +} + +string exportFolder = PromptChooseDirectory(); +if (exportFolder == null) { + throw new ScriptException("The export folder was not set."); +} + +string searchPattern = "*.png"; + +DirectoryInfo textureDirectoryA = new DirectoryInfo(importFolderA); +DirectoryInfo textureDirectoryB = new DirectoryInfo(importFolderB); +FileInfo[] filesA = textureDirectoryA.GetFiles(searchPattern, SearchOption.AllDirectories); + +foreach (FileInfo fileA in filesA) { + FileInfo[] fileMatch = textureDirectoryB.GetFiles(fileA.Name); + if (fileMatch == null) { + continue; + } + FileInfo fileB = fileMatch[0]; + Bitmap bitmapA = new Bitmap(System.IO.Path.Combine(importFolderA, fileA.Name)); + Bitmap bitmapB = new Bitmap(System.IO.Path.Combine(importFolderB, fileA.Name)); + int width = bitmapA.Size.Width + bitmapB.Size.Width; + int height = bitmapA.Size.Width; + if (bitmapB.Size.Width > height) { + height = bitmapB.Size.Width; + } + + Bitmap outputBitmap = new Bitmap(width, height); + + outputBitmap = SuperimposeOntoBitmap(bitmapA, outputBitmap, 0); + outputBitmap = SuperimposeOntoBitmap(bitmapB, outputBitmap, bitmapA.Size.Width); + + outputBitmap.Save(System.IO.Path.Combine(exportFolder, fileA.Name), ImageFormat.Png); +} + +Bitmap SuperimposeOntoBitmap(Bitmap bitmapToAdd, Bitmap baseBitmap, int x) { + Graphics g = Graphics.FromImage(baseBitmap); + g.CompositingMode = CompositingMode.SourceOver; + g.DrawImage(bitmapToAdd, new System.Drawing.Point(x, 0)); + return baseBitmap; +} diff --git a/UndertaleModToolUpdater/UndertaleModToolUpdater.csproj b/UndertaleModToolUpdater/UndertaleModToolUpdater.csproj index a7dd39252..c699a9c7f 100644 --- a/UndertaleModToolUpdater/UndertaleModToolUpdater.csproj +++ b/UndertaleModToolUpdater/UndertaleModToolUpdater.csproj @@ -6,6 +6,7 @@ publish\ true win-x64;win-x86 + True