Compare commits

...

8 Commits
0.4.4 ... main

Author SHA1 Message Date
George Sokianos 64f00204e7 Changes in the code to work on AmigaOS4 and release files added 2023-09-17 17:53:43 +01:00
Gregory Montoir 100218a3c2 Import 0.5.1 2023-04-15 08:44:11 +08:00
Gregory Montoir 1f86fdea2d Import 0.5.0 2023-03-31 21:23:51 +08:00
Gregory Montoir 419cf91dfe Import 0.4.9 2021-09-06 00:00:00 +08:00
Gregory Montoir 315bb9bcff Import 0.4.8 2021-05-28 00:00:00 +08:00
Gregory Montoir bc1337da63 Import 0.4.7 2021-05-02 00:00:00 +08:00
Gregory Montoir 2dc61ca627 Import 0.4.6 2019-12-29 00:00:00 +08:00
Gregory Montoir 86baaa3a9a Import 0.4.5 2019-10-28 00:00:00 +08:00
72 changed files with 10917 additions and 1530 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
*.o
*.d
*.lha
rs

168
CHANGES.txt Normal file
View File

@ -0,0 +1,168 @@
* release 0.5.1
- added looping for DOS .prf music
- changed audio mixer to stereo
- updated timings for cutscenes
* release 0.5.0
- added CD-i widescreen mode (flashp*bob)
- added support for DOS .prf music (Adlib, MT32)
* release 0.4.9
- added option to match original inventory items order
- added zoom from DOS version
- added Sega CD tracks playback based on stb_vorbis (OGG 22khz)
- fixed piege opcode 0x57
- fixed volume of repeating sounds
* release 0.4.8
- added detection for DOS version with .ABA files
- added Macintosh credits
- fixed ESPIONS cutscene timing with Amiga music
* release 0.4.7
- added detection for Macintosh CD version
- restored some content from MEMO cutscene
* release 0.4.6
- added rewind to automatic saves
- fixed passwords and protection codes input
* release 0.4.5
- added low-pass filtering for in-game music
- added support for 3DO background music (tunes/*.Cpc)
- added protection screen for DOS SSI version
- fixed ASC and CARTE cutscenes enablement
- fixed loudness of sound effects and in-game music
* release 0.4.4
- added auto-save
- fixed graphical glitches with Macintosh data files
* release 0.4.3
- added title and logo screens for Macintosh
- fixed cutscene offsets with Macintosh data files
* release 0.4.2
- added graphic borders (16:9 displays)
- added detection for Macintosh AppleDouble
- added language selection in title screen
- added story texts for Macintosh
* release 0.4.1
- added support for Macintosh sounds playback
- improved graphics scalers performance
- fixed palette glitches with .BNQ DOS data files
* release 0.4.0
- added initial support for Macintosh data files (512x448 resolution)
- added configuration file entries for disabled cutscenes
- fixed screen shaking offset (level 2)
* release 0.3.7
- added 'caillou-f.set' cutscene
- added detection for 'fbdemofr.zip' DOS demo data files
- fixed cutscene text offsets with Japanese language
* release 0.3.6
- added cutscene music looping
- added 'DEMO' to title menu
- fixed potential crash with the 'walk through walls' glitch
* release 0.3.5
- added Japanese in-game texts
- added support for localized .TBN (PC-CD)
* release 0.3.4
- added screenshot for SDL2
- added support for external graphics scalers (xbrz, scale2x)
- fixed keypress detection for fullscreen/windowed
* release 0.3.3
- added configuration for disabling .SEQ playback
- fixed some Amiga palette glitches
- fixed protection screen for Amiga
* release 0.3.2
- added support for DOS demo*.bin files
- added detection for DOS SSI version
- added compilation with SDL2
* release 0.3.1
- added workarounds for cutscene glitches present in the game (espions, voyage)
- fixed DOS credits sequence
- fixed Amiga cutscene captions display
- fixed Amiga 24px sprites
* release 0.3.0
- added configuration file
- added optional playback for disabled cutscenes (asc, metro, serrure)
- added support for DOS demo data file (DEMO_UK.ABA)
- set frame rate to 30Hz
* release 0.2.2
- added support for level background music
- added Italian texts
- fixed PC-CD SEQ cutscenes numbering
- fixed several issues with Amiga data files
* release 0.2.1 (2011/03/15)
- added music playback to PC-CD SEQ cutscenes
- fixed some palette and sprite issues with Amiga data
* release 0.2.0 (2011/03/11)
- added support for PC-CD SEQ cutscenes
- added support for Amiga data files (experimental)
- fixed minor sound glitches
* release 0.1.9 (2007/03/16)
- fixed minor glitches in cutscenes
- fixed several Conrad moves
- fixed fast mode
- made game version autodetection defaulting to English
- added support for SegaCD speech files
* release 0.1.8 (2005/08/31)
- fixed crash in MOD player
- fixed minor glitch with in-game save switches
* release 0.1.7 (2005/08/24)
- improved in-game menu
- added autodetection for language of the game
- added support for Amiga music files
- added support for the protection screen (disabled by default)
* release 0.1.6 (2005/06/05)
- added in-game menu
- fixed remaining graphical glitches
* release 0.1.5 (2005/04/05)
- added spanish and german versions support
- added input keys recording
- reduced memory usage
* release 0.1.4 (2005/04/02)
- added screen shaking (level 2)
- added support for Amiga music (experimental)
- fixed screen refresh after teleportation
- fixed bug in savestate restore
* release 0.1.3 (2005/02/15)
- added dirty blocks rendering
- added save/load states
- fixed issues with level 2 cutscenes (subway maps, mission screens)
- fixed glitch in continue/abort screen
* release 0.1.2 (2005/02/07)
- added sound effects playback
- added support for polygonal cutscenes
- added support for final credits sequence
- fixed instructions screen display in English version
* release 0.1.1 (2005/01/29)
- added missing opcodes, game should now be completable
- added scale2x and scale3x filters
- enabled level switching
- fixed several engine bugs
* release 0.1.0 (2005/01/23)
- first public release

View File

@ -2,29 +2,36 @@
SDL_CFLAGS := `sdl2-config --cflags`
SDL_LIBS := `sdl2-config --libs`
DL_LIBS := -ldl
MODPLUG_LIBS := -lmodplug
TREMOR_LIBS := -lvorbisidec -logg
TREMOR_LIBS := #-lvorbisidec -logg
ZLIB_LIBS := -lz
CXXFLAGS += -Wall -Wpedantic -MMD $(SDL_CFLAGS) -DUSE_MODPLUG -DUSE_STATIC_SCALER -DUSE_TREMOR -DUSE_ZLIB
LIBS = $(SDL_LIBS) $(MODPLUG_LIBS) $(TREMOR_LIBS) $(ZLIB_LIBS)
SRCS = collision.cpp cutscene.cpp decode_mac.cpp dynlib.cpp file.cpp fs.cpp game.cpp graphics.cpp main.cpp \
menu.cpp mixer.cpp mod_player.cpp ogg_player.cpp piege.cpp resource.cpp resource_aba.cpp \
CXXFLAGS += -Wall -Wextra -Wno-unused-parameter -Wpedantic -MMD $(SDL_CFLAGS) -DUSE_MODPLUG -DUSE_STB_VORBIS -DUSE_ZLIB
SRCS = collision.cpp cpc_player.cpp cutscene.cpp decode_mac.cpp file.cpp fs.cpp game.cpp graphics.cpp main.cpp \
menu.cpp midi_parser.cpp mixer.cpp mod_player.cpp ogg_player.cpp \
piege.cpp prf_player.cpp protection.cpp resource.cpp resource_aba.cpp \
resource_mac.cpp scaler.cpp screenshot.cpp seq_player.cpp \
sfx_player.cpp staticres.cpp systemstub_sdl.cpp unpack.cpp util.cpp video.cpp
SCALERS := scalers/scaler_nearest.cpp scalers/scaler_tv2x.cpp scalers/scaler_xbrz.cpp scalers/xbrz/xbrz.cpp
#CXXFLAGS += -DUSE_STATIC_SCALER
#SCALERS := scalers/scaler_nearest.cpp scalers/scaler_tv2x.cpp scalers/scaler_xbr.cpp
OBJS = $(SRCS:.cpp=.o) $(SCALERS:.cpp=.o)
DEPS = $(SRCS:.cpp=.d) $(SCALERS:.cpp=.d)
#CXXFLAGS += -DUSE_MIDI_DRIVER
#MIDIDRIVERS := midi_driver_adlib.cpp midi_driver_mt32.cpp
#MIDI_LIBS := -lmt32emu
LIBS = $(SDL_LIBS) $(DL_LIBS) $(MODPLUG_LIBS) $(TREMOR_LIBS) $(ZLIB_LIBS)
LIBS = $(MIDI_LIBS) $(MODPLUG_LIBS) $(SDL_LIBS) $(TREMOR_LIBS) $(ZLIB_LIBS)
OBJS = $(SRCS:.cpp=.o) $(SCALERS:.cpp=.o) $(MIDIDRIVERS:.cpp=.o)
DEPS = $(SRCS:.cpp=.d) $(SCALERS:.cpp=.d) $(MIDIDRIVERS:.cpp=.d)
rs: $(OBJS)
$(CXX) $(LDFLAGS) -o $@ $(OBJS) $(LIBS)
clean:
rm -f *.o *.d
rm -f $(OBJS) $(DEPS)
-include $(DEPS)

55
Makefile.os4 Normal file
View File

@ -0,0 +1,55 @@
SDL_CFLAGS := -I/sdk/local/newlib/include/SDL2 -DNDEBUG=1
# -DUSE_STATIC_SCALER=1
SDL_LIBS := -lSDL2_mixer -lmikmod -lmodplug -lFLAC -logg \
-lSDL2 -lpthread -athread=native -lstdc++
MODPLUG_LIBS := -lmodplug
TREMOR_LIBS := #-lvorbisidec -logg
ZLIB_LIBS := -lz
LIBS = $(SDL_LIBS) $(MODPLUG_LIBS) $(TREMOR_LIBS) $(ZLIB_LIBS)
CXXFLAGS += -Wall -Wextra -Wno-unused-parameter -Wpedantic -MMD $(SDL_CFLAGS) -DUSE_MODPLUG -DUSE_STB_VORBIS -DUSE_ZLIB
SRCS = collision.cpp cpc_player.cpp cutscene.cpp decode_mac.cpp file.cpp fs.cpp game.cpp graphics.cpp main.cpp \
menu.cpp midi_parser.cpp mixer.cpp mod_player.cpp ogg_player.cpp \
piege.cpp prf_player.cpp protection.cpp resource.cpp resource_aba.cpp \
resource_mac.cpp scaler.cpp screenshot.cpp seq_player.cpp \
sfx_player.cpp staticres.cpp systemstub_sdl.cpp unpack.cpp util.cpp video.cpp
#CXXFLAGS += -DUSE_STATIC_SCALER
#SCALERS := scalers/scaler_nearest.cpp scalers/scaler_tv2x.cpp scalers/scaler_xbr.cpp
#CXXFLAGS += -DUSE_MIDI_DRIVER
#MIDIDRIVERS := midi_driver_adlib.cpp midi_driver_mt32.cpp
#MIDI_LIBS := -lmt32emu
LIBS = $(MIDI_LIBS) $(MODPLUG_LIBS) $(SDL_LIBS) $(TREMOR_LIBS) $(ZLIB_LIBS)
OBJS = $(SRCS:.cpp=.o) $(SCALERS:.cpp=.o) $(MIDIDRIVERS:.cpp=.o)
DEPS = $(SRCS:.cpp=.d) $(SCALERS:.cpp=.d) $(MIDIDRIVERS:.cpp=.d)
rs: $(OBJS)
$(CXX) $(LDFLAGS) -o $@ $(OBJS) $(LIBS)
clean:
rm -f $(OBJS) $(DEPS)
# prepare an archive for the program
release:
@echo "Creating release files..."
@strip rs
@mkdir -p release/
@copy ALL release_files/ release/ QUIET
@mkdir -p release/REminiscence/data
@mkdir -p release/REminiscence/saves
@copy rs "release/REminiscence/"
@copy README.txt "release/REminiscence/"
@copy rs.cfg "release/REminiscence/"
@echo "Creating release archive..."
@lha -aeqr3 a REminiscence.lha release/
@echo "Clean release files..."
@delete release ALL QUIET FORCE
-include $(DEPS)

View File

@ -1,6 +1,6 @@
REminiscence README
Release version: 0.4.4
Release version: 0.5.1
-------------------------------------------------------------------------------
@ -18,65 +18,66 @@ Data Files:
You will need the original files of the PC (DOS or CD), Amiga or Macintosh
release. Support for Amiga and Macintosh is still experimental.
For the Macintosh release, the resource fork must dumped as a file named
For the Macintosh release, the resource fork must be dumped as a file named
'FLASHBACK.BIN' (MacBinary) or 'FLASHBACK.RSRC' (AppleDouble).
To hear music during polygonal cutscenes with the PC version, you need to copy
the music/ directory of the Amiga version or use the .mod fileset from
unexotica [4].
For speech with in-game dialogues, you need to copy the 'VOICE.VCE' file from
the SegaCD version to the DATA directory.
The music/ directory of the Amiga version or the .mod fileset from
unexotica [4] can be used as an alternative to the MIDI tracks from the
DOS version. Simply copy the files to the DATA directory and set the
'use_prf_music' option to false in the configuration file.
Running:
--------
By default, the engine tries to load the game data files from the 'DATA'
directory, as the original game executable did. The savestates are saved in the
current directory.
By default, the engine loads the game data files from the 'DATA' directory,
as the original game executable did. The savestates are saved in the current
directory.
These paths can be changed using command line switches :
These paths can be changed using command line switches:
Usage: rs [OPTIONS]...
--datapath=PATH Path to data files (default 'DATA')
--savepath=PATH Path to save files (default '.')
--levelnum=NUM Level to start from (default '0')
--fullscreen Fullscreen display
--widescreen=MODE 16:9 display
--widescreen=MODE Widescreen display (adjacent,mirror,blur,cdi,none)
--scaler=NAME@X Graphics scaler (default 'scale@3')
--language=LANG Language (fr,en,de,sp,it,jp)
--autosave Save game state automatically
--mididriver=MIDI Driver (adlib, mt32)
The scaler option specifies the algorithm used to smoothen the image in
addition to a scaling factor. External scalers are also supported, the suffix
shall be used as the name. Eg. If you have scaler_xbrz.dll, you can pass
'--scaler xbrz@2' to use that algorithm with a doubled window size (512x448).
The scaler option specifies the algorithm used to smoothen the image and the
scaling factor. External scalers are also supported, the suffix shall be used
as the name. Eg. If you have scaler_xbr.dll, you can pass '--scaler xbr@2'
to use that algorithm with a doubled window size (512x448).
The widescreen option accepts two modes :
'adjacent' : left and right rooms bitmaps will be drawn
'mirror' : the current room bitmap will be drawn mirrored
The widescreen option accepts the modes below:
In-game hotkeys :
adjacent draw left and right rooms bitmap
mirror mirror the current room bitmap
blur blur and stretch the current room bitmap
cdi use bitmaps from the CD-i release ('flashp?.bob' files)
Arrow Keys move Conrad
Enter use the current inventory object
Shift talk / use / run / shoot
Escape display the options
Backspace display the inventory
Alt Enter toggle windowed/fullscreen mode
Alt + and - increase or decrease game screen scaler factor
Alt S write screenshot as .tga
Ctrl S save game state
Ctrl L load game state
Ctrl + and - change game state slot
Function Keys change game screen scaler
In-game keys:
Debug hotkeys :
Ctrl F toggle fast mode
Ctrl I Conrad 'infinite' life
Ctrl B toggle display of updated dirty blocks
Arrow Keys move Conrad
Enter use the current inventory object
Shift talk / use / run / shoot
Escape display options
Backspace / Tab display inventory / skip cutscene
Alt Enter toggle windowed / fullscreen mode
Alt + and - increase or decrease game screen scaler factor
Alt S take screenshot
Ctrl G toggle auto zoom (DOS version only)
Ctrl S save game state
Ctrl L load game state
Ctrl R rewind game state buffer (requires --autosave)
Ctrl + and - change game state slot
Function Keys change game screen scaler
Credits:

View File

@ -1,7 +1,7 @@
/*
* REminiscence - Flashback interpreter
* Copyright (C) 2005-2018 Gregory Montoir (cyx@users.sourceforge.net)
* Copyright (C) 2005-2019 Gregory Montoir (cyx@users.sourceforge.net)
*/
#include "game.h"
@ -42,13 +42,13 @@ void Game::col_clearState() {
void Game::col_preparePiegeState(LivePGE *pge) {
debug(DBG_COL, "Game::col_preparePiegeState() pge_num=%ld", pge - &_pgeLive[0]);
CollisionSlot *ct_slot1, *ct_slot2;
if (pge->init_PGE->unk1C == 0) {
if (pge->init_PGE->collision_data_len == 0) {
pge->collision_slot = 0xFF;
return;
}
int i = 0;
ct_slot1 = 0;
for (int c = 0; c < pge->init_PGE->unk1C; ++c) {
for (int c = 0; c < pge->init_PGE->collision_data_len; ++c) {
ct_slot2 = _col_curSlot;
if (ct_slot2 + 1 > &_col_slots[255])
return;
@ -231,7 +231,7 @@ int16_t Game::col_detectHit(LivePGE *pge, int16_t arg2, int16_t arg4, col_Callba
if (pge_room < 0 || pge_room >= 0x40) {
return 0;
}
int16_t thr = pge->init_PGE->counter_values[0];
int16_t thr = pge->init_PGE->data[0];
if (thr > 0) {
pos_dx = -1;
pos_dy = -1;
@ -332,7 +332,7 @@ int Game::col_detectHitCallback4(LivePGE *pge1, LivePGE *pge2, int16_t unk1, int
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);
pge_sendMessage(pge2->index, pge1->index, unk1);
return 1;
}
}
@ -346,7 +346,7 @@ int Game::col_detectHitCallback5(LivePGE *pge1, LivePGE *pge2, int16_t unk1, int
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);
pge_sendMessage(pge2->index, pge1->index, unk1);
return 1;
}
}
@ -355,33 +355,33 @@ int Game::col_detectHitCallback5(LivePGE *pge1, LivePGE *pge2, int16_t unk1, int
return 0;
}
int Game::col_detectHitCallbackHelper(LivePGE *pge, int16_t groupId) {
int Game::col_detectHitCallbackHelper(LivePGE *pge, int16_t msgNum) {
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
while (pge->obj_type == obj->type && on->num_objects > i) {
if (obj->opcode2 == 0x6B) { // pge_isToggleable
if (obj->opcode_arg2 == 0) {
if (groupId == 1 || groupId == 2) return 0xFFFF;
if (msgNum == 1 || msgNum == 2) return 0xFFFF;
}
if (obj->opcode_arg2 == 1) {
if (groupId == 3 || groupId == 4) return 0xFFFF;
if (msgNum == 3 || msgNum == 4) return 0xFFFF;
}
} else if (obj->opcode2 == 0x22) { // pge_op_isInGroup
if (obj->opcode_arg2 == groupId) return 0xFFFF;
} else if (obj->opcode2 == 0x22) { // pge_hasPiegeSentMessage
if (obj->opcode_arg2 == msgNum) return 0xFFFF;
}
if (obj->opcode1 == 0x6B) { // pge_op_isInGroupSlice
if (obj->opcode1 == 0x6B) { // pge_isToggleable
if (obj->opcode_arg1 == 0) {
if (groupId == 1 || groupId == 2) return 0xFFFF;
if (msgNum == 1 || msgNum == 2) return 0xFFFF;
}
if (obj->opcode_arg1 == 1) {
if (groupId == 3 || groupId == 4) return 0xFFFF;
if (msgNum == 3 || msgNum == 4) return 0xFFFF;
}
} else if (obj->opcode1 == 0x22) { // pge_op_isInGroup
if (obj->opcode_arg1 == groupId) return 0xFFFF;
} else if (obj->opcode1 == 0x22) { // pge_hasPiegeSentMessage
if (obj->opcode_arg1 == msgNum) return 0xFFFF;
}
++obj;
++i;
@ -415,7 +415,7 @@ int Game::col_detectGunHitCallback2(LivePGE *pge1, LivePGE *pge2, int16_t arg4,
}
}
if (col_detectHitCallbackHelper(pge1, id) != 0) {
pge_updateGroup(pge2->index, pge1->index, id);
pge_sendMessage(pge2->index, pge1->index, id);
return 1;
}
}
@ -439,10 +439,9 @@ int Game::col_detectGunHitCallback3(LivePGE *pge1, LivePGE *pge2, int16_t arg4,
}
}
if (col_detectHitCallbackHelper(pge1, id) != 0) {
pge_updateGroup(pge2->index, pge1->index, id);
pge_sendMessage(pge2->index, pge1->index, id);
return 1;
}
}
}
return 0;
@ -453,9 +452,9 @@ int Game::col_detectGunHit(LivePGE *pge, int16_t arg2, int16_t arg4, col_Callbac
if (pge_room < 0 || pge_room >= 0x40) return 0;
int16_t thr, pos_dx, pos_dy;
if (argC == -1) {
thr = pge->init_PGE->counter_values[0];
thr = pge->init_PGE->data[0];
} else {
thr = pge->init_PGE->counter_values[3];
thr = pge->init_PGE->data[3];
}
if (thr > 0) {
pos_dx = -1;

131
cpc_player.cpp Normal file
View File

@ -0,0 +1,131 @@
#include "cpc_player.h"
#include "mixer.h"
#include "util.h"
static const char *_tunes[] = {
"Options.Cpc",
"Jungle.Cpc",
"Subway.Cpc",
"TVshow.Cpc",
"City.Cpc",
"Alien.Cpc"
};
CpcPlayer::CpcPlayer(Mixer *mixer, FileSystem *fs)
: _mix(mixer), _fs(fs) {
}
CpcPlayer::~CpcPlayer() {
}
bool CpcPlayer::playTrack(int num) {
_compression[0] = 0;
const int tuneNum = num - 2;
if (tuneNum >= 0 && tuneNum < ARRAYSIZE(_tunes) && _f.open(_tunes[tuneNum], "rb", _fs)) {
_pos = 0;
_sampleL = _sampleR = 0;
while (nextChunk()) {
if (_compression[0]) {
_restartPos = _nextPos;
_mix->setPremixHook(mixCallback, this);
return true;
}
}
}
return false;
}
void CpcPlayer::stopTrack() {
_f.close();
}
void CpcPlayer::pauseTrack() {
_mix->setPremixHook(0, 0);
}
void CpcPlayer::resumeTrack() {
_mix->setPremixHook(mixCallback, this);
}
bool CpcPlayer::nextChunk() {
bool found = false;
while (!_f.ioErr() && !found) {
const uint32_t pos = _pos;
char tag[4];
_f.read(tag, sizeof(tag));
const uint32_t len = _f.readUint32BE();
_nextPos = pos + len;
if (memcmp(tag, "SNDS", 4) == 0) {
_f.readUint32BE();
_f.readUint32BE();
char type[4];
_f.read(type, sizeof(type));
if (memcmp(type, "SHDR", 4) == 0) {
uint8_t buf[24];
_f.read(buf, sizeof(buf));
const uint32_t rate = _f.readUint32BE();
const uint32_t channels = _f.readUint32BE();
if (channels != 2 || rate != _mix->getSampleRate()) {
warning("Unsupported CPC tune channels %d rate %d", channels, rate);
break;
}
_f.read(_compression, sizeof(_compression) - 1);
_compression[sizeof(_compression) - 1] = 0;
if (strcmp(_compression, "SDX2") != 0) {
warning("Unsupported CPC compression '%s'", _compression);
break;
}
found = true;
} else if (memcmp(type, "SSMP", 4) == 0) {
_samplesLeft = _f.readUint32BE();
found = true;
break;
} else {
warning("Unhandled SNDS chunk '%c%c%c%c'", type[0], type[1], type[2], type[3]);
}
} else if (memcmp(tag, "SHDR", 4) == 0 || memcmp(tag, "FILL", 4) == 0 || memcmp(tag, "CTRL", 4) == 0) {
// ignore
} else {
warning("Unhandled chunk '%c%c%c%c' size %d", tag[0], tag[1], tag[2], tag[3], len);
}
_f.seek(_nextPos);
_pos = _nextPos;
}
return found;
}
static int16_t decodeSDX2(int16_t prev, int8_t data) {
const int sqr = data * ABS(data) * 2;
return (data & 1) != 0 ? prev + sqr : sqr;
}
int8_t CpcPlayer::readSampleData() {
if (_samplesLeft <= 0) {
_f.seek(_nextPos);
_pos = _nextPos;
if (!nextChunk()) {
// rewind
_f.seek(_restartPos);
nextChunk();
_sampleL = _sampleR = 0;
}
}
const int8_t data = _f.readByte();
--_samplesLeft;
return data;
}
bool CpcPlayer::mix(int16_t *buf, int len) {
for (int i = 0; i < len; ++i) {
_sampleL = decodeSDX2(_sampleL, readSampleData());
_sampleR = decodeSDX2(_sampleR, readSampleData());
*buf++ = _sampleL;
*buf++ = _sampleR;
}
return true;
}
bool CpcPlayer::mixCallback(void *param, int16_t *buf, int len) {
return ((CpcPlayer *)param)->mix(buf, len);
}

37
cpc_player.h Normal file
View File

@ -0,0 +1,37 @@
#ifndef CPC_PLAYER_H__
#define CPC_PLAYER_H__
#include "intern.h"
#include "file.h"
struct FileSystem;
struct Mixer;
struct CpcPlayer {
Mixer *_mix;
FileSystem *_fs;
File _f;
uint32_t _pos;
uint32_t _nextPos;
uint32_t _restartPos;
char _compression[5];
int _samplesLeft;
int16_t _sampleL, _sampleR;
CpcPlayer(Mixer *mixer, FileSystem *fs);
~CpcPlayer();
bool playTrack(int num);
void stopTrack();
void pauseTrack();
void resumeTrack();
bool nextChunk();
int8_t readSampleData();
bool mix(int16_t *buf, int len);
static bool mixCallback(void *param, int16_t *buf, int len);
};
#endif

View File

@ -1,7 +1,7 @@
/*
* REminiscence - Flashback interpreter
* Copyright (C) 2005-2018 Gregory Montoir (cyx@users.sourceforge.net)
* Copyright (C) 2005-2019 Gregory Montoir (cyx@users.sourceforge.net)
*/
#include <math.h>
@ -35,15 +35,16 @@ const uint8_t *Cutscene::getPolygonData() const {
return _res->_pol;
}
void Cutscene::sync() {
void Cutscene::sync(int frameDelay) {
if (_stub->_pi.quit) {
return;
}
if (_stub->_pi.dbgMask & PlayerInput::DF_FASTMODE) {
return;
}
static const int frameHz = 60;
const int32_t delay = _stub->getTimeStamp() - _tstamp;
const int32_t pause = _frameDelay * TIMER_SLICE - delay;
const int32_t pause = frameDelay * (1000 / frameHz) - delay;
if (pause > 0) {
_stub->sleep(pause);
}
@ -71,11 +72,11 @@ void Cutscene::updatePalette() {
}
}
void Cutscene::setPalette() {
sync();
void Cutscene::updateScreen() {
sync(_frameDelay - 1);
updatePalette();
SWAP(_page0, _page1);
_stub->copyRect(0, 0, _vid->_w, _vid->_h, _page0, _vid->_w);
SWAP(_frontPage, _backPage);
_stub->copyRect(0, 0, _vid->_w, _vid->_h, _frontPage, _vid->_w);
_stub->updateScreen(0);
}
@ -94,6 +95,15 @@ void Cutscene::setPalette() {
sin(330) table: 221, math:-127
*/
/*
a = rotation angle
b = scale/distort vertically (180)
c = scale/distort horizontally (90)
| x | cos_a sin_a | cos_b | cos_c * sin_b |
| y | sin_a -cos_a | sin_c | 1 |
*/
void Cutscene::setRotationTransform(uint16_t a, uint16_t b, uint16_t c) { // identity a:0 b:180 c:90
const int16_t sin_a = SIN(a);
const int16_t cos_a = COS(a);
@ -101,10 +111,10 @@ void Cutscene::setRotationTransform(uint16_t a, uint16_t b, uint16_t c) { // ide
const int16_t cos_c = COS(c);
const int16_t sin_b = SIN(b);
const int16_t cos_b = COS(b);
_rotMat[0] /* .x1 */ = ((cos_a * cos_b) >> 8) - ((((cos_c * sin_a) >> 8) * sin_b) >> 8);
_rotMat[1] /* .y1 */ = ((sin_a * cos_b) >> 8) + ((((cos_c * cos_a) >> 8) * sin_b) >> 8);
_rotMat[2] /* .x2 */ = ( sin_c * sin_a) >> 8;
_rotMat[3] /* .y2 */ = (-sin_c * cos_a) >> 8;
_rotMat[0] = ((cos_a * cos_b) >> 8) - ((((cos_c * sin_a) >> 8) * sin_b) >> 8);
_rotMat[1] = ((sin_a * cos_b) >> 8) + ((((cos_c * cos_a) >> 8) * sin_b) >> 8);
_rotMat[2] = ( sin_c * sin_a) >> 8;
_rotMat[3] = (-sin_c * cos_a) >> 8;
}
static bool isNewLineChar(uint8_t chr, Resource *res) {
@ -138,10 +148,12 @@ uint16_t Cutscene::findTextSeparators(const uint8_t *p, int len) {
void Cutscene::drawText(int16_t x, int16_t y, const uint8_t *p, uint16_t color, uint8_t *page, int textJustify) {
debug(DBG_CUT, "Cutscene::drawText(x=%d, y=%d, c=%d, justify=%d)", x, y, color, textJustify);
int len = 0;
if (_res->_type == kResourceTypeMac) {
if (p != _textBuf && _res->isMac()) {
len = *p++;
} else {
len = strlen((const char *)p);
while (p[len] != 0xA && p[len]) {
++len;
}
}
Video::drawCharFunc dcf = _vid->_drawChar;
const uint8_t *fnt = (_res->_lang == LANG_JP) ? Video::_font8Jp : _res->_fnt;
@ -178,52 +190,78 @@ void Cutscene::drawText(int16_t x, int16_t y, const uint8_t *p, uint16_t color,
}
}
void Cutscene::swapLayers() {
void Cutscene::clearBackPage() {
if (_clearScreen == 0) {
memcpy(_page1, _pageC, _vid->_layerSize);
memcpy(_backPage, _auxPage, _vid->_layerSize);
} else {
memset(_page1, 0xC0, _vid->_layerSize);
memset(_backPage, 0xC0, _vid->_layerSize);
}
}
void Cutscene::drawCreditsText() {
if (_creditsSequence) {
if (_textUnk2 != 0) {
if (_varText == 0) {
_textUnk2 = 0;
} else {
if (_creditsKeepText) {
if (_creditsSlowText) {
return;
}
_creditsKeepText = false;
}
if (_creditsTextCounter <= 0) {
uint8_t code;
const bool isMac = _res->isMac();
if (isMac && _creditsTextLen <= 0) {
const uint8_t *p = _res->getCreditsString(_creditsTextIndex++);
if (!p) {
return;
}
_creditsTextCounter = 60;
_creditsTextPosX = p[0];
_creditsTextPosY = p[1];
_creditsTextLen = p[2];
_textCurPtr = p + 2;
code = 0;
} else {
code = *_textCurPtr;
}
if (_creditsTextCounter <= 0) {
uint8_t code = *_textCurPtr;
if (code == 0xFF) {
_textBuf[0] = 0xA;
} else if (code == 0xFE) {
++_textCurPtr;
code = *_textCurPtr++;
_creditsTextCounter = code;
} else if (code == 1) {
++_textCurPtr;
_creditsTextPosX = *_textCurPtr++;
_creditsTextPosY = *_textCurPtr++;
} else if (code == 0) {
_textCurBuf = _textBuf;
_textBuf[0] = 0xA;
++_textCurPtr;
if (_varText != 0) {
_textUnk2 = 0xFF;
}
} else {
*_textCurBuf++ = code;
*_textCurBuf = 0xA;
++_textCurPtr;
if (code == 0x7D && isMac) {
++_textCurPtr;
code = *_textCurPtr++;
_creditsTextLen -= 2;
assert(code > 0x30);
for (int i = 0; i < (code - 0x30); ++i) {
*_textCurBuf++ = ' ';
}
*_textCurBuf = 0xA;
} else if (code == 0xFF) {
_textBuf[0] = 0xA;
} else if (code == 0xFE) {
++_textCurPtr;
_creditsTextCounter = *_textCurPtr++;
} else if (code == 1) {
++_textCurPtr;
_creditsTextPosX = *_textCurPtr++;
_creditsTextPosY = *_textCurPtr++;
} else if (code == 0) {
_textCurBuf = _textBuf;
_textBuf[0] = 0xA;
++_textCurPtr;
if (_creditsSlowText) {
_creditsKeepText = true;
}
} else {
_creditsTextCounter -= 10;
*_textCurBuf++ = code;
*_textCurBuf = 0xA;
++_textCurPtr;
if (isMac) {
--_creditsTextLen;
if (_creditsTextLen == 0) {
_creditsTextCounter = 600;
}
}
}
drawText((_creditsTextPosX - 1) * 8, _creditsTextPosY * 8, _textBuf, 0xEF, _page1, kTextJustifyLeft);
} else {
_creditsTextCounter -= 10;
}
drawText((_creditsTextPosX - 1) * 8, _creditsTextPosY * 8, _textBuf, 0xEF, _backPage, kTextJustifyLeft);
}
void Cutscene::drawProtectionShape(uint8_t shapeNum, int16_t zoom) {
@ -235,7 +273,6 @@ void Cutscene::drawProtectionShape(uint8_t shapeNum, int16_t zoom) {
int16_t x = 0;
int16_t y = 0;
zoom += 512;
setRotationTransform(0, 180, 90);
const uint8_t *shapeOffsetTable = _protectionShapeData + READ_BE_UINT16(_protectionShapeData + 0x02);
const uint8_t *shapeDataTable = _protectionShapeData + READ_BE_UINT16(_protectionShapeData + 0x0E);
@ -257,7 +294,7 @@ void Cutscene::drawProtectionShape(uint8_t shapeNum, int16_t zoom) {
}
_hasAlphaColor = (verticesOffset & 0x4000) != 0;
_primitiveColor = 0xC0 + *shapeData++;
drawShapeScaleRotate(p, zoom, dx, dy, x, y, 0, 0);
drawShapeScale(p, zoom, dx, dy, x, y, 0, 0);
++_shape_count;
}
}
@ -265,19 +302,27 @@ void Cutscene::drawProtectionShape(uint8_t shapeNum, int16_t zoom) {
void Cutscene::op_markCurPos() {
debug(DBG_CUT, "Cutscene::op_markCurPos()");
_cmdPtrBak = _cmdPtr;
drawCreditsText();
_frameDelay = 5;
setPalette();
swapLayers();
_varText = 0;
if (!_creditsSequence) {
if (_id == kCineDebut) {
_frameDelay = 7;
} else if (_id == kCineChute) {
_frameDelay = 6;
}
} else {
drawCreditsText();
}
updateScreen();
clearBackPage();
_creditsSlowText = false;
}
void Cutscene::op_refreshScreen() {
debug(DBG_CUT, "Cutscene::op_refreshScreen()");
_clearScreen = fetchNextCmdByte();
if (_clearScreen != 0) {
swapLayers();
_varText = 0;
clearBackPage();
_creditsSlowText = false;
}
}
@ -286,26 +331,26 @@ void Cutscene::op_waitForSync() {
if (_creditsSequence) {
uint16_t n = fetchNextCmdByte() * 2;
do {
_varText = 0xFF;
_creditsSlowText = true;
_frameDelay = 3;
if (_textBuf == _textCurBuf) {
_creditsTextCounter = _res->isAmiga() ? 60 : 20;
_creditsTextCounter = _res->isDOS() ? 20 : 60;
}
memcpy(_page1, _page0, _vid->_layerSize);
memcpy(_backPage, _frontPage, _vid->_layerSize);
drawCreditsText();
setPalette();
updateScreen();
} while (--n);
swapLayers();
_varText = 0;
clearBackPage();
_creditsSlowText = false;
} else {
_frameDelay = fetchNextCmdByte() * 4;
sync(); // XXX handle input
sync(_frameDelay);
}
}
void Cutscene::drawShape(const uint8_t *data, int16_t x, int16_t y) {
debug(DBG_CUT, "Cutscene::drawShape()");
_gfx.setLayer(_page1, _vid->_w);
_gfx.setLayer(_backPage, _vid->_w);
uint8_t numVertices = *data++;
if (numVertices & 0x80) {
Point pt;
@ -386,10 +431,12 @@ void Cutscene::op_drawShape() {
drawShape(primitiveVertices, x + dx, y + dy);
}
if (_clearScreen != 0) {
memcpy(_pageC, _page1, _vid->_layerSize);
memcpy(_auxPage, _backPage, _vid->_layerSize);
}
}
static int _paletteNum = -1;
void Cutscene::op_setPalette() {
debug(DBG_CUT, "Cutscene::op_setPalette()");
uint8_t num = fetchNextCmdByte();
@ -401,34 +448,30 @@ void Cutscene::op_setPalette() {
_palBuf[0x20] = 0x0F;
_palBuf[0x21] = 0xFF;
}
_paletteNum = num;
}
void Cutscene::op_drawStringAtBottom() {
debug(DBG_CUT, "Cutscene::op_drawStringAtBottom()");
void Cutscene::op_drawCaptionText() {
debug(DBG_CUT, "Cutscene::op_drawCaptionText()");
uint16_t strId = fetchNextCmdWord();
if (!_creditsSequence) {
// 'espions' - ignore last call, allows caption to be displayed longer on the screen
if (_id == 0x39 && strId == 0xFFFF) {
if ((_res->isDOS() && (_cmdPtr - _cmdPtrBak) == 0x10) || (_res->isAmiga() && (_cmdPtr - getCommandData()) == 0x9F3)) {
_frameDelay = 100;
setPalette();
return;
}
}
const int h = 45 * _vid->_layerScale;
const int y = Video::GAMESCREEN_H * _vid->_layerScale - h;
memset(_pageC + y * _vid->_w, 0xC0, h * _vid->_w);
memset(_page1 + y * _vid->_w, 0xC0, h * _vid->_w);
memset(_page0 + y * _vid->_w, 0xC0, h * _vid->_w);
memset(_auxPage + y * _vid->_w, 0xC0, h * _vid->_w);
memset(_backPage + y * _vid->_w, 0xC0, h * _vid->_w);
memset(_frontPage + y * _vid->_w, 0xC0, h * _vid->_w);
if (strId != 0xFFFF) {
const uint8_t *str = _res->getCineString(strId);
if (str) {
drawText(0, 129, str, 0xEF, _page1, kTextJustifyAlign);
drawText(0, 129, str, 0xEF, _pageC, kTextJustifyAlign);
drawText(0, 129, str, 0xEF, _backPage, kTextJustifyAlign);
drawText(0, 129, str, 0xEF, _auxPage, kTextJustifyAlign);
}
} else if (_id == kCineEspions) {
// cutscene relies on drawCaptionText opcodes for timing
_frameDelay = 100;
sync(_frameDelay);
}
}
}
@ -445,15 +488,15 @@ void Cutscene::op_skip3() {
void Cutscene::op_refreshAll() {
debug(DBG_CUT, "Cutscene::op_refreshAll()");
_frameDelay = 5;
setPalette();
swapLayers();
_varText = 0xFF;
updateScreen();
clearBackPage();
_creditsSlowText = true;
op_handleKeys();
}
void Cutscene::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) {
debug(DBG_CUT, "Cutscene::drawShapeScale(%d, %d, %d, %d, %d, %d, %d)", zoom, b, c, d, e, f, g);
_gfx.setLayer(_page1, _vid->_w);
_gfx.setLayer(_backPage, _vid->_w);
uint8_t numVertices = *data++;
if (numVertices & 0x80) {
int16_t x, y;
@ -624,7 +667,7 @@ void Cutscene::op_drawShapeScale() {
_hasAlphaColor = (verticesOffset & 0x4000) != 0;
uint8_t color = *shapeData++;
if (_clearScreen == 0) {
color += 0x10; // 2nd pal buf
color += 0x10; // 2nd palette buffer
}
_primitiveColor = 0xC0 + color;
drawShapeScale(p, zoom, dx, dy, x, y, 0, 0);
@ -635,7 +678,7 @@ void Cutscene::op_drawShapeScale() {
void Cutscene::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) {
debug(DBG_CUT, "Cutscene::drawShapeScaleRotate(%d, %d, %d, %d, %d, %d, %d)", zoom, b, c, d, e, f, g);
_gfx.setLayer(_page1, _vid->_w);
_gfx.setLayer(_backPage, _vid->_w);
uint8_t numVertices = *data++;
if (numVertices & 0x80) {
int16_t x, y, ix, iy;
@ -717,7 +760,7 @@ void Cutscene::drawShapeScaleRotate(const uint8_t *data, int16_t zoom, int16_t b
_gfx.drawPoint(_primitiveColor, &pt);
} else {
int16_t x, y, a, shape_last_x, shape_last_y;
Point tempVertices[40];
Point tempVertices[MAX_VERTICES];
_shape_cur_x = b + READ_BE_UINT16(data); data += 2;
x = _shape_cur_x;
_shape_cur_y = c + READ_BE_UINT16(data); data += 2;
@ -849,7 +892,7 @@ void Cutscene::op_drawShapeScaleRotate() {
_hasAlphaColor = (verticesOffset & 0x4000) != 0;
uint8_t color = *shapeData++;
if (_clearScreen == 0) {
color += 0x10; // 2nd pal buf
color += 0x10; // 2nd palette buffer
}
_primitiveColor = 0xC0 + color;
drawShapeScaleRotate(p, zoom, dx, dy, x, y, 0, 0);
@ -857,19 +900,86 @@ void Cutscene::op_drawShapeScaleRotate() {
}
}
void Cutscene::op_drawCreditsText() {
debug(DBG_CUT, "Cutscene::op_drawCreditsText()");
_varText = 0xFF;
static const uint16_t memoSetPos[] = {
2, 0xffca, 0x0010, 2, 0xffcb, 0x000f, 2, 0xffcd, 0x000e, 2, 0xffd0, 0x000d, 2, 0xffd3, 0x000c, 2, 0xffd7, 0x000b,
2, 0xffd9, 0x000a, 2, 0xffdb, 0x0009, 2, 0xffdd, 0x0008, 2, 0xffdd, 0x0008, 2, 0xffdd, 0x0008, 2, 0xffdd, 0x0008,
2, 0xffdd, 0x0008, 2, 0xffdd, 0x0008, 2, 0xffdd, 0x0008, 2, 0xffdd, 0x0008, 4, 0xffe2, 0xfffe, 2, 0xffdd, 0x0008,
4, 0xffe2, 0xfffe, 2, 0xffdd, 0x0008, 4, 0xffe2, 0xfffe, 2, 0xffdd, 0x0008, 4, 0xffe2, 0xfffe, 2, 0xffdd, 0x0008,
2, 0xffdd, 0x0008, 2, 0xffdd, 0x0008, 2, 0xffdd, 0x0008, 2, 0xffdd, 0x0008, 2, 0xffdd, 0x0008, 2, 0xffdd, 0x0008,
2, 0xffdc, 0x0008, 2, 0xffda, 0x0008, 2, 0xffd6, 0x0009, 2, 0xffd2, 0x000b, 2, 0xffce, 0x000e, 2, 0xffc9, 0x0010,
2, 0xffc7, 0x0012, 2, 0xffc8, 0x0013, 2, 0xffca, 0x0015, 2, 0xffce, 0x0014, 2, 0xffd1, 0x0013, 2, 0xffd4, 0x0012,
2, 0xffd6, 0x0011, 2, 0xffd8, 0x0011, 2, 0xffd8, 0x0011, 2, 0xffd8, 0x0011, 2, 0xffd8, 0x0011, 2, 0xffd8, 0x0011,
2, 0xffd8, 0x0011, 2, 0xffd8, 0x0011, 4, 0xffdc, 0x0009, 2, 0xffd8, 0x0011, 4, 0xffdc, 0x0009, 2, 0xffd8, 0x0011,
4, 0xffdc, 0x0009, 2, 0xffd8, 0x0011, 2, 0xffd8, 0x0011, 2, 0xffd8, 0x0011, 2, 0xffd8, 0x0011, 2, 0xffd8, 0x0011,
2, 0xffd8, 0x0011, 2, 0xffd7, 0x0011, 2, 0xffd6, 0x0011, 2, 0xffd3, 0x0011, 2, 0xffcd, 0x0012, 2, 0xffc7, 0x0014,
2, 0xffc1, 0x0016
};
static bool _drawMemoSetShapes;
static uint32_t _memoSetOffset;
static void readSetPalette(const uint8_t *p, uint16_t offset, uint16_t *palette);
static int findSetPaletteColor(const uint16_t color, const uint16_t *paletteBuffer) {
int index = -1;
int currentSum = 0;
for (int l = 0; l < 32; ++l) {
if (color == paletteBuffer[l]) {
return l;
}
const int dr = ((color >> 8) & 15) - ((paletteBuffer[l] >> 8) & 15);
const int dg = ((color >> 4) & 15) - ((paletteBuffer[l] >> 4) & 15);
const int db = (color & 15) - (paletteBuffer[l] & 15);
const int sum = dr * dr + dg * dg + db * db;
if (index < 0 || sum < currentSum) {
currentSum = sum;
index = l;
}
}
return index;
}
void Cutscene::op_copyScreen() {
debug(DBG_CUT, "Cutscene::op_copyScreen()");
_creditsSlowText = true;
if (_textCurBuf == _textBuf) {
++_creditsTextCounter;
}
memcpy(_page1, _page0, _vid->_layerSize);
memcpy(_backPage, _frontPage, _vid->_layerSize);
_frameDelay = 10;
setPalette();
const bool drawMemoShapes = _drawMemoSetShapes && (_paletteNum == 19 || _paletteNum == 23) && (_memoSetOffset + 3) <= sizeof(memoSetPos);
if (drawMemoShapes) {
uint16_t paletteBuffer[32];
for (int i = 0; i < 32; ++i) {
paletteBuffer[i] = READ_BE_UINT16(_palBuf + i * 2);
}
uint16_t tempPalette[16];
readSetPalette(_memoSetShape2Data, 0x462, tempPalette);
uint8_t paletteLut[32];
for (int k = 0; k < 16; ++k) {
const int index = findSetPaletteColor(tempPalette[k], paletteBuffer);
paletteLut[k] = 0xC0 + index;
}
_gfx.setLayer(_backPage, _vid->_w);
drawSetShape(_memoSetShape2Data, 0, (int16_t)memoSetPos[_memoSetOffset + 1], (int16_t)memoSetPos[_memoSetOffset + 2], paletteLut);
_memoSetOffset += 3;
if (memoSetPos[_memoSetOffset] == 4) {
drawSetShape(_memoSetShape4Data, 0, (int16_t)memoSetPos[_memoSetOffset + 1], (int16_t)memoSetPos[_memoSetOffset + 2], paletteLut);
_memoSetOffset += 3;
}
}
updateScreen();
if (drawMemoShapes) {
SWAP(_frontPage, _backPage);
}
}
void Cutscene::op_drawStringAtPos() {
debug(DBG_CUT, "Cutscene::op_drawStringAtPos()");
void Cutscene::op_drawTextAtPos() {
debug(DBG_CUT, "Cutscene::op_drawTextAtPos()");
uint16_t strId = fetchNextCmdWord();
if (strId != 0xFFFF) {
int16_t x = (int8_t)fetchNextCmdByte() * 8;
@ -877,13 +987,13 @@ void Cutscene::op_drawStringAtPos() {
if (!_creditsSequence) {
const uint8_t *str = _res->getCineString(strId & 0xFFF);
if (str) {
uint8_t color = 0xD0 + (strId >> 0xC);
drawText(x, y, str, color, _page1, kTextJustifyCenter);
const uint8_t color = 0xD0 + (strId >> 0xC);
drawText(x, y, str, color, _backPage, kTextJustifyCenter);
}
// 'voyage' - cutscene script redraws the string to refresh the screen
if (_id == 0x34 && (strId & 0xFFF) == 0x45) {
if (_id == kCineVoyage && (strId & 0xFFF) == 0x45) {
if ((_cmdPtr - _cmdPtrBak) == 0xA) {
_stub->copyRect(0, 0, _vid->_w, _vid->_h, _page1, _vid->_w);
_stub->copyRect(0, 0, _vid->_w, _vid->_h, _backPage, _vid->_w);
_stub->updateScreen(0);
} else {
_stub->sleep(15);
@ -988,6 +1098,10 @@ void Cutscene::mainLoop(uint16_t num) {
_polPtr = getPolygonData();
debug(DBG_CUT, "_baseOffset = %d offset = %d", _baseOffset, offset);
_paletteNum = -1;
_drawMemoSetShapes = (_id == kCineMemo && g_options.restore_memo_cutscene);
_memoSetOffset = 0;
while (!_stub->_pi.quit && !_interrupted && !_stop) {
uint8_t op = fetchNextCmdByte();
debug(DBG_CUT, "Cutscene::play() opcode = 0x%X (%d)", op, (op >> 2));
@ -1009,16 +1123,18 @@ void Cutscene::mainLoop(uint16_t num) {
bool Cutscene::load(uint16_t cutName) {
assert(cutName != 0xFFFF);
const char *name = _namesTable[cutName & 0xFF];
const char *name = _namesTableDOS[cutName & 0xFF];
switch (_res->_type) {
case kResourceTypeAmiga:
if (strncmp(name, "INTRO", 5) == 0) {
if (cutName == 7) {
name = "INTRO";
} else if (cutName == 10) {
name = "SERRURE";
}
_res->load(name, Resource::OT_CMP);
if (_id == 0x39 && _res->_lang != LANG_FR) {
if (_id == kCineEspions) {
//
// 'espions' - '... the power which we need' caption is missing in Amiga English.
// '... the power which we need' caption is missing.
// fixed in DOS version, opcodes order is wrong
//
// opcode 0 pos 0x323
@ -1027,7 +1143,7 @@ bool Cutscene::load(uint16_t cutName) {
//
uint8_t *p = _res->_cmd + 0x322;
if (memcmp(p, "\x00\x18\x00\x3a", 4) == 0) {
p[0] = 0x06 << 2; // op_drawStringAtBottom
p[0] = 0x06 << 2; // op_drawCaptionText
p[1] = 0x00;
p[2] = 0x3a;
p[3] = 0x00; // op_markCurPos
@ -1062,9 +1178,9 @@ void Cutscene::unload() {
}
void Cutscene::prepare() {
_page0 = _vid->_frontLayer;
_page1 = _vid->_tempLayer;
_pageC = _vid->_tempLayer2;
_frontPage = _vid->_frontLayer;
_backPage = _vid->_tempLayer;
_auxPage = _vid->_tempLayer2;
_stub->_pi.dirMask = 0;
_stub->_pi.enter = false;
_stub->_pi.space = false;
@ -1084,15 +1200,17 @@ void Cutscene::prepare() {
void Cutscene::playCredits() {
if (_res->isMac()) {
warning("Cutscene::playCredits() unimplemented");
return;
_res->MAC_loadCreditsText();
_creditsTextIndex = 0;
_creditsTextLen = 0;
} else {
_textCurPtr = _res->isAmiga() ? _creditsDataAmiga : _creditsDataDOS;
}
_textCurPtr = _res->isAmiga() ? _creditsDataAmiga : _creditsDataDOS;
_textBuf[0] = 0xA;
_textCurBuf = _textBuf;
_creditsSequence = true;
_varText = 0;
_textUnk2 = 0;
_creditsSlowText = false;
_creditsKeepText = false;
_creditsTextCounter = 0;
_interrupted = false;
const uint16_t *cut_seq = _creditsCutSeq;
@ -1102,8 +1220,9 @@ void Cutscene::playCredits() {
break;
}
prepare();
uint16_t cutName = _offsetsTable[cut_id * 2 + 0];
uint16_t cutOff = _offsetsTable[cut_id * 2 + 1];
const uint16_t *offsets = _res->isAmiga() ? _offsetsTableAmiga : _offsetsTableDOS;
uint16_t cutName = offsets[cut_id * 2 + 0];
uint16_t cutOff = offsets[cut_id * 2 + 1];
if (load(cutName)) {
mainLoop(cutOff);
unload();
@ -1128,9 +1247,9 @@ void Cutscene::playText(const char *str) {
}
}
const int y = (128 - lines * 8) / 2;
memset(_page1, 0xC0, _vid->_layerSize);
drawText(0, y, (const uint8_t *)str, 0xC1, _page1, kTextJustifyAlign);
_stub->copyRect(0, 0, _vid->_w, _vid->_h, _page1, _vid->_w);
memset(_backPage, 0xC0, _vid->_layerSize);
drawText(0, y, (const uint8_t *)str, 0xC1, _backPage, kTextJustifyAlign);
_stub->copyRect(0, 0, _vid->_w, _vid->_h, _backPage, _vid->_w);
_stub->updateScreen(0);
while (!_stub->_pi.quit) {
@ -1139,7 +1258,7 @@ void Cutscene::playText(const char *str) {
_stub->_pi.backspace = false;
break;
}
_stub->sleep(TIMER_SLICE);
_stub->sleep(30);
}
}
@ -1149,18 +1268,26 @@ void Cutscene::play() {
debug(DBG_CUT, "Cutscene::play() _id=0x%X", _id);
_creditsSequence = false;
prepare();
uint16_t cutName = _offsetsTable[_id * 2 + 0];
uint16_t cutOff = _offsetsTable[_id * 2 + 1];
const uint16_t *offsets = _res->isAmiga() ? _offsetsTableAmiga : _offsetsTableDOS;
uint16_t cutName = offsets[_id * 2 + 0];
uint16_t cutOff = offsets[_id * 2 + 1];
if (cutName == 0xFFFF) {
switch (_id) {
case 3: // keys
if (g_options.play_carte_cutscene) {
cutName = 2; // CARTE
}
break;
case 8: // save checkpoints
break;
case 19:
if (g_options.play_serrure_cutscene) {
cutName = 31; // SERRURE
}
break;
case 22:
case 23:
case 24:
case 22: // Level 2 fuse repaired
case 23: // switches
case 24: // Level 2 fuse is blown
if (g_options.play_asc_cutscene) {
cutName = 12; // ASC
}
@ -1171,6 +1298,11 @@ void Cutscene::play() {
cutName = 14; // METRO
}
break;
case 46: // Level 2 terminal card mission
break;
default:
warning("Unknown cutscene %d", _id);
break;
}
}
if (_patchedOffsetsTable) {
@ -1213,7 +1345,7 @@ static void readSetPalette(const uint8_t *p, uint16_t offset, uint16_t *palette)
}
}
void Cutscene::drawSetShape(const uint8_t *p, uint16_t offset, int x, int y, uint8_t *paletteLut) {
void Cutscene::drawSetShape(const uint8_t *p, uint16_t offset, int x, int y, const uint8_t *paletteLut) {
const int count = READ_BE_UINT16(p + offset); offset += 2;
for (int i = 0; i < count - 1; ++i) {
offset += 5; // shape_marker
@ -1280,14 +1412,14 @@ void Cutscene::playSet(const uint8_t *p, int offset) {
}
prepare();
_gfx.setLayer(_page1, _vid->_w);
_gfx.setLayer(_backPage, _vid->_w);
offset = 10;
const int frames = READ_BE_UINT16(p + offset); offset += 2;
for (int i = 0; i < frames && !_stub->_pi.quit; ++i) {
for (int i = 0; i < frames && !_stub->_pi.quit && !_interrupted; ++i) {
const uint32_t timestamp = _stub->getTimeStamp();
memset(_page1, 0xC0, _vid->_layerSize);
memset(_backPage, 0xC0, _vid->_layerSize);
const int shapeBg = READ_BE_UINT16(p + offset); offset += 2;
const int count = READ_BE_UINT16(p + offset); offset += 2;
@ -1334,10 +1466,14 @@ void Cutscene::playSet(const uint8_t *p, int offset) {
_stub->setPaletteEntry(0xC0 + j, &c);
}
_stub->copyRect(0, 0, _vid->_w, _vid->_h, _page1, _vid->_w);
_stub->copyRect(0, 0, _vid->_w, _vid->_h, _backPage, _vid->_w);
_stub->updateScreen(0);
const int diff = 6 * TIMER_SLICE - (_stub->getTimeStamp() - timestamp);
const int diff = 90 - (_stub->getTimeStamp() - timestamp);
_stub->sleep((diff < 16) ? 16 : diff);
_stub->processEvents();
if (_stub->_pi.backspace) {
_stub->_pi.backspace = false;
_interrupted = true;
}
}
}

View File

@ -1,7 +1,7 @@
/*
* REminiscence - Flashback interpreter
* Copyright (C) 2005-2018 Gregory Montoir (cyx@users.sourceforge.net)
* Copyright (C) 2005-2019 Gregory Montoir (cyx@users.sourceforge.net)
*/
#ifndef CUTSCENE_H__
@ -18,8 +18,8 @@ struct Cutscene {
typedef void (Cutscene::*OpcodeStub)();
enum {
NUM_OPCODES = 15,
TIMER_SLICE = 15
MAX_VERTICES = 128,
NUM_OPCODES = 15
};
enum {
@ -28,6 +28,14 @@ struct Cutscene {
kTextJustifyCenter = 2,
};
enum {
kCineDebut = 0,
kCineChute = 47,
kCineMemo = 48,
kCineVoyage = 52,
kCineEspions = 57
};
struct SetShape {
uint16_t offset;
uint16_t size;
@ -39,8 +47,9 @@ struct Cutscene {
};
static const OpcodeStub _opcodeTable[];
static const char *_namesTable[];
static const uint16_t _offsetsTable[];
static const char *_namesTableDOS[];
static const uint16_t _offsetsTableDOS[];
static const uint16_t _offsetsTableAmiga[];
static const uint8_t _amigaDemoOffsetsTable[];
static const uint8_t _ssiOffsetsTable[];
static const uint16_t _cosTable[];
@ -48,11 +57,14 @@ struct Cutscene {
static const uint8_t _creditsDataDOS[];
static const uint8_t _creditsDataAmiga[];
static const uint16_t _creditsCutSeq[];
static const uint8_t _musicTable[];
static const uint8_t _musicTableDOS[];
static const uint8_t _musicTableAmiga[];
static const uint8_t _protectionShapeData[];
static const Text _frTextsTable[];
static const Text _enTextsTable[];
static const uint8_t _caillouSetData[];
static const uint8_t _memoSetShape2Data[];
static const uint8_t _memoSetShape4Data[];
Graphics _gfx;
Resource *_res;
@ -70,15 +82,14 @@ struct Cutscene {
uint32_t _tstamp;
uint8_t _frameDelay;
bool _newPal;
uint8_t _palBuf[0x20 * 2];
uint8_t _palBuf[16 * sizeof(uint16_t) * 2];
uint16_t _baseOffset;
bool _creditsSequence;
uint32_t _rotMat[4];
uint8_t _primitiveColor;
uint8_t _clearScreen;
Point _vertices[0x80];
Point _vertices[MAX_VERTICES];
bool _hasAlphaColor;
uint8_t _varText;
uint8_t _varKey;
int16_t _shape_ix;
int16_t _shape_iy;
@ -97,25 +108,28 @@ struct Cutscene {
uint8_t _textBuf[500];
const uint8_t *_textCurPtr;
uint8_t *_textCurBuf;
uint8_t _textUnk2;
bool _creditsSlowText;
bool _creditsKeepText;
uint8_t _creditsTextPosX;
uint8_t _creditsTextPosY;
int16_t _creditsTextCounter;
uint8_t *_page0, *_page1, *_pageC;
int _creditsTextIndex; /* MAC has the credits data in a resource */
int _creditsTextLen;
uint8_t *_frontPage, *_backPage, *_auxPage;
Cutscene(Resource *res, SystemStub *stub, Video *vid);
const uint8_t *getCommandData() const;
const uint8_t *getPolygonData() const;
void sync();
void sync(int delay);
void copyPalette(const uint8_t *pal, uint16_t num);
void updatePalette();
void setPalette();
void updateScreen();
void setRotationTransform(uint16_t a, uint16_t b, uint16_t c);
uint16_t findTextSeparators(const uint8_t *p, int len);
void drawText(int16_t x, int16_t y, const uint8_t *p, uint16_t color, uint8_t *page, int textJustify);
void swapLayers();
void clearBackPage();
void drawCreditsText();
void drawProtectionShape(uint8_t shapeNum, int16_t zoom);
void drawShape(const uint8_t *data, int16_t x, int16_t y);
@ -127,14 +141,14 @@ struct Cutscene {
void op_waitForSync();
void op_drawShape();
void op_setPalette();
void op_drawStringAtBottom();
void op_drawCaptionText();
void op_nop();
void op_skip3();
void op_refreshAll();
void op_drawShapeScale();
void op_drawShapeScaleRotate();
void op_drawCreditsText();
void op_drawStringAtPos();
void op_copyScreen();
void op_drawTextAtPos();
void op_handleKeys();
uint8_t fetchNextCmdByte();
@ -147,7 +161,7 @@ struct Cutscene {
void playText(const char *str);
void play();
void drawSetShape(const uint8_t *p, uint16_t offset, int x, int y, uint8_t *paletteLut);
void drawSetShape(const uint8_t *p, uint16_t offset, int x, int y, const uint8_t *paletteLut);
void playSet(const uint8_t *p, int offset);
};

View File

@ -3,11 +3,15 @@
#include <stdlib.h>
#include <string.h>
#include "decode_mac.h"
#include "file.h"
#include "util.h"
uint8_t *decodeLzss(File &f, uint32_t &decodedSize) {
decodedSize = f.readUint32BE();
uint8_t *dst = (uint8_t *)malloc(decodedSize);
if (!dst) {
warning("Failed to allocate %d bytes for LZSS", decodedSize);
return 0;
}
uint32_t count = 0;
while (count < decodedSize) {
const int code = f.readByte();
@ -30,57 +34,64 @@ uint8_t *decodeLzss(File &f, uint32_t &decodedSize) {
}
static void setPixel(int x, int y, int w, int h, uint8_t color, DecodeBuffer *buf) {
buf->setPixel(buf, x, y, w, h, color);
}
void decodeC103(const uint8_t *a3, int w, int h, DecodeBuffer *buf) {
uint8_t d0;
int d3 = 0;
int d7 = 1;
int d6 = 0;
int d1 = 0;
static const uint32_t d5 = 0xFFF;
uint8_t a1[0x1000];
for (int y = 0; y < h; ++y) {
for (int x = 0; x < w; ++x) {
assert(d6 >= 0);
if (d6 == 0) {
int carry = d7 & 1;
d7 >>= 1;
if (d7 == 0) {
d7 = *a3++;
const int extended_bit = carry ? 0x80 : 0;
carry = d7 & 1;
d7 = extended_bit | (d7 >> 1);
}
if (!carry) {
d0 = *a3++;
a1[d3] = d0;
++d3;
d3 &= d5;
setPixel(x, y, w, h, d0, buf);
continue;
}
d1 = READ_BE_UINT16(a3); a3 += 2;
d6 = d1;
d1 &= d5;
++d1;
d1 = (d3 - d1) & d5;
d6 >>= 12;
d6 += 3;
}
d0 = a1[d1++];
d1 &= d5;
a1[d3++] = d0;
d3 &= d5;
setPixel(x, y, w, h, d0, buf);
--d6;
y += buf->y;
if (y >= 0 && y < buf->h) {
if (buf->xflip) {
x = w - 1 - x;
}
x += buf->x;
if (x >= 0 && x < buf->w) {
buf->setPixel(buf, x, y, color);
}
}
}
void decodeC211(const uint8_t *a3, int w, int h, DecodeBuffer *buf) {
void decodeC103(const uint8_t *src, int w, int h, DecodeBuffer *buf) {
static const int kBits = 12;
static const int kMask = (1 << kBits) - 1;
int cursor = 0;
int bits = 1;
int count = 0;
int offset = 0;
uint8_t window[(1 << kBits)];
for (int y = 0; y < h; ++y) {
for (int x = 0; x < w; ++x) {
if (count == 0) {
int carry = bits & 1;
bits >>= 1;
if (bits == 0) {
bits = *src++;
if (carry) {
bits |= 0x100;
}
carry = bits & 1;
bits >>= 1;
}
if (!carry) {
const uint8_t color = *src++;
window[cursor] = color;
++cursor;
cursor &= kMask;
setPixel(x, y, w, h, color, buf);
continue;
}
offset = READ_BE_UINT16(src); src += 2;
count = 3 + (offset >> 12);
offset &= kMask;
offset = (cursor - offset - 1) & kMask;
}
const uint8_t color = window[offset++];
offset &= kMask;
window[cursor++] = color;
cursor &= kMask;
setPixel(x, y, w, h, color, buf);
--count;
}
}
}
void decodeC211(const uint8_t *src, int w, int h, DecodeBuffer *buf) {
struct {
const uint8_t *ptr;
int repeatCount;
@ -90,52 +101,48 @@ void decodeC211(const uint8_t *a3, int w, int h, DecodeBuffer *buf) {
int sp = 0;
while (1) {
uint8_t d0 = *a3++;
if ((d0 & 0x80) != 0) {
const uint8_t code = *src++;
if ((code & 0x80) != 0) {
++y;
x = 0;
}
int d1 = d0 & 0x1F;
if (d1 == 0) {
d1 = READ_BE_UINT16(a3); a3 += 2;
int count = code & 0x1F;
if (count == 0) {
count = READ_BE_UINT16(src); src += 2;
}
const int carry_set = (d0 & 0x40) != 0;
d0 <<= 2;
if (!carry_set) {
if ((d0 & 0x80) == 0) {
--d1;
if (d1 == 0) {
if ((code & 0x40) == 0) {
if ((code & 0x20) == 0) {
if (count == 1) {
assert(sp > 0);
--stack[sp - 1].repeatCount;
if (stack[sp - 1].repeatCount >= 0) {
a3 = stack[sp - 1].ptr;
src = stack[sp - 1].ptr;
} else {
--sp;
}
} else {
assert(sp < ARRAYSIZE(stack));
stack[sp].ptr = a3;
stack[sp].repeatCount = d1;
stack[sp].ptr = src;
stack[sp].repeatCount = count - 1;
++sp;
}
} else {
x += d1;
x += count;
}
} else {
if ((d0 & 0x80) == 0) {
if (d1 == 1) {
if ((code & 0x20) == 0) {
if (count == 1) {
return;
}
const uint8_t color = *a3++;
for (int i = 0; i < d1; ++i) {
const uint8_t color = *src++;
for (int i = 0; i < count; ++i) {
setPixel(x++, y, w, h, color, buf);
}
} else {
for (int i = 0; i < d1; ++i) {
setPixel(x++, y, w, h, *a3++, buf);
for (int i = 0; i < count; ++i) {
setPixel(x++, y, w, h, *src++, buf);
}
}
}
}
}

View File

@ -13,7 +13,7 @@ struct DecodeBuffer {
int x, y;
bool xflip;
void (*setPixel)(DecodeBuffer *buf, int src_x, int src_y, int src_w, int src_h, uint8_t color);
void (*setPixel)(DecodeBuffer *buf, int x, int y, uint8_t color);
void *dataPtr;
};

View File

@ -1,59 +0,0 @@
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#else
#include <dlfcn.h>
#endif
#include <stdio.h>
#include <sys/param.h>
#include "dynlib.h"
#ifdef WIN32
struct DynLib_impl {
HINSTANCE _dl;
DynLib_impl(const char *name) {
char dllname[MAXPATHLEN];
snprintf(dllname, sizeof(dllname), "%s.dll", name);
_dl = LoadLibrary(dllname);
}
~DynLib_impl() {
if (_dl) {
FreeLibrary(_dl);
_dl = 0;
}
}
void *getSymbol(const char *name) {
return (void *)GetProcAddress(_dl, name);
}
};
#else
struct DynLib_impl {
void *_dl;
DynLib_impl(const char *name) {
char soname[MAXPATHLEN];
snprintf(soname, sizeof(soname), "%s.so", name);
_dl = dlopen(soname, RTLD_LAZY);
}
~DynLib_impl() {
if (_dl) {
dlclose(_dl);
}
}
void *getSymbol(const char *name) {
return dlsym(_dl, name);
}
};
#endif
DynLib::DynLib(const char *name) {
_impl = new DynLib_impl(name);
}
DynLib::~DynLib() {
delete _impl;
}
void *DynLib::getSymbol(const char *name) {
return _impl->getSymbol(name);
}

View File

@ -1,16 +0,0 @@
#ifndef DYNLIB_H__
#define DYNLIB_H__
struct DynLib_impl;
struct DynLib {
DynLib_impl *_impl;
DynLib(const char *name);
~DynLib();
void *getSymbol(const char *name);
};
#endif // DYNLIB_H__

103
file.cpp
View File

@ -1,7 +1,7 @@
/*
* REminiscence - Flashback interpreter
* Copyright (C) 2005-2018 Gregory Montoir (cyx@users.sourceforge.net)
* Copyright (C) 2005-2019 Gregory Montoir (cyx@users.sourceforge.net)
*/
#include <sys/param.h>
@ -24,6 +24,7 @@ struct File_impl {
virtual void close() = 0;
virtual uint32_t size() = 0;
virtual void seek(int32_t off) = 0;
virtual uint32_t tell() = 0;
virtual uint32_t read(void *ptr, uint32_t len) = 0;
virtual uint32_t write(const void *ptr, uint32_t len) = 0;
};
@ -57,6 +58,12 @@ struct StdioFile : File_impl {
fseek(_fp, off, SEEK_SET);
}
}
uint32_t tell() {
if (_fp) {
return ftell(_fp);
}
return 0;
}
uint32_t read(void *ptr, uint32_t len) {
if (_fp) {
uint32_t r = fread(ptr, 1, len, _fp);
@ -109,6 +116,12 @@ struct GzipFile : File_impl {
gzseek(_fp, off, SEEK_SET);
}
}
uint32_t tell() {
if (_fp) {
return gztell(_fp);
}
return 0;
}
uint32_t read(void *ptr, uint32_t len) {
if (_fp) {
uint32_t r = gzread(_fp, ptr, len);
@ -197,6 +210,58 @@ struct AssetFile: File_impl {
};
#endif
struct MemoryBufferFile: File_impl {
uint8_t *_ptr;
uint32_t _capacity, _offset, _len;
MemoryBufferFile(int initialCapacity) {
_capacity = initialCapacity;
_ptr = (uint8_t *)malloc(_capacity);
_offset = _len = 0;
}
~MemoryBufferFile() {
free(_ptr);
}
bool open(const char *path, const char *mode) {
return false;
}
void close() {
}
uint32_t size() {
return _len;
}
uint32_t tell() {
return _offset;
}
void seek(int offs) {
_offset = offs;
}
uint32_t read(void *ptr, uint32_t len) {
int count = len;
if (_offset + count > _len) {
count = _len - _offset;
_ioErr = true;
}
if (count != 0) {
memcpy(ptr, _ptr + _offset, count);
_offset += count;
}
return count;
}
uint32_t write(const void *ptr, uint32_t len) {
int count = len;
while (_offset + count > _capacity) {
_capacity *= 2;
_ptr = (uint8_t *)realloc(_ptr, _capacity);
}
if (count != 0) {
memcpy(_ptr + _offset, ptr, count);
_offset += count;
}
_len = _offset;
return count;
}
};
File::File()
: _impl(0) {
}
@ -264,6 +329,15 @@ bool File::open(const char *filename, const char *mode, const char *directory) {
return _impl->open(path, mode);
}
void File::openMemoryBuffer(int initialCapacity) {
if (_impl) {
_impl->close();
delete _impl;
_impl = 0;
}
_impl = new MemoryBufferFile(initialCapacity);
}
void File::close() {
if (_impl) {
_impl->close();
@ -282,6 +356,10 @@ void File::seek(int32_t off) {
_impl->seek(off);
}
uint32_t File::tell() {
return _impl->tell();
}
uint32_t File::read(void *ptr, uint32_t len) {
return _impl->read(ptr, len);
}
@ -324,6 +402,16 @@ void File::writeByte(uint8_t b) {
write(&b, 1);
}
void File::writeUint16LE(uint16_t n) {
writeByte(n & 0xFF);
writeByte(n >> 8);
}
void File::writeUint32LE(uint32_t n) {
writeUint16LE(n & 0xFFFF);
writeUint16LE(n >> 16);
}
void File::writeUint16BE(uint16_t n) {
writeByte(n >> 8);
writeByte(n & 0xFF);
@ -333,3 +421,16 @@ void File::writeUint32BE(uint32_t n) {
writeUint16BE(n >> 16);
writeUint16BE(n & 0xFFFF);
}
void dumpFile(const char *filename, const uint8_t *p, int size) {
char path[MAXPATHLEN];
snprintf(path, sizeof(path), "DUMP/%s", filename);
FILE *fp = fopen(filename, "wb");
if (fp) {
const int count = fwrite(p, 1, size, fp);
if (count != size) {
warning("Failed to write %d bytes (expected %d)", count, size);
}
fclose(fp);
}
}

8
file.h
View File

@ -1,7 +1,7 @@
/*
* REminiscence - Flashback interpreter
* Copyright (C) 2005-2018 Gregory Montoir (cyx@users.sourceforge.net)
* Copyright (C) 2005-2019 Gregory Montoir (cyx@users.sourceforge.net)
*/
#ifndef FILE_H__
@ -20,10 +20,12 @@ struct File {
bool open(const char *filename, const char *mode, FileSystem *fs);
bool open(const char *filename, const char *mode, const char *directory);
void openMemoryBuffer(int initialCapacity);
void close();
bool ioErr() const;
uint32_t size();
void seek(int32_t off);
uint32_t tell();
uint32_t read(void *ptr, uint32_t len);
uint8_t readByte();
uint16_t readUint16LE();
@ -32,8 +34,12 @@ struct File {
uint32_t readUint32BE();
uint32_t write(const void *ptr, uint32_t size);
void writeByte(uint8_t b);
void writeUint16LE(uint16_t n);
void writeUint32LE(uint32_t n);
void writeUint16BE(uint16_t n);
void writeUint32BE(uint32_t n);
};
void dumpFile(const char *filename, const uint8_t *p, int size);
#endif // FILE_H__

6
fs.cpp
View File

@ -1,9 +1,13 @@
/*
* REminiscence - Flashback interpreter
* Copyright (C) 2005-2018 Gregory Montoir (cyx@users.sourceforge.net)
* Copyright (C) 2005-2019 Gregory Montoir (cyx@users.sourceforge.net)
*/
#if defined(__amigaos4__)
#include <sys/unistd.h>
#endif
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>

2
fs.h
View File

@ -1,7 +1,7 @@
/*
* REminiscence - Flashback interpreter
* Copyright (C) 2005-2018 Gregory Montoir (cyx@users.sourceforge.net)
* Copyright (C) 2005-2019 Gregory Montoir (cyx@users.sourceforge.net)
*/
#ifndef FS_H__

572
game.cpp

File diff suppressed because it is too large Load Diff

84
game.h
View File

@ -1,7 +1,7 @@
/*
* REminiscence - Flashback interpreter
* Copyright (C) 2005-2018 Gregory Montoir (cyx@users.sourceforge.net)
* Copyright (C) 2005-2019 Gregory Montoir (cyx@users.sourceforge.net)
*/
#ifndef GAME_H__
@ -19,6 +19,12 @@ struct File;
struct FileSystem;
struct SystemStub;
enum {
kCheatOneHitKill = 1 << 0,
kCheatNoHit = 1 << 1,
kCheatLifeCounter = 1 << 2
};
struct Game {
typedef int (Game::*pge_OpcodeProc)(ObjectOpcodeArgs *args);
typedef int (Game::*pge_ZOrderCallback)(LivePGE *, LivePGE *, uint8_t, uint8_t);
@ -27,8 +33,9 @@ struct Game {
enum {
kIngameSaveSlot = 0,
kRewindSize = 120, // 10mins (~2MB)
kAutoSaveSlot = 255,
kAutoSaveIntervalMs = 30 * 1000
kAutoSaveIntervalMs = 5 * 1000
};
enum {
@ -54,6 +61,9 @@ struct Game {
static const pge_OpcodeProc _pge_opcodeTable[];
static const uint8_t _pge_modKeysTable[];
static const uint8_t _protectionCodeData[];
static const uint8_t _protectionWordData[];
static const uint8_t _protectionNumberDataAmiga[];
static const uint8_t _protectionCodeDataAmiga[];
static const uint8_t _protectionPal[];
Cutscene _cut;
@ -65,6 +75,9 @@ struct Game {
SystemStub *_stub;
FileSystem *_fs;
const char *_savePath;
File _rewindBuffer[kRewindSize];
int _rewindPtr, _rewindLen;
uint32_t _cheats;
const uint8_t *_stringsTable;
const char **_textsTable;
@ -96,7 +109,7 @@ struct Game {
bool _autoSave;
uint32_t _saveTimestamp;
Game(SystemStub *, FileSystem *, const char *savePath, int level, ResourceType ver, Language lang, WidescreenMode widescreenMode, bool autoSave);
Game(SystemStub *, FileSystem *, const char *savePath, int level, ResourceType ver, Language lang, WidescreenMode widescreenMode, bool autoSave, int midiDriver, uint32_t cheats);
void run();
void displayTitleScreenAmiga();
@ -115,7 +128,6 @@ struct Game {
void showFinalScore();
bool handleConfigPanel();
bool handleContinueAbort();
bool handleProtectionScreen();
void printSaveStateCompleted();
void drawLevelTexts();
void drawStoryTexts();
@ -127,7 +139,6 @@ struct Game {
void drawPiege(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);
@ -135,12 +146,15 @@ struct Game {
void changeLevel();
void handleInventory();
// protection
bool handleProtectionScreenShape();
bool handleProtectionScreenWords();
// pieges
bool _pge_playAnimSound;
GroupPGE _pge_groups[256];
GroupPGE *_pge_groupsTable[256];
GroupPGE *_pge_nextFreeGroup;
MessagePGE _pge_messages[256];
MessagePGE *_pge_messagesTable[256]; // indexed by pge number
MessagePGE *_pge_nextFreeMessage;
LivePGE *_pge_liveTable2[256]; // active pieges list (index = pge number)
LivePGE *_pge_liveTable1[256]; // pieges list by room (index = room)
LivePGE _pgeLive[256];
@ -148,17 +162,19 @@ struct Game {
bool _pge_currentPiegeFacingDir; // (false == left)
bool _pge_processOBJ;
uint8_t _pge_inpKeysMask;
uint16_t _pge_opTempVar1;
uint16_t _pge_opTempVar2;
uint16_t _pge_opGunVar;
uint16_t _pge_compareVar1;
uint16_t _pge_compareVar2;
uint8_t _pge_zoomPiegeNum;
uint8_t _pge_zoomCounter;
int _pge_zoomX, _pge_zoomY;
void pge_resetGroups();
void pge_removeFromGroup(uint8_t idx);
int pge_isInGroup(LivePGE *pge_dst, uint16_t group_id, uint16_t counter);
void pge_resetMessages();
void pge_clearMessages(uint8_t pge_index);
int pge_hasMessageData(LivePGE *pge, uint16_t msg_num, uint16_t counter) const;
void pge_loadForCurrentLevel(uint16_t idx);
void pge_process(LivePGE *pge);
void pge_setupNextAnimFrame(LivePGE *pge, GroupPGE *le);
void pge_setupNextAnimFrame(LivePGE *pge, MessagePGE *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);
@ -201,11 +217,11 @@ struct Game {
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_hasPiegeSentMessage(ObjectOpcodeArgs *args);
int pge_op_sendMessageData0(ObjectOpcodeArgs *args);
int pge_op_sendMessageData1(ObjectOpcodeArgs *args);
int pge_op_sendMessageData2(ObjectOpcodeArgs *args);
int pge_op_sendMessageData3(ObjectOpcodeArgs *args);
int pge_op_isPiegeDead(ObjectOpcodeArgs *args);
int pge_op_collides1u2o(ObjectOpcodeArgs *args);
int pge_op_collides1u1o(ObjectOpcodeArgs *args);
@ -223,10 +239,10 @@ struct Game {
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_hasMessageData0(ObjectOpcodeArgs *args);
int pge_hasMessageData1(ObjectOpcodeArgs *args);
int pge_hasMessageData2(ObjectOpcodeArgs *args);
int pge_hasMessageData3(ObjectOpcodeArgs *args);
int pge_o_unk0x3C(ObjectOpcodeArgs *args);
int pge_o_unk0x3D(ObjectOpcodeArgs *args);
int pge_op_setPiegeCounter(ObjectOpcodeArgs *args);
@ -241,7 +257,7 @@ struct Game {
int pge_o_unk0x47(ObjectOpcodeArgs *args);
int pge_o_unk0x48(ObjectOpcodeArgs *args);
int pge_o_unk0x49(ObjectOpcodeArgs *args);
int pge_o_unk0x4A(ObjectOpcodeArgs *args);
int pge_op_killInventoryPiege(ObjectOpcodeArgs *args);
int pge_op_killPiege(ObjectOpcodeArgs *args);
int pge_op_isInCurrentRoom(ObjectOpcodeArgs *args);
int pge_op_isNotInCurrentRoom(ObjectOpcodeArgs *args);
@ -257,7 +273,7 @@ struct Game {
int pge_op_setLifeCounter(ObjectOpcodeArgs *args);
int pge_op_decLifeCounter(ObjectOpcodeArgs *args);
int pge_op_playCutscene(ObjectOpcodeArgs *args);
int pge_op_isTempVar2Set(ObjectOpcodeArgs *args);
int pge_op_compareUnkVar(ObjectOpcodeArgs *args);
int pge_op_playDeathCutscene(ObjectOpcodeArgs *args);
int pge_o_unk0x5D(ObjectOpcodeArgs *args);
int pge_o_unk0x5E(ObjectOpcodeArgs *args);
@ -273,7 +289,7 @@ struct Game {
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_isToggleable(ObjectOpcodeArgs *args);
int pge_o_unk0x6C(ObjectOpcodeArgs *args);
int pge_op_isCollidingObject(ObjectOpcodeArgs *args);
int pge_o_unk0x6E(ObjectOpcodeArgs *args);
@ -303,16 +319,16 @@ struct Game {
int pge_o_unk0x86(ObjectOpcodeArgs *args);
int pge_op_playSoundGroup(ObjectOpcodeArgs *args);
int pge_op_adjustPos(ObjectOpcodeArgs *args);
int pge_op_setTempVar1(ObjectOpcodeArgs *args);
int pge_op_isTempVar1Set(ObjectOpcodeArgs *args);
int pge_op_setGunVar(ObjectOpcodeArgs *args);
int pge_op_compareGunVar(ObjectOpcodeArgs *args);
int pge_setCurrentInventoryObject(LivePGE *pge);
void pge_updateInventory(LivePGE *pge1, LivePGE *pge2);
void pge_reorderInventory(LivePGE *pge);
LivePGE *pge_getInventoryItemBefore(LivePGE *pge, LivePGE *last_pge);
LivePGE *pge_getPreviousInventoryItem(LivePGE *pge, LivePGE *last_pge);
void pge_addToInventory(LivePGE *pge1, LivePGE *pge2, LivePGE *pge3);
int pge_updateCollisionState(LivePGE *pge, int16_t pge_dy, uint8_t var8);
int pge_updateCollisionState(LivePGE *pge, int16_t pge_dy, uint8_t value);
int pge_ZOrder(LivePGE *pge, int16_t num, pge_ZOrderCallback compare, uint16_t unk);
void pge_updateGroup(uint8_t idx, uint8_t unk1, int16_t unk2);
void pge_sendMessage(uint8_t src_pge_index, uint8_t dst_pge_index, int16_t num);
void pge_removeFromInventory(LivePGE *pge1, LivePGE *pge2, LivePGE *pge3);
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);
@ -324,6 +340,7 @@ struct Game {
int pge_ZOrderIfTypeAndSameDirection(LivePGE *pge1, LivePGE *pge2, uint8_t comp, uint8_t comp2);
int pge_ZOrderIfTypeAndDifferentDirection(LivePGE *pge1, LivePGE *pge2, uint8_t comp, uint8_t comp2);
int pge_ZOrderByNumber(LivePGE *pge1, LivePGE *pge2, uint8_t comp, uint8_t comp2);
void pge_updateZoom();
// collision
@ -379,7 +396,10 @@ struct Game {
bool saveGameState(uint8_t slot);
bool loadGameState(uint8_t slot);
void saveState(File *f);
void loadState(File *f);
void loadState(File *f, int version);
void clearStateRewind();
bool saveStateRewind();
bool loadStateRewind();
};
#endif // GAME_H__

View File

@ -1,7 +1,7 @@
/*
* REminiscence - Flashback interpreter
* Copyright (C) 2005-2018 Gregory Montoir (cyx@users.sourceforge.net)
* Copyright (C) 2005-2019 Gregory Montoir (cyx@users.sourceforge.net)
*/
#include "graphics.h"

View File

@ -1,7 +1,7 @@
/*
* REminiscence - Flashback interpreter
* Copyright (C) 2005-2018 Gregory Montoir (cyx@users.sourceforge.net)
* Copyright (C) 2005-2019 Gregory Montoir (cyx@users.sourceforge.net)
*/
#ifndef GRAPHICS_H__

View File

@ -1,7 +1,7 @@
/*
* REminiscence - Flashback interpreter
* Copyright (C) 2005-2018 Gregory Montoir (cyx@users.sourceforge.net)
* Copyright (C) 2005-2019 Gregory Montoir (cyx@users.sourceforge.net)
*/
#ifndef INTERN_H__
@ -46,6 +46,16 @@ inline int16_t ADDC_S16(int a, int b) {
return a;
}
inline int16_t S8_to_S16(int a) {
if (a < -128) {
return -32768;
} else if (a > 127) {
return 32767;
} else {
return ((uint8_t)a) * 257;
}
}
template<typename T>
inline void SWAP(T &a, T &b) {
T tmp = a;
@ -106,6 +116,13 @@ enum WidescreenMode {
kWidescreenNone,
kWidescreenAdjacentRooms,
kWidescreenMirrorRoom,
kWidescreenBlur,
kWidescreenCDi,
};
enum {
kWidescreenBorderCDiW = 52,
kWidescreenBorderCDiH = 224
};
struct Options {
@ -113,13 +130,20 @@ struct Options {
bool enable_password_menu;
bool enable_language_selection;
bool fade_out_palette;
bool use_tiledata;
bool use_tile_data;
bool use_text_cutscenes;
bool use_seq_cutscenes;
bool use_words_protection;
bool use_white_tshirt;
bool use_prf_music;
bool play_asc_cutscene;
bool play_caillou_cutscene;
bool play_metro_cutscene;
bool play_serrure_cutscene;
bool play_carte_cutscene;
bool play_gamesaved_sound;
bool restore_memo_cutscene;
bool order_inventory_original;
};
struct Color {
@ -155,8 +179,8 @@ struct InitPGE {
int16_t pos_y;
uint16_t obj_node_number;
uint16_t life;
int16_t counter_values[4];
uint8_t object_type;
int16_t data[4];
uint8_t object_type; // 1:conrad, 10:monster
uint8_t init_room;
uint8_t room_location;
uint8_t init_flags;
@ -165,8 +189,8 @@ struct InitPGE {
uint8_t object_id;
uint8_t skill;
uint8_t mirror_x;
uint8_t flags;
uint8_t unk1C; // collidable, collision_data_len
uint8_t flags; // 1:xflip 4:active
uint8_t collision_data_len;
uint16_t text_num;
};
@ -181,7 +205,7 @@ struct LivePGE {
uint8_t collision_slot;
uint8_t next_inventory_PGE;
uint8_t current_inventory_PGE;
uint8_t unkF; // unk_inventory_PGE
uint8_t ref_inventory_PGE;
uint16_t anim_number;
uint8_t flags;
uint8_t index;
@ -190,10 +214,10 @@ struct LivePGE {
InitPGE *init_PGE;
};
struct GroupPGE {
GroupPGE *next_entry;
uint16_t index;
uint16_t group_id;
struct MessagePGE {
MessagePGE *next_entry;
uint16_t src_pge;
uint16_t msg_num;
};
struct Object {
@ -212,9 +236,8 @@ struct Object {
};
struct ObjectNode {
uint16_t last_obj_number;
Object *objects;
uint16_t num_objects;
Object *objects;
};
struct ObjectOpcodeArgs {
@ -251,9 +274,9 @@ struct BankSlot {
struct CollisionSlot2 {
CollisionSlot2 *next_slot;
int8_t *unk2;
int8_t *unk2; // grid_data_pos
uint8_t data_size;
uint8_t data_buf[0x10]; // XXX check size
uint8_t data_buf[0x10]; // <= InitPGE.collision_data_len
};
struct InventoryItem {
@ -267,6 +290,7 @@ struct SoundFx {
uint16_t len;
uint16_t freq;
uint8_t *data;
int8_t peak;
};
extern Options g_options;

119
main.cpp
View File

@ -1,7 +1,7 @@
/*
* REminiscence - Flashback interpreter
* Copyright (C) 2005-2018 Gregory Montoir (cyx@users.sourceforge.net)
* Copyright (C) 2005-2019 Gregory Montoir (cyx@users.sourceforge.net)
*/
#include <SDL.h>
@ -18,14 +18,15 @@
static const char *USAGE =
"REminiscence - Flashback Interpreter\n"
"Usage: %s [OPTIONS]...\n"
" --datapath=PATH Path to data files (default 'DATA')\n"
" --savepath=PATH Path to save files (default '.')\n"
" --datapath=PATH Path to data files (default 'data')\n"
" --savepath=PATH Path to save files (default 'saves')\n"
" --levelnum=NUM Start to level, bypass introduction\n"
" --fullscreen Fullscreen display\n"
" --widescreen=MODE 16:9 display\n"
" --widescreen=MODE 16:9 display (adjacent,mirror,blur,none)\n"
" --scaler=NAME@X Graphics scaler (default 'scale@3')\n"
" --language=LANG Language (fr,en,de,sp,it,jp)\n"
" --autosave Save game state automatically\n"
" --mididriver=MIDI Driver (adlib, mt32)\n"
;
static int detectVersion(FileSystem *fs) {
@ -35,7 +36,9 @@ static int detectVersion(FileSystem *fs) {
const char *name;
} table[] = {
{ "DEMO_UK.ABA", kResourceTypeDOS, "DOS (Demo)" },
{ "GLOB_FR.ABA", kResourceTypeDOS, "DOS" },
{ "INTRO.SEQ", kResourceTypeDOS, "DOS CD" },
{ "MENU1SSI.MAP", kResourceTypeDOS, "DOS SSI" },
{ "LEVEL1.MAP", kResourceTypeDOS, "DOS" },
{ "LEVEL1.BNQ", kResourceTypeDOS, "DOS (Demo)" },
{ "LEVEL1.LEV", kResourceTypeAmiga, "Amiga" },
@ -59,7 +62,8 @@ static Language detectLanguage(FileSystem *fs) {
const char *filename;
Language language;
} table[] = {
// PC
// DOS
{ "GLOB_FR.ABA", LANG_FR },
{ "ENGCINE.TXT", LANG_EN },
{ "FR_CINE.TXT", LANG_FR },
{ "GERCINE.TXT", LANG_DE },
@ -90,10 +94,17 @@ static void initOptions() {
g_options.fade_out_palette = true;
g_options.use_text_cutscenes = false;
g_options.use_seq_cutscenes = true;
g_options.use_words_protection = false;
g_options.use_white_tshirt = false;
g_options.use_prf_music = true;
g_options.play_asc_cutscene = false;
g_options.play_caillou_cutscene = false;
g_options.play_metro_cutscene = false;
g_options.play_serrure_cutscene = false;
g_options.play_carte_cutscene = false;
g_options.play_gamesaved_sound = false;
g_options.restore_memo_cutscene = true;
g_options.order_inventory_original = false;
// read configuration file
struct {
const char *name;
@ -103,13 +114,20 @@ static void initOptions() {
{ "enable_password_menu", &g_options.enable_password_menu },
{ "enable_language_selection", &g_options.enable_language_selection },
{ "fade_out_palette", &g_options.fade_out_palette },
{ "use_tiledata", &g_options.use_tiledata },
{ "use_tile_data", &g_options.use_tile_data },
{ "use_text_cutscenes", &g_options.use_text_cutscenes },
{ "use_seq_cutscenes", &g_options.use_seq_cutscenes },
{ "use_words_protection", &g_options.use_words_protection },
{ "use_white_tshirt", &g_options.use_white_tshirt },
{ "use_prf_music", &g_options.use_prf_music },
{ "play_asc_cutscene", &g_options.play_asc_cutscene },
{ "play_caillou_cutscene", &g_options.play_caillou_cutscene },
{ "play_metro_cutscene", &g_options.play_metro_cutscene },
{ "play_serrure_cutscene", &g_options.play_serrure_cutscene },
{ "play_carte_cutscene", &g_options.play_carte_cutscene },
{ "play_gamesaved_sound", &g_options.play_gamesaved_sound },
{ "restore_memo_cutscene", &g_options.restore_memo_cutscene },
{ "order_inventory_original", &g_options.order_inventory_original },
{ 0, 0 }
};
static const char *filename = "rs.cfg";
@ -147,50 +165,13 @@ static void initOptions() {
}
static void parseScaler(char *name, ScalerParameters *scalerParameters) {
struct {
const char *name;
int type;
const Scaler *scaler;
} scalers[] = {
{ "point", kScalerTypePoint, 0 },
{ "linear", kScalerTypeLinear, 0 },
{ "scale", kScalerTypeInternal, &_internalScaler },
#ifdef USE_STATIC_SCALER
{ "nearest", kScalerTypeInternal, &scaler_nearest },
{ "tv2x", kScalerTypeInternal, &scaler_tv2x },
{ "xbrz", kScalerTypeInternal, &scaler_xbrz },
#endif
{ 0, -1 }
};
bool found = false;
char *sep = strchr(name, '@');
if (sep) {
*sep = 0;
}
for (int i = 0; scalers[i].name; ++i) {
if (strcmp(scalers[i].name, name) == 0) {
scalerParameters->type = (ScalerType)scalers[i].type;
scalerParameters->scaler = scalers[i].scaler;
found = true;
break;
}
}
if (!found) {
char libname[32];
snprintf(libname, sizeof(libname), "scaler_%s", name);
const Scaler *scaler = findScaler(libname);
if (!scaler) {
warning("Scaler '%s' not found, using default", libname);
} else if (scaler->tag != SCALER_TAG) {
warning("Unexpected tag %d for scaler '%s'", scaler->tag, libname);
} else {
scalerParameters->type = kScalerTypeExternal;
scalerParameters->scaler = scaler;
}
}
if (sep) {
scalerParameters->factor = atoi(sep + 1);
}
strncpy(scalerParameters->name, name, sizeof(scalerParameters->name) - 1);
scalerParameters->name[sizeof(scalerParameters->name) - 1] = 0;
}
static WidescreenMode parseWidescreen(const char *mode) {
@ -200,6 +181,8 @@ static WidescreenMode parseWidescreen(const char *mode) {
} modes[] = {
{ "adjacent", kWidescreenAdjacentRooms },
{ "mirror", kWidescreenMirrorRoom },
{ "blur", kWidescreenBlur },
{ "cdi", kWidescreenCDi },
{ 0, kWidescreenNone },
};
for (int i = 0; modes[i].name; ++i) {
@ -207,19 +190,21 @@ static WidescreenMode parseWidescreen(const char *mode) {
return modes[i].mode;
}
}
warning("Unhandled widecreen mode '%s', defaults to adjacent rooms", mode);
return kWidescreenAdjacentRooms; // default value
warning("Unhandled widecreen mode '%s', defaults to 16:9 blur", mode);
return kWidescreenBlur;
}
int main(int argc, char *argv[]) {
const char *dataPath = "DATA";
const char *savePath = ".";
const char *dataPath = "data";
const char *savePath = "saves/";
int levelNum = 0;
bool fullscreen = false;
bool autoSave = false;
uint32_t cheats = 0;
WidescreenMode widescreen = kWidescreenNone;
ScalerParameters scalerParameters = ScalerParameters::defaults();
int forcedLanguage = -1;
int midiDriver = MODE_ADLIB;
if (argc == 2) {
// data path as the only command line argument
struct stat st;
@ -237,6 +222,8 @@ int main(int argc, char *argv[]) {
{ "language", required_argument, 0, 6 },
{ "widescreen", required_argument, 0, 7 },
{ "autosave", no_argument, 0, 8 },
{ "cheats", required_argument, 0, 9 },
{ "mididriver", required_argument, 0, 10 },
{ 0, 0, 0, 0 }
};
int index;
@ -273,12 +260,16 @@ int main(int argc, char *argv[]) {
{ LANG_JP, "JP" },
{ -1, 0 }
};
for (int i = 0; languages[i].str; ++i) {
int i = 0;
for (; languages[i].str; ++i) {
if (strcasecmp(languages[i].str, optarg) == 0) {
forcedLanguage = languages[i].lang;
break;
}
}
if (!languages[i].str) {
warning("Invalid language '%s'", optarg);
}
}
break;
case 7:
@ -287,6 +278,30 @@ int main(int argc, char *argv[]) {
case 8:
autoSave = true;
break;
case 9:
cheats = atoi(optarg);
break;
case 10: {
static const struct {
int mode;
const char *str;
} drivers[] = {
{ MODE_ADLIB, "adlib" },
{ MODE_MT32, "mt32" },
{ -1, 0 }
};
int i = 0;
for (; drivers[i].str; ++i) {
if (strcasecmp(drivers[i].str, optarg) == 0) {
midiDriver = drivers[i].mode;
break;
}
}
if (!drivers[i].str) {
warning("Invalid MIDI driver '%s'", optarg);
}
}
break;
default:
printf(USAGE, argv[0]);
return 0;
@ -302,8 +317,8 @@ int main(int argc, char *argv[]) {
}
const Language language = (forcedLanguage == -1) ? detectLanguage(&fs) : (Language)forcedLanguage;
SystemStub *stub = SystemStub_SDL_create();
Game *g = new Game(stub, &fs, savePath, levelNum, (ResourceType)version, language, widescreen, autoSave);
stub->init(g_caption, g->_vid._w, g->_vid._h, fullscreen, widescreen != kWidescreenNone, &scalerParameters);
Game *g = new Game(stub, &fs, savePath, levelNum, (ResourceType)version, language, widescreen, autoSave, midiDriver, cheats);
stub->init(g_caption, g->_vid._w, g->_vid._h, fullscreen, widescreen, &scalerParameters);
g->run();
delete g;
stub->destroy();

View File

@ -1,7 +1,7 @@
/*
* REminiscence - Flashback interpreter
* Copyright (C) 2005-2018 Gregory Montoir (cyx@users.sourceforge.net)
* Copyright (C) 2005-2019 Gregory Montoir (cyx@users.sourceforge.net)
*/
#include "game.h"
@ -89,17 +89,20 @@ void Menu::drawString2(const char *str, int16_t y, int16_t x) {
void Menu::loadPicture(const char *prefix) {
debug(DBG_MENU, "Menu::loadPicture('%s')", prefix);
static const int kPictureW = 256;
static const int kPictureH = 224;
_res->load_MAP_menu(prefix, _res->_scratchBuffer);
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->_scratchBuffer[0x3800 * i + x + 64 * y];
for (int y = 0; y < kPictureH; ++y) {
for (int x = 0; x < kPictureW / 4; ++x) {
_vid->_frontLayer[i + x * 4 + kPictureW * y] = _res->_scratchBuffer[0x3800 * i + x + 64 * y];
}
}
}
memcpy(_vid->_backLayer, _vid->_frontLayer, _vid->_layerSize);
_res->load_PAL_menu(prefix, _res->_scratchBuffer);
_stub->setPalette(_res->_scratchBuffer, 256);
_vid->updateWidescreen();
}
void Menu::handleInfoScreen() {
@ -209,9 +212,6 @@ bool Menu::handlePasswordScreen() {
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;
@ -259,9 +259,9 @@ bool Menu::handleLevelScreen() {
}
_vid->markBlockAsDirty(4 * Video::CHAR_W, 7 * Video::CHAR_H, 192, 7 * Video::CHAR_H, _vid->_layerScale);
drawString(_res->getMenuString(LocaleData::LI_13_EASY), 23, 4, (currentSkill == 0) ? 2 : 3);
drawString(_res->getMenuString(LocaleData::LI_14_NORMAL), 23, 14, (currentSkill == 1) ? 2 : 3);
drawString(_res->getMenuString(LocaleData::LI_15_EXPERT), 23, 24, (currentSkill == 2) ? 2 : 3);
drawString(_res->getMenuString(LocaleData::LI_13_EASY), 23, 4, (currentSkill == 0) ? 2 : 3);
drawString(_res->getMenuString(LocaleData::LI_14_NORMAL), 23, 14, (currentSkill == 1) ? 2 : 3);
drawString(_res->getMenuString(LocaleData::LI_15_EXPERT), 23, 24, (currentSkill == 2) ? 2 : 3);
_vid->markBlockAsDirty(4 * Video::CHAR_W, 23 * Video::CHAR_H, 192, Video::CHAR_H, _vid->_layerScale);
_vid->updateScreen();
@ -361,10 +361,8 @@ void Menu::handleTitleScreen() {
++menuItemsCount;
_selectedOption = -1;
_currentScreen = -1;
_nextScreen = SCREEN_TITLE;
bool quitLoop = false;
int currentEntry = 0;
static const struct {
@ -386,7 +384,7 @@ void Menu::handleTitleScreen() {
}
}
while (!quitLoop) {
while (!_stub->_pi.quit) {
int selectedItem = -1;
int previousLanguage = currentLanguage;
@ -398,7 +396,6 @@ void Menu::handleTitleScreen() {
_charVar3 = 1;
_charVar4 = 2;
currentEntry = 0;
_currentScreen = _nextScreen;
_nextScreen = -1;
}
@ -444,30 +441,27 @@ void Menu::handleTitleScreen() {
_selectedOption = menuItems[selectedItem].opt;
switch (_selectedOption) {
case MENU_OPTION_ITEM_START:
quitLoop = true;
break;
return;
case MENU_OPTION_ITEM_SKILL:
_currentScreen = SCREEN_SKILL;
handleSkillScreen();
break;
case MENU_OPTION_ITEM_PASSWORD:
_currentScreen = SCREEN_PASSWORD;
quitLoop = handlePasswordScreen();
if (handlePasswordScreen()) {
return;
}
break;
case MENU_OPTION_ITEM_LEVEL:
_currentScreen = SCREEN_LEVEL;
quitLoop = handleLevelScreen();
if (handleLevelScreen()) {
return;
}
break;
case MENU_OPTION_ITEM_INFO:
_currentScreen = SCREEN_INFO;
handleInfoScreen();
break;
case MENU_OPTION_ITEM_DEMO:
quitLoop = true;
break;
return;
case MENU_OPTION_ITEM_QUIT:
quitLoop = true;
break;
return;
}
_nextScreen = SCREEN_TITLE;
continue;
@ -497,9 +491,6 @@ void Menu::handleTitleScreen() {
_vid->updateScreen();
_stub->sleep(EVENTS_DELAY);
_stub->processEvents();
if (_stub->_pi.quit) {
break;
}
}
}

2
menu.h
View File

@ -1,7 +1,7 @@
/*
* REminiscence - Flashback interpreter
* Copyright (C) 2005-2018 Gregory Montoir (cyx@users.sourceforge.net)
* Copyright (C) 2005-2019 Gregory Montoir (cyx@users.sourceforge.net)
*/
#ifndef MENU_H__

41
midi_driver.h Normal file
View File

@ -0,0 +1,41 @@
#ifndef MIDI_DRIVER_H__
#define MIDI_DRIVER_H__
struct MidiDriver {
virtual ~MidiDriver() {};
virtual int init() = 0;
virtual void fini() = 0;
virtual void reset(int rate) = 0;
virtual void setInstrumentData(int channel, int num, const void *data) { // fmopl only
}
virtual void noteOff(int channel, int note, int velocity) = 0;
virtual void noteOn(int channel, int note, int velocity) = 0;
virtual void controlChange(int channel, int type, int value) = 0;
virtual void programChange(int channel, int num) = 0;
virtual void pitchBend(int channel, int lsb, int msb) = 0;
virtual void readSamples(short *samples, int len) = 0;
};
struct MidiDriverInfo {
const char *name;
MidiDriver *(*create)();
};
#ifndef MIDI_DRIVER_SYMBOL
#ifdef _WIN32
#define MIDI_DRIVER_SYMBOL __declspec(dllexport)
#else
#define MIDI_DRIVER_SYMBOL
#endif
#endif
MIDI_DRIVER_SYMBOL extern const MidiDriverInfo midi_driver_adlib;
MIDI_DRIVER_SYMBOL extern const MidiDriverInfo midi_driver_mt32;
#endif /* MIDI_DRIVER_H__ */

369
midi_driver_adlib.cpp Normal file
View File

@ -0,0 +1,369 @@
#include <assert.h>
#include <stdio.h>
#include <stdint.h>
#include "midi_driver.h"
extern "C" {
#include "opl3.c"
}
#define NUM_CHANNELS 11 /* rhythm mode */
static opl3_chip _opl3;
static const int kMusicVolume = 63; // 44;
/* Two-operator melodic and percussion mode */
static const uint8_t _adlibOperatorIndexTable[] = { 0, 3, 1, 4, 2, 5, 6, 9, 7, 0xA, 8, 0xB, 0xC, 0xF, 0x10, 0x10, 0xE, 0xE, 0x11, 0x11, 0xD, 0xD };
static const uint8_t _adlibOperatorTable[] = { 0, 1, 2, 3, 4, 5, 8, 9, 0xA, 0xB, 0xC, 0xD, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15 };
static const uint16_t _adlibFrequencyTable[] = {
0x157, 0x16C, 0x181, 0x198, 0x1B1, 0x1CB, 0x1E6, 0x203, 0x222, 0x243, 0x266, 0x28A
};
static struct {
uint8_t midi_channel; // track_num
uint8_t note;
uint8_t hw_channel;
const uint8_t *instrument;
} _adlibPlayingNoteTable[NUM_CHANNELS];
static int16_t _adlibChannelPlayingTable[NUM_CHANNELS]; // could be a bitmask
static int16_t _adlibChannelVolumeTable[NUM_CHANNELS];
static uint8_t _regBD = 0x20;
static const uint8_t *_currentInstrumentData;
static uint16_t READ_LE_UINT16(const uint8_t *p) {
return p[0] | (p[1] << 8);
}
struct MidiDriver_adlib: MidiDriver {
virtual int init() {
return 0;
}
virtual void fini() {
}
virtual void reset(int rate) {
for (int i = 0; i < NUM_CHANNELS; ++i) {
_adlibPlayingNoteTable[i].midi_channel = 0xFF;
_adlibChannelPlayingTable[i] = 0;
_adlibChannelVolumeTable[i] = 10;
}
OPL3_Reset(&_opl3, rate);
adlibReset();
}
virtual void setInstrumentData(int channel, int num, const void *data) {
// fprintf(stdout, "instrument channel:%d num:%d data:%p\n", channel, num, data);
assert(data);
_currentInstrumentData = (const uint8_t *)data;
}
virtual void noteOff(int channel, int note, int velocity) {
// fprintf(stdout, "noteOff %d channel %d\n", note, channel);
for (int i = 0; i < NUM_CHANNELS; ++i) {
if (_adlibPlayingNoteTable[i].midi_channel != channel) {
continue;
}
if (_adlibPlayingNoteTable[i].note != note) {
//fprintf(stdout, "WARNING: noteOff: channel %d is playing note %d (expected %d)\n", channel, _adlibPlayingNoteTable[i].note, note);
continue;
}
_adlibPlayingNoteTable[i].midi_channel = 0xFF;
const int hw_channel = _adlibPlayingNoteTable[i].hw_channel;
//fprintf(stdout, "free hw_channel %d\n", hw_channel);
_adlibChannelPlayingTable[hw_channel] = 0;
if (_adlibPlayingNoteTable[i].instrument != _currentInstrumentData) {
// fprintf(stdout, "WARNING: noteOff: instrument changed for channel %d hw_channel %d\n", channel, hw_channel);
}
if (_adlibPlayingNoteTable[i].instrument[0] == 0) { //_currentInstrumentData[0] == 0) {
adlibMelodicKeyOff(hw_channel, note);
} else {
adlibPercussionKeyOff(hw_channel);
}
return;
}
// fprintf(stdout, "WARNING: noteOff: channel %d note %d not found\n", channel, note);
}
virtual void noteOn(int channel, int note, int velocity) {
if (_currentInstrumentData[0] == 0) {
/* melodic channel */
int i;
for (i = 0; i < 6; ++i) {
if (_adlibChannelPlayingTable[i] == 0) {
break;
}
}
if (i == 6) {
// fprintf(stdout, "No free melodic channel found (A) 6 voices busy, track %d note %d\n", channel, note);
return;
}
const int hw_channel = i;
_adlibChannelPlayingTable[hw_channel] = 0xFF;
for (i = 0; i < NUM_CHANNELS; ++i) {
if (_adlibPlayingNoteTable[i].midi_channel == 0xFF) {
break;
}
}
if (i == NUM_CHANNELS) {
_adlibChannelPlayingTable[hw_channel] = 0;
// fprintf(stdout, "No free channel found (B)\n");
return;
}
_adlibPlayingNoteTable[i].midi_channel = channel;
_adlibPlayingNoteTable[i].note = note;
_adlibPlayingNoteTable[i].hw_channel = hw_channel;
_adlibPlayingNoteTable[i].instrument = _currentInstrumentData;
_adlibChannelVolumeTable[hw_channel] = velocity;
adlibSetupInstrument(hw_channel, _currentInstrumentData);
adlibMelodicKeyOn(hw_channel, velocity, note);
} else {
/* percussion channel */
const int hw_channel = _currentInstrumentData[1]; // channel
assert(hw_channel >= 6 && hw_channel <= 10);
if (_adlibChannelPlayingTable[hw_channel] != 0) {
// fprintf(stdout, "No free channel found (C) hw_channel %d for track %d note %d\n", hw_channel, channel, note);
return;
}
_adlibChannelPlayingTable[hw_channel] = 0xFF;
int i;
for (i = 6; i < NUM_CHANNELS; ++i) {
if (_adlibPlayingNoteTable[i].midi_channel == 0xFF) {
break;
}
}
if (i == NUM_CHANNELS) {
_adlibChannelPlayingTable[hw_channel] = 0;
// fprintf(stdout, "No free channel found (D) hw_channel %d\n", hw_channel);
return;
}
//fprintf(stdout, "Adding percussion channel %d note %d index %d track %d\n", hw_channel, note, i, channel);
_adlibPlayingNoteTable[i].midi_channel = channel;
_adlibPlayingNoteTable[i].note = note;
_adlibPlayingNoteTable[i].hw_channel = hw_channel;
_adlibPlayingNoteTable[i].instrument = _currentInstrumentData;
_adlibChannelVolumeTable[hw_channel] = velocity;
adlibSetupInstrument(hw_channel, _currentInstrumentData);
adlibPercussionKeyOn(hw_channel, velocity, note);
}
}
virtual void controlChange(int channel, int type, int value) {
}
virtual void programChange(int channel, int num) {
}
virtual void pitchBend(int channel, int lsb, int msb) {
adlibPitchBend(channel, (msb << 7) | lsb);
}
virtual void readSamples(int16_t *samples, int len) {
OPL3_GenerateStream(&_opl3, samples, len);
}
void adlibSetupInstrument(int channel, const uint8_t *arg0) {
const int volume = (_adlibChannelVolumeTable[channel] * kMusicVolume) >> 6;
// fprintf(stdout, "instrument channel:%d volume:%d\n", channel, volume);
const int mode = arg0[0];
const int hw_channel = arg0[1];
const int num = (mode != 0) ? hw_channel : channel;
const uint8_t modulatorOperator = _adlibOperatorTable[_adlibOperatorIndexTable[num * 2]];
const uint8_t carrierOperator = _adlibOperatorTable[_adlibOperatorIndexTable[num * 2 + 1]];
//fprintf(stdout, "operator modulator %d carrier %d\n", modulatorOperator, carrierOperator);
if (mode == 0 || hw_channel == 6) { // else goto loc_F24_284E5
// modulator
const uint8_t *bx = arg0 + 6;
uint16_t reg2x = 0;
if (READ_LE_UINT16(bx + 0x12) != 0) { // amplitude vibrato
reg2x |= 0x80;
}
if (READ_LE_UINT16(bx + 0x14) != 0) { // frequency vibrato
reg2x |= 0x40;
}
if (READ_LE_UINT16(bx + 0x0A) != 0) { // sustaining sound
reg2x |= 0x20;
}
if (READ_LE_UINT16(bx + 0x16) != 0) { // envelope scaling
reg2x |= 0x10;
}
reg2x |= READ_LE_UINT16(bx + 2) & 15; // frequency multiplier
writeRegister(0x20 + modulatorOperator, reg2x);
uint16_t dx;
if (READ_LE_UINT16(bx + 0x18) != 0) { // frequency modulation
dx = READ_LE_UINT16(bx + 0x10) & 63;
} else {
const int ax = (63 - (READ_LE_UINT16(bx + 0x10) & 63)) * volume; // output level
dx = 63 - (((ax + 127) + ax) / 254);
}
const uint8_t reg4x = dx | (READ_LE_UINT16(bx) << 6);
writeRegister(0x40 + modulatorOperator, reg4x);
writeRegister(0x60 + modulatorOperator, (READ_LE_UINT16(bx + 12) & 15) | (READ_LE_UINT16(bx + 6) << 4));
writeRegister(0x80 + modulatorOperator, (READ_LE_UINT16(bx + 14) & 15) | (READ_LE_UINT16(bx + 8) << 4));
uint16_t ax = 1;
if (READ_LE_UINT16(bx + 0x18) != 0) {
ax = 0;
}
dx = (READ_LE_UINT16(bx + 4) << 1) | ax;
ax = 0xC0 + ((mode != 0) ? hw_channel : channel);
writeRegister(ax, dx);
writeRegister(0xE0 + modulatorOperator, arg0[2] & 3); /* original writes to 0xE, typo */
}
// carrier
const uint8_t *bx = arg0 + 0x20; // 26 + 6
uint8_t reg2x = 0;
if (READ_LE_UINT16(bx + 0x12) != 0) {
reg2x |= 0x80;
}
if (READ_LE_UINT16(bx + 0x14) != 0) {
reg2x |= 0x40;
}
if (READ_LE_UINT16(bx + 0x0A) != 0) {
reg2x |= 0x20;
}
if (READ_LE_UINT16(bx + 0x16) != 0) {
reg2x |= 0x10;
}
reg2x |= (READ_LE_UINT16(bx + 2) & 15);
writeRegister(0x20 + carrierOperator, reg2x);
const int ax = (63 - (READ_LE_UINT16(bx + 0x10) & 63)) * volume;
int dx = 63 - (((ax + 127) + ax) / 254);
const uint8_t reg4x = dx | (READ_LE_UINT16(bx) << 6);
writeRegister(0x40 + carrierOperator, reg4x);
writeRegister(0x60 + carrierOperator, (READ_LE_UINT16(bx + 12) & 15) | (READ_LE_UINT16(bx + 6) << 4));
writeRegister(0x80 + carrierOperator, (READ_LE_UINT16(bx + 14) & 15) | (READ_LE_UINT16(bx + 8) << 4));
writeRegister(0xE0 + carrierOperator, arg0[3] & 3); /* original writes to 0xE, typo */
}
void adlibPercussionKeyOn(int channel, int var6, int note) {
// 6: Bass Drum
// 7: Snare Drum
// 8: Tom-Tom
// 9: Cymbal
// 10: Hi-Hat
assert(channel >= 6 && channel <= 10);
_regBD &= ~(1 << (10 - channel));
writeRegister(0xBD, _regBD);
int hw_channel = channel;
if (channel == 9) {
hw_channel = 8;
} else if (channel == 10) {
hw_channel = 7;
}
const uint16_t freq = _adlibFrequencyTable[note % 12];
writeRegister(0xA0 + hw_channel, freq);
const uint8_t regBx = ((note / 12) << 2) | ((freq & 0x300) >> 8);
writeRegister(0xB0 + hw_channel, regBx);
_regBD |= 1 << (10 - channel);
writeRegister(0xBD, _regBD);
}
void adlibMelodicKeyOn(int channel, int volume, int note) {
writeRegister(0xB0 + channel, 0);
const uint16_t freq = _adlibFrequencyTable[note % 12];
writeRegister(0xA0 + channel, freq & 0xFF); /* Frequency (Low) */
const uint8_t octave = (note / 12);
const uint8_t regBx = 0x20 | (octave << 2) | ((freq & 0x300) >> 8); /* Key-On | Octave | Frequency (High) */
writeRegister(0xB0 + channel, regBx);
}
void adlibMelodicKeyOff(int channel, int note) {
const uint16_t freq = _adlibFrequencyTable[note % 12];
const uint8_t octave = (note / 12);
const uint8_t regBx = (octave << 2) | ((freq & 0x300) >> 8); /* Octave | Frequency (High) */
writeRegister(0xB0 + channel, regBx);
}
void adlibPercussionKeyOff(int channel) {
assert(channel >= 6 && channel <= 10);
_regBD &= ~(1 << (10 - channel));
writeRegister(0xBD, _regBD);
}
void adlibPitchBend(int channel, int value) {
//fprintf(stdout, "pitch bend channel:%d value:%d\n", channel, value);
if (value == 0x2000) {
return;
}
if (value & 0x2000) {
value = -(value & 0x1FFF);
}
const int pitchBend = value;
for (int i = 0; i < NUM_CHANNELS; ++i) {
if (_adlibPlayingNoteTable[i].midi_channel != channel) {
continue;
}
const int note = _adlibPlayingNoteTable[i].note;
const int hw_channel = _adlibPlayingNoteTable[i].hw_channel;
static const int kVarC = 0x2000; // / byte_1B73_2BE66;
value = pitchBend / kVarC + note % 12;
int octave = note / 12;
if (value < 0) {
--octave;
value = -value;
} else if (value > 12) {
++octave;
value -= 12;
}
--octave;
const int var14 = ((pitchBend % kVarC) * 25) / kVarC;
const uint16_t freq = var14 + _adlibFrequencyTable[value % 12];
writeRegister(0xA0 + hw_channel, freq & 0xFF);
const uint8_t regBx = 0x20 | (octave << 2) | ((freq & 0x300) >> 8);
writeRegister(0xB0 + hw_channel, regBx);
}
}
void adlibReset() {
for (int i = 0; i < 6; ++i) {
adlibMelodicKeyOff(i, 0);
}
for (int i = 6; i < 11; ++i) {
adlibPercussionKeyOff(i);
}
writeRegister(8, 0x40);
for (int i = 0; i < 18; ++i) {
writeRegister(_adlibOperatorTable[i] + 0x40, 0x3F);
}
for (int i = 0; i < 9; ++i) {
writeRegister(0xB0 + i, 0);
}
for (int i = 0; i < 9; ++i) {
writeRegister(0xC0 + i, 0);
}
for (int i = 0; i < 18; ++i) {
writeRegister(_adlibOperatorTable[i] + 0x60, 0);
}
for (int i = 0; i < 18; ++i) {
writeRegister(_adlibOperatorTable[i] + 0x80, 0);
}
for (int i = 0; i < 18; ++i) {
writeRegister(_adlibOperatorTable[i] + 0x20, 0);
}
for (int i = 0; i < 18; ++i) {
writeRegister(_adlibOperatorTable[i] + 0xE0, 0);
}
writeRegister(1, 0x20);
writeRegister(1, 0);
for (int i = 0; i < 9; ++i) {
writeRegister(0xB0 + i, 0);
writeRegister(0xA0 + i, 0);
}
writeRegister(0xBD, 0);
_regBD = 0x20;
}
void writeRegister(uint8_t port, uint8_t value) {
//fprintf(stdout, "0x%02x:0x%02x\n", port, value);
OPL3_WriteReg(&_opl3, port, value);
}
};
static MidiDriver *createMidiDriver() {
return new MidiDriver_adlib;
}
const MidiDriverInfo midi_driver_adlib = {
"Adlib",
createMidiDriver
};

87
midi_driver_mt32.cpp Normal file
View File

@ -0,0 +1,87 @@
#include <stdio.h>
#include <stdint.h>
#include "midi_driver.h"
#define MT32EMU_API_TYPE 1
#include <mt32emu.h>
static const bool _isCM32L = false;
static const char *CM32L_CONTROL_FILENAME = "CM32L_CONTROL.ROM";
static const char *CM32L_PCM_FILENAME = "CM32L_PCM.ROM";
static const char *MT32_CONTROL_FILENAME = "MT32_CONTROL.ROM";
static const char *MT32_PCM_FILENAME = "MT32_PCM.ROM";
static uint32_t msg(int cmd, int param1 = 0, int param2 = 0) {
return (param2 << 16) | (param1 << 8) | cmd;
}
struct MidiDriver_mt32emu: MidiDriver {
virtual int init() {
mt32emu_report_handler_i report = { 0 };
_context = mt32emu_create_context(report, this);
const char *control = _isCM32L ? CM32L_CONTROL_FILENAME : MT32_CONTROL_FILENAME;
if (mt32emu_add_rom_file(_context, control) != MT32EMU_RC_ADDED_CONTROL_ROM) {
fprintf(stdout, "WARNING: Failed to add MT32 rom file '%s'", control);
return -1;
}
const char *pcm = _isCM32L ? CM32L_PCM_FILENAME : MT32_PCM_FILENAME;
if (mt32emu_add_rom_file(_context, pcm) != MT32EMU_RC_ADDED_PCM_ROM) {
fprintf(stdout, "WARNING: Failed to add MT32 rom file '%s'", pcm);
return -1;
}
mt32emu_rom_info romInfo;
mt32emu_get_rom_info(_context, &romInfo);
fprintf(stdout, "mt32emu version %s\n", mt32emu_get_library_version_string());
//fprintf(stdout, "control rom %s %s\n", romInfo.control_rom_id, romInfo.control_rom_description);
//fprintf(stdout, "pcm rom %s %s\n", romInfo.pcm_rom_id, romInfo.pcm_rom_description);
mt32emu_set_midi_delay_mode(_context, MT32EMU_MDM_IMMEDIATE);
return 0;
}
virtual void fini() {
if (mt32emu_is_open(_context)) {
mt32emu_close_synth(_context);
}
mt32emu_free_context(_context);
}
virtual void reset(int rate) {
mt32emu_set_stereo_output_samplerate(_context, rate);
if (mt32emu_is_open(_context)) {
mt32emu_close_synth(_context);
}
mt32emu_open_synth(_context);
}
virtual void noteOff(int channel, int note, int velocity) {
mt32emu_play_msg(_context, msg(0x80 | channel, note, velocity));
}
virtual void noteOn(int channel, int note, int velocity) {
mt32emu_play_msg(_context, msg(0x90 | channel, note, velocity));
}
virtual void controlChange(int channel, int type, int value) {
mt32emu_play_msg(_context, msg(0xB0 | channel, type, value));
}
virtual void programChange(int channel, int num) {
mt32emu_play_msg(_context, msg(0xC0 | channel, num));
}
virtual void pitchBend(int channel, int lsb, int msb) {
mt32emu_play_msg(_context, msg(0xE0 | channel, lsb, msb));
}
virtual void readSamples(int16_t *samples, int len) {
mt32emu_render_bit16s(_context, samples, len);
}
mt32emu_context _context;
};
static MidiDriver *createMidiDriver() {
return new MidiDriver_mt32emu;
}
const MidiDriverInfo midi_driver_mt32 = {
"MT32",
createMidiDriver
};

114
midi_parser.cpp Normal file
View File

@ -0,0 +1,114 @@
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include "file.h"
#include "midi_parser.h"
#include "util.h"
void MidiTrack::rewind() {
endOfTrack = false;
offset = 0;
}
static uint32_t readVLQ(const uint8_t *data, uint32_t& offset) {
uint32_t value = 0;
for (int i = 0; i < 4; ++i) {
const uint8_t b = data[offset++];
value = (value << 7) | (b & 0x7F);
if ((b & 0x80) == 0) {
break;
}
}
return value;
}
const MidiEvent *MidiTrack::nextEvent() {
while (offset < size) {
MidiEvent &ev = event;
ev.timestamp = readVLQ(data, offset);
ev.command = data[offset++];
switch (ev.command & 0xF0) {
case MIDI_COMMAND_NOTE_OFF:
case MIDI_COMMAND_NOTE_ON:
ev.param1 = data[offset++];
ev.param2 = data[offset++];
debug(DBG_MIDI, "Note %s: note %d vel %d timestamp %d", (((ev.command & 0xF0) == MIDI_COMMAND_NOTE_OFF) ? "Off" : "On"), ev.param1, ev.param2, ev.timestamp);
break;
case MIDI_COMMAND_PROGRAM_CHANGE:
ev.param1 = data[offset++];
debug(DBG_MIDI, "Program %d timestamp %d", ev.param1, ev.timestamp);
break;
case MIDI_COMMAND_CHANNEL_PRESSURE:
ev.param1 = data[offset++];
debug(DBG_MIDI, "Channel pressure %d timestamp %d", ev.param1, ev.timestamp);
break;
case MIDI_COMMAND_PITCH_BEND:
ev.param1 = data[offset++];
ev.param2 = data[offset++];
debug(DBG_MIDI, "Pitch Bend %d,%d", ev.param1, ev.param2);
break;
case MIDI_COMMAND_SYSEX:
if (ev.command == 0xFF) {
const int num = data[offset++];
const int len = readVLQ(data, offset);
offset += len;
debug(DBG_MIDI, "SysEx timestamp %d", ev.timestamp);
assert(ev.timestamp == 0);
if (num == MIDI_SYSEX_END_OF_TRACK) {
endOfTrack = true;
return 0;
}
continue;
}
/* fall-through */
default:
warning("Unhandled MIDI command 0x%x", ev.command);
break;
}
return &event;
}
return 0;
}
MidiParser::MidiParser() {
memset(_tracks, 0, sizeof(_tracks));
_tracksCount = 0;
}
void MidiParser::loadMid(File *f) {
for (int i = 0; i < MAX_TRACKS; ++i) {
free(_tracks[i].data);
_tracks[i].data = 0;
}
memset(_tracks, 0, sizeof(_tracks));
_tracksCount = 0;
char tag[4];
f->read(tag, 4);
assert(memcmp(tag, "MThd", 4) == 0);
const uint32_t len = f->readUint32BE();
assert(len == 6);
f->readByte();
const int type = f->readByte();
const int tracksCount = f->readUint16BE();
assert(tracksCount <= MAX_TRACKS);
const int ppqn = f->readUint16BE();
debug(DBG_MIDI, "MThd type %d tracks %d ppqn %d len %d", type, tracksCount, ppqn, len);
for (int i = 0; i < tracksCount; ++i) {
f->read(tag, 4);
assert(memcmp(tag, "MTrk", 4) == 0);
const uint32_t len = f->readUint32BE();
debug(DBG_MIDI, "Track #%d len %d", i, len);
MidiTrack *track = &_tracks[i];
track->data = (uint8_t *)malloc(len);
if (!track->data) {
warning("Failed to allocate %d bytes for MIDI track %d", len, i);
} else {
track->endOfTrack = false;
track->offset = 0;
track->size = len;
f->read(track->data, len);
}
}
_tracksCount = tracksCount;
}

47
midi_parser.h Normal file
View File

@ -0,0 +1,47 @@
#ifndef MIDI_PARSER_H__
#define MIDI_PARSER_H__
#include <stdint.h>
#define MIDI_COMMAND_NOTE_OFF 0x80
#define MIDI_COMMAND_NOTE_ON 0x90
#define MIDI_COMMAND_PROGRAM_CHANGE 0xC0
#define MIDI_COMMAND_CHANNEL_PRESSURE 0xD0 /* unused */
#define MIDI_COMMAND_PITCH_BEND 0xE0
#define MIDI_COMMAND_SYSEX 0xF0 /* unused */
#define MIDI_SYSEX_END_OF_TRACK 0x2F
struct MidiEvent {
uint32_t timestamp;
uint8_t command;
uint8_t param1;
uint8_t param2;
};
struct MidiTrack {
bool endOfTrack;
uint8_t *data;
uint32_t offset, size;
MidiEvent event;
void rewind();
const MidiEvent *nextEvent();
};
struct File;
#define MAX_TRACKS 16
struct MidiParser {
MidiParser();
void loadMid(File *f);
int _tracksCount;
MidiTrack _tracks[MAX_TRACKS];
};
#endif /* MIDI_PARSER_H__ */

100
mixer.cpp
View File

@ -1,20 +1,23 @@
/*
* REminiscence - Flashback interpreter
* Copyright (C) 2005-2018 Gregory Montoir (cyx@users.sourceforge.net)
* Copyright (C) 2005-2019 Gregory Montoir (cyx@users.sourceforge.net)
*/
#include "mixer.h"
#include "systemstub.h"
#include "util.h"
Mixer::Mixer(FileSystem *fs, SystemStub *stub)
: _stub(stub), _musicType(MT_NONE), _mod(this, fs), _ogg(this, fs), _sfx(this) {
Mixer::Mixer(FileSystem *fs, SystemStub *stub, int midiDriver)
: _stub(stub), _musicType(MT_NONE), _cpc(this, fs), _mod(this, fs), _ogg(this, fs), _prf(this, fs, midiDriver), _sfx(this) {
_musicTrack = -1;
_backgroundMusicType = MT_NONE;
}
void Mixer::init() {
memset(_channels, 0, sizeof(_channels));
for (int i = 0; i < NUM_CHANNELS; ++i) {
_channels[i].active = false;
}
_premixHook = 0;
_stub->startAudio(Mixer::mixCallback, this);
}
@ -32,15 +35,16 @@ void Mixer::setPremixHook(PremixHook premixHook, void *userData) {
_premixHookData = userData;
}
void Mixer::play(const MixerChunk *mc, uint16_t freq, uint8_t volume) {
void Mixer::play(const uint8_t *data, uint32_t len, 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) {
if (cur->chunk.data == data) {
cur->chunkPos = 0;
cur->volume = volume;
return;
}
} else {
@ -51,18 +55,19 @@ void Mixer::play(const MixerChunk *mc, uint16_t freq, uint8_t volume) {
if (ch) {
ch->active = true;
ch->volume = volume;
ch->chunk = *mc;
ch->chunk.data = data;
ch->chunk.len = len;
ch->chunkPos = 0;
ch->chunkInc = (freq << FRAC_BITS) / _stub->getOutputSampleRate();
}
}
bool Mixer::isPlaying(const MixerChunk *mc) const {
bool Mixer::isPlaying(const uint8_t *data) 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) {
if (ch->active && ch->chunk.data == data) {
return true;
}
}
@ -85,23 +90,27 @@ 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;
}
}
void Mixer::playMusic(int num, int tempo) {
debug(DBG_SND, "Mixer::playMusic(%d, %d)", num, tempo);
int trackNum = -1;
if (num == 1) { // menu screen
if (_ogg.playTrack(2)) {
_musicType = MT_OGG;
_musicTrack = 2;
trackNum = 2;
} else if (num > MUSIC_TRACK) {
trackNum = num - MUSIC_TRACK;
}
if (trackNum != -1 && trackNum != _musicTrack) {
if (_ogg.playTrack(trackNum)) {
_backgroundMusicType = _musicType = MT_OGG;
_musicTrack = trackNum;
return;
}
if (_cpc.playTrack(trackNum)) {
_backgroundMusicType = _musicType = MT_CPC;
_musicTrack = trackNum;
return;
}
}
if (_musicType == MT_OGG && isMusicSfx(num)) { // do not play level action music with background music
if ((_musicType == MT_OGG || _musicType == MT_CPC) && isMusicSfx(num)) { // do not play level action music with background music
return;
}
if (isMusicSfx(num)) { // level action sequence
@ -110,9 +119,17 @@ void Mixer::playMusic(int num) {
_musicType = MT_SFX;
}
} else { // cutscene
_mod.play(num);
_mod.play(num, tempo);
if (_mod._playing) {
_musicType = MT_MOD;
return;
}
if (g_options.use_prf_music) {
_prf.play(num);
if (_prf._playing) {
_musicType = MT_PRF;
return;
}
}
}
}
@ -128,17 +145,35 @@ void Mixer::stopMusic() {
case MT_OGG:
_ogg.pauseTrack();
break;
case MT_PRF:
_prf.stop();
break;
case MT_SFX:
_sfx.stop();
break;
case MT_CPC:
_cpc.pauseTrack();
break;
}
_musicType = MT_NONE;
if (_musicTrack != -1) {
_ogg.resumeTrack();
_musicType = MT_OGG;
if (_musicTrack > 2) { // do not resume menu music
switch (_backgroundMusicType) {
case MT_OGG:
_ogg.resumeTrack();
_musicType = MT_OGG;
break;
case MT_CPC:
_cpc.resumeTrack();
_musicType = MT_CPC;
break;
default:
break;
}
}
}
static const bool kUseNr = false;
static void nr(int16_t *buf, int len) {
static int prev = 0;
for (int i = 0; i < len; ++i) {
@ -159,17 +194,22 @@ void Mixer::mix(int16_t *out, int len) {
MixerChannel *ch = &_channels[i];
if (ch->active) {
for (int pos = 0; pos < len; ++pos) {
if ((ch->chunkPos >> FRAC_BITS) >= (ch->chunk.len - 1)) {
const uint32_t cpos = ch->chunkPos >> FRAC_BITS;
if (cpos >= ch->chunk.len) {
ch->active = false;
break;
}
const int sample = ch->chunk.getPCM(ch->chunkPos >> FRAC_BITS);
out[pos] = ADDC_S16(out[pos], (sample * ch->volume / Mixer::MAX_VOLUME) << 8);
const int sample8 = ch->chunk.getPCM(cpos) * ch->volume / Mixer::MAX_VOLUME;
const int sample16 = S8_to_S16(sample8);
out[2 * pos] = ADDC_S16(out[2 * pos], sample16);
out[2 * pos + 1] = ADDC_S16(out[2 * pos + 1], sample16);
ch->chunkPos += ch->chunkInc;
}
}
}
nr(out, len);
if (kUseNr) {
nr(out, len * 2); // stereo
}
}
void Mixer::mixCallback(void *param, int16_t *buf, int len) {

19
mixer.h
View File

@ -1,19 +1,21 @@
/*
* REminiscence - Flashback interpreter
* Copyright (C) 2005-2018 Gregory Montoir (cyx@users.sourceforge.net)
* Copyright (C) 2005-2019 Gregory Montoir (cyx@users.sourceforge.net)
*/
#ifndef MIXER_H__
#define MIXER_H__
#include "intern.h"
#include "cpc_player.h"
#include "mod_player.h"
#include "ogg_player.h"
#include "prf_player.h"
#include "sfx_player.h"
struct MixerChunk {
uint8_t *data;
const uint8_t *data;
uint32_t len;
MixerChunk()
@ -48,7 +50,9 @@ struct Mixer {
MT_NONE,
MT_MOD,
MT_OGG,
MT_PRF,
MT_SFX,
MT_CPC,
};
enum {
@ -63,21 +67,24 @@ struct Mixer {
MixerChannel _channels[NUM_CHANNELS];
PremixHook _premixHook;
void *_premixHookData;
MusicType _backgroundMusicType;
MusicType _musicType;
CpcPlayer _cpc;
ModPlayer _mod;
OggPlayer _ogg;
PrfPlayer _prf;
SfxPlayer _sfx;
int _musicTrack;
Mixer(FileSystem *fs, SystemStub *stub);
Mixer(FileSystem *fs, SystemStub *stub, int midiDriver);
void init();
void free();
void setPremixHook(PremixHook premixHook, void *userData);
void play(const MixerChunk *mc, uint16_t freq, uint8_t volume);
bool isPlaying(const MixerChunk *mc) const;
void play(const uint8_t *data, uint32_t len, uint16_t freq, uint8_t volume);
bool isPlaying(const uint8_t *data) const;
uint32_t getSampleRate() const;
void stopAll();
void playMusic(int num);
void playMusic(int num, int tempo = 0);
void stopMusic();
void mix(int16_t *buf, int len);

View File

@ -1,7 +1,7 @@
/*
* REminiscence - Flashback interpreter
* Copyright (C) 2005-2018 Gregory Montoir (cyx@users.sourceforge.net)
* Copyright (C) 2005-2019 Gregory Montoir (cyx@users.sourceforge.net)
*/
#include "file.h"
@ -16,6 +16,7 @@ struct ModPlayer_impl {
ModPlugFile *_mf;
ModPlug_Settings _settings;
int _songTempo;
bool _repeatIntro;
ModPlayer_impl()
@ -26,7 +27,7 @@ struct ModPlayer_impl {
memset(&_settings, 0, sizeof(_settings));
ModPlug_GetSettings(&_settings);
_settings.mFlags = MODPLUG_ENABLE_OVERSAMPLING | MODPLUG_ENABLE_NOISE_REDUCTION;
_settings.mChannels = 1;
_settings.mChannels = 2;
_settings.mBits = 16;
_settings.mFrequency = rate;
_settings.mResamplingMode = MODPLUG_RESAMPLE_FIR;
@ -40,6 +41,7 @@ struct ModPlayer_impl {
if (data) {
f->read(data, size);
_mf = ModPlug_Load(data, size);
free(data);
}
return _mf != 0;
}
@ -58,7 +60,7 @@ struct ModPlayer_impl {
ModPlug_SeekOrder(_mf, 1);
_repeatIntro = false;
}
const int count = ModPlug_Read(_mf, buf, len * sizeof(int16_t));
const int count = ModPlug_Read(_mf, buf, len * sizeof(int16_t) * 2);
// setting mLoopCount to non-zero does not trigger any looping in
// my test and ModPlug_Read returns 0.
// looking at the libmodplug-0.8.8 tarball, it seems the variable
@ -81,6 +83,7 @@ struct ModPlayer_impl {
NUM_TRACKS = 4,
NUM_PATTERNS = 128,
FRAC_BITS = 12,
BASE_TEMPO = 125,
PAULA_FREQ = 3546897
};
@ -203,17 +206,20 @@ bool ModPlayer_impl::load(File *f) {
f->read(_modInfo.patternOrderTable, NUM_PATTERNS);
f->readUint32BE(); // 'M.K.', Protracker, 4 channels
uint16_t n = 0;
uint8_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);
const int patternsSize = (n + 1) * 64 * 4 * 4; // 64 lines of 4 notes per channel
_modInfo.patternsTable = (uint8_t *)malloc(patternsSize);
if (!_modInfo.patternsTable) {
warning("Unable to allocate %d bytes for .MOD patterns table", patternsSize);
return false;
}
f->read(_modInfo.patternsTable, patternsSize);
for (int s = 0; s < NUM_SAMPLES; ++s) {
SampleInfo *si = &_modInfo.samples[s];
@ -230,7 +236,7 @@ bool ModPlayer_impl::load(File *f) {
_currentTick = 0;
_patternDelay = 0;
_songSpeed = 6;
_songTempo = 125;
_songTempo = BASE_TEMPO;
_patternLoopPos = 0;
_patternLoopCount = -1;
_samplesLeft = 0;
@ -502,6 +508,7 @@ void ModPlayer_impl::handleEffect(int trackNum, bool tick) {
tk->volume = 0;
}
}
break;
case 0xD: // delay sample
if (!tick) {
tk->delayCounter = effectY;
@ -612,8 +619,11 @@ void ModPlayer_impl::mixSamples(int16_t *buf, int samplesLen) {
curLen = 0;
}
while (count--) {
const int out = si->getPCM(pos >> FRAC_BITS);
*mixbuf = ADDC_S16(*mixbuf, (out * tk->volume / 64) << 8);
const int sample8 = si->getPCM(pos >> FRAC_BITS) * tk->volume / 64;
const int sample16 = S8_to_S16(sample8);
*mixbuf = ADDC_S16(*mixbuf, sample16);
++mixbuf;
*mixbuf = ADDC_S16(*mixbuf, sample16);
++mixbuf;
pos += deltaPos;
}
@ -624,9 +634,9 @@ void ModPlayer_impl::mixSamples(int16_t *buf, int samplesLen) {
}
bool ModPlayer_impl::mix(int16_t *buf, int len) {
memset(buf, 0, sizeof(int16_t) * len);
memset(buf, 0, sizeof(int16_t) * len * 2); // stereo
if (_playing) {
const int samplesPerTick = _mixingRate / (50 * _songTempo / 125);
const int samplesPerTick = _mixingRate / (50 * _songTempo / BASE_TEMPO);
while (len != 0) {
if (_samplesLeft == 0) {
handleTick();
@ -639,7 +649,7 @@ bool ModPlayer_impl::mix(int16_t *buf, int len) {
_samplesLeft -= count;
len -= count;
mixSamples(buf, count);
buf += count;
buf += count * 2; // stereo
}
}
return _playing;
@ -655,20 +665,24 @@ ModPlayer::~ModPlayer() {
delete _impl;
}
void ModPlayer::play(int num) {
if (num < _modulesFilesCount) {
void ModPlayer::play(int num, int tempo) {
if (num * 2 < _namesCount) {
File f;
for (uint8_t i = 0; i < ARRAYSIZE(_modulesFiles[num]); ++i) {
if (f.open(_modulesFiles[num][i], "rb", _fs)) {
_impl->init(_mix->getSampleRate());
if (_impl->load(&f)) {
_impl->_repeatIntro = (num == 0) && !_isAmiga;
_mix->setPremixHook(mixCallback, _impl);
_playing = true;
}
if (!f.open(_names[num * 2], "rb", _fs)) {
const char *p = _names[num * 2 + 1];
char name[32];
snprintf(name, sizeof(name), "mod.flashback-%s", p ? p : _names[num * 2]);
if (!f.open(name, "rb", _fs)) {
return;
}
}
_impl->init(_mix->getSampleRate());
if (_impl->load(&f)) {
_impl->_songTempo = tempo;
_impl->_repeatIntro = (num == 0) && !_isAmiga;
_mix->setPremixHook(mixCallback, _impl);
_playing = true;
}
}
}
@ -676,7 +690,7 @@ void ModPlayer::stop() {
if (_playing) {
_mix->setPremixHook(0, 0);
_impl->unload();
_playing = true;
_playing = false;
}
}

View File

@ -1,7 +1,7 @@
/*
* REminiscence - Flashback interpreter
* Copyright (C) 2005-2018 Gregory Montoir (cyx@users.sourceforge.net)
* Copyright (C) 2005-2019 Gregory Montoir (cyx@users.sourceforge.net)
*/
#ifndef MOD_PLAYER_H__
@ -16,8 +16,8 @@ struct ModPlayer_impl;
struct ModPlayer {
static const uint16_t _periodTable[];
static const char *_modulesFiles[][2];
static const int _modulesFilesCount;
static const char *_names[];
static const int _namesCount;
bool _isAmiga;
bool _playing;
@ -28,7 +28,7 @@ struct ModPlayer {
ModPlayer(Mixer *mixer, FileSystem *fs);
~ModPlayer();
void play(int num);
void play(int num, int tempo);
void stop();
static bool mixCallback(void *param, int16_t *buf, int len);

View File

@ -1,12 +1,15 @@
/*
* REminiscence - Flashback interpreter
* Copyright (C) 2005-2018 Gregory Montoir (cyx@users.sourceforge.net)
* Copyright (C) 2005-2019 Gregory Montoir (cyx@users.sourceforge.net)
*/
#ifdef USE_TREMOR
#include <tremor/ivorbisfile.h>
#endif
#ifdef USE_STB_VORBIS
#include "stb_vorbis.c"
#endif
#include "file.h"
#include "mixer.h"
#include "ogg_player.h"
@ -67,13 +70,17 @@ struct OggDecoder_impl {
}
}
bool load(VorbisFile *f, int mixerSampleRate) {
bool load(const char *name, FileSystem *fs, int mixerSampleRate) {
if (!_f.open(name, "rb", fs)) {
return false;
}
_f.offset = 0;
ov_callbacks ovcb;
ovcb.read_func = VorbisFile::readHelper;
ovcb.seek_func = VorbisFile::seekHelper;
ovcb.close_func = VorbisFile::closeHelper;
ovcb.tell_func = VorbisFile::tellHelper;
if (ov_open_callbacks(f, &_ovf, 0, 0, ovcb) < 0) {
if (ov_open_callbacks(&_f, &_ovf, 0, 0, ovcb) < 0) {
warning("Invalid .ogg file");
return false;
}
@ -112,8 +119,9 @@ struct OggDecoder_impl {
case 2:
assert((len & 3) == 0);
for (int i = 0; i < len / 2; i += 2) {
const int16_t s16 = (_readBuf[i] + _readBuf[i + 1]) / 2;
*dst = ADDC_S16(*dst, s16);
*dst = ADDC_S16(*dst, _readBuf[i]);
++dst;
*dst = ADDC_S16(*dst, _readBuf[i + 1]);
++dst;
}
break;
@ -121,6 +129,8 @@ struct OggDecoder_impl {
for (int i = 0; i < len / 2; ++i) {
*dst = ADDC_S16(*dst, _readBuf[i]);
++dst;
*dst = ADDC_S16(*dst, _readBuf[i]);
++dst;
}
break;
}
@ -131,6 +141,7 @@ struct OggDecoder_impl {
return count;
}
VorbisFile _f;
OggVorbis_File _ovf;
int _channels;
bool _open;
@ -139,66 +150,168 @@ struct OggDecoder_impl {
};
#endif
#ifdef USE_STB_VORBIS
static const int kMusicVolume = 192;
struct OggDecoder_impl {
OggDecoder_impl()
: _v(0) {
}
~OggDecoder_impl() {
if (_v) {
stb_vorbis_close(_v);
_v = 0;
}
}
bool load(const char *name, FileSystem *fs, int mixerSampleRate) {
if (!_f.open(name, "rb", fs)) {
return false;
}
_count = _f.read(_buffer, sizeof(_buffer));
if (_count > 0) {
int bytes = 0;
int error = 0;
_v = stb_vorbis_open_pushdata(_buffer, _count, &bytes, &error, 0);
if (_v) {
_offset = bytes;
stb_vorbis_info info = stb_vorbis_get_info(_v);
if (info.channels != 2 || (int)info.sample_rate != mixerSampleRate) {
warning("Unhandled ogg/pcm format ch %d rate %d", info.channels, info.sample_rate);
return false;
}
_decodedSamplesLen = 0;
return true;
}
}
return false;
}
int read(int16_t *dst, int samples) {
int total = 0;
if (_decodedSamplesLen != 0) {
const int len = MIN(_decodedSamplesLen, samples);
for (int i = 0; i < len; ++i) {
*dst = ADDC_S16(*dst, ((_decodedSamples[0][i] * kMusicVolume) >> 8));
++dst;
*dst = ADDC_S16(*dst, ((_decodedSamples[1][i] * kMusicVolume) >> 8));
++dst;
}
total += len;
_decodedSamplesLen -= len;
}
while (total < samples) {
int channels = 0;
float **outputs;
int count;
int bytes = stb_vorbis_decode_frame_pushdata(_v, _buffer + _offset, _count - _offset, &channels, &outputs, &count);
if (bytes == 0) {
if (_offset != _count) {
memmove(_buffer, _buffer + _offset, _count - _offset);
_offset = _count - _offset;
} else {
_offset = 0;
}
_count = sizeof(_buffer) - _offset;
bytes = _f.read(_buffer + _offset, _count);
if (bytes < 0) {
break;
}
if (bytes == 0) {
// rewind
_f.seek(0);
_count = _f.read(_buffer, sizeof(_buffer));
stb_vorbis_flush_pushdata(_v);
} else {
_count = _offset + bytes;
}
_offset = 0;
continue;
}
_offset += bytes;
if (channels == 2) {
const int remain = samples - total;
const int len = MIN(count, remain);
for (int i = 0; i < len; ++i) {
const int l = int(outputs[0][i] * 32768 + .5);
const int r = int(outputs[1][i] * 32768 + .5);
*dst = ADDC_S16(*dst, ((l * kMusicVolume) >> 8));
++dst;
*dst = ADDC_S16(*dst, ((r * kMusicVolume) >> 8));
++dst;
}
if (count > remain) {
_decodedSamplesLen = count - remain;
assert(_decodedSamplesLen < 1024);
for (int i = 0; i < _decodedSamplesLen; ++i) {
_decodedSamples[0][i] = int(outputs[0][len + i] * 32768 + .5);
_decodedSamples[1][i] = int(outputs[1][len + i] * 32768 + .5);
}
total = samples;
break;
}
} else {
warning("Invalid decoded data channels %d count %d", channels, count);
}
total += count;
}
return total;
}
uint8_t _buffer[8192];
int16_t _decodedSamples[2][1024];
int _decodedSamplesLen;
uint32_t _offset, _count;
stb_vorbis *_v;
File _f;
};
#endif
OggPlayer::OggPlayer(Mixer *mixer, FileSystem *fs)
: _mix(mixer), _fs(fs), _impl(0) {
: _mix(mixer), _fs(fs) {
_impl = new OggDecoder_impl;
}
OggPlayer::~OggPlayer() {
#ifdef USE_TREMOR
delete _impl;
#endif
_impl = 0;
}
// https://www.amigaremix.com/remix/191
static const char *kMenuThemeRemix = "deadly_cookie_-_flashback.ogg";
bool OggPlayer::playTrack(int num) {
#ifdef USE_TREMOR
stopTrack();
char buf[16];
snprintf(buf, sizeof(buf), "track%02d.ogg", num);
VorbisFile *vf = new VorbisFile;
if (vf->open(buf, "rb", _fs)) {
vf->offset = 0;
_impl = new OggDecoder_impl();
if (_impl->load(vf, _mix->getSampleRate())) {
debug(DBG_INFO, "Playing '%s'", buf);
_mix->setPremixHook(mixCallback, this);
return true;
}
if (_impl->load(buf, _fs, _mix->getSampleRate())
|| (num == 2 && _impl->load(kMenuThemeRemix, _fs, _mix->getSampleRate()))) {
_mix->setPremixHook(mixCallback, this);
return true;
}
delete vf;
#endif
return false;
}
void OggPlayer::stopTrack() {
#ifdef USE_TREMOR
_mix->setPremixHook(0, 0);
delete _impl;
_impl = 0;
#endif
}
void OggPlayer::pauseTrack() {
#ifdef USE_TREMOR
if (_impl) {
_mix->setPremixHook(0, 0);
}
#endif
}
void OggPlayer::pauseTrack() {
if (_impl) {
_mix->setPremixHook(0, 0);
}
}
void OggPlayer::resumeTrack() {
#ifdef USE_TREMOR
if (_impl) {
_mix->setPremixHook(mixCallback, this);
}
#endif
}
bool OggPlayer::mix(int16_t *buf, int len) {
#ifdef USE_TREMOR
if (_impl) {
return _impl->read(buf, len) != 0;
}
#endif
return false;
}

View File

@ -1,7 +1,7 @@
/*
* REminiscence - Flashback interpreter
* Copyright (C) 2005-2018 Gregory Montoir (cyx@users.sourceforge.net)
* Copyright (C) 2005-2019 Gregory Montoir (cyx@users.sourceforge.net)
*/
#ifndef OGG_PLAYER_H__

450
piege.cpp
View File

@ -1,55 +1,55 @@
/*
* REminiscence - Flashback interpreter
* Copyright (C) 2005-2018 Gregory Montoir (cyx@users.sourceforge.net)
* Copyright (C) 2005-2019 Gregory Montoir (cyx@users.sourceforge.net)
*/
#include "cutscene.h"
#include "game.h"
#include "resource.h"
#include "systemstub.h"
#include "util.h"
void Game::pge_resetGroups() {
memset(_pge_groupsTable, 0, sizeof(_pge_groupsTable));
GroupPGE *le = &_pge_groups[0];
_pge_nextFreeGroup = le;
void Game::pge_resetMessages() {
memset(_pge_messagesTable, 0, sizeof(_pge_messagesTable));
MessagePGE *le = &_pge_messages[0];
_pge_nextFreeMessage = le;
int n = 0xFF;
while (n--) {
le->next_entry = le + 1;
le->index = 0;
le->group_id = 0;
le->src_pge = 0;
le->msg_num = 0;
++le;
}
le->next_entry = 0;
le->index = 0;
le->group_id = 0;
le->src_pge = 0;
le->msg_num = 0;
}
void Game::pge_removeFromGroup(uint8_t idx) {
GroupPGE *le = _pge_groupsTable[idx];
void Game::pge_clearMessages(uint8_t pge_index) {
MessagePGE *le = _pge_messagesTable[pge_index];
if (le) {
_pge_groupsTable[idx] = 0;
GroupPGE *next = _pge_nextFreeGroup;
_pge_messagesTable[pge_index] = 0;
MessagePGE *next = _pge_nextFreeMessage;
while (le) {
GroupPGE *cur = le->next_entry;
MessagePGE *cur = le->next_entry;
le->next_entry = next;
le->index = 0;
le->group_id = 0;
le->src_pge = 0;
le->msg_num = 0;
next = le;
le = cur;
}
_pge_nextFreeGroup = next;
_pge_nextFreeMessage = next;
}
}
int Game::pge_isInGroup(LivePGE *pge_dst, uint16_t group_id, uint16_t counter) {
int Game::pge_hasMessageData(LivePGE *pge, uint16_t msg_num, uint16_t counter) const {
assert(counter >= 1 && counter <= 4);
uint16_t c = pge_dst->init_PGE->counter_values[counter - 1];
GroupPGE *le = _pge_groupsTable[pge_dst->index];
uint16_t pge_src_index = pge->init_PGE->data[counter - 1];
const MessagePGE *le = _pge_messagesTable[pge->index];
while (le) {
if (le->group_id == group_id && le->index == c)
if (le->msg_num == msg_num && le->src_pge == pge_src_index) {
return 1;
}
le = le->next_entry;
}
return 0;
@ -76,7 +76,7 @@ void Game::pge_loadForCurrentLevel(uint16_t idx) {
live_pge->collision_slot = 0xFF;
live_pge->next_inventory_PGE = 0xFF;
live_pge->current_inventory_PGE = 0xFF;
live_pge->unkF = 0xFF;
live_pge->ref_inventory_PGE = 0xFF;
live_pge->anim_number = 0;
live_pge->index = idx;
live_pge->next_PGE_in_room = 0;
@ -117,7 +117,7 @@ void Game::pge_process(LivePGE *pge) {
_pge_playAnimSound = true;
_pge_currentPiegeFacingDir = (pge->flags & 1) != 0;
_pge_currentPiegeRoom = pge->room_location;
GroupPGE *le = _pge_groupsTable[pge->index];
MessagePGE *le = _pge_messagesTable[pge->index];
if (le) {
pge_setupNextAnimFrame(pge, le);
}
@ -129,7 +129,7 @@ void Game::pge_process(LivePGE *pge) {
Object *obj = &on->objects[pge->first_obj_number];
while (1) {
if (obj->type != pge->obj_type) {
pge_removeFromGroup(pge->index);
pge_clearMessages(pge->index);
return;
}
uint16_t _ax = pge_execute(pge, init_pge, obj);
@ -137,7 +137,7 @@ void Game::pge_process(LivePGE *pge) {
if (_currentLevel == 6 && (_currentRoom == 50 || _currentRoom == 51)) {
if (pge->index == 79 && _ax == 0xFFFF && obj->opcode1 == 0x60 && obj->opcode2 == 0 && obj->opcode3 == 0) {
if (col_getGridPos(&_pgeLive[79], 0) == col_getGridPos(&_pgeLive[0], 0)) {
pge_updateGroup(79, 0, 4);
pge_sendMessage(79, 0, 4);
}
}
}
@ -156,37 +156,37 @@ void Game::pge_process(LivePGE *pge) {
}
pge_setupAnim(pge);
++pge->anim_seq;
pge_removeFromGroup(pge->index);
pge_clearMessages(pge->index);
}
void Game::pge_setupNextAnimFrame(LivePGE *pge, GroupPGE *le) {
void Game::pge_setupNextAnimFrame(LivePGE *pge, MessagePGE *le) {
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 (i < on->last_obj_number && pge->obj_type == obj->type) {
GroupPGE *next_le = le;
while (i < on->num_objects && pge->obj_type == obj->type) {
MessagePGE *next_le = le;
while (next_le) {
uint16_t groupId = next_le->group_id;
if (obj->opcode2 == 0x6B) { // pge_op_isInGroupSlice
uint16_t msgNum = next_le->msg_num;
if (obj->opcode2 == 0x6B) { // pge_isToggleable
if (obj->opcode_arg2 == 0) {
if (groupId == 1 || groupId == 2) goto set_anim;
if (msgNum == 1 || msgNum == 2) goto set_anim;
}
if (obj->opcode_arg2 == 1) {
if (groupId == 3 || groupId == 4) goto set_anim;
if (msgNum == 3 || msgNum == 4) goto set_anim;
}
} else if (groupId == obj->opcode_arg2) {
} else if (msgNum == obj->opcode_arg2) {
if (obj->opcode2 == 0x22 || obj->opcode2 == 0x6F) goto set_anim;
}
if (obj->opcode1 == 0x6B) { // pge_op_isInGroupSlice
if (obj->opcode1 == 0x6B) { // pge_isToggleable
if (obj->opcode_arg1 == 0) {
if (groupId == 1 || groupId == 2) goto set_anim;
if (msgNum == 1 || msgNum == 2) goto set_anim;
}
if (obj->opcode_arg1 == 1) {
if (groupId == 3 || groupId == 4) goto set_anim;
if (msgNum == 3 || msgNum == 4) goto set_anim;
}
} else if (groupId == obj->opcode_arg1) {
} else if (msgNum == obj->opcode_arg1) {
if (obj->opcode1 == 0x22 || obj->opcode1 == 0x6F) goto set_anim;
}
next_le = next_le->next_entry;
@ -317,8 +317,14 @@ int Game::pge_execute(LivePGE *live_pge, InitPGE *init_pge, const Object *obj) {
--live_pge->life;
if (init_pge->object_type == 1) {
_pge_processOBJ = true;
if (_cheats & kCheatLifeCounter) {
++live_pge->life;
}
} else if (init_pge->object_type == 10) {
_score += 100;
if (_cheats & kCheatOneHitKill) {
live_pge->life = 0;
}
}
}
if (obj->flags & 4) {
@ -369,7 +375,7 @@ void Game::pge_prepare() {
void Game::pge_setupDefaultAnim(LivePGE *pge) {
const uint8_t *anim_data = _res.getAniData(pge->obj_type);
if (pge->anim_seq < _res._readUint16(anim_data)) {
if (1 || pge->anim_seq < _res._readUint16(anim_data)) { /* matches disassembly but should probably be >= */
pge->anim_seq = 0;
}
const uint8_t *anim_frame = anim_data + 6 + pge->anim_seq * 4;
@ -397,7 +403,7 @@ uint16_t Game::pge_processOBJ(LivePGE *pge) {
ObjectNode *on = _res._objectNodesMap[init_pge->obj_node_number];
Object *obj = &on->objects[pge->first_obj_number];
int i = pge->first_obj_number;
while (i < on->last_obj_number && pge->obj_type == obj->type) {
while (i < on->num_objects && pge->obj_type == obj->type) {
if (obj->opcode2 == 0x6B) return 0xFFFF;
if (obj->opcode2 == 0x22 && obj->opcode_arg2 <= 4) return 0xFFFF;
@ -435,7 +441,7 @@ void Game::pge_setupOtherPieges(LivePGE *pge, InitPGE *init_pge) {
_currentRoom = room;
col_prepareRoomState();
_loadMap = true;
if (!(_currentRoom & 0x80) && _currentRoom < 0x40) {
if (_currentRoom < 0x40) {
LivePGE *pge_it = _pge_liveTable1[_currentRoom];
while (pge_it) {
if (pge_it->init_PGE->flags & 4) {
@ -796,10 +802,10 @@ int Game::pge_op_collides2u2o(ObjectOpcodeArgs *args) {
return 0;
}
int Game::pge_op_isInGroup(ObjectOpcodeArgs *args) {
GroupPGE *le = _pge_groupsTable[args->pge->index];
int Game::pge_hasPiegeSentMessage(ObjectOpcodeArgs *args) {
MessagePGE *le = _pge_messagesTable[args->pge->index];
while (le) {
if (le->group_id == args->a) {
if (le->msg_num == args->a) {
return 0xFFFF;
}
le = le->next_entry;
@ -807,27 +813,27 @@ int Game::pge_op_isInGroup(ObjectOpcodeArgs *args) {
return 0;
}
int Game::pge_op_updateGroup0(ObjectOpcodeArgs *args) {
int Game::pge_op_sendMessageData0(ObjectOpcodeArgs *args) {
LivePGE *pge = args->pge;
pge_updateGroup(pge->index, pge->init_PGE->counter_values[0], args->a);
pge_sendMessage(pge->index, pge->init_PGE->data[0], args->a);
return 0xFFFF;
}
int Game::pge_op_updateGroup1(ObjectOpcodeArgs *args) {
int Game::pge_op_sendMessageData1(ObjectOpcodeArgs *args) {
LivePGE *pge = args->pge;
pge_updateGroup(pge->index, pge->init_PGE->counter_values[1], args->a);
pge_sendMessage(pge->index, pge->init_PGE->data[1], args->a);
return 0xFFFF;
}
int Game::pge_op_updateGroup2(ObjectOpcodeArgs *args) {
int Game::pge_op_sendMessageData2(ObjectOpcodeArgs *args) {
LivePGE *pge = args->pge;
pge_updateGroup(pge->index, pge->init_PGE->counter_values[2], args->a);
pge_sendMessage(pge->index, pge->init_PGE->data[2], args->a);
return 0xFFFF;
}
int Game::pge_op_updateGroup3(ObjectOpcodeArgs *args) {
int Game::pge_op_sendMessageData3(ObjectOpcodeArgs *args) {
LivePGE *pge = args->pge;
pge_updateGroup(pge->index, pge->init_PGE->counter_values[3], args->a);
pge_sendMessage(pge->index, pge->init_PGE->data[3], args->a);
return 0xFFFF;
}
@ -888,7 +894,7 @@ int Game::pge_op_nop(ObjectOpcodeArgs *args) {
int Game::pge_op_pickupObject(ObjectOpcodeArgs *args) {
LivePGE *pge = col_findPiege(args->pge, 3);
if (pge) {
pge_updateGroup(args->pge->index, pge->index, args->a);
pge_sendMessage(args->pge->index, pge->index, args->a);
return 0xFFFF;
}
return 0;
@ -908,7 +914,7 @@ int Game::pge_op_copyPiege(ObjectOpcodeArgs *args) {
dst->pos_y = src->pos_y;
dst->room_location = src->room_location;
dst->flags &= 0xFE;
dst->flags &= ~1;
if (src->flags & 1) {
dst->flags |= 1;
}
@ -918,7 +924,7 @@ int Game::pge_op_copyPiege(ObjectOpcodeArgs *args) {
int Game::pge_op_removeItemFromInventory(ObjectOpcodeArgs *args) {
if (args->pge->current_inventory_PGE != 0xFF) {
pge_updateGroup(args->pge->index, args->pge->current_inventory_PGE, args->a);
pge_sendMessage(args->pge->index, args->pge->current_inventory_PGE, args->a);
}
return 1;
}
@ -960,20 +966,20 @@ int Game::pge_op_setCollisionState0(ObjectOpcodeArgs *args) {
return pge_updateCollisionState(args->pge, args->a, 0);
}
int Game::pge_op_isInGroup1(ObjectOpcodeArgs *args) {
return pge_isInGroup(args->pge, args->a, 1);
int Game::pge_hasMessageData0(ObjectOpcodeArgs *args) {
return pge_hasMessageData(args->pge, args->a, 1);
}
int Game::pge_op_isInGroup2(ObjectOpcodeArgs *args) {
return pge_isInGroup(args->pge, args->a, 2);
int Game::pge_hasMessageData1(ObjectOpcodeArgs *args) {
return pge_hasMessageData(args->pge, args->a, 2);
}
int Game::pge_op_isInGroup3(ObjectOpcodeArgs *args) {
return pge_isInGroup(args->pge, args->a, 3);
int Game::pge_hasMessageData2(ObjectOpcodeArgs *args) {
return pge_hasMessageData(args->pge, args->a, 3);
}
int Game::pge_op_isInGroup4(ObjectOpcodeArgs *args) {
return pge_isInGroup(args->pge, args->a, 4);
int Game::pge_hasMessageData3(ObjectOpcodeArgs *args) {
return pge_hasMessageData(args->pge, args->a, 4);
}
int Game::pge_o_unk0x3C(ObjectOpcodeArgs *args) {
@ -1102,7 +1108,7 @@ int Game::pge_o_unk0x40(ObjectOpcodeArgs *args) {
int Game::pge_op_wakeUpPiege(ObjectOpcodeArgs *args) {
if (args->a <= 3) {
int16_t num = args->pge->init_PGE->counter_values[args->a];
int16_t num = args->pge->init_PGE->data[args->a];
if (num >= 0) {
LivePGE *pge = &_pgeLive[num];
pge->flags |= 4;
@ -1114,7 +1120,7 @@ int Game::pge_op_wakeUpPiege(ObjectOpcodeArgs *args) {
int Game::pge_op_removePiege(ObjectOpcodeArgs *args) {
if (args->a <= 3) {
int16_t num = args->pge->init_PGE->counter_values[args->a];
int16_t num = args->pge->init_PGE->data[args->a];
if (num >= 0) {
_pge_liveTable2[num] = 0;
_pgeLive[num].flags &= ~4;
@ -1127,8 +1133,7 @@ int Game::pge_op_removePiegeIfNotNear(ObjectOpcodeArgs *args) {
LivePGE *pge = args->pge;
if (!(pge->init_PGE->flags & 4)) goto kill_pge;
if (_currentRoom & 0x80) goto skip_pge;
if (pge->room_location & 0x80) goto kill_pge;
if (pge->room_location > 0x3F) goto kill_pge;
if (!(pge->room_location < 0x40)) goto kill_pge;
if (pge->room_location == _currentRoom) goto skip_pge;
if (pge->room_location == _res._ctData[CT_UP_ROOM + _currentRoom]) goto skip_pge;
if (pge->room_location == _res._ctData[CT_DOWN_ROOM + _currentRoom]) goto skip_pge;
@ -1146,7 +1151,7 @@ skip_pge:
}
int Game::pge_op_loadPiegeCounter(ObjectOpcodeArgs *args) {
args->pge->counter_value = args->pge->init_PGE->counter_values[args->a];
args->pge->counter_value = args->pge->init_PGE->data[args->a];
return 1;
}
@ -1168,24 +1173,24 @@ int Game::pge_o_unk0x47(ObjectOpcodeArgs *args) {
// used with Ian in level2
int Game::pge_o_unk0x48(ObjectOpcodeArgs *args) {
LivePGE *pge = col_findPiege(&_pgeLive[0], args->pge->init_PGE->counter_values[0]);
LivePGE *pge = col_findPiege(&_pgeLive[0], args->pge->init_PGE->data[0]);
if (pge && pge->life == args->pge->life) {
pge_updateGroup(args->pge->index, pge->index, args->a);
pge_sendMessage(args->pge->index, pge->index, args->a);
return 1;
}
return 0;
}
int Game::pge_o_unk0x49(ObjectOpcodeArgs *args) {
return pge_ZOrder(&_pgeLive[0], args->a, &Game::pge_ZOrderIfIndex, args->pge->init_PGE->counter_values[0]);
return pge_ZOrder(&_pgeLive[0], args->a, &Game::pge_ZOrderIfIndex, args->pge->init_PGE->data[0]);
}
int Game::pge_o_unk0x4A(ObjectOpcodeArgs *args) {
int Game::pge_op_killInventoryPiege(ObjectOpcodeArgs *args) {
LivePGE *pge = args->pge;
pge->room_location = 0xFE;
pge->flags &= ~4;
_pge_liveTable2[pge->index] = 0;
LivePGE *inv_pge = pge_getInventoryItemBefore(&_pgeLive[args->a], pge);
LivePGE *inv_pge = pge_getPreviousInventoryItem(&_pgeLive[args->a], pge);
if (inv_pge == &_pgeLive[args->a]) {
if (pge->index != inv_pge->current_inventory_PGE) {
return 1;
@ -1266,14 +1271,11 @@ int Game::pge_op_incLife(ObjectOpcodeArgs *args) {
return 1;
}
// level2, Ian
int Game::pge_op_setPiegeDefaultAnim(ObjectOpcodeArgs *args) {
assert(args->a >= 0 && args->a < 4);
int16_t r = args->pge->init_PGE->counter_values[args->a];
args->pge->room_location = r;
if (r == 1) {
// this happens after death tower, on earth, when Conrad passes
// by the first policeman who's about to shoot him in the back
InitPGE *init_pge = args->pge->init_PGE;
args->pge->room_location = init_pge->data[args->a];
if (init_pge->object_type == 1) {
_loadMap = true;
}
pge_setupDefaultAnim(args->pge);
@ -1281,7 +1283,7 @@ int Game::pge_op_setPiegeDefaultAnim(ObjectOpcodeArgs *args) {
}
int Game::pge_op_setLifeCounter(ObjectOpcodeArgs *args) {
_pgeLive[args->a].life = args->pge->init_PGE->counter_values[0];
_pgeLive[args->a].life = args->pge->init_PGE->data[0];
return 1;
}
@ -1297,8 +1299,9 @@ int Game::pge_op_playCutscene(ObjectOpcodeArgs *args) {
return 1;
}
int Game::pge_op_isTempVar2Set(ObjectOpcodeArgs *args) {
if (_pge_opTempVar2 == args->a) {
// unused
int Game::pge_op_compareUnkVar(ObjectOpcodeArgs *args) {
if (args->a == -1) {
return 1;
}
return 0;
@ -1306,7 +1309,7 @@ int Game::pge_op_isTempVar2Set(ObjectOpcodeArgs *args) {
int Game::pge_op_playDeathCutscene(ObjectOpcodeArgs *args) {
if (_deathCutsceneCounter == 0) {
_deathCutsceneCounter = args->pge->init_PGE->counter_values[3] + 1;
_deathCutsceneCounter = args->pge->init_PGE->data[3] + 1;
_cut._deathCutsceneId = args->a;
}
return 1;
@ -1327,7 +1330,7 @@ int Game::pge_o_unk0x5F(ObjectOpcodeArgs *args) {
if (pge_room < 0 || pge_room >= 0x40) return 0;
int16_t dx;
int16_t _cx = pge->init_PGE->counter_values[0];
int16_t _cx = pge->init_PGE->data[0];
if (_cx <= 0) {
dx = 1;
_cx = -_cx;
@ -1364,10 +1367,10 @@ int Game::pge_o_unk0x5F(ObjectOpcodeArgs *args) {
}
int Game::pge_op_findAndCopyPiege(ObjectOpcodeArgs *args) {
GroupPGE *le = _pge_groupsTable[args->pge->index];
MessagePGE *le = _pge_messagesTable[args->pge->index];
while (le) {
if (le->group_id == args->a) {
args->a = le->index;
if (le->msg_num == args->a) {
args->a = le->src_pge;
args->b = 0;
pge_op_copyPiege(args);
return 1;
@ -1401,16 +1404,16 @@ int Game::pge_o_unk0x64(ObjectOpcodeArgs *args) {
int Game::pge_op_addToCredits(ObjectOpcodeArgs *args) {
assert(args->a >= 0 && args->a < 3);
uint8_t pge = args->pge->init_PGE->counter_values[args->a];
int16_t val = args->pge->init_PGE->counter_values[args->a + 1];
uint8_t pge = args->pge->init_PGE->data[args->a];
int16_t val = args->pge->init_PGE->data[args->a + 1];
_pgeLive[pge].life += val;
return 1;
}
int Game::pge_op_subFromCredits(ObjectOpcodeArgs *args) {
assert(args->a >= 0 && args->a < 3);
uint8_t pge = args->pge->init_PGE->counter_values[args->a];
int16_t val = args->pge->init_PGE->counter_values[args->a + 1];
uint8_t pge = args->pge->init_PGE->data[args->a];
int16_t val = args->pge->init_PGE->data[args->a + 1];
_pgeLive[pge].life -= val;
return 1;
}
@ -1429,6 +1432,9 @@ int Game::pge_op_setCollisionState2(ObjectOpcodeArgs *args) {
int Game::pge_op_saveState(ObjectOpcodeArgs *args) {
_saveStateCompleted = true;
_validSaveState = saveGameState(kIngameSaveSlot);
if (_validSaveState && g_options.play_gamesaved_sound) {
_mix.play(Resource::_gameSavedSoundData, Resource::_gameSavedSoundLen, 8000, Mixer::MAX_VOLUME);
}
return 0xFFFF;
}
@ -1541,20 +1547,20 @@ loc_0_15446:
return 0;
}
int Game::pge_op_isInGroupSlice(ObjectOpcodeArgs *args) {
int Game::pge_isToggleable(ObjectOpcodeArgs *args) {
LivePGE *pge = args->pge;
GroupPGE *le = _pge_groupsTable[pge->index];
MessagePGE *le = _pge_messagesTable[pge->index];
if (le) {
if (args->a == 0) {
do {
if (le->group_id == 1 || le->group_id == 2) {
if (le->msg_num == 1 || le->msg_num == 2) {
return 1;
}
le = le->next_entry;
} while (le);
} else {
do {
if (le->group_id == 3 || le->group_id == 4) {
if (le->msg_num == 3 || le->msg_num == 4) {
return 1;
}
le = le->next_entry;
@ -1565,10 +1571,10 @@ int Game::pge_op_isInGroupSlice(ObjectOpcodeArgs *args) {
}
int Game::pge_o_unk0x6C(ObjectOpcodeArgs *args) {
LivePGE *pge = col_findPiege(&_pgeLive[0], args->pge->init_PGE->counter_values[0]);
LivePGE *pge = col_findPiege(&_pgeLive[0], args->pge->init_PGE->data[0]);
if (pge) {
if (pge->life <= args->pge->life) {
pge_updateGroup(args->pge->index, pge->index, args->a);
pge_sendMessage(args->pge->index, pge->index, args->a);
return 1;
}
}
@ -1586,10 +1592,10 @@ int Game::pge_op_isCollidingObject(ObjectOpcodeArgs *args) {
// elevator
int Game::pge_o_unk0x6E(ObjectOpcodeArgs *args) {
GroupPGE *le = _pge_groupsTable[args->pge->index];
MessagePGE *le = _pge_messagesTable[args->pge->index];
while (le) {
if (args->a == le->group_id) {
pge_updateInventory(&_pgeLive[le->index], args->pge);
if (args->a == le->msg_num) {
pge_updateInventory(&_pgeLive[le->src_pge], args->pge);
return 0xFFFF;
}
le = le->next_entry;
@ -1600,10 +1606,10 @@ int Game::pge_o_unk0x6E(ObjectOpcodeArgs *args) {
int Game::pge_o_unk0x6F(ObjectOpcodeArgs *args) {
LivePGE *pge = args->pge;
GroupPGE *le = _pge_groupsTable[pge->index];
MessagePGE *le = _pge_messagesTable[pge->index];
while (le) {
if (args->a == le->group_id) {
pge_updateGroup(pge->index, le->index, 0xC);
if (args->a == le->msg_num) {
pge_sendMessage(pge->index, le->src_pge, 0xC);
return 1;
}
le = le->next_entry;
@ -1614,7 +1620,7 @@ int Game::pge_o_unk0x6F(ObjectOpcodeArgs *args) {
int Game::pge_o_unk0x70(ObjectOpcodeArgs *args) {
uint8_t pge_num = args->pge->current_inventory_PGE;
while (pge_num != 0xFF) {
pge_updateGroup(args->pge->index, _pgeLive[pge_num].index, args->a);
pge_sendMessage(args->pge->index, _pgeLive[pge_num].index, args->a);
pge_num = _pgeLive[pge_num].next_inventory_PGE;
}
return 1;
@ -1623,9 +1629,9 @@ int Game::pge_o_unk0x70(ObjectOpcodeArgs *args) {
// elevator
int Game::pge_o_unk0x71(ObjectOpcodeArgs *args) {
LivePGE *pge = args->pge;
GroupPGE *le = _pge_groupsTable[pge->index];
MessagePGE *le = _pge_messagesTable[pge->index];
while (le) {
if (le->group_id == args->a) {
if (le->msg_num == args->a) {
pge_reorderInventory(args->pge);
return 1;
}
@ -1635,21 +1641,24 @@ int Game::pge_o_unk0x71(ObjectOpcodeArgs *args) {
}
int Game::pge_o_unk0x72(ObjectOpcodeArgs *args) {
int8_t *var4 = &_res._ctData[0x100] + args->pge->room_location * 0x70;
var4 += (((args->pge->pos_y / 36) & ~1) + args->a) * 16 + (args->pge->pos_x + 8) / 16;
int8_t *grid_data = &_res._ctData[0x100] + args->pge->room_location * 0x70;
int16_t pge_pos_y = ((args->pge->pos_y / 36) & ~1) + args->a;
int16_t pge_pos_x = (args->pge->pos_x + 8) >> 4;
grid_data += pge_pos_y * 16 + pge_pos_x;
CollisionSlot2 *_di = _col_slots2Next;
int _cx = 0x100;
while (_di && _cx != 0) {
if (_di->unk2 != var4) {
int count = 256; // ARRAYSIZE(_col_slots2)
while (_di && count != 0) {
if (_di->unk2 != grid_data) {
_di = _di->next_slot;
--_cx;
--count;
} else {
memcpy(_di->unk2, _di->data_buf, _di->data_size + 1);
break;
}
}
return 0xFFFF; // XXX var4;
// original returns the pointer to ctData
return 0xFFFF;
}
int Game::pge_o_unk0x73(ObjectOpcodeArgs *args) {
@ -1682,7 +1691,7 @@ int Game::pge_op_isBelowConrad(ObjectOpcodeArgs *args) {
if ((pge_conrad->pos_y - 8) / 72 < _si->pos_y / 72) {
return 0xFFFF;
}
} else if (!(_si->room_location & 0x80) && _si->room_location < 0x40) {
} else if (_si->room_location < 0x40) {
if (pge_conrad->room_location == _res._ctData[CT_UP_ROOM + _si->room_location]) {
return 0xFFFF;
}
@ -1697,7 +1706,7 @@ int Game::pge_op_isAboveConrad(ObjectOpcodeArgs *args) {
if ((pge_conrad->pos_y - 8) / 72 > _si->pos_y / 72) {
return 0xFFFF;
}
} else if (!(_si->room_location & 0x80) && _si->room_location < 0x40) {
} else if (_si->room_location < 0x40) {
if (pge_conrad->room_location == _res._ctData[CT_DOWN_ROOM + _si->room_location]) {
return 0xFFFF;
}
@ -1732,7 +1741,7 @@ int Game::pge_op_isNotFacingConrad(ObjectOpcodeArgs *args) {
}
}
} else if (args->a == 0) {
if (!(pge->room_location & 0x80) && pge->room_location < 0x40) {
if (pge->room_location < 0x40) {
if (_pge_currentPiegeFacingDir) {
if (pge_conrad->room_location == _res._ctData[CT_RIGHT_ROOM + pge->room_location])
return 0xFFFF;
@ -1773,7 +1782,7 @@ int Game::pge_op_isFacingConrad(ObjectOpcodeArgs *args) {
}
}
} else if (args->a == 0) {
if (!(pge->room_location & 0x80) && pge->room_location < 0x40) {
if (pge->room_location < 0x40) {
if (_pge_currentPiegeFacingDir) {
if (pge_conrad->room_location == _res._ctData[CT_LEFT_ROOM + pge->room_location])
return 0xFFFF;
@ -1782,7 +1791,6 @@ int Game::pge_op_isFacingConrad(ObjectOpcodeArgs *args) {
return 0xFFFF;
}
}
}
}
return 0;
@ -1814,7 +1822,7 @@ int Game::pge_o_unk0x7C(ObjectOpcodeArgs *args) {
}
}
if (pge != 0) {
pge_updateGroup(args->pge->index, pge->index, args->a);
pge_sendMessage(args->pge->index, pge->index, args->a);
}
return 0;
}
@ -1840,7 +1848,7 @@ int Game::pge_o_unk0x7F(ObjectOpcodeArgs *args) {
CollisionSlot *slot = _col_slotsTable[var4];
while (slot) {
if (slot->live_pge != args->pge) {
if (slot->live_pge->init_PGE->object_type == 3 && var2 != slot->live_pge->unkF) {
if (slot->live_pge->init_PGE->object_type == 3 && var2 != slot->live_pge->ref_inventory_PGE) {
return 0;
}
}
@ -1854,7 +1862,7 @@ int Game::pge_o_unk0x7F(ObjectOpcodeArgs *args) {
}
int Game::pge_op_setPiegePosX(ObjectOpcodeArgs *args) {
uint8_t pge_num = args->pge->unkF;
uint8_t pge_num = args->pge->ref_inventory_PGE;
if (pge_num != 0xFF) {
args->pge->pos_x = _pgeLive[pge_num].pos_x;
}
@ -1862,7 +1870,7 @@ int Game::pge_op_setPiegePosX(ObjectOpcodeArgs *args) {
}
int Game::pge_op_setPiegePosModX(ObjectOpcodeArgs *args) {
uint8_t pge_num = args->pge->unkF;
uint8_t pge_num = args->pge->ref_inventory_PGE;
if (pge_num != 0xFF) {
int16_t dx = _pgeLive[pge_num].pos_x % 256;
if (dx >= args->pge->pos_x) {
@ -1873,25 +1881,33 @@ int Game::pge_op_setPiegePosModX(ObjectOpcodeArgs *args) {
return 0xFFFF;
}
// taxi, level4
// taxi and teleporter
int Game::pge_op_changeRoom(ObjectOpcodeArgs *args) {
// pge_op_protectionScreen
InitPGE *init_pge_1 = args->pge->init_PGE;
assert(args->a >= 0 && args->a < 3);
int16_t _ax = init_pge_1->counter_values[args->a];
int16_t _bx = init_pge_1->counter_values[args->a + 1];
const int16_t _ax = init_pge_1->data[args->a];
if (_ax == 0 && !g_options.bypass_protection && !g_options.use_words_protection && (_res.isAmiga() || _res.isDOS())) {
if (!handleProtectionScreenShape()) {
// _pge_opcodeTable[0x82] = &Game::pge_op_nop;
// _pge_opGunVar = 0;
// return;
}
}
// pge_op_changeRoom
const int16_t _bx = init_pge_1->data[args->a + 1];
LivePGE *live_pge_1 = &_pgeLive[_bx];
LivePGE *live_pge_2 = &_pgeLive[_ax];
int8_t pge_room = live_pge_1->room_location;
if (pge_room >= 0 && pge_room < 0x40) {
int8_t _al = live_pge_2->room_location;
const int8_t room = live_pge_2->room_location;
live_pge_2->pos_x = live_pge_1->pos_x;
live_pge_2->pos_y = live_pge_1->pos_y;
live_pge_2->room_location = live_pge_1->room_location;
pge_addToCurrentRoomList(live_pge_2, _al);
pge_addToCurrentRoomList(live_pge_2, room);
InitPGE *init_pge_2 = live_pge_2->init_PGE;
init_pge_1 = live_pge_1->init_PGE;
if (init_pge_2->obj_node_number == init_pge_1->obj_node_number) {
live_pge_2->flags &= 0xFE;
live_pge_2->flags &= ~1;
if (live_pge_1->flags & 1) {
live_pge_2->flags |= 1;
}
@ -1949,7 +1965,7 @@ int Game::pge_o_unk0x86(ObjectOpcodeArgs *args) {
int Game::pge_op_playSoundGroup(ObjectOpcodeArgs *args) {
assert(args->a < 4);
uint16_t c = args->pge->init_PGE->counter_values[args->a];
uint16_t c = args->pge->init_PGE->data[args->a];
uint8_t sfxId = c & 0xFF;
uint8_t softVol = c >> 8;
playSound(sfxId, softVol);
@ -1965,13 +1981,13 @@ int Game::pge_op_adjustPos(ObjectOpcodeArgs *args) {
return 0xFFFF;
}
int Game::pge_op_setTempVar1(ObjectOpcodeArgs *args) {
_pge_opTempVar1 = args->a;
int Game::pge_op_setGunVar(ObjectOpcodeArgs *args) {
_pge_opGunVar = args->a;
return 0xFFFF;
}
int Game::pge_op_isTempVar1Set(ObjectOpcodeArgs *args) {
if (_pge_opTempVar1 != args->a) {
int Game::pge_op_compareGunVar(ObjectOpcodeArgs *args) {
if (_pge_opGunVar != args->a) {
return 0;
} else {
return 0xFFFF;
@ -1979,7 +1995,7 @@ int Game::pge_op_isTempVar1Set(ObjectOpcodeArgs *args) {
}
int Game::pge_setCurrentInventoryObject(LivePGE *pge) {
LivePGE *_bx = pge_getInventoryItemBefore(&_pgeLive[0], pge);
LivePGE *_bx = pge_getPreviousInventoryItem(&_pgeLive[0], pge);
if (_bx == &_pgeLive[0]) {
if (_bx->current_inventory_PGE != pge->index) {
return 0;
@ -1995,17 +2011,17 @@ int Game::pge_setCurrentInventoryObject(LivePGE *pge) {
}
void Game::pge_updateInventory(LivePGE *pge1, LivePGE *pge2) {
if (pge2->unkF != 0xFF) {
if (pge2->ref_inventory_PGE != 0xFF) {
pge_reorderInventory(pge2);
}
LivePGE *_ax = pge_getInventoryItemBefore(pge1, 0);
LivePGE *_ax = pge_getPreviousInventoryItem(pge1, 0);
pge_addToInventory(_ax, pge2, pge1);
}
void Game::pge_reorderInventory(LivePGE *pge) {
if (pge->unkF != 0xFF) {
LivePGE *_bx = &_pgeLive[pge->unkF];
LivePGE *_di = pge_getInventoryItemBefore(_bx, pge);
if (pge->ref_inventory_PGE != 0xFF) {
LivePGE *_bx = &_pgeLive[pge->ref_inventory_PGE];
LivePGE *_di = pge_getPreviousInventoryItem(_bx, pge);
if (_di == _bx) {
if (_di->current_inventory_PGE == pge->index) {
pge_removeFromInventory(_di, pge, _bx);
@ -2018,7 +2034,7 @@ void Game::pge_reorderInventory(LivePGE *pge) {
}
}
LivePGE *Game::pge_getInventoryItemBefore(LivePGE *pge, LivePGE *last_pge) {
LivePGE *Game::pge_getPreviousInventoryItem(LivePGE *pge, LivePGE *last_pge) {
LivePGE *_di = pge;
uint8_t n = _di->current_inventory_PGE;
while (n != 0xFF) {
@ -2034,7 +2050,7 @@ LivePGE *Game::pge_getInventoryItemBefore(LivePGE *pge, LivePGE *last_pge) {
}
void Game::pge_addToInventory(LivePGE *pge1, LivePGE *pge2, LivePGE *pge3) {
pge2->unkF = pge3->index;
pge2->ref_inventory_PGE = pge3->index;
if (pge1 == pge3) {
pge2->next_inventory_PGE = pge1->current_inventory_PGE;
pge1->current_inventory_PGE = pge2->index;
@ -2044,9 +2060,9 @@ void Game::pge_addToInventory(LivePGE *pge1, LivePGE *pge2, LivePGE *pge3) {
}
}
int Game::pge_updateCollisionState(LivePGE *pge, int16_t pge_dy, uint8_t var8) {
uint8_t pge_unk1C = pge->init_PGE->unk1C;
if (!(pge->room_location & 0x80) && pge->room_location < 0x40) {
int Game::pge_updateCollisionState(LivePGE *pge, int16_t pge_dy, uint8_t value) {
const uint8_t pge_collision_data_len = pge->init_PGE->collision_data_len;
if (pge->room_location < 0x40) {
int8_t *grid_data = &_res._ctData[0x100] + 0x70 * pge->room_location;
int16_t pge_pos_y = ((pge->pos_y / 36) & ~1) + pge_dy;
int16_t pge_pos_x = (pge->pos_x + 8) >> 4;
@ -2055,20 +2071,32 @@ int Game::pge_updateCollisionState(LivePGE *pge, int16_t pge_dy, uint8_t var8) {
CollisionSlot2 *slot1 = _col_slots2Next;
int16_t i = 255;
pge_pos_x = i;
if (_pge_currentPiegeFacingDir) {
i = pge_unk1C - 1;
i = pge_collision_data_len - 1;
grid_data -= i;
}
while (slot1) {
if (slot1->unk2 == grid_data) {
slot1->data_size = pge_unk1C - 1;
assert(pge_unk1C < 0x70);
memset(grid_data, var8, pge_unk1C);
grid_data += pge_unk1C;
slot1->data_size = pge_collision_data_len - 1;
assert(pge_collision_data_len < 0x70);
memset(grid_data, value, pge_collision_data_len);
return 1;
} else {
// the increment looks wrong but matches the DOS disassembly
//
// seg000:667B inc cx
// seg000:667C mov si, bx
// seg000:667E mov bx, [bx+t_collision_slot2.next_slot]
// seg000:6680 loop loc_0_1665B
//
// interestingly Amiga does not have it
//
// CODE:000042BA movea.l a4,a5
// CODE:000042BC movea.l 0(a4),a4
// CODE:000042C0 dbf d0,loc_4290
++i;
slot1 = slot1->next_slot;
if (--i == 0) {
break;
@ -2078,14 +2106,14 @@ int Game::pge_updateCollisionState(LivePGE *pge, int16_t pge_dy, uint8_t var8) {
if (_col_slots2Cur < &_col_slots2[255]) {
slot1 = _col_slots2Cur;
slot1->unk2 = grid_data;
slot1->data_size = pge_unk1C - 1;
slot1->data_size = pge_collision_data_len - 1;
uint8_t *dst = &slot1->data_buf[0];
int8_t *src = grid_data;
int n = pge_unk1C;
int n = pge_collision_data_len;
assert(n < 0x10);
while (n--) {
*dst++ = *src;
*src++ = var8;
*src++ = value;
}
++_col_slots2Cur;
slot1->next_slot = _col_slots2Next;
@ -2120,41 +2148,47 @@ int Game::pge_ZOrder(LivePGE *pge, int16_t num, pge_ZOrderCallback compare, uint
return 0;
}
void Game::pge_updateGroup(uint8_t idx, uint8_t unk1, int16_t unk2) {
debug(DBG_GAME, "Game::pge_updateGroup() idx=0x%X unk1=0x%X unk2=0x%X", idx, unk1, unk2);
LivePGE *pge = &_pgeLive[unk1];
void Game::pge_sendMessage(uint8_t src_pge_index, uint8_t dst_pge_index, int16_t num) {
debug(DBG_GAME, "Game::pge_sendMessage() src=0x%X dst=0x%X num=0x%X", src_pge_index, dst_pge_index, num);
LivePGE *pge = &_pgeLive[dst_pge_index];
if (!(pge->flags & 4)) {
if (!(pge->init_PGE->flags & 1)) {
return;
}
pge->flags |= 4;
_pge_liveTable2[unk1] = pge;
_pge_liveTable2[dst_pge_index] = pge;
}
if (unk2 <= 4) {
if (num <= 4) {
uint8_t pge_room = pge->room_location;
pge = &_pgeLive[idx];
pge = &_pgeLive[src_pge_index];
if (pge_room != pge->room_location) {
return;
}
if (unk1 == 0 && _blinkingConradCounter != 0) {
if (dst_pge_index == 0 && (_blinkingConradCounter != 0 || (_cheats & kCheatNoHit) != 0)) {
return;
}
// XXX
if (_stub->_pi.dbgMask & PlayerInput::DF_AUTOZOOM) {
const int type = _pgeLive[dst_pge_index].init_PGE->object_type;
if (type == 1 || type == 10) {
_pge_zoomPiegeNum = dst_pge_index;
_pge_zoomCounter = 0;
}
}
}
GroupPGE *le = _pge_nextFreeGroup;
MessagePGE *le = _pge_nextFreeMessage;
if (le) {
// append to the list
_pge_nextFreeGroup = le->next_entry;
GroupPGE *_ax = _pge_groupsTable[unk1];
_pge_groupsTable[unk1] = le;
le->next_entry = _ax;
le->index = idx;
le->group_id = unk2;
_pge_nextFreeMessage = le->next_entry;
MessagePGE *next = _pge_messagesTable[dst_pge_index];
_pge_messagesTable[dst_pge_index] = le;
le->next_entry = next;
le->src_pge = src_pge_index;
le->msg_num = num;
}
}
void Game::pge_removeFromInventory(LivePGE *pge1, LivePGE *pge2, LivePGE *pge3) {
pge2->unkF = 0xFF;
pge2->ref_inventory_PGE = 0xFF;
if (pge3 == pge1) {
pge3->current_inventory_PGE = pge2->next_inventory_PGE;
pge2->next_inventory_PGE = 0xFF;
@ -2184,7 +2218,7 @@ int Game::pge_ZOrderByAnimYIfType(LivePGE *pge1, LivePGE *pge2, uint8_t comp, ui
int Game::pge_ZOrderIfIndex(LivePGE *pge1, LivePGE *pge2, uint8_t comp, uint8_t comp2) {
if (pge1->index != comp2) {
pge_updateGroup(pge2->index, pge1->index, comp);
pge_sendMessage(pge2->index, pge1->index, comp);
return 1;
}
return 0;
@ -2192,7 +2226,7 @@ int Game::pge_ZOrderIfIndex(LivePGE *pge1, LivePGE *pge2, uint8_t comp, uint8_t
int Game::pge_ZOrderByIndex(LivePGE *pge1, LivePGE *pge2, uint8_t comp, uint8_t comp2) {
if (pge1 != pge2) {
pge_updateGroup(pge2->index, pge1->index, comp);
pge_sendMessage(pge2->index, pge1->index, comp);
_pge_compareVar1 = 0xFFFF;
}
return 0;
@ -2215,7 +2249,7 @@ int Game::pge_ZOrderIfDifferentDirection(LivePGE *pge1, LivePGE *pge2, uint8_t c
if (pge1 != pge2) {
if ((pge1->flags & 1) != (pge2->flags & 1)) {
_pge_compareVar1 = 1;
pge_updateGroup(pge2->index, pge1->index, comp);
pge_sendMessage(pge2->index, pge1->index, comp);
if (pge2->index == 0) {
return 0xFFFF;
}
@ -2228,7 +2262,7 @@ int Game::pge_ZOrderIfSameDirection(LivePGE *pge1, LivePGE *pge2, uint8_t comp,
if (pge1 != pge2) {
if ((pge1->flags & 1) == (pge2->flags & 1)) {
_pge_compareVar2 = 1;
pge_updateGroup(pge2->index, pge1->index, comp);
pge_sendMessage(pge2->index, pge1->index, comp);
if (pge2->index == 0) {
return 0xFFFF;
}
@ -2258,3 +2292,69 @@ int Game::pge_ZOrderIfTypeAndDifferentDirection(LivePGE *pge1, LivePGE *pge2, ui
int Game::pge_ZOrderByNumber(LivePGE *pge1, LivePGE *pge2, uint8_t comp, uint8_t comp2) {
return pge1 - pge2;
}
static int pge_zoomDx(int prev_x, int cur_x) {
int dx = ABS(cur_x - prev_x);
if (dx < 4) {
dx = 1;
} else if (dx < 8) {
dx = 2;
} else if (dx < 16) {
dx = 4;
} else {
dx = 8;
}
return (prev_x < cur_x) ? dx : -dx;
}
static int pge_zoomDy(int prev_y, int cur_y, bool flag) {
int dy = ABS(cur_y - prev_y);
if (flag) {
if (dy < 2) {
return 0;
}
} else {
if (dy < 4) {
return 0;
}
}
if (dy < 8) {
dy = 2;
} else if (dy < 16) {
dy = 4;
} else {
dy = 8;
}
return (prev_y < cur_y) ? dy : -dy;
}
void Game::pge_updateZoom() {
static const int kZoomW = Video::GAMESCREEN_W / 2;
static const int kZoomH = Video::GAMESCREEN_H / 2;
if (_pge_zoomPiegeNum != 0) {
LivePGE *pge = &_pgeLive[_pge_zoomPiegeNum];
if (pge->room_location != _currentRoom) {
_pge_zoomPiegeNum = 0;
} else if (_pge_zoomCounter < 30) {
int x = pge->pos_x + ((_pgeLive[0].flags & 1) ? 22 - kZoomW : -12);
x = CLIP(x, 0, Video::GAMESCREEN_W - kZoomW);
if (_pge_zoomCounter != 0 && _pge_zoomX != x) {
const int dx = pge_zoomDx(_pge_zoomX, x);
x = _pge_zoomX + dx;
}
_pge_zoomX = x;
int y = pge->pos_y - 24 - kZoomH / 2;
y = CLIP(y, 0, Video::GAMESCREEN_H - kZoomH);
if (_pge_zoomCounter != 0 && _pge_zoomY != y) {
const int dy = pge_zoomDy(_pge_zoomY, y, (pge->ref_inventory_PGE != 0xFF));
y = _pge_zoomY + dy;
}
_pge_zoomY = y;
_stub->zoomRect(x, y, kZoomW, kZoomH);
}
++_pge_zoomCounter;
if (_pge_zoomCounter == 40) {
_pge_zoomPiegeNum = 0;
}
}
}

414
prf_player.cpp Normal file
View File

@ -0,0 +1,414 @@
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include "file.h"
#ifdef _WIN32
#define MIDI_DRIVER_SYMBOL __declspec(dllimport)
#else
#define MIDI_DRIVER_SYMBOL
#endif
#include "midi_driver.h"
#include "midi_parser.h"
#include "mixer.h"
#include "prf_player.h"
#include "util.h"
static const struct {
int mode;
int hz;
const MidiDriverInfo *info;
} _midiDrivers[] = {
#ifdef USE_MIDI_DRIVER
{ MODE_ADLIB, TIMER_ADLIB_HZ, &midi_driver_adlib },
{ MODE_MT32, TIMER_MT32_HZ, &midi_driver_mt32 },
#endif
{ -1, 0, 0 }
};
static const int kMusicVolume = 63;
PrfPlayer::PrfPlayer(Mixer *mix, FileSystem *fs, int mode)
: _playing(false), _mixer(mix), _fs(fs), _mode(mode), _driver(0) {
for (int i = 0; _midiDrivers[i].info; ++i) {
if (_midiDrivers[i].mode == mode) {
_timerHz = _midiDrivers[i].hz;
_driver = _midiDrivers[i].info->create();
assert(_driver);
if (_driver->init() < 0) {
warning("Failed to initialize MIDI driver %s", _midiDrivers[i].info->name);
_driver = 0;
}
return;
}
}
fprintf(stdout, "WARNING: no midi driver for mode %d", mode);
}
PrfPlayer::~PrfPlayer() {
if (_driver) {
_driver->fini();
_driver = 0;
}
}
void PrfPlayer::play(int num) {
_playing = false;
memset(&_prfData, 0, sizeof(_prfData));
memset(&_tracks, 0, sizeof(_tracks));
if (num < _namesCount) {
char name[64];
snprintf(name, sizeof(name), "%s.prf", (num == 1 && _mode == MODE_MT32) ? "opt" : _names[num]);
File f;
if (f.open(name, "rb", _fs)) {
loadPrf(&f);
if (_mode == MODE_ADLIB) {
for (int i = 0; i < 16; ++i) {
memset(_adlibInstrumentData[i], 0, ADLIB_INSTRUMENT_DATA_LEN);
if (_prfData.instruments[i][0]) {
snprintf(name, sizeof(name), "%s.INS", _prfData.instruments[i]);
if (f.open(name, "rb", _fs)) {
loadIns(&f, i);
} else {
warning("Unable to open '%s'", name);
}
}
}
}
if (f.open(_prfData.midi, "rb", _fs)) {
_parser.loadMid(&f);
play();
}
}
}
}
void PrfPlayer::loadPrf(File *f) {
for (int i = 0; i < 16; ++i) {
f->read(_prfData.instruments[i], INSTRUMENT_NAME_LEN);
const int len = strlen(_prfData.instruments[i]);
if (len > 8) {
_prfData.instruments[i][8] = 0;
debug(DBG_PRF, "Truncating instrument name to '%s'", _prfData.instruments[i]);
}
}
for (int i = 0; i < 16; ++i) {
_prfData.adlibNotes[i] = f->readUint16LE();
}
for (int i = 0; i < 16; ++i) {
_prfData.adlibVelocities[i] = f->readUint16LE();
}
_prfData.timerTicks = f->readUint32LE();
_prfData.timerMod = f->readUint16LE();
f->read(_prfData.midi, MIDI_FILENAME_LEN);
_prfData.adlibDoNotesLookup = f->readUint16LE();
for (int i = 0; i < 16; ++i) {
_prfData.adlibPrograms[i] = f->readUint16LE();
}
for (int i = 0; i < 16; ++i) {
_prfData.mt32Programs[i] = f->readUint16LE();
}
for (int i = 0; i < 16; ++i) {
_prfData.mt32Velocities[i] = f->readUint16LE();
}
for (int i = 0; i < 16; ++i) {
_prfData.mt32Notes[i] = f->readUint16LE();
}
for (int i = 0; i < 16; ++i) {
_tracks[i].hw_channel_num = f->readByte();
}
for (int i = 0; i < 16; ++i) {
_tracks[i].mt32_program_num = f->readByte();
}
for (int i = 0; i < 16; ++i) {
_tracks[i].loop_flag = f->readByte();
}
_prfData.totalDurationTicks = f->readUint32LE();
_prfData.mt32DoChannelsLookup = f->readByte();
assert(f->tell() == 0x2F1);
debug(DBG_PRF, "duration %d timer %d", _prfData.totalDurationTicks, _prfData.timerTicks);
}
void PrfPlayer::loadIns(File *f, int i) {
uint8_t *p = _adlibInstrumentData[i];
p[0] = f->readByte();
p[1] = f->readByte();
f->read(&p[6], 26 * 2);
f->seek(54 + 20);
p[2] = f->readByte();
f->readByte();
p[3] = f->readByte();
f->readByte();
if (p[2] != 0 || p[3] != 0) {
debug(DBG_PRF, "Wave Select Register 0x%02x 0x%02x was ignored", p[2], p[3]);
}
p[4] = 0;
p[5] = 0;
const int unk = f->readUint16LE();
assert(unk == 1);
assert(f->tell() == 80);
}
void PrfPlayer::play() {
if (!_driver) {
return;
}
_driver->reset(_mixer->getSampleRate());
for (int i = 0; i < _parser._tracksCount; ++i) {
_tracks[i].instrument_num = i;
if (_mode == MODE_MT32) {
mt32ProgramChange(i, _tracks[i].mt32_program_num);
mt32ControlChangeResetRPN(i);
}
}
for (int i = 0; i < 16; ++i) {
_tracks[i].instrument_num = i;
}
for (int i = 0; i < _parser._tracksCount; ++i) {
PrfTrack *current_track = &_tracks[i];
MidiTrack *track = &_parser._tracks[i];
track->rewind();
const MidiEvent *ev = track->nextEvent();
current_track->counter = ev ? ev->timestamp : 0;
current_track->counter2 = current_track->counter % _prfData.timerMod;
}
_timerTick = _musicTick = 0;
_samplesLeft = 0;
_samplesPerTick = _mixer->getSampleRate() / _timerHz;
_playing = true;
_mixer->setPremixHook(mixCallback, this);
}
void PrfPlayer::stop() {
if (_playing) {
_mixer->setPremixHook(0, 0);
_playing = false;
}
}
void PrfPlayer::mt32NoteOn(int track, int note, int velocity) {
if (_prfData.mt32DoChannelsLookup != 0 && _tracks[track].hw_channel_num == 9) {
note = _prfData.mt32Notes[_tracks[track].instrument_num];
}
_driver->noteOn(_tracks[track].hw_channel_num, note, (velocity * kMusicVolume) >> 6);
}
void PrfPlayer::mt32NoteOff(int track, int note, int velocity) {
if (_prfData.mt32DoChannelsLookup != 0 && _tracks[track].hw_channel_num == 9) {
note = _prfData.mt32Notes[_tracks[track].instrument_num];
}
_driver->noteOff(_tracks[track].hw_channel_num, note, (velocity * kMusicVolume) >> 6);
}
void PrfPlayer::mt32ProgramChange(int track, int num) {
_driver->programChange(_tracks[track].hw_channel_num, num);
}
void PrfPlayer::mt32PitchBend(int track, int lsb, int msb) {
_driver->pitchBend(_tracks[track].hw_channel_num, lsb, msb);
}
static const uint8_t MIDI_CONTROLLER_RPN_LSB = 0x64;
static const uint8_t MIDI_CONTROLLER_RPN_MSB = 0x65;
static const uint8_t MIDI_CONTROLLER_DATA_ENTRY_LSB = 0x26;
static const uint8_t MIDI_CONTROLLER_DATA_ENTRY_MSB = 0x06;
void PrfPlayer::mt32ControlChangeResetRPN(int track) {
_driver->controlChange(_tracks[track].hw_channel_num, MIDI_CONTROLLER_RPN_MSB, 0);
_driver->controlChange(_tracks[track].hw_channel_num, MIDI_CONTROLLER_RPN_LSB, 0);
_driver->controlChange(_tracks[track].hw_channel_num, MIDI_CONTROLLER_DATA_ENTRY_MSB, 1);
_driver->controlChange(_tracks[track].hw_channel_num, MIDI_CONTROLLER_DATA_ENTRY_LSB, 0);
}
void PrfPlayer::adlibNoteOn(int track, int note, int velocity) {
const int instrument_num = _tracks[track].instrument_num;
_driver->setInstrumentData(track, instrument_num, _adlibInstrumentData[instrument_num]);
_driver->noteOn(track, note, velocity);
}
void PrfPlayer::adlibNoteOff(int track, int note, int velocity) {
const int instrument_num = _tracks[track].instrument_num;
_driver->setInstrumentData(track, instrument_num, _adlibInstrumentData[instrument_num]);
_driver->noteOff(track, note, velocity);
}
void PrfPlayer::handleTick() {
for (int i = 0; i < _parser._tracksCount; ++i) {
MidiTrack *track = &_parser._tracks[i];
if (track->endOfTrack) {
continue;
}
const int track_index = i;
PrfTrack *current_track = &_tracks[i];
if (current_track->counter != 0) {
--current_track->counter;
continue;
}
while (1) {
const MidiEvent &ev = track->event;
switch (ev.command & 0xF0) {
case MIDI_COMMAND_NOTE_OFF: {
int note = ev.param1;
int velocity = ev.param2;
if (_mode == MODE_MT32) {
note += _prfData.mt32Notes[current_track->instrument_num];
velocity += _prfData.mt32Velocities[current_track->instrument_num];
if (velocity < 0) {
velocity = 0;
} else if (velocity > 127) {
velocity = 127;
}
mt32NoteOff(track_index, note, velocity);
} else {
if (_prfData.adlibDoNotesLookup) {
note += _prfData.adlibNotes[current_track->instrument_num];
} else {
note += _prfData.adlibNotes[track_index];
}
if (_prfData.adlibDoNotesLookup) {
velocity += _prfData.adlibVelocities[current_track->instrument_num];
} else {
velocity += _prfData.adlibVelocities[track_index];
}
if (velocity < 0) {
velocity = 0;
} else if (velocity > 127) {
velocity = 127;
}
adlibNoteOff(track_index, note, velocity);
}
}
break;
case MIDI_COMMAND_NOTE_ON: {
int note = ev.param1;
int velocity = ev.param2;
if (_mode == MODE_MT32) {
note += _prfData.mt32Notes[current_track->instrument_num];
if (velocity == 0) {
mt32NoteOff(track_index, note, velocity);
break;
}
velocity += _prfData.mt32Velocities[current_track->instrument_num];
if (velocity < 0) {
velocity = 0;
} else if (velocity > 127) {
velocity = 127;
}
mt32NoteOn(track_index, note, velocity);
} else {
assert(_mode == MODE_ADLIB);
if (_prfData.adlibDoNotesLookup) {
note += _prfData.adlibNotes[current_track->instrument_num];
} else {
note += _prfData.adlibNotes[track_index];
}
if (velocity == 0) {
adlibNoteOff(track_index, note, velocity);
break;
}
if (_prfData.adlibDoNotesLookup) {
velocity += _prfData.adlibVelocities[current_track->instrument_num];
} else {
velocity += _prfData.adlibVelocities[track_index];
}
if (velocity < 0) {
velocity = 0;
} else if (velocity > 127) {
velocity = 127;
}
adlibNoteOn(track_index, note, velocity);
}
}
break;
case MIDI_COMMAND_PROGRAM_CHANGE: {
if (_mode == MODE_MT32) {
for (int num = 0; num < 16; ++num) {
if (_prfData.mt32Programs[num] == ev.param1) {
mt32ProgramChange(track_index, _tracks[num].mt32_program_num);
break;
}
}
} else {
assert(_mode == MODE_ADLIB);
if (_prfData.adlibDoNotesLookup) {
for (int num = 0; num < 16; ++num) {
if (_prfData.adlibPrograms[num] == ev.param1) {
current_track->instrument_num = num;
break;
}
}
}
}
}
break;
case MIDI_COMMAND_CHANNEL_PRESSURE:
break;
case MIDI_COMMAND_PITCH_BEND: {
if (_mode == MODE_MT32) {
mt32PitchBend(track_index, ev.param1, ev.param2);
} else {
assert(_mode == MODE_ADLIB);
_driver->pitchBend(track_index, ev.param1, ev.param2);
}
}
break;
default:
warning("Unhandled MIDI event command 0x%x", ev.command);
break;
}
const MidiEvent *next = track->nextEvent();
if (!next) {
if (current_track->loop_flag == 0) {
track->rewind();
const MidiEvent *next = track->nextEvent();
if (next) {
current_track->counter = _prfData.timerMod - current_track->counter2 + next->timestamp - 1;
current_track->counter2 = next->timestamp % _prfData.timerMod;
}
}
break;
}
current_track->counter = next->timestamp;
current_track->counter2 = (current_track->counter + current_track->counter2) % _prfData.timerMod;
if (current_track->counter != 0) {
--current_track->counter;
break;
}
}
}
}
bool PrfPlayer::end() const {
return (_prfData.totalDurationTicks != 0) && _musicTick > _prfData.totalDurationTicks;
}
int PrfPlayer::readSamples(int16_t *samples, int count) {
const int total = count;
while (count != 0) {
if (_samplesLeft == 0) {
handleTick();
if (_prfData.totalDurationTicks != 0) {
++_musicTick;
if (_musicTick == _prfData.totalDurationTicks + 1) {
debug(DBG_PRF, "End of music");
}
}
_samplesLeft = _samplesPerTick * _prfData.timerTicks;
}
const int len = (count < _samplesLeft) ? count : _samplesLeft;
_driver->readSamples(samples, len);
_samplesLeft -= len;
count -= len;
samples += len * 2;
}
return total - count;
}
bool PrfPlayer::mixCallback(void *param, int16_t *buf, int len) {
return ((PrfPlayer *)param)->mix(buf, len);
}
bool PrfPlayer::mix(int16_t *buf, int len) {
const int count = readSamples(buf, len);
return count != 0;
}

122
prf_player.h Normal file
View File

@ -0,0 +1,122 @@
#ifndef PRF_PLAYER_H__
#define PRF_PLAYER_H__
#include <stdint.h>
#include "midi_parser.h"
static const int TIMER_ADLIB_HZ = 2082;
static const int TIMER_MT32_HZ = 2242;
struct MidiDriver;
#define INSTRUMENT_NAME_LEN 30
#define MIDI_FILENAME_LEN 20
struct PrfData {
char instruments[16][INSTRUMENT_NAME_LEN];
int16_t adlibNotes[16];
int16_t adlibVelocities[16];
uint32_t timerTicks;
uint16_t timerMod;
char midi[MIDI_FILENAME_LEN];
uint16_t adlibDoNotesLookup;
int16_t adlibPrograms[16];
int16_t mt32Programs[16];
int16_t mt32Velocities[16];
int16_t mt32Notes[16];
uint32_t totalDurationTicks;
uint8_t mt32DoChannelsLookup;
};
struct PrfTrack {
uint8_t instrument_num;
uint32_t counter;
uint32_t counter2;
uint8_t hw_channel_num;
uint8_t mt32_program_num;
uint8_t loop_flag;
};
#define ADLIB_INSTRUMENT_DATA_LEN 58
struct AdlibInstrument {
uint8_t mode;
uint8_t channel_num;
uint8_t modulator_wave_select;
uint8_t carrier_wave_select;
uint8_t unk4;
uint8_t unk5;
struct {
uint16_t key_scaling; // 0
uint16_t frequency_multiplier; // 2
uint16_t feedback_strength; // 4
uint16_t attack_rate; // 6
uint16_t sustain_level; // 8
uint16_t sustain_sound; // 10
uint16_t decay_rate; // 12
uint16_t release_rate; // 14
uint16_t output_level; // 16
uint16_t amplitude_vibrato; // 18
uint16_t frequency_vibrato; // 20
uint16_t envelope_scaling; // 22
uint16_t frequency_modulation; // 24
} op[2]; /* modulator, carrier */
};
enum {
MODE_ADLIB,
MODE_MT32,
};
struct File;
struct FileSystem;
struct Mixer;
struct PrfPlayer {
static const char *_names[];
static const int _namesCount;
PrfPlayer(Mixer *mix, FileSystem *fs, int mode);
~PrfPlayer();
void play(int num);
void loadPrf(File *f);
void loadIns(File *f, int num);
void play();
void stop();
void mt32NoteOn(int track, int note, int velocity);
void mt32NoteOff(int track, int note, int velocity);
void mt32ProgramChange(int track, int num);
void mt32PitchBend(int track, int lsb, int msb);
void mt32ControlChangeResetRPN(int track);
void adlibNoteOn(int track, int note, int velocity);
void adlibNoteOff(int track, int note, int velocity);
void handleTick();
bool end() const;
int readSamples(int16_t *samples, int count);
static bool mixCallback(void *param, int16_t *buf, int len);
bool mix(int16_t *buf, int len);
bool _playing;
Mixer *_mixer;
FileSystem *_fs;
int _mode;
int _timerHz;
PrfTrack _tracks[16];
MidiDriver *_driver;
MidiParser _parser;
PrfData _prfData;
int _samplesLeft, _samplesPerTick;
uint32_t _timerTick, _musicTick;
uint8_t _adlibInstrumentData[16][ADLIB_INSTRUMENT_DATA_LEN];
};
#endif /* PRF_PLAYER_H__ */

257
protection.cpp Normal file
View File

@ -0,0 +1,257 @@
/*
* REminiscence - Flashback interpreter
* Copyright (C) 2005-2019 Gregory Montoir (cyx@users.sourceforge.net)
*/
#include "game.h"
#include "screenshot.h"
#include "systemstub.h"
static uint8_t reverseBits(uint8_t ch) {
static const uint8_t lut[] = {
0x0, 0x8, 0x4, 0xC, 0x2, 0xA, 0x6, 0xE,
0x1, 0x9, 0x5, 0xD, 0x3, 0xB, 0x7, 0xF
};
return (lut[ch & 15] << 4) | lut[ch >> 4];
}
static uint8_t decryptChar(uint8_t ch) {
return reverseBits(ch ^ 0x55);
}
static uint8_t encryptChar(uint8_t ch) {
return reverseBits(ch) ^ 0x55;
}
bool Game::handleProtectionScreenShape() {
bool valid = false;
_cut.prepare();
const int palOffset = _res.isAmiga() ? 32 : 0;
_cut.copyPalette(_protectionPal + palOffset, 0);
_cut.updatePalette();
_cut._gfx.setClippingRect(64, 48, 128, 128);
_menu._charVar1 = 0xE0;
_menu._charVar2 = 0xEF;
_menu._charVar4 = 0xE5;
_menu._charVar5 = 0xE2;
// 5 codes per shape (a code is 6 characters long)
if (0) {
for (int shape = 0; shape < 30; ++shape) {
fprintf(stdout, "Shape #%2d\n", shape);
for (int code = 0; code < 5; ++code) {
const int offset = (shape * 5 + code) * 6;
if (_res.isAmiga()) {
fprintf(stdout, "\t ");
for (int i = 0; i < 6; ++i) {
const char chr = _protectionNumberDataAmiga[(shape * 5 + code) * 6 + i] ^ 0xD7;
fprintf(stdout, "%c", chr);
}
fprintf(stdout, " : ");
for (int i = 0; i < 6; ++i) {
fprintf(stdout, "%c", _protectionCodeDataAmiga[offset + i] ^ 0xD7);
}
} else {
fprintf(stdout, "\t code %d : ", code + 1);
for (int i = 0; i < 6; ++i) {
fprintf(stdout, "%c", decryptChar(_protectionCodeData[offset + i]));
}
}
fprintf(stdout, "\n");
}
}
}
if (0) {
uint8_t palette[256 * 3];
_stub->getPalette(palette, 256);
for (int shape = 0; shape < 30; ++shape) {
_cut.drawProtectionShape(shape, 0);
char fname[32];
snprintf(fname, sizeof(fname), "shape_%02d.bmp", shape);
saveBMP(fname, _vid._tempLayer, palette, _vid._w, _vid._h);
}
}
const int shapeNum = getRandomNumber() % 30;
const int codeNum = getRandomNumber() % 5;
for (int16_t zoom = 2000; zoom >= 0; zoom -= 100) {
_cut.drawProtectionShape(shapeNum, zoom);
_stub->copyRect(0, 0, _vid._w, _vid._h, _vid._tempLayer, _vid._w);
_stub->updateScreen(0);
_stub->sleep(30);
}
_vid.setTextPalette();
char codeNumber[8];
if (_res.isAmiga()) {
static const uint8_t kNumberLen = 6;
for (int i = 0; i < kNumberLen; ++i) {
codeNumber[i] = _protectionNumberDataAmiga[(shapeNum * 5 + codeNum) * kNumberLen + i] ^ 0xD7;
}
codeNumber[kNumberLen] = 0;
} else {
snprintf(codeNumber, sizeof(codeNumber), "CODE %d", codeNum + 1);
}
static const int kCodeLen = 6;
char codeText[kCodeLen + 1];
int len = 0;
do {
codeText[len] = '\0';
memcpy(_vid._frontLayer, _vid._tempLayer, _vid._layerSize);
_vid.drawString("PROTECTION", 11 * Video::CHAR_W, 2 * Video::CHAR_H, _menu._charVar2);
char buf[32];
snprintf(buf, sizeof(buf), "%s : %s", codeNumber, codeText);
_vid.drawString(buf, 8 * Video::CHAR_W, 23 * Video::CHAR_H, _menu._charVar2);
_vid.updateScreen();
_stub->sleep(50);
_stub->processEvents();
char c = _stub->_pi.lastChar;
if (c != 0) {
_stub->_pi.lastChar = 0;
if (len < kCodeLen) {
if ((c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')) {
codeText[len] = c;
++len;
}
}
}
if (_stub->_pi.backspace) {
_stub->_pi.backspace = false;
if (len > 0) {
--len;
}
}
if (_stub->_pi.enter) {
_stub->_pi.enter = false;
if (len > 0) {
int charsCount = 0;
if (_res.isAmiga()) {
const uint8_t *p = _protectionCodeDataAmiga + (shapeNum * 5 + codeNum) * kCodeLen;
for (int i = 0; i < len && (codeText[i] ^ 0xD7) == p[i]; ++i) {
++charsCount;
}
} else {
const uint8_t *p = _protectionCodeData + (shapeNum * 5 + codeNum) * kCodeLen;
for (int i = 0; i < len && encryptChar(codeText[i]) == p[i]; ++i) {
++charsCount;
}
}
valid = (charsCount == kCodeLen);
break;
}
}
} while (!_stub->_pi.quit);
_vid.fadeOut();
return valid;
}
bool Game::handleProtectionScreenWords() {
bool valid = false;
static const int kWordsCount = 40;
if (0) {
for (int i = 0; i < kWordsCount * 18; i += 18) {
const uint8_t *data = _protectionWordData + i;
fprintf(stdout, "page %d column %d line %2d word %d : ", data[0], data[1], data[2], data[3]);
for (int j = 4; j < 18; ++j) {
const uint8_t ch = decryptChar(data[j]);
if (!(ch >= 'A' && ch <= 'Z')) {
break;
}
fprintf(stdout, "%c", ch);
}
fprintf(stdout, "\n");
}
}
_vid.setTextPalette();
_vid.setPalette0xF();
memset(_vid._frontLayer, 0, _vid._layerSize);
static const char *kText[] = {
"Enter the word found in the",
"following location in your",
"rulebook. (Do not count the",
"title header that appears on",
"all pages. Ignore captions",
"and header).",
0
};
for (int i = 0; kText[i]; ++i) {
_vid.drawString(kText[i], 24, 16 + i * Video::CHAR_H, 0xE5);
}
static const int icon_spr_w = 16;
static const int icon_spr_h = 16;
int icon_num = 31;
for (int y = 140; y < 140 + 5 * icon_spr_h; y += icon_spr_h) {
for (int x = 56; x < 56 + 9 * icon_spr_w; x += icon_spr_w) {
drawIcon(icon_num, x, y, 0xF);
++icon_num;
}
}
const uint8_t code = getRandomNumber() % kWordsCount;
const uint8_t *protectionData = _protectionWordData + code * 18;
static const char *kSecurityCodeText = "SECURITY CODE";
_vid.drawString(kSecurityCodeText, 72 + (114 - strlen(kSecurityCodeText) * 8) / 2, 158, 0xE4);
char buf[16];
snprintf(buf, sizeof(buf), "PAGE %d", protectionData[0]);
_vid.drawString(buf, 69, 189, 0xE5);
snprintf(buf, sizeof(buf), "COLUMN %d", protectionData[1]);
_vid.drawString(buf, 69, 197, 0xE5);
snprintf(buf, sizeof(buf), "LINE %d", protectionData[2]);
_vid.drawString(buf, (protectionData[2] < 10) ? 141 : 133, 189, 0xE5);
snprintf(buf, sizeof(buf), "WORD %d", protectionData[3]);
_vid.drawString(buf, 141, 197, 0xE5);
memcpy(_vid._tempLayer, _vid._frontLayer, _vid._layerSize);
static const int kCodeLen = 14;
char codeText[kCodeLen + 1];
int len = 0;
do {
memcpy(_vid._frontLayer, _vid._tempLayer, _vid._layerSize);
codeText[len] = '\0';
_vid.drawString(codeText, 72 + (114 - strlen(codeText) * 8) / 2, 166, 0xE3);
_vid.updateScreen();
_stub->sleep(50);
_stub->processEvents();
char c = _stub->_pi.lastChar;
if (c != 0) {
_stub->_pi.lastChar = 0;
if (len < kCodeLen) {
if (c >= 'A' && c <= 'Z') {
codeText[len] = c;
++len;
}
}
}
if (_stub->_pi.backspace) {
_stub->_pi.backspace = false;
if (len > 0) {
--len;
}
}
if (_stub->_pi.enter) {
_stub->_pi.enter = false;
if (len > 0) {
int charsCount = 0;
for (int i = 0; i < len; ++i) {
if (encryptChar(codeText[i]) != protectionData[4 + i]) {
break;
}
++charsCount;
}
// words are padded with spaces
valid = decryptChar(protectionData[4 + charsCount]) == 0x20;
}
}
} while (!_stub->_pi.quit);
_vid.fadeOut();
return valid;
}

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,45 @@
# REminiscence for AmigaOS 4.1 FE
REminiscence is a re-implementation of the engine used in the game Flashback
made by Delphine Software and released in 1992. It is developed
by Gregory Montoir and you can find it on GitHub at
https://github.com/cyxx/REminiscence
The icon is based on cedry2kio work as found at
https://www.deviantart.com/cedry2kio/art/Flashback-Icon-1-404447315
## Installation
Extract the archive wherever you want. In the REminiscence there is the
*data* folder where you need to copy the data files for the game. The
*saves* folder is where tha saves files will be stored.
Please read the README.txt file for more information about where to
find the data files.
## I would like to thank
- Gregory Montoir for developing and open sourcing REminiscence
- Capehill for his tireless work on SDL port for AmigaOS 4.1 FE
## Known issues
None yet. If you find any issue please let me know.
## Support
If you enjoy what I am doing and would like to keep me up during the night,
please consider to buy me a coffee at:
https://ko-fi.com/walkero
## Known issues
You can find the known issues at
https://git.walkero.gr/walkero/breakhack/issues
# Changelog
## [0.5.1r1] - 2023-09-17
### Added
- First release for AmigaOS 4

Binary file not shown.

View File

@ -0,0 +1,14 @@
stack 50000
Set title "REminiscence"
Set rcnum `RequestChoice "$title" "Run in window or fullscreen?" "Window" "Fullscreen" "Oh, forget it"`
If $rcnum EQ 1
rs --scaler scale@3
ENDIF
If $rcnum EQ 2
rs --fullscreen
ENDIF

Binary file not shown.

View File

@ -1,7 +1,7 @@
/*
* REminiscence - Flashback interpreter
* Copyright (C) 2005-2018 Gregory Montoir (cyx@users.sourceforge.net)
* Copyright (C) 2005-2019 Gregory Montoir (cyx@users.sourceforge.net)
*/
#include "decode_mac.h"
@ -53,20 +53,31 @@ Resource::~Resource() {
free(_sfxList);
free(_bankData);
delete _aba;
delete _mac;
}
static const char *_demoAba = "DEMO_UK.ABA";
static const char *_joystickAba[] = {
"GLOB1_FB.ABA", "GLOB2_FB.ABA", "GLOB_FR.ABA", 0
};
void Resource::init() {
switch (_type) {
case kResourceTypeAmiga:
_isDemo = _fs->exists("demo.lev");
break;
case kResourceTypeDOS:
if (_fs->exists(ResourceAba::FILENAME)) { // fbdemous
if (_fs->exists(_demoAba)) { // fbdemous
_aba = new ResourceAba(_fs);
_aba->readEntries();
_aba->readEntries(_demoAba);
_isDemo = true;
}
if (!fileExists("LEVEL1.MAP")) { // fbdemofr (no cutscenes)
} else if (_fs->exists(_joystickAba[0])) { // Joystick "Hors Serie" April 1996
_aba = new ResourceAba(_fs);
for (int i = 0; _joystickAba[i]; ++i) {
_aba->readEntries(_joystickAba[i]);
}
} else if (!fileExists("LEVEL2.MAP")) { // fbdemofr (no cutscenes)
_isDemo = true;
}
break;
@ -138,10 +149,6 @@ void Resource::load_DEM(const char *filename) {
void Resource::load_FIB(const char *fileName) {
debug(DBG_RES, "Resource::load_FIB('%s')", fileName);
static const uint8_t fibonacciTable[] = {
0xDE, 0xEB, 0xF3, 0xF8, 0xFB, 0xFD, 0xFE, 0xFF,
0x00, 0x01, 0x02, 0x03, 0x05, 0x08, 0x0D, 0x15
};
snprintf(_entryName, sizeof(_entryName), "%s.FIB", fileName);
File f;
if (f.open(_entryName, "rb", _fs)) {
@ -150,37 +157,48 @@ void Resource::load_FIB(const char *fileName) {
if (!_sfxList) {
error("Unable to allocate SoundFx table");
}
int i;
for (i = 0; i < _numSfx; ++i) {
for (int i = 0; i < _numSfx; ++i) {
SoundFx *sfx = &_sfxList[i];
sfx->offset = f.readUint32LE();
sfx->len = f.readUint16LE();
sfx->freq = 6000;
sfx->data = 0;
}
for (i = 0; i < _numSfx; ++i) {
for (int i = 0; i < _numSfx; ++i) {
SoundFx *sfx = &_sfxList[i];
if (sfx->len == 0) {
continue;
}
f.seek(sfx->offset);
uint8_t *data = (uint8_t *)malloc(sfx->len * 2);
const int len = (sfx->len * 2) - 1;
uint8_t *data = (uint8_t *)malloc(len);
if (!data) {
error("Unable to allocate SoundFx data buffer");
}
sfx->data = data;
uint8_t c = f.readByte();
// Fibonacci-delta decoding
static const int8_t codeToDelta[16] = { -34, -21, -13, -8, -5, -3, -2, -1, 0, 1, 2, 3, 5, 8, 13, 21 };
int c = (int8_t)f.readByte();
*data++ = c;
*data++ = c;
uint16_t sz = sfx->len - 1;
while (sz--) {
uint8_t d = f.readByte();
c += fibonacciTable[d >> 4];
*data++ = c;
c += fibonacciTable[d & 15];
*data++ = c;
sfx->peak = ABS(c);
for (int j = 1; j < sfx->len; ++j) {
const uint8_t d = f.readByte();
c += codeToDelta[d >> 4];
*data++ = CLIP(c, -128, 127);
if (ABS(c) > sfx->peak) {
sfx->peak = ABS(c);
}
c += codeToDelta[d & 15];
*data++ = CLIP(c, -128, 127);
if (ABS(c) > sfx->peak) {
sfx->peak = ABS(c);
}
}
sfx->len *= 2;
sfx->len = len;
}
if (f.ioErr()) {
error("I/O error when reading '%s'", _entryName);
@ -190,6 +208,19 @@ void Resource::load_FIB(const char *fileName) {
}
}
static void normalizeSPL(SoundFx *sfx) {
static const int kGain = 2;
sfx->peak = ABS(sfx->data[0]);
for (int i = 1; i < sfx->len; ++i) {
const int8_t sample = sfx->data[i];
if (ABS(sample) > sfx->peak) {
sfx->peak = ABS(sample);
}
sfx->data[i] = sample / kGain;
}
}
void Resource::load_SPL_demo() {
_numSfx = NUM_SFXS;
_sfxList = (SoundFx *)calloc(_numSfx, sizeof(SoundFx));
@ -207,6 +238,7 @@ void Resource::load_SPL_demo() {
sfx->offset = 0;
sfx->len = size;
sfx->freq = kPaulaFreq / 650;
normalizeSPL(sfx);
}
}
}
@ -277,7 +309,7 @@ void Resource::load_CMP_menu(const char *fileName) {
error("Failed to allocate CMP temporary buffer");
}
f.read(tmp, size);
if (!delphine_unpack(_scratchBuffer, kScratchBufferSize, tmp, size)) {
if (!bytekiller_unpack(_scratchBuffer, kScratchBufferSize, tmp, size)) {
error("Bad CRC for %s", fileName);
}
free(tmp);
@ -382,7 +414,7 @@ void Resource::load_CINE() {
case kResourceTypeDOS:
if (_cine_off == 0) {
snprintf(_entryName, sizeof(_entryName), "%sCINE.BIN", prefix);
if (!_fs->exists(_entryName)) {
if (!fileExists(_entryName)) {
strcpy(_entryName, "ENGCINE.BIN");
}
File f;
@ -407,7 +439,7 @@ void Resource::load_CINE() {
}
if (_cine_txt == 0) {
snprintf(_entryName, sizeof(_entryName), "%sCINE.TXT", prefix);
if (!_fs->exists(_entryName)) {
if (!fileExists(_entryName)) {
strcpy(_entryName, "ENGCINE.TXT");
}
File f;
@ -668,7 +700,7 @@ void Resource::load(const char *objName, int objType, const char *ext) {
_pal = dat;
break;
case OT_CT:
if (!delphine_unpack((uint8_t *)_ctData, sizeof(_ctData), dat, size)) {
if (!bytekiller_unpack((uint8_t *)_ctData, sizeof(_ctData), dat, size)) {
error("Bad CRC for '%s'", _entryName);
}
free(dat);
@ -678,7 +710,7 @@ void Resource::load(const char *objName, int objType, const char *ext) {
_numSpc = READ_BE_UINT16(_spc) / 2;
break;
case OT_RP:
if (size != 0x4A) {
if (size != sizeof(_rp)) {
error("Unexpected size %d for '%s'", size, _entryName);
}
memcpy(_rp, dat, size);
@ -707,6 +739,10 @@ void Resource::load(const char *objName, int objType, const char *ext) {
case OT_POL:
_pol = dat;
break;
case OT_SGD:
_sgd = dat;
_sgd[0] = 0; // clear number of entries, fix first offset
break;
case OT_BNQ:
_bnq = dat;
break;
@ -720,6 +756,7 @@ void Resource::load(const char *objName, int objType, const char *ext) {
switch (objType) {
case OT_CMD:
case OT_POL:
case OT_CMP:
warning("Unable to load '%s' type %d", _entryName, objType);
return;
}
@ -736,7 +773,7 @@ void Resource::load_CT(File *pf) {
error("Unable to allocate CT buffer");
} else {
pf->read(tmp, len);
if (!delphine_unpack((uint8_t *)_ctData, sizeof(_ctData), tmp, len)) {
if (!bytekiller_unpack((uint8_t *)_ctData, sizeof(_ctData), tmp, len)) {
error("Bad CRC for collision data");
}
free(tmp);
@ -803,7 +840,7 @@ void Resource::load_SPRM(File *f) {
void Resource::load_RP(File *f) {
debug(DBG_RES, "Resource::load_RP()");
f->read(_rp, 0x4A);
f->read(_rp, sizeof(_rp));
}
void Resource::load_SPC(File *f) {
@ -880,9 +917,9 @@ void Resource::load_OBJ(File *f) {
error("Unable to allocate ObjectNode num=%d", i);
}
f->seek(offsets[i] + 2);
on->last_obj_number = f->readUint16LE();
on->num_objects = objectsCount[iObj];
debug(DBG_RES, "last=%d num=%d", on->last_obj_number, on->num_objects);
on->num_objects = f->readUint16LE();
debug(DBG_RES, "count=%d", on->num_objects, objectsCount[iObj]);
assert(on->num_objects == objectsCount[iObj]);
on->objects = (Object *)malloc(sizeof(Object) * on->num_objects);
for (int j = 0; j < on->num_objects; ++j) {
Object *obj = &on->objects[j];
@ -936,7 +973,7 @@ void Resource::load_OBC(File *f) {
}
f->seek(4);
f->read(packedData, packedSize);
if (!delphine_unpack(tmp, unpackedSize, packedData, packedSize)) {
if (!bytekiller_unpack(tmp, unpackedSize, packedData, packedSize)) {
error("Bad CRC for compressed object data");
}
free(packedData);
@ -975,8 +1012,8 @@ void Resource::decodeOBJ(const uint8_t *tmp, int size) {
error("Unable to allocate ObjectNode num=%d", i);
}
const uint8_t *objData = tmp + offsets[i];
on->last_obj_number = _readUint16(objData); objData += 2;
on->num_objects = objectsCount[iObj];
on->num_objects = _readUint16(objData); objData += 2;
assert(on->num_objects == objectsCount[iObj]);
on->objects = (Object *)malloc(sizeof(Object) * on->num_objects);
for (int j = 0; j < on->num_objects; ++j) {
Object *obj = &on->objects[j];
@ -1027,7 +1064,7 @@ void Resource::load_PGE(File *f) {
pge->obj_node_number = f->readUint16LE();
pge->life = f->readUint16LE();
for (int lc = 0; lc < 4; ++lc) {
pge->counter_values[lc] = f->readUint16LE();
pge->data[lc] = f->readUint16LE();
}
pge->object_type = f->readByte();
pge->init_room = f->readByte();
@ -1039,7 +1076,7 @@ void Resource::load_PGE(File *f) {
pge->skill = f->readByte();
pge->mirror_x = f->readByte();
pge->flags = f->readByte();
pge->unk1C = f->readByte();
pge->collision_data_len = f->readByte();
f->readByte();
pge->text_num = f->readUint16LE();
}
@ -1058,7 +1095,7 @@ void Resource::decodePGE(const uint8_t *p, int size) {
pge->obj_node_number = _readUint16(p); p += 2;
pge->life = _readUint16(p); p += 2;
for (int lc = 0; lc < 4; ++lc) {
pge->counter_values[lc] = _readUint16(p); p += 2;
pge->data[lc] = _readUint16(p); p += 2;
}
pge->object_type = *p++;
pge->init_room = *p++;
@ -1070,7 +1107,7 @@ void Resource::decodePGE(const uint8_t *p, int size) {
pge->skill = *p++;
pge->mirror_x = *p++;
pge->flags = *p++;
pge->unk1C = *p++;
pge->collision_data_len = *p++;
++p;
pge->text_num = _readUint16(p); p += 2;
}
@ -1153,7 +1190,7 @@ void Resource::load_CMP(File *pf) {
}
if (data[0].packedSize == data[0].size) {
memcpy(_pol, tmp + data[0].offset, data[0].packedSize);
} else if (!delphine_unpack(_pol, data[0].size, tmp + data[0].offset, data[0].packedSize)) {
} else if (!bytekiller_unpack(_pol, data[0].size, tmp + data[0].offset, data[0].packedSize)) {
error("Bad CRC for cutscene polygon data");
}
_cmd = (uint8_t *)malloc(data[1].size);
@ -1162,7 +1199,7 @@ void Resource::load_CMP(File *pf) {
}
if (data[1].packedSize == data[1].size) {
memcpy(_cmd, tmp + data[1].offset, data[1].packedSize);
} else if (!delphine_unpack(_cmd, data[1].size, tmp + data[1].offset, data[1].packedSize)) {
} else if (!bytekiller_unpack(_cmd, data[1].size, tmp + data[1].offset, data[1].packedSize)) {
error("Bad CRC for cutscene command data");
}
free(tmp);
@ -1229,15 +1266,18 @@ void Resource::load_SPL(File *f) {
}
debug(DBG_RES, "sfx=%d size=%d", i, size);
assert(size != 0 && (size & 1) == 0);
if (i != 64) {
if (i == 64) {
warning("Skipping sound #%d (%s) size %d", i, _splNames[i], size);
f->seek(offset + size);
} else {
_sfxList[i].offset = offset;
_sfxList[i].len = size;
_sfxList[i].freq = kPaulaFreq / 650;
_sfxList[i].data = (uint8_t *)malloc(size);
assert(_sfxList[i].data);
f->read(_sfxList[i].data, size);
} else {
f->seek(offset + size);
if (_sfxList[i].data) {
f->read(_sfxList[i].data, size);
_sfxList[i].len = size;
normalizeSPL(&_sfxList[i]);
}
}
offset += size;
}
@ -1278,7 +1318,7 @@ void Resource::load_SGD(File *f) {
if (!_sgd) {
error("Unable to allocate SGD buffer");
}
if (!delphine_unpack(_sgd, size, tmp, len)) {
if (!bytekiller_unpack(_sgd, size, tmp, len)) {
error("Bad CRC for SGD data");
}
free(tmp);
@ -1310,12 +1350,12 @@ void Resource::load_SPM(File *f) {
if (!_spr1) {
error("Unable to allocate SPR1 buffer");
}
if (!delphine_unpack(_spr1, size, tmp, len)) {
if (!bytekiller_unpack(_spr1, size, tmp, len)) {
error("Bad CRC for SPM data");
}
} else {
assert(size <= sizeof(_sprm));
if (!delphine_unpack(_sprm, sizeof(_sprm), tmp, len)) {
if (!bytekiller_unpack(_sprm, sizeof(_sprm), tmp, len)) {
error("Bad CRC for SPM data");
}
}
@ -1377,6 +1417,10 @@ uint8_t *Resource::loadBankData(uint16_t num) {
dataOffset &= 0xFFFF;
}
const int size = getBankDataSize(num);
if (size == 0) {
warning("Invalid bank data %d", num);
return _bankDataHead;
}
const int avail = _bankDataTail - _bankDataHead;
if (avail < size) {
clearBankData();
@ -1391,7 +1435,7 @@ uint8_t *Resource::loadBankData(uint16_t num) {
} else {
assert(dataOffset > 4);
assert(size == (int)READ_BE_UINT32(data - 4));
if (!delphine_unpack(_bankDataHead, _bankDataTail - _bankDataHead, data, 0)) {
if (!bytekiller_unpack(_bankDataHead, _bankDataTail - _bankDataHead, data, 0)) {
error("Bad CRC for bank data %d", num);
}
}
@ -1400,23 +1444,51 @@ uint8_t *Resource::loadBankData(uint16_t num) {
return bankData;
}
uint8_t *Resource::decodeResourceMacText(const char *name, const char *suffix) {
char buf[256];
snprintf(buf, sizeof(buf), "%s %s", name, suffix);
const ResourceMacEntry *entry = _mac->findEntry(buf);
if (entry) {
return decodeResourceMacData(entry, false);
} else { // CD version
if (strcmp(name, "Flashback") == 0) {
name = "Game";
}
const char *language = (_lang == LANG_FR) ? "French" : "English";
snprintf(buf, sizeof(buf), "%s %s %s", name, suffix, language);
return decodeResourceMacData(buf, false);
}
}
uint8_t *Resource::decodeResourceMacData(const char *name, bool decompressLzss) {
_resourceMacDataSize = 0;
uint8_t *data = 0;
const ResourceMacEntry *entry = _mac->findEntry(name);
if (entry) {
_mac->_f.seek(_mac->_dataOffset + entry->dataOffset);
_resourceMacDataSize = _mac->_f.readUint32BE();
if (decompressLzss) {
data = decodeLzss(_mac->_f, _resourceMacDataSize);
} else {
data = (uint8_t *)malloc(_resourceMacDataSize);
if (data) {
_mac->_f.read(data, _resourceMacDataSize);
}
data = decodeResourceMacData(entry, decompressLzss);
} else {
_resourceMacDataSize = 0;
error("Resource '%s' not found", name);
}
return data;
}
uint8_t *Resource::decodeResourceMacData(const ResourceMacEntry *entry, bool decompressLzss) {
assert(entry);
_mac->_f.seek(_mac->_dataOffset + entry->dataOffset);
_resourceMacDataSize = _mac->_f.readUint32BE();
uint8_t *data = 0;
if (decompressLzss) {
data = decodeLzss(_mac->_f, _resourceMacDataSize);
if (!data) {
error("Failed to decompress '%s'", entry->name);
}
} else {
error("Resource '%s' not found", name);
data = (uint8_t *)malloc(_resourceMacDataSize);
if (!data) {
error("Failed to allocate %d bytes for '%s'", _resourceMacDataSize, entry->name);
} else {
_mac->_f.read(data, _resourceMacDataSize);
}
}
return data;
}
@ -1460,10 +1532,8 @@ void Resource::MAC_decodeDataCLUT(const uint8_t *ptr) {
void Resource::MAC_loadClutData() {
uint8_t *ptr = decodeResourceMacData("Flashback colors", false);
if (ptr) {
MAC_decodeDataCLUT(ptr);
free(ptr);
}
MAC_decodeDataCLUT(ptr);
free(ptr);
}
void Resource::MAC_loadFontData() {
@ -1534,61 +1604,49 @@ static const char *_macLevelNumbers[] = { "1", "2", "3", "4-1", "4-2", "5-1", "5
void Resource::MAC_loadLevelData(int level) {
char name[64];
// .PGE
snprintf(name, sizeof(name), "Level %s objects", _macLevelNumbers[level]);
uint8_t *ptr = decodeResourceMacData(name, true);
if (ptr) {
decodePGE(ptr, _resourceMacDataSize);
free(ptr);
} else {
error("Failed to load '%s'", name);
}
decodePGE(ptr, _resourceMacDataSize);
free(ptr);
// .ANI
snprintf(name, sizeof(name), "Level %s sequences", _macLevelNumbers[level]);
_ani = decodeResourceMacData(name, true);
if (_ani) {
assert(READ_BE_UINT16(_ani) == 0x48D);
} else {
error("Failed to load '%s'", name);
}
assert(READ_BE_UINT16(_ani) == 0x48D);
// .OBJ
snprintf(name, sizeof(name), "Level %s conditions", _macLevelNumbers[level]);
ptr = decodeResourceMacData(name, true);
if (ptr) {
assert(READ_BE_UINT16(ptr) == 0xE6);
decodeOBJ(ptr, _resourceMacDataSize);
free(ptr);
} else {
error("Failed to load '%s'", name);
}
assert(READ_BE_UINT16(ptr) == 0xE6);
decodeOBJ(ptr, _resourceMacDataSize);
free(ptr);
// .CT
snprintf(name, sizeof(name), "Level %c map", _macLevelNumbers[level][0]);
ptr = decodeResourceMacData(name, true);
if (ptr) {
assert(_resourceMacDataSize == 0x1D00);
memcpy(_ctData, ptr, _resourceMacDataSize);
free(ptr);
} else {
error("Failed to load '%s'", name);
}
assert(_resourceMacDataSize == 0x1D00);
memcpy(_ctData, ptr, _resourceMacDataSize);
free(ptr);
// .SPC
snprintf(name, sizeof(name), "Objects %c", _macLevelNumbers[level][0]);
_spc = decodeResourceMacData(name, true);
// .TBN
snprintf(name, sizeof(name), "Level %s names", _macLevelNumbers[level]);
_tbn = decodeResourceMacData(name, false);
_str = decodeResourceMacData("Flashback strings", false);
// .TBN
snprintf(name, sizeof(name), "Level %s", _macLevelNumbers[level]);
_tbn = decodeResourceMacText(name, "names");
_str = decodeResourceMacText("Flashback", "strings");
}
void Resource::MAC_loadLevelRoom(int level, int i, DecodeBuffer *dst) {
char name[64];
snprintf(name, sizeof(name), "Level %c Room %d", _macLevelNumbers[level][0], i);
uint8_t *ptr = decodeResourceMacData(name, true);
if (ptr) {
MAC_decodeImageData(ptr, 0, dst);
free(ptr);
}
MAC_decodeImageData(ptr, 0, dst);
free(ptr);
}
void Resource::MAC_clearClut16(Color *clut, uint8_t dest) {
@ -1663,24 +1721,33 @@ void Resource::MAC_unloadCutscene() {
}
void Resource::MAC_loadCutscene(const char *cutscene) {
MAC_unloadCutscene();
char name[32];
free(_cmd);
snprintf(name, sizeof(name), "%s movie", cutscene);
stringLowerCase(name);
_cmd = decodeResourceMacData(name, true);
free(_pol);
const ResourceMacEntry *cmdEntry = _mac->findEntry(name);
if (!cmdEntry) {
return;
}
_cmd = decodeResourceMacData(cmdEntry, true);
snprintf(name, sizeof(name), "%s polygons", cutscene);
stringLowerCase(name);
_pol = decodeResourceMacData(name, true);
const ResourceMacEntry *polEntry = _mac->findEntry(name);
if (!polEntry) {
return;
}
_pol = decodeResourceMacData(polEntry, true);
}
void Resource::MAC_loadCutsceneText() {
_cine_txt = decodeResourceMacData("Movie strings", false);
_cine_txt = decodeResourceMacText("Movie", "strings");
_cine_off = 0; // offsets are prepended to _cine_txt
}
void Resource::MAC_loadCreditsText() {
_credits = decodeResourceMacData("Credit strings", false);
_credits = decodeResourceMacText("Credit", "strings");
}
void Resource::MAC_loadSounds() {
@ -1697,12 +1764,13 @@ void Resource::MAC_loadSounds() {
return;
}
static const int kHeaderSize = 0x24;
static const int kSoundType = 4;
const int soundType = _mac->_sndIndex;
assert(soundType != -1);
for (int i = 0; i < NUM_SFXS; ++i) {
const int num = table[i];
if (num != -1) {
assert(num >= 0 && num < _mac->_types[kSoundType].count);
const ResourceMacEntry *entry = &_mac->_entries[kSoundType][num];
assert(num >= 0 && num < _mac->_types[soundType].count);
const ResourceMacEntry *entry = &_mac->_entries[soundType][num];
_mac->_f.seek(_mac->_dataOffset + entry->dataOffset);
int dataSize = _mac->_f.readUint32BE();
assert(dataSize > kHeaderSize);

View File

@ -1,7 +1,7 @@
/*
* REminiscence - Flashback interpreter
* Copyright (C) 2005-2018 Gregory Montoir (cyx@users.sourceforge.net)
* Copyright (C) 2005-2019 Gregory Montoir (cyx@users.sourceforge.net)
*/
#ifndef RESOURCE_H__
@ -119,6 +119,8 @@ struct Resource {
static const uint16_t _voicesOffsetsTable[];
static const uint32_t _spmOffsetsTable[];
static const char *_splNames[];
static const uint8_t _gameSavedSoundData[];
static const uint16_t _gameSavedSoundLen;
FileSystem *_fs;
ResourceType _type;
@ -135,13 +137,13 @@ struct Resource {
uint8_t *_icn;
int _icnLen;
uint8_t *_tab;
uint8_t *_spc; // BE
uint8_t *_spc;
uint16_t _numSpc;
uint8_t _rp[0x4A];
uint8_t *_pal; // BE
uint8_t _rp[74];
uint8_t *_pal;
uint8_t *_ani;
uint8_t *_tbn;
int8_t _ctData[0x1D00];
int8_t _ctData[256 + 112 * 64];
uint8_t *_spr1;
uint8_t *_sprData[NUM_SPRITES]; // 0-0x22F + 0x28E-0x2E9 ... conrad, 0x22F-0x28D : junkie
uint8_t _sprm[0x10000];
@ -311,12 +313,23 @@ struct Resource {
const char *getMenuString(int num) const {
return (num >= 0 && num < LocaleData::LI_NUM) ? _textsTable[num] : "";
}
const uint8_t *getCreditsString(int num) {
assert(_type == kResourceTypeMac);
const int count = READ_BE_UINT16(_credits);
if (num < count) {
const int offset = READ_BE_UINT16(_credits + 2 + num * 2);
return _credits + offset;
}
return 0;
}
void clearBankData();
int getBankDataSize(uint16_t num);
uint8_t *findBankData(uint16_t num);
uint8_t *loadBankData(uint16_t num);
uint8_t *decodeResourceMacText(const char *name, const char *suffix);
uint8_t *decodeResourceMacData(const char *name, bool decompressLzss);
uint8_t *decodeResourceMacData(const ResourceMacEntry *entry, bool decompressLzss);
void MAC_decodeImageData(const uint8_t *ptr, int i, DecodeBuffer *dst);
void MAC_decodeDataCLUT(const uint8_t *ptr);
void MAC_loadClutData();
@ -340,7 +353,7 @@ struct Resource {
void MAC_loadSounds();
int MAC_getPersoFrame(int anim) const {
static const int data[] = {
static const int16_t data[] = {
0x000, 0x22E,
0x28E, 0x2E9,
0x4E9, 0x506,
@ -358,7 +371,7 @@ struct Resource {
return 0;
}
int MAC_getMonsterFrame(int anim) const {
static const int data[] = {
static const int16_t data[] = {
0x22F, 0x28D, // junky - 94
0x2EA, 0x385, // mercenai - 156
0x387, 0x42F, // replican - 169

View File

@ -3,10 +3,9 @@
#include "unpack.h"
#include "util.h"
const char *ResourceAba::FILENAME = "DEMO_UK.ABA";
ResourceAba::ResourceAba(FileSystem *fs)
: _fs(fs) {
_filesCount = 0;
_entries = 0;
_entriesCount = 0;
}
@ -15,40 +14,49 @@ ResourceAba::~ResourceAba() {
free(_entries);
}
void ResourceAba::readEntries() {
if (_f.open(FILENAME, "rb", _fs)) {
_entriesCount = _f.readUint16BE();
_entries = (ResourceAbaEntry *)calloc(_entriesCount, sizeof(ResourceAbaEntry));
static int compareAbaEntry(const void *a, const void *b) {
return strcasecmp(((const ResourceAbaEntry *)a)->name, ((const ResourceAbaEntry *)b)->name);
}
void ResourceAba::readEntries(const char *aba) {
assert(_filesCount < 3);
if (_f[_filesCount].open(aba, "rb", _fs)) {
File &f = _f[_filesCount];
const int currentCount = _entriesCount;
const int entriesCount = f.readUint16BE();
_entries = (ResourceAbaEntry *)realloc(_entries, (currentCount + entriesCount) * sizeof(ResourceAbaEntry));
if (!_entries) {
error("Failed to allocate %d _entries", _entriesCount);
error("Failed to allocate %d _entries", currentCount + entriesCount);
return;
}
const int entrySize = _f.readUint16BE();
_entriesCount = currentCount + entriesCount;
const int entrySize = f.readUint16BE();
assert(entrySize == 30);
uint32_t nextOffset = 0;
for (int i = 0; i < _entriesCount; ++i) {
_f.read(_entries[i].name, sizeof(_entries[i].name));
_entries[i].offset = _f.readUint32BE();
_entries[i].compressedSize = _f.readUint32BE();
_entries[i].size = _f.readUint32BE();
const uint32_t tag = _f.readUint32BE();
for (int i = 0; i < entriesCount; ++i) {
const int j = currentCount + i;
f.read(_entries[j].name, sizeof(_entries[j].name));
_entries[j].offset = f.readUint32BE();
_entries[j].compressedSize = f.readUint32BE();
_entries[j].size = f.readUint32BE();
_entries[j].fileIndex = _filesCount;
const uint32_t tag = f.readUint32BE();
assert(tag == TAG);
debug(DBG_RES, "'%s' offset 0x%X size %d/%d", _entries[i].name, _entries[i].offset, _entries[i].compressedSize, _entries[i].size);
debug(DBG_RES, "'%s' offset 0x%X size %d/%d", _entries[j].name, _entries[j].offset, _entries[j].compressedSize, _entries[j].size);
if (i != 0) {
assert(nextOffset == _entries[i].offset);
assert(nextOffset == _entries[j].offset);
}
nextOffset = _entries[i].offset + _entries[i].compressedSize;
nextOffset = _entries[j].offset + _entries[j].compressedSize;
}
qsort(_entries, _entriesCount, sizeof(ResourceAbaEntry), compareAbaEntry);
++_filesCount;
}
}
const ResourceAbaEntry *ResourceAba::findEntry(const char *name) const {
for (int i = 0; i < _entriesCount; ++i) {
if (strcasecmp(_entries[i].name, name) == 0) {
return &_entries[i];
}
}
return 0;
ResourceAbaEntry tmp;
strcpy(tmp.name, name);
return (const ResourceAbaEntry *)bsearch(&tmp, _entries, _entriesCount, sizeof(ResourceAbaEntry), compareAbaEntry);
}
uint8_t *ResourceAba::loadEntry(const char *name, uint32_t *size) {
@ -63,8 +71,8 @@ uint8_t *ResourceAba::loadEntry(const char *name, uint32_t *size) {
error("Failed to allocate %d bytes", e->compressedSize);
return 0;
}
_f.seek(e->offset);
_f.read(tmp, e->compressedSize);
_f[e->fileIndex].seek(e->offset);
_f[e->fileIndex].read(tmp, e->compressedSize);
if (e->compressedSize == e->size) {
dst = tmp;
} else {
@ -74,7 +82,7 @@ uint8_t *ResourceAba::loadEntry(const char *name, uint32_t *size) {
free(tmp);
return 0;
}
const bool ret = delphine_unpack(dst, e->size, tmp, e->compressedSize);
const bool ret = bytekiller_unpack(dst, e->size, tmp, e->compressedSize);
if (!ret) {
error("Bad CRC for '%s'", name);
}

View File

@ -11,22 +11,23 @@ struct ResourceAbaEntry {
uint32_t offset;
uint32_t compressedSize;
uint32_t size;
int fileIndex;
};
struct ResourceAba {
static const char *FILENAME;
static const int TAG = 0x442E4D2E;
FileSystem *_fs;
File _f;
File _f[3];
int _filesCount;
ResourceAbaEntry *_entries;
int _entriesCount;
ResourceAba(FileSystem *fs);
~ResourceAba();
void readEntries();
void readEntries(const char *aba);
const ResourceAbaEntry *findEntry(const char *name) const;
uint8_t *loadEntry(const char *name, uint32_t *size = 0);
};

View File

@ -10,7 +10,7 @@ const char *ResourceMac::FILENAME1 = "Flashback.bin";
const char *ResourceMac::FILENAME2 = "Flashback.rsrc";
ResourceMac::ResourceMac(const char *filePath, FileSystem *fs)
: _dataOffset(0), _types(0), _entries(0) {
: _dataOffset(0), _types(0), _entries(0), _sndIndex(-1) {
memset(&_map, 0, sizeof(_map));
_f.open(filePath, "rb", fs);
}
@ -66,6 +66,9 @@ void ResourceMac::loadResourceFork(uint32_t resourceOffset, uint32_t dataSize) {
_f.read(_types[i].id, 4);
_types[i].count = _f.readUint16BE() + 1;
_types[i].startOffset = _f.readUint16BE();
if (_sndIndex < 0 && memcmp(_types[i].id, "snd ", 4) == 0) {
_sndIndex = i;
}
}
_entries = (ResourceMacEntry **)calloc(_map.typesCount, sizeof(ResourceMacEntry *));
for (int i = 0; i < _map.typesCount; ++i) {

View File

@ -39,6 +39,7 @@ struct ResourceMac {
ResourceMacMap _map;
ResourceMacType *_types;
ResourceMacEntry **_entries;
int _sndIndex;
ResourceMac(const char *filePath, FileSystem *);
~ResourceMac();

27
rs.cfg
View File

@ -1,4 +1,4 @@
# display copy protection shapes
# display copy protection (shapes or words lookup)
bypass_protection=true
# use original password level selection menu screen
@ -11,22 +11,43 @@ enable_language_selection=true
fade_out_palette=false
# use .BNQ & .LEV datafiles (tile based rendering) for backgrounds (instead of .MAP)
use_tiledata=false
use_tile_data=false
# display text instead of playing the polygon cutscenes
use_text_cutscenes=false
# enable playback of .SEQ cutscenes (always use polygon cutscenes if false)
# enable playback of PC CD .SEQ cutscenes (use polygon cutscenes if false)
use_seq_cutscenes=true
# if copy protection is enabled, display the words manual lookup screen (as in DOS SSI version)
use_words_protection=false
# white t-shirt for Conrad
use_white_tshirt=false
# use .PRF music files
use_prf_music=true
# enable playback of 'ASC' cutscene
play_asc_cutscene=false
# enable playback of 'CAILLOU-F.SET' cutscene
play_caillou_cutscene=true
# enable playback of 'CARTE' cutscene
play_carte_cutscene=true
# enable playback of 'METRO' cutscene
play_metro_cutscene=false
# enable playback of 'SERRURE' cutscene
play_serrure_cutscene=true
# play 'Game saved' sample when saving with level checkpoints (as in the 3DO version)
play_gamesaved_sound=true
# restore content from 'MEMO' cutscene
restore_memo_cutscene=true
# order inventory items as the original game, last grabbed item is at the bottom of the inventory
order_inventory_original=false

View File

@ -1,11 +1,10 @@
/*
* REminiscence - Flashback interpreter
* Copyright (C) 2005-2018 Gregory Montoir (cyx@users.sourceforge.net)
* Copyright (C) 2005-2019 Gregory Montoir (cyx@users.sourceforge.net)
*/
#include "scaler.h"
#include "dynlib.h"
#include "util.h"
static void scanline2x(uint32_t *dst0, uint32_t *dst1, const uint32_t *src0, const uint32_t *src1, const uint32_t *src2, int w) {
@ -282,17 +281,3 @@ const Scaler _internalScaler = {
2, 4,
scaleNx,
};
static DynLib *dynLib;
static const char *kSoSym = "getScaler";
const Scaler *findScaler(const char *name) {
dynLib = new DynLib(name);
void *symbol = dynLib->getSymbol(kSoSym);
if (symbol) {
typedef const Scaler *(*GetScalerProc)();
return ((GetScalerProc)symbol)();
}
return 0;
}

View File

@ -1,7 +1,7 @@
/*
* REminiscence - Flashback interpreter
* Copyright (C) 2005-2018 Gregory Montoir (cyx@users.sourceforge.net)
* Copyright (C) 2005-2019 Gregory Montoir (cyx@users.sourceforge.net)
*/
#ifndef SCALER_H__
@ -34,7 +34,7 @@ const Scaler *findScaler(const char *name);
#ifdef USE_STATIC_SCALER
extern const Scaler scaler_nearest;
extern const Scaler scaler_tv2x;
extern const Scaler scaler_xbrz;
extern const Scaler scaler_xbr;
#endif
#endif // SCALER_H__

View File

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

View File

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

View File

@ -1,7 +1,7 @@
/*
* REminiscence - Flashback interpreter
* Copyright (C) 2005-2018 Gregory Montoir (cyx@users.sourceforge.net)
* Copyright (C) 2005-2019 Gregory Montoir (cyx@users.sourceforge.net)
*/
#include "file.h"
@ -24,6 +24,7 @@ void SeqDemuxer::close() {
for (int i = 0; i < kBuffersCount; ++i) {
free(_buffers[i].data);
}
memset(_buffers, 0, sizeof(_buffers));
}
bool SeqDemuxer::readHeader() {
@ -56,9 +57,7 @@ bool SeqDemuxer::readFrameData() {
}
_f->seek(_frameOffset);
_audioDataOffset = _f->readUint16LE();
_audioDataSize = (_audioDataOffset != 0) ? kAudioBufferSize * 2 : 0;
_paletteDataOffset = _f->readUint16LE();
_paletteDataSize = (_paletteDataOffset != 0) ? 768 : 0;
uint8_t num[4];
for (int i = 0; i < 4; ++i) {
num[i] = _f->readByte();
@ -219,6 +218,7 @@ SeqPlayer::SeqPlayer(SystemStub *stub, Mixer *mixer)
: _stub(stub), _buf(0), _mix(mixer) {
_soundQueuePreloadSize = 0;
_soundQueue = 0;
_soundQueueTail = 0;
}
SeqPlayer::~SeqPlayer() {
@ -226,10 +226,8 @@ 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]);
}
uint8_t palette[256 * 3];
_stub->getPalette(palette, 256);
_mix->setPremixHook(mixCallback, this);
memset(_buf, 0, 256 * 224);
bool clearScreen = true;
@ -243,7 +241,7 @@ void SeqPlayer::play(File *f) {
if (!_demux.readFrameData()) {
break;
}
if (_demux._audioDataSize != 0) {
if (_demux._audioDataOffset != 0) {
SoundBufferQueue *sbq = (SoundBufferQueue *)malloc(sizeof(SoundBufferQueue));
if (sbq) {
sbq->data = (int16_t *)calloc(SeqDemuxer::kAudioBufferSize, sizeof(int16_t));
@ -259,25 +257,23 @@ void SeqPlayer::play(File *f) {
}
if (sbq) {
LockAudioStack las(_stub);
if (!_soundQueue) {
_soundQueue = sbq;
if (_soundQueueTail) {
_soundQueueTail->next = sbq;
} else {
SoundBufferQueue *p = _soundQueue;
while (p->next) {
p = p->next;
}
p->next = sbq;
assert(!_soundQueue);
_soundQueue = sbq;
}
_soundQueueTail = sbq;
if (_soundQueuePreloadSize < kSoundPreloadSize) {
++_soundQueuePreloadSize;
}
}
}
if (_demux._paletteDataSize != 0) {
if (_demux._paletteDataOffset != 0) {
uint8_t buf[256 * 3];
_demux.readPalette(buf);
for (int i = 0; i < 256 * 3; ++i) {
buf[i] = (buf[i] << 2) | (buf[i] & 3);
buf[i] = (buf[i] << 2) | (buf[i] >> 4);
}
_stub->setPalette(buf, 256);
}
@ -315,9 +311,8 @@ void SeqPlayer::play(File *f) {
_stub->sleep(diff);
}
}
for (int i = 0; i < 256; ++i) {
_stub->setPaletteEntry(i, &pal[i]);
}
// restore level palette
_stub->setPalette(palette, 256);
_mix->setPremixHook(0, 0);
_demux.close();
// flush sound queue
@ -328,6 +323,7 @@ void SeqPlayer::play(File *f) {
free(_soundQueue);
_soundQueue = next;
}
_soundQueueTail = 0;
_soundQueuePreloadSize = 0;
}
}
@ -337,15 +333,24 @@ bool SeqPlayer::mix(int16_t *buf, int samples) {
return true;
}
while (_soundQueue && samples > 0) {
*buf++ = _soundQueue->data[_soundQueue->read];
++_soundQueue->read;
const int count = MIN(samples, _soundQueue->size - _soundQueue->read);
const int16_t *src = (const int16_t *)(_soundQueue->data + _soundQueue->read);
for (int i = 0; i < count; ++i) {
const int16_t sample = *src++;
*buf++ = sample;
*buf++ = sample;
}
_soundQueue->read += count;
if (_soundQueue->read == _soundQueue->size) {
SoundBufferQueue *next = _soundQueue->next;
free(_soundQueue->data);
free(_soundQueue);
_soundQueue = next;
}
--samples;
samples -= count;
}
if (!_soundQueue) {
_soundQueueTail = 0;
}
return true;
}

View File

@ -1,7 +1,7 @@
/*
* REminiscence - Flashback interpreter
* Copyright (C) 2005-2018 Gregory Montoir (cyx@users.sourceforge.net)
* Copyright (C) 2005-2019 Gregory Montoir (cyx@users.sourceforge.net)
*/
#ifndef SEQ_PLAYER_H__
@ -32,9 +32,7 @@ struct SeqDemuxer {
int _frameOffset;
int _audioDataOffset;
int _audioDataSize;
int _paletteDataOffset;
int _paletteDataSize;
int _videoData;
struct {
int size;
@ -74,7 +72,7 @@ struct SeqPlayer {
Mixer *_mix;
SeqDemuxer _demux;
int _soundQueuePreloadSize;
SoundBufferQueue *_soundQueue;
SoundBufferQueue *_soundQueue, *_soundQueueTail;
};
#endif // SEQ_PLAYER_H__

View File

@ -1,13 +1,35 @@
/*
* REminiscence - Flashback interpreter
* Copyright (C) 2005-2018 Gregory Montoir (cyx@users.sourceforge.net)
* Copyright (C) 2005-2019 Gregory Montoir (cyx@users.sourceforge.net)
*/
#include "mixer.h"
#include "sfx_player.h"
#include "util.h"
// volume instruments are either equal to 64 or 32 (this corresponds to aud0vol)
// use one third of the volume for master (for comparison, modplug uses a master volume of 128, max 512)
static const int kMasterVolume = 64 * 3;
// 12 dB/oct Butterworth low-pass filter at 3.3 kHz
static const bool kLowPassFilter = false;
#define NZEROS 2
#define NPOLES 2
static float bw_xf[NZEROS+1], bw_yf[NPOLES+1];
static const float GAIN = 7.655158005e+00;
static void butterworth(int16_t *p, int len) {
for (int i = 0; i < len; ++i) {
bw_xf[0] = bw_xf[1]; bw_xf[1] = bw_xf[2];
bw_xf[2] = p[i] / GAIN;
bw_yf[0] = bw_yf[1]; bw_yf[1] = bw_yf[2];
bw_yf[2] = (bw_xf[0] + bw_xf[2]) + 2 * bw_xf[1] + (-0.2729352339 * bw_yf[0]) + (0.7504117278 * bw_yf[1]);
p[i] = (int16_t)CLIP(bw_yf[2], -32768.f, 32767.f);
}
}
SfxPlayer::SfxPlayer(Mixer *mixer)
: _mod(0), _playing(false), _mix(mixer) {
}
@ -15,20 +37,23 @@ SfxPlayer::SfxPlayer(Mixer *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;
assert(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;
if (kLowPassFilter) {
memset(bw_xf, 0, sizeof(bw_xf));
memset(bw_yf, 0, sizeof(bw_yf));
}
}
}
@ -126,8 +151,11 @@ void SfxPlayer::mixSamples(int16_t *buf, int samplesLen) {
curLen = 0;
}
while (count--) {
const int out = si->getPCM(pos >> FRAC_BITS);
*mixbuf = ADDC_S16(*mixbuf, (out * si->vol / 64) << 8);
const int sample8 = si->getPCM(pos >> FRAC_BITS) * si->vol / kMasterVolume;
const int sample16 = S8_to_S16(sample8);
*mixbuf = ADDC_S16(*mixbuf, sample16);
++mixbuf;
*mixbuf = ADDC_S16(*mixbuf, sample16);
++mixbuf;
pos += deltaPos;
}
@ -138,7 +166,7 @@ void SfxPlayer::mixSamples(int16_t *buf, int samplesLen) {
}
bool SfxPlayer::mix(int16_t *buf, int len) {
memset(buf, 0, sizeof(int16_t) * len);
memset(buf, 0, sizeof(int16_t) * len * 2); // stereo
if (_playing) {
const int samplesPerTick = _mix->getSampleRate() / 50;
while (len != 0) {
@ -153,7 +181,10 @@ bool SfxPlayer::mix(int16_t *buf, int len) {
_samplesLeft -= count;
len -= count;
mixSamples(buf, count);
buf += count;
if (kLowPassFilter) {
butterworth(buf, count * 2); // stereo
}
buf += count * 2; // stereo
}
}
return _playing;

View File

@ -1,7 +1,7 @@
/*
* REminiscence - Flashback interpreter
* Copyright (C) 2005-2018 Gregory Montoir (cyx@users.sourceforge.net)
* Copyright (C) 2005-2019 Gregory Montoir (cyx@users.sourceforge.net)
*/
#ifndef SFX_PLAYER_H__

File diff suppressed because it is too large Load Diff

5584
stb_vorbis.c Executable file

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
/*
* REminiscence - Flashback interpreter
* Copyright (C) 2005-2018 Gregory Montoir (cyx@users.sourceforge.net)
* Copyright (C) 2005-2019 Gregory Montoir (cyx@users.sourceforge.net)
*/
#ifndef SYSTEMSTUB_H__
@ -20,7 +20,8 @@ struct PlayerInput {
enum {
DF_FASTMODE = 1 << 0,
DF_DBLOCKS = 1 << 1,
DF_SETLIFE = 1 << 2
DF_SETLIFE = 1 << 2,
DF_AUTOZOOM = 1 << 3
};
uint8_t dirMask;
@ -35,6 +36,7 @@ struct PlayerInput {
bool save;
bool load;
int stateSlot;
bool rewind;
uint8_t dbgMask;
bool quit;
@ -42,7 +44,7 @@ struct PlayerInput {
struct ScalerParameters {
ScalerType type;
const Scaler *scaler;
char name[32];
int factor;
static ScalerParameters defaults();
@ -55,20 +57,26 @@ struct SystemStub {
virtual ~SystemStub() {}
virtual void init(const char *title, int w, int h, bool fullscreen, bool widescreen, ScalerParameters *scalerParameters) = 0;
virtual void init(const char *title, int w, int h, bool fullscreen, int widescreenMode, const ScalerParameters *scalerParameters) = 0;
virtual void destroy() = 0;
virtual bool hasWidescreen() const = 0;
virtual void setScreenSize(int w, int h) = 0;
virtual void setPalette(const uint8_t *pal, int n) = 0;
virtual void getPalette(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 copyRectRgb24(int x, int y, int w, int h, const uint8_t *rgb) = 0;
virtual void copyRectLeftBorder(int w, int h, const uint8_t *buf) = 0;
virtual void copyRectRightBorder(int w, int h, const uint8_t *buf) = 0;
virtual void copyRectMirrorBorders(int w, int h, const uint8_t *buf) = 0;
virtual void zoomRect(int x, int y, int h, int w) = 0;
virtual void copyWidescreenLeft(int w, int h, const uint8_t *buf) = 0;
virtual void copyWidescreenRight(int w, int h, const uint8_t *buf) = 0;
virtual void copyWidescreenMirror(int w, int h, const uint8_t *buf) = 0;
virtual void copyWidescreenBlur(int w, int h, const uint8_t *buf) = 0;
virtual void copyWidescreenCDi(int w, int h, const uint8_t *buf, const uint8_t *pal) = 0;
virtual void clearWidescreen() = 0;
virtual void enableWidescreen(bool enable) = 0;
virtual void fadeScreen() = 0;
virtual void updateScreen(int shakeOffset) = 0;
@ -94,6 +102,22 @@ struct LockAudioStack {
SystemStub *_stub;
};
struct ToggleWidescreenStack {
ToggleWidescreenStack(SystemStub *stub, bool state)
: _stub(stub), _state(state) {
if (_stub->hasWidescreen()) {
_stub->enableWidescreen(_state);
}
}
~ToggleWidescreenStack() {
if (_stub->hasWidescreen()) {
_stub->enableWidescreen(!_state);
}
}
SystemStub *_stub;
bool _state;
};
extern SystemStub *SystemStub_SDL_create();
#endif // SYSTEMSTUB_H__

View File

@ -1,7 +1,6 @@
/*
* REminiscence - Flashback interpreter
* Copyright (C) 2005-2018 Gregory Montoir (cyx@users.sourceforge.net)
* Copyright (C) 2005-2019 Gregory Montoir (cyx@users.sourceforge.net)
*/
#include <SDL.h>
@ -22,7 +21,7 @@ static const uint32_t kPixelFormat = SDL_PIXELFORMAT_RGB888;
ScalerParameters ScalerParameters::defaults() {
ScalerParameters params;
params.type = kScalerTypeInternal;
params.scaler = &_internalScaler;
params.name[0] = 0;
params.factor = _internalScaler.factorMin + (_internalScaler.factorMax - _internalScaler.factorMin) / 2;
return params;
}
@ -32,6 +31,7 @@ struct SystemStub_SDL : SystemStub {
SDL_Renderer *_renderer;
SDL_Texture *_texture;
int _texW, _texH;
SDL_Rect _texRect;
SDL_GameController *_controller;
SDL_PixelFormat *_fmt;
const char *_caption;
@ -47,26 +47,34 @@ struct SystemStub_SDL : SystemStub {
void *_audioCbData;
int _screenshot;
ScalerType _scalerType;
const Scaler *_scaler;
int _scaleFactor;
bool _widescreen;
SDL_Texture *_wideTexture;
const Scaler *_scaler;
void *_scalerSo;
int _widescreenMode;
SDL_Texture *_widescreenTexture;
int _wideMargin;
bool _enableWidescreen;
virtual ~SystemStub_SDL() {}
virtual void init(const char *title, int w, int h, bool fullscreen, bool widescreen, ScalerParameters *scalerParameters);
virtual void init(const char *title, int w, int h, bool fullscreen, int widescreenMode, const ScalerParameters *scalerParameters);
virtual void destroy();
virtual bool hasWidescreen() const;
virtual void setScreenSize(int w, int h);
virtual void setPalette(const uint8_t *pal, int n);
virtual void getPalette(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 copyRectRgb24(int x, int y, int w, int h, const uint8_t *rgb);
virtual void copyRectLeftBorder(int w, int h, const uint8_t *buf);
virtual void copyRectRightBorder(int w, int h, const uint8_t *buf);
virtual void copyRectMirrorBorders(int w, int h, const uint8_t *buf);
virtual void zoomRect(int x, int y, int w, int h);
virtual void copyWidescreenLeft(int w, int h, const uint8_t *buf);
virtual void copyWidescreenRight(int w, int h, const uint8_t *buf);
virtual void copyWidescreenMirror(int w, int h, const uint8_t *buf);
virtual void copyWidescreenBlur(int w, int h, const uint8_t *buf);
virtual void copyWidescreenCDi(int w, int h, const uint8_t *buf, const uint8_t *pal);
virtual void clearWidescreen();
virtual void enableWidescreen(bool enable);
virtual void fadeScreen();
virtual void updateScreen(int shakeOffset);
virtual void processEvents();
@ -83,7 +91,8 @@ struct SystemStub_SDL : SystemStub {
void prepareGraphics();
void cleanupGraphics();
void changeGraphics(bool fullscreen, int scaleFactor);
void changeScaler(int scaler);
void setScaler(const ScalerParameters *parameters);
void changeScaler(int scalerNum);
void drawRect(int x, int y, int w, int h, uint8_t color);
};
@ -91,8 +100,8 @@ SystemStub *SystemStub_SDL_create() {
return new SystemStub_SDL();
}
void SystemStub_SDL::init(const char *title, int w, int h, bool fullscreen, bool widescreen, ScalerParameters *scalerParameters) {
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_JOYSTICK);
void SystemStub_SDL::init(const char *title, int w, int h, bool fullscreen, int widescreenMode, const ScalerParameters *scalerParameters) {
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER);
SDL_ShowCursor(SDL_DISABLE);
_caption = title;
memset(&_pi, 0, sizeof(_pi));
@ -103,15 +112,20 @@ void SystemStub_SDL::init(const char *title, int w, int h, bool fullscreen, bool
_screenBuffer = 0;
_fadeOnUpdateScreen = false;
_fullscreen = fullscreen;
_scalerType = scalerParameters->type;
_scaler = scalerParameters->scaler;
_scaleFactor = _scaler ? CLIP(scalerParameters->factor, _scaler->factorMin, _scaler->factorMax) : 1;
_scalerType = kScalerTypeInternal;
_scaleFactor = 1;
_scaler = 0;
_scalerSo = 0;
if (scalerParameters->name[0]) {
setScaler(scalerParameters);
}
memset(_rgbPalette, 0, sizeof(_rgbPalette));
memset(_darkPalette, 0, sizeof(_darkPalette));
_screenW = _screenH = 0;
_widescreen = widescreen;
_wideTexture = 0;
_widescreenMode = widescreenMode;
_widescreenTexture = 0;
_wideMargin = 0;
_enableWidescreen = false;
setScreenSize(w, h);
_joystick = 0;
_controller = 0;
@ -137,6 +151,10 @@ void SystemStub_SDL::destroy() {
SDL_FreeFormat(_fmt);
_fmt = 0;
}
if (_scalerSo) {
SDL_UnloadObject(_scalerSo);
_scalerSo = 0;
}
if (_controller) {
SDL_GameControllerClose(_controller);
_controller = 0;
@ -149,7 +167,7 @@ void SystemStub_SDL::destroy() {
}
bool SystemStub_SDL::hasWidescreen() const {
return _widescreen;
return _widescreenMode != kWidescreenNone;
}
void SystemStub_SDL::setScreenSize(int w, int h) {
@ -184,6 +202,14 @@ void SystemStub_SDL::setPalette(const uint8_t *pal, int n) {
}
}
void SystemStub_SDL::getPalette(uint8_t *pal, int n) {
assert(n <= 256);
for (int i = 0; i < n; ++i) {
SDL_GetRGB(_rgbPalette[i], _fmt, &pal[0], &pal[1], &pal[2]);
pal += 3;
}
}
void SystemStub_SDL::setPaletteEntry(int i, const Color *c) {
setPaletteColor(i, c->r, c->g, c->b);
}
@ -246,6 +272,16 @@ void SystemStub_SDL::copyRectRgb24(int x, int y, int w, int h, const uint8_t *rg
}
}
void SystemStub_SDL::zoomRect(int x, int y, int w, int h) {
if (_pi.dbgMask & PlayerInput::DF_DBLOCKS) {
drawRect(x, y, w, h, 0xE7);
}
_texRect.x = x * _texW / _screenW;
_texRect.y = y * _texH / _screenH;
_texRect.w = w * _texW / _screenW;
_texRect.h = h * _texH / _screenH;
}
static void clearTexture(SDL_Texture *texture, int h, SDL_PixelFormat *fmt) {
void *dst = 0;
int pitch = 0;
@ -259,7 +295,7 @@ static void clearTexture(SDL_Texture *texture, int h, SDL_PixelFormat *fmt) {
}
}
void SystemStub_SDL::copyRectLeftBorder(int w, int h, const uint8_t *buf) {
void SystemStub_SDL::copyWidescreenLeft(int w, int h, const uint8_t *buf) {
assert(w >= _wideMargin);
uint32_t *rgb = (uint32_t *)malloc(w * h * sizeof(uint32_t));
if (rgb) {
@ -279,12 +315,12 @@ void SystemStub_SDL::copyRectLeftBorder(int w, int h, const uint8_t *buf) {
r.y = 0;
r.w = _wideMargin;
r.h = h;
SDL_UpdateTexture(_wideTexture, &r, rgb + xOffset, w * sizeof(uint32_t));
SDL_UpdateTexture(_widescreenTexture, &r, rgb + xOffset, w * sizeof(uint32_t));
free(rgb);
}
}
void SystemStub_SDL::copyRectRightBorder(int w, int h, const uint8_t *buf) {
void SystemStub_SDL::copyWidescreenRight(int w, int h, const uint8_t *buf) {
assert(w >= _wideMargin);
uint32_t *rgb = (uint32_t *)malloc(w * h * sizeof(uint32_t));
if (rgb) {
@ -304,12 +340,12 @@ void SystemStub_SDL::copyRectRightBorder(int w, int h, const uint8_t *buf) {
r.y = 0;
r.w = _wideMargin;
r.h = h;
SDL_UpdateTexture(_wideTexture, &r, rgb + xOffset, w * sizeof(uint32_t));
SDL_UpdateTexture(_widescreenTexture, &r, rgb + xOffset, w * sizeof(uint32_t));
free(rgb);
}
}
void SystemStub_SDL::copyRectMirrorBorders(int w, int h, const uint8_t *buf) {
void SystemStub_SDL::copyWidescreenMirror(int w, int h, const uint8_t *buf) {
assert(w >= _wideMargin);
uint32_t *rgb = (uint32_t *)malloc(w * h * sizeof(uint32_t));
if (rgb) {
@ -318,7 +354,7 @@ void SystemStub_SDL::copyRectMirrorBorders(int w, int h, const uint8_t *buf) {
}
void *dst = 0;
int pitch = 0;
if (SDL_LockTexture(_wideTexture, 0, &dst, &pitch) == 0) {
if (SDL_LockTexture(_widescreenTexture, 0, &dst, &pitch) == 0) {
assert((pitch & 3) == 0);
uint32_t *p = (uint32_t *)dst;
for (int y = 0; y < h; ++y) {
@ -332,12 +368,147 @@ void SystemStub_SDL::copyRectMirrorBorders(int w, int h, const uint8_t *buf) {
}
p += pitch / sizeof(uint32_t);
}
SDL_UnlockTexture(_wideTexture);
SDL_UnlockTexture(_widescreenTexture);
}
free(rgb);
}
}
static void blur_h(int radius, const uint32_t *src, int srcPitch, int w, int h, const SDL_PixelFormat *fmt, uint32_t *dst, int dstPitch) {
const int count = 2 * radius + 1;
for (int y = 0; y < h; ++y) {
uint32_t r = 0;
uint32_t g = 0;
uint32_t b = 0;
uint32_t color;
for (int x = -radius; x <= radius; ++x) {
color = src[MAX(x, 0)];
r += (color & fmt->Rmask) >> fmt->Rshift;
g += (color & fmt->Gmask) >> fmt->Gshift;
b += (color & fmt->Bmask) >> fmt->Bshift;
}
dst[0] = ((r / count) << fmt->Rshift) | ((g / count) << fmt->Gshift) | ((b / count) << fmt->Bshift);
for (int x = 1; x < w; ++x) {
color = src[MIN(x + radius, w - 1)];
r += (color & fmt->Rmask) >> fmt->Rshift;
g += (color & fmt->Gmask) >> fmt->Gshift;
b += (color & fmt->Bmask) >> fmt->Bshift;
color = src[MAX(x - radius - 1, 0)];
r -= (color & fmt->Rmask) >> fmt->Rshift;
g -= (color & fmt->Gmask) >> fmt->Gshift;
b -= (color & fmt->Bmask) >> fmt->Bshift;
dst[x] = ((r / count) << fmt->Rshift) | ((g / count) << fmt->Gshift) | ((b / count) << fmt->Bshift);
}
src += srcPitch;
dst += dstPitch;
}
}
static void blur_v(int radius, const uint32_t *src, int srcPitch, int w, int h, const SDL_PixelFormat *fmt, uint32_t *dst, int dstPitch) {
const int count = 2 * radius + 1;
for (int x = 0; x < w; ++x) {
uint32_t r = 0;
uint32_t g = 0;
uint32_t b = 0;
uint32_t color;
for (int y = -radius; y <= radius; ++y) {
color = src[MAX(y, 0) * srcPitch];
r += (color & fmt->Rmask) >> fmt->Rshift;
g += (color & fmt->Gmask) >> fmt->Gshift;
b += (color & fmt->Bmask) >> fmt->Bshift;
}
dst[0] = ((r / count) << fmt->Rshift) | ((g / count) << fmt->Gshift) | ((b / count) << fmt->Bshift);
for (int y = 1; y < h; ++y) {
color = src[MIN(y + radius, h - 1) * srcPitch];
r += (color & fmt->Rmask) >> fmt->Rshift;
g += (color & fmt->Gmask) >> fmt->Gshift;
b += (color & fmt->Bmask) >> fmt->Bshift;
color = src[MAX(y - radius - 1, 0) * srcPitch];
r -= (color & fmt->Rmask) >> fmt->Rshift;
g -= (color & fmt->Gmask) >> fmt->Gshift;
b -= (color & fmt->Bmask) >> fmt->Bshift;
dst[y * dstPitch] = ((r / count) << fmt->Rshift) | ((g / count) << fmt->Gshift) | ((b / count) << fmt->Bshift);
}
++src;
++dst;
}
}
void SystemStub_SDL::copyWidescreenBlur(int w, int h, const uint8_t *buf) {
assert(w == _screenW && h == _screenH);
void *ptr = 0;
int pitch = 0;
if (SDL_LockTexture(_widescreenTexture, 0, &ptr, &pitch) == 0) {
assert((pitch & 3) == 0);
uint32_t *src = (uint32_t *)malloc(w * h * sizeof(uint32_t));
uint32_t *tmp = (uint32_t *)malloc(w * h * sizeof(uint32_t));
uint32_t *dst = (uint32_t *)ptr;
if (src && tmp) {
for (int i = 0; i < w * h; ++i) {
src[i] = _rgbPalette[buf[i]];
}
static const int radius = 8;
blur_h(radius, src, w, w, h, _fmt, tmp, w);
blur_v(radius, tmp, w, w, h, _fmt, dst, pitch / sizeof(uint32_t));
}
free(src);
free(tmp);
SDL_UnlockTexture(_widescreenTexture);
}
}
void SystemStub_SDL::copyWidescreenCDi(int w, int h, const uint8_t *buf, const uint8_t *pal) {
uint32_t *rgb = (uint32_t *)malloc(w * h * sizeof(uint32_t));
if (rgb) {
for (int i = 0; i < w * h; ++i) {
const uint8_t *p = pal + buf[i] * 3;
const uint32_t color = SDL_MapRGB(_fmt, p[0], p[1], p[2]);
rgb[i] = color;
}
SDL_Rect r;
r.y = 0;
r.w = w;
r.h = h;
// left border
r.x = 0;
SDL_UpdateTexture(_widescreenTexture, &r, rgb, w * sizeof(uint32_t));
// right border
r.x = _screenW + w;
SDL_UpdateTexture(_widescreenTexture, &r, rgb, w * sizeof(uint32_t));
free(rgb);
}
}
void SystemStub_SDL::clearWidescreen() {
clearTexture(_widescreenTexture, _screenH, _fmt);
}
void SystemStub_SDL::enableWidescreen(bool enable) {
_enableWidescreen = enable;
}
void SystemStub_SDL::fadeScreen() {
_fadeOnUpdateScreen = true;
}
@ -355,16 +526,18 @@ void SystemStub_SDL::updateScreen(int shakeOffset) {
SDL_UpdateTexture(_texture, 0, _screenBuffer, _screenW * sizeof(uint32_t));
}
SDL_RenderClear(_renderer);
if (_widescreen) {
// borders / background screen
SDL_RenderCopy(_renderer, _wideTexture, 0, 0);
if (_widescreenMode != kWidescreenNone) {
if (_enableWidescreen) {
// borders / background screen
SDL_RenderCopy(_renderer, _widescreenTexture, 0, 0);
}
// game screen
SDL_Rect r;
r.y = shakeOffset * _scaleFactor;
SDL_RenderGetLogicalSize(_renderer, &r.w, &r.h);
r.x = (r.w - _texW) / 2;
r.w = _texW;
SDL_RenderCopy(_renderer, _texture, 0, &r);
SDL_RenderCopy(_renderer, _texture, &_texRect, &r);
} else {
if (_fadeOnUpdateScreen) {
SDL_SetRenderDrawBlendMode(_renderer, SDL_BLENDMODE_BLEND);
@ -386,9 +559,13 @@ void SystemStub_SDL::updateScreen(int shakeOffset) {
r.x = 0;
r.y = shakeOffset * _scaleFactor;
SDL_RenderGetLogicalSize(_renderer, &r.w, &r.h);
SDL_RenderCopy(_renderer, _texture, 0, &r);
SDL_RenderCopy(_renderer, _texture, &_texRect, &r);
}
SDL_RenderPresent(_renderer);
_texRect.x = 0;
_texRect.y = 0;
_texRect.w = _texW;
_texRect.h = _texH;
}
void SystemStub_SDL::processEvents() {
@ -408,12 +585,30 @@ void SystemStub_SDL::processEvents() {
}
}
// only used for the protection codes and level passwords
static void setAsciiChar(PlayerInput &pi, const SDL_Keysym *key) {
if (key->sym >= SDLK_0 && key->sym <= SDLK_9) {
pi.lastChar = '0' + key->sym - SDLK_0;
} else if (key->sym >= SDLK_a && key->sym <= SDLK_z) {
pi.lastChar = 'A' + key->sym - SDLK_a;
} else if (key->scancode == SDL_SCANCODE_0) {
pi.lastChar = '0';
} else if (key->scancode >= SDL_SCANCODE_1 && key->scancode <= SDL_SCANCODE_9) {
pi.lastChar = '1' + key->scancode - SDL_SCANCODE_1;
} else if (key->sym == SDLK_SPACE || key->sym == SDLK_KP_SPACE) {
pi.lastChar = ' ';
} else {
pi.lastChar = 0;
}
}
void SystemStub_SDL::processEvent(const SDL_Event &ev, bool &paused) {
switch (ev.type) {
case SDL_QUIT:
_pi.quit = true;
break;
case SDL_WINDOWEVENT:
#if !defined(__amigaos4__)
switch (ev.window.event) {
case SDL_WINDOWEVENT_FOCUS_GAINED:
case SDL_WINDOWEVENT_FOCUS_LOST:
@ -421,6 +616,7 @@ void SystemStub_SDL::processEvent(const SDL_Event &ev, bool &paused) {
SDL_PauseAudio(paused);
break;
}
#endif
break;
case SDL_JOYHATMOTION:
if (_joystick) {
@ -585,8 +781,8 @@ void SystemStub_SDL::processEvent(const SDL_Event &ev, bool &paused) {
break;
case SDLK_s: {
char name[32];
snprintf(name, sizeof(name), "screenshot-%03d.tga", _screenshot);
saveTGA(name, (const uint8_t *)_screenBuffer, _screenW, _screenH);
snprintf(name, sizeof(name), "screenshot-%03d.bmp", _screenshot);
saveBMP(name, (const uint8_t *)_screenBuffer, 0, _screenW, _screenH);
++_screenshot;
debug(DBG_INFO, "Written '%s'", name);
}
@ -613,6 +809,13 @@ void SystemStub_SDL::processEvent(const SDL_Event &ev, bool &paused) {
case SDLK_l:
_pi.load = true;
break;
case SDLK_r:
_pi.rewind = true;
break;
case SDLK_g:
_pi.dbgMask ^= PlayerInput::DF_AUTOZOOM;
debug(DBG_INFO, "Auto zoom %s", (_pi.dbgMask & PlayerInput::DF_AUTOZOOM) ? "enabled" : "disabled");
break;
case SDLK_KP_PLUS:
case SDLK_PAGEUP:
_pi.stateSlot = 1;
@ -624,18 +827,22 @@ void SystemStub_SDL::processEvent(const SDL_Event &ev, bool &paused) {
}
break;
}
_pi.lastChar = ev.key.keysym.sym;
setAsciiChar(_pi, &ev.key.keysym);
switch (ev.key.keysym.sym) {
case SDLK_LEFT:
case SDLK_KP_4:
_pi.dirMask &= ~PlayerInput::DIR_LEFT;
break;
case SDLK_RIGHT:
case SDLK_KP_6:
_pi.dirMask &= ~PlayerInput::DIR_RIGHT;
break;
case SDLK_UP:
case SDLK_KP_8:
_pi.dirMask &= ~PlayerInput::DIR_UP;
break;
case SDLK_DOWN:
case SDLK_KP_2:
_pi.dirMask &= ~PlayerInput::DIR_DOWN;
break;
case SDLK_SPACE:
@ -671,15 +878,19 @@ void SystemStub_SDL::processEvent(const SDL_Event &ev, bool &paused) {
}
switch (ev.key.keysym.sym) {
case SDLK_LEFT:
case SDLK_KP_4:
_pi.dirMask |= PlayerInput::DIR_LEFT;
break;
case SDLK_RIGHT:
case SDLK_KP_6:
_pi.dirMask |= PlayerInput::DIR_RIGHT;
break;
case SDLK_UP:
case SDLK_KP_8:
_pi.dirMask |= PlayerInput::DIR_UP;
break;
case SDLK_DOWN:
case SDLK_KP_2:
_pi.dirMask |= PlayerInput::DIR_DOWN;
break;
case SDLK_BACKSPACE:
@ -719,7 +930,8 @@ uint32_t SystemStub_SDL::getTimeStamp() {
static void mixAudioS16(void *param, uint8_t *buf, int len) {
SystemStub_SDL *stub = (SystemStub_SDL *)param;
memset(buf, 0, len);
stub->_audioCbProc(stub->_audioCbData, (int16_t *)buf, len / 2);
assert((len & 3) == 0);
stub->_audioCbProc(stub->_audioCbData, (int16_t *)buf, len / (sizeof(int16_t) * 2));
}
void SystemStub_SDL::startAudio(AudioCallback callback, void *param) {
@ -727,7 +939,7 @@ void SystemStub_SDL::startAudio(AudioCallback callback, void *param) {
memset(&desired, 0, sizeof(desired));
desired.freq = kAudioHz;
desired.format = AUDIO_S16SYS;
desired.channels = 1;
desired.channels = 2;
desired.samples = 2048;
desired.callback = mixAudioS16;
desired.userdata = this;
@ -756,6 +968,10 @@ void SystemStub_SDL::unlockAudio() {
SDL_UnlockAudio();
}
static bool is16_9(const SDL_DisplayMode *mode) {
return (mode->w / (float)mode->h) >= (16 / 9.f);
}
void SystemStub_SDL::prepareGraphics() {
_texW = _screenW;
_texH = _screenH;
@ -773,6 +989,10 @@ void SystemStub_SDL::prepareGraphics() {
_texH *= _scaleFactor;
break;
}
_texRect.x = 0;
_texRect.y = 0;
_texRect.w = _texW;
_texRect.h = _texH;
int windowW = _screenW * _scaleFactor;
int windowH = _screenH * _scaleFactor;
int flags = 0;
@ -781,7 +1001,17 @@ void SystemStub_SDL::prepareGraphics() {
} else {
flags |= SDL_WINDOW_RESIZABLE;
}
if (_widescreen) {
if (0 /* && _widescreenMode == kWidescreenDefault */) {
SDL_DisplayMode dm;
if (SDL_GetDesktopDisplayMode(0, &dm) == 0 && is16_9(&dm)) {
_widescreenMode = kWidescreenBlur; // default widescreen mode
} else {
_widescreenMode = kWidescreenNone;
}
}
if (_widescreenMode == kWidescreenCDi) {
windowW = (_screenW + kWidescreenBorderCDiW * 2) * _scaleFactor;
} else if (_widescreenMode != kWidescreenNone) {
windowW = windowH * 16 / 9;
}
_window = SDL_CreateWindow(_caption, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, windowW, windowH, flags);
@ -793,11 +1023,17 @@ void SystemStub_SDL::prepareGraphics() {
_renderer = SDL_CreateRenderer(_window, -1, SDL_RENDERER_ACCELERATED);
SDL_RenderSetLogicalSize(_renderer, windowW, windowH);
_texture = SDL_CreateTexture(_renderer, kPixelFormat, SDL_TEXTUREACCESS_STREAMING, _texW, _texH);
if (_widescreen) {
const int w = _screenH * 16 / 9;
const int h = _screenH;
_wideTexture = SDL_CreateTexture(_renderer, kPixelFormat, SDL_TEXTUREACCESS_STREAMING, w, h);
clearTexture(_wideTexture, _screenH, _fmt);
if (_widescreenMode != kWidescreenNone) {
int w = _screenH * 16 / 9;
// in blur mode, the background texture has the same dimensions as the game texture
// SDL stretches the texture to 16:9
if (_widescreenMode == kWidescreenBlur) {
w = _screenW;
} else if (_widescreenMode == kWidescreenCDi) {
w = _screenW + kWidescreenBorderCDiW * 2;
}
_widescreenTexture = SDL_CreateTexture(_renderer, kPixelFormat, SDL_TEXTUREACCESS_STREAMING, w, _screenH);
clearTexture(_widescreenTexture, _screenH, _fmt);
// left and right borders
_wideMargin = (w - _screenW) / 2;
@ -809,9 +1045,9 @@ void SystemStub_SDL::cleanupGraphics() {
SDL_DestroyTexture(_texture);
_texture = 0;
}
if (_wideTexture) {
SDL_DestroyTexture(_wideTexture);
_wideTexture = 0;
if (_widescreenTexture) {
SDL_DestroyTexture(_widescreenTexture);
_widescreenTexture = 0;
}
if (_renderer) {
SDL_DestroyRenderer(_renderer);
@ -824,7 +1060,7 @@ void SystemStub_SDL::cleanupGraphics() {
}
void SystemStub_SDL::changeGraphics(bool fullscreen, int scaleFactor) {
int factor = CLIP(scaleFactor, _scaler->factorMin, _scaler->factorMax);
const int factor = _scaler ? CLIP(scaleFactor, _scaler->factorMin, _scaler->factorMax) : scaleFactor;
if (fullscreen == _fullscreen && factor == _scaleFactor) {
// no change
return;
@ -835,46 +1071,104 @@ void SystemStub_SDL::changeGraphics(bool fullscreen, int scaleFactor) {
prepareGraphics();
}
void SystemStub_SDL::changeScaler(int scaler) {
ScalerParameters scalerParameters = ScalerParameters::defaults();
switch (scaler) {
void SystemStub_SDL::setScaler(const ScalerParameters *parameters) {
static const struct {
const char *name;
int type;
const Scaler *scaler;
} scalers[] = {
{ "point", kScalerTypePoint, 0 },
{ "linear", kScalerTypeLinear, 0 },
{ "scale", kScalerTypeInternal, &_internalScaler },
#ifdef USE_STATIC_SCALER
{ "nearest", kScalerTypeInternal, &scaler_nearest },
{ "tv2x", kScalerTypeInternal, &scaler_tv2x },
{ "xbr", kScalerTypeInternal, &scaler_xbr },
#endif
{ 0, -1, 0 }
};
bool found = false;
for (int i = 0; scalers[i].name; ++i) {
if (strcmp(scalers[i].name, parameters->name) == 0) {
_scalerType = (ScalerType)scalers[i].type;
_scaler = scalers[i].scaler;
found = true;
break;
}
}
if (!found) {
#ifdef _WIN32
static const char *libSuffix = "dll";
#else
static const char *libSuffix = "so";
#endif
char libname[64];
snprintf(libname, sizeof(libname), "scaler_%s.%s", parameters->name, libSuffix);
_scalerSo = SDL_LoadObject(libname);
if (!_scalerSo) {
warning("Scaler '%s' not found, using default", libname);
} else {
static const char *kSoSym = "getScaler";
void *symbol = SDL_LoadFunction(_scalerSo, kSoSym);
if (!symbol) {
warning("Symbol '%s' not found in '%s'", kSoSym, libname);
} else {
typedef const Scaler *(*GetScalerProc)();
const Scaler *scaler = ((GetScalerProc)symbol)();
const int tag = scaler ? scaler->tag : 0;
if (tag != SCALER_TAG) {
warning("Unexpected tag %d for scaler '%s'", tag, libname);
} else {
_scalerType = kScalerTypeExternal;
_scaler = scaler;
}
}
}
}
_scaleFactor = _scaler ? CLIP(parameters->factor, _scaler->factorMin, _scaler->factorMax) : parameters->factor;
}
void SystemStub_SDL::changeScaler(int scalerNum) {
ScalerType type = kScalerTypeInternal;
const Scaler *scaler = 0;
switch (scalerNum) {
case 0:
scalerParameters.type = kScalerTypePoint;
type = kScalerTypePoint;
break;
case 1:
scalerParameters.type = kScalerTypeLinear;
type = kScalerTypeLinear;
break;
case 2:
scalerParameters.type = kScalerTypeInternal;
scalerParameters.scaler = &_internalScaler;
type = kScalerTypeInternal;
scaler = &_internalScaler;
break;
#ifdef USE_STATIC_SCALER
case 3:
scalerParameters.type = kScalerTypeInternal;
scalerParameters.scaler = &scaler_nearest;
type = kScalerTypeInternal;
scaler = &scaler_nearest;
break;
case 4:
scalerParameters.type = kScalerTypeInternal;
scalerParameters.scaler = &scaler_tv2x;
type = kScalerTypeInternal;
scaler = &scaler_tv2x;
break;
case 5:
scalerParameters.type = kScalerTypeInternal;
scalerParameters.scaler = &scaler_xbrz;
type = kScalerTypeInternal;
scaler = &scaler_xbr;
break;
#endif
default:
return;
}
if (_scalerType != scalerParameters.type || scalerParameters.scaler != _scaler) {
_scalerType = scalerParameters.type;
_scaler = scalerParameters.scaler;
const int scaleFactor = CLIP(_scaleFactor, _scaler->factorMin, _scaler->factorMax);
// only recreate the window if dimensions actually changed
if (scaleFactor != _scaleFactor) {
cleanupGraphics();
_scaleFactor = scaleFactor;
prepareGraphics();
if (_scalerType != type || scaler != _scaler) {
_scalerType = type;
_scaler = scaler;
if (_scalerType == kScalerTypeInternal || _scalerType == kScalerTypeExternal) {
_scaleFactor = CLIP(_scaleFactor, _scaler->factorMin, _scaler->factorMax);
} else {
_scaleFactor = 1;
}
cleanupGraphics();
prepareGraphics();
}
}

View File

@ -1,7 +1,7 @@
/*
* REminiscence - Flashback interpreter
* Copyright (C) 2005-2018 Gregory Montoir (cyx@users.sourceforge.net)
* Copyright (C) 2005-2019 Gregory Montoir (cyx@users.sourceforge.net)
*/
#include "unpack.h"
@ -15,56 +15,50 @@ struct UnpackCtx {
const uint8_t *src;
};
static bool nextBit(UnpackCtx *uc) {
bool carry = (uc->bits & 1) != 0;
static int nextBit(UnpackCtx *uc) {
int bit = (uc->bits & 1);
uc->bits >>= 1;
if (uc->bits == 0) {
uc->bits = READ_BE_UINT32(uc->src); uc->src -= 4;
uc->crc ^= uc->bits;
carry = (uc->bits & 1) != 0;
uc->bits = (1 << 31) | (uc->bits >> 1);
if (uc->bits == 0) { // getnextlwd
const uint32_t bits = READ_BE_UINT32(uc->src); uc->src -= 4;
uc->crc ^= bits;
bit = (bits & 1);
uc->bits = (1 << 31) | (bits >> 1);
}
return carry;
return bit;
}
static int getBits(UnpackCtx *uc, int count) {
int bits = 0;
template<int count>
static uint32_t getBits(UnpackCtx *uc) { // rdd1bits
uint32_t bits = 0;
for (int i = 0; i < count; ++i) {
bits <<= 1;
if (nextBit(uc)) {
bits |= 1;
}
bits = (bits << 1) | nextBit(uc);
}
return bits;
}
static void copyLiteral(UnpackCtx *uc, int bitsCount, int len) {
int count = getBits(uc, bitsCount) + len + 1;
uc->size -= count;
static void copyLiteral(UnpackCtx *uc, int len) { // getd3chr
uc->size -= len;
if (uc->size < 0) {
count += uc->size;
len += uc->size;
uc->size = 0;
}
for (int i = 0; i < count; ++i) {
*(uc->dst - i) = (uint8_t)getBits(uc, 8);
for (int i = 0; i < len; ++i, --uc->dst) {
*(uc->dst) = (uint8_t)getBits<8>(uc);
}
uc->dst -= count;
}
static void copyReference(UnpackCtx *uc, int bitsCount, int count) {
uc->size -= count;
static void copyReference(UnpackCtx *uc, int len, int offset) { // copyd3bytes
uc->size -= len;
if (uc->size < 0) {
count += uc->size;
len += uc->size;
uc->size = 0;
}
const int offset = getBits(uc, bitsCount);
for (int i = 0; i < count; ++i) {
*(uc->dst - i) = *(uc->dst - i + offset);
for (int i = 0; i < len; ++i, --uc->dst) {
*(uc->dst) = *(uc->dst + offset);
}
uc->dst -= count;
}
bool delphine_unpack(uint8_t *dst, int dstSize, const uint8_t *src, int srcSize) {
bool bytekiller_unpack(uint8_t *dst, int dstSize, const uint8_t *src, int srcSize) {
UnpackCtx uc;
uc.src = src + srcSize - 4;
uc.size = READ_BE_UINT32(uc.src); uc.src -= 4;
@ -79,24 +73,26 @@ bool delphine_unpack(uint8_t *dst, int dstSize, const uint8_t *src, int srcSize)
do {
if (!nextBit(&uc)) {
if (!nextBit(&uc)) {
copyLiteral(&uc, 3, 0);
copyLiteral(&uc, getBits<3>(&uc) + 1);
} else {
copyReference(&uc, 8, 2);
copyReference(&uc, 2, getBits<8>(&uc));
}
} else {
const int code = getBits(&uc, 2);
const int code = getBits<2>(&uc);
switch (code) {
case 3:
copyLiteral(&uc, 8, 8);
copyLiteral(&uc, getBits<8>(&uc) + 9);
break;
case 2:
copyReference(&uc, 12, getBits(&uc, 8) + 1);
case 2: {
const int len = getBits<8>(&uc) + 1;
copyReference(&uc, len, getBits<12>(&uc));
}
break;
case 1:
copyReference(&uc, 10, 4);
copyReference(&uc, 4, getBits<10>(&uc));
break;
case 0:
copyReference(&uc, 9, 3);
copyReference(&uc, 3, getBits<9>(&uc));
break;
}
}

View File

@ -1,7 +1,7 @@
/*
* REminiscence - Flashback interpreter
* Copyright (C) 2005-2018 Gregory Montoir (cyx@users.sourceforge.net)
* Copyright (C) 2005-2019 Gregory Montoir (cyx@users.sourceforge.net)
*/
#ifndef UNPACK_H__
@ -9,6 +9,6 @@
#include "intern.h"
extern bool delphine_unpack(uint8_t *dst, int dstSize, const uint8_t *src, int srcSize);
extern bool bytekiller_unpack(uint8_t *dst, int dstSize, const uint8_t *src, int srcSize);
#endif // UNPACK_H__

View File

@ -1,7 +1,7 @@
/*
* REminiscence - Flashback interpreter
* Copyright (C) 2005-2018 Gregory Montoir (cyx@users.sourceforge.net)
* Copyright (C) 2005-2019 Gregory Montoir (cyx@users.sourceforge.net)
*/
#ifdef _WIN32
@ -18,6 +18,7 @@
uint16_t g_debugMask;
#ifndef NDEBUG
void debug(uint16_t cm, const char *msg, ...) {
char buf[1024];
if (cm & g_debugMask) {
@ -32,6 +33,7 @@ void debug(uint16_t cm, const char *msg, ...) {
#endif
}
}
#endif
void error(const char *msg, ...) {
char buf[1024];

16
util.h
View File

@ -1,7 +1,7 @@
/*
* REminiscence - Flashback interpreter
* Copyright (C) 2005-2018 Gregory Montoir (cyx@users.sourceforge.net)
* Copyright (C) 2005-2019 Gregory Montoir (cyx@users.sourceforge.net)
*/
#ifndef UTIL_H__
@ -23,13 +23,19 @@ enum {
DBG_MOD = 1 << 10,
DBG_SFX = 1 << 11,
DBG_FILE = 1 << 12,
DBG_DEMO = 1 << 13
DBG_DEMO = 1 << 13,
DBG_PRF = 1 << 14,
DBG_MIDI = 1 << 15
};
extern uint16_t g_debugMask;
extern void debug(uint16_t cm, const char *msg, ...); // __attribute__((__format__(__printf__, 2, 3)))
extern void error(const char *msg, ...); // __attribute__((__format__(__printf__, 1, 2)))
extern void warning(const char *msg, ...); // __attribute__((__format__(__printf__, 1, 2)))
extern void debug(uint16_t cm, const char *msg, ...);
extern void error(const char *msg, ...);
extern void warning(const char *msg, ...);
#ifdef NDEBUG
#define debug(x, ...)
#endif
#endif // UTIL_H__

146
video.cpp
View File

@ -1,7 +1,7 @@
/*
* REminiscence - Flashback interpreter
* Copyright (C) 2005-2018 Gregory Montoir (cyx@users.sourceforge.net)
* Copyright (C) 2005-2019 Gregory Montoir (cyx@users.sourceforge.net)
*/
#include "decode_mac.h"
@ -11,8 +11,8 @@
#include "util.h"
#include "video.h"
Video::Video(Resource *res, SystemStub *stub)
: _res(res), _stub(stub) {
Video::Video(Resource *res, SystemStub *stub, WidescreenMode widescreenMode)
: _res(res), _stub(stub), _widescreenMode(widescreenMode) {
_layerScale = (_res->_type == kResourceTypeMac) ? 2 : 1; // Macintosh version is 512x448
_w = GAMESCREEN_W * _layerScale;
_h = GAMESCREEN_H * _layerScale;
@ -115,6 +115,19 @@ void Video::updateScreen() {
}
}
void Video::updateWidescreen() {
if (_stub->hasWidescreen()) {
if (_widescreenMode == kWidescreenMirrorRoom) {
_stub->copyWidescreenMirror(_w, _h, _backLayer);
} else if (_widescreenMode == kWidescreenBlur) {
_stub->copyWidescreenBlur(_w, _h, _backLayer);
} else if (_widescreenMode == kWidescreenCDi) {
} else {
_stub->clearWidescreen();
}
}
}
void Video::fullRefresh() {
debug(DBG_VIDEO, "Video::fullRefresh()");
_fullRefresh = true;
@ -154,20 +167,26 @@ void Video::setPaletteColorBE(int num, int offset) {
void Video::setPaletteSlotBE(int palSlot, int palNum) {
debug(DBG_VIDEO, "Video::setPaletteSlotBE()");
const uint8_t *p = _res->_pal + palNum * 0x20;
const uint8_t *p = _res->_pal + palNum * 32;
for (int i = 0; i < 16; ++i) {
const int color = READ_BE_UINT16(p); p += 2;
Color c = AMIGA_convertColor(color, true);
_stub->setPaletteEntry(palSlot * 0x10 + i, &c);
_stub->setPaletteEntry(palSlot * 16 + 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;
const uint16_t color = READ_LE_UINT16(palData + i * 2);
Color c = AMIGA_convertColor(color);
_stub->setPaletteEntry(palSlot * 0x10 + i, &c);
_stub->setPaletteEntry(palSlot * 16 + i, &c);
}
if (palSlot == 4 && g_options.use_white_tshirt) {
const Color color12 = AMIGA_convertColor(0x888);
const Color color13 = AMIGA_convertColor((palData == _conradPal2) ? 0x888 : 0xCCC);
_stub->setPaletteEntry(palSlot * 16 + 12, &color12);
_stub->setPaletteEntry(palSlot * 16 + 13, &color13);
}
}
@ -315,6 +334,33 @@ void Video::PC_decodeSpc(const uint8_t *src, int w, int h, uint8_t *dst) {
}
}
void Video::PC_decodeSpm(const uint8_t *dataPtr, uint8_t *dst) {
const int len = 2 * READ_BE_UINT16(dataPtr); dataPtr += 2;
uint8_t *dst2 = dst + 1024;
for (int i = 0; i < len; ++i) {
*dst2++ = dataPtr[i] >> 4;
*dst2++ = dataPtr[i] & 15;
}
const uint8_t *src = dst + 1024;
const uint8_t *end = src + len;
do {
const uint8_t code = *src++;
if (code == 0xF) {
uint8_t color = *src++;
int count = *src++;
if (color == 0xF) {
count = (count << 4) | *src++;
color = *src++;
}
count += 4;
memset(dst, color, count);
dst += count;
} else {
*dst++ = code;
}
} while (src < end);
}
static void AMIGA_planar16(uint8_t *dst, int w, int h, int depth, const uint8_t *src) {
const int pitch = w * 16;
const int planarSize = w * 2 * h;
@ -453,14 +499,14 @@ static void PC_drawTileMask(uint8_t *dst, int x0, int y0, int w, int h, uint8_t
}
}
static void decodeSgd(uint8_t *dst, const uint8_t *src, const uint8_t *data, const bool isAmiga) {
static void decodeSgd(uint8_t *dst, const uint8_t *src, const uint8_t *data, const bool isAmiga, int level, int room) {
int num = -1;
uint8_t buf[256 * 32];
int count = READ_BE_UINT16(src) - 1; src += 2;
do {
int d2 = READ_BE_UINT16(src); src += 2;
const int d0 = (int16_t)READ_BE_UINT16(src); src += 2;
const int d1 = (int16_t)READ_BE_UINT16(src); src += 2;
int x_pos = (int16_t)READ_BE_UINT16(src); src += 2;
int y_pos = (int16_t)READ_BE_UINT16(src); src += 2;
if (d2 != 0xFFFF) {
d2 &= ~(1 << 15);
const int32_t offset = READ_BE_UINT32(data + d2 * 4);
@ -481,13 +527,16 @@ static void decodeSgd(uint8_t *dst, const uint8_t *src, const uint8_t *data, con
}
}
}
if (level == 0 && room == 26 && d2 == 38 && y_pos == 176) {
y_pos += 8;
}
const int w = (buf[0] + 1) >> 1;
const int h = buf[1] + 1;
const int planarSize = READ_BE_UINT16(buf + 2);
if (isAmiga) {
AMIGA_planar_mask(dst, d0, d1, w, h, buf + 4, buf + 4 + planarSize, planarSize);
AMIGA_planar_mask(dst, x_pos, y_pos, w, h, buf + 4, buf + 4 + planarSize, planarSize);
} else {
PC_drawTileMask(dst, d0, d1, w, h, buf + 4, buf + 4 + planarSize, planarSize);
PC_drawTileMask(dst, x_pos, y_pos, w, h, buf + 4, buf + 4 + planarSize, planarSize);
}
} while (--count >= 0);
}
@ -603,7 +652,7 @@ static void decodeLevHelper(uint8_t *dst, const uint8_t *src, int offset10, int
const int d3 = isPC ? READ_LE_UINT16(a0) : READ_BE_UINT16(a0); a0 += 2;
int d0 = d3 & 0x7FF;
if (d0 != 0 && sgdBuf) {
d0 -= 896;
d0 -= 0x380;
}
if (d0 != 0) {
const uint8_t *a2 = a5 + d0 * 32;
@ -614,6 +663,9 @@ static void decodeLevHelper(uint8_t *dst, const uint8_t *src, int offset10, int
mask = 0x10;
} else if ((d3 & 0x8000) != 0) {
mask = 0x80 + ((d3 >> 6) & 0x10);
if (d3 & 0x4000) {
mask = 0x90;
}
}
if (isPC) {
PC_drawTile(dst + y * 256 + x, a2, mask, xflip, yflip, 0);
@ -629,7 +681,7 @@ static void decodeLevHelper(uint8_t *dst, const uint8_t *src, int offset10, int
void Video::AMIGA_decodeLev(int level, int room) {
uint8_t *tmp = _res->_scratchBuffer;
const int offset = READ_BE_UINT32(_res->_lev + room * 4);
if (!delphine_unpack(tmp, Resource::kScratchBufferSize, _res->_lev, offset)) {
if (!bytekiller_unpack(tmp, Resource::kScratchBufferSize, _res->_lev, offset)) {
warning("Bad CRC for level %d room %d", level, room);
return;
}
@ -673,7 +725,7 @@ void Video::AMIGA_decodeLev(int level, int room) {
memset(_frontLayer, 0, _layerSize);
if (tmp[1] != 0) {
assert(_res->_sgd);
decodeSgd(_frontLayer, tmp + offset10, _res->_sgd, _res->isAmiga());
decodeSgd(_frontLayer, tmp + offset10, _res->_sgd, _res->isAmiga(), level, room);
offset10 = 0;
}
decodeLevHelper(_frontLayer, tmp, offset10, offset12, buf, tmp[1] != 0, _res->isDOS());
@ -912,7 +964,7 @@ void Video::MAC_drawStringChar(uint8_t *dst, int pitch, int x, int y, const uint
buf.h = _h;
buf.x = x * _layerScale;
buf.y = y * _layerScale;
buf.setPixel = Video::MAC_drawBufferFont;
buf.setPixel = Video::MAC_setPixelFont;
_MAC_fontFrontColor = color;
_MAC_fontShadowColor = _charShadowColor;
assert(chr >= 32);
@ -963,7 +1015,7 @@ void Video::MAC_decodeMap(int level, int room) {
buf.ptr = _frontLayer;
buf.w = buf.pitch = _w;
buf.h = _h;
buf.setPixel = Video::MAC_drawBuffer;
buf.setPixel = Video::MAC_setPixel;
_res->MAC_loadLevelRoom(level, room, &buf);
memcpy(_backLayer, _frontLayer, _layerSize);
Color roomPalette[256];
@ -979,45 +1031,27 @@ void Video::MAC_decodeMap(int level, int room) {
}
}
void Video::MAC_drawBuffer(DecodeBuffer *buf, int src_x, int src_y, int src_w, int src_h, uint8_t color) {
const int y = buf->y + src_y;
if (y >= 0 && y < buf->h) {
const int x = buf->xflip ? (buf->x + (src_w - 1 - src_x)) : (buf->x + src_x);
if (x >= 0 && x < buf->w) {
const int offset = y * buf->pitch + x;
buf->ptr[offset] = color;
}
void Video::MAC_setPixel(DecodeBuffer *buf, int x, int y, uint8_t color) {
const int offset = y * buf->pitch + x;
buf->ptr[offset] = color;
}
void Video::MAC_setPixelMask(DecodeBuffer *buf, int x, int y, uint8_t color) {
const int offset = y * buf->pitch + x;
if ((buf->ptr[offset] & 0x80) == 0) {
buf->ptr[offset] = color;
}
}
void Video::MAC_drawBufferMask(DecodeBuffer *buf, int src_x, int src_y, int src_w, int src_h, uint8_t color) {
const int y = buf->y + src_y;
if (y >= 0 && y < buf->h) {
const int x = buf->xflip ? (buf->x + (src_w - 1 - src_x)) : (buf->x + src_x);
if (x >= 0 && x < buf->w) {
const int offset = y * buf->pitch + x;
if ((buf->ptr[offset] & 0x80) == 0) {
buf->ptr[offset] = color;
}
}
}
}
void Video::MAC_drawBufferFont(DecodeBuffer *buf, int src_x, int src_y, int src_w, int src_h, uint8_t color) {
const int y = buf->y + src_y;
if (y >= 0 && y < buf->h) {
const int x = buf->x + src_x;
if (x >= 0 && x < buf->w) {
const int offset = y * buf->pitch + x;
switch (color) {
case 0xC0:
buf->ptr[offset] = _MAC_fontShadowColor;
break;
case 0xC1:
buf->ptr[offset] = _MAC_fontFrontColor;
break;
}
}
void Video::MAC_setPixelFont(DecodeBuffer *buf, int x, int y, uint8_t color) {
const int offset = y * buf->pitch + x;
switch (color) {
case 0xC0:
buf->ptr[offset] = _MAC_fontShadowColor;
break;
case 0xC1:
buf->ptr[offset] = _MAC_fontFrontColor;
break;
}
}
@ -1031,9 +1065,9 @@ void Video::fillRect(int x, int y, int w, int h, uint8_t color) {
static void fixOffsetDecodeBuffer(DecodeBuffer *buf, const uint8_t *dataPtr) {
if (buf->xflip) {
buf->x += (int16_t)READ_BE_UINT16(dataPtr + 4) - READ_BE_UINT16(dataPtr) - 1;
buf->x += (int16_t)READ_BE_UINT16(dataPtr + 4) - READ_BE_UINT16(dataPtr) - 1;
} else {
buf->x -= (int16_t)READ_BE_UINT16(dataPtr + 4);
buf->x -= (int16_t)READ_BE_UINT16(dataPtr + 4);
}
buf->y -= (int16_t)READ_BE_UINT16(dataPtr + 6);
}
@ -1049,7 +1083,7 @@ void Video::MAC_drawSprite(int x, int y, const uint8_t *data, int frame, bool xf
buf.h = _h;
buf.x = x * _layerScale;
buf.y = y * _layerScale;
buf.setPixel = eraseBackground ? MAC_drawBuffer : MAC_drawBufferMask;
buf.setPixel = eraseBackground ? MAC_setPixel : MAC_setPixelMask;
fixOffsetDecodeBuffer(&buf, dataPtr);
_res->MAC_decodeImageData(data, frame, &buf);
markBlockAsDirty(buf.x, buf.y, READ_BE_UINT16(dataPtr), READ_BE_UINT16(dataPtr + 2), 1);

13
video.h
View File

@ -1,7 +1,7 @@
/*
* REminiscence - Flashback interpreter
* Copyright (C) 2005-2018 Gregory Montoir (cyx@users.sourceforge.net)
* Copyright (C) 2005-2019 Gregory Montoir (cyx@users.sourceforge.net)
*/
#ifndef VIDEO_H__
@ -32,6 +32,7 @@ struct Video {
Resource *_res;
SystemStub *_stub;
WidescreenMode _widescreenMode;
int _w, _h;
int _layerSize;
@ -50,11 +51,12 @@ struct Video {
uint8_t _shakeOffset;
drawCharFunc _drawChar;
Video(Resource *res, SystemStub *stub);
Video(Resource *res, SystemStub *stub, WidescreenMode widescreenMode);
~Video();
void markBlockAsDirty(int16_t x, int16_t y, uint16_t w, uint16_t h, int scale);
void updateScreen();
void updateWidescreen();
void fullRefresh();
void fadeOut();
void fadeOutPalette();
@ -68,6 +70,7 @@ struct Video {
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 PC_decodeSpm(const uint8_t *dataPtr, uint8_t *dstPtr);
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);
@ -87,9 +90,9 @@ struct Video {
void drawStringLen(const char *str, int len, int x, int y, uint8_t color);
static Color AMIGA_convertColor(const uint16_t color, bool bgr = false);
void MAC_decodeMap(int level, int room);
static void MAC_drawBuffer(DecodeBuffer *buf, int src_x, int src_y, int src_w, int src_h, uint8_t color);
static void MAC_drawBufferMask(DecodeBuffer *buf, int src_x, int src_y, int src_w, int src_h, uint8_t color);
static void MAC_drawBufferFont(DecodeBuffer *buf, int src_x, int src_y, int src_w, int src_h, uint8_t color);
static void MAC_setPixel(DecodeBuffer *buf, int x, int y, uint8_t color);
static void MAC_setPixelMask(DecodeBuffer *buf, int x, int y, uint8_t color);
static void MAC_setPixelFont(DecodeBuffer *buf, int x, int y, uint8_t color);
void fillRect(int x, int y, int w, int h, uint8_t color);
void MAC_drawSprite(int x, int y, const uint8_t *data, int frame, bool xflip, bool eraseBackground);
};