2019-03-21 22:54:06 +01:00
|
|
|
#include <stdio.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <time.h>
|
|
|
|
#include <getopt.h>
|
|
|
|
|
2022-03-20 20:45:51 +01:00
|
|
|
#include <fxgxa.h>
|
2019-03-21 22:54:06 +01:00
|
|
|
#include <g1a.h>
|
2022-03-20 20:45:51 +01:00
|
|
|
#include <g3a.h>
|
2019-03-21 22:54:06 +01:00
|
|
|
|
|
|
|
static const char *help_string =
|
2022-03-20 20:45:51 +01:00
|
|
|
"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"
|
2019-03-21 22:54:06 +01:00
|
|
|
"\n"
|
2022-03-20 20:45:51 +01:00
|
|
|
"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"
|
2019-03-21 22:54:06 +01:00
|
|
|
"\n"
|
|
|
|
"Operating modes:\n"
|
2022-03-20 20:45:51 +01:00
|
|
|
" -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"
|
2019-03-21 22:54:06 +01:00
|
|
|
" -r, --repair Recalculate control bytes and checksums\n"
|
|
|
|
" -x, --extract Extract icon into a PNG file\n"
|
|
|
|
"\n"
|
|
|
|
"General options:\n"
|
2022-03-20 20:45:51 +01:00
|
|
|
" -o, --output=<file> Output file (default: input with .g1a/.g3a suffix\n"
|
2019-03-21 22:54:06 +01:00
|
|
|
" [-g]; with .png suffix [-x]; input file [-e, -r])\n"
|
2022-03-20 20:45:51 +01:00
|
|
|
" --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"
|
2019-03-21 22:54:06 +01:00
|
|
|
"\n"
|
|
|
|
"Generation and edition options:\n"
|
2022-03-20 20:45:51 +01:00
|
|
|
" -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"
|
2019-03-21 22:54:06 +01:00
|
|
|
" --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";
|
|
|
|
|
|
|
|
/*
|
|
|
|
** Field customization
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* A set of user-defined fields, often taken on the command-line
|
|
|
|
Default values are NULL and indicate "no value" (-g) or "no change" (-e). */
|
|
|
|
struct fields
|
|
|
|
{
|
|
|
|
/* New values for basic fields */
|
|
|
|
const char *name;
|
|
|
|
const char *version;
|
|
|
|
const char *internal;
|
|
|
|
const char *date;
|
|
|
|
/* Icon file name */
|
|
|
|
const char *icon;
|
2022-03-20 20:45:51 +01:00
|
|
|
const char *icon_uns, *icon_sel;
|
|
|
|
|
|
|
|
// TODO: G3A: Fill the filename field
|
2019-03-21 22:54:06 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
/* fields_edit(): Set the value of some fields altogether
|
2022-03-20 20:45:51 +01:00
|
|
|
@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)
|
2019-03-21 22:54:06 +01:00
|
|
|
{
|
|
|
|
/* For easy fields, just call the appropriate edition function */
|
2022-03-20 20:45:51 +01:00
|
|
|
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);
|
2019-03-21 22:54:06 +01:00
|
|
|
|
|
|
|
/* Load icon from PNG file */
|
2022-03-20 20:45:51 +01:00
|
|
|
if(fields->icon && is_g1a(gxa)) {
|
|
|
|
int w, h;
|
|
|
|
uint8_t *rgb24 = icon_load(fields->icon, &w, &h);
|
|
|
|
|
|
|
|
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(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);
|
2019-03-21 22:54:06 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
** Tool implementation
|
|
|
|
*/
|
|
|
|
|
|
|
|
int main(int argc, char **argv)
|
|
|
|
{
|
|
|
|
/* Result of option parsing */
|
|
|
|
int mode = 'g', error = 0;
|
|
|
|
struct fields fields = { 0 };
|
|
|
|
const char *output = NULL;
|
2022-03-20 20:45:51 +01:00
|
|
|
const char *output_uns=NULL, *output_sel=NULL;
|
2019-03-21 22:54:06 +01:00
|
|
|
|
|
|
|
const struct option longs[] = {
|
2022-03-20 20:45:51 +01:00
|
|
|
{ "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 },
|
2019-03-21 22:54:06 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
int option = 0;
|
|
|
|
while(option >= 0 && option != '?')
|
|
|
|
switch((option = getopt_long(argc, argv, "hgedrxo:i:n:", longs, NULL)))
|
|
|
|
{
|
|
|
|
case 'h':
|
|
|
|
fprintf(stderr, help_string, argv[0]);
|
|
|
|
return 0;
|
|
|
|
case 'g':
|
|
|
|
case 'e':
|
|
|
|
case 'd':
|
|
|
|
case 'r':
|
|
|
|
case 'x':
|
2022-03-20 20:45:51 +01:00
|
|
|
case '1':
|
|
|
|
case '3':
|
2019-03-21 22:54:06 +01:00
|
|
|
mode = option;
|
|
|
|
break;
|
|
|
|
case 'o':
|
|
|
|
output = optarg;
|
|
|
|
break;
|
2022-03-20 20:45:51 +01:00
|
|
|
case 'O':
|
|
|
|
output_uns = optarg;
|
|
|
|
break;
|
|
|
|
case 'P':
|
|
|
|
output_sel = optarg;
|
|
|
|
break;
|
2019-03-21 22:54:06 +01:00
|
|
|
case 'i':
|
|
|
|
fields.icon = optarg;
|
|
|
|
break;
|
2022-03-20 20:45:51 +01:00
|
|
|
case 'I':
|
|
|
|
fields.icon_uns = optarg;
|
|
|
|
break;
|
|
|
|
case 'J':
|
|
|
|
fields.icon_sel = optarg;
|
|
|
|
break;
|
2019-03-21 22:54:06 +01:00
|
|
|
case 'n':
|
|
|
|
fields.name = optarg;
|
|
|
|
break;
|
|
|
|
case 'v':
|
|
|
|
fields.version = optarg;
|
|
|
|
break;
|
|
|
|
case 't':
|
|
|
|
fields.internal = optarg;
|
|
|
|
break;
|
|
|
|
case 'a':
|
|
|
|
fields.date = optarg;
|
|
|
|
break;
|
|
|
|
case '?':
|
|
|
|
error = 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2022-03-20 20:45:51 +01:00
|
|
|
if(mode == 'g' && !strcmp(argv[0], "fxg1a"))
|
|
|
|
mode = '1';
|
|
|
|
if(mode == 'g' && !strcmp(argv[0], "fxg3a"))
|
|
|
|
mode = '3';
|
2019-03-21 22:54:06 +01:00
|
|
|
if(error) return 1;
|
|
|
|
|
|
|
|
if(argv[optind] == NULL)
|
|
|
|
{
|
|
|
|
fprintf(stderr, help_string, argv[0]);
|
|
|
|
return 1;
|
|
|
|
}
|
2022-03-20 20:45:51 +01:00
|
|
|
if(mode == 'g') {
|
|
|
|
fprintf(stderr, "cannot guess -g; use --g1a or --g3a\n");
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
if(mode == '1' || mode == '3')
|
2019-03-21 22:54:06 +01:00
|
|
|
{
|
|
|
|
/* Load binary file into memory */
|
|
|
|
size_t size;
|
2022-03-20 20:45:51 +01:00
|
|
|
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;
|
2019-03-21 22:54:06 +01:00
|
|
|
|
|
|
|
/* If [output] is set, use it, otherwise compute a default */
|
|
|
|
char *alloc = NULL;
|
|
|
|
if(!output)
|
|
|
|
{
|
|
|
|
alloc = malloc(strlen(argv[optind]) + 5);
|
|
|
|
if(!alloc) {fprintf(stderr, "error: %m\n"); return 1;}
|
2022-03-20 20:45:51 +01:00
|
|
|
default_output(argv[optind],
|
|
|
|
(mode == '1' ? ".g1a" : ".g3a"), alloc);
|
2019-03-21 22:54:06 +01:00
|
|
|
}
|
|
|
|
|
2022-03-20 20:45:51 +01:00
|
|
|
/* First set the type so that is_g1a() and is_g3a() work */
|
|
|
|
((uint8_t *)gxa)[8] = (mode == '1' ? 0xf3 : 0x2c);
|
|
|
|
|
2019-03-21 22:54:06 +01:00
|
|
|
/* Start with output file name as application name */
|
2022-03-20 20:45:51 +01:00
|
|
|
edit_name(gxa, output ? output : alloc);
|
2019-03-21 22:54:06 +01:00
|
|
|
|
|
|
|
/* 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);
|
2022-03-20 20:45:51 +01:00
|
|
|
edit_date(gxa, date);
|
2019-03-21 22:54:06 +01:00
|
|
|
|
2021-01-13 16:21:13 +01:00
|
|
|
/* Start with an uppercase name as internal name */
|
2022-03-20 20:45:51 +01:00
|
|
|
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);
|
2021-01-13 16:21:13 +01:00
|
|
|
|
2019-03-21 22:54:06 +01:00
|
|
|
/* Edit the fields with user-customized values */
|
2022-03-20 20:45:51 +01:00
|
|
|
fields_edit(gxa, &fields);
|
2019-03-21 22:54:06 +01:00
|
|
|
|
|
|
|
/* Set fixed fields and calculate checksums */
|
2022-03-20 20:45:51 +01:00
|
|
|
sign(gxa, size);
|
2019-03-21 22:54:06 +01:00
|
|
|
|
2022-03-20 20:45:51 +01:00
|
|
|
save_gxa(output ? output : alloc, gxa, size);
|
2019-03-21 22:54:06 +01:00
|
|
|
free(alloc);
|
2022-03-20 20:45:51 +01:00
|
|
|
free(gxa);
|
2019-03-21 22:54:06 +01:00
|
|
|
}
|
|
|
|
if(mode == 'e')
|
|
|
|
{
|
2022-03-20 20:45:51 +01:00
|
|
|
/* Load file into memory */
|
2019-03-21 22:54:06 +01:00
|
|
|
size_t size;
|
2022-03-20 20:45:51 +01:00
|
|
|
void *gxa = load_gxa(argv[optind], &size);
|
|
|
|
if(!gxa) return 1;
|
2019-03-21 22:54:06 +01:00
|
|
|
|
|
|
|
/* Edit the fields with user-customized values */
|
2022-03-20 20:45:51 +01:00
|
|
|
fields_edit(gxa, &fields);
|
2019-03-21 22:54:06 +01:00
|
|
|
|
|
|
|
/* We don't reset fixed fields or recalculate checksums because
|
|
|
|
we only want to edit what was requested by the user.
|
|
|
|
Besides, the control bytes and checksums do *not* depend on
|
|
|
|
the value of user-customizable fields. */
|
|
|
|
|
|
|
|
/* Regenerate input file, or output somewhere else */
|
|
|
|
if(!output) output = argv[optind];
|
2022-03-20 20:45:51 +01:00
|
|
|
save_gxa(output, gxa, size);
|
|
|
|
free(gxa);
|
2019-03-21 22:54:06 +01:00
|
|
|
}
|
|
|
|
if(mode == 'd')
|
|
|
|
{
|
|
|
|
size_t size;
|
2022-03-20 20:45:51 +01:00
|
|
|
void *gxa = load_gxa(argv[optind], &size);
|
|
|
|
if(!gxa) return 1;
|
2019-03-21 22:54:06 +01:00
|
|
|
|
2022-03-20 20:45:51 +01:00
|
|
|
dump(gxa, size);
|
|
|
|
free(gxa);
|
2019-03-21 22:54:06 +01:00
|
|
|
}
|
|
|
|
if(mode == 'r')
|
|
|
|
{
|
|
|
|
size_t size;
|
2022-03-20 20:45:51 +01:00
|
|
|
void *gxa = load_gxa(argv[optind], &size);
|
|
|
|
if(!gxa) return 1;
|
2019-03-21 22:54:06 +01:00
|
|
|
|
|
|
|
/* Repair file by recalculating fixed fields and checksums */
|
2022-03-20 20:45:51 +01:00
|
|
|
sign(gxa, size);
|
2019-03-21 22:54:06 +01:00
|
|
|
|
|
|
|
/* Regenerate input file, or output somewhere else */
|
|
|
|
if(!output) output = argv[optind];
|
2022-03-20 20:45:51 +01:00
|
|
|
save_gxa(output, gxa, size);
|
|
|
|
free(gxa);
|
2019-03-21 22:54:06 +01:00
|
|
|
}
|
|
|
|
if(mode == 'x')
|
|
|
|
{
|
|
|
|
size_t size;
|
2022-03-20 20:45:51 +01:00
|
|
|
void *gxa = load_gxa(argv[optind], &size);
|
|
|
|
if(!gxa) 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 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(output ? output : alloc, rgb24, 30, 19);
|
|
|
|
free(alloc);
|
|
|
|
free(rgb24);
|
2019-03-21 22:54:06 +01:00
|
|
|
}
|
2022-03-20 20:45:51 +01:00
|
|
|
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);
|
2019-03-21 22:54:06 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|