Import 0.5.0

This commit is contained in:
Gregory Montoir 2023-03-31 21:23:51 +08:00
parent 419cf91dfe
commit 1f86fdea2d
29 changed files with 1426 additions and 81 deletions

View File

@ -1,9 +1,13 @@
* 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 repeating sounds volume
- fixed volume of repeating sounds
* release 0.4.8
- added detection for DOS version with .ABA files

View File

@ -6,18 +6,27 @@ MODPLUG_LIBS := -lmodplug
TREMOR_LIBS := #-lvorbisidec -logg
ZLIB_LIBS := -lz
CXXFLAGS += -Wall -Wpedantic -MMD $(SDL_CFLAGS) -DUSE_MODPLUG -DUSE_STB_VORBIS -DUSE_ZLIB
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 mixer.cpp mod_player.cpp ogg_player.cpp piege.cpp protection.cpp resource.cpp resource_aba.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
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) $(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)

View File

@ -1,6 +1,6 @@
REminiscence README
Release version: 0.4.9
Release version: 0.5.0
-------------------------------------------------------------------------------
@ -21,43 +21,48 @@ release. Support for Amiga and Macintosh is still experimental.
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 (adjacent,mirror,blur,none)
--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_xbr.dll, you can pass
'--scaler xbr@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)
In-game keys:
Arrow Keys move Conrad
Enter use the current inventory object

View File

@ -361,7 +361,7 @@ int Game::col_detectHitCallbackHelper(LivePGE *pge, int16_t msgNum) {
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) {
while (pge->obj_type == obj->type && on->num_objects > i) {
if (obj->opcode2 == 0x6B) { // pge_isToggleable
if (obj->opcode_arg2 == 0) {
if (msgNum == 1 || msgNum == 2) return 0xFFFF;

View File

@ -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);
@ -343,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);
}

1
file.h
View File

@ -25,6 +25,7 @@ struct File {
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();

View File

@ -9,13 +9,14 @@
#include "file.h"
#include "fs.h"
#include "game.h"
#include "screenshot.h"
#include "seq_player.h"
#include "systemstub.h"
#include "util.h"
Game::Game(SystemStub *stub, FileSystem *fs, const char *savePath, int level, ResourceType ver, Language lang, WidescreenMode widescreenMode, bool autoSave, uint32_t cheats)
Game::Game(SystemStub *stub, FileSystem *fs, const char *savePath, int level, ResourceType ver, Language lang, WidescreenMode widescreenMode, bool autoSave, int midiDriver, uint32_t cheats)
: _cut(&_res, stub, &_vid), _menu(&_res, stub, &_vid),
_mix(fs, stub), _res(fs, ver, lang), _seq(stub, &_mix), _vid(&_res, stub, widescreenMode),
_mix(fs, stub, midiDriver), _res(fs, ver, lang), _seq(stub, &_mix), _vid(&_res, stub, widescreenMode),
_stub(stub), _fs(fs), _savePath(savePath) {
_stateSlot = 1;
_inp_demPos = 0;
@ -1372,11 +1373,11 @@ void Game::drawObjectFrame(const uint8_t *bankDataPtr, const uint8_t *dataPtr, i
void Game::drawCharacter(const uint8_t *dataPtr, int16_t pos_x, int16_t pos_y, uint8_t a, uint8_t b, uint8_t flags) {
debug(DBG_GAME, "Game::drawCharacter(%p, %d, %d, 0x%X, 0x%X, 0x%X)", dataPtr, pos_x, pos_y, a, b, flags);
bool var16 = false; // sprite_mirror_y
bool sprite_mirror_y = false;
if (b & 0x40) {
b &= 0xBF;
b &= ~0x40;
SWAP(a, b);
var16 = true;
sprite_mirror_y = true;
}
uint16_t sprite_h = a;
uint16_t sprite_w = b;
@ -1392,7 +1393,7 @@ void Game::drawCharacter(const uint8_t *dataPtr, int16_t pos_x, int16_t pos_y, u
sprite_clipped_w = 256 - pos_x;
if (flags & 2) {
var14 = true;
if (var16) {
if (sprite_mirror_y) {
src += (sprite_w - 1) * sprite_h;
} else {
src += sprite_w - 1;
@ -1402,7 +1403,7 @@ void Game::drawCharacter(const uint8_t *dataPtr, int16_t pos_x, int16_t pos_y, u
} else {
sprite_clipped_w = pos_x + sprite_w;
if (!(flags & 2)) {
if (var16) {
if (sprite_mirror_y) {
src -= sprite_h * pos_x;
pos_x = 0;
} else {
@ -1411,7 +1412,7 @@ void Game::drawCharacter(const uint8_t *dataPtr, int16_t pos_x, int16_t pos_y, u
}
} else {
var14 = true;
if (var16) {
if (sprite_mirror_y) {
src += sprite_h * (pos_x + sprite_w - 1);
pos_x = 0;
} else {
@ -1434,7 +1435,7 @@ void Game::drawCharacter(const uint8_t *dataPtr, int16_t pos_x, int16_t pos_y, u
}
} else {
sprite_clipped_h = sprite_h + pos_y;
if (var16) {
if (sprite_mirror_y) {
src -= pos_y;
} else {
src -= sprite_w * pos_y;
@ -1446,7 +1447,7 @@ void Game::drawCharacter(const uint8_t *dataPtr, int16_t pos_x, int16_t pos_y, u
}
if (!var14 && (flags & 2)) {
if (var16) {
if (sprite_mirror_y) {
src += sprite_h * (sprite_w - 1);
} else {
src += sprite_w - 1;
@ -1459,13 +1460,13 @@ void Game::drawCharacter(const uint8_t *dataPtr, int16_t pos_x, int16_t pos_y, u
debug(DBG_GAME, "dst_offset=0x%X src_offset=%ld", dst_offset, src - dataPtr);
if (!(flags & 2)) {
if (var16) {
if (sprite_mirror_y) {
_vid.drawSpriteSub5(src, _vid._frontLayer + dst_offset, sprite_h, sprite_clipped_h, sprite_clipped_w, sprite_col_mask);
} else {
_vid.drawSpriteSub3(src, _vid._frontLayer + dst_offset, sprite_w, sprite_clipped_h, sprite_clipped_w, sprite_col_mask);
}
} else {
if (var16) {
if (sprite_mirror_y) {
_vid.drawSpriteSub6(src, _vid._frontLayer + dst_offset, sprite_h, sprite_clipped_h, sprite_clipped_w, sprite_col_mask);
} else {
_vid.drawSpriteSub4(src, _vid._frontLayer + dst_offset, sprite_w, sprite_clipped_h, sprite_clipped_w, sprite_col_mask);
@ -1536,7 +1537,7 @@ bool Game::hasLevelMap(int level, int room) const {
if (_res._map) {
return READ_LE_UINT32(_res._map + room * 6) != 0;
} else if (_res._lev) {
return READ_BE_UINT32(_res._lev + room * 4) != 0;
return READ_BE_UINT32(_res._lev + room * 4) > 0x100;
}
return false;
}
@ -1702,6 +1703,19 @@ void Game::loadLevelData() {
_res.MAC_loadLevelData(_currentLevel);
break;
}
if (0) {
for (int i = 0; i < 64; ++i) {
if (hasLevelMap(_currentLevel, i)) {
_currentRoom = i;
loadLevelMap();
uint8_t palette[256 * 3];
_stub->getPalette(palette, 256);
char name[64];
snprintf(name, sizeof(name), "DUMP/level%d_room%02d.bmp", _currentLevel, i);
saveBMP(name, _vid._backLayer, palette, _vid._w, _vid._h);
}
}
}
_cut._id = lvl->cutscene_id;
@ -1747,6 +1761,22 @@ void Game::loadLevelData() {
_validSaveState = false;
_mix.playMusic(Mixer::MUSIC_TRACK + lvl->track);
if (_widescreenMode == kWidescreenCDi) {
char name[16];
snprintf(name, sizeof(name), "flashp%d.bob", _currentLevel + 1);
File f;
if (f.open(name, "rb", _fs)) {
const int w = f.readUint16LE();
const int h = f.readUint16LE();
if (w != kWidescreenBorderCDiW || h != kWidescreenBorderCDiH) {
warning("Unsupported CDi border w:%d h:%d", w, h);
} else {
f.read(_res._scratchBuffer, 256 * 3 + w * h);
_stub->copyWidescreenCDi(w, h, _res._scratchBuffer + 256 * 3, _res._scratchBuffer);
}
}
}
}
void Game::drawIcon(uint8_t iconNum, int16_t x, int16_t y, uint8_t colMask) {

2
game.h
View File

@ -109,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, uint32_t cheats);
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();

View File

@ -118,6 +118,12 @@ enum WidescreenMode {
kWidescreenAdjacentRooms,
kWidescreenMirrorRoom,
kWidescreenBlur,
kWidescreenCDi,
};
enum {
kWidescreenBorderCDiW = 52,
kWidescreenBorderCDiH = 224
};
struct Options {
@ -130,6 +136,7 @@ struct Options {
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;
@ -230,9 +237,8 @@ struct Object {
};
struct ObjectNode {
uint16_t last_obj_number;
Object *objects;
uint16_t num_objects;
Object *objects;
};
struct ObjectOpcodeArgs {

View File

@ -26,6 +26,7 @@ static const char *USAGE =
" --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) {
@ -95,6 +96,7 @@ static void initOptions() {
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;
@ -117,6 +119,7 @@ static void initOptions() {
{ "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 },
@ -179,6 +182,7 @@ static WidescreenMode parseWidescreen(const char *mode) {
{ "adjacent", kWidescreenAdjacentRooms },
{ "mirror", kWidescreenMirrorRoom },
{ "blur", kWidescreenBlur },
{ "cdi", kWidescreenCDi },
{ 0, kWidescreenNone },
};
for (int i = 0; modes[i].name; ++i) {
@ -200,6 +204,7 @@ int main(int argc, char *argv[]) {
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;
@ -218,6 +223,7 @@ int main(int argc, char *argv[]) {
{ "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;
@ -271,6 +277,23 @@ int main(int argc, char *argv[]) {
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 }
};
for (int i = 0; drivers[i].str; ++i) {
if (strcasecmp(drivers[i].str, optarg) == 0) {
midiDriver = drivers[i].mode;
break;
}
}
}
break;
default:
printf(USAGE, argv[0]);
return 0;
@ -286,7 +309,7 @@ int main(int argc, char *argv[]) {
}
const Language language = (forcedLanguage == -1) ? detectLanguage(&fs) : (Language)forcedLanguage;
SystemStub *stub = SystemStub_SDL_create();
Game *g = new Game(stub, &fs, savePath, levelNum, (ResourceType)version, language, widescreen, autoSave, cheats);
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;

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

90
midi_parser.cpp Normal file
View File

@ -0,0 +1,90 @@
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include "file.h"
#include "midi_parser.h"
#include "util.h"
static uint32_t readVLQ(File *f) {
uint32_t value = 0;
for (int i = 0; i < 4; ++i) {
const uint8_t b = f->readByte();
value = (value << 7) | (b & 0x7F);
if ((b & 0x80) == 0) {
break;
}
}
return value;
}
static void loadTrk(File *f, int len, MidiTrack &track) {
const uint32_t end = f->tell() + len;
while (f->tell() < end) {
MidiEvent ev;
ev.timestamp = readVLQ(f);
ev.command = f->readByte();
switch (ev.command & 0xF0) {
case MIDI_COMMAND_NOTE_OFF:
case MIDI_COMMAND_NOTE_ON:
ev.param1 = f->readByte();
ev.param2 = f->readByte();
//fprintf(stdout, "\t Note %s: note %d vel %d timestamp %d\n", (((ev.command & 0xF0) == MIDI_COMMAND_NOTE_OFF) ? "Off" : "On"), ev.param1, ev.param2, ev.timestamp);
break;
case MIDI_COMMAND_PROGRAM_CHANGE:
ev.param1 = f->readByte();
//fprintf(stdout, "\t Program %d timestamp %d\n", ev.param1, ev.timestamp);
break;
case MIDI_COMMAND_CHANNEL_PRESSURE:
ev.param1 = f->readByte();
//fprintf(stdout, "\t Channel pressure %d timestamp %d\n", ev.param1, ev.timestamp);
break;
case MIDI_COMMAND_PITCH_BEND:
ev.param1 = f->readByte();
ev.param2 = f->readByte();
//fprintf(stdout, "\t Pitch Bend %d,%d\n", ev.param1, ev.param2);
break;
case MIDI_COMMAND_SYSEX:
if (ev.command == 0xFF) {
f->readByte(); /* type */
const int len = readVLQ(f);
f->seek(f->tell() + len);
//fprintf(stdout, "\t SysEx timestamp %d\n", ev.timestamp);
assert(ev.timestamp == 0);
continue;
}
/* fall-through */
default:
warning("Unhandled MIDI command 0x%x", ev.command);
break;
}
track.events.push(ev);
}
}
void MidiParser::loadMid(File *f) {
for (int i = 0; i < MAX_TRACKS; ++i) {
_tracks[i].events = std::queue<MidiEvent>();
}
_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();
//fprintf(stdout, "MThd type %d tracks %d ppqn %d len %d\n", 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();
//fprintf(stdout, "Track #%d len %d\n", i, len);
loadTrk(f, len, _tracks[i]);
//fprintf(stdout, "Track #%d events %ld\n", i, track.events.size());
}
_tracksCount = tracksCount;
}

37
midi_parser.h Normal file
View File

@ -0,0 +1,37 @@
#ifndef MIDI_PARSER_H__
#define MIDI_PARSER_H__
#include <queue>
#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 */
struct MidiEvent {
uint32_t timestamp;
uint8_t command;
uint8_t param1;
uint8_t param2;
};
struct MidiTrack {
std::queue<MidiEvent> events;
};
struct File;
#define MAX_TRACKS 16
struct MidiParser {
void loadMid(File *f);
int _tracksCount;
MidiTrack _tracks[MAX_TRACKS];
};
#endif /* MIDI_PARSER_H__ */

View File

@ -8,14 +8,16 @@
#include "systemstub.h"
#include "util.h"
Mixer::Mixer(FileSystem *fs, SystemStub *stub)
: _stub(stub), _musicType(MT_NONE), _cpc(this, fs), _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);
}
@ -120,6 +122,14 @@ void Mixer::playMusic(int num) {
_mod.play(num);
if (_mod._playing) {
_musicType = MT_MOD;
return;
}
if (g_options.use_prf_music) {
_prf.play(num);
if (_prf._playing) {
_musicType = MT_PRF;
return;
}
}
}
}
@ -135,6 +145,9 @@ void Mixer::stopMusic() {
case MT_OGG:
_ogg.pauseTrack();
break;
case MT_PRF:
_prf.stop();
break;
case MT_SFX:
_sfx.stop();
break;

View File

@ -11,6 +11,7 @@
#include "cpc_player.h"
#include "mod_player.h"
#include "ogg_player.h"
#include "prf_player.h"
#include "sfx_player.h"
struct MixerChunk {
@ -49,6 +50,7 @@ struct Mixer {
MT_NONE,
MT_MOD,
MT_OGG,
MT_PRF,
MT_SFX,
MT_CPC,
};
@ -70,10 +72,11 @@ struct Mixer {
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);

View File

@ -165,7 +165,7 @@ void Game::pge_setupNextAnimFrame(LivePGE *pge, MessagePGE *le) {
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) {
MessagePGE *next_le = le;
while (next_le) {
uint16_t msgNum = next_le->msg_num;
@ -403,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;

421
prf_player.cpp Normal file
View File

@ -0,0 +1,421 @@
#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];
MidiEvent ev = track->events.front();
current_track->counter = ev.timestamp;
}
_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->events.size() == 0) {
continue;
}
const int track_index = i;
PrfTrack *current_track = &_tracks[i];
if (current_track->counter != 0) {
--current_track->counter;
continue;
}
next_event:
MidiEvent ev = track->events.front();
track->events.pop();
if (current_track->loop_flag) {
track->events.push(ev);
}
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;
}
if (track->events.size() != 0) {
ev = track->events.front();
current_track->counter = ev.timestamp;
if (current_track->counter == 0) {
goto next_event;
}
--current_track->counter;
}
}
}
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) {
//++_timerTick;
//if (_timerTick == _prfData.timerTicks) {
//fprintf(stdout, "musicTick #%d of %d\n", _musicTick, _prfData.totalDurationTicks);
handleTick();
//_timerTick = 0;
if (_prfData.totalDurationTicks != 0) {
++_musicTick;
if (_musicTick == _prfData.totalDurationTicks + 1) {
debug(DBG_PRF, "End of music");
//break;
}
}
//}
_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) {
int16_t *p = (int16_t *)alloca(len * sizeof(int16_t) * 2);
if (p) {
const int count = readSamples(p, len);
/* stereo to mono */
for (int i = 0; i < count; ++i) {
const int16_t l = *p++;
const int16_t r = *p++;
buf[i] = CLIP((l + r) / 2, -32768, 32767);
}
return count != 0;
}
return false;
}

121
prf_player.h Normal file
View File

@ -0,0 +1,121 @@
#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;
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__ */

View File

@ -917,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];
@ -1012,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];

View File

@ -353,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,
@ -371,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

3
rs.cfg
View File

@ -25,6 +25,9 @@ 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

View File

@ -6503,3 +6503,34 @@ const uint8_t Menu::_flagSp16x12[] = {
0x03, 0x00, 0x8d, 0x03, 0x00, 0x8d, 0x03, 0x00, 0x8d, 0x03, 0x00, 0x8d, 0x03, 0x00, 0x8d, 0x03,
0x00, 0x8d, 0x03, 0x00, 0x8d, 0x03, 0x00, 0x8d, 0x03, 0x00, 0x8d, 0x03, 0x00, 0x8e, 0x03, 0x00
};
const char *PrfPlayer::_names[] = {
"introl3",
"option3",
"journal3",
"chute3",
"desinte3",
"capture3",
"voyage3",
"telepor3",
"planexp3",
"end31",
"lift3",
"present3",
"gameove3",
"holo3",
"memory3",
"chutevi3",
"reveil3",
"misvali3",
"taxi3",
"donner3",
"mission3",
"objet3",
"recharg3",
"generat3",
"pont3",
"rechage3"
};
const int PrfPlayer::_namesCount = ARRAYSIZE(_names);

View File

@ -74,6 +74,7 @@ struct SystemStub {
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;

View File

@ -72,6 +72,7 @@ struct SystemStub_SDL : SystemStub {
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();
@ -478,6 +479,28 @@ void SystemStub_SDL::copyWidescreenBlur(int w, int h, const uint8_t *buf) {
}
}
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);
}
@ -983,7 +1006,9 @@ void SystemStub_SDL::prepareGraphics() {
_widescreenMode = kWidescreenNone;
}
}
if (_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);
@ -996,11 +1021,15 @@ void SystemStub_SDL::prepareGraphics() {
SDL_RenderSetLogicalSize(_renderer, windowW, windowH);
_texture = SDL_CreateTexture(_renderer, kPixelFormat, SDL_TEXTUREACCESS_STREAMING, _texW, _texH);
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
const int w = (_widescreenMode == kWidescreenBlur) ? _screenW : _screenH * 16 / 9;
const int h = _screenH;
_widescreenTexture = SDL_CreateTexture(_renderer, kPixelFormat, SDL_TEXTUREACCESS_STREAMING, w, h);
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
@ -1053,7 +1082,7 @@ void SystemStub_SDL::setScaler(const ScalerParameters *parameters) {
{ "tv2x", kScalerTypeInternal, &scaler_tv2x },
{ "xbr", kScalerTypeInternal, &scaler_xbr },
#endif
{ 0, -1 }
{ 0, -1, 0 }
};
bool found = false;
for (int i = 0; scalers[i].name; ++i) {

View File

@ -15,13 +15,13 @@ struct UnpackCtx {
const uint8_t *src;
};
static bool nextBit(UnpackCtx *uc) {
bool bit = (uc->bits & 1) != 0;
static int nextBit(UnpackCtx *uc) {
int bit = (uc->bits & 1);
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) != 0;
bit = (bits & 1);
uc->bits = (1 << 31) | (bits >> 1);
}
return bit;
@ -30,10 +30,8 @@ static bool nextBit(UnpackCtx *uc) {
template<int count>
static uint32_t getBits(UnpackCtx *uc) { // rdd1bits
uint32_t bits = 0;
for (uint32_t mask = 1 << (count - 1); mask != 0; mask >>= 1) {
if (nextBit(uc)) {
bits |= mask;
}
for (int i = 0; i < count; ++i) {
bits = (bits << 1) | nextBit(uc);
}
return bits;
}
@ -44,10 +42,9 @@ static void copyLiteral(UnpackCtx *uc, int len) { // getd3chr
len += uc->size;
uc->size = 0;
}
for (int i = 0; i < len; ++i) {
*(uc->dst - i) = (uint8_t)getBits<8>(uc);
for (int i = 0; i < len; ++i, --uc->dst) {
*(uc->dst) = (uint8_t)getBits<8>(uc);
}
uc->dst -= len;
}
static void copyReference(UnpackCtx *uc, int len, int offset) { // copyd3bytes
@ -56,10 +53,9 @@ static void copyReference(UnpackCtx *uc, int len, int offset) { // copyd3bytes
len += uc->size;
uc->size = 0;
}
for (int i = 0; i < len; ++i) {
*(uc->dst - i) = *(uc->dst - i + offset);
for (int i = 0; i < len; ++i, --uc->dst) {
*(uc->dst) = *(uc->dst + offset);
}
uc->dst -= len;
}
bool bytekiller_unpack(uint8_t *dst, int dstSize, const uint8_t *src, int srcSize) {

3
util.h
View File

@ -23,7 +23,8 @@ 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
};
extern uint16_t g_debugMask;

View File

@ -121,6 +121,7 @@ void Video::updateWidescreen() {
_stub->copyWidescreenMirror(_w, _h, _backLayer);
} else if (_widescreenMode == kWidescreenBlur) {
_stub->copyWidescreenBlur(_w, _h, _backLayer);
} else if (_widescreenMode == kWidescreenCDi) {
} else {
_stub->clearWidescreen();
}
@ -498,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);
@ -526,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);
}
@ -648,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;
@ -659,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);
@ -718,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());