Compare commits
5 Commits
Author | SHA1 | Date |
---|---|---|
|
64f00204e7 | |
|
100218a3c2 | |
|
1f86fdea2d | |
|
419cf91dfe | |
|
315bb9bcff |
|
@ -0,0 +1,4 @@
|
|||
*.o
|
||||
*.d
|
||||
*.lha
|
||||
rs
|
21
CHANGES.txt
21
CHANGES.txt
|
@ -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
|
||||
|
|
21
Makefile
21
Makefile
|
@ -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)
|
||||
|
|
|
@ -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)
|
72
README.txt
72
README.txt
|
@ -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:
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
163
cutscene.cpp
163
cutscene.cpp
|
@ -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) {
|
||||
|
|
20
cutscene.h
20
cutscene.h
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
17
file.cpp
17
file.cpp
|
@ -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
1
file.h
|
@ -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
4
fs.cpp
|
@ -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
234
game.cpp
|
@ -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
30
game.h
|
@ -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();
|
||||
|
|
30
intern.h
30
intern.h
|
@ -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 {
|
||||
|
|
54
main.cpp
54
main.cpp
|
@ -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;
|
||||
|
|
32
menu.cpp
32
menu.cpp
|
@ -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;
|
||||
|
|
|
@ -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__ */
|
|
@ -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
|
||||
};
|
|
@ -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
|
||||
};
|
|
@ -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;
|
||||
}
|
|
@ -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__ */
|
66
mixer.cpp
66
mixer.cpp
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
7
mixer.h
7
mixer.h
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
179
ogg_player.cpp
179
ogg_player.cpp
|
@ -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
278
piege.cpp
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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.
|
@ -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.
|
@ -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.
154
resource.cpp
154
resource.cpp
|
@ -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() {
|
||||
|
|
22
resource.h
22
resource.h
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
10
rs.cfg
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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__
|
||||
|
|
|
@ -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;
|
||||
|
|
105
staticres.cpp
105
staticres.cpp
|
@ -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);
|
||||
|
|
File diff suppressed because it is too large
Load Diff
21
systemstub.h
21
systemstub.h
|
@ -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__
|
||||
|
|
|
@ -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) {
|
||||
|
|
18
unpack.cpp
18
unpack.cpp
|
@ -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) {
|
||||
|
|
2
util.cpp
2
util.cpp
|
@ -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
4
util.h
|
@ -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;
|
||||
|
|
25
video.cpp
25
video.cpp
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue