diff --git a/.gitignore b/.gitignore index 12a83dd..56d1abe 100644 --- a/.gitignore +++ b/.gitignore @@ -3,5 +3,6 @@ /man /p7* /mcsfile* +/g1a-wrapper* .*.swp diff --git a/src/g1a-wrapper/args.c b/src/g1a-wrapper/args.c new file mode 100644 index 0000000..12e2d4c --- /dev/null +++ b/src/g1a-wrapper/args.c @@ -0,0 +1,194 @@ +/* ***************************************************************************** + * g1a-wrapper/args.c -- g1a-wrapper command-line argument parsing. + * Copyright (C) 2017 Thomas "Cakeisalie5" Touhey + * + * This file is part of p7utils. + * p7utils is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2.0 of the License, + * or (at your option) any later version. + * + * p7utils is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with p7utils; if not, see . + * ************************************************************************** */ +#include "main.h" +#include +#include +#include +#include +#include + +/* ************************************************************************** */ +/* Help and version messages */ +/* ************************************************************************** */ +/* Version message */ +static const char version_message[] = +QUOTE(BIN) " - from " QUOTE(NAME) " v" QUOTE(VERSION) + " (licensed under GPLv2)\n" +"Maintained by " QUOTE(MAINTAINER) ".\n" +"\n" +"This is free software; see the source for copying conditions.\n" +"There is NO warranty; not even for MERCHANTABILITY or\n" +"FITNESS FOR A PARTICULAR PURPOSE."; + +/* Main help message */ +static const char help_message[] = +"Usage: " QUOTE(BIN) " [-v] [--help|-h] \n" +" [-o ] [-i ] [-n ]\n" +" [--version ] [--internal <@ADDIN>]\n" +" [--date Output file name, default is 'addin.g1a'.\n" +" -i The application icon, must be 30x19.\n" +" Default is blank.\n" +" -n The application name. '[A-Z]{,8}' formatted.\n" +" Default name is the truncated output filename.\n" +" --version The application version, 'MM.mm.pppp' formatted.\n" +" Default is '00.00.0000'.\n" +" --internal The application internal name. '@[A-Z]{,7}' formatted.\n" +" Default is '@ADDIN'.\n" +" --date The application build date. 'yyyy.MMdd.hhmm' formatted.\n" +" Default is the current time.\n" +"\n" +"Report bugs to " QUOTE(MAINTAINER) "."; + +/* ************************************************************************** */ +/* Main function */ +/* ************************************************************************** */ +/** + * parse_args: + * Args parsing main function. + * + * Was my very first experiment with getopt. + * Then I took an arrow in the knee. + * + * @arg ac the arguments count + * @arg av the arguments values + * @arg args the parsed args pointer + * @return if it was successfully parsed + */ + +int parse_args(int ac, char **av, args_t *args) +{ + /* initialize args */ + static char name[9]; + *args = (args_t){ + .outfile = "addin.g1a", .iconfile = NULL, + .name = name, .intname = "@ADDIN", + .version = {0, 0, 0}, .date = time(NULL)}; + + /* define options */ + const char *short_options = "hvo:i:n:"; + struct option long_options[] = { + {"help", no_argument, NULL, 'h'}, + {"version", required_argument, NULL, 'V'}, + {"internal", required_argument, NULL, 'I'}, + {"date", required_argument, NULL, 'D'}, + {NULL, 0, NULL, 0}, + }; + + /* get all options */ + int c; opterr = 0; + int help = 0; + const char *s_ver = NULL, *s_date = NULL; + while ((c = getopt_long(ac, av, short_options, long_options, NULL)) != -1) + switch (c) { + /* help, version */ + case 'h': help = 1; break; + case 'v': puts(version_message); return (0); break; + + /* general options */ + case 'o': args->outfile = optarg; break; + case 'i': args->iconfile = optarg; break; + + /* build options */ + case 'n': args->name = optarg; break; + case 'V': s_ver = optarg; break; + case 'I': args->intname = optarg; break; + case 'D': s_date = optarg; break; + + /* in case of error */ + case '?': switch (optopt) { + case 'o': err("-o: expected an argument"); break; + case 'i': err("-i: expected an argument"); break; + case 'n': err("-n: expected an argument"); break; + case 'V': err("--version: expected an argument"); break; + case 'I': err("--internal: expected an argument"); break; + case 'D': err("--date: expected an argument"); break; + } break; + } + + /* get non-option arguments (subcommand and parameters) */ + int pc = ac - optind; char **pv = &av[optind]; + if (pc != 1) help = 1; + args->infile = pv[0]; + + /* put help */ + if (help) { puts(help_message); return (0); } + + /* get name */ + const char *p = strrchr(pv[0], '/'); + if (!p) p = pv[0]; + /* TODO: MS-Windows */ + const char *e = strrchr(p, '.'); + name[0] = 0; + for (int i = 0; p < e && i < 8; p++) { + if (isupper(*p)) { + name[i++] = *p; + name[i] = 0; + } + } if (!name[0]) + strcpy(name, "ADDIN"); + + /* check internal name */ + if (args->intname[0] != '@') { + err("internal name should start with an '@'"); + return (0); + } for (int i = 1; args->intname[i]; i++) { + if (i >= 8) { + err("internal name should be up to 8 characters only!"); + return (0); + } + + if (!isupper(args->intname[i])) { + err("internal name should be an '@' then up to seven uppercase " + "letters!"); + return (0); + } + } + + /* get version */ + if (s_ver) { + if (g1m_check_version(s_ver)) { + err("version string '%s' does not have " + "expected format 'MM.mm.ffff'", s_ver); + return (0); + } + + /* decode! */ + g1m_decode_version(s_ver, &args->version); + } + + /* get date */ + if (s_date) { + if (g1m_check_date(s_date)) { + err("date string '%s' does not have " + "expected format 'yyyy.MMdd.hhmm'", s_date); + return (0); + } + + /* decode! */ + g1m_decode_date(s_date, &args->date); + } + + /* everything went well */ + return (1); +} diff --git a/src/g1a-wrapper/icon.c b/src/g1a-wrapper/icon.c new file mode 100644 index 0000000..0b7dc60 --- /dev/null +++ b/src/g1a-wrapper/icon.c @@ -0,0 +1,94 @@ +/* ***************************************************************************** + * g1a-wrapper/icon.c -- g1a-wrapper icon decoding function. + * Copyright (C) 2016-2017 Thomas "Cakeisalie5" Touhey + * + * This file is part of p7utils. + * p7utils is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2.0 of the License, + * or (at your option) any later version. + * + * p7utils is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with p7utils; if not, see . + * ************************************************************************** */ +#include "main.h" +#include +#include + +/** + * open_icon: + * Open the icon. + * + * @arg path the image path. + * @arg icon the image. + */ + +int open_icon(const char *path, uint32_t **icon) +{ + int ret = 1; + Image *images = NULL, *image = NULL; + ImageInfo *info = NULL; + ExceptionInfo *exception = NULL; + + /* initialize */ + MagickCoreGenesis("g1a-wrapper", MagickTrue); + + /* read source image and image info */ + exception = AcquireExceptionInfo(); + info = CloneImageInfo((ImageInfo*)NULL); + strcpy(info->filename, path); + images = ReadImage(info, exception); + if (!images) { + err("unable to open the icon: %s", exception->reason); + goto fail; + } + + /* get the first frame */ + image = RemoveFirstImageFromList(&images); + if (!image) goto fail; + + /* check the dimensions */ + unsigned int width = image->columns, height = image->rows; + if (width != 30 || height < 17 || height > 19) { + err("icon must be between 30x17 and 30x19 pixels, is %ux%u", + width, height); + goto fail; + } + + /* get the pixels */ + const Quantum *pixels = GetVirtualPixels(image, 0, 0, width, height, + exception); + if (!pixels) { + err("unable to access icon pixels."); + goto fail; + } + + /* skip the first line */ + if (height == 19) pixels += width * 4; + + /* get the pixels */ + for (size_t y = 0; y < 17; y++) for (size_t x = 0; x < 30; x++) { + /* get the total */ + float r = *pixels++; + float g = *pixels++; + float b = *pixels++; + pixels++; + + /* check the total */ + icon[y][x] = (r + g + b > 0x198) ? 0xFFFFFF : 0x000000; + } + + ret = 0; +fail: + if (info) DestroyImageInfo(info); + if (exception) DestroyExceptionInfo(exception); + if (image) DestroyImage(image); + if (images) DestroyImage(images); + MagickCoreTerminus(); + return (ret); +} diff --git a/src/g1a-wrapper/main.c b/src/g1a-wrapper/main.c new file mode 100644 index 0000000..db5a07e --- /dev/null +++ b/src/g1a-wrapper/main.c @@ -0,0 +1,100 @@ +/* ***************************************************************************** + * g1a-wrapper/main.c -- g1a-wrapper main source. + * Copyright (C) 2016-2017 Thomas "Cakeisalie5" Touhey + * + * This file is part of p7utils. + * p7utils is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2.0 of the License, + * or (at your option) any later version. + * + * p7utils is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with p7utils; if not, see . + * ************************************************************************** */ +#include "main.h" +#include +#include +#include +#include + +/* ************************************************************************** */ +/* Main function */ +/* ************************************************************************** */ +/** + * main: + * User entry point of the program. + * + * @arg ac arguments count. + * @arg av arguments values. + * @return return code (0 if ok) + */ + +int main(int ac, char **av) +{ + int ret = 1; FILE *in = NULL; + g1m_handle_t *handle = NULL; + + /* Parse args */ + args_t args; + if (!parse_args(ac, av, &args)) + return (0); + + /* get file length */ + in = fopen(args.infile, "r"); + if (!in) { + err("couldn't open input file: %s", strerror(errno)); + goto fail; + } + if (fseek(in, 0L, SEEK_END) < 0) { + err("couldn't seek in file."); + goto fail; + } + long off = ftell(in); + if (off < 0L) + goto fail; + size_t sz = (off < 0) ? SIZE_MAX : (size_t)off; + if (fseek(in, 0L, SEEK_SET)) + goto fail; + + /* make the handle */ + int err = g1m_make_addin(&handle, g1m_platform_fx, sz, + args.name, args.intname, &args.version, &args.date); + if (err) { + err("couldn't make libg1m handle: %s", g1m_strerror(err)); + goto fail; + } + + /* decode the icon */ + open_icon(args.iconfile, handle->icon_unsel); + + /* copy the file content */ + unsigned char *p = handle->content; + while (sz) { + size_t rd = fread(p, 1, sz, in); + if (!rd) { + err("couldn't read binary data: %s", strerror(errno)); + goto fail; + } + sz -= rd; p += rd; + } + + /* encode */ + err = g1m_write(handle, args.outfile); + if (err) { + err("couldn't write to output file: %s", + err == g1m_error_nostream ? strerror(errno) : g1m_strerror(err)); + goto fail; + } + + /* we're good */ + ret = 0; +fail: + if (in) fclose(in); + if (handle) g1m_free(handle); + return (ret); +} diff --git a/src/g1a-wrapper/main.h b/src/g1a-wrapper/main.h new file mode 100644 index 0000000..9593736 --- /dev/null +++ b/src/g1a-wrapper/main.h @@ -0,0 +1,50 @@ +/* ***************************************************************************** + * g1a-wrapper/main.h -- g1a-wrapper main header. + * Copyright (C) 2017 Thomas "Cakeisalie5" Touhey + * + * This file is part of p7utils. + * p7utils is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2.0 of the License, + * or (at your option) any later version. + * + * p7utils is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with p7utils; if not, see . + * ************************************************************************** */ +#ifndef MAIN_H +# define MAIN_H +# define Q(x) #x +# define QUOTE(x) Q(x) +# include +# include +# define warn(M, ...) \ + fprintf(stderr, "g1a-wrapper: warning: " M "\n", ##__VA_ARGS__) +# define err(M, ...) \ + fprintf(stderr, "g1a-wrapper: error: " M "\n", ##__VA_ARGS__) + +/* Arguments */ +typedef struct { + /* general options */ + const char *infile; /* path to the input file */ + const char *outfile; /* default: addin.g1a */ + const char *iconfile; /* icon file, default is blank */ + + /* build options */ + const char *name; /* default: truncated output filename */ + const char *intname; /* default: @ADDIN */ + g1m_version_t version; /* default: 00.00.0000 */ + time_t date; /* default: current time */ +} args_t; + +/* Command-line arguments parsing function */ +extern int parse_args(int ac, char **av, args_t *args); + +/* Icon decoding function */ +extern int open_icon(const char *path, uint32_t **icon); + +#endif /* MAIN_H */ diff --git a/src/g1a-wrapper/vars.mk b/src/g1a-wrapper/vars.mk new file mode 100644 index 0000000..a1a18cd --- /dev/null +++ b/src/g1a-wrapper/vars.mk @@ -0,0 +1,4 @@ +#!/usr/bin/make -f +disable: +libs: + @echo libg1m MagickCore