[Scummvm-git-logs] scummvm master -> e600faa76ab6a2de10611a9dc07ca8e034bb9d27
sev-
noreply at scummvm.org
Tue Feb 10 12:45:19 UTC 2026
This automated email contains information about 1 new commit which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .
Summary:
e600faa76a SCUMM: MMNES - Add support for playback of title screens.
Commit: e600faa76ab6a2de10611a9dc07ca8e034bb9d27
https://github.com/scummvm/scummvm/commit/e600faa76ab6a2de10611a9dc07ca8e034bb9d27
Author: Robert Megone (robert.megone at gmail.com)
Date: 2026-02-10T13:45:15+01:00
Commit Message:
SCUMM: MMNES - Add support for playback of title screens.
Changed paths:
engines/scumm/file_nes.cpp
engines/scumm/file_nes.h
engines/scumm/players/player_nes.cpp
engines/scumm/players/player_nes.h
engines/scumm/scumm.cpp
engines/scumm/scumm.h
diff --git a/engines/scumm/file_nes.cpp b/engines/scumm/file_nes.cpp
index 8f3d212be27..50c1c0613f5 100644
--- a/engines/scumm/file_nes.cpp
+++ b/engines/scumm/file_nes.cpp
@@ -20,6 +20,10 @@
*/
#include "scumm/file_nes.h"
+#include "scumm/scumm.h"
+#include "scumm/players/player_nes.h"
+#include "common/events.h"
+#include "common/system.h"
#include "common/debug.h"
#include "common/endian.h"
#include "common/md5.h"
@@ -897,6 +901,69 @@ const ScummNESFile::ResourceGroup res_charset = {
}
};
+static const ScummNESFile::Resource res_titles_usa[2] = { {0x02701, 0x0BCA}, {0x0324D, 0x091F} };
+static const ScummNESFile::Resource res_titles_eur[2] = { {0x02701, 0x0B8C}, {0x0320F, 0x091F} };
+static const ScummNESFile::Resource res_titles_swe[2] = { {0x02701, 0x0B8C}, {0x0320F, 0x091F} };
+static const ScummNESFile::Resource res_titles_fra[2] = { {0x02701, 0x0B8C}, {0x0320F, 0x091F} };
+static const ScummNESFile::Resource res_titles_ger[2] = { {0x02701, 0x0B8C}, {0x0320F, 0x091F} };
+static const ScummNESFile::Resource res_titles_esp[2] = { {0x02701, 0x0B8C}, {0x0320F, 0x091F} };
+static const ScummNESFile::Resource res_titles_ita[2] = { {0x02701, 0x0B8C}, {0x0320F, 0x091F} };
+
+static const ScummNESFile::ResourceGroup res_titles = {
+ ScummNESFile::NES_TITLES,
+ {
+ res_titles_usa,
+ res_titles_eur,
+ res_titles_swe,
+ res_titles_fra,
+ res_titles_ger,
+ res_titles_esp,
+ res_titles_ita,
+ }
+};
+
+static const ScummNESFile::Resource res_title2_sparklechr_usa[1] = { { 0x024E9, 0x0060 } };
+static const ScummNESFile::Resource res_title2_sparklechr_eur[1] = { { 0x024E0, 0x0060 } };
+static const ScummNESFile::Resource res_title2_sparklechr_swe[1] = { { 0x024E0, 0x0060 } };
+static const ScummNESFile::Resource res_title2_sparklechr_fra[1] = { { 0x024E0, 0x0060 } };
+static const ScummNESFile::Resource res_title2_sparklechr_ger[1] = { { 0x024E0, 0x0060 } };
+static const ScummNESFile::Resource res_title2_sparklechr_esp[1] = { { 0x024E0, 0x0060 } };
+static const ScummNESFile::Resource res_title2_sparklechr_ita[1] = { { 0x024E0, 0x0060 } };
+
+static const ScummNESFile::ResourceGroup res_title2_sparklechr = {
+ ScummNESFile::NES_TITLE2_SPARKLECHR,
+ {
+ res_title2_sparklechr_usa,
+ res_title2_sparklechr_eur,
+ res_title2_sparklechr_swe,
+ res_title2_sparklechr_fra,
+ res_title2_sparklechr_ger,
+ res_title2_sparklechr_esp,
+ res_title2_sparklechr_ita,
+ }
+};
+
+static const ScummNESFile::Resource res_title2_sparklepal_usa[1] = { { 0x02549, 0x0010 } };
+static const ScummNESFile::Resource res_title2_sparklepal_eur[1] = { { 0x02540, 0x0010 } };
+static const ScummNESFile::Resource res_title2_sparklepal_swe[1] = { { 0x02540, 0x0010 } };
+static const ScummNESFile::Resource res_title2_sparklepal_fra[1] = { { 0x02540, 0x0010 } };
+static const ScummNESFile::Resource res_title2_sparklepal_ger[1] = { { 0x02540, 0x0010 } };
+static const ScummNESFile::Resource res_title2_sparklepal_esp[1] = { { 0x02540, 0x0010 } };
+static const ScummNESFile::Resource res_title2_sparklepal_ita[1] = { { 0x02540, 0x0010 } };
+
+static const ScummNESFile::ResourceGroup res_title2_sparklepal = {
+ ScummNESFile::NES_TITLE2_SPARKLEPAL,
+ {
+ res_title2_sparklepal_usa,
+ res_title2_sparklepal_eur,
+ res_title2_sparklepal_swe,
+ res_title2_sparklepal_fra,
+ res_title2_sparklepal_ger,
+ res_title2_sparklepal_esp,
+ res_title2_sparklepal_ita,
+ }
+};
+
static const ScummNESFile::Resource res_preplist_usa[1] = { { 0x3FB5A, 0x000E } };
static const ScummNESFile::Resource res_preplist_eur[1] = { { 0x3FB90, 0x000E } };
static const ScummNESFile::Resource res_preplist_swe[1] = { { 0x3FBA9, 0x000E } };
@@ -938,6 +1005,33 @@ byte ScummNESFile::fileReadByte() {
return b;
}
+
+void ScummNESFile::decodeTitleRLE(Common::Array<byte> &dst, uint32 expectedSize) {
+ dst.clear();
+ dst.reserve(expectedSize);
+
+ while (dst.size() < expectedSize) {
+ const byte loop = fileReadByte();
+ const uint32 count = (uint32)(loop & 0x7F);
+
+ if (count == 0) {
+ continue;
+ }
+
+ if (loop & 0x80) {
+ for (uint32 i = 0; i < count && dst.size() < expectedSize; ++i) {
+ dst.push_back(fileReadByte());
+ }
+ } else {
+ const byte data = fileReadByte();
+ for (uint32 i = 0; i < count && dst.size() < expectedSize; ++i) {
+ dst.push_back(data);
+ }
+ }
+ }
+}
+
+
uint16 ScummNESFile::fileReadUint16LE() {
uint16 a = fileReadByte();
uint16 b = fileReadByte();
@@ -1091,6 +1185,83 @@ uint16 ScummNESFile::extractResource(Common::WriteStream *output, const Resource
return reslen;
}
+
+bool ScummNESFile::decodeTitleScreen(uint index, NESTitleScreen &out) {
+ if (index >= 2)
+ return false;
+
+ const Resource *res = res_titles.langs[_ROMset];
+ if (!res)
+ return false;
+
+ res = &res[index];
+
+ _baseStream->seek(res->offset, SEEK_SET);
+
+ out.unk1 = fileReadUint16LE();
+ out.unk2 = fileReadUint16LE();
+
+ out.numberOfTiles = (byte)(fileReadByte() + 1);
+
+ const uint32 gfxSize = (uint32)out.numberOfTiles * 16u;
+ decodeTitleRLE(out.gfx, gfxSize);
+
+ out.unk3 = fileReadUint16LE();
+ out.unk4 = fileReadByte();
+ out.width = (byte)(fileReadByte() + 1);
+ out.height = (byte)(fileReadByte() + 1);
+
+ decodeTitleRLE(out.nametable, (uint32)out.width * (uint32)out.height);
+
+ out.unk5 = fileReadUint16LE();
+ out.unk6 = fileReadByte();
+ out.attrWidth = (byte)(fileReadByte() + 1);
+ out.attrHeight = (byte)(fileReadByte() + 1);
+
+ decodeTitleRLE(out.attributes, (uint32)out.attrWidth * (uint32)out.attrHeight);
+
+ out.stepNum = fileReadByte();
+
+ out.palette.clear();
+ out.palette.reserve(16);
+ for (int i = 0; i < 16; ++i)
+ out.palette.push_back(fileReadByte());
+
+ out.endOfData = fileReadByte();
+
+ return true;
+}
+
+bool ScummNESFile::readTitle2SparkleChr(Common::Array<byte> &out) {
+ const Resource *res = nullptr;
+ if (_ROMset < 0 || _ROMset >= kROMsetNum)
+ return false;
+
+ res = res_title2_sparklechr.langs[_ROMset];
+ if (!res)
+ return false;
+
+ _baseStream->seek(res->offset, SEEK_SET);
+ out.resize(res->length);
+ if (_baseStream->read(out.data(), res->length) != res->length)
+ return false;
+
+ return true;
+}
+
+bool ScummNESFile::readTitle2SparklePalette(Common::Array<byte> &out) {
+ const Resource *res = res_title2_sparklepal.langs[_ROMset];
+ if (!res)
+ return false;
+
+ out.resize(res->length);
+ _baseStream->seek(res->offset, SEEK_SET);
+ if (_baseStream->read(out.data(), res->length) != res->length)
+ return false;
+
+ return true;
+}
+
struct ScummNESFile::LFLEntry {
const ResourceGroup *type;
int index;
@@ -1461,4 +1632,340 @@ uint32 ScummNESFile::read(void *dataPtr, uint32 dataSize) {
return realLen;
}
+static bool nesTitleWaitOrSkip(OSystem *system, uint32 timeoutMs) {
+ const uint32 startTimeMs = system->getMillis();
+
+ Common::EventManager *eventMan = system->getEventManager();
+ while (true) {
+ Common::Event event;
+ while (eventMan->pollEvent(event)) {
+ if (event.type == Common::EVENT_KEYDOWN && event.kbd.keycode == Common::KEYCODE_ESCAPE)
+ return true;
+
+ if (event.type == Common::EVENT_LBUTTONDOWN || event.type == Common::EVENT_RBUTTONDOWN)
+ return true;
+ }
+
+ if (timeoutMs && (system->getMillis() - startTimeMs) >= timeoutMs)
+ return true;
+
+ system->delayMillis(10);
+ }
+}
+
+static void nesTitle2TwinkleStep(
+ Scumm::Player_NES *player,
+ const byte (*twinkleGroups)[4][6],
+ byte group,
+ byte step ) {
+ if (!player)
+ return;
+
+ if (group >= 8 || step >= 4)
+ return;
+
+ if (step == 0)
+ player->startTitleTwinkleGroup(twinkleGroups[group]);
+ }
+
+static bool nesTitle2WaitOrSkipWithSpriteAnim(
+ OSystem *system,
+ Scumm::ScummEngine *vm,
+ const Scumm::ScummNESFile::NESTitleScreen &t,
+ const byte *backgroundFrame,
+ uint32 waitMs,
+ Scumm::ScummNESFile *nesFile,
+ Scumm::Player_NES *player ) {
+ const uint32 startMs = system->getMillis();
+
+ struct ActiveSparkle {
+ byte x;
+ byte y;
+ byte frameIndex;
+ byte positionIndex;
+ };
+
+ struct PendingChirp {
+ byte groupIndex;
+ byte stepIndex;
+ int16 framesUntilNext;
+ };
+
+ static bool sTitle2SparklesInitialized = false;
+ static uint32 sTitle2LastTickMs = 0;
+ static int16 sTitle2AnimFramesUntilAdvance = 0;
+ static int16 sTitle2SpawnFramesUntilNext = 0;
+ static uint sTitle2NextPositionIndex = 0;
+ static uint sTitle2SpawnedCount = 0;
+ static bool sTitle2AllSpawned = false;
+ static Common::Array<ActiveSparkle> sTitle2ActiveSparkles;
+ static Common::Array<PendingChirp> sTitle2PendingChirps;
+
+ static const byte kTwinkleGroups[8][4][6] = {
+ {
+ { 0x03, 0x05, 0x00, 0x0A, 0x40, 0x04 },
+ { 0x07, 0x05, 0x00, 0x0E, 0x40, 0x04 },
+ { 0x03, 0x05, 0x00, 0x0B, 0x40, 0x04 },
+ { 0x07, 0x05, 0x00, 0x10, 0x40, 0x04 }
+ },
+ {
+ { 0x03, 0x05, 0x00, 0x0B, 0x40, 0x04 },
+ { 0x07, 0x05, 0x00, 0x10, 0x40, 0x04 },
+ { 0x03, 0x05, 0x00, 0x09, 0x40, 0x04 },
+ { 0x07, 0x05, 0x00, 0x12, 0x40, 0x04 }
+ },
+ {
+ { 0x03, 0x05, 0x00, 0x09, 0x40, 0x04 },
+ { 0x07, 0x05, 0x00, 0x12, 0x40, 0x04 },
+ { 0x03, 0x05, 0x00, 0x0A, 0x40, 0x04 },
+ { 0x07, 0x05, 0x00, 0x0F, 0x40, 0x04 }
+ },
+ {
+ { 0x03, 0x05, 0x00, 0x08, 0x40, 0x04 },
+ { 0x07, 0x05, 0x00, 0x11, 0x40, 0x04 },
+ { 0x03, 0x05, 0x00, 0x09, 0x40, 0x04 },
+ { 0x07, 0x05, 0x00, 0x0E, 0x40, 0x04 }
+ },
+ {
+ { 0x07, 0x05, 0x00, 0x0E, 0x40, 0x04 },
+ { 0x03, 0x05, 0x00, 0x0C, 0x40, 0x04 },
+ { 0x07, 0x05, 0x00, 0x0D, 0x40, 0x04 },
+ { 0x03, 0x05, 0x00, 0x08, 0x40, 0x04 }
+ },
+ {
+ { 0x07, 0x05, 0x00, 0x0F, 0x40, 0x04 },
+ { 0x03, 0x05, 0x00, 0x08, 0x40, 0x04 },
+ { 0x07, 0x05, 0x00, 0x11, 0x40, 0x04 },
+ { 0x03, 0x05, 0x00, 0x09, 0x40, 0x04 }
+ },
+ {
+ { 0x07, 0x05, 0x00, 0x0B, 0x40, 0x04 },
+ { 0x03, 0x05, 0x00, 0x09, 0x40, 0x04 },
+ { 0x07, 0x05, 0x00, 0x0D, 0x40, 0x04 },
+ { 0x03, 0x05, 0x00, 0x0A, 0x40, 0x04 }
+ },
+ {
+ { 0x07, 0x05, 0x00, 0x11, 0x40, 0x04 },
+ { 0x03, 0x05, 0x00, 0x09, 0x40, 0x04 },
+ { 0x07, 0x05, 0x00, 0x0E, 0x40, 0x04 },
+ { 0x03, 0x05, 0x00, 0x0C, 0x40, 0x04 }
+ }
+ };
+
+ Common::Array<byte> sparkleChr;
+ if (!nesFile || !nesFile->readTitle2SparkleChr(sparkleChr))
+ return nesTitleWaitOrSkip(system, waitMs);
+
+ Common::Array<byte> sparklePalette;
+ if (!nesFile->readTitle2SparklePalette(sparklePalette) || sparklePalette.size() < 16)
+ return nesTitleWaitOrSkip(system, waitMs);
+
+ const byte sparkleSubPaletteIndex = 0;
+
+ const byte sparklePosX[8] = { 0x25, 0x3E, 0x5D, 0x6B, 0x92, 0xA4, 0xCC, 0xE5 };
+ const byte sparklePosY[8] = { 0x68, 0x48, 0x60, 0x3E, 0x6D, 0x5E, 0x50, 0x3D };
+ const int16 animFramesPerStep = 10;
+ const int16 spawnFramesPerSparkle = 24;
+
+ if (!sTitle2SparklesInitialized) {
+ sTitle2SparklesInitialized = true;
+ sTitle2LastTickMs = system->getMillis();
+ sTitle2AnimFramesUntilAdvance = animFramesPerStep;
+ sTitle2SpawnFramesUntilNext = spawnFramesPerSparkle;
+ sTitle2NextPositionIndex = 0;
+ sTitle2SpawnedCount = 0;
+ sTitle2AllSpawned = false;
+ sTitle2ActiveSparkles.clear();
+ sTitle2PendingChirps.clear();
+ }
+
+ Common::EventManager *evm = system->getEventManager();
+ Common::Array<byte> framePixels;
+ framePixels.resize(256u * 240u);
+
+ while (true) {
+ Common::Event ev;
+ while (evm->pollEvent(ev)) {
+ if (ev.type == Common::EVENT_KEYDOWN && ev.kbd.keycode == Common::KEYCODE_ESCAPE)
+ return true;
+
+ if (ev.type == Common::EVENT_LBUTTONDOWN || ev.type == Common::EVENT_RBUTTONDOWN)
+ return true;
+ }
+
+ if (waitMs && (system->getMillis() - startMs) >= waitMs)
+ return true;
+
+ const uint32 nowMs = system->getMillis();
+ const uint32 elapsedMs = nowMs - sTitle2LastTickMs;
+ uint32 tickCount = elapsedMs / 16;
+ if (tickCount == 0) {
+ system->delayMillis(1);
+ continue;
+ }
+ if (tickCount > 5)
+ tickCount = 5;
+ for (uint32 tickIndex = 0; tickIndex < tickCount; ++tickIndex) {
+ sTitle2AnimFramesUntilAdvance--;
+ if (sTitle2AnimFramesUntilAdvance < 0) {
+ sTitle2AnimFramesUntilAdvance = animFramesPerStep;
+
+ for (int i = (int)sTitle2ActiveSparkles.size() - 1; i >= 0; --i) {
+ sTitle2ActiveSparkles[i].frameIndex++;
+ if (sTitle2ActiveSparkles[i].frameIndex >= 6)
+ sTitle2ActiveSparkles.remove_at(i);
+ }
+ }
+
+ if (!sTitle2AllSpawned) {
+ sTitle2SpawnFramesUntilNext--;
+ if (sTitle2SpawnFramesUntilNext < 0) {
+ sTitle2SpawnFramesUntilNext = spawnFramesPerSparkle;
+
+ ActiveSparkle sp;
+ sp.x = sparklePosX[sTitle2NextPositionIndex];
+ sp.y = sparklePosY[sTitle2NextPositionIndex];
+ sp.frameIndex = 0;
+ sp.positionIndex = (byte)sTitle2NextPositionIndex;
+ sTitle2ActiveSparkles.push_back(sp);
+
+ PendingChirp pc;
+ pc.groupIndex = (byte)sTitle2NextPositionIndex;
+ pc.stepIndex = 0;
+ pc.framesUntilNext = 1;
+ sTitle2PendingChirps.push_back(pc);
+
+ sTitle2NextPositionIndex = (sTitle2NextPositionIndex + 1) & 7;
+ sTitle2SpawnedCount++;
+ if (sTitle2SpawnedCount >= 8)
+ sTitle2AllSpawned = true;
+ }
+ }
+
+ for (int i = (int)sTitle2PendingChirps.size() - 1; i >= 0; --i) {
+ PendingChirp &pc = sTitle2PendingChirps[i];
+ pc.framesUntilNext--;
+
+ if (pc.framesUntilNext <= 0) {
+ nesTitle2TwinkleStep(player, kTwinkleGroups, pc.groupIndex, pc.stepIndex);
+ sTitle2PendingChirps.remove_at(i);
+ break;
+ }
+ }
+
+}
+ sTitle2LastTickMs += tickCount * 16;
+
+ memcpy(framePixels.begin(), backgroundFrame, 256u * 240u);
+
+ for (uint sparkleIndex = 0; sparkleIndex < sTitle2ActiveSparkles.size(); ++sparkleIndex) {
+ const ActiveSparkle &sp = sTitle2ActiveSparkles[sparkleIndex];
+ const uint32 chrTileOffset = (uint32)sp.frameIndex * 16u;
+ if (chrTileOffset + 16u > sparkleChr.size())
+ continue;
+
+ for (uint pixelY = 0; pixelY < 8; ++pixelY) {
+ const byte p0 = sparkleChr[chrTileOffset + pixelY];
+ const byte p1 = sparkleChr[chrTileOffset + 8u + pixelY];
+
+ for (uint pixelX = 0; pixelX < 8; ++pixelX) {
+ const uint shift = 7u - pixelX;
+ const byte ci = (byte)(((p0 >> shift) & 1u) | (((p1 >> shift) & 1u) << 1u));
+ if (ci == 0)
+ continue;
+
+ const int screenX = (int)sp.x + (int)pixelX;
+ const int screenY = (int)sp.y + (int)pixelY;
+ if (screenX < 0 || screenX >= 256 || screenY < 0 || screenY >= 240)
+ continue;
+
+ const uint32 dstIndex = (uint32)screenY * 256u + (uint32)screenX;
+
+ const uint32 paletteBase = (uint32)sparkleSubPaletteIndex * 4u;
+ framePixels[dstIndex] = (byte)(sparklePalette[paletteBase + (uint32)ci] & 0x3Fu);
+ }
+ }
+ }
+
+ system->copyRectToScreen(framePixels.begin(), 256, 0, 0, 256, 240);
+ system->updateScreen();
+ }
+}
+
+
+
+void ScummEngine::playNESTitleScreens() {
+
+ ScummNESFile *nesFile = dynamic_cast<ScummNESFile *>(_fileHandle);
+ if (!nesFile)
+ return;
+
+ Player_NES *player = dynamic_cast<Player_NES *>(_musicEngine);
+
+ resetPalette(true);
+
+ Common::Array<byte> framePixels;
+ framePixels.resize(256u * 240u);
+
+ for (uint titleIndex = 0; titleIndex < 2; ++titleIndex) {
+ ScummNESFile::NESTitleScreen title;
+ if (!nesFile->decodeTitleScreen(titleIndex, title))
+ return;
+
+ memset(framePixels.begin(), 0, 256u * 240u);
+
+ const byte backgroundColorIndex = (title.palette.size() >= 1) ? (byte)(title.palette[0] & 0x3Fu) : 0;
+
+ for (int tileY = 0; tileY < 30; ++tileY) {
+ for (int tileX = 0; tileX < 32; ++tileX) {
+ const uint32 nametableIndex = (uint32)tileY * 32u + (uint32)tileX;
+ const byte tileIndex = (nametableIndex < title.nametable.size()) ? title.nametable[nametableIndex] : 0;
+ const uint32 tileOff = (uint32)tileIndex * 16u;
+
+ if (tileOff + 16u > title.gfx.size())
+ continue;
+
+ const int attrX = tileX / 4;
+ const int attrY = tileY / 4;
+ const uint32 attrIndex = (uint32)attrY * 8u + (uint32)attrX;
+ const byte attrByte = (attrIndex < title.attributes.size()) ? title.attributes[attrIndex] : 0;
+ const int quadX = (tileX % 4) / 2;
+ const int quadY = (tileY % 4) / 2;
+ const int shift = (quadY * 2 + quadX) * 2;
+ const byte subPaletteIndex = (byte)((attrByte >> shift) & 0x03u);
+
+ for (int py = 0; py < 8; ++py) {
+ const byte plane0 = title.gfx[tileOff + (uint32)py];
+ const byte plane1 = title.gfx[tileOff + 8u + (uint32)py];
+ const int y = tileY * 8 + py;
+ byte *dstRow = framePixels.begin() + y * 256 + tileX * 8;
+
+ for (int px = 0; px < 8; ++px) {
+ const int bitIndex = 7 - px;
+ const byte lowBit = (byte)((plane0 >> bitIndex) & 1u);
+ const byte highBit = (byte)((plane1 >> bitIndex) & 1u);
+ const byte colorIndex = (byte)(lowBit | (highBit << 1u));
+
+ byte nesColor = backgroundColorIndex;
+ if (colorIndex != 0 && title.palette.size() >= 16) {
+ const int paletteBase = (int)subPaletteIndex * 4;
+ nesColor = (byte)(title.palette[paletteBase + (int)colorIndex] & 0x3Fu);
+ }
+ dstRow[px] = nesColor;
+ }
+ }
+ }
+ }
+
+ _system->copyRectToScreen(framePixels.begin(), 256, 0, 0, 256, 240);
+ _system->updateScreen();
+
+ if (titleIndex == 0) {
+ nesTitleWaitOrSkip(_system, 5000u);
+ } else {
+ nesTitle2WaitOrSkipWithSpriteAnim(_system, this, title, framePixels.begin(), 5500u, nesFile, player);
+ }
+ }
+}
+
} // End of namespace Scumm
diff --git a/engines/scumm/file_nes.h b/engines/scumm/file_nes.h
index e4ca9a5109d..982dcd14569 100644
--- a/engines/scumm/file_nes.h
+++ b/engines/scumm/file_nes.h
@@ -23,6 +23,7 @@
#define SCUMM_FILE_NES_H
#include "scumm/file.h"
+#include "common/array.h"
namespace Scumm {
@@ -59,10 +60,38 @@ public:
NES_SPROFFS,
NES_SPRDATA,
NES_CHARSET,
- NES_PREPLIST
+ NES_PREPLIST,
+ NES_TITLES,
+ NES_TITLE2_SPARKLECHR,
+ NES_TITLE2_SPARKLEPAL,
};
+ struct NESTitleScreen {
+ uint16 unk1;
+ uint16 unk2;
+ byte numberOfTiles;
+ Common::Array<byte> gfx;
+ uint16 unk3;
+ byte unk4;
+ byte width;
+ byte height;
+ Common::Array<byte> nametable;
+ uint16 unk5;
+ byte unk6;
+ byte attrWidth;
+ byte attrHeight;
+ Common::Array<byte> attributes;
+ byte stepNum;
+ Common::Array<byte> palette;
+ byte endOfData;
+ };
+
+ bool decodeTitleScreen(uint titleIndex, NESTitleScreen &outTitle);
+ bool readTitle2SparkleChr(Common::Array<byte> &outChr);
+ bool readTitle2SparklePalette(Common::Array<byte> &outPalette);
+
+
private:
Common::SeekableReadStream *_stream;
ROMset _ROMset;
@@ -73,6 +102,7 @@ private:
uint16 extractResource(Common::WriteStream *out, const Resource *res, ResType type);
byte fileReadByte();
+ void decodeTitleRLE(Common::Array<byte> &dst, uint32 expectedSize);
uint16 fileReadUint16LE();
public:
diff --git a/engines/scumm/players/player_nes.cpp b/engines/scumm/players/player_nes.cpp
index d145c2fa083..9de4160c38a 100644
--- a/engines/scumm/players/player_nes.cpp
+++ b/engines/scumm/players/player_nes.cpp
@@ -22,6 +22,8 @@
#ifndef DISABLE_NES_APU
#include "engines/engine.h"
+#include "common/array.h"
+#include "common/system.h"
#include "scumm/players/player_nes.h"
#include "scumm/scumm.h"
#include "audio/mixer.h"
@@ -603,6 +605,8 @@ Player_NES::Player_NES(ScummEngine *scumm, Audio::Mixer *mixer) {
_slot[i].data = nullptr;
}
+ _title2SfxActive = false;
+
for (i = 0; i < NUMCHANS; i++) {
_mchan[i].command = 0;
_mchan[i].framedelay = 0;
@@ -644,6 +648,7 @@ int Player_NES::readBuffer(int16 *buffer, const int numSamples) {
return numSamples;
}
void Player_NES::stopAllSounds() {
+ _title2SfxActive = false;
for (int i = 0; i < NUMSLOTS; i++) {
_slot[i].framesleft = 0;
_slot[i].type = 0;
@@ -654,6 +659,34 @@ void Player_NES::stopAllSounds() {
checkSilenceChannels(0);
}
+
+void Player_NES::startTitleTwinkleGroup(const byte twinkleSteps4x6[4][6]) {
+ _title2SfxActive = true;
+ _titleSfxBuf.clear();
+ _titleSfxBuf.reserve(29);
+
+ for (int stepIndex = 0; stepIndex < 4; ++stepIndex) {
+ _titleSfxBuf.push_back(twinkleSteps4x6[stepIndex][0]);
+ _titleSfxBuf.push_back(twinkleSteps4x6[stepIndex][1]);
+ _titleSfxBuf.push_back(twinkleSteps4x6[stepIndex][2]);
+ _titleSfxBuf.push_back(twinkleSteps4x6[stepIndex][3]);
+ _titleSfxBuf.push_back(twinkleSteps4x6[stepIndex][4]);
+ _titleSfxBuf.push_back(0x10);
+ _titleSfxBuf.push_back(twinkleSteps4x6[stepIndex][5]);
+ }
+
+ _titleSfxBuf.push_back(0xFF);
+
+ const int slotIndex = 0;
+ _slot[slotIndex].type = 10;
+ _slot[slotIndex].id = -2;
+ _slot[slotIndex].data = _titleSfxBuf.data();
+ _slot[slotIndex].offset = 0;
+ _slot[slotIndex].framesleft = 1;
+ checkSilenceChannels(slotIndex);
+ isSFXplaying = true;
+}
+
void Player_NES::stopSound(int nr) {
if (nr == -1)
return;
@@ -722,6 +755,8 @@ void Player_NES::sound_play() {
}
void Player_NES::playSFX (int nr) {
+ const bool isTitle2Chirp = (nr == 0 && _slot[nr].id == -2 && _title2SfxActive);
+
if (--_slot[nr].framesleft)
return;
@@ -741,15 +776,26 @@ void Player_NES::playSFX (int nr) {
_slot[nr].id = -1;
_slot[nr].type = 0;
isSFXplaying = false;
- APU_writeControl(0);
+
+ if (!isTitle2Chirp)
+ APU_writeControl(0);
if (!nr && _slot[1].framesleft) {
_slot[1].framesleft = 1;
isSFXplaying = true;
}
+
+ if (isTitle2Chirp)
+ _title2SfxActive = false;
+
return;
} else {
- _slot[nr].framesleft = _slot[nr].data[_slot[nr].offset++];
+ if (isTitle2Chirp) {
+ const byte frames = _slot[nr].data[_slot[nr].offset++];
+ _slot[nr].framesleft = frames;
+ } else {
+ _slot[nr].framesleft = _slot[nr].data[_slot[nr].offset++];
+ }
return;
}
}
diff --git a/engines/scumm/players/player_nes.h b/engines/scumm/players/player_nes.h
index 80beacd0ad4..30f1012f698 100644
--- a/engines/scumm/players/player_nes.h
+++ b/engines/scumm/players/player_nes.h
@@ -23,6 +23,7 @@
#define SCUMM_PLAYERS_PLAYER_NES_H
#include "common/scummsys.h"
+#include "common/array.h"
#include "scumm/music.h"
#include "audio/audiostream.h"
#include "audio/mixer.h"
@@ -52,6 +53,8 @@ public:
void stopAllSounds() override;
int getSoundStatus(int sound) const override;
+ void startTitleTwinkleGroup(const byte twinkleSteps4x6[4][6]);
+
// AudioStream API
int readBuffer(int16 *buffer, const int numSamples) override;
bool isStereo() const override { return false; }
@@ -87,6 +90,9 @@ private:
int offset;
} _slot[NUMSLOTS];
+ Common::Array<byte> _titleSfxBuf;
+ bool _title2SfxActive;
+
struct mchan {
int command;
int framedelay;
diff --git a/engines/scumm/scumm.cpp b/engines/scumm/scumm.cpp
index 4ad63d89546..9202ff34db8 100644
--- a/engines/scumm/scumm.cpp
+++ b/engines/scumm/scumm.cpp
@@ -2630,6 +2630,9 @@ Common::Error ScummEngine::go() {
// If requested, load a save game instead of running the boot script
if (_saveLoadFlag != 2 || !loadState(_saveLoadSlot, _saveTemporaryState)) {
_saveLoadFlag = 0;
+ if (_game.platform == Common::kPlatformNES && _game.id == GID_MANIAC && !(_game.features & GF_DEMO)) {
+ playNESTitleScreens();
+ }
runBootscript();
} else {
_loadFromLauncher = true; // The only purpose of this is triggering the IQ points update for INDY3/4
diff --git a/engines/scumm/scumm.h b/engines/scumm/scumm.h
index 2458f156a3d..99e7660fc23 100644
--- a/engines/scumm/scumm.h
+++ b/engines/scumm/scumm.h
@@ -1348,6 +1348,7 @@ protected:
void initCycl(const byte *ptr); // Color cycle
void decodeNESBaseTiles();
+ void playNESTitleScreens();
void drawObject(int obj, int scrollType);
void drawRoomObjects(int arg);
More information about the Scummvm-git-logs
mailing list