diff --git a/LibGGPK/GrindingGearsPackageContainer.cs b/LibGGPK/GrindingGearsPackageContainer.cs
index da2cadf..36db641 100644
--- a/LibGGPK/GrindingGearsPackageContainer.cs
+++ b/LibGGPK/GrindingGearsPackageContainer.cs
@@ -29,7 +29,7 @@ public class GrindingGearsPackageContainer
/// An estimation of the number of records in the Contents.GGPK file. This is only
/// used to inform the users of the parsing progress.
///
- private const int EstimatedFileCount = 175000;
+ private const int EstimatedFileCount = 700000;
public bool IsReadOnly { get { return _isReadOnly; } }
private bool _isReadOnly;
@@ -48,9 +48,6 @@ public GrindingGearsPackageContainer()
}
#region Read GGPK
-
-
-
///
/// Parses the GGPK pack file and builds a directory tree from it.
///
@@ -74,6 +71,163 @@ public void Read(string pathToGgpk, Action output)
}
CreateDirectoryTree(output);
+
+ if (output != null)
+ {
+ output(Environment.NewLine);
+ output("Finished!" + Environment.NewLine);
+ }
+ }
+
+ public void Read(string pathToGgpk, string pathToBin, Action output)
+ {
+ _pathToGppk = pathToGgpk;
+ if (output != null)
+ {
+ output("Parsing GGPK..." + Environment.NewLine);
+ output("Reading bin file records:" + Environment.NewLine);
+ }
+
+ DeserializeRecords(pathToBin, output);
+
+ if (output != null)
+ {
+ output(Environment.NewLine);
+ output("Building directory tree..." + Environment.NewLine);
+ }
+
+ CreateDirectoryTree(output);
+
+ if (output != null)
+ {
+ output(Environment.NewLine);
+ output("Finished!" + Environment.NewLine);
+ }
+ }
+
+ public void SerializeRecords(string pathToBin, Action output)
+ {
+ if (output != null)
+ {
+ output(Environment.NewLine);
+ output("Serializing... ");
+ }
+
+ var Serialized = File.Create(pathToBin);
+ var s = new BinaryWriter(Serialized);
+ foreach (var record in RecordOffsets)
+ {
+ s.Write(record.Key);
+ var baseRecord = record.Value;
+ if (baseRecord is FileRecord)
+ {
+ s.Write((byte)1);
+ FileRecord fr = (FileRecord)baseRecord;
+ s.Write(fr.RecordBegin);
+ s.Write(fr.Length);
+ s.Write(fr.Hash);
+ s.Write(fr.Name);
+ s.Write(fr.DataBegin);
+ s.Write(fr.DataLength);
+ }
+ else if (baseRecord is GgpkRecord)
+ {
+ s.Write((byte)2);
+ GgpkRecord gr = (GgpkRecord)baseRecord;
+ s.Write(gr.RecordBegin);
+ s.Write(gr.Length);
+ s.Write(gr.RecordOffsets.Length);
+ foreach (long l in gr.RecordOffsets)
+ {
+ s.Write(l);
+ }
+ }
+ else if (baseRecord is FreeRecord)
+ {
+ s.Write((byte)3);
+ FreeRecord fr = (FreeRecord)baseRecord;
+ s.Write(fr.RecordBegin);
+ s.Write(fr.Length);
+ s.Write(fr.NextFreeOffset);
+ }
+ else if (baseRecord is DirectoryRecord)
+ {
+ s.Write((byte)4);
+ DirectoryRecord dr = (DirectoryRecord)baseRecord;
+ s.Write(dr.RecordBegin);
+ s.Write(dr.Length);
+ s.Write(dr.Hash);
+ s.Write(dr.Name);
+ s.Write(dr.EntriesBegin);
+ s.Write(dr.Entries.Count);
+ foreach (var directoryEntry in dr.Entries)
+ {
+ s.Write(directoryEntry.EntryNameHash);
+ s.Write(directoryEntry.Offset);
+ }
+ }
+ }
+ Serialized.Flush();
+ Serialized.Close();
+
+ output?.Invoke("Done!" + Environment.NewLine);
+ }
+
+ public void DeserializeRecords(string pathToBin, Action output)
+ {
+ if (output != null)
+ {
+ output(Environment.NewLine);
+ output("Deserializing... ");
+ }
+
+ var Serialized = File.OpenRead(pathToBin);
+ var s = new BinaryReader(Serialized);
+ while (Serialized.Length - Serialized.Position > 1)
+ {
+ long offset = s.ReadInt64();
+ switch (s.ReadByte())
+ {
+ case 1:
+ RecordOffsets.Add(offset, new FileRecord(s.ReadInt64(), s.ReadUInt32(), s.ReadBytes(32), s.ReadString(), s.ReadInt64(), s.ReadInt64()));
+ break;
+ case 2:
+ long recordBegin = s.ReadInt64();
+ uint length = s.ReadUInt32();
+ long[] recordOffsets = new long[s.ReadInt32()];
+ for (int i = 0; i < recordOffsets.Length; i++)
+ {
+ recordOffsets[i] = s.ReadInt64();
+ }
+ RecordOffsets.Add(offset, new GgpkRecord(recordBegin, length, recordOffsets));
+ break;
+ case 3:
+ RecordOffsets.Add(offset, new FreeRecord(s.ReadUInt32(), s.ReadInt64(), s.ReadInt64()));
+ break;
+ case 4:
+ long recordBegin2 = s.ReadInt64();
+ uint length2 = s.ReadUInt32();
+ byte[] hash = s.ReadBytes(32);
+ string name = s.ReadString();
+ long entriesBegin = s.ReadInt64();
+ int entriesCount = s.ReadInt32();
+ var entries = new List(entriesCount);
+ for (int i = 0; i < entriesCount; i++)
+ {
+ entries.Add(new DirectoryRecord.DirectoryEntry
+ {
+ EntryNameHash = s.ReadUInt32(),
+ Offset = s.ReadInt64(),
+ });
+ }
+ RecordOffsets.Add(offset, new DirectoryRecord(recordBegin2, length2, hash, name, entriesBegin, entries));
+ break;
+ }
+ }
+ Serialized.Flush();
+ Serialized.Close();
+
+ output?.Invoke("Done!" + Environment.NewLine);
}
///
@@ -99,8 +253,7 @@ private void ReadRecordOffsets(string pathToGgpk, Action output)
var percentComplete = currentOffset / (float)streamLength;
if (percentComplete - previousPercentComplete >= 0.10f)
{
- if (output != null)
- output(String.Format("\t{0:00.00}%{1}", 100.0 * percentComplete, Environment.NewLine));
+ output?.Invoke(String.Format("\t{0:00.00}%{1}", 100.0 * percentComplete, Environment.NewLine));
previousPercentComplete = percentComplete;
}
}
@@ -329,11 +482,11 @@ public void Save(string pathToGgpkNew, Action output)
if (!(percentComplete - previousPercentComplete >= 0.05f)) return;
if (output != null)
- output(String.Format(" {0:00.00}%", 100.0*percentComplete));
+ output(String.Format(" {0:00.00}%", 100.0 * percentComplete));
previousPercentComplete = percentComplete;
});
if (output != null) output(" 100%");
-
+
// write root directory
var rootDirectoryOffset = writer.BaseStream.Position;
DirectoryRoot.Record.Write(writer, changedOffsets);
@@ -349,7 +502,7 @@ public void Save(string pathToGgpkNew, Action output)
ggpkRecordNew.RecordOffsets[0] = rootDirectoryOffset;
ggpkRecordNew.RecordOffsets[1] = firstFreeRecordOffset;
ggpkRecordNew.Write(writer, changedOffsets);
- if (output != null)
+ if (output != null)
output("Finished !!!");
}
}
diff --git a/LibGGPK/Records/DirectoryRecord.cs b/LibGGPK/Records/DirectoryRecord.cs
index c234354..45a887d 100644
--- a/LibGGPK/Records/DirectoryRecord.cs
+++ b/LibGGPK/Records/DirectoryRecord.cs
@@ -51,6 +51,16 @@ public DirectoryRecord(uint length, BinaryReader br)
Read(br);
}
+ public DirectoryRecord(long recordBegin, uint length, byte[] hash, string name, long entriesBegin, List entries)
+ {
+ RecordBegin = recordBegin;
+ Length = length;
+ Hash = hash;
+ Name = name;
+ EntriesBegin = entriesBegin;
+ Entries = entries;
+ }
+
///
/// Reads the PDIR record entry from the specified stream
///
diff --git a/LibGGPK/Records/FileRecord.cs b/LibGGPK/Records/FileRecord.cs
index 7fa3d25..3f8e028 100644
--- a/LibGGPK/Records/FileRecord.cs
+++ b/LibGGPK/Records/FileRecord.cs
@@ -132,6 +132,16 @@ public enum DataFormat
///
public DirectoryTreeNode ContainingDirectory;
+ public FileRecord(long recordBegin, uint length, byte[] hash, string name, long dataBegin, long dataLength)
+ {
+ RecordBegin = recordBegin;
+ Length = length;
+ Hash = hash;
+ Name = name;
+ DataBegin = dataBegin;
+ DataLength = dataLength;
+ }
+
public FileRecord(uint length, BinaryReader br)
{
RecordBegin = br.BaseStream.Position - 8;
@@ -248,7 +258,7 @@ public DataFormat FileFormat
{
if (Name.Equals("GameObjectRegister"))
return DataFormat.Unicode;
- return KnownFileFormats[Path.GetExtension(Name).ToLower()];
+ return KnownFileFormats.ContainsKey(Path.GetExtension(Name).ToLower()) ? KnownFileFormats[Path.GetExtension(Name).ToLower()] : DataFormat.Unknown;
}
}
diff --git a/LibGGPK/Records/GGPKRecord.cs b/LibGGPK/Records/GGPKRecord.cs
index 276cdb4..4e9c050 100644
--- a/LibGGPK/Records/GGPKRecord.cs
+++ b/LibGGPK/Records/GGPKRecord.cs
@@ -32,6 +32,13 @@ public GgpkRecord(uint length, BinaryReader br)
Read(br);
}
+ public GgpkRecord(long recordBegin, uint length, long[] recordOffsets)
+ {
+ RecordBegin = recordBegin;
+ Length = length;
+ RecordOffsets = recordOffsets;
+ }
+
///
/// Reads the GGPK record entry from the specified stream
///
diff --git a/VisualGGPK/MainWindow.xaml b/VisualGGPK/MainWindow.xaml
index 315bf03..2faa5f6 100644
--- a/VisualGGPK/MainWindow.xaml
+++ b/VisualGGPK/MainWindow.xaml
@@ -5,7 +5,7 @@
xmlns:Properties="clr-namespace:VisualGGPK.Properties"
x:Class="VisualGGPK.MainWindow"
Title="VisualGGPK"
- Height="500" Width="1000"
+ Height="600" Width="1100"
Loaded="Window_Loaded"
AllowDrop="True"
Drop="Window_Drop_1"
@@ -47,7 +47,7 @@
-
+
@@ -71,8 +71,9 @@
-
+
+
@@ -90,4 +91,4 @@
-
+
\ No newline at end of file
diff --git a/VisualGGPK/MainWindow.xaml.cs b/VisualGGPK/MainWindow.xaml.cs
index a87b2cc..5ce53a1 100644
--- a/VisualGGPK/MainWindow.xaml.cs
+++ b/VisualGGPK/MainWindow.xaml.cs
@@ -29,6 +29,7 @@ namespace VisualGGPK
public partial class MainWindow
{
private string _ggpkPath = String.Empty;
+ private string _binPath = null;
private GrindingGearsPackageContainer _content;
private Thread _workerThread;
@@ -81,6 +82,8 @@ private void ReloadGgpkFile()
TextBoxOutput.Visibility = Visibility.Visible;
TextBoxOutput.Text = string.Empty;
_content = null;
+ SaveButton.IsEnabled = false;
+ SerializeButton.IsEnabled = false;
_workerThread = new Thread(() =>
{
@@ -91,7 +94,7 @@ private void ReloadGgpkFile()
}
catch (Exception ex)
{
- Output(string.Format(Settings.Strings["ReloadGGPK_Failed"], ex.Message));
+ Output(string.Format(Settings.Strings["ReloadGGPK_Failed"], ex.ToString()));
return;
}
@@ -121,7 +124,7 @@ private void ReloadGgpkFile()
}
catch (Exception ex)
{
- Output(string.Format(Settings.Strings["Error_Read_Directory_Tree"], ex.Message));
+ Output(string.Format(Settings.Strings["Error_Read_Directory_Tree"], ex.ToString()));
Output(ex.StackTrace);
return;
}
@@ -132,6 +135,85 @@ private void ReloadGgpkFile()
_workerThread = null;
OutputLine(Settings.Strings["ReloadGGPK_Successful"]);
+
+ TextBoxOutput.Dispatcher.BeginInvoke(new Action(() =>
+ {
+ SaveButton.IsEnabled = true;
+ SerializeButton.IsEnabled = true;
+ }));
+ });
+
+ _workerThread.Start();
+ }
+
+ ///
+ /// Load the Records.bin, rebuilds the tree
+ ///
+ private void LoadBinFile()
+ {
+ TreeView1.Items.Clear();
+ ResetViewer();
+ TextBoxOutput.Visibility = Visibility.Visible;
+ TextBoxOutput.Text = string.Empty;
+ _content = null;
+ SaveButton.IsEnabled = false;
+ SerializeButton.IsEnabled = false;
+
+ _workerThread = new Thread(() =>
+ {
+ _content = new GrindingGearsPackageContainer();
+ try
+ {
+ _content.Read(_ggpkPath, _binPath, Output);
+ }
+ catch (Exception ex)
+ {
+ Output(string.Format(Settings.Strings["ReloadGGPK_Failed"], ex.ToString()));
+ return;
+ }
+
+ if (_content.IsReadOnly)
+ {
+ Output(Settings.Strings["ReloadGGPK_ReadOnly"] + Environment.NewLine);
+ UpdateTitle(Settings.Strings["MainWindow_Title_Readonly"]);
+ }
+
+ OutputLine(Settings.Strings["ReloadGGPK_Traversing_Tree"]);
+
+ // Collect all FileRecordPath -> FileRecord pairs for easier replacing
+ _recordsByPath = new Dictionary(_content.RecordOffsets.Count);
+ DirectoryTreeNode.TraverseTreePostorder(
+ _content.DirectoryRoot,
+ null,
+ n => _recordsByPath.Add(n.GetDirectoryPath() + n.Name, n));
+
+ TreeView1.Dispatcher.BeginInvoke(new Action(() =>
+ {
+ try
+ {
+ var rootItem = CreateLazyTreeViewItem(_content.DirectoryRoot);
+ TreeView1.Items.Add(rootItem);
+ rootItem.IsExpanded = true;
+ rootItem.RaiseEvent(new RoutedEventArgs(TreeViewItem.ExpandedEvent, rootItem));
+ }
+ catch (Exception ex)
+ {
+ Output(string.Format(Settings.Strings["Error_Read_Directory_Tree"], ex.ToString()));
+ Output(ex.StackTrace);
+ return;
+ }
+
+ }), null);
+
+ _workerThread = null;
+
+ OutputLine(Settings.Strings["ReloadGGPK_Successful"]);
+
+ SaveButton.Dispatcher.BeginInvoke(new Action(() =>
+ {
+ SaveButton.IsEnabled = true;
+ SerializeButton.IsEnabled = true;
+ }));
});
_workerThread.Start();
@@ -166,13 +248,20 @@ private void UpdateDisplayPanel()
ResetViewer();
var selectedItem = TreeView1.SelectedItem;
if (selectedItem == null)
+ {
+ TextBoxOutput.Visibility = Visibility.Visible;
return;
+ }
var item = selectedItem as TreeViewItem;
if (item == null)
+ {
+ TextBoxOutput.Visibility = Visibility.Visible;
return;
+ }
if (item.Tag is DirectoryTreeNode)
{
+ TextBoxOutput.Visibility = Visibility.Visible;
var selectedDirectory = item.Tag as DirectoryTreeNode;
if (selectedDirectory.Record == null)
return;
@@ -229,7 +318,7 @@ private void UpdateDisplayPanel()
var sb = new StringBuilder();
while (ex != null)
{
- sb.AppendLine(ex.Message);
+ sb.AppendLine(ex.ToString());
ex = ex.InnerException;
}
@@ -353,7 +442,7 @@ private void ExportFileRecord(FileRecord selectedRecord)
catch (Exception ex)
{
MessageBox.Show(
- string.Format(Settings.Strings["ExportSelectedItem_Failed"], ex.Message),
+ string.Format(Settings.Strings["ExportSelectedItem_Failed"], ex.ToString()),
Settings.Strings["Error_Caption"], MessageBoxButton.OK,
MessageBoxImage.Error);
}
@@ -393,7 +482,7 @@ private void OpenFileRecord(FileRecord selectedRecord)
catch (Exception ex)
{
MessageBox.Show(
- string.Format(Settings.Strings["ViewSelectedItem_Failed"], ex.Message),
+ string.Format(Settings.Strings["ViewSelectedItem_Failed"], ex.ToString()),
Settings.Strings["Error_Caption"],
MessageBoxButton.OK, MessageBoxImage.Error);
return;
@@ -438,7 +527,7 @@ private void ExportAllItemsInDirectory(DirectoryTreeNode selectedDirectoryNode)
}
catch (Exception ex)
{
- MessageBox.Show(string.Format(Settings.Strings["ExportAllItemsInDirectory_Failed"], ex.Message), Settings.Strings["Error_Caption"], MessageBoxButton.OK, MessageBoxImage.Error);
+ MessageBox.Show(string.Format(Settings.Strings["ExportAllItemsInDirectory_Failed"], ex.ToString()), Settings.Strings["Error_Caption"], MessageBoxButton.OK, MessageBoxImage.Error);
}
}
@@ -476,7 +565,7 @@ private void ReplaceFileRecord(FileRecord recordToReplace)
catch (Exception ex)
{
MessageBox.Show(
- string.Format(Settings.Strings["ReplaceItem_Failed"], ex.Message),
+ string.Format(Settings.Strings["ReplaceItem_Failed"], ex.ToString()),
Settings.Strings["Error_Caption"],
MessageBoxButton.OK, MessageBoxImage.Error);
}
@@ -634,7 +723,10 @@ private void Window_Loaded(object sender, RoutedEventArgs e)
{
var ofd = new OpenFileDialog
{
+ AddExtension = true,
CheckFileExists = true,
+ DefaultExt = "ggpk",
+ FileName = "Content.ggpk",
Filter = Settings.Strings["Load_GGPK_Filter"]
};
@@ -679,8 +771,30 @@ private void Window_Loaded(object sender, RoutedEventArgs e)
Close();
return;
}
- _ggpkPath = ofd.FileName;
- ReloadGgpkFile();
+ if (Path.GetExtension(ofd.FileName).ToLower() == ".bin")
+ {
+ _binPath = ofd.FileName;
+ ofd.FileName = "Content.ggpk";
+ if (ofd.ShowDialog() == true)
+ {
+ if (!File.Exists(ofd.FileName))
+ {
+ Close();
+ return;
+ }
+ _ggpkPath = ofd.FileName;
+ LoadBinFile();
+ }
+ else
+ {
+ Close();
+ return;
+ }
+ } else
+ {
+ _ggpkPath = ofd.FileName;
+ ReloadGgpkFile();
+ }
}
else
{
@@ -990,5 +1104,24 @@ private void OnSaveClicked(object sender, RoutedEventArgs e)
TextBoxOutput.Text = string.Empty;
_content.Save(openFileDialog.FileName, OutputLine);
}
+
+ private void OnSerializeClicked(object sender, RoutedEventArgs e)
+ {
+ // Serialize GGPK Records
+ var saveFileDialog = new SaveFileDialog
+ {
+ AddExtension = true,
+ CheckPathExists = true,
+ DefaultExt = "bin",
+ FileName = "Records.bin",
+ OverwritePrompt = true
+ };
+
+ if (saveFileDialog.ShowDialog() == true)
+ {
+ TextBoxOutput.Visibility = Visibility.Visible;
+ _content.SerializeRecords(saveFileDialog.FileName, Output);
+ }
+ }
}
}
\ No newline at end of file
diff --git a/VisualGGPK/Properties/Resources.Designer.cs b/VisualGGPK/Properties/Resources.Designer.cs
index 8d93a7f..f148f9a 100644
--- a/VisualGGPK/Properties/Resources.Designer.cs
+++ b/VisualGGPK/Properties/Resources.Designer.cs
@@ -19,7 +19,7 @@ namespace VisualGGPK.Properties {
// 類別透過 ResGen 或 Visual Studio 這類工具。
// 若要加入或移除成員,請編輯您的 .ResX 檔,然後重新執行 ResGen
// (利用 /str 選項),或重建您的 VS 專案。
- [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")]
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
public class Resources {
@@ -259,7 +259,7 @@ public static string LabelFileOFfset_Content {
}
///
- /// 查詢類似 GGPK Pack File|*.ggpk 的當地語系化字串。
+ /// 查詢類似 GGPK Pack File|*.ggpk|Records Bin File|*.bin 的當地語系化字串。
///
public static string Load_GGPK_Filter {
get {
diff --git a/VisualGGPK/Properties/Resources.resx b/VisualGGPK/Properties/Resources.resx
index 1f32b06..aac0ab7 100644
--- a/VisualGGPK/Properties/Resources.resx
+++ b/VisualGGPK/Properties/Resources.resx
@@ -118,7 +118,7 @@
System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
- GGPK Pack File|*.ggpk
+ GGPK Pack File|*.ggpk|Records Bin File|*.bin
Failed to read file: {0}