Import 0.2.2

This commit is contained in:
Gregory Montoir 2015-08-03 00:00:00 +08:00
parent 57f515ca56
commit f10e912082
43 changed files with 17853 additions and 0 deletions

30
Makefile Normal file
View File

@ -0,0 +1,30 @@
SDL_CFLAGS = `sdl-config --cflags`
SDL_LIBS = `sdl-config --libs`
VORBIS_LIBS = -lvorbisidec
ZLIB_LIBS = -lz
DEFINES = -DBYPASS_PROTECTION
#DEFINES = -DBYPASS_PROTECTION -DENABLE_PASSWORD_MENU -DNDEBUG
CXXFLAGS += -Wall -Wuninitialized -Wshadow -Wundef -Wreorder -Wnon-virtual-dtor -Wno-multichar
CXXFLAGS += -MMD $(SDL_CFLAGS) -DUSE_ZLIB $(DEFINES)
SRCS = collision.cpp cutscene.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 scaler.cpp seq_player.cpp \
sfx_player.cpp staticres.cpp systemstub_sdl.cpp unpack.cpp util.cpp video.cpp
OBJS = $(SRCS:.cpp=.o)
DEPS = $(SRCS:.cpp=.d)
LIBS = $(SDL_LIBS) $(VORBIS_LIBS) $(ZLIB_LIBS)
-include Makefile.local
rs: $(OBJS)
$(CXX) $(LDFLAGS) -o $@ $(OBJS) $(LIBS)
clean:
rm -f *.o *.d
-include $(DEPS)

110
README Normal file
View File

@ -0,0 +1,110 @@
REminiscence README
Release version: 0.2.2 ($date)
-------------------------------------------------------------------------------
About:
------
REminiscence is a re-implementation of the engine used in the game Flashback
made by Delphine Software and released in 1992. More informations about the
game can be found at [1], [2] and [3].
Compiling:
----------
Update the defines in the Makefile if needed. The SDL and zlib libraries are required.
Data Files:
-----------
You will need the original files of the PC (DOS or CD) or Amiga release.
If you have a version distributed by SSI, you'll have to rename the files
and drop the 'ssi' suffix (ie. logosssi.cmd -> logos.cmd).
To hear background music during polygonal cutscenes with the PC version,
you'll need to copy the .mod files of the Amiga version :
mod.flashback-ascenseur
mod.flashback-ceinturea
mod.flashback-chute
mod.flashback-desintegr
mod.flashback-donneobjt
mod.flashback-fin
mod.flashback-fin2
mod.flashback-game_over
mod.flashback-holocube
mod.flashback-introb
mod.flashback-jungle
mod.flashback-logo
mod.flashback-memoire
mod.flashback-missionca
mod.flashback-options1
mod.flashback-options2
mod.flashback-reunion
mod.flashback-taxi
mod.flashback-teleport2
mod.flashback-teleporta
mod.flashback-voyage
To hear voice during in-game dialogues, you'll need to copy the 'VOICE.VCE'
file from the SegaCD version to the DATA directory.
Running:
--------
By default, the engine will try to load the game data files from the 'DATA'
directory (as the original game did). The savestates are saved in the current
directory. These paths can be changed using command line switches :
Usage: rs [OPTIONS]...
--datapath=PATH Path to data files (default 'DATA')
--savepath=PATH Path to save files (default '.')
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
Alt + and - change video scaler
Ctrl S save game state
Ctrl L load game state
Ctrl + and - change game state slot
Ctrl R toggle input keys record
Ctrl P toggle input keys replay
Debug hotkeys :
Ctrl F toggle fast mode
Ctrl I Conrad 'infinite' life
Ctrl B toggle display of updated dirty blocks
Ctrl M mirror mode (right - left swapped)
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:
--------
Gregory Montoir, cyx@users.sourceforge.net
URLs:
-----
[1] http://www.mobygames.com/game/flashback-the-quest-for-identity
[2] http://en.wikipedia.org/wiki/Flashback:_The_Quest_for_Identity
[3] http://ramal.free.fr/fb_en.htm

522
collision.cpp Normal file
View File

@ -0,0 +1,522 @@
/* REminiscence - Flashback interpreter
* Copyright (C) 2005-2015 Gregory Montoir
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "game.h"
#include "resource.h"
void Game::col_prepareRoomState() {
memset(_col_activeCollisionSlots, 0xFF, sizeof(_col_activeCollisionSlots));
_col_currentLeftRoom = _res._ctData[CT_LEFT_ROOM + _currentRoom];
_col_currentRightRoom = _res._ctData[CT_RIGHT_ROOM + _currentRoom];
for (int i = 0; i != _col_curPos; ++i) {
CollisionSlot *_di = _col_slotsTable[i];
uint8_t room = _di->ct_pos / 64;
if (room == _currentRoom) {
_col_activeCollisionSlots[0x30 + (_di->ct_pos & 0x3F)] = i;
} else if (room == _col_currentLeftRoom) {
_col_activeCollisionSlots[0x00 + (_di->ct_pos & 0x3F)] = i;
} else if (room == _col_currentRightRoom) {
_col_activeCollisionSlots[0x60 + (_di->ct_pos & 0x3F)] = i;
}
}
#ifdef DEBUG_COLLISION
printf("---\n");
for (int y = 0; y < 7; ++y) {
for (int x = 0; x < 16; ++x) {
printf("%d", _res._ctData[0x100 + _currentRoom * 0x70 + y * 16 + x]);
}
printf("\n");
}
#endif
}
void Game::col_clearState() {
_col_curPos = 0;
_col_curSlot = _col_slots;
}
void Game::col_preparePiegeState(LivePGE *pge) {
debug(DBG_COL, "Game::col_preparePiegeState() pge_num=%d", pge - &_pgeLive[0]);
CollisionSlot *ct_slot1, *ct_slot2;
if (pge->init_PGE->unk1C == 0) {
pge->collision_slot = 0xFF;
return;
}
int i = 0;
ct_slot1 = 0;
for (int c = 0; c < pge->init_PGE->unk1C; ++c) {
ct_slot2 = _col_curSlot;
if (ct_slot2 + 1 > &_col_slots[255])
return;
_col_curSlot = ct_slot2 + 1;
int16_t pos = col_getGridPos(pge, i);
if (pos < 0) {
if (ct_slot1 == 0) {
pge->collision_slot = 0xFF;
} else {
ct_slot1->index = 0xFFFF;
}
return;
}
ct_slot2->ct_pos = pos;
ct_slot2->live_pge = pge;
ct_slot2->index = 0xFFFF;
int16_t _ax = col_findSlot(pos);
if (_ax >= 0) {
ct_slot2->prev_slot = _col_slotsTable[_ax];
_col_slotsTable[_ax] = ct_slot2;
if (ct_slot1 == 0) {
pge->collision_slot = _ax;
} else {
ct_slot1->index = _ax;
}
LivePGE *temp_pge = ct_slot2->live_pge;
if (temp_pge->flags & 0x80) {
_pge_liveTable2[temp_pge->index] = temp_pge;
temp_pge->flags |= 4;
}
if (ct_slot2->prev_slot) {
temp_pge = ct_slot2->prev_slot->live_pge;
if (temp_pge->flags & 0x80) {
_pge_liveTable2[temp_pge->index] = temp_pge;
temp_pge->flags |= 4;
}
}
} else {
ct_slot2->prev_slot = 0;
_col_slotsTable[_col_curPos] = ct_slot2;
if (ct_slot1 == 0) {
pge->collision_slot = _col_curPos;
} else {
ct_slot1->index = _col_curPos;
}
_col_curPos++;
}
ct_slot1 = ct_slot2;
i += 0x10;
}
}
uint16_t Game::col_getGridPos(LivePGE *pge, int16_t dx) {
int16_t x = pge->pos_x + dx;
int16_t y = pge->pos_y;
int8_t c = pge->room_location;
if (c < 0) return 0xFFFF;
if (x < 0) {
c = _res._ctData[CT_LEFT_ROOM + c];
if (c < 0) return 0xFFFF;
x += 256;
} else if (x >= 256) {
c = _res._ctData[CT_RIGHT_ROOM + c];
if (c < 0) return 0xFFFF;
x -= 256;
} else if (y < 0) {
c = _res._ctData[CT_UP_ROOM + c];
if (c < 0) return 0xFFFF;
y += 216;
} else if (y >= 216) {
c = _res._ctData[CT_DOWN_ROOM + c];
if (c < 0) return 0xFFFF;
y -= 216;
}
x = (x + 8) >> 4;
y = (y - 8) / 72;
if (x < 0 || x > 15 || y < 0 || y > 2) {
return 0xFFFF;
} else {
return y * 16 + x + c * 64;
}
}
int16_t Game::col_findSlot(int16_t pos) {
for (uint16_t i = 0; i < _col_curPos; ++i) {
if (_col_slotsTable[i]->ct_pos == pos)
return i;
}
return -1;
}
int16_t Game::col_getGridData(LivePGE *pge, int16_t dy, int16_t dx) {
if (_pge_currentPiegeFacingDir) {
dx = -dx;
}
const int16_t pge_grid_y = _col_currentPiegeGridPosY + dy;
const int16_t pge_grid_x = _col_currentPiegeGridPosX + dx;
const int8_t *room_ct_data;
int8_t next_room;
if (pge_grid_x < 0) {
room_ct_data = &_res._ctData[CT_LEFT_ROOM];
next_room = room_ct_data[pge->room_location];
if (next_room < 0) return 1;
room_ct_data += pge_grid_x + 16 + pge_grid_y * 16 + next_room * 0x70;
return (int16_t)room_ct_data[0x40];
} else if (pge_grid_x >= 16) {
room_ct_data = &_res._ctData[CT_RIGHT_ROOM];
next_room = room_ct_data[pge->room_location];
if (next_room < 0) return 1;
room_ct_data += pge_grid_x - 16 + pge_grid_y * 16 + next_room * 0x70;
return (int16_t)room_ct_data[0x80];
} else if (pge_grid_y < 1) {
room_ct_data = &_res._ctData[CT_UP_ROOM];
next_room = room_ct_data[pge->room_location];
if (next_room < 0) return 1;
room_ct_data += pge_grid_x + (pge_grid_y + 6) * 16 + next_room * 0x70;
return (int16_t)room_ct_data[0x100];
} else if (pge_grid_y >= 7) {
room_ct_data = &_res._ctData[CT_DOWN_ROOM];
next_room = room_ct_data[pge->room_location];
if (next_room < 0) return 1;
room_ct_data += pge_grid_x + (pge_grid_y - 6) * 16 + next_room * 0x70;
return (int16_t)room_ct_data[0xC0];
} else {
room_ct_data = &_res._ctData[0x100];
room_ct_data += pge_grid_x + pge_grid_y * 16 + pge->room_location * 0x70;
return (int16_t)room_ct_data[0];
}
}
LivePGE *Game::col_findPiege(LivePGE *pge, uint16_t arg2) {
if (pge->collision_slot != 0xFF) {
CollisionSlot *slot = _col_slotsTable[pge->collision_slot];
while (slot) {
if (slot->live_pge == pge) {
slot = slot->prev_slot;
} else {
if (arg2 == 0xFFFF || arg2 == slot->live_pge->init_PGE->object_type) {
return slot->live_pge;
} else {
slot = slot->prev_slot;
}
}
}
}
return 0;
}
uint8_t Game::col_findCurrentCollidingObject(LivePGE *pge, uint8_t n1, uint8_t n2, uint8_t n3, LivePGE **pge_out) {
if (pge_out) {
*pge_out = pge;
}
if (pge->collision_slot != 0xFF) {
CollisionSlot *cs = _col_slotsTable[pge->collision_slot];
while (cs) {
LivePGE *col_pge = cs->live_pge;
if (pge_out) {
*pge_out = col_pge;
}
if (col_pge->init_PGE->object_type == n1 ||
col_pge->init_PGE->object_type == n2 ||
col_pge->init_PGE->object_type == n3) {
return col_pge->init_PGE->colliding_icon_num;
} else {
cs = cs->prev_slot;
}
}
}
return 0;
}
int16_t Game::col_detectHit(LivePGE *pge, int16_t arg2, int16_t arg4, col_Callback1 callback1, col_Callback2 callback2, int16_t argA, int16_t argC) {
debug(DBG_COL, "col_detectHit()");
int16_t pos_dx, pos_dy, var8, varA;
int16_t collision_score = 0;
int8_t pge_room = pge->room_location;
if (pge_room < 0 || pge_room >= 0x40) {
return 0;
}
int16_t thr = pge->init_PGE->counter_values[0];
if (thr > 0) {
pos_dx = -1;
pos_dy = -1;
} else {
pos_dx = 1;
pos_dy = 1;
thr = -thr;
}
if (_pge_currentPiegeFacingDir) {
pos_dx = -pos_dx;
}
int16_t grid_pos_x = (pge->pos_x + 8) >> 4;
int16_t grid_pos_y = (pge->pos_y / 72);
if (grid_pos_y >= 0 && grid_pos_y <= 2) {
grid_pos_y *= 16;
collision_score = 0;
var8 = 0;
varA = 0;
if (argA != 0) {
var8 = pos_dy;
grid_pos_x += pos_dx;
varA = 1;
}
while (varA <= thr) {
if (grid_pos_x < 0) {
pge_room = _res._ctData[CT_LEFT_ROOM + pge_room];
if (pge_room < 0) break;
grid_pos_x += 16;
}
if (grid_pos_x >= 16) {
pge_room = _res._ctData[CT_RIGHT_ROOM + pge_room];
if (pge_room < 0) break;
grid_pos_x -= 16;
}
int16_t slot = col_findSlot(grid_pos_y + grid_pos_x + pge_room * 64);
if (slot >= 0) {
CollisionSlot *cs = _col_slotsTable[slot];
while (cs) {
collision_score += (this->*callback1)(cs->live_pge, pge, arg2, arg4);
cs = cs->prev_slot;
}
}
if ((this->*callback2)(pge, var8, varA, arg2) != 0) {
break;
}
grid_pos_x += pos_dx;
++varA;
var8 += pos_dy;
}
}
if (argC == -1) {
return collision_score;
} else {
return 0;
}
}
int Game::col_detectHitCallback1(LivePGE *pge, int16_t dy, int16_t unk1, int16_t unk2) {
if (col_getGridData(pge, 1, dy) != 0) {
return 1;
} else {
return 0;
}
}
int Game::col_detectHitCallback6(LivePGE *pge, int16_t dy, int16_t unk1, int16_t unk2) {
return 0;
}
int Game::col_detectHitCallback2(LivePGE *pge1, LivePGE *pge2, int16_t unk1, int16_t unk2) {
if (pge1 != pge2 && (pge1->flags & 4)) {
if (pge1->init_PGE->object_type == unk2) {
if ((pge1->flags & 1) == (pge2->flags & 1)) {
if (col_detectHitCallbackHelper(pge1, unk1) == 0) {
return 1;
}
}
}
}
return 0;
}
int Game::col_detectHitCallback3(LivePGE *pge1, LivePGE *pge2, int16_t unk1, int16_t unk2) {
if (pge1 != pge2 && (pge1->flags & 4)) {
if (pge1->init_PGE->object_type == unk2) {
if ((pge1->flags & 1) != (pge2->flags & 1)) {
if (col_detectHitCallbackHelper(pge1, unk1) == 0) {
return 1;
}
}
}
}
return 0;
}
int Game::col_detectHitCallback4(LivePGE *pge1, LivePGE *pge2, int16_t unk1, int16_t unk2) {
if (pge1 != pge2 && (pge1->flags & 4)) {
if (pge1->init_PGE->object_type == unk2) {
if ((pge1->flags & 1) != (pge2->flags & 1)) {
if (col_detectHitCallbackHelper(pge1, unk1) == 0) {
pge_updateGroup(pge2->index, pge1->index, unk1);
return 1;
}
}
}
}
return 0;
}
int Game::col_detectHitCallback5(LivePGE *pge1, LivePGE *pge2, int16_t unk1, int16_t unk2) {
if (pge1 != pge2 && (pge1->flags & 4)) {
if (pge1->init_PGE->object_type == unk2) {
if ((pge1->flags & 1) == (pge2->flags & 1)) {
if (col_detectHitCallbackHelper(pge1, unk1) == 0) {
pge_updateGroup(pge2->index, pge1->index, unk1);
return 1;
}
}
}
}
return 0;
}
int Game::col_detectHitCallbackHelper(LivePGE *pge, int16_t groupId) {
InitPGE *init_pge = pge->init_PGE;
assert(init_pge->obj_node_number < _res._numObjectNodes);
ObjectNode *on = _res._objectNodesMap[init_pge->obj_node_number];
Object *obj = &on->objects[pge->first_obj_number];
int i = pge->first_obj_number;
while (pge->obj_type == obj->type && on->last_obj_number > i) {
if (obj->opcode2 == 0x6B) { // pge_op_isInGroupSlice
if (obj->opcode_arg2 == 0) {
if (groupId == 1 || groupId == 2) return 0xFFFF;
}
if (obj->opcode_arg2 == 1) {
if (groupId == 3 || groupId == 4) return 0xFFFF;
}
} else if (obj->opcode2 == 0x22) { // pge_op_isInGroup
if (obj->opcode_arg2 == groupId) return 0xFFFF;
}
if (obj->opcode1 == 0x6B) { // pge_op_isInGroupSlice
if (obj->opcode_arg1 == 0) {
if (groupId == 1 || groupId == 2) return 0xFFFF;
}
if (obj->opcode_arg1 == 1) {
if (groupId == 3 || groupId == 4) return 0xFFFF;
}
} else if (obj->opcode1 == 0x22) { // pge_op_isInGroup
if (obj->opcode_arg1 == groupId) return 0xFFFF;
}
++obj;
++i;
}
return 0;
}
int Game::col_detectGunHitCallback1(LivePGE *pge, int16_t arg2, int16_t arg4, int16_t arg6) {
int16_t _ax = col_getGridData(pge, 1, arg2);
if (_ax != 0) {
if (!(_ax & 2) || (arg6 != 1)) {
return _ax;
}
}
return 0;
}
int Game::col_detectGunHitCallback2(LivePGE *pge1, LivePGE *pge2, int16_t arg4, int16_t) {
if (pge1 != pge2 && (pge1->flags & 4)) {
if (pge1->init_PGE->object_type == 1 || pge1->init_PGE->object_type == 10) {
uint8_t id;
if ((pge1->flags & 1) != (pge2->flags & 1)) {
id = 4;
if (arg4 == 0) {
id = 3;
}
} else {
id = 2;
if (arg4 == 0) {
id = 1;
}
}
if (col_detectHitCallbackHelper(pge1, id) != 0) {
pge_updateGroup(pge2->index, pge1->index, id);
return 1;
}
}
}
return 0;
}
int Game::col_detectGunHitCallback3(LivePGE *pge1, LivePGE *pge2, int16_t arg4, int16_t) {
if (pge1 != pge2 && (pge1->flags & 4)) {
if (pge1->init_PGE->object_type == 1 || pge1->init_PGE->object_type == 12 || pge1->init_PGE->object_type == 10) {
uint8_t id;
if ((pge1->flags & 1) != (pge2->flags & 1)) {
id = 4;
if (arg4 == 0) {
id = 3;
}
} else {
id = 2;
if (arg4 == 0) {
id = 1;
}
}
if (col_detectHitCallbackHelper(pge1, id) != 0) {
pge_updateGroup(pge2->index, pge1->index, id);
return 1;
}
}
}
return 0;
}
int Game::col_detectGunHit(LivePGE *pge, int16_t arg2, int16_t arg4, col_Callback1 callback1, col_Callback2 callback2, int16_t argA, int16_t argC) {
int8_t pge_room = pge->room_location;
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];
} else {
thr = pge->init_PGE->counter_values[3];
}
if (thr > 0) {
pos_dx = -1;
pos_dy = -1;
} else {
pos_dx = 1;
pos_dy = 1;
thr = -thr;
}
if (_pge_currentPiegeFacingDir) {
pos_dx = -pos_dx;
}
int16_t grid_pos_x = (pge->pos_x + 8) >> 4;
int16_t grid_pos_y = (pge->pos_y - 8) / 72;
if (grid_pos_y >= 0 && grid_pos_y <= 2) {
grid_pos_y *= 16;
int16_t var8 = 0;
int16_t varA = 0;
if (argA != 0) {
var8 = pos_dy;
grid_pos_x += pos_dx;
varA = 1;
}
while (varA <= thr) {
if (grid_pos_x < 0) {
pge_room = _res._ctData[CT_LEFT_ROOM + pge_room];
if (pge_room < 0) return 0;
grid_pos_x += 0x10;
}
if (grid_pos_x >= 0x10) {
pge_room = _res._ctData[CT_RIGHT_ROOM + pge_room];
if (pge_room < 0) return 0;
grid_pos_x -= 0x10;
}
int16_t slot = col_findSlot(pge_room * 64 + grid_pos_x + grid_pos_y);
if (slot >= 0) {
CollisionSlot *cs = _col_slotsTable[slot];
while (cs) {
int r = (this->*callback1)(cs->live_pge, pge, arg2, arg4);
if (r != 0) return r;
cs = cs->prev_slot;
}
}
if ((this->*callback2)(pge, var8, varA, arg2) != 0) {
break;
}
grid_pos_x += pos_dx;
++varA;
var8 += pos_dy;
}
}
return 0;
}

1031
cutscene.cpp Normal file

File diff suppressed because it is too large Load Diff

134
cutscene.h Normal file
View File

@ -0,0 +1,134 @@
/* REminiscence - Flashback interpreter
* Copyright (C) 2005-2015 Gregory Montoir
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef CUTSCENE_H__
#define CUTSCENE_H__
#include "intern.h"
#include "graphics.h"
struct Resource;
struct SystemStub;
struct Video;
struct Cutscene {
typedef void (Cutscene::*OpcodeStub)();
enum {
NUM_OPCODES = 15,
TIMER_SLICE = 15
};
static const OpcodeStub _opcodeTable[];
static const char *_namesTable[];
static const uint16_t _offsetsTable[];
static const uint16_t _cosTable[];
static const uint16_t _sinTable[];
static const uint8_t _creditsData[];
static const uint16_t _creditsCutSeq[];
static const uint8_t _musicTable[];
static const uint8_t _protectionShapeData[];
Graphics _gfx;
Resource *_res;
SystemStub *_stub;
Video *_vid;
uint16_t _id;
uint16_t _deathCutsceneId;
bool _interrupted;
bool _stop;
uint8_t *_polPtr;
uint8_t *_cmdPtr;
uint8_t *_cmdPtrBak;
uint32_t _tstamp;
uint8_t _frameDelay;
bool _newPal;
uint8_t _palBuf[0x20 * 2];
uint16_t _startOffset;
bool _creditsSequence;
uint32_t _rotData[4];
uint8_t _primitiveColor;
uint8_t _clearScreen;
Point _vertices[0x80];
bool _hasAlphaColor;
uint8_t _varText;
uint8_t _varKey;
int16_t _shape_ix;
int16_t _shape_iy;
int16_t _shape_ox;
int16_t _shape_oy;
int16_t _shape_cur_x;
int16_t _shape_cur_y;
int16_t _shape_prev_x;
int16_t _shape_prev_y;
uint16_t _shape_count;
uint32_t _shape_cur_x16;
uint32_t _shape_cur_y16;
uint32_t _shape_prev_x16;
uint32_t _shape_prev_y16;
uint8_t _textSep[0x14];
uint8_t _textBuf[500];
const uint8_t *_textCurPtr;
uint8_t *_textCurBuf;
uint8_t _textUnk2;
uint8_t _creditsTextPosX;
uint8_t _creditsTextPosY;
int16_t _creditsTextCounter;
uint8_t *_page0, *_page1, *_pageC;
Cutscene(Resource *res, SystemStub *stub, Video *vid);
void sync();
void copyPalette(const uint8_t *pal, uint16_t num);
void updatePalette();
void setPalette();
void initRotationData(uint16_t a, uint16_t b, uint16_t c);
uint16_t findTextSeparators(const uint8_t *p);
void drawText(int16_t x, int16_t y, const uint8_t *p, uint16_t color, uint8_t *page, uint8_t n);
void swapLayers();
void drawCreditsText();
void drawProtectionShape(uint8_t shapeNum, int16_t zoom);
void drawShape(const uint8_t *data, int16_t x, int16_t y);
void drawShapeScale(const uint8_t *data, int16_t zoom, int16_t b, int16_t c, int16_t d, int16_t e, int16_t f, int16_t g);
void drawShapeScaleRotate(const uint8_t *data, int16_t zoom, int16_t b, int16_t c, int16_t d, int16_t e, int16_t f, int16_t g);
void op_markCurPos();
void op_refreshScreen();
void op_waitForSync();
void op_drawShape();
void op_setPalette();
void op_drawStringAtBottom();
void op_nop();
void op_skip3();
void op_refreshAll();
void op_drawShapeScale();
void op_drawShapeScaleRotate();
void op_drawCreditsText();
void op_drawStringAtPos();
void op_handleKeys();
uint8_t fetchNextCmdByte();
uint16_t fetchNextCmdWord();
void mainLoop(uint16_t offset);
void load(uint16_t cutName);
void prepare();
void startCredits();
void play();
};
#endif // CUTSCENE_H__

260
file.cpp Normal file
View File

@ -0,0 +1,260 @@
/* REminiscence - Flashback interpreter
* Copyright (C) 2005-2015 Gregory Montoir
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "fs.h"
#ifdef USE_ZLIB
#include "zlib.h"
#endif
#include "file.h"
struct File_impl {
bool _ioErr;
File_impl() : _ioErr(false) {}
virtual ~File_impl() {}
virtual bool open(const char *path, const char *mode) = 0;
virtual void close() = 0;
virtual uint32_t size() = 0;
virtual void seek(int32_t off) = 0;
virtual uint32_t read(void *ptr, uint32_t len) = 0;
virtual uint32_t write(void *ptr, uint32_t len) = 0;
};
struct stdFile : File_impl {
FILE *_fp;
stdFile() : _fp(0) {}
bool open(const char *path, const char *mode) {
_ioErr = false;
_fp = fopen(path, mode);
return (_fp != 0);
}
void close() {
if (_fp) {
fclose(_fp);
_fp = 0;
}
}
uint32_t size() {
uint32_t sz = 0;
if (_fp) {
int pos = ftell(_fp);
fseek(_fp, 0, SEEK_END);
sz = ftell(_fp);
fseek(_fp, pos, SEEK_SET);
}
return sz;
}
void seek(int32_t off) {
if (_fp) {
fseek(_fp, off, SEEK_SET);
}
}
uint32_t read(void *ptr, uint32_t len) {
if (_fp) {
uint32_t r = fread(ptr, 1, len, _fp);
if (r != len) {
_ioErr = true;
}
return r;
}
return 0;
}
uint32_t write(void *ptr, uint32_t len) {
if (_fp) {
uint32_t r = fwrite(ptr, 1, len, _fp);
if (r != len) {
_ioErr = true;
}
return r;
}
return 0;
}
};
#ifdef USE_ZLIB
struct zlibFile : File_impl {
gzFile _fp;
zlibFile() : _fp(0) {}
bool open(const char *path, const char *mode) {
_ioErr = false;
_fp = gzopen(path, mode);
return (_fp != 0);
}
void close() {
if (_fp) {
gzclose(_fp);
_fp = 0;
}
}
uint32_t size() {
uint32_t sz = 0;
if (_fp) {
int pos = gztell(_fp);
gzseek(_fp, 0, SEEK_END);
sz = gztell(_fp);
gzseek(_fp, pos, SEEK_SET);
}
return sz;
}
void seek(int32_t off) {
if (_fp) {
gzseek(_fp, off, SEEK_SET);
}
}
uint32_t read(void *ptr, uint32_t len) {
if (_fp) {
uint32_t r = gzread(_fp, ptr, len);
if (r != len) {
_ioErr = true;
}
return r;
}
return 0;
}
uint32_t write(void *ptr, uint32_t len) {
if (_fp) {
uint32_t r = gzwrite(_fp, ptr, len);
if (r != len) {
_ioErr = true;
}
return r;
}
return 0;
}
};
#endif
File::File()
: _impl(0) {
}
File::~File() {
if (_impl) {
_impl->close();
delete _impl;
}
}
bool File::open(const char *filename, const char *mode, FileSystem *fs) {
if (_impl) {
_impl->close();
delete _impl;
_impl = 0;
}
assert(mode[0] != 'z');
_impl = new stdFile;
char *path = fs->findPath(filename);
if (path) {
debug(DBG_FILE, "Open file name '%s' mode '%s' path '%s'", filename, mode, path);
bool ret = _impl->open(path, mode);
free(path);
return ret;
}
return false;
}
bool File::open(const char *filename, const char *mode, const char *directory) {
if (_impl) {
_impl->close();
delete _impl;
_impl = 0;
}
#ifdef USE_ZLIB
if (mode[0] == 'z') {
_impl = new zlibFile;
++mode;
}
#endif
if (!_impl) {
_impl = new stdFile;
}
char path[512];
snprintf(path, sizeof(path), "%s/%s", directory, filename);
debug(DBG_FILE, "Open file name '%s' mode '%s' path '%s'", filename, mode, path);
return _impl->open(path, mode);
}
void File::close() {
if (_impl) {
_impl->close();
}
}
bool File::ioErr() const {
return _impl->_ioErr;
}
uint32_t File::size() {
return _impl->size();
}
void File::seek(int32_t off) {
_impl->seek(off);
}
uint32_t File::read(void *ptr, uint32_t len) {
return _impl->read(ptr, len);
}
uint8_t File::readByte() {
uint8_t b;
read(&b, 1);
return b;
}
uint16_t File::readUint16LE() {
uint8_t lo = readByte();
uint8_t hi = readByte();
return (hi << 8) | lo;
}
uint32_t File::readUint32LE() {
uint16_t lo = readUint16LE();
uint16_t hi = readUint16LE();
return (hi << 16) | lo;
}
uint16_t File::readUint16BE() {
uint8_t hi = readByte();
uint8_t lo = readByte();
return (hi << 8) | lo;
}
uint32_t File::readUint32BE() {
uint16_t hi = readUint16BE();
uint16_t lo = readUint16BE();
return (hi << 16) | lo;
}
uint32_t File::write(void *ptr, uint32_t len) {
return _impl->write(ptr, len);
}
void File::writeByte(uint8_t b) {
write(&b, 1);
}
void File::writeUint16BE(uint16_t n) {
writeByte(n >> 8);
writeByte(n & 0xFF);
}
void File::writeUint32BE(uint32_t n) {
writeUint16BE(n >> 16);
writeUint16BE(n & 0xFFFF);
}

50
file.h Normal file
View File

@ -0,0 +1,50 @@
/* REminiscence - Flashback interpreter
* Copyright (C) 2005-2015 Gregory Montoir
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef FILE_H__
#define FILE_H__
#include "intern.h"
struct File_impl;
struct FileSystem;
struct File {
File();
~File();
File_impl *_impl;
bool open(const char *filename, const char *mode, FileSystem *fs);
bool open(const char *filename, const char *mode, const char *directory);
void close();
bool ioErr() const;
uint32_t size();
void seek(int32_t off);
uint32_t read(void *ptr, uint32_t len);
uint8_t readByte();
uint16_t readUint16LE();
uint32_t readUint32LE();
uint16_t readUint16BE();
uint32_t readUint32BE();
uint32_t write(void *ptr, uint32_t size);
void writeByte(uint8_t b);
void writeUint16BE(uint16_t n);
void writeUint32BE(uint32_t n);
};
#endif // FILE_H__

167
fs.cpp Normal file
View File

@ -0,0 +1,167 @@
/* REminiscence - Flashback interpreter
* Copyright (C) 2005-2015 Gregory Montoir
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#else
#include <dirent.h>
#include <sys/stat.h>
#endif
#include "fs.h"
struct FileName {
char *name;
int dir;
};
struct FileSystem_impl {
char **_dirsList;
int _dirsCount;
FileName *_filesList;
int _filesCount;
FileSystem_impl() :
_dirsList(0), _dirsCount(0), _filesList(0), _filesCount(0) {
}
~FileSystem_impl() {
for (int i = 0; i < _dirsCount; ++i) {
free(_dirsList[i]);
}
free(_dirsList);
for (int i = 0; i < _filesCount; ++i) {
free(_filesList[i].name);
}
free(_filesList);
}
void setRootDirectory(const char *dir) {
getPathListFromDirectory(dir);
debug(DBG_FILE, "Found %d files and %d directories", _filesCount, _dirsCount);
}
char *findPath(const char *name) const {
for (int i = 0; i < _filesCount; ++i) {
if (strcasecmp(_filesList[i].name, name) == 0) {
const char *dir = _dirsList[_filesList[i].dir];
const int len = strlen(dir) + 1 + strlen(_filesList[i].name) + 1;
char *p = (char *)malloc(len);
if (p) {
snprintf(p, len, "%s/%s", dir, _filesList[i].name);
}
return p;
}
}
return 0;
}
void addPath(const char *dir, const char *name) {
int index = -1;
for (int i = 0; i < _dirsCount; ++i) {
if (strcmp(_dirsList[i], dir) == 0) {
index = i;
break;
}
}
if (index == -1) {
_dirsList = (char **)realloc(_dirsList, (_dirsCount + 1) * sizeof(char *));
if (_dirsList) {
_dirsList[_dirsCount] = strdup(dir);
index = _dirsCount;
++_dirsCount;
}
}
_filesList = (FileName *)realloc(_filesList, (_filesCount + 1) * sizeof(FileName));
if (_filesList) {
_filesList[_filesCount].name = strdup(name);
_filesList[_filesCount].dir = index;
++_filesCount;
}
}
void getPathListFromDirectory(const char *dir);
};
#ifdef _WIN32
void FileSystem_impl::getPathListFromDirectory(const char *dir) {
WIN32_FIND_DATA findData;
char searchPath[MAX_PATH];
snprintf(searchPath, sizeof(searchPath), "%s/*", dir);
HANDLE h = FindFirstFile(searchPath, &findData);
if (h) {
do {
if (findData.cFileName[0] == '.') {
continue;
}
char filePath[MAX_PATH];
snprintf(filePath, sizeof(filePath), "%s/%s", dir, findData.cFileName);
if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
getPathListFromDirectory(filePath);
} else {
addPath(dir, findData.cFileName);
}
} while (FindNextFile(h, &findData));
FindClose(h);
}
}
#else
void FileSystem_impl::getPathListFromDirectory(const char *dir) {
DIR *d = opendir(dir);
if (d) {
dirent *de;
while ((de = readdir(d)) != NULL) {
if (de->d_name[0] == '.') {
continue;
}
char filePath[512];
snprintf(filePath, sizeof(filePath), "%s/%s", dir, de->d_name);
struct stat st;
if (stat(filePath, &st) == 0) {
if (S_ISDIR(st.st_mode)) {
getPathListFromDirectory(filePath);
} else {
addPath(dir, de->d_name);
}
}
}
closedir(d);
}
}
#endif
FileSystem::FileSystem(const char *dataPath) {
_impl = new FileSystem_impl;
_impl->setRootDirectory(dataPath);
}
FileSystem::~FileSystem() {
delete _impl;
}
char *FileSystem::findPath(const char *filename) const {
return _impl->findPath(filename);
}
bool FileSystem::exists(const char *filename) const {
char *path = findPath(filename);
if (path) {
free(path);
}
return path != 0;
}

36
fs.h Normal file
View File

@ -0,0 +1,36 @@
/* REminiscence - Flashback interpreter
* Copyright (C) 2005-2015 Gregory Montoir
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef FS_H__
#define FS_H__
#include "intern.h"
struct FileSystem_impl;
struct FileSystem {
FileSystem(const char *dataPath);
~FileSystem();
FileSystem_impl *_impl;
char *findPath(const char *filename) const;
bool exists(const char *filename) const;
};
#endif // FS_H__

1805
game.cpp Normal file

File diff suppressed because it is too large Load Diff

383
game.h Normal file
View File

@ -0,0 +1,383 @@
/* REminiscence - Flashback interpreter
* Copyright (C) 2005-2015 Gregory Montoir
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef GAME_H__
#define GAME_H__
#include "intern.h"
#include "cutscene.h"
#include "menu.h"
#include "mixer.h"
#include "resource.h"
#include "seq_player.h"
#include "video.h"
struct File;
struct FileSystem;
struct SystemStub;
struct Game {
typedef int (Game::*pge_OpcodeProc)(ObjectOpcodeArgs *args);
typedef int (Game::*pge_ZOrderCallback)(LivePGE *, LivePGE *, uint8_t, uint8_t);
typedef int (Game::*col_Callback1)(LivePGE *, LivePGE *, int16_t, int16_t);
typedef int (Game::*col_Callback2)(LivePGE *, int16_t, int16_t, int16_t);
enum {
CT_UP_ROOM = 0x00,
CT_DOWN_ROOM = 0x40,
CT_RIGHT_ROOM = 0x80,
CT_LEFT_ROOM = 0xC0
};
static const Level _gameLevels[];
static const uint16_t _scoreTable[];
static const uint8_t _monsterListLevel1[];
static const uint8_t _monsterListLevel2[];
static const uint8_t _monsterListLevel3[];
static const uint8_t _monsterListLevel4_1[];
static const uint8_t _monsterListLevel4_2[];
static const uint8_t _monsterListLevel5_1[];
static const uint8_t _monsterListLevel5_2[];
static const uint8_t *_monsterListLevels[];
static const uint8_t _monsterPals[4][32];
static const char *_monsterNames[2][4];
static const pge_OpcodeProc _pge_opcodeTable[];
static const uint8_t _pge_modKeysTable[];
static const uint8_t _protectionCodeData[];
static const uint8_t _protectionPal[];
Cutscene _cut;
Menu _menu;
Mixer _mix;
Resource _res;
SeqPlayer _seq;
Video _vid;
SystemStub *_stub;
FileSystem *_fs;
const char *_savePath;
const uint8_t *_stringsTable;
const char **_textsTable;
uint8_t _currentLevel;
uint8_t _skillLevel;
uint32_t _score;
uint8_t _currentRoom;
uint8_t _currentIcon;
bool _loadMap;
uint8_t _printLevelCodeCounter;
uint32_t _randSeed;
uint16_t _currentInventoryIconNum;
uint16_t _curMonsterFrame;
uint16_t _curMonsterNum;
uint8_t _blinkingConradCounter;
uint16_t _textToDisplay;
bool _eraseBackground;
AnimBufferState _animBuffer0State[41];
AnimBufferState _animBuffer1State[6]; // Conrad
AnimBufferState _animBuffer2State[42];
AnimBufferState _animBuffer3State[12];
AnimBuffers _animBuffers;
uint16_t _deathCutsceneCounter;
bool _saveStateCompleted;
bool _endLoop;
Game(SystemStub *, FileSystem *, const char *savePath, int level, ResourceType ver, Language lang);
void run();
void resetGameState();
void mainLoop();
void updateTiming();
void playCutscene(int id = -1);
bool playCutsceneSeq(const char *name);
void loadLevelMap();
void loadLevelData();
void drawIcon(uint8_t iconNum, int16_t x, int16_t y, uint8_t colMask);
void drawCurrentInventoryItem();
void printLevelCode();
void showFinalScore();
bool handleConfigPanel();
bool handleContinueAbort();
bool handleProtectionScreen();
void printSaveStateCompleted();
void drawLevelTexts();
void drawStoryTexts();
void prepareAnims();
void prepareAnimsHelper(LivePGE *pge, int16_t dx, int16_t dy);
void drawAnims();
void drawAnimBuffer(uint8_t stateNum, AnimBufferState *state);
void drawObject(const uint8_t *dataPtr, int16_t x, int16_t y, uint8_t flags);
void drawObjectFrame(const uint8_t *bankDataPtr, const uint8_t *dataPtr, int16_t x, int16_t y, uint8_t flags);
void decodeCharacterFrame(const uint8_t *dataPtr, uint8_t *dstPtr);
void drawCharacter(const uint8_t *dataPtr, int16_t x, int16_t y, uint8_t a, uint8_t b, uint8_t flags);
int loadMonsterSprites(LivePGE *pge);
void playSound(uint8_t sfxId, uint8_t softVol);
uint16_t getRandomNumber();
void changeLevel();
uint16_t getLineLength(const uint8_t *str) const;
void handleInventory();
// pieges
bool _pge_playAnimSound;
GroupPGE _pge_groups[256];
GroupPGE *_pge_groupsTable[256];
GroupPGE *_pge_nextFreeGroup;
LivePGE *_pge_liveTable2[256]; // active pieges list (index = pge number)
LivePGE *_pge_liveTable1[256]; // pieges list by room (index = room)
LivePGE _pgeLive[256];
uint8_t _pge_currentPiegeRoom;
bool _pge_currentPiegeFacingDir; // (false == left)
bool _pge_processOBJ;
uint8_t _pge_inpKeysMask;
uint16_t _pge_opTempVar1;
uint16_t _pge_opTempVar2;
uint16_t _pge_compareVar1;
uint16_t _pge_compareVar2;
void pge_resetGroups();
void pge_removeFromGroup(uint8_t idx);
int pge_isInGroup(LivePGE *pge_dst, uint16_t group_id, uint16_t counter);
void pge_loadForCurrentLevel(uint16_t idx);
void pge_process(LivePGE *pge);
void pge_setupNextAnimFrame(LivePGE *pge, GroupPGE *le);
void pge_playAnimSound(LivePGE *pge, uint16_t arg2);
void pge_setupAnim(LivePGE *pge);
int pge_execute(LivePGE *live_pge, InitPGE *init_pge, const Object *obj);
void pge_prepare();
void pge_setupDefaultAnim(LivePGE *pge);
uint16_t pge_processOBJ(LivePGE *pge);
void pge_setupOtherPieges(LivePGE *pge, InitPGE *init_pge);
void pge_addToCurrentRoomList(LivePGE *pge, uint8_t room);
void pge_getInput();
int pge_op_isInpUp(ObjectOpcodeArgs *args);
int pge_op_isInpBackward(ObjectOpcodeArgs *args);
int pge_op_isInpDown(ObjectOpcodeArgs *args);
int pge_op_isInpForward(ObjectOpcodeArgs *args);
int pge_op_isInpUpMod(ObjectOpcodeArgs *args);
int pge_op_isInpBackwardMod(ObjectOpcodeArgs *args);
int pge_op_isInpDownMod(ObjectOpcodeArgs *args);
int pge_op_isInpForwardMod(ObjectOpcodeArgs *args);
int pge_op_isInpIdle(ObjectOpcodeArgs *args);
int pge_op_isInpNoMod(ObjectOpcodeArgs *args);
int pge_op_getCollision0u(ObjectOpcodeArgs *args);
int pge_op_getCollision00(ObjectOpcodeArgs *args);
int pge_op_getCollision0d(ObjectOpcodeArgs *args);
int pge_op_getCollision1u(ObjectOpcodeArgs *args);
int pge_op_getCollision10(ObjectOpcodeArgs *args);
int pge_op_getCollision1d(ObjectOpcodeArgs *args);
int pge_op_getCollision2u(ObjectOpcodeArgs *args);
int pge_op_getCollision20(ObjectOpcodeArgs *args);
int pge_op_getCollision2d(ObjectOpcodeArgs *args);
int pge_op_doesNotCollide0u(ObjectOpcodeArgs *args);
int pge_op_doesNotCollide00(ObjectOpcodeArgs *args);
int pge_op_doesNotCollide0d(ObjectOpcodeArgs *args);
int pge_op_doesNotCollide1u(ObjectOpcodeArgs *args);
int pge_op_doesNotCollide10(ObjectOpcodeArgs *args);
int pge_op_doesNotCollide1d(ObjectOpcodeArgs *args);
int pge_op_doesNotCollide2u(ObjectOpcodeArgs *args);
int pge_op_doesNotCollide20(ObjectOpcodeArgs *args);
int pge_op_doesNotCollide2d(ObjectOpcodeArgs *args);
int pge_op_collides0o0d(ObjectOpcodeArgs *args);
int pge_op_collides2o2d(ObjectOpcodeArgs *args);
int pge_op_collides0o0u(ObjectOpcodeArgs *args);
int pge_op_collides2o2u(ObjectOpcodeArgs *args);
int pge_op_collides2u2o(ObjectOpcodeArgs *args);
int pge_op_isInGroup(ObjectOpcodeArgs *args);
int pge_op_updateGroup0(ObjectOpcodeArgs *args);
int pge_op_updateGroup1(ObjectOpcodeArgs *args);
int pge_op_updateGroup2(ObjectOpcodeArgs *args);
int pge_op_updateGroup3(ObjectOpcodeArgs *args);
int pge_op_isPiegeDead(ObjectOpcodeArgs *args);
int pge_op_collides1u2o(ObjectOpcodeArgs *args);
int pge_op_collides1u1o(ObjectOpcodeArgs *args);
int pge_op_collides1o1u(ObjectOpcodeArgs *args);
int pge_o_unk0x2B(ObjectOpcodeArgs *args);
int pge_o_unk0x2C(ObjectOpcodeArgs *args);
int pge_o_unk0x2D(ObjectOpcodeArgs *args);
int pge_op_nop(ObjectOpcodeArgs *args);
int pge_op_pickupObject(ObjectOpcodeArgs *args);
int pge_op_addItemToInventory(ObjectOpcodeArgs *args);
int pge_op_copyPiege(ObjectOpcodeArgs *args);
int pge_op_canUseCurrentInventoryItem(ObjectOpcodeArgs *args);
int pge_op_removeItemFromInventory(ObjectOpcodeArgs *args);
int pge_o_unk0x34(ObjectOpcodeArgs *args);
int pge_op_isInpMod(ObjectOpcodeArgs *args);
int pge_op_setCollisionState1(ObjectOpcodeArgs *args);
int pge_op_setCollisionState0(ObjectOpcodeArgs *args);
int pge_op_isInGroup1(ObjectOpcodeArgs *args);
int pge_op_isInGroup2(ObjectOpcodeArgs *args);
int pge_op_isInGroup3(ObjectOpcodeArgs *args);
int pge_op_isInGroup4(ObjectOpcodeArgs *args);
int pge_o_unk0x3C(ObjectOpcodeArgs *args);
int pge_o_unk0x3D(ObjectOpcodeArgs *args);
int pge_op_setPiegeCounter(ObjectOpcodeArgs *args);
int pge_op_decPiegeCounter(ObjectOpcodeArgs *args);
int pge_o_unk0x40(ObjectOpcodeArgs *args);
int pge_op_wakeUpPiege(ObjectOpcodeArgs *args);
int pge_op_removePiege(ObjectOpcodeArgs *args);
int pge_op_removePiegeIfNotNear(ObjectOpcodeArgs *args);
int pge_op_loadPiegeCounter(ObjectOpcodeArgs *args);
int pge_o_unk0x45(ObjectOpcodeArgs *args);
int pge_o_unk0x46(ObjectOpcodeArgs *args);
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_killPiege(ObjectOpcodeArgs *args);
int pge_op_isInCurrentRoom(ObjectOpcodeArgs *args);
int pge_op_isNotInCurrentRoom(ObjectOpcodeArgs *args);
int pge_op_scrollPosY(ObjectOpcodeArgs *args);
int pge_op_playDefaultDeathCutscene(ObjectOpcodeArgs *args);
int pge_o_unk0x50(ObjectOpcodeArgs *args);
int pge_o_unk0x52(ObjectOpcodeArgs *args);
int pge_o_unk0x53(ObjectOpcodeArgs *args);
int pge_op_isPiegeNear(ObjectOpcodeArgs *args);
int pge_op_setLife(ObjectOpcodeArgs *args);
int pge_op_incLife(ObjectOpcodeArgs *args);
int pge_op_setPiegeDefaultAnim(ObjectOpcodeArgs *args);
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_playDeathCutscene(ObjectOpcodeArgs *args);
int pge_o_unk0x5D(ObjectOpcodeArgs *args);
int pge_o_unk0x5E(ObjectOpcodeArgs *args);
int pge_o_unk0x5F(ObjectOpcodeArgs *args);
int pge_op_findAndCopyPiege(ObjectOpcodeArgs *args);
int pge_op_isInRandomRange(ObjectOpcodeArgs *args);
int pge_o_unk0x62(ObjectOpcodeArgs *args);
int pge_o_unk0x63(ObjectOpcodeArgs *args);
int pge_o_unk0x64(ObjectOpcodeArgs *args);
int pge_op_addToCredits(ObjectOpcodeArgs *args);
int pge_op_subFromCredits(ObjectOpcodeArgs *args);
int pge_o_unk0x67(ObjectOpcodeArgs *args);
int pge_op_setCollisionState2(ObjectOpcodeArgs *args);
int pge_op_saveState(ObjectOpcodeArgs *args);
int pge_o_unk0x6A(ObjectOpcodeArgs *args);
int pge_op_isInGroupSlice(ObjectOpcodeArgs *args);
int pge_o_unk0x6C(ObjectOpcodeArgs *args);
int pge_op_isCollidingObject(ObjectOpcodeArgs *args);
int pge_o_unk0x6E(ObjectOpcodeArgs *args);
int pge_o_unk0x6F(ObjectOpcodeArgs *args);
int pge_o_unk0x70(ObjectOpcodeArgs *args);
int pge_o_unk0x71(ObjectOpcodeArgs *args);
int pge_o_unk0x72(ObjectOpcodeArgs *args);
int pge_o_unk0x73(ObjectOpcodeArgs *args);
int pge_op_collides4u(ObjectOpcodeArgs *args);
int pge_op_doesNotCollide4u(ObjectOpcodeArgs *args);
int pge_op_isBelowConrad(ObjectOpcodeArgs *args);
int pge_op_isAboveConrad(ObjectOpcodeArgs *args);
int pge_op_isNotFacingConrad(ObjectOpcodeArgs *args);
int pge_op_isFacingConrad(ObjectOpcodeArgs *args);
int pge_op_collides2u1u(ObjectOpcodeArgs *args);
int pge_op_displayText(ObjectOpcodeArgs *args);
int pge_o_unk0x7C(ObjectOpcodeArgs *args);
int pge_op_playSound(ObjectOpcodeArgs *args);
int pge_o_unk0x7E(ObjectOpcodeArgs *args);
int pge_o_unk0x7F(ObjectOpcodeArgs *args);
int pge_op_setPiegePosX(ObjectOpcodeArgs *args);
int pge_op_setPiegePosModX(ObjectOpcodeArgs *args);
int pge_op_changeRoom(ObjectOpcodeArgs *args);
int pge_op_hasInventoryItem(ObjectOpcodeArgs *args);
int pge_op_changeLevel(ObjectOpcodeArgs *args);
int pge_op_shakeScreen(ObjectOpcodeArgs *args);
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_setCurrentInventoryObject(LivePGE *pge);
void pge_updateInventory(LivePGE *pge1, LivePGE *pge2);
void pge_reorderInventory(LivePGE *pge);
LivePGE *pge_getInventoryItemBefore(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_ZOrder(LivePGE *pge, int16_t num, pge_ZOrderCallback compare, uint16_t unk);
void pge_updateGroup(uint8_t idx, uint8_t unk1, int16_t unk2);
void pge_removeFromInventory(LivePGE *pge1, LivePGE *pge2, LivePGE *pge3);
int pge_ZOrderByAnimY(LivePGE *pge1, LivePGE *pge2, uint8_t comp, uint8_t comp2);
int pge_ZOrderByAnimYIfType(LivePGE *pge1, LivePGE *pge2, uint8_t comp, uint8_t comp2);
int pge_ZOrderIfIndex(LivePGE *pge1, LivePGE *pge2, uint8_t comp, uint8_t comp2);
int pge_ZOrderByIndex(LivePGE *pge1, LivePGE *pge2, uint8_t comp, uint8_t comp2);
int pge_ZOrderByObj(LivePGE *pge1, LivePGE *pge2, uint8_t comp, uint8_t comp2);
int pge_ZOrderIfDifferentDirection(LivePGE *pge1, LivePGE *pge2, uint8_t comp, uint8_t comp2);
int pge_ZOrderIfSameDirection(LivePGE *pge1, LivePGE *pge2, uint8_t comp, uint8_t comp2);
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);
// collision
CollisionSlot _col_slots[256];
uint8_t _col_curPos;
CollisionSlot *_col_slotsTable[256];
CollisionSlot *_col_curSlot;
CollisionSlot2 _col_slots2[256];
CollisionSlot2 *_col_slots2Cur;
CollisionSlot2 *_col_slots2Next;
uint8_t _col_activeCollisionSlots[0x30 * 3]; // left, current, right
uint8_t _col_currentLeftRoom;
uint8_t _col_currentRightRoom;
int16_t _col_currentPiegeGridPosX;
int16_t _col_currentPiegeGridPosY;
void col_prepareRoomState();
void col_clearState();
LivePGE *col_findPiege(LivePGE *pge, uint16_t arg2);
int16_t col_findSlot(int16_t pos);
void col_preparePiegeState(LivePGE *dst_pge);
uint16_t col_getGridPos(LivePGE *pge, int16_t dx);
int16_t col_getGridData(LivePGE *pge, int16_t dy, int16_t dx);
uint8_t col_findCurrentCollidingObject(LivePGE *pge, uint8_t n1, uint8_t n2, uint8_t n3, LivePGE **pge_out);
int16_t col_detectHit(LivePGE *pge, int16_t arg2, int16_t arg4, col_Callback1 callback1, col_Callback2 callback2, int16_t argA, int16_t argC);
int col_detectHitCallback2(LivePGE *pge1, LivePGE *pge2, int16_t unk1, int16_t unk2);
int col_detectHitCallback3(LivePGE *pge1, LivePGE *pge2, int16_t unk1, int16_t unk2);
int col_detectHitCallback4(LivePGE *pge1, LivePGE *pge2, int16_t unk1, int16_t unk2);
int col_detectHitCallback5(LivePGE *pge1, LivePGE *pge2, int16_t unk1, int16_t unk2);
int col_detectHitCallback1(LivePGE *pge, int16_t dy, int16_t unk1, int16_t unk2);
int col_detectHitCallback6(LivePGE *pge, int16_t dy, int16_t unk1, int16_t unk2);
int col_detectHitCallbackHelper(LivePGE *pge, int16_t unk1);
int col_detectGunHitCallback1(LivePGE *pge, int16_t arg2, int16_t arg4, int16_t arg6);
int col_detectGunHitCallback2(LivePGE *pge1, LivePGE *pge2, int16_t arg4, int16_t);
int col_detectGunHitCallback3(LivePGE *pge1, LivePGE *pge2, int16_t arg4, int16_t);
int col_detectGunHit(LivePGE *pge, int16_t arg2, int16_t arg4, col_Callback1 callback1, col_Callback2 callback2, int16_t argA, int16_t argC);
// input
uint8_t _inp_lastKeysHit;
uint8_t _inp_lastKeysHitLeftRight;
bool _inp_replay;
bool _inp_record;
File *_inp_demo;
void inp_handleSpecialKeys();
void inp_update();
// save/load state
uint8_t _stateSlot;
bool _validSaveState;
void makeGameDemoName(char *buf);
void makeGameStateName(uint8_t slot, char *buf);
bool saveGameState(uint8_t slot);
bool loadGameState(uint8_t slot);
void saveState(File *f);
void loadState(File *f);
};
#endif // GAME_H__

717
graphics.cpp Normal file
View File

@ -0,0 +1,717 @@
/* REminiscence - Flashback interpreter
* Copyright (C) 2005-2015 Gregory Montoir
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "graphics.h"
void Graphics::setClippingRect(int16_t rx, int16_t ry, int16_t rw, int16_t rh) {
debug(DBG_VIDEO, "Graphics::setClippingRect(%d, %d, %d, %d)", rx, ry, rw, rh);
_crx = rx;
_cry = ry;
_crw = rw;
_crh = rh;
}
void Graphics::drawPoint(uint8_t color, const Point *pt) {
debug(DBG_VIDEO, "Graphics::drawPoint() col=0x%X x=%d, y=%d", color, pt->x, pt->y);
if (pt->x >= 0 && pt->x < _crw && pt->y >= 0 && pt->y < _crh) {
*(_layer + (pt->y + _cry) * 256 + pt->x + _crx) = color;
}
}
void Graphics::drawLine(uint8_t color, const Point *pt1, const Point *pt2) {
debug(DBG_VIDEO, "Graphics::drawLine()");
int16_t dxincr1 = 1;
int16_t dyincr1 = 1;
int16_t dx = pt2->x - pt1->x;
if (dx < 0) {
dxincr1 = -1;
dx = -dx;
}
int16_t dy = pt2->y - pt1->y;
if (dy < 0) {
dyincr1 = -1;
dy = -dy;
}
int16_t dxincr2, dyincr2, delta1, delta2;
if (dx < dy) {
dxincr2 = 0;
dyincr2 = 1;
delta1 = dx;
delta2 = dy;
if (dyincr1 < 0) {
dyincr2 = -1;
}
} else {
dxincr2 = 1;
dyincr2 = 0;
delta1 = dy;
delta2 = dx;
if (dxincr1 < 0) {
dxincr2 = -1;
}
}
Point pt;
pt.x = pt1->x;
pt.y = pt1->y;
int16_t octincr1 = delta1 * 2 - delta2 * 2;
int16_t octincr2 = delta1 * 2;
int16_t oct = delta1 * 2 - delta2;
if (delta2 >= 0) {
drawPoint(color, &pt);
while (--delta2 >= 0) {
if (oct >= 0) {
pt.x += dxincr1;
pt.y += dyincr1;
oct += octincr1;
} else {
pt.x += dxincr2;
pt.y += dyincr2;
oct += octincr2;
}
drawPoint(color, &pt);
}
}
}
void Graphics::addEllipseRadius(int16_t y, int16_t x1, int16_t x2) {
debug(DBG_VIDEO, "Graphics::addEllipseRadius()");
if (y >= 0 && y <= _crh) {
y = (y - _areaPoints[0]) * 2;
if (x1 < 0) {
x1 = 0;
}
if (x2 >= _crw) {
x2 = _crw - 1;
}
_areaPoints[y + 1] = x1;
_areaPoints[y + 2] = x2;
}
}
void Graphics::drawEllipse(uint8_t color, bool hasAlpha, const Point *pt, int16_t rx, int16_t ry) {
debug(DBG_VIDEO, "Graphics::drawEllipse()");
bool flag = false;
int16_t y = pt->y - ry;
if (y < 0) {
y = 0;
}
if (y < _crh) {
if (pt->y + ry >= 0) {
_areaPoints[0] = y;
int32_t dy = 0;
int32_t rxsq = rx * rx;
int32_t rxsq2 = rx * rx * 2;
int32_t rxsq4 = rx * rx * 4;
int32_t rysq = ry * ry;
int32_t rysq2 = ry * ry * 2;
int32_t rysq4 = ry * ry * 4;
int32_t dx = 0;
int32_t b = rx * ((rysq2 & 0xFFFF) + (rysq2 >> 16));
int32_t a = 2 * b;
int32_t ny1, ny2, nx1, nx2;
ny1 = ny2 = rysq4 / 2 - a + rxsq;
nx1 = nx2 = rxsq2 - b + rysq;
while (ny2 < 0) {
int16_t x2 = pt->x + rx;
int16_t x1 = pt->x - rx;
int16_t by = pt->y + dy;
int16_t ty = pt->y - dy;
if (x1 != x2) {
addEllipseRadius(by, x1, x2);
if (ty < by) {
addEllipseRadius(ty, x1, x2);
}
}
dy += 1;
dx += rxsq4;
nx1 = dx;
if (nx2 < 0) {
nx2 += nx1 + rxsq2;
ny2 += nx1;
} else {
--rx;
a -= rysq4;
ny1 = a;
nx2 += nx1 + rxsq2 - ny1;
ny2 += nx1 + rysq2 - ny1;
}
}
while (rx >= 0) {
bool flag2 = false;
int16_t x2 = pt->x + rx;
int16_t x1 = pt->x - rx;
int16_t by = pt->y + dy;
int16_t ty = pt->y - dy;
if (!flag && x1 != x2) {
flag2 = true;
addEllipseRadius(by, x1, x2);
if (ty < by) {
addEllipseRadius(ty, x1, x2);
}
}
if (flag2) {
flag = true;
}
--rx;
a -= rysq4;
nx1 = a;
if (ny2 < 0) {
++dy;
flag = false;
dx += rxsq4;
ny2 += dx - nx1 + rysq2;
ny1 = dx - nx1 + rysq2;
} else {
ny2 += rysq2 - nx1;
ny1 = rysq2 - nx1;
}
}
if (flag) {
++dy;
}
while (dy <= ry) {
int16_t ty = pt->y - dy;
int16_t by = pt->y + dy;
if (ty < by) {
addEllipseRadius(ty, pt->x, pt->x);
}
addEllipseRadius(by, pt->x, pt->x);
++dy;
}
y = pt->y + ry + 1;
if (y > _crh) {
y = _crh;
}
y = (y - _areaPoints[0]) * 2;
_areaPoints[y + 1] = -1;
fillArea(color, hasAlpha);
}
}
}
void Graphics::fillArea(uint8_t color, bool hasAlpha) {
debug(DBG_VIDEO, "Graphics::fillArea()");
int16_t *pts = _areaPoints;
uint8_t *dst = _layer + (_cry + *pts++) * 256 + _crx;
int16_t x1 = *pts++;
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
}
}
dst += 256;
x1 = *pts++;
} while (x1 >= 0);
} else {
do {
int16_t x2 = *pts++;
if (x2 < _crw && x2 >= x1) {
int len = x2 - x1 + 1;
memset(dst + x1, color, len);
}
dst += 256;
x1 = *pts++;
} while (x1 >= 0);
}
}
}
void Graphics::drawSegment(uint8_t color, bool hasAlpha, int16_t ys, const Point *pts, uint8_t numPts) {
debug(DBG_VIDEO, "Graphics::drawSegment()");
int16_t xmin, xmax, ymin, ymax;
xmin = xmax = pts[0].x;
ymin = ymax = pts[0].y;
for (int i = 1; i < numPts; ++i) {
int16_t x = pts[i].x;
int16_t y = pts[i].y;
if ((xmin << 16) + ymin > (x << 16) + y) {
xmin = x;
ymin = y;
}
if ((xmax << 16) + ymax < (x << 16) + y) {
xmax = x;
ymax = y;
}
}
if (xmin < 0) {
xmin = 0;
}
if (xmax >= _crw) {
xmax = _crw - 1;
}
_areaPoints[0] = ys;
_areaPoints[1] = xmin;
_areaPoints[2] = xmax;
_areaPoints[3] = -1;
fillArea(color, hasAlpha);
}
void Graphics::drawPolygonOutline(uint8_t color, const Point *pts, uint8_t numPts) {
debug(DBG_VIDEO, "Graphics::drawPolygonOutline()");
assert(numPts >= 2);
int i;
for (i = 0; i < numPts - 1; ++i) {
drawLine(color, &pts[i], &pts[i + 1]);
}
drawLine(color, &pts[i], &pts[0]);
}
static int32_t calcPolyStep1(int16_t dx, int16_t dy) {
debug(DBG_VIDEO, "Graphics::calcPolyStep1()");
assert(dy != 0);
int32_t a = dx * 256;
if ((a >> 16) < dy) {
a = ((int16_t)(a / dy)) * 256;
} else {
a = ((a / 256) / dy) & 0xFFFF0000;
}
return a;
}
static int32_t calcPolyStep2(int16_t dx, int16_t dy) {
debug(DBG_VIDEO, "Graphics::calcPolyStep2()");
assert(dy != 0);
int32_t a = dx * 256;
if ((a >> 16) < dy) {
a = ((int16_t)(a / dy)) * 256;
} else {
a = ((a / 256) / dy) << 16;
}
return a;
}
static void drawPolygonHelper1(int32_t &x, int16_t &y, int32_t &step, int16_t *&pts, int16_t *&start) {
bool first = true;
x = pts[0];
y = pts[1];
int16_t dy, dx;
do {
if (first) {
first = false;
} else {
x = *pts;
}
--pts;
dy = *pts - y;
--pts;
dx = *pts - x;
} while (dy <= 0 && start < pts);
x <<= 16;
if (dy > 0) {
step = calcPolyStep1(dx, dy);
}
}
static void drawPolygonHelper2(int32_t &x, int16_t &y, int32_t &step, int16_t *&pts, int16_t *&start) {
bool first = true;
x = *start++;
y = *start++;
int16_t dy, dx;
do {
if (first) {
first = false;
} else {
x = *start;
start += 2;
}
dy = start[1] - y;
dx = start[0] - x;
} while (dy <= 0 && start < pts);
x <<= 16;
if (dy > 0) {
step = calcPolyStep2(dx, dy);
}
}
void Graphics::drawPolygon(uint8_t color, bool hasAlpha, const Point *pts, uint8_t numPts) {
debug(DBG_VIDEO, "Graphics::drawPolygon()");
assert(numPts * 4 < 0x100);
int16_t *apts1 = &_areaPoints[0x100];
int16_t *apts2 = &_areaPoints[0x100 + numPts * 2];
int16_t xmin, xmax, ymin, ymax;
xmin = xmax = pts[0].x;
ymin = ymax = pts[0].y;
int16_t *spts = apts1;
*apts1++ = *apts2++ = pts[0].x;
*apts1++ = *apts2++ = pts[0].y;
for (int p = 1; p < numPts; ++p) {
int16_t x = pts[p].x;
int16_t y = pts[p].y;
if (ymin > y) {
ymin = y;
spts = apts1;
}
if (ymax < y) {
ymax = y;
}
*apts1++ = *apts2++ = x;
*apts1++ = *apts2++ = y;
if (xmin > x) {
xmin = x;
}
if (xmax < x) {
xmax = x;
}
}
int16_t *rpts = _areaPoints;
if (xmax < 0 || xmin >= _crw || ymax < 0 || ymin >= _crh) {
return;
}
if (numPts == 2) {
drawLine(color, &pts[0], &pts[1]);
return;
}
if (ymax == ymin) {
drawSegment(color, hasAlpha, ymax, pts, numPts);
return;
}
int16_t x, dx, y, dy;
int32_t a, b, d, f;
int32_t xstep1 = 0;
int32_t xstep2 = 0;
apts1 = &spts[numPts * 2];
xmax = _crw - 1;
ymax = _crh - 1;
int32_t l1 = 65536;
int32_t l2 = -65536;
if (ymin < 0) {
int16_t x0, y0;
do {
--apts1;
y0 = *apts1;
--apts1;
x0 = *apts1;
} while (y0 < 0);
x = apts1[2];
y = apts1[3];
dy = y0 - y;
dx = x0 - x;
xstep1 = (dy << 16) | dx;
assert(dy != 0);
a = y * dx / dy;
b = (x - a) << 16;
d = xstep1 = calcPolyStep1(dx, dy);
if (d < 0) {
d = -d;
}
if (d < l1) {
d = l2;
}
d /= 2;
b -= d;
do {
x0 = *spts++;
y0 = *spts++;
} while (*(spts + 1) < 0);
dy = spts[1] - y0;
dx = spts[0] - x0;
xstep2 = (dy << 16) | dx;
assert(dy != 0);
a = y0 * dx / dy;
f = (x0 - a) << 16;
d = xstep2 = calcPolyStep2(dx, dy);
if (d < 0) {
d = -d;
}
if (d < l1) {
d = l1;
}
d /= 2;
f += d;
ymin = 0;
*rpts++ = 0;
goto gfx_startLine;
}
*rpts++ = ymin;
gfx_startNewLine:
drawPolygonHelper2(f, ymin, xstep2, apts1, spts);
if (spts >= apts1) {
b = apts1[0] << 16;
dy = apts1[1];
if (dy <= ymax) goto gfx_endLine;
goto gfx_fillArea;
}
drawPolygonHelper1(b, ymin, xstep1, apts1, spts);
d = xstep1;
if (d < 0) {
if (d >= l2) {
d = l1;
}
d /= 2;
b += d;
}
d = xstep2;
if (d >= 0) {
if (d <= l1) {
d = l1;
}
d /= 2;
f += d;
}
d = b;
if (d < 0) {
d = 0;
}
x = f >> 16;
if (x > xmax) {
x = xmax;
}
*rpts++ = d >> 16;
*rpts++ = x;
++ymin;
d = xstep1;
if (d >= 0) {
if (d <= l1) {
d = l1;
}
d /= 2;
}
b += d;
d = xstep2;
if (d < 0) {
if (d >= l2) {
d = l1;
}
d /= 2;
}
f += d;
gfx_startLine:
while (1) {
dy = apts1[1];
if (spts >= apts1) {
break;
} else if (dy > spts[1]) {
dy = spts[1];
if (dy > ymax) {
goto gfx_drawPolygonEnd;
}
dy -= ymin;
if (dy > 0) {
--dy;
do {
a = b;
if (a < 0) {
a = 0;
}
x = f >> 16;
if (x > xmax) {
x = xmax;
}
*rpts++ = a >> 16;
*rpts++ = x;
b += xstep1;
f += xstep2;
--dy;
} while (dy >= 0);
}
drawPolygonHelper2(f, ymin, xstep2, apts1, spts);
d = xstep2;
if (d >= 0) {
if (d <= l1) {
d = l1;
}
d /= 2;
f += d;
} else {
d = b;
if (d < 0) {
d = 0;
}
x = f >> 16;
if (x > xmax) {
x = xmax;
}
*rpts++ = d >> 16;
*rpts++ = x;
++ymin;
d = xstep2;
if (d >= l2) {
d = l1;
}
d /= 2;
f += d;
b += xstep1;
}
} else if (dy == spts[1]) {
if (dy > ymax) goto gfx_drawPolygonEnd;
dy -= ymin;
if (dy > 0) {
--dy;
do {
a = b;
if (a < 0) {
a = 0;
}
x = f >> 16;
if (x > xmax) {
x = xmax;
}
*rpts++ = a >> 16;
*rpts++ = x;
b += xstep1;
f += xstep2;
--dy;
} while (dy >= 0);
}
goto gfx_startNewLine;
} else if (dy > ymax) {
goto gfx_drawPolygonEnd;
} else {
dy -= ymin;
if (dy > 0) {
--dy;
do {
a = b;
if (a < 0) {
a = 0;
}
x = f >> 16;
if (x > xmax) {
x = xmax;
}
*rpts++ = a >> 16;
*rpts++ = x;
b += xstep1;
f += xstep2;
--dy;
} while (dy >= 0);
}
drawPolygonHelper1(b, ymin, xstep1, apts1, spts);
d = xstep1;
if (d < 0) {
if (d >= l2) {
d = l1;
}
d /= 2;
b += d;
} else {
d = b;
if (d < 0) {
d = 0;
}
x = f >> 16;
if (x > xmax) {
x = xmax;
}
*rpts++ = d >> 16;
*rpts++ = x;
++ymin;
d = xstep1;
if (d <= l1) {
d = l1;
}
d /= 2;
b += d;
f += xstep2;
}
}
}
if (dy > ymax) goto gfx_drawPolygonEnd;
dy -= ymin;
if (dy < 0) goto gfx_fillArea;
if (dy > 0) {
--dy;
do {
a = b;
if (a < 0) {
a = 0;
}
x = f >> 16;
if (x > xmax) {
x = xmax;
}
*rpts++ = a >> 16;
*rpts++ = x;
b += xstep1;
f += xstep2;
--dy;
} while (dy >= 0);
}
b = f = (apts1[0] << 16) | apts1[1];
gfx_endLine:
d = xstep1;
if (d >= 0) {
if (d >= l1) {
d /= 2;
b -= d;
}
}
d = xstep2;
if (d < 0) {
d /= 2;
f -= d;
}
a = b;
if (a < 0) {
a = 0;
}
x = f >> 16;
if (x > xmax) {
x = xmax;
}
*rpts++ = a >> 16;
*rpts++ = x;
goto gfx_fillArea;
gfx_drawPolygonEnd:
dy = ymax - ymin;
if (dy >= 0) {
do {
a = b;
if (a < 0) {
a = 0;
}
x = f >> 16;
if (x > xmax) {
x = xmax;
}
*rpts++ = a >> 16;
*rpts++ = x;
b += xstep1;
f += xstep2;
--dy;
} while (dy >= 0);
}
gfx_fillArea:
*rpts++ = -1;
fillArea(color, hasAlpha);
}

39
graphics.h Normal file
View File

@ -0,0 +1,39 @@
/* REminiscence - Flashback interpreter
* Copyright (C) 2005-2015 Gregory Montoir
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef GRAPHICS_H__
#define GRAPHICS_H__
#include "intern.h"
struct Graphics {
uint8_t *_layer;
int16_t _areaPoints[0x200];
int16_t _crx, _cry, _crw, _crh;
void setClippingRect(int16_t vx, int16_t vy, int16_t vw, int16_t vh);
void drawPoint(uint8_t color, const Point *pt);
void drawLine(uint8_t color, const Point *pt1, const Point *pt2);
void addEllipseRadius(int16_t y, int16_t x1, int16_t x2);
void drawEllipse(uint8_t color, bool hasAlpha, const Point *pt, int16_t rx, int16_t ry);
void fillArea(uint8_t color, bool hasAlpha);
void drawSegment(uint8_t color, bool hasAlpha, int16_t ys, const Point *pts, uint8_t numPts);
void drawPolygonOutline(uint8_t color, const Point *pts, uint8_t numPts);
void drawPolygon(uint8_t color, bool hasAlpha, const Point *pts, uint8_t numPts);
};
#endif // GRAPHICS_H__

224
intern.h Normal file
View File

@ -0,0 +1,224 @@
/* REminiscence - Flashback interpreter
* Copyright (C) 2005-2015 Gregory Montoir
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef INTERN_H__
#define INTERN_H__
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cassert>
#include <stdint.h>
#include "util.h"
#define ABS(x) ((x)<0?-(x):(x))
#define MAX(x,y) ((x)>(y)?(x):(y))
#define MIN(x,y) ((x)<(y)?(x):(y))
#ifndef ARRAYSIZE
#define ARRAYSIZE(a) (sizeof(a)/sizeof(a[0]))
#endif
inline void SWAP_UINT16(uint16_t *ptr) {
const uint8_t hi = *ptr >> 8;
const uint8_t lo = *ptr & 255;
*ptr = (lo << 8) | hi;
}
inline uint16_t READ_BE_UINT16(const void *ptr) {
const uint8_t *b = (const uint8_t *)ptr;
return (b[0] << 8) | b[1];
}
inline uint32_t READ_BE_UINT32(const void *ptr) {
const uint8_t *b = (const uint8_t *)ptr;
return (b[0] << 24) | (b[1] << 16) | (b[2] << 8) | b[3];
}
inline uint16_t READ_LE_UINT16(const void *ptr) {
const uint8_t *b = (const uint8_t *)ptr;
return (b[1] << 8) | b[0];
}
inline uint32_t READ_LE_UINT32(const void *ptr) {
const uint8_t *b = (const uint8_t *)ptr;
return (b[3] << 24) | (b[2] << 16) | (b[1] << 8) | b[0];
}
template<typename T>
inline void SWAP(T &a, T &b) {
T tmp = a;
a = b;
b = tmp;
}
enum Language {
LANG_FR,
LANG_EN,
LANG_DE,
LANG_SP,
LANG_IT
};
enum ResourceType {
kResourceTypeAmiga,
kResourceTypePC
};
struct Color {
uint8_t r;
uint8_t g;
uint8_t b;
};
struct Point {
int16_t x;
int16_t y;
};
struct Level {
const char *name;
const char *name2;
const char *nameAmiga;
uint16_t cutscene_id;
uint8_t sound;
uint8_t track;
};
struct InitPGE {
uint16_t type;
int16_t pos_x;
int16_t pos_y;
uint16_t obj_node_number;
uint16_t life;
int16_t counter_values[4];
uint8_t object_type;
uint8_t init_room;
uint8_t room_location;
uint8_t init_flags;
uint8_t colliding_icon_num;
uint8_t icon_num;
uint8_t object_id;
uint8_t skill;
uint8_t mirror_x;
uint8_t flags;
uint8_t unk1C; // collidable, collision_data_len
uint16_t text_num;
};
struct LivePGE {
uint16_t obj_type;
int16_t pos_x;
int16_t pos_y;
uint8_t anim_seq;
uint8_t room_location;
int16_t life;
int16_t counter_value;
uint8_t collision_slot;
uint8_t next_inventory_PGE;
uint8_t current_inventory_PGE;
uint8_t unkF; // unk_inventory_PGE
uint16_t anim_number;
uint8_t flags;
uint8_t index;
uint16_t first_obj_number;
LivePGE *next_PGE_in_room;
InitPGE *init_PGE;
};
struct GroupPGE {
GroupPGE *next_entry;
uint16_t index;
uint16_t group_id;
};
struct Object {
uint16_t type;
int8_t dx;
int8_t dy;
uint16_t init_obj_type;
uint8_t opcode2;
uint8_t opcode1;
uint8_t flags;
uint8_t opcode3;
uint16_t init_obj_number;
int16_t opcode_arg1;
int16_t opcode_arg2;
int16_t opcode_arg3;
};
struct ObjectNode {
uint16_t last_obj_number;
Object *objects;
uint16_t num_objects;
};
struct ObjectOpcodeArgs {
LivePGE *pge; // arg0
int16_t a; // arg2
int16_t b; // arg4
};
struct AnimBufferState {
int16_t x, y;
uint8_t w, h;
const uint8_t *dataPtr;
LivePGE *pge;
};
struct AnimBuffers {
AnimBufferState *_states[4];
uint8_t _curPos[4];
void addState(uint8_t stateNum, int16_t x, int16_t y, const uint8_t *dataPtr, LivePGE *pge, uint8_t w = 0, uint8_t h = 0);
};
struct CollisionSlot {
int16_t ct_pos;
CollisionSlot *prev_slot;
LivePGE *live_pge;
uint16_t index;
};
struct BankSlot {
uint16_t entryNum;
uint8_t *ptr;
};
struct CollisionSlot2 {
CollisionSlot2 *next_slot;
int8_t *unk2;
uint8_t data_size;
uint8_t data_buf[0x10]; // XXX check size
};
struct InventoryItem {
uint8_t icon_num;
InitPGE *init_pge;
LivePGE *live_pge;
};
struct SoundFx {
uint32_t offset;
uint16_t len;
uint8_t *data;
};
extern const char *g_caption;
#endif // INTERN_H__

49
locale.cpp Normal file
View File

@ -0,0 +1,49 @@
/* REminiscence - Flashback interpreter
* Copyright (C) 2005-2015 Gregory Montoir
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "locale.h"
Locale::Locale(Version ver)
: _ver(ver) {
switch (_ver) {
case LANG_FR:
_stringsTable = _stringsTableFR;
_textsTable = _textsTableFR;
break;
case LANG_EN:
_stringsTable = _stringsTableEN;
_textsTable = _textsTableEN;
break;
case LANG_DE:
_stringsTable = _stringsTableDE;
_textsTable = _textsTableDE;
break;
case LANG_SP:
_stringsTable = _stringsTableSP;
_textsTable = _textsTableSP;
break;
}
}
const char *Locale::get(int id) const {
const char *text = 0;
if (id >= 0 && id < LI_NUM) {
text = _textsTable[id];
}
return text;
}

68
locale.h Normal file
View File

@ -0,0 +1,68 @@
/* REminiscence - Flashback interpreter
* Copyright (C) 2005-2015 Gregory Montoir
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef LOCALE_H__
#define LOCALE_H__
#include "intern.h"
struct Locale {
enum Id {
LI_01_CONTINUE_OR_ABORT = 0,
LI_02_TIME,
LI_03_CONTINUE,
LI_04_ABORT,
LI_05_COMPLETED,
LI_06_LEVEL,
LI_07_START,
LI_08_SKILL,
LI_09_PASSWORD,
LI_10_INFO,
LI_11_QUIT,
LI_12_SKILL_LEVEL,
LI_13_EASY,
LI_14_NORMAL,
LI_15_EXPERT,
LI_16_ENTER_PASSWORD1,
LI_17_ENTER_PASSWORD2,
LI_18_RESUME_GAME,
LI_19_ABORT_GAME,
LI_20_LOAD_GAME,
LI_21_SAVE_GAME,
LI_22_SAVE_SLOT,
LI_NUM
};
static const char *_textsTableFR[];
static const char *_textsTableEN[];
static const char *_textsTableDE[];
static const char *_textsTableSP[];
static const uint8_t _stringsTableFR[];
static const uint8_t _stringsTableEN[];
static const uint8_t _stringsTableDE[];
static const uint8_t _stringsTableSP[];
Version _ver;
const char **_textsTable;
const uint8_t *_stringsTable;
Locale(Version ver);
const char *get(int id) const;
};
#endif // LOCALE_H__

132
main.cpp Normal file
View File

@ -0,0 +1,132 @@
/* REminiscence - Flashback interpreter
* Copyright (C) 2005-2015 Gregory Montoir
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <getopt.h>
#include <sys/stat.h>
#include "file.h"
#include "fs.h"
#include "game.h"
#include "systemstub.h"
static const char *USAGE =
"REminiscence - Flashback Interpreter\n"
"Usage: %s [OPTIONS]...\n"
" --datapath=PATH Path to data files (default 'DATA')\n"
" --savepath=PATH Path to save files (default '.')\n"
" --levelnum=NUM Starting level (default '0')";
static int detectVersion(FileSystem *fs) {
static const struct {
const char *filename;
int type;
const char *name;
} table[] = {
{ "LEVEL1.MAP", kResourceTypePC, "PC" },
{ "LEVEL1.LEV", kResourceTypeAmiga, "Amiga" },
{ "DEMO.LEV", kResourceTypeAmiga, "Amiga" },
{ 0, -1 }
};
for (int i = 0; table[i].filename; ++i) {
File f;
if (f.open(table[i].filename, "rb", fs)) {
debug(DBG_INFO, "Detected %s version", table[i].name);
return table[i].type;
}
}
return -1;
}
static Language detectLanguage(FileSystem *fs) {
static const struct {
const char *filename;
Language language;
} table[] = {
// PC
{ "ENGCINE.TXT", LANG_EN },
{ "FR_CINE.TXT", LANG_FR },
{ "GERCINE.TXT", LANG_DE },
{ "SPACINE.TXT", LANG_SP },
{ "ITACINE.TXT", LANG_IT },
// Amiga
{ "FRCINE.TXT", LANG_FR },
{ 0, LANG_EN }
};
for (int i = 0; table[i].filename; ++i) {
File f;
if (f.open(table[i].filename, "rb", fs)) {
return table[i].language;
}
}
return LANG_EN;
}
const char *g_caption = "REminiscence";
#undef main
int main(int argc, char *argv[]) {
const char *dataPath = "DATA";
const char *savePath = ".";
int levelNum = 0;
if (argc == 2) {
// data path as the only command line argument
struct stat st;
if (stat(argv[1], &st) == 0 && S_ISDIR(st.st_mode)) {
dataPath = strdup(argv[1]);
}
}
while (1) {
static struct option options[] = {
{ "datapath", required_argument, 0, 1 },
{ "savepath", required_argument, 0, 2 },
{ "levelnum", required_argument, 0, 3 },
{ 0, 0, 0, 0 }
};
int index;
const int c = getopt_long(argc, argv, "", options, &index);
if (c == -1) {
break;
}
switch (c) {
case 1:
dataPath = strdup(optarg);
break;
case 2:
savePath = strdup(optarg);
break;
case 3:
levelNum = atoi(optarg);
break;
default:
printf(USAGE, argv[0]);
return 0;
}
}
g_debugMask = DBG_INFO; // DBG_CUT | DBG_VIDEO | DBG_RES | DBG_MENU | DBG_PGE | DBG_GAME | DBG_UNPACK | DBG_COL | DBG_MOD | DBG_SFX | DBG_FILE;
FileSystem fs(dataPath);
const int version = detectVersion(&fs);
if (version == -1) {
error("Unable to find data files, check that all required files are present");
return -1;
}
Language language = detectLanguage(&fs);
SystemStub *stub = SystemStub_SDL_create();
Game *g = new Game(stub, &fs, savePath, levelNum, (ResourceType)version, language);
g->run();
delete g;
delete stub;
return 0;
}

407
menu.cpp Normal file
View File

@ -0,0 +1,407 @@
/* REminiscence - Flashback interpreter
* Copyright (C) 2005-2015 Gregory Montoir
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "game.h"
#include "resource.h"
#include "systemstub.h"
#include "video.h"
#include "menu.h"
Menu::Menu(Resource *res, SystemStub *stub, Video *vid)
: _res(res), _stub(stub), _vid(vid) {
}
void Menu::drawString(const char *str, int16_t y, int16_t x, uint8_t color) {
debug(DBG_MENU, "Menu::drawString()");
uint8_t v1b = _vid->_charFrontColor;
uint8_t v2b = _vid->_charTransparentColor;
uint8_t v3b = _vid->_charShadowColor;
switch (color) {
case 0:
_vid->_charFrontColor = _charVar1;
_vid->_charTransparentColor = _charVar2;
_vid->_charShadowColor = _charVar2;
break;
case 1:
_vid->_charFrontColor = _charVar2;
_vid->_charTransparentColor = _charVar1;
_vid->_charShadowColor = _charVar1;
break;
case 2:
_vid->_charFrontColor = _charVar3;
_vid->_charTransparentColor = 0xFF;
_vid->_charShadowColor = _charVar1;
break;
case 3:
_vid->_charFrontColor = _charVar4;
_vid->_charTransparentColor = 0xFF;
_vid->_charShadowColor = _charVar1;
break;
case 4:
_vid->_charFrontColor = _charVar2;
_vid->_charTransparentColor = 0xFF;
_vid->_charShadowColor = _charVar1;
break;
case 5:
_vid->_charFrontColor = _charVar2;
_vid->_charTransparentColor = 0xFF;
_vid->_charShadowColor = _charVar5;
break;
}
drawString2(str, y, x);
_vid->_charFrontColor = v1b;
_vid->_charTransparentColor = v2b;
_vid->_charShadowColor = v3b;
}
void Menu::drawString2(const char *str, int16_t y, int16_t x) {
debug(DBG_MENU, "Menu::drawString2()");
int len = 0;
while (*str) {
_vid->PC_drawChar((uint8_t)*str, y, x + len);
++str;
++len;
}
_vid->markBlockAsDirty(x * 8, y * 8, len * 8, 8);
}
void Menu::loadPicture(const char *prefix) {
debug(DBG_MENU, "Menu::loadPicture('%s')", prefix);
_res->load_MAP_menu(prefix, _res->_memBuf);
for (int i = 0; i < 4; ++i) {
for (int y = 0; y < 224; ++y) {
for (int x = 0; x < 64; ++x) {
_vid->_frontLayer[i + x * 4 + 256 * y] = _res->_memBuf[0x3800 * i + x + 64 * y];
}
}
}
_res->load_PAL_menu(prefix, _res->_memBuf);
_stub->setPalette(_res->_memBuf, 256);
}
void Menu::handleInfoScreen() {
debug(DBG_MENU, "Menu::handleInfoScreen()");
_vid->fadeOut();
switch (_res->_lang) {
case LANG_FR:
loadPicture("instru_f");
break;
case LANG_EN:
case LANG_DE:
case LANG_SP:
case LANG_IT:
loadPicture("instru_e");
break;
}
_vid->fullRefresh();
_vid->updateScreen();
do {
_stub->sleep(EVENTS_DELAY);
_stub->processEvents();
if (_stub->_pi.enter) {
_stub->_pi.enter = false;
break;
}
} while (!_stub->_pi.quit);
}
void Menu::handleSkillScreen(uint8_t &new_skill) {
debug(DBG_MENU, "Menu::handleSkillScreen()");
static const uint8_t option_colors[3][3] = { { 2, 3, 3 }, { 3, 2, 3}, { 3, 3, 2 } };
_vid->fadeOut();
loadPicture("menu3");
_vid->fullRefresh();
drawString(_res->getMenuString(LocaleData::LI_12_SKILL_LEVEL), 12, 4, 3);
int skill_level = new_skill;
do {
drawString(_res->getMenuString(LocaleData::LI_13_EASY), 15, 14, option_colors[skill_level][0]);
drawString(_res->getMenuString(LocaleData::LI_14_NORMAL), 17, 14, option_colors[skill_level][1]);
drawString(_res->getMenuString(LocaleData::LI_15_EXPERT), 19, 14, option_colors[skill_level][2]);
_vid->updateScreen();
_stub->sleep(EVENTS_DELAY);
_stub->processEvents();
if (_stub->_pi.dirMask & PlayerInput::DIR_UP) {
_stub->_pi.dirMask &= ~PlayerInput::DIR_UP;
if (skill_level != 0) {
--skill_level;
} else {
skill_level = 2;
}
}
if (_stub->_pi.dirMask & PlayerInput::DIR_DOWN) {
_stub->_pi.dirMask &= ~PlayerInput::DIR_DOWN;
if (skill_level != 2) {
++skill_level;
} else {
skill_level = 0;
}
}
if (_stub->_pi.enter) {
_stub->_pi.enter = false;
new_skill = skill_level;
return;
}
} while (!_stub->_pi.quit);
new_skill = 1;
}
bool Menu::handlePasswordScreen(uint8_t &new_skill, uint8_t &new_level) {
debug(DBG_MENU, "Menu::handlePasswordScreen()");
_vid->fadeOut();
_vid->_charShadowColor = _charVar1;
_vid->_charTransparentColor = 0xFF;
_vid->_charFrontColor = _charVar4;
_vid->fullRefresh();
char password[7];
int len = 0;
do {
loadPicture("menu2");
drawString2(_res->getMenuString(LocaleData::LI_16_ENTER_PASSWORD1), 15, 3);
drawString2(_res->getMenuString(LocaleData::LI_17_ENTER_PASSWORD2), 17, 3);
for (int i = 0; i < len; ++i) {
_vid->PC_drawChar((uint8_t)password[i], 21, i + 15);
}
_vid->PC_drawChar(0x20, 21, len + 15);
_vid->markBlockAsDirty(15 * 8, 21 * 8, (len + 1) * 8, 8);
_vid->updateScreen();
_stub->sleep(EVENTS_DELAY);
_stub->processEvents();
char c = _stub->_pi.lastChar;
if (c != 0) {
_stub->_pi.lastChar = 0;
if (len < 6) {
if (c >= 'a' && c <= 'z') {
c &= ~0x20;
}
if ((c >= 'A' && c <= 'Z') || (c == 0x20)) {
password[len] = c;
++len;
}
}
}
if (_stub->_pi.backspace) {
_stub->_pi.backspace = false;
if (len > 0) {
--len;
}
}
if (_stub->_pi.enter) {
_stub->_pi.enter = false;
password[len] = '\0';
for (int level = 0; level < 8; ++level) {
for (int skill = 0; skill < 3; ++skill) {
if (strcmp(_passwords[level][skill], password) == 0) {
new_level = level;
new_skill = skill;
return true;
}
}
}
return false;
}
} while (!_stub->_pi.quit);
return false;
}
bool Menu::handleLevelScreen(uint8_t &new_skill, uint8_t &new_level) {
debug(DBG_MENU, "Menu::handleLevelScreen()");
_vid->fadeOut();
loadPicture("menu2");
_vid->fullRefresh();
uint8_t currentSkill = new_skill;
uint8_t currentLevel = new_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);
}
_vid->markBlockAsDirty(4 * 8, 7 * 8, 192, 7 * 8);
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 * 8, 23 * 8, 192, 8);
_vid->updateScreen();
_stub->sleep(EVENTS_DELAY);
_stub->processEvents();
if (_stub->_pi.dirMask & PlayerInput::DIR_UP) {
_stub->_pi.dirMask &= ~PlayerInput::DIR_UP;
if (currentLevel != 0) {
--currentLevel;
} else {
currentLevel = 6;
}
}
if (_stub->_pi.dirMask & PlayerInput::DIR_DOWN) {
_stub->_pi.dirMask &= ~PlayerInput::DIR_DOWN;
if (currentLevel != 6) {
++currentLevel;
} else {
currentLevel = 0;
}
}
if (_stub->_pi.dirMask & PlayerInput::DIR_LEFT) {
_stub->_pi.dirMask &= ~PlayerInput::DIR_LEFT;
if (currentSkill != 0) {
--currentSkill;
} else {
currentSkill = 2;
}
}
if (_stub->_pi.dirMask & PlayerInput::DIR_RIGHT) {
_stub->_pi.dirMask &= ~PlayerInput::DIR_RIGHT;
if (currentSkill != 2) {
++currentSkill;
} else {
currentSkill = 0;
}
}
if (_stub->_pi.enter) {
_stub->_pi.enter = false;
new_skill = currentSkill;
new_level = currentLevel;
return true;
}
} while (!_stub->_pi.quit);
return false;
}
bool Menu::handleTitleScreen(uint8_t &new_skill, uint8_t &new_level) {
debug(DBG_MENU, "Menu::handleTitleScreen()");
bool quit_loop = false;
int menu_entry = 0;
bool reinit_screen = true;
bool continue_game = true;
_charVar1 = 0;
_charVar2 = 0;
_charVar3 = 0;
_charVar4 = 0;
_charVar5 = 0;
static const struct {
int str;
int opt;
} menu_items[] = {
{ LocaleData::LI_07_START, MENU_OPTION_ITEM_START },
#ifdef ENABLE_PASSWORD_MENU
{ LocaleData::LI_08_SKILL, MENU_OPTION_ITEM_SKILL },
{ LocaleData::LI_09_PASSWORD, MENU_OPTION_ITEM_PASSWORD },
#else
{ LocaleData::LI_06_LEVEL, MENU_OPTION_ITEM_LEVEL },
#endif
{ LocaleData::LI_10_INFO, MENU_OPTION_ITEM_INFO },
{ LocaleData::LI_11_QUIT, MENU_OPTION_ITEM_QUIT }
};
static const int menu_items_count = ARRAYSIZE(menu_items);
while (!quit_loop) {
if (reinit_screen) {
_vid->fadeOut();
loadPicture("menu1");
_vid->fullRefresh();
_charVar3 = 1;
_charVar4 = 2;
menu_entry = 0;
reinit_screen = false;
}
int selected_menu_entry = -1;
const int y_start = 26 - menu_items_count * 2;
for (int i = 0; i < menu_items_count; ++i) {
drawString(_res->getMenuString(menu_items[i].str), y_start + i * 2, 20, (i == menu_entry) ? 2 : 3);
}
_vid->updateScreen();
_stub->sleep(EVENTS_DELAY);
_stub->processEvents();
if (_stub->_pi.dirMask & PlayerInput::DIR_UP) {
_stub->_pi.dirMask &= ~PlayerInput::DIR_UP;
if (menu_entry != 0) {
--menu_entry;
} else {
menu_entry = menu_items_count - 1;
}
}
if (_stub->_pi.dirMask & PlayerInput::DIR_DOWN) {
_stub->_pi.dirMask &= ~PlayerInput::DIR_DOWN;
if (menu_entry != menu_items_count - 1) {
++menu_entry;
} else {
menu_entry = 0;
}
}
if (_stub->_pi.enter) {
_stub->_pi.enter = false;
selected_menu_entry = menu_entry;
}
if (selected_menu_entry != -1) {
switch (menu_items[selected_menu_entry].opt) {
case MENU_OPTION_ITEM_START:
quit_loop = true;
break;
case MENU_OPTION_ITEM_SKILL:
handleSkillScreen(new_skill);
reinit_screen = true;
break;
case MENU_OPTION_ITEM_PASSWORD:
if (handlePasswordScreen(new_skill, new_level)) {
quit_loop = true;
} else {
reinit_screen = true;
}
break;
case MENU_OPTION_ITEM_LEVEL:
if (handleLevelScreen(new_skill, new_level)) {
quit_loop = true;
} else {
reinit_screen = true;
}
break;
case MENU_OPTION_ITEM_INFO:
handleInfoScreen();
reinit_screen = true;
break;
case MENU_OPTION_ITEM_QUIT:
continue_game = false;
quit_loop = true;
break;
}
}
if (_stub->_pi.quit) {
continue_game = false;
quit_loop = true;
break;
}
}
return continue_game;
}

66
menu.h Normal file
View File

@ -0,0 +1,66 @@
/* REminiscence - Flashback interpreter
* Copyright (C) 2005-2015 Gregory Montoir
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MENU_H__
#define MENU_H__
#include "intern.h"
struct Resource;
struct SystemStub;
struct Video;
struct Menu {
enum {
MENU_OPTION_ITEM_START,
MENU_OPTION_ITEM_SKILL,
MENU_OPTION_ITEM_PASSWORD,
MENU_OPTION_ITEM_LEVEL,
MENU_OPTION_ITEM_INFO,
MENU_OPTION_ITEM_QUIT
};
enum {
EVENTS_DELAY = 80
};
static const char *_passwords[8][3];
Resource *_res;
SystemStub *_stub;
Video *_vid;
const char **_textOptions;
uint8_t _charVar1;
uint8_t _charVar2;
uint8_t _charVar3;
uint8_t _charVar4;
uint8_t _charVar5;
Menu(Resource *res, SystemStub *stub, Video *vid);
void drawString(const char *str, int16_t y, int16_t x, uint8_t color);
void drawString2(const char *str, int16_t y, int16_t x);
void loadPicture(const char *prefix);
void handleInfoScreen();
void handleSkillScreen(uint8_t &new_skill);
bool handlePasswordScreen(uint8_t &new_skill, uint8_t &new_level);
bool handleLevelScreen(uint8_t &new_skill, uint8_t &new_level);
bool handleTitleScreen(uint8_t &new_skill, uint8_t &new_level);
};
#endif // MENU_H__

183
mixer.cpp Normal file
View File

@ -0,0 +1,183 @@
/* REminiscence - Flashback interpreter
* Copyright (C) 2005-2015 Gregory Montoir
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "mixer.h"
#include "systemstub.h"
Mixer::Mixer(FileSystem *fs, SystemStub *stub)
: _stub(stub), _musicType(MT_NONE), _mod(this, fs), _ogg(this, fs), _sfx(this) {
_musicTrack = -1;
}
void Mixer::init() {
memset(_channels, 0, sizeof(_channels));
_premixHook = 0;
_stub->startAudio(Mixer::mixCallback, this);
}
void Mixer::free() {
setPremixHook(0, 0);
stopAll();
_stub->stopAudio();
}
void Mixer::setPremixHook(PremixHook premixHook, void *userData) {
debug(DBG_SND, "Mixer::setPremixHook()");
LockAudioStack las(_stub);
_premixHook = premixHook;
_premixHookData = userData;
}
void Mixer::play(const MixerChunk *mc, uint16_t freq, uint8_t volume) {
debug(DBG_SND, "Mixer::play(%d, %d)", freq, volume);
LockAudioStack las(_stub);
MixerChannel *ch = 0;
for (int i = 0; i < NUM_CHANNELS; ++i) {
MixerChannel *cur = &_channels[i];
if (cur->active) {
if (cur->chunk.data == mc->data) {
cur->chunkPos = 0;
return;
}
} else {
ch = cur;
break;
}
}
if (ch) {
ch->active = true;
ch->volume = volume;
ch->chunk = *mc;
ch->chunkPos = 0;
ch->chunkInc = (freq << FRAC_BITS) / _stub->getOutputSampleRate();
}
}
bool Mixer::isPlaying(const MixerChunk *mc) const {
debug(DBG_SND, "Mixer::isPlaying");
LockAudioStack las(_stub);
for (int i = 0; i < NUM_CHANNELS; ++i) {
const MixerChannel *ch = &_channels[i];
if (ch->active && ch->chunk.data == mc->data) {
return true;
}
}
return false;
}
uint32_t Mixer::getSampleRate() const {
return _stub->getOutputSampleRate();
}
void Mixer::stopAll() {
debug(DBG_SND, "Mixer::stopAll()");
LockAudioStack las(_stub);
for (uint8_t i = 0; i < NUM_CHANNELS; ++i) {
_channels[i].active = false;
}
}
static bool isMusicSfx(int num) {
return (num >= 68 && num <= 75);
}
void Mixer::playMusic(int num) {
debug(DBG_SND, "Mixer::playMusic(%d)", num);
if (num > MUSIC_TRACK && num != _musicTrack) {
if (_ogg.playTrack(num - MUSIC_TRACK)) {
_musicType = MT_OGG;
_musicTrack = num;
return;
}
}
if (num == 1) { // menu screen
if (_ogg.playTrack(2)) {
_musicType = MT_OGG;
_musicTrack = 2;
return;
}
}
if (isMusicSfx(num)) { // level action sequence
_sfx.play(num);
_musicType = MT_SFX;
} else { // cutscene
_mod.play(num);
_musicType = MT_MOD;
}
}
void Mixer::stopMusic() {
debug(DBG_SND, "Mixer::stopMusic()");
switch (_musicType) {
case MT_NONE:
break;
case MT_MOD:
_mod.stop();
break;
case MT_OGG:
_ogg.stopTrack();
_musicTrack = -1;
break;
case MT_SFX:
_sfx.stop();
break;
}
_musicType = MT_NONE;
if (_musicTrack != -1) {
_ogg.resumeTrack();
_musicType = MT_OGG;
}
}
void Mixer::mix(int8_t *buf, int len) {
memset(buf, 0, len);
if (_premixHook) {
if (!_premixHook(_premixHookData, buf, len)) {
_premixHook = 0;
_premixHookData = 0;
}
}
for (uint8_t i = 0; i < NUM_CHANNELS; ++i) {
MixerChannel *ch = &_channels[i];
if (ch->active) {
for (int pos = 0; pos < len; ++pos) {
if ((ch->chunkPos >> FRAC_BITS) >= (ch->chunk.len - 1)) {
ch->active = false;
break;
}
int out = resampleLinear(&ch->chunk, ch->chunkPos, ch->chunkInc, FRAC_BITS);
addclamp(buf[pos], out * ch->volume / Mixer::MAX_VOLUME);
ch->chunkPos += ch->chunkInc;
}
}
}
}
void Mixer::addclamp(int8_t& a, int b) {
int add = a + b;
if (add < -128) {
add = -128;
} else if (add > 127) {
add = 127;
}
a = add;
}
void Mixer::mixCallback(void *param, int8_t *buf, int len) {
((Mixer *)param)->mix(buf, len);
}

108
mixer.h Normal file
View File

@ -0,0 +1,108 @@
/* REminiscence - Flashback interpreter
* Copyright (C) 2005-2015 Gregory Montoir
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MIXER_H__
#define MIXER_H__
#include "intern.h"
#include "mod_player.h"
#include "ogg_player.h"
#include "sfx_player.h"
struct MixerChunk {
uint8_t *data;
uint32_t len;
MixerChunk()
: data(0), len(0) {
}
int8_t getPCM(int offset) const {
if (offset < 0) {
offset = 0;
} else if (offset >= (int)len) {
offset = len - 1;
}
return (int8_t)data[offset];
}
};
struct MixerChannel {
uint8_t active;
uint8_t volume;
MixerChunk chunk;
uint32_t chunkPos;
uint32_t chunkInc;
};
struct FileSystem;
struct SystemStub;
struct Mixer {
typedef bool (*PremixHook)(void *userData, int8_t *buf, int len);
enum MusicType {
MT_NONE,
MT_MOD,
MT_OGG,
MT_SFX,
};
enum {
MUSIC_TRACK = 1000,
NUM_CHANNELS = 4,
FRAC_BITS = 12,
MAX_VOLUME = 64
};
FileSystem *_fs;
SystemStub *_stub;
MixerChannel _channels[NUM_CHANNELS];
PremixHook _premixHook;
void *_premixHookData;
MusicType _musicType;
ModPlayer _mod;
OggPlayer _ogg;
SfxPlayer _sfx;
int _musicTrack;
Mixer(FileSystem *fs, SystemStub *stub);
void init();
void free();
void setPremixHook(PremixHook premixHook, void *userData);
void play(const MixerChunk *mc, uint16_t freq, uint8_t volume);
bool isPlaying(const MixerChunk *mc) const;
uint32_t getSampleRate() const;
void stopAll();
void playMusic(int num);
void stopMusic();
void mix(int8_t *buf, int len);
static void addclamp(int8_t &a, int b);
static void mixCallback(void *param, int8_t *buf, int len);
};
template <class T>
int resampleLinear(T *sample, int pos, int step, int fracBits) {
const int inputPos = pos >> fracBits;
const int inputFrac = pos & ((1 << fracBits) - 1);
int out = sample->getPCM(inputPos);
out += (sample->getPCM(inputPos + 1) - out) * inputFrac >> fracBits;
return out;
}
#endif // MIXER_H__

522
mod_player.cpp Normal file
View File

@ -0,0 +1,522 @@
/* REminiscence - Flashback interpreter
* Copyright (C) 2005-2015 Gregory Montoir
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "file.h"
#include "mixer.h"
#include "mod_player.h"
ModPlayer::ModPlayer(Mixer *mixer, FileSystem *fs)
: _playing(false), _mix(mixer), _fs(fs) {
memset(&_modInfo, 0, sizeof(_modInfo));
}
uint16_t ModPlayer::findPeriod(uint16_t period, uint8_t fineTune) const {
for (int p = 0; p < 36; ++p) {
if (_periodTable[p] == period) {
return fineTune * 36 + p;
}
}
error("Invalid period=%d", period);
return 0;
}
void ModPlayer::load(File *f) {
f->read(_modInfo.songName, 20);
_modInfo.songName[20] = 0;
debug(DBG_MOD, "songName = '%s'", _modInfo.songName);
for (int s = 0; s < NUM_SAMPLES; ++s) {
SampleInfo *si = &_modInfo.samples[s];
f->read(si->name, 22);
si->name[22] = 0;
si->len = f->readUint16BE() * 2;
si->fineTune = f->readByte();
si->volume = f->readByte();
si->repeatPos = f->readUint16BE() * 2;
si->repeatLen = f->readUint16BE() * 2;
si->data = 0;
assert(si->len == 0 || si->repeatPos + si->repeatLen <= si->len);
debug(DBG_MOD, "sample=%d name='%s' len=%d vol=%d", s, si->name, si->len, si->volume);
}
_modInfo.numPatterns = f->readByte();
assert(_modInfo.numPatterns < NUM_PATTERNS);
f->readByte(); // 0x7F
f->read(_modInfo.patternOrderTable, NUM_PATTERNS);
f->readUint32BE(); // 'M.K.', Protracker, 4 channels
uint16_t n = 0;
for (int i = 0; i < NUM_PATTERNS; ++i) {
if (_modInfo.patternOrderTable[i] != 0) {
n = MAX(n, _modInfo.patternOrderTable[i]);
}
}
debug(DBG_MOD, "numPatterns=%d",n + 1);
n = (n + 1) * 64 * 4 * 4; // 64 lines of 4 notes per channel
_modInfo.patternsTable = (uint8_t *)malloc(n);
assert(_modInfo.patternsTable);
f->read(_modInfo.patternsTable, n);
for (int s = 0; s < NUM_SAMPLES; ++s) {
SampleInfo *si = &_modInfo.samples[s];
if (si->len != 0) {
si->data = (int8_t *)malloc(si->len);
if (si->data) {
f->read(si->data, si->len);
}
}
}
}
void ModPlayer::unload() {
if (_modInfo.songName[0]) {
free(_modInfo.patternsTable);
for (int s = 0; s < NUM_SAMPLES; ++s) {
free(_modInfo.samples[s].data);
}
memset(&_modInfo, 0, sizeof(_modInfo));
}
}
void ModPlayer::play(uint8_t num) {
if (!_playing && num < _modulesFilesCount) {
File f;
bool found = false;
for (uint8_t i = 0; i < ARRAYSIZE(_modulesFiles[num]); ++i) {
if (f.open(_modulesFiles[num][i], "rb", _fs)) {
found = true;
break;
}
}
if (!found) {
warning("Can't find music file %d", num);
} else {
load(&f);
_currentPatternOrder = 0;
_currentPatternPos = 0;
_currentTick = 0;
_patternDelay = 0;
_songSpeed = 6;
_songTempo = 125;
_patternLoopPos = 0;
_patternLoopCount = -1;
_samplesLeft = 0;
_songNum = num;
_introSongHack = false;
memset(_tracks, 0, sizeof(_tracks));
_mix->setPremixHook(mixCallback, this);
_playing = true;
}
}
}
void ModPlayer::stop() {
if (_playing) {
_mix->setPremixHook(0, 0);
_playing = false;
}
unload();
}
void ModPlayer::handleNote(int trackNum, uint32_t noteData) {
Track *tk = &_tracks[trackNum];
uint16_t sampleNum = ((noteData >> 24) & 0xF0) | ((noteData >> 12) & 0xF);
uint16_t samplePeriod = (noteData >> 16) & 0xFFF;
uint16_t effectData = noteData & 0xFFF;
debug(DBG_MOD, "ModPlayer::handleNote(%d) p=%d/%d sampleNumber=0x%X samplePeriod=0x%X effectData=0x%X tk->period=%d", trackNum, _currentPatternPos, _currentPatternOrder, sampleNum, samplePeriod, effectData, tk->period);
if (sampleNum != 0) {
tk->sample = &_modInfo.samples[sampleNum - 1];
tk->volume = tk->sample->volume;
tk->pos = 0;
}
if (samplePeriod != 0) {
tk->periodIndex = findPeriod(samplePeriod, tk->sample->fineTune);
if ((effectData >> 8) != 0x3 && (effectData >> 8) != 0x5) {
tk->period = _periodTable[tk->periodIndex];
tk->freq = PAULA_FREQ / tk->period;
} else {
tk->portamento = _periodTable[tk->periodIndex];
}
tk->vibratoAmp = 0;
tk->vibratoSpeed = 0;
tk->vibratoPos = 0;
}
tk->effectData = effectData;
}
void ModPlayer::applyVolumeSlide(int trackNum, int amount) {
debug(DBG_MOD, "ModPlayer::applyVolumeSlide(%d, %d)", trackNum, amount);
Track *tk = &_tracks[trackNum];
int vol = tk->volume + amount;
if (vol < 0) {
vol = 0;
} else if (vol > 64) {
vol = 64;
}
tk->volume = vol;
}
void ModPlayer::applyVibrato(int trackNum) {
debug(DBG_MOD, "ModPlayer::applyVibrato(%d)", trackNum);
Track *tk = &_tracks[trackNum];
int vib = tk->vibratoAmp * _sineWaveTable[tk->vibratoPos] / 128;
if (tk->period + vib != 0) {
tk->freq = PAULA_FREQ / (tk->period + vib);
}
tk->vibratoPos += tk->vibratoSpeed;
if (tk->vibratoPos >= 64) {
tk->vibratoPos = 0;
}
}
void ModPlayer::applyPortamento(int trackNum) {
debug(DBG_MOD, "ModPlayer::applyPortamento(%d)", trackNum);
Track *tk = &_tracks[trackNum];
if (tk->period < tk->portamento) {
tk->period = MIN(tk->period + tk->portamentoSpeed, tk->portamento);
} else if (tk->period > tk->portamento) {
tk->period = MAX(tk->period - tk->portamentoSpeed, tk->portamento);
}
if (tk->period != 0) {
tk->freq = PAULA_FREQ / tk->period;
}
}
void ModPlayer::handleEffect(int trackNum, bool tick) {
Track *tk = &_tracks[trackNum];
uint8_t effectNum = tk->effectData >> 8;
uint8_t effectXY = tk->effectData & 0xFF;
uint8_t effectX = effectXY >> 4;
uint8_t effectY = effectXY & 0xF;
debug(DBG_MOD, "ModPlayer::handleEffect(%d) effectNum=0x%X effectXY=0x%X", trackNum, effectNum, effectXY);
switch (effectNum) {
case 0x0: // arpeggio
if (tick && effectXY != 0) {
uint16_t period = tk->period;
switch (_currentTick & 3) {
case 1:
period = _periodTable[tk->periodIndex + effectX];
break;
case 2:
period = _periodTable[tk->periodIndex + effectY];
break;
}
tk->freq = PAULA_FREQ / period;
}
break;
case 0x1: // portamento up
if (tick) {
tk->period -= effectXY;
if (tk->period < 113) { // note B-3
tk->period = 113;
}
tk->freq = PAULA_FREQ / tk->period;
}
break;
case 0x2: // portamento down
if (tick) {
tk->period += effectXY;
if (tk->period > 856) { // note C-1
tk->period = 856;
}
tk->freq = PAULA_FREQ / tk->period;
}
break;
case 0x3: // tone portamento
if (!tick) {
if (effectXY != 0) {
tk->portamentoSpeed = effectXY;
}
} else {
applyPortamento(trackNum);
}
break;
case 0x4: // vibrato
if (!tick) {
if (effectX != 0) {
tk->vibratoSpeed = effectX;
}
if (effectY != 0) {
tk->vibratoAmp = effectY;
}
} else {
applyVibrato(trackNum);
}
break;
case 0x5: // tone portamento + volume slide
if (tick) {
applyPortamento(trackNum);
applyVolumeSlide(trackNum, effectX - effectY);
}
break;
case 0x6: // vibrato + volume slide
if (tick) {
applyVibrato(trackNum);
applyVolumeSlide(trackNum, effectX - effectY);
}
break;
case 0x9: // set sample offset
if (!tick) {
tk->pos = effectXY << (8 + FRAC_BITS);
}
break;
case 0xA: // volume slide
if (tick) {
applyVolumeSlide(trackNum, effectX - effectY);
}
break;
case 0xB: // position jump
if (!tick) {
_currentPatternOrder = effectXY;
_currentPatternPos = 0;
assert(_currentPatternOrder < _modInfo.numPatterns);
}
break;
case 0xC: // set volume
if (!tick) {
assert(effectXY <= 64);
tk->volume = effectXY;
}
break;
case 0xD: // pattern break
if (!tick) {
_currentPatternPos = effectX * 10 + effectY;
assert(_currentPatternPos < 64);
++_currentPatternOrder;
debug(DBG_MOD, "_currentPatternPos = %d _currentPatternOrder = %d", _currentPatternPos, _currentPatternOrder);
}
break;
case 0xE: // extended effects
switch (effectX) {
case 0x0: // set filter, ignored
break;
case 0x1: // fineslide up
if (!tick) {
tk->period -= effectY;
if (tk->period < 113) { // B-3 note
tk->period = 113;
}
tk->freq = PAULA_FREQ / tk->period;
}
break;
case 0x2: // fineslide down
if (!tick) {
tk->period += effectY;
if (tk->period > 856) { // C-1 note
tk->period = 856;
}
tk->freq = PAULA_FREQ / tk->period;
}
break;
case 0x6: // loop pattern
if (!tick) {
if (effectY == 0) {
_patternLoopPos = _currentPatternPos | (_currentPatternOrder << 8);
debug(DBG_MOD, "_patternLoopPos=%d/%d", _currentPatternPos, _currentPatternOrder);
} else {
if (_patternLoopCount == -1) {
_patternLoopCount = effectY;
_currentPatternPos = _patternLoopPos & 0xFF;
_currentPatternOrder = _patternLoopPos >> 8;
} else {
--_patternLoopCount;
if (_patternLoopCount != 0) {
_currentPatternPos = _patternLoopPos & 0xFF;
_currentPatternOrder = _patternLoopPos >> 8;
} else {
_patternLoopCount = -1;
}
}
debug(DBG_MOD, "_patternLoopCount=%d", _patternLoopCount);
}
}
break;
case 0x9: // retrigger sample
if (tick) {
tk->retriggerCounter = effectY;
} else {
if (tk->retriggerCounter == 0) {
tk->pos = 0;
tk->retriggerCounter = effectY;
debug(DBG_MOD, "retrigger sample=%d _songSpeed=%d", effectY, _songSpeed);
}
--tk->retriggerCounter;
}
break;
case 0xA: // fine volume slide up
if (!tick) {
applyVolumeSlide(trackNum, effectY);
}
break;
case 0xB: // fine volume slide down
if (!tick) {
applyVolumeSlide(trackNum, -effectY);
}
break;
case 0xC: // cut sample
if (!tick) {
tk->cutCounter = effectY;
} else {
--tk->cutCounter;
if (tk->cutCounter == 0) {
tk->volume = 0;
}
}
case 0xD: // delay sample
if (!tick) {
tk->delayCounter = effectY;
} else {
if (tk->delayCounter != 0) {
--tk->delayCounter;
}
}
break;
case 0xE: // delay pattern
if (!tick) {
debug(DBG_MOD, "ModPlayer::handleEffect() _currentTick=%d delay pattern=%d", _currentTick, effectY);
_patternDelay = effectY;
}
break;
default:
warning("Unhandled extended effect 0x%X params=0x%X", effectX, effectY);
break;
}
break;
case 0xF: // set speed
if (!tick) {
if (effectXY < 0x20) {
_songSpeed = effectXY;
} else {
_songTempo = effectXY;
}
}
break;
default:
warning("Unhandled effect 0x%X params=0x%X", effectNum, effectXY);
break;
}
}
void ModPlayer::handleTick() {
if (!_playing) {
return;
}
// if (_patternDelay != 0) {
// --_patternDelay;
// return;
// }
if (_currentTick == 0) {
debug(DBG_MOD, "_currentPatternOrder=%d _currentPatternPos=%d", _currentPatternOrder, _currentPatternPos);
uint8_t currentPattern = _modInfo.patternOrderTable[_currentPatternOrder];
const uint8_t *p = _modInfo.patternsTable + (currentPattern * 64 + _currentPatternPos) * 16;
for (int i = 0; i < NUM_TRACKS; ++i) {
uint32_t noteData = READ_BE_UINT32(p);
handleNote(i, noteData);
p += 4;
}
++_currentPatternPos;
if (_currentPatternPos == 64) {
++_currentPatternOrder;
_currentPatternPos = 0;
debug(DBG_MOD, "ModPlayer::handleTick() _currentPatternOrder = %d/%d", _currentPatternOrder, _modInfo.numPatterns);
// On the amiga version, the introduction cutscene is shorter than the PC version ;
// so the music module doesn't synchronize at all with the PC datafiles, here we
// add a hack to let the music play longer
if (_songNum == 0 && _currentPatternOrder == 3 && !_introSongHack) {
_currentPatternOrder = 1;
_introSongHack = true;
// warning("Introduction module synchronization hack");
}
}
}
for (int i = 0; i < NUM_TRACKS; ++i) {
handleEffect(i, (_currentTick != 0));
}
++_currentTick;
if (_currentTick == _songSpeed) {
_currentTick = 0;
}
if (_currentPatternOrder == _modInfo.numPatterns) {
debug(DBG_MOD, "ModPlayer::handleEffect() _currentPatternOrder == _modInfo.numPatterns");
_playing = false;
}
}
void ModPlayer::mixSamples(int8_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;
SampleInfo *si = tk->sample;
int len = si->len << FRAC_BITS;
int loopLen = si->repeatLen << FRAC_BITS;
int loopPos = si->repeatPos << FRAC_BITS;
int deltaPos = (tk->freq << FRAC_BITS) / _mix->getSampleRate();
int curLen = samplesLen;
int pos = tk->pos;
while (curLen != 0) {
int count;
if (loopLen > (2 << FRAC_BITS)) {
if (pos >= loopPos + loopLen) {
pos -= loopLen;
}
count = MIN(curLen, (loopPos + loopLen - pos - 1) / deltaPos + 1);
curLen -= count;
} else {
if (pos >= len) {
count = 0;
} else {
count = MIN(curLen, (len - pos - 1) / deltaPos + 1);
}
curLen = 0;
}
while (count--) {
int out = resampleLinear(si, pos, deltaPos, FRAC_BITS);
Mixer::addclamp(*mixbuf++, out * tk->volume / 64);
pos += deltaPos;
}
}
tk->pos = pos;
}
}
}
bool ModPlayer::mix(int8_t *buf, int len) {
if (_playing) {
memset(buf, 0, len);
const int samplesPerTick = _mix->getSampleRate() / (50 * _songTempo / 125);
while (len != 0) {
if (_samplesLeft == 0) {
handleTick();
_samplesLeft = samplesPerTick;
}
int count = _samplesLeft;
if (count > len) {
count = len;
}
_samplesLeft -= count;
len -= count;
mixSamples(buf, count);
buf += count;
}
}
return _playing;
}
bool ModPlayer::mixCallback(void *param, int8_t *buf, int len) {
return ((ModPlayer *)param)->mix(buf, len);
}

122
mod_player.h Normal file
View File

@ -0,0 +1,122 @@
/* REminiscence - Flashback interpreter
* Copyright (C) 2005-2015 Gregory Montoir
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MOD_PLAYER_H__
#define MOD_PLAYER_H__
#include "intern.h"
struct File;
struct FileSystem;
struct Mixer;
struct ModPlayer {
enum {
NUM_SAMPLES = 31,
NUM_TRACKS = 4,
NUM_PATTERNS = 128,
FRAC_BITS = 12,
PAULA_FREQ = 3546897
};
struct SampleInfo {
char name[23];
uint16_t len;
uint8_t fineTune;
uint8_t volume;
uint16_t repeatPos;
uint16_t repeatLen;
int8_t *data;
int8_t getPCM(int offset) const {
if (offset < 0) {
offset = 0;
} else if (offset >= (int)len) {
offset = len - 1;
}
return data[offset];
}
};
struct ModuleInfo {
char songName[21];
SampleInfo samples[NUM_SAMPLES];
uint8_t numPatterns;
uint8_t patternOrderTable[NUM_PATTERNS];
uint8_t *patternsTable;
};
struct Track {
SampleInfo *sample;
uint8_t volume;
int pos;
int freq;
uint16_t period;
uint16_t periodIndex;
uint16_t effectData;
int vibratoSpeed;
int vibratoAmp;
int vibratoPos;
int portamento;
int portamentoSpeed;
int retriggerCounter;
int delayCounter;
int cutCounter;
};
static const int8_t _sineWaveTable[];
static const uint16_t _periodTable[];
static const char *_modulesFiles[][2];
static const int _modulesFilesCount;
ModuleInfo _modInfo;
uint8_t _currentPatternOrder;
uint8_t _currentPatternPos;
uint8_t _currentTick;
uint8_t _songSpeed;
uint8_t _songTempo;
int _patternDelay;
int _patternLoopPos;
int _patternLoopCount;
int _samplesLeft;
uint8_t _songNum;
bool _introSongHack;
bool _playing;
Track _tracks[NUM_TRACKS];
Mixer *_mix;
FileSystem *_fs;
ModPlayer(Mixer *mixer, FileSystem *fs);
uint16_t findPeriod(uint16_t period, uint8_t fineTune) const;
void load(File *f);
void unload();
void play(uint8_t num);
void stop();
void handleNote(int trackNum, uint32_t noteData);
void handleTick();
void applyVolumeSlide(int trackNum, int amount);
void applyVibrato(int trackNum);
void applyPortamento(int trackNum);
void handleEffect(int trackNum, bool tick);
void mixSamples(int8_t *buf, int len);
bool mix(int8_t *buf, int len);
static bool mixCallback(void *param, int8_t *buf, int len);
};
#endif // MOD_PLAYER_H__

214
ogg_player.cpp Normal file
View File

@ -0,0 +1,214 @@
/* REminiscence - Flashback interpreter
* Copyright (C) 2005-2015 Gregory Montoir
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef USE_TREMOR
#include <tremor/ivorbisfile.h>
#endif
#include "file.h"
#include "mixer.h"
#include "ogg_player.h"
#ifdef USE_TREMOR
struct VorbisFile: File {
uint32_t offset;
static size_t readHelper(void *ptr, size_t size, size_t nmemb, void *datasource) {
VorbisFile *vf = (VorbisFile *)datasource;
if (size != 0 && nmemb != 0) {
const int n = vf->read(ptr, size * nmemb);
if (n > 0) {
vf->offset += n;
return n / size;
}
}
return 0;
}
static int seekHelper(void *datasource, ogg_int64_t offset, int whence) {
VorbisFile *vf = (VorbisFile *)datasource;
switch (whence) {
case SEEK_SET:
vf->offset = offset;
break;
case SEEK_CUR:
vf->offset += offset;
break;
case SEEK_END:
vf->offset = vf->size() + offset;
break;
}
vf->seek(vf->offset);
return 0;
}
static int closeHelper(void *datasource) {
VorbisFile *vf = (VorbisFile *)datasource;
vf->close();
delete vf;
return 0;
}
static long tellHelper(void *datasource) {
VorbisFile *vf = (VorbisFile *)datasource;
return vf->offset;
}
};
struct OggDecoder_impl {
OggDecoder_impl()
: _open(false), _readBuf(0), _readBufSize(0) {
}
~OggDecoder_impl() {
free(_readBuf);
_readBuf = 0;
if (_open) {
ov_clear(&_ovf);
}
}
bool load(VorbisFile *f, int mixerSampleRate) {
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) {
warning("Invalid .ogg file");
return false;
}
_open = true;
vorbis_info *vi = ov_info(&_ovf, -1);
if ((vi->channels != 1 && vi->channels != 2) || vi->rate != mixerSampleRate) {
warning("Unhandled ogg/pcm format ch %d rate %d", vi->channels, vi->rate);
return false;
}
_channels = vi->channels;
return true;
}
int read(int8_t *dst, int samples) {
int size = samples * _channels * sizeof(int16_t);
if (size > _readBufSize) {
_readBufSize = size;
free(_readBuf);
_readBuf = (int16_t *)malloc(_readBufSize);
if (!_readBuf) {
return 0;
}
}
int count = 0;
while (size > 0) {
const int len = ov_read(&_ovf, (char *)_readBuf, size, 0);
if (len < 0) {
// error in decoder
return 0;
} else if (len == 0) {
// loop
ov_raw_seek(&_ovf, 0);
continue;
}
switch (_channels) {
case 2:
assert((len & 1) == 0);
for (int i = 0; i < len / 2; i += 2) {
Mixer::addclamp(*dst++, ((_readBuf[i] + _readBuf[i + 1]) / 2) >> 8);
}
break;
case 1:
for (int i = 0; i < len / 2; ++i) {
Mixer::addclamp(*dst++, _readBuf[i] >> 8);
}
break;
}
size -= len;
count += len;
}
assert(size == 0);
return count;
}
OggVorbis_File _ovf;
int _channels;
bool _open;
int16_t *_readBuf;
int _readBufSize;
};
#endif
OggPlayer::OggPlayer(Mixer *mixer, FileSystem *fs)
: _mix(mixer), _fs(fs), _impl(0) {
}
OggPlayer::~OggPlayer() {
#ifdef USE_TREMOR
delete _impl;
#endif
}
bool OggPlayer::playTrack(int num) {
#ifdef USE_TREMOR
stopTrack();
char buf[16];
snprintf(buf, sizeof(buf), "track%02d.ogg", num);
VorbisFile *vf = new VorbisFile;
if (vf->open(buf, "rb", _fs)) {
vf->offset = 0;
_impl = new OggDecoder_impl();
if (_impl->load(vf, _mix->getSampleRate())) {
debug(DBG_INFO, "Playing '%s'", buf);
_mix->setPremixHook(mixCallback, this);
return true;
}
}
delete vf;
#endif
return false;
}
void OggPlayer::stopTrack() {
#ifdef USE_TREMOR
_mix->setPremixHook(0, 0);
delete _impl;
_impl = 0;
#endif
}
void OggPlayer::pauseTrack() {
#ifdef USE_TREMOR
if (_impl) {
_mix->setPremixHook(0, 0);
}
#endif
}
void OggPlayer::resumeTrack() {
#ifdef USE_TREMOR
if (_impl) {
_mix->setPremixHook(mixCallback, this);
}
#endif
}
bool OggPlayer::mix(int8_t *buf, int len) {
#ifdef USE_TREMOR
if (_impl) {
return _impl->read(buf, len) != 0;
}
#endif
return false;
}
bool OggPlayer::mixCallback(void *param, int8_t *buf, int len) {
return ((OggPlayer *)param)->mix(buf, len);
}

45
ogg_player.h Normal file
View File

@ -0,0 +1,45 @@
/* REminiscence - Flashback interpreter
* Copyright (C) 2005-2015 Gregory Montoir
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef OGG_PLAYER_H__
#define OGG_PLAYER_H__
#include "intern.h"
struct FileSystem;
struct Mixer;
struct OggDecoder_impl;
struct OggPlayer {
OggPlayer(Mixer *mixer, FileSystem *fs);
~OggPlayer();
bool playTrack(int num);
void stopTrack();
void pauseTrack();
void resumeTrack();
bool isPlaying() const { return _impl != 0; }
bool mix(int8_t *buf, int len);
static bool mixCallback(void *param, int8_t *buf, int len);
Mixer *_mix;
FileSystem *_fs;
OggDecoder_impl *_impl;
};
#endif // OGG_PLAYER_H__

2264
piege.cpp Normal file

File diff suppressed because it is too large Load Diff

1130
resource.cpp Normal file

File diff suppressed because it is too large Load Diff

214
resource.h Normal file
View File

@ -0,0 +1,214 @@
/* REminiscence - Flashback interpreter
* Copyright (C) 2005-2015 Gregory Montoir
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef RESOURCE_H__
#define RESOURCE_H__
#include "intern.h"
struct File;
struct FileSystem;
struct LocaleData {
enum Id {
LI_01_CONTINUE_OR_ABORT = 0,
LI_02_TIME,
LI_03_CONTINUE,
LI_04_ABORT,
LI_05_COMPLETED,
LI_06_LEVEL,
LI_07_START,
LI_08_SKILL,
LI_09_PASSWORD,
LI_10_INFO,
LI_11_QUIT,
LI_12_SKILL_LEVEL,
LI_13_EASY,
LI_14_NORMAL,
LI_15_EXPERT,
LI_16_ENTER_PASSWORD1,
LI_17_ENTER_PASSWORD2,
LI_18_RESUME_GAME,
LI_19_ABORT_GAME,
LI_20_LOAD_GAME,
LI_21_SAVE_GAME,
LI_22_SAVE_SLOT,
LI_NUM
};
static const char *_textsTableFR[];
static const char *_textsTableEN[];
static const char *_textsTableDE[];
static const char *_textsTableSP[];
static const char *_textsTableIT[];
static const uint8_t _stringsTableFR[];
static const uint8_t _stringsTableEN[];
static const uint8_t _stringsTableDE[];
static const uint8_t _stringsTableSP[];
static const uint8_t _stringsTableIT[];
};
struct Resource {
typedef void (Resource::*LoadStub)(File *);
enum ObjectType {
OT_MBK,
OT_PGE,
OT_PAL,
OT_CT,
OT_MAP,
OT_SPC,
OT_RP,
OT_RPC,
OT_DEMO,
OT_ANI,
OT_OBJ,
OT_TBN,
OT_SPR,
OT_TAB,
OT_ICN,
OT_FNT,
OT_TXTBIN,
OT_CMD,
OT_POL,
OT_SPRM,
OT_OFF,
OT_CMP,
OT_OBC,
OT_SPL,
OT_LEV,
OT_SGD,
OT_SPM
};
enum {
NUM_SFXS = 66,
NUM_SPRITES = 1287
};
static const uint16_t _voicesOffsetsTable[];
static const uint32_t _spmOffsetsTable[];
static const char *_splNames[];
FileSystem *_fs;
ResourceType _type;
Language _lang;
bool _hasSeqData;
char _entryName[32];
uint8_t *_fnt;
uint8_t *_mbk;
uint8_t *_icn;
int _icnLen;
uint8_t *_tab;
uint8_t *_spc; // BE
uint16_t _numSpc;
uint8_t _rp[0x4A];
uint8_t *_pal; // BE
uint8_t *_ani;
uint8_t *_tbn;
int8_t _ctData[0x1D00];
uint8_t *_spr1;
uint8_t *_sprData[NUM_SPRITES]; // 0-0x22F + 0x28E-0x2E9 ... conrad, 0x22F-0x28D : junkie
uint8_t _sprm[0x10000];
uint16_t _pgeNum;
InitPGE _pgeInit[256];
uint8_t *_map;
uint8_t *_lev;
int _levNum;
uint8_t *_sgd;
uint16_t _numObjectNodes;
ObjectNode *_objectNodesMap[255];
uint8_t *_memBuf;
SoundFx *_sfxList;
uint8_t _numSfx;
uint8_t *_cmd;
uint8_t *_pol;
uint8_t *_cine_off;
uint8_t *_cine_txt;
char **_extTextsTable;
const char **_textsTable;
uint8_t *_extStringsTable;
const uint8_t *_stringsTable;
uint8_t *_bankData;
uint8_t *_bankDataHead;
uint8_t *_bankDataTail;
BankSlot _bankBuffers[50];
int _bankBuffersCount;
Resource(FileSystem *fs, ResourceType type, Language lang);
~Resource();
void clearLevelRes();
void load_FIB(const char *fileName);
void load_SPL_demo();
void load_MAP_menu(const char *fileName, uint8_t *dstPtr);
void load_PAL_menu(const char *fileName, uint8_t *dstPtr);
void load_SPR_OFF(const char *fileName, uint8_t *sprData);
void load_CINE();
void load_TEXT();
void free_TEXT();
void load(const char *objName, int objType, const char *ext = 0);
void load_CT(File *pf);
void load_FNT(File *pf);
void load_MBK(File *pf);
void load_ICN(File *pf);
void load_SPR(File *pf);
void load_SPRM(File *pf);
void load_RP(File *pf);
void load_SPC(File *pf);
void load_PAL(File *pf);
void load_MAP(File *pf);
void load_OBJ(File *pf);
void free_OBJ();
void load_OBC(File *pf);
void decodeOBJ(const uint8_t *, int);
void load_PGE(File *pf);
void load_ANI(File *pf);
void load_TBN(File *pf);
void load_CMD(File *pf);
void load_POL(File *pf);
void load_CMP(File *pf);
void load_VCE(int num, int segment, uint8_t **buf, uint32_t *bufSize);
void load_SPL(File *pf);
void load_LEV(File *pf);
void load_SGD(File *pf);
void load_SPM(File *f);
const uint8_t *getAniData(int num) const {
const int offset = READ_LE_UINT16(_ani + num * 2);
return _ani + offset;
}
const uint8_t *getGameString(int num) {
return _stringsTable + READ_LE_UINT16(_stringsTable + num * 2);
}
const uint8_t *getCineString(int num) {
if (_cine_off) {
const int offset = READ_BE_UINT16(_cine_off + num * 2);
return _cine_txt + offset;
}
return 0;
}
const char *getMenuString(int num) {
return (num >= 0 && num < LocaleData::LI_NUM) ? _textsTable[num] : "";
}
void clearBankData();
int getBankDataSize(uint16_t num);
uint8_t *findBankData(uint16_t num);
uint8_t *loadBankData(uint16_t num);
};
#endif // RESOURCE_H__

192
scaler.cpp Normal file
View File

@ -0,0 +1,192 @@
/* REminiscence - Flashback interpreter
* Copyright (C) 2005-2015 Gregory Montoir
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "scaler.h"
static void point1x(uint16_t *dst, int dstPitch, const uint16_t *src, int srcPitch, int w, int h) {
dstPitch >>= 1;
while (h--) {
memcpy(dst, src, w * 2);
dst += dstPitch;
src += srcPitch;
}
}
static void point2x(uint16_t *dst, int dstPitch, const uint16_t *src, int srcPitch, int w, int h) {
dstPitch >>= 1;
const int dstPitch2 = dstPitch * 2;
while (h--) {
uint16_t *p = dst;
for (int i = 0; i < w; ++i, p += 2) {
const uint16_t c = *(src + i);
for (int j = 0; j < 2; ++j) {
*(p + j) = *(p + dstPitch + j) = c;
}
}
dst += dstPitch2;
src += srcPitch;
}
}
static void point3x(uint16_t *dst, int dstPitch, const uint16_t *src, int srcPitch, int w, int h) {
dstPitch >>= 1;
const int dstPitch2 = dstPitch * 2;
const int dstPitch3 = dstPitch * 3;
while (h--) {
uint16_t *p = dst;
for (int i = 0; i < w; ++i, p += 3) {
const uint16_t c = *(src + i);
for (int j = 0; j < 3; ++j) {
*(p + j) = *(p + dstPitch + j) = *(p + dstPitch2 + j) = c;
}
}
dst += dstPitch3;
src += srcPitch;
}
}
static void point4x(uint16_t *dst, int dstPitch, const uint16_t *src, int srcPitch, int w, int h) {
dstPitch >>= 1;
const int dstPitch2 = dstPitch * 2;
const int dstPitch3 = dstPitch * 3;
const int dstPitch4 = dstPitch * 4;
while (h--) {
uint16_t *p = dst;
for (int i = 0; i < w; ++i, p += 4) {
const uint16_t c = *(src + i);
for (int j = 0; j < 4; ++j) {
*(p + j) = *(p + dstPitch + j) = *(p + dstPitch2 + j) = *(p + dstPitch3 + j) = c;
}
}
dst += dstPitch4;
src += srcPitch;
}
}
static void scale2x(uint16_t *dst, int dstPitch, const uint16_t *src, int srcPitch, int w, int h) {
dstPitch >>= 1;
const int dstPitch2 = dstPitch * 2;
while (h--) {
uint16_t *p = dst;
uint16_t D = *(src - 1);
uint16_t E = *(src);
for (int i = 0; i < w; ++i, p += 2) {
uint16_t B = *(src + i - srcPitch);
uint16_t F = *(src + i + 1);
uint16_t H = *(src + i + srcPitch);
if (B != H && D != F) {
*(p) = D == B ? D : E;
*(p + 1) = B == F ? F : E;
*(p + dstPitch) = D == H ? D : E;
*(p + dstPitch + 1) = H == F ? F : E;
} else {
*(p) = E;
*(p + 1) = E;
*(p + dstPitch) = E;
*(p + dstPitch + 1) = E;
}
D = E;
E = F;
}
dst += dstPitch2;
src += srcPitch;
}
}
static void scale3x(uint16_t *dst, int dstPitch, const uint16_t *src, int srcPitch, int w, int h) {
dstPitch >>= 1;
const int dstPitch2 = dstPitch * 2;
const int dstPitch3 = dstPitch * 3;
while (h--) {
uint16_t *p = dst;
uint16_t A = *(src - srcPitch - 1);
uint16_t B = *(src - srcPitch);
uint16_t D = *(src - 1);
uint16_t E = *(src);
uint16_t G = *(src + srcPitch - 1);
uint16_t H = *(src + srcPitch);
for (int i = 0; i < w; ++i, p += 3) {
uint16_t C = *(src + i - srcPitch + 1);
uint16_t F = *(src + i + 1);
uint16_t I = *(src + i + srcPitch + 1);
if (B != H && D != F) {
*(p) = D == B ? D : E;
*(p + 1) = (D == B && E != C) || (B == F && E != A) ? B : E;
*(p + 2) = B == F ? F : E;
*(p + dstPitch) = (D == B && E != G) || (D == B && E != A) ? D : E;
*(p + dstPitch + 1) = E;
*(p + dstPitch + 2) = (B == F && E != I) || (H == F && E != C) ? F : E;
*(p + dstPitch2) = D == H ? D : E;
*(p + dstPitch2 + 1) = (D == H && E != I) || (H == F && E != G) ? H : E;
*(p + dstPitch2 + 2) = H == F ? F : E;
} else {
*(p) = E;
*(p + 1) = E;
*(p + 2) = E;
*(p + dstPitch) = E;
*(p + dstPitch + 1) = E;
*(p + dstPitch + 2) = E;
*(p + dstPitch2) = E;
*(p + dstPitch2 + 1) = E;
*(p + dstPitch2 + 2) = E;
}
A = B;
B = C;
D = E;
E = F;
G = H;
H = I;
}
dst += dstPitch3;
src += srcPitch;
}
}
void scale4x(uint16_t *dst, int dstPitch, const uint16_t *src, int srcPitch, int w, int h) {
static struct {
uint16_t *ptr;
int w, h, pitch;
int size;
} buf;
const int size = (w * 2 + 2) * (h * 2 + 2) * sizeof(uint16_t);
if (buf.size < size) {
free(buf.ptr);
buf.size = size;
buf.w = w * 2;
buf.h = h * 2;
buf.pitch = buf.w + 2;
buf.ptr = (uint16_t *)malloc(buf.size);
if (!buf.ptr) {
error("Unable to allocate scale4x intermediate buffer");
}
}
scale2x(buf.ptr + buf.pitch + 1, buf.pitch * sizeof(uint16_t), src, srcPitch, w, h);
scale2x(dst, dstPitch, buf.ptr + buf.pitch + 1, buf.pitch, w * 2, h * 2);
}
const Scaler _scalers[] = {
{ "point1x", &point1x, 1 },
{ "point2x", &point2x, 2 },
{ "scale2x", &scale2x, 2 },
{ "point3x", &point3x, 3 },
{ "scale3x", &scale3x, 3 },
{ "point4x", &point4x, 4 },
{ "scale4x", &scale4x, 4 },
{ 0, 0, 0 }
};

44
scaler.h Normal file
View File

@ -0,0 +1,44 @@
/* REminiscence - Flashback interpreter
* Copyright (C) 2005-2015 Gregory Montoir
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SCALER_H__
#define SCALER_H__
#include "intern.h"
typedef void (*ScaleProc)(uint16_t *dst, int dstPitch, const uint16_t *src, int srcPitch, int w, int h);
enum {
SCALER_POINT_1X = 0,
SCALER_POINT_2X,
SCALER_SCALE_2X,
SCALER_POINT_3X,
SCALER_SCALE_3X,
SCALER_POINT_4X,
SCALER_SCALE_4X,
NUM_SCALERS = 7
};
struct Scaler {
const char *name;
ScaleProc proc;
uint8_t factor;
};
extern const Scaler _scalers[];
#endif // SCALER_H__

367
seq_player.cpp Normal file
View File

@ -0,0 +1,367 @@
/* REminiscence - Flashback interpreter
* Copyright (C) 2005-2015 Gregory Montoir
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "file.h"
#include "fs.h"
#include "mixer.h"
#include "seq_player.h"
#include "systemstub.h"
bool SeqDemuxer::open(File *f) {
_f = f;
_fileSize = _f->size();
memset(_buffers, 0, sizeof(_buffers));
_frameOffset = 0;
return readHeader();
}
void SeqDemuxer::close() {
_f = 0;
for (int i = 0; i < kBuffersCount; ++i) {
free(_buffers[i].data);
}
}
bool SeqDemuxer::readHeader() {
for (int i = 0; i < 256; i += 4) {
if (_f->readUint32LE() != 0) {
return false;
}
}
for (int i = 0; i < kBuffersCount; ++i) {
const int size = _f->readUint16LE();
if (size != 0) {
_buffers[i].size = 0;
_buffers[i].avail = size;
_buffers[i].data = (uint8_t *)malloc(size);
if (!_buffers[i].data) {
error("Unable to allocate %d bytes for SEQ buffer %d", size, i);
}
}
}
for (int i = 1; i <= 100; ++i) {
readFrameData();
}
return true;
}
bool SeqDemuxer::readFrameData() {
_frameOffset += kFrameSize;
if (_frameOffset >= _fileSize) {
return false;
}
_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();
}
uint16_t offsets[4];
for (int i = 0; i < 4; ++i) {
offsets[i] = _f->readUint16LE();
}
for (int i = 0; i < 3; ++i) {
if (offsets[i] != 0) {
int e = i + 1;
while (e < 3 && offsets[e] == 0) {
++e;
}
fillBuffer(num[i + 1], offsets[i], offsets[e] - offsets[i]);
}
}
if (num[0] != 255) {
assert(num[0] < kBuffersCount);
_videoData = num[0];
} else {
_videoData = -1;
}
return !_f->ioErr();
}
void SeqDemuxer::fillBuffer(int num, int offset, int size) {
assert(num < kBuffersCount);
_f->seek(_frameOffset + offset);
assert(_buffers[num].size + size <= _buffers[num].avail);
_f->read(_buffers[num].data + _buffers[num].size, size);
_buffers[num].size += size;
}
void SeqDemuxer::clearBuffer(int num) {
_buffers[num].size = 0;
}
void SeqDemuxer::readPalette(uint8_t *dst) {
_f->seek(_frameOffset + _paletteDataOffset);
_f->read(dst, 256 * 3);
}
void SeqDemuxer::readAudioS8(uint8_t *dst) {
_f->seek(_frameOffset + _audioDataOffset);
for (int i = 0; i < kAudioBufferSize; ++i) {
dst[i] = _f->readUint16BE() >> 8;
}
}
struct BitStream {
BitStream(const uint8_t *src)
: _src(src) {
_bits = READ_LE_UINT16(_src); _src += 2;
_len = 16;
}
int getBits(int count) {
if (count > _len) {
_bits |= READ_LE_UINT16(_src) << _len; _src += 2;
_len += 16;
}
assert(count <= _len);
const int x = _bits & ((1 << count) - 1);
_bits >>= count;
_len -= count;
return x;
}
int getSignedBits(int count) {
const int32_t x = getBits(count);
return (x << (32 - count)) >> (32 - count);
}
const uint8_t *_src;
int _len;
uint32_t _bits;
};
static const uint8_t *decodeSeqOp1Helper(const uint8_t *src, uint8_t *dst, int dstSize) {
int codes[64], count = 0;
BitStream bs(src);
for (int i = 0, sz = 0; i < 64 && sz < 64; ++i) {
codes[i] = bs.getSignedBits(4);
sz += ABS(codes[i]);
count += 4;
}
src += (count + 7) / 8;
for (int i = 0; i < 64 && dstSize > 0; ++i) {
int len = codes[i];
if (len < 0) {
len = -len;
memset(dst, *src++, MIN(len, dstSize));
} else {
memcpy(dst, src, MIN(len, dstSize));
src += len;
}
dst += len;
dstSize -= len;
}
return src;
}
static const uint8_t *decodeSeqOp1(uint8_t *dst, int pitch, const uint8_t *src) {
const int len = *src++;
if (len & 0x80) {
uint8_t buf[8 * 8];
switch (len & 3) {
case 1:
src = decodeSeqOp1Helper(src, buf, sizeof(buf));
for (int y = 0; y < 8; ++y) {
memcpy(dst, buf + y * 8, 8);
dst += pitch;
}
break;
case 2:
src = decodeSeqOp1Helper(src, buf, sizeof(buf));
for (int i = 0; i < 8; i++) {
for (int y = 0; y < 8; ++y) {
dst[y * pitch] = buf[i * 8 + y];
}
++dst;
}
break;
}
} else {
static const uint8_t log2_16[] = { 0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3 };
BitStream bs(src + len);
assert(len <= 16);
const int bits = log2_16[len - 1] + 1;
for (int y = 0; y < 8; ++y) {
for (int x = 0; x < 8; ++x) {
dst[y * pitch + x] = src[bs.getBits(bits)];
}
}
src += len + bits * 8;
}
return src;
}
static const uint8_t *decodeSeqOp2(uint8_t *dst, int pitch, const uint8_t *src) {
for (int y = 0; y < 8; ++y) {
memcpy(dst + y * pitch, src, 8);
src += 8;
}
return src;
}
static const uint8_t *decodeSeqOp3(uint8_t *dst, int pitch, const uint8_t *src) {
int pos;
do {
pos = *src++;
const int offset = ((pos >> 3) & 7) * pitch + (pos & 7);
dst[offset] = *src++;
} while ((pos & 0x80) == 0);
return src;
}
SeqPlayer::SeqPlayer(SystemStub *stub, Mixer *mixer)
: _stub(stub), _buf(0), _mix(mixer) {
_soundQueuePreloadSize = 0;
_soundQueue = 0;
}
SeqPlayer::~SeqPlayer() {
}
void SeqPlayer::play(File *f) {
if (_demux.open(f)) {
Color pal[256];
for (int i = 0; i < 256; ++i) {
_stub->getPaletteEntry(i, &pal[i]);
}
_mix->setPremixHook(mixCallback, this);
memset(_buf, 0, 256 * 224);
bool clearScreen = true;
while (true) {
const uint32_t nextFrameTimeStamp = _stub->getTimeStamp() + 1000 / 25;
_stub->processEvents();
if (_stub->_pi.quit || _stub->_pi.backspace) {
_stub->_pi.backspace = false;
break;
}
if (!_demux.readFrameData()) {
break;
}
if (_demux._audioDataSize != 0) {
SoundBufferQueue *sbq = (SoundBufferQueue *)malloc(sizeof(SoundBufferQueue));
if (sbq) {
sbq->data = (uint8_t *)malloc(SeqDemuxer::kAudioBufferSize);
if (sbq->data) {
_demux.readAudioS8(sbq->data);
sbq->size = SeqDemuxer::kAudioBufferSize;
sbq->read = 0;
sbq->next = 0;
} else {
free(sbq);
sbq = 0;
}
}
if (sbq) {
LockAudioStack las(_stub);
if (!_soundQueue) {
_soundQueue = sbq;
} else {
SoundBufferQueue *p = _soundQueue;
while (p->next) {
p = p->next;
}
p->next = sbq;
}
if (_soundQueuePreloadSize < kSoundPreloadSize) {
++_soundQueuePreloadSize;
}
}
}
if (_demux._paletteDataSize != 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);
}
_stub->setPalette(buf, 256);
}
if (_demux._videoData != -1) {
const int y0 = (224 - kVideoHeight) / 2;
const uint8_t *src = _demux._buffers[_demux._videoData].data;
_demux.clearBuffer(_demux._videoData);
BitStream bs(src); src += 128;
for (int y = 0; y < kVideoHeight; y += 8) {
for (int x = 0; x < kVideoWidth; x += 8) {
const int offset = (y0 + y) * 256 + x;
switch (bs.getBits(2)) {
case 1:
src = decodeSeqOp1(_buf + offset, 256, src);
break;
case 2:
src = decodeSeqOp2(_buf + offset, 256, src);
break;
case 3:
src = decodeSeqOp3(_buf + offset, 256, src);
break;
}
}
}
if (clearScreen) {
clearScreen = false;
_stub->copyRect(0, 0, kVideoWidth, 224, _buf, 256);
} else {
_stub->copyRect(0, y0, kVideoWidth, kVideoHeight, _buf, 256);
}
_stub->updateScreen(0);
}
const int diff = nextFrameTimeStamp - _stub->getTimeStamp();
if (diff > 0) {
_stub->sleep(diff);
}
}
for (int i = 0; i < 256; ++i) {
_stub->setPaletteEntry(i, &pal[i]);
}
_mix->setPremixHook(0, 0);
_demux.close();
// flush sound queue
LockAudioStack las(_stub);
while (_soundQueue) {
SoundBufferQueue *next = _soundQueue->next;
free(_soundQueue->data);
free(_soundQueue);
_soundQueue = next;
}
_soundQueuePreloadSize = 0;
}
}
bool SeqPlayer::mix(int8_t *buf, int samples) {
if (_soundQueuePreloadSize < kSoundPreloadSize) {
return true;
}
while (_soundQueue && samples > 0) {
*buf++ = _soundQueue->data[_soundQueue->read];
++_soundQueue->read;
if (_soundQueue->read == _soundQueue->size) {
SoundBufferQueue *next = _soundQueue->next;
free(_soundQueue->data);
free(_soundQueue);
_soundQueue = next;
}
--samples;
}
return true;
}
bool SeqPlayer::mixCallback(void *param, int8_t *buf, int len) {
return ((SeqPlayer *)param)->mix(buf, len);
}

92
seq_player.h Normal file
View File

@ -0,0 +1,92 @@
/* REminiscence - Flashback interpreter
* Copyright (C) 2005-2015 Gregory Montoir
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SEQ_PLAYER_H__
#define SEQ_PLAYER_H__
#include "intern.h"
struct File;
struct SystemStub;
struct Mixer;
struct SeqDemuxer {
enum {
kFrameSize = 6144,
kAudioBufferSize = 882,
kBuffersCount = 30
};
bool open(File *f);
void close();
bool readHeader();
bool readFrameData();
void fillBuffer(int num, int offset, int size);
void clearBuffer(int num);
void readPalette(uint8_t *dst);
void readAudioS8(uint8_t *dst);
int _frameOffset;
int _audioDataOffset;
int _audioDataSize;
int _paletteDataOffset;
int _paletteDataSize;
int _videoData;
struct {
int size;
int avail;
uint8_t *data;
} _buffers[kBuffersCount];
int _fileSize;
File *_f;
};
struct SeqPlayer {
enum {
kVideoWidth = 256,
kVideoHeight = 128,
kSoundPreloadSize = 4
};
static const char *_namesTable[];
struct SoundBufferQueue {
uint8_t *data;
int size;
int read;
SoundBufferQueue *next;
};
SeqPlayer(SystemStub *stub, Mixer *mixer);
~SeqPlayer();
void setBackBuffer(uint8_t *buf) { _buf = buf; }
void play(File *f);
bool mix(int8_t *buf, int len);
static bool mixCallback(void *param, int8_t *buf, int len);
SystemStub *_stub;
uint8_t *_buf;
Mixer *_mix;
SeqDemuxer _demux;
int _soundQueuePreloadSize;
SoundBufferQueue *_soundQueue;
};
#endif // SEQ_PLAYER_H__

174
sfx_player.cpp Normal file
View File

@ -0,0 +1,174 @@
/* REminiscence - Flashback interpreter
* Copyright (C) 2005-2015 Gregory Montoir
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "mixer.h"
#include "sfx_player.h"
SfxPlayer::SfxPlayer(Mixer *mixer)
: _mod(0), _playing(false), _mix(mixer) {
}
void SfxPlayer::play(uint8_t num) {
debug(DBG_SFX, "SfxPlayer::play(%d)", num);
if (!_playing) {
if (num >= 68 && num <= 75) {
static const Module *modTable[] = {
&_module68, &_module68, &_module70, &_module70,
&_module72, &_module73, &_module74, &_module75
};
_mod = modTable[num - 68];
_curOrder = 0;
_numOrders = READ_BE_UINT16(_mod->moduleData);
_orderDelay = 0;
_modData = _mod->moduleData + 0x22;
memset(_samples, 0, sizeof(_samples));
_samplesLeft = 0;
_mix->setPremixHook(mixCallback, this);
_playing = true;
}
}
}
void SfxPlayer::stop() {
if (_playing) {
_mix->setPremixHook(0, 0);
_playing = false;
}
}
void SfxPlayer::playSample(int channel, const uint8_t *sampleData, uint16_t period) {
assert(channel < NUM_CHANNELS);
SampleInfo *si = &_samples[channel];
si->len = READ_BE_UINT16(sampleData); sampleData += 2;
si->vol = READ_BE_UINT16(sampleData); sampleData += 2;
si->loopPos = READ_BE_UINT16(sampleData); sampleData += 2;
si->loopLen = READ_BE_UINT16(sampleData); sampleData += 2;
si->freq = PAULA_FREQ / period;
si->pos = 0;
si->data = sampleData;
}
void SfxPlayer::handleTick() {
if (!_playing) {
return;
}
if (_orderDelay != 0) {
--_orderDelay;
// check for end of song
if (_orderDelay == 0 && _modData == 0) {
_playing = false;
}
} else {
_orderDelay = READ_BE_UINT16(_mod->moduleData + 2);
debug(DBG_SFX, "curOrder=%d/%d _orderDelay=%d\n", _curOrder, _numOrders, _orderDelay);
int16_t period = 0;
for (int ch = 0; ch < 3; ++ch) {
const uint8_t *sampleData = 0;
uint8_t b = *_modData++;
if (b != 0) {
--b;
assert(b < 5);
period = READ_BE_UINT16(_mod->moduleData + 4 + b * 2);
sampleData = _mod->sampleData[b];
}
b = *_modData++;
if (b != 0) {
int16_t per = period + (b - 1);
if (per >= 0 && per < 40) {
per = _periodTable[per];
} else if (per == -3) {
per = 0xA0;
} else {
per = 0x71;
}
playSample(ch, sampleData, per);
}
}
++_curOrder;
if (_curOrder >= _numOrders) {
debug(DBG_SFX, "End of song");
_orderDelay += 20;
_modData = 0;
}
}
}
void SfxPlayer::mixSamples(int8_t *buf, int samplesLen) {
for (int i = 0; i < NUM_CHANNELS; ++i) {
SampleInfo *si = &_samples[i];
if (si->data) {
int8_t *mixbuf = buf;
int len = si->len << FRAC_BITS;
int loopLen = si->loopLen << FRAC_BITS;
int loopPos = si->loopPos << FRAC_BITS;
int deltaPos = (si->freq << FRAC_BITS) / _mix->getSampleRate();
int curLen = samplesLen;
int pos = si->pos;
while (curLen != 0) {
int count;
if (loopLen > (2 << FRAC_BITS)) {
assert(si->loopPos + si->loopLen <= si->len);
if (pos >= loopPos + loopLen) {
pos -= loopLen;
}
count = MIN(curLen, (loopPos + loopLen - pos - 1) / deltaPos + 1);
curLen -= count;
} else {
if (pos >= len) {
count = 0;
} else {
count = MIN(curLen, (len - pos - 1) / deltaPos + 1);
}
curLen = 0;
}
while (count--) {
int out = resampleLinear(si, pos, deltaPos, FRAC_BITS);
Mixer::addclamp(*mixbuf++, out * si->vol / 64);
pos += deltaPos;
}
}
si->pos = pos;
}
}
}
bool SfxPlayer::mix(int8_t *buf, int len) {
if (_playing) {
memset(buf, 0, len);
const int samplesPerTick = _mix->getSampleRate() / 50;
while (len != 0) {
if (_samplesLeft == 0) {
handleTick();
_samplesLeft = samplesPerTick;
}
int count = _samplesLeft;
if (count > len) {
count = len;
}
_samplesLeft -= count;
len -= count;
mixSamples(buf, count);
buf += count;
}
}
return _playing;
}
bool SfxPlayer::mixCallback(void *param, int8_t *buf, int len) {
return ((SfxPlayer *)param)->mix(buf, len);
}

101
sfx_player.h Normal file
View File

@ -0,0 +1,101 @@
/* REminiscence - Flashback interpreter
* Copyright (C) 2005-2015 Gregory Montoir
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SFX_PLAYER_H__
#define SFX_PLAYER_H__
#include "intern.h"
struct Mixer;
struct SfxPlayer {
enum {
NUM_SAMPLES = 5,
NUM_CHANNELS = 3,
FRAC_BITS = 12,
PAULA_FREQ = 3546897
};
struct Module {
const uint8_t *sampleData[NUM_SAMPLES];
const uint8_t *moduleData;
};
struct SampleInfo {
uint16_t len;
uint16_t vol;
uint16_t loopPos;
uint16_t loopLen;
int freq;
int pos;
const uint8_t *data;
int8_t getPCM(int offset) const {
if (offset < 0) {
offset = 0;
} else if (offset >= (int)len) {
offset = len - 1;
}
return (int8_t)data[offset];
}
};
static const uint8_t _musicData68[];
static const uint8_t _musicData70[];
static const uint8_t _musicData72[];
static const uint8_t _musicData73[];
static const uint8_t _musicData74[];
static const uint8_t _musicData75[];
static const uint8_t _musicDataSample1[];
static const uint8_t _musicDataSample2[]; // tick
static const uint8_t _musicDataSample3[]; // bell
static const uint8_t _musicDataSample4[];
static const uint8_t _musicDataSample5[];
static const uint8_t _musicDataSample6[];
static const uint8_t _musicDataSample7[];
static const uint8_t _musicDataSample8[];
static const Module _module68;
static const Module _module70;
static const Module _module72;
static const Module _module73;
static const Module _module74;
static const Module _module75;
static const uint16_t _periodTable[];
const Module *_mod;
bool _playing;
int _samplesLeft;
uint16_t _curOrder;
uint16_t _numOrders;
uint16_t _orderDelay;
const uint8_t *_modData;
SampleInfo _samples[NUM_CHANNELS];
Mixer *_mix;
SfxPlayer(Mixer *mixer);
void play(uint8_t num);
void stop();
void playSample(int channel, const uint8_t *sampleData, uint16_t period);
void handleTick();
bool mix(int8_t *buf, int len);
void mixSamples(int8_t *buf, int samplesLen);
static bool mixCallback(void *param, int8_t *buf, int len);
};
#endif // SFX_PLAYER_H__

3972
staticres.cpp Normal file

File diff suppressed because it is too large Load Diff

100
systemstub.h Normal file
View File

@ -0,0 +1,100 @@
/* REminiscence - Flashback interpreter
* Copyright (C) 2005-2015 Gregory Montoir
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SYSTEMSTUB_H__
#define SYSTEMSTUB_H__
#include "intern.h"
struct PlayerInput {
enum {
DIR_UP = 1 << 0,
DIR_DOWN = 1 << 1,
DIR_LEFT = 1 << 2,
DIR_RIGHT = 1 << 3
};
enum {
DF_FASTMODE = 1 << 0,
DF_DBLOCKS = 1 << 1,
DF_SETLIFE = 1 << 2
};
uint8_t dirMask;
bool enter;
bool space;
bool shift;
bool backspace;
bool escape;
char lastChar;
bool save;
bool load;
int stateSlot;
bool inpRecord;
bool inpReplay;
bool mirrorMode;
uint8_t dbgMask;
bool quit;
};
struct SystemStub {
typedef void (*AudioCallback)(void *param, int8_t *stream, int len);
PlayerInput _pi;
virtual ~SystemStub() {}
virtual void init(const char *title, int w, int h) = 0;
virtual void destroy() = 0;
virtual void setPalette(const uint8_t *pal, int n) = 0;
virtual void setPaletteEntry(int i, const Color *c) = 0;
virtual void getPaletteEntry(int i, Color *c) = 0;
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 fadeScreen() = 0;
virtual void updateScreen(int shakeOffset) = 0;
virtual void processEvents() = 0;
virtual void sleep(int duration) = 0;
virtual uint32_t getTimeStamp() = 0;
virtual void startAudio(AudioCallback callback, void *param) = 0;
virtual void stopAudio() = 0;
virtual uint32_t getOutputSampleRate() = 0;
virtual void lockAudio() = 0;
virtual void unlockAudio() = 0;
};
struct LockAudioStack {
LockAudioStack(SystemStub *stub)
: _stub(stub) {
_stub->lockAudio();
}
~LockAudioStack() {
_stub->unlockAudio();
}
SystemStub *_stub;
};
extern SystemStub *SystemStub_SDL_create();
#endif // SYSTEMSTUB_H__

619
systemstub_sdl.cpp Normal file
View File

@ -0,0 +1,619 @@
/* REminiscence - Flashback interpreter
* Copyright (C) 2005-2015 Gregory Montoir
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <SDL.h>
#include "scaler.h"
#include "systemstub.h"
struct SystemStub_SDL : SystemStub {
enum {
MAX_BLIT_RECTS = 200,
SOUND_SAMPLE_RATE = 22050,
JOYSTICK_COMMIT_VALUE = 3200
};
uint16_t *_screenBuffer;
uint16_t *_fadeScreenBuffer;
SDL_Surface *_screenSurface;
bool _fullscreen;
int _currentScaler;
uint8_t _overscanColor;
uint16_t _pal[256];
int _screenW, _screenH;
SDL_Joystick *_joystick;
SDL_Rect _blitRects[MAX_BLIT_RECTS];
int _numBlitRects;
bool _fadeOnUpdateScreen;
void (*_audioCbProc)(void *, int8_t *, int);
void *_audioCbData;
virtual ~SystemStub_SDL() {}
virtual void init(const char *title, int w, int h);
virtual void destroy();
virtual void setPalette(const uint8_t *pal, int n);
virtual void setPaletteEntry(int i, const Color *c);
virtual void getPaletteEntry(int i, Color *c);
virtual void setOverscanColor(int i);
virtual void copyRect(int x, int y, int w, int h, const uint8_t *buf, int pitch);
virtual void fadeScreen();
virtual void updateScreen(int shakeOffset);
virtual void processEvents();
virtual void sleep(int duration);
virtual uint32_t getTimeStamp();
virtual void startAudio(AudioCallback callback, void *param);
virtual void stopAudio();
virtual uint32_t getOutputSampleRate();
virtual void lockAudio();
virtual void unlockAudio();
void processEvent(const SDL_Event &ev, bool &paused);
void updateScreen_GL(int shakeOffset);
void updateScreen_SW(int shakeOffset);
void prepareGfxMode();
void cleanupGfxMode();
void switchGfxMode(bool fullscreen, uint8_t scaler);
void flipGfx();
void forceGfxRedraw();
void drawRect(SDL_Rect *rect, uint8_t color, uint16_t *dst, uint16_t dstPitch);
};
SystemStub *SystemStub_SDL_create() {
return new SystemStub_SDL();
}
void SystemStub_SDL::init(const char *title, int w, int h) {
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_JOYSTICK);
SDL_ShowCursor(SDL_DISABLE);
SDL_WM_SetCaption(title, NULL);
memset(&_pi, 0, sizeof(_pi));
_screenW = w;
_screenH = h;
// allocate some extra bytes for the scaling routines
const int screenBufferSize = (w + 2) * (h + 2) * sizeof(uint16_t);
_screenBuffer = (uint16_t *)malloc(screenBufferSize);
if (!_screenBuffer) {
error("SystemStub_SDL::init() Unable to allocate offscreen buffer");
}
memset(_screenBuffer, 0, screenBufferSize);
_fadeScreenBuffer = 0;
_fadeOnUpdateScreen = false;
_fullscreen = false;
_currentScaler = SCALER_SCALE_3X;
memset(_pal, 0, sizeof(_pal));
prepareGfxMode();
_joystick = NULL;
if (SDL_NumJoysticks() > 0) {
_joystick = SDL_JoystickOpen(0);
}
}
void SystemStub_SDL::destroy() {
cleanupGfxMode();
if (SDL_JoystickOpened(0)) {
SDL_JoystickClose(_joystick);
}
SDL_Quit();
}
void SystemStub_SDL::setPalette(const uint8_t *pal, int n) {
assert(n <= 256);
for (int i = 0; i < n; ++i) {
uint8_t r = pal[i * 3 + 0];
uint8_t g = pal[i * 3 + 1];
uint8_t b = pal[i * 3 + 2];
_pal[i] = SDL_MapRGB(_screenSurface->format, r, g, b);
}
}
void SystemStub_SDL::setPaletteEntry(int i, const Color *c) {
uint8_t r = (c->r << 2) | (c->r & 3);
uint8_t g = (c->g << 2) | (c->g & 3);
uint8_t b = (c->b << 2) | (c->b & 3);
_pal[i] = SDL_MapRGB(_screenSurface->format, r, g, b);
}
void SystemStub_SDL::getPaletteEntry(int i, Color *c) {
SDL_GetRGB(_pal[i], _screenSurface->format, &c->r, &c->g, &c->b);
c->r >>= 2;
c->g >>= 2;
c->b >>= 2;
}
void SystemStub_SDL::setOverscanColor(int i) {
_overscanColor = i;
}
void SystemStub_SDL::copyRect(int x, int y, int w, int h, const uint8_t *buf, int pitch) {
if (_numBlitRects >= MAX_BLIT_RECTS) {
warning("SystemStub_SDL::copyRect() Too many blit rects, you may experience graphical glitches");
} else {
// extend the dirty region by 1 pixel for scalers accessing 'outer' pixels
--x;
--y;
w += 2;
h += 2;
if (x < 0) {
x = 0;
} else if (x >= _screenW) {
return;
}
if (y < 0) {
y = 0;
} else if (y >= _screenH) {
return;
}
if (x + w > _screenW) {
w = _screenW - x;
}
if (y + h > _screenH) {
h = _screenH - y;
}
SDL_Rect *br = &_blitRects[_numBlitRects];
br->x = _pi.mirrorMode ? _screenW - (x + w) : x;
br->y = y;
br->w = w;
br->h = h;
++_numBlitRects;
uint16_t *p = _screenBuffer + (br->y + 1) * _screenW + (br->x + 1);
buf += y * pitch + x;
if (_pi.mirrorMode) {
while (h--) {
for (int i = 0; i < w; ++i) {
p[i] = _pal[buf[w - 1 - i]];
}
p += _screenW;
buf += pitch;
}
} else {
while (h--) {
for (int i = 0; i < w; ++i) {
p[i] = _pal[buf[i]];
}
p += _screenW;
buf += pitch;
}
}
if (_pi.dbgMask & PlayerInput::DF_DBLOCKS) {
drawRect(br, 0xE7, _screenBuffer + _screenW + 1, _screenW * 2);
}
}
}
void SystemStub_SDL::fadeScreen() {
const int fadeScreenBufferSize = _screenH * _screenW * sizeof(uint16_t);
if (!_fadeScreenBuffer) {
_fadeScreenBuffer = (uint16_t *)malloc(fadeScreenBufferSize);
assert(_fadeScreenBuffer);
}
_fadeOnUpdateScreen = true;
memcpy(_fadeScreenBuffer, _screenBuffer + _screenW + 1, fadeScreenBufferSize);
}
static uint16_t blendPixel16(uint16_t colorSrc, uint16_t colorDst, uint32_t mask, int step) {
const uint32_t pSrc = (colorSrc | (colorSrc << 16)) & mask;
const uint32_t pDst = (colorDst | (colorDst << 16)) & mask;
const uint32_t pRes = ((pDst - pSrc) * step / 16 + pSrc) & mask;
return pRes | (pRes >> 16);
}
void SystemStub_SDL::updateScreen(int shakeOffset) {
const int mul = _scalers[_currentScaler].factor;
if (_fadeOnUpdateScreen) {
const int tempScreenBufferSize = (_screenH + 2) * (_screenW + 2) * sizeof(uint16_t);
uint16_t *tempScreenBuffer = (uint16_t *)calloc(tempScreenBufferSize, 1);
assert(tempScreenBuffer);
const SDL_PixelFormat *pf = _screenSurface->format;
const uint32_t colorMask = (pf->Gmask << 16) | (pf->Rmask | pf->Bmask);
const uint16_t *screenBuffer = _screenBuffer + _screenW + 1;
for (int i = 1; i <= 16; ++i) {
for (int x = 0; x < _screenH * _screenW; ++x) {
tempScreenBuffer[_screenW + 1 + x] = blendPixel16(_fadeScreenBuffer[x], screenBuffer[x], colorMask, i);
}
SDL_LockSurface(_screenSurface);
uint16_t *dst = (uint16_t *)_screenSurface->pixels;
const uint16_t *src = tempScreenBuffer + _screenW + 1;
(*_scalers[_currentScaler].proc)(dst, _screenSurface->pitch, src, _screenW, _screenW, _screenH);
SDL_UnlockSurface(_screenSurface);
SDL_UpdateRect(_screenSurface, 0, 0, _screenW * mul, _screenH * mul);
SDL_Delay(30);
}
free(tempScreenBuffer);
_fadeOnUpdateScreen = false;
return;
}
if (shakeOffset == 0) {
for (int i = 0; i < _numBlitRects; ++i) {
SDL_Rect *br = &_blitRects[i];
int dx = br->x * mul;
int dy = br->y * mul;
SDL_LockSurface(_screenSurface);
uint16_t *dst = (uint16_t *)_screenSurface->pixels + dy * _screenSurface->pitch / 2 + dx;
const uint16_t *src = _screenBuffer + (br->y + 1) * _screenW + (br->x + 1);
(*_scalers[_currentScaler].proc)(dst, _screenSurface->pitch, src, _screenW, br->w, br->h);
SDL_UnlockSurface(_screenSurface);
br->x *= mul;
br->y *= mul;
br->w *= mul;
br->h *= mul;
}
SDL_UpdateRects(_screenSurface, _numBlitRects, _blitRects);
} else {
SDL_LockSurface(_screenSurface);
int w = _screenW;
int h = _screenH - shakeOffset;
uint16_t *dst = (uint16_t *)_screenSurface->pixels + shakeOffset * mul * _screenSurface->pitch / 2;
const uint16_t *src = _screenBuffer + _screenW + 1;
(*_scalers[_currentScaler].proc)(dst, _screenSurface->pitch, src, _screenW, w, h);
SDL_UnlockSurface(_screenSurface);
SDL_Rect r;
r.x = 0;
r.y = 0;
r.w = _screenW * mul;
r.h = shakeOffset * mul;
SDL_FillRect(_screenSurface, &r, _pal[_overscanColor]);
SDL_UpdateRect(_screenSurface, 0, 0, _screenW * mul, _screenH * mul);
}
_numBlitRects = 0;
}
void SystemStub_SDL::processEvents() {
while (true) {
bool paused = false;
SDL_Event ev;
while (SDL_PollEvent(&ev)) {
processEvent(ev, paused);
if (_pi.quit) {
return;
}
}
if (!paused) {
break;
}
SDL_Delay(100);
}
}
void SystemStub_SDL::processEvent(const SDL_Event &ev, bool &paused) {
switch (ev.type) {
case SDL_QUIT:
_pi.quit = true;
break;
case SDL_ACTIVEEVENT:
if (ev.active.state & SDL_APPINPUTFOCUS) {
paused = ev.active.gain == 0;
SDL_PauseAudio(paused ? 1 : 0);
}
break;
case SDL_JOYHATMOTION:
_pi.dirMask = 0;
if (ev.jhat.value & SDL_HAT_UP) {
_pi.dirMask |= PlayerInput::DIR_UP;
}
if (ev.jhat.value & SDL_HAT_DOWN) {
_pi.dirMask |= PlayerInput::DIR_DOWN;
}
if (ev.jhat.value & SDL_HAT_LEFT) {
_pi.dirMask |= PlayerInput::DIR_LEFT;
}
if (ev.jhat.value & SDL_HAT_RIGHT) {
_pi.dirMask |= PlayerInput::DIR_RIGHT;
}
break;
case SDL_JOYAXISMOTION:
switch (ev.jaxis.axis) {
case 0:
if (ev.jaxis.value > JOYSTICK_COMMIT_VALUE) {
_pi.dirMask |= PlayerInput::DIR_RIGHT;
if (_pi.dirMask & PlayerInput::DIR_LEFT) {
_pi.dirMask &= ~PlayerInput::DIR_LEFT;
}
} else if (ev.jaxis.value < -JOYSTICK_COMMIT_VALUE) {
_pi.dirMask |= PlayerInput::DIR_LEFT;
if (_pi.dirMask & PlayerInput::DIR_RIGHT) {
_pi.dirMask &= ~PlayerInput::DIR_RIGHT;
}
} else {
_pi.dirMask &= ~(PlayerInput::DIR_RIGHT | PlayerInput::DIR_LEFT);
}
break;
case 1:
if (ev.jaxis.value > JOYSTICK_COMMIT_VALUE) {
_pi.dirMask |= PlayerInput::DIR_DOWN;
if (_pi.dirMask & PlayerInput::DIR_UP) {
_pi.dirMask &= ~PlayerInput::DIR_UP;
}
} else if (ev.jaxis.value < -JOYSTICK_COMMIT_VALUE) {
_pi.dirMask |= PlayerInput::DIR_UP;
if (_pi.dirMask & PlayerInput::DIR_DOWN) {
_pi.dirMask &= ~PlayerInput::DIR_DOWN;
}
} else {
_pi.dirMask &= ~(PlayerInput::DIR_UP | PlayerInput::DIR_DOWN);
}
break;
}
break;
case SDL_JOYBUTTONDOWN:
switch (ev.jbutton.button) {
case 0:
_pi.space = true;
break;
case 1:
_pi.shift = true;
break;
case 2:
_pi.enter = true;
break;
case 3:
_pi.backspace = true;
break;
}
break;
case SDL_JOYBUTTONUP:
switch (ev.jbutton.button) {
case 0:
_pi.space = false;
break;
case 1:
_pi.shift = false;
break;
case 2:
_pi.enter = false;
break;
case 3:
_pi.backspace = false;
break;
}
break;
case SDL_KEYUP:
switch (ev.key.keysym.sym) {
case SDLK_LEFT:
_pi.dirMask &= ~PlayerInput::DIR_LEFT;
break;
case SDLK_RIGHT:
_pi.dirMask &= ~PlayerInput::DIR_RIGHT;
break;
case SDLK_UP:
_pi.dirMask &= ~PlayerInput::DIR_UP;
break;
case SDLK_DOWN:
_pi.dirMask &= ~PlayerInput::DIR_DOWN;
break;
case SDLK_SPACE:
_pi.space = false;
break;
case SDLK_RSHIFT:
case SDLK_LSHIFT:
_pi.shift = false;
break;
case SDLK_RETURN:
_pi.enter = false;
break;
case SDLK_ESCAPE:
_pi.escape = false;
break;
default:
break;
}
break;
case SDL_KEYDOWN:
if (ev.key.keysym.mod & KMOD_ALT) {
if (ev.key.keysym.sym == SDLK_RETURN) {
switchGfxMode(!_fullscreen, _currentScaler);
} else if (ev.key.keysym.sym == SDLK_KP_PLUS || ev.key.keysym.sym == SDLK_PAGEUP) {
uint8_t s = _currentScaler + 1;
if (s < NUM_SCALERS) {
switchGfxMode(_fullscreen, s);
}
} else if (ev.key.keysym.sym == SDLK_KP_MINUS || ev.key.keysym.sym == SDLK_PAGEDOWN) {
int8_t s = _currentScaler - 1;
if (_currentScaler > 0) {
switchGfxMode(_fullscreen, s);
}
}
break;
} else if (ev.key.keysym.mod & KMOD_CTRL) {
if (ev.key.keysym.sym == SDLK_f) {
_pi.dbgMask ^= PlayerInput::DF_FASTMODE;
} else if (ev.key.keysym.sym == SDLK_b) {
_pi.dbgMask ^= PlayerInput::DF_DBLOCKS;
} else if (ev.key.keysym.sym == SDLK_i) {
_pi.dbgMask ^= PlayerInput::DF_SETLIFE;
} else if (ev.key.keysym.sym == SDLK_m) {
_pi.mirrorMode = !_pi.mirrorMode;
flipGfx();
} else if (ev.key.keysym.sym == SDLK_s) {
_pi.save = true;
} else if (ev.key.keysym.sym == SDLK_l) {
_pi.load = true;
} else if (ev.key.keysym.sym == SDLK_KP_PLUS || ev.key.keysym.sym == SDLK_PAGEUP) {
_pi.stateSlot = 1;
} else if (ev.key.keysym.sym == SDLK_KP_MINUS || ev.key.keysym.sym == SDLK_PAGEDOWN) {
_pi.stateSlot = -1;
} else if (ev.key.keysym.sym == SDLK_r) {
_pi.inpRecord = true;
} else if (ev.key.keysym.sym == SDLK_p) {
_pi.inpReplay = true;
}
}
_pi.lastChar = ev.key.keysym.sym;
switch (ev.key.keysym.sym) {
case SDLK_LEFT:
_pi.dirMask |= PlayerInput::DIR_LEFT;
break;
case SDLK_RIGHT:
_pi.dirMask |= PlayerInput::DIR_RIGHT;
break;
case SDLK_UP:
_pi.dirMask |= PlayerInput::DIR_UP;
break;
case SDLK_DOWN:
_pi.dirMask |= PlayerInput::DIR_DOWN;
break;
case SDLK_BACKSPACE:
case SDLK_TAB:
_pi.backspace = true;
break;
case SDLK_SPACE:
_pi.space = true;
break;
case SDLK_RSHIFT:
case SDLK_LSHIFT:
_pi.shift = true;
break;
case SDLK_RETURN:
_pi.enter = true;
break;
case SDLK_ESCAPE:
_pi.escape = true;
break;
default:
break;
}
break;
default:
break;
}
}
void SystemStub_SDL::sleep(int duration) {
SDL_Delay(duration);
}
uint32_t SystemStub_SDL::getTimeStamp() {
return SDL_GetTicks();
}
static void mixAudioS8ToU8(void *param, uint8_t *buf, int len) {
SystemStub_SDL *stub = (SystemStub_SDL *)param;
stub->_audioCbProc(stub->_audioCbData, (int8_t *)buf, len);
for (int i = 0; i < len; ++i) {
buf[i] ^= 0x80;
}
}
void SystemStub_SDL::startAudio(AudioCallback callback, void *param) {
SDL_AudioSpec desired, obtained;
memset(&desired, 0, sizeof(desired));
desired.freq = SOUND_SAMPLE_RATE;
desired.format = AUDIO_U8;
desired.channels = 1;
desired.samples = 2048;
desired.callback = mixAudioS8ToU8;
desired.userdata = this;
if (SDL_OpenAudio(&desired, &obtained) == 0) {
_audioCbProc = callback;
_audioCbData = param;
SDL_PauseAudio(0);
} else {
error("SystemStub_SDL::startAudio() Unable to open sound device");
}
}
void SystemStub_SDL::stopAudio() {
SDL_CloseAudio();
}
uint32_t SystemStub_SDL::getOutputSampleRate() {
return SOUND_SAMPLE_RATE;
}
void SystemStub_SDL::lockAudio() {
SDL_LockAudio();
}
void SystemStub_SDL::unlockAudio() {
SDL_UnlockAudio();
}
void SystemStub_SDL::prepareGfxMode() {
int w = _screenW * _scalers[_currentScaler].factor;
int h = _screenH * _scalers[_currentScaler].factor;
_screenSurface = SDL_SetVideoMode(w, h, 16, _fullscreen ? (SDL_FULLSCREEN | SDL_HWSURFACE) : SDL_HWSURFACE);
if (!_screenSurface) {
error("SystemStub_SDL::prepareGfxMode() Unable to allocate _screen buffer");
}
forceGfxRedraw();
}
void SystemStub_SDL::cleanupGfxMode() {
if (_screenBuffer) {
free(_screenBuffer);
_screenBuffer = 0;
}
if (_fadeScreenBuffer) {
free(_fadeScreenBuffer);
_fadeScreenBuffer = 0;
}
if (_screenSurface) {
// freed by SDL_Quit()
_screenSurface = 0;
}
}
void SystemStub_SDL::switchGfxMode(bool fullscreen, uint8_t scaler) {
SDL_FreeSurface(_screenSurface);
_fullscreen = fullscreen;
_currentScaler = scaler;
prepareGfxMode();
forceGfxRedraw();
}
void SystemStub_SDL::flipGfx() {
uint16_t scanline[256];
assert(_screenW <= 256);
uint16_t *p = _screenBuffer + _screenW + 1;
for (int y = 0; y < _screenH; ++y) {
p += _screenW;
for (int x = 0; x < _screenW; ++x) {
scanline[x] = *--p;
}
memcpy(p, scanline, _screenW * sizeof(uint16_t));
p += _screenW;
}
forceGfxRedraw();
}
void SystemStub_SDL::forceGfxRedraw() {
_numBlitRects = 1;
_blitRects[0].x = 0;
_blitRects[0].y = 0;
_blitRects[0].w = _screenW;
_blitRects[0].h = _screenH;
}
void SystemStub_SDL::drawRect(SDL_Rect *rect, uint8_t color, uint16_t *dst, uint16_t dstPitch) {
dstPitch >>= 1;
int x1 = rect->x;
int y1 = rect->y;
int x2 = rect->x + rect->w - 1;
int y2 = rect->y + rect->h - 1;
assert(x1 >= 0 && x2 < _screenW && y1 >= 0 && y2 < _screenH);
for (int i = x1; i <= x2; ++i) {
*(dst + y1 * dstPitch + i) = *(dst + y2 * dstPitch + i) = _pal[color];
}
for (int j = y1; j <= y2; ++j) {
*(dst + j * dstPitch + x1) = *(dst + j * dstPitch + x2) = _pal[color];
}
}

108
unpack.cpp Normal file
View File

@ -0,0 +1,108 @@
/* REminiscence - Flashback interpreter
* Copyright (C) 2005-2015 Gregory Montoir
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "unpack.h"
struct UnpackCtx {
int size, datasize;
uint32_t crc;
uint32_t bits;
uint8_t *dst;
const uint8_t *src;
};
static int shiftBit(UnpackCtx *uc, int CF) {
int rCF = (uc->bits & 1);
uc->bits >>= 1;
if (CF) {
uc->bits |= 0x80000000;
}
return rCF;
}
static int nextBit(UnpackCtx *uc) {
int CF = shiftBit(uc, 0);
if (uc->bits == 0) {
uc->bits = READ_BE_UINT32(uc->src); uc->src -= 4;
uc->crc ^= uc->bits;
CF = shiftBit(uc, 1);
}
return CF;
}
static uint16_t getBits(UnpackCtx *uc, uint8_t num_bits) {
uint16_t c = 0;
while (num_bits--) {
c <<= 1;
if (nextBit(uc)) {
c |= 1;
}
}
return c;
}
static void unpackHelper1(UnpackCtx *uc, uint8_t num_bits, uint8_t add_count) {
uint16_t count = getBits(uc, num_bits) + add_count + 1;
uc->datasize -= count;
while (count--) {
*uc->dst = (uint8_t)getBits(uc, 8);
--uc->dst;
}
}
static void unpackHelper2(UnpackCtx *uc, uint8_t num_bits) {
uint16_t i = getBits(uc, num_bits);
uint16_t count = uc->size + 1;
uc->datasize -= count;
while (count--) {
*uc->dst = *(uc->dst + i);
--uc->dst;
}
}
bool delphine_unpack(uint8_t *dst, const uint8_t *src, int len) {
UnpackCtx uc;
uc.src = src + len - 4;
uc.datasize = READ_BE_UINT32(uc.src); uc.src -= 4;
uc.dst = dst + uc.datasize - 1;
uc.size = 0;
uc.crc = READ_BE_UINT32(uc.src); uc.src -= 4;
uc.bits = READ_BE_UINT32(uc.src); uc.src -= 4;
uc.crc ^= uc.bits;
do {
if (!nextBit(&uc)) {
uc.size = 1;
if (!nextBit(&uc)) {
unpackHelper1(&uc, 3, 0);
} else {
unpackHelper2(&uc, 8);
}
} else {
uint16_t c = getBits(&uc, 2);
if (c == 3) {
unpackHelper1(&uc, 8, 8);
} else if (c < 2) {
uc.size = c + 2;
unpackHelper2(&uc, c + 9);
} else {
uc.size = getBits(&uc, 8);
unpackHelper2(&uc, 12);
}
}
} while (uc.datasize > 0);
return uc.crc == 0;
}

25
unpack.h Normal file
View File

@ -0,0 +1,25 @@
/* REminiscence - Flashback interpreter
* Copyright (C) 2005-2015 Gregory Montoir
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef UNPACK_H__
#define UNPACK_H__
#include "intern.h"
extern bool delphine_unpack(uint8_t *dst, const uint8_t *src, int len);
#endif // UNPACK_H__

61
util.cpp Normal file
View File

@ -0,0 +1,61 @@
/* REminiscence - Flashback interpreter
* Copyright (C) 2005-2015 Gregory Montoir
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#endif
#include <cstdarg>
#include "util.h"
uint16_t g_debugMask;
void debug(uint16_t cm, const char *msg, ...) {
char buf[1024];
if (cm & g_debugMask) {
va_list va;
va_start(va, msg);
vsprintf(buf, msg, va);
va_end(va);
printf("%s\n", buf);
fflush(stdout);
}
}
void error(const char *msg, ...) {
char buf[1024];
va_list va;
va_start(va, msg);
vsnprintf(buf, sizeof(buf), msg, va);
va_end(va);
fprintf(stderr, "ERROR: %s!\n", buf);
#ifdef _WIN32
MessageBox(0, buf, g_caption, MB_ICONERROR);
#endif
exit(-1);
}
void warning(const char *msg, ...) {
char buf[1024];
va_list va;
va_start(va, msg);
vsnprintf(buf, sizeof(buf), msg, va);
va_end(va);
fprintf(stderr, "WARNING: %s!\n", buf);
}

45
util.h Normal file
View File

@ -0,0 +1,45 @@
/* REminiscence - Flashback interpreter
* Copyright (C) 2005-2015 Gregory Montoir
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef UTIL_H__
#define UTIL_H__
#include "intern.h"
enum {
DBG_INFO = 1 << 0,
DBG_RES = 1 << 1,
DBG_MENU = 1 << 2,
DBG_UNPACK = 1 << 3,
DBG_PGE = 1 << 4,
DBG_VIDEO = 1 << 5,
DBG_GAME = 1 << 6,
DBG_COL = 1 << 7,
DBG_SND = 1 << 8,
DBG_CUT = 1 << 9,
DBG_MOD = 1 << 10,
DBG_SFX = 1 << 11,
DBG_FILE = 1 << 12
};
extern uint16_t g_debugMask;
extern void debug(uint16_t cm, const char *msg, ...);
extern void error(const char *msg, ...);
extern void warning(const char *msg, ...);
#endif // UTIL_H__

831
video.cpp Normal file
View File

@ -0,0 +1,831 @@
/* REminiscence - Flashback interpreter
* Copyright (C) 2005-2015 Gregory Montoir
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "resource.h"
#include "systemstub.h"
#include "unpack.h"
#include "video.h"
Video::Video(Resource *res, SystemStub *stub)
: _res(res), _stub(stub) {
_frontLayer = (uint8_t *)malloc(GAMESCREEN_W * GAMESCREEN_H);
memset(_frontLayer, 0, GAMESCREEN_W * GAMESCREEN_H);
_backLayer = (uint8_t *)malloc(GAMESCREEN_W * GAMESCREEN_H);
memset(_backLayer, 0, GAMESCREEN_W * GAMESCREEN_H);
_tempLayer = (uint8_t *)malloc(GAMESCREEN_W * GAMESCREEN_H);
memset(_tempLayer, 0, GAMESCREEN_W * GAMESCREEN_H);
_tempLayer2 = (uint8_t *)malloc(GAMESCREEN_W * GAMESCREEN_H);
memset(_tempLayer2, 0, GAMESCREEN_W * GAMESCREEN_H);
_screenBlocks = (uint8_t *)malloc((GAMESCREEN_W / SCREENBLOCK_W) * (GAMESCREEN_H / SCREENBLOCK_H));
memset(_screenBlocks, 0, (GAMESCREEN_W / SCREENBLOCK_W) * (GAMESCREEN_H / SCREENBLOCK_H));
_fullRefresh = true;
_shakeOffset = 0;
_charFrontColor = 0;
_charTransparentColor = 0;
_charShadowColor = 0;
}
Video::~Video() {
free(_frontLayer);
free(_backLayer);
free(_tempLayer);
free(_tempLayer2);
free(_screenBlocks);
}
void Video::markBlockAsDirty(int16_t x, int16_t y, uint16_t w, uint16_t h) {
debug(DBG_VIDEO, "Video::markBlockAsDirty(%d, %d, %d, %d)", x, y, w, h);
assert(x >= 0 && x + w <= GAMESCREEN_W && y >= 0 && y + h <= GAMESCREEN_H);
int bx1 = x / SCREENBLOCK_W;
int by1 = y / SCREENBLOCK_H;
int bx2 = (x + w - 1) / SCREENBLOCK_W;
int by2 = (y + h - 1) / SCREENBLOCK_H;
assert(bx2 < GAMESCREEN_W / SCREENBLOCK_W && by2 < GAMESCREEN_H / SCREENBLOCK_H);
for (; by1 <= by2; ++by1) {
for (int i = bx1; i <= bx2; ++i) {
_screenBlocks[by1 * (GAMESCREEN_W / SCREENBLOCK_W) + i] = 2;
}
}
}
void Video::updateScreen() {
debug(DBG_VIDEO, "Video::updateScreen()");
// _fullRefresh = true;
if (_fullRefresh) {
_stub->copyRect(0, 0, Video::GAMESCREEN_W, Video::GAMESCREEN_H, _frontLayer, 256);
_stub->updateScreen(_shakeOffset);
_fullRefresh = false;
} else {
int i, j;
int count = 0;
uint8_t *p = _screenBlocks;
for (j = 0; j < GAMESCREEN_H / SCREENBLOCK_H; ++j) {
uint16_t nh = 0;
for (i = 0; i < GAMESCREEN_W / SCREENBLOCK_W; ++i) {
if (p[i] != 0) {
--p[i];
++nh;
} else if (nh != 0) {
int16_t x = (i - nh) * SCREENBLOCK_W;
_stub->copyRect(x, j * SCREENBLOCK_H, nh * SCREENBLOCK_W, SCREENBLOCK_H, _frontLayer, 256);
nh = 0;
++count;
}
}
if (nh != 0) {
int16_t x = (i - nh) * SCREENBLOCK_W;
_stub->copyRect(x, j * SCREENBLOCK_H, nh * SCREENBLOCK_W, SCREENBLOCK_H, _frontLayer, 256);
++count;
}
p += GAMESCREEN_W / SCREENBLOCK_W;
}
if (count != 0) {
_stub->updateScreen(_shakeOffset);
}
}
if (_shakeOffset != 0) {
_shakeOffset = 0;
_fullRefresh = true;
}
}
void Video::fullRefresh() {
debug(DBG_VIDEO, "Video::fullRefresh()");
_fullRefresh = true;
memset(_screenBlocks, 0, (GAMESCREEN_W / SCREENBLOCK_W) * (GAMESCREEN_H / SCREENBLOCK_H));
}
void Video::fadeOut() {
debug(DBG_VIDEO, "Video::fadeOut()");
_stub->fadeScreen();
// fadeOutPalette();
}
void Video::fadeOutPalette() {
for (int step = 16; step >= 0; --step) {
for (int c = 0; c < 256; ++c) {
Color col;
_stub->getPaletteEntry(c, &col);
col.r = col.r * step >> 4;
col.g = col.g * step >> 4;
col.b = col.b * step >> 4;
_stub->setPaletteEntry(c, &col);
}
fullRefresh();
updateScreen();
_stub->sleep(50);
}
}
void Video::setPaletteColorBE(int num, int offset) {
const int color = READ_BE_UINT16(_res->_pal + offset * 2);
Color c;
c.r = (color & 0x00F) << 2;
c.g = (color & 0x0F0) >> 2;
c.b = (color & 0xF00) >> 6;
if (color != 0) {
c.r |= 3;
c.g |= 3;
c.b |= 3;
}
_stub->setPaletteEntry(num, &c);
}
void Video::setPaletteSlotBE(int palSlot, int palNum) {
debug(DBG_VIDEO, "Video::setPaletteSlotBE()");
const uint8_t *p = _res->_pal + palNum * 0x20;
for (int i = 0; i < 16; ++i) {
const int color = READ_BE_UINT16(p); p += 2;
Color c;
c.r = (color & 0x00F) << 2;
c.g = (color & 0x0F0) >> 2;
c.b = (color & 0xF00) >> 6;
if (color != 0) {
c.r |= 3;
c.g |= 3;
c.b |= 3;
}
_stub->setPaletteEntry(palSlot * 0x10 + i, &c);
}
}
void Video::setPaletteSlotLE(int palSlot, const uint8_t *palData) {
debug(DBG_VIDEO, "Video::setPaletteSlotLE()");
for (int i = 0; i < 16; ++i) {
uint16_t color = READ_LE_UINT16(palData); palData += 2;
Color c;
c.b = (color & 0x00F) << 2;
c.g = (color & 0x0F0) >> 2;
c.r = (color & 0xF00) >> 6;
_stub->setPaletteEntry(palSlot * 0x10 + i, &c);
}
}
void Video::setTextPalette() {
debug(DBG_VIDEO, "Video::setTextPalette()");
setPaletteSlotLE(0xE, _textPal);
}
void Video::setPalette0xF() {
debug(DBG_VIDEO, "Video::setPalette0xF()");
const uint8_t *p = _palSlot0xF;
for (int i = 0; i < 16; ++i) {
Color c;
c.r = *p++ >> 2;
c.g = *p++ >> 2;
c.b = *p++ >> 2;
_stub->setPaletteEntry(0xF0 + i, &c);
}
}
static void PC_decodeMapHelper(int sz, const uint8_t *src, uint8_t *dst) {
const uint8_t *end = src + sz;
while (src < end) {
int16_t code = (int8_t)*src++;
if (code < 0) {
const int len = 1 - code;
memset(dst, *src++, len);
dst += len;
} else {
++code;
memcpy(dst, src, code);
src += code;
dst += code;
}
}
}
void Video::PC_decodeMap(int level, int room) {
debug(DBG_VIDEO, "Video::PC_decodeMap(%d)", room);
assert(room < 0x40);
int32_t off = READ_LE_UINT32(_res->_map + room * 6);
if (off == 0) {
error("Invalid room %d", room);
}
bool packed = true;
if (off < 0) {
off = -off;
packed = false;
}
const uint8_t *p = _res->_map + off;
_mapPalSlot1 = *p++;
_mapPalSlot2 = *p++;
_mapPalSlot3 = *p++;
_mapPalSlot4 = *p++;
if (level == 4 && room == 60) {
// workaround for wrong palette colors (fire)
_mapPalSlot4 = 5;
}
if (packed) {
uint8_t *vid = _frontLayer;
for (int i = 0; i < 4; ++i) {
const int sz = READ_LE_UINT16(p); p += 2;
PC_decodeMapHelper(sz, p, _res->_memBuf); p += sz;
memcpy(vid, _res->_memBuf, 256 * 56);
vid += 256 * 56;
}
} else {
for (int i = 0; i < 4; ++i) {
for (int y = 0; y < 224; ++y) {
for (int x = 0; x < 64; ++x) {
_frontLayer[i + x * 4 + 256 * y] = p[256 * 56 * i + x + 64 * y];
}
}
}
}
memcpy(_backLayer, _frontLayer, Video::GAMESCREEN_W * Video::GAMESCREEN_H);
}
void Video::PC_setLevelPalettes() {
debug(DBG_VIDEO, "Video::PC_setLevelPalettes()");
if (_unkPalSlot2 == 0) {
_unkPalSlot2 = _mapPalSlot3;
}
if (_unkPalSlot1 == 0) {
_unkPalSlot1 = _mapPalSlot3;
}
setPaletteSlotBE(0x0, _mapPalSlot1);
setPaletteSlotBE(0x1, _mapPalSlot2);
setPaletteSlotBE(0x2, _mapPalSlot3);
setPaletteSlotBE(0x3, _mapPalSlot4);
if (_unkPalSlot1 == _mapPalSlot3) {
setPaletteSlotLE(4, _conradPal1);
} else {
setPaletteSlotLE(4, _conradPal2);
}
// slot 5 is monster palette
setPaletteSlotBE(0x8, _mapPalSlot1);
setPaletteSlotBE(0x9, _mapPalSlot2);
setPaletteSlotBE(0xA, _unkPalSlot2);
setPaletteSlotBE(0xB, _mapPalSlot4);
// slots 0xC and 0xD are cutscene palettes
setTextPalette();
}
void Video::PC_decodeIcn(const uint8_t *src, int num, uint8_t *dst) {
const int offset = READ_LE_UINT16(src + num * 2);
const uint8_t *p = src + offset + 2;
for (int i = 0; i < 16 * 16 / 2; ++i) {
*dst++ = p[i] >> 4;
*dst++ = p[i] & 15;
}
}
void Video::PC_decodeSpc(const uint8_t *src, int w, int h, uint8_t *dst) {
const int size = w * h / 2;
for (int i = 0; i < size; ++i) {
*dst++ = src[i] >> 4;
*dst++ = src[i] & 15;
}
}
static void AMIGA_blit3pNxN(uint8_t *dst, int pitch, int w, int h, const uint8_t *src) {
const int planarSize = w * 2 * h;
for (int y = 0; y < h; ++y) {
for (int x = 0; x < w; ++x) {
for (int i = 0; i < 16; ++i) {
int color = 0;
const int mask = 1 << (15 - i);
for (int bit = 0; bit < 3; ++bit) {
if (READ_BE_UINT16(src + bit * planarSize) & mask) {
color |= 1 << bit;
}
}
dst[x * 16 + i] = color;
}
src += 2;
}
dst += pitch;
}
}
static void AMIGA_blit4p16xN(uint8_t *dst, int w, int h, const uint8_t *src) {
const int planarSize = w * 2 * h;
assert(w == 1);
for (int y = 0; y < h; ++y) {
for (int i = 0; i < 16; ++i) {
int color = 0;
const int mask = 1 << (15 - i);
for (int bit = 0; bit < 4; ++bit) {
if (READ_BE_UINT16(src + bit * planarSize) & mask) {
color |= 1 << bit;
}
}
dst[i] = color;
}
src += 2;
dst += 16;
}
}
static void AMIGA_blit4p8xN(uint8_t *dst, int w, int h, const uint8_t *src) {
assert(w == 8);
for (int y = 0; y < h; ++y) {
for (int i = 0; i < 8; ++i) {
int color = 0;
const int mask = 1 << (7 - i);
for (int bit = 0; bit < 4; ++bit) {
if (src[bit] & mask) {
color |= 1 << bit;
}
}
dst[i] = color;
}
src += 4;
dst += w;
}
}
static void AMIGA_blit4pNxN(uint8_t *dst, int w, int h, const uint8_t *src) {
const int planarSize = w / 8 * h;
for (int y = 0; y < h; ++y) {
for (int x = 0; x < w / 8; ++x) {
for (int i = 0; i < 8; ++i) {
int color = 0;
const int mask = 1 << (7 - i);
for (int bit = 0; bit < 4; ++bit) {
if (src[bit * planarSize] & mask) {
color |= 1 << bit;
}
}
dst[x * 8 + i] = color;
}
++src;
}
dst += w;
}
}
static void AMIGA_blit4pNxN_mask(uint8_t *dst, int x0, int y0, int w, int h, uint8_t *src, uint8_t *mask, int size) {
dst += y0 * 256 + x0;
for (int y = 0; y < h; ++y) {
for (int x = 0; x < w * 2; ++x) {
for (int i = 0; i < 8; ++i) {
const int c_mask = 1 << (7 - i);
int color = 0;
for (int j = 0; j < 4; ++j) {
if (mask[j * size] & c_mask) {
color |= 1 << j;
}
}
if (*src & c_mask) {
const int px = x0 + 8 * x + i;
const int py = y0 + y;
if (px >= 0 && px < 256 && py >= 0 && py < 224) {
dst[8 * x + i] = color;
}
}
}
++src;
++mask;
}
dst += 256;
}
}
static void AMIGA_decodeRLE(uint8_t *dst, const uint8_t *src) {
int code = READ_BE_UINT16(src) & 0x7FFF; src += 2;
const uint8_t *end = src + code;
do {
code = *src++;
if ((code & 0x80) == 0) {
++code;
memcpy(dst, src, code);
src += code;
} else {
code = 1 - ((int8_t)code);
memset(dst, *src, code);
++src;
}
dst += code;
} while (src < end);
assert(src == end);
}
static void AMIGA_decodeSgd(uint8_t *dst, const uint8_t *src, const uint8_t *data) {
int num = -1;
uint8_t buf[256 * 32];
int count = READ_BE_UINT16(src) - 1; src += 2;
do {
int d2 = READ_BE_UINT16(src); src += 2;
int d0 = READ_BE_UINT16(src); src += 2;
int d1 = READ_BE_UINT16(src); src += 2;
if (d2 != 0xFFFF) {
d2 &= ~(1 << 15);
const int offset = READ_BE_UINT32(data + d2 * 4);
if (offset < 0) {
const uint8_t *ptr = data - offset;
const int size = READ_BE_UINT16(ptr); ptr += 2;
if (num != d2) {
num = d2;
assert(size <= (int)sizeof(buf));
memcpy(buf, ptr, size);
}
} else {
if (num != d2) {
num = d2;
const int size = READ_BE_UINT16(data + offset) & 0x7FFF;
assert(size <= (int)sizeof(buf));
AMIGA_decodeRLE(buf, data + offset);
}
}
}
const int w = (buf[0] + 1) >> 1;
const int h = buf[1] + 1;
const int planarSize = READ_BE_UINT16(buf + 2);
AMIGA_blit4pNxN_mask(dst, (int16_t)d0, (int16_t)d1, w, h, buf + 4, buf + 4 + planarSize, planarSize);
} while (--count >= 0);
}
static const uint8_t *AMIGA_mirrorY(const uint8_t *a2) {
static uint8_t buf[32];
a2 += 24;
for (int j = 0; j < 4; ++j) {
for (int i = 0; i < 8; ++i) {
buf[31 - j * 8 - i] = *a2++;
}
a2 -= 16;
}
return buf;
}
static const uint8_t *AMIGA_mirrorX(const uint8_t *a2) {
static uint8_t buf[32];
for (int i = 0; i < 32; ++i) {
uint8_t mask = 0;
for (int bit = 0; bit < 8; ++bit) {
if (a2[i] & (1 << bit)) {
mask |= 1 << (7 - bit);
}
}
buf[i] = mask;
}
return buf;
}
static void AMIGA_blit4p8x8(uint8_t *dst, int pitch, const uint8_t *src, int pal, int colorKey = -1) {
for (int y = 0; y < 8; ++y) {
for (int i = 0; i < 8; ++i) {
const int mask = 1 << (7 - i);
int color = 0;
for (int bit = 0; bit < 4; ++bit) {
if (src[8 * bit] & mask) {
color |= 1 << bit;
}
}
if (color != colorKey) {
dst[i] = pal + color;
}
}
++src;
dst += pitch;
}
}
static void AMIGA_decodeLevHelper(uint8_t *dst, const uint8_t *src, int offset10, int offset12, const uint8_t *a5, bool sgdBuf) {
if (offset10 != 0) {
const uint8_t *a0 = src + offset10;
for (int y = 0; y < 224; y += 8) {
for (int x = 0; x < 256; x += 8) {
const int d3 = READ_BE_UINT16(a0); a0 += 2;
const int d0 = d3 & 0x7FF;
if (d0 != 0) {
const uint8_t *a2 = a5 + d0 * 32;
if ((d3 & (1 << 12)) != 0) {
a2 = AMIGA_mirrorY(a2);
}
if ((d3 & (1 << 11)) != 0) {
a2 = AMIGA_mirrorX(a2);
}
int mask = 0;
if ((d3 < (1 << 15)) == 0) {
mask = 0x80;
}
AMIGA_blit4p8x8(dst + y * 256 + x, 256, a2, mask);
}
}
}
}
if (offset12 != 0) {
const uint8_t *a0 = src + offset12;
for (int y = 0; y < 224; y += 8) {
for (int x = 0; x < 256; x += 8) {
int d3 = READ_BE_UINT16(a0); a0 += 2;
int d0 = d3 & 0x7FF;
if (d0 != 0 && sgdBuf) {
d0 -= 896;
}
if (d0 != 0) {
const uint8_t *a2 = a5 + d0 * 32;
if ((d3 & (1 << 12)) != 0) {
a2 = AMIGA_mirrorY(a2);
}
if ((d3 & (1 << 11)) != 0) {
a2 = AMIGA_mirrorX(a2);
}
int mask = 0;
if ((d3 & 0x6000) != 0 && sgdBuf) {
mask = 0x10;
} else if ((d3 < (1 << 15)) == 0) {
mask = 0x80;
}
AMIGA_blit4p8x8(dst + y * 256 + x, 256, a2, mask, 0);
}
}
}
}
}
void Video::AMIGA_decodeLev(int level, int room) {
uint8_t *tmp = _res->_memBuf;
const int offset = READ_BE_UINT32(_res->_lev + room * 4);
if (!delphine_unpack(tmp, _res->_lev, offset)) {
error("Bad CRC for level %d room %d", level, room);
}
uint16_t offset10 = READ_BE_UINT16(tmp + 10);
const uint16_t offset12 = READ_BE_UINT16(tmp + 12);
const uint16_t offset14 = READ_BE_UINT16(tmp + 14);
static const int kTempMbkSize = 1024;
uint8_t *buf = (uint8_t *)malloc(kTempMbkSize * 32);
if (!buf) {
error("Unable to allocate mbk temporary buffer");
}
int sz = 0;
memset(buf, 0, 32);
sz += 32;
const uint8_t *a1 = tmp + offset14;
for (bool loop = true; loop;) {
int d0 = READ_BE_UINT16(a1); a1 += 2;
if (d0 & 0x8000) {
d0 &= ~0x8000;
loop = false;
}
const int d1 = _res->getBankDataSize(d0);
const uint8_t *a6 = _res->findBankData(d0);
if (!a6) {
a6 = _res->loadBankData(d0);
}
const int d3 = *a1++;
if (d3 == 255) {
assert(sz + d1 < kTempMbkSize * 32);
memcpy(buf + sz, a6, d1);
sz += d1;
} else {
for (int i = 0; i < d3 + 1; ++i) {
const int d4 = *a1++;
assert(sz + 32 < kTempMbkSize * 32);
memcpy(buf + sz, a6 + d4 * 32, 32);
sz += 32;
}
}
}
memset(_frontLayer, 0, Video::GAMESCREEN_W * Video::GAMESCREEN_H);
if (tmp[1] != 0) {
assert(_res->_sgd);
AMIGA_decodeSgd(_frontLayer, tmp + offset10, _res->_sgd);
offset10 = 0;
}
AMIGA_decodeLevHelper(_frontLayer, tmp, offset10, offset12, buf, tmp[1] != 0);
memcpy(_backLayer, _frontLayer, Video::GAMESCREEN_W * Video::GAMESCREEN_H);
uint16_t num[4];
for (int i = 0; i < 4; ++i) {
num[i] = READ_BE_UINT16(tmp + 2 + i * 2);
}
_mapPalSlot1 = num[1];
_mapPalSlot2 = num[2];
setPaletteSlotBE(0x0, num[0]);
for (int i = 1; i < 5; ++i) {
setPaletteSlotBE(i, _mapPalSlot2);
}
setPaletteSlotBE(0x6, _mapPalSlot2);
setPaletteSlotBE(0x8, num[0]);
setPaletteSlotBE(0xA, _mapPalSlot2);
}
void Video::AMIGA_decodeSpm(const uint8_t *src, uint8_t *dst) {
uint8_t buf[256 * 32];
const int size = READ_BE_UINT16(src + 3) & 0x7FFF;
assert(size <= (int)sizeof(buf));
AMIGA_decodeRLE(buf, src + 3);
const int w = (src[2] >> 7) + 1;
const int h = src[2] & 0x7F;
AMIGA_blit3pNxN(dst, w * 16, w, h, buf);
}
void Video::AMIGA_decodeIcn(const uint8_t *src, int num, uint8_t *dst) {
for (int i = 0; i < num; ++i) {
const int h = 1 + *src++;
const int w = 1 + *src++;
const int size = w * h * 8;
src += 4 + size;
}
const int h = 1 + *src++;
const int w = 1 + *src++;
AMIGA_blit4p16xN(dst, w, h, src + 4);
}
void Video::AMIGA_decodeSpc(const uint8_t *src, int w, int h, uint8_t *dst) {
switch (w) {
case 8:
AMIGA_blit4p8xN(dst, w, h, src);
break;
default:
AMIGA_blit4pNxN(dst, w, h, src);
break;
}
}
void Video::drawSpriteSub1(const uint8_t *src, uint8_t *dst, int pitch, int h, int w, uint8_t colMask) {
debug(DBG_VIDEO, "Video::drawSpriteSub1(0x%X, 0x%X, 0x%X, 0x%X)", pitch, w, h, colMask);
while (h--) {
for (int i = 0; i < w; ++i) {
if (src[i] != 0) {
dst[i] = src[i] | colMask;
}
}
src += pitch;
dst += 256;
}
}
void Video::drawSpriteSub2(const uint8_t *src, uint8_t *dst, int pitch, int h, int w, uint8_t colMask) {
debug(DBG_VIDEO, "Video::drawSpriteSub2(0x%X, 0x%X, 0x%X, 0x%X)", pitch, w, h, colMask);
while (h--) {
for (int i = 0; i < w; ++i) {
if (src[-i] != 0) {
dst[i] = src[-i] | colMask;
}
}
src += pitch;
dst += 256;
}
}
void Video::drawSpriteSub3(const uint8_t *src, uint8_t *dst, int pitch, int h, int w, uint8_t colMask) {
debug(DBG_VIDEO, "Video::drawSpriteSub3(0x%X, 0x%X, 0x%X, 0x%X)", pitch, w, h, colMask);
while (h--) {
for (int i = 0; i < w; ++i) {
if (src[i] != 0 && !(dst[i] & 0x80)) {
dst[i] = src[i] | colMask;
}
}
src += pitch;
dst += 256;
}
}
void Video::drawSpriteSub4(const uint8_t *src, uint8_t *dst, int pitch, int h, int w, uint8_t colMask) {
debug(DBG_VIDEO, "Video::drawSpriteSub4(0x%X, 0x%X, 0x%X, 0x%X)", pitch, w, h, colMask);
while (h--) {
for (int i = 0; i < w; ++i) {
if (src[-i] != 0 && !(dst[i] & 0x80)) {
dst[i] = src[-i] | colMask;
}
}
src += pitch;
dst += 256;
}
}
void Video::drawSpriteSub5(const uint8_t *src, uint8_t *dst, int pitch, int h, int w, uint8_t colMask) {
debug(DBG_VIDEO, "Video::drawSpriteSub5(0x%X, 0x%X, 0x%X, 0x%X)", pitch, w, h, colMask);
while (h--) {
for (int i = 0; i < w; ++i) {
if (src[i * pitch] != 0 && !(dst[i] & 0x80)) {
dst[i] = src[i * pitch] | colMask;
}
}
++src;
dst += 256;
}
}
void Video::drawSpriteSub6(const uint8_t *src, uint8_t *dst, int pitch, int h, int w, uint8_t colMask) {
debug(DBG_VIDEO, "Video::drawSpriteSub6(0x%X, 0x%X, 0x%X, 0x%X)", pitch, w, h, colMask);
while (h--) {
for (int i = 0; i < w; ++i) {
if (src[-i * pitch] != 0 && !(dst[i] & 0x80)) {
dst[i] = src[-i * pitch] | colMask;
}
}
++src;
dst += 256;
}
}
void Video::PC_drawChar(uint8_t c, int16_t y, int16_t x) {
debug(DBG_VIDEO, "Video::PC_drawChar(0x%X, %d, %d)", c, y, x);
y *= 8;
x *= 8;
const uint8_t *src = _res->_fnt + (c - 32) * 32;
uint8_t *dst = _frontLayer + x + 256 * y;
for (int h = 0; h < 8; ++h) {
for (int i = 0; i < 4; ++i) {
uint8_t c1 = (*src & 0xF0) >> 4;
uint8_t c2 = (*src & 0x0F) >> 0;
++src;
if (c1 != 0) {
if (c1 != 2) {
*dst = _charFrontColor;
} else {
*dst = _charShadowColor;
}
} else if (_charTransparentColor != 0xFF) {
*dst = _charTransparentColor;
}
++dst;
if (c2 != 0) {
if (c2 != 2) {
*dst = _charFrontColor;
} else {
*dst = _charShadowColor;
}
} else if (_charTransparentColor != 0xFF) {
*dst = _charTransparentColor;
}
++dst;
}
dst += 256 - 8;
}
}
void Video::AMIGA_drawStringChar(uint8_t *dst, int pitch, const uint8_t *src, uint8_t color, uint8_t chr) {
assert(chr >= 32);
AMIGA_decodeIcn(src, chr - 32, _res->_memBuf);
src = _res->_memBuf;
for (int y = 0; y < 8; ++y) {
for (int x = 0; x < 8; ++x) {
if (src[x] != 0) {
dst[x] = 0x1D;
}
}
src += 16;
dst += pitch;
}
}
void Video::PC_drawStringChar(uint8_t *dst, int pitch, const uint8_t *src, uint8_t color, uint8_t chr) {
assert(chr >= 32);
src += (chr - 32) * 8 * 4;
for (int y = 0; y < 8; ++y) {
for (int x = 0; x < 4; ++x) {
const uint8_t c1 = src[x] >> 4;
if (c1 != 0) {
*dst = (c1 == 15) ? color : (0xE0 + c1);
}
++dst;
const uint8_t c2 = src[x] & 15;
if (c2 != 0) {
*dst = (c2 == 15) ? color : (0xE0 + c2);
}
++dst;
}
src += 4;
dst += pitch - CHAR_W;
}
}
const char *Video::drawString(const char *str, int16_t x, int16_t y, uint8_t col) {
debug(DBG_VIDEO, "Video::drawString('%s', %d, %d, 0x%X)", str, x, y, col);
void (Video::*drawCharFunc)(uint8_t *, int, const uint8_t *, uint8_t, uint8_t) = 0;
switch (_res->_type) {
case kResourceTypeAmiga:
drawCharFunc = &Video::AMIGA_drawStringChar;
break;
case kResourceTypePC:
drawCharFunc = &Video::PC_drawStringChar;
break;
}
int len = 0;
uint8_t *dst = _frontLayer + y * 256 + x;
while (1) {
const uint8_t c = *str++;
if (c == 0 || c == 0xB || c == 0xA) {
break;
}
(this->*drawCharFunc)(dst, 256, _res->_fnt, col, c);
dst += CHAR_W;
++len;
}
markBlockAsDirty(x, y, len * 8, 8);
return str - 1;
}

90
video.h Normal file
View File

@ -0,0 +1,90 @@
/* REminiscence - Flashback interpreter
* Copyright (C) 2005-2015 Gregory Montoir
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef VIDEO_H__
#define VIDEO_H__
#include "intern.h"
struct Resource;
struct SystemStub;
struct Video {
enum {
GAMESCREEN_W = 256,
GAMESCREEN_H = 224,
SCREENBLOCK_W = 8,
SCREENBLOCK_H = 8,
CHAR_W = 8,
CHAR_H = 8
};
static const uint8_t _conradPal1[];
static const uint8_t _conradPal2[];
static const uint8_t _textPal[];
static const uint8_t _palSlot0xF[];
Resource *_res;
SystemStub *_stub;
uint8_t *_frontLayer;
uint8_t *_backLayer;
uint8_t *_tempLayer;
uint8_t *_tempLayer2;
uint8_t _unkPalSlot1, _unkPalSlot2;
uint8_t _mapPalSlot1, _mapPalSlot2, _mapPalSlot3, _mapPalSlot4;
uint8_t _charFrontColor;
uint8_t _charTransparentColor;
uint8_t _charShadowColor;
uint8_t *_screenBlocks;
bool _fullRefresh;
uint8_t _shakeOffset;
Video(Resource *res, SystemStub *stub);
~Video();
void markBlockAsDirty(int16_t x, int16_t y, uint16_t w, uint16_t h);
void updateScreen();
void fullRefresh();
void fadeOut();
void fadeOutPalette();
void setPaletteColorBE(int num, int offset);
void setPaletteSlotBE(int palSlot, int palNum);
void setPaletteSlotLE(int palSlot, const uint8_t *palData);
void setTextPalette();
void setPalette0xF();
void PC_decodeMap(int level, int room);
void PC_setLevelPalettes();
void PC_decodeIcn(const uint8_t *src, int num, uint8_t *dst);
void PC_decodeSpc(const uint8_t *src, int w, int h, uint8_t *dst);
void AMIGA_decodeLev(int level, int room);
void AMIGA_decodeSpm(const uint8_t *src, uint8_t *dst);
void AMIGA_decodeIcn(const uint8_t *src, int num, uint8_t *dst);
void AMIGA_decodeSpc(const uint8_t *src, int w, int h, uint8_t *dst);
void drawSpriteSub1(const uint8_t *src, uint8_t *dst, int pitch, int h, int w, uint8_t colMask);
void drawSpriteSub2(const uint8_t *src, uint8_t *dst, int pitch, int h, int w, uint8_t colMask);
void drawSpriteSub3(const uint8_t *src, uint8_t *dst, int pitch, int h, int w, uint8_t colMask);
void drawSpriteSub4(const uint8_t *src, uint8_t *dst, int pitch, int h, int w, uint8_t colMask);
void drawSpriteSub5(const uint8_t *src, uint8_t *dst, int pitch, int h, int w, uint8_t colMask);
void drawSpriteSub6(const uint8_t *src, uint8_t *dst, int pitch, int h, int w, uint8_t colMask);
void PC_drawChar(uint8_t c, int16_t y, int16_t x);
void PC_drawStringChar(uint8_t *dst, int pitch, const uint8_t *src, uint8_t color, uint8_t chr);
void AMIGA_drawStringChar(uint8_t *dst, int pitch, const uint8_t *src, uint8_t color, uint8_t chr);
const char *drawString(const char *str, int16_t x, int16_t y, uint8_t col);
};
#endif // VIDEO_H__