Import 0.4.9

This commit is contained in:
Gregory Montoir 2021-09-06 00:00:00 +08:00
parent 315bb9bcff
commit 419cf91dfe
26 changed files with 662 additions and 421 deletions

View File

@ -1,3 +1,10 @@
* 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 repeating sounds volume
* release 0.4.8
- added detection for DOS version with .ABA files
- added Macintosh credits

View File

@ -3,10 +3,10 @@ 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
CXXFLAGS += -Wall -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 \

View File

@ -1,6 +1,6 @@
REminiscence README
Release version: 0.4.8
Release version: 0.4.9
-------------------------------------------------------------------------------
@ -62,23 +62,18 @@ In-game hotkeys :
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
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 write screenshot as .tga
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
Debug hotkeys :
Ctrl F toggle fast mode
Ctrl I Conrad 'infinite' life
Ctrl B toggle display of updated dirty blocks
Credits:
--------

View File

@ -42,13 +42,13 @@ void Game::col_clearState() {
void Game::col_preparePiegeState(LivePGE *pge) {
debug(DBG_COL, "Game::col_preparePiegeState() pge_num=%ld", pge - &_pgeLive[0]);
CollisionSlot *ct_slot1, *ct_slot2;
if (pge->init_PGE->unk1C == 0) {
if (pge->init_PGE->collision_data_len == 0) {
pge->collision_slot = 0xFF;
return;
}
int i = 0;
ct_slot1 = 0;
for (int c = 0; c < pge->init_PGE->unk1C; ++c) {
for (int c = 0; c < pge->init_PGE->collision_data_len; ++c) {
ct_slot2 = _col_curSlot;
if (ct_slot2 + 1 > &_col_slots[255])
return;
@ -231,7 +231,7 @@ int16_t Game::col_detectHit(LivePGE *pge, int16_t arg2, int16_t arg4, col_Callba
if (pge_room < 0 || pge_room >= 0x40) {
return 0;
}
int16_t thr = pge->init_PGE->counter_values[0];
int16_t thr = pge->init_PGE->data[0];
if (thr > 0) {
pos_dx = -1;
pos_dy = -1;
@ -452,9 +452,9 @@ int Game::col_detectGunHit(LivePGE *pge, int16_t arg2, int16_t arg4, col_Callbac
if (pge_room < 0 || pge_room >= 0x40) return 0;
int16_t thr, pos_dx, pos_dy;
if (argC == -1) {
thr = pge->init_PGE->counter_values[0];
thr = pge->init_PGE->data[0];
} else {
thr = pge->init_PGE->counter_values[3];
thr = pge->init_PGE->data[3];
}
if (thr > 0) {
pos_dx = -1;

View File

@ -108,6 +108,7 @@ int8_t CpcPlayer::readSampleData() {
// rewind
_f.seek(_restartPos);
nextChunk();
_sampleL = _sampleR = 0;
}
}
const int8_t data = _f.readByte();

View File

@ -147,16 +147,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->isMac()) {
if (p == _textBuf) {
while (p[len] != 0xA) {
if (p != _textBuf && _res->isMac()) {
len = *p++;
} else {
while (p[len] != 0xA && p[len]) {
++len;
}
} else {
len = *p++;
}
} else {
len = strlen((const char *)p);
}
Video::drawCharFunc dcf = _vid->_drawChar;
const uint8_t *fnt = (_res->_lang == LANG_JP) ? Video::_font8Jp : _res->_fnt;
@ -202,7 +198,6 @@ void Cutscene::clearBackPage() {
}
void Cutscene::drawCreditsText() {
if (_creditsSequence) {
if (_creditsKeepText) {
if (_creditsSlowText) {
return;
@ -266,7 +261,6 @@ void Cutscene::drawCreditsText() {
_creditsTextCounter -= 10;
}
drawText((_creditsTextPosX - 1) * 8, _creditsTextPosY * 8, _textBuf, 0xEF, _backPage, kTextJustifyLeft);
}
}
void Cutscene::drawProtectionShape(uint8_t shapeNum, int16_t zoom) {
@ -307,8 +301,16 @@ 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 = false;

View File

@ -30,6 +30,8 @@ struct Cutscene {
};
enum {
kCineDebut = 0,
kCineChute = 47,
kCineMemo = 48,
kCineVoyage = 52,
kCineEspions = 57

123
game.cpp
View File

@ -13,7 +13,7 @@
#include "systemstub.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, 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),
_stub(stub), _fs(fs), _savePath(savePath) {
@ -26,6 +26,7 @@ Game::Game(SystemStub *stub, FileSystem *fs, const char *savePath, int level, Re
_autoSave = autoSave;
_rewindPtr = -1;
_rewindLen = 0;
_cheats = cheats;
}
void Game::run() {
@ -56,7 +57,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;
@ -126,12 +127,11 @@ void Game::run() {
_skillLevel = kSkillNormal;
_currentLevel = _demoInputs[_demoBin].level;
_randSeed = 0;
_mix.stopMusic();
break;
}
} else {
_demoBin = -1;
_skillLevel = _menu._skill;
_currentLevel = _menu._level;
}
_mix.stopMusic();
break;
case kResourceTypeAmiga:
@ -171,7 +171,6 @@ void Game::run() {
if (_demoBin != -1 && _inp_demPos >= _res._demLen) {
debug(DBG_DEMO, "End of demo");
// exit level
_demoBin = -1;
_endLoop = true;
}
}
@ -360,15 +359,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() {
@ -417,7 +418,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) {
@ -431,6 +436,9 @@ void Game::mainLoop() {
_vid.fullRefresh();
}
}
if (_res.isDOS() && (_stub->_pi.dbgMask & PlayerInput::DF_AUTOZOOM) != 0) {
pge_updateZoom();
}
prepareAnims();
drawAnims();
drawCurrentInventoryItem();
@ -481,9 +489,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;
@ -532,22 +538,20 @@ void Game::playCutscene(int id) {
} else {
_cut._id = 0xFFFF;
}
_mix.stopMusic();
return;
}
}
}
if (_cut._id != 0x4A) {
_mix.playMusic(Cutscene::_musicTable[_cut._id]);
}
_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);
@ -561,9 +565,6 @@ void Game::playCutscene(int id) {
_cut.playCredits();
}
_mix.stopMusic();
if (_stub->hasWidescreen()) {
_stub->enableWidescreen(true);
}
}
}
@ -639,10 +640,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 +1028,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) {
@ -1540,6 +1541,10 @@ bool Game::hasLevelMap(int level, int room) const {
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 +1579,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 +1599,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 {
@ -1910,24 +1915,50 @@ 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 (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_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) {
@ -1942,6 +1973,7 @@ void Game::handleInventory() {
current_item = current_line * 4;
}
}
}
if (_stub->_pi.dirMask & PlayerInput::DIR_LEFT) {
_stub->_pi.dirMask &= ~PlayerInput::DIR_LEFT;
if (current_item > 0) {
@ -1990,6 +2022,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 +2040,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 +2072,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 +2118,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 +2149,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 +2183,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 +2229,9 @@ void Game::loadState(File *f) {
}
}
resetGameState();
if (version >= 3) {
_pge_opGunVar = f->readUint16BE();
}
}
void Game::clearStateRewind() {
@ -2234,7 +2273,7 @@ bool Game::loadStateRewind() {
}
File &f = _rewindBuffer[ptr];
f.seek(0);
loadState(&f);
loadState(&f, kSaveVersion);
if (_rewindLen > 0) {
--_rewindLen;
}

30
game.h
View File

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

View File

@ -137,6 +137,7 @@ struct Options {
bool play_carte_cutscene;
bool play_gamesaved_sound;
bool restore_memo_cutscene;
bool order_inventory_original;
};
struct Color {
@ -172,8 +173,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 +184,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 +195,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 +210,7 @@ struct LivePGE {
struct MessagePGE {
MessagePGE *next_entry;
uint16_t index; // src_pge
uint16_t src_pge;
uint16_t msg_num;
};
@ -268,9 +269,9 @@ struct BankSlot {
struct CollisionSlot2 {
CollisionSlot2 *next_slot;
int8_t *unk2;
int8_t *unk2; // grid_data_pos
uint8_t data_size;
uint8_t data_buf[0x10]; // XXX check size
uint8_t data_buf[0x10]; // <= InitPGE.collision_data_len
};
struct InventoryItem {

View File

@ -102,6 +102,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;
@ -123,6 +124,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";
@ -194,6 +196,7 @@ int main(int argc, char *argv[]) {
int levelNum = 0;
bool fullscreen = false;
bool autoSave = false;
uint32_t cheats = 0;
WidescreenMode widescreen = kWidescreenNone;
ScalerParameters scalerParameters = ScalerParameters::defaults();
int forcedLanguage = -1;
@ -214,6 +217,7 @@ 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 },
{ 0, 0, 0, 0 }
};
int index;
@ -264,6 +268,9 @@ int main(int argc, char *argv[]) {
case 8:
autoSave = true;
break;
case 9:
cheats = atoi(optarg);
break;
default:
printf(USAGE, argv[0]);
return 0;
@ -279,7 +286,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, cheats);
stub->init(g_caption, g->_vid._w, g->_vid._h, fullscreen, widescreen, &scalerParameters);
g->run();
delete g;

View File

@ -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;

View File

@ -42,6 +42,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 {
@ -89,22 +90,21 @@ static bool isMusicSfx(int num) {
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;
}
}
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;
}
}

View File

@ -7,6 +7,9 @@
#ifdef USE_TREMOR
#include <tremor/ivorbisfile.h>
#endif
#ifdef USE_STB_VORBIS
#include "stb_vorbis.c"
#endif
#include "file.h"
#include "mixer.h"
#include "ogg_player.h"
@ -67,13 +70,17 @@ struct OggDecoder_impl {
}
}
bool load(VorbisFile *f, int mixerSampleRate) {
bool load(const char *name, FileSystem *fs, int mixerSampleRate) {
if (!_f.open(name, "rb", fs)) {
return false;
}
_f.offset = 0;
ov_callbacks ovcb;
ovcb.read_func = VorbisFile::readHelper;
ovcb.seek_func = VorbisFile::seekHelper;
ovcb.close_func = VorbisFile::closeHelper;
ovcb.tell_func = VorbisFile::tellHelper;
if (ov_open_callbacks(f, &_ovf, 0, 0, ovcb) < 0) {
if (ov_open_callbacks(&_f, &_ovf, 0, 0, ovcb) < 0) {
warning("Invalid .ogg file");
return false;
}
@ -131,6 +138,7 @@ struct OggDecoder_impl {
return count;
}
VorbisFile _f;
OggVorbis_File _ovf;
int _channels;
bool _open;
@ -139,66 +147,163 @@ 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) {
const int sample = (_decodedSamples[0][i] + _decodedSamples[1][i]) / 2;
*dst = ADDC_S16(*dst, ((sample * 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);
const int sample = (l + r) / 2;
*dst = ADDC_S16(*dst, ((sample * 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;
}
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())) {
if (_impl->load(buf, _fs, _mix->getSampleRate())) {
debug(DBG_INFO, "Playing '%s'", buf);
_mix->setPremixHook(mixCallback, this);
return true;
}
}
delete vf;
#endif
return false;
}
void OggPlayer::stopTrack() {
#ifdef USE_TREMOR
if (_impl) {
_mix->setPremixHook(0, 0);
delete _impl;
_impl = 0;
#endif
}
}
void OggPlayer::pauseTrack() {
#ifdef USE_TREMOR
if (_impl) {
_mix->setPremixHook(0, 0);
}
#endif
}
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;
}

242
piege.cpp
View File

@ -16,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;
}
@ -33,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;
@ -44,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;
@ -76,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;
@ -317,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) {
@ -369,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;
@ -435,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) {
@ -809,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;
}
@ -1102,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;
@ -1114,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;
@ -1127,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;
@ -1146,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;
}
@ -1168,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;
@ -1177,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;
@ -1266,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);
@ -1281,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;
}
@ -1297,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;
@ -1306,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;
@ -1327,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;
@ -1367,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;
@ -1401,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;
}
@ -1568,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);
@ -1592,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;
@ -1606,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;
@ -1685,7 +1688,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;
}
@ -1700,7 +1703,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;
}
@ -1735,7 +1738,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;
@ -1776,7 +1779,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;
@ -1842,7 +1845,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;
}
}
@ -1856,7 +1859,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;
}
@ -1864,7 +1867,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) {
@ -1877,30 +1880,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) {
@ -1962,7 +1962,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);
@ -1978,13 +1978,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;
@ -1992,7 +1992,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;
@ -2008,17 +2008,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);
@ -2031,7 +2031,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) {
@ -2047,7 +2047,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;
@ -2057,9 +2057,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;
@ -2069,14 +2069,14 @@ 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 {
++i;
@ -2089,14 +2089,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;
@ -2147,10 +2147,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) {
@ -2159,13 +2165,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;
@ -2269,3 +2275,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;
}
}
}

View File

@ -1064,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();
@ -1076,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();
}
@ -1095,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++;
@ -1107,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;
}
@ -1417,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();

View File

@ -15,7 +15,7 @@ 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(const char *aba) {

7
rs.cfg
View File

@ -19,14 +19,14 @@ 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
# 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 +45,6 @@ play_gamesaved_sound=true
# restore content from 'MEMO' cutscene
restore_memo_cutscene=true
# order inventory items as the original game, last grabbed item is at the bottom of the inventory
order_inventory_original=false

View File

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

View File

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

View File

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

View File

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

View File

@ -3585,7 +3585,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 +3606,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 +3665,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[] = {

View File

@ -20,7 +20,8 @@ struct PlayerInput {
enum {
DF_FASTMODE = 1 << 0,
DF_DBLOCKS = 1 << 1,
DF_SETLIFE = 1 << 2
DF_SETLIFE = 1 << 2,
DF_AUTOZOOM = 1 << 3
};
uint8_t dirMask;
@ -68,6 +69,7 @@ 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;
@ -99,6 +101,22 @@ struct LockAudioStack {
SystemStub *_stub;
};
struct ToggleWidescreenStack {
ToggleWidescreenStack(SystemStub *stub, bool state)
: _stub(stub), _state(state) {
if (_stub->hasWidescreen()) {
_stub->enableWidescreen(_state);
}
}
~ToggleWidescreenStack() {
if (_stub->hasWidescreen()) {
_stub->enableWidescreen(!_state);
}
}
SystemStub *_stub;
bool _state;
};
extern SystemStub *SystemStub_SDL_create();
#endif // SYSTEMSTUB_H__

View File

@ -31,6 +31,7 @@ struct SystemStub_SDL : SystemStub {
SDL_Renderer *_renderer;
SDL_Texture *_texture;
int _texW, _texH;
SDL_Rect _texRect;
SDL_GameController *_controller;
SDL_PixelFormat *_fmt;
const char *_caption;
@ -66,6 +67,7 @@ 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);
@ -98,7 +100,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 +271,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;
@ -502,7 +514,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 +536,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() {
@ -740,8 +756,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 +787,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 +805,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 +853,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:
@ -935,6 +963,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;
@ -1061,7 +1093,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) {