diff --git a/Makefile b/Makefile index b0c60bb..a3c6013 100644 --- a/Makefile +++ b/Makefile @@ -7,15 +7,17 @@ MODPLUG_LIBS := -lmodplug TREMOR_LIBS := -lvorbisidec -logg ZLIB_LIBS := -lz -CXXFLAGS += -O2 -Wall -MMD $(SDL_CFLAGS) -DUSE_MODPLUG -DUSE_TREMOR -DUSE_ZLIB +CXXFLAGS += -O2 -Wall -MMD $(SDL_CFLAGS) -DUSE_MODPLUG -DUSE_STATIC_SCALER -DUSE_TREMOR -DUSE_ZLIB 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) -DEPS = $(SRCS:.cpp=.d) +SCALERS := scalers/scaler_nearest.cpp scalers/scaler_tv2x.cpp scalers/scaler_xbrz.cpp scalers/xbrz/xbrz.cpp + +OBJS = $(SRCS:.cpp=.o) $(SCALERS:.cpp=.o) +DEPS = $(SRCS:.cpp=.d) $(SCALERS:.cpp=.d) LIBS = $(SDL_LIBS) $(DL_LIBS) $(MODPLUG_LIBS) $(TREMOR_LIBS) $(ZLIB_LIBS) diff --git a/README.txt b/README.txt index 5211a89..a65fe8f 100644 --- a/README.txt +++ b/README.txt @@ -1,6 +1,6 @@ REminiscence README -Release version: 0.4.2 +Release version: 0.4.3 ------------------------------------------------------------------------------- @@ -43,7 +43,7 @@ These paths can be changed using command line switches : --savepath=PATH Path to save files (default '.') --levelnum=NUM Level to start from (default '0') --fullscreen Fullscreen display - --widescreen 16:9 display + --widescreen=MODE 16:9 display --scaler=NAME@X Graphics scaler (default 'scale@3') --language=LANG Language (fr,en,de,sp,it,jp) @@ -52,6 +52,10 @@ addition to a scaling factor. External scalers are also supported, the suffix shall be used as the name. Eg. If you have scaler_xbrz.dll, you can pass '--scaler xbrz@2' to use that algorithm with a doubled window size (512x448). +The widescreen option accepts two modes : + 'adjacent' : left and right rooms bitmaps will be drawn (default) + 'mirror' : the current room bitmap will be drawn mirrored + In-game hotkeys : Arrow Keys move Conrad @@ -60,11 +64,12 @@ In-game hotkeys : Escape display the options Backspace display the inventory Alt Enter toggle windowed/fullscreen mode - Alt + and - change video scaler + Alt + and - increase or decrease game screen scaler factor Alt S write screenshot as .tga Ctrl S save game state Ctrl L load game state Ctrl + and - change game state slot + Function Keys change game screen scaler Debug hotkeys : diff --git a/cutscene.cpp b/cutscene.cpp index 3b1869d..7d7cea8 100644 --- a/cutscene.cpp +++ b/cutscene.cpp @@ -938,7 +938,12 @@ void Cutscene::op_handleKeys() { _cmdPtr = getCommandData(); n = READ_BE_UINT16(_cmdPtr + n * 2 + 2); } - _cmdPtr = _cmdPtrBak = getCommandData() + n + _startOffset; + if (_res->isMac()) { + _cmdPtr = getCommandData(); + _baseOffset = READ_BE_UINT16(_cmdPtr + 2 + n * 2); + n = 0; + } + _cmdPtr = _cmdPtrBak = getCommandData() + n + _baseOffset; } uint8_t Cutscene::fetchNextCmdByte() { @@ -951,7 +956,7 @@ uint16_t Cutscene::fetchNextCmdWord() { return i; } -void Cutscene::mainLoop(uint16_t offset) { +void Cutscene::mainLoop(uint16_t num) { _frameDelay = 5; _tstamp = _stub->getTimeStamp(); @@ -963,14 +968,20 @@ void Cutscene::mainLoop(uint16_t offset) { _newPal = false; _hasAlphaColor = false; const uint8_t *p = getCommandData(); - if (offset != 0) { - offset = READ_BE_UINT16(p + (offset + 1) * 2); + int offset = 0; + if (_res->isMac()) { + // const int count = READ_BE_UINT16(p); + _baseOffset = READ_BE_UINT16(p + 2 + num * 2); + } else { + if (num != 0) { + offset = READ_BE_UINT16(p + 2 + num * 2); + } + _baseOffset = (READ_BE_UINT16(p) + 1) * 2; } - _startOffset = (READ_BE_UINT16(p) + 1) * 2; _varKey = 0; - _cmdPtr = _cmdPtrBak = p + _startOffset + offset; + _cmdPtr = _cmdPtrBak = p + _baseOffset + offset; _polPtr = getPolygonData(); - debug(DBG_CUT, "_startOffset = %d offset = %d", _startOffset, offset); + debug(DBG_CUT, "_baseOffset = %d offset = %d", _baseOffset, offset); while (!_stub->_pi.quit && !_interrupted && !_stop) { uint8_t op = fetchNextCmdByte(); diff --git a/cutscene.h b/cutscene.h index e5d5fc9..972d962 100644 --- a/cutscene.h +++ b/cutscene.h @@ -71,7 +71,7 @@ struct Cutscene { uint8_t _frameDelay; bool _newPal; uint8_t _palBuf[0x20 * 2]; - uint16_t _startOffset; + uint16_t _baseOffset; bool _creditsSequence; uint32_t _rotMat[4]; uint8_t _primitiveColor; @@ -139,7 +139,7 @@ struct Cutscene { uint8_t fetchNextCmdByte(); uint16_t fetchNextCmdWord(); - void mainLoop(uint16_t offset); + void mainLoop(uint16_t num); bool load(uint16_t cutName); void unload(); void prepare(); diff --git a/game.cpp b/game.cpp index a75175c..ea67cf3 100644 --- a/game.cpp +++ b/game.cpp @@ -14,7 +14,7 @@ #include "unpack.h" #include "util.h" -Game::Game(SystemStub *stub, FileSystem *fs, const char *savePath, int level, ResourceType ver, Language lang) +Game::Game(SystemStub *stub, FileSystem *fs, const char *savePath, int level, ResourceType ver, Language lang, WidescreenMode widescreenMode) : _cut(&_res, stub, &_vid), _menu(&_res, stub, &_vid), _mix(fs, stub), _res(fs, ver, lang), _seq(stub, &_mix), _vid(&_res, stub), _stub(stub), _fs(fs), _savePath(savePath) { @@ -23,6 +23,7 @@ Game::Game(SystemStub *stub, FileSystem *fs, const char *savePath, int level, Re _skillLevel = _menu._skill = kSkillNormal; _currentLevel = _menu._level = level; _demoBin = -1; + _widescreenMode = widescreenMode; } void Game::run() { @@ -63,6 +64,12 @@ void Game::run() { _mix.init(); _mix._mod._isAmiga = _res.isAmiga(); + if (_res.isMac()) { + displayTitleScreenMac(Menu::kMacTitleScreen_MacPlay); + if (!_stub->_pi.quit) { + displayTitleScreenMac(Menu::kMacTitleScreen_Presage); + } + } playCutscene(0x40); playCutscene(0x0D); @@ -86,7 +93,7 @@ void Game::run() { break; } - bool presentMenu = (_res._type == kResourceTypeAmiga) || (_res._type == kResourceTypeDOS && _res.fileExists("MENU1.MAP")); + bool presentMenu = ((_res._type != kResourceTypeDOS) || _res.fileExists("MENU1.MAP")); while (!_stub->_pi.quit) { if (presentMenu) { _mix.playMusic(1); @@ -121,7 +128,7 @@ void Game::run() { _stub->setScreenSize(Video::GAMESCREEN_W, Video::GAMESCREEN_H); break; case kResourceTypeMac: - // TODO: + displayTitleScreenMac(Menu::kMacTitleScreen_Flashback); break; } if (_stub->_pi.quit) { @@ -172,10 +179,10 @@ void Game::run() { void Game::displayTitleScreenAmiga() { static const char *FILENAME = "present.cmp"; - _res.load_CMP_menu(FILENAME, _res._scratchBuffer); + _res.load_CMP_menu(FILENAME); static const int kW = 320; static const int kH = 224; - uint8_t *buf = (uint8_t *)calloc(kW * kH, 1); + uint8_t *buf = (uint8_t *)calloc(1, kW * kH); if (!buf) { error("Failed to allocate screen buffer w=%d h=%d", kW, kH); } @@ -190,13 +197,13 @@ void Game::displayTitleScreenAmiga() { _stub->setPaletteEntry(i, &c); } _stub->setScreenSize(kW, kH); + // fill with black _stub->copyRect(0, 0, kW, kH, buf, kW); _stub->updateScreen(0); _vid.AMIGA_decodeCmp(_res._scratchBuffer + 6, buf); - free(buf); int h = 0; while (1) { - if (h < kH / 2) { + if (h <= kH / 2) { const int y = kH / 2 - h; _stub->copyRect(0, y, kW, h * 2, buf, kW); _stub->updateScreen(0); @@ -212,6 +219,72 @@ void Game::displayTitleScreenAmiga() { } _stub->sleep(30); } + free(buf); +} + +void Game::displayTitleScreenMac(int num) { + const int w = 512; + int h = 384; + int clutBaseColor = 0; + switch (num) { + case Menu::kMacTitleScreen_MacPlay: + break; + case Menu::kMacTitleScreen_Presage: + clutBaseColor = 12; + break; + case Menu::kMacTitleScreen_Flashback: + case Menu::kMacTitleScreen_LeftEye: + case Menu::kMacTitleScreen_RightEye: + h = 448; + break; + case Menu::kMacTitleScreen_Controls: + break; + } + DecodeBuffer buf; + memset(&buf, 0, sizeof(buf)); + buf.ptr = _vid._frontLayer; + buf.pitch = buf.w = _vid._w; + buf.h = _vid._h; + buf.x = (_vid._w - w) / 2; + buf.y = (_vid._h - h) / 2; + buf.setPixel = Video::MAC_drawBuffer; + memset(_vid._frontLayer, 0, _vid._layerSize); + _res.MAC_loadTitleImage(num, &buf); + for (int i = 0; i < 12; ++i) { + Color palette[16]; + _res.MAC_copyClut16(palette, 0, clutBaseColor + i); + const int basePaletteColor = i * 16; + for (int j = 0; j < 16; ++j) { + _stub->setPaletteEntry(basePaletteColor + j, &palette[j]); + } + } + if (num == Menu::kMacTitleScreen_MacPlay) { + Color palette[16]; + _res.MAC_copyClut16(palette, 0, 56); + for (int i = 12; i < 16; ++i) { + const int basePaletteColor = i * 16; + for (int j = 0; j < 16; ++j) { + _stub->setPaletteEntry(basePaletteColor + j, &palette[j]); + } + } + } else if (num == Menu::kMacTitleScreen_Presage) { + Color c; + c.r = c.g = c.b = 0; + _stub->setPaletteEntry(0, &c); + } + _stub->copyRect(0, 0, _vid._w, _vid._h, _vid._frontLayer, _vid._w); + _stub->updateScreen(0); + while (1) { + _stub->processEvents(); + if (_stub->_pi.quit) { + break; + } + if (_stub->_pi.enter) { + _stub->_pi.enter = false; + break; + } + _stub->sleep(30); + } } void Game::resetGameState() { @@ -496,8 +569,12 @@ bool Game::handleConfigPanel() { switch (_res._type) { case kResourceTypeAmiga: - // TODO - return true; + for (int i = 0; i < h; ++i) { + for (int j = 0; j < w; ++j) { + _vid.fillRect(Video::CHAR_W * (x + j), Video::CHAR_H * (y + i), Video::CHAR_W, Video::CHAR_H, 0xE2); + } + } + break; case kResourceTypeDOS: // top-left rounded corner _vid.PC_drawChar(0x81, y, x, kUseDefaultFont); @@ -543,7 +620,7 @@ bool Game::handleConfigPanel() { _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); + _vid.fillRect(Video::CHAR_W * (x + j), Video::CHAR_H * (y + i), Video::CHAR_W, Video::CHAR_H, 0xE2); } } break; @@ -1523,7 +1600,7 @@ void Game::loadLevelMap() { _vid.AMIGA_decodeLev(_currentLevel, _currentRoom); break; case kResourceTypeDOS: - if (_stub->hasWidescreen()) { // draw adjacent rooms + if (_stub->hasWidescreen() && _widescreenMode == kWidescreenAdjacentRooms) { const int leftRoom = _res._ctData[CT_LEFT_ROOM + _currentRoom]; if (leftRoom > 0 && hasLevelMap(_currentLevel, leftRoom)) { _vid.PC_decodeMap(_currentLevel, leftRoom); @@ -1540,6 +1617,9 @@ void Game::loadLevelMap() { } } _vid.PC_decodeMap(_currentLevel, _currentRoom); + if (_stub->hasWidescreen() && _widescreenMode == kWidescreenMirrorRoom) { + _stub->copyRectMirrorBorders(Video::GAMESCREEN_W, Video::GAMESCREEN_H, _vid._backLayer); + } break; case kResourceTypeMac: _vid.MAC_decodeMap(_currentLevel, _currentRoom); diff --git a/game.h b/game.h index ee13086..410510d 100644 --- a/game.h +++ b/game.h @@ -86,11 +86,13 @@ struct Game { bool _saveStateCompleted; bool _endLoop; uint32_t _frameTimestamp; + WidescreenMode _widescreenMode; - Game(SystemStub *, FileSystem *, const char *savePath, int level, ResourceType ver, Language lang); + Game(SystemStub *, FileSystem *, const char *savePath, int level, ResourceType ver, Language lang, WidescreenMode widescreenMode); void run(); void displayTitleScreenAmiga(); + void displayTitleScreenMac(int num); void resetGameState(); void mainLoop(); void updateTiming(); diff --git a/intern.h b/intern.h index 9efae06..7959e20 100644 --- a/intern.h +++ b/intern.h @@ -100,6 +100,12 @@ enum Skill { kSkillExpert, }; +enum WidescreenMode { + kWidescreenNone, + kWidescreenAdjacentRooms, + kWidescreenMirrorRoom, +}; + struct Options { bool bypass_protection; bool enable_password_menu; diff --git a/main.cpp b/main.cpp index f400d0b..baf733a 100644 --- a/main.cpp +++ b/main.cpp @@ -22,7 +22,7 @@ static const char *USAGE = " --savepath=PATH Path to save files (default '.')\n" " --levelnum=NUM Start to level, bypass introduction\n" " --fullscreen Fullscreen display\n" - " --widescreen 16:9 display\n" + " --widescreen=MODE 16:9 display\n" " --scaler=NAME@X Graphics scaler (default 'scale@3')\n" " --language=LANG Language (fr,en,de,sp,it,jp)\n" ; @@ -74,6 +74,7 @@ static Language detectLanguage(FileSystem *fs) { return table[i].language; } } + warning("Unable to detect language, defaults to English"); return LANG_EN; } @@ -126,12 +127,17 @@ static void initOptions() { } if (*p) { const bool value = (*p == 't' || *p == 'T' || *p == '1'); + bool foundOption = false; for (int i = 0; opts[i].name; ++i) { if (strncmp(buf, opts[i].name, strlen(opts[i].name)) == 0) { *opts[i].value = value; + foundOption = true; break; } } + if (!foundOption) { + warning("Unhandled option '%s', ignoring", buf); + } } } } @@ -143,10 +149,16 @@ static void parseScaler(char *name, ScalerParameters *scalerParameters) { struct { const char *name; int type; + const Scaler *scaler; } scalers[] = { - { "point", kScalerTypePoint }, - { "linear", kScalerTypeLinear }, - { "scale", kScalerTypeInternal }, + { "point", kScalerTypePoint, 0 }, + { "linear", kScalerTypeLinear, 0 }, + { "scale", kScalerTypeInternal, &_internalScaler }, +#ifdef USE_STATIC_SCALER + { "nearest", kScalerTypeInternal, &scaler_nearest }, + { "tv2x", kScalerTypeInternal, &scaler_tv2x }, + { "xbrz", kScalerTypeInternal, &scaler_xbrz }, +#endif { 0, -1 } }; bool found = false; @@ -157,6 +169,7 @@ static void parseScaler(char *name, ScalerParameters *scalerParameters) { for (int i = 0; scalers[i].name; ++i) { if (strcmp(scalers[i].name, name) == 0) { scalerParameters->type = (ScalerType)scalers[i].type; + scalerParameters->scaler = scalers[i].scaler; found = true; break; } @@ -179,12 +192,30 @@ static void parseScaler(char *name, ScalerParameters *scalerParameters) { } } +static WidescreenMode parseWidescreen(const char *mode) { + static const struct { + const char *name; + WidescreenMode mode; + } modes[] = { + { "adjacent", kWidescreenAdjacentRooms }, + { "mirror", kWidescreenMirrorRoom }, + { 0, kWidescreenNone }, + }; + for (int i = 0; modes[i].name; ++i) { + if (strcasecmp(modes[i].name, mode) == 0) { + return modes[i].mode; + } + } + warning("Unhandled widecreen mode '%s', defaults to adjacent rooms", mode); + return kWidescreenAdjacentRooms; // default value +} + int main(int argc, char *argv[]) { const char *dataPath = "DATA"; const char *savePath = "."; int levelNum = 0; bool fullscreen = false; - bool widescreen = false; + WidescreenMode widescreen = kWidescreenNone; ScalerParameters scalerParameters = ScalerParameters::defaults(); int forcedLanguage = -1; if (argc == 2) { @@ -202,7 +233,7 @@ int main(int argc, char *argv[]) { { "fullscreen", no_argument, 0, 4 }, { "scaler", required_argument, 0, 5 }, { "language", required_argument, 0, 6 }, - { "widescreen", no_argument, 0, 7 }, + { "widescreen", required_argument, 0, 7 }, { 0, 0, 0, 0 } }; int index; @@ -248,7 +279,7 @@ int main(int argc, char *argv[]) { } break; case 7: - widescreen = true; + widescreen = parseWidescreen(optarg); break; default: printf(USAGE, argv[0]); @@ -265,8 +296,8 @@ 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, g->_vid._w, g->_vid._h, fullscreen, widescreen, &scalerParameters); + Game *g = new Game(stub, &fs, savePath, levelNum, (ResourceType)version, language, widescreen); + stub->init(g_caption, g->_vid._w, g->_vid._h, fullscreen, widescreen != kWidescreenNone, &scalerParameters); g->run(); delete g; stub->destroy(); diff --git a/menu.cpp b/menu.cpp index 5fed812..349d6bf 100644 --- a/menu.cpp +++ b/menu.cpp @@ -68,6 +68,11 @@ void Menu::drawString2(const char *str, int16_t y, int16_t x) { int h = Video::CHAR_H; int len = 0; switch (_res->_type) { + case kResourceTypeAmiga: + for (; str[len]; ++len) { + _vid->AMIGA_drawStringChar(_vid->_frontLayer, _vid->_w, Video::CHAR_W * (x + len), Video::CHAR_H * y, _res->_fnt, _vid->_charFrontColor, (uint8_t)str[len]); + } + break; case kResourceTypeDOS: for (; str[len]; ++len) { _vid->PC_drawChar((uint8_t)str[len], y, x + len, true); @@ -77,8 +82,6 @@ void Menu::drawString2(const char *str, int16_t y, int16_t x) { 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 * w, y * h, len * w, h); diff --git a/menu.h b/menu.h index 5974198..bbf0d58 100644 --- a/menu.h +++ b/menu.h @@ -30,6 +30,14 @@ struct Menu { SCREEN_LEVEL, SCREEN_INFO }; + enum { + kMacTitleScreen_MacPlay = 1, + kMacTitleScreen_Presage = 2, + kMacTitleScreen_Flashback = 3, + kMacTitleScreen_LeftEye = 4, + kMacTitleScreen_RightEye = 5, + kMacTitleScreen_Controls = 6 + }; enum { EVENTS_DELAY = 80 diff --git a/resource.cpp b/resource.cpp index 247f627..d9b2d75 100644 --- a/resource.cpp +++ b/resource.cpp @@ -21,7 +21,7 @@ Resource::Resource(FileSystem *fs, ResourceType ver, Language lang) { _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); + _scratchBuffer = (uint8_t *)malloc(kScratchBufferSize); if (!_scratchBuffer) { error("Unable to allocate temporary memory buffer"); } @@ -36,9 +36,9 @@ Resource::Resource(FileSystem *fs, ResourceType ver, Language lang) { Resource::~Resource() { clearLevelRes(); + MAC_unloadLevelData(); free(_fnt); - free(_icn); _icn = 0; - _icnLen = 0; + free(_icn); free(_tab); free(_spc); free(_spr1); @@ -268,7 +268,7 @@ void Resource::load_PAL_menu(const char *fileName, uint8_t *dstPtr) { error("Cannot load '%s'", _entryName); } -void Resource::load_CMP_menu(const char *fileName, uint8_t *dstPtr) { +void Resource::load_CMP_menu(const char *fileName) { File f; if (f.open(fileName, "rb", _fs)) { const uint32_t size = f.readUint32BE(); @@ -277,7 +277,7 @@ void Resource::load_CMP_menu(const char *fileName, uint8_t *dstPtr) { error("Failed to allocate CMP temporary buffer"); } f.read(tmp, size); - if (!delphine_unpack(dstPtr, tmp, size)) { + if (!delphine_unpack(_scratchBuffer, kScratchBufferSize, tmp, size)) { error("Bad CRC for %s", fileName); } free(tmp); @@ -668,7 +668,7 @@ void Resource::load(const char *objName, int objType, const char *ext) { _pal = dat; break; case OT_CT: - if (!delphine_unpack((uint8_t *)_ctData, dat, size)) { + if (!delphine_unpack((uint8_t *)_ctData, sizeof(_ctData), dat, size)) { error("Bad CRC for '%s'", _entryName); } free(dat); @@ -736,7 +736,7 @@ void Resource::load_CT(File *pf) { error("Unable to allocate CT buffer"); } else { pf->read(tmp, len); - if (!delphine_unpack((uint8_t *)_ctData, tmp, len)) { + if (!delphine_unpack((uint8_t *)_ctData, sizeof(_ctData), tmp, len)) { error("Bad CRC for collision data"); } free(tmp); @@ -936,7 +936,7 @@ void Resource::load_OBC(File *f) { } f->seek(4); f->read(packedData, packedSize); - if (!delphine_unpack(tmp, packedData, packedSize)) { + if (!delphine_unpack(tmp, unpackedSize, packedData, packedSize)) { error("Bad CRC for compressed object data"); } free(packedData); @@ -1153,7 +1153,7 @@ void Resource::load_CMP(File *pf) { } if (data[0].packedSize == data[0].size) { memcpy(_pol, tmp + data[0].offset, data[0].packedSize); - } else if (!delphine_unpack(_pol, tmp + data[0].offset, data[0].packedSize)) { + } else if (!delphine_unpack(_pol, data[0].size, tmp + data[0].offset, data[0].packedSize)) { error("Bad CRC for cutscene polygon data"); } _cmd = (uint8_t *)malloc(data[1].size); @@ -1162,7 +1162,7 @@ void Resource::load_CMP(File *pf) { } if (data[1].packedSize == data[1].size) { memcpy(_cmd, tmp + data[1].offset, data[1].packedSize); - } else if (!delphine_unpack(_cmd, tmp + data[1].offset, data[1].packedSize)) { + } else if (!delphine_unpack(_cmd, data[1].size, tmp + data[1].offset, data[1].packedSize)) { error("Bad CRC for cutscene command data"); } free(tmp); @@ -1278,7 +1278,7 @@ void Resource::load_SGD(File *f) { if (!_sgd) { error("Unable to allocate SGD buffer"); } - if (!delphine_unpack(_sgd, tmp, len)) { + if (!delphine_unpack(_sgd, size, tmp, len)) { error("Bad CRC for SGD data"); } free(tmp); @@ -1310,12 +1310,12 @@ void Resource::load_SPM(File *f) { if (!_spr1) { error("Unable to allocate SPR1 buffer"); } - if (!delphine_unpack(_spr1, tmp, len)) { + if (!delphine_unpack(_spr1, size, tmp, len)) { error("Bad CRC for SPM data"); } } else { assert(size <= sizeof(_sprm)); - if (!delphine_unpack(_sprm, tmp, len)) { + if (!delphine_unpack(_sprm, sizeof(_sprm), tmp, len)) { error("Bad CRC for SPM data"); } } @@ -1391,7 +1391,7 @@ uint8_t *Resource::loadBankData(uint16_t num) { } else { assert(dataOffset > 4); assert(size == (int)READ_BE_UINT32(data - 4)); - if (!delphine_unpack(_bankDataHead, data, 0)) { + if (!delphine_unpack(_bankDataHead, _bankDataTail - _bankDataHead, data, 0)) { error("Bad CRC for bank data %d", num); } } diff --git a/resource.h b/resource.h index 91f89a5..29db4c3 100644 --- a/resource.h +++ b/resource.h @@ -112,7 +112,8 @@ struct Resource { enum { kPaulaFreq = 3546897, - kClutSize = 1024 + kClutSize = 1024, + kScratchBufferSize = 320 * 224 + 1024 }; static const uint16_t _voicesOffsetsTable[]; @@ -187,6 +188,7 @@ struct Resource { bool isDOS() const { return _type == kResourceTypeDOS; } bool isAmiga() const { return _type == kResourceTypeAmiga; } + bool isMac() const { return _type == kResourceTypeMac; } bool fileExists(const char *filename); @@ -196,7 +198,7 @@ struct Resource { void load_SPL_demo(); void load_MAP_menu(const char *fileName, uint8_t *dstPtr); void load_PAL_menu(const char *fileName, uint8_t *dstPtr); - void load_CMP_menu(const char *fileName, uint8_t *dstPtr); + void load_CMP_menu(const char *fileName); void load_SPR_OFF(const char *fileName, uint8_t *sprData); void load_CINE(); void free_CINE(); diff --git a/resource_aba.cpp b/resource_aba.cpp index f1a55bf..b76ef21 100644 --- a/resource_aba.cpp +++ b/resource_aba.cpp @@ -74,7 +74,7 @@ uint8_t *ResourceAba::loadEntry(const char *name, uint32_t *size) { free(tmp); return 0; } - const bool ret = delphine_unpack(dst, tmp, e->compressedSize); + const bool ret = delphine_unpack(dst, e->size, tmp, e->compressedSize); if (!ret) { error("Bad CRC for '%s'", name); } diff --git a/rs.cfg b/rs.cfg index 4202fe5..3972c13 100644 --- a/rs.cfg +++ b/rs.cfg @@ -16,7 +16,7 @@ use_tiledata=false # display text instead of playing the polygon cutscenes use_text_cutscenes=false -# enable playback of .SEQ cutscenes (use polygonal if false) +# enable playback of .SEQ cutscenes (always use polygon cutscenes if false) use_seq_cutscenes=true # enable playback of 'ASC' cutscene diff --git a/scaler.h b/scaler.h index 5070c69..46f6603 100644 --- a/scaler.h +++ b/scaler.h @@ -31,4 +31,10 @@ extern const Scaler _internalScaler; const Scaler *findScaler(const char *name); +#ifdef USE_STATIC_SCALER +extern const Scaler scaler_nearest; +extern const Scaler scaler_tv2x; +extern const Scaler scaler_xbrz; +#endif + #endif // SCALER_H__ diff --git a/systemstub.h b/systemstub.h index c1664fe..0b70c4e 100644 --- a/systemstub.h +++ b/systemstub.h @@ -68,6 +68,7 @@ struct SystemStub { virtual void copyRectRgb24(int x, int y, int w, int h, const uint8_t *rgb) = 0; virtual void copyRectLeftBorder(int w, int h, const uint8_t *buf) = 0; virtual void copyRectRightBorder(int w, int h, const uint8_t *buf) = 0; + virtual void copyRectMirrorBorders(int w, int h, const uint8_t *buf) = 0; virtual void fadeScreen() = 0; virtual void updateScreen(int shakeOffset) = 0; diff --git a/systemstub_sdl.cpp b/systemstub_sdl.cpp index 8d61544..fab6537 100644 --- a/systemstub_sdl.cpp +++ b/systemstub_sdl.cpp @@ -66,6 +66,7 @@ struct SystemStub_SDL : SystemStub { virtual void copyRectRgb24(int x, int y, int w, int h, const uint8_t *rgb); virtual void copyRectLeftBorder(int w, int h, const uint8_t *buf); virtual void copyRectRightBorder(int w, int h, const uint8_t *buf); + virtual void copyRectMirrorBorders(int w, int h, const uint8_t *buf); virtual void fadeScreen(); virtual void updateScreen(int shakeOffset); virtual void processEvents(); @@ -82,6 +83,7 @@ struct SystemStub_SDL : SystemStub { void prepareGraphics(); void cleanupGraphics(); void changeGraphics(bool fullscreen, int scaleFactor); + void changeScaler(int scaler); void drawRect(int x, int y, int w, int h, uint8_t color); }; @@ -97,13 +99,13 @@ void SystemStub_SDL::init(const char *title, int w, int h, bool fullscreen, bool _window = 0; _renderer = 0; _texture = 0; - _fmt = 0; + _fmt = SDL_AllocFormat(kPixelFormat); _screenBuffer = 0; _fadeOnUpdateScreen = false; _fullscreen = fullscreen; _scalerType = scalerParameters->type; _scaler = scalerParameters->scaler; - _scaleFactor = CLIP(scalerParameters->factor, _scaler->factorMin, _scaler->factorMax); + _scaleFactor = _scaler ? CLIP(scalerParameters->factor, _scaler->factorMin, _scaler->factorMax) : 1; memset(_rgbPalette, 0, sizeof(_rgbPalette)); memset(_darkPalette, 0, sizeof(_darkPalette)); _screenW = _screenH = 0; @@ -127,6 +129,14 @@ void SystemStub_SDL::init(const char *title, int w, int h, bool fullscreen, bool void SystemStub_SDL::destroy() { cleanupGraphics(); + if (_screenBuffer) { + free(_screenBuffer); + _screenBuffer = 0; + } + if (_fmt) { + SDL_FreeFormat(_fmt); + _fmt = 0; + } if (_controller) { SDL_GameControllerClose(_controller); _controller = 0; @@ -147,6 +157,10 @@ void SystemStub_SDL::setScreenSize(int w, int h) { return; } cleanupGraphics(); + if (_screenBuffer) { + free(_screenBuffer); + _screenBuffer = 0; + } const int screenBufferSize = w * h * sizeof(uint32_t); _screenBuffer = (uint32_t *)calloc(1, screenBufferSize); if (!_screenBuffer) { @@ -232,6 +246,19 @@ void SystemStub_SDL::copyRectRgb24(int x, int y, int w, int h, const uint8_t *rg } } +static void clearTexture(SDL_Texture *texture, int h, SDL_PixelFormat *fmt) { + void *dst = 0; + int pitch = 0; + if (SDL_LockTexture(texture, 0, &dst, &pitch) == 0) { + assert((pitch & 3) == 0); + const uint32_t color = SDL_MapRGB(fmt, 0, 0, 0); + for (uint32_t i = 0; i < h * pitch / sizeof(uint32_t); ++i) { + ((uint32_t *)dst)[i] = color; + } + SDL_UnlockTexture(texture); + } +} + void SystemStub_SDL::copyRectLeftBorder(int w, int h, const uint8_t *buf) { assert(w >= _wideMargin); uint32_t *rgb = (uint32_t *)malloc(w * h * sizeof(uint32_t)); @@ -241,7 +268,10 @@ void SystemStub_SDL::copyRectLeftBorder(int w, int h, const uint8_t *buf) { rgb[i] = _darkPalette[buf[i]]; } } else { - memset(rgb, 0, w * h * sizeof(uint32_t)); + const uint32_t color = SDL_MapRGB(_fmt, 0, 0, 0); + for (int i = 0; i < w * h; ++i) { + rgb[i] = color; + } } const int xOffset = w - _wideMargin; SDL_Rect r; @@ -263,7 +293,10 @@ void SystemStub_SDL::copyRectRightBorder(int w, int h, const uint8_t *buf) { rgb[i] = _darkPalette[buf[i]]; } } else { - memset(rgb, 0, w * h * sizeof(uint32_t)); + const uint32_t color = SDL_MapRGB(_fmt, 0, 0, 0); + for (int i = 0; i < w * h; ++i) { + rgb[i] = color; + } } const int xOffset = 0; SDL_Rect r; @@ -276,6 +309,35 @@ void SystemStub_SDL::copyRectRightBorder(int w, int h, const uint8_t *buf) { } } +void SystemStub_SDL::copyRectMirrorBorders(int w, int h, const uint8_t *buf) { + assert(w >= _wideMargin); + uint32_t *rgb = (uint32_t *)malloc(w * h * sizeof(uint32_t)); + if (rgb) { + for (int i = 0; i < w * h; ++i) { + rgb[i] = _darkPalette[buf[i]]; + } + void *dst = 0; + int pitch = 0; + if (SDL_LockTexture(_wideTexture, 0, &dst, &pitch) == 0) { + assert((pitch & 3) == 0); + uint32_t *p = (uint32_t *)dst; + for (int y = 0; y < h; ++y) { + for (int x = 0; x < _wideMargin; ++x) { + // left side + const int xLeft = _wideMargin - 1 - x; + p[x] = rgb[y * w + xLeft]; + // right side + const int xRight = w - 1 - x; + p[_wideMargin + _screenW + x] = rgb[y * w + xRight]; + } + p += pitch / sizeof(uint32_t); + } + SDL_UnlockTexture(_wideTexture); + } + free(rgb); + } +} + void SystemStub_SDL::fadeScreen() { _fadeOnUpdateScreen = true; } @@ -511,11 +573,15 @@ void SystemStub_SDL::processEvent(const SDL_Event &ev, bool &paused) { break; case SDLK_KP_PLUS: case SDLK_PAGEUP: - changeGraphics(_fullscreen, _scaleFactor + 1); + if (_scalerType == kScalerTypeInternal || _scalerType == kScalerTypeExternal) { + changeGraphics(_fullscreen, _scaleFactor + 1); + } break; case SDLK_KP_MINUS: case SDLK_PAGEDOWN: - changeGraphics(_fullscreen, _scaleFactor - 1); + if (_scalerType == kScalerTypeInternal || _scalerType == kScalerTypeExternal) { + changeGraphics(_fullscreen, _scaleFactor - 1); + } break; case SDLK_s: { char name[32]; @@ -585,6 +651,16 @@ void SystemStub_SDL::processEvent(const SDL_Event &ev, bool &paused) { case SDLK_ESCAPE: _pi.escape = false; break; + case SDLK_F1: + case SDLK_F2: + case SDLK_F3: + case SDLK_F4: + case SDLK_F5: + case SDLK_F6: + case SDLK_F7: + case SDLK_F8: + changeScaler(ev.key.keysym.sym - SDLK_F1); + break; default: break; } @@ -717,21 +793,18 @@ void SystemStub_SDL::prepareGraphics() { _renderer = SDL_CreateRenderer(_window, -1, SDL_RENDERER_ACCELERATED); SDL_RenderSetLogicalSize(_renderer, windowW, windowH); _texture = SDL_CreateTexture(_renderer, kPixelFormat, SDL_TEXTUREACCESS_STREAMING, _texW, _texH); - _fmt = SDL_AllocFormat(kPixelFormat); if (_widescreen) { const int w = _screenH * 16 / 9; const int h = _screenH; _wideTexture = SDL_CreateTexture(_renderer, kPixelFormat, SDL_TEXTUREACCESS_STREAMING, w, h); + clearTexture(_wideTexture, _screenH, _fmt); + // left and right borders _wideMargin = (w - _screenW) / 2; } } void SystemStub_SDL::cleanupGraphics() { - if (_screenBuffer) { - free(_screenBuffer); - _screenBuffer = 0; - } if (_texture) { SDL_DestroyTexture(_texture); _texture = 0; @@ -748,10 +821,6 @@ void SystemStub_SDL::cleanupGraphics() { SDL_DestroyWindow(_window); _window = 0; } - if (_fmt) { - SDL_FreeFormat(_fmt); - _fmt = 0; - } } void SystemStub_SDL::changeGraphics(bool fullscreen, int scaleFactor) { @@ -762,29 +831,53 @@ void SystemStub_SDL::changeGraphics(bool fullscreen, int scaleFactor) { } _fullscreen = fullscreen; _scaleFactor = factor; - if (_texture) { - SDL_DestroyTexture(_texture); - _texture = 0; - } - if (_wideTexture) { - SDL_DestroyTexture(_wideTexture); - _wideTexture = 0; - } - if (_renderer) { - SDL_DestroyRenderer(_renderer); - _renderer = 0; - } - if (_window) { - SDL_DestroyWindow(_window); - _window = 0; - } - if (_fmt) { - SDL_FreeFormat(_fmt); - _fmt = 0; - } + cleanupGraphics(); prepareGraphics(); } +void SystemStub_SDL::changeScaler(int scaler) { + ScalerParameters scalerParameters = ScalerParameters::defaults(); + switch (scaler) { + case 0: + scalerParameters.type = kScalerTypePoint; + break; + case 1: + scalerParameters.type = kScalerTypeLinear; + break; + case 2: + scalerParameters.type = kScalerTypeInternal; + scalerParameters.scaler = &_internalScaler; + break; +#ifdef USE_STATIC_SCALER + case 3: + scalerParameters.type = kScalerTypeInternal; + scalerParameters.scaler = &scaler_nearest; + break; + case 4: + scalerParameters.type = kScalerTypeInternal; + scalerParameters.scaler = &scaler_tv2x; + break; + case 5: + scalerParameters.type = kScalerTypeInternal; + scalerParameters.scaler = &scaler_xbrz; + break; +#endif + default: + return; + } + if (_scalerType != scalerParameters.type || scalerParameters.scaler != _scaler) { + _scalerType = scalerParameters.type; + _scaler = scalerParameters.scaler; + const int scaleFactor = CLIP(_scaleFactor, _scaler->factorMin, _scaler->factorMax); + // only recreate the window if dimensions actually changed + if (scaleFactor != _scaleFactor) { + cleanupGraphics(); + _scaleFactor = scaleFactor; + prepareGraphics(); + } + } +} + void SystemStub_SDL::drawRect(int x, int y, int w, int h, uint8_t color) { const int x1 = x; const int y1 = y; diff --git a/unpack.cpp b/unpack.cpp index ad490a2..44fe0bc 100644 --- a/unpack.cpp +++ b/unpack.cpp @@ -5,9 +5,10 @@ */ #include "unpack.h" +#include "util.h" struct UnpackCtx { - int datasize; + int size; uint32_t crc; uint32_t bits; uint8_t *dst; @@ -26,40 +27,52 @@ static bool nextBit(UnpackCtx *uc) { return carry; } -static uint16_t getBits(UnpackCtx *uc, int bitsCount) { - uint16_t c = 0; - for (int i = 0; i < bitsCount; ++i) { - c <<= 1; +static int getBits(UnpackCtx *uc, int count) { + int bits = 0; + for (int i = 0; i < count; ++i) { + bits <<= 1; if (nextBit(uc)) { - c |= 1; + bits |= 1; } } - return c; + return bits; } static void copyLiteral(UnpackCtx *uc, int bitsCount, int len) { - const int count = getBits(uc, bitsCount) + len + 1; - for (int i = 0; i < count; ++i) { - *uc->dst = (uint8_t)getBits(uc, 8); - --uc->dst; + int count = getBits(uc, bitsCount) + len + 1; + uc->size -= count; + if (uc->size < 0) { + count += uc->size; + uc->size = 0; } - uc->datasize -= count; + for (int i = 0; i < count; ++i) { + *(uc->dst - i) = (uint8_t)getBits(uc, 8); + } + uc->dst -= count; } static void copyReference(UnpackCtx *uc, int bitsCount, int count) { - const uint16_t offset = getBits(uc, bitsCount); - for (int i = 0; i < count; ++i) { - *uc->dst = *(uc->dst + offset); - --uc->dst; + uc->size -= count; + if (uc->size < 0) { + count += uc->size; + uc->size = 0; } - uc->datasize -= count; + const int offset = getBits(uc, bitsCount); + for (int i = 0; i < count; ++i) { + *(uc->dst - i) = *(uc->dst - i + offset); + } + uc->dst -= count; } -bool delphine_unpack(uint8_t *dst, const uint8_t *src, int len) { +bool delphine_unpack(uint8_t *dst, int dstSize, const uint8_t *src, int srcSize) { UnpackCtx uc; - uc.src = src + len - 4; - uc.datasize = READ_BE_UINT32(uc.src); uc.src -= 4; - uc.dst = dst + uc.datasize - 1; + uc.src = src + srcSize - 4; + uc.size = READ_BE_UINT32(uc.src); uc.src -= 4; + if (uc.size > dstSize) { + warning("Unexpected unpack size %d, buffer size %d", uc.size, dstSize); + return false; + } + uc.dst = dst + uc.size - 1; uc.crc = READ_BE_UINT32(uc.src); uc.src -= 4; uc.bits = READ_BE_UINT32(uc.src); uc.src -= 4; uc.crc ^= uc.bits; @@ -87,6 +100,7 @@ bool delphine_unpack(uint8_t *dst, const uint8_t *src, int len) { break; } } - } while (uc.datasize > 0); + } while (uc.size > 0); + assert(uc.size == 0); return uc.crc == 0; } diff --git a/unpack.h b/unpack.h index a5f050f..4f2a44d 100644 --- a/unpack.h +++ b/unpack.h @@ -9,6 +9,6 @@ #include "intern.h" -extern bool delphine_unpack(uint8_t *dst, const uint8_t *src, int len); +extern bool delphine_unpack(uint8_t *dst, int dstSize, const uint8_t *src, int srcSize); #endif // UNPACK_H__ diff --git a/video.cpp b/video.cpp index aae4b80..0363c3a 100644 --- a/video.cpp +++ b/video.cpp @@ -629,8 +629,9 @@ static void decodeLevHelper(uint8_t *dst, const uint8_t *src, int offset10, int void Video::AMIGA_decodeLev(int level, int room) { uint8_t *tmp = _res->_scratchBuffer; const int offset = READ_BE_UINT32(_res->_lev + room * 4); - if (!delphine_unpack(tmp, _res->_lev, offset)) { - error("Bad CRC for level %d room %d", level, room); + if (!delphine_unpack(tmp, Resource::kScratchBufferSize, _res->_lev, offset)) { + warning("Bad CRC for level %d room %d", level, room); + return; } uint16_t offset10 = READ_BE_UINT16(tmp + 10); const uint16_t offset12 = READ_BE_UINT16(tmp + 12); @@ -900,6 +901,9 @@ void Video::PC_drawStringChar(uint8_t *dst, int pitch, int x, int y, const uint8 } } +static uint8_t _MAC_fontFrontColor; +static uint8_t _MAC_fontShadowColor; + 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)); @@ -909,8 +913,8 @@ void Video::MAC_drawStringChar(uint8_t *dst, int pitch, int x, int y, const uint buf.x = x * _layerScale; buf.y = y * _layerScale; buf.setPixel = Video::MAC_drawBufferFont; - _charFrontColor = color; - buf.dataPtr = this; + _MAC_fontFrontColor = color; + _MAC_fontShadowColor = _charShadowColor; assert(chr >= 32); _res->MAC_decodeImageData(_res->_fnt, chr - 32, &buf); } @@ -1004,21 +1008,20 @@ void Video::MAC_drawBufferFont(DecodeBuffer *buf, int src_x, int src_y, int src_ 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; + buf->ptr[offset] = _MAC_fontShadowColor; break; case 0xC1: - buf->ptr[offset] = vid->_charFrontColor; + buf->ptr[offset] = _MAC_fontFrontColor; break; } } } } -void Video::MAC_fillRect(int x, int y, int w, int h, uint8_t color) { +void Video::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); diff --git a/video.h b/video.h index 8526e43..a58b32e 100644 --- a/video.h +++ b/video.h @@ -90,7 +90,7 @@ struct Video { 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); + void fillRect(int x, int y, int w, int h, uint8_t color); void MAC_drawSprite(int x, int y, const uint8_t *data, int frame, bool xflip, bool eraseBackground); };