mirror of
https://github.com/nunuhara/xsystem4.git
synced 2026-10-04 12:58:04 +03:00
Move libsys4 into separate repository
To allow pulling xsystem4 and alice-tools apart.
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
[submodule "subprojects/libsys4"]
|
||||
path = subprojects/libsys4
|
||||
url = https://github.com/nunuhara/libsys4
|
||||
@@ -25,6 +25,13 @@ First install the dependencies (corresponding Debian package in parentheses):
|
||||
* SDL2_ttf (libsdl2-ttf-dev)
|
||||
* zlib (zlib1g-dev)
|
||||
|
||||
Then fetch the git submodules,
|
||||
|
||||
git submodule init
|
||||
git submodule update
|
||||
|
||||
(Alternatively, pass `--recurse-submodules` when cloning this repository)
|
||||
|
||||
Then build the xsystem4 executable with meson,
|
||||
|
||||
mkdir build
|
||||
|
||||
@@ -20,4 +20,7 @@ bison = find_program('bison')
|
||||
|
||||
incdir = include_directories('include')
|
||||
|
||||
libsys4_proj = subproject('libsys4')
|
||||
libsys4_dep = libsys4_proj.get_variable('libsys4_dep')
|
||||
|
||||
subdir('src')
|
||||
|
||||
@@ -1,157 +0,0 @@
|
||||
/* Copyright (C) 2019 Nunuhara Cabbage <nunuhara@haniwa.technology>
|
||||
*
|
||||
* This program 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 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 this program; if not, see <http://gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <zlib.h>
|
||||
|
||||
#include "system4.h"
|
||||
#include "system4/acx.h"
|
||||
#include "system4/string.h"
|
||||
#include "little_endian.h"
|
||||
|
||||
static struct acx *acx_read(uint8_t *data_raw)
|
||||
{
|
||||
struct acx *acx = xcalloc(1, sizeof(struct acx));
|
||||
acx->nr_columns = LittleEndian_getDW(data_raw, 0);
|
||||
acx->nr_lines = LittleEndian_getDW(data_raw, 4 + acx->nr_columns * 4);
|
||||
uint8_t *data = data_raw + (8 + acx->nr_columns * 4);
|
||||
|
||||
acx->column_types = xcalloc(acx->nr_columns, sizeof(int32_t));
|
||||
for (int i = 0; i < acx->nr_columns; i++) {
|
||||
acx->column_types[i] = LittleEndian_getDW(data_raw, 4 + 4*i);
|
||||
if (acx->column_types[i] != ACX_INT && acx->column_types[i] != ACX_STRING)
|
||||
WARNING("Column %d has unknown type (%d)", i, acx->column_types[i]);
|
||||
}
|
||||
|
||||
int ptr = 0;
|
||||
acx->lines = xcalloc(acx->nr_lines * acx->nr_columns, sizeof(union acx_value));
|
||||
for (int line = 0; line < acx->nr_lines; line++) {
|
||||
for (int col = 0; col < acx->nr_columns; col++) {
|
||||
if (acx->column_types[col] == 2) {
|
||||
int old_ptr = ptr;
|
||||
while (data[ptr])
|
||||
ptr++;
|
||||
acx->lines[line*acx->nr_columns + col].s = make_string((char*)data+old_ptr, ptr-old_ptr);
|
||||
ptr++;
|
||||
} else {
|
||||
acx->lines[line*acx->nr_columns + col].i = LittleEndian_getDW(data, ptr);
|
||||
ptr += 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return acx;
|
||||
}
|
||||
|
||||
struct acx *acx_load(const char *path)
|
||||
{
|
||||
FILE *fp;
|
||||
long len;
|
||||
uint8_t *buf = NULL;
|
||||
|
||||
if (!(fp = fopen(path, "rb"))) {
|
||||
WARNING("ACXLoader.Load: Failed to open %s", path);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// get size of ACX file
|
||||
fseek(fp, 0, SEEK_END);
|
||||
len = ftell(fp);
|
||||
fseek(fp, 0, SEEK_SET);
|
||||
|
||||
// read ACX file into memory
|
||||
buf = xmalloc(len + 4);
|
||||
fread(buf, 1, len, fp);
|
||||
fclose(fp);
|
||||
|
||||
// check magic
|
||||
if (strncmp((char*)buf, "ACX\0\0\0\0", 8)) {
|
||||
free(buf);
|
||||
return false;
|
||||
}
|
||||
|
||||
int compressed_size = LittleEndian_getDW(buf, 8);
|
||||
unsigned long size = LittleEndian_getDW(buf, 12);
|
||||
uint8_t *data_raw = xmalloc(size);
|
||||
|
||||
if (Z_OK != uncompress(data_raw, &size, buf+16, compressed_size)) {
|
||||
WARNING("ACXLoader.Load: uncompress failed");
|
||||
free(buf);
|
||||
free(data_raw);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct acx *acx = acx_read(data_raw);
|
||||
free(buf);
|
||||
free(data_raw);
|
||||
return acx;
|
||||
}
|
||||
|
||||
void acx_free(struct acx *acx)
|
||||
{
|
||||
if (!acx)
|
||||
return;
|
||||
|
||||
for (int col = 0; col < acx->nr_columns; col++) {
|
||||
if (acx->column_types[col] != 2)
|
||||
continue;
|
||||
for (int line = 0; line < acx->nr_lines; line++) {
|
||||
free_string(acx->lines[line*acx->nr_columns+col].s);
|
||||
}
|
||||
}
|
||||
free(acx->lines);
|
||||
free(acx->column_types);
|
||||
free(acx);
|
||||
}
|
||||
|
||||
int acx_nr_lines(struct acx *acx)
|
||||
{
|
||||
return acx->nr_lines;
|
||||
}
|
||||
|
||||
int acx_nr_columns(struct acx *acx)
|
||||
{
|
||||
return acx->nr_columns;
|
||||
}
|
||||
|
||||
enum acx_column_type acx_column_type(struct acx *acx, int col)
|
||||
{
|
||||
assert(col >= 0);
|
||||
assert(col < acx->nr_columns);
|
||||
return acx->column_types[col];
|
||||
}
|
||||
|
||||
int acx_get_int(struct acx *acx, int line, int col)
|
||||
{
|
||||
assert(line >= 0);
|
||||
assert(line < acx->nr_lines);
|
||||
assert(col >= 0);
|
||||
assert(col < acx->nr_columns);
|
||||
return acx->lines[line*acx->nr_columns + col].i;
|
||||
}
|
||||
|
||||
struct string *acx_get_string(struct acx *acx, int line, int col)
|
||||
{
|
||||
assert(line >= 0);
|
||||
assert(line < acx->nr_lines);
|
||||
assert(col >= 0);
|
||||
assert(col < acx->nr_columns);
|
||||
return acx->lines[line*acx->nr_columns + col].s;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,309 +0,0 @@
|
||||
/* Copyright (C) 2019 Nunuhara Cabbage <nunuhara@haniwa.technology>
|
||||
*
|
||||
* This program 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 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 this program; if not, see <http://gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <zlib.h>
|
||||
#include "little_endian.h"
|
||||
#include "system4.h"
|
||||
#include "system4/afa.h"
|
||||
#include "system4/archive.h"
|
||||
#include "system4/buffer.h"
|
||||
#include "system4/string.h"
|
||||
|
||||
static bool afa_exists(struct archive *ar, int no);
|
||||
static struct archive_data *afa_get(struct archive *ar, int no);
|
||||
static struct archive_data *afa_get_by_name(struct archive *ar, const char *name);
|
||||
static bool afa_load_file(struct archive_data *data);
|
||||
static void afa_for_each(struct archive *ar, void (*iter)(struct archive_data *data, void *user), void *user);
|
||||
static void afa_free_data(struct archive_data *data);
|
||||
static void afa_free(struct archive *ar);
|
||||
|
||||
struct archive_ops afa_archive_ops = {
|
||||
.exists = afa_exists,
|
||||
.get = afa_get,
|
||||
.get_by_name = afa_get_by_name,
|
||||
.load_file = afa_load_file,
|
||||
.for_each = afa_for_each,
|
||||
.free_data = afa_free_data,
|
||||
.free = afa_free,
|
||||
};
|
||||
|
||||
static bool afa_exists(struct archive *_ar, int no)
|
||||
{
|
||||
struct afa_archive *ar = (struct afa_archive*)_ar;
|
||||
return (uint32_t)no < ar->nr_files;
|
||||
}
|
||||
|
||||
static bool afa_load_file(struct archive_data *data)
|
||||
{
|
||||
if (data->data)
|
||||
return true;
|
||||
|
||||
struct afa_archive *ar = (struct afa_archive*)data->archive;
|
||||
struct afa_entry *e = &ar->files[data->no];
|
||||
|
||||
if (ar->ar.mmapped) {
|
||||
data->data = (uint8_t*)ar->mmap_ptr + ar->data_start + e->off;
|
||||
return true;
|
||||
}
|
||||
|
||||
fseek(ar->f, ar->data_start + e->off, SEEK_SET);
|
||||
|
||||
data->data = xmalloc(e->size);
|
||||
if (fread(data->data, e->size, 1, ar->f) != 1) {
|
||||
WARNING("Failed to read '%s': %s", ar->filename, strerror(errno));
|
||||
free(data->data);
|
||||
free(data);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static struct archive_data *afa_get_descriptor(struct archive *_ar, int no)
|
||||
{
|
||||
struct afa_archive *ar = (struct afa_archive*)_ar;
|
||||
if ((uint32_t)no >= ar->nr_files)
|
||||
return NULL;
|
||||
struct afa_entry *e = &ar->files[no];
|
||||
struct archive_data *data = xcalloc(1, sizeof(struct archive_data));
|
||||
data->size = e->size;
|
||||
data->name = e->name->text;
|
||||
data->no = no;
|
||||
data->archive = _ar;
|
||||
return data;
|
||||
}
|
||||
|
||||
static struct archive_data *afa_get(struct archive *_ar, int no)
|
||||
{
|
||||
struct archive_data *data = afa_get_descriptor(_ar, no);
|
||||
if (!data)
|
||||
return NULL;
|
||||
if (!afa_load_file(data)) {
|
||||
afa_free_data(data);
|
||||
return NULL;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
static struct archive_data *afa_get_by_name(struct archive *_ar, const char *name)
|
||||
{
|
||||
// TODO: index filenames
|
||||
struct afa_archive *ar = (struct afa_archive*)_ar;
|
||||
for (uint32_t i = 0; i < ar->nr_files; i++) {
|
||||
if (!strcmp(name, ar->files[i].name->text)) {
|
||||
return afa_get(_ar, i);
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void afa_for_each(struct archive *_ar, void (*iter)(struct archive_data *data, void *user), void *user)
|
||||
{
|
||||
struct afa_archive *ar = (struct afa_archive*)_ar;
|
||||
for (uint32_t i = 0; i < ar->nr_files; i++) {
|
||||
struct archive_data *data = afa_get_descriptor(_ar, i);
|
||||
if (!data)
|
||||
continue;
|
||||
iter(data, user);
|
||||
afa_free_data(data);
|
||||
}
|
||||
}
|
||||
|
||||
static void afa_free_data(struct archive_data *data)
|
||||
{
|
||||
if (data->data && !data->archive->mmapped)
|
||||
free(data->data);
|
||||
free(data);
|
||||
}
|
||||
|
||||
static void afa_free(struct archive *_ar)
|
||||
{
|
||||
struct afa_archive *ar = (struct afa_archive*)_ar;
|
||||
for (uint32_t i = 0; i < ar->nr_files; i++) {
|
||||
free_string(ar->files[i].name);
|
||||
}
|
||||
if (ar->f)
|
||||
fclose(ar->f);
|
||||
free(ar->filename);
|
||||
free(ar->files);
|
||||
free(ar);
|
||||
}
|
||||
|
||||
static bool afa_read_header(FILE *f, struct afa_archive *ar, int *error)
|
||||
{
|
||||
char buf[44];
|
||||
if (fread(buf, 44, 1, f) != 1) {
|
||||
*error = ARCHIVE_FILE_ERROR;
|
||||
return false;
|
||||
}
|
||||
|
||||
fseek(f, 0, SEEK_END);
|
||||
ar->file_size = ftell(f);
|
||||
fseek(f, 0, SEEK_SET);
|
||||
|
||||
if (strncmp(buf, "AFAH", 4) ||
|
||||
strncmp(buf+8, "AlicArch", 8) ||
|
||||
strncmp(buf+28, "INFO", 4) ||
|
||||
LittleEndian_getDW((uint8_t*)buf, 4) != 0x1c) {
|
||||
*error = ARCHIVE_BAD_ARCHIVE_ERROR;
|
||||
return false;
|
||||
}
|
||||
|
||||
ar->version = LittleEndian_getDW((uint8_t*)buf, 16);
|
||||
ar->unknown = LittleEndian_getDW((uint8_t*)buf, 20);
|
||||
ar->data_start = LittleEndian_getDW((uint8_t*)buf, 24);
|
||||
ar->compressed_size = LittleEndian_getDW((uint8_t*)buf, 32) - 16;
|
||||
ar->uncompressed_size = LittleEndian_getDW((uint8_t*)buf, 36);
|
||||
ar->nr_files = LittleEndian_getDW((uint8_t*)buf, 40);
|
||||
|
||||
if (ar->data_start+8 >= ar->file_size) {
|
||||
*error = ARCHIVE_FILE_ERROR;
|
||||
return false;
|
||||
}
|
||||
|
||||
fseek(f, ar->data_start, SEEK_SET);
|
||||
if (fread(buf, 8, 1, f) != 1) {
|
||||
*error = ARCHIVE_FILE_ERROR;
|
||||
return false;
|
||||
}
|
||||
fseek(f, 0, SEEK_SET);
|
||||
|
||||
if (strncmp(buf, "DATA", 4)) {
|
||||
*error = ARCHIVE_BAD_ARCHIVE_ERROR;
|
||||
return false;
|
||||
}
|
||||
|
||||
ar->data_size = LittleEndian_getDW((uint8_t*)buf, 4);
|
||||
if (ar->data_start + ar->data_size > ar->file_size) {
|
||||
*error = ARCHIVE_BAD_ARCHIVE_ERROR;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool afa_read_entry(struct buffer *in, struct afa_archive *ar, struct afa_entry *entry, int *error)
|
||||
{
|
||||
uint32_t name_len = buffer_read_int32(in);
|
||||
entry->name = buffer_read_pascal_string(in); // NOTE: length is padded
|
||||
entry->name->size = name_len; // fix length
|
||||
|
||||
entry->unknown0 = buffer_read_int32(in);
|
||||
entry->unknown1 = buffer_read_int32(in);
|
||||
if (ar->version == 1)
|
||||
entry->unknown2 = buffer_read_int32(in);
|
||||
|
||||
entry->off = buffer_read_int32(in);
|
||||
entry->size = buffer_read_int32(in);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool afa_read_file_table(FILE *f, struct afa_archive *ar, int *error)
|
||||
{
|
||||
uint8_t *buf = xmalloc(ar->compressed_size);
|
||||
uint8_t *table = xmalloc(ar->uncompressed_size);
|
||||
fseek(f, 44, SEEK_SET);
|
||||
if (fread(buf, ar->compressed_size, 1, f) != 1) {
|
||||
*error = ARCHIVE_FILE_ERROR;
|
||||
goto exit_err;
|
||||
}
|
||||
|
||||
unsigned long uncompressed_size = ar->uncompressed_size;
|
||||
if (uncompress(table, &uncompressed_size, buf, ar->compressed_size) != Z_OK) {
|
||||
*error = ARCHIVE_BAD_ARCHIVE_ERROR;
|
||||
goto exit_err;
|
||||
}
|
||||
|
||||
struct buffer r;
|
||||
buffer_init(&r, table, ar->uncompressed_size);
|
||||
ar->files = xcalloc(ar->nr_files, sizeof(struct afa_entry));
|
||||
for (uint32_t i = 0; i < ar->nr_files; i++) {
|
||||
if (!afa_read_entry(&r, ar, &ar->files[i], error)) {
|
||||
free(ar->files);
|
||||
goto exit_err;
|
||||
}
|
||||
}
|
||||
|
||||
free(buf);
|
||||
free(table);
|
||||
return true;
|
||||
exit_err:
|
||||
free(buf);
|
||||
free(table);
|
||||
return false;
|
||||
}
|
||||
|
||||
struct afa_archive *afa_open(const char *file, int flags, int *error)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
flags &= ~ARCHIVE_MMAP;
|
||||
#endif
|
||||
FILE *fp;
|
||||
struct afa_archive *ar = xcalloc(1, sizeof(struct afa_archive));
|
||||
|
||||
if (!(fp = fopen(file, "rb"))) {
|
||||
WARNING("fopen failed: %s", strerror(errno));
|
||||
*error = ARCHIVE_FILE_ERROR;
|
||||
goto exit_err;
|
||||
}
|
||||
if (!afa_read_header(fp, ar, error)) {
|
||||
WARNING("afa_read_header failed");
|
||||
fclose(fp);
|
||||
goto exit_err;
|
||||
}
|
||||
if (!afa_read_file_table(fp, ar, error)) {
|
||||
WARNING("afa_read_file_table failed");
|
||||
fclose(fp);
|
||||
goto exit_err;
|
||||
}
|
||||
if (flags & ARCHIVE_MMAP) {
|
||||
if (fclose(fp)) {
|
||||
WARNING("fclose failed: %s", strerror(errno));
|
||||
*error = ARCHIVE_FILE_ERROR;
|
||||
goto exit_err;
|
||||
}
|
||||
|
||||
int fd = open(file, O_RDONLY);
|
||||
if (fd < 0) {
|
||||
WARNING("open failed: %s", strerror(errno));
|
||||
*error = ARCHIVE_FILE_ERROR;
|
||||
goto exit_err;
|
||||
}
|
||||
ar->mmap_ptr = mmap(0, ar->file_size, PROT_READ, MAP_SHARED, fd, 0);
|
||||
close(fd);
|
||||
if (ar->mmap_ptr == MAP_FAILED) {
|
||||
WARNING("mmap failed: %s", strerror(errno));
|
||||
*error = ARCHIVE_FILE_ERROR;
|
||||
goto exit_err;
|
||||
}
|
||||
ar->ar.mmapped = true;
|
||||
} else {
|
||||
ar->f = fp;
|
||||
}
|
||||
ar->filename = strdup(file);
|
||||
ar->ar.ops = &afa_archive_ops;
|
||||
return ar;
|
||||
exit_err:
|
||||
free(ar);
|
||||
return NULL;
|
||||
}
|
||||
@@ -1,156 +0,0 @@
|
||||
/* Copyright (C) 2019 Nunuhara Cabbage <nunuhara@haniwa.technology>
|
||||
*
|
||||
* This program 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 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 this program; if not, see <http://gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <turbojpeg.h>
|
||||
#include <zlib.h>
|
||||
#include "little_endian.h"
|
||||
#include "system4.h"
|
||||
#include "system4/cg.h"
|
||||
#include "system4/pms.h"
|
||||
|
||||
bool ajp_checkfmt(const uint8_t *data)
|
||||
{
|
||||
if (data[0] != 'A' || data[1] != 'J' || data[2] != 'P' || data[3] != '\0')
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static const uint8_t ajp_key[] = {
|
||||
0x5d, 0x91, 0xae, 0x87,
|
||||
0x4a, 0x56, 0x41, 0xcd,
|
||||
0x83, 0xec, 0x4c, 0x92,
|
||||
0xb5, 0xcb, 0x16, 0x34
|
||||
};
|
||||
|
||||
static void ajp_decrypt(uint8_t *data, size_t size)
|
||||
{
|
||||
for (size_t i = 0; i < 16 && i < size; i++) {
|
||||
data[i] ^= ajp_key[i];
|
||||
}
|
||||
}
|
||||
|
||||
struct ajp_header {
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
uint32_t jpeg_off;
|
||||
uint32_t jpeg_size;
|
||||
uint32_t mask_off;
|
||||
uint32_t mask_size;
|
||||
};
|
||||
|
||||
static void ajp_extract_header(const uint8_t *data, struct ajp_header *ajp)
|
||||
{
|
||||
ajp->width = LittleEndian_getDW(data, 12);
|
||||
ajp->height = LittleEndian_getDW(data, 16);
|
||||
ajp->jpeg_off = LittleEndian_getDW(data, 20);
|
||||
ajp->jpeg_size = LittleEndian_getDW(data, 24);
|
||||
ajp->mask_off = LittleEndian_getDW(data, 28);
|
||||
ajp->mask_size = LittleEndian_getDW(data, 32);
|
||||
}
|
||||
|
||||
static void ajp_init_metrics(struct ajp_header *ajp, struct cg_metrics *dst)
|
||||
{
|
||||
dst->w = ajp->width;
|
||||
dst->h = ajp->height;
|
||||
dst->bpp = 24;
|
||||
dst->has_pixel = ajp->jpeg_size > 0;
|
||||
//dst->has_alpha = ajp->mask_size > 0;
|
||||
dst->has_alpha = false;
|
||||
dst->pixel_pitch = ajp->width * 3;
|
||||
dst->alpha_pitch = 1;
|
||||
}
|
||||
|
||||
void ajp_extract(const uint8_t *data, size_t size, struct cg *cg)
|
||||
{
|
||||
uint8_t *buf = NULL, *jpeg_data = NULL, *mask_data = NULL;
|
||||
int width, height, subsamp;
|
||||
struct ajp_header ajp;
|
||||
ajp_extract_header(data, &ajp);
|
||||
ajp_init_metrics(&ajp, &cg->metrics);
|
||||
|
||||
if (ajp.jpeg_off > size)
|
||||
ERROR("AJP JPEG offset invalid");
|
||||
if (ajp.jpeg_off + ajp.jpeg_size > size)
|
||||
ERROR("AJP JPEG size invalid");
|
||||
if (ajp.mask_off > size)
|
||||
ERROR("AJP mask offset invalid");
|
||||
if (ajp.mask_off + ajp.mask_size > size)
|
||||
ERROR("AJP mask size invalid");
|
||||
|
||||
jpeg_data = xmalloc(ajp.jpeg_size);
|
||||
mask_data = xmalloc(ajp.mask_size);
|
||||
memcpy(jpeg_data, data + ajp.jpeg_off, ajp.jpeg_size);
|
||||
memcpy(mask_data, data + ajp.mask_off, ajp.mask_size);
|
||||
ajp_decrypt(jpeg_data, ajp.jpeg_size);
|
||||
ajp_decrypt(mask_data, ajp.mask_size);
|
||||
|
||||
tjhandle decompressor = tjInitDecompress();
|
||||
tjDecompressHeader2(decompressor, jpeg_data, ajp.jpeg_size, &width, &height, &subsamp);
|
||||
if ((uint32_t)width != ajp.width)
|
||||
WARNING("AJP width doesn't match JPEG width (%d vs. %u)", width, ajp.width);
|
||||
if ((uint32_t)height != ajp.height)
|
||||
WARNING("AJP height doesn't match JPEG height (%d vs. %u)", height, ajp.height);
|
||||
|
||||
buf = xmalloc(width * height * 3);
|
||||
if (tjDecompress2(decompressor, jpeg_data, ajp.jpeg_size, buf, width, 0, height, TJPF_RGB, TJFLAG_FASTDCT) < 0) {
|
||||
WARNING("JPEG decompression failed: %s", tjGetErrorStr());
|
||||
free(buf);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
uint8_t *mask;
|
||||
if (ajp.mask_size && pms8_checkfmt(mask_data)) {
|
||||
mask = pms_extract_mask(mask_data, ajp.mask_size);
|
||||
} else if (mask_data[0] == 0x78) {
|
||||
// compressed
|
||||
unsigned long uncompressed_size = width*height;
|
||||
mask = xmalloc(uncompressed_size);
|
||||
if (uncompress(mask, &uncompressed_size, mask_data, ajp.mask_size) != Z_OK) {
|
||||
WARNING("uncompress failed");
|
||||
memset(mask, 0xFF, width * height);
|
||||
} else if (uncompressed_size != (unsigned)width * (unsigned)height) {
|
||||
WARNING("Unexpected AJP mask size");
|
||||
}
|
||||
} else {
|
||||
if (ajp.mask_size)
|
||||
WARNING("Unsupported AJP mask format: %02x %02x %02x %02x",
|
||||
mask_data[0], mask_data[1], mask_data[2], mask_data[3]);
|
||||
mask = xmalloc(width * height);
|
||||
memset(mask, 0xFF, width * height);
|
||||
}
|
||||
|
||||
uint8_t *tmp = xmalloc(width * height * 4);
|
||||
for (int i = 0; i < width * height; i++) {
|
||||
tmp[i*4+0] = buf[i*3+0];
|
||||
tmp[i*4+1] = buf[i*3+1];
|
||||
tmp[i*4+2] = buf[i*3+2];
|
||||
tmp[i*4+3] = mask[i];
|
||||
}
|
||||
free(buf);
|
||||
free(mask);
|
||||
buf = tmp;
|
||||
|
||||
cg->type = ALCG_AJP;
|
||||
cg->pixels = buf;
|
||||
|
||||
cleanup:
|
||||
free(jpeg_data);
|
||||
free(mask_data);
|
||||
tjDestroy(decompressor);
|
||||
}
|
||||
@@ -1,402 +0,0 @@
|
||||
/* Copyright (C) 2019 Nunuhara Cabbage <nunuhara@haniwa.technology>
|
||||
*
|
||||
* Based on code from xsystem35
|
||||
* Copyright (C) 1997-1998 Masaki Chikama (Wren) <chikama@kasumi.ipl.mech.nagoya-u.ac.jp>
|
||||
* 1998- <masaki-c@is.aist-nara.ac.jp>
|
||||
*
|
||||
* This program 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 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 this program; if not, see <http://gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include "little_endian.h"
|
||||
#include "system4.h"
|
||||
#include "system4/ald.h"
|
||||
|
||||
static bool ald_exists(struct archive *ar, int no);
|
||||
static struct archive_data *ald_get(struct archive *ar, int no);
|
||||
static struct archive_data *ald_get_by_name(struct archive *_ar, const char *name);
|
||||
static bool ald_load_file(struct archive_data *data);
|
||||
static void ald_for_each(struct archive *_ar, void (*iter)(struct archive_data *data, void *user), void *user);
|
||||
static void ald_free_data(struct archive_data *data);
|
||||
static void ald_free(struct archive *ar);
|
||||
|
||||
struct archive_ops ald_archive_ops = {
|
||||
.exists = ald_exists,
|
||||
.get = ald_get,
|
||||
.get_by_name = ald_get_by_name,
|
||||
.load_file = ald_load_file,
|
||||
.for_each = ald_for_each,
|
||||
.free_data = ald_free_data,
|
||||
.free = ald_free,
|
||||
};
|
||||
|
||||
/* Get the size of a file in bytes. */
|
||||
static long get_file_size(FILE *fp)
|
||||
{
|
||||
fseek(fp, 0L, SEEK_END);
|
||||
return ftell(fp);
|
||||
}
|
||||
|
||||
/* Check validity of file. */
|
||||
static bool file_check(FILE *fp)
|
||||
{
|
||||
uint8_t b[6];
|
||||
int mapsize, ptrsize;
|
||||
long filesize;
|
||||
|
||||
// get filesize / 256
|
||||
filesize = (get_file_size(fp) + 255) >> 8;
|
||||
|
||||
long fsize = get_file_size(fp);
|
||||
fseek(fp, fsize & ~0xFF, SEEK_SET);
|
||||
fread(b, 1, 4, fp);
|
||||
|
||||
// read top 6 bytes
|
||||
fseek(fp, 0L, SEEK_SET);
|
||||
fread(b, 1, 6, fp);
|
||||
|
||||
// get ptrsize and mapsize
|
||||
ptrsize = LittleEndian_get3B(b, 0);
|
||||
mapsize = LittleEndian_get3B(b, 3) - ptrsize;
|
||||
|
||||
// check that sizes are valid
|
||||
if (ptrsize < 0 || mapsize < 0)
|
||||
return false;
|
||||
if (ptrsize > filesize || mapsize > filesize)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Read the file map of an ALD file into a `struct ald_archive`. */
|
||||
static void get_filemap(struct ald_archive *archive, FILE *fp)
|
||||
{
|
||||
uint8_t b[6], *_b;
|
||||
int mapsize, ptrsize;
|
||||
|
||||
// read top 6 bytes
|
||||
fseek(fp, 0L, SEEK_SET);
|
||||
fread(b, 1, 6, fp);
|
||||
|
||||
// get ptrsize and mapsize
|
||||
ptrsize = LittleEndian_get3B(b, 0);
|
||||
mapsize = LittleEndian_get3B(b, 3) - ptrsize;
|
||||
|
||||
// allocate read buffer
|
||||
_b = malloc(mapsize * 256);
|
||||
|
||||
// read filemap
|
||||
fseek(fp, ptrsize * 256L, SEEK_SET);
|
||||
fread(_b, 256, mapsize, fp);
|
||||
|
||||
// get max file number from mapdata
|
||||
archive->maxfile = (mapsize * 256) / 3;
|
||||
|
||||
// map of disk
|
||||
archive->map_disk = malloc(archive->maxfile);
|
||||
archive->map_ptr = malloc(sizeof(short) * archive->maxfile);
|
||||
|
||||
for (int i = 0; i < archive->maxfile; i++) {
|
||||
archive->map_disk[i] = _b[i * 3] - 1;
|
||||
archive->map_ptr[i] = LittleEndian_getW(_b, i * 3 + 1) - 1;
|
||||
}
|
||||
|
||||
free(_b);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Read the pointer map of an ALD file into a `struct ald_archive`. */
|
||||
static void get_ptrmap(struct ald_archive *archive, FILE *fp, int disk)
|
||||
{
|
||||
uint8_t b[8], *_b;
|
||||
int ptrsize, filecnt;
|
||||
|
||||
// read top 6 bytes
|
||||
fseek(fp, 0L, SEEK_SET);
|
||||
fread(b, 1, 6, fp);
|
||||
|
||||
// get ptrmap size
|
||||
ptrsize = LittleEndian_get3B(b, 0);
|
||||
|
||||
// estimate number of entries in ptrmap
|
||||
filecnt = (ptrsize * 256) / 3;
|
||||
|
||||
// allocate read buffer
|
||||
_b = malloc(ptrsize * 256);
|
||||
|
||||
// read pointers
|
||||
fseek(fp, 0L, SEEK_SET);
|
||||
fread(_b, 256, ptrsize, fp);
|
||||
|
||||
// allocate pointers buffer
|
||||
archive->fileptr[disk] = calloc(filecnt, sizeof(int));
|
||||
|
||||
// store pointers
|
||||
for (int i = 0; i < filecnt - 1; i++) {
|
||||
*(archive->fileptr[disk] + i) = (LittleEndian_get3B(_b, i * 3 + 3) * 256);
|
||||
}
|
||||
|
||||
free(_b);
|
||||
return;
|
||||
}
|
||||
|
||||
static int _ald_get(struct ald_archive *ar, int no, int *disk_out, int *dataptr_out)
|
||||
{
|
||||
int disk, ptr, dataptr, dataptr2;
|
||||
|
||||
// check that index is within range of file map
|
||||
if (no < 0 || no >= ar->maxfile)
|
||||
return 0;
|
||||
|
||||
// look up data in file map
|
||||
disk = ar->map_disk[no];
|
||||
ptr = ar->map_ptr[no];
|
||||
if (disk < 0 || ptr < 0)
|
||||
return 0;
|
||||
|
||||
// no file registered
|
||||
if (ar->fileptr[disk] == NULL)
|
||||
return 0;
|
||||
|
||||
// get pointer in file and size
|
||||
dataptr = *(ar->fileptr[disk] + ptr);
|
||||
dataptr2 = *(ar->fileptr[disk] + ptr + 1);
|
||||
if (!dataptr || !dataptr2)
|
||||
return 0;
|
||||
|
||||
*dataptr_out = dataptr;
|
||||
*disk_out = disk;
|
||||
return dataptr2 - dataptr;
|
||||
}
|
||||
|
||||
static bool ald_exists(struct archive *ar, int no)
|
||||
{
|
||||
int disk, dataptr;
|
||||
return ar && !!_ald_get((struct ald_archive*)ar, no, &disk, &dataptr);
|
||||
}
|
||||
|
||||
/* Get a descriptor for a file in an ALD archive. */
|
||||
struct archive_data *ald_get_descriptor(struct archive *_ar, int no)
|
||||
{
|
||||
if (!_ar)
|
||||
return NULL;
|
||||
|
||||
struct ald_archive *ar = (struct ald_archive*)_ar;
|
||||
struct ald_archive_data *dfile = calloc(1, sizeof(struct ald_archive_data));
|
||||
|
||||
if (!_ald_get(ar, no, &dfile->disk, &dfile->dataptr)) {
|
||||
free(dfile);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// read header
|
||||
if (ar->ar.mmapped) {
|
||||
uint8_t *hdr = ar->files[dfile->disk].data + dfile->dataptr;
|
||||
dfile->hdr_size = LittleEndian_getDW(hdr, 0);
|
||||
dfile->data.size = LittleEndian_getDW(hdr, 4);
|
||||
dfile->data.name = strdup((char*)hdr + 16);
|
||||
} else {
|
||||
uint8_t *hdr = xmalloc(8);
|
||||
FILE *fp = ar->files[dfile->disk].fp;
|
||||
|
||||
// read header size, file size
|
||||
fseek(fp, dfile->dataptr, SEEK_SET);
|
||||
fread(hdr, 8, 1, fp);
|
||||
dfile->hdr_size = LittleEndian_getDW(hdr, 0);
|
||||
dfile->data.size = LittleEndian_getDW(hdr, 4);
|
||||
|
||||
// read name
|
||||
dfile->data.name = xcalloc(dfile->hdr_size-8, 1);
|
||||
fread(dfile->data.name, dfile->hdr_size-8, 1, fp);
|
||||
|
||||
free(hdr);
|
||||
}
|
||||
|
||||
dfile->data.no = no;
|
||||
dfile->data.archive = &ar->ar;
|
||||
return &dfile->data;
|
||||
}
|
||||
|
||||
/* Get a file from an ALD archive. */
|
||||
struct archive_data *ald_get(struct archive *ar, int no)
|
||||
{
|
||||
if (!ar)
|
||||
return NULL;
|
||||
|
||||
struct archive_data *data = ald_get_descriptor(ar, no);
|
||||
if (!data)
|
||||
return NULL;
|
||||
|
||||
if (!ald_load_file(data)) {
|
||||
ald_free_data(data);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
static struct archive_data *ald_get_by_name(struct archive *_ar, const char *name)
|
||||
{
|
||||
struct ald_archive *ar = (struct ald_archive*)_ar;
|
||||
for (int i = 0; i < ar->maxfile; i++) {
|
||||
struct archive_data *data = ald_get_descriptor(_ar, i);
|
||||
if (!data)
|
||||
continue;
|
||||
if (!strcmp(data->name, name)) {
|
||||
ald_load_file(data);
|
||||
return data;
|
||||
}
|
||||
ald_free_data(data);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static bool ald_load_file(struct archive_data *data)
|
||||
{
|
||||
struct ald_archive *ar = (struct ald_archive*)data->archive;
|
||||
struct ald_archive_data *dfile = (struct ald_archive_data*)data;
|
||||
if (data->archive->mmapped) {
|
||||
data->data = ar->files[dfile->disk].data + dfile->dataptr + dfile->hdr_size;
|
||||
} else {
|
||||
FILE *fp = ar->files[dfile->disk].fp;
|
||||
data->data = xmalloc(data->size);
|
||||
|
||||
fseek(fp, dfile->dataptr + dfile->hdr_size, SEEK_SET);
|
||||
fread(data->data, data->size, 1, fp);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
static void ald_for_each(struct archive *_ar, void (*iter)(struct archive_data *data, void *user), void *user)
|
||||
{
|
||||
struct ald_archive *ar = (struct ald_archive*)_ar;
|
||||
for (int i = 0; i < ar->maxfile; i++) {
|
||||
struct archive_data *data = ald_get_descriptor(_ar, i);
|
||||
if (!data)
|
||||
continue;
|
||||
iter(data, user);
|
||||
ald_free_data(data);
|
||||
}
|
||||
}
|
||||
|
||||
/* Free an ald_data strcture returned by `ald_get`. */
|
||||
static void ald_free_data(struct archive_data *data)
|
||||
{
|
||||
if (!data)
|
||||
return;
|
||||
if (!data->archive->mmapped && data->data)
|
||||
free(data->data);
|
||||
free(data->name);
|
||||
free(data);
|
||||
}
|
||||
|
||||
/* Free an ald_archive structure returned by `ald_open`. */
|
||||
static void ald_free(struct archive *_ar)
|
||||
{
|
||||
if (!_ar)
|
||||
return;
|
||||
|
||||
struct ald_archive *ar = (struct ald_archive*)_ar;
|
||||
|
||||
// unmap mmap files/close file descriptors
|
||||
for (int i = 0; i < ALD_FILEMAX; i++) {
|
||||
if (ar->ar.mmapped && ar->files[i].data) {
|
||||
munmap(ar->files[i].data, ar->files[i].size);
|
||||
} else if (ar->files[i].fp) {
|
||||
fclose(ar->files[i].fp);
|
||||
}
|
||||
free(ar->files[i].name);
|
||||
}
|
||||
|
||||
free(ar);
|
||||
}
|
||||
|
||||
/* Open an ALD archive, optionally memory-mapping it. */
|
||||
struct archive *ald_open(char **files, int count, int flags, int *error)
|
||||
{
|
||||
FILE *fp;
|
||||
long filesize;
|
||||
struct ald_archive *ar = calloc(1, sizeof(struct ald_archive));
|
||||
bool gotmap = false;
|
||||
|
||||
#ifdef _WIN32
|
||||
flags &= ~ARCHIVE_MMAP;
|
||||
#endif
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (!files[i])
|
||||
continue;
|
||||
if (!(fp = fopen(files[i], "r"))) {
|
||||
*error = ARCHIVE_FILE_ERROR;
|
||||
goto exit_err;
|
||||
}
|
||||
// check if it's a valid archive
|
||||
if (!file_check(fp)) {
|
||||
*error = ARCHIVE_BAD_ARCHIVE_ERROR;
|
||||
fclose(fp);
|
||||
goto exit_err;
|
||||
}
|
||||
// get file map, if we haven't already
|
||||
if (!gotmap) {
|
||||
get_filemap(ar, fp);
|
||||
gotmap = true;
|
||||
}
|
||||
// get pointer map
|
||||
get_ptrmap(ar, fp, i);
|
||||
// get file size for mmap
|
||||
filesize = get_file_size(fp);
|
||||
// copy filename
|
||||
ar->files[i].name = strdup(files[i]);
|
||||
// close
|
||||
fclose(fp);
|
||||
if (flags & ARCHIVE_MMAP) {
|
||||
int fd;
|
||||
if (0 > (fd = open(files[i], O_RDONLY))) {
|
||||
*error = ARCHIVE_FILE_ERROR;
|
||||
goto exit_err;
|
||||
}
|
||||
ar->files[i].data = mmap(0, filesize, PROT_READ, MAP_SHARED, fd, 0);
|
||||
close(fd);
|
||||
if (ar->files[i].data == MAP_FAILED) {
|
||||
*error = ARCHIVE_FILE_ERROR;
|
||||
goto exit_err;
|
||||
}
|
||||
ar->files[i].size = filesize;
|
||||
} else {
|
||||
// get a file descriptor for each file
|
||||
if (!(ar->files[i].fp = fopen(files[i], "rb"))) {
|
||||
*error = ARCHIVE_FILE_ERROR;
|
||||
goto exit_err;
|
||||
}
|
||||
}
|
||||
}
|
||||
int c = 0;
|
||||
for (int i = 0; i < ar->maxfile; i++) {
|
||||
if (ar->map_disk[i] < 0 || ar->map_ptr[i] < 0)
|
||||
continue;
|
||||
c++;
|
||||
}
|
||||
ar->ar.mmapped = flags & ARCHIVE_MMAP;
|
||||
ar->nr_files = count;
|
||||
ar->ar.ops = &ald_archive_ops;
|
||||
return &ar->ar;
|
||||
exit_err:
|
||||
free(ar);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
/* Copyright (C) 2019 Nunuhara Cabbage <nunuhara@haniwa.technology>
|
||||
*
|
||||
* This program 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 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 this program; if not, see <http://gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "system4.h"
|
||||
#include "system4/ald.h"
|
||||
|
||||
static const char *errtab[ARCHIVE_MAX_ERROR] = {
|
||||
[ARCHIVE_SUCCESS] = "Success",
|
||||
[ARCHIVE_FILE_ERROR] = "Error opening ALD file",
|
||||
[ARCHIVE_BAD_ARCHIVE_ERROR] = "Invalid ALD file"
|
||||
};
|
||||
|
||||
/* Get a message describing an error. */
|
||||
const char *archive_strerror(int error)
|
||||
{
|
||||
if (error < ARCHIVE_MAX_ERROR)
|
||||
return errtab[error];
|
||||
return "Invalid error number";
|
||||
}
|
||||
-153
@@ -1,153 +0,0 @@
|
||||
/* Copyright (C) 2019 Nunuhara Cabbage <nunuhara@haniwa.technology>
|
||||
*
|
||||
* This program 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 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 this program; if not, see <http://gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include "little_endian.h"
|
||||
#include "system4.h"
|
||||
#include "system4/buffer.h"
|
||||
#include "system4/string.h"
|
||||
|
||||
void buffer_init(struct buffer *r, uint8_t *buf, size_t size)
|
||||
{
|
||||
r->buf = buf;
|
||||
r->size = size;
|
||||
r->index = 0;
|
||||
}
|
||||
|
||||
int32_t buffer_read_int32(struct buffer *r)
|
||||
{
|
||||
if (r->index + 4 > r->size)
|
||||
ERROR("Out of bounds buffer read");
|
||||
int32_t v = LittleEndian_getDW(r->buf, r->index);
|
||||
r->index += 4;
|
||||
return v;
|
||||
}
|
||||
|
||||
uint8_t buffer_read_u8(struct buffer *r)
|
||||
{
|
||||
if (r->index + 1 > r->size)
|
||||
ERROR("Out of bounds buffer read");
|
||||
return r->buf[r->index++];
|
||||
}
|
||||
|
||||
uint16_t buffer_read_u16(struct buffer *r)
|
||||
{
|
||||
if (r->index + 2 > r->size)
|
||||
ERROR("Out of bounds buffer read");
|
||||
uint16_t v = LittleEndian_getW(r->buf, r->index);
|
||||
r->index += 2;
|
||||
return v;
|
||||
}
|
||||
|
||||
float buffer_read_float(struct buffer *r)
|
||||
{
|
||||
union { int32_t i; float f; } v;
|
||||
v.i = buffer_read_int32(r);
|
||||
return v.f;
|
||||
}
|
||||
|
||||
struct string *buffer_read_pascal_string(struct buffer *r)
|
||||
{
|
||||
int32_t len = buffer_read_int32(r);
|
||||
if (len < 0)
|
||||
ERROR("Invalid string length: %d", len);
|
||||
struct string *s = make_string((char*)r->buf+r->index, len);
|
||||
r->index += len;
|
||||
return s;
|
||||
}
|
||||
|
||||
void buffer_skip(struct buffer *r, size_t off)
|
||||
{
|
||||
r->index = min(r->index + off, r->size);
|
||||
}
|
||||
|
||||
size_t buffer_remaining(struct buffer *r)
|
||||
{
|
||||
return r->size - r->index;
|
||||
}
|
||||
|
||||
char *buffer_strdata(struct buffer *r)
|
||||
{
|
||||
return (char*)r->buf + r->index;
|
||||
}
|
||||
|
||||
static void alloc_buffer(struct buffer *b, size_t size)
|
||||
{
|
||||
if (b->index + size < b->size)
|
||||
return;
|
||||
|
||||
size_t new_size = b->index + size;
|
||||
if (!b->size)
|
||||
b->size = 64;
|
||||
while (b->size <= new_size)
|
||||
b->size *= 2;
|
||||
b->buf = xrealloc(b->buf, b->size);
|
||||
}
|
||||
|
||||
void buffer_write_int32(struct buffer *b, uint32_t v)
|
||||
{
|
||||
alloc_buffer(b, 4);
|
||||
b->buf[b->index++] = (v & 0x000000FF);
|
||||
b->buf[b->index++] = (v & 0x0000FF00) >> 8;
|
||||
b->buf[b->index++] = (v & 0x00FF0000) >> 16;
|
||||
b->buf[b->index++] = (v & 0xFF000000) >> 24;
|
||||
}
|
||||
|
||||
void buffer_write_int32_at(struct buffer *buf, size_t index, uint32_t v)
|
||||
{
|
||||
size_t tmp = buf->index;
|
||||
buf->index = index;
|
||||
buffer_write_int32(buf, v);
|
||||
buf->index = tmp;
|
||||
}
|
||||
|
||||
void buffer_write_int16(struct buffer *b, uint16_t v)
|
||||
{
|
||||
alloc_buffer(b, 2);
|
||||
b->buf[b->index++] = (v & 0x00FF);
|
||||
b->buf[b->index++] = (v & 0xFF00) >> 8;
|
||||
}
|
||||
|
||||
void buffer_write_float(struct buffer *b, float f)
|
||||
{
|
||||
union { float f; uint32_t u; } v;
|
||||
v.f = f;
|
||||
buffer_write_int32(b, v.u);
|
||||
}
|
||||
|
||||
void buffer_write_bytes(struct buffer *b, const uint8_t *bytes, size_t len)
|
||||
{
|
||||
alloc_buffer(b, len);
|
||||
memcpy(b->buf + b->index, bytes, len);
|
||||
b->index += len;
|
||||
}
|
||||
|
||||
void buffer_write_string(struct buffer *b, struct string *s)
|
||||
{
|
||||
buffer_write_bytes(b, (uint8_t*)s->text, s->size+1);
|
||||
}
|
||||
|
||||
void buffer_write_cstring(struct buffer *b, const char *s)
|
||||
{
|
||||
buffer_write_bytes(b, (uint8_t*)s, strlen(s)+1);
|
||||
}
|
||||
|
||||
void buffer_write_pascal_string(struct buffer *b, struct string *s)
|
||||
{
|
||||
buffer_write_int32(b, s->size);
|
||||
buffer_write_bytes(b, (uint8_t*)s->text, s->size);
|
||||
}
|
||||
@@ -1,143 +0,0 @@
|
||||
/* Copyright (C) 2019 Nunuhara Cabbage <nunuhara@haniwa.technology>
|
||||
*
|
||||
* This program 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 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 this program; if not, see <http://gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include "system4.h"
|
||||
#include "system4/archive.h"
|
||||
#include "system4/cg.h"
|
||||
#include "system4/ajp.h"
|
||||
#include "system4/dcf.h"
|
||||
#include "system4/qnt.h"
|
||||
#include "system4/webp.h"
|
||||
|
||||
/*
|
||||
* Identify cg format
|
||||
* data: pointer to compressed data
|
||||
* return: cg type
|
||||
*/
|
||||
enum cg_type cg_check_format(uint8_t *data)
|
||||
{
|
||||
if (qnt_checkfmt(data)) {
|
||||
return ALCG_QNT;
|
||||
} else if (ajp_checkfmt(data)) {
|
||||
return ALCG_AJP;
|
||||
} else if (webp_checkfmt(data)) {
|
||||
return ALCG_WEBP;
|
||||
} else if (dcf_checkfmt(data)) {
|
||||
return ALCG_DCF;
|
||||
}
|
||||
return ALCG_UNKNOWN;
|
||||
}
|
||||
|
||||
bool cg_get_metrics(struct archive *ar, int no, struct cg_metrics *dst)
|
||||
{
|
||||
struct archive_data *dfile;
|
||||
|
||||
if (!(dfile = archive_get(ar, no)))
|
||||
return false;
|
||||
|
||||
switch (cg_check_format(dfile->data)) {
|
||||
case ALCG_QNT:
|
||||
qnt_get_metrics(dfile->data, dst);
|
||||
break;
|
||||
case ALCG_AJP:
|
||||
WARNING("AJP GetMetrics not implemented");
|
||||
archive_free_data(dfile);
|
||||
return false;
|
||||
case ALCG_WEBP:
|
||||
webp_get_metrics(dfile->data, dfile->size, dst);
|
||||
break;
|
||||
case ALCG_DCF:
|
||||
dcf_get_metrics(dfile->data, dfile->size, dst);
|
||||
break;
|
||||
default:
|
||||
WARNING("Unknown CG type (CG %d)", no);
|
||||
archive_free_data(dfile);
|
||||
return false;
|
||||
}
|
||||
archive_free_data(dfile);
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Free CG data
|
||||
* cg: object to free
|
||||
*/
|
||||
void cg_free(struct cg *cg)
|
||||
{
|
||||
if (!cg)
|
||||
return;
|
||||
free(cg->pixels);
|
||||
free(cg);
|
||||
}
|
||||
|
||||
struct cg *cg_load_data(struct archive_data *dfile)
|
||||
{
|
||||
int type;
|
||||
struct cg *cg = xcalloc(1, sizeof(struct cg));
|
||||
|
||||
// check loaded cg format
|
||||
type = cg_check_format(dfile->data);
|
||||
|
||||
// extract cg
|
||||
switch(type) {
|
||||
case ALCG_QNT:
|
||||
qnt_extract(dfile->data, cg);
|
||||
break;
|
||||
case ALCG_AJP:
|
||||
ajp_extract(dfile->data, dfile->size, cg);
|
||||
break;
|
||||
case ALCG_PNG:
|
||||
WARNING("Unimplemented CG type: PNG");
|
||||
break;
|
||||
case ALCG_WEBP:
|
||||
webp_extract(dfile->data, dfile->size, cg, dfile->archive);
|
||||
break;
|
||||
case ALCG_DCF:
|
||||
dcf_extract(dfile->data, dfile->size, cg);
|
||||
break;
|
||||
default:
|
||||
WARNING("Unknown CG type");
|
||||
break;
|
||||
}
|
||||
|
||||
if (cg->pixels)
|
||||
return cg;
|
||||
free(cg);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Load cg data from file or cache
|
||||
* no: file no ( >= 0)
|
||||
* return: cg object(extracted)
|
||||
*/
|
||||
struct cg *cg_load(struct archive *ar, int no)
|
||||
{
|
||||
struct cg *cg;
|
||||
struct archive_data *dfile;
|
||||
|
||||
if (!(dfile = archive_get(ar, no))) {
|
||||
WARNING("Failed to load CG %d", no);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cg = cg_load_data(dfile);
|
||||
archive_free_data(dfile);
|
||||
return cg;
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
/* Copyright (C) 2019 Nunuhara Cabbage <nunuhara@haniwa.technology>
|
||||
*
|
||||
* This program 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 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 this program; if not, see <http://gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "little_endian.h"
|
||||
#include "system4.h"
|
||||
#include "system4/cg.h"
|
||||
#include "system4/dcf.h"
|
||||
#include "system4/qnt.h"
|
||||
|
||||
static const uint8_t *dcf_get_qnt(const uint8_t *data)
|
||||
{
|
||||
if (data[0] != 'd' && data[1] != 'c' && data[2] != 'f' && data[3] != 0x20)
|
||||
return NULL;
|
||||
|
||||
int h2 = 8 + LittleEndian_getDW(data, 4);
|
||||
if (data[h2] != 'd' || data[h2+1] != 'f' || data[h2+2] != 'd' || data[h2+3] != 'l')
|
||||
return NULL;
|
||||
|
||||
int h3 = h2 + 8 + LittleEndian_getDW(data, h2+4);
|
||||
if (data[h3] != 'd' || data[h3+1] != 'c' || data[h3+2] != 'g' || data[h3+3] != 'd')
|
||||
return NULL;
|
||||
|
||||
if (data[h3+8] != 'Q' || data[h3+9] != 'N' || data[h3+10] != 'T' || data[h3+11] != '\0')
|
||||
return NULL;
|
||||
|
||||
return data + h3 + 8;
|
||||
}
|
||||
|
||||
bool dcf_checkfmt(const uint8_t *data)
|
||||
{
|
||||
return !strncmp((const char*)data, "dcf\x20", 4);
|
||||
}
|
||||
|
||||
void dcf_get_metrics(const uint8_t *data, possibly_unused size_t size, struct cg_metrics *m)
|
||||
{
|
||||
if (!(data = dcf_get_qnt(data)))
|
||||
return;
|
||||
qnt_get_metrics(data, m);
|
||||
}
|
||||
|
||||
void dcf_extract(const uint8_t *data, possibly_unused size_t size, struct cg *cg)
|
||||
{
|
||||
if (!(data = dcf_get_qnt(data)))
|
||||
return;
|
||||
qnt_extract(data, cg);
|
||||
}
|
||||
@@ -1,517 +0,0 @@
|
||||
/* Copyright (C) 2019 Nunuhara Cabbage <nunuhara@haniwa.technology>
|
||||
*
|
||||
* This program 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 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 this program; if not, see <http://gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <zlib.h>
|
||||
#include "little_endian.h"
|
||||
#include "system4.h"
|
||||
#include "system4/buffer.h"
|
||||
#include "system4/ex.h"
|
||||
#include "system4/string.h"
|
||||
|
||||
#define EX_ERROR(buf, fmt, ...) ERROR("At 0x%08x: " fmt, (uint32_t)(buf)->index, ##__VA_ARGS__)
|
||||
#define EX_WARNING(buf, fmt, ...) WARNING("At 0x%08x: " fmt, (uint32_t)(buf)->index, ##__VA_ARGS__)
|
||||
|
||||
const char *ex_strtype(enum ex_value_type type)
|
||||
{
|
||||
switch (type) {
|
||||
case EX_INT: return "int";
|
||||
case EX_FLOAT: return "float";
|
||||
case EX_STRING: return "string";
|
||||
case EX_TABLE: return "table";
|
||||
case EX_LIST: return "list";
|
||||
case EX_TREE: return "tree";
|
||||
default: return "unknown_type";
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t ex_decode_table[256];
|
||||
uint8_t ex_decode_table_inv[256];
|
||||
|
||||
static struct string *ex_read_string(struct buffer *r)
|
||||
{
|
||||
struct string *s = buffer_read_pascal_string(r);
|
||||
// TODO: at most 3 bytes padding, no need for full strlen
|
||||
s->size = strlen(s->text);
|
||||
// TODO: validate?
|
||||
return s;
|
||||
}
|
||||
|
||||
static bool ex_initialized = false;
|
||||
|
||||
static void ex_init(void)
|
||||
{
|
||||
// initialize table
|
||||
for (int i = 0; i < 256; i++) {
|
||||
int tmpfor = i;
|
||||
int tmp = tmpfor;
|
||||
tmp = (tmp & 0x55) + ((tmp >> 1) & 0x55);
|
||||
tmp = (tmp & 0x33) + ((tmp >> 2) & 0x33);
|
||||
tmp = (tmp & 0x0F) + ((tmp >> 4) & 0x0F);
|
||||
if ((tmp & 0x01) == 0) {
|
||||
tmpfor = ((tmpfor << (8 - tmp)) | (tmpfor >> tmp)) & 0xFF;
|
||||
} else {
|
||||
tmpfor = ((tmpfor >> (8 - tmp)) | (tmpfor << tmp)) & 0xFF;
|
||||
}
|
||||
ex_decode_table[i] = tmpfor;
|
||||
}
|
||||
// initialize inverse table
|
||||
for (int i = 0; i < 256; i++) {
|
||||
ex_decode_table_inv[ex_decode_table[i]] = i;
|
||||
}
|
||||
|
||||
ex_initialized = true;
|
||||
}
|
||||
|
||||
void ex_encode(uint8_t *buf, size_t size)
|
||||
{
|
||||
if (!ex_initialized)
|
||||
ex_init();
|
||||
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
buf[i] = ex_decode_table_inv[buf[i]];
|
||||
}
|
||||
}
|
||||
|
||||
static uint8_t *ex_decode(uint8_t *data, size_t *len, uint32_t *nr_blocks)
|
||||
{
|
||||
struct buffer r;
|
||||
uint32_t compressed_size;
|
||||
unsigned long uncompressed_size;
|
||||
|
||||
if (!ex_initialized)
|
||||
ex_init();
|
||||
|
||||
buffer_init(&r, data, *len);
|
||||
if (strncmp(buffer_strdata(&r), "HEAD", 4))
|
||||
EX_ERROR(&r, "Missing HEAD section marker");
|
||||
buffer_skip(&r, 8);
|
||||
|
||||
if (strncmp(buffer_strdata(&r), "EXTF", 4)) {
|
||||
EX_ERROR(&r, "Missing EXTF section marker");
|
||||
}
|
||||
buffer_skip(&r, 8);
|
||||
|
||||
if (nr_blocks)
|
||||
*nr_blocks = buffer_read_int32(&r);
|
||||
else
|
||||
buffer_read_int32(&r);
|
||||
|
||||
if (strncmp(buffer_strdata(&r), "DATA", 4)) {
|
||||
EX_ERROR(&r, "Missing DATA section marker");
|
||||
}
|
||||
buffer_skip(&r, 4);
|
||||
|
||||
compressed_size = buffer_read_int32(&r);
|
||||
uncompressed_size = buffer_read_int32(&r);
|
||||
|
||||
// decode compressed data
|
||||
for (size_t i = 0; i < compressed_size; i++) {
|
||||
data[r.index+i] = ex_decode_table[data[r.index+i]];
|
||||
}
|
||||
|
||||
uint8_t *out = xmalloc(uncompressed_size);
|
||||
int rv = uncompress(out, &uncompressed_size, (uint8_t*)buffer_strdata(&r), compressed_size);
|
||||
switch (rv) {
|
||||
case Z_BUF_ERROR: ERROR("Uncompress failed: Z_BUF_ERROR");
|
||||
case Z_MEM_ERROR: ERROR("Uncompress failed: Z_MEM_ERROR");
|
||||
case Z_DATA_ERROR: ERROR("Uncompress failed: Z_DATA_ERROR");
|
||||
case Z_OK: break;
|
||||
default: ERROR("Uncompress failed: %d", rv);
|
||||
}
|
||||
|
||||
// decompress
|
||||
*len = uncompressed_size;
|
||||
return out;
|
||||
}
|
||||
|
||||
static void ex_read_fields(struct buffer *r, struct ex_table *table);
|
||||
static void ex_read_table(struct buffer *r, struct ex_table *table, struct ex_field *fields, uint32_t nr_fields);
|
||||
static void ex_read_list(struct buffer *r, struct ex_list *list);
|
||||
|
||||
static void _ex_read_value(struct buffer *r, struct ex_value *value, struct ex_field *fields, uint32_t nr_fields)
|
||||
{
|
||||
switch (value->type) {
|
||||
case EX_INT:
|
||||
value->i = buffer_read_int32(r);
|
||||
break;
|
||||
case EX_FLOAT:
|
||||
value->f = buffer_read_float(r);
|
||||
break;
|
||||
case EX_STRING:
|
||||
value->s = buffer_read_pascal_string(r);
|
||||
break;
|
||||
case EX_TABLE:
|
||||
value->t = xcalloc(1, sizeof(struct ex_table));
|
||||
// XXX: if nr_fields is zero, we are NOT a sub-table and therefore need to read fields
|
||||
if (!nr_fields) {
|
||||
ex_read_fields(r, value->t);
|
||||
ex_read_table(r, value->t, value->t->fields, value->t->nr_fields);
|
||||
} else {
|
||||
ex_read_table(r, value->t, fields, nr_fields);
|
||||
}
|
||||
break;
|
||||
case EX_LIST:
|
||||
value->list = xcalloc(1, sizeof(struct ex_list));
|
||||
ex_read_list(r, value->list);
|
||||
break;
|
||||
default:
|
||||
EX_ERROR(r, "Unhandled value type: %d", value->type);
|
||||
}
|
||||
}
|
||||
|
||||
static void ex_read_value(struct buffer *r, struct ex_value *value, struct ex_field *fields, uint32_t nr_fields)
|
||||
{
|
||||
value->type = buffer_read_int32(r);
|
||||
_ex_read_value(r, value, fields, nr_fields);
|
||||
}
|
||||
|
||||
static void ex_read_field(struct buffer *r, struct ex_field *field)
|
||||
{
|
||||
field->type = buffer_read_int32(r);
|
||||
if (field->type < EX_INT || field->type > EX_TABLE)
|
||||
EX_ERROR(r, "Unknown/invalid field type: %d", field->type);
|
||||
|
||||
field->name = ex_read_string(r);
|
||||
field->has_value = buffer_read_int32(r);
|
||||
field->is_index = buffer_read_int32(r);
|
||||
if (field->has_value) {
|
||||
field->value.type = field->type;
|
||||
_ex_read_value(r, &field->value, NULL, 0);
|
||||
}
|
||||
if (field->has_value && field->has_value != 1)
|
||||
EX_WARNING(r, "Non-boolean for field->has_value: %d", field->has_value);
|
||||
if (field->is_index && field->is_index != 1)
|
||||
EX_WARNING(r, "Non-boolean for field->is_index: %d", field->is_index);
|
||||
|
||||
if (field->type == EX_TABLE) {
|
||||
field->nr_subfields = buffer_read_int32(r);
|
||||
if (field->nr_subfields > 255)
|
||||
EX_ERROR(r, "Too many subfields: %u", field->nr_subfields);
|
||||
|
||||
field->subfields = xcalloc(field->nr_subfields, sizeof(struct ex_field));
|
||||
for (uint32_t i = 0; i < field->nr_subfields; i++) {
|
||||
ex_read_field(r, &field->subfields[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void ex_read_fields(struct buffer *r, struct ex_table *table)
|
||||
{
|
||||
table->nr_fields = buffer_read_int32(r);
|
||||
table->fields = xcalloc(table->nr_fields, sizeof(struct ex_field));
|
||||
for (uint32_t i = 0; i < table->nr_fields; i++) {
|
||||
ex_read_field(r, &table->fields[i]);
|
||||
}
|
||||
}
|
||||
|
||||
enum table_layout {
|
||||
TABLE_DEFAULT,
|
||||
TABLE_COLUMNS_FIRST,
|
||||
TABLE_ROWS_FIRST
|
||||
};
|
||||
|
||||
static enum table_layout table_layout = TABLE_DEFAULT;
|
||||
|
||||
static void ex_read_table(struct buffer *r, struct ex_table *table, struct ex_field *fields, uint32_t nr_fields)
|
||||
{
|
||||
// NOTE: Starting in Evenicle, the rows/columns quantities are reversed
|
||||
if (table_layout == TABLE_ROWS_FIRST) {
|
||||
table->nr_rows = buffer_read_int32(r);
|
||||
table->nr_columns = buffer_read_int32(r);
|
||||
} else {
|
||||
table->nr_columns = buffer_read_int32(r);
|
||||
table->nr_rows = buffer_read_int32(r);
|
||||
}
|
||||
|
||||
// try switching to column-major order if fields don't make sense
|
||||
if (table_layout == TABLE_DEFAULT && table->nr_columns != nr_fields) {
|
||||
if (table->nr_rows == nr_fields) {
|
||||
uint32_t tmp = table->nr_columns;
|
||||
table->nr_columns = table->nr_rows;
|
||||
table->nr_rows = tmp;
|
||||
table_layout = TABLE_ROWS_FIRST;
|
||||
} else {
|
||||
EX_ERROR(r, "Number of fields doesn't match number of columns: %u, %u", table->nr_columns, nr_fields);
|
||||
}
|
||||
} else if (table->nr_columns != nr_fields) {
|
||||
EX_ERROR(r, "Number of fields doesn't match number of columns: %u, %u", table->nr_columns, nr_fields);
|
||||
}
|
||||
|
||||
table->rows = xcalloc(table->nr_rows, sizeof(struct ex_value*));
|
||||
for (uint32_t i = 0; i < table->nr_rows; i++) {
|
||||
table->rows[i] = xcalloc(table->nr_columns, sizeof(struct ex_value));
|
||||
for (uint32_t j = 0; j < table->nr_columns; j++) {
|
||||
ex_read_value(r, &table->rows[i][j], fields[j].subfields, fields[j].nr_subfields);
|
||||
if (table->rows[i][j].type != fields[j].type) {
|
||||
// broken table in Rance 03?
|
||||
EX_WARNING(r, "Column type doesn't match field type: expected %s; got %s",
|
||||
ex_strtype(fields[j].type), ex_strtype(table->rows[i][j].type));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void ex_read_list(struct buffer *r, struct ex_list *list)
|
||||
{
|
||||
list->nr_items = buffer_read_int32(r);
|
||||
list->items = xcalloc(list->nr_items, sizeof(struct ex_list_item));
|
||||
for (uint32_t i = 0; i < list->nr_items; i++) {
|
||||
list->items[i].value.type = buffer_read_int32(r);
|
||||
list->items[i].size = buffer_read_int32(r);
|
||||
size_t data_loc = r->index;
|
||||
_ex_read_value(r, &list->items[i].value, NULL, 0);
|
||||
if (r->index - data_loc != list->items[i].size) {
|
||||
EX_ERROR(r, "Incorrect size for list item: %" SIZE_T_FMT "u / %" SIZE_T_FMT "u",
|
||||
list->items[i].size, r->index - data_loc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void ex_read_tree(struct buffer *r, struct ex_tree *tree)
|
||||
{
|
||||
tree->name = ex_read_string(r);
|
||||
uint32_t is_leaf = buffer_read_int32(r);
|
||||
if (is_leaf > 1)
|
||||
EX_ERROR(r, "tree->is_leaf is not a boolean: %u", is_leaf);
|
||||
tree->is_leaf = is_leaf;
|
||||
|
||||
if (!tree->is_leaf) {
|
||||
tree->nr_children = buffer_read_int32(r);
|
||||
tree->children = xcalloc(tree->nr_children, sizeof(struct ex_tree));
|
||||
for (uint32_t i = 0; i < tree->nr_children; i++) {
|
||||
ex_read_tree(r, &tree->children[i]);
|
||||
}
|
||||
} else {
|
||||
tree->leaf.value.type = buffer_read_int32(r);
|
||||
tree->leaf.size = buffer_read_int32(r);
|
||||
size_t data_loc = r->index;
|
||||
tree->leaf.name = ex_read_string(r);
|
||||
_ex_read_value(r, &tree->leaf.value, NULL, 0);
|
||||
|
||||
if (r->index - data_loc != tree->leaf.size) {
|
||||
EX_ERROR(r, "Incorrect size for leaf node: %" SIZE_T_FMT "u / %" SIZE_T_FMT "u",
|
||||
tree->leaf.size, r->index - data_loc);
|
||||
}
|
||||
|
||||
int32_t zero = buffer_read_int32(r);
|
||||
if (zero) {
|
||||
EX_ERROR(r, "Expected 0 after leaf node: 0x%x at 0x%zx", zero, r->index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void ex_read_block(struct buffer *r, struct ex_block *block)
|
||||
{
|
||||
block->val.type = buffer_read_int32(r);
|
||||
if (block->val.type < EX_INT || block->val.type > EX_TREE)
|
||||
EX_ERROR(r, "Unknown/invalid block type: %d", block->val.type);
|
||||
|
||||
block->size = buffer_read_int32(r);
|
||||
if (block->size > buffer_remaining(r))
|
||||
EX_ERROR(r, "Block size extends past end of file: %" SIZE_T_FMT "u", block->size);
|
||||
|
||||
size_t data_loc = r->index;
|
||||
block->name = ex_read_string(r);
|
||||
|
||||
switch (block->val.type) {
|
||||
case EX_INT:
|
||||
block->val.i = buffer_read_int32(r);
|
||||
break;
|
||||
case EX_FLOAT:
|
||||
block->val.f = buffer_read_float(r);
|
||||
break;
|
||||
case EX_STRING:
|
||||
block->val.s = buffer_read_pascal_string(r);
|
||||
break;
|
||||
case EX_TABLE:
|
||||
block->val.t = xcalloc(1, sizeof(struct ex_table));
|
||||
ex_read_fields(r, block->val.t);
|
||||
ex_read_table(r, block->val.t, block->val.t->fields, block->val.t->nr_fields);
|
||||
break;
|
||||
case EX_LIST:
|
||||
block->val.list = xcalloc(1, sizeof(struct ex_list));
|
||||
ex_read_list(r, block->val.list);
|
||||
break;
|
||||
case EX_TREE:
|
||||
block->val.tree = xcalloc(1, sizeof(struct ex_tree));
|
||||
ex_read_tree(r, block->val.tree);
|
||||
break;
|
||||
}
|
||||
|
||||
if (r->index - data_loc != block->size) {
|
||||
EX_ERROR(r, "Incorrect block size: %" SIZE_T_FMT "u / %" SIZE_T_FMT "u",
|
||||
r->index - data_loc, block->size);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t *ex_decrypt(const char *path, size_t *size, uint32_t *nr_blocks)
|
||||
{
|
||||
FILE *fp;
|
||||
long len;
|
||||
uint8_t *buf = NULL;
|
||||
|
||||
if (!(fp = fopen(path, "rb")))
|
||||
ERROR("fopen failed: %s", strerror(errno));
|
||||
|
||||
fseek(fp, 0, SEEK_END);
|
||||
len = ftell(fp);
|
||||
fseek(fp, 0, SEEK_SET);
|
||||
|
||||
|
||||
buf = xmalloc(len+1);
|
||||
if (fread(buf, len, 1, fp) != 1)
|
||||
ERROR("fread failed: %s", strerror(errno));
|
||||
if (fclose(fp))
|
||||
ERROR("fclose failed: %s", strerror(errno));
|
||||
buf[len] = '\0';
|
||||
|
||||
*size = len;
|
||||
uint8_t *decoded = ex_decode(buf, size, nr_blocks);
|
||||
free(buf);
|
||||
return decoded;
|
||||
}
|
||||
|
||||
struct ex *ex_read(const char *path)
|
||||
{
|
||||
size_t decoded_len;
|
||||
uint32_t nr_blocks;
|
||||
uint8_t *decoded = ex_decrypt(path, &decoded_len, &nr_blocks);
|
||||
|
||||
struct buffer r;
|
||||
buffer_init(&r, decoded, decoded_len);
|
||||
|
||||
struct ex *ex = xmalloc(sizeof(struct ex));
|
||||
ex->nr_blocks = nr_blocks;
|
||||
ex->blocks = xcalloc(nr_blocks, sizeof(struct ex_block));
|
||||
for (size_t i = 0; i < nr_blocks; i++) {
|
||||
ex_read_block(&r, &ex->blocks[i]);
|
||||
}
|
||||
|
||||
free(decoded);
|
||||
return ex;
|
||||
}
|
||||
|
||||
static void ex_free_table(struct ex_table *table);
|
||||
static void ex_free_list(struct ex_list *list);
|
||||
static void ex_free_tree(struct ex_tree *tree);
|
||||
|
||||
static void ex_free_value(struct ex_value *value)
|
||||
{
|
||||
switch (value->type) {
|
||||
case EX_STRING:
|
||||
free_string(value->s);
|
||||
break;
|
||||
case EX_TABLE:
|
||||
ex_free_table(value->t);
|
||||
break;
|
||||
case EX_LIST:
|
||||
ex_free_list(value->list);
|
||||
break;
|
||||
case EX_TREE:
|
||||
ex_free_tree(value->tree);
|
||||
free(value->tree);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void ex_free_values(struct ex_value *values, uint32_t n)
|
||||
{
|
||||
for (uint32_t i = 0; i < n; i++) {
|
||||
ex_free_value(&values[i]);
|
||||
}
|
||||
free(values);
|
||||
}
|
||||
|
||||
static void ex_free_fields(struct ex_field *fields, uint32_t n)
|
||||
{
|
||||
for (uint32_t i = 0; i < n; i++) {
|
||||
free_string(fields[i].name);
|
||||
if (fields[i].type == EX_STRING && fields[i].has_value)
|
||||
free_string(fields[i].value.s);
|
||||
ex_free_fields(fields[i].subfields, fields[i].nr_subfields);
|
||||
}
|
||||
free(fields);
|
||||
}
|
||||
|
||||
static void ex_free_table(struct ex_table *table)
|
||||
{
|
||||
ex_free_fields(table->fields, table->nr_fields);
|
||||
for (uint32_t i = 0; i < table->nr_rows; i++) {
|
||||
ex_free_values(table->rows[i], table->nr_columns);
|
||||
}
|
||||
free(table->rows);
|
||||
free(table);
|
||||
}
|
||||
|
||||
static void ex_free_list(struct ex_list *list)
|
||||
{
|
||||
for (uint32_t i = 0; i < list->nr_items; i++) {
|
||||
ex_free_value(&list->items[i].value);
|
||||
}
|
||||
free(list->items);
|
||||
free(list);
|
||||
}
|
||||
|
||||
static void ex_free_tree(struct ex_tree *tree)
|
||||
{
|
||||
free_string(tree->name);
|
||||
|
||||
if (tree->is_leaf) {
|
||||
free_string(tree->leaf.name);
|
||||
ex_free_value(&tree->leaf.value);
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < tree->nr_children; i++) {
|
||||
ex_free_tree(&tree->children[i]);
|
||||
}
|
||||
free(tree->children);
|
||||
}
|
||||
|
||||
void ex_free(struct ex *ex)
|
||||
{
|
||||
for (uint32_t i = 0; i < ex->nr_blocks; i++) {
|
||||
struct ex_block *block = &ex->blocks[i];
|
||||
free_string(block->name);
|
||||
switch (block->val.type) {
|
||||
case EX_STRING:
|
||||
free_string(block->val.s);
|
||||
break;
|
||||
case EX_TABLE:
|
||||
ex_free_table(block->val.t);
|
||||
break;
|
||||
case EX_LIST:
|
||||
ex_free_list(block->val.list);
|
||||
break;
|
||||
case EX_TREE:
|
||||
ex_free_tree(block->val.tree);
|
||||
free(block->val.tree);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
free(ex->blocks);
|
||||
free(ex);
|
||||
}
|
||||
|
||||
-135
@@ -1,135 +0,0 @@
|
||||
/* Copyright (C) 2019 Nunuhara Cabbage <nunuhara@haniwa.technology>
|
||||
*
|
||||
* This program 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 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 this program; if not, see <http://gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include "system4.h"
|
||||
|
||||
#if (defined(_WIN32) || defined(__WIN32__))
|
||||
#include <Windows.h>
|
||||
#include <direct.h>
|
||||
static int make_dir(const char *path, possibly_unused int mode)
|
||||
{
|
||||
int nr_wchars = MultiByteToWideChar(CP_UTF8, 0, path, -1, NULL, 0);
|
||||
wchar_t *wpath = xmalloc(nr_wchars * sizeof(wchar_t));
|
||||
MultiByteToWideChar(CP_UTF8, 0, path, -1, wpath, nr_wchars);
|
||||
|
||||
int r = _wmkdir(wpath);
|
||||
free(wpath);
|
||||
return r;
|
||||
}
|
||||
#else
|
||||
#define make_dir(path, mode) mkdir(path, mode)
|
||||
#endif
|
||||
|
||||
FILE *file_open_utf8(const char *path, const char *mode)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
int nr_wchars = MultiByteToWideChar(CP_UTF8, 0, path, -1, NULL, 0);
|
||||
wchar_t *wpath = xmalloc(nr_wchars * sizeof(wchar_t));
|
||||
MultiByteToWideChar(CP_UTF8, 0, path, -1, wpath, nr_wchars);
|
||||
|
||||
wchar_t wmode[64];
|
||||
mbstowcs(wmode, mode, 64);
|
||||
|
||||
FILE *f = _wfopen(wpath, wmode);
|
||||
free(wpath);
|
||||
return f;
|
||||
#else
|
||||
return fopen(path, mode);
|
||||
#endif
|
||||
}
|
||||
|
||||
void *file_read(const char *path, size_t *len_out)
|
||||
{
|
||||
FILE *fp;
|
||||
long len;
|
||||
uint8_t *buf;
|
||||
|
||||
if (!(fp = fopen(path, "rb")))
|
||||
return NULL;
|
||||
|
||||
fseek(fp, 0, SEEK_END);
|
||||
len = ftell(fp);
|
||||
fseek(fp, 0, SEEK_SET);
|
||||
|
||||
buf = xmalloc(len + 1);
|
||||
if (fread(buf, len, 1, fp) != 1) {
|
||||
free(buf);
|
||||
return NULL;
|
||||
}
|
||||
if (fclose(fp)) {
|
||||
free(buf);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (len_out)
|
||||
*len_out = len;
|
||||
|
||||
buf[len] = 0;
|
||||
return buf;
|
||||
}
|
||||
|
||||
bool file_exists(const char *path)
|
||||
{
|
||||
return access(path, F_OK) != -1;
|
||||
}
|
||||
|
||||
// Adapted from http://stackoverflow.com/a/2336245/119527
|
||||
int mkdir_p(const char *path)
|
||||
{
|
||||
const size_t len = strlen(path);
|
||||
char _path[PATH_MAX];
|
||||
char *p;
|
||||
|
||||
errno = 0;
|
||||
|
||||
// Copy string so its mutable
|
||||
if (len > sizeof(_path)-1) {
|
||||
errno = ENAMETOOLONG;
|
||||
return -1;
|
||||
}
|
||||
strcpy(_path, path);
|
||||
|
||||
// Iterate the string
|
||||
for (p = _path + 1; *p; p++) {
|
||||
if (*p == '/') {
|
||||
// Temporarily truncate
|
||||
*p = '\0';
|
||||
|
||||
if (make_dir(_path, S_IRWXU) != 0) {
|
||||
if (errno != EEXIST)
|
||||
return -1;
|
||||
}
|
||||
|
||||
*p = '/';
|
||||
}
|
||||
}
|
||||
|
||||
if (make_dir(_path, S_IRWXU) != 0) {
|
||||
if (errno != EEXIST)
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
-330
@@ -1,330 +0,0 @@
|
||||
/* Copyright (C) 2019 Nunuhara Cabbage <nunuhara@haniwa.technology>
|
||||
*
|
||||
* This program 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 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 this program; if not, see <http://gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <zlib.h>
|
||||
#include "little_endian.h"
|
||||
#include "file.h"
|
||||
#include "system4.h"
|
||||
#include "system4/archive.h"
|
||||
#include "system4/buffer.h"
|
||||
#include "system4/cg.h"
|
||||
#include "system4/ajp.h"
|
||||
|
||||
enum flat_data_type {
|
||||
FLAG_CG = 2,
|
||||
FLAT_ZLIB = 5,
|
||||
};
|
||||
|
||||
struct flat_entry {
|
||||
size_t off;
|
||||
size_t size;
|
||||
int type;
|
||||
};
|
||||
|
||||
struct flat_data {
|
||||
struct archive_data super;
|
||||
struct flat_entry *entry;
|
||||
bool allocated;
|
||||
};
|
||||
|
||||
struct flat_archive {
|
||||
struct archive ar;
|
||||
bool allocated;
|
||||
size_t libl_off;
|
||||
size_t talt_off;
|
||||
size_t filesize;
|
||||
unsigned nr_libl_files;
|
||||
struct flat_entry *libl_files;
|
||||
unsigned nr_talt_files;
|
||||
struct flat_entry *talt_files;
|
||||
uint8_t *data;
|
||||
};
|
||||
|
||||
static const char *get_file_extension(int type, const char *data)
|
||||
{
|
||||
switch (type) {
|
||||
case FLAG_CG:
|
||||
if (!strncmp(data, "AJP", 4))
|
||||
return ".ajp";
|
||||
if (!strncmp(data, "QNT", 4))
|
||||
return ".qnt";
|
||||
return ".img";
|
||||
case FLAT_ZLIB:
|
||||
return ".z.dat";
|
||||
default:
|
||||
return ".dat";
|
||||
}
|
||||
}
|
||||
|
||||
static void flat_free_data(struct archive_data *data)
|
||||
{
|
||||
struct flat_data *flat = (struct flat_data*)data;
|
||||
if (flat->allocated)
|
||||
free(data->data);
|
||||
free(data->name);
|
||||
free(data);
|
||||
}
|
||||
|
||||
static void flat_free(struct archive *_ar)
|
||||
{
|
||||
struct flat_archive *ar = (struct flat_archive*)_ar;
|
||||
if (ar->allocated)
|
||||
free(ar->data);
|
||||
free(ar->libl_files);
|
||||
free(ar->talt_files);
|
||||
free(ar);
|
||||
}
|
||||
|
||||
static struct flat_entry *flat_get_entry(struct flat_archive *ar, unsigned no)
|
||||
{
|
||||
if (no < ar->nr_libl_files)
|
||||
return &ar->libl_files[no];
|
||||
|
||||
no -= ar->nr_libl_files;
|
||||
if (no < ar->nr_talt_files)
|
||||
return &ar->talt_files[no];
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static struct archive_data *flat_get(struct archive *_ar, int no)
|
||||
{
|
||||
struct flat_archive *ar = (struct flat_archive*)_ar;
|
||||
struct flat_entry *e = flat_get_entry(ar, no);
|
||||
struct flat_data *data = xcalloc(1, sizeof(struct flat_data));
|
||||
|
||||
data->super.data = ar->data + e->off;
|
||||
data->super.size = e->size;
|
||||
data->super.name = xmalloc(256);
|
||||
snprintf(data->super.name, 256, "%d%s", no, get_file_extension(e->type, (const char*)ar->data+e->off));
|
||||
data->super.no = no;
|
||||
data->super.archive = _ar;
|
||||
data->entry = e;
|
||||
return &data->super;
|
||||
}
|
||||
|
||||
static bool flat_load_file(possibly_unused struct archive_data *data)
|
||||
{
|
||||
struct flat_archive *ar = (struct flat_archive*)data->archive;
|
||||
struct flat_data *flatdata = (struct flat_data*)data;
|
||||
struct flat_entry *e = flatdata->entry;
|
||||
|
||||
// inflate zlib compressed data
|
||||
if (e->type == FLAT_ZLIB && ar->data[e->off+4] == 0x78) {
|
||||
unsigned long size = LittleEndian_getDW(ar->data, e->off);
|
||||
uint8_t *out = xmalloc(size);
|
||||
if (uncompress(out, &size, ar->data + e->off + 4, e->size - 4) != Z_OK) {
|
||||
WARNING("uncompress failed");
|
||||
free(out);
|
||||
return false;
|
||||
}
|
||||
data->data = out;
|
||||
data->size = size;
|
||||
flatdata->allocated = true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void flat_for_each(struct archive *_ar, void (*iter)(struct archive_data *data, void *user), void *user)
|
||||
{
|
||||
struct flat_archive *ar = (struct flat_archive*)_ar;
|
||||
for (unsigned i = 0; i < ar->nr_libl_files + ar->nr_talt_files; i++) {
|
||||
struct archive_data *data = flat_get(_ar, i);
|
||||
if (!data)
|
||||
continue;
|
||||
iter(data, user);
|
||||
flat_free_data(data);
|
||||
}
|
||||
}
|
||||
|
||||
static struct archive_ops flat_archive_ops = {
|
||||
.get = flat_get,
|
||||
.load_file = flat_load_file,
|
||||
.for_each = flat_for_each,
|
||||
.free_data = flat_free_data,
|
||||
.free = flat_free,
|
||||
};
|
||||
|
||||
static bool is_image(const char *data)
|
||||
{
|
||||
if (!strncmp(data, "AJP", 4))
|
||||
return true;
|
||||
if (!strncmp(data, "QNT", 4))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static void read_libl(uint8_t *data, size_t size, struct flat_archive *ar)
|
||||
{
|
||||
struct buffer r;
|
||||
buffer_init(&r, data, size);
|
||||
|
||||
buffer_skip(&r, 8);
|
||||
ar->nr_libl_files = buffer_read_int32(&r);
|
||||
ar->libl_files = xcalloc(ar->nr_libl_files, sizeof(struct flat_entry));
|
||||
for (unsigned i = 0; i < ar->nr_libl_files; i++) {
|
||||
int len = buffer_read_int32(&r);
|
||||
buffer_skip(&r, len);
|
||||
buffer_align(&r, 4);
|
||||
|
||||
ar->libl_files[i].type = buffer_read_int32(&r);
|
||||
ar->libl_files[i].size = buffer_read_int32(&r);
|
||||
ar->libl_files[i].off = ar->libl_off + r.index;
|
||||
|
||||
if (ar->libl_files[i].type == FLAG_CG && !is_image(buffer_strdata(&r))) {
|
||||
// XXX: special case: CG usually (not always!) has extra int32
|
||||
if (is_image(buffer_strdata(&r)+4)) {
|
||||
ar->libl_files[i].off += 4;
|
||||
ar->libl_files[i].size -= 4;
|
||||
buffer_skip(&r, 4);
|
||||
} else {
|
||||
WARNING("Couldn't read CG data in LIBL section");
|
||||
}
|
||||
}
|
||||
|
||||
buffer_skip(&r, ar->libl_files[i].size);
|
||||
buffer_align(&r, 4);
|
||||
}
|
||||
|
||||
if (r.index != size)
|
||||
WARNING("Junk at end of LIBL section");
|
||||
}
|
||||
|
||||
static void read_talt(uint8_t *data, size_t size, struct flat_archive *ar)
|
||||
{
|
||||
if (size < 16)
|
||||
return;
|
||||
|
||||
struct buffer r;
|
||||
buffer_init(&r, data, size);
|
||||
|
||||
buffer_skip(&r, 8);
|
||||
ar->nr_talt_files = buffer_read_int32(&r);
|
||||
ar->talt_files = xcalloc(ar->nr_talt_files, sizeof(struct flat_entry));
|
||||
for (unsigned i = 0; i < ar->nr_talt_files; i++) {
|
||||
ar->talt_files[i].size = buffer_read_int32(&r);
|
||||
ar->talt_files[i].off = ar->talt_off + r.index;
|
||||
|
||||
if (strncmp(buffer_strdata(&r), "AJP", 4))
|
||||
ERROR("NOT AN AJP FILE");
|
||||
|
||||
buffer_skip(&r, ar->talt_files[i].size);
|
||||
buffer_align(&r, 4);
|
||||
|
||||
unsigned nr_meta = buffer_read_int32(&r);
|
||||
for (unsigned j = 0; j < nr_meta; j++) {
|
||||
unsigned size = buffer_read_int32(&r);
|
||||
buffer_skip(&r, size);
|
||||
buffer_align(&r, 4);
|
||||
buffer_read_int32(&r);
|
||||
buffer_read_int32(&r);
|
||||
buffer_read_int32(&r);
|
||||
buffer_read_int32(&r);
|
||||
}
|
||||
}
|
||||
|
||||
if (r.index != size)
|
||||
WARNING("Junk at end of TALT section");
|
||||
}
|
||||
|
||||
struct archive *flat_open(uint8_t *data, size_t size, int *error)
|
||||
{
|
||||
struct flat_archive *ar = NULL;
|
||||
size_t off, libl_off, talt_off;
|
||||
struct buffer r;
|
||||
buffer_init(&r, data, size);
|
||||
|
||||
if (!strncmp(buffer_strdata(&r), "ELNA", 4)) {
|
||||
buffer_seek(&r, 8);
|
||||
}
|
||||
|
||||
// FLAT section
|
||||
if (strncmp(buffer_strdata(&r), "FLAT", 4))
|
||||
goto exit_err;
|
||||
buffer_skip(&r, 4);
|
||||
off = buffer_read_int32(&r);
|
||||
buffer_skip(&r, off);
|
||||
|
||||
if (!strncmp(buffer_strdata(&r), "TMNL", 4)) {
|
||||
buffer_skip(&r, 4);
|
||||
off = buffer_read_int32(&r);
|
||||
buffer_skip(&r, off);
|
||||
}
|
||||
|
||||
// MTLC section
|
||||
if (strncmp(buffer_strdata(&r), "MTLC", 4))
|
||||
goto exit_err;
|
||||
buffer_skip(&r, 4);
|
||||
off = buffer_read_int32(&r);
|
||||
buffer_skip(&r, off);
|
||||
|
||||
// LIBL section
|
||||
if (strncmp(buffer_strdata(&r), "LIBL", 4))
|
||||
goto exit_err;
|
||||
libl_off = r.index;
|
||||
buffer_skip(&r, 4);
|
||||
off = buffer_read_int32(&r);
|
||||
buffer_skip(&r, off);
|
||||
|
||||
// TALT section
|
||||
talt_off = r.index;
|
||||
if (r.index < size) {
|
||||
if (strncmp(buffer_strdata(&r), "TALT", 4))
|
||||
goto exit_err;
|
||||
buffer_skip(&r, 4);
|
||||
off = buffer_read_int32(&r);
|
||||
}
|
||||
|
||||
if (r.index + off < size)
|
||||
WARNING("Junk at end of FLAT file? %uB/%uB", (unsigned)r.index+off, (unsigned)size);
|
||||
else if (r.index + off > size)
|
||||
WARNING("FLAT file truncated? %uB/%uB", (unsigned)size, (unsigned)r.index+off);
|
||||
|
||||
ar = xcalloc(1, sizeof(struct flat_archive));
|
||||
ar->libl_off = libl_off;
|
||||
ar->talt_off = talt_off;
|
||||
ar->filesize = size;
|
||||
ar->data = data;
|
||||
read_libl(data+libl_off, talt_off - libl_off, ar);
|
||||
read_talt(data+talt_off, size - talt_off, ar);
|
||||
|
||||
ar->ar.ops = &flat_archive_ops;
|
||||
return &ar->ar;
|
||||
exit_err:
|
||||
*error = ARCHIVE_BAD_ARCHIVE_ERROR;
|
||||
free(ar);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct archive *flat_open_file(const char *path, possibly_unused int flags, int *error)
|
||||
{
|
||||
size_t size;
|
||||
uint8_t *data = file_read(path, &size);
|
||||
if (!data)
|
||||
return NULL;
|
||||
|
||||
struct flat_archive *ar = (struct flat_archive*)flat_open(data, size, error);
|
||||
if (!ar) {
|
||||
free(data);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ar->allocated = true;
|
||||
return (struct archive*)ar;
|
||||
}
|
||||
@@ -1,193 +0,0 @@
|
||||
/* Copyright (C) 2019 Nunuhara Cabbage <nunuhara@haniwa.technology>
|
||||
*
|
||||
* This program 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 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 this program; if not, see <http://gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include <zlib.h>
|
||||
#include "system4.h"
|
||||
#include "system4/buffer.h"
|
||||
#include "system4/fnl.h"
|
||||
#include "system4/utfsjis.h"
|
||||
#include "file.h"
|
||||
#include "little_endian.h"
|
||||
|
||||
static unsigned int sjis_code_to_index(uint16_t code)
|
||||
{
|
||||
// one byte
|
||||
if (code < 0x20)
|
||||
return 0;
|
||||
if (code < 0x7f)
|
||||
return code - 0x20;
|
||||
if (code < 0xa1)
|
||||
return 0;
|
||||
if (code < 0xe0)
|
||||
return code - 0x42;
|
||||
|
||||
// two byte
|
||||
uint8_t fst = code >> 8;
|
||||
uint8_t snd = code & 0xFF;
|
||||
|
||||
if (fst < 0x81)
|
||||
return 0;
|
||||
if (snd < 0x40 || snd == 0x7f || snd > 0xfc)
|
||||
return 0;
|
||||
|
||||
return code - (0x80a2 + (68 * (fst - 0x81)) + (snd > 0x7f ? 1 : 0));
|
||||
}
|
||||
|
||||
struct fnl_glyph *fnl_get_glyph(struct fnl_font_face *font, uint16_t code)
|
||||
{
|
||||
unsigned int index = sjis_code_to_index(code);
|
||||
|
||||
if (index >= font->nr_glyphs)
|
||||
index = 0;
|
||||
if (!font->glyphs[index].data_pos)
|
||||
index = 0;
|
||||
|
||||
return &font->glyphs[index];
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the closest font size.
|
||||
*/
|
||||
struct fnl_font_size *fnl_get_font_size(struct fnl_font *font, float size)
|
||||
{
|
||||
float min_diff = 9999;
|
||||
struct fnl_font_size *closest = &font->sizes[0];
|
||||
for (unsigned i = 0; i < font->nr_sizes; i++) {
|
||||
float diff = fabsf(font->sizes[i].size - size);
|
||||
if (diff < min_diff) {
|
||||
min_diff = diff;
|
||||
closest = &font->sizes[i];
|
||||
}
|
||||
}
|
||||
return closest;
|
||||
}
|
||||
|
||||
static void fnl_read_glyph(struct buffer *r, uint32_t height, struct fnl_glyph *dst)
|
||||
{
|
||||
dst->height = height;
|
||||
dst->real_width = buffer_read_u16(r);
|
||||
dst->data_pos = buffer_read_int32(r);
|
||||
dst->data_compsize = buffer_read_int32(r);
|
||||
}
|
||||
|
||||
uint8_t *fnl_glyph_data(struct fnl *fnl, struct fnl_glyph *g, unsigned long *size)
|
||||
{
|
||||
if (!g->data_pos) {
|
||||
*size = 0;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
*size = g->height * g->height * 4; // FIXME: determine real bound
|
||||
uint8_t *data = xmalloc(*size);
|
||||
int rv = uncompress(data, size, fnl->data + g->data_pos, g->data_compsize);
|
||||
if (rv != Z_OK) {
|
||||
if (rv == Z_BUF_ERROR)
|
||||
ERROR("uncompress failed: Z_BUF_ERROR");
|
||||
else if (rv == Z_MEM_ERROR)
|
||||
ERROR("uncompress failed: Z_MEM_ERROR");
|
||||
else if (rv == Z_DATA_ERROR)
|
||||
ERROR("uncompress failed: Z_DATA_ERROR");
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
static void fnl_read_font_face(struct buffer *r, struct fnl_font_face *dst)
|
||||
{
|
||||
dst->height = buffer_read_int32(r);
|
||||
dst->uk = buffer_read_int32(r);
|
||||
dst->nr_glyphs = buffer_read_int32(r);
|
||||
|
||||
dst->glyphs = xcalloc(dst->nr_glyphs, sizeof(struct fnl_glyph));
|
||||
for (size_t i = 0; i < dst->nr_glyphs; i++) {
|
||||
fnl_read_glyph(r, dst->height, &dst->glyphs[i]);
|
||||
}
|
||||
|
||||
for (unsigned i = 0; i < 11; i++) {
|
||||
dst->_sizes[i] = dst->height / (float)(i+2);
|
||||
}
|
||||
}
|
||||
|
||||
static void fnl_read_font(struct buffer *r, struct fnl_font *dst)
|
||||
{
|
||||
dst->nr_faces = buffer_read_int32(r);
|
||||
|
||||
dst->faces = xcalloc(dst->nr_faces, sizeof(struct fnl_font_face));
|
||||
for (size_t i = 0; i < dst->nr_faces; i++) {
|
||||
dst->faces[i].font = dst;
|
||||
fnl_read_font_face(r, &dst->faces[i]);
|
||||
}
|
||||
|
||||
dst->nr_sizes = dst->nr_faces * 11;
|
||||
dst->sizes = xcalloc(dst->nr_sizes, sizeof(struct fnl_font_size));
|
||||
for (unsigned face = 0, s = 0; face < dst->nr_faces; face++) {
|
||||
for (unsigned i = 0; i < 11; i++) {
|
||||
dst->sizes[s++] = (struct fnl_font_size) {
|
||||
.face = face,
|
||||
.size = dst->faces[face]._sizes[i],
|
||||
.denominator = i+2
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
struct fnl *fnl_open(const char *path)
|
||||
{
|
||||
size_t filesize;
|
||||
struct fnl *fnl = xcalloc(1, sizeof(struct fnl));
|
||||
fnl->data = file_read(path, &filesize);
|
||||
|
||||
if (fnl->data[0] != 'F' || fnl->data[1] != 'N' || fnl->data[2] != 'A' || fnl->data[3] != '\0')
|
||||
goto err;
|
||||
|
||||
struct buffer r;
|
||||
buffer_init(&r, fnl->data, filesize);
|
||||
buffer_skip(&r, 4);
|
||||
fnl->uk = buffer_read_int32(&r);
|
||||
if (fnl->uk != 0)
|
||||
WARNING("Unexpected value for fnl->uk: %d", fnl->uk);
|
||||
|
||||
fnl->filesize = buffer_read_int32(&r);
|
||||
fnl->data_offset = buffer_read_int32(&r);
|
||||
fnl->nr_fonts = buffer_read_int32(&r);
|
||||
|
||||
fnl->fonts = xcalloc(fnl->nr_fonts, sizeof(struct fnl_font));
|
||||
for (size_t i = 0; i < fnl->nr_fonts; i++) {
|
||||
fnl->fonts[i].fnl = fnl;
|
||||
fnl_read_font(&r, &fnl->fonts[i]);
|
||||
}
|
||||
return fnl;
|
||||
err:
|
||||
free(fnl->data);
|
||||
free(fnl);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void fnl_free(struct fnl *fnl)
|
||||
{
|
||||
for (size_t font = 0; font < fnl->nr_fonts; font++) {
|
||||
for (size_t face = 0; face < fnl->fonts[font].nr_faces; face++) {
|
||||
free(fnl->fonts[font].faces[face].glyphs);
|
||||
}
|
||||
free(fnl->fonts[font].faces);
|
||||
}
|
||||
free(fnl->fonts);
|
||||
free(fnl->data);
|
||||
free(fnl);
|
||||
}
|
||||
-116
@@ -1,116 +0,0 @@
|
||||
/* Copyright (C) 2019 Nunuhara Cabbage <nunuhara@haniwa.technology>
|
||||
*
|
||||
* This program 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 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 this program; if not, see <http://gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "system4.h"
|
||||
#include "system4/hashtable.h"
|
||||
|
||||
struct ht_bucket {
|
||||
size_t nr_slots;
|
||||
struct ht_slot slots[];
|
||||
};
|
||||
|
||||
struct hash_table {
|
||||
size_t nr_buckets;
|
||||
struct ht_bucket *buckets[];
|
||||
};
|
||||
|
||||
static unsigned long string_hash(const char *_str)
|
||||
{
|
||||
const unsigned char *str = (const unsigned char*)_str;
|
||||
unsigned long hash = 5381;
|
||||
int c;
|
||||
|
||||
while ((c = *str++))
|
||||
hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
struct hash_table *ht_create(size_t nr_buckets)
|
||||
{
|
||||
struct hash_table *ht = xmalloc(sizeof(struct hash_table) + sizeof(struct ht_bucket*)*nr_buckets);
|
||||
ht->nr_buckets = nr_buckets;
|
||||
for (size_t i = 0; i < nr_buckets; i++) {
|
||||
ht->buckets[i] = NULL;
|
||||
}
|
||||
return ht;
|
||||
}
|
||||
|
||||
void *ht_get(struct hash_table *ht, const char *key, void *dflt)
|
||||
{
|
||||
unsigned long k = string_hash(key) & (ht->nr_buckets - 1);
|
||||
if (!ht->buckets[k])
|
||||
return dflt;
|
||||
for (size_t i = 0; i < ht->buckets[k]->nr_slots; i++) {
|
||||
if (!strcmp(ht->buckets[k]->slots[i].key, key))
|
||||
return ht->buckets[k]->slots[i].value;
|
||||
}
|
||||
return dflt;
|
||||
}
|
||||
|
||||
struct ht_slot *ht_put(struct hash_table *ht, const char *key, void *dflt)
|
||||
{
|
||||
unsigned long k = string_hash(key) & (ht->nr_buckets - 1);
|
||||
|
||||
// init new bucket
|
||||
if (!ht->buckets[k]) {
|
||||
ht->buckets[k] = xmalloc(sizeof(struct ht_bucket) + sizeof(struct ht_slot));
|
||||
ht->buckets[k]->nr_slots = 1;
|
||||
ht->buckets[k]->slots[0].key = strdup(key);
|
||||
ht->buckets[k]->slots[0].value = dflt;
|
||||
return &ht->buckets[k]->slots[0];
|
||||
}
|
||||
|
||||
// search for key in bucket
|
||||
for (size_t i = 0; i < ht->buckets[k]->nr_slots; i++) {
|
||||
if (!strcmp(ht->buckets[k]->slots[i].key, key))
|
||||
return &ht->buckets[k]->slots[i];
|
||||
}
|
||||
|
||||
// alloc new slot
|
||||
size_t i = ht->buckets[k]->nr_slots;
|
||||
ht->buckets[k] = xrealloc(ht->buckets[k], sizeof(struct ht_bucket) + sizeof(struct ht_slot)*(i+1));
|
||||
ht->buckets[k]->nr_slots = i+1;
|
||||
ht->buckets[k]->slots[i].key = strdup(key);
|
||||
ht->buckets[k]->slots[i].value = dflt;
|
||||
return &ht->buckets[k]->slots[i];
|
||||
}
|
||||
|
||||
void ht_foreach_value(struct hash_table *ht, void(*fun)(void*))
|
||||
{
|
||||
for (size_t i = 0; i < ht->nr_buckets; i++) {
|
||||
if (!ht->buckets[i])
|
||||
continue;
|
||||
for (size_t j = 0; j < ht->buckets[i]->nr_slots; j++) {
|
||||
fun(ht->buckets[i]->slots[j].value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ht_free(struct hash_table *ht)
|
||||
{
|
||||
for (size_t i = 0; i < ht->nr_buckets; i++) {
|
||||
if (!ht->buckets[i])
|
||||
continue;
|
||||
for (size_t j = 0; j < ht->buckets[i]->nr_slots; j++) {
|
||||
free(ht->buckets[i]->slots[j].key);
|
||||
}
|
||||
free(ht->buckets[i]);
|
||||
}
|
||||
free(ht);
|
||||
}
|
||||
@@ -1,145 +0,0 @@
|
||||
/* Copyright (C) 2019 Nunuhara Cabbage <nunuhara@haniwa.technology>
|
||||
*
|
||||
* This program 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 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 this program; if not, see <http://gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include "system4.h"
|
||||
#include "system4/ini.h"
|
||||
#include "system4/string.h"
|
||||
#include "ini_parser.tab.h"
|
||||
|
||||
extern FILE *yini_in;
|
||||
extern unsigned long yini_line;
|
||||
|
||||
static void list_assign(struct ini_value *list, size_t i, struct ini_value *item)
|
||||
{
|
||||
// grow list if needed
|
||||
if (i >= list->list_size) {
|
||||
list->list = xrealloc(list->list, (i+1) * sizeof(struct ini_value));
|
||||
for (size_t j = list->list_size; j < i; j++) {
|
||||
list->list[j].type = INI_NULL;
|
||||
}
|
||||
list->list_size = i+1;
|
||||
}
|
||||
|
||||
list->list[i] = *item;
|
||||
}
|
||||
|
||||
struct ini_entry *ini_parse(const char *path, int *nr_entries)
|
||||
{
|
||||
struct ini_entry _e;
|
||||
|
||||
if (!(yini_in = fopen(path, "rb"))) {
|
||||
*nr_entries = INI_FILE_ERROR;
|
||||
return NULL;
|
||||
}
|
||||
yini_line = 1;
|
||||
yini_parse();
|
||||
|
||||
kvec_t(struct ini_entry) entries;
|
||||
kv_init(entries);
|
||||
|
||||
for (size_t i = 0; i < kv_size(*yini_entries); i++) {
|
||||
struct ini_entry *e = kv_A(*yini_entries, i);
|
||||
|
||||
if (e->value.type != _INI_LIST_ENTRY) {
|
||||
kv_push(struct ini_entry, entries, *e);
|
||||
free(e);
|
||||
continue;
|
||||
}
|
||||
|
||||
// try to find existing list to put entry into
|
||||
struct ini_entry *list = NULL;
|
||||
for (size_t i = 0; i < kv_size(entries); i++) {
|
||||
struct ini_entry *other = &kv_A(entries, i);
|
||||
if (strcmp(e->name->text, other->name->text))
|
||||
continue;
|
||||
if (other->value.type != INI_LIST) {
|
||||
WARNING("ignoring list assignment to non-list: %s[%" SIZE_T_FMT "]", e->name, e->value._list_pos);
|
||||
goto end_list_assign;
|
||||
}
|
||||
free_string(e->name);
|
||||
list = other;
|
||||
break;
|
||||
}
|
||||
|
||||
// create new list if not found
|
||||
if (!list) {
|
||||
_e = (struct ini_entry) {
|
||||
.name = e->name,
|
||||
.value = {
|
||||
.type = INI_LIST,
|
||||
.list_size = 0,
|
||||
.list = NULL
|
||||
}
|
||||
};
|
||||
list = kv_pushp(struct ini_entry, entries);
|
||||
*list = _e;
|
||||
}
|
||||
|
||||
list_assign(&list->value, e->value._list_pos, e->value._list_value);
|
||||
end_list_assign:
|
||||
free(e->value._list_value);
|
||||
free(e);
|
||||
}
|
||||
kv_destroy(*yini_entries);
|
||||
free(yini_entries);
|
||||
|
||||
*nr_entries = kv_size(entries);
|
||||
return kv_data(entries);
|
||||
}
|
||||
|
||||
struct ini_entry *ini_make_entry(struct string *name, struct ini_value value)
|
||||
{
|
||||
struct ini_entry *entry = xmalloc(sizeof(struct ini_entry));
|
||||
entry->name = name;
|
||||
entry->value = value;
|
||||
return entry;
|
||||
}
|
||||
|
||||
static void ini_free_value(struct ini_value *value)
|
||||
{
|
||||
switch (value->type) {
|
||||
case INI_STRING:
|
||||
free_string(value->s);
|
||||
break;
|
||||
case INI_LIST:
|
||||
for (size_t i = 0; i < value->list_size; i++) {
|
||||
ini_free_value(&value->list[i]);
|
||||
}
|
||||
free(value->list);
|
||||
break;
|
||||
case _INI_LIST_ENTRY:
|
||||
ini_free_value(value->_list_value);
|
||||
free(value->_list_value);
|
||||
break;
|
||||
case INI_FORMATION:
|
||||
for (size_t i = 0; i < value->nr_entries; i++) {
|
||||
ini_free_entry(&value->entries[i]);
|
||||
}
|
||||
free(value->entries);
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
void ini_free_entry(struct ini_entry *entry)
|
||||
{
|
||||
free_string(entry->name);
|
||||
ini_free_value(&entry->value);
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
/* Copyright (C) 2019 Nunuhara Cabbage <nunuhara@haniwa.technology>
|
||||
*
|
||||
* This program 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 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 this program; if not, see <http://gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
%{
|
||||
|
||||
#pragma GCC diagnostic ignored "-Wunused-function"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include "ini_parser.tab.h"
|
||||
#include "system4.h"
|
||||
#include "system4/string.h"
|
||||
|
||||
char string_buf[65536];
|
||||
char *string_buf_ptr;
|
||||
|
||||
unsigned long yini_line = 1;
|
||||
|
||||
%}
|
||||
|
||||
%option noyywrap
|
||||
%option prefix="yini_"
|
||||
|
||||
%x str
|
||||
|
||||
%%
|
||||
|
||||
\/\/[^\n]*\n yini_line++;
|
||||
[ \t\r] ;
|
||||
\n yini_line++;
|
||||
;[^\n]*\n yini_line++;
|
||||
\{ return '{';
|
||||
\} return '}';
|
||||
\[ return '[';
|
||||
\] return ']';
|
||||
= return '=';
|
||||
, return ',';
|
||||
true return TRUE;
|
||||
false return FALSE;
|
||||
Formation return FORMATION;
|
||||
[0-9]+ yini_lval.i = atoi(yini_text); return INTEGER;
|
||||
[a-zA-Z]+[a-zA-Z0-9_-]* yini_lval.s = make_string(yini_text, yini_leng); return IDENTIFIER;
|
||||
|
||||
\" string_buf_ptr = string_buf; BEGIN(str);
|
||||
|
||||
<str>{
|
||||
\" {
|
||||
BEGIN(INITIAL);
|
||||
*string_buf_ptr = '\0';
|
||||
yini_lval.s = make_string(string_buf, string_buf_ptr - string_buf);
|
||||
return STRING;
|
||||
}
|
||||
|
||||
\n ERROR("Unterminated string literal at line %lu", yini_line);
|
||||
|
||||
[^\n\"]+ {
|
||||
char *yptr = yini_text;
|
||||
while (*yptr)
|
||||
*string_buf_ptr++ = *yptr++;
|
||||
}
|
||||
}
|
||||
|
||||
%%
|
||||
@@ -1,136 +0,0 @@
|
||||
%define api.prefix {yini_}
|
||||
|
||||
%union {
|
||||
int token;
|
||||
int i;
|
||||
struct string *s;
|
||||
struct ini_entry *entry;
|
||||
struct ini_value value;
|
||||
entry_list *entries;
|
||||
value_list *list;
|
||||
}
|
||||
|
||||
%code requires {
|
||||
#include "kvec.h"
|
||||
#include "system4/ini.h"
|
||||
|
||||
kv_decl(entry_list, struct ini_entry*);
|
||||
kv_decl(value_list, struct ini_value);
|
||||
entry_list *yini_entries;
|
||||
}
|
||||
|
||||
%{
|
||||
|
||||
#include <stdio.h>
|
||||
#include "system4.h"
|
||||
#include "system4/string.h"
|
||||
|
||||
extern unsigned long yini_line;
|
||||
extern int yini_lex();
|
||||
void yini_error(const char *s) { ERROR("at line %lu: %s", yini_line, s); }
|
||||
|
||||
static entry_list *make_ini(void)
|
||||
{
|
||||
entry_list *ini = xmalloc(sizeof(entry_list));
|
||||
kv_init(*ini);
|
||||
return ini;
|
||||
}
|
||||
|
||||
static void push_entry(entry_list *ini, struct ini_entry *entry)
|
||||
{
|
||||
kv_push(struct ini_entry*, *ini, entry);
|
||||
}
|
||||
|
||||
static struct ini_value make_list(value_list *values)
|
||||
{
|
||||
if (!values)
|
||||
return ini_make_list(NULL, 0);
|
||||
struct ini_value *vals = kv_data(*values);
|
||||
size_t nr_vals = kv_size(*values);
|
||||
free(values);
|
||||
return ini_make_list(vals, nr_vals);
|
||||
}
|
||||
|
||||
static value_list *make_value_list(void)
|
||||
{
|
||||
value_list *values = xmalloc(sizeof(value_list));
|
||||
kv_init(*values);
|
||||
return values;
|
||||
}
|
||||
|
||||
static void push_value(value_list *values, struct ini_value value)
|
||||
{
|
||||
kv_push(struct ini_value, *values, value);
|
||||
}
|
||||
|
||||
static struct ini_entry *make_list_assign(struct string *name, int i, struct ini_value value)
|
||||
{
|
||||
struct ini_value *val = xmalloc(sizeof(struct ini_value));
|
||||
*val = value;
|
||||
|
||||
struct ini_value wrapper = {
|
||||
.type = _INI_LIST_ENTRY,
|
||||
._list_pos = i,
|
||||
._list_value = val
|
||||
};
|
||||
return ini_make_entry(name, wrapper);
|
||||
}
|
||||
|
||||
static struct ini_entry *make_formation(struct string *name, entry_list *entries)
|
||||
{
|
||||
struct ini_value wrapper = {
|
||||
.type = INI_FORMATION,
|
||||
.nr_entries = kv_size(*entries),
|
||||
.entries = xcalloc(kv_size(*entries), sizeof(struct ini_entry)),
|
||||
};
|
||||
for (size_t i = 0; i < wrapper.nr_entries; i++) {
|
||||
wrapper.entries[i] = *kv_A(*entries, i);
|
||||
free(kv_A(*entries, i));
|
||||
}
|
||||
kv_destroy(*entries);
|
||||
free(entries);
|
||||
return ini_make_entry(name, wrapper);
|
||||
}
|
||||
|
||||
%}
|
||||
|
||||
%token <token> TRUE FALSE FORMATION
|
||||
%token <i> INTEGER
|
||||
%token <s> STRING IDENTIFIER
|
||||
|
||||
%type <entries> inifile
|
||||
%type <entries> entries
|
||||
%type <entry> entry
|
||||
%type <value> value
|
||||
%type <list> list
|
||||
|
||||
%start inifile
|
||||
|
||||
%%
|
||||
|
||||
inifile : entries { yini_entries = $1; }
|
||||
;
|
||||
|
||||
entries : entry { $$ = make_ini(); push_entry($$, $1); }
|
||||
| entries entry { push_entry($1, $2); }
|
||||
;
|
||||
|
||||
entry : IDENTIFIER '=' value { $$ = ini_make_entry($1, $3); }
|
||||
| IDENTIFIER '[' INTEGER ']' '=' value { $$ = make_list_assign($1, $3, $6); }
|
||||
| FORMATION STRING '{' entries '}' { $$ = make_formation($2, $4); }
|
||||
;
|
||||
|
||||
value : INTEGER { $$ = ini_make_integer($1); }
|
||||
| TRUE { $$ = ini_make_boolean(true); }
|
||||
| FALSE { $$ = ini_make_boolean(false); }
|
||||
| STRING { $$ = ini_make_string($1); }
|
||||
| '{' '}' { $$ = make_list(NULL); }
|
||||
| '{' list '}' { $$ = make_list($2); }
|
||||
| '{' list ',' '}' { $$ = make_list($2); }
|
||||
;
|
||||
|
||||
list : value { $$ = make_value_list(); push_value($$, $1); }
|
||||
| list ',' value { push_value($1, $3); }
|
||||
;
|
||||
|
||||
%%
|
||||
@@ -1,403 +0,0 @@
|
||||
/* Copyright (C) 2019 Nunuhara Cabbage <nunuhara@haniwa.technology>
|
||||
*
|
||||
* This program 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 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 this program; if not, see <http://gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "system4.h"
|
||||
#include "system4/instructions.h"
|
||||
|
||||
#define SYS(syscode, sysname, rt, nargs, ...) \
|
||||
[syscode] = { \
|
||||
.code = syscode, \
|
||||
.name = "system." #sysname , \
|
||||
.implemented = true, \
|
||||
.return_type = rt, \
|
||||
.nr_args = nargs, \
|
||||
.argtypes = { __VA_ARGS__ } \
|
||||
}
|
||||
|
||||
#define TODO(syscode, sysname, rt, nargs, ...) \
|
||||
[syscode] = { \
|
||||
.code = syscode, \
|
||||
.name = "system." #sysname , \
|
||||
.implemented = false, \
|
||||
.return_type = rt, \
|
||||
.nr_args = nargs, \
|
||||
.argtypes = { __VA_ARGS__ } \
|
||||
}
|
||||
|
||||
const struct syscall syscalls[NR_SYSCALLS] = {
|
||||
SYS ( SYS_EXIT, Exit, AIN_VOID_TYPE, 1, AIN_INT ),
|
||||
SYS ( SYS_GLOBAL_SAVE, GlobalSave, AIN_INT_TYPE, 2, AIN_STRING, AIN_STRING ),
|
||||
SYS ( SYS_GLOBAL_LOAD, GlobalLoad, AIN_INT_TYPE, 2, AIN_STRING, AIN_STRING ),
|
||||
SYS ( SYS_LOCK_PEEK, LockPeek, AIN_INT_TYPE, 0 ),
|
||||
SYS ( SYS_UNLOCK_PEEK, UnlockPeek, AIN_INT_TYPE, 0 ),
|
||||
SYS ( SYS_RESET, Reset, AIN_VOID_TYPE, 0 ),
|
||||
SYS ( SYS_OUTPUT, Output, AIN_STRING_TYPE, 1, AIN_STRING ),
|
||||
SYS ( SYS_MSGBOX, MsgBox, AIN_STRING_TYPE, 1, AIN_STRING ),
|
||||
SYS ( SYS_RESUME_SAVE, ResumeSave, AIN_INT_TYPE, 3, AIN_STRING, AIN_STRING, AIN_REF_INT ),
|
||||
SYS ( SYS_RESUME_LOAD, ResumeLoad, AIN_VOID_TYPE, 2, AIN_STRING, AIN_STRING ),
|
||||
SYS ( SYS_EXISTS_FILE, ExistsFile, AIN_INT_TYPE, 1, AIN_STRING ),
|
||||
SYS ( SYS_OPEN_WEB, OpenWeb, AIN_VOID_TYPE, 1, AIN_STRING ),
|
||||
SYS ( SYS_GET_SAVE_FOLDER_NAME, GetSaveFolderName, AIN_STRING_TYPE, 0 ),
|
||||
SYS ( SYS_GET_TIME, GetTime, AIN_INT_TYPE, 0 ),
|
||||
TODO ( SYS_GET_GAME_NAME, GetGameName, AIN_STRING_TYPE, 0 ),
|
||||
SYS ( SYS_ERROR, Error, AIN_STRING_TYPE, 1, AIN_STRING ),
|
||||
SYS ( SYS_EXISTS_SAVE_FILE, ExistsSaveFile, AIN_INT_TYPE, 1, AIN_STRING ),
|
||||
SYS ( SYS_IS_DEBUG_MODE, IsDebugMode, AIN_INT_TYPE, 0 ),
|
||||
SYS ( SYS_MSGBOX_OK_CANCEL, MsgBoxOkCancel, AIN_INT_TYPE, 1, AIN_STRING ),
|
||||
SYS ( SYS_GET_FUNC_STACK_NAME, GetFuncStackName, AIN_STRING_TYPE, 1, AIN_INT ),
|
||||
SYS ( SYS_PEEK, Peek, AIN_VOID_TYPE, 0 ),
|
||||
SYS ( SYS_SLEEP, Sleep, AIN_VOID_TYPE, 1, AIN_INT ),
|
||||
SYS ( SYS_GROUP_SAVE, GroupSave, AIN_INT_TYPE, 4, AIN_STRING, AIN_STRING, AIN_STRING, AIN_REF_INT ),
|
||||
SYS ( SYS_GROUP_LOAD, GroupLoad, AIN_INT_TYPE, 4, AIN_STRING, AIN_STRING, AIN_STRING, AIN_REF_INT ),
|
||||
TODO ( SYS_RESUME_WRITE_COMMENT, ResumeWriteComment, AIN_BOOL_TYPE, 3, AIN_STRING, AIN_STRING, AIN_REF_ARRAY_STRING ),
|
||||
TODO ( SYS_RESUME_READ_COMMENT, ResumeReadComment, AIN_BOOL_TYPE, 3, AIN_STRING, AIN_STRING, AIN_REF_ARRAY_STRING ),
|
||||
TODO ( SYS_DELETE_SAVE_FILE, DeleteSaveFile, AIN_INT_TYPE, 1, AIN_STRING ),
|
||||
TODO ( SYS_EXIST_FUNC, ExistFunc, AIN_BOOL_TYPE, 1, AIN_STRING ),
|
||||
TODO ( SYS_COPY_SAVE_FILE, CopySaveFile, AIN_INT_TYPE, 2, AIN_STRING, AIN_STRING ),
|
||||
};
|
||||
|
||||
// JMP = instruction that modifies the instruction pointer
|
||||
// OP = everything else
|
||||
#define _OP(code, opname, nargs, ...) \
|
||||
[code] = { \
|
||||
.opcode = code, \
|
||||
.name = opname, \
|
||||
.nr_args = nargs, \
|
||||
.ip_inc = 2 + nargs * 4, \
|
||||
.implemented = true, \
|
||||
.args = { __VA_ARGS__ } \
|
||||
}
|
||||
|
||||
#define OP(code, nargs, ...) \
|
||||
[code] = { \
|
||||
.opcode = code, \
|
||||
.name = #code , \
|
||||
.nr_args = nargs, \
|
||||
.ip_inc = 2 + nargs * 4, \
|
||||
.implemented = true, \
|
||||
.args = { __VA_ARGS__ } \
|
||||
}
|
||||
#define JMP(code, nargs, ...) \
|
||||
[code] = { \
|
||||
.opcode = code, \
|
||||
.name = #code , \
|
||||
.nr_args = nargs, \
|
||||
.ip_inc = 0, \
|
||||
.implemented = true, \
|
||||
.args = { __VA_ARGS__ } \
|
||||
}
|
||||
|
||||
#undef TODO
|
||||
#define TODO(code, nargs, ...) \
|
||||
[code] = { \
|
||||
.opcode = code, \
|
||||
.name = #code, \
|
||||
.nr_args = nargs, \
|
||||
.ip_inc = 2 + nargs * 4, \
|
||||
.implemented = false, \
|
||||
.args = { __VA_ARGS__ } \
|
||||
}
|
||||
|
||||
struct instruction instructions[NR_OPCODES] = {
|
||||
// TYPE OPCODE NR_ARGS, ARG_TYPES...
|
||||
OP ( PUSH, 1, T_INT ),
|
||||
OP ( POP, 0 ),
|
||||
OP ( REF, 0 ),
|
||||
OP ( REFREF, 0 ),
|
||||
OP ( PUSHGLOBALPAGE, 0 ),
|
||||
OP ( PUSHLOCALPAGE, 0 ),
|
||||
OP ( INV, 0 ),
|
||||
OP ( NOT, 0 ),
|
||||
OP ( COMPL, 0 ),
|
||||
OP ( ADD, 0 ),
|
||||
OP ( SUB, 0 ),
|
||||
OP ( MUL, 0 ),
|
||||
OP ( DIV, 0 ),
|
||||
OP ( MOD, 0 ),
|
||||
OP ( AND, 0 ),
|
||||
OP ( OR, 0 ),
|
||||
OP ( XOR, 0 ),
|
||||
OP ( LSHIFT, 0 ),
|
||||
OP ( RSHIFT, 0 ),
|
||||
OP ( LT, 0 ),
|
||||
OP ( GT, 0 ),
|
||||
OP ( LTE, 0 ),
|
||||
OP ( GTE, 0 ),
|
||||
OP ( NOTE, 0 ),
|
||||
OP ( EQUALE, 0 ),
|
||||
OP ( ASSIGN, 0 ),
|
||||
OP ( PLUSA, 0 ),
|
||||
OP ( MINUSA, 0 ),
|
||||
OP ( MULA, 0 ),
|
||||
OP ( DIVA, 0 ),
|
||||
OP ( MODA, 0 ),
|
||||
OP ( ANDA, 0 ),
|
||||
OP ( ORA, 0 ),
|
||||
OP ( XORA, 0 ),
|
||||
OP ( LSHIFTA, 0 ),
|
||||
OP ( RSHIFTA, 0 ),
|
||||
OP ( F_ASSIGN, 0 ),
|
||||
OP ( F_PLUSA, 0 ),
|
||||
OP ( F_MINUSA, 0 ),
|
||||
OP ( F_MULA, 0 ),
|
||||
OP ( F_DIVA, 0 ),
|
||||
OP ( DUP2, 0 ),
|
||||
OP ( DUP_X2, 0 ),
|
||||
TODO ( CMP, 0 ),
|
||||
JMP ( JUMP, 1, T_ADDR ),
|
||||
JMP ( IFZ, 1, T_ADDR ),
|
||||
JMP ( IFNZ, 1, T_ADDR ),
|
||||
JMP ( RETURN, 0 ),
|
||||
JMP ( CALLFUNC, 1, T_FUNC ),
|
||||
OP ( INC, 0 ),
|
||||
OP ( DEC, 0 ),
|
||||
OP ( FTOI, 0 ),
|
||||
OP ( ITOF, 0 ),
|
||||
OP ( F_INV, 0 ),
|
||||
OP ( F_ADD, 0 ),
|
||||
OP ( F_SUB, 0 ),
|
||||
OP ( F_MUL, 0 ),
|
||||
OP ( F_DIV, 0 ),
|
||||
OP ( F_LT, 0 ),
|
||||
OP ( F_GT, 0 ),
|
||||
OP ( F_LTE, 0 ),
|
||||
OP ( F_GTE, 0 ),
|
||||
OP ( F_NOTE, 0 ),
|
||||
OP ( F_EQUALE, 0 ),
|
||||
OP ( F_PUSH, 1, T_FLOAT ),
|
||||
OP ( S_PUSH, 1, T_STRING ),
|
||||
OP ( S_POP, 0 ),
|
||||
OP ( S_ADD, 0 ),
|
||||
OP ( S_ASSIGN, 0 ),
|
||||
TODO ( S_PLUSA, 0 ),
|
||||
OP ( S_REF, 0 ),
|
||||
TODO ( S_REFREF, 0 ),
|
||||
OP ( S_NOTE, 0 ),
|
||||
OP ( S_EQUALE, 0 ),
|
||||
TODO ( SF_CREATE, 0 ),
|
||||
TODO ( SF_CREATEPIXEL, 0 ),
|
||||
TODO ( SF_CREATEALPHA, 0 ),
|
||||
|
||||
OP ( SR_POP, 0 ),
|
||||
OP ( SR_ASSIGN, 0 ),
|
||||
OP ( SR_REF, 1, T_STRUCT ),
|
||||
TODO ( SR_REFREF, 0 ),
|
||||
OP ( A_ALLOC, 0 ),
|
||||
OP ( A_REALLOC, 0 ),
|
||||
OP ( A_FREE, 0 ),
|
||||
OP ( A_NUMOF, 0 ),
|
||||
OP ( A_COPY, 0 ),
|
||||
OP ( A_FILL, 0 ),
|
||||
OP ( C_REF, 0 ),
|
||||
OP ( C_ASSIGN, 0 ),
|
||||
JMP ( MSG, 1, T_MSG ),
|
||||
OP ( CALLHLL, 2, T_HLL, T_HLLFUNC, T_INT ), // XXX: changed in ain version > 8
|
||||
OP ( PUSHSTRUCTPAGE, 0 ),
|
||||
JMP ( CALLMETHOD, 1, T_FUNC ),
|
||||
OP ( SH_GLOBALREF, 1, T_GLOBAL ),
|
||||
OP ( SH_LOCALREF, 1, T_LOCAL ),
|
||||
JMP ( SWITCH, 1, T_SWITCH ),
|
||||
JMP ( STRSWITCH, 1, T_SWITCH ),
|
||||
OP ( FUNC, 1, T_FUNC ),
|
||||
_OP ( _EOF, "EOF", 1, T_FILE ),
|
||||
OP ( CALLSYS, 1, T_SYSCALL ),
|
||||
JMP ( SJUMP, 0 ),
|
||||
OP ( CALLONJUMP, 0 ),
|
||||
OP ( SWAP, 0 ),
|
||||
OP ( SH_STRUCTREF, 1, T_MEMB ),
|
||||
OP ( S_LENGTH, 0 ),
|
||||
OP ( S_LENGTHBYTE, 0 ),
|
||||
OP ( I_STRING, 0 ),
|
||||
JMP ( CALLFUNC2, 0 ),
|
||||
OP ( DUP2_X1, 0 ),
|
||||
OP ( R_ASSIGN, 0 ),
|
||||
OP ( FT_ASSIGNS, 0 ),
|
||||
OP ( ASSERT, 0 ),
|
||||
OP ( S_LT, 0 ),
|
||||
OP ( S_GT, 0 ),
|
||||
OP ( S_LTE, 0 ),
|
||||
OP ( S_GTE, 0 ),
|
||||
OP ( S_LENGTH2, 0 ),
|
||||
TODO ( S_LENGTHBYTE2, 0 ),
|
||||
OP ( NEW, 0, T_STRUCT, T_INT ), // FIXME: 2nd arg is T_FUNC *OR* -1
|
||||
OP ( DELETE, 0 ),
|
||||
TODO ( CHECKUDO, 0 ),
|
||||
OP ( A_REF, 0 ),
|
||||
OP ( DUP, 0 ),
|
||||
OP ( DUP_U2, 0 ),
|
||||
OP ( SP_INC, 0 ),
|
||||
TODO ( SP_DEC, 0 ),
|
||||
OP ( ENDFUNC, 1, T_FUNC ),
|
||||
TODO ( R_EQUALE, 0 ),
|
||||
TODO ( R_NOTE, 0 ),
|
||||
OP ( SH_LOCALCREATE, 2, T_LOCAL, T_STRUCT ),
|
||||
OP ( SH_LOCALDELETE, 1, T_LOCAL ),
|
||||
OP ( STOI, 0 ),
|
||||
OP ( A_PUSHBACK, 0 ),
|
||||
OP ( A_POPBACK, 0 ),
|
||||
OP ( S_EMPTY, 0 ),
|
||||
OP ( A_EMPTY, 0 ),
|
||||
OP ( A_ERASE, 0 ),
|
||||
OP ( A_INSERT, 0 ),
|
||||
OP ( SH_LOCALINC, 1, T_LOCAL ),
|
||||
OP ( SH_LOCALDEC, 1, T_LOCAL ),
|
||||
OP ( SH_LOCALASSIGN, 2, T_LOCAL, T_INT ),
|
||||
OP ( ITOB, 0 ),
|
||||
OP ( S_FIND, 0 ),
|
||||
OP ( S_GETPART, 0 ),
|
||||
OP ( A_SORT, 0 ),
|
||||
TODO ( S_PUSHBACK, 0 ),
|
||||
TODO ( S_POPBACK, 0 ),
|
||||
OP ( FTOS, 0 ),
|
||||
OP ( S_MOD, 0, T_INT ), // XXX: changed in ain version > 8
|
||||
OP ( S_PLUSA2, 0 ),
|
||||
OP ( OBJSWAP, 0, T_INT ), // XXX: changed in ain version > 8
|
||||
TODO ( S_ERASE, 0 ),
|
||||
TODO ( SR_REF2, 1, T_INT ),
|
||||
OP ( S_ERASE2, 0 ),
|
||||
OP ( S_PUSHBACK2, 0 ),
|
||||
OP ( S_POPBACK2, 0 ),
|
||||
OP ( ITOLI, 0 ),
|
||||
OP ( LI_ADD, 0 ),
|
||||
OP ( LI_SUB, 0 ),
|
||||
OP ( LI_MUL, 0 ),
|
||||
OP ( LI_DIV, 0 ),
|
||||
OP ( LI_MOD, 0 ),
|
||||
OP ( LI_ASSIGN, 0 ),
|
||||
OP ( LI_PLUSA, 0 ),
|
||||
OP ( LI_MINUSA, 0 ),
|
||||
OP ( LI_MULA, 0 ),
|
||||
OP ( LI_DIVA, 0 ),
|
||||
OP ( LI_MODA, 0 ),
|
||||
OP ( LI_ANDA, 0 ),
|
||||
OP ( LI_ORA, 0 ),
|
||||
OP ( LI_XORA, 0 ),
|
||||
OP ( LI_LSHIFTA, 0 ),
|
||||
OP ( LI_RSHIFTA, 0 ),
|
||||
OP ( LI_INC, 0 ),
|
||||
OP ( LI_DEC, 0 ),
|
||||
OP ( A_FIND, 0 ),
|
||||
OP ( A_REVERSE, 0 ),
|
||||
|
||||
TODO ( SH_SR_ASSIGN, 0 ),
|
||||
TODO ( SH_MEM_ASSIGN_LOCAL, 2, T_MEMB, T_LOCAL ),
|
||||
TODO ( A_NUMOF_GLOB_1, 1, T_GLOBAL ),
|
||||
TODO ( A_NUMOF_STRUCT_1, 1, T_MEMB ),
|
||||
TODO ( SH_MEM_ASSIGN_IMM, 2, T_MEMB, T_INT ),
|
||||
TODO ( SH_LOCALREFREF, 1, T_LOCAL ),
|
||||
TODO ( SH_LOCALASSIGN_SUB_IMM, 2, T_LOCAL, T_INT ),
|
||||
TODO ( SH_IF_LOC_LT_IMM, 3, T_LOCAL, T_INT, T_ADDR ),
|
||||
TODO ( SH_IF_LOC_GE_IMM, 3, T_LOCAL, T_INT, T_ADDR ),
|
||||
TODO ( SH_LOCREF_ASSIGN_MEM, 2, T_LOCAL, T_MEMB ),
|
||||
TODO ( PAGE_REF, 1, T_INT ),
|
||||
TODO ( SH_GLOBAL_ASSIGN_LOCAL, 2, T_GLOBAL, T_LOCAL ),
|
||||
TODO ( SH_STRUCTREF_GT_IMM, 2, T_MEMB, T_INT ),
|
||||
TODO ( SH_STRUCT_ASSIGN_LOCALREF_ITOB, 2, T_MEMB, T_LOCAL ),
|
||||
TODO ( SH_LOCAL_ASSIGN_STRUCTREF, 2, T_LOCAL, T_MEMB ),
|
||||
TODO ( SH_IF_STRUCTREF_NE_LOCALREF, 3, T_MEMB, T_LOCAL, T_ADDR ),
|
||||
TODO ( SH_IF_STRUCTREF_GT_IMM, 3, T_MEMB, T_INT, T_ADDR ),
|
||||
TODO ( SH_STRUCTREF_CALLMETHOD_NO_PARAM, 2, T_MEMB, T_FUNC ),
|
||||
TODO ( SH_STRUCTREF2, 2, T_MEMB, T_MEMB2 ),
|
||||
TODO ( SH_REF_STRUCTREF2, 2, T_MEMB, T_MEMB2 ),
|
||||
TODO ( SH_STRUCTREF3, 3, T_MEMB, T_MEMB2, T_MEMB3 ),
|
||||
TODO ( SH_STRUCTREF2_CALLMETHOD_NO_PARAM, 3, T_MEMB, T_MEMB2, T_FUNC ),
|
||||
TODO ( SH_IF_STRUCTREF_Z, 2, T_MEMB, T_ADDR ),
|
||||
TODO ( SH_IF_STRUCT_A_NOT_EMPTY, 2, T_MEMB, T_ADDR ),
|
||||
TODO ( SH_IF_LOC_GT_IMM, 3, T_LOCAL, T_INT, T_ADDR ),
|
||||
TODO ( SH_IF_STRUCTREF_NE_IMM, 3, T_MEMB, T_INT, T_ADDR ),
|
||||
TODO ( THISCALLMETHOD_NOPARAM, 1, T_FUNC ),
|
||||
TODO ( SH_IF_LOC_NE_IMM, 3, T_LOCAL, T_INT, T_ADDR ),
|
||||
TODO ( SH_IF_STRUCTREF_EQ_IMM, 3, T_MEMB, T_INT, T_ADDR ),
|
||||
TODO ( SH_GLOBAL_ASSIGN_IMM, 2, T_GLOBAL, T_INT ),
|
||||
TODO ( SH_LOCALSTRUCT_ASSIGN_IMM, 3, T_LOCAL, T_LOCMEMB, T_INT ),
|
||||
TODO ( SH_STRUCT_A_PUSHBACK_LOCAL_STRUCT, 2, T_MEMB, T_LOCAL ),
|
||||
TODO ( SH_GLOBAL_A_PUSHBACK_LOCAL_STRUCT, 2, T_GLOBAL, T_LOCAL ),
|
||||
TODO ( SH_LOCAL_A_PUSHBACK_LOCAL_STRUCT, 2, T_LOCAL, T_LOCAL ),
|
||||
TODO ( SH_IF_SREF_NE_STR0, 2, T_STRING, T_ADDR ),
|
||||
TODO ( SH_S_ASSIGN_REF, 0 ),
|
||||
TODO ( SH_A_FIND_SREF, 0 ),
|
||||
TODO ( SH_SREF_EMPTY, 0 ),
|
||||
TODO ( SH_STRUCTSREF_EQ_LOCALSREF, 2 , T_MEMB, T_LOCAL ),
|
||||
TODO ( SH_LOCALSREF_EQ_STR0, 2, T_LOCAL, T_STRING ),
|
||||
TODO ( SH_STRUCTSREF_NE_LOCALSREF, 2, T_MEMB, T_LOCAL ),
|
||||
TODO ( SH_LOCALSREF_NE_STR0, 2, T_LOCAL, T_STRING ),
|
||||
TODO ( SH_STRUCT_SR_REF, 2, T_MEMB, T_STRUCT ),
|
||||
TODO ( SH_STRUCT_S_REF, 1, T_MEMB ),
|
||||
TODO ( S_REF2, 1, T_MEMB ),
|
||||
TODO ( SH_REF_LOCAL_ASSIGN_STRUCTREF2, 3, T_INT, T_INT, T_INT ), // FIXME: arguments types unknown
|
||||
TODO ( SH_GLOBAL_S_REF, 1, T_GLOBAL ),
|
||||
TODO ( SH_LOCAL_S_REF, 1, T_LOCAL ),
|
||||
TODO ( SH_LOCALREF_SASSIGN_LOCALSREF, 2, T_LOCAL, T_LOCAL ),
|
||||
TODO ( SH_LOCAL_APUSHBACK_LOCALSREF, 2, T_LOCAL, T_LOCAL ),
|
||||
TODO ( SH_S_ASSIGN_CALLSYS19, 0 ),
|
||||
TODO ( SH_S_ASSIGN_STR0, 1, T_STRING ),
|
||||
TODO ( SH_SASSIGN_LOCALSREF, 1, T_LOCAL ),
|
||||
TODO ( SH_STRUCTREF_SASSIGN_LOCALSREF, 2, T_MEMB, T_LOCAL ),
|
||||
TODO ( SH_LOCALSREF_EMPTY, 1, T_LOCAL ),
|
||||
TODO ( SH_GLOBAL_APUSHBACK_LOCALSREF, 2, T_GLOBAL, T_LOCAL ),
|
||||
TODO ( SH_STRUCT_APUSHBACK_LOCALSREF, 2, T_MEMB, T_LOCAL ),
|
||||
TODO ( SH_STRUCTSREF_EMPTY, 1, T_MEMB ),
|
||||
TODO ( SH_GLOBALSREF_EMPTY, 1, T_GLOBAL ),
|
||||
TODO ( SH_SASSIGN_STRUCTSREF, 1, T_MEMB ),
|
||||
TODO ( SH_SASSIGN_GLOBALSREF, 1, T_GLOBAL ),
|
||||
TODO ( SH_STRUCTSREF_NE_STR0, 2, T_MEMB, T_STRING ),
|
||||
TODO ( SH_GLOBALSREF_NE_STR0, 2, T_GLOBAL, T_STRING ),
|
||||
TODO ( SH_LOC_LT_IMM_OR_LOC_GE_IMM, 3, T_LOCAL, T_INT, T_INT ),
|
||||
|
||||
TODO ( A_SORT_MEM, 0 ),
|
||||
TODO ( DG_ADD, 0 ),
|
||||
TODO ( DG_SET, 0 ),
|
||||
TODO ( DG_CALL, 2, T_DLG, T_ADDR ),
|
||||
TODO ( DG_NUMOF, 0 ),
|
||||
TODO ( DG_EXIST, 0 ),
|
||||
TODO ( DG_ERASE, 0 ),
|
||||
TODO ( DG_CLEAR, 0 ),
|
||||
TODO ( DG_COPY, 0 ),
|
||||
TODO ( DG_ASSIGN, 0 ),
|
||||
TODO ( DG_PLUSA, 0 ),
|
||||
TODO ( DG_POP, 0 ),
|
||||
TODO ( DG_NEW_FROM_METHOD, 0 ),
|
||||
TODO ( DG_MINUSA, 0 ),
|
||||
TODO ( DG_CALLBEGIN, 1, T_DLG ),
|
||||
TODO ( DG_NEW, 0 ),
|
||||
TODO ( DG_STR_TO_METHOD, 0, T_DLG ), // XXX: changed in ain version > 8
|
||||
|
||||
TODO ( OP_0X102, 0 ),
|
||||
TODO ( OP_0X103, 0 ),
|
||||
TODO ( OP_0X104, 0 ),
|
||||
TODO ( OP_0X105, 1, T_STRUCT ),
|
||||
TODO ( OP_0X106, 1, T_INT ),
|
||||
TODO ( OP_0X107, 0 ),
|
||||
TODO ( OP_0X108, 0 ),
|
||||
TODO ( OP_0X109, 0 ),
|
||||
TODO ( OP_0X10A, 1, T_INT ),
|
||||
TODO ( OP_0X10B, 2, T_INT, T_INT ),
|
||||
TODO ( OP_0X10C, 1, T_INT ),
|
||||
TODO ( OP_0X10D, 1, T_INT ),
|
||||
TODO ( OP_0X10E, 1, T_INT ),
|
||||
TODO ( OP_0X10F, 0 ),
|
||||
TODO ( OP_0X110, 1, T_INT ),
|
||||
};
|
||||
|
||||
void initialize_instructions(void)
|
||||
{
|
||||
// this is a dumb hack to force linking/initialization of the above
|
||||
// arrays on Windows.
|
||||
}
|
||||
+2
-35
@@ -1,28 +1,3 @@
|
||||
# sources for libsys4.a
|
||||
system4 = ['acx.c',
|
||||
'afa.c',
|
||||
'ain.c',
|
||||
'ajp.c',
|
||||
'ald.c',
|
||||
'archive.c',
|
||||
'buffer.c',
|
||||
'cg.c',
|
||||
'dcf.c',
|
||||
'ex.c',
|
||||
'file.c',
|
||||
'flat.c',
|
||||
'fnl.c',
|
||||
'hashtable.c',
|
||||
'ini.c',
|
||||
'instructions.c',
|
||||
'pms.c',
|
||||
'qnt.c',
|
||||
'string.c',
|
||||
'system.c',
|
||||
'utfsjis.c',
|
||||
'webp.c',
|
||||
]
|
||||
|
||||
# sources for xsystem4
|
||||
xsystem4 = ['audio.c',
|
||||
'cJSON.c',
|
||||
@@ -62,7 +37,7 @@ xsystem4 = ['audio.c',
|
||||
'hll/SystemServiceEx.c',
|
||||
]
|
||||
|
||||
xsystem4_deps = [libm, zlib, sdl2, sdl2ttf, sdl2mixer, ffi, gl, glew]
|
||||
xsystem4_deps = [libm, zlib, sdl2, sdl2ttf, sdl2mixer, ffi, gl, glew, libsys4_dep]
|
||||
if chibi.found()
|
||||
xsystem4_deps += chibi
|
||||
xsystem4 += 'debugger.c'
|
||||
@@ -77,17 +52,9 @@ bisongen = generator(bison,
|
||||
output: ['@BASENAME@.tab.c', '@BASENAME@.tab.h'],
|
||||
arguments : ['--verbose', '@INPUT@', '--defines=@OUTPUT1@', '--output=@OUTPUT0@'])
|
||||
|
||||
system4 += flexgen.process('ini_lexer.l')
|
||||
system4 += bisongen.process('ini_parser.y')
|
||||
|
||||
libsys4 = static_library('sys4', system4,
|
||||
dependencies : [libm, zlib, tj, webp, png],
|
||||
include_directories : incdir)
|
||||
|
||||
executable('xsystem4', xsystem4,
|
||||
dependencies : xsystem4_deps,
|
||||
c_args : ['-Wno-unused-parameter'],
|
||||
include_directories : incdir,
|
||||
link_with : libsys4)
|
||||
include_directories : incdir)
|
||||
|
||||
subdir('tools')
|
||||
|
||||
@@ -1,327 +0,0 @@
|
||||
/* Copyright (C) 2019 Nunuhara Cabbage <nunuhara@haniwa.technology>
|
||||
*
|
||||
* Based on code from xsystem35
|
||||
* Copyright (C) 1997-1998 Masaki Chikama (Wren) <chikama@kasumi.ipl.mech.nagoya-u.ac.jp>
|
||||
* 1998- <masaki-c@is.aist-nara.ac.jp>
|
||||
*
|
||||
* This program 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 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 this program; if not, see <http://gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include "little_endian.h"
|
||||
#include "system4.h"
|
||||
#include "system4/cg.h"
|
||||
#include "system4/pms.h"
|
||||
|
||||
struct pms_header {
|
||||
int version; // PMS data version
|
||||
int header_size; // size of header
|
||||
int bpp; // PMS data depth, 8 or 16
|
||||
int bpps; // shadow data depth, if exists
|
||||
int sf; // sprite flag (not used?)
|
||||
int bf; // palette bank
|
||||
int x; // display location x
|
||||
int y; // display location y
|
||||
int width; // image width
|
||||
int height; // image height
|
||||
int dp; // data pointer
|
||||
int pp; // pointer to palette or alpha
|
||||
int cp; // pointer to comment
|
||||
};
|
||||
|
||||
bool pms_checkfmt(const uint8_t *data)
|
||||
{
|
||||
int x, y, w, h;
|
||||
|
||||
if (data[0] != 'P' || data[1] != 'M')
|
||||
return false;
|
||||
|
||||
x = LittleEndian_getDW(data, 16);
|
||||
y = LittleEndian_getDW(data, 20);
|
||||
w = LittleEndian_getDW(data, 24);
|
||||
h = LittleEndian_getDW(data, 28);
|
||||
|
||||
if (x < 0 || y < 0 || w < 0 || h < 0)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool pms8_checkfmt(const uint8_t *data)
|
||||
{
|
||||
return pms_checkfmt(data) && data[6] == 8;
|
||||
}
|
||||
|
||||
bool pms16_checkfmt(const uint8_t *data)
|
||||
{
|
||||
return pms_checkfmt(data) && data[7] == 16;
|
||||
}
|
||||
|
||||
static struct cg_palette *pms_get_palette(const uint8_t *b)
|
||||
{
|
||||
struct cg_palette *pal = xmalloc(sizeof(struct cg_palette));
|
||||
for (int i = 0; i < 256; i++) {
|
||||
pal->red[i] = *b++;
|
||||
pal->green[i] = *b++;
|
||||
pal->blue[i] = *b++;
|
||||
}
|
||||
return pal;
|
||||
}
|
||||
|
||||
/* Convert PMS8 image data to bitmap. */
|
||||
static uint8_t *pms8_extract(struct pms_header *pms, const uint8_t *b)
|
||||
{
|
||||
int n, c0, c1;
|
||||
const int scanline = pms->width;
|
||||
uint8_t *pic = xmalloc((pms->width+10)*(pms->height+10));
|
||||
|
||||
// for each line...
|
||||
for (int y = 0; y < pms->height; y ++) {
|
||||
// for each pixel...
|
||||
for (int x = 0; x < pms->width; ) {
|
||||
int loc = y * scanline + x;
|
||||
c0 = *b++;
|
||||
// non-command byte: read 1 pixel into buffer
|
||||
if (c0 <= 0xf7) {
|
||||
pic[loc] = c0;
|
||||
x++;
|
||||
}
|
||||
// copy n+3 pixels from previous line
|
||||
else if (c0 == 0xff) {
|
||||
n = (*b++) + 3;
|
||||
x += n;
|
||||
memcpy(pic + loc, pic + loc - scanline, n);
|
||||
}
|
||||
// copy n+3 pixels from 2 lines previous
|
||||
else if (c0 == 0xfe) {
|
||||
n = (*b++) + 3;
|
||||
x += n;
|
||||
memcpy(pic + loc, pic + loc - scanline * 2, n);
|
||||
}
|
||||
// repeat 1 pixel n+4 times (1-byte RLE)
|
||||
else if (c0 == 0xfd) {
|
||||
n = (*b++) + 4;
|
||||
x += n;
|
||||
c0 = *b++;
|
||||
memset(pic + loc, c0, n);
|
||||
}
|
||||
// repeast a sequence of 2 pixels n+3 times (2-byte RLE)
|
||||
else if (c0 == 0xfc) {
|
||||
n = ((*b++) + 3) * 2;
|
||||
x += n;
|
||||
c0 = *b++;
|
||||
c1 = *b++;
|
||||
for (int i = 0; i < n; i+=2) {
|
||||
pic[loc+i] = c0;
|
||||
pic[loc+i+1] = c1;
|
||||
}
|
||||
}
|
||||
// not sure why this exists, probably padding
|
||||
else {
|
||||
pic[loc] = *b++;
|
||||
x++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return pic;
|
||||
}
|
||||
|
||||
/* Convert PMS16 image data to bitmap. */
|
||||
static uint16_t *pms16_extract(struct pms_header *pms, const uint8_t *b)
|
||||
{
|
||||
int n, c0, c1, pc0, pc1;
|
||||
const int scanline = pms->width;
|
||||
uint16_t *pic = xmalloc(sizeof(uint16_t) * (pms->width+10) * (pms->height+10));
|
||||
|
||||
for (int y = 0; y < pms->height; y++) {
|
||||
for (int x = 0; x < pms->width;) {
|
||||
int loc = y * scanline + x;
|
||||
c0 = *b++;
|
||||
// non-command byte: read 1 pixel into buffer
|
||||
if (c0 <= 0xf7) {
|
||||
c1 = *b++;
|
||||
x++;
|
||||
pic[loc] = c0 | (c1 << 8);
|
||||
}
|
||||
// copy n+2 pixels from previous line into buffer
|
||||
else if (c0 == 0xff) {
|
||||
n = (*b++) + 2;
|
||||
x += n;
|
||||
for (int i = 0; i < n; i++) {
|
||||
pic[loc + i] = pic[loc + i - scanline];
|
||||
}
|
||||
}
|
||||
// copy n+2 pixels from 2 lines previous
|
||||
else if (c0 == 0xfe) {
|
||||
n = (*b++) + 2;
|
||||
x += n;
|
||||
for (int i = 0; i < n; i++) {
|
||||
pic[loc + i] = pic[loc + i - scanline * 2];
|
||||
}
|
||||
}
|
||||
// repeat a pixel n+3 times (1-byte RLE)
|
||||
else if (c0 == 0xfd) {
|
||||
n = (*b++) + 3;
|
||||
x += n;
|
||||
c0 = *b++;
|
||||
c1 = *b++;
|
||||
pc0 = c0 | (c1 << 8);
|
||||
for (int i = 0; i < n; i++) {
|
||||
pic[loc + i] = pc0;
|
||||
}
|
||||
}
|
||||
// repeat a sequence of 2 pixels n+2 times (2-byte RLE)
|
||||
else if (c0 == 0xfc) {
|
||||
n = ((*b++) + 2) * 2;
|
||||
x += n;
|
||||
c0 = *b++;
|
||||
c1 = *b++;
|
||||
pc0 = c0 | (c1 << 8);
|
||||
c0 = *b++;
|
||||
c1 = *b++;
|
||||
pc1 = c0 | (c1 << 8);
|
||||
for (int i = 0; i < n; i+=2) {
|
||||
pic[loc + i ] = pc0;
|
||||
pic[loc + i + 1] = pc1;
|
||||
}
|
||||
}
|
||||
// copy 1 pixel from previous line (left diagonal)
|
||||
else if (c0 == 0xfb) {
|
||||
x++;
|
||||
pic[loc] = pic[loc - scanline - 1];
|
||||
}
|
||||
// copy 1 pixel from previous line (right diagonal)
|
||||
else if (c0 == 0xfa) {
|
||||
x++;
|
||||
pic[loc] = pic[loc - scanline + 1];
|
||||
}
|
||||
// this one's a bit tricky, but basically what this does is it
|
||||
// combines one byte with the next n bytes
|
||||
else if (c0 == 0xf9) {
|
||||
n = (*b++) + 1;
|
||||
x += n;
|
||||
c0 = *b++;
|
||||
c1 = *b++;
|
||||
pc0 = ((c0 & 0xe0)<<8) + ((c0 & 0x18)<<6) + ((c0 & 0x07)<<2);
|
||||
pc1 = ((c1 & 0xc0)<<5) + ((c1 & 0x3c)<<3) + (c1 & 0x03);
|
||||
pic[loc] = pc0 + pc1;
|
||||
for (int i = 1; i < n; i++) {
|
||||
c1 = *b++;
|
||||
pc1 = ((c1 & 0xc0)<<5) + ((c1 & 0x3c)<<3) + (c1 & 0x03);
|
||||
pic[loc + i] = pc0 | pc1;
|
||||
}
|
||||
}
|
||||
// not sure why this exists, probably padding
|
||||
else {
|
||||
c0 = *b++;
|
||||
c1 = *b++;
|
||||
x++;
|
||||
pic[loc] = c0 | (c1 << 8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return pic;
|
||||
}
|
||||
|
||||
static void pms_read_header(struct pms_header *hdr, const uint8_t *data)
|
||||
{
|
||||
hdr->version = LittleEndian_getW(data, 2);
|
||||
hdr->header_size = LittleEndian_getW(data, 4);
|
||||
hdr->bpp = data[6];
|
||||
hdr->bpps = data[7];
|
||||
hdr->sf = data[8];
|
||||
hdr->bf = LittleEndian_getW(data, 10);
|
||||
hdr->x = LittleEndian_getDW(data, 16);
|
||||
hdr->y = LittleEndian_getDW(data, 20);
|
||||
hdr->width = LittleEndian_getDW(data, 24);
|
||||
hdr->height = LittleEndian_getDW(data, 28);
|
||||
hdr->dp = LittleEndian_getDW(data, 32);
|
||||
hdr->pp = LittleEndian_getDW(data, 36);
|
||||
hdr->cp = LittleEndian_getDW(data, 40);
|
||||
}
|
||||
|
||||
static void pms_init_metrics(struct pms_header *pms, struct cg_metrics *dst)
|
||||
{
|
||||
dst->x = pms->x;
|
||||
dst->y = pms->y;
|
||||
dst->w = pms->width;
|
||||
dst->h = pms->height;
|
||||
dst->bpp = pms->bpp;
|
||||
dst->has_pixel = pms->dp;
|
||||
dst->has_alpha = pms->pp;
|
||||
dst->pixel_pitch = pms->width * (pms->bpp / 8);
|
||||
dst->alpha_pitch = 1;
|
||||
}
|
||||
|
||||
/* Load a PMS8 CG. */
|
||||
static void pms8_load(const uint8_t *data, struct pms_header *pms, struct cg *cg)
|
||||
{
|
||||
cg->pal = pms_get_palette(data + pms->pp);
|
||||
|
||||
cg->type = ALCG_PMS8;
|
||||
cg->pixels = pms8_extract(pms, data + pms->dp);
|
||||
// TODO: convert to RGBA
|
||||
}
|
||||
|
||||
static void pms16_load(const uint8_t *data, struct pms_header *pms, struct cg *cg)
|
||||
{
|
||||
cg->type = ALCG_PMS16;
|
||||
cg->pixels = pms16_extract(pms, data + pms->dp);
|
||||
|
||||
if (pms->pp) {
|
||||
// TODO: alpha channel
|
||||
//uint8_t *alpha = malloc((pms->width + 10) * (pms->height + 10));
|
||||
//pms8_extract(pms, alpha, data + pms->pp);
|
||||
}
|
||||
// TODO: convert to RGBA
|
||||
}
|
||||
|
||||
void pms_extract(const uint8_t *data, size_t size, struct cg *cg)
|
||||
{
|
||||
struct pms_header pms;
|
||||
pms_read_header(&pms, data);
|
||||
pms_init_metrics(&pms, &cg->metrics);
|
||||
|
||||
if ((size_t)pms.dp > size)
|
||||
ERROR("PMS pixel offset out of bounds");
|
||||
if ((size_t)pms.pp > size)
|
||||
ERROR("PMS palette/alpha offset out of bounds");
|
||||
|
||||
if (pms.bpp == 8)
|
||||
pms8_load(data, &pms, cg);
|
||||
else if (pms.bpp == 16)
|
||||
pms16_load(data, &pms, cg);
|
||||
else
|
||||
WARNING("Unsupported PMS bpp: %d", pms.bpp);
|
||||
}
|
||||
|
||||
uint8_t *pms_extract_mask(const uint8_t *data, size_t size)
|
||||
{
|
||||
struct pms_header pms;
|
||||
pms_read_header(&pms, data);
|
||||
|
||||
if ((size_t)pms.dp > size)
|
||||
ERROR("PMS pixel offset out of bounds");
|
||||
if ((size_t)pms.pp > size)
|
||||
ERROR("PMS palette/alpha offset out of bounds");
|
||||
|
||||
if (pms.bpp != 8)
|
||||
ERROR("PMS mask is not 8bpp");
|
||||
|
||||
return pms8_extract(&pms, data + pms.dp);
|
||||
}
|
||||
@@ -1,287 +0,0 @@
|
||||
/*
|
||||
* qnt.c extract QNT cg
|
||||
*
|
||||
* Copyright (C) 1997-1998 Masaki Chikama (Wren) <chikama@kasumi.ipl.mech.nagoya-u.ac.jp>
|
||||
* 1998- <masaki-c@is.aist-nara.ac.jp>
|
||||
* 2019 Nunuhara Cabbage <nunuhara@haniwa.technology>
|
||||
*
|
||||
* This program 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 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
* @version 1.0 01/07/30 initial version
|
||||
* @version 1.1 01/08/10 fix alpha map extraction
|
||||
* @version 1.2 01/11/26 zlibの展開バッファが画像サイズよりも大きなものを
|
||||
* 要求するときの workaround
|
||||
* @version 1.2 01/11/28 format version 1 に対応
|
||||
* *(top + 4) is 0 -> version 0
|
||||
* *(top + 4) is 1 -> version 1
|
||||
* @version 1.3 03/02/22 縦横奇数サイズ時にpixel/alphaともおかしかったのをfix
|
||||
* zlib展開バッファを縦横それぞれ1byteづつ多くとる
|
||||
* ようにした
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <zlib.h>
|
||||
#include "little_endian.h"
|
||||
#include "system4.h"
|
||||
#include "system4/cg.h"
|
||||
#include "system4/qnt.h"
|
||||
|
||||
/*
|
||||
zlib の展開バッファで、幅×高さ×3に、さらにどれくらい余裕をとるか
|
||||
(リクルスで 1164バイトというのがあった)
|
||||
*/
|
||||
#define ZLIBBUF_MARGIN 5*1024
|
||||
|
||||
/*
|
||||
* Get information from header
|
||||
*
|
||||
* b: raw data
|
||||
*
|
||||
* return: acquired qnt information object
|
||||
*/
|
||||
void qnt_extract_header(const uint8_t *b, struct qnt_header *qnt)
|
||||
{
|
||||
int rsv0;
|
||||
|
||||
rsv0 = LittleEndian_getDW(b, 4);
|
||||
if (rsv0 == 0) {
|
||||
qnt->hdr_size = 48;
|
||||
qnt->x0 = LittleEndian_getDW(b, 8);
|
||||
qnt->y0 = LittleEndian_getDW(b, 12);
|
||||
qnt->width = LittleEndian_getDW(b, 16);
|
||||
qnt->height = LittleEndian_getDW(b, 20);
|
||||
qnt->bpp = LittleEndian_getDW(b, 24);
|
||||
qnt->rsv = LittleEndian_getDW(b, 28);
|
||||
qnt->pixel_size = LittleEndian_getDW(b, 32);
|
||||
qnt->alpha_size = LittleEndian_getDW(b, 36);
|
||||
} else {
|
||||
qnt->hdr_size = LittleEndian_getDW(b, 8);
|
||||
qnt->x0 = LittleEndian_getDW(b, 12);
|
||||
qnt->y0 = LittleEndian_getDW(b, 16);
|
||||
qnt->width = LittleEndian_getDW(b, 20);
|
||||
qnt->height = LittleEndian_getDW(b, 24);
|
||||
qnt->bpp = LittleEndian_getDW(b, 28);
|
||||
qnt->rsv = LittleEndian_getDW(b, 32);
|
||||
qnt->pixel_size = LittleEndian_getDW(b, 36);
|
||||
qnt->alpha_size = LittleEndian_getDW(b, 40);
|
||||
}
|
||||
if (qnt->bpp != 24)
|
||||
ERROR("Unsupported bits-per-pixel: %d", qnt->bpp);
|
||||
}
|
||||
|
||||
/*
|
||||
* Do extract qnt pixel image
|
||||
*
|
||||
* qnt: qnt header information
|
||||
* pic: pixel to be stored
|
||||
* b : raw data (pointer to pixel)
|
||||
*/
|
||||
static void extract_pixel(struct qnt_header *qnt, uint8_t *pic, const uint8_t *b)
|
||||
{
|
||||
int i, j, x, y, w, h;
|
||||
unsigned long ucbuf = (qnt->width+1) * (qnt->height+1) * 3 + ZLIBBUF_MARGIN;
|
||||
uint8_t *raw = malloc(sizeof(uint8_t) * ucbuf);
|
||||
|
||||
if (Z_OK != uncompress(raw, &ucbuf, b, qnt->pixel_size)) {
|
||||
WARNING("uncompress failed\n");
|
||||
free(raw);
|
||||
return;
|
||||
}
|
||||
|
||||
w = qnt->width;
|
||||
h = qnt->height;
|
||||
|
||||
j = 0;
|
||||
for (i = 2; i >= 0; i--) {
|
||||
for (y = 0; y < (h -1); y+=2) {
|
||||
for (x = 0; x < (w -1); x+=2) {
|
||||
pic[( y *w+x) *3 +i] = raw[j];
|
||||
pic[((y+1)*w+x) *3 +i] = raw[j+1];
|
||||
pic[( y *w+x+1)*3 +i] = raw[j+2];
|
||||
pic[((y+1)*w+x+1)*3 +i] = raw[j+3];
|
||||
j+=4;
|
||||
}
|
||||
if (x != w) {
|
||||
pic[( y *w+x)*3 +i] = raw[j];
|
||||
pic[((y+1)*w+x)*3 +i] = raw[j+1];
|
||||
j+=4;
|
||||
}
|
||||
}
|
||||
if (y != h) {
|
||||
for (x = 0; x < (w -1); x+=2) {
|
||||
pic[(y*w+x )*3+i] = raw[j];
|
||||
pic[(y*w+x+1)*3+i] = raw[j+2];
|
||||
j+=4;
|
||||
}
|
||||
if (x != w) {
|
||||
pic[( y *w+x)*3 +i] = raw[j];
|
||||
j+=4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (w > 1) {
|
||||
for (x = 1; x < w; x++) {
|
||||
pic[x*3 ] = pic[(x-1)*3 ] - pic[x*3 ];
|
||||
pic[x*3+1] = pic[(x-1)*3+1] - pic[x*3+1];
|
||||
pic[x*3+2] = pic[(x-1)*3+2] - pic[x*3+2];
|
||||
}
|
||||
}
|
||||
|
||||
if (h > 1) {
|
||||
for (y = 1; y < h; y++) {
|
||||
pic[(y*w)*3 ] = pic[((y-1)*w)*3 ] - pic[(y*w)*3 ];
|
||||
pic[(y*w)*3+1] = pic[((y-1)*w)*3+1] - pic[(y*w)*3+1];
|
||||
pic[(y*w)*3+2] = pic[((y-1)*w)*3+2] - pic[(y*w)*3+2];
|
||||
|
||||
for (x = 1; x < w; x++) {
|
||||
int px, py;
|
||||
py = pic[((y-1)*w+x )*3];
|
||||
px = pic[( y *w+x-1)*3];
|
||||
pic[(y*w+x)*3] = ((py+px)>>1) - pic[(y*w+x)*3];
|
||||
py = pic[((y-1)*w+x )*3+1];
|
||||
px = pic[( y *w+x-1)*3+1];
|
||||
pic[(y*w+x)*3+1] = ((py+px)>>1) - pic[(y*w+x)*3+1];
|
||||
py = pic[((y-1)*w+x )*3+2];
|
||||
px = pic[( y *w+x-1)*3+2];
|
||||
pic[(y*w+x)*3+2] = ((py+px)>>1) - pic[(y*w+x)*3+2];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
free(raw);
|
||||
}
|
||||
|
||||
/*
|
||||
* Do extract qnt alpha image
|
||||
*
|
||||
* qnt: qnt header information
|
||||
* pic: pixel to be stored
|
||||
* b : raw data (pointer to alpha pixel)
|
||||
*/
|
||||
static void extract_alpha(struct qnt_header *qnt, uint8_t *pic, const uint8_t *b)
|
||||
{
|
||||
int i, x, y, w, h;
|
||||
unsigned long ucbuf = (qnt->width+1) * (qnt->height+1) + ZLIBBUF_MARGIN;
|
||||
uint8_t *raw = malloc(sizeof(uint8_t) * ucbuf);
|
||||
|
||||
if (Z_OK != uncompress(raw, &ucbuf, b, qnt->alpha_size)) {
|
||||
WARNING("uncompress failed\n");
|
||||
free(raw);
|
||||
return;
|
||||
}
|
||||
|
||||
w = qnt->width;
|
||||
h = qnt->height;
|
||||
|
||||
i = 1;
|
||||
if (w > 1) {
|
||||
pic[0] = raw[0];
|
||||
for (x = 1; x < w; x++) {
|
||||
pic[x] = pic[x-1] - raw[i];
|
||||
i++;
|
||||
}
|
||||
if (w%2) i++;
|
||||
}
|
||||
|
||||
if (h > 1) {
|
||||
for (y = 1; y < h; y++) {
|
||||
pic[y*w] = pic[(y-1) *w] - raw[i]; i++;
|
||||
for (x = 1; x < w; x++) {
|
||||
int pax, pay;
|
||||
pax = pic[ y * w + x -1];
|
||||
pay = pic[(y-1)* w + x ];
|
||||
pic[y*w+x] = ((pax+pay) >> 1) - raw[i];
|
||||
i++;
|
||||
}
|
||||
if (w%2) i++;
|
||||
}
|
||||
}
|
||||
|
||||
free(raw);
|
||||
}
|
||||
|
||||
/*
|
||||
* Check data is qnt format cg or not
|
||||
*
|
||||
* data: raw data (pointer to data top)
|
||||
*
|
||||
* return: true if data is qnt
|
||||
*/
|
||||
bool qnt_checkfmt(const uint8_t *data)
|
||||
{
|
||||
if (data[0] != 'Q' || data[1] != 'N' || data[2] != 'T') return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void qnt_init_metrics(struct qnt_header *qnt, struct cg_metrics *dst)
|
||||
{
|
||||
dst->w = qnt->width;
|
||||
dst->h = qnt->height;
|
||||
dst->bpp = qnt->bpp;
|
||||
dst->has_pixel = qnt->pixel_size > 0;
|
||||
dst->has_alpha = qnt->alpha_size > 0;
|
||||
dst->pixel_pitch = qnt->width * (qnt->bpp / 8);
|
||||
dst->alpha_pitch = 1;
|
||||
}
|
||||
|
||||
bool qnt_get_metrics(const uint8_t *data, struct cg_metrics *dst)
|
||||
{
|
||||
struct qnt_header qnt;
|
||||
qnt_extract_header(data, &qnt);
|
||||
qnt_init_metrics(&qnt, dst);
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Extract qnt header and pixel
|
||||
*
|
||||
* data: raw data (pointer to data top)
|
||||
*
|
||||
* return: extracted image data and information
|
||||
*/
|
||||
void qnt_extract(const uint8_t *data, struct cg *cg)
|
||||
{
|
||||
struct qnt_header qnt;
|
||||
qnt_extract_header(data, &qnt);
|
||||
qnt_init_metrics(&qnt, &cg->metrics);
|
||||
|
||||
uint8_t *pixels = xcalloc(3, (qnt.width+10) * (qnt.height+10));
|
||||
extract_pixel(&qnt, pixels, data + qnt.hdr_size);
|
||||
|
||||
cg->type = ALCG_QNT;
|
||||
|
||||
// combine color/alpha data
|
||||
uint8_t *alpha = xmalloc((qnt.width+10) * (qnt.height+10));
|
||||
uint8_t *tmp = xmalloc((qnt.width+10) * (qnt.height+10) * 4);
|
||||
if (qnt.alpha_size) {
|
||||
extract_alpha(&qnt, alpha, data + qnt.hdr_size + qnt.pixel_size);
|
||||
} else {
|
||||
// FIXME: Some CGs don't display correctly unless we add an alpha channel here.
|
||||
// Not sure why. It seems to affect some but not all alpha-less CGs.
|
||||
// E.g. CG#90 (and similar) from the Rance 2 digest version.
|
||||
memset(alpha, 0xFF, (qnt.width+10)*(qnt.height+10));
|
||||
}
|
||||
for (int src_i = 0, dst_i = 0, p = 0; p < qnt.width * qnt.height; p++) {
|
||||
tmp[dst_i++] = pixels[src_i++];
|
||||
tmp[dst_i++] = pixels[src_i++];
|
||||
tmp[dst_i++] = pixels[src_i++];
|
||||
tmp[dst_i++] = alpha[p];
|
||||
}
|
||||
free(alpha);
|
||||
free(pixels);
|
||||
cg->pixels = tmp;
|
||||
}
|
||||
-397
@@ -1,397 +0,0 @@
|
||||
/* Copyright (C) 2019 Nunuhara Cabbage <nunuhara@haniwa.technology>
|
||||
*
|
||||
* This program 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 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 this program; if not, see <http://gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "system4.h"
|
||||
#include "system4/string.h"
|
||||
#include "system4/utfsjis.h"
|
||||
|
||||
struct string EMPTY_STRING = {
|
||||
.cow = true,
|
||||
.ref = 1,
|
||||
.size = 0,
|
||||
.text = ""
|
||||
};
|
||||
|
||||
static struct string *alloc_string(int size)
|
||||
{
|
||||
return xmalloc(sizeof(struct string) + size + 1);
|
||||
}
|
||||
|
||||
void free_string(struct string *str)
|
||||
{
|
||||
if (!str->ref)
|
||||
ERROR("Double free of string object");
|
||||
if (!--str->ref) {
|
||||
free(str);
|
||||
}
|
||||
}
|
||||
|
||||
struct string *make_string(const char *str, unsigned int len)
|
||||
{
|
||||
struct string *s = alloc_string(len);
|
||||
s->size = len;
|
||||
s->ref = 1;
|
||||
s->cow = 0;
|
||||
memcpy(s->text, str, len);
|
||||
s->text[len] = '\0';
|
||||
return s;
|
||||
}
|
||||
|
||||
struct string *cstr_to_string(const char *str)
|
||||
{
|
||||
return make_string(str, strlen(str));
|
||||
}
|
||||
|
||||
struct string *string_ref(struct string *s)
|
||||
{
|
||||
s->cow = 1;
|
||||
s->ref++;
|
||||
return s;
|
||||
}
|
||||
|
||||
struct string *string_dup(const struct string *in)
|
||||
{
|
||||
struct string *out = alloc_string(in->size);
|
||||
out->size = in->size;
|
||||
out->ref = 1;
|
||||
out->cow = 0;
|
||||
memcpy(out->text, in->text, in->size + 1);
|
||||
return out;
|
||||
}
|
||||
|
||||
struct string *integer_to_string(int n)
|
||||
{
|
||||
char buf[512];
|
||||
int len = snprintf(buf, 512, "%d", n);
|
||||
return make_string(buf, len);
|
||||
}
|
||||
|
||||
static void number_zen2han(char *buf)
|
||||
{
|
||||
for (int src = 0, dst = 0; buf[src];) {
|
||||
uint8_t b1 = buf[src];
|
||||
if (SJIS_2BYTE(b1)) {
|
||||
uint8_t b2 = buf[src+1];
|
||||
if (b1 == 0x82 && b2 >= 0x4f && b2 <= 0x58) {
|
||||
buf[dst++] = '0' + (b2 - 0x4f);
|
||||
src += 2;
|
||||
} else if (b1 == 0x81 && b2 == 0x7c) {
|
||||
buf[dst++] = '-';
|
||||
src += 2;
|
||||
} else if (b1 == 0x81 && b2 == 0x44) {
|
||||
buf[dst++] = '.';
|
||||
src += 2;
|
||||
} else if (b1 == 0x81 && b2 == 0x40) {
|
||||
buf[dst++] = ' ';
|
||||
src += 2;
|
||||
} else {
|
||||
buf[dst++] = buf[src++];
|
||||
buf[dst++] = buf[src++];
|
||||
}
|
||||
} else {
|
||||
buf[dst++] = buf[src++];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int string_to_integer(struct string *s)
|
||||
{
|
||||
char *buf = xstrdup(s->text);
|
||||
number_zen2han(buf);
|
||||
int n = atoi(buf);
|
||||
free(buf);
|
||||
return n;
|
||||
}
|
||||
|
||||
struct string *float_to_string(float f, int precision)
|
||||
{
|
||||
char buf[512];
|
||||
int len;
|
||||
|
||||
// System40.exe pushes -1, defaults to 6
|
||||
if (precision < 0) {
|
||||
precision = 6;
|
||||
}
|
||||
|
||||
len = snprintf(buf, 512, "%.*f", precision, f);
|
||||
return make_string(buf, len);
|
||||
}
|
||||
|
||||
struct string *string_concatenate(const struct string *a, const struct string *b)
|
||||
{
|
||||
struct string *s = alloc_string(a->size + b->size);
|
||||
s->size = a->size + b->size;
|
||||
s->ref = 1;
|
||||
s->cow = 0;
|
||||
memcpy(s->text, a->text, a->size);
|
||||
memcpy(s->text + a->size, b->text, b->size + 1);
|
||||
return s;
|
||||
}
|
||||
|
||||
struct string *string_copy(const struct string *s, int index, int len)
|
||||
{
|
||||
if (index < 0)
|
||||
index = 0;
|
||||
if (len <= 0)
|
||||
return make_string("", 0);
|
||||
if ((index = sjis_index(s->text, index)) < 0)
|
||||
return make_string("", 0);
|
||||
|
||||
if ((len = sjis_index(s->text + index, len)) < 0)
|
||||
len = s->size - index;
|
||||
|
||||
return make_string(s->text + index, len);
|
||||
}
|
||||
|
||||
static struct string *cow_check(struct string *s)
|
||||
{
|
||||
if (s->cow && s->ref > 1) {
|
||||
struct string *out = string_dup(s);
|
||||
free_string(s);
|
||||
return out;
|
||||
}
|
||||
if (s->cow)
|
||||
s->cow = 0;
|
||||
return s;
|
||||
}
|
||||
|
||||
void string_append_cstr(struct string **_a, const char *b, size_t b_size)
|
||||
{
|
||||
if (!b_size)
|
||||
return;
|
||||
struct string *a = *_a = cow_check(*_a);
|
||||
|
||||
a = xrealloc(a, sizeof(struct string) + a->size + b_size + 1);
|
||||
memcpy(a->text + a->size, b, b_size);
|
||||
a->size += b_size;
|
||||
a->text[a->size] = '\0';
|
||||
*_a = a;
|
||||
}
|
||||
|
||||
void string_append(struct string **_a, const struct string *b)
|
||||
{
|
||||
struct string *a = *_a = cow_check(*_a);
|
||||
size_t a_size = a->size;
|
||||
|
||||
a = xrealloc(a, sizeof(struct string) + a->size + b->size + 1);
|
||||
a->size = a_size + b->size;
|
||||
memcpy(a->text + a_size, b->text, b->size + 1);
|
||||
*_a = a;
|
||||
}
|
||||
|
||||
void string_push_back(struct string **s, int c)
|
||||
{
|
||||
int bytes = SJIS_2BYTE(c) ? 2 : 1;
|
||||
*s = cow_check(*s);
|
||||
*s = xrealloc(*s, sizeof(struct string) + (*s)->size + bytes + 1);
|
||||
(*s)->text[(*s)->size++] = c & 0xFF;
|
||||
if (bytes == 2) {
|
||||
(*s)->text[(*s)->size++] = c >> 8;
|
||||
}
|
||||
(*s)->text[(*s)->size] = '\0';
|
||||
}
|
||||
|
||||
void string_pop_back(struct string **s)
|
||||
{
|
||||
*s = cow_check(*s);
|
||||
// get index of last character
|
||||
int c = 0;
|
||||
for (int i = 0; i < (*s)->size; i++) {
|
||||
c = i;
|
||||
if (SJIS_2BYTE((*s)->text[i])) {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
(*s)->text[c] = '\0';
|
||||
(*s)->size = c;
|
||||
}
|
||||
|
||||
void string_erase(struct string **s, int index)
|
||||
{
|
||||
int bytes;
|
||||
if (index < 0)
|
||||
index = 0;
|
||||
if (index >= (*s)->size)
|
||||
return;
|
||||
if ((index = sjis_index((*s)->text, index)) < 0)
|
||||
return;
|
||||
bytes = SJIS_2BYTE((*s)->text[index]) ? 2 : 1;
|
||||
|
||||
*s = cow_check(*s);
|
||||
size_t size = (*s)->size - index - bytes;
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
(*s)->text[index+i] = (*s)->text[index+bytes+i];
|
||||
}
|
||||
(*s)->size -= bytes;
|
||||
(*s)->text[(*s)->size] = '\0';
|
||||
}
|
||||
|
||||
void string_clear(struct string *s)
|
||||
{
|
||||
s->size = 0;
|
||||
s->text[0] = '\0';
|
||||
}
|
||||
|
||||
int string_find(const struct string *haystack, const struct string *needle)
|
||||
{
|
||||
int c = 0;
|
||||
for (int i = 0; i < haystack->size; i++, c++) {
|
||||
if (!strncmp(haystack->text+i, needle->text, needle->size))
|
||||
return c;
|
||||
if (SJIS_2BYTE(haystack->text[i])) {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int string_get_char(const struct string *str, int i)
|
||||
{
|
||||
if ((i = sjis_index(str->text, i)) < 0)
|
||||
ERROR("String index out of bounds");
|
||||
|
||||
if (SJIS_2BYTE(str->text[i]))
|
||||
return (uint8_t)str->text[i] | ((uint8_t)str->text[i+1] << 8);
|
||||
return str->text[i];
|
||||
}
|
||||
|
||||
void string_set_char(struct string **_str, int i, unsigned int c)
|
||||
{
|
||||
struct string *str = *_str = cow_check(*_str);
|
||||
int bytes_src, bytes_dst;
|
||||
|
||||
if ((i = sjis_index(str->text, i)) < 0)
|
||||
ERROR("String index out of bounds");
|
||||
|
||||
bytes_src = SJIS_2BYTE(c) ? 2 : 1;
|
||||
bytes_dst = SJIS_2BYTE(str->text[i]) ? 2 : 1;
|
||||
|
||||
if (bytes_src == 1 && bytes_dst == 1) {
|
||||
str->text[i] = c;
|
||||
} else if (bytes_src == 2 && bytes_dst == 2) {
|
||||
str->text[i] = c & 0xFF;
|
||||
str->text[i+1] = (c >> 8) & 0xFF;
|
||||
} else if (bytes_src == 1 && bytes_dst == 2) {
|
||||
// shrink 1 byte
|
||||
str->text[i] = c;
|
||||
for (int j = i+1; j < str->size; j++) {
|
||||
str->text[j] = str->text[j+1];
|
||||
}
|
||||
str->size--;
|
||||
} else if (bytes_src == 2 && bytes_dst == 1) {
|
||||
// grow 1 byte
|
||||
str = xrealloc(str, sizeof(struct string) + str->size + 2);
|
||||
str->size++;
|
||||
*_str = str;
|
||||
for (int j = str->size; j > i; j--) {
|
||||
str->text[j] = str->text[j-1];
|
||||
}
|
||||
str->text[i] = c & 0xFF;
|
||||
str->text[i+1] = (c >> 8) & 0xFF;
|
||||
}
|
||||
}
|
||||
|
||||
#define DIGIT_MAX 512
|
||||
|
||||
static int number_han2zen(char *buf, size_t size)
|
||||
{
|
||||
char *tmp = xmalloc(size*2);
|
||||
int i = 0;
|
||||
for (char *p = buf; *p && i < DIGIT_MAX-2; p++) {
|
||||
switch (*p) {
|
||||
case '0': case '1': case '2': case '3': case '4':
|
||||
case '5': case '6': case '7': case '8': case '9':
|
||||
tmp[i++] = 0x82;
|
||||
tmp[i++] = 0x4f + (*p - '0');
|
||||
break;
|
||||
case '-':
|
||||
tmp[i++] = 0x81;
|
||||
tmp[i++] = 0x7c;
|
||||
break;
|
||||
case '.':
|
||||
tmp[i++] = 0x81;
|
||||
tmp[i++] = 0x44;
|
||||
break;
|
||||
case ' ':
|
||||
tmp[i++] = 0x81;
|
||||
tmp[i++] = 0x40;
|
||||
break;
|
||||
default:
|
||||
tmp[i++] = *p;
|
||||
}
|
||||
}
|
||||
tmp[i] = '\0';
|
||||
|
||||
memcpy(buf, tmp, i+1);
|
||||
free(tmp);
|
||||
return i;
|
||||
}
|
||||
|
||||
int int_to_cstr(char *buf, size_t size, int v, int figures, bool zero_pad, bool zenkaku)
|
||||
{
|
||||
char fmt[64];
|
||||
int i = 0;
|
||||
|
||||
// prepare format string for snprintf
|
||||
fmt[i++] = '%';
|
||||
if (figures > 0) {
|
||||
if (zero_pad)
|
||||
fmt[i++] = '0';
|
||||
i += snprintf(fmt+i, 64-i, "%d", figures);
|
||||
}
|
||||
fmt[i++] = 'd';
|
||||
fmt[i] = '\0';
|
||||
|
||||
i = snprintf(buf, size-1, fmt, v);
|
||||
buf[i] = '\0';
|
||||
|
||||
if (zenkaku)
|
||||
i = number_han2zen(buf, size);
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
int float_to_cstr(char *buf, size_t size, float v, int figures, bool zero_pad, int precision, bool zenkaku)
|
||||
{
|
||||
char fmt[64];
|
||||
int i = 0;
|
||||
|
||||
fmt[i++] = '%';
|
||||
if (figures > 0) {
|
||||
if (zero_pad)
|
||||
fmt[i++] = '0';
|
||||
i += snprintf(fmt+i, 64-i, "%d", figures);
|
||||
}
|
||||
fmt[i++] = '.';
|
||||
i += snprintf(fmt+i, 64-i, "%d", precision);
|
||||
fmt[i++] = 'f';
|
||||
fmt[i] = '\0';
|
||||
|
||||
i = snprintf(buf, size-1, fmt, v);
|
||||
buf[i] = '\0';
|
||||
|
||||
if (zenkaku) {
|
||||
i = number_han2zen(buf, DIGIT_MAX);
|
||||
// XXX: bug in System40.exe
|
||||
buf[i++] = 'F';
|
||||
buf[i] = '\0';
|
||||
}
|
||||
return i;
|
||||
}
|
||||
-110
@@ -1,110 +0,0 @@
|
||||
/* Copyright (C) 2019 Nunuhara Cabbage <nunuhara@haniwa.technology>
|
||||
*
|
||||
* This program 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 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 this program; if not, see <http://gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "system4.h"
|
||||
#include "system4/utfsjis.h"
|
||||
|
||||
mem_alloc void *_xmalloc(size_t size, const char *func)
|
||||
{
|
||||
void *ptr = malloc(size);
|
||||
if (!ptr) {
|
||||
sys_error("*ERROR*(%s): Out of memory\n", func);
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
|
||||
mem_alloc void *_xcalloc(size_t nmemb, size_t size, const char *func)
|
||||
{
|
||||
void *ptr = calloc(nmemb, size);
|
||||
if (!ptr) {
|
||||
sys_error("*ERROR*(%s): Out of memory\n", func);
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
|
||||
mem_alloc void *_xrealloc(void *ptr, size_t size, const char *func)
|
||||
{
|
||||
ptr = realloc(ptr, size);
|
||||
if (!ptr) {
|
||||
sys_error("*ERROR*(%s): Out of memory\n", func);
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
|
||||
mem_alloc char *_xstrdup(const char *in, const char *func)
|
||||
{
|
||||
char *out = strdup(in);
|
||||
if (!out) {
|
||||
sys_error("*ERROR*(%s): Out of memory\n", func);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
mem_alloc void *xrealloc_array(void *dst, size_t old_nmemb, size_t new_nmemb, size_t size)
|
||||
{
|
||||
dst = xrealloc(dst, new_nmemb * size);
|
||||
if (new_nmemb > old_nmemb)
|
||||
memset((char*)dst + old_nmemb*size, 0, (new_nmemb - old_nmemb) * size);
|
||||
return dst;
|
||||
}
|
||||
|
||||
noreturn void sys_verror(const char *fmt, va_list ap)
|
||||
{
|
||||
vfprintf(stderr, fmt, ap);
|
||||
sys_exit(1);
|
||||
}
|
||||
|
||||
noreturn void sys_error(const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
sys_verror(fmt, ap);
|
||||
}
|
||||
|
||||
void sys_vwarning(const char *fmt, va_list ap)
|
||||
{
|
||||
vfprintf(stderr, fmt, ap);
|
||||
}
|
||||
|
||||
void sys_warning(const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
sys_vwarning(fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void sys_message(const char *fmt, ...)
|
||||
{
|
||||
if (sys_silent)
|
||||
return;
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
vprintf(fmt, ap);
|
||||
va_end(ap);
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
noreturn void sys_exit(int code)
|
||||
{
|
||||
// TODO: cleanup
|
||||
exit(code);
|
||||
}
|
||||
@@ -12,11 +12,9 @@ acxbuild += bisongen.process('csv_parser.y')
|
||||
executable('acxdump', acxdump,
|
||||
dependencies : tool_deps,
|
||||
c_args : ['-Wno-unused-parameter'],
|
||||
include_directories : incdir,
|
||||
link_with : libsys4)
|
||||
include_directories : incdir)
|
||||
|
||||
executable('acxbuild', acxbuild,
|
||||
dependencies : tool_deps,
|
||||
c_args : ['-Wno-unused-parameter'],
|
||||
include_directories : incdir,
|
||||
link_with : libsys4)
|
||||
include_directories : incdir)
|
||||
|
||||
@@ -4,12 +4,10 @@ aincmp = ['../dasm.c',
|
||||
|
||||
executable('aincmp', aincmp,
|
||||
dependencies : tool_deps,
|
||||
include_directories : incdir,
|
||||
link_with : libsys4)
|
||||
include_directories : incdir)
|
||||
|
||||
excmp = ['excmp.c']
|
||||
|
||||
executable('excmp', excmp,
|
||||
dependencies : tool_deps,
|
||||
include_directories : incdir,
|
||||
link_with : libsys4)
|
||||
include_directories : incdir)
|
||||
|
||||
@@ -10,5 +10,4 @@ aindump = ['../../cJSON.c',
|
||||
|
||||
executable('aindump', aindump,
|
||||
dependencies : tool_deps,
|
||||
include_directories : incdir,
|
||||
link_with : libsys4)
|
||||
include_directories : incdir)
|
||||
|
||||
@@ -21,5 +21,4 @@ ainedit += bisongen.process('asm_parser.y', 'text_parser.y', 'jaf_parser.y')
|
||||
executable('ainedit', ainedit,
|
||||
dependencies : tool_deps,
|
||||
c_args : ['-Wno-unused-parameter'],
|
||||
include_directories : incdir,
|
||||
link_with : libsys4)
|
||||
include_directories : incdir)
|
||||
|
||||
@@ -5,5 +5,4 @@ alicear = ['alice-ar.c',
|
||||
executable('alice-ar', alicear,
|
||||
dependencies: tool_deps,
|
||||
c_args : ['-Wno-unused-parameter'],
|
||||
include_directories : incdir,
|
||||
link_with : libsys4)
|
||||
include_directories : incdir)
|
||||
|
||||
@@ -15,11 +15,9 @@ exbuild += bisongen.process('ex_parser.y')
|
||||
executable('exdump', exdump,
|
||||
dependencies : tool_deps,
|
||||
c_args : ['-Wno-unused-parameter'],
|
||||
include_directories : incdir,
|
||||
link_with : libsys4)
|
||||
include_directories : incdir)
|
||||
|
||||
executable('exbuild', exbuild,
|
||||
dependencies : tool_deps,
|
||||
c_args : ['-Wno-unused-parameter'],
|
||||
include_directories : incdir,
|
||||
link_with : libsys4)
|
||||
include_directories : incdir)
|
||||
|
||||
@@ -4,5 +4,4 @@ fnldump = ['fnldump.c',
|
||||
executable('fnldump', fnldump,
|
||||
dependencies: tool_deps,
|
||||
c_args : ['-Wno-unused-parameter'],
|
||||
include_directories : incdir,
|
||||
link_with : libsys4)
|
||||
include_directories : incdir)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
if build_machine.system() == 'windows'
|
||||
iconv = dependency('iconv')
|
||||
tool_deps = [libm, zlib, iconv]
|
||||
tool_deps = [libm, zlib, iconv, libsys4_dep]
|
||||
else
|
||||
tool_deps = [libm, zlib]
|
||||
tool_deps = [libm, zlib, libsys4_dep]
|
||||
endif
|
||||
|
||||
subdir('acx')
|
||||
|
||||
-213
@@ -1,213 +0,0 @@
|
||||
/*
|
||||
* utfsjis.c -- utf-8/sjis related function
|
||||
*
|
||||
* Copyright (C) 1997 Yutaka OIWA <oiwa@is.s.u-tokyo.ac.jp>
|
||||
*
|
||||
* written for Satoshi KURAMOCHI's "eplaymidi"
|
||||
* <satoshi@ueda.info.waseda.ac.jp>
|
||||
*
|
||||
* This program 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 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "system4/utfsjis.h"
|
||||
#include "system4/s2utbl.h"
|
||||
|
||||
// Convert a character-index to a byte-index for a given SJIS string.
|
||||
int sjis_index(const char *_src, int index)
|
||||
{
|
||||
const uint8_t *src = (uint8_t*)_src;
|
||||
int i, c;
|
||||
for (i = 0, c = 0; c < index && src[i]; i++, c++) {
|
||||
if (SJIS_2BYTE(src[i])) {
|
||||
i++;
|
||||
// check for invalid sjis data
|
||||
if (!src[i])
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return src[i] ? i : -1;
|
||||
}
|
||||
|
||||
#include "system4.h"
|
||||
|
||||
char *sjis2utf(const char *_src, size_t len) {
|
||||
if (!len)
|
||||
len = strlen(_src);
|
||||
const uint8_t *src = (uint8_t*)_src;
|
||||
uint8_t* dst = malloc(len * 3 + 1);
|
||||
uint8_t* dstp = dst;
|
||||
|
||||
while (*src) {
|
||||
if (*src <= 0x7f) {
|
||||
*dstp++ = *src++;
|
||||
continue;
|
||||
}
|
||||
|
||||
int c;
|
||||
if (*src >= 0xa0 && *src <= 0xdf) {
|
||||
c = 0xff60 + *src - 0xa0;
|
||||
src++;
|
||||
} else {
|
||||
// guard against invalid byte sequence
|
||||
c = *(src+1);
|
||||
if (c < 0x40 || c == 0x7f || c > 0xfc) {
|
||||
*dstp++ = '?';
|
||||
src++;
|
||||
continue;
|
||||
}
|
||||
c = s2u[*src - 0x80][*(src+1) - 0x40];
|
||||
src += 2;
|
||||
}
|
||||
|
||||
if (c <= 0x7f) {
|
||||
*dstp++ = c;
|
||||
} else if (c <= 0x7ff) {
|
||||
*dstp++ = 0xc0 | c >> 6;
|
||||
*dstp++ = 0x80 | (c & 0x3f);
|
||||
} else {
|
||||
*dstp++ = 0xe0 | c >> 12;
|
||||
*dstp++ = 0x80 | (c >> 6 & 0x3f);
|
||||
*dstp++ = 0x80 | (c & 0x3f);
|
||||
}
|
||||
}
|
||||
*dstp = '\0';
|
||||
return (char*)dst;
|
||||
}
|
||||
|
||||
static int unicode_to_sjis(int u) {
|
||||
for (int b1 = 0x81; b1 <= 0xff; b1++) {
|
||||
if (b1 >= 0xa0 && b1 <= 0xdf)
|
||||
continue;
|
||||
for (int b2 = 0x40; b2 <= 0xff; b2++) {
|
||||
if (u == s2u[b1 - 0x80][b2 - 0x40])
|
||||
return b1 << 8 | b2;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
char *utf2sjis(const char *_src, size_t len) {
|
||||
if (!len)
|
||||
len = strlen(_src);
|
||||
const uint8_t *src = (uint8_t*)_src;
|
||||
uint8_t* dst = malloc(len + 1);
|
||||
uint8_t* dstp = dst;
|
||||
|
||||
while (*src) {
|
||||
if (*src <= 0x7f) {
|
||||
*dstp++ = *src++;
|
||||
continue;
|
||||
}
|
||||
|
||||
int u;
|
||||
if (*src <= 0xdf) {
|
||||
u = (src[0] & 0x1f) << 6 | (src[1] & 0x3f);
|
||||
src += 2;
|
||||
} else if (*src <= 0xef) {
|
||||
u = (src[0] & 0xf) << 12 | (src[1] & 0x3f) << 6 | (src[2] & 0x3f);
|
||||
src += 3;
|
||||
} else {
|
||||
*dstp++ = '?';
|
||||
do src++; while ((*src & 0xc0) == 0x80);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (u > 0xff60 && u <= 0xff9f) {
|
||||
*dstp++ = u - 0xff60 + 0xa0;
|
||||
} else {
|
||||
int c = unicode_to_sjis(u);
|
||||
if (c) {
|
||||
*dstp++ = c >> 8;
|
||||
*dstp++ = c & 0xff;
|
||||
} else {
|
||||
*dstp++ = '?';
|
||||
}
|
||||
}
|
||||
}
|
||||
*dstp = '\0';
|
||||
return (char*)dst;
|
||||
}
|
||||
|
||||
/* src 内に半角カナもしくはASCII文字があるかどうか */
|
||||
bool sjis_has_hankaku(const char *_src) {
|
||||
const uint8_t *src = (uint8_t*)_src;
|
||||
while(*src) {
|
||||
if (SJIS_2BYTE(*src)) {
|
||||
src++;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
src++;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* src 内に 全角文字があるかどうか */
|
||||
bool sjis_has_zenkaku(const char *_src) {
|
||||
const uint8_t *src = (uint8_t*)_src;
|
||||
while(*src) {
|
||||
if (SJIS_2BYTE(*src)) {
|
||||
return true;
|
||||
}
|
||||
src++;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* src 中の文字数を数える 全角文字も1文字 */
|
||||
int sjis_count_char(const char *_src) {
|
||||
const uint8_t *src = (uint8_t*)_src;
|
||||
int c = 0;
|
||||
|
||||
while(*src) {
|
||||
if (SJIS_2BYTE(*src)) {
|
||||
src++;
|
||||
}
|
||||
c++; src++;
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
/* SJIS(EUC) を含む文字列の ASCII を大文字化する */
|
||||
void sjis_toupper(char *_src) {
|
||||
uint8_t *src = (uint8_t*)_src;
|
||||
while(*src) {
|
||||
if (SJIS_2BYTE(*src)) {
|
||||
src++;
|
||||
} else {
|
||||
if (*src >= 0x60 && *src <= 0x7a) {
|
||||
*src &= 0xdf;
|
||||
}
|
||||
}
|
||||
src++;
|
||||
}
|
||||
}
|
||||
|
||||
/* SJIS を含む文字列の ASCII を大文字化する2 */
|
||||
char *sjis_toupper2(const char *_src, size_t len) {
|
||||
const uint8_t *src = (uint8_t*)_src;
|
||||
uint8_t *dst;
|
||||
|
||||
dst = malloc(len +1);
|
||||
if (dst == NULL) return NULL;
|
||||
strcpy((char*)dst, (char*)src);
|
||||
sjis_toupper((char*)dst);
|
||||
return (char*)dst;
|
||||
}
|
||||
-123
@@ -1,123 +0,0 @@
|
||||
/* Copyright (C) 2019 Nunuhara Cabbage <nunuhara@haniwa.technology>
|
||||
*
|
||||
* This program 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 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 this program; if not, see <http://gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <webp/decode.h>
|
||||
|
||||
#include "system4.h"
|
||||
#include "system4/ald.h"
|
||||
#include "system4/cg.h"
|
||||
#include "system4/webp.h"
|
||||
|
||||
#include "little_endian.h"
|
||||
#include "xsystem4.h"
|
||||
|
||||
bool webp_checkfmt(const uint8_t *data)
|
||||
{
|
||||
if (data[0] != 'R' || data[1] != 'I' || data[2] != 'F' || data[3] != 'F')
|
||||
return false;
|
||||
if (data[8] != 'W' || data[9] != 'E' || data[10] != 'B' || data[11] != 'P')
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void webp_init_metrics(struct cg_metrics *m)
|
||||
{
|
||||
m->x = 0;
|
||||
m->y = 0;
|
||||
m->bpp = 24;
|
||||
m->has_pixel = true;
|
||||
m->has_alpha = true; // FIXME
|
||||
m->pixel_pitch = m->w * 3;
|
||||
m->alpha_pitch = 1;
|
||||
}
|
||||
|
||||
void webp_get_metrics(uint8_t *data, size_t size, struct cg_metrics *m)
|
||||
{
|
||||
WebPGetInfo(data, size, &m->w, &m->h);
|
||||
webp_init_metrics(m);
|
||||
}
|
||||
|
||||
static int get_base_cg(uint8_t *data, size_t size)
|
||||
{
|
||||
if (size < 12)
|
||||
return -1;
|
||||
if (data[size-12] != 'O' || data[size-11] != 'V' || data[size-10] != 'E' || data[size-9] != 'R')
|
||||
return -1;
|
||||
int uk = LittleEndian_getDW(data, size-8); // size?
|
||||
if (uk != 4)
|
||||
WARNING("WEBP: expected 0x4 preceding base CG number, got %d", uk);
|
||||
return LittleEndian_getDW(data, size-4);
|
||||
}
|
||||
|
||||
void webp_extract(uint8_t *data, size_t size, struct cg *cg, struct archive *ar)
|
||||
{
|
||||
cg->pixels = WebPDecodeRGBA(data, size, &cg->metrics.w, &cg->metrics.h);
|
||||
webp_init_metrics(&cg->metrics);
|
||||
cg->type = ALCG_WEBP;
|
||||
|
||||
int base = get_base_cg(data, size);
|
||||
if (base < 0)
|
||||
return;
|
||||
|
||||
// FIXME: possible infinite recursion on broken/malicious ALD
|
||||
struct cg *base_cg = cg_load(ar, base-1);
|
||||
if (base_cg->metrics.w != cg->metrics.w || base_cg->metrics.h != cg->metrics.h) {
|
||||
ERROR("webp base CG dimensions don't match: (%d,%d) / (%d,%d)",
|
||||
base_cg->metrics.w, base_cg->metrics.h, cg->metrics.w, cg->metrics.h);
|
||||
}
|
||||
|
||||
// mask alpha color
|
||||
uint8_t *pixels = cg->pixels;
|
||||
uint8_t *base_pixels = base_cg->pixels;
|
||||
for (int row = 0; row < cg->metrics.h; row++) {
|
||||
for (int x = 0; x < cg->metrics.w; x++) {
|
||||
int p = cg->metrics.w*row*4 + x*4;
|
||||
if (pixels[p] == 255 && pixels[p+1] == 0 && pixels[p+2] == 255) {
|
||||
pixels[p+0] = base_pixels[p+0];
|
||||
pixels[p+1] = base_pixels[p+1];
|
||||
pixels[p+2] = base_pixels[p+2];
|
||||
pixels[p+3] = base_pixels[p+3];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cg_free(base_cg);
|
||||
}
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <webp/encode.h>
|
||||
|
||||
void webp_save(const char *path, uint8_t *pixels, int w, int h, bool alpha)
|
||||
{
|
||||
size_t len;
|
||||
uint8_t *output;
|
||||
FILE *f = fopen(path, "wb");
|
||||
if (!f)
|
||||
ERROR("fopen failed: %s", strerror(errno));
|
||||
if (alpha) {
|
||||
len = WebPEncodeLosslessRGBA(pixels, w, h, w*4, &output);
|
||||
} else {
|
||||
len = WebPEncodeLosslessRGB(pixels, w, h, w*3, &output);
|
||||
}
|
||||
if (!fwrite(output, len, 1, f))
|
||||
ERROR("fwrite failed: %s", strerror(errno));
|
||||
fclose(f);
|
||||
WebPFree(output);
|
||||
}
|
||||
Submodule
+1
Submodule subprojects/libsys4 added at 171f771302
Reference in New Issue
Block a user