From 2cc5d7ac5b4cb79b9144c7fabadfcd0df3a29053 Mon Sep 17 00:00:00 2001 From: Lephenixnoir Date: Sun, 20 Mar 2022 19:45:51 +0000 Subject: [PATCH] implement fxgxa and use it in both build systems --- CMakeLists.txt | 25 ++- README.md | 32 ++-- fxgxa/dump.c | 171 ++++++++++++++++++--- fxgxa/edit.c | 143 +++++++++++++----- fxgxa/file.c | 51 +++---- fxgxa/fxg1a.h | 176 --------------------- fxgxa/fxgxa.h | 198 ++++++++++++++++++++++++ fxgxa/g1a.h | 73 ++++----- fxgxa/g3a.h | 58 +++++++ fxgxa/icon.c | 247 +++++++++++++++++------------- fxgxa/main.c | 277 ++++++++++++++++++++++------------ fxgxa/util.c | 68 ++++++--- fxsdk/assets/Makefile | 6 +- fxsdk/cmake/GenerateG1A.cmake | 16 +- fxsdk/cmake/GenerateG3A.cmake | 15 +- 15 files changed, 991 insertions(+), 565 deletions(-) delete mode 100644 fxgxa/fxg1a.h create mode 100644 fxgxa/fxgxa.h create mode 100644 fxgxa/g3a.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 6aeb531..e9c8520 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,19 +23,15 @@ set(BIN "${CMAKE_CURRENT_BINARY_DIR}") add_compile_options(-Wall -Wextra -std=c11 -Og -g -D_GNU_SOURCE) -# fxg1a -add_executable(fxg1a fxgxa/dump.c fxgxa/edit.c fxgxa/file.c fxgxa/icon.c +# fxgxa +add_executable(fxgxa fxgxa/dump.c fxgxa/edit.c fxgxa/file.c fxgxa/icon.c fxgxa/main.c fxgxa/util.c) -target_include_directories(fxg1a PUBLIC fxgxa/) -target_link_libraries(fxg1a PkgConfig::libpng) -target_compile_definitions(fxg1a PRIVATE -DFXGXA_FORMAT_G1A) +target_include_directories(fxgxa PUBLIC fxgxa/) +target_link_libraries(fxgxa PkgConfig::libpng) -# fxg3a -add_executable(fxg3a fxgxa/dump.c fxgxa/edit.c fxgxa/file.c fxgxa/icon.c - fxgxa/main.c fxgxa/util.c) -target_include_directories(fxg3a PUBLIC fxgxa/) -target_link_libraries(fxg3a PkgConfig::libpng) -target_compile_definitions(fxg3a PRIVATE -DFXGXA_FORMAT_G3A) +# fxg1a as a symlink (for compatibility= +add_custom_target(fxg1a ALL + COMMAND ${CMAKE_COMMAND} -E create_symlink "fxgxa" "fxg1a") # fxsdk add_custom_command(OUTPUT "${BIN}/fxsdk.sh" @@ -64,10 +60,9 @@ endif() install(PROGRAMS "${BIN}/fxsdk.sh" TYPE BIN RENAME fxsdk) install(DIRECTORY fxsdk/assets DESTINATION share/fxsdk) install(DIRECTORY fxsdk/cmake/ DESTINATION lib/cmake/fxsdk) -# fxg1a -install(TARGETS fxg1a) -# fxg3a -install(TARGETS fxg3a) +# fxgxa, fxg1a +install(TARGETS fxgxa) +install(FILES "${BIN}/fxg1a" TYPE BIN) # fxconv install(PROGRAMS fxconv/fxconv-main.py TYPE BIN RENAME fxconv) install(FILES fxconv/fxconv.py TYPE BIN) diff --git a/README.md b/README.md index 62b6d36..8be4fa1 100644 --- a/README.md +++ b/README.md @@ -70,29 +70,27 @@ Summary of commands (`fxsdk --help` for details): * `fxsdk build/build-fx/build-cg`: Configure and compile add-ins and libraries * `fxsdk send/send-fx/send-cg`: Install files to the calculator (WIP) -**G1A file generation** with `fxg1a` +**G1A/G3A file generation** with `fxgxa` -`fxg1a` is a versatile g1a file editor that creates, edits and dumps the header -of fx-9860G add-ins files. It is used to build a g1a file out of a binary -program. +`fxgxa` is a versatile g1a/g3a file editor that creates, edits and dumps the +header of fx-9860G add-ins files. It is used to build g1a/g3a files out of a +binary program. -It supports PNG icons, checking the validity and checksums of the header, -repairing broken headers and dumping both the application data and icon. +It supports PNG icons of any formats, checking the validity and checksums of +the header, repairing broken headers and dumping both the application data and +icons. -`fxg1a` is called automatically by the build system in your add-in, so you +`fxgxa` is called automatically by the build system in your add-in, so you don't need to worry about it, but here are the main commands: -* `fxg1a -g`: Generate g1a files -* `fxg1a -e`: Edit g1a files -* `fxg1a -d`: Dump metadata, checksum, and icon -* `fxg1a -r`: Repair control bytes and checksums for broken files -* `fxg1a -x`: Extract icon into a PNG file +* `fxgxa --g1a|--g3a`: Generate g1a/g3a files +* `fxgxa -e`: Edit g1a/g3a files +* `fxgxa -d`: Dump metadata, checksum, and icon +* `fxgxa -r`: Repair control bytes and checksums for broken files +* `fxgxa -x`: Extract icons into PNG files -**G3A file generation** with `fxg3a` - -`fxg3a` is very similar to `fxg1a`, but generates and edits g3a files instead. -These files are used for fx-CG 10/20/50 programs. The main commands are -identical. +`fxgxa` has an alias, `fxg1a`, for compatibility with fxSDK up to 2.7.0 for +which there was no g3a file editor in the fxSDK. **Asset conversion** with `fxconv` diff --git a/fxgxa/dump.c b/fxgxa/dump.c index 94e8afa..0c610f0 100644 --- a/fxgxa/dump.c +++ b/fxgxa/dump.c @@ -2,10 +2,10 @@ #include #include -#include +#include #include -/* check(): Check validity of a g1a control or fixed field +/* check_g1a(): Check validity of a g1a control or fixed field This function checks a single field of a g1a header (depending on the value of @test, from 0 up) and returns: @@ -21,11 +21,11 @@ @g1a G1A file being manipulated @size File size @status Array row, at least 81 bytes free */ -static int check(int test, struct g1a const *g1a, size_t size, char *status) +#define m(msg, ...) sprintf(status, msg, ##__VA_ARGS__) +static int check_g1a(int test, struct g1a const *g1a, size_t size,char *status) { - #define m(msg, ...) sprintf(status, msg, ##__VA_ARGS__) - struct header const *h = &g1a->header; + struct g1a_header const *h = &g1a->header; uint8_t const *raw = (void *)h; uint16_t sum; @@ -43,7 +43,7 @@ static int check(int test, struct g1a const *g1a, size_t size, char *status) case 2: m("Sequence 1 0x0010001000 0x%02x%02x%02x%02x%02x", h->seq1[0], h->seq1[1], h->seq1[2], h->seq1[3], h->seq1[4]); - return strncmp((const char *)h->seq1, "\x00\x01\x00\x01\x00", + return memcmp((const char *)h->seq1, "\x00\x10\x00\x10\x00", 5) ? 1:0; case 3: ctrl = raw[0x13] + 0x41; @@ -61,7 +61,7 @@ static int check(int test, struct g1a const *g1a, size_t size, char *status) m("Control 2 0x%02x 0x%02x", ctrl, h->control2); return (h->control2 != ctrl) ? 2:0; case 7: - sum = checksum(g1a, size); + sum = checksum_g1a(g1a, size); m("Checksum 0x%02x 0x%02x", sum, be16toh(h->checksum)); return (be16toh(h->checksum) != sum) ? 2:0; @@ -74,10 +74,7 @@ static int check(int test, struct g1a const *g1a, size_t size, char *status) } } -/* unknown(): Print an unknown field - @data Address of field - @offset Offset of field in header - @size Number of consecutive unknown bytes */ +/* unknown(): Dump the contents of an unknown field */ static void unknown(uint8_t const *data, size_t offset, size_t size) { printf(" 0x%03zx %-4zd 0x", offset, size); @@ -85,32 +82,28 @@ static void unknown(uint8_t const *data, size_t offset, size_t size) printf("\n"); } -/* field(): Print a text field with limited size - @field Address of text field - @size Maximum number of bytes to print */ +/* field(): Print a potentially not NUL-terminated text field */ static void field(const char *field, size_t size) { for(size_t i = 0; i < size && field[i]; i++) putchar(field[i]); printf("\n"); } -/* dump(): Print the detailed header fields of a g1a file */ -void dump(struct g1a const *g1a, size_t size) +void dump_g1a(struct g1a const *g1a, size_t size) { - struct header const *header = &g1a->header; + struct g1a_header const *header = &g1a->header; uint8_t const *raw = (void *)header; /* Checks for g1a files */ char status[81]; - int ret = 0; - int passed = 0; + int ret=0, passed=0; printf("G1A signature checks:\n\n"); printf(" Sta. Field Expected Value\n"); for(int test = 0; ret >= 0; test++) { - ret = check(test, g1a, size, status); + ret = check_g1a(test, g1a, size, status); passed += !ret; if(ret < 0) break; @@ -142,5 +135,141 @@ void dump(struct g1a const *g1a, size_t size) field(header->date, 14); printf("\nProgram icon:\n\n"); - icon_print(header->icon); + icon_print_1(header->icon, 30, 17); +} + +/* See check_g3a() for a description */ +static int check_g3a(int test, struct g3a const *g3a, size_t size,char *status) +{ + struct g3a_header const *h = &g3a->header; + uint8_t const *raw = (void *)h; + + uint16_t sum; + uint32_t sum2; + uint8_t ctrl; + + switch(test) + { + case 0: + m("Signature \"USBPower\" \"########\""); + strncpy(status + 28, h->magic, 8); + return strncmp(h->magic, "USBPower", 8) ? 2:0; + case 1: + m("MCS Type 0x2c 0x%02x", h->mcs_type); + return (h->mcs_type != 0x2c) ? 2:0; + case 2: + m("Sequence 1 0x0010001000 0x%02x%02x%02x%02x%02x", + h->seq1[0], h->seq1[1], h->seq1[2], h->seq1[3], h->seq1[4]); + return memcmp((const char *)h->seq1, "\x00\x01\x00\x01\x00", + 5) ? 1:0; + case 3: + ctrl = raw[0x13] + 0x41; + m("Control 1 0x%02x 0x%02x", ctrl, h->control1); + return (h->control1 != ctrl) ? 2:0; + case 4: + m("Sequence 2 0x01 0x%02x", h->seq2); + return (h->seq2 != 0x01) ? 1:0; + case 5: + m("File size 1 %-8zu %u", size, + be32toh(h->filesize_be1)); + return (be32toh(h->filesize_be1) != size) ? 2:0; + case 6: + ctrl = raw[0x13] + 0xb8; + m("Control 2 0x%02x 0x%02x", ctrl, h->control2); + return (h->control2 != ctrl) ? 2:0; + case 7: + sum = checksum_g3a(g3a, size); + m("Checksum 0x%02x 0x%02x", sum, + be16toh(h->checksum)); + return (be16toh(h->checksum) != sum) ? 2:0; + case 8: + m("File size 2 %-8zu %u", size - 0x7004, + be32toh(h->filesize_be2)); + return (be32toh(h->filesize_be2) != size - 0x7004) ? 2:0; + case 9: + sum2 = checksum_g3a_2(g3a, size); + m("Checksum 2 0x%08x 0x%08x", sum2, + be32toh(h->checksum_2)); + return (be32toh(h->checksum_2) != sum2) ? 2:0; + case 10: + m("File size 1 %-8zu %u", size, + be32toh(h->filesize_be3)); + return (be32toh(h->filesize_be3) != size) ? 2:0; + case 11: + sum2 = checksum_g3a_2(g3a, size); + uint32_t footer = be32toh(*(uint32_t *)((void *)g3a+size-4)); + m("Footer 0x%08x 0x%08x", sum2, footer); + return (footer != sum2) ? 2:0; + default: + return -1; + } +} + +void dump_g3a(struct g3a const *g3a, size_t size) +{ + struct g3a_header const *header = &g3a->header; + uint8_t const *raw = (void *)header; + + /* Checks for g3a files */ + char status[81]; + int ret=0, passed=0; + + printf("G3A signature checks:\n\n"); + printf(" Sta. Field Expected Value\n"); + + for(int test = 0; ret >= 0; test++) + { + ret = check_g3a(test, g3a, size, status); + passed += !ret; + if(ret < 0) break; + + printf(" %s %s\n", ret ? "FAIL" : "OK ", status); + } + + printf("\nFields with unknown meanings:\n\n"); + printf(" Offset Size Value\n"); + + unknown(raw, 0x015, 1); + unknown(raw, 0x018, 6); + unknown(raw, 0x026, 8); + unknown(raw, 0x032, 14); + unknown(raw, 0x050, 12); + unknown(raw, 0x12c, 4); + unknown(raw, 0x13a, 2); + unknown(raw, 0x14a, 38); + + printf(" 0x590 2348 "); + bool is_zeros = true; + for(int i = 0; i < 2348; i++) + if(raw[0x590+i] != 0) is_zeros =1; + printf(is_zeros ? "\n" : "\n"); + + printf("\nApplication metadata:\n\n"); + + printf(" Program name: "); + field(header->name, 16); + printf(" Internal name: "); + field(header->internal, 11); + printf(" Version: "); + field(header->version, 10); + printf(" Build date: "); + field(header->date, 14); + printf(" Filename: "); + field(header->filename, 324); + + printf("\nUnselected program icon:\n\n"); + icon_print_16(header->icon_uns, 92, 64); + + printf("\nSelected program icon:\n\n"); + icon_print_16(header->icon_sel, 92, 64); + + printf("\n"); +} + +void dump(void *gxa, size_t size) +{ + if(is_g1a(gxa)) + return dump_g1a(gxa, size); + if(is_g3a(gxa)) + return dump_g3a(gxa, size); } diff --git a/fxgxa/edit.c b/fxgxa/edit.c index bb14496..c388374 100644 --- a/fxgxa/edit.c +++ b/fxgxa/edit.c @@ -1,72 +1,139 @@ -#include +#include #include #include #include -/* sign(): Sign header by filling fixed fields and checksums */ -void sign(struct g1a *g1a, size_t size) +void sign(void *gxa, size_t size) { - struct header *header = &g1a->header; + if(is_g1a(gxa)) { + struct g1a_header *header = gxa; - /* Fixed elements */ + /* Fixed elements */ + memcpy(header->magic, "USBPower", 8); + header->mcs_type = 0xf3; + memcpy(header->seq1, "\x00\x10\x00\x10\x00", 5); + header->seq2 = 0x01; + header->filesize_be1 = htobe32(size); + header->filesize_be2 = htobe32(size); - memcpy(header->magic, "USBPower", 8); - header->mcs_type = 0xf3; - memcpy(header->seq1, "\x00\x10\x00\x10\x00", 5); - header->seq2 = 0x01; + /* Control bytes and checksums */ + header->control1 = size + 0x41; + header->control2 = size + 0xb8; + header->checksum = htobe16(checksum_g1a(gxa, size)); + } + else if(is_g3a(gxa)) { + struct g3a_header *header = gxa; - header->filesize_be1 = htobe32(size); - header->filesize_be2 = htobe32(size); + /* Fixed elements */ + memcpy(header->magic, "USBPower", 8); + header->mcs_type = 0x2c; + memcpy(header->seq1, "\x00\x01\x00\x01\x00", 5); + header->seq2 = 0x01; + memcpy(header->seq3, "\x01\x01", 2); + header->filesize_be1 = htobe32(size); + header->filesize_be2 = htobe32(size - 0x7000 - 4); + header->filesize_be3 = htobe32(size); - /* Control bytes and checksums */ + /* Control bytes and checksums */ + header->control1 = size + 0x41; + header->control2 = size + 0xb8; + header->checksum = htobe16(checksum_g3a(gxa, size)); + header->checksum_2 = htobe32(checksum_g3a_2(gxa, size)); - header->control1 = size + 0x41; - header->control2 = size + 0xb8; - header->checksum = htobe16(checksum(g1a, size)); + /* Last 4 bytes */ + uint32_t *footer = gxa + size - 4; + *footer = header->checksum_2; + } } -/* edit_name(): Set application name */ -void edit_name(struct g1a *g1a, const char *name) +void edit_name(void *gxa, const char *name) { - memset(g1a->header.name, 0, 8); - if(!name) return; + if(is_g1a(gxa)) { + memset(G1A(gxa)->header.name, 0, 8); + if(!name) return; - for(int i = 0; name[i] && i < 8; i++) - g1a->header.name[i] = name[i]; + for(int i = 0; name[i] && i < 8; i++) + G1A(gxa)->header.name[i] = name[i]; + } + else if(is_g3a(gxa)) { + memset(G3A(gxa)->header.name, 0, 16); + if(!name) return; + + for(int i = 0; name[i] && i < 16; i++) + G3A(gxa)->header.name[i] = name[i]; + + for(int j = 0; j < 8; j++) { + memset(G3A(gxa)->header.label[j], 0, 24); + for(int i = 0; name[i] && i < 24; i++) + G3A(gxa)->header.label[j][i] = name[i]; + } + } } -/* edit_internal(): Set internal name */ -void edit_internal(struct g1a *g1a, const char *internal) +void edit_internal(void *gxa, const char *internal) { - memset(g1a->header.internal, 0, 8); + char *dst = NULL; + int size = 0; + + if(is_g1a(gxa)) { + dst = G1A(gxa)->header.internal; + size = 8; + } + else if(is_g3a(gxa)) { + dst = G3A(gxa)->header.internal; + size = 11; + } + + memset(dst, 0, size); if(!internal) return; - for(int i = 0; internal[i] && i < 8; i++) - g1a->header.internal[i] = internal[i]; + for(int i = 0; internal[i] && i < size; i++) + dst[i] = internal[i]; } -/* edit_version(): Set version string */ -void edit_version(struct g1a *g1a, const char *version) +void edit_version(void *gxa, const char *version) { - memset(g1a->header.version, 0, 10); + char *dst = NULL; + int size = 10; + + if(is_g1a(gxa)) + dst = G1A(gxa)->header.version; + else if(is_g3a(gxa)) + dst = G3A(gxa)->header.version; + + memset(dst, 0, size); if(!version) return; - for(int i = 0; version[i] && i < 10; i++) - g1a->header.version[i] = version[i]; + for(int i = 0; version[i] && i < size; i++) + dst[i] = version[i]; } -/* edit_date(): Set build date */ -void edit_date(struct g1a *g1a, const char *date) +void edit_date(void *gxa, const char *date) { - memset(g1a->header.date, 0, 14); + char *dst = NULL; + int size = 14; + + if(is_g1a(gxa)) + dst = G1A(gxa)->header.date; + else if(is_g3a(gxa)) + dst = G3A(gxa)->header.date; + + memset(dst, 0, size); if(!date) return; - for(int i = 0; date[i] && i < 14; i++) - g1a->header.date[i] = date[i]; + for(int i = 0; date[i] && i < size; i++) + dst[i] = date[i]; } -/* edit_icon(): Set icon from monochrome bitmap */ -void edit_icon(struct g1a *g1a, uint8_t const *mono) +void edit_g1a_icon(struct g1a *g1a, uint8_t const *mono) { memcpy(g1a->header.icon, mono, 68); } + +void edit_g3a_icon(struct g3a *g3a, uint16_t const *icon, bool selected) +{ + if(selected) + memcpy(g3a->header.icon_sel, icon, 92*64*2); + else + memcpy(g3a->header.icon_uns, icon, 92*64*2); +} diff --git a/fxgxa/file.c b/fxgxa/file.c index da59116..35ffe0b 100644 --- a/fxgxa/file.c +++ b/fxgxa/file.c @@ -7,13 +7,13 @@ #include #include -#include +#include /* invert_header(): Bit-invert a standard header Part of the header is stored inverted in files for obfuscation purposes. */ -static void invert_header(struct g1a *g1a) +static void invert_header(void *gxa) { - uint8_t *data = (void *)&g1a->header; + uint8_t *data = gxa; for(size_t i = 0; i < 0x20; i++) data[i] = ~data[i]; } @@ -26,7 +26,7 @@ static void invert_header(struct g1a *g1a) /* load(): Fully load a file into memory Allocates a buffer with @prepend leading bytes initialized to zero. */ -static void *load(const char *filename, size_t *size, size_t prepend) +static void *load(const char *filename, size_t *size, int header, int footer) { int fd; struct stat statbuf; @@ -40,13 +40,13 @@ static void *load(const char *filename, size_t *size, size_t prepend) if(x > 0) fail("cannot stat %s", filename); filesize = statbuf.st_size; - data = malloc(prepend + filesize); + data = malloc(header + filesize + footer); if(!data) fail("cannot load %s", filename); size_t remaining = filesize; while(remaining > 0) { - size_t offset = prepend + filesize - remaining; + size_t offset = header + filesize - remaining; ssize_t y = read(fd, data + offset, remaining); if(y < 0) fail("cannot read from %s", filename); @@ -54,58 +54,55 @@ static void *load(const char *filename, size_t *size, size_t prepend) } close(fd); - memset(data, 0, prepend); + memset(data, 0, header); + memset(data + header + filesize, 0, footer); - if(size) *size = prepend + filesize; + if(size) *size = header + filesize + footer; return data; } -/* load_g1a(): Load a g1a file into memory */ -struct g1a *load_g1a(const char *filename, size_t *size) +void *load_gxa(const char *filename, size_t *size) { - struct g1a *ret = load(filename, size, 0); + void *ret = load(filename, size, 0, 0); if(ret) invert_header(ret); return ret; } -/* load_binary(): Load a binary file into memory */ -struct g1a *load_binary(const char *filename, size_t *size) +void *load_binary(const char *filename, size_t *size, int header, int footer) { - struct g1a *ret = load(filename, size, 0x200); - if(ret) memset(ret, 0xff, 0x20); + void *ret = load(filename, size, header, footer); + if(ret) invert_header(ret); return ret; } #undef fail #define fail(msg, ...) { \ fprintf(stderr, "error: " msg ": %m\n", ##__VA_ARGS__); \ - close(fd); \ - invert_header(g1a); \ - return 1; \ + rc = 1; \ + goto end; \ } -/* save_g1a(): Save a g1a file to disk */ -int save_g1a(const char *filename, struct g1a *g1a, size_t size) +int save_gxa(const char *filename, void *gxa, size_t size) { /* Invert header before saving */ - invert_header(g1a); + invert_header(gxa); + int rc = 0; int fd = creat(filename, 0644); if(fd < 0) fail("cannot open %s", filename); - void const *raw = g1a; ssize_t status; - size_t written = 0; while(written < size) { - status = write(fd, raw + written, size - written); + status = write(fd, gxa + written, size - written); if(status < 0) fail("cannot write to %s", filename); written += status; } - close(fd); +end: /* Before returning, re-invert header for further use */ - invert_header(g1a); - return 0; + if(fd >= 0) close(fd); + invert_header(gxa); + return rc; } diff --git a/fxgxa/fxg1a.h b/fxgxa/fxg1a.h deleted file mode 100644 index 4e29d62..0000000 --- a/fxgxa/fxg1a.h +++ /dev/null @@ -1,176 +0,0 @@ -//--- -// fxg1a:fxg1a - Main interfaces -//--- - -#ifndef FX_FXG1A -#define FX_FXG1A - -#include -#include -#include -#include - -/* -** Header dumping (dump.c) -*/ - -/* dump(): Print the detailed header fields of a g1a file - This function takes as argument the full file loaded into memory and the - size of the file. It does various printing to stdout as main job. - - @g1a Full file data - @size Size of g1a file */ -void dump(struct g1a const *g1a, size_t size); - - -/* -** Header manipulation (edit.c) -*/ - -/* sign(): Sign header by filling fixed fields and checksums - This function fills the fixed fields and various checksums of a g1a file. To - do this it accesses some of the binary data. To set the user-customizable - field, use the edit_*() functions. (The value of the customizable fields - does not influence the checksums so it's okay to not call this function - afterwards.) - - @g1a Header to sign - @size Size of raw file data */ -void sign(struct g1a *g1a, size_t size); - -/* edit_*(): Set various fields of a g1a header */ - -void edit_name (struct g1a *g1a, const char *name); -void edit_internal (struct g1a *g1a, const char *internal); -void edit_version (struct g1a *g1a, const char *version); -void edit_date (struct g1a *g1a, const char *date); - -/* edit_icon(): Set monochrome icon of a g1a header - The icon parameter must be loaded in 1-bit bitmap format. */ -void edit_icon(struct g1a *header, uint8_t const *mono); - - -/* -** Utility functions (util.c) -*/ - -/* checksum(): Sum of 8 big-endian shorts at 0x300 - Computes the third checksum by summing bytes from the data part of the file. - - @g1a Add-in file whose checksum is requested - @size Size of file */ -uint16_t checksum(struct g1a const *g1a, size_t size); - -/* default_output(): Calculate default output file name - This function computes a default file name by replacing the extension of - @name (if it exists) or adding one. The extension is specified as a suffix, - usually in the form ".ext". - - The resulting string might be as long as the length of @name plus that of - @suffix (plus one NUL byte); the provided buffer must point to a suitably- - large allocated space. - - @name Input file name - @suffix Suffix to add or replace @name's extension with - @output Output file name */ -void default_output(const char *name, const char *suffix, char *output); - -/* default_internal(): Calculate default internal name - This function determines a default internal name, which is '@' followed by - at most 7 uppercase letters taken from the application name. - - @name Application name - @output Internal name string (9 bytes) */ -void default_internal(const char *name, char *output); - - -/* -** File manipulation (file.c) -*/ - -/* load_g1a(): Load a g1a file into memory - This function loads @filename into a dynamically-allocated buffer and - returns the address of that buffer; it must be free()'d after use. When - loading the file, if @size is not NULL, it receives the size of the file. - On error, load() prints a message an stderr and returns NULL. The header - is inverted before this function returns. - - @filename File to load - @size If non-NULL, receives the file size - Returns a pointer to a buffer with loaded data, or NULL on error. */ -struct g1a *load_g1a(const char *filename, size_t *size); - -/* load_binary(): Load a binary file into memory - This function operates like load_g1a() but reserves space for an empty - header. The header is initialized with all zeros. - - @filename File to load - @size If non-NULL, receives the file size - Returns a pointer to a buffer with loaded data, or NULL on error. */ -struct g1a *load_binary(const char *filename, size_t *size); - -/* save_g1a(): Save a g1a file to disk - This functions creates @filename, then writes a g1a header and a chunk of - raw data to it. Since it temporarily inverts the header to comply with - Casio's obfuscated format, it needs write access to @g1a. Returns non-zero - on error. - - @filename File to write (it will be overridden if it exists) - @g1a G1A data to write - @size Size of data - Returns zero on success and a nonzero error code otherwise. */ -int save_g1a(const char *filename, struct g1a *g1a, size_t size); - - -/* -** Icon management (icon.c) -*/ - -/* icon_print(): Show a monochrome 30*17 icon on stdout - The buffer should point to a 68-byte array. */ -void icon_print(uint8_t const *icon); - -/* icon_load(): Load a monochrome PNG image into an array - This function loads a PNG image into a 1-bit buffer; each row is represented - by a fixed number of bytes, each byte being 8 pixels. Rows are loaded from - top to bottom, and from left to right. - - If the image is not a PNG image or a reading error occurs, this functions - prints an error message on stderr and returns NULL. - - @filename PNG file to load - @width If non-NULL, receives image width - @height If non-NULL, receives image height - Returns a pointer to a free()able buffer with loaded data, NULL on error. */ -uint8_t *icon_load(const char *filename, size_t *width, size_t *height); - -/* icon_save(): Save an 8-bit array to a PNG image - Assumes 8-bit GRAY format. - - @filename Target filename - @input An 8-bit GRAY image - @width Width of input, should be equal to stride - @height Height of input - Returns non-zero on error. */ -int icon_save(const char *filename, uint8_t *input, size_t width, - size_t height); - -/* icon_conv_8to1(): Convert an 8-bit icon to 1-bit - The returned 1-bit icon is always of size 30*17, if the input size does not - match it is adjusted. - - @input 8-bi data - @width Width of input image - @height Height of input image - Returns a free()able buffer with a 1-bit icon on success, NULL on error. */ -uint8_t *icon_conv_8to1(uint8_t const *input, size_t width, size_t height); - -/* icon_conv_1to8(): Convert an 1-bit icon to 8-bit - Input 1-bit is assumed to be 30*17 in size, this function returns an 8-bit - buffer with the same dimensions. - - @mono Input monochrome icon (from a g1a header, for instance) - Returns a free()able buffer, or NULL on error. */ -uint8_t *icon_conv_1to8(uint8_t const *mono); - -#endif /* FX_FXG1A */ diff --git a/fxgxa/fxgxa.h b/fxgxa/fxgxa.h new file mode 100644 index 0000000..5a661b0 --- /dev/null +++ b/fxgxa/fxgxa.h @@ -0,0 +1,198 @@ +//--- +// fxgxa:fxgxa - Main interfaces +//--- + +#ifndef FX_FXGXA +#define FX_FXGXA + +#include +#include +#include +#include +#include + +/* In this file, many functions accept either a [struct g1a] or a [struct g3a] + as their argument. In this case, the argument is noted [void *gxa]. */ + + +/* +** Header dumping (dump.c) +*/ + +/* dump(): Print the detailed header fields of a file + This function takes as argument the full file loaded into memory and the + size of the file. It does various printing to stdout as main job. + + @gxa Full file data + @size Size of the file */ +void dump(void *gxa, size_t size); + + +/* +** Header manipulation (edit.c) +*/ + +/* sign(): Sign header by filling fixed fields and checksums + This function fills the fixed fields and various checksums of a g1a file. To + do this it accesses some of the binary data. To set the user-customizable + field, use the edit_*() functions. (The value of the customizable fields + does not influence the checksums so it's okay to not call this function + afterwards.) + + @g1a Header to sign + @size Size of raw file data */ +void sign(void *gxa, size_t size); + +/* edit_*(): Set various fields */ + +void edit_name (void *gxa, const char *name); +void edit_internal (void *gxa, const char *internal); +void edit_version (void *gxa, const char *version); +void edit_date (void *gxa, const char *date); + +/* edit_g1a_icon(): Set monochrome icon of a g1a header + The icon parameter must be loaded in 1-bit bitmap format. */ +void edit_g1a_icon(struct g1a *header, uint8_t const *mono); + +/* edit_g3a_icon(): Set one of the color icons of a g3a header + The icon must be loaded in RGB565 16-bit format. */ +void edit_g3a_icon(struct g3a *header, uint16_t const *icon, bool selected); + + +/* +** Utility functions (util.c) +*/ + +/* is_g1a(), is_g3a(): Check file type */ +bool is_g1a(void *gxa); +bool is_g3a(void *gxa); + +#define G1A(gxa) ((struct g1a *)(gxa)) +#define G3A(gxa) ((struct g3a *)(gxa)) + +/* word_sum(): Sum of big-endian words at some offset in a file + This is used for the third checksum. + + @gxa File whose checksum is requested + @offset File offset to start counting + @words Number of words to read + @size Size of file (in case the region overflows) */ +uint16_t word_sum(void const *gxa, size_t offset, int words, size_t size); + +/* checksum_g1a(): Word sum of 8 big-endian shorts at 0x300 */ +uint16_t checksum_g1a(struct g1a const *g1a, size_t size); + +/* checksum_g3a(): Word sum of 8 big-endian shorts at 0x7100 */ +uint16_t checksum_g3a(struct g3a const *g3a, size_t size); + +/* checksum_g3a_2(): Sum of ranges 0...0x20 + 0x24...EOF-4 (in-out) */ +uint32_t checksum_g3a_2(struct g3a const *g3a, size_t size); + +/* default_output(): Calculate default output file name + This function computes a default file name by replacing the extension of + @name (if it exists) or adding one. The extension is specified as a suffix, + usually in the form ".ext". + + The resulting string might be as long as the length of @name plus that of + @suffix (plus one NUL byte); the provided buffer must point to a suitably- + large allocated space. + + @name Input file name + @suffix Suffix to add or replace @name's extension with + @output Output file name */ +void default_output(const char *name, const char *suffix, char *output); + +/* default_internal(): Calculate default internal name + This function determines a default internal name, which is '@' followed by + at most size-1 uppercase letters taken from the application name. The buffer + must have [size+1] bytes reserved. + + @name Application name + @output Internal name string (9 bytes) */ +void default_internal(const char *name, char *output, size_t size); + + +/* +** File manipulation (file.c) +*/ + +/* load_gxa(): Load a g1a/g3a file into memory + This function loads @filename into a dynamically-allocated buffer and + returns the address of that buffer; it must be free()'d after use. When + loading the file, if @size is not NULL, it receives the size of the file. + On error, load() prints a message an stderr and returns NULL. The header + is inverted before this function returns. + + @filename File to load + @size If non-NULL, receives the file size + Returns a pointer to a buffer with loaded data, or NULL on error. */ +void *load_gxa(const char *filename, size_t *size); + +/* load_binary(): Load a binary file into memory + This function operates like load_gxa() but reserves space for an empty + header. The header is initialized with all zeros. + + @filename File to load + @size If non-NULL, receives the file size (with header/footer) + @header_size Extra room to add as header + @footer_size Extra room to add as footer + Returns a pointer to a buffer with loaded data, or NULL on error. */ +void *load_binary(const char *filename, size_t *size, int header_size, + int footer_size); + +/* save_gxa(): Save a g1a/g3a file to disk + This functions creates @filename, then writes a header and a chunk of raw + data to it. Since it temporarily inverts the header to comply with Casio's + obfuscated format, it needs write access to @g1a. Returns non-zero on error. + + @filename File to write (it will be overridden if it exists) + @gxa G1A/G3A data to write + @size Size of data + Returns zero on success and a nonzero error code otherwise. */ +int save_gxa(const char *filename, void *gxa, size_t size); + + +/* +** Icon management (icon.c) +*/ + +/* icon_load(): Load a PNG image into a RGB888 array + This function loads a PNG image into an RGB888 buffer. If the image is not a + PNG image or a reading error occurs, this functions prints an error message + on stderr and returns NULL. + + @filename PNG file to load + @width If non-NULL, receives image width + @height If non-NULL, receives image height + Returns a pointer to a free()able buffer with loaded data, NULL on error. */ +uint8_t *icon_load(const char *filename, int *width, int *height); + +/* icon_save(): Save an RGB888 array to a PNG image. + @filename Target filename + @input A 24-bit RGB888 array + @width Width of input (should have no gaps) + @height Height of input + Returns non-zero on error. */ +int icon_save(const char *filename, uint8_t *input, int width, int height); + +/* icon_conv_24to1(): Convert RGB888 to 1-bit monochrome array + Rows are byte-padded (ie. width is rounded up to a multiple of 8). Returns + newly-allocated memory, NULL on error. */ +uint8_t *icon_conv_24to1(uint8_t const *rgb24, int width, int height); + +/* icon_conv_1to24(): Convert a 1-bit monochrome array to RGB888 */ +uint8_t *icon_conv_1to24(uint8_t const *mono, int width, int height); + +/* icon_conv_24to16(): Convert RGB888 to big-endian RGB565 */ +uint16_t *icon_conv_24to16(uint8_t const *rgb24, int width, int height); + +/* icon_conv_16to24(): Convert big-endian RGB565 to RGB888 */ +uint8_t *icon_conv_16to24(uint16_t const *rgb16be, int width, int height); + +/* icon_print_1(): Show a 1-bit image on stdout (ASCII art) */ +void icon_print_1(uint8_t const *mono, int width, int height); + +/* icon_print_16(): Show a 16-bit image on stdout (RGB ANSI escape codes) */ +void icon_print_16(uint16_t const *rgb16be, int width, int height); + +#endif /* FX_FXGXA */ diff --git a/fxgxa/g1a.h b/fxgxa/g1a.h index 862c74b..0a59516 100644 --- a/fxgxa/g1a.h +++ b/fxgxa/g1a.h @@ -1,5 +1,5 @@ //--- -// fxg1a:g1a - Add-in header for Casio's G1A format +// fxgxa:g1a - Add-in header for Casio's G1A format //--- #ifndef FX_G1A @@ -8,52 +8,53 @@ #include /* TODO: eStrips are not supported yet */ -struct estrip +struct g1a_estrip { - uint8_t data[80]; + uint8_t data[80]; }; /* G1A file header with 0x200 bytes. When output to a file the standard part (first 0x20 bytes) of this header is bit-inverted, but the non-inverted version makes a lot more sens so we'll be using it. */ -struct header -{ /* Offset Size Value */ - char magic[8]; /* 0x000 8 "USBPower" */ - uint8_t mcs_type; /* 0x008 1 0xf3 (AddIn) */ - uint8_t seq1[5]; /* 0x009 5 0x0010001000 */ - uint8_t control1; /* 0x00e 1 *0x13 + 0x41 */ - uint8_t seq2; /* 0x00f 1 0x01 */ - uint32_t filesize_be1; /* 0x010 4 File size, big endian */ - uint8_t control2; /* 0x014 1 *0x13 + 0xb8 */ - uint8_t _1; /* 0x015 1 ??? */ - uint16_t checksum; /* 0x016 2 BE sum of 8 shorts at 0x300 */ - uint8_t _2[6]; /* 0x018 6 ??? */ - uint16_t mcs_objects; /* 0x01e 2 MCS-only, unused */ - char internal[8]; /* 0x020 8 Internal app name with '@' */ - uint8_t _3[3]; /* 0x028 3 ??? */ - uint8_t estrips; /* 0x02b 1 Number of estrips (0..4) */ - uint8_t _4[4]; /* 0x02c 4 ??? */ - char version[10]; /* 0x030 10 Version "MM.mm.pppp" */ - uint8_t _5[2]; /* 0x03a 2 ??? */ - char date[14]; /* 0x03c 14 Build date "yyyy.MMdd.hhmm" */ - uint8_t _6[2]; /* 0x04a 2 ??? */ - uint8_t icon[68]; /* 0x04c 68 30*17 monochrome icon */ - struct estrip estrip1; /* 0x090 80 eStrip 1 */ - struct estrip estrip2; /* 0x0e0 80 eStrip 2 */ - struct estrip estrip3; /* 0x130 80 eStrip 3 */ - struct estrip estrip4; /* 0x180 80 eStrip 4 */ - uint8_t _7[4]; /* 0x1d0 4 ??? */ - char name[8]; /* 0x1d4 8 Add-in name */ - uint8_t _8[20]; /* 0x1dc 20 ??? */ - uint32_t filesize_be2; /* 0x1f0 4 File size, big endian */ - uint8_t _9[12]; /* 0x1f4 12 ??? */ +struct g1a_header +{ /* Offset Size Value */ + char magic[8]; /* 0x000 8 "USBPower" */ + uint8_t mcs_type; /* 0x008 1 0xf3 (AddIn) */ + uint8_t seq1[5]; /* 0x009 5 0x0010001000 */ + uint8_t control1; /* 0x00e 1 *0x13 + 0x41 */ + uint8_t seq2; /* 0x00f 1 0x01 */ + uint32_t filesize_be1; /* 0x010 4 File size, big endian */ + uint8_t control2; /* 0x014 1 *0x13 + 0xb8 */ + uint8_t _1; /* 0x015 1 ??? */ + uint16_t checksum; /* 0x016 2 BE sum of 8 shorts at 0x300 */ + uint8_t _2[6]; /* 0x018 6 ??? */ + uint16_t mcs_objects; /* 0x01e 2 MCS-only, unused */ + + char internal[8]; /* 0x020 8 Internal app name with '@' */ + uint8_t _3[3]; /* 0x028 3 ??? */ + uint8_t estrips; /* 0x02b 1 Number of estrips (0..4) */ + uint8_t _4[4]; /* 0x02c 4 ??? */ + char version[10]; /* 0x030 10 Version "MM.mm.pppp" */ + uint8_t _5[2]; /* 0x03a 2 ??? */ + char date[14]; /* 0x03c 14 Build date "yyyy.MMdd.hhmm" */ + uint8_t _6[2]; /* 0x04a 2 ??? */ + uint8_t icon[68]; /* 0x04c 68 30*17 monochrome icon */ + struct g1a_estrip estrip1; /* 0x090 80 eStrip 1 */ + struct g1a_estrip estrip2; /* 0x0e0 80 eStrip 2 */ + struct g1a_estrip estrip3; /* 0x130 80 eStrip 3 */ + struct g1a_estrip estrip4; /* 0x180 80 eStrip 4 */ + uint8_t _7[4]; /* 0x1d0 4 ??? */ + char name[8]; /* 0x1d4 8 Add-in name */ + uint8_t _8[20]; /* 0x1dc 20 ??? */ + uint32_t filesize_be2; /* 0x1f0 4 File size, big endian */ + uint8_t _9[12]; /* 0x1f4 12 ??? */ }; /* A full g1a file, suitable for use with pointers */ struct g1a { - struct header header; - uint8_t code[]; + struct g1a_header header; + uint8_t code[]; }; #endif /* FX_G1A */ diff --git a/fxgxa/g3a.h b/fxgxa/g3a.h new file mode 100644 index 0000000..058f948 --- /dev/null +++ b/fxgxa/g3a.h @@ -0,0 +1,58 @@ +//--- +// fxgxa:g3a - Add-in header for Casio's G3A format +//--- + +#ifndef FX_G3A +#define FX_G3A + +#include + +/* G3A file header with 0x7000 bytes. When output to a file the standard part + (first 0x20 bytes) of this header is bit-inverted. */ +struct g3a_header +{ /* Offset|Size|Value */ + char magic[8]; /* 0x000 8 "USBPower" */ + uint8_t mcs_type; /* 0x008 1 0x2c (AddIn) */ + uint8_t seq1[5]; /* 0x009 5 0x0001000100 */ + uint8_t control1; /* 0x00e 1 *0x13 + 0x41 */ + uint8_t seq2; /* 0x00f 1 0x01 */ + uint32_t filesize_be1; /* 0x010 4 File size, big endian */ + uint8_t control2; /* 0x014 1 *0x13 + 0xb8 */ + uint8_t _1; /* 0x015 1 ??? */ + uint16_t checksum; /* 0x016 2 BE sum of 8 shorts at 7100 */ + uint8_t _2[6]; /* 0x018 6 ??? */ + uint16_t mcs_objects; /* 0x01e 2 MCS-only, unused */ + + uint32_t checksum_2; /* 0x020 4 Checksum: 0..1f+24..EOF-4 */ + uint8_t seq3[2]; /* 0x024 2 0x0101 */ + uint8_t _3[8]; /* 0x026 8 ??? */ + uint32_t filesize_be2; /* 0x02e 4 Filesize - 0x7000 - 4 */ + uint8_t _4[14]; /* 0x032 14 ??? */ + char name[16]; /* 0x040 16 Add-in name + NUL */ + uint8_t _5[12]; /* 0x050 12 ??? */ + uint32_t filesize_be3; /* 0x05c 4 Filesize */ + char internal[11]; /* 0x060 11 Internal name with '@' */ + char label[8][24]; /* 0x06b 192 Language labels */ + uint8_t allow_estrip; /* 0x12b 1 Allow use as eAct strip? */ + uint8_t _6[4]; /* 0x12c 4 ??? */ + char version[10]; /* 0x130 10 Version "MM.mm.pppp" */ + uint8_t _7[2]; /* 0x13a 2 ??? */ + char date[14]; /* 0x13c 14 Date "yyyy.MMdd.hhmm" */ + uint8_t _8[38]; /* 0x14a 38 ??? */ + char estrip_label[8][36]; /* 0x170 288 eStrip language labels */ + uint8_t eact_icon[0x300]; /* 0x290 768 eAct icon (64x24) */ + uint8_t _9[0x92c]; /* 0x590 2348 ??? */ + char filename[324]; /* 0xebc 324 Filename "X.g3a" */ + uint16_t icon_uns[0x1800]; /* 0x1000 5376 Unselected icon */ + uint16_t icon_sel[0x1800]; /* 0x4000 5376 Selected icon */ + +} __attribute__((packed, aligned(4))); + +/* A full g3a file, suitable for use with pointers */ +struct g3a +{ + struct g3a_header header; + uint8_t code[]; +}; + +#endif /* FX_G3A */ diff --git a/fxgxa/icon.c b/fxgxa/icon.c index 79a21ee..d0162f9 100644 --- a/fxgxa/icon.c +++ b/fxgxa/icon.c @@ -1,132 +1,173 @@ #include #include - -#include +#include #include -/* icon_print(): Show a monochrome 30*17 icon on stdout */ -void icon_print(uint8_t const *icon) +uint8_t *icon_load(const char *filename, int *width, int *height) { - for(int y = 0; y < 17; y++) - { - for(int x = 0; x < 30; x++) - { - int v = icon[(y << 2) + (x >> 3)] & (0x80 >> (x & 7)); - putchar(v ? '#' : ' '); - putchar(v ? '#' : ' '); - } + png_image img; + memset(&img, 0, sizeof img); + img.opaque = NULL; + img.version = PNG_IMAGE_VERSION; - putchar('\n'); - } + void *buffer = NULL; + + png_image_begin_read_from_file(&img, filename); + if(img.warning_or_error) { + fprintf(stderr, "libpng %s: %s\n", img.warning_or_error == 1 + ? "warning": "error", img.message); + if(img.warning_or_error > 1) goto err; + } + + img.format = PNG_FORMAT_RGB; + + buffer = calloc(img.width * img.height, 3); + if(!buffer) { + fprintf(stderr, "error: cannot read %s: %m\n", filename); + goto err; + } + + png_image_finish_read(&img, NULL, buffer, 0, NULL); + if(img.warning_or_error) { + fprintf(stderr, "libpng %s: %s\n", img.warning_or_error == 1 + ? "warning": "error", img.message); + if(img.warning_or_error > 1) goto err; + } + if(width) *width = img.width; + if(height) *height = img.height; + + png_image_free(&img); + return buffer; + +err: + png_image_free(&img); + free(buffer); + return NULL; } -/* icon_load(): Load a monochrome PNG image into an array */ -uint8_t *icon_load(const char *filename, size_t *width, size_t *height) +int icon_save(const char *filename, uint8_t *input, int width, int height) { - png_image img; - memset(&img, 0, sizeof img); - img.opaque = NULL; - img.version = PNG_IMAGE_VERSION; + png_image img; + memset(&img, 0, sizeof img); - png_image_begin_read_from_file(&img, filename); - if(img.warning_or_error) - { - fprintf(stderr, "libpng %s: %s\n", img.warning_or_error == 1 - ? "warning": "error", img.message); - if(img.warning_or_error > 1) - { - png_image_free(&img); - return NULL; - } - } + img.version = PNG_IMAGE_VERSION; + img.width = width; + img.height = height; + img.format = PNG_FORMAT_RGB; - img.format = PNG_FORMAT_GRAY; + png_image_write_to_file(&img, filename, 0, input, 0, NULL); + png_image_free(&img); - void *buffer = calloc(img.width * img.height, 1); - if(!buffer) - { - fprintf(stderr, "error: cannot read %s: %m\n", filename); - png_image_free(&img); - return NULL; - } + if(img.warning_or_error) { + fprintf(stderr, "libpng %s: %s\n", img.warning_or_error == 1 + ? "warning": "error", img.message); + if(img.warning_or_error > 1) return 1; + } - png_image_finish_read(&img, NULL, buffer, img.width, NULL); - if(width) *width = img.width; - if(height) *height = img.height; - - png_image_free(&img); - return buffer; + return 0; } -/* icon_save(): Save an 8-bit array to a PNG image */ -int icon_save(const char *filename, uint8_t *input, size_t width, - size_t height) +uint8_t *icon_conv_24to1(uint8_t const *rgb24, int width, int height) { - png_image img; - memset(&img, 0, sizeof img); + int bytes_per_row = (width + 7) >> 3; + uint8_t *mono = calloc(bytes_per_row * height, 1); + if(!mono) return NULL; - img.version = PNG_IMAGE_VERSION; - img.width = width; - img.height = height; - img.format = PNG_FORMAT_GRAY; + for(int y = 0; y < height; y++) + for(int x = 0; x < width; x++) { + int in = 3 * (y * width + x); + int out = (y * bytes_per_row) + (x >> 3); + int color = (rgb24[in] + rgb24[in+1] + rgb24[in+2]) < 384; + mono[out] |= color << (~x & 7); + } - png_image_write_to_file(&img, filename, 0, input, 0, NULL); - png_image_free(&img); - - if(img.warning_or_error) - { - fprintf(stderr, "libpng %s: %s\n", img.warning_or_error == 1 - ? "warning": "error", img.message); - if(img.warning_or_error > 1) return 1; - } - - return 0; + return mono; } -/* icon_conv_8to1(): Convert an 8-bit icon to 1-bit */ -uint8_t *icon_conv_8to1(uint8_t const *input, size_t width, size_t height) +uint8_t *icon_conv_1to24(uint8_t const *mono, int width, int height) { - if(!input) return NULL; - uint8_t *mono = calloc(68, 1); - if(!mono) return NULL; - size_t stride = width; + int bytes_per_row = (width + 7) >> 3; + uint8_t *rgb24 = calloc(width * height, 3); + if(!rgb24) return NULL; - /* If the image is wider than 30 pixels, ignore columns at the right */ - if(width > 30) width = 30; + for(int y = 0; y < height; y++) + for(int x = 0; x < width; x++) { + int in = (y * bytes_per_row) + (x >> 3); + int out = 3 * (y * width + x); + int color = (mono[in] & (0x80 >> (x & 7))) != 0 ? 0x00 : 0xff; + rgb24[out] = color; + rgb24[out+1] = color; + rgb24[out+2] = color; + } - /* Skip the first line if there is enough rows, because in standard - 30*19 icons, the first and last lines are skipped */ - if(height > 17) input += stride, height--; - - /* If height is still larger than 17, ignore rows at the bottom */ - if(height > 17) height = 17; - - /* Then copy individual pixels on the currently-blank image */ - for(size_t y = 0; y < height; y++) - for(size_t x = 0; x < width; x++) - { - int offset = (y << 2) + (x >> 3); - int color = input[y * stride + x] < 128; - uint8_t mask = color << (~x & 7); - mono[offset] |= mask; - } - - return mono; + return rgb24; } -/* icon_conv_1to8(): Convert an 1-bit icon to 8-bit */ -uint8_t *icon_conv_1to8(uint8_t const *mono) +uint16_t *icon_conv_24to16(uint8_t const *rgb24, int width, int height) { - uint8_t *data = calloc(30 * 17, 1); - if(!data) return NULL; + uint16_t *rgb16be = calloc(width * height, 2); + if(!rgb16be) return NULL; - for(int y = 0; y < 17; y++) - for(int x = 0; x < 30; x++) - { - int offset = (y << 2) + (x >> 3); - int bit = mono[offset] & (0x80 >> (x & 7)); - data[y * 30 + x] = (bit ? 0x00 : 0xff); - } + for(int y = 0; y < height; y++) + for(int x = 0; x < width; x++) { + int in = 3 * (y * width + x); + int color = ((rgb24[in] & 0xf8) << 8) + | ((rgb24[in+1] & 0xfc) << 3) + | ((rgb24[in+2] & 0xf8) >> 3); + rgb16be[y * width + x] = htobe16(color); + } - return data; + return rgb16be; +} + +uint8_t *icon_conv_16to24(uint16_t const *rgb16be, int width, int height) +{ + uint8_t *rgb24 = calloc(width * height, 3); + if(!rgb24) return NULL; + + for(int y = 0; y < height; y++) + for(int x = 0; x < width; x++) { + int out = 3 * (y * width + x); + int color = be16toh(rgb16be[y * width + x]); + rgb24[out] = (color & 0xf800) >> 8; + rgb24[out+1] = (color & 0x07e0) >> 3; + rgb24[out+2] = (color & 0x001f) << 3; + } + + return rgb24; +} + +void icon_print_1(uint8_t const *mono, int width, int height) +{ + int bytes_per_row = (width + 7) >> 3; + + for(int y = 0; y < height; y++) { + for(int x = 0; x < width; x++) { + int in = (y * bytes_per_row) + (x >> 3); + int color = (mono[in] & (0x80 >> (x & 7))) != 0; + putchar(color ? '#' : ' '); + putchar(color ? '#' : ' '); + } + putchar('\n'); + } +} + +void icon_print_16(uint16_t const *rgb16be, int width, int height) +{ + uint8_t *rgb24 = icon_conv_16to24(rgb16be, width, height); + if(!rgb24) { + fprintf(stderr, "error: %m\n"); + return; + }; + + for(int y = 0; y < height; y++) { + for(int x = 0; x < width; x++) { + int in = 3 * (y * width + x); + int r=rgb24[in], g=rgb24[in+1], b=rgb24[in+2]; + printf("\e[48;2;%d;%d;%dm ", r, g, b); + } + printf("\e[0m\n"); + } + + free(rgb24); } diff --git a/fxgxa/main.c b/fxgxa/main.c index d5707c5..42313f0 100644 --- a/fxgxa/main.c +++ b/fxgxa/main.c @@ -3,33 +3,38 @@ #include #include -#include +#include #include +#include static const char *help_string = -"usage: %1$s [-g] [options...]\n" -" %1$s -e [options...]\n" -" %1$s -d \n" -" %1$s -r [-o ]\n" -" %1$s -x [-o ]\n" +"usage: fxgxa [-g] [options...]\n" +" fxgxa -e [options...]\n" +" fxgxa -d \n" +" fxgxa -r [-o ]\n" +" fxgxa -x [-o ]\n" "\n" -"fxg1a creates or edits g1a files (add-in applications for Casio fx9860g\n" -"calculator series) that consist of a g1a header followed by binary code.\n" +"fxgxa creates or edits g1a and g3a files (add-in applications for CASIO\n" +"fx-9860G and fx-CG series) that consist of a header followed by code.\n" "\n" "Operating modes:\n" -" -g, --g1a Generate a g1a file (default)\n" -" -e, --edit Edit header of an existing g1a file\n" -" -d, --dump Dump header of an existing g1a file\n" +" -g, --g1a, --g3a Generate a g1a/g3a file (default)\n" +" -e, --edit Edit header of an existing g1a/g3a file\n" +" -d, --dump Dump header of an existing g1a/g3a file\n" " -r, --repair Recalculate control bytes and checksums\n" " -x, --extract Extract icon into a PNG file\n" "\n" "General options:\n" -" -o, --output= Output file (default: input file with .g1a suffix\n" +" -o, --output= Output file (default: input with .g1a/.g3a suffix\n" " [-g]; with .png suffix [-x]; input file [-e, -r])\n" +" --output-uns= Output for unselected icon with [-x] and g3a file\n" +" --output-sel= Output for selected icon with [-x] and g3a file\n" "\n" "Generation and edition options:\n" -" -i, --icon= Program icon, in PNG format (default: blank icon)\n" -" -n, --name= Add-in name, 8 bytes (default: output file name)\n" +" -i, --icon= Program icon, in PNG format (default: blank) [g1a]\n" +" --icon-uns= Unselected program icon, in PNG format [g3a]\n" +" --icon-sel= Selected program icon, in PNG format [g3a]\n" +" -n, --name= Add-in name (default: output file name)\n" " --version= Program version, MM.mm.pppp format (default: empty)\n" " --internal= Internal name, eg. '@NAME' (default: empty)\n" " --date= Date of build, yyyy.MMdd.hhmm (default: now)\n"; @@ -49,31 +54,58 @@ struct fields const char *date; /* Icon file name */ const char *icon; + const char *icon_uns, *icon_sel; + + // TODO: G3A: Fill the filename field }; /* fields_edit(): Set the value of some fields altogether - @header Header to edit, is assumed checksumed and filled - @fields New values for fields, any members can be NULL */ -void fields_edit(struct g1a *header, struct fields const *fields) + @gxa Header to edit, is assumed checksumed and filled + @fields New values for fields, any members can be NULL */ +void fields_edit(void *gxa, struct fields const *fields) { /* For easy fields, just call the appropriate edition function */ - if(fields->name) edit_name(header, fields->name); - if(fields->version) edit_version(header, fields->version); - if(fields->internal) edit_internal(header, fields->internal); - if(fields->date) edit_date(header, fields->date); + if(fields->name) edit_name(gxa, fields->name); + if(fields->version) edit_version(gxa, fields->version); + if(fields->internal) edit_internal(gxa, fields->internal); + if(fields->date) edit_date(gxa, fields->date); /* Load icon from PNG file */ - if(fields->icon) - { - size_t width, height; - uint8_t *data = icon_load(fields->icon, &width, &height); - if(!data) return; + if(fields->icon && is_g1a(gxa)) { + int w, h; + uint8_t *rgb24 = icon_load(fields->icon, &w, &h); - uint8_t *mono = icon_conv_8to1(data, width, height); - free(data); + if(rgb24) { + /* Skip the first row if h > 17, since the usual + representation at 30x19 skips the first and last */ + uint8_t *mono = icon_conv_24to1( + h > 17 ? rgb24 + 3*w : rgb24, w, h - (h > 17)); + if(mono) edit_g1a_icon(gxa, mono); + free(mono); + } + free(rgb24); + } + if(fields->icon_uns && is_g3a(gxa)) { + int w, h; + uint8_t *rgb24 = icon_load(fields->icon_uns, &w, &h); - if(!mono) return; - edit_icon(header, mono); + if(rgb24) { + uint16_t *rgb16be = icon_conv_24to16(rgb24, w, h); + if(rgb16be) edit_g3a_icon(gxa, rgb16be, false); + free(rgb16be); + } + free(rgb24); + } + if(fields->icon_sel && is_g3a(gxa)) { + int w, h; + uint8_t *rgb24 = icon_load(fields->icon_sel, &w, &h); + + if(rgb24) { + uint16_t *rgb16be = icon_conv_24to16(rgb24, w, h); + if(rgb16be) edit_g3a_icon(gxa, rgb16be, true); + free(rgb16be); + } + free(rgb24); } } @@ -87,21 +119,27 @@ int main(int argc, char **argv) int mode = 'g', error = 0; struct fields fields = { 0 }; const char *output = NULL; + const char *output_uns=NULL, *output_sel=NULL; const struct option longs[] = { - { "help", no_argument, NULL, 'h' }, - { "g1a", no_argument, NULL, 'g' }, - { "edit", no_argument, NULL, 'e' }, - { "dump", no_argument, NULL, 'd' }, - { "repair", no_argument, NULL, 'r' }, - { "extract", no_argument, NULL, 'x' }, - { "output", required_argument, NULL, 'o' }, - { "icon", required_argument, NULL, 'i' }, - { "name", required_argument, NULL, 'n' }, - { "version", required_argument, NULL, 'v' }, - { "internal", required_argument, NULL, 't' }, - { "date", required_argument, NULL, 'a' }, - { NULL, 0, NULL, 0 }, + { "help", no_argument, NULL, 'h' }, + { "g1a", no_argument, NULL, '1' }, + { "g3a", no_argument, NULL, '3' }, + { "edit", no_argument, NULL, 'e' }, + { "dump", no_argument, NULL, 'd' }, + { "repair", no_argument, NULL, 'r' }, + { "extract", no_argument, NULL, 'x' }, + { "output", required_argument, NULL, 'o' }, + { "output-uns", required_argument, NULL, 'O' }, + { "output-sel", required_argument, NULL, 'P' }, + { "icon", required_argument, NULL, 'i' }, + { "icon-uns", required_argument, NULL, 'I' }, + { "icon-sel", required_argument, NULL, 'J' }, + { "name", required_argument, NULL, 'n' }, + { "version", required_argument, NULL, 'v' }, + { "internal", required_argument, NULL, 't' }, + { "date", required_argument, NULL, 'a' }, + { NULL, 0, NULL, 0 }, }; int option = 0; @@ -116,14 +154,28 @@ int main(int argc, char **argv) case 'd': case 'r': case 'x': + case '1': + case '3': mode = option; break; case 'o': output = optarg; break; + case 'O': + output_uns = optarg; + break; + case 'P': + output_sel = optarg; + break; case 'i': fields.icon = optarg; break; + case 'I': + fields.icon_uns = optarg; + break; + case 'J': + fields.icon_sel = optarg; + break; case 'n': fields.name = optarg; break; @@ -141,6 +193,10 @@ int main(int argc, char **argv) break; } + if(mode == 'g' && !strcmp(argv[0], "fxg1a")) + mode = '1'; + if(mode == 'g' && !strcmp(argv[0], "fxg3a")) + mode = '3'; if(error) return 1; if(argv[optind] == NULL) @@ -148,12 +204,18 @@ int main(int argc, char **argv) fprintf(stderr, help_string, argv[0]); return 1; } - if(mode == 'g') + if(mode == 'g') { + fprintf(stderr, "cannot guess -g; use --g1a or --g3a\n"); + return 1; + } + if(mode == '1' || mode == '3') { /* Load binary file into memory */ size_t size; - struct g1a *g1a = load_binary(argv[optind], &size); - if(!g1a) return 1; + int header = (mode == '1' ? 0x200 : 0x7000); + int footer = (mode == '1' ? 0 : 4); + void *gxa = load_binary(argv[optind], &size, header, footer); + if(!gxa) return 1; /* If [output] is set, use it, otherwise compute a default */ char *alloc = NULL; @@ -161,46 +223,52 @@ int main(int argc, char **argv) { alloc = malloc(strlen(argv[optind]) + 5); if(!alloc) {fprintf(stderr, "error: %m\n"); return 1;} - default_output(argv[optind], ".g1a", alloc); + default_output(argv[optind], + (mode == '1' ? ".g1a" : ".g3a"), alloc); } + /* First set the type so that is_g1a() and is_g3a() work */ + ((uint8_t *)gxa)[8] = (mode == '1' ? 0xf3 : 0x2c); + /* Start with output file name as application name */ - edit_name(g1a, output ? output : alloc); + edit_name(gxa, output ? output : alloc); /* Start with "now" as build date */ char date[15]; time_t t = time(NULL); struct tm *now = localtime(&t); strftime(date, 15, "%Y.%m%d.%H%M", now); - edit_date(g1a, date); + edit_date(gxa, date); /* Start with an uppercase name as internal name */ - char internal[9]; - default_internal(fields.name ? fields.name : g1a->header.name, - internal); - edit_internal(g1a, internal); + char internal[12]; + if(fields.name) + default_internal(fields.name, internal, 11); + else if(is_g1a(gxa)) + default_internal(G1A(gxa)->header.name, internal, 11); + else if(is_g3a(gxa)) + default_internal(G3A(gxa)->header.name, internal, 11); + edit_internal(gxa, internal); /* Edit the fields with user-customized values */ - fields_edit(g1a, &fields); + fields_edit(gxa, &fields); /* Set fixed fields and calculate checksums */ - sign(g1a, size); + sign(gxa, size); - save_g1a(output ? output : alloc, g1a, size); + save_gxa(output ? output : alloc, gxa, size); free(alloc); - - /* Write output file */ - free(g1a); + free(gxa); } if(mode == 'e') { - /* Load g1a file into memory */ + /* Load file into memory */ size_t size; - struct g1a *g1a = load_g1a(argv[optind], &size); - if(!g1a) return 1; + void *gxa = load_gxa(argv[optind], &size); + if(!gxa) return 1; /* Edit the fields with user-customized values */ - fields_edit(g1a, &fields); + fields_edit(gxa, &fields); /* We don't reset fixed fields or recalculate checksums because we only want to edit what was requested by the user. @@ -209,63 +277,78 @@ int main(int argc, char **argv) /* Regenerate input file, or output somewhere else */ if(!output) output = argv[optind]; - save_g1a(output, g1a, size); - free(g1a); + save_gxa(output, gxa, size); + free(gxa); } if(mode == 'd') { - /* Load and dump the g1a */ size_t size; - struct g1a *g1a = load_g1a(argv[optind], &size); - if(!g1a) return 1; + void *gxa = load_gxa(argv[optind], &size); + if(!gxa) return 1; - dump(g1a, size); - free(g1a); + dump(gxa, size); + free(gxa); } if(mode == 'r') { - /* Load g1a file into memory */ size_t size; - struct g1a *g1a = load_g1a(argv[optind], &size); - if(!g1a) return 1; + void *gxa = load_gxa(argv[optind], &size); + if(!gxa) return 1; /* Repair file by recalculating fixed fields and checksums */ - sign(g1a, size); + sign(gxa, size); /* Regenerate input file, or output somewhere else */ if(!output) output = argv[optind]; - save_g1a(output, g1a, size); - free(g1a); + save_gxa(output, gxa, size); + free(gxa); } if(mode == 'x') { - /* Load g1a file into memory */ size_t size; - struct g1a *g1a = load_g1a(argv[optind], &size); - if(!g1a) return 1; + void *gxa = load_gxa(argv[optind], &size); + if(!gxa) return 1; - /* Generate 8-bit icon from g1a 1-bit */ - uint8_t *data = icon_conv_1to8(g1a->header.icon); - if(!data) - { - fprintf(stderr, "error: %m\n"); - return 1; - } + if(is_g1a(gxa)) { + /* Add clean top/bottom rows */ + uint8_t mono[76]; + memcpy(mono, "\x00\x00\x00\x00", 4); + memcpy(mono+4, G1A(gxa)->header.icon, 68); + memcpy(mono+72, "\x7f\xff\xff\xfc", 4); + uint8_t *rgb24 = icon_conv_1to24(mono, 30, 19); - /* Calculate a default output name if none is provided */ - if(output) - { - icon_save(output, data, 30, 17); - } - else - { - char *alloc = malloc(strlen(argv[optind]) + 5); - if(!alloc) {fprintf(stderr, "error: %m\n"); return 1;} - default_output(argv[optind], ".png", alloc); + /* Calculate a default output name if none is given */ + char *alloc = NULL; + if(!output) { + alloc = malloc(strlen(argv[optind]) + 5); + if(!alloc) { + fprintf(stderr, "error: %m\n"); + return 1; + } + default_output(argv[optind], ".png", alloc); + } - icon_save(alloc, data, 30, 17); + icon_save(output ? output : alloc, rgb24, 30, 19); free(alloc); + free(rgb24); } + else if(is_g3a(gxa)) { + uint8_t *rgb24_uns = icon_conv_16to24( + G3A(gxa)->header.icon_uns, 92, 64); + uint8_t *rgb24_sel = icon_conv_16to24( + G3A(gxa)->header.icon_sel, 92, 64); + + if(output_uns) + icon_save(output_uns, rgb24_uns, 92, 64); + if(output_sel) + icon_save(output_sel, rgb24_sel, 92, 64); + if(!output_uns && !output_sel) fprintf(stderr, "Please" + " specify --output-uns or --output-sel.\n"); + + free(rgb24_uns); + free(rgb24_sel); + } + } return 0; diff --git a/fxgxa/util.c b/fxgxa/util.c index fa2183d..4ca4123 100644 --- a/fxgxa/util.c +++ b/fxgxa/util.c @@ -1,31 +1,64 @@ #include #include #include -#include +#include -/* -** Public API -*/ - -/* checksum(): Sum of 8 big-endian shorts at 0x300 */ -uint16_t checksum(struct g1a const *g1a, size_t size) +bool is_g1a(void *gxa) { - uint16_t shorts[16] = { 0 }; + /* Check the byte at offset 8 for file type 0xf3 */ + uint8_t mcs_type = ((uint8_t *)gxa)[8]; + return mcs_type == 0xf3; +} - /* Extract 16 bytes from the file (maybe less are available) */ - int available = size - 0x300; +bool is_g3a(void *gxa) +{ + /* Check the byte at offset 8 for file type 0x2c */ + uint8_t mcs_type = ((uint8_t *)gxa)[8]; + return mcs_type == 0x2c; +} + +uint16_t word_sum(void const *gxa, size_t offset, int words, size_t size) +{ + uint16_t shorts[words]; + memset(shorts, 0, 2*words); + + /* Extract up to [2*words] bytes from the file */ + int available = (int)size - offset; if(available < 0) available = 0; - if(available > 16) available = 16; - memcpy(shorts, g1a->code + 0x100, available); + if(available > 2*words) available = 2*words; + memcpy(shorts, gxa+offset, available); /* Do the big-endian sum */ uint16_t sum = 0; - for(int i = 0; i < 8; i++) sum += htobe16(shorts[i]); + for(int i = 0; i < words; i++) + sum += htobe16(shorts[i]); + + return sum; +} + +uint16_t checksum_g1a(struct g1a const *g1a, size_t size) +{ + return word_sum(g1a, 0x300, 8, size); +} + +uint16_t checksum_g3a(struct g3a const *g3a, size_t size) +{ + return word_sum(g3a, 0x7100, 8, size); +} + +uint32_t checksum_g3a_2(struct g3a const *g3a, size_t size) +{ + uint32_t sum = 0; + uint8_t *data = (void *)g3a; + + for(size_t i = 0; i < 0x20; i++) + sum += (data[i] ^ 0xff); + for(size_t i = 0x24; i < size - 4; i++) + sum += data[i]; return sum; } -/* default_output(): Calculate default output file name */ void default_output(const char *name, const char *suffix, char *output) { /* Check if there is a dot at the end of @name, before the last '/'. @@ -48,13 +81,12 @@ void default_output(const char *name, const char *suffix, char *output) } } -/* default_internal(): Calculate default internal name */ -void default_internal(const char *name, char *output) +void default_internal(const char *name, char *output, size_t size) { output[0] = '@'; - int i=1; + size_t i = 1; - for(int j=0; name[j] && i < 8; j++) + for(int j = 0; name[j] && i < size; j++) { if(isalpha(name[j])) output[i++] = toupper(name[j]); } diff --git a/fxsdk/assets/Makefile b/fxsdk/assets/Makefile index 99852d6..caff6d9 100755 --- a/fxsdk/assets/Makefile +++ b/fxsdk/assets/Makefile @@ -26,7 +26,7 @@ BINFLAGS := -R .bss -R .gint_bss NAME_G1A ?= $(NAME) NAME_G3A ?= $(NAME) G1AF := -i "$(ICON_FX)" -n "$(NAME_G1A)" --internal="$(INTERNAL)" -G3AF := -n basic:"$(NAME_G3A)" -i uns:"$(ICON_CG_UNS)" -i sel:"$(ICON_CG_SEL)" +G3AF := -n "$(NAME_G3A)" --icon-uns="$(ICON_CG_UNS)" --icon-sel="$(ICON_CG_SEL)" ifeq "$(TOOLCHAIN_FX)" "" TOOLCHAIN_FX := sh3eb-elf @@ -104,13 +104,13 @@ $(TARGET_FX): $(obj-fx) $(deps-fx) @ mkdir -p $(dir $@) $(TOOLCHAIN_FX)-gcc -o $(ELF_FX) $(obj-fx) $(CFLAGSFX) $(LDFLAGSFX) $(TOOLCHAIN_FX)-objcopy -O binary $(BINFLAGS) $(ELF_FX) $(BIN_FX) - fxg1a $(BIN_FX) -o $@ $(G1AF) + fxgxa --g1a $(BIN_FX) -o $@ $(G1AF) $(TARGET_CG): $(obj-cg) $(deps-cg) @ mkdir -p $(dir $@) $(TOOLCHAIN_CG)-gcc -o $(ELF_CG) $(obj-cg) $(CFLAGSCG) $(LDFLAGSCG) $(TOOLCHAIN_CG)-objcopy -O binary $(BINFLAGS) $(ELF_CG) $(BIN_CG) - mkg3a $(G3AF) $(BIN_CG) $@ + fxgxa --g3a $(BIN_CG) -o $@ $(G3AF) # C sources build-fx/%.c.o: %.c diff --git a/fxsdk/cmake/GenerateG1A.cmake b/fxsdk/cmake/GenerateG1A.cmake index 5a1b31f..dd0aa7a 100644 --- a/fxsdk/cmake/GenerateG1A.cmake +++ b/fxsdk/cmake/GenerateG1A.cmake @@ -20,27 +20,27 @@ function(generate_g1a) set(G1A_OUTPUT "${G1A_TARGET}.g1a") endif() - # Compute the set of fxg1a arguments + # Compute the set of fxgxa arguments - set(FXG1A_ARGS "") + set(FXGXA_ARGS "") - # Support empty names enen though they're not normally used in g1a files + # Support empty names even though they're not normally used in g1a files if(DEFINED G1A_NAME OR "NAME" IN_LIST G1A_KEYWORDS_MISSING_VALUES) - list(APPEND FXG1A_ARGS "-n" "${G1A_NAME}") + list(APPEND FXGXA_ARGS "-n" "${G1A_NAME}") endif() if(DEFINED G1A_ICON) get_filename_component(G1A_ICON "${G1A_ICON}" ABSOLUTE BASE_DIR "${CMAKE_CURRENT_SOURCE_DIR}") - list(APPEND FXG1A_ARGS "-i" "${G1A_ICON}") + list(APPEND FXGXA_ARGS "-i" "${G1A_ICON}") endif() if(DEFINED G1A_INTERNAL) - list(APPEND FXG1A_ARGS "--internal=${G1A_INTERNAL}") + list(APPEND FXGXA_ARGS "--internal=${G1A_INTERNAL}") endif() if(DEFINED G1A_VERSION) - list(APPEND FXG1A_ARGS "--version=${G1A_VERSION}") + list(APPEND FXGXA_ARGS "--version=${G1A_VERSION}") endif() string(REGEX REPLACE "sh-elf-gcc$" "sh-elf-objcopy" OBJCOPY "${CMAKE_C_COMPILER}") @@ -48,7 +48,7 @@ function(generate_g1a) add_custom_command( TARGET "${G1A_TARGET}" POST_BUILD COMMAND "${OBJCOPY}" -O binary -R .bss -R .gint_bss "${G1A_TARGET}" "${G1A_TARGET}.bin" - COMMAND fxg1a ${FXG1A_ARGS} -o "${G1A_OUTPUT}" "${G1A_TARGET}.bin" + COMMAND fxgxa --g1a ${FXGXA_ARGS} -o "${G1A_OUTPUT}" "${G1A_TARGET}.bin" WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" ) if(DEFINED G1A_ICON) diff --git a/fxsdk/cmake/GenerateG3A.cmake b/fxsdk/cmake/GenerateG3A.cmake index 9e4654e..ddfd28d 100644 --- a/fxsdk/cmake/GenerateG3A.cmake +++ b/fxsdk/cmake/GenerateG3A.cmake @@ -27,19 +27,22 @@ function(generate_g3a) set(G3A_OUTPUT "${G3A_TARGET}.g3a") endif() - # Compute the set of mkg3a arguments + # Compute the set of fxgxa arguments - set(MKG3A_ARGS "") + set(FXGXA_ARGS "") # Empty names are commonly used to avoid having the file name printed over # the icon, but using ARGN in cmake_parse_arguments() drops the empty string. # Check in KEYWORDS_MISSING_VALUES as a complement. if(DEFINED G3A_NAME OR "NAME" IN_LIST G3A_KEYWORDS_MISSING_VALUES) - list(APPEND MKG3A_ARGS "-n" "basic:${G3A_NAME}") + if("${G3A_NAME}" STREQUAL "") + set(G3A_NAME "''") + endif() + list(APPEND FXGXA_ARGS "-n" "${G3A_NAME}") endif() if(DEFINED G3A_VERSION) - list(APPEND MKG3A_ARGS "-V" "${G3A_VERSION}") + list(APPEND FXGXA_ARGS "--version=${G3A_VERSION}") endif() if(DEFINED G3A_ICONS) @@ -52,7 +55,7 @@ function(generate_g3a) # Who doesn't REALLY love to deal with escaping string(REPLACE "'" "\\'" G3A_ICON1B "${G3A_ICON1}") string(REPLACE "'" "\\'" G3A_ICON2B "${G3A_ICON2}") - list(APPEND MKG3A_ARGS "-i" "uns:${G3A_ICON1B}" "-i" "sel:${G3A_ICON2B}") + list(APPEND FXGXA_ARGS "--icon-uns=${G3A_ICON1B}" "--icon-sel=${G3A_ICON2B}") endif() string(REPLACE "gcc" "objcopy" OBJCOPY "${CMAKE_C_COMPILER}") @@ -60,7 +63,7 @@ function(generate_g3a) add_custom_command( TARGET "${G3A_TARGET}" POST_BUILD COMMAND ${OBJCOPY} -O binary -R .bss -R .gint_bss ${G3A_TARGET} ${G3A_TARGET}.bin - COMMAND mkg3a ${MKG3A_ARGS} "${G3A_TARGET}.bin" "${G3A_OUTPUT}" + COMMAND fxgxa --g3a ${FXGXA_ARGS} "${G3A_TARGET}.bin" -o "${G3A_OUTPUT}" WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" ) if(DEFINED G3A_ICONS)