From fbac545897b258a6731f133a72c1daf3560595ab Mon Sep 17 00:00:00 2001 From: Steveice10 <1269164+Steveice10@users.noreply.github.com> Date: Fri, 17 Feb 2017 23:47:03 -0800 Subject: [PATCH] Major cleanup. --- source/3ds/3ds.h | 12 - source/3ds/cbmd.cpp | 80 ++--- source/3ds/cbmd.h | 14 +- source/3ds/cwav.cpp | 228 +++++++++----- source/3ds/cwav.h | 9 +- source/3ds/lz11.cpp | 16 +- source/3ds/lz11.h | 2 +- source/3ds/smdh.h | 135 ++++---- source/3ds/util.cpp | 110 ------- source/3ds/util.h | 17 - source/cmd.cpp | 753 ++++++++++++++++++++++++++++---------------- source/pc/wav.cpp | 87 +++-- source/pc/wav.h | 7 - source/types.h | 2 + 14 files changed, 843 insertions(+), 629 deletions(-) delete mode 100644 source/3ds/3ds.h delete mode 100644 source/3ds/util.cpp delete mode 100644 source/3ds/util.h diff --git a/source/3ds/3ds.h b/source/3ds/3ds.h deleted file mode 100644 index 738ecd2..0000000 --- a/source/3ds/3ds.h +++ /dev/null @@ -1,12 +0,0 @@ -#ifndef I_3DS_H -#define I_3DS_H - -#include "cbmd.h" -#include "cwav.h" -#include "smdh.h" - -#include "data.h" -#include "lz11.h" -#include "util.h" - -#endif \ No newline at end of file diff --git a/source/3ds/cbmd.cpp b/source/3ds/cbmd.cpp index e7722d5..d848f0d 100644 --- a/source/3ds/cbmd.cpp +++ b/source/3ds/cbmd.cpp @@ -6,76 +6,78 @@ #include typedef struct { - char magic[4] = {'C', 'B', 'M', 'D'}; - u32 zero = 0; - u32 cgfxOffsets[14] = {0}; - u8 padding[0x44] = {0}; - u32 cwavOffset = 0; + char magic[4]; + u32 zero; + u32 cgfxOffsets[CBMD_NUM_CGFXS]; + u8 padding[0x44]; + u32 cwavOffset; } CBMDHeader; -u8* cbmd_build_data(CBMD cbmd, u32* size, bool bnr) { - u32 headerSize = sizeof(CBMDHeader); +static void* cbmd_build_data(u32* size, CBMD cbmd, bool bnr) { CBMDHeader header; + memset(&header, 0, sizeof(header)); - u8* compressedCGFXs[14] = {NULL}; + memcpy(header.magic, "CBMD", sizeof(header.magic)); + + u32 outputSize = sizeof(CBMDHeader); + + void* compressedCGFXs[14] = {NULL}; u32 compressedCGFXSizes[14] = {0}; - - u32 offset = headerSize; - for(int i = 0; i < 14; i++) { + for(u32 i = 0; i < CBMD_NUM_CGFXS; i++) { if(cbmd.cgfxs[i] != NULL) { - compressedCGFXs[i] = lz11_compress(cbmd.cgfxs[i], cbmd.cgfxSizes[i], &compressedCGFXSizes[i]); - header.cgfxOffsets[i] = offset; - offset += compressedCGFXSizes[i]; + header.cgfxOffsets[i] = outputSize; + + compressedCGFXs[i] = lz11_compress(&compressedCGFXSizes[i], cbmd.cgfxs[i], cbmd.cgfxSizes[i]); + outputSize += compressedCGFXSizes[i]; } } - u32 pad = 0; if(bnr) { - pad = 0x10 - (offset % 0x10); - offset += pad; + outputSize = (outputSize + 0xF) & ~0xF; } if(cbmd.cwav != NULL) { - header.cwavOffset = offset; - offset += cbmd.cwavSize; + header.cwavOffset = outputSize; + + outputSize += cbmd.cwavSize; } - u8* output = (u8*) malloc(offset); - u32 pos = 0; + void* output = calloc(outputSize, sizeof(u8)); + if(output == NULL) { + for(u32 i = 0; i < CBMD_NUM_CGFXS; i++) { + if(cbmd.cgfxs[i] != NULL) { + free(compressedCGFXs[i]); + } + } - memcpy(output + pos, &header, headerSize); - pos += headerSize; + printf("ERROR: Could not allocate memory for CBMD data.\n"); + return NULL; + } - for(int i = 0; i < 14; i++) { + memcpy(output, &header, sizeof(header)); + + for(u32 i = 0; i < CBMD_NUM_CGFXS; i++) { if(compressedCGFXs[i] != NULL) { - memcpy(output + pos, compressedCGFXs[i], compressedCGFXSizes[i]); + memcpy(&((u8*) output)[header.cgfxOffsets[i]], compressedCGFXs[i], compressedCGFXSizes[i]); free(compressedCGFXs[i]); - compressedCGFXs[i] = NULL; - pos += compressedCGFXSizes[i]; } } - if(bnr) { - memset(output + pos, 0, pad); - pos += pad; - } - if(cbmd.cwav != NULL) { - memcpy(output + pos, cbmd.cwav, cbmd.cwavSize); - pos += cbmd.cwavSize; + memcpy(&((u8*) output)[header.cwavOffset], cbmd.cwav, cbmd.cwavSize); } if(size != NULL) { - *size = offset; + *size = outputSize; } return output; } -u8* cbmd_build(CBMD cbmd, u32* size) { - return cbmd_build_data(cbmd, size, false); +void* cbmd_build(u32* size, CBMD cbmd) { + return cbmd_build_data(size, cbmd, false); } -u8* bnr_build(CBMD cbmd, u32* size) { - return cbmd_build_data(cbmd, size, true); +void* bnr_build(u32* size, CBMD cbmd) { + return cbmd_build_data(size, cbmd, true); } \ No newline at end of file diff --git a/source/3ds/cbmd.h b/source/3ds/cbmd.h index c9cac83..9ecb65f 100644 --- a/source/3ds/cbmd.h +++ b/source/3ds/cbmd.h @@ -3,6 +3,8 @@ #include "../types.h" +#define CBMD_NUM_CGFXS 14 + typedef enum { CGFX_COMMON, CGFX_EUR_ENGLISH, @@ -21,13 +23,13 @@ typedef enum { } CBMDCGFX; typedef struct { - u8* cgfxs[14] = {NULL}; - u32 cgfxSizes[14] = {0}; - u8* cwav = NULL; - u32 cwavSize = 0; + void* cgfxs[CBMD_NUM_CGFXS]; + u32 cgfxSizes[CBMD_NUM_CGFXS]; + void* cwav; + u32 cwavSize; } CBMD; -u8* cbmd_build(CBMD cbmd, u32* size); -u8* bnr_build(CBMD cbmd, u32* size); +void* cbmd_build(u32* size, CBMD cbmd); +void* bnr_build(u32* size, CBMD cbmd); #endif \ No newline at end of file diff --git a/source/3ds/cwav.cpp b/source/3ds/cwav.cpp index 4de6e0e..33f61be 100644 --- a/source/3ds/cwav.cpp +++ b/source/3ds/cwav.cpp @@ -3,118 +3,180 @@ #include #include -typedef enum { - PCM8, - PCM16, - TODO -} CWAVType; +enum { + CWAV_REF_DSP_ADPCM_INFO = 0x0300, + CWAV_REF_IMA_ADPCM_INFO = 0x0301, + CWAV_REF_SAMPLE_DATA = 0x1F00, + CWAV_REF_INFO_BLOCK = 0x7000, + CWAV_REF_DATA_BLOCK = 0x7001, + CWAV_REF_CHANNEL_INFO = 0x7100 +}; typedef struct { - char magic[4] = {'C', 'W', 'A', 'V'}; - u16 endianess = 0xFEFF; - u16 structLength = 0x40; - u32 unknown0 = 0; + u16 typeId; + u16 padding; + u32 offset; +} CWAVReference; + +typedef struct { + CWAVReference ref; + u32 size; +} CWAVSizedReference; + +typedef struct { + u32 count; + CWAVReference contents[0]; // Relative to beginning of CWAVReferenceTable. +} CWAVReferenceTable; + +#define CWAV_MAGIC "CWAV" + +enum { + CWAV_ENDIANNESS_LITTLE = 0xFEFF, + CWAV_ENDIANNESS_BIG = 0xFFFE +}; + +#define CWAV_VERSION 0x02010000 + +typedef struct { + char magic[4]; + u16 endianness; + u16 headerSize; + u32 version; u32 fileSize; - u32 numChunks = 2; - u32 infoChunkFlags = 0x7000; - u32 infoChunkOffset; - u32 infoChunkLength; - u32 dataChunkFlags = 0x7000; - u32 dataChunkOffset; - u32 dataChunkLength; - u8 reserved[0x14] = {0}; + u16 numBlocks; + u16 reserved; + CWAVSizedReference infoBlock; // Relative to start of file. + CWAVSizedReference dataBlock; // Relative to start of file. } CWAVHeader; +#define CWAV_BLOCK_MAGIC_INFO "INFO" +#define CWAV_BLOCK_MAGIC_DATA "DATA" + typedef struct { - char magic[4] = {'I', 'N', 'F', 'O'}; - u32 length; - u32 type; + char magic[4]; + u32 size; +} CWAVBlockHeader; + +enum { + CWAV_ENCODING_PCM8 = 0, + CWAV_ENCODING_PCM16 = 1, + CWAV_ENCODING_DSP_ADPCM = 2, + CWAV_ENCODING_IMA_ADPCM = 3 +}; + +typedef struct { + CWAVBlockHeader header; + u8 encoding; + bool loop; + u16 padding; u32 sampleRate; - u32 unknown1 = 0; - u32 totalSamples; - u32 unknown2 = 0; - u32 totalChannels; -} CWAVInfoHeader; + u32 loopStartFrame; + u32 loopEndFrame; + u32 reserved; + CWAVReferenceTable channelInfos; +} CWAVInfoBlockHeader; typedef struct { - u32 flags = 0x7100; - u32 offset; -} CWAVChannelDataPointer; + CWAVReference samples; // Relative to CWAVDataBlock.data + CWAVReference adpcmInfo; // Relative to beginning of CWAVChannelInfo. + u32 reserved; +} CWAVChannelInfo; typedef struct { - u32 flags = 0x1F00; - u32 offset; - u32 unknown3 = 0; - u32 unknown4 = 0; - u32 padding = 0; -} CWAVChannelData; + u16 coefficients[16]; +} CWAVDSPADPCMParam; typedef struct { - char magic[4] = {'D', 'A', 'T', 'A'}; - u32 length; -} CWAVDataHeader; + u8 predictorScale; + u8 reserved; + u16 previousSample; + u16 secondPreviousSample; +} CWAVDSPADPCMContext; -u8* cwav_build(CWAV cwav, u32* size) { - CWAVHeader header; - u32 offset = sizeof(CWAVHeader); +typedef struct { + CWAVDSPADPCMParam param; + CWAVDSPADPCMContext context; + CWAVDSPADPCMContext loopContext; + u16 padding; +} CWAVDSPADPCMInfo; - header.infoChunkOffset = offset; - header.infoChunkLength = (u32) sizeof(CWAVInfoHeader) + (u32) ((sizeof(CWAVChannelDataPointer) + sizeof(CWAVChannelData)) * cwav.channels); - offset += header.infoChunkLength; +typedef struct { + u16 data; + u8 tableIndex; + u8 padding; +} CWAVIMAADPCMContext; - header.dataChunkOffset = offset; - header.dataChunkLength = (u32) sizeof(CWAVDataHeader) + cwav.dataSize; - offset += header.dataChunkLength; +typedef struct { + CWAVIMAADPCMContext context; + CWAVIMAADPCMContext loopContext; +} CWAVIMAADPCMInfo; - header.fileSize = offset; +typedef struct { + CWAVBlockHeader header; + u8 data[0]; +} CWAVDataBlock; - u8* output = (u8*) malloc(offset); - u32 pos = 0; +void* cwav_build(u32* size, CWAV cwav) { + u32 headerSize = (sizeof(CWAVHeader) + 0x1F) & ~0x1F; + u32 infoSize = ((sizeof(CWAVInfoBlockHeader) + (cwav.channels * (sizeof(CWAVReference) + sizeof(CWAVChannelInfo)))) + 0x1F) & ~0x1F; + u32 dataSize = ((sizeof(CWAVDataBlock) + 0x1F) & ~0x1F) + cwav.dataSize; - memcpy(output + pos, &header, sizeof(CWAVHeader)); - pos += sizeof(CWAVHeader); + u32 outputSize = headerSize + infoSize + dataSize; - u32 bytesPerSample = (u32) cwav.bitsPerSample / 8; - - CWAVInfoHeader infoHeader; - infoHeader.length = header.infoChunkLength; - infoHeader.type = cwav.bitsPerSample == 16 ? PCM16 : PCM8; - infoHeader.sampleRate = cwav.sampleRate; - infoHeader.totalSamples = cwav.dataSize / bytesPerSample / cwav.channels; - infoHeader.totalChannels = cwav.channels; - memcpy(output + pos, &infoHeader, sizeof(CWAVInfoHeader)); - pos += sizeof(CWAVInfoHeader); - - for(int i = 0; i < cwav.channels; i++) { - CWAVChannelDataPointer pointer; - pointer.offset = 0x4 + (cwav.channels * (u32) sizeof(CWAVChannelDataPointer)) + (i * (u32) sizeof(CWAVChannelData)); - memcpy(output + pos, &pointer, sizeof(CWAVChannelDataPointer)); - pos += sizeof(CWAVChannelDataPointer); + void* output = calloc(outputSize, sizeof(u8)); + if(output == NULL) { + printf("ERROR: Could not allocate memory for CWAV data.\n"); + return NULL; } - u32 bytesPerChannel = cwav.dataSize / cwav.channels; - for(int i = 0; i < cwav.channels; i++) { - CWAVChannelData data; - data.offset = i * bytesPerChannel; - memcpy(output + pos, &data, sizeof(CWAVChannelData)); - pos += sizeof(CWAVChannelData); + CWAVHeader* header = (CWAVHeader*) &((u8*) output)[0]; + memcpy(header->magic, CWAV_MAGIC, sizeof(header->magic)); + header->endianness = CWAV_ENDIANNESS_LITTLE; + header->headerSize = (u16) headerSize; + header->version = CWAV_VERSION; + header->fileSize = outputSize; + header->numBlocks = 2; + header->infoBlock.ref.typeId = CWAV_REF_INFO_BLOCK; + header->infoBlock.ref.offset = headerSize; + header->infoBlock.size = infoSize; + header->dataBlock.ref.typeId = CWAV_REF_DATA_BLOCK; + header->dataBlock.ref.offset = headerSize + infoSize; + header->dataBlock.size = dataSize; + + CWAVInfoBlockHeader* infoBlockHeader = (CWAVInfoBlockHeader*) &((u8*) output)[headerSize]; + memcpy(infoBlockHeader->header.magic, CWAV_BLOCK_MAGIC_INFO, sizeof(infoBlockHeader->header.magic)); + infoBlockHeader->header.size = infoSize; + infoBlockHeader->encoding = cwav.bitsPerSample == 16 ? CWAV_ENCODING_PCM16 : CWAV_ENCODING_PCM8; + infoBlockHeader->loop = cwav.loop; + infoBlockHeader->sampleRate = cwav.sampleRate; + infoBlockHeader->loopStartFrame = cwav.loopStartFrame; + infoBlockHeader->loopEndFrame = cwav.loopEndFrame != 0 ? cwav.loopEndFrame : cwav.dataSize / cwav.channels / (cwav.bitsPerSample / 8); + infoBlockHeader->channelInfos.count = cwav.channels; + for(u32 c = 0; c < cwav.channels; c++) { + infoBlockHeader->channelInfos.contents[c].typeId = CWAV_REF_CHANNEL_INFO; + infoBlockHeader->channelInfos.contents[c].offset = sizeof(CWAVReferenceTable) + (cwav.channels * sizeof(CWAVReference)) + (c * sizeof(CWAVChannelInfo)); + + CWAVChannelInfo* info = (CWAVChannelInfo*) &((u8*) output)[headerSize + sizeof(CWAVInfoBlockHeader) + (cwav.channels * sizeof(CWAVReference)) + (c * sizeof(CWAVChannelInfo))]; + info->samples.typeId = CWAV_REF_SAMPLE_DATA; + info->samples.offset = (((sizeof(CWAVDataBlock) + 0x1F) & ~0x1F) - sizeof(CWAVDataBlock)) + (c * (cwav.dataSize / cwav.channels)); + info->adpcmInfo.typeId = 0; + info->adpcmInfo.offset = 0xFFFFFFFF; } - CWAVDataHeader dataHeader; - dataHeader.length = header.dataChunkLength; - memcpy(output + pos, &dataHeader, sizeof(CWAVDataHeader)); - pos += sizeof(CWAVDataHeader); + CWAVDataBlock* dataBlock = (CWAVDataBlock*) &((u8*) output)[headerSize + infoSize]; + memcpy(dataBlock->header.magic, CWAV_BLOCK_MAGIC_DATA, sizeof(dataBlock->header.magic)); + dataBlock->header.size = dataSize; - for(int i = 0; i < cwav.dataSize; i += cwav.channels * bytesPerSample) { - for(int c = 0; c < cwav.channels; c++) { - memcpy(output + pos + (bytesPerChannel * c) + (i / cwav.channels), cwav.data + i + (c * bytesPerSample), bytesPerSample); + for(u32 i = 0; i < cwav.dataSize; i += cwav.channels * (cwav.bitsPerSample / 8)) { + for(u32 c = 0; c < cwav.channels; c++) { + CWAVChannelInfo* info = (CWAVChannelInfo*) &((u8*) output)[headerSize + sizeof(CWAVInfoBlockHeader) + (cwav.channels * sizeof(CWAVReference)) + (c * sizeof(CWAVChannelInfo))]; + + memcpy(&dataBlock->data[info->samples.offset + (i / cwav.channels)], &((u8*) cwav.data)[i + (c * (cwav.bitsPerSample / 8))], cwav.bitsPerSample / 8); } } - pos += cwav.dataSize; - if(size != NULL) { - *size = pos; + *size = outputSize; } return output; diff --git a/source/3ds/cwav.h b/source/3ds/cwav.h index 568f7ef..f502867 100644 --- a/source/3ds/cwav.h +++ b/source/3ds/cwav.h @@ -7,10 +7,15 @@ typedef struct { u32 channels; u32 sampleRate; u32 bitsPerSample; + + bool loop; + u32 loopStartFrame; + u32 loopEndFrame; + u32 dataSize; - u8* data; + void* data; } CWAV; -u8* cwav_build(CWAV wav, u32* size); +void* cwav_build(u32* size, CWAV wav); #endif \ No newline at end of file diff --git a/source/3ds/lz11.cpp b/source/3ds/lz11.cpp index 133ce9a..c0c239a 100644 --- a/source/3ds/lz11.cpp +++ b/source/3ds/lz11.cpp @@ -1,6 +1,5 @@ #include "lz11.h" -#include #include #include @@ -46,8 +45,8 @@ int lz11_get_occurence_length(u8* newPtr, int newLength, u8* oldPtr, int oldLeng return maxLength; } -u8* lz11_compress(u8* input, u32 inputSize, u32* size) { - if (inputSize > 0xFFFFFF) { +void* lz11_compress(u32* size, void* input, u32 inputSize) { + if(inputSize > 0xFFFFFF) { printf("ERROR: LZ11 input is too large.\n"); return NULL; } @@ -74,9 +73,9 @@ u8* lz11_compress(u8* input, u32 inputSize, u32* size) { int disp = 0; int oldLength = MIN(readBytes, 0x1000); - int length = lz11_get_occurence_length(input + readBytes, MIN(inputSize - readBytes, 0x10110), input + readBytes - oldLength, oldLength, &disp); + int length = lz11_get_occurence_length((u8*) input + readBytes, MIN(inputSize - readBytes, 0x10110), (u8*) input + readBytes - oldLength, oldLength, &disp); if(length < 3) { - outbuffer[bufferlength++] = *(input + (readBytes++)); + outbuffer[bufferlength++] = *((u8*) input + (readBytes++)); } else { readBytes += length; outbuffer[0] |= (u8)(1 << (7 - bufferedBlocks)); @@ -119,9 +118,12 @@ u8* lz11_compress(u8* input, u32 inputSize, u32* size) { compressedLength += padLength; } - u8* buf = (u8*) malloc((size_t) compressedLength); + void* buf = malloc((size_t) compressedLength); ss.read((char*) buf, compressedLength); - *size = (u32) compressedLength; + if(size != NULL) { + *size = (u32) compressedLength; + } + return buf; } diff --git a/source/3ds/lz11.h b/source/3ds/lz11.h index d590107..d4b8d80 100644 --- a/source/3ds/lz11.h +++ b/source/3ds/lz11.h @@ -3,6 +3,6 @@ #include "../types.h" -u8* lz11_compress(u8* input, u32 inputSize, u32* size); +void* lz11_compress(u32* size, void* input, u32 inputSize); #endif \ No newline at end of file diff --git a/source/3ds/smdh.h b/source/3ds/smdh.h index e7500a3..a733bd3 100644 --- a/source/3ds/smdh.h +++ b/source/3ds/smdh.h @@ -3,87 +3,94 @@ #include "../types.h" -typedef enum { - JAPANESE, - ENGLISH, - FRENCH, - GERMAN, - ITALIAN, - SPANISH, - SIMPLIFIED_CHINESE, - KOREAN, - DUTCH, - PORTUGESE, - RUSSIAN, - TRADITIONAL_CHINESE -} SMDHTitleLanguage; +#define SMDH_NUM_LANGUAGE_SLOTS 16 +#define SMDH_NUM_VALID_LANGUAGE_SLOTS 12 -// TODO: Provide values to set ratings to. -typedef enum { - CERO = 0, - ESRB = 1, - USK = 3, - PEGI_GEN = 4, - PEGI_PTR = 6, - PEGI_BBFC = 7, - COB = 8, - GRB = 9, - CGSRR = 10 -} SMDHGameRating; +#define SMDH_NUM_RATING_SLOTS 16 + +#define SMDH_SMALL_ICON_SIZE 24 +#define SMDH_LARGE_ICON_SIZE 48 typedef enum { - JAPAN = 0x01, - NORTH_AMERICA = 0x02, - EUROPE = 0x04, - AUSTRALIA = 0x08, - CHINA = 0x10, - KOREA = 0x20, - TAIWAN = 0x40, + SMDH_LANGUAGE_JAPANESE, + SMDH_LANGUAGE_ENGLISH, + SMDH_LANGUAGE_FRENCH, + SMDH_LANGUAGE_GERMAN, + SMDH_LANGUAGE_ITALIAN, + SMDH_LANGUAGE_SPANISH, + SMDH_LANGUAGE_SIMPLIFIED_CHINESE, + SMDH_LANGUAGE_KOREAN, + SMDH_LANGUAGE_DUTCH, + SMDH_LANGUAGE_PORTUGUESE, + SMDH_LANGUAGE_RUSSIAN, + SMDH_LANGUAGE_TRADITIONAL_CHINESE +} SMDHLanguage; + +typedef enum { + SMDH_RATING_CERO = 0, + SMDH_RATING_ESRB = 1, + SMDH_RATING_USK = 3, + SMDH_RATING_PEGI_GEN = 4, + SMDH_RATING_PEGI_PTR = 6, + SMDH_RATING_PEGI_BBFC = 7, + SMDH_RATING_COB = 8, + SMDH_RATING_GRB = 9, + SMDH_RATING_CGSRR = 10 +} SMDHRating; + +typedef enum { + SMDH_REGION_JAPAN = 0x01, + SMDH_REGION_NORTH_AMERICA = 0x02, + SMDH_REGION_EUROPE = 0x04, + SMDH_REGION_AUSTRALIA = 0x08, + SMDH_REGION_CHINA = 0x10, + SMDH_REGION_KOREA = 0x20, + SMDH_REGION_TAIWAN = 0x40, // Not a bitmask, but a value. - REGION_FREE = 0x7FFFFFFF -} SMDHRegionFlag; + SMDH_REGION_FREE = 0x7FFFFFFF +} SMDHRegion; typedef enum { - VISIBLE = 0x0001, - AUTO_BOOT = 0x0002, - ALLOW_3D = 0x0004, - REQUIRE_EULA = 0x0008, - AUTO_SAVE_ON_EXIT = 0x0010, - USE_EXTENDED_BANNER = 0x0020, - RATING_REQUIED = 0x0040, - USE_SAVE_DATA = 0x0080, - RECORD_USAGE = 0x0100, - DISABLE_SAVE_BACKUPS = 0x0400, - NEW_3DS = 0x1000 + SMDH_FLAG_VISIBLE = 0x0001, + SMDH_FLAG_AUTO_BOOT = 0x0002, + SMDH_FLAG_ALLOW_3D = 0x0004, + SMDH_FLAG_REQUIRE_EULA = 0x0008, + SMDH_FLAG_AUTO_SAVE_ON_EXIT = 0x0010, + SMDH_FLAG_USE_EXTENDED_BANNER = 0x0020, + SMDH_FLAG_RATING_REQUIED = 0x0040, + SMDH_FLAG_USE_SAVE_DATA = 0x0080, + SMDH_FLAG_RECORD_USAGE = 0x0100, + SMDH_FLAG_DISABLE_SAVE_BACKUPS = 0x0400, + SMDH_FLAG_NEW_3DS = 0x1000 } SMDHFlag; typedef struct { - u16 shortTitle[0x40] = {0}; - u16 longTitle[0x80] = {0}; - u16 publisher[0x40] = {0}; + u16 shortTitle[0x40]; + u16 longTitle[0x80]; + u16 publisher[0x40]; } SMDHTitle; typedef struct { - u8 gameRatings[0x10] = {0}; - u32 regionLock = REGION_FREE; - u8 matchMakerId[0xC] = {0}; - u32 flags = 0; - u16 eulaVersion = 0; - u16 reserved1 = 0; - u32 optimalBannerFrame = 0; - u32 streetpassId = 0; + u8 gameRatings[SMDH_NUM_RATING_SLOTS]; + u32 regionLock; + u8 matchMakerId[0xC]; + u32 flags; + u16 eulaVersion; + u16 reserved1; + u32 optimalBannerFrame; + u32 streetpassId; } SMDHSettings; typedef struct { - char magic[4] = {'S', 'M', 'D', 'H'}; - u16 version = 0; - u16 reserved0 = 0; - SMDHTitle titles[0x10]; + char magic[4]; + u16 version; + u16 reserved0; + SMDHTitle titles[SMDH_NUM_LANGUAGE_SLOTS]; SMDHSettings settings; - u64 reserved2 = 0; - u8 smallIcon[0x480] = {0}; - u8 largeIcon[0x1200] = {0}; + u64 reserved1; + u16 smallIcon[SMDH_SMALL_ICON_SIZE * SMDH_SMALL_ICON_SIZE]; + u16 largeIcon[SMDH_LARGE_ICON_SIZE * SMDH_LARGE_ICON_SIZE]; } SMDH; #endif \ No newline at end of file diff --git a/source/3ds/util.cpp b/source/3ds/util.cpp deleted file mode 100644 index 8bf9fb7..0000000 --- a/source/3ds/util.cpp +++ /dev/null @@ -1,110 +0,0 @@ -#include "util.h" - -#include "../pc/stb_image.h" - -#include - -u8 TILE_ORDER[64] = { 0, 1, 8, 9, 2, 3, 10, 11, 16, 17, 24, 25, 18, 19, 26, 27, - 4, 5, 12, 13, 6, 7, 14, 15, 20, 21, 28, 29, 22, 23, 30, 31, - 32, 33, 40, 41, 34, 35, 42, 43, 48, 49, 56, 57, 50, 51, 58, 59, - 36, 37, 44, 45, 38, 39, 46, 47, 52, 53, 60, 61, 54, 55, 62, 63 }; - -void utf8_to_utf16(u16* dst, const char* src, size_t max_len) { - size_t n = 0; - while(src[n]) { - dst[n] = (u16) src[n]; - if(n++ >= max_len) { - return; - } - } -} - -u16 pack_color(u8 r, u8 g, u8 b, u8 a, PixelFormat format) { - if(format == RGB565) { - float alpha = a / 255.0f; - r = (u8) (r * alpha) >> 3; - g = (u8) (g * alpha) >> 2; - b = (u8) (b * alpha) >> 3; - return (r << 11) | (g << 5) | b; - } else if(format == RGBA4444) { - r >>= 4; - g >>= 4; - b >>= 4; - a >>= 4; - return r << 12 | g << 8 | b << 4 | a; - } - - return 0; -} - -u8* load_image(const char* image, u32 width, u32 height) { - unsigned char *img; - int imgWidth, imgHeight, imgDepth; - - img = stbi_load(image, &imgWidth, &imgHeight, &imgDepth, STBI_rgb_alpha); - - if(img == NULL) { - printf("ERROR: Could not load image file: %s.\n", stbi_failure_reason()); - return NULL; - } - - if(width == 0) { - width = imgWidth; - } - - if(height == 0) { - height = imgHeight; - } - - if(imgWidth != width || imgHeight != height) { - printf("ERROR: Image must be exactly %d x %d in size.\n", width, height); - stbi_image_free(img); - return NULL; - } - - if(imgDepth != STBI_rgb_alpha) { - printf("ERROR: Decoded image does't match expected format (%d, wanted %d).\n", - imgDepth, STBI_rgb_alpha); - stbi_image_free(img); - return NULL; - } - - return img; -} - -void free_image(u8* img) { - stbi_image_free(img); -} - -u16* image_data_to_tiles(u8* img, u32 width, u32 height, PixelFormat format, u32* size) { - u16* converted = (u16*) malloc(width * height * sizeof(u16)); - u32 n = 0; - for(int y = 0; y < height; y += 8) { - for(int x = 0; x < width; x += 8) { - for(int k = 0; k < 8 * 8; k++) { - u32 xx = (u32) (TILE_ORDER[k] & 0x7); - u32 yy = (u32) (TILE_ORDER[k] >> 3); - - u8* pixel = img + (((y + yy) * width + (x + xx)) * 4); - converted[n++] = pack_color(pixel[0], pixel[1], pixel[2], pixel[3], format); - } - } - } - - if(size != NULL) { - *size = width * height * (u32) sizeof(u16); - } - - return converted; -} - -u16* image_to_tiles(const char* image, u32 width, u32 height, PixelFormat format, u32* size) { - u8* img = load_image(image, width, height); - if(img == NULL) { - return NULL; - } - - u16* tiles = image_data_to_tiles(img, width, height, format, size); - free_image(img); - return tiles; -} \ No newline at end of file diff --git a/source/3ds/util.h b/source/3ds/util.h deleted file mode 100644 index 9e1d6c7..0000000 --- a/source/3ds/util.h +++ /dev/null @@ -1,17 +0,0 @@ -#ifndef UTIL_H -#define UTIL_H - -#include "../types.h" - -typedef enum { - RGB565, - RGBA4444 -} PixelFormat; - -void utf8_to_utf16(u16* dst, const char* src, size_t max_len); -u16 pack_color(u8 r, u8 g, u8 b, u8 a, PixelFormat format); -u8* load_image(const char* image, u32 width, u32 height); -u16* image_data_to_tiles(u8* img, u32 width, u32 height, PixelFormat format, u32* size); -u16* image_to_tiles(const char* image, u32 width, u32 height, PixelFormat format, u32* size); - -#endif \ No newline at end of file diff --git a/source/cmd.cpp b/source/cmd.cpp index a3658eb..bfba7a8 100644 --- a/source/cmd.cpp +++ b/source/cmd.cpp @@ -1,303 +1,394 @@ #include "cmd.h" -#include "3ds/3ds.h" -#include "pc/wav.h" -#include "pc/stb_vorbis.h" -#include "types.h" +#include "3ds/cbmd.h" +#include "3ds/cwav.h" +#include "3ds/data.h" +#include "3ds/lz11.h" #include "3ds/smdh.h" +#include "pc/stb_image.h" +#include "pc/stb_vorbis.h" +#include "pc/wav.h" -#include #include #include +#include #include -#include #include -u8* convert_to_cgfx(const std::string& image, u32 width, u32 height, u32* size) { - u32 convertedSize = 0; - u16* converted = image_to_tiles(image.c_str(), width, height, RGBA4444, &convertedSize); - if(converted == NULL) { +typedef enum { + RGB565, + RGBA4444 +} PixelFormat; + +static void utf8_to_utf16(u16* dst, const char* src, size_t maxLen) { + if(maxLen == 0) { + return; + } + + size_t n = 0; + while(src[n]) { + dst[n] = (u16) src[n]; + if(n++ >= maxLen) { + return; + } + } +} + +static void* read_file(u32* size, const std::string& file) { + FILE* fd = fopen(file.c_str(), "rb"); + if(fd == NULL) { + perror("ERROR: Could not open file for reading"); return NULL; } - u8* ret = (u8*) malloc(BANNER_CGFX_HEADER_LENGTH + convertedSize); - memcpy(ret, BANNER_CGFX_HEADER, BANNER_CGFX_HEADER_LENGTH); - memcpy(ret + BANNER_CGFX_HEADER_LENGTH, converted, convertedSize); + long tell = 0; + if(fseek(fd, 0, SEEK_END) != 0 || (tell = ftell(fd)) < 0 || fseek(fd, 0, SEEK_SET) != 0) { + fclose(fd); - *size = BANNER_CGFX_HEADER_LENGTH + convertedSize; - free(converted); - return ret; + perror("ERROR: Failed to determine file size"); + return NULL; + } + + u32 bufferSize = (u32) tell; + void* buffer = malloc(bufferSize); + if(buffer == NULL) { + fclose(fd); + + printf("ERROR: Could not allocate memory for file data.\n"); + return NULL; + } + + long readRet = fread(buffer, 1, bufferSize, fd); + + fclose(fd); + + if(readRet != bufferSize) { + free(buffer); + + perror("ERROR: Failed to read file"); + return NULL; + } + + if(size != NULL) { + *size = bufferSize; + } + + return buffer; } -u8* convert_to_cwav(const std::string& file, u32* size) { - u8* ret = NULL; - // Determine what file type we have - FILE* fd = fopen(file.c_str(), "rb"); - char magic[4]; - fread(magic, sizeof(magic), 1, fd); - rewind(fd); // equivalent to SEEK_SET to pos 0 +static bool write_file(void* contents, u32 size, const std::string& file) { + FILE* fd = fopen(file.c_str(), "wb"); + if(fd == NULL) { + perror("ERROR: Could not open file for writing"); + return false; + } - if (magic[0] == 'R' && magic[1] == 'I' && magic[2] == 'F' && magic[3] == 'F') { + long writeRet = fwrite(contents, 1, size, fd); + + fclose(fd); + + if(writeRet != size) { + perror("ERROR: Failed to write file"); + return false; + } + + return true; +} + +static void* load_image(const std::string& file, u32 width, u32 height) { + int imgWidth = 0; + int imgHeight = 0; + int imgDepth = 0; + unsigned char* img = stbi_load(file.c_str(), &imgWidth, &imgHeight, &imgDepth, STBI_rgb_alpha); + if(img == NULL) { + printf("ERROR: Could not load image file: %s.\n", stbi_failure_reason()); + return NULL; + } + + if(width == 0) { + width = (u32) imgWidth; + } + + if(height == 0) { + height = (u32) imgHeight; + } + + if(imgWidth != width || imgHeight != height) { + stbi_image_free(img); + + printf("ERROR: Image must be exactly %d x %d in size.\n", width, height); + return NULL; + } + + if(imgDepth != STBI_rgb_alpha) { + stbi_image_free(img); + + printf("ERROR: Decoded image does't match expected format (%d, wanted %d).\n", imgDepth, STBI_rgb_alpha); + return NULL; + } + + return img; +} + +static void free_image(void* img) { + stbi_image_free(img); +} + +static void image_data_to_tiles(void* out, void* img, u32 width, u32 height, PixelFormat format) { + for(u32 y = 0; y < height; y++) { + for(u32 x = 0; x < width; x++) { + u32 index = (((y >> 3) * (width >> 3) + (x >> 3)) << 6) + ((x & 1) | ((y & 1) << 1) | ((x & 2) << 1) | ((y & 2) << 2) | ((x & 4) << 2) | ((y & 4) << 3)); + + u8* pixel = &((u8*) img)[(y * width + x) * sizeof(u32)]; + u16 color = 0; + if(format == RGB565) { + float alpha = pixel[3] / 255.0f; + color = (u16) ((((u8) (pixel[0] * alpha) & ~0x7) << 8) | (((u8) (pixel[1] * alpha) & ~0x3) << 3) | ((u8) (pixel[2] * alpha) >> 3)); + } else if(format == RGBA4444) { + color = (u16) (((pixel[0] & ~0xF) << 8) | ((pixel[1] & ~0xF) << 4)| (pixel[2] & ~0xF) | (pixel[3] >> 4)); + } + + ((u16*) out)[index] = color; + } + } +} + +static void* convert_to_cgfx(u32* size, const std::string& file) { + u32 bufferSize = BANNER_CGFX_HEADER_LENGTH + (256 * 128 * sizeof(u16)); + void* buffer = malloc(bufferSize); + if(buffer == NULL) { + printf("ERROR: Could not allocate memory for CGFX data.\n"); + return NULL; + } + + void* img = load_image(file, 256, 128); + if(img == NULL) { + free(buffer); + + return NULL; + } + + memcpy(buffer, BANNER_CGFX_HEADER, BANNER_CGFX_HEADER_LENGTH); + image_data_to_tiles((u16*) ((u8*) buffer + BANNER_CGFX_HEADER_LENGTH), img, 256, 128, RGBA4444); + + free_image(img); + + if(size != NULL) { + *size = bufferSize; + } + + return buffer; +} + +static void* convert_to_cwav(u32* size, const std::string& file, bool loop, u32 loopStartFrame, u32 loopEndFrame) { + FILE* fd = fopen(file.c_str(), "rb"); + if(fd == NULL) { + perror("ERROR: Failed to open file for CWAV conversion"); + return NULL; + } + + char magic[4]; + if(fread(magic, 1, sizeof(magic), fd) != sizeof(magic)) { + fclose(fd); + + perror("ERROR: Failed to read audio file magic"); + return NULL; + } + + rewind(fd); + + CWAV cwav; + memset(&cwav, 0, sizeof(cwav)); + + cwav.loop = loop; + cwav.loopStartFrame = loopStartFrame; + cwav.loopEndFrame = loopEndFrame; + + if(memcmp(magic, "RIFF", sizeof(magic)) == 0) { WAV* wav = wav_read(fd); if(wav != NULL) { - CWAV cwav; cwav.channels = wav->format.numChannels; cwav.sampleRate = wav->format.sampleRate; cwav.bitsPerSample = wav->format.bitsPerSample; - cwav.dataSize = wav->data.size; - cwav.data = wav->data.data; - ret = cwav_build(cwav, size); + cwav.dataSize = wav->data.size; + cwav.data = calloc(wav->data.size, sizeof(u8)); + if(cwav.data != NULL) { + memcpy(cwav.data, wav->data.data, wav->data.size); + } else { + printf("ERROR: Could not allocate memory for CWAV sample data.\n"); + } wav_free(wav); } - } else if (magic[0] == 'O' && magic[1] == 'g' && magic[2] == 'g' && magic[3] == 'S') { - int error; - stb_vorbis* vorb = stb_vorbis_open_file(fd, false, &error, NULL); - if(vorb != NULL) { - stb_vorbis_info info = stb_vorbis_get_info(vorb); + } else if(memcmp(magic, "OggS", sizeof(magic)) == 0) { + int error = 0; + stb_vorbis* vorbis = stb_vorbis_open_file(fd, false, &error, NULL); + + if(vorbis != NULL) { + stb_vorbis_info info = stb_vorbis_get_info(vorbis); + u32 sampleCount = stb_vorbis_stream_length_in_samples(vorbis) * info.channels; - CWAV cwav; cwav.channels = (u32) info.channels; cwav.sampleRate = info.sample_rate; - cwav.bitsPerSample = 16; // stb_vorbis always outputs 16 bit samples - u32 sampleCount = stb_vorbis_stream_length_in_samples(vorb) * info.channels; - cwav.dataSize = sampleCount * 2; - cwav.data = (u8*) calloc(sampleCount, 2); - stb_vorbis_get_samples_short_interleaved(vorb, info.channels, (short*) cwav.data, sampleCount); + cwav.bitsPerSample = sizeof(u16) * 8; - ret = cwav_build(cwav, size); + cwav.dataSize = sampleCount * sizeof(u16); + cwav.data = calloc(sampleCount, sizeof(u16)); + if(cwav.data != NULL) { + stb_vorbis_get_samples_short_interleaved(vorbis, info.channels, (short*) cwav.data, sampleCount); + } else { + printf("ERROR: Could not allocate memory for CWAV sample data.\n"); + } - free(cwav.data); - stb_vorbis_close(vorb); + stb_vorbis_close(vorbis); } else { - printf("ERROR: Vorbis open failed, error %d.\n", error); + printf("ERROR: Failed to open vorbis file: %d.\n", error); } } else { - printf("ERROR: Audio file header '%c%c%c%c' unrecognized.\n", magic[0], magic[1], magic[2], magic[3]); + printf("ERROR: Audio file magic '%c%c%c%c' unrecognized.\n", magic[0], magic[1], magic[2], magic[3]); } fclose(fd); + + if(cwav.data == NULL) { + return NULL; + } + + void* ret = cwav_build(size, cwav); + + free(cwav.data); return ret; } -int cmd_make_banner(const std::string& image, const std::string& audio, const std::string& cgfxFile, const std::string& cwavFile, const std::string& output) { - u32 cgfxSize = 0; - u8* cgfx = NULL; - if(!cgfxFile.empty()) { - FILE* fd = fopen(cgfxFile.c_str(), "r"); - if(!fd) { - printf("ERROR: Could not open CGFX file: %s\n", strerror(errno)); - return 1; - } - - fseek(fd, 0, SEEK_END); - cgfxSize = (u32) ftell(fd); - fseek(fd, 0, SEEK_SET); - - cgfx = (u8*) malloc(cgfxSize); - fread(cgfx, 1, cgfxSize, fd); - fclose(fd); - } else { - cgfx = convert_to_cgfx(image, 256, 128, &cgfxSize); - if(cgfx == NULL) { - return 2; - } - } - - u32 cwavSize = 0; - u8* cwav = NULL; - if(!cwavFile.empty()) { - FILE* fd = fopen(cwavFile.c_str(), "r"); - if(!fd) { - printf("ERROR: Could not open CWAV file: %s\n", strerror(errno)); - return 3; - } - - fseek(fd, 0, SEEK_END); - cwavSize = (u32) ftell(fd); - fseek(fd, 0, SEEK_SET); - - cwav = (u8*) malloc(cwavSize); - fread(cwav, 1, cwavSize, fd); - fclose(fd); - } else { - cwav = convert_to_cwav(audio, &cwavSize); - if(cwav == NULL) { - return 4; - } - } - +static int cmd_make_banner(const std::string* images, const std::string& audio, const std::string* cgfxFiles, const std::string& cwavFile, const std::string& output) { CBMD cbmd; - cbmd.cgfxs[CGFX_COMMON] = cgfx; - cbmd.cgfxSizes[CGFX_COMMON] = cgfxSize; - cbmd.cwav = cwav; - cbmd.cwavSize = cwavSize; + memset(&cbmd, 0, sizeof(cbmd)); + + bool error = false; + + for(u32 i = 0; i < CBMD_NUM_CGFXS && !error; i++) { + if(!cgfxFiles[i].empty()) { + error = (cbmd.cgfxs[i] = read_file(&cbmd.cgfxSizes[i], cgfxFiles[i])) == NULL; + } else if(!images[i].empty()) { + error = (cbmd.cgfxs[i] = convert_to_cgfx(&cbmd.cgfxSizes[i], images[i])) == NULL; + } + } + + if(!error) { + if(!cwavFile.empty()) { + error = (cbmd.cwav = read_file(&cbmd.cwavSize, cwavFile)) == NULL; + } else { + error = (cbmd.cwav = convert_to_cwav(&cbmd.cwavSize, audio, false, 0, 0)) == NULL; + } + } u32 bnrSize = 0; - u8* bnr = bnr_build(cbmd, &bnrSize); - free(cgfx); - free(cwav); + void* bnr = !error ? bnr_build(&bnrSize, cbmd) : NULL; - FILE* fd = fopen(output.c_str(), "wb"); - if(fd == NULL) { - printf("ERROR: Could not open output file: %s\n", strerror(errno)); - return 5; + for(u32 i = 0; i < CBMD_NUM_CGFXS && !error; i++) { + if(cbmd.cgfxs[i] != NULL) { + free(cbmd.cgfxs[i]); + } } - fwrite(bnr, 1, bnrSize, fd); - fclose(fd); + if(cbmd.cwav != NULL) { + free(cbmd.cwav); + } - free(bnr); + if(bnr == NULL || !write_file(bnr, bnrSize, output)) { + return 1; + } printf("Created banner \"%s\".\n", output.c_str()); return 0; } -int cmd_make_smdh(const std::string& shortTitle, const std::string& longTitle, const std::string& publisher, const std::string& icon, SMDH base, const std::string& output) { - u8* icon48Data = load_image(icon.c_str(), 48, 48); +static int cmd_make_smdh(SMDH& smdh, const std::string& icon, const std::string& output) { + u8* icon48Data = (u8*) load_image(icon.c_str(), SMDH_LARGE_ICON_SIZE, SMDH_LARGE_ICON_SIZE); if(icon48Data == NULL) { return 1; } - u16* icon48 = image_data_to_tiles(icon48Data, 48, 48, RGB565, NULL); - if(icon48 == NULL) { - return 1; - } + u8 icon24Data[SMDH_SMALL_ICON_SIZE * SMDH_SMALL_ICON_SIZE * sizeof(u32)]; - u8 icon24Data[24 * 24 * 4]; - for(int y = 0; y < 48; y += 2) { - for(int x = 0; x < 48; x += 2) { - int i1 = (y * 48 + x) * 4; - u8 r1 = icon48Data[i1 + 0]; - u8 g1 = icon48Data[i1 + 1]; - u8 b1 = icon48Data[i1 + 2]; - u8 a1 = icon48Data[i1 + 3]; + u32 scale = SMDH_LARGE_ICON_SIZE / SMDH_SMALL_ICON_SIZE; + u32 samples = scale * scale; + for(u32 y = 0; y < SMDH_LARGE_ICON_SIZE; y += scale) { + for(u32 x = 0; x < SMDH_LARGE_ICON_SIZE; x += scale) { + u32 r = 0; + u32 g = 0; + u32 b = 0; + u32 a = 0; - int i2 = (y * 48 + (x + 1)) * 4; - u8 r2 = icon48Data[i2 + 0]; - u8 g2 = icon48Data[i2 + 1]; - u8 b2 = icon48Data[i2 + 2]; - u8 a2 = icon48Data[i2 + 3]; + for(u32 oy = 0; oy < scale; oy++) { + for(u32 ox = 0; ox < scale; ox++) { + int i = ((y + oy) * SMDH_LARGE_ICON_SIZE + (x + ox)) * sizeof(u32); + r += icon48Data[i + 0]; + g += icon48Data[i + 1]; + b += icon48Data[i + 2]; + a += icon48Data[i + 3]; + } + } - int i3 = ((y + 1) * 48 + x) * 4; - u8 r3 = icon48Data[i3 + 0]; - u8 g3 = icon48Data[i3 + 1]; - u8 b3 = icon48Data[i3 + 2]; - u8 a3 = icon48Data[i3 + 3]; - - int i4 = ((y + 1) * 48 + (x + 1)) * 4; - u8 r4 = icon48Data[i4 + 0]; - u8 g4 = icon48Data[i4 + 1]; - u8 b4 = icon48Data[i4 + 2]; - u8 a4 = icon48Data[i4 + 3]; - - int id = ((y / 2) * 24 + (x / 2)) * 4; - icon24Data[id + 0] = (u8) ((r1 + r2 + r3 + r4) / 4); - icon24Data[id + 1] = (u8) ((g1 + g2 + g3 + g4) / 4); - icon24Data[id + 2] = (u8) ((b1 + b2 + b3 + b4) / 4); - icon24Data[id + 3] = (u8) ((a1 + a2 + a3 + a4) / 4); + int i = ((y / scale) * SMDH_SMALL_ICON_SIZE + (x / scale)) * sizeof(u32); + icon24Data[i + 0] = (u8) (r / samples); + icon24Data[i + 1] = (u8) (g / samples); + icon24Data[i + 2] = (u8) (b / samples); + icon24Data[i + 3] = (u8) (a / samples); } } - u16* icon24 = image_data_to_tiles(icon24Data, 24, 24, RGB565, NULL); - if(icon24 == NULL) { + image_data_to_tiles(smdh.largeIcon, icon48Data, SMDH_LARGE_ICON_SIZE, SMDH_LARGE_ICON_SIZE, RGB565); + image_data_to_tiles(smdh.smallIcon, icon24Data, SMDH_SMALL_ICON_SIZE, SMDH_SMALL_ICON_SIZE, RGB565); + + free_image(icon48Data); + + if(!write_file(&smdh, sizeof(SMDH), output)) { return 1; } - for(int i = 0; i < 0x10; i++) { - utf8_to_utf16(base.titles[i].shortTitle, shortTitle.c_str(), 0x40); - utf8_to_utf16(base.titles[i].longTitle, longTitle.c_str(), 0x80); - utf8_to_utf16(base.titles[i].publisher, publisher.c_str(), 0x40); - } - - memcpy(base.largeIcon, icon48, 0x1200); - memcpy(base.smallIcon, icon24, 0x480); - free(icon48); - - FILE* fd = fopen(output.c_str(), "wb"); - if(fd == NULL) { - printf("ERROR: Could not open output file: %s\n", strerror(errno)); - return 2; - } - - fwrite(&base, 1, sizeof(SMDH), fd); - fclose(fd); - printf("Created SMDH \"%s\".\n", output.c_str()); return 0; } -int cmd_make_cwav(const std::string& input, const std::string& output) { +static int cmd_make_cwav(const std::string& input, const std::string& output, bool loop, u32 loopStartFrame, u32 loopEndFrame) { u32 cwavSize = 0; - u8* cwav = convert_to_cwav(input, &cwavSize); - if(cwav == NULL) { + void* cwav = convert_to_cwav(&cwavSize, input, loop, loopStartFrame, loopEndFrame); + if(cwav == NULL || !write_file(cwav, cwavSize, output)) { return 1; } - FILE* fd = fopen(output.c_str(), "wb"); - if(fd == NULL) { - printf("ERROR: Could not open output file: %s\n", strerror(errno)); - return 2; - } - - fwrite(cwav, 1, cwavSize, fd); - fclose(fd); - - free(cwav); - printf("Created CWAV \"%s\".\n", output.c_str()); return 0; } -int cmd_lz11(const std::string& input, const std::string& output) { - FILE* in = fopen(input.c_str(), "r"); - if(in == NULL) { - printf("ERROR: Could not open input file: %s\n", strerror(errno)); +static int cmd_lz11(const std::string& input, const std::string& output) { + u32 size = 0; + void* data = read_file(&size, input); + if(data == NULL) { return 1; } - fseek(in, 0, SEEK_END); - u32 size = (u32) ftell(in); - fseek(in, 0, SEEK_SET); + u32 compressedSize = 0; + void* compressed = lz11_compress(&compressedSize, data, size); - u8* data = (u8*) malloc(size); - if(data == NULL) { - printf("ERROR: Could not allocate data buffer.\n"); - return 2; - } - - fread(data, 1, size, in); - fclose(in); - - u32 compressedSize; - u8* compressed = lz11_compress(data, size, &compressedSize); free(data); - if(compressed == NULL) { - return 3; + + if(compressed == NULL || !write_file(compressed, compressedSize, output)) { + return 1; } - FILE* fd = fopen(output.c_str(), "wb"); - if(fd == NULL) { - printf("ERROR: Could not open output file: %s\n", strerror(errno)); - return 4; - } - - fwrite(compressed, 1, compressedSize, fd); - fclose(fd); - - free(compressed); - printf("Compressed to file \"%s\".\n", output.c_str()); return 0; } -std::map cmd_get_args(int argc, char* argv[]) { +static std::map cmd_get_args(int argc, char* argv[]) { std::map args; for(int i = 0; i < argc; i++) { if((strncmp(argv[i], "-", 1) == 0 || strncmp(argv[i], "--", 2) == 0) && argc != i + 1) { - args.insert(std::pair(argv[i], argv[i + 1])); + args[argv[i]] = argv[i + 1]; i++; } } @@ -305,7 +396,7 @@ std::map cmd_get_args(int argc, char* argv[]) { return args; } -std::string cmd_find_arg(const std::map& args, const std::string& shortOpt, const std::string& longOpt, const std::string& def) { +static std::string cmd_find_arg(const std::map& args, const std::string& shortOpt, const std::string& longOpt, const std::string& def) { std::string sopt = "-" + shortOpt; std::string lopt = "--" + longOpt; @@ -322,7 +413,7 @@ std::string cmd_find_arg(const std::map& args, const s return def; } -std::vector cmd_parse_list(const std::string& list) { +static std::vector cmd_parse_list(const std::string& list) { std::vector ret; std::string::size_type lastPos = 0; std::string::size_type pos = 0; @@ -338,19 +429,81 @@ std::vector cmd_parse_list(const std::string& list) { return ret; } -void cmd_print_info(const std::string& command) { +static void cmd_print_info(const std::string& command) { if(command.compare("makebanner") == 0) { printf("makebanner - Creates a .bnr file.\n"); - printf(" -i/--image: PNG file to use as the banner's image. Interchangeable with -ci.\n"); - printf(" -a/--audio: WAV file to use as the banner's tune. Interchangeable with -ca.\n"); - printf(" -ci/--cgfximage: CGFX file to use as the banner's image. Interchangeable with -i.\n"); - printf(" -ca/--cwavaudio: CWAV file to use as the banner's tune. Interchangeable with -a.\n"); + printf(" -i/--image: Optional if specified for a language or with a CGFX. PNG file to use as the banner's default image.\n"); + printf(" -eei/--eurenglishimage: Optional if default or CGFX specified. PNG file to use as the banner's EUR English image.\n"); + printf(" -efi/--eurfrenchimage: Optional if default or CGFX specified. PNG file to use as the banner's EUR French image.\n"); + printf(" -egi/--eurgermanimage: Optional if default or CGFX specified. PNG file to use as the banner's EUR German image.\n"); + printf(" -eii/--euritalianimage: Optional if default or CGFX specified. PNG file to use as the banner's EUR Italian image.\n"); + printf(" -esi/--eurspanishimage: Optional if default or CGFX specified. PNG file to use as the banner's EUR Spanish image.\n"); + printf(" -edi/--eurdutchimage: Optional if default or CGFX specified. PNG file to use as the banner's EUR Dutch image.\n"); + printf(" -epi/--eurportugueseimage: Optional if default or CGFX specified. PNG file to use as the banner's EUR Portuguese image.\n"); + printf(" -eri/--eurrussianimage: Optional if default or CGFX specified. PNG file to use as the banner's EUR Russian image.\n"); + printf(" -jji/--jpnjapaneseimage: Optional if default or CGFX specified. PNG file to use as the banner's JPN Japanese image.\n"); + printf(" -uei/--usaenglishimage: Optional if default or CGFX specified. PNG file to use as the banner's USA English image.\n"); + printf(" -ufi/--usafrenchimage: Optional if default or CGFX specified. PNG file to use as the banner's USA French image.\n"); + printf(" -usi/--usaspanishimage: Optional if default or CGFX specified. PNG file to use as the banner's USA Spanish image.\n"); + printf(" -upi/--usaportugueseimage: Optional if default or CGFX specified. PNG file to use as the banner's USA Portuguese image.\n"); + printf(" -ci/--cgfximage: Optional if specified for a language or with a PNG. CGFX file to use as the banner's default image.\n"); + printf(" -eeci/--eurenglishcgfximage: Optional if default or PNG specified. CGFX file to use as the banner's EUR English image.\n"); + printf(" -efci/--eurfrenchcgfximage: Optional if default or PNG specified. CGFX file to use as the banner's EUR French image.\n"); + printf(" -egci/--eurgermancgfximage: Optional if default or PNG specified. CGFX file to use as the banner's EUR German image.\n"); + printf(" -eici/--euritaliancgfximage: Optional if default or PNG specified. CGFX file to use as the banner's EUR Italian image.\n"); + printf(" -esci/--eurspanishcgfximage: Optional if default or PNG specified. CGFX file to use as the banner's EUR Spanish image.\n"); + printf(" -edci/--eurdutchcgfximage: Optional if default or PNG specified. CGFX file to use as the banner's EUR Dutch image.\n"); + printf(" -epci/--eurportuguesecgfximage: Optional if default or PNG specified. CGFX file to use as the banner's EUR Portuguese image.\n"); + printf(" -erci/--eurrussiancgfximage: Optional if default or PNG specified. CGFX file to use as the banner's EUR Russian image.\n"); + printf(" -jjci/--jpnjapanesecgfximage: Optional if default or PNG specified. CGFX file to use as the banner's JPN Japanese image.\n"); + printf(" -ueci/--usaenglishcgfximage: Optional if default or PNG specified. CGFX file to use as the banner's USA English image.\n"); + printf(" -ufci/--usafrenchcgfximage: Optional if default or PNG specified. CGFX file to use as the banner's USA French image.\n"); + printf(" -usci/--usaspanishcgfximage: Optional if default or PNG specified. CGFX file to use as the banner's USA Spanish image.\n"); + printf(" -upci/--usaportuguesecgfximage: Optional if default or PNG specified. CGFX file to use as the banner's USA Portuguese image.\n"); + printf(" -a/--audio: Optional if specified for a language or with a CWAV. WAV/OGG file to use as the banner's tune.\n"); + printf(" -ca/--cwavaudio: Optional if specified for a language or with a WAV/OGG. CWAV file to use as the banner's tune.\n"); printf(" -o/--output: File to output the created banner to.\n"); } else if(command.compare("makesmdh") == 0) { printf("makesmdh - Creates a .smdh/.icn file.\n"); - printf(" -s/--shorttitle: Short title of the application.\n"); - printf(" -l/--longtitle: Long title of the application.\n"); - printf(" -p/--publisher: Publisher of the application.\n"); + printf(" -s/--shorttitle: Optional if specified for a language. Default short title of the application.\n"); + printf(" -js/--japaneseshorttitle: Optional if default specified. Japanese short title of the application.\n"); + printf(" -es/--englishshorttitle: Optional if default specified. English short title of the application.\n"); + printf(" -fs/--frenchshorttitle: Optional if default specified. French short title of the application.\n"); + printf(" -gs/--germanshorttitle: Optional if default specified. German short title of the application.\n"); + printf(" -is/--italianshorttitle: Optional if default specified. Italian short title of the application.\n"); + printf(" -ss/--spanishshorttitle: Optional if default specified. Spanish short title of the application.\n"); + printf(" -scs/--simplifiedchineseshorttitle: Optional if default specified. Simplified Chinese short title of the application.\n"); + printf(" -ks/--koreanshorttitle: Optional if default specified. Korean short title of the application.\n"); + printf(" -ds/--dutchshorttitle: Optional if default specified. Dutch short title of the application.\n"); + printf(" -ps/--portugueseshorttitle: Optional if default specified. Portuguese short title of the application.\n"); + printf(" -rs/--russianshorttitle: Optional if default specified. Russian short title of the application.\n"); + printf(" -tcs/--traditionalchineseshorttitle: Optional if default specified. Traditional Chinese short title of the application.\n"); + printf(" -l/--longtitle: Optional if specified for a language. Default long title of the application.\n"); + printf(" -jl/--japaneselongtitle: Optional if default specified. Japanese long title of the application.\n"); + printf(" -el/--englishlongtitle: Optional if default specified. English long title of the application.\n"); + printf(" -fl/--frenchlongtitle: Optional if default specified. French long title of the application.\n"); + printf(" -gl/--germanlongtitle: Optional if default specified. German long title of the application.\n"); + printf(" -il/--italianlongtitle: Optional if default specified. Italian long title of the application.\n"); + printf(" -sl/--spanishlongtitle: Optional if default specified. Spanish long title of the application.\n"); + printf(" -scl/--simplifiedchineselongtitle: Optional if default specified. Simplified Chinese long title of the application.\n"); + printf(" -kl/--koreanlongtitle: Optional if default specified. Korean long title of the application.\n"); + printf(" -dl/--dutchlongtitle: Optional if default specified. Dutch long title of the application.\n"); + printf(" -pl/--portugueselongtitle: Optional if default specified. Portuguese long title of the application.\n"); + printf(" -rl/--russianlongtitle: Optional if default specified. Russian long title of the application.\n"); + printf(" -tcl/--traditionalchineselongtitle: Optional if default specified. Traditional Chinese long title of the application.\n"); + printf(" -p/--publisher: Optional if specified for a language. Default publisher of the application.\n"); + printf(" -jp/--japanesepublisher: Optional if default specified. Japanese publisher of the application.\n"); + printf(" -ep/--englishpublisher: Optional if default specified. English publisher of the application.\n"); + printf(" -fp/--frenchpublisher: Optional if default specified. French publisher of the application.\n"); + printf(" -gp/--germanpublisher: Optional if default specified. German publisher of the application.\n"); + printf(" -ip/--italianpublisher: Optional if default specified. Italian publisher of the application.\n"); + printf(" -sp/--spanishpublisher: Optional if default specified. Spanish publisher of the application.\n"); + printf(" -scp/--simplifiedchinesepublisher: Optional if default specified. Simplified Chinese publisher of the application.\n"); + printf(" -kp/--koreanpublisher: Optional if default specified. Korean publisher of the application.\n"); + printf(" -dp/--dutchpublisher: Optional if default specified. Dutch publisher of the application.\n"); + printf(" -pp/--portuguesepublisher: Optional if default specified. Portuguese publisher of the application.\n"); + printf(" -rp/--russianpublisher: Optional if default specified. Russian publisher of the application.\n"); + printf(" -tcp/--traditionalchinesepublisher: Optional if default specified. Traditional Chinese publisher of the application.\n"); printf(" -i/--icon: PNG file to use as an icon.\n"); printf(" -o/--output: File to output the created SMDH/ICN to.\n"); printf(" -r/--regions: Optional. Comma separated list of regions to lock the SMDH to.\n"); @@ -374,6 +527,9 @@ void cmd_print_info(const std::string& command) { printf("makecwav - Creates a CWAV file from a WAV.\n"); printf(" -i/--input: WAV file to convert.\n"); printf(" -o/--output: File to output the created CWAV to.\n"); + printf(" -l/--loop: Optional. Whether or not the audio should loop (false/true).\n"); + printf(" -s/--loopstartframe: Optional. Sample frame to return to when looping.\n"); + printf(" -f/--loopendframe: Optional. Sample frame to loop at.\n"); } else if(command.compare("lz11") == 0) { printf("lz11 - Compresses a file with LZ11.\n"); printf(" -i/--input: File to compress.\n"); @@ -381,7 +537,7 @@ void cmd_print_info(const std::string& command) { } } -void cmd_print_commands() { +static void cmd_print_commands() { printf("Available commands:\n"); cmd_print_info("makebanner"); cmd_print_info("makesmdh"); @@ -389,23 +545,23 @@ void cmd_print_commands() { cmd_print_info("lz11"); } -void cmd_print_usage(const std::string& executedFrom) { +static void cmd_print_usage(const std::string& executedFrom) { printf("bannertool v%d.%d.%d\n", VERSION_MAJOR, VERSION_MINOR, VERSION_MICRO); printf("Usage: %s \n", executedFrom.c_str()); cmd_print_commands(); } -void cmd_missing_args(const std::string& command) { +static void cmd_missing_args(const std::string& command) { printf("Missing arguments for command \"%s\".\n", command.c_str()); cmd_print_info(command); } -void cmd_invalid_arg(const std::string& argument, const std::string& command) { +static void cmd_invalid_arg(const std::string& argument, const std::string& command) { printf("Invalid value for argument \"%s\" in command \"%s\".\n", argument.c_str(), command.c_str()); cmd_print_info(command); } -void cmd_invalid_command(const std::string& command) { +static void cmd_invalid_command(const std::string& command) { printf("Invalid command \"%s\".\n", command.c_str()); cmd_print_commands(); } @@ -419,52 +575,113 @@ int cmd_process_command(int argc, char* argv[]) { char* command = argv[1]; std::map args = cmd_get_args(argc, argv); if(strcmp(command, "makebanner") == 0) { - const std::string banner = cmd_find_arg(args, "i", "image", ""); const std::string audio = cmd_find_arg(args, "a", "audio", ""); - const std::string cgfxFile = cmd_find_arg(args, "ci", "cgfximage", ""); const std::string cwavFile = cmd_find_arg(args, "ca", "cwavaudio", ""); const std::string output = cmd_find_arg(args, "o", "output", ""); - if((banner.empty() && cgfxFile.empty()) || (audio.empty() && cwavFile.empty()) || output.empty()) { + if((audio.empty() && cwavFile.empty()) || output.empty()) { cmd_missing_args(command); return -1; } - return cmd_make_banner(banner, audio, cgfxFile, cwavFile, output); + static const char* imageShortArgs[CBMD_NUM_CGFXS] = {"i", "eei", "efi", "egi", "eii", "esi", "edi", "epi", "eri", "jji", "uei", "ufi", "usi", "upi"}; + static const char* imageLongArgs[CBMD_NUM_CGFXS] = {"image", "eurenglishimage", "eurfrenchimage", "eurgermanimage", "euritalianimage", "eurspanishimage", "eurdutchimage", "eurportugueseimage", "eurrussianimage", "jpnjapaneseimage", "usaenglishimage", "usafrenchimage", "usaspanishimage", "usaportugueseimage"}; + static const char* cgfxImageShortArgs[CBMD_NUM_CGFXS] = {"ci", "eeci", "efci", "egci", "eici", "esci", "edci", "epci", "erci", "jjci", "ueci", "ufci", "usci", "upci"}; + static const char* cgfxImageLongArgs[CBMD_NUM_CGFXS] = {"cgfximage", "eurenglishcgfximage", "eurfrenchcgfximage", "eurgermancgfximage", "euritaliancgfximage", "eurspanishcgfximage", "eurdutchcgfximage", "eurportuguesecgfximage", "eurrussiancgfximage", "jpnjapanesecgfximage", "usaenglishcgfximage", "usafrenchcgfximage", "usaspanishcgfximage", "usaportuguesecgfximage"}; + + std::string images[CBMD_NUM_CGFXS] = {""}; + std::string cgfxFiles[CBMD_NUM_CGFXS] = {""}; + + bool found = false; + + for(u32 i = 0; i < CBMD_NUM_CGFXS; i++) { + images[i] = cmd_find_arg(args, imageShortArgs[i], imageLongArgs[i], ""); + cgfxFiles[i] = cmd_find_arg(args, cgfxImageShortArgs[i], cgfxImageLongArgs[i], ""); + + found = found || !images[i].empty() || !cgfxFiles[i].empty(); + } + + if(!found) { + cmd_missing_args(command); + return -1; + } + + return cmd_make_banner(images, audio, cgfxFiles, cwavFile, output); } else if(strcmp(command, "makesmdh") == 0) { - const std::string shortTitle = cmd_find_arg(args, "s", "shorttitle", ""); - const std::string longTitle = cmd_find_arg(args, "l", "longtitle", ""); - const std::string publisher = cmd_find_arg(args, "p", "publisher", ""); const std::string icon = cmd_find_arg(args, "i", "icon", ""); const std::string output = cmd_find_arg(args, "o", "output", ""); - if(shortTitle.empty() || longTitle.empty() || publisher.empty() || icon.empty() || output.empty()) { + if(icon.empty() || output.empty()) { cmd_missing_args(command); return -1; } SMDH smdh; - + memset(&smdh, 0, sizeof(smdh)); + + memcpy(smdh.magic, "SMDH", sizeof(smdh.magic)); + + static const char* shortTitleShortArgs[SMDH_NUM_VALID_LANGUAGE_SLOTS] = {"js", "es", "fs", "gs", "is", "ss", "scs", "ks", "ds", "ps", "rs", "tcs"}; + static const char* shortTitleLongArgs[SMDH_NUM_VALID_LANGUAGE_SLOTS] = {"japaneseshorttitle", "englishshorttitle", "frenchshorttitle", "germanshorttitle", "italianshorttitle", "spanishshorttitle", "simplifiedchineseshorttitle", "koreanshorttitle", "dutchshorttitle", "portugueseshorttitle", "russianshorttitle", "traditionalchineseshorttitle"}; + static const char* longTitleShortArgs[SMDH_NUM_VALID_LANGUAGE_SLOTS] = {"jl", "el", "fl", "gl", "il", "sl", "scl", "kl", "dl", "pl", "rl", "tcl"}; + static const char* longTitleLongArgs[SMDH_NUM_VALID_LANGUAGE_SLOTS] = {"japaneselongtitle", "englishlongtitle", "frenchlongtitle", "germanlongtitle", "italianlongtitle", "spanishlongtitle", "simplifiedchineselongtitle", "koreanlongtitle", "dutchlongtitle", "portugueselongtitle", "russianlongtitle", "traditionalchineselongtitle"}; + static const char* publisherShortArgs[SMDH_NUM_VALID_LANGUAGE_SLOTS] = {"jp", "ep", "fp", "gp", "ip", "sp", "scp", "kp", "dp", "pp", "rp", "tcp"}; + static const char* publisherLongArgs[SMDH_NUM_VALID_LANGUAGE_SLOTS] = {"japanesepublisher", "englishpublisher", "frenchpublisher", "germanpublisher", "italianpublisher", "spanishpublisher", "simplifiedchinesepublisher", "koreanpublisher", "dutchpublisher", "portuguesepublisher", "russianpublisher", "traditionalchinesepublisher"}; + + const std::string shortTitle = cmd_find_arg(args, "s", "shorttitle", ""); + const std::string longTitle = cmd_find_arg(args, "l", "longtitle", ""); + const std::string publisher = cmd_find_arg(args, "p", "publisher", ""); + + bool shortTitleFound = false; + bool longTitleFound = false; + bool publisherFound = false; + + for(u32 i = 0; i < SMDH_NUM_LANGUAGE_SLOTS; i++) { + std::string currShortTitle = shortTitle; + std::string currLongTitle = longTitle; + std::string currPublisher = publisher; + + if(i < SMDH_NUM_VALID_LANGUAGE_SLOTS) { + currShortTitle = cmd_find_arg(args, shortTitleShortArgs[i], shortTitleLongArgs[i], shortTitle); + currLongTitle = cmd_find_arg(args, longTitleShortArgs[i], longTitleLongArgs[i], longTitle); + currPublisher = cmd_find_arg(args, publisherShortArgs[i], publisherLongArgs[i], publisher); + } + + shortTitleFound = shortTitleFound || !currShortTitle.empty(); + longTitleFound = longTitleFound || !currLongTitle.empty(); + publisherFound = publisherFound || !currPublisher.empty(); + + utf8_to_utf16(smdh.titles[i].shortTitle, currShortTitle.c_str(), sizeof(smdh.titles[i].shortTitle) / 2); + utf8_to_utf16(smdh.titles[i].longTitle, currLongTitle.c_str(), sizeof(smdh.titles[i].longTitle) / 2); + utf8_to_utf16(smdh.titles[i].publisher, currPublisher.c_str(), sizeof(smdh.titles[i].publisher) / 2); + } + + if(!shortTitleFound || !longTitleFound || !publisherFound) { + cmd_missing_args(command); + return -1; + } + std::vector regions = cmd_parse_list(cmd_find_arg(args, "r", "regions", "regionfree")); for(std::vector::iterator it = regions.begin(); it != regions.end(); it++) { const std::string region = *it; if(region.compare("regionfree") == 0) { - smdh.settings.regionLock = REGION_FREE; + smdh.settings.regionLock = SMDH_REGION_FREE; break; } else if(region.compare("japan") == 0) { - smdh.settings.regionLock |= JAPAN; + smdh.settings.regionLock |= SMDH_REGION_JAPAN; } else if(region.compare("northamerica") == 0) { - smdh.settings.regionLock |= NORTH_AMERICA; + smdh.settings.regionLock |= SMDH_REGION_NORTH_AMERICA; } else if(region.compare("europe") == 0) { - smdh.settings.regionLock |= EUROPE; + smdh.settings.regionLock |= SMDH_REGION_EUROPE; } else if(region.compare("australia") == 0) { - smdh.settings.regionLock |= AUSTRALIA; + smdh.settings.regionLock |= SMDH_REGION_AUSTRALIA; } else if(region.compare("china") == 0) { - smdh.settings.regionLock |= CHINA; + smdh.settings.regionLock |= SMDH_REGION_CHINA; } else if(region.compare("korea") == 0) { - smdh.settings.regionLock |= KOREA; + smdh.settings.regionLock |= SMDH_REGION_KOREA; } else if(region.compare("taiwan") == 0) { - smdh.settings.regionLock |= TAIWAN; + smdh.settings.regionLock |= SMDH_REGION_TAIWAN; } else { cmd_invalid_arg("regions", command); + return -1; } } @@ -472,29 +689,30 @@ int cmd_process_command(int argc, char* argv[]) { for(std::vector::iterator it = flags.begin(); it != flags.end(); it++) { const std::string flag = *it; if(flag.compare("visible") == 0) { - smdh.settings.flags |= VISIBLE; + smdh.settings.flags |= SMDH_FLAG_VISIBLE; } else if(flag.compare("autoboot") == 0) { - smdh.settings.flags |= AUTO_BOOT; + smdh.settings.flags |= SMDH_FLAG_AUTO_BOOT; } else if(flag.compare("allow3d") == 0) { - smdh.settings.flags |= ALLOW_3D; + smdh.settings.flags |= SMDH_FLAG_ALLOW_3D; } else if(flag.compare("requireeula") == 0) { - smdh.settings.flags |= REQUIRE_EULA; + smdh.settings.flags |= SMDH_FLAG_REQUIRE_EULA; } else if(flag.compare("autosave") == 0) { - smdh.settings.flags |= AUTO_SAVE_ON_EXIT; + smdh.settings.flags |= SMDH_FLAG_AUTO_SAVE_ON_EXIT; } else if(flag.compare("extendedbanner") == 0) { - smdh.settings.flags |= USE_EXTENDED_BANNER; + smdh.settings.flags |= SMDH_FLAG_USE_EXTENDED_BANNER; } else if(flag.compare("ratingrequired") == 0) { - smdh.settings.flags |= RATING_REQUIED; + smdh.settings.flags |= SMDH_FLAG_RATING_REQUIED; } else if(flag.compare("savedata") == 0) { - smdh.settings.flags |= USE_SAVE_DATA; + smdh.settings.flags |= SMDH_FLAG_USE_SAVE_DATA; } else if(flag.compare("recordusage") == 0) { - smdh.settings.flags |= RECORD_USAGE; + smdh.settings.flags |= SMDH_FLAG_RECORD_USAGE; } else if(flag.compare("nosavebackups") == 0) { - smdh.settings.flags |= DISABLE_SAVE_BACKUPS; + smdh.settings.flags |= SMDH_FLAG_DISABLE_SAVE_BACKUPS; } else if(flag.compare("new3ds") == 0) { - smdh.settings.flags |= NEW_3DS; + smdh.settings.flags |= SMDH_FLAG_NEW_3DS; } else { cmd_invalid_arg("flags", command); + return -1; } } @@ -505,26 +723,35 @@ int cmd_process_command(int argc, char* argv[]) { smdh.settings.optimalBannerFrame = (u32) atoll(cmd_find_arg(args, "obf", "optimalbannerframe", "0").c_str()); smdh.settings.streetpassId = (u32) atoll(cmd_find_arg(args, "spid", "streetpassid", "0").c_str()); - smdh.settings.gameRatings[CERO] = (u8) atoi(cmd_find_arg(args, "cer", "cero", "0").c_str()); - smdh.settings.gameRatings[ESRB] = (u8) atoi(cmd_find_arg(args, "er", "esrb", "0").c_str()); - smdh.settings.gameRatings[USK] = (u8) atoi(cmd_find_arg(args, "ur", "usk", "0").c_str()); - smdh.settings.gameRatings[PEGI_GEN] = (u8) atoi(cmd_find_arg(args, "pgr", "pegigen", "0").c_str()); - smdh.settings.gameRatings[PEGI_PTR] = (u8) atoi(cmd_find_arg(args, "ppr", "pegiptr", "0").c_str()); - smdh.settings.gameRatings[PEGI_BBFC] = (u8) atoi(cmd_find_arg(args, "pbr", "pegibbfc", "0").c_str()); - smdh.settings.gameRatings[COB] = (u8) atoi(cmd_find_arg(args, "cor", "cob", "0").c_str()); - smdh.settings.gameRatings[GRB] = (u8) atoi(cmd_find_arg(args, "gr", "grb", "0").c_str()); - smdh.settings.gameRatings[CGSRR] = (u8) atoi(cmd_find_arg(args, "cgr", "cgsrr", "0").c_str()); + smdh.settings.gameRatings[SMDH_RATING_CERO] = (u8) atoi(cmd_find_arg(args, "cer", "cero", "0").c_str()); + smdh.settings.gameRatings[SMDH_RATING_ESRB] = (u8) atoi(cmd_find_arg(args, "er", "esrb", "0").c_str()); + smdh.settings.gameRatings[SMDH_RATING_USK] = (u8) atoi(cmd_find_arg(args, "ur", "usk", "0").c_str()); + smdh.settings.gameRatings[SMDH_RATING_PEGI_GEN] = (u8) atoi(cmd_find_arg(args, "pgr", "pegigen", "0").c_str()); + smdh.settings.gameRatings[SMDH_RATING_PEGI_PTR] = (u8) atoi(cmd_find_arg(args, "ppr", "pegiptr", "0").c_str()); + smdh.settings.gameRatings[SMDH_RATING_PEGI_BBFC] = (u8) atoi(cmd_find_arg(args, "pbr", "pegibbfc", "0").c_str()); + smdh.settings.gameRatings[SMDH_RATING_COB] = (u8) atoi(cmd_find_arg(args, "cor", "cob", "0").c_str()); + smdh.settings.gameRatings[SMDH_RATING_GRB] = (u8) atoi(cmd_find_arg(args, "gr", "grb", "0").c_str()); + smdh.settings.gameRatings[SMDH_RATING_CGSRR] = (u8) atoi(cmd_find_arg(args, "cgr", "cgsrr", "0").c_str()); - return cmd_make_smdh(shortTitle, longTitle, publisher, icon, smdh, output); + return cmd_make_smdh(smdh, icon, output); } else if(strcmp(command, "makecwav") == 0) { const std::string input = cmd_find_arg(args, "i", "input", ""); const std::string output = cmd_find_arg(args, "o", "output", ""); + std::string loop = cmd_find_arg(args, "l", "loop", "false"); + u32 loopStartFrame = (u32) atol(cmd_find_arg(args, "s", "loopstartframe", "0").c_str()); + u32 loopEndFrame = (u32) atol(cmd_find_arg(args, "e", "loopendframe", "0").c_str()); if(input.empty() || output.empty()) { cmd_missing_args(command); return -1; } - return cmd_make_cwav(input, output); + std::transform(loop.begin(), loop.end(), loop.begin(), (int (*)(int)) std::tolower); + if(loop != "false" && loop != "true") { + cmd_invalid_arg("loop", command); + return -1; + } + + return cmd_make_cwav(input, output, loop == "true", loopStartFrame, loopEndFrame); } else if(strcmp(command, "lz11") == 0) { const std::string input = cmd_find_arg(args, "i", "input", ""); const std::string output = cmd_find_arg(args, "o", "output", ""); diff --git a/source/pc/wav.cpp b/source/pc/wav.cpp index d369a86..8e71d8b 100644 --- a/source/pc/wav.cpp +++ b/source/pc/wav.cpp @@ -1,8 +1,9 @@ #include "wav.h" +#include + #include #include -#include typedef struct { FILE* fd; @@ -11,11 +12,11 @@ typedef struct { } WavContext; static bool wav_next_chunk(WavContext* context) { - if(fseek(context->fd, context->currChunkDataPos + (memcmp(context->currChunk.chunkId, "RIFF", 4) == 0 ? 4 : context->currChunk.chunkSize), SEEK_SET) < 0) { + if(fseek(context->fd, context->currChunkDataPos + (memcmp(context->currChunk.chunkId, "RIFF", 4) == 0 ? 4 : context->currChunk.chunkSize), SEEK_SET) != 0) { return false; } - if(fread(&context->currChunk, sizeof(WavChunkHeader), 1, context->fd) <= 0) { + if(fread(&context->currChunk, 1, sizeof(WavChunkHeader), context->fd) != sizeof(WavChunkHeader)) { return false; } @@ -24,12 +25,12 @@ static bool wav_next_chunk(WavContext* context) { } static bool wav_read_chunk_data(WavContext* context, void* data, size_t maxSize) { - if(fseek(context->fd, context->currChunkDataPos, SEEK_SET) < 0) { + if(fseek(context->fd, context->currChunkDataPos, SEEK_SET) != 0) { return false; } size_t size = context->currChunk.chunkSize < maxSize ? context->currChunk.chunkSize : maxSize; - if(fread(data, size, 1, context->fd) <= 0) { + if(fread(data, 1, size, context->fd) != size) { return false; } @@ -37,40 +38,90 @@ static bool wav_read_chunk_data(WavContext* context, void* data, size_t maxSize) } WAV* wav_read(FILE* fd) { - if(!fd) { - printf("ERROR: Could not open WAV file: %s\n", strerror(errno)); + WAV* wav = (WAV*) calloc(1, sizeof(WAV)); + if(wav == NULL) { + printf("ERROR: Could not allocate memory for WAV data.\n"); return NULL; } - WAV* wav = (WAV*) calloc(1, sizeof(WAV)); + char error[128] = {'\0'}; + bool riff = false; + bool fmt = false; + bool data = false; WavContext context; memset(&context, 0, sizeof(context)); context.fd = fd; - u32 foundChunks = 0; - while(wav_next_chunk(&context)) { + while(strlen(error) == 0 && (!riff || !fmt || !data) && wav_next_chunk(&context)) { if(memcmp(context.currChunk.chunkId, "RIFF", 4) == 0) { - if(wav_read_chunk_data(&context, &wav->riff, sizeof(WavRiffChunk))) { - foundChunks++; + riff = true; + + char format[4]; + if(wav_read_chunk_data(&context, format, sizeof(format))) { + if(memcmp(format, "WAVE", sizeof(format)) != 0) { + strncpy(error, "ERROR: RIFF file is not of WAVE format", sizeof(error)); + } + } else { + strncpy(error, "ERROR: Failed to read RIFF chunk data", sizeof(error)); } } else if(memcmp(context.currChunk.chunkId, "fmt ", 4) == 0) { - if(wav_read_chunk_data(&context, &wav->format, sizeof(WavFormatChunk))) { - foundChunks++; + fmt = true; + + if(!wav_read_chunk_data(&context, &wav->format, sizeof(WavFormatChunk))) { + strncpy(error, "ERROR: Failed to read fmt chunk data", sizeof(error)); } } else if(memcmp(context.currChunk.chunkId, "data", 4) == 0) { + data = true; + wav->data.size = context.currChunk.chunkSize; wav->data.data = (u8*) malloc(wav->data.size); - if(wav_read_chunk_data(&context, wav->data.data, wav->data.size)) { - foundChunks++; + if(wav->data.data != NULL) { + if(!wav_read_chunk_data(&context, wav->data.data, wav->data.size)) { + strncpy(error, "ERROR: Failed to read data chunk data", sizeof(error)); + } + } else { + strncpy(error, "ERROR: Could not allocate memory for WAV samples", sizeof(error)); } } } - if(foundChunks != 3) { + if(strlen(error) == 0 && (!riff || !fmt || !data)) { + std::stringstream stream; + stream << "ERROR: Missing one or more WAV chunks: "; + + if(!riff) { + stream << "RIFF"; + } + + if(!fmt) { + if(!riff) { + stream << ", "; + } + + stream << "fmt"; + } + + if(!data) { + if(!riff || !fmt) { + stream << ", "; + } + + stream << "data"; + } + + strncpy(error, stream.str().c_str(), sizeof(error)); + } + + if(strlen(error) > 0) { wav_free(wav); - printf("ERROR: Failed to read WAV chunks: %s\n", errno != 0 ? strerror(errno) : "Not enough chunks."); + if(errno != 0) { + perror(error); + } else { + printf("%s.\n", error); + } + return NULL; } diff --git a/source/pc/wav.h b/source/pc/wav.h index a4dc866..1ebef55 100644 --- a/source/pc/wav.h +++ b/source/pc/wav.h @@ -1,8 +1,6 @@ #ifndef WAV_H #define WAV_H -#include - #include "../types.h" typedef struct { @@ -10,10 +8,6 @@ typedef struct { u32 chunkSize; } WavChunkHeader; -typedef struct { - char format[4]; -} WavRiffChunk; - typedef struct { u16 format; u16 numChannels; @@ -29,7 +23,6 @@ typedef struct { } WavDataChunk; typedef struct { - WavRiffChunk riff; WavFormatChunk format; WavDataChunk data; } WAV; diff --git a/source/types.h b/source/types.h index 50fa311..8139ea1 100644 --- a/source/types.h +++ b/source/types.h @@ -5,6 +5,8 @@ #include #include +#include + typedef uint8_t u8; typedef uint16_t u16; typedef uint32_t u32;