diff --git a/.vscode/launch.json b/.vscode/launch.json index f926839..d9f3659 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -10,7 +10,7 @@ "request": "launch", "preLaunchTask": "build", // If you have changed target frameworks, make sure to update the program path. - "program": "${workspaceFolder}/bin/Debug/net6.0/LightvnTools.dll", + "program": "${workspaceFolder}/bin/Debug/net8.0/LightvnTools.dll", "args": [], "cwd": "${workspaceFolder}", // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console @@ -23,4 +23,4 @@ "request": "attach" } ] -} \ No newline at end of file +} diff --git a/Program.cs b/LightvnTools.cs similarity index 72% rename from Program.cs rename to LightvnTools.cs index 4c30e56..752f310 100644 --- a/Program.cs +++ b/LightvnTools.cs @@ -3,14 +3,14 @@ using ICSharpCode.SharpZipLib.Zip; namespace LightvnTools { - public class Program + public class LightvnTools { - static readonly string VERSION = "1.1.0"; + static readonly string VERSION = "1.2.0"; // PKZip signature static readonly byte[] PKZIP = { 0x50, 0x4B, 0x03, 0x04 }; - // Key used to decrypt the file header and footer (reverse) + // Key used to XOR the file header and footer (reverse) // Text: `d6c5fKI3GgBWpZF3Tz6ia3kF0` // Source: https://github.com/morkt/GARbro/issues/440 static readonly byte[] KEY = { 0x64, 0x36, 0x63, 0x35, 0x66, 0x4B, 0x49, 0x33, 0x47, 0x67, 0x42, 0x57, 0x70, 0x5A, 0x46, 0x33, 0x54, 0x7A, 0x36, 0x69, 0x61, 0x33, 0x6B, 0x46, 0x30 }; @@ -23,26 +23,65 @@ namespace LightvnTools Console.WriteLine($"Light.vnTools v{VERSION}"); Console.WriteLine(); Console.WriteLine( - "Light.vnTools is an unpack and repacking tool for Light.vn game engine (lightvn.net)." + "Light.vnTools is an unpack and repacking tool for game made with Light.vn game engine (lightvn.net)." ); Console.WriteLine(); Console.WriteLine("Usage:"); - Console.WriteLine(" Unpack: Drag and drop '.vndat' file to 'LightvnTools.exe'"); + Console.WriteLine(" Unpack: Drag and drop '.vndat' / '.mcdat' file(s) to 'LightvnTools.exe'"); Console.WriteLine(" Repack: Drag and drop unpacked folder to 'LightvnTools.exe'"); - Console.ReadLine(); + Console.ReadKey(); return; } - string input = args[0]; string zipPassword = Encoding.UTF8.GetString(KEY); - if (File.Exists(input)) - Unpack(input, Path.GetFileNameWithoutExtension(input), zipPassword); + for (int i = 0; i < args.Length; i++) + { + if (File.Exists(args[i])) + { + if (IsVndat(args[i])) + { + UnpackVndat(args[i], Path.GetFileNameWithoutExtension(args[i]), zipPassword); + } + else if (Path.GetExtension(args[i]).Contains("mcdat")) + { + Console.WriteLine($"Decrypting {args[i]}..."); + XOR(args[i], $"{args[i]}.dec"); + } + else if (Path.GetExtension(args[i]).Contains("dec")) + { + Console.WriteLine($"Encrypting {args[i]}..."); + XOR(args[i], args[i].Replace("dec", "enc")); + } + else + { + Console.WriteLine($"Unsupported file! {args[i]}"); + break; + } + } - if (Directory.Exists(input)) - Repack(input, zipPassword); + if (Directory.Exists(args[i])) + { + RepackVndat(args[i], zipPassword); + GetFilesRecursive(args[i]).ToList().ForEach(path => + { + if (path.Contains("mcdat")) + { + Console.WriteLine($"Decrypting {path}..."); + XOR(path, $"{path}.dec"); + } + else if (path.Contains("dec")) + { + Console.WriteLine($"Encrypting {path}..."); + XOR(path, path.Replace("dec", "enc")); + } + }); + } + } - Console.ReadLine(); + Console.WriteLine("\nDone."); + Console.ReadKey(); + return; } /// @@ -51,14 +90,8 @@ namespace LightvnTools /// /// /// - static void Unpack(string vndatFile, string outputFolder, string? password = "") + static void UnpackVndat(string vndatFile, string outputFolder, string? password = "") { - if (!IsVndat(vndatFile)) - { - Console.WriteLine($"{Path.GetFileName(vndatFile)} isn\'t a `.vndat` (zip) file!"); - return; - } - bool usePassword = IsPasswordProtectedZip(vndatFile); using ZipFile zipFile = new(vndatFile); @@ -100,7 +133,7 @@ namespace LightvnTools Console.WriteLine("Done."); } - // Only XOR `.vndat` contents that's not password protected. + // Only XOR the `.vndat` contents if the archive is not password protected. if (!usePassword) { string[] files = GetFilesRecursive(outputFolder); @@ -109,8 +142,8 @@ namespace LightvnTools { foreach (string file in files) { - Console.WriteLine($"Decrypting {file}..."); - XorVndatContent(file); + Console.WriteLine($"XORing {file}..."); + XOR(file); } Console.WriteLine("Done."); @@ -123,7 +156,7 @@ namespace LightvnTools /// /// /// - static void Repack(string sourceFolder, string? password = "") + static void RepackVndat(string sourceFolder, string? password = "") { string outputFile = $"{Path.GetFileName(sourceFolder)}.vndat"; string[] files = GetFilesRecursive(sourceFolder); @@ -158,7 +191,7 @@ namespace LightvnTools foreach (string file in files) { Console.WriteLine($"Encrypting {Path.GetRelativePath(sourceFolder, file)}..."); - XorVndatContent(file); + XOR(file); } } @@ -251,48 +284,53 @@ namespace LightvnTools } /// - /// Encrypt (XOR) `.vndat` file content. + /// XOR data. + /// + /// + /// + static byte[] XOR(byte[] buffer) + { + if (buffer.Length < 100) + { + if (buffer.Length <= 0) + return buffer; + + // XOR entire bytes + for (int i = 0; i < buffer.Length; i++) + buffer[i] ^= REVERSED_KEY[i % KEY.Length]; + } + else + { + // XOR the first 100 bytes + for (int i = 0; i < 100; i++) + buffer[i] ^= KEY[i % KEY.Length]; + + // XOR the last 100 bytes + for (int i = 0; i < 99; i++) + buffer[buffer.Length - 99 + i] ^= REVERSED_KEY[i % KEY.Length]; + } + + return buffer; + } + + /// + /// Do XOR operation on the . /// /// - static void XorVndatContent(string filePath) + static void XOR(string filePath, string? outputFilePath = null) { try { byte[] buffer; int bufferLength; - using (FileStream inputStream = File.OpenRead(filePath)) - { - buffer = new byte[bufferLength = (int)inputStream.Length]; - inputStream.Read(buffer, 0, bufferLength); - } + using FileStream inputStream = File.OpenRead(filePath); + buffer = new byte[bufferLength = (int)inputStream.Length]; + inputStream.Read(buffer, 0, bufferLength); - if (bufferLength < 100) - { - if (bufferLength == 0) - { - Console.WriteLine($"Skipping {filePath}. File is empty."); - return; - } + buffer = XOR(buffer); - Console.WriteLine($"File size is smaller than 100 bytes: {filePath}"); - - // XOR entire bytes - for (int i = 0; i < bufferLength; i++) - buffer[i] ^= REVERSED_KEY[i % KEY.Length]; - } - else - { - // XOR the first 100 bytes - for (int i = 0; i < 100; i++) - buffer[i] ^= KEY[i % KEY.Length]; - - // XOR the last 100 bytes - for (int i = 0; i < 99; i++) - buffer[bufferLength - 99 + i] ^= REVERSED_KEY[i % KEY.Length]; - } - - using FileStream outputStream = File.OpenWrite(filePath); + using FileStream outputStream = File.OpenWrite(outputFilePath ?? filePath); outputStream.Write(buffer, 0, bufferLength); } catch (Exception ex) diff --git a/LightvnTools.csproj b/LightvnTools.csproj index 5c72cd5..b892e22 100644 --- a/LightvnTools.csproj +++ b/LightvnTools.csproj @@ -2,7 +2,7 @@ Exe - net6.0;net7.0 + net7.0;net8.0 enable enable diff --git a/README.md b/README.md index 9ba48ad..1a74e80 100644 --- a/README.md +++ b/README.md @@ -2,19 +2,27 @@ ## Overview -Light.vnTools is an unpack and repacking tool for game created with [Light.vn](https://lightvn.net "Light.vn") game engine. +Light.vnTools is an unpack/decrypt and repack/encrypt tool for game made with [Light.vn](https://lightvn.net "Light.vn") (Visual Novel) game engine. ## Requirements -- [.NET 6.0 SDK](https://dotnet.microsoft.com/download/dotnet/6.0 "Download .NET 6.0 SDK") or [.NET 7.0 SDK](https://dotnet.microsoft.com/download/dotnet/7.0 "Download .NET 7.0 SDK") +- [.NET 7.0 SDK](https://dotnet.microsoft.com/download/dotnet/7.0 "Download .NET 7.0 SDK") / [.NET 8.0 SDK](https://dotnet.microsoft.com/download/dotnet/8.0 "Download .NET 8.0 SDK") ## Usage +> [!IMPORTANT] +> The "new" encryption scheme that works on the newer Light.vn version is stripping the original file name, +> so I can't recover it. +> +> We're missing the original file extension, but we can guess it by reading the file header and set the extension based on that, +> +> For the time being you need to do it yourself by grabbing a hex editor program like [ImHex](https://github.com/WerWolv/ImHex "Visit ImHex GitHub repository"), find the **magic header**, do some Googling then rename the file. + - Download Light.vnTools at [Releases](https://github.com/kiraio-moe/Light.vnTools/releases "Light.vnTools Releases"). -- Unpack: - Drag and drop `.vndat` file to `LightvnTools.exe`. -- Repack: - Drag and drop **unpacked folder** to `LightvnTools.exe`. +- Unpack/Decrypt: + Drag and drop `.vndat` / `.mcdat` file(s) to the `LightvnTools.exe`. +- Repack/Encrypt: + Drag and drop **unpacked folder/file(s)** to the `LightvnTools.exe`. ## Credits @@ -28,4 +36,4 @@ For more information about the GNU General Public License version 3.0 (GNU GPL 3 ## Disclaimer -This tool is intentionally created as a modding tool to translate the visual novel game created with Light.vn game engine. I didn't condone any piracy in any forms such as taking the game visual and sell it illegally which harm the game developer. Use the tool at your own risk (DWYOR - Do What You Own Risk). +This tool is intentionally created as a modding tool to translate the visual novel game created with Light.vn game engine. I didn't condone any piracy in any forms such as taking the game visual and sell it illegally which harm the developer. Use the tool at your own risk (DWYOR - Do What You Own Risk).