From 1eab1a481f60dae141eda7305a1102090fda7fb3 Mon Sep 17 00:00:00 2001 From: Matthieu Milan Date: Sat, 25 Jan 2020 16:17:30 +0100 Subject: [PATCH] Import 0.2.7 changes --- CHANGES.txt | 7 +- Makefile | 4 +- README.txt | 8 +- defs.h | 2 +- fs.h | 2 +- fs_android.cpp | 8 +- fs_posix.cpp | 4 +- game.cpp | 137 ++++++++++++---------------- lzw.cpp | 51 +++++------ main.cpp | 19 ++-- mdec.cpp | 222 +++++++++++++++++++++++++++++++++++++++++++++ mdec.h | 24 +++++ mdec_coeffs.h | 235 ++++++++++++++++++++++++++++++++++++++++++++++++ menu.cpp | 190 +++++++++++++++++++++++++++------------ menu.h | 10 ++- resource.cpp | 112 +++++++++++++---------- resource.h | 4 +- screenshot.cpp | 86 +++++++++--------- screenshot.h | 4 +- sound.cpp | 4 +- system.h | 3 +- system_sdl2.cpp | 51 ++++++++--- video.cpp | 55 ++++++++++++ video.h | 7 ++ 24 files changed, 952 insertions(+), 297 deletions(-) create mode 100644 mdec.cpp create mode 100644 mdec.h create mode 100644 mdec_coeffs.h diff --git a/CHANGES.txt b/CHANGES.txt index a0fe7fd..b7ba19f 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,6 +1,11 @@ +* release 0.2.7 + - added 'projection' submenu + - added PSX backgrounds (MDEC) + - fixed menu on big endian platforms + * release 0.2.6 - added initial code for menu - - added initial code for PSX sounds (SPU ADPCM) + - added PSX sounds (SPU ADPCM) - fixed skull animation in 'rock' screen 18 * release 0.2.5 diff --git a/Makefile b/Makefile index bb1e33e..a2eb586 100644 --- a/Makefile +++ b/Makefile @@ -7,8 +7,8 @@ CPPFLAGS += -g -Wall -Wpedantic $(SDL_CFLAGS) $(DEFINES) -MMD SRCS = andy.cpp benchmark.cpp fileio.cpp fs_posix.cpp game.cpp \ level1_rock.cpp level2_fort.cpp level3_pwr1.cpp level4_isld.cpp \ level5_lava.cpp level6_pwr2.cpp level7_lar1.cpp level8_lar2.cpp level9_dark.cpp \ - lzw.cpp main.cpp menu.cpp mixer.cpp monsters.cpp paf.cpp random.cpp resource.cpp \ - screenshot.cpp sound.cpp staticres.cpp system_sdl2.cpp \ + lzw.cpp main.cpp mdec.cpp menu.cpp mixer.cpp monsters.cpp paf.cpp random.cpp \ + resource.cpp screenshot.cpp sound.cpp staticres.cpp system_sdl2.cpp \ util.cpp video.cpp SCALERS := scaler_nearest.cpp scaler_xbr.cpp diff --git a/README.txt b/README.txt index 873ac25..9f7528a 100644 --- a/README.txt +++ b/README.txt @@ -1,6 +1,6 @@ hode README -Release version: 0.2.6 +Release version: 0.2.7 ------------------------------------------------------------------------------- @@ -48,10 +48,10 @@ Game progress is saved in 'setup.cfg', similar to the original engine. Status: ------- -Settings and projection menus are not implemented. +Settings submenu is not implemented. -PSX version datafiles can be used but background screens and overlays (MDEC) -are not decoded yet. +PSX version datafiles can be used but background overlays (MDEC) are not +decoded and menu is unavailable. Credits: diff --git a/defs.h b/defs.h index fc71647..596f39c 100644 --- a/defs.h +++ b/defs.h @@ -66,7 +66,7 @@ struct SetupConfig { uint8_t currentPlayer; // 0xD1 uint8_t unkD2; uint8_t checksum; -} PACKED; // sizeof == 212 +}; // sizeof == 212 struct Point8_t { int8_t x; diff --git a/fs.h b/fs.h index 2b39d44..0bb0217 100644 --- a/fs.h +++ b/fs.h @@ -16,7 +16,7 @@ struct FileSystem { FILE *openAssetFile(const char *filename); FILE *openSaveFile(const char *filename, bool write); - void closeFile(FILE *); + int closeFile(FILE *); void addFilePath(const char *path); void listFiles(const char *dir); diff --git a/fs_android.cpp b/fs_android.cpp index c1e9a5d..1679c1a 100644 --- a/fs_android.cpp +++ b/fs_android.cpp @@ -114,8 +114,8 @@ FILE *FileSystem::openSaveFile(const char *filename, bool write) { return fp; } -void FileSystem::closeFile(FILE *fp) { - if (fp) { - fclose(fp); - } +int FileSystem::closeFile(FILE *fp) { + const int err = ferror(fp); + fclose(fp); + return err; } diff --git a/fs_posix.cpp b/fs_posix.cpp index 365b40d..ebc749c 100644 --- a/fs_posix.cpp +++ b/fs_posix.cpp @@ -59,8 +59,10 @@ FILE *FileSystem::openSaveFile(const char *filename, bool write) { return fopen(path, write ? "wb" : "rb"); } -void FileSystem::closeFile(FILE *fp) { +int FileSystem::closeFile(FILE *fp) { + const int err = ferror(fp); fclose(fp); + return err; } void FileSystem::addFilePath(const char *path) { diff --git a/game.cpp b/game.cpp index 19c26bc..af42f24 100644 --- a/game.cpp +++ b/game.cpp @@ -13,9 +13,6 @@ #include "util.h" #include "video.h" -// menu settings and player progress -static const char *_setupCfg = "setup.cfg"; - // starting level cutscene number static const uint8_t _cutscenes[] = { 0, 2, 4, 5, 6, 8, 10, 14, 19 }; @@ -319,7 +316,7 @@ void Game::setupBackgroundBitmap() { playSound(lvl->backgroundBitmapId, 0, 0, 3); } if (_res->_isPsx) { - assert(READ_LE_UINT16(pic + 4) == 0x3800); + _video->decodeBackgroundPsx(pic + 2); } else { decodeLZW(pic, _video->_backgroundLayer); } @@ -1310,13 +1307,14 @@ void Game::updateScreenHelper(int num) { if (_res->_isPsx) { p->framesCount = READ_LE_UINT32(data); data += 4; ptr->currentSound = READ_LE_UINT32(data); data += 4; + p->nextSpriteData = READ_LE_UINT16(data + 6) + data + 6; } else { p->framesCount = READ_LE_UINT16(data); data += 2; ptr->currentSound = READ_LE_UINT16(data); data += 2; + p->nextSpriteData = READ_LE_UINT16(data + 4) + data + 4; } p->currentSpriteData = p->otherSpriteData = data; p->currentFrame = 0; - p->nextSpriteData = READ_LE_UINT16(data + 4) + data + 4; } break; case 1: { @@ -2107,6 +2105,8 @@ void Game::mainLoop(int level, int checkpoint, bool levelChanged) { } _paf->_playedMask = _setupConfig.players[num].cutscenesMask; debug(kDebug_GAME, "Restart at level %d checkpoint %d cutscenes 0x%x", level, checkpoint, _paf->_playedMask); + // resume once, on the starting level + _resumeGame = false; } _video->_font = _res->_fontBuffer; assert(level < kLvl_test); @@ -2166,7 +2166,6 @@ void Game::mainLoop(int level, int checkpoint, bool levelChanged) { g_system->sleep(delay); } while (!_endLevel); _animBackgroundDataCount = 0; - saveSetupCfg(); callLevel_terminate(); } @@ -2248,36 +2247,42 @@ void Game::updateLvlObjectLists() { } LvlObject *Game::updateAnimatedLvlObjectType0(LvlObject *ptr) { + const bool isPsx = _res->_isPsx; + const int soundDataLen = isPsx ? sizeof(uint32_t) : sizeof(uint16_t); AnimBackgroundData *vg = (AnimBackgroundData *)getLvlObjectDataPtr(ptr, kObjectDataTypeAnimBackgroundData); - const uint8_t *vf = vg->currentSpriteData + 2; + const uint8_t *data = vg->currentSpriteData + soundDataLen; if (_res->_currentScreenResourceNum == ptr->screenNum) { if (ptr->currentSound != 0xFFFF) { playSound(ptr->currentSound, ptr, 0, 3); ptr->currentSound = 0xFFFF; } - Sprite *spr = _spritesNextPtr; - if (spr && READ_LE_UINT16(vf + 2) > 8) { - spr->xPos = vf[0]; - spr->yPos = vf[1]; - spr->w = READ_LE_UINT16(vf + 4); - spr->h = READ_LE_UINT16(vf + 6); - spr->bitmapBits = vf + 8; - spr->num = ptr->flags2; - const int index = spr->num & 0x1F; - _spritesNextPtr = spr->nextPtr; - spr->nextPtr = _typeSpritesList[index]; - _typeSpritesList[index] = spr; + if (isPsx) { + _video->decodeTilePsx(data); + } else { + Sprite *spr = _spritesNextPtr; + if (spr && READ_LE_UINT16(data + 2) > 8) { + spr->xPos = data[0]; + spr->yPos = data[1]; + spr->w = READ_LE_UINT16(data + 4); + spr->h = READ_LE_UINT16(data + 6); + spr->bitmapBits = data + 8; + spr->num = ptr->flags2; + const int index = spr->num & 0x1F; + _spritesNextPtr = spr->nextPtr; + spr->nextPtr = _typeSpritesList[index]; + _typeSpritesList[index] = spr; + } } } int16_t soundNum = -1; - const int len = READ_LE_UINT16(vf + 2); - const uint8_t *nextSpriteData = len + vf + 2; + const int len = READ_LE_UINT16(data + 2); + const uint8_t *nextSpriteData = len + data + 2; switch (ptr->objectUpdateType - 1) { case 6: vg->currentSpriteData = vg->nextSpriteData; if (vg->currentFrame == 0) { vg->currentFrame = 1; - soundNum = READ_LE_UINT16(vg->nextSpriteData); + soundNum = isPsx ? READ_LE_UINT32(vg->nextSpriteData) : READ_LE_UINT16(vg->nextSpriteData); } ptr->objectUpdateType = 4; break; @@ -2290,48 +2295,50 @@ LvlObject *Game::updateAnimatedLvlObjectType0(LvlObject *ptr) { ++vg->currentFrame; if (vg->currentFrame < vg->framesCount) { vg->currentSpriteData = nextSpriteData; - soundNum = READ_LE_UINT16(nextSpriteData); } else { vg->currentFrame = 0; vg->currentSpriteData = vg->otherSpriteData; ptr->objectUpdateType = 1; - soundNum = READ_LE_UINT16(vg->currentSpriteData); } + soundNum = isPsx ? READ_LE_UINT32(vg->currentSpriteData) : READ_LE_UINT16(vg->currentSpriteData); break; case 4: ++vg->currentFrame; if (vg->currentFrame < vg->framesCount) { // bugfix: original uses '<=' (out of bounds) vg->currentSpriteData = nextSpriteData; - soundNum = READ_LE_UINT16(nextSpriteData); } else { vg->currentFrame = 0; vg->currentSpriteData = vg->otherSpriteData; ptr->objectUpdateType = 1; - soundNum = READ_LE_UINT16(vg->currentSpriteData); } + soundNum = isPsx ? READ_LE_UINT32(vg->currentSpriteData) : READ_LE_UINT16(vg->currentSpriteData); break; case 2: while (vg->currentFrame < vg->framesCount - 2) { ++vg->currentFrame; vg->currentSpriteData = nextSpriteData; - nextSpriteData += 2; + nextSpriteData += soundDataLen; const int len = READ_LE_UINT16(nextSpriteData + 2); nextSpriteData += len + 2; } - nextSpriteData = vg->currentSpriteData + 2; + data = vg->currentSpriteData + soundDataLen; if (_res->_currentScreenResourceNum == ptr->screenNum) { - Sprite *spr = _spritesNextPtr; - if (spr && READ_LE_UINT16(nextSpriteData + 2) > 8) { - spr->w = READ_LE_UINT16(nextSpriteData + 4); - spr->h = READ_LE_UINT16(nextSpriteData + 6); - spr->bitmapBits = nextSpriteData + 8; - spr->xPos = nextSpriteData[0]; - spr->yPos = nextSpriteData[1]; - _spritesNextPtr = spr->nextPtr; - spr->num = ptr->flags2; - const int index = spr->num & 0x1F; - spr->nextPtr = _typeSpritesList[index]; - _typeSpritesList[index] = spr; + if (isPsx) { + _video->decodeTilePsx(data); + } else { + Sprite *spr = _spritesNextPtr; + if (spr && READ_LE_UINT16(data + 2) > 8) { + spr->w = READ_LE_UINT16(data + 4); + spr->h = READ_LE_UINT16(data + 6); + spr->bitmapBits = data + 8; + spr->xPos = data[0]; + spr->yPos = data[1]; + _spritesNextPtr = spr->nextPtr; + spr->num = ptr->flags2; + const int index = spr->num & 0x1F; + spr->nextPtr = _typeSpritesList[index]; + _typeSpritesList[index] = spr; + } } } ptr->objectUpdateType = 1; @@ -2340,7 +2347,7 @@ LvlObject *Game::updateAnimatedLvlObjectType0(LvlObject *ptr) { ++vg->currentFrame; if (vg->currentFrame < vg->framesCount - 1) { vg->currentSpriteData = nextSpriteData; - soundNum = READ_LE_UINT16(vg->currentSpriteData); + soundNum = isPsx ? READ_LE_UINT32(vg->currentSpriteData) : READ_LE_UINT16(vg->currentSpriteData); } else { if (vg->currentFrame > vg->framesCount) { vg->currentFrame = vg->framesCount; @@ -2352,7 +2359,7 @@ LvlObject *Game::updateAnimatedLvlObjectType0(LvlObject *ptr) { case 0: return ptr->nextPtr; default: - soundNum = READ_LE_UINT16(vg->currentSpriteData); + soundNum = isPsx ? READ_LE_UINT32(vg->currentSpriteData) : READ_LE_UINT16(vg->currentSpriteData); if (ptr->hitCount == 0) { ++vg->currentFrame; if (vg->currentFrame >= vg->framesCount) { @@ -2490,9 +2497,6 @@ LvlObject *Game::updateAnimatedLvlObjectTypeDefault(LvlObject *ptr) { LvlObject *Game::updateAnimatedLvlObject(LvlObject *o) { switch (o->type) { case 0: - if (_res->_isPsx) { - return o->nextPtr; - } o = updateAnimatedLvlObjectType0(o); break; case 1: @@ -4787,10 +4791,7 @@ void Game::updateWormHoleSprites() { bool Game::loadSetupCfg(bool resume) { _resumeGame = resume; - FILE *fp = _fs.openSaveFile(_setupCfg, false); - if (fp) { - _res->readSetupCfg(fp, &_setupConfig); - _fs.closeFile(fp); + if (_res->readSetupCfg(&_setupConfig)) { return true; } memset(&_setupConfig, 0, sizeof(_setupConfig)); @@ -4798,10 +4799,6 @@ bool Game::loadSetupCfg(bool resume) { } void Game::saveSetupCfg() { - if (!_resumeGame) { - // do not save progress when game is started from a specific level/checkpoint - return; - } const int num = _setupConfig.currentPlayer; if (_currentLevelCheckpoint > _setupConfig.players[num].progress[_currentLevel]) { _setupConfig.players[num].progress[_currentLevel] = _currentLevelCheckpoint; @@ -4814,39 +4811,19 @@ void Game::saveSetupCfg() { if (_currentLevel > _setupConfig.players[num].currentLevel) { _setupConfig.players[num].currentLevel = _currentLevel; } - FILE *fp = _fs.openSaveFile(_setupCfg, true); - if (fp) { - _res->writeSetupCfg(fp, &_setupConfig); - _fs.closeFile(fp); - } else { - warning("Failed to save '%s'", _setupCfg); - } + _res->writeSetupCfg(&_setupConfig); } void Game::captureScreenshot() { static int screenshot = 1; - char name[64]; - - snprintf(name, sizeof(name), "screenshot-%03d-front.bmp", screenshot); - saveBMP(name, _video->_frontLayer, _video->_palette, Video::W, Video::H); - - snprintf(name, sizeof(name), "screenshot-%03d-background.bmp", screenshot); - saveBMP(name, _video->_backgroundLayer, _video->_palette, Video::W, Video::H); - snprintf(name, sizeof(name), "screenshot-%03d-shadow.bmp", screenshot); - saveBMP(name, _video->_shadowLayer, _video->_palette, Video::W, Video::H); - - static const int kPaletteRectSize = 8; - uint8_t paletteBuffer[8 * 256 * 8]; - for (int x = 0; x < 256; ++x) { - const int xOffset = x * kPaletteRectSize; - for (int y = 0; y < kPaletteRectSize; ++y) { - memset(paletteBuffer + xOffset + y * 256 * kPaletteRectSize, x, kPaletteRectSize); - } + char name[64]; + snprintf(name, sizeof(name), "screenshot-%03d.bmp", screenshot); + FILE *fp = _fs.openSaveFile(name, true); + if (fp) { + saveBMP(fp, _video->_frontLayer, _video->_palette, Video::W, Video::H); + fclose(fp); } - snprintf(name, sizeof(name), "screenshot-%03d-palette.bmp", screenshot); - saveBMP(name, paletteBuffer, _video->_palette, 256 * kPaletteRectSize, kPaletteRectSize); - ++screenshot; } diff --git a/lzw.cpp b/lzw.cpp index df1737f..676c103 100644 --- a/lzw.cpp +++ b/lzw.cpp @@ -10,55 +10,52 @@ enum { kClearCode = 1 << (kCodeWidth - 1), kEndCode = kClearCode + 1, kNewCodes = kEndCode + 1, + kStackSize = 8192, kMaxBits = 12 }; struct LzwDecoder { - uint16_t _prefix[4096]; - uint8_t _stack[8192]; + uint16_t _prefix[1 << kMaxBits]; + uint8_t _stack[kStackSize]; const uint8_t *_buf; - uint8_t _currentBits; - uint8_t _codeSize; + uint32_t _currentBits; uint8_t _bitsLeft; - uint32_t nextCode(); + uint32_t nextCode(int codeSize); int decode(uint8_t *dst); }; static struct LzwDecoder _lzw; -uint32_t LzwDecoder::nextCode() { - if (_bitsLeft == 0) { - _currentBits = *_buf++; - _bitsLeft = 8; - } - uint32_t code = _currentBits >> (8 - _bitsLeft); - while (_bitsLeft < _codeSize) { - _currentBits = *_buf++; - code |= _currentBits << _bitsLeft; +uint32_t LzwDecoder::nextCode(int codeSize) { // 9 to 12bits + _currentBits |= (*_buf++) << _bitsLeft; + _bitsLeft += 8; + if (_bitsLeft < codeSize) { + _currentBits |= (*_buf++) << _bitsLeft; _bitsLeft += 8; } - code &= (1 << _codeSize) - 1; - _bitsLeft -= _codeSize; + const uint32_t code = _currentBits & ((1 << codeSize) - 1); + _currentBits >>= codeSize; + _bitsLeft -= codeSize; return code; } int LzwDecoder::decode(uint8_t *dst) { uint8_t *p = dst; - uint32_t topSlot = 1 << kCodeWidth; - uint8_t *stackPtr = &_stack[8191]; - uint32_t currentSlot = kNewCodes; + uint8_t *stackPtr = &_stack[kStackSize - 1]; uint32_t previousCode = 0; uint32_t lastCode = 0; uint32_t currentCode; - _codeSize = kCodeWidth; - while ((currentCode = nextCode()) != kEndCode) { + uint32_t currentSlot = kNewCodes; + uint32_t topSlot = 1 << kCodeWidth; + int codeSize = kCodeWidth; + while ((currentCode = nextCode(codeSize)) != kEndCode) { if (currentCode == kClearCode) { currentSlot = kNewCodes; - _codeSize = kCodeWidth; topSlot = 1 << kCodeWidth; - while ((currentCode = nextCode()) == kClearCode) { + codeSize = kCodeWidth; + while ((currentCode = nextCode(codeSize)) == kClearCode) { } if (currentCode == kEndCode) { break; @@ -73,8 +70,8 @@ int LzwDecoder::decode(uint8_t *dst) { uint32_t code = currentCode; if (currentCode >= slot) { code = lastCode; - currentStackPtr = &_stack[8190]; - _stack[8190] = (uint8_t)previousCode; + currentStackPtr = &_stack[kStackSize - 2]; + *currentStackPtr = (uint8_t)previousCode; } while (code >= kNewCodes) { --currentStackPtr; @@ -92,9 +89,9 @@ int LzwDecoder::decode(uint8_t *dst) { ++slot; currentSlot = slot; } - if (slot >= topSlot && _codeSize < kMaxBits) { + if (slot >= topSlot && codeSize < kMaxBits) { topSlot <<= 1; - ++_codeSize; + ++codeSize; } while (currentStackPtr < stackPtr) { *p++ = *currentStackPtr++; diff --git a/main.cpp b/main.cpp index 02b485a..6cc32db 100644 --- a/main.cpp +++ b/main.cpp @@ -35,7 +35,7 @@ static bool _fullscreen = false; static bool _widescreen = false; static const bool _runBenchmark = false; -static bool _runMenu = false; +static bool _runMenu = true; static void lockAudio(int flag) { if (flag) { @@ -60,7 +60,7 @@ static void setupAudio(Game *g) { static const char *_defaultDataPath = "."; -static const char* _defaultSavePath = "."; +static const char *_defaultSavePath = "."; static const char *_levelNames[] = { "rock", @@ -188,20 +188,25 @@ int main(int argc, char *argv[]) { } Game *g = new Game(dataPath ? dataPath : _defaultDataPath, savePath ? savePath : _defaultSavePath, cheats); ini_parse(_configIni, handleConfigIni, g); - setupAudio(g); - g_system->init(_title, Video::W, Video::H, _fullscreen, _widescreen); if (_runBenchmark) { g->benchmarkCpu(); } + // load setup.dat and detects if these are PC or PSX datafiles g->_res->loadSetupDat(); + const bool isPsx = g->_res->_isPsx; + g_system->init(_title, Video::W, Video::H, _fullscreen, _widescreen, isPsx); + setupAudio(g); g->loadSetupCfg(resume); bool runGame = true; - if (_runMenu && resume && !g->_res->_isPsx) { + if (_runMenu && resume && !isPsx) { Menu *m = new Menu(g, g->_paf, g->_res, g->_video); runGame = m->mainLoop(); delete m; } if (runGame) { + if (isPsx) { + g->_video->initPsx(); + } bool levelChanged = false; do { g->mainLoop(level, checkpoint, levelChanged); @@ -209,6 +214,10 @@ int main(int argc, char *argv[]) { checkpoint = 0; levelChanged = true; } while (!g_system->inp.quit && level < kLvl_test); + // do not save progress when game is started from a specific level/checkpoint + if (resume) { + g->saveSetupCfg(); + } } g_system->stopAudio(); g->_mix.fini(); diff --git a/mdec.cpp b/mdec.cpp new file mode 100644 index 0000000..fea5c75 --- /dev/null +++ b/mdec.cpp @@ -0,0 +1,222 @@ + +#include +#include "intern.h" +#include "mdec.h" +#include "mdec_coeffs.h" + +struct BitStream { // most significant 16 bits + const uint8_t *_src; + uint32_t _bits; + int _len; + const uint8_t *_end; + + BitStream(const uint8_t *src, int size) + : _src(src), _bits(0), _len(0), _end(src + size) { + } + + bool endOfStream() const { + return _src >= _end && _len == 0; + } + + int getBits(int count) { // 6 to 16 bits + if (_len < count) { + _bits <<= 16; + assert(_src < _end); + _bits |= READ_LE_UINT16(_src); _src += 2; + _len += 16; + } + assert(_len >= count); + const int value = (_bits >> (_len - count)) & ((1 << count) - 1); + _len -= count; + return value; + } + int getSignedBits(int len) { + const int shift = 32 - len; + int32_t value = getBits(len); + return (value << shift) >> shift; + } + bool getBit() { + if (_len == 0) { + assert(_src < _end); + _bits = READ_LE_UINT16(_src); _src += 2; + _len = 16; + } + --_len; + return (_bits & (1 << _len)) != 0; + } +}; + +static int readDC(BitStream *bs, int version) { + assert(version == 2); + return bs->getSignedBits(10); +} + +static void readAC(BitStream *bs, int *coefficients) { + int count = 0; + int node = 0; + while (!bs->endOfStream()) { + const uint16_t value = _acHuffTree[node].value; + switch (value) { + case kAcHuff_EscapeCode: { + const int zeroes = bs->getBits(6); + count += zeroes + 1; + assert(count < 63); + coefficients += zeroes; + *coefficients++ = bs->getSignedBits(10); + } + break; + case kAcHuff_EndOfBlock: + return; + case 0: + if (bs->getBit()) { + node = _acHuffTree[node].right; + } else { + node = _acHuffTree[node].left; + } + continue; + default: { + const int zeroes = value >> 8; + count += zeroes + 1; + assert(count < 63); + coefficients += zeroes; + const int nonZeroes = value & 255; + *coefficients++ = bs->getBit() ? -nonZeroes : nonZeroes; + } + break; + } + node = 0; // root + } +} + +static const uint8_t _zigZagTable[8 * 8] = { + 0, 1, 5, 6, 14, 15, 27, 28, + 2, 4, 7, 13, 16, 26, 29, 42, + 3, 8, 12, 17, 25, 30, 41, 43, + 9, 11, 18, 24, 31, 40, 44, 53, + 10, 19, 23, 32, 39, 45, 52, 54, + 20, 22, 33, 38, 46, 51, 55, 60, + 21, 34, 37, 47, 50, 56, 59, 61, + 35, 36, 48, 49, 57, 58, 62, 63 +}; + +static const uint8_t _quantizationTable[8 * 8] = { + 2, 16, 19, 22, 26, 27, 29, 34, + 16, 16, 22, 24, 27, 29, 34, 37, + 19, 22, 26, 27, 29, 34, 34, 38, + 22, 22, 26, 27, 29, 34, 37, 40, + 22, 26, 27, 29, 32, 35, 40, 48, + 26, 27, 29, 32, 35, 40, 48, 58, + 26, 27, 29, 34, 38, 46, 56, 69, + 27, 29, 35, 38, 46, 56, 69, 83 +}; + +static void dequantizeBlock(int *coefficients, float *block, int scale) { + block[0] = coefficients[0] * _quantizationTable[0]; // DC + for (int i = 1; i < 8 * 8; i++) { + block[i] = coefficients[_zigZagTable[i]] * _quantizationTable[i] * scale / 8.f; + } +} + +static const double _idct8x8[8][8] = { + { 0.353553390593274, 0.490392640201615, 0.461939766255643, 0.415734806151273, 0.353553390593274, 0.277785116509801, 0.191341716182545, 0.097545161008064 }, + { 0.353553390593274, 0.415734806151273, 0.191341716182545, -0.097545161008064, -0.353553390593274, -0.490392640201615, -0.461939766255643, -0.277785116509801 }, + { 0.353553390593274, 0.277785116509801, -0.191341716182545, -0.490392640201615, -0.353553390593274, 0.097545161008064, 0.461939766255643, 0.415734806151273 }, + { 0.353553390593274, 0.097545161008064, -0.461939766255643, -0.277785116509801, 0.353553390593274, 0.415734806151273, -0.191341716182545, -0.490392640201615 }, + { 0.353553390593274, -0.097545161008064, -0.461939766255643, 0.277785116509801, 0.353553390593274, -0.415734806151273, -0.191341716182545, 0.490392640201615 }, + { 0.353553390593274, -0.277785116509801, -0.191341716182545, 0.490392640201615, -0.353553390593273, -0.097545161008064, 0.461939766255643, -0.415734806151273 }, + { 0.353553390593274, -0.415734806151273, 0.191341716182545, 0.097545161008064, -0.353553390593274, 0.490392640201615, -0.461939766255643, 0.277785116509801 }, + { 0.353553390593274, -0.490392640201615, 0.461939766255643, -0.415734806151273, 0.353553390593273, -0.277785116509801, 0.191341716182545, -0.097545161008064 } +}; + +static void idct(float *dequantData, float *result) { + float tmp[8 * 8]; + // 1D IDCT rows + for (int y = 0; y < 8; y++) { + for (int x = 0; x < 8; x++) { + float p = 0; + for (int i = 0; i < 8; ++i) { + p += dequantData[i] * _idct8x8[x][i]; + } + tmp[y + x * 8] = p; + } + dequantData += 8; + } + // 1D IDCT columns + for (int x = 0; x < 8; x++) { + const float *u = tmp + x * 8; + for (int y = 0; y < 8; y++) { + float p = 0; + for (int i = 0; i < 8; ++i) { + p += u[i] * _idct8x8[y][i]; + } + result[y * 8 + x] = p; + } + } +} + +static void decodeBlock(BitStream *bs, int x8, int y8, uint8_t *dst, int dstPitch, int scale, int version) { + int coefficients[8 * 8]; + memset(coefficients, 0, sizeof(coefficients)); + coefficients[0] = readDC(bs, version); + readAC(bs, &coefficients[1]); + + float dequantData[8 * 8]; + dequantizeBlock(coefficients, dequantData, scale); + + float idctData[8 * 8]; + idct(dequantData, idctData); + + dst += (y8 * dstPitch + x8) * 8; + for (int y = 0; y < 8; y++) { + for (int x = 0; x < 8; x++) { + const int val = (int)round(idctData[y * 8 + x]); // (-128,127) range + dst[x] = (val < -128) ? 0 : ((val > 127) ? 255 : (128 + val)); + } + dst += dstPitch; + } +} + +int decodeMDEC(const uint8_t *src, int len, const uint8_t *mborder, int w, int h, MdecOutput *out) { + BitStream bs(src, len); + bs.getBits(16); + const uint16_t vlc = bs.getBits(16); + assert(vlc == 0x3800); + const uint16_t qscale = bs.getBits(16); + const uint16_t version = bs.getBits(16); + // fprintf(stdout, "mdec qscale %d version %d\n", qscale, version); + assert(version == 2); + + const int blockW = (w + 15) / 16; + const int blockH = (h + 15) / 16; + + const int yPitch = out->planes[kOutputPlaneY].pitch; + uint8_t *yPtr = out->planes[kOutputPlaneY].ptr + out->y * yPitch + out->x; + const int cbPitch = out->planes[kOutputPlaneCb].pitch; + uint8_t *cbPtr = out->planes[kOutputPlaneCb].ptr + out->y / 2 * cbPitch + out->x / 2; + const int crPitch = out->planes[kOutputPlaneCr].pitch; + uint8_t *crPtr = out->planes[kOutputPlaneCr].ptr + out->y / 2 * crPitch + out->x / 2; + + for (int x = 0; x < blockW; ++x) { + for (int y = 0; y < blockH; ++y) { + if (mborder) { + const uint8_t xy = *mborder++; + if ((xy & 15) != x || (xy >> 4) != y) { + continue; + } + } + decodeBlock(&bs, x, y, crPtr, crPitch, qscale, version); + decodeBlock(&bs, x, y, cbPtr, cbPitch, qscale, version); + decodeBlock(&bs, 2 * x, 2 * y, yPtr, yPitch, qscale, version); + decodeBlock(&bs, 2 * x + 1, 2 * y, yPtr, yPitch, qscale, version); + decodeBlock(&bs, 2 * x, 2 * y + 1, yPtr, yPitch, qscale, version); + decodeBlock(&bs, 2 * x + 1, 2 * y + 1, yPtr, yPitch, qscale, version); + } + } + + if (!mborder) { + const int eof = bs.getBits(11); + assert(eof == 0x3FE); // v2 frame + } + + return bs._src - src; +} diff --git a/mdec.h b/mdec.h new file mode 100644 index 0000000..57a6f4d --- /dev/null +++ b/mdec.h @@ -0,0 +1,24 @@ + +#ifndef MDEC_H__ +#define MDEC_H__ + +#include + +enum { + kOutputPlaneY = 0, + kOutputPlaneCb = 1, + kOutputPlaneCr = 2 +}; + +struct MdecOutput { + int x, y; + int w, h; + struct { + uint8_t *ptr; + int pitch; + } planes[3]; +}; + +int decodeMDEC(const uint8_t *src, int len, const uint8_t *mborder, int w, int h, MdecOutput *out); + +#endif // MDEC_H__ diff --git a/mdec_coeffs.h b/mdec_coeffs.h new file mode 100644 index 0000000..d08658e --- /dev/null +++ b/mdec_coeffs.h @@ -0,0 +1,235 @@ +static const uint16_t kAcHuff_EscapeCode = 0xfffe; +static const uint16_t kAcHuff_EndOfBlock = 0xffff; +struct AcHuff { + int16_t left; + int16_t right; + uint16_t value; +}; +static const AcHuff _acHuffTree[] = { + { 3, 1, 0 }, + { 225, 2, 0 }, + { -1, -1, 0x0001 }, + { 9, 4, 0 }, + { 6, 5, 0 }, + { -1, -1, 0x0101 }, + { 7, 8, 0 }, + { -1, -1, 0x0002 }, + { -1, -1, 0x0201 }, + { 16, 10, 0 }, + { 11, 13, 0 }, + { 32, 12, 0 }, + { -1, -1, 0x0003 }, + { 14, 15, 0 }, + { -1, -1, 0x0401 }, + { -1, -1, 0x0301 }, + { 24, 17, 0 }, + { 18, 21, 0 }, + { 19, 20, 0 }, + { -1, -1, 0x0701 }, + { -1, -1, 0x0601 }, + { 22, 23, 0 }, + { -1, -1, 0x0102 }, + { -1, -1, 0x0501 }, + { 47, 25, 0 }, + { 26, 29, 0 }, + { 27, 28, 0 }, + { -1, -1, 0x0202 }, + { -1, -1, 0x0901 }, + { 30, 31, 0 }, + { -1, -1, 0x0004 }, + { -1, -1, 0x0801 }, + { 33, 40, 0 }, + { 34, 37, 0 }, + { 35, 36, 0 }, + { -1, -1, 0x0d01 }, + { -1, -1, 0x0006 }, + { 38, 39, 0 }, + { -1, -1, 0x0c01 }, + { -1, -1, 0x0b01 }, + { 41, 44, 0 }, + { 42, 43, 0 }, + { -1, -1, 0x0302 }, + { -1, -1, 0x0103 }, + { 45, 46, 0 }, + { -1, -1, 0x0005 }, + { -1, -1, 0x0a01 }, + { 48, 224, 0 }, + { 64, 49, 0 }, + { 50, 57, 0 }, + { 51, 54, 0 }, + { 52, 53, 0 }, + { -1, -1, 0x1001 }, + { -1, -1, 0x0502 }, + { 55, 56, 0 }, + { -1, -1, 0x0007 }, + { -1, -1, 0x0203 }, + { 58, 61, 0 }, + { 59, 60, 0 }, + { -1, -1, 0x0104 }, + { -1, -1, 0x0f01 }, + { 62, 63, 0 }, + { -1, -1, 0x0e01 }, + { -1, -1, 0x0402 }, + { 96, 65, 0 }, + { 66, 81, 0 }, + { 67, 74, 0 }, + { 68, 71, 0 }, + { 69, 70, 0 }, + { -1, -1, 0x000b }, + { -1, -1, 0x0802 }, + { 72, 73, 0 }, + { -1, -1, 0x0403 }, + { -1, -1, 0x000a }, + { 75, 78, 0 }, + { 76, 77, 0 }, + { -1, -1, 0x0204 }, + { -1, -1, 0x0702 }, + { 79, 80, 0 }, + { -1, -1, 0x1501 }, + { -1, -1, 0x1401 }, + { 82, 89, 0 }, + { 83, 86, 0 }, + { 84, 85, 0 }, + { -1, -1, 0x0009 }, + { -1, -1, 0x1301 }, + { 87, 88, 0 }, + { -1, -1, 0x1201 }, + { -1, -1, 0x0105 }, + { 90, 93, 0 }, + { 91, 92, 0 }, + { -1, -1, 0x0303 }, + { -1, -1, 0x0008 }, + { 94, 95, 0 }, + { -1, -1, 0x0602 }, + { -1, -1, 0x1101 }, + { 128, 97, 0 }, + { 98, 113, 0 }, + { 99, 106, 0 }, + { 100, 103, 0 }, + { 101, 102, 0 }, + { -1, -1, 0x0a02 }, + { -1, -1, 0x0902 }, + { 104, 105, 0 }, + { -1, -1, 0x0503 }, + { -1, -1, 0x0304 }, + { 107, 110, 0 }, + { 108, 109, 0 }, + { -1, -1, 0x0205 }, + { -1, -1, 0x0107 }, + { 111, 112, 0 }, + { -1, -1, 0x0106 }, + { -1, -1, 0x000f }, + { 114, 121, 0 }, + { 115, 118, 0 }, + { 116, 117, 0 }, + { -1, -1, 0x000e }, + { -1, -1, 0x000d }, + { 119, 120, 0 }, + { -1, -1, 0x000c }, + { -1, -1, 0x1a01 }, + { 122, 125, 0 }, + { 123, 124, 0 }, + { -1, -1, 0x1901 }, + { -1, -1, 0x1801 }, + { 126, 127, 0 }, + { -1, -1, 0x1701 }, + { -1, -1, 0x1601 }, + { 160, 129, 0 }, + { 130, 145, 0 }, + { 131, 138, 0 }, + { 132, 135, 0 }, + { 133, 134, 0 }, + { -1, -1, 0x001f }, + { -1, -1, 0x001e }, + { 136, 137, 0 }, + { -1, -1, 0x001d }, + { -1, -1, 0x001c }, + { 139, 142, 0 }, + { 140, 141, 0 }, + { -1, -1, 0x001b }, + { -1, -1, 0x001a }, + { 143, 144, 0 }, + { -1, -1, 0x0019 }, + { -1, -1, 0x0018 }, + { 146, 153, 0 }, + { 147, 150, 0 }, + { 148, 149, 0 }, + { -1, -1, 0x0017 }, + { -1, -1, 0x0016 }, + { 151, 152, 0 }, + { -1, -1, 0x0015 }, + { -1, -1, 0x0014 }, + { 154, 157, 0 }, + { 155, 156, 0 }, + { -1, -1, 0x0013 }, + { -1, -1, 0x0012 }, + { 158, 159, 0 }, + { -1, -1, 0x0011 }, + { -1, -1, 0x0010 }, + { 192, 161, 0 }, + { 162, 177, 0 }, + { 163, 170, 0 }, + { 164, 167, 0 }, + { 165, 166, 0 }, + { -1, -1, 0x0028 }, + { -1, -1, 0x0027 }, + { 168, 169, 0 }, + { -1, -1, 0x0026 }, + { -1, -1, 0x0025 }, + { 171, 174, 0 }, + { 172, 173, 0 }, + { -1, -1, 0x0024 }, + { -1, -1, 0x0023 }, + { 175, 176, 0 }, + { -1, -1, 0x0022 }, + { -1, -1, 0x0021 }, + { 178, 185, 0 }, + { 179, 182, 0 }, + { 180, 181, 0 }, + { -1, -1, 0x0020 }, + { -1, -1, 0x010e }, + { 183, 184, 0 }, + { -1, -1, 0x010d }, + { -1, -1, 0x010c }, + { 186, 189, 0 }, + { 187, 188, 0 }, + { -1, -1, 0x010b }, + { -1, -1, 0x010a }, + { 190, 191, 0 }, + { -1, -1, 0x0109 }, + { -1, -1, 0x0108 }, + { -1, 193, 0 }, + { 194, 209, 0 }, + { 195, 202, 0 }, + { 196, 199, 0 }, + { 197, 198, 0 }, + { -1, -1, 0x0112 }, + { -1, -1, 0x0111 }, + { 200, 201, 0 }, + { -1, -1, 0x0110 }, + { -1, -1, 0x010f }, + { 203, 206, 0 }, + { 204, 205, 0 }, + { -1, -1, 0x0603 }, + { -1, -1, 0x1002 }, + { 207, 208, 0 }, + { -1, -1, 0x0f02 }, + { -1, -1, 0x0e02 }, + { 210, 217, 0 }, + { 211, 214, 0 }, + { 212, 213, 0 }, + { -1, -1, 0x0d02 }, + { -1, -1, 0x0c02 }, + { 215, 216, 0 }, + { -1, -1, 0x0b02 }, + { -1, -1, 0x1f01 }, + { 218, 221, 0 }, + { 219, 220, 0 }, + { -1, -1, 0x1e01 }, + { -1, -1, 0x1d01 }, + { 222, 223, 0 }, + { -1, -1, 0x1c01 }, + { -1, -1, 0x1b01 }, + { -1, -1, 0xfffe }, + { -1, -1, 0xffff }, +}; diff --git a/menu.cpp b/menu.cpp index 351ced5..367d63a 100644 --- a/menu.cpp +++ b/menu.cpp @@ -9,7 +9,7 @@ #include "video.h" enum { - kTitleScreen_AssignPlayer, + kTitleScreen_AssignPlayer = 0, kTitleScreen_Play, kTitleScreen_Options, kTitleScreen_Quit @@ -89,12 +89,14 @@ void Menu::loadData() { if (version == 10) { _titleSprites = (DatSpritesGroup *)(ptr + ptrOffset); - _titleSprites->size = le16toh(_titleSprites->size); + _titleSprites->size = le32toh(_titleSprites->size); + _titleSprites->count = le16toh(_titleSprites->count); ptrOffset += sizeof(DatSpritesGroup) + _titleSprites->size; _titleSprites->firstFrameOffset = 0; _playerSprites = (DatSpritesGroup *)(ptr + ptrOffset); - _playerSprites->size = le16toh(_playerSprites->size); + _playerSprites->size = le32toh(_playerSprites->size); + _playerSprites->count = le16toh(_playerSprites->count); ptrOffset += sizeof(DatSpritesGroup) + _playerSprites->size; _playerSprites->firstFrameOffset = 0; @@ -115,9 +117,7 @@ void Menu::loadData() { _cutscenesBitmaps = (DatBitmapsGroup *)(ptr + ptrOffset); ptrOffset += cutscenesCount * sizeof(DatBitmapsGroup); _cutscenesBitmapsData = ptr + ptrOffset; - for (int i = 0; i < cutscenesCount; ++i) { - ptrOffset += _cutscenesBitmaps[i].w * _cutscenesBitmaps[i].h + 768; - } + ptrOffset += readBitmapsGroup(cutscenesCount, _cutscenesBitmaps, ptrOffset); for (int i = 0; i < kCheckpointLevelsCount; ++i) { DatBitmapsGroup *bitmapsGroup = (DatBitmapsGroup *)(ptr + ptrOffset); @@ -167,10 +167,8 @@ void Menu::loadData() { const int cutscenesCount = _res->_datHdr.cutscenesCount; _cutscenesBitmaps = (DatBitmapsGroup *)(ptr + hdrOffset); _cutscenesBitmapsData = ptr + ptrOffset; - for (int i = 0; i < cutscenesCount; ++i) { - hdrOffset += sizeof(DatBitmapsGroup); - ptrOffset += _cutscenesBitmaps[i].w * _cutscenesBitmaps[i].h + 768; - } + hdrOffset += cutscenesCount * sizeof(DatBitmapsGroup); + ptrOffset += readBitmapsGroup(cutscenesCount, _cutscenesBitmaps, ptrOffset); for (int i = 0; i < kCheckpointLevelsCount; ++i) { DatBitmapsGroup *bitmapsGroup = (DatBitmapsGroup *)(ptr + hdrOffset); @@ -193,13 +191,15 @@ void Menu::loadData() { if (version == 11) { _titleSprites = (DatSpritesGroup *)(ptr + ptrOffset); - _titleSprites->size = le16toh(_titleSprites->size); + _titleSprites->size = le32toh(_titleSprites->size); ptrOffset += sizeof(DatSpritesGroup) + _titleSprites->size; + _titleSprites->count = le16toh(_titleSprites->count); _titleSprites->firstFrameOffset = 0; _playerSprites = (DatSpritesGroup *)(ptr + ptrOffset); - _playerSprites->size = le16toh(_playerSprites->size); + _playerSprites->size = le32toh(_playerSprites->size); ptrOffset += sizeof(DatSpritesGroup) + _playerSprites->size; + _playerSprites->count = le16toh(_playerSprites->count); _playerSprites->firstFrameOffset = 0; _optionData = ptr + ptrOffset; @@ -221,7 +221,8 @@ void Menu::loadData() { _iconsSpritesData = ptr + ptrOffset; const uint32_t baseOffset = ptrOffset; for (int i = 0; i < iconsCount; ++i) { - _iconsSprites[i].size = le16toh(_iconsSprites[i].size); + _iconsSprites[i].size = le32toh(_iconsSprites[i].size); + _iconsSprites[i].count = le16toh(_iconsSprites[i].count); _iconsSprites[i].firstFrameOffset = ptrOffset - baseOffset; ptrOffset += _iconsSprites[i].size; } @@ -235,8 +236,10 @@ void Menu::loadData() { for (int i = 0; i < _optionsButtonSpritesCount; ++i) { WRITE_LE_UINT32(_res->_menuBuffer0 + hdrOffset, ptrOffset - baseOffset); DatSpritesGroup *spritesGroup = (DatSpritesGroup *)(ptr + hdrOffset + 4); + spritesGroup->size = le32toh(spritesGroup->size); + spritesGroup->count = le16toh(spritesGroup->count); hdrOffset += 20; - ptrOffset += le16toh(spritesGroup->size); + ptrOffset += spritesGroup->size; } } else { _optionsButtonSpritesData = 0; @@ -283,7 +286,8 @@ int Menu::getSoundNum(int num) const { } p += count2 * 2; } - assert((p - _soundData) == _res->_datHdr.soundDataSize); + // sound not found + assert((uint32_t)(p - _soundData) == _res->_datHdr.soundDataSize); } return -1; } @@ -610,29 +614,27 @@ void Menu::handleAssignPlayer() { } void Menu::updateBitmapsCircularList(const DatBitmapsGroup *bitmapsGroup, const uint8_t *bitmapData, int num, int count) { - if (num == 0) { - if (count < 3) { - _bitmapCircularListIndex[0] = -1; - } else { - _bitmapCircularListIndex[0] = count - 1; - } - _bitmapCircularListIndex[1] = 0; - _bitmapCircularListIndex[2] = 1; - } else if (num == 1) { - _bitmapCircularListIndex[0] = 0; - _bitmapCircularListIndex[1] = 1; - _bitmapCircularListIndex[2] = 2; + _bitmapCircularListIndex[1] = num; + if (count > 1) { + _bitmapCircularListIndex[2] = (num + 1) % count; } else { - _bitmapCircularListIndex[0] = num - 1; - _bitmapCircularListIndex[1] = num; - if (num == count - 1) { - _bitmapCircularListIndex[2] = 0; - } else { - _bitmapCircularListIndex[2] = num + 1; + _bitmapCircularListIndex[2] = -1; + } + if (count > 2) { + _bitmapCircularListIndex[0] = (num + count - 1) % count; + } else { + _bitmapCircularListIndex[0] = -1; + } + if (bitmapsGroup == _cutscenesBitmaps) { + for (int i = 0; i < 3; ++i) { + const int num = _bitmapCircularListIndex[i]; + if (num != -1) { + _bitmapCircularListIndex[i] = _cutsceneIndexes[num]; + } } } for (int i = 0; i < 3; ++i) { - if (_bitmapCircularListIndex[i] != -1 && _bitmapCircularListIndex[i] < count) { + if (_bitmapCircularListIndex[i] != -1) { const DatBitmapsGroup *bitmap = &bitmapsGroup[_bitmapCircularListIndex[i]]; memcpy(_paletteBuffer + (105 + 50 * i) * 3, bitmapData + bitmap->palette, 50 * 3); } @@ -648,32 +650,13 @@ void Menu::drawBitmapsCircularList(const DatBitmapsGroup *bitmapsGroup, const ui updateBitmapsCircularList(bitmapsGroup, bitmapData, num, count); static const int xPos[] = { -60, 68, 196 }; for (int i = 0; i < 3; ++i) { - if (_bitmapCircularListIndex[i] != -1 && _bitmapCircularListIndex[i] < count) { + if (_bitmapCircularListIndex[i] != -1) { const DatBitmapsGroup *bitmap = &bitmapsGroup[_bitmapCircularListIndex[i]]; drawBitmap(bitmap, bitmapData + bitmap->offset, xPos[i], 14, bitmap->w, bitmap->h, 105 + 50 * i); } } } -void Menu::scrollBitmapsCheckpoints(int dir) { - decodeLZW(_optionsBitmapData[_optionNum], _video->_frontLayer); - drawSpriteNextFrame(_iconsSprites, 5, 0, 0); - drawSpritePos(&_iconsSprites[0], _iconsSpritesData, 119, 108, (_checkpointNum + 1) / 10); - drawSpritePos(&_iconsSprites[0], _iconsSpritesData, 127, 108, (_checkpointNum + 1) % 10); - drawSpriteNextFrame(_iconsSprites, (_loadCheckpointButtonState != 0) ? 8 : 7, 0, 0); - if (dir) { - ++_checkpointNum; - if (_checkpointNum >= _lastLevelCheckpointNum[_levelNum]) { - _checkpointNum = 0; - } - } else { - --_checkpointNum; - if (_checkpointNum < 0) { - _checkpointNum = _lastLevelCheckpointNum[_levelNum] - 1; - } - } -} - void Menu::drawCheckpointScreen() { decodeLZW(_optionsBitmapData[_optionNum], _video->_frontLayer); drawBitmapsCircularList(_checkpointsBitmaps[_levelNum], _checkpointsBitmapsData[_levelNum], _checkpointNum, _lastLevelCheckpointNum[_levelNum], false); @@ -700,6 +683,22 @@ void Menu::drawLevelScreen() { refreshScreen(false); } +void Menu::drawCutsceneScreen() { + decodeLZW(_optionsBitmapData[_optionNum], _video->_frontLayer); + drawBitmapsCircularList(_cutscenesBitmaps, _cutscenesBitmapsData, _cutsceneNum, _cutsceneIndexesCount, false); + drawSpriteNextFrame(_iconsSprites, 10, 0, 0); + drawSpritePos(&_iconsSprites[0], _iconsSpritesData, 119, 108, (_cutsceneNum + 1) / 10); + drawSpritePos(&_iconsSprites[0], _iconsSpritesData, 127, 108, (_cutsceneNum + 1) % 10); + const int num = _loadCutsceneButtonState; + if (num > 1 && num < 7) { + drawSprite(&_iconsSprites[14], _iconsSpritesData, num - 2); + } else { + drawSpriteNextFrame(_iconsSprites, (num != 0) ? 13 : 12, 0, 0); + } + drawSpriteNextFrame(_iconsSprites, 11, 0, 0); + refreshScreen(false); +} + void Menu::handleSettingsScreen(int num) { } @@ -737,6 +736,9 @@ void Menu::changeToOption(int num) { memcpy(_paletteBuffer, _optionsBitmapData[0] + _optionsBitmapSize[0], 768); handleSettingsScreen(5); } else if (_optionNum == kMenu_Cutscenes + 1) { + _loadCutsceneButtonState = 0; + _cutsceneNum = 0; + drawCutsceneScreen(); } else if (_optionsBitmapSize[_optionNum] != 0) { decodeLZW(_optionsBitmapData[_optionNum], _video->_frontLayer); memcpy(_paletteBuffer, _optionsBitmapData[_optionNum] + _optionsBitmapSize[_optionNum], 256 * 3); @@ -827,20 +829,75 @@ void Menu::handleLoadCheckpoint(int num) { } else if (num == 14) { if (_lastLevelCheckpointNum[_levelNum] > 2 || _loadCheckpointButtonState == 0) { playSound(0x70); - scrollBitmapsCheckpoints(1); + ++_checkpointNum; + if (_checkpointNum >= _lastLevelCheckpointNum[_levelNum]) { + _checkpointNum = 0; + } } } else if (num == 15) { if (_lastLevelCheckpointNum[_levelNum] > 2 || _loadCheckpointButtonState == 1) { playSound(0x70); - scrollBitmapsCheckpoints(0); + --_checkpointNum; + if (_checkpointNum < 0) { + _checkpointNum = _lastLevelCheckpointNum[_levelNum] - 1; + } } } drawCheckpointScreen(); } +void Menu::handleLoadCutscene(int num) { + const uint8_t *data = &_optionData[num * 8]; + num = data[5]; + if (num == 6) { + if (_loadCutsceneButtonState != 0) { + playSound(0x70); + _loadCutsceneButtonState = 0; + } + } else if (num == 7) { + if (_loadCutsceneButtonState == 0) { + playSound(0x70); + _loadCutsceneButtonState = 1; + } + } else if (num == 8) { + if (_loadCutsceneButtonState != 0) { + playSound(0x80); + _condMask = 0x80; + } else { + playSound(0x78); + _loadCutsceneButtonState = 2; + const int num = _cutscenesBitmaps[_cutsceneIndexes[_cutsceneNum]].data; + _paf->play(num); + if (num == kPafAnimation_end) { + _paf->play(kPafAnimation_cinema); + } + playSound(0x98); + playSound(0xA0); + _loadCutsceneButtonState = 0; + } + } else if (num == 9) { + if (_cutsceneIndexesCount > 2 || _loadCutsceneButtonState == 0) { + playSound(0x70); + ++_cutsceneNum; + if (_cutsceneNum >= _cutsceneIndexesCount) { + _cutsceneNum = 0; + } + } + } else if (num == 10) { + if (_cutsceneIndexesCount > 2 || _loadCutsceneButtonState == 1) { + playSound(0x70); + --_cutsceneNum; + if (_cutsceneNum < 0) { + _cutsceneNum = _cutsceneIndexesCount - 1; + } + } + } + drawCutsceneScreen(); +} + static bool matchInput(uint8_t menu, uint8_t type, uint8_t mask, const PlayerInput &inp, uint8_t optionMask) { if (type != 0) { - if (menu == kMenu_Cutscenes || menu == kMenu_Settings) { + if (menu == kMenu_Settings) { // not implemented yet return false; } @@ -878,6 +935,17 @@ void Menu::handleOptions() { if (_lastLevelNum >= _res->_datHdr.levelsCount) { _lastLevelNum = _res->_datHdr.levelsCount; } + _cutsceneIndexesCount = 0; + const uint32_t playedCutscenesMask = _config->players[_config->currentPlayer].cutscenesMask; + for (int i = 0; i < _res->_datHdr.cutscenesCount; ++i) { + if ((playedCutscenesMask & (1 << i)) != 0) { + _cutsceneIndexes[_cutsceneIndexesCount] = i; + ++_cutsceneIndexesCount; + } + } + if (_cutsceneIndexesCount > _res->_datHdr.cutscenesCount) { + _cutsceneIndexesCount = _res->_datHdr.cutscenesCount; + } _optionNum = kMenu_Settings; changeToOption(0); _condMask = 0; @@ -887,6 +955,7 @@ void Menu::handleOptions() { _optionNum = -1; break; } + // get transition from inputs and menu return code (_condMask) int num = -1; for (int i = 0; i < _res->_datHdr.menusCount; ++i) { const uint8_t *data = _optionData + i * 8; @@ -918,6 +987,9 @@ void Menu::handleOptions() { case 8: changeToOption(num); break; + case 9: + handleLoadCutscene(num); + break; case 10: handleLoadLevel(num); break; @@ -931,8 +1003,8 @@ void Menu::handleOptions() { warning("Unhandled option %d %d", _optionNum, data[4]); break; } - if (_optionNum == 16 || _optionNum == 1 || _optionNum == 3 || _optionNum == kMenu_ResumeGame) { - // _g->saveSetupCfg(); + if (_optionNum == kMenu_Quit + 1 || _optionNum == kMenu_NewGame + 1 || _optionNum == kMenu_CurrentGame + 1 || _optionNum == kMenu_ResumeGame) { + // 'setup.cfg' is saved when exiting the main loop break; } } diff --git a/menu.h b/menu.h index bd2efc9..1d55219 100644 --- a/menu.h +++ b/menu.h @@ -27,7 +27,7 @@ struct DatBitmapsGroup { uint8_t w; uint8_t h; uint8_t colors; - uint8_t unk3; // padding to 4 bytes + uint8_t data; uint32_t offset; // 4 uint32_t palette; // 8 } PACKED; // sizeof == 12 @@ -35,6 +35,7 @@ struct DatBitmapsGroup { struct Menu { enum { kCheckpointLevelsCount = 8, + kCutsceneIndexesCount = 22, // kPafAnimation_cinema + 1 kOptionsCount = 19 }; @@ -79,6 +80,10 @@ struct Menu { uint8_t _condMask; uint8_t _loadCheckpointButtonState; int _bitmapCircularListIndex[3]; + int _cutsceneIndexesCount; + int _cutsceneNum; + uint8_t _loadCutsceneButtonState; + int _cutsceneIndexes[kCutsceneIndexesCount]; Menu(Game *g, PafPlayer *paf, Resource *res, Video *video); @@ -106,13 +111,14 @@ struct Menu { void handleAssignPlayer(); void updateBitmapsCircularList(const DatBitmapsGroup *bitmapsGroup, const uint8_t *bitmapData, int num, int count); void drawBitmapsCircularList(const DatBitmapsGroup *bitmapsGroup, const uint8_t *bitmapData, int num, int count, bool updatePalette); - void scrollBitmapsCheckpoints(int dir); void drawCheckpointScreen(); void drawLevelScreen(); + void drawCutsceneScreen(); void handleSettingsScreen(int num); void changeToOption(int num); void handleLoadLevel(int num); void handleLoadCheckpoint(int num); + void handleLoadCutscene(int num); void handleOptions(); }; diff --git a/resource.cpp b/resource.cpp index c635787..4a3d66e 100644 --- a/resource.cpp +++ b/resource.cpp @@ -10,6 +10,9 @@ #include "resource.h" #include "util.h" +// menu settings and player progress +static const char *_setupCfg = "setup.cfg"; + static const char *_setupDat = "SETUP.DAT"; static const char *_setupDax = "SETUP.DAX"; @@ -1151,7 +1154,11 @@ void Resource::resetSssFilters() { void Resource::preloadSssPcmList(const SssPreloadInfoData *preloadInfoData) { File *fp = _sssFile; if (_isPsx) { - _lvlFile->seek(preloadInfoData->pcmBlockOffset * 2048, SEEK_SET); + const uint16_t offset = preloadInfoData->pcmBlockOffset; + if (offset == 0xFFFF) { + return; + } + _lvlFile->seek(offset * 2048, SEEK_SET); fp = _lvlFile; } const uint8_t num = preloadInfoData->preload1Index; @@ -1850,69 +1857,82 @@ enum { static uint8_t _checksum; -template +static void persistUint8(FILE *fp, const uint8_t &val) { + fputc(val, fp); + _checksum ^= val; +} + static void persistUint8(FILE *fp, uint8_t &val) { - if (M == kModeSave) { - fputc(val, fp); - } else if (M == kModeLoad) { - val = fgetc(fp); - } + val = fgetc(fp); _checksum ^= val; } -template +static void persistUint32(FILE *fp, const uint32_t &val) { + for (int i = 0; i < 4; ++i) { + const uint8_t b = (val >> (i * 8)) & 0xFF; + fputc(b, fp); + _checksum ^= b; + } +} + static void persistUint32(FILE *fp, uint32_t &val) { - if (M == kModeSave) { - for (int i = 0; i < 4; ++i) { - const uint8_t b = (val >> (i * 8)) & 0xFF; - fputc(b, fp); - _checksum ^= b; - } - } else if (M == kModeLoad) { - val = 0; - for (int i = 0; i < 4; ++i) { - const uint8_t b = fgetc(fp); - val |= b << (i * 8); - _checksum ^= b; - } + val = 0; + for (int i = 0; i < 4; ++i) { + const uint8_t b = fgetc(fp); + val |= b << (i * 8); + _checksum ^= b; } } -template -static void persistSetupCfg(FILE *fp, SetupConfig *config) { +template +static void persistSetupCfg(FILE *fp, T *config) { _checksum = 0; for (int i = 0; i < 4; ++i) { for (int j = 0; j < 10; ++j) { - persistUint8(fp, config->players[i].progress[j]); + persistUint8(fp, config->players[i].progress[j]); } - persistUint8(fp, config->players[i].levelNum); - persistUint8(fp, config->players[i].checkpointNum); - persistUint32(fp, config->players[i].cutscenesMask); - persistUint8(fp, config->players[i].difficulty); + persistUint8(fp, config->players[i].levelNum); + persistUint8(fp, config->players[i].checkpointNum); + persistUint32(fp, config->players[i].cutscenesMask); + persistUint8(fp, config->players[i].difficulty); for (int j = 0; j < 32; ++j) { - persistUint8(fp, config->players[i].controls[j]); + persistUint8(fp, config->players[i].controls[j]); } - persistUint8(fp, config->players[i].stereo); - persistUint8(fp, config->players[i].volume); - persistUint8(fp, config->players[i].currentLevel); - } - persistUint8(fp, config->unkD0); - persistUint8(fp, config->currentPlayer); - uint8_t checksum = _checksum; - persistUint8(fp, config->unkD2); + persistUint8(fp, config->players[i].stereo); + persistUint8(fp, config->players[i].volume); + persistUint8(fp, config->players[i].currentLevel); + } + persistUint8(fp, config->unkD0); + persistUint8(fp, config->currentPlayer); + const uint8_t checksum = _checksum; + persistUint8(fp, config->unkD2); if (M == kModeSave) { - config->checksum = checksum; - } - persistUint8(fp, config->checksum); - if (M == kModeLoad && checksum != config->checksum) { - warning("Invalid checksum 0x%x (0x%x) for 'setup.cfg'", config->checksum, checksum); + persistUint8(fp, checksum); + } else { + persistUint8(fp, config->checksum); + if (M == kModeLoad && checksum != config->checksum) { + warning("Invalid checksum 0x%x (0x%x) for 'setup.cfg'", config->checksum, checksum); + } } } -void Resource::writeSetupCfg(FILE *fp, SetupConfig *config) { - persistSetupCfg(fp, config); +bool Resource::writeSetupCfg(const SetupConfig *config) { + FILE *fp = _fs->openSaveFile(_setupCfg, true); + if (fp) { + persistSetupCfg(fp, config); + _fs->closeFile(fp); + return true; + } + warning("Failed to save '%s'", _setupCfg); + return false; } -void Resource::readSetupCfg(FILE *fp, SetupConfig *config) { - persistSetupCfg(fp, config); +bool Resource::readSetupCfg(SetupConfig *config) { + FILE *fp = _fs->openSaveFile(_setupCfg, false); + if (fp) { + persistSetupCfg(fp, config); + _fs->closeFile(fp); + return true; + } + return false; } diff --git a/resource.h b/resource.h index 685ce9f..a858b7c 100644 --- a/resource.h +++ b/resource.h @@ -661,8 +661,8 @@ struct Resource { const MstScreenArea *findMstCodeForPos(int num, int xPos, int yPos) const; void flagMstCodeForPos(int num, uint8_t value); - void writeSetupCfg(FILE *fp, SetupConfig *config); - void readSetupCfg(FILE *fp, SetupConfig *config); + bool writeSetupCfg(const SetupConfig *config); + bool readSetupCfg(SetupConfig *config); }; #endif // RESOURCE_H__ diff --git a/screenshot.cpp b/screenshot.cpp index 89183ab..59e6f36 100644 --- a/screenshot.cpp +++ b/screenshot.cpp @@ -1,6 +1,5 @@ #include -#include #include "screenshot.h" static void fwriteUint16LE(FILE *fp, uint16_t n) { @@ -15,53 +14,48 @@ static void fwriteUint32LE(FILE *fp, uint32_t n) { static const uint16_t TAG_BM = 0x4D42; -void saveBMP(const char *filename, const uint8_t *bits, const uint8_t *pal, int w, int h) { - FILE *fp = fopen(filename, "wb"); - if (fp) { - int alignWidth = (w + 3) & ~3; - int imageSize = alignWidth * h; - - // Write file header - fwriteUint16LE(fp, TAG_BM); - fwriteUint32LE(fp, 14 + 40 + 4 * 256 + imageSize); - fwriteUint16LE(fp, 0); // reserved1 - fwriteUint16LE(fp, 0); // reserved2 - fwriteUint32LE(fp, 14 + 40 + 4 * 256); - - // Write info header - fwriteUint32LE(fp, 40); - fwriteUint32LE(fp, w); - fwriteUint32LE(fp, h); - fwriteUint16LE(fp, 1); // planes - fwriteUint16LE(fp, 8); // bit_count - fwriteUint32LE(fp, 0); // compression - fwriteUint32LE(fp, imageSize); // size_image - fwriteUint32LE(fp, 0); // x_pels_per_meter - fwriteUint32LE(fp, 0); // y_pels_per_meter - fwriteUint32LE(fp, 0); // num_colors_used - fwriteUint32LE(fp, 0); // num_colors_important +void saveBMP(FILE *fp, const uint8_t *bits, const uint8_t *pal, int w, int h) { + const int alignWidth = (w + 3) & ~3; + const int imageSize = alignWidth * h; + + // Write file header + fwriteUint16LE(fp, TAG_BM); + fwriteUint32LE(fp, 14 + 40 + 4 * 256 + imageSize); + fwriteUint16LE(fp, 0); // reserved1 + fwriteUint16LE(fp, 0); // reserved2 + fwriteUint32LE(fp, 14 + 40 + 4 * 256); + + // Write info header + fwriteUint32LE(fp, 40); + fwriteUint32LE(fp, w); + fwriteUint32LE(fp, h); + fwriteUint16LE(fp, 1); // planes + fwriteUint16LE(fp, 8); // bit_count + fwriteUint32LE(fp, 0); // compression + fwriteUint32LE(fp, imageSize); // size_image + fwriteUint32LE(fp, 0); // x_pels_per_meter + fwriteUint32LE(fp, 0); // y_pels_per_meter + fwriteUint32LE(fp, 0); // num_colors_used + fwriteUint32LE(fp, 0); // num_colors_important + + // Write palette data + for (int i = 0; i < 256; ++i) { + fputc(pal[2], fp); + fputc(pal[1], fp); + fputc(pal[0], fp); + fputc(0, fp); + pal += 3; + } - // Write palette data - for (int i = 0; i < 256; ++i) { - fputc(pal[2], fp); - fputc(pal[1], fp); - fputc(pal[0], fp); + // Write bitmap data + const int pitch = w; + bits += h * pitch; + for (int i = 0; i < h; ++i) { + bits -= pitch; + fwrite(bits, w, 1, fp); + int pad = alignWidth - w; + while (pad--) { fputc(0, fp); - pal += 3; } - - // Write bitmap data - const int pitch = w; - bits += h * pitch; - for (int i = 0; i < h; ++i) { - bits -= pitch; - fwrite(bits, w, 1, fp); - int pad = alignWidth - w; - while (pad--) { - fputc(0, fp); - } - } - - fclose(fp); } } diff --git a/screenshot.h b/screenshot.h index b569b11..003f1a1 100644 --- a/screenshot.h +++ b/screenshot.h @@ -2,9 +2,9 @@ #ifndef SCREENSHOT_H__ #define SCREENSHOT_H__ +#include #include -void saveTGA(const char *filename, const uint8_t *rgb, int w, int h); -void saveBMP(const char *filename, const uint8_t *bits, const uint8_t *pal, int w, int h); +void saveBMP(FILE *fp, const uint8_t *bits, const uint8_t *pal, int w, int h); #endif diff --git a/sound.cpp b/sound.cpp index e08f9e1..c85d3b5 100644 --- a/sound.cpp +++ b/sound.cpp @@ -836,9 +836,9 @@ SssObject *Game::startSoundObject(int bankIndex, int sampleIndex, uint32_t flags assert(sampleNum >= 0 && sampleNum < _res->_sssHdr.samplesDataCount); SssSample *sample = &_res->_sssSamplesData[sampleNum]; - // original loads the PCM data in a seperate thread + // original preloads PCM when changing screens SssPcm *pcm = &_res->_sssPcmTable[sample->pcm]; - if (!pcm->ptr) { + if (!pcm->ptr && !_res->_isPsx) { _res->loadSssPcm(_res->_sssFile, pcm); } diff --git a/system.h b/system.h index ace4c4f..2695f14 100644 --- a/system.h +++ b/system.h @@ -43,7 +43,7 @@ struct System { virtual ~System() {} - virtual void init(const char *title, int w, int h, bool fullscreen, bool widescreen) = 0; + virtual void init(const char *title, int w, int h, bool fullscreen, bool widescreen, bool yuv) = 0; virtual void destroy() = 0; virtual void setScaler(const char *name, int multiplier) = 0; @@ -51,6 +51,7 @@ struct System { virtual void setPalette(const uint8_t *pal, int n, int depth = 8) = 0; virtual void copyRect(int x, int y, int w, int h, const uint8_t *buf, int pitch) = 0; + virtual void copyYuv(int w, int h, const uint8_t *y, int ypitch, const uint8_t *u, int upitch, const uint8_t *v, int vpitch) = 0; virtual void fillRect(int x, int y, int w, int h, uint8_t color) = 0; virtual void copyRectWidescreen(int w, int h, const uint8_t *buf, const uint8_t *pal) = 0; virtual void shakeScreen(int dx, int dy) = 0; diff --git a/system_sdl2.cpp b/system_sdl2.cpp index a9c01dd..55b847f 100644 --- a/system_sdl2.cpp +++ b/system_sdl2.cpp @@ -15,8 +15,6 @@ static const char *kIconBmp = "icon.bmp"; static int _scalerMultiplier = 3; static const Scaler *_scaler = &scaler_xbr; -static const int _pixelFormat = SDL_PIXELFORMAT_RGB888; - static const struct { const char *name; const Scaler *scaler; @@ -43,6 +41,7 @@ struct System_SDL2 : System { SDL_Window *_window; SDL_Renderer *_renderer; SDL_Texture *_texture; + SDL_Texture *_backgroundTexture; // YUV (PSX) int _texW, _texH; SDL_PixelFormat *_fmt; uint32_t _pal[256]; @@ -58,12 +57,13 @@ struct System_SDL2 : System { System_SDL2(); virtual ~System_SDL2() {} - virtual void init(const char *title, int w, int h, bool fullscreen, bool widescreen); + virtual void init(const char *title, int w, int h, bool fullscreen, bool widescreen, bool yuv); virtual void destroy(); virtual void setScaler(const char *name, int multiplier); virtual void setGamma(float gamma); virtual void setPalette(const uint8_t *pal, int n, int depth); virtual void copyRect(int x, int y, int w, int h, const uint8_t *buf, int pitch); + virtual void copyYuv(int w, int h, const uint8_t *y, int ypitch, const uint8_t *u, int upitch, const uint8_t *v, int vpitch); virtual void fillRect(int x, int y, int w, int h, uint8_t color); virtual void copyRectWidescreen(int w, int h, const uint8_t *buf, const uint8_t *pal); virtual void shakeScreen(int dx, int dy); @@ -82,7 +82,7 @@ struct System_SDL2 : System { void addKeyMapping(int key, uint8_t mask); void setupDefaultKeyMappings(); void updateKeys(PlayerInput *inp); - void prepareScaledGfx(const char *caption, bool fullscreen, bool widescreen); + void prepareScaledGfx(const char *caption, bool fullscreen, bool widescreen, bool yuv); }; static System_SDL2 system_sdl2; @@ -90,14 +90,14 @@ System *const g_system = &system_sdl2; System_SDL2::System_SDL2() : _offscreenLut(0), _offscreenRgb(0), - _window(0), _renderer(0), _texture(0), _fmt(0), _widescreenTexture(0), + _window(0), _renderer(0), _texture(0), _backgroundTexture(0), _fmt(0), _widescreenTexture(0), _controller(0), _joystick(0) { for (int i = 0; i < 256; ++i) { _gammaLut[i] = i; } } -void System_SDL2::init(const char *title, int w, int h, bool fullscreen, bool widescreen) { +void System_SDL2::init(const char *title, int w, int h, bool fullscreen, bool widescreen, bool yuv) { SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER); SDL_ShowCursor(SDL_DISABLE); setupDefaultKeyMappings(); @@ -117,7 +117,7 @@ void System_SDL2::init(const char *title, int w, int h, bool fullscreen, bool wi error("System_SDL2::init() Unable to allocate RGB offscreen buffer"); } memset(_offscreenLut, 0, offscreenSize); - prepareScaledGfx(title, fullscreen, widescreen); + prepareScaledGfx(title, fullscreen, widescreen, yuv); _joystick = 0; _controller = 0; const int count = SDL_NumJoysticks(); @@ -158,6 +158,10 @@ void System_SDL2::destroy() { SDL_DestroyTexture(_widescreenTexture); _widescreenTexture = 0; } + if (_backgroundTexture) { + SDL_DestroyTexture(_backgroundTexture); + _backgroundTexture = 0; + } if (_renderer) { SDL_DestroyRenderer(_renderer); _renderer = 0; @@ -315,6 +319,9 @@ void System_SDL2::setPalette(const uint8_t *pal, int n, int depth) { b = _gammaLut[b]; _pal[i] = SDL_MapRGB(_fmt, r, g, b); } + if (_backgroundTexture) { + _pal[0] = 0; + } } void System_SDL2::copyRect(int x, int y, int w, int h, const uint8_t *buf, int pitch) { @@ -326,6 +333,12 @@ void System_SDL2::copyRect(int x, int y, int w, int h, const uint8_t *buf, int p } } +void System_SDL2::copyYuv(int w, int h, const uint8_t *y, int ypitch, const uint8_t *u, int upitch, const uint8_t *v, int vpitch) { + if (_backgroundTexture) { + SDL_UpdateYUVTexture(_backgroundTexture, 0, y, ypitch, u, upitch, v, vpitch); + } +} + void System_SDL2::fillRect(int x, int y, int w, int h, uint8_t color) { assert(x >= 0 && x + w <= _screenW && y >= 0 && y + h <= _screenH); for (int i = 0; i < h; ++i) { @@ -390,6 +403,7 @@ void System_SDL2::updateScreen(bool drawWidescreen) { SDL_UnlockTexture(_texture); SDL_RenderClear(_renderer); + if (_widescreenTexture) { if (drawWidescreen) { SDL_RenderCopy(_renderer, _widescreenTexture, 0, 0); @@ -402,8 +416,14 @@ void System_SDL2::updateScreen(bool drawWidescreen) { r.w = _texW; r.y += (r.h - _texH) / 2; r.h = _texH; + if (_backgroundTexture) { + SDL_RenderCopy(_renderer, _backgroundTexture, 0, &r); + } SDL_RenderCopy(_renderer, _texture, 0, &r); } else { + if (_backgroundTexture) { + SDL_RenderCopy(_renderer, _backgroundTexture, 0, 0); + } SDL_RenderCopy(_renderer, _texture, 0, 0); } SDL_RenderPresent(_renderer); @@ -703,7 +723,7 @@ void System_SDL2::updateKeys(PlayerInput *inp) { inp->mask |= pad.mask; } -void System_SDL2::prepareScaledGfx(const char *caption, bool fullscreen, bool widescreen) { +void System_SDL2::prepareScaledGfx(const char *caption, bool fullscreen, bool widescreen, bool yuv) { int flags = 0; if (fullscreen) { flags |= SDL_WINDOW_FULLSCREEN_DESKTOP; @@ -723,11 +743,20 @@ void System_SDL2::prepareScaledGfx(const char *caption, bool fullscreen, bool wi _renderer = SDL_CreateRenderer(_window, -1, SDL_RENDERER_ACCELERATED); SDL_RenderSetLogicalSize(_renderer, windowW, windowH); SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1"); - _texture = SDL_CreateTexture(_renderer, _pixelFormat, SDL_TEXTUREACCESS_STREAMING, _texW, _texH); + + const int pixelFormat = yuv ? SDL_PIXELFORMAT_RGBA8888 : SDL_PIXELFORMAT_RGB888; + _texture = SDL_CreateTexture(_renderer, pixelFormat, SDL_TEXTUREACCESS_STREAMING, _texW, _texH); if (widescreen) { - _widescreenTexture = SDL_CreateTexture(_renderer, _pixelFormat, SDL_TEXTUREACCESS_STREAMING, _screenW, _screenH); + _widescreenTexture = SDL_CreateTexture(_renderer, pixelFormat, SDL_TEXTUREACCESS_STREAMING, _screenW, _screenH); } else { _widescreenTexture = 0; } - _fmt = SDL_AllocFormat(_pixelFormat); + if (yuv) { + _backgroundTexture = SDL_CreateTexture(_renderer, SDL_PIXELFORMAT_YV12, SDL_TEXTUREACCESS_STREAMING, _screenW, _screenH); + // the game texture is drawn on top + SDL_SetTextureBlendMode(_texture, SDL_BLENDMODE_BLEND); + } else { + _backgroundTexture = 0; + } + _fmt = SDL_AllocFormat(pixelFormat); } diff --git a/video.cpp b/video.cpp index 91d2ac2..24cccac 100644 --- a/video.cpp +++ b/video.cpp @@ -4,6 +4,7 @@ */ #include "video.h" +#include "mdec.h" #include "system.h" static const bool kUseShadowColorLut = false; @@ -34,6 +35,7 @@ Video::Video() { _transformShadowLayerDelta = 0; _fillColor = 0xC4; _blackColor = 255; + memset(&_mdec, 0, sizeof(_mdec)); } Video::~Video() { @@ -42,6 +44,9 @@ Video::~Video() { free(_backgroundLayer); free(_shadowColorLookupTable); free(_shadowScreenMaskBuffer); + free(_mdec.planes[kOutputPlaneY].ptr); + free(_mdec.planes[kOutputPlaneCb].ptr); + free(_mdec.planes[kOutputPlaneCr].ptr); } static int colorBrightness(int r, int g, int b) { @@ -79,6 +84,9 @@ void Video::updateGameDisplay(uint8_t *buf) { } } g_system->copyRect(0, 0, W, H, buf, 256); + if (_mdec.planes[0].ptr) { + g_system->copyYuv(Video::W, Video::H, _mdec.planes[0].ptr, _mdec.planes[0].pitch, _mdec.planes[1].ptr, _mdec.planes[1].pitch, _mdec.planes[2].ptr, _mdec.planes[2].pitch); + } } void Video::updateScreen() { @@ -479,3 +487,50 @@ uint8_t Video::findWhiteColor() const { } return color; } + +void Video::initPsx() { + const int w = (W + 15) & ~15; + const int h = (H + 15) & ~15; + _mdec.planes[kOutputPlaneY].ptr = (uint8_t *)malloc(w * h); + _mdec.planes[kOutputPlaneY].pitch = w; + const int w2 = w / 2; + const int h2 = h / 2; + _mdec.planes[kOutputPlaneCb].ptr = (uint8_t *)malloc(w2 * h2); + _mdec.planes[kOutputPlaneCb].pitch = w2; + _mdec.planes[kOutputPlaneCr].ptr = (uint8_t *)malloc(w2 * h2); + _mdec.planes[kOutputPlaneCr].pitch = w2; +} + +void Video::decodeBackgroundPsx(const uint8_t *src) { + const int len = W * H * sizeof(uint16_t); + _mdec.x = 0; + _mdec.y = 0; + _mdec.w = W; + _mdec.h = H; + decodeMDEC(src, len, 0, W, H, &_mdec); +} + +void Video::decodeTilePsx(const uint8_t *src) { + const uint16_t size = READ_LE_UINT16(src + 2); + if (size > 6) { + const int count = READ_LE_UINT32(src + 4); + assert(count >= 1 && count <= 3); + int offset = 8; + for (int i = 0; i < count && offset < size; ++i) { + _mdec.x = src[offset]; + _mdec.y = src[offset + 1]; + const int len = READ_LE_UINT16(src + offset + 2); + _mdec.w = src[offset + 4] * 16; + _mdec.h = src[offset + 5] * 16; + const int tiles = src[offset + 7]; + const uint8_t *data = &src[offset + 8]; + if (tiles == 0) { + decodeMDEC(data, len - 8, 0, _mdec.w, _mdec.h, &_mdec); + } else if (0) { + decodeMDEC(data + tiles, len - 8, data, _mdec.w, _mdec.h, &_mdec); + } + offset += len; + } + assert(offset == size + 2); + } +} diff --git a/video.h b/video.h index 74fe919..fc4e805 100644 --- a/video.h +++ b/video.h @@ -7,6 +7,7 @@ #define VIDEO_H__ #include "intern.h" +#include "mdec.h" enum { kSprHorizFlip = 1 << 0, // left-right @@ -55,6 +56,8 @@ struct Video { int x, y, w, h; } _spr; + MdecOutput _mdec; + Video(); ~Video(); @@ -75,6 +78,10 @@ struct Video { void drawStringCharacter(int x, int y, uint8_t chr, uint8_t color, uint8_t *dst); void drawString(const char *s, int x, int y, uint8_t color, uint8_t *dst); uint8_t findWhiteColor() const; + + void initPsx(); + void decodeBackgroundPsx(const uint8_t *src); + void decodeTilePsx(const uint8_t *src); }; #endif // VIDEO_H__