From 6c8b7ffc90aeb99f26661aef81f144d77c9037a8 Mon Sep 17 00:00:00 2001 From: Arneth Date: Sat, 24 May 2025 16:29:24 -0400 Subject: [PATCH] Add project files. --- KgK _alr regenerator.sln | 25 ++ .../KgK _alr regenerator.csproj | 11 + KgK _alr regenerator/Program.cs | 348 ++++++++++++++++++ 3 files changed, 384 insertions(+) create mode 100644 KgK _alr regenerator.sln create mode 100644 KgK _alr regenerator/KgK _alr regenerator.csproj create mode 100644 KgK _alr regenerator/Program.cs diff --git a/KgK _alr regenerator.sln b/KgK _alr regenerator.sln new file mode 100644 index 0000000..2f58977 --- /dev/null +++ b/KgK _alr regenerator.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.14.36109.1 d17.14 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KgK _alr regenerator", "KgK _alr regenerator\KgK _alr regenerator.csproj", "{76B5F35D-7A5E-4D10-87FD-EB3E2E057DA8}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {76B5F35D-7A5E-4D10-87FD-EB3E2E057DA8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {76B5F35D-7A5E-4D10-87FD-EB3E2E057DA8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {76B5F35D-7A5E-4D10-87FD-EB3E2E057DA8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {76B5F35D-7A5E-4D10-87FD-EB3E2E057DA8}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {63C2861D-EA7E-4084-AD27-8A668278BA06} + EndGlobalSection +EndGlobal diff --git a/KgK _alr regenerator/KgK _alr regenerator.csproj b/KgK _alr regenerator/KgK _alr regenerator.csproj new file mode 100644 index 0000000..98a6ad1 --- /dev/null +++ b/KgK _alr regenerator/KgK _alr regenerator.csproj @@ -0,0 +1,11 @@ + + + + Exe + net8.0 + KgK___alr_regenerator + enable + enable + + + diff --git a/KgK _alr regenerator/Program.cs b/KgK _alr regenerator/Program.cs new file mode 100644 index 0000000..9c2ddba --- /dev/null +++ b/KgK _alr regenerator/Program.cs @@ -0,0 +1,348 @@ +using System; +using System.Drawing; +using System.IO; +using System.Text; +using static Program; +using static System.Net.Mime.MediaTypeNames; +using static System.Runtime.InteropServices.JavaScript.JSType; + +class Program +{ + static void Main() + { + //TEST REMOVE LATER + string[] args = new string[] { "--all", "C:\\Users\\Lauren\\Desktop\\Ken Ga Kimi\\TEST", "C:\\Users\\Lauren\\Desktop\\Ken Ga Kimi\\TEST\\OUTPUT" }; + + if (args.Length < 1) + { + PrintUsage(); + return; + } + + string outputDirectory; + + switch (args[0]) + { + case "--all": + if (args.Length != 3) + { + Console.WriteLine("ERROR: incorrect number of arguments"); + PrintUsage(); + return; + } + string inputDirectory = args[1]; + outputDirectory = args[2]; + // Validate and process folders + RegenerateAll(inputDirectory, outputDirectory); + + break; + + case "--single": + if (args.Length != 4) + { + Console.WriteLine("ERROR: incorrect number of arguments"); + PrintUsage(); + return; + } + string scriptName = args[1]; + string alrName = args[2]; + outputDirectory = args[3]; + // Validate and process files + break; + + default: + PrintUsage(); + break; + } + } + + static void PrintUsage() + { + Console.WriteLine("Console tool for regenerating Ken ga Kimi _alr files"); + Console.WriteLine("Use to regenerate all _alr files or a single file"); + Console.WriteLine("Adding or removing \n\nor\n@ANY_NAME\nANY_DIALOGUE\n\n breaks single file mode"); + Console.WriteLine("Usage:"); + Console.WriteLine(" --all "); + Console.WriteLine(" --single "); + } + + public static void RegenerateAll(string inputDirectory, string outputDirectory) + { + if (!Directory.Exists(inputDirectory)) + { + Console.WriteLine($"ERROR: Input folder '{inputDirectory}' does not exist."); + return; + + } + //Read both _alr and script files from the input directory + List alrFileNames = Directory.GetFiles(inputDirectory) + .Where(f => Path.GetFileName(f).Contains("_alr", StringComparison.OrdinalIgnoreCase)) + .ToList(); + List scriptFileNames = Directory.GetFiles(inputDirectory) + .Where(f => !Path.GetFileName(f).Contains("_alr", StringComparison.OrdinalIgnoreCase)) + .ToList(); + + //Create lists to hold the files + List alrFiles = new List(); + List scriptFiles = new List(); + + if (alrFileNames.Count == 0) + { + Console.WriteLine("No _alr files found"); + return; + } + else + { + if (!Directory.Exists(outputDirectory)) + { + Directory.CreateDirectory(outputDirectory); + } + foreach (var file in alrFileNames) + { + alrFiles.Add(ReadAlrFile(file)); + } + foreach (var file in scriptFileNames) + { + scriptFiles.Add(ReadScriptFile(file)); + } + // Sort alrFiles by StartingFlag, with nulls first + alrFiles = alrFiles + .OrderBy(f => f.StartingFlag.HasValue ? 1 : 0) // nulls first + .ThenBy(f => f.StartingFlag) + .ToList(); + int position = 0; + foreach (var alrFile in alrFiles) + { + ScriptFile? matchingScript = scriptFiles.FirstOrDefault(s => string.Equals(s.ScriptTitle, alrFile.ScriptTitle.Substring(0, alrFile.ScriptTitle.Length - 4), StringComparison.OrdinalIgnoreCase)); + if (matchingScript != null) + { + byte[] alr = RegenerateAlrFile(alrFile, matchingScript, ref position); + File.WriteAllBytes(Path.Combine(outputDirectory, alrFile.FileName), alr); + } + else + { + Console.WriteLine($"No matching script file found for alr: {alrFile.ScriptTitle}"); + } + } + + Console.WriteLine($"Found {alrFiles.Count} _alr files in {inputDirectory}"); + } + } + + public static AlrFile ReadAlrFile(string filePath) + { + byte[] data = File.ReadAllBytes(filePath); + + int offset = 0; + int nameLen = BitConverter.ToInt32(data, offset); + offset += 4; + if (nameLen < 0 || data.Length < offset + nameLen) { + throw new InvalidDataException("Invalid file name length in _alr file."); + } + string scriptFileName = Encoding.UTF8.GetString(data, offset, nameLen); + offset += nameLen; + int size = BitConverter.ToInt32(data, offset); + offset += 4; + offset = RoundUpToNextMultipleOf4(offset); + int uabeaOffset = offset; + + var entries = new List(); + if (size > 0) + { + while (offset + 8 <= data.Length) + { + int key = BitConverter.ToInt32(data, offset); + int value = BitConverter.ToInt32(data, offset + 4); + entries.Add(new KeyValuePair { Key = key, Value = value }); + offset += 8; + } + } + + return new AlrFile + { + FileName = Path.GetFileName(filePath), + ScriptTitleLength = nameLen, + ScriptTitle = scriptFileName, + DataSize = size, + UabeaHeaderSize = uabeaOffset, + StartingFlag = entries.Count > 0 ? entries[0].Value : null, + Entries = entries + }; + } + + public static ScriptFile ReadScriptFile(string filePath) + { + string allScriptText = File.ReadAllText(filePath, Encoding.Unicode); + byte[] data = Encoding.Unicode.GetBytes(allScriptText); + + int offset = 0; + int nameLen = BitConverter.ToInt32(data, offset); + offset += 4; + if (nameLen < 0 || data.Length < offset + nameLen) + { + throw new InvalidDataException("Invalid file name length in _alr file."); + } + string scriptFileName = Encoding.UTF8.GetString(data, offset, nameLen); + offset += nameLen; + offset = RoundUpToNextMultipleOf4(offset); + int size = BitConverter.ToInt32(data, offset); + offset += 4; + if (data[offset+1] == 255 && data[offset + 2] == 254) { + offset += 2; + } + //offset = RoundUpToNextMultipleOf4(offset); + int uabeaOffset = offset; + + byte[] scriptText = data.Skip(uabeaOffset).ToArray(); + + return new ScriptFile + { + FileName = Path.GetFileName(filePath), + ScriptTitleLength = nameLen, + ScriptTitle = scriptFileName, + DataSize = size, + UabeaHeaderSize = uabeaOffset, + ScriptText = scriptText, + }; + } + + public static byte[] RegenerateAlrFile(AlrFile alr, ScriptFile script, ref int position) + { + List newAlr = new List(); + + newAlr.AddRange(BitConverter.GetBytes(alr.ScriptTitleLength)); + newAlr.AddRange(Encoding.UTF8.GetBytes(alr.ScriptTitle)); + + if (alr.StartingFlag == null) + { + newAlr.AddRange(BitConverter.GetBytes(0)); + int padding = (4 - (newAlr.Count % 4)) % 4; + for (int i = 0; i < padding; i++) + { + newAlr.Add(0x00); + } + return newAlr.ToArray(); + } + else + { + //Padding for title + int padding = (4 - (newAlr.Count % 4)) % 4; + for (int i = 0; i < padding; i++) + { + newAlr.Add(0x00); + } + } + List searchStrings = new List() { + "\n@", + "\n", + "\n", + }; + List<(string, int)> stringPositions = FindStringPositions(script.ScriptText, searchStrings); + //Add length of data + newAlr.AddRange(BitConverter.GetBytes(stringPositions.Count * 8)); + + //position += 1; + for (int i = 0; i < stringPositions.Count; i++) + { + newAlr.AddRange(BitConverter.GetBytes(stringPositions[i].Item2)); + newAlr.AddRange(BitConverter.GetBytes(position)); + if (stringPositions[i].Item1 != "\n@") + { + position += 1; + } + else if (stringPositions[i].Item1 == "\n@" && i > 0 && stringPositions[i - 1].Item1 != "\n@") + { + //Don't add to position if the previous string was a tag + } + else if (stringPositions.Count == 1) + { + position += 1; + } + + } + return newAlr.ToArray(); + } + + public static List<(string SearchString, int Position)> FindStringPositions(byte[] scriptBytes, IEnumerable searchStrings) + { + var results = new List<(string, int)>(); + // Decode the script bytes to string for line analysis + string scriptText = Encoding.Unicode.GetString(scriptBytes); + + foreach (var search in searchStrings) + { + byte[] searchBytes = Encoding.Unicode.GetBytes(search); + int index = 0; + while (index <= scriptBytes.Length - searchBytes.Length) + { + bool match = true; + for (int j = 0; j < searchBytes.Length; j++) + { + if (scriptBytes[index + j] != searchBytes[j]) + { + match = false; + break; + } + } + if (match) + { + // Find the character index in the string corresponding to this byte index + int charIndex = Encoding.Unicode.GetString(scriptBytes, 0, index).Length; + // Find the start of the line + int lineStart = scriptText.LastIndexOf('\n', charIndex - 1); + if (lineStart == -1) lineStart = 0; else lineStart++; + int lineEnd = scriptText.IndexOf('\n', charIndex); + if (lineEnd == -1) lineEnd = scriptText.Length; + string line = scriptText.Substring(lineStart, lineEnd - lineStart).TrimStart(); + + // Only add if the line does NOT start with // + if (!line.StartsWith("//")) + { + results.Add((search, index)); + } + index += searchBytes.Length; // Move past this match + } + else + { + index++; + } + } + } + // Sort by position in order of appearance + return results.OrderBy(r => r.Item2).ToList(); + } + + public static int RoundUpToNextMultipleOf4(int number) + { + return (number % 4 == 0) ? number : number + (4 - (number % 4)); + } + + public class AlrFile + { + public string? FileName; + public int ScriptTitleLength; + public string? ScriptTitle; + public int DataSize; + public int UabeaHeaderSize; + public int? StartingFlag; + public List Entries = new List(); + } + + public class ScriptFile + { + public string? FileName; + public int ScriptTitleLength; + public string? ScriptTitle; + public int DataSize; + public int? UabeaHeaderSize; + public byte[]? ScriptText; + } + + public struct KeyValuePair + { + public int Key; + public int Value; + } +} \ No newline at end of file