mirror of
https://github.com/ApfelTeeSaft/ChunkDBExtractor.git
synced 2026-09-03 11:33:33 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4b1b9f3e1f | ||
|
|
106a322e4a | ||
|
|
5410bfaecf |
@@ -0,0 +1,25 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.8.34525.116
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChunkDBExtractor", "ChunkDBExtractor\ChunkDBExtractor.csproj", "{4A2367D7-CA9E-4B23-9EB1-D0DA803E9B86}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{4A2367D7-CA9E-4B23-9EB1-D0DA803E9B86}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{4A2367D7-CA9E-4B23-9EB1-D0DA803E9B86}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4A2367D7-CA9E-4B23-9EB1-D0DA803E9B86}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{4A2367D7-CA9E-4B23-9EB1-D0DA803E9B86}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {72A65FA7-51DC-4E62-927D-B55412251CCD}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -0,0 +1,9 @@
|
||||
<Application x:Class="ChunkDBExtractor.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:ChunkDBExtractor"
|
||||
StartupUri="MainWindow.xaml">
|
||||
<Application.Resources>
|
||||
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
@@ -0,0 +1,14 @@
|
||||
using System.Configuration;
|
||||
using System.Data;
|
||||
using System.Windows;
|
||||
|
||||
namespace ChunkDBExtractor
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for App.xaml
|
||||
/// </summary>
|
||||
public partial class App : Application
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using System.Windows;
|
||||
|
||||
[assembly: ThemeInfo(
|
||||
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
|
||||
//(used if a resource is not found in the page,
|
||||
// or application resource dictionaries)
|
||||
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
|
||||
//(used if a resource is not found in the page,
|
||||
// app, or any theme specific resource dictionaries)
|
||||
)]
|
||||
@@ -0,0 +1,15 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<UseWPF>true</UseWPF>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ICSharpCode.SharpZipLib.dll" Version="0.85.4.369" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup />
|
||||
<ItemGroup>
|
||||
<ApplicationDefinition Update="App.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
</ApplicationDefinition>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Update="ProgressWindow.xaml.cs">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="MainWindow.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Update="ProgressWindow.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,16 @@
|
||||
<Window x:Class="ChunkDBExtractor.MainWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Title="ChunkDB Extractor" Height="200" Width="500" ResizeMode="NoResize">
|
||||
<Grid>
|
||||
<StackPanel Margin="10">
|
||||
<Button Name="SelectSourceButton" Content="Select Source Folder" Click="SelectSourceButton_Click" />
|
||||
<TextBox Name="SourceFolderTextBox" IsReadOnly="True" Margin="0,5,0,10" />
|
||||
|
||||
<Button Name="SelectDestinationButton" Content="Select Destination Folder" Click="SelectDestinationButton_Click" />
|
||||
<TextBox Name="DestinationFolderTextBox" IsReadOnly="True" Margin="0,5,0,10" />
|
||||
|
||||
<Button Name="ExtractButton" Content="Extract Files" Click="ExtractButton_Click" IsEnabled="False" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Window>
|
||||
@@ -0,0 +1,268 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Windows;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Win32;
|
||||
using System.Collections.Generic;
|
||||
using ICSharpCode.SharpZipLib.Zip.Compression;
|
||||
using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
|
||||
|
||||
namespace ChunkDBExtractor
|
||||
{
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
private string sourceFolder;
|
||||
private string destinationFolder;
|
||||
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void SelectSourceButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var dialog = new OpenFileDialog
|
||||
{
|
||||
CheckFileExists = false,
|
||||
CheckPathExists = true,
|
||||
ValidateNames = false,
|
||||
FileName = "Select Folder"
|
||||
};
|
||||
|
||||
if (dialog.ShowDialog() == true)
|
||||
{
|
||||
sourceFolder = Path.GetDirectoryName(dialog.FileName);
|
||||
SourceFolderTextBox.Text = sourceFolder;
|
||||
CheckIfReadyToExtract();
|
||||
}
|
||||
}
|
||||
|
||||
private void SelectDestinationButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var dialog = new OpenFileDialog
|
||||
{
|
||||
CheckFileExists = false,
|
||||
CheckPathExists = true,
|
||||
ValidateNames = false,
|
||||
FileName = "Select Folder"
|
||||
};
|
||||
|
||||
if (dialog.ShowDialog() == true)
|
||||
{
|
||||
destinationFolder = Path.GetDirectoryName(dialog.FileName);
|
||||
DestinationFolderTextBox.Text = destinationFolder;
|
||||
CheckIfReadyToExtract();
|
||||
}
|
||||
}
|
||||
|
||||
private void ExtractButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (string.IsNullOrEmpty(sourceFolder) || string.IsNullOrEmpty(destinationFolder))
|
||||
{
|
||||
MessageBox.Show("Please select both source and destination folders.");
|
||||
return;
|
||||
}
|
||||
|
||||
ProgressWindow progressWindow = new ProgressWindow();
|
||||
progressWindow.Owner = this;
|
||||
progressWindow.ShowInTaskbar = false;
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
progressWindow.Dispatcher.Invoke(() => progressWindow.StartAnimation());
|
||||
await ExtractChunkDBFilesAsync(sourceFolder, destinationFolder, progressWindow);
|
||||
|
||||
progressWindow.Dispatcher.Invoke(() =>
|
||||
{
|
||||
progressWindow.StopAnimation();
|
||||
progressWindow.Close();
|
||||
});
|
||||
|
||||
MessageBox.Show("Files extracted successfully!");
|
||||
});
|
||||
|
||||
progressWindow.ShowDialog();
|
||||
}
|
||||
|
||||
private void CheckIfReadyToExtract()
|
||||
{
|
||||
ExtractButton.IsEnabled = !string.IsNullOrEmpty(sourceFolder) && !string.IsNullOrEmpty(destinationFolder);
|
||||
}
|
||||
|
||||
private async Task ExtractChunkDBFilesAsync(string source, string destination, ProgressWindow progressWindow)
|
||||
{
|
||||
var chunkdbFiles = Directory.GetFiles(source, "*.chunkdb");
|
||||
|
||||
foreach (var file in chunkdbFiles)
|
||||
{
|
||||
try
|
||||
{
|
||||
progressWindow.UpdateProgress(Path.GetFileName(file));
|
||||
|
||||
await Task.Run(() => ExtractChunkDBFile(file, destination));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show($"An error occurred while extracting {file}: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ExtractChunkDBFile(string file, string destination)
|
||||
{
|
||||
using (var stream = File.OpenRead(file))
|
||||
using (var reader = new BinaryReader(stream))
|
||||
{
|
||||
var chunkDatabase = new FChunkDatabase(reader);
|
||||
|
||||
foreach (var location in chunkDatabase.Locations)
|
||||
{
|
||||
stream.Seek((long)location.ByteStart, SeekOrigin.Begin);
|
||||
var chunkHeader = new FChunkHeader(reader);
|
||||
|
||||
var guidString = $"{chunkHeader.Guid.A:X8}{chunkHeader.Guid.B:X8}{chunkHeader.Guid.C:X8}{chunkHeader.Guid.D:X8}";
|
||||
var chunkFilePath = Path.Combine(destination, guidString);
|
||||
|
||||
var chunkData = reader.ReadBytes((int)chunkHeader.DataSizeCompressed);
|
||||
var decompressedData = chunkHeader.StoredAs == EChunkStorageFlags.None ? chunkData : FChunkHeader.Decompress(chunkData);
|
||||
|
||||
File.WriteAllBytes(chunkFilePath, decompressedData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ChunkDBTool
|
||||
private class FChunkDatabase
|
||||
{
|
||||
private const uint CHUNKDB_HEADER_MAGIC = 0xB1FE3AA3;
|
||||
|
||||
public uint Version { get; private set; }
|
||||
public uint HeaderSize { get; private set; }
|
||||
public ulong DataSize { get; private set; }
|
||||
public int ChunkCount { get; private set; }
|
||||
|
||||
public FChunkLocation[] Locations { get; private set; }
|
||||
public FChunkHeader[] Chunks { get; private set; }
|
||||
|
||||
public FChunkDatabase(BinaryReader reader)
|
||||
{
|
||||
if (reader.ReadUInt32() != CHUNKDB_HEADER_MAGIC)
|
||||
throw new Exception("Incorrect chunkdb.");
|
||||
|
||||
Version = reader.ReadUInt32();
|
||||
HeaderSize = reader.ReadUInt32();
|
||||
DataSize = reader.ReadUInt64();
|
||||
ChunkCount = reader.ReadInt32();
|
||||
|
||||
var locations = new List<FChunkLocation>();
|
||||
for (var i = 0; i < ChunkCount; i++)
|
||||
locations.Add(new FChunkLocation(reader));
|
||||
|
||||
Locations = locations.ToArray();
|
||||
|
||||
var chunks = new List<FChunkHeader>();
|
||||
for (var i = 0; i < Locations.Length; i++)
|
||||
{
|
||||
reader.BaseStream.Position = (long)Locations[i].ByteStart;
|
||||
chunks.Add(new FChunkHeader(reader));
|
||||
}
|
||||
|
||||
Chunks = chunks.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
private class FChunkLocation
|
||||
{
|
||||
public FGuid ChunkId { get; private set; }
|
||||
public ulong ByteStart { get; private set; }
|
||||
public int ByteSize { get; private set; }
|
||||
|
||||
public FChunkLocation(BinaryReader reader)
|
||||
{
|
||||
ChunkId = new FGuid(reader);
|
||||
ByteStart = reader.ReadUInt64();
|
||||
ByteSize = reader.ReadInt32();
|
||||
}
|
||||
}
|
||||
|
||||
private class FGuid
|
||||
{
|
||||
public uint A { get; private set; }
|
||||
public uint B { get; private set; }
|
||||
public uint C { get; private set; }
|
||||
public uint D { get; private set; }
|
||||
|
||||
public FGuid(BinaryReader reader)
|
||||
{
|
||||
A = reader.ReadUInt32();
|
||||
B = reader.ReadUInt32();
|
||||
C = reader.ReadUInt32();
|
||||
D = reader.ReadUInt32();
|
||||
}
|
||||
}
|
||||
|
||||
private class FChunkHeader
|
||||
{
|
||||
private const uint CHUNK_HEADER_MAGIC = 0xB1FE3AA2;
|
||||
|
||||
public uint Version { get; private set; }
|
||||
public uint HeaderSize { get; private set; }
|
||||
public uint DataSizeCompressed { get; private set; }
|
||||
public FGuid Guid { get; private set; }
|
||||
public ulong RollingHash { get; private set; }
|
||||
public EChunkStorageFlags StoredAs { get; private set; }
|
||||
public byte[] SHAHash { get; private set; }
|
||||
public EChunkHashFlags HashType { get; private set; }
|
||||
|
||||
public FChunkHeader(BinaryReader reader)
|
||||
{
|
||||
if (reader.ReadUInt32() != CHUNK_HEADER_MAGIC)
|
||||
throw new Exception("Incorrect chunk.");
|
||||
|
||||
Version = reader.ReadUInt32();
|
||||
HeaderSize = reader.ReadUInt32();
|
||||
DataSizeCompressed = reader.ReadUInt32();
|
||||
Guid = new FGuid(reader);
|
||||
RollingHash = reader.ReadUInt64();
|
||||
StoredAs = (EChunkStorageFlags)reader.ReadByte();
|
||||
SHAHash = reader.ReadBytes(20);
|
||||
HashType = (EChunkHashFlags)reader.ReadByte();
|
||||
}
|
||||
|
||||
public static byte[] Decompress(byte[] data)
|
||||
{
|
||||
MemoryStream inflated = new MemoryStream();
|
||||
using (Stream inflater = new InflaterInputStream(
|
||||
new MemoryStream(data), new Inflater(false)))
|
||||
{
|
||||
int count = 0;
|
||||
byte[] deflated = new byte[4096];
|
||||
while ((count = inflater.Read(deflated, 0, deflated.Length)) != 0)
|
||||
{
|
||||
inflated.Write(deflated, 0, count);
|
||||
}
|
||||
inflated.Seek(0, SeekOrigin.Begin);
|
||||
}
|
||||
byte[] content = new byte[inflated.Length];
|
||||
inflated.Read(content, 0, content.Length);
|
||||
return content;
|
||||
}
|
||||
}
|
||||
|
||||
[Flags]
|
||||
private enum EChunkStorageFlags : byte
|
||||
{
|
||||
None = 0,
|
||||
Compressed = 1,
|
||||
Encrypted = 2
|
||||
}
|
||||
|
||||
[Flags]
|
||||
private enum EChunkHashFlags : byte
|
||||
{
|
||||
None = 0,
|
||||
RollingPoly64 = 1,
|
||||
Sha1 = 2
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<Window x:Class="ChunkDBExtractor.ProgressWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Title="Processing..." Height="120" Width="400" WindowStartupLocation="CenterScreen" WindowStyle="ToolWindow">
|
||||
<Grid>
|
||||
<StackPanel Margin="20">
|
||||
<TextBlock Name="FileNameTextBlock" FontSize="16" TextAlignment="Center" Margin="0,0,0,10"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Window>
|
||||
@@ -0,0 +1,52 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace ChunkDBExtractor
|
||||
{
|
||||
public partial class ProgressWindow : Window
|
||||
{
|
||||
private DispatcherTimer animationTimer;
|
||||
private int dotCount = 0;
|
||||
private string baseText = "Processing";
|
||||
|
||||
public ProgressWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
// Initialize the animation timer
|
||||
animationTimer = new DispatcherTimer();
|
||||
animationTimer.Interval = TimeSpan.FromMilliseconds(500); // Update every 500ms
|
||||
animationTimer.Tick += UpdateAnimation;
|
||||
}
|
||||
|
||||
private void UpdateAnimation(object sender, EventArgs e)
|
||||
{
|
||||
dotCount = (dotCount + 1) % 4; // Cycle between 0, 1, 2, 3
|
||||
string dots = new string('.', dotCount);
|
||||
FileNameTextBlock.Text = $"{baseText}{dots}";
|
||||
}
|
||||
|
||||
public void StartAnimation()
|
||||
{
|
||||
animationTimer.Start();
|
||||
}
|
||||
|
||||
public void StopAnimation()
|
||||
{
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
animationTimer.Stop();
|
||||
FileNameTextBlock.Text = "Extraction Complete";
|
||||
});
|
||||
}
|
||||
|
||||
public void UpdateProgress(string fileName)
|
||||
{
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
baseText = $"Processing: {fileName}"; // Update the base text with the current filename
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user