You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

348 lines
12 KiB
C#

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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<ANY_TAG>\nor\n@ANY_NAME\nANY_DIALOGUE\n<pb>\n breaks single file mode");
Console.WriteLine("Usage:");
Console.WriteLine(" --all <scriptFolder> <outputDirectory>");
Console.WriteLine(" --single <scriptFile> <alrFile> <outputDirectory>");
}
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<string> alrFileNames = Directory.GetFiles(inputDirectory)
.Where(f => Path.GetFileName(f).Contains("_alr", StringComparison.OrdinalIgnoreCase))
.ToList();
List<string> scriptFileNames = Directory.GetFiles(inputDirectory)
.Where(f => !Path.GetFileName(f).Contains("_alr", StringComparison.OrdinalIgnoreCase))
.ToList();
//Create lists to hold the files
List<AlrFile> alrFiles = new List<AlrFile>();
List<ScriptFile> scriptFiles = new List<ScriptFile>();
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<KeyValuePair>();
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<byte> newAlr = new List<byte>();
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<string> searchStrings = new List<string>() {
"\n",
"\n<voice name=",
"\n<textboxtype type=\"monologue",
"\n<textboxtype type=\"dialogue\">",
"\n<monologue background=\"on\">",
};
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<string> 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<KeyValuePair> Entries = new List<KeyValuePair>();
}
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;
}
}