cake
/
libg1m
Archived
1
0
Fork 0
This repository has been archived on 2024-03-16. You can view files and clone it, but cannot push or open issues or pull requests.
libg1m/src/decode/casemul.c

400 lines
12 KiB
C

/* *****************************************************************************
* 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 e32toh(N) ((big_endian) ? be32toh(N) : le32toh(N))
#define htoe32(N) ((big_endian) ? htobe32(N) : htole32(N))
#define VER htoe32(0x00000100) /* 1.00 */
/* ************************************************************************** */
/* 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, uint32_t signature,
uint_fast32_t version)
{
DREAD(hd, casemul_internal_header)
/* check type */
if (signature != hd.signature) {
#if LOGLEVEL <= ll_error
const char *a = (char*)&signature, *b = (char*)&hd.signature;
log_error("signature mismatch!");
log_error("expected '%c%c%c%c', got '%c%c%c%c'",
a[0], a[1], a[2], a[3], b[0], b[1], b[2], b[3]);
#endif
return (g1m_error_magic);
}
/* check version */
if (le32toh(hd.version) != version) {
log_error("version mismatch!");
log_error("epxected %08" PRIxFAST32 ", got %08" PRIx32,
version, 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.
* @arg big_endian big endian?
* @return the error code.
*/
static int read_top(g1m_buffer_t *buffer, char *name, uint_fast32_t *length,
int big_endian)
{
/* read and check the name length */
uint32_t name_length; READ(&name_length, sizeof(uint32_t))
name_length = e32toh(name_length);
/* read the raw name */
uint8_t buf[name_length];
READ(buf, name_length)
/* copy to the real name */
if (name_length > 8) name_length = 8;
memcpy(name, buf, name_length);
name[name_length] = 0;
/* read the length and correct */
READ(length, sizeof(uint32_t))
*length = e32toh(*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.
* @arg big_endian whether the file is big endian or not.
* @return the error (if occured).
*/
static int read_picture(g1m_mcsfile_t **pfile, g1m_buffer_t *buffer,
int big_endian)
{
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, big_endian))
|| (err = read_internal(buffer, MAKELONG('PI', 'CT'), VER)))
return (err);
/* specific things */
DREAD(pct, casemul_pict_header)
log_info("picture dimension is %d*%dpx", pct.width, pct.height);
/* read all of the pixels */
unsigned int total = pct.width * pct.height;
uint8_t rawpx[total]; READ(rawpx, total)
uint8_t *px = rawpx;
/* make the head and allocate file */
g1m_mcshead_t head = {
.g1m_mcshead_type = g1m_mcstype_capture,
.g1m_mcshead_width = pct.width,
.g1m_mcshead_height = pct.height,
};
memcpy(head.g1m_mcshead_name, name, strlen(name) + 1);
head.g1m_mcshead_id = name[7] - '0';
err = g1m_make_mcsfile(pfile, &head);
if (err) return (err);
/* set the pixels */
uint32_t **img = (*pfile)->g1m_mcsfile_pics[0];
for (int x = 0; x < pct.width; x++) for (int y = 0; y < pct.height; y++)
img[y][x] = colours[*px++];
/* finish */
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.
* @arg big_endian whether the file is big endian encoded or not.
* @return the error that occured.
*/
static int read_matrix(g1m_mcsfile_t **pfile, g1m_buffer_t *buffer,
int big_endian)
{
int err; *pfile = NULL;
/* general record things */
char name[13]; uint_fast32_t record_length;
if ((err = read_top(buffer, name, &record_length, big_endian))
|| (err = read_internal(buffer, MAKELONG('MT', 'RX'), VER)))
return (err);
/* read specific things */
DREAD(mtx, casemul_mtrx_header)
unsigned int width = e32toh(mtx.lines) & 0x7FFF,
height = e32toh(mtx.columns) & 0x7FFF;
/* read double tab */
unsigned int total = width * height;
double tab[total]; READ(tab, total * sizeof(double))
double *raw = tab;
/* make the head and allocate file */
g1m_mcshead_t head = {
.g1m_mcshead_type = g1m_mcstype_matrix,
.g1m_mcshead_width = width,
.g1m_mcshead_height = height,
};
memcpy(head.g1m_mcshead_name, name, strlen(name) + 1);
head.g1m_mcshead_id = name[4] - 'A' + 1;
err = g1m_make_mcsfile(pfile, &head);
if (err) return (err);
/* read the matrix */
g1m_mcscell_t **cells = (*pfile)->g1m_mcsfile_cells;
for (unsigned int y = 0; y < height; y++)
for (unsigned int x = 0; x < width; x++) {
/* read the bcd */
g1m_bcd_t bcd; g1m_bcd_fromdouble(*raw++, &bcd);
#if LOGLEVEL <= ll_info
/* log the bcd */
char buf[G1M_BCD_GOODBUFSIZE];
g1m_bcdtoa(&bcd, buf, G1M_BCD_GOODBUFSIZE);
log_info("[%d][%d] %s", y, x, buf);
#endif
/* make the cell */
cells[y][x] = (g1m_mcscell_t){
.g1m_mcscell_real = bcd,
.g1m_mcscell_imgn = {},
.g1m_mcscell_flags = g1m_mcscellflag_used
};
}
/* no error */
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.
* @arg big_endian whether the file is big endian encoded or not.
* @return the error that occured.
*/
static int read_list(g1m_mcsfile_t **pfile, g1m_buffer_t *buffer,
int big_endian)
{
int err; *pfile = NULL;
/* general record things */
char name[13]; uint_fast32_t record_length;
if ((err = read_top(buffer, name, &record_length, big_endian))
|| (err = read_internal(buffer, MAKELONG('LI', 'ST'), VER)))
return (err);
/* read specific things */
DREAD(lst, casemul_list_header)
int len = le32toh(lst.lines) & 0x7FFF;
log_info("%d elements in list", len);
/* read double tab */
double tab[len]; READ(tab, len * sizeof(double))
double *raw = tab;
/* make head */
g1m_mcshead_t head = {
.g1m_mcshead_type = g1m_mcstype_list,
.g1m_mcshead_width = 1,
.g1m_mcshead_height = len
};
memcpy(head.g1m_mcshead_name, name, strlen(name) + 1);
head.g1m_mcshead_id = name[5] - '0';
err = g1m_make_mcsfile(pfile, &head);
if (err) return (err);
/* read the list */
g1m_mcscell_t **cells = (*pfile)->g1m_mcsfile_cells;
for (int x = 0; x < len; x++) {
/* read bcd */
g1m_bcd_t bcd; g1m_bcd_fromdouble(*raw++, &bcd);
#if LOGLEVEL <= ll_info
/* log information */
char buf[G1M_BCD_GOODBUFSIZE];
g1m_bcdtoa(&bcd, buf, G1M_BCD_GOODBUFSIZE);
log_info("[%d] %s", x, buf);
#endif
/* set the cell */
cells[x][0] = (g1m_mcscell_t){
.g1m_mcscell_real = bcd,
.g1m_mcscell_imgn = {},
.g1m_mcscell_flags = g1m_mcscellflag_used
};
}
/* no error */
return (0);
}
/* ************************************************************************** */
/* Main function */
/* ************************************************************************** */
/**
* g1m_decode_casemul:
* Decode a CasEmul file.
*
* @arg h the handle to make.
* @arg buffer the buffer to read from.
* @arg big_endian whether the file is big endian or not.
* @return the error code (0 if ok).
*/
int g1m_decode_casemul(g1m_handle_t **h, g1m_buffer_t *buffer, int big_endian)
{
int err;
/* read the overall header's internal header end */
struct casemul_internal_header overall_int = {
.signature = MAKELONG('CA', 'FS')};
READ(&overall_int.version, 8);
if (e32toh(overall_int.version) != 0x100)
return (g1m_error_magic);
/* read the overall (global) header */
DREAD(glb, casemul_header)
#if LOGLEVEL <= ll_info
glb.source_offset = e32toh(glb.source_offset);
glb.compiled_offset = e32toh(glb.compiled_offset);
log_info("Header source offset is: 0x%08X", glb.source_offset);
if (glb.flags & casemul_compiled)
log_info("Header compiled offset is: 0x%08X", glb.compiled_offset);
else
log_info("The file has got no compiled part.");
#endif
/* read the source header */
if ((err = read_internal(buffer, MAKELONG('SR', 'CE'), VER)))
return (err);
DREAD(src, casemul_source_header)
/* make the handle */
err = g1m_make_mcs(h, /* src.programs + */ src.pictures + src.matrixes
+ src.lists);
if (err) return (err);
g1m_handle_t *handle = *h;
/* read each program; TODO: put this in a function when token parsing
* is managed. */
for (int i = 0; i < src.programs; i++) {
log_info("Reading program #%d", i + 1);
log_warn("Program content will be skipped!");
/* general record things */
char name[13]; uint_fast32_t record_length;
if ((err = read_top(buffer, name, &record_length, big_endian))
|| (err = read_internal(buffer, MAKELONG('PR', 'OG'), 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++) {
log_info("Reading picture #%d", i + 1);
err = read_picture(&handle->g1m_handle_files[handle->g1m_handle_count],
buffer, big_endian);
if (err) goto fail;
handle->g1m_handle_count++;
}
/* read each matrix */
for (int i = 0; i < src.matrixes; i++) {
log_info("Reading matrix #%d", i + 1);
err = read_matrix(&handle->g1m_handle_files[handle->g1m_handle_count],
buffer, big_endian);
if (err) goto fail;
handle->g1m_handle_count++;
}
/* read each list */
for (int i = 0; i < src.lists; i++) {
log_info("Reading list #%d", i + 1);
err = read_list(&handle->g1m_handle_files[handle->g1m_handle_count],
buffer, big_endian);
if (err) goto fail;
handle->g1m_handle_count++;
}
/* TODO: skip compiled part? */
log_warn("Should read compiled part here!");
/* no error! */
return (0);
fail:
g1m_free(handle);
return (err);
}