From 1cfa9d733bdaa9f6373fe69ad2d615bf06305a73 Mon Sep 17 00:00:00 2001 From: "Thomas \"Cakeisalie5\" Touhey" Date: Sun, 30 Oct 2016 20:18:15 +0100 Subject: [PATCH] Initial commit --- .gitignore | 4 + Makefile | 206 ++++++++++++++++++++++++++++++ Makefile.msg | 66 ++++++++++ Makefile.vars | 118 +++++++++++++++++ README.md | 0 configure | 140 ++++++++++++++++++++ doc/libg1m.3.txt | 25 ++++ include/libg1m.h | 59 +++++++++ include/libg1m/compiler.h | 18 +++ include/libg1m/internals.h | 30 +++++ include/libg1m/internals/format.h | 155 ++++++++++++++++++++++ include/libg1m/internals/log.h | 59 +++++++++ src/handle.c | 85 ++++++++++++ src/log.c | 120 +++++++++++++++++ src/parse.c | 127 ++++++++++++++++++ src/strerror.c | 37 ++++++ src/version.c | 57 +++++++++ 17 files changed, 1306 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 Makefile.msg create mode 100644 Makefile.vars create mode 100644 README.md create mode 100755 configure create mode 100644 doc/libg1m.3.txt create mode 100644 include/libg1m.h create mode 100644 include/libg1m/compiler.h create mode 100644 include/libg1m/internals.h create mode 100644 include/libg1m/internals/format.h create mode 100644 include/libg1m/internals/log.h create mode 100644 src/handle.c create mode 100644 src/log.c create mode 100644 src/parse.c create mode 100644 src/strerror.c create mode 100644 src/version.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..636c419 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/Makefile.cfg +/obj +/libg1m.so* +/man diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..4aa19dd --- /dev/null +++ b/Makefile @@ -0,0 +1,206 @@ +#!/usr/bin/make -f +#******************************************************************************# +# Include variables and message subsystem # +#******************************************************************************# +include Makefile.vars +include Makefile.msg + +#******************************************************************************# +# General targets # +#******************************************************************************# +# Build everything. +all: all-lib all-doc + +# Mostly clean everything. (remove everything but the end results) +mostlyclean: mostlyclean-lib mostlyclean-doc +mclean: mostlyclean + +# Clean everything. +clean: clean-lib clean-doc +fclean: clean + +# Remake everything. (clean and build) +re: clean all + +# Install everything. +install: install-lib $(if $(INSTALL_MANPAGES),install-doc) + +# Uninstall everything. (EXPERIMENTAL) +uninstall: uninstall-lib uninstall-doc + +# Reinstall everything. (EXPERIMENTAL) +reinstall: uninstall install + +.PHONY: all mostlyclean mclean clean fclean re install uninstall reinstall +#******************************************************************************# +# Configuration (version) checking dependencies # +#******************************************************************************# +# Define the dependencies. + CHECKCFG := $(if $(shell test -f Makefile.cfg || echo y),check-config, \ + $(if $(shell [ "$(VERSION)" = "$(CONFIG_VERSION)" ] || echo y), \ + check-config-version)) + +# Define the rules. + check-config: + @echo -e "\033[1;31mNo configuration file found!" + @echo -e "You should configure before re-running this target.\033[0m" + @false + check-config-version: + @echo -e "\033[1;31mConfiguration version is incorrect!" + @echo -e "You should re-configure before re-running this target.\033[0m" + @false + +.PHONY: check-config check-config-version +#******************************************************************************# +# Information getting from the Makefile variables # +#******************************************************************************# +# Get the project name. + getname: + @echo lib$(NAME) + +# Get the project author. + getauthor: + @echo $(AUTHOR) + +# Get the project author email. + getmail: + @echo $(AUTHOR_MAIL) + +# Get the project version. + getversion: + @echo $(VERSION) + +.PHONY: getname getauthor getmail getversion +#******************************************************************************# +# Library-specific targets # +#******************************************************************************# +# Make the library. +all-lib: $(CHECKCFG) lib$(NAME).so + +# Make the objects directory. + $(OBJDIR): + $(call bcmd,mkdir,$@,$(MD) $@) + +# Make an object out of a source file. + $(OBJDIR)/%.o: $(SRCDIR)/%.c | $(OBJDIR) + $(call bcmd,cc,$@,$(CC) -c -o $@ $< $(CFLAGS)) + +# Make the (shared) library. + lib$(NAME).so: $(SRC:%=$(OBJDIR)/%.o) + $(call bcmd,ld,$@,$(LD) -o $@ $^ $(LDFLAGS)) + +# Remove the objects directory. +mostlyclean-lib: + $(call rmsg,Removing the objects directory.) + $(call rcmd,$(OBJDIR),$(RM) -r $(OBJDIR)) +mclean-lib: mostlyclean-lib + +# Clean and remove the built library +clean-lib: mclean-lib + $(call rmsg,Removing the built library.) + $(call rcmd,lib$(NAME).so,$(RM) lib$(NAME).so) + +# Remake the library +re-lib: clean-lib all-lib + +# Install the library and development files. +install-lib: $(CHECKCFG) lib$(NAME).so + $(call imsg,Installing the library.) + $(call qcmd,$(INST) -m 755 -d "$(ILIBDIR)") + $(call icmd,lib$(NAME).so.$(VERSION_MAJOR),\ + $(INST) -m 755 lib$(NAME).so "$(ILIBDIR)/lib$(NAME).so.$(VERSION_MAJOR)") + + $(if $(INSTALL_DEVEL),\ + $(call imsg,Linking lib$(NAME).so to lib$(NAME).so.$(VERSION_MAJOR).)) + $(if $(INSTALL_DEVEL),\ + $(call qcmd,$(LN) lib$(NAME).so.$(VERSION_MAJOR) \ + "$(ILIBDIR)/lib$(NAME).so")) + + $(if $(INSTALL_DEVEL),\ + $(call imsg,Installing development files.)) + $(if $(INSTALL_DEVEL),\ + $(call qcmd,$(INST) -m 755 -d $(patsubst %,"$(IINCDIR)/%",\ + $(sort $(dir $(INCPUB)))))) + $(if $(INSTALL_DEVEL),\ + $(foreach i,$(INCPUB),$(call icmd,$(i).h,\ + $(INST) -m 644 $(INCDIR)/$(i).h "$(IINCDIR)/$(i).h"$(\n)))) + +# Uninstall the library and development files. (EXPERIMENTAL) +uninstall-lib: $(CHECKCFG) + $(call rmsg,Uninstalling the library.) + $(call rcmd,lib$(NAME).a) + $(call rcmd,lib$(NAME).so.*) + $(call qcmd,$(RM) "$(ILIBDIR)/lib$(NAME).a" "$(ILIBDIR)/lib$(NAME).so"*) + $(call rcmd,include/lib$(NAME).h) + $(call rcmd,include/lib$(NAME)) + $(call qcmd,$(RM) "$(IINCDIR)/lib$(NAME).h") + $(call qcmd,$(RM) -r "$(IINCDIR)/lib$(NAME)") + +.PHONY: all-lib mostlyclean-lib mclean-lib clean-lib re-lib +.PHONY: install-lib uninstall-lib +#******************************************************************************# +# Documentation-related # +#******************************************************************************# +# Make all manpages. +all-doc: $(foreach s,$(MAN_SECTIONS), $(MAN_$(s):%=$(MANDIR)/man$(s)/%.$(s))) + +# Make manpages directories. + $(MAN_SECTIONS:%=$(MANDIR)/man%): + $(call bcmd,mkdir,$@,$(MD) $@) + +# Make a manpage. +define make-manpage-rule + $(MANDIR)/man$1/%.$1: $(DOCDIR)/%.$1.txt | $(MANDIR)/man$1 + $(call bcmd,a2x,$$<,$(A2X) -f manpage -D $$| $$< 2>/dev/null) +endef +$(foreach section, $(MAN_SECTIONS), \ +$(eval $(call make-manpage-rule,$(section)))) + +# Mostly clean (do nothing, really) +mostlyclean-doc: +mclean-doc: mostlyclean-doc + +# Remove all built manpages. +clean-doc: + $(call rmsg,Removing manpages directory.) + $(call rcmd,$(MANDIR),$(RM) -r $(MANDIR)) + +# Remake all manpages. +# (I don't really know why some people would want to do that though) +re-doc: clean-doc all-doc + +# Install a manpages section. +define make-installmansection-rule + install-doc-$1: $(MAN_$1:%=$(MANDIR)/man$1/%.$1) + $(call imsg,Installing manpages section $1.) + $(call qcmd,$(INST) -m 755 -d "$(IMANDIR)/man$1") + $(foreach i,$(MAN_$1),$(call icmd,man$1/$(i).$1)) + $(call qcmd,$(INST) -m 644 -t "$(IMANDIR)/man$1" \ + $(MAN_$1:%=$(MANDIR)/man$1/%.$1)) + $(call qcmd,$(GZIP) $(MAN_$1:%="$(IMANDIR)/man$1/%.$1")) +endef +$(foreach section, $(MAN_SECTIONS), \ +$(eval $(call make-installmansection-rule,$(section)))) + +# Install manpages. +install-doc: $(CHECKCFG) $(MAN_SECTIONS:%=install-doc-%) + +# Clean a manpages section. +define make-uninstall-doc-rule + uninstall-doc-$1: + $(call rmsg,Uninstalling manpages section $1.) + $(call rcmd,man$1/lib$(NAME).$1) + $(call rcmd,man$1/$(NAME)_*.$1) + $(call qcmd,$(RM) "$(IMANDIR)/man$1/lib$(NAME).$1"* \ + "$(IMANDIR)/man$1/$(NAME)_"*".$1"*) +endef +$(foreach sec,$(MAN_SECTIONS), \ +$(eval $(call make-uninstall-doc-rule,$(sec)))) + +# Uninstall manpages +uninstall-doc: $(CHECKCFG) $(MAN_SECTIONS:%=uninstall-doc-%) + +.PHONY: all-doc mostlyclean-doc mclean-doc clean-doc re-doc +.PHONY: install-doc uninstall-doc +.PHONY: $(foreach s,$(MAN_SECTIONS),install-doc-$(s) uninstall-doc-$(s)) +# End of file. diff --git a/Makefile.msg b/Makefile.msg new file mode 100644 index 0000000..3e75542 --- /dev/null +++ b/Makefile.msg @@ -0,0 +1,66 @@ +#!/usr/bin/make -f +# The Makefile message subsystem. +# For nice logs. 5 dollars per log only. +#******************************************************************************# +# Colors and misc # +#******************************************************************************# +# Used colors ANSI modifiers escape codes + color_green := 32 + color_red := 31 + color_yellow := 33 + +# Newline - comes handy in some situations +define \n + + +endef + +#******************************************************************************# +# General messages # +#******************************************************************************# +# Command message - display basic info about the command, and run it. +define cmd +@$(if $(MAKE_FULL_LOG),,printf "\033[1;""$4""m>\033[0m \033[1m%s\033[0m %s\n" "$1" "$2";) + $(if $(MAKE_FULL_LOG),,@)$3 +endef + +# Quiet command - make it non-quiet if full log is enabled. +define qcmd +$(if $(MAKE_FULL_LOG),,@)$1 +endef + +# Normal message - display it. +define msg +$(if $(MAKE_FULL_LOG),,@printf "\033[1;""$2""m>\033[0m \033[1m%s\033[0m\n" "$1") +endef + +#******************************************************************************# +# Commands # +#******************************************************************************# +# Build command +define bcmd +$(call cmd,$1,$2,$3,$(color_green)) +endef + +# Remove command +define rcmd +$(call cmd,rm,$1,$2,$(color_red)) +endef + +# Install command +define icmd +$(call cmd,install,$1,$2,$(color_yellow)) +endef + +#******************************************************************************# +# Messages # +#******************************************************************************# +# Remove message +define rmsg +$(call msg,$1,$(color_red)) +endef + +# Install message +define imsg +$(call msg,$1,$(color_yellow)) +endef diff --git a/Makefile.vars b/Makefile.vars new file mode 100644 index 0000000..af9d3a5 --- /dev/null +++ b/Makefile.vars @@ -0,0 +1,118 @@ +#!/usr/bin/make -f +#******************************************************************************# +# Include configuration # +#******************************************************************************# +-include Makefile.cfg + +#******************************************************************************# +# Project main information # +#******************************************************************************# +# Project name + NAME := g1m + +# Author information + AUTHOR := Thomas \"Cakeisalie5\" Touhey + AUTHOR_MAIL := thomas@touhey.fr + +# Project license + LICENSE := GPLv2 + +# Project version + VERSION_MAJOR := 0 + VERSION_MINOR := 1 + INDEV := yes + VERSION := $(VERSION_MAJOR).$(VERSION_MINOR)$(if $(INDEV),-indev) + +#******************************************************************************# +# Project directories # +#******************************************************************************# +# Headers directory - where all the headers are. + INCDIR := ./include + +# Sources directory - where all the sources are. + SRCDIR := ./src + +# Objects directory - where the objects will be put. + OBJDIR := ./obj + +# Documentation directory - where the asciidoc sources for the manpages are. + DOCDIR := ./doc + +# Manpages directory - where the manpages will be put. + MANDIR := ./man + +#******************************************************************************# +# Binary utilities # +#******************************************************************************# +# Compiler + CC := gcc +# - Check flags (warnings) + CHKFLAGS := -Wall -Wextra -Wno-attributes +# - For random manipulations (profiling, ...) + CMOREFLAGS := +# - All C compiling flags + CFLAGS := -I $(INCDIR) $(CHKFLAGS) -std=gnu11 -fPIC -O2 \ + -fstack-protector-strong -D LOGLEVEL="ll_$(LOG_LEVEL)" \ + -D_GNU_SOURCE -D_USE_GNU \ + -D AUTHOR="$(AUTHOR)" -D AUTHOR_MAIL="$(AUTHOR_MAIL)" \ + -D LICENSE="$(LICENSE)" -D VERSION="$(VERSION)" \ + $(CMOREFLAGS) + +# Linker + LD := gcc +# - Linker flags + LDFLAGS := -shared -lusb-1.0 \ + -e __lib$(NAME)_version \ + -Wl,-soname,lib$(NAME).so.$(VERSION_MAJOR) \ + -Wl,-z,relro -Wl,-z,combreloc -Wl,-z,defs + +# Directory maker + MD := mkdir -p +# Symbolic link maker + LN := ln -sf +# File remover + RM := rm -f +# Documentation creator + A2X := a2x +# Installer + INST := install +# GZipper + GZIP := gzip -f + +#******************************************************************************# +# Look for sources # +#******************************************************************************# + SRC := $(basename $(shell find $(SRCDIR) -name "*.c" -printf "%P\n")) + +#******************************************************************************# +# Look for public headers (not internals.h or internals/**/*.h # +#******************************************************************************# + INCPUB := $(basename $(shell find $(INCDIR) \ + -name "*.h" -and -not -path "*internals*" -printf "%P\n" | sort)) + +#******************************************************************************# +# Look for manpages # +#******************************************************************************# +# Get the manpages sections and contents + MAN_SECTIONS := +define check-man + MAN_SECTIONS += $1 + MAN_$1 += $2 +endef +$(foreach doc, $(basename $(shell find $(DOCDIR) \ + -maxdepth 1 -mindepth 1 -printf "%P\n" -type f -or -type l -name "*.*.txt")), \ +$(eval $(call check-man,$(patsubst .%,%,$(suffix $(doc))),$(basename $(doc))))) + +# Remove duplicate sections. + MAN_SECTIONS := $(sort $(MAN_SECTIONS)) + +#******************************************************************************# +# Check for DESTDIR (add as prefix to installation root) # +#******************************************************************************# +define add-dest-dir + $1 = $(DESTDIR)$($1) +endef +$(if $(DESTDIR), $(foreach idir,ILIBDIR IINCDIR IMANDIR UDEVDIR, \ +$(eval $(call add-dest-dir,$(idir))))) + +# END OF FILE diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/configure b/configure new file mode 100755 index 0000000..aeece6a --- /dev/null +++ b/configure @@ -0,0 +1,140 @@ +#!/bin/sh +# Defaults +name="$(make -sC $(dirname $0) getname)" +author="$(make -sC $(dirname $0) getauthor)" +complete_author="$author <$(make -sC $(dirname $0) getmail)>" +version="$(make -sC $(dirname $0) getversion)" +# - Platform +platform="$(gcc --print-multiarch)" +platform="$([ "$platform" ] && echo "/$platform")" +# - Basic things +make_full_log= +loglevel=none # none, info, warn, error, fatal +root='' +# - main things +prefix='${root}/usr' +bindir='${prefix}/bin' +libdir='${prefix}/lib'"$platform" +includedir='${prefix}/include'"$platform" +mandir='${prefix}/share/man' +# - misc +install_manpages=yes +install_devel=yes + +# Help +usage() { +cat <&1 1>"$(dirname $0)/Makefile.cfg" + +cat <&3 3>&- +chmod +x "$(dirname $0)/Makefile.cfg" + +# We're done +echo "Configuration loaded, you can make now." diff --git a/doc/libg1m.3.txt b/doc/libg1m.3.txt new file mode 100644 index 0000000..33d55b7 --- /dev/null +++ b/doc/libg1m.3.txt @@ -0,0 +1,25 @@ +LIBG1M(3) +========= +Thomas "Cakeisalie5" Touhey +:Email: thomas@touhey.fr +:man source: libg1m +:man manual: libg1m manual + +NAME +---- +libg1m - manipulation library for casio's standard format + +SYNOPSIS +-------- +[source,c] +---- +#include +---- + +DESCRIPTION +----------- +libg1m enables reading (parsing) and writing of g** files : g1m, g1a. + +SEE ALSO +-------- +*g1m_open(3)* diff --git a/include/libg1m.h b/include/libg1m.h new file mode 100644 index 0000000..edd50d9 --- /dev/null +++ b/include/libg1m.h @@ -0,0 +1,59 @@ +/* ************************************************************************** */ +/* _____ _ */ +/* libg1m.h |_ _|__ _ _| |__ ___ _ _ */ +/* | Project : libg1m | |/ _ \| | | | '_ \ / _ \ | | | */ +/* | | (_) | |_| | | | | __/ |_| | */ +/* By: thomas |_|\___/ \__,_|_| |_|\___|\__, |.fr */ +/* Last updated: 2016/09/03 23:23:56 |___/ */ +/* */ +/* ************************************************************************** */ +#ifndef LIBG1M_H +# define LIBG1M_H +# include +# include +# ifdef __cplusplus +extern "C" { +# endif + +/* ************************************************************************** */ +/* Errors */ +/* ************************************************************************** */ +/* main enumeration */ +typedef enum { + g1m_noerror, + + /* check errno for more info */ + g1m_error_errno, + /* no stream sent */ + g1m_error_nostream, + /* could not read from stream */ + g1m_error_noread, + /* could not seek in stream */ + g1m_error_noseek, + /* magic or control problem */ + g1m_error_magic, + /* memory allocation problem */ + g1m_error_alloc, +} g1m_error_t; + +/* Message getting macro */ +extern const char *g1m_error_strings[]; +#define g1m_strerror(N) g1m_error_strings[N] +#define g1m_geterror(N) g1m_error_strings[N] + +/* ************************************************************************** */ +/* Main types and functions */ +/* ************************************************************************** */ +/* handle */ +struct g1m; +typedef struct g1m g1m_t; + +/* open and close handles */ +int g1m_open(g1m_t **handle, const char *path); +int g1m_fopen(g1m_t **handle, FILE *stream); +void g1m_close(g1m_t *handle); + +# ifdef __cplusplus +} +# endif +#endif /* LIBG1M_H */ diff --git a/include/libg1m/compiler.h b/include/libg1m/compiler.h new file mode 100644 index 0000000..d2bd4e0 --- /dev/null +++ b/include/libg1m/compiler.h @@ -0,0 +1,18 @@ +/* ************************************************************************** */ +/* _____ _ */ +/* libg1m/compiler.h |_ _|__ _ _| |__ ___ _ _ */ +/* | Project : libg1m | |/ _ \| | | | '_ \ / _ \ | | | */ +/* | | (_) | |_| | | | | __/ |_| | */ +/* By: thomas |_|\___/ \__,_|_| |_|\___|\__, |.fr */ +/* Last updated: 2016/09/05 13:22:50 |___/ */ +/* */ +/* ************************************************************************** */ + +#ifndef LIBG1M_COMPILER_H +# define LIBG1M_COMPILER_H + +# ifndef __unused +# define __unused __attribute__((unused)) +# endif + +#endif /* LIBG1M_COMPILER_H */ diff --git a/include/libg1m/internals.h b/include/libg1m/internals.h new file mode 100644 index 0000000..8da9d29 --- /dev/null +++ b/include/libg1m/internals.h @@ -0,0 +1,30 @@ +/* ************************************************************************** */ +/* _____ _ */ +/* internals.h |_ _|__ _ _| |__ ___ _ _ */ +/* | Project : libg1m | |/ _ \| | | | '_ \ / _ \ | | | */ +/* | | (_) | |_| | | | | __/ |_| | */ +/* By: thomas |_|\___/ \__,_|_| |_|\___|\__, |.fr */ +/* Last updated: 2016/09/03 23:24:04 |___/ */ +/* */ +/* ************************************************************************** */ +#ifndef LIBG1M_INTERNALS_H +# define LIBG1M_INTERNALS_H +# include +# include +# include + +/* ************************************************************************** */ +/* Main handle type */ +/* ************************************************************************** */ +/* Handle */ +struct g1m { + FILE *stream; +}; + +/* ************************************************************************** */ +/* Utilities */ +/* ************************************************************************** */ +/* Parsing */ +int g1m_parse(g1m_t *handle); + +#endif /* LIBG1M_INTERNALS_H */ diff --git a/include/libg1m/internals/format.h b/include/libg1m/internals/format.h new file mode 100644 index 0000000..9aba6e2 --- /dev/null +++ b/include/libg1m/internals/format.h @@ -0,0 +1,155 @@ +/* ************************************************************************** */ +/* _____ _ */ +/* libg1m/internals/format.h |_ _|__ _ _| |__ ___ _ _ */ +/* | Project: libg1m | |/ _ \| | | | '_ \ / _ \ | | | */ +/* | | (_) | |_| | | | | __/ |_| | */ +/* By: thomas |_|\___/ \__,_|_| |_|\___|\__, |.fr */ +/* Last updated: 2016/10/30 17:29:42 |___/ */ +/* */ +/* ************************************************************************** */ +#ifndef LIBG1M_INTERNALS_FORMAT_H +# define LIBG1M_INTERNALS_FORMAT_H +# include +# include + +/* Welcome on this new episode of Monster Circus! Today we'll present to you + * something that you might not be able to forget: the G1M format. + * This format is used by CASIO on its calculators for storing add-ins + * (compiled programs), MCS (main memory) saves, and + * e-acts (simple documents). */ + +enum g1m_type { + g1m_type_addin = 0xF3, + g1m_type_mcs = 0x31, + g1m_type_eact = 0x49 +}; + +/* It all starts with a header, called Standard Header by Simon Lothar. + * This Standard Header contains the total filesize, the G1M type (add-in, + * MCS, e-acts), some data that will be useful for the MCS type, and some + * magic and control bytes. + * + * Multi-bytes integers are Big Endian for all of the headers. + * The LSB is the Least Significant Byte: once adapted to the host endianness, + * it can simply be obtained bitwise-AND-ing with 0xff. */ + +struct standard_header { + /* the file identifier, should be "USBPower". */ + uint8_t main_id[8]; + + /* our filetype! */ + uint8_t type; + + /* magic numbers: {0x00, 0x10, 0x00, 0x10, 0x00} */ + uint8_t magic[5]; + + /* first control byte: filesize LSB + 0x41 */ + uint8_t control; + + /* magic byte: 0x01 */ + uint8_t magic2; + + /* total filesize */ + uint32_t filesize; + + /* second control byte: filesize LSB + 0xb8 */ + uint8_t control2; + + /* 4-bytes alignment */ + uint8_t align[9] __unused; + + /* number of objects contained (useful for MCS filetype) */ + uint16_t number; +}; + +/* After the Standard Header is read and the G1M type is read, we have parts, + * each with their own subheaders. + * + * Let's start with the simplest type: the add-in part. Well, it will always + * have one part: the add-in part, with the G1A subheader. */ + +struct g1a_subheader { + /* the internal name, of format "@APPNAME". + * useful for add-ins calling themselves... I guess? */ + uint8_t internal_name[8]; + + /* the number of estrips (I don't know yet) */ + uint8_t estrips_count; + + /* the add-in version, of format "01.23.4567" + * the "01.23" will be displayed in SYSTEM > VERSION */ + uint8_t version[10]; + + /* the add-in creation type, of format "YYYY.MMDD.HHMM" */ + uint8_t creation_date[14]; + + /* 30x17 pixel menu icon bitmap */ + uint8_t icon[68]; + + /* program title */ + uint8_t title[8]; + + /* and the filesize of the part! */ + uint32_t filesize; +}; + +/* Then the G1A file will just contain the add-in code and stop. + * But hey, that was the easiest G1M type. Let's dive through the feared + * type: the MCS one. + * + * So now we are not restricted to only one part. Remember the `number` field + * of the Standard Header? Well, it's the number of parts there is. + * And each part have a header, so let's describe it here. + */ + +struct mcs_subheader { + /* main ID ("PROGRAM", zero padded) */ + uint8_t intname[16]; + + /* subitem count: haha, read what follows! */ + uint32_t subcount; +}; + +/* Because yes, an part of an MCS G1M file has subparts! + * Each one of them represent a single file in the MCS. + * According to their type, they are in different directories: */ + +enum mcs_dirtype { + mcs_dtype_program = 0x1, + mcs_dtype_list = 0x5, + mcs_dtype_picture = 0x7, + mcs_dtype_capture = 0xa +}; + +/* And they also have a directory name, which is less precise. Why? + * We don't know! That's the main thing with reverse engineering. + * + * For basic programs, it is: */ +#define DIRNAME_PROGRAM "system" +/* For most alpha-mem elements (list, matrixes, pictures): */ +#define DIRNAME_ALPHAMEM "main" +/* And for captures: */ +#define DIRNAME_CAPTURE "@REV2" + +/* Now all of this is said, each part has this header: */ + +struct mcs_partheader { + /* the directory name (zero-padded).*/ + uint8_t dirname[8]; + + /* the file name (item name) */ + uint8_t filename[8]; + + /* the directory type. */ + uint8_t dirtype; + + /* the size of the part data, without the part header. */ + uint16_t datalength; + + /* some alignment */ + uint8_t align[3] __unused; +}; + +/* And I don't know anything about the E-Act type, sorry. */ + +#endif /* LIBG1M_INTERNALS_FORMAT_H */ diff --git a/include/libg1m/internals/log.h b/include/libg1m/internals/log.h new file mode 100644 index 0000000..5d16d96 --- /dev/null +++ b/include/libg1m/internals/log.h @@ -0,0 +1,59 @@ +/* ************************************************************************** */ +/* _____ _ */ +/* libg1m/internals/log.h |_ _|__ _ _| |__ ___ _ _ */ +/* | Project : libg1m | |/ _ \| | | | '_ \ / _ \ | | | */ +/* | | (_) | |_| | | | | __/ |_| | */ +/* By: thomas |_|\___/ \__,_|_| |_|\___|\__, |.fr */ +/* Last updated: 2016/09/05 12:58:52 |___/ */ +/* */ +/* ************************************************************************** */ + +#ifndef LIBG1M_INTERNALS_LOG_H +# define LIBG1M_INTERNALS_LOG_H + +/* ************************************************************************** */ +/* Log utility */ +/* ************************************************************************** */ +/* Log level */ +# define ll_info 0 +# define ll_warn 1 +# define ll_error 2 +# define ll_fatal 3 +# define ll_none 4 + +/* Macros */ +# define log(P, S, ...) fprintf(stderr, P "%s: " S "\n", __FUNCTION__, ##__VA_ARGS__) +# define logm(S, M, N) g1m_log_mem(S, M, N) + +# if LOGLEVEL <= ll_info +# define log_info(S, ...) log("[libg1m info] ", S, ##__VA_ARGS__) +# define logm_info(M, N) logm("[libg1m info] ", M, N) +# else +# define log_info(S, ...) +# define logm_info(M, N) +# endif + +# if LOGLEVEL <= ll_warn +# define log_warn(S, ...) log("[libg1m warn] ", S, ##__VA_ARGS__) +# define logm_warn(M, N) logm("[libg1m warn] ", M, N) +# else +# define log_warn(S, ...) +# define logm_warn(M, N) +# endif + +# if LOGLEVEL <= ll_error +# define log_error(S, ...) log("[libg1m error] ", S, ##__VA_ARGS__) +# else +# define log_error(S, ...) +# endif + +# if LOGLEVEL <= ll_fatal +# define log_fatal(S, ...) log("[libg1m fatal] ", S, ##__VA_ARGS__) +# else +# define log_fatal(S, ...) +# endif + +/* Functions prototypes */ +void g1m_log_mem(const char *prefix, void *m, size_t n); + +#endif /* LIBG1M_INTERNALS_LOG_H */ diff --git a/src/handle.c b/src/handle.c new file mode 100644 index 0000000..e2005ab --- /dev/null +++ b/src/handle.c @@ -0,0 +1,85 @@ +/* ************************************************************************** */ +/* _____ _ */ +/* handle.c |_ _|__ _ _| |__ ___ _ _ */ +/* | Project : libg1m | |/ _ \| | | | '_ \ / _ \ | | | */ +/* | | (_) | |_| | | | | __/ |_| | */ +/* By: thomas |_|\___/ \__,_|_| |_|\___|\__, |.fr */ +/* Last updated: 2016/09/04 00:09:47 |___/ */ +/* */ +/* ************************************************************************** */ +#include +#include +#include + +/** + * g1m_fopen: + * Open handle using FILE* pointer. + * + * @arg handle the handle to create + * @arg stream the stream + * @return the error code (0 if ok) + */ + +int g1m_fopen(g1m_t **handle, FILE *stream) +{ + int err; + + /* check stream */ + if (!stream) return (g1m_error_nostream); + if (!__freadable(stream)) return (g1m_error_noread); + + /* seek to beginning */ + if (fseek(stream, 0L, SEEK_SET)) return (g1m_error_noseek); + + /* create the handle */ + *handle = malloc(sizeof(g1m_t)); + if (!*handle) return (g1m_error_alloc); + (*handle)->stream = stream; + + /* fill it by parsing opened file */ + if ((err = g1m_parse(*handle))) { + free(*handle); + *handle = NULL; + return (err); + } + + /* everything ok */ + return (0); +} + +/** + * g1m_open: + * Open handle using path. + * + * @arg handle the handle to create. + * @arg path the path of the file to open. + * @return the error code (0 if ok). + */ + +int g1m_open(g1m_t **handle, const char *path) +{ + /* open stream */ + FILE *f = fopen(path, "r+"); + if (!f) return (g1m_error_errno); + + /* open using `g1m_fopen` */ + int err; + if ((err = g1m_fopen(handle, f))) + fclose(f); + return (err); +} + +/** + * g1m_close: + * Close handle using path. + * + * @arg handle the handle to close. + * @return the error code (0 if ok). + */ + +void g1m_close(g1m_t *handle) +{ + /* close stream */ + fclose(handle->stream); + free(handle); +} diff --git a/src/log.c b/src/log.c new file mode 100644 index 0000000..c182bd0 --- /dev/null +++ b/src/log.c @@ -0,0 +1,120 @@ +/* ************************************************************************** */ +/* _____ _ */ +/* log.c |_ _|__ _ _| |__ ___ _ _ */ +/* | Project : libg1m | |/ _ \| | | | '_ \ / _ \ | | | */ +/* | | (_) | |_| | | | | __/ |_| | */ +/* By: thomas |_|\___/ \__,_|_| |_|\___|\__, |.fr */ +/* Last updated: 2016/08/19 12:04:33 |___/ */ +/* */ +/* ************************************************************************** */ +#include +#include +#include +#define min(A, B) ((A) < (B) ? (A) : (B)) + +/** + * putascii: + * Put a number in ASCII-hex, in a n-dimensionned field. + * + * @arg p pointer where to put ASCII number + * @arg i ASCII number + * @arg n the size of the field + */ + +static void putascii(unsigned char *p, unsigned int i, int n) +{ + /* goto end of the field */ + p += (n - 1); + /* for each digit */ + while (n--) { + /* get end digit from this point */ + int j = i % 16; + /* put it in ASCII-hex */ + *p-- = j >= 10 ? j - 10 + 'A' : j + '0'; + /* then go to digit that's left */ + i /= 16; + } +} + +/** + * log_mem_hex: + * Prints the octal interpretation of a max of two octets. + * + * @arg s the string where to put it + * @arg m the memory zone to print + * @arg n the size of the memory zone + */ + +static void log_mem_hex(char *s, unsigned char *m, size_t n) +{ + size_t l = 0; + while (l < 16) { + /* put the hex number */ + if (n) putascii((unsigned char*)s, *m++, 2), s += 2; + else *s++ = ' ', *s++ = ' '; + + /* decrement size of the memory zone to go */ + n -= !!n; + + /* go to next character if s is at the ending of a group */ + if (l++ % 2) s++; + } +} + +/** + * log_mem_asc: + * Prints the ascii interpretation of a max of two octets. + * + * @arg s the string where to put it + * @arg m the memory zone to print + * @arg n the size of the memory zone + */ + +static void log_mem_asc(char *s, unsigned char *m, size_t n) +{ + size_t l = 0; + /* for each byte */ + while (n-- && l++ < 16) { + /* put the character (or a dot if non printable) */ + if (isprint(*m++)) *s++ = *((char*)m - 1); + else *s++ = '.'; + } + /* put the line ending */ + *s++ = '\n', *s = '\0'; +} + +/** + * g1m_log_mem: + * Print memory zone. + * + * @arg prefx the line prefix + * @arg m the memory zone to print + * @arg n the size of the memory zone + */ + +void g1m_log_mem(const char *prefx, void *m, size_t n) +{ + /* if nothing, print it directly */ + if (!n) fprintf(stderr, "%s(nothing)\n", prefx); + + /* prepare line buffer */ + unsigned int lineoff = strlen(prefx); + char linebuf[strlen(prefx) + 58]; + memcpy(linebuf, prefx, lineoff); + /* - for spaces - */ + memcpy(&linebuf[lineoff], "0000 0000 0000 0000 0000 0000 0000 0000 ", 40); + + /* then loop-loop-loop-loop-loop */ + unsigned char *p = m; + while (n > 0) { + /* fill in ascii-hex part */ + log_mem_hex(&linebuf[lineoff], p, n); + /* fill in ascii part */ + log_mem_asc(&linebuf[lineoff + 40], p, n); + /* then print line */ + fputs(linebuf, stderr); + /* and increment pointer */ + p += 16; + n -= min(16, n); + } +} diff --git a/src/parse.c b/src/parse.c new file mode 100644 index 0000000..64d656a --- /dev/null +++ b/src/parse.c @@ -0,0 +1,127 @@ +/* ************************************************************************** */ +/* _____ _ */ +/* parse.c |_ _|__ _ _| |__ ___ _ _ */ +/* | Project: libg1m | |/ _ \| | | | '_ \ / _ \ | | | */ +/* | | (_) | |_| | | | | __/ |_| | */ +/* By: thomas |_|\___/ \__,_|_| |_|\___|\__, |.fr */ +/* Last updated: 2016/10/30 18:24:25 |___/ */ +/* */ +/* ************************************************************************** */ +#include +#include +#include + +/** + * g1m_parse_addin: + * We have passed the Standard Header and it's a b... add-in! Let's parse it! + * + * @arg handle the handle. + * @return the error code (0 if ok). + */ + +static int g1m_parse_addin(g1m_t *handle) +{ + /* get the subheader */ + struct g1a_subheader hd; + size_t rc = fread(&hd, sizeof(struct g1a_subheader), 1, handle->stream); + if (rc < sizeof(struct g1a_subheader)) return (g1m_error_magic); + + /* correct subheader endianess */ + hd.filesize = be32toh(hd.filesize); + + /* no errors */ + return (0); +} + +/** + * g1m_parse_mcs: + * We have passed the Standard Header and it's a g... MCS! Let's parse it! + * + * @arg handle the handle. + * @arg num number of sizes. + * @return the error code (0 if ok). + */ + +static int g1m_parse_mcs(g1m_t *handle, uint_fast16_t num) +{ + /* read all of the parts */ + for (uint_fast16_t i = 0; i < num; i++) { + /* get the subheader */ + struct mcs_subheader hd; + size_t rc = fread(&hd, sizeof(struct mcs_subheader), 1, handle->stream); + if (rc < sizeof(struct mcs_subheader)) return (g1m_error_magic); + + /* correct endianess */ + hd.subcount = be32toh(hd.subcount); + + /* foreach subpart */ + for (uint_fast32_t j = 0; j < num; j++) { + /* get the part header */ + struct mcs_partheader phd; + rc = fread(&phd, sizeof(struct mcs_partheader), 1, handle->stream); + if (rc < sizeof(struct mcs_partheader)) return (g1m_error_magic); + + /* correct endianess */ + phd.datalength = be32toh(phd.datalength); + + /* and read that much data */ + if (fseek(handle->stream, phd.datalength, SEEK_CUR)) + return (g1m_error_magic); + } + } + + /* no error */ + return (0); +} + +/** + * g1m_parse: + * Parse a G1M file. + * + * Read the standard header, correct endianness, check magic numbers, + * then read subparts according to the G1M type. + * + * @arg handle the handle. + * @return the error code (0 if ok). + */ + +int g1m_parse(g1m_t *handle) +{ + /* get the standard header */ + struct standard_header hd; + size_t rc = fread(&hd, sizeof(struct standard_header), 1, handle->stream); + if (rc < sizeof(struct standard_header)) return (g1m_error_magic); + + /* correct standard header endianess */ + hd.filesize = be32toh(hd.filesize); + hd.number = be16toh(hd.number); + + /* check standard header magics */ + if (memcmp(hd.magic, (unsigned char[]){0x00, 0x10, 0x00, 0x10, 0x00}, 5)) + return (g1m_error_magic); + if (hd.magic2 != 0x01) + return (g1m_error_magic); + if (hd.control != ((hd.filesize + 0x41) & 0xff)) + return (g1m_error_magic); + if (hd.control2 != ((hd.filesize + 0xb8) & 0xff)) + return (g1m_error_magic); + + /* log some data */ + log_info("Standard header was the following:"); + logm_info(&hd, sizeof(struct standard_header)); + log_info("G1M type was '%8s'", hd.main_id); + log_info("Type was 0x%02x", hd.type); + + /* subparse. */ + switch (hd.type) { + case g1m_type_addin: + return (g1m_parse_addin(handle)); + case g1m_type_mcs: + return (g1m_parse_mcs(handle, hd.number)); + /* TODO: add g1m_type_eact */ + default: break; + } + + /* no error */ + return (0); +} diff --git a/src/strerror.c b/src/strerror.c new file mode 100644 index 0000000..0e9625d --- /dev/null +++ b/src/strerror.c @@ -0,0 +1,37 @@ +/* ************************************************************************** */ +/* _____ _ */ +/* strerror.c |_ _|__ _ _| |__ ___ _ _ */ +/* | Project: libg1m | |/ _ \| | | | '_ \ / _ \ | | | */ +/* | | (_) | |_| | | | | __/ |_| | */ +/* By: thomas |_|\___/ \__,_|_| |_|\___|\__, |.fr */ +/* Last updated: 2016/10/30 20:05:20 |___/ */ +/* */ +/* ************************************************************************** */ +#include + +/** + * g1m_error_strings: + * String descriptions of libg1m errors. + * + * This list MUST evolve whenever the g1m_error_t enumeration + * is updated. Could cause invalid error strings otherwise, + * or segfaults! + */ + +const char *g1m_error_strings[] = { + [g1m_noerror] = + "no error have been encountered", + + [g1m_error_errno] = + "libc error, check errno for more info", + [g1m_error_nostream] = + "sent stream was NULL", + [g1m_error_noread] = + "given stream was not readable", + [g1m_error_noseek] = + "given stream was not seekable", + [g1m_error_magic] = + "magic/control/size problem", + [g1m_error_alloc] = + "could not allocate memory", +}; diff --git a/src/version.c b/src/version.c new file mode 100644 index 0000000..5a98aaf --- /dev/null +++ b/src/version.c @@ -0,0 +1,57 @@ +/* ************************************************************************** */ +/* _____ _ */ +/* version.c |_ _|__ _ _| |__ ___ _ _ */ +/* | Project: libg1m | |/ _ \| | | | '_ \ / _ \ | | | */ +/* | | (_) | |_| | | | | __/ |_| | */ +/* By: thomas |_|\___/ \__,_|_| |_|\___|\__, |.fr */ +/* Last updated: 2016/10/13 07:33:17 |___/ */ +/* */ +/* ************************************************************************** */ + +#include +#include +#define Q(x) #x +#define QUOTE(x) Q(x) + +/* get loglevel string */ +#if LOGLEVEL == ll_info +# define LLS "info" +#elif LOGLEVEL == ll_warn +# define LLS "warn" +#elif LOGLEVEL == ll_error +# define LLS "error" +#elif LOGLEVEL == ll_fatal +# define LLS "fatal" +#elif LOGLEVEL == ll_none +# define LLS "none" +#else +# define LLS "unknown" +#endif + +/** + * version_message: + * The message that should be displayed when the library is executed. + */ + +static const char version_message[] = +"libg1m v" QUOTE(VERSION) " (licensed under " QUOTE(LICENSE) ")\n" +"Made by " QUOTE(AUTHOR) " <" QUOTE(AUTHOR_MAIL) ">.\n" +"\n" +"Compiled with the '" LLS "' loglevel.\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."; + +/** + * __libg1m_version: + * Display version when the library is executed. + */ + +extern void __libg1m_version(void) + __attribute__((noreturn)); +void __libg1m_version(void) +{ + puts(version_message); + _exit(0); +}