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>
This commit is contained in:
Franklin Wei 2014-10-13 21:00:47 -04:00 committed by Michael Giacomelli
parent b681e932a9
commit 33cb13dee5
41 changed files with 6964 additions and 0 deletions

View file

@ -134,4 +134,5 @@ wavrecord,apps
wavview,viewers
wormlet,games
xobox,games
xworld,games
zxbox,viewers

View file

@ -12,6 +12,12 @@ clock
/* For all targets with a bitmap display */
#ifdef HAVE_LCD_BITMAP
/* XWorld only supports color horizontal stride LCDs /for now/ ;) */
#if (defined(HAVE_LCD_COLOR) && \
(!defined(LCD_STRIDEFORMAT) || (LCD_STRIDEFORMAT != VERTICAL_STRIDE))
xworld
#endif
#if (CONFIG_KEYPAD != ONDIO_PAD) /* not enough buttons */ \
&& (CONFIG_KEYPAD != SANSA_M200_PAD) /* not enough buttons */ \
&& (CONFIG_KEYPAD != HM60X_PAD) /* not enough buttons */ \

View file

@ -0,0 +1,86 @@
This is the original readme from the "Fabother World" sources; the Rockbox port's
readme is in README.rockbox.
Franklin Wei
=================================================================================
This is "Fabother World": an Another World (Out Of This World in North America) interpreter codebase. This work is based on:
- Piotr Padkowski's newRaw interpreter which was based on
- Gregory Montoir's reverse engineering of
- Eric Chahi's assembly code.
I cleaned up a lot of the code, removing cryptic hexadecimal notation
with meaningful macros name. I also cleanup a lot of the code so it has a
C/C++ philosophy instead of an assembly structure.
I also created a Visual Studio 2010 project.
TODO:
Create a MacOS X project.
Add a different rendering path OpenGL support.
Fabien Sanglard
raw README
Release version: 0.1.1 (May 15 2004)
-------------------------------------------------------------------------------
About:
------
raw is a re-implementation of the engine used in the game Another World. This
game, released under the name Out Of This World in non-European countries, was
written by Eric Chahi at the beginning of the '90s. More information can be
found here : http://www.mobygames.com/game/sheet/p,2/gameId,564/.
Please be aware that, currently, this implementation may contains bugs and
non-implemented features that make it impossible to finish the game.
Supported Versions:
-------------------
Currently, only the english PC DOS version is supported ("Out of this World").
Compiling:
----------
Tweak the Makefile if needed and type make (only gcc3 has been tested so far).
The SDL and zlib libraries are required.
Running:
--------
You will need the original files, here is the required list :
BANK*
MEMLIST.BIN
To start the game, you can either :
- put the game's datafiles in the same directory as the executable
- use the --datapath command line option to specify the datafiles directory
Here are the various in game hotkeys :
Arrow Keys allow you to move Lester
Enter/Space allow you run/shoot with your gun
C allow to enter a code to jump at a specific level
P pause the game
Alt X exit the game
Ctrl S save game state
Ctrl L load game state
Ctrl + and - change game state slot
Ctrl F toggle fast mode
Alt Enter toggle windowed/fullscreen mode
Alt + and - change scaler factor
Credits:
--------
Eric Chahi, obviously, for making this great game.
Contact:
--------
Gregory Montoir, cyx@users.sourceforge.net

View file

@ -0,0 +1,4 @@
Changes:
Added 2x and 3x high quality scalers (ripped from Reminescence)

View file

@ -0,0 +1,51 @@
This is the Rockbox port of Fabien Sanglard's "Fabother World", an Another World
interpreter.
Porting process:
----------------
The original code abstracted most of the platform-specific tasks, such as file I/O,
sound, input, and video. However, the original code was in C++, so it was converted
to C class-by-class. The conversion was attempted to be as conservative as possible,
so little code was rewritten during the conversion process.
Notes:
------
- Optimization is badly needed.
- Vertical stride support is almost there.
- The game looks terrible in B+W/grayscale. This was the primary reason no attempt
was made to support these targets.
- The game does not run well on devices that have an LCD with a vertical stride.
- The M:Robe 500 is the only color device that meets this criterion, so it is
disabled by default.
- Sound doesn't sound 100% like the PC version. Perhaps the frequency reported to
the mixer is incorrect, or the buffer size is too big so that short sounds are
being missed.
To do (in no particular order):
-------------------------------
- Support vertical stride LCD's
- Support grayscale/monochrome LCD's
- Optimize
Credits:
--------
**************************************
**************************************
********** !!!ERIC CHAHI!!! **********
**************************************
**************************************
<the original author of Another World>
Gregory Montoir
Piotr Padkowski
Fabien Sanglard
Rockbox porters:
----------------
Franklin Wei
Benjamin Brown

View file

@ -0,0 +1,15 @@
bank.c
engine.c
file.c
intern.c
mixer.c
parts.c
resource.c
serializer.c
sfxplayer.c
sys.c
util.c
video.c
video_data.c
vm.c
xworld.c

View file

@ -0,0 +1,51 @@
/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2014 Franklin Wei, Benjamin Brown
*
* 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.
*
***************************************************************************/
#ifndef __SYS_H__
#define __SYS_H__
#include "rbendian.h"
#include "stdint.h"
#ifdef ROCKBOX_LITTLE_ENDIAN
#define SYS_LITTLE_ENDIAN
#else
#define SYS_BIG_ENDIAN
#endif
#if defined SYS_LITTLE_ENDIAN
#define READ_BE_UINT16(p) ((((const uint8_t*)p)[0] << 8) | ((const uint8_t*)p)[1])
#define READ_BE_UINT32(p) ((((const uint8_t*)p)[0] << 24) | (((const uint8_t*)p)[1] << 16) | (((const uint8_t*)p)[2] << 8) | ((const uint8_t*)p)[3])
#elif defined SYS_BIG_ENDIAN
#define READ_BE_UINT16(p) (*(const uint16_t*)p)
#define READ_BE_UINT32(p) (*(const uint32_t*)p)
#else
#error No endianness defined
#endif
#endif

153
apps/plugins/xworld/bank.c Normal file
View file

@ -0,0 +1,153 @@
/***************************************************************************
* __________ __ ___.
* 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 "bank.h"
#include "file.h"
#include "resource.h"
void bank_create(struct Bank* b, const char *dataDir)
{
b->_dataDir = dataDir;
}
bool bank_read(struct Bank* b, const struct MemEntry *me, uint8_t *buf) {
bool ret = false;
char bankName[10];
rb->snprintf(bankName, 10, "bank%02x", me->bankId);
File f;
file_create(&f, false);
if (!file_open(&f, bankName, b->_dataDir, "rb"))
error("bank_read() unable to open '%s' in dir '%s'", bankName, b->_dataDir);
file_seek(&f, me->bankOffset);
/* Depending if the resource is packed or not we */
/* can read directly or unpack it. */
if (me->packedSize == me->size) {
file_read(&f, buf, me->packedSize);
ret = true;
} else {
file_read(&f, buf, me->packedSize);
b->_startBuf = buf;
b->_iBuf = buf + me->packedSize - 4;
ret = bank_unpack(b);
}
file_close(&f);
return ret;
}
void bank_decUnk1(struct Bank* b, uint8_t numChunks, uint8_t addCount) {
uint16_t count = bank_getCode(b, numChunks) + addCount + 1;
debug(DBG_BANK, "bank_decUnk1(%d, %d) count=%d", numChunks, addCount, count);
b->_unpCtx.datasize -= count;
while (count--) {
assert(b->_oBuf >= b->_iBuf && b->_oBuf >= b->_startBuf);
*b->_oBuf = (uint8_t)bank_getCode(b, 8);
--b->_oBuf;
}
}
/*
Note from fab: This look like run-length encoding.
*/
void bank_decUnk2(struct Bank* b, uint8_t numChunks) {
uint16_t i = bank_getCode(b, numChunks);
uint16_t count = b->_unpCtx.size + 1;
debug(DBG_BANK, "bank_decUnk2(%d) i=%d count=%d", numChunks, i, count);
b->_unpCtx.datasize -= count;
while (count--) {
assert(b->_oBuf >= b->_iBuf && b->_oBuf >= b->_startBuf);
*b->_oBuf = *(b->_oBuf + i);
--b->_oBuf;
}
}
/*
Most resource in the banks are compacted.
*/
bool bank_unpack(struct Bank* b) {
b->_unpCtx.size = 0;
b->_unpCtx.datasize = READ_BE_UINT32(b->_iBuf);
b->_iBuf -= 4;
b->_oBuf = b->_startBuf + b->_unpCtx.datasize - 1;
b->_unpCtx.crc = READ_BE_UINT32(b->_iBuf);
b->_iBuf -= 4;
b->_unpCtx.chk = READ_BE_UINT32(b->_iBuf);
b->_iBuf -= 4;
b->_unpCtx.crc ^= b->_unpCtx.chk;
do {
if (!bank_nextChunk(b)) {
b->_unpCtx.size = 1;
if (!bank_nextChunk(b)) {
bank_decUnk1(b, 3, 0);
} else {
bank_decUnk2(b, 8);
}
} else {
uint16_t c = bank_getCode(b, 2);
if (c == 3) {
bank_decUnk1(b, 8, 8);
} else {
if (c < 2) {
b->_unpCtx.size = c + 2;
bank_decUnk2(b, c + 9);
} else {
b->_unpCtx.size = bank_getCode(b, 8);
bank_decUnk2(b, 12);
}
}
}
} while (b->_unpCtx.datasize > 0);
return (b->_unpCtx.crc == 0);
}
uint16_t bank_getCode(struct Bank* b, uint8_t numChunks) {
uint16_t c = 0;
while (numChunks--) {
c <<= 1;
if (bank_nextChunk(b)) {
c |= 1;
}
}
return c;
}
bool bank_nextChunk(struct Bank* b) {
bool CF = bank_rcr(b, false);
if (b->_unpCtx.chk == 0) {
assert(b->_iBuf >= b->_startBuf);
b->_unpCtx.chk = READ_BE_UINT32(b->_iBuf);
b->_iBuf -= 4;
b->_unpCtx.crc ^= b->_unpCtx.chk;
CF = bank_rcr(b, true);
}
return CF;
}
bool bank_rcr(struct Bank* b, bool CF) {
bool rCF = (b->_unpCtx.chk & 1);
b->_unpCtx.chk >>= 1;
if (CF) b->_unpCtx.chk |= 0x80000000;
return rCF;
}

View file

@ -0,0 +1,55 @@
/***************************************************************************
* __________ __ ___.
* 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.
*
***************************************************************************/
#ifndef __BANK_H__
#define __BANK_H__
#include "intern.h"
struct MemEntry;
struct UnpackContext {
uint16_t size;
uint32_t crc;
uint32_t chk;
int32_t datasize;
};
struct Bank
{
struct UnpackContext _unpCtx;
const char *_dataDir;
uint8_t *_iBuf, *_oBuf, *_startBuf;
};
/* needs allocated memory */
void bank_create(struct Bank*, const char *dataDir);
bool bank_read(struct Bank*, const struct MemEntry *me, uint8_t *buf);
void bank_decUnk1(struct Bank*, uint8_t numChunks, uint8_t addCount);
void bank_decUnk2(struct Bank*, uint8_t numChunks);
bool bank_unpack(struct Bank*);
uint16_t bank_getCode(struct Bank*, uint8_t numChunks);
bool bank_nextChunk(struct Bank*);
bool bank_rcr(struct Bank*, bool CF);
#endif

View file

@ -0,0 +1,397 @@
/***************************************************************************
* __________ __ ___.
* 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;
}

View file

@ -0,0 +1,70 @@
/***************************************************************************
* __________ __ ___.
* 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.
*
***************************************************************************/
#ifndef __ENGINE_H__
#define __ENGINE_H__
#include "intern.h"
#include "vm.h"
#include "mixer.h"
#include "sfxplayer.h"
#include "resource.h"
#include "video.h"
#include "sys.h"
#define STRING_TABLE_FILE "xworld.strings" /* this is relative to dataDir */
#define STRING_TABLE_VERSION 0x03
#define XWORLD_FONT_FILE "xworld.font" /* relative to dataDir */
#define XWORLD_FONT_VERSION 0x01
struct System;
#define MAX_SAVE_SLOTS 1
#define SAVE_MAGIC 0x42424657
struct Engine {
struct System *sys;
struct VirtualMachine vm;
struct Mixer mixer;
struct Resource res;
struct SfxPlayer player;
struct Video video;
const char *_dataDir, *_saveDir;
uint8_t _stateSlot;
};
void engine_create(struct Engine* e, struct System* stub, const char* dataDir, const char* saveDir);
void engine_run(struct Engine*);
void engine_init(struct Engine*);
void engine_finish(struct Engine*);
void engine_processInput(struct Engine*);
bool engine_loadFontFile(struct Engine*);
bool engine_loadStringTable(struct Engine*);
void engine_makeGameStateName(struct Engine*, uint8_t slot, char *buf, int sz);
void engine_saveGameState(struct Engine*, uint8_t slot, const char *desc);
bool engine_loadGameState(struct Engine*, uint8_t slot);
void engine_deleteGameState(struct Engine*, uint8_t slot);
const char* engine_getDataDir(struct Engine*);
#endif

172
apps/plugins/xworld/file.c Normal file
View file

@ -0,0 +1,172 @@
/***************************************************************************
* __________ __ ___.
* 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 "file.h"
void file_create(struct File* f, bool gzipped) {
f->gzipped = gzipped;
f->fd = -1;
f->ioErr = false;
}
bool file_open(struct File* f, const char *filename, const char *directory, const char *mode) {
char buf[512];
rb->snprintf(buf, 512, "%s/%s", directory, filename);
char *p = buf + rb->strlen(directory) + 1;
string_lower(p);
int flags = 0;
for(int i = 0; mode[i]; ++i)
{
switch(mode[i])
{
case 'w':
flags |= O_WRONLY | O_CREAT | O_TRUNC;
break;
case 'r':
flags |= O_RDONLY;
break;
default:
break;
}
}
f->fd = -1;
debug(DBG_FILE, "trying %s first", buf);
f->fd = rb->open(buf, flags, 0666);
if (f->fd < 0) { // let's try uppercase
string_upper(p);
debug(DBG_FILE, "now trying %s uppercase", buf);
f->fd = rb->open(buf, flags, 0666);
}
if(f->fd > 0)
return true;
else
return false;
}
void file_close(struct File* f) {
if(f->gzipped)
{
}
else
{
rb->close(f->fd);
}
}
bool file_ioErr(struct File* f) {
return f->ioErr;
}
void file_seek(struct File* f, int32_t off) {
if(f->gzipped)
{
}
else
{
rb->lseek(f->fd, off, SEEK_SET);
}
}
int file_read(struct File* f, void *ptr, uint32_t size) {
if(f->gzipped)
{
return -1;
}
else
{
unsigned int rc = rb->read(f->fd, ptr, size);
if(rc != size)
f->ioErr = true;
return rc;
}
}
uint8_t file_readByte(struct File* f) {
uint8_t b;
if(f->gzipped)
{
b = 0xff;
}
else
{
if(rb->read(f->fd, &b, 1) != 1)
{
f->ioErr = true;
debug(DBG_FILE, "file read failed");
}
}
return b;
}
uint16_t file_readUint16BE(struct File* f) {
uint8_t hi = file_readByte(f);
uint8_t lo = file_readByte(f);
return (hi << 8) | lo;
}
uint32_t file_readUint32BE(struct File* f) {
uint16_t hi = file_readUint16BE(f);
uint16_t lo = file_readUint16BE(f);
return (hi << 16) | lo;
}
int file_write(struct File* f, void *ptr, uint32_t size) {
if(f->gzipped)
{
return 0;
}
else
{
return rb->write(f->fd, ptr, size);
}
}
void file_writeByte(struct File* f, uint8_t b) {
file_write(f, &b, 1);
}
void file_writeUint16BE(struct File* f, uint16_t n) {
file_writeByte(f, n >> 8);
file_writeByte(f, n & 0xFF);
}
void file_writeUint32BE(struct File* f, uint32_t n) {
file_writeUint16BE(f, n >> 16);
file_writeUint16BE(f, n & 0xFFFF);
}
void file_remove(const char* filename, const char* directory)
{
char buf[512];
rb->snprintf(buf, 512, "%s/%s", directory, filename);
char *p = buf + rb->strlen(directory) + 1;
string_lower(p);
if(rb->file_exists(buf))
{
rb->remove(buf);
}
else
{
string_upper(p);
rb->remove(buf);
}
}

View file

@ -0,0 +1,51 @@
/***************************************************************************
* __________ __ ___.
* 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.
*
***************************************************************************/
#ifndef __FILE_H__
#define __FILE_H__
#include "intern.h"
typedef struct File {
int fd;
bool gzipped;
bool ioErr;
} File;
void file_create(struct File*, bool gzipped);
bool file_open(struct File*, const char *filename, const char *directory, const char *mode);
void file_close(struct File*);
bool file_ioErr(struct File*);
void file_seek(struct File*, int32_t off);
int file_read(struct File*, void *ptr, uint32_t size);
uint8_t file_readByte(struct File*);
uint16_t file_readUint16BE(struct File*);
uint32_t file_readUint32BE(struct File*);
int file_write(struct File*, void *ptr, uint32_t size);
void file_writeByte(struct File*, uint8_t b);
void file_writeUint16BE(struct File*, uint16_t n);
void file_writeUint32BE(struct File*, uint32_t n);
void file_remove(const char* filename, const char* directory);
#endif

View file

@ -0,0 +1,34 @@
/***************************************************************************
* __________ __ ___.
* 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 "intern.h"
#include "awendian.h"
uint8_t ICODE_ATTR scriptPtr_fetchByte(struct Ptr* p) {
return *p->pc++;
}
uint16_t ICODE_ATTR scriptPtr_fetchWord(struct Ptr* p) {
uint16_t i = READ_BE_UINT16(p->pc);
p->pc += 2;
return i;
}

View file

@ -0,0 +1,44 @@
/***************************************************************************
* __________ __ ___.
* 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.
*
***************************************************************************/
#ifndef __INTERN_H__
#define __INTERN_H__
#include "plugin.h"
#include "string.h"
#include "awendian.h"
#include "util.h"
#define assert(c) (c?(void)0:error("Assertion failed line %d, file %s", __LINE__, __FILE__))
struct Ptr {
uint8_t* pc;
};
uint8_t scriptPtr_fetchByte(struct Ptr* p) ICODE_ATTR;
uint16_t scriptPtr_fetchWord(struct Ptr* p) ICODE_ATTR;
struct Point {
int16_t x, y;
};
#endif

View file

@ -0,0 +1,183 @@
/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2014 Franklin Wei, Benjamin Brown
*
* 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.
*
***************************************************************************/
#ifndef _XWORLD_KEYMAPS_H
#define _XWORLD_KEYMAPS_H
#endif
#if (CONFIG_KEYPAD == PHILIPS_HDD1630_PAD) || \
(CONFIG_KEYPAD == PHILIPS_HDD6330_PAD) || \
(CONFIG_KEYPAD == PHILIPS_SA9200_PAD) || \
(CONFIG_KEYPAD == CREATIVE_ZENXFI2_PAD) || \
(CONFIG_KEYPAD == CREATIVE_ZENXFI3_PAD) || \
(CONFIG_KEYPAD == SANSA_CONNECT_PAD) || \
(CONFIG_KEYPAD == SANSA_C200_PAD) || \
(CONFIG_KEYPAD == SANSA_CLIP_PAD) || \
(CONFIG_KEYPAD == SANSA_E200_PAD) || \
(CONFIG_KEYPAD == SANSA_FUZE_PAD) || \
(CONFIG_KEYPAD == SANSA_FUZEPLUS_PAD) || \
(CONFIG_KEYPAD == GIGABEAT_PAD) || \
(CONFIG_KEYPAD == GIGABEAT_S_PAD) || \
(CONFIG_KEYPAD == SAMSUNG_YH920_PAD) || \
(CONFIG_KEYPAD == SAMSUNG_YH820_PAD) || \
(CONFIG_KEYPAD == IAUDIO_X5M5_PAD) || \
(CONFIG_KEYPAD == CREATIVE_ZEN_PAD) || \
(CONFIG_KEYPAD == SONY_NWZ_PAD) || \
(CONFIG_KEYPAD == CREATIVEZVM_PAD) || \
(CONFIG_KEYPAD == SAMSUNG_YPR0_PAD) || \
(CONFIG_KEYPAD == IRIVER_H300_PAD) || \
(CONFIG_KEYPAD == HM801_PAD)
#define BTN_UP BUTTON_UP
#define BTN_DOWN BUTTON_DOWN
#define BTN_LEFT BUTTON_LEFT
#define BTN_RIGHT BUTTON_RIGHT
#if (CONFIG_KEYPAD == SANSA_FUZEPLUS_PAD)
#define BTN_UP_LEFT BUTTON_BACK
#define BTN_UP_RIGHT BUTTON_PLAYPAUSE
#define BTN_DOWN_LEFT BUTTON_BOTTOMLEFT
#define BTN_DOWN_RIGHT BUTTON_BOTTOMRIGHT
#endif
#if (CONFIG_KEYPAD == PHILIPS_HDD1630_PAD) || \
(CONFIG_KEYPAD == PHILIPS_HDD6330_PAD) || \
(CONFIG_KEYPAD == PHILIPS_SA9200_PAD) || \
(CONFIG_KEYPAD == CREATIVE_ZENXFI2_PAD) || \
(CONFIG_KEYPAD == CREATIVE_ZENXFI3_PAD) || \
(CONFIG_KEYPAD == SANSA_CONNECT_PAD) || \
(CONFIG_KEYPAD == SANSA_C200_PAD) || \
(CONFIG_KEYPAD == SANSA_FUZEPLUS_PAD) || \
(CONFIG_KEYPAD == DX50_PAD) || \
(CONFIG_KEYPAD == ONDAVX747_PAD)
#define BTN_FIRE BUTTON_VOL_UP
#define BTN_PAUSE BUTTON_VOL_DOWN
#elif (CONFIG_KEYPAD == SANSA_FUZE_PAD)
#define BTN_FIRE BUTTON_HOME
#define BTN_PAUSE BUTTON_SELECT
#elif (CONFIG_KEYPAD == SAMSUNG_YH920_PAD)
#define BTN_FIRE BUTTON_FFWD
#define BTN_PAUSE BUTTON_REW
#elif (CONFIG_KEYPAD == SANSA_E200_PAD)
#define BTN_FIRE BUTTON_REC
#define BTN_PAUSE BUTTON_POWER
#elif (CONFIG_KEYPAD == SANSA_CLIP_PAD)
#define BTN_FIRE BUTTON_SELECT
#define BTN_PAUSE BUTTON_POWER
#elif (CONFIG_KEYPAD == CREATIVE_ZEN_PAD)
#define BTN_FIRE BUTTON_SELECT
#define BTN_PAUSE BUTTON_BACK
#elif (CONFIG_KEYPAD == CREATIVEZVM_PAD)
#define BTN_FIRE BUTTON_PLAY
#define BTN_PAUSE BUTTON_MENU
#elif (CONFIG_KEYPAD == SAMSUNG_YPR0_PAD)
#define BTN_FIRE BUTTON_USER
#define BTN_PAUSE BUTTON_MENU
#elif (CONFIG_KEYPAD == SONY_NWZ_PAD)
#define BTN_FIRE BUTTON_PLAY
#define BTN_PAUSE BUTTON_BACK
#elif (CONFIG_KEYPAD == IRIVER_H300_PAD)
#define BTN_FIRE BUTTON_REC
#define BTN_PAUSE BUTTON_MODE
#elif (CONFIG_KEYPAD == HM801_PAD)
#define BTN_FIRE BUTTON_PREV
#define BTN_PAUSE BUTTON_NEXT
#elif (CONFIG_KEYPAD == SAMSUNG_YH820_PAD) || \
(CONFIG_KEYPAD == IAUDIO_X5M5_PAD)
#define BTN_FIRE BUTTON_REC
#define BTN_PAUSE BUTTON_PLAY
#elif (CONFIG_KEYPAD == GIGABEAT_PAD) || \
(CONFIG_KEYPAD == GIGABEAT_S_PAD)
#define BTN_FIRE BUTTON_VOL_UP
#define BTN_PAUSE BUTTON_MENU
#endif
#elif (CONFIG_KEYPAD == PBELL_VIBE500_PAD)
#define BTN_UP BUTTON_OK
#define BTN_DOWN BUTTON_CANCEL
#define BTN_LEFT BUTTON_MENU
#define BTN_RIGHT BUTTON_PLAY
#define BTN_FIRE BUTTON_POWER
#define BTN_PAUSE BUTTON_REC
#elif (CONFIG_KEYPAD == IRIVER_H10_PAD)
#define BTN_UP BUTTON_SCROLL_UP
#define BTN_DOWN BUTTON_SCROLL_DOWN
#define BTN_LEFT BUTTON_LEFT
#define BTN_RIGHT BUTTON_RIGHT
#define BTN_FIRE BUTTON_REW
#define BTN_PAUSE BUTTON_PLAY
#elif (CONFIG_KEYPAD == MROBE500_PAD)
#define BTN_FIRE BUTTON_POWER
#elif (CONFIG_KEYPAD == MROBE_REMOTE)
#define BTN_UP BUTTON_RC_PLAY
#define BTN_DOWN BUTTON_RC_DOWN
#define BTN_LEFT BUTTON_RC_REW
#define BTN_RIGHT BUTTON_RC_FF
#elif (CONFIG_KEYPAD == IPOD_4G_PAD) || \
(CONFIG_KEYPAD == IPOD_3G_PAD) || \
(CONFIG_KEYPAD == IPOD_1G2G_PAD)
#define BTN_UP BUTTON_MENU
#define BTN_DOWN BUTTON_PLAY
#define BTN_LEFT BUTTON_LEFT
#define BTN_RIGHT BUTTON_RIGHT
#define BTN_FIRE BUTTON_SELECT
#define BTN_PAUSE (BUTTON_MENU | BUTTON_SELECT)
#elif (CONFIG_KEYPAD == ONDAVX777_PAD)
#define BTN_FIRE BUTTON_POWER
#elif (CONFIG_KEYPAD == DX50_PAD)
#define BTN_FIRE BUTTON_PLUS
#define BTN_PAUSE BUTTON_MENU
#else
#error Unsupported keypad
#endif
#ifdef HAVE_TOUCHSCREEN
#define BTN_UP BUTTON_TOPMIDDLE
#define BTN_DOWN BUTTON_BOTTOMMIDDLE
#define BTN_LEFT BUTTON_LEFT
#define BTN_RIGHT BUTTON_RIGHT
#if (CONFIG_KEYPAD == MROBE500_PAD)
#define BTN_PAUSE BUTTON_BOTTOMLEFT
#elif (CONFIG_KEYPAD != COWON_D2_PAD) || (CONFIG_KEYPAD != DX50_PAD)
#define BTN_FIRE BUTTON_BOTTOMLEFT
#define BTN_PAUSE BUTTON_TOPLEFT
#endif
#endif

199
apps/plugins/xworld/mixer.c Normal file
View file

@ -0,0 +1,199 @@
/***************************************************************************
* __________ __ ___.
* 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 "mixer.h"
#include "serializer.h"
#include "sys.h"
static int8_t ICODE_ATTR addclamp(int a, int b) {
int add = a + b;
if (add < -128) {
add = -128;
}
else if (add > 127) {
add = 127;
}
return (int8_t)add;
}
void mixer_create(struct Mixer* mx, struct System *stub)
{
mx->sys = stub;
}
static void mixer_mixCallback(void *param, uint8_t *buf, int len);
void mixer_init(struct Mixer* mx) {
rb->memset(mx->_channels, 0, sizeof(mx->_channels));
if(!mx->sys)
{
error("in mixer sys is NULL");
}
mx->_mutex = sys_createMutex(mx->sys);
sys_startAudio(mx->sys, mixer_mixCallback, mx);
}
void mixer_free(struct Mixer* mx) {
mixer_stopAll(mx);
sys_stopAudio(mx->sys);
sys_destroyMutex(mx->sys, mx->_mutex);
}
void mixer_playChannel(struct Mixer* mx, uint8_t channel, const struct MixerChunk *mc, uint16_t freq, uint8_t volume) {
debug(DBG_SND, "mixer_playChannel(%d, %d, %d)", channel, freq, volume);
assert(channel < AUDIO_NUM_CHANNELS);
/* FW: the mutex code was converted 1:1 from C++ to C, leading to the ugly calls */
/* to constructors/destructors as seen here */
struct MutexStack_t ms;
MutexStack(&ms, mx->sys, mx->_mutex);
struct MixerChannel *ch = &mx->_channels[channel];
ch->active = true;
ch->volume = volume;
ch->chunk = *mc;
ch->chunkPos = 0;
ch->chunkInc = (freq << 8) / sys_getOutputSampleRate(mx->sys);
MutexStack_destroy(&ms);
}
void mixer_stopChannel(struct Mixer* mx, uint8_t channel) {
debug(DBG_SND, "mixer_stopChannel(%d)", channel);
assert(channel < AUDIO_NUM_CHANNELS);
struct MutexStack_t ms;
MutexStack(&ms, mx->sys, mx->_mutex);
mx->_channels[channel].active = false;
MutexStack_destroy(&ms);
}
void mixer_setChannelVolume(struct Mixer* mx, uint8_t channel, uint8_t volume) {
debug(DBG_SND, "mixer_setChannelVolume(%d, %d)", channel, volume);
assert(channel < AUDIO_NUM_CHANNELS);
struct MutexStack_t ms;
MutexStack(&ms, mx->sys, mx->_mutex);
mx->_channels[channel].volume = volume;
MutexStack_destroy(&ms);
}
void mixer_stopAll(struct Mixer* mx) {
debug(DBG_SND, "mixer_stopAll()");
struct MutexStack_t ms;
MutexStack(&ms, mx->sys, mx->_mutex);
for (uint8_t i = 0; i < AUDIO_NUM_CHANNELS; ++i) {
mx->_channels[i].active = false;
}
MutexStack_destroy(&ms);
}
/* Mx is SDL callback. Called in order to populate the buf with len bytes. */
/* The mixer iterates through all active channels and combine all sounds. */
/* Since there is no way to know when SDL will ask for a buffer fill, we need */
/* to synchronize with a mutex so the channels remain stable during the execution */
/* of this method. */
static void ICODE_ATTR mixer_mix(struct Mixer* mx, int8_t *buf, int len) {
int8_t *pBuf;
struct MutexStack_t ms;
MutexStack(&ms, mx->sys, mx->_mutex);
/* Clear the buffer since nothing guarantees we are receiving clean memory. */
rb->memset(buf, 0, len);
for (uint8_t i = 0; i < AUDIO_NUM_CHANNELS; ++i) {
struct MixerChannel *ch = &mx->_channels[i];
if (!ch->active)
continue;
pBuf = buf;
for (int j = 0; j < len; ++j, ++pBuf) {
uint16_t p1, p2;
uint16_t ilc = (ch->chunkPos & 0xFF);
p1 = ch->chunkPos >> 8;
ch->chunkPos += ch->chunkInc;
if (ch->chunk.loopLen != 0) {
if (p1 == ch->chunk.loopPos + ch->chunk.loopLen - 1) {
debug(DBG_SND, "Looping sample on channel %d", i);
ch->chunkPos = p2 = ch->chunk.loopPos;
} else {
p2 = p1 + 1;
}
} else {
if (p1 == ch->chunk.len - 1) {
debug(DBG_SND, "Stopping sample on channel %d", i);
ch->active = false;
break;
} else {
p2 = p1 + 1;
}
}
/* interpolate */
int8_t b1 = *(int8_t *)(ch->chunk.data + p1);
int8_t b2 = *(int8_t *)(ch->chunk.data + p2);
int8_t b = (int8_t)((b1 * (0xFF - ilc) + b2 * ilc) >> 8);
/* set volume and clamp */
*pBuf = addclamp(*pBuf, (int)b * ch->volume / 0x40); /* 0x40=64 */
}
}
MutexStack_destroy(&ms);
}
static void ICODE_ATTR mixer_mixCallback(void *param, uint8_t *buf, int len) {
debug(DBG_SND, "mixer_mixCallback");
mixer_mix((struct Mixer*)param, (int8_t *)buf, len);
}
void mixer_saveOrLoad(struct Mixer* mx, struct Serializer *ser) {
sys_lockMutex(mx->sys, mx->_mutex);
for (int i = 0; i < AUDIO_NUM_CHANNELS; ++i) {
struct MixerChannel *ch = &mx->_channels[i];
struct Entry entries[] = {
SE_INT(&ch->active, SES_BOOL, VER(2)),
SE_INT(&ch->volume, SES_INT8, VER(2)),
SE_INT(&ch->chunkPos, SES_INT32, VER(2)),
SE_INT(&ch->chunkInc, SES_INT32, VER(2)),
SE_PTR(&ch->chunk.data, VER(2)),
SE_INT(&ch->chunk.len, SES_INT16, VER(2)),
SE_INT(&ch->chunk.loopPos, SES_INT16, VER(2)),
SE_INT(&ch->chunk.loopLen, SES_INT16, VER(2)),
SE_END()
};
ser_saveOrLoadEntries(ser, entries);
}
sys_unlockMutex(mx->sys, mx->_mutex);
};

View file

@ -0,0 +1,69 @@
/***************************************************************************
* __________ __ ___.
* 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.
*
***************************************************************************/
#ifndef __MIXER_H__
#define __MIXER_H__
#include "intern.h"
struct MixerChunk {
const uint8_t *data;
uint16_t len;
uint16_t loopPos;
uint16_t loopLen;
};
struct MixerChannel {
uint8_t active;
uint8_t volume;
struct MixerChunk chunk;
uint32_t chunkPos;
uint32_t chunkInc;
};
struct Serializer;
struct System;
#define AUDIO_NUM_CHANNELS 4
struct Mixer {
void *_mutex;
struct System *sys;
/* Since the virtual machine and SDL are running simultaneously in two different threads */
/* any read or write to an elements of the sound channels MUST be synchronized with a */
/* mutex. */
struct MixerChannel _channels[AUDIO_NUM_CHANNELS];
};
void mixer_create(struct Mixer*, struct System *stub);
void mixer_init(struct Mixer*);
void mixer_free(struct Mixer*);
void mixer_playChannel(struct Mixer*, uint8_t channel, const struct MixerChunk *mc, uint16_t freq, uint8_t volume);
void mixer_stopChannel(struct Mixer*, uint8_t channel);
void mixer_setChannelVolume(struct Mixer*, uint8_t channel, uint8_t volume);
void mixer_stopAll(struct Mixer*);
void mixer_saveOrLoad(struct Mixer*, struct Serializer *ser);
#endif

View file

@ -0,0 +1,56 @@
/***************************************************************************
* __________ __ ___.
* 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 "parts.h"
/*
#define MEMLIST_PART_PALETTE 0
#define MEMLIST_PART_CODE 1
#define MEMLIST_PART_VIDEO1 2
#define MEMLIST_PART_VIDEO2 3
*/
/*
MEMLIST_PART_VIDEO1 and MEMLIST_PART_VIDEO2 are used to store polygons.
It seems that:
- MEMLIST_PART_VIDEO1 contains the cinematic polygons.
- MEMLIST_PART_VIDEO2 contains the polygons for player and enemies animations.
That would make sense since protection screen and cinematic game parts do not load MEMLIST_PART_VIDEO2.
*/
const uint16_t memListParts[GAME_NUM_PARTS][4] = {
/* MEMLIST_PART_PALETTE MEMLIST_PART_CODE MEMLIST_PART_VIDEO1 MEMLIST_PART_VIDEO2 */
{ 0x14, 0x15, 0x16, 0x00 }, /* protection screens */
{ 0x17, 0x18, 0x19, 0x00 }, /* introduction cinematic */
{ 0x1A, 0x1B, 0x1C, 0x11 },
{ 0x1D, 0x1E, 0x1F, 0x11 },
{ 0x20, 0x21, 0x22, 0x11 },
{ 0x23, 0x24, 0x25, 0x00 }, /* battlechar cinematic */
{ 0x26, 0x27, 0x28, 0x11 },
{ 0x29, 0x2A, 0x2B, 0x11 },
{ 0x7D, 0x7E, 0x7F, 0x00 },
{ 0x7D, 0x7E, 0x7F, 0x00 } /* password screen */
};

View file

@ -0,0 +1,57 @@
/***************************************************************************
* __________ __ ___.
* 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.
*
***************************************************************************/
#ifndef __AW_PARTS_
#define __AW_PARTS_
#include "intern.h"
#include "awendian.h"
/* The game is divided in 10 parts. */
#define GAME_NUM_PARTS 10
#define GAME_PART_FIRST 0x3E80
#define GAME_PART1 0x3E80
#define GAME_PART2 0x3E81 /* Introduction */
#define GAME_PART3 0x3E82
#define GAME_PART4 0x3E83 /* Wake up in the suspended jail */
#define GAME_PART5 0x3E84
#define GAME_PART6 0x3E85 /* BattleChar sequence */
#define GAME_PART7 0x3E86
#define GAME_PART8 0x3E87
#define GAME_PART9 0x3E88
#define GAME_PART10 0x3E89
#define GAME_PART_LAST 0x3E89
extern const uint16_t memListParts[GAME_NUM_PARTS][4];
/* For each part of the game, four resources are referenced. */
#define MEMLIST_PART_PALETTE 0
#define MEMLIST_PART_CODE 1
#define MEMLIST_PART_POLY_CINEMATIC 2
#define MEMLIST_PART_VIDEO2 3
#define MEMLIST_PART_NONE 0x00
#endif

View file

@ -0,0 +1,443 @@
/***************************************************************************
* __________ __ ___.
* 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 "resource.h"
#include "bank.h"
#include "file.h"
#include "serializer.h"
#include "video.h"
#include "util.h"
#include "parts.h"
#include "vm.h"
#include "sys.h"
void res_create(struct Resource* res, struct Video* vid, struct System* sys, const char* dataDir)
{
res->video = vid;
res->sys = sys;
res->_dataDir = dataDir;
res->currentPartId = 0;
res->requestedNextPart = 0;
}
void res_readBank(struct Resource* res, const MemEntry *me, uint8_t *dstBuf) {
uint16_t n = me - res->_memList;
debug(DBG_BANK, "res_readBank(%d)", n);
struct Bank bk;
bank_create(&bk, res->_dataDir);
if (!bank_read(&bk, me, dstBuf)) {
error("res_readBank() unable to unpack entry %d\n", n);
}
}
#ifdef XWORLD_DEBUG
static const char *resTypeToString(struct Resource* res, unsigned int type)
{
(void) res;
static const char* resTypes[] =
{
"RT_SOUND",
"RT_MUSIC",
"RT_POLY_ANIM",
"RT_PALETTE",
"RT_BYTECODE",
"RT_POLY_CINEMATIC"
};
if (type >= (sizeof(resTypes) / sizeof(const char *)))
return "RT_UNKNOWN";
return resTypes[type];
}
#endif
#define RES_SIZE 0
#define RES_COMPRESSED 1
int resourceSizeStats[7][2];
#define STATS_TOTAL_SIZE 6
int resourceUnitStats[7][2];
/*
Read all entries from memlist.bin. Do not load anything in memory,
this is just a fast way to access the data later based on their id.
*/
void res_readEntries(struct Resource* res) {
File f;
file_create(&f, false);
int resourceCounter = 0;
if (!file_open(&f, "memlist.bin", res->_dataDir, "rb")) {
error("Could not open 'MEMLIST.BIN', data files missing");
/* error() will exit() no need to return or do anything else. */
}
/* Prepare stats array */
rb->memset(resourceSizeStats, 0, sizeof(resourceSizeStats));
rb->memset(resourceUnitStats, 0, sizeof(resourceUnitStats));
res->_numMemList = 0;
struct MemEntry *memEntry = res->_memList;
while (1) {
assert(res->_numMemList < ARRAYLEN(res->_memList));
memEntry->state = file_readByte(&f);
memEntry->type = file_readByte(&f);
memEntry->bufPtr = 0;
file_readUint16BE(&f);
memEntry->unk4 = file_readUint16BE(&f);
memEntry->rankNum = file_readByte(&f);
memEntry->bankId = file_readByte(&f);
memEntry->bankOffset = file_readUint32BE(&f);
memEntry->unkC = file_readUint16BE(&f);
memEntry->packedSize = file_readUint16BE(&f);
memEntry->unk10 = file_readUint16BE(&f);
memEntry->size = file_readUint16BE(&f);
debug(DBG_RES, "mementry state is %d", memEntry->state);
if (memEntry->state == MEMENTRY_STATE_END_OF_MEMLIST) {
break;
}
/* Memory tracking */
if (memEntry->packedSize == memEntry->size)
{
resourceUnitStats[memEntry->type][RES_SIZE] ++;
resourceUnitStats[STATS_TOTAL_SIZE][RES_SIZE] ++;
}
else
{
resourceUnitStats[memEntry->type][RES_COMPRESSED] ++;
resourceUnitStats[STATS_TOTAL_SIZE][RES_COMPRESSED] ++;
}
resourceSizeStats[memEntry->type][RES_SIZE] += memEntry->size;
resourceSizeStats[STATS_TOTAL_SIZE][RES_SIZE] += memEntry->size;
resourceSizeStats[memEntry->type][RES_COMPRESSED] += memEntry->packedSize;
resourceSizeStats[STATS_TOTAL_SIZE][RES_COMPRESSED] += memEntry->packedSize;
debug(DBG_RES, "R:0x%02X, %-17s size=%5d (compacted gain=%2.0f%%)",
resourceCounter,
resTypeToString(res, memEntry->type),
memEntry->size,
memEntry->size ? (memEntry->size - memEntry->packedSize) / (float)memEntry->size * 100.0f : 0.0f);
resourceCounter++;
res->_numMemList++;
memEntry++;
}
debug(DBG_RES, "\n");
debug(DBG_RES, "Total # resources: %d", resourceCounter);
debug(DBG_RES, "Compressed : %d", resourceUnitStats[STATS_TOTAL_SIZE][RES_COMPRESSED]);
debug(DBG_RES, "Uncompressed : %d", resourceUnitStats[STATS_TOTAL_SIZE][RES_SIZE]);
debug(DBG_RES, "Note: %2.0f%% of resources are compressed.", 100 * resourceUnitStats[STATS_TOTAL_SIZE][RES_COMPRESSED] / (float)resourceCounter);
debug(DBG_RES, "\n");
debug(DBG_RES, "Total size (uncompressed) : %7d bytes.", resourceSizeStats[STATS_TOTAL_SIZE][RES_SIZE]);
debug(DBG_RES, "Total size (compressed) : %7d bytes.", resourceSizeStats[STATS_TOTAL_SIZE][RES_COMPRESSED]);
debug(DBG_RES, "Note: Overall compression gain is : %2.0f%%.",
(resourceSizeStats[STATS_TOTAL_SIZE][RES_SIZE] - resourceSizeStats[STATS_TOTAL_SIZE][RES_COMPRESSED]) / (float)resourceSizeStats[STATS_TOTAL_SIZE][RES_SIZE] * 100);
debug(DBG_RES, "\n");
for(int i = 0 ; i < 6 ; i++)
debug(DBG_RES, "Total %-17s unpacked size: %7d (%2.0f%% of total unpacked size) packedSize %7d (%2.0f%% of floppy space) gain:(%2.0f%%)",
resTypeToString(res, i),
resourceSizeStats[i][RES_SIZE],
resourceSizeStats[i][RES_SIZE] / (float)resourceSizeStats[STATS_TOTAL_SIZE][RES_SIZE] * 100.0f,
resourceSizeStats[i][RES_COMPRESSED],
resourceSizeStats[i][RES_COMPRESSED] / (float)resourceSizeStats[STATS_TOTAL_SIZE][RES_COMPRESSED] * 100.0f,
(resourceSizeStats[i][RES_SIZE] - resourceSizeStats[i][RES_COMPRESSED]) / (float)resourceSizeStats[i][RES_SIZE] * 100.0f);
debug(DBG_RES, "Note: Damn you sound compression rate!");
debug(DBG_RES, "\nTotal bank files: %d", resourceUnitStats[STATS_TOTAL_SIZE][RES_SIZE] + resourceUnitStats[STATS_TOTAL_SIZE][RES_COMPRESSED]);
for(int i = 0 ; i < 6 ; i++)
debug(DBG_RES, "Total %-17s files: %3d", resTypeToString(res, i), resourceUnitStats[i][RES_SIZE] + resourceUnitStats[i][RES_COMPRESSED]);
file_close(&f);
}
/*
Go over every resource and check if they are marked at "MEMENTRY_STATE_LOAD_ME".
Load them in memory and mark them are MEMENTRY_STATE_LOADED
*/
void res_loadMarkedAsNeeded(struct Resource* res) {
while (1) {
struct MemEntry *me = NULL;
/* get resource with max rankNum */
uint8_t maxNum = 0;
uint16_t i = res->_numMemList;
struct MemEntry *it = res->_memList;
while (i--) {
if (it->state == MEMENTRY_STATE_LOAD_ME && maxNum <= it->rankNum) {
maxNum = it->rankNum;
me = it;
}
it++;
}
if (me == NULL) {
break; // no entry found
}
/* At this point the resource descriptor should be pointed to "me" */
uint8_t *loadDestination = NULL;
if (me->type == RT_POLY_ANIM) {
loadDestination = res->_vidCurPtr;
} else {
loadDestination = res->_scriptCurPtr;
if (me->size > res->_vidBakPtr - res->_scriptCurPtr) {
warning("res_load() not enough memory");
me->state = MEMENTRY_STATE_NOT_NEEDED;
continue;
}
}
if (me->bankId == 0) {
warning("res_load() ec=0x%X (me->bankId == 0)", 0xF00);
me->state = MEMENTRY_STATE_NOT_NEEDED;
} else {
debug(DBG_BANK, "res_load() bufPos=%X size=%X type=%X pos=%X bankId=%X", loadDestination - res->_memPtrStart, me->packedSize, me->type, me->bankOffset, me->bankId);
res_readBank(res, me, loadDestination);
if(me->type == RT_POLY_ANIM) {
video_copyPagePtr(res->video, res->_vidCurPtr);
me->state = MEMENTRY_STATE_NOT_NEEDED;
} else {
me->bufPtr = loadDestination;
me->state = MEMENTRY_STATE_LOADED;
res->_scriptCurPtr += me->size;
}
}
}
}
void res_invalidateRes(struct Resource* res) {
struct MemEntry *me = res->_memList;
uint16_t i = res->_numMemList;
while (i--) {
if (me->type <= RT_POLY_ANIM || me->type > 6) { /* 6 WTF ?!?! ResType goes up to 5 !! */
me->state = MEMENTRY_STATE_NOT_NEEDED;
}
++me;
}
res->_scriptCurPtr = res->_scriptBakPtr;
}
void res_invalidateAll(struct Resource* res) {
struct MemEntry *me = res->_memList;
uint16_t i = res->_numMemList;
while (i--) {
me->state = MEMENTRY_STATE_NOT_NEEDED;
++me;
}
res->_scriptCurPtr = res->_memPtrStart;
}
/* This method serves two purpose:
- Load parts in memory segments (palette,code,video1,video2)
or
- Load a resource in memory
This is decided based on the resourceId. If it does not match a mementry id it is supposed to
be a part id. */
void res_loadPartsOrMemoryEntry(struct Resource* res, uint16_t resourceId) {
if (resourceId > res->_numMemList) {
res->requestedNextPart = resourceId;
} else {
struct MemEntry *me = &res->_memList[resourceId];
if (me->state == MEMENTRY_STATE_NOT_NEEDED) {
me->state = MEMENTRY_STATE_LOAD_ME;
res_loadMarkedAsNeeded(res);
}
}
}
/* Protection screen and cinematic don't need the player and enemies polygon data
so _memList[video2Index] is never loaded for those parts of the game. When
needed (for action phrases) _memList[video2Index] is always loaded with 0x11
(as seen in memListParts). */
void res_setupPart(struct Resource* res, uint16_t partId) {
if (partId == res->currentPartId)
return;
if (partId < GAME_PART_FIRST || partId > GAME_PART_LAST)
error("res_setupPart() ec=0x%X invalid partId", partId);
uint16_t memListPartIndex = partId - GAME_PART_FIRST;
uint8_t paletteIndex = memListParts[memListPartIndex][MEMLIST_PART_PALETTE];
uint8_t codeIndex = memListParts[memListPartIndex][MEMLIST_PART_CODE];
uint8_t videoCinematicIndex = memListParts[memListPartIndex][MEMLIST_PART_POLY_CINEMATIC];
uint8_t video2Index = memListParts[memListPartIndex][MEMLIST_PART_VIDEO2];
/* Mark all resources as located on harddrive. */
res_invalidateAll(res);
res->_memList[paletteIndex].state = MEMENTRY_STATE_LOAD_ME;
res->_memList[codeIndex].state = MEMENTRY_STATE_LOAD_ME;
res->_memList[videoCinematicIndex].state = MEMENTRY_STATE_LOAD_ME;
/* This is probably a cinematic or a non interactive part of the game. */
/* Player and enemy polygons are not needed. */
if (video2Index != MEMLIST_PART_NONE)
res->_memList[video2Index].state = MEMENTRY_STATE_LOAD_ME;
res_loadMarkedAsNeeded(res);
res->segPalettes = res->_memList[paletteIndex].bufPtr;
debug(DBG_RES, "paletteIndex is 0x%08x", res->segPalettes);
res->segBytecode = res->_memList[codeIndex].bufPtr;
res->segCinematic = res->_memList[videoCinematicIndex].bufPtr;
/* This is probably a cinematic or a non interactive part of the game. */
/* Player and enemy polygons are not needed. */
if (video2Index != MEMLIST_PART_NONE)
res->_segVideo2 = res->_memList[video2Index].bufPtr;
debug(DBG_RES, "");
debug(DBG_RES, "setupPart(%d)", partId - GAME_PART_FIRST);
debug(DBG_RES, "Loaded resource %d (%s) in segPalettes.", paletteIndex, resTypeToString(res, res->_memList[paletteIndex].type));
debug(DBG_RES, "Loaded resource %d (%s) in segBytecode.", codeIndex, resTypeToString(res, res->_memList[codeIndex].type));
debug(DBG_RES, "Loaded resource %d (%s) in segCinematic.", videoCinematicIndex, resTypeToString(res, res->_memList[videoCinematicIndex].type));
/* prevent warnings: */
#ifdef XWORLD_DEBUG
if (video2Index != MEMLIST_PART_NONE)
debug(DBG_RES, "Loaded resource %d (%s) in _segVideo2.", video2Index, resTypeToString(res, res->_memList[video2Index].type));
#endif
res->currentPartId = partId;
/* _scriptCurPtr is changed in res->load(); */
res->_scriptBakPtr = res->_scriptCurPtr;
}
void res_allocMemBlock(struct Resource* res) {
if(rb->audio_status())
rb->audio_stop();
/* steal the audio buffer */
size_t sz;
/* memory usage is as follows:
[VM memory - 600K]
[Framebuffers - 128K]
[Temporary framebuffer - 192K]
[String table buffer]
*/
res->_memPtrStart = rb->plugin_get_audio_buffer(&sz);
if(sz < MEM_BLOCK_SIZE + (4 * VID_PAGE_SIZE) + 320 * 200 * sizeof(fb_data))
{
warning("res_allocMemBlock: can't allocate enough memory!");
}
res->sys->membuf = res->_memPtrStart + ( MEM_BLOCK_SIZE + (4 * VID_PAGE_SIZE) + 320 * 200 * sizeof(fb_data));
res->sys->bytes_left = sz - (MEM_BLOCK_SIZE + (4 * VID_PAGE_SIZE) + 320 * 200 * sizeof(fb_data));
debug(DBG_RES, "audiobuf is %d bytes in size", sz);
res->_scriptBakPtr = res->_scriptCurPtr = res->_memPtrStart;
res->_vidBakPtr = res->_vidCurPtr = res->_memPtrStart + MEM_BLOCK_SIZE - 0x800 * 16; //0x800 = 2048, so we have 32KB free for vidBack and vidCur
res->_useSegVideo2 = false;
}
void res_freeMemBlock(struct Resource* res) {
(void) res;
/* there's no need to do anything to free the audio buffer */
return;
}
void res_saveOrLoad(struct Resource* res, struct Serializer *ser) {
uint8_t loadedList[64];
if (ser->_mode == SM_SAVE) {
rb->memset(loadedList, 0, sizeof(loadedList));
uint8_t *p = loadedList;
uint8_t *q = res->_memPtrStart;
while (1) {
struct MemEntry *it = res->_memList;
struct MemEntry *me = 0;
uint16_t num = res->_numMemList;
while (num--) {
if (it->state == MEMENTRY_STATE_LOADED && it->bufPtr == q) {
me = it;
}
++it;
}
if (me == 0) {
break;
} else {
assert(p < loadedList + 64);
*p++ = me - res->_memList;
q += me->size;
}
}
}
struct Entry entries[] = {
SE_ARRAY(loadedList, 64, SES_INT8, VER(1)),
SE_INT(&res->currentPartId, SES_INT16, VER(1)),
SE_PTR(&res->_scriptBakPtr, VER(1)),
SE_PTR(&res->_scriptCurPtr, VER(1)),
SE_PTR(&res->_vidBakPtr, VER(1)),
SE_PTR(&res->_vidCurPtr, VER(1)),
SE_INT(&res->_useSegVideo2, SES_BOOL, VER(1)),
SE_PTR(&res->segPalettes, VER(1)),
SE_PTR(&res->segBytecode, VER(1)),
SE_PTR(&res->segCinematic, VER(1)),
SE_PTR(&res->_segVideo2, VER(1)),
SE_END()
};
ser_saveOrLoadEntries(ser, entries);
if (ser->_mode == SM_LOAD) {
uint8_t *p = loadedList;
uint8_t *q = res->_memPtrStart;
while (*p) {
struct MemEntry *me = &res->_memList[*p++];
res_readBank(res, me, q);
me->bufPtr = q;
me->state = MEMENTRY_STATE_LOADED;
q += me->size;
}
}
}
const char* res_getDataDir(struct Resource* res)
{
return res->_dataDir;
}

View file

@ -0,0 +1,107 @@
/***************************************************************************
* __________ __ ___.
* 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.
*
***************************************************************************/
#ifndef __RESOURCE_H__
#define __RESOURCE_H__
#include "intern.h"
#define MEMENTRY_STATE_END_OF_MEMLIST 0xFF
#define MEMENTRY_STATE_NOT_NEEDED 0
#define MEMENTRY_STATE_LOADED 1
#define MEMENTRY_STATE_LOAD_ME 2
/*
This is a directory entry. When the game starts, it loads memlist.bin and
populate and array of MemEntry
*/
typedef struct MemEntry {
uint8_t state; /* 0x0 */
uint8_t type; /* 0x1, Resource::ResType */
uint8_t *bufPtr; /* 0x2 */
uint16_t unk4; /* 0x4, unused */
uint8_t rankNum; /* 0x6 */
uint8_t bankId; /* 0x7 */
uint32_t bankOffset; /* 0x8 0xA */
uint16_t unkC; /* 0xC, unused */
uint16_t packedSize; /* 0xE */
/* All ressources are packed (for a gain of 28% according to Chahi) */
uint16_t unk10; /* 0x10, unused */
uint16_t size; /* 0x12 */
} __attribute__((packed)) MemEntry;
/*
Note: state is not a boolean, it can have value 0, 1, 2 or 255, respectively meaning:
0:NOT_NEEDED
1:LOADED
2:LOAD_ME
255:END_OF_MEMLIST
See MEMENTRY_STATE_* #defines above.
*/
struct Serializer;
struct Video;
#define MEM_BLOCK_SIZE (600 * 1024)
#define RT_SOUND 0
#define RT_MUSIC 1
#define RT_POLY_ANIM 2
#define RT_PALETTE 3
#define RT_BYTECODE 4
#define RT_POLY_CINEMATIC 5
struct Resource {
struct Video *video;
struct System *sys;
const char *_dataDir;
struct MemEntry _memList[150];
uint16_t _numMemList;
uint16_t currentPartId, requestedNextPart;
uint8_t *_memPtrStart, *_scriptBakPtr, *_scriptCurPtr, *_vidBakPtr, *_vidCurPtr;
bool _useSegVideo2;
uint8_t *segPalettes;
uint8_t *segBytecode;
uint8_t *segCinematic;
uint8_t *_segVideo2;
};
void res_create(struct Resource*, struct Video*, struct System*, const char* dataDir);
void res_readBank(struct Resource*, const MemEntry *me, uint8_t *dstBuf);
void res_readEntries(struct Resource*);
void res_loadMarkedAsNeeded(struct Resource*);
void res_invalidateAll(struct Resource*);
void res_invalidateRes(struct Resource*);
void res_loadPartsOrMemoryEntry(struct Resource*, uint16_t num);
void res_setupPart(struct Resource*, uint16_t ptrId);
void res_allocMemBlock(struct Resource*);
void res_freeMemBlock(struct Resource*);
void res_saveOrLoad(struct Resource*, struct Serializer *ser);
const char* res_getDataDir(struct Resource*);
#endif

View file

@ -0,0 +1,141 @@
/***************************************************************************
* __________ __ ___.
* 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 "serializer.h"
#include "file.h"
void ser_create(struct Serializer* c, File *stream, enum Mode mode, uint8_t *ptrBlock, uint16_t saveVer)
{
c->_stream = stream;
c->_mode = mode;
c->_ptrBlock = ptrBlock;
c->_saveVer = saveVer;
}
void ser_saveOrLoadEntries(struct Serializer* c, struct Entry *entry) {
debug(DBG_SER, "ser_saveOrLoadEntries() _mode=%d", c->_mode);
c->_bytesCount = 0;
switch (c->_mode) {
case SM_SAVE:
ser_saveEntries(c, entry);
break;
case SM_LOAD:
ser_loadEntries(c, entry);
break;
}
debug(DBG_SER, "ser_saveOrLoadEntries() _bytesCount=%d", c->_bytesCount);
}
void ser_saveEntries(struct Serializer* c, struct Entry *entry) {
debug(DBG_SER, "ser_saveEntries()");
for (; entry->type != SET_END; ++entry) {
if (entry->maxVer == CUR_VER) {
switch (entry->type) {
case SET_INT:
ser_saveInt(c, entry->size, entry->data);
c->_bytesCount += entry->size;
break;
case SET_ARRAY:
if (entry->size == SES_INT8) {
file_write(c->_stream, entry->data, entry->n);
c->_bytesCount += entry->n;
} else {
uint8_t *p = (uint8_t *)entry->data;
for (int i = 0; i < entry->n; ++i) {
ser_saveInt(c, entry->size, p);
p += entry->size;
c->_bytesCount += entry->size;
}
}
break;
case SET_PTR:
file_writeUint32BE(c->_stream, *(uint8_t **)(entry->data) - c->_ptrBlock);
c->_bytesCount += 4;
break;
case SET_END:
break;
}
}
}
}
void ser_loadEntries(struct Serializer* c, struct Entry *entry) {
debug(DBG_SER, "ser_loadEntries()");
for (; entry->type != SET_END; ++entry) {
if (c->_saveVer >= entry->minVer && c->_saveVer <= entry->maxVer) {
switch (entry->type) {
case SET_INT:
ser_loadInt(c, entry->size, entry->data);
c->_bytesCount += entry->size;
break;
case SET_ARRAY:
if (entry->size == SES_INT8) {
file_read(c->_stream, entry->data, entry->n);
c->_bytesCount += entry->n;
} else {
uint8_t *p = (uint8_t *)entry->data;
for (int i = 0; i < entry->n; ++i) {
ser_loadInt(c, entry->size, p);
p += entry->size;
c->_bytesCount += entry->size;
}
}
break;
case SET_PTR:
*(uint8_t **)(entry->data) = c->_ptrBlock + file_readUint32BE(c->_stream);
c->_bytesCount += 4;
break;
case SET_END:
break;
}
}
}
}
void ser_saveInt(struct Serializer* c, uint8_t es, void *p) {
switch (es) {
case 1:
file_writeByte(c->_stream, *(uint8_t *)p);
break;
case 2:
file_writeUint16BE(c->_stream, *(uint16_t *)p);
break;
case 4:
file_writeUint32BE(c->_stream, *(uint32_t *)p);
break;
}
}
void ser_loadInt(struct Serializer* c, uint8_t es, void *p) {
switch (es) {
case 1:
*(uint8_t *)p = file_readByte(c->_stream);
break;
case 2:
*(uint16_t *)p = file_readUint16BE(c->_stream);
break;
case 4:
*(uint32_t *)p = file_readUint32BE(c->_stream);
break;
}
}

View file

@ -0,0 +1,84 @@
/***************************************************************************
* __________ __ ___.
* 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.
*
***************************************************************************/
#ifndef __SERIALIZER_H__
#define __SERIALIZER_H__
#include "intern.h"
#define CUR_VER 2
#define VER(x) x
enum EntryType {
SET_INT,
SET_ARRAY,
SET_PTR,
SET_END
};
#define SE_INT(i,sz,ver) { SET_INT, sz, 1, i, ver, CUR_VER }
#define SE_ARRAY(a,n,sz,ver) { SET_ARRAY, sz, n, a, ver, CUR_VER }
#define SE_PTR(p,ver) { SET_PTR, 0, 0, p, ver, CUR_VER }
#define SE_END() { SET_END, 0, 0, 0, 0, 0 }
struct File;
enum {
SES_BOOL = 1,
SES_INT8 = 1,
SES_INT16 = 2,
SES_INT32 = 4
};
enum Mode {
SM_SAVE,
SM_LOAD
};
struct Entry {
enum EntryType type;
uint8_t size;
uint16_t n;
void *data;
uint16_t minVer;
uint16_t maxVer;
};
struct Serializer {
File *_stream;
enum Mode _mode;
uint8_t *_ptrBlock;
uint16_t _saveVer;
uint32_t _bytesCount;
};
void ser_create(struct Serializer*, File *stream, enum Mode mode, uint8_t *ptrBlock, uint16_t saveVer);
void ser_saveOrLoadEntries(struct Serializer*, struct Entry *entry);
void ser_saveEntries(struct Serializer*, struct Entry *entry);
void ser_loadEntries(struct Serializer*, struct Entry *entry);
void ser_saveInt(struct Serializer*, uint8_t es, void *p);
void ser_loadInt(struct Serializer*, uint8_t es, void *p);
#endif

View file

@ -0,0 +1,247 @@
/***************************************************************************
* __________ __ ___.
* 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 "sfxplayer.h"
#include "mixer.h"
#include "resource.h"
#include "serializer.h"
#include "sys.h"
void player_create(struct SfxPlayer* sfx, struct Mixer *mix, struct Resource *res, struct System *stub)
{
sfx->mixer = mix;
sfx->res = res;
sfx->sys = stub;
sfx->_delay = 0;
sfx->_resNum = 0;
}
void player_init(struct SfxPlayer* sfx) {
debug(DBG_SND, "sys is 0x%08x", sfx->sys);
sfx->_mutex = sys_createMutex(sfx->sys);
}
void player_free(struct SfxPlayer* sfx) {
player_stop(sfx);
sys_destroyMutex(sfx->sys, sfx->_mutex);
}
void player_setEventsDelay(struct SfxPlayer* sfx, uint16_t delay) {
debug(DBG_SND, "player_setEventsDelay(%d)", delay);
struct MutexStack_t ms;
MutexStack(&ms, sfx->sys, sfx->_mutex);
sfx->_delay = delay * 60 / 7050;
MutexStack_destroy(&ms);
}
void player_loadSfxModule(struct SfxPlayer* sfx, uint16_t resNum, uint16_t delay, uint8_t pos) {
debug(DBG_SND, "player_loadSfxModule(0x%X, %d, %d)", resNum, delay, pos);
struct MutexStack_t ms;
MutexStack(&ms, sfx->sys, sfx->_mutex);
struct MemEntry *me = &sfx->res->_memList[resNum];
if (me->state == MEMENTRY_STATE_LOADED && me->type == RT_MUSIC) {
sfx->_resNum = resNum;
rb->memset(&sfx->_sfxMod, 0, sizeof(struct SfxModule));
sfx->_sfxMod.curOrder = pos;
sfx->_sfxMod.numOrder = READ_BE_UINT16(me->bufPtr + 0x3E);
debug(DBG_SND, "player_loadSfxModule() curOrder = 0x%X numOrder = 0x%X", sfx->_sfxMod.curOrder, sfx->_sfxMod.numOrder);
for (int i = 0; i < 0x80; ++i) {
sfx->_sfxMod.orderTable[i] = *(me->bufPtr + 0x40 + i);
}
if (delay == 0) {
sfx->_delay = READ_BE_UINT16(me->bufPtr);
} else {
sfx->_delay = delay;
}
sfx->_delay = sfx->_delay * 60 / 7050;
sfx->_sfxMod.data = me->bufPtr + 0xC0;
debug(DBG_SND, "player_loadSfxModule() eventDelay = %d ms", sfx->_delay);
player_prepareInstruments(sfx, me->bufPtr + 2);
} else {
warning("player_loadSfxModule() ec=0x%X", 0xF8);
}
MutexStack_destroy(&ms);
}
void player_prepareInstruments(struct SfxPlayer* sfx, const uint8_t *p) {
rb->memset(sfx->_sfxMod.samples, 0, sizeof(sfx->_sfxMod.samples));
for (int i = 0; i < 15; ++i) {
struct SfxInstrument *ins = &sfx->_sfxMod.samples[i];
uint16_t resNum = READ_BE_UINT16(p);
p += 2;
if (resNum != 0) {
ins->volume = READ_BE_UINT16(p);
struct MemEntry *me = &sfx->res->_memList[resNum];
if (me->state == MEMENTRY_STATE_LOADED && me->type == RT_SOUND) {
ins->data = me->bufPtr;
rb->memset(ins->data + 8, 0, 4);
debug(DBG_SND, "Loaded instrument 0x%X n=%d volume=%d", resNum, i, ins->volume);
} else {
error("Error loading instrument 0x%X", resNum);
}
}
p += 2; /* skip volume */
}
}
void player_start(struct SfxPlayer* sfx) {
debug(DBG_SND, "player_start()");
struct MutexStack_t ms;
MutexStack(&ms, sfx->sys, sfx->_mutex);
sfx->_sfxMod.curPos = 0;
sfx->_timerId = sys_addTimer(sfx->sys, sfx->_delay, player_eventsCallback, sfx);
MutexStack_destroy(&ms);
}
void player_stop(struct SfxPlayer* sfx) {
debug(DBG_SND, "player_stop()");
struct MutexStack_t ms;
MutexStack(&ms, sfx->sys, sfx->_mutex);
if (sfx->_resNum != 0) {
sfx->_resNum = 0;
sys_removeTimer(sfx->sys, sfx->_timerId);
}
MutexStack_destroy(&ms);
}
void player_handleEvents(struct SfxPlayer* sfx) {
struct MutexStack_t ms;
MutexStack(&ms, sfx->sys, sfx->_mutex);
uint8_t order = sfx->_sfxMod.orderTable[sfx->_sfxMod.curOrder];
const uint8_t *patternData = sfx->_sfxMod.data + sfx->_sfxMod.curPos + order * 1024;
for (uint8_t ch = 0; ch < 4; ++ch) {
player_handlePattern(sfx, ch, patternData);
patternData += 4;
}
sfx->_sfxMod.curPos += 4 * 4;
debug(DBG_SND, "player_handleEvents() order = 0x%X curPos = 0x%X", order, sfx->_sfxMod.curPos);
if (sfx->_sfxMod.curPos >= 1024) {
sfx->_sfxMod.curPos = 0;
order = sfx->_sfxMod.curOrder + 1;
if (order == sfx->_sfxMod.numOrder) {
sfx->_resNum = 0;
sys_removeTimer(sfx->sys, sfx->_timerId);
mixer_stopAll(sfx->mixer);
}
sfx->_sfxMod.curOrder = order;
}
MutexStack_destroy(&ms);
}
void player_handlePattern(struct SfxPlayer* sfx, uint8_t channel, const uint8_t *data) {
struct SfxPattern pat;
rb->memset(&pat, 0, sizeof(struct SfxPattern));
pat.note_1 = READ_BE_UINT16(data + 0);
pat.note_2 = READ_BE_UINT16(data + 2);
if (pat.note_1 != 0xFFFD) {
uint16_t sample = (pat.note_2 & 0xF000) >> 12;
if (sample != 0) {
uint8_t *ptr = sfx->_sfxMod.samples[sample - 1].data;
if (ptr != 0) {
debug(DBG_SND, "player_handlePattern() preparing sample %d", sample);
pat.sampleVolume = sfx->_sfxMod.samples[sample - 1].volume;
pat.sampleStart = 8;
pat.sampleBuffer = ptr;
pat.sampleLen = READ_BE_UINT16(ptr) * 2;
uint16_t loopLen = READ_BE_UINT16(ptr + 2) * 2;
if (loopLen != 0) {
pat.loopPos = pat.sampleLen;
pat.loopData = ptr;
pat.loopLen = loopLen;
} else {
pat.loopPos = 0;
pat.loopData = 0;
pat.loopLen = 0;
}
int16_t m = pat.sampleVolume;
uint8_t effect = (pat.note_2 & 0x0F00) >> 8;
if (effect == 5) { /* volume up */
uint8_t volume = (pat.note_2 & 0xFF);
m += volume;
if (m > 0x3F) {
m = 0x3F;
}
} else if (effect == 6) { /* volume down */
uint8_t volume = (pat.note_2 & 0xFF);
m -= volume;
if (m < 0) {
m = 0;
}
}
mixer_setChannelVolume(sfx->mixer, channel, m);
pat.sampleVolume = m;
}
}
}
if (pat.note_1 == 0xFFFD) {
debug(DBG_SND, "player_handlePattern() _scriptVars[0xF4] = 0x%X", pat.note_2);
*sfx->_markVar = pat.note_2;
} else if (pat.note_1 != 0) {
if (pat.note_1 == 0xFFFE) {
mixer_stopChannel(sfx->mixer, channel);
} else if (pat.sampleBuffer != 0) {
struct MixerChunk mc;
rb->memset(&mc, 0, sizeof(mc));
mc.data = pat.sampleBuffer + pat.sampleStart;
mc.len = pat.sampleLen;
mc.loopPos = pat.loopPos;
mc.loopLen = pat.loopLen;
/* convert amiga period value to hz */
uint16_t freq = 7159092 / (pat.note_1 * 2);
debug(DBG_SND, "player_handlePattern() adding sample freq = 0x%X", freq);
mixer_playChannel(sfx->mixer, channel, &mc, freq, pat.sampleVolume);
}
}
}
uint32_t player_eventsCallback(uint32_t interval, void *param) {
(void) interval;
debug(DBG_SND, "player_eventsCallback with interval %d ms and param 0x%08x", interval, param);
struct SfxPlayer *p = (struct SfxPlayer *)param;
player_handleEvents(p);
return p->_delay;
}
void player_saveOrLoad(struct SfxPlayer* sfx, struct Serializer *ser) {
sys_lockMutex(sfx->sys, sfx->_mutex);
struct Entry entries[] = {
SE_INT(&sfx->_delay, SES_INT8, VER(2)),
SE_INT(&sfx->_resNum, SES_INT16, VER(2)),
SE_INT(&sfx->_sfxMod.curPos, SES_INT16, VER(2)),
SE_INT(&sfx->_sfxMod.curOrder, SES_INT8, VER(2)),
SE_END()
};
ser_saveOrLoadEntries(ser, entries);
sys_unlockMutex(sfx->sys, sfx->_mutex);
if (ser->_mode == SM_LOAD && sfx->_resNum != 0) {
uint16_t delay = sfx->_delay;
player_loadSfxModule(sfx, sfx->_resNum, 0, sfx->_sfxMod.curOrder);
sfx->_delay = delay;
sfx->_timerId = sys_addTimer(sfx->sys, sfx->_delay, player_eventsCallback, sfx);
}
}

View file

@ -0,0 +1,88 @@
/***************************************************************************
* __________ __ ___.
* 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.
*
***************************************************************************/
#ifndef __SFXPLAYER_H__
#define __SFXPLAYER_H__
#include "intern.h"
struct SfxInstrument {
uint8_t *data;
uint16_t volume;
};
struct SfxModule {
const uint8_t *data;
uint16_t curPos;
uint8_t curOrder;
uint8_t numOrder;
uint8_t orderTable[0x80];
struct SfxInstrument samples[15];
};
struct SfxPattern {
uint16_t note_1;
uint16_t note_2;
uint16_t sampleStart;
uint8_t *sampleBuffer;
uint16_t sampleLen;
uint16_t loopPos;
uint8_t *loopData;
uint16_t loopLen;
uint16_t sampleVolume;
};
struct Mixer;
struct Resource;
struct Serializer;
struct System;
struct SfxPlayer {
struct Mixer *mixer;
struct Resource *res;
struct System *sys;
void *_mutex;
void *_timerId;
uint16_t _delay;
uint16_t _resNum;
struct SfxModule _sfxMod;
int16_t *_markVar;
};
void player_create(struct SfxPlayer*, struct Mixer *mix, struct Resource *res, struct System *stub);
void player_init(struct SfxPlayer*);
void player_free(struct SfxPlayer*);
void player_setEventsDelay(struct SfxPlayer*, uint16_t delay);
void player_loadSfxModule(struct SfxPlayer*, uint16_t resNum, uint16_t delay, uint8_t pos);
void player_prepareInstruments(struct SfxPlayer*, const uint8_t *p);
void player_start(struct SfxPlayer*);
void player_stop(struct SfxPlayer*);
void player_handleEvents(struct SfxPlayer*);
void player_handlePattern(struct SfxPlayer*, uint8_t channel, const uint8_t *patternData);
uint32_t player_eventsCallback(uint32_t interval, void *param);
void player_saveOrLoad(struct SfxPlayer*, struct Serializer *ser);
#endif

942
apps/plugins/xworld/sys.c Normal file
View file

@ -0,0 +1,942 @@
/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2014 Franklin Wei, Benjamin Brown
*
* 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.
*
***************************************************************************/
/* TODO: */
/* vertical stride support (as of Dec. 2014, only the M:Robe 500 has a color,
vertical stride LCD) */
/* monochrome/grayscale support (many of these targets have vertical strides,
so get that working first!) */
#include "plugin.h"
#include "lib/display_text.h"
#include "lib/helper.h"
#include "lib/playback_control.h"
#include "lib/pluginlib_actions.h"
#include "lib/pluginlib_bmp.h"
#include "lib/pluginlib_exit.h"
#include "sys.h"
#include "parts.h"
#include "engine.h"
#include "keymaps.h"
static struct System* save_sys;
static bool sys_save_settings(struct System* sys)
{
File f;
file_create(&f, false);
if(!file_open(&f, SETTINGS_FILE, sys->e->_saveDir, "wb"))
{
return false;
}
file_write(&f, &sys->settings, sizeof(sys->settings));
file_close(&f);
return true;
}
static void sys_rotate_keymap(struct System* sys)
{
switch(sys->settings.rotation_option)
{
case 0:
sys->keymap.up = BTN_UP;
sys->keymap.down = BTN_DOWN;
sys->keymap.left = BTN_LEFT;
sys->keymap.right = BTN_RIGHT;
#if (CONFIG_KEYPAD == SANSA_FUZEPLUS_PAD)
sys->keymap.upleft = BTN_UP_LEFT;
sys->keymap.upright = BTN_UP_RIGHT;
sys->keymap.downleft = BTN_DOWN_RIGHT;
sys->keymap.downright = BTN_DOWN_LEFT;
#endif
break;
case 1:
sys->keymap.up = BTN_RIGHT;
sys->keymap.down = BTN_LEFT;
sys->keymap.left = BTN_UP;
sys->keymap.right = BTN_DOWN;
#if (CONFIG_KEYPAD == SANSA_FUZEPLUS_PAD)
sys->keymap.upleft = BTN_UP_RIGHT;
sys->keymap.upright = BTN_DOWN_RIGHT;
sys->keymap.downleft = BTN_UP_LEFT;
sys->keymap.downright = BTN_DOWN_LEFT;
#endif
break;
case 2:
sys->keymap.up = BTN_LEFT;
sys->keymap.down = BTN_RIGHT;
sys->keymap.left = BTN_DOWN;
sys->keymap.right = BTN_UP;
#if (CONFIG_KEYPAD == SANSA_FUZEPLUS_PAD)
sys->keymap.upleft = BTN_DOWN_LEFT;
sys->keymap.upright = BTN_UP_LEFT;
sys->keymap.downleft = BTN_DOWN_RIGHT;
sys->keymap.downright = BTN_DOWN_LEFT;
#endif
break;
default:
error("sys_rotate_keymap: fall-through!");
}
}
static bool sys_load_settings(struct System* sys)
{
File f;
file_create(&f, false);
if(!file_open(&f, SETTINGS_FILE, sys->e->_saveDir, "rb"))
{
return false;
}
file_read(&f, &sys->settings, sizeof(sys->settings));
file_close(&f);
sys_rotate_keymap(sys);
return true;
}
void exit_handler(void)
{
sys_save_settings(save_sys);
sys_stopAudio(save_sys);
#ifdef HAVE_ADJUSTABLE_CPU_FREQ
rb->cpu_boost(false);
#endif
backlight_use_settings();
}
static bool sys_do_help(void)
{
#ifdef HAVE_LCD_COLOR
rb->lcd_set_foreground(LCD_WHITE);
rb->lcd_set_background(LCD_BLACK);
#endif
rb->lcd_setfont(FONT_UI);
char* help_text[] = {"XWorld", "",
"XWorld", "is", "an", "interpreter", "for", "Another", "World,", "a", "fantastic", "game", "by", "Eric", "Chahi."
};
struct style_text style[] = {
{0, TEXT_CENTER | TEXT_UNDERLINE},
LAST_STYLE_ITEM
};
return display_text(ARRAYLEN(help_text), help_text, style, NULL, true);
}
static const struct opt_items scaling_settings[3] = {
{ "Disabled", -1 },
{ "Fast" , -1 },
#ifdef HAVE_LCD_COLOR
{ "Good" , -1 }
#endif
};
static const struct opt_items rotation_settings[3] = {
{ "Disabled" , -1 },
{ "Clockwise" , -1 },
{ "Anticlockwise", -1 }
};
static void do_video_settings(struct System* sys)
{
MENUITEM_STRINGLIST(menu, "Video Settings", NULL,
"Negative",
"Scaling",
"Rotation",
"Show FPS",
"Zoom on code",
"Back");
int sel = 0;
while(1)
{
switch(rb->do_menu(&menu, &sel, NULL, false))
{
case 0:
rb->set_bool("Negative", &sys->settings.negative_enabled);
break;
case 1:
rb->set_option("Scaling", &sys->settings.scaling_quality, INT, scaling_settings,
#ifdef HAVE_LCD_COLOR
3
#else
2
#endif
, NULL);
if(sys->settings.scaling_quality &&
sys->settings.zoom)
{
rb->splash(HZ*2, "Zoom automatically disabled.");
sys->settings.zoom = false;
}
break;
case 2:
rb->set_option("Rotation", &sys->settings.rotation_option, INT, rotation_settings, 3, NULL);
if(sys->settings.rotation_option &&
sys->settings.zoom)
{
rb->splash(HZ*2, "Zoom automatically disabled.");
sys->settings.zoom = false;
}
sys_rotate_keymap(sys);
break;
case 3:
rb->set_bool("Show FPS", &sys->settings.showfps);
break;
case 4:
rb->set_bool("Zoom on code", &sys->settings.zoom);
/* zoom only works with scaling and rotation disabled */
if(sys->settings.zoom &&
( sys->settings.scaling_quality |
sys->settings.rotation_option))
{
rb->splash(HZ*2, "Scaling and rotation automatically disabled.");
sys->settings.scaling_quality = 0;
sys->settings.rotation_option = 0;
}
break;
case 5:
rb->lcd_clear_display();
sys_save_settings(sys);
return;
}
}
}
#define MAX_SOUNDBUF_SIZE 512
const struct opt_items sound_bufsize_options[] = {
{"8 samples" , 8},
{"16 samples" , 16},
{"32 samples" , 32},
{"64 samples" , 64},
{"128 samples", 128},
{"256 samples", 256},
{"512 samples", 512},
};
static void do_sound_settings(struct System* sys)
{
MENUITEM_STRINGLIST(menu, "Sound Settings", NULL,
"Enabled",
"Buffer Level",
"Back",
);
int sel = 0;
while(1)
{
switch(rb->do_menu(&menu, &sel, NULL, false))
{
case 0:
rb->set_bool("Enabled", &sys->settings.sound_enabled);
break;
case 1:
rb->set_option("Buffer Level", &sys->settings.sound_bufsize, INT,
sound_bufsize_options, ARRAYLEN(sound_bufsize_options), NULL);
break;
case 2:
sys_save_settings(sys);
return;
}
}
}
static void sys_reset_settings(struct System* sys)
{
sys->settings.negative_enabled = false;
sys->settings.rotation_option = 0;
sys->settings.scaling_quality = 1;
sys->settings.sound_enabled = true;
sys->settings.sound_bufsize = 64;
sys->settings.showfps = true;
sys->settings.zoom = false;
sys_rotate_keymap(sys);
}
static struct System* mainmenu_sysptr;
static int mainmenu_cb(int action, const struct menu_item_ex *this_item)
{
int idx = ((intptr_t)this_item);
if(action == ACTION_REQUEST_MENUITEM && !mainmenu_sysptr->loaded && (idx == 0 || idx == 8 || idx == 10))
return ACTION_EXIT_MENUITEM;
return action;
}
static AudioCallback audio_callback;
static void* audio_param;
static struct System* audio_sys;
/************************************** MAIN MENU ***************************************/
void sys_menu(struct System* sys)
{
sys_stopAudio(sys);
rb->splash(0, "Loading...");
sys->loaded = engine_loadGameState(sys->e, 0);
rb->lcd_update();
mainmenu_sysptr = sys;
int sel = 0;
MENUITEM_STRINGLIST(menu, "XWorld Menu", mainmenu_cb,
"Resume Game", /* 0 */
"Start New Game", /* 1 */
"Playback Control", /* 2 */
"Video Settings", /* 3 */
"Sound Settings", /* 4 */
"Fast Mode", /* 5 */
"Help", /* 6 */
"Reset Settings", /* 7 */
"Load", /* 8 */
"Save", /* 9 */
"Quit without Saving", /* 10 */
"Save and Quit"); /* 11 */
bool quit = false;
while(!quit)
{
int item;
switch(item = rb->do_menu(&menu, &sel, NULL, false))
{
case 0:
quit = true;
break;
case 1:
vm_initForPart(&sys->e->vm, GAME_PART_FIRST); // This game part is the protection screen
quit = true;
break;
case 2:
playback_control(NULL);
break;
case 3:
do_video_settings(sys);
break;
case 4:
do_sound_settings(sys);
break;
case 5:
rb->set_bool("Fast Mode", &sys->e->vm._fastMode);
sys_save_settings(sys);
break;
case 6:
sys_do_help();
break;
case 7:
sys_reset_settings(sys);
sys_save_settings(sys);
break;
case 8:
rb->splash(0, "Loading...");
sys->loaded = engine_loadGameState(sys->e, 0);
rb->lcd_update();
break;
case 9:
sys->e->_stateSlot = 0;
rb->splash(0, "Saving...");
engine_saveGameState(sys->e, sys->e->_stateSlot, "quicksave");
rb->lcd_update();
break;
case 10:
engine_deleteGameState(sys->e, 0);
exit(PLUGIN_OK);
break;
case 11:
/* saves are NOT deleted on loading */
exit(PLUGIN_OK);
break;
default:
error("sys_menu: fall-through!");
}
}
rb->lcd_clear_display();
sys_startAudio(sys, audio_callback, audio_param);
}
void sys_init(struct System* sys, const char* title)
{
(void) title;
#ifdef HAVE_ADJUSTABLE_CPU_FREQ
rb->cpu_boost(true);
#endif
backlight_ignore_timeout();
rb_atexit(exit_handler);
save_sys = sys;
rb->memset(&sys->input, 0, sizeof(sys->input));
sys->mutex_bitfield = ~0;
if(!sys_load_settings(sys))
{
sys_reset_settings(sys);
}
}
void sys_destroy(struct System* sys)
{
(void) sys;
rb->splash(HZ, "Exiting...");
}
void sys_setPalette(struct System* sys, uint8_t start, uint8_t n, const uint8_t *buf)
{
for(int i = start; i < start + n; ++i)
{
uint8_t c[3];
for (int j = 0; j < 3; j++) {
uint8_t col = buf[i * 3 + j];
c[j] = (col << 2) | (col & 3);
}
#if (LCD_DEPTH > 16) && (LCD_DEPTH <= 24)
sys->palette[i] = (fb_data) {
c[2], c[1], c[0]
};
#elif defined(HAVE_LCD_COLOR)
sys->palette[i] = FB_RGBPACK(c[0], c[1], c[2]);
#elif LCD_DEPTH > 1
sys->palette[i] = LCD_BRIGHTNESS((c[0] + c[1] + c[2]) / 3);
#endif
}
}
/****************************** THE MAIN DRAWING METHOD ********************************/
void sys_copyRect(struct System* sys, uint16_t x, uint16_t y, uint16_t w, uint16_t h, const uint8_t *buf, uint32_t pitch)
{
static int last_timestamp = 1;
/* get the address of the temporary framebuffer that has been allocated in the audiobuf */
fb_data* framebuffer = (fb_data*) sys->e->res._memPtrStart + MEM_BLOCK_SIZE + (4 * VID_PAGE_SIZE);
buf += y * pitch + x;
/************************ BLIT THE TEMPORARY FRAMEBUFFER ***********************/
if(sys->settings.rotation_option)
{
/* clockwise */
if(sys->settings.rotation_option == 1)
{
while (h--) {
/* one byte gives two pixels */
for (int i = 0; i < w / 2; ++i) {
uint8_t pix1 = *(buf + i) >> 4;
uint8_t pix2 = *(buf + i) & 0xF;
#if defined(LCD_STRIDEFORMAT) && (LCD_STRIDEFORMAT == VERTICAL_STRIDE)
framebuffer[( (h * 2) ) * 320 + i] = sys->palette[pix1];
framebuffer[( (h * 2) + 1) * 320 + i] = sys->palette[pix2];
#else
framebuffer[( (i * 2) ) * 200 + h] = sys->palette[pix1];
framebuffer[( (i * 2) + 1) * 200 + h] = sys->palette[pix2];
#endif
}
buf += pitch;
}
}
/* counterclockwise */
else
{
while (h--) {
/* one byte gives two pixels */
for (int i = 0; i < w / 2; ++i) {
uint8_t pix1 = *(buf + i) >> 4;
uint8_t pix2 = *(buf + i) & 0xF;
#if defined(LCD_STRIDEFORMAT) && (LCD_STRIDEFORMAT == VERTICAL_STRIDE)
framebuffer[(200 - h * 2 ) * 320 + ( 320 - i )] = sys->palette[pix1];
framebuffer[(200 - h * 2 - 1) * 320 + ( 320 - i )] = sys->palette[pix2];
#else
framebuffer[(320 - i * 2 ) * 200 + ( 200 - h )] = sys->palette[pix1];
framebuffer[(320 - i * 2 - 1) * 200 + ( 200 - h )] = sys->palette[pix2];
#endif
}
buf += pitch;
}
}
}
/* no rotation */
else
{
int next = 0;
#if defined(LCD_STRIDEFORMAT) && (LCD_STRIDEFORMAT == VERTICAL_STRIDE)
for(int x = 0; x < w / 2; ++x)
{
for(int y = 0; y < h; ++y)
{
uint8_t pix1 = buf[ y * w + x ] >> 4;
uint8_t pix2 = buf[ y * w + x ] & 0xF;
framebuffer[(x + 0)*h + y] = sys->palette[pix1];
framebuffer[(x + 1)*h + y] = sys->palette[pix2];
}
}
#else
while (h--) {
/* one byte gives two pixels */
for (int i = 0; i < w / 2; ++i) {
uint8_t pix1 = *(buf + i) >> 4;
uint8_t pix2 = *(buf + i) & 0xF;
framebuffer[next] = sys->palette[pix1];
++next;
framebuffer[next] = sys->palette[pix2];
++next;
}
buf += pitch;
}
#endif
}
/*************************** NOW SCALE IT! ***************************/
if(sys->settings.scaling_quality)
{
struct bitmap in_bmp;
if(sys->settings.rotation_option)
{
#if defined(LCD_STRIDEFORMAT) && (LCD_STRIDEFORMAT == VERTICAL_STRIDE)
in_bmp.width = 320;
in_bmp.height = 200;
#else
in_bmp.width = 200;
in_bmp.height = 320;
#endif
}
else
{
#if defined(LCD_STRIDEFORMAT) && (LCD_STRIDEFORMAT == VERTICAL_STRIDE)
in_bmp.width = 200;
in_bmp.height = 320;
#else
in_bmp.width = 320;
in_bmp.height = 200;
#endif
}
in_bmp.data = (unsigned char*) framebuffer;
struct bitmap out_bmp;
out_bmp.width = LCD_WIDTH;
out_bmp.height = LCD_HEIGHT;
out_bmp.data = (unsigned char*) rb->lcd_framebuffer;
#ifdef HAVE_LCD_COLOR
if(sys->settings.scaling_quality == 1)
#endif
simple_resize_bitmap(&in_bmp, &out_bmp);
#ifdef HAVE_LCD_COLOR
else
smooth_resize_bitmap(&in_bmp, &out_bmp);
#endif
}
else
{
#if defined(LCD_STRIDEFORMAT) && (LCD_STRIDEFORMAT == VERTICAL_STRIDE)
for(int x = 0; x < 320; ++x)
{
for(int y = 0; y < 200; ++y)
{
rb->lcd_set_foreground(framebuffer[x * 200 + y]);
rb->lcd_drawpixel(x, y);
}
}
#else
if(sys->settings.zoom)
{
rb->lcd_bitmap_part(framebuffer, CODE_X, CODE_Y, STRIDE(SCREEN_MAIN, 320, 200), 0, 0, 320 - CODE_X, 200 - CODE_Y);
}
else
{
if(sys->settings.rotation_option)
rb->lcd_bitmap(framebuffer, 0, 0, 200, 320);
else
rb->lcd_bitmap(framebuffer, 0, 0, 320, 200);
}
#endif
}
/************************************* APPLY FILTERS ******************************************/
if(sys->settings.negative_enabled)
{
for(int y = 0; y < LCD_HEIGHT; ++y)
{
for(int x = 0; x < LCD_WIDTH; ++x)
{
#ifdef HAVE_LCD_COLOR
int r, g, b;
fb_data pix = rb->lcd_framebuffer[y * LCD_WIDTH + x];
r = RGB_UNPACK_RED (pix);
g = RGB_UNPACK_GREEN(pix);
b = RGB_UNPACK_BLUE (pix);
rb->lcd_framebuffer[y * LCD_WIDTH + x] = LCD_RGBPACK(0xff - r, 0xff - g, 0xff - b);
#else
rb->lcd_framebuffer[y * LCD_WIDTH + x] = LCD_BRIGHTNESS(0xff - rb->lcd_framebuffer[y * LCD_WIDTH + x]);
#endif
}
}
}
/*********************** SHOW FPS *************************/
int current_time = sys_getTimeStamp(sys);
if(sys->settings.showfps)
{
rb->lcd_set_foreground(LCD_BLACK);
rb->lcd_set_background(LCD_WHITE);
/* use 1000 and not HZ here because getTimeStamp is in milliseconds */
rb->lcd_putsf(0, 0, "FPS: %d", 1000 / ((current_time - last_timestamp == 0) ? 1 : current_time - last_timestamp));
}
rb->lcd_update();
last_timestamp = sys_getTimeStamp(sys);
}
static void do_pause_menu(struct System* sys)
{
sys_stopAudio(sys);
int sel = 0;
MENUITEM_STRINGLIST(menu, "XWorld Menu", NULL,
"Resume Game", /* 0 */
"Start New Game", /* 1 */
"Playback Control", /* 2 */
"Video Settings", /* 3 */
"Sound Settings", /* 4 */
"Fast Mode", /* 5 */
"Enter Code", /* 6 */
"Help", /* 7 */
"Reset Settings", /* 8 */
"Load", /* 9 */
"Save", /* 10 */
"Quit"); /* 11 */
bool quit = false;
while(!quit)
{
switch(rb->do_menu(&menu, &sel, NULL, false))
{
case 0:
quit = true;
break;
case 1:
vm_initForPart(&sys->e->vm, GAME_PART_FIRST);
quit = true;
break;
case 2:
playback_control(NULL);
break;
case 3:
do_video_settings(sys);
break;
case 4:
do_sound_settings(sys);
break;
case 5:
rb->set_bool("Fast Mode", &sys->e->vm._fastMode);
sys_save_settings(sys);
break;
case 6:
sys->input.code = true;
quit = true;
break;
case 7:
sys_do_help();
break;
case 8:
sys_reset_settings(sys);
sys_save_settings(sys);
break;
case 9:
rb->splash(0, "Loading...");
sys->loaded = engine_loadGameState(sys->e, 0);
rb->lcd_update();
quit = true;
break;
case 10:
sys->e->_stateSlot = 0;
rb->splash(0, "Saving...");
engine_saveGameState(sys->e, sys->e->_stateSlot, "quicksave");
rb->lcd_update();
break;
case 11:
exit(PLUGIN_OK);
break;
}
}
rb->lcd_clear_display();
sys_startAudio(sys, audio_callback, audio_param);
}
void sys_processEvents(struct System* sys)
{
int btn = rb->button_get(false);
btn &= ~BUTTON_REDRAW;
debug(DBG_SYS, "button is 0x%08x", btn);
/* exit early if we can */
if(btn == BUTTON_NONE)
{
return;
}
/* Ignore some buttons that cause errant input */
#if (CONFIG_KEYPAD == IPOD_4G_PAD) || \
(CONFIG_KEYPAD == IPOD_3G_PAD) || \
(CONFIG_KEYPAD == IPOD_1G2G_PAD)
if(btn & 0x80000000)
return;
#endif
#if (CONFIG_KEYPAD == SANSA_E200_PAD)
if(btn == (BUTTON_SCROLL_FWD || BUTTON_SCROLL_BACK))
return;
#endif
#if (CONFIG_KEYPAD == SANSA_FUZEPLUS_PAD)
if(btn == (BUTTON_SELECT))
return;
#endif
/* handle special keys first */
switch(btn)
{
case BTN_PAUSE:
do_pause_menu(sys);
sys->input.dirMask = 0;
sys->input.button = false;
/* exit early to avoid unwanted button presses being detected */
return;
default:
exit_on_usb(btn);
break;
}
/* handle releases */
if(btn & BUTTON_REL)
{
debug(DBG_SYS, "button_rel");
btn &= ~BUTTON_REL;
if(btn & BTN_FIRE)
sys->input.button = false;
if(btn & sys->keymap.up)
sys->input.dirMask &= ~DIR_UP;
if(btn & sys->keymap.down)
sys->input.dirMask &= ~DIR_DOWN;
if(btn & sys->keymap.left)
sys->input.dirMask &= ~DIR_LEFT;
if(btn & sys->keymap.right)
sys->input.dirMask &= ~DIR_RIGHT;
#ifdef BTN_DOWN_LEFT
if(btn & sys->keymap.downleft)
sys->input.dirMask &= ~(DIR_DOWN | DIR_LEFT);
#endif
#ifdef BTN_DOWN_RIGHT
if(btn & sys->keymap.downright)
sys->input.dirMask &= ~(DIR_DOWN | DIR_RIGHT);
#endif
#ifdef BTN_UP_LEFT
if(btn & sys->keymap.upleft)
sys->input.dirMask &= ~(DIR_UP | DIR_LEFT);
#endif
#ifdef BTN_UP_RIGHT
if(btn & sys->keymap.upright)
sys->input.dirMask &= ~(DIR_UP | DIR_RIGHT);
#endif
}
/* then handle presses */
else
{
if(btn & BTN_FIRE)
sys->input.button = true;
if(btn & sys->keymap.up)
sys->input.dirMask |= DIR_UP;
if(btn & sys->keymap.down)
sys->input.dirMask |= DIR_DOWN;
if(btn & sys->keymap.left)
sys->input.dirMask |= DIR_LEFT;
if(btn & sys->keymap.right)
sys->input.dirMask |= DIR_RIGHT;
#ifdef BTN_DOWN_LEFT
if(btn & sys->keymap.downleft)
sys->input.dirMask |= (DIR_DOWN | DIR_LEFT);
#endif
#ifdef BTN_DOWN_RIGHT
if(btn & sys->keymap.downright)
sys->input.dirMask |= (DIR_DOWN | DIR_RIGHT);
#endif
#ifdef BTN_UP_LEFT
if(btn & sys->keymap.upleft)
sys->input.dirMask |= (DIR_UP | DIR_LEFT);
#endif
#ifdef BTN_UP_RIGHT
if(btn & sys->keymap.upright)
sys->input.dirMask |= (DIR_UP | DIR_RIGHT);
#endif
}
debug(DBG_SYS, "dirMask is 0x%02x", sys->input.dirMask);
debug(DBG_SYS, "button is %s", sys->input.button == true ? "true" : "false");
}
void sys_sleep(struct System* sys, uint32_t duration)
{
(void) sys;
/* duration is in ms */
rb->sleep(duration / 10);
}
uint32_t sys_getTimeStamp(struct System* sys)
{
(void) sys;
return (uint32_t) (*rb->current_tick) * 10;
}
static int16_t rb_soundbuf [MAX_SOUNDBUF_SIZE] IBSS_ATTR;
static int8_t temp_soundbuf[MAX_SOUNDBUF_SIZE] IBSS_ATTR;
static void ICODE_ATTR get_more(const void** start, size_t* size)
{
if(audio_sys->settings.sound_enabled)
{
audio_callback(audio_param, temp_soundbuf, audio_sys->settings.sound_bufsize);
/* convert xworld format (signed 8-bit) to rockbox format (signed 16-bit) */
for(int i = 0; i < audio_sys->settings.sound_bufsize; ++i)
{
rb_soundbuf[i] = temp_soundbuf[i] * 0x100;
}
*start = rb_soundbuf;
*size = audio_sys->settings.sound_bufsize;
}
else
{
*start = NULL;
*size = 0;
}
}
void sys_startAudio(struct System* sys, AudioCallback callback, void *param)
{
(void) sys;
audio_callback = callback;
audio_param = param;
audio_sys = sys;
rb->pcm_play_data(get_more, NULL, NULL, 0);
}
void sys_stopAudio(struct System* sys)
{
(void) sys;
rb->pcm_play_stop();
}
uint32_t sys_getOutputSampleRate(struct System* sys)
{
(void) sys;
return rb->mixer_get_frequency();
}
/* pretty non-reentrant here, but who cares? it's not like someone
would want to run two instances of the game on Rockbox! :D */
static uint32_t cur_delay;
static void* cur_param;
static TimerCallback user_callback;
static void timer_callback(void)
{
debug(DBG_SYS, "timer callback with delay of %d ms callback 0x%08x param 0x%08x", cur_delay, timer_callback, cur_param);
uint32_t ret = user_callback(cur_delay, cur_param);
if(ret != cur_delay)
{
cur_delay = ret;
rb->timer_register(1, NULL, TIMER_FREQ / 1000 * ret, timer_callback IF_COP(, CPU));
}
}
void *sys_addTimer(struct System* sys, uint32_t delay, TimerCallback callback, void *param)
{
(void) sys;
debug(DBG_SYS, "registering timer with delay of %d ms callback 0x%08x param 0x%08x", delay, callback, param);
user_callback = callback;
cur_delay = delay;
cur_param = param;
rb->timer_register(1, NULL, TIMER_FREQ / 1000 * delay, timer_callback IF_COP(, CPU));
return NULL;
}
void sys_removeTimer(struct System* sys, void *timerId)
{
(void) sys;
(void) timerId;
/* there's only one timer per game instance, so ignore both parameters */
rb->timer_unregister();
}
void *sys_createMutex(struct System* sys)
{
if(!sys)
error("sys is NULL!");
for(int i = 0; i < MAX_MUTEXES; ++i)
{
if(sys->mutex_bitfield & (1 << i))
{
rb->mutex_init(&sys->mutex_memory[i]);
sys->mutex_bitfield |= (1 << i);
return &sys->mutex_memory[i];
}
}
warning("Out of mutexes!");
return NULL;
}
void sys_destroyMutex(struct System* sys, void *mutex)
{
int mutex_number = ((char*)mutex - (char*)sys->mutex_memory) / sizeof(struct mutex); /* pointer arithmetic! check for bugs! */
sys->mutex_bitfield &= ~(1 << mutex_number);
}
void sys_lockMutex(struct System* sys, void *mutex)
{
(void) sys;
debug(DBG_SYS, "calling mutex_lock");
rb->mutex_lock((struct mutex*) mutex);
}
void sys_unlockMutex(struct System* sys, void *mutex)
{
(void) sys;
debug(DBG_SYS, "calling mutex_unlock");
rb->mutex_unlock((struct mutex*) mutex);
}
uint8_t* getOffScreenFramebuffer(struct System* sys)
{
(void) sys;
return NULL;
}
void *sys_get_buffer(struct System* sys, size_t sz)
{
if((signed int)sys->bytes_left - (signed int)sz >= 0)
{
void* ret = sys->membuf;
rb->memset(ret, 0, sz);
sys->membuf += sz;
return ret;
}
else
{
error("sys_get_buffer: out of memory!");
return NULL;
}
}
void MutexStack(struct MutexStack_t* s, struct System *stub, void *mutex)
{
s->sys = stub;
s->_mutex = mutex;
sys_lockMutex(s->sys, s->_mutex);
}
void MutexStack_destroy(struct MutexStack_t* s)
{
sys_unlockMutex(s->sys, s->_mutex);
}

146
apps/plugins/xworld/sys.h Normal file
View file

@ -0,0 +1,146 @@
/***************************************************************************
* __________ __ ___.
* 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.
*
***************************************************************************/
#ifndef __XWORLD_SYS_H__
#define __XWORLD_SYS_H__
#include "intern.h"
#define SYS_NEGATIVE_COLOR
#define NUM_COLORS 16
#define MAX_MUTEXES 16
#define SETTINGS_FILE "settings.xfg"
#define CODE_X 80
#define CODE_Y 36
enum {
DIR_LEFT = 1 << 0,
DIR_RIGHT = 1 << 1,
DIR_UP = 1 << 2,
DIR_DOWN = 1 << 3
};
struct PlayerInput {
uint8_t dirMask;
bool button;
bool code;
bool pause;
bool quit;
char lastChar;
bool save, load;
bool fastMode;
int8_t stateSlot;
};
struct keymapping_t {
int up;
int down;
int left;
int right;
#if (CONFIG_KEYPAD == SANSA_FUZEPLUS_PAD)
int upleft;
int upright;
int downleft;
int downright;
#endif
};
typedef void (*AudioCallback)(void *param, uint8_t *stream, int len);
typedef uint32_t (*TimerCallback)(uint32_t delay, void *param);
struct System {
struct mutex mutex_memory[MAX_MUTEXES];
uint16_t mutex_bitfield;
struct PlayerInput input;
fb_data palette[NUM_COLORS];
struct Engine* e;
struct keymapping_t keymap;
void *membuf;
size_t bytes_left;
bool loaded;
struct {
bool negative_enabled;
/*
scaling quality:
0: off
1: fast
2: good (color only)
*/
int scaling_quality;
/*
rotation:
0: off
1: cw
2: ccw
*/
int rotation_option;
bool showfps;
bool sound_enabled;
int sound_bufsize;
bool zoom;
} settings;
};
void sys_init(struct System*, const char *title);
void sys_menu(struct System*);
void sys_destroy(struct System*);
void sys_setPalette(struct System*, uint8_t s, uint8_t n, const uint8_t *buf);
void sys_copyRect(struct System*, uint16_t x, uint16_t y, uint16_t w, uint16_t h, const uint8_t *buf, uint32_t pitch);
void sys_processEvents(struct System*);
void sys_sleep(struct System*, uint32_t duration);
uint32_t sys_getTimeStamp(struct System*);
void sys_startAudio(struct System*, AudioCallback callback, void *param);
void sys_stopAudio(struct System*);
uint32_t sys_getOutputSampleRate(struct System*);
void *sys_addTimer(struct System*, uint32_t delay, TimerCallback callback, void *param);
void sys_removeTimer(struct System*, void *timerId);
void *sys_createMutex(struct System*);
void sys_destroyMutex(struct System*, void *mutex);
void sys_lockMutex(struct System*, void *mutex);
void sys_unlockMutex(struct System*, void *mutex);
/* a quick 'n dirty function to get some bytes in the audio buffer after zeroing them */
/* pretty much does the same thing as calloc, though much uglier and there's no free() */
void *sys_get_buffer(struct System*, size_t);
uint8_t* getOffScreenFramebuffer(struct System*);
struct MutexStack_t {
struct System *sys;
void *_mutex;
};
void MutexStack(struct MutexStack_t*, struct System*, void*);
void MutexStack_destroy(struct MutexStack_t*);
#endif

View file

@ -0,0 +1,82 @@
/***************************************************************************
* __________ __ ___.
* 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 "lib/pluginlib_exit.h"
#include <stdarg.h>
#include "util.h"
uint16_t g_debugMask;
#ifdef XWORLD_DEBUG
void debug_real(uint16_t cm, const char *msg, ...) {
#ifdef ROCKBOX_HAS_LOGF
char buf[1024];
if (cm & g_debugMask) {
va_list va;
va_start(va, msg);
rb->vsnprintf(buf, 1024, msg, va);
va_end(va);
LOGF("%s", buf);
}
#else
(void) cm;
(void) msg;
#endif
}
#endif
void error(const char *msg, ...) {
char buf[1024];
va_list va;
va_start(va, msg);
rb->vsnprintf(buf, 1024, msg, va);
va_end(va);
rb->splashf(HZ * 2, "ERROR: %s!", buf);
LOGF("ERROR: %s", buf);
exit(-1);
}
void warning(const char *msg, ...) {
char buf[1024];
va_list va;
va_start(va, msg);
rb->vsnprintf(buf, 1024, msg, va);
va_end(va);
rb->splashf(HZ * 2, "WARNING: %s!", buf);
LOGF("WARNING: %s", buf);
}
void string_lower(char *p) {
for (; *p; ++p) {
if (*p >= 'A' && *p <= 'Z') {
*p += 'a' - 'A';
}
}
}
void string_upper(char *p) {
for (; *p; ++p) {
if (*p >= 'a' && *p <= 'z') {
*p += 'A' - 'a';
}
}
}

View file

@ -0,0 +1,59 @@
/***************************************************************************
* __________ __ ___.
* 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.
*
***************************************************************************/
#ifndef __UTIL_H__
#define __UTIL_H__
#include "intern.h"
/* #define XWORLD_DEBUG */
#ifdef XWORLD_DEBUG
#define debug(m,f,...) debug_real(m, f, ##__VA_ARGS__)
#else
#define debug(m,f,...)
#endif
enum {
DBG_VM = 1 << 0,
DBG_BANK = 1 << 1,
DBG_VIDEO = 1 << 2,
DBG_SND = 1 << 3,
DBG_SER = 1 << 4,
DBG_INFO = 1 << 5,
DBG_RES = 1 << 6,
DBG_FILE = 1 << 7,
DBG_SYS = 1 << 8,
DBG_ENG = 1 << 9
};
extern uint16_t g_debugMask;
#ifdef XWORLD_DEBUG
extern void debug_real(uint16_t cm, const char *msg, ...);
#endif
extern void error(const char *msg, ...);
extern void warning(const char *msg, ...);
extern void string_lower(char *p);
extern void string_upper(char *p);
#endif

1141
apps/plugins/xworld/video.c Normal file

File diff suppressed because it is too large Load diff

127
apps/plugins/xworld/video.h Normal file
View file

@ -0,0 +1,127 @@
/***************************************************************************
* __________ __ ___.
* 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.
*
***************************************************************************/
#ifndef __VIDEO_H__
#define __VIDEO_H__
#include "intern.h"
struct StrEntry {
uint16_t id;
char *str;
};
#define MAX_POINTS 50
struct Polygon {
uint16_t bbw, bbh;
uint8_t numPoints;
struct Point points[MAX_POINTS];
};
void polygon_readVertices(struct Polygon*, const uint8_t *p, uint16_t zoom);
struct Resource;
struct Serializer;
struct System;
// This is used to detect the end of _stringsTableEng and _stringsTableDemo
#define END_OF_STRING_DICTIONARY 0xFFFF
// Special value when no palette change is necessary
#define NO_PALETTE_CHANGE_REQUESTED 0xFF
/* 320x200 pixels, with 2 pixels/byte */
#define VID_PAGE_SIZE ( 320 * 200 / 2 )
struct Video {
/* FW: static const uint8_t _font[];*/
/* FW: moved to video_data.c */
struct Resource *res;
struct System *sys;
uint8_t paletteIdRequested, currentPaletteId;
uint8_t *_pagePtrs[4];
uint8_t *page_data;
// I am almost sure that:
// _curPagePtr1 is the backbuffer
// _curPagePtr2 is the frontbuffer
// _curPagePtr3 is the background builder.
uint8_t *_curPagePtr1, *_curPagePtr2, *_curPagePtr3;
struct Polygon polygon;
int16_t _hliney;
//Precomputer division lookup table
uint16_t _interpTable[0x400];
struct Ptr _pData;
uint8_t *_dataBuf;
};
typedef void (*drawLine)(struct Video*, int16_t x1, int16_t x2, uint8_t col);
//Video(Resource *res, System *stub);
void video_create(struct Video*, struct Resource*, struct System*);
void video_init(struct Video* v);
void video_setDataBuffer(struct Video* v, uint8_t *dataBuf, uint16_t offset);
void video_readAndDrawPolygon(struct Video* v, uint8_t color, uint16_t zoom, const struct Point *pt);
void video_fillPolygon(struct Video* v, uint16_t color, uint16_t zoom, const struct Point *pt);
void video_readAndDrawPolygonHierarchy(struct Video* v, uint16_t zoom, const struct Point *pt);
int32_t video_calcStep(struct Video* v, const struct Point *p1, const struct Point *p2, uint16_t *dy);
void video_drawString(struct Video* v, uint8_t color, uint16_t x, uint16_t y, uint16_t strId);
void video_drawChar(struct Video* v, uint8_t c, uint16_t x, uint16_t y, uint8_t color, uint8_t *buf);
void video_drawPoint(struct Video* v, uint8_t color, int16_t x, int16_t y);
void video_drawLineBlend(struct Video* v, int16_t x1, int16_t x2, uint8_t color);
void video_drawLineN(struct Video* v, int16_t x1, int16_t x2, uint8_t color);
void video_drawLineP(struct Video* v, int16_t x1, int16_t x2, uint8_t color);
uint8_t *video_getPagePtr(struct Video* v, uint8_t page);
void video_changePagePtr1(struct Video* v, uint8_t page);
void video_fillPage(struct Video* v, uint8_t page, uint8_t color);
void video_copyPage(struct Video* v, uint8_t src, uint8_t dst, int16_t vscroll);
void video_copyPagePtr(struct Video* v, const uint8_t *src);
uint8_t *video_allocPage(struct Video* v);
void video_changePal(struct Video* v, uint8_t pal);
void video_updateDisplay(struct Video* v, uint8_t page);
void video_saveOrLoad(struct Video* v, struct Serializer *ser);
#define TRACE_PALETTE 0
#define TRACE_FRAMEBUFFER 0
#if TRACE_FRAMEBUFFER
void video_dumpFrameBuffer(struct Video* v, uint8_t *src, uint8_t *dst, int x, int y);
void video_dumpFrameBuffers(struct Video* v, char* comment);
#endif
#define TRACE_BG_BUFFER 0
#if TRACE_BG_BUFFER
void video_dumpBackGroundBuffer(struct Video* v);
#endif
#endif

View file

@ -0,0 +1,271 @@
/***************************************************************************
* __________ __ ___.
* 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 "video.h"
#include "video_data.h"
#include "stdint.h"
/* this font is based off 10-Fixed.bdf with lowercase characters
from 09-Fixed.bdf and a handcrafted copyright symbol */
uint8_t video_font[FONT_SIZE] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* ' ' */
0x20, 0x20, 0x20, 0x20, 0x20, 0x00, 0x20, 0x00, /* '!' */
0x50, 0x50, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, /* '"' */
0x50, 0x50, 0xF8, 0x50, 0xF8, 0x50, 0x50, 0x00, /* '#' */
0x20, 0x70, 0xA0, 0x70, 0x28, 0x70, 0x20, 0x00, /* '$' */
0x48, 0xA8, 0x50, 0x20, 0x50, 0xA8, 0x90, 0x00, /* '%' */
0x40, 0xA0, 0xA0, 0x40, 0xA8, 0x90, 0x68, 0x00, /* '&' */
0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, /* ''' */
0x10, 0x20, 0x40, 0x40, 0x40, 0x20, 0x10, 0x00, /* '(' */
0x40, 0x20, 0x10, 0x10, 0x10, 0x20, 0x40, 0x00, /* ')' */
0x00, 0x88, 0x50, 0xF8, 0x50, 0x88, 0x00, 0x00, /* '*' */
0x00, 0x20, 0x20, 0xF8, 0x20, 0x20, 0x00, 0x00, /* '+' */
0x00, 0x00, 0x00, 0x00, 0x30, 0x20, 0x40, 0x00, /* ',' */
0x00, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x00, 0x00, /* '-' */
0x00, 0x00, 0x00, 0x00, 0x20, 0x50, 0x20, 0x00, /* '.' */
0x08, 0x08, 0x10, 0x20, 0x40, 0x80, 0x80, 0x00, /* '/' */
0x20, 0x50, 0x88, 0x88, 0x88, 0x50, 0x20, 0x00, /* '0' */
0x20, 0x60, 0xA0, 0x20, 0x20, 0x20, 0xF8, 0x00, /* '1' */
0x70, 0x88, 0x08, 0x30, 0x40, 0x80, 0xF8, 0x00, /* '2' */
0xF8, 0x08, 0x10, 0x30, 0x08, 0x88, 0x70, 0x00, /* '3' */
0x10, 0x30, 0x50, 0x90, 0xF8, 0x10, 0x10, 0x00, /* '4' */
0xF8, 0x80, 0xB0, 0xC8, 0x08, 0x88, 0x70, 0x00, /* '5' */
0x30, 0x40, 0x80, 0xB0, 0xC8, 0x88, 0x70, 0x00, /* '6' */
0xF8, 0x08, 0x10, 0x10, 0x20, 0x40, 0x40, 0x00, /* '7' */
0x70, 0x88, 0x88, 0x70, 0x88, 0x88, 0x70, 0x00, /* '8' */
0x70, 0x88, 0x98, 0x68, 0x08, 0x10, 0x60, 0x00, /* '9' */
0x00, 0x60, 0x60, 0x00, 0x60, 0x60, 0x00, 0x00, /* ':' */
0x00, 0x30, 0x30, 0x00, 0x30, 0x20, 0x40, 0x00, /* ';' */
0x08, 0x10, 0x20, 0x40, 0x20, 0x10, 0x08, 0x00, /* '<' */
0x00, 0x00, 0xF8, 0x00, 0xF8, 0x00, 0x00, 0x00, /* '=' */
0x40, 0x20, 0x10, 0x08, 0x10, 0x20, 0x40, 0x00, /* '>' */
0x70, 0x88, 0x10, 0x20, 0x20, 0x00, 0x20, 0x00, /* '?' */
0x40, 0x20, 0x10, 0x10, 0x10, 0x20, 0x40, 0x00, /* ')' */
0x20, 0x50, 0x88, 0x88, 0xF8, 0x88, 0x88, 0x00, /* 'A' */
0xF0, 0x88, 0x88, 0xF0, 0x88, 0x88, 0xF0, 0x00, /* 'B' */
0x70, 0x88, 0x80, 0x80, 0x80, 0x88, 0x70, 0x00, /* 'C' */
0xF0, 0x88, 0x88, 0x88, 0x88, 0x88, 0xF0, 0x00, /* 'D' */
0xF8, 0x80, 0x80, 0xF0, 0x80, 0x80, 0xF8, 0x00, /* 'E' */
0xF8, 0x80, 0x80, 0xF0, 0x80, 0x80, 0x80, 0x00, /* 'F' */
0x70, 0x88, 0x80, 0x80, 0x98, 0x88, 0x70, 0x00, /* 'G' */
0x88, 0x88, 0x88, 0xF8, 0x88, 0x88, 0x88, 0x00, /* 'H' */
0x70, 0x20, 0x20, 0x20, 0x20, 0x20, 0x70, 0x00, /* 'I' */
0x38, 0x10, 0x10, 0x10, 0x10, 0x90, 0x60, 0x00, /* 'J' */
0x88, 0x90, 0xA0, 0xC0, 0xA0, 0x90, 0x88, 0x00, /* 'K' */
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xF8, 0x00, /* 'L' */
0x88, 0x88, 0xD8, 0xA8, 0x88, 0x88, 0x88, 0x00, /* 'M' */
0x88, 0x88, 0xC8, 0xA8, 0x98, 0x88, 0x88, 0x00, /* 'N' */
0x70, 0x88, 0x88, 0x88, 0x88, 0x88, 0x70, 0x00, /* 'O' */
0xF0, 0x88, 0x88, 0xF0, 0x80, 0x80, 0x80, 0x00, /* 'P' */
0x70, 0x88, 0x88, 0x88, 0x88, 0xA8, 0x70, 0x00, /* 'Q' */
0xF0, 0x88, 0x88, 0xF0, 0xA0, 0x90, 0x88, 0x00, /* 'R' */
0x70, 0x88, 0x80, 0x70, 0x08, 0x88, 0x70, 0x00, /* 'S' */
0xF8, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x00, /* 'T' */
0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x70, 0x00, /* 'U' */
0x88, 0x88, 0x88, 0x50, 0x50, 0x50, 0x20, 0x00, /* 'V' */
0x88, 0x88, 0x88, 0xA8, 0xA8, 0xD8, 0x88, 0x00, /* 'W' */
0x88, 0x88, 0x50, 0x20, 0x50, 0x88, 0x88, 0x00, /* 'X' */
0x88, 0x88, 0x50, 0x20, 0x20, 0x20, 0x20, 0x00, /* 'Y' */
0xF8, 0x08, 0x10, 0x20, 0x40, 0x80, 0xF8, 0x00, /* 'Z' */
0x70, 0x40, 0x40, 0x40, 0x40, 0x70, 0x00, 0x00, /* '[' */
0x80, 0x80, 0x40, 0x20, 0x10, 0x10, 0x00, 0x00, /* '\' */
0x70, 0x10, 0x10, 0x10, 0x10, 0x70, 0x00, 0x00, /* ']' */
0x20, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* '^' */
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x00, /* '_' */
0x40, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* '`' */
0x00, 0x00, 0x70, 0x08, 0x78, 0x88, 0x78, 0x00, /* 'a' */
0x80, 0x80, 0xB0, 0xC8, 0x88, 0xC8, 0xB0, 0x00, /* 'b' */
0x00, 0x00, 0x70, 0x88, 0x80, 0x88, 0x70, 0x00, /* 'c' */
0x08, 0x08, 0x68, 0x98, 0x88, 0x98, 0x68, 0x00, /* 'd' */
0x00, 0x00, 0x70, 0x88, 0xF8, 0x80, 0x70, 0x00, /* 'e' */
0x30, 0x48, 0x40, 0xF0, 0x40, 0x40, 0x40, 0x00, /* 'f' */
0x00, 0x00, 0x60, 0x90, 0x90, 0x70, 0x10, 0x60, /* 'g' */
0x80, 0x80, 0xB0, 0xC8, 0x88, 0x88, 0x88, 0x00, /* 'h' */
0x20, 0x00, 0x60, 0x20, 0x20, 0x20, 0x70, 0x00, /* 'i' */
0x20, 0x00, 0x60, 0x20, 0x20, 0x20, 0xA0, 0x40, /* 'j' */
0x80, 0x80, 0x88, 0x90, 0xE0, 0x90, 0x88, 0x00, /* 'k' */
0x60, 0x20, 0x20, 0x20, 0x20, 0x20, 0x70, 0x00, /* 'l' */
0x00, 0x00, 0xD0, 0xA8, 0xA8, 0xA8, 0x88, 0x00, /* 'm' */
0x00, 0x00, 0xB0, 0xC8, 0x88, 0x88, 0x88, 0x00, /* 'n' */
0x00, 0x00, 0x70, 0x88, 0x88, 0x88, 0x70, 0x00, /* 'o' */
0x00, 0x00, 0xE0, 0x90, 0x90, 0xE0, 0x80, 0x80, /* 'p' */
0x00, 0x00, 0x70, 0x90, 0x90, 0x70, 0x10, 0x10, /* 'q' */
0x00, 0x00, 0xB0, 0xC8, 0x80, 0x80, 0x80, 0x00, /* 'r' */
0x00, 0x00, 0x70, 0x80, 0x70, 0x08, 0xF0, 0x00, /* 's' */
0x40, 0x40, 0xF0, 0x40, 0x40, 0x48, 0x30, 0x00, /* 't' */
0x00, 0x00, 0x88, 0x88, 0x88, 0x98, 0x68, 0x00, /* 'u' */
0x00, 0x00, 0x88, 0x88, 0x50, 0x50, 0x20, 0x00, /* 'v' */
0x00, 0x00, 0x88, 0x88, 0xA8, 0xA8, 0x50, 0x00, /* 'w' */
0x00, 0x00, 0x88, 0x50, 0x20, 0x50, 0x88, 0x00, /* 'x' */
0x00, 0x00, 0x90, 0x90, 0x90, 0x70, 0x90, 0x60, /* 'y' */
0x00, 0x00, 0xF8, 0x10, 0x20, 0x40, 0xF8, 0x00, /* 'z' */
0x18, 0x20, 0x10, 0x60, 0x10, 0x20, 0x18, 0x00, /* '{' */
0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0x00, /* cursor */
0x38, 0x54, 0xAA, 0xA2, 0xAA, 0x54, 0x38, 0x00, /* copyright symbol */
0x70, 0x88, 0x88, 0x88, 0x88, 0x50, 0xD8, 0x00, /* omega */
0x00, 0xA0, 0x10, 0x80, 0x10, 0x80, 0x50, 0x00, /* DEL */
};
struct StrEntry video_stringsTableEng[MAX_STRING_TABLE_SIZE] = {
{ 0x001, "B A N A N A 2000" },
{ 0x002, "Copyright } 2014 Banana Corporation \nGPLv2\n\nBUNIX Revision 3.14" },
{ 0x003, "1" },
{ 0x004, "3" },
{ 0x005, "." },
{ 0x006, "a" },
{ 0x007, "@" },
{ 0x008, "BANANA 2000" },
{ 0x00A, "R" },
{ 0x00B, "U" },
{ 0x00C, "N" },
{ 0x00D, "P" },
{ 0x00E, "R" },
{ 0x00F, "O" },
{ 0x010, "J" },
{ 0x011, "E" },
{ 0x012, "C" },
{ 0x013, "T" },
{ 0x014, "Fields 100.05Mf OK" },
{ 0x015, "Lines of Flux % 14.077 OK" },
{ 0x016, "IONS OK" },
{ 0x017, " %%%ddd OK" },
{ 0x018, "TEMP ok" },
{ 0x019, "EXECUTE" },
{ 0x01A, "V= 24%\nG: 1.05\n\nMG: 177.2l\n\nOPT: G>\n\n Field:\nI: OFF\nII: ON\nIII: ON\n\np~: I\n" },
{ 0x01B, "on" },
{ 0x01C, "-" },
{ 0x021, "|" },
{ 0x022, "--- Simulation ---" },
{ 0x023, " TEST WILL START IN SECONDS" },
{ 0x024, " 20" },
{ 0x025, " 19" },
{ 0x026, " 18" },
{ 0x027, " 4" },
{ 0x028, " 3" },
{ 0x029, " 2" },
{ 0x02A, " 1" },
{ 0x02B, " 0" },
{ 0x02C, "C A U T I O N" },
{ 0x031, "- Test 0:\nGenerate electron beam\n" },
{ 0x032, "- Test 1:\nCalculating flux coefficient\n" },
{ 0x033, "- Test 2:\nIncrease magnetic field\n" },
{ 0x034, "R E S U L T S" },
{ 0x035, "- NOTE:\nChances of producing:\n Anti-matter: 34 %\n Neutrino 71: 4 %\n Positron 34: 99 %\n" },
{ 0x036, " Continue Test y/n ?" },
{ 0x037, "Are You Sure?" },
{ 0x038, "Setting Configuration\n of accelerator\n'Verified'" },
{ 0x039, " Continue ?" },
{ 0x03C, "T___T" },
{ 0x03D, "OOO ~" },
{ 0x03E, ".40X13DD" },
{ 0x03F, "ferfxwre" },
{ 0x040, "Trfor 25%" },
{ 0x041, "32% 56% GOOD" },
{ 0x042, "E=2.7182818289" },
{ 0x043, "G=330.01" },
{ 0x044, "+" },
{ 0x045, "*" },
{ 0x046, "% 234" },
{ 0x047, "Gorwle 12" },
{ 0x048, "[[[[" },
{ 0x049, "Elephine Soft" },
{ 0x04A, "By Many talented People" },
{ 0x04B, " 4" },
{ 0x04C, " 16" },
{ 0x12C, "0" },
{ 0x12D, "1" },
{ 0x12E, "2" },
{ 0x12F, "3" },
{ 0x130, "4" },
{ 0x131, "5" },
{ 0x132, "6" },
{ 0x133, "7" },
{ 0x134, "8" },
{ 0x135, "9" },
{ 0x136, "A" },
{ 0x137, "B" },
{ 0x138, "C" },
{ 0x139, "D" },
{ 0x13A, "E" },
{ 0x13B, "F" },
{ 0x13C, " LEVEL CODE:" },
{ 0x13D, " PRESS ANY KEY TO CONTINUE" },
{ 0x13E, " ENTER CODE" },
{ 0x13F, " CODE NOT VALID!!" },
{ 0x140, "AN NULER" },
{ 0x141, " ??????\n\n\n\n\n\n\n\n\nANY KEY TO CONTINUE" },
{ 0x142, " ENTER THE CODE CORRELATING TO\n POSITION\n ON THE DECODER WHEEL" },
{ 0x143, " LOAD..." },
{ 0x144, " ERROR" },
{ 0x15E, "LDKD" },
{ 0x15F, "HTDC" },
{ 0x160, "CLLD" },
{ 0x161, "FXLC" },
{ 0x162, "KRFK" },
{ 0x163, "XDDJ" },
{ 0x164, "LBKG" },
{ 0x165, "KLFB" },
{ 0x166, "TTCT" },
{ 0x167, "DDRX" },
{ 0x168, "TBHK" },
{ 0x169, "BRTD" },
{ 0x16A, "CKJL" },
{ 0x16B, "LFCK" },
{ 0x16C, "BFLX" },
{ 0x16D, "XJRT" },
{ 0x16E, "HRTB" },
{ 0x16F, "HBHK" },
{ 0x170, "JCGB" },
{ 0x171, "HHFL" },
{ 0x172, "TFBB" },
{ 0x173, "TXHF" },
{ 0x174, "JHJL" },
{ 0x181, " " },
{ 0x182, " " },
{ 0x183, " " },
{ 0x184, " " },
{ 0x185, " " },
{ 0x186, " " },
{ 0x187, " " },
{ 0x188, " " },
{ 0x18B, " " },
{ 0x18C, " " },
{ 0x18D, " " },
{ 0x18E, " " },
{ 0x258, " " },
{ 0x259, " " },
{ 0x25A, " " },
{ 0x25B, " " },
{ 0x25C, " " },
{ 0x25D, " " },
{ 0x263, " " },
{ 0x264, " " },
{ 0x265, " " },
{ 0x190, "Hello Master." },
{ 0x191, "Identifiy confirmed.\nAccess granted." },
{ 0x192, " ACCESSING" },
{ 0x193, " " },
{ 0x194, "y\n" },
{ 0x193, "!!!\n" },
{ END_OF_STRING_DICTIONARY, "" }
};

View file

@ -0,0 +1,29 @@
/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2014 Franklin Wei
*
* 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 "video.h"
#include "stdint.h"
#define FONT_SIZE (8 * (0x80 - ' '))
#define MAX_STRING_TABLE_SIZE 255
extern uint8_t video_font[FONT_SIZE];
extern struct StrEntry video_stringsTableEng[MAX_STRING_TABLE_SIZE];

763
apps/plugins/xworld/vm.c Normal file
View file

@ -0,0 +1,763 @@
/***************************************************************************
* __________ __ ___.
* 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 "vm.h"
#include "mixer.h"
#include "resource.h"
#include "video.h"
#include "serializer.h"
#include "sfxplayer.h"
#include "sys.h"
#include "parts.h"
#include "file.h"
static const uint16_t vm_frequenceTable[] = {
0x0CFF, 0x0DC3, 0x0E91, 0x0F6F, 0x1056, 0x114E, 0x1259, 0x136C,
0x149F, 0x15D9, 0x1726, 0x1888, 0x19FD, 0x1B86, 0x1D21, 0x1EDE,
0x20AB, 0x229C, 0x24B3, 0x26D7, 0x293F, 0x2BB2, 0x2E4C, 0x3110,
0x33FB, 0x370D, 0x3A43, 0x3DDF, 0x4157, 0x4538, 0x4998, 0x4DAE,
0x5240, 0x5764, 0x5C9A, 0x61C8, 0x6793, 0x6E19, 0x7485, 0x7BBD
};
void vm_create(struct VirtualMachine* m, struct Mixer *mix, struct Resource* res, struct SfxPlayer *ply, struct Video *vid, struct System *stub)
{
m->res = res;
m->video = vid;
m->sys = stub;
m->mixer = mix;
m->player = ply;
}
void vm_init(struct VirtualMachine* m) {
rb->memset(m->vmVariables, 0, sizeof(m->vmVariables));
m->vmVariables[0x54] = 0x81;
m->vmVariables[VM_VARIABLE_RANDOM_SEED] = *rb->current_tick;
m->_fastMode = false;
m->player->_markVar = &m->vmVariables[VM_VARIABLE_MUS_MARK];
}
void vm_op_movConst(struct VirtualMachine* m) {
uint8_t variableId = scriptPtr_fetchByte(&m->_scriptPtr);
int16_t value = scriptPtr_fetchWord(&m->_scriptPtr);
debug(DBG_VM, "vm_op_movConst(0x%02X, %d)", variableId, value);
m->vmVariables[variableId] = value;
}
void vm_op_mov(struct VirtualMachine* m) {
uint8_t dstVariableId = scriptPtr_fetchByte(&m->_scriptPtr);
uint8_t srcVariableId = scriptPtr_fetchByte(&m->_scriptPtr);
debug(DBG_VM, "vm_op_mov(0x%02X, 0x%02X)", dstVariableId, srcVariableId);
m->vmVariables[dstVariableId] = m->vmVariables[srcVariableId];
}
void vm_op_add(struct VirtualMachine* m) {
uint8_t dstVariableId = scriptPtr_fetchByte(&m->_scriptPtr);
uint8_t srcVariableId = scriptPtr_fetchByte(&m->_scriptPtr);
debug(DBG_VM, "vm_op_add(0x%02X, 0x%02X)", dstVariableId, srcVariableId);
m->vmVariables[dstVariableId] += m->vmVariables[srcVariableId];
}
void vm_op_addConst(struct VirtualMachine* m) {
if (m->res->currentPartId == 0x3E86 && m->_scriptPtr.pc == m->res->segBytecode + 0x6D48) {
warning("vm_op_addConst() hack for non-stop looping gun sound bug");
// the script 0x27 slot 0x17 doesn't stop the gun sound from looping, I
// don't really know why ; for now, let's play the 'stopping sound' like
// the other scripts do
// (0x6D43) jmp(0x6CE5)
// (0x6D46) break
// (0x6D47) VAR(6) += -50
vm_snd_playSound(m, 0x5B, 1, 64, 1);
}
uint8_t variableId = scriptPtr_fetchByte(&m->_scriptPtr);
int16_t value = scriptPtr_fetchWord(&m->_scriptPtr);
debug(DBG_VM, "vm_op_addConst(0x%02X, %d)", variableId, value);
m->vmVariables[variableId] += value;
}
void vm_op_call(struct VirtualMachine* m) {
uint16_t offset = scriptPtr_fetchWord(&m->_scriptPtr);
uint8_t sp = m->_stackPtr;
debug(DBG_VM, "vm_op_call(0x%X)", offset);
m->_scriptStackCalls[sp] = m->_scriptPtr.pc - m->res->segBytecode;
if (m->_stackPtr == 0xFF) {
error("vm_op_call() ec=0x%X stack overflow", 0x8F);
}
++m->_stackPtr;
m->_scriptPtr.pc = m->res->segBytecode + offset ;
}
void vm_op_ret(struct VirtualMachine* m) {
debug(DBG_VM, "vm_op_ret()");
if (m->_stackPtr == 0) {
error("vm_op_ret() ec=0x%X stack underflow", 0x8F);
}
--m->_stackPtr;
uint8_t sp = m->_stackPtr;
m->_scriptPtr.pc = m->res->segBytecode + m->_scriptStackCalls[sp];
}
void vm_op_pauseThread(struct VirtualMachine* m) {
debug(DBG_VM, "vm_op_pauseThread()");
m->gotoNextThread = true;
}
void vm_op_jmp(struct VirtualMachine* m) {
uint16_t pcOffset = scriptPtr_fetchWord(&m->_scriptPtr);
debug(DBG_VM, "vm_op_jmp(0x%02X)", pcOffset);
m->_scriptPtr.pc = m->res->segBytecode + pcOffset;
}
void vm_op_setSetVect(struct VirtualMachine* m) {
uint8_t threadId = scriptPtr_fetchByte(&m->_scriptPtr);
uint16_t pcOffsetRequested = scriptPtr_fetchWord(&m->_scriptPtr);
debug(DBG_VM, "vm_op_setSetVect(0x%X, 0x%X)", threadId, pcOffsetRequested);
m->threadsData[REQUESTED_PC_OFFSET][threadId] = pcOffsetRequested;
}
void vm_op_jnz(struct VirtualMachine* m) {
uint8_t i = scriptPtr_fetchByte(&m->_scriptPtr);
debug(DBG_VM, "vm_op_jnz(0x%02X)", i);
--m->vmVariables[i];
if (m->vmVariables[i] != 0) {
vm_op_jmp(m);
} else {
scriptPtr_fetchWord(&m->_scriptPtr);
}
}
#define BYPASS_PROTECTION
void vm_op_condJmp(struct VirtualMachine* m) {
//printf("Jump : %X \n",m->_scriptPtr.pc-m->res->segBytecode);
//FCS Whoever wrote this is patching the bytecode on the fly. This is ballzy !!
#ifdef BYPASS_PROTECTION
if (m->res->currentPartId == GAME_PART_FIRST && m->_scriptPtr.pc == m->res->segBytecode + 0xCB9) {
// (0x0CB8) condJmp(0x80, VAR(41), VAR(30), 0xCD3)
*(m->_scriptPtr.pc + 0x00) = 0x81;
*(m->_scriptPtr.pc + 0x03) = 0x0D;
*(m->_scriptPtr.pc + 0x04) = 0x24;
// (0x0D4E) condJmp(0x4, VAR(50), 6, 0xDBC)
*(m->_scriptPtr.pc + 0x99) = 0x0D;
*(m->_scriptPtr.pc + 0x9A) = 0x5A;
debug(DBG_VM, "vm_op_condJmp() bypassing protection");
debug(DBG_VM, "bytecode has been patched");
//vm_bypassProtection(m);
}
#endif
uint8_t opcode = scriptPtr_fetchByte(&m->_scriptPtr);
int16_t b = m->vmVariables[scriptPtr_fetchByte(&m->_scriptPtr)];
uint8_t c = scriptPtr_fetchByte(&m->_scriptPtr);
int16_t a;
if (opcode & 0x80) {
a = m->vmVariables[c];
} else if (opcode & 0x40) {
a = c * 256 + scriptPtr_fetchByte(&m->_scriptPtr);
} else {
a = c;
}
debug(DBG_VM, "vm_op_condJmp(%d, 0x%02X, 0x%02X)", opcode, b, a);
// Check if the conditional value is met.
bool expr = false;
switch (opcode & 7) {
case 0: // jz
expr = (b == a);
break;
case 1: // jnz
expr = (b != a);
break;
case 2: // jg
expr = (b > a);
break;
case 3: // jge
expr = (b >= a);
break;
case 4: // jl
expr = (b < a);
break;
case 5: // jle
expr = (b <= a);
break;
default:
warning("vm_op_condJmp() invalid condition %d", (opcode & 7));
break;
}
if (expr) {
vm_op_jmp(m);
} else {
scriptPtr_fetchWord(&m->_scriptPtr);
}
}
void vm_op_setPalette(struct VirtualMachine* m) {
uint16_t paletteId = scriptPtr_fetchWord(&m->_scriptPtr);
debug(DBG_VM, "vm_op_changePalette(%d)", paletteId);
m->video->paletteIdRequested = paletteId >> 8;
}
void vm_op_resetThread(struct VirtualMachine* m) {
uint8_t threadId = scriptPtr_fetchByte(&m->_scriptPtr);
uint8_t i = scriptPtr_fetchByte(&m->_scriptPtr);
// FCS: WTF, this is cryptic as hell !!
// int8_t n = (i & 0x3F) - threadId; //0x3F = 0011 1111
// The following is so much clearer
//Make sure i is within [0-VM_NUM_THREADS-1]
i = i & (VM_NUM_THREADS - 1) ;
int8_t n = i - threadId;
if (n < 0) {
warning("vm_op_m->resetThread() ec=0x%X (n < 0)", 0x880);
return;
}
++n;
uint8_t a = scriptPtr_fetchByte(&m->_scriptPtr);
debug(DBG_VM, "vm_op_m->resetThread(%d, %d, %d)", threadId, i, a);
if (a == 2) {
uint16_t *p = &m->threadsData[REQUESTED_PC_OFFSET][threadId];
while (n--) {
*p++ = 0xFFFE;
}
} else if (a < 2) {
uint8_t *p = &m->vmIsChannelActive[REQUESTED_STATE][threadId];
while (n--) {
*p++ = a;
}
}
}
void vm_op_selectVideoPage(struct VirtualMachine* m) {
uint8_t frameBufferId = scriptPtr_fetchByte(&m->_scriptPtr);
debug(DBG_VM, "vm_op_selectVideoPage(%d)", frameBufferId);
video_changePagePtr1(m->video, frameBufferId);
}
void vm_op_fillVideoPage(struct VirtualMachine* m) {
uint8_t pageId = scriptPtr_fetchByte(&m->_scriptPtr);
uint8_t color = scriptPtr_fetchByte(&m->_scriptPtr);
debug(DBG_VM, "vm_op_fillVideoPage(%d, %d)", pageId, color);
video_fillPage(m->video, pageId, color);
}
void vm_op_copyVideoPage(struct VirtualMachine* m) {
uint8_t srcPageId = scriptPtr_fetchByte(&m->_scriptPtr);
uint8_t dstPageId = scriptPtr_fetchByte(&m->_scriptPtr);
debug(DBG_VM, "vm_op_copyVideoPage(%d, %d)", srcPageId, dstPageId);
video_copyPage(m->video, srcPageId, dstPageId, m->vmVariables[VM_VARIABLE_SCROLL_Y]);
}
static uint32_t lastTimeStamp = 0;
void vm_op_blitFramebuffer(struct VirtualMachine* m) {
uint8_t pageId = scriptPtr_fetchByte(&m->_scriptPtr);
debug(DBG_VM, "vm_op_blitFramebuffer(%d)", pageId);
vm_inp_handleSpecialKeys(m);
/* Nasty hack....was this present in the original assembly ??!! */
if (m->res->currentPartId == GAME_PART_FIRST && m->vmVariables[0x67] == 1)
m->vmVariables[0xDC] = 0x21;
if (!m->_fastMode) {
int32_t delay = sys_getTimeStamp(m->sys) - lastTimeStamp;
int32_t timeToSleep = m->vmVariables[VM_VARIABLE_PAUSE_SLICES] * 20 - delay;
/* The bytecode will set m->vmVariables[VM_VARIABLE_PAUSE_SLICES] from 1 to 5 */
/* The virtual machine hence indicates how long the image should be displayed. */
if (timeToSleep > 0)
{
sys_sleep(m->sys, timeToSleep);
}
lastTimeStamp = sys_getTimeStamp(m->sys);
}
/* WTF ? */
m->vmVariables[0xF7] = 0;
video_updateDisplay(m->video, pageId);
}
void vm_op_killThread(struct VirtualMachine* m) {
debug(DBG_VM, "vm_op_killThread()");
m->_scriptPtr.pc = m->res->segBytecode + 0xFFFF;
m->gotoNextThread = true;
}
void vm_op_drawString(struct VirtualMachine* m) {
uint16_t stringId = scriptPtr_fetchWord(&m->_scriptPtr);
uint16_t x = scriptPtr_fetchByte(&m->_scriptPtr);
uint16_t y = scriptPtr_fetchByte(&m->_scriptPtr);
uint16_t color = scriptPtr_fetchByte(&m->_scriptPtr);
debug(DBG_VM, "vm_op_drawString(0x%03X, %d, %d, %d)", stringId, x, y, color);
video_drawString(m->video, color, x, y, stringId);
}
void vm_op_sub(struct VirtualMachine* m) {
uint8_t i = scriptPtr_fetchByte(&m->_scriptPtr);
uint8_t j = scriptPtr_fetchByte(&m->_scriptPtr);
debug(DBG_VM, "vm_op_sub(0x%02X, 0x%02X)", i, j);
m->vmVariables[i] -= m->vmVariables[j];
}
void vm_op_and(struct VirtualMachine* m) {
uint8_t variableId = scriptPtr_fetchByte(&m->_scriptPtr);
uint16_t n = scriptPtr_fetchWord(&m->_scriptPtr);
debug(DBG_VM, "vm_op_and(0x%02X, %d)", variableId, n);
m->vmVariables[variableId] = (uint16_t)m->vmVariables[variableId] & n;
}
void vm_op_or(struct VirtualMachine* m) {
uint8_t variableId = scriptPtr_fetchByte(&m->_scriptPtr);
uint16_t value = scriptPtr_fetchWord(&m->_scriptPtr);
debug(DBG_VM, "vm_op_or(0x%02X, %d)", variableId, value);
m->vmVariables[variableId] = (uint16_t)m->vmVariables[variableId] | value;
}
void vm_op_shl(struct VirtualMachine* m) {
uint8_t variableId = scriptPtr_fetchByte(&m->_scriptPtr);
uint16_t leftShiftValue = scriptPtr_fetchWord(&m->_scriptPtr);
debug(DBG_VM, "vm_op_shl(0x%02X, %d)", variableId, leftShiftValue);
m->vmVariables[variableId] = (uint16_t)m->vmVariables[variableId] << leftShiftValue;
}
void vm_op_shr(struct VirtualMachine* m) {
uint8_t variableId = scriptPtr_fetchByte(&m->_scriptPtr);
uint16_t rightShiftValue = scriptPtr_fetchWord(&m->_scriptPtr);
debug(DBG_VM, "vm_op_shr(0x%02X, %d)", variableId, rightShiftValue);
m->vmVariables[variableId] = (uint16_t)m->vmVariables[variableId] >> rightShiftValue;
}
void vm_op_playSound(struct VirtualMachine* m) {
uint16_t resourceId = scriptPtr_fetchWord(&m->_scriptPtr);
uint8_t freq = scriptPtr_fetchByte(&m->_scriptPtr);
uint8_t vol = scriptPtr_fetchByte(&m->_scriptPtr);
uint8_t channel = scriptPtr_fetchByte(&m->_scriptPtr);
debug(DBG_VM, "vm_op_playSound(0x%X, %d, %d, %d)", resourceId, freq, vol, channel);
vm_snd_playSound(m, resourceId, freq, vol, channel);
}
void vm_op_updateMemList(struct VirtualMachine* m) {
uint16_t resourceId = scriptPtr_fetchWord(&m->_scriptPtr);
debug(DBG_VM, "vm_op_updateMemList(%d)", resourceId);
if (resourceId == 0) {
player_stop(m->player);
mixer_stopAll(m->mixer);
res_invalidateRes(m->res);
} else {
res_loadPartsOrMemoryEntry(m->res, resourceId);
}
}
void vm_op_playMusic(struct VirtualMachine* m) {
uint16_t resNum = scriptPtr_fetchWord(&m->_scriptPtr);
uint16_t delay = scriptPtr_fetchWord(&m->_scriptPtr);
uint8_t pos = scriptPtr_fetchByte(&m->_scriptPtr);
debug(DBG_VM, "vm_op_playMusic(0x%X, %d, %d)", resNum, delay, pos);
vm_snd_playMusic(m, resNum, delay, pos);
}
void vm_initForPart(struct VirtualMachine* m, uint16_t partId) {
player_stop(m->player);
mixer_stopAll(m->mixer);
/* WTF is that ? */
m->vmVariables[0xE4] = 0x14;
res_setupPart(m->res, partId);
/* Set all thread to inactive (pc at 0xFFFF or 0xFFFE ) */
rb->memset((uint8_t *)m->threadsData, 0xFF, sizeof(m->threadsData));
rb->memset((uint8_t *)m->vmIsChannelActive, 0, sizeof(m->vmIsChannelActive));
int firstThreadId = 0;
m->threadsData[PC_OFFSET][firstThreadId] = 0;
}
/*
This is called every frames in the infinite loop.
*/
void vm_checkThreadRequests(struct VirtualMachine* m) {
/* Check if a part switch has been requested. */
if (m->res->requestedNextPart != 0) {
vm_initForPart(m, m->res->requestedNextPart);
m->res->requestedNextPart = 0;
}
/* Check if a state update has been requested for any thread during the previous VM execution: */
/* - Pause */
/* - Jump */
/* JUMP: */
/* Note: If a jump has been requested, the jump destination is stored */
/* in m->threadsData[REQUESTED_PC_OFFSET]. Otherwise m->threadsData[REQUESTED_PC_OFFSET] == 0xFFFF */
/* PAUSE: */
/* Note: If a pause has been requested it is stored in m->vmIsChannelActive[REQUESTED_STATE][i] */
for (int threadId = 0; threadId < VM_NUM_THREADS; threadId++) {
m->vmIsChannelActive[CURR_STATE][threadId] = m->vmIsChannelActive[REQUESTED_STATE][threadId];
uint16_t n = m->threadsData[REQUESTED_PC_OFFSET][threadId];
if (n != VM_NO_SETVEC_REQUESTED) {
m->threadsData[PC_OFFSET][threadId] = (n == 0xFFFE) ? VM_INACTIVE_THREAD : n;
m->threadsData[REQUESTED_PC_OFFSET][threadId] = VM_NO_SETVEC_REQUESTED;
}
}
}
void vm_hostFrame(struct VirtualMachine* m) {
/* Run the Virtual Machine for every active threads (one vm frame). */
/* Inactive threads are marked with a thread instruction pointer set to 0xFFFF (VM_INACTIVE_THREAD). */
/* A thread must feature a break opcode so the interpreter can move to the next thread. */
for (int threadId = 0; threadId < VM_NUM_THREADS; threadId++) {
if (m->vmIsChannelActive[CURR_STATE][threadId])
continue;
uint16_t n = m->threadsData[PC_OFFSET][threadId];
if (n != VM_INACTIVE_THREAD) {
/* Set the script pointer to the right location. */
/* script pc is used in executeThread in order */
/* to get the next opcode. */
m->_scriptPtr.pc = m->res->segBytecode + n;
m->_stackPtr = 0;
m->gotoNextThread = false;
debug(DBG_VM, "vm_hostFrame() i=0x%02X n=0x%02X *p=0x%02X", threadId, n, *m->_scriptPtr.pc);
vm_executeThread(m);
/* Since .pc is going to be modified by this next loop iteration, we need to save it. */
m->threadsData[PC_OFFSET][threadId] = m->_scriptPtr.pc - m->res->segBytecode;
debug(DBG_VM, "vm_hostFrame() i=0x%02X pos=0x%X", threadId, m->threadsData[PC_OFFSET][threadId]);
if (m->sys->input.quit) {
break;
}
}
}
}
#define COLOR_BLACK 0xFF
#define DEFAULT_ZOOM 0x40
void vm_executeThread(struct VirtualMachine* m) {
while (!m->gotoNextThread) {
uint8_t opcode = scriptPtr_fetchByte(&m->_scriptPtr);
/* 1000 0000 is set */
if (opcode & 0x80)
{
uint16_t off = ((opcode << 8) | scriptPtr_fetchByte(&m->_scriptPtr)) * 2;
m->res->_useSegVideo2 = false;
int16_t x = scriptPtr_fetchByte(&m->_scriptPtr);
int16_t y = scriptPtr_fetchByte(&m->_scriptPtr);
int16_t h = y - 199;
if (h > 0) {
y = 199;
x += h;
}
debug(DBG_VIDEO, "vid_opcd_0x80 : opcode=0x%X off=0x%X x=%d y=%d", opcode, off, x, y);
/* This switch the polygon database to "cinematic" and probably draws a black polygon */
/* over all the screen. */
video_setDataBuffer(m->video, m->res->segCinematic, off);
struct Point temp;
temp.x = x;
temp.y = y;
video_readAndDrawPolygon(m->video, COLOR_BLACK, DEFAULT_ZOOM, &temp);
continue;
}
/* 0100 0000 is set */
if (opcode & 0x40)
{
int16_t x, y;
uint16_t off = scriptPtr_fetchWord(&m->_scriptPtr) * 2;
x = scriptPtr_fetchByte(&m->_scriptPtr);
m->res->_useSegVideo2 = false;
if (!(opcode & 0x20))
{
if (!(opcode & 0x10)) /* 0001 0000 is set */
{
x = (x << 8) | scriptPtr_fetchByte(&m->_scriptPtr);
} else {
x = m->vmVariables[x];
}
}
else
{
if (opcode & 0x10) { /* 0001 0000 is set */
x += 0x100;
}
}
y = scriptPtr_fetchByte(&m->_scriptPtr);
if (!(opcode & 8)) /* 0000 1000 is set */
{
if (!(opcode & 4)) { /* 0000 0100 is set */
y = (y << 8) | scriptPtr_fetchByte(&m->_scriptPtr);
} else {
y = m->vmVariables[y];
}
}
uint16_t zoom = scriptPtr_fetchByte(&m->_scriptPtr);
if (!(opcode & 2)) /* 0000 0010 is set */
{
if (!(opcode & 1)) /* 0000 0001 is set */
{
--m->_scriptPtr.pc;
zoom = 0x40;
}
else
{
zoom = m->vmVariables[zoom];
}
}
else
{
if (opcode & 1) { /* 0000 0001 is set */
m->res->_useSegVideo2 = true;
--m->_scriptPtr.pc;
zoom = 0x40;
}
}
debug(DBG_VIDEO, "vid_opcd_0x40 : off=0x%X x=%d y=%d", off, x, y);
video_setDataBuffer(m->video, m->res->_useSegVideo2 ? m->res->_segVideo2 : m->res->segCinematic, off);
struct Point temp;
temp.x = x;
temp.y = y;
video_readAndDrawPolygon(m->video, 0xFF, zoom, &temp);
continue;
}
if (opcode > 0x1A)
{
error("vm_executeThread() ec=0x%X invalid opcode=0x%X", 0xFFF, opcode);
}
else
{
(vm_opcodeTable[opcode])(m);
}
rb->yield();
}
}
void vm_inp_updatePlayer(struct VirtualMachine* m) {
sys_processEvents(m->sys);
if (m->res->currentPartId == 0x3E89) {
char c = m->sys->input.lastChar;
if (c == 8 || /*c == 0xD |*/ c == 0 || (c >= 'a' && c <= 'z')) {
m->vmVariables[VM_VARIABLE_LAST_KEYCHAR] = c & ~0x20;
m->sys->input.lastChar = 0;
}
}
int16_t lr = 0;
int16_t mask = 0;
int16_t ud = 0;
if (m->sys->input.dirMask & DIR_RIGHT) {
lr = 1;
mask |= 1;
}
if (m->sys->input.dirMask & DIR_LEFT) {
lr = -1;
mask |= 2;
}
if (m->sys->input.dirMask & DIR_DOWN) {
ud = 1;
mask |= 4;
}
m->vmVariables[VM_VARIABLE_HERO_POS_UP_DOWN] = ud;
if (m->sys->input.dirMask & DIR_UP) {
m->vmVariables[VM_VARIABLE_HERO_POS_UP_DOWN] = -1;
}
if (m->sys->input.dirMask & DIR_UP) { /* inpJump */
ud = -1;
mask |= 8;
}
m->vmVariables[VM_VARIABLE_HERO_POS_JUMP_DOWN] = ud;
m->vmVariables[VM_VARIABLE_HERO_POS_LEFT_RIGHT] = lr;
m->vmVariables[VM_VARIABLE_HERO_POS_MASK] = mask;
int16_t button = 0;
if (m->sys->input.button) {
button = 1;
mask |= 0x80;
}
m->vmVariables[VM_VARIABLE_HERO_ACTION] = button;
m->vmVariables[VM_VARIABLE_HERO_ACTION_POS_MASK] = mask;
}
void vm_inp_handleSpecialKeys(struct VirtualMachine* m) {
if (m->sys->input.pause) {
if (m->res->currentPartId != GAME_PART1 && m->res->currentPartId != GAME_PART2) {
m->sys->input.pause = false;
while (!m->sys->input.pause) {
sys_processEvents(m->sys);
sys_sleep(m->sys, 200);
}
}
m->sys->input.pause = false;
}
if (m->sys->input.code) {
m->sys->input.code = false;
if (m->res->currentPartId != GAME_PART_LAST && m->res->currentPartId != GAME_PART_FIRST) {
m->res->requestedNextPart = GAME_PART_LAST;
}
}
/* User has inputted a bad code, the "ERROR" screen is showing */
if (m->vmVariables[0xC9] == 1) {
debug(DBG_VM, "vm_inp_handleSpecialKeys() unhandled case (m->vmVariables[0xC9] == 1)");
}
}
void vm_snd_playSound(struct VirtualMachine* m, uint16_t resNum, uint8_t freq, uint8_t vol, uint8_t channel) {
debug(DBG_SND, "snd_playSound(0x%X, %d, %d, %d)", resNum, freq, vol, channel);
struct MemEntry *me = &m->res->_memList[resNum];
if (me->state != MEMENTRY_STATE_LOADED)
return;
if (vol == 0) {
mixer_stopChannel(m->mixer, channel);
} else {
struct MixerChunk mc;
rb->memset(&mc, 0, sizeof(mc));
mc.data = me->bufPtr + 8; /* skip header */
mc.len = READ_BE_UINT16(me->bufPtr) * 2;
mc.loopLen = READ_BE_UINT16(me->bufPtr + 2) * 2;
if (mc.loopLen != 0) {
mc.loopPos = mc.len;
}
assert(freq < 40);
mixer_playChannel(m->mixer, channel & 3, &mc, vm_frequenceTable[freq], MIN(vol, 0x3F));
}
}
void vm_snd_playMusic(struct VirtualMachine* m, uint16_t resNum, uint16_t delay, uint8_t pos) {
debug(DBG_SND, "snd_playMusic(0x%X, %d, %d)", resNum, delay, pos);
if (resNum != 0) {
player_loadSfxModule(m->player, resNum, delay, pos);
player_start(m->player);
} else if (delay != 0) {
player_setEventsDelay(m->player, delay);
} else {
player_stop(m->player);
}
}
void vm_saveOrLoad(struct VirtualMachine* m, struct Serializer *ser) {
struct Entry entries[] = {
SE_ARRAY(m->vmVariables, 0x100, SES_INT16, VER(1)),
SE_ARRAY(m->_scriptStackCalls, 0x100, SES_INT16, VER(1)),
SE_ARRAY(m->threadsData, 0x40 * 2, SES_INT16, VER(1)),
SE_ARRAY(m->vmIsChannelActive, 0x40 * 2, SES_INT8, VER(1)),
SE_END()
};
ser_saveOrLoadEntries(ser, entries);
}
void vm_bypassProtection(struct VirtualMachine* m)
{
File f;
file_create(&f, true);
if (!file_open(&f, "bank0e", res_getDataDir(m->res), "rb")) {
warning("Unable to bypass protection: add bank0e file to datadir");
} else {
struct Serializer s;
ser_create(&s, &f, SM_LOAD, m->res->_memPtrStart, 2);
vm_saveOrLoad(m, &s);
res_saveOrLoad(m->res, &s);
video_saveOrLoad(m->video, &s);
player_saveOrLoad(m->player, &s);
mixer_saveOrLoad(m->mixer, &s);
}
file_close(&f);
}

184
apps/plugins/xworld/vm.h Normal file
View file

@ -0,0 +1,184 @@
/***************************************************************************
* __________ __ ___.
* 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.
*
***************************************************************************/
#ifndef __LOGIC_H__
#define __LOGIC_H__
#include "intern.h"
#define VM_NUM_THREADS 64
#define VM_NUM_VARIABLES 256
#define VM_NO_SETVEC_REQUESTED 0xFFFF
#define VM_INACTIVE_THREAD 0xFFFF
#define VM_VARIABLE_RANDOM_SEED 0x3C
#define VM_VARIABLE_LAST_KEYCHAR 0xDA
#define VM_VARIABLE_HERO_POS_UP_DOWN 0xE5
#define VM_VARIABLE_MUS_MARK 0xF4
#define VM_VARIABLE_SCROLL_Y 0xF9
#define VM_VARIABLE_HERO_ACTION 0xFA
#define VM_VARIABLE_HERO_POS_JUMP_DOWN 0xFB
#define VM_VARIABLE_HERO_POS_LEFT_RIGHT 0xFC
#define VM_VARIABLE_HERO_POS_MASK 0xFD
#define VM_VARIABLE_HERO_ACTION_POS_MASK 0xFE
#define VM_VARIABLE_PAUSE_SLICES 0xFF
struct Mixer;
struct Resource;
struct Serializer;
struct SfxPlayer;
struct System;
struct Video;
//For threadsData navigation
#define PC_OFFSET 0
#define REQUESTED_PC_OFFSET 1
#define NUM_DATA_FIELDS 2
//For vmIsChannelActive navigation
#define CURR_STATE 0
#define REQUESTED_STATE 1
#define NUM_THREAD_FIELDS 2
struct VirtualMachine;
void vm_create(struct VirtualMachine*, struct Mixer *mix, struct Resource *res, struct SfxPlayer *ply, struct Video *vid, struct System *stub);
void vm_init(struct VirtualMachine*);
void vm_op_movConst(struct VirtualMachine*);
void vm_op_mov(struct VirtualMachine*);
void vm_op_add(struct VirtualMachine*);
void vm_op_addConst(struct VirtualMachine*);
void vm_op_call(struct VirtualMachine*);
void vm_op_ret(struct VirtualMachine*);
void vm_op_pauseThread(struct VirtualMachine*);
void vm_op_jmp(struct VirtualMachine*);
void vm_op_setSetVect(struct VirtualMachine*);
void vm_op_jnz(struct VirtualMachine*);
void vm_op_condJmp(struct VirtualMachine*);
void vm_op_setPalette(struct VirtualMachine*);
void vm_op_resetThread(struct VirtualMachine*);
void vm_op_selectVideoPage(struct VirtualMachine*);
void vm_op_fillVideoPage(struct VirtualMachine*);
void vm_op_copyVideoPage(struct VirtualMachine*);
void vm_op_blitFramebuffer(struct VirtualMachine*);
void vm_op_killThread(struct VirtualMachine*);
void vm_op_drawString(struct VirtualMachine*);
void vm_op_sub(struct VirtualMachine*);
void vm_op_and(struct VirtualMachine*);
void vm_op_or(struct VirtualMachine*);
void vm_op_shl(struct VirtualMachine*);
void vm_op_shr(struct VirtualMachine*);
void vm_op_playSound(struct VirtualMachine*);
void vm_op_updateMemList(struct VirtualMachine*);
void vm_op_playMusic(struct VirtualMachine*);
void vm_initForPart(struct VirtualMachine*, uint16_t partId);
void vm_setupPart(struct VirtualMachine*, uint16_t partId);
void vm_checkThreadRequests(struct VirtualMachine*);
void vm_hostFrame(struct VirtualMachine*);
void vm_executeThread(struct VirtualMachine*);
void vm_inp_updatePlayer(struct VirtualMachine*);
void vm_inp_handleSpecialKeys(struct VirtualMachine*);
void vm_snd_playSound(struct VirtualMachine*, uint16_t resNum, uint8_t freq, uint8_t vol, uint8_t channel);
void vm_snd_playMusic(struct VirtualMachine*, uint16_t resNum, uint16_t delay, uint8_t pos);
void vm_saveOrLoad(struct VirtualMachine*, struct Serializer *ser);
void vm_bypassProtection(struct VirtualMachine*);
typedef void (*OpcodeStub)(struct VirtualMachine*);
// The type of entries in opcodeTable. This allows "fast" branching
static const OpcodeStub vm_opcodeTable[] = {
/* 0x00 */
&vm_op_movConst,
&vm_op_mov,
&vm_op_add,
&vm_op_addConst,
/* 0x04 */
&vm_op_call,
&vm_op_ret,
&vm_op_pauseThread,
&vm_op_jmp,
/* 0x08 */
&vm_op_setSetVect,
&vm_op_jnz,
&vm_op_condJmp,
&vm_op_setPalette,
/* 0x0C */
&vm_op_resetThread,
&vm_op_selectVideoPage,
&vm_op_fillVideoPage,
&vm_op_copyVideoPage,
/* 0x10 */
&vm_op_blitFramebuffer,
&vm_op_killThread,
&vm_op_drawString,
&vm_op_sub,
/* 0x14 */
&vm_op_and,
&vm_op_or,
&vm_op_shl,
&vm_op_shr,
/* 0x18 */
&vm_op_playSound,
&vm_op_updateMemList,
&vm_op_playMusic
};
struct VirtualMachine {
//This table is used to play a sound
//static const uint16_t frequenceTable[];
/* FW: moved from staticres.c to vm.c */
struct Mixer *mixer;
struct Resource *res;
struct SfxPlayer *player;
struct Video *video;
struct System *sys;
int16_t vmVariables[VM_NUM_VARIABLES];
uint16_t _scriptStackCalls[VM_NUM_THREADS];
uint16_t threadsData[NUM_DATA_FIELDS][VM_NUM_THREADS];
// This array is used:
// 0 to save the channel's instruction pointer
// when the channel release control (this happens on a break).
// 1 When a setVec is requested for the next vm frame.
uint8_t vmIsChannelActive[NUM_THREAD_FIELDS][VM_NUM_THREADS];
struct Ptr _scriptPtr;
uint8_t _stackPtr;
bool gotoNextThread;
bool _fastMode;
};
#endif

View file

@ -0,0 +1,244 @@
/***************************************************************************
* __________ __ ___.
* 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 "sys.h"
#include "util.h"
/* we don't want these on the stack, they're big and could cause a stack overflow */
static struct Engine e;
static struct System sys;
enum plugin_status plugin_start(const void* parameter)
{
(void) parameter;
/* no trailing slashes */
const char *dataPath = "/.rockbox/xworld";
const char *savePath = "/.rockbox/xworld";
g_debugMask = 0;
engine_create(&e, &sys, dataPath, savePath);
engine_init(&e);
sys_menu(&sys);
engine_run(&e);
engine_finish(&e);
return PLUGIN_OK;
}
/*
Game was originally made with 16. SIXTEEN colors. Running on 320x200 (64,000 pixels.)
Great fan site here: https://sites.google.com/site/interlinkknight/anotherworld
Contains the wheelcode :P !
A lot of details can be found regarding the game and engine architecture at:
http://www.anotherworld.fr/anotherworld_uk/another_world.htm
The chronology of the game implementation can retraced via the ordering of the opcodes:
The sound and music opcode are at the end: Music and sound was done at the end.
Call tree:
=========
SDLSystem systemImplementaion ;
System *sys = & systemImplementaion ;
main
{
Engine *e = new Engine();
e->run()
{
sys->init("Out Of This World");
setup();
vm.restartAt(0x3E80); // demo starts at 0x3E81
while (!_stub->_pi.quit)
{
vm.setupScripts();
vm.inp_updatePlayer();
processInput();
vm.runScripts();
}
finish();
}
}
Virtual Machine:
================
Seems the threading model is collaborative multi-tasking (as opposed to preemptive multitasking):
A thread (called a Channel on Eric Chahi's website) will release the hand to the next one via the
break opcode.
It seems that even when a setvec is requested by a thread, we cannot set the instruction pointer
yet. The thread is allowed to keep on executing its code for the remaining of the vm frame.
A virtual machine frame has a variable duration. The unit of time is 20ms and the frame can be set
to live for 1 (20ms ; 50Hz) up to 5 (100ms ; 10Hz).
There are 30 something opcodes. The graphic opcode are more complex, not only the declare the operation to perform
they also define where to find the vertices (segVideo1 or segVideo2).
No stack available but a thread can save its pc (Program Counter) once: One method call and return is possible.
Video :
=======
Double buffer architecture. AW opcodes even has a special instruction for blitting from one
frame buffer to an other.
Double buffering is implemented in software
According to Eric Chahi's webpage there are 4 framebuffer. Since on full screenbuffer is 320x200/2 = 32kB
that would mean the total size consumed is 128KB ?
Sound :
=======
Mixing is done on software.
Since the virtual machine and SDL are running simultaneously in two different threads:
Any read or write to an elements of the sound channels MUST be synchronized with a
mutex.
FastMode :
==========
The game engine features a "fast-mode"...what it to be able to respond to the now defunct
TURBO button commonly found on 386/486 era PC ?!
Endianess:
==========
Atari and Amiga used bigEndian CPUs. Data are hence stored within BANK in big endian format.
On an Intel or ARM CPU data will have to be transformed when read.
The original codebase contained a looooot of cryptic hexa values.
0x100 (for 256 variables)
0x400 (for one kilobyte)
0x40 (for num threads)
0x3F (num thread mask)
I cleaned that up.
Questions & Answers :
=====================
Q: How does the interpreter deals with the CPU speed ?! A pentium is a tad faster than a Motorola 68000
after all.
A: See vm frame time: The vm frame duration is variable. The vm actually write for how long a video frame
should be displayed in variable 0xFF. The value is the number of 20ms slice
Q: Why is a palette 2048 bytes if there are only 16 colors ? I would have expected 48 bytes...
A: ???
Q: Why does Resource::load() search for ressource to load from higher to lower....since it will load stuff
until no more ressources are marked as "Need to be loaded".
A: ???
Original DOS version :
======================
Banks: 1,236,519 B
exe : 20,293 B
Total bank size: 1236519 (100%)
---------------------------------
Total RT_SOUND size: 585052 ( 47%)
Total RT_MUSIC size: 3540 ( 0%)
Total RT_POLY_ANIM size: 106676 ( 9%)
Total RT_PALETTE size: 11032 ( 1%)
Total RT_BYTECODE size: 135948 ( 11%)
Total RT_POLY_CINEMATIC size: 291008 ( 24%)
As usual sounds are the most consuming assets (Quake1,Quake2 etc.....)
memlist.bin features 146 entries :
==================================
Most important part in an entry are:
bankId : - Give the file were the resource is.
offset : - How much to skip in the file before hiting the resource.
size,packetSize : - How much to read, should we unpack what we read.
Polygons drawing :
=================
Polygons can be given as:
- a pure screenspace sequence of points: I call those screenspace polygons.
- a list of delta to add or substract to the first vertex. I call those: objectspace polygons.
Video :
=======
Q: Why 4 framebuffer ?
A: It seems the background is generated once (like in the introduction) and stored in a framebuffer.
Every frame the saved background is copied and new elements are drawn on top.
Trivia :
========
If you are used to RGBA 32bits per pixel framebuffer you are in for a shock:
Another world is 16 colors palette based, making it 4bits per pixel !!
Video generation :
==================
Thank god the engine sets the palette before starting to drawing instead of after bliting.
I would have been unable to generate screenshots otherwise.
Memory managment :
=================
There is 0 malloc during the game. All resources are loaded in one big buffer (Resource::load).
The entire buffer is "freed" at the end of a game part.
The renderer is actually capable of Blending a new poly in the framebuffer (Video::drawLineT)
I am almost sure that:
_curPagePtr1 is the backbuffer
_curPagePtr2 is the frontbuffer
_curPagePtr3 is the background builder.
* Why does memlist.bin uses a special state field 0xFF in order to mark the end of resources ??!
It would have been so much easier to write the number of resources at the beginning of the code.
*/

View file

@ -0,0 +1,27 @@
# __________ __ ___.
# Open \______ \ ____ ____ | | _\_ |__ _______ ___
# Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
# Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
# Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
# \/ \/ \/ \/ \/
# $Id$
#
XWORLDSRCDIR := $(APPSDIR)/plugins/xworld
XWORLDBUILDDIR := $(BUILDDIR)/apps/plugins/xworld
ROCKS += $(XWORLDBUILDDIR)/xworld.rock
XWORLD_SRC := $(call preprocess, $(XWORLDSRCDIR)/SOURCES)
XWORLD_OBJ := $(call c2obj, $(XWORLD_SRC))
# add source files to OTHER_SRC to get automatic dependencies
OTHER_SRC += $(XWORLD_SRC)
XWORLDFLAGS = $(filter-out -O%,$(PLUGINFLAGS)) -O2
$(XWORLDBUILDDIR)/xworld.rock: $(XWORLD_OBJ)
$(XWORLDBUILDDIR)/%.o: $(XWORLDSRCDIR)/%.c $(XWORLDSRCDIR)/xworld.make
$(SILENT)mkdir -p $(dir $@)
$(call PRINTS,CC $(subst $(ROOTDIR)/,,$<))$(CC) -I$(dir $<) $(XWORLDFLAGS) -c $< -o $@

View file

@ -672,3 +672,5 @@ The Pure Data team (Miller Puckette and others)
The MikMod team
Michael McTernan (The ARM unwinder author)
Albert Song
The New RAW team (Piotr Padkowski and others)
The Fabother World team (Fabien Sanglard and others)

View file

@ -94,6 +94,8 @@ text files%
\opt{lcd_bitmap}{\input{plugins/xobox.tex}}
\opt{lcd_bitmap}{\input{plugins/xworld.tex}}
\section{Demos}
\opt{lcd_bitmap}{\input{plugins/bounce.tex}}

81
manual/plugins/xworld.tex Normal file
View file

@ -0,0 +1,81 @@
\subsection{XWorld}
In this cinematic, award winning platform game by Éric Chahi, you must evade capture
and do your best to escape an alien planet. After an experiment goes awry the hero
must team up with an unlikely ally, when they both become fugitives on another world.
XWorld requires the data files, bank* and memlist.bin, from the original "Another World"
PC game to be copied into the .rockbox/xworld/ directory before the game can be played.
\begin{btnmap}
%
\opt{RECORDER_PAD,ONDIO_PAD,IRIVER_H100_PAD,IRIVER_H300_PAD,IAUDIO_X5_PAD%
,SANSA_E200_PAD,SANSA_FUZE_PAD,SANSA_C200_PAD,SANSA_CLIP_PAD,GIGABEAT_PAD%
,GIGABEAT_S_PAD,MROBE100_PAD,PBELL_VIBE500_PAD,SANSA_FUZEPLUS_PAD%
,SAMSUNG_YH92X_PAD,SAMSUNG_YH820_PAD}
{\ButtonUp}
\opt{IPOD_4G_PAD,IPOD_3G_PAD,IPOD_1G2G_PAD}{\ButtonMenu}
\opt{IRIVER_H10_PAD}{\ButtonScrollUp}
\opt{HAVE_TOUCHSCREEN}{\TouchTopMiddle}
\opt{PBELL_VIBE500_PAD}{\ButtonOk}
\opt{HAVEREMOTEKEYMAP}{& }
& Up and Jump \\
%
\opt{RECORDER_PAD,ONDIO_PAD,IRIVER_H100_PAD,IRIVER_H300_PAD,IAUDIO_X5_PAD%
,SANSA_E200_PAD,SANSA_FUZE_PAD,SANSA_C200_PAD,SANSA_CLIP_PAD,GIGABEAT_PAD%
,GIGABEAT_S_PAD,MROBE100_PAD,PBELL_VIBE500_PAD,SANSA_FUZEPLUS_PAD%
,SAMSUNG_YH92X_PAD,SAMSUNG_YH820_PAD}
{\ButtonDown}
\opt{IPOD_4G_PAD,IPOD_3G_PAD,IPOD_1G2G_PAD}{\ButtonPlay}
\opt{IRIVER_H10_PAD}{\ButtonScrollDown}
\opt{HAVE_TOUCHSCREEN}{\TouchBottomMiddle}
\opt{PBELL_VIBE500_PAD}{\ButtonCancel}
\opt{HAVEREMOTEKEYMAP}{& }
& Down and Crouch\\
%
\opt{RECORDER_PAD,ONDIO_PAD,IRIVER_H100_PAD,IRIVER_H300_PAD,IAUDIO_X5_PAD%
,SANSA_E200_PAD,SANSA_FUZE_PAD,SANSA_C200_PAD,SANSA_CLIP_PAD,GIGABEAT_PAD%
,GIGABEAT_S_PAD,MROBE100_PAD,PBELL_VIBE500_PAD,SANSA_FUZEPLUS_PAD%
,SAMSUNG_YH92X_PAD,SAMSUNG_YH820_PAD,IPOD_4G_PAD,IPOD_3G_PAD,IPOD_1G2G_PAD%
,IRIVER_H10_PAD}
{\ButtonLeft / \ButtonRight}
\opt{HAVE_TOUCHSCREEN}{\TouchMidLeft / \TouchMidRight}
\opt{PBELL_VIBE500_PAD}{\ButtonMenu / \ButtonPlay}
\opt{HAVEREMOTEKEYMAP}{& }
& Move Left and Right\\
%
\opt{SANSA_FUZE_PAD}{\ButtonHome}
\opt{SAMSUNG_YH920_PAD}{\ButtonFFWD}
\opt{IRIVER_H300_PAD,SANSA_E200_PAD,SAMSUNG_YH820_PAD,IAUDIO_X5M5_PAD}{\ButtonRec}
\opt{IPOD_4G_PAD,IPOD_3G_PAD,IPOD_1G2G_PAD,CREATIVE_ZEN_PAD,SANSA_CLIP_PAD}{\ButtonSelect}
\opt{SONY_NWZ_PAD,CREATIVEZVM_PAD}{\ButtonPlay}
\opt{ONDAVX777_PAD,MROBE500_PAD,PBELL_VIBE500_PAD}{\ButtonPower}
\opt{SAMSUNG_YPR0_PAD}{\ButtonUser}
\opt{IRIVER_H10_PAD}{\ButtonRew}
\opt{HM801_PAD}{\ButtonPrev}
\opt{SONY_NWZ_PAD,CREATIVEZVM_PAD}{\ButtonPlay}
\opt{MROBE500_PAD}{\ButtonPower}
\opt{DX50_PAD,ONDAVX747_PAD,PHILIPS_HDD1630_PAD,PHILIPS_HDD6330_PAD,PHILIPS_SA9200_PAD%
,CREATIVE_ZENXFI2_PAD,CREATIVE_ZENXFI3_PAD,SANSA_CONNECT_PAD,SANSA_C200_PAD%
,SANSA_FUZEPLUS_PAD,GIGABEAT_PAD,GIGABEAT_S_PAD}{\ButtonVolUp}
\opt{HAVE_TOUCHSCREEN}{\ButtonBottomLeft}
\opt{HAVEREMOTEKEYMAP}{& }
& Action and Fire\\
%
\opt{DX50_PAD,ONDAVX747_PAD,PHILIPS_HDD1630_PAD,PHILIPS_HDD6330_PAD,PHILIPS_SA9200_PAD%
,CREATIVE_ZENXFI2_PAD,CREATIVE_ZENXFI3_PAD,SANSA_CONNECT_PAD,SANSA_C200_PAD%
,SANSA_FUZEPLUS_PAD}{\ButtonVolDown}
\opt{GIGABEAT_PAD,GIGABEAT_S_PAD}{\ButtonMenu}
\opt{SANSA_FUZE_PAD}{\ButtonSelect}
\opt{SAMSUNG_YH920_PAD}{\ButtonRew}
\opt{SAMSUNG_YH820_PAD,IAUDIO_X5M5_PAD}{\ButtonPlay}
\opt{SANSA_E200_PAD,SANSA_CLIP_PAD}{\ButtonPower}
\opt{CREATIVE_ZEN_PAD,SONY_NWZ_PAD}{\ButtonBack}
\opt{CREATIVEZVM_PAD,SAMSUNG_YPR0_PAD}{\ButtonMenu}
\opt{IRIVER_H300_PAD}{\ButtonMode}
\opt{HM801_PAD}{\ButtonNext}
\opt{PBELL_VIBE500_PAD}{\ButtonRec}
\opt{IRIVER_H10_PAD}{\ButtonPlay}
\opt{IPOD_4G_PAD,IPOD_3G_PAD,IPOD_1G2G_PAD}{\ButtonMenu / \ButtonSelect}
\opt{HAVEREMOTEKEYMAP}{& }
& Menu\\
\end{btnmap}