Compare commits

...

5 Commits
0.4.7 ... main

Author SHA1 Message Date
George Sokianos 64f00204e7 Changes in the code to work on AmigaOS4 and release files added 2023-09-17 17:53:43 +01:00
Gregory Montoir 100218a3c2 Import 0.5.1 2023-04-15 08:44:11 +08:00
Gregory Montoir 1f86fdea2d Import 0.5.0 2023-03-31 21:23:51 +08:00
Gregory Montoir 419cf91dfe Import 0.4.9 2021-09-06 00:00:00 +08:00
Gregory Montoir 315bb9bcff Import 0.4.8 2021-05-28 00:00:00 +08:00
55 changed files with 8173 additions and 670 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
*.o
*.d
*.lha
rs

View File

@ -1,3 +1,24 @@
* release 0.5.1
- added looping for DOS .prf music
- changed audio mixer to stereo
- updated timings for cutscenes
* release 0.5.0
- added CD-i widescreen mode (flashp*bob)
- added support for DOS .prf music (Adlib, MT32)
* release 0.4.9
- added option to match original inventory items order
- added zoom from DOS version
- added Sega CD tracks playback based on stb_vorbis (OGG 22khz)
- fixed piege opcode 0x57
- fixed volume of repeating sounds
* release 0.4.8
- added detection for DOS version with .ABA files
- added Macintosh credits
- fixed ESPIONS cutscene timing with Amiga music
* release 0.4.7
- added detection for Macintosh CD version
- restored some content from MEMO cutscene

View File

@ -3,21 +3,30 @@ SDL_CFLAGS := `sdl2-config --cflags`
SDL_LIBS := `sdl2-config --libs`
MODPLUG_LIBS := -lmodplug
TREMOR_LIBS := -lvorbisidec -logg
TREMOR_LIBS := #-lvorbisidec -logg
ZLIB_LIBS := -lz
CXXFLAGS += -Wall -Wpedantic -MMD $(SDL_CFLAGS) -DUSE_MODPLUG -DUSE_TREMOR -DUSE_ZLIB
LIBS = $(SDL_LIBS) $(MODPLUG_LIBS) $(TREMOR_LIBS) $(ZLIB_LIBS)
CXXFLAGS += -Wall -Wextra -Wno-unused-parameter -Wpedantic -MMD $(SDL_CFLAGS) -DUSE_MODPLUG -DUSE_STB_VORBIS -DUSE_ZLIB
SRCS = collision.cpp cpc_player.cpp cutscene.cpp decode_mac.cpp file.cpp fs.cpp game.cpp graphics.cpp main.cpp \
menu.cpp mixer.cpp mod_player.cpp ogg_player.cpp piege.cpp protection.cpp resource.cpp resource_aba.cpp \
menu.cpp midi_parser.cpp mixer.cpp mod_player.cpp ogg_player.cpp \
piege.cpp prf_player.cpp protection.cpp resource.cpp resource_aba.cpp \
resource_mac.cpp scaler.cpp screenshot.cpp seq_player.cpp \
sfx_player.cpp staticres.cpp systemstub_sdl.cpp unpack.cpp util.cpp video.cpp
#CXXFLAGS += -DUSE_STATIC_SCALER
#SCALERS := scalers/scaler_nearest.cpp scalers/scaler_tv2x.cpp scalers/scaler_xbr.cpp
OBJS = $(SRCS:.cpp=.o) $(SCALERS:.cpp=.o)
DEPS = $(SRCS:.cpp=.d) $(SCALERS:.cpp=.d)
#CXXFLAGS += -DUSE_MIDI_DRIVER
#MIDIDRIVERS := midi_driver_adlib.cpp midi_driver_mt32.cpp
#MIDI_LIBS := -lmt32emu
LIBS = $(SDL_LIBS) $(MODPLUG_LIBS) $(TREMOR_LIBS) $(ZLIB_LIBS)
LIBS = $(MIDI_LIBS) $(MODPLUG_LIBS) $(SDL_LIBS) $(TREMOR_LIBS) $(ZLIB_LIBS)
OBJS = $(SRCS:.cpp=.o) $(SCALERS:.cpp=.o) $(MIDIDRIVERS:.cpp=.o)
DEPS = $(SRCS:.cpp=.d) $(SCALERS:.cpp=.d) $(MIDIDRIVERS:.cpp=.d)
rs: $(OBJS)
$(CXX) $(LDFLAGS) -o $@ $(OBJS) $(LIBS)

55
Makefile.os4 Normal file
View File

@ -0,0 +1,55 @@
SDL_CFLAGS := -I/sdk/local/newlib/include/SDL2 -DNDEBUG=1
# -DUSE_STATIC_SCALER=1
SDL_LIBS := -lSDL2_mixer -lmikmod -lmodplug -lFLAC -logg \
-lSDL2 -lpthread -athread=native -lstdc++
MODPLUG_LIBS := -lmodplug
TREMOR_LIBS := #-lvorbisidec -logg
ZLIB_LIBS := -lz
LIBS = $(SDL_LIBS) $(MODPLUG_LIBS) $(TREMOR_LIBS) $(ZLIB_LIBS)
CXXFLAGS += -Wall -Wextra -Wno-unused-parameter -Wpedantic -MMD $(SDL_CFLAGS) -DUSE_MODPLUG -DUSE_STB_VORBIS -DUSE_ZLIB
SRCS = collision.cpp cpc_player.cpp cutscene.cpp decode_mac.cpp file.cpp fs.cpp game.cpp graphics.cpp main.cpp \
menu.cpp midi_parser.cpp mixer.cpp mod_player.cpp ogg_player.cpp \
piege.cpp prf_player.cpp protection.cpp resource.cpp resource_aba.cpp \
resource_mac.cpp scaler.cpp screenshot.cpp seq_player.cpp \
sfx_player.cpp staticres.cpp systemstub_sdl.cpp unpack.cpp util.cpp video.cpp
#CXXFLAGS += -DUSE_STATIC_SCALER
#SCALERS := scalers/scaler_nearest.cpp scalers/scaler_tv2x.cpp scalers/scaler_xbr.cpp
#CXXFLAGS += -DUSE_MIDI_DRIVER
#MIDIDRIVERS := midi_driver_adlib.cpp midi_driver_mt32.cpp
#MIDI_LIBS := -lmt32emu
LIBS = $(MIDI_LIBS) $(MODPLUG_LIBS) $(SDL_LIBS) $(TREMOR_LIBS) $(ZLIB_LIBS)
OBJS = $(SRCS:.cpp=.o) $(SCALERS:.cpp=.o) $(MIDIDRIVERS:.cpp=.o)
DEPS = $(SRCS:.cpp=.d) $(SCALERS:.cpp=.d) $(MIDIDRIVERS:.cpp=.d)
rs: $(OBJS)
$(CXX) $(LDFLAGS) -o $@ $(OBJS) $(LIBS)
clean:
rm -f $(OBJS) $(DEPS)
# prepare an archive for the program
release:
@echo "Creating release files..."
@strip rs
@mkdir -p release/
@copy ALL release_files/ release/ QUIET
@mkdir -p release/REminiscence/data
@mkdir -p release/REminiscence/saves
@copy rs "release/REminiscence/"
@copy README.txt "release/REminiscence/"
@copy rs.cfg "release/REminiscence/"
@echo "Creating release archive..."
@lha -aeqr3 a REminiscence.lha release/
@echo "Clean release files..."
@delete release ALL QUIET FORCE
-include $(DEPS)

View File

@ -1,6 +1,6 @@
REminiscence README
Release version: 0.4.7
Release version: 0.5.1
-------------------------------------------------------------------------------
@ -21,63 +21,63 @@ release. Support for Amiga and Macintosh is still experimental.
For the Macintosh release, the resource fork must be dumped as a file named
'FLASHBACK.BIN' (MacBinary) or 'FLASHBACK.RSRC' (AppleDouble).
To hear music during polygonal cutscenes with the PC version, you need to copy
the music/ directory of the Amiga version or use the .mod fileset from
unexotica [4].
For speech with in-game dialogues, you need to copy the 'VOICE.VCE' file from
the SegaCD version to the DATA directory.
The music/ directory of the Amiga version or the .mod fileset from
unexotica [4] can be used as an alternative to the MIDI tracks from the
DOS version. Simply copy the files to the DATA directory and set the
'use_prf_music' option to false in the configuration file.
Running:
--------
By default, the engine tries to load the game data files from the 'DATA'
directory, as the original game executable did. The savestates are saved in the
current directory.
By default, the engine loads the game data files from the 'DATA' directory,
as the original game executable did. The savestates are saved in the current
directory.
These paths can be changed using command line switches :
These paths can be changed using command line switches:
Usage: rs [OPTIONS]...
--datapath=PATH Path to data files (default 'DATA')
--savepath=PATH Path to save files (default '.')
--levelnum=NUM Level to start from (default '0')
--fullscreen Fullscreen display
--widescreen=MODE 16:9 display (adjacent,mirror,blur,none)
--widescreen=MODE Widescreen display (adjacent,mirror,blur,cdi,none)
--scaler=NAME@X Graphics scaler (default 'scale@3')
--language=LANG Language (fr,en,de,sp,it,jp)
--autosave Save game state automatically
--mididriver=MIDI Driver (adlib, mt32)
The scaler option specifies the algorithm used to smoothen the image in
addition to a scaling factor. External scalers are also supported, the suffix
shall be used as the name. Eg. If you have scaler_xbr.dll, you can pass
'--scaler xbr@2' to use that algorithm with a doubled window size (512x448).
The scaler option specifies the algorithm used to smoothen the image and the
scaling factor. External scalers are also supported, the suffix shall be used
as the name. Eg. If you have scaler_xbr.dll, you can pass '--scaler xbr@2'
to use that algorithm with a doubled window size (512x448).
The widescreen option accepts two modes :
'adjacent' : left and right rooms bitmaps will be drawn
'mirror' : the current room bitmap will be drawn mirrored
The widescreen option accepts the modes below:
In-game hotkeys :
adjacent draw left and right rooms bitmap
mirror mirror the current room bitmap
blur blur and stretch the current room bitmap
cdi use bitmaps from the CD-i release ('flashp?.bob' files)
Arrow Keys move Conrad
Enter use the current inventory object
Shift talk / use / run / shoot
Escape display the options
Backspace display the inventory
Alt Enter toggle windowed/fullscreen mode
Alt + and - increase or decrease game screen scaler factor
Alt S write screenshot as .tga
Ctrl S save game state
Ctrl L load game state
Ctrl R rewind game state buffer (requires --autosave)
Ctrl + and - change game state slot
Function Keys change game screen scaler
In-game keys:
Debug hotkeys :
Ctrl F toggle fast mode
Ctrl I Conrad 'infinite' life
Ctrl B toggle display of updated dirty blocks
Arrow Keys move Conrad
Enter use the current inventory object
Shift talk / use / run / shoot
Escape display options
Backspace / Tab display inventory / skip cutscene
Alt Enter toggle windowed / fullscreen mode
Alt + and - increase or decrease game screen scaler factor
Alt S take screenshot
Ctrl G toggle auto zoom (DOS version only)
Ctrl S save game state
Ctrl L load game state
Ctrl R rewind game state buffer (requires --autosave)
Ctrl + and - change game state slot
Function Keys change game screen scaler
Credits:

View File

@ -42,13 +42,13 @@ void Game::col_clearState() {
void Game::col_preparePiegeState(LivePGE *pge) {
debug(DBG_COL, "Game::col_preparePiegeState() pge_num=%ld", pge - &_pgeLive[0]);
CollisionSlot *ct_slot1, *ct_slot2;
if (pge->init_PGE->unk1C == 0) {
if (pge->init_PGE->collision_data_len == 0) {
pge->collision_slot = 0xFF;
return;
}
int i = 0;
ct_slot1 = 0;
for (int c = 0; c < pge->init_PGE->unk1C; ++c) {
for (int c = 0; c < pge->init_PGE->collision_data_len; ++c) {
ct_slot2 = _col_curSlot;
if (ct_slot2 + 1 > &_col_slots[255])
return;
@ -231,7 +231,7 @@ int16_t Game::col_detectHit(LivePGE *pge, int16_t arg2, int16_t arg4, col_Callba
if (pge_room < 0 || pge_room >= 0x40) {
return 0;
}
int16_t thr = pge->init_PGE->counter_values[0];
int16_t thr = pge->init_PGE->data[0];
if (thr > 0) {
pos_dx = -1;
pos_dy = -1;
@ -361,7 +361,7 @@ int Game::col_detectHitCallbackHelper(LivePGE *pge, int16_t msgNum) {
ObjectNode *on = _res._objectNodesMap[init_pge->obj_node_number];
Object *obj = &on->objects[pge->first_obj_number];
int i = pge->first_obj_number;
while (pge->obj_type == obj->type && on->last_obj_number > i) {
while (pge->obj_type == obj->type && on->num_objects > i) {
if (obj->opcode2 == 0x6B) { // pge_isToggleable
if (obj->opcode_arg2 == 0) {
if (msgNum == 1 || msgNum == 2) return 0xFFFF;
@ -452,9 +452,9 @@ int Game::col_detectGunHit(LivePGE *pge, int16_t arg2, int16_t arg4, col_Callbac
if (pge_room < 0 || pge_room >= 0x40) return 0;
int16_t thr, pos_dx, pos_dy;
if (argC == -1) {
thr = pge->init_PGE->counter_values[0];
thr = pge->init_PGE->data[0];
} else {
thr = pge->init_PGE->counter_values[3];
thr = pge->init_PGE->data[3];
}
if (thr > 0) {
pos_dx = -1;

View File

@ -108,6 +108,7 @@ int8_t CpcPlayer::readSampleData() {
// rewind
_f.seek(_restartPos);
nextChunk();
_sampleL = _sampleR = 0;
}
}
const int8_t data = _f.readByte();
@ -119,7 +120,8 @@ bool CpcPlayer::mix(int16_t *buf, int len) {
for (int i = 0; i < len; ++i) {
_sampleL = decodeSDX2(_sampleL, readSampleData());
_sampleR = decodeSDX2(_sampleR, readSampleData());
*buf++ = (_sampleL + _sampleR) / 2;
*buf++ = _sampleL;
*buf++ = _sampleR;
}
return true;
}

View File

@ -35,15 +35,16 @@ const uint8_t *Cutscene::getPolygonData() const {
return _res->_pol;
}
void Cutscene::sync() {
void Cutscene::sync(int frameDelay) {
if (_stub->_pi.quit) {
return;
}
if (_stub->_pi.dbgMask & PlayerInput::DF_FASTMODE) {
return;
}
static const int frameHz = 60;
const int32_t delay = _stub->getTimeStamp() - _tstamp;
const int32_t pause = _frameDelay * TIMER_SLICE - delay;
const int32_t pause = frameDelay * (1000 / frameHz) - delay;
if (pause > 0) {
_stub->sleep(pause);
}
@ -72,7 +73,7 @@ void Cutscene::updatePalette() {
}
void Cutscene::updateScreen() {
sync();
sync(_frameDelay - 1);
updatePalette();
SWAP(_frontPage, _backPage);
_stub->copyRect(0, 0, _vid->_w, _vid->_h, _frontPage, _vid->_w);
@ -147,10 +148,12 @@ uint16_t Cutscene::findTextSeparators(const uint8_t *p, int len) {
void Cutscene::drawText(int16_t x, int16_t y, const uint8_t *p, uint16_t color, uint8_t *page, int textJustify) {
debug(DBG_CUT, "Cutscene::drawText(x=%d, y=%d, c=%d, justify=%d)", x, y, color, textJustify);
int len = 0;
if (_res->_type == kResourceTypeMac) {
if (p != _textBuf && _res->isMac()) {
len = *p++;
} else {
len = strlen((const char *)p);
while (p[len] != 0xA && p[len]) {
++len;
}
}
Video::drawCharFunc dcf = _vid->_drawChar;
const uint8_t *fnt = (_res->_lang == LANG_JP) ? Video::_font8Jp : _res->_fnt;
@ -196,42 +199,69 @@ void Cutscene::clearBackPage() {
}
void Cutscene::drawCreditsText() {
if (_creditsSequence) {
if (_creditsKeepText != 0) {
if (_creditsSlowText == 0) {
_creditsKeepText = 0;
} else {
if (_creditsKeepText) {
if (_creditsSlowText) {
return;
}
_creditsKeepText = false;
}
if (_creditsTextCounter <= 0) {
uint8_t code;
const bool isMac = _res->isMac();
if (isMac && _creditsTextLen <= 0) {
const uint8_t *p = _res->getCreditsString(_creditsTextIndex++);
if (!p) {
return;
}
_creditsTextCounter = 60;
_creditsTextPosX = p[0];
_creditsTextPosY = p[1];
_creditsTextLen = p[2];
_textCurPtr = p + 2;
code = 0;
} else {
code = *_textCurPtr;
}
if (_creditsTextCounter <= 0) {
const uint8_t code = *_textCurPtr;
if (code == 0xFF) {
_textBuf[0] = 0xA;
} else if (code == 0xFE) {
++_textCurPtr;
_creditsTextCounter = *_textCurPtr++;
} else if (code == 1) {
++_textCurPtr;
_creditsTextPosX = *_textCurPtr++;
_creditsTextPosY = *_textCurPtr++;
} else if (code == 0) {
_textCurBuf = _textBuf;
_textBuf[0] = 0xA;
++_textCurPtr;
if (_creditsSlowText != 0) {
_creditsKeepText = 0xFF;
}
} else {
*_textCurBuf++ = code;
*_textCurBuf = 0xA;
++_textCurPtr;
if (code == 0x7D && isMac) {
++_textCurPtr;
code = *_textCurPtr++;
_creditsTextLen -= 2;
assert(code > 0x30);
for (int i = 0; i < (code - 0x30); ++i) {
*_textCurBuf++ = ' ';
}
*_textCurBuf = 0xA;
} else if (code == 0xFF) {
_textBuf[0] = 0xA;
} else if (code == 0xFE) {
++_textCurPtr;
_creditsTextCounter = *_textCurPtr++;
} else if (code == 1) {
++_textCurPtr;
_creditsTextPosX = *_textCurPtr++;
_creditsTextPosY = *_textCurPtr++;
} else if (code == 0) {
_textCurBuf = _textBuf;
_textBuf[0] = 0xA;
++_textCurPtr;
if (_creditsSlowText) {
_creditsKeepText = true;
}
} else {
_creditsTextCounter -= 10;
*_textCurBuf++ = code;
*_textCurBuf = 0xA;
++_textCurPtr;
if (isMac) {
--_creditsTextLen;
if (_creditsTextLen == 0) {
_creditsTextCounter = 600;
}
}
}
drawText((_creditsTextPosX - 1) * 8, _creditsTextPosY * 8, _textBuf, 0xEF, _backPage, kTextJustifyLeft);
} else {
_creditsTextCounter -= 10;
}
drawText((_creditsTextPosX - 1) * 8, _creditsTextPosY * 8, _textBuf, 0xEF, _backPage, kTextJustifyLeft);
}
void Cutscene::drawProtectionShape(uint8_t shapeNum, int16_t zoom) {
@ -272,11 +302,19 @@ void Cutscene::drawProtectionShape(uint8_t shapeNum, int16_t zoom) {
void Cutscene::op_markCurPos() {
debug(DBG_CUT, "Cutscene::op_markCurPos()");
_cmdPtrBak = _cmdPtr;
drawCreditsText();
_frameDelay = 5;
if (!_creditsSequence) {
if (_id == kCineDebut) {
_frameDelay = 7;
} else if (_id == kCineChute) {
_frameDelay = 6;
}
} else {
drawCreditsText();
}
updateScreen();
clearBackPage();
_creditsSlowText = 0;
_creditsSlowText = false;
}
void Cutscene::op_refreshScreen() {
@ -284,7 +322,7 @@ void Cutscene::op_refreshScreen() {
_clearScreen = fetchNextCmdByte();
if (_clearScreen != 0) {
clearBackPage();
_creditsSlowText = 0;
_creditsSlowText = false;
}
}
@ -293,20 +331,20 @@ void Cutscene::op_waitForSync() {
if (_creditsSequence) {
uint16_t n = fetchNextCmdByte() * 2;
do {
_creditsSlowText = 0xFF;
_creditsSlowText = true;
_frameDelay = 3;
if (_textBuf == _textCurBuf) {
_creditsTextCounter = _res->isAmiga() ? 60 : 20;
_creditsTextCounter = _res->isDOS() ? 20 : 60;
}
memcpy(_backPage, _frontPage, _vid->_layerSize);
drawCreditsText();
updateScreen();
} while (--n);
clearBackPage();
_creditsSlowText = 0;
_creditsSlowText = false;
} else {
_frameDelay = fetchNextCmdByte() * 4;
sync(); // XXX handle input
sync(_frameDelay);
}
}
@ -418,15 +456,6 @@ void Cutscene::op_drawCaptionText() {
uint16_t strId = fetchNextCmdWord();
if (!_creditsSequence) {
// 'espions' - ignore last call, allows caption to be displayed longer on the screen
if (_id == 0x39 && strId == 0xFFFF) {
if ((_res->isDOS() && (_cmdPtr - _cmdPtrBak) == 0x10) || (_res->isAmiga() && (_cmdPtr - getCommandData()) == 0x9F3)) {
_frameDelay = 100;
updateScreen();
return;
}
}
const int h = 45 * _vid->_layerScale;
const int y = Video::GAMESCREEN_H * _vid->_layerScale - h;
@ -439,6 +468,10 @@ void Cutscene::op_drawCaptionText() {
drawText(0, 129, str, 0xEF, _backPage, kTextJustifyAlign);
drawText(0, 129, str, 0xEF, _auxPage, kTextJustifyAlign);
}
} else if (_id == kCineEspions) {
// cutscene relies on drawCaptionText opcodes for timing
_frameDelay = 100;
sync(_frameDelay);
}
}
}
@ -457,7 +490,7 @@ void Cutscene::op_refreshAll() {
_frameDelay = 5;
updateScreen();
clearBackPage();
_creditsSlowText = 0xFF;
_creditsSlowText = true;
op_handleKeys();
}
@ -634,7 +667,7 @@ void Cutscene::op_drawShapeScale() {
_hasAlphaColor = (verticesOffset & 0x4000) != 0;
uint8_t color = *shapeData++;
if (_clearScreen == 0) {
color += 0x10; // 2nd paletter buffer
color += 0x10; // 2nd palette buffer
}
_primitiveColor = 0xC0 + color;
drawShapeScale(p, zoom, dx, dy, x, y, 0, 0);
@ -908,7 +941,7 @@ static int findSetPaletteColor(const uint16_t color, const uint16_t *paletteBuff
void Cutscene::op_copyScreen() {
debug(DBG_CUT, "Cutscene::op_copyScreen()");
_creditsSlowText = 0xFF;
_creditsSlowText = true;
if (_textCurBuf == _textBuf) {
++_creditsTextCounter;
}
@ -954,11 +987,11 @@ void Cutscene::op_drawTextAtPos() {
if (!_creditsSequence) {
const uint8_t *str = _res->getCineString(strId & 0xFFF);
if (str) {
uint8_t color = 0xD0 + (strId >> 0xC);
const uint8_t color = 0xD0 + (strId >> 0xC);
drawText(x, y, str, color, _backPage, kTextJustifyCenter);
}
// 'voyage' - cutscene script redraws the string to refresh the screen
if (_id == 0x34 && (strId & 0xFFF) == 0x45) {
if (_id == kCineVoyage && (strId & 0xFFF) == 0x45) {
if ((_cmdPtr - _cmdPtrBak) == 0xA) {
_stub->copyRect(0, 0, _vid->_w, _vid->_h, _backPage, _vid->_w);
_stub->updateScreen(0);
@ -1099,9 +1132,9 @@ bool Cutscene::load(uint16_t cutName) {
name = "SERRURE";
}
_res->load(name, Resource::OT_CMP);
if (_id == 0x39 && _res->_lang != LANG_FR) {
if (_id == kCineEspions) {
//
// 'espions' - '... the power which we need' caption is missing in Amiga English.
// '... the power which we need' caption is missing.
// fixed in DOS version, opcodes order is wrong
//
// opcode 0 pos 0x323
@ -1167,15 +1200,17 @@ void Cutscene::prepare() {
void Cutscene::playCredits() {
if (_res->isMac()) {
warning("Cutscene::playCredits() unimplemented");
return;
_res->MAC_loadCreditsText();
_creditsTextIndex = 0;
_creditsTextLen = 0;
} else {
_textCurPtr = _res->isAmiga() ? _creditsDataAmiga : _creditsDataDOS;
}
_textCurPtr = _res->isAmiga() ? _creditsDataAmiga : _creditsDataDOS;
_textBuf[0] = 0xA;
_textCurBuf = _textBuf;
_creditsSequence = true;
_creditsSlowText = 0;
_creditsKeepText = 0;
_creditsSlowText = false;
_creditsKeepText = false;
_creditsTextCounter = 0;
_interrupted = false;
const uint16_t *cut_seq = _creditsCutSeq;
@ -1223,7 +1258,7 @@ void Cutscene::playText(const char *str) {
_stub->_pi.backspace = false;
break;
}
_stub->sleep(TIMER_SLICE);
_stub->sleep(30);
}
}
@ -1433,7 +1468,7 @@ void Cutscene::playSet(const uint8_t *p, int offset) {
_stub->copyRect(0, 0, _vid->_w, _vid->_h, _backPage, _vid->_w);
_stub->updateScreen(0);
const int diff = 6 * TIMER_SLICE - (_stub->getTimeStamp() - timestamp);
const int diff = 90 - (_stub->getTimeStamp() - timestamp);
_stub->sleep((diff < 16) ? 16 : diff);
_stub->processEvents();
if (_stub->_pi.backspace) {

View File

@ -19,8 +19,7 @@ struct Cutscene {
enum {
MAX_VERTICES = 128,
NUM_OPCODES = 15,
TIMER_SLICE = 15
NUM_OPCODES = 15
};
enum {
@ -30,7 +29,11 @@ struct Cutscene {
};
enum {
kCineMemo = 48
kCineDebut = 0,
kCineChute = 47,
kCineMemo = 48,
kCineVoyage = 52,
kCineEspions = 57
};
struct SetShape {
@ -54,7 +57,8 @@ struct Cutscene {
static const uint8_t _creditsDataDOS[];
static const uint8_t _creditsDataAmiga[];
static const uint16_t _creditsCutSeq[];
static const uint8_t _musicTable[];
static const uint8_t _musicTableDOS[];
static const uint8_t _musicTableAmiga[];
static const uint8_t _protectionShapeData[];
static const Text _frTextsTable[];
static const Text _enTextsTable[];
@ -104,11 +108,13 @@ struct Cutscene {
uint8_t _textBuf[500];
const uint8_t *_textCurPtr;
uint8_t *_textCurBuf;
uint8_t _creditsSlowText;
uint8_t _creditsKeepText;
bool _creditsSlowText;
bool _creditsKeepText;
uint8_t _creditsTextPosX;
uint8_t _creditsTextPosY;
int16_t _creditsTextCounter;
int _creditsTextIndex; /* MAC has the credits data in a resource */
int _creditsTextLen;
uint8_t *_frontPage, *_backPage, *_auxPage;
Cutscene(Resource *res, SystemStub *stub, Video *vid);
@ -116,7 +122,7 @@ struct Cutscene {
const uint8_t *getCommandData() const;
const uint8_t *getPolygonData() const;
void sync();
void sync(int delay);
void copyPalette(const uint8_t *pal, uint16_t num);
void updatePalette();
void updateScreen();

View File

@ -3,11 +3,15 @@
#include <stdlib.h>
#include <string.h>
#include "decode_mac.h"
#include "file.h"
#include "util.h"
uint8_t *decodeLzss(File &f, uint32_t &decodedSize) {
decodedSize = f.readUint32BE();
uint8_t *dst = (uint8_t *)malloc(decodedSize);
if (!dst) {
warning("Failed to allocate %d bytes for LZSS", decodedSize);
return 0;
}
uint32_t count = 0;
while (count < decodedSize) {
const int code = f.readByte();

View File

@ -24,6 +24,7 @@ struct File_impl {
virtual void close() = 0;
virtual uint32_t size() = 0;
virtual void seek(int32_t off) = 0;
virtual uint32_t tell() = 0;
virtual uint32_t read(void *ptr, uint32_t len) = 0;
virtual uint32_t write(const void *ptr, uint32_t len) = 0;
};
@ -57,6 +58,12 @@ struct StdioFile : File_impl {
fseek(_fp, off, SEEK_SET);
}
}
uint32_t tell() {
if (_fp) {
return ftell(_fp);
}
return 0;
}
uint32_t read(void *ptr, uint32_t len) {
if (_fp) {
uint32_t r = fread(ptr, 1, len, _fp);
@ -109,6 +116,12 @@ struct GzipFile : File_impl {
gzseek(_fp, off, SEEK_SET);
}
}
uint32_t tell() {
if (_fp) {
return gztell(_fp);
}
return 0;
}
uint32_t read(void *ptr, uint32_t len) {
if (_fp) {
uint32_t r = gzread(_fp, ptr, len);
@ -343,6 +356,10 @@ void File::seek(int32_t off) {
_impl->seek(off);
}
uint32_t File::tell() {
return _impl->tell();
}
uint32_t File::read(void *ptr, uint32_t len) {
return _impl->read(ptr, len);
}

1
file.h
View File

@ -25,6 +25,7 @@ struct File {
bool ioErr() const;
uint32_t size();
void seek(int32_t off);
uint32_t tell();
uint32_t read(void *ptr, uint32_t len);
uint8_t readByte();
uint16_t readUint16LE();

4
fs.cpp
View File

@ -4,6 +4,10 @@
* Copyright (C) 2005-2019 Gregory Montoir (cyx@users.sourceforge.net)
*/
#if defined(__amigaos4__)
#include <sys/unistd.h>
#endif
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>

234
game.cpp
View File

@ -9,14 +9,14 @@
#include "file.h"
#include "fs.h"
#include "game.h"
#include "screenshot.h"
#include "seq_player.h"
#include "systemstub.h"
#include "unpack.h"
#include "util.h"
Game::Game(SystemStub *stub, FileSystem *fs, const char *savePath, int level, ResourceType ver, Language lang, WidescreenMode widescreenMode, bool autoSave)
Game::Game(SystemStub *stub, FileSystem *fs, const char *savePath, int level, ResourceType ver, Language lang, WidescreenMode widescreenMode, bool autoSave, int midiDriver, uint32_t cheats)
: _cut(&_res, stub, &_vid), _menu(&_res, stub, &_vid),
_mix(fs, stub), _res(fs, ver, lang), _seq(stub, &_mix), _vid(&_res, stub, widescreenMode),
_mix(fs, stub, midiDriver), _res(fs, ver, lang), _seq(stub, &_mix), _vid(&_res, stub, widescreenMode),
_stub(stub), _fs(fs), _savePath(savePath) {
_stateSlot = 1;
_inp_demPos = 0;
@ -27,6 +27,7 @@ Game::Game(SystemStub *stub, FileSystem *fs, const char *savePath, int level, Re
_autoSave = autoSave;
_rewindPtr = -1;
_rewindLen = 0;
_cheats = cheats;
}
void Game::run() {
@ -57,7 +58,7 @@ void Game::run() {
break;
}
if (!g_options.bypass_protection && !g_options.use_words_protection && !_res.isMac()) {
if (!g_options.bypass_protection && !g_options.use_words_protection && (_res.isAmiga() || _res.isDOS())) {
while (!handleProtectionScreenShape()) {
if (_stub->_pi.quit) {
return;
@ -127,13 +128,11 @@ void Game::run() {
_skillLevel = kSkillNormal;
_currentLevel = _demoInputs[_demoBin].level;
_randSeed = 0;
_mix.stopMusic();
break;
} else {
_demoBin = -1;
_skillLevel = _menu._skill;
_currentLevel = _menu._level;
}
_demoBin = -1;
_skillLevel = _menu._skill;
_currentLevel = _menu._level;
_mix.stopMusic();
break;
case kResourceTypeAmiga:
displayTitleScreenAmiga();
@ -143,6 +142,7 @@ void Game::run() {
displayTitleScreenMac(Menu::kMacTitleScreen_Flashback);
break;
}
_mix.stopMusic();
}
if (_stub->_pi.quit) {
break;
@ -172,7 +172,6 @@ void Game::run() {
if (_demoBin != -1 && _inp_demPos >= _res._demLen) {
debug(DBG_DEMO, "End of demo");
// exit level
_demoBin = -1;
_endLoop = true;
}
}
@ -361,15 +360,17 @@ void Game::resetGameState() {
_animBuffers._curPos[3] = 0xFF;
_currentRoom = _res._pgeInit[0].init_room;
_cut._deathCutsceneId = 0xFFFF;
_pge_opTempVar2 = 0xFFFF;
_deathCutsceneCounter = 0;
_saveStateCompleted = false;
_loadMap = true;
pge_resetMessages();
_blinkingConradCounter = 0;
_pge_processOBJ = false;
_pge_opTempVar1 = 0;
_pge_opGunVar = 0;
_textToDisplay = 0xFFFF;
_pge_zoomPiegeNum = 0;
_pge_zoomCounter = 0;
_pge_zoomX = _pge_zoomY = 0;
}
void Game::mainLoop() {
@ -418,7 +419,11 @@ void Game::mainLoop() {
_currentLevel = oldLevel;
}
changeLevel();
_pge_opTempVar1 = 0;
_pge_opGunVar = 0;
return;
}
if (_currentLevel == 3 && _cut._id == 50) {
// do not draw next room when boarding taxi
return;
}
if (_loadMap) {
@ -432,6 +437,9 @@ void Game::mainLoop() {
_vid.fullRefresh();
}
}
if (_res.isDOS() && (_stub->_pi.dbgMask & PlayerInput::DF_AUTOZOOM) != 0) {
pge_updateZoom();
}
prepareAnims();
drawAnims();
drawCurrentInventoryItem();
@ -482,9 +490,7 @@ void Game::playCutscene(int id) {
_cut._id = id;
}
if (_cut._id != 0xFFFF) {
if (_stub->hasWidescreen()) {
_stub->enableWidescreen(false);
}
ToggleWidescreenStack tws(_stub, false);
_mix.stopMusic();
if (_res._hasSeqData) {
int num = 0;
@ -533,22 +539,31 @@ void Game::playCutscene(int id) {
} else {
_cut._id = 0xFFFF;
}
_mix.stopMusic();
return;
}
}
}
if (_cut._id != 0x4A) {
_mix.playMusic(Cutscene::_musicTable[_cut._id]);
if (_res.isAmiga()) {
const int num = Cutscene::_musicTableAmiga[_cut._id * 2];
if (num != 0xFF) {
const int bpm = Cutscene::_musicTableAmiga[_cut._id * 2 + 1];
_mix.playMusic(num, bpm);
}
} else {
const int num = Cutscene::_musicTableDOS[_cut._id];
if (num != 0xFF) {
_mix.playMusic(num);
}
}
_cut.play();
if (id == 0xD && !_cut._interrupted) {
const bool extendedIntroduction = (_res._type == kResourceTypeDOS || _res._type == kResourceTypeMac);
if (extendedIntroduction) {
_cut._id = 0x4A;
if (!_res.isAmiga()) {
_cut._id = 0x4A; // second part of the introduction cutscene
_cut.play();
}
}
if (_res._type == kResourceTypeMac && !(id == 0x48 || id == 0x49)) { // continue or score screens
if (_res.isMac() && !(id == 0x48 || id == 0x49)) { // continue or score screens
// restore palette entries modified by the cutscene player (0xC and 0xD)
Color palette[32];
_res.MAC_copyClut16(palette, 0, 0x37);
@ -557,13 +572,11 @@ void Game::playCutscene(int id) {
_stub->setPaletteEntry(0xC0 + i, &palette[i]);
}
}
if (id == 0x3D) {
if (_cut._id == 0x3D) {
_mix.playMusic(Mixer::MUSIC_TRACK + 9);
_cut.playCredits();
}
_mix.stopMusic();
if (_stub->hasWidescreen()) {
_stub->enableWidescreen(true);
}
}
}
@ -639,10 +652,10 @@ void Game::showFinalScore() {
}
bool Game::handleConfigPanel() {
const int x = 7;
const int y = 10;
const int w = 17;
const int h = 12;
static const int x = 7;
static const int y = 10;
static const int w = 17;
static const int h = 12;
_vid._charShadowColor = 0xE2;
_vid._charFrontColor = 0xEE;
@ -1027,7 +1040,7 @@ void Game::drawString(const uint8_t *p, int x, int y, uint8_t color, bool hcente
}
void Game::prepareAnims() {
if (!(_currentRoom & 0x80) && _currentRoom < 0x40) {
if (_currentRoom < 0x40) {
int8_t pge_room;
LivePGE *pge = _pge_liveTable1[_currentRoom];
while (pge) {
@ -1371,11 +1384,11 @@ void Game::drawObjectFrame(const uint8_t *bankDataPtr, const uint8_t *dataPtr, i
void Game::drawCharacter(const uint8_t *dataPtr, int16_t pos_x, int16_t pos_y, uint8_t a, uint8_t b, uint8_t flags) {
debug(DBG_GAME, "Game::drawCharacter(%p, %d, %d, 0x%X, 0x%X, 0x%X)", dataPtr, pos_x, pos_y, a, b, flags);
bool var16 = false; // sprite_mirror_y
bool sprite_mirror_y = false;
if (b & 0x40) {
b &= 0xBF;
b &= ~0x40;
SWAP(a, b);
var16 = true;
sprite_mirror_y = true;
}
uint16_t sprite_h = a;
uint16_t sprite_w = b;
@ -1391,7 +1404,7 @@ void Game::drawCharacter(const uint8_t *dataPtr, int16_t pos_x, int16_t pos_y, u
sprite_clipped_w = 256 - pos_x;
if (flags & 2) {
var14 = true;
if (var16) {
if (sprite_mirror_y) {
src += (sprite_w - 1) * sprite_h;
} else {
src += sprite_w - 1;
@ -1401,7 +1414,7 @@ void Game::drawCharacter(const uint8_t *dataPtr, int16_t pos_x, int16_t pos_y, u
} else {
sprite_clipped_w = pos_x + sprite_w;
if (!(flags & 2)) {
if (var16) {
if (sprite_mirror_y) {
src -= sprite_h * pos_x;
pos_x = 0;
} else {
@ -1410,7 +1423,7 @@ void Game::drawCharacter(const uint8_t *dataPtr, int16_t pos_x, int16_t pos_y, u
}
} else {
var14 = true;
if (var16) {
if (sprite_mirror_y) {
src += sprite_h * (pos_x + sprite_w - 1);
pos_x = 0;
} else {
@ -1433,7 +1446,7 @@ void Game::drawCharacter(const uint8_t *dataPtr, int16_t pos_x, int16_t pos_y, u
}
} else {
sprite_clipped_h = sprite_h + pos_y;
if (var16) {
if (sprite_mirror_y) {
src -= pos_y;
} else {
src -= sprite_w * pos_y;
@ -1445,7 +1458,7 @@ void Game::drawCharacter(const uint8_t *dataPtr, int16_t pos_x, int16_t pos_y, u
}
if (!var14 && (flags & 2)) {
if (var16) {
if (sprite_mirror_y) {
src += sprite_h * (sprite_w - 1);
} else {
src += sprite_w - 1;
@ -1458,13 +1471,13 @@ void Game::drawCharacter(const uint8_t *dataPtr, int16_t pos_x, int16_t pos_y, u
debug(DBG_GAME, "dst_offset=0x%X src_offset=%ld", dst_offset, src - dataPtr);
if (!(flags & 2)) {
if (var16) {
if (sprite_mirror_y) {
_vid.drawSpriteSub5(src, _vid._frontLayer + dst_offset, sprite_h, sprite_clipped_h, sprite_clipped_w, sprite_col_mask);
} else {
_vid.drawSpriteSub3(src, _vid._frontLayer + dst_offset, sprite_w, sprite_clipped_h, sprite_clipped_w, sprite_col_mask);
}
} else {
if (var16) {
if (sprite_mirror_y) {
_vid.drawSpriteSub6(src, _vid._frontLayer + dst_offset, sprite_h, sprite_clipped_h, sprite_clipped_w, sprite_col_mask);
} else {
_vid.drawSpriteSub4(src, _vid._frontLayer + dst_offset, sprite_w, sprite_clipped_h, sprite_clipped_w, sprite_col_mask);
@ -1535,11 +1548,15 @@ bool Game::hasLevelMap(int level, int room) const {
if (_res._map) {
return READ_LE_UINT32(_res._map + room * 6) != 0;
} else if (_res._lev) {
return READ_BE_UINT32(_res._lev + room * 4) != 0;
return READ_BE_UINT32(_res._lev + room * 4) > 0x100;
}
return false;
}
static bool isMetro(int level, int room) {
return level == 1 && (room == 0 || room == 13 || room == 38 || room == 51);
}
void Game::loadLevelMap() {
debug(DBG_GAME, "Game::loadLevelMap() room=%d", _currentRoom);
bool widescreenUpdated = false;
@ -1574,14 +1591,14 @@ void Game::loadLevelMap() {
case kResourceTypeDOS:
if (_stub->hasWidescreen() && _widescreenMode == kWidescreenAdjacentRooms) {
const int leftRoom = _res._ctData[CT_LEFT_ROOM + _currentRoom];
if (leftRoom > 0 && hasLevelMap(_currentLevel, leftRoom)) {
if (leftRoom >= 0 && hasLevelMap(_currentLevel, leftRoom) && !isMetro(_currentLevel, leftRoom)) {
_vid.PC_decodeMap(_currentLevel, leftRoom);
_stub->copyWidescreenLeft(Video::GAMESCREEN_W, Video::GAMESCREEN_H, _vid._backLayer);
} else {
_stub->copyWidescreenLeft(Video::GAMESCREEN_W, Video::GAMESCREEN_H, 0);
}
const int rightRoom = _res._ctData[CT_RIGHT_ROOM + _currentRoom];
if (rightRoom > 0 && hasLevelMap(_currentLevel, rightRoom)) {
if (rightRoom >= 0 && hasLevelMap(_currentLevel, rightRoom) && !isMetro(_currentLevel, rightRoom)) {
_vid.PC_decodeMap(_currentLevel, rightRoom);
_stub->copyWidescreenRight(Video::GAMESCREEN_W, Video::GAMESCREEN_H, _vid._backLayer);
} else {
@ -1594,14 +1611,14 @@ void Game::loadLevelMap() {
case kResourceTypeMac:
if (_stub->hasWidescreen() && _widescreenMode == kWidescreenAdjacentRooms) {
const int leftRoom = _res._ctData[CT_LEFT_ROOM + _currentRoom];
if (leftRoom > 0 && hasLevelMap(_currentLevel, leftRoom)) {
if (leftRoom >= 0 && hasLevelMap(_currentLevel, leftRoom)) {
_vid.MAC_decodeMap(_currentLevel, leftRoom);
_stub->copyWidescreenLeft(_vid._w, _vid._h, _vid._backLayer);
} else {
_stub->copyWidescreenLeft(_vid._w, _vid._h, 0);
}
const int rightRoom = _res._ctData[CT_RIGHT_ROOM + _currentRoom];
if (rightRoom > 0 && hasLevelMap(_currentLevel, rightRoom)) {
if (rightRoom >= 0 && hasLevelMap(_currentLevel, rightRoom)) {
_vid.MAC_decodeMap(_currentLevel, rightRoom);
_stub->copyWidescreenRight(_vid._w, _vid._h, _vid._backLayer);
} else {
@ -1678,7 +1695,7 @@ void Game::loadLevelData() {
_res.load(lvl->name, Resource::OT_CT);
_res.load(lvl->name, Resource::OT_PAL);
_res.load(lvl->name, Resource::OT_RP);
if (_res._isDemo || g_options.use_tile_data) { // use .BNQ/.LEV/(.SGD) instead of .MAP (PC demo)
if (_res._isDemo || g_options.use_tile_data || _res._aba) { // use .BNQ/.LEV/(.SGD) instead of .MAP (PC demo)
if (_currentLevel == 0) {
_res.load(lvl->name, Resource::OT_SGD);
}
@ -1697,6 +1714,31 @@ void Game::loadLevelData() {
_res.MAC_loadLevelData(_currentLevel);
break;
}
if (0) {
for (int i = 0; i < 64; ++i) {
if (hasLevelMap(_currentLevel, i)) {
_currentRoom = i;
loadLevelMap();
uint8_t palette[256 * 3];
_stub->getPalette(palette, 256);
char name[64];
snprintf(name, sizeof(name), "DUMP/level%d_room%02d.bmp", _currentLevel, i);
saveBMP(name, _vid._backLayer, palette, _vid._w, _vid._h);
memcpy(_vid._frontLayer, _vid._backLayer, _vid._layerSize);
for (int y = 0; y < 7; ++y) {
for (int x = 0; x < 16; ++x) {
const uint8_t num = _res._ctData[0x100 + _currentRoom * 0x70 + y * 16 + x];
char buf[16];
snprintf(buf, sizeof(buf), "%d", num);
_vid.drawString(buf, x * 16, y * 36 + 8, 0xE7);
}
}
snprintf(name, sizeof(name), "DUMP/level%d_room%02d_grid.bmp", _currentLevel, i);
saveBMP(name, _vid._frontLayer, palette, _vid._w, _vid._h);
}
}
}
_cut._id = lvl->cutscene_id;
@ -1742,6 +1784,22 @@ void Game::loadLevelData() {
_validSaveState = false;
_mix.playMusic(Mixer::MUSIC_TRACK + lvl->track);
if (_widescreenMode == kWidescreenCDi) {
char name[16];
snprintf(name, sizeof(name), "flashp%d.bob", _currentLevel + 1);
File f;
if (f.open(name, "rb", _fs)) {
const int w = f.readUint16LE();
const int h = f.readUint16LE();
if (w != kWidescreenBorderCDiW || h != kWidescreenBorderCDiH) {
warning("Unsupported CDi border w:%d h:%d", w, h);
} else {
f.read(_res._scratchBuffer, 256 * 3 + w * h);
_stub->copyWidescreenCDi(w, h, _res._scratchBuffer + 256 * 3, _res._scratchBuffer);
}
}
}
}
void Game::drawIcon(uint8_t iconNum, int16_t x, int16_t y, uint8_t colMask) {
@ -1910,36 +1968,63 @@ void Game::handleInventory() {
}
icon_x_pos += 32;
}
if (current_line != 0) {
drawIcon(78, 120, 176, 0xA); // down arrow
}
if (current_line != num_lines - 1) {
if (current_line != (g_options.order_inventory_original ? 0 : (num_lines - 1))) {
drawIcon(77, 120, 143, 0xA); // up arrow
}
if (current_line != (g_options.order_inventory_original ? (num_lines - 1) : 0)) {
drawIcon(78, 120, 176, 0xA); // down arrow
}
} else {
char buf[50];
snprintf(buf, sizeof(buf), "SCORE %08u", _score);
_vid.drawString(buf, (114 - strlen(buf) * Video::CHAR_W) / 2 + 72, 158, 0xE5);
snprintf(buf, sizeof(buf), "%s:%s", _res.getMenuString(LocaleData::LI_06_LEVEL), _res.getMenuString(LocaleData::LI_13_EASY + _skillLevel));
_vid.drawString(buf, (114 - strlen(buf) * Video::CHAR_W) / 2 + 72, 166, 0xE5);
if (0) { // if the protection screen code was not properly cracked...
static const uint8_t kCrackerText[17] = {
0x19, 0x08, 0x1B, 0x19, 0x11, 0x1F, 0x08, 0x67, 0x18,
0x16, 0x1B, 0x13, 0x08, 0x1F, 0x1B, 0x0F, 0x5A
};
for (int i = 0; i < 17; ++i) {
buf[i] = kCrackerText[i] ^ 0x5A;
}
_vid.drawString(buf, 65, 193, 0xE4);
}
}
_vid.updateScreen();
_stub->sleep(80);
inp_update();
if (_stub->_pi.dirMask & PlayerInput::DIR_UP) {
_stub->_pi.dirMask &= ~PlayerInput::DIR_UP;
if (current_line < num_lines - 1) {
++current_line;
current_item = current_line * 4;
if (g_options.order_inventory_original) {
if (_stub->_pi.dirMask & PlayerInput::DIR_DOWN) {
_stub->_pi.dirMask &= ~PlayerInput::DIR_DOWN;
if (current_line < num_lines - 1) {
++current_line;
current_item = current_line * 4;
}
}
}
if (_stub->_pi.dirMask & PlayerInput::DIR_DOWN) {
_stub->_pi.dirMask &= ~PlayerInput::DIR_DOWN;
if (current_line > 0) {
--current_line;
current_item = current_line * 4;
if (_stub->_pi.dirMask & PlayerInput::DIR_UP) {
_stub->_pi.dirMask &= ~PlayerInput::DIR_UP;
if (current_line > 0) {
--current_line;
current_item = current_line * 4;
}
}
} else {
if (_stub->_pi.dirMask & PlayerInput::DIR_UP) {
_stub->_pi.dirMask &= ~PlayerInput::DIR_UP;
if (current_line < num_lines - 1) {
++current_line;
current_item = current_line * 4;
}
}
if (_stub->_pi.dirMask & PlayerInput::DIR_DOWN) {
_stub->_pi.dirMask &= ~PlayerInput::DIR_DOWN;
if (current_line > 0) {
--current_line;
current_item = current_line * 4;
}
}
}
if (_stub->_pi.dirMask & PlayerInput::DIR_LEFT) {
@ -1990,6 +2075,9 @@ void Game::makeGameStateName(uint8_t slot, char *buf) {
sprintf(buf, "rs-level%d-%02d.state", _currentLevel + 1, slot);
}
// 3: persist _pge_opGunVar
static const int kSaveVersion = 3;
static const uint32_t TAG_FBSV = 0x46425356;
bool Game::saveGameState(uint8_t slot) {
@ -2005,7 +2093,7 @@ bool Game::saveGameState(uint8_t slot) {
} else {
// header
f.writeUint32BE(TAG_FBSV);
f.writeUint16BE(2);
f.writeUint16BE(kSaveVersion);
char buf[32];
memset(buf, 0, sizeof(buf));
snprintf(buf, sizeof(buf), "level=%d room=%d", _currentLevel + 1, _currentRoom);
@ -2037,15 +2125,15 @@ bool Game::loadGameState(uint8_t slot) {
if (id != TAG_FBSV) {
warning("Bad save state format");
} else {
uint16_t ver = f.readUint16BE();
if (ver != 2) {
const uint16_t version = f.readUint16BE();
if (version < 2) {
warning("Invalid save state version");
} else {
// header
char buf[32];
f.read(buf, sizeof(buf));
// contents
loadState(&f);
loadState(&f, version);
if (f.ioErr()) {
warning("I/O error when loading game state");
} else {
@ -2083,7 +2171,7 @@ void Game::saveState(File *f) {
f->writeByte(pge->collision_slot);
f->writeByte(pge->next_inventory_PGE);
f->writeByte(pge->current_inventory_PGE);
f->writeByte(pge->unkF);
f->writeByte(pge->ref_inventory_PGE);
f->writeUint16BE(pge->anim_number);
f->writeByte(pge->flags);
f->writeByte(pge->index);
@ -2114,9 +2202,10 @@ void Game::saveState(File *f) {
f->writeByte(cs2->data_size);
f->write(cs2->data_buf, 0x10);
}
f->writeUint16BE(_pge_opGunVar);
}
void Game::loadState(File *f) {
void Game::loadState(File *f, int version) {
uint16_t i;
uint32_t off;
_skillLevel = f->readByte();
@ -2147,7 +2236,7 @@ void Game::loadState(File *f) {
pge->collision_slot = f->readByte();
pge->next_inventory_PGE = f->readByte();
pge->current_inventory_PGE = f->readByte();
pge->unkF = f->readByte();
pge->ref_inventory_PGE = f->readByte();
pge->anim_number = f->readUint16BE();
pge->flags = f->readByte();
pge->index = f->readByte();
@ -2193,6 +2282,9 @@ void Game::loadState(File *f) {
}
}
resetGameState();
if (version >= 3) {
_pge_opGunVar = f->readUint16BE();
}
}
void Game::clearStateRewind() {
@ -2234,7 +2326,7 @@ bool Game::loadStateRewind() {
}
File &f = _rewindBuffer[ptr];
f.seek(0);
loadState(&f);
loadState(&f, kSaveVersion);
if (_rewindLen > 0) {
--_rewindLen;
}

30
game.h
View File

@ -19,6 +19,12 @@ struct File;
struct FileSystem;
struct SystemStub;
enum {
kCheatOneHitKill = 1 << 0,
kCheatNoHit = 1 << 1,
kCheatLifeCounter = 1 << 2
};
struct Game {
typedef int (Game::*pge_OpcodeProc)(ObjectOpcodeArgs *args);
typedef int (Game::*pge_ZOrderCallback)(LivePGE *, LivePGE *, uint8_t, uint8_t);
@ -71,6 +77,7 @@ struct Game {
const char *_savePath;
File _rewindBuffer[kRewindSize];
int _rewindPtr, _rewindLen;
uint32_t _cheats;
const uint8_t *_stringsTable;
const char **_textsTable;
@ -102,7 +109,7 @@ struct Game {
bool _autoSave;
uint32_t _saveTimestamp;
Game(SystemStub *, FileSystem *, const char *savePath, int level, ResourceType ver, Language lang, WidescreenMode widescreenMode, bool autoSave);
Game(SystemStub *, FileSystem *, const char *savePath, int level, ResourceType ver, Language lang, WidescreenMode widescreenMode, bool autoSave, int midiDriver, uint32_t cheats);
void run();
void displayTitleScreenAmiga();
@ -155,10 +162,12 @@ struct Game {
bool _pge_currentPiegeFacingDir; // (false == left)
bool _pge_processOBJ;
uint8_t _pge_inpKeysMask;
uint16_t _pge_opTempVar1;
uint16_t _pge_opTempVar2;
uint16_t _pge_opGunVar;
uint16_t _pge_compareVar1;
uint16_t _pge_compareVar2;
uint8_t _pge_zoomPiegeNum;
uint8_t _pge_zoomCounter;
int _pge_zoomX, _pge_zoomY;
void pge_resetMessages();
void pge_clearMessages(uint8_t pge_index);
@ -248,7 +257,7 @@ struct Game {
int pge_o_unk0x47(ObjectOpcodeArgs *args);
int pge_o_unk0x48(ObjectOpcodeArgs *args);
int pge_o_unk0x49(ObjectOpcodeArgs *args);
int pge_o_unk0x4A(ObjectOpcodeArgs *args);
int pge_op_killInventoryPiege(ObjectOpcodeArgs *args);
int pge_op_killPiege(ObjectOpcodeArgs *args);
int pge_op_isInCurrentRoom(ObjectOpcodeArgs *args);
int pge_op_isNotInCurrentRoom(ObjectOpcodeArgs *args);
@ -264,7 +273,7 @@ struct Game {
int pge_op_setLifeCounter(ObjectOpcodeArgs *args);
int pge_op_decLifeCounter(ObjectOpcodeArgs *args);
int pge_op_playCutscene(ObjectOpcodeArgs *args);
int pge_op_isTempVar2Set(ObjectOpcodeArgs *args);
int pge_op_compareUnkVar(ObjectOpcodeArgs *args);
int pge_op_playDeathCutscene(ObjectOpcodeArgs *args);
int pge_o_unk0x5D(ObjectOpcodeArgs *args);
int pge_o_unk0x5E(ObjectOpcodeArgs *args);
@ -310,14 +319,14 @@ struct Game {
int pge_o_unk0x86(ObjectOpcodeArgs *args);
int pge_op_playSoundGroup(ObjectOpcodeArgs *args);
int pge_op_adjustPos(ObjectOpcodeArgs *args);
int pge_op_setTempVar1(ObjectOpcodeArgs *args);
int pge_op_isTempVar1Set(ObjectOpcodeArgs *args);
int pge_op_setGunVar(ObjectOpcodeArgs *args);
int pge_op_compareGunVar(ObjectOpcodeArgs *args);
int pge_setCurrentInventoryObject(LivePGE *pge);
void pge_updateInventory(LivePGE *pge1, LivePGE *pge2);
void pge_reorderInventory(LivePGE *pge);
LivePGE *pge_getInventoryItemBefore(LivePGE *pge, LivePGE *last_pge);
LivePGE *pge_getPreviousInventoryItem(LivePGE *pge, LivePGE *last_pge);
void pge_addToInventory(LivePGE *pge1, LivePGE *pge2, LivePGE *pge3);
int pge_updateCollisionState(LivePGE *pge, int16_t pge_dy, uint8_t var8);
int pge_updateCollisionState(LivePGE *pge, int16_t pge_dy, uint8_t value);
int pge_ZOrder(LivePGE *pge, int16_t num, pge_ZOrderCallback compare, uint16_t unk);
void pge_sendMessage(uint8_t src_pge_index, uint8_t dst_pge_index, int16_t num);
void pge_removeFromInventory(LivePGE *pge1, LivePGE *pge2, LivePGE *pge3);
@ -331,6 +340,7 @@ struct Game {
int pge_ZOrderIfTypeAndSameDirection(LivePGE *pge1, LivePGE *pge2, uint8_t comp, uint8_t comp2);
int pge_ZOrderIfTypeAndDifferentDirection(LivePGE *pge1, LivePGE *pge2, uint8_t comp, uint8_t comp2);
int pge_ZOrderByNumber(LivePGE *pge1, LivePGE *pge2, uint8_t comp, uint8_t comp2);
void pge_updateZoom();
// collision
@ -386,7 +396,7 @@ struct Game {
bool saveGameState(uint8_t slot);
bool loadGameState(uint8_t slot);
void saveState(File *f);
void loadState(File *f);
void loadState(File *f, int version);
void clearStateRewind();
bool saveStateRewind();
bool loadStateRewind();

View File

@ -52,8 +52,7 @@ inline int16_t S8_to_S16(int a) {
} else if (a > 127) {
return 32767;
} else {
const uint8_t u8 = (a ^ 0x80);
return ((u8 << 8) | u8) - 32768;
return ((uint8_t)a) * 257;
}
}
@ -118,6 +117,12 @@ enum WidescreenMode {
kWidescreenAdjacentRooms,
kWidescreenMirrorRoom,
kWidescreenBlur,
kWidescreenCDi,
};
enum {
kWidescreenBorderCDiW = 52,
kWidescreenBorderCDiH = 224
};
struct Options {
@ -130,6 +135,7 @@ struct Options {
bool use_seq_cutscenes;
bool use_words_protection;
bool use_white_tshirt;
bool use_prf_music;
bool play_asc_cutscene;
bool play_caillou_cutscene;
bool play_metro_cutscene;
@ -137,6 +143,7 @@ struct Options {
bool play_carte_cutscene;
bool play_gamesaved_sound;
bool restore_memo_cutscene;
bool order_inventory_original;
};
struct Color {
@ -172,8 +179,8 @@ struct InitPGE {
int16_t pos_y;
uint16_t obj_node_number;
uint16_t life;
int16_t counter_values[4]; // data
uint8_t object_type;
int16_t data[4];
uint8_t object_type; // 1:conrad, 10:monster
uint8_t init_room;
uint8_t room_location;
uint8_t init_flags;
@ -183,7 +190,7 @@ struct InitPGE {
uint8_t skill;
uint8_t mirror_x;
uint8_t flags; // 1:xflip 4:active
uint8_t unk1C; // collidable, collision_data_len
uint8_t collision_data_len;
uint16_t text_num;
};
@ -194,11 +201,11 @@ struct LivePGE {
uint8_t anim_seq;
uint8_t room_location;
int16_t life;
int16_t counter_value; // msg
int16_t counter_value;
uint8_t collision_slot;
uint8_t next_inventory_PGE;
uint8_t current_inventory_PGE;
uint8_t unkF; // unk_inventory_PGE
uint8_t ref_inventory_PGE;
uint16_t anim_number;
uint8_t flags;
uint8_t index;
@ -209,7 +216,7 @@ struct LivePGE {
struct MessagePGE {
MessagePGE *next_entry;
uint16_t index; // src_pge
uint16_t src_pge;
uint16_t msg_num;
};
@ -229,9 +236,8 @@ struct Object {
};
struct ObjectNode {
uint16_t last_obj_number;
Object *objects;
uint16_t num_objects;
Object *objects;
};
struct ObjectOpcodeArgs {
@ -268,9 +274,9 @@ struct BankSlot {
struct CollisionSlot2 {
CollisionSlot2 *next_slot;
int8_t *unk2;
int8_t *unk2; // grid_data_pos
uint8_t data_size;
uint8_t data_buf[0x10]; // XXX check size
uint8_t data_buf[0x10]; // <= InitPGE.collision_data_len
};
struct InventoryItem {

View File

@ -18,14 +18,15 @@
static const char *USAGE =
"REminiscence - Flashback Interpreter\n"
"Usage: %s [OPTIONS]...\n"
" --datapath=PATH Path to data files (default 'DATA')\n"
" --savepath=PATH Path to save files (default '.')\n"
" --datapath=PATH Path to data files (default 'data')\n"
" --savepath=PATH Path to save files (default 'saves')\n"
" --levelnum=NUM Start to level, bypass introduction\n"
" --fullscreen Fullscreen display\n"
" --widescreen=MODE 16:9 display (adjacent,mirror,blur,none)\n"
" --scaler=NAME@X Graphics scaler (default 'scale@3')\n"
" --language=LANG Language (fr,en,de,sp,it,jp)\n"
" --autosave Save game state automatically\n"
" --mididriver=MIDI Driver (adlib, mt32)\n"
;
static int detectVersion(FileSystem *fs) {
@ -35,6 +36,7 @@ static int detectVersion(FileSystem *fs) {
const char *name;
} table[] = {
{ "DEMO_UK.ABA", kResourceTypeDOS, "DOS (Demo)" },
{ "GLOB_FR.ABA", kResourceTypeDOS, "DOS" },
{ "INTRO.SEQ", kResourceTypeDOS, "DOS CD" },
{ "MENU1SSI.MAP", kResourceTypeDOS, "DOS SSI" },
{ "LEVEL1.MAP", kResourceTypeDOS, "DOS" },
@ -60,7 +62,8 @@ static Language detectLanguage(FileSystem *fs) {
const char *filename;
Language language;
} table[] = {
// PC
// DOS
{ "GLOB_FR.ABA", LANG_FR },
{ "ENGCINE.TXT", LANG_EN },
{ "FR_CINE.TXT", LANG_FR },
{ "GERCINE.TXT", LANG_DE },
@ -93,6 +96,7 @@ static void initOptions() {
g_options.use_seq_cutscenes = true;
g_options.use_words_protection = false;
g_options.use_white_tshirt = false;
g_options.use_prf_music = true;
g_options.play_asc_cutscene = false;
g_options.play_caillou_cutscene = false;
g_options.play_metro_cutscene = false;
@ -100,6 +104,7 @@ static void initOptions() {
g_options.play_carte_cutscene = false;
g_options.play_gamesaved_sound = false;
g_options.restore_memo_cutscene = true;
g_options.order_inventory_original = false;
// read configuration file
struct {
const char *name;
@ -114,6 +119,7 @@ static void initOptions() {
{ "use_seq_cutscenes", &g_options.use_seq_cutscenes },
{ "use_words_protection", &g_options.use_words_protection },
{ "use_white_tshirt", &g_options.use_white_tshirt },
{ "use_prf_music", &g_options.use_prf_music },
{ "play_asc_cutscene", &g_options.play_asc_cutscene },
{ "play_caillou_cutscene", &g_options.play_caillou_cutscene },
{ "play_metro_cutscene", &g_options.play_metro_cutscene },
@ -121,6 +127,7 @@ static void initOptions() {
{ "play_carte_cutscene", &g_options.play_carte_cutscene },
{ "play_gamesaved_sound", &g_options.play_gamesaved_sound },
{ "restore_memo_cutscene", &g_options.restore_memo_cutscene },
{ "order_inventory_original", &g_options.order_inventory_original },
{ 0, 0 }
};
static const char *filename = "rs.cfg";
@ -175,6 +182,7 @@ static WidescreenMode parseWidescreen(const char *mode) {
{ "adjacent", kWidescreenAdjacentRooms },
{ "mirror", kWidescreenMirrorRoom },
{ "blur", kWidescreenBlur },
{ "cdi", kWidescreenCDi },
{ 0, kWidescreenNone },
};
for (int i = 0; modes[i].name; ++i) {
@ -187,14 +195,16 @@ static WidescreenMode parseWidescreen(const char *mode) {
}
int main(int argc, char *argv[]) {
const char *dataPath = "DATA";
const char *savePath = ".";
const char *dataPath = "data";
const char *savePath = "saves/";
int levelNum = 0;
bool fullscreen = false;
bool autoSave = false;
uint32_t cheats = 0;
WidescreenMode widescreen = kWidescreenNone;
ScalerParameters scalerParameters = ScalerParameters::defaults();
int forcedLanguage = -1;
int midiDriver = MODE_ADLIB;
if (argc == 2) {
// data path as the only command line argument
struct stat st;
@ -212,6 +222,8 @@ int main(int argc, char *argv[]) {
{ "language", required_argument, 0, 6 },
{ "widescreen", required_argument, 0, 7 },
{ "autosave", no_argument, 0, 8 },
{ "cheats", required_argument, 0, 9 },
{ "mididriver", required_argument, 0, 10 },
{ 0, 0, 0, 0 }
};
int index;
@ -248,12 +260,16 @@ int main(int argc, char *argv[]) {
{ LANG_JP, "JP" },
{ -1, 0 }
};
for (int i = 0; languages[i].str; ++i) {
int i = 0;
for (; languages[i].str; ++i) {
if (strcasecmp(languages[i].str, optarg) == 0) {
forcedLanguage = languages[i].lang;
break;
}
}
if (!languages[i].str) {
warning("Invalid language '%s'", optarg);
}
}
break;
case 7:
@ -262,6 +278,30 @@ int main(int argc, char *argv[]) {
case 8:
autoSave = true;
break;
case 9:
cheats = atoi(optarg);
break;
case 10: {
static const struct {
int mode;
const char *str;
} drivers[] = {
{ MODE_ADLIB, "adlib" },
{ MODE_MT32, "mt32" },
{ -1, 0 }
};
int i = 0;
for (; drivers[i].str; ++i) {
if (strcasecmp(drivers[i].str, optarg) == 0) {
midiDriver = drivers[i].mode;
break;
}
}
if (!drivers[i].str) {
warning("Invalid MIDI driver '%s'", optarg);
}
}
break;
default:
printf(USAGE, argv[0]);
return 0;
@ -277,7 +317,7 @@ int main(int argc, char *argv[]) {
}
const Language language = (forcedLanguage == -1) ? detectLanguage(&fs) : (Language)forcedLanguage;
SystemStub *stub = SystemStub_SDL_create();
Game *g = new Game(stub, &fs, savePath, levelNum, (ResourceType)version, language, widescreen, autoSave);
Game *g = new Game(stub, &fs, savePath, levelNum, (ResourceType)version, language, widescreen, autoSave, midiDriver, cheats);
stub->init(g_caption, g->_vid._w, g->_vid._h, fullscreen, widescreen, &scalerParameters);
g->run();
delete g;

View File

@ -259,9 +259,9 @@ bool Menu::handleLevelScreen() {
}
_vid->markBlockAsDirty(4 * Video::CHAR_W, 7 * Video::CHAR_H, 192, 7 * Video::CHAR_H, _vid->_layerScale);
drawString(_res->getMenuString(LocaleData::LI_13_EASY), 23, 4, (currentSkill == 0) ? 2 : 3);
drawString(_res->getMenuString(LocaleData::LI_14_NORMAL), 23, 14, (currentSkill == 1) ? 2 : 3);
drawString(_res->getMenuString(LocaleData::LI_15_EXPERT), 23, 24, (currentSkill == 2) ? 2 : 3);
drawString(_res->getMenuString(LocaleData::LI_13_EASY), 23, 4, (currentSkill == 0) ? 2 : 3);
drawString(_res->getMenuString(LocaleData::LI_14_NORMAL), 23, 14, (currentSkill == 1) ? 2 : 3);
drawString(_res->getMenuString(LocaleData::LI_15_EXPERT), 23, 24, (currentSkill == 2) ? 2 : 3);
_vid->markBlockAsDirty(4 * Video::CHAR_W, 23 * Video::CHAR_H, 192, Video::CHAR_H, _vid->_layerScale);
_vid->updateScreen();
@ -361,10 +361,8 @@ void Menu::handleTitleScreen() {
++menuItemsCount;
_selectedOption = -1;
_currentScreen = -1;
_nextScreen = SCREEN_TITLE;
bool quitLoop = false;
int currentEntry = 0;
static const struct {
@ -386,7 +384,7 @@ void Menu::handleTitleScreen() {
}
}
while (!quitLoop && !_stub->_pi.quit) {
while (!_stub->_pi.quit) {
int selectedItem = -1;
int previousLanguage = currentLanguage;
@ -398,7 +396,6 @@ void Menu::handleTitleScreen() {
_charVar3 = 1;
_charVar4 = 2;
currentEntry = 0;
_currentScreen = _nextScreen;
_nextScreen = -1;
}
@ -444,30 +441,27 @@ void Menu::handleTitleScreen() {
_selectedOption = menuItems[selectedItem].opt;
switch (_selectedOption) {
case MENU_OPTION_ITEM_START:
quitLoop = true;
break;
return;
case MENU_OPTION_ITEM_SKILL:
_currentScreen = SCREEN_SKILL;
handleSkillScreen();
break;
case MENU_OPTION_ITEM_PASSWORD:
_currentScreen = SCREEN_PASSWORD;
quitLoop = handlePasswordScreen();
if (handlePasswordScreen()) {
return;
}
break;
case MENU_OPTION_ITEM_LEVEL:
_currentScreen = SCREEN_LEVEL;
quitLoop = handleLevelScreen();
if (handleLevelScreen()) {
return;
}
break;
case MENU_OPTION_ITEM_INFO:
_currentScreen = SCREEN_INFO;
handleInfoScreen();
break;
case MENU_OPTION_ITEM_DEMO:
quitLoop = true;
break;
return;
case MENU_OPTION_ITEM_QUIT:
quitLoop = true;
break;
return;
}
_nextScreen = SCREEN_TITLE;
continue;

41
midi_driver.h Normal file
View File

@ -0,0 +1,41 @@
#ifndef MIDI_DRIVER_H__
#define MIDI_DRIVER_H__
struct MidiDriver {
virtual ~MidiDriver() {};
virtual int init() = 0;
virtual void fini() = 0;
virtual void reset(int rate) = 0;
virtual void setInstrumentData(int channel, int num, const void *data) { // fmopl only
}
virtual void noteOff(int channel, int note, int velocity) = 0;
virtual void noteOn(int channel, int note, int velocity) = 0;
virtual void controlChange(int channel, int type, int value) = 0;
virtual void programChange(int channel, int num) = 0;
virtual void pitchBend(int channel, int lsb, int msb) = 0;
virtual void readSamples(short *samples, int len) = 0;
};
struct MidiDriverInfo {
const char *name;
MidiDriver *(*create)();
};
#ifndef MIDI_DRIVER_SYMBOL
#ifdef _WIN32
#define MIDI_DRIVER_SYMBOL __declspec(dllexport)
#else
#define MIDI_DRIVER_SYMBOL
#endif
#endif
MIDI_DRIVER_SYMBOL extern const MidiDriverInfo midi_driver_adlib;
MIDI_DRIVER_SYMBOL extern const MidiDriverInfo midi_driver_mt32;
#endif /* MIDI_DRIVER_H__ */

369
midi_driver_adlib.cpp Normal file
View File

@ -0,0 +1,369 @@
#include <assert.h>
#include <stdio.h>
#include <stdint.h>
#include "midi_driver.h"
extern "C" {
#include "opl3.c"
}
#define NUM_CHANNELS 11 /* rhythm mode */
static opl3_chip _opl3;
static const int kMusicVolume = 63; // 44;
/* Two-operator melodic and percussion mode */
static const uint8_t _adlibOperatorIndexTable[] = { 0, 3, 1, 4, 2, 5, 6, 9, 7, 0xA, 8, 0xB, 0xC, 0xF, 0x10, 0x10, 0xE, 0xE, 0x11, 0x11, 0xD, 0xD };
static const uint8_t _adlibOperatorTable[] = { 0, 1, 2, 3, 4, 5, 8, 9, 0xA, 0xB, 0xC, 0xD, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15 };
static const uint16_t _adlibFrequencyTable[] = {
0x157, 0x16C, 0x181, 0x198, 0x1B1, 0x1CB, 0x1E6, 0x203, 0x222, 0x243, 0x266, 0x28A
};
static struct {
uint8_t midi_channel; // track_num
uint8_t note;
uint8_t hw_channel;
const uint8_t *instrument;
} _adlibPlayingNoteTable[NUM_CHANNELS];
static int16_t _adlibChannelPlayingTable[NUM_CHANNELS]; // could be a bitmask
static int16_t _adlibChannelVolumeTable[NUM_CHANNELS];
static uint8_t _regBD = 0x20;
static const uint8_t *_currentInstrumentData;
static uint16_t READ_LE_UINT16(const uint8_t *p) {
return p[0] | (p[1] << 8);
}
struct MidiDriver_adlib: MidiDriver {
virtual int init() {
return 0;
}
virtual void fini() {
}
virtual void reset(int rate) {
for (int i = 0; i < NUM_CHANNELS; ++i) {
_adlibPlayingNoteTable[i].midi_channel = 0xFF;
_adlibChannelPlayingTable[i] = 0;
_adlibChannelVolumeTable[i] = 10;
}
OPL3_Reset(&_opl3, rate);
adlibReset();
}
virtual void setInstrumentData(int channel, int num, const void *data) {
// fprintf(stdout, "instrument channel:%d num:%d data:%p\n", channel, num, data);
assert(data);
_currentInstrumentData = (const uint8_t *)data;
}
virtual void noteOff(int channel, int note, int velocity) {
// fprintf(stdout, "noteOff %d channel %d\n", note, channel);
for (int i = 0; i < NUM_CHANNELS; ++i) {
if (_adlibPlayingNoteTable[i].midi_channel != channel) {
continue;
}
if (_adlibPlayingNoteTable[i].note != note) {
//fprintf(stdout, "WARNING: noteOff: channel %d is playing note %d (expected %d)\n", channel, _adlibPlayingNoteTable[i].note, note);
continue;
}
_adlibPlayingNoteTable[i].midi_channel = 0xFF;
const int hw_channel = _adlibPlayingNoteTable[i].hw_channel;
//fprintf(stdout, "free hw_channel %d\n", hw_channel);
_adlibChannelPlayingTable[hw_channel] = 0;
if (_adlibPlayingNoteTable[i].instrument != _currentInstrumentData) {
// fprintf(stdout, "WARNING: noteOff: instrument changed for channel %d hw_channel %d\n", channel, hw_channel);
}
if (_adlibPlayingNoteTable[i].instrument[0] == 0) { //_currentInstrumentData[0] == 0) {
adlibMelodicKeyOff(hw_channel, note);
} else {
adlibPercussionKeyOff(hw_channel);
}
return;
}
// fprintf(stdout, "WARNING: noteOff: channel %d note %d not found\n", channel, note);
}
virtual void noteOn(int channel, int note, int velocity) {
if (_currentInstrumentData[0] == 0) {
/* melodic channel */
int i;
for (i = 0; i < 6; ++i) {
if (_adlibChannelPlayingTable[i] == 0) {
break;
}
}
if (i == 6) {
// fprintf(stdout, "No free melodic channel found (A) 6 voices busy, track %d note %d\n", channel, note);
return;
}
const int hw_channel = i;
_adlibChannelPlayingTable[hw_channel] = 0xFF;
for (i = 0; i < NUM_CHANNELS; ++i) {
if (_adlibPlayingNoteTable[i].midi_channel == 0xFF) {
break;
}
}
if (i == NUM_CHANNELS) {
_adlibChannelPlayingTable[hw_channel] = 0;
// fprintf(stdout, "No free channel found (B)\n");
return;
}
_adlibPlayingNoteTable[i].midi_channel = channel;
_adlibPlayingNoteTable[i].note = note;
_adlibPlayingNoteTable[i].hw_channel = hw_channel;
_adlibPlayingNoteTable[i].instrument = _currentInstrumentData;
_adlibChannelVolumeTable[hw_channel] = velocity;
adlibSetupInstrument(hw_channel, _currentInstrumentData);
adlibMelodicKeyOn(hw_channel, velocity, note);
} else {
/* percussion channel */
const int hw_channel = _currentInstrumentData[1]; // channel
assert(hw_channel >= 6 && hw_channel <= 10);
if (_adlibChannelPlayingTable[hw_channel] != 0) {
// fprintf(stdout, "No free channel found (C) hw_channel %d for track %d note %d\n", hw_channel, channel, note);
return;
}
_adlibChannelPlayingTable[hw_channel] = 0xFF;
int i;
for (i = 6; i < NUM_CHANNELS; ++i) {
if (_adlibPlayingNoteTable[i].midi_channel == 0xFF) {
break;
}
}
if (i == NUM_CHANNELS) {
_adlibChannelPlayingTable[hw_channel] = 0;
// fprintf(stdout, "No free channel found (D) hw_channel %d\n", hw_channel);
return;
}
//fprintf(stdout, "Adding percussion channel %d note %d index %d track %d\n", hw_channel, note, i, channel);
_adlibPlayingNoteTable[i].midi_channel = channel;
_adlibPlayingNoteTable[i].note = note;
_adlibPlayingNoteTable[i].hw_channel = hw_channel;
_adlibPlayingNoteTable[i].instrument = _currentInstrumentData;
_adlibChannelVolumeTable[hw_channel] = velocity;
adlibSetupInstrument(hw_channel, _currentInstrumentData);
adlibPercussionKeyOn(hw_channel, velocity, note);
}
}
virtual void controlChange(int channel, int type, int value) {
}
virtual void programChange(int channel, int num) {
}
virtual void pitchBend(int channel, int lsb, int msb) {
adlibPitchBend(channel, (msb << 7) | lsb);
}
virtual void readSamples(int16_t *samples, int len) {
OPL3_GenerateStream(&_opl3, samples, len);
}
void adlibSetupInstrument(int channel, const uint8_t *arg0) {
const int volume = (_adlibChannelVolumeTable[channel] * kMusicVolume) >> 6;
// fprintf(stdout, "instrument channel:%d volume:%d\n", channel, volume);
const int mode = arg0[0];
const int hw_channel = arg0[1];
const int num = (mode != 0) ? hw_channel : channel;
const uint8_t modulatorOperator = _adlibOperatorTable[_adlibOperatorIndexTable[num * 2]];
const uint8_t carrierOperator = _adlibOperatorTable[_adlibOperatorIndexTable[num * 2 + 1]];
//fprintf(stdout, "operator modulator %d carrier %d\n", modulatorOperator, carrierOperator);
if (mode == 0 || hw_channel == 6) { // else goto loc_F24_284E5
// modulator
const uint8_t *bx = arg0 + 6;
uint16_t reg2x = 0;
if (READ_LE_UINT16(bx + 0x12) != 0) { // amplitude vibrato
reg2x |= 0x80;
}
if (READ_LE_UINT16(bx + 0x14) != 0) { // frequency vibrato
reg2x |= 0x40;
}
if (READ_LE_UINT16(bx + 0x0A) != 0) { // sustaining sound
reg2x |= 0x20;
}
if (READ_LE_UINT16(bx + 0x16) != 0) { // envelope scaling
reg2x |= 0x10;
}
reg2x |= READ_LE_UINT16(bx + 2) & 15; // frequency multiplier
writeRegister(0x20 + modulatorOperator, reg2x);
uint16_t dx;
if (READ_LE_UINT16(bx + 0x18) != 0) { // frequency modulation
dx = READ_LE_UINT16(bx + 0x10) & 63;
} else {
const int ax = (63 - (READ_LE_UINT16(bx + 0x10) & 63)) * volume; // output level
dx = 63 - (((ax + 127) + ax) / 254);
}
const uint8_t reg4x = dx | (READ_LE_UINT16(bx) << 6);
writeRegister(0x40 + modulatorOperator, reg4x);
writeRegister(0x60 + modulatorOperator, (READ_LE_UINT16(bx + 12) & 15) | (READ_LE_UINT16(bx + 6) << 4));
writeRegister(0x80 + modulatorOperator, (READ_LE_UINT16(bx + 14) & 15) | (READ_LE_UINT16(bx + 8) << 4));
uint16_t ax = 1;
if (READ_LE_UINT16(bx + 0x18) != 0) {
ax = 0;
}
dx = (READ_LE_UINT16(bx + 4) << 1) | ax;
ax = 0xC0 + ((mode != 0) ? hw_channel : channel);
writeRegister(ax, dx);
writeRegister(0xE0 + modulatorOperator, arg0[2] & 3); /* original writes to 0xE, typo */
}
// carrier
const uint8_t *bx = arg0 + 0x20; // 26 + 6
uint8_t reg2x = 0;
if (READ_LE_UINT16(bx + 0x12) != 0) {
reg2x |= 0x80;
}
if (READ_LE_UINT16(bx + 0x14) != 0) {
reg2x |= 0x40;
}
if (READ_LE_UINT16(bx + 0x0A) != 0) {
reg2x |= 0x20;
}
if (READ_LE_UINT16(bx + 0x16) != 0) {
reg2x |= 0x10;
}
reg2x |= (READ_LE_UINT16(bx + 2) & 15);
writeRegister(0x20 + carrierOperator, reg2x);
const int ax = (63 - (READ_LE_UINT16(bx + 0x10) & 63)) * volume;
int dx = 63 - (((ax + 127) + ax) / 254);
const uint8_t reg4x = dx | (READ_LE_UINT16(bx) << 6);
writeRegister(0x40 + carrierOperator, reg4x);
writeRegister(0x60 + carrierOperator, (READ_LE_UINT16(bx + 12) & 15) | (READ_LE_UINT16(bx + 6) << 4));
writeRegister(0x80 + carrierOperator, (READ_LE_UINT16(bx + 14) & 15) | (READ_LE_UINT16(bx + 8) << 4));
writeRegister(0xE0 + carrierOperator, arg0[3] & 3); /* original writes to 0xE, typo */
}
void adlibPercussionKeyOn(int channel, int var6, int note) {
// 6: Bass Drum
// 7: Snare Drum
// 8: Tom-Tom
// 9: Cymbal
// 10: Hi-Hat
assert(channel >= 6 && channel <= 10);
_regBD &= ~(1 << (10 - channel));
writeRegister(0xBD, _regBD);
int hw_channel = channel;
if (channel == 9) {
hw_channel = 8;
} else if (channel == 10) {
hw_channel = 7;
}
const uint16_t freq = _adlibFrequencyTable[note % 12];
writeRegister(0xA0 + hw_channel, freq);
const uint8_t regBx = ((note / 12) << 2) | ((freq & 0x300) >> 8);
writeRegister(0xB0 + hw_channel, regBx);
_regBD |= 1 << (10 - channel);
writeRegister(0xBD, _regBD);
}
void adlibMelodicKeyOn(int channel, int volume, int note) {
writeRegister(0xB0 + channel, 0);
const uint16_t freq = _adlibFrequencyTable[note % 12];
writeRegister(0xA0 + channel, freq & 0xFF); /* Frequency (Low) */
const uint8_t octave = (note / 12);
const uint8_t regBx = 0x20 | (octave << 2) | ((freq & 0x300) >> 8); /* Key-On | Octave | Frequency (High) */
writeRegister(0xB0 + channel, regBx);
}
void adlibMelodicKeyOff(int channel, int note) {
const uint16_t freq = _adlibFrequencyTable[note % 12];
const uint8_t octave = (note / 12);
const uint8_t regBx = (octave << 2) | ((freq & 0x300) >> 8); /* Octave | Frequency (High) */
writeRegister(0xB0 + channel, regBx);
}
void adlibPercussionKeyOff(int channel) {
assert(channel >= 6 && channel <= 10);
_regBD &= ~(1 << (10 - channel));
writeRegister(0xBD, _regBD);
}
void adlibPitchBend(int channel, int value) {
//fprintf(stdout, "pitch bend channel:%d value:%d\n", channel, value);
if (value == 0x2000) {
return;
}
if (value & 0x2000) {
value = -(value & 0x1FFF);
}
const int pitchBend = value;
for (int i = 0; i < NUM_CHANNELS; ++i) {
if (_adlibPlayingNoteTable[i].midi_channel != channel) {
continue;
}
const int note = _adlibPlayingNoteTable[i].note;
const int hw_channel = _adlibPlayingNoteTable[i].hw_channel;
static const int kVarC = 0x2000; // / byte_1B73_2BE66;
value = pitchBend / kVarC + note % 12;
int octave = note / 12;
if (value < 0) {
--octave;
value = -value;
} else if (value > 12) {
++octave;
value -= 12;
}
--octave;
const int var14 = ((pitchBend % kVarC) * 25) / kVarC;
const uint16_t freq = var14 + _adlibFrequencyTable[value % 12];
writeRegister(0xA0 + hw_channel, freq & 0xFF);
const uint8_t regBx = 0x20 | (octave << 2) | ((freq & 0x300) >> 8);
writeRegister(0xB0 + hw_channel, regBx);
}
}
void adlibReset() {
for (int i = 0; i < 6; ++i) {
adlibMelodicKeyOff(i, 0);
}
for (int i = 6; i < 11; ++i) {
adlibPercussionKeyOff(i);
}
writeRegister(8, 0x40);
for (int i = 0; i < 18; ++i) {
writeRegister(_adlibOperatorTable[i] + 0x40, 0x3F);
}
for (int i = 0; i < 9; ++i) {
writeRegister(0xB0 + i, 0);
}
for (int i = 0; i < 9; ++i) {
writeRegister(0xC0 + i, 0);
}
for (int i = 0; i < 18; ++i) {
writeRegister(_adlibOperatorTable[i] + 0x60, 0);
}
for (int i = 0; i < 18; ++i) {
writeRegister(_adlibOperatorTable[i] + 0x80, 0);
}
for (int i = 0; i < 18; ++i) {
writeRegister(_adlibOperatorTable[i] + 0x20, 0);
}
for (int i = 0; i < 18; ++i) {
writeRegister(_adlibOperatorTable[i] + 0xE0, 0);
}
writeRegister(1, 0x20);
writeRegister(1, 0);
for (int i = 0; i < 9; ++i) {
writeRegister(0xB0 + i, 0);
writeRegister(0xA0 + i, 0);
}
writeRegister(0xBD, 0);
_regBD = 0x20;
}
void writeRegister(uint8_t port, uint8_t value) {
//fprintf(stdout, "0x%02x:0x%02x\n", port, value);
OPL3_WriteReg(&_opl3, port, value);
}
};
static MidiDriver *createMidiDriver() {
return new MidiDriver_adlib;
}
const MidiDriverInfo midi_driver_adlib = {
"Adlib",
createMidiDriver
};

87
midi_driver_mt32.cpp Normal file
View File

@ -0,0 +1,87 @@
#include <stdio.h>
#include <stdint.h>
#include "midi_driver.h"
#define MT32EMU_API_TYPE 1
#include <mt32emu.h>
static const bool _isCM32L = false;
static const char *CM32L_CONTROL_FILENAME = "CM32L_CONTROL.ROM";
static const char *CM32L_PCM_FILENAME = "CM32L_PCM.ROM";
static const char *MT32_CONTROL_FILENAME = "MT32_CONTROL.ROM";
static const char *MT32_PCM_FILENAME = "MT32_PCM.ROM";
static uint32_t msg(int cmd, int param1 = 0, int param2 = 0) {
return (param2 << 16) | (param1 << 8) | cmd;
}
struct MidiDriver_mt32emu: MidiDriver {
virtual int init() {
mt32emu_report_handler_i report = { 0 };
_context = mt32emu_create_context(report, this);
const char *control = _isCM32L ? CM32L_CONTROL_FILENAME : MT32_CONTROL_FILENAME;
if (mt32emu_add_rom_file(_context, control) != MT32EMU_RC_ADDED_CONTROL_ROM) {
fprintf(stdout, "WARNING: Failed to add MT32 rom file '%s'", control);
return -1;
}
const char *pcm = _isCM32L ? CM32L_PCM_FILENAME : MT32_PCM_FILENAME;
if (mt32emu_add_rom_file(_context, pcm) != MT32EMU_RC_ADDED_PCM_ROM) {
fprintf(stdout, "WARNING: Failed to add MT32 rom file '%s'", pcm);
return -1;
}
mt32emu_rom_info romInfo;
mt32emu_get_rom_info(_context, &romInfo);
fprintf(stdout, "mt32emu version %s\n", mt32emu_get_library_version_string());
//fprintf(stdout, "control rom %s %s\n", romInfo.control_rom_id, romInfo.control_rom_description);
//fprintf(stdout, "pcm rom %s %s\n", romInfo.pcm_rom_id, romInfo.pcm_rom_description);
mt32emu_set_midi_delay_mode(_context, MT32EMU_MDM_IMMEDIATE);
return 0;
}
virtual void fini() {
if (mt32emu_is_open(_context)) {
mt32emu_close_synth(_context);
}
mt32emu_free_context(_context);
}
virtual void reset(int rate) {
mt32emu_set_stereo_output_samplerate(_context, rate);
if (mt32emu_is_open(_context)) {
mt32emu_close_synth(_context);
}
mt32emu_open_synth(_context);
}
virtual void noteOff(int channel, int note, int velocity) {
mt32emu_play_msg(_context, msg(0x80 | channel, note, velocity));
}
virtual void noteOn(int channel, int note, int velocity) {
mt32emu_play_msg(_context, msg(0x90 | channel, note, velocity));
}
virtual void controlChange(int channel, int type, int value) {
mt32emu_play_msg(_context, msg(0xB0 | channel, type, value));
}
virtual void programChange(int channel, int num) {
mt32emu_play_msg(_context, msg(0xC0 | channel, num));
}
virtual void pitchBend(int channel, int lsb, int msb) {
mt32emu_play_msg(_context, msg(0xE0 | channel, lsb, msb));
}
virtual void readSamples(int16_t *samples, int len) {
mt32emu_render_bit16s(_context, samples, len);
}
mt32emu_context _context;
};
static MidiDriver *createMidiDriver() {
return new MidiDriver_mt32emu;
}
const MidiDriverInfo midi_driver_mt32 = {
"MT32",
createMidiDriver
};

114
midi_parser.cpp Normal file
View File

@ -0,0 +1,114 @@
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include "file.h"
#include "midi_parser.h"
#include "util.h"
void MidiTrack::rewind() {
endOfTrack = false;
offset = 0;
}
static uint32_t readVLQ(const uint8_t *data, uint32_t& offset) {
uint32_t value = 0;
for (int i = 0; i < 4; ++i) {
const uint8_t b = data[offset++];
value = (value << 7) | (b & 0x7F);
if ((b & 0x80) == 0) {
break;
}
}
return value;
}
const MidiEvent *MidiTrack::nextEvent() {
while (offset < size) {
MidiEvent &ev = event;
ev.timestamp = readVLQ(data, offset);
ev.command = data[offset++];
switch (ev.command & 0xF0) {
case MIDI_COMMAND_NOTE_OFF:
case MIDI_COMMAND_NOTE_ON:
ev.param1 = data[offset++];
ev.param2 = data[offset++];
debug(DBG_MIDI, "Note %s: note %d vel %d timestamp %d", (((ev.command & 0xF0) == MIDI_COMMAND_NOTE_OFF) ? "Off" : "On"), ev.param1, ev.param2, ev.timestamp);
break;
case MIDI_COMMAND_PROGRAM_CHANGE:
ev.param1 = data[offset++];
debug(DBG_MIDI, "Program %d timestamp %d", ev.param1, ev.timestamp);
break;
case MIDI_COMMAND_CHANNEL_PRESSURE:
ev.param1 = data[offset++];
debug(DBG_MIDI, "Channel pressure %d timestamp %d", ev.param1, ev.timestamp);
break;
case MIDI_COMMAND_PITCH_BEND:
ev.param1 = data[offset++];
ev.param2 = data[offset++];
debug(DBG_MIDI, "Pitch Bend %d,%d", ev.param1, ev.param2);
break;
case MIDI_COMMAND_SYSEX:
if (ev.command == 0xFF) {
const int num = data[offset++];
const int len = readVLQ(data, offset);
offset += len;
debug(DBG_MIDI, "SysEx timestamp %d", ev.timestamp);
assert(ev.timestamp == 0);
if (num == MIDI_SYSEX_END_OF_TRACK) {
endOfTrack = true;
return 0;
}
continue;
}
/* fall-through */
default:
warning("Unhandled MIDI command 0x%x", ev.command);
break;
}
return &event;
}
return 0;
}
MidiParser::MidiParser() {
memset(_tracks, 0, sizeof(_tracks));
_tracksCount = 0;
}
void MidiParser::loadMid(File *f) {
for (int i = 0; i < MAX_TRACKS; ++i) {
free(_tracks[i].data);
_tracks[i].data = 0;
}
memset(_tracks, 0, sizeof(_tracks));
_tracksCount = 0;
char tag[4];
f->read(tag, 4);
assert(memcmp(tag, "MThd", 4) == 0);
const uint32_t len = f->readUint32BE();
assert(len == 6);
f->readByte();
const int type = f->readByte();
const int tracksCount = f->readUint16BE();
assert(tracksCount <= MAX_TRACKS);
const int ppqn = f->readUint16BE();
debug(DBG_MIDI, "MThd type %d tracks %d ppqn %d len %d", type, tracksCount, ppqn, len);
for (int i = 0; i < tracksCount; ++i) {
f->read(tag, 4);
assert(memcmp(tag, "MTrk", 4) == 0);
const uint32_t len = f->readUint32BE();
debug(DBG_MIDI, "Track #%d len %d", i, len);
MidiTrack *track = &_tracks[i];
track->data = (uint8_t *)malloc(len);
if (!track->data) {
warning("Failed to allocate %d bytes for MIDI track %d", len, i);
} else {
track->endOfTrack = false;
track->offset = 0;
track->size = len;
f->read(track->data, len);
}
}
_tracksCount = tracksCount;
}

47
midi_parser.h Normal file
View File

@ -0,0 +1,47 @@
#ifndef MIDI_PARSER_H__
#define MIDI_PARSER_H__
#include <stdint.h>
#define MIDI_COMMAND_NOTE_OFF 0x80
#define MIDI_COMMAND_NOTE_ON 0x90
#define MIDI_COMMAND_PROGRAM_CHANGE 0xC0
#define MIDI_COMMAND_CHANNEL_PRESSURE 0xD0 /* unused */
#define MIDI_COMMAND_PITCH_BEND 0xE0
#define MIDI_COMMAND_SYSEX 0xF0 /* unused */
#define MIDI_SYSEX_END_OF_TRACK 0x2F
struct MidiEvent {
uint32_t timestamp;
uint8_t command;
uint8_t param1;
uint8_t param2;
};
struct MidiTrack {
bool endOfTrack;
uint8_t *data;
uint32_t offset, size;
MidiEvent event;
void rewind();
const MidiEvent *nextEvent();
};
struct File;
#define MAX_TRACKS 16
struct MidiParser {
MidiParser();
void loadMid(File *f);
int _tracksCount;
MidiTrack _tracks[MAX_TRACKS];
};
#endif /* MIDI_PARSER_H__ */

View File

@ -8,14 +8,16 @@
#include "systemstub.h"
#include "util.h"
Mixer::Mixer(FileSystem *fs, SystemStub *stub)
: _stub(stub), _musicType(MT_NONE), _cpc(this, fs), _mod(this, fs), _ogg(this, fs), _sfx(this) {
Mixer::Mixer(FileSystem *fs, SystemStub *stub, int midiDriver)
: _stub(stub), _musicType(MT_NONE), _cpc(this, fs), _mod(this, fs), _ogg(this, fs), _prf(this, fs, midiDriver), _sfx(this) {
_musicTrack = -1;
_backgroundMusicType = MT_NONE;
}
void Mixer::init() {
memset(_channels, 0, sizeof(_channels));
for (int i = 0; i < NUM_CHANNELS; ++i) {
_channels[i].active = false;
}
_premixHook = 0;
_stub->startAudio(Mixer::mixCallback, this);
}
@ -42,6 +44,7 @@ void Mixer::play(const uint8_t *data, uint32_t len, uint16_t freq, uint8_t volum
if (cur->active) {
if (cur->chunk.data == data) {
cur->chunkPos = 0;
cur->volume = volume;
return;
}
} else {
@ -87,24 +90,23 @@ static bool isMusicSfx(int num) {
return (num >= 68 && num <= 75);
}
void Mixer::playMusic(int num) {
debug(DBG_SND, "Mixer::playMusic(%d)", num);
if (num > MUSIC_TRACK && num != _musicTrack) {
if (_ogg.playTrack(num - MUSIC_TRACK)) {
_backgroundMusicType = _musicType = MT_OGG;
_musicTrack = num;
return;
}
if (_cpc.playTrack(num - MUSIC_TRACK)) {
_backgroundMusicType = _musicType = MT_CPC;
_musicTrack = num;
return;
}
}
void Mixer::playMusic(int num, int tempo) {
debug(DBG_SND, "Mixer::playMusic(%d, %d)", num, tempo);
int trackNum = -1;
if (num == 1) { // menu screen
if (_cpc.playTrack(2) || _ogg.playTrack(2)) {
trackNum = 2;
} else if (num > MUSIC_TRACK) {
trackNum = num - MUSIC_TRACK;
}
if (trackNum != -1 && trackNum != _musicTrack) {
if (_ogg.playTrack(trackNum)) {
_backgroundMusicType = _musicType = MT_OGG;
_musicTrack = 2;
_musicTrack = trackNum;
return;
}
if (_cpc.playTrack(trackNum)) {
_backgroundMusicType = _musicType = MT_CPC;
_musicTrack = trackNum;
return;
}
}
@ -117,9 +119,17 @@ void Mixer::playMusic(int num) {
_musicType = MT_SFX;
}
} else { // cutscene
_mod.play(num);
_mod.play(num, tempo);
if (_mod._playing) {
_musicType = MT_MOD;
return;
}
if (g_options.use_prf_music) {
_prf.play(num);
if (_prf._playing) {
_musicType = MT_PRF;
return;
}
}
}
}
@ -135,6 +145,9 @@ void Mixer::stopMusic() {
case MT_OGG:
_ogg.pauseTrack();
break;
case MT_PRF:
_prf.stop();
break;
case MT_SFX:
_sfx.stop();
break;
@ -143,7 +156,7 @@ void Mixer::stopMusic() {
break;
}
_musicType = MT_NONE;
if (_musicTrack != -1) {
if (_musicTrack > 2) { // do not resume menu music
switch (_backgroundMusicType) {
case MT_OGG:
_ogg.resumeTrack();
@ -181,18 +194,21 @@ void Mixer::mix(int16_t *out, int len) {
MixerChannel *ch = &_channels[i];
if (ch->active) {
for (int pos = 0; pos < len; ++pos) {
if ((ch->chunkPos >> FRAC_BITS) >= (ch->chunk.len - 1)) {
const uint32_t cpos = ch->chunkPos >> FRAC_BITS;
if (cpos >= ch->chunk.len) {
ch->active = false;
break;
}
const int sample = ch->chunk.getPCM(ch->chunkPos >> FRAC_BITS) * ch->volume / Mixer::MAX_VOLUME;
out[pos] = ADDC_S16(out[pos], S8_to_S16(sample));
const int sample8 = ch->chunk.getPCM(cpos) * ch->volume / Mixer::MAX_VOLUME;
const int sample16 = S8_to_S16(sample8);
out[2 * pos] = ADDC_S16(out[2 * pos], sample16);
out[2 * pos + 1] = ADDC_S16(out[2 * pos + 1], sample16);
ch->chunkPos += ch->chunkInc;
}
}
}
if (kUseNr) {
nr(out, len);
nr(out, len * 2); // stereo
}
}

View File

@ -11,6 +11,7 @@
#include "cpc_player.h"
#include "mod_player.h"
#include "ogg_player.h"
#include "prf_player.h"
#include "sfx_player.h"
struct MixerChunk {
@ -49,6 +50,7 @@ struct Mixer {
MT_NONE,
MT_MOD,
MT_OGG,
MT_PRF,
MT_SFX,
MT_CPC,
};
@ -70,10 +72,11 @@ struct Mixer {
CpcPlayer _cpc;
ModPlayer _mod;
OggPlayer _ogg;
PrfPlayer _prf;
SfxPlayer _sfx;
int _musicTrack;
Mixer(FileSystem *fs, SystemStub *stub);
Mixer(FileSystem *fs, SystemStub *stub, int midiDriver);
void init();
void free();
void setPremixHook(PremixHook premixHook, void *userData);
@ -81,7 +84,7 @@ struct Mixer {
bool isPlaying(const uint8_t *data) const;
uint32_t getSampleRate() const;
void stopAll();
void playMusic(int num);
void playMusic(int num, int tempo = 0);
void stopMusic();
void mix(int16_t *buf, int len);

View File

@ -16,6 +16,7 @@ struct ModPlayer_impl {
ModPlugFile *_mf;
ModPlug_Settings _settings;
int _songTempo;
bool _repeatIntro;
ModPlayer_impl()
@ -26,7 +27,7 @@ struct ModPlayer_impl {
memset(&_settings, 0, sizeof(_settings));
ModPlug_GetSettings(&_settings);
_settings.mFlags = MODPLUG_ENABLE_OVERSAMPLING | MODPLUG_ENABLE_NOISE_REDUCTION;
_settings.mChannels = 1;
_settings.mChannels = 2;
_settings.mBits = 16;
_settings.mFrequency = rate;
_settings.mResamplingMode = MODPLUG_RESAMPLE_FIR;
@ -59,7 +60,7 @@ struct ModPlayer_impl {
ModPlug_SeekOrder(_mf, 1);
_repeatIntro = false;
}
const int count = ModPlug_Read(_mf, buf, len * sizeof(int16_t));
const int count = ModPlug_Read(_mf, buf, len * sizeof(int16_t) * 2);
// setting mLoopCount to non-zero does not trigger any looping in
// my test and ModPlug_Read returns 0.
// looking at the libmodplug-0.8.8 tarball, it seems the variable
@ -82,6 +83,7 @@ struct ModPlayer_impl {
NUM_TRACKS = 4,
NUM_PATTERNS = 128,
FRAC_BITS = 12,
BASE_TEMPO = 125,
PAULA_FREQ = 3546897
};
@ -234,7 +236,7 @@ bool ModPlayer_impl::load(File *f) {
_currentTick = 0;
_patternDelay = 0;
_songSpeed = 6;
_songTempo = 125;
_songTempo = BASE_TEMPO;
_patternLoopPos = 0;
_patternLoopCount = -1;
_samplesLeft = 0;
@ -506,6 +508,7 @@ void ModPlayer_impl::handleEffect(int trackNum, bool tick) {
tk->volume = 0;
}
}
break;
case 0xD: // delay sample
if (!tick) {
tk->delayCounter = effectY;
@ -616,8 +619,11 @@ void ModPlayer_impl::mixSamples(int16_t *buf, int samplesLen) {
curLen = 0;
}
while (count--) {
const int out = si->getPCM(pos >> FRAC_BITS) * tk->volume / 64;
*mixbuf = ADDC_S16(*mixbuf, S8_to_S16(out));
const int sample8 = si->getPCM(pos >> FRAC_BITS) * tk->volume / 64;
const int sample16 = S8_to_S16(sample8);
*mixbuf = ADDC_S16(*mixbuf, sample16);
++mixbuf;
*mixbuf = ADDC_S16(*mixbuf, sample16);
++mixbuf;
pos += deltaPos;
}
@ -628,9 +634,9 @@ void ModPlayer_impl::mixSamples(int16_t *buf, int samplesLen) {
}
bool ModPlayer_impl::mix(int16_t *buf, int len) {
memset(buf, 0, sizeof(int16_t) * len);
memset(buf, 0, sizeof(int16_t) * len * 2); // stereo
if (_playing) {
const int samplesPerTick = _mixingRate / (50 * _songTempo / 125);
const int samplesPerTick = _mixingRate / (50 * _songTempo / BASE_TEMPO);
while (len != 0) {
if (_samplesLeft == 0) {
handleTick();
@ -643,7 +649,7 @@ bool ModPlayer_impl::mix(int16_t *buf, int len) {
_samplesLeft -= count;
len -= count;
mixSamples(buf, count);
buf += count;
buf += count * 2; // stereo
}
}
return _playing;
@ -659,20 +665,24 @@ ModPlayer::~ModPlayer() {
delete _impl;
}
void ModPlayer::play(int num) {
if (num < _modulesFilesCount) {
void ModPlayer::play(int num, int tempo) {
if (num * 2 < _namesCount) {
File f;
for (uint8_t i = 0; i < ARRAYSIZE(_modulesFiles[num]); ++i) {
if (f.open(_modulesFiles[num][i], "rb", _fs)) {
_impl->init(_mix->getSampleRate());
if (_impl->load(&f)) {
_impl->_repeatIntro = (num == 0) && !_isAmiga;
_mix->setPremixHook(mixCallback, _impl);
_playing = true;
}
if (!f.open(_names[num * 2], "rb", _fs)) {
const char *p = _names[num * 2 + 1];
char name[32];
snprintf(name, sizeof(name), "mod.flashback-%s", p ? p : _names[num * 2]);
if (!f.open(name, "rb", _fs)) {
return;
}
}
_impl->init(_mix->getSampleRate());
if (_impl->load(&f)) {
_impl->_songTempo = tempo;
_impl->_repeatIntro = (num == 0) && !_isAmiga;
_mix->setPremixHook(mixCallback, _impl);
_playing = true;
}
}
}

View File

@ -16,8 +16,8 @@ struct ModPlayer_impl;
struct ModPlayer {
static const uint16_t _periodTable[];
static const char *_modulesFiles[][2];
static const int _modulesFilesCount;
static const char *_names[];
static const int _namesCount;
bool _isAmiga;
bool _playing;
@ -28,7 +28,7 @@ struct ModPlayer {
ModPlayer(Mixer *mixer, FileSystem *fs);
~ModPlayer();
void play(int num);
void play(int num, int tempo);
void stop();
static bool mixCallback(void *param, int16_t *buf, int len);

View File

@ -7,6 +7,9 @@
#ifdef USE_TREMOR
#include <tremor/ivorbisfile.h>
#endif
#ifdef USE_STB_VORBIS
#include "stb_vorbis.c"
#endif
#include "file.h"
#include "mixer.h"
#include "ogg_player.h"
@ -67,13 +70,17 @@ struct OggDecoder_impl {
}
}
bool load(VorbisFile *f, int mixerSampleRate) {
bool load(const char *name, FileSystem *fs, int mixerSampleRate) {
if (!_f.open(name, "rb", fs)) {
return false;
}
_f.offset = 0;
ov_callbacks ovcb;
ovcb.read_func = VorbisFile::readHelper;
ovcb.seek_func = VorbisFile::seekHelper;
ovcb.close_func = VorbisFile::closeHelper;
ovcb.tell_func = VorbisFile::tellHelper;
if (ov_open_callbacks(f, &_ovf, 0, 0, ovcb) < 0) {
if (ov_open_callbacks(&_f, &_ovf, 0, 0, ovcb) < 0) {
warning("Invalid .ogg file");
return false;
}
@ -112,8 +119,9 @@ struct OggDecoder_impl {
case 2:
assert((len & 3) == 0);
for (int i = 0; i < len / 2; i += 2) {
const int16_t s16 = (_readBuf[i] + _readBuf[i + 1]) / 2;
*dst = ADDC_S16(*dst, s16);
*dst = ADDC_S16(*dst, _readBuf[i]);
++dst;
*dst = ADDC_S16(*dst, _readBuf[i + 1]);
++dst;
}
break;
@ -121,6 +129,8 @@ struct OggDecoder_impl {
for (int i = 0; i < len / 2; ++i) {
*dst = ADDC_S16(*dst, _readBuf[i]);
++dst;
*dst = ADDC_S16(*dst, _readBuf[i]);
++dst;
}
break;
}
@ -131,6 +141,7 @@ struct OggDecoder_impl {
return count;
}
VorbisFile _f;
OggVorbis_File _ovf;
int _channels;
bool _open;
@ -139,66 +150,168 @@ struct OggDecoder_impl {
};
#endif
#ifdef USE_STB_VORBIS
static const int kMusicVolume = 192;
struct OggDecoder_impl {
OggDecoder_impl()
: _v(0) {
}
~OggDecoder_impl() {
if (_v) {
stb_vorbis_close(_v);
_v = 0;
}
}
bool load(const char *name, FileSystem *fs, int mixerSampleRate) {
if (!_f.open(name, "rb", fs)) {
return false;
}
_count = _f.read(_buffer, sizeof(_buffer));
if (_count > 0) {
int bytes = 0;
int error = 0;
_v = stb_vorbis_open_pushdata(_buffer, _count, &bytes, &error, 0);
if (_v) {
_offset = bytes;
stb_vorbis_info info = stb_vorbis_get_info(_v);
if (info.channels != 2 || (int)info.sample_rate != mixerSampleRate) {
warning("Unhandled ogg/pcm format ch %d rate %d", info.channels, info.sample_rate);
return false;
}
_decodedSamplesLen = 0;
return true;
}
}
return false;
}
int read(int16_t *dst, int samples) {
int total = 0;
if (_decodedSamplesLen != 0) {
const int len = MIN(_decodedSamplesLen, samples);
for (int i = 0; i < len; ++i) {
*dst = ADDC_S16(*dst, ((_decodedSamples[0][i] * kMusicVolume) >> 8));
++dst;
*dst = ADDC_S16(*dst, ((_decodedSamples[1][i] * kMusicVolume) >> 8));
++dst;
}
total += len;
_decodedSamplesLen -= len;
}
while (total < samples) {
int channels = 0;
float **outputs;
int count;
int bytes = stb_vorbis_decode_frame_pushdata(_v, _buffer + _offset, _count - _offset, &channels, &outputs, &count);
if (bytes == 0) {
if (_offset != _count) {
memmove(_buffer, _buffer + _offset, _count - _offset);
_offset = _count - _offset;
} else {
_offset = 0;
}
_count = sizeof(_buffer) - _offset;
bytes = _f.read(_buffer + _offset, _count);
if (bytes < 0) {
break;
}
if (bytes == 0) {
// rewind
_f.seek(0);
_count = _f.read(_buffer, sizeof(_buffer));
stb_vorbis_flush_pushdata(_v);
} else {
_count = _offset + bytes;
}
_offset = 0;
continue;
}
_offset += bytes;
if (channels == 2) {
const int remain = samples - total;
const int len = MIN(count, remain);
for (int i = 0; i < len; ++i) {
const int l = int(outputs[0][i] * 32768 + .5);
const int r = int(outputs[1][i] * 32768 + .5);
*dst = ADDC_S16(*dst, ((l * kMusicVolume) >> 8));
++dst;
*dst = ADDC_S16(*dst, ((r * kMusicVolume) >> 8));
++dst;
}
if (count > remain) {
_decodedSamplesLen = count - remain;
assert(_decodedSamplesLen < 1024);
for (int i = 0; i < _decodedSamplesLen; ++i) {
_decodedSamples[0][i] = int(outputs[0][len + i] * 32768 + .5);
_decodedSamples[1][i] = int(outputs[1][len + i] * 32768 + .5);
}
total = samples;
break;
}
} else {
warning("Invalid decoded data channels %d count %d", channels, count);
}
total += count;
}
return total;
}
uint8_t _buffer[8192];
int16_t _decodedSamples[2][1024];
int _decodedSamplesLen;
uint32_t _offset, _count;
stb_vorbis *_v;
File _f;
};
#endif
OggPlayer::OggPlayer(Mixer *mixer, FileSystem *fs)
: _mix(mixer), _fs(fs), _impl(0) {
: _mix(mixer), _fs(fs) {
_impl = new OggDecoder_impl;
}
OggPlayer::~OggPlayer() {
#ifdef USE_TREMOR
delete _impl;
#endif
_impl = 0;
}
// https://www.amigaremix.com/remix/191
static const char *kMenuThemeRemix = "deadly_cookie_-_flashback.ogg";
bool OggPlayer::playTrack(int num) {
#ifdef USE_TREMOR
stopTrack();
char buf[16];
snprintf(buf, sizeof(buf), "track%02d.ogg", num);
VorbisFile *vf = new VorbisFile;
if (vf->open(buf, "rb", _fs)) {
vf->offset = 0;
_impl = new OggDecoder_impl();
if (_impl->load(vf, _mix->getSampleRate())) {
debug(DBG_INFO, "Playing '%s'", buf);
_mix->setPremixHook(mixCallback, this);
return true;
}
if (_impl->load(buf, _fs, _mix->getSampleRate())
|| (num == 2 && _impl->load(kMenuThemeRemix, _fs, _mix->getSampleRate()))) {
_mix->setPremixHook(mixCallback, this);
return true;
}
delete vf;
#endif
return false;
}
void OggPlayer::stopTrack() {
#ifdef USE_TREMOR
_mix->setPremixHook(0, 0);
delete _impl;
_impl = 0;
#endif
}
void OggPlayer::pauseTrack() {
#ifdef USE_TREMOR
if (_impl) {
_mix->setPremixHook(0, 0);
}
#endif
}
void OggPlayer::pauseTrack() {
if (_impl) {
_mix->setPremixHook(0, 0);
}
}
void OggPlayer::resumeTrack() {
#ifdef USE_TREMOR
if (_impl) {
_mix->setPremixHook(mixCallback, this);
}
#endif
}
bool OggPlayer::mix(int16_t *buf, int len) {
#ifdef USE_TREMOR
if (_impl) {
return _impl->read(buf, len) != 0;
}
#endif
return false;
}

278
piege.cpp
View File

@ -4,7 +4,6 @@
* Copyright (C) 2005-2019 Gregory Montoir (cyx@users.sourceforge.net)
*/
#include "cutscene.h"
#include "game.h"
#include "resource.h"
#include "systemstub.h"
@ -17,12 +16,12 @@ void Game::pge_resetMessages() {
int n = 0xFF;
while (n--) {
le->next_entry = le + 1;
le->index = 0;
le->src_pge = 0;
le->msg_num = 0;
++le;
}
le->next_entry = 0;
le->index = 0;
le->src_pge = 0;
le->msg_num = 0;
}
@ -34,7 +33,7 @@ void Game::pge_clearMessages(uint8_t pge_index) {
while (le) {
MessagePGE *cur = le->next_entry;
le->next_entry = next;
le->index = 0;
le->src_pge = 0;
le->msg_num = 0;
next = le;
le = cur;
@ -45,10 +44,10 @@ void Game::pge_clearMessages(uint8_t pge_index) {
int Game::pge_hasMessageData(LivePGE *pge, uint16_t msg_num, uint16_t counter) const {
assert(counter >= 1 && counter <= 4);
uint16_t pge_src_index = pge->init_PGE->counter_values[counter - 1];
uint16_t pge_src_index = pge->init_PGE->data[counter - 1];
const MessagePGE *le = _pge_messagesTable[pge->index];
while (le) {
if (le->msg_num == msg_num && le->index == pge_src_index) {
if (le->msg_num == msg_num && le->src_pge == pge_src_index) {
return 1;
}
le = le->next_entry;
@ -77,7 +76,7 @@ void Game::pge_loadForCurrentLevel(uint16_t idx) {
live_pge->collision_slot = 0xFF;
live_pge->next_inventory_PGE = 0xFF;
live_pge->current_inventory_PGE = 0xFF;
live_pge->unkF = 0xFF;
live_pge->ref_inventory_PGE = 0xFF;
live_pge->anim_number = 0;
live_pge->index = idx;
live_pge->next_PGE_in_room = 0;
@ -166,7 +165,7 @@ void Game::pge_setupNextAnimFrame(LivePGE *pge, MessagePGE *le) {
ObjectNode *on = _res._objectNodesMap[init_pge->obj_node_number];
Object *obj = &on->objects[pge->first_obj_number];
int i = pge->first_obj_number;
while (i < on->last_obj_number && pge->obj_type == obj->type) {
while (i < on->num_objects && pge->obj_type == obj->type) {
MessagePGE *next_le = le;
while (next_le) {
uint16_t msgNum = next_le->msg_num;
@ -318,8 +317,14 @@ int Game::pge_execute(LivePGE *live_pge, InitPGE *init_pge, const Object *obj) {
--live_pge->life;
if (init_pge->object_type == 1) {
_pge_processOBJ = true;
if (_cheats & kCheatLifeCounter) {
++live_pge->life;
}
} else if (init_pge->object_type == 10) {
_score += 100;
if (_cheats & kCheatOneHitKill) {
live_pge->life = 0;
}
}
}
if (obj->flags & 4) {
@ -370,7 +375,7 @@ void Game::pge_prepare() {
void Game::pge_setupDefaultAnim(LivePGE *pge) {
const uint8_t *anim_data = _res.getAniData(pge->obj_type);
if (pge->anim_seq < _res._readUint16(anim_data)) {
if (1 || pge->anim_seq < _res._readUint16(anim_data)) { /* matches disassembly but should probably be >= */
pge->anim_seq = 0;
}
const uint8_t *anim_frame = anim_data + 6 + pge->anim_seq * 4;
@ -398,7 +403,7 @@ uint16_t Game::pge_processOBJ(LivePGE *pge) {
ObjectNode *on = _res._objectNodesMap[init_pge->obj_node_number];
Object *obj = &on->objects[pge->first_obj_number];
int i = pge->first_obj_number;
while (i < on->last_obj_number && pge->obj_type == obj->type) {
while (i < on->num_objects && pge->obj_type == obj->type) {
if (obj->opcode2 == 0x6B) return 0xFFFF;
if (obj->opcode2 == 0x22 && obj->opcode_arg2 <= 4) return 0xFFFF;
@ -436,7 +441,7 @@ void Game::pge_setupOtherPieges(LivePGE *pge, InitPGE *init_pge) {
_currentRoom = room;
col_prepareRoomState();
_loadMap = true;
if (!(_currentRoom & 0x80) && _currentRoom < 0x40) {
if (_currentRoom < 0x40) {
LivePGE *pge_it = _pge_liveTable1[_currentRoom];
while (pge_it) {
if (pge_it->init_PGE->flags & 4) {
@ -810,25 +815,25 @@ int Game::pge_hasPiegeSentMessage(ObjectOpcodeArgs *args) {
int Game::pge_op_sendMessageData0(ObjectOpcodeArgs *args) {
LivePGE *pge = args->pge;
pge_sendMessage(pge->index, pge->init_PGE->counter_values[0], args->a);
pge_sendMessage(pge->index, pge->init_PGE->data[0], args->a);
return 0xFFFF;
}
int Game::pge_op_sendMessageData1(ObjectOpcodeArgs *args) {
LivePGE *pge = args->pge;
pge_sendMessage(pge->index, pge->init_PGE->counter_values[1], args->a);
pge_sendMessage(pge->index, pge->init_PGE->data[1], args->a);
return 0xFFFF;
}
int Game::pge_op_sendMessageData2(ObjectOpcodeArgs *args) {
LivePGE *pge = args->pge;
pge_sendMessage(pge->index, pge->init_PGE->counter_values[2], args->a);
pge_sendMessage(pge->index, pge->init_PGE->data[2], args->a);
return 0xFFFF;
}
int Game::pge_op_sendMessageData3(ObjectOpcodeArgs *args) {
LivePGE *pge = args->pge;
pge_sendMessage(pge->index, pge->init_PGE->counter_values[3], args->a);
pge_sendMessage(pge->index, pge->init_PGE->data[3], args->a);
return 0xFFFF;
}
@ -1103,7 +1108,7 @@ int Game::pge_o_unk0x40(ObjectOpcodeArgs *args) {
int Game::pge_op_wakeUpPiege(ObjectOpcodeArgs *args) {
if (args->a <= 3) {
int16_t num = args->pge->init_PGE->counter_values[args->a];
int16_t num = args->pge->init_PGE->data[args->a];
if (num >= 0) {
LivePGE *pge = &_pgeLive[num];
pge->flags |= 4;
@ -1115,7 +1120,7 @@ int Game::pge_op_wakeUpPiege(ObjectOpcodeArgs *args) {
int Game::pge_op_removePiege(ObjectOpcodeArgs *args) {
if (args->a <= 3) {
int16_t num = args->pge->init_PGE->counter_values[args->a];
int16_t num = args->pge->init_PGE->data[args->a];
if (num >= 0) {
_pge_liveTable2[num] = 0;
_pgeLive[num].flags &= ~4;
@ -1128,8 +1133,7 @@ int Game::pge_op_removePiegeIfNotNear(ObjectOpcodeArgs *args) {
LivePGE *pge = args->pge;
if (!(pge->init_PGE->flags & 4)) goto kill_pge;
if (_currentRoom & 0x80) goto skip_pge;
if (pge->room_location & 0x80) goto kill_pge;
if (pge->room_location > 0x3F) goto kill_pge;
if (!(pge->room_location < 0x40)) goto kill_pge;
if (pge->room_location == _currentRoom) goto skip_pge;
if (pge->room_location == _res._ctData[CT_UP_ROOM + _currentRoom]) goto skip_pge;
if (pge->room_location == _res._ctData[CT_DOWN_ROOM + _currentRoom]) goto skip_pge;
@ -1147,7 +1151,7 @@ skip_pge:
}
int Game::pge_op_loadPiegeCounter(ObjectOpcodeArgs *args) {
args->pge->counter_value = args->pge->init_PGE->counter_values[args->a];
args->pge->counter_value = args->pge->init_PGE->data[args->a];
return 1;
}
@ -1169,7 +1173,7 @@ int Game::pge_o_unk0x47(ObjectOpcodeArgs *args) {
// used with Ian in level2
int Game::pge_o_unk0x48(ObjectOpcodeArgs *args) {
LivePGE *pge = col_findPiege(&_pgeLive[0], args->pge->init_PGE->counter_values[0]);
LivePGE *pge = col_findPiege(&_pgeLive[0], args->pge->init_PGE->data[0]);
if (pge && pge->life == args->pge->life) {
pge_sendMessage(args->pge->index, pge->index, args->a);
return 1;
@ -1178,15 +1182,15 @@ int Game::pge_o_unk0x48(ObjectOpcodeArgs *args) {
}
int Game::pge_o_unk0x49(ObjectOpcodeArgs *args) {
return pge_ZOrder(&_pgeLive[0], args->a, &Game::pge_ZOrderIfIndex, args->pge->init_PGE->counter_values[0]);
return pge_ZOrder(&_pgeLive[0], args->a, &Game::pge_ZOrderIfIndex, args->pge->init_PGE->data[0]);
}
int Game::pge_o_unk0x4A(ObjectOpcodeArgs *args) {
int Game::pge_op_killInventoryPiege(ObjectOpcodeArgs *args) {
LivePGE *pge = args->pge;
pge->room_location = 0xFE;
pge->flags &= ~4;
_pge_liveTable2[pge->index] = 0;
LivePGE *inv_pge = pge_getInventoryItemBefore(&_pgeLive[args->a], pge);
LivePGE *inv_pge = pge_getPreviousInventoryItem(&_pgeLive[args->a], pge);
if (inv_pge == &_pgeLive[args->a]) {
if (pge->index != inv_pge->current_inventory_PGE) {
return 1;
@ -1267,14 +1271,11 @@ int Game::pge_op_incLife(ObjectOpcodeArgs *args) {
return 1;
}
// level2, Ian
int Game::pge_op_setPiegeDefaultAnim(ObjectOpcodeArgs *args) {
assert(args->a >= 0 && args->a < 4);
int16_t r = args->pge->init_PGE->counter_values[args->a];
args->pge->room_location = r;
if (r == 1) {
// this happens after death tower, on earth, when Conrad passes
// by the first policeman who's about to shoot him in the back
InitPGE *init_pge = args->pge->init_PGE;
args->pge->room_location = init_pge->data[args->a];
if (init_pge->object_type == 1) {
_loadMap = true;
}
pge_setupDefaultAnim(args->pge);
@ -1282,7 +1283,7 @@ int Game::pge_op_setPiegeDefaultAnim(ObjectOpcodeArgs *args) {
}
int Game::pge_op_setLifeCounter(ObjectOpcodeArgs *args) {
_pgeLive[args->a].life = args->pge->init_PGE->counter_values[0];
_pgeLive[args->a].life = args->pge->init_PGE->data[0];
return 1;
}
@ -1298,8 +1299,9 @@ int Game::pge_op_playCutscene(ObjectOpcodeArgs *args) {
return 1;
}
int Game::pge_op_isTempVar2Set(ObjectOpcodeArgs *args) {
if (_pge_opTempVar2 == args->a) {
// unused
int Game::pge_op_compareUnkVar(ObjectOpcodeArgs *args) {
if (args->a == -1) {
return 1;
}
return 0;
@ -1307,7 +1309,7 @@ int Game::pge_op_isTempVar2Set(ObjectOpcodeArgs *args) {
int Game::pge_op_playDeathCutscene(ObjectOpcodeArgs *args) {
if (_deathCutsceneCounter == 0) {
_deathCutsceneCounter = args->pge->init_PGE->counter_values[3] + 1;
_deathCutsceneCounter = args->pge->init_PGE->data[3] + 1;
_cut._deathCutsceneId = args->a;
}
return 1;
@ -1328,7 +1330,7 @@ int Game::pge_o_unk0x5F(ObjectOpcodeArgs *args) {
if (pge_room < 0 || pge_room >= 0x40) return 0;
int16_t dx;
int16_t _cx = pge->init_PGE->counter_values[0];
int16_t _cx = pge->init_PGE->data[0];
if (_cx <= 0) {
dx = 1;
_cx = -_cx;
@ -1368,7 +1370,7 @@ int Game::pge_op_findAndCopyPiege(ObjectOpcodeArgs *args) {
MessagePGE *le = _pge_messagesTable[args->pge->index];
while (le) {
if (le->msg_num == args->a) {
args->a = le->index;
args->a = le->src_pge;
args->b = 0;
pge_op_copyPiege(args);
return 1;
@ -1402,16 +1404,16 @@ int Game::pge_o_unk0x64(ObjectOpcodeArgs *args) {
int Game::pge_op_addToCredits(ObjectOpcodeArgs *args) {
assert(args->a >= 0 && args->a < 3);
uint8_t pge = args->pge->init_PGE->counter_values[args->a];
int16_t val = args->pge->init_PGE->counter_values[args->a + 1];
uint8_t pge = args->pge->init_PGE->data[args->a];
int16_t val = args->pge->init_PGE->data[args->a + 1];
_pgeLive[pge].life += val;
return 1;
}
int Game::pge_op_subFromCredits(ObjectOpcodeArgs *args) {
assert(args->a >= 0 && args->a < 3);
uint8_t pge = args->pge->init_PGE->counter_values[args->a];
int16_t val = args->pge->init_PGE->counter_values[args->a + 1];
uint8_t pge = args->pge->init_PGE->data[args->a];
int16_t val = args->pge->init_PGE->data[args->a + 1];
_pgeLive[pge].life -= val;
return 1;
}
@ -1569,7 +1571,7 @@ int Game::pge_isToggleable(ObjectOpcodeArgs *args) {
}
int Game::pge_o_unk0x6C(ObjectOpcodeArgs *args) {
LivePGE *pge = col_findPiege(&_pgeLive[0], args->pge->init_PGE->counter_values[0]);
LivePGE *pge = col_findPiege(&_pgeLive[0], args->pge->init_PGE->data[0]);
if (pge) {
if (pge->life <= args->pge->life) {
pge_sendMessage(args->pge->index, pge->index, args->a);
@ -1593,7 +1595,7 @@ int Game::pge_o_unk0x6E(ObjectOpcodeArgs *args) {
MessagePGE *le = _pge_messagesTable[args->pge->index];
while (le) {
if (args->a == le->msg_num) {
pge_updateInventory(&_pgeLive[le->index], args->pge);
pge_updateInventory(&_pgeLive[le->src_pge], args->pge);
return 0xFFFF;
}
le = le->next_entry;
@ -1607,7 +1609,7 @@ int Game::pge_o_unk0x6F(ObjectOpcodeArgs *args) {
MessagePGE *le = _pge_messagesTable[pge->index];
while (le) {
if (args->a == le->msg_num) {
pge_sendMessage(pge->index, le->index, 0xC);
pge_sendMessage(pge->index, le->src_pge, 0xC);
return 1;
}
le = le->next_entry;
@ -1639,21 +1641,24 @@ int Game::pge_o_unk0x71(ObjectOpcodeArgs *args) {
}
int Game::pge_o_unk0x72(ObjectOpcodeArgs *args) {
int8_t *var4 = &_res._ctData[0x100] + args->pge->room_location * 0x70;
var4 += (((args->pge->pos_y / 36) & ~1) + args->a) * 16 + (args->pge->pos_x + 8) / 16;
int8_t *grid_data = &_res._ctData[0x100] + args->pge->room_location * 0x70;
int16_t pge_pos_y = ((args->pge->pos_y / 36) & ~1) + args->a;
int16_t pge_pos_x = (args->pge->pos_x + 8) >> 4;
grid_data += pge_pos_y * 16 + pge_pos_x;
CollisionSlot2 *_di = _col_slots2Next;
int _cx = 0x100;
while (_di && _cx != 0) {
if (_di->unk2 != var4) {
int count = 256; // ARRAYSIZE(_col_slots2)
while (_di && count != 0) {
if (_di->unk2 != grid_data) {
_di = _di->next_slot;
--_cx;
--count;
} else {
memcpy(_di->unk2, _di->data_buf, _di->data_size + 1);
break;
}
}
return 0xFFFF; // XXX var4;
// original returns the pointer to ctData
return 0xFFFF;
}
int Game::pge_o_unk0x73(ObjectOpcodeArgs *args) {
@ -1686,7 +1691,7 @@ int Game::pge_op_isBelowConrad(ObjectOpcodeArgs *args) {
if ((pge_conrad->pos_y - 8) / 72 < _si->pos_y / 72) {
return 0xFFFF;
}
} else if (!(_si->room_location & 0x80) && _si->room_location < 0x40) {
} else if (_si->room_location < 0x40) {
if (pge_conrad->room_location == _res._ctData[CT_UP_ROOM + _si->room_location]) {
return 0xFFFF;
}
@ -1701,7 +1706,7 @@ int Game::pge_op_isAboveConrad(ObjectOpcodeArgs *args) {
if ((pge_conrad->pos_y - 8) / 72 > _si->pos_y / 72) {
return 0xFFFF;
}
} else if (!(_si->room_location & 0x80) && _si->room_location < 0x40) {
} else if (_si->room_location < 0x40) {
if (pge_conrad->room_location == _res._ctData[CT_DOWN_ROOM + _si->room_location]) {
return 0xFFFF;
}
@ -1736,7 +1741,7 @@ int Game::pge_op_isNotFacingConrad(ObjectOpcodeArgs *args) {
}
}
} else if (args->a == 0) {
if (!(pge->room_location & 0x80) && pge->room_location < 0x40) {
if (pge->room_location < 0x40) {
if (_pge_currentPiegeFacingDir) {
if (pge_conrad->room_location == _res._ctData[CT_RIGHT_ROOM + pge->room_location])
return 0xFFFF;
@ -1777,7 +1782,7 @@ int Game::pge_op_isFacingConrad(ObjectOpcodeArgs *args) {
}
}
} else if (args->a == 0) {
if (!(pge->room_location & 0x80) && pge->room_location < 0x40) {
if (pge->room_location < 0x40) {
if (_pge_currentPiegeFacingDir) {
if (pge_conrad->room_location == _res._ctData[CT_LEFT_ROOM + pge->room_location])
return 0xFFFF;
@ -1843,7 +1848,7 @@ int Game::pge_o_unk0x7F(ObjectOpcodeArgs *args) {
CollisionSlot *slot = _col_slotsTable[var4];
while (slot) {
if (slot->live_pge != args->pge) {
if (slot->live_pge->init_PGE->object_type == 3 && var2 != slot->live_pge->unkF) {
if (slot->live_pge->init_PGE->object_type == 3 && var2 != slot->live_pge->ref_inventory_PGE) {
return 0;
}
}
@ -1857,7 +1862,7 @@ int Game::pge_o_unk0x7F(ObjectOpcodeArgs *args) {
}
int Game::pge_op_setPiegePosX(ObjectOpcodeArgs *args) {
uint8_t pge_num = args->pge->unkF;
uint8_t pge_num = args->pge->ref_inventory_PGE;
if (pge_num != 0xFF) {
args->pge->pos_x = _pgeLive[pge_num].pos_x;
}
@ -1865,7 +1870,7 @@ int Game::pge_op_setPiegePosX(ObjectOpcodeArgs *args) {
}
int Game::pge_op_setPiegePosModX(ObjectOpcodeArgs *args) {
uint8_t pge_num = args->pge->unkF;
uint8_t pge_num = args->pge->ref_inventory_PGE;
if (pge_num != 0xFF) {
int16_t dx = _pgeLive[pge_num].pos_x % 256;
if (dx >= args->pge->pos_x) {
@ -1878,30 +1883,27 @@ int Game::pge_op_setPiegePosModX(ObjectOpcodeArgs *args) {
// taxi and teleporter
int Game::pge_op_changeRoom(ObjectOpcodeArgs *args) {
// pge_op_protectionScreen
InitPGE *init_pge_1 = args->pge->init_PGE;
assert(args->a >= 0 && args->a < 3);
const int16_t _ax = init_pge_1->counter_values[args->a];
if (_ax == 0 && !g_options.bypass_protection && !g_options.use_words_protection && !_res.isMac()) {
const int16_t _ax = init_pge_1->data[args->a];
if (_ax == 0 && !g_options.bypass_protection && !g_options.use_words_protection && (_res.isAmiga() || _res.isDOS())) {
if (!handleProtectionScreenShape()) {
warning("Game::pge_op_changeRoom() protection check failed");
// when protection check fails, the changeRoom opcode is disabled,
// rendering the teleporter unusable.
//
// _pge_opcodeTable[0x82] = &Game::pge_op_nop;
// _pge_opTempVar1 = 0xFFFF;
// _pge_opGunVar = 0;
// return;
}
}
const int16_t _bx = init_pge_1->counter_values[args->a + 1];
// pge_op_changeRoom
const int16_t _bx = init_pge_1->data[args->a + 1];
LivePGE *live_pge_1 = &_pgeLive[_bx];
LivePGE *live_pge_2 = &_pgeLive[_ax];
int8_t pge_room = live_pge_1->room_location;
if (pge_room >= 0 && pge_room < 0x40) {
int8_t _al = live_pge_2->room_location;
const int8_t room = live_pge_2->room_location;
live_pge_2->pos_x = live_pge_1->pos_x;
live_pge_2->pos_y = live_pge_1->pos_y;
live_pge_2->room_location = live_pge_1->room_location;
pge_addToCurrentRoomList(live_pge_2, _al);
pge_addToCurrentRoomList(live_pge_2, room);
InitPGE *init_pge_2 = live_pge_2->init_PGE;
init_pge_1 = live_pge_1->init_PGE;
if (init_pge_2->obj_node_number == init_pge_1->obj_node_number) {
@ -1963,7 +1965,7 @@ int Game::pge_o_unk0x86(ObjectOpcodeArgs *args) {
int Game::pge_op_playSoundGroup(ObjectOpcodeArgs *args) {
assert(args->a < 4);
uint16_t c = args->pge->init_PGE->counter_values[args->a];
uint16_t c = args->pge->init_PGE->data[args->a];
uint8_t sfxId = c & 0xFF;
uint8_t softVol = c >> 8;
playSound(sfxId, softVol);
@ -1979,13 +1981,13 @@ int Game::pge_op_adjustPos(ObjectOpcodeArgs *args) {
return 0xFFFF;
}
int Game::pge_op_setTempVar1(ObjectOpcodeArgs *args) {
_pge_opTempVar1 = args->a;
int Game::pge_op_setGunVar(ObjectOpcodeArgs *args) {
_pge_opGunVar = args->a;
return 0xFFFF;
}
int Game::pge_op_isTempVar1Set(ObjectOpcodeArgs *args) {
if (_pge_opTempVar1 != args->a) {
int Game::pge_op_compareGunVar(ObjectOpcodeArgs *args) {
if (_pge_opGunVar != args->a) {
return 0;
} else {
return 0xFFFF;
@ -1993,7 +1995,7 @@ int Game::pge_op_isTempVar1Set(ObjectOpcodeArgs *args) {
}
int Game::pge_setCurrentInventoryObject(LivePGE *pge) {
LivePGE *_bx = pge_getInventoryItemBefore(&_pgeLive[0], pge);
LivePGE *_bx = pge_getPreviousInventoryItem(&_pgeLive[0], pge);
if (_bx == &_pgeLive[0]) {
if (_bx->current_inventory_PGE != pge->index) {
return 0;
@ -2009,17 +2011,17 @@ int Game::pge_setCurrentInventoryObject(LivePGE *pge) {
}
void Game::pge_updateInventory(LivePGE *pge1, LivePGE *pge2) {
if (pge2->unkF != 0xFF) {
if (pge2->ref_inventory_PGE != 0xFF) {
pge_reorderInventory(pge2);
}
LivePGE *_ax = pge_getInventoryItemBefore(pge1, 0);
LivePGE *_ax = pge_getPreviousInventoryItem(pge1, 0);
pge_addToInventory(_ax, pge2, pge1);
}
void Game::pge_reorderInventory(LivePGE *pge) {
if (pge->unkF != 0xFF) {
LivePGE *_bx = &_pgeLive[pge->unkF];
LivePGE *_di = pge_getInventoryItemBefore(_bx, pge);
if (pge->ref_inventory_PGE != 0xFF) {
LivePGE *_bx = &_pgeLive[pge->ref_inventory_PGE];
LivePGE *_di = pge_getPreviousInventoryItem(_bx, pge);
if (_di == _bx) {
if (_di->current_inventory_PGE == pge->index) {
pge_removeFromInventory(_di, pge, _bx);
@ -2032,7 +2034,7 @@ void Game::pge_reorderInventory(LivePGE *pge) {
}
}
LivePGE *Game::pge_getInventoryItemBefore(LivePGE *pge, LivePGE *last_pge) {
LivePGE *Game::pge_getPreviousInventoryItem(LivePGE *pge, LivePGE *last_pge) {
LivePGE *_di = pge;
uint8_t n = _di->current_inventory_PGE;
while (n != 0xFF) {
@ -2048,7 +2050,7 @@ LivePGE *Game::pge_getInventoryItemBefore(LivePGE *pge, LivePGE *last_pge) {
}
void Game::pge_addToInventory(LivePGE *pge1, LivePGE *pge2, LivePGE *pge3) {
pge2->unkF = pge3->index;
pge2->ref_inventory_PGE = pge3->index;
if (pge1 == pge3) {
pge2->next_inventory_PGE = pge1->current_inventory_PGE;
pge1->current_inventory_PGE = pge2->index;
@ -2058,9 +2060,9 @@ void Game::pge_addToInventory(LivePGE *pge1, LivePGE *pge2, LivePGE *pge3) {
}
}
int Game::pge_updateCollisionState(LivePGE *pge, int16_t pge_dy, uint8_t var8) {
uint8_t pge_unk1C = pge->init_PGE->unk1C;
if (!(pge->room_location & 0x80) && pge->room_location < 0x40) {
int Game::pge_updateCollisionState(LivePGE *pge, int16_t pge_dy, uint8_t value) {
const uint8_t pge_collision_data_len = pge->init_PGE->collision_data_len;
if (pge->room_location < 0x40) {
int8_t *grid_data = &_res._ctData[0x100] + 0x70 * pge->room_location;
int16_t pge_pos_y = ((pge->pos_y / 36) & ~1) + pge_dy;
int16_t pge_pos_x = (pge->pos_x + 8) >> 4;
@ -2070,17 +2072,31 @@ int Game::pge_updateCollisionState(LivePGE *pge, int16_t pge_dy, uint8_t var8) {
CollisionSlot2 *slot1 = _col_slots2Next;
int16_t i = 255;
if (_pge_currentPiegeFacingDir) {
i = pge_unk1C - 1;
i = pge_collision_data_len - 1;
grid_data -= i;
}
while (slot1) {
if (slot1->unk2 == grid_data) {
slot1->data_size = pge_unk1C - 1;
assert(pge_unk1C < 0x70);
memset(grid_data, var8, pge_unk1C);
slot1->data_size = pge_collision_data_len - 1;
assert(pge_collision_data_len < 0x70);
memset(grid_data, value, pge_collision_data_len);
return 1;
} else {
// the increment looks wrong but matches the DOS disassembly
//
// seg000:667B inc cx
// seg000:667C mov si, bx
// seg000:667E mov bx, [bx+t_collision_slot2.next_slot]
// seg000:6680 loop loc_0_1665B
//
// interestingly Amiga does not have it
//
// CODE:000042BA movea.l a4,a5
// CODE:000042BC movea.l 0(a4),a4
// CODE:000042C0 dbf d0,loc_4290
++i;
slot1 = slot1->next_slot;
if (--i == 0) {
break;
@ -2090,14 +2106,14 @@ int Game::pge_updateCollisionState(LivePGE *pge, int16_t pge_dy, uint8_t var8) {
if (_col_slots2Cur < &_col_slots2[255]) {
slot1 = _col_slots2Cur;
slot1->unk2 = grid_data;
slot1->data_size = pge_unk1C - 1;
slot1->data_size = pge_collision_data_len - 1;
uint8_t *dst = &slot1->data_buf[0];
int8_t *src = grid_data;
int n = pge_unk1C;
int n = pge_collision_data_len;
assert(n < 0x10);
while (n--) {
*dst++ = *src;
*src++ = var8;
*src++ = value;
}
++_col_slots2Cur;
slot1->next_slot = _col_slots2Next;
@ -2148,10 +2164,16 @@ void Game::pge_sendMessage(uint8_t src_pge_index, uint8_t dst_pge_index, int16_t
if (pge_room != pge->room_location) {
return;
}
if (dst_pge_index == 0 && _blinkingConradCounter != 0) {
if (dst_pge_index == 0 && (_blinkingConradCounter != 0 || (_cheats & kCheatNoHit) != 0)) {
return;
}
// XXX
if (_stub->_pi.dbgMask & PlayerInput::DF_AUTOZOOM) {
const int type = _pgeLive[dst_pge_index].init_PGE->object_type;
if (type == 1 || type == 10) {
_pge_zoomPiegeNum = dst_pge_index;
_pge_zoomCounter = 0;
}
}
}
MessagePGE *le = _pge_nextFreeMessage;
if (le) {
@ -2160,13 +2182,13 @@ void Game::pge_sendMessage(uint8_t src_pge_index, uint8_t dst_pge_index, int16_t
MessagePGE *next = _pge_messagesTable[dst_pge_index];
_pge_messagesTable[dst_pge_index] = le;
le->next_entry = next;
le->index = src_pge_index;
le->src_pge = src_pge_index;
le->msg_num = num;
}
}
void Game::pge_removeFromInventory(LivePGE *pge1, LivePGE *pge2, LivePGE *pge3) {
pge2->unkF = 0xFF;
pge2->ref_inventory_PGE = 0xFF;
if (pge3 == pge1) {
pge3->current_inventory_PGE = pge2->next_inventory_PGE;
pge2->next_inventory_PGE = 0xFF;
@ -2270,3 +2292,69 @@ int Game::pge_ZOrderIfTypeAndDifferentDirection(LivePGE *pge1, LivePGE *pge2, ui
int Game::pge_ZOrderByNumber(LivePGE *pge1, LivePGE *pge2, uint8_t comp, uint8_t comp2) {
return pge1 - pge2;
}
static int pge_zoomDx(int prev_x, int cur_x) {
int dx = ABS(cur_x - prev_x);
if (dx < 4) {
dx = 1;
} else if (dx < 8) {
dx = 2;
} else if (dx < 16) {
dx = 4;
} else {
dx = 8;
}
return (prev_x < cur_x) ? dx : -dx;
}
static int pge_zoomDy(int prev_y, int cur_y, bool flag) {
int dy = ABS(cur_y - prev_y);
if (flag) {
if (dy < 2) {
return 0;
}
} else {
if (dy < 4) {
return 0;
}
}
if (dy < 8) {
dy = 2;
} else if (dy < 16) {
dy = 4;
} else {
dy = 8;
}
return (prev_y < cur_y) ? dy : -dy;
}
void Game::pge_updateZoom() {
static const int kZoomW = Video::GAMESCREEN_W / 2;
static const int kZoomH = Video::GAMESCREEN_H / 2;
if (_pge_zoomPiegeNum != 0) {
LivePGE *pge = &_pgeLive[_pge_zoomPiegeNum];
if (pge->room_location != _currentRoom) {
_pge_zoomPiegeNum = 0;
} else if (_pge_zoomCounter < 30) {
int x = pge->pos_x + ((_pgeLive[0].flags & 1) ? 22 - kZoomW : -12);
x = CLIP(x, 0, Video::GAMESCREEN_W - kZoomW);
if (_pge_zoomCounter != 0 && _pge_zoomX != x) {
const int dx = pge_zoomDx(_pge_zoomX, x);
x = _pge_zoomX + dx;
}
_pge_zoomX = x;
int y = pge->pos_y - 24 - kZoomH / 2;
y = CLIP(y, 0, Video::GAMESCREEN_H - kZoomH);
if (_pge_zoomCounter != 0 && _pge_zoomY != y) {
const int dy = pge_zoomDy(_pge_zoomY, y, (pge->ref_inventory_PGE != 0xFF));
y = _pge_zoomY + dy;
}
_pge_zoomY = y;
_stub->zoomRect(x, y, kZoomW, kZoomH);
}
++_pge_zoomCounter;
if (_pge_zoomCounter == 40) {
_pge_zoomPiegeNum = 0;
}
}
}

414
prf_player.cpp Normal file
View File

@ -0,0 +1,414 @@
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include "file.h"
#ifdef _WIN32
#define MIDI_DRIVER_SYMBOL __declspec(dllimport)
#else
#define MIDI_DRIVER_SYMBOL
#endif
#include "midi_driver.h"
#include "midi_parser.h"
#include "mixer.h"
#include "prf_player.h"
#include "util.h"
static const struct {
int mode;
int hz;
const MidiDriverInfo *info;
} _midiDrivers[] = {
#ifdef USE_MIDI_DRIVER
{ MODE_ADLIB, TIMER_ADLIB_HZ, &midi_driver_adlib },
{ MODE_MT32, TIMER_MT32_HZ, &midi_driver_mt32 },
#endif
{ -1, 0, 0 }
};
static const int kMusicVolume = 63;
PrfPlayer::PrfPlayer(Mixer *mix, FileSystem *fs, int mode)
: _playing(false), _mixer(mix), _fs(fs), _mode(mode), _driver(0) {
for (int i = 0; _midiDrivers[i].info; ++i) {
if (_midiDrivers[i].mode == mode) {
_timerHz = _midiDrivers[i].hz;
_driver = _midiDrivers[i].info->create();
assert(_driver);
if (_driver->init() < 0) {
warning("Failed to initialize MIDI driver %s", _midiDrivers[i].info->name);
_driver = 0;
}
return;
}
}
fprintf(stdout, "WARNING: no midi driver for mode %d", mode);
}
PrfPlayer::~PrfPlayer() {
if (_driver) {
_driver->fini();
_driver = 0;
}
}
void PrfPlayer::play(int num) {
_playing = false;
memset(&_prfData, 0, sizeof(_prfData));
memset(&_tracks, 0, sizeof(_tracks));
if (num < _namesCount) {
char name[64];
snprintf(name, sizeof(name), "%s.prf", (num == 1 && _mode == MODE_MT32) ? "opt" : _names[num]);
File f;
if (f.open(name, "rb", _fs)) {
loadPrf(&f);
if (_mode == MODE_ADLIB) {
for (int i = 0; i < 16; ++i) {
memset(_adlibInstrumentData[i], 0, ADLIB_INSTRUMENT_DATA_LEN);
if (_prfData.instruments[i][0]) {
snprintf(name, sizeof(name), "%s.INS", _prfData.instruments[i]);
if (f.open(name, "rb", _fs)) {
loadIns(&f, i);
} else {
warning("Unable to open '%s'", name);
}
}
}
}
if (f.open(_prfData.midi, "rb", _fs)) {
_parser.loadMid(&f);
play();
}
}
}
}
void PrfPlayer::loadPrf(File *f) {
for (int i = 0; i < 16; ++i) {
f->read(_prfData.instruments[i], INSTRUMENT_NAME_LEN);
const int len = strlen(_prfData.instruments[i]);
if (len > 8) {
_prfData.instruments[i][8] = 0;
debug(DBG_PRF, "Truncating instrument name to '%s'", _prfData.instruments[i]);
}
}
for (int i = 0; i < 16; ++i) {
_prfData.adlibNotes[i] = f->readUint16LE();
}
for (int i = 0; i < 16; ++i) {
_prfData.adlibVelocities[i] = f->readUint16LE();
}
_prfData.timerTicks = f->readUint32LE();
_prfData.timerMod = f->readUint16LE();
f->read(_prfData.midi, MIDI_FILENAME_LEN);
_prfData.adlibDoNotesLookup = f->readUint16LE();
for (int i = 0; i < 16; ++i) {
_prfData.adlibPrograms[i] = f->readUint16LE();
}
for (int i = 0; i < 16; ++i) {
_prfData.mt32Programs[i] = f->readUint16LE();
}
for (int i = 0; i < 16; ++i) {
_prfData.mt32Velocities[i] = f->readUint16LE();
}
for (int i = 0; i < 16; ++i) {
_prfData.mt32Notes[i] = f->readUint16LE();
}
for (int i = 0; i < 16; ++i) {
_tracks[i].hw_channel_num = f->readByte();
}
for (int i = 0; i < 16; ++i) {
_tracks[i].mt32_program_num = f->readByte();
}
for (int i = 0; i < 16; ++i) {
_tracks[i].loop_flag = f->readByte();
}
_prfData.totalDurationTicks = f->readUint32LE();
_prfData.mt32DoChannelsLookup = f->readByte();
assert(f->tell() == 0x2F1);
debug(DBG_PRF, "duration %d timer %d", _prfData.totalDurationTicks, _prfData.timerTicks);
}
void PrfPlayer::loadIns(File *f, int i) {
uint8_t *p = _adlibInstrumentData[i];
p[0] = f->readByte();
p[1] = f->readByte();
f->read(&p[6], 26 * 2);
f->seek(54 + 20);
p[2] = f->readByte();
f->readByte();
p[3] = f->readByte();
f->readByte();
if (p[2] != 0 || p[3] != 0) {
debug(DBG_PRF, "Wave Select Register 0x%02x 0x%02x was ignored", p[2], p[3]);
}
p[4] = 0;
p[5] = 0;
const int unk = f->readUint16LE();
assert(unk == 1);
assert(f->tell() == 80);
}
void PrfPlayer::play() {
if (!_driver) {
return;
}
_driver->reset(_mixer->getSampleRate());
for (int i = 0; i < _parser._tracksCount; ++i) {
_tracks[i].instrument_num = i;
if (_mode == MODE_MT32) {
mt32ProgramChange(i, _tracks[i].mt32_program_num);
mt32ControlChangeResetRPN(i);
}
}
for (int i = 0; i < 16; ++i) {
_tracks[i].instrument_num = i;
}
for (int i = 0; i < _parser._tracksCount; ++i) {
PrfTrack *current_track = &_tracks[i];
MidiTrack *track = &_parser._tracks[i];
track->rewind();
const MidiEvent *ev = track->nextEvent();
current_track->counter = ev ? ev->timestamp : 0;
current_track->counter2 = current_track->counter % _prfData.timerMod;
}
_timerTick = _musicTick = 0;
_samplesLeft = 0;
_samplesPerTick = _mixer->getSampleRate() / _timerHz;
_playing = true;
_mixer->setPremixHook(mixCallback, this);
}
void PrfPlayer::stop() {
if (_playing) {
_mixer->setPremixHook(0, 0);
_playing = false;
}
}
void PrfPlayer::mt32NoteOn(int track, int note, int velocity) {
if (_prfData.mt32DoChannelsLookup != 0 && _tracks[track].hw_channel_num == 9) {
note = _prfData.mt32Notes[_tracks[track].instrument_num];
}
_driver->noteOn(_tracks[track].hw_channel_num, note, (velocity * kMusicVolume) >> 6);
}
void PrfPlayer::mt32NoteOff(int track, int note, int velocity) {
if (_prfData.mt32DoChannelsLookup != 0 && _tracks[track].hw_channel_num == 9) {
note = _prfData.mt32Notes[_tracks[track].instrument_num];
}
_driver->noteOff(_tracks[track].hw_channel_num, note, (velocity * kMusicVolume) >> 6);
}
void PrfPlayer::mt32ProgramChange(int track, int num) {
_driver->programChange(_tracks[track].hw_channel_num, num);
}
void PrfPlayer::mt32PitchBend(int track, int lsb, int msb) {
_driver->pitchBend(_tracks[track].hw_channel_num, lsb, msb);
}
static const uint8_t MIDI_CONTROLLER_RPN_LSB = 0x64;
static const uint8_t MIDI_CONTROLLER_RPN_MSB = 0x65;
static const uint8_t MIDI_CONTROLLER_DATA_ENTRY_LSB = 0x26;
static const uint8_t MIDI_CONTROLLER_DATA_ENTRY_MSB = 0x06;
void PrfPlayer::mt32ControlChangeResetRPN(int track) {
_driver->controlChange(_tracks[track].hw_channel_num, MIDI_CONTROLLER_RPN_MSB, 0);
_driver->controlChange(_tracks[track].hw_channel_num, MIDI_CONTROLLER_RPN_LSB, 0);
_driver->controlChange(_tracks[track].hw_channel_num, MIDI_CONTROLLER_DATA_ENTRY_MSB, 1);
_driver->controlChange(_tracks[track].hw_channel_num, MIDI_CONTROLLER_DATA_ENTRY_LSB, 0);
}
void PrfPlayer::adlibNoteOn(int track, int note, int velocity) {
const int instrument_num = _tracks[track].instrument_num;
_driver->setInstrumentData(track, instrument_num, _adlibInstrumentData[instrument_num]);
_driver->noteOn(track, note, velocity);
}
void PrfPlayer::adlibNoteOff(int track, int note, int velocity) {
const int instrument_num = _tracks[track].instrument_num;
_driver->setInstrumentData(track, instrument_num, _adlibInstrumentData[instrument_num]);
_driver->noteOff(track, note, velocity);
}
void PrfPlayer::handleTick() {
for (int i = 0; i < _parser._tracksCount; ++i) {
MidiTrack *track = &_parser._tracks[i];
if (track->endOfTrack) {
continue;
}
const int track_index = i;
PrfTrack *current_track = &_tracks[i];
if (current_track->counter != 0) {
--current_track->counter;
continue;
}
while (1) {
const MidiEvent &ev = track->event;
switch (ev.command & 0xF0) {
case MIDI_COMMAND_NOTE_OFF: {
int note = ev.param1;
int velocity = ev.param2;
if (_mode == MODE_MT32) {
note += _prfData.mt32Notes[current_track->instrument_num];
velocity += _prfData.mt32Velocities[current_track->instrument_num];
if (velocity < 0) {
velocity = 0;
} else if (velocity > 127) {
velocity = 127;
}
mt32NoteOff(track_index, note, velocity);
} else {
if (_prfData.adlibDoNotesLookup) {
note += _prfData.adlibNotes[current_track->instrument_num];
} else {
note += _prfData.adlibNotes[track_index];
}
if (_prfData.adlibDoNotesLookup) {
velocity += _prfData.adlibVelocities[current_track->instrument_num];
} else {
velocity += _prfData.adlibVelocities[track_index];
}
if (velocity < 0) {
velocity = 0;
} else if (velocity > 127) {
velocity = 127;
}
adlibNoteOff(track_index, note, velocity);
}
}
break;
case MIDI_COMMAND_NOTE_ON: {
int note = ev.param1;
int velocity = ev.param2;
if (_mode == MODE_MT32) {
note += _prfData.mt32Notes[current_track->instrument_num];
if (velocity == 0) {
mt32NoteOff(track_index, note, velocity);
break;
}
velocity += _prfData.mt32Velocities[current_track->instrument_num];
if (velocity < 0) {
velocity = 0;
} else if (velocity > 127) {
velocity = 127;
}
mt32NoteOn(track_index, note, velocity);
} else {
assert(_mode == MODE_ADLIB);
if (_prfData.adlibDoNotesLookup) {
note += _prfData.adlibNotes[current_track->instrument_num];
} else {
note += _prfData.adlibNotes[track_index];
}
if (velocity == 0) {
adlibNoteOff(track_index, note, velocity);
break;
}
if (_prfData.adlibDoNotesLookup) {
velocity += _prfData.adlibVelocities[current_track->instrument_num];
} else {
velocity += _prfData.adlibVelocities[track_index];
}
if (velocity < 0) {
velocity = 0;
} else if (velocity > 127) {
velocity = 127;
}
adlibNoteOn(track_index, note, velocity);
}
}
break;
case MIDI_COMMAND_PROGRAM_CHANGE: {
if (_mode == MODE_MT32) {
for (int num = 0; num < 16; ++num) {
if (_prfData.mt32Programs[num] == ev.param1) {
mt32ProgramChange(track_index, _tracks[num].mt32_program_num);
break;
}
}
} else {
assert(_mode == MODE_ADLIB);
if (_prfData.adlibDoNotesLookup) {
for (int num = 0; num < 16; ++num) {
if (_prfData.adlibPrograms[num] == ev.param1) {
current_track->instrument_num = num;
break;
}
}
}
}
}
break;
case MIDI_COMMAND_CHANNEL_PRESSURE:
break;
case MIDI_COMMAND_PITCH_BEND: {
if (_mode == MODE_MT32) {
mt32PitchBend(track_index, ev.param1, ev.param2);
} else {
assert(_mode == MODE_ADLIB);
_driver->pitchBend(track_index, ev.param1, ev.param2);
}
}
break;
default:
warning("Unhandled MIDI event command 0x%x", ev.command);
break;
}
const MidiEvent *next = track->nextEvent();
if (!next) {
if (current_track->loop_flag == 0) {
track->rewind();
const MidiEvent *next = track->nextEvent();
if (next) {
current_track->counter = _prfData.timerMod - current_track->counter2 + next->timestamp - 1;
current_track->counter2 = next->timestamp % _prfData.timerMod;
}
}
break;
}
current_track->counter = next->timestamp;
current_track->counter2 = (current_track->counter + current_track->counter2) % _prfData.timerMod;
if (current_track->counter != 0) {
--current_track->counter;
break;
}
}
}
}
bool PrfPlayer::end() const {
return (_prfData.totalDurationTicks != 0) && _musicTick > _prfData.totalDurationTicks;
}
int PrfPlayer::readSamples(int16_t *samples, int count) {
const int total = count;
while (count != 0) {
if (_samplesLeft == 0) {
handleTick();
if (_prfData.totalDurationTicks != 0) {
++_musicTick;
if (_musicTick == _prfData.totalDurationTicks + 1) {
debug(DBG_PRF, "End of music");
}
}
_samplesLeft = _samplesPerTick * _prfData.timerTicks;
}
const int len = (count < _samplesLeft) ? count : _samplesLeft;
_driver->readSamples(samples, len);
_samplesLeft -= len;
count -= len;
samples += len * 2;
}
return total - count;
}
bool PrfPlayer::mixCallback(void *param, int16_t *buf, int len) {
return ((PrfPlayer *)param)->mix(buf, len);
}
bool PrfPlayer::mix(int16_t *buf, int len) {
const int count = readSamples(buf, len);
return count != 0;
}

122
prf_player.h Normal file
View File

@ -0,0 +1,122 @@
#ifndef PRF_PLAYER_H__
#define PRF_PLAYER_H__
#include <stdint.h>
#include "midi_parser.h"
static const int TIMER_ADLIB_HZ = 2082;
static const int TIMER_MT32_HZ = 2242;
struct MidiDriver;
#define INSTRUMENT_NAME_LEN 30
#define MIDI_FILENAME_LEN 20
struct PrfData {
char instruments[16][INSTRUMENT_NAME_LEN];
int16_t adlibNotes[16];
int16_t adlibVelocities[16];
uint32_t timerTicks;
uint16_t timerMod;
char midi[MIDI_FILENAME_LEN];
uint16_t adlibDoNotesLookup;
int16_t adlibPrograms[16];
int16_t mt32Programs[16];
int16_t mt32Velocities[16];
int16_t mt32Notes[16];
uint32_t totalDurationTicks;
uint8_t mt32DoChannelsLookup;
};
struct PrfTrack {
uint8_t instrument_num;
uint32_t counter;
uint32_t counter2;
uint8_t hw_channel_num;
uint8_t mt32_program_num;
uint8_t loop_flag;
};
#define ADLIB_INSTRUMENT_DATA_LEN 58
struct AdlibInstrument {
uint8_t mode;
uint8_t channel_num;
uint8_t modulator_wave_select;
uint8_t carrier_wave_select;
uint8_t unk4;
uint8_t unk5;
struct {
uint16_t key_scaling; // 0
uint16_t frequency_multiplier; // 2
uint16_t feedback_strength; // 4
uint16_t attack_rate; // 6
uint16_t sustain_level; // 8
uint16_t sustain_sound; // 10
uint16_t decay_rate; // 12
uint16_t release_rate; // 14
uint16_t output_level; // 16
uint16_t amplitude_vibrato; // 18
uint16_t frequency_vibrato; // 20
uint16_t envelope_scaling; // 22
uint16_t frequency_modulation; // 24
} op[2]; /* modulator, carrier */
};
enum {
MODE_ADLIB,
MODE_MT32,
};
struct File;
struct FileSystem;
struct Mixer;
struct PrfPlayer {
static const char *_names[];
static const int _namesCount;
PrfPlayer(Mixer *mix, FileSystem *fs, int mode);
~PrfPlayer();
void play(int num);
void loadPrf(File *f);
void loadIns(File *f, int num);
void play();
void stop();
void mt32NoteOn(int track, int note, int velocity);
void mt32NoteOff(int track, int note, int velocity);
void mt32ProgramChange(int track, int num);
void mt32PitchBend(int track, int lsb, int msb);
void mt32ControlChangeResetRPN(int track);
void adlibNoteOn(int track, int note, int velocity);
void adlibNoteOff(int track, int note, int velocity);
void handleTick();
bool end() const;
int readSamples(int16_t *samples, int count);
static bool mixCallback(void *param, int16_t *buf, int len);
bool mix(int16_t *buf, int len);
bool _playing;
Mixer *_mixer;
FileSystem *_fs;
int _mode;
int _timerHz;
PrfTrack _tracks[16];
MidiDriver *_driver;
MidiParser _parser;
PrfData _prfData;
int _samplesLeft, _samplesPerTick;
uint32_t _timerTick, _musicTick;
uint8_t _adlibInstrumentData[16][ADLIB_INSTRUMENT_DATA_LEN];
};
#endif /* PRF_PLAYER_H__ */

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,45 @@
# REminiscence for AmigaOS 4.1 FE
REminiscence is a re-implementation of the engine used in the game Flashback
made by Delphine Software and released in 1992. It is developed
by Gregory Montoir and you can find it on GitHub at
https://github.com/cyxx/REminiscence
The icon is based on cedry2kio work as found at
https://www.deviantart.com/cedry2kio/art/Flashback-Icon-1-404447315
## Installation
Extract the archive wherever you want. In the REminiscence there is the
*data* folder where you need to copy the data files for the game. The
*saves* folder is where tha saves files will be stored.
Please read the README.txt file for more information about where to
find the data files.
## I would like to thank
- Gregory Montoir for developing and open sourcing REminiscence
- Capehill for his tireless work on SDL port for AmigaOS 4.1 FE
## Known issues
None yet. If you find any issue please let me know.
## Support
If you enjoy what I am doing and would like to keep me up during the night,
please consider to buy me a coffee at:
https://ko-fi.com/walkero
## Known issues
You can find the known issues at
https://git.walkero.gr/walkero/breakhack/issues
# Changelog
## [0.5.1r1] - 2023-09-17
### Added
- First release for AmigaOS 4

Binary file not shown.

View File

@ -0,0 +1,14 @@
stack 50000
Set title "REminiscence"
Set rcnum `RequestChoice "$title" "Run in window or fullscreen?" "Window" "Fullscreen" "Oh, forget it"`
If $rcnum EQ 1
rs --scaler scale@3
ENDIF
If $rcnum EQ 2
rs --fullscreen
ENDIF

Binary file not shown.

View File

@ -56,16 +56,27 @@ Resource::~Resource() {
delete _mac;
}
static const char *_demoAba = "DEMO_UK.ABA";
static const char *_joystickAba[] = {
"GLOB1_FB.ABA", "GLOB2_FB.ABA", "GLOB_FR.ABA", 0
};
void Resource::init() {
switch (_type) {
case kResourceTypeAmiga:
_isDemo = _fs->exists("demo.lev");
break;
case kResourceTypeDOS:
if (_fs->exists(ResourceAba::FILENAME)) { // fbdemous
if (_fs->exists(_demoAba)) { // fbdemous
_aba = new ResourceAba(_fs);
_aba->readEntries();
_aba->readEntries(_demoAba);
_isDemo = true;
} else if (_fs->exists(_joystickAba[0])) { // Joystick "Hors Serie" April 1996
_aba = new ResourceAba(_fs);
for (int i = 0; _joystickAba[i]; ++i) {
_aba->readEntries(_joystickAba[i]);
}
} else if (!fileExists("LEVEL2.MAP")) { // fbdemofr (no cutscenes)
_isDemo = true;
}
@ -403,7 +414,7 @@ void Resource::load_CINE() {
case kResourceTypeDOS:
if (_cine_off == 0) {
snprintf(_entryName, sizeof(_entryName), "%sCINE.BIN", prefix);
if (!_fs->exists(_entryName)) {
if (!fileExists(_entryName)) {
strcpy(_entryName, "ENGCINE.BIN");
}
File f;
@ -428,7 +439,7 @@ void Resource::load_CINE() {
}
if (_cine_txt == 0) {
snprintf(_entryName, sizeof(_entryName), "%sCINE.TXT", prefix);
if (!_fs->exists(_entryName)) {
if (!fileExists(_entryName)) {
strcpy(_entryName, "ENGCINE.TXT");
}
File f;
@ -699,7 +710,7 @@ void Resource::load(const char *objName, int objType, const char *ext) {
_numSpc = READ_BE_UINT16(_spc) / 2;
break;
case OT_RP:
if (size != 0x4A) {
if (size != sizeof(_rp)) {
error("Unexpected size %d for '%s'", size, _entryName);
}
memcpy(_rp, dat, size);
@ -728,6 +739,10 @@ void Resource::load(const char *objName, int objType, const char *ext) {
case OT_POL:
_pol = dat;
break;
case OT_SGD:
_sgd = dat;
_sgd[0] = 0; // clear number of entries, fix first offset
break;
case OT_BNQ:
_bnq = dat;
break;
@ -825,7 +840,7 @@ void Resource::load_SPRM(File *f) {
void Resource::load_RP(File *f) {
debug(DBG_RES, "Resource::load_RP()");
f->read(_rp, 0x4A);
f->read(_rp, sizeof(_rp));
}
void Resource::load_SPC(File *f) {
@ -902,9 +917,9 @@ void Resource::load_OBJ(File *f) {
error("Unable to allocate ObjectNode num=%d", i);
}
f->seek(offsets[i] + 2);
on->last_obj_number = f->readUint16LE();
on->num_objects = objectsCount[iObj];
debug(DBG_RES, "last=%d num=%d", on->last_obj_number, on->num_objects);
on->num_objects = f->readUint16LE();
debug(DBG_RES, "count=%d", on->num_objects, objectsCount[iObj]);
assert(on->num_objects == objectsCount[iObj]);
on->objects = (Object *)malloc(sizeof(Object) * on->num_objects);
for (int j = 0; j < on->num_objects; ++j) {
Object *obj = &on->objects[j];
@ -997,8 +1012,8 @@ void Resource::decodeOBJ(const uint8_t *tmp, int size) {
error("Unable to allocate ObjectNode num=%d", i);
}
const uint8_t *objData = tmp + offsets[i];
on->last_obj_number = _readUint16(objData); objData += 2;
on->num_objects = objectsCount[iObj];
on->num_objects = _readUint16(objData); objData += 2;
assert(on->num_objects == objectsCount[iObj]);
on->objects = (Object *)malloc(sizeof(Object) * on->num_objects);
for (int j = 0; j < on->num_objects; ++j) {
Object *obj = &on->objects[j];
@ -1049,7 +1064,7 @@ void Resource::load_PGE(File *f) {
pge->obj_node_number = f->readUint16LE();
pge->life = f->readUint16LE();
for (int lc = 0; lc < 4; ++lc) {
pge->counter_values[lc] = f->readUint16LE();
pge->data[lc] = f->readUint16LE();
}
pge->object_type = f->readByte();
pge->init_room = f->readByte();
@ -1061,7 +1076,7 @@ void Resource::load_PGE(File *f) {
pge->skill = f->readByte();
pge->mirror_x = f->readByte();
pge->flags = f->readByte();
pge->unk1C = f->readByte();
pge->collision_data_len = f->readByte();
f->readByte();
pge->text_num = f->readUint16LE();
}
@ -1080,7 +1095,7 @@ void Resource::decodePGE(const uint8_t *p, int size) {
pge->obj_node_number = _readUint16(p); p += 2;
pge->life = _readUint16(p); p += 2;
for (int lc = 0; lc < 4; ++lc) {
pge->counter_values[lc] = _readUint16(p); p += 2;
pge->data[lc] = _readUint16(p); p += 2;
}
pge->object_type = *p++;
pge->init_room = *p++;
@ -1092,7 +1107,7 @@ void Resource::decodePGE(const uint8_t *p, int size) {
pge->skill = *p++;
pge->mirror_x = *p++;
pge->flags = *p++;
pge->unk1C = *p++;
pge->collision_data_len = *p++;
++p;
pge->text_num = _readUint16(p); p += 2;
}
@ -1402,6 +1417,10 @@ uint8_t *Resource::loadBankData(uint16_t num) {
dataOffset &= 0xFFFF;
}
const int size = getBankDataSize(num);
if (size == 0) {
warning("Invalid bank data %d", num);
return _bankDataHead;
}
const int avail = _bankDataTail - _bankDataHead;
if (avail < size) {
clearBankData();
@ -1430,7 +1449,7 @@ uint8_t *Resource::decodeResourceMacText(const char *name, const char *suffix) {
snprintf(buf, sizeof(buf), "%s %s", name, suffix);
const ResourceMacEntry *entry = _mac->findEntry(buf);
if (entry) {
return decodeResourceMacData(buf, false);
return decodeResourceMacData(entry, false);
} else { // CD version
if (strcmp(name, "Flashback") == 0) {
name = "Game";
@ -1442,22 +1461,34 @@ uint8_t *Resource::decodeResourceMacText(const char *name, const char *suffix) {
}
uint8_t *Resource::decodeResourceMacData(const char *name, bool decompressLzss) {
_resourceMacDataSize = 0;
uint8_t *data = 0;
const ResourceMacEntry *entry = _mac->findEntry(name);
if (entry) {
_mac->_f.seek(_mac->_dataOffset + entry->dataOffset);
_resourceMacDataSize = _mac->_f.readUint32BE();
if (decompressLzss) {
data = decodeLzss(_mac->_f, _resourceMacDataSize);
} else {
data = (uint8_t *)malloc(_resourceMacDataSize);
if (data) {
_mac->_f.read(data, _resourceMacDataSize);
}
data = decodeResourceMacData(entry, decompressLzss);
} else {
_resourceMacDataSize = 0;
error("Resource '%s' not found", name);
}
return data;
}
uint8_t *Resource::decodeResourceMacData(const ResourceMacEntry *entry, bool decompressLzss) {
assert(entry);
_mac->_f.seek(_mac->_dataOffset + entry->dataOffset);
_resourceMacDataSize = _mac->_f.readUint32BE();
uint8_t *data = 0;
if (decompressLzss) {
data = decodeLzss(_mac->_f, _resourceMacDataSize);
if (!data) {
error("Failed to decompress '%s'", entry->name);
}
} else {
error("Resource '%s' not found", name);
data = (uint8_t *)malloc(_resourceMacDataSize);
if (!data) {
error("Failed to allocate %d bytes for '%s'", _resourceMacDataSize, entry->name);
} else {
_mac->_f.read(data, _resourceMacDataSize);
}
}
return data;
}
@ -1501,10 +1532,8 @@ void Resource::MAC_decodeDataCLUT(const uint8_t *ptr) {
void Resource::MAC_loadClutData() {
uint8_t *ptr = decodeResourceMacData("Flashback colors", false);
if (ptr) {
MAC_decodeDataCLUT(ptr);
free(ptr);
}
MAC_decodeDataCLUT(ptr);
free(ptr);
}
void Resource::MAC_loadFontData() {
@ -1575,46 +1604,36 @@ static const char *_macLevelNumbers[] = { "1", "2", "3", "4-1", "4-2", "5-1", "5
void Resource::MAC_loadLevelData(int level) {
char name[64];
// .PGE
snprintf(name, sizeof(name), "Level %s objects", _macLevelNumbers[level]);
uint8_t *ptr = decodeResourceMacData(name, true);
if (ptr) {
decodePGE(ptr, _resourceMacDataSize);
free(ptr);
} else {
error("Failed to load '%s'", name);
}
decodePGE(ptr, _resourceMacDataSize);
free(ptr);
// .ANI
snprintf(name, sizeof(name), "Level %s sequences", _macLevelNumbers[level]);
_ani = decodeResourceMacData(name, true);
if (_ani) {
assert(READ_BE_UINT16(_ani) == 0x48D);
} else {
error("Failed to load '%s'", name);
}
assert(READ_BE_UINT16(_ani) == 0x48D);
// .OBJ
snprintf(name, sizeof(name), "Level %s conditions", _macLevelNumbers[level]);
ptr = decodeResourceMacData(name, true);
if (ptr) {
assert(READ_BE_UINT16(ptr) == 0xE6);
decodeOBJ(ptr, _resourceMacDataSize);
free(ptr);
} else {
error("Failed to load '%s'", name);
}
assert(READ_BE_UINT16(ptr) == 0xE6);
decodeOBJ(ptr, _resourceMacDataSize);
free(ptr);
// .CT
snprintf(name, sizeof(name), "Level %c map", _macLevelNumbers[level][0]);
ptr = decodeResourceMacData(name, true);
if (ptr) {
assert(_resourceMacDataSize == 0x1D00);
memcpy(_ctData, ptr, _resourceMacDataSize);
free(ptr);
} else {
error("Failed to load '%s'", name);
}
assert(_resourceMacDataSize == 0x1D00);
memcpy(_ctData, ptr, _resourceMacDataSize);
free(ptr);
// .SPC
snprintf(name, sizeof(name), "Objects %c", _macLevelNumbers[level][0]);
_spc = decodeResourceMacData(name, true);
// .TBN
snprintf(name, sizeof(name), "Level %s", _macLevelNumbers[level]);
_tbn = decodeResourceMacText(name, "names");
@ -1626,10 +1645,8 @@ void Resource::MAC_loadLevelRoom(int level, int i, DecodeBuffer *dst) {
char name[64];
snprintf(name, sizeof(name), "Level %c Room %d", _macLevelNumbers[level][0], i);
uint8_t *ptr = decodeResourceMacData(name, true);
if (ptr) {
MAC_decodeImageData(ptr, 0, dst);
free(ptr);
}
MAC_decodeImageData(ptr, 0, dst);
free(ptr);
}
void Resource::MAC_clearClut16(Color *clut, uint8_t dest) {
@ -1704,15 +1721,24 @@ void Resource::MAC_unloadCutscene() {
}
void Resource::MAC_loadCutscene(const char *cutscene) {
MAC_unloadCutscene();
char name[32];
free(_cmd);
snprintf(name, sizeof(name), "%s movie", cutscene);
stringLowerCase(name);
_cmd = decodeResourceMacData(name, true);
free(_pol);
const ResourceMacEntry *cmdEntry = _mac->findEntry(name);
if (!cmdEntry) {
return;
}
_cmd = decodeResourceMacData(cmdEntry, true);
snprintf(name, sizeof(name), "%s polygons", cutscene);
stringLowerCase(name);
_pol = decodeResourceMacData(name, true);
const ResourceMacEntry *polEntry = _mac->findEntry(name);
if (!polEntry) {
return;
}
_pol = decodeResourceMacData(polEntry, true);
}
void Resource::MAC_loadCutsceneText() {

View File

@ -137,13 +137,13 @@ struct Resource {
uint8_t *_icn;
int _icnLen;
uint8_t *_tab;
uint8_t *_spc; // BE
uint8_t *_spc;
uint16_t _numSpc;
uint8_t _rp[0x4A];
uint8_t *_pal; // BE
uint8_t _rp[74];
uint8_t *_pal;
uint8_t *_ani;
uint8_t *_tbn;
int8_t _ctData[0x1D00];
int8_t _ctData[256 + 112 * 64];
uint8_t *_spr1;
uint8_t *_sprData[NUM_SPRITES]; // 0-0x22F + 0x28E-0x2E9 ... conrad, 0x22F-0x28D : junkie
uint8_t _sprm[0x10000];
@ -313,6 +313,15 @@ struct Resource {
const char *getMenuString(int num) const {
return (num >= 0 && num < LocaleData::LI_NUM) ? _textsTable[num] : "";
}
const uint8_t *getCreditsString(int num) {
assert(_type == kResourceTypeMac);
const int count = READ_BE_UINT16(_credits);
if (num < count) {
const int offset = READ_BE_UINT16(_credits + 2 + num * 2);
return _credits + offset;
}
return 0;
}
void clearBankData();
int getBankDataSize(uint16_t num);
uint8_t *findBankData(uint16_t num);
@ -320,6 +329,7 @@ struct Resource {
uint8_t *decodeResourceMacText(const char *name, const char *suffix);
uint8_t *decodeResourceMacData(const char *name, bool decompressLzss);
uint8_t *decodeResourceMacData(const ResourceMacEntry *entry, bool decompressLzss);
void MAC_decodeImageData(const uint8_t *ptr, int i, DecodeBuffer *dst);
void MAC_decodeDataCLUT(const uint8_t *ptr);
void MAC_loadClutData();
@ -343,7 +353,7 @@ struct Resource {
void MAC_loadSounds();
int MAC_getPersoFrame(int anim) const {
static const int data[] = {
static const int16_t data[] = {
0x000, 0x22E,
0x28E, 0x2E9,
0x4E9, 0x506,
@ -361,7 +371,7 @@ struct Resource {
return 0;
}
int MAC_getMonsterFrame(int anim) const {
static const int data[] = {
static const int16_t data[] = {
0x22F, 0x28D, // junky - 94
0x2EA, 0x385, // mercenai - 156
0x387, 0x42F, // replican - 169

View File

@ -3,10 +3,9 @@
#include "unpack.h"
#include "util.h"
const char *ResourceAba::FILENAME = "DEMO_UK.ABA";
ResourceAba::ResourceAba(FileSystem *fs)
: _fs(fs) {
_filesCount = 0;
_entries = 0;
_entriesCount = 0;
}
@ -16,34 +15,41 @@ ResourceAba::~ResourceAba() {
}
static int compareAbaEntry(const void *a, const void *b) {
return strcasecmp(((ResourceAbaEntry *)a)->name, ((ResourceAbaEntry *)b)->name);
return strcasecmp(((const ResourceAbaEntry *)a)->name, ((const ResourceAbaEntry *)b)->name);
}
void ResourceAba::readEntries() {
if (_f.open(FILENAME, "rb", _fs)) {
_entriesCount = _f.readUint16BE();
_entries = (ResourceAbaEntry *)calloc(_entriesCount, sizeof(ResourceAbaEntry));
void ResourceAba::readEntries(const char *aba) {
assert(_filesCount < 3);
if (_f[_filesCount].open(aba, "rb", _fs)) {
File &f = _f[_filesCount];
const int currentCount = _entriesCount;
const int entriesCount = f.readUint16BE();
_entries = (ResourceAbaEntry *)realloc(_entries, (currentCount + entriesCount) * sizeof(ResourceAbaEntry));
if (!_entries) {
error("Failed to allocate %d _entries", _entriesCount);
error("Failed to allocate %d _entries", currentCount + entriesCount);
return;
}
const int entrySize = _f.readUint16BE();
_entriesCount = currentCount + entriesCount;
const int entrySize = f.readUint16BE();
assert(entrySize == 30);
uint32_t nextOffset = 0;
for (int i = 0; i < _entriesCount; ++i) {
_f.read(_entries[i].name, sizeof(_entries[i].name));
_entries[i].offset = _f.readUint32BE();
_entries[i].compressedSize = _f.readUint32BE();
_entries[i].size = _f.readUint32BE();
const uint32_t tag = _f.readUint32BE();
for (int i = 0; i < entriesCount; ++i) {
const int j = currentCount + i;
f.read(_entries[j].name, sizeof(_entries[j].name));
_entries[j].offset = f.readUint32BE();
_entries[j].compressedSize = f.readUint32BE();
_entries[j].size = f.readUint32BE();
_entries[j].fileIndex = _filesCount;
const uint32_t tag = f.readUint32BE();
assert(tag == TAG);
debug(DBG_RES, "'%s' offset 0x%X size %d/%d", _entries[i].name, _entries[i].offset, _entries[i].compressedSize, _entries[i].size);
debug(DBG_RES, "'%s' offset 0x%X size %d/%d", _entries[j].name, _entries[j].offset, _entries[j].compressedSize, _entries[j].size);
if (i != 0) {
assert(nextOffset == _entries[i].offset);
assert(nextOffset == _entries[j].offset);
}
nextOffset = _entries[i].offset + _entries[i].compressedSize;
nextOffset = _entries[j].offset + _entries[j].compressedSize;
}
qsort(_entries, _entriesCount, sizeof(ResourceAbaEntry), compareAbaEntry);
++_filesCount;
}
}
@ -65,8 +71,8 @@ uint8_t *ResourceAba::loadEntry(const char *name, uint32_t *size) {
error("Failed to allocate %d bytes", e->compressedSize);
return 0;
}
_f.seek(e->offset);
_f.read(tmp, e->compressedSize);
_f[e->fileIndex].seek(e->offset);
_f[e->fileIndex].read(tmp, e->compressedSize);
if (e->compressedSize == e->size) {
dst = tmp;
} else {

View File

@ -11,22 +11,23 @@ struct ResourceAbaEntry {
uint32_t offset;
uint32_t compressedSize;
uint32_t size;
int fileIndex;
};
struct ResourceAba {
static const char *FILENAME;
static const int TAG = 0x442E4D2E;
FileSystem *_fs;
File _f;
File _f[3];
int _filesCount;
ResourceAbaEntry *_entries;
int _entriesCount;
ResourceAba(FileSystem *fs);
~ResourceAba();
void readEntries();
void readEntries(const char *aba);
const ResourceAbaEntry *findEntry(const char *name) const;
uint8_t *loadEntry(const char *name, uint32_t *size = 0);
};

10
rs.cfg
View File

@ -19,14 +19,17 @@ use_text_cutscenes=false
# enable playback of PC CD .SEQ cutscenes (use polygon cutscenes if false)
use_seq_cutscenes=true
# if copy protection is enabled, display the words manual lookup screen (as in DOS SSI version).
# if copy protection is enabled, display the words manual lookup screen (as in DOS SSI version)
use_words_protection=false
# white t-shirt for Conrad
use_white_tshirt=false
# use .PRF music files
use_prf_music=true
# enable playback of 'ASC' cutscene
play_asc_cutscene=true
play_asc_cutscene=false
# enable playback of 'CAILLOU-F.SET' cutscene
play_caillou_cutscene=true
@ -45,3 +48,6 @@ play_gamesaved_sound=true
# restore content from 'MEMO' cutscene
restore_memo_cutscene=true
# order inventory items as the original game, last grabbed item is at the bottom of the inventory
order_inventory_original=false

View File

@ -2,83 +2,29 @@
#include "screenshot.h"
#include "file.h"
#define kTgaImageTypeUncompressedTrueColor 2
#define kTgaImageTypeRunLengthEncodedTrueColor 10
#define kTgaDirectionTop (1 << 5)
void saveTGA(const char *filename, const uint8_t *rgba, int w, int h) {
static const uint8_t kImageType = kTgaImageTypeRunLengthEncodedTrueColor;
File f;
if (f.open(filename, "wb", ".")) {
f.writeByte(0); // ID Length
f.writeByte(0); // ColorMap Type
f.writeByte(kImageType);
f.writeUint16LE(0); // ColorMap Start
f.writeUint16LE(0); // ColorMap Length
f.writeByte(0); // ColorMap Bits
f.writeUint16LE(0); // X-origin
f.writeUint16LE(0); // Y-origin
f.writeUint16LE(w); // Image Width
f.writeUint16LE(h); // Image Height
f.writeByte(24); // Pixel Depth
f.writeByte(kTgaDirectionTop); // Descriptor
if (kImageType == kTgaImageTypeUncompressedTrueColor) {
for (int i = 0; i < w * h; ++i) {
f.writeByte(rgba[0]);
f.writeByte(rgba[1]);
f.writeByte(rgba[2]);
rgba += 4;
}
} else {
assert(kImageType == kTgaImageTypeRunLengthEncodedTrueColor);
int prevColor = rgba[2] + (rgba[1] << 8) + (rgba[0] << 16); rgba += 4;
int count = 0;
for (int i = 1; i < w * h; ++i) {
int color = rgba[2] + (rgba[1] << 8) + (rgba[0] << 16); rgba += 4;
if (prevColor == color && count < 127) {
++count;
continue;
}
f.writeByte(count | 0x80);
f.writeByte((prevColor >> 16) & 255);
f.writeByte((prevColor >> 8) & 255);
f.writeByte( prevColor & 255);
count = 0;
prevColor = color;
}
if (count != 0) {
f.writeByte(count | 0x80);
f.writeByte((prevColor >> 16) & 255);
f.writeByte((prevColor >> 8) & 255);
f.writeByte( prevColor & 255);
}
}
}
}
static const uint16_t TAG_BM = 0x4D42;
void saveBMP(const char *filename, const uint8_t *bits, const uint8_t *pal, int w, int h) {
File f;
if (f.open(filename, "wb", ".")) {
const int alignWidth = (w + 3) & ~3;
const int paletteSize = pal ? 4 * 256 : 0;
const int bitsCount = pal ? 8 : 32;
const int alignWidth = ((w * bitsCount / 8) + 3) & ~3;
const int imageSize = alignWidth * h;
// Write file header
f.writeUint16LE(TAG_BM);
f.writeUint32LE(14 + 40 + 4 * 256 + imageSize);
f.writeUint32LE(14 + 40 + paletteSize + imageSize);
f.writeUint16LE(0); // reserved1
f.writeUint16LE(0); // reserved2
f.writeUint32LE(14 + 40 + 4 * 256);
f.writeUint32LE(14 + 40 + paletteSize);
// Write info header
f.writeUint32LE(40);
f.writeUint32LE(w);
f.writeUint32LE(h);
f.writeUint16LE(1); // planes
f.writeUint16LE(8); // bit_count
f.writeUint16LE(bitsCount); // bit_count
f.writeUint32LE(0); // compression
f.writeUint32LE(imageSize); // size_image
f.writeUint32LE(0); // x_pels_per_meter
@ -86,16 +32,19 @@ void saveBMP(const char *filename, const uint8_t *bits, const uint8_t *pal, int
f.writeUint32LE(0); // num_colors_used
f.writeUint32LE(0); // num_colors_important
// Write palette data
for (int i = 0; i < 256; ++i) {
f.writeByte(pal[2]);
f.writeByte(pal[1]);
f.writeByte(pal[0]);
f.writeByte(0);
pal += 3;
if (pal) {
// Write palette data
for (int i = 0; i < 256; ++i) {
f.writeByte(pal[2]);
f.writeByte(pal[1]);
f.writeByte(pal[0]);
f.writeByte(0);
pal += 3;
}
}
// Write bitmap data
w *= bitsCount / 8;
const int pitch = w;
bits += h * pitch;
for (int i = 0; i < h; ++i) {

View File

@ -4,7 +4,6 @@
#include <stdint.h>
void saveTGA(const char *filename, const uint8_t *rgb, int w, int h);
void saveBMP(const char *filename, const uint8_t *bits, const uint8_t *pal, int w, int h);
#endif

View File

@ -57,9 +57,7 @@ bool SeqDemuxer::readFrameData() {
}
_f->seek(_frameOffset);
_audioDataOffset = _f->readUint16LE();
_audioDataSize = (_audioDataOffset != 0) ? kAudioBufferSize * 2 : 0;
_paletteDataOffset = _f->readUint16LE();
_paletteDataSize = (_paletteDataOffset != 0) ? 768 : 0;
uint8_t num[4];
for (int i = 0; i < 4; ++i) {
num[i] = _f->readByte();
@ -220,6 +218,7 @@ SeqPlayer::SeqPlayer(SystemStub *stub, Mixer *mixer)
: _stub(stub), _buf(0), _mix(mixer) {
_soundQueuePreloadSize = 0;
_soundQueue = 0;
_soundQueueTail = 0;
}
SeqPlayer::~SeqPlayer() {
@ -242,7 +241,7 @@ void SeqPlayer::play(File *f) {
if (!_demux.readFrameData()) {
break;
}
if (_demux._audioDataSize != 0) {
if (_demux._audioDataOffset != 0) {
SoundBufferQueue *sbq = (SoundBufferQueue *)malloc(sizeof(SoundBufferQueue));
if (sbq) {
sbq->data = (int16_t *)calloc(SeqDemuxer::kAudioBufferSize, sizeof(int16_t));
@ -258,25 +257,23 @@ void SeqPlayer::play(File *f) {
}
if (sbq) {
LockAudioStack las(_stub);
if (!_soundQueue) {
_soundQueue = sbq;
if (_soundQueueTail) {
_soundQueueTail->next = sbq;
} else {
SoundBufferQueue *p = _soundQueue;
while (p->next) {
p = p->next;
}
p->next = sbq;
assert(!_soundQueue);
_soundQueue = sbq;
}
_soundQueueTail = sbq;
if (_soundQueuePreloadSize < kSoundPreloadSize) {
++_soundQueuePreloadSize;
}
}
}
if (_demux._paletteDataSize != 0) {
if (_demux._paletteDataOffset != 0) {
uint8_t buf[256 * 3];
_demux.readPalette(buf);
for (int i = 0; i < 256 * 3; ++i) {
buf[i] = (buf[i] << 2) | (buf[i] & 3);
buf[i] = (buf[i] << 2) | (buf[i] >> 4);
}
_stub->setPalette(buf, 256);
}
@ -326,6 +323,7 @@ void SeqPlayer::play(File *f) {
free(_soundQueue);
_soundQueue = next;
}
_soundQueueTail = 0;
_soundQueuePreloadSize = 0;
}
}
@ -335,15 +333,24 @@ bool SeqPlayer::mix(int16_t *buf, int samples) {
return true;
}
while (_soundQueue && samples > 0) {
*buf++ = _soundQueue->data[_soundQueue->read];
++_soundQueue->read;
const int count = MIN(samples, _soundQueue->size - _soundQueue->read);
const int16_t *src = (const int16_t *)(_soundQueue->data + _soundQueue->read);
for (int i = 0; i < count; ++i) {
const int16_t sample = *src++;
*buf++ = sample;
*buf++ = sample;
}
_soundQueue->read += count;
if (_soundQueue->read == _soundQueue->size) {
SoundBufferQueue *next = _soundQueue->next;
free(_soundQueue->data);
free(_soundQueue);
_soundQueue = next;
}
--samples;
samples -= count;
}
if (!_soundQueue) {
_soundQueueTail = 0;
}
return true;
}

View File

@ -32,9 +32,7 @@ struct SeqDemuxer {
int _frameOffset;
int _audioDataOffset;
int _audioDataSize;
int _paletteDataOffset;
int _paletteDataSize;
int _videoData;
struct {
int size;
@ -74,7 +72,7 @@ struct SeqPlayer {
Mixer *_mix;
SeqDemuxer _demux;
int _soundQueuePreloadSize;
SoundBufferQueue *_soundQueue;
SoundBufferQueue *_soundQueue, *_soundQueueTail;
};
#endif // SEQ_PLAYER_H__

View File

@ -13,7 +13,7 @@
static const int kMasterVolume = 64 * 3;
// 12 dB/oct Butterworth low-pass filter at 3.3 kHz
static const bool kLowPassFilter = true;
static const bool kLowPassFilter = false;
#define NZEROS 2
#define NPOLES 2
@ -151,8 +151,11 @@ void SfxPlayer::mixSamples(int16_t *buf, int samplesLen) {
curLen = 0;
}
while (count--) {
const int out = si->getPCM(pos >> FRAC_BITS) * si->vol / kMasterVolume;
*mixbuf = ADDC_S16(*mixbuf, S8_to_S16(out));
const int sample8 = si->getPCM(pos >> FRAC_BITS) * si->vol / kMasterVolume;
const int sample16 = S8_to_S16(sample8);
*mixbuf = ADDC_S16(*mixbuf, sample16);
++mixbuf;
*mixbuf = ADDC_S16(*mixbuf, sample16);
++mixbuf;
pos += deltaPos;
}
@ -163,7 +166,7 @@ void SfxPlayer::mixSamples(int16_t *buf, int samplesLen) {
}
bool SfxPlayer::mix(int16_t *buf, int len) {
memset(buf, 0, sizeof(int16_t) * len);
memset(buf, 0, sizeof(int16_t) * len * 2); // stereo
if (_playing) {
const int samplesPerTick = _mix->getSampleRate() / 50;
while (len != 0) {
@ -179,9 +182,9 @@ bool SfxPlayer::mix(int16_t *buf, int len) {
len -= count;
mixSamples(buf, count);
if (kLowPassFilter) {
butterworth(buf, count);
butterworth(buf, count * 2); // stereo
}
buf += count;
buf += count * 2; // stereo
}
}
return _playing;

View File

@ -537,7 +537,7 @@ const uint16_t Cutscene::_creditsCutSeq[] = {
0x00, 0x05, 0x2F, 0x32, 0x36, 0x3E, 0x30, 0x39, 0x3F, 0x14, 0x34, 0xFFFF
};
const uint8_t Cutscene::_musicTable[] = {
const uint8_t Cutscene::_musicTableDOS[] = {
0x10, 0x15, 0x15, 0xFF, 0x15, 0x19, 0x0F, 0xFF, 0x15, 0x04, 0x15, 0xFF, 0xFF, 0x00, 0x19, 0x15,
0x15, 0x0D, 0x15, 0x0D, 0x18, 0x13, 0xFF, 0xFF, 0xFF, 0x14, 0x14, 0x14, 0x14, 0x14, 0xFF, 0xFF,
0x13, 0x13, 0x13, 0x13, 0x15, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x13, 0x13, 0x11, 0xFF, 0x03,
@ -545,6 +545,19 @@ const uint8_t Cutscene::_musicTable[] = {
0x0B, 0x0C, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0xFF, 0xFF, 0xFF
};
const uint8_t Cutscene::_musicTableAmiga[] = {
0x10, 0x7D, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F, 0x7D, 0xFF, 0xFF,
0xFF, 0xFF, 0x04, 0x73, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x6B, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0x0D, 0x7D, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x13, 0x7D, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x13, 0x7D, 0x13, 0x7D, 0x13, 0x7D, 0x13, 0x7D, 0xFF, 0xFF, 0x14, 0x7D, 0x14, 0x7D, 0x14, 0x7D,
0x14, 0x7D, 0x14, 0x7D, 0x14, 0x7D, 0x13, 0x9B, 0x13, 0x9B, 0x11, 0x7D, 0xFF, 0xFF, 0x03, 0x78,
0x0E, 0x7D, 0x13, 0x7D, 0x12, 0x6E, 0xFF, 0xFF, 0x06, 0x87, 0x07, 0x7D, 0x0A, 0x7D, 0x0A, 0x7D,
0xFF, 0xFF, 0x05, 0x7D, 0x13, 0x7D, 0x02, 0x7D, 0xFF, 0xFF, 0x09, 0x7D, 0xFF, 0xFF, 0x08, 0x7D,
0x0B, 0x7D, 0x0C, 0x7D, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0x3D, 0x25, 0x27, 0x28
};
const uint8_t Cutscene::_protectionShapeData[] = {
0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x52, 0x00, 0x00, 0x06, 0xD4, 0x00, 0x00, 0x00, 0x92,
0x00, 0x00, 0x08, 0x24, 0x00, 0x00, 0x00, 0x02, 0x00, 0x28, 0x00, 0x54, 0x00, 0x8C, 0x00, 0xB2,
@ -3585,7 +3598,7 @@ const Game::pge_OpcodeProc Game::_pge_opcodeTable[] = {
/* 0x48 */
&Game::pge_o_unk0x48,
&Game::pge_o_unk0x49,
&Game::pge_o_unk0x4A,
&Game::pge_op_killInventoryPiege,
&Game::pge_op_killPiege,
/* 0x4C */
&Game::pge_op_isInCurrentRoom,
@ -3606,7 +3619,7 @@ const Game::pge_OpcodeProc Game::_pge_opcodeTable[] = {
&Game::pge_op_setLifeCounter,
&Game::pge_op_decLifeCounter,
&Game::pge_op_playCutscene,
&Game::pge_op_isTempVar2Set,
&Game::pge_op_compareUnkVar,
/* 0x5C */
&Game::pge_op_playDeathCutscene,
&Game::pge_o_unk0x5D,
@ -3665,8 +3678,8 @@ const Game::pge_OpcodeProc Game::_pge_opcodeTable[] = {
/* 0x88 */
&Game::pge_op_adjustPos,
0,
&Game::pge_op_setTempVar1,
&Game::pge_op_isTempVar1Set
&Game::pge_op_setGunVar,
&Game::pge_op_compareGunVar
};
const uint8_t Game::_pge_modKeysTable[] = {
@ -4419,36 +4432,31 @@ const uint16_t ModPlayer::_periodTable[] = {
216, 203, 192, 181, 171, 161, 152, 144, 136, 128, 121, 114 // C-3 to B-3 Finetune -1
};
const char *ModPlayer::_modulesFiles[][2] = {
{ "intro", "mod.flashback-introb" }, // introl3
{ "options", "mod.flashback-options2" }, // option3
{ "journal", "mod.flashback-options1" }, // journal3
{ "ceinture", "mod.flashback-ceinturea" }, // chute3
{ "desinteg", "mod.flashback-desintegr" }, // desinte3
{ "reunion", "mod.flashback-reunion" }, // capture3
{ "voyage", "mod.flashback-voyage" }, // voyage3
{ "level4", "mod.flashback-teleporta" }, // telepor3
{ "planetexplo", "mod.flashback-teleport2" }, // planexp3
{ "fin", "mod.flashback-fin" }, // end31
{ "ascenseur", "mod.flashback-ascenseur" }, // lift3
{ "logo", "mod.flashback-logo" }, // present3
{ "game_over", "mod.flashback-game_over" }, // gameove3
{ "holocube", "mod.flashback-holocube" }, // holo3
{ "memoire", "mod.flashback-memoire" }, // memory3
{ "chute", "mod.flashback-chute" }, // chutevi3
{ "debut", "mod.flashback-jungle" }, // reveil3
{ "missions", "mod.flashback-missionca" }, // misvali3
{ "taxi", "mod.flashback-taxi" }, // taxi3
{ "donneobj", "mod.flashback-donneobjt" }, // donner3
{ "missions2", "mod.flashback-fin2" } // mission3
// { 0, 0, }, // objet3
// { 0, 0, }, // recharg3
// { 0, 0, }, // generat3
// { 0, 0, }, // pont3
// { 0, 0, } // rechage3
const char *ModPlayer::_names[] = {
"intro", "introb",
"options", "options2",
"journal", "options1",
"ceinture", "ceinturea",
"desinteg", "desintegr",
"reunion", 0,
"voyage", 0,
"level4", "teleporta",
"planetexplo", "teleport2",
"fin", 0,
"ascenseur", 0,
"logo", 0,
"game_over", 0,
"holocube", 0,
"memoire", 0,
"chute", 0,
"debut", "jungle",
"missions", "missionca",
"taxi", 0,
"donneobj", "donneobjt",
"missions2", "fin2",
};
const int ModPlayer::_modulesFilesCount = ARRAYSIZE(_modulesFiles);
const int ModPlayer::_namesCount = ARRAYSIZE(_names);
const char *SeqPlayer::_namesTable[] = {
/* 0x00 */
@ -6503,3 +6511,34 @@ const uint8_t Menu::_flagSp16x12[] = {
0x03, 0x00, 0x8d, 0x03, 0x00, 0x8d, 0x03, 0x00, 0x8d, 0x03, 0x00, 0x8d, 0x03, 0x00, 0x8d, 0x03,
0x00, 0x8d, 0x03, 0x00, 0x8d, 0x03, 0x00, 0x8d, 0x03, 0x00, 0x8d, 0x03, 0x00, 0x8e, 0x03, 0x00
};
const char *PrfPlayer::_names[] = {
"introl3",
"option3",
"journal3",
"chute3",
"desinte3",
"capture3",
"voyage3",
"telepor3",
"planexp3",
"end31",
"lift3",
"present3",
"gameove3",
"holo3",
"memory3",
"chutevi3",
"reveil3",
"misvali3",
"taxi3",
"donner3",
"mission3",
"objet3",
"recharg3",
"generat3",
"pont3",
"rechage3"
};
const int PrfPlayer::_namesCount = ARRAYSIZE(_names);

5584
stb_vorbis.c Executable file

File diff suppressed because it is too large Load Diff

View File

@ -20,7 +20,8 @@ struct PlayerInput {
enum {
DF_FASTMODE = 1 << 0,
DF_DBLOCKS = 1 << 1,
DF_SETLIFE = 1 << 2
DF_SETLIFE = 1 << 2,
DF_AUTOZOOM = 1 << 3
};
uint8_t dirMask;
@ -68,10 +69,12 @@ struct SystemStub {
virtual void setOverscanColor(int i) = 0;
virtual void copyRect(int x, int y, int w, int h, const uint8_t *buf, int pitch) = 0;
virtual void copyRectRgb24(int x, int y, int w, int h, const uint8_t *rgb) = 0;
virtual void zoomRect(int x, int y, int h, int w) = 0;
virtual void copyWidescreenLeft(int w, int h, const uint8_t *buf) = 0;
virtual void copyWidescreenRight(int w, int h, const uint8_t *buf) = 0;
virtual void copyWidescreenMirror(int w, int h, const uint8_t *buf) = 0;
virtual void copyWidescreenBlur(int w, int h, const uint8_t *buf) = 0;
virtual void copyWidescreenCDi(int w, int h, const uint8_t *buf, const uint8_t *pal) = 0;
virtual void clearWidescreen() = 0;
virtual void enableWidescreen(bool enable) = 0;
virtual void fadeScreen() = 0;
@ -99,6 +102,22 @@ struct LockAudioStack {
SystemStub *_stub;
};
struct ToggleWidescreenStack {
ToggleWidescreenStack(SystemStub *stub, bool state)
: _stub(stub), _state(state) {
if (_stub->hasWidescreen()) {
_stub->enableWidescreen(_state);
}
}
~ToggleWidescreenStack() {
if (_stub->hasWidescreen()) {
_stub->enableWidescreen(!_state);
}
}
SystemStub *_stub;
bool _state;
};
extern SystemStub *SystemStub_SDL_create();
#endif // SYSTEMSTUB_H__

View File

@ -31,6 +31,7 @@ struct SystemStub_SDL : SystemStub {
SDL_Renderer *_renderer;
SDL_Texture *_texture;
int _texW, _texH;
SDL_Rect _texRect;
SDL_GameController *_controller;
SDL_PixelFormat *_fmt;
const char *_caption;
@ -66,10 +67,12 @@ struct SystemStub_SDL : SystemStub {
virtual void setOverscanColor(int i);
virtual void copyRect(int x, int y, int w, int h, const uint8_t *buf, int pitch);
virtual void copyRectRgb24(int x, int y, int w, int h, const uint8_t *rgb);
virtual void zoomRect(int x, int y, int w, int h);
virtual void copyWidescreenLeft(int w, int h, const uint8_t *buf);
virtual void copyWidescreenRight(int w, int h, const uint8_t *buf);
virtual void copyWidescreenMirror(int w, int h, const uint8_t *buf);
virtual void copyWidescreenBlur(int w, int h, const uint8_t *buf);
virtual void copyWidescreenCDi(int w, int h, const uint8_t *buf, const uint8_t *pal);
virtual void clearWidescreen();
virtual void enableWidescreen(bool enable);
virtual void fadeScreen();
@ -98,7 +101,7 @@ SystemStub *SystemStub_SDL_create() {
}
void SystemStub_SDL::init(const char *title, int w, int h, bool fullscreen, int widescreenMode, const ScalerParameters *scalerParameters) {
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_JOYSTICK);
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER);
SDL_ShowCursor(SDL_DISABLE);
_caption = title;
memset(&_pi, 0, sizeof(_pi));
@ -269,6 +272,16 @@ void SystemStub_SDL::copyRectRgb24(int x, int y, int w, int h, const uint8_t *rg
}
}
void SystemStub_SDL::zoomRect(int x, int y, int w, int h) {
if (_pi.dbgMask & PlayerInput::DF_DBLOCKS) {
drawRect(x, y, w, h, 0xE7);
}
_texRect.x = x * _texW / _screenW;
_texRect.y = y * _texH / _screenH;
_texRect.w = w * _texW / _screenW;
_texRect.h = h * _texH / _screenH;
}
static void clearTexture(SDL_Texture *texture, int h, SDL_PixelFormat *fmt) {
void *dst = 0;
int pitch = 0;
@ -466,6 +479,28 @@ void SystemStub_SDL::copyWidescreenBlur(int w, int h, const uint8_t *buf) {
}
}
void SystemStub_SDL::copyWidescreenCDi(int w, int h, const uint8_t *buf, const uint8_t *pal) {
uint32_t *rgb = (uint32_t *)malloc(w * h * sizeof(uint32_t));
if (rgb) {
for (int i = 0; i < w * h; ++i) {
const uint8_t *p = pal + buf[i] * 3;
const uint32_t color = SDL_MapRGB(_fmt, p[0], p[1], p[2]);
rgb[i] = color;
}
SDL_Rect r;
r.y = 0;
r.w = w;
r.h = h;
// left border
r.x = 0;
SDL_UpdateTexture(_widescreenTexture, &r, rgb, w * sizeof(uint32_t));
// right border
r.x = _screenW + w;
SDL_UpdateTexture(_widescreenTexture, &r, rgb, w * sizeof(uint32_t));
free(rgb);
}
}
void SystemStub_SDL::clearWidescreen() {
clearTexture(_widescreenTexture, _screenH, _fmt);
}
@ -502,7 +537,7 @@ void SystemStub_SDL::updateScreen(int shakeOffset) {
SDL_RenderGetLogicalSize(_renderer, &r.w, &r.h);
r.x = (r.w - _texW) / 2;
r.w = _texW;
SDL_RenderCopy(_renderer, _texture, 0, &r);
SDL_RenderCopy(_renderer, _texture, &_texRect, &r);
} else {
if (_fadeOnUpdateScreen) {
SDL_SetRenderDrawBlendMode(_renderer, SDL_BLENDMODE_BLEND);
@ -524,9 +559,13 @@ void SystemStub_SDL::updateScreen(int shakeOffset) {
r.x = 0;
r.y = shakeOffset * _scaleFactor;
SDL_RenderGetLogicalSize(_renderer, &r.w, &r.h);
SDL_RenderCopy(_renderer, _texture, 0, &r);
SDL_RenderCopy(_renderer, _texture, &_texRect, &r);
}
SDL_RenderPresent(_renderer);
_texRect.x = 0;
_texRect.y = 0;
_texRect.w = _texW;
_texRect.h = _texH;
}
void SystemStub_SDL::processEvents() {
@ -569,6 +608,7 @@ void SystemStub_SDL::processEvent(const SDL_Event &ev, bool &paused) {
_pi.quit = true;
break;
case SDL_WINDOWEVENT:
#if !defined(__amigaos4__)
switch (ev.window.event) {
case SDL_WINDOWEVENT_FOCUS_GAINED:
case SDL_WINDOWEVENT_FOCUS_LOST:
@ -576,6 +616,7 @@ void SystemStub_SDL::processEvent(const SDL_Event &ev, bool &paused) {
SDL_PauseAudio(paused);
break;
}
#endif
break;
case SDL_JOYHATMOTION:
if (_joystick) {
@ -740,8 +781,8 @@ void SystemStub_SDL::processEvent(const SDL_Event &ev, bool &paused) {
break;
case SDLK_s: {
char name[32];
snprintf(name, sizeof(name), "screenshot-%03d.tga", _screenshot);
saveTGA(name, (const uint8_t *)_screenBuffer, _screenW, _screenH);
snprintf(name, sizeof(name), "screenshot-%03d.bmp", _screenshot);
saveBMP(name, (const uint8_t *)_screenBuffer, 0, _screenW, _screenH);
++_screenshot;
debug(DBG_INFO, "Written '%s'", name);
}
@ -771,6 +812,10 @@ void SystemStub_SDL::processEvent(const SDL_Event &ev, bool &paused) {
case SDLK_r:
_pi.rewind = true;
break;
case SDLK_g:
_pi.dbgMask ^= PlayerInput::DF_AUTOZOOM;
debug(DBG_INFO, "Auto zoom %s", (_pi.dbgMask & PlayerInput::DF_AUTOZOOM) ? "enabled" : "disabled");
break;
case SDLK_KP_PLUS:
case SDLK_PAGEUP:
_pi.stateSlot = 1;
@ -785,15 +830,19 @@ void SystemStub_SDL::processEvent(const SDL_Event &ev, bool &paused) {
setAsciiChar(_pi, &ev.key.keysym);
switch (ev.key.keysym.sym) {
case SDLK_LEFT:
case SDLK_KP_4:
_pi.dirMask &= ~PlayerInput::DIR_LEFT;
break;
case SDLK_RIGHT:
case SDLK_KP_6:
_pi.dirMask &= ~PlayerInput::DIR_RIGHT;
break;
case SDLK_UP:
case SDLK_KP_8:
_pi.dirMask &= ~PlayerInput::DIR_UP;
break;
case SDLK_DOWN:
case SDLK_KP_2:
_pi.dirMask &= ~PlayerInput::DIR_DOWN;
break;
case SDLK_SPACE:
@ -829,15 +878,19 @@ void SystemStub_SDL::processEvent(const SDL_Event &ev, bool &paused) {
}
switch (ev.key.keysym.sym) {
case SDLK_LEFT:
case SDLK_KP_4:
_pi.dirMask |= PlayerInput::DIR_LEFT;
break;
case SDLK_RIGHT:
case SDLK_KP_6:
_pi.dirMask |= PlayerInput::DIR_RIGHT;
break;
case SDLK_UP:
case SDLK_KP_8:
_pi.dirMask |= PlayerInput::DIR_UP;
break;
case SDLK_DOWN:
case SDLK_KP_2:
_pi.dirMask |= PlayerInput::DIR_DOWN;
break;
case SDLK_BACKSPACE:
@ -877,7 +930,8 @@ uint32_t SystemStub_SDL::getTimeStamp() {
static void mixAudioS16(void *param, uint8_t *buf, int len) {
SystemStub_SDL *stub = (SystemStub_SDL *)param;
memset(buf, 0, len);
stub->_audioCbProc(stub->_audioCbData, (int16_t *)buf, len / 2);
assert((len & 3) == 0);
stub->_audioCbProc(stub->_audioCbData, (int16_t *)buf, len / (sizeof(int16_t) * 2));
}
void SystemStub_SDL::startAudio(AudioCallback callback, void *param) {
@ -885,7 +939,7 @@ void SystemStub_SDL::startAudio(AudioCallback callback, void *param) {
memset(&desired, 0, sizeof(desired));
desired.freq = kAudioHz;
desired.format = AUDIO_S16SYS;
desired.channels = 1;
desired.channels = 2;
desired.samples = 2048;
desired.callback = mixAudioS16;
desired.userdata = this;
@ -935,6 +989,10 @@ void SystemStub_SDL::prepareGraphics() {
_texH *= _scaleFactor;
break;
}
_texRect.x = 0;
_texRect.y = 0;
_texRect.w = _texW;
_texRect.h = _texH;
int windowW = _screenW * _scaleFactor;
int windowH = _screenH * _scaleFactor;
int flags = 0;
@ -951,7 +1009,9 @@ void SystemStub_SDL::prepareGraphics() {
_widescreenMode = kWidescreenNone;
}
}
if (_widescreenMode != kWidescreenNone) {
if (_widescreenMode == kWidescreenCDi) {
windowW = (_screenW + kWidescreenBorderCDiW * 2) * _scaleFactor;
} else if (_widescreenMode != kWidescreenNone) {
windowW = windowH * 16 / 9;
}
_window = SDL_CreateWindow(_caption, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, windowW, windowH, flags);
@ -964,11 +1024,15 @@ void SystemStub_SDL::prepareGraphics() {
SDL_RenderSetLogicalSize(_renderer, windowW, windowH);
_texture = SDL_CreateTexture(_renderer, kPixelFormat, SDL_TEXTUREACCESS_STREAMING, _texW, _texH);
if (_widescreenMode != kWidescreenNone) {
int w = _screenH * 16 / 9;
// in blur mode, the background texture has the same dimensions as the game texture
// SDL stretches the texture to 16:9
const int w = (_widescreenMode == kWidescreenBlur) ? _screenW : _screenH * 16 / 9;
const int h = _screenH;
_widescreenTexture = SDL_CreateTexture(_renderer, kPixelFormat, SDL_TEXTUREACCESS_STREAMING, w, h);
if (_widescreenMode == kWidescreenBlur) {
w = _screenW;
} else if (_widescreenMode == kWidescreenCDi) {
w = _screenW + kWidescreenBorderCDiW * 2;
}
_widescreenTexture = SDL_CreateTexture(_renderer, kPixelFormat, SDL_TEXTUREACCESS_STREAMING, w, _screenH);
clearTexture(_widescreenTexture, _screenH, _fmt);
// left and right borders
@ -1021,7 +1085,7 @@ void SystemStub_SDL::setScaler(const ScalerParameters *parameters) {
{ "tv2x", kScalerTypeInternal, &scaler_tv2x },
{ "xbr", kScalerTypeInternal, &scaler_xbr },
#endif
{ 0, -1 }
{ 0, -1, 0 }
};
bool found = false;
for (int i = 0; scalers[i].name; ++i) {
@ -1061,7 +1125,7 @@ void SystemStub_SDL::setScaler(const ScalerParameters *parameters) {
}
}
}
_scaleFactor = _scaler ? CLIP(parameters->factor, _scaler->factorMin, _scaler->factorMax) : 1;
_scaleFactor = _scaler ? CLIP(parameters->factor, _scaler->factorMin, _scaler->factorMax) : parameters->factor;
}
void SystemStub_SDL::changeScaler(int scalerNum) {

View File

@ -15,13 +15,13 @@ struct UnpackCtx {
const uint8_t *src;
};
static bool nextBit(UnpackCtx *uc) {
bool bit = (uc->bits & 1) != 0;
static int nextBit(UnpackCtx *uc) {
int bit = (uc->bits & 1);
uc->bits >>= 1;
if (uc->bits == 0) { // getnextlwd
const uint32_t bits = READ_BE_UINT32(uc->src); uc->src -= 4;
uc->crc ^= bits;
bit = (bits & 1) != 0;
bit = (bits & 1);
uc->bits = (1 << 31) | (bits >> 1);
}
return bit;
@ -31,7 +31,7 @@ template<int count>
static uint32_t getBits(UnpackCtx *uc) { // rdd1bits
uint32_t bits = 0;
for (int i = 0; i < count; ++i) {
bits |= (nextBit(uc) ? 1 : 0) << (count - 1 - i);
bits = (bits << 1) | nextBit(uc);
}
return bits;
}
@ -42,10 +42,9 @@ static void copyLiteral(UnpackCtx *uc, int len) { // getd3chr
len += uc->size;
uc->size = 0;
}
for (int i = 0; i < len; ++i) {
*(uc->dst - i) = (uint8_t)getBits<8>(uc);
for (int i = 0; i < len; ++i, --uc->dst) {
*(uc->dst) = (uint8_t)getBits<8>(uc);
}
uc->dst -= len;
}
static void copyReference(UnpackCtx *uc, int len, int offset) { // copyd3bytes
@ -54,10 +53,9 @@ static void copyReference(UnpackCtx *uc, int len, int offset) { // copyd3bytes
len += uc->size;
uc->size = 0;
}
for (int i = 0; i < len; ++i) {
*(uc->dst - i) = *(uc->dst - i + offset);
for (int i = 0; i < len; ++i, --uc->dst) {
*(uc->dst) = *(uc->dst + offset);
}
uc->dst -= len;
}
bool bytekiller_unpack(uint8_t *dst, int dstSize, const uint8_t *src, int srcSize) {

View File

@ -18,6 +18,7 @@
uint16_t g_debugMask;
#ifndef NDEBUG
void debug(uint16_t cm, const char *msg, ...) {
char buf[1024];
if (cm & g_debugMask) {
@ -32,6 +33,7 @@ void debug(uint16_t cm, const char *msg, ...) {
#endif
}
}
#endif
void error(const char *msg, ...) {
char buf[1024];

4
util.h
View File

@ -23,7 +23,9 @@ enum {
DBG_MOD = 1 << 10,
DBG_SFX = 1 << 11,
DBG_FILE = 1 << 12,
DBG_DEMO = 1 << 13
DBG_DEMO = 1 << 13,
DBG_PRF = 1 << 14,
DBG_MIDI = 1 << 15
};
extern uint16_t g_debugMask;

View File

@ -121,6 +121,7 @@ void Video::updateWidescreen() {
_stub->copyWidescreenMirror(_w, _h, _backLayer);
} else if (_widescreenMode == kWidescreenBlur) {
_stub->copyWidescreenBlur(_w, _h, _backLayer);
} else if (_widescreenMode == kWidescreenCDi) {
} else {
_stub->clearWidescreen();
}
@ -498,14 +499,14 @@ static void PC_drawTileMask(uint8_t *dst, int x0, int y0, int w, int h, uint8_t
}
}
static void decodeSgd(uint8_t *dst, const uint8_t *src, const uint8_t *data, const bool isAmiga) {
static void decodeSgd(uint8_t *dst, const uint8_t *src, const uint8_t *data, const bool isAmiga, int level, int room) {
int num = -1;
uint8_t buf[256 * 32];
int count = READ_BE_UINT16(src) - 1; src += 2;
do {
int d2 = READ_BE_UINT16(src); src += 2;
const int d0 = (int16_t)READ_BE_UINT16(src); src += 2;
const int d1 = (int16_t)READ_BE_UINT16(src); src += 2;
int x_pos = (int16_t)READ_BE_UINT16(src); src += 2;
int y_pos = (int16_t)READ_BE_UINT16(src); src += 2;
if (d2 != 0xFFFF) {
d2 &= ~(1 << 15);
const int32_t offset = READ_BE_UINT32(data + d2 * 4);
@ -526,13 +527,16 @@ static void decodeSgd(uint8_t *dst, const uint8_t *src, const uint8_t *data, con
}
}
}
if (level == 0 && room == 26 && d2 == 38 && y_pos == 176) {
y_pos += 8;
}
const int w = (buf[0] + 1) >> 1;
const int h = buf[1] + 1;
const int planarSize = READ_BE_UINT16(buf + 2);
if (isAmiga) {
AMIGA_planar_mask(dst, d0, d1, w, h, buf + 4, buf + 4 + planarSize, planarSize);
AMIGA_planar_mask(dst, x_pos, y_pos, w, h, buf + 4, buf + 4 + planarSize, planarSize);
} else {
PC_drawTileMask(dst, d0, d1, w, h, buf + 4, buf + 4 + planarSize, planarSize);
PC_drawTileMask(dst, x_pos, y_pos, w, h, buf + 4, buf + 4 + planarSize, planarSize);
}
} while (--count >= 0);
}
@ -648,7 +652,7 @@ static void decodeLevHelper(uint8_t *dst, const uint8_t *src, int offset10, int
const int d3 = isPC ? READ_LE_UINT16(a0) : READ_BE_UINT16(a0); a0 += 2;
int d0 = d3 & 0x7FF;
if (d0 != 0 && sgdBuf) {
d0 -= 896;
d0 -= 0x380;
}
if (d0 != 0) {
const uint8_t *a2 = a5 + d0 * 32;
@ -659,6 +663,9 @@ static void decodeLevHelper(uint8_t *dst, const uint8_t *src, int offset10, int
mask = 0x10;
} else if ((d3 & 0x8000) != 0) {
mask = 0x80 + ((d3 >> 6) & 0x10);
if (d3 & 0x4000) {
mask = 0x90;
}
}
if (isPC) {
PC_drawTile(dst + y * 256 + x, a2, mask, xflip, yflip, 0);
@ -718,7 +725,7 @@ void Video::AMIGA_decodeLev(int level, int room) {
memset(_frontLayer, 0, _layerSize);
if (tmp[1] != 0) {
assert(_res->_sgd);
decodeSgd(_frontLayer, tmp + offset10, _res->_sgd, _res->isAmiga());
decodeSgd(_frontLayer, tmp + offset10, _res->_sgd, _res->isAmiga(), level, room);
offset10 = 0;
}
decodeLevHelper(_frontLayer, tmp, offset10, offset12, buf, tmp[1] != 0, _res->isDOS());
@ -1058,9 +1065,9 @@ void Video::fillRect(int x, int y, int w, int h, uint8_t color) {
static void fixOffsetDecodeBuffer(DecodeBuffer *buf, const uint8_t *dataPtr) {
if (buf->xflip) {
buf->x += (int16_t)READ_BE_UINT16(dataPtr + 4) - READ_BE_UINT16(dataPtr) - 1;
buf->x += (int16_t)READ_BE_UINT16(dataPtr + 4) - READ_BE_UINT16(dataPtr) - 1;
} else {
buf->x -= (int16_t)READ_BE_UINT16(dataPtr + 4);
buf->x -= (int16_t)READ_BE_UINT16(dataPtr + 4);
}
buf->y -= (int16_t)READ_BE_UINT16(dataPtr + 6);
}