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:
parent
b681e932a9
commit
33cb13dee5
41 changed files with 6964 additions and 0 deletions
|
@ -134,4 +134,5 @@ wavrecord,apps
|
|||
wavview,viewers
|
||||
wormlet,games
|
||||
xobox,games
|
||||
xworld,games
|
||||
zxbox,viewers
|
||||
|
|
|
@ -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 */ \
|
||||
|
|
86
apps/plugins/xworld/README
Normal file
86
apps/plugins/xworld/README
Normal 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
|
4
apps/plugins/xworld/README.newraw
Normal file
4
apps/plugins/xworld/README.newraw
Normal file
|
@ -0,0 +1,4 @@
|
|||
|
||||
Changes:
|
||||
Added 2x and 3x high quality scalers (ripped from Reminescence)
|
||||
|
51
apps/plugins/xworld/README.rockbox
Normal file
51
apps/plugins/xworld/README.rockbox
Normal 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
|
15
apps/plugins/xworld/SOURCES
Normal file
15
apps/plugins/xworld/SOURCES
Normal 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
|
51
apps/plugins/xworld/awendian.h
Normal file
51
apps/plugins/xworld/awendian.h
Normal 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
153
apps/plugins/xworld/bank.c
Normal 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;
|
||||
}
|
55
apps/plugins/xworld/bank.h
Normal file
55
apps/plugins/xworld/bank.h
Normal 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
|
397
apps/plugins/xworld/engine.c
Normal file
397
apps/plugins/xworld/engine.c
Normal 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;
|
||||
}
|
70
apps/plugins/xworld/engine.h
Normal file
70
apps/plugins/xworld/engine.h
Normal 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
172
apps/plugins/xworld/file.c
Normal 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);
|
||||
}
|
||||
}
|
51
apps/plugins/xworld/file.h
Normal file
51
apps/plugins/xworld/file.h
Normal 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
|
34
apps/plugins/xworld/intern.c
Normal file
34
apps/plugins/xworld/intern.c
Normal 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;
|
||||
}
|
44
apps/plugins/xworld/intern.h
Normal file
44
apps/plugins/xworld/intern.h
Normal 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
|
183
apps/plugins/xworld/keymaps.h
Normal file
183
apps/plugins/xworld/keymaps.h
Normal 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
199
apps/plugins/xworld/mixer.c
Normal 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);
|
||||
};
|
69
apps/plugins/xworld/mixer.h
Normal file
69
apps/plugins/xworld/mixer.h
Normal 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
|
56
apps/plugins/xworld/parts.c
Normal file
56
apps/plugins/xworld/parts.c
Normal 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 */
|
||||
};
|
57
apps/plugins/xworld/parts.h
Normal file
57
apps/plugins/xworld/parts.h
Normal 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
|
443
apps/plugins/xworld/resource.c
Normal file
443
apps/plugins/xworld/resource.c
Normal 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;
|
||||
}
|
107
apps/plugins/xworld/resource.h
Normal file
107
apps/plugins/xworld/resource.h
Normal 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
|
141
apps/plugins/xworld/serializer.c
Normal file
141
apps/plugins/xworld/serializer.c
Normal 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;
|
||||
}
|
||||
}
|
84
apps/plugins/xworld/serializer.h
Normal file
84
apps/plugins/xworld/serializer.h
Normal 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
|
247
apps/plugins/xworld/sfxplayer.c
Normal file
247
apps/plugins/xworld/sfxplayer.c
Normal 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);
|
||||
}
|
||||
}
|
88
apps/plugins/xworld/sfxplayer.h
Normal file
88
apps/plugins/xworld/sfxplayer.h
Normal 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
942
apps/plugins/xworld/sys.c
Normal 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
146
apps/plugins/xworld/sys.h
Normal 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
|
82
apps/plugins/xworld/util.c
Normal file
82
apps/plugins/xworld/util.c
Normal 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';
|
||||
}
|
||||
}
|
||||
}
|
59
apps/plugins/xworld/util.h
Normal file
59
apps/plugins/xworld/util.h
Normal 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
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
127
apps/plugins/xworld/video.h
Normal 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
|
271
apps/plugins/xworld/video_data.c
Normal file
271
apps/plugins/xworld/video_data.c
Normal 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, "" }
|
||||
};
|
29
apps/plugins/xworld/video_data.h
Normal file
29
apps/plugins/xworld/video_data.h
Normal 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
763
apps/plugins/xworld/vm.c
Normal 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
184
apps/plugins/xworld/vm.h
Normal 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
|
244
apps/plugins/xworld/xworld.c
Normal file
244
apps/plugins/xworld/xworld.c
Normal 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.
|
||||
*/
|
27
apps/plugins/xworld/xworld.make
Normal file
27
apps/plugins/xworld/xworld.make
Normal 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 $@
|
|
@ -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)
|
||||
|
|
|
@ -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
81
manual/plugins/xworld.tex
Normal 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}
|
Loading…
Reference in a new issue