Skip to content

Commit

Permalink
Add project files.
Browse files Browse the repository at this point in the history
  • Loading branch information
Cra-ZGuy committed Jan 4, 2025
1 parent 51ff586 commit d952d07
Show file tree
Hide file tree
Showing 11 changed files with 664 additions and 0 deletions.
87 changes: 87 additions & 0 deletions CommandLineHandler.cs
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();
}
}
}
25 changes: 25 additions & 0 deletions EncryptMC.csproj
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 added EncryptMC.ico
Binary file not shown.
25 changes: 25 additions & 0 deletions EncryptMC.sln
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
55 changes: 55 additions & 0 deletions InputValidator.cs
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();
}
}
}
}
154 changes: 154 additions & 0 deletions PackEncryption.cs
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;
}
}
}
Loading

0 comments on commit d952d07

Please sign in to comment.