-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add encryption operation & refine UX
Signed-off-by: Bayu Satiyo <[email protected]>
- Loading branch information
1 parent
2659e48
commit 2853013
Showing
3 changed files
with
261 additions
and
47 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
namespace Reverse1999 | ||
{ | ||
internal class BadApple | ||
{ | ||
const string LAST_OPEN_FILE = "last_open.txt"; | ||
|
||
internal static void SaveLastOpenedFile(string filePath) | ||
{ | ||
try | ||
{ | ||
// Write the last opened directory to a text file | ||
using StreamWriter writer = new(LAST_OPEN_FILE); | ||
writer.WriteLine($"last_opened={filePath}"); | ||
} | ||
catch (Exception ex) | ||
{ | ||
throw new Exception(ex.Message); | ||
} | ||
} | ||
|
||
internal static string GetLastOpenedFile() | ||
{ | ||
string lastOpenedDirectory = string.Empty; | ||
|
||
if (!File.Exists(LAST_OPEN_FILE)) | ||
return lastOpenedDirectory; | ||
|
||
try | ||
{ | ||
// Read the last opened directory from the text file | ||
using StreamReader reader = new(LAST_OPEN_FILE); | ||
string? line; | ||
while ((line = reader.ReadLine()) != null) | ||
{ | ||
if (line.StartsWith("last_opened=")) | ||
{ | ||
lastOpenedDirectory = line["last_opened=".Length..]; | ||
break; | ||
} | ||
} | ||
} | ||
catch (Exception ex) | ||
{ | ||
throw new Exception(ex.Message); | ||
} | ||
|
||
return lastOpenedDirectory; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,90 +1,213 @@ | ||
using System.Reflection; | ||
using NativeFileDialogSharp; | ||
|
||
namespace Reverse1999 | ||
{ | ||
public class Timekeeper | ||
internal class Timekeeper | ||
{ | ||
const byte DECRYPTION_KEY = 0x55; | ||
const byte VERIFICATION_KEY = 0x6E; | ||
const string DECRYPTED_BUNDLES_DIR = "bundles_decoded"; | ||
const string ENCRYPTED_BUNDLES_DIR = "bundles_encoded"; | ||
const string BUNDLES_DIR = "bundles"; | ||
const string VERSION = "1.0.0"; | ||
private static readonly byte[] UNITYFS_ID = { 0x55, 0x6E, 0x69, 0x74, 0x79, 0x46, 0x53 }; | ||
private static readonly byte[] UNITYFS_ENCRYPTED_ID = | ||
{ | ||
0xDF, | ||
0xE4, | ||
0xE3, | ||
0xFE, | ||
0xF3, | ||
0xCC, | ||
0xD9 | ||
}; | ||
|
||
private enum OperationType | ||
{ | ||
Decrypt, | ||
Encrypt | ||
} | ||
|
||
class FileDecryptor | ||
private class BundleDecryptor | ||
{ | ||
public string DecryptedPath { get; } | ||
private static byte[] XorDataChunk(byte[] chunk, byte key) | ||
{ | ||
byte[] xoredChunk = new byte[chunk.Length]; | ||
|
||
for (int i = 0; i < chunk.Length; i++) | ||
xoredChunk[i] = (byte)(chunk[i] ^ key); | ||
|
||
return xoredChunk; | ||
} | ||
|
||
public FileDecryptor(string bundlesPath) | ||
private static bool TestHeader(byte[] originalHeader, byte[] comparisonHeader) | ||
{ | ||
DecryptedPath = Path.Combine(bundlesPath, DECRYPTED_BUNDLES_DIR); | ||
for (int i = 0; i < comparisonHeader.Length; i++) | ||
{ | ||
if (originalHeader[i] != comparisonHeader[i]) | ||
return false; | ||
} | ||
return true; | ||
} | ||
|
||
if (!Directory.Exists(DecryptedPath)) | ||
Directory.CreateDirectory(DecryptedPath); | ||
private static byte GenerateKey(byte key, OperationType operationType) | ||
{ | ||
return (byte)( | ||
key | ||
^ ( | ||
operationType == OperationType.Encrypt | ||
? UNITYFS_ENCRYPTED_ID[0] | ||
: UNITYFS_ID[0] | ||
) | ||
); | ||
} | ||
|
||
static byte[] DecryptDataChunk(byte[] chunk, byte key) | ||
private static string GetOutputPath(string assetPath, string assetFileName) | ||
{ | ||
byte[] decryptedChunk = new byte[chunk.Length]; | ||
string directory = Path.GetDirectoryName(assetPath) ?? string.Empty; | ||
|
||
for (int i = 0; i < chunk.Length; i++) | ||
decryptedChunk[i] = (byte)(chunk[i] ^ key); | ||
if (assetFileName.Contains("_MOD")) | ||
return Path.Combine(directory, assetFileName.Replace("_MOD", "_DEC")); | ||
|
||
return decryptedChunk; | ||
if (assetFileName.Contains("_DEC")) | ||
return Path.Combine(directory, assetFileName.Replace("_DEC", "_MOD")); | ||
|
||
return Path.Combine( | ||
directory, | ||
$"{Path.GetFileNameWithoutExtension(assetPath)}_DEC{Path.GetExtension(assetPath)}" | ||
); | ||
} | ||
|
||
public static void DecryptFile(string inputPath, string outputPath) | ||
private static void ProcessFile( | ||
string inputPath, | ||
string outputPath, | ||
OperationType operationType | ||
) | ||
{ | ||
byte[] inputData = File.ReadAllBytes(inputPath); | ||
byte key = (byte)(inputData[0] ^ DECRYPTION_KEY); // generate xor key from the first byte | ||
Console.WriteLine($"XOR Key: {key}"); | ||
try | ||
{ | ||
byte[] inputData = File.ReadAllBytes(inputPath); | ||
byte key = GenerateKey(inputData[0], operationType); | ||
|
||
byte[] comparisonHeader = | ||
operationType == OperationType.Encrypt ? UNITYFS_ID : UNITYFS_ENCRYPTED_ID; | ||
|
||
if (!TestHeader(inputData, comparisonHeader)) | ||
{ | ||
Console.WriteLine("Invalid asset bundle file!"); | ||
return; | ||
} | ||
|
||
if (key != (byte)(inputData[1] ^ VERIFICATION_KEY)) // verify key with the second byte | ||
throw new Exception("Invalid key"); | ||
Console.WriteLine($"Operation: {operationType}"); | ||
Console.WriteLine( | ||
$"Saving Xor-ed asset bundle {Path.GetFileName(inputPath)} as {outputPath}." | ||
); | ||
byte[] xoredData = XorDataChunk(inputData, key); | ||
File.WriteAllBytes(outputPath, xoredData); | ||
} | ||
catch (Exception ex) | ||
{ | ||
Console.WriteLine($"Error! {ex.Message}. Skipping..."); | ||
} | ||
} | ||
|
||
byte[] decryptedData = DecryptDataChunk(inputData, key); // decrypt the entire data | ||
File.WriteAllBytes(outputPath, decryptedData); | ||
public static void XorBundleFile(string inputPath, string outputPath) | ||
{ | ||
string assetFileName = Path.GetFileNameWithoutExtension(inputPath); | ||
OperationType operationType = assetFileName[^4..] switch | ||
{ | ||
"_DEC" => OperationType.Encrypt, | ||
_ => OperationType.Decrypt, | ||
}; | ||
ProcessFile(inputPath, outputPath, operationType); | ||
} | ||
|
||
public (TimeSpan Duration, int FilesDecrypted) DecryptBundles(string bundlesPath) | ||
public static (TimeSpan Duration, int FilesXored) XorBundleAssets(string[] assetPaths) | ||
{ | ||
DateTime startTime = DateTime.Now; | ||
int filesDecrypted = 0; | ||
int filesXored = 0; | ||
|
||
Parallel.ForEach( | ||
Directory.EnumerateFiles(bundlesPath, "*.dat", SearchOption.AllDirectories), | ||
assetPaths, | ||
new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount }, | ||
filePath => | ||
assetPath => | ||
{ | ||
string outputPath = Path.Combine(DecryptedPath, Path.GetFileName(filePath)); | ||
DecryptFile(filePath, outputPath); | ||
filesDecrypted++; | ||
Console.WriteLine( | ||
$"Decrypted {Path.GetFileName(filePath)} to {outputPath}" | ||
); | ||
try | ||
{ | ||
string assetFileName = Path.GetFileName(assetPath); | ||
string outputPath = GetOutputPath(assetPath, assetFileName); | ||
|
||
XorBundleFile(assetPath, outputPath); | ||
filesXored++; | ||
} | ||
catch { } | ||
} | ||
); | ||
|
||
TimeSpan duration = DateTime.Now - startTime; | ||
return (duration, filesDecrypted); | ||
return (duration, filesXored); | ||
} | ||
} | ||
|
||
static void Main() | ||
private static void PrintHelp() | ||
{ | ||
Console.Title = "Reverse: 1999 - Anarchist"; | ||
Console.WriteLine( | ||
"Reverse: 1999 - Anarchist is an asset encryptor & decryptor for Reverse: 1999 game by BLUEPOCH." | ||
); | ||
Console.WriteLine( | ||
"For more information, visit: https://github.com/kiraio-moe/Reverse1999-Anarchist" | ||
); | ||
Console.WriteLine($"Version: {VERSION}"); | ||
Console.WriteLine( | ||
"Usage: Asset bundle WITHOUT any suffix/has '_MOD' suffix will be DECRYPTED | '_DEC' suffix will be ENCRYPTED" | ||
); | ||
Console.WriteLine(); | ||
} | ||
|
||
private static void Main(string[] args) | ||
{ | ||
string? cwd = | ||
Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? string.Empty; | ||
string? bundlesPath = Path.Combine(cwd, BUNDLES_DIR); | ||
PrintHelp(); | ||
|
||
Console.WriteLine($"Path: {bundlesPath}"); | ||
string? cwd = Path.GetDirectoryName(AppContext.BaseDirectory); | ||
string[]? assetsPath = args; | ||
|
||
FileDecryptor decryptor = new(bundlesPath); | ||
(TimeSpan duration, int filesDecrypted) = decryptor.DecryptBundles(bundlesPath); | ||
PickFile: | ||
if (args.Length < 1) | ||
{ | ||
Console.WriteLine( | ||
"Press SPACE BAR to perform encryption/decryption operation, X to exit." | ||
); | ||
ConsoleKeyInfo keyInfo = Console.ReadKey(true); | ||
|
||
switch (keyInfo.Key) | ||
{ | ||
case ConsoleKey.Spacebar: | ||
Console.WriteLine("Opening file dialog..."); | ||
DialogResult filePicker = Dialog.FileOpenMultiple( | ||
"dat", | ||
Path.GetDirectoryName(BadApple.GetLastOpenedFile()) ?? cwd | ||
); | ||
|
||
if (filePicker.IsCancelled) | ||
{ | ||
Console.WriteLine("Canceled."); | ||
goto PickFile; | ||
} | ||
|
||
assetsPath = filePicker.Paths.ToArray(); | ||
BadApple.SaveLastOpenedFile(assetsPath[0]); | ||
break; | ||
|
||
case ConsoleKey.X: | ||
return; | ||
|
||
default: | ||
goto PickFile; | ||
} | ||
} | ||
|
||
(TimeSpan duration, int filesDecrypted) = BundleDecryptor.XorBundleAssets(assetsPath); | ||
double rps = filesDecrypted / duration.TotalSeconds; | ||
Console.WriteLine($"Decryption completed in {duration}. Rate: {rps:F2} files/sec"); | ||
|
||
Console.WriteLine("Press any key to exit"); | ||
Console.ReadLine(); | ||
Console.WriteLine($"Xor-ing completed in {duration}. Rate: {rps:F2} files/sec"); | ||
Console.WriteLine(); | ||
goto PickFile; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
#!/bin/bash | ||
|
||
# Define the target operating systems and architectures | ||
VERSION="1.0.0" | ||
TARGET_OS_ARCHITECTURES=("win-x64" "win-arm64" "linux-x64" "linux-arm64" "osx-x64" "osx-arm64") | ||
TARGET_FRAMEWORKS=("net6.0" "net7.0" "net8.0") | ||
|
||
echo "Building Reverse: 1999 - Anarchist..." | ||
# Build the project for each target OS architecture | ||
for os_arch in "${TARGET_OS_ARCHITECTURES[@]}" | ||
do | ||
for framework in "${TARGET_FRAMEWORKS[@]}" | ||
do | ||
echo "Building for $os_arch architecture..." | ||
dotnet publish -p:PublishSingleFile=true -c Release -f "$framework" -r "$os_arch" --no-self-contained | ||
|
||
# Check if build was successful | ||
if [ $? -eq 0 ]; then | ||
echo "Build for $os_arch completed successfully." | ||
else | ||
echo "Build for $os_arch failed." | ||
exit 1 # Exit the script with an error code | ||
fi | ||
done | ||
done | ||
|
||
echo "Build process completed for all target OS architectures." | ||
echo "Creating ZIP archive for every architecture..." | ||
for framework in "${TARGET_FRAMEWORKS[@]}" | ||
do | ||
for os_arch in "${TARGET_OS_ARCHITECTURES[@]}" | ||
do | ||
PUBLISH_PATH="bin/Release/${framework}/${os_arch}/publish" | ||
ZIP_OUTPUT="Reverse1999-Anarchist-v${VERSION}-${framework}-${os_arch}.zip" | ||
|
||
# Make a zip file for every architecture to be distributed | ||
cd "${PUBLISH_PATH}" | ||
zip -r "../../../${ZIP_OUTPUT}" * # /bin/Releases directory | ||
cd "../../../../../" # build.sh directory | ||
done | ||
done |