implement fxgxa and use it in both build systems
This commit is contained in:
parent
ec39aa5cde
commit
2cc5d7ac5b
|
@ -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)
|
||||
|
|
32
README.md
32
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`
|
||||
|
||||
|
|
171
fxgxa/dump.c
171
fxgxa/dump.c
|
@ -2,10 +2,10 @@
|
|||
#include <string.h>
|
||||
#include <endianness.h>
|
||||
|
||||
#include <fxg1a.h>
|
||||
#include <fxgxa.h>
|
||||
#include <g1a.h>
|
||||
|
||||
/* 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 ? "<All zeros>\n" : "<Not shown (non-zero)>\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);
|
||||
}
|
||||
|
|
143
fxgxa/edit.c
143
fxgxa/edit.c
|
@ -1,72 +1,139 @@
|
|||
#include <fxg1a.h>
|
||||
#include <fxgxa.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <endianness.h>
|
||||
|
||||
/* 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);
|
||||
}
|
||||
|
|
51
fxgxa/file.c
51
fxgxa/file.c
|
@ -7,13 +7,13 @@
|
|||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <fxg1a.h>
|
||||
#include <fxgxa.h>
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
|
176
fxgxa/fxg1a.h
176
fxgxa/fxg1a.h
|
@ -1,176 +0,0 @@
|
|||
//---
|
||||
// fxg1a:fxg1a - Main interfaces
|
||||
//---
|
||||
|
||||
#ifndef FX_FXG1A
|
||||
#define FX_FXG1A
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <g1a.h>
|
||||
|
||||
/*
|
||||
** 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 */
|
|
@ -0,0 +1,198 @@
|
|||
//---
|
||||
// fxgxa:fxgxa - Main interfaces
|
||||
//---
|
||||
|
||||
#ifndef FX_FXGXA
|
||||
#define FX_FXGXA
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <g1a.h>
|
||||
#include <g3a.h>
|
||||
|
||||
/* 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 */
|
73
fxgxa/g1a.h
73
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 <stdint.h>
|
||||
|
||||
/* 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 */
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
//---
|
||||
// fxgxa:g3a - Add-in header for Casio's G3A format
|
||||
//---
|
||||
|
||||
#ifndef FX_G3A
|
||||
#define FX_G3A
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/* 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 */
|
247
fxgxa/icon.c
247
fxgxa/icon.c
|
@ -1,132 +1,173 @@
|
|||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <fxg1a.h>
|
||||
#include <fxgxa.h>
|
||||
#include <png.h>
|
||||
|
||||
/* 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;
|
||||