Added Casemul files support.
This commit is contained in:
parent
1b99b96f54
commit
931e14f1fd
|
@ -129,6 +129,9 @@ struct casemul_comp_header {
|
|||
* uint32_t length;
|
||||
* The subheader+data length.
|
||||
*
|
||||
* Actually, it looks like the name length is always 12 and finishes with
|
||||
* "W:g\xA7"; I don't know why, I'll have to investiguate on this. XXX
|
||||
*
|
||||
* Then expect an internal header, part of the subheader, that expresses the
|
||||
* type of it (expected version for all of them is 1.00). */
|
||||
/* ************************************************************************** */
|
||||
|
@ -150,7 +153,7 @@ struct casemul_prog_header {
|
|||
|
||||
struct casemul_pict_header {
|
||||
/* size */
|
||||
uint8_t w, h;
|
||||
uint8_t width, height;
|
||||
|
||||
/* ... aaaaand alignment. (you know, unchecked theory?) */
|
||||
uint8_t _align[2];
|
||||
|
|
|
@ -130,6 +130,7 @@ int g1m_decode_std(g1m_t *handle, const char *path, g1m_buffer_t *buffer,
|
|||
struct standard_header*, g1m_type_t expected_types);
|
||||
int g1m_decode_cas(g1m_t *handle, g1m_buffer_t *buffer,
|
||||
g1m_type_t expected_types);
|
||||
int g1m_decode_casemul(g1m_t *handle, g1m_buffer_t *buffer);
|
||||
|
||||
/* ************************************************************************** */
|
||||
/* "Std"-specific decoding functions */
|
||||
|
|
|
@ -32,12 +32,18 @@
|
|||
# include <libkern/OSByteOrder.h>
|
||||
|
||||
# define be16toh(x) OSSwapBigToHostInt16(x)
|
||||
# define le16toh(x) OSSwapLittleToHostInt16(x)
|
||||
# define be32toh(x) OSSwapBigToHostInt32(x)
|
||||
# define le32toh(x) OSSwapLittleToHostInt32(x)
|
||||
# define be64toh(x) OSSwapBigToHostInt64(x)
|
||||
# define le64toh(x) OSSwapLittleToHostInt64(x)
|
||||
|
||||
# define htobe16(x) OSSwapHostToBigInt16(x)
|
||||
# define htole16(x) OSSwapHostToLittleInt16(x)
|
||||
# define htobe32(x) OSSwapHostToBigInt32(x)
|
||||
# define htole32(x) OSSwapHostToLittleInt32(x)
|
||||
# define htobe64(x) OSSwapHostToBigInt64(x)
|
||||
# define htole64(x) OSSwapHostToLittleInt64(x)
|
||||
|
||||
# elif defined(__OpenBSD__)
|
||||
# include <sys/endian.h>
|
||||
|
@ -48,20 +54,32 @@
|
|||
|
||||
# if BYTE_ORDER == LITTLE_ENDIAN
|
||||
# define be16toh(x) ntohs(x)
|
||||
# define le16toh(x) (x)
|
||||
# define be32toh(x) ntohl(x)
|
||||
# define le32toh(x) (x)
|
||||
# define be64toh(x) ntohll(x)
|
||||
# define le64toh(x) (x)
|
||||
|
||||
# define htobe16(x) htons(x)
|
||||
# define htole16(x) (x)
|
||||
# define htobe32(x) htonl(x)
|
||||
# define htole32(x) (x)
|
||||
# define htobe64(x) htonll(x)
|
||||
# define htole64(x) (x)
|
||||
# else
|
||||
# define be16toh(x) (x)
|
||||
# define le16toh(x) ntohs(x)
|
||||
# define be32toh(x) (x)
|
||||
# define le32toh(x) ntohl(x)
|
||||
# define be64toh(x) (x)
|
||||
# define le64toh(x) ntohll(x)
|
||||
|
||||
# define htobe16(x) (x)
|
||||
# define htole16(x) htons(x)
|
||||
# define htobe32(x) (x)
|
||||
# define htole32(x) htonl(x)
|
||||
# define htobe64(x) (x)
|
||||
# define htole64(x) htonll(x)
|
||||
# endif
|
||||
|
||||
# else
|
||||
|
|
|
@ -29,23 +29,25 @@
|
|||
/* MCS file type */
|
||||
typedef unsigned int g1m_mcsfile_type_t;
|
||||
typedef unsigned int g1m_mcstype_t;
|
||||
# define g1m_mcstype_unknown 0x0000
|
||||
# define g1m_mcstype_program 0x0001
|
||||
# define g1m_mcstype_list 0x0002
|
||||
# define g1m_mcstype_mat 0x0004
|
||||
# define g1m_mcstype_pict 0x0008
|
||||
# define g1m_mcstype_capt 0x0010
|
||||
# define g1m_mcstype_spreadsheet 0x0020
|
||||
# define g1m_mcstype_string 0x0040
|
||||
# define g1m_mcstype_setup 0x0080
|
||||
# define g1m_mcstype_alphamem 0x0100
|
||||
# define g1m_mcstype_vct 0x0200
|
||||
# define g1m_mcstype_variable 0x0400
|
||||
# define g1m_mcstype_unknown 0x0000
|
||||
# define g1m_mcstype_program 0x0001
|
||||
# define g1m_mcstype_list 0x0002
|
||||
# define g1m_mcstype_mat 0x0004
|
||||
# define g1m_mcstype_pict 0x0008
|
||||
# define g1m_mcstype_capt 0x0010
|
||||
# define g1m_mcstype_ssheet 0x0020
|
||||
# define g1m_mcstype_string 0x0040
|
||||
# define g1m_mcstype_setup 0x0080
|
||||
# define g1m_mcstype_alphamem 0x0100
|
||||
# define g1m_mcstype_vct 0x0200
|
||||
# define g1m_mcstype_variable 0x0400
|
||||
|
||||
/* MCS file type aliases */
|
||||
# define g1m_mcstype_picture g1m_mcstype_pict
|
||||
# define g1m_mcstype_capture g1m_mcstype_capt
|
||||
# define g1m_mcstype_vector g1m_mcstype_vct
|
||||
# define g1m_mcstype_matrix g1m_mcstype_mat
|
||||
# define g1m_mcstype_picture g1m_mcstype_pict
|
||||
# define g1m_mcstype_capture g1m_mcstype_capt
|
||||
# define g1m_mcstype_spreadsheet g1m_mcstype_ssheet
|
||||
# define g1m_mcstype_vector g1m_mcstype_vct
|
||||
|
||||
/* Macros to check if the type uses the ID, and to interact with it */
|
||||
# define g1m_mcstype_uses_id(T) ((T) & (\
|
||||
|
|
|
@ -42,7 +42,7 @@ void g1m_bcd_fromdouble(double dbl, g1m_bcd_t *bcd)
|
|||
exp++;
|
||||
dbl /= 10;
|
||||
}
|
||||
while (dbl < 1) {
|
||||
if (dbl > 0) while (dbl < 1) {
|
||||
exp--;
|
||||
dbl *= 10;
|
||||
}
|
||||
|
|
|
@ -43,8 +43,15 @@ int g1m_decode(g1m_t *handle, const char *path, g1m_buffer_t *buffer,
|
|||
if (buf[0] == ':')
|
||||
return (g1m_decode_cas(handle, buffer, expected_types));
|
||||
|
||||
/* identify a Casemul file */
|
||||
READ(&buf[1], 3)
|
||||
log_info("Buffer content is:");
|
||||
logm_info(buf, 4);
|
||||
if (!memcmp(buf, "ACFS", 4))
|
||||
return (g1m_decode_casemul(handle, buffer));
|
||||
|
||||
/* identify a standard header (send a _copy_) */
|
||||
READ(&buf[1], 0x1F)
|
||||
READ(&buf[1], 0x1C)
|
||||
uint8_t altbuf[0x20]; memcpy(altbuf, buf, 0x20);
|
||||
return (g1m_decode_std(handle, path, buffer,
|
||||
(struct standard_header*)altbuf, expected_types));
|
||||
|
|
|
@ -0,0 +1,378 @@
|
|||
/* *****************************************************************************
|
||||
* decode/casemul.c -- decode a Casemul file.
|
||||
* Copyright (C) 2017 Thomas "Cakeisalie5" Touhey <thomas@touhey.fr>
|
||||
*
|
||||
* This file is part of libg1m.
|
||||
* libg1m is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 3.0 of the License,
|
||||
* or (at your option) any later version.
|
||||
*
|
||||
* libg1m 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 Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with libg1m; if not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Based on the Casetta project documentation:
|
||||
* https://casetta.tuxfamily.org/formats/casemul
|
||||
*
|
||||
* The support for the subfiles (program, picture, matrix, list) are not added
|
||||
* in the specific files in `decode/mcs/` as this is *not* the format they
|
||||
* have while in the MCS, but the format CasEmul uses internally.
|
||||
* ************************************************************************** */
|
||||
#include <libg1m/internals.h>
|
||||
#define VER 0x00000100 /* 1.00, the expected version in the internal headers */
|
||||
|
||||
/* ************************************************************************** */
|
||||
/* Utilities */
|
||||
/* ************************************************************************** */
|
||||
/**
|
||||
* read_internal:
|
||||
* Read the internal headers, and check them.
|
||||
*
|
||||
* @arg buffer the buffer to read from.
|
||||
* @arg type the expected type.
|
||||
* @arg version the expected version.
|
||||
* @return the error (if any).
|
||||
*/
|
||||
|
||||
static int read_internal(g1m_buffer_t *buffer, const char *type,
|
||||
uint_fast32_t version)
|
||||
{
|
||||
DREAD(hd, casemul_internal_header)
|
||||
|
||||
/* check type */
|
||||
if (type[0] != hd.type[1] || type[1] != hd.type[0]
|
||||
|| type[2] != hd.type[3] || type[3] != hd.type[2]) {
|
||||
log_error("type mismatch!");
|
||||
log_error("expected '%c%c%c%c', got '%c%c%c%c'",
|
||||
type[0], type[1], type[2], type[3],
|
||||
hd.type[1], hd.type[0], hd.type[3], hd.type[2]);
|
||||
return (g1m_error_magic);
|
||||
}
|
||||
|
||||
/* check version */
|
||||
if (le32toh(hd.version) != version) {
|
||||
log_error("version mismatch!");
|
||||
log_error("version mismatch!");
|
||||
log_error("epxected %08" PRIxFAST32 ", got %08" PRIx32,
|
||||
version, le32toh(hd.version));
|
||||
return (g1m_error_magic);
|
||||
}
|
||||
|
||||
/* nothing wrong :) */
|
||||
return (0);
|
||||
}
|
||||
|
||||
/**
|
||||
* read_top:
|
||||
* Read the top of a record.
|
||||
*
|
||||
* @arg buffer the buffer to read from.
|
||||
* @arg name the name buffer (13 bytes including '\0').
|
||||
* @arg length the subheader+data length.
|
||||
* @return the error code.
|
||||
*/
|
||||
|
||||
static int read_top(g1m_buffer_t *buffer, char *name, uint_fast32_t *length)
|
||||
{
|
||||
/* read and check the name length */
|
||||
uint32_t name_length; READ(&name_length, sizeof(uint32_t))
|
||||
name_length = le32toh(name_length);
|
||||
|
||||
/* read the name and copy */
|
||||
uint8_t buf[name_length];
|
||||
READ(buf, name_length)
|
||||
name_length = max(name_length, 8);
|
||||
memcpy(name, buf, name_length);
|
||||
name[name_length] = 0;
|
||||
logm_info(name, name_length);
|
||||
|
||||
/* read the length and correct */
|
||||
READ(length, sizeof(uint32_t))
|
||||
*length = le32toh(*length);
|
||||
|
||||
/* no error! */
|
||||
return (0);
|
||||
}
|
||||
|
||||
/* ************************************************************************** */
|
||||
/* Intermediate functions */
|
||||
/* ************************************************************************** */
|
||||
/**
|
||||
* read_picture:
|
||||
* Read a picture record and content.
|
||||
*
|
||||
* @arg pfile the pointer to the mcsfile to make and fill.
|
||||
* @arg buffer the buffer to read from.
|
||||
* @return the error (if occured).
|
||||
*/
|
||||
|
||||
static int read_picture(g1m_mcsfile_t **pfile, g1m_buffer_t *buffer)
|
||||
{
|
||||
int err; *pfile = NULL;
|
||||
static const uint32_t colours[256] = {
|
||||
[0x00] = 0xFFFFFF, /* white */
|
||||
[0x01] = 0xFF8000, /* orange */
|
||||
[0x02] = 0x00FF00, /* green */
|
||||
[0x03] = 0x0000FF, /* blue */
|
||||
/* other colours are black */
|
||||
};
|
||||
|
||||
/* general record things */
|
||||
char name[13]; uint_fast32_t record_length;
|
||||
if ((err = read_top(buffer, name, &record_length))
|
||||
|| (err = read_internal(buffer, "PICT", VER)))
|
||||
return (err);
|
||||
|
||||
/* specific things */
|
||||
DREAD(pct, casemul_pict_header)
|
||||
|
||||
/* read all of the pixels */
|
||||
unsigned int total = pct.width * pct.height;
|
||||
uint8_t rawpx[total]; READ(rawpx, total)
|
||||
uint8_t *px = rawpx;
|
||||
|
||||
/* allocate the file */
|
||||
g1m_mcsfile_t *file = malloc(sizeof(g1m_mcsfile_t));
|
||||
if (!file) return (g1m_error_alloc);
|
||||
memset(file, 0, sizeof(g1m_mcsfile_t));
|
||||
|
||||
/* make the pixels */
|
||||
int width = pct.width, height = pct.height;
|
||||
uint32_t **img = alloc_pixels(width, height);
|
||||
if (!img) { free(file); return (g1m_error_alloc); }
|
||||
|
||||
/* set the head */
|
||||
file->head.type = g1m_mcstype_capt;
|
||||
memcpy(file->head.name, name, strlen(name) + 1);
|
||||
/* TODO: id */
|
||||
|
||||
/* set the pixels */
|
||||
prepare_pixels(img, width, height)
|
||||
file->width = width;
|
||||
file->height = height;
|
||||
file->image = img;
|
||||
for (int x = 0; x < width; x++) for (int y = 0; y < height; y++)
|
||||
img[y][x] = colours[*px++];
|
||||
|
||||
/* finish */
|
||||
*pfile = file;
|
||||
return (0);
|
||||
}
|
||||
|
||||
/**
|
||||
* read_matrix:
|
||||
* Read a matrix record and content.
|
||||
*
|
||||
* @arg pfile the pointer to the mcsfile to make and fill.
|
||||
* @arg buffer the buffer to read from.
|
||||
* @return the error that occured.
|
||||
*/
|
||||
|
||||
static int read_matrix(g1m_mcsfile_t **pfile, g1m_buffer_t *buffer)
|
||||
{
|
||||
int err;
|
||||
|
||||
/* general record things */
|
||||
char name[13]; uint_fast32_t record_length;
|
||||
if ((err = read_top(buffer, name, &record_length))
|
||||
|| (err = read_internal(buffer, "MTRX", VER)))
|
||||
return (err);
|
||||
|
||||
/* read specific things */
|
||||
DREAD(mtx, casemul_mtrx_header)
|
||||
|
||||
/* read double tab */
|
||||
int width = le32toh(mtx.lines) & 0x7FFF,
|
||||
height = le32toh(mtx.columns) & 0x7FFF;
|
||||
unsigned int total = width * height;
|
||||
double tab[total]; READ(tab, total * sizeof(double))
|
||||
double *raw = tab;
|
||||
|
||||
/* allocate handle */
|
||||
g1m_mcsfile_t *file = malloc(sizeof(g1m_mcsfile_t));
|
||||
if (!file) return (g1m_error_alloc);
|
||||
memset(file, 0, sizeof(g1m_mcsfile_t));
|
||||
|
||||
/* allocate matrix */
|
||||
file->cells = malloc(sizeof(g1m_mcs_cell_t*) * height);
|
||||
if (!file->cells) { free(file); return (g1m_error_alloc); }
|
||||
g1m_mcs_cell_t *rws = malloc(sizeof(g1m_mcs_cell_t) * height * width);
|
||||
if (!rws) { free(file); free(file->cells); return (g1m_error_alloc); }
|
||||
|
||||
/* set the information */
|
||||
file->head.type = g1m_mcstype_mat;
|
||||
memcpy(file->head.name, name, strlen(name) + 1);
|
||||
/* TODO: id */
|
||||
|
||||
/* read the matrix */
|
||||
file->columns = width;
|
||||
file->rows = height;
|
||||
for (int y = 0; y < height; y++) {
|
||||
file->cells[y] = &rws[width * y];
|
||||
for (int x = 0; x < width; x++) {
|
||||
g1m_bcd_t bcd; g1m_bcd_fromdouble(*raw++, &bcd);
|
||||
file->cells[y][x] = (g1m_mcs_cell_t){
|
||||
.real = bcd,
|
||||
.imgn = {},
|
||||
.used = 1
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/* no error */
|
||||
*pfile = file;
|
||||
return (0);
|
||||
}
|
||||
|
||||
/**
|
||||
* read_list:
|
||||
* Read a list record and content.
|
||||
*
|
||||
* @arg pfile the pointer to the mcsfile to make and fill.
|
||||
* @arg buffer the buffer to read from.
|
||||
* @return the error that occured.
|
||||
*/
|
||||
|
||||
static int read_list(g1m_mcsfile_t **pfile, g1m_buffer_t *buffer)
|
||||
{
|
||||
int err;
|
||||
|
||||
/* general record things */
|
||||
char name[13]; uint_fast32_t record_length;
|
||||
if ((err = read_top(buffer, name, &record_length))
|
||||
|| (err = read_internal(buffer, "LIST", VER)))
|
||||
return (err);
|
||||
|
||||
/* read specific things */
|
||||
DREAD(lst, casemul_list_header)
|
||||
|
||||
/* read double tab */
|
||||
int len = le32toh(lst.lines) & 0x7FFF;
|
||||
double tab[len]; READ(tab, len * sizeof(double))
|
||||
double *raw = tab;
|
||||
|
||||
/* allocate handle */
|
||||
g1m_mcsfile_t *file = malloc(sizeof(g1m_mcsfile_t));
|
||||
if (!file) return (g1m_error_alloc);
|
||||
memset(file, 0, sizeof(g1m_mcsfile_t));
|
||||
|
||||
/* allocate matrix */
|
||||
file->cells = malloc(sizeof(g1m_mcs_cell_t*) * len);
|
||||
if (!file->cells) { free(file); return (g1m_error_alloc); }
|
||||
g1m_mcs_cell_t *rws = malloc(sizeof(g1m_mcs_cell_t) * len);
|
||||
if (!rws) { free(file->cells); free(file); return (g1m_error_alloc); }
|
||||
|
||||
/* set the information */
|
||||
file->head.type = g1m_mcstype_list;
|
||||
memcpy(file->head.name, name, strlen(name) + 1);
|
||||
/* TODO: id */
|
||||
|
||||
/* read the list */
|
||||
file->columns = 1; file->rows = len;
|
||||
for (int x = 0; x < len; x++) {
|
||||
file->cells[x] = &rws[x];
|
||||
g1m_bcd_t bcd; g1m_bcd_fromdouble(*raw++, &bcd);
|
||||
file->cells[x][0] = (g1m_mcs_cell_t){
|
||||
.real = bcd,
|
||||
.imgn = {},
|
||||
.used = 1
|
||||
};
|
||||
}
|
||||
|
||||
/* no error */
|
||||
*pfile = file;
|
||||
return (0);
|
||||
}
|
||||
|
||||
/* ************************************************************************** */
|
||||
/* Main function */
|
||||
/* ************************************************************************** */
|
||||
/**
|
||||
* g1m_decode_casemul:
|
||||
* Decode a CasEmul file.
|
||||
*
|
||||
* @arg handle the handle.
|
||||
* @arg buffer the buffer to read from.
|
||||
* @return the error code (0 if ok).
|
||||
*/
|
||||
|
||||
int g1m_decode_casemul(g1m_t *handle, g1m_buffer_t *buffer)
|
||||
{
|
||||
int err;
|
||||
|
||||
/* read the overall header's internal header end */
|
||||
struct casemul_internal_header overall_int = {.type = "ACFS"};
|
||||
READ(&overall_int.version, 8);
|
||||
if (le32toh(overall_int.version) != 0x100)
|
||||
return (g1m_error_magic);
|
||||
|
||||
/* read the overall (global) header */
|
||||
DREAD(glb, casemul_header)
|
||||
|
||||
/* read the source header */
|
||||
if ((err = read_internal(buffer, "SRCE", VER)))
|
||||
return (err);
|
||||
DREAD(src, casemul_source_header)
|
||||
log_info("%d program(s), %d pic(s), %d matrix(es), %d list(s)",
|
||||
src.programs, src.pictures, src.matrixes, src.lists);
|
||||
|
||||
/* prepare the tab */
|
||||
handle->type = g1m_type_mcs;
|
||||
handle->platform = g1m_platform_fx;
|
||||
handle->count = 0;
|
||||
handle->_size = /* src.programs + */ src.pictures + src.matrixes +
|
||||
src.lists;
|
||||
if (!(handle->files = malloc(sizeof(g1m_mcsfile_t*) * handle->_size)))
|
||||
return (g1m_error_alloc);
|
||||
|
||||
/* read each program; TODO: put this in a function when token parsing
|
||||
* is managed. */
|
||||
for (int i = 0; i < src.programs; i++) {
|
||||
/* general record things */
|
||||
char name[13]; uint_fast32_t record_length;
|
||||
if ((err = read_top(buffer, name, &record_length))
|
||||
|| (err = read_internal(buffer, "PROG", VER)))
|
||||
goto fail;
|
||||
|
||||
/* specific things */
|
||||
GDREAD(prg, casemul_prog_header)
|
||||
prg.length = le32toh(prg.length);
|
||||
|
||||
/* TODO: decode using tokens (cf. refc and libfc) */
|
||||
SKIP(prg.length)
|
||||
}
|
||||
|
||||
/* read each picture */
|
||||
for (int i = 0; i < src.pictures; i++) {
|
||||
if ((err = read_picture(&handle->files[handle->count], buffer)))
|
||||
goto fail;
|
||||
handle->count++;
|
||||
}
|
||||
|
||||
/* read each matrix */
|
||||
for (int i = 0; i < src.matrixes; i++) {
|
||||
if ((err = read_matrix(&handle->files[handle->count], buffer)))
|
||||
goto fail;
|
||||
handle->count++;
|
||||
}
|
||||
|
||||
/* read each list */
|
||||
for (int i = 0; i < src.lists; i++) {
|
||||
if ((err = read_list(&handle->files[handle->count], buffer)))
|
||||
goto fail;
|
||||
handle->count++;
|
||||
}
|
||||
|
||||
/* TODO: skip compiled part? */
|
||||
|
||||
/* no error! */
|
||||
return (0);
|
||||
fail:
|
||||
g1m_free_mcs(handle);
|
||||
return (err);
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
/* *****************************************************************************
|
||||
* decode/casemul.c -- decode a Casemul file.
|
||||
* Copyright (C) 2017 Thomas "Cakeisalie5" Touhey <thomas@touhey.fr>
|
||||
*
|
||||
* This file is part of libg1m.
|
||||
* libg1m is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 3.0 of the License,
|
||||
* or (at your option) any later version.
|
||||
*
|
||||
* libg1m 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 Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with libg1m; if not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Based on the Casetta project documentation:
|
||||
* https://casetta.tuxfamily.org/formats/casemul
|
||||
* ************************************************************************** */
|
||||
#include <libg1m/internals.h>
|
||||
|
||||
/* TODO */
|
Reference in New Issue