Import 0.4.4

This commit is contained in:
Gregory Montoir 2019-10-28 00:00:00 +08:00
parent 514785d0a1
commit 222984d851
25 changed files with 526 additions and 135 deletions

View File

@ -7,7 +7,7 @@ MODPLUG_LIBS := -lmodplug
TREMOR_LIBS := -lvorbisidec -logg
ZLIB_LIBS := -lz
CXXFLAGS += -O2 -Wall -MMD $(SDL_CFLAGS) -DUSE_MODPLUG -DUSE_STATIC_SCALER -DUSE_TREMOR -DUSE_ZLIB
CXXFLAGS += -Wall -Wpedantic -MMD $(SDL_CFLAGS) -DUSE_MODPLUG -DUSE_STATIC_SCALER -DUSE_TREMOR -DUSE_ZLIB
SRCS = collision.cpp cutscene.cpp decode_mac.cpp dynlib.cpp file.cpp fs.cpp game.cpp graphics.cpp main.cpp \
menu.cpp mixer.cpp mod_player.cpp ogg_player.cpp piege.cpp resource.cpp resource_aba.cpp \

View File

@ -1,6 +1,6 @@
REminiscence README
Release version: 0.4.3
Release version: 0.4.4
-------------------------------------------------------------------------------
@ -46,6 +46,7 @@ These paths can be changed using command line switches :
--widescreen=MODE 16:9 display
--scaler=NAME@X Graphics scaler (default 'scale@3')
--language=LANG Language (fr,en,de,sp,it,jp)
--autosave Save game state automatically
The scaler option specifies the algorithm used to smoothen the image in
addition to a scaling factor. External scalers are also supported, the suffix
@ -53,7 +54,7 @@ shall be used as the name. Eg. If you have scaler_xbrz.dll, you can pass
'--scaler xbrz@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 (default)
'adjacent' : left and right rooms bitmaps will be drawn
'mirror' : the current room bitmap will be drawn mirrored
In-game hotkeys :
@ -83,7 +84,6 @@ Credits:
Delphine Software, obviously, for making another great game.
Yaz0r, Pixel and gawd for sharing information they gathered on the game.
Nicolas Bondoux for sound fixes.
Contact:

View File

@ -112,12 +112,12 @@ static bool isNewLineChar(uint8_t chr, Resource *res) {
return chr == nl;
}
uint16_t Cutscene::findTextSeparators(const uint8_t *p) {
uint16_t Cutscene::findTextSeparators(const uint8_t *p, int len) {
uint8_t *q = _textSep;
uint16_t ret = 0;
uint16_t pos = 0;
for (; *p != 0xA && *p; ++p) {
if (isNewLineChar(*p, _res)) {
for (int i = 0; i < len && p[i] != 0xA; ++i) {
if (isNewLineChar(p[i], _res)) {
*q++ = pos;
if (pos > ret) {
ret = pos;
@ -137,41 +137,43 @@ uint16_t Cutscene::findTextSeparators(const uint8_t *p) {
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) {
warning("Unhandled Cutscene::drawText"); // TODO
return;
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;
uint16_t last_sep = 0;
uint16_t lastSep = 0;
if (textJustify != kTextJustifyLeft) {
last_sep = findTextSeparators(p);
lastSep = findTextSeparators(p, len);
if (textJustify != kTextJustifyCenter) {
last_sep = (_res->_lang == LANG_JP) ? 20 : 30;
lastSep = (_res->_lang == LANG_JP) ? 20 : 30;
}
}
const uint8_t *sep = _textSep;
y += 50;
x += (_res->_lang == LANG_JP) ? 0 : 8;
int16_t yy = y;
int16_t xx = x;
int16_t yPos = y;
int16_t xPos = x;
if (textJustify != kTextJustifyLeft) {
xx += ((last_sep - *sep++) / 2) * Video::CHAR_W;
xPos += ((lastSep - *sep++) / 2) * Video::CHAR_W;
}
for (; *p != 0xA && *p; ++p) {
if (isNewLineChar(*p, _res)) {
yy += Video::CHAR_H;
xx = x;
for (int i = 0; i < len && p[i] != 0xA; ++i) {
if (isNewLineChar(p[i], _res)) {
yPos += Video::CHAR_H;
xPos = x;
if (textJustify != kTextJustifyLeft) {
xx += ((last_sep - *sep++) / 2) * Video::CHAR_W;
xPos += ((lastSep - *sep++) / 2) * Video::CHAR_W;
}
} else if (*p == 0x20) {
xx += Video::CHAR_W;
} else if (*p == 0x9) {
} else if (p[i] == 0x20) {
xPos += Video::CHAR_W;
} else if (p[i] == 0x9) {
// ignore tab
} else {
(_vid->*dcf)(page, _vid->_w, xx, yy, fnt, color, *p);
xx += Video::CHAR_W;
(_vid->*dcf)(page, _vid->_w, xPos, yPos, fnt, color, p[i]);
xPos += Video::CHAR_W;
}
}
}
@ -415,9 +417,12 @@ void Cutscene::op_drawStringAtBottom() {
}
}
memset(_pageC + 179 * _vid->_w, 0xC0, 45 * _vid->_w);
memset(_page1 + 179 * _vid->_w, 0xC0, 45 * _vid->_w);
memset(_page0 + 179 * _vid->_w, 0xC0, 45 * _vid->_w);
const int h = 45 * _vid->_layerScale;
const int y = Video::GAMESCREEN_H * _vid->_layerScale - h;
memset(_pageC + y * _vid->_w, 0xC0, h * _vid->_w);
memset(_page1 + y * _vid->_w, 0xC0, h * _vid->_w);
memset(_page0 + y * _vid->_w, 0xC0, h * _vid->_w);
if (strId != 0xFFFF) {
const uint8_t *str = _res->getCineString(strId);
if (str) {
@ -878,7 +883,7 @@ void Cutscene::op_drawStringAtPos() {
// 'voyage' - cutscene script redraws the string to refresh the screen
if (_id == 0x34 && (strId & 0xFFF) == 0x45) {
if ((_cmdPtr - _cmdPtrBak) == 0xA) {
_stub->copyRect(0, 0, _vid->_w, _vid->_h, _page1, 256);
_stub->copyRect(0, 0, _vid->_w, _vid->_h, _page1, _vid->_w);
_stub->updateScreen(0);
} else {
_stub->sleep(15);
@ -1059,9 +1064,7 @@ void Cutscene::unload() {
void Cutscene::prepare() {
_page0 = _vid->_frontLayer;
_page1 = _vid->_tempLayer;
memset(_page1, 0, _vid->_layerSize);
_pageC = _vid->_tempLayer2;
memset(_pageC, 0, _vid->_layerSize);
_stub->_pi.dirMask = 0;
_stub->_pi.enter = false;
_stub->_pi.space = false;
@ -1080,6 +1083,10 @@ void Cutscene::prepare() {
}
void Cutscene::playCredits() {
if (_res->isMac()) {
warning("Cutscene::playCredits() unimplemented");
return;
}
_textCurPtr = _res->isAmiga() ? _creditsDataAmiga : _creditsDataDOS;
_textBuf[0] = 0xA;
_textCurBuf = _textBuf;

View File

@ -113,7 +113,7 @@ struct Cutscene {
void updatePalette();
void setPalette();
void setRotationTransform(uint16_t a, uint16_t b, uint16_t c);
uint16_t findTextSeparators(const uint8_t *p);
uint16_t findTextSeparators(const uint8_t *p, int len);
void drawText(int16_t x, int16_t y, const uint8_t *p, uint16_t color, uint8_t *page, int textJustify);
void swapLayers();
void drawCreditsText();

141
decode_mac.cpp Normal file
View File

@ -0,0 +1,141 @@
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include "decode_mac.h"
#include "file.h"
uint8_t *decodeLzss(File &f, uint32_t &decodedSize) {
decodedSize = f.readUint32BE();
uint8_t *dst = (uint8_t *)malloc(decodedSize);
uint32_t count = 0;
while (count < decodedSize) {
const int code = f.readByte();
for (int i = 0; i < 8 && count < decodedSize; ++i) {
if ((code & (1 << i)) == 0) {
dst[count++] = f.readByte();
} else {
int offset = f.readUint16BE();
const int len = (offset >> 12) + 3;
offset &= 0xFFF;
for (int j = 0; j < len; ++j) {
dst[count + j] = dst[count - offset - 1 + j];
}
count += len;
}
}
}
assert(count == decodedSize);
return dst;
}
static void setPixel(int x, int y, int w, int h, uint8_t color, DecodeBuffer *buf) {
buf->setPixel(buf, x, y, w, h, color);
}
void decodeC103(const uint8_t *a3, int w, int h, DecodeBuffer *buf) {
uint8_t d0;
int d3 = 0;
int d7 = 1;
int d6 = 0;
int d1 = 0;
static const uint32_t d5 = 0xFFF;
uint8_t a1[0x1000];
for (int y = 0; y < h; ++y) {
for (int x = 0; x < w; ++x) {
assert(d6 >= 0);
if (d6 == 0) {
int carry = d7 & 1;
d7 >>= 1;
if (d7 == 0) {
d7 = *a3++;
const int extended_bit = carry ? 0x80 : 0;
carry = d7 & 1;
d7 = extended_bit | (d7 >> 1);
}
if (!carry) {
d0 = *a3++;
a1[d3] = d0;
++d3;
d3 &= d5;
setPixel(x, y, w, h, d0, buf);
continue;
}
d1 = READ_BE_UINT16(a3); a3 += 2;
d6 = d1;
d1 &= d5;
++d1;
d1 = (d3 - d1) & d5;
d6 >>= 12;
d6 += 3;
}
d0 = a1[d1++];
d1 &= d5;
a1[d3++] = d0;
d3 &= d5;
setPixel(x, y, w, h, d0, buf);
--d6;
}
}
}
void decodeC211(const uint8_t *a3, int w, int h, DecodeBuffer *buf) {
struct {
const uint8_t *ptr;
int repeatCount;
} stack[512];
int y = 0;
int x = 0;
int sp = 0;
while (1) {
uint8_t d0 = *a3++;
if ((d0 & 0x80) != 0) {
++y;
x = 0;
}
int d1 = d0 & 0x1F;
if (d1 == 0) {
d1 = READ_BE_UINT16(a3); a3 += 2;
}
const int carry_set = (d0 & 0x40) != 0;
d0 <<= 2;
if (!carry_set) {
if ((d0 & 0x80) == 0) {
--d1;
if (d1 == 0) {
assert(sp > 0);
--stack[sp - 1].repeatCount;
if (stack[sp - 1].repeatCount >= 0) {
a3 = stack[sp - 1].ptr;
} else {
--sp;
}
} else {
assert(sp < ARRAYSIZE(stack));
stack[sp].ptr = a3;
stack[sp].repeatCount = d1;
++sp;
}
} else {
x += d1;
}
} else {
if ((d0 & 0x80) == 0) {
if (d1 == 1) {
return;
}
const uint8_t color = *a3++;
for (int i = 0; i < d1; ++i) {
setPixel(x++, y, w, h, color, buf);
}
} else {
for (int i = 0; i < d1; ++i) {
setPixel(x++, y, w, h, *a3++, buf);
}
}
}
}
}

23
decode_mac.h Normal file
View File

@ -0,0 +1,23 @@
#ifndef DECODE_MAC_H__
#define DECODE_MAC_H__
#include <stdint.h>
#include "file.h"
uint8_t *decodeLzss(File &f, uint32_t &decodedSize);
struct DecodeBuffer {
uint8_t *ptr;
int w, h, pitch;
int x, y;
bool xflip;
void (*setPixel)(DecodeBuffer *buf, int src_x, int src_y, int src_w, int src_h, uint8_t color);
void *dataPtr;
};
void decodeC103(const uint8_t *a3, int w, int h, DecodeBuffer *buf);
void decodeC211(const uint8_t *a3, int w, int h, DecodeBuffer *buf);
#endif

View File

@ -14,7 +14,7 @@
#include "unpack.h"
#include "util.h"
Game::Game(SystemStub *stub, FileSystem *fs, const char *savePath, int level, ResourceType ver, Language lang, WidescreenMode widescreenMode)
Game::Game(SystemStub *stub, FileSystem *fs, const char *savePath, int level, ResourceType ver, Language lang, WidescreenMode widescreenMode, bool autoSave)
: _cut(&_res, stub, &_vid), _menu(&_res, stub, &_vid),
_mix(fs, stub), _res(fs, ver, lang), _seq(stub, &_mix), _vid(&_res, stub),
_stub(stub), _fs(fs), _savePath(savePath) {
@ -24,6 +24,7 @@ Game::Game(SystemStub *stub, FileSystem *fs, const char *savePath, int level, Re
_currentLevel = _menu._level = level;
_demoBin = -1;
_widescreenMode = widescreenMode;
_autoSave = autoSave;
}
void Game::run() {
@ -150,6 +151,7 @@ void Game::run() {
resetGameState();
_endLoop = false;
_frameTimestamp = _stub->getTimeStamp();
_saveTimestamp = _frameTimestamp;
while (!_stub->_pi.quit && !_endLoop) {
mainLoop();
if (_demoBin != -1 && _inp_demPos >= _res._demLen) {
@ -166,8 +168,8 @@ void Game::run() {
_stub->_pi.shift = false;
// clear widescreen borders
if (_stub->hasWidescreen()) {
_stub->copyRectLeftBorder(Video::GAMESCREEN_W, Video::GAMESCREEN_H, 0);
_stub->copyRectRightBorder(Video::GAMESCREEN_W, Video::GAMESCREEN_H, 0);
_stub->copyRectLeftBorder(_vid._w, _vid._h, 0);
_stub->copyRectRightBorder(_vid._w, _vid._h, 0);
}
}
}
@ -271,10 +273,34 @@ void Game::displayTitleScreenMac(int num) {
Color c;
c.r = c.g = c.b = 0;
_stub->setPaletteEntry(0, &c);
} else if (num == Menu::kMacTitleScreen_Flashback) {
_vid.setTextPalette();
_vid._charShadowColor = 0xE0;
}
_stub->copyRect(0, 0, _vid._w, _vid._h, _vid._frontLayer, _vid._w);
_stub->updateScreen(0);
while (1) {
if (num == Menu::kMacTitleScreen_Flashback) {
static const uint8_t selectedColor = 0xE4;
static const uint8_t defaultColor = 0xE8;
for (int i = 0; i < 7; ++i) {
const char *str = Menu::_levelNames[i];
_vid.drawString(str, 24, 24 + i * 16, (_currentLevel == i) ? selectedColor : defaultColor);
}
if (_stub->_pi.dirMask & PlayerInput::DIR_UP) {
_stub->_pi.dirMask &= ~PlayerInput::DIR_UP;
if (_currentLevel > 0) {
--_currentLevel;
}
}
if (_stub->_pi.dirMask & PlayerInput::DIR_DOWN) {
_stub->_pi.dirMask &= ~PlayerInput::DIR_DOWN;
if (_currentLevel < 6) {
++_currentLevel;
}
}
_vid.updateScreen();
}
_stub->processEvents();
if (_stub->_pi.quit) {
break;
@ -324,10 +350,10 @@ void Game::mainLoop() {
playCutscene(0x41);
_endLoop = true;
} else {
if (_validSaveState) {
if (!loadGameState(0)) {
_endLoop = true;
}
if (_autoSave && loadGameState(kAutoSaveSlot)) {
// autosave
} else if (_validSaveState && loadGameState(kIngameSaveSlot)) {
// ingame save
} else {
loadLevelData();
resetGameState();
@ -393,6 +419,13 @@ void Game::mainLoop() {
}
}
inp_handleSpecialKeys();
if (_stub->getTimeStamp() - _saveTimestamp >= kAutoSaveIntervalMs) {
// do not save if we just died
if (_pgeLive[0].life > 0) {
saveGameState(kAutoSaveSlot);
_saveTimestamp = _stub->getTimeStamp();
}
}
}
void Game::updateTiming() {
@ -474,7 +507,7 @@ void Game::playCutscene(int id) {
_cut.play();
}
}
if (_res._type == kResourceTypeMac) {
if (_res._type == kResourceTypeMac && !(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);
@ -533,8 +566,8 @@ void Game::drawCurrentInventoryItem() {
void Game::showFinalScore() {
if (_stub->hasWidescreen()) {
_stub->copyRectLeftBorder(Video::GAMESCREEN_W, Video::GAMESCREEN_H, 0);
_stub->copyRectRightBorder(Video::GAMESCREEN_W, Video::GAMESCREEN_H, 0);
_stub->copyRectLeftBorder(_vid._w, _vid._h, 0);
_stub->copyRectRightBorder(_vid._w, _vid._h, 0);
}
playCutscene(0x49);
char buf[50];
@ -697,8 +730,8 @@ bool Game::handleConfigPanel() {
bool Game::handleContinueAbort() {
if (_stub->hasWidescreen()) {
_stub->copyRectLeftBorder(Video::GAMESCREEN_W, Video::GAMESCREEN_H, 0);
_stub->copyRectRightBorder(Video::GAMESCREEN_W, Video::GAMESCREEN_H, 0);
_stub->copyRectLeftBorder(_vid._w, _vid._h, 0);
_stub->copyRectRightBorder(_vid._w, _vid._h, 0);
}
playCutscene(0x48);
int timeout = 100;
@ -1362,7 +1395,7 @@ void Game::drawObjectFrame(const uint8_t *bankDataPtr, const uint8_t *dataPtr, i
_vid.drawSpriteSub4(src, _vid._frontLayer + dst_offset, sprite_w, sprite_clipped_h, sprite_clipped_w, sprite_col_mask);
}
}
_vid.markBlockAsDirty(sprite_x, sprite_y, sprite_clipped_w, sprite_clipped_h);
_vid.markBlockAsDirty(sprite_x, sprite_y, sprite_clipped_w, sprite_clipped_h, _vid._layerScale);
}
void Game::decodeCharacterFrame(const uint8_t *dataPtr, uint8_t *dstPtr) {
@ -1499,7 +1532,7 @@ void Game::drawCharacter(const uint8_t *dataPtr, int16_t pos_x, int16_t pos_y, u
_vid.drawSpriteSub4(src, _vid._frontLayer + dst_offset, sprite_w, sprite_clipped_h, sprite_clipped_w, sprite_col_mask);
}
}
_vid.markBlockAsDirty(pos_x, pos_y, sprite_clipped_w, sprite_clipped_h);
_vid.markBlockAsDirty(pos_x, pos_y, sprite_clipped_w, sprite_clipped_h, _vid._layerScale);
}
int Game::loadMonsterSprites(LivePGE *pge) {
@ -1617,14 +1650,30 @@ void Game::loadLevelMap() {
}
}
_vid.PC_decodeMap(_currentLevel, _currentRoom);
if (_stub->hasWidescreen() && _widescreenMode == kWidescreenMirrorRoom) {
_stub->copyRectMirrorBorders(Video::GAMESCREEN_W, Video::GAMESCREEN_H, _vid._backLayer);
}
break;
case kResourceTypeMac:
if (_stub->hasWidescreen() && _widescreenMode == kWidescreenAdjacentRooms) {
const int leftRoom = _res._ctData[CT_LEFT_ROOM + _currentRoom];
if (leftRoom > 0 && hasLevelMap(_currentLevel, leftRoom)) {
_vid.MAC_decodeMap(_currentLevel, leftRoom);
_stub->copyRectLeftBorder(_vid._w, _vid._h, _vid._backLayer);
} else {
_stub->copyRectLeftBorder(_vid._w, _vid._h, 0);
}
const int rightRoom = _res._ctData[CT_RIGHT_ROOM + _currentRoom];
if (rightRoom > 0 && hasLevelMap(_currentLevel, rightRoom)) {
_vid.MAC_decodeMap(_currentLevel, rightRoom);
_stub->copyRectRightBorder(_vid._w, _vid._h, _vid._backLayer);
} else {
_stub->copyRectRightBorder(_vid._w, _vid._h, 0);
}
}
_vid.MAC_decodeMap(_currentLevel, _currentRoom);
break;
}
if (_stub->hasWidescreen() && _widescreenMode == kWidescreenMirrorRoom) {
_stub->copyRectMirrorBorders(_vid._w, _vid._h, _vid._backLayer);
}
}
void Game::loadLevelData() {
@ -1806,7 +1855,7 @@ void Game::drawIcon(uint8_t iconNum, int16_t x, int16_t y, uint8_t colMask) {
return;
}
_vid.drawSpriteSub1(buf, _vid._frontLayer + x + y * _vid._w, 16, 16, 16, colMask << 4);
_vid.markBlockAsDirty(x, y, 16, 16);
_vid.markBlockAsDirty(x, y, 16, 16, _vid._layerScale);
}
void Game::playSound(uint8_t sfxId, uint8_t softVol) {
@ -2006,7 +2055,7 @@ static const uint32_t TAG_FBSV = 0x46425356;
bool Game::saveGameState(uint8_t slot) {
bool success = false;
char stateFile[20];
char stateFile[32];
makeGameStateName(slot, stateFile);
File f;
if (!f.open(stateFile, "zwb", _savePath)) {
@ -2033,7 +2082,7 @@ bool Game::saveGameState(uint8_t slot) {
bool Game::loadGameState(uint8_t slot) {
bool success = false;
char stateFile[20];
char stateFile[32];
makeGameStateName(slot, stateFile);
File f;
if (!f.open(stateFile, "zrb", _savePath)) {

10
game.h
View File

@ -25,6 +25,12 @@ struct Game {
typedef int (Game::*col_Callback1)(LivePGE *, LivePGE *, int16_t, int16_t);
typedef int (Game::*col_Callback2)(LivePGE *, int16_t, int16_t, int16_t);
enum {
kIngameSaveSlot = 0,
kAutoSaveSlot = 255,
kAutoSaveIntervalMs = 30 * 1000
};
enum {
CT_UP_ROOM = 0x00,
CT_DOWN_ROOM = 0x40,
@ -87,8 +93,10 @@ struct Game {
bool _endLoop;
uint32_t _frameTimestamp;
WidescreenMode _widescreenMode;
bool _autoSave;
uint32_t _saveTimestamp;
Game(SystemStub *, FileSystem *, const char *savePath, int level, ResourceType ver, Language lang, WidescreenMode widescreenMode);
Game(SystemStub *, FileSystem *, const char *savePath, int level, ResourceType ver, Language lang, WidescreenMode widescreenMode, bool autoSave);
void run();
void displayTitleScreenAmiga();

View File

@ -211,21 +211,18 @@ void Graphics::fillArea(uint8_t color, bool hasAlpha) {
if (x1 >= 0) {
if (hasAlpha && color > 0xC7) {
do {
int16_t x2 = *pts++;
if (x2 < _crw && x2 >= x1) {
int len = x2 - x1 + 1;
for (int i = 0; i < len; ++i) {
*(dst + x1 + i) |= color & 8; // XXX 0x88
}
const int16_t x2 = MIN<int16_t>(_crw - 1, *pts++);
for (; x1 <= x2; ++x1) {
*(dst + x1) |= color & 8;
}
dst += _layerPitch;
x1 = *pts++;
} while (x1 >= 0);
} else {
do {
int16_t x2 = *pts++;
if (x2 < _crw && x2 >= x1) {
int len = x2 - x1 + 1;
const int16_t x2 = MIN<int16_t>(_crw - 1, *pts++);
if (x1 <= x2) {
const int len = x2 - x1 + 1;
memset(dst + x1, color, len);
}
dst += _layerPitch;

View File

@ -13,12 +13,6 @@
#include <assert.h>
#include <stdint.h>
#undef ABS
#define ABS(x) ((x)<0?-(x):(x))
#undef MAX
#define MAX(x,y) ((x)>(y)?(x):(y))
#undef MIN
#define MIN(x,y) ((x)<(y)?(x):(y))
#undef ARRAYSIZE
#define ARRAYSIZE(a) (int)(sizeof(a)/sizeof(a[0]))
@ -42,16 +36,6 @@ inline uint32_t READ_LE_UINT32(const void *ptr) {
return (b[3] << 24) | (b[2] << 16) | (b[1] << 8) | b[0];
}
inline int8_t ADDC_S8(int a, int b) {
a += b;
if (a < -128) {
a = -128;
} else if (a > 127) {
a = 127;
}
return a;
}
inline int16_t ADDC_S16(int a, int b) {
a += b;
if (a < -32768) {
@ -79,6 +63,24 @@ inline T CLIP(const T& val, const T& a, const T& b) {
return val;
}
#undef MIN
template<typename T>
inline T MIN(T v1, T v2) {
return (v1 < v2) ? v1 : v2;
}
#undef MAX
template<typename T>
inline T MAX(T v1, T v2) {
return (v1 > v2) ? v1 : v2;
}
#undef ABS
template<typename T>
inline T ABS(T t) {
return (t < 0) ? -t : t;
}
enum Language {
LANG_FR,
LANG_EN,

View File

@ -25,6 +25,7 @@ static const char *USAGE =
" --widescreen=MODE 16:9 display\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"
;
static int detectVersion(FileSystem *fs) {
@ -215,6 +216,7 @@ int main(int argc, char *argv[]) {
const char *savePath = ".";
int levelNum = 0;
bool fullscreen = false;
bool autoSave = false;
WidescreenMode widescreen = kWidescreenNone;
ScalerParameters scalerParameters = ScalerParameters::defaults();
int forcedLanguage = -1;
@ -234,6 +236,7 @@ int main(int argc, char *argv[]) {
{ "scaler", required_argument, 0, 5 },
{ "language", required_argument, 0, 6 },
{ "widescreen", required_argument, 0, 7 },
{ "autosave", no_argument, 0, 8 },
{ 0, 0, 0, 0 }
};
int index;
@ -281,6 +284,9 @@ int main(int argc, char *argv[]) {
case 7:
widescreen = parseWidescreen(optarg);
break;
case 8:
autoSave = true;
break;
default:
printf(USAGE, argv[0]);
return 0;
@ -296,7 +302,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);
Game *g = new Game(stub, &fs, savePath, levelNum, (ResourceType)version, language, widescreen, autoSave);
stub->init(g_caption, g->_vid._w, g->_vid._h, fullscreen, widescreen != kWidescreenNone, &scalerParameters);
g->run();
delete g;

View File

@ -84,7 +84,7 @@ void Menu::drawString2(const char *str, int16_t y, int16_t x) {
}
break;
}
_vid->markBlockAsDirty(x * w, y * h, len * w, h);
_vid->markBlockAsDirty(x * w, y * h, len * w, h, _vid->_layerScale);
}
void Menu::loadPicture(const char *prefix) {
@ -201,7 +201,7 @@ bool Menu::handlePasswordScreen() {
}
_vid->PC_drawChar(0x20, 21, len + 15);
_vid->markBlockAsDirty(15 * Video::CHAR_W, 21 * Video::CHAR_H, (len + 1) * Video::CHAR_W, Video::CHAR_H);
_vid->markBlockAsDirty(15 * Video::CHAR_W, 21 * Video::CHAR_H, (len + 1) * Video::CHAR_W, Video::CHAR_H, _vid->_layerScale);
_vid->updateScreen();
_stub->sleep(EVENTS_DELAY);
_stub->processEvents();
@ -254,24 +254,15 @@ bool Menu::handleLevelScreen() {
int currentSkill = _skill;
int currentLevel = _level;
do {
static const char *levelTitles[] = {
"Titan / The Jungle",
"Titan / New Washington",
"Titan / Death Tower Show",
"Earth / Surface",
"Earth / Paradise Club",
"Planet Morphs / Surface",
"Planet Morphs / Inner Core"
};
for (int i = 0; i < 7; ++i) {
drawString(levelTitles[i], 7 + i * 2, 4, (currentLevel == i) ? 2 : 3);
drawString(_levelNames[i], 7 + i * 2, 4, (currentLevel == i) ? 2 : 3);
}
_vid->markBlockAsDirty(4 * Video::CHAR_W, 7 * Video::CHAR_H, 192, 7 * Video::CHAR_H);
_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);
_vid->markBlockAsDirty(4 * Video::CHAR_W, 23 * Video::CHAR_H, 192, Video::CHAR_H);
_vid->markBlockAsDirty(4 * Video::CHAR_W, 23 * Video::CHAR_H, 192, Video::CHAR_H, _vid->_layerScale);
_vid->updateScreen();
_stub->sleep(EVENTS_DELAY);

1
menu.h
View File

@ -48,6 +48,7 @@ struct Menu {
int opt;
};
static const char *_levelNames[];
static const char *_passwordsDOS[];
static const char *_passwordsFrAmiga[];
static const char *_passwordsEnAmiga[];

View File

@ -139,6 +139,15 @@ void Mixer::stopMusic() {
}
}
static void nr(int16_t *buf, int len) {
static int prev = 0;
for (int i = 0; i < len; ++i) {
const int vnr = buf[i] >> 1;
buf[i] = vnr + prev;
prev = vnr;
}
}
void Mixer::mix(int16_t *out, int len) {
if (_premixHook) {
if (!_premixHook(_premixHookData, out, len)) {
@ -160,6 +169,7 @@ void Mixer::mix(int16_t *out, int len) {
}
}
}
nr(out, len);
}
void Mixer::mixCallback(void *param, int16_t *buf, int len) {

View File

@ -156,8 +156,7 @@ struct ModPlayer_impl {
void applyVibrato(int trackNum);
void applyPortamento(int trackNum);
void handleEffect(int trackNum, bool tick);
void mixSamples(int8_t *buf, int len);
bool mixS8(int8_t *buf, int len);
void mixSamples(int16_t *buf, int len);
bool mix(int16_t *buf, int len);
};
@ -584,11 +583,11 @@ void ModPlayer_impl::handleTick() {
}
}
void ModPlayer_impl::mixSamples(int8_t *buf, int samplesLen) {
void ModPlayer_impl::mixSamples(int16_t *buf, int samplesLen) {
for (int i = 0; i < NUM_TRACKS; ++i) {
Track *tk = &_tracks[i];
if (tk->sample != 0 && tk->delayCounter == 0) {
int8_t *mixbuf = buf;
int16_t *mixbuf = buf;
SampleInfo *si = tk->sample;
int len = si->len << FRAC_BITS;
int loopLen = si->repeatLen << FRAC_BITS;
@ -614,7 +613,7 @@ void ModPlayer_impl::mixSamples(int8_t *buf, int samplesLen) {
}
while (count--) {
const int out = si->getPCM(pos >> FRAC_BITS);
*mixbuf = ADDC_S8(*mixbuf, out * tk->volume / 64);
*mixbuf = ADDC_S16(*mixbuf, (out * tk->volume / 64) << 8);
++mixbuf;
pos += deltaPos;
}
@ -624,7 +623,8 @@ void ModPlayer_impl::mixSamples(int8_t *buf, int samplesLen) {
}
}
bool ModPlayer_impl::mixS8(int8_t *buf, int len) {
bool ModPlayer_impl::mix(int16_t *buf, int len) {
memset(buf, 0, sizeof(int16_t) * len);
if (_playing) {
const int samplesPerTick = _mixingRate / (50 * _songTempo / 125);
while (len != 0) {
@ -644,16 +644,6 @@ bool ModPlayer_impl::mixS8(int8_t *buf, int len) {
}
return _playing;
}
bool ModPlayer_impl::mix(int16_t *samples, int len) {
int8_t buf[len];
memset(buf, 0, sizeof(buf));
const bool ret = mixS8(buf, len);
for (int i = 0; i < len; ++i) {
samples[i] = buf[i] << 8;
}
return ret;
}
#endif
ModPlayer::ModPlayer(Mixer *mixer, FileSystem *fs)

View File

@ -325,7 +325,7 @@ int Game::pge_execute(LivePGE *live_pge, InitPGE *init_pge, const Object *obj) {
++live_pge->life;
}
if (obj->flags & 8) {
live_pge->life = 0xFFFF;
live_pge->life = -1;
}
if (live_pge->flags & 1) {
@ -1428,7 +1428,7 @@ int Game::pge_op_setCollisionState2(ObjectOpcodeArgs *args) {
int Game::pge_op_saveState(ObjectOpcodeArgs *args) {
_saveStateCompleted = true;
_validSaveState = saveGameState(0);
_validSaveState = saveGameState(kIngameSaveSlot);
return 0xFFFF;
}

View File

@ -1679,6 +1679,10 @@ void Resource::MAC_loadCutsceneText() {
_cine_off = 0; // offsets are prepended to _cine_txt
}
void Resource::MAC_loadCreditsText() {
_credits = decodeResourceMacData("Credit strings", false);
}
void Resource::MAC_loadSounds() {
static const int8_t table[NUM_SFXS] = {
0, -1, 1, 2, 3, 4, -1, 5, 6, 7, 8, 9, 10, 11, -1, 12,

View File

@ -177,6 +177,7 @@ struct Resource {
uint8_t *_perso;
uint8_t *_monster;
uint8_t *_str;
uint8_t *_credits;
Resource(FileSystem *fs, ResourceType type, Language lang);
~Resource();
@ -335,6 +336,7 @@ struct Resource {
void MAC_unloadCutscene();
void MAC_loadCutscene(const char *cutscene);
void MAC_loadCutsceneText();
void MAC_loadCreditsText();
void MAC_loadSounds();
int MAC_getPersoFrame(int anim) const {

103
resource_mac.cpp Normal file
View File

@ -0,0 +1,103 @@
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include "fs.h"
#include "resource_mac.h"
#include "util.h"
const char *ResourceMac::FILENAME1 = "Flashback.bin";
const char *ResourceMac::FILENAME2 = "Flashback.rsrc";
ResourceMac::ResourceMac(const char *filePath, FileSystem *fs)
: _dataOffset(0), _types(0), _entries(0) {
memset(&_map, 0, sizeof(_map));
_f.open(filePath, "rb", fs);
}
ResourceMac::~ResourceMac() {
if (_entries) {
for (int i = 0; i < _map.typesCount; ++i) {
free(_entries[i]);
}
free(_entries);
}
free(_types);
}
void ResourceMac::load() {
const uint32_t sig = _f.readUint32BE();
if (sig == 0x00051607) { // AppleDouble
debug(DBG_INFO, "Load Macintosh data from AppleDouble");
_f.seek(24);
const int count = _f.readUint16BE();
for (int i = 0; i < count; ++i) {
const int id = _f.readUint32BE();
const int offset = _f.readUint32BE();
const int length = _f.readUint32BE();
if (id == 2) { // resource fork
loadResourceFork(offset, length);
break;
}
}
} else { // MacBinary
debug(DBG_INFO, "Load Macintosh data from MacBinary");
_f.seek(83);
uint32_t dataSize = _f.readUint32BE();
uint32_t resourceOffset = 128 + ((dataSize + 127) & ~127);
loadResourceFork(resourceOffset, dataSize);
}
}
void ResourceMac::loadResourceFork(uint32_t resourceOffset, uint32_t dataSize) {
_f.seek(resourceOffset);
_dataOffset = resourceOffset + _f.readUint32BE();
uint32_t mapOffset = resourceOffset + _f.readUint32BE();
_f.seek(mapOffset + 22);
_f.readUint16BE();
_map.typesOffset = _f.readUint16BE();
_map.namesOffset = _f.readUint16BE();
_map.typesCount = _f.readUint16BE() + 1;
_f.seek(mapOffset + _map.typesOffset + 2);
_types = (ResourceMacType *)calloc(_map.typesCount, sizeof(ResourceMacType));
for (int i = 0; i < _map.typesCount; ++i) {
_f.read(_types[i].id, 4);
_types[i].count = _f.readUint16BE() + 1;
_types[i].startOffset = _f.readUint16BE();
}
_entries = (ResourceMacEntry **)calloc(_map.typesCount, sizeof(ResourceMacEntry *));
for (int i = 0; i < _map.typesCount; ++i) {
_f.seek(mapOffset + _map.typesOffset + _types[i].startOffset);
_entries[i] = (ResourceMacEntry *)calloc(_types[i].count, sizeof(ResourceMacEntry));
for (int j = 0; j < _types[i].count; ++j) {
_entries[i][j].id = _f.readUint16BE();
_entries[i][j].nameOffset = _f.readUint16BE();
_entries[i][j].dataOffset = _f.readUint32BE() & 0x00FFFFFF;
_f.readUint32BE();
}
for (int j = 0; j < _types[i].count; ++j) {
_entries[i][j].name[0] = '\0';
if (_entries[i][j].nameOffset != 0xFFFF) {
_f.seek(mapOffset + _map.namesOffset + _entries[i][j].nameOffset);
const int len = _f.readByte();
assert(len < kResourceMacEntryNameLength - 1);
_f.read(_entries[i][j].name, len);
_entries[i][j].name[len] = '\0';
}
}
}
}
const ResourceMacEntry *ResourceMac::findEntry(const char *name) const {
for (int type = 0; type < _map.typesCount; ++type) {
for (int i = 0; i < _types[type].count; ++i) {
if (strcmp(name, _entries[type][i].name) == 0) {
return &_entries[type][i];
}
}
}
return 0;
}

53
resource_mac.h Normal file
View File

@ -0,0 +1,53 @@
#ifndef RESOURCE_MAC_H__
#define RESOURCE_MAC_H__
#include <stdint.h>
#include "file.h"
struct ResourceMacMap {
uint16_t typesOffset;
uint16_t namesOffset;
uint16_t typesCount;
};
struct ResourceMacType {
unsigned char id[4];
uint16_t count;
uint16_t startOffset;
};
enum {
kResourceMacEntryNameLength = 64
};
struct ResourceMacEntry {
uint16_t id;
uint16_t nameOffset;
uint32_t dataOffset;
char name[kResourceMacEntryNameLength];
};
struct ResourceMac {
static const char *FILENAME1;
static const char *FILENAME2;
File _f;
uint32_t _dataOffset;
ResourceMacMap _map;
ResourceMacType *_types;
ResourceMacEntry **_entries;
ResourceMac(const char *filePath, FileSystem *);
~ResourceMac();
bool isOpen() const { return _entries != 0; }
void load();
void loadResourceFork(uint32_t offset, uint32_t size);
const ResourceMacEntry *findEntry(const char *name) const;
};
#endif

View File

@ -97,11 +97,11 @@ void SfxPlayer::handleTick() {
}
}
void SfxPlayer::mixSamples(int8_t *buf, int samplesLen) {
void SfxPlayer::mixSamples(int16_t *buf, int samplesLen) {
for (int i = 0; i < NUM_CHANNELS; ++i) {
SampleInfo *si = &_samples[i];
if (si->data) {
int8_t *mixbuf = buf;
int16_t *mixbuf = buf;
int len = si->len << FRAC_BITS;
int loopLen = si->loopLen << FRAC_BITS;
int loopPos = si->loopPos << FRAC_BITS;
@ -127,7 +127,7 @@ void SfxPlayer::mixSamples(int8_t *buf, int samplesLen) {
}
while (count--) {
const int out = si->getPCM(pos >> FRAC_BITS);
*mixbuf = ADDC_S8(*mixbuf, out * si->vol / 64);
*mixbuf = ADDC_S16(*mixbuf, (out * si->vol / 64) << 8);
++mixbuf;
pos += deltaPos;
}
@ -137,7 +137,8 @@ void SfxPlayer::mixSamples(int8_t *buf, int samplesLen) {
}
}
bool SfxPlayer::mix(int8_t *buf, int len) {
bool SfxPlayer::mix(int16_t *buf, int len) {
memset(buf, 0, sizeof(int16_t) * len);
if (_playing) {
const int samplesPerTick = _mix->getSampleRate() / 50;
while (len != 0) {
@ -159,11 +160,5 @@ bool SfxPlayer::mix(int8_t *buf, int len) {
}
bool SfxPlayer::mixCallback(void *param, int16_t *samples, int len) {
int8_t buf[len];
memset(buf, 0, sizeof(buf));
const bool ret = ((SfxPlayer *)param)->mix(buf, len);
for (int i = 0; i < len; ++i) {
samples[i] = buf[i] << 8;
}
return ret;
return ((SfxPlayer *)param)->mix(samples, len);
}

View File

@ -81,9 +81,9 @@ struct SfxPlayer {
void stop();
void playSample(int channel, const uint8_t *sampleData, uint16_t period);
void handleTick();
void mixSamples(int8_t *samples, int samplesLen);
void mixSamples(int16_t *samples, int samplesLen);
bool mix(int8_t *buf, int len);
bool mix(int16_t *buf, int len);
static bool mixCallback(void *param, int16_t *buf, int len);
};

View File

@ -3223,6 +3223,16 @@ const uint8_t Game::_protectionPal[] = {
0x08, 0x88, 0x09, 0x99, 0x0A, 0xAA, 0x0B, 0xBB, 0x0C, 0xCC, 0x0D, 0xDD, 0x0E, 0xEE, 0x0F, 0xFF
};
const char *Menu::_levelNames[] {
"Titan / The Jungle",
"Titan / New Washington",
"Titan / Death Tower Show",
"Earth / Surface",
"Earth / Paradise Club",
"Planet Morphs / Surface",
"Planet Morphs / Inner Core"
};
const char *Menu::_passwordsDOS[] = {
"JAGUAR", "COMBEL", "ANTIC", "NOLAN", "ARTHUR", "SHIRYU", "RENDER", "BELUGA", // easy
"BANTHA", "SHIVA", "KASYYK", "SARLAC", "MAENOC", "SULUST", "NEPTUN", "BELUGA", // normal

View File

@ -49,12 +49,12 @@ Video::~Video() {
free(_screenBlocks);
}
void Video::markBlockAsDirty(int16_t x, int16_t y, uint16_t w, uint16_t h) {
void Video::markBlockAsDirty(int16_t x, int16_t y, uint16_t w, uint16_t h, int scale) {
debug(DBG_VIDEO, "Video::markBlockAsDirty(%d, %d, %d, %d)", x, y, w, h);
int bx1 = _layerScale * x / SCREENBLOCK_W;
int by1 = _layerScale * y / SCREENBLOCK_H;
int bx2 = _layerScale * (x + w - 1) / SCREENBLOCK_W;
int by2 = _layerScale * (y + h - 1) / SCREENBLOCK_H;
int bx1 = scale * x / SCREENBLOCK_W;
int by1 = scale * y / SCREENBLOCK_H;
int bx2 = scale * (x + w - 1) / SCREENBLOCK_W;
int by2 = scale * (y + h - 1) / SCREENBLOCK_H;
if (bx1 < 0) {
bx1 = 0;
}
@ -907,7 +907,7 @@ static uint8_t _MAC_fontShadowColor;
void Video::MAC_drawStringChar(uint8_t *dst, int pitch, int x, int y, const uint8_t *src, uint8_t color, uint8_t chr) {
DecodeBuffer buf;
memset(&buf, 0, sizeof(buf));
buf.ptr = _frontLayer;
buf.ptr = dst;
buf.w = buf.pitch = _w;
buf.h = _h;
buf.x = x * _layerScale;
@ -931,7 +931,7 @@ const char *Video::drawString(const char *str, int16_t x, int16_t y, uint8_t col
(this->*_drawChar)(_frontLayer, _w, x + len * CHAR_W, y, fnt, col, c);
++len;
}
markBlockAsDirty(x, y, len * CHAR_W, CHAR_H);
markBlockAsDirty(x, y, len * CHAR_W, CHAR_H, _layerScale);
return str - 1;
}
@ -940,7 +940,7 @@ void Video::drawStringLen(const char *str, int len, int x, int y, uint8_t color)
for (int i = 0; i < len; ++i) {
(this->*_drawChar)(_frontLayer, _w, x + i * CHAR_W, y, fnt, color, str[i]);
}
markBlockAsDirty(x, y, len * CHAR_W, CHAR_H);
markBlockAsDirty(x, y, len * CHAR_W, CHAR_H, _layerScale);
}
Color Video::AMIGA_convertColor(const uint16_t color, bool bgr) { // 4bits to 8bits
@ -1052,7 +1052,6 @@ void Video::MAC_drawSprite(int x, int y, const uint8_t *data, int frame, bool xf
buf.setPixel = eraseBackground ? MAC_drawBuffer : MAC_drawBufferMask;
fixOffsetDecodeBuffer(&buf, dataPtr);
_res->MAC_decodeImageData(data, frame, &buf);
// divide by screen scale as the dirty blocks range is 256,224
markBlockAsDirty(buf.x / _layerScale, buf.y / _layerScale, READ_BE_UINT16(dataPtr) / _layerScale, READ_BE_UINT16(dataPtr + 2) / _layerScale);
markBlockAsDirty(buf.x, buf.y, READ_BE_UINT16(dataPtr), READ_BE_UINT16(dataPtr + 2), 1);
}
}

View File

@ -53,7 +53,7 @@ struct Video {
Video(Resource *res, SystemStub *stub);
~Video();
void markBlockAsDirty(int16_t x, int16_t y, uint16_t w, uint16_t h);
void markBlockAsDirty(int16_t x, int16_t y, uint16_t w, uint16_t h, int scale);
void updateScreen();
void fullRefresh();
void fadeOut();