[Scummvm-git-logs] scummvm master -> d88ede37a6cb6e350a86fe4670153b76143fd246
neuromancer
noreply at scummvm.org
Tue Sep 8 11:35:04 UTC 2026
This automated email contains information about 13 new commits which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .
Summary:
6732084fdf FREESCAPE: avoid invisible faces in 3dck
107a1a5211 FREESCAPE: correctly process inputs events in 3dck
774e43c325 FREESCAPE: added some 3dck games/demos for detection
c5c0eba8e2 FREESCAPE: adjusted delay timing in 3dck
3ac67c9b30 FREESCAPE: initial implementation of sound for 3dck games
fa58919982 FREESCAPE: fixes to run Desert Maze (3dck game)
d36c070008 FREESCAPE: corrected palette in CPC 3dck games
21f0ea581d FREESCAPE: input/movement fixes for 3dck games
3dca7164ad FREESCAPE: implementation of 8bit sounds for 3dck games
3cb304bda7 FREESCAPE: initial support for zx spectrum 3dck games
dc4b8e3436 FREESCAPE: complete implementation of Colour opcode for zx spectrum 3dck games
28c76906af FREESCAPE: initial code to support c64 3dck games
d88ede37a6 COMMON: moved unp64 to common/compression
Commit: 6732084fdffafa4fc3a62e382d088d100c5ca91f
https://github.com/scummvm/scummvm/commit/6732084fdffafa4fc3a62e382d088d100c5ca91f
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-09-08T13:33:08+02:00
Commit Message:
FREESCAPE: avoid invisible faces in 3dck
Changed paths:
engines/freescape/games/3dck/3dck.cpp
diff --git a/engines/freescape/games/3dck/3dck.cpp b/engines/freescape/games/3dck/3dck.cpp
index 24c178415e2..e736a3e0caa 100644
--- a/engines/freescape/games/3dck/3dck.cpp
+++ b/engines/freescape/games/3dck/3dck.cpp
@@ -332,6 +332,12 @@ Object *KitEngine::loadObject(Common::SeekableReadStream &file, ObjectData &data
colors->push_back(first);
colors->push_back(second);
}
+ if (GeometricObject::isPyramid(type)) {
+ // Kit stores opposite sides together; the renderer walks around the base.
+ const byte sides[] = {(*colors)[2], (*colors)[0], (*colors)[3], (*colors)[1]};
+ for (uint i = 0; i < ARRAYSIZE(sides); i++)
+ (*colors)[i] = sides[i];
+ }
int ordinateCount = GeometricObject::numberOfOrdinatesForType(type);
if (ordinateCount) {
requireBytes(payload, 2 * ordinateCount);
Commit: 107a1a521197a26ef2c0621b20c91e1340f6b7a4
https://github.com/scummvm/scummvm/commit/107a1a521197a26ef2c0621b20c91e1340f6b7a4
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-09-08T13:33:08+02:00
Commit Message:
FREESCAPE: correctly process inputs events in 3dck
Changed paths:
engines/freescape/games/3dck/3dck.cpp
engines/freescape/games/3dck/3dck.h
engines/freescape/games/3dck/ui.cpp
engines/freescape/language/execution_3dck16.cpp
diff --git a/engines/freescape/games/3dck/3dck.cpp b/engines/freescape/games/3dck/3dck.cpp
index e736a3e0caa..96e601579ee 100644
--- a/engines/freescape/games/3dck/3dck.cpp
+++ b/engines/freescape/games/3dck/3dck.cpp
@@ -416,6 +416,7 @@ void KitEngine::gotoArea(uint16 areaID, int entranceID) {
_gfx->_scale = _currentArea->getScale();
_gotoExecuted = true;
_delayedShootObject = nullptr;
+ _pendingInteractions = 0;
_timerTicks = 0;
_scriptSurface.fillRect(_viewArea, 0);
resetInput();
@@ -429,6 +430,47 @@ void KitEngine::checkIfStillInArea() {
_position.z() = CLIP(_position.z(), 0.0f, limit);
}
+void KitEngine::updatePlayerMovement(float deltaTime) {
+ if (_scriptFrameActive || _initialScriptPending)
+ return;
+ updateInteractions();
+ if (!_eventManager->isActionActive(kActionMoveUp))
+ _moveForward = false;
+ if (!_eventManager->isActionActive(kActionMoveDown))
+ _moveBackward = false;
+ if (!_eventManager->isActionActive(kActionMoveLeft))
+ _strafeLeft = false;
+ if (!_eventManager->isActionActive(kActionMoveRight))
+ _strafeRight = false;
+
+ Math::Vector3d front = _flyMode ? _cameraFront : directionToVector(0, _yaw, false);
+ Math::Vector3d movement;
+ if (_moveForward)
+ movement += front;
+ if (_moveBackward)
+ movement -= front;
+ if (_strafeLeft)
+ movement += _cameraRight;
+ if (_strafeRight)
+ movement -= _cameraRight;
+ if (movement.length() == 0)
+ return;
+ movement.normalize();
+
+ // The runner advances one full step per completed script frame.
+ float height = _position.y();
+ resolveCollisions(_position + movement * _playerSteps[_playerStepIndex]);
+ checkIfStillInArea();
+ _lastPosition = _position;
+ _gotoExecuted = false;
+ clearGameBit(31);
+ if (_hasFallen) {
+ _kitVariables[10] += MAX<int>(0, height - _position.y() - _maxFallingDistance);
+ _hasFallen = false;
+ _avoidRenderingFrames = 0;
+ }
+}
+
bool KitEngine::checkIfGameEnded() {
return false;
}
diff --git a/engines/freescape/games/3dck/3dck.h b/engines/freescape/games/3dck/3dck.h
index e09933f47be..ee5b034028a 100644
--- a/engines/freescape/games/3dck/3dck.h
+++ b/engines/freescape/games/3dck/3dck.h
@@ -141,6 +141,7 @@ private:
void collectObjects(uint16 area, uint16 id, Common::Array<uint16> &objects);
void setObjectStatus(uint16 area, uint16 id, Token::Type operation);
bool moveAnimation(ScriptState &script, Math::Vector3d movement, bool absolute);
+ void updateInteractions();
void interact(bool shot);
void printMessage(uint16 indicator, const Common::String &message);
void updateIndicators();
@@ -161,6 +162,7 @@ private:
bool _timerTriggered = false, _initialScriptPending = false;
bool _scriptFrameActive = false, _scriptDelayed = false;
bool _soundWarning = false;
+ byte _pendingInteractions = 0, _shootCooldown = 0, _activateCooldown = 0;
uint _scriptQueueIndex = 0;
Common::Array<ScriptEntry> _scriptQueue;
Common::Array<ScriptState *> _suspendedScripts;
diff --git a/engines/freescape/games/3dck/ui.cpp b/engines/freescape/games/3dck/ui.cpp
index ce116adce4f..c8ac0a951ed 100644
--- a/engines/freescape/games/3dck/ui.cpp
+++ b/engines/freescape/games/3dck/ui.cpp
@@ -147,8 +147,8 @@ bool KitEngine::handleInput(const Common::Event &event) {
_kitVariables[15] = key;
}
if (event.customType == kActionShoot || event.customType == kActionActivate) {
- if (!_scriptFrameActive)
- interact(event.customType == kActionShoot);
+ if (!event.kbdRepeat)
+ _pendingInteractions |= event.customType == kActionShoot ? 1 : 2;
return true;
}
// Track held movement keys during DELAY; movement itself waits.
@@ -169,16 +169,51 @@ bool KitEngine::handleInput(const Common::Event &event) {
_kitVariables[16] |= event.type == Common::EVENT_LBUTTONDOWN ? 1 : 2;
_kitVariables[17] = mouse.x;
_kitVariables[18] = mouse.y;
- if (!_scriptFrameActive)
- interact(event.type == Common::EVENT_LBUTTONDOWN);
+ if (_viewArea.contains(_crossairPosition))
+ _pendingInteractions |= event.type == Common::EVENT_LBUTTONDOWN ? 1 : 2;
+ return true;
+ } else if (event.type == Common::EVENT_MOUSEMOVE) {
+ if (_hasFallen || _playerWasCrushed)
+ return true;
+ if (_shootMode)
+ _crossairPosition = getNormalizedPosition(event.mouse);
+ else {
+ // Relative mouse input keeps queued button events intact.
+ int y = _invertY ? -event.relMouse.y : event.relMouse.y;
+ rotate(event.relMouse.x * _mouseSensitivity, y * _mouseSensitivity, 0);
+ }
return true;
}
return false;
}
+void KitEngine::updateInteractions() {
+ int buttons = g_system->getEventManager()->getButtonState();
+ bool shot = (buttons & Common::EventManager::LBUTTON) || _eventManager->isActionActive(kActionShoot);
+ bool activated = (buttons & Common::EventManager::RBUTTON) || _eventManager->isActionActive(kActionActivate);
+ if (_shootCooldown)
+ _shootCooldown--;
+ if (_activateCooldown)
+ _activateCooldown--;
+
+ // The DOS runner uses bit 3 to require a button release between shots.
+ if (!_shootCooldown && ((_pendingInteractions & 1) || (shot && !(_kitVariables[20] & 8)))) {
+ _pendingInteractions &= ~1;
+ interact(true);
+ }
+ if (!_activateCooldown && ((_pendingInteractions & 2) || activated)) {
+ _pendingInteractions &= ~2;
+ interact(false);
+ }
+}
+
void KitEngine::interact(bool shot) {
if (!_viewArea.contains(_crossairPosition) || (shot && !(_kitVariables[20] & 1)))
return;
+ if (shot)
+ _shootCooldown = 2;
+ else
+ _activateCooldown = 2;
float x = 2.0f * (_crossairPosition.x - _viewArea.left) / _viewArea.width() - 1;
float y = 1 - 2.0f * (_crossairPosition.y - _viewArea.top) / _viewArea.height();
float projection = tan(Math::deg2rad(_fieldOfView) / 2);
diff --git a/engines/freescape/language/execution_3dck16.cpp b/engines/freescape/language/execution_3dck16.cpp
index 271aa4ad2c6..7233405abf0 100644
--- a/engines/freescape/language/execution_3dck16.cpp
+++ b/engines/freescape/language/execution_3dck16.cpp
@@ -35,6 +35,7 @@ void KitEngine::resetScripts() {
_lastScriptTick = _ticks;
_scriptFrameActive = _scriptDelayed = false;
_initialScriptPending = _initialCondition != 0;
+ _pendingInteractions = _shootCooldown = _activateCooldown = 0;
_scriptQueue.clear();
_suspendedScripts.clear();
_scriptSurface.fillRect(_fullscreenViewArea, 0);
@@ -801,16 +802,4 @@ bool KitEngine::moveAnimation(ScriptState &script, Math::Vector3d movement, bool
return unobstructed;
}
-void KitEngine::updatePlayerMovement(float deltaTime) {
- if (_scriptFrameActive)
- return;
- float height = _position.y();
- FreescapeEngine::updatePlayerMovement(deltaTime);
- if (_hasFallen) {
- _kitVariables[10] += MAX<int>(0, height - _position.y() - _maxFallingDistance);
- _hasFallen = false;
- _avoidRenderingFrames = 0;
- }
-}
-
} // namespace Freescape
Commit: 774e43c325604af321c76e7a332827b9cb11f2a1
https://github.com/scummvm/scummvm/commit/774e43c325604af321c76e7a332827b9cb11f2a1
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-09-08T13:33:08+02:00
Commit Message:
FREESCAPE: added some 3dck games/demos for detection
Changed paths:
engines/freescape/detection.cpp
engines/freescape/games/3dck/3dck.cpp
engines/freescape/language/detokeniser_3dck16.cpp
diff --git a/engines/freescape/detection.cpp b/engines/freescape/detection.cpp
index cbd4f199658..ad80610c043 100644
--- a/engines/freescape/detection.cpp
+++ b/engines/freescape/detection.cpp
@@ -1228,6 +1228,24 @@ const ADGameDescription gameDescriptions[] = {
ADGF_UNSUPPORTED,
GUIO1(GUIO_NOMIDI)
},
+ {
+ "3dkit",
+ "Christmas Tree Demo",
+ AD_ENTRY1s("CHRISTMA.RUN", "106b8f0dd0384d3138a8f0f62caef392", 69910),
+ Common::EN_ANY,
+ Common::kPlatformDOS,
+ ADGF_UNSTABLE | ADGF_DEMO,
+ GUIO2(GUIO_NOMIDI, GUIO_RENDERVGA)
+ },
+ {
+ "3dkit",
+ "Desert Maze",
+ AD_ENTRY1s("DESMAZE.RUN", "5cfab15e53d77029bdb02c87acae3186", 99212),
+ Common::EN_ANY,
+ Common::kPlatformDOS,
+ ADGF_UNSTABLE,
+ GUIO2(GUIO_NOMIDI, GUIO_RENDERVGA)
+ },
{
"3dkit",
"Desert Sands v1.3",
@@ -1246,6 +1264,15 @@ const ADGameDescription gameDescriptions[] = {
ADGF_UNSUPPORTED,
GUIO1(GUIO_NOMIDI)
},
+ {
+ "3dkit",
+ "Easy? or Not?",
+ AD_ENTRY1s("EASY.RUN", "28e4c38ff4d06443433a02f857069fa4", 93108),
+ Common::EN_ANY,
+ Common::kPlatformDOS,
+ ADGF_UNSTABLE,
+ GUIO2(GUIO_NOMIDI, GUIO_RENDERVGA)
+ },
{
"3dkit",
"Eerie Estates",
@@ -1255,6 +1282,15 @@ const ADGameDescription gameDescriptions[] = {
ADGF_UNSUPPORTED,
GUIO1(GUIO_NOMIDI)
},
+ {
+ "3dkit",
+ "Funnyrace",
+ AD_ENTRY1s("FUNRACE.RUN", "a8643c6eb251802a9e27612d417592be", 99098),
+ Common::EN_ANY,
+ Common::kPlatformDOS,
+ ADGF_UNSTABLE,
+ GUIO2(GUIO_NOMIDI, GUIO_RENDERVGA)
+ },
{
"3dkit",
"Gaynor Ave. House",
@@ -1300,6 +1336,15 @@ const ADGameDescription gameDescriptions[] = {
ADGF_UNSUPPORTED,
GUIO1(GUIO_NOMIDI)
},
+ {
+ "3dkit",
+ "Mountain Adventure",
+ AD_ENTRY1s("MOUNTAIN.RUN", "ec3bb57fe23b1a6785e870af1baa74d7", 129106),
+ Common::EN_ANY,
+ Common::kPlatformDOS,
+ ADGF_UNSTABLE,
+ GUIO2(GUIO_NOMIDI, GUIO_RENDERVGA)
+ },
{
"3dkit",
"Rubber Room v1.0",
@@ -1318,6 +1363,15 @@ const ADGameDescription gameDescriptions[] = {
ADGF_UNSUPPORTED,
GUIO1(GUIO_NOMIDI)
},
+ {
+ "3dkit",
+ "Tunnel Adventure",
+ AD_ENTRY1s("TUNNEL.RUN", "414738d92decdfee028fa0d04679c23d", 98918),
+ Common::EN_ANY,
+ Common::kPlatformDOS,
+ ADGF_UNSTABLE,
+ GUIO2(GUIO_NOMIDI, GUIO_RENDERVGA)
+ },
{
"3dkit",
"Virtual Reality Studio Demo",
diff --git a/engines/freescape/games/3dck/3dck.cpp b/engines/freescape/games/3dck/3dck.cpp
index 96e601579ee..c7f8ccc1708 100644
--- a/engines/freescape/games/3dck/3dck.cpp
+++ b/engines/freescape/games/3dck/3dck.cpp
@@ -30,6 +30,7 @@ namespace Freescape {
enum {
kKitAnimatorType = 16,
+ kKitDisabledType = 0x7f,
kKitInitiallyInvisible = 0x04,
kKitMovable = 0x80
};
@@ -178,7 +179,8 @@ void KitEngine::loadWorld(Common::SeekableReadStream &file) {
_angleRotationIndex = 0;
file.skip(2);
- uint32 indicatorOffset = 2 * file.readUint16BE();
+ // DOS word offsets wrap at 64 KiB.
+ uint32 indicatorOffset = uint16(2 * file.readUint16BE());
uint16 indicatorCount = file.readUint16BE();
_initialCondition = file.readUint16BE();
if (indicatorOffset > uint32(file.size()) || (!indicatorOffset && indicatorCount))
@@ -195,10 +197,14 @@ void KitEngine::loadWorld(Common::SeekableReadStream &file) {
requireBytes(file, uint32(areaCount) * 4);
Common::Array<uint32> areaOffsets;
for (uint i = 0; i < areaCount; i++) {
- uint32 offset = file.readUint32BE();
- if (offset >= areasEnd / 2)
+ uint32 offset = uint16(2 * file.readUint32BE());
+ if (indicatorCount && offset >= indicatorOffset && offset < indicatorOffset + 2 * _indicatorData.size()) {
+ warning("Ignoring stale 3D Construction Kit area offset %u into indicator data", offset);
+ continue;
+ }
+ if (offset >= areasEnd)
error("Invalid 3D Construction Kit area offset");
- areaOffsets.push_back(2 * offset);
+ areaOffsets.push_back(offset);
}
Common::sort(areaOffsets.begin(), areaOffsets.end());
if (areaOffsets.empty() || globalConditions < uint32(file.pos()) ||
@@ -266,6 +272,8 @@ Area *KitEngine::loadArea(Common::SeekableReadStream &file) {
for (uint i = 0; i < objectCount; i++) {
ObjectData record;
Object *obj = loadObject(objectData, record);
+ if (record.type == kKitDisabledType)
+ continue;
if (data.objects.contains(record.id))
error("Duplicate 3D Construction Kit object %u in area %u", record.id, id);
data.objects[record.id] = record;
@@ -280,12 +288,9 @@ Area *KitEngine::loadArea(Common::SeekableReadStream &file) {
error("Duplicate 3D Construction Kit object %u", obj->getObjectID());
(*map)[obj->getObjectID()] = obj;
}
- if (objectData.pos() != objectData.size())
- error("Invalid 3D Construction Kit object count");
+ // Unused records can remain between the counted objects and conditions.
file.seek(conditions);
data.conditions = loadConditions(file);
- if (file.pos() != file.size())
- error("Invalid 3D Construction Kit area condition size");
debugC(1, kFreescapeDebugParser, "3DCK area %u: %u objects, %u conditions", id, objectCount, data.conditions.size());
Area *area = new Area(id, flags, objects, entrances, false);
@@ -315,6 +320,10 @@ Object *KitEngine::loadObject(Common::SeekableReadStream &file, ObjectData &data
error("Invalid 3D Construction Kit object size");
requireBytes(file, 2 * (words - 10));
uint32 end = file.pos() + 2 * (words - 10);
+ if (data.type == kKitDisabledType) {
+ file.seek(end);
+ return nullptr;
+ }
Common::SeekableSubReadStream payload(&file, file.pos(), end);
if (data.type > kKitAnimatorType)
error("Unsupported 3D Construction Kit object %u (type %u)", data.id, data.type);
diff --git a/engines/freescape/language/detokeniser_3dck16.cpp b/engines/freescape/language/detokeniser_3dck16.cpp
index 964f90b8fdf..dfae0771a4b 100644
--- a/engines/freescape/language/detokeniser_3dck16.cpp
+++ b/engines/freescape/language/detokeniser_3dck16.cpp
@@ -138,8 +138,14 @@ Common::String detokeniseKit16Condition(const Common::Array<byte> &tokenisedCond
loops++;
break;
case Token::AGAIN:
- if (--loops < 0)
+ if (loops) {
+ loops--;
+ } else if (!READ_BE_UINT16(&tokenisedCondition[bytePointer + 2])) {
+ // An orphaned AGAIN with no jump offset has no effect in RUNVGA.
+ instruction = FCLInstruction(Token::NOP);
+ } else {
error("16-bit FCL AGAIN without LOOP");
+ }
break;
default:
break;
Commit: c5c0eba8e278fad0b6a7fce500a7321647397140
https://github.com/scummvm/scummvm/commit/c5c0eba8e278fad0b6a7fce500a7321647397140
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-09-08T13:33:08+02:00
Commit Message:
FREESCAPE: adjusted delay timing in 3dck
Changed paths:
engines/freescape/games/3dck/3dck.cpp
engines/freescape/language/execution.h
engines/freescape/language/execution_3dck16.cpp
engines/freescape/language/execution_3dck16.h
engines/freescape/language/execution_freescape.cpp
diff --git a/engines/freescape/games/3dck/3dck.cpp b/engines/freescape/games/3dck/3dck.cpp
index c7f8ccc1708..06c1d36d74a 100644
--- a/engines/freescape/games/3dck/3dck.cpp
+++ b/engines/freescape/games/3dck/3dck.cpp
@@ -127,6 +127,8 @@ void KitEngine::loadAssets() {
_palette[i] = (component << 2) | (component >> 4);
}
_border->setPalette(_palette, 0, 256);
+ // Preserve the full VGA palette during shared border processing.
+ _border->convertToInPlace(_gfx->_texturePixelFormat);
_gfx->_palette = _palette;
_gfx->_keyColor = 0;
_scriptSurface.create(_screenW, _screenH, _gfx->_texturePixelFormat);
diff --git a/engines/freescape/language/execution.h b/engines/freescape/language/execution.h
index fc44a51c1ae..1e853e16c22 100644
--- a/engines/freescape/language/execution.h
+++ b/engines/freescape/language/execution.h
@@ -26,6 +26,9 @@
namespace Freescape {
+// Default redraw cadence in 50 Hz ticks.
+static const uint32 kFCLRedrawTicks = 8;
+
enum FCLExecutionResult {
kFCLFinished,
kFCLYielded, // Resume this script after the other scripts in the frame.
diff --git a/engines/freescape/language/execution_3dck16.cpp b/engines/freescape/language/execution_3dck16.cpp
index 7233405abf0..01725a257da 100644
--- a/engines/freescape/language/execution_3dck16.cpp
+++ b/engines/freescape/language/execution_3dck16.cpp
@@ -75,6 +75,7 @@ void KitEngine::resetScripts() {
void KitEngine::startScript(ScriptState &script) {
script.code = script.source;
script.ip = script.restart = 0;
+ script.resumeTick = 0;
script.loops.clear();
script.predicate = FCLPredicateState(true);
script.events = script.object ? script.object->flags & 0x38 : 0;
@@ -205,6 +206,10 @@ void KitEngine::updateScripts() {
_scriptQueueIndex++;
continue;
}
+ if (animator && script.running && int32(_scriptTicks - script.resumeTick) < 0) {
+ _scriptQueueIndex++;
+ continue;
+ }
if (!script.running)
startScript(script);
entry.resume = true;
@@ -218,7 +223,9 @@ void KitEngine::updateScripts() {
script.running = false;
if (animator)
object->flags |= 2;
- } else if (!animator) {
+ } else if (animator) {
+ script.resumeTick = _scriptTicks + kFCLRedrawTicks;
+ } else {
_suspendedScripts.push_back(&script);
}
_scriptQueueIndex++;
diff --git a/engines/freescape/language/execution_3dck16.h b/engines/freescape/language/execution_3dck16.h
index 0e9c92a9367..5e62e6c0b4e 100644
--- a/engines/freescape/language/execution_3dck16.h
+++ b/engines/freescape/language/execution_3dck16.h
@@ -36,6 +36,7 @@ struct FCLKit16Loop {
struct FCLKit16ExecutionState : FCLExecutionFrame {
const FCLInstructionVector *source = nullptr;
uint32 restart = 0;
+ uint32 resumeTick = 0;
Common::HashMap<uint32, FCLKit16Loop> loops;
bool running = false;
FCLPredicateState predicate = FCLPredicateState(true);
diff --git a/engines/freescape/language/execution_freescape.cpp b/engines/freescape/language/execution_freescape.cpp
index 7ef56674c3c..ccccf44576c 100644
--- a/engines/freescape/language/execution_freescape.cpp
+++ b/engines/freescape/language/execution_freescape.cpp
@@ -23,6 +23,7 @@
// available at https://github.com/TomHarte/Phantasma/ (MIT)
#include "freescape/freescape.h"
+#include "freescape/language/execution.h"
#include "freescape/language/variables.h"
#include "freescape/sweepAABB.h"
@@ -298,7 +299,7 @@ bool FreescapeEngine::executeCode(FCLInstructionVector &code, bool shot, bool co
void FreescapeEngine::executeRedraw(FCLInstruction &instruction) {
debugC(1, kFreescapeDebugCode, "Redrawing screen");
- uint32 delay = (100 / 15) + 1;
+ uint32 delay = kFCLRedrawTicks - 1; // waitInLoop includes its final tick.
if (isEclipse2() && _currentArea->getAreaID() == _startArea && _gameStateControl == kFreescapeGameStateStart)
delay = delay * 10;
Commit: 3ac67c9b3013dc0f8451b23d5e467b6a93469a38
https://github.com/scummvm/scummvm/commit/3ac67c9b3013dc0f8451b23d5e467b6a93469a38
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-09-08T13:33:08+02:00
Commit Message:
FREESCAPE: initial implementation of sound for 3dck games
Changed paths:
A engines/freescape/sound/3dck.cpp
A engines/freescape/sound/3dck.h
A engines/freescape/sound/3dck_adlib.cpp
engines/freescape/games/3dck/3dck.cpp
engines/freescape/games/3dck/3dck.h
engines/freescape/games/3dck/ui.cpp
engines/freescape/language/execution_3dck16.cpp
engines/freescape/module.mk
diff --git a/engines/freescape/games/3dck/3dck.cpp b/engines/freescape/games/3dck/3dck.cpp
index 06c1d36d74a..d8811d6f550 100644
--- a/engines/freescape/games/3dck/3dck.cpp
+++ b/engines/freescape/games/3dck/3dck.cpp
@@ -133,6 +133,9 @@ void KitEngine::loadAssets() {
_gfx->_keyColor = 0;
_scriptSurface.create(_screenW, _screenH, _gfx->_texturePixelFormat);
_scriptSurface.fillRect(_fullscreenViewArea, 0);
+ uint16 soundSize = readBlockSize(file);
+ Common::SeekableSubReadStream sounds(&file, file.pos(), file.pos() + soundSize);
+ loadSounds(sounds);
}
void KitEngine::loadWorld(Common::SeekableReadStream &file) {
@@ -472,9 +475,18 @@ void KitEngine::updatePlayerMovement(float deltaTime) {
float height = _position.y();
resolveCollisions(_position + movement * _playerSteps[_playerStepIndex]);
checkIfStillInArea();
+ bool blocked = (_position - _lastPosition).length() < 1;
_lastPosition = _position;
_gotoExecuted = false;
clearGameBit(31);
+ if (_hasFallen)
+ _pendingSound = 7;
+ else if (!_flyMode && _position.y() > height)
+ _pendingSound = 5;
+ else if (!_flyMode && _position.y() < height)
+ _pendingSound = 6;
+ else if (blocked)
+ _pendingSound = 2;
if (_hasFallen) {
_kitVariables[10] += MAX<int>(0, height - _position.y() - _maxFallingDistance);
_hasFallen = false;
diff --git a/engines/freescape/games/3dck/3dck.h b/engines/freescape/games/3dck/3dck.h
index ee5b034028a..49695fee625 100644
--- a/engines/freescape/games/3dck/3dck.h
+++ b/engines/freescape/games/3dck/3dck.h
@@ -97,6 +97,8 @@ private:
};
void loadWorld(Common::SeekableReadStream &file);
+ void loadSounds(Common::SeekableReadStream &file);
+ void playPendingSound();
Area *loadArea(Common::SeekableReadStream &file);
Object *loadObject(Common::SeekableReadStream &file, ObjectData &data);
Common::Array<ConditionData> loadConditions(Common::SeekableReadStream &file);
@@ -161,7 +163,7 @@ private:
int _lastScriptTick = 0;
bool _timerTriggered = false, _initialScriptPending = false;
bool _scriptFrameActive = false, _scriptDelayed = false;
- bool _soundWarning = false;
+ int _pendingSound = -1;
byte _pendingInteractions = 0, _shootCooldown = 0, _activateCooldown = 0;
uint _scriptQueueIndex = 0;
Common::Array<ScriptEntry> _scriptQueue;
diff --git a/engines/freescape/games/3dck/ui.cpp b/engines/freescape/games/3dck/ui.cpp
index c8ac0a951ed..5f757430745 100644
--- a/engines/freescape/games/3dck/ui.cpp
+++ b/engines/freescape/games/3dck/ui.cpp
@@ -107,6 +107,8 @@ void KitEngine::drawUI() {
drawFullscreenSurface(_scriptSurface.surfacePtr());
_gfx->setViewport(_fullscreenViewArea);
_gfx->renderCrossair(_crossairPosition);
+ if (!_scriptFrameActive)
+ playPendingSound();
}
bool KitEngine::handleInput(const Common::Event &event) {
@@ -224,6 +226,8 @@ void KitEngine::interact(bool shot) {
_kitVariables[21]++;
if (_kitVariables[20] & 2)
_shootingFrames = 3;
+ if (_kitVariables[20] & 4)
+ _pendingSound = 4;
}
if (!object || !object->isGeometric())
return;
diff --git a/engines/freescape/language/execution_3dck16.cpp b/engines/freescape/language/execution_3dck16.cpp
index 01725a257da..5599a6521ee 100644
--- a/engines/freescape/language/execution_3dck16.cpp
+++ b/engines/freescape/language/execution_3dck16.cpp
@@ -27,6 +27,8 @@
namespace Freescape {
void KitEngine::resetScripts() {
+ stopAllSounds();
+ _pendingSound = -1;
// V255 survives ENDGAME, as in the original runner.
memset(_kitVariables, 0, 255 * sizeof(_kitVariables[0]));
_changedVariables = 0;
@@ -398,6 +400,7 @@ FCLExecutionResult KitEngine::executeCode(ScriptState &script, uint &budget) {
break;
case Token::REDRAW:
_scriptSurface.fillRect(_viewArea, 0);
+ playPendingSound();
return animator ? kFCLYielded : kFCLPaused;
case Token::LOOP:
executeLoop(instruction, script);
@@ -731,10 +734,11 @@ void KitEngine::executeMove(const FCLInstruction &instruction, ScriptState &scri
}
void KitEngine::executeSound(const FCLInstruction &instruction) {
- if (!_soundWarning) {
- warning("3D Construction Kit sound playback is not implemented");
- _soundWarning = true;
- }
+ uint16 index = getVariableOrConstant(instruction._source, instruction._sourceType);
+ if (instruction.getType() == Token::SYNCSND)
+ _pendingSound = index == 0xffff ? -1 : index;
+ else if (_sound)
+ _sound->playSound(index, Sound::kTypeNormal);
}
bool KitEngine::executeObjectConditions(GeometricObject *obj, bool shot, bool collided, bool activated) {
diff --git a/engines/freescape/module.mk b/engines/freescape/module.mk
index 9cdd6b53917..4edc107e0f9 100644
--- a/engines/freescape/module.mk
+++ b/engines/freescape/module.mk
@@ -78,6 +78,8 @@ MODULE_OBJS := \
sound/amiga.o \
sound/atari.o \
sound/common.o \
+ sound/3dck.o \
+ sound/3dck_adlib.o \
sound/cpc.o \
sound/dos.o \
sound/fx.o \
diff --git a/engines/freescape/sound/3dck.cpp b/engines/freescape/sound/3dck.cpp
new file mode 100644
index 00000000000..9c13f3dad75
--- /dev/null
+++ b/engines/freescape/sound/3dck.cpp
@@ -0,0 +1,190 @@
+/* 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 "common/endian.h"
+#include "common/mutex.h"
+#include "freescape/games/3dck/3dck.h"
+#include "freescape/sound/3dck.h"
+
+namespace Freescape {
+
+class KitSpeakerSound : public Sound, public Audio::AudioStream {
+public:
+ KitSpeakerSound(Audio::Mixer *mixer, const Common::Array<byte> &data);
+ ~KitSpeakerSound() override { _mixer->stopHandle(_handle); }
+
+ void playSound(int index, Type type) override;
+ void stopSound(Type type) override;
+ bool isPlayingSound(Type type) const override;
+ bool isSoundAvailable(int index) const override;
+ int readBuffer(int16 *buffer, int samples) override;
+ int getRate() const override { return _mixer->getOutputRate(); }
+ bool isStereo() const override { return false; }
+ bool endOfData() const override { return false; }
+
+private:
+ void loadPart();
+ void tick();
+
+ struct Part {
+ byte steps, duration;
+ uint16 delta;
+ };
+ struct Effect {
+ uint16 divisor;
+ byte repeats;
+ Common::Array<Part> parts;
+ };
+ Common::Array<Effect> _effects;
+ Audio::Mixer *_mixer;
+ Audio::SoundHandle _handle;
+ mutable Common::Mutex _mutex;
+ const Effect *_effect = nullptr;
+ uint _part = 0;
+ byte _steps = 0, _duration = 0, _repeats = 0;
+ uint16 _divisor = 0;
+ uint64 _phase = 0, _timer = 0;
+ Type _type = kTypeNormal;
+};
+
+KitSpeakerSound::KitSpeakerSound(Audio::Mixer *mixer, const Common::Array<byte> &data) : _mixer(mixer) {
+ uint count = READ_LE_UINT16(&data[0x323]);
+ if (0x325 + count * 4 > 0x375)
+ error("Invalid 3D Construction Kit speaker table");
+ _effects.resize(count);
+ for (uint i = 0; i < count; i++) {
+ const byte *entry = &data[0x325 + i * 4];
+ if (entry[0] == 0xff)
+ continue;
+ uint offset = 0x375 + entry[0] * 5;
+ if (offset >= data.size() || !data[offset] || offset + 1 + data[offset] * 4 > data.size())
+ error("Invalid 3D Construction Kit speaker pattern");
+ Effect &effect = _effects[i];
+ effect.divisor = READ_LE_UINT16(entry + 1);
+ effect.repeats = entry[3];
+ effect.parts.resize(data[offset++]);
+ for (Part &part : effect.parts) {
+ part.steps = data[offset];
+ part.delta = READ_LE_UINT16(&data[offset + 1]);
+ part.duration = data[offset + 3];
+ offset += 4;
+ }
+ }
+ _mixer->playStream(Audio::Mixer::kSFXSoundType, &_handle, this, -1,
+ Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO);
+}
+
+bool KitSpeakerSound::isSoundAvailable(int index) const {
+ return index > 0 && uint(index) <= _effects.size() && !_effects[index - 1].parts.empty();
+}
+
+void KitSpeakerSound::playSound(int index, Type type) {
+ if (!isSoundAvailable(index))
+ return;
+ Common::StackLock lock(_mutex);
+ _effect = &_effects[index - 1];
+ _type = type;
+ _part = 0;
+ _repeats = _effect->repeats;
+ _divisor = _effect->divisor;
+ _phase = 0;
+ loadPart();
+}
+
+void KitSpeakerSound::loadPart() {
+ _steps = _effect->parts[_part].steps;
+ _duration = _effect->parts[_part].duration;
+}
+
+void KitSpeakerSound::tick() {
+ if (!_effect || --_duration)
+ return;
+ _duration = _effect->parts[_part].duration;
+ _divisor += _effect->parts[_part].delta;
+ _phase = 0;
+ if (--_steps)
+ return;
+ if (++_part == _effect->parts.size()) {
+ if (!--_repeats) {
+ _effect = nullptr;
+ return;
+ }
+ _part = 0;
+ }
+ loadPart();
+}
+
+int KitSpeakerSound::readBuffer(int16 *buffer, int samples) {
+ Common::StackLock lock(_mutex);
+ // The DOS IRQ uses PIT reload 3637; channel 2 holds an unsigned divisor.
+ uint64 interval = uint64(getRate()) * 3637;
+ for (int i = 0; i < samples; i++) {
+ if (_effect) {
+ uint64 period = uint64(_divisor ? _divisor : 65536) * getRate();
+ _phase %= period;
+ buffer[i] = _phase < period / 2 ? 2540 : -2540;
+ _phase += kKitPITClock;
+ } else {
+ buffer[i] = 0;
+ }
+ _timer += kKitPITClock;
+ while (_timer >= interval) {
+ _timer -= interval;
+ tick();
+ }
+ }
+ return samples;
+}
+
+void KitSpeakerSound::stopSound(Type type) {
+ Common::StackLock lock(_mutex);
+ if (type == kTypeNormal || _type == type)
+ _effect = nullptr;
+}
+
+bool KitSpeakerSound::isPlayingSound(Type type) const {
+ Common::StackLock lock(_mutex);
+ return _effect && (type == kTypeNormal || _type == type);
+}
+
+void KitEngine::loadSounds(Common::SeekableReadStream &file) {
+ Common::Array<byte> data;
+ data.resize(file.size());
+ if (data.size() < 0x108 || file.read(data.data(), data.size()) != data.size() ||
+ memcmp(data.data(), "3D Construction Kit (c) Incentive Software.", 42))
+ error("Invalid 3D Construction Kit sound driver");
+ uint16 init = READ_LE_UINT16(&data[0x100]);
+ uint16 play = READ_LE_UINT16(&data[0x104]);
+ if (init == 0x124 && play == 0x1c5 && data.size() >= 0x375)
+ _sound = new KitSpeakerSound(_mixer, data);
+ else if (init == 0x79a && play == 0x864 && data.size() >= 0x4480)
+ _sound = createKitAdLibSound(_mixer, data);
+ else if (init != 0x115 || play != 0x1a7)
+ warning("Unsupported 3D Construction Kit sound driver");
+}
+
+void KitEngine::playPendingSound() {
+ if (_pendingSound >= 0 && _sound)
+ _sound->playSound(_pendingSound, Sound::kTypeNormal);
+ _pendingSound = -1;
+}
+
+} // namespace Freescape
diff --git a/engines/freescape/sound/3dck.h b/engines/freescape/sound/3dck.h
new file mode 100644
index 00000000000..a2965b0db1f
--- /dev/null
+++ b/engines/freescape/sound/3dck.h
@@ -0,0 +1,41 @@
+/* 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/>.
+ *
+ */
+
+#ifndef FREESCAPE_SOUND_3DCK_H
+#define FREESCAPE_SOUND_3DCK_H
+
+#include "common/array.h"
+
+namespace Audio {
+class Mixer;
+}
+
+namespace Freescape {
+
+class Sound;
+
+static const uint32 kKitPITClock = 1193182;
+
+Sound *createKitAdLibSound(Audio::Mixer *mixer, const Common::Array<byte> &data);
+
+} // namespace Freescape
+
+#endif
diff --git a/engines/freescape/sound/3dck_adlib.cpp b/engines/freescape/sound/3dck_adlib.cpp
new file mode 100644
index 00000000000..f4ffc715148
--- /dev/null
+++ b/engines/freescape/sound/3dck_adlib.cpp
@@ -0,0 +1,725 @@
+/* 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 "audio/fmopl.h"
+#include "audio/mixer.h"
+#include "common/endian.h"
+#include "common/mutex.h"
+#include "common/queue.h"
+#include "common/textconsole.h"
+#include "freescape/sound.h"
+#include "freescape/sound/3dck.h"
+
+namespace Freescape {
+
+// The RUN driver uses early Westwood AdLib bytecode with absolute offsets.
+class KitAdLibSound : public Sound {
+public:
+ KitAdLibSound(Audio::Mixer *mixer, const Common::Array<byte> &data);
+ ~KitAdLibSound() override;
+ void playSound(int index, Type type) override;
+ void stopSound(Type type) override;
+ bool isPlayingSound(Type type) const override;
+ bool isSoundAvailable(int index) const override;
+
+private:
+ enum Command {
+ kRepeat = 0, kCheckRepeat, kStartProgram, kNoteSpacing, kJump, kCall, kReturn,
+ kBaseOctave, kStop, kRest, kWriteRegister, kNoteDuration, kBaseNote,
+ kSecondaryEffect, kStopOther,
+ kInstrument = 16, kSlide, kRemoveSlide, kBaseFrequency,
+ kVibrato = 21, kPriority = 26, kBeat = 28, kWaitBeat, kLevel1,
+ kDuration = 32, kNoteOn, kFractionalSpacing = 36, kTempo = 38,
+ kRemoveSecondary, kChannelTempo = 41, kLevel3 = 43, kLevel2, kChangeLevel2,
+ kAMDepth, kVibratoDepth, kChangeLevel1, kClearChannel = 51,
+ kRandomNote = 53, kRemoveVibrato, kPitchBend = 57, kResetTempo, kNop,
+ kRandomDuration, kChangeTempo, kKeyModulation = 63, kRemoveKeyModulation,
+ kSetupRhythm, kPlayRhythm, kRemoveRhythm, kRhythmLevel2, kChangeRhythmLevel1,
+ kRhythmLevel1, kTempoReset = 72, kNoteLevels
+ };
+ enum Effect { kNoEffect, kSlideEffect, kVibratoEffect };
+ struct Channel {
+ uint32 ip = 0;
+ uint16 stack[4] = {};
+ byte stackSize = 0, duration = 0, repeat = 0;
+ int8 priority = 0, baseOctave = 0, baseNote = 0, pitchBend = 0;
+ byte baseFrequency = 0, rawNote = 0;
+ byte tempo = 255, timer = 255, tempoReset = 0;
+ byte spacing = 1, fractionalSpacing = 0, gateDuration = 0, durationRandomness = 0;
+ byte regA = 0, regB = 0;
+ byte level1 = 0, level2 = 0, level3 = 0;
+ byte opLevel[2] = {}, noteLevelScale[2] = {}, noteLevel = 0;
+ bool additive = false;
+ Effect effect = kNoEffect;
+ byte slideTempo = 0, slideTimer = 255;
+ int16 slideStep = 0, vibratoStep = 0;
+ byte vibratoTempo = 0, vibratoTimer = 0, vibratoRange = 0;
+ byte vibratoSteps = 0, vibratoCount = 0, vibratoDelay = 0, vibratoWait = 0;
+ uint16 secondaryData = 0;
+ byte secondaryTempo = 0, secondaryTimer = 0, secondaryRegister = 0;
+ int8 secondarySize = 0, secondaryPosition = 0;
+ };
+
+ bool contains(uint32 offset, uint size) const { return offset <= _data.size() && size <= _data.size() - offset; }
+ uint16 word(uint offset) const { return contains(offset, 2) ? READ_LE_UINT16(&_data[offset]) : 0; }
+ uint16 program(uint16 bank, byte index) const;
+ uint16 mapSound(uint16 index) const { return index <= 20 ? word(0x2f6 + index * 2) : index; }
+ static bool advance(byte &timer, byte tempo) {
+ uint sum = timer + tempo;
+ timer = sum;
+ return sum > 255;
+ }
+ void onTimer();
+ void tick();
+ void startProgram(byte index);
+ void stopChannel(uint index, bool clear);
+ bool executeChannel(uint index);
+ bool executeCommand(uint index, byte command, const byte *values);
+ void updateEffects(uint index);
+ void setupNote(uint index, byte note);
+ void noteOn(uint index);
+ void noteOff(uint index);
+ bool setupDuration(Channel &channel, byte duration);
+ void setupInstrument(uint index, byte instrument, Channel &channel);
+ void setupRhythm(Channel &channel, const byte *values);
+ void rhythmLevel(byte command, byte mask, byte value);
+ void adjustLevel(uint index);
+ byte operatorLevel(const Channel &channel, uint op) const;
+ void writeRegister(byte reg, byte value);
+ void writeVolume(uint index);
+ void updateVolume();
+ uint16 getRandomNumber();
+
+ Common::Array<byte> _data;
+ Common::Queue<byte> _queue;
+ Channel _channels[10];
+ Audio::Mixer *_mixer;
+ OPL::OPL *_opl;
+ mutable Common::Mutex _mutex;
+ uint32 _timer = 0;
+ uint16 _bank = 0, _random = 0x1234;
+ byte _divider = 12, _tempo = 0, _beatTimer = 255;
+ byte _beatDivider = 0, _beatCount = 0, _beat = 0, _beatWaiting = 0;
+ byte _depth = 0, _rhythm = 0;
+ byte _rhythmLevel[5] = {}, _rhythmLevel1[5] = {}, _rhythmLevel2[5] = {};
+ byte _keyModulation = 0, _keyPeriod = 0, _keyTimer = 0, _keyState = 0;
+ byte _registers[256] = {};
+ int _volume = 255;
+ static const byte kOperators[9];
+ static const byte kArgumentCounts[74];
+};
+
+const byte KitAdLibSound::kOperators[9] = {0, 1, 2, 8, 9, 10, 16, 17, 18};
+const byte KitAdLibSound::kArgumentCounts[74] = {
+ 1, 2, 1, 1, 2, 2, 0, 1, 0, 1, 2, 2, 1, 5, 1, 0,
+ 1, 3, 0, 1, 0, 4, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0,
+ 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 2, 2, 1, 1,
+ 1, 0, 0, 1, 0, 2, 0, 0, 0, 1, 0, 0, 1, 1, 0, 2,
+ 0, 9, 1, 0, 2, 2, 2, 0, 1, 2
+};
+
+KitAdLibSound::KitAdLibSound(Audio::Mixer *mixer, const Common::Array<byte> &data) : _data(data), _mixer(mixer) {
+ _opl = OPL::Config::create();
+ if (!_opl || !_opl->init())
+ error("Unable to initialize 3D Construction Kit AdLib sound");
+ _bank = word(0x447c);
+ writeRegister(1, 0x20);
+ writeRegister(8, 0);
+ stopSound(kTypeNormal);
+ _opl->start(new Common::Functor0Mem<void, KitAdLibSound>(this, &KitAdLibSound::onTimer), 1000);
+}
+
+KitAdLibSound::~KitAdLibSound() {
+ _opl->stop();
+ delete _opl;
+}
+
+uint16 KitAdLibSound::program(uint16 bank, byte index) const {
+ uint16 offset = word(bank + index * 2);
+ return offset && contains(offset, 2) && _data[offset] < 10 ? offset : 0;
+}
+
+bool KitAdLibSound::isSoundAvailable(int index) const {
+ if (index < 0 || index > 0xffff)
+ return false;
+ uint16 sound = mapSound(index);
+ return program(word(0x447c + (sound > 255 ? 2 : 0)), sound & 255) != 0;
+}
+
+void KitAdLibSound::playSound(int index, Type type) {
+ if (!isSoundAvailable(index))
+ return;
+ Common::StackLock lock(_mutex);
+ uint16 sound = mapSound(index);
+ _bank = word(0x447c + (sound > 255 ? 2 : 0));
+ if (_queue.size() == 16)
+ _queue.pop();
+ _queue.push(sound & 255);
+}
+
+void KitAdLibSound::stopSound(Type type) {
+ if (type == kTypeMovement)
+ return;
+ Common::StackLock lock(_mutex);
+ _queue.clear();
+ _keyModulation = _rhythm = _depth = 0;
+ writeRegister(0xbd, 0);
+ for (uint i = 0; i < 10; i++) {
+ _channels[i] = Channel();
+ stopChannel(i, true);
+ }
+}
+
+bool KitAdLibSound::isPlayingSound(Type type) const {
+ Common::StackLock lock(_mutex);
+ if (!_queue.empty())
+ return true;
+ for (const Channel &channel : _channels)
+ if (channel.ip)
+ return true;
+ return false;
+}
+
+void KitAdLibSound::onTimer() {
+ Common::StackLock lock(_mutex);
+ updateVolume();
+ // PIT reload 1365, with one sequencer update every twelve interrupts.
+ _timer += kKitPITClock;
+ while (_timer >= 1365 * 1000) {
+ _timer -= 1365 * 1000;
+ if (_keyModulation && !--_keyTimer) {
+ _keyState ^= 0x20;
+ _channels[0].regB = (_channels[0].regB & 0x1f) | _keyState;
+ writeRegister(0xb0, _channels[0].regB);
+ _keyTimer = _keyPeriod;
+ }
+ if (!--_divider) {
+ _divider = 12;
+ tick();
+ }
+ }
+}
+
+void KitAdLibSound::startProgram(byte index) {
+ uint16 offset = program(_bank, index);
+ if (!offset)
+ return;
+ byte channelIndex = _data[offset];
+ int8 priority = _data[offset + 1];
+ Channel &channel = _channels[channelIndex];
+ if (!channelIndex)
+ _keyModulation = 0;
+ if (priority < channel.priority)
+ return;
+ byte level = channel.level2;
+ channel = Channel();
+ channel.level2 = level;
+ channel.priority = priority;
+ channel.ip = offset + 2;
+ channel.duration = 1;
+ if (channelIndex < 9 && (!_rhythm || channelIndex < 6)) {
+ byte op = kOperators[channelIndex];
+ writeRegister(0x60 + op, 255);
+ writeRegister(0x63 + op, 255);
+ writeRegister(0x80 + op, 255);
+ writeRegister(0x83 + op, 255);
+ writeRegister(0xb0 + channelIndex, 0);
+ writeRegister(0xb0 + channelIndex, 0x20);
+ }
+}
+
+void KitAdLibSound::tick() {
+ while (!_queue.empty())
+ startProgram(_queue.pop());
+ for (int i = 9; i >= 0; i--) {
+ Channel &channel = _channels[i];
+ if (!channel.ip)
+ continue;
+ if (channel.tempoReset)
+ channel.tempo = _tempo;
+ if (advance(channel.timer, channel.tempo)) {
+ if (!--channel.duration) {
+ if (!executeChannel(i))
+ continue;
+ } else if (channel.duration == channel.spacing || channel.duration == channel.gateDuration) {
+ noteOff(i);
+ }
+ }
+ if (channel.ip)
+ updateEffects(i);
+ }
+ if (advance(_beatTimer, _tempo) && !--_beatCount) {
+ _beatCount = _beatDivider;
+ _beat++;
+ }
+}
+
+bool KitAdLibSound::executeChannel(uint index) {
+ Channel &channel = _channels[index];
+ for (uint budget = 4096; budget && channel.ip; budget--) {
+ if (!contains(channel.ip, 1))
+ break;
+ byte opcode = _data[channel.ip++];
+ if (opcode & 0x80) {
+ opcode &= 0x7f;
+ if (opcode >= ARRAYSIZE(kArgumentCounts) || !contains(channel.ip, kArgumentCounts[opcode]))
+ break;
+ const byte *values = _data.data() + channel.ip;
+ channel.ip += kArgumentCounts[opcode];
+ if (executeCommand(index, opcode, values))
+ return channel.ip != 0 && opcode != kWaitBeat;
+ } else {
+ uint size = 1 + ((channel.noteLevelScale[0] || channel.noteLevelScale[1]) ? 1 : 0);
+ if (!contains(channel.ip, size))
+ break;
+ setupNote(index, opcode);
+ noteOn(index);
+ bool wait = setupDuration(channel, _data[channel.ip++]);
+ if (size == 2) {
+ channel.noteLevel = _data[channel.ip++];
+ adjustLevel(index);
+ }
+ if (wait)
+ return true;
+ }
+ }
+ if (channel.ip)
+ warning("Invalid 3D Construction Kit AdLib program at %x", channel.ip);
+ stopChannel(index, false);
+ return false;
+}
+
+bool KitAdLibSound::executeCommand(uint index, byte command, const byte *v) {
+ Channel &channel = _channels[index];
+ switch (command) {
+ case kRepeat: channel.repeat = v[0]; break;
+ case kCheckRepeat:
+ if (!--channel.repeat)
+ break;
+ // fall through
+ case kJump: channel.ip = READ_LE_UINT16(v); break;
+ case kCall:
+ if (channel.stackSize == ARRAYSIZE(channel.stack)) {
+ stopChannel(index, false);
+ return true;
+ }
+ channel.stack[channel.stackSize++] = channel.ip;
+ channel.ip = READ_LE_UINT16(v);
+ break;
+ case kReturn:
+ if (!channel.stackSize) {
+ stopChannel(index, false);
+ return true;
+ }
+ channel.ip = channel.stack[--channel.stackSize];
+ break;
+ case kStartProgram:
+ if (v[0] != 255)
+ startProgram(v[0]);
+ break;
+ case kNoteSpacing: channel.spacing = v[0]; break;
+ case kBaseOctave: channel.baseOctave = v[0]; break;
+ case kRest:
+ noteOff(index);
+ return setupDuration(channel, v[0]);
+ case kWriteRegister: writeRegister(v[0], v[1]); break;
+ case kNoteDuration:
+ setupNote(index, v[0]);
+ return setupDuration(channel, v[1]);
+ case kBaseNote: channel.baseNote = v[0]; break;
+ case kSecondaryEffect:
+ channel.secondaryTempo = channel.secondaryTimer = v[0];
+ channel.secondarySize = channel.secondaryPosition = v[1];
+ channel.secondaryRegister = v[2];
+ channel.secondaryData = READ_LE_UINT16(v + 3);
+ break;
+ case kStopOther:
+ if (v[0] < 10) {
+ _channels[v[0]].ip = 0;
+ _channels[v[0]].priority = 0;
+ }
+ break;
+ case kInstrument: setupInstrument(index, v[0], channel); break;
+ case kSlide:
+ channel.slideTempo = v[0];
+ channel.slideTimer = 255;
+ channel.slideStep = READ_BE_UINT16(v + 1);
+ channel.effect = kSlideEffect;
+ break;
+ case kRemoveSlide:
+ channel.slideStep = 0;
+ // fall through
+ case kRemoveVibrato: channel.effect = kNoEffect; break;
+ case kBaseFrequency: channel.baseFrequency = v[0]; break;
+ case kVibrato:
+ channel.vibratoTempo = v[0];
+ channel.vibratoRange = v[1];
+ channel.vibratoCount = v[2] + 1;
+ channel.vibratoSteps = v[2] * 2;
+ channel.vibratoDelay = v[3];
+ channel.effect = kVibratoEffect;
+ break;
+ case kPriority: channel.priority = v[0]; break;
+ case kBeat:
+ _beatDivider = _beatCount = v[0] >> 1;
+ _beatTimer = 255;
+ _beat = _beatWaiting = 0;
+ break;
+ case kWaitBeat:
+ if (_beatWaiting && (_beat & v[0])) {
+ _beatWaiting = 0;
+ break;
+ }
+ if (!_beatWaiting && !(_beat & v[0]))
+ _beatWaiting = 1;
+ channel.ip -= 2;
+ channel.duration = 1;
+ return true;
+ case kLevel1: channel.level1 = v[0]; adjustLevel(index); break;
+ case kDuration: return setupDuration(channel, v[0]);
+ case kNoteOn:
+ noteOn(index);
+ return setupDuration(channel, v[0]);
+ case kFractionalSpacing: channel.fractionalSpacing = v[0] & 7; break;
+ case kTempo: _tempo = v[0]; break;
+ case kRemoveSecondary: channel.secondaryData = 0; break;
+ case kChannelTempo: channel.tempo = v[0]; break;
+ case kLevel3: channel.level3 = v[0]; break;
+ case kLevel2:
+ case kChangeLevel2:
+ if (v[0] < 10) {
+ _channels[v[0]].level2 = v[1] + (command == kChangeLevel2 ? _channels[v[0]].level2 : 0);
+ adjustLevel(v[0]);
+ }
+ break;
+ case kAMDepth:
+ _depth = (_depth & 0x7f) | ((v[0] & 1) << 7);
+ writeRegister(0xbd, _depth);
+ break;
+ case kVibratoDepth:
+ _depth = (_depth & 0xbf) | ((v[0] & 1) << 6);
+ writeRegister(0xbd, _depth);
+ break;
+ case kChangeLevel1: channel.level1 += v[0]; adjustLevel(index); break;
+ case kClearChannel:
+ if (v[0] < 10)
+ stopChannel(v[0], true);
+ break;
+ case kRandomNote: {
+ uint16 note = (((channel.regB & 0x1f) << 8) | channel.regA) + (getRandomNumber() & READ_BE_UINT16(v));
+ if (index < 9) {
+ writeRegister(0xa0 + index, note & 255);
+ writeRegister(0xb0 + index, (note >> 8) | (channel.regB & 0x20));
+ }
+ break;
+ }
+ case kPitchBend: channel.pitchBend = v[0]; setupNote(index, channel.rawNote); break;
+ case kResetTempo: channel.tempo = _tempo; break;
+ case kNop: break;
+ case kRandomDuration: channel.durationRandomness = v[0]; break;
+ case kChangeTempo: channel.tempo = CLIP<int>(channel.tempo + int8(v[0]), 1, 255); break;
+ case kKeyModulation:
+ if (v[1] < 5) {
+ _keyModulation = v[0];
+ if (v[0] == 2) {
+ uint16 period = word(0x21b5 + v[1] * 2);
+ uint16 frequency = word(0x21b5 + (v[1] + 1) * 2);
+ if (contains(period, 1) && contains(frequency, 1)) {
+ _keyPeriod = _data[period];
+ writeRegister(0xa0, _data[frequency]);
+ }
+ }
+ }
+ break;
+ case kRemoveKeyModulation: _keyModulation = 0; break;
+ case kSetupRhythm: setupRhythm(channel, v); break;
+ case kPlayRhythm:
+ writeRegister(0xbd, (_rhythm & ~(v[0] & 31)) | 0x20);
+ _rhythm |= v[0];
+ writeRegister(0xbd, _depth | 0x20 | _rhythm);
+ break;
+ case kRemoveRhythm:
+ _rhythm = 0;
+ writeRegister(0xbd, _depth & 0xc0);
+ break;
+ case kRhythmLevel2:
+ case kChangeRhythmLevel1:
+ case kRhythmLevel1: rhythmLevel(command, v[0], v[1]); break;
+ case kTempoReset: channel.tempoReset = v[0]; break;
+ case kNoteLevels:
+ channel.noteLevelScale[0] = v[0];
+ channel.noteLevelScale[1] = v[1];
+ break;
+ default:
+ stopChannel(index, false);
+ return true;
+ }
+ return false;
+}
+
+void KitAdLibSound::stopChannel(uint index, bool clear) {
+ Channel &channel = _channels[index];
+ channel.ip = 0;
+ channel.duration = 0;
+ channel.priority = 0;
+ if (!clear) {
+ noteOff(index);
+ return;
+ }
+ channel.level2 = 0;
+ if (!index)
+ _keyModulation = 0;
+ if (index < 9) {
+ writeRegister(0xc0 + index, 0);
+ writeRegister(0x43 + kOperators[index], 0x3f);
+ writeRegister(0x83 + kOperators[index], 0xff);
+ writeRegister(0xb0 + index, 0);
+ }
+}
+
+void KitAdLibSound::setupNote(uint index, byte note) {
+ if (index >= 9)
+ return;
+ Channel &channel = _channels[index];
+ channel.rawNote = note;
+ int pitch = int8((note & 15) + channel.baseNote);
+ int octave = byte((note & 0xf0) + channel.baseOctave);
+ while (pitch < 0) { pitch += 12; octave -= 16; }
+ while (pitch >= 12) { pitch -= 12; octave += 16; }
+ uint16 frequency = word(0x63e + pitch * 2) + channel.baseFrequency;
+ uint16 value = frequency | (((octave >> 2) & 0x1c) << 8);
+ if (channel.pitchBend) {
+ uint table = (note & 15) + (channel.pitchBend > 0 ? 2 : 0);
+ uint offset = word(0x11a + table * 2) + ABS(int(channel.pitchBend));
+ if (contains(offset, 1))
+ value += channel.pitchBend > 0 ? _data[offset] : -_data[offset];
+ }
+ channel.regA = value;
+ channel.regB = (value >> 8) | (channel.regB & 0x20);
+ writeRegister(0xa0 + index, channel.regA);
+ writeRegister(0xb0 + index, channel.regB);
+}
+
+void KitAdLibSound::noteOn(uint index) {
+ if (index >= 9)
+ return;
+ Channel &channel = _channels[index];
+ channel.regB |= 0x20;
+ writeRegister(0xb0 + index, channel.regB);
+ uint frequency = ((channel.regB << 8) | channel.regA) & 0x3ff;
+ channel.vibratoStep = (frequency >> (9 - MIN<uint>(channel.vibratoRange, 9))) & 255;
+ channel.vibratoWait = channel.vibratoDelay;
+}
+
+void KitAdLibSound::noteOff(uint index) {
+ if (index >= 9 || (_rhythm && index >= 6))
+ return;
+ _channels[index].regB &= ~0x20;
+ writeRegister(0xb0 + index, _channels[index].regB);
+}
+
+bool KitAdLibSound::setupDuration(Channel &channel, byte duration) {
+ channel.duration = duration;
+ if (channel.durationRandomness)
+ channel.duration += getRandomNumber() & channel.durationRandomness;
+ else if (channel.fractionalSpacing)
+ channel.gateDuration = (duration >> 3) * channel.fractionalSpacing;
+ return duration != 0;
+}
+
+void KitAdLibSound::setupInstrument(uint index, byte instrument, Channel &channel) {
+ uint offset = word(0x1892 + instrument * 2);
+ if (index >= 9 || !offset || !contains(offset, 11))
+ return;
+ const byte *data = &_data[offset];
+ byte op = kOperators[index];
+ writeRegister(0x20 + op, data[0]);
+ writeRegister(0x23 + op, data[1]);
+ writeRegister(0xc0 + index, data[2]);
+ channel.additive = data[2] & 1;
+ writeRegister(0xe0 + op, data[3]);
+ writeRegister(0xe3 + op, data[4]);
+ channel.opLevel[0] = data[5];
+ channel.opLevel[1] = data[6];
+ writeRegister(0x40 + op, operatorLevel(channel, 0));
+ writeRegister(0x43 + op, operatorLevel(channel, 1));
+ writeRegister(0x60 + op, data[7]);
+ writeRegister(0x63 + op, data[8]);
+ writeRegister(0x80 + op, data[9]);
+ writeRegister(0x83 + op, data[10]);
+}
+
+byte KitAdLibSound::operatorLevel(const Channel &channel, uint op) const {
+ byte level = channel.opLevel[op] & 63;
+ if (op || channel.additive)
+ level += channel.level1 + channel.level2 + channel.level3;
+ if (channel.noteLevelScale[op]) {
+ uint shift = channel.noteLevelScale[op] + 1;
+ if (shift < 16)
+ level -= uint16(channel.noteLevel << shift) >> 8;
+ }
+ return (channel.opLevel[op] & 0xc0) | (int8(level) < 0 ? 0 : MIN<int>(level, 63));
+}
+
+void KitAdLibSound::adjustLevel(uint index) {
+ if (index >= 9)
+ return;
+ Channel &channel = _channels[index];
+ writeRegister(0x43 + kOperators[index], operatorLevel(channel, 1));
+ writeRegister(0x40 + kOperators[index], operatorLevel(channel, 0));
+}
+
+void KitAdLibSound::updateEffects(uint index) {
+ if (index >= 9)
+ return;
+ Channel &channel = _channels[index];
+ bool changed = false;
+ uint16 frequency = ((channel.regB << 8) | channel.regA) & 0x3ff;
+ if (channel.effect == kSlideEffect && advance(channel.slideTimer, channel.slideTempo)) {
+ byte octave = channel.regB & 0x1c;
+ frequency += channel.slideStep;
+ if (channel.slideStep >= 0 && int16(frequency) >= 0x2de) {
+ frequency >>= 1;
+ octave = (octave + 4) & 0x1c;
+ } else if (channel.slideStep < 0 && int16(frequency) <= 0x184) {
+ frequency <<= 1;
+ octave = (octave - 4) & 0x1c;
+ }
+ frequency &= 0x3ff;
+ channel.regB = (channel.regB & 0x20) | octave;
+ changed = true;
+ } else if (channel.effect == kVibratoEffect) {
+ if (channel.vibratoWait) {
+ channel.vibratoWait--;
+ } else if (advance(channel.vibratoTimer, channel.vibratoTempo)) {
+ if (!--channel.vibratoCount) {
+ channel.vibratoStep = -channel.vibratoStep;
+ channel.vibratoCount = channel.vibratoSteps;
+ }
+ frequency += channel.vibratoStep;
+ changed = true;
+ }
+ }
+ if (changed) {
+ channel.regA = frequency;
+ channel.regB = (channel.regB & 0xfc) | (frequency >> 8);
+ writeRegister(0xa0 + index, channel.regA);
+ writeRegister(0xb0 + index, channel.regB);
+ }
+ if (channel.secondaryData && advance(channel.secondaryTimer, channel.secondaryTempo)) {
+ if (--channel.secondaryPosition < 0)
+ channel.secondaryPosition = channel.secondarySize;
+ uint offset = channel.secondaryData + byte(channel.secondaryPosition);
+ if (contains(offset, 1))
+ writeRegister(channel.secondaryRegister + kOperators[index], _data[offset]);
+ }
+}
+
+void KitAdLibSound::setupRhythm(Channel &channel, const byte *values) {
+ for (uint i = 0; i < 3; i++) {
+ setupInstrument(6 + i, values[i], channel);
+ if (i == 0)
+ _rhythmLevel[4] = channel.opLevel[1];
+ else if (i == 1) {
+ _rhythmLevel[0] = channel.opLevel[0];
+ _rhythmLevel[3] = channel.opLevel[1];
+ } else {
+ _rhythmLevel[2] = channel.opLevel[0];
+ _rhythmLevel[1] = channel.opLevel[1];
+ }
+ }
+ for (uint i = 0; i < 3; i++) {
+ _channels[6 + i].regB = values[3 + i * 2] & 0x2f;
+ writeRegister(0xb6 + i, _channels[6 + i].regB);
+ writeRegister(0xa6 + i, values[4 + i * 2]);
+ }
+ _rhythm = 0x20;
+}
+
+void KitAdLibSound::rhythmLevel(byte command, byte mask, byte value) {
+ static const byte registers[5] = {0x51, 0x55, 0x52, 0x54, 0x53};
+ for (uint i = 0; i < 5; i++) {
+ if (!(mask & (1 << i)))
+ continue;
+ byte level = value + _rhythmLevel[i] + _rhythmLevel2[i];
+ if (command == kRhythmLevel2) {
+ _rhythmLevel2[i] = value;
+ level = value + _rhythmLevel[i] + _rhythmLevel1[i] + value;
+ } else if (command == kChangeRhythmLevel1) {
+ level += _rhythmLevel1[i];
+ _rhythmLevel1[i] = MIN<int>(level, 63);
+ } else {
+ _rhythmLevel1[i] = value;
+ }
+ writeRegister(registers[i], MIN<int>(level, 63));
+ }
+}
+
+void KitAdLibSound::writeVolume(uint index) {
+ byte op = kOperators[index];
+ for (uint i = 0; i < 2; i++) {
+ byte reg = 0x40 + op + i * 3;
+ byte value = _registers[reg];
+ if (i || (_registers[0xc0 + index] & 1) || ((_registers[0xbd] & 0x20) && index >= 7))
+ value = (value & 0xc0) | (63 - (63 - (value & 63)) * _volume / 255);
+ _opl->writeReg(reg, value);
+ }
+}
+
+void KitAdLibSound::writeRegister(byte reg, byte value) {
+ _registers[reg] = value;
+ if (reg >= 0x40 && reg <= 0x55) {
+ for (uint i = 0; i < 9; i++) {
+ if (reg == 0x40 + kOperators[i] || reg == 0x43 + kOperators[i]) {
+ writeVolume(i);
+ return;
+ }
+ }
+ }
+ _opl->writeReg(reg, value);
+ if (reg >= 0xc0 && reg <= 0xc8)
+ writeVolume(reg - 0xc0);
+ else if (reg == 0xbd)
+ for (uint i = 6; i < 9; i++)
+ writeVolume(i);
+}
+
+void KitAdLibSound::updateVolume() {
+ int volume = _mixer->isSoundTypeMuted(Audio::Mixer::kSFXSoundType) ? 0 :
+ _mixer->getVolumeForSoundType(Audio::Mixer::kSFXSoundType);
+ if (_volume == volume)
+ return;
+ _volume = volume;
+ for (uint i = 0; i < 9; i++)
+ writeVolume(i);
+}
+
+uint16 KitAdLibSound::getRandomNumber() {
+ _random += 0x9248;
+ _random = (_random >> 3) | (_random << 13);
+ return _random;
+}
+
+Sound *createKitAdLibSound(Audio::Mixer *mixer, const Common::Array<byte> &data) {
+ return new KitAdLibSound(mixer, data);
+}
+
+} // namespace Freescape
Commit: fa5891998224ade6d6d11746875587420de93351
https://github.com/scummvm/scummvm/commit/fa5891998224ade6d6d11746875587420de93351
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-09-08T13:33:08+02:00
Commit Message:
FREESCAPE: fixes to run Desert Maze (3dck game)
Changed paths:
engines/freescape/games/3dck/3dck.cpp
engines/freescape/language/execution_3dck16.cpp
diff --git a/engines/freescape/games/3dck/3dck.cpp b/engines/freescape/games/3dck/3dck.cpp
index d8811d6f550..0cbb56f3b42 100644
--- a/engines/freescape/games/3dck/3dck.cpp
+++ b/engines/freescape/games/3dck/3dck.cpp
@@ -339,6 +339,13 @@ Object *KitEngine::loadObject(Common::SeekableReadStream &file, ObjectData &data
if (geometric) {
ObjectType type = ObjectType(data.type);
int colorCount = GeometricObject::numberOfColoursForObjectOfType(type);
+ int ordinateCount = GeometricObject::numberOfOrdinatesForType(type);
+ // Hidden editor remnants can lack geometry (Desert Maze).
+ if ((data.flags & kKitInitiallyInvisible) && payload.size() < colorCount + 2 * ordinateCount) {
+ warning("Ignoring incomplete hidden 3D Construction Kit object %u", data.id);
+ file.seek(end);
+ return nullptr;
+ }
colors = new Common::Array<uint8>();
for (int i = 0; i < colorCount; i += 2) {
byte first, second;
@@ -352,7 +359,6 @@ Object *KitEngine::loadObject(Common::SeekableReadStream &file, ObjectData &data
for (uint i = 0; i < ARRAYSIZE(sides); i++)
(*colors)[i] = sides[i];
}
- int ordinateCount = GeometricObject::numberOfOrdinatesForType(type);
if (ordinateCount) {
requireBytes(payload, 2 * ordinateCount);
ordinates = new Common::Array<float>();
@@ -414,18 +420,22 @@ void KitEngine::initGameState() {
void KitEngine::gotoArea(uint16 areaID, int entranceID) {
if (!_areaMap.contains(areaID))
error("Unknown 3D Construction Kit area %u", areaID);
+ float oldScale = _currentArea ? _currentArea->getScale() : 1;
if (_currentArea)
_kitVariables[9] = _currentArea->getAreaID();
_currentArea = _areaMap[areaID];
Entrance *entrance = static_cast<Entrance *>(_currentArea->entranceWithID(entranceID));
- if (!entrance)
- error("Unknown 3D Construction Kit entrance %d", entranceID);
- _position = entrance->getOrigin();
- _position.y() += _playerHeight;
- Math::Vector3d rotation = entrance->getRotation();
- _pitch = rotation.x();
- _yaw = 90.0f - rotation.y();
- _roll = rotation.z();
+ if (entrance) {
+ _position = entrance->getOrigin();
+ _position.y() += _playerHeight;
+ Math::Vector3d rotation = entrance->getRotation();
+ _pitch = rotation.x();
+ _yaw = 90.0f - rotation.y();
+ _roll = rotation.z();
+ } else {
+ // RUNVGA retains world coordinates and rotation when the entrance is absent.
+ _position *= oldScale / _currentArea->getScale();
+ }
_lastPosition = _position;
_gfx->_scale = _currentArea->getScale();
_gotoExecuted = true;
diff --git a/engines/freescape/language/execution_3dck16.cpp b/engines/freescape/language/execution_3dck16.cpp
index 5599a6521ee..f0e12ce7dcd 100644
--- a/engines/freescape/language/execution_3dck16.cpp
+++ b/engines/freescape/language/execution_3dck16.cpp
@@ -604,7 +604,7 @@ bool KitEngine::executeGoto(const FCLInstruction &instruction) {
if (instruction._sourceType != Token::UNKNOWN)
area = getVariableOrConstant(instruction._source, instruction._sourceType);
int32 entrance = getVariableOrConstant(instruction._destination, instruction._destinationType);
- if (!_areaMap.contains(area) || !_areaMap[area]->entranceWithID(entrance & 0x7fff)) {
+ if (!_areaMap.contains(area)) {
warning("Invalid 3D Construction Kit GOTO (%d, %u)", entrance, area);
return false;
}
Commit: d36c070008f1652233caf5a07f28e0724d514222
https://github.com/scummvm/scummvm/commit/d36c070008f1652233caf5a07f28e0724d514222
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-09-08T13:33:09+02:00
Commit Message:
FREESCAPE: corrected palette in CPC 3dck games
Changed paths:
engines/freescape/detection.cpp
engines/freescape/games/3dck/8bit.cpp
engines/freescape/games/3dck/8bit.h
engines/freescape/games/3dck/8bitUI.cpp
engines/freescape/language/execution_3dck8.cpp
diff --git a/engines/freescape/detection.cpp b/engines/freescape/detection.cpp
index ad80610c043..602ba9531c8 100644
--- a/engines/freescape/detection.cpp
+++ b/engines/freescape/detection.cpp
@@ -1165,6 +1165,16 @@ const ADGameDescription gameDescriptions[] = {
ADGF_UNSTABLE,
GUIO2(GUIO_NOMIDI, GUIO_RENDERCPC)
},
+ {
+ "3dkit",
+ "Ciudadela Fantasma",
+ AD_ENTRY2s("DATA.DAT", "284bd42e1ce459a9c97e30496d35803f", 6271,
+ "CIUDAD10.BIN", "ad9339f91dd579351f0a1a7b4d963c49", 25216),
+ Common::ES_ESP,
+ Common::kPlatformAmstradCPC,
+ ADGF_UNSTABLE,
+ GUIO2(GUIO_NOMIDI, GUIO_RENDERCPC)
+ },
{
"3dkit",
"Cube",
diff --git a/engines/freescape/games/3dck/8bit.cpp b/engines/freescape/games/3dck/8bit.cpp
index a23aa372257..9e307cd8eaf 100644
--- a/engines/freescape/games/3dck/8bit.cpp
+++ b/engines/freescape/games/3dck/8bit.cpp
@@ -20,6 +20,7 @@
*/
#include "common/algorithm.h"
+#include "common/endian.h"
#include "common/substream.h"
#include "math/utils.h"
@@ -43,11 +44,27 @@ Kit8Engine::Kit8Engine(OSystem *syst, const ADGameDescription *gd) : FreescapeEn
}
void Kit8Engine::loadAssets() {
- Common::File file;
- if (!file.open(_gameDescription->filesDescriptions[0].fileName))
+ Common::File dataFile;
+ if (!dataFile.open(_gameDescription->filesDescriptions[0].fileName))
error("Unable to open 8-bit 3D Construction Kit data");
+ requireBytes(dataFile, 160);
+ uint32 signature = dataFile.readUint32BE();
+ uint32 dataOffset = 0;
+ if (signature != MKTAG('K', 'I', 'T', 'A') && signature != MKTAG('K', 'I', 'T', 'C')) {
+ byte header[128];
+ dataFile.seek(0);
+ dataFile.read(header, sizeof(header));
+ uint16 checksum = 0;
+ for (uint i = 0; i < 67; i++)
+ checksum += header[i];
+ if (checksum != READ_LE_UINT16(header + 67) || READ_LE_UINT24(header + 64) != dataFile.size() - sizeof(header))
+ error("Invalid 3D Construction Kit AMSDOS header");
+ dataOffset = sizeof(header);
+ }
+ Common::SeekableSubReadStream file(&dataFile, dataOffset, dataFile.size());
requireBytes(file, 160);
- if (file.readUint32BE() != MKTAG('K', 'I', 'T', 'C') || file.readUint16LE() != file.size())
+ signature = file.readUint32BE();
+ if ((signature != MKTAG('K', 'I', 'T', 'A') && signature != MKTAG('K', 'I', 'T', 'C')) || file.readUint16LE() != file.size())
error("Unsupported 8-bit 3D Construction Kit data format");
uint16 procedures = file.readUint16LE();
uint16 conditions = file.readUint16LE();
@@ -126,8 +143,11 @@ void Kit8Engine::loadAssets() {
if (entry._key == 255)
continue;
for (byte id : _areaData[entry._key].globals) {
- if (!_areaMap.contains(255) || !_areaMap[255]->objectWithID(id) || entry._value->objectWithID(id))
- error("Invalid 8-bit 3D Construction Kit global object %u", id);
+ // The CPC runner ignores references to absent globals.
+ if (!_areaMap.contains(255) || !_areaMap[255]->objectWithID(id))
+ continue;
+ if (entry._value->objectWithID(id))
+ error("Duplicate 8-bit 3D Construction Kit global object %u", id);
entry._value->addObjectFromArea(id, _areaMap[255]);
}
}
@@ -186,6 +206,12 @@ Area *Kit8Engine::loadArea(Common::SeekableReadStream &file) {
byte type = header[0] & 0x0f;
byte objectID = header[7];
byte size = header[8];
+ if (id == 255 && !objectID && !size) {
+ // Ciudadela Fantasma ends its globals with an unused, incomplete record.
+ warning("Ignoring incomplete 8-bit 3D Construction Kit global object");
+ file.seek(conditions);
+ break;
+ }
if (size < 9 || start + size > conditions)
error("Invalid 8-bit 3D Construction Kit object size");
if (objectID == 255) {
@@ -249,6 +275,15 @@ GeometricObject *Kit8Engine::loadGeometricObject(Common::SeekableReadStream &fil
colors->push_back(color & 15);
colors->push_back(color >> 4);
}
+ if (type == kCubeType) {
+ // CPC Kit stores the positive X face first.
+ SWAP((*colors)[0], (*colors)[1]);
+ } else if (GeometricObject::isPyramid(type)) {
+ // Kit stores opposite sides together; the renderer walks around the base.
+ const byte sides[] = {(*colors)[2], (*colors)[0], (*colors)[3], (*colors)[1]};
+ for (uint i = 0; i < ARRAYSIZE(sides); i++)
+ (*colors)[i] = sides[i];
+ }
Common::Array<float> *ordinates = nullptr;
if (ordinateCount) {
static const byte pyramidAxes[3][2] = {{1, 2}, {0, 2}, {0, 1}};
@@ -304,6 +339,8 @@ void Kit8Engine::gotoArea(uint16 areaID, int entranceID) {
memcpy(_palette, data.palette, sizeof(_palette));
for (byte id : data.globals) {
Object *object = _currentArea->objectWithID(id);
+ if (!object)
+ continue;
object->restore();
object->makeVisible();
}
diff --git a/engines/freescape/games/3dck/8bit.h b/engines/freescape/games/3dck/8bit.h
index 0bb35b8c734..a0e55c56b67 100644
--- a/engines/freescape/games/3dck/8bit.h
+++ b/engines/freescape/games/3dck/8bit.h
@@ -104,6 +104,7 @@ private:
byte _colorPatterns[15][4] = {};
byte _instruments[8][6] = {};
byte _textColor = 7, _movementMode = 1;
+ bool _textOutputEnabled = false;
byte _climbHeight = 0, _fallHeight = 0, _walkSpeed = 0, _activationRange = 0;
byte _shotObject = 0, _hitObject = 0, _activatedObject = 0;
bool _fallen = false, _crushed = false, _crossVisible = true;
diff --git a/engines/freescape/games/3dck/8bitUI.cpp b/engines/freescape/games/3dck/8bitUI.cpp
index 06a87a06ca5..a64ea3eeada 100644
--- a/engines/freescape/games/3dck/8bitUI.cpp
+++ b/engines/freescape/games/3dck/8bitUI.cpp
@@ -33,12 +33,14 @@ static const byte kCPCInks[27] = {
};
void Kit8Engine::loadPresentation() {
+ // Shade 0 is transparent; the CPC runner indexes these patterns with shade - 1.
static const byte patterns[15][4] = {
+ {0x00, 0x00, 0x00, 0x00},
{0x0f, 0x0f, 0x0f, 0x0f}, {0xf0, 0xf0, 0xf0, 0xf0}, {0xff, 0xff, 0xff, 0xff},
{0x0a, 0x05, 0x0a, 0x05}, {0xa0, 0x50, 0xa0, 0x50}, {0xaa, 0x55, 0xaa, 0x55},
{0xa5, 0x5a, 0xa5, 0x5a}, {0xaf, 0x5f, 0xaf, 0x5f}, {0xfa, 0xf5, 0xfa, 0xf5},
{0x02, 0x08, 0x02, 0x08}, {0x20, 0x80, 0x20, 0x80}, {0x22, 0x88, 0x22, 0x88},
- {0x2d, 0x87, 0x2d, 0x87}, {0x2f, 0x8f, 0x2f, 0x8f}, {0xff, 0x66, 0x66, 0xff}
+ {0x2d, 0x87, 0x2d, 0x87}, {0x2f, 0x8f, 0x2f, 0x8f}
};
memcpy(_colorPatterns, patterns, sizeof(_colorPatterns));
_colorMap.resize(ARRAYSIZE(_colorPatterns));
@@ -67,7 +69,8 @@ void Kit8Engine::loadPresentation() {
}
_borderSurface.fillRect(_viewArea, 255);
// A saved editor data file omits the runner's font and border.
- if (file.open("DISC.BIN") && file.size() == 25216) {
+ const char *runner = _gameDescription->filesDescriptions[1].fileName;
+ if (file.open(runner ? runner : "DISC.BIN") && file.size() == 25216) {
byte header[128];
file.read(header, sizeof(header));
if (READ_LE_UINT16(header + 21) == 0x3e00 && READ_LE_UINT16(header + 24) == 25088) {
@@ -88,7 +91,7 @@ void Kit8Engine::applyPalette() {
}
void Kit8Engine::printText(const Common::String &text, byte x, byte y, byte color) {
- if (x >= 40 || y >= 25)
+ if (!_textOutputEnabled || x >= 40 || y >= 25)
return;
Graphics::DosFont font;
byte background = ((color >> 3) & 1) | ((color >> 1) & 2);
diff --git a/engines/freescape/language/execution_3dck8.cpp b/engines/freescape/language/execution_3dck8.cpp
index 5e4e31d73cf..bd68d657b77 100644
--- a/engines/freescape/language/execution_3dck8.cpp
+++ b/engines/freescape/language/execution_3dck8.cpp
@@ -29,7 +29,6 @@ void Kit8Engine::resetScripts() {
memset(_kitVariables, 0, sizeof(_kitVariables));
_changedVariables = 0;
_currentKey = 255;
- _textColor = 7;
_kitVariables[121] = _kitVariables[125] = 255;
_kitVariables[127] = 0x9c;
_script = ScriptState();
@@ -137,6 +136,8 @@ void Kit8Engine::updateScripts() {
uint budget = 4096;
while (budget) {
if (_script.stack.empty()) {
+ // The CPC runner enables text after its first initialization condition.
+ _textOutputEnabled = true;
if (_conditionIndex == _activeConditions->size()) {
if (!_globalPhase)
break;
Commit: 21f0ea581dceff4101034d6f15036cbc57fca5a7
https://github.com/scummvm/scummvm/commit/21f0ea581dceff4101034d6f15036cbc57fca5a7
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-09-08T13:33:09+02:00
Commit Message:
FREESCAPE: input/movement fixes for 3dck games
Changed paths:
engines/freescape/area.h
engines/freescape/freescape.cpp
engines/freescape/freescape.h
engines/freescape/games/3dck/8bit.cpp
engines/freescape/games/3dck/8bit.h
engines/freescape/games/3dck/8bitUI.cpp
engines/freescape/movement.cpp
diff --git a/engines/freescape/area.h b/engines/freescape/area.h
index 988c476f799..5dc65ecb921 100644
--- a/engines/freescape/area.h
+++ b/engines/freescape/area.h
@@ -85,6 +85,7 @@ public:
void resetArea();
void resetAreaGroups();
bool isOutside();
+ bool hasDrawableObjects() const { return !_drawableObjects.empty(); }
bool hasActiveGroups();
Common::Array<Common::String> _conditionSources;
diff --git a/engines/freescape/freescape.cpp b/engines/freescape/freescape.cpp
index bfbed6299a5..c36689b45b8 100644
--- a/engines/freescape/freescape.cpp
+++ b/engines/freescape/freescape.cpp
@@ -749,7 +749,8 @@ void FreescapeEngine::drawFrame() {
drawBackground();
if (_avoidRenderingFrames == 0) { // Avoid rendering inside objects
- _currentArea->draw(_gfx, _ticks / 10, renderPosition, _cameraFront, _roll, false, fov, aspectRatio, _nearClipPlane, farClipPlane);
+ if (_currentArea->hasDrawableObjects())
+ _currentArea->draw(_gfx, _ticks / 10, renderPosition, _cameraFront, _roll, false, fov, aspectRatio, _nearClipPlane, farClipPlane);
if (_gameStateControl == kFreescapeGameStatePlaying &&
_currentArea->hasActiveGroups() && _ticks % 50 == 0) {
executeMovementConditions();
@@ -822,7 +823,7 @@ void FreescapeEngine::drawFrameStereo(int farClipPlane) {
_gfx->positionCamera(renderPosition, renderPosition + _cameraFront, _roll);
drawBackground();
- if (_avoidRenderingFrames == 0)
+ if (_avoidRenderingFrames == 0 && _currentArea->hasDrawableObjects())
_currentArea->drawDepthLayer(_gfx, _ticks / 10, renderPosition, _cameraFront, _roll, false, Area::kRenderDepthBackground, stereoForegroundDistance, fov, aspectRatio, _nearClipPlane, farClipPlane);
for (int pass = 0; pass < 2; pass++) {
@@ -832,7 +833,7 @@ void FreescapeEngine::drawFrameStereo(int farClipPlane) {
_gfx->clearDepthBuffer();
- if (_avoidRenderingFrames == 0) // Avoid rendering inside objects
+ if (_avoidRenderingFrames == 0 && _currentArea->hasDrawableObjects()) // Avoid rendering inside objects
_currentArea->drawDepthLayer(_gfx, _ticks / 10, renderPosition, _cameraFront, _roll, false, Area::kRenderDepthForeground, stereoForegroundDistance, fov, aspectRatio, _nearClipPlane, farClipPlane);
if (_underFireFrames > 0) {
diff --git a/engines/freescape/freescape.h b/engines/freescape/freescape.h
index d5e4775752a..384be2497de 100644
--- a/engines/freescape/freescape.h
+++ b/engines/freescape/freescape.h
@@ -406,6 +406,7 @@ public:
void updatePlayerMovementSmooth(float deltaTime);
void updatePlayerMovementClassic(float deltaTime);
void resolveCollisions(Math::Vector3d newPosition);
+ virtual Math::Vector3d clipPosition(const Math::Vector3d &position) const { return position; }
virtual void checkIfStillInArea();
void changePlayerHeight(int index);
void increaseStepSize();
diff --git a/engines/freescape/games/3dck/8bit.cpp b/engines/freescape/games/3dck/8bit.cpp
index 9e307cd8eaf..5ce87e50516 100644
--- a/engines/freescape/games/3dck/8bit.cpp
+++ b/engines/freescape/games/3dck/8bit.cpp
@@ -372,11 +372,17 @@ void Kit8Engine::setMovementMode(byte mode) {
_lastPosition = _position;
}
-void Kit8Engine::checkIfStillInArea() {
+Math::Vector3d Kit8Engine::clipPosition(const Math::Vector3d &position) const {
float scale = _currentArea->getScale();
- _position.x() = CLIP(_position.x(), 0.0f, 4063.5f / scale);
- _position.y() = CLIP(_position.y(), 0.0f, 2015.5f / scale);
- _position.z() = CLIP(_position.z(), 0.0f, 4063.5f / scale);
+ // Walking bounds apply to the feet, before restoring the eye height.
+ float height = _flyMode ? 0 : _playerHeight;
+ return Math::Vector3d(CLIP(position.x(), 0.0f, 4063.5f / scale),
+ CLIP(position.y() - height, 0.0f, 2015.5f / scale) + height,
+ CLIP(position.z(), 0.0f, 4063.5f / scale));
+}
+
+void Kit8Engine::checkIfStillInArea() {
+ _position = clipPosition(_position);
}
void Kit8Engine::updatePlayerMovement(float deltaTime) {
diff --git a/engines/freescape/games/3dck/8bit.h b/engines/freescape/games/3dck/8bit.h
index a0e55c56b67..1b52f8c292a 100644
--- a/engines/freescape/games/3dck/8bit.h
+++ b/engines/freescape/games/3dck/8bit.h
@@ -34,6 +34,7 @@ public:
void loadAssets() override;
void initGameState() override;
void gotoArea(uint16 areaID, int entranceID) override;
+ Math::Vector3d clipPosition(const Math::Vector3d &position) const override;
void checkIfStillInArea() override;
bool checkIfGameEnded() override { return false; }
void borderScreen() override {}
diff --git a/engines/freescape/games/3dck/8bitUI.cpp b/engines/freescape/games/3dck/8bitUI.cpp
index a64ea3eeada..861c4c22710 100644
--- a/engines/freescape/games/3dck/8bitUI.cpp
+++ b/engines/freescape/games/3dck/8bitUI.cpp
@@ -169,6 +169,8 @@ void Kit8Engine::drawUI() {
bool Kit8Engine::handleInput(const Common::Event &event) {
if (event.type == Common::EVENT_KEYDOWN || event.type == Common::EVENT_KEYUP) {
byte key = event.kbd.ascii < 128 ? event.kbd.ascii : 255;
+ if (event.kbd.keycode == Common::KEYCODE_RETURN || event.kbd.keycode == Common::KEYCODE_KP_ENTER)
+ key = 13;
if (key >= 'a' && key <= 'z')
key -= 'a' - 'A';
if (event.type == Common::EVENT_KEYDOWN)
@@ -177,19 +179,25 @@ bool Kit8Engine::handleInput(const Common::Event &event) {
_currentKey = 255;
if (!_scriptFrameActive)
_kitVariables[121] = _currentKey;
- } else if (event.type == Common::EVENT_CUSTOM_ENGINE_ACTION_START) {
+ } else if (event.type == Common::EVENT_CUSTOM_ENGINE_ACTION_START || event.type == Common::EVENT_CUSTOM_ENGINE_ACTION_END) {
+ if (event.customType == kActionSkip || event.customType == kActionInfoMenu) {
+ byte key = event.customType == kActionSkip ? ' ' : 'I';
+ if (event.type == Common::EVENT_CUSTOM_ENGINE_ACTION_START)
+ _currentKey = key;
+ else if (_currentKey == key)
+ _currentKey = 255;
+ if (!_scriptFrameActive)
+ _kitVariables[121] = _currentKey;
+ return true;
+ }
+ if (event.type == Common::EVENT_CUSTOM_ENGINE_ACTION_END)
+ return false;
switch (event.customType) {
case kActionShoot:
case kActionActivate:
if (!_scriptFrameActive)
interact(event.customType == kActionShoot);
return true;
- case kActionSkip:
- _kitVariables[121] = ' ';
- return true;
- case kActionInfoMenu:
- _kitVariables[121] = 'I';
- return true;
case kActionEscape:
case kActionChangeMode:
return false;
diff --git a/engines/freescape/movement.cpp b/engines/freescape/movement.cpp
index e699693df56..e52b226f2b4 100644
--- a/engines/freescape/movement.cpp
+++ b/engines/freescape/movement.cpp
@@ -31,6 +31,7 @@ namespace Freescape {
void FreescapeEngine::initKeymaps(Common::Keymap *engineKeyMap, Common::Keymap *infoScreenKeyMap, const char *target) {
Common::Action *act;
+ const bool isKit = Common::String(_gameDescription->gameId) == "3dkit";
act = new Common::Action(Common::kStandardActionMoveUp, _("Up"));
act->setCustomEngineActionEvent(kActionMoveUp);
@@ -93,10 +94,19 @@ void FreescapeEngine::initKeymaps(Common::Keymap *engineKeyMap, Common::Keymap *
act = new Common::Action("SKIP", _("Skip"));
act->setCustomEngineActionEvent(kActionSkip);
act->addDefaultInputMapping("SPACE");
- act->addDefaultInputMapping("RETURN");
+ if (!isKit)
+ act->addDefaultInputMapping("RETURN");
act->addDefaultInputMapping("JOY_X");
engineKeyMap->addAction(act);
+ if (isKit) {
+ act = new Common::Action("RETURN", _("Return"));
+ act->setKeyEvent(Common::KeyState(Common::KEYCODE_RETURN, 13));
+ act->addDefaultInputMapping("RETURN");
+ act->addDefaultInputMapping("KP_ENTER");
+ engineKeyMap->addAction(act);
+ }
+
// I18N: Toggles between cursor lock modes, switching between free cursor movement and camera/head movement.
act = new Common::Action("SWITCH", _("Change mode"));
act->setCustomEngineActionEvent(kActionChangeMode);
@@ -597,12 +607,13 @@ void FreescapeEngine::updatePlayerMovementSmooth(float deltaTime) {
clearGameBit(31);
}
-void FreescapeEngine::resolveCollisions(Math::Vector3d const position) {
+void FreescapeEngine::resolveCollisions(Math::Vector3d position) {
if (_noClipMode) {
_position = position;
return;
}
+ position = clipPosition(position);
Math::Vector3d newPosition = position;
Math::Vector3d lastPosition = _lastPosition;
@@ -653,6 +664,7 @@ void FreescapeEngine::resolveCollisions(Math::Vector3d const position) {
Math::Vector3d fallStart = newPosition; // current standing point
Math::Vector3d fallEnd = fallStart; // copy for downward probe
fallEnd.y() = -8192; // probe way down below
+ fallEnd = clipPosition(fallEnd);
newPosition = _currentArea->resolveCollisions(fallStart, fallEnd, _playerHeight);
int fallen = _lastPosition.y() - newPosition.y();
Commit: 3dca7164adc9dece8cb907b362979b8e0a65d30b
https://github.com/scummvm/scummvm/commit/3dca7164adc9dece8cb907b362979b8e0a65d30b
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-09-08T13:33:09+02:00
Commit Message:
FREESCAPE: implementation of 8bit sounds for 3dck games
Changed paths:
A engines/freescape/sound/3dck_cpc.cpp
engines/freescape/freescape.h
engines/freescape/games/3dck/8bit.cpp
engines/freescape/games/3dck/8bit.h
engines/freescape/games/3dck/8bitUI.cpp
engines/freescape/language/execution_3dck8.cpp
engines/freescape/module.mk
diff --git a/engines/freescape/freescape.h b/engines/freescape/freescape.h
index 384be2497de..b0310b1948e 100644
--- a/engines/freescape/freescape.h
+++ b/engines/freescape/freescape.h
@@ -523,7 +523,7 @@ public:
void waitForSounds(Sound::Type type = Sound::kTypeNormal);
void stopAllSounds(Sound::Type type = Sound::kTypeNormal);
bool isPlayingSound(Sound::Type type = Sound::kTypeNormal);
- void playSound(int index, bool sync, Sound::Type type = Sound::kTypeNormal);
+ virtual void playSound(int index, bool sync, Sound::Type type = Sound::kTypeNormal);
void playWav(const Common::Path &filename);
void playMusic(const Common::Path &filename);
diff --git a/engines/freescape/games/3dck/8bit.cpp b/engines/freescape/games/3dck/8bit.cpp
index 5ce87e50516..beabffcf95c 100644
--- a/engines/freescape/games/3dck/8bit.cpp
+++ b/engines/freescape/games/3dck/8bit.cpp
@@ -41,6 +41,8 @@ Kit8Engine::Kit8Engine(OSystem *syst, const ADGameDescription *gd) : FreescapeEn
_playerHeightNumber = _playerHeightMaxNumber = 0;
_playerWidth = _playerDepth = 16;
_soundIndexShoot = -1;
+ _soundIndexCollide = 5;
+ _soundIndexFall = 7;
}
void Kit8Engine::loadAssets() {
@@ -154,6 +156,7 @@ void Kit8Engine::loadAssets() {
if (!_areaMap.contains(_startArea) || !_areaMap[_startArea]->entranceWithID(_startEntrance))
error("Invalid 8-bit 3D Construction Kit starting entrance");
loadPresentation();
+ loadSounds();
}
Common::Array<Kit8Engine::ConditionData> Kit8Engine::loadConditions(Common::SeekableReadStream &file) {
diff --git a/engines/freescape/games/3dck/8bit.h b/engines/freescape/games/3dck/8bit.h
index 1b52f8c292a..5021f42a564 100644
--- a/engines/freescape/games/3dck/8bit.h
+++ b/engines/freescape/games/3dck/8bit.h
@@ -44,6 +44,7 @@ public:
void updateTimeVariables() override;
void checkSensors() override;
void updateScripts() override;
+ void playSound(int index, bool sync, Sound::Type type = Sound::kTypeNormal) override;
bool executeObjectConditions(GeometricObject *obj, bool shot, bool collided, bool activated) override;
void executeLocalGlobalConditions(bool shot, bool collided, bool timer) override {}
bool canLoadGameStateCurrently(Common::U32String *msg = nullptr) override { return false; }
@@ -67,6 +68,8 @@ private:
Area *loadArea(Common::SeekableReadStream &file);
GeometricObject *loadGeometricObject(Common::SeekableReadStream &file, const byte header[9]);
void loadPresentation();
+ void loadSounds();
+ void playPendingSound();
void applyPalette();
void setMovementMode(byte mode);
void readSystemVariables();
@@ -109,7 +112,9 @@ private:
byte _climbHeight = 0, _fallHeight = 0, _walkSpeed = 0, _activationRange = 0;
byte _shotObject = 0, _hitObject = 0, _activatedObject = 0;
bool _fallen = false, _crushed = false, _crossVisible = true;
- bool _timerTriggered = false, _pendingTimer = false, _soundWarning = false;
+ bool _timerTriggered = false, _pendingTimer = false;
+ byte _pendingSound = 0;
+ bool _soundSyncReady = false;
uint32 _lastTime = 0, _timerTicks = 0, _timerInterval = 0, _delayUntil = 0;
byte _fontData[96][8] = {};
bool _hasFont = false;
diff --git a/engines/freescape/games/3dck/8bitUI.cpp b/engines/freescape/games/3dck/8bitUI.cpp
index 861c4c22710..6901ad289b6 100644
--- a/engines/freescape/games/3dck/8bitUI.cpp
+++ b/engines/freescape/games/3dck/8bitUI.cpp
@@ -164,6 +164,7 @@ void Kit8Engine::drawUI() {
_gfx->setViewport(_fullscreenViewArea);
_gfx->renderCrossair(_crossairPosition);
}
+ playPendingSound();
}
bool Kit8Engine::handleInput(const Common::Event &event) {
@@ -231,6 +232,7 @@ void Kit8Engine::interact(bool shot) {
if (_kitVariables[125] != 255)
_kitVariables[125]--;
_shootingFrames = 3;
+ playSound(3, true);
}
if (!object || !object->isGeometric())
return;
diff --git a/engines/freescape/language/execution_3dck8.cpp b/engines/freescape/language/execution_3dck8.cpp
index bd68d657b77..75100fa28b4 100644
--- a/engines/freescape/language/execution_3dck8.cpp
+++ b/engines/freescape/language/execution_3dck8.cpp
@@ -26,6 +26,9 @@
namespace Freescape {
void Kit8Engine::resetScripts() {
+ stopAllSounds();
+ _pendingSound = 0;
+ _soundSyncReady = false;
memset(_kitVariables, 0, sizeof(_kitVariables));
_changedVariables = 0;
_currentKey = 255;
@@ -159,6 +162,7 @@ void Kit8Engine::updateScripts() {
return;
updateInstruments();
_scriptFrameActive = false;
+ _soundSyncReady = true;
_shotObject = _hitObject = _activatedObject = 0;
}
@@ -265,6 +269,7 @@ FCLExecutionResult Kit8Engine::executeCode(ScriptState &script, uint &budget) {
writeSystemVariables();
_scriptSurface.fillRect(_viewArea, 255);
updateInstruments();
+ _soundSyncReady = true;
return kFCLPaused;
default:
error("Unsupported 8-bit 3D Construction Kit instruction %u", op);
@@ -428,10 +433,7 @@ void Kit8Engine::executeColour(const FCLInstruction &instruction) {
}
void Kit8Engine::executeSound(const FCLInstruction &instruction) {
- if (instruction._source && !_soundWarning) {
- warning("8-bit 3D Construction Kit sound effects are not implemented");
- _soundWarning = true;
- }
+ playSound(instruction._source, instruction.getType() == Token::SYNCSND);
}
bool Kit8Engine::executeObjectConditions(GeometricObject *object, bool shot, bool collided, bool activated) {
diff --git a/engines/freescape/module.mk b/engines/freescape/module.mk
index 4edc107e0f9..0d1c9eace2e 100644
--- a/engines/freescape/module.mk
+++ b/engines/freescape/module.mk
@@ -80,6 +80,7 @@ MODULE_OBJS := \
sound/common.o \
sound/3dck.o \
sound/3dck_adlib.o \
+ sound/3dck_cpc.o \
sound/cpc.o \
sound/dos.o \
sound/fx.o \
diff --git a/engines/freescape/sound/3dck_cpc.cpp b/engines/freescape/sound/3dck_cpc.cpp
new file mode 100644
index 00000000000..5f3532df717
--- /dev/null
+++ b/engines/freescape/sound/3dck_cpc.cpp
@@ -0,0 +1,226 @@
+/* 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 "audio/softsynth/ay8912.h"
+#include "common/endian.h"
+#include "common/mutex.h"
+
+#include "freescape/games/3dck/8bit.h"
+
+namespace Freescape {
+
+// Standard CPC runner bank at 0x9a9f, also used for editor data without a runner.
+static const byte kKitCPCSounds[13][16] = {
+ {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // Silence
+ {0x0f, 0x00, 0x0f, 0x00, 0x0f, 0x00, 0x00, 0x38, 0x0f, 0x0f, 0x0f, 0x01, 0x00, 0xff, 0x00, 0x1e}, // Ping
+ {0x00, 0x0c, 0x01, 0x0c, 0x02, 0x0c, 0x00, 0x38, 0x0f, 0x0f, 0x0f, 0x00, 0x10, 0x00, 0x00, 0x40}, // Buzz
+ {0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x0f, 0x0f, 0x0f, 0x00, 0x14, 0x00, 0x00, 0x10}, // Fire
+ {0x40, 0x01, 0x80, 0x01, 0xc0, 0x01, 0x00, 0x38, 0x0f, 0x0f, 0x0f, 0x01, 0xa0, 0x00, 0x00, 0x10}, // Activate
+ {0xc0, 0x00, 0x00, 0x09, 0x00, 0x07, 0x10, 0x38, 0x0f, 0x0f, 0x0f, 0x00, 0x7f, 0x00, 0xa0, 0x0a}, // Bump
+ {0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x0f, 0x0f, 0x0f, 0x01, 0x08, 0xff, 0x01, 0x1c}, // Fall
+ {0x00, 0x10, 0x10, 0x10, 0x20, 0x10, 0x00, 0x38, 0x0f, 0x0f, 0x0f, 0x00, 0x04, 0x00, 0x00, 0x90}, // Fail
+ {0x00, 0x08, 0x10, 0x08, 0x20, 0x08, 0x00, 0x38, 0x0f, 0x0f, 0x0f, 0x00, 0xf0, 0x00, 0x00, 0x80}, // Bonus 1
+ {0x00, 0x08, 0x40, 0x08, 0x80, 0x08, 0x00, 0x30, 0x0f, 0x0f, 0x0f, 0x00, 0xe0, 0x00, 0x04, 0x41}, // Bonus 2
+ {0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x38, 0x0f, 0x0f, 0x0f, 0x00, 0xf0, 0x00, 0xff, 0x34}, // Bonus 3
+ {0x00, 0x08, 0x10, 0x08, 0x20, 0x08, 0x20, 0x36, 0x0f, 0x0f, 0x0f, 0x00, 0x01, 0x00, 0x01, 0x20}, // Door open
+ {0x00, 0x08, 0x10, 0x08, 0x20, 0x08, 0x20, 0x36, 0x0f, 0x0f, 0x0f, 0x00, 0xff, 0x00, 0xff, 0x20} // Door close
+};
+
+class KitCPCSound : public Sound, public Audio::AudioStream {
+public:
+ KitCPCSound(Audio::Mixer *mixer, const byte *data);
+ ~KitCPCSound() override { _mixer->stopHandle(_handle); }
+
+ void playSound(int index, Type type) override;
+ void stopSound(Type type) override;
+ bool isPlayingSound(Type type) const override;
+ bool isSoundAvailable(int index) const override { return index >= 0 && index < ARRAYSIZE(_effects); }
+ int readBuffer(int16 *buffer, int samples) override;
+ int getRate() const override { return _psg.getRate(); }
+ bool isStereo() const override { return true; }
+ bool endOfData() const override { return false; }
+
+private:
+ struct Effect {
+ uint16 tone[3];
+ byte noise, mixer, volume[3], delay;
+ int8 toneStep, volumeStep, noiseStep;
+ byte duration;
+ };
+ void tick();
+ void silence();
+ void writeRegisters();
+
+ Effect _effects[13], _effect = {};
+ Audio::Mixer *_mixer;
+ Audio::SoundHandle _handle;
+ Audio::AY8912Stream _psg;
+ mutable Common::Mutex _mutex;
+ Type _type = kTypeNormal;
+ bool _active = false;
+ byte _delay = 0;
+ int _samplesLeft = 0;
+};
+
+KitCPCSound::KitCPCSound(Audio::Mixer *mixer, const byte *data) : _mixer(mixer), _psg(62500, 1000000) {
+ // An integral AY clock divisor keeps pitch exact with AY8912Stream.
+ for (Effect &effect : _effects) {
+ for (int channel = 0; channel < 3; channel++) {
+ effect.tone[channel] = READ_LE_UINT16(data + 2 * channel);
+ effect.volume[channel] = data[8 + channel];
+ }
+ effect.noise = data[6];
+ effect.mixer = data[7];
+ effect.delay = data[11];
+ effect.toneStep = int8(data[12]);
+ effect.volumeStep = int8(data[13]);
+ effect.noiseStep = int8(data[14]);
+ effect.duration = data[15];
+ data += 16;
+ }
+ silence();
+ _mixer->playStream(Audio::Mixer::kSFXSoundType, &_handle, this, -1,
+ kFreescapeDefaultVolume, 0, DisposeAfterUse::NO);
+}
+
+void KitCPCSound::silence() {
+ _active = false;
+ _psg.setReg(7, 0x3f);
+ for (int channel = 0; channel < 3; channel++)
+ _psg.setReg(8 + channel, 0);
+}
+
+void KitCPCSound::playSound(int index, Type type) {
+ if (!isSoundAvailable(index))
+ return;
+ Common::StackLock lock(_mutex);
+ _effect = _effects[index];
+ _type = type;
+ _delay = 0;
+ _active = _effect.mixer != 0x3f;
+ if (!_active)
+ silence();
+}
+
+void KitCPCSound::stopSound(Type type) {
+ Common::StackLock lock(_mutex);
+ if (type == kTypeNormal || type == _type)
+ silence();
+}
+
+bool KitCPCSound::isPlayingSound(Type type) const {
+ Common::StackLock lock(_mutex);
+ return _active && (type == kTypeNormal || type == _type);
+}
+
+void KitCPCSound::writeRegisters() {
+ for (int channel = 0; channel < 3; channel++) {
+ _psg.setReg(2 * channel, _effect.tone[channel]);
+ _psg.setReg(2 * channel + 1, _effect.tone[channel] >> 8);
+ _psg.setReg(8 + channel, _effect.volume[channel]);
+ }
+ _psg.setReg(6, _effect.noise);
+ _psg.setReg(7, _effect.mixer);
+ // The runner writes its modulation bytes to envelope registers too.
+ _psg.setReg(11, _effect.delay);
+ _psg.setReg(12, _effect.toneStep);
+ _psg.setReg(13, _effect.volumeStep);
+}
+
+void KitCPCSound::tick() {
+ if (!_active)
+ return;
+ // CPC routine 0x9a14 decrements duration before the modulation delay.
+ if (!--_effect.duration) {
+ silence();
+ return;
+ }
+ if (_delay) {
+ --_delay;
+ return;
+ }
+ _delay = _effect.delay;
+ writeRegisters();
+ for (int channel = 0; channel < 3; channel++) {
+ _effect.tone[channel] += _effect.toneStep;
+ _effect.volume[channel] += _effect.volumeStep;
+ }
+ _effect.noise += _effect.noiseStep;
+}
+
+int KitCPCSound::readBuffer(int16 *buffer, int samples) {
+ Common::StackLock lock(_mutex);
+ assert(!(samples & 1));
+ int done = 0;
+ while (done < samples) {
+ if (!_samplesLeft) {
+ tick();
+ _samplesLeft = 2 * getRate() / 50;
+ }
+ int count = MIN(samples - done, _samplesLeft);
+ _psg.readBuffer(buffer + done, count);
+ done += count;
+ _samplesLeft -= count;
+ }
+ return samples;
+}
+
+void Kit8Engine::loadSounds() {
+ byte data[sizeof(kKitCPCSounds)];
+ memcpy(data, kKitCPCSounds, sizeof(data));
+ Common::File file;
+ const char *runner = _gameDescription->filesDescriptions[1].fileName;
+ if (file.open(runner ? runner : "DISC.BIN") && file.size() == 25216) {
+ byte header[128];
+ file.read(header, sizeof(header));
+ if (READ_LE_UINT16(header + 21) == 0x3e00 && READ_LE_UINT16(header + 24) == 25088) {
+ file.seek(128 + 0x9a9f - 0x3e00);
+ if (file.read(data, sizeof(data)) != sizeof(data))
+ error("Truncated 3D Construction Kit CPC sound bank");
+ }
+ }
+ _sound = new KitCPCSound(_mixer, data);
+}
+
+void Kit8Engine::playSound(int index, bool sync, Sound::Type type) {
+ if (!_sound || !_sound->isSoundAvailable(index))
+ return;
+ if (type == Sound::kTypeMovement) {
+ // A bump must not replace an already pending shot or scripted effect.
+ if (index != _soundIndexCollide || !_pendingSound)
+ _pendingSound = index;
+ } else if (sync) {
+ // SYNCSND 0 cancels the pending effect in the CPC runner.
+ _pendingSound = index;
+ } else
+ _sound->playSound(index, type);
+}
+
+void Kit8Engine::playPendingSound() {
+ if (!_soundSyncReady)
+ return;
+ if (_pendingSound && _sound)
+ _sound->playSound(_pendingSound, Sound::kTypeNormal);
+ _pendingSound = 0;
+ _soundSyncReady = false;
+}
+
+} // namespace Freescape
Commit: 3cb304bda7e17a996a3d648e18b0c7f14de07dbe
https://github.com/scummvm/scummvm/commit/3cb304bda7e17a996a3d648e18b0c7f14de07dbe
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-09-08T13:33:09+02:00
Commit Message:
FREESCAPE: initial support for zx spectrum 3dck games
Changed paths:
A engines/freescape/games/3dck/zx.cpp
A engines/freescape/sound/3dck_zx.cpp
engines/freescape/detection.cpp
engines/freescape/games/3dck/8bit.cpp
engines/freescape/games/3dck/8bit.h
engines/freescape/games/3dck/8bitUI.cpp
engines/freescape/language/execution_3dck8.cpp
engines/freescape/metaengine.cpp
engines/freescape/module.mk
engines/freescape/sound/3dck_cpc.cpp
engines/freescape/zx_tape.cpp
diff --git a/engines/freescape/detection.cpp b/engines/freescape/detection.cpp
index 602ba9531c8..911e369c072 100644
--- a/engines/freescape/detection.cpp
+++ b/engines/freescape/detection.cpp
@@ -1156,6 +1156,16 @@ const ADGameDescription gameDescriptions[] = {
GUIO3(GUIO_NOMIDI, GUIO_RENDEREGA, GAMEOPTION_WASD_CONTROLS)
},
// 3D Construction Kit games
+ {
+ "3dkit",
+ "Dead by Dawn",
+ AD_ENTRY2s("3dkit.zx.data", "ec7ff343b0ba9f2e685bde2fb8f6d8d8", 13242,
+ "3dkit.zx.code", "1ef359f328832b77f0adead4d292f21f", 24575),
+ Common::EN_ANY,
+ Common::kPlatformZX,
+ ADGF_UNSTABLE,
+ GUIO2(GUIO_NOMIDI, GUIO_RENDERZX)
+ },
{
"3dkit",
"A Chance in Hell",
@@ -1507,12 +1517,13 @@ ADDetectedGames FreescapeMetaEngineDetection::detectZxTapeGames(const Common::FS
Common::File file;
Common::String name = node.getName();
if ((name.hasSuffixIgnoreCase(".tap") || name.hasSuffixIgnoreCase(".tzx")) && file.open(node)) {
+ Freescape::ZxTapeFileList files;
+ // Decode sampled recordings once, then match each game's virtual filenames.
+ if (!Freescape::extractZxSpectrumTapeFiles(file, "", files))
+ continue;
for (const ADGameDescription *desc = Freescape::gameDescriptions; desc->gameId; ++desc) {
if (!(desc->flags & skipADFlags) && desc->platform == Common::kPlatformZX) {
- file.seek(0);
- Freescape::ZxTapeFileList files;
- if (Freescape::extractZxSpectrumTapeFiles(file, desc->gameId, files) &&
- Freescape::matchZxSpectrumTapeFiles(files, *desc, _md5Bytes))
+ if (Freescape::matchZxSpectrumTapeFiles(files, *desc, _md5Bytes))
detectedGames.push_back(ADDetectedGame(desc));
}
}
diff --git a/engines/freescape/games/3dck/8bit.cpp b/engines/freescape/games/3dck/8bit.cpp
index beabffcf95c..4b95216f8e9 100644
--- a/engines/freescape/games/3dck/8bit.cpp
+++ b/engines/freescape/games/3dck/8bit.cpp
@@ -35,8 +35,8 @@ static void requireBytes(Common::SeekableReadStream &file, uint32 size) {
}
Kit8Engine::Kit8Engine(OSystem *syst, const ADGameDescription *gd) : FreescapeEngine(syst, gd) {
- _screenW = 320;
- _screenH = 200;
+ _screenW = isSpectrum() ? 256 : 320;
+ _screenH = isSpectrum() ? 192 : 200;
_fullscreenViewArea = Common::Rect(_screenW, _screenH);
_playerHeightNumber = _playerHeightMaxNumber = 0;
_playerWidth = _playerDepth = 16;
@@ -52,7 +52,7 @@ void Kit8Engine::loadAssets() {
requireBytes(dataFile, 160);
uint32 signature = dataFile.readUint32BE();
uint32 dataOffset = 0;
- if (signature != MKTAG('K', 'I', 'T', 'A') && signature != MKTAG('K', 'I', 'T', 'C')) {
+ if (isCPC() && signature != MKTAG('K', 'I', 'T', 'A') && signature != MKTAG('K', 'I', 'T', 'C')) {
byte header[128];
dataFile.seek(0);
dataFile.read(header, sizeof(header));
@@ -66,7 +66,9 @@ void Kit8Engine::loadAssets() {
Common::SeekableSubReadStream file(&dataFile, dataOffset, dataFile.size());
requireBytes(file, 160);
signature = file.readUint32BE();
- if ((signature != MKTAG('K', 'I', 'T', 'A') && signature != MKTAG('K', 'I', 'T', 'C')) || file.readUint16LE() != file.size())
+ bool validSignature = isSpectrum() ? signature == MKTAG('K', 'I', 'T', 'S') :
+ signature == MKTAG('K', 'I', 'T', 'A') || signature == MKTAG('K', 'I', 'T', 'C');
+ if (!validSignature || file.readUint16LE() != file.size())
error("Unsupported 8-bit 3D Construction Kit data format");
uint16 procedures = file.readUint16LE();
uint16 conditions = file.readUint16LE();
@@ -87,7 +89,7 @@ void Kit8Engine::loadAssets() {
if (!width || !height || x + width > _screenW || y + height > _screenH || !_walkSpeed || !turnSpeed)
error("Invalid 8-bit 3D Construction Kit display or movement settings");
_viewArea = Common::Rect(x, y, x + width, y + height);
- // CPC projection scales: 125 * 64 / (extent - 1), with a depth scale of 18.
+ // Projection scales: 125 * 64 / (extent - 1), with a depth scale of 18.
int xScale = 8000 / (width - 1);
int yScale = 8000 / (height - 1);
if (xScale > 127 || yScale > 127)
@@ -193,7 +195,7 @@ Area *Kit8Engine::loadArea(Common::SeekableReadStream &file) {
AreaData &data = _areaData[id];
for (uint i = 0; i < 4; i++) {
data.palette[i] = file.readByte();
- if (data.palette[i] > 26)
+ if (data.palette[i] > (isSpectrum() ? (i == 2 ? 1 : 7) : 26))
error("Invalid 8-bit 3D Construction Kit palette");
}
byte scale = file.readByte();
@@ -389,18 +391,32 @@ void Kit8Engine::checkIfStillInArea() {
}
void Kit8Engine::updatePlayerMovement(float deltaTime) {
- if (_scriptFrameActive || _initialScriptPending)
+ if (!isFrameReady() || _scriptFrameActive || _initialScriptPending || (isSpectrum() && isPlayingSound()))
return;
Math::Vector3d front = _cameraFront;
if (_movementMode == 3)
_cameraFront = directionToVector(0, _yaw, false);
- FreescapeEngine::updatePlayerMovement(deltaTime);
+ FreescapeEngine::updatePlayerMovement(kFrameDuration / 1000.0f);
_cameraFront = front;
}
+void Kit8Engine::pauseEngineIntern(bool pause) {
+ uint32 now = g_system->getMillis();
+ if (pause)
+ _pauseStartTime = now;
+ else {
+ uint32 elapsed = now - _pauseStartTime;
+ _lastTime += elapsed;
+ _nextFrameTime += elapsed;
+ if (_delayUntil)
+ _delayUntil += elapsed;
+ }
+ FreescapeEngine::pauseEngineIntern(pause);
+}
+
void Kit8Engine::checkSensors() {
// TODO: sensor firing.
- if (_scriptFrameActive || !_currentArea)
+ if (!isFrameReady() || _scriptFrameActive || !_currentArea)
return;
for (auto *object : _sensors) {
Sensor *sensor = static_cast<Sensor *>(object);
diff --git a/engines/freescape/games/3dck/8bit.h b/engines/freescape/games/3dck/8bit.h
index 5021f42a564..24e07b8e590 100644
--- a/engines/freescape/games/3dck/8bit.h
+++ b/engines/freescape/games/3dck/8bit.h
@@ -39,6 +39,7 @@ public:
bool checkIfGameEnded() override { return false; }
void borderScreen() override {}
void drawUI() override;
+ void initKeymaps(Common::Keymap *engineKeyMap, Common::Keymap *infoScreenKeyMap, const char *target) override;
bool handleInput(const Common::Event &event) override;
void updatePlayerMovement(float deltaTime) override;
void updateTimeVariables() override;
@@ -52,6 +53,7 @@ public:
private:
typedef FCLKit8ExecutionState ScriptState;
+ static const uint kFrameDuration = 100;
struct ConditionData {
byte id;
@@ -68,13 +70,19 @@ private:
Area *loadArea(Common::SeekableReadStream &file);
GeometricObject *loadGeometricObject(Common::SeekableReadStream &file, const byte header[9]);
void loadPresentation();
+ void loadPresentationZX();
void loadSounds();
+ void loadSoundsZX();
void playPendingSound();
void applyPalette();
+ void applyPaletteZX();
+ void setAttributesZX(const Common::Rect &rect, byte color);
void setMovementMode(byte mode);
void readSystemVariables();
void writeSystemVariables();
void resetScripts();
+ bool isFrameReady() const { return int32(_lastTime - _nextFrameTime) >= 0; }
+ void pauseEngineIntern(bool pause) override;
void beginScriptFrame();
void startScript(ScriptState &script, const FCLInstructionVector &code);
FCLExecutionResult executeCode(ScriptState &script, uint &budget);
@@ -101,11 +109,14 @@ private:
const Common::Array<ConditionData> *_activeConditions = nullptr;
uint _conditionIndex = 0;
bool _initialScriptPending = true, _scriptFrameActive = false, _globalPhase = false;
+ bool _redrawPending = false;
byte _kitVariables[128] = {};
uint16 _changedVariables = 0;
byte _currentKey = 255;
byte _palette[4] = {};
byte _colorPatterns[15][4] = {};
+ byte _zxPalette[16 * 3] = {};
+ byte _borderAttributes[32 * 24] = {}, _attributes[32 * 24] = {};
byte _instruments[8][6] = {};
byte _textColor = 7, _movementMode = 1;
bool _textOutputEnabled = false;
@@ -116,6 +127,7 @@ private:
byte _pendingSound = 0;
bool _soundSyncReady = false;
uint32 _lastTime = 0, _timerTicks = 0, _timerInterval = 0, _delayUntil = 0;
+ uint32 _nextFrameTime = 0, _pauseStartTime = 0;
byte _fontData[96][8] = {};
bool _hasFont = false;
Graphics::ManagedSurface _scriptSurface, _overlaySurface, _borderSurface;
diff --git a/engines/freescape/games/3dck/8bitUI.cpp b/engines/freescape/games/3dck/8bitUI.cpp
index 6901ad289b6..f37c522ddb6 100644
--- a/engines/freescape/games/3dck/8bitUI.cpp
+++ b/engines/freescape/games/3dck/8bitUI.cpp
@@ -33,6 +33,19 @@ static const byte kCPCInks[27] = {
};
void Kit8Engine::loadPresentation() {
+ _gfx->_keyColor = 0;
+ _scriptSurface.create(_screenW, _screenH, Graphics::PixelFormat::createFormatCLUT8());
+ _scriptSurface.fillRect(_fullscreenViewArea, 255);
+ _overlaySurface.create(_screenW, _screenH, _gfx->_texturePixelFormat);
+ _borderSurface.create(_screenW, _screenH, Graphics::PixelFormat::createFormatCLUT8());
+ _borderSurface.fillRect(_fullscreenViewArea, 0);
+ _colorMap.resize(ARRAYSIZE(_colorPatterns));
+ for (uint i = 0; i < _colorMap.size(); i++)
+ _colorMap[i] = _colorPatterns[i];
+ if (isSpectrum()) {
+ loadPresentationZX();
+ return;
+ }
// Shade 0 is transparent; the CPC runner indexes these patterns with shade - 1.
static const byte patterns[15][4] = {
{0x00, 0x00, 0x00, 0x00},
@@ -43,15 +56,6 @@ void Kit8Engine::loadPresentation() {
{0x2d, 0x87, 0x2d, 0x87}, {0x2f, 0x8f, 0x2f, 0x8f}
};
memcpy(_colorPatterns, patterns, sizeof(_colorPatterns));
- _colorMap.resize(ARRAYSIZE(_colorPatterns));
- for (uint i = 0; i < _colorMap.size(); i++)
- _colorMap[i] = _colorPatterns[i];
- _gfx->_keyColor = 0;
- _scriptSurface.create(_screenW, _screenH, Graphics::PixelFormat::createFormatCLUT8());
- _scriptSurface.fillRect(_fullscreenViewArea, 255);
- _overlaySurface.create(_screenW, _screenH, _gfx->_texturePixelFormat);
- _borderSurface.create(_screenW, _screenH, Graphics::PixelFormat::createFormatCLUT8());
- _borderSurface.fillRect(_fullscreenViewArea, 0);
Common::File file;
if (file.open("BORDER.DAT")) {
@@ -81,6 +85,10 @@ void Kit8Engine::loadPresentation() {
}
void Kit8Engine::applyPalette() {
+ if (isSpectrum()) {
+ applyPaletteZX();
+ return;
+ }
_gfx->_fourColorBackground = kCPCInks[_palette[0]];
_gfx->_underFireBackgroundColor = kCPCInks[_palette[2]];
_gfx->_paperColor = kCPCInks[_palette[1]];
@@ -91,22 +99,25 @@ void Kit8Engine::applyPalette() {
}
void Kit8Engine::printText(const Common::String &text, byte x, byte y, byte color) {
- if (!_textOutputEnabled || x >= 40 || y >= 25)
+ if (!_textOutputEnabled || x >= _screenW / 8 || y >= _screenH / 8)
return;
Graphics::DosFont font;
- byte background = ((color >> 3) & 1) | ((color >> 1) & 2);
- for (uint i = 0; i < text.size() && x < 40; i++, x++) {
+ byte foreground = isSpectrum() ? 1 : color & 3;
+ byte background = isSpectrum() ? 0 : ((color >> 3) & 1) | ((color >> 1) & 2);
+ for (uint i = 0; i < text.size() && x < _screenW / 8; i++, x++) {
byte chr = text[i];
+ if (isSpectrum())
+ _attributes[y * 32 + x] = color;
_scriptSurface.fillRect(Common::Rect(8 * x, 8 * y, 8 * x + 8, 8 * y + 8), background);
if (_hasFont && chr >= 32 && chr < 128) {
for (int row = 0; row < 8; row++) {
for (int col = 0; col < 8; col++) {
if (_fontData[chr - 32][row] & (0x80 >> col))
- _scriptSurface.setPixel(8 * x + col, 8 * y + row, color & 3);
+ _scriptSurface.setPixel(8 * x + col, 8 * y + row, foreground);
}
}
} else
- font.drawChar(_scriptSurface.surfacePtr(), chr, 8 * x, 8 * y, color & 3);
+ font.drawChar(_scriptSurface.surfacePtr(), chr, 8 * x, 8 * y, foreground);
}
}
@@ -119,36 +130,42 @@ void Kit8Engine::updateInstruments() {
for (const auto &instrument : _instruments) {
byte type = instrument[0], x = instrument[1], y = instrument[2], length = instrument[3];
byte variable = instrument[4] & 127, color = instrument[5];
- if (!type || type > 3 || x >= 40 || y >= 25 || !length)
+ if (!type || type > 3 || x >= _screenW / 8 || y >= _screenH / 8 || !length)
continue;
uint16 value = _kitVariables[variable];
if (type == 1) {
- if (length > 5 || x + length > 40)
+ if (length > 5 || x + length > _screenW / 8)
continue;
if (length > 3)
value |= _kitVariables[(variable + 1) & 127] << 8;
printText(Common::String::format("%0*u", length, value), x, y, color);
} else {
- if ((type == 2 && x + length > 40) || (type == 3 && y + length > 25))
+ if ((type == 2 && x + length > _screenW / 8) || (type == 3 && y + length > _screenH / 8))
continue;
Common::Rect bar(8 * x, 8 * y, 8 * (x + (type == 2 ? length : 1)), 8 * (y + (type == 3 ? length : 1)));
- _scriptSurface.fillRect(bar, (color >> 2) & 3);
+ if (isSpectrum())
+ setAttributesZX(bar, color);
+ _scriptSurface.fillRect(bar, isSpectrum() ? 0 : (color >> 2) & 3);
int filled = MIN<int>(value, 8 * length);
if (type == 2)
bar.right = bar.left + filled;
else
bar.top = bar.bottom - filled;
if (!bar.isEmpty())
- _scriptSurface.fillRect(bar, color & 3);
+ _scriptSurface.fillRect(bar, isSpectrum() ? 1 : color & 3);
}
}
}
void Kit8Engine::drawUI() {
- uint32 colors[4];
- for (uint i = 0; i < 4; i++) {
+ uint32 colors[16];
+ bool flash = (g_system->getMillis() / 320) & 1;
+ for (uint i = 0; i < (isSpectrum() ? 16 : 4); i++) {
byte r, g, b;
- _gfx->selectColorFromFourColorPalette(i, r, g, b);
+ if (isSpectrum())
+ _gfx->readFromPalette(i, r, g, b);
+ else
+ _gfx->selectColorFromFourColorPalette(i, r, g, b);
colors[i] = _overlaySurface.format.ARGBToColor(255, r, g, b);
}
for (int y = 0; y < _screenH; y++) {
@@ -156,6 +173,12 @@ void Kit8Engine::drawUI() {
byte pen = _scriptSurface.getPixel(x, y);
if (pen == 255)
pen = _borderSurface.getPixel(x, y);
+ if (isSpectrum() && pen != 255) {
+ byte attr = _attributes[(y / 8) * 32 + x / 8];
+ if ((attr & 128) && flash)
+ pen ^= 1;
+ pen = ((attr >> 3) & 8) | (pen ? attr & 7 : (attr >> 3) & 7);
+ }
_overlaySurface.setPixel(x, y, pen == 255 ? 0 : colors[pen]);
}
}
@@ -169,6 +192,11 @@ void Kit8Engine::drawUI() {
bool Kit8Engine::handleInput(const Common::Event &event) {
if (event.type == Common::EVENT_KEYDOWN || event.type == Common::EVENT_KEYUP) {
+ if (isSpectrum() && event.kbd.keycode == Common::KEYCODE_BREAK) {
+ if (event.type == Common::EVENT_KEYDOWN)
+ _gameStateControl = kFreescapeGameStateRestart;
+ return true;
+ }
byte key = event.kbd.ascii < 128 ? event.kbd.ascii : 255;
if (event.kbd.keycode == Common::KEYCODE_RETURN || event.kbd.keycode == Common::KEYCODE_KP_ENTER)
key = 13;
@@ -181,11 +209,20 @@ bool Kit8Engine::handleInput(const Common::Event &event) {
if (!_scriptFrameActive)
_kitVariables[121] = _currentKey;
} else if (event.type == Common::EVENT_CUSTOM_ENGINE_ACTION_START || event.type == Common::EVENT_CUSTOM_ENGINE_ACTION_END) {
+ if (isSpectrum() && (event.customType == kActionRiseOrFlyUp || event.customType == kActionLowerOrFlyDown)) {
+ if (event.type == Common::EVENT_CUSTOM_ENGINE_ACTION_START && !_scriptFrameActive && !isPlayingSound())
+ setMovementMode(event.customType == kActionRiseOrFlyUp ? 1 : 0);
+ return true;
+ }
if (event.customType == kActionSkip || event.customType == kActionInfoMenu) {
byte key = event.customType == kActionSkip ? ' ' : 'I';
- if (event.type == Common::EVENT_CUSTOM_ENGINE_ACTION_START)
+ if (event.type == Common::EVENT_CUSTOM_ENGINE_ACTION_START) {
_currentKey = key;
- else if (_currentKey == key)
+ if (isSpectrum() && event.customType == kActionInfoMenu && !_scriptFrameActive) {
+ _pitch = _roll = 0;
+ updateCamera();
+ }
+ } else if (_currentKey == key)
_currentKey = 255;
if (!_scriptFrameActive)
_kitVariables[121] = _currentKey;
@@ -220,7 +257,7 @@ bool Kit8Engine::handleInput(const Common::Event &event) {
}
void Kit8Engine::interact(bool shot) {
- if (!_viewArea.contains(_crossairPosition) || (shot && !_kitVariables[125]))
+ if (!_viewArea.contains(_crossairPosition) || (shot && !_kitVariables[125]) || (isSpectrum() && isPlayingSound()))
return;
float x = 2.0f * (_crossairPosition.x - _viewArea.left) / _viewArea.width() - 1;
float y = 1 - 2.0f * (_crossairPosition.y - _viewArea.top) / _viewArea.height();
diff --git a/engines/freescape/games/3dck/zx.cpp b/engines/freescape/games/3dck/zx.cpp
new file mode 100644
index 00000000000..6154ce4c56e
--- /dev/null
+++ b/engines/freescape/games/3dck/zx.cpp
@@ -0,0 +1,136 @@
+/* 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 "backends/keymapper/action.h"
+#include "backends/keymapper/keymap.h"
+#include "common/endian.h"
+#include "common/translation.h"
+#include "image/scr.h"
+
+#include "freescape/games/3dck/8bit.h"
+
+namespace Freescape {
+
+void Kit8Engine::initKeymaps(Common::Keymap *engineKeyMap, Common::Keymap *infoScreenKeyMap, const char *target) {
+ FreescapeEngine::initKeymaps(engineKeyMap, infoScreenKeyMap, target);
+ if (!isSpectrum())
+ return;
+ for (Common::Action *action : engineKeyMap->getActions()) {
+ switch (action->event.customType) {
+ case kActionShoot: action->addDefaultInputMapping("b"); break;
+ case kActionMoveLeft: action->addDefaultInputMapping("z"); break;
+ case kActionMoveRight: action->addDefaultInputMapping("x"); break;
+ case kActionInfoMenu: action->description = _("Center view"); break;
+ default: break;
+ }
+ }
+ const struct {
+ const char *id, *label, *key;
+ uint action;
+ } controls[] = {
+ {"ROTL", _s("Rotate left"), "q", kActionRotateLeft},
+ {"ROTR", _s("Rotate right"), "w", kActionRotateRight},
+ {"ACTIVATE", _s("Activate"), "a", kActionActivate},
+ {"RISE", _s("Stand up"), "r", kActionRiseOrFlyUp},
+ {"LOWER", _s("Crouch"), "f", kActionLowerOrFlyDown}
+ };
+ for (const auto &control : controls) {
+ Common::Action *action = new Common::Action(control.id, _(control.label));
+ action->setCustomEngineActionEvent(control.action);
+ action->addDefaultInputMapping(control.key);
+ engineKeyMap->addAction(action);
+ }
+ Common::Action *act = new Common::Action("BREAK", _("Restart"));
+ act->setKeyEvent(Common::KeyState(Common::KEYCODE_BREAK, 27));
+ act->addDefaultInputMapping("S+SPACE");
+ act->addDefaultInputMapping("BREAK");
+ engineKeyMap->addAction(act);
+}
+
+void Kit8Engine::loadPresentationZX() {
+ Common::File file;
+ Image::ScrDecoder decoder;
+ memcpy(_zxPalette, decoder.getPalette().data(), sizeof(_zxPalette));
+ if (file.open("3dkit.zx.title")) {
+ if (file.size() != 6912 || !decoder.loadStream(file))
+ error("Invalid 3D Construction Kit Spectrum title");
+ _title = new Graphics::ManagedSurface;
+ _title->copyFrom(*decoder.getSurface());
+ _title->convertToInPlace(_gfx->_texturePixelFormat, _zxPalette, 16);
+ file.close();
+ }
+ if (!file.open("3dkit.zx.border") || file.size() != 6912)
+ error("Missing 3D Construction Kit Spectrum border");
+ byte bitmap[6144];
+ if (file.read(bitmap, sizeof(bitmap)) != sizeof(bitmap) ||
+ file.read(_borderAttributes, sizeof(_borderAttributes)) != sizeof(_borderAttributes))
+ error("Truncated 3D Construction Kit Spectrum border");
+ memcpy(_attributes, _borderAttributes, sizeof(_attributes));
+ for (int y = 0; y < _screenH; ++y) {
+ for (int x = 0; x < _screenW; ++x) {
+ uint address = ((y & 0xc0) << 5) | ((y & 7) << 8) | ((y & 0x38) << 2) | (x >> 3);
+ _borderSurface.setPixel(x, y, (bitmap[address] >> (7 - (x & 7))) & 1);
+ }
+ }
+ _borderSurface.fillRect(_viewArea, 255);
+ file.close();
+
+ if (!file.open("3dkit.zx.code") || file.size() < 0x5eb7)
+ error("Missing 3D Construction Kit Spectrum runner");
+ file.seek(0x5e7b);
+ if (file.read(_colorPatterns, sizeof(_colorPatterns)) != sizeof(_colorPatterns))
+ error("Truncated 3D Construction Kit Spectrum shades");
+ file.seek(0x1690);
+ uint16 font = file.readUint16LE();
+ if (font >= 0xa000 && font - 0xa000 + sizeof(_fontData) <= uint32(file.size())) {
+ file.seek(font - 0xa000);
+ _hasFont = file.read(_fontData, sizeof(_fontData)) == sizeof(_fontData);
+ } else {
+ // Tapes omit the ROM font; the runner also has a compact character set.
+ file.seek(0x220);
+ for (uint chr = 0; chr < 64; ++chr) {
+ for (uint row = 1; row <= 6; ++row)
+ _fontData[chr][row] = file.readByte() >> 1;
+ }
+ for (uint chr = 64; chr < 96; ++chr)
+ memcpy(_fontData[chr], _fontData[chr - 32], 8);
+ _hasFont = !file.err();
+ }
+}
+
+void Kit8Engine::setAttributesZX(const Common::Rect &rect, byte color) {
+ for (int y = rect.top / 8; y < (rect.bottom + 7) / 8; ++y) {
+ for (int x = rect.left / 8; x < (rect.right + 7) / 8; ++x)
+ _attributes[y * 32 + x] = color;
+ }
+}
+
+void Kit8Engine::applyPaletteZX() {
+ _gfx->_palette = _zxPalette;
+ _gfx->_inkColor = _palette[0] | (_palette[2] << 3);
+ _gfx->_paperColor = _palette[1] | (_palette[2] << 3);
+ _currentArea->_usualBackgroundColor = 1;
+ _currentArea->_skyColor = 1;
+ _currentArea->_underFireBackgroundColor = 2;
+ setAttributesZX(_viewArea, _palette[0] | (_palette[1] << 3) | (_palette[2] << 6));
+}
+
+} // namespace Freescape
diff --git a/engines/freescape/language/execution_3dck8.cpp b/engines/freescape/language/execution_3dck8.cpp
index 75100fa28b4..a12583e29ee 100644
--- a/engines/freescape/language/execution_3dck8.cpp
+++ b/engines/freescape/language/execution_3dck8.cpp
@@ -38,12 +38,15 @@ void Kit8Engine::resetScripts() {
_activeConditions = nullptr;
_initialScriptPending = true;
_scriptFrameActive = false;
+ _redrawPending = false;
_shotObject = _hitObject = _activatedObject = 0;
_fallen = _crushed = _pendingTimer = _timerTriggered = false;
_crossVisible = true;
_timerTicks = _timerInterval = _delayUntil = 0;
_lastTime = g_system->getMillis();
+ _nextFrameTime = _lastTime;
_scriptSurface.fillRect(_fullscreenViewArea, 255);
+ memcpy(_attributes, _borderAttributes, sizeof(_attributes));
}
void Kit8Engine::readSystemVariables() {
@@ -85,6 +88,11 @@ void Kit8Engine::writeSystemVariables() {
void Kit8Engine::updateTimeVariables() {
uint32 now = g_system->getMillis();
+ // The Spectrum beeper disables interrupts until the effect ends.
+ if (isSpectrum() && isPlayingSound()) {
+ _lastTime = now;
+ return;
+ }
uint32 elapsed = (now - _lastTime) / 20;
_lastTime += 20 * elapsed;
uint16 counter = (_kitVariables[122] | (_kitVariables[123] << 8)) + elapsed;
@@ -127,10 +135,15 @@ void Kit8Engine::beginScriptFrame() {
}
void Kit8Engine::updateScripts() {
+ if (isSpectrum() && isPlayingSound())
+ return;
_fallen |= _hasFallen;
_crushed |= _playerWasCrushed;
_hasFallen = _playerWasCrushed = false;
_avoidRenderingFrames = 0;
+ if ((!_scriptFrameActive || _redrawPending) && !isFrameReady())
+ return;
+ _redrawPending = false;
if (_delayUntil && int32(_delayUntil - g_system->getMillis()) > 0)
return;
_delayUntil = 0;
@@ -162,6 +175,8 @@ void Kit8Engine::updateScripts() {
return;
updateInstruments();
_scriptFrameActive = false;
+ // Approximate 8-bit rendering time using Freescape's movement cadence.
+ _nextFrameTime = _lastTime + kFrameDuration;
_soundSyncReady = true;
_shotObject = _hitObject = _activatedObject = 0;
}
@@ -255,6 +270,8 @@ FCLExecutionResult Kit8Engine::executeCode(ScriptState &script, uint &budget) {
case Token::SOUND:
case Token::SYNCSND:
executeSound(instruction);
+ if (isSpectrum() && isPlayingSound())
+ return kFCLPaused;
break;
case Token::DELAY:
_delayUntil = g_system->getMillis() + 20 * (instruction._source ? instruction._source : 256);
@@ -269,6 +286,8 @@ FCLExecutionResult Kit8Engine::executeCode(ScriptState &script, uint &budget) {
writeSystemVariables();
_scriptSurface.fillRect(_viewArea, 255);
updateInstruments();
+ _nextFrameTime = _lastTime + kFrameDuration;
+ _redrawPending = true;
_soundSyncReady = true;
return kFCLPaused;
default:
@@ -428,7 +447,8 @@ void Kit8Engine::executeCall(const FCLInstruction &instruction, ScriptState &scr
}
void Kit8Engine::executeColour(const FCLInstruction &instruction) {
- _palette[instruction._source & 3] = MIN<int>(26, instruction._destination);
+ uint index = instruction._source & 3;
+ _palette[index] = isSpectrum() ? instruction._destination & (index == 2 ? 1 : 7) : MIN<int>(26, instruction._destination);
applyPalette();
}
diff --git a/engines/freescape/metaengine.cpp b/engines/freescape/metaengine.cpp
index 9359f73db00..0ce141c5b8c 100644
--- a/engines/freescape/metaengine.cpp
+++ b/engines/freescape/metaengine.cpp
@@ -211,7 +211,7 @@ Common::Error FreescapeMetaEngine::createInstance(OSystem *syst, Engine **engine
} else if (Common::String(gd->gameId) == "castlemaster" || Common::String(gd->gameId) == "castlemaster2") {
*engine = (Engine *)new Freescape::CastleEngine(syst, gd);
} else if (Common::String(gd->gameId) == "3dkit") {
- if (gd->platform == Common::kPlatformAmstradCPC)
+ if (gd->platform == Common::kPlatformAmstradCPC || gd->platform == Common::kPlatformZX)
*engine = new Freescape::Kit8Engine(syst, gd);
else
*engine = new Freescape::KitEngine(syst, gd);
diff --git a/engines/freescape/module.mk b/engines/freescape/module.mk
index 0d1c9eace2e..f3d7522dedb 100644
--- a/engines/freescape/module.mk
+++ b/engines/freescape/module.mk
@@ -56,6 +56,7 @@ MODULE_OBJS := \
games/3dck/8bit.o \
games/3dck/8bitUI.o \
games/3dck/ui.o \
+ games/3dck/zx.o \
games/palettes.o \
gfx.o \
loaders/8bitImage.o \
@@ -81,6 +82,7 @@ MODULE_OBJS := \
sound/3dck.o \
sound/3dck_adlib.o \
sound/3dck_cpc.o \
+ sound/3dck_zx.o \
sound/cpc.o \
sound/dos.o \
sound/fx.o \
diff --git a/engines/freescape/sound/3dck_cpc.cpp b/engines/freescape/sound/3dck_cpc.cpp
index 5f3532df717..ff45be3d756 100644
--- a/engines/freescape/sound/3dck_cpc.cpp
+++ b/engines/freescape/sound/3dck_cpc.cpp
@@ -184,6 +184,10 @@ int KitCPCSound::readBuffer(int16 *buffer, int samples) {
}
void Kit8Engine::loadSounds() {
+ if (isSpectrum()) {
+ loadSoundsZX();
+ return;
+ }
byte data[sizeof(kKitCPCSounds)];
memcpy(data, kKitCPCSounds, sizeof(data));
Common::File file;
diff --git a/engines/freescape/sound/3dck_zx.cpp b/engines/freescape/sound/3dck_zx.cpp
new file mode 100644
index 00000000000..7595202ec8b
--- /dev/null
+++ b/engines/freescape/sound/3dck_zx.cpp
@@ -0,0 +1,131 @@
+/* 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 "common/endian.h"
+
+#include "freescape/games/3dck/8bit.h"
+
+namespace Freescape {
+
+class KitZXSound : public Sound {
+public:
+ KitZXSound(Audio::Mixer *mixer, const byte *data, uint size);
+ ~KitZXSound() override { _mixer->stopHandle(_handle); }
+
+ void playSound(int index, Type type) override;
+ void stopSound(Type type) override;
+ bool isPlayingSound(Type type) const override;
+ bool isSoundAvailable(int index) const override { return index >= 0 && index <= 255; }
+
+private:
+ struct Sweep {
+ byte steps;
+ int8 step;
+ byte duration;
+ };
+ struct Effect {
+ uint16 period;
+ int8 step;
+ byte repeats;
+ Common::Array<Sweep> sweeps;
+ };
+ Effect _effects[16];
+ Audio::Mixer *_mixer;
+ Audio::SoundHandle _handle;
+ SizedPCSpeaker _speaker;
+ Type _type = kTypeNormal;
+};
+
+KitZXSound::KitZXSound(Audio::Mixer *mixer, const byte *data, uint size) : _mixer(mixer) {
+ for (uint index = 1; index < ARRAYSIZE(_effects); ++index) {
+ uint offset = 16 + data[index];
+ if (offset + 5 > size)
+ error("Invalid 3D Construction Kit Spectrum sound offset");
+ Effect &effect = _effects[index];
+ effect.period = READ_LE_UINT16(data + offset);
+ effect.step = int8(data[offset + 2]);
+ effect.repeats = data[offset + 3];
+ uint count = data[offset + 4] ? data[offset + 4] : 256;
+ offset += 5;
+ if (offset + 3 * count > size)
+ error("Invalid 3D Construction Kit Spectrum sound sweep");
+ for (uint i = 0; i < count; ++i) {
+ Sweep sweep = {data[offset], int8(data[offset + 1]), data[offset + 2]};
+ effect.sweeps.push_back(sweep);
+ offset += 3;
+ }
+ }
+}
+
+void KitZXSound::playSound(int index, Type type) {
+ if (!isSoundAvailable(index) || !(index & 15))
+ return;
+ stopSound(kTypeNormal);
+ _type = type;
+ const Effect &effect = _effects[index & 15];
+ uint16 base = effect.period;
+ uint repeats = effect.repeats ? effect.repeats : 256;
+ for (uint repeat = 0; repeat < repeats; ++repeat) {
+ uint16 period = base;
+ for (const Sweep &sweep : effect.sweeps) {
+ uint steps = sweep.steps ? sweep.steps : 256;
+ for (uint step = 0; step < steps; ++step) {
+ uint16 cycles = period ? (208 * sweep.duration) / period + 1 : 0;
+ int16 delay = 7 * period - 30;
+ if (delay < 0)
+ delay = 1;
+ // Runner 0xba90 uses 8 * HL + 236 T-states per beeper cycle.
+ float frequency = 3500000.0f / (8 * delay + 236);
+ uint32 duration = uint32(1000000.0f * (uint32(cycles) + 1) / frequency);
+ _speaker.playQueue(Audio::PCSpeaker::kWaveFormSquare, frequency, duration);
+ period = (period + sweep.step) & 0xfff;
+ }
+ }
+ base += effect.step;
+ }
+ _mixer->playStream(Audio::Mixer::kSFXSoundType, &_handle, &_speaker, -1,
+ kFreescapeDefaultVolume, 0, DisposeAfterUse::NO);
+}
+
+void KitZXSound::stopSound(Type type) {
+ if (type == kTypeNormal || type == _type) {
+ _mixer->stopHandle(_handle);
+ _speaker.stop();
+ }
+}
+
+bool KitZXSound::isPlayingSound(Type type) const {
+ return (type == kTypeNormal || type == _type) && _speaker.isPlaying();
+}
+
+void Kit8Engine::loadSoundsZX() {
+ Common::File file;
+ if (!file.open("3dkit.zx.code"))
+ error("Missing 3D Construction Kit Spectrum sounds");
+ byte data[118];
+ file.seek(0x1acc);
+ if (file.read(data, sizeof(data)) != sizeof(data))
+ error("Truncated 3D Construction Kit Spectrum sound bank");
+ _sound = new KitZXSound(_mixer, data, sizeof(data));
+}
+
+} // namespace Freescape
+
diff --git a/engines/freescape/zx_tape.cpp b/engines/freescape/zx_tape.cpp
index 3190231fab1..a72bde27be8 100644
--- a/engines/freescape/zx_tape.cpp
+++ b/engines/freescape/zx_tape.cpp
@@ -21,6 +21,7 @@
#include "freescape/zx_tape.h"
+#include "common/endian.h"
#include "common/file.h"
#include "common/formats/spectrum_tape.h"
#include "common/fs.h"
@@ -30,6 +31,101 @@
namespace Freescape {
+class ZxRecordingDecoder {
+public:
+ ZxRecordingDecoder(Common::SpectrumTapeBlocks &blocks) : _blocks(blocks) {}
+ bool decode(const Common::Array<byte> &data);
+ void finish();
+
+private:
+ void pulse(uint32 length);
+ void endBlock();
+ Common::SpectrumTapeBlocks &_blocks;
+ Common::Array<byte> _tap;
+ uint32 _length = 0;
+ uint _pilot = 0, _bits = 0;
+ int _level = -1, _halfBit = -1;
+ byte _byte = 0, _checksum = 0;
+ bool _sync = false, _reading = false;
+};
+
+void ZxRecordingDecoder::endBlock() {
+ if (!_bits && !_checksum && _tap.size() >= 2 && (_tap[0] == 0 || _tap[0] == 255)) {
+ Common::SpectrumTapeBlock block;
+ block.id = 0x10;
+ block.tap = _tap;
+ _blocks.push_back(block);
+ }
+ _tap.clear();
+ _bits = _byte = _checksum = 0;
+ _halfBit = -1;
+ _reading = false;
+}
+
+void ZxRecordingDecoder::pulse(uint32 length) {
+ // ROM tape encoding: a pilot and two sync pulses, then two pulses per bit.
+ if (_reading) {
+ int bit = length >= 400 && length < 1300 ? 0 : length >= 1300 && length <= 2050 ? 1 : -1;
+ if (bit < 0 || (_halfBit >= 0 && bit != _halfBit) || _tap.size() > 65536) {
+ endBlock();
+ } else {
+ if (_halfBit < 0)
+ _halfBit = bit;
+ else {
+ _halfBit = -1;
+ _byte = (_byte << 1) | bit;
+ if (++_bits == 8) {
+ _tap.push_back(_byte);
+ _checksum ^= _byte;
+ _bits = _byte = 0;
+ }
+ }
+ return;
+ }
+ }
+ if (_sync) {
+ _reading = length >= 400 && length <= 1100;
+ _sync = false;
+ } else if (length >= 1900 && length <= 2500) {
+ ++_pilot;
+ } else {
+ _sync = _pilot >= 256 && length >= 400 && length <= 1100;
+ _pilot = 0;
+ }
+}
+
+bool ZxRecordingDecoder::decode(const Common::Array<byte> &data) {
+ if (data.size() < 9 || !READ_LE_UINT16(data.data()) || data[4] < 1 || data[4] > 8 ||
+ READ_LE_UINT24(data.data() + 5) != data.size() - 8)
+ return false;
+ uint period = READ_LE_UINT16(data.data());
+ for (uint i = 8; i < data.size(); ++i) {
+ uint bits = i + 1 == data.size() ? data[4] : 8;
+ for (uint bit = 0; bit < bits; ++bit) {
+ int level = (data[i] >> (7 - bit)) & 1;
+ if (level != _level) {
+ if (_length)
+ pulse(_length);
+ _level = level;
+ _length = 0;
+ }
+ _length = MIN<uint32>(10000, _length + period);
+ }
+ }
+ if (READ_LE_UINT16(data.data() + 2))
+ finish();
+ return true;
+}
+
+void ZxRecordingDecoder::finish() {
+ if (_length)
+ pulse(_length);
+ endBlock();
+ _length = _pilot = 0;
+ _level = -1;
+ _sync = false;
+}
+
bool extractZxSpectrumTapeFiles(Common::SeekableReadStream &stream, const char *prefix, ZxTapeFileList &files) {
files.clear();
@@ -37,15 +133,33 @@ bool extractZxSpectrumTapeFiles(Common::SeekableReadStream &stream, const char *
if (!Common::parseSpectrumTape(stream, blocks))
return false;
+ Common::SpectrumTapeBlocks decoded;
+ ZxRecordingDecoder recording(decoded);
+ for (const Common::SpectrumTapeBlock &block : blocks) {
+ if (block.id == 0x15) {
+ if (!recording.decode(block.data))
+ return false;
+ } else {
+ recording.finish();
+ decoded.push_back(block);
+ }
+ }
+ recording.finish();
+
Common::Array<byte> title;
Common::Array<byte> border;
Common::Array<byte> data;
+ Common::Array<byte> kitData;
+ Common::Array<byte> code;
- for (const Common::SpectrumTapeBlock &block : blocks) {
+ for (const Common::SpectrumTapeBlock &block : decoded) {
if (block.tap.size() >= 2) {
Common::Array<byte> body;
body.assign(block.tap.begin() + 1, block.tap.end() - 1);
- if (body.size() == 6912) {
+ if (body.size() >= 160 && READ_BE_UINT32(body.data()) == MKTAG('K', 'I', 'T', 'S') &&
+ READ_LE_UINT16(body.data() + 4) == body.size()) {
+ kitData = body;
+ } else if (body.size() == 6912) {
title = border;
border = body;
} else if (body.size() >= data.size()) {
@@ -53,6 +167,10 @@ bool extractZxSpectrumTapeFiles(Common::SeekableReadStream &stream, const char *
}
}
}
+ if (!kitData.empty()) {
+ code = data;
+ data = kitData;
+ }
const struct {
const char *suffix;
@@ -60,7 +178,8 @@ bool extractZxSpectrumTapeFiles(Common::SeekableReadStream &stream, const char *
} outputs[] = {
{ "title", title },
{ "border", border },
- { "data", data }
+ { "data", data },
+ { "code", code }
};
for (uint i = 0; i < ARRAYSIZE(outputs); ++i) {
if (!outputs[i].payload.empty()) {
@@ -80,8 +199,12 @@ bool matchZxSpectrumTapeFiles(const ZxTapeFileList &files, const ADGameDescripti
for (const ADGameFileDescription *fileDesc = desc.filesDescriptions; matched && fileDesc->fileName; ++fileDesc) {
bool fileMatched = false;
Common::Path fileName(fileDesc->fileName, Common::Path::kNoSeparator);
+ Common::String unprefixedName(fileDesc->fileName);
+ if (unprefixedName.hasPrefix(Common::String(desc.gameId) + ".zx."))
+ unprefixedName.erase(0, strlen(desc.gameId));
for (uint i = 0; !fileMatched && i < files.size(); ++i) {
- fileMatched = files[i].name.equalsIgnoreCase(fileName) &&
+ fileMatched = (files[i].name.equalsIgnoreCase(fileName) ||
+ files[i].name.equalsIgnoreCase(Common::Path(unprefixedName, Common::Path::kNoSeparator))) &&
(fileDesc->fileSize == AD_NO_SIZE || fileDesc->fileSize == files[i].data.size());
if (fileMatched && fileDesc->md5) {
Common::MemoryReadStream stream(files[i].data.data(), files[i].data.size());
Commit: dc4b8e34362b7f2c216a98b316e8e8a9685c2e47
https://github.com/scummvm/scummvm/commit/dc4b8e34362b7f2c216a98b316e8e8a9685c2e47
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-09-08T13:33:09+02:00
Commit Message:
FREESCAPE: complete implementation of Colour opcode for zx spectrum 3dck games
Changed paths:
engines/freescape/language/execution_3dck8.cpp
diff --git a/engines/freescape/language/execution_3dck8.cpp b/engines/freescape/language/execution_3dck8.cpp
index a12583e29ee..2904c325e06 100644
--- a/engines/freescape/language/execution_3dck8.cpp
+++ b/engines/freescape/language/execution_3dck8.cpp
@@ -447,6 +447,11 @@ void Kit8Engine::executeCall(const FCLInstruction &instruction, ScriptState &scr
}
void Kit8Engine::executeColour(const FCLInstruction &instruction) {
+ if (isSpectrum() && instruction._source >= 3) {
+ // The Spectrum runner routes selectors 3 and above to the hardware border.
+ _palette[3] = instruction._destination & 7;
+ return;
+ }
uint index = instruction._source & 3;
_palette[index] = isSpectrum() ? instruction._destination & (index == 2 ? 1 : 7) : MIN<int>(26, instruction._destination);
applyPalette();
Commit: 28c76906affa04ee9c0887e66320753ba0c67b7b
https://github.com/scummvm/scummvm/commit/28c76906affa04ee9c0887e66320753ba0c67b7b
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-09-08T13:33:09+02:00
Commit Message:
FREESCAPE: initial code to support c64 3dck games
Changed paths:
A engines/freescape/games/3dck/c64.cpp
engines/freescape/detection.cpp
engines/freescape/games/3dck/8bit.cpp
engines/freescape/games/3dck/8bit.h
engines/freescape/games/3dck/8bitUI.cpp
engines/freescape/language/execution_3dck8.cpp
engines/freescape/metaengine.cpp
engines/freescape/module.mk
engines/freescape/sound/3dck_cpc.cpp
diff --git a/engines/freescape/detection.cpp b/engines/freescape/detection.cpp
index 911e369c072..8dae99dcd0f 100644
--- a/engines/freescape/detection.cpp
+++ b/engines/freescape/detection.cpp
@@ -1175,6 +1175,16 @@ const ADGameDescription gameDescriptions[] = {
ADGF_UNSTABLE,
GUIO2(GUIO_NOMIDI, GUIO_RENDERCPC)
},
+ {
+ "3dkit",
+ "A Chance in Hell",
+ AD_ENTRY2s("3D1", "a3a5df3cf7ef4fec315e2eb18e00de26", 39938,
+ "3D4", "t:252c24449148d4ba4a176b14fed3f15d", 8002),
+ Common::EN_ANY,
+ Common::kPlatformC64,
+ ADGF_UNSTABLE,
+ GUIO2(GUIO_NOMIDI, GUIO_RENDERC64)
+ },
{
"3dkit",
"Ciudadela Fantasma",
diff --git a/engines/freescape/games/3dck/8bit.cpp b/engines/freescape/games/3dck/8bit.cpp
index 4b95216f8e9..2cdf41901af 100644
--- a/engines/freescape/games/3dck/8bit.cpp
+++ b/engines/freescape/games/3dck/8bit.cpp
@@ -52,7 +52,21 @@ void Kit8Engine::loadAssets() {
requireBytes(dataFile, 160);
uint32 signature = dataFile.readUint32BE();
uint32 dataOffset = 0;
- if (isCPC() && signature != MKTAG('K', 'I', 'T', 'A') && signature != MKTAG('K', 'I', 'T', 'C')) {
+ uint32 dataEnd = dataFile.size();
+ if (isC64()) {
+ dataFile.seek(0);
+ if (dataFile.readUint16LE() != 0x0400)
+ error("Invalid 3D Construction Kit C64 runner address");
+ // The compiled runner embeds its world at $4a00.
+ dataOffset = 2 + 0x4a00 - 0x0400;
+ if (dataOffset + 160 > dataEnd)
+ error("Truncated 3D Construction Kit C64 runner");
+ dataFile.seek(dataOffset + 4);
+ uint16 size = dataFile.readUint16LE();
+ if (size < 160 || dataOffset + size > dataEnd)
+ error("Invalid 3D Construction Kit C64 world size");
+ dataEnd = dataOffset + size;
+ } else if (isCPC() && signature != MKTAG('K', 'I', 'T', 'A') && signature != MKTAG('K', 'I', 'T', 'C')) {
byte header[128];
dataFile.seek(0);
dataFile.read(header, sizeof(header));
@@ -63,11 +77,11 @@ void Kit8Engine::loadAssets() {
error("Invalid 3D Construction Kit AMSDOS header");
dataOffset = sizeof(header);
}
- Common::SeekableSubReadStream file(&dataFile, dataOffset, dataFile.size());
+ Common::SeekableSubReadStream file(&dataFile, dataOffset, dataEnd);
requireBytes(file, 160);
signature = file.readUint32BE();
bool validSignature = isSpectrum() ? signature == MKTAG('K', 'I', 'T', 'S') :
- signature == MKTAG('K', 'I', 'T', 'A') || signature == MKTAG('K', 'I', 'T', 'C');
+ signature == MKTAG('K', 'I', 'T', 'C') || (isCPC() && signature == MKTAG('K', 'I', 'T', 'A'));
if (!validSignature || file.readUint16LE() != file.size())
error("Unsupported 8-bit 3D Construction Kit data format");
uint16 procedures = file.readUint16LE();
@@ -92,6 +106,15 @@ void Kit8Engine::loadAssets() {
// Projection scales: 125 * 64 / (extent - 1), with a depth scale of 18.
int xScale = 8000 / (width - 1);
int yScale = 8000 / (height - 1);
+ if (isC64()) {
+ // C64 normalizes the projection scales to 64.
+ xScale = 64;
+ yScale = 64 * width / height;
+ if (yScale >= 64) {
+ xScale = 4096 / yScale;
+ yScale = 64;
+ }
+ }
if (xScale > 127 || yScale > 127)
error("Unsupported 8-bit 3D Construction Kit viewport size");
_fieldOfView = 2 * Math::rad2deg(atan(18.0f / xScale));
@@ -127,6 +150,9 @@ void Kit8Engine::loadAssets() {
Common::String text;
while (length--)
text += char(messageData.readByte());
+ // C64 message lengths include the editor's terminator.
+ if (isC64() && !text.empty() && byte(text.lastChar()) == 0xff)
+ text.deleteLastChar();
_kitMessages[id] = text;
}
Common::SeekableSubReadStream procedureData(&file, procedures, conditions);
@@ -195,7 +221,7 @@ Area *Kit8Engine::loadArea(Common::SeekableReadStream &file) {
AreaData &data = _areaData[id];
for (uint i = 0; i < 4; i++) {
data.palette[i] = file.readByte();
- if (data.palette[i] > (isSpectrum() ? (i == 2 ? 1 : 7) : 26))
+ if (data.palette[i] > (isSpectrum() ? (i == 2 ? 1 : 7) : isC64() ? 15 : 26))
error("Invalid 8-bit 3D Construction Kit palette");
}
byte scale = file.readByte();
diff --git a/engines/freescape/games/3dck/8bit.h b/engines/freescape/games/3dck/8bit.h
index 24e07b8e590..fb6f3359d7a 100644
--- a/engines/freescape/games/3dck/8bit.h
+++ b/engines/freescape/games/3dck/8bit.h
@@ -71,11 +71,13 @@ private:
GeometricObject *loadGeometricObject(Common::SeekableReadStream &file, const byte header[9]);
void loadPresentation();
void loadPresentationZX();
+ void loadPresentationC64();
void loadSounds();
void loadSoundsZX();
void playPendingSound();
void applyPalette();
void applyPaletteZX();
+ void applyPaletteC64();
void setAttributesZX(const Common::Rect &rect, byte color);
void setMovementMode(byte mode);
void readSystemVariables();
diff --git a/engines/freescape/games/3dck/8bitUI.cpp b/engines/freescape/games/3dck/8bitUI.cpp
index f37c522ddb6..ff6021df423 100644
--- a/engines/freescape/games/3dck/8bitUI.cpp
+++ b/engines/freescape/games/3dck/8bitUI.cpp
@@ -46,6 +46,10 @@ void Kit8Engine::loadPresentation() {
loadPresentationZX();
return;
}
+ if (isC64()) {
+ loadPresentationC64();
+ return;
+ }
// Shade 0 is transparent; the CPC runner indexes these patterns with shade - 1.
static const byte patterns[15][4] = {
{0x00, 0x00, 0x00, 0x00},
@@ -89,6 +93,10 @@ void Kit8Engine::applyPalette() {
applyPaletteZX();
return;
}
+ if (isC64()) {
+ applyPaletteC64();
+ return;
+ }
_gfx->_fourColorBackground = kCPCInks[_palette[0]];
_gfx->_underFireBackgroundColor = kCPCInks[_palette[2]];
_gfx->_paperColor = kCPCInks[_palette[1]];
@@ -102,8 +110,8 @@ void Kit8Engine::printText(const Common::String &text, byte x, byte y, byte colo
if (!_textOutputEnabled || x >= _screenW / 8 || y >= _screenH / 8)
return;
Graphics::DosFont font;
- byte foreground = isSpectrum() ? 1 : color & 3;
- byte background = isSpectrum() ? 0 : ((color >> 3) & 1) | ((color >> 1) & 2);
+ byte foreground = isSpectrum() ? 1 : color & (isC64() ? 15 : 3);
+ byte background = isSpectrum() ? 0 : isC64() ? color >> 4 : ((color >> 3) & 1) | ((color >> 1) & 2);
for (uint i = 0; i < text.size() && x < _screenW / 8; i++, x++) {
byte chr = text[i];
if (isSpectrum())
@@ -145,14 +153,16 @@ void Kit8Engine::updateInstruments() {
Common::Rect bar(8 * x, 8 * y, 8 * (x + (type == 2 ? length : 1)), 8 * (y + (type == 3 ? length : 1)));
if (isSpectrum())
setAttributesZX(bar, color);
- _scriptSurface.fillRect(bar, isSpectrum() ? 0 : (color >> 2) & 3);
+ _scriptSurface.fillRect(bar, isSpectrum() ? 0 : isC64() ? color >> 4 : (color >> 2) & 3);
int filled = MIN<int>(value, 8 * length);
+ if (isC64() && type == 2)
+ filled &= ~1;
if (type == 2)
bar.right = bar.left + filled;
else
bar.top = bar.bottom - filled;
if (!bar.isEmpty())
- _scriptSurface.fillRect(bar, isSpectrum() ? 1 : color & 3);
+ _scriptSurface.fillRect(bar, isSpectrum() ? 1 : color & (isC64() ? 15 : 3));
}
}
}
@@ -160,9 +170,9 @@ void Kit8Engine::updateInstruments() {
void Kit8Engine::drawUI() {
uint32 colors[16];
bool flash = (g_system->getMillis() / 320) & 1;
- for (uint i = 0; i < (isSpectrum() ? 16 : 4); i++) {
+ for (uint i = 0; i < (isSpectrum() || isC64() ? 16 : 4); i++) {
byte r, g, b;
- if (isSpectrum())
+ if (isSpectrum() || isC64())
_gfx->readFromPalette(i, r, g, b);
else
_gfx->selectColorFromFourColorPalette(i, r, g, b);
@@ -173,6 +183,8 @@ void Kit8Engine::drawUI() {
byte pen = _scriptSurface.getPixel(x, y);
if (pen == 255)
pen = _borderSurface.getPixel(x, y);
+ if (isC64() && pen == 16)
+ pen = _palette[0];
if (isSpectrum() && pen != 255) {
byte attr = _attributes[(y / 8) * 32 + x / 8];
if ((attr & 128) && flash)
diff --git a/engines/freescape/games/3dck/c64.cpp b/engines/freescape/games/3dck/c64.cpp
new file mode 100644
index 00000000000..d08183b2be0
--- /dev/null
+++ b/engines/freescape/games/3dck/c64.cpp
@@ -0,0 +1,86 @@
+/* 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 "common/endian.h"
+
+#include "freescape/games/3dck/8bit.h"
+
+namespace Freescape {
+
+static Common::Array<byte> loadKitC64Program(const char *name, uint16 address, uint32 size) {
+ Common::File file;
+ if (!file.open(name) || file.size() != size + 2 || file.readUint16LE() != address)
+ error("Invalid 3D Construction Kit C64 file %s", name);
+ Common::Array<byte> data;
+ data.resize(size);
+ if (file.read(data.data(), size) != size)
+ error("Truncated 3D Construction Kit C64 file %s", name);
+ return data;
+}
+
+void Kit8Engine::loadPresentationC64() {
+ Common::Array<byte> runner = loadKitC64Program("3D1", 0x0400, 39936);
+ Common::Array<byte> characters = loadKitC64Program("3D3", 0xc400, 3072);
+ Common::Array<byte> bitmap = loadKitC64Program("3D4", 0x3800, 8000);
+ memcpy(_colorPatterns, runner.data() + 0x672e - 0x0400, sizeof(_colorPatterns));
+
+ for (int y = 0; y < _screenH; ++y) {
+ for (int x = 0; x < _screenW; ++x) {
+ uint cell = (y / 8) * 40 + x / 8;
+ byte pen = (bitmap[8 * cell + (y & 7)] >> (6 - (x & 6))) & 3;
+ byte screen = runner[0x3000 - 0x0400 + cell];
+ // Index 16 follows the VIC background colour of the current area.
+ byte color = pen == 0 ? 16 : pen == 1 ? screen >> 4 : pen == 2 ? screen & 15 :
+ runner[0x3400 - 0x0400 + cell] & 15;
+ _borderSurface.setPixel(x, y, color);
+ }
+ }
+ _borderSurface.fillRect(_viewArea, 255);
+
+ uint16 font = READ_LE_UINT16(runner.data() + 0x1a7b - 0x0400);
+ if (font < 0xc400 || font + 64 * 8 > 0xc400 + characters.size())
+ error("Invalid 3D Construction Kit C64 font address");
+ for (uint chr = 32; chr < 128; ++chr) {
+ uint glyph = chr == 32 ? 0 : chr >= 65 ? chr - 64 : chr - 21;
+ for (uint row = 0; row < 8; ++row) {
+ byte pixels = characters[font - 0xc400 + 8 * glyph + row];
+ byte bits = 0;
+ for (uint col = 0; col < 4; ++col) {
+ if (((pixels >> (6 - 2 * col)) & 3) == 2)
+ bits |= 3 << (6 - 2 * col);
+ }
+ _fontData[chr - 32][row] = bits;
+ }
+ }
+ _hasFont = true;
+}
+
+void Kit8Engine::applyPaletteC64() {
+ _gfx->_fourColorBackground = _palette[0];
+ _gfx->_underFireBackgroundColor = _palette[2];
+ _gfx->_paperColor = _palette[1];
+ _gfx->_inkColor = _palette[3];
+ _currentArea->_usualBackgroundColor = 1;
+ _currentArea->_skyColor = 1;
+ _currentArea->_underFireBackgroundColor = 4;
+}
+
+} // namespace Freescape
diff --git a/engines/freescape/language/execution_3dck8.cpp b/engines/freescape/language/execution_3dck8.cpp
index 2904c325e06..8e41e879b52 100644
--- a/engines/freescape/language/execution_3dck8.cpp
+++ b/engines/freescape/language/execution_3dck8.cpp
@@ -447,13 +447,16 @@ void Kit8Engine::executeCall(const FCLInstruction &instruction, ScriptState &scr
}
void Kit8Engine::executeColour(const FCLInstruction &instruction) {
+ if (isC64() && instruction._source >= 4)
+ return;
if (isSpectrum() && instruction._source >= 3) {
// The Spectrum runner routes selectors 3 and above to the hardware border.
_palette[3] = instruction._destination & 7;
return;
}
uint index = instruction._source & 3;
- _palette[index] = isSpectrum() ? instruction._destination & (index == 2 ? 1 : 7) : MIN<int>(26, instruction._destination);
+ _palette[index] = isSpectrum() ? instruction._destination & (index == 2 ? 1 : 7) :
+ isC64() ? instruction._destination & 15 : MIN<int>(26, instruction._destination);
applyPalette();
}
diff --git a/engines/freescape/metaengine.cpp b/engines/freescape/metaengine.cpp
index 0ce141c5b8c..add43801236 100644
--- a/engines/freescape/metaengine.cpp
+++ b/engines/freescape/metaengine.cpp
@@ -211,7 +211,7 @@ Common::Error FreescapeMetaEngine::createInstance(OSystem *syst, Engine **engine
} else if (Common::String(gd->gameId) == "castlemaster" || Common::String(gd->gameId) == "castlemaster2") {
*engine = (Engine *)new Freescape::CastleEngine(syst, gd);
} else if (Common::String(gd->gameId) == "3dkit") {
- if (gd->platform == Common::kPlatformAmstradCPC || gd->platform == Common::kPlatformZX)
+ if (gd->platform == Common::kPlatformAmstradCPC || gd->platform == Common::kPlatformZX || gd->platform == Common::kPlatformC64)
*engine = new Freescape::Kit8Engine(syst, gd);
else
*engine = new Freescape::KitEngine(syst, gd);
diff --git a/engines/freescape/module.mk b/engines/freescape/module.mk
index f3d7522dedb..ef50be2b776 100644
--- a/engines/freescape/module.mk
+++ b/engines/freescape/module.mk
@@ -55,6 +55,7 @@ MODULE_OBJS := \
games/3dck/3dck.o \
games/3dck/8bit.o \
games/3dck/8bitUI.o \
+ games/3dck/c64.o \
games/3dck/ui.o \
games/3dck/zx.o \
games/palettes.o \
diff --git a/engines/freescape/sound/3dck_cpc.cpp b/engines/freescape/sound/3dck_cpc.cpp
index ff45be3d756..289376d225c 100644
--- a/engines/freescape/sound/3dck_cpc.cpp
+++ b/engines/freescape/sound/3dck_cpc.cpp
@@ -184,6 +184,10 @@ int KitCPCSound::readBuffer(int16 *buffer, int samples) {
}
void Kit8Engine::loadSounds() {
+ if (isC64()) {
+ warning("3D Construction Kit C64 sound effects are not implemented");
+ return;
+ }
if (isSpectrum()) {
loadSoundsZX();
return;
Commit: d88ede37a6cb6e350a86fe4670153b76143fd246
https://github.com/scummvm/scummvm/commit/d88ede37a6cb6e350a86fe4670153b76143fd246
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-09-08T13:33:09+02:00
Commit Message:
COMMON: moved unp64 to common/compression
Changed paths:
A common/compression/unp64.h
A common/compression/unp64/6502_emu.cpp
A common/compression/unp64/6502_emu.h
A common/compression/unp64/exo_util.cpp
A common/compression/unp64/exo_util.h
A common/compression/unp64/scanners/action_packer.cpp
A common/compression/unp64/scanners/byte_boiler.cpp
A common/compression/unp64/scanners/caution.cpp
A common/compression/unp64/scanners/ccs.cpp
A common/compression/unp64/scanners/cruel.cpp
A common/compression/unp64/scanners/eca.cpp
A common/compression/unp64/scanners/exomizer.cpp
A common/compression/unp64/scanners/expert.cpp
A common/compression/unp64/scanners/master_compressor.cpp
A common/compression/unp64/scanners/megabyte.cpp
A common/compression/unp64/scanners/pu_crunch.cpp
A common/compression/unp64/scanners/scanners.cpp
A common/compression/unp64/scanners/section8.cpp
A common/compression/unp64/scanners/tbc_multicomp.cpp
A common/compression/unp64/scanners/tcs_crunch.cpp
A common/compression/unp64/scanners/xtc.cpp
A common/compression/unp64/unp64.cpp
A common/compression/unp64/unp64.h
R engines/glk/scott/unp64/6502_emu.cpp
R engines/glk/scott/unp64/6502_emu.h
R engines/glk/scott/unp64/exo_util.cpp
R engines/glk/scott/unp64/exo_util.h
R engines/glk/scott/unp64/scanners/action_packer.cpp
R engines/glk/scott/unp64/scanners/byte_boiler.cpp
R engines/glk/scott/unp64/scanners/caution.cpp
R engines/glk/scott/unp64/scanners/ccs.cpp
R engines/glk/scott/unp64/scanners/cruel.cpp
R engines/glk/scott/unp64/scanners/eca.cpp
R engines/glk/scott/unp64/scanners/exomizer.cpp
R engines/glk/scott/unp64/scanners/expert.cpp
R engines/glk/scott/unp64/scanners/master_compressor.cpp
R engines/glk/scott/unp64/scanners/megabyte.cpp
R engines/glk/scott/unp64/scanners/pu_crunch.cpp
R engines/glk/scott/unp64/scanners/scanners.cpp
R engines/glk/scott/unp64/scanners/section8.cpp
R engines/glk/scott/unp64/scanners/tbc_multicomp.cpp
R engines/glk/scott/unp64/scanners/tcs_crunch.cpp
R engines/glk/scott/unp64/scanners/xtc.cpp
R engines/glk/scott/unp64/unp64.cpp
R engines/glk/scott/unp64/unp64.h
R engines/glk/scott/unp64/unp64_interface.h
common/compression/module.mk
configure
engines/glk/configure.engine
engines/glk/module.mk
engines/glk/scott/c64_checksums.cpp
engines/glk/scott/globals.h
diff --git a/common/compression/module.mk b/common/compression/module.mk
index 05c6ec4f545..8ef332a20ee 100644
--- a/common/compression/module.mk
+++ b/common/compression/module.mk
@@ -15,6 +15,29 @@ MODULE_OBJS := \
unzip.o \
vise.o
+ifdef USE_UNP64
+MODULE_OBJS += \
+ unp64/unp64.o \
+ unp64/6502_emu.o \
+ unp64/exo_util.o \
+ unp64/scanners/scanners.o \
+ unp64/scanners/action_packer.o \
+ unp64/scanners/byte_boiler.o \
+ unp64/scanners/caution.o \
+ unp64/scanners/ccs.o \
+ unp64/scanners/cruel.o \
+ unp64/scanners/eca.o \
+ unp64/scanners/exomizer.o \
+ unp64/scanners/expert.o \
+ unp64/scanners/master_compressor.o \
+ unp64/scanners/megabyte.o \
+ unp64/scanners/pu_crunch.o \
+ unp64/scanners/section8.o \
+ unp64/scanners/tbc_multicomp.o \
+ unp64/scanners/tcs_crunch.o \
+ unp64/scanners/xtc.o
+endif
+
ifdef USE_ZLIB
MODULE_OBJS += \
zlib.o
diff --git a/engines/glk/scott/unp64/unp64_interface.h b/common/compression/unp64.h
similarity index 66%
rename from engines/glk/scott/unp64/unp64_interface.h
rename to common/compression/unp64.h
index c8b262a233e..0e532c50869 100644
--- a/engines/glk/scott/unp64/unp64_interface.h
+++ b/common/compression/unp64.h
@@ -19,17 +19,21 @@
*
*/
-#ifndef GLK_SCOTT_UNP64_INTERFACE_H
-#define GLK_SCOTT_UNP64_INTERFACE_H
+#ifndef COMMON_COMPRESSION_UNP64_H
+#define COMMON_COMPRESSION_UNP64_H
-#include "glk/scott/types.h"
+#include "common/scummsys.h"
-namespace Glk {
-namespace Scott {
+namespace Common {
+namespace Unp64 {
-int unp64(uint8_t *compressed, size_t length, uint8_t *destinationBuffer, size_t *finalLength, const char *settings);
+/**
+ * Unpack a C64 PRG, preserving its two-byte load address.
+ * destinationBuffer must hold 65536 bytes. Returns zero on failure.
+ */
+int unp64(const byte *compressed, uint32 length, byte *destinationBuffer, uint32 *finalLength, const char *settings);
-} // End of namespace Scott
-} // End of namespace Glk
+} // End of namespace Unp64
+} // End of namespace Common
-#endif
+#endif
diff --git a/engines/glk/scott/unp64/6502_emu.cpp b/common/compression/unp64/6502_emu.cpp
similarity index 93%
rename from engines/glk/scott/unp64/6502_emu.cpp
rename to common/compression/unp64/6502_emu.cpp
index 527de32bd44..ddeac291362 100644
--- a/engines/glk/scott/unp64/6502_emu.cpp
+++ b/common/compression/unp64/6502_emu.cpp
@@ -41,11 +41,10 @@
*
*/
-#include "glk/scott/globals.h"
-#include "glk/scott/unp64/6502_emu.h"
+#include "common/compression/unp64/6502_emu.h"
-namespace Glk {
-namespace Scott {
+namespace Common {
+namespace Unp64 {
#define FLAG_N 128
#define FLAG_V 64
@@ -55,13 +54,13 @@ namespace Scott {
#define FLAG_C 1
struct ArgEa {
- uint16_t _value;
+ uint16 _value;
};
struct ArgRelative {
- int8_t _value;
+ int8 _value;
};
struct ArgImmediate {
- uint8_t _value;
+ uint8 _value;
};
union InstArg {
@@ -86,7 +85,7 @@ struct ModeInfo {
struct InstInfo {
OpInfo *_op;
ModeInfo *_mode;
- uint8_t _cycles;
+ uint8 _cycles;
};
#define MODE_IMMEDIATE 0
@@ -116,30 +115,30 @@ static int mode_zp(CpuCtx *r, InstArg *arg) {
}
static int mode_zpx(CpuCtx *r, InstArg *arg) { /* iAN: ldx #1 lda $ff,x should fetch from $00 and not $100 */
- uint8_t lsbLo = (r->_mem[r->_pc + 1] + r->_x) & 0xff;
+ uint8 lsbLo = (r->_mem[r->_pc + 1] + r->_x) & 0xff;
arg->_ea._value = lsbLo;
r->_pc += 2;
return MODE_ZERO_PAGE_X;
}
static int mode_zpy(CpuCtx *r, InstArg *arg) { /* iAN: ldy #1 ldx $ff,y should fetch from $00 and not $100 */
- uint8_t lsbLo = (r->_mem[r->_pc + 1] + r->_y) & 0xff;
+ uint8 lsbLo = (r->_mem[r->_pc + 1] + r->_y) & 0xff;
arg->_ea._value = lsbLo;
r->_pc += 2;
return MODE_ZERO_PAGE_Y;
}
static int mode_abs(CpuCtx *r, InstArg *arg) {
- uint16_t offset = r->_mem[r->_pc + 1];
- uint16_t base = r->_mem[r->_pc + 2] << 8;
+ uint16 offset = r->_mem[r->_pc + 1];
+ uint16 base = r->_mem[r->_pc + 2] << 8;
arg->_ea._value = base + offset;
r->_pc += 3;
return MODE_ABSOLUTE;
}
static int mode_absx(CpuCtx *r, InstArg *arg) {
- uint16_t offset = r->_mem[r->_pc + 1] + r->_x;
- uint16_t base = r->_mem[r->_pc + 2] << 8;
+ uint16 offset = r->_mem[r->_pc + 1] + r->_x;
+ uint16 base = r->_mem[r->_pc + 2] << 8;
arg->_ea._value = base + offset;
r->_pc += 3;
r->_cycles += (offset > 255);
@@ -147,8 +146,8 @@ static int mode_absx(CpuCtx *r, InstArg *arg) {
}
static int mode_absy(CpuCtx *r, InstArg *arg) {
- uint16_t offset = r->_mem[r->_pc + 1] + r->_y;
- uint16_t base = r->_mem[r->_pc + 2] << 8;
+ uint16 offset = r->_mem[r->_pc + 1] + r->_y;
+ uint16 base = r->_mem[r->_pc + 2] << 8;
arg->_ea._value = base + offset;
r->_pc += 3;
r->_cycles += (offset > 255);
@@ -167,20 +166,20 @@ static int mode_ind(CpuCtx *r, InstArg *arg) {
}
static int mode_indx(CpuCtx *r, InstArg *arg) {
- uint8_t lsbLo = r->_mem[r->_pc + 1] + r->_x;
- uint8_t msbLo = lsbLo + 1;
- uint16_t base = r->_mem[msbLo] << 8;
- uint16_t offset = r->_mem[lsbLo];
+ uint8 lsbLo = r->_mem[r->_pc + 1] + r->_x;
+ uint8 msbLo = lsbLo + 1;
+ uint16 base = r->_mem[msbLo] << 8;
+ uint16 offset = r->_mem[lsbLo];
arg->_ea._value = base + offset;
r->_pc += 2;
return MODE_INDIRECT_X;
}
static int mode_indy(CpuCtx *r, InstArg *arg) {
- uint8_t lsbLo = r->_mem[r->_pc + 1];
- uint8_t msbLo = lsbLo + 1;
- uint16_t base = r->_mem[msbLo] << 8;
- uint16_t offset = r->_mem[lsbLo] + r->_y;
+ uint8 lsbLo = r->_mem[r->_pc + 1];
+ uint8 msbLo = lsbLo + 1;
+ uint16 base = r->_mem[msbLo] << 8;
+ uint16 offset = r->_mem[lsbLo] + r->_y;
arg->_ea._value = base + offset;
r->_pc += 2;
r->_cycles += (offset > 255);
@@ -188,7 +187,7 @@ static int mode_indy(CpuCtx *r, InstArg *arg) {
}
static int mode_rel(CpuCtx *r, InstArg *arg) {
- arg->_rel._value = (int8_t)r->_mem[r->_pc + 1];
+ arg->_rel._value = (int8)r->_mem[r->_pc + 1];
r->_pc += 2;
return MODE_RELATIVE;
}
@@ -217,7 +216,7 @@ static ModeInfo mode_rel_o = { &mode_rel, "$%02x" };
static ModeInfo mode_acc_o = { &mode_acc, "a" };
static ModeInfo mode_imp_o = { &mode_imp, nullptr };
-static void updateFlagsNz(CpuCtx *r, uint8_t value) {
+static void updateFlagsNz(CpuCtx *r, uint8 value) {
r->_flags &= ~(FLAG_Z | FLAG_N);
r->_flags |= (value == 0 ? FLAG_Z : 0) | (value & FLAG_N);
}
@@ -226,8 +225,8 @@ static void updateCarry(CpuCtx *r, int boolean) {
r->_flags = (r->_flags & ~FLAG_C) | (boolean != 0 ? FLAG_C : 0);
}
-static uint16_t subtract(CpuCtx *r, int carry, uint8_t val1, uint8_t value) {
- uint16_t target = val1 - value - (1 - !!carry);
+static uint16 subtract(CpuCtx *r, int carry, uint8 val1, uint8 value) {
+ uint16 target = val1 - value - (1 - !!carry);
updateCarry(r, !(target & 256));
updateFlagsNz(r, target & 255);
return target;
@@ -238,8 +237,8 @@ static void update_overflow(CpuCtx *r, int boolean) {
}
static void op_adc(CpuCtx *r, int mode, InstArg *arg) {
- uint8_t value;
- uint16_t result;
+ uint8 value;
+ uint16 result;
switch (mode) {
case MODE_IMMEDIATE:
value = arg->_imm._value;
@@ -256,7 +255,7 @@ static void op_adc(CpuCtx *r, int mode, InstArg *arg) {
}
static void op_and(CpuCtx *r, int mode, InstArg *arg) {
- uint8_t value;
+ uint8 value;
switch (mode) {
case MODE_IMMEDIATE:
value = arg->_imm._value;
@@ -270,7 +269,7 @@ static void op_and(CpuCtx *r, int mode, InstArg *arg) {
}
static void op_asl(CpuCtx *r, int mode, InstArg *arg) {
- uint8_t *valuep;
+ uint8 *valuep;
switch (mode) {
case MODE_ACCUMULATOR:
valuep = &r->_a;
@@ -285,7 +284,7 @@ static void op_asl(CpuCtx *r, int mode, InstArg *arg) {
}
static void branch(CpuCtx *r, InstArg *arg) {
- uint16_t target = r->_pc + arg->_rel._value;
+ uint16 target = r->_pc + arg->_rel._value;
r->_cycles += 1 + ((target & ~255) != (r->_pc & ~255));
r->_pc = target;
}
@@ -369,7 +368,7 @@ static void op_clv(CpuCtx *r, int mode, InstArg *arg) {
static void op_cmp(CpuCtx *r, int mode, InstArg *arg) {
- uint8_t value;
+ uint8 value;
switch (mode) {
case MODE_IMMEDIATE:
value = arg->_imm._value;
@@ -382,7 +381,7 @@ static void op_cmp(CpuCtx *r, int mode, InstArg *arg) {
}
static void op_cpx(CpuCtx *r, int mode, InstArg *arg) {
- uint8_t value;
+ uint8 value;
switch (mode) {
case MODE_IMMEDIATE:
value = arg->_imm._value;
@@ -395,7 +394,7 @@ static void op_cpx(CpuCtx *r, int mode, InstArg *arg) {
}
static void op_cpy(CpuCtx *r, int mode, InstArg *arg) {
- uint8_t value;
+ uint8 value;
switch (mode) {
case MODE_IMMEDIATE:
value = arg->_imm._value;
@@ -423,7 +422,7 @@ static void op_dey(CpuCtx *r, int mode, InstArg *arg) {
}
static void op_eor(CpuCtx *r, int mode, InstArg *arg) {
- uint8_t value;
+ uint8 value;
switch (mode) {
case MODE_IMMEDIATE:
value = arg->_imm._value;
@@ -463,7 +462,7 @@ static void op_jsr(CpuCtx *r, int mode, InstArg *arg) {
}
static void op_lda(CpuCtx *r, int mode, InstArg *arg) {
- uint8_t value;
+ uint8 value;
switch (mode) {
case MODE_IMMEDIATE:
value = arg->_imm._value;
@@ -477,7 +476,7 @@ static void op_lda(CpuCtx *r, int mode, InstArg *arg) {
}
static void op_ldx(CpuCtx *r, int mode, InstArg *arg) {
- uint8_t value;
+ uint8 value;
switch (mode) {
case MODE_IMMEDIATE:
value = arg->_imm._value;
@@ -491,7 +490,7 @@ static void op_ldx(CpuCtx *r, int mode, InstArg *arg) {
}
static void op_ldy(CpuCtx *r, int mode, InstArg *arg) {
- uint8_t value;
+ uint8 value;
switch (mode) {
case MODE_IMMEDIATE:
value = arg->_imm._value;
@@ -505,7 +504,7 @@ static void op_ldy(CpuCtx *r, int mode, InstArg *arg) {
}
static void op_lsr(CpuCtx *r, int mode, InstArg *arg) {
- uint8_t *valuep;
+ uint8 *valuep;
switch (mode) {
case MODE_ACCUMULATOR:
valuep = &r->_a;
@@ -522,7 +521,7 @@ static void op_lsr(CpuCtx *r, int mode, InstArg *arg) {
static void op_nop(CpuCtx *r, int mode, InstArg *arg) {}
static void op_ora(CpuCtx *r, int mode, InstArg *arg) {
- uint8_t value;
+ uint8 value;
switch (mode) {
case MODE_IMMEDIATE:
value = arg->_imm._value;
@@ -553,8 +552,8 @@ static void op_plp(CpuCtx *r, int mode, InstArg *arg) {
}
static void op_rol(CpuCtx *r, int mode, InstArg *arg) {
- uint8_t *valuep;
- uint8_t old_flags;
+ uint8 *valuep;
+ uint8 old_flags;
switch (mode) {
case MODE_ACCUMULATOR:
valuep = &r->_a;
@@ -571,8 +570,8 @@ static void op_rol(CpuCtx *r, int mode, InstArg *arg) {
}
static void op_ror(CpuCtx *r, int mode, InstArg *arg) {
- uint8_t *valuep;
- uint8_t old_flags;
+ uint8 *valuep;
+ uint8 old_flags;
switch (mode) {
case MODE_ACCUMULATOR:
valuep = &r->_a;
@@ -601,8 +600,8 @@ static void op_rts(CpuCtx *r, int mode, InstArg *arg) {
}
static void op_sbc(CpuCtx *r, int mode, InstArg *arg) {
- uint8_t value;
- uint16_t result;
+ uint8 value;
+ uint16 result;
switch (mode) {
case MODE_IMMEDIATE:
value = arg->_imm._value;
@@ -672,7 +671,7 @@ static void op_tya(CpuCtx *r, int mode, InstArg *arg) {
/* iAN */
static void op_anc(CpuCtx *r, int mode, InstArg *arg) {
- uint8_t value;
+ uint8 value;
switch (mode) {
case MODE_IMMEDIATE:
value = arg->_imm._value;
@@ -1123,13 +1122,13 @@ static InstInfo g_ops[256] = {
{ &op_isb_o, &mode_absx_o, 7 }, /* $ff isb $ffff,x */
};
-int flipfire(void) {
- _G(_retfire) ^= 0x90;
- return _G(_retfire);
+static int flipfire(CpuCtx *r) {
+ r->_retfire ^= 0x90;
+ return r->_retfire;
}
-int flipspace(void) {
- _G(_retspace) ^= 0x10;
- return _G(_retspace);
+static int flipspace(CpuCtx *r) {
+ r->_retspace ^= 0x10;
+ return r->_retspace;
}
int nextInst(CpuCtx* r) {
@@ -1173,13 +1172,13 @@ int nextInst(CpuCtx* r) {
if (arg->_ea._value == 0xd011) {
switch (opCode) {
case 0x8D:
- _G(_byted011)[0] = r->_a & 0x7f;
+ r->_byted011[0] = r->_a & 0x7f;
break;
case 0x8E:
- _G(_byted011)[0] = r->_x & 0x7f;
+ r->_byted011[0] = r->_x & 0x7f;
break;
case 0x8C:
- _G(_byted011)[0] = r->_y & 0x7f;
+ r->_byted011[0] = r->_y & 0x7f;
break;
default:
break;
@@ -1187,16 +1186,16 @@ int nextInst(CpuCtx* r) {
}
WriteToIO = 1;
} else {
- _G(_byted011)[1] = (r->_cycles / 0x3f) % 0x157;
- _G(_byted011)[0] = (_G(_byted011)[0] & 0x7f) | ((_G(_byted011)[1] & 0x100) >> 1);
- _G(_byted011)[1] &= 0xff;
+ r->_byted011[1] = (r->_cycles / 0x3f) % 0x157;
+ r->_byted011[0] = (r->_byted011[0] & 0x7f) | ((r->_byted011[1] & 0x100) >> 1);
+ r->_byted011[1] &= 0xff;
switch (opCode) {
case 0xad:
case 0xaf: /* lda $ffff / lax $ffff */
if ((arg->_ea._value == 0xd011) || (arg->_ea._value == 0xd012)) {
r->_cycles += g_ops[opCode]._cycles;
- r->_a = _G(_byted011)[arg->_ea._value - 0xd011];
+ r->_a = r->_byted011[arg->_ea._value - 0xd011];
if (opCode == 0xaf)
r->_x = r->_a;
updateFlagsNz(r, r->_a);
@@ -1208,9 +1207,9 @@ int nextInst(CpuCtx* r) {
if (arg->_ea._value == 0xdc00 || arg->_ea._value == 0xdc01) {
r->_cycles += g_ops[opCode]._cycles;
if (arg->_ea._value == 0xdc00)
- r->_a = flipfire();
+ r->_a = flipfire(r);
else
- r->_a = flipspace();
+ r->_a = flipspace(r);
if (opCode == 0xaf)
r->_x = r->_a;
updateFlagsNz(r, r->_a);
@@ -1234,9 +1233,9 @@ int nextInst(CpuCtx* r) {
if (arg->_ea._value == 0xdc00 || arg->_ea._value == 0xdc01) {
r->_cycles += g_ops[opCode]._cycles;
if (arg->_ea._value == 0xdc00)
- r->_a &= flipfire();
+ r->_a &= flipfire(r);
else
- r->_a &= flipspace();
+ r->_a &= flipspace(r);
updateFlagsNz(r, r->_a);
WriteToIO = 6;
@@ -1247,7 +1246,7 @@ int nextInst(CpuCtx* r) {
if ((arg->_ea._value == 0xd011) || (arg->_ea._value == 0xd012)) {
r->_cycles += g_ops[opCode]._cycles;
- r->_x = _G(_byted011)[arg->_ea._value - 0xd011];
+ r->_x = r->_byted011[arg->_ea._value - 0xd011];
updateFlagsNz(r, r->_x);
WriteToIO = 5;
break;
@@ -1255,9 +1254,9 @@ int nextInst(CpuCtx* r) {
if (arg->_ea._value == 0xdc00 || arg->_ea._value == 0xdc01) {
r->_cycles += g_ops[opCode]._cycles;
if (arg->_ea._value == 0xdc00)
- r->_x = flipfire();
+ r->_x = flipfire(r);
else
- r->_x = flipspace();
+ r->_x = flipspace(r);
updateFlagsNz(r, r->_x);
WriteToIO = 6;
break;
@@ -1267,7 +1266,7 @@ int nextInst(CpuCtx* r) {
if ((arg->_ea._value == 0xd011) || (arg->_ea._value == 0xd012)) {
r->_cycles += g_ops[opCode]._cycles;
- r->_y = _G(_byted011)[arg->_ea._value - 0xd011];
+ r->_y = r->_byted011[arg->_ea._value - 0xd011];
updateFlagsNz(r, r->_y);
WriteToIO = 5;
break;
@@ -1275,9 +1274,9 @@ int nextInst(CpuCtx* r) {
if (arg->_ea._value == 0xdc00 || arg->_ea._value == 0xdc01) {
r->_cycles += g_ops[opCode]._cycles;
if (arg->_ea._value == 0xdc00)
- r->_y = flipfire();
+ r->_y = flipfire(r);
else
- r->_y = flipspace();
+ r->_y = flipspace(r);
updateFlagsNz(r, r->_y);
WriteToIO = 6;
break;
@@ -1288,7 +1287,7 @@ int nextInst(CpuCtx* r) {
if ((arg->_ea._value == 0xd011) || (arg->_ea._value == 0xd012)) {
r->_cycles += g_ops[opCode]._cycles;
- bt = _G(_byted011)[arg->_ea._value - 0xd011];
+ bt = r->_byted011[arg->_ea._value - 0xd011];
r->_flags &= ~(FLAG_N | FLAG_V | FLAG_Z);
r->_flags |= (bt & FLAG_N) != 0 ? FLAG_N : 0;
r->_flags |= (bt & FLAG_V) != 0 ? FLAG_V : 0;
@@ -1302,7 +1301,7 @@ int nextInst(CpuCtx* r) {
case 0xcc: /* cpy $ffff */
if ((arg->_ea._value == 0xd011) || (arg->_ea._value == 0xd012)) {
r->_cycles += g_ops[opCode]._cycles;
- bt = _G(_byted011)[arg->_ea._value - 0xd011];
+ bt = r->_byted011[arg->_ea._value - 0xd011];
br = r->_a;
if (opCode == 0xec)
br = r->_x;
@@ -1321,9 +1320,9 @@ int nextInst(CpuCtx* r) {
if (opCode == 0xcc)
br = r->_y;
if (arg->_ea._value == 0xdc00)
- bt = flipfire();
+ bt = flipfire(r);
else
- bt = flipspace();
+ bt = flipspace(r);
subtract(r, 1, br, bt);
WriteToIO = 6;
break;
@@ -1345,5 +1344,5 @@ int nextInst(CpuCtx* r) {
return 0;
}
-} // End of namespace Scott
-} // End of namespace Glk
+} // End of namespace Unp64
+} // End of namespace Common
diff --git a/engines/glk/scott/unp64/6502_emu.h b/common/compression/unp64/6502_emu.h
similarity index 82%
rename from engines/glk/scott/unp64/6502_emu.h
rename to common/compression/unp64/6502_emu.h
index 377f8368804..50e0615bf4f 100644
--- a/engines/glk/scott/unp64/6502_emu.h
+++ b/common/compression/unp64/6502_emu.h
@@ -41,28 +41,31 @@
*
*/
-#ifndef GLK_SCOTT_6502_EMU_H
-#define GLK_SCOTT_6502_EMU_H
+#ifndef COMMON_COMPRESSION_UNP64_6502_EMU_H
+#define COMMON_COMPRESSION_UNP64_6502_EMU_H
-#include "glk/scott/types.h"
+#include "common/scummsys.h"
-namespace Glk {
-namespace Scott {
+namespace Common {
+namespace Unp64 {
struct CpuCtx {
- uint32_t _cycles;
- uint16_t _pc;
- uint8_t *_mem;
- uint8_t _sp;
- uint8_t _flags;
- uint8_t _a;
- uint8_t _x;
- uint8_t _y;
+ uint32 _cycles;
+ uint16 _pc;
+ uint8 *_mem;
+ uint8 _sp;
+ uint8 _flags;
+ uint8 _a;
+ uint8 _x;
+ uint8 _y;
+ int _byted011[2] = {0, 0};
+ int _retfire = 0xff;
+ int _retspace = 0xff;
};
int nextInst(CpuCtx *r);
-} // End of namespace Scott
-} // End of namespace Glk
+} // End of namespace Unp64
+} // End of namespace Common
#endif
diff --git a/engines/glk/scott/unp64/exo_util.cpp b/common/compression/unp64/exo_util.cpp
similarity index 82%
rename from engines/glk/scott/unp64/exo_util.cpp
rename to common/compression/unp64/exo_util.cpp
index 4a9da958a69..dbfda94060c 100644
--- a/engines/glk/scott/unp64/exo_util.cpp
+++ b/common/compression/unp64/exo_util.cpp
@@ -42,10 +42,10 @@
*/
#include "common/util.h"
-#include "glk/scott/unp64/exo_util.h"
+#include "common/compression/unp64/exo_util.h"
-namespace Glk {
-namespace Scott {
+namespace Common {
+namespace Unp64 {
int findSys(const byte *buf, int target) {
int outstart = -1;
@@ -123,7 +123,7 @@ int findSys(const byte *buf, int target) {
return outstart;
}
-static void loadPrgData(byte mem[65536], uint8_t *data, size_t dataLength, LoadInfo *info) {
+static void loadPrgData(byte mem[65536], const byte *data, uint32 dataLength, LoadInfo *info) {
int len = MIN(65536 - info->_start, static_cast<int>(dataLength));
memcpy(mem + info->_start, data, (size_t)len);
@@ -135,7 +135,7 @@ static void loadPrgData(byte mem[65536], uint8_t *data, size_t dataLength, LoadI
}
}
-void loadData(uint8_t *data, size_t dataLength, byte mem[65536], LoadInfo *info) {
+void loadData(const byte *data, uint32 dataLength, byte mem[65536], LoadInfo *info) {
int load = data[0] + data[1] * 0x100;
info->_start = load;
@@ -180,7 +180,7 @@ int strToInt(const char *str, int *value) {
return status;
}
-bool u32eq(const unsigned char *addr, uint32_t val)
+bool u32eq(const unsigned char *addr, uint32 val)
{
return addr[3] == (val >> 24) &&
addr[2] == ((val >> 16) & 0xff) &&
@@ -188,48 +188,48 @@ bool u32eq(const unsigned char *addr, uint32_t val)
addr[0] == (val & 0xff);
}
-bool u32eqmasked(const unsigned char *addr, uint32_t mask, uint32_t val)
+bool u32eqmasked(const unsigned char *addr, uint32 mask, uint32 val)
{
- uint32_t val1 = addr[0] | (addr[1] << 8) | (addr[2] << 16) | (addr[3] << 24);
+ uint32 val1 = addr[0] | (addr[1] << 8) | (addr[2] << 16) | (addr[3] << 24);
return (val1 & mask) == val;
}
-bool u32eqxored(const unsigned char *addr, uint32_t xormask, uint32_t val)
+bool u32eqxored(const unsigned char *addr, uint32 xormask, uint32 val)
{
- uint32_t val1 = addr[0] | (addr[1] << 8) | (addr[2] << 16) | (addr[3] << 24);
+ uint32 val1 = addr[0] | (addr[1] << 8) | (addr[2] << 16) | (addr[3] << 24);
return (val1 ^ xormask) == val;
}
-bool u16eqmasked(const unsigned char *addr, uint16_t mask, uint16_t val)
+bool u16eqmasked(const unsigned char *addr, uint16 mask, uint16 val)
{
- uint16_t val1 = addr[0] | (addr[1] << 8);
+ uint16 val1 = addr[0] | (addr[1] << 8);
return (val1 & mask) == val;
}
-bool u16eq(const unsigned char *addr, uint16_t val)
+bool u16eq(const unsigned char *addr, uint16 val)
{
return addr[1] == (val >> 8) &&
addr[0] == (val & 0xff);
}
-bool u16noteq(const unsigned char *addr, uint16_t val)
+bool u16noteq(const unsigned char *addr, uint16 val)
{
return addr[1] != (val >> 8) ||
addr[0] != (val & 0xff);
}
-bool u16gteq(const unsigned char *addr, uint16_t val)
+bool u16gteq(const unsigned char *addr, uint16 val)
{
- uint16_t val2 = addr[0] | (addr[1] << 8);
+ uint16 val2 = addr[0] | (addr[1] << 8);
return val2 >= val;
}
-bool u16lteq(const unsigned char *addr, uint16_t val)
+bool u16lteq(const unsigned char *addr, uint16 val)
{
- uint16_t val2 = addr[0] | (addr[1] << 8);
+ uint16 val2 = addr[0] | (addr[1] << 8);
return val2 <= val;
}
-} // End of namespace Scott
-} // End of namespace Glk
+} // End of namespace Unp64
+} // End of namespace Common
diff --git a/engines/glk/scott/unp64/exo_util.h b/common/compression/unp64/exo_util.h
similarity index 73%
rename from engines/glk/scott/unp64/exo_util.h
rename to common/compression/unp64/exo_util.h
index d4b6b137326..5dc04765f88 100644
--- a/engines/glk/scott/unp64/exo_util.h
+++ b/common/compression/unp64/exo_util.h
@@ -41,13 +41,13 @@
*
*/
-#ifndef GLK_SCOTT_EXO_UTIL_H
-#define GLK_SCOTT_EXO_UTIL_H
+#ifndef COMMON_COMPRESSION_UNP64_EXO_UTIL_H
+#define COMMON_COMPRESSION_UNP64_EXO_UTIL_H
-#include "glk/scott/types.h"
+#include "common/scummsys.h"
-namespace Glk {
-namespace Scott {
+namespace Common {
+namespace Unp64 {
struct LoadInfo {
int _basicTxtStart; /* in */
@@ -59,20 +59,20 @@ struct LoadInfo {
int findSys(const byte *buf, int target);
-void loadData(uint8_t *data, size_t dataLength, byte mem[65536], LoadInfo *info);
+void loadData(const byte *data, uint32 dataLength, byte mem[65536], LoadInfo *info);
int strToInt(const char *str, int *value);
-bool u32eq(const unsigned char *addr, uint32_t val);
-bool u16eq(const unsigned char *addr, uint16_t val);
-bool u16gteq(const unsigned char *addr, uint16_t val);
-bool u16lteq(const unsigned char *addr, uint16_t val);
-bool u16noteq(const unsigned char *addr, uint16_t val);
-bool u32eqmasked(const unsigned char *addr, uint32_t mask, uint32_t val);
-bool u32eqxored(const unsigned char *addr, uint32_t ormask, uint32_t val);
-bool u16eqmasked(const unsigned char *addr, uint16_t mask, uint16_t val);
+bool u32eq(const unsigned char *addr, uint32 val);
+bool u16eq(const unsigned char *addr, uint16 val);
+bool u16gteq(const unsigned char *addr, uint16 val);
+bool u16lteq(const unsigned char *addr, uint16 val);
+bool u16noteq(const unsigned char *addr, uint16 val);
+bool u32eqmasked(const unsigned char *addr, uint32 mask, uint32 val);
+bool u32eqxored(const unsigned char *addr, uint32 ormask, uint32 val);
+bool u16eqmasked(const unsigned char *addr, uint16 mask, uint16 val);
-} // End of namespace Scott
-} // End of namespace Glk
+} // End of namespace Unp64
+} // End of namespace Common
#endif
diff --git a/engines/glk/scott/unp64/scanners/action_packer.cpp b/common/compression/unp64/scanners/action_packer.cpp
similarity index 86%
rename from engines/glk/scott/unp64/scanners/action_packer.cpp
rename to common/compression/unp64/scanners/action_packer.cpp
index 545ed40ed9c..1c2fc23cd18 100644
--- a/engines/glk/scott/unp64/scanners/action_packer.cpp
+++ b/common/compression/unp64/scanners/action_packer.cpp
@@ -20,12 +20,12 @@
*/
#include "common/endian.h"
-#include "glk/scott/types.h"
-#include "glk/scott/unp64/unp64.h"
-#include "glk/scott/unp64/exo_util.h"
+#include "common/scummsys.h"
+#include "common/compression/unp64/unp64.h"
+#include "common/compression/unp64/exo_util.h"
-namespace Glk {
-namespace Scott {
+namespace Common {
+namespace Unp64 {
void scnActionPacker(UnpStr *unp) {
byte *mem;
@@ -49,5 +49,5 @@ void scnActionPacker(UnpStr *unp) {
}
}
-} // End of namespace Scott
-} // End of namespace Glk
+} // End of namespace Unp64
+} // End of namespace Common
diff --git a/engines/glk/scott/unp64/scanners/byte_boiler.cpp b/common/compression/unp64/scanners/byte_boiler.cpp
similarity index 92%
rename from engines/glk/scott/unp64/scanners/byte_boiler.cpp
rename to common/compression/unp64/scanners/byte_boiler.cpp
index cf0a2b50f6d..7c79a712592 100644
--- a/engines/glk/scott/unp64/scanners/byte_boiler.cpp
+++ b/common/compression/unp64/scanners/byte_boiler.cpp
@@ -20,12 +20,12 @@
*/
#include "common/endian.h"
-#include "glk/scott/types.h"
-#include "glk/scott/unp64/unp64.h"
-#include "glk/scott/unp64/exo_util.h"
+#include "common/scummsys.h"
+#include "common/compression/unp64/unp64.h"
+#include "common/compression/unp64/exo_util.h"
-namespace Glk {
-namespace Scott {
+namespace Common {
+namespace Unp64 {
void scnByteBoiler(UnpStr *unp) {
byte *mem;
@@ -90,5 +90,5 @@ void scnByteBoiler(UnpStr *unp) {
}
}
-} // End of namespace Scott
-} // End of namespace Glk
+} // End of namespace Unp64
+} // End of namespace Common
diff --git a/engines/glk/scott/unp64/scanners/caution.cpp b/common/compression/unp64/scanners/caution.cpp
similarity index 94%
rename from engines/glk/scott/unp64/scanners/caution.cpp
rename to common/compression/unp64/scanners/caution.cpp
index 58ecaa117fd..b060b589142 100644
--- a/engines/glk/scott/unp64/scanners/caution.cpp
+++ b/common/compression/unp64/scanners/caution.cpp
@@ -20,12 +20,12 @@
*/
#include "common/endian.h"
-#include "glk/scott/types.h"
-#include "glk/scott/unp64/unp64.h"
-#include "glk/scott/unp64/exo_util.h"
+#include "common/scummsys.h"
+#include "common/compression/unp64/unp64.h"
+#include "common/compression/unp64/exo_util.h"
-namespace Glk {
-namespace Scott {
+namespace Common {
+namespace Unp64 {
void scnCaution(UnpStr *unp) {
byte *mem;
@@ -131,5 +131,5 @@ void scnCaution(UnpStr *unp) {
}
}
-} // End of namespace Scott
-} // End of namespace Glk
+} // End of namespace Unp64
+} // End of namespace Common
diff --git a/engines/glk/scott/unp64/scanners/ccs.cpp b/common/compression/unp64/scanners/ccs.cpp
similarity index 96%
rename from engines/glk/scott/unp64/scanners/ccs.cpp
rename to common/compression/unp64/scanners/ccs.cpp
index 78ca623800a..f5ccb3ac8de 100644
--- a/engines/glk/scott/unp64/scanners/ccs.cpp
+++ b/common/compression/unp64/scanners/ccs.cpp
@@ -20,12 +20,12 @@
*/
#include "common/endian.h"
-#include "glk/scott/types.h"
-#include "glk/scott/unp64/unp64.h"
-#include "glk/scott/unp64/exo_util.h"
+#include "common/scummsys.h"
+#include "common/compression/unp64/unp64.h"
+#include "common/compression/unp64/exo_util.h"
-namespace Glk {
-namespace Scott {
+namespace Common {
+namespace Unp64 {
void scnCCS(UnpStr *unp) {
byte *mem;
@@ -204,5 +204,5 @@ void scnCCS(UnpStr *unp) {
}
}
-} // End of namespace Scott
-} // End of namespace Glk
+} // End of namespace Unp64
+} // End of namespace Common
diff --git a/engines/glk/scott/unp64/scanners/cruel.cpp b/common/compression/unp64/scanners/cruel.cpp
similarity index 98%
rename from engines/glk/scott/unp64/scanners/cruel.cpp
rename to common/compression/unp64/scanners/cruel.cpp
index 97c5070ea83..bcdd823728c 100644
--- a/engines/glk/scott/unp64/scanners/cruel.cpp
+++ b/common/compression/unp64/scanners/cruel.cpp
@@ -20,12 +20,12 @@
*/
#include "common/endian.h"
-#include "glk/scott/types.h"
-#include "glk/scott/unp64/unp64.h"
-#include "glk/scott/unp64/exo_util.h"
+#include "common/scummsys.h"
+#include "common/compression/unp64/unp64.h"
+#include "common/compression/unp64/exo_util.h"
-namespace Glk {
-namespace Scott {
+namespace Common {
+namespace Unp64 {
void scnCruel(UnpStr *unp) {
byte *mem;
@@ -370,5 +370,5 @@ void scnCruel(UnpStr *unp) {
}
}
-} // End of namespace Scott
-} // End of namespace Glk
+} // End of namespace Unp64
+} // End of namespace Common
diff --git a/engines/glk/scott/unp64/scanners/eca.cpp b/common/compression/unp64/scanners/eca.cpp
similarity index 96%
rename from engines/glk/scott/unp64/scanners/eca.cpp
rename to common/compression/unp64/scanners/eca.cpp
index ec443939a66..a3848927d90 100644
--- a/engines/glk/scott/unp64/scanners/eca.cpp
+++ b/common/compression/unp64/scanners/eca.cpp
@@ -20,12 +20,12 @@
*/
#include "common/endian.h"
-#include "glk/scott/types.h"
-#include "glk/scott/unp64/unp64.h"
-#include "glk/scott/unp64/exo_util.h"
+#include "common/scummsys.h"
+#include "common/compression/unp64/unp64.h"
+#include "common/compression/unp64/exo_util.h"
-namespace Glk {
-namespace Scott {
+namespace Common {
+namespace Unp64 {
void scnECA(UnpStr *unp) {
byte *mem;
@@ -181,5 +181,5 @@ void scnECA(UnpStr *unp) {
}
}
-} // End of namespace Scott
-} // End of namespace Glk
+} // End of namespace Unp64
+} // End of namespace Common
diff --git a/engines/glk/scott/unp64/scanners/exomizer.cpp b/common/compression/unp64/scanners/exomizer.cpp
similarity index 95%
rename from engines/glk/scott/unp64/scanners/exomizer.cpp
rename to common/compression/unp64/scanners/exomizer.cpp
index 356d7f50e94..bf6fb2c6934 100644
--- a/engines/glk/scott/unp64/scanners/exomizer.cpp
+++ b/common/compression/unp64/scanners/exomizer.cpp
@@ -20,12 +20,12 @@
*/
#include "common/endian.h"
-#include "glk/scott/types.h"
-#include "glk/scott/unp64/unp64.h"
-#include "glk/scott/unp64/exo_util.h"
+#include "common/scummsys.h"
+#include "common/compression/unp64/unp64.h"
+#include "common/compression/unp64/exo_util.h"
-namespace Glk {
-namespace Scott {
+namespace Common {
+namespace Unp64 {
void scnExomizer(UnpStr *unp) {
byte *mem;
@@ -173,5 +173,5 @@ void scnExomizer(UnpStr *unp) {
}
}
-} // End of namespace Scott
-} // End of namespace Glk
+} // End of namespace Unp64
+} // End of namespace Common
diff --git a/engines/glk/scott/unp64/scanners/expert.cpp b/common/compression/unp64/scanners/expert.cpp
similarity index 96%
rename from engines/glk/scott/unp64/scanners/expert.cpp
rename to common/compression/unp64/scanners/expert.cpp
index 1db0a19944c..4c99f78882c 100644
--- a/engines/glk/scott/unp64/scanners/expert.cpp
+++ b/common/compression/unp64/scanners/expert.cpp
@@ -20,12 +20,12 @@
*/
#include "common/endian.h"
-#include "glk/scott/types.h"
-#include "glk/scott/unp64/unp64.h"
-#include "glk/scott/unp64/exo_util.h"
+#include "common/scummsys.h"
+#include "common/compression/unp64/unp64.h"
+#include "common/compression/unp64/exo_util.h"
-namespace Glk {
-namespace Scott {
+namespace Common {
+namespace Unp64 {
void scnExpert(UnpStr *unp) {
byte *mem;
@@ -243,5 +243,5 @@ void scnExpert(UnpStr *unp) {
}
}
-} // End of namespace Scott
-} // End of namespace Glk
+} // End of namespace Unp64
+} // End of namespace Common
diff --git a/engines/glk/scott/unp64/scanners/master_compressor.cpp b/common/compression/unp64/scanners/master_compressor.cpp
similarity index 94%
rename from engines/glk/scott/unp64/scanners/master_compressor.cpp
rename to common/compression/unp64/scanners/master_compressor.cpp
index 33927026740..b26cb5e044f 100644
--- a/engines/glk/scott/unp64/scanners/master_compressor.cpp
+++ b/common/compression/unp64/scanners/master_compressor.cpp
@@ -20,12 +20,12 @@
*/
#include "common/endian.h"
-#include "glk/scott/types.h"
-#include "glk/scott/unp64/unp64.h"
-#include "glk/scott/unp64/exo_util.h"
+#include "common/scummsys.h"
+#include "common/compression/unp64/unp64.h"
+#include "common/compression/unp64/exo_util.h"
-namespace Glk {
-namespace Scott {
+namespace Common {
+namespace Unp64 {
void scnMasterCompressor(UnpStr *unp) {
byte *mem;
@@ -121,5 +121,5 @@ void scnMasterCompressor(UnpStr *unp) {
}
}
-} // End of namespace Scott
-} // End of namespace Glk
+} // End of namespace Unp64
+} // End of namespace Common
diff --git a/engines/glk/scott/unp64/scanners/megabyte.cpp b/common/compression/unp64/scanners/megabyte.cpp
similarity index 91%
rename from engines/glk/scott/unp64/scanners/megabyte.cpp
rename to common/compression/unp64/scanners/megabyte.cpp
index a63706d451b..6cfa5ab97bc 100644
--- a/engines/glk/scott/unp64/scanners/megabyte.cpp
+++ b/common/compression/unp64/scanners/megabyte.cpp
@@ -20,12 +20,12 @@
*/
#include "common/endian.h"
-#include "glk/scott/types.h"
-#include "glk/scott/unp64/unp64.h"
-#include "glk/scott/unp64/exo_util.h"
+#include "common/scummsys.h"
+#include "common/compression/unp64/unp64.h"
+#include "common/compression/unp64/exo_util.h"
-namespace Glk {
-namespace Scott {
+namespace Common {
+namespace Unp64 {
void scnMegabyte(UnpStr *unp) {
byte *mem;
@@ -78,5 +78,5 @@ void scnMegabyte(UnpStr *unp) {
}
}
-} // End of namespace Scott
-} // End of namespace Glk
+} // End of namespace Unp64
+} // End of namespace Common
diff --git a/engines/glk/scott/unp64/scanners/pu_crunch.cpp b/common/compression/unp64/scanners/pu_crunch.cpp
similarity index 96%
rename from engines/glk/scott/unp64/scanners/pu_crunch.cpp
rename to common/compression/unp64/scanners/pu_crunch.cpp
index 2b3ed04a293..80b65ccdcf9 100644
--- a/engines/glk/scott/unp64/scanners/pu_crunch.cpp
+++ b/common/compression/unp64/scanners/pu_crunch.cpp
@@ -20,12 +20,12 @@
*/
#include "common/endian.h"
-#include "glk/scott/types.h"
-#include "glk/scott/unp64/unp64.h"
-#include "glk/scott/unp64/exo_util.h"
+#include "common/scummsys.h"
+#include "common/compression/unp64/unp64.h"
+#include "common/compression/unp64/exo_util.h"
-namespace Glk {
-namespace Scott {
+namespace Common {
+namespace Unp64 {
void scnPuCrunch(UnpStr *unp) {
byte *mem;
@@ -190,5 +190,5 @@ void scnPuCrunch(UnpStr *unp) {
}
}
-} // End of namespace Scott
-} // End of namespace Glk
+} // End of namespace Unp64
+} // End of namespace Common
diff --git a/engines/glk/scott/unp64/scanners/scanners.cpp b/common/compression/unp64/scanners/scanners.cpp
similarity index 92%
rename from engines/glk/scott/unp64/scanners/scanners.cpp
rename to common/compression/unp64/scanners/scanners.cpp
index e05722f28f6..cdd537bd9cd 100644
--- a/engines/glk/scott/unp64/scanners/scanners.cpp
+++ b/common/compression/unp64/scanners/scanners.cpp
@@ -19,11 +19,11 @@
*
*/
-#include "glk/scott/unp64/unp64.h"
+#include "common/compression/unp64/unp64.h"
#include "common/util.h"
-namespace Glk {
-namespace Scott {
+namespace Common {
+namespace Unp64 {
void scnECA(UnpStr *unp);
void scnExpert(UnpStr *unp);
@@ -69,5 +69,5 @@ void scanners(UnpStr* unp) {
}
}
-} // End of namespace Scott
-} // End of namespace Glk
+} // End of namespace Unp64
+} // End of namespace Common
diff --git a/engines/glk/scott/unp64/scanners/section8.cpp b/common/compression/unp64/scanners/section8.cpp
similarity index 93%
rename from engines/glk/scott/unp64/scanners/section8.cpp
rename to common/compression/unp64/scanners/section8.cpp
index 3361e09c6fc..a268a50fe74 100644
--- a/engines/glk/scott/unp64/scanners/section8.cpp
+++ b/common/compression/unp64/scanners/section8.cpp
@@ -20,12 +20,12 @@
*/
#include "common/endian.h"
-#include "glk/scott/types.h"
-#include "glk/scott/unp64/unp64.h"
-#include "glk/scott/unp64/exo_util.h"
+#include "common/scummsys.h"
+#include "common/compression/unp64/unp64.h"
+#include "common/compression/unp64/exo_util.h"
-namespace Glk {
-namespace Scott {
+namespace Common {
+namespace Unp64 {
void scnSection8(UnpStr *unp) {
byte *mem;
@@ -99,5 +99,5 @@ void scnSection8(UnpStr *unp) {
}
}
-} // End of namespace Scott
-} // End of namespace Glk
+} // End of namespace Unp64
+} // End of namespace Common
diff --git a/engines/glk/scott/unp64/scanners/tbc_multicomp.cpp b/common/compression/unp64/scanners/tbc_multicomp.cpp
similarity index 95%
rename from engines/glk/scott/unp64/scanners/tbc_multicomp.cpp
rename to common/compression/unp64/scanners/tbc_multicomp.cpp
index 068779f1e96..bdb69f780dd 100644
--- a/engines/glk/scott/unp64/scanners/tbc_multicomp.cpp
+++ b/common/compression/unp64/scanners/tbc_multicomp.cpp
@@ -20,12 +20,12 @@
*/
#include "common/endian.h"
-#include "glk/scott/types.h"
-#include "glk/scott/unp64/unp64.h"
-#include "glk/scott/unp64/exo_util.h"
+#include "common/scummsys.h"
+#include "common/compression/unp64/unp64.h"
+#include "common/compression/unp64/exo_util.h"
-namespace Glk {
-namespace Scott {
+namespace Common {
+namespace Unp64 {
void scnTBCMultiComp(UnpStr *unp) {
byte *mem;
@@ -162,5 +162,5 @@ void scnTBCMultiComp(UnpStr *unp) {
}
}
-} // End of namespace Scott
-} // End of namespace Glk
+} // End of namespace Unp64
+} // End of namespace Common
diff --git a/engines/glk/scott/unp64/scanners/tcs_crunch.cpp b/common/compression/unp64/scanners/tcs_crunch.cpp
similarity index 91%
rename from engines/glk/scott/unp64/scanners/tcs_crunch.cpp
rename to common/compression/unp64/scanners/tcs_crunch.cpp
index b10243f5dc9..dbe59279e48 100644
--- a/engines/glk/scott/unp64/scanners/tcs_crunch.cpp
+++ b/common/compression/unp64/scanners/tcs_crunch.cpp
@@ -20,12 +20,12 @@
*/
#include "common/endian.h"
-#include "glk/scott/types.h"
-#include "glk/scott/unp64/unp64.h"
-#include "glk/scott/unp64/exo_util.h"
+#include "common/scummsys.h"
+#include "common/compression/unp64/unp64.h"
+#include "common/compression/unp64/exo_util.h"
-namespace Glk {
-namespace Scott {
+namespace Common {
+namespace Unp64 {
void scnTCScrunch(UnpStr *unp) {
byte *mem;
@@ -74,5 +74,5 @@ void scnTCScrunch(UnpStr *unp) {
}
}
-} // End of namespace Scott
-} // End of namespace Glk
+} // End of namespace Unp64
+} // End of namespace Common
diff --git a/engines/glk/scott/unp64/scanners/xtc.cpp b/common/compression/unp64/scanners/xtc.cpp
similarity index 96%
rename from engines/glk/scott/unp64/scanners/xtc.cpp
rename to common/compression/unp64/scanners/xtc.cpp
index a480e8646c6..79b47ca139a 100644
--- a/engines/glk/scott/unp64/scanners/xtc.cpp
+++ b/common/compression/unp64/scanners/xtc.cpp
@@ -20,12 +20,12 @@
*/
#include "common/endian.h"
-#include "glk/scott/types.h"
-#include "glk/scott/unp64/unp64.h"
-#include "glk/scott/unp64/exo_util.h"
+#include "common/scummsys.h"
+#include "common/compression/unp64/unp64.h"
+#include "common/compression/unp64/exo_util.h"
-namespace Glk {
-namespace Scott {
+namespace Common {
+namespace Unp64 {
void scnXTC(UnpStr *unp) {
byte *mem;
@@ -158,5 +158,5 @@ void scnXTC(UnpStr *unp) {
}
}
-} // End of namespace Scott
-} // End of namespace Glk
+} // End of namespace Unp64
+} // End of namespace Common
diff --git a/engines/glk/scott/unp64/unp64.cpp b/common/compression/unp64/unp64.cpp
similarity index 57%
rename from engines/glk/scott/unp64/unp64.cpp
rename to common/compression/unp64/unp64.cpp
index 95beae50226..140bdc7e6d6 100644
--- a/engines/glk/scott/unp64/unp64.cpp
+++ b/common/compression/unp64/unp64.cpp
@@ -19,9 +19,7 @@
*
*/
-// This is a cut-down version of UNP64 with only the bare minimum
-// needed to decompress a number of Scott Adams Commodore 64 games
-// for the ScottFree interpreter.
+// Adapted from the ScottFree version of UNP64 for shared use in ScummVM.
/*
UNP64 - generic Commodore 64 prg unpacker
@@ -53,51 +51,51 @@ Follows original disclaimer
*
*/
-#include "glk/scott/globals.h"
-#include "glk/scott/types.h"
-#include "glk/scott/unp64/6502_emu.h"
-#include "glk/scott/unp64/exo_util.h"
-#include "glk/scott/unp64/unp64.h"
+#include "common/compression/unp64.h"
+#include "common/compression/unp64/6502_emu.h"
+#include "common/compression/unp64/exo_util.h"
+#include "common/compression/unp64/unp64.h"
+#include "common/str.h"
#include "common/util.h"
-namespace Glk {
-namespace Scott {
-
-void reinitUnp(void) {
- _G(_unp)._idFlag = 0;
- _G(_unp)._forced = 0;
- _G(_unp)._strMem = 0x800;
- _G(_unp)._retAdr = 0x800;
- _G(_unp)._depAdr = 0;
- _G(_unp)._endAdr = 0x10000;
- _G(_unp)._rtAFrc = 0;
- _G(_unp)._wrMemF = 0;
- _G(_unp)._lfMemF = 0;
- _G(_unp)._exoFnd = 0;
- _G(_unp)._ecaFlg = 0;
- _G(_unp)._fEndBf = 0;
- _G(_unp)._fEndAf = 0;
- _G(_unp)._fStrAf = 0;
- _G(_unp)._fStrBf = 0;
- _G(_unp)._mon1st = 0;
+namespace Common {
+namespace Unp64 {
+
+static void reinitUnp(UnpStr &unp) {
+ unp._idFlag = 0;
+ unp._forced = 0;
+ unp._strMem = 0x800;
+ unp._retAdr = 0x800;
+ unp._depAdr = 0;
+ unp._endAdr = 0x10000;
+ unp._rtAFrc = 0;
+ unp._wrMemF = 0;
+ unp._lfMemF = 0;
+ unp._exoFnd = 0;
+ unp._ecaFlg = 0;
+ unp._fEndBf = 0;
+ unp._fEndAf = 0;
+ unp._fStrAf = 0;
+ unp._fStrBf = 0;
+ unp._mon1st = 0;
}
-int isBasicRun1(int pc) {
+static int isBasicRun1(int pc) {
if (pc == 0xa7ae || pc == 0xa7ea || pc == 0xa7b1 || pc == 0xa474 || pc == 0xa533 || pc == 0xa871 || pc == 0xa888 || pc == 0xa8bc)
return 1;
else
return 0;
}
-int isBasicRun2(int pc) {
+static int isBasicRun2(int pc) {
if (isBasicRun1(pc) || ((pc >= 0xA57C) && (pc <= 0xA659)) || pc == 0xa660 || pc == 0xa68e)
return 1;
else
return 0;
}
-int unp64(byte *compressed, size_t length, byte *destinationBuffer, size_t *finalLength, const char *switches) {
+int unp64(const byte *compressed, uint32 length, byte *destinationBuffer, uint32 *finalLength, const char *switches) {
char settings[4][64];
int numSettings = 0;
@@ -116,6 +114,8 @@ int unp64(byte *compressed, size_t length, byte *destinationBuffer, size_t *fina
}
}
+ UnpStr unp;
+ int iter = 0;
CpuCtx r[1];
LoadInfo info[1];
char name[260] = {0}, forcedname[260] = {0};
@@ -155,21 +155,21 @@ int unp64(byte *compressed, size_t length, byte *destinationBuffer, size_t *fina
int iterMax = ITERMAX;
int p;
- memset(&_G(_unp), 0, sizeof(_G(_unp)));
- reinitUnp();
- _G(_unp)._fStack = 1;
- _G(_unp)._mem = mem;
- _G(_unp)._r = r;
- _G(_unp)._name = name;
- _G(_unp)._info = info;
+ memset(&unp, 0, sizeof(unp));
+ reinitUnp(unp);
+ unp._fStack = 1;
+ unp._mem = mem;
+ unp._r = r;
+ unp._name = name;
+ unp._info = info;
p = 0;
if (numSettings != 0) {
- if (settings[0][0] == '-' && _G(_parsePar) && settings[0][1] == 'f') {
- strToInt(settings[p] + 2, (int *)&_G(_unp)._filler);
- if (_G(_unp)._filler) {
- memset(mem + (_G(_unp)._filler >> 16), _G(_unp)._filler & 0xff, 0x10000 - (_G(_unp)._filler >> 16));
+ if (settings[0][0] == '-' && settings[0][1] == 'f') {
+ strToInt(settings[p] + 2, (int *)&unp._filler);
+ if (unp._filler) {
+ memset(mem + (unp._filler >> 16), unp._filler & 0xff, 0x10000 - (unp._filler >> 16));
}
p++;
}
@@ -184,11 +184,11 @@ int unp64(byte *compressed, size_t length, byte *destinationBuffer, size_t *fina
info->_run = findSys(mem + info->_basicTxtStart, 0x9e);
}
- scanners(&_G(_unp));
- if (_G(_unp)._idFlag == 2)
+ scanners(&unp);
+ if (unp._idFlag == 2)
return 0;
- if ((_G(_unp)._recurs == 0) && (numSettings > 0)) {
+ if ((unp._recurs == 0) && (numSettings > 0)) {
while (p < numSettings) {
if (settings[p][0] == '-') {
switch (settings[p][1]) {
@@ -196,48 +196,48 @@ int unp64(byte *compressed, size_t length, byte *destinationBuffer, size_t *fina
p = numSettings;
break;
case 'e':
- strToInt(settings[p] + 2, &_G(_unp)._forced);
- _G(_unp)._forced &= 0xffff;
- if (_G(_unp)._forced < 0x1)
- _G(_unp)._forced = 0;
+ strToInt(settings[p] + 2, &unp._forced);
+ unp._forced &= 0xffff;
+ if (unp._forced < 0x1)
+ unp._forced = 0;
break;
case 'a':
- _G(_unp)._strMem = 2;
- _G(_unp)._endAdr = 0x10001;
- _G(_unp)._fEndAf = 0;
- _G(_unp)._fStrAf = 0;
- _G(_unp)._strAdC = 0;
- _G(_unp)._endAdC = 0;
- _G(_unp)._monEnd = 0;
- _G(_unp)._monStr = 0;
+ unp._strMem = 2;
+ unp._endAdr = 0x10001;
+ unp._fEndAf = 0;
+ unp._fStrAf = 0;
+ unp._strAdC = 0;
+ unp._endAdC = 0;
+ unp._monEnd = 0;
+ unp._monStr = 0;
break;
case 'r':
- strToInt(settings[p] + 2, &_G(_unp)._retAdr);
- _G(_unp)._retAdr &= 0xffff;
+ strToInt(settings[p] + 2, &unp._retAdr);
+ unp._retAdr &= 0xffff;
break;
case 'R':
- strToInt(settings[p] + 2, &_G(_unp)._retAdr);
- _G(_unp)._retAdr &= 0xffff;
- _G(_unp)._rtAFrc = 1;
+ strToInt(settings[p] + 2, &unp._retAdr);
+ unp._retAdr &= 0xffff;
+ unp._rtAFrc = 1;
break;
case 'd':
- strToInt(settings[p] + 2, &_G(_unp)._depAdr);
- _G(_unp)._depAdr &= 0xffff;
+ strToInt(settings[p] + 2, &unp._depAdr);
+ unp._depAdr &= 0xffff;
break;
case 't':
- strToInt(settings[p] + 2, &_G(_unp)._endAdr);
- _G(_unp)._endAdr &= 0xffff;
- if (_G(_unp)._endAdr >= 0x100)
- _G(_unp)._endAdr++;
+ strToInt(settings[p] + 2, &unp._endAdr);
+ unp._endAdr &= 0xffff;
+ if (unp._endAdr >= 0x100)
+ unp._endAdr++;
break;
case 'u':
- _G(_unp)._wrMemF = 1;
+ unp._wrMemF = 1;
break;
case 'l':
- _G(_unp)._lfMemF = info->_end;
+ unp._lfMemF = info->_end;
break;
case 's':
- _G(_unp)._fStack = 0;
+ unp._fStack = 0;
break;
case 'x':
break;
@@ -248,7 +248,7 @@ int unp64(byte *compressed, size_t length, byte *destinationBuffer, size_t *fina
//copyRoms[1][1] = 1;
break;
case 'c':
- _G(_unp)._recurs++;
+ unp._recurs++;
break;
case 'm': // keep undocumented for now
strToInt(settings[p] + 2, &iterMax);
@@ -258,32 +258,32 @@ int unp64(byte *compressed, size_t length, byte *destinationBuffer, size_t *fina
}
}
- if (_G(_unp)._idOnly) {
- if (_G(_unp)._depAdr == 0)
+ if (unp._idOnly) {
+ if (unp._depAdr == 0)
return 0;
}
- if (_G(_unp)._wrMemF | _G(_unp)._lfMemF) {
+ if (unp._wrMemF | unp._lfMemF) {
memcpy(oldmem, mem, sizeof(oldmem));
}
- if (_G(_unp)._forced) {
- info->_run = _G(_unp)._forced;
+ if (unp._forced) {
+ info->_run = unp._forced;
}
if (info->_run == -1) {
return 0;
}
- if (_G(_unp)._strMem > _G(_unp)._retAdr) {
- _G(_unp)._strMem = _G(_unp)._retAdr;
+ if (unp._strMem > unp._retAdr) {
+ unp._strMem = unp._retAdr;
}
mem[0] = 0x60;
r->_cycles = 0;
mem[1] = 0x37;
- if (((_G(_unp)._forced >= 0xa000) && (_G(_unp)._forced < 0xc000)) || (_G(_unp)._forced >= 0xd000))
+ if (((unp._forced >= 0xa000) && (unp._forced < 0xc000)) || (unp._forced >= 0xd000))
mem[1] = 0x38;
/* some packers rely on basic pointers already set */
@@ -311,7 +311,7 @@ int unp64(byte *compressed, size_t length, byte *destinationBuffer, size_t *fina
mem[0x52] = 0;
mem[0x53] = 3;
- if (_G(_unp)._fStack) {
+ if (unp._fStack) {
memcpy(mem + 0x100, stack,
sizeof(stack)); /* stack as found on clean start */
r->_sp = 0xf6; /* sys from immediate mode leaves $f6 in stackptr */
@@ -335,8 +335,8 @@ int unp64(byte *compressed, size_t length, byte *destinationBuffer, size_t *fina
r->_x = 0;
}
- _G(_iter) = 0;
- while ((_G(_unp)._depAdr ? r->_pc != _G(_unp)._depAdr : r->_pc >= _G(_unp)._retAdr)) {
+ iter = 0;
+ while ((unp._depAdr ? r->_pc != unp._depAdr : r->_pc >= unp._retAdr)) {
if ((((mem[1] & 0x7) >= 6) && (r->_pc >= 0xe000)) || ((r->_pc >= 0xa000) && (r->_pc <= 0xbfff) && ((mem[1] & 0x7) > 6))) {
/* some packer relies on regs set at return from CLRSCR */
if ((r->_pc == 0xe536) || (r->_pc == 0xe544) || (r->_pc == 0xff5b) || ((r->_pc == 0xffd2) && (r->_a == 0x93))) {
@@ -408,39 +408,39 @@ int unp64(byte *compressed, size_t length, byte *destinationBuffer, size_t *fina
if (nextInst(r) == 1)
return 0;
- _G(_iter)++;
- if (_G(_iter) == iterMax) {
+ iter++;
+ if (iter == iterMax) {
return 0;
}
- if (_G(_unp)._exoFnd && (_G(_unp)._endAdr == 0x10000) && (r->_pc >= 0x100) && (r->_pc <= 0x200) && (_G(_unp)._strMem != 2)) {
- _G(_unp)._endAdr = r->_mem[0xfe] + (r->_mem[0xff] << 8);
- if ((_G(_unp)._exoFnd & 0xff) == 0x30) { /* low byte of _endAdr, it's a lda $ff00,y */
- _G(_unp)._endAdr = (_G(_unp)._exoFnd >> 8) + (r->_mem[0xff] << 8);
- } else if ((_G(_unp)._exoFnd & 0xff) == 0x32) { /* add 1 */
- _G(_unp)._endAdr = 1 + ((_G(_unp)._exoFnd >> 8) + (r->_mem[0xff] << 8));
+ if (unp._exoFnd && (unp._endAdr == 0x10000) && (r->_pc >= 0x100) && (r->_pc <= 0x200) && (unp._strMem != 2)) {
+ unp._endAdr = r->_mem[0xfe] + (r->_mem[0xff] << 8);
+ if ((unp._exoFnd & 0xff) == 0x30) { /* low byte of _endAdr, it's a lda $ff00,y */
+ unp._endAdr = (unp._exoFnd >> 8) + (r->_mem[0xff] << 8);
+ } else if ((unp._exoFnd & 0xff) == 0x32) { /* add 1 */
+ unp._endAdr = 1 + ((unp._exoFnd >> 8) + (r->_mem[0xff] << 8));
}
- if (_G(_unp)._endAdr == 0)
- _G(_unp)._endAdr = 0x10001;
+ if (unp._endAdr == 0)
+ unp._endAdr = 0x10001;
}
- if (_G(_unp)._fEndBf && (_G(_unp)._endAdr == 0x10000) && (r->_pc == _G(_unp)._depAdr)) {
- _G(_unp)._endAdr = r->_mem[_G(_unp)._fEndBf] | r->_mem[_G(_unp)._fEndBf + 1] << 8;
- _G(_unp)._endAdr++;
+ if (unp._fEndBf && (unp._endAdr == 0x10000) && (r->_pc == unp._depAdr)) {
+ unp._endAdr = r->_mem[unp._fEndBf] | r->_mem[unp._fEndBf + 1] << 8;
+ unp._endAdr++;
- if (_G(_unp)._endAdr == 0)
- _G(_unp)._endAdr = 0x10001;
+ if (unp._endAdr == 0)
+ unp._endAdr = 0x10001;
- _G(_unp)._fEndBf = 0;
+ unp._fEndBf = 0;
}
- if (_G(_unp)._fStrBf && (_G(_unp)._strMem != 0x2) && (r->_pc == _G(_unp)._depAdr)) {
- _G(_unp)._strMem = r->_mem[_G(_unp)._fStrBf] | r->_mem[_G(_unp)._fStrBf + 1] << 8;
- _G(_unp)._fStrBf = 0;
+ if (unp._fStrBf && (unp._strMem != 0x2) && (r->_pc == unp._depAdr)) {
+ unp._strMem = r->_mem[unp._fStrBf] | r->_mem[unp._fStrBf + 1] << 8;
+ unp._fStrBf = 0;
}
- if (_G(_unp)._debugP) {
+ if (unp._debugP) {
for (p = 0; p < 0x20; p += 2) {
if (*(unsigned short int *)(mem + 0x314 + p) != *(unsigned short int *)(vector + p)) {
*(unsigned short int *)(vector + p) = *(unsigned short int *)(mem + 0x314 + p);
@@ -449,22 +449,22 @@ int unp64(byte *compressed, size_t length, byte *destinationBuffer, size_t *fina
}
}
- _G(_iter) = 0;
- while (_G(_unp)._rtAFrc ? r->_pc != _G(_unp)._retAdr : r->_pc < _G(_unp)._retAdr) {
- if (_G(_unp)._monEnd && r->_pc == _G(_unp)._depAdr) {
- p = r->_mem[_G(_unp)._monEnd >> 16] | r->_mem[_G(_unp)._monEnd & 0xffff] << 8;
- if (p > (_G(_unp)._endAdr & 0xffff)) {
- _G(_unp)._endAdr = p;
+ iter = 0;
+ while (unp._rtAFrc ? r->_pc != unp._retAdr : r->_pc < unp._retAdr) {
+ if (unp._monEnd && r->_pc == unp._depAdr) {
+ p = r->_mem[unp._monEnd >> 16] | r->_mem[unp._monEnd & 0xffff] << 8;
+ if (p > (unp._endAdr & 0xffff)) {
+ unp._endAdr = p;
}
}
- if (_G(_unp)._monStr && r->_pc == _G(_unp)._depAdr) {
- p = r->_mem[_G(_unp)._monStr >> 16] | r->_mem[_G(_unp)._monStr & 0xffff] << 8;
+ if (unp._monStr && r->_pc == unp._depAdr) {
+ p = r->_mem[unp._monStr >> 16] | r->_mem[unp._monStr & 0xffff] << 8;
if (p > 0) {
- if (_G(_unp)._mon1st == 0) {
- _G(_unp)._strMem = p;
+ if (unp._mon1st == 0) {
+ unp._strMem = p;
}
- _G(_unp)._mon1st = (unsigned int)_G(_unp)._strMem;
- _G(_unp)._strMem = (p < _G(_unp)._strMem ? p : _G(_unp)._strMem);
+ unp._mon1st = (unsigned int)unp._strMem;
+ unp._strMem = (p < unp._strMem ? p : unp._strMem);
}
}
@@ -477,16 +477,16 @@ int unp64(byte *compressed, size_t length, byte *destinationBuffer, size_t *fina
if (nextInst(r) == 1)
return 0;
- if ((mem[r->_pc] == 0x40) && (_G(_unp)._rtiFrc == 1)) {
- _G(_unp)._retAdr = r->_pc;
- _G(_unp)._rtAFrc = 1;
- if (_G(_unp)._retAdr < _G(_unp)._strMem)
- _G(_unp)._strMem = 2;
+ if ((mem[r->_pc] == 0x40) && (unp._rtiFrc == 1)) {
+ unp._retAdr = r->_pc;
+ unp._rtAFrc = 1;
+ if (unp._retAdr < unp._strMem)
+ unp._strMem = 2;
break;
}
- _G(_iter)++;
- if (_G(_iter) == iterMax) {
+ iter++;
+ if (iter == iterMax) {
return 0;
}
@@ -523,37 +523,37 @@ int unp64(byte *compressed, size_t length, byte *destinationBuffer, size_t *fina
}
}
- if (_G(_unp)._fEndAf && _G(_unp)._monEnd) {
- _G(_unp)._endAdC = (unsigned int)(mem[_G(_unp)._fEndAf] | mem[_G(_unp)._fEndAf + 1] << 8);
- if ((int)_G(_unp)._endAdC > _G(_unp)._endAdr)
- _G(_unp)._endAdr = (int)_G(_unp)._endAdC;
+ if (unp._fEndAf && unp._monEnd) {
+ unp._endAdC = (unsigned int)(mem[unp._fEndAf] | mem[unp._fEndAf + 1] << 8);
+ if ((int)unp._endAdC > unp._endAdr)
+ unp._endAdr = (int)unp._endAdC;
- _G(_unp)._endAdC = 0;
- _G(_unp)._fEndAf = 0;
+ unp._endAdC = 0;
+ unp._fEndAf = 0;
}
- if (_G(_unp)._fEndAf && (_G(_unp)._endAdr == 0x10000)) {
- _G(_unp)._endAdr = r->_mem[_G(_unp)._fEndAf] | r->_mem[_G(_unp)._fEndAf + 1] << 8;
- if (_G(_unp)._endAdr == 0)
- _G(_unp)._endAdr = 0x10000;
+ if (unp._fEndAf && (unp._endAdr == 0x10000)) {
+ unp._endAdr = r->_mem[unp._fEndAf] | r->_mem[unp._fEndAf + 1] << 8;
+ if (unp._endAdr == 0)
+ unp._endAdr = 0x10000;
else
- _G(_unp)._endAdr++;
- _G(_unp)._fEndAf = 0;
+ unp._endAdr++;
+ unp._fEndAf = 0;
}
- if (_G(_unp)._fStrAf /*&&(_G(_unp)._strMem==0x800)*/) {
- _G(_unp)._strMem = r->_mem[_G(_unp)._fStrAf] | r->_mem[_G(_unp)._fStrAf + 1] << 8;
- _G(_unp)._strMem++;
- _G(_unp)._fStrAf = 0;
+ if (unp._fStrAf /*&&(unp._strMem==0x800)*/) {
+ unp._strMem = r->_mem[unp._fStrAf] | r->_mem[unp._fStrAf + 1] << 8;
+ unp._strMem++;
+ unp._fStrAf = 0;
}
- if (_G(_unp)._exoFnd && (_G(_unp)._strMem != 2)) {
- _G(_unp)._strMem = r->_mem[0xfe] + (r->_mem[0xff] << 8);
+ if (unp._exoFnd && (unp._strMem != 2)) {
+ unp._strMem = r->_mem[0xfe] + (r->_mem[0xff] << 8);
- if ((_G(_unp)._exoFnd & 0xff) == 0x30) {
- _G(_unp)._strMem += r->_y;
- } else if ((_G(_unp)._exoFnd & 0xff) == 0x32) {
- _G(_unp)._strMem += r->_y + 1;
+ if ((unp._exoFnd & 0xff) == 0x30) {
+ unp._strMem += r->_y;
+ } else if ((unp._exoFnd & 0xff) == 0x32) {
+ unp._strMem += r->_y + 1;
}
}
@@ -570,12 +570,12 @@ int unp64(byte *compressed, size_t length, byte *destinationBuffer, size_t *fina
}
}
- if (_G(_unp)._wrMemF) {
- _G(_unp)._wrMemF = 0;
+ if (unp._wrMemF) {
+ unp._wrMemF = 0;
for (p = 0x800; p < 0x10000; p += 4) {
if (*(unsigned int *)(oldmem + p) == *(unsigned int *)(mem + p)) {
*(unsigned int *)(mem + p) = 0;
- _G(_unp)._wrMemF = 1;
+ unp._wrMemF = 1;
}
}
/* clean also the $fd30 table copy in RAM */
@@ -584,13 +584,13 @@ int unp64(byte *compressed, size_t length, byte *destinationBuffer, size_t *fina
}
}
- if (_G(_unp)._lfMemF) {
+ if (unp._lfMemF) {
for (p = 0xffff; p > 0x0800; p--) {
- if (oldmem[--_G(_unp)._lfMemF] == mem[p])
+ if (oldmem[--unp._lfMemF] == mem[p])
mem[p] = 0x0;
else {
if (p >= 0xffff)
- _G(_unp)._lfMemF = 0 | _G(_unp)._ecaFlg;
+ unp._lfMemF = 0 | unp._ecaFlg;
break;
}
}
@@ -605,131 +605,131 @@ int unp64(byte *compressed, size_t length, byte *destinationBuffer, size_t *fina
ln = 248;
}
- Common::sprintf_s(name + ln, sizeof(name) - ln, ".%04x%s", r->_pc, ((_G(_unp)._wrMemF | _G(_unp)._lfMemF) ? ".clean" : ""));
+ Common::sprintf_s(name + ln, sizeof(name) - ln, ".%04x%s", r->_pc, ((unp._wrMemF | unp._lfMemF) ? ".clean" : ""));
}
/* endadr is set to a ZP location? then use it as a pointer
todo: use __fEndAf instead, it can be used for any location, not only ZP. */
- if (_G(_unp)._endAdr && (_G(_unp)._endAdr < 0x100)) {
- p = (mem[_G(_unp)._endAdr] | mem[_G(_unp)._endAdr + 1] << 8) & 0xffff;
- _G(_unp)._endAdr = p;
+ if (unp._endAdr && (unp._endAdr < 0x100)) {
+ p = (mem[unp._endAdr] | mem[unp._endAdr + 1] << 8) & 0xffff;
+ unp._endAdr = p;
}
- if (_G(_unp)._ecaFlg && (_G(_unp)._strMem != 2)) /* checkme */ {
- if (_G(_unp)._endAdr >= ((_G(_unp)._ecaFlg >> 16) & 0xffff)) {
+ if (unp._ecaFlg && (unp._strMem != 2)) /* checkme */ {
+ if (unp._endAdr >= ((unp._ecaFlg >> 16) & 0xffff)) {
/* most of the times transfers $2000 byte from $d000-efff to $e000-ffff but there are exceptions */
- if (_G(_unp)._lfMemF)
- memset(mem + ((_G(_unp)._ecaFlg >> 16) & 0xffff), 0, 0x1000);
- _G(_unp)._endAdr += 0x1000;
+ if (unp._lfMemF)
+ memset(mem + ((unp._ecaFlg >> 16) & 0xffff), 0, 0x1000);
+ unp._endAdr += 0x1000;
}
}
- if (_G(_unp)._endAdr <= 0)
- _G(_unp)._endAdr = 0x10000;
+ if (unp._endAdr <= 0)
+ unp._endAdr = 0x10000;
- if (_G(_unp)._endAdr > 0x10000)
- _G(_unp)._endAdr = 0x10000;
+ if (unp._endAdr > 0x10000)
+ unp._endAdr = 0x10000;
- if (_G(_unp)._endAdr < _G(_unp)._strMem)
- _G(_unp)._endAdr = 0x10000;
+ if (unp._endAdr < unp._strMem)
+ unp._endAdr = 0x10000;
- if (_G(_unp)._endAdC & 0xffff) {
- _G(_unp)._endAdr += (_G(_unp)._endAdC & 0xffff);
- _G(_unp)._endAdr &= 0xffff;
+ if (unp._endAdC & 0xffff) {
+ unp._endAdr += (unp._endAdC & 0xffff);
+ unp._endAdr &= 0xffff;
}
- if (_G(_unp)._endAdC & EA_USE_A) {
- _G(_unp)._endAdr += r->_a;
- _G(_unp)._endAdr &= 0xffff;
+ if (unp._endAdC & EA_USE_A) {
+ unp._endAdr += r->_a;
+ unp._endAdr &= 0xffff;
}
- if (_G(_unp)._endAdC & EA_USE_X) {
- _G(_unp)._endAdr += r->_x;
- _G(_unp)._endAdr &= 0xffff;
+ if (unp._endAdC & EA_USE_X) {
+ unp._endAdr += r->_x;
+ unp._endAdr &= 0xffff;
}
- if (_G(_unp)._endAdC & EA_USE_Y) {
- _G(_unp)._endAdr += r->_y;
- _G(_unp)._endAdr &= 0xffff;
+ if (unp._endAdC & EA_USE_Y) {
+ unp._endAdr += r->_y;
+ unp._endAdr &= 0xffff;
}
- if (_G(_unp)._strAdC & 0xffff) {
- _G(_unp)._strMem += (_G(_unp)._strAdC & 0xffff);
- _G(_unp)._strMem &= 0xffff;
+ if (unp._strAdC & 0xffff) {
+ unp._strMem += (unp._strAdC & 0xffff);
+ unp._strMem &= 0xffff;
/* only if ea_addff, no reg involved */
- if (((_G(_unp)._strAdC & 0xffff0000) == EA_ADDFF) && ((_G(_unp)._strMem & 0xff) == 0)) {
- _G(_unp)._strMem += 0x100;
- _G(_unp)._strMem &= 0xffff;
+ if (((unp._strAdC & 0xffff0000) == EA_ADDFF) && ((unp._strMem & 0xff) == 0)) {
+ unp._strMem += 0x100;
+ unp._strMem &= 0xffff;
}
}
- if (_G(_unp)._strAdC & EA_USE_A) {
- _G(_unp)._strMem += r->_a;
- _G(_unp)._strMem &= 0xffff;
- if (_G(_unp)._strAdC & EA_ADDFF) {
- if ((_G(_unp)._strMem & 0xff) == 0xff)
- _G(_unp)._strMem++;
+ if (unp._strAdC & EA_USE_A) {
+ unp._strMem += r->_a;
+ unp._strMem &= 0xffff;
+ if (unp._strAdC & EA_ADDFF) {
+ if ((unp._strMem & 0xff) == 0xff)
+ unp._strMem++;
if (r->_a == 0) {
- _G(_unp)._strMem += 0x100;
- _G(_unp)._strMem &= 0xffff;
+ unp._strMem += 0x100;
+ unp._strMem &= 0xffff;
}
}
}
- if (_G(_unp)._strAdC & EA_USE_X) {
- _G(_unp)._strMem += r->_x;
- _G(_unp)._strMem &= 0xffff;
+ if (unp._strAdC & EA_USE_X) {
+ unp._strMem += r->_x;
+ unp._strMem &= 0xffff;
- if (_G(_unp)._strAdC & EA_ADDFF) {
- if ((_G(_unp)._strMem & 0xff) == 0xff)
- _G(_unp)._strMem++;
+ if (unp._strAdC & EA_ADDFF) {
+ if ((unp._strMem & 0xff) == 0xff)
+ unp._strMem++;
if (r->_x == 0) {
- _G(_unp)._strMem += 0x100;
- _G(_unp)._strMem &= 0xffff;
+ unp._strMem += 0x100;
+ unp._strMem &= 0xffff;
}
}
}
- if (_G(_unp)._strAdC & EA_USE_Y) {
- _G(_unp)._strMem += r->_y;
- _G(_unp)._strMem &= 0xffff;
+ if (unp._strAdC & EA_USE_Y) {
+ unp._strMem += r->_y;
+ unp._strMem &= 0xffff;
- if (_G(_unp)._strAdC & EA_ADDFF) {
- if ((_G(_unp)._strMem & 0xff) == 0xff)
- _G(_unp)._strMem++;
+ if (unp._strAdC & EA_ADDFF) {
+ if ((unp._strMem & 0xff) == 0xff)
+ unp._strMem++;
if (r->_y == 0) {
- _G(_unp)._strMem += 0x100;
- _G(_unp)._strMem &= 0xffff;
+ unp._strMem += 0x100;
+ unp._strMem &= 0xffff;
}
}
}
- if (_G(_unp)._endAdr <= 0)
- _G(_unp)._endAdr = 0x10000;
+ if (unp._endAdr <= 0)
+ unp._endAdr = 0x10000;
- if (_G(_unp)._endAdr > 0x10000)
- _G(_unp)._endAdr = 0x10000;
+ if (unp._endAdr > 0x10000)
+ unp._endAdr = 0x10000;
- if (_G(_unp)._endAdr < _G(_unp)._strMem)
- _G(_unp)._endAdr = 0x10000;
+ if (unp._endAdr < unp._strMem)
+ unp._endAdr = 0x10000;
- mem[_G(_unp)._strMem - 2] = _G(_unp)._strMem & 0xff;
- mem[_G(_unp)._strMem - 1] = _G(_unp)._strMem >> 8;
+ mem[unp._strMem - 2] = unp._strMem & 0xff;
+ mem[unp._strMem - 1] = unp._strMem >> 8;
- memcpy(destinationBuffer, mem + (_G(_unp)._strMem - 2), (size_t)(_G(_unp)._endAdr - _G(_unp)._strMem + 2));
- *finalLength = (size_t)(_G(_unp)._endAdr - _G(_unp)._strMem + 2);
+ memcpy(destinationBuffer, mem + (unp._strMem - 2), (size_t)(unp._endAdr - unp._strMem + 2));
+ *finalLength = (size_t)(unp._endAdr - unp._strMem + 2);
- if (_G(_unp)._recurs) {
- if (++_G(_unp)._recurs > RECUMAX)
+ if (unp._recurs) {
+ if (++unp._recurs > RECUMAX)
return 1;
- reinitUnp();
+ reinitUnp(unp);
goto looprecurse;
}
return 1;
}
-} // End of namespace Scott
-} // End of namespace Glk
+} // End of namespace Unp64
+} // End of namespace Common
diff --git a/engines/glk/scott/unp64/unp64.h b/common/compression/unp64/unp64.h
similarity index 93%
rename from engines/glk/scott/unp64/unp64.h
rename to common/compression/unp64/unp64.h
index 865816e49e9..76b2a37b7a6 100644
--- a/engines/glk/scott/unp64/unp64.h
+++ b/common/compression/unp64/unp64.h
@@ -19,11 +19,11 @@
*
*/
-#ifndef GLK_SCOTT_UNP64_H
-#define GLK_SCOTT_UNP64_H
+#ifndef COMMON_COMPRESSION_UNP64_UNP64_H
+#define COMMON_COMPRESSION_UNP64_UNP64_H
-namespace Glk {
-namespace Scott {
+namespace Common {
+namespace Unp64 {
struct LoadInfo;
struct CpuCtx;
@@ -38,7 +38,7 @@ struct UnpStr {
int _rtAFrc; /* flag, return address must be exactly RetAdr, else anything >= RetAdr */
int _wrMemF; /* flag, clean unwritten memory */
int _lfMemF; /* flag, clean end memory leftovers */
- int _exoFnd; /* flag, Exomizer detected */
+ int _exoFnd; /* flag, Exomizer detected */
int _fStack; /* flag, fill stack with 0 and SP=$ff, else as in C64 */
int _ecaFlg; /* ECA found, holds relocated areas high bytes */
int _fEndBf; /* End memory address pointer before unpacking, set when DepAdr is reached */
@@ -72,7 +72,7 @@ typedef void (*Scnptr)(UnpStr *);
void scanners(UnpStr *);
-} // End of namespace Scott
-} // End of namespace Glk
+} // End of namespace Unp64
+} // End of namespace Common
-#endif
+#endif
diff --git a/configure b/configure
index eb45d0fda27..f01a8aa289b 100755
--- a/configure
+++ b/configure
@@ -315,6 +315,7 @@ _fmtowns_pc98_audio=auto
_sid_audio=auto
_svq1=auto
_truemotion1=auto
+_unp64=auto
_vgmtrans_audio=auto
_xan=auto
_midi=auto
@@ -366,6 +367,7 @@ add_component svq1 "Sorenson Video 1" "_svq1" "USE_SVQ1"
add_component tinygl "TinyGL" "_tinygl" "USE_TINYGL"
add_component truemotion1 "TrueMotion 1" "_truemotion1" "USE_TRUEMOTION1"
add_component universaltracker "External Tracker Libraries" "_universaltracker" "USE_UNIVERSALTRACKER"
+add_component unp64 "UNP64" "_unp64" "USE_UNP64"
add_component vgmtrans_audio "VGMTrans Soundfont audio" "_vgmtrans_audio" "USE_VGMTRANS_AUDIO"
add_component xan "XAN" "_xan" "USE_XAN"
diff --git a/engines/glk/configure.engine b/engines/glk/configure.engine
index 7db6d57f739..bf140785752 100644
--- a/engines/glk/configure.engine
+++ b/engines/glk/configure.engine
@@ -1,3 +1,3 @@
# This file is included from the main "configure" script
# add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps] [components]
-add_engine glk "Glk Interactive Fiction games" yes "" "" "16bit freetype2 jpeg png"
+add_engine glk "Glk Interactive Fiction games" yes "" "" "16bit freetype2 jpeg png unp64"
diff --git a/engines/glk/module.mk b/engines/glk/module.mk
index 52e0dc24c94..03cf17c394c 100644
--- a/engines/glk/module.mk
+++ b/engines/glk/module.mk
@@ -260,25 +260,6 @@ MODULE_OBJS := \
scott/scott.o \
scott/seas_of_blood.o \
scott/ti99_4a_terp.o \
- scott/unp64/unp64.o \
- scott/unp64/6502_emu.o \
- scott/unp64/exo_util.o \
- scott/unp64/scanners/scanners.o \
- scott/unp64/scanners/action_packer.o \
- scott/unp64/scanners/byte_boiler.o \
- scott/unp64/scanners/caution.o \
- scott/unp64/scanners/ccs.o \
- scott/unp64/scanners/cruel.o \
- scott/unp64/scanners/eca.o \
- scott/unp64/scanners/exomizer.o \
- scott/unp64/scanners/expert.o \
- scott/unp64/scanners/master_compressor.o \
- scott/unp64/scanners/megabyte.o \
- scott/unp64/scanners/pu_crunch.o \
- scott/unp64/scanners/section8.o \
- scott/unp64/scanners/tbc_multicomp.o \
- scott/unp64/scanners/tcs_crunch.o \
- scott/unp64/scanners/xtc.o \
tads/os_banners.o \
tads/os_buffer.o \
tads/os_glk.o \
diff --git a/engines/glk/scott/c64_checksums.cpp b/engines/glk/scott/c64_checksums.cpp
index 4bf9fb9106c..da9092eb5e1 100644
--- a/engines/glk/scott/c64_checksums.cpp
+++ b/engines/glk/scott/c64_checksums.cpp
@@ -30,6 +30,7 @@
* https://github.com/angstsmurf/spatterlight/tree/master/terps/scott
*/
+#include "common/compression/unp64.h"
#include "common/str.h"
#include "common/scummsys.h"
#include "common/ptr.h"
@@ -41,7 +42,6 @@
#include "glk/scott/game_info.h"
#include "glk/scott/resource.h"
#include "glk/scott/saga_draw.h"
-#include "glk/scott/unp64/unp64_interface.h"
namespace Glk {
namespace Scott {
@@ -480,7 +480,7 @@ int decrunchC64(uint8_t **sf, size_t *extent, C64Rec record) {
uint8_t *uncompressed = nullptr;
_G(_fileLength) = *extent;
- size_t decompressedLength = *extent;
+ uint32 decompressedLength = *extent;
uncompressed = new uint8_t[0xffff];
@@ -489,9 +489,9 @@ int decrunchC64(uint8_t **sf, size_t *extent, C64Rec record) {
for (int i = 1; i <= record._decompressIterations; i++) {
/* We only send switches on the iteration specified by parameter */
if (i == record._parameter && record._switches != nullptr) {
- result = unp64(_G(_entireFile), _G(_fileLength), uncompressed, &decompressedLength, record._switches);
+ result = Common::Unp64::unp64(_G(_entireFile), _G(_fileLength), uncompressed, &decompressedLength, record._switches);
} else
- result = unp64(_G(_entireFile), _G(_fileLength), uncompressed, &decompressedLength, nullptr);
+ result = Common::Unp64::unp64(_G(_entireFile), _G(_fileLength), uncompressed, &decompressedLength, nullptr);
if (result) {
if (_G(_entireFile) != nullptr)
delete[] _G(_entireFile);
diff --git a/engines/glk/scott/globals.h b/engines/glk/scott/globals.h
index aff6425ad2a..d38379ad455 100644
--- a/engines/glk/scott/globals.h
+++ b/engines/glk/scott/globals.h
@@ -41,7 +41,6 @@
#include "glk/windows.h"
#include "glk/scott/definitions.h"
#include "glk/scott/types.h"
-#include "glk/scott/unp64/unp64.h"
namespace Glk {
namespace Scott {
@@ -182,16 +181,6 @@ public:
// detect game
Common::HashMap<Common::String, int> _md5Index;
- // unp64
- UnpStr _unp;
- int _parsePar = 1;
- int _iter = 0;
-
- // 6502 emu
- int _byted011[2] = {0, 0};
- int _retfire = 0xff;
- int _retspace = 0xff;
-
// robin of sherwood]
uint8_t *_forestImages = nullptr;
More information about the Scummvm-git-logs
mailing list