Add new encryption type
This encryption type is come from the Soul Engine [http://soulengineproject.com/] (is this beta version of Light.vn?) that I found in HIMAWARI game (https://www.freem.ne.jp/win/game/17154). Thanks to Zakamutt (https://forums.fuwanovel.net/profile/1088-zakamutt/) for reporting this. How does this encryption work? The `.vndat` file are password protected with the same key as Light.vn `d6c5fKI3GgBWpZF3Tz6ia3kF0` while the contents of `.vndat` file isn't encrypted at all. Because of this, I need to rewrite the entire unpack and repack logic. Signed-off-by: Bayu Satiyo <itsyuukunz@gmail.com>
This commit is contained in:
parent
33848ae084
commit
ea9a31d407
@ -7,4 +7,8 @@
|
|||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="SharpZipLib" Version="1.4.2" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
268
Program.cs
268
Program.cs
@ -1,11 +1,11 @@
|
|||||||
using System.IO.Compression;
|
using System.Text;
|
||||||
using System.Text;
|
using ICSharpCode.SharpZipLib.Zip;
|
||||||
|
|
||||||
namespace LightvnTools
|
namespace LightvnTools
|
||||||
{
|
{
|
||||||
public class Program
|
public class Program
|
||||||
{
|
{
|
||||||
static readonly string VERSION = "1.0.0";
|
static readonly string VERSION = "1.1.0";
|
||||||
|
|
||||||
// PKZip signature
|
// PKZip signature
|
||||||
static readonly byte[] PKZIP = { 0x50, 0x4B, 0x03, 0x04 };
|
static readonly byte[] PKZIP = { 0x50, 0x4B, 0x03, 0x04 };
|
||||||
@ -34,70 +34,172 @@ namespace LightvnTools
|
|||||||
}
|
}
|
||||||
|
|
||||||
string input = args[0];
|
string input = args[0];
|
||||||
|
string zipPassword = Encoding.UTF8.GetString(KEY);
|
||||||
|
|
||||||
// Unpack .vndat
|
|
||||||
if (File.Exists(input))
|
if (File.Exists(input))
|
||||||
{
|
Unpack(input, Path.GetFileNameWithoutExtension(input), zipPassword);
|
||||||
if (!IsZip(input))
|
|
||||||
{
|
|
||||||
Console.WriteLine("Not a .vndat (zip) file!");
|
|
||||||
Console.ReadLine();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
string outputFolder = Path.GetFileNameWithoutExtension(input);
|
|
||||||
|
|
||||||
if (!Directory.Exists(outputFolder))
|
|
||||||
Directory.CreateDirectory(outputFolder);
|
|
||||||
|
|
||||||
// Extract .vndat file
|
|
||||||
Console.WriteLine($"Extracting {Path.GetFileName(input)} to ./{outputFolder}/...");
|
|
||||||
ZipFile.ExtractToDirectory(input, outputFolder, true);
|
|
||||||
|
|
||||||
// Decrypt .vndat file contents
|
|
||||||
string[] files = GetFilesRecursive(outputFolder);
|
|
||||||
|
|
||||||
foreach (string file in files)
|
|
||||||
{
|
|
||||||
Console.WriteLine($"Decrypting {file}...");
|
|
||||||
XORFile(file);
|
|
||||||
}
|
|
||||||
|
|
||||||
Console.WriteLine("Done.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Repack
|
|
||||||
if (Directory.Exists(input))
|
if (Directory.Exists(input))
|
||||||
{
|
Repack(input, zipPassword);
|
||||||
string[] files = GetFilesRecursive(input);
|
|
||||||
|
|
||||||
// Encrypting the files back
|
|
||||||
foreach (string file in files)
|
|
||||||
{
|
|
||||||
Console.WriteLine($"Encrypting {Path.GetFileName(file)}...");
|
|
||||||
XORFile(file);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Archiving to .vndat
|
|
||||||
string fileName = $"{input}.vndat";
|
|
||||||
|
|
||||||
Console.WriteLine($"Archiving as {fileName}...");
|
|
||||||
File.Copy(fileName, $"{fileName}.bak");
|
|
||||||
File.Delete(fileName);
|
|
||||||
ZipFile.CreateFromDirectory(input, fileName, CompressionLevel.Optimal, false);
|
|
||||||
|
|
||||||
Console.WriteLine("Done.");
|
|
||||||
}
|
|
||||||
|
|
||||||
Console.ReadLine();
|
Console.ReadLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Check if the given file is Zip or not.
|
/// Extract `.vndat` file.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="vndatFile"></param>
|
||||||
|
/// <param name="outputFolder"></param>
|
||||||
|
/// <param name="password"></param>
|
||||||
|
static void Unpack(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);
|
||||||
|
Directory.CreateDirectory(outputFolder);
|
||||||
|
|
||||||
|
// Old Light.vn encrypt the `.vndat` file with `KEY` as the password.
|
||||||
|
if (usePassword)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"{Path.GetFileName(vndatFile)} are password protected. Using `{password}` as the password.");
|
||||||
|
zipFile.Password = password;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (zipFile.Count > 0)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Extracting {Path.GetFileName(vndatFile)}...");
|
||||||
|
|
||||||
|
foreach (ZipEntry entry in zipFile)
|
||||||
|
{
|
||||||
|
string? entryPath = Path.Combine(outputFolder, entry.Name);
|
||||||
|
Directory.CreateDirectory(Path.GetDirectoryName(entryPath));
|
||||||
|
|
||||||
|
if (!entry.IsDirectory)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Writing {entryPath}...");
|
||||||
|
|
||||||
|
using Stream inputStream = zipFile.GetInputStream(entry);
|
||||||
|
using FileStream outputStream = File.Create(entryPath);
|
||||||
|
inputStream.CopyTo(outputStream);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Failed to write {entryPath}! {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine("Done.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only XOR `.vndat` contents that's not password protected.
|
||||||
|
if (!usePassword)
|
||||||
|
{
|
||||||
|
string[] files = GetFilesRecursive(outputFolder);
|
||||||
|
|
||||||
|
if (files.Length > 0)
|
||||||
|
{
|
||||||
|
foreach (string file in files)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Decrypting {file}...");
|
||||||
|
XorVndatContent(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine("Done.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Archive folder as `.vndat` file.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sourceFolder"></param>
|
||||||
|
/// <param name="password"></param>
|
||||||
|
static void Repack(string sourceFolder, string? password = "")
|
||||||
|
{
|
||||||
|
string outputFile = $"{Path.GetFileName(sourceFolder)}.vndat";
|
||||||
|
string[] files = GetFilesRecursive(sourceFolder);
|
||||||
|
string? tempFolder = $"{sourceFolder}_temp";
|
||||||
|
|
||||||
|
// Only backup original file once
|
||||||
|
string backupFile = $"{outputFile}.bak";
|
||||||
|
if (!File.Exists(backupFile))
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Backup the original file as {Path.GetFileName(backupFile)}...");
|
||||||
|
File.Copy(outputFile, backupFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool usePassword = IsPasswordProtectedZip(backupFile);
|
||||||
|
|
||||||
|
using ZipOutputStream zipStream = new(File.Create(outputFile));
|
||||||
|
|
||||||
|
// Uses the backup file to check if it's encrypted to bypass
|
||||||
|
// the file is being used by another process exception.
|
||||||
|
if (usePassword)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Encrypting {Path.GetFileName(outputFile)} using `{password}` as the password...");
|
||||||
|
zipStream.Password = password;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Creating a temporary copy of {Path.GetFileName(sourceFolder)} to perform XOR encryption...");
|
||||||
|
|
||||||
|
CopyFolder(sourceFolder, tempFolder);
|
||||||
|
files = GetFilesRecursive(tempFolder);
|
||||||
|
|
||||||
|
foreach (string file in files)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Encrypting {Path.GetRelativePath(sourceFolder, file)}...");
|
||||||
|
XorVndatContent(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine($"Creating {outputFile} archive...");
|
||||||
|
|
||||||
|
foreach (string filePath in files)
|
||||||
|
{
|
||||||
|
FileInfo file = new(filePath);
|
||||||
|
// Keep file structure by including the folder
|
||||||
|
string entryName = filePath[usePassword ? sourceFolder.Length.. : tempFolder.Length..].TrimStart('\\');
|
||||||
|
ZipEntry entry = new(entryName)
|
||||||
|
{
|
||||||
|
DateTime = DateTime.Now,
|
||||||
|
Size = file.Length
|
||||||
|
};
|
||||||
|
zipStream.PutNextEntry(entry);
|
||||||
|
|
||||||
|
using FileStream fileStream = file.OpenRead();
|
||||||
|
byte[] buffer = new byte[4096]; // Optimum size
|
||||||
|
int bytesRead;
|
||||||
|
while ((bytesRead = fileStream.Read(buffer, 0, buffer.Length)) > 0)
|
||||||
|
{
|
||||||
|
zipStream.Write(buffer, 0, bytesRead);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!usePassword)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Cleaning up temporary files...");
|
||||||
|
Directory.Delete(tempFolder, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine("Done.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check if the given file is `.vndat` file (Zip) or not.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="filePath"></param>
|
/// <param name="filePath"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
static bool IsZip(string filePath)
|
static bool IsVndat(string filePath)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -122,10 +224,37 @@ namespace LightvnTools
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// XOR the given file.
|
/// Check if the ZIP file is password protected.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="filePath"></param>
|
/// <param name="filePath"></param>
|
||||||
static void XORFile(string filePath)
|
/// <returns></returns>
|
||||||
|
static bool IsPasswordProtectedZip(string filePath)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using FileStream fileStream = new(filePath, FileMode.Open, FileAccess.Read);
|
||||||
|
using ZipInputStream zipStream = new(fileStream);
|
||||||
|
|
||||||
|
ZipEntry entry;
|
||||||
|
while ((entry = zipStream.GetNextEntry()) != null)
|
||||||
|
{
|
||||||
|
if (entry.IsCrypted)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Encrypt (XOR) `.vndat` file content.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="filePath"></param>
|
||||||
|
static void XorVndatContent(string filePath)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -181,5 +310,30 @@ namespace LightvnTools
|
|||||||
{
|
{
|
||||||
return Directory.GetFiles(sourceFolder, "*.*", SearchOption.AllDirectories);
|
return Directory.GetFiles(sourceFolder, "*.*", SearchOption.AllDirectories);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Copy entire files in a folder.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sourceDirectory"></param>
|
||||||
|
/// <param name="destinationDirectory"></param>
|
||||||
|
static void CopyFolder(string sourceDirectory, string destinationDirectory)
|
||||||
|
{
|
||||||
|
if (!Directory.Exists(destinationDirectory))
|
||||||
|
Directory.CreateDirectory(destinationDirectory);
|
||||||
|
|
||||||
|
string[] files = GetFilesRecursive(sourceDirectory);
|
||||||
|
|
||||||
|
foreach (string sourceFilePath in files)
|
||||||
|
{
|
||||||
|
string relativePath = sourceFilePath[sourceDirectory.Length..].TrimStart('\\');
|
||||||
|
string destinationFilePath = Path.Combine(destinationDirectory, relativePath);
|
||||||
|
|
||||||
|
string destinationFileDirectory = Path.GetDirectoryName(destinationFilePath);
|
||||||
|
if (!Directory.Exists(destinationFileDirectory))
|
||||||
|
Directory.CreateDirectory(destinationFileDirectory);
|
||||||
|
|
||||||
|
File.Copy(sourceFilePath, destinationFilePath, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user