[Scummvm-git-logs] scummvm master -> c197d5bb564c01d24f8861f8560cdbb6a4969a69
sev-
noreply at scummvm.org
Tue Feb 10 20:29:44 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:
c197d5bb56 SCUMM: Attempt to fix compilation with disabled SCUMM engine
Commit: c197d5bb564c01d24f8861f8560cdbb6a4969a69
https://github.com/scummvm/scummvm/commit/c197d5bb564c01d24f8861f8560cdbb6a4969a69
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2026-02-10T21:29:29+01:00
Commit Message:
SCUMM: Attempt to fix compilation with disabled SCUMM engine
It might not work properly, as I weirdly split ScummNESFile
class into two parts, but it works well with optimized linker.
Changed paths:
A engines/scumm/file_nes_titles.cpp
engines/scumm/file_nes.cpp
engines/scumm/module.mk
diff --git a/engines/scumm/file_nes.cpp b/engines/scumm/file_nes.cpp
index 2aec6bc84fc..8f3d212be27 100644
--- a/engines/scumm/file_nes.cpp
+++ b/engines/scumm/file_nes.cpp
@@ -20,10 +20,6 @@
*/
#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"
@@ -901,69 +897,6 @@ 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 } };
@@ -1005,33 +938,6 @@ 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();
@@ -1185,83 +1091,6 @@ 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;
@@ -1632,340 +1461,4 @@ 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_titles.cpp b/engines/scumm/file_nes_titles.cpp
new file mode 100644
index 00000000000..4df04778e56
--- /dev/null
+++ b/engines/scumm/file_nes_titles.cpp
@@ -0,0 +1,544 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "scumm/file_nes.h"
+#include "scumm/scumm.h"
+#include "scumm/players/player_nes.h"
+
+namespace Scumm {
+
+#pragma mark -
+#pragma mark --- ScummNESFile Titles ---
+#pragma mark -
+
+
+struct ScummNESFile::Resource {
+ uint32 offset;
+ uint16 length;
+};
+
+struct ScummNESFile::ResourceGroup {
+ ResType type;
+ const Resource *langs[ScummNESFile::kROMsetNum];
+};
+
+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,
+ }
+};
+
+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);
+ }
+ }
+ }
+}
+
+
+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;
+}
+
+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/module.mk b/engines/scumm/module.mk
index de3c730f912..c6a55772478 100644
--- a/engines/scumm/module.mk
+++ b/engines/scumm/module.mk
@@ -17,6 +17,7 @@ MODULE_OBJS := \
file.o \
file_engine.o \
file_nes.o \
+ file_nes_titles.o \
gfx_gui.o \
gfx_mac.o \
gfx_towns.o \
More information about the Scummvm-git-logs
mailing list