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