Files
scummvm/engines/agos/vga_e2.cpp
T
Coen Rampen 0fafb0ee19 AGOS: Fix Waxworks crash when playing digital SFX
Waxworks DOS has both digital and limited MIDI SFX. The game scripts use two
different opcodes for trigging digital and MIDI SFX; if a sound effect has both
a digital and a MIDI version, both opcodes are triggered. When digital SFX are
active, ScummVM would try to handle both the digital and the MIDI opcode by
playing a digital SFX. This would crash the game if the MIDI SFX ID was invalid
as a digital SFX ID. Strangely enough, this bug also seems to affect the Amiga
version. Apparently both opcodes are still in the scripts of this version, even
though it does not support MIDI at all AFAIK.

This commit fixes the issue by only playing the MIDI SFX if digital SFX are
turned off and ignoring them otherwise.
2022-08-05 22:16:43 +02:00

409 lines
9.5 KiB
C++

/* 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/>.
*
*/
// Video script opcodes for Simon1/Simon2
#include "agos/agos.h"
#include "agos/intern.h"
#include "agos/sound.h"
#include "common/endian.h"
#include "common/system.h"
#include "graphics/surface.h"
#include "graphics/palette.h"
namespace AGOS {
void AGOSEngine_Elvira2::setupVideoOpcodes(VgaOpcodeProc *op) {
AGOSEngine::setupVideoOpcodes(op);
op[17] = &AGOSEngine::vc17_waitEnd;
op[19] = &AGOSEngine::vc19_loop;
op[22] = &AGOSEngine::vc22_setPalette;
op[28] = &AGOSEngine::vc28_playSFX;
op[32] = &AGOSEngine::vc32_saveScreen;
op[37] = &AGOSEngine::vc37_pokePalette;
op[45] = &AGOSEngine::vc45_setWindowPalette;
op[46] = &AGOSEngine::vc46_setPaletteSlot1;
op[47] = &AGOSEngine::vc47_setPaletteSlot2;
op[48] = &AGOSEngine::vc48_setPaletteSlot3;
op[53] = &AGOSEngine::vc53_dissolveIn;
op[54] = &AGOSEngine::vc54_dissolveOut;
op[57] = &AGOSEngine::vc57_blackPalette;
op[56] = &AGOSEngine::vc56_fullScreen;
op[58] = &AGOSEngine::vc58_checkCodeWheel;
op[59] = &AGOSEngine::vc59_ifEGA;
}
void AGOSEngine::vc43_ifBitSet() {
if (!getBitFlag(vcReadNextWord())) {
vcSkipNextInstruction();
}
}
void AGOSEngine::vc44_ifBitClear() {
if (getBitFlag(vcReadNextWord())) {
vcSkipNextInstruction();
}
}
void AGOSEngine::vc45_setWindowPalette() {
uint16 num = vcReadNextWord();
uint16 color = vcReadNextWord();
const uint16 *vlut = &_videoWindows[num * 4];
uint8 width = vlut[2] * 8;
uint8 height = vlut[3];
if (num == 4) {
byte *dst = (byte *)_window4BackScn->getPixels();
for (uint8 h = 0; h < height; h++) {
for (uint8 w = 0; w < width; w++) {
uint16 val = READ_LE_UINT16(dst + w * 2);
val &= 0xF0F;
val |= color * 16;
WRITE_LE_UINT16(dst + w * 2, val);
}
dst += width * 2;
}
} else {
Graphics::Surface *screen = getBackendSurface();
byte *dst = (byte *)screen->getBasePtr(vlut[0] * 16, vlut[1]);
if (getGameType() == GType_ELVIRA2 && num == 7) {
dst -= 8;
width += 4;
}
for (uint8 h = 0; h < height; h++) {
for (uint8 w = 0; w < width; w++) {
uint16 val = READ_LE_UINT16(dst + w * 2);
val &= 0xF0F;
val |= color * 16;
WRITE_LE_UINT16(dst + w * 2, val);
}
dst += screen->pitch;
}
updateBackendSurface();
}
}
void AGOSEngine::setPaletteSlot(uint16 srcOffs, uint8 dstOffs) {
byte *offs, *palptr, *src;
uint16 num;
palptr = _displayPalette + dstOffs * 3 * 16;
offs = _curVgaFile1 + READ_BE_UINT16(_curVgaFile1 + 6);
src = offs + srcOffs * 32;
num = 16;
do {
uint16 color = READ_BE_UINT16(src);
palptr[0] = ((color & 0xf00) >> 8) * 32;
palptr[1] = ((color & 0x0f0) >> 4) * 32;
palptr[2] = ((color & 0x00f) >> 0) * 32;
palptr += 3;
src += 2;
} while (--num);
_paletteFlag = 2;
}
void AGOSEngine::vc46_setPaletteSlot1() {
uint16 srcOffs = vcReadNextWord();
setPaletteSlot(srcOffs, 1);
}
void AGOSEngine::vc47_setPaletteSlot2() {
uint16 srcOffs = vcReadNextWord();
setPaletteSlot(srcOffs, 2);
}
void AGOSEngine::vc48_setPaletteSlot3() {
uint16 srcOffs = vcReadNextWord();
setPaletteSlot(srcOffs, 3);
}
void AGOSEngine::vc49_setBit() {
uint16 bit = vcReadNextWord();
if (getGameType() == GType_FF && bit == 82) {
_variableArrayPtr = _variableArray2;
}
setBitFlag(bit, true);
}
void AGOSEngine::vc50_clearBit() {
uint16 bit = vcReadNextWord();
if (getGameType() == GType_FF && bit == 82) {
_variableArrayPtr = _variableArray;
}
setBitFlag(bit, false);
}
void AGOSEngine::vc51_enableBox() {
enableBox(vcReadNextWord());
}
void AGOSEngine::vc52_playSound() {
bool ambient = false;
uint16 sound = vcReadNextWord();
if (sound >= 0x8000) {
ambient = true;
sound = -sound;
}
if (getGameType() == GType_FF || getGameType() == GType_PP) {
int16 pan = vcReadNextWord();
int16 vol = vcReadNextWord();
if (ambient)
loadSound(sound, pan, vol, Sound::TYPE_AMBIENT);
else
loadSound(sound, pan, vol, Sound::TYPE_SFX);
} else if (getGameType() == GType_SIMON2) {
if (ambient)
_sound->playAmbient(sound);
else
_sound->playEffects(sound);
} else if (getFeatures() & GF_TALKIE) {
_sound->playEffects(sound);
} else {
// Waxworks uses 2 opcodes to play SFX: vc28 for digital SFX and vc52
// for MIDI SFX. If a sound effect has both a MIDI and a digital
// version, both opcodes are triggered. Only one of them should play
// a sound effect.
playSfx(sound, 0, 0, false, getGameType() == GType_WW);
}
}
void AGOSEngine::vc53_dissolveIn() {
uint16 num = vcReadNextWord();
uint16 speed = vcReadNextWord() + 1;
byte *src, *dst, *srcOffs, *srcOffs2, *dstOffs, *dstOffs2;
int16 xoffs, yoffs;
uint8 color = 0;
// Only uses Video Window 4
num = 4;
uint16 dissolveX = _videoWindows[num * 4 + 2] * 8;
uint16 dissolveY = (_videoWindows[num * 4 + 3] + 1) / 2;
uint16 dissolveCheck = dissolveY * dissolveX * 4;
uint16 dissolveDelay = dissolveCheck * 2 / speed;
uint16 dissolveCount = dissolveCheck * 2 / speed;
int16 x = _videoWindows[num * 4 + 0] * 16;
int16 y = _videoWindows[num * 4 + 1];
uint16 count = dissolveCheck * 2;
while (count--) {
Graphics::Surface *screen = getBackendSurface();
byte *dstPtr = (byte *)screen->getBasePtr(x, y);
yoffs = _rnd.getRandomNumber(dissolveY);
dst = dstPtr + yoffs * screen->pitch;
src = (byte *)_window4BackScn->getBasePtr(0, yoffs);
xoffs = _rnd.getRandomNumber(dissolveX);
dst += xoffs;
src += xoffs;
*dst &= color;
*dst |= *src & 0xF;
dstOffs = dst;
srcOffs = src;
xoffs = dissolveX * 2 - 1 - (xoffs * 2);
dst += xoffs;
src += xoffs;
*dst &= color;
*dst |= *src & 0xF;
srcOffs2 = src;
dstOffs2 = dst;
yoffs = (dissolveY - 1) * 2 - (yoffs * 2);
src = srcOffs + yoffs * _window4BackScn->pitch;
dst = dstOffs + yoffs * screen->pitch;
color = 0xF0;
*dst &= color;
*dst |= *src & 0xF;
dst = dstOffs2 + yoffs * screen->pitch;
src = srcOffs2 + yoffs * _window4BackScn->pitch;
*dst &= color;
*dst |= *src & 0xF;
updateBackendSurface();
dissolveCount--;
if (!dissolveCount) {
if (count >= dissolveCheck)
dissolveDelay++;
dissolveCount = dissolveDelay;
delay(1);
}
}
}
void AGOSEngine::vc54_dissolveOut() {
uint16 num = vcReadNextWord();
uint16 color = vcReadNextWord();
uint16 speed = vcReadNextWord() + 1;
byte *dst, *dstOffs;
int16 xoffs, yoffs;
uint16 dissolveX = _videoWindows[num * 4 + 2] * 8;
uint16 dissolveY = (_videoWindows[num * 4 + 3] + 1) / 2;
uint16 dissolveCheck = dissolveY * dissolveX * 4;
uint16 dissolveDelay = dissolveCheck * 2 / speed;
uint16 dissolveCount = dissolveCheck * 2 / speed;
int16 x = _videoWindows[num * 4 + 0] * 16;
int16 y = _videoWindows[num * 4 + 1];
uint16 count = dissolveCheck * 2;
while (count--) {
Graphics::Surface *screen = getBackendSurface();
byte *dstPtr = (byte *)screen->getBasePtr(x, y);
color |= dstPtr[0] & 0xF0;
yoffs = _rnd.getRandomNumber(dissolveY);
xoffs = _rnd.getRandomNumber(dissolveX);
dst = dstPtr + xoffs + yoffs * screen->pitch;
*dst = color;
dstOffs = dst;
xoffs = dissolveX * 2 - 1 - (xoffs * 2);
dst += xoffs;
*dst = color;
yoffs = (dissolveY - 1) * 2 - (yoffs * 2);
dst = dstOffs + yoffs * screen->pitch;
*dst = color;
dst += xoffs;
*dst = color;
updateBackendSurface();
dissolveCount--;
if (!dissolveCount) {
if (count >= dissolveCheck)
dissolveDelay++;
dissolveCount = dissolveDelay;
delay(1);
}
}
}
void AGOSEngine::vc55_moveBox() {
HitArea *ha = _hitAreas;
uint count = ARRAYSIZE(_hitAreas);
uint16 id = vcReadNextWord();
int16 x = vcReadNextWord();
int16 y = vcReadNextWord();
for (;;) {
if (ha->id == id) {
ha->x += x;
ha->y += y;
break;
}
ha++;
if (!--count)
break;
}
_needHitAreaRecalc++;
}
void AGOSEngine::fullFade() {
uint8 *srcPal, *dstPal;
int c, p;
for (c = 64; c != 0; c --) {
srcPal = _curVgaFile2 + 32;
dstPal = _currentPalette;
for (p = 768; p !=0; p -= 3) {
uint8 r = srcPal[0] * 4;
if (dstPal[0] != r)
dstPal[0] += 4;
uint8 g = srcPal[1] * 4;
if (dstPal[1] != g)
dstPal[1] += 4;
uint8 b = srcPal[2] * 4;
if (dstPal[2] != b)
dstPal[2] += 4;
srcPal += 3;
dstPal += 3;
}
_system->getPaletteManager()->setPalette(_currentPalette, 0, 256);
delay(5);
}
}
void AGOSEngine::vc56_fullScreen() {
Graphics::Surface *screen = getBackendSurface();
byte *dst = (byte *)screen->getPixels();
byte *src = _curVgaFile2 + 800;
for (int i = 0; i < _screenHeight; i++) {
memcpy(dst, src, _screenWidth);
src += 320;
dst += screen->pitch;
}
updateBackendSurface();
fullFade();
}
void AGOSEngine::vc57_blackPalette() {
memset(_currentPalette, 0, sizeof(_currentPalette));
_system->getPaletteManager()->setPalette(_currentPalette, 0, 256);
}
void AGOSEngine::vc58_checkCodeWheel() {
_variableArray[0] = 0;
}
void AGOSEngine::vc59_ifEGA() {
// Skip if not EGA
vcSkipNextInstruction();
}
} // End of namespace AGOS