3 Commits
Author SHA1 Message Date
ApfelTeeSaft 4b1b9f3e1f fix a few things 2024-09-16 13:52:09 +02:00
ApfelTeeSaft 106a322e4a add ProgressBar per chunkdb 2024-09-16 13:29:33 +02:00
ApfelTeeSaft 5410bfaecf initial code 2024-09-16 12:34:19 +02:00
10 changed files with 441 additions and 0 deletions
+25
View File
@@ -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
+9
View File
@@ -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>
+14
View File
@@ -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
{
}
}
+10
View File
@@ -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)
)]
+15
View File
@@ -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>
+16
View File
@@ -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>
+268
View File
@@ -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
}
}
}
+10
View File
@@ -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>
+52
View File
@@ -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
});
}
}
}