mirror of
https://github.com/cemu-project/WudCompress.git
synced 2024-11-25 10:56:53 +01:00
Add files via upload
This commit is contained in:
parent
2d391a8083
commit
b0ab5f6b6a
20
WudCompress.sln
Normal file
20
WudCompress.sln
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 10.00
|
||||||
|
# Visual Studio 2008
|
||||||
|
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WudCompress", "WudCompress\WudCompress.vcproj", "{6FC9B74D-FB07-4173-B7CB-86D34867BCD7}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Win32 = Debug|Win32
|
||||||
|
Release|Win32 = Release|Win32
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{6FC9B74D-FB07-4173-B7CB-86D34867BCD7}.Debug|Win32.ActiveCfg = Debug|Win32
|
||||||
|
{6FC9B74D-FB07-4173-B7CB-86D34867BCD7}.Debug|Win32.Build.0 = Debug|Win32
|
||||||
|
{6FC9B74D-FB07-4173-B7CB-86D34867BCD7}.Release|Win32.ActiveCfg = Release|Win32
|
||||||
|
{6FC9B74D-FB07-4173-B7CB-86D34867BCD7}.Release|Win32.Build.0 = Release|Win32
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
193
WudCompress/WudCompress.vcproj
Normal file
193
WudCompress/WudCompress.vcproj
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
<?xml version="1.0" encoding="Windows-1252"?>
|
||||||
|
<VisualStudioProject
|
||||||
|
ProjectType="Visual C++"
|
||||||
|
Version="9,00"
|
||||||
|
Name="WudCompress"
|
||||||
|
ProjectGUID="{6FC9B74D-FB07-4173-B7CB-86D34867BCD7}"
|
||||||
|
RootNamespace="WudCompress"
|
||||||
|
TargetFrameworkVersion="196613"
|
||||||
|
>
|
||||||
|
<Platforms>
|
||||||
|
<Platform
|
||||||
|
Name="Win32"
|
||||||
|
/>
|
||||||
|
</Platforms>
|
||||||
|
<ToolFiles>
|
||||||
|
</ToolFiles>
|
||||||
|
<Configurations>
|
||||||
|
<Configuration
|
||||||
|
Name="Debug|Win32"
|
||||||
|
OutputDirectory="$(SolutionDir)$(ConfigurationName)"
|
||||||
|
IntermediateDirectory="$(ConfigurationName)"
|
||||||
|
ConfigurationType="1"
|
||||||
|
CharacterSet="2"
|
||||||
|
>
|
||||||
|
<Tool
|
||||||
|
Name="VCPreBuildEventTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCCustomBuildTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCXMLDataGeneratorTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCWebServiceProxyGeneratorTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCMIDLTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCCLCompilerTool"
|
||||||
|
Optimization="0"
|
||||||
|
MinimalRebuild="true"
|
||||||
|
BasicRuntimeChecks="3"
|
||||||
|
RuntimeLibrary="3"
|
||||||
|
WarningLevel="3"
|
||||||
|
DebugInformationFormat="4"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCManagedResourceCompilerTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCResourceCompilerTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCPreLinkEventTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCLinkerTool"
|
||||||
|
GenerateDebugInformation="true"
|
||||||
|
TargetMachine="1"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCALinkTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCManifestTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCXDCMakeTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCBscMakeTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCFxCopTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCAppVerifierTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCPostBuildEventTool"
|
||||||
|
/>
|
||||||
|
</Configuration>
|
||||||
|
<Configuration
|
||||||
|
Name="Release|Win32"
|
||||||
|
OutputDirectory="$(SolutionDir)$(ConfigurationName)"
|
||||||
|
IntermediateDirectory="$(ConfigurationName)"
|
||||||
|
ConfigurationType="1"
|
||||||
|
CharacterSet="2"
|
||||||
|
WholeProgramOptimization="1"
|
||||||
|
>
|
||||||
|
<Tool
|
||||||
|
Name="VCPreBuildEventTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCCustomBuildTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCXMLDataGeneratorTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCWebServiceProxyGeneratorTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCMIDLTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCCLCompilerTool"
|
||||||
|
Optimization="2"
|
||||||
|
EnableIntrinsicFunctions="true"
|
||||||
|
FavorSizeOrSpeed="1"
|
||||||
|
RuntimeLibrary="0"
|
||||||
|
EnableFunctionLevelLinking="true"
|
||||||
|
WarningLevel="3"
|
||||||
|
DebugInformationFormat="3"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCManagedResourceCompilerTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCResourceCompilerTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCPreLinkEventTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCLinkerTool"
|
||||||
|
GenerateDebugInformation="true"
|
||||||
|
OptimizeReferences="2"
|
||||||
|
EnableCOMDATFolding="2"
|
||||||
|
TargetMachine="1"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCALinkTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCManifestTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCXDCMakeTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCBscMakeTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCFxCopTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCAppVerifierTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCPostBuildEventTool"
|
||||||
|
/>
|
||||||
|
</Configuration>
|
||||||
|
</Configurations>
|
||||||
|
<References>
|
||||||
|
</References>
|
||||||
|
<Files>
|
||||||
|
<Filter
|
||||||
|
Name="Source Files"
|
||||||
|
Filter="cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx"
|
||||||
|
UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}"
|
||||||
|
>
|
||||||
|
<File
|
||||||
|
RelativePath=".\main.cpp"
|
||||||
|
>
|
||||||
|
</File>
|
||||||
|
<File
|
||||||
|
RelativePath=".\wud.cpp"
|
||||||
|
>
|
||||||
|
</File>
|
||||||
|
<File
|
||||||
|
RelativePath=".\wud.h"
|
||||||
|
>
|
||||||
|
</File>
|
||||||
|
</Filter>
|
||||||
|
<Filter
|
||||||
|
Name="Header Files"
|
||||||
|
Filter="h;hpp;hxx;hm;inl;inc;xsd"
|
||||||
|
UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}"
|
||||||
|
>
|
||||||
|
</Filter>
|
||||||
|
<Filter
|
||||||
|
Name="Resource Files"
|
||||||
|
Filter="rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav"
|
||||||
|
UniqueIdentifier="{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}"
|
||||||
|
>
|
||||||
|
</Filter>
|
||||||
|
</Files>
|
||||||
|
<Globals>
|
||||||
|
</Globals>
|
||||||
|
</VisualStudioProject>
|
349
WudCompress/main.cpp
Normal file
349
WudCompress/main.cpp
Normal file
@ -0,0 +1,349 @@
|
|||||||
|
#include<stdio.h>
|
||||||
|
#include<stdlib.h>
|
||||||
|
#include<string.h>
|
||||||
|
#include"wud.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* WUX file structure (v1.0):
|
||||||
|
[Header]
|
||||||
|
UINT32 magic1 "WUX0"
|
||||||
|
UINT32 magic2 0x1099d02e
|
||||||
|
UINT32 sectorSize Size per uncompressed sector (SECTOR_SIZE constant)
|
||||||
|
UINT64 uncompressedSize Size of the Wii U image before being compressed
|
||||||
|
UINT32 flags Enable optional parts of the header (not used right now)
|
||||||
|
|
||||||
|
[SectorIndexTable]
|
||||||
|
UINT32[] lookupIndex table of indices for lookup of each sector. To calculate number of entries in this array: sectorCount = (uncompressedSize+sectorSize-1)/sectorSize
|
||||||
|
|
||||||
|
[SectorData]
|
||||||
|
UINT8[] padding Padding until the next field (sectorData) is aligned to sectorSize bytes. You can write whatever data you want here
|
||||||
|
UINT8[] sectorData Array of unique sectors. Size in bytes: sectorSize * sectorCount
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#define SECTOR_SIZE (0x8000)
|
||||||
|
#define SECTOR_HASH_SIZE (32)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Hash function used to create a hash of each sector
|
||||||
|
* The hashes are then compared to find duplicate sectors
|
||||||
|
*/
|
||||||
|
void calculateHash256(unsigned char* data, unsigned int length, unsigned char* hashOut)
|
||||||
|
{
|
||||||
|
// cheap and simple hash implementation
|
||||||
|
// you can replace this part with your favorite hash method
|
||||||
|
memset(hashOut, 0x00, 32);
|
||||||
|
for(unsigned int i=0; i<length; i++)
|
||||||
|
{
|
||||||
|
hashOut[i%32] ^= data[i];
|
||||||
|
hashOut[(i+7)%32] += data[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Compare content of two WUD/WUX files
|
||||||
|
* Used to compare uncompressed and compressed version of a WUD
|
||||||
|
* Returns false if there is even a single byte of difference
|
||||||
|
*/
|
||||||
|
bool validateWUX(char* wud1Path, char* wud2Path)
|
||||||
|
{
|
||||||
|
puts("Checking for errors...");
|
||||||
|
wud_t* wudFile1 = wud_open(wud1Path);
|
||||||
|
wud_t* wudFile2 = wud_open(wud2Path);
|
||||||
|
if( wudFile1 == NULL )
|
||||||
|
{
|
||||||
|
printf("Failed to open \"%s\"\n", wud1Path);
|
||||||
|
if( wudFile2 == NULL )
|
||||||
|
wud_close(wudFile2);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if( wudFile2 == NULL )
|
||||||
|
{
|
||||||
|
printf("Failed to open \"%s\"\n", wud2Path);
|
||||||
|
if( wudFile1 == NULL )
|
||||||
|
wud_close(wudFile1);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// get and compare sizes
|
||||||
|
long long wud1Size = wud_getWUDSize(wudFile1);
|
||||||
|
long long wud2Size = wud_getWUDSize(wudFile2);
|
||||||
|
if( wud1Size != wud2Size )
|
||||||
|
{
|
||||||
|
printf("WUD data size mismatch\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// compare data
|
||||||
|
long long currentValidationOffset = 0;
|
||||||
|
unsigned int tempBufferSize = 1024*1024+19; // 1MB + some extra bytes to make the number uneven (we want to provoke cross-sector reads)
|
||||||
|
unsigned char* tempBufferWUD1 = (unsigned char*)malloc(tempBufferSize);
|
||||||
|
unsigned char* tempBufferWUD2 = (unsigned char*)malloc(tempBufferSize);
|
||||||
|
bool dataMismatch = false;
|
||||||
|
int pct = -1;
|
||||||
|
printf("0%\r");
|
||||||
|
while( currentValidationOffset < wud1Size )
|
||||||
|
{
|
||||||
|
// calculate how many bytes we are reading in this cycle
|
||||||
|
long long remainingBytes = wud1Size - currentValidationOffset;
|
||||||
|
unsigned int bytesToRead = tempBufferSize;
|
||||||
|
if( remainingBytes < (long long)bytesToRead )
|
||||||
|
bytesToRead = (unsigned int)remainingBytes;
|
||||||
|
unsigned int readByteCount1 = wud_readData(wudFile1, tempBufferWUD1, bytesToRead, currentValidationOffset);
|
||||||
|
unsigned int readByteCount2 = wud_readData(wudFile2, tempBufferWUD2, bytesToRead, currentValidationOffset);
|
||||||
|
if( readByteCount1 != readByteCount2 || bytesToRead != readByteCount1 )
|
||||||
|
{
|
||||||
|
printf("Data read size mismatch\n");
|
||||||
|
dataMismatch = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// compare buffers
|
||||||
|
if( memcmp(tempBufferWUD1, tempBufferWUD2, bytesToRead) != 0 )
|
||||||
|
{
|
||||||
|
printf("Data mismatch\n");
|
||||||
|
dataMismatch = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// progress offset
|
||||||
|
currentValidationOffset += bytesToRead;
|
||||||
|
// display current progress
|
||||||
|
int newPct = (int)(currentValidationOffset*1000LL / wud1Size);
|
||||||
|
if( newPct != pct )
|
||||||
|
{
|
||||||
|
printf("%d.%d%% \r", (newPct/10), (newPct%10));
|
||||||
|
pct = newPct;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
puts("");
|
||||||
|
free(tempBufferWUD1);
|
||||||
|
free(tempBufferWUD2);
|
||||||
|
wud_close(wudFile1);
|
||||||
|
wud_close(wudFile2);
|
||||||
|
return dataMismatch == false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool compressWUD(wud_t* inputFile, FILE* outputFile, char* outputPath)
|
||||||
|
{
|
||||||
|
long long inputSize = wud_getWUDSize(inputFile);
|
||||||
|
// write header
|
||||||
|
wuxHeader_t wuxHeader = {0};
|
||||||
|
wuxHeader.magic0 = WUX_MAGIC_0;
|
||||||
|
wuxHeader.magic1 = WUX_MAGIC_1;
|
||||||
|
wuxHeader.sectorSize = SECTOR_SIZE;
|
||||||
|
wuxHeader.uncompressedSize = inputSize;
|
||||||
|
wuxHeader.flags = 0;
|
||||||
|
fwrite(&wuxHeader, sizeof(wuxHeader_t), 1, outputFile);
|
||||||
|
unsigned int sectorTableEntryCount = (unsigned int)((inputSize+SECTOR_SIZE-1) / (long long)SECTOR_SIZE);
|
||||||
|
|
||||||
|
// remember current seek offset, this is where the index table will be written after compression is done
|
||||||
|
long long offsetIndexTable = _ftelli64(outputFile);
|
||||||
|
// skip index table and padding
|
||||||
|
long long offsetSectorArrayStart = (offsetIndexTable + (long long)sectorTableEntryCount*sizeof(unsigned int));
|
||||||
|
// align to SECTOR_SIZE
|
||||||
|
offsetSectorArrayStart = (offsetSectorArrayStart + SECTOR_SIZE - 1);
|
||||||
|
offsetSectorArrayStart = offsetSectorArrayStart - (offsetSectorArrayStart%SECTOR_SIZE);
|
||||||
|
_fseeki64(outputFile, offsetSectorArrayStart, SEEK_SET);
|
||||||
|
|
||||||
|
unsigned int indexTableSize = sizeof(unsigned int) * sectorTableEntryCount;
|
||||||
|
unsigned int* sectorIndexTable = (unsigned int*)malloc(sizeof(unsigned int) * sectorTableEntryCount);
|
||||||
|
unsigned char* sectorHashArray = (unsigned char*)malloc(sizeof(unsigned char) * SECTOR_HASH_SIZE * sectorTableEntryCount);
|
||||||
|
unsigned int uniqueSectorCount = 0;
|
||||||
|
printf("Compressing %d sectors...\n", sectorTableEntryCount);
|
||||||
|
unsigned char buffer[SECTOR_SIZE];
|
||||||
|
unsigned char sectorHash[32];
|
||||||
|
unsigned int storedSectors = 0;
|
||||||
|
int currentPct = 0;
|
||||||
|
long long compressedSize = offsetSectorArrayStart;
|
||||||
|
for(unsigned int i=0; i<sectorTableEntryCount; i++)
|
||||||
|
{
|
||||||
|
// print status
|
||||||
|
int newPct = ((i+1)*1000)/sectorTableEntryCount;
|
||||||
|
if( currentPct != newPct )
|
||||||
|
{
|
||||||
|
currentPct = newPct;
|
||||||
|
int compressionRatio = (int)(((long long)i * SECTOR_SIZE)*10 / compressedSize);
|
||||||
|
printf("\r%d.%d%% Compression ratio: 1:%d.%d \r", currentPct/10, currentPct%10, compressionRatio/10, compressionRatio%10);
|
||||||
|
}
|
||||||
|
// read sector and generate hash
|
||||||
|
wud_readData(inputFile, buffer, SECTOR_SIZE, (long long)i * (long long)SECTOR_SIZE);
|
||||||
|
calculateHash256(buffer, SECTOR_SIZE, sectorHash);
|
||||||
|
unsigned int sectorReuseIndex = 0xFFFFFFFF;
|
||||||
|
// try to locate any previous sector with same hash
|
||||||
|
for(unsigned int f=0; f<uniqueSectorCount; f++)
|
||||||
|
{
|
||||||
|
if( memcmp(sectorHash, sectorHashArray+f*SECTOR_HASH_SIZE, SECTOR_HASH_SIZE) == 0 )
|
||||||
|
{
|
||||||
|
sectorReuseIndex = f;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// if we found a sector then just store the index
|
||||||
|
if( sectorReuseIndex != 0xFFFFFFFF )
|
||||||
|
{
|
||||||
|
sectorIndexTable[i] = sectorReuseIndex;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// else store the sector and append a new index
|
||||||
|
fwrite(buffer, SECTOR_SIZE, 1, outputFile);
|
||||||
|
memcpy(sectorHashArray+uniqueSectorCount*SECTOR_HASH_SIZE, sectorHash, SECTOR_HASH_SIZE);
|
||||||
|
compressedSize += SECTOR_SIZE;
|
||||||
|
sectorIndexTable[i] = uniqueSectorCount;
|
||||||
|
uniqueSectorCount++;
|
||||||
|
storedSectors++;
|
||||||
|
}
|
||||||
|
printf("100%% \n");
|
||||||
|
_fseeki64(outputFile, offsetIndexTable, SEEK_SET);
|
||||||
|
fwrite(sectorIndexTable, sectorTableEntryCount, sizeof(unsigned int), outputFile);
|
||||||
|
fclose(outputFile);
|
||||||
|
puts("done");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool decompressWUD(wud_t* inputFile, FILE* outputFile, char* outputPath)
|
||||||
|
{
|
||||||
|
long long inputSize = wud_getWUDSize(inputFile);
|
||||||
|
printf("Decompressing...\n");
|
||||||
|
unsigned char buffer[SECTOR_SIZE];
|
||||||
|
long long currentIndex = 0;
|
||||||
|
int currentPct = 0;
|
||||||
|
while( currentIndex < inputSize )
|
||||||
|
{
|
||||||
|
// print status
|
||||||
|
int newPct = (int)((currentIndex*1000LL)/inputSize);
|
||||||
|
if( currentPct != newPct )
|
||||||
|
{
|
||||||
|
currentPct = newPct;
|
||||||
|
printf("\r%d.%d%% \r", currentPct/10, currentPct%10);
|
||||||
|
}
|
||||||
|
// calculate how many bytes to read
|
||||||
|
int bytesToRead = SECTOR_SIZE;
|
||||||
|
if( (inputSize-currentIndex) < SECTOR_SIZE )
|
||||||
|
bytesToRead = (int)(inputSize-currentIndex);
|
||||||
|
// read data
|
||||||
|
wud_readData(inputFile, buffer, bytesToRead, currentIndex);
|
||||||
|
// write data
|
||||||
|
fwrite(buffer, bytesToRead, 1, outputFile);
|
||||||
|
currentIndex += (long long)bytesToRead;
|
||||||
|
}
|
||||||
|
printf("100%% \n");
|
||||||
|
fclose(outputFile);
|
||||||
|
puts("done");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
if( argc < 2 )
|
||||||
|
{
|
||||||
|
puts("Wii U image compression tool v1.0 by Exzap");
|
||||||
|
puts("Lossless compression and decompression for Wii U dumps.");
|
||||||
|
puts("");
|
||||||
|
puts("Usage:");
|
||||||
|
puts("WudCompress <game.wud/game.wux> [-noverify]");
|
||||||
|
puts("");
|
||||||
|
puts("Parameters:");
|
||||||
|
puts("-noverify Skip the file validation step at the end");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
char* wudPath = argv[1];
|
||||||
|
// parse options
|
||||||
|
bool skipVerify = false;
|
||||||
|
for(int i=2; i<argc; i++)
|
||||||
|
{
|
||||||
|
if( stricmp(argv[i], "-noverify") == 0 )
|
||||||
|
{
|
||||||
|
skipVerify = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
printf("Unknown option: %s\n", argv[i]);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// verify path
|
||||||
|
if( wudPath[0] == '-' )
|
||||||
|
{
|
||||||
|
puts("Invalid input file");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
// open input file
|
||||||
|
wud_t* wud = wud_open(wudPath);
|
||||||
|
if( wud == NULL )
|
||||||
|
{
|
||||||
|
printf("Unable to open input file \"%s\"\n", wudPath);
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
// create path of output file by replacing the extension with .wux
|
||||||
|
char* outputPath = (char*)malloc(strlen(wudPath)+4+1); // allocate space for up to 4 extra characters in case we need to add the .wux extension
|
||||||
|
strcpy(outputPath, wudPath);
|
||||||
|
// replace with opposite extension (wux <-> wud)
|
||||||
|
char* newExtension;
|
||||||
|
if( wud_isWUXCompressed(wud) )
|
||||||
|
{
|
||||||
|
printf("Mode: Decompress\n");
|
||||||
|
newExtension = ".wud";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
printf("Mode: Compress\n");
|
||||||
|
newExtension = ".wux";
|
||||||
|
}
|
||||||
|
bool extensionFound = false;
|
||||||
|
for(int i=strlen(outputPath)-1; i>=0; i--)
|
||||||
|
{
|
||||||
|
if( outputPath[i] == '.' )
|
||||||
|
{
|
||||||
|
extensionFound = true;
|
||||||
|
strcpy(outputPath+i, newExtension);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if( extensionFound == false )
|
||||||
|
strcat(outputPath, newExtension);
|
||||||
|
// make sure the output file doesn't already exist (avoid accidental overwriting)
|
||||||
|
FILE* outputFile;
|
||||||
|
outputFile = fopen(outputPath, "r");
|
||||||
|
if( outputFile != NULL )
|
||||||
|
{
|
||||||
|
printf("Output file \"%s\" already exists.\n", outputPath);
|
||||||
|
wud_close(wud);
|
||||||
|
return -4;
|
||||||
|
}
|
||||||
|
// open output file
|
||||||
|
outputFile = fopen(outputPath, "wb");
|
||||||
|
if( outputFile == NULL )
|
||||||
|
{
|
||||||
|
printf("Unable to create output file\n");
|
||||||
|
wud_close(wud);
|
||||||
|
return -3;
|
||||||
|
}
|
||||||
|
printf("Input:\n");
|
||||||
|
puts(wudPath);
|
||||||
|
printf("Output:\n");
|
||||||
|
puts(outputPath);
|
||||||
|
if( wud_isWUXCompressed(wud) )
|
||||||
|
{
|
||||||
|
if( decompressWUD(wud, outputFile, outputPath) == false )
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if( compressWUD(wud, outputFile, outputPath) == false )
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
// verify
|
||||||
|
if( skipVerify == false )
|
||||||
|
{
|
||||||
|
if( validateWUX(wudPath, outputPath) == false )
|
||||||
|
{
|
||||||
|
printf("Validation failed. \"%s\" is corrupted.\n", outputPath);
|
||||||
|
// delete output file
|
||||||
|
remove(outputPath);
|
||||||
|
return -5;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
printf("Validation successful. No errors detected.\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
157
WudCompress/wud.cpp
Normal file
157
WudCompress/wud.cpp
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
#include<stdio.h>
|
||||||
|
#include<stdlib.h>
|
||||||
|
#include<string.h>
|
||||||
|
#include"wud.h"
|
||||||
|
|
||||||
|
long long wud_getFileSize64(FILE* file)
|
||||||
|
{
|
||||||
|
long long prevSeek = _ftelli64(file);
|
||||||
|
_fseeki64(file, 0, SEEK_END);
|
||||||
|
long long fileSize = _ftelli64(file);
|
||||||
|
_fseeki64(file, prevSeek, SEEK_SET);
|
||||||
|
return fileSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
long long wud_getCurrentSeek64(FILE* file)
|
||||||
|
{
|
||||||
|
long long currentSeek = _ftelli64(file);
|
||||||
|
return currentSeek;
|
||||||
|
}
|
||||||
|
|
||||||
|
void wud_setCurrentSeek64(FILE* file, long long newSeek)
|
||||||
|
{
|
||||||
|
_fseeki64(file, newSeek, SEEK_SET);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Open .wud (Wii U image) or .wux (Wii U compressed image) file
|
||||||
|
*/
|
||||||
|
wud_t* wud_open(char* path)
|
||||||
|
{
|
||||||
|
FILE* inputFile;
|
||||||
|
inputFile = fopen(path, "rb");
|
||||||
|
if( inputFile == NULL )
|
||||||
|
return NULL;
|
||||||
|
// allocate wud struct
|
||||||
|
wud_t* wud = (wud_t*)malloc(sizeof(wud_t));
|
||||||
|
memset(wud, 0x00, sizeof(wud_t));
|
||||||
|
wud->fileWud = inputFile;
|
||||||
|
// get size of file
|
||||||
|
long long inputFileSize = wud_getFileSize64(wud->fileWud);
|
||||||
|
// determine whether the WUD is compressed or not
|
||||||
|
wuxHeader_t wuxHeader = {0};
|
||||||
|
if( fread(&wuxHeader, sizeof(wuxHeader_t), 1, wud->fileWud) != 1 )
|
||||||
|
{
|
||||||
|
// file is too short to be either
|
||||||
|
wud_close(wud);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if( wuxHeader.magic0 == WUX_MAGIC_0 && wuxHeader.magic1 == WUX_MAGIC_1 )
|
||||||
|
{
|
||||||
|
// this is a compressed file
|
||||||
|
wud->isCompressed = true;
|
||||||
|
wud->sectorSize = wuxHeader.sectorSize;
|
||||||
|
wud->uncompressedSize = wuxHeader.uncompressedSize;
|
||||||
|
// validate header values
|
||||||
|
if( wud->sectorSize < 0x100 || wud->sectorSize >= 0x10000000 )
|
||||||
|
{
|
||||||
|
wud_close(wud);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
// calculate offsets and sizes
|
||||||
|
wud->indexTableEntryCount = (unsigned int)((wud->uncompressedSize+(long long)(wud->sectorSize-1)) / (long long)wud->sectorSize);
|
||||||
|
wud->offsetIndexTable = wud_getCurrentSeek64(wud->fileWud);
|
||||||
|
wud->offsetSectorArray = (wud->offsetIndexTable + (long long)wud->indexTableEntryCount*sizeof(unsigned int));
|
||||||
|
// align to SECTOR_SIZE
|
||||||
|
wud->offsetSectorArray = (wud->offsetSectorArray + (long long)(wud->sectorSize-1));
|
||||||
|
wud->offsetSectorArray = wud->offsetSectorArray - (wud->offsetSectorArray%(long long)wud->sectorSize);
|
||||||
|
// read index table
|
||||||
|
unsigned int indexTableSize = sizeof(unsigned int) * wud->indexTableEntryCount;
|
||||||
|
wud->indexTable = (unsigned int*)malloc(sizeof(unsigned int) * wud->indexTableEntryCount);
|
||||||
|
wud_setCurrentSeek64(wud->fileWud, wud->offsetIndexTable);
|
||||||
|
if( fread(wud->indexTable, sizeof(unsigned int), wud->indexTableEntryCount, wud->fileWud) != wud->indexTableEntryCount )
|
||||||
|
{
|
||||||
|
// could not read index table
|
||||||
|
wud_close(wud);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// uncompressed file
|
||||||
|
wud->uncompressedSize = inputFileSize;
|
||||||
|
}
|
||||||
|
return wud;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Close wud/wux reader
|
||||||
|
*/
|
||||||
|
void wud_close(wud_t* wud)
|
||||||
|
{
|
||||||
|
fclose(wud->fileWud);
|
||||||
|
if( wud->indexTable )
|
||||||
|
free(wud->indexTable);
|
||||||
|
free(wud);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Read data
|
||||||
|
* Transparently handles WUX decompression
|
||||||
|
* Can read up to 4GB-1 at once
|
||||||
|
*/
|
||||||
|
unsigned int wud_readData(wud_t* wud, void* buffer, unsigned int length, long long offset)
|
||||||
|
{
|
||||||
|
// make sure there is no out-of-bounds read
|
||||||
|
long long fileBytesLeft = wud->uncompressedSize - offset;
|
||||||
|
if( fileBytesLeft <= 0 )
|
||||||
|
return 0;
|
||||||
|
if( fileBytesLeft < (long long)length )
|
||||||
|
length = (unsigned int)fileBytesLeft;
|
||||||
|
// read data
|
||||||
|
unsigned int readBytes = 0;
|
||||||
|
if( wud->isCompressed == false )
|
||||||
|
{
|
||||||
|
// uncompressed read is just a 1:1 copy
|
||||||
|
wud_setCurrentSeek64(wud->fileWud, offset);
|
||||||
|
readBytes = (unsigned int)fread(buffer, 1, length, wud->fileWud);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// compressed read must be handled on a per-sector level
|
||||||
|
while( length > 0 )
|
||||||
|
{
|
||||||
|
unsigned int sectorOffset = (unsigned int)(offset % (long long)wud->sectorSize);
|
||||||
|
unsigned int remainingSectorBytes = wud->sectorSize - sectorOffset;
|
||||||
|
unsigned int sectorIndex = (unsigned int)(offset / (long long)wud->sectorSize);
|
||||||
|
unsigned int bytesToRead = (remainingSectorBytes<length)?remainingSectorBytes:length; // read only up to the end of the current sector
|
||||||
|
// look up real sector index
|
||||||
|
sectorIndex = wud->indexTable[sectorIndex];
|
||||||
|
wud_setCurrentSeek64(wud->fileWud, wud->offsetSectorArray + (long long)sectorIndex*(long long)wud->sectorSize+(long long)sectorOffset);
|
||||||
|
readBytes += (unsigned int)fread(buffer, 1, bytesToRead, wud->fileWud);
|
||||||
|
// progress read offset, write pointer and decrease length
|
||||||
|
buffer = (void*)((char*)buffer + bytesToRead);
|
||||||
|
length -= bytesToRead;
|
||||||
|
offset += bytesToRead;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return readBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns true if the file uses .wux compression, false otherwise
|
||||||
|
*/
|
||||||
|
bool wud_isWUXCompressed(wud_t* wud)
|
||||||
|
{
|
||||||
|
return wud->isCompressed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns size of data in bytes
|
||||||
|
* For .wud: Size of raw file
|
||||||
|
* For .wux: Size of uncompressed data
|
||||||
|
*/
|
||||||
|
long long wud_getWUDSize(wud_t* wud)
|
||||||
|
{
|
||||||
|
return wud->uncompressedSize;
|
||||||
|
}
|
32
WudCompress/wud.h
Normal file
32
WudCompress/wud.h
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
unsigned int magic0;
|
||||||
|
unsigned int magic1;
|
||||||
|
unsigned int sectorSize;
|
||||||
|
unsigned long long uncompressedSize;
|
||||||
|
unsigned int flags;
|
||||||
|
}wuxHeader_t;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
FILE* fileWud;
|
||||||
|
long long uncompressedSize;
|
||||||
|
bool isCompressed;
|
||||||
|
// data only used when compressed
|
||||||
|
unsigned int sectorSize;
|
||||||
|
unsigned int indexTableEntryCount;
|
||||||
|
unsigned int* indexTable;
|
||||||
|
long long offsetIndexTable;
|
||||||
|
long long offsetSectorArray;
|
||||||
|
}wud_t;
|
||||||
|
|
||||||
|
#define WUX_MAGIC_0 '0XUW' // "WUX0"
|
||||||
|
#define WUX_MAGIC_1 0x1099d02e
|
||||||
|
|
||||||
|
// wud and wux functions
|
||||||
|
wud_t* wud_open(char* path); // handles both, compressed and uncompressed files
|
||||||
|
void wud_close(wud_t* wud);
|
||||||
|
|
||||||
|
unsigned int wud_readData(wud_t* wud, void* buffer, unsigned int length, long long offset);
|
||||||
|
bool wud_isWUXCompressed(wud_t* wud);
|
||||||
|
long long wud_getWUDSize(wud_t* wud);
|
Loading…
Reference in New Issue
Block a user