Import 0.3.1

This commit is contained in:
Gregory Montoir 2016-05-10 00:00:00 +08:00
parent cb9e469636
commit 577fec920a
26 changed files with 775 additions and 288 deletions

View File

@ -1,12 +1,12 @@
SDL_CFLAGS = `sdl-config --cflags` SDL_CFLAGS = `sdl-config --cflags`
SDL_LIBS = `sdl-config --libs` SDL_LIBS = `sdl-config --libs`
VORBIS_LIBS = -lvorbisidec
MODPLUG_LIBS = -lmodplug MODPLUG_LIBS = -lmodplug
# TREMOR_LIBS = -lvorbisidec -logg
ZLIB_LIBS = -lz ZLIB_LIBS = -lz
CXX := clang++ CXX := clang++
CXXFLAGS := -Wall -MMD $(SDL_CFLAGS) -DUSE_ZLIB # -DUSE_MODPLUG CXXFLAGS := -Wall -MMD $(SDL_CFLAGS) -DUSE_MODPLUG -DUSE_ZLIB # -DUSE_TREMOR
SRCS = collision.cpp cutscene.cpp file.cpp fs.cpp game.cpp graphics.cpp main.cpp menu.cpp \ SRCS = collision.cpp cutscene.cpp file.cpp fs.cpp game.cpp graphics.cpp main.cpp menu.cpp \
mixer.cpp mod_player.cpp ogg_player.cpp piege.cpp resource.cpp resource_aba.cpp \ mixer.cpp mod_player.cpp ogg_player.cpp piege.cpp resource.cpp resource_aba.cpp \
@ -16,7 +16,7 @@ SRCS = collision.cpp cutscene.cpp file.cpp fs.cpp game.cpp graphics.cpp main.cpp
OBJS = $(SRCS:.cpp=.o) OBJS = $(SRCS:.cpp=.o)
DEPS = $(SRCS:.cpp=.d) DEPS = $(SRCS:.cpp=.d)
LIBS = $(SDL_LIBS) $(VORBIS_LIBS) $(MODPLUG_LIBS) $(ZLIB_LIBS) LIBS = $(SDL_LIBS) $(MODPLUG_LIBS) $(TREMOR_LIBS) $(ZLIB_LIBS)
rs: $(OBJS) rs: $(OBJS)
$(CXX) $(LDFLAGS) -o $@ $(OBJS) $(LIBS) $(CXX) $(LDFLAGS) -o $@ $(OBJS) $(LIBS)

View File

@ -1,6 +1,6 @@
REminiscence README REminiscence README
Release version: 0.3.0 ($date) Release version: 0.3.1
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
@ -15,7 +15,8 @@ game can be found at [1], [2] and [3].
Compiling: Compiling:
---------- ----------
Update the defines in the Makefile if needed. The SDL and zlib libraries are required. Update the defines in the Makefile if needed. The SDL, zlib and modplug
libraries are required.
Data Files: Data Files:
@ -64,6 +65,10 @@ directory. These paths can be changed using command line switches :
Usage: rs [OPTIONS]... Usage: rs [OPTIONS]...
--datapath=PATH Path to data files (default 'DATA') --datapath=PATH Path to data files (default 'DATA')
--savepath=PATH Path to save files (default '.') --savepath=PATH Path to save files (default '.')
--levelnum=NUM Level to start from (default '0')
--fullscreen Fullscreen display
--scaler=INDEX Graphics scaler
--language=LANG Language (fr,en,de,sp,it)
In-game hotkeys : In-game hotkeys :

View File

@ -18,13 +18,16 @@ Cutscene::Cutscene(Resource *res, SystemStub *stub, Video *vid)
} }
void Cutscene::sync() { void Cutscene::sync() {
// XXX input handling if (_stub->_pi.quit) {
if (!(_stub->_pi.dbgMask & PlayerInput::DF_FASTMODE)) { return;
int32_t delay = _stub->getTimeStamp() - _tstamp; }
int32_t pause = _frameDelay * TIMER_SLICE - delay; if (_stub->_pi.dbgMask & PlayerInput::DF_FASTMODE) {
if (pause > 0) { return;
_stub->sleep(pause); }
} const int32_t delay = _stub->getTimeStamp() - _tstamp;
const int32_t pause = _frameDelay * TIMER_SLICE - delay;
if (pause > 0) {
_stub->sleep(pause);
} }
_tstamp = _stub->getTimeStamp(); _tstamp = _stub->getTimeStamp();
} }
@ -90,7 +93,7 @@ uint16_t Cutscene::findTextSeparators(const uint8_t *p) {
uint8_t *q = _textSep; uint8_t *q = _textSep;
uint16_t ret = 0; uint16_t ret = 0;
uint16_t pos = 0; uint16_t pos = 0;
for (; *p != 0xA; ++p) { for (; *p != 0xA && *p; ++p) {
if (*p == 0x7C) { if (*p == 0x7C) {
*q++ = pos; *q++ = pos;
if (pos > ret) { if (pos > ret) {
@ -111,6 +114,7 @@ uint16_t Cutscene::findTextSeparators(const uint8_t *p) {
void Cutscene::drawText(int16_t x, int16_t y, const uint8_t *p, uint16_t color, uint8_t *page, uint8_t n) { void Cutscene::drawText(int16_t x, int16_t y, const uint8_t *p, uint16_t color, uint8_t *page, uint8_t n) {
debug(DBG_CUT, "Cutscene::drawText(x=%d, y=%d, c=%d)", x, y, color); debug(DBG_CUT, "Cutscene::drawText(x=%d, y=%d, c=%d)", x, y, color);
Video::drawCharFunc dcf = _vid->_drawChar;
uint16_t last_sep = 0; uint16_t last_sep = 0;
if (n != 0) { if (n != 0) {
last_sep = findTextSeparators(p); last_sep = findTextSeparators(p);
@ -126,7 +130,7 @@ void Cutscene::drawText(int16_t x, int16_t y, const uint8_t *p, uint16_t color,
if (n != 0) { if (n != 0) {
xx += ((last_sep - *sep++) & 0xFE) * 4; xx += ((last_sep - *sep++) & 0xFE) * 4;
} }
for (; *p != 0xA; ++p) { for (; *p != 0xA && *p; ++p) {
if (*p == 0x7C) { if (*p == 0x7C) {
yy += 8; yy += 8;
xx = x; xx = x;
@ -135,25 +139,11 @@ void Cutscene::drawText(int16_t x, int16_t y, const uint8_t *p, uint16_t color,
} }
} else if (*p == 0x20) { } else if (*p == 0x20) {
xx += 8; xx += 8;
} else if (*p == 0x9) {
// ignore tab
} else { } else {
uint8_t *dst_char = page + 256 * yy + xx; uint8_t *dst = page + 256 * yy + xx;
const uint8_t *src = _res->_fnt + (*p - 32) * 32; (_vid->*dcf)(dst, 256, _res->_fnt, color, *p);
for (int h = 0; h < 8; ++h) {
for (int w = 0; w < 4; ++w) {
uint8_t c1 = (*src & 0xF0) >> 4;
uint8_t c2 = (*src & 0x0F) >> 0;
++src;
if (c1 != 0) {
*dst_char = (c1 == 0xF) ? color : (0xE0 + c1);
}
++dst_char;
if (c2 != 0) {
*dst_char = (c2 == 0xF) ? color : (0xE0 + c2);
}
++dst_char;
}
dst_char += 256 - 8;
}
xx += 8; xx += 8;
} }
} }
@ -176,11 +166,10 @@ void Cutscene::drawCreditsText() {
return; return;
} }
} }
if (_creditsTextCounter <= 0) { // XXX if (_creditsTextCounter <= 0) {
uint8_t code = *_textCurPtr; uint8_t code = *_textCurPtr;
if (code == 0xFF) { if (code == 0xFF) {
_textBuf[0] = 0xA; _textBuf[0] = 0xA;
// _cut_status = 0;
} else if (code == 0xFE) { } else if (code == 0xFE) {
++_textCurPtr; ++_textCurPtr;
code = *_textCurPtr++; code = *_textCurPtr++;
@ -198,10 +187,11 @@ void Cutscene::drawCreditsText() {
} }
} else { } else {
*_textCurBuf++ = code; *_textCurBuf++ = code;
*_textCurBuf++ = 0xA; *_textCurBuf = 0xA;
++_textCurPtr;
} }
} else { } else {
_creditsTextCounter -= 10; // XXX adjust _creditsTextCounter -= 10;
} }
drawText((_creditsTextPosX - 1) * 8, _creditsTextPosY * 8, _textBuf, 0xEF, _page1, 0); drawText((_creditsTextPosX - 1) * 8, _creditsTextPosY * 8, _textBuf, 0xEF, _page1, 0);
} }
@ -270,7 +260,7 @@ void Cutscene::op_waitForSync() {
_varText = 0xFF; _varText = 0xFF;
_frameDelay = 3; _frameDelay = 3;
if (_textBuf == _textCurBuf) { if (_textBuf == _textCurBuf) {
_creditsTextCounter = 20; _creditsTextCounter = _res->isAmiga() ? 60 : 20;
} }
memcpy(_page1, _page0, _vid->_layerSize); memcpy(_page1, _page0, _vid->_layerSize);
drawCreditsText(); drawCreditsText();
@ -385,6 +375,16 @@ void Cutscene::op_drawStringAtBottom() {
debug(DBG_CUT, "Cutscene::op_drawStringAtBottom()"); debug(DBG_CUT, "Cutscene::op_drawStringAtBottom()");
uint16_t strId = fetchNextCmdWord(); uint16_t strId = fetchNextCmdWord();
if (!_creditsSequence) { if (!_creditsSequence) {
// 'espions' - ignore last call, allows caption to be displayed longer on the screen
if (_id == 0x39 && strId == 0xFFFF) {
if ((_res->isDOS() && (_cmdPtr - _cmdPtrBak) == 0x10) || (_res->isAmiga() && (_cmdPtr - _res->_cmd) == 0x9F3)) {
_frameDelay = 100;
setPalette();
return;
}
}
memset(_pageC + 179 * 256, 0xC0, 45 * 256); memset(_pageC + 179 * 256, 0xC0, 45 * 256);
memset(_page1 + 179 * 256, 0xC0, 45 * 256); memset(_page1 + 179 * 256, 0xC0, 45 * 256);
memset(_page0 + 179 * 256, 0xC0, 45 * 256); memset(_page0 + 179 * 256, 0xC0, 45 * 256);
@ -839,7 +839,7 @@ void Cutscene::op_drawStringAtPos() {
uint8_t color = 0xD0 + (strId >> 0xC); uint8_t color = 0xD0 + (strId >> 0xC);
drawText(x, y, str, color, _page1, 2); drawText(x, y, str, color, _page1, 2);
} }
// workaround for buggy cutscene script // 'voyage' - cutscene script redraws the string to refresh the screen
if (_id == 0x34 && (strId & 0xFFF) == 0x45) { if (_id == 0x34 && (strId & 0xFFF) == 0x45) {
if ((_cmdPtr - _cmdPtrBak) == 0xA) { if ((_cmdPtr - _cmdPtrBak) == 0xA) {
_stub->copyRect(0, 0, _vid->_w, _vid->_h, _page1, 256); _stub->copyRect(0, 0, _vid->_w, _vid->_h, _page1, 256);
@ -964,13 +964,30 @@ void Cutscene::load(uint16_t cutName) {
name = "INTRO"; name = "INTRO";
} }
_res->load(name, Resource::OT_CMP); _res->load(name, Resource::OT_CMP);
if (_id == 0x39 && _res->_lang != LANG_FR) {
//
// 'espions' - '... the power which we need' caption is missing in Amiga English.
// fixed in DOS version, opcodes order is wrong
//
// opcode 0 pos 0x323
// opcode 6 pos 0x324
// str 0x3a
//
uint8_t *p = _res->_cmd + 0x322;
if (memcmp(p, "\x00\x18\x00\x3a", 4) == 0) {
p[0] = 0x06 << 2; // op_drawStringAtBottom
p[1] = 0x00;
p[2] = 0x3a;
p[3] = 0x00; // op_markCurPos
}
}
break; break;
case kResourceTypeDOS: case kResourceTypeDOS:
_res->load(name, Resource::OT_CMD); _res->load(name, Resource::OT_CMD);
_res->load(name, Resource::OT_POL); _res->load(name, Resource::OT_POL);
_res->load_CINE();
break; break;
} }
_res->load_CINE();
} }
void Cutscene::prepare() { void Cutscene::prepare() {
@ -986,12 +1003,11 @@ void Cutscene::prepare() {
_gfx.setClippingRect(8, 50, 240, 128); _gfx.setClippingRect(8, 50, 240, 128);
} }
void Cutscene::startCredits() { void Cutscene::playCredits() {
_textCurPtr = _creditsData; _textCurPtr = _res->isAmiga() ? _creditsDataAmiga : _creditsDataDOS;
_textBuf[0] = 0xA; _textBuf[0] = 0xA;
_textCurBuf = _textBuf; _textCurBuf = _textBuf;
_creditsSequence = true; _creditsSequence = true;
// _cut_status = 1;
_varText = 0; _varText = 0;
_textUnk2 = 0; _textUnk2 = 0;
_creditsTextCounter = 0; _creditsTextCounter = 0;
@ -1011,6 +1027,37 @@ void Cutscene::startCredits() {
_creditsSequence = false; _creditsSequence = false;
} }
void Cutscene::playText(const char *str) {
Color c;
// background
c.r = c.g = c.b = 0;
_stub->setPaletteEntry(0xC0, &c);
// text
c.r = c.g = c.b = 255;
_stub->setPaletteEntry(0xC1, &c);
int lines = 0;
for (int i = 0; str[i]; ++i) {
if (str[i] == '|') {
++lines;
}
}
const int y = (128 - lines * 8) / 2;
memset(_page1, 0xC0, _vid->_layerSize);
drawText(0, y, (const uint8_t *)str, 0xC1, _page1, 1);
_stub->copyRect(0, 0, _vid->_w, _vid->_h, _page1, 256);
_stub->updateScreen(0);
while (!_stub->_pi.quit) {
_stub->processEvents();
if (_stub->_pi.backspace) {
_stub->_pi.backspace = false;
break;
}
_stub->sleep(TIMER_SLICE);
}
}
void Cutscene::play() { void Cutscene::play() {
if (_id != 0xFFFF) { if (_id != 0xFFFF) {
_textCurBuf = NULL; _textCurBuf = NULL;
@ -1044,7 +1091,14 @@ void Cutscene::play() {
} }
} }
} }
if (cutName != 0xFFFF) { if (g_options.use_text_cutscenes && _res->_lang == LANG_FR) {
for (int i = 0; _frTextsTable[i].str; ++i) {
if (_id == _frTextsTable[i].num) {
playText(_frTextsTable[i].str);
break;
}
}
} else if (cutName != 0xFFFF) {
load(cutName); load(cutName);
mainLoop(cutOff); mainLoop(cutOff);
} }

View File

@ -22,16 +22,23 @@ struct Cutscene {
TIMER_SLICE = 15 TIMER_SLICE = 15
}; };
struct Text {
int num;
const char *str;
};
static const OpcodeStub _opcodeTable[]; static const OpcodeStub _opcodeTable[];
static const char *_namesTable[]; static const char *_namesTable[];
static const uint16_t _offsetsTable[]; static const uint16_t _offsetsTable[];
static const uint8_t _amigaDemoOffsetsTable[]; static const uint8_t _amigaDemoOffsetsTable[];
static const uint16_t _cosTable[]; static const uint16_t _cosTable[];
static const uint16_t _sinTable[]; static const uint16_t _sinTable[];
static const uint8_t _creditsData[]; static const uint8_t _creditsDataDOS[];
static const uint8_t _creditsDataAmiga[];
static const uint16_t _creditsCutSeq[]; static const uint16_t _creditsCutSeq[];
static const uint8_t _musicTable[]; static const uint8_t _musicTable[];
static const uint8_t _protectionShapeData[]; static const uint8_t _protectionShapeData[];
static const Text _frTextsTable[];
Graphics _gfx; Graphics _gfx;
Resource *_res; Resource *_res;
@ -118,7 +125,8 @@ struct Cutscene {
void mainLoop(uint16_t offset); void mainLoop(uint16_t offset);
void load(uint16_t cutName); void load(uint16_t cutName);
void prepare(); void prepare();
void startCredits(); void playCredits();
void playText(const char *str);
void play(); void play();
}; };

View File

@ -24,9 +24,9 @@ struct File_impl {
virtual uint32_t write(void *ptr, uint32_t len) = 0; virtual uint32_t write(void *ptr, uint32_t len) = 0;
}; };
struct stdFile : File_impl { struct StdioFile : File_impl {
FILE *_fp; FILE *_fp;
stdFile() : _fp(0) {} StdioFile() : _fp(0) {}
bool open(const char *path, const char *mode) { bool open(const char *path, const char *mode) {
_ioErr = false; _ioErr = false;
_fp = fopen(path, mode); _fp = fopen(path, mode);
@ -76,9 +76,9 @@ struct stdFile : File_impl {
}; };
#ifdef USE_ZLIB #ifdef USE_ZLIB
struct zlibFile : File_impl { struct GzipFile : File_impl {
gzFile _fp; gzFile _fp;
zlibFile() : _fp(0) {} GzipFile() : _fp(0) {}
bool open(const char *path, const char *mode) { bool open(const char *path, const char *mode) {
_ioErr = false; _ioErr = false;
_fp = gzopen(path, mode); _fp = gzopen(path, mode);
@ -147,7 +147,7 @@ bool File::open(const char *filename, const char *mode, FileSystem *fs) {
_impl = 0; _impl = 0;
} }
assert(mode[0] != 'z'); assert(mode[0] != 'z');
_impl = new stdFile; _impl = new StdioFile;
char *path = fs->findPath(filename); char *path = fs->findPath(filename);
if (path) { if (path) {
debug(DBG_FILE, "Open file name '%s' mode '%s' path '%s'", filename, mode, path); debug(DBG_FILE, "Open file name '%s' mode '%s' path '%s'", filename, mode, path);
@ -166,12 +166,12 @@ bool File::open(const char *filename, const char *mode, const char *directory) {
} }
#ifdef USE_ZLIB #ifdef USE_ZLIB
if (mode[0] == 'z') { if (mode[0] == 'z') {
_impl = new zlibFile; _impl = new GzipFile;
++mode; ++mode;
} }
#endif #endif
if (!_impl) { if (!_impl) {
_impl = new stdFile; _impl = new StdioFile;
} }
char path[MAXPATHLEN]; char path[MAXPATHLEN];
snprintf(path, sizeof(path), "%s/%s", directory, filename); snprintf(path, sizeof(path), "%s/%s", directory, filename);

View File

@ -69,21 +69,26 @@ void Game::run() {
break; break;
} }
if (_res.isAmiga()) {
displayTitleScreenAmiga();
_stub->setScreenSize(Video::GAMESCREEN_W, Video::GAMESCREEN_H);
}
while (!_stub->_pi.quit) { while (!_stub->_pi.quit) {
if (_res.isDOS()) { switch (_res._type) {
case kResourceTypeDOS:
_mix.playMusic(1); _mix.playMusic(1);
_menu.handleTitleScreen(); _menu.handleTitleScreen();
if (_menu._selectedOption == Menu::MENU_OPTION_ITEM_QUIT) { if (_menu._selectedOption == Menu::MENU_OPTION_ITEM_QUIT || _stub->_pi.quit) {
_stub->_pi.quit = true;
break; break;
} }
_skillLevel = _menu._skill; _skillLevel = _menu._skill;
_currentLevel = _menu._level; _currentLevel = _menu._level;
_mix.stopMusic(); _mix.stopMusic();
break;
case kResourceTypeAmiga:
displayTitleScreenAmiga();
_stub->setScreenSize(Video::GAMESCREEN_W, Video::GAMESCREEN_H);
break;
}
if (_stub->_pi.quit) {
break;
} }
if (_currentLevel == 7) { if (_currentLevel == 7) {
_vid.fadeOut(); _vid.fadeOut();
@ -328,7 +333,7 @@ void Game::playCutscene(int id) {
_cut.play(); _cut.play();
} }
if (id == 0x3D) { if (id == 0x3D) {
_cut.startCredits(); _cut.playCredits();
} }
_mix.stopMusic(); _mix.stopMusic();
} }

View File

@ -46,6 +46,26 @@ inline uint32_t READ_LE_UINT32(const void *ptr) {
return (b[3] << 24) | (b[2] << 16) | (b[1] << 8) | b[0]; return (b[3] << 24) | (b[2] << 16) | (b[1] << 8) | b[0];
} }
inline int8_t ADDC_S8(int a, int b) {
a += b;
if (a < -128) {
a = -128;
} else if (a > 127) {
a = 127;
}
return a;
}
inline int16_t ADDC_S16(int a, int b) {
a += b;
if (a < -32768) {
a = -32768;
} else if (a > 32767) {
a = 32767;
}
return a;
}
template<typename T> template<typename T>
inline void SWAP(T &a, T &b) { inline void SWAP(T &a, T &b) {
T tmp = a; T tmp = a;
@ -72,6 +92,7 @@ struct Options {
bool enable_password_menu; bool enable_password_menu;
bool fade_out_palette; bool fade_out_palette;
bool use_tiledata; bool use_tiledata;
bool use_text_cutscenes;
}; };
struct Color { struct Color {

View File

@ -19,9 +19,10 @@ static const char *USAGE =
"Usage: %s [OPTIONS]...\n" "Usage: %s [OPTIONS]...\n"
" --datapath=PATH Path to data files (default 'DATA')\n" " --datapath=PATH Path to data files (default 'DATA')\n"
" --savepath=PATH Path to save files (default '.')\n" " --savepath=PATH Path to save files (default '.')\n"
" --levelnum=NUM Start level (default '0')\n" " --levelnum=NUM Level to start from (default '0')\n"
" --fullscreen Start fullscreen\n" " --fullscreen Fullscreen display\n"
" --scaler=INDEX Graphics scaler\n" " --scaler=INDEX Graphics scaler\n"
" --language=LANG Language (fr,en,de,sp,it)\n"
; ;
static int detectVersion(FileSystem *fs) { static int detectVersion(FileSystem *fs) {
@ -80,6 +81,7 @@ static void initOptions() {
g_options.play_disabled_cutscenes = false; g_options.play_disabled_cutscenes = false;
g_options.enable_password_menu = false; g_options.enable_password_menu = false;
g_options.fade_out_palette = true; g_options.fade_out_palette = true;
g_options.use_text_cutscenes = false;
// read configuration file // read configuration file
struct { struct {
const char *name; const char *name;
@ -90,6 +92,7 @@ static void initOptions() {
{ "enable_password_menu", &g_options.enable_password_menu }, { "enable_password_menu", &g_options.enable_password_menu },
{ "fade_out_palette", &g_options.fade_out_palette }, { "fade_out_palette", &g_options.fade_out_palette },
{ "use_tiledata", &g_options.use_tiledata }, { "use_tiledata", &g_options.use_tiledata },
{ "use_text_cutscenes", &g_options.use_text_cutscenes },
{ 0, 0 } { 0, 0 }
}; };
static const char *filename = "rs.cfg"; static const char *filename = "rs.cfg";
@ -130,6 +133,7 @@ int main(int argc, char *argv[]) {
int levelNum = 0; int levelNum = 0;
int scaler = DEFAULT_SCALER; int scaler = DEFAULT_SCALER;
bool fullscreen = false; bool fullscreen = false;
int forcedLanguage = -1;
if (argc == 2) { if (argc == 2) {
// data path as the only command line argument // data path as the only command line argument
struct stat st; struct stat st;
@ -144,6 +148,7 @@ int main(int argc, char *argv[]) {
{ "levelnum", required_argument, 0, 3 }, { "levelnum", required_argument, 0, 3 },
{ "fullscreen", no_argument, 0, 4 }, { "fullscreen", no_argument, 0, 4 },
{ "scaler", required_argument, 0, 5 }, { "scaler", required_argument, 0, 5 },
{ "language", required_argument, 0, 6 },
{ 0, 0, 0, 0 } { 0, 0, 0, 0 }
}; };
int index; int index;
@ -170,6 +175,26 @@ int main(int argc, char *argv[]) {
scaler = DEFAULT_SCALER; scaler = DEFAULT_SCALER;
} }
break; break;
case 6: {
static const struct {
int lang;
const char *str;
} languages[] = {
{ LANG_FR, "FR" },
{ LANG_EN, "EN" },
{ LANG_DE, "DE" },
{ LANG_SP, "SP" },
{ LANG_IT, "IT" },
{ -1, 0 }
};
for (int i = 0; languages[i].str; ++i) {
if (strcasecmp(languages[i].str, optarg) == 0) {
forcedLanguage = languages[i].lang;
break;
}
}
}
break;
default: default:
printf(USAGE, argv[0]); printf(USAGE, argv[0]);
return 0; return 0;
@ -183,7 +208,7 @@ int main(int argc, char *argv[]) {
error("Unable to find data files, check that all required files are present"); error("Unable to find data files, check that all required files are present");
return -1; return -1;
} }
Language language = detectLanguage(&fs); const Language language = (forcedLanguage == -1) ? detectLanguage(&fs) : (Language)forcedLanguage;
SystemStub *stub = SystemStub_SDL_create(); SystemStub *stub = SystemStub_SDL_create();
Game *g = new Game(stub, &fs, savePath, levelNum, (ResourceType)version, language); Game *g = new Game(stub, &fs, savePath, levelNum, (ResourceType)version, language);
stub->init(g_caption, Video::GAMESCREEN_W, Video::GAMESCREEN_H, scaler, fullscreen); stub->init(g_caption, Video::GAMESCREEN_W, Video::GAMESCREEN_H, scaler, fullscreen);

View File

@ -101,12 +101,19 @@ void Mixer::playMusic(int num) {
return; return;
} }
} }
if (_musicType == MT_OGG && isMusicSfx(num)) { // do not play level action music with background music
return;
}
if (isMusicSfx(num)) { // level action sequence if (isMusicSfx(num)) { // level action sequence
_sfx.play(num); _sfx.play(num);
_musicType = MT_SFX; if (_sfx._playing) {
_musicType = MT_SFX;
}
} else { // cutscene } else { // cutscene
_mod.play(num); _mod.play(num);
_musicType = MT_MOD; if (_mod._playing) {
_musicType = MT_MOD;
}
} }
} }
@ -119,8 +126,7 @@ void Mixer::stopMusic() {
_mod.stop(); _mod.stop();
break; break;
case MT_OGG: case MT_OGG:
_ogg.stopTrack(); _ogg.pauseTrack();
_musicTrack = -1;
break; break;
case MT_SFX: case MT_SFX:
_sfx.stop(); _sfx.stop();
@ -133,20 +139,9 @@ void Mixer::stopMusic() {
} }
} }
static void nr(const int8_t *in, int len, int8_t *out) { void Mixer::mix(int16_t *out, int len) {
static int prev = 0;
for (int i = 0; i < len; ++i) {
const int vnr = in[i] >> 1;
out[i] = vnr + prev;
prev = vnr;
}
}
void Mixer::mix(int8_t *out, int len) {
int8_t buf[len];
memset(buf, 0, len);
if (_premixHook) { if (_premixHook) {
if (!_premixHook(_premixHookData, buf, len)) { if (!_premixHook(_premixHookData, out, len)) {
_premixHook = 0; _premixHook = 0;
_premixHookData = 0; _premixHookData = 0;
} }
@ -160,24 +155,13 @@ void Mixer::mix(int8_t *out, int len) {
break; break;
} }
const int sample = ch->chunk.getPCM(ch->chunkPos >> FRAC_BITS); const int sample = ch->chunk.getPCM(ch->chunkPos >> FRAC_BITS);
addclamp(buf[pos], sample * ch->volume / Mixer::MAX_VOLUME); out[pos] = ADDC_S16(out[pos], (sample * ch->volume / Mixer::MAX_VOLUME) << 8);
ch->chunkPos += ch->chunkInc; ch->chunkPos += ch->chunkInc;
} }
} }
} }
nr(buf, len, out);
} }
void Mixer::addclamp(int8_t& a, int b) { void Mixer::mixCallback(void *param, int16_t *buf, int len) {
int add = a + b;
if (add < -128) {
add = -128;
} else if (add > 127) {
add = 127;
}
a = add;
}
void Mixer::mixCallback(void *param, int8_t *buf, int len) {
((Mixer *)param)->mix(buf, len); ((Mixer *)param)->mix(buf, len);
} }

View File

@ -42,7 +42,7 @@ struct FileSystem;
struct SystemStub; struct SystemStub;
struct Mixer { struct Mixer {
typedef bool (*PremixHook)(void *userData, int8_t *buf, int len); typedef bool (*PremixHook)(void *userData, int16_t *buf, int len);
enum MusicType { enum MusicType {
MT_NONE, MT_NONE,
@ -79,10 +79,9 @@ struct Mixer {
void stopAll(); void stopAll();
void playMusic(int num); void playMusic(int num);
void stopMusic(); void stopMusic();
void mix(int8_t *buf, int len); void mix(int16_t *buf, int len);
static void addclamp(int8_t &a, int b); static void mixCallback(void *param, int16_t *buf, int len);
static void mixCallback(void *param, int8_t *buf, int len);
}; };
#endif // MIXER_H__ #endif // MIXER_H__

View File

@ -27,7 +27,7 @@ struct ModPlayer_impl {
ModPlug_GetSettings(&_settings); ModPlug_GetSettings(&_settings);
_settings.mFlags = MODPLUG_ENABLE_OVERSAMPLING | MODPLUG_ENABLE_NOISE_REDUCTION; _settings.mFlags = MODPLUG_ENABLE_OVERSAMPLING | MODPLUG_ENABLE_NOISE_REDUCTION;
_settings.mChannels = 1; _settings.mChannels = 1;
_settings.mBits = 8; _settings.mBits = 16;
_settings.mFrequency = rate; _settings.mFrequency = rate;
_settings.mResamplingMode = MODPLUG_RESAMPLE_FIR; _settings.mResamplingMode = MODPLUG_RESAMPLE_FIR;
ModPlug_SetSettings(&_settings); ModPlug_SetSettings(&_settings);
@ -50,18 +50,14 @@ struct ModPlayer_impl {
} }
} }
bool mix(int8_t *buf, int len) { bool mix(int16_t *buf, int len) {
memset(buf, 0, len);
if (_mf) { if (_mf) {
const int order = ModPlug_GetCurrentOrder(_mf); const int order = ModPlug_GetCurrentOrder(_mf);
if (order == 3 && _repeatIntro) { if (order == 3 && _repeatIntro) {
ModPlug_SeekOrder(_mf, 1); ModPlug_SeekOrder(_mf, 1);
_repeatIntro = false; _repeatIntro = false;
} }
const int count = ModPlug_Read(_mf, buf, len); const int count = ModPlug_Read(_mf, buf, len * sizeof(int16_t));
for (int i = 0; i < count; ++i) {
buf[i] ^= 0x80;
}
return count > 0; return count > 0;
} }
return false; return false;
@ -152,7 +148,8 @@ struct ModPlayer_impl {
void applyPortamento(int trackNum); void applyPortamento(int trackNum);
void handleEffect(int trackNum, bool tick); void handleEffect(int trackNum, bool tick);
void mixSamples(int8_t *buf, int len); void mixSamples(int8_t *buf, int len);
bool mix(int8_t *buf, int len); bool mixS8(int8_t *buf, int len);
bool mix(int16_t *buf, int len);
}; };
ModPlayer_impl::ModPlayer_impl() ModPlayer_impl::ModPlayer_impl()
@ -607,7 +604,8 @@ void ModPlayer_impl::mixSamples(int8_t *buf, int samplesLen) {
} }
while (count--) { while (count--) {
const int out = si->getPCM(pos >> FRAC_BITS); const int out = si->getPCM(pos >> FRAC_BITS);
Mixer::addclamp(*mixbuf++, out * tk->volume / 64); *mixbuf = ADDC_S8(*mixbuf, out * tk->volume / 64);
++mixbuf;
pos += deltaPos; pos += deltaPos;
} }
} }
@ -616,9 +614,8 @@ void ModPlayer_impl::mixSamples(int8_t *buf, int samplesLen) {
} }
} }
bool ModPlayer_impl::mix(int8_t *buf, int len) { bool ModPlayer_impl::mixS8(int8_t *buf, int len) {
if (_playing) { if (_playing) {
memset(buf, 0, len);
const int samplesPerTick = _mixingRate / (50 * _songTempo / 125); const int samplesPerTick = _mixingRate / (50 * _songTempo / 125);
while (len != 0) { while (len != 0) {
if (_samplesLeft == 0) { if (_samplesLeft == 0) {
@ -637,6 +634,16 @@ bool ModPlayer_impl::mix(int8_t *buf, int len) {
} }
return _playing; return _playing;
} }
bool ModPlayer_impl::mix(int16_t *samples, int len) {
int8_t buf[len];
memset(buf, 0, sizeof(buf));
const bool ret = mixS8(buf, len);
for (int i = 0; i < len; ++i) {
samples[i] = buf[i] << 8;
}
return ret;
}
#endif #endif
ModPlayer::ModPlayer(Mixer *mixer, FileSystem *fs) ModPlayer::ModPlayer(Mixer *mixer, FileSystem *fs)
@ -673,6 +680,6 @@ void ModPlayer::stop() {
} }
} }
bool ModPlayer::mixCallback(void *param, int8_t *buf, int len) { bool ModPlayer::mixCallback(void *param, int16_t *buf, int len) {
return ((ModPlayer_impl *)param)->mix(buf, len); return ((ModPlayer_impl *)param)->mix(buf, len);
} }

View File

@ -31,7 +31,7 @@ struct ModPlayer {
void play(int num); void play(int num);
void stop(); void stop();
static bool mixCallback(void *param, int8_t *buf, int len); static bool mixCallback(void *param, int16_t *buf, int len);
}; };
#endif // MOD_PLAYER_H__ #endif // MOD_PLAYER_H__

View File

@ -10,6 +10,7 @@
#include "file.h" #include "file.h"
#include "mixer.h" #include "mixer.h"
#include "ogg_player.h" #include "ogg_player.h"
#include "util.h"
#ifdef USE_TREMOR #ifdef USE_TREMOR
struct VorbisFile: File { struct VorbisFile: File {
@ -85,7 +86,7 @@ struct OggDecoder_impl {
_channels = vi->channels; _channels = vi->channels;
return true; return true;
} }
int read(int8_t *dst, int samples) { int read(int16_t *dst, int samples) {
int size = samples * _channels * sizeof(int16_t); int size = samples * _channels * sizeof(int16_t);
if (size > _readBufSize) { if (size > _readBufSize) {
_readBufSize = size; _readBufSize = size;
@ -100,22 +101,26 @@ struct OggDecoder_impl {
const int len = ov_read(&_ovf, (char *)_readBuf, size, 0); const int len = ov_read(&_ovf, (char *)_readBuf, size, 0);
if (len < 0) { if (len < 0) {
// error in decoder // error in decoder
return 0; return count;
} else if (len == 0) { } else if (len == 0) {
// loop // loop
ov_raw_seek(&_ovf, 0); ov_raw_seek(&_ovf, 0);
continue; continue;
} }
assert((len & 1) == 0);
switch (_channels) { switch (_channels) {
case 2: case 2:
assert((len & 1) == 0); assert((len & 3) == 0);
for (int i = 0; i < len / 2; i += 2) { for (int i = 0; i < len / 2; i += 2) {
Mixer::addclamp(*dst++, ((_readBuf[i] + _readBuf[i + 1]) / 2) >> 8); const int16_t s16 = (_readBuf[i] + _readBuf[i + 1]) / 2;
*dst = ADDC_S16(*dst, s16);
++dst;
} }
break; break;
case 1: case 1:
for (int i = 0; i < len / 2; ++i) { for (int i = 0; i < len / 2; ++i) {
Mixer::addclamp(*dst++, _readBuf[i] >> 8); *dst = ADDC_S16(*dst, _readBuf[i]);
++dst;
} }
break; break;
} }
@ -188,7 +193,7 @@ void OggPlayer::resumeTrack() {
#endif #endif
} }
bool OggPlayer::mix(int8_t *buf, int len) { bool OggPlayer::mix(int16_t *buf, int len) {
#ifdef USE_TREMOR #ifdef USE_TREMOR
if (_impl) { if (_impl) {
return _impl->read(buf, len) != 0; return _impl->read(buf, len) != 0;
@ -197,7 +202,7 @@ bool OggPlayer::mix(int8_t *buf, int len) {
return false; return false;
} }
bool OggPlayer::mixCallback(void *param, int8_t *buf, int len) { bool OggPlayer::mixCallback(void *param, int16_t *buf, int len) {
return ((OggPlayer *)param)->mix(buf, len); return ((OggPlayer *)param)->mix(buf, len);
} }

View File

@ -22,8 +22,8 @@ struct OggPlayer {
void pauseTrack(); void pauseTrack();
void resumeTrack(); void resumeTrack();
bool isPlaying() const { return _impl != 0; } bool isPlaying() const { return _impl != 0; }
bool mix(int8_t *buf, int len); bool mix(int16_t *buf, int len);
static bool mixCallback(void *param, int8_t *buf, int len); static bool mixCallback(void *param, int16_t *buf, int len);
Mixer *_mix; Mixer *_mix;
FileSystem *_fs; FileSystem *_fs;

View File

@ -269,28 +269,65 @@ void Resource::load_SPR_OFF(const char *fileName, uint8_t *sprData) {
error("Cannot load '%s'", _entryName); error("Cannot load '%s'", _entryName);
} }
void Resource::load_CINE() { static const char *getCineName(Language lang, ResourceType type) {
const char *baseName = 0; switch (lang) {
switch (_lang) {
case LANG_FR: case LANG_FR:
baseName = "FR_CINE"; if (type == kResourceTypeAmiga) {
break; return "FR";
case LANG_EN: }
baseName = "ENGCINE"; return "FR_";
break;
case LANG_DE: case LANG_DE:
baseName = "GERCINE"; return "GER";
break;
case LANG_SP: case LANG_SP:
baseName = "SPACINE"; return "SPA";
break;
case LANG_IT: case LANG_IT:
baseName = "ITACINE"; return "ITA";
break; case LANG_EN:
default:
return "ENG";
}
}
void Resource::load_CINE() {
const char *prefix = getCineName(_lang, _type);
debug(DBG_RES, "Resource::load_CINE('%s')", prefix);
if (_type == kResourceTypeAmiga) {
if (_isDemo) {
// file not present in demo data files
return;
}
if (_cine_txt == 0) {
snprintf(_entryName, sizeof(_entryName), "%sCINE.TXT", prefix);
File f;
if (f.open(_entryName, "rb", _fs)) {
const int len = f.size();
_cine_txt = (uint8_t *)malloc(len + 1);
if (!_cine_txt) {
error("Unable to allocate cinematics text data");
}
f.read(_cine_txt, len);
if (f.ioErr()) {
error("I/O error when reading '%s'", _entryName);
}
_cine_txt[len] = 0;
uint8_t *p = _cine_txt;
for (int i = 0; i < NUM_CUTSCENE_TEXTS; ++i) {
_cineStrings[i] = p;
uint8_t *sep = (uint8_t *)memchr(p, '\n', &_cine_txt[len] - p);
if (!sep) {
break;
}
p = sep + 1;
}
}
if (!_cine_txt) {
error("Cannot load '%s'", _entryName);
}
}
return;
} }
debug(DBG_RES, "Resource::load_CINE('%s')", baseName);
if (_cine_off == 0) { if (_cine_off == 0) {
snprintf(_entryName, sizeof(_entryName), "%s.BIN", baseName); snprintf(_entryName, sizeof(_entryName), "%sCINE.BIN", prefix);
File f; File f;
if (f.open(_entryName, "rb", _fs)) { if (f.open(_entryName, "rb", _fs)) {
int len = f.size(); int len = f.size();
@ -310,7 +347,7 @@ void Resource::load_CINE() {
} }
} }
if (_cine_txt == 0) { if (_cine_txt == 0) {
snprintf(_entryName, sizeof(_entryName), "%s.TXT", baseName); snprintf(_entryName, sizeof(_entryName), "%sCINE.TXT", prefix);
File f; File f;
if (f.open(_entryName, "rb", _fs)) { if (f.open(_entryName, "rb", _fs)) {
int len = f.size(); int len = f.size();

View File

@ -90,6 +90,7 @@ struct Resource {
enum { enum {
NUM_SFXS = 66, NUM_SFXS = 66,
NUM_BANK_BUFFERS = 50, NUM_BANK_BUFFERS = 50,
NUM_CUTSCENE_TEXTS = 117,
NUM_SPRITES = 1287 NUM_SPRITES = 1287
}; };
@ -135,6 +136,7 @@ struct Resource {
uint8_t _numSfx; uint8_t _numSfx;
uint8_t *_cmd; uint8_t *_cmd;
uint8_t *_pol; uint8_t *_pol;
uint8_t *_cineStrings[NUM_CUTSCENE_TEXTS];
uint8_t *_cine_off; uint8_t *_cine_off;
uint8_t *_cine_txt; uint8_t *_cine_txt;
char **_extTextsTable; char **_extTextsTable;
@ -209,7 +211,7 @@ struct Resource {
const int offset = READ_BE_UINT16(_cine_off + num * 2); const int offset = READ_BE_UINT16(_cine_off + num * 2);
return _cine_txt + offset; return _cine_txt + offset;
} }
return 0; return (num >= 0 && num < NUM_CUTSCENE_TEXTS) ? _cineStrings[num] : 0;
} }
const char *getMenuString(int num) { const char *getMenuString(int num) {
return (num >= 0 && num < LocaleData::LI_NUM) ? _textsTable[num] : ""; return (num >= 0 && num < LocaleData::LI_NUM) ? _textsTable[num] : "";

3
rs.cfg
View File

@ -12,3 +12,6 @@ fade_out_palette=true
# use .BNQ & .LEV datafiles (tile based rendering) for backgrounds (instead of .MAP) # use .BNQ & .LEV datafiles (tile based rendering) for backgrounds (instead of .MAP)
use_tiledata=false use_tiledata=false
# display text instead of playing the polygon cutscenes (french only)
use_text_cutscenes=false

View File

@ -102,10 +102,10 @@ void SeqDemuxer::readPalette(uint8_t *dst) {
_f->read(dst, 256 * 3); _f->read(dst, 256 * 3);
} }
void SeqDemuxer::readAudioS8(uint8_t *dst) { void SeqDemuxer::readAudio(int16_t *dst) {
_f->seek(_frameOffset + _audioDataOffset); _f->seek(_frameOffset + _audioDataOffset);
for (int i = 0; i < kAudioBufferSize; ++i) { for (int i = 0; i < kAudioBufferSize; ++i) {
dst[i] = _f->readUint16BE() >> 8; dst[i] = _f->readUint16BE();
} }
} }
@ -246,9 +246,9 @@ void SeqPlayer::play(File *f) {
if (_demux._audioDataSize != 0) { if (_demux._audioDataSize != 0) {
SoundBufferQueue *sbq = (SoundBufferQueue *)malloc(sizeof(SoundBufferQueue)); SoundBufferQueue *sbq = (SoundBufferQueue *)malloc(sizeof(SoundBufferQueue));
if (sbq) { if (sbq) {
sbq->data = (uint8_t *)malloc(SeqDemuxer::kAudioBufferSize); sbq->data = (int16_t *)calloc(SeqDemuxer::kAudioBufferSize, sizeof(int16_t));
if (sbq->data) { if (sbq->data) {
_demux.readAudioS8(sbq->data); _demux.readAudio(sbq->data);
sbq->size = SeqDemuxer::kAudioBufferSize; sbq->size = SeqDemuxer::kAudioBufferSize;
sbq->read = 0; sbq->read = 0;
sbq->next = 0; sbq->next = 0;
@ -332,7 +332,7 @@ void SeqPlayer::play(File *f) {
} }
} }
bool SeqPlayer::mix(int8_t *buf, int samples) { bool SeqPlayer::mix(int16_t *buf, int samples) {
if (_soundQueuePreloadSize < kSoundPreloadSize) { if (_soundQueuePreloadSize < kSoundPreloadSize) {
return true; return true;
} }
@ -350,7 +350,7 @@ bool SeqPlayer::mix(int8_t *buf, int samples) {
return true; return true;
} }
bool SeqPlayer::mixCallback(void *param, int8_t *buf, int len) { bool SeqPlayer::mixCallback(void *param, int16_t *buf, int len) {
return ((SeqPlayer *)param)->mix(buf, len); return ((SeqPlayer *)param)->mix(buf, len);
} }

View File

@ -28,7 +28,7 @@ struct SeqDemuxer {
void fillBuffer(int num, int offset, int size); void fillBuffer(int num, int offset, int size);
void clearBuffer(int num); void clearBuffer(int num);
void readPalette(uint8_t *dst); void readPalette(uint8_t *dst);
void readAudioS8(uint8_t *dst); void readAudio(int16_t *dst);
int _frameOffset; int _frameOffset;
int _audioDataOffset; int _audioDataOffset;
@ -55,7 +55,7 @@ struct SeqPlayer {
static const char *_namesTable[]; static const char *_namesTable[];
struct SoundBufferQueue { struct SoundBufferQueue {
uint8_t *data; int16_t *data;
int size; int size;
int read; int read;
SoundBufferQueue *next; SoundBufferQueue *next;
@ -66,8 +66,8 @@ struct SeqPlayer {
void setBackBuffer(uint8_t *buf) { _buf = buf; } void setBackBuffer(uint8_t *buf) { _buf = buf; }
void play(File *f); void play(File *f);
bool mix(int8_t *buf, int len); bool mix(int16_t *buf, int len);
static bool mixCallback(void *param, int8_t *buf, int len); static bool mixCallback(void *param, int16_t *buf, int len);
SystemStub *_stub; SystemStub *_stub;
uint8_t *_buf; uint8_t *_buf;

View File

@ -127,7 +127,8 @@ void SfxPlayer::mixSamples(int8_t *buf, int samplesLen) {
} }
while (count--) { while (count--) {
const int out = si->getPCM(pos >> FRAC_BITS); const int out = si->getPCM(pos >> FRAC_BITS);
Mixer::addclamp(*mixbuf++, out * si->vol / 64); *mixbuf = ADDC_S8(*mixbuf, out * si->vol / 64);
++mixbuf;
pos += deltaPos; pos += deltaPos;
} }
} }
@ -138,7 +139,6 @@ void SfxPlayer::mixSamples(int8_t *buf, int samplesLen) {
bool SfxPlayer::mix(int8_t *buf, int len) { bool SfxPlayer::mix(int8_t *buf, int len) {
if (_playing) { if (_playing) {
memset(buf, 0, len);
const int samplesPerTick = _mix->getSampleRate() / 50; const int samplesPerTick = _mix->getSampleRate() / 50;
while (len != 0) { while (len != 0) {
if (_samplesLeft == 0) { if (_samplesLeft == 0) {
@ -158,6 +158,12 @@ bool SfxPlayer::mix(int8_t *buf, int len) {
return _playing; return _playing;
} }
bool SfxPlayer::mixCallback(void *param, int8_t *buf, int len) { bool SfxPlayer::mixCallback(void *param, int16_t *samples, int len) {
return ((SfxPlayer *)param)->mix(buf, len); int8_t buf[len];
memset(buf, 0, sizeof(buf));
const bool ret = ((SfxPlayer *)param)->mix(buf, len);
for (int i = 0; i < len; ++i) {
samples[i] = buf[i] << 8;
}
return ret;
} }

View File

@ -81,10 +81,10 @@ struct SfxPlayer {
void stop(); void stop();
void playSample(int channel, const uint8_t *sampleData, uint16_t period); void playSample(int channel, const uint8_t *sampleData, uint16_t period);
void handleTick(); void handleTick();
bool mix(int8_t *buf, int len); void mixSamples(int8_t *samples, int samplesLen);
void mixSamples(int8_t *buf, int samplesLen);
static bool mixCallback(void *param, int8_t *buf, int len); bool mix(int8_t *buf, int len);
static bool mixCallback(void *param, int16_t *buf, int len);
}; };
#endif // SFX_PLAYER_H__ #endif // SFX_PLAYER_H__

View File

@ -188,7 +188,7 @@ const uint16_t Cutscene::_sinTable[] = {
0xFFDD, 0xFFE1, 0xFFE6, 0xFFEA, 0xFFEF, 0xFFF3, 0xFFF8, 0xFFFC 0xFFDD, 0xFFE1, 0xFFE6, 0xFFEA, 0xFFEF, 0xFFF3, 0xFFF8, 0xFFFC
}; };
const uint8_t Cutscene::_creditsData[] = { const uint8_t Cutscene::_creditsDataDOS[] = {
0xFE, 0x14, 0x00, 0x01, 0x00, 0x04, 0x20, 0x20, 0x54, 0x68, 0x65, 0x20, 0x46, 0x6C, 0x61, 0x73, 0xFE, 0x14, 0x00, 0x01, 0x00, 0x04, 0x20, 0x20, 0x54, 0x68, 0x65, 0x20, 0x46, 0x6C, 0x61, 0x73,
0x68, 0x42, 0x61, 0x63, 0x6B, 0x20, 0x54, 0x65, 0x61, 0x6D, 0x20, 0x69, 0x73, 0x2E, 0x2E, 0x2E, 0x68, 0x42, 0x61, 0x63, 0x6B, 0x20, 0x54, 0x65, 0x61, 0x6D, 0x20, 0x69, 0x73, 0x2E, 0x2E, 0x2E,
0xFE, 0x32, 0xFE, 0x14, 0x00, 0x01, 0x00, 0x07, 0x00, 0x01, 0x00, 0x05, 0x20, 0x20, 0x49, 0x6E, 0xFE, 0x32, 0xFE, 0x14, 0x00, 0x01, 0x00, 0x07, 0x00, 0x01, 0x00, 0x05, 0x20, 0x20, 0x49, 0x6E,
@ -356,6 +356,164 @@ const uint8_t Cutscene::_creditsData[] = {
0x20, 0x20, 0x20, 0x20, 0x20, 0x7C, 0xFE, 0x50, 0xFE, 0xFF, 0x00, 0xFF 0x20, 0x20, 0x20, 0x20, 0x20, 0x7C, 0xFE, 0x50, 0xFE, 0xFF, 0x00, 0xFF
}; };
const uint8_t Cutscene::_creditsDataAmiga[] = {
0xFE, 0x14, 0x00, 0x01, 0x00, 0x08, 0x20, 0x20, 0x20, 0x20, 0x20, 0x54, 0x68, 0x65, 0x20, 0x46,
0x6C, 0x61, 0x73, 0x68, 0x42, 0x61, 0x63, 0x6B, 0x20, 0x54, 0x65, 0x61, 0x6D, 0x20, 0x69, 0x73,
0x2E, 0x2E, 0x2E, 0xFE, 0x32, 0x00, 0x01, 0x00, 0x08, 0x20, 0x20, 0x41, 0x6D, 0x69, 0x67, 0x61,
0x20, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x3A, 0x7C, 0xFE, 0x14, 0x7C, 0x7C, 0xFE, 0x14,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x42,
0x65, 0x6E, 0x6F, 0x69, 0x73, 0x74, 0x20, 0x41, 0x72, 0x6F, 0x6E, 0x20, 0x20, 0x20, 0x20, 0x7C,
0xFE, 0x14, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x54, 0x68, 0x69, 0x65, 0x72, 0x72, 0x79, 0x20, 0x4C, 0x65, 0x76, 0x61, 0x73, 0x74, 0x72,
0x65, 0x20, 0x7C, 0xFE, 0x14, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x54, 0x68, 0x69, 0x65, 0x72, 0x72, 0x79, 0x20, 0x50, 0x65, 0x72, 0x72,
0x65, 0x61, 0x75, 0x20, 0x20, 0x7C, 0xFE, 0x14, 0xFE, 0x14, 0x00, 0x01, 0x00, 0x07, 0x00, 0x01,
0x00, 0x09, 0x20, 0x20, 0x50, 0x72, 0x6F, 0x67, 0x72, 0x61, 0x6D, 0x6D, 0x65, 0x72, 0x73, 0x3A,
0x7C, 0x7C, 0xFE, 0x14, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x42, 0x65, 0x6E, 0x6F, 0x69, 0x73, 0x74, 0x20, 0x41, 0x72, 0x6F, 0x6E,
0x20, 0x20, 0x20, 0x20, 0x7C, 0xFE, 0x14, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x50, 0x68, 0x69, 0x6C, 0x69, 0x70, 0x70, 0x65, 0x20, 0x43,
0x68, 0x61, 0x73, 0x74, 0x65, 0x6C, 0x20, 0x7C, 0xFE, 0x14, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x50, 0x61, 0x75,
0x6C, 0x20, 0x43, 0x75, 0x69, 0x73, 0x73, 0x65, 0x74, 0x20, 0x7C, 0xFE, 0x14, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x46, 0x72, 0x65, 0x64,
0x65, 0x72, 0x69, 0x63, 0x20, 0x53, 0x61, 0x76, 0x6F, 0x69, 0x72, 0x20, 0x20, 0x7C, 0xFE, 0x14,
0xFE, 0x14, 0x00, 0x01, 0x00, 0x07, 0x20, 0x20, 0x47, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x20,
0x41, 0x72, 0x74, 0x69, 0x73, 0x74, 0x73, 0x3A, 0x7C, 0x7C, 0xFE, 0x14, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x50, 0x61, 0x74, 0x72, 0x69,
0x63, 0x6B, 0x20, 0x44, 0x61, 0x68, 0x65, 0x72, 0x20, 0x20, 0x20, 0x20, 0x7C, 0xFE, 0x14, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x54, 0x68,
0x69, 0x65, 0x72, 0x72, 0x79, 0x20, 0x4C, 0x65, 0x76, 0x61, 0x73, 0x74, 0x72, 0x65, 0x20, 0x7C,
0xFE, 0x14, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x44, 0x65, 0x6E, 0x69, 0x73, 0x20, 0x4D, 0x65, 0x72, 0x63, 0x69, 0x65, 0x72,
0x20, 0x20, 0x7C, 0xFE, 0x14, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x54, 0x68, 0x69, 0x65, 0x72, 0x72, 0x79, 0x20, 0x50, 0x65, 0x72, 0x72,
0x65, 0x61, 0x75, 0x20, 0x20, 0x7C, 0xFE, 0x14, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x43, 0x68, 0x72, 0x69, 0x73, 0x74, 0x69, 0x61, 0x6E, 0x20, 0x52,
0x6F, 0x62, 0x65, 0x72, 0x74, 0x20, 0x20, 0x20, 0x7C, 0xFE, 0x14, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x46, 0x61, 0x62, 0x72, 0x69, 0x63,
0x65, 0x20, 0x56, 0x69, 0x73, 0x73, 0x65, 0x72, 0x6F, 0x74, 0x20, 0x7C, 0xFE, 0x14, 0xFE, 0x14,
0x00, 0x01, 0x00, 0x07, 0x20, 0x20, 0x48, 0x61, 0x72, 0x64, 0x77, 0x61, 0x72, 0x65, 0x20, 0x45,
0x6E, 0x67, 0x69, 0x6E, 0x65, 0x65, 0x72, 0x3A, 0x7C, 0x7C, 0xFE, 0x14, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x54, 0x68, 0x69, 0x65, 0x72, 0x72,
0x79, 0x20, 0x47, 0x61, 0x65, 0x72, 0x74, 0x68, 0x6E, 0x65, 0x72, 0x20, 0x7C, 0xFE, 0x14, 0x00,
0x01, 0x00, 0x07, 0x20, 0x20, 0x53, 0x74, 0x6F, 0x72, 0x79, 0x3A, 0x7C, 0x7C, 0xFE, 0x14, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x50, 0x61, 0x75, 0x6C, 0x20, 0x43, 0x75, 0x69, 0x73, 0x73, 0x65, 0x74, 0x20, 0x20, 0x7C,
0xFE, 0x14, 0x00, 0x01, 0x00, 0x08, 0x20, 0x20, 0x4C, 0x65, 0x76, 0x65, 0x6C, 0x20, 0x44, 0x65,
0x73, 0x69, 0x67, 0x6E, 0x3A, 0x7C, 0x7C, 0xFE, 0x14, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x50, 0x61, 0x75, 0x6C, 0x20,
0x43, 0x75, 0x69, 0x73, 0x73, 0x65, 0x74, 0x20, 0x20, 0x7C, 0xFE, 0x14, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x50, 0x61, 0x74, 0x72, 0x69,
0x63, 0x6B, 0x20, 0x44, 0x61, 0x68, 0x65, 0x72, 0x20, 0x20, 0x20, 0x20, 0x7C, 0xFE, 0x14, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x44, 0x65, 0x6E, 0x69, 0x73, 0x20, 0x4D, 0x65, 0x72, 0x63, 0x69, 0x65, 0x72, 0x20, 0x20, 0x7C,
0xFE, 0x14, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x46, 0x72, 0x65, 0x64, 0x65, 0x72, 0x69, 0x63, 0x20, 0x53, 0x61, 0x76, 0x6F, 0x69, 0x72, 0x20,
0x20, 0x20, 0x7C, 0xFE, 0x14, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x46, 0x61, 0x62, 0x72, 0x69, 0x63, 0x65, 0x20, 0x56, 0x69, 0x73, 0x73,
0x65, 0x72, 0x6F, 0x74, 0x20, 0x7C, 0xFE, 0x14, 0xFE, 0x14, 0x00, 0x01, 0x00, 0x08, 0x20, 0x20,
0x4D, 0x75, 0x73, 0x69, 0x63, 0x3A, 0x7C, 0x7C, 0xFE, 0x14, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x4A, 0x65, 0x61, 0x6E,
0x20, 0x42, 0x61, 0x75, 0x64, 0x6C, 0x6F, 0x74, 0x20, 0x20, 0x7C, 0xFE, 0x14, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x52, 0x61, 0x70, 0x68,
0x61, 0x65, 0x6C, 0x20, 0x47, 0x65, 0x73, 0x71, 0x75, 0x61, 0x20, 0x20, 0x20, 0x7C, 0xFE, 0x14,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x46,
0x61, 0x62, 0x72, 0x69, 0x63, 0x65, 0x20, 0x56, 0x69, 0x73, 0x73, 0x65, 0x72, 0x6F, 0x74, 0x20,
0x7C, 0xFE, 0x14, 0xFE, 0x14, 0x00, 0x01, 0x00, 0x08, 0x20, 0x20, 0x53, 0x6F, 0x75, 0x6E, 0x64,
0x20, 0x66, 0x78, 0x3A, 0x7C, 0x7C, 0xFE, 0x14, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x42, 0x65, 0x6E, 0x6F, 0x69, 0x73, 0x74, 0x20, 0x41,
0x72, 0x6F, 0x6E, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7C, 0xFE, 0x14, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x50, 0x68, 0x69, 0x6C, 0x69, 0x70, 0x70,
0x65, 0x20, 0x43, 0x68, 0x61, 0x73, 0x74, 0x65, 0x6C, 0x20, 0x20, 0x7C, 0xFE, 0x14, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x50, 0x61, 0x75, 0x6C, 0x20, 0x43, 0x75, 0x69, 0x73, 0x73, 0x65, 0x74, 0x20, 0x20, 0x7C, 0xFE,
0x14, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x46, 0x61, 0x62, 0x72, 0x69, 0x63, 0x65, 0x20, 0x56, 0x69, 0x73, 0x73, 0x65, 0x72, 0x6F, 0x74,
0x20, 0x7C, 0xFE, 0x14, 0xFE, 0x14, 0x00, 0x01, 0x00, 0x06, 0x20, 0x20, 0x41, 0x63, 0x74, 0x6F,
0x72, 0x73, 0x3A, 0x7C, 0x7C, 0xFE, 0x14, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x42, 0x65, 0x6E, 0x6F, 0x69, 0x73, 0x74, 0x20, 0x41, 0x72,
0x6F, 0x6E, 0x20, 0x20, 0x20, 0x20, 0x7C, 0xFE, 0x14, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x50, 0x61, 0x74, 0x72, 0x69, 0x63, 0x6B, 0x20,
0x44, 0x61, 0x68, 0x65, 0x72, 0x20, 0x20, 0x20, 0x20, 0x7C, 0xFE, 0x14, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x54, 0x68, 0x69, 0x65, 0x72,
0x72, 0x79, 0x20, 0x4C, 0x65, 0x76, 0x61, 0x73, 0x74, 0x72, 0x65, 0x20, 0x7C, 0xFE, 0x14, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x44, 0x65, 0x6E, 0x69, 0x73, 0x20, 0x4D, 0x65, 0x72, 0x63, 0x69, 0x65, 0x72, 0x20, 0x20, 0x7C,
0xFE, 0x14, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x54, 0x68, 0x69, 0x65, 0x72, 0x72, 0x79, 0x20, 0x50, 0x65, 0x72, 0x72, 0x65, 0x61, 0x75,
0x20, 0x20, 0x7C, 0xFE, 0x14, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x43, 0x68, 0x72, 0x69, 0x73, 0x74, 0x69, 0x61, 0x6E, 0x20, 0x52, 0x6F, 0x62, 0x65,
0x72, 0x74, 0x20, 0x20, 0x20, 0x7C, 0xFE, 0x14, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x46, 0x61, 0x62, 0x72, 0x69, 0x63, 0x65, 0x20, 0x56,
0x69, 0x73, 0x73, 0x65, 0x72, 0x6F, 0x74, 0x20, 0x7C, 0xFE, 0x14, 0xFE, 0x14, 0x00, 0x01, 0x00,
0x0A, 0x20, 0x20, 0x56, 0x69, 0x64, 0x65, 0x6F, 0x20, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6F,
0x72, 0x73, 0x3A, 0x7C, 0x7C, 0xFE, 0x14, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x09, 0x20, 0x20, 0x20, 0x50, 0x61, 0x74, 0x72, 0x69, 0x63, 0x6B, 0x20, 0x44,
0x61, 0x68, 0x65, 0x72, 0x20, 0x20, 0x20, 0x20, 0x7C, 0xFE, 0x14, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x54, 0x68, 0x69, 0x65, 0x72, 0x72,
0x79, 0x20, 0x50, 0x65, 0x72, 0x72, 0x65, 0x61, 0x75, 0x20, 0x20, 0x7C, 0xFE, 0x14, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x46, 0x61, 0x62,
0x72, 0x69, 0x63, 0x65, 0x20, 0x56, 0x69, 0x73, 0x73, 0x65, 0x72, 0x6F, 0x74, 0x20, 0x7C, 0xFE,
0x14, 0xFE, 0x14, 0x00, 0x01, 0x00, 0x0A, 0x20, 0x20, 0x56, 0x69, 0x64, 0x65, 0x6F, 0x20, 0x43,
0x6F, 0x2D, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6F, 0x72, 0x73, 0x3A, 0x7C, 0x7C, 0xFE, 0x14,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x54,
0x68, 0x69, 0x65, 0x72, 0x72, 0x79, 0x20, 0x4C, 0x65, 0x76, 0x61, 0x73, 0x74, 0x72, 0x65, 0x20,
0x7C, 0xFE, 0x14, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x44, 0x65, 0x6E, 0x69, 0x73, 0x20, 0x4D, 0x65, 0x72, 0x63, 0x69, 0x65,
0x72, 0x20, 0x20, 0x7C, 0xFE, 0x14, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x43, 0x68, 0x72, 0x69, 0x73, 0x74, 0x69, 0x61, 0x6E, 0x20, 0x52, 0x6F, 0x62,
0x65, 0x72, 0x74, 0x20, 0x20, 0x20, 0x7C, 0xFE, 0x14, 0xFE, 0x14, 0x00, 0x01, 0x00, 0x0A, 0x20,
0x20, 0x56, 0x69, 0x64, 0x65, 0x6F, 0x20, 0x73, 0x66, 0x78, 0x3A, 0x7C, 0x7C, 0xFE, 0x14, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x50, 0x61, 0x75, 0x6C, 0x20, 0x43, 0x75, 0x69, 0x73, 0x73, 0x65, 0x74, 0x20, 0x20, 0x7C,
0xFE, 0x14, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x54, 0x68, 0x69, 0x65, 0x72, 0x72, 0x79, 0x20, 0x50, 0x65, 0x72, 0x72, 0x65, 0x61, 0x75,
0x20, 0x20, 0x7C, 0xFE, 0x14, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x46, 0x61, 0x62, 0x72, 0x69, 0x63, 0x65, 0x20, 0x56, 0x69, 0x73, 0x73,
0x65, 0x72, 0x6F, 0x74, 0x20, 0x7C, 0xFE, 0x14, 0xFE, 0x14, 0x00, 0x01, 0x00, 0x07, 0x20, 0x20,
0x54, 0x65, 0x73, 0x74, 0x65, 0x72, 0x73, 0x3A, 0x7C, 0x7C, 0xFE, 0x14, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x50, 0x68, 0x69, 0x6C,
0x20, 0x42, 0x72, 0x61, 0x64, 0x6C, 0x65, 0x79, 0x20, 0x20, 0x20, 0x20, 0x7C, 0xFE, 0x14, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x50, 0x61, 0x74, 0x72, 0x69,
0x63, 0x69, 0x61, 0x20, 0x43, 0x75, 0x69, 0x73, 0x73, 0x65, 0x74, 0x20, 0x20, 0x20, 0x20, 0x7C,
0xFE, 0x14, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x53, 0x69, 0x6D, 0x6F, 0x6E, 0x20, 0x48, 0x61, 0x64, 0x6C, 0x69, 0x6E, 0x67, 0x74, 0x6F,
0x6E, 0x20, 0x7C, 0xFE, 0x14, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x44, 0x61, 0x6E, 0x69, 0x65, 0x6C, 0x20, 0x4C, 0x6C, 0x65, 0x77, 0x65, 0x6C,
0x6C, 0x79, 0x6E, 0x20, 0x20, 0x7C, 0xFE, 0x14, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x4A, 0x65, 0x61, 0x6E, 0x2D, 0x50, 0x69, 0x65, 0x72, 0x72, 0x65, 0x20, 0x4C, 0x75, 0x63,
0x6B, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7C, 0xFE, 0x14, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x4D, 0x61, 0x72, 0x74, 0x69, 0x6E, 0x20,
0x53, 0x6D, 0x69, 0x74, 0x68, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7C, 0xFE, 0x14, 0xFE, 0x14,
0x00, 0x01, 0x00, 0x08, 0x20, 0x20, 0x4D, 0x61, 0x6E, 0x79, 0x20, 0x74, 0x68, 0x61, 0x6E, 0x6B,
0x73, 0x20, 0x74, 0x6F, 0x2E, 0x2E, 0x2E, 0x7C, 0x7C, 0xFE, 0x14, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x4C, 0x6F, 0x72, 0x69, 0x20, 0x43,
0x68, 0x72, 0x69, 0x73, 0x74, 0x65, 0x6E, 0x73, 0x65, 0x6E, 0x20, 0x7C, 0xFE, 0x14, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x50, 0x61, 0x74, 0x72, 0x69, 0x63, 0x69,
0x61, 0x20, 0x43, 0x75, 0x69, 0x73, 0x73, 0x65, 0x74, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7C, 0xFE,
0x14, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x41, 0x6E, 0x6E, 0x65, 0x2D, 0x4D,
0x61, 0x72, 0x69, 0x65, 0x20, 0x4A, 0x6F, 0x61, 0x73, 0x73, 0x69, 0x6D, 0x20, 0x20, 0x20, 0x20,
0x20, 0x7C, 0xFE, 0x14, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x4A, 0x65, 0x61, 0x6E,
0x2D, 0x50, 0x69, 0x65, 0x72, 0x72, 0x65, 0x20, 0x4C, 0x75, 0x63, 0x6B, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x7C, 0xFE, 0x14, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x4D, 0x61, 0x72, 0x63, 0x20, 0x4D, 0x69, 0x6E, 0x69, 0x65,
0x72, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7C, 0xFE, 0x14, 0xFE, 0x14, 0x00, 0x01, 0x00, 0x05,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x44, 0x69, 0x72, 0x65, 0x63,
0x74, 0x65, 0x64, 0x20, 0x62, 0x79, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x7C, 0x7C, 0xFE, 0x14, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x50, 0x61,
0x75, 0x6C, 0x20, 0x43, 0x75, 0x69, 0x73, 0x73, 0x65, 0x74, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x7C, 0xFE, 0x14, 0xFE, 0x14, 0x00, 0x01, 0x00, 0x05, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x50, 0x72, 0x6F, 0x64, 0x75, 0x63, 0x65, 0x64, 0x20,
0x62, 0x79, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7C, 0x7C, 0xFE,
0x14, 0x20, 0x20, 0x20, 0x44, 0x65, 0x6C, 0x70, 0x68, 0x69, 0x6E, 0x65, 0x20, 0x53, 0x6F, 0x66,
0x74, 0x77, 0x61, 0x72, 0x65, 0x20, 0x2F, 0x20, 0x55, 0x53, 0x20, 0x47, 0x4F, 0x4C, 0x44, 0x20,
0x20, 0x7C, 0xFE, 0x14, 0x20, 0x7C, 0x20, 0x7C, 0x20, 0x7C, 0x20, 0x7C, 0x20, 0x7C, 0x20, 0x7C,
0x20, 0x7C, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x28, 0x63, 0x29, 0x4D,
0x43, 0x4D, 0x58, 0x43, 0x49, 0x49, 0x49, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x7C, 0xFE, 0x14, 0xFE, 0x28, 0x00, 0xFF
};
const uint16_t Cutscene::_creditsCutSeq[] = { const uint16_t Cutscene::_creditsCutSeq[] = {
0x00, 0x05, 0x2F, 0x32, 0x36, 0x3E, 0x30, 0x39, 0x3F, 0x14, 0x34, 0xFFFF 0x00, 0x05, 0x2F, 0x32, 0x36, 0x3E, 0x30, 0x39, 0x3F, 0x14, 0x34, 0xFFFF
}; };
@ -649,6 +807,29 @@ const uint8_t Cutscene::_protectionShapeData[] = {
0x03, 0xFD, 0x06, 0xFC, 0x00, 0x19 0x03, 0xFD, 0x06, 0xFC, 0x00, 0x19
}; };
const Cutscene::Text Cutscene::_frTextsTable[] = {
{ 1, "VOUS RAMASSEZ L'HOLOCUBE" },
{ 2, "VOUS RAMASSEZ LA CLE" },
{ 4, "VOUS RAMASSEZ LE PISTOLET" },
{ 5, "VOTRE BOUCLIER EST RECHARGE" },
{ 10, "VOUS RAMASSEZ||LA CARTE DE CREDITS" },
{ 14, "LA PILE EST RECHARGEE" },
{ 15, "VOUS RAMASSEZ LA PILE" },
{ 16, "VOUS RAMASSEZ LE TELEPORTEUR" },
{ 18, "VOUS RAMASSEZ LA CARTE ID" },
{ 21, "VOUS LUI DONNEZ LE TELEPORTEUR" },
{ 32, "LA RECEPTIONNISTE VOUS DONNE||UN PAQUET" },
{ 33, "VOUS DONNEZ LE COLIS" },
{ 34, "LE GOUVERNEUR VOUS DONNE||LA CARTE DE TRAVAIL" },
{ 35, "LE FAUSSAIRE VOUS DONNE||LA FAUSSE CARTE ID" },
{ 36, "VOUS RAMASSEZ LE FUSIBLE" },
{ 43, "VOUS LUI TENDEZ||VOS PAPIERS" },
{ 44, "VOUS LUI DONNEZ L'ARGENT" },
{ 49, "IL VOUS DONNE||UNE CEINTURE ANTI-G" },
{ 60, "VOUS RAMASSEZ LE TELE RECEPTEUR" },
{ -1, 0 }
};
const Level Game::_gameLevels[] = { const Level Game::_gameLevels[] = {
{ "level1", "level1", "level1", 0x00, 1, 3 }, { "level1", "level1", "level1", 0x00, 1, 3 },
{ "level2", "level2", "level2", 0x2F, 1, 4 }, { "level2", "level2", "level2", 0x2F, 1, 4 },

View File

@ -45,7 +45,7 @@ struct PlayerInput {
}; };
struct SystemStub { struct SystemStub {
typedef void (*AudioCallback)(void *param, int8_t *stream, int len); typedef void (*AudioCallback)(void *param, int16_t *stream, int len);
PlayerInput _pi; PlayerInput _pi;

View File

@ -9,26 +9,31 @@
#include "systemstub.h" #include "systemstub.h"
#include "util.h" #include "util.h"
struct SystemStub_SDL : SystemStub { static const int kAudioHz = 22050;
enum { static const int kJoystickCommitValue = 3200;
MAX_BLIT_RECTS = 200,
SOUND_SAMPLE_RATE = 22050,
JOYSTICK_COMMIT_VALUE = 3200
};
struct SystemStub_SDL : SystemStub {
#if SDL_VERSION_ATLEAST(2, 0, 0)
SDL_Window *_window;
SDL_Renderer *_renderer;
SDL_Texture *_texture;
#else
SDL_Surface *_surface;
#endif
SDL_PixelFormat *_fmt;
const char *_caption;
uint16_t *_screenBuffer; uint16_t *_screenBuffer;
uint16_t *_fadeScreenBuffer; uint16_t *_fadeScreenBuffer;
SDL_Surface *_screenSurface;
bool _fullscreen; bool _fullscreen;
int _currentScaler; int _scaler;
uint8_t _overscanColor; uint8_t _overscanColor;
uint16_t _pal[256]; uint16_t _pal[256];
int _screenW, _screenH; int _screenW, _screenH;
SDL_Joystick *_joystick; SDL_Joystick *_joystick;
SDL_Rect _blitRects[MAX_BLIT_RECTS]; SDL_Rect _blitRects[200];
int _numBlitRects; int _numBlitRects;
bool _fadeOnUpdateScreen; bool _fadeOnUpdateScreen;
void (*_audioCbProc)(void *, int8_t *, int); void (*_audioCbProc)(void *, int16_t *, int);
void *_audioCbData; void *_audioCbData;
int _screenshot; int _screenshot;
@ -53,11 +58,11 @@ struct SystemStub_SDL : SystemStub {
virtual void unlockAudio(); virtual void unlockAudio();
void processEvent(const SDL_Event &ev, bool &paused); void processEvent(const SDL_Event &ev, bool &paused);
void prepareGfxMode(); void prepareGraphics();
void cleanupGfxMode(); void cleanupGraphics();
void switchGfxMode(bool fullscreen, uint8_t scaler); void changeGraphics(bool fullscreen, uint8_t scaler);
void flipGfx(); void flipGraphics();
void forceGfxRedraw(); void forceGraphicsRedraw();
void drawRect(SDL_Rect *rect, uint8_t color, uint16_t *dst, uint16_t dstPitch); void drawRect(SDL_Rect *rect, uint8_t color, uint16_t *dst, uint16_t dstPitch);
}; };
@ -68,14 +73,15 @@ SystemStub *SystemStub_SDL_create() {
void SystemStub_SDL::init(const char *title, int w, int h, int scaler, bool fullscreen) { void SystemStub_SDL::init(const char *title, int w, int h, int scaler, bool fullscreen) {
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_JOYSTICK); SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_JOYSTICK);
SDL_ShowCursor(SDL_DISABLE); SDL_ShowCursor(SDL_DISABLE);
SDL_WM_SetCaption(title, NULL); _caption = title;
memset(&_pi, 0, sizeof(_pi)); memset(&_pi, 0, sizeof(_pi));
_screenBuffer = 0; _screenBuffer = 0;
_fadeScreenBuffer = 0; _fadeScreenBuffer = 0;
_fadeOnUpdateScreen = false; _fadeOnUpdateScreen = false;
_fullscreen = fullscreen; _fullscreen = fullscreen;
_currentScaler = scaler; _scaler = scaler;
memset(_pal, 0, sizeof(_pal)); memset(_pal, 0, sizeof(_pal));
_screenW = _screenH = 0;
setScreenSize(w, h); setScreenSize(w, h);
_joystick = NULL; _joystick = NULL;
if (SDL_NumJoysticks() > 0) { if (SDL_NumJoysticks() > 0) {
@ -85,9 +91,10 @@ void SystemStub_SDL::init(const char *title, int w, int h, int scaler, bool full
} }
void SystemStub_SDL::destroy() { void SystemStub_SDL::destroy() {
cleanupGfxMode(); cleanupGraphics();
if (SDL_JoystickOpened(0)) { if (_joystick) {
SDL_JoystickClose(_joystick); SDL_JoystickClose(_joystick);
_joystick = 0;
} }
SDL_Quit(); SDL_Quit();
} }
@ -108,7 +115,7 @@ void SystemStub_SDL::setScreenSize(int w, int h) {
} }
_screenW = w; _screenW = w;
_screenH = h; _screenH = h;
prepareGfxMode(); prepareGraphics();
} }
void SystemStub_SDL::setPalette(const uint8_t *pal, int n) { void SystemStub_SDL::setPalette(const uint8_t *pal, int n) {
@ -117,16 +124,16 @@ void SystemStub_SDL::setPalette(const uint8_t *pal, int n) {
uint8_t r = pal[i * 3 + 0]; uint8_t r = pal[i * 3 + 0];
uint8_t g = pal[i * 3 + 1]; uint8_t g = pal[i * 3 + 1];
uint8_t b = pal[i * 3 + 2]; uint8_t b = pal[i * 3 + 2];
_pal[i] = SDL_MapRGB(_screenSurface->format, r, g, b); _pal[i] = SDL_MapRGB(_fmt, r, g, b);
} }
} }
void SystemStub_SDL::setPaletteEntry(int i, const Color *c) { void SystemStub_SDL::setPaletteEntry(int i, const Color *c) {
_pal[i] = SDL_MapRGB(_screenSurface->format, c->r, c->g, c->b); _pal[i] = SDL_MapRGB(_fmt, c->r, c->g, c->b);
} }
void SystemStub_SDL::getPaletteEntry(int i, Color *c) { void SystemStub_SDL::getPaletteEntry(int i, Color *c) {
SDL_GetRGB(_pal[i], _screenSurface->format, &c->r, &c->g, &c->b); SDL_GetRGB(_pal[i], _fmt, &c->r, &c->g, &c->b);
} }
void SystemStub_SDL::setOverscanColor(int i) { void SystemStub_SDL::setOverscanColor(int i) {
@ -134,7 +141,7 @@ void SystemStub_SDL::setOverscanColor(int i) {
} }
void SystemStub_SDL::copyRect(int x, int y, int w, int h, const uint8_t *buf, int pitch) { void SystemStub_SDL::copyRect(int x, int y, int w, int h, const uint8_t *buf, int pitch) {
if (_numBlitRects >= MAX_BLIT_RECTS) { if (_numBlitRects >= ARRAYSIZE(_blitRects)) {
warning("SystemStub_SDL::copyRect() Too many blit rects, you may experience graphical glitches"); warning("SystemStub_SDL::copyRect() Too many blit rects, you may experience graphical glitches");
} else { } else {
// extend the dirty region by 1 pixel for scalers accessing 'outer' pixels // extend the dirty region by 1 pixel for scalers accessing 'outer' pixels
@ -211,24 +218,39 @@ static uint16_t blendPixel16(uint16_t colorSrc, uint16_t colorDst, uint32_t mask
} }
void SystemStub_SDL::updateScreen(int shakeOffset) { void SystemStub_SDL::updateScreen(int shakeOffset) {
const int mul = _scalers[_currentScaler].factor; #if SDL_VERSION_ATLEAST(2, 0, 0)
// _fadeOnUpdateScreen
SDL_UpdateTexture(_texture, 0, _screenBuffer + _screenW + 1, _screenW * sizeof(uint16_t));
SDL_RenderClear(_renderer);
if (shakeOffset != 0) {
SDL_Rect r;
r.x = 0;
r.y = shakeOffset * _scalers[_scaler].factor;
SDL_GetRendererOutputSize(_renderer, &r.w, &r.h);
r.h -= r.y;
SDL_RenderCopy(_renderer, _texture, 0, &r);
} else {
SDL_RenderCopy(_renderer, _texture, 0, 0);
}
SDL_RenderPresent(_renderer);
#else
const int mul = _scalers[_scaler].factor;
if (_fadeOnUpdateScreen) { if (_fadeOnUpdateScreen) {
const int tempScreenBufferSize = (_screenH + 2) * (_screenW + 2) * sizeof(uint16_t); const int tempScreenBufferSize = (_screenH + 2) * (_screenW + 2) * sizeof(uint16_t);
uint16_t *tempScreenBuffer = (uint16_t *)calloc(tempScreenBufferSize, 1); uint16_t *tempScreenBuffer = (uint16_t *)calloc(tempScreenBufferSize, 1);
assert(tempScreenBuffer); assert(tempScreenBuffer);
const SDL_PixelFormat *pf = _screenSurface->format; const uint32_t colorMask = (_fmt->Gmask << 16) | (_fmt->Rmask | _fmt->Bmask);
const uint32_t colorMask = (pf->Gmask << 16) | (pf->Rmask | pf->Bmask);
const uint16_t *screenBuffer = _screenBuffer + _screenW + 1; const uint16_t *screenBuffer = _screenBuffer + _screenW + 1;
for (int i = 1; i <= 16; ++i) { for (int i = 1; i <= 16; ++i) {
for (int x = 0; x < _screenH * _screenW; ++x) { for (int x = 0; x < _screenH * _screenW; ++x) {
tempScreenBuffer[_screenW + 1 + x] = blendPixel16(_fadeScreenBuffer[x], screenBuffer[x], colorMask, i); tempScreenBuffer[_screenW + 1 + x] = blendPixel16(_fadeScreenBuffer[x], screenBuffer[x], colorMask, i);
} }
SDL_LockSurface(_screenSurface); SDL_LockSurface(_surface);
uint16_t *dst = (uint16_t *)_screenSurface->pixels; uint16_t *dst = (uint16_t *)_surface->pixels;
const uint16_t *src = tempScreenBuffer + _screenW + 1; const uint16_t *src = tempScreenBuffer + _screenW + 1;
(*_scalers[_currentScaler].proc)(dst, _screenSurface->pitch, src, _screenW, _screenW, _screenH); (*_scalers[_scaler].proc)(dst, _surface->pitch, src, _screenW, _screenW, _screenH);
SDL_UnlockSurface(_screenSurface); SDL_UnlockSurface(_surface);
SDL_UpdateRect(_screenSurface, 0, 0, _screenW * mul, _screenH * mul); SDL_UpdateRect(_surface, 0, 0, _screenW * mul, _screenH * mul);
SDL_Delay(30); SDL_Delay(30);
} }
free(tempScreenBuffer); free(tempScreenBuffer);
@ -240,35 +262,36 @@ void SystemStub_SDL::updateScreen(int shakeOffset) {
SDL_Rect *br = &_blitRects[i]; SDL_Rect *br = &_blitRects[i];
int dx = br->x * mul; int dx = br->x * mul;
int dy = br->y * mul; int dy = br->y * mul;
SDL_LockSurface(_screenSurface); SDL_LockSurface(_surface);
uint16_t *dst = (uint16_t *)_screenSurface->pixels + dy * _screenSurface->pitch / 2 + dx; uint16_t *dst = (uint16_t *)_surface->pixels + dy * _surface->pitch / 2 + dx;
const uint16_t *src = _screenBuffer + (br->y + 1) * _screenW + (br->x + 1); const uint16_t *src = _screenBuffer + (br->y + 1) * _screenW + (br->x + 1);
(*_scalers[_currentScaler].proc)(dst, _screenSurface->pitch, src, _screenW, br->w, br->h); (*_scalers[_scaler].proc)(dst, _surface->pitch, src, _screenW, br->w, br->h);
SDL_UnlockSurface(_screenSurface); SDL_UnlockSurface(_surface);
br->x *= mul; br->x *= mul;
br->y *= mul; br->y *= mul;
br->w *= mul; br->w *= mul;
br->h *= mul; br->h *= mul;
} }
SDL_UpdateRects(_screenSurface, _numBlitRects, _blitRects); SDL_UpdateRects(_surface, _numBlitRects, _blitRects);
} else { } else {
SDL_LockSurface(_screenSurface); SDL_LockSurface(_surface);
int w = _screenW; int w = _screenW;
int h = _screenH - shakeOffset; int h = _screenH - shakeOffset;
uint16_t *dst = (uint16_t *)_screenSurface->pixels + shakeOffset * mul * _screenSurface->pitch / 2; uint16_t *dst = (uint16_t *)_surface->pixels + shakeOffset * mul * _surface->pitch / 2;
const uint16_t *src = _screenBuffer + _screenW + 1; const uint16_t *src = _screenBuffer + _screenW + 1;
(*_scalers[_currentScaler].proc)(dst, _screenSurface->pitch, src, _screenW, w, h); (*_scalers[_scaler].proc)(dst, _surface->pitch, src, _screenW, w, h);
SDL_UnlockSurface(_screenSurface); SDL_UnlockSurface(_surface);
SDL_Rect r; SDL_Rect r;
r.x = 0; r.x = 0;
r.y = 0; r.y = 0;
r.w = _screenW * mul; r.w = _screenW * mul;
r.h = shakeOffset * mul; r.h = shakeOffset * mul;
SDL_FillRect(_screenSurface, &r, _pal[_overscanColor]); SDL_FillRect(_surface, &r, _pal[_overscanColor]);
SDL_UpdateRect(_screenSurface, 0, 0, _screenW * mul, _screenH * mul); SDL_UpdateRect(_surface, 0, 0, _screenW * mul, _screenH * mul);
} }
#endif
_numBlitRects = 0; _numBlitRects = 0;
} }
@ -294,13 +317,26 @@ void SystemStub_SDL::processEvent(const SDL_Event &ev, bool &paused) {
case SDL_QUIT: case SDL_QUIT:
_pi.quit = true; _pi.quit = true;
break; break;
#if SDL_VERSION_ATLEAST(2, 0, 0)
case SDL_WINDOWEVENT:
switch (ev.window.event) {
case SDL_WINDOWEVENT_FOCUS_GAINED:
case SDL_WINDOWEVENT_FOCUS_LOST:
paused = (ev.window.event == SDL_WINDOWEVENT_FOCUS_LOST);
SDL_PauseAudio(paused);
break;
}
break;
#else
case SDL_ACTIVEEVENT: case SDL_ACTIVEEVENT:
if (ev.active.state & SDL_APPINPUTFOCUS) { if (ev.active.state & SDL_APPINPUTFOCUS) {
paused = ev.active.gain == 0; paused = ev.active.gain == 0;
SDL_PauseAudio(paused ? 1 : 0); SDL_PauseAudio(paused ? 1 : 0);
} }
break; break;
case SDL_JOYHATMOTION: #endif
case SDL_JOYHATMOTION:
if (_joystick) {
_pi.dirMask = 0; _pi.dirMask = 0;
if (ev.jhat.value & SDL_HAT_UP) { if (ev.jhat.value & SDL_HAT_UP) {
_pi.dirMask |= PlayerInput::DIR_UP; _pi.dirMask |= PlayerInput::DIR_UP;
@ -314,42 +350,32 @@ void SystemStub_SDL::processEvent(const SDL_Event &ev, bool &paused) {
if (ev.jhat.value & SDL_HAT_RIGHT) { if (ev.jhat.value & SDL_HAT_RIGHT) {
_pi.dirMask |= PlayerInput::DIR_RIGHT; _pi.dirMask |= PlayerInput::DIR_RIGHT;
} }
break; }
case SDL_JOYAXISMOTION: break;
case SDL_JOYAXISMOTION:
if (_joystick) {
switch (ev.jaxis.axis) { switch (ev.jaxis.axis) {
case 0: case 0:
if (ev.jaxis.value > JOYSTICK_COMMIT_VALUE) { _pi.dirMask &= ~(PlayerInput::DIR_RIGHT | PlayerInput::DIR_LEFT);
if (ev.jaxis.value > kJoystickCommitValue) {
_pi.dirMask |= PlayerInput::DIR_RIGHT; _pi.dirMask |= PlayerInput::DIR_RIGHT;
if (_pi.dirMask & PlayerInput::DIR_LEFT) { } else if (ev.jaxis.value < -kJoystickCommitValue) {
_pi.dirMask &= ~PlayerInput::DIR_LEFT;
}
} else if (ev.jaxis.value < -JOYSTICK_COMMIT_VALUE) {
_pi.dirMask |= PlayerInput::DIR_LEFT; _pi.dirMask |= PlayerInput::DIR_LEFT;
if (_pi.dirMask & PlayerInput::DIR_RIGHT) {
_pi.dirMask &= ~PlayerInput::DIR_RIGHT;
}
} else {
_pi.dirMask &= ~(PlayerInput::DIR_RIGHT | PlayerInput::DIR_LEFT);
} }
break; break;
case 1: case 1:
if (ev.jaxis.value > JOYSTICK_COMMIT_VALUE) { _pi.dirMask &= ~(PlayerInput::DIR_UP | PlayerInput::DIR_DOWN);
if (ev.jaxis.value > kJoystickCommitValue) {
_pi.dirMask |= PlayerInput::DIR_DOWN; _pi.dirMask |= PlayerInput::DIR_DOWN;
if (_pi.dirMask & PlayerInput::DIR_UP) { } else if (ev.jaxis.value < -kJoystickCommitValue) {
_pi.dirMask &= ~PlayerInput::DIR_UP;
}
} else if (ev.jaxis.value < -JOYSTICK_COMMIT_VALUE) {
_pi.dirMask |= PlayerInput::DIR_UP; _pi.dirMask |= PlayerInput::DIR_UP;
if (_pi.dirMask & PlayerInput::DIR_DOWN) {
_pi.dirMask &= ~PlayerInput::DIR_DOWN;
}
} else {
_pi.dirMask &= ~(PlayerInput::DIR_UP | PlayerInput::DIR_DOWN);
} }
break; break;
} }
break; }
case SDL_JOYBUTTONDOWN: break;
case SDL_JOYBUTTONDOWN:
if (_joystick) {
switch (ev.jbutton.button) { switch (ev.jbutton.button) {
case 0: case 0:
_pi.space = true; _pi.space = true;
@ -364,8 +390,10 @@ void SystemStub_SDL::processEvent(const SDL_Event &ev, bool &paused) {
_pi.backspace = true; _pi.backspace = true;
break; break;
} }
break; }
case SDL_JOYBUTTONUP: break;
case SDL_JOYBUTTONUP:
if (_joystick) {
switch (ev.jbutton.button) { switch (ev.jbutton.button) {
case 0: case 0:
_pi.space = false; _pi.space = false;
@ -380,7 +408,8 @@ void SystemStub_SDL::processEvent(const SDL_Event &ev, bool &paused) {
_pi.backspace = false; _pi.backspace = false;
break; break;
} }
break; }
break;
case SDL_KEYUP: case SDL_KEYUP:
switch (ev.key.keysym.sym) { switch (ev.key.keysym.sym) {
case SDLK_LEFT: case SDLK_LEFT:
@ -415,23 +444,26 @@ void SystemStub_SDL::processEvent(const SDL_Event &ev, bool &paused) {
case SDL_KEYDOWN: case SDL_KEYDOWN:
if (ev.key.keysym.mod & KMOD_ALT) { if (ev.key.keysym.mod & KMOD_ALT) {
if (ev.key.keysym.sym == SDLK_RETURN) { if (ev.key.keysym.sym == SDLK_RETURN) {
switchGfxMode(!_fullscreen, _currentScaler); changeGraphics(!_fullscreen, _scaler);
} else if (ev.key.keysym.sym == SDLK_KP_PLUS || ev.key.keysym.sym == SDLK_PAGEUP) { } else if (ev.key.keysym.sym == SDLK_KP_PLUS || ev.key.keysym.sym == SDLK_PAGEUP) {
uint8_t s = _currentScaler + 1; uint8_t s = _scaler + 1;
if (s < NUM_SCALERS) { if (s < NUM_SCALERS) {
switchGfxMode(_fullscreen, s); changeGraphics(_fullscreen, s);
} }
} else if (ev.key.keysym.sym == SDLK_KP_MINUS || ev.key.keysym.sym == SDLK_PAGEDOWN) { } else if (ev.key.keysym.sym == SDLK_KP_MINUS || ev.key.keysym.sym == SDLK_PAGEDOWN) {
int8_t s = _currentScaler - 1; int8_t s = _scaler - 1;
if (_currentScaler > 0) { if (_scaler > 0) {
switchGfxMode(_fullscreen, s); changeGraphics(_fullscreen, s);
} }
} else if (ev.key.keysym.sym == SDLK_s) { } else if (ev.key.keysym.sym == SDLK_s) {
#if SDL_VERSION_ATLEAST(2, 0, 0)
#else
char name[32]; char name[32];
snprintf(name, sizeof(name), "screenshot-%03d.bmp", _screenshot); snprintf(name, sizeof(name), "screenshot-%03d.bmp", _screenshot);
SDL_SaveBMP(_screenSurface, name); SDL_SaveBMP(_surface, name);
++_screenshot; ++_screenshot;
debug(DBG_INFO, "Written '%s'", name); debug(DBG_INFO, "Written '%s'", name);
#endif
} }
break; break;
} else if (ev.key.keysym.mod & KMOD_CTRL) { } else if (ev.key.keysym.mod & KMOD_CTRL) {
@ -443,7 +475,7 @@ void SystemStub_SDL::processEvent(const SDL_Event &ev, bool &paused) {
_pi.dbgMask ^= PlayerInput::DF_SETLIFE; _pi.dbgMask ^= PlayerInput::DF_SETLIFE;
} else if (ev.key.keysym.sym == SDLK_m) { } else if (ev.key.keysym.sym == SDLK_m) {
_pi.mirrorMode = !_pi.mirrorMode; _pi.mirrorMode = !_pi.mirrorMode;
flipGfx(); flipGraphics();
} else if (ev.key.keysym.sym == SDLK_s) { } else if (ev.key.keysym.sym == SDLK_s) {
_pi.save = true; _pi.save = true;
} else if (ev.key.keysym.sym == SDLK_l) { } else if (ev.key.keysym.sym == SDLK_l) {
@ -506,22 +538,20 @@ uint32_t SystemStub_SDL::getTimeStamp() {
return SDL_GetTicks(); return SDL_GetTicks();
} }
static void mixAudioS8ToU8(void *param, uint8_t *buf, int len) { static void mixAudioS16(void *param, uint8_t *buf, int len) {
SystemStub_SDL *stub = (SystemStub_SDL *)param; SystemStub_SDL *stub = (SystemStub_SDL *)param;
stub->_audioCbProc(stub->_audioCbData, (int8_t *)buf, len); memset(buf, 0, len);
for (int i = 0; i < len; ++i) { stub->_audioCbProc(stub->_audioCbData, (int16_t *)buf, len / 2);
buf[i] ^= 0x80;
}
} }
void SystemStub_SDL::startAudio(AudioCallback callback, void *param) { void SystemStub_SDL::startAudio(AudioCallback callback, void *param) {
SDL_AudioSpec desired, obtained; SDL_AudioSpec desired, obtained;
memset(&desired, 0, sizeof(desired)); memset(&desired, 0, sizeof(desired));
desired.freq = SOUND_SAMPLE_RATE; desired.freq = kAudioHz;
desired.format = AUDIO_U8; desired.format = AUDIO_S16SYS;
desired.channels = 1; desired.channels = 1;
desired.samples = 2048; desired.samples = 2048;
desired.callback = mixAudioS8ToU8; desired.callback = mixAudioS16;
desired.userdata = this; desired.userdata = this;
if (SDL_OpenAudio(&desired, &obtained) == 0) { if (SDL_OpenAudio(&desired, &obtained) == 0) {
_audioCbProc = callback; _audioCbProc = callback;
@ -537,7 +567,7 @@ void SystemStub_SDL::stopAudio() {
} }
uint32_t SystemStub_SDL::getOutputSampleRate() { uint32_t SystemStub_SDL::getOutputSampleRate() {
return SOUND_SAMPLE_RATE; return kAudioHz;
} }
void SystemStub_SDL::lockAudio() { void SystemStub_SDL::lockAudio() {
@ -548,17 +578,49 @@ void SystemStub_SDL::unlockAudio() {
SDL_UnlockAudio(); SDL_UnlockAudio();
} }
void SystemStub_SDL::prepareGfxMode() { void SystemStub_SDL::prepareGraphics() {
int w = _screenW * _scalers[_currentScaler].factor; #if SDL_VERSION_ATLEAST(2, 0, 0)
int h = _screenH * _scalers[_currentScaler].factor; switch (_scaler) {
_screenSurface = SDL_SetVideoMode(w, h, 16, _fullscreen ? (SDL_FULLSCREEN | SDL_HWSURFACE) : SDL_HWSURFACE); case SCALER_SCALE_2X:
if (!_screenSurface) { case SCALER_SCALE_3X:
error("SystemStub_SDL::prepareGfxMode() Unable to allocate _screen buffer"); case SCALER_SCALE_4X:
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1");
break;
default:
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "0"); // nearest pixel sampling
break;
} }
forceGfxRedraw(); const int windowW = _screenW * _scalers[_scaler].factor;
const int windowH = _screenH * _scalers[_scaler].factor;
int flags = 0;
if (_fullscreen) {
flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
}
_window = SDL_CreateWindow(_caption, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, windowW, windowH, flags);
_renderer = SDL_CreateRenderer(_window, -1, SDL_RENDERER_ACCELERATED);
SDL_RenderSetLogicalSize(_renderer, windowW, windowH);
static const uint32_t kPixelFormat = SDL_PIXELFORMAT_RGB565;
_texture = SDL_CreateTexture(_renderer, kPixelFormat, SDL_TEXTUREACCESS_STREAMING, _screenW, _screenH);
_fmt = SDL_AllocFormat(kPixelFormat);
#else
SDL_WM_SetCaption(_caption, NULL);
const int w = _screenW * _scalers[_scaler].factor;
const int h = _screenH * _scalers[_scaler].factor;
int flags = SDL_HWSURFACE;
if (_fullscreen) {
flags |= SDL_FULLSCREEN;
}
static const int kBitDepth = 16;
_surface = SDL_SetVideoMode(w, h, kBitDepth, flags);
if (!_surface) {
error("SystemStub_SDL::prepareGraphics() Unable to allocate _screen buffer");
}
_fmt = _surface->format;
#endif
forceGraphicsRedraw();
} }
void SystemStub_SDL::cleanupGfxMode() { void SystemStub_SDL::cleanupGraphics() {
if (_screenBuffer) { if (_screenBuffer) {
free(_screenBuffer); free(_screenBuffer);
_screenBuffer = 0; _screenBuffer = 0;
@ -567,21 +629,56 @@ void SystemStub_SDL::cleanupGfxMode() {
free(_fadeScreenBuffer); free(_fadeScreenBuffer);
_fadeScreenBuffer = 0; _fadeScreenBuffer = 0;
} }
if (_screenSurface) { #if SDL_VERSION_ATLEAST(2, 0, 0)
// freed by SDL_Quit() if (_window) {
_screenSurface = 0; SDL_DestroyWindow(_window);
_window = 0;
} }
if (_renderer) {
SDL_DestroyRenderer(_renderer);
_renderer = 0;
}
if (_fmt) {
SDL_FreeFormat(_fmt);
_fmt = 0;
}
#else
if (_surface) {
// freed by SDL_Quit()
_surface = 0;
}
#endif
} }
void SystemStub_SDL::switchGfxMode(bool fullscreen, uint8_t scaler) { void SystemStub_SDL::changeGraphics(bool fullscreen, uint8_t scaler) {
SDL_FreeSurface(_screenSurface); if (_fadeScreenBuffer) {
free(_fadeScreenBuffer);
_fadeScreenBuffer = 0;
}
#if SDL_VERSION_ATLEAST(2, 0, 0)
if (_window) {
SDL_DestroyWindow(_window);
_window = 0;
}
if (_renderer) {
SDL_DestroyRenderer(_renderer);
_renderer = 0;
}
if (_fmt) {
SDL_FreeFormat(_fmt);
_fmt = 0;
}
#else
SDL_FreeSurface(_surface);
_surface = 0;
#endif
_fullscreen = fullscreen; _fullscreen = fullscreen;
_currentScaler = scaler; _scaler = scaler;
prepareGfxMode(); prepareGraphics();
forceGfxRedraw(); forceGraphicsRedraw();
} }
void SystemStub_SDL::flipGfx() { void SystemStub_SDL::flipGraphics() {
uint16_t scanline[256]; uint16_t scanline[256];
assert(_screenW <= 256); assert(_screenW <= 256);
uint16_t *p = _screenBuffer + _screenW + 1; uint16_t *p = _screenBuffer + _screenW + 1;
@ -593,10 +690,10 @@ void SystemStub_SDL::flipGfx() {
memcpy(p, scanline, _screenW * sizeof(uint16_t)); memcpy(p, scanline, _screenW * sizeof(uint16_t));
p += _screenW; p += _screenW;
} }
forceGfxRedraw(); forceGraphicsRedraw();
} }
void SystemStub_SDL::forceGfxRedraw() { void SystemStub_SDL::forceGraphicsRedraw() {
_numBlitRects = 1; _numBlitRects = 1;
_blitRects[0].x = 0; _blitRects[0].x = 0;
_blitRects[0].y = 0; _blitRects[0].y = 0;

View File

@ -25,6 +25,15 @@ Video::Video(Resource *res, SystemStub *stub)
_charFrontColor = 0; _charFrontColor = 0;
_charTransparentColor = 0; _charTransparentColor = 0;
_charShadowColor = 0; _charShadowColor = 0;
_drawChar = 0;
switch (_res->_type) {
case kResourceTypeAmiga:
_drawChar = &Video::AMIGA_drawStringChar;
break;
case kResourceTypeDOS:
_drawChar = &Video::PC_drawStringChar;
break;
}
} }
Video::~Video() { Video::~Video() {
@ -150,6 +159,12 @@ void Video::setPaletteSlotLE(int palSlot, const uint8_t *palData) {
void Video::setTextPalette() { void Video::setTextPalette() {
debug(DBG_VIDEO, "Video::setTextPalette()"); debug(DBG_VIDEO, "Video::setTextPalette()");
setPaletteSlotLE(0xE, _textPal); setPaletteSlotLE(0xE, _textPal);
if (_res->isAmiga()) {
Color c;
c.r = c.g = 0xEE;
c.b = 0;
_stub->setPaletteEntry(0xE7, &c);
}
} }
void Video::setPalette0xF() { void Video::setPalette0xF() {
@ -313,6 +328,35 @@ static void AMIGA_planar8(uint8_t *dst, int w, int h, const uint8_t *src) {
} }
} }
static void AMIGA_planar24(uint8_t *dst, int w, int h, const uint8_t *src) {
assert(w == 24);
for (int y = 0; y < h; ++y) {
for (int i = 0; i < 16; ++i) {
int color = 0;
const int mask = 1 << (15 - i);
for (int bit = 0; bit < 4; ++bit) {
if (READ_BE_UINT16(src + bit * 2) & mask) {
color |= 1 << bit;
}
}
dst[i] = color;
}
src += 8;
for (int i = 0; i < 8; ++i) {
int color = 0;
const int mask = 1 << (7 - i);
for (int bit = 0; bit < 4; ++bit) {
if (src[bit] & mask) {
color |= 1 << bit;
}
}
dst[16 + i] = color;
}
src += 4;
dst += w;
}
}
static void AMIGA_planar_mask(uint8_t *dst, int x0, int y0, int w, int h, uint8_t *src, uint8_t *mask, int size) { static void AMIGA_planar_mask(uint8_t *dst, int x0, int y0, int w, int h, uint8_t *src, uint8_t *mask, int size) {
dst += y0 * 256 + x0; dst += y0 * 256 + x0;
for (int y = 0; y < h; ++y) { for (int y = 0; y < h; ++y) {
@ -341,22 +385,23 @@ static void AMIGA_planar_mask(uint8_t *dst, int x0, int y0, int w, int h, uint8_
} }
static void AMIGA_decodeRle(uint8_t *dst, const uint8_t *src) { static void AMIGA_decodeRle(uint8_t *dst, const uint8_t *src) {
int code = READ_BE_UINT16(src) & 0x7FFF; src += 2; const int size = READ_BE_UINT16(src) & 0x7FFF; src += 2;
const uint8_t *end = src + code; for (int i = 0; i < size; ) {
do { int code = src[i++];
code = *src++;
if ((code & 0x80) == 0) { if ((code & 0x80) == 0) {
++code; ++code;
memcpy(dst, src, code); if (i + code > size) {
src += code; code = size - i;
}
memcpy(dst, &src[i], code);
i += code;
} else { } else {
code = 1 - ((int8_t)code); code = 1 - ((int8_t)code);
memset(dst, *src, code); memset(dst, src[i], code);
++src; ++i;
} }
dst += code; dst += code;
} while (src < end); }
assert(src == end);
} }
static void PC_drawTileMask(uint8_t *dst, int x0, int y0, int w, int h, uint8_t *m, uint8_t *p, int size) { static void PC_drawTileMask(uint8_t *dst, int x0, int y0, int w, int h, uint8_t *m, uint8_t *p, int size) {
@ -615,12 +660,17 @@ void Video::AMIGA_decodeLev(int level, int room) {
// done in ::PC_setLevelPalettes // done in ::PC_setLevelPalettes
return; return;
} }
// background
setPaletteSlotBE(0x0, _mapPalSlot1); setPaletteSlotBE(0x0, _mapPalSlot1);
for (int i = 1; i < 5; ++i) { // objects
setPaletteSlotBE(i, _mapPalSlot3); setPaletteSlotBE(0x1, (level == 0 || level == 1) ? _mapPalSlot3 : _mapPalSlot2);
} setPaletteSlotBE(0x2, _mapPalSlot3);
setPaletteSlotBE(0x6, _mapPalSlot3); setPaletteSlotBE(0x3, _mapPalSlot3);
// conrad
setPaletteSlotBE(0x4, _mapPalSlot3);
// foreground
setPaletteSlotBE(0x8, _mapPalSlot1); setPaletteSlotBE(0x8, _mapPalSlot1);
// inventory
setPaletteSlotBE(0xA, _mapPalSlot3); setPaletteSlotBE(0xA, _mapPalSlot3);
} }
@ -655,6 +705,9 @@ void Video::AMIGA_decodeSpc(const uint8_t *src, int w, int h, uint8_t *dst) {
case 32: case 32:
AMIGA_planar16(dst, w / 16, h, 4, src); AMIGA_planar16(dst, w / 16, h, 4, src);
break; break;
case 24:
AMIGA_planar24(dst, w, h, src);
break;
default: default:
warning("AMIGA_decodeSpc w=%d unimplemented", w); warning("AMIGA_decodeSpc w=%d unimplemented", w);
break; break;
@ -788,7 +841,7 @@ void Video::AMIGA_drawStringChar(uint8_t *dst, int pitch, const uint8_t *src, ui
for (int y = 0; y < 8; ++y) { for (int y = 0; y < 8; ++y) {
for (int x = 0; x < 8; ++x) { for (int x = 0; x < 8; ++x) {
if (src[x] != 0) { if (src[x] != 0) {
dst[x] = 0x1D; dst[x] = color;
} }
} }
src += 16; src += 16;
@ -819,15 +872,7 @@ void Video::PC_drawStringChar(uint8_t *dst, int pitch, const uint8_t *src, uint8
const char *Video::drawString(const char *str, int16_t x, int16_t y, uint8_t col) { const char *Video::drawString(const char *str, int16_t x, int16_t y, uint8_t col) {
debug(DBG_VIDEO, "Video::drawString('%s', %d, %d, 0x%X)", str, x, y, col); debug(DBG_VIDEO, "Video::drawString('%s', %d, %d, 0x%X)", str, x, y, col);
void (Video::*drawCharFunc)(uint8_t *, int, const uint8_t *, uint8_t, uint8_t) = 0; drawCharFunc dcf = _drawChar;
switch (_res->_type) {
case kResourceTypeAmiga:
drawCharFunc = &Video::AMIGA_drawStringChar;
break;
case kResourceTypeDOS:
drawCharFunc = &Video::PC_drawStringChar;
break;
}
int len = 0; int len = 0;
uint8_t *dst = _frontLayer + y * 256 + x; uint8_t *dst = _frontLayer + y * 256 + x;
while (1) { while (1) {
@ -835,7 +880,7 @@ const char *Video::drawString(const char *str, int16_t x, int16_t y, uint8_t col
if (c == 0 || c == 0xB || c == 0xA) { if (c == 0 || c == 0xB || c == 0xA) {
break; break;
} }
(this->*drawCharFunc)(dst, 256, _res->_fnt, col, c); (this->*dcf)(dst, 256, _res->_fnt, col, c);
dst += CHAR_W; dst += CHAR_W;
++len; ++len;
} }

View File

@ -13,6 +13,8 @@ struct Resource;
struct SystemStub; struct SystemStub;
struct Video { struct Video {
typedef void (Video::*drawCharFunc)(uint8_t *, int, const uint8_t *, uint8_t, uint8_t);
enum { enum {
GAMESCREEN_W = 256, GAMESCREEN_W = 256,
GAMESCREEN_H = 224, GAMESCREEN_H = 224,
@ -44,6 +46,7 @@ struct Video {
uint8_t *_screenBlocks; uint8_t *_screenBlocks;
bool _fullRefresh; bool _fullRefresh;
uint8_t _shakeOffset; uint8_t _shakeOffset;
drawCharFunc _drawChar;
Video(Resource *res, SystemStub *stub); Video(Resource *res, SystemStub *stub);
~Video(); ~Video();