Compare commits
8 Commits
Author | SHA1 | Date |
---|---|---|
George Sokianos | 64f00204e7 | |
Gregory Montoir | 100218a3c2 | |
Gregory Montoir | 1f86fdea2d | |
Gregory Montoir | 419cf91dfe | |
Gregory Montoir | 315bb9bcff | |
Gregory Montoir | bc1337da63 | |
Gregory Montoir | 2dc61ca627 | |
Gregory Montoir | 86baaa3a9a |
|
@ -0,0 +1,4 @@
|
|||
*.o
|
||||
*.d
|
||||
*.lha
|
||||
rs
|
|
@ -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
|
27
Makefile
27
Makefile
|
@ -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)
|
||||
|
|
|
@ -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)
|
73
README.txt
73
README.txt
|
@ -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:
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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
|
392
cutscene.cpp
392
cutscene.cpp
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
50
cutscene.h
50
cutscene.h
|
@ -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);
|
||||
};
|
||||
|
||||
|
|
147
decode_mac.cpp
147
decode_mac.cpp
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
59
dynlib.cpp
59
dynlib.cpp
|
@ -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);
|
||||
}
|
16
dynlib.h
16
dynlib.h
|
@ -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
103
file.cpp
|
@ -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
8
file.h
|
@ -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
6
fs.cpp
|
@ -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
2
fs.h
|
@ -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__
|
||||
|
|
84
game.h
84
game.h
|
@ -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__
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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__
|
||||
|
|
54
intern.h
54
intern.h
|
@ -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
119
main.cpp
|
@ -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();
|
||||
|
|
49
menu.cpp
49
menu.cpp
|
@ -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
2
menu.h
|
@ -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__
|
||||
|
|
|
@ -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__ */
|
|
@ -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
|
||||
};
|
|
@ -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
|
||||
};
|
|
@ -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;
|
||||
}
|
|
@ -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
100
mixer.cpp
|
@ -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
19
mixer.h
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
181
ogg_player.cpp
181
ogg_player.cpp
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
450
piege.cpp
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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__ */
|
|
@ -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.
|
@ -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.
|
@ -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.
286
resource.cpp
286
resource.cpp
|
@ -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);
|
||||
|
|
27
resource.h
27
resource.h
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -39,6 +39,7 @@ struct ResourceMac {
|
|||
ResourceMacMap _map;
|
||||
ResourceMacType *_types;
|
||||
ResourceMacEntry **_entries;
|
||||
int _sndIndex;
|
||||
|
||||
ResourceMac(const char *filePath, FileSystem *);
|
||||
~ResourceMac();
|
||||
|
|
27
rs.cfg
27
rs.cfg
|
@ -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
|
||||
|
|
17
scaler.cpp
17
scaler.cpp
|
@ -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;
|
||||
}
|
||||
|
|
4
scaler.h
4
scaler.h
|
@ -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__
|
||||
|
|
105
screenshot.cpp
105
screenshot.cpp
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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__
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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__
|
||||
|
|
928
staticres.cpp
928
staticres.cpp
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
38
systemstub.h
38
systemstub.h
|
@ -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__
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
72
unpack.cpp
72
unpack.cpp
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
4
unpack.h
4
unpack.h
|
@ -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__
|
||||
|
|
4
util.cpp
4
util.cpp
|
@ -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
16
util.h
|
@ -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
146
video.cpp
|
@ -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
13
video.h
|
@ -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);
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue