implement fxgxa and use it in both build systems

This commit is contained in:
Lephenixnoir 2022-03-20 19:45:51 +00:00
parent ec39aa5cde
commit 2cc5d7ac5b
Signed by untrusted user: Lephenixnoir
GPG Key ID: 1BBA026E13FC0495
15 changed files with 991 additions and 565 deletions

View File

@ -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)

View File

@ -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`

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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 */

198
fxgxa/fxgxa.h Normal file
View File

@ -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 */

View File

@ -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 */

58
fxgxa/g3a.h Normal file
View File

@ -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 */

View File

@ -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;
}
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);
}

View File

@ -3,33 +3,38 @@
#include <time.h>
#include <getopt.h>
#include <fxg1a.h>
#include <fxgxa.h>
#include <g1a.h>
#include <g3a.h>
static const char *help_string =
"usage: %1$s [-g] <binary file> [options...]\n"
" %1$s -e <g1a file> [options...]\n"
" %1$s -d <g1a file>\n"
" %1$s -r <g1a file> [-o <g1a file>]\n"
" %1$s -x <g1a file> [-o <png file>]\n"
"usage: fxgxa [-g] <binary file> [options...]\n"
" fxgxa -e <g1a/g3a file> [options...]\n"
" fxgxa -d <g1a/g3a file>\n"
" fxgxa -r <g1a/g3a file> [-o <g1a/g3a file>]\n"
" fxgxa -x <g1a/g3a file> [-o <png file>]\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=<file> Output file (default: input file with .g1a suffix\n"
" -o, --output=<file> Output file (default: input with .g1a/.g3a suffix\n"
" [-g]; with .png suffix [-x]; input file [-e, -r])\n"
" --output-uns=<file> Output for unselected icon with [-x] and g3a file\n"
" --output-sel=<file> Output for selected icon with [-x] and g3a file\n"
"\n"
"Generation and edition options:\n"
" -i, --icon=<png> Program icon, in PNG format (default: blank icon)\n"
" -n, --name=<name> Add-in name, 8 bytes (default: output file name)\n"
" -i, --icon=<png> Program icon, in PNG format (default: blank) [g1a]\n"
" --icon-uns=<png> Unselected program icon, in PNG format [g3a]\n"
" --icon-sel=<png> Selected program icon, in PNG format [g3a]\n"
" -n, --name=<name> Add-in name (default: output file name)\n"
" --version=<text> Program version, MM.mm.pppp format (default: empty)\n"
" --internal=<name> Internal name, eg. '@NAME' (default: empty)\n"
" --date=<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;

View File

@ -1,31 +1,64 @@
#include <string.h>
#include <ctype.h>
#include <endianness.h>
#include <fxg1a.h>
#include <fxgxa.h>
/*
** 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]);
}

View File

@ -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

View File

@ -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)

View File

@ -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)