Skip to content

Commit

Permalink
Implemented #8, #9 and #10
Browse files Browse the repository at this point in the history
Fixed a bug with duplicate files that was found in `Window kill`
  • Loading branch information
DmitriySalnikov committed Jan 21, 2024
1 parent b3040a0 commit 19efaee
Show file tree
Hide file tree
Showing 14 changed files with 315 additions and 101 deletions.
10 changes: 6 additions & 4 deletions Core/GodotPCKExplorer/PCKActions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -323,18 +323,19 @@ PCKReaderEncryptionKeyResult getEncKey()
/// <param name="dirPath">The directory from which the files will be recursively packed.</param>
/// <param name="filePath">The path to the new PCK file.</param>
/// <param name="strVer">A version of the file. Format: [pack version].[godot major].[godot minor].[godot patch] e.g. <c>2.4.1.1</c></param>
/// <param name="packPathPrefix">The path prefix in the pack. For example, if the prefix is <c>test_folder/</c>, then the path <c>res://icon.png</c> is converted to <c>res://test_folder/icon.png</c>.</param>
/// <param name="alignment">The address of each file will be aligned to this value.</param>
/// <param name="embed">If enabled and an existing <see cref="filePath"/> is specified, then the PCK will be embedded into this file.</param>
/// <param name="encKey">The encryption key if you want to encrypt a new PCK file.</param>
/// <param name="encIndex">Whether to encrypt the index (list of contents).</param>
/// <param name="encFiles">Whether to encrypt the contents of files.</param>
/// <param name="cancellationToken">Cancellation token to interrupt the extraction process.</param>
/// <returns><c>true</c> if successful</returns>
public static bool Pack(string dirPath, string filePath, string strVer, uint alignment = 16, bool embed = false, string? encKey = null, bool encIndex = false, bool encFiles = false, CancellationToken? cancellationToken = null)
public static bool Pack(string dirPath, string filePath, string strVer, string packPathPrefix = "", uint alignment = 16, bool embed = false, string? encKey = null, bool encIndex = false, bool encFiles = false, CancellationToken? cancellationToken = null)
{
if (Directory.Exists(dirPath))
{
return Pack(PCKUtils.GetListOfFilesToPack(dirPath), filePath, strVer, alignment, embed, encKey, encIndex, encFiles, cancellationToken);
return Pack(PCKUtils.GetListOfFilesToPack(dirPath), filePath, strVer, packPathPrefix, alignment, embed, encKey, encIndex, encFiles, cancellationToken);
}
else
{
Expand All @@ -349,14 +350,15 @@ public static bool Pack(string dirPath, string filePath, string strVer, uint ali
/// <param name="files">A list of files to be packed.</param>
/// <param name="filePath">The path to the new PCK file.</param>
/// <param name="strVer">A version of the file. Format: [pack version].[godot major].[godot minor].[godot patch] e.g. <c>2.4.1.1</c></param>
/// <param name="packPathPrefix">The path prefix in the pack. For example, if the prefix is <c>test_folder/</c>, then the path <c>res://icon.png</c> is converted to <c>res://test_folder/icon.png</c>.</param>
/// <param name="alignment">The address of each file will be aligned to this value.</param>
/// <param name="embed">If enabled and an existing <see cref="filePath"/> is specified, then the PCK will be embedded into this file.</param>
/// <param name="encKey">The encryption key if you want to encrypt a new PCK file.</param>
/// <param name="encIndex">Whether to encrypt the index (list of contents).</param>
/// <param name="encFiles">Whether to encrypt the contents of files.</param>
/// <param name="cancellationToken">Cancellation token to interrupt the extraction process.</param>
/// <returns><c>true</c> if successful</returns>
public static bool Pack(IEnumerable<PCKPackerFile> files, string filePath, string strVer, uint alignment = 16, bool embed = false, string? encKey = null, bool encIndex = false, bool encFiles = false, CancellationToken? cancellationToken = null)
public static bool Pack(IEnumerable<PCKPackerFile> files, string filePath, string strVer, string packPathPrefix = "", uint alignment = 16, bool embed = false, string? encKey = null, bool encIndex = false, bool encFiles = false, CancellationToken? cancellationToken = null)
{
if (!files.Any())
{
Expand Down Expand Up @@ -393,7 +395,7 @@ public static bool Pack(IEnumerable<PCKPackerFile> files, string filePath, strin
}
}

if (PCKPacker.PackFiles(filePath, embed, files, alignment, ver, PCKUtils.HexStringToByteArray(encKey), encIndex, encFiles, cancellationToken))
if (PCKPacker.PackFiles(filePath, embed, files, ver, packPathPrefix, alignment, PCKUtils.HexStringToByteArray(encKey), encIndex, encFiles, cancellationToken))
{
return true;
}
Expand Down
14 changes: 9 additions & 5 deletions Core/GodotPCKExplorer/PCKPacker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,18 +89,20 @@ static void CloseAndDeleteFile(BinaryWriter? writer, string out_pck)
/// <param name="outPck">Output file. It can be a new or an existing file.</param>
/// <param name="embed">If enabled and an existing <see cref="outPck"/> is specified, then the PCK will be embedded into this file.</param>
/// <param name="files">Enumeration of <see cref="PCKPackerRegularFile"/> files to be packed.</param>
/// <param name="alignment">The address of each file will be aligned to this value.</param>
/// <param name="godotVersion">PCK file version.</param>
/// <param name="packPathPrefix">The path prefix in the pack. For example, if the prefix is <c>test_folder/</c>, then the path <c>res://icon.png</c> is converted to <c>res://test_folder/icon.png</c>.</param>
/// <param name="alignment">The address of each file will be aligned to this value.</param>
/// <param name="encKey">Specify the encryption key if you want the file to be encrypted. To specify a <see cref="string"/>, look at <seealso cref="PCKUtils.HexStringToByteArray(string?)"/></param>
/// <param name="encrypt_index">Whether to encrypt the index (list of contents).</param>
/// <param name="encrypt_files">Whether to encrypt the contents of files.</param>
/// <param name="cancellationToken">Cancellation token to interrupt the extraction process.</param>
/// <returns><c>true</c> if successful</returns>
public static bool PackFiles(string outPck, bool embed, IEnumerable<PCKPackerFile> files, uint alignment, PCKVersion godotVersion, byte[]? encKey = null, bool encrypt_index = false, bool encrypt_files = false, CancellationToken? cancellationToken = null)
public static bool PackFiles(string outPck, bool embed, IEnumerable<PCKPackerFile> files, PCKVersion godotVersion, string packPathPrefix = "", uint alignment = 16, byte[]? encKey = null, bool encrypt_index = false, bool encrypt_files = false, CancellationToken? cancellationToken = null)
{
byte[]? EncryptionKey = encKey;
bool EncryptIndex = encrypt_index;
bool EncryptFiles = encrypt_files;
packPathPrefix = packPathPrefix.Replace("\\", "/");

const string baseOp = "Pack files";
var op = baseOp;
Expand Down Expand Up @@ -247,7 +249,8 @@ public static bool PackFiles(string outPck, bool embed, IEnumerable<PCKPackerFil
}

file_idx++;
var str = Encoding.UTF8.GetBytes(file.Path).ToList();
string res_file = PCKUtils.GetResFilePath(file.Path, packPathPrefix);
var str = Encoding.UTF8.GetBytes(res_file).ToList();
var str_len = str.Count;

// Godot 4's PCK uses padding for some reason...
Expand Down Expand Up @@ -282,7 +285,7 @@ public static bool PackFiles(string outPck, bool embed, IEnumerable<PCKPackerFil
file.IsEncrypted = EncryptFiles;
index_writer.Write((int)(file.IsEncrypted ? 1 : 0));

PCKActions.progress?.LogProgress(op, $"Calculated MD5: {file.Path}\n{PCKUtils.ByteArrayToHexString(file.MD5, " ")}");
PCKActions.progress?.LogProgress(op, $"Calculated MD5: {res_file}\n{PCKUtils.ByteArrayToHexString(file.MD5, " ")}");
}

PCKActions.progress?.LogProgress(op, (int)(((double)file_idx / files.Count()) * 100));
Expand Down Expand Up @@ -327,7 +330,8 @@ public static bool PackFiles(string outPck, bool embed, IEnumerable<PCKPackerFil
CloseAndDeleteFile(binWriter, outPck);
return false;
}
PCKActions.progress?.LogProgress(op, file.Path);

PCKActions.progress?.LogProgress(op, PCKUtils.GetResFilePath(file.Path, packPathPrefix));

// go back to store the file's offset
{
Expand Down
41 changes: 40 additions & 1 deletion Core/GodotPCKExplorer/PCKReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,7 @@ public bool OpenFile(BinaryReader fileReader, bool show_NotPCKError = true, bool
tmp_reader = new BinaryReader(mem);
}

List<PCKFile> tmp_files = new List<PCKFile>();
for (int i = 0; i < PCK_FileCount; i++)
{
int path_size = tmp_reader.ReadInt32();
Expand All @@ -369,7 +370,8 @@ public bool OpenFile(BinaryReader fileReader, bool show_NotPCKError = true, bool
PCKActions.progress?.LogProgress(op, $"{path}\nSize: {size} Flags: {flags}");
PCKActions.progress?.LogProgress(op, (int)(((double)i / PCK_FileCount) * 100));
}
Files.Add(path, new PCKFile(fileReader, path, ofs, pos_of_ofs, size, md5, flags, PCK_VersionPack));

tmp_files.Add(new PCKFile(fileReader, path, ofs, pos_of_ofs, size, md5, flags, PCK_VersionPack));

if (cancellationToken?.IsCancellationRequested ?? false)
{
Expand All @@ -383,6 +385,43 @@ public bool OpenFile(BinaryReader fileReader, bool show_NotPCKError = true, bool
}
};

// In some rare cases, the PCK may contain duplicate files
// So it is necessary to mark them as duplicates,
// because those lower on the list will overwrite those higher up.
{
Dictionary<string, int> duplicates = new Dictionary<string, int>();
for (int i = tmp_files.Count - 1; i >= 0; i--)
{
string tmp_path = tmp_files[i].FilePath;
if (!duplicates.ContainsKey(tmp_path))
{
duplicates.Add(tmp_path, 0);
}
else
{
duplicates[tmp_path]++;
}

if (duplicates[tmp_path] == 0)
{
continue;
}

string new_path = Path.ChangeExtension(tmp_path, $"duplicate_{duplicates[tmp_path]}" + Path.GetExtension(tmp_path));
tmp_files[i].FilePath = new_path;

if (logFileNamesProgress)
{
PCKActions.progress?.LogProgress(op, $"Duplicate file found. It will be renamed '{tmp_path}' -> '{new_path}'");
}
}

foreach (PCKFile file in tmp_files)
{
Files.Add(file.FilePath, file);
}
}

if (IsEncryptedIndex)
{
tmp_reader.Close();
Expand Down
5 changes: 5 additions & 0 deletions Core/GodotPCKExplorer/PCKUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,11 @@ public static void AddPadding(BinaryWriter p_file, long p_bytes, bool randomFill
}
}

public static string GetResFilePath(string path, string prefix)
{
return ("res://" + prefix + (path.StartsWith("res://") ? path.Replace("res://", "") : path)).Replace("\\", "/");
}

public static List<PCKPackerRegularFile> GetListOfFilesToPack(string folder, CancellationToken? cancellationToken = null)
{
if (!Directory.Exists(folder))
Expand Down
26 changes: 16 additions & 10 deletions Explorer/Console/ConsoleCommands.cs
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ static void PackPCKCommand(string[] args, bool embed)
string dirPath;
string filePath;
string strVer;
string prefix = "";
string? encKey = null;
string encType = "both";

Expand All @@ -235,26 +236,31 @@ static void PackPCKCommand(string[] args, bool embed)

if (args.Length > 4)
{
encKey = args[4];

if (!ValidateEncryptionKey(encKey))
return;
prefix = args[4];

if (args.Length > 5)
{
encType = args[5];
encKey = args[5];

if (!encyptionTypes.Contains(encType))
{
LogErrHelp($"Invalid encryption type: {encType}");
if (!ValidateEncryptionKey(encKey))
return;

if (args.Length > 6)
{
encType = args[6];

if (!encyptionTypes.Contains(encType))
{
LogErrHelp($"Invalid encryption type: {encType}");
return;
}
}
}
}
}
else
{
LogErrHelp($"Invalid number of arguments! Expected from 4 to 5, but got {args.Length}");
LogErrHelp($"Invalid number of arguments! Expected from 4 to 6, but got {args.Length}");
return;
}
}
Expand All @@ -281,7 +287,7 @@ static void PackPCKCommand(string[] args, bool embed)
break;
}

var res = PCKActions.Pack(dirPath, filePath, strVer, 16, embed, encKey, encIndex, encFiles);
var res = PCKActions.Pack(dirPath, filePath, strVer, prefix, 16, embed, encKey, encIndex, encFiles);
SetResult(res);
}

Expand Down
8 changes: 4 additions & 4 deletions Explorer/Console/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -86,14 +86,14 @@ public static void LogHelp()
-p Pack content of folder into .pck file
The version should be in this format: PACK_VERSION.GODOT_MINOR._MAJOR._PATCH
-p [path to folder] [output pack file] [version] {[encryption key]} {[encryption: both|index|files]}
-p [path to folder] [output pack file] [version] {[path prefix]} {[encryption key]} {[encryption: both|index|files]}
-p ""C:/Directory with files"" C:/Game_New.pck 1.3.2.0
-p ""C:/Directory with files"" C:/Game_New.pck 2.4.0.1
-p ""C:/Directory with files"" C:/Game_New.pck 2.4.0.1 7FDBF68B69B838194A6F1055395225BBA3F1C5689D08D71DCD620A7068F61CBA files
-p ""C:/Directory with files"" C:/Game_New.pck 2.4.0.1 """" 7FDBF68B69B838194A6F1055395225BBA3F1C5689D08D71DCD620A7068F61CBA files
-pe Pack embedded. Equal to just -p, but embed '.pck' into target file
-pe [path to folder] [exe to pack into] [version] {[encryption key]} {[encryption: both|index|files]}
-pe ""C:/Directory with files"" C:/Game.exe 1.3.2.0
-pe [path to folder] [exe to pack into] [version] {[path prefix]} {[encryption key]} {[encryption: both|index|files]}
-pe ""C:/Directory with files"" C:/Game.exe 1.3.2.0 ""mod_folder/""
-m Merge pack into target file. So you can copy the '.pck' from one file to another
-m [path to pack] [file to merge into]
Expand Down
Loading

0 comments on commit 19efaee

Please sign in to comment.