Import 0.2.2
This commit is contained in:
parent
57f515ca56
commit
f10e912082
|
@ -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)
|
|
@ -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
|
|
@ -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;
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -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__
|
|
@ -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);
|
||||
}
|
|
@ -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__
|
|
@ -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;
|
||||
}
|
|
@ -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__
|
||||
|
|
@ -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__
|
|
@ -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);
|
||||
}
|
|
@ -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__
|
|
@ -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__
|
|
@ -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;
|
||||
}
|
|
@ -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__
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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__
|
|
@ -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);
|
||||
}
|
|
@ -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__
|
|
@ -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);
|
||||
}
|
|
@ -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__
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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__
|
||||
|
File diff suppressed because it is too large
Load Diff
|
@ -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__
|
|
@ -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 }
|
||||
};
|
||||
|
|
@ -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__
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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__
|
||||
|
|
@ -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);
|
||||
}
|
|
@ -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__
|
File diff suppressed because it is too large
Load Diff
|
@ -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__
|
|
@ -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];
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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__
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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__
|
|
@ -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;
|
||||
}
|
|
@ -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__
|
Loading…
Reference in New Issue