diff --git a/Makefile b/Makefile index 9c8c9a3..c0a1722 100644 --- a/Makefile +++ b/Makefile @@ -9,9 +9,9 @@ ZLIB_LIBS := -lz CXXFLAGS += -Wall -MMD $(SDL_CFLAGS) -DUSE_MODPLUG -DUSE_TREMOR -DUSE_ZLIB -SRCS = collision.cpp cutscene.cpp dynlib.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 \ - scaler.cpp screenshot.cpp seq_player.cpp \ +SRCS = collision.cpp cutscene.cpp decode_mac.cpp dynlib.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 \ + resource_mac.cpp scaler.cpp screenshot.cpp seq_player.cpp \ sfx_player.cpp staticres.cpp systemstub_sdl.cpp unpack.cpp util.cpp video.cpp OBJS = $(SRCS:.cpp=.o) diff --git a/README.txt b/README.txt index e2a05ed..8c74e8c 100644 --- a/README.txt +++ b/README.txt @@ -1,6 +1,6 @@ REminiscence README -Release version: 0.3.7 +Release version: 0.4.0 ------------------------------------------------------------------------------- @@ -22,11 +22,15 @@ libraries are required. Data Files: ----------- -You will need the original files of the PC (DOS or CD) or Amiga release. +You will need the original files of the PC (DOS or CD), Amiga or Macintosh +release. + +For the Macintosh release, the resource fork must dumped as a file named +'FLASHBACK.BIN'. It shall contain 38 distinct data types. To have background music during polygonal cutscenes with the PC version, you need to copy the music/ directory of the Amiga version or use the .mod -fileset from unexotica. +fileset from unexotica [4]. To hear voice during in-game dialogues, you'll need to copy the 'VOICE.VCE' file from the SegaCD version to the DATA directory. @@ -93,3 +97,4 @@ URLs: [1] http://www.mobygames.com/game/flashback-the-quest-for-identity [2] http://en.wikipedia.org/wiki/Flashback:_The_Quest_for_Identity [3] http://ramal.free.fr/fb_en.htm +[4] https://www.exotica.org.uk/wiki/Flashback diff --git a/cutscene.cpp b/cutscene.cpp index a90dff0..c028e61 100644 --- a/cutscene.cpp +++ b/cutscene.cpp @@ -11,12 +11,30 @@ #include "util.h" #include "video.h" +static void scalePoints(Point *pt, int count, int scale) { + if (scale != 1) { + while (count-- > 0) { + pt->x *= scale; + pt->y *= scale; + ++pt; + } + } +} + Cutscene::Cutscene(Resource *res, SystemStub *stub, Video *vid) : _res(res), _stub(stub), _vid(vid) { _patchedOffsetsTable = 0; memset(_palBuf, 0, sizeof(_palBuf)); } +const uint8_t *Cutscene::getCommandData() const { + return _res->_cmd; +} + +const uint8_t *Cutscene::getPolygonData() const { + return _res->_pol; +} + void Cutscene::sync() { if (_stub->_pi.quit) { return; @@ -57,7 +75,7 @@ void Cutscene::setPalette() { sync(); updatePalette(); SWAP(_page0, _page1); - _stub->copyRect(0, 0, _vid->_w, _vid->_h, _page0, 256); + _stub->copyRect(0, 0, _vid->_w, _vid->_h, _page0, _vid->_w); _stub->updateScreen(0); } @@ -119,6 +137,10 @@ 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, int textJustify) { debug(DBG_CUT, "Cutscene::drawText(x=%d, y=%d, c=%d, justify=%d)", x, y, color, textJustify); + if (_res->_type == kResourceTypeMac) { + warning("Unhandled Cutscene::drawText"); // TODO + return; + } Video::drawCharFunc dcf = _vid->_drawChar; const uint8_t *fnt = (_res->_lang == LANG_JP) ? Video::_font8Jp : _res->_fnt; uint16_t last_sep = 0; @@ -134,23 +156,22 @@ void Cutscene::drawText(int16_t x, int16_t y, const uint8_t *p, uint16_t color, int16_t yy = y; int16_t xx = x; if (textJustify != kTextJustifyLeft) { - xx += ((last_sep - *sep++) / 2) * 8; + xx += ((last_sep - *sep++) / 2) * Video::CHAR_W; } for (; *p != 0xA && *p; ++p) { if (isNewLineChar(*p, _res)) { - yy += 8; + yy += Video::CHAR_H; xx = x; if (textJustify != kTextJustifyLeft) { - xx += ((last_sep - *sep++) / 2) * 8; + xx += ((last_sep - *sep++) / 2) * Video::CHAR_W; } } else if (*p == 0x20) { - xx += 8; + xx += Video::CHAR_W; } else if (*p == 0x9) { // ignore tab } else { - uint8_t *dst = page + 256 * yy + xx; - (_vid->*dcf)(dst, 256, fnt, color, *p); - xx += 8; + (_vid->*dcf)(page, _vid->_w, xx, yy, fnt, color, *p); + xx += Video::CHAR_W; } } } @@ -282,7 +303,7 @@ void Cutscene::op_waitForSync() { void Cutscene::drawShape(const uint8_t *data, int16_t x, int16_t y) { debug(DBG_CUT, "Cutscene::drawShape()"); - _gfx._layer = _page1; + _gfx.setLayer(_page1, _vid->_w); uint8_t numVertices = *data++; if (numVertices & 0x80) { Point pt; @@ -290,11 +311,13 @@ void Cutscene::drawShape(const uint8_t *data, int16_t x, int16_t y) { pt.y = READ_BE_UINT16(data) + y; data += 2; uint16_t rx = READ_BE_UINT16(data); data += 2; uint16_t ry = READ_BE_UINT16(data); data += 2; + scalePoints(&pt, 1, _vid->_layerScale); _gfx.drawEllipse(_primitiveColor, _hasAlphaColor, &pt, rx, ry); } else if (numVertices == 0) { Point pt; pt.x = READ_BE_UINT16(data) + x; data += 2; pt.y = READ_BE_UINT16(data) + y; data += 2; + scalePoints(&pt, 1, _vid->_layerScale); _gfx.drawPoint(_primitiveColor, &pt); } else { Point *pt = _vertices; @@ -319,6 +342,7 @@ void Cutscene::drawShape(const uint8_t *data, int16_t x, int16_t y) { ++pt; } } + scalePoints(_vertices, numVertices, _vid->_layerScale); _gfx.drawPolygon(_primitiveColor, _hasAlphaColor, _vertices, numVertices); } } @@ -384,16 +408,16 @@ void Cutscene::op_drawStringAtBottom() { // '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)) { + if ((_res->isDOS() && (_cmdPtr - _cmdPtrBak) == 0x10) || (_res->isAmiga() && (_cmdPtr - getCommandData()) == 0x9F3)) { _frameDelay = 100; setPalette(); return; } } - memset(_pageC + 179 * 256, 0xC0, 45 * 256); - memset(_page1 + 179 * 256, 0xC0, 45 * 256); - memset(_page0 + 179 * 256, 0xC0, 45 * 256); + memset(_pageC + 179 * _vid->_w, 0xC0, 45 * _vid->_w); + memset(_page1 + 179 * _vid->_w, 0xC0, 45 * _vid->_w); + memset(_page0 + 179 * _vid->_w, 0xC0, 45 * _vid->_w); if (strId != 0xFFFF) { const uint8_t *str = _res->getCineString(strId); if (str) { @@ -424,7 +448,7 @@ void Cutscene::op_refreshAll() { void Cutscene::drawShapeScale(const uint8_t *data, int16_t zoom, int16_t b, int16_t c, int16_t d, int16_t e, int16_t f, int16_t g) { debug(DBG_CUT, "Cutscene::drawShapeScale(%d, %d, %d, %d, %d, %d, %d)", zoom, b, c, d, e, f, g); - _gfx._layer = _page1; + _gfx.setLayer(_page1, _vid->_w); uint8_t numVertices = *data++; if (numVertices & 0x80) { int16_t x, y; @@ -473,6 +497,7 @@ void Cutscene::drawShapeScale(const uint8_t *data, int16_t zoom, int16_t b, int1 po.y = _vertices[0].y + e + _shape_iy; int16_t rx = _vertices[0].x - _vertices[2].x; int16_t ry = _vertices[0].y - _vertices[1].y; + scalePoints(&po, 1, _vid->_layerScale); _gfx.drawEllipse(_primitiveColor, _hasAlphaColor, &po, rx, ry); } else if (numVertices == 0) { Point pt; @@ -495,6 +520,7 @@ void Cutscene::drawShapeScale(const uint8_t *data, int16_t zoom, int16_t b, int1 _shape_prev_y = _shape_cur_y; _shape_prev_x16 = _shape_cur_x16; _shape_prev_y16 = _shape_cur_y16; + scalePoints(&pt, 1, _vid->_layerScale); _gfx.drawPoint(_primitiveColor, &pt); } else { Point *pt = _vertices; @@ -540,6 +566,7 @@ void Cutscene::drawShapeScale(const uint8_t *data, int16_t zoom, int16_t b, int1 _shape_prev_y = _shape_cur_y; _shape_prev_x16 = _shape_cur_x16; _shape_prev_y16 = _shape_cur_y16; + scalePoints(_vertices, numVertices, _vid->_layerScale); _gfx.drawPolygon(_primitiveColor, _hasAlphaColor, _vertices, numVertices); } } @@ -603,7 +630,7 @@ void Cutscene::op_drawShapeScale() { void Cutscene::drawShapeScaleRotate(const uint8_t *data, int16_t zoom, int16_t b, int16_t c, int16_t d, int16_t e, int16_t f, int16_t g) { debug(DBG_CUT, "Cutscene::drawShapeScaleRotate(%d, %d, %d, %d, %d, %d, %d)", zoom, b, c, d, e, f, g); - _gfx._layer = _page1; + _gfx.setLayer(_page1, _vid->_w); uint8_t numVertices = *data++; if (numVertices & 0x80) { int16_t x, y, ix, iy; @@ -654,6 +681,7 @@ void Cutscene::drawShapeScaleRotate(const uint8_t *data, int16_t zoom, int16_t b po.y = _vertices[0].y + e + _shape_iy; int16_t rx = _vertices[0].x - _vertices[2].x; int16_t ry = _vertices[0].y - _vertices[1].y; + scalePoints(&po, 1, _vid->_layerScale); _gfx.drawEllipse(_primitiveColor, _hasAlphaColor, &po, rx, ry); } else if (numVertices == 0) { Point pt; @@ -680,6 +708,7 @@ void Cutscene::drawShapeScaleRotate(const uint8_t *data, int16_t zoom, int16_t b _shape_prev_y = _shape_cur_y; _shape_prev_x16 = _shape_cur_x16; _shape_prev_y16 = _shape_cur_y16; + scalePoints(&pt, 1, _vid->_layerScale); _gfx.drawPoint(_primitiveColor, &pt); } else { int16_t x, y, a, shape_last_x, shape_last_y; @@ -758,6 +787,7 @@ void Cutscene::drawShapeScaleRotate(const uint8_t *data, int16_t zoom, int16_t b _shape_prev_y = _shape_cur_y; _shape_prev_x16 = _shape_cur_x16; _shape_prev_y16 = _shape_cur_y16; + scalePoints(_vertices, numVertices + 1, _vid->_layerScale); _gfx.drawPolygon(_primitiveColor, _hasAlphaColor, _vertices, numVertices + 1); } } @@ -905,10 +935,10 @@ void Cutscene::op_handleKeys() { } _varKey = 0; --n; - _cmdPtr = _res->_cmd; + _cmdPtr = getCommandData(); n = READ_BE_UINT16(_cmdPtr + n * 2 + 2); } - _cmdPtr = _cmdPtrBak = _res->_cmd + n + _startOffset; + _cmdPtr = _cmdPtrBak = getCommandData() + n + _startOffset; } uint8_t Cutscene::fetchNextCmdByte() { @@ -932,14 +962,14 @@ void Cutscene::mainLoop(uint16_t offset) { } _newPal = false; _hasAlphaColor = false; - uint8_t *p = _res->_cmd; + const uint8_t *p = getCommandData(); if (offset != 0) { offset = READ_BE_UINT16(p + (offset + 1) * 2); } _startOffset = (READ_BE_UINT16(p) + 1) * 2; _varKey = 0; _cmdPtr = _cmdPtrBak = p + _startOffset + offset; - _polPtr = _res->_pol; + _polPtr = getPolygonData(); debug(DBG_CUT, "_startOffset = %d offset = %d", _startOffset, offset); while (!_stub->_pi.quit && !_interrupted && !_stop) { @@ -992,6 +1022,9 @@ bool Cutscene::load(uint16_t cutName) { _res->load(name, Resource::OT_CMD); _res->load(name, Resource::OT_POL); break; + case kResourceTypeMac: + _res->MAC_loadCutscene(name); + break; } _res->load_CINE(); return _res->_cmd && _res->_pol; @@ -1006,6 +1039,9 @@ void Cutscene::unload() { _res->unload(Resource::OT_CMD); _res->unload(Resource::OT_POL); break; + case kResourceTypeMac: + _res->MAC_unloadCutscene(); + break; } } @@ -1019,7 +1055,15 @@ void Cutscene::prepare() { _stub->_pi.shift = false; _interrupted = false; _stop = false; - _gfx.setClippingRect(8, 50, 240, 128); + const int w = 240; + const int h = 128; + const int x = (Video::GAMESCREEN_W - w) / 2; + const int y = 50; + const int sw = w * _vid->_layerScale; + const int sh = h * _vid->_layerScale; + const int sx = x * _vid->_layerScale; + const int sy = y * _vid->_layerScale; + _gfx.setClippingRect(sx, sy, sw, sh); } void Cutscene::playCredits() { @@ -1066,7 +1110,7 @@ void Cutscene::playText(const char *str) { const int y = (128 - lines * 8) / 2; memset(_page1, 0xC0, _vid->_layerSize); drawText(0, y, (const uint8_t *)str, 0xC1, _page1, kTextJustifyAlign); - _stub->copyRect(0, 0, _vid->_w, _vid->_h, _page1, 256); + _stub->copyRect(0, 0, _vid->_w, _vid->_h, _page1, _vid->_w); _stub->updateScreen(0); while (!_stub->_pi.quit) { @@ -1087,19 +1131,25 @@ void Cutscene::play() { prepare(); uint16_t cutName = _offsetsTable[_id * 2 + 0]; uint16_t cutOff = _offsetsTable[_id * 2 + 1]; - if (cutName == 0xFFFF && g_options.play_disabled_cutscenes) { + if (cutName == 0xFFFF) { switch (_id) { case 19: - cutName = 31; // SERRURE + if (g_options.play_serrure_cutscene) { + cutName = 31; // SERRURE + } break; case 22: case 23: case 24: - cutName = 12; // ASC + if (g_options.play_asc_cutscene) { + cutName = 12; // ASC + } break; case 30: case 31: - cutName = 14; // METRO + if (g_options.play_metro_cutscene) { + cutName = 14; // METRO + } break; } } @@ -1125,7 +1175,7 @@ void Cutscene::play() { mainLoop(cutOff); unload(); } - } else if (_id == 8 && g_options.play_stone_cutscene) { + } else if (_id == 8 && g_options.play_caillou_cutscene) { playSet(_caillouSetData, 0x5E4); } _vid->fullRefresh(); @@ -1158,12 +1208,14 @@ void Cutscene::drawSetShape(const uint8_t *p, uint16_t offset, int x, int y, uin Point pt; pt.x = x + ix; pt.y = y + iy; + scalePoints(&pt, 1, _vid->_layerScale); _gfx.drawEllipse(color, false, &pt, rx, ry); } else { for (int i = 0; i < verticesCount; ++i) { _vertices[i].x = x + (int16_t)READ_BE_UINT16(p + offset); offset += 2; _vertices[i].y = y + (int16_t)READ_BE_UINT16(p + offset); offset += 2; } + scalePoints(_vertices, verticesCount, _vid->_layerScale); _gfx.drawPolygon(color, false, _vertices, verticesCount); } } @@ -1208,7 +1260,7 @@ void Cutscene::playSet(const uint8_t *p, int offset) { } prepare(); - _gfx._layer = _page1; + _gfx.setLayer(_page1, _vid->_w); offset = 10; const int frames = READ_BE_UINT16(p + offset); offset += 2; @@ -1262,7 +1314,7 @@ void Cutscene::playSet(const uint8_t *p, int offset) { _stub->setPaletteEntry(0xC0 + j, &c); } - _stub->copyRect(0, 0, _vid->_w, _vid->_h, _page1, 256); + _stub->copyRect(0, 0, _vid->_w, _vid->_h, _page1, _vid->_w); _stub->updateScreen(0); const int diff = 6 * TIMER_SLICE - (_stub->getTimeStamp() - timestamp); _stub->sleep((diff < 16) ? 16 : diff); diff --git a/cutscene.h b/cutscene.h index f0d7bb1..e5d5fc9 100644 --- a/cutscene.h +++ b/cutscene.h @@ -64,9 +64,9 @@ struct Cutscene { uint16_t _deathCutsceneId; bool _interrupted; bool _stop; - uint8_t *_polPtr; - uint8_t *_cmdPtr; - uint8_t *_cmdPtrBak; + const uint8_t *_polPtr; + const uint8_t *_cmdPtr; + const uint8_t *_cmdPtrBak; uint32_t _tstamp; uint8_t _frameDelay; bool _newPal; @@ -105,6 +105,9 @@ struct Cutscene { Cutscene(Resource *res, SystemStub *stub, Video *vid); + const uint8_t *getCommandData() const; + const uint8_t *getPolygonData() const; + void sync(); void copyPalette(const uint8_t *pal, uint16_t num); void updatePalette(); diff --git a/game.cpp b/game.cpp index 8467647..55fa1c7 100644 --- a/game.cpp +++ b/game.cpp @@ -4,7 +4,8 @@ * Copyright (C) 2005-2018 Gregory Montoir (cyx@users.sourceforge.net) */ -#include +#include +#include "decode_mac.h" #include "file.h" #include "fs.h" #include "game.h" @@ -46,6 +47,10 @@ void Game::run() { _cut._patchedOffsetsTable = Cutscene::_ssiOffsetsTable; } break; + case kResourceTypeMac: + _res.MAC_loadClutData(); + _res.MAC_loadFontData(); + break; } if (!g_options.bypass_protection) { @@ -74,12 +79,15 @@ void Game::run() { _res.load_SPR_OFF("PERSO", _res._spr1); _res.load_FIB("GLOBAL"); break; + case kResourceTypeMac: + _res.MAC_loadIconData(); + _res.MAC_loadPersoData(); + break; } + bool presentMenu = (_res._type == kResourceTypeAmiga) || (_res._type == kResourceTypeDOS && _res.fileExists("MENU1.MAP")); while (!_stub->_pi.quit) { - if (_res._isDemo) { - // do not present title screen and menus - } else { + if (presentMenu) { _mix.playMusic(1); switch (_res._type) { case kResourceTypeDOS: @@ -111,6 +119,9 @@ void Game::run() { displayTitleScreenAmiga(); _stub->setScreenSize(Video::GAMESCREEN_W, Video::GAMESCREEN_H); break; + case kResourceTypeMac: + // TODO: + break; } if (_stub->_pi.quit) { break; @@ -374,9 +385,12 @@ void Game::playCutscene(int id) { _mix.playMusic(Cutscene::_musicTable[_cut._id]); } _cut.play(); - if (id == 0xD && !_cut._interrupted && _res.isDOS()) { - _cut._id = 0x4A; - _cut.play(); + if (id == 0xD && !_cut._interrupted) { + const bool extendedIntroduction = (_res._type == kResourceTypeDOS || _res._type == kResourceTypeMac); + if (extendedIntroduction) { + _cut._id = 0x4A; + _cut.play(); + } } if (id == 0x3D) { _cut.playCredits(); @@ -430,11 +444,11 @@ void Game::showFinalScore() { playCutscene(0x49); char buf[50]; snprintf(buf, sizeof(buf), "SCORE %08u", _score); - _vid.drawString(buf, (256 - strlen(buf) * 8) / 2, 40, 0xE5); - strcpy(buf, _menu._passwords[7][_skillLevel]); - _vid.drawString(buf, (256 - strlen(buf) * 8) / 2, 16, 0xE7); + _vid.drawString(buf, (Video::GAMESCREEN_W - strlen(buf) * Video::CHAR_W) / 2, 40, 0xE5); + const char *str = _menu.getLevelPassword(7, _skillLevel); + _vid.drawString(str, (Video::GAMESCREEN_W - strlen(str) * Video::CHAR_W) / 2, 16, 0xE7); while (!_stub->_pi.quit) { - _stub->copyRect(0, 0, _vid._w, _vid._h, _vid._frontLayer, 256); + _stub->copyRect(0, 0, _vid._w, _vid._h, _vid._frontLayer, _vid._w); _stub->updateScreen(0); _stub->processEvents(); if (_stub->_pi.enter) { @@ -446,9 +460,6 @@ void Game::showFinalScore() { } bool Game::handleConfigPanel() { - if (_res.isAmiga()) { - return true; - } const int x = 7; const int y = 10; const int w = 17; @@ -461,33 +472,60 @@ bool Game::handleConfigPanel() { // the panel background is drawn using special characters from FB_TXT.FNT static const bool kUseDefaultFont = true; - // top-left rounded corder - _vid.PC_drawChar(0x81, y, x, kUseDefaultFont); - // top horizontal line - for (int i = 1; i < w; ++i) { - _vid.PC_drawChar(0x85, y, x + i, kUseDefaultFont); - } - // top-right rounded corner - _vid.PC_drawChar(0x82, y, x + w, kUseDefaultFont); - for (int j = 1; j < h; ++j) { - // left vertical line - _vid.PC_drawChar(0x86, y + j, x, kUseDefaultFont); + switch (_res._type) { + case kResourceTypeAmiga: + // TODO + return true; + case kResourceTypeDOS: + // top-left rounded corner + _vid.PC_drawChar(0x81, y, x, kUseDefaultFont); + // top-right rounded corner + _vid.PC_drawChar(0x82, y, x + w, kUseDefaultFont); + // bottom-left rounded corner + _vid.PC_drawChar(0x83, y + h, x, kUseDefaultFont); + // bottom-right rounded corner + _vid.PC_drawChar(0x84, y + h, x + w, kUseDefaultFont); + // horizontal lines for (int i = 1; i < w; ++i) { - _vid._charTransparentColor = 0xE2; - _vid.PC_drawChar(0x20, y + j, x + i, kUseDefaultFont); + _vid.PC_drawChar(0x85, y, x + i, kUseDefaultFont); + _vid.PC_drawChar(0x88, y + h, x + i, kUseDefaultFont); } - _vid._charTransparentColor = 0xFF; - // right vertical line - _vid.PC_drawChar(0x87, y + j, x + w, kUseDefaultFont); + for (int j = 1; j < h; ++j) { + _vid._charTransparentColor = 0xFF; + // left vertical line + _vid.PC_drawChar(0x86, y + j, x, kUseDefaultFont); + // right vertical line + _vid.PC_drawChar(0x87, y + j, x + w, kUseDefaultFont); + _vid._charTransparentColor = 0xE2; + for (int i = 1; i < w; ++i) { + _vid.PC_drawChar(0x20, y + j, x + i, kUseDefaultFont); + } + } + break; + case kResourceTypeMac: + // top-left rounded corner + _vid.MAC_drawStringChar(_vid._frontLayer, _vid._w, Video::CHAR_W * x, Video::CHAR_H * y, _res._fnt, _vid._charFrontColor, 0x81); + // top-right rounded corner + _vid.MAC_drawStringChar(_vid._frontLayer, _vid._w, Video::CHAR_W * (x + w), Video::CHAR_H * y, _res._fnt, _vid._charFrontColor, 0x82); + // bottom-left rounded corner + _vid.MAC_drawStringChar(_vid._frontLayer, _vid._w, Video::CHAR_W * x, Video::CHAR_H * (y + h), _res._fnt, _vid._charFrontColor, 0x83); + // bottom-right rounded corner + _vid.MAC_drawStringChar(_vid._frontLayer, _vid._w, Video::CHAR_W * (x + w), Video::CHAR_H * (y + h), _res._fnt, _vid._charFrontColor, 0x84); + // horizontal lines + for (int i = 1; i < w; ++i) { + _vid.MAC_drawStringChar(_vid._frontLayer, _vid._w, Video::CHAR_W * (x + i), Video::CHAR_H * y, _res._fnt, _vid._charFrontColor, 0x85); + _vid.MAC_drawStringChar(_vid._frontLayer, _vid._w, Video::CHAR_W * (x + i), Video::CHAR_H * (y + h), _res._fnt, _vid._charFrontColor, 0x88); + } + // vertical lines + for (int i = 1; i < h; ++i) { + _vid.MAC_drawStringChar(_vid._frontLayer, _vid._w, Video::CHAR_W * x, Video::CHAR_H * (y + i), _res._fnt, _vid._charFrontColor, 0x86); + _vid.MAC_drawStringChar(_vid._frontLayer, _vid._w, Video::CHAR_W * (x + w), Video::CHAR_H * (y + i), _res._fnt, _vid._charFrontColor, 0x87); + for (int j = 1; j < w; ++j) { + _vid.MAC_fillRect(Video::CHAR_W * (x + j), Video::CHAR_H * (y + i), Video::CHAR_W, Video::CHAR_H, 0xE2); + } + } + break; } - // bottom-left rounded corner - _vid.PC_drawChar(0x83, y + h, x, kUseDefaultFont); - // bottom horizontal line - for (int i = 1; i < w; ++i) { - _vid.PC_drawChar(0x88, y + h, x + i, kUseDefaultFont); - } - // bottom-right rounded corner - _vid.PC_drawChar(0x84, y + h, x + w, kUseDefaultFont); _menu._charVar3 = 0xE4; _menu._charVar4 = 0xE5; @@ -570,15 +608,15 @@ bool Game::handleContinueAbort() { while (timeout >= 0 && !_stub->_pi.quit) { const char *str; str = _res.getMenuString(LocaleData::LI_01_CONTINUE_OR_ABORT); - _vid.drawString(str, (256 - strlen(str) * 8) / 2, 64, 0xE3); + _vid.drawString(str, (Video::GAMESCREEN_W - strlen(str) * Video::CHAR_W) / 2, 64, 0xE3); str = _res.getMenuString(LocaleData::LI_02_TIME); char buf[50]; snprintf(buf, sizeof(buf), "%s : %d", str, timeout / 10); _vid.drawString(buf, 96, 88, 0xE3); str = _res.getMenuString(LocaleData::LI_03_CONTINUE); - _vid.drawString(str, (256 - strlen(str) * 8) / 2, 104, colors[0]); + _vid.drawString(str, (Video::GAMESCREEN_W - strlen(str) * Video::CHAR_W) / 2, 104, colors[0]); str = _res.getMenuString(LocaleData::LI_04_ABORT); - _vid.drawString(str, (256 - strlen(str) * 8) / 2, 112, colors[1]); + _vid.drawString(str, (Video::GAMESCREEN_W - strlen(str) * Video::CHAR_W) / 2, 112, colors[1]); snprintf(buf, sizeof(buf), "SCORE %08u", _score); _vid.drawString(buf, 64, 154, 0xE3); if (_stub->_pi.dirMask & PlayerInput::DIR_UP) { @@ -599,7 +637,7 @@ bool Game::handleContinueAbort() { _stub->_pi.enter = false; return (current_color == 0); } - _stub->copyRect(0, 0, _vid._w, _vid._h, _vid._frontLayer, 256); + _stub->copyRect(0, 0, _vid._w, _vid._h, _vid._frontLayer, _vid._w); _stub->updateScreen(0); static const int COLOR_STEP = 8; static const int COLOR_MIN = 16; @@ -642,7 +680,7 @@ bool Game::handleProtectionScreen() { int shapeNum = getRandomNumber() % 30; for (int16_t zoom = 2000; zoom != 0; zoom -= 100) { _cut.drawProtectionShape(shapeNum, zoom); - _stub->copyRect(0, 0, _vid._w, _vid._h, _vid._tempLayer, 256); + _stub->copyRect(0, 0, _vid._w, _vid._h, _vid._tempLayer, _vid._w); _stub->updateScreen(0); _stub->sleep(30); } @@ -654,10 +692,10 @@ bool Game::handleProtectionScreen() { do { codeText[len] = '\0'; memcpy(_vid._frontLayer, _vid._tempLayer, _vid._layerSize); - _vid.drawString("PROTECTION", 11 * 8, 2 * 8, _menu._charVar2); + _vid.drawString("PROTECTION", 11 * Video::CHAR_W, 2 * Video::CHAR_H, _menu._charVar2); char buf[20]; snprintf(buf, sizeof(buf), "CODE %d : %s", codeNum + 1, codeText); - _vid.drawString(buf, 8 * 8, 23 * 8, _menu._charVar2); + _vid.drawString(buf, 8 * Video::CHAR_W, 23 * Video::CHAR_H, _menu._charVar2); _vid.updateScreen(); _stub->sleep(50); _stub->processEvents(); @@ -711,16 +749,8 @@ void Game::printLevelCode() { --_printLevelCodeCounter; if (_printLevelCodeCounter != 0) { char buf[32]; - const char *code = Menu::_passwords[_currentLevel][_skillLevel]; - if (_res.isAmiga()) { - if (_res._lang == LANG_FR) { - code = Menu::_passwordsFrAmiga[_skillLevel * 7 + _currentLevel]; - } else { - code = Menu::_passwordsEnAmiga[_skillLevel * 7 + _currentLevel]; - } - } - snprintf(buf, sizeof(buf), "CODE: %s", code); - _vid.drawString(buf, (_vid._w - strlen(buf) * 8) / 2, 16, 0xE7); + snprintf(buf, sizeof(buf), "CODE: %s", _menu.getLevelPassword(_currentLevel, _skillLevel)); + _vid.drawString(buf, (Video::GAMESCREEN_W - strlen(buf) * Video::CHAR_W) / 2, 16, 0xE7); } } } @@ -728,7 +758,7 @@ void Game::printLevelCode() { void Game::printSaveStateCompleted() { if (_saveStateCompleted) { const char *str = _res.getMenuString(LocaleData::LI_05_COMPLETED); - _vid.drawString(str, (176 - strlen(str) * 8) / 2, 34, 0xE6); + _vid.drawString(str, (176 - strlen(str) * Video::CHAR_W) / 2, 34, 0xE6); } } @@ -744,8 +774,8 @@ void Game::drawLevelTexts() { uint8_t icon_num = obj - 1; drawIcon(icon_num, 80, 8, 0xA); uint8_t txt_num = pge->init_PGE->text_num; - const char *str = (const char *)_res.getTextString(_currentLevel, txt_num); - _vid.drawString(str, (176 - strlen(str) * 8) / 2, 26, 0xE6); + const uint8_t *str = _res.getTextString(_currentLevel, txt_num); + drawString(str, 176, 26, 0xE6, true); if (icon_num == 2) { printSaveStateCompleted(); return; @@ -768,6 +798,11 @@ static int getLineLength(const uint8_t *str) { void Game::drawStoryTexts() { if (_textToDisplay != 0xFFFF) { + if (_res._type == kResourceTypeMac) { + warning("Unhandled textToDisplay %d", _textToDisplay); + _textToDisplay = 0xFFFF; + return; + } uint8_t textColor = 0xE8; const uint8_t *str = _res.getGameString(_textToDisplay); memcpy(_vid._tempLayer, _vid._frontLayer, _vid._layerSize); @@ -797,7 +832,7 @@ void Game::drawStoryTexts() { int yPos = 26; while (1) { const int len = getLineLength(str); - str = (const uint8_t *)_vid.drawString((const char *)str, (176 - len * 8) / 2, yPos, textColor); + str = (const uint8_t *)_vid.drawString((const char *)str, (176 - len * Video::CHAR_W) / 2, yPos, textColor); yPos += 8; if (*str == 0 || *str == 0xB) { break; @@ -832,6 +867,21 @@ void Game::drawStoryTexts() { } } +void Game::drawString(const uint8_t *p, int x, int y, uint8_t color, bool hcenter) { + const char *str = (const char *)p; + int len = 0; + if (_res._type == kResourceTypeMac) { + len = *p; + ++str; + } else { + len = strlen(str); + } + if (hcenter) { + x = (x - len * Video::CHAR_W) / 2; + } + _vid.drawStringLen(str, len, x, y, color); +} + void Game::prepareAnims() { if (!(_currentRoom & 0x80) && _currentRoom < 0x40) { int8_t pge_room; @@ -889,13 +939,22 @@ void Game::prepareAnimsHelper(LivePGE *pge, int16_t dx, int16_t dy) { if (pge->index != 0 && loadMonsterSprites(pge) == 0) { return; } - assert(pge->anim_number < 1287); - const uint8_t *dataPtr = _res._sprData[pge->anim_number]; - if (dataPtr == 0) { - return; + const uint8_t *dataPtr = 0; + int8_t dw = 0, dh = 0; + switch (_res._type) { + case kResourceTypeAmiga: + case kResourceTypeDOS: + assert(pge->anim_number < 1287); + dataPtr = _res._sprData[pge->anim_number]; + if (dataPtr == 0) { + return; + } + dw = (int8_t)dataPtr[0]; + dh = (int8_t)dataPtr[1]; + break; + case kResourceTypeMac: + break; } - const int8_t dw = (int8_t)dataPtr[0]; - const int8_t dh = (int8_t)dataPtr[1]; uint8_t w = 0, h = 0; switch (_res._type) { case kResourceTypeAmiga: @@ -907,8 +966,10 @@ void Game::prepareAnimsHelper(LivePGE *pge, int16_t dx, int16_t dy) { h = dataPtr[3]; dataPtr += 4; break; + case kResourceTypeMac: + break; } - const int16_t ypos = dy + pge->pos_y - dh + 2; + int16_t ypos = dy + pge->pos_y - dh + 2; int16_t xpos = dx + pge->pos_x - dw; if (pge->flags & 2) { xpos = dw + dx + pge->pos_x; @@ -932,8 +993,16 @@ void Game::prepareAnimsHelper(LivePGE *pge, int16_t dx, int16_t dy) { _animBuffers.addState(0, xpos, ypos, dataPtr, pge, w, h); } } else { - assert(pge->anim_number < _res._numSpc); - const uint8_t *dataPtr = _res._spc + READ_BE_UINT16(_res._spc + pge->anim_number * 2); + const uint8_t *dataPtr = 0; + switch (_res._type) { + case kResourceTypeAmiga: + case kResourceTypeDOS: + assert(pge->anim_number < _res._numSpc); + dataPtr = _res._spc + READ_BE_UINT16(_res._spc + pge->anim_number * 2); + break; + case kResourceTypeMac: + break; + } const int16_t xpos = dx + pge->pos_x + 8; const int16_t ypos = dy + pge->pos_y + 2; if (pge->init_PGE->object_type == 11) { @@ -984,15 +1053,76 @@ void Game::drawAnimBuffer(uint8_t stateNum, AnimBufferState *state) { drawCharacter(state->dataPtr, state->x, state->y, state->h, state->w, pge->flags); } break; + case kResourceTypeMac: + drawPiege(state); + break; } } else { - drawObject(state->dataPtr, state->x, state->y, pge->flags); + drawPiege(state); } --state; } while (--numAnims != 0); } } +static void fixOffsetDecodeBuffer(DecodeBuffer *buf, const uint8_t *dataPtr) { + if (buf->xflip) { + buf->x += (int16_t)READ_BE_UINT16(dataPtr + 4) - READ_BE_UINT16(dataPtr) - 1; + } else { + buf->x -= (int16_t)READ_BE_UINT16(dataPtr + 4); + } + buf->y -= (int16_t)READ_BE_UINT16(dataPtr + 6); +} + +void Game::drawPiege(AnimBufferState *state) { + LivePGE *pge = state->pge; + switch (_res._type) { + case kResourceTypeAmiga: + case kResourceTypeDOS: + drawObject(state->dataPtr, state->x, state->y, pge->flags); + break; + case kResourceTypeMac: { + DecodeBuffer buf; + memset(&buf, 0, sizeof(buf)); + buf.xflip = (pge->flags & 2); + buf.ptr = _vid._frontLayer; + buf.w = buf.pitch = _vid._w; + buf.h = _vid._h; + buf.x = state->x * _vid._layerScale; + buf.y = state->y * _vid._layerScale; + buf.setPixel = _eraseBackground ? Video::MAC_drawBuffer : Video::MAC_drawBufferMask; + if (pge->flags & 8) { + const uint8_t *dataPtr = _res.MAC_getImageData(_res._spc, pge->anim_number); + if (dataPtr) { + fixOffsetDecodeBuffer(&buf, dataPtr); + _res.MAC_decodeImageData(_res._spc, pge->anim_number, &buf); + _vid.MAC_markBlockAsDirty(buf.x, buf.y, READ_BE_UINT16(dataPtr), READ_BE_UINT16(dataPtr + 2)); + } + } else if (pge->index == 0) { + if (pge->anim_number == 0x386) { + return; + } + const int frame = _res.MAC_getPersoFrame(pge->anim_number); + const uint8_t *dataPtr = _res.MAC_getImageData(_res._perso, frame); + if (dataPtr) { + fixOffsetDecodeBuffer(&buf, dataPtr); + _res.MAC_decodeImageData(_res._perso, frame, &buf); + _vid.MAC_markBlockAsDirty(buf.x, buf.y, READ_BE_UINT16(dataPtr), READ_BE_UINT16(dataPtr + 2)); + } + } else { + const int frame = _res.MAC_getMonsterFrame(pge->anim_number); + const uint8_t *dataPtr = _res.MAC_getImageData(_res._monster, frame); + if (dataPtr) { + fixOffsetDecodeBuffer(&buf, dataPtr); + _res.MAC_decodeImageData(_res._monster, frame, &buf); + _vid.MAC_markBlockAsDirty(buf.x, buf.y, READ_BE_UINT16(dataPtr), READ_BE_UINT16(dataPtr + 2)); + } + } + } + break; + } +} + void Game::drawObject(const uint8_t *dataPtr, int16_t x, int16_t y, uint8_t flags) { debug(DBG_GAME, "Game::drawObject() dataPtr[]=0x%X dx=%d dy=%d", dataPtr[0], (int8_t)dataPtr[1], (int8_t)dataPtr[2]); assert(dataPtr[0] < 0x4A); @@ -1018,6 +1148,9 @@ void Game::drawObject(const uint8_t *dataPtr, int16_t x, int16_t y, uint8_t flag count = dataPtr[5]; dataPtr += 6; break; + case kResourceTypeMac: + assert(0); // different graphics format + break; } for (int i = 0; i < count; ++i) { drawObjectFrame(data, dataPtr, posx, posy, flags); @@ -1052,6 +1185,9 @@ void Game::drawObjectFrame(const uint8_t *bankDataPtr, const uint8_t *dataPtr, i case kResourceTypeDOS: _vid.PC_decodeSpc(src, sprite_w, sprite_h, _res._scratchBuffer); break; + case kResourceTypeMac: + assert(0); // different graphics format + break; } src = _res._scratchBuffer; @@ -1283,24 +1419,42 @@ int Game::loadMonsterSprites(LivePGE *pge) { _curMonsterFrame = mList[0]; if (_curMonsterNum != mList[1]) { _curMonsterNum = mList[1]; - if (_res.isAmiga()) { - _res.load(_monsterNames[1][_curMonsterNum], Resource::OT_SPM); - static const uint8_t tab[4] = { 0, 8, 0, 8 }; - const int offset = _vid._mapPalSlot3 * 16 + tab[_curMonsterNum]; - for (int i = 0; i < 8; ++i) { - _vid.setPaletteColorBE(0x50 + i, offset + i); + switch (_res._type) { + case kResourceTypeAmiga: { + _res.load(_monsterNames[1][_curMonsterNum], Resource::OT_SPM); + static const uint8_t tab[4] = { 0, 8, 0, 8 }; + const int offset = _vid._mapPalSlot3 * 16 + tab[_curMonsterNum]; + for (int i = 0; i < 8; ++i) { + _vid.setPaletteColorBE(0x50 + i, offset + i); + } } - } else { - const char *name = _monsterNames[0][_curMonsterNum]; - _res.load(name, Resource::OT_SPRM); - _res.load_SPR_OFF(name, _res._sprm); - _vid.setPaletteSlotLE(5, _monsterPals[_curMonsterNum]); + break; + case kResourceTypeDOS: { + const char *name = _monsterNames[0][_curMonsterNum]; + _res.load(name, Resource::OT_SPRM); + _res.load_SPR_OFF(name, _res._sprm); + _vid.setPaletteSlotLE(5, _monsterPals[_curMonsterNum]); + } + break; + case kResourceTypeMac: { + Color palette[256]; + _res.MAC_loadMonsterData(_monsterNames[0][_curMonsterNum], palette); + static const int kMonsterPalette = 5; + for (int i = 0; i < 16; ++i) { + const int color = kMonsterPalette * 16 + i; + _stub->setPaletteEntry(color, &palette[color]); + } + } + break; } } return 0xFFFF; } bool Game::hasLevelMap(int level, int room) const { + if (_res._type == kResourceTypeMac) { + return _res.MAC_hasLevelMap(level, room); + } if (_res._map) { return READ_LE_UINT32(_res._map + room * 6) != 0; } else if (_res._lev) { @@ -1347,6 +1501,29 @@ void Game::loadLevelMap() { } _vid.PC_setLevelPalettes(); break; + case kResourceTypeMac: + { + DecodeBuffer buf; + memset(&buf, 0, sizeof(buf)); + buf.ptr = _vid._frontLayer; + buf.w = buf.pitch = _vid._w; + buf.h = _vid._h; + buf.setPixel = Video::MAC_drawBuffer; + _res.MAC_loadLevelRoom(_currentLevel, _currentRoom, &buf); + memcpy(_vid._backLayer, _vid._frontLayer, _vid._layerSize); + Color roomPalette[256]; + _res.MAC_setupRoomClut(_currentLevel, _currentRoom, roomPalette); + for (int j = 0; j < 16; ++j) { + if (j == 5 || j == 7 || j == 14 || j == 15) { + continue; + } + for (int i = 0; i < 16; ++i) { + const int color = j * 16 + i; + _stub->setPaletteEntry(color, &roomPalette[color]); + } + } + } + break; } } @@ -1425,6 +1602,10 @@ void Game::loadLevelData() { _res.load(lvl->name2, Resource::OT_ANI); _res.load(lvl->name2, Resource::OT_TBN); break; + case kResourceTypeMac: + _res.MAC_unloadLevelData(); + _res.MAC_loadLevelData(_currentLevel); + break; } _cut._id = lvl->cutscene_id; @@ -1509,8 +1690,34 @@ void Game::drawIcon(uint8_t iconNum, int16_t x, int16_t y, uint8_t colMask) { case kResourceTypeDOS: _vid.PC_decodeIcn(_res._icn, iconNum, buf); break; + case kResourceTypeMac: + switch (iconNum) { + case 76: // cursor + iconNum = 32; + break; + case 77: // up + iconNum = 33; + break; + case 78: // down + iconNum = 34; + break; + } + { + const uint8_t *dataPtr = _res.MAC_getImageData(_res._icn, iconNum); + DecodeBuffer buf; + memset(&buf, 0, sizeof(buf)); + buf.ptr = _vid._frontLayer; + buf.w = buf.pitch = _vid._w; + buf.h = _vid._h; + buf.x = x * _vid._layerScale; + buf.y = y * _vid._layerScale; + buf.setPixel = Video::MAC_drawBuffer; + _res.MAC_decodeImageData(_res._icn, iconNum, &buf); + _vid.MAC_markBlockAsDirty(buf.x, buf.y, READ_BE_UINT16(dataPtr), READ_BE_UINT16(dataPtr + 2)); + } + return; } - _vid.drawSpriteSub1(buf, _vid._frontLayer + x + y * 256, 16, 16, 16, colMask << 4); + _vid.drawSpriteSub1(buf, _vid._frontLayer + x + y * _vid._w, 16, 16, 16, colMask << 4); _vid.markBlockAsDirty(x, y, 16, 16); } @@ -1569,35 +1776,43 @@ void Game::handleInventory() { int current_line = 0; bool display_score = false; while (!_stub->_pi.backspace && !_stub->_pi.quit) { - // draw inventory background - int icon_h = 5; - int icon_y = 140; - int icon_num = 31; static const int icon_spr_w = 16; static const int icon_spr_h = 16; - do { - int icon_x = 56; - int icon_w = 9; - do { - drawIcon(icon_num, icon_x, icon_y, 0xF); - ++icon_num; - icon_x += icon_spr_w; - } while (--icon_w); - icon_y += icon_spr_h; - } while (--icon_h); - if (_res._type == kResourceTypeAmiga) { - // draw outline rectangle - static const uint8_t outline_color = 0xE7; - uint8_t *p = _vid._frontLayer + 140 * Video::GAMESCREEN_W + 56; - memset(p + 1, outline_color, 9 * icon_spr_w - 2); - p += Video::GAMESCREEN_W; - for (int y = 1; y < 5 * icon_spr_h - 1; ++y) { - p[0] = p[9 * icon_spr_w - 1] = outline_color; - p += Video::GAMESCREEN_W; + switch (_res._type) { + case kResourceTypeAmiga: + case kResourceTypeDOS: { + // draw inventory background + int icon_h = 5; + int icon_y = 140; + int icon_num = 31; + do { + int icon_x = 56; + int icon_w = 9; + do { + drawIcon(icon_num, icon_x, icon_y, 0xF); + ++icon_num; + icon_x += icon_spr_w; + } while (--icon_w); + icon_y += icon_spr_h; + } while (--icon_h); } - memset(p + 1, outline_color, 9 * icon_spr_w - 2); + if (_res._type == kResourceTypeAmiga) { + // draw outline rectangle + static const uint8_t outline_color = 0xE7; + uint8_t *p = _vid._frontLayer + 140 * Video::GAMESCREEN_W + 56; + memset(p + 1, outline_color, 9 * icon_spr_w - 2); + p += Video::GAMESCREEN_W; + for (int y = 1; y < 5 * icon_spr_h - 1; ++y) { + p[0] = p[9 * icon_spr_w - 1] = outline_color; + p += Video::GAMESCREEN_W; + } + memset(p + 1, outline_color, 9 * icon_spr_w - 2); + } + break; + case kResourceTypeMac: + drawIcon(31, 56, 140, 0xF); + break; } - if (!display_score) { int icon_x_pos = 72; for (int i = 0; i < 4; ++i) { @@ -1610,12 +1825,12 @@ void Game::handleInventory() { drawIcon(76, icon_x_pos, 157, 0xA); selected_pge = items[item_it].live_pge; uint8_t txt_num = items[item_it].init_pge->text_num; - const char *str = (const char *)_res.getTextString(_currentLevel, txt_num); - _vid.drawString(str, (256 - strlen(str) * 8) / 2, 189, 0xED); + const uint8_t *str = _res.getTextString(_currentLevel, txt_num); + drawString(str, Video::GAMESCREEN_W, 189, 0xED, true); if (items[item_it].init_pge->init_flags & 4) { char buf[10]; snprintf(buf, sizeof(buf), "%d", selected_pge->life); - _vid.drawString(buf, (256 - strlen(buf) * 8) / 2, 197, 0xED); + _vid.drawString(buf, (Video::GAMESCREEN_W - strlen(buf) * Video::CHAR_W) / 2, 197, 0xED); } } icon_x_pos += 32; @@ -1629,9 +1844,9 @@ void Game::handleInventory() { } else { char buf[50]; snprintf(buf, sizeof(buf), "SCORE %08u", _score); - _vid.drawString(buf, (114 - strlen(buf) * 8) / 2 + 72, 158, 0xE5); + _vid.drawString(buf, (114 - strlen(buf) * Video::CHAR_W) / 2 + 72, 158, 0xE5); snprintf(buf, sizeof(buf), "%s:%s", _res.getMenuString(LocaleData::LI_06_LEVEL), _res.getMenuString(LocaleData::LI_13_EASY + _skillLevel)); - _vid.drawString(buf, (114 - strlen(buf) * 8) / 2 + 72, 166, 0xE5); + _vid.drawString(buf, (114 - strlen(buf) * Video::CHAR_W) / 2 + 72, 166, 0xE5); } _vid.updateScreen(); diff --git a/game.h b/game.h index 01a563f..ee13086 100644 --- a/game.h +++ b/game.h @@ -109,10 +109,12 @@ struct Game { void printSaveStateCompleted(); void drawLevelTexts(); void drawStoryTexts(); + void drawString(const uint8_t *p, int x, int y, uint8_t color, bool hcenter = false); void prepareAnims(); void prepareAnimsHelper(LivePGE *pge, int16_t dx, int16_t dy); void drawAnims(); void drawAnimBuffer(uint8_t stateNum, AnimBufferState *state); + void drawPiege(AnimBufferState *state); void drawObject(const uint8_t *dataPtr, int16_t x, int16_t y, uint8_t flags); void drawObjectFrame(const uint8_t *bankDataPtr, const uint8_t *dataPtr, int16_t x, int16_t y, uint8_t flags); void decodeCharacterFrame(const uint8_t *dataPtr, uint8_t *dstPtr); diff --git a/graphics.cpp b/graphics.cpp index b7b4832..0cc9a34 100644 --- a/graphics.cpp +++ b/graphics.cpp @@ -7,6 +7,11 @@ #include "graphics.h" #include "util.h" +void Graphics::setLayer(uint8_t *layer, int pitch) { + _layer = layer; + _layerPitch = pitch; +} + void Graphics::setClippingRect(int16_t rx, int16_t ry, int16_t rw, int16_t rh) { debug(DBG_VIDEO, "Graphics::setClippingRect(%d, %d, %d, %d)", rx, ry, rw, rh); _crx = rx; @@ -18,7 +23,7 @@ void Graphics::setClippingRect(int16_t rx, int16_t ry, int16_t rw, int16_t rh) { void Graphics::drawPoint(uint8_t color, const Point *pt) { debug(DBG_VIDEO, "Graphics::drawPoint() col=0x%X x=%d, y=%d", color, pt->x, pt->y); if (pt->x >= 0 && pt->x < _crw && pt->y >= 0 && pt->y < _crh) { - *(_layer + (pt->y + _cry) * 256 + pt->x + _crx) = color; + *(_layer + (pt->y + _cry) * _layerPitch + pt->x + _crx) = color; } } @@ -201,7 +206,7 @@ void Graphics::drawEllipse(uint8_t color, bool hasAlpha, const Point *pt, int16_ void Graphics::fillArea(uint8_t color, bool hasAlpha) { debug(DBG_VIDEO, "Graphics::fillArea()"); int16_t *pts = _areaPoints; - uint8_t *dst = _layer + (_cry + *pts++) * 256 + _crx; + uint8_t *dst = _layer + (_cry + *pts++) * _layerPitch + _crx; int16_t x1 = *pts++; if (x1 >= 0) { if (hasAlpha && color > 0xC7) { @@ -213,7 +218,7 @@ void Graphics::fillArea(uint8_t color, bool hasAlpha) { *(dst + x1 + i) |= color & 8; // XXX 0x88 } } - dst += 256; + dst += _layerPitch; x1 = *pts++; } while (x1 >= 0); } else { @@ -223,7 +228,7 @@ void Graphics::fillArea(uint8_t color, bool hasAlpha) { int len = x2 - x1 + 1; memset(dst + x1, color, len); } - dst += 256; + dst += _layerPitch; x1 = *pts++; } while (x1 >= 0); } @@ -341,8 +346,8 @@ void Graphics::drawPolygon(uint8_t color, bool hasAlpha, const Point *pts, uint8 debug(DBG_VIDEO, "Graphics::drawPolygon()"); assert(numPts * 4 < 0x100); - int16_t *apts1 = &_areaPoints[0x100]; - int16_t *apts2 = &_areaPoints[0x100 + numPts * 2]; + int16_t *apts1 = &_areaPoints[AREA_POINTS_SIZE]; + int16_t *apts2 = &_areaPoints[AREA_POINTS_SIZE + numPts * 2]; int16_t xmin, xmax, ymin, ymax; xmin = xmax = pts[0].x; diff --git a/graphics.h b/graphics.h index 2027a6e..69973ee 100644 --- a/graphics.h +++ b/graphics.h @@ -10,10 +10,13 @@ #include "intern.h" struct Graphics { + static const int AREA_POINTS_SIZE = 256 * 2; // maxY * sizeof(Point) / sizeof(int16_t) uint8_t *_layer; - int16_t _areaPoints[0x200]; + int _layerPitch; + int16_t _areaPoints[AREA_POINTS_SIZE * 2]; int16_t _crx, _cry, _crw, _crh; + void setLayer(uint8_t *layer, int pitch); void setClippingRect(int16_t vx, int16_t vy, int16_t vw, int16_t vh); void drawPoint(uint8_t color, const Point *pt); void drawLine(uint8_t color, const Point *pt1, const Point *pt2); diff --git a/intern.h b/intern.h index cf54b79..0fd89ab 100644 --- a/intern.h +++ b/intern.h @@ -80,7 +80,8 @@ enum Language { enum ResourceType { kResourceTypeAmiga, - kResourceTypeDOS + kResourceTypeDOS, + kResourceTypeMac, }; enum Skill { @@ -91,13 +92,15 @@ enum Skill { struct Options { bool bypass_protection; - bool play_disabled_cutscenes; bool enable_password_menu; bool fade_out_palette; bool use_tiledata; bool use_text_cutscenes; bool use_seq_cutscenes; - bool play_stone_cutscene; + bool play_asc_cutscene; + bool play_caillou_cutscene; + bool play_metro_cutscene; + bool play_serrure_cutscene; }; struct Color { diff --git a/main.cpp b/main.cpp index b57e4d7..e937f2d 100644 --- a/main.cpp +++ b/main.cpp @@ -38,6 +38,7 @@ static int detectVersion(FileSystem *fs) { { "LEVEL1.BNQ", kResourceTypeDOS, "DOS (Demo)" }, { "LEVEL1.LEV", kResourceTypeAmiga, "Amiga" }, { "DEMO.LEV", kResourceTypeAmiga, "Amiga (Demo)" }, + { "FLASHBACK.BIN", kResourceTypeMac, "Macintosh" }, { 0, -1, 0 } }; for (int i = 0; table[i].filename; ++i) { @@ -80,24 +81,29 @@ const char *g_caption = "REminiscence"; static void initOptions() { // defaults g_options.bypass_protection = true; - g_options.play_disabled_cutscenes = false; g_options.enable_password_menu = false; g_options.fade_out_palette = true; g_options.use_text_cutscenes = false; g_options.use_seq_cutscenes = true; + g_options.play_asc_cutscene = false; + g_options.play_caillou_cutscene = false; + g_options.play_metro_cutscene = false; + g_options.play_serrure_cutscene = false; // read configuration file struct { const char *name; bool *value; } opts[] = { { "bypass_protection", &g_options.bypass_protection }, - { "play_disabled_cutscenes", &g_options.play_disabled_cutscenes }, { "enable_password_menu", &g_options.enable_password_menu }, { "fade_out_palette", &g_options.fade_out_palette }, { "use_tiledata", &g_options.use_tiledata }, { "use_text_cutscenes", &g_options.use_text_cutscenes }, { "use_seq_cutscenes", &g_options.use_seq_cutscenes }, - { "play_stone_cutscene", &g_options.play_stone_cutscene }, + { "play_asc_cutscene", &g_options.play_asc_cutscene }, + { "play_caillou_cutscene", &g_options.play_caillou_cutscene }, + { "play_metro_cutscene", &g_options.play_metro_cutscene }, + { "play_serrure_cutscene", &g_options.play_serrure_cutscene }, { 0, 0 } }; static const char *filename = "rs.cfg"; @@ -251,7 +257,7 @@ int main(int argc, char *argv[]) { const Language language = (forcedLanguage == -1) ? detectLanguage(&fs) : (Language)forcedLanguage; SystemStub *stub = SystemStub_SDL_create(); Game *g = new Game(stub, &fs, savePath, levelNum, (ResourceType)version, language); - stub->init(g_caption, Video::GAMESCREEN_W, Video::GAMESCREEN_H, fullscreen, &scalerParameters); + stub->init(g_caption, g->_vid._w, g->_vid._h, fullscreen, &scalerParameters); g->run(); delete g; stub->destroy(); diff --git a/menu.cpp b/menu.cpp index c788b10..133d5bd 100644 --- a/menu.cpp +++ b/menu.cpp @@ -64,11 +64,24 @@ void Menu::drawString(const char *str, int16_t y, int16_t x, uint8_t color) { void Menu::drawString2(const char *str, int16_t y, int16_t x) { debug(DBG_MENU, "Menu::drawString2()"); - int i = 0; - for (; str[i]; ++i) { - _vid->PC_drawChar((uint8_t)str[i], y, x + i, true); + int w = Video::CHAR_W; + int h = Video::CHAR_H; + int len = 0; + switch (_res->_type) { + case kResourceTypeDOS: + for (; str[len]; ++len) { + _vid->PC_drawChar((uint8_t)str[len], y, x + len, true); + } + break; + case kResourceTypeMac: + for (; str[len]; ++len) { + _vid->MAC_drawStringChar(_vid->_frontLayer, _vid->_w, Video::CHAR_W * (x + len), Video::CHAR_H * y, _res->_fnt, _vid->_charFrontColor, (uint8_t)str[len]); + } + w *= _vid->_layerScale; + h *= _vid->_layerScale; + break; } - _vid->markBlockAsDirty(x * 8, y * 8, i * 8, 8); + _vid->markBlockAsDirty(x * w, y * h, len * w, h); } void Menu::loadPicture(const char *prefix) { @@ -216,7 +229,7 @@ bool Menu::handlePasswordScreen() { password[len] = '\0'; for (int level = 0; level < 8; ++level) { for (int skill = 0; skill < 3; ++skill) { - if (strcmp(_passwords[level][skill], password) == 0) { + if (strcmp(getLevelPassword(level, skill), password) == 0) { _level = level; _skill = skill; return true; @@ -328,17 +341,19 @@ void Menu::handleTitleScreen() { menuItems[menuItemsCount].str = LocaleData::LI_07_START; menuItems[menuItemsCount].opt = MENU_OPTION_ITEM_START; ++menuItemsCount; - if (g_options.enable_password_menu) { - menuItems[menuItemsCount].str = LocaleData::LI_08_SKILL; - menuItems[menuItemsCount].opt = MENU_OPTION_ITEM_SKILL; - ++menuItemsCount; - menuItems[menuItemsCount].str = LocaleData::LI_09_PASSWORD; - menuItems[menuItemsCount].opt = MENU_OPTION_ITEM_PASSWORD; - ++menuItemsCount; - } else { - menuItems[menuItemsCount].str = LocaleData::LI_06_LEVEL; - menuItems[menuItemsCount].opt = MENU_OPTION_ITEM_LEVEL; - ++menuItemsCount; + if (!_res->_isDemo) { + if (g_options.enable_password_menu) { + menuItems[menuItemsCount].str = LocaleData::LI_08_SKILL; + menuItems[menuItemsCount].opt = MENU_OPTION_ITEM_SKILL; + ++menuItemsCount; + menuItems[menuItemsCount].str = LocaleData::LI_09_PASSWORD; + menuItems[menuItemsCount].opt = MENU_OPTION_ITEM_PASSWORD; + ++menuItemsCount; + } else { + menuItems[menuItemsCount].str = LocaleData::LI_06_LEVEL; + menuItems[menuItemsCount].opt = MENU_OPTION_ITEM_LEVEL; + ++menuItemsCount; + } } menuItems[menuItemsCount].str = LocaleData::LI_10_INFO; menuItems[menuItemsCount].opt = MENU_OPTION_ITEM_INFO; @@ -435,3 +450,23 @@ void Menu::handleTitleScreen() { } } } + +const char *Menu::getLevelPassword(int level, int skill) const { + switch (_res->_type) { + case kResourceTypeAmiga: + if (level < 7) { + if (_res->_lang == LANG_FR) { + return _passwordsFrAmiga[skill * 7 + level]; + } else { + return _passwordsEnAmiga[skill * 7 + level]; + } + } + break; + case kResourceTypeMac: + return _passwordsMac[skill * 8 + level]; + case kResourceTypeDOS: + // default + break; + } + return _passwordsDOS[skill * 8 + level]; +} diff --git a/menu.h b/menu.h index 1b1b95b..64afef8 100644 --- a/menu.h +++ b/menu.h @@ -40,9 +40,10 @@ struct Menu { int opt; }; - static const char *_passwords[8][3]; + static const char *_passwordsDOS[]; static const char *_passwordsFrAmiga[]; static const char *_passwordsEnAmiga[]; + static const char *_passwordsMac[]; Resource *_res; SystemStub *_stub; @@ -72,6 +73,8 @@ struct Menu { bool handlePasswordScreen(); bool handleLevelScreen(); void handleTitleScreen(); + + const char *getLevelPassword(int level, int skill) const; }; #endif // MENU_H__ diff --git a/resource.cpp b/resource.cpp index 9840682..56d205e 100644 --- a/resource.cpp +++ b/resource.cpp @@ -4,6 +4,7 @@ * Copyright (C) 2005-2018 Gregory Montoir (cyx@users.sourceforge.net) */ +#include "decode_mac.h" #include "file.h" #include "fs.h" #include "resource.h" @@ -17,6 +18,7 @@ Resource::Resource(FileSystem *fs, ResourceType ver, Language lang) { _lang = lang; _isDemo = false; _aba = 0; + _mac = 0; _readUint16 = (_type == kResourceTypeDOS) ? READ_LE_UINT16 : READ_BE_UINT16; _readUint32 = (_type == kResourceTypeDOS) ? READ_LE_UINT32 : READ_BE_UINT32; _scratchBuffer = (uint8_t *)malloc(320 * 224 + 1024); @@ -63,16 +65,30 @@ void Resource::init() { _aba = new ResourceAba(_fs); _aba->readEntries(); _isDemo = true; - } else if (!_fs->exists("LEVEL1.MAP")) { // fbdemofr (no cutscenes) + } + if (!fileExists("LEVEL1.MAP")) { // fbdemofr (no cutscenes) _isDemo = true; } break; + case kResourceTypeMac: + _mac = new ResourceMac(ResourceMac::FILENAME, _fs); + _mac->loadMap(); + break; } } void Resource::fini() { } +bool Resource::fileExists(const char *filename) { + if (_fs->exists(filename)) { + return true; + } else if (_aba) { + return _aba->findEntry(filename) != 0; + } + return false; +} + void Resource::clearLevelRes() { free(_tbn); _tbn = 0; free(_mbk); _mbk = 0; @@ -96,6 +112,12 @@ void Resource::load_DEM(const char *filename) { if (_dem) { f.read(_dem, _demLen); } + } else if (_aba) { + uint32_t size; + _dem = _aba->loadEntry(filename, &size); + if (_dem) { + _demLen = size; + } } } @@ -386,6 +408,9 @@ void Resource::load_CINE() { error("Cannot load '%s'", _entryName); } break; + case kResourceTypeMac: + MAC_loadCutsceneText(); + break; } } @@ -893,6 +918,10 @@ void Resource::decodeOBJ(const uint8_t *tmp, int size) { uint32_t offsets[256]; int tmpOffset = 0; _numObjectNodes = 230; + if (_type == kResourceTypeMac) { + _numObjectNodes = _readUint16(tmp); + tmpOffset += 2; + } for (int i = 0; i < _numObjectNodes; ++i) { offsets[i] = _readUint32(tmp + tmpOffset); tmpOffset += 4; } @@ -1292,6 +1321,9 @@ int Resource::getBankDataSize(uint16_t num) { len &= 0x7FFF; } break; + case kResourceTypeMac: + assert(0); // different graphics format + break; } return len * 32; } @@ -1337,3 +1369,281 @@ uint8_t *Resource::loadBankData(uint16_t num) { return bankData; } +uint8_t *Resource::decodeResourceMacData(const char *name, bool decompressLzss) { + _resourceMacDataSize = 0; + uint8_t *data = 0; + const ResourceMacEntry *entry = _mac->findEntry(name); + if (entry) { + _mac->_f.seek(_mac->_dataOffset + entry->dataOffset); + _resourceMacDataSize = _mac->_f.readUint32BE(); + if (decompressLzss) { + data = decodeLzss(_mac->_f, _resourceMacDataSize); + } else { + data = (uint8_t *)malloc(_resourceMacDataSize); + if (data) { + _mac->_f.read(data, _resourceMacDataSize); + } + } + } else { + error("Resource '%s' not found", name); + } + return data; +} + +void Resource::MAC_decodeImageData(const uint8_t *ptr, int i, DecodeBuffer *dst) { + const uint8_t *basePtr = ptr; + const uint16_t sig = READ_BE_UINT16(ptr); ptr += 2; + assert(sig == 0xC211 || sig == 0xC103); + const int count = READ_BE_UINT16(ptr); ptr += 2; + assert(i < count); + ptr += 4; + const uint32_t offset = READ_BE_UINT32(ptr + i * 4); + if (offset != 0) { + ptr = basePtr + offset; + const int w = READ_BE_UINT16(ptr); ptr += 2; + const int h = READ_BE_UINT16(ptr); ptr += 2; + switch (sig) { + case 0xC211: + decodeC211(ptr + 4, w, h, dst); + break; + case 0xC103: + decodeC103(ptr, w, h, dst); + break; + } + } +} + +void Resource::MAC_decodeDataCLUT(const uint8_t *ptr) { + ptr += 6; // seed+flags + _clutSize = READ_BE_UINT16(ptr); ptr += 2; + assert(_clutSize < kClutSize); + for (int i = 0; i < _clutSize; ++i) { + const int index = READ_BE_UINT16(ptr); ptr += 2; + assert(i == index); + // ignore lower bits + _clut[i].r = ptr[0]; ptr += 2; + _clut[i].g = ptr[0]; ptr += 2; + _clut[i].b = ptr[0]; ptr += 2; + } +} + +void Resource::MAC_loadClutData() { + uint8_t *ptr = decodeResourceMacData("Flashback colors", false); + if (ptr) { + MAC_decodeDataCLUT(ptr); + free(ptr); + } +} + +void Resource::MAC_loadFontData() { + _fnt = decodeResourceMacData("Font", true); +} + +void Resource::MAC_loadIconData() { + _icn = decodeResourceMacData("Icons", true); +} + +void Resource::MAC_loadPersoData() { + _perso = decodeResourceMacData("Person", true); +} + +void Resource::MAC_loadMonsterData(const char *name, Color *clut) { + static const struct { + const char *id; + const char *name; + int index; + } data[] = { + { "junky", "Junky", 0x32 }, + { "mercenai", "Mercenary", 0x34 }, + { "replican", "Replicant", 0x35 }, + { "glue", "Alien", 0x36 }, + { 0, 0, 0 } + }; + free(_monster); + _monster = 0; + for (int i = 0; data[i].id; ++i) { + if (strcmp(data[i].id, name) == 0) { + _monster = decodeResourceMacData(data[i].name, true); + assert(_monster); + MAC_copyClut16(clut, 5, data[i].index); + break; + } + } +} + +void Resource::MAC_loadTitleImage(int i, DecodeBuffer *buf) { + char name[64]; + snprintf(name, sizeof(name), "Title %d", i); + uint8_t *ptr = decodeResourceMacData(name, (i == 6)); + if (ptr) { + MAC_decodeImageData(ptr, 0, buf); + free(ptr); + } +} + +void Resource::MAC_unloadLevelData() { + free(_ani); + _ani = 0; + ObjectNode *prevNode = 0; + for (int i = 0; i < _numObjectNodes; ++i) { + if (prevNode != _objectNodesMap[i]) { + free(_objectNodesMap[i]); + prevNode = _objectNodesMap[i]; + } + } + _numObjectNodes = 0; + free(_tbn); + _tbn = 0; + free(_str); + _str = 0; +} + +static const int _macLevelColorOffsets[] = { 24, 28, 36, 40, 44 }; // red palette: 32 +static const char *_macLevelNumbers[] = { "1", "2", "3", "4-1", "4-2", "5-1", "5-2" }; + +void Resource::MAC_loadLevelData(int level) { + char name[64]; + // .PGE + snprintf(name, sizeof(name), "Level %s objects", _macLevelNumbers[level]); + uint8_t *ptr = decodeResourceMacData(name, true); + if (ptr) { + decodePGE(ptr, _resourceMacDataSize); + free(ptr); + } else { + error("Failed to load '%s'", name); + } + // .ANI + snprintf(name, sizeof(name), "Level %s sequences", _macLevelNumbers[level]); + _ani = decodeResourceMacData(name, true); + if (_ani) { + assert(READ_BE_UINT16(_ani) == 0x48D); + } else { + error("Failed to load '%s'", name); + } + // .OBJ + snprintf(name, sizeof(name), "Level %s conditions", _macLevelNumbers[level]); + ptr = decodeResourceMacData(name, true); + if (ptr) { + assert(READ_BE_UINT16(ptr) == 0xE6); + decodeOBJ(ptr, _resourceMacDataSize); + free(ptr); + } else { + error("Failed to load '%s'", name); + } + // .CT + snprintf(name, sizeof(name), "Level %c map", _macLevelNumbers[level][0]); + ptr = decodeResourceMacData(name, true); + if (ptr) { + assert(_resourceMacDataSize == 0x1D00); + memcpy(_ctData, ptr, _resourceMacDataSize); + free(ptr); + } else { + error("Failed to load '%s'", name); + } + // .SPC + snprintf(name, sizeof(name), "Objects %c", _macLevelNumbers[level][0]); + _spc = decodeResourceMacData(name, true); + // .TBN + snprintf(name, sizeof(name), "Level %s names", _macLevelNumbers[level]); + _tbn = decodeResourceMacData(name, false); + + _str = decodeResourceMacData("Flashback strings", false); +} + +void Resource::MAC_loadLevelRoom(int level, int i, DecodeBuffer *dst) { + char name[64]; + snprintf(name, sizeof(name), "Level %c Room %d", _macLevelNumbers[level][0], i); + uint8_t *ptr = decodeResourceMacData(name, true); + if (ptr) { + MAC_decodeImageData(ptr, 0, dst); + free(ptr); + } +} + +void Resource::MAC_clearClut16(Color *clut, uint8_t dest) { + memset(&clut[dest * 16], 0, 16 * sizeof(Color)); +} + +void Resource::MAC_copyClut16(Color *clut, uint8_t dest, uint8_t src) { + memcpy(&clut[dest * 16], &_clut[src * 16], 16 * sizeof(Color)); +} + +void Resource::MAC_setupRoomClut(int level, int room, Color *clut) { + const int num = _macLevelNumbers[level][0] - '1'; + int offset = _macLevelColorOffsets[num]; + if (level == 1) { + switch (room) { + case 27: + case 28: + case 29: + case 30: + case 35: + case 36: + case 37: + case 45: + case 46: + offset = 32; + break; + } + } + for (int i = 0; i < 4; ++i) { + MAC_copyClut16(clut, i, offset + i); + MAC_copyClut16(clut, 8 + i, offset + i); + } + MAC_copyClut16(clut, 4, 0x30); + // 5 is monster palette + MAC_clearClut16(clut, 6); + MAC_copyClut16(clut, 0xA, _macLevelColorOffsets[0] + 2); + MAC_copyClut16(clut, 0xC, 0x37); + MAC_copyClut16(clut, 0xD, 0x38); +} + +const uint8_t *Resource::MAC_getImageData(const uint8_t *ptr, int i) { + const uint8_t *basePtr = ptr; + const uint16_t sig = READ_BE_UINT16(ptr); ptr += 2; + assert(sig == 0xC211); + const int count = READ_BE_UINT16(ptr); ptr += 2; + assert(i < count); + ptr += 4; + const uint32_t offset = READ_BE_UINT32(ptr + i * 4); + return (offset != 0) ? basePtr + offset : 0; +} + +bool Resource::MAC_hasLevelMap(int level, int room) const { + char name[64]; + snprintf(name, sizeof(name), "Level %c Room %d", _macLevelNumbers[level][0], room); + return _mac->findEntry(name) != 0; +} + +static void stringLowerCase(char *p) { + while (*p) { + if (*p >= 'A' && *p <= 'Z') { + *p += 'a' - 'A'; + } + ++p; + } +} + +void Resource::MAC_unloadCutscene() { + free(_cmd); + _cmd = 0; + free(_pol); + _pol = 0; +} + +void Resource::MAC_loadCutscene(const char *cutscene) { + char name[32]; + free(_cmd); + snprintf(name, sizeof(name), "%s movie", cutscene); + stringLowerCase(name); + _cmd = decodeResourceMacData(name, true); + free(_pol); + snprintf(name, sizeof(name), "%s polygons", cutscene); + stringLowerCase(name); + _pol = decodeResourceMacData(name, true); +} + +void Resource::MAC_loadCutsceneText() { + _cine_txt = decodeResourceMacData("Movie strings", false); + _cine_off = 0; // offsets are prepended to _cine_txt +} diff --git a/resource.h b/resource.h index 75c54ef..80bf8dc 100644 --- a/resource.h +++ b/resource.h @@ -9,7 +9,9 @@ #include "intern.h" #include "resource_aba.h" +#include "resource_mac.h" +struct DecodeBuffer; struct File; struct FileSystem; @@ -108,6 +110,10 @@ struct Resource { NUM_SPRITES = 1287 }; + enum { + kClutSize = 1024 + }; + static const uint16_t _voicesOffsetsTable[]; static const uint32_t _spmOffsetsTable[]; static const char *_splNames[]; @@ -117,6 +123,7 @@ struct Resource { Language _lang; bool _isDemo; ResourceAba *_aba; + ResourceMac *_mac; uint16_t (*_readUint16)(const void *); uint32_t (*_readUint32)(const void *); bool _hasSeqData; @@ -162,6 +169,12 @@ struct Resource { int _bankBuffersCount; uint8_t *_dem; int _demLen; + uint32_t _resourceMacDataSize; + int _clutSize; + Color _clut[kClutSize]; + uint8_t *_perso; + uint8_t *_monster; + uint8_t *_str; Resource(FileSystem *fs, ResourceType type, Language lang); ~Resource(); @@ -172,6 +185,8 @@ struct Resource { bool isDOS() const { return _type == kResourceTypeDOS; } bool isAmiga() const { return _type == kResourceTypeAmiga; } + bool fileExists(const char *filename); + void clearLevelRes(); void load_DEM(const char *filename); void load_FIB(const char *fileName); @@ -213,10 +228,16 @@ struct Resource { void load_BNQ(File *pf); void load_SPM(File *f); const uint8_t *getAniData(int num) const { + if (_type == kResourceTypeMac) { + const int count = READ_BE_UINT16(_ani); + assert(num < count); + const int offset = READ_BE_UINT16(_ani + 2 + num * 2); + return _ani + offset; + } const int offset = _readUint16(_ani + 2 + num * 2); return _ani + 2 + offset; } - const uint8_t *getTextString(int level, int num) { + const uint8_t *getTextString(int level, int num) const { if (_lang == LANG_JP) { const uint8_t *p = 0; switch (level) { @@ -246,29 +267,103 @@ struct Resource { } return p + READ_LE_UINT16(p + num * 2); } + if (_type == kResourceTypeMac) { + const int count = READ_BE_UINT16(_tbn); + assert(num < count); + const int offset = READ_BE_UINT16(_tbn + 2 + num * 2); + return _tbn + offset; + } return _tbn + _readUint16(_tbn + num * 2); } - const uint8_t *getGameString(int num) { + const uint8_t *getGameString(int num) const { + if (_type == kResourceTypeMac) { + const int count = READ_BE_UINT16(_str); + assert(num < count); + const int offset = READ_BE_UINT16(_str + 2 + num * 2); + return _str + offset; + } return _stringsTable + READ_LE_UINT16(_stringsTable + num * 2); } - const uint8_t *getCineString(int num) { + const uint8_t *getCineString(int num) const { if (_lang == LANG_JP) { const int offset = READ_BE_UINT16(LocaleData::_cineBinJP + num * 2); return LocaleData::_cineTxtJP + offset; } + if (_type == kResourceTypeMac) { + const int count = READ_BE_UINT16(_cine_txt); + assert(num < count); + const int offset = READ_BE_UINT16(_cine_txt + 2 + num * 2); + return _cine_txt + offset; + } if (_cine_off) { const int offset = READ_BE_UINT16(_cine_off + num * 2); return _cine_txt + offset; } return (num >= 0 && num < NUM_CUTSCENE_TEXTS) ? _cineStrings[num] : 0; } - const char *getMenuString(int num) { + const char *getMenuString(int num) const { return (num >= 0 && num < LocaleData::LI_NUM) ? _textsTable[num] : ""; } void clearBankData(); int getBankDataSize(uint16_t num); uint8_t *findBankData(uint16_t num); uint8_t *loadBankData(uint16_t num); + + uint8_t *decodeResourceMacData(const char *name, bool decompressLzss); + void MAC_decodeImageData(const uint8_t *ptr, int i, DecodeBuffer *dst); + void MAC_decodeDataCLUT(const uint8_t *ptr); + void MAC_loadClutData(); + void MAC_loadFontData(); + void MAC_loadIconData(); + void MAC_loadPersoData(); + void MAC_loadMonsterData(const char *name, Color *clut); + void MAC_loadTitleImage(int i, DecodeBuffer *buf); + void MAC_unloadLevelData(); + void MAC_loadLevelData(int level); + void MAC_loadLevelRoom(int level, int i, DecodeBuffer *dst); + void MAC_clearClut16(Color *clut, uint8_t dest); + void MAC_copyClut16(Color *clut, uint8_t dest, uint8_t src); + void MAC_setupRoomClut(int level, int room, Color *clut); + const uint8_t *MAC_getImageData(const uint8_t *ptr, int i); + bool MAC_hasLevelMap(int level, int room) const; + void MAC_unloadCutscene(); + void MAC_loadCutscene(const char *cutscene); + void MAC_loadCutsceneText(); + + int MAC_getPersoFrame(int anim) const { + static const int data[] = { + 0x000, 0x22E, + 0x28E, 0x2E9, + 0x4E9, 0x506, + -1 + }; + int offset = 0; + for (int i = 0; data[i] != -1; i += 2) { + if (anim >= data[i] && anim <= data[i + 1]) { + return offset + anim - data[i]; + } + const int count = data[i + 1] + 1 - data[i]; + offset += count; + } + assert(0); + return 0; + } + int MAC_getMonsterFrame(int anim) const { + static const int data[] = { + 0x22F, 0x28D, // junky - 94 + 0x2EA, 0x385, // mercenai - 156 + 0x387, 0x42F, // replican - 169 + 0x430, 0x4E8, // glue - 185 + -1 + }; + for (int i = 0; data[i] != -1; i += 2) { + if (anim >= data[i] && anim <= data[i + 1]) { + return anim - data[i]; + } + } + assert(0); + return 0; + } }; #endif // RESOURCE_H__ diff --git a/rs.cfg b/rs.cfg index db41998..fb5f9d6 100644 --- a/rs.cfg +++ b/rs.cfg @@ -1,9 +1,6 @@ # display copy protection shapes bypass_protection=true -# enable playback of 'asc', 'metro' and 'serrure' cutscenes -play_disabled_cutscenes=false - # use original password level selection menu screen enable_password_menu=false @@ -19,5 +16,14 @@ use_text_cutscenes=false # enable playback of .SEQ cutscenes (use polygonal if false) use_seq_cutscenes=true +# enable playback of 'ASC' cutscene +play_asc_cutscene=true + # enable playback of 'CAILLOU-F.SET' cutscene -play_stone_cutscene=true +play_caillou_cutscene=true + +# enable playback of 'METRO' cutscene +play_metro_cutscene=false + +# enable playback of 'SERRURE' cutscene +play_serrure_cutscene=true diff --git a/staticres.cpp b/staticres.cpp index 6d9c5d4..418af01 100644 --- a/staticres.cpp +++ b/staticres.cpp @@ -3223,15 +3223,10 @@ const uint8_t Game::_protectionPal[] = { 0x08, 0x88, 0x09, 0x99, 0x0A, 0xAA, 0x0B, 0xBB, 0x0C, 0xCC, 0x0D, 0xDD, 0x0E, 0xEE, 0x0F, 0xFF }; -const char *Menu::_passwords[8][3] = { - { "JAGUAR", "BANTHA", "TOHOLD" }, - { "COMBEL", "SHIVA", "PICOLO" }, - { "ANTIC", "KASYYK", "FUGU" }, - { "NOLAN", "SARLAC", "CAPSUL" }, - { "ARTHUR", "MAENOC", "ZZZAP" }, - { "SHIRYU", "SULUST", "MANIAC" }, - { "RENDER", "NEPTUN", "NO WAY" }, - { "BELUGA", "BELUGA", "BELUGA" } +const char *Menu::_passwordsDOS[] = { + "JAGUAR", "COMBEL", "ANTIC", "NOLAN", "ARTHUR", "SHIRYU", "RENDER", "BELUGA", // easy + "BANTHA", "SHIVA", "KASYYK", "SARLAC", "MAENOC", "SULUST", "NEPTUN", "BELUGA", // normal + "TOHOLD", "PICOLO", "FUGU", "CAPSUL", "ZZZAP", "MANIAC", "NO WAY", "BELUGA", // hard }; const char *Menu::_passwordsFrAmiga[] = { @@ -3246,6 +3241,12 @@ const char *Menu::_passwordsEnAmiga[] = { "MINE", "YOUR", "NEST", "LINE", "LISA", "MARY", "MICE", // hard }; +const char *Menu::_passwordsMac[] = { + "QUENCH", "GHOST", "LEGEND", "SPHERE", "BULLET", "DISRUPT", "TRAUMA", "OPAQUE", // easy + "HICK", "FRGO", "JERK", "KOIK", "KIMO", "LEDUX", "MORDO", "OPAQUE", // normal + "CURIOUS", "IMPACT", "LETHAL", "PERSIST", "MORTAL", "VERDICT", "KNIGHT", "OPAQUE", // hard +}; + const uint8_t Video::_conradPal1[] = { 0x00, 0x00, 0xCC, 0x0C, 0x8F, 0x08, 0x7E, 0x07, 0x6C, 0x06, 0x5B, 0x05, 0x4A, 0x04, 0x63, 0x09, 0x52, 0x07, 0x41, 0x06, 0x30, 0x06, 0x76, 0x0C, 0x14, 0x09, 0x25, 0x0B, 0x88, 0x08, 0xFF, 0x0F diff --git a/systemstub_sdl.cpp b/systemstub_sdl.cpp index 59d4971..6ed5551 100644 --- a/systemstub_sdl.cpp +++ b/systemstub_sdl.cpp @@ -234,7 +234,6 @@ void SystemStub_SDL::updateScreen(int shakeOffset) { r.x = 0; r.y = shakeOffset * _scaleFactor; SDL_GetRendererOutputSize(_renderer, &r.w, &r.h); - r.h -= r.y; SDL_RenderCopy(_renderer, _texture, 0, &r); } else { SDL_RenderCopy(_renderer, _texture, 0, 0); diff --git a/video.cpp b/video.cpp index f2ffb3e..99bae0a 100644 --- a/video.cpp +++ b/video.cpp @@ -4,6 +4,7 @@ * Copyright (C) 2005-2018 Gregory Montoir (cyx@users.sourceforge.net) */ +#include "decode_mac.h" #include "resource.h" #include "systemstub.h" #include "unpack.h" @@ -12,8 +13,9 @@ Video::Video(Resource *res, SystemStub *stub) : _res(res), _stub(stub) { - _w = GAMESCREEN_W; - _h = GAMESCREEN_H; + _layerScale = (_res->_type == kResourceTypeMac) ? 2 : 1; // Macintosh version is 512x448 + _w = GAMESCREEN_W * _layerScale; + _h = GAMESCREEN_H * _layerScale; _layerSize = _w * _h; _frontLayer = (uint8_t *)calloc(1, _layerSize); _backLayer = (uint8_t *)calloc(1,_layerSize); @@ -33,6 +35,9 @@ Video::Video(Resource *res, SystemStub *stub) case kResourceTypeDOS: _drawChar = &Video::PC_drawStringChar; break; + case kResourceTypeMac: + _drawChar = &Video::MAC_drawStringChar; + break; } } @@ -63,7 +68,7 @@ void Video::updateScreen() { debug(DBG_VIDEO, "Video::updateScreen()"); // _fullRefresh = true; if (_fullRefresh) { - _stub->copyRect(0, 0, _w, _h, _frontLayer, 256); + _stub->copyRect(0, 0, _w, _h, _frontLayer, _w); _stub->updateScreen(_shakeOffset); _fullRefresh = false; } else { @@ -78,14 +83,14 @@ void Video::updateScreen() { ++nh; } else if (nh != 0) { int16_t x = (i - nh) * SCREENBLOCK_W; - _stub->copyRect(x, j * SCREENBLOCK_H, nh * SCREENBLOCK_W, SCREENBLOCK_H, _frontLayer, 256); + _stub->copyRect(x, j * SCREENBLOCK_H, nh * SCREENBLOCK_W, SCREENBLOCK_H, _frontLayer, _w); nh = 0; ++count; } } if (nh != 0) { int16_t x = (i - nh) * SCREENBLOCK_W; - _stub->copyRect(x, j * SCREENBLOCK_H, nh * SCREENBLOCK_W, SCREENBLOCK_H, _frontLayer, 256); + _stub->copyRect(x, j * SCREENBLOCK_H, nh * SCREENBLOCK_W, SCREENBLOCK_H, _frontLayer, _w); ++count; } p += _w / SCREENBLOCK_W; @@ -800,11 +805,11 @@ void Video::drawSpriteSub6(const uint8_t *src, uint8_t *dst, int pitch, int h, i void Video::PC_drawChar(uint8_t c, int16_t y, int16_t x, bool forceDefaultFont) { debug(DBG_VIDEO, "Video::PC_drawChar(0x%X, %d, %d)", c, y, x); const uint8_t *fnt = (_res->_lang == LANG_JP && !forceDefaultFont) ? _font8Jp : _res->_fnt; - y *= 8; - x *= 8; + y *= CHAR_W; + x *= CHAR_H; const uint8_t *src = fnt + (c - 32) * 32; - uint8_t *dst = _frontLayer + x + 256 * y; - for (int h = 0; h < 8; ++h) { + uint8_t *dst = _frontLayer + x + _w * y; + for (int h = 0; h < CHAR_H; ++h) { for (int i = 0; i < 4; ++i, ++src) { const uint8_t c1 = *src >> 4; if (c1 != 0) { @@ -829,11 +834,12 @@ void Video::PC_drawChar(uint8_t c, int16_t y, int16_t x, bool forceDefaultFont) } ++dst; } - dst += 256 - 8; + dst += _w - CHAR_W; } } -void Video::AMIGA_drawStringChar(uint8_t *dst, int pitch, const uint8_t *src, uint8_t color, uint8_t chr) { +void Video::AMIGA_drawStringChar(uint8_t *dst, int pitch, int x, int y, const uint8_t *src, uint8_t color, uint8_t chr) { + dst += y * pitch + x; assert(chr >= 32); AMIGA_decodeIcn(src, chr - 32, _res->_scratchBuffer); src = _res->_scratchBuffer; @@ -848,7 +854,8 @@ void Video::AMIGA_drawStringChar(uint8_t *dst, int pitch, const uint8_t *src, ui } } -void Video::PC_drawStringChar(uint8_t *dst, int pitch, const uint8_t *src, uint8_t color, uint8_t chr) { +void Video::PC_drawStringChar(uint8_t *dst, int pitch, int x, int y, const uint8_t *src, uint8_t color, uint8_t chr) { + dst += y * pitch + x; assert(chr >= 32); src += (chr - 32) * 8 * 4; for (int y = 0; y < 8; ++y) { @@ -869,25 +876,45 @@ void Video::PC_drawStringChar(uint8_t *dst, int pitch, const uint8_t *src, uint8 } } +void Video::MAC_drawStringChar(uint8_t *dst, int pitch, int x, int y, const uint8_t *src, uint8_t color, uint8_t chr) { + DecodeBuffer buf; + memset(&buf, 0, sizeof(buf)); + buf.ptr = _frontLayer; + buf.w = buf.pitch = _w; + buf.h = _h; + buf.x = x * _layerScale; + buf.y = y * _layerScale; + buf.setPixel = Video::MAC_drawBufferFont; + _charFrontColor = color; + buf.dataPtr = this; + assert(chr >= 32); + _res->MAC_decodeImageData(_res->_fnt, chr - 32, &buf); +} + 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); const uint8_t *fnt = (_res->_lang == LANG_JP) ? _font8Jp : _res->_fnt; - drawCharFunc dcf = _drawChar; int len = 0; - uint8_t *dst = _frontLayer + y * 256 + x; while (1) { const uint8_t c = *str++; if (c == 0 || c == 0xB || c == 0xA) { break; } - (this->*dcf)(dst, 256, fnt, col, c); - dst += CHAR_W; + (this->*_drawChar)(_frontLayer, _w, x + len * CHAR_W, y, fnt, col, c); ++len; } - markBlockAsDirty(x, y, len * 8, 8); + markBlockAsDirty(x, y, len * CHAR_W, CHAR_H); return str - 1; } +void Video::drawStringLen(const char *str, int len, int x, int y, uint8_t color) { + const uint8_t *fnt = (_res->_lang == LANG_JP) ? _font8Jp : _res->_fnt; + for (int i = 0; i < len; ++i) { + (this->*_drawChar)(_frontLayer, _w, x + i * CHAR_W, y, fnt, color, str[i]); + } + markBlockAsDirty(x, y, len * CHAR_W, CHAR_H); +} + Color Video::AMIGA_convertColor(const uint16_t color, bool bgr) { // 4bits to 8bits int r = (color & 0xF00) >> 8; int g = (color & 0xF0) >> 4; @@ -901,3 +928,81 @@ Color Video::AMIGA_convertColor(const uint16_t color, bool bgr) { // 4bits to 8b c.b = (b << 4) | b; return c; } + +void Video::MAC_decodeMap(int level, int room) { +} + +void Video::MAC_markBlockAsDirty(int x, int y, int w, int h) { + if (x < 0) { + w += x; + x = 0; + } + if (x + w > _w) { + w = _w - x; + } + if (w <= 0) { + return; + } + if (y < 0) { + h += y; + y = 0; + } + if (y + h > _h) { + h = _h - y; + } + if (h <= 0) { + return; + } + markBlockAsDirty(x, y, w, h); +} + +void Video::MAC_drawBuffer(DecodeBuffer *buf, int src_x, int src_y, int src_w, int src_h, uint8_t color) { + const int y = buf->y + src_y; + if (y >= 0 && y < buf->h) { + const int x = buf->xflip ? (buf->x + (src_w - 1 - src_x)) : (buf->x + src_x); + if (x >= 0 && x < buf->w) { + const int offset = y * buf->pitch + x; + buf->ptr[offset] = color; + } + } +} + +void Video::MAC_drawBufferMask(DecodeBuffer *buf, int src_x, int src_y, int src_w, int src_h, uint8_t color) { + const int y = buf->y + src_y; + if (y >= 0 && y < buf->h) { + const int x = buf->xflip ? (buf->x + (src_w - 1 - src_x)) : (buf->x + src_x); + if (x >= 0 && x < buf->w) { + const int offset = y * buf->pitch + x; + if ((buf->ptr[offset] & 0x80) == 0) { + buf->ptr[offset] = color; + } + } + } +} + +void Video::MAC_drawBufferFont(DecodeBuffer *buf, int src_x, int src_y, int src_w, int src_h, uint8_t color) { + const int y = buf->y + src_y; + if (y >= 0 && y < buf->h) { + const int x = buf->x + src_x; + if (x >= 0 && x < buf->w) { + const Video *vid = (Video *)buf->dataPtr; + const int offset = y * buf->pitch + x; + switch (color) { + case 0xC0: + buf->ptr[offset] = vid->_charShadowColor; + break; + case 0xC1: + buf->ptr[offset] = vid->_charFrontColor; + break; + } + } + } +} + +void Video::MAC_fillRect(int x, int y, int w, int h, uint8_t color) { + uint8_t *p = _frontLayer + y * _layerScale * _w + x * _layerScale; + for (int j = 0; j < h * _layerScale; ++j) { + memset(p, color, w * _layerScale); + p += _w; + } +} diff --git a/video.h b/video.h index 6dc605b..6226177 100644 --- a/video.h +++ b/video.h @@ -13,7 +13,7 @@ struct Resource; struct SystemStub; struct Video { - typedef void (Video::*drawCharFunc)(uint8_t *, int, const uint8_t *, uint8_t, uint8_t); + typedef void (Video::*drawCharFunc)(uint8_t *, int, int, int, const uint8_t *, uint8_t, uint8_t); enum { GAMESCREEN_W = 256, @@ -35,6 +35,7 @@ struct Video { int _w, _h; int _layerSize; + int _layerScale; // 1 for Amiga/PC, 2 for Macintosh uint8_t *_frontLayer; uint8_t *_backLayer; uint8_t *_tempLayer; @@ -79,10 +80,18 @@ struct Video { void drawSpriteSub5(const uint8_t *src, uint8_t *dst, int pitch, int h, int w, uint8_t colMask); void drawSpriteSub6(const uint8_t *src, uint8_t *dst, int pitch, int h, int w, uint8_t colMask); void PC_drawChar(uint8_t c, int16_t y, int16_t x, bool forceDefaultFont = false); - void PC_drawStringChar(uint8_t *dst, int pitch, const uint8_t *src, uint8_t color, uint8_t chr); - void AMIGA_drawStringChar(uint8_t *dst, int pitch, const uint8_t *src, uint8_t color, uint8_t chr); + void PC_drawStringChar(uint8_t *dst, int pitch, int x, int y, const uint8_t *src, uint8_t color, uint8_t chr); + void AMIGA_drawStringChar(uint8_t *dst, int pitch, int x, int y, const uint8_t *src, uint8_t color, uint8_t chr); + void MAC_drawStringChar(uint8_t *dst, int pitch, int x, int y, const uint8_t *src, uint8_t color, uint8_t chr); const char *drawString(const char *str, int16_t x, int16_t y, uint8_t col); + void drawStringLen(const char *str, int len, int x, int y, uint8_t color); static Color AMIGA_convertColor(const uint16_t color, bool bgr = false); + void MAC_decodeMap(int level, int room); + void MAC_markBlockAsDirty(int x, int y, int w, int h); + static void MAC_drawBuffer(DecodeBuffer *buf, int src_x, int src_y, int src_w, int src_h, uint8_t color); + static void MAC_drawBufferMask(DecodeBuffer *buf, int src_x, int src_y, int src_w, int src_h, uint8_t color); + static void MAC_drawBufferFont(DecodeBuffer *buf, int src_x, int src_y, int src_w, int src_h, uint8_t color); + void MAC_fillRect(int x, int y, int w, int h, uint8_t color); }; #endif // VIDEO_H__