-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
11 changed files
with
664 additions
and
0 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,87 @@ | ||
using System.Diagnostics; | ||
|
||
namespace EncryptMC | ||
{ | ||
internal static class CommandLineHandler | ||
{ | ||
internal static (string inputPath, string outputPath, string contentKey, bool isInteractiveMode) ParseArgs(string[] args) | ||
{ | ||
int argCount = args.Length; | ||
bool isInteractive = false; | ||
|
||
string inputPath; | ||
string outputPath; | ||
string contentKey; | ||
|
||
switch (argCount) | ||
{ | ||
case 0: // Interactive mode | ||
isInteractive = true; | ||
Console.Clear(); | ||
Console.WriteLine("===== EncryptMC =====\n"); | ||
|
||
Console.Write("Enter the input path: "); | ||
string? temp = Console.ReadLine(); | ||
if (string.IsNullOrEmpty(temp)) | ||
{ | ||
ShowError("Invalid input path."); | ||
return (string.Empty, string.Empty, string.Empty, true); | ||
} | ||
inputPath = Path.GetFullPath(temp); | ||
Console.WriteLine($"\nInput Path: {inputPath}\n"); | ||
|
||
Console.Write("Enter the output path: "); | ||
temp = Console.ReadLine(); | ||
if (string.IsNullOrEmpty(temp)) | ||
{ | ||
ShowError("Invalid output path."); | ||
return (string.Empty, string.Empty, string.Empty, true); | ||
} | ||
outputPath = Path.GetFullPath(temp); | ||
Console.WriteLine($"\nOutput Path: {outputPath}\n"); | ||
|
||
Console.Write("Enter the content key (must be 32 bytes): "); | ||
temp = Console.ReadLine(); | ||
if (string.IsNullOrEmpty(temp)) | ||
{ | ||
ShowError("Invalid content key."); | ||
return (string.Empty, string.Empty, string.Empty, true); | ||
} | ||
contentKey = temp; | ||
Console.WriteLine($"\nContent Key: {contentKey}\n"); | ||
break; | ||
|
||
case 3: // Command line mode | ||
inputPath = Path.GetFullPath(args[0]); | ||
outputPath = Path.GetFullPath(args[1]); | ||
contentKey = args[2]; | ||
|
||
Console.WriteLine($"\nInput Path: {inputPath}\n"); | ||
Console.WriteLine($"\nOutput Path: {outputPath}\n"); | ||
Console.WriteLine($"\nContent Key: {contentKey}\n"); | ||
break; | ||
|
||
default: // Invalid arguments | ||
ShowUsage(); | ||
return (string.Empty, string.Empty, string.Empty, false); | ||
} | ||
|
||
return (inputPath, outputPath, contentKey, isInteractive); | ||
} | ||
|
||
private static void ShowUsage() | ||
{ | ||
string exeName = Process.GetCurrentProcess().ProcessName; | ||
Console.ForegroundColor = ConsoleColor.Red; | ||
Console.WriteLine($"\nUsage: ./{exeName}.exe <inputPath> <outputPath> <contentKey>"); | ||
Console.ResetColor(); | ||
} | ||
|
||
private static void ShowError(string error) | ||
{ | ||
Console.ForegroundColor = ConsoleColor.Red; | ||
Console.WriteLine($"\n{error}"); | ||
Console.ResetColor(); | ||
} | ||
} | ||
} |
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,25 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<OutputType>Exe</OutputType> | ||
<TargetFramework>net8.0</TargetFramework> | ||
<ImplicitUsings>enable</ImplicitUsings> | ||
<Nullable>enable</Nullable> | ||
<PublishAot>true</PublishAot> | ||
<InvariantGlobalization>true</InvariantGlobalization> | ||
<StartupObject>EncryptMC.Program</StartupObject> | ||
<ApplicationIcon>EncryptMC.ico</ApplicationIcon> | ||
<Title>EncryptMC</Title> | ||
<Authors>James Deogrades</Authors> | ||
<Copyright>© 2025 James Deogrades. Licensed under the MIT License.</Copyright> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<Content Include="EncryptMC.ico" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> | ||
</ItemGroup> | ||
|
||
</Project> |
Binary file not shown.
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,25 @@ | ||
|
||
Microsoft Visual Studio Solution File, Format Version 12.00 | ||
# Visual Studio Version 17 | ||
VisualStudioVersion = 17.11.35312.102 | ||
MinimumVisualStudioVersion = 10.0.40219.1 | ||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EncryptMC", "EncryptMC.csproj", "{F6FD4BCA-FB8B-4D96-9272-FFC531B1E604}" | ||
EndProject | ||
Global | ||
GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||
Debug|Any CPU = Debug|Any CPU | ||
Release|Any CPU = Release|Any CPU | ||
EndGlobalSection | ||
GlobalSection(ProjectConfigurationPlatforms) = postSolution | ||
{F6FD4BCA-FB8B-4D96-9272-FFC531B1E604}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||
{F6FD4BCA-FB8B-4D96-9272-FFC531B1E604}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||
{F6FD4BCA-FB8B-4D96-9272-FFC531B1E604}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||
{F6FD4BCA-FB8B-4D96-9272-FFC531B1E604}.Release|Any CPU.Build.0 = Release|Any CPU | ||
EndGlobalSection | ||
GlobalSection(SolutionProperties) = preSolution | ||
HideSolutionNode = FALSE | ||
EndGlobalSection | ||
GlobalSection(ExtensibilityGlobals) = postSolution | ||
SolutionGuid = {D0E17F41-CEE9-4562-86ED-CA31E3F9201D} | ||
EndGlobalSection | ||
EndGlobal |
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,55 @@ | ||
using EncryptMC.Utility; | ||
|
||
namespace EncryptMC | ||
{ | ||
internal static class InputValidator | ||
{ | ||
internal static bool ValidatePathsAndKey(string inputPath, string outputPath, string contentKey, bool isInteractive) | ||
{ | ||
// Check input path | ||
if (!Directory.Exists(inputPath)) | ||
{ | ||
ShowError("Input path does not exist or is not a directory.\n", isInteractive); | ||
return false; | ||
} | ||
|
||
// Check output path | ||
if (!Directory.Exists(outputPath)) | ||
{ | ||
ShowError("Output path does not exist or is not a directory.\n", isInteractive); | ||
return false; | ||
} | ||
|
||
// Check if input path is a subpath of output path | ||
if (PathUtility.IsSubPath(inputPath, outputPath)) | ||
{ | ||
ShowError("Input path cannot be a subpath of output path.\n", isInteractive); | ||
return false; | ||
} | ||
|
||
// Check if content key is 32 bytes | ||
if (contentKey.Length != 32) | ||
{ | ||
ShowError("Content key must be exactly 32 bytes long.\n", isInteractive); | ||
return false; | ||
} | ||
|
||
return true; | ||
} | ||
|
||
private static void ShowError(string message, bool isInteractive) | ||
{ | ||
Console.ForegroundColor = ConsoleColor.Red; | ||
Console.WriteLine(message); | ||
Console.ResetColor(); | ||
|
||
if (isInteractive) | ||
{ | ||
Console.ForegroundColor = ConsoleColor.Yellow; | ||
Console.WriteLine("Press Enter to exit..."); | ||
Console.ReadKey(true); | ||
Console.ResetColor(); | ||
} | ||
} | ||
} | ||
} |
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,154 @@ | ||
using EncryptMC.Utility; | ||
using Newtonsoft.Json; | ||
using System.Text; | ||
|
||
namespace EncryptMC | ||
{ | ||
internal static class PackEncryption | ||
{ | ||
// Paths that should not be encrypted | ||
private static readonly HashSet<string> DoNotEncrypt = | ||
[ | ||
"contents.json", | ||
"manifest.json", | ||
"pack_icon.png", | ||
"texts" | ||
]; | ||
|
||
internal static void EncryptPack(string inputPath, string outputPath, string contentKey, string uuid) | ||
{ | ||
string contentsPath = Path.Combine(outputPath, "contents.json"); | ||
List<Dictionary<string, string>> contentsList = []; | ||
|
||
// Parallel traversal and encryption | ||
ParallelEncrypt(inputPath, outputPath, contentsList); | ||
|
||
// Prepare contents.json file | ||
var contentsFile = new Dictionary<string, object> | ||
{ | ||
{ "version", 1 }, | ||
{ "content", contentsList } | ||
}; | ||
|
||
// Serialize contents to JSON | ||
string jsonString = JsonConvert.SerializeObject(contentsFile, Formatting.None); | ||
|
||
// Encrypt JSON | ||
byte[] jsonData = Encoding.UTF8.GetBytes(jsonString); | ||
byte[] encryptedDataForContents = EncryptionUtility.EncryptData(contentKey, jsonData); | ||
|
||
// Write result to contents.json with special header | ||
using (FileStream fileStream = new(contentsPath, FileMode.Create, FileAccess.Write)) | ||
{ | ||
// Write header (4 + 4 + 8 = 16 bytes) | ||
fileStream.Write(BitConverter.GetBytes(0), 0, 4); // Zero bytes (4 bytes) | ||
fileStream.Write(BitConverter.GetBytes(0x9BCFB9FC), 0, 4); // Magic number (4 bytes) | ||
fileStream.Write(BitConverter.GetBytes(0L), 0, 8); // Padding (8 bytes) | ||
|
||
// Write UUID length and contents (1 byte for length + actual UUID) | ||
byte[] uuidBytes = Encoding.UTF8.GetBytes(uuid); | ||
fileStream.WriteByte((byte) uuidBytes.Length); | ||
fileStream.Write(uuidBytes, 0, uuidBytes.Length); | ||
|
||
// Add padding to reach required length (0xEF - length of UUID) | ||
int paddingLength = 0xEF - uuidBytes.Length; | ||
fileStream.Write(new byte[paddingLength], 0, paddingLength); | ||
|
||
// Write encrypted data | ||
fileStream.Write(encryptedDataForContents, 0, encryptedDataForContents.Length); | ||
} | ||
} | ||
|
||
private static void ParallelEncrypt(string inputPath, string outputPath, List<Dictionary<string, string>> contents) | ||
{ | ||
bool inputOutputPathsEqual = inputPath.Equals(outputPath, StringComparison.OrdinalIgnoreCase); | ||
|
||
// Create the base directory (just in case) | ||
PathUtility.EnsureOutputFolderExists(outputPath); | ||
|
||
// Create all subdirectories (sequentially) | ||
string[]? allDirectories = Directory.GetDirectories(inputPath, "*", SearchOption.AllDirectories); | ||
foreach (string dir in allDirectories) | ||
{ | ||
string relativeFilePath = Path.GetRelativePath(inputPath, dir).Replace("\\", "/"); | ||
string outputDir = Path.Combine(outputPath, relativeFilePath); | ||
|
||
Directory.CreateDirectory(outputDir); | ||
|
||
// Record this directory in final contents list | ||
lock (contents) | ||
{ | ||
contents.Add(new Dictionary<string, string> | ||
{ | ||
{ "path", relativeFilePath + "/" } | ||
}); | ||
} | ||
} | ||
|
||
// Grab all files | ||
string[]? allFiles = Directory.GetFiles(inputPath, "*", SearchOption.AllDirectories); | ||
|
||
// Process all files in parallel | ||
Parallel.ForEach(allFiles, file => | ||
{ | ||
string relativeFilePath = Path.GetRelativePath(inputPath, file).Replace("\\", "/"); | ||
string outputFilePath = Path.Combine(outputPath, relativeFilePath); | ||
bool shouldEncryptFile = ShouldEncrypt(relativeFilePath, DoNotEncrypt); | ||
|
||
if (shouldEncryptFile) | ||
{ | ||
string fileContentKey = EncryptionUtility.GenerateContentKey(); | ||
|
||
// Encrypt file contents | ||
byte[] fileContents = File.ReadAllBytes(file); | ||
EncryptionUtility.WriteDataToEncryptedFile(outputFilePath, fileContents, fileContentKey); | ||
|
||
// Add file reference with encryption key | ||
lock (contents) | ||
{ | ||
contents.Add(new Dictionary<string, string> | ||
{ | ||
{ "key", fileContentKey }, | ||
{ "path", relativeFilePath } | ||
}); | ||
} | ||
} | ||
else | ||
{ | ||
if (!inputOutputPathsEqual) | ||
{ | ||
// Copy file without encryption | ||
Directory.CreateDirectory(Path.GetDirectoryName(outputFilePath)!); | ||
File.Copy(file, outputFilePath, overwrite: true); | ||
} | ||
|
||
// Add file reference without encryption key | ||
lock (contents) | ||
{ | ||
contents.Add(new Dictionary<string, string> | ||
{ | ||
{ "path", relativeFilePath } | ||
}); | ||
} | ||
} | ||
}); | ||
} | ||
|
||
private static bool ShouldEncrypt(string relativePath, HashSet<string> doNotEncrypt) | ||
{ | ||
// Normalize path to forward slashes | ||
string normalizedPath = relativePath.Replace("\\", "/").TrimStart('/'); | ||
|
||
// Check if path is in DoNotEncrypt set or subdirectory | ||
foreach (string excludePath in doNotEncrypt) | ||
{ | ||
string? cleanedExclude = excludePath.Replace("\\", "/").TrimStart('/'); | ||
if (normalizedPath.StartsWith(cleanedExclude, StringComparison.OrdinalIgnoreCase)) | ||
{ | ||
return false; | ||
} | ||
} | ||
return true; | ||
} | ||
} | ||
} |
Oops, something went wrong.