diff --git a/WudCompress.sln b/WudCompress.sln new file mode 100644 index 0000000..c10cbf9 --- /dev/null +++ b/WudCompress.sln @@ -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 diff --git a/WudCompress/WudCompress.vcproj b/WudCompress/WudCompress.vcproj new file mode 100644 index 0000000..c47d258 --- /dev/null +++ b/WudCompress/WudCompress.vcproj @@ -0,0 +1,193 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/WudCompress/main.cpp b/WudCompress/main.cpp new file mode 100644 index 0000000..793b6e2 --- /dev/null +++ b/WudCompress/main.cpp @@ -0,0 +1,349 @@ +#include +#include +#include +#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 [-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 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; +} \ No newline at end of file diff --git a/WudCompress/wud.cpp b/WudCompress/wud.cpp new file mode 100644 index 0000000..eb728a5 --- /dev/null +++ b/WudCompress/wud.cpp @@ -0,0 +1,157 @@ +#include +#include +#include +#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 = (remainingSectorBytesindexTable[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; +} \ No newline at end of file diff --git a/WudCompress/wud.h b/WudCompress/wud.h new file mode 100644 index 0000000..b748d29 --- /dev/null +++ b/WudCompress/wud.h @@ -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); \ No newline at end of file