diff --git a/source/3ds/3ds.h b/source/3ds/3ds.h index efcad23..7dafa99 100644 --- a/source/3ds/3ds.h +++ b/source/3ds/3ds.h @@ -3,6 +3,7 @@ #include "cbmd.h" #include "cwav.h" +#include "smdh.h" #include "lz11.h" #include "util.h" diff --git a/source/3ds/cwav.cpp b/source/3ds/cwav.cpp index 368ed6c..3659a4f 100644 --- a/source/3ds/cwav.cpp +++ b/source/3ds/cwav.cpp @@ -1,11 +1,53 @@ #include "cwav.h" -#include "../wav.h" - -#include #include #include +typedef struct { + char magic[4] = {'C', 'W', 'A', 'V'}; + u16 endianess = 0xFEFF; + u16 structLength = 0x40; + u32 unknown0 = 0; + u32 fileSize; + u32 numChunks = 2; + u32 infoChunkFlags = 0x7000; + u32 infoChunkOffset; + u32 infoChunkLength; + u32 dataChunkFlags = 0x7000; + u32 dataChunkOffset; + u32 dataChunkLength; + u8 reserved[0x14] = {0}; +} Header; + +typedef struct { + char magic[4] = {'I', 'N', 'F', 'O'}; + u32 length = 0xC0; + u32 type; + u32 sampleRate; + u32 unknown1 = 0; + u32 totalSamples; + u32 unknown2 = 0; + u32 totalChannels; +} InfoHeader; + +typedef struct { + char magic[4] = {'D', 'A', 'T', 'A'}; + u32 length; +} DataHeader; + +typedef struct { + u32 flags = 0x7100; + u32 offset; +} ChannelDataPointer; + +typedef struct { + u32 flags = 0x1F00; + u32 offset; + u32 unknown3 = 0; + u32 unknown4 = 0; + u32 padding = 0; +} ChannelData; + u8* build_cwav(WAV wav, u32* size) { Header header; u32 offset = sizeof(Header); diff --git a/source/3ds/cwav.h b/source/3ds/cwav.h index 7ba31b9..ffe3741 100644 --- a/source/3ds/cwav.h +++ b/source/3ds/cwav.h @@ -4,51 +4,6 @@ #include "../types.h" #include "../wav.h" -typedef struct { - char magic[4] = {'C', 'W', 'A', 'V'}; - u16 endianess = 0xFEFF; - u16 structLength = 0x40; - u32 unknown0 = 0; - u32 fileSize; - u32 numChunks = 2; - u32 infoChunkFlags = 0x7000; - u32 infoChunkOffset; - u32 infoChunkLength; - u32 dataChunkFlags = 0x7000; - u32 dataChunkOffset; - u32 dataChunkLength; - u8 reserved[0x14] = {0}; -} Header; - -typedef struct { - char magic[4] = {'I', 'N', 'F', 'O'}; - u32 length = 0xC0; - u32 type; - u32 sampleRate; - u32 unknown1 = 0; - u32 totalSamples; - u32 unknown2 = 0; - u32 totalChannels; -} InfoHeader; - -typedef struct { - char magic[4] = {'D', 'A', 'T', 'A'}; - u32 length; -} DataHeader; - -typedef struct { - u32 flags = 0x7100; - u32 offset; -} ChannelDataPointer; - -typedef struct { - u32 flags = 0x1F00; - u32 offset; - u32 unknown3 = 0; - u32 unknown4 = 0; - u32 padding = 0; -} ChannelData; - u8* build_cwav(WAV wav, u32* size); #endif \ No newline at end of file diff --git a/source/3ds/smdh.h b/source/3ds/smdh.h new file mode 100644 index 0000000..eea9506 --- /dev/null +++ b/source/3ds/smdh.h @@ -0,0 +1,96 @@ +#ifndef __SMDH_H__ +#define __SMDH_H__ + +#include "../types.h" + +typedef enum { + JAPANESE, + ENGLISH, + FRENCH, + GERMAN, + ITALIAN, + SPANISH, + SIMPLIFIED_CHINESE, + KOREAN, + DUTCH, + PORTUGESE, + RUSSIAN, + TRADITIONAL_CHINESE +} SMDHTitleLanguage; + +typedef struct { + u16 shortDescription[0x40] = {0}; + u16 longDescription[0x80] = {0}; + u16 publisher[0x40] = {0}; +} SMDHTitle; + +typedef struct { + // TODO: values... + u8 cero = 0; + u8 esrb = 0; + u8 reserved0 = 0; + u8 usk = 0; + u8 pegiGen = 0; + u8 reserved1 = 0; + u8 pegiPrt = 0; + u8 pegiBbfc = 0; + u8 cob = 0; + u8 grb = 0; + u8 cgsrr = 0; + u8 reserved2 = 0; + u8 reserved3 = 0; + u8 reserved4 = 0; + u8 reserved5 = 0; + u8 reserved6 = 0; +} SMDHGameRatings; + +typedef struct _region_lock { + _region_lock() : japan(true), northAmerica(true), europe(true), australia(true), china(true), korea(true), taiwan(true) {} + + bool japan : 1; + bool northAmerica : 1; + bool europe : 1; + bool australia : 1; + bool china : 1; + bool korea : 1; + bool taiwan : 1; +} SMDHRegionLock; + +typedef struct _flags { + _flags() : visible(true), autoBoot(false), allow3d(true), requireEula(false), autoSaveOnExit(false), useExtendedBanner(false), ratingRequired(false), useSaveData(false), recordUsage(true), disableSaveBackups(false) {} + + bool visible : 1; + bool autoBoot : 1; + bool allow3d : 1; + bool requireEula : 1; + bool autoSaveOnExit : 1; + bool useExtendedBanner : 1; + bool ratingRequired : 1; + bool useSaveData : 1; + bool recordUsage : 1; + bool disableSaveBackups : 1; +} SMDHFlags; + +typedef struct { + SMDHGameRatings gameRatings; + SMDHRegionLock regionLock; + u8 matchMakerId[0xC] = {0}; + SMDHFlags flags; + u16 eulaVersion = 0; + u16 reserved1 = 0; + u32 optimalBannerFrame = 0; + u32 streetpassId = 0; +} SMDHSettings; + +typedef struct { + char magic[4] = {'S', 'M', 'D', 'H'}; + u16 version = 0; + u16 reserved0 = 0; + SMDHTitle titles[0x10]; + SMDHSettings settings; + u64 reserved2 = 0; + u8 smallIcon[0x480] = {0}; + u8 largeIcon[0x1200] = {0}; +} SMDH; + +#endif \ No newline at end of file diff --git a/source/3ds/util.cpp b/source/3ds/util.cpp index f2eed40..ebe4b53 100644 --- a/source/3ds/util.cpp +++ b/source/3ds/util.cpp @@ -7,7 +7,14 @@ u8 TILE_ORDER[64] = { 0, 1, 8, 9, 2, 3, 10, 11, 16, 17, 24, 25, 18, 19, 26 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 }; -u8* image_to_tiles(const char* image, u32 width, u32 height, u32* size) { +u16 rgba_to_rgb565(u8 r, u8 g, u8 b, u8 a) { + r = (u8) (1.0f * r * a / 255.0f) >> 3; + g = (u8) (1.0f * g * a / 255.0f) >> 2; + b = (u8) (1.0f * b * a / 255.0f) >> 3; + return (r << 11) | (g << 5) | b; +} + +u16* image_to_tiles(const char* image, u32 width, u32 height, u32* size) { unsigned char* img; unsigned int imgWidth, imgHeight; if(lodepng_decode32_file(&img, &imgWidth, &imgHeight, image)) { @@ -28,7 +35,7 @@ u8* image_to_tiles(const char* image, u32 width, u32 height, u32* size) { return NULL; } - u8* converted = (u8*) malloc(width * height * 2); + 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) { @@ -37,15 +44,26 @@ u8* image_to_tiles(const char* image, u32 width, u32 height, u32* size) { u32 yy = (u32) (TILE_ORDER[k] >> 3); u8* pixel = img + (((y + yy) * width + (x + xx)) * 4); - converted[n++] = ((pixel[2] >> 4) << 4) | (pixel[3] >> 4); - converted[n++] = ((pixel[0] >> 4) << 4) | (pixel[1] >> 4); + converted[n++] = rgba_to_rgb565(pixel[0], pixel[1], pixel[2], pixel[3]); } } } if(size != NULL) { - *size = width * height * 2; + *size = width * height * (u32) sizeof(u16); } return converted; +} + +void utf8_to_utf16(u16* dst, const char* src, size_t max_len) { + u8* u8dst = (u8*) dst; + size_t n = 0; + while(src[n]) { + u8dst[n * 2] = (u8) src[n]; + u8dst[n * 2 + 1] = 0; + if(n++ >= max_len) { + return; + } + } } \ No newline at end of file diff --git a/source/3ds/util.h b/source/3ds/util.h index 5d62b46..5bb5937 100644 --- a/source/3ds/util.h +++ b/source/3ds/util.h @@ -3,6 +3,8 @@ #include "../types.h" -u8* image_to_tiles(const char* image, u32 width, u32 height, u32* size); +u16 rgba_to_rgb565(u8 r, u8 g, u8 b, u8 a); +u16* image_to_tiles(const char* image, u32 width, u32 height, u32* size); +void utf8_to_utf16(u16* dst, const char* src, size_t max_len); #endif \ No newline at end of file diff --git a/source/commandline.cpp b/source/commandline.cpp index 6d2c470..6826a1c 100644 --- a/source/commandline.cpp +++ b/source/commandline.cpp @@ -39,9 +39,18 @@ void cmd_print_usage(const char* executedFrom) { void cmd_print_info(const char* command) { if(strcmp(command, "makebanner") == 0) { printf("makebanner - Creates a .bnr file.\n"); - printf(" -i/--image: PNG file to use as the banner image.\n"); - printf(" -a/--audio: WAV file to use as the banner's tune.\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(" -o/--output: File to output the created banner to.\n"); + } else if(strcmp(command, "makesmdh") == 0) { + printf("makesmdh - Creates a .smdh/.icn file.\n"); + printf(" -s/--shortdescription: Short description of the application.\n"); + printf(" -l/--longdescription: Long description of the application.\n"); + printf(" -p/--publisher: 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"); } else if(strcmp(command, "makecwav") == 0) { printf("makecwav - Creates a CWAV file from a WAV.\n"); printf(" -i/--input: WAV file to convert.\n"); @@ -56,6 +65,7 @@ void cmd_print_info(const char* command) { void cmd_print_commands() { printf("Available commands:\n"); cmd_print_info("makebanner"); + cmd_print_info("makesmdh"); cmd_print_info("makecwav"); cmd_print_info("lz11"); } diff --git a/source/main.cpp b/source/main.cpp index a55a5fc..c7948b0 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -3,6 +3,7 @@ #include "data.h" #include "types.h" #include "wav.h" +#include "3ds/smdh.h" #include #include @@ -10,7 +11,7 @@ u8* convert_to_cgfx(const char* image, u32 width, u32 height, u32* size) { u32 convertedSize = 0; - u8* converted = image_to_tiles(image, width, height, &convertedSize); + u16* converted = image_to_tiles(image, width, height, &convertedSize); if(converted == NULL) { return NULL; } @@ -37,17 +38,49 @@ u8* convert_to_cwav(const char* file, u32* size) { return cwav; } -int make_banner(const char* image, const char* audio, const char* output) { +int make_banner(const char* image, const char* audio, char* cgfxFile, char* cwavFile, const char* output) { u32 cgfxSize = 0; - u8* cgfx = convert_to_cgfx(image, 256, 128, &cgfxSize); - if(!cgfx) { - return 1; + u8* cgfx = NULL; + if(cgfxFile != NULL) { + FILE* fd = fopen(cgfxFile, "r"); + if(!fd) { + printf("ERROR: Could not open CGFX file: %s\n", strerror(errno)); + } + + 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) { + return 1; + } } u32 cwavSize = 0; - u8* cwav = convert_to_cwav(audio, &cwavSize); - if(!cwav) { - return 2; + u8* cwav = NULL; + if(cwavFile != NULL) { + FILE* fd = fopen(cwavFile, "r"); + if(!fd) { + printf("ERROR: Could not open CWAV file: %s\n", strerror(errno)); + } + + 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) { + return 2; + } } CBMD cbmd; @@ -76,6 +109,43 @@ int make_banner(const char* image, const char* audio, const char* output) { return 0; } +int make_smdh(char* shortDescription, char* longDescription, char* publisher, char* icon, char* output) { + u16* icon48 = image_to_tiles(icon, 48, 48, NULL); + if(icon48 == NULL) { + return 1; + } + + u16 icon24[24 * 24]; + for(int y = 0; y < 24; y++) { + for(int x = 0; x < 24; x++) { + icon24[y * 24 + x] = icon48[y * 48 + x]; + } + } + + SMDH smdh; + for(int i = 0; i < 0x10; i++) { + utf8_to_utf16(smdh.titles[i].shortDescription, shortDescription, 0x40); + utf8_to_utf16(smdh.titles[i].longDescription, longDescription, 0x80); + utf8_to_utf16(smdh.titles[i].publisher, publisher, 0x40); + } + + memcpy(smdh.largeIcon, icon48, 0x1200); + memcpy(smdh.smallIcon, icon24, 0x480); + free(icon48); + + FILE* fd = fopen(output, "wb"); + if(!fd) { + printf("ERROR: Could not open output file: %s\n", strerror(errno)); + return 2; + } + + fwrite(&smdh, 1, sizeof(SMDH), fd); + fclose(fd); + + printf("Created SMDH \"%s\".\n", output); + return 0; +} + int make_cwav(char* input, char* output) { u32 cwavSize = 0; u8* cwav = convert_to_cwav(input, &cwavSize); @@ -143,15 +213,29 @@ int main(int argc, char* argv[]) { char* command = argv[1]; std::map args = cmd_get_args(argc, argv); if(strcmp(command, "makebanner") == 0) { - char* banner = cmd_find_arg(args, "i", "image"); - char* audio = cmd_find_arg(args, "a", "audio"); - char* output = cmd_find_arg(args, "o", "output"); - if(!banner || !audio || !output) { + char *banner = cmd_find_arg(args, "i", "image"); + char *audio = cmd_find_arg(args, "a", "audio"); + char *cgfxFile = cmd_find_arg(args, "ci", "cgfximage"); + char *cwavFile = cmd_find_arg(args, "ca", "cwavaudio"); + char *output = cmd_find_arg(args, "o", "output"); + if(!(banner || cgfxFile) || !(audio || cwavFile) || !output) { cmd_missing_args(command); return -1; } - return make_banner(banner, audio, output); + return make_banner(banner, audio, cgfxFile, cwavFile, output); + } else if(strcmp(command, "makesmdh") == 0) { + char* shortDescription = cmd_find_arg(args, "s", "shortdescription"); + char* longDescription = cmd_find_arg(args, "l", "longdescription"); + char* publisher = cmd_find_arg(args, "p", "publisher"); + char* icon = cmd_find_arg(args, "i", "icon"); + char* output = cmd_find_arg(args, "o", "output"); + if(!shortDescription || !longDescription || !publisher || !icon || !output) { + cmd_missing_args(command); + return -1; + } + + return make_smdh(shortDescription, longDescription, publisher, icon, output); } else if(strcmp(command, "makecwav") == 0) { char* input = cmd_find_arg(args, "i", "input"); char* output = cmd_find_arg(args, "o", "output");