rockbox/apps/plugins/xworld/engine.c
Franklin Wei 33cb13dee5 Xworld - Another World interpreter for Rockbox
Co-conspirators: Franklin Wei, Benjamin Brown

--------------------------------------------------------------------
This work is based on:
- Fabien Sanglard's "Fabother World" based on
- Piotr Padkowski's newRaw interpreter which was based on
- Gregory Montoir's reverse engineering of
- Eric Chahi's assembly code

--------------------------------------------------------------------
Progress:

* The plugin runs pretty nicely (with sound!) on most color targets
* Keymaps for color LCD targets are complete
* The manual entry is finished
* Grayscale/monochrome support is NOT PLANNED
  - the game looks horrible in grayscale! :p

--------------------------------------------------------------------
Notes:

* The original game strings were built-in to the executable, and
  were copyrighted and could not be used.
* This port ships with an alternate set of strings by default, but
  can load the "official" strings from a file at runtime.

--------------------------------------------------------------------
To be done (in descending order of importance):

* vertical stride compatibility                          <30% done>
* optimization                                           <10% done>

Change-Id: I3155b0d97c2ac470cb8a2040f40d4139ddcebfa5
Reviewed-on: http://gerrit.rockbox.org/1077
Reviewed-by: Michael Giacomelli <giac2000@hotmail.com>
2014-12-23 23:48:12 +01:00

397 lines
11 KiB
C

/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2014 Franklin Wei, Benjamin Brown
* Copyright (C) 2004 Gregory Montoir
*
* 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 software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
***************************************************************************/
#include "plugin.h"
#include "engine.h"
#include "file.h"
#include "serializer.h"
#include "sys.h"
#include "parts.h"
#include "video_data.h"
#include "video.h"
void engine_create(struct Engine* e, struct System* stub, const char* dataDir, const char* saveDir)
{
e->sys = stub;
e->sys->e = e;
e->_dataDir = dataDir;
e->_saveDir = saveDir;
mixer_create(&e->mixer, e->sys);
/* this needs to be here and not engine_init() to ensure that it is not called on a reset */
res_create(&e->res, &e->video, e->sys, dataDir);
res_allocMemBlock(&e->res);
video_create(&e->video, &e->res, e->sys);
player_create(&e->player, &e->mixer, &e->res, e->sys);
vm_create(&e->vm, &e->mixer, &e->res, &e->player, &e->video, e->sys);
}
void engine_run(struct Engine* e) {
while (!e->sys->input.quit) {
vm_checkThreadRequests(&e->vm);
vm_inp_updatePlayer(&e->vm);
engine_processInput(e);
vm_hostFrame(&e->vm);
}
}
/*
* this function loads the font in XWORLD_FONT_FILE into video_font
*/
/*
* the file format for the font file is like this:
* "XFNT" magic
* 8-bit version number
* <768 bytes data>
* sum of data, XOR'ed by version number repeated 4 times (32-bit)
*/
bool engine_loadFontFile(struct Engine* e)
{
uint8_t *old_font = sys_get_buffer(e->sys, sizeof(video_font));
rb->memcpy(old_font, video_font, sizeof(video_font));
File f;
file_create(&f, false);
if(!file_open(&f, XWORLD_FONT_FILE, e->_dataDir, "rb"))
{
goto fail;
}
/* read header */
char header[5];
int ret = file_read(&f, header, sizeof(header));
if(ret != sizeof(header) ||
header[0] != 'X' ||
header[1] != 'F' ||
header[2] != 'N' ||
header[3] != 'T')
{
warning("Invalid font file signature, falling back to alternate font");
goto fail;
}
if(header[4] != XWORLD_FONT_VERSION)
{
warning("Font file version mismatch (have=%d, need=%d), falling back to alternate font", header[4], XWORLD_FONT_VERSION);
goto fail;
}
uint32_t sum = 0;
for(unsigned int i = 0;i<sizeof(video_font);++i)
{
sum += video_font[i] = file_readByte(&f);
}
uint32_t mask = (header[4] << 24) |
(header[4] << 16) |
(header[4] << 8 ) |
(header[4] << 0 );
sum ^= mask;
uint32_t check = file_readUint32BE(&f);
if(check != sum)
{
warning("Bad font checksum, falling back to alternate font");
goto fail;
}
file_close(&f);
return true;
fail:
file_close(&f);
memcpy(video_font, old_font, sizeof(video_font));
return false;
}
/*
* this function loads the string table in STRING_TABLE_FILE into
* video_stringsTableEng
*/
/*
* the file format for the string table is like this:
* "XWST" magic
* 8-bit version number
* 8-bit title length
* <title data (0-255 bytes, _NO NULL_)
* 16-bit number of string entries (currently limited to 255)
* entry format:
struct file_entry_t
{
uint16_t id;
uint16_t len; - length of str
char* str; - NO NULL
}
*/
bool engine_loadStringTable(struct Engine* e)
{
File f;
file_create(&f, false);
if(!file_open(&f, STRING_TABLE_FILE, e->_dataDir, "rb"))
{
/*
* this gives verbose warnings while loadFontFile doesn't because the font looks similar
* enough to pass for the "original", but the strings don't
*/
warning("Unable to find string table, falling back to alternate strings");
goto fail;
}
/* read header */
char header[5];
int ret = file_read(&f, header, sizeof(header));
if(ret != sizeof(header) ||
header[0] != 'X' ||
header[1] != 'W' ||
header[2] != 'S' ||
header[3] != 'T')
{
warning("Invalid string table signature, falling back to alternate strings");
goto fail;
}
if(header[4] != STRING_TABLE_VERSION)
{
warning("String table version mismatch (have=%d, need=%d), falling back to alternate strings", header[4], STRING_TABLE_VERSION);
goto fail;
}
/* read title */
uint8_t title_length = file_readByte(&f);
char *title_buf;
if(title_length)
{
title_buf = sys_get_buffer(e->sys, (int32_t)title_length + 1); /* make room for the NULL */
ret = file_read(&f, title_buf, title_length);
if(ret != title_length)
{
warning("Title shorter than expected, falling back to alternate strings");
goto fail;
}
}
else
{
title_buf = "UNKNOWN";
}
/* read entries */
uint16_t num_entries = file_readUint16BE(&f);
for(unsigned int i = 0; i < num_entries && i < ARRAYLEN(video_stringsTableEng); ++i)
{
video_stringsTableEng[i].id = file_readUint16BE(&f);
uint16_t len = file_readUint16BE(&f);
if(file_ioErr(&f))
{
warning("Unexpected EOF in while parsing entry %d, falling back to alternate strings", i);
goto fail;
}
video_stringsTableEng[i].str = sys_get_buffer(e->sys, (int32_t)len + 1);
ret = file_read(&f, video_stringsTableEng[i].str, len);
if(ret != len)
{
warning("Entry %d too short, falling back to alternate strings", i);
goto fail;
}
}
file_close(&f);
rb->splashf(HZ, "String table '%s' loaded", title_buf);
return true;
fail:
file_close(&f);
return false;
}
void engine_init(struct Engine* e) {
sys_init(e->sys, "Out Of This World");
res_readEntries(&e->res);
engine_loadStringTable(e);
engine_loadFontFile(e);
video_init(&e->video);
vm_init(&e->vm);
mixer_init(&e->mixer);
player_init(&e->player);
/* Init virtual machine, legacy way */
/* vm_initForPart(&e->vm, GAME_PART_FIRST); // This game part is the protection screen */
/* Try to cheat here. You can jump anywhere but the VM crashes afterward. */
/* Starting somewhere is probably not enough, the variables and calls return are probably missing. */
/* vm_initForPart(&e->vm, GAME_PART2); Skip protection screen and go directly to intro */
/* vm_initForPart(&e->vm, GAME_PART3); CRASH */
/* vm_initForPart(&e->vm, GAME_PART4); Start directly in jail but then crash */
/* vm->initForPart(&e->vm, GAME_PART5); CRASH */
/* vm->initForPart(GAME_PART6); Start in the battlechar but CRASH afteward */
/* vm->initForPart(GAME_PART7); CRASH */
/* vm->initForPart(GAME_PART8); CRASH */
/* vm->initForPart(GAME_PART9); Green screen not doing anything */
}
void engine_finish(struct Engine* e) {
player_free(&e->player);
mixer_free(&e->mixer);
res_freeMemBlock(&e->res);
}
void engine_processInput(struct Engine* e) {
if (e->sys->input.load) {
engine_loadGameState(e, e->_stateSlot);
e->sys->input.load = false;
}
if (e->sys->input.save) {
engine_saveGameState(e, e->_stateSlot, "quicksave");
e->sys->input.save = false;
}
if (e->sys->input.fastMode) {
e->vm._fastMode = !&e->vm._fastMode;
e->sys->input.fastMode = false;
}
if (e->sys->input.stateSlot != 0) {
int8_t slot = e->_stateSlot + e->sys->input.stateSlot;
if (slot >= 0 && slot < MAX_SAVE_SLOTS) {
e->_stateSlot = slot;
debug(DBG_INFO, "Current game state slot is %d", e->_stateSlot);
}
e->sys->input.stateSlot = 0;
}
}
void engine_makeGameStateName(struct Engine* e, uint8_t slot, char *buf, int sz) {
(void) e;
rb->snprintf(buf, sz, "xworld_save.s%02d", slot);
}
void engine_saveGameState(struct Engine* e, uint8_t slot, const char *desc) {
char stateFile[20];
/* sizeof(char) is guaranteed to be 1 */
engine_makeGameStateName(e, slot, stateFile, sizeof(stateFile));
File f;
file_create(&f, false);
if (!file_open(&f, stateFile, e->_saveDir, "wb")) {
warning("Unable to save state file '%s'", stateFile);
} else {
/* header */
file_writeUint32BE(&f, SAVE_MAGIC);
file_writeUint16BE(&f, CUR_VER);
file_writeUint16BE(&f, 0);
char hdrdesc[32];
strncpy(hdrdesc, desc, sizeof(hdrdesc) - 1);
file_write(&f, hdrdesc, sizeof(hdrdesc));
/* contents */
struct Serializer s;
ser_create(&s, &f, SM_SAVE, e->res._memPtrStart, CUR_VER);
vm_saveOrLoad(&e->vm, &s);
res_saveOrLoad(&e->res, &s);
video_saveOrLoad(&e->video, &s);
player_saveOrLoad(&e->player, &s);
mixer_saveOrLoad(&e->mixer, &s);
if (file_ioErr(&f)) {
warning("I/O error when saving game state");
} else {
debug(DBG_INFO, "Saved state to slot %d", e->_stateSlot);
}
}
file_close(&f);
}
bool engine_loadGameState(struct Engine* e, uint8_t slot) {
char stateFile[20];
engine_makeGameStateName(e, slot, stateFile, 20);
File f;
file_create(&f, false);
if (!file_open(&f, stateFile, e->_saveDir, "rb")) {
debug(DBG_ENG, "Unable to open state file '%s'", stateFile);
goto fail;
} else {
uint32_t id = file_readUint32BE(&f);
if (id != SAVE_MAGIC) {
debug(DBG_ENG, "Bad savegame format");
goto fail;
} else {
/* mute */
player_stop(&e->player);
mixer_stopAll(&e->mixer);
/* header */
uint16_t ver = file_readUint16BE(&f);
file_readUint16BE(&f);
char hdrdesc[32];
file_read(&f, hdrdesc, sizeof(hdrdesc));
/* contents */
struct Serializer s;
ser_create(&s, &f, SM_LOAD, e->res._memPtrStart, ver);
vm_saveOrLoad(&e->vm, &s);
res_saveOrLoad(&e->res, &s);
video_saveOrLoad(&e->video, &s);
player_saveOrLoad(&e->player, &s);
mixer_saveOrLoad(&e->mixer, &s);
}
if (file_ioErr(&f)) {
debug(DBG_ENG, "I/O error when loading game state");
goto fail;
} else {
debug(DBG_INFO, "Loaded state from slot %d", e->_stateSlot);
}
}
file_close(&f);
return true;
fail:
file_close(&f);
return false;
}
void engine_deleteGameState(struct Engine* e, uint8_t slot) {
char stateFile[20];
engine_makeGameStateName(e, slot, stateFile, 20);
file_remove(stateFile, e->_saveDir);
}
const char* engine_getDataDir(struct Engine* e)
{
return e->_dataDir;
}