diff options
Diffstat (limited to 'engines/agi')
35 files changed, 5107 insertions, 3373 deletions
diff --git a/engines/agi/agi.cpp b/engines/agi/agi.cpp index c2c6d10bfe..e83ef4ead9 100644 --- a/engines/agi/agi.cpp +++ b/engines/agi/agi.cpp @@ -272,20 +272,18 @@ void AgiEngine::processEvents() { } void AgiEngine::pollTimer() { - static uint32 m = 0; uint32 dm; - if (_tickTimer < m) - m = 0; + if (_tickTimer < _lastTickTimer) + _lastTickTimer = 0; - while ((dm = _tickTimer - m) < 5) { + while ((dm = _tickTimer - _lastTickTimer) < 5) { processEvents(); - if (_console->isAttached()) - _console->onFrame(); + _console->onFrame(); _system->delayMillis(10); _system->updateScreen(); } - m = _tickTimer; + _lastTickTimer = _tickTimer; } void AgiEngine::agiTimerFunctionLow(void *refCon) { @@ -345,7 +343,7 @@ int AgiEngine::agiInit() { // clear view table for (i = 0; i < MAX_VIEWTABLE; i++) - memset(&_game.viewTable[i], 0, sizeof(VtEntry)); + memset(&_game.viewTable[i], 0, sizeof(struct VtEntry)); initWords(); @@ -506,13 +504,6 @@ AgiEngine::AgiEngine(OSystem *syst, const AGIGameDescription *gameDesc) : AgiBas _mixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, ConfMan.getInt("sfx_volume")); _mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, ConfMan.getInt("music_volume")); - const GameSettings *g; - - const char *gameid = ConfMan.get("gameid").c_str(); - for (g = agiSettings; g->gameid; ++g) - if (!scumm_stricmp(g->gameid, gameid)) - _gameId = g->id; - parseFeatures(); _rnd = new Common::RandomSource(); @@ -543,6 +534,7 @@ AgiEngine::AgiEngine(OSystem *syst, const AGIGameDescription *gameDesc) : AgiBas _allowSynthetic = false; _tickTimer = 0; + _lastTickTimer = 0; _intobj = NULL; @@ -556,7 +548,7 @@ AgiEngine::AgiEngine(OSystem *syst, const AGIGameDescription *gameDesc) : AgiBas _restartGame = false; - _oldMode = -1; + _oldMode = INPUT_NONE; _predictiveDialogRunning = false; _predictiveDictText = NULL; @@ -569,6 +561,10 @@ AgiEngine::AgiEngine(OSystem *syst, const AGIGameDescription *gameDesc) : AgiBas _game.lastController = 0; for (int i = 0; i < MAX_DIRS; i++) _game.controllerOccured[i] = false; + + setupOpcodes(); + _curLogic = NULL; + _timerHack = 0; } void AgiEngine::initialize() { @@ -583,13 +579,19 @@ void AgiEngine::initialize() { } else if (getPlatform() == Common::kPlatformCoCo3) { _soundemu = SOUND_EMU_COCO3; } else { - switch (MidiDriver::detectMusicDriver(MDT_PCSPK)) { - case MD_PCSPK: + switch (MidiDriver::getMusicType(MidiDriver::detectDevice(MDT_PCSPK|MDT_ADLIB|MDT_PCJR|MDT_MIDI))) { + case MT_PCSPK: _soundemu = SOUND_EMU_PC; break; - default: + case MT_PCJR: + _soundemu = SOUND_EMU_PCJR; + break; + case MT_ADLIB: _soundemu = SOUND_EMU_NONE; break; + default: + _soundemu = SOUND_EMU_MIDI; + break; } } @@ -607,6 +609,8 @@ void AgiEngine::initialize() { _renderMode = Common::kRenderEGA; break; } + } else { + _renderMode = Common::kRenderDefault; } _buttonStyle = AgiButtonStyle(_renderMode); diff --git a/engines/agi/agi.h b/engines/agi/agi.h index fb9e204101..507e7f7a11 100644 --- a/engines/agi/agi.h +++ b/engines/agi/agi.h @@ -37,6 +37,14 @@ #include "gui/debugger.h" +// AGI resources +#include "agi/console.h" +#include "agi/view.h" +#include "agi/picture.h" +#include "agi/logic.h" +#include "agi/sound.h" + + namespace Common { class RandomSource; } /** @@ -110,23 +118,13 @@ enum AgiGameID { GID_SQ2, GID_XMASCARD, GID_FANMADE, - GID_GETOUTTASQ, + GID_GETOUTTASQ, // Fanmade + GID_SQ0, // Fanmade GID_MICKEY, // PreAGI GID_WINNIE, // PreAGI - GID_TROLL // PreAGI + GID_TROLL // PreAGI }; -} // End of namespace Agi - -// AGI resources -#include "agi/console.h" -#include "agi/view.h" -#include "agi/picture.h" -#include "agi/logic.h" -#include "agi/sound.h" - -namespace Agi { - enum AgiGameType { GType_PreAGI = 0, GType_V2 = 1, @@ -151,7 +149,8 @@ enum AgiGameFeatures { GF_MENUS = (1 << 7), GF_ESCPAUSE = (1 << 8), GF_OLDAMIGAV20 = (1 << 9), - GF_CLIPCOORDS = (1 << 10) + GF_CLIPCOORDS = (1 << 10), + GF_2GSOLDSOUND = (1 << 11) }; struct AGIGameDescription; @@ -316,6 +315,12 @@ enum AgiComputerType { kAgiComputerAmigaOld = 20 // Older Amiga AGI interpreters' value (Seldom used) }; +enum AgiSoundType { + kAgiSoundPC = 1, + kAgiSoundTandy = 3, // Tandy (This value is also used by the Amiga AGI and Apple IIGS AGI) + kAgiSound2GSOld = 8 // Apple IIGS's Gold Rush! (Version 1.0M 1989-02-28 (CE), AGI 3.003) uses value 8 +}; + /** * AGI flags */ @@ -497,10 +502,32 @@ struct ScriptPos { int curIP; }; -#define EGO_VIEW_TABLE 0 -#define HORIZON 36 -#define _WIDTH 160 -#define _HEIGHT 168 +enum { + EGO_VIEW_TABLE = 0, + HORIZON = 36, + _WIDTH = 160, + _HEIGHT = 168 +}; + +enum InputMode { + INPUT_NORMAL = 0x01, + INPUT_GETSTRING = 0x02, + INPUT_MENU = 0x03, + INPUT_NONE = 0x04 +}; + +enum State { + STATE_INIT = 0x00, + STATE_LOADED = 0x01, + STATE_RUNNING = 0x02 +}; + +enum { + SBUF16_OFFSET = 0, + SBUF256_OFFSET = ((_WIDTH) * (_HEIGHT)), + FROM_SBUF16_TO_SBUF256_OFFSET = ((SBUF256_OFFSET) - (SBUF16_OFFSET)), + FROM_SBUF256_TO_SBUF16_OFFSET = ((SBUF16_OFFSET) - (SBUF256_OFFSET)) +}; /** * AGI game structure. @@ -508,10 +535,7 @@ struct ScriptPos { * by the interpreter. */ struct AgiGame { -#define STATE_INIT 0x00 -#define STATE_LOADED 0x01 -#define STATE_RUNNING 0x02 - int state; /**< state of the interpreter */ + State state; /**< state of the interpreter */ // TODO: Check whether adjMouseX and adjMouseY must be saved and loaded when using savegames. // If they must be then loading and saving is partially broken at the moment. @@ -535,12 +559,9 @@ struct AgiGame { uint8 inputBuffer[40]; /**< buffer for user input */ uint8 echoBuffer[40]; /**< buffer for echo.line */ int keypress; -#define INPUT_NORMAL 0x01 -#define INPUT_GETSTRING 0x02 -#define INPUT_MENU 0x03 -#define INPUT_NONE 0x04 - int inputMode; /**< keyboard input mode */ - int inputEnabled; /**< keyboard input enabled */ + + InputMode inputMode; /**< keyboard input mode */ + bool inputEnabled; /**< keyboard input enabled */ int lognum; /**< current logic number */ Common::Array<ScriptPos> execStack; @@ -568,10 +589,7 @@ struct AgiGame { char cursorChar; unsigned int colorFg; unsigned int colorBg; -#define SBUF16_OFFSET 0 -#define SBUF256_OFFSET ((_WIDTH) * (_HEIGHT)) -#define FROM_SBUF16_TO_SBUF256_OFFSET ((SBUF256_OFFSET) - (SBUF16_OFFSET)) -#define FROM_SBUF256_TO_SBUF16_OFFSET ((SBUF16_OFFSET) - (SBUF256_OFFSET)) + uint8 *sbufOrig; /**< Pointer to the 160x336 AGI screen buffer that contains vertically two 160x168 screens (16 color and 256 color). */ uint8 *sbuf16c; /**< 160x168 16 color (+control line & priority information) AGI screen buffer. Points at sbufOrig + SBUF16_OFFSET. */ uint8 *sbuf256c; /**< 160x168 256 color AGI screen buffer (For AGI256 and AGI256-2 support). Points at sbufOrig + SBUF256_OFFSET. */ @@ -774,8 +792,6 @@ public: }; class AgiEngine : public AgiBase { - int _gameId; - protected: // Engine APIs virtual Common::Error go(); @@ -788,9 +804,6 @@ protected: public: AgiEngine(OSystem *syst, const AGIGameDescription *gameDesc); virtual ~AgiEngine(); - int getGameId() { - return _gameId; - } Common::Error loadGameState(int slot); Common::Error saveGameState(int slot, const char *desc); @@ -798,6 +811,7 @@ public: private: uint32 _tickTimer; + uint32 _lastTickTimer; int _keyQueue[KEY_QUEUE_SIZE]; int _keyQueueStart; @@ -829,7 +843,7 @@ public: int loadGameSimple(); uint8 *_intobj; - int _oldMode; + InputMode _oldMode; bool _restartGame; Menu* _menu; @@ -871,7 +885,7 @@ public: static void agiTimerFunctionLow(void *refCon); void initPriTable(); - void newInputMode(int); + void newInputMode(InputMode mode); void oldInputMode(); int getvar(int); @@ -920,6 +934,17 @@ public: int testIfCode(int); void executeAgiCommand(uint8, uint8 *); +private: + // Some submethods of testIfCode + uint8 testObjRight(uint8, uint8, uint8, uint8, uint8); + uint8 testObjCentre(uint8, uint8, uint8, uint8, uint8); + uint8 testObjInBox(uint8, uint8, uint8, uint8, uint8); + uint8 testPosn(uint8, uint8, uint8, uint8, uint8); + uint8 testSaid(uint8, uint8 *); + uint8 testController(uint8); + uint8 testKeypressed(); + uint8 testCompareStrings(uint8, uint8); + // View private: @@ -1024,6 +1049,198 @@ private: bool _predictiveDialogRunning; public: char _predictiveResult[40]; + +private: + typedef void (AgiEngine::*AgiCommand)(uint8 *); + + AgiCommand _agiCommands[183]; + AgiLogic *_curLogic; + int _timerHack; // Workaround for timer loop in MH1 logic 153 + + void setupOpcodes(); + + void cmd_increment(uint8 *p); + void cmd_decrement(uint8 *p); + void cmd_assignn(uint8 *p); + void cmd_assignv(uint8 *p); + void cmd_addn(uint8 *p); + void cmd_addv(uint8 *p); + void cmd_subn(uint8 *p); + void cmd_subv(uint8 *p); // 0x08 + void cmd_lindirectv(uint8 *p); + void cmd_rindirect(uint8 *p); + void cmd_lindirectn(uint8 *p); + void cmd_set(uint8 *p); + void cmd_reset(uint8 *p); + void cmd_toggle(uint8 *p); + void cmd_set_v(uint8 *p); + void cmd_reset_v(uint8 *p); // 0x10 + void cmd_toggle_v(uint8 *p); + void cmd_new_room(uint8 *p); + void cmd_new_room_f(uint8 *p); + void cmd_load_logic(uint8 *p); + void cmd_load_logic_f(uint8 *p); + void cmd_call(uint8 *p); + void cmd_call_f(uint8 *p); + void cmd_load_pic(uint8 *p); // 0x18 + void cmd_draw_pic(uint8 *p); + void cmd_show_pic(uint8 *p); + void cmd_discard_pic(uint8 *p); + void cmd_overlay_pic(uint8 *p); + void cmd_show_pri_screen(uint8 *p); + void cmd_load_view(uint8 *p); + void cmd_load_view_f(uint8 *p); + void cmd_discard_view(uint8 *p); // 0x20 + void cmd_animate_obj(uint8 *p); + void cmd_unanimate_all(uint8 *p); + void cmd_draw(uint8 *p); + void cmd_erase(uint8 *p); + void cmd_position(uint8 *p); + void cmd_position_f(uint8 *p); + void cmd_get_posn(uint8 *p); + void cmd_reposition(uint8 *p); // 0x28 + void cmd_set_view(uint8 *p); + void cmd_set_view_f(uint8 *p); + void cmd_set_loop(uint8 *p); + void cmd_set_loop_f(uint8 *p); + void cmd_fix_loop(uint8 *p); + void cmd_release_loop(uint8 *p); + void cmd_set_cel(uint8 *p); + void cmd_set_cel_f(uint8 *p); // 0x30 + void cmd_last_cel(uint8 *p); + void cmd_current_cel(uint8 *p); + void cmd_current_loop(uint8 *p); + void cmd_current_view(uint8 *p); + void cmd_number_of_loops(uint8 *p); + void cmd_set_priority(uint8 *p); + void cmd_set_priority_f(uint8 *p); + void cmd_release_priority(uint8 *p); // 0x38 + void cmd_get_priority(uint8 *p); + void cmd_stop_update(uint8 *p); + void cmd_start_update(uint8 *p); + void cmd_force_update(uint8 *p); + void cmd_ignore_horizon(uint8 *p); + void cmd_observe_horizon(uint8 *p); + void cmd_set_horizon(uint8 *p); + void cmd_object_on_water(uint8 *p); // 0x40 + void cmd_object_on_land(uint8 *p); + void cmd_object_on_anything(uint8 *p); + void cmd_ignore_objs(uint8 *p); + void cmd_observe_objs(uint8 *p); + void cmd_distance(uint8 *p); + void cmd_stop_cycling(uint8 *p); + void cmd_start_cycling(uint8 *p); + void cmd_normal_cycle(uint8 *p); // 0x48 + void cmd_end_of_loop(uint8 *p); + void cmd_reverse_cycle(uint8 *p); + void cmd_reverse_loop(uint8 *p); + void cmd_cycle_time(uint8 *p); + void cmd_stop_motion(uint8 *p); + void cmd_start_motion(uint8 *p); + void cmd_step_size(uint8 *p); + void cmd_step_time(uint8 *p); // 0x50 + void cmd_move_obj(uint8 *p); + void cmd_move_obj_f(uint8 *p); + void cmd_follow_ego(uint8 *p); + void cmd_wander(uint8 *p); + void cmd_normal_motion(uint8 *p); + void cmd_set_dir(uint8 *p); + void cmd_get_dir(uint8 *p); + void cmd_ignore_blocks(uint8 *p); // 0x58 + void cmd_observe_blocks(uint8 *p); + void cmd_block(uint8 *p); + void cmd_unblock(uint8 *p); + void cmd_get(uint8 *p); + void cmd_get_f(uint8 *p); + void cmd_drop(uint8 *p); + void cmd_put(uint8 *p); + void cmd_put_f(uint8 *p); // 0x60 + void cmd_get_room_f(uint8 *p); + void cmd_load_sound(uint8 *p); + void cmd_sound(uint8 *p); + void cmd_stop_sound(uint8 *p); + void cmd_print(uint8 *p); + void cmd_print_f(uint8 *p); + void cmd_display(uint8 *p); + void cmd_display_f(uint8 *p); // 0x68 + void cmd_clear_lines(uint8 *p); + void cmd_text_screen(uint8 *p); + void cmd_graphics(uint8 *p); + void cmd_set_cursor_char(uint8 *p); + void cmd_set_text_attribute(uint8 *p); + void cmd_shake_screen(uint8 *p); + void cmd_configure_screen(uint8 *p); + void cmd_status_line_on(uint8 *p); // 0x70 + void cmd_status_line_off(uint8 *p); + void cmd_set_string(uint8 *p); + void cmd_get_string(uint8 *p); + void cmd_word_to_string(uint8 *p); + void cmd_parse(uint8 *p); + void cmd_get_num(uint8 *p); + void cmd_prevent_input(uint8 *p); + void cmd_accept_input(uint8 *p); // 0x78 + void cmd_set_key(uint8 *p); + void cmd_add_to_pic(uint8 *p); + void cmd_add_to_pic_f(uint8 *p); + void cmd_status(uint8 *p); + void cmd_save_game(uint8 *p); + void cmd_load_game(uint8 *p); + void cmd_init_disk(uint8 *p); + void cmd_restart_game(uint8 *p); // 0x80 + void cmd_show_obj(uint8 *p); + void cmd_random(uint8 *p); + void cmd_program_control(uint8 *p); + void cmd_player_control(uint8 *p); + void cmd_obj_status_f(uint8 *p); + void cmd_quit(uint8 *p); + void cmd_show_mem(uint8 *p); + void cmd_pause(uint8 *p); // 0x88 + void cmd_echo_line(uint8 *p); + void cmd_cancel_line(uint8 *p); + void cmd_init_joy(uint8 *p); + void cmd_toggle_monitor(uint8 *p); + void cmd_version(uint8 *p); + void cmd_script_size(uint8 *p); + void cmd_set_game_id(uint8 *p); + void cmd_log(uint8 *p); // 0x90 + void cmd_set_scan_start(uint8 *p); + void cmd_reset_scan_start(uint8 *p); + void cmd_reposition_to(uint8 *p); + void cmd_reposition_to_f(uint8 *p); + void cmd_trace_on(uint8 *p); + void cmd_trace_info(uint8 *p); + void cmd_print_at(uint8 *p); + void cmd_print_at_v(uint8 *p); // 0x98 + //void cmd_discard_view(uint8 *p); // Opcode repeated from 0x20 ? + void cmd_clear_text_rect(uint8 *p); + void cmd_set_upper_left(uint8 *p); + void cmd_set_menu(uint8 *p); + void cmd_set_menu_item(uint8 *p); + void cmd_submit_menu(uint8 *p); + void cmd_enable_item(uint8 *p); + void cmd_disable_item(uint8 *p); // 0xa0 + void cmd_menu_input(uint8 *p); + void cmd_show_obj_v(uint8 *p); + void cmd_open_dialogue(uint8 *p); + void cmd_close_dialogue(uint8 *p); + void cmd_mul_n(uint8 *p); + void cmd_mul_v(uint8 *p); + void cmd_div_n(uint8 *p); + void cmd_div_v(uint8 *p); // 0xa8 + void cmd_close_window(uint8 *p); + void cmd_set_simple(uint8 *p); + void cmd_push_script(uint8 *p); + void cmd_pop_script(uint8 *p); + void cmd_hold_key(uint8 *p); + void cmd_set_pri_base(uint8 *p); + void cmd_discard_sound(uint8 *p); + void cmd_hide_mouse(uint8 *p); // 0xb0 + void cmd_allow_menu(uint8 *p); + void cmd_show_mouse(uint8 *p); + void cmd_fence_mouse(uint8 *p); + void cmd_mouse_posn(uint8 *p); + void cmd_release_key(uint8 *p); + void cmd_adj_ego_move_to_x_y(uint8 *p); }; } // End of namespace Agi diff --git a/engines/agi/console.cpp b/engines/agi/console.cpp index a0621f80dd..e5942455e2 100644 --- a/engines/agi/console.cpp +++ b/engines/agi/console.cpp @@ -53,18 +53,9 @@ Console::Console(AgiEngine *vm) : GUI::Debugger() { DCmd_Register("bt", WRAP_METHOD(Console, Cmd_BT)); } -Console::~Console() { -} - -void Console::preEnter() { -} - -void Console::postEnter() { -} - bool Console::Cmd_SetVar(int argc, const char **argv) { if (argc != 3) { - DebugPrintf("Usage: setvar <varnum> <value>"); + DebugPrintf("Usage: setvar <varnum> <value>\n"); return true; } int p1 = (int)atoi(argv[1]); @@ -76,7 +67,7 @@ bool Console::Cmd_SetVar(int argc, const char **argv) { bool Console::Cmd_SetFlag(int argc, const char **argv) { if (argc != 3) { - DebugPrintf("Usage: setvar <varnum> <value>"); + DebugPrintf("Usage: setvar <varnum> <value>\n"); return true; } int p1 = (int)atoi(argv[1]); @@ -88,7 +79,7 @@ bool Console::Cmd_SetFlag(int argc, const char **argv) { bool Console::Cmd_SetObj(int argc, const char **argv) { if (argc != 3) { - DebugPrintf("Usage: setvar <varnum> <value>"); + DebugPrintf("Usage: setvar <varnum> <value>\n"); return true; } int p1 = (int)atoi(argv[1]); @@ -99,6 +90,11 @@ bool Console::Cmd_SetObj(int argc, const char **argv) { } bool Console::Cmd_RunOpcode(int argc, const char **argv) { + if (argc < 2) { + DebugPrintf("Usage: runopcode <name> <parameter0> ....\n"); + return true; + } + for (int i = 0; logicNamesCmd[i].name; i++) { if (!strcmp(argv[1], logicNamesCmd[i].name)) { uint8 p[16]; @@ -120,6 +116,8 @@ bool Console::Cmd_RunOpcode(int argc, const char **argv) { } } + DebugPrintf("Unknown opcode\n"); + return true; } @@ -243,6 +241,10 @@ bool Console::Cmd_Cont(int argc, const char **argv) { } bool Console::Cmd_Room(int argc, const char **argv) { + if (argc == 2) { + _vm->newRoom(strtoul(argv[1], NULL, 0)); + } + DebugPrintf("Current room: %d\n", _vm->getvar(0)); return true; diff --git a/engines/agi/console.h b/engines/agi/console.h index e8eccbe50a..e79db42054 100644 --- a/engines/agi/console.h +++ b/engines/agi/console.h @@ -46,11 +46,6 @@ struct AgiDebug { class Console : public GUI::Debugger { public: Console(AgiEngine *vm); - virtual ~Console(); - -protected: - virtual void preEnter(); - virtual void postEnter(); private: bool Cmd_SetVar(int argc, const char **argv); @@ -80,10 +75,6 @@ public: PreAGI_Console(PreAgiEngine *vm); virtual ~PreAGI_Console() {} -protected: - virtual void preEnter() {} - virtual void postEnter() {} - private: PreAgiEngine *_vm; }; @@ -94,10 +85,6 @@ public: Mickey_Console(PreAgiEngine *vm, Mickey *mickey); virtual ~Mickey_Console() {} -protected: - virtual void preEnter() {} - virtual void postEnter() {} - private: Mickey *_mickey; @@ -112,10 +99,6 @@ public: Winnie_Console(PreAgiEngine *vm, Winnie *winnie); virtual ~Winnie_Console() {} -protected: - virtual void preEnter() {} - virtual void postEnter() {} - private: Winnie *_winnie; diff --git a/engines/agi/cycle.cpp b/engines/agi/cycle.cpp index 10df40556f..b7eba22298 100644 --- a/engines/agi/cycle.cpp +++ b/engines/agi/cycle.cpp @@ -177,9 +177,12 @@ void AgiEngine::updateTimer() { setvar(vDays, getvar(vDays) + 1); } -void AgiEngine::newInputMode(int i) { +void AgiEngine::newInputMode(InputMode mode) { + if (mode == INPUT_MENU && !getflag(fMenusWork) && !(getFeatures() & GF_MENUS)) + return; + _oldMode = _game.inputMode; - _game.inputMode = i; + _game.inputMode = mode; } void AgiEngine::oldInputMode() { @@ -200,7 +203,7 @@ int AgiEngine::mainCycle() { // vars in every interpreter cycle. // // We run AGIMOUSE always as a side effect - if (getFeatures() & GF_AGIMOUSE || 1) { + if (getFeatures() & GF_AGIMOUSE || true) { _game.vars[28] = _mouse.x / 2; _game.vars[29] = _mouse.y; } @@ -314,7 +317,8 @@ int AgiEngine::playGame() { _game.clockEnabled = true; _game.lineUserInput = 22; - if (getFeatures() & GF_AGIMOUSE) + // We run AGIMOUSE always as a side effect + if (getFeatures() & GF_AGIMOUSE || true) report("Using AGI Mouse 1.0 protocol\n"); if (getFeatures() & GF_AGIPAL) @@ -386,28 +390,33 @@ int AgiEngine::runGame() { _restartGame = false; } - // Set computer type (v20 i.e. vComputer) + // Set computer type (v20 i.e. vComputer) and sound type switch (getPlatform()) { case Common::kPlatformAtariST: setvar(vComputer, kAgiComputerAtariST); + setvar(vSoundgen, kAgiSoundPC); break; case Common::kPlatformAmiga: if (getFeatures() & GF_OLDAMIGAV20) setvar(vComputer, kAgiComputerAmigaOld); else setvar(vComputer, kAgiComputerAmiga); + setvar(vSoundgen, kAgiSoundTandy); break; case Common::kPlatformApple2GS: setvar(vComputer, kAgiComputerApple2GS); + if (getFeatures() & GF_2GSOLDSOUND) + setvar(vSoundgen, kAgiSound2GSOld); + else + setvar(vSoundgen, kAgiSoundTandy); break; case Common::kPlatformPC: default: setvar(vComputer, kAgiComputerPC); + setvar(vSoundgen, kAgiSoundPC); break; } - setvar(vSoundgen, 1); // IBM PC SOUND - // Set monitor type (v26 i.e. vMonitor) switch (_renderMode) { case Common::kRenderCGA: @@ -430,7 +439,7 @@ int AgiEngine::runGame() { setvar(vFreePages, 180); // Set amount of free memory to realistic value setvar(vMaxInputChars, 38); _game.inputMode = INPUT_NONE; - _game.inputEnabled = 0; + _game.inputEnabled = false; _game.hasPrompt = 0; _game.state = STATE_RUNNING; diff --git a/engines/agi/detection.cpp b/engines/agi/detection.cpp index e72647d5e2..d1bed5d716 100644 --- a/engines/agi/detection.cpp +++ b/engines/agi/detection.cpp @@ -125,841 +125,7 @@ static const PlainGameDescriptor agiGames[] = { {0, 0} }; - -namespace Agi { - -using Common::GUIO_NONE; - -#define GAME_LVFPN(id,name,fname,md5,size,lang,ver,features,gid,platform,interp) { \ - { \ - id, \ - name, \ - AD_ENTRY1s(fname,md5,size), \ - lang, \ - platform, \ - ADGF_NO_FLAGS, \ - GUIO_NONE \ - }, \ - gid, \ - interp, \ - features, \ - ver, \ - } - -#define GAME_LVFPNF(id,name,fname,md5,size,lang,ver,features,gid,platform,interp) { \ - { \ - id, \ - name, \ - AD_ENTRY1s(fname,md5,size), \ - lang, \ - platform, \ - ADGF_USEEXTRAASTITLE, \ - GUIO_NONE \ - }, \ - gid, \ - interp, \ - features, \ - ver, \ - } - -#define GAME(id,name,md5,ver,gid) GAME_LVFPN(id,name,"logdir",md5,-1,Common::EN_ANY,ver,0,gid,Common::kPlatformPC,GType_V2) -#define GAME3(id,name,fname,md5,ver,gid) GAME_LVFPN(id,name,fname,md5,-1,Common::EN_ANY,ver,0,gid,Common::kPlatformPC,GType_V3) - -#define GAME_P(id,name,md5,ver,gid,platform) GAME_LVFPN(id,name,"logdir",md5,-1,Common::EN_ANY,ver,0,gid,platform,GType_V2) - -#define GAME_FP(id,name,md5,ver,flags,gid,platform) GAME_LVFPN(id,name,"logdir",md5,-1,Common::EN_ANY,ver,flags,gid,platform,GType_V2) - -#define GAME_PS(id,name,md5,size,ver,gid,platform) GAME_LVFPN(id,name,"logdir",md5,size,Common::EN_ANY,ver,0,gid,platform,GType_V2) - -#define GAME_LPS(id,name,md5,size,lang,ver,gid,platform) GAME_LVFPN(id,name,"logdir",md5,size,lang,ver,0,gid,platform,GType_V2) - -#define GAME_LFPS(id,name,md5,size,lang,ver,flags,gid,platform) GAME_LVFPN(id,name,"logdir",md5,size,lang,ver,flags,gid,platform,GType_V2) - -#define GAME3_P(id,name,fname,md5,ver,flags,gid,platform) GAME_LVFPN(id,name,fname,md5,-1,Common::EN_ANY,ver,flags,gid,platform,GType_V3) - -#define GAMEpre_P(id,name,fname,md5,ver,gid,platform) GAME_LVFPN(id,name,fname,md5,-1,Common::EN_ANY,ver,0,gid,platform,GType_PreAGI) - -#define GAMEpre_PS(id,name,fname,md5,size,ver,gid,platform) GAME_LVFPN(id,name,fname,md5,size,Common::EN_ANY,ver,0,gid,platform,GType_PreAGI) - -#define GAME3_PS(id,name,fname,md5,size,ver,flags,gid,platform) GAME_LVFPN(id,name,fname,md5,size,Common::EN_ANY,ver,flags,gid,platform,GType_V3) - -#define FANMADE_ILVF(id,name,md5,lang,ver,features) GAME_LVFPNF(id,name,"logdir",md5,-1,lang,ver,(GF_FANMADE|features),GID_FANMADE,Common::kPlatformPC,GType_V2) - -#define FANMADE_ISVP(id,name,md5,size,ver,platform) GAME_LVFPNF(id,name,"logdir",md5,size,Common::EN_ANY,ver,GF_FANMADE,GID_FANMADE,platform,GType_V2) -#define FANMADE_SVP(name,md5,size,ver,platform) FANMADE_ISVP("agi-fanmade",name,md5,size,ver,platform) - -#define FANMADE_LVF(name,md5,lang,ver,features) FANMADE_ILVF("agi-fanmade",name,md5,lang,ver,features) - -#define FANMADE_LF(name,md5,lang,features) FANMADE_LVF(name,md5,lang,0x2917,features) -#define FANMADE_IF(id,name,md5,features) FANMADE_ILVF(id,name,md5,Common::EN_ANY,0x2917,features) - -#define FANMADE_V(name,md5,ver) FANMADE_LVF(name,md5,Common::EN_ANY,ver,0) -#define FANMADE_F(name,md5,features) FANMADE_LF(name,md5,Common::EN_ANY,features) -#define FANMADE_L(name,md5,lang) FANMADE_LF(name,md5,lang,0) -#define FANMADE_I(id,name,md5) FANMADE_IF(id,name,md5,0) - -#define FANMADE(name,md5) FANMADE_F(name,md5,0) - -static const AGIGameDescription gameDescriptions[] = { - - // AGI Demo 1 (PC) 05/87 [AGI 2.425] - GAME("agidemo", "Demo 1 1987-05-20", "9c4a5b09cc3564bc48b4766e679ea332", 0x2440, GID_AGIDEMO), - - // AGI Demo 2 (IIgs) 1.0C (Censored) - GAME_P("agidemo", "Demo 2 1987-11-24 1.0C", "580ffdc569ff158f56fb92761604f70e", 0x2917, GID_AGIDEMO, Common::kPlatformApple2GS), - - // AGI Demo 2 (PC 3.5") 11/87 [AGI 2.915] - GAME("agidemo", "Demo 2 1987-11-24 3.5\"", "e8ebeb0bbe978172fe166f91f51598c7", 0x2917, GID_AGIDEMO), - - // AGI Demo 2 (PC 5.25") 11/87 [v1] [AGI 2.915] - GAME("agidemo", "Demo 2 1987-11-24 [version 1] 5.25\"", "852ac303a374df62571642ca1e2d1f0a", 0x2917, GID_AGIDEMO), - - // AGI Demo 2 (PC 5.25") 01/88 [v2] [AGI 2.917] - GAME("agidemo", "Demo 2 1987-11-25 [version 2] 5.25\"", "1503f02086ea9f388e7e041c039eaa69", 0x2917, GID_AGIDEMO), - - // AGI Demo 3 (PC) 09/88 [AGI 3.002.102] - GAME3("agidemo", "Demo 3 1988-09-13", "dmdir", "289c7a2c881f1d973661e961ced77d74", 0x3149, GID_AGIDEMO), - - // AGI Demo for Kings Quest III and Space Quest I - GAME("agidemo", "Demo Kings Quest III and Space Quest I", "502e6bf96827b6c4d3e67c9cdccd1033", 0x2272, GID_AGIDEMO), - - // Black Cauldron (Amiga) 2.00 6/14/87 - GAME_P("bc", "2.00 1987-06-14", "7b01694af21213b4727bb94476f64eb5", 0x2440, GID_BC, Common::kPlatformAmiga), - - // Black Cauldron (Apple IIgs) 1.0O 2/24/89 (CE) - // Menus not tested - GAME3_P("bc", "1.0O 1989-02-24 (CE)", "bcdir", "dc09d30b147242692f4f85b9811962db", 0x3149, 0, GID_BC, Common::kPlatformApple2GS), - - // Black Cauldron (PC) 2.00 6/14/87 [AGI 2.439] - GAME("bc", "2.00 1987-06-14", "7f598d4712319b09d7bd5b3be10a2e4a", 0x2440, GID_BC), - - // Black Cauldron (Russian) - GAME_LPS("bc", "", "b7de782dfdf8ea7dde8064f09804bcf5", 357, Common::RU_RUS, 0x2440, GID_BC, Common::kPlatformPC), - - // Black Cauldron (PC 5.25") 2.10 11/10/88 [AGI 3.002.098] - GAME3("bc", "2.10 1988-11-10 5.25\"", "bcdir", "0c5a9acbcc7e51127c34818e75806df6", 0x3149, GID_BC), - - // Black Cauldron (PC) 2.10 [AGI 3.002.097] - GAME3("bc", "2.10", "bcdir", "0de3953c9225009dc91e5b0d1692967b", 0x3149, GID_BC), - - // Black Cauldron (CoCo3 360k) [AGI 2.023] - GAME_PS("bc", "", "51212c54808ade96176f201ae0ac7a6f", 357, 0x2440, GID_BC, Common::kPlatformCoCo3), - - // Black Cauldron (CoCo3 360k) [AGI 2.072] - GAME_PS("bc", "updated", "c4e1937f74e8100cd0152b904434d8b4", 357, 0x2440, GID_BC, Common::kPlatformCoCo3), - -// TODO -// These aren't supposed to work now as they require unsupported agi engine 2.01 -#if 0 - // Donald Duck's Playground (Amiga) 1.0C - // Menus not tested - GAME_P("ddp", "1.0C 1987-04-27", "550971d196f65190a5c760d2479406ef", 0x2272, GID_DDP, Common::kPlatformAmiga), - - // Donald Duck's Playground (ST) 1.0A 8/8/86 - // Menus not tested - GAME("ddp", "1.0A 1986-08-08", "64388812e25dbd75f7af1103bc348596", 0x2272, GID_DDP), - - // reported by Filippos (thebluegr) in bugreport #1654500 - // Menus not tested - GAME_PS("ddp", "1.0C 1986-06-09", "550971d196f65190a5c760d2479406ef", 132, 0x2272, GID_DDP, Common::kPlatformPC), -#endif - - // Gold Rush! (Amiga) 1.01 1/13/89 aka 2.05 3/9/89 # 2.316 - GAME3_PS("goldrush", "1.01 1989-01-13 aka 2.05 1989-03-09", "dirs", "a1d4de3e75c2688c1e2ca2634ffc3bd8", 2399, 0x3149, 0, GID_GOLDRUSH, Common::kPlatformAmiga), - - // Gold Rush! (Apple IIgs) 1.0M 2/28/89 (CE) aka 2.01 12/22/88 - // Menus not tested - GAME3_P("goldrush", "1.0M 1989-02-28 (CE) aka 2.01 1988-12-22", "grdir", "3f7b9ce62631434389f85371b11921d6", 0x3149, 0, GID_GOLDRUSH, Common::kPlatformApple2GS), - - // Gold Rush! (ST) 1.01 1/13/89 aka 2.01 12/22/88 - GAME3_P("goldrush", "1.01 1989-01-13 aka 2.01 1988-12-22", "grdir", "4dd4d50480a3d6c206fa227ce8142735", 0x3149, 0, GID_GOLDRUSH, Common::kPlatformAtariST), - - // Gold Rush! (PC 5.25") 2.01 12/22/88 [AGI 3.002.149] - GAME3("goldrush", "2.01 1988-12-22 5.25\"", "grdir", "db733d199238d4009a9e95f11ece34e9", 0x3149, GID_GOLDRUSH), - - // Gold Rush! (PC 3.5") 2.01 12/22/88 [AGI 3.002.149] - GAME3("goldrush", "2.01 1988-12-22 3.5\"", "grdir", "6a285235745f69b4b421403659497216", 0x3149, GID_GOLDRUSH), - - // Gold Rush! (PC 3.5", bought from The Software Farm) 3.0 1998-12-22 [AGI 3.002.149] - GAME3("goldrush", "3.0 1998-12-22 3.5\"", "grdir", "6882b6090473209da4cd78bb59f78dbe", 0x3149, GID_GOLDRUSH), - - { - // Gold Rush! (PC 5.25") 2.01 12/22/88 [AGI 3.002.149] - { - "goldrush", - "2.01 1988-12-22", - { - { "grdir", 0, "db733d199238d4009a9e95f11ece34e9", 2399}, - { "vol.0", 0, "4b6423d143674d3757ab1b875d25951d", 25070}, - { NULL, 0, NULL, 0} - }, - Common::EN_ANY, - Common::kPlatformMacintosh, - ADGF_NO_FLAGS, - GUIO_NONE - }, - GID_GOLDRUSH, - GType_V3, - GF_MACGOLDRUSH, - 0x3149, - }, - - - // Gold Rush! (CoCo3 720k) [AGI 2.023] - GAME_PS("goldrush", "", "0a41b65efc0cd6c4271e957e6ffbbd8e", 744, 0x2440, GID_GOLDRUSH, Common::kPlatformCoCo3), - - // Gold Rush! (CoCo3 360k/720k) [AGI 2.072] - GAME_PS("goldrush", "updated", "c49bf56bf91e31a4601a604e51ef8bfb", 744, 0x2440, GID_GOLDRUSH, Common::kPlatformCoCo3), - - // King's Quest 1 (Amiga) 1.0U # 2.082 - // The original game did not have menus, they are enabled under ScummVM - GAME_FP("kq1", "1.0U 1986", "246c695324f1c514aee2b904fa352fad", 0x2440, GF_MENUS, GID_KQ1, Common::kPlatformAmiga), - - // King's Quest 1 (ST) 1.0V - // The original game did not have menus, they are enabled under ScummVM - GAME_FP("kq1", "1.0V 1986", "c3a017e556c4b0eece366a4cd9abb657", 0x2272, GF_MENUS, GID_KQ1, Common::kPlatformAtariST), - - // King's Quest 1 (IIgs) 1.0S-88223 - // Menus not tested - GAME_P("kq1", "1.0S 1988-02-23", "f4277aa34b43d37382bc424c81627617", 0x2272, GID_KQ1, Common::kPlatformApple2GS), - - // King's Quest 1 (Mac) 2.0C - GAME_P("kq1", "2.0C 1987-03-26", "d4c4739d4ac63f7dbd29255425077d48", 0x2440, GID_KQ1, Common::kPlatformMacintosh), - - // King's Quest 1 (PC 5.25"/3.5") 2.0F [AGI 2.917] - GAME("kq1", "2.0F 1987-05-05 5.25\"/3.5\"", "10ad66e2ecbd66951534a50aedcd0128", 0x2917, GID_KQ1), - - // King's Quest 1 (CoCo3 360k) [AGI 2.023] - GAME_PS("kq1", "", "10ad66e2ecbd66951534a50aedcd0128", 315, 0x2440, GID_KQ1, Common::kPlatformCoCo3), - - // King's Quest 1 (CoCo3 360k) [AGI 2.023] - GAME_PS("kq1", "fixed", "4c8ef8b5d2f1b6c1a93e456d1f1ffc74", 768, 0x2440, GID_KQ1, Common::kPlatformCoCo3), - - // King's Quest 1 (CoCo3 360k) [AGI 2.072] - GAME_PS("kq1", "updated", "94087178c78933a4af3cd24d1c8dd7b2", 315, 0x2440, GID_KQ1, Common::kPlatformCoCo3), - - // King's Quest 2 (IIgs) 2.0A 6/16/88 (CE) - GAME_P("kq2", "2.0A 1988-06-16 (CE)", "5203c8b95250a2ecfee93ddb99414753", 0x2917, GID_KQ2, Common::kPlatformApple2GS), - - // King's Quest 2 (Amiga) 2.0J (Broken) - GAME_P("kq2", "2.0J 1987-01-29 [OBJECT decrypted]", "b866f0fab2fad91433a637a828cfa410", 0x2440, GID_KQ2, Common::kPlatformAmiga), - - // King's Quest 2 (Mac) 2.0R - GAME_P("kq2", "2.0R 1988-03-23", "cbdb0083317c8e7cfb7ac35da4bc7fdc", 0x2440, GID_KQ2, Common::kPlatformMacintosh), - - // King's Quest 2 (PC) 2.1 [AGI 2.411]; entry from DAGII, but missing from Sarien? - // XXX: any major differences from 2.411 to 2.440? - GAME("kq2", "2.1 1987-04-10", "759e39f891a0e1d86dd29d7de485c6ac", 0x2440, GID_KQ2), - - // King's Quest 2 (PC 5.25"/3.5") 2.2 [AGI 2.426] - GAME("kq2", "2.2 1987-05-07 5.25\"/3.5\"", "b944c4ff18fb8867362dc21cc688a283", 0x2917, GID_KQ2), - - // King's Quest 2 (Russian) - GAME_LPS("kq2", "", "35211c574ececebdc723b23e35f99275", 543, Common::RU_RUS, 0x2917, GID_KQ2, Common::kPlatformPC), - - // King's Quest 2 (CoCo3 360k) [AGI 2.023] - GAME_PS("kq2", "", "b944c4ff18fb8867362dc21cc688a283", 543, 0x2440, GID_KQ2, Common::kPlatformCoCo3), - - // King's Quest 2 (CoCo3 360k) [AGI 2.072] - GAME_PS("kq2", "updated", "f64a606de740a5348f3d125c03e989fe", 543, 0x2440, GID_KQ2, Common::kPlatformCoCo3), - - // King's Quest 2 (CoCo3 360k) [AGI 2.023] - GAME_PS("kq2", "fixed", "fb33ac2768a94a89117a270771db465c", 768, 0x2440, GID_KQ2, Common::kPlatformCoCo3), - - // King's Quest 3 (Amiga) 1.01 11/8/86 - // The original game did not have menus, they are enabled under ScummVM - GAME_FP("kq3", "1.01 1986-11-08", "8ab343306df0e2d98f136be4e8cfd0ef", 0x2440, GF_MENUS, GID_KQ3, Common::kPlatformAmiga), - - // King's Quest 3 (ST) 1.02 11/18/86 - // Does not have menus, crashes if menus are enforced. Therefore, ESC pauses the game - GAME_FP("kq3", "1.02 1986-11-18", "8846df2654302b623217ba8bd6d657a9", 0x2272, GF_ESCPAUSE, GID_KQ3, Common::kPlatformAtariST), - - // King's Quest 3 (Mac) 2.14 3/15/88 - GAME_P("kq3", "2.14 1988-03-15", "7639c0da5ce94848227d409351fabda2", 0x2440, GID_KQ3, Common::kPlatformMacintosh), - - // King's Quest 3 (IIgs) 2.0A 8/28/88 (CE) - GAME_P("kq3", "2.0A 1988-08-28 (CE)", "ac30b7ca5a089b5e642fbcdcbe872c12", 0x2917, GID_KQ3, Common::kPlatformApple2GS), - - // King's Quest 3 (Amiga) 2.15 11/15/89 # 2.333 - // Original pauses with ESC, has menus accessible with mouse. - // ver = 0x3086 -> menus accessible with ESC or mouse, bug #2835581 (KQ3: Game Crash When Leaving Tavern as Fly). - // ver = 0x3149 -> menus accessible with mouse, ESC pauses game, bug #2835581 disappears. - GAME3_PS("kq3", "2.15 1989-11-15", "dirs", "8e35bded2bc5cf20f5eec2b15523b155", 1805, 0x3149, 0, GID_KQ3, Common::kPlatformAmiga), - - // King's Quest 3 (PC) 1.01 11/08/86 [AGI 2.272] - // Does not have menus, crashes if menus are enforced. Therefore, ESC pauses the game - GAME_FP("kq3", "1.01 1986-11-08", "9c2b34e7ffaa89c8e2ecfeb3695d444b", 0x2272, GF_ESCPAUSE, GID_KQ3, Common::kPlatformPC), - - // King's Quest 3 (Russian) - GAME_LFPS("kq3", "", "5856dec6ccb9c4b70aee21044a19270a", 390, Common::RU_RUS, 0x2272, GF_ESCPAUSE, GID_KQ3, Common::kPlatformPC), - - // King's Quest 3 (PC 5.25") 2.00 5/25/87 [AGI 2.435] - GAME("kq3", "2.00 1987-05-25 5.25\"", "18aad8f7acaaff760720c5c6885b6bab", 0x2440, GID_KQ3), - - // King's Quest 3 (Mac) 2.14 3/15/88 - // Menus not tested - GAME_P("kq3", "2.14 1988-03-15 5.25\"", "7650e659c7bc0f1e9f8a410b7a2e9de6", 0x2440, GID_KQ3, Common::kPlatformMacintosh), - - // King's Quest 3 (PC 3.5") 2.14 3/15/88 [AGI 2.936] - GAME("kq3", "2.14 1988-03-15 3.5\"", "d3d17b77b3b3cd13246749231d9473cd", 0x2936, GID_KQ3), - - // King's Quest 3 (CoCo3 158k/360k) [AGI 2.023] - GAME_PS("kq3", "", "5a6be7d16b1c742c369ef5cc64fefdd2", 429, 0x2440, GID_KQ3, Common::kPlatformCoCo3), - - // King's Quest 4 (PC 5.25") 2.0 7/27/88 [AGI 3.002.086] - GAME3("kq4", "2.0 1988-07-27", "kq4dir", "f50f7f997208ca0e35b2650baec43a2d", 0x3086, GID_KQ4), - - // King's Quest 4 (PC 3.5") 2.0 7/27/88 [AGI 3.002.086] - GAME3("kq4", "2.0 1988-07-27 3.5\"", "kq4dir", "fe44655c42f16c6f81046fdf169b6337", 0x3086, GID_KQ4), - - // King's Quest 4 (PC 3.5") 2.2 9/27/88 [AGI 3.002.086] - // Menus not tested - GAME3("kq4", "2.2 1988-09-27 3.5\"", "kq4dir", "7470b3aeb49d867541fc66cc8454fb7d", 0x3086, GID_KQ4), - - // King's Quest 4 (PC 5.25") 2.3 9/27/88 [AGI 3.002.086] - GAME3("kq4", "2.3 1988-09-27", "kq4dir", "6d7714b8b61466a5f5981242b993498f", 0x3086, GID_KQ4), - - // King's Quest 4 (PC 3.5") 2.3 9/27/88 [AGI 3.002.086] - GAME3("kq4", "2.3 1988-09-27 3.5\"", "kq4dir", "82a0d39af891042e99ac1bd6e0b29046", 0x3086, GID_KQ4), - - // King's Quest 4 (IIgs) 1.0K 11/22/88 (CE) - // Menus not tested - GAME3_P("kq4", "1.0K 1988-11-22", "kq4dir", "8536859331159f15012e35dc82cb154e", 0x3086, 0, GID_KQ4, Common::kPlatformApple2GS), - - // King's Quest 4 demo (PC) [AGI 3.002.102] - // Menus not tested - GAME3("kq4", "Demo 1988-12-20", "dmdir", "a3332d70170a878469d870b14863d0bf", 0x3149, GID_KQ4), - - // King's Quest 4 (CoCo3 720k) [AGI 2.023] - GAME_PS("kq4", "", "9e7729a28e749ca241d2bf71b9b2dbde", 741, 0x2440, GID_KQ4, Common::kPlatformCoCo3), - - // King's Quest 4 (CoCo3 360k/720k) [AGI 2.072] - GAME_PS("kq4", "updated", "1959ca10739edb34069bb504dbd74805", 741, 0x2440, GID_KQ4, Common::kPlatformCoCo3), - - // Leisure Suit Larry 1 (PC 5.25"/3.5") 1.00 6/1/87 [AGI 2.440] - GAME("lsl1", "1.00 1987-06-01 5.25\"/3.5\"", "1fe764e66857e7f305a5f03ca3f4971d", 0x2440, GID_LSL1), - - // Leisure Suit Larry 1 Polish - GAME_LPS("lsl1", "2.00 2001-12-11", "7ba1fccc46d27c141e704706c1d0a85f", 303, Common::PL_POL, 0x2440, GID_LSL1, Common::kPlatformPC), - - // Leisure Suit Larry 1 Polish - Demo - GAME_LPS("lsl1", "Demo", "3b2f564306c401dff6334441df967ddd", 666, Common::PL_POL, 0x2917, GID_LSL1, Common::kPlatformPC), - - // Leisure Suit Larry 1 (ST) 1.04 6/18/87 - GAME_P("lsl1", "1.04 1987-06-18", "8b579f8673fe9448c2538f5ed9887cf0", 0x2440, GID_LSL1, Common::kPlatformAtariST), - - // Leisure Suit Larry 1 (Amiga) 1.05 6/26/87 # x.yyy - GAME_P("lsl1", "1.05 1987-06-26", "3f5d26d8834ca49c147fb60936869d56", 0x2440, GID_LSL1, Common::kPlatformAmiga), - - // Leisure Suit Larry 1 (IIgs) 1.0E - GAME_P("lsl1", "1.0E 1987", "5f9e1dd68d626c6d303131c119582ad4", 0x2440, GID_LSL1, Common::kPlatformApple2GS), - - // Leisure Suit Larry 1 (Mac) 1.05 6/26/87 - GAME_P("lsl1", "1.05 1987-06-26", "8a0076429890531832f0dc113285e31e", 0x2440, GID_LSL1, Common::kPlatformMacintosh), - - // Leisure Suit Larry 1 (CoCo3 158k/360k) [AGI 2.072] - GAME_PS("lsl1", "", "a2de1fe76565c3e8b40c9d036b5e5612", 198, 0x2440, GID_LSL1, Common::kPlatformCoCo3), - - // Manhunter NY (ST) 1.03 10/20/88 - GAME3_P("mh1", "1.03 1988-10-20", "mhdir", "f2d58056ad802452d60776ee920a52a6", 0x3149, 0, GID_MH1, Common::kPlatformAtariST), - - // Manhunter NY (IIgs) 2.0E 10/05/88 (CE) - GAME3_P("mh1", "2.0E 1988-10-05 (CE)", "mhdir", "2f1509f76f24e6e7d213f2dadebbf156", 0x3149, 0, GID_MH1, Common::kPlatformApple2GS), - - // Manhunter NY (Amiga) 1.06 3/18/89 - GAME3_P("mh1", "1.06 1989-03-18", "dirs", "92c6183042d1c2bb76236236a7d7a847", 0x3149, GF_OLDAMIGAV20, GID_MH1, Common::kPlatformAmiga), - - // reported by Filippos (thebluegr) in bugreport #1654500 - // Manhunter NY (PC 5.25") 1.22 8/31/88 [AGI 3.002.107] - GAME3_PS("mh1", "1.22 1988-08-31", "mhdir", "0c7b86f05fe02c2e26cff1b07450b82a", 2123, 0x3149, 0, GID_MH1, Common::kPlatformPC), - - // Manhunter NY (PC 3.5") 1.22 8/31/88 [AGI 3.002.102] - GAME3_PS("mh1", "1.22 1988-08-31", "mhdir", "5b625329021ad49fd0c1d6f2d6f54bba", 2141, 0x3149, 0, GID_MH1, Common::kPlatformPC), - - // Manhunter NY (CoCo3 720k) [AGI 2.023] - GAME_PS("mh1", "", "b968285caf2f591c78dd9c9e26ab8974", 495, 0x2440, GID_MH1, Common::kPlatformCoCo3), - - // Manhunter NY (CoCo3 360k/720k) [AGI 2.072] - GAME_PS("mh1", "updated", "d47da950c62289f8d4ccf36af73365f2", 495, 0x2440, GID_MH1, Common::kPlatformCoCo3), - - // Manhunter SF (ST) 1.0 7/29/89 - GAME3_P("mh2", "1.0 1989-07-29", "mh2dir", "5e3581495708b952fea24438a6c7e040", 0x3149, 0, GID_MH1, Common::kPlatformAtariST), - - // Manhunter SF (Amiga) 3.06 8/17/89 # 2.333 - GAME3_PS("mh2", "3.06 1989-08-17", "dirs", "b412e8a126368b76696696f7632d4c16", 2573, 0x3086, GF_OLDAMIGAV20, GID_MH2, Common::kPlatformAmiga), - - // Manhunter SF (PC 5.25") 3.03 8/17/89 [AGI 3.002.149] - GAME3("mh2", "3.03 1989-08-17 5.25\"", "mh2dir", "b90e4795413c43de469a715fb3c1fa93", 0x3149, GID_MH2), - - // Manhunter SF (PC 3.5") 3.02 7/26/89 [AGI 3.002.149] - GAME3("mh2", "3.02 1989-07-26 3.5\"", "mh2dir", "6fb6f0ee2437704c409cf17e081ba152", 0x3149, GID_MH2), - - // Manhunter SF (CoCo3 720k) [AGI 2.023] - GAME_PS("mh2", "", "acaaa577e10d1753c5a74f6ae1d858d4", 591, 0x2440, GID_MH2, Common::kPlatformCoCo3), - - // Manhunter SF (CoCo3 720k) [AGI 2.072] - GAME_PS("mh2", "updated", "c64875766700196e72a92359f70f45a9", 591, 0x2440, GID_MH2, Common::kPlatformCoCo3), - - // Mickey's Space Adventure - // Preagi game - GAMEpre_P("mickey", "", "1.pic", "b6ec04c91a05df374792872c4d4ce66d", 0x0000, GID_MICKEY, Common::kPlatformPC), - -#if 0 - // Mixed-Up Mother Goose (Amiga) 1.1 - // Problematic: crashes - // Menus not tested - GAME3_PS("mixedup", "1.1 1986-12-10", "dirs", "5c1295fe6daaf95831195ba12894dbd9", 2021, 0x3086, 0, GID_MIXEDUP, Common::kPlatformAmiga), -#endif - - // Mixed Up Mother Goose (IIgs) - GAME_P("mixedup", "1987", "3541954a7303467c6df87665312ffb6a", 0x2917, GID_MIXEDUP, Common::kPlatformApple2GS), - - // Mixed-Up Mother Goose (PC) [AGI 2.915] - GAME("mixedup", "1987-11-10", "e524655abf9b96a3b179ffcd1d0f79af", 0x2917, GID_MIXEDUP), - - // Mixed-Up Mother Goose (CoCo3 360k) [AGI 2.072] - GAME_PS("mixedup", "", "44e63e9b4d4822a31edea0e8a7e7eac4", 606, 0x2440, GID_MIXEDUP, Common::kPlatformCoCo3), - - // Police Quest 1 (PC) 2.0E 11/17/87 [AGI 2.915] - GAME("pq1", "2.0E 1987-11-17", "2fd992a92df6ab0461d5a2cd83c72139", 0x2917, GID_PQ1), - - // Police Quest 1 (Mac) 2.0G 12/3/87 - GAME_P("pq1", "2.0G 1987-12-03", "805750b66c1c5b88a214e67bfdca17a1", 0x2440, GID_PQ1, Common::kPlatformMacintosh), - - // Police Quest 1 (IIgs) 2.0B-88421 - GAME_P("pq1", "2.0B 1988-04-21", "e7c175918372336461e3811d594f482f", 0x2917, GID_PQ1, Common::kPlatformApple2GS), - - // Police Quest 1 (Amiga) 2.0B 2/22/89 # 2.310 - GAME3_PS("pq1", "2.0B 1989-02-22", "dirs", "cfa93e5f2aa7378bddd10ad6746a2ffb", 1613, 0x3149, 0, GID_PQ1, Common::kPlatformAmiga), - - // Police Quest 1 (IIgs) 2.0A-88318 - GAME_P("pq1", "2.0A 1988-03-18", "8994e39d0901de3d07cecfb954075bb5", 0x2917, GID_PQ1, Common::kPlatformApple2GS), - - // Police Quest 1 (PC) 2.0A 10/23/87 [AGI 2.903/2.911] - GAME("pq1", "2.0A 1987-10-23", "b9dbb305092851da5e34d6a9f00240b1", 0x2917, GID_PQ1), - - // Police Quest 1 (Russian) - GAME_LPS("pq1", "", "604cc8041d24c4c7e5fa8baf386ef76e", 360, Common::RU_RUS, 0x2917, GID_PQ1, Common::kPlatformPC), - - // Police Quest 1 2.0G 12/3/87 - GAME("pq1", "2.0G 1987-12-03 5.25\"/ST", "231f3e28170d6e982fc0ced4c98c5c1c", 0x2440, GID_PQ1), - - // Police Quest 1 (PC) 2.0G 12/3/87; entry from DAGII, but missing from Sarien? - // not sure about disk format -- dsymonds - GAME("pq1", "2.0G 1987-12-03", "d194e5d88363095f55d5096b8e32fbbb", 0x2917, GID_PQ1), - - // Police Quest 1 (CoCo3 360k) [AGI 2.023] - GAME_PS("pq1", "", "28a077041f75aab78f66804800940085", 375, 0x2440, GID_PQ1, Common::kPlatformCoCo3), - - // Police Quest 1 (CoCo3 360k) [AGI 2.072] - GAME_PS("pq1", "updated", "63b9a9c6eec154751dd446cd3693e0e2", 768, 0x2440, GID_PQ1, Common::kPlatformCoCo3), - - // Space Quest 1 (ST) 1.1A - // The original game did not have menus, they are enabled under ScummVM - GAME_FP("sq1", "1.1A 1986-02-06", "6421fb64b0e6604c9dd065975d9279e9", 0x2440, GF_MENUS, GID_SQ1, Common::kPlatformAtariST), - - // Space Quest 1 (PC 360k) 1.1A [AGI 2.272] - // The original game did not have menus, they are enabled under ScummVM - GAME_FP("sq1", "1.1A 1986-11-13", "8d8c20ab9f4b6e4817698637174a1cb6", 0x2272, GF_MENUS, GID_SQ1, Common::kPlatformPC), - - // Space Quest 1 (PC 720k) 1.1A [AGI 2.272] - // The original game did not have menus, they are enabled under ScummVM - GAME_FP("sq1", "1.1A 720kb", "0a92b1be7daf3bb98caad3f849868aeb", 0x2272, GF_MENUS, GID_SQ1, Common::kPlatformPC), - - // Space Quest 1 (Amiga) 1.2 # 2.082 - // The original game did not have menus, they are enabled under ScummVM - GAME_FP("sq1", "1.2 1986", "0b216d931e95750f1f4837d6a4b821e5", 0x2440, GF_MENUS | GF_OLDAMIGAV20, GID_SQ1, Common::kPlatformAmiga), - - // Space Quest 1 (Mac) 1.5D - GAME_P("sq1", "1.5D 1987-04-02", "ce88419aadd073d1c6682d859b3d8aa2", 0x2440, GID_SQ1, Common::kPlatformMacintosh), - - // Space Quest 1 (IIgs) 2.2 - GAME_P("sq1", "2.2 1987", "64b9b3d04c1066d36e6a6e56187a83f7", 0x2917, GID_SQ1, Common::kPlatformApple2GS), - - // Space Quest 1 (PC) 1.0X [AGI 2.089] - // Does not have menus, crashes if menus are enforced. Therefore, ESC pauses the game - GAME_FP("sq1", "1.0X 1986-09-24", "af93941b6c51460790a9efa0e8cb7122", 0x2089, GF_ESCPAUSE, GID_SQ1, Common::kPlatformPC), - - // Space Quest 1 (Russian) - GAME_LFPS("sq1", "", "a279eb8ddbdefdb1ea6adc827a1d632a", 372, Common::RU_RUS, 0x2089, GF_ESCPAUSE, GID_SQ1, Common::kPlatformPC), - - // Space Quest 1 (PC 5.25"/3.5") 2.2 [AGI 2.426/2.917] - GAME("sq1", "2.2 1987-05-07 5.25\"/3.5\"", "5d67630aba008ec5f7f9a6d0a00582f4", 0x2440, GID_SQ1), - - // Space Quest 1 (CoCo3 360k) [AGI 2.072] - GAME_PS("sq1", "", "5d67630aba008ec5f7f9a6d0a00582f4", 372, 0x2440, GID_SQ1, Common::kPlatformCoCo3), - - // Space Quest 1 (CoCo3 360k) [AGI 2.023] - GAME_PS("sq1", "fixed", "ca822b768b6462e410423ea7f498daee", 768, 0x2440, GID_SQ1, Common::kPlatformCoCo3), - - // Space Quest 1 (CoCo3 360k) [AGI 2.072] - GAME_PS("sq1", "updated", "7fa54e6bb7ffeb4cf20eca39d86f5fb2", 387, 0x2440, GID_SQ1, Common::kPlatformCoCo3), - - // Space Quest 2 (PC 3.5") 2.0D [AGI 2.936] - GAME("sq2", "2.0D 1988-03-14 3.5\"", "85390bde8958c39830e1adbe9fff87f3", 0x2936, GID_SQ2), - - // Space Quest 2 (IIgs) 2.0A 7/25/88 (CE) - GAME_P("sq2", "2.0A 1988-07-25 (CE)", "5dfdac98dd3c01fcfb166529f917e911", 0x2936, GID_SQ2, Common::kPlatformApple2GS), - - { - // Space Quest 2 (Amiga) 2.0F - { - "sq2", - "2.0F 1986-12-09 [VOL.2->PICTURE.16 broken]", - { - { "logdir", 0, "28add5125484302d213911df60d2aded", 426}, - { "object", 0, "5dc52be721257719f4b311a84ce22b16", 372}, - { NULL, 0, NULL, 0} - }, - Common::EN_ANY, - Common::kPlatformAmiga, - ADGF_NO_FLAGS, - GUIO_NONE - }, - GID_SQ2, - GType_V2, - 0, - 0x2936, - }, - - - // Space Quest 2 (Mac) 2.0D - GAME_P("sq2", "2.0D 1988-04-04", "bfbebe0b59d83f931f2e1c62ce9484a7", 0x2936, GID_SQ2, Common::kPlatformMacintosh), - - // reported by Filippos (thebluegr) in bugreport #1654500 - // Space Quest 2 (PC 5.25") 2.0A [AGI 2.912] - GAME_PS("sq2", "2.0A 1987-11-06 5.25\"", "ad7ce8f800581ecc536f3e8021d7a74d", 423, 0x2917, GID_SQ2, Common::kPlatformPC), - - // Space Quest 2 (Russian) - GAME_LPS("sq2", "", "ba21c8934caf28e3ba45ce7d1cd6b041", 423, Common::RU_RUS, 0x2917, GID_SQ2, Common::kPlatformPC), - - // Space Quest 2 (PC 3.5") 2.0A [AGI 2.912] - GAME_PS("sq2", "2.0A 1987-11-06 3.5\"", "6c25e33d23b8bed42a5c7fa63d588e5c", 423, 0x2917, GID_SQ2, Common::kPlatformPC), - - // Space Quest 2 (PC 5.25"/ST) 2.0C/A [AGI 2.915] - // Menus not tested - GAME("sq2", "2.0C/A 5.25\"/ST", "bd71fe54869e86945041700f1804a651", 0x2917, GID_SQ2), - - // Space Quest 2 (PC 3.5") 2.0F [AGI 2.936] - GAME("sq2", "2.0F 1989-01-05 3.5\"", "28add5125484302d213911df60d2aded", 0x2936, GID_SQ2), - - // Space Quest 2 (CoCo3 360k) [AGI 2.023] - GAME_PS("sq2", "", "12973d39b892dc9d280257fd271e9597", 768, 0x2440, GID_SQ2, Common::kPlatformCoCo3), - - // Space Quest 2 (CoCo3 360k) [AGI 2.072] - GAME_PS("sq2", "updated", "d24f19b047e65e1763eff4b46f3d50df", 768, 0x2440, GID_SQ2, Common::kPlatformCoCo3), - - // Troll's Tale - GAMEpre_PS("troll", "", "troll.img", "62903f264b3d849be4214b3a5c42a2fa", 184320, 0x0000, GID_TROLL, Common::kPlatformPC), - - // Winnie the Pooh in the Hundred Acre Wood - GAMEpre_P("winnie", "", "title.pic", "2e7900c1ccaa7671d65405f6d1efed30", 0x0000, GID_WINNIE, Common::kPlatformPC), - - // Winnie the Pooh in the Hundred Acre Wood (Amiga) - GAMEpre_P("winnie", "", "title", "2e7900c1ccaa7671d65405f6d1efed30", 0x0000, GID_WINNIE, Common::kPlatformAmiga), - - // Winnie the Pooh in the Hundred Acre Wood (C64) - GAMEpre_P("winnie", "", "title.pic", "d4eb97cffc866110f71e1ec9f84fe643", 0x0000, GID_WINNIE, Common::kPlatformC64), - - // Winnie the Pooh in the Hundred Acre Wood (Apple //gs) - GAMEpre_P("winnie", "", "title.pic", "45e06010a3c61d78f4661103c901ae11", 0x0000, GID_WINNIE, Common::kPlatformApple2GS), - - // Xmas Card 1986 (PC) [AGI 2.272] - GAME("xmascard", "1986-11-13 [version 1]", "3067b8d5957e2861e069c3c0011bd43d", 0x2272, GID_XMASCARD), - - // Xmas Card 1986 (CoCo3 360k) [AGI 2.072] - GAME_PS("xmascard", "", "25ad35e9628fc77e5e0dd35852a272b6", 768, 0x2440, GID_XMASCARD, Common::kPlatformCoCo3), - - FANMADE_F("2 Player Demo", "4279f46b3cebd855132496476b1d2cca", GF_AGIMOUSE), - FANMADE("AGI Contest 1 Template", "d879aed25da6fc655564b29567358ae2"), - FANMADE("AGI Contest 2 Template", "5a2fb2894207eff36c72f5c1b08bcc07"), - FANMADE("AGI Mouse Demo 0.60 demo 1", "c07e2519de674c67386cb2cc6f2e3904"), - FANMADE("AGI Mouse Demo 0.60 demo 2", "cc49d8b88ed6faf4f53ce92c84e0fe1b"), - FANMADE("AGI Mouse Demo 0.70", "3497c291e4afb6f758e61740678a2aec"), - FANMADE_F("AGI Mouse Demo 1.00", "20397f0bf0ef936f416bb321fb768fc7", GF_AGIMOUSE), - FANMADE_F("AGI Mouse Demo 1.10", "f4ad396b496d6167635ad0b410312ab8", GF_AGIMOUSE|GF_AGIPAL), - FANMADE("AGI Piano (v1.0)", "8778b3d89eb93c1d50a70ef06ef10310"), - FANMADE("AGI Quest (v1.46-TJ0)", "1cf1a5307c1a0a405f5039354f679814"), - FANMADE_I("tetris", "", "7a874e2db2162e7a4ce31c9130248d8a"), - FANMADE_V("AGI Trek (Demo)", "c02882b8a8245b629c91caf7eb78eafe", 0x2440), - FANMADE_F("AGI256 Demo", "79261ac143b2e2773b2753674733b0d5", GF_AGI256), - FANMADE_F("AGI256-2 Demo", "3cad9b3aff1467cebf0c5c5b110985c5", GF_AGI256_2), - FANMADE_LF("Abrah: L'orphelin de l'espace (v1.2)", "b7b6d1539e14d5a26fa3088288e1badc", Common::FR_FRA, GF_AGIPAL), - FANMADE("Acidopolis", "7017db1a4b726d0d59e65e9020f7d9f7"), - FANMADE("Agent 0055 (v1.0)", "c2b34a0c77acb05482781dda32895f24"), - FANMADE("Agent 06 vs. The Super Nazi", "136f89ca9f117c617e88a85119777529"), - FANMADE("Agent Quest", "59e49e8f72058a33c00d60ee1097e631"), - FANMADE("Al Pond - On Holiday (v1.0)", "a84975496b42d485920e886e92eed68b"), - FANMADE("Al Pond - On Holiday (v1.1)", "7c95ac4689d0c3bfec61e935f3093634"), - FANMADE("Al Pond - On Holiday (v1.3)", "8f30c260de9e1dd3d8b8f89cc19d2633"), - FANMADE("Al Pond 1 - Al Lives Forever (v1.0)", "e8921c3043b749b056ff51f56d1b451b"), - FANMADE("Al Pond 1 - Al Lives Forever (v1.3)", "fb4699474054962e0dbfb4cf12ca52f6"), - FANMADE("Apocalyptic Quest (v0.03 Teaser)", "42ced528b67965d3bc3b52c635f94a57"), - FANMADE_F("Apocalyptic Quest (v4.00 Alpha 1)", "e15581628d84949b8d352d224ec3184b", GF_AGIMOUSE), - FANMADE_F("Apocalyptic Quest (v4.00 Alpha 2)", "0eee850005860e46345b38fea093d194", GF_AGIMOUSE), - FANMADE_F("Band Quest (Demo)", "7326abefd793571cc17ed0db647bdf34", GF_AGIMOUSE), - FANMADE_F("Band Quest (Early Demo)", "de4758dd34676b248c8301b32d93bc6f", GF_AGIMOUSE), - FANMADE("Beyond the Titanic 2", "9b8de38dc64ffb3f52b7877ea3ebcef9"), - FANMADE("Biri Quest 1", "1b08f34f2c43e626c775c9d6649e2f17"), - FANMADE("Bob The Farmboy", "e4b7df9d0830addee5af946d380e66d7"), - FANMADE_F("Boring Man 1: The Toad to Robinland", "d74481cbd227f67ace37ce6a5493039f", GF_AGIMOUSE), - FANMADE_F("Boring Man 2: Ho Man! This Game Sucks!", "250032ba105bdf7c1bc4fed767c2d37e", GF_AGIMOUSE), - FANMADE("Botz", "a8fabe4e807adfe5ec02bfec6d983695"), - FANMADE("Brian's Quest (v1.0)", "0964aa79b9cdcff7f33a12b1d7e04b9c"), - FANMADE("CPU-21 (v1.0)", "35b7cdb4d17e890e4c52018d96e9cbf4"), - FANMADE_I("caitlyn", "Demo", "5b8a3cdb2fc05469f8119d49f50fbe98"), - FANMADE_I("caitlyn", "", "818469c484cae6dad6f0e9a353f68bf8"), - FANMADE("Car Driver (v1.1)", "2311611d2d36d20ccc9da806e6cba157"), - FANMADE("Cloak of Darkness (v1.0)", "5ba6e18bf0b53be10db8f2f3831ee3e5"), - FANMADE("Coco Coq (English) - Coco Coq In Grostesteing's Base (v.1.0.3)", "97631f8e710544a58bd6da9e780f9320"), - FANMADE_L("Coco Coq (French) - Coco Coq Dans la Base de Grostesteing (v1.0.2)", "ef579ebccfe5e356f9a557eb3b2d8649", Common::FR_FRA), - FANMADE("Corby's Murder Mystery (v1.0)", "4ebe62ac24c5a8c7b7898c8eb070efe5"), - FANMADE_F("DG: The AGIMouse Adventure (English v1.1)", "efe453b92bc1487ea69fbebede4d5f26", GF_AGIMOUSE|GF_AGIPAL), - FANMADE_LF("DG: The AGIMouse Adventure (French v1.1)", "eb3d17ca466d672cbb95947e8d6e846a", Common::FR_FRA, GF_AGIMOUSE|GF_AGIPAL), - FANMADE("DG: The Adventure Game (English v1.1)", "0d6376d493fa7a21ec4da1a063e12b25"), - FANMADE_L("DG: The Adventure Game (French v1.1)", "258bdb3bb8e61c92b71f2f456cc69e23", Common::FR_FRA), - FANMADE("Dashiki (16 Colors)", "9b2c7b9b0283ab9f12bedc0cb6770a07"), - FANMADE_F("Dashiki (256 Colors)", "c68052bb209e23b39b55ff3d759958e6", GF_AGIMOUSE|GF_AGI256), - FANMADE("Date Quest 1 (v1.0)", "ba3dcb2600645be53a13170aa1a12e69"), - FANMADE("Date Quest 2 (v1.0 Demo)", "1602d6a2874856e928d9a8c8d2d166e9"), - FANMADE("Date Quest 2 (v1.0)", "f13f6fc85aa3e6e02b0c20408fb63b47"), - FANMADE("Dave's Quest (v0.07)", "f29c3660de37bacc1d23547a167f27c9"), - FANMADE("Dave's Quest (v0.17)", "da3772624cc4a86f7137db812f6d7c39"), - FANMADE("Disco Nights (Demo)", "dc5a2b21182ba38bdcd992a3a978e690"), - FANMADE("Dogs Quest - The Quest for the Golden Bone (v1.0)", "f197357edaaea0ff70880602d2f09b3e"), - FANMADE("Dr. Jummybummy's Space Adventure", "988bd81785f8a452440a2a8ac67f96aa"), - FANMADE("Ed Ward", "98be839b9f30cbedea4c9cee5442d827"), - FANMADE("Elfintard", "c3b847e9e9e978af9708df76a0751dc2"), - FANMADE("Enclosure (v1.01)", "f08e66fee9ecdde77db7ee9a10c96ba2"), - FANMADE("Enclosure (v1.03)", "e4a0613ed02401502e506ba3565a8c40"), - FANMADE_SVP("Enclosure", "fe98e6126db74c6cc6fd8fe395cc6e8c", 345, 0x2440, Common::kPlatformCoCo3), - FANMADE("Epic Fighting (v0.1)", "aff24a1b3bdd676187685c4d95ba4294"), - FANMADE("Escape Quest (v0.0.3)", "2346b65619b1da0298b715b06d1a45a1"), - FANMADE("Escape from the Desert (beta 1)", "dfdc634d340854bd6ece28024010758d"), - FANMADE("Escape from the Salesman", "e723ca4fe0f6f56affe039fbb4dbeb6c"), - FANMADE("Fu$k Quest 1 (final)", "1cd0587422313f6ca77d6a95988e88ed"), - FANMADE("Fu$k Quest 1", "1cd0587422313f6ca77d6a95988e88ed"), - FANMADE("Fu$k Quest 2 - Romancing the Bone (Teaser)", "d288355d71d9bb1639260ccaa3b2fbfe"), - FANMADE("Fu$k Quest 2 - Romancing the Bone", "294beeb7765c7ea6b05ed7b9bf7bff4f"), - FANMADE("Gennadi Tahab Autot - Mission Pack 1 - Kuressaare", "bfa5fe71978e6ccf3d4eedd430124015"), - FANMADE("Go West, Young Hippie", "ff31484ea465441cb5f3a0f8e956b716"), - FANMADE("Good Man (demo v3.41)", "3facd8a8f856b7b6e0f6c3200274d88c"), - - { - // Groza - { - "agi-fanmade", - "Groza (russian) [AGDS sample]", - AD_ENTRY1("logdir", "421da3a18004122a966d64ab6bd86d2e"), - Common::RU_RUS, - Common::kPlatformPC, - ADGF_USEEXTRAASTITLE, - GUIO_NONE - }, - GID_FANMADE, - GType_V2, - GF_AGDS, - 0x2440, - }, - - { - // Get Outta SQ - { - "agi-fanmade", - "Get Outta Space Quest", - AD_ENTRY1("logdir", "aaea5b4a348acb669d13b0e6f22d4dc9"), - Common::EN_ANY, - Common::kPlatformPC, - ADGF_USEEXTRAASTITLE, - GUIO_NONE - }, - GID_GETOUTTASQ, - GType_V2, - 0, - 0x2440, - }, - - FANMADE_F("Half-Death - Terror At White-Mesa", "b62c05d0ace878261392073f57ae788c", GF_AGIMOUSE), - FANMADE("Hank's Quest (v1.0 English) - Victim of Society", "64c15b3d0483d17888129100dc5af213"), - FANMADE("Hank's Quest (v1.1 English) - Victim of Society", "86d1f1dd9b0c4858d096e2a60cca8a14"), - FANMADE_L("Hank's Quest (v1.81 Dutch) - Slachtoffer Van Het Gebeuren", "41e53972d55ff3dff9e90d15fe1b659f", Common::NL_NLD), - FANMADE("Hank's Quest (v1.81 English) - Victim of Society", "7a776383282f62a57c3a960dafca62d1"), - FANMADE("Herbao (v0.2)", "6a5186fc8383a9060517403e85214fc2"), - FANMADE_F("Hitler's Legacy (v.0004q)", "a412881269ba34584bd0a3268e5a9863", GF_AGIMOUSE), - FANMADE("Hobbits", "4a1c1ef3a7901baf0ab45fde0cfadd89"), - FANMADE_F("Isabella Coq - A Present For My Dad", "55c6819f2330c4d5d6459874c9f123d9", GF_AGIMOUSE), - FANMADE("Jack & Julia - VAMPYR", "8aa0b9a26f8d5a4421067ab8cc3706f6"), - FANMADE("Jeff's Quest (v.5 alpha Jun 1)", "10f1720eed40c12b02a0f32df3e72ded"), - FANMADE("Jeff's Quest (v.5 alpha May 31)", "51ff71c0ed90db4e987a488ed3bf0551"), - FANMADE("Jen's Quest (Demo 1)", "361afb5bdb6160213a1857245e711939"), - FANMADE("Jen's Quest (Demo 2)", "3c321eee33013b289ab8775449df7df2"), - FANMADE("Jiggy Jiggy Uh! Uh!", "bc331588a71e7a1c8840f6cc9b9487e4"), - FANMADE("Jimmy In: The Alien Attack (v0.1)", "a4e9db0564a494728de7873684a4307c"), - FANMADE("Joe McMuffin In \"What's Cooking, Doc\" (v1.0)", "8a3de7e61a99cb605fa6d233dd91c8e1"), - FANMADE_LVF("Jolimie, le Village Maudit (v0.5)", "21818501636b3cb8ad5de5c1a66de5c2", Common::FR_FRA, 0x2936, GF_AGIMOUSE|GF_AGIPAL), - FANMADE_LVF("Jolimie, le Village Maudit (v1.1)", "68d7aef1161bb5972fe03efdf29ccb7f", Common::FR_FRA, 0x2936, GF_AGIMOUSE|GF_AGIPAL), - FANMADE("Journey Of Chef", "aa0a0b5a6364801ae65fdb96d6741df5"), - FANMADE("Jukebox (v1.0)", "c4b9c5528cc67f6ba777033830de7751"), - FANMADE("Justin Quest (v1.0 in development)", "103050989da7e0ffdc1c5e1793a4e1ec"), - FANMADE("J\xf5ulumaa (v0.05) (Estonian)", "53982ecbfb907e41392b3961ad1c3475"), - FANMADE("Kings Quest 2 - Breast Intentions (v2.0 Mar 26)", "a25d7379d281b1b296d4785df90a8e78"), - FANMADE("Kings Quest 2 - Breast Intentions (v2.0 Aug 16)", "6b4f796d0421d2e12e501b511962e03a"), - FANMADE("Lasse Holm: The Quest for Revenge (v1.0)", "f9fbcc8a4ef510bfbb92423296ff4abb"), - FANMADE("Lawman for Hire", "c78b28bfd3767dd455b992cd8b7854fa"), - FANMADE("Lefty Goes on Vacation (Not in The Right Place)", "ccdc49a33870310b01f2c48b8a1f3c34"), - FANMADE("Les Ins\xe3parables (v1.0)", "4b780887cab0ecabc5eca319acb3acf2"), - FANMADE("Little Pirate (Demo 2 v0.6)", "437068efe4ec32d436da09d6f2ea56e1"), - FANMADE("Lost Eternity (v1.0)", "95f15c5632feb8a39e9ca3d9af35fcc9"), - FANMADE("MD Quest - The Search for Michiel (v0.10)", "2a6fcb21d2b5e4144c38ed817fabe8ee"), - FANMADE("Maale Adummin Quest", "ddfbeb33feb7cf78504fe4dba14ec63b"), - FANMADE("Monkey Man", "2322d03f997e8cc235d4578efff69cfa"), - FANMADE_F("Napalm Quest (v0.5)", "b659afb491d967bb34810d1c6ce22093", GF_AGIMOUSE), - FANMADE("Naturette 1 (English v1.2)", "0a75884e7f010974a230bdf269651117"), - FANMADE("Naturette 1 (English v1.3)", "f15bbf999ac55ebd404aa1eb84f7c1d9"), - FANMADE_L("Naturette 1 (French v1.2)", "d3665622cc41aeb9c7ecf4fa43f20e53", Common::FR_FRA), - FANMADE_F("Naturette 2: Daughter of the Moon (v1.0)", "bdf76a45621c7f56d1c9d40292c6137a", GF_AGIMOUSE|GF_AGIPAL), - FANMADE_F("Naturette 3: Adventure in Treeworld (v1.0a)", "6dbb0e7fc75fec442e6d9e5a06f1530e", GF_AGIMOUSE|GF_AGIPAL), - FANMADE_F("Naturette 4: From a Planet to Another Planet (Not Finished)", "13be8cd9cf35aeff0a39b8757057fbc8", GF_AGIMOUSE), - // FIXME: Actually Naturette 4 has both English and French language support built into it. How to add that information? - FANMADE_F("Naturette 4: From a Planet to Another Planet (2007-10-05)", "8253706b6ef5423a79413b216760297c", GF_AGIMOUSE|GF_AGIPAL), - FANMADE("New AGI Hangman Test", "d69c0e9050ccc29fd662b74d9fc73a15"), - FANMADE("Nick's Quest - In Pursuit of QuakeMovie (v2.1 Gold)", "e29cbf9222551aee40397fabc83eeca0"), - FANMADE_F("Open Mic Night (v0.1)", "70000a2f67aac27d1133d019df70246d", GF_AGIMOUSE|GF_AGIPAL), - FANMADE("Operation: Recon", "0679ce8405411866ccffc8a6743370d0"), - FANMADE("Patrick's Quest (Demo v1.0)", "f254f5b894b98fec5f92acc07fb62841"), - FANMADE("Phantasmagoria", "87d20c1c11aee99a4baad3797b63146b"), - FANMADE("Pharaoh Quest (v0.0)", "51c630899d076cf799e573dadaa2276d"), - FANMADE("Phil's Quest - the Search for Tolbaga", "5e7ca45c360e03164b8358e49900c588"), - FANMADE("Pinkun Maze Quest (v0.1)", "148ff0843af389928b3939f463bfd20d"), - FANMADE("Pirate Quest", "bb612a919ed2b9ea23bbf03ce69fed42"), - FANMADE("Pothead (v0.1)", "d181101385d3a45082f418cd4b3c5b01"), - FANMADE("President's Quest", "4937d0e8ecadb7888faeb347799b0388"), - FANMADE("Prince Quest", "266248d75c3130c8ccc9c9bf2ad30a0d"), - FANMADE("Professor (English) - The Professor is Missing (Mar 17)", "6232de31cc204affdf2e92dfe3dc0e4d"), - FANMADE("Professor (English) - The Professor is Missing (Mar 22)", "b5fcf0ca2f0d1c073be82f01e2170961"), - FANMADE_L("Professor (French) - Le Professeur a Disparu", "7d9f8a4d4610bb9b0b97caa17590c2d3", Common::FR_FRA), - FANMADE("Quest for Glory VI - Hero's Adventure", "d26765c3075064c80d284c5e06e33a7e"), - FANMADE("Quest for Home", "d2895dc1cd3930f2489af0f843b144b3"), - FANMADE("Quest for Ladies (demo v1.1 Apr 1)", "3f6e02f16e1154a0daf296c8895edd97"), - FANMADE("Quest for Ladies (demo v1.1 Apr 6)", "f75e7b6a0769a3fa926eea0854711591"), - FANMADE("Quest for Piracy 1 - Enter the Silver Pirate (v0.15)", "d23f5c2a26f6dc60c686f8a2436ea4a6"), - FANMADE("Quest for a Record Deal", "f4fbd7abf056d2d3204f790da5ac89ab"), - FANMADE("Ralph's Quest (v0.1)", "5cf56378aa01a26ec30f25295f0750ca"), - FANMADE("Residence 44 Quest (Dutch v0.99)", "7c5cc64200660c70240053b33d379d7d"), - FANMADE("Residence 44 Quest (English v0.99)", "fe507851fddc863d540f2bec67cc67fd"), - FANMADE("Residence 44 Quest (English v1.0a)", "f99e3f69dc8c77a45399da9472ef5801"), - FANMADE("SQ2Eye (v0.3)", "2be2519401d38ad9ce8f43b948d093a3"), - // FANMADE("SQ2Eye (v0.4)", "2be2519401d38ad9ce8f43b948d093a3"), - FANMADE("SQ2Eye (v0.41)", "f0e82c55f10eb3542d7cd96c107ae113"), - FANMADE("SQ2Eye (v0.42)", "d7beae55f6328ef8b2da47b1aafea40c"), - FANMADE("SQ2Eye (v0.43)", "2a895f06e45de153bb4b77c982009e06"), - FANMADE("SQ2Eye (v0.44)", "5174fc4b6d8a477ba0ff0575cd64e0aa"), - FANMADE("SQ2Eye (v0.45)", "6e06f8bb7b90ce6f6aabf1a0e620159c"), - FANMADE("SQ2Eye (v0.46)", "bf0ad7a035ff9113951d09d1efe380c4"), - FANMADE("SQ2Eye (v0.47)", "85dc3be1d33ff932c292b74f9037abaa"), - FANMADE("SQ2Eye (v0.48)", "587574252972a5b5c070a647973a9b4a"), - FANMADE("SQ2Eye (v0.481)", "fc9234beb49804ae869696ce5af8ef30"), - FANMADE("SQ2Eye (v0.482)", "3ed84b7b87fa6840f25c15f250a11ffb"), - FANMADE("SQ2Eye (v0.483)", "647c31298d3f9cda641231b893e347c0"), - FANMADE("SQ2Eye (v0.484)", "f2c86fae7b9046d408c62c8c49a4b882"), - FANMADE("SQ2Eye (v0.485)", "af59e36bc28f44545458b68a93e91e67"), - FANMADE("SQ2Eye (v0.486)", "3fd86436e93456770dbdd4593eded70a"), - FANMADE("Save Santa (v1.0)", "4644f6beb5802081772f14be56ae196c"), - FANMADE("Save Santa (v1.3)", "f8afdb6efc5af5e7c0228b44633066af"), - FANMADE("Schiller (preview 1)", "ade39dea968c959cfebe1cf935d653e9"), - FANMADE("Schiller (preview 2)", "62cd1f8fc758bf6b4aa334e553624cef"), - FANMADE_IF("serguei1", "v1.0", "b86725f067e456e10cdbdf5f58e01dec", GF_AGIMOUSE|GF_AGIPAL), - FANMADE_IF("serguei1", "v1.1 2002 Sep 5", "91975c1fb4b13b0f9a8e9ff74731030d", GF_AGIMOUSE|GF_AGIPAL), - FANMADE_IF("serguei1", "v1.1 2003 Apr 10", "91975c1fb4b13b0f9a8e9ff74731030d", GF_AGIMOUSE|GF_AGIPAL), - FANMADE_IF("serguei2", "v0.1.1 Demo", "906ccbc2ddedb29b63141acc6d10cd28", GF_AGIMOUSE), - FANMADE_IF("serguei2", "v1.3.1 Demo (March 22nd 2008)", "ad1308fcb8f48723cd388e012ebf5e20", GF_AGIMOUSE|GF_AGIPAL), - FANMADE("Shifty (v1.0)", "2a07984d27b938364bf6bd243ac75080"), - FANMADE_F("Sliding Tile Game (v1.00)", "949bfff5d8a81c3139152eed4d84ca75", GF_AGIMOUSE), - FANMADE("Snowboarding Demo (v1.0)", "24bb8f29f1eddb5c0a099705267c86e4"), - FANMADE("Solar System Tour", "b5a3d0f392dfd76a6aa63f3d5f578403"), - FANMADE("Sorceror's Appraisal", "fe62615557b3cb7b08dd60c9d35efef1"), - FANMADE_I("sq0", "v1.03", "d2fd6f7404e86182458494e64375e590"), - FANMADE_I("sq0", "v1.04", "2ad9d1a4624a98571ee77dcc83f231b6"), - FANMADE_ISVP("sq0", "", "e1a8e4efcce86e1efcaa14633b9eb986", 762, 0x2440, Common::kPlatformCoCo3), - FANMADE_I("sqx", "v10.0 Feb 05", "c992ae2f8ab18360404efdf16fa9edd1"), - FANMADE_I("sqx", "v10.0 Jul 18", "812edec45cefad559d190ffde2f9c910"), - FANMADE_ISVP("sqx", "", "f0a59044475a5fa37c055d8c3eb4d1a7", 768, 0x2440, Common::kPlatformCoCo3), - FANMADE_F("Space Quest 3.5", "c077bc28d7b36213dd99dc9ecb0147fc", GF_AGIMOUSE|GF_AGIPAL), - FANMADE_F("Space Trek (v1.0)", "807a1aeadb2ace6968831d36ab5ea37a", GF_CLIPCOORDS), - FANMADE("Special Delivery", "88764dfe61126b8e73612c851b510a33"), - FANMADE("Speeder Bike Challenge (v1.0)", "2deb25bab379285ca955df398d96c1e7"), - FANMADE("Star Commander 1 - The Escape (v1.0)", "a7806f01e6fa14ebc029faa58f263750"), - FANMADE("Star Pilot: Bigger Fish", "8cb26f8e1c045b75c6576c839d4a0172"), - FANMADE_F("Street Quest (Demo)", "cf2aa94a7eb78dce6892c37f03e310d6", GF_AGIPAL), - FANMADE("Tales of the Tiki", "8103c9c87e3964690a14a3d0d83f7ddc"), - FANMADE("Tex McPhilip 1 - Quest For The Papacy", "3c74b9a24b51aa8020ac82bee3132266"), - FANMADE("Tex McPhilip 2 - Road To Divinity (v1.5)", "7387e8df854440bc26620ca0ea43af9a"), - FANMADE("Tex McPhilip 3 - A Destiny of Sin (Demo v0.25)", "992d12031a486ad84e592ff5d7c9d782"), - FANMADE("The 13th Disciple (v1.00)", "887719ad59afce9a41ec057dbb73ad73"), - FANMADE("The Adventures of a Crazed Hermit", "6e3086cbb794d3299a9c5a9792295511"), - FANMADE("The Grateful Dead", "c2146631afacf8cb455ce24f3d2d46e7"), - FANMADE("The Legend of Shay-Larah 1 - The Lost Prince", "04e720c8e30c9cf12db22ea14a24a3dd"), - FANMADE("The Legend of Zelda: The Fungus of Time (Demo v1.00)", "dcaf8166ceb62a3d9b9aea7f3b197c09"), - FANMADE("The Legendary Harry Soupsmith (Demo 1998 Apr 2)", "64c46b0d6fc135c9835afa80980d2831"), - FANMADE("The Legendary Harry Soupsmith (Demo 1998 Aug 19)", "8d06d82970f2c591d880a95476efbcf0"), - FANMADE("The Long Haired Dude: Encounter of the 18-th Kind", "86ea17b9fc2f3e537a7e40863d352c29"), - FANMADE("The Lost Planet (v0.9)", "590dffcbd932a9fbe554be13b769cac0"), - FANMADE("The Lost Planet (v1.0)", "58564df8b6394612dd4b6f5c0fd68d44"), - FANMADE("The New Adventure of Roger Wilco (v1.00)", "e5f0a7cb8d49f66b89114951888ca688"), - FANMADE("The Ruby Cast (v0.02)", "ed138e461bb1516e097007e017ab62df"), - FANMADE("The Shadow Plan", "c02cd10267e721f4e836b1431f504a0a"), - FANMADE("Time Quest (Demo v0.1)", "12e1a6f03ea4b8c5531acd0400b4ed8d"), - FANMADE("Time Quest (Demo v0.2)", "7b710608abc99e0861ac59b967bf3f6d"), - FANMADE_SVP("Time Quest", "90314f473d8317be5cd1f0306f139aea", 300, 0x2440, Common::kPlatformCoCo3), - FANMADE("Tonight The Shrieking Corpses Bleed (Demo v0.11)", "bcc57a7c8d563fa0c333107ae1c0a6e6"), - FANMADE("Tonight The Shrieking Corpses Bleed (v1.01)", "36b38f621b38e8d104aa0807302dc8c9"), - FANMADE("Turks' Quest - Heir to the Planet", "3d19254b737c8b218e5bc4580542b79a"), - FANMADE("URI Quest (v0.173 Feb 27)", "3986eefcf546dafc45f920ae91a697c3"), - FANMADE("URI Quest (v0.173 Jan 29)", "494150940d34130605a4f2e67ee40b12"), - { - // V - The Graphical Adventure - { - "agi-fanmade", - "V - The Graphical Adventure (Demo 2)", - AD_ENTRY1s("vdir", "c71f5c1e008d352ae9040b77fcf79327", 3080), - Common::EN_ANY, - Common::kPlatformPC, - ADGF_USEEXTRAASTITLE, - GUIO_NONE - }, - GID_FANMADE, - GType_V3, - GF_FANMADE, - 0x3149, - }, - FANMADE_SVP("V - The Graphical Adventure", "1646eaade74f137a9041eb427a389969", 768, 0x2440, Common::kPlatformCoCo3), - - FANMADE("Voodoo Girl - Queen of the Darned (v1.2 2002 Jan 1)", "ae95f0c77d9a97b61420fd192348b937"), - FANMADE("Voodoo Girl - Queen of the Darned (v1.2 2002 Mar 29)", "11d0417b7b886f963d0b36789dac4c8f"), - FANMADE("Wizaro (v0.1)", "abeec1eda6eaf8dbc52443ea97ff140c"), - - { AD_TABLE_END_MARKER, 0, 0, 0, 0 } -}; - -/** - * The fallback game descriptor used by the AGI engine's fallbackDetector. - * Contents of this struct are to be overwritten by the fallbackDetector. - */ -static AGIGameDescription g_fallbackDesc = { - { - "", - "", - AD_ENTRY1(0, 0), // This should always be AD_ENTRY1(0, 0) in the fallback descriptor - Common::UNK_LANG, - Common::kPlatformPC, - ADGF_NO_FLAGS, - GUIO_NONE - }, - GID_FANMADE, - GType_V2, - GF_FANMADE, - 0x2917, -}; +#include "agi/detection_tables.h" static const ADParams detectionParams = { // Pointer to ADGameDescription or its superset structure @@ -979,11 +145,13 @@ static const ADParams detectionParams = { // Flags 0, // Additional GUI options (for every game} - Common::GUIO_NOSPEECH | Common::GUIO_NOMIDI + Common::GUIO_NOSPEECH, + // Maximum directory depth + 1, + // List of directory globs + 0 }; -} // End of namespace Agi - using namespace Agi; class AgiMetaEngine : public AdvancedMetaEngine { @@ -1322,6 +490,9 @@ bool AgiBase::canLoadGameStateCurrently() { } bool AgiBase::canSaveGameStateCurrently() { + if (getGameID() == GID_BC) // Technically in Black Cauldron we may save anytime + return true; + return (!(getGameType() == GType_PreAGI) && getflag(fMenusWork) && !_noSaveLoadAllowed && _game.inputEnabled); } diff --git a/engines/agi/detection_tables.h b/engines/agi/detection_tables.h new file mode 100644 index 0000000000..711701f55a --- /dev/null +++ b/engines/agi/detection_tables.h @@ -0,0 +1,863 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +namespace Agi { + +using Common::GUIO_NONE; + +#define GAME_LVFPN(id,extra,fname,md5,size,lang,ver,features,gid,platform,interp) { \ + { \ + id, \ + extra, \ + AD_ENTRY1s(fname,md5,size), \ + lang, \ + platform, \ + ADGF_NO_FLAGS, \ + GUIO_NONE \ + }, \ + gid, \ + interp, \ + features, \ + ver, \ + } + +#define GAME_LVFPNF(id,name,fname,md5,size,lang,ver,features,gid,platform,interp) { \ + { \ + id, \ + name, \ + AD_ENTRY1s(fname,md5,size), \ + lang, \ + platform, \ + ADGF_USEEXTRAASTITLE, \ + GUIO_NONE \ + }, \ + gid, \ + interp, \ + features, \ + ver, \ + } + +#define GAME(id,extra,md5,ver,gid) GAME_LVFPN(id,extra,"logdir",md5,-1,Common::EN_ANY,ver,0,gid,Common::kPlatformPC,GType_V2) +#define GAME3(id,extra,fname,md5,ver,gid) GAME_LVFPN(id,extra,fname,md5,-1,Common::EN_ANY,ver,0,gid,Common::kPlatformPC,GType_V3) + +#define GAME_P(id,extra,md5,ver,gid,platform) GAME_LVFPN(id,extra,"logdir",md5,-1,Common::EN_ANY,ver,0,gid,platform,GType_V2) + +#define GAME_FP(id,extra,md5,ver,flags,gid,platform) GAME_LVFPN(id,extra,"logdir",md5,-1,Common::EN_ANY,ver,flags,gid,platform,GType_V2) +#define GAME_F(id,extra,md5,ver,flags,gid) GAME_FP(id,extra,md5,ver,flags,gid,Common::kPlatformPC) + +#define GAME_PS(id,extra,md5,size,ver,gid,platform) GAME_LVFPN(id,extra,"logdir",md5,size,Common::EN_ANY,ver,0,gid,platform,GType_V2) + +#define GAME_LPS(id,extra,md5,size,lang,ver,gid,platform) GAME_LVFPN(id,extra,"logdir",md5,size,lang,ver,0,gid,platform,GType_V2) + +#define GAME_LFPS(id,extra,md5,size,lang,ver,flags,gid,platform) GAME_LVFPN(id,extra,"logdir",md5,size,lang,ver,flags,gid,platform,GType_V2) + +#define GAME3_P(id,extra,fname,md5,ver,flags,gid,platform) GAME_LVFPN(id,extra,fname,md5,-1,Common::EN_ANY,ver,flags,gid,platform,GType_V3) + +#define GAMEpre_P(id,extra,fname,md5,ver,gid,platform) GAME_LVFPN(id,extra,fname,md5,-1,Common::EN_ANY,ver,0,gid,platform,GType_PreAGI) + +#define GAMEpre_PS(id,extra,fname,md5,size,ver,gid,platform) GAME_LVFPN(id,extra,fname,md5,size,Common::EN_ANY,ver,0,gid,platform,GType_PreAGI) + +#define GAME3_PS(id,extra,fname,md5,size,ver,flags,gid,platform) GAME_LVFPN(id,extra,fname,md5,size,Common::EN_ANY,ver,flags,gid,platform,GType_V3) + +#define FANMADE_ILVF(id,name,md5,lang,ver,features) GAME_LVFPNF(id,name,"logdir",md5,-1,lang,ver,(GF_FANMADE|features),GID_FANMADE,Common::kPlatformPC,GType_V2) + +#define FANMADE_ISVP(id,name,md5,size,ver,platform) GAME_LVFPNF(id,name,"logdir",md5,size,Common::EN_ANY,ver,GF_FANMADE,GID_FANMADE,platform,GType_V2) +#define FANMADE_SVP(name,md5,size,ver,platform) FANMADE_ISVP("agi-fanmade",name,md5,size,ver,platform) + +#define FANMADE_LVF(name,md5,lang,ver,features) FANMADE_ILVF("agi-fanmade",name,md5,lang,ver,features) + +#define FANMADE_LF(name,md5,lang,features) FANMADE_LVF(name,md5,lang,0x2917,features) +#define FANMADE_IF(id,name,md5,features) FANMADE_ILVF(id,name,md5,Common::EN_ANY,0x2917,features) + +#define FANMADE_V(name,md5,ver) FANMADE_LVF(name,md5,Common::EN_ANY,ver,0) +#define FANMADE_F(name,md5,features) FANMADE_LF(name,md5,Common::EN_ANY,features) +#define FANMADE_L(name,md5,lang) FANMADE_LF(name,md5,lang,0) +#define FANMADE_I(id,name,md5) FANMADE_IF(id,name,md5,0) + +#define FANMADE(name,md5) FANMADE_F(name,md5,0) + +static const AGIGameDescription gameDescriptions[] = { + + // AGI Demo 1 (PC) 05/87 [AGI 2.425] + GAME("agidemo", "Demo 1 1987-05-20", "9c4a5b09cc3564bc48b4766e679ea332", 0x2440, GID_AGIDEMO), + + // AGI Demo 2 (IIgs) 1.0C (Censored) + GAME_P("agidemo", "Demo 2 1987-11-24 1.0C", "580ffdc569ff158f56fb92761604f70e", 0x2917, GID_AGIDEMO, Common::kPlatformApple2GS), + + // AGI Demo 2 (PC 3.5") 11/87 [AGI 2.915] + GAME("agidemo", "Demo 2 1987-11-24 3.5\"", "e8ebeb0bbe978172fe166f91f51598c7", 0x2917, GID_AGIDEMO), + + // AGI Demo 2 (PC 5.25") 11/87 [v1] [AGI 2.915] + GAME("agidemo", "Demo 2 1987-11-24 [version 1] 5.25\"", "852ac303a374df62571642ca1e2d1f0a", 0x2917, GID_AGIDEMO), + + // AGI Demo 2 (PC 5.25") 01/88 [v2] [AGI 2.917] + GAME("agidemo", "Demo 2 1987-11-25 [version 2] 5.25\"", "1503f02086ea9f388e7e041c039eaa69", 0x2917, GID_AGIDEMO), + + // AGI Demo 3 (PC) 09/88 [AGI 3.002.102] + GAME3("agidemo", "Demo 3 1988-09-13", "dmdir", "289c7a2c881f1d973661e961ced77d74", 0x3149, GID_AGIDEMO), + + // AGI Demo for Kings Quest III and Space Quest I + GAME("agidemo", "Demo Kings Quest III and Space Quest I", "502e6bf96827b6c4d3e67c9cdccd1033", 0x2272, GID_AGIDEMO), + + // Black Cauldron (Amiga) 2.00 6/14/87 + GAME_P("bc", "2.00 1987-06-14", "7b01694af21213b4727bb94476f64eb5", 0x2440, GID_BC, Common::kPlatformAmiga), + + // Black Cauldron (Apple IIgs) 1.0O 2/24/89 (CE) + // Menus not tested + GAME3_P("bc", "1.0O 1989-02-24 (CE)", "bcdir", "dc09d30b147242692f4f85b9811962db", 0x3149, 0, GID_BC, Common::kPlatformApple2GS), + + // Black Cauldron (PC) 2.00 6/14/87 [AGI 2.439] + GAME("bc", "2.00 1987-06-14", "7f598d4712319b09d7bd5b3be10a2e4a", 0x2440, GID_BC), + + // Black Cauldron (Russian) + GAME_LPS("bc", "", "b7de782dfdf8ea7dde8064f09804bcf5", 357, Common::RU_RUS, 0x2440, GID_BC, Common::kPlatformPC), + + // Black Cauldron (PC 5.25") 2.10 11/10/88 [AGI 3.002.098] + GAME3("bc", "2.10 1988-11-10 5.25\"", "bcdir", "0c5a9acbcc7e51127c34818e75806df6", 0x3149, GID_BC), + + // Black Cauldron (PC) 2.10 [AGI 3.002.097] + GAME3("bc", "2.10", "bcdir", "0de3953c9225009dc91e5b0d1692967b", 0x3149, GID_BC), + + // Black Cauldron (CoCo3 360k) [AGI 2.023] + GAME_PS("bc", "", "51212c54808ade96176f201ae0ac7a6f", 357, 0x2440, GID_BC, Common::kPlatformCoCo3), + + // Black Cauldron (CoCo3 360k) [AGI 2.072] + GAME_PS("bc", "updated", "c4e1937f74e8100cd0152b904434d8b4", 357, 0x2440, GID_BC, Common::kPlatformCoCo3), + +// TODO +// These aren't supposed to work now as they require unsupported agi engine 2.01 +#if 0 + // Donald Duck's Playground (Amiga) 1.0C + // Menus not tested + GAME_P("ddp", "1.0C 1987-04-27", "550971d196f65190a5c760d2479406ef", 0x2272, GID_DDP, Common::kPlatformAmiga), + + // Donald Duck's Playground (ST) 1.0A 8/8/86 + // Menus not tested + GAME("ddp", "1.0A 1986-08-08", "64388812e25dbd75f7af1103bc348596", 0x2272, GID_DDP), + + // reported by Filippos (thebluegr) in bugreport #1654500 + // Menus not tested + GAME_PS("ddp", "1.0C 1986-06-09", "550971d196f65190a5c760d2479406ef", 132, 0x2272, GID_DDP, Common::kPlatformPC), +#endif + + // Gold Rush! (Amiga) 1.01 1/13/89 aka 2.05 3/9/89 # 2.316 + GAME3_PS("goldrush", "1.01 1989-01-13 aka 2.05 1989-03-09", "dirs", "a1d4de3e75c2688c1e2ca2634ffc3bd8", 2399, 0x3149, 0, GID_GOLDRUSH, Common::kPlatformAmiga), + + // Gold Rush! (Apple IIgs) 1.0M 2/28/89 (CE) aka 2.01 12/22/88 + // Menus not tested + GAME3_P("goldrush", "1.0M 1989-02-28 (CE) aka 2.01 1988-12-22", "grdir", "3f7b9ce62631434389f85371b11921d6", 0x3149, GF_2GSOLDSOUND, GID_GOLDRUSH, Common::kPlatformApple2GS), + + // Gold Rush! (ST) 1.01 1/13/89 aka 2.01 12/22/88 + GAME3_P("goldrush", "1.01 1989-01-13 aka 2.01 1988-12-22", "grdir", "4dd4d50480a3d6c206fa227ce8142735", 0x3149, 0, GID_GOLDRUSH, Common::kPlatformAtariST), + + // Gold Rush! (PC 5.25") 2.01 12/22/88 [AGI 3.002.149] + GAME3("goldrush", "2.01 1988-12-22 5.25\"", "grdir", "db733d199238d4009a9e95f11ece34e9", 0x3149, GID_GOLDRUSH), + + // Gold Rush! (PC 3.5") 2.01 12/22/88 [AGI 3.002.149] + GAME3("goldrush", "2.01 1988-12-22 3.5\"", "grdir", "6a285235745f69b4b421403659497216", 0x3149, GID_GOLDRUSH), + + // Gold Rush! (PC 3.5", bought from The Software Farm) 3.0 1998-12-22 [AGI 3.002.149] + GAME3("goldrush", "3.0 1998-12-22 3.5\"", "grdir", "6882b6090473209da4cd78bb59f78dbe", 0x3149, GID_GOLDRUSH), + + { + // Gold Rush! (PC 5.25") 2.01 12/22/88 [AGI 3.002.149] + { + "goldrush", + "2.01 1988-12-22", + { + { "grdir", 0, "db733d199238d4009a9e95f11ece34e9", 2399}, + { "vol.0", 0, "4b6423d143674d3757ab1b875d25951d", 25070}, + { NULL, 0, NULL, 0} + }, + Common::EN_ANY, + Common::kPlatformMacintosh, + ADGF_NO_FLAGS, + GUIO_NONE + }, + GID_GOLDRUSH, + GType_V3, + GF_MACGOLDRUSH, + 0x3149, + }, + + + // Gold Rush! (CoCo3 720k) [AGI 2.023] + GAME_PS("goldrush", "", "0a41b65efc0cd6c4271e957e6ffbbd8e", 744, 0x2440, GID_GOLDRUSH, Common::kPlatformCoCo3), + + // Gold Rush! (CoCo3 360k/720k) [AGI 2.072] + GAME_PS("goldrush", "updated", "c49bf56bf91e31a4601a604e51ef8bfb", 744, 0x2440, GID_GOLDRUSH, Common::kPlatformCoCo3), + + // King's Quest 1 (Amiga) 1.0U # 2.082 + // The original game did not have menus, they are enabled under ScummVM + GAME_FP("kq1", "1.0U 1986", "246c695324f1c514aee2b904fa352fad", 0x2440, GF_MENUS, GID_KQ1, Common::kPlatformAmiga), + + // King's Quest 1 (ST) 1.0V + // The original game did not have menus, they are enabled under ScummVM + GAME_FP("kq1", "1.0V 1986", "c3a017e556c4b0eece366a4cd9abb657", 0x2272, GF_MENUS, GID_KQ1, Common::kPlatformAtariST), + + // King's Quest 1 (IIgs) 1.0S-88223 + // Menus not tested + GAME_P("kq1", "1.0S 1988-02-23", "f4277aa34b43d37382bc424c81627617", 0x2272, GID_KQ1, Common::kPlatformApple2GS), + + // King's Quest 1 (Mac) 2.0C + GAME_P("kq1", "2.0C 1987-03-26", "d4c4739d4ac63f7dbd29255425077d48", 0x2440, GID_KQ1, Common::kPlatformMacintosh), + + // King's Quest 1 (PC 5.25"/3.5") 2.0F [AGI 2.917] + GAME("kq1", "2.0F 1987-05-05 5.25\"/3.5\"", "10ad66e2ecbd66951534a50aedcd0128", 0x2917, GID_KQ1), + + // King's Quest 1 (CoCo3 360k) [AGI 2.023] + GAME_PS("kq1", "", "10ad66e2ecbd66951534a50aedcd0128", 315, 0x2440, GID_KQ1, Common::kPlatformCoCo3), + + // King's Quest 1 (CoCo3 360k) [AGI 2.023] + GAME_PS("kq1", "fixed", "4c8ef8b5d2f1b6c1a93e456d1f1ffc74", 768, 0x2440, GID_KQ1, Common::kPlatformCoCo3), + + // King's Quest 1 (CoCo3 360k) [AGI 2.072] + GAME_PS("kq1", "updated", "94087178c78933a4af3cd24d1c8dd7b2", 315, 0x2440, GID_KQ1, Common::kPlatformCoCo3), + + // King's Quest 2 (IIgs) 2.0A 6/16/88 (CE) + GAME_P("kq2", "2.0A 1988-06-16 (CE)", "5203c8b95250a2ecfee93ddb99414753", 0x2917, GID_KQ2, Common::kPlatformApple2GS), + + // King's Quest 2 (Amiga) 2.0J (Broken) + GAME_P("kq2", "2.0J 1987-01-29 [OBJECT decrypted]", "b866f0fab2fad91433a637a828cfa410", 0x2440, GID_KQ2, Common::kPlatformAmiga), + + // King's Quest 2 (Mac) 2.0R + GAME_P("kq2", "2.0R 1988-03-23", "cbdb0083317c8e7cfb7ac35da4bc7fdc", 0x2440, GID_KQ2, Common::kPlatformMacintosh), + + // King's Quest 2 (PC) 2.1 [AGI 2.411]; entry from DAGII, but missing from Sarien? + // XXX: any major differences from 2.411 to 2.440? + GAME("kq2", "2.1 1987-04-10", "759e39f891a0e1d86dd29d7de485c6ac", 0x2440, GID_KQ2), + + // King's Quest 2 (PC 5.25"/3.5") 2.2 [AGI 2.426] + GAME("kq2", "2.2 1987-05-07 5.25\"/3.5\"", "b944c4ff18fb8867362dc21cc688a283", 0x2917, GID_KQ2), + + // King's Quest 2 (Russian) + GAME_LPS("kq2", "", "35211c574ececebdc723b23e35f99275", 543, Common::RU_RUS, 0x2917, GID_KQ2, Common::kPlatformPC), + + // King's Quest 2 (CoCo3 360k) [AGI 2.023] + GAME_PS("kq2", "", "b944c4ff18fb8867362dc21cc688a283", 543, 0x2440, GID_KQ2, Common::kPlatformCoCo3), + + // King's Quest 2 (CoCo3 360k) [AGI 2.072] + GAME_PS("kq2", "updated", "f64a606de740a5348f3d125c03e989fe", 543, 0x2440, GID_KQ2, Common::kPlatformCoCo3), + + // King's Quest 2 (CoCo3 360k) [AGI 2.023] + GAME_PS("kq2", "fixed", "fb33ac2768a94a89117a270771db465c", 768, 0x2440, GID_KQ2, Common::kPlatformCoCo3), + + // King's Quest 3 (Amiga) 1.01 11/8/86 + // The original game did not have menus, they are enabled under ScummVM + GAME_FP("kq3", "1.01 1986-11-08", "8ab343306df0e2d98f136be4e8cfd0ef", 0x2440, GF_MENUS, GID_KQ3, Common::kPlatformAmiga), + + // King's Quest 3 (ST) 1.02 11/18/86 + // Does not have menus, crashes if menus are enforced. Therefore, ESC pauses the game + GAME_FP("kq3", "1.02 1986-11-18", "8846df2654302b623217ba8bd6d657a9", 0x2272, GF_ESCPAUSE, GID_KQ3, Common::kPlatformAtariST), + + // King's Quest 3 (Mac) 2.14 3/15/88 + GAME_P("kq3", "2.14 1988-03-15", "7639c0da5ce94848227d409351fabda2", 0x2440, GID_KQ3, Common::kPlatformMacintosh), + + // King's Quest 3 (IIgs) 2.0A 8/28/88 (CE) + GAME_P("kq3", "2.0A 1988-08-28 (CE)", "ac30b7ca5a089b5e642fbcdcbe872c12", 0x2917, GID_KQ3, Common::kPlatformApple2GS), + + // King's Quest 3 (Amiga) 2.15 11/15/89 # 2.333 + // Original pauses with ESC, has menus accessible with mouse. + // ver = 0x3086 -> menus accessible with ESC or mouse, bug #2835581 (KQ3: Game Crash When Leaving Tavern as Fly). + // ver = 0x3149 -> menus accessible with mouse, ESC pauses game, bug #2835581 disappears. + GAME3_PS("kq3", "2.15 1989-11-15", "dirs", "8e35bded2bc5cf20f5eec2b15523b155", 1805, 0x3149, 0, GID_KQ3, Common::kPlatformAmiga), + + // King's Quest 3 (PC) 1.01 11/08/86 [AGI 2.272] + // Does not have menus, crashes if menus are enforced. Therefore, ESC pauses the game + GAME_FP("kq3", "1.01 1986-11-08", "9c2b34e7ffaa89c8e2ecfeb3695d444b", 0x2272, GF_ESCPAUSE, GID_KQ3, Common::kPlatformPC), + + // King's Quest 3 (Russian) + GAME_LFPS("kq3", "", "5856dec6ccb9c4b70aee21044a19270a", 390, Common::RU_RUS, 0x2272, GF_ESCPAUSE, GID_KQ3, Common::kPlatformPC), + + // King's Quest 3 (PC 5.25") 2.00 5/25/87 [AGI 2.435] + GAME("kq3", "2.00 1987-05-25 5.25\"", "18aad8f7acaaff760720c5c6885b6bab", 0x2440, GID_KQ3), + + // King's Quest 3 (Mac) 2.14 3/15/88 + // Menus not tested + GAME_P("kq3", "2.14 1988-03-15 5.25\"", "7650e659c7bc0f1e9f8a410b7a2e9de6", 0x2440, GID_KQ3, Common::kPlatformMacintosh), + + // King's Quest 3 (PC 3.5") 2.14 3/15/88 [AGI 2.936] + GAME("kq3", "2.14 1988-03-15 3.5\"", "d3d17b77b3b3cd13246749231d9473cd", 0x2936, GID_KQ3), + + // King's Quest 3 (CoCo3 158k/360k) [AGI 2.023] + GAME_PS("kq3", "", "5a6be7d16b1c742c369ef5cc64fefdd2", 429, 0x2440, GID_KQ3, Common::kPlatformCoCo3), + + // King's Quest 4 (PC 5.25") 2.0 7/27/88 [AGI 3.002.086] + GAME3("kq4", "2.0 1988-07-27", "kq4dir", "f50f7f997208ca0e35b2650baec43a2d", 0x3086, GID_KQ4), + + // King's Quest 4 (PC 3.5") 2.0 7/27/88 [AGI 3.002.086] + GAME3("kq4", "2.0 1988-07-27 3.5\"", "kq4dir", "fe44655c42f16c6f81046fdf169b6337", 0x3086, GID_KQ4), + + // King's Quest 4 (PC 3.5") 2.2 9/27/88 [AGI 3.002.086] + // Menus not tested + GAME3("kq4", "2.2 1988-09-27 3.5\"", "kq4dir", "7470b3aeb49d867541fc66cc8454fb7d", 0x3086, GID_KQ4), + + // King's Quest 4 (PC 5.25") 2.3 9/27/88 [AGI 3.002.086] + GAME3("kq4", "2.3 1988-09-27", "kq4dir", "6d7714b8b61466a5f5981242b993498f", 0x3086, GID_KQ4), + + // King's Quest 4 (PC 3.5") 2.3 9/27/88 [AGI 3.002.086] + GAME3("kq4", "2.3 1988-09-27 3.5\"", "kq4dir", "82a0d39af891042e99ac1bd6e0b29046", 0x3086, GID_KQ4), + + // King's Quest 4 (IIgs) 1.0K 11/22/88 (CE) + // Menus not tested + GAME3_P("kq4", "1.0K 1988-11-22", "kq4dir", "8536859331159f15012e35dc82cb154e", 0x3086, 0, GID_KQ4, Common::kPlatformApple2GS), + + // King's Quest 4 demo (PC) [AGI 3.002.102] + // Menus not tested + GAME3("kq4", "Demo 1988-12-20", "dmdir", "a3332d70170a878469d870b14863d0bf", 0x3149, GID_KQ4), + + // King's Quest 4 (CoCo3 720k) [AGI 2.023] + GAME_PS("kq4", "", "9e7729a28e749ca241d2bf71b9b2dbde", 741, 0x2440, GID_KQ4, Common::kPlatformCoCo3), + + // King's Quest 4 (CoCo3 360k/720k) [AGI 2.072] + GAME_PS("kq4", "updated", "1959ca10739edb34069bb504dbd74805", 741, 0x2440, GID_KQ4, Common::kPlatformCoCo3), + + // Leisure Suit Larry 1 (PC 5.25"/3.5") 1.00 6/1/87 [AGI 2.440] + GAME("lsl1", "1.00 1987-06-01 5.25\"/3.5\"", "1fe764e66857e7f305a5f03ca3f4971d", 0x2440, GID_LSL1), + + // Leisure Suit Larry 1 Polish + GAME_LPS("lsl1", "2.00 2001-12-11", "7ba1fccc46d27c141e704706c1d0a85f", 303, Common::PL_POL, 0x2440, GID_LSL1, Common::kPlatformPC), + + // Leisure Suit Larry 1 Polish - Demo + GAME_LPS("lsl1", "Demo", "3b2f564306c401dff6334441df967ddd", 666, Common::PL_POL, 0x2917, GID_LSL1, Common::kPlatformPC), + + // Leisure Suit Larry 1 (ST) 1.04 6/18/87 + GAME_P("lsl1", "1.04 1987-06-18", "8b579f8673fe9448c2538f5ed9887cf0", 0x2440, GID_LSL1, Common::kPlatformAtariST), + + // Leisure Suit Larry 1 (Amiga) 1.05 6/26/87 # x.yyy + GAME_P("lsl1", "1.05 1987-06-26", "3f5d26d8834ca49c147fb60936869d56", 0x2440, GID_LSL1, Common::kPlatformAmiga), + + // Leisure Suit Larry 1 (IIgs) 1.0E + GAME_P("lsl1", "1.0E 1987", "5f9e1dd68d626c6d303131c119582ad4", 0x2440, GID_LSL1, Common::kPlatformApple2GS), + + // Leisure Suit Larry 1 (Mac) 1.05 6/26/87 + GAME_P("lsl1", "1.05 1987-06-26", "8a0076429890531832f0dc113285e31e", 0x2440, GID_LSL1, Common::kPlatformMacintosh), + + // Leisure Suit Larry 1 (CoCo3 158k/360k) [AGI 2.072] + GAME_PS("lsl1", "", "a2de1fe76565c3e8b40c9d036b5e5612", 198, 0x2440, GID_LSL1, Common::kPlatformCoCo3), + + // Manhunter NY (ST) 1.03 10/20/88 + GAME3_P("mh1", "1.03 1988-10-20", "mhdir", "f2d58056ad802452d60776ee920a52a6", 0x3149, 0, GID_MH1, Common::kPlatformAtariST), + + // Manhunter NY (IIgs) 2.0E 10/05/88 (CE) + GAME3_P("mh1", "2.0E 1988-10-05 (CE)", "mhdir", "2f1509f76f24e6e7d213f2dadebbf156", 0x3149, 0, GID_MH1, Common::kPlatformApple2GS), + + // Manhunter NY (Amiga) 1.06 3/18/89 + GAME3_P("mh1", "1.06 1989-03-18", "dirs", "92c6183042d1c2bb76236236a7d7a847", 0x3149, GF_OLDAMIGAV20, GID_MH1, Common::kPlatformAmiga), + + // reported by Filippos (thebluegr) in bugreport #1654500 + // Manhunter NY (PC 5.25") 1.22 8/31/88 [AGI 3.002.107] + GAME3_PS("mh1", "1.22 1988-08-31", "mhdir", "0c7b86f05fe02c2e26cff1b07450b82a", 2123, 0x3149, 0, GID_MH1, Common::kPlatformPC), + + // Manhunter NY (PC 3.5") 1.22 8/31/88 [AGI 3.002.102] + GAME3_PS("mh1", "1.22 1988-08-31", "mhdir", "5b625329021ad49fd0c1d6f2d6f54bba", 2141, 0x3149, 0, GID_MH1, Common::kPlatformPC), + + // Manhunter NY (CoCo3 720k) [AGI 2.023] + GAME_PS("mh1", "", "b968285caf2f591c78dd9c9e26ab8974", 495, 0x2440, GID_MH1, Common::kPlatformCoCo3), + + // Manhunter NY (CoCo3 360k/720k) [AGI 2.072] + GAME_PS("mh1", "updated", "d47da950c62289f8d4ccf36af73365f2", 495, 0x2440, GID_MH1, Common::kPlatformCoCo3), + + // Manhunter SF (ST) 1.0 7/29/89 + GAME3_P("mh2", "1.0 1989-07-29", "mh2dir", "5e3581495708b952fea24438a6c7e040", 0x3149, 0, GID_MH1, Common::kPlatformAtariST), + + // Manhunter SF (Amiga) 3.06 8/17/89 # 2.333 + GAME3_PS("mh2", "3.06 1989-08-17", "dirs", "b412e8a126368b76696696f7632d4c16", 2573, 0x3086, GF_OLDAMIGAV20, GID_MH2, Common::kPlatformAmiga), + + // Manhunter SF (PC 5.25") 3.03 8/17/89 [AGI 3.002.149] + GAME3("mh2", "3.03 1989-08-17 5.25\"", "mh2dir", "b90e4795413c43de469a715fb3c1fa93", 0x3149, GID_MH2), + + // Manhunter SF (PC 3.5") 3.02 7/26/89 [AGI 3.002.149] + GAME3("mh2", "3.02 1989-07-26 3.5\"", "mh2dir", "6fb6f0ee2437704c409cf17e081ba152", 0x3149, GID_MH2), + + // Manhunter SF (CoCo3 720k) [AGI 2.023] + GAME_PS("mh2", "", "acaaa577e10d1753c5a74f6ae1d858d4", 591, 0x2440, GID_MH2, Common::kPlatformCoCo3), + + // Manhunter SF (CoCo3 720k) [AGI 2.072] + GAME_PS("mh2", "updated", "c64875766700196e72a92359f70f45a9", 591, 0x2440, GID_MH2, Common::kPlatformCoCo3), + + // Mickey's Space Adventure + // Preagi game + GAMEpre_P("mickey", "", "1.pic", "b6ec04c91a05df374792872c4d4ce66d", 0x0000, GID_MICKEY, Common::kPlatformPC), + +#if 0 + // Mixed-Up Mother Goose (Amiga) 1.1 + // Problematic: crashes + // Menus not tested + GAME3_PS("mixedup", "1.1 1986-12-10", "dirs", "5c1295fe6daaf95831195ba12894dbd9", 2021, 0x3086, 0, GID_MIXEDUP, Common::kPlatformAmiga), +#endif + + // Mixed Up Mother Goose (IIgs) + GAME_P("mixedup", "1987", "3541954a7303467c6df87665312ffb6a", 0x2917, GID_MIXEDUP, Common::kPlatformApple2GS), + + // Mixed-Up Mother Goose (PC) [AGI 2.915] + GAME("mixedup", "1987-11-10", "e524655abf9b96a3b179ffcd1d0f79af", 0x2917, GID_MIXEDUP), + + // Mixed-Up Mother Goose (CoCo3 360k) [AGI 2.072] + GAME_PS("mixedup", "", "44e63e9b4d4822a31edea0e8a7e7eac4", 606, 0x2440, GID_MIXEDUP, Common::kPlatformCoCo3), + + // Police Quest 1 (PC) 2.0E 11/17/87 [AGI 2.915] + GAME("pq1", "2.0E 1987-11-17", "2fd992a92df6ab0461d5a2cd83c72139", 0x2917, GID_PQ1), + + // Police Quest 1 (Mac) 2.0G 12/3/87 + GAME_P("pq1", "2.0G 1987-12-03", "805750b66c1c5b88a214e67bfdca17a1", 0x2440, GID_PQ1, Common::kPlatformMacintosh), + + // Police Quest 1 (IIgs) 2.0B-88421 + GAME_P("pq1", "2.0B 1988-04-21", "e7c175918372336461e3811d594f482f", 0x2917, GID_PQ1, Common::kPlatformApple2GS), + + // Police Quest 1 (Amiga) 2.0B 2/22/89 # 2.310 + GAME3_PS("pq1", "2.0B 1989-02-22", "dirs", "cfa93e5f2aa7378bddd10ad6746a2ffb", 1613, 0x3149, 0, GID_PQ1, Common::kPlatformAmiga), + + // Police Quest 1 (IIgs) 2.0A-88318 + GAME_P("pq1", "2.0A 1988-03-18", "8994e39d0901de3d07cecfb954075bb5", 0x2917, GID_PQ1, Common::kPlatformApple2GS), + + // Police Quest 1 (PC) 2.0A 10/23/87 [AGI 2.903/2.911] + GAME("pq1", "2.0A 1987-10-23", "b9dbb305092851da5e34d6a9f00240b1", 0x2917, GID_PQ1), + + // Police Quest 1 (Russian) + GAME_LPS("pq1", "", "604cc8041d24c4c7e5fa8baf386ef76e", 360, Common::RU_RUS, 0x2917, GID_PQ1, Common::kPlatformPC), + + // Police Quest 1 2.0G 12/3/87 + GAME("pq1", "2.0G 1987-12-03 5.25\"/ST", "231f3e28170d6e982fc0ced4c98c5c1c", 0x2440, GID_PQ1), + + // Police Quest 1 (PC) 2.0G 12/3/87; entry from DAGII, but missing from Sarien? + // not sure about disk format -- dsymonds + GAME("pq1", "2.0G 1987-12-03", "d194e5d88363095f55d5096b8e32fbbb", 0x2917, GID_PQ1), + + // Police Quest 1 (CoCo3 360k) [AGI 2.023] + GAME_PS("pq1", "", "28a077041f75aab78f66804800940085", 375, 0x2440, GID_PQ1, Common::kPlatformCoCo3), + + // Police Quest 1 (CoCo3 360k) [AGI 2.072] + GAME_PS("pq1", "updated", "63b9a9c6eec154751dd446cd3693e0e2", 768, 0x2440, GID_PQ1, Common::kPlatformCoCo3), + + // Space Quest 1 (ST) 1.1A + // The original game did not have menus, they are enabled under ScummVM + GAME_FP("sq1", "1.1A 1986-02-06", "6421fb64b0e6604c9dd065975d9279e9", 0x2440, GF_MENUS, GID_SQ1, Common::kPlatformAtariST), + + // Space Quest 1 (PC 360k) 1.1A [AGI 2.272] + // The original game did not have menus, they are enabled under ScummVM + GAME_FP("sq1", "1.1A 1986-11-13", "8d8c20ab9f4b6e4817698637174a1cb6", 0x2272, GF_MENUS, GID_SQ1, Common::kPlatformPC), + + // Space Quest 1 (PC 720k) 1.1A [AGI 2.272] + // The original game did not have menus, they are enabled under ScummVM + GAME_FP("sq1", "1.1A 720kb", "0a92b1be7daf3bb98caad3f849868aeb", 0x2272, GF_MENUS, GID_SQ1, Common::kPlatformPC), + + // Space Quest 1 (Amiga) 1.2 # 2.082 + // The original game did not have menus, they are enabled under ScummVM + GAME_FP("sq1", "1.2 1986", "0b216d931e95750f1f4837d6a4b821e5", 0x2440, GF_MENUS | GF_OLDAMIGAV20, GID_SQ1, Common::kPlatformAmiga), + + // Space Quest 1 (Mac) 1.5D + GAME_P("sq1", "1.5D 1987-04-02", "ce88419aadd073d1c6682d859b3d8aa2", 0x2440, GID_SQ1, Common::kPlatformMacintosh), + + // Space Quest 1 (IIgs) 2.2 + GAME_P("sq1", "2.2 1987", "64b9b3d04c1066d36e6a6e56187a83f7", 0x2917, GID_SQ1, Common::kPlatformApple2GS), + + // Space Quest 1 (PC) 1.0X [AGI 2.089] + // Does not have menus, crashes if menus are enforced. Therefore, ESC pauses the game + GAME_FP("sq1", "1.0X 1986-09-24", "af93941b6c51460790a9efa0e8cb7122", 0x2089, GF_ESCPAUSE, GID_SQ1, Common::kPlatformPC), + + // Space Quest 1 (Russian) + GAME_LFPS("sq1", "", "a279eb8ddbdefdb1ea6adc827a1d632a", 372, Common::RU_RUS, 0x2089, GF_ESCPAUSE, GID_SQ1, Common::kPlatformPC), + + // Space Quest 1 (PC 5.25"/3.5") 2.2 [AGI 2.426/2.917] + GAME("sq1", "2.2 1987-05-07 5.25\"/3.5\"", "5d67630aba008ec5f7f9a6d0a00582f4", 0x2440, GID_SQ1), + + // Space Quest 1 (CoCo3 360k) [AGI 2.072] + GAME_PS("sq1", "", "5d67630aba008ec5f7f9a6d0a00582f4", 372, 0x2440, GID_SQ1, Common::kPlatformCoCo3), + + // Space Quest 1 (CoCo3 360k) [AGI 2.023] + GAME_PS("sq1", "fixed", "ca822b768b6462e410423ea7f498daee", 768, 0x2440, GID_SQ1, Common::kPlatformCoCo3), + + // Space Quest 1 (CoCo3 360k) [AGI 2.072] + GAME_PS("sq1", "updated", "7fa54e6bb7ffeb4cf20eca39d86f5fb2", 387, 0x2440, GID_SQ1, Common::kPlatformCoCo3), + + // Space Quest 2 (PC 3.5") 2.0D [AGI 2.936] + GAME("sq2", "2.0D 1988-03-14 3.5\"", "85390bde8958c39830e1adbe9fff87f3", 0x2936, GID_SQ2), + + // Space Quest 2 (IIgs) 2.0A 7/25/88 (CE) + GAME_P("sq2", "2.0A 1988-07-25 (CE)", "5dfdac98dd3c01fcfb166529f917e911", 0x2936, GID_SQ2, Common::kPlatformApple2GS), + + { + // Space Quest 2 (Amiga) 2.0F + { + "sq2", + "2.0F 1986-12-09 [VOL.2->PICTURE.16 broken]", + { + { "logdir", 0, "28add5125484302d213911df60d2aded", 426}, + { "object", 0, "5dc52be721257719f4b311a84ce22b16", 372}, + { NULL, 0, NULL, 0} + }, + Common::EN_ANY, + Common::kPlatformAmiga, + ADGF_NO_FLAGS, + GUIO_NONE + }, + GID_SQ2, + GType_V2, + 0, + 0x2936, + }, + + + // Space Quest 2 (Mac) 2.0D + GAME_P("sq2", "2.0D 1988-04-04", "bfbebe0b59d83f931f2e1c62ce9484a7", 0x2936, GID_SQ2, Common::kPlatformMacintosh), + + // reported by Filippos (thebluegr) in bugreport #1654500 + // Space Quest 2 (PC 5.25") 2.0A [AGI 2.912] + GAME_PS("sq2", "2.0A 1987-11-06 5.25\"", "ad7ce8f800581ecc536f3e8021d7a74d", 423, 0x2917, GID_SQ2, Common::kPlatformPC), + + // Space Quest 2 (Russian) + GAME_LPS("sq2", "", "ba21c8934caf28e3ba45ce7d1cd6b041", 423, Common::RU_RUS, 0x2917, GID_SQ2, Common::kPlatformPC), + + // Space Quest 2 (PC 3.5") 2.0A [AGI 2.912] + GAME_PS("sq2", "2.0A 1987-11-06 3.5\"", "6c25e33d23b8bed42a5c7fa63d588e5c", 423, 0x2917, GID_SQ2, Common::kPlatformPC), + + // Space Quest 2 (PC 5.25"/ST) 2.0C/A [AGI 2.915] + // Menus not tested + GAME("sq2", "2.0C/A 5.25\"/ST", "bd71fe54869e86945041700f1804a651", 0x2917, GID_SQ2), + + // Space Quest 2 (PC 3.5") 2.0F [AGI 2.936] + GAME("sq2", "2.0F 1989-01-05 3.5\"", "28add5125484302d213911df60d2aded", 0x2936, GID_SQ2), + + // Space Quest 2 (CoCo3 360k) [AGI 2.023] + GAME_PS("sq2", "", "12973d39b892dc9d280257fd271e9597", 768, 0x2440, GID_SQ2, Common::kPlatformCoCo3), + + // Space Quest 2 (CoCo3 360k) [AGI 2.072] + GAME_PS("sq2", "updated", "d24f19b047e65e1763eff4b46f3d50df", 768, 0x2440, GID_SQ2, Common::kPlatformCoCo3), + + // Troll's Tale + GAMEpre_PS("troll", "", "troll.img", "62903f264b3d849be4214b3a5c42a2fa", 184320, 0x0000, GID_TROLL, Common::kPlatformPC), + + // Winnie the Pooh in the Hundred Acre Wood + GAMEpre_P("winnie", "", "title.pic", "2e7900c1ccaa7671d65405f6d1efed30", 0x0000, GID_WINNIE, Common::kPlatformPC), + + // Winnie the Pooh in the Hundred Acre Wood (Amiga) + GAMEpre_P("winnie", "", "title", "2e7900c1ccaa7671d65405f6d1efed30", 0x0000, GID_WINNIE, Common::kPlatformAmiga), + + // Winnie the Pooh in the Hundred Acre Wood (C64) + GAMEpre_P("winnie", "", "title.pic", "d4eb97cffc866110f71e1ec9f84fe643", 0x0000, GID_WINNIE, Common::kPlatformC64), + + // Winnie the Pooh in the Hundred Acre Wood (Apple //gs) + GAMEpre_P("winnie", "", "title.pic", "45e06010a3c61d78f4661103c901ae11", 0x0000, GID_WINNIE, Common::kPlatformApple2GS), + + // Xmas Card 1986 (PC) [AGI 2.272] + GAME("xmascard", "1986-11-13 [version 1]", "3067b8d5957e2861e069c3c0011bd43d", 0x2272, GID_XMASCARD), + + // Xmas Card 1986 (CoCo3 360k) [AGI 2.072] + GAME_PS("xmascard", "", "25ad35e9628fc77e5e0dd35852a272b6", 768, 0x2440, GID_XMASCARD, Common::kPlatformCoCo3), + + FANMADE_F("2 Player Demo", "4279f46b3cebd855132496476b1d2cca", GF_AGIMOUSE), + FANMADE("AGI Contest 1 Template", "d879aed25da6fc655564b29567358ae2"), + FANMADE("AGI Contest 2 Template", "5a2fb2894207eff36c72f5c1b08bcc07"), + FANMADE("AGI Mouse Demo 0.60 demo 1", "c07e2519de674c67386cb2cc6f2e3904"), + FANMADE("AGI Mouse Demo 0.60 demo 2", "cc49d8b88ed6faf4f53ce92c84e0fe1b"), + FANMADE("AGI Mouse Demo 0.70", "3497c291e4afb6f758e61740678a2aec"), + FANMADE_F("AGI Mouse Demo 1.00", "20397f0bf0ef936f416bb321fb768fc7", GF_AGIMOUSE), + FANMADE_F("AGI Mouse Demo 1.10", "f4ad396b496d6167635ad0b410312ab8", GF_AGIMOUSE|GF_AGIPAL), + FANMADE("AGI Piano (v1.0)", "8778b3d89eb93c1d50a70ef06ef10310"), + FANMADE("AGI Quest (v1.46-TJ0)", "1cf1a5307c1a0a405f5039354f679814"), + GAME("tetris", "", "7a874e2db2162e7a4ce31c9130248d8a", 0x2917, GID_FANMADE), + FANMADE_V("AGI Trek (Demo)", "c02882b8a8245b629c91caf7eb78eafe", 0x2440), + FANMADE_F("AGI256 Demo", "79261ac143b2e2773b2753674733b0d5", GF_AGI256), + FANMADE_F("AGI256-2 Demo", "3cad9b3aff1467cebf0c5c5b110985c5", GF_AGI256_2), + FANMADE_LF("Abrah: L'orphelin de l'espace (v1.2)", "b7b6d1539e14d5a26fa3088288e1badc", Common::FR_FRA, GF_AGIPAL), + FANMADE("Acidopolis", "7017db1a4b726d0d59e65e9020f7d9f7"), + FANMADE("Agent 0055 (v1.0)", "c2b34a0c77acb05482781dda32895f24"), + FANMADE("Agent 06 vs. The Super Nazi", "136f89ca9f117c617e88a85119777529"), + FANMADE("Agent Quest", "59e49e8f72058a33c00d60ee1097e631"), + FANMADE("Al Pond - On Holiday (v1.0)", "a84975496b42d485920e886e92eed68b"), + FANMADE("Al Pond - On Holiday (v1.1)", "7c95ac4689d0c3bfec61e935f3093634"), + FANMADE("Al Pond - On Holiday (v1.3)", "8f30c260de9e1dd3d8b8f89cc19d2633"), + FANMADE("Al Pond 1 - Al Lives Forever (v1.0)", "e8921c3043b749b056ff51f56d1b451b"), + FANMADE("Al Pond 1 - Al Lives Forever (v1.3)", "fb4699474054962e0dbfb4cf12ca52f6"), + FANMADE("Apocalyptic Quest (v0.03 Teaser)", "42ced528b67965d3bc3b52c635f94a57"), + FANMADE_F("Apocalyptic Quest (v4.00 Alpha 1)", "e15581628d84949b8d352d224ec3184b", GF_AGIMOUSE), + FANMADE_F("Apocalyptic Quest (v4.00 Alpha 2)", "0eee850005860e46345b38fea093d194", GF_AGIMOUSE), + FANMADE_F("Band Quest (Demo)", "7326abefd793571cc17ed0db647bdf34", GF_AGIMOUSE), + FANMADE_F("Band Quest (Early Demo)", "de4758dd34676b248c8301b32d93bc6f", GF_AGIMOUSE), + FANMADE("Beyond the Titanic 2", "9b8de38dc64ffb3f52b7877ea3ebcef9"), + FANMADE("Biri Quest 1", "1b08f34f2c43e626c775c9d6649e2f17"), + FANMADE("Bob The Farmboy", "e4b7df9d0830addee5af946d380e66d7"), + FANMADE_F("Boring Man 1: The Toad to Robinland", "d74481cbd227f67ace37ce6a5493039f", GF_AGIMOUSE), + FANMADE_F("Boring Man 2: Ho Man! This Game Sucks!", "250032ba105bdf7c1bc4fed767c2d37e", GF_AGIMOUSE), + FANMADE("Botz", "a8fabe4e807adfe5ec02bfec6d983695"), + FANMADE("Brian's Quest (v1.0)", "0964aa79b9cdcff7f33a12b1d7e04b9c"), + FANMADE("CPU-21 (v1.0)", "35b7cdb4d17e890e4c52018d96e9cbf4"), + GAME("caitlyn", "Demo", "5b8a3cdb2fc05469f8119d49f50fbe98", 0x2917, GID_FANMADE), + GAME("caitlyn", "", "818469c484cae6dad6f0e9a353f68bf8", 0x2917, GID_FANMADE), + FANMADE("Car Driver (v1.1)", "2311611d2d36d20ccc9da806e6cba157"), + FANMADE("Cloak of Darkness (v1.0)", "5ba6e18bf0b53be10db8f2f3831ee3e5"), + FANMADE("Coco Coq (English) - Coco Coq In Grostesteing's Base (v.1.0.3)", "97631f8e710544a58bd6da9e780f9320"), + FANMADE_L("Coco Coq (French) - Coco Coq Dans la Base de Grostesteing (v1.0.2)", "ef579ebccfe5e356f9a557eb3b2d8649", Common::FR_FRA), + FANMADE("Corby's Murder Mystery (v1.0)", "4ebe62ac24c5a8c7b7898c8eb070efe5"), + FANMADE_F("DG: The AGIMouse Adventure (English v1.1)", "efe453b92bc1487ea69fbebede4d5f26", GF_AGIMOUSE|GF_AGIPAL), + FANMADE_LF("DG: The AGIMouse Adventure (French v1.1)", "eb3d17ca466d672cbb95947e8d6e846a", Common::FR_FRA, GF_AGIMOUSE|GF_AGIPAL), + FANMADE("DG: The Adventure Game (English v1.1)", "0d6376d493fa7a21ec4da1a063e12b25"), + FANMADE_L("DG: The Adventure Game (French v1.1)", "258bdb3bb8e61c92b71f2f456cc69e23", Common::FR_FRA), + FANMADE("Dashiki (16 Colors)", "9b2c7b9b0283ab9f12bedc0cb6770a07"), + FANMADE_F("Dashiki (256 Colors)", "c68052bb209e23b39b55ff3d759958e6", GF_AGIMOUSE|GF_AGI256), + FANMADE("Date Quest 1 (v1.0)", "ba3dcb2600645be53a13170aa1a12e69"), + FANMADE("Date Quest 2 (v1.0 Demo)", "1602d6a2874856e928d9a8c8d2d166e9"), + FANMADE("Date Quest 2 (v1.0)", "f13f6fc85aa3e6e02b0c20408fb63b47"), + FANMADE("Dave's Quest (v0.07)", "f29c3660de37bacc1d23547a167f27c9"), + FANMADE("Dave's Quest (v0.17)", "da3772624cc4a86f7137db812f6d7c39"), + FANMADE("Disco Nights (Demo)", "dc5a2b21182ba38bdcd992a3a978e690"), + FANMADE("Dogs Quest - The Quest for the Golden Bone (v1.0)", "f197357edaaea0ff70880602d2f09b3e"), + FANMADE("Dr. Jummybummy's Space Adventure", "988bd81785f8a452440a2a8ac67f96aa"), + FANMADE("Ed Ward", "98be839b9f30cbedea4c9cee5442d827"), + FANMADE("Elfintard", "c3b847e9e9e978af9708df76a0751dc2"), + FANMADE("Enclosure (v1.01)", "f08e66fee9ecdde77db7ee9a10c96ba2"), + FANMADE("Enclosure (v1.03)", "e4a0613ed02401502e506ba3565a8c40"), + FANMADE_SVP("Enclosure", "fe98e6126db74c6cc6fd8fe395cc6e8c", 345, 0x2440, Common::kPlatformCoCo3), + FANMADE("Epic Fighting (v0.1)", "aff24a1b3bdd676187685c4d95ba4294"), + FANMADE("Escape Quest (v0.0.3)", "2346b65619b1da0298b715b06d1a45a1"), + FANMADE("Escape from the Desert (beta 1)", "dfdc634d340854bd6ece28024010758d"), + FANMADE("Escape from the Salesman", "e723ca4fe0f6f56affe039fbb4dbeb6c"), + FANMADE("Fu$k Quest 1 (final)", "1cd0587422313f6ca77d6a95988e88ed"), + FANMADE("Fu$k Quest 1", "1cd0587422313f6ca77d6a95988e88ed"), + FANMADE("Fu$k Quest 2 - Romancing the Bone (Teaser)", "d288355d71d9bb1639260ccaa3b2fbfe"), + FANMADE("Fu$k Quest 2 - Romancing the Bone", "294beeb7765c7ea6b05ed7b9bf7bff4f"), + FANMADE("Gennadi Tahab Autot - Mission Pack 1 - Kuressaare", "bfa5fe71978e6ccf3d4eedd430124015"), + FANMADE("Go West, Young Hippie", "ff31484ea465441cb5f3a0f8e956b716"), + FANMADE("Good Man (demo v3.41)", "3facd8a8f856b7b6e0f6c3200274d88c"), + + { + // Groza + { + "agi-fanmade", + "Groza (russian) [AGDS sample]", + AD_ENTRY1("logdir", "421da3a18004122a966d64ab6bd86d2e"), + Common::RU_RUS, + Common::kPlatformPC, + ADGF_USEEXTRAASTITLE, + GUIO_NONE + }, + GID_FANMADE, + GType_V2, + GF_AGDS, + 0x2440, + }, + + { + // Get Outta SQ + { + "agi-fanmade", + "Get Outta Space Quest", + AD_ENTRY1("logdir", "aaea5b4a348acb669d13b0e6f22d4dc9"), + Common::EN_ANY, + Common::kPlatformPC, + ADGF_USEEXTRAASTITLE, + GUIO_NONE + }, + GID_GETOUTTASQ, + GType_V2, + 0, + 0x2440, + }, + + FANMADE_F("Half-Death - Terror At White-Mesa", "b62c05d0ace878261392073f57ae788c", GF_AGIMOUSE), + FANMADE("Hank's Quest (v1.0 English) - Victim of Society", "64c15b3d0483d17888129100dc5af213"), + FANMADE("Hank's Quest (v1.1 English) - Victim of Society", "86d1f1dd9b0c4858d096e2a60cca8a14"), + FANMADE_L("Hank's Quest (v1.81 Dutch) - Slachtoffer Van Het Gebeuren", "41e53972d55ff3dff9e90d15fe1b659f", Common::NL_NLD), + FANMADE("Hank's Quest (v1.81 English) - Victim of Society", "7a776383282f62a57c3a960dafca62d1"), + FANMADE("Herbao (v0.2)", "6a5186fc8383a9060517403e85214fc2"), + FANMADE_F("Hitler's Legacy (v.0004q)", "a412881269ba34584bd0a3268e5a9863", GF_AGIMOUSE), + FANMADE("Hobbits", "4a1c1ef3a7901baf0ab45fde0cfadd89"), + FANMADE_F("Isabella Coq - A Present For My Dad", "55c6819f2330c4d5d6459874c9f123d9", GF_AGIMOUSE), + FANMADE("Jack & Julia - VAMPYR", "8aa0b9a26f8d5a4421067ab8cc3706f6"), + FANMADE("Jeff's Quest (v.5 alpha Jun 1)", "10f1720eed40c12b02a0f32df3e72ded"), + FANMADE("Jeff's Quest (v.5 alpha May 31)", "51ff71c0ed90db4e987a488ed3bf0551"), + FANMADE("Jen's Quest (Demo 1)", "361afb5bdb6160213a1857245e711939"), + FANMADE("Jen's Quest (Demo 2)", "3c321eee33013b289ab8775449df7df2"), + FANMADE("Jiggy Jiggy Uh! Uh!", "bc331588a71e7a1c8840f6cc9b9487e4"), + FANMADE("Jimmy In: The Alien Attack (v0.1)", "a4e9db0564a494728de7873684a4307c"), + FANMADE("Joe McMuffin In \"What's Cooking, Doc\" (v1.0)", "8a3de7e61a99cb605fa6d233dd91c8e1"), + FANMADE_LVF("Jolimie, le Village Maudit (v0.5)", "21818501636b3cb8ad5de5c1a66de5c2", Common::FR_FRA, 0x2936, GF_AGIMOUSE|GF_AGIPAL), + FANMADE_LVF("Jolimie, le Village Maudit (v1.1)", "68d7aef1161bb5972fe03efdf29ccb7f", Common::FR_FRA, 0x2936, GF_AGIMOUSE|GF_AGIPAL), + FANMADE("Journey Of Chef", "aa0a0b5a6364801ae65fdb96d6741df5"), + FANMADE("Jukebox (v1.0)", "c4b9c5528cc67f6ba777033830de7751"), + FANMADE("Justin Quest (v1.0 in development)", "103050989da7e0ffdc1c5e1793a4e1ec"), + FANMADE("J\xf5ulumaa (v0.05) (Estonian)", "53982ecbfb907e41392b3961ad1c3475"), + FANMADE("Kings Quest 2 - Breast Intentions (v2.0 Mar 26)", "a25d7379d281b1b296d4785df90a8e78"), + FANMADE("Kings Quest 2 - Breast Intentions (v2.0 Aug 16)", "6b4f796d0421d2e12e501b511962e03a"), + FANMADE("Lasse Holm: The Quest for Revenge (v1.0)", "f9fbcc8a4ef510bfbb92423296ff4abb"), + FANMADE("Lawman for Hire", "c78b28bfd3767dd455b992cd8b7854fa"), + FANMADE("Lefty Goes on Vacation (Not in The Right Place)", "ccdc49a33870310b01f2c48b8a1f3c34"), + FANMADE("Les Ins\xe3parables (v1.0)", "4b780887cab0ecabc5eca319acb3acf2"), + FANMADE("Little Pirate (Demo 2 v0.6)", "437068efe4ec32d436da09d6f2ea56e1"), + FANMADE("Lost Eternity (v1.0)", "95f15c5632feb8a39e9ca3d9af35fcc9"), + FANMADE("MD Quest - The Search for Michiel (v0.10)", "2a6fcb21d2b5e4144c38ed817fabe8ee"), + FANMADE("Maale Adummin Quest", "ddfbeb33feb7cf78504fe4dba14ec63b"), + FANMADE("Monkey Man", "2322d03f997e8cc235d4578efff69cfa"), + FANMADE_F("Napalm Quest (v0.5)", "b659afb491d967bb34810d1c6ce22093", GF_AGIMOUSE), + FANMADE("Naturette 1 (English v1.2)", "0a75884e7f010974a230bdf269651117"), + FANMADE("Naturette 1 (English v1.3)", "f15bbf999ac55ebd404aa1eb84f7c1d9"), + FANMADE_L("Naturette 1 (French v1.2)", "d3665622cc41aeb9c7ecf4fa43f20e53", Common::FR_FRA), + FANMADE_F("Naturette 2: Daughter of the Moon (v1.0)", "bdf76a45621c7f56d1c9d40292c6137a", GF_AGIMOUSE|GF_AGIPAL), + FANMADE_F("Naturette 3: Adventure in Treeworld (v1.0a)", "6dbb0e7fc75fec442e6d9e5a06f1530e", GF_AGIMOUSE|GF_AGIPAL), + FANMADE_F("Naturette 4: From a Planet to Another Planet (Not Finished)", "13be8cd9cf35aeff0a39b8757057fbc8", GF_AGIMOUSE), + // FIXME: Actually Naturette 4 has both English and French language support built into it. How to add that information? + FANMADE_F("Naturette 4: From a Planet to Another Planet (2007-10-05)", "8253706b6ef5423a79413b216760297c", GF_AGIMOUSE|GF_AGIPAL), + FANMADE("New AGI Hangman Test", "d69c0e9050ccc29fd662b74d9fc73a15"), + FANMADE("Nick's Quest - In Pursuit of QuakeMovie (v2.1 Gold)", "e29cbf9222551aee40397fabc83eeca0"), + FANMADE_F("Open Mic Night (v0.1)", "70000a2f67aac27d1133d019df70246d", GF_AGIMOUSE|GF_AGIPAL), + FANMADE("Operation: Recon", "0679ce8405411866ccffc8a6743370d0"), + FANMADE("Patrick's Quest (Demo v1.0)", "f254f5b894b98fec5f92acc07fb62841"), + FANMADE("Phantasmagoria", "87d20c1c11aee99a4baad3797b63146b"), + FANMADE("Pharaoh Quest (v0.0)", "51c630899d076cf799e573dadaa2276d"), + FANMADE("Phil's Quest - the Search for Tolbaga", "5e7ca45c360e03164b8358e49900c588"), + FANMADE("Pinkun Maze Quest (v0.1)", "148ff0843af389928b3939f463bfd20d"), + FANMADE("Pirate Quest", "bb612a919ed2b9ea23bbf03ce69fed42"), + FANMADE("Pothead (v0.1)", "d181101385d3a45082f418cd4b3c5b01"), + FANMADE("President's Quest", "4937d0e8ecadb7888faeb347799b0388"), + FANMADE("Prince Quest", "266248d75c3130c8ccc9c9bf2ad30a0d"), + FANMADE("Professor (English) - The Professor is Missing (Mar 17)", "6232de31cc204affdf2e92dfe3dc0e4d"), + FANMADE("Professor (English) - The Professor is Missing (Mar 22)", "b5fcf0ca2f0d1c073be82f01e2170961"), + FANMADE_L("Professor (French) - Le Professeur a Disparu", "7d9f8a4d4610bb9b0b97caa17590c2d3", Common::FR_FRA), + FANMADE("Quest for Glory VI - Hero's Adventure", "d26765c3075064c80d284c5e06e33a7e"), + FANMADE("Quest for Home", "d2895dc1cd3930f2489af0f843b144b3"), + FANMADE("Quest for Ladies (demo v1.1 Apr 1)", "3f6e02f16e1154a0daf296c8895edd97"), + FANMADE("Quest for Ladies (demo v1.1 Apr 6)", "f75e7b6a0769a3fa926eea0854711591"), + FANMADE("Quest for Piracy 1 - Enter the Silver Pirate (v0.15)", "d23f5c2a26f6dc60c686f8a2436ea4a6"), + FANMADE("Quest for a Record Deal", "f4fbd7abf056d2d3204f790da5ac89ab"), + FANMADE("Ralph's Quest (v0.1)", "5cf56378aa01a26ec30f25295f0750ca"), + FANMADE("Residence 44 Quest (Dutch v0.99)", "7c5cc64200660c70240053b33d379d7d"), + FANMADE("Residence 44 Quest (English v0.99)", "fe507851fddc863d540f2bec67cc67fd"), + FANMADE("Residence 44 Quest (English v1.0a)", "f99e3f69dc8c77a45399da9472ef5801"), + FANMADE("SQ2Eye (v0.3)", "2be2519401d38ad9ce8f43b948d093a3"), + // FANMADE("SQ2Eye (v0.4)", "2be2519401d38ad9ce8f43b948d093a3"), + FANMADE("SQ2Eye (v0.41)", "f0e82c55f10eb3542d7cd96c107ae113"), + FANMADE("SQ2Eye (v0.42)", "d7beae55f6328ef8b2da47b1aafea40c"), + FANMADE("SQ2Eye (v0.43)", "2a895f06e45de153bb4b77c982009e06"), + FANMADE("SQ2Eye (v0.44)", "5174fc4b6d8a477ba0ff0575cd64e0aa"), + FANMADE("SQ2Eye (v0.45)", "6e06f8bb7b90ce6f6aabf1a0e620159c"), + FANMADE("SQ2Eye (v0.46)", "bf0ad7a035ff9113951d09d1efe380c4"), + FANMADE("SQ2Eye (v0.47)", "85dc3be1d33ff932c292b74f9037abaa"), + FANMADE("SQ2Eye (v0.48)", "587574252972a5b5c070a647973a9b4a"), + FANMADE("SQ2Eye (v0.481)", "fc9234beb49804ae869696ce5af8ef30"), + FANMADE("SQ2Eye (v0.482)", "3ed84b7b87fa6840f25c15f250a11ffb"), + FANMADE("SQ2Eye (v0.483)", "647c31298d3f9cda641231b893e347c0"), + FANMADE("SQ2Eye (v0.484)", "f2c86fae7b9046d408c62c8c49a4b882"), + FANMADE("SQ2Eye (v0.485)", "af59e36bc28f44545458b68a93e91e67"), + FANMADE("SQ2Eye (v0.486)", "3fd86436e93456770dbdd4593eded70a"), + FANMADE("Save Santa (v1.0)", "4644f6beb5802081772f14be56ae196c"), + FANMADE("Save Santa (v1.3)", "f8afdb6efc5af5e7c0228b44633066af"), + FANMADE("Schiller (preview 1)", "ade39dea968c959cfebe1cf935d653e9"), + FANMADE("Schiller (preview 2)", "62cd1f8fc758bf6b4aa334e553624cef"), + GAME_F("serguei1", "v1.0", "b86725f067e456e10cdbdf5f58e01dec", 0x2917, GF_FANMADE|GF_AGIMOUSE|GF_AGIPAL, GID_FANMADE), + // FIXME: The following two entries have identical MD5 checksums? + GAME_F("serguei1", "v1.1 2002 Sep 5", "91975c1fb4b13b0f9a8e9ff74731030d", 0x2917, GF_FANMADE|GF_AGIMOUSE|GF_AGIPAL, GID_FANMADE), + GAME_F("serguei1", "v1.1 2003 Apr 10", "91975c1fb4b13b0f9a8e9ff74731030d", 0x2917, GF_FANMADE|GF_AGIMOUSE|GF_AGIPAL, GID_FANMADE), + GAME_F("serguei2", "v0.1.1 Demo", "906ccbc2ddedb29b63141acc6d10cd28", 0x2917, GF_FANMADE|GF_AGIMOUSE, GID_FANMADE), + GAME_F("serguei2", "v1.3.1 Demo (March 22nd 2008)", "ad1308fcb8f48723cd388e012ebf5e20", 0x2917, GF_FANMADE|GF_AGIMOUSE|GF_AGIPAL, GID_FANMADE), + FANMADE("Shifty (v1.0)", "2a07984d27b938364bf6bd243ac75080"), + FANMADE_F("Sliding Tile Game (v1.00)", "949bfff5d8a81c3139152eed4d84ca75", GF_AGIMOUSE), + FANMADE("Snowboarding Demo (v1.0)", "24bb8f29f1eddb5c0a099705267c86e4"), + FANMADE("Solar System Tour", "b5a3d0f392dfd76a6aa63f3d5f578403"), + FANMADE("Sorceror's Appraisal", "fe62615557b3cb7b08dd60c9d35efef1"), + GAME("sq0", "v1.03", "d2fd6f7404e86182458494e64375e590", 0x2917, GID_FANMADE), + GAME("sq0", "v1.04", "2ad9d1a4624a98571ee77dcc83f231b6", 0x2917, GID_FANMADE), + GAME_PS("sq0", "", "e1a8e4efcce86e1efcaa14633b9eb986", 762, 0x2440, GID_FANMADE, Common::kPlatformCoCo3), + GAME("sqx", "v10.0 Feb 05", "c992ae2f8ab18360404efdf16fa9edd1", 0x2917, GID_FANMADE), + GAME("sqx", "v10.0 Jul 18", "812edec45cefad559d190ffde2f9c910", 0x2917, GID_FANMADE), + GAME_PS("sqx", "", "f0a59044475a5fa37c055d8c3eb4d1a7", 768, 0x2440, GID_FANMADE, Common::kPlatformCoCo3), + FANMADE_F("Space Quest 3.5", "c077bc28d7b36213dd99dc9ecb0147fc", GF_AGIMOUSE|GF_AGIPAL), + FANMADE_F("Space Trek (v1.0)", "807a1aeadb2ace6968831d36ab5ea37a", GF_CLIPCOORDS), + FANMADE("Special Delivery", "88764dfe61126b8e73612c851b510a33"), + FANMADE("Speeder Bike Challenge (v1.0)", "2deb25bab379285ca955df398d96c1e7"), + FANMADE("Star Commander 1 - The Escape (v1.0)", "a7806f01e6fa14ebc029faa58f263750"), + FANMADE("Star Pilot: Bigger Fish", "8cb26f8e1c045b75c6576c839d4a0172"), + FANMADE_F("Street Quest (Demo)", "cf2aa94a7eb78dce6892c37f03e310d6", GF_AGIPAL), + FANMADE("Tales of the Tiki", "8103c9c87e3964690a14a3d0d83f7ddc"), + FANMADE("Tex McPhilip 1 - Quest For The Papacy", "3c74b9a24b51aa8020ac82bee3132266"), + FANMADE("Tex McPhilip 2 - Road To Divinity (v1.5)", "7387e8df854440bc26620ca0ea43af9a"), + FANMADE("Tex McPhilip 3 - A Destiny of Sin (Demo v0.25)", "992d12031a486ad84e592ff5d7c9d782"), + FANMADE("The 13th Disciple (v1.00)", "887719ad59afce9a41ec057dbb73ad73"), + FANMADE("The Adventures of a Crazed Hermit", "6e3086cbb794d3299a9c5a9792295511"), + FANMADE("The Grateful Dead", "c2146631afacf8cb455ce24f3d2d46e7"), + FANMADE("The Legend of Shay-Larah 1 - The Lost Prince", "04e720c8e30c9cf12db22ea14a24a3dd"), + FANMADE("The Legend of Zelda: The Fungus of Time (Demo v1.00)", "dcaf8166ceb62a3d9b9aea7f3b197c09"), + FANMADE("The Legendary Harry Soupsmith (Demo 1998 Apr 2)", "64c46b0d6fc135c9835afa80980d2831"), + FANMADE("The Legendary Harry Soupsmith (Demo 1998 Aug 19)", "8d06d82970f2c591d880a95476efbcf0"), + FANMADE("The Long Haired Dude: Encounter of the 18-th Kind", "86ea17b9fc2f3e537a7e40863d352c29"), + FANMADE("The Lost Planet (v0.9)", "590dffcbd932a9fbe554be13b769cac0"), + FANMADE("The Lost Planet (v1.0)", "58564df8b6394612dd4b6f5c0fd68d44"), + FANMADE("The New Adventure of Roger Wilco (v1.00)", "e5f0a7cb8d49f66b89114951888ca688"), + FANMADE("The Ruby Cast (v0.02)", "ed138e461bb1516e097007e017ab62df"), + FANMADE("The Shadow Plan", "c02cd10267e721f4e836b1431f504a0a"), + FANMADE("Time Quest (Demo v0.1)", "12e1a6f03ea4b8c5531acd0400b4ed8d"), + FANMADE("Time Quest (Demo v0.2)", "7b710608abc99e0861ac59b967bf3f6d"), + FANMADE_SVP("Time Quest", "90314f473d8317be5cd1f0306f139aea", 300, 0x2440, Common::kPlatformCoCo3), + FANMADE("Tonight The Shrieking Corpses Bleed (Demo v0.11)", "bcc57a7c8d563fa0c333107ae1c0a6e6"), + FANMADE("Tonight The Shrieking Corpses Bleed (v1.01)", "36b38f621b38e8d104aa0807302dc8c9"), + FANMADE("Turks' Quest - Heir to the Planet", "3d19254b737c8b218e5bc4580542b79a"), + FANMADE("URI Quest (v0.173 Feb 27)", "3986eefcf546dafc45f920ae91a697c3"), + FANMADE("URI Quest (v0.173 Jan 29)", "494150940d34130605a4f2e67ee40b12"), + { + // V - The Graphical Adventure + { + "agi-fanmade", + "V - The Graphical Adventure (Demo 2)", + AD_ENTRY1s("vdir", "c71f5c1e008d352ae9040b77fcf79327", 3080), + Common::EN_ANY, + Common::kPlatformPC, + ADGF_USEEXTRAASTITLE, + GUIO_NONE + }, + GID_FANMADE, + GType_V3, + GF_FANMADE, + 0x3149, + }, + FANMADE_SVP("V - The Graphical Adventure", "1646eaade74f137a9041eb427a389969", 768, 0x2440, Common::kPlatformCoCo3), + + FANMADE("Voodoo Girl - Queen of the Darned (v1.2 2002 Jan 1)", "ae95f0c77d9a97b61420fd192348b937"), + FANMADE("Voodoo Girl - Queen of the Darned (v1.2 2002 Mar 29)", "11d0417b7b886f963d0b36789dac4c8f"), + FANMADE("Wizaro (v0.1)", "abeec1eda6eaf8dbc52443ea97ff140c"), + + { AD_TABLE_END_MARKER, 0, 0, 0, 0 } +}; + +/** + * The fallback game descriptor used by the AGI engine's fallbackDetector. + * Contents of this struct are to be overwritten by the fallbackDetector. + */ +static AGIGameDescription g_fallbackDesc = { + { + "", + "", + AD_ENTRY1(0, 0), // This should always be AD_ENTRY1(0, 0) in the fallback descriptor + Common::UNK_LANG, + Common::kPlatformPC, + ADGF_NO_FLAGS, + GUIO_NONE + }, + GID_FANMADE, + GType_V2, + GF_FANMADE, + 0x2917, +}; + +} // End of namespace Agi diff --git a/engines/agi/keyboard.cpp b/engines/agi/keyboard.cpp index 2bea49a807..62bcd5d8d8 100644 --- a/engines/agi/keyboard.cpp +++ b/engines/agi/keyboard.cpp @@ -104,10 +104,10 @@ int AgiEngine::handleController(int key) { VtEntry *v = &_game.viewTable[0]; int i; - // AGI 3.149 games and The Black Cauldron need KEY_ESCAPE to use menus + // AGI 3.149 games, The Black Cauldron and King's Quest 4 need KEY_ESCAPE to use menus // Games with the GF_ESCPAUSE flag need KEY_ESCAPE to pause the game if (key == 0 || - (key == KEY_ESCAPE && getVersion() != 0x3149 && getGameID() != GID_BC && !(getFeatures() & GF_ESCPAUSE)) ) + (key == KEY_ESCAPE && getVersion() != 0x3149 && getGameID() != GID_BC && getGameID() != GID_KQ4 && !(getFeatures() & GF_ESCPAUSE)) ) return false; if ((getGameID() == GID_MH1 || getGameID() == GID_MH2) && (key == KEY_ENTER) && @@ -121,7 +121,7 @@ int AgiEngine::handleController(int key) { if (_game.controllers[i].keycode == key) { debugC(3, kDebugLevelInput, "event %d: key press", _game.controllers[i].controller); _game.controllerOccured[_game.controllers[i].controller] = true; - report("event AC:%i occured\n", _game.controllers[i].controller); + report("event AC:%i occurred\n", _game.controllers[i].controller); return true; } } @@ -191,9 +191,8 @@ int AgiEngine::handleController(int key) { } } - v->flags &= ~ADJ_EGO_XY; - if (d || key == KEY_STATIONARY) { + v->flags &= ~ADJ_EGO_XY; v->direction = v->direction == d ? 0 : d; return true; } @@ -320,7 +319,7 @@ void AgiEngine::handleKeys(int key) { // Clear to start a new line _game.hasPrompt = 0; _game.inputBuffer[_game.cursorPos = 0] = 0; - debugC(3, kDebugLevelInput, "clear lines"); + debugC(3, kDebugLevelInput | kDebugLevelText, "clear lines"); clearLines(l, l + 1, bg); flushLines(l, l + 1); #ifdef __DS__ diff --git a/engines/agi/loader_v2.cpp b/engines/agi/loader_v2.cpp index 3d1c4fa2cf..de6f8d0653 100644 --- a/engines/agi/loader_v2.cpp +++ b/engines/agi/loader_v2.cpp @@ -232,7 +232,7 @@ int AgiLoader_v2::loadResource(int t, int n) { if (data != NULL) { // Freeing of the raw resource from memory is delegated to the createFromRawResource-function - _vm->_game.sounds[n] = AgiSound::createFromRawResource(data, _vm->_game.dirSound[n].len, n, *_vm->_sound); + _vm->_game.sounds[n] = AgiSound::createFromRawResource(data, _vm->_game.dirSound[n].len, n, *_vm->_sound, _vm->_soundemu); _vm->_game.dirSound[n].flags |= RES_LOADED; } else { ec = errBadResource; diff --git a/engines/agi/loader_v3.cpp b/engines/agi/loader_v3.cpp index cd97c44521..f145140768 100644 --- a/engines/agi/loader_v3.cpp +++ b/engines/agi/loader_v3.cpp @@ -227,19 +227,12 @@ uint8 *AgiLoader_v3::loadVolRes(AgiDir *agid) { compBuffer = (uint8 *)calloc(1, agid->clen + 32); fp.read(compBuffer, agid->clen); - if (x[2] & 0x80 || agid->len == agid->clen) { + if (x[2] & 0x80) { // compressed pic + data = _vm->_picture->convertV3Pic(compBuffer, agid->clen); + // compBuffer has been freed inside convertV3Pic() + } else if (agid->len == agid->clen) { // do not decompress data = compBuffer; - -#if 0 - // CM: added to avoid problems in - // convert_v2_v3_pic() when clen > len - // e.g. Sierra demo 4, first picture - // (Tue Mar 16 13:13:43 EST 1999) - agid->len = agid->clen; - - // Now removed to fix Gold Rush! in demo4 -#endif } else { // it is compressed data = (uint8 *)calloc(1, agid->len + 32); @@ -309,7 +302,6 @@ int AgiLoader_v3::loadResource(int t, int n) { unloadResource(rPICTURE, n); data = loadVolRes(&_vm->_game.dirPic[n]); if (data != NULL) { - data = _vm->_picture->convertV3Pic(data, _vm->_game.dirPic[n].len); _vm->_game.pictures[n].rdata = data; _vm->_game.dirPic[n].flags |= RES_LOADED; } else { @@ -324,7 +316,7 @@ int AgiLoader_v3::loadResource(int t, int n) { data = loadVolRes(&_vm->_game.dirSound[n]); if (data != NULL) { // Freeing of the raw resource from memory is delegated to the createFromRawResource-function - _vm->_game.sounds[n] = AgiSound::createFromRawResource(data, _vm->_game.dirSound[n].len, n, *_vm->_sound); + _vm->_game.sounds[n] = AgiSound::createFromRawResource(data, _vm->_game.dirSound[n].len, n, *_vm->_sound, _vm->_soundemu); _vm->_game.dirSound[n].flags |= RES_LOADED; } else { ec = errBadResource; diff --git a/engines/agi/module.mk b/engines/agi/module.mk index f031834c9d..2339d1019f 100644 --- a/engines/agi/module.mk +++ b/engines/agi/module.mk @@ -30,6 +30,11 @@ MODULE_OBJS := \ predictive.o \ saveload.o \ sound.o \ + sound_2gs.o \ + sound_coco3.o \ + sound_midi.o \ + sound_pcjr.o \ + sound_sarien.o \ sprite.o \ text.o \ view.o \ diff --git a/engines/agi/op_cmd.cpp b/engines/agi/op_cmd.cpp index d7e3ba416c..a60080186c 100644 --- a/engines/agi/op_cmd.cpp +++ b/engines/agi/op_cmd.cpp @@ -43,221 +43,201 @@ namespace Agi { #define p5 (p[5]) #define p6 (p[6]) -#define game g_agi->_game -#define g_sprites g_agi->_sprites -#define g_sound g_agi->_sound -#define g_gfx g_agi->_gfx -#define g_picture g_agi->_picture +#define ip _curLogic->cIP +#define vt _game.viewTable[p0] +#define vt_v _game.viewTable[_game.vars[p0]] -#define ip curLogic->cIP -#define vt game.viewTable[p0] -#define vt_v game.viewTable[game.vars[p0]] +#define _v _game.vars -static struct AgiLogic *curLogic; - -int timerHack; // Workaround for timer loop in MH1 - -#define _v game.vars -#define cmd(x) static void cmd_##x (AgiEngine *g_agi, uint8 *p) - -cmd(increment) { +void AgiEngine::cmd_increment(uint8 *p) { if (_v[p0] != 0xff) ++_v[p0]; } -cmd(decrement) { +void AgiEngine::cmd_decrement(uint8 *p) { if (_v[p0] != 0) --_v[p0]; } -cmd(assignn) { +void AgiEngine::cmd_assignn(uint8 *p) { _v[p0] = p1; - // WORKAROUND for a bug in fan game "Get outta SQ" + // WORKAROUND for a bug in fan _game "Get outta SQ" // Total number of points is stored in variable 7, which - // is then incorrectly assigned to 0. Thus, when the game + // is then incorrectly assigned to 0. Thus, when the _game // is restarted, "Points 0 of 0" is shown. We set the // variable to the correct value here // Fixes bug #1942476 - "AGI: Fan(Get Outta SQ) - Score // is lost on restart" - if (g_agi->getGameID() == GID_GETOUTTASQ && p0 == 7) + if (getGameID() == GID_GETOUTTASQ && p0 == 7) _v[p0] = 8; } -cmd(addn) { +void AgiEngine::cmd_addn(uint8 *p) { _v[p0] += p1; } -cmd(subn) { +void AgiEngine::cmd_subn(uint8 *p) { _v[p0] -= p1; } -cmd(assignv) { +void AgiEngine::cmd_assignv(uint8 *p) { _v[p0] = _v[p1]; } -cmd(addv) { +void AgiEngine::cmd_addv(uint8 *p) { _v[p0] += _v[p1]; } -cmd(subv) { +void AgiEngine::cmd_subv(uint8 *p) { _v[p0] -= _v[p1]; } -cmd(mul_n) { +void AgiEngine::cmd_mul_n(uint8 *p) { _v[p0] *= p1; } -cmd(mul_v) { +void AgiEngine::cmd_mul_v(uint8 *p) { _v[p0] *= _v[p1]; } -cmd(div_n) { +void AgiEngine::cmd_div_n(uint8 *p) { _v[p0] /= p1; } -cmd(div_v) { +void AgiEngine::cmd_div_v(uint8 *p) { _v[p0] /= _v[p1]; } -cmd(random) { - _v[p2] = g_agi->_rnd->getRandomNumber(p1 - p0) + p0; +void AgiEngine::cmd_random(uint8 *p) { + _v[p2] = _rnd->getRandomNumber(p1 - p0) + p0; } -cmd(lindirectn) { +void AgiEngine::cmd_lindirectn(uint8 *p) { _v[_v[p0]] = p1; } -cmd(lindirectv) { +void AgiEngine::cmd_lindirectv(uint8 *p) { _v[_v[p0]] = _v[p1]; } -cmd(rindirect) { +void AgiEngine::cmd_rindirect(uint8 *p) { _v[p0] = _v[_v[p1]]; } -cmd(set) { - g_agi->setflag(*p, true); +void AgiEngine::cmd_set(uint8 *p) { + setflag(*p, true); } -cmd(reset) { - g_agi->setflag(*p, false); +void AgiEngine::cmd_reset(uint8 *p) { + setflag(*p, false); } -cmd(toggle) { - g_agi->setflag(*p, !g_agi->getflag(*p)); +void AgiEngine::cmd_toggle(uint8 *p) { + setflag(*p, !getflag(*p)); } -cmd(set_v) { - g_agi->setflag(_v[p0], true); +void AgiEngine::cmd_set_v(uint8 *p) { + setflag(_v[p0], true); } -cmd(reset_v) { - g_agi->setflag(_v[p0], false); +void AgiEngine::cmd_reset_v(uint8 *p) { + setflag(_v[p0], false); } -cmd(toggle_v) { - g_agi->setflag(_v[p0], !g_agi->getflag(_v[p0])); +void AgiEngine::cmd_toggle_v(uint8 *p) { + setflag(_v[p0], !getflag(_v[p0])); } -cmd(new_room) { - g_agi->newRoom(p0); +void AgiEngine::cmd_new_room(uint8 *p) { + newRoom(p0); // WORKAROUND: Works around intro skipping bug (#1737343) in Gold Rush. // Intro was skipped because the enter-keypress finalizing the entering // of the copy protection string (Copy protection is in logic.128) was // left over to the intro scene (Starts with room 73 i.e. logic.073). // The intro scene checks for any keys pressed and if it finds any it - // jumps to the game's start (Room 1 i.e. logic.001). We clear the + // jumps to the _game's start (Room 1 i.e. logic.001). We clear the // keyboard buffer when the intro sequence's first room (Room 73) is // loaded so that no keys from the copy protection scene can be left - // over to cause the intro to skip to the game's start. - if (g_agi->getGameID() == GID_GOLDRUSH && p0 == 73) - game.keypress = 0; + // over to cause the intro to skip to the _game's start. + if (getGameID() == GID_GOLDRUSH && p0 == 73) + _game.keypress = 0; } -cmd(new_room_f) { - g_agi->newRoom(_v[p0]); +void AgiEngine::cmd_new_room_f(uint8 *p) { + newRoom(_v[p0]); } -cmd(load_view) { - g_agi->agiLoadResource(rVIEW, p0); +void AgiEngine::cmd_load_view(uint8 *p) { + agiLoadResource(rVIEW, p0); } -cmd(load_logic) { - g_agi->agiLoadResource(rLOGIC, p0); +void AgiEngine::cmd_load_logic(uint8 *p) { + agiLoadResource(rLOGIC, p0); } -cmd(load_sound) { - g_agi->agiLoadResource(rSOUND, p0); +void AgiEngine::cmd_load_sound(uint8 *p) { + agiLoadResource(rSOUND, p0); } -cmd(load_view_f) { - g_agi->agiLoadResource(rVIEW, _v[p0]); +void AgiEngine::cmd_load_view_f(uint8 *p) { + agiLoadResource(rVIEW, _v[p0]); } -cmd(load_logic_f) { - g_agi->agiLoadResource(rLOGIC, _v[p0]); +void AgiEngine::cmd_load_logic_f(uint8 *p) { + agiLoadResource(rLOGIC, _v[p0]); } -cmd(discard_view) { - g_agi->agiUnloadResource(rVIEW, p0); +void AgiEngine::cmd_discard_view(uint8 *p) { + agiUnloadResource(rVIEW, p0); } -cmd(object_on_anything) { +void AgiEngine::cmd_object_on_anything(uint8 *p) { vt.flags &= ~(ON_WATER | ON_LAND); } -cmd(object_on_land) { - debugC(4, kDebugLevelScripts, "p0 = %d", p0); +void AgiEngine::cmd_object_on_land(uint8 *p) { vt.flags |= ON_LAND; } -cmd(object_on_water) { - debugC(4, kDebugLevelScripts, "p0 = %d", p0); +void AgiEngine::cmd_object_on_water(uint8 *p) { vt.flags |= ON_WATER; } -cmd(observe_horizon) { - debugC(4, kDebugLevelScripts, "p0 = %d", p0); +void AgiEngine::cmd_observe_horizon(uint8 *p) { vt.flags &= ~IGNORE_HORIZON; } -cmd(ignore_horizon) { - debugC(4, kDebugLevelScripts, "p0 = %d", p0); +void AgiEngine::cmd_ignore_horizon(uint8 *p) { vt.flags |= IGNORE_HORIZON; } -cmd(observe_objs) { - debugC(4, kDebugLevelScripts, "p0 = %d", p0); +void AgiEngine::cmd_observe_objs(uint8 *p) { vt.flags &= ~IGNORE_OBJECTS; } -cmd(ignore_objs) { - debugC(4, kDebugLevelScripts, "p0 = %d", p0); +void AgiEngine::cmd_ignore_objs(uint8 *p) { vt.flags |= IGNORE_OBJECTS; } -cmd(observe_blocks) { - debugC(4, kDebugLevelScripts, "p0 = %d", p0); +void AgiEngine::cmd_observe_blocks(uint8 *p) { vt.flags &= ~IGNORE_BLOCKS; } -cmd(ignore_blocks) { - debugC(4, kDebugLevelScripts, "p0 = %d", p0); +void AgiEngine::cmd_ignore_blocks(uint8 *p) { vt.flags |= IGNORE_BLOCKS; } -cmd(set_horizon) { - debugC(4, kDebugLevelScripts, "p0 = %d", p0); - game.horizon = p0; +void AgiEngine::cmd_set_horizon(uint8 *p) { + _game.horizon = p0; } -cmd(get_priority) { +void AgiEngine::cmd_get_priority(uint8 *p) { _v[p1] = vt.priority; } -cmd(set_priority) { +void AgiEngine::cmd_set_priority(uint8 *p) { vt.flags |= FIXED_PRIORITY; vt.priority = p1; @@ -268,259 +248,256 @@ cmd(set_priority) { // It seems that in this scene, ego's priority is set to 8, but the priority of // the last dwarf with the soup bowls (view 152) is also set to 8, which causes // the dwarf to be drawn behind ego - // With this workaround, when the game scripts set the priority of view 152 + // With this workaround, when the _game scripts set the priority of view 152 // (seventh dwarf with soup bowls), ego's priority is set to 7 - // The game script itself sets priotity 8 for ego before she starts walking, + // The _game script itself sets priotity 8 for ego before she starts walking, // and then releases the fixed priority set on ego after ego is seated // Therefore, this workaround only affects that specific part of this scene // Ego is set to object 19 by script 54 - if (g_agi->getGameID() == GID_KQ4 && vt.currentView == 152) { - game.viewTable[19].flags |= FIXED_PRIORITY; - game.viewTable[19].priority = 7; + if (getGameID() == GID_KQ4 && vt.currentView == 152) { + _game.viewTable[19].flags |= FIXED_PRIORITY; + _game.viewTable[19].priority = 7; } } -cmd(set_priority_f) { +void AgiEngine::cmd_set_priority_f(uint8 *p) { vt.flags |= FIXED_PRIORITY; vt.priority = _v[p1]; } -cmd(release_priority) { +void AgiEngine::cmd_release_priority(uint8 *p) { vt.flags &= ~FIXED_PRIORITY; } -cmd(set_upper_left) { // do nothing (AGI 2.917) +void AgiEngine::cmd_set_upper_left(uint8 *p) { // do nothing (AGI 2.917) } -cmd(start_update) { - g_agi->startUpdate(&vt); +void AgiEngine::cmd_start_update(uint8 *p) { + startUpdate(&vt); } -cmd(stop_update) { - g_agi->stopUpdate(&vt); +void AgiEngine::cmd_stop_update(uint8 *p) { + stopUpdate(&vt); } -cmd(current_view) { +void AgiEngine::cmd_current_view(uint8 *p) { _v[p1] = vt.currentView; } -cmd(current_cel) { +void AgiEngine::cmd_current_cel(uint8 *p) { _v[p1] = vt.currentCel; debugC(4, kDebugLevelScripts, "v%d=%d", p1, _v[p1]); } -cmd(current_loop) { +void AgiEngine::cmd_current_loop(uint8 *p) { _v[p1] = vt.currentLoop; } -cmd(last_cel) { +void AgiEngine::cmd_last_cel(uint8 *p) { _v[p1] = vt.loopData->numCels - 1; } -cmd(set_cel) { - g_agi->setCel(&vt, p1); +void AgiEngine::cmd_set_cel(uint8 *p) { + setCel(&vt, p1); vt.flags &= ~DONTUPDATE; } -cmd(set_cel_f) { - g_agi->setCel(&vt, _v[p1]); +void AgiEngine::cmd_set_cel_f(uint8 *p) { + setCel(&vt, _v[p1]); vt.flags &= ~DONTUPDATE; } -cmd(set_view) { - debugC(4, kDebugLevelScripts, "o%d, %d", p0, p1); - g_agi->setView(&vt, p1); +void AgiEngine::cmd_set_view(uint8 *p) { + setView(&vt, p1); } -cmd(set_view_f) { - g_agi->setView(&vt, _v[p1]); +void AgiEngine::cmd_set_view_f(uint8 *p) { + setView(&vt, _v[p1]); } -cmd(set_loop) { - g_agi->setLoop(&vt, p1); +void AgiEngine::cmd_set_loop(uint8 *p) { + setLoop(&vt, p1); } -cmd(set_loop_f) { - g_agi->setLoop(&vt, _v[p1]); +void AgiEngine::cmd_set_loop_f(uint8 *p) { + setLoop(&vt, _v[p1]); } -cmd(number_of_loops) { +void AgiEngine::cmd_number_of_loops(uint8 *p) { _v[p1] = vt.numLoops; } -cmd(fix_loop) { +void AgiEngine::cmd_fix_loop(uint8 *p) { vt.flags |= FIX_LOOP; } -cmd(release_loop) { +void AgiEngine::cmd_release_loop(uint8 *p) { vt.flags &= ~FIX_LOOP; } -cmd(step_size) { +void AgiEngine::cmd_step_size(uint8 *p) { vt.stepSize = _v[p1]; } -cmd(step_time) { +void AgiEngine::cmd_step_time(uint8 *p) { vt.stepTime = vt.stepTimeCount = _v[p1]; } -cmd(cycle_time) { +void AgiEngine::cmd_cycle_time(uint8 *p) { vt.cycleTime = vt.cycleTimeCount = _v[p1]; } -cmd(stop_cycling) { +void AgiEngine::cmd_stop_cycling(uint8 *p) { vt.flags &= ~CYCLING; } -cmd(start_cycling) { +void AgiEngine::cmd_start_cycling(uint8 *p) { vt.flags |= CYCLING; } -cmd(normal_cycle) { +void AgiEngine::cmd_normal_cycle(uint8 *p) { vt.cycle = CYCLE_NORMAL; vt.flags |= CYCLING; } -cmd(reverse_cycle) { +void AgiEngine::cmd_reverse_cycle(uint8 *p) { vt.cycle = CYCLE_REVERSE; vt.flags |= CYCLING; } -cmd(set_dir) { +void AgiEngine::cmd_set_dir(uint8 *p) { vt.direction = _v[p1]; } -cmd(get_dir) { +void AgiEngine::cmd_get_dir(uint8 *p) { _v[p1] = vt.direction; } -cmd(get_room_f) { - _v[p1] = g_agi->objectGetLocation(_v[p0]); +void AgiEngine::cmd_get_room_f(uint8 *p) { + _v[p1] = objectGetLocation(_v[p0]); } -cmd(put) { - g_agi->objectSetLocation(p0, _v[p1]); +void AgiEngine::cmd_put(uint8 *p) { + objectSetLocation(p0, _v[p1]); } -cmd(put_f) { - g_agi->objectSetLocation(_v[p0], _v[p1]); +void AgiEngine::cmd_put_f(uint8 *p) { + objectSetLocation(_v[p0], _v[p1]); } -cmd(drop) { - g_agi->objectSetLocation(p0, 0); +void AgiEngine::cmd_drop(uint8 *p) { + objectSetLocation(p0, 0); } -cmd(get) { - g_agi->objectSetLocation(p0, EGO_OWNED); +void AgiEngine::cmd_get(uint8 *p) { + objectSetLocation(p0, EGO_OWNED); } -cmd(get_f) { - g_agi->objectSetLocation(_v[p0], EGO_OWNED); +void AgiEngine::cmd_get_f(uint8 *p) { + objectSetLocation(_v[p0], EGO_OWNED); } -cmd(word_to_string) { - strcpy(game.strings[p0], game.egoWords[p1].word); +void AgiEngine::cmd_word_to_string(uint8 *p) { + strcpy(_game.strings[p0], _game.egoWords[p1].word); } -cmd(open_dialogue) { - debugC(4, kDebugLevelScripts, "p0 = %d", p0); - game.hasWindow = true; +void AgiEngine::cmd_open_dialogue(uint8 *p) { + _game.hasWindow = true; } -cmd(close_dialogue) { - debugC(4, kDebugLevelScripts, "p0 = %d", p0); - game.hasWindow = false; +void AgiEngine::cmd_close_dialogue(uint8 *p) { + _game.hasWindow = false; } -cmd(close_window) { - g_agi->closeWindow(); +void AgiEngine::cmd_close_window(uint8 *p) { + closeWindow(); } -cmd(status_line_on) { - game.statusLine = true; - g_agi->writeStatus(); +void AgiEngine::cmd_status_line_on(uint8 *p) { + _game.statusLine = true; + writeStatus(); } -cmd(status_line_off) { - game.statusLine = false; - g_agi->writeStatus(); +void AgiEngine::cmd_status_line_off(uint8 *p) { + _game.statusLine = false; + writeStatus(); } -cmd(show_obj) { - g_sprites->showObj(p0); +void AgiEngine::cmd_show_obj(uint8 *p) { + _sprites->showObj(p0); } -cmd(show_obj_v) { - g_sprites->showObj(_v[p0]); +void AgiEngine::cmd_show_obj_v(uint8 *p) { + _sprites->showObj(_v[p0]); } -cmd(sound) { - g_sound->startSound(p0, p1); +void AgiEngine::cmd_sound(uint8 *p) { + _sound->startSound(p0, p1); } -cmd(stop_sound) { - g_sound->stopSound(); +void AgiEngine::cmd_stop_sound(uint8 *p) { + _sound->stopSound(); } -cmd(menu_input) { - g_agi->newInputMode(INPUT_MENU); +void AgiEngine::cmd_menu_input(uint8 *p) { + newInputMode(INPUT_MENU); } -cmd(enable_item) { - g_agi->_menu->setItem(p0, true); +void AgiEngine::cmd_enable_item(uint8 *p) { + _menu->setItem(p0, true); } -cmd(disable_item) { - g_agi->_menu->setItem(p0, false); +void AgiEngine::cmd_disable_item(uint8 *p) { + _menu->setItem(p0, false); } -cmd(submit_menu) { - g_agi->_menu->submit(); +void AgiEngine::cmd_submit_menu(uint8 *p) { + _menu->submit(); } -cmd(set_scan_start) { - curLogic->sIP = curLogic->cIP; +void AgiEngine::cmd_set_scan_start(uint8 *p) { + _curLogic->sIP = _curLogic->cIP; } -cmd(reset_scan_start) { - curLogic->sIP = 2; +void AgiEngine::cmd_reset_scan_start(uint8 *p) { + _curLogic->sIP = 2; } -cmd(save_game) { - game.simpleSave ? g_agi->saveGameSimple() : g_agi->saveGameDialog(); +void AgiEngine::cmd_save_game(uint8 *p) { + _game.simpleSave ? saveGameSimple() : saveGameDialog(); } -cmd(load_game) { +void AgiEngine::cmd_load_game(uint8 *p) { assert(1); - game.simpleSave ? g_agi->loadGameSimple() : g_agi->loadGameDialog(); + _game.simpleSave ? loadGameSimple() : loadGameDialog(); } -cmd(init_disk) { // do nothing +void AgiEngine::cmd_init_disk(uint8 *p) { // do nothing } -cmd(log) { // do nothing +void AgiEngine::cmd_log(uint8 *p) { // do nothing } -cmd(trace_on) { // do nothing +void AgiEngine::cmd_trace_on(uint8 *p) { // do nothing } -cmd(trace_info) { // do nothing +void AgiEngine::cmd_trace_info(uint8 *p) { // do nothing } -cmd(show_mem) { - g_agi->messageBox("Enough memory"); +void AgiEngine::cmd_show_mem(uint8 *p) { + messageBox("Enough memory"); } -cmd(init_joy) { // do nothing +void AgiEngine::cmd_init_joy(uint8 *p) { // do nothing } -cmd(script_size) { +void AgiEngine::cmd_script_size(uint8 *p) { report("script.size(%d)\n", p0); } -cmd(cancel_line) { - g_agi->_game.inputBuffer[0] = 0; - g_agi->writePrompt(); +void AgiEngine::cmd_cancel_line(uint8 *p) { + _game.inputBuffer[0] = 0; + writePrompt(); } // This implementation is based on observations of Amiga's Gold Rush. @@ -533,7 +510,7 @@ cmd(cancel_line) { // 4051 (When ego is stationary), // 471 (When walking on the first screen's bridge), // 71 (When walking around, using the mouse or the keyboard). -cmd(obj_status_f) { +void AgiEngine::cmd_obj_status_f(uint8 *p) { const char *cycleDesc; // Object's cycle description line const char *motionDesc; // Object's motion description line char msg[256]; // The whole object status message @@ -594,7 +571,7 @@ cmd(obj_status_f) { vt_v.stepSize, cycleDesc, motionDesc); - g_agi->messageBox(msg); + messageBox(msg); } // unknown commands: @@ -605,49 +582,49 @@ cmd(obj_status_f) { // unk_174: Change priority table (used in KQ4) -- j5 // unk_177: Disable menus completely -- j5 // unk_181: Deactivate keypressed control (default control of ego) -cmd(set_simple) { - if (!(g_agi->getFeatures() & (GF_AGI256 | GF_AGI256_2))) { - game.simpleSave = true; +void AgiEngine::cmd_set_simple(uint8 *p) { + if (!(getFeatures() & (GF_AGI256 | GF_AGI256_2))) { + _game.simpleSave = true; } else { // AGI256 and AGI256-2 use this unknown170 command to load 256 color pictures. - // Load the picture. Similar to cmd(load_pic). - g_sprites->eraseBoth(); - g_agi->agiLoadResource(rPICTURE, _v[p0]); + // Load the picture. Similar to void AgiEngine::cmd_load_pic(uint8 *p). + _sprites->eraseBoth(); + agiLoadResource(rPICTURE, _v[p0]); - // Draw the picture. Similar to cmd(draw_pic). - g_picture->decodePicture(_v[p0], false, true); - g_sprites->blitBoth(); - game.pictureShown = 0; + // Draw the picture. Similar to void AgiEngine::cmd_draw_pic(uint8 *p). + _picture->decodePicture(_v[p0], false, true); + _sprites->blitBoth(); + _game.pictureShown = 0; - // Show the picture. Similar to cmd(show_pic). - g_agi->setflag(fOutputMode, false); - cmd_close_window(g_agi, NULL); - g_picture->showPic(); - game.pictureShown = 1; + // Show the picture. Similar to void AgiEngine::cmd_show_pic(uint8 *p). + setflag(fOutputMode, false); + closeWindow(); + _picture->showPic(); + _game.pictureShown = 1; // Simulate slowww computer. Many effects rely on this - g_agi->pause(kPausePicture); + pause(kPausePicture); } } -cmd(pop_script) { - if (g_agi->getVersion() >= 0x2915) { +void AgiEngine::cmd_pop_script(uint8 *p) { + if (getVersion() >= 0x2915) { report("pop.script\n"); } } -cmd(hold_key) { - if (g_agi->getVersion() >= 0x3098) { - g_agi->_egoHoldKey = true; +void AgiEngine::cmd_hold_key(uint8 *p) { + if (getVersion() >= 0x3098) { + _egoHoldKey = true; } } -cmd(discard_sound) { - if (g_agi->getVersion() >= 0x2936) { +void AgiEngine::cmd_discard_sound(uint8 *p) { + if (getVersion() >= 0x2936) { report("discard.sound\n"); } } -cmd(hide_mouse) { +void AgiEngine::cmd_hide_mouse(uint8 *p) { // WORKAROUND: Turns off current movement that's being caused with the mouse. // This fixes problems with too many popup boxes appearing in the Amiga // Gold Rush's copy protection failure scene (i.e. the hanging scene, logic.192). @@ -655,34 +632,34 @@ cmd(hide_mouse) { // to walk somewhere else than to the right using the mouse. // FIXME: Write a proper implementation using disassembly and // apply it to other games as well if applicable. - game.viewTable[0].flags &= ~ADJ_EGO_XY; + _game.viewTable[0].flags &= ~ADJ_EGO_XY; g_system->showMouse(false); } -cmd(allow_menu) { - if (g_agi->getVersion() >= 0x3098) { - g_agi->setflag(fMenusWork, ((p0 != 0) ? true : false)); +void AgiEngine::cmd_allow_menu(uint8 *p) { + if (getVersion() >= 0x3098) { + setflag(fMenusWork, ((p0 != 0) ? true : false)); } } -cmd(show_mouse) { +void AgiEngine::cmd_show_mouse(uint8 *p) { g_system->showMouse(true); } -cmd(fence_mouse) { - g_agi->_game.mouseFence.moveTo(p0, p1); - g_agi->_game.mouseFence.setWidth(p2 - p0); - g_agi->_game.mouseFence.setHeight(p3 - p1); +void AgiEngine::cmd_fence_mouse(uint8 *p) { + _game.mouseFence.moveTo(p0, p1); + _game.mouseFence.setWidth(p2 - p0); + _game.mouseFence.setHeight(p3 - p1); } -cmd(release_key) { - if (g_agi->getVersion() >= 0x3098) { - g_agi->_egoHoldKey = false; +void AgiEngine::cmd_release_key(uint8 *p) { + if (getVersion() >= 0x3098) { + _egoHoldKey = false; } } -cmd(adj_ego_move_to_x_y) { +void AgiEngine::cmd_adj_ego_move_to_x_y(uint8 *p) { int8 x, y; switch (logicNamesCmd[182].numArgs) { @@ -704,56 +681,57 @@ cmd(adj_ego_move_to_x_y) { // onto the ladder so this is more like it (Although that may be caused // by something else because this command doesn't do any flag manipulations // in the Amiga version - checked it with disassembly). - if (x != game.adjMouseX || y != game.adjMouseY) - game.viewTable[EGO_VIEW_TABLE].flags &= ~ADJ_EGO_XY; + if (x != _game.adjMouseX || y != _game.adjMouseY) + _game.viewTable[EGO_VIEW_TABLE].flags &= ~ADJ_EGO_XY; - game.adjMouseX = x; - game.adjMouseY = y; + _game.adjMouseX = x; + _game.adjMouseY = y; debugC(4, kDebugLevelScripts, "adj.ego.move.to.x.y(%d, %d)", x, y); break; // TODO: Check where (if anywhere) the 0 arguments version is used case 0: default: - game.viewTable[0].flags |= ADJ_EGO_XY; + _game.viewTable[0].flags |= ADJ_EGO_XY; break; } } -cmd(parse) { +void AgiEngine::cmd_parse(uint8 *p) { _v[vWordNotFound] = 0; - g_agi->setflag(fEnteredCli, false); - g_agi->setflag(fSaidAcceptedInput, false); + setflag(fEnteredCli, false); + setflag(fSaidAcceptedInput, false); - g_agi->dictionaryWords(g_agi->agiSprintf(game.strings[p0])); + dictionaryWords(agiSprintf(_game.strings[p0])); } -cmd(call) { +void AgiEngine::cmd_call(uint8 *p) { int oldCIP; int oldLognum; // CM: we don't save sIP because set.scan.start can be // used in a called script (fixes xmas demo) - oldCIP = curLogic->cIP; - oldLognum = game.lognum; + oldCIP = _curLogic->cIP; + oldLognum = _game.lognum; - g_agi->runLogic(p0); + runLogic(p0); - game.lognum = oldLognum; - curLogic = &game.logics[game.lognum]; - curLogic->cIP = oldCIP; + _game.lognum = oldLognum; + _curLogic = &_game.logics[_game.lognum]; + _curLogic->cIP = oldCIP; } -cmd(call_f) { - cmd_call(g_agi, &_v[p0]); +void AgiEngine::cmd_call_f(uint8 *p) { + cmd_call(&_v[p0]); } -cmd(draw_pic) { +void AgiEngine::cmd_draw_pic(uint8 *p) { debugC(6, kDebugLevelScripts, "=== draw pic %d ===", _v[p0]); - g_sprites->eraseBoth(); - g_picture->decodePicture(_v[p0], true); - g_sprites->blitBoth(); - game.pictureShown = 0; + _sprites->eraseBoth(); + _picture->decodePicture(_v[p0], true); + _sprites->blitBoth(); + _sprites->commitBoth(); + _game.pictureShown = 0; debugC(6, kDebugLevelScripts, "--- end of draw pic %d ---", _v[p0]); // WORKAROUND for a script bug which exists in SQ1, logic scripts @@ -768,63 +746,64 @@ cmd(draw_pic) { // above the ground), flag 103 is reset, thereby fixing this issue. Note // that this is a script bug and occurs in the original interpreter as well. // Fixes bug #1658514: AGI: SQ1 (2.2 DOS ENG) bizzare exploding roger - if (g_agi->getGameID() == GID_SQ1 && _v[p0] == 20) - g_agi->setflag(103, false); + if (getGameID() == GID_SQ1 && _v[p0] == 20) + setflag(103, false); // Simulate slowww computer. Many effects rely on this - g_agi->pause(kPausePicture); + pause(kPausePicture); } -cmd(show_pic) { +void AgiEngine::cmd_show_pic(uint8 *p) { debugC(6, kDebugLevelScripts, "=== show pic ==="); - g_agi->setflag(fOutputMode, false); - cmd_close_window(g_agi, NULL); - g_picture->showPic(); - game.pictureShown = 1; + setflag(fOutputMode, false); + closeWindow(); + _picture->showPic(); + _game.pictureShown = 1; debugC(6, kDebugLevelScripts, "--- end of show pic ---"); } -cmd(load_pic) { - g_sprites->eraseBoth(); - g_agi->agiLoadResource(rPICTURE, _v[p0]); - g_sprites->blitBoth(); +void AgiEngine::cmd_load_pic(uint8 *p) { + _sprites->eraseBoth(); + agiLoadResource(rPICTURE, _v[p0]); + _sprites->blitBoth(); + _sprites->commitBoth(); } -cmd(discard_pic) { +void AgiEngine::cmd_discard_pic(uint8 *p) { debugC(6, kDebugLevelScripts, "--- discard pic ---"); // do nothing } -cmd(overlay_pic) { +void AgiEngine::cmd_overlay_pic(uint8 *p) { debugC(6, kDebugLevelScripts, "--- overlay pic ---"); - g_sprites->eraseBoth(); - g_picture->decodePicture(_v[p0], false); - g_sprites->blitBoth(); - game.pictureShown = 0; - g_sprites->commitBoth(); + _sprites->eraseBoth(); + _picture->decodePicture(_v[p0], false); + _sprites->blitBoth(); + _game.pictureShown = 0; + _sprites->commitBoth(); // Simulate slowww computer. Many effects rely on this - g_agi->pause(kPausePicture); + pause(kPausePicture); } -cmd(show_pri_screen) { - g_agi->_debug.priority = 1; - g_sprites->eraseBoth(); - g_picture->showPic(); - g_sprites->blitBoth(); +void AgiEngine::cmd_show_pri_screen(uint8 *p) { + _debug.priority = 1; + _sprites->eraseBoth(); + _picture->showPic(); + _sprites->blitBoth(); - g_agi->waitKey(); + waitKey(); - g_agi->_debug.priority = 0; - g_sprites->eraseBoth(); - g_picture->showPic(); - g_sprites->blitBoth(); + _debug.priority = 0; + _sprites->eraseBoth(); + _picture->showPic(); + _sprites->blitBoth(); } -cmd(animate_obj) { +void AgiEngine::cmd_animate_obj(uint8 *p) { if (vt.flags & ANIMATED) return; @@ -835,14 +814,14 @@ cmd(animate_obj) { vt.direction = 0; } -cmd(unanimate_all) { +void AgiEngine::cmd_unanimate_all(uint8 *p) { int i; for (i = 0; i < MAX_VIEWTABLE; i++) - game.viewTable[i].flags &= ~(ANIMATED | DRAWN); + _game.viewTable[i].flags &= ~(ANIMATED | DRAWN); } -cmd(draw) { +void AgiEngine::cmd_draw(uint8 *p) { if (vt.flags & DRAWN) return; @@ -852,19 +831,19 @@ cmd(draw) { debugC(4, kDebugLevelScripts, "draw entry %d", vt.entry); vt.flags |= UPDATE; - if (g_agi->getVersion() >= 0x3000) { - g_agi->setLoop(&vt, vt.currentLoop); - g_agi->setCel(&vt, vt.currentCel); + if (getVersion() >= 0x3000) { + setLoop(&vt, vt.currentLoop); + setCel(&vt, vt.currentCel); } - g_agi->fixPosition(p0); + fixPosition(p0); vt.xPos2 = vt.xPos; vt.yPos2 = vt.yPos; vt.celData2 = vt.celData; - g_sprites->eraseUpdSprites(); + _sprites->eraseUpdSprites(); vt.flags |= DRAWN; - // WORKAROUND: This fixes a bug with AGI Fanmade game Space Trek. + // WORKAROUND: This fixes a bug with AGI Fanmade _game Space Trek. // The original workaround checked if AGI version was <= 2.440, which could // cause regressions with some AGI games. The original workaround no longer // works for Space Trek in ScummVM, as all fanmade games are set to use @@ -875,36 +854,43 @@ cmd(draw) { // TODO: Investigate this further and check if any other fanmade AGI // games are affected. If yes, then it'd be best to set this for Space // Trek only - if (g_agi->getFeatures() & GF_FANMADE) // See Sarien bug #546562 + if (getFeatures() & GF_FANMADE) // See Sarien bug #546562 vt.flags |= ANIMATED; - g_sprites->blitUpdSprites(); + _sprites->blitUpdSprites(); vt.flags &= ~DONTUPDATE; - g_sprites->commitBlock(vt.xPos, vt.yPos - vt.ySize + 1, vt.xPos + vt.xSize - 1, vt.yPos); + _sprites->commitBlock(vt.xPos, vt.yPos - vt.ySize + 1, vt.xPos + vt.xSize - 1, vt.yPos, true); debugC(4, kDebugLevelScripts, "vt entry #%d flags = %02x", p0, vt.flags); } -cmd(erase) { +void AgiEngine::cmd_erase(uint8 *p) { if (~vt.flags & DRAWN) return; - g_sprites->eraseUpdSprites(); + _sprites->eraseUpdSprites(); if (vt.flags & UPDATE) { vt.flags &= ~DRAWN; } else { - g_sprites->eraseNonupdSprites(); + _sprites->eraseNonupdSprites(); vt.flags &= ~DRAWN; - g_sprites->blitNonupdSprites(); + _sprites->blitNonupdSprites(); } - g_sprites->blitUpdSprites(); + _sprites->blitUpdSprites(); + + int x1, y1, x2, y2; + + x1 = MIN((int)MIN(vt.xPos, vt.xPos2), MIN(vt.xPos + vt.celData->width, vt.xPos2 + vt.celData2->width)); + x2 = MAX((int)MAX(vt.xPos, vt.xPos2), MAX(vt.xPos + vt.celData->width, vt.xPos2 + vt.celData2->width)); + y1 = MIN((int)MIN(vt.yPos, vt.yPos2), MIN(vt.yPos - vt.celData->height, vt.yPos2 - vt.celData2->height)); + y2 = MAX((int)MAX(vt.yPos, vt.yPos2), MAX(vt.yPos - vt.celData->height, vt.yPos2 - vt.celData2->height)); - g_sprites->commitBlock(vt.xPos, vt.yPos - vt.ySize + 1, vt.xPos + vt.xSize - 1, vt.yPos); + _sprites->commitBlock(x1, y1, x2, y2, true); } -cmd(position) { +void AgiEngine::cmd_position(uint8 *p) { vt.xPos = vt.xPos2 = p1; vt.yPos = vt.yPos2 = p2; @@ -921,27 +907,27 @@ cmd(position) { // I haven't checked but if Space Trek solely abuses the position-command we wouldn't // strictly need the identical workaround in the position.v-command but it does make // for a nice symmetry. - if (g_agi->getFeatures() & GF_CLIPCOORDS) - g_agi->clipViewCoordinates(&vt); + if (getFeatures() & GF_CLIPCOORDS) + clipViewCoordinates(&vt); } -cmd(position_f) { +void AgiEngine::cmd_position_f(uint8 *p) { vt.xPos = vt.xPos2 = _v[p1]; vt.yPos = vt.yPos2 = _v[p2]; // WORKAROUND: Part of the fix for bug #1659209 "AGI: Space Trek sprite duplication" // with an accompanying identical workaround in position-command (i.e. command 0x25). // See that workaround's comment for more in-depth information. - if (g_agi->getFeatures() & GF_CLIPCOORDS) - g_agi->clipViewCoordinates(&vt); + if (getFeatures() & GF_CLIPCOORDS) + clipViewCoordinates(&vt); } -cmd(get_posn) { - game.vars[p1] = (unsigned char)vt.xPos; - game.vars[p2] = (unsigned char)vt.yPos; +void AgiEngine::cmd_get_posn(uint8 *p) { + _game.vars[p1] = (unsigned char)vt.xPos; + _game.vars[p2] = (unsigned char)vt.yPos; } -cmd(reposition) { +void AgiEngine::cmd_reposition(uint8 *p) { int dx = (int8) _v[p1], dy = (int8) _v[p2]; debugC(4, kDebugLevelScripts, "dx=%d, dy=%d", dx, dy); @@ -957,106 +943,106 @@ cmd(reposition) { else vt.yPos += dy; - g_agi->fixPosition(p0); + fixPosition(p0); } -cmd(reposition_to) { +void AgiEngine::cmd_reposition_to(uint8 *p) { vt.xPos = p1; vt.yPos = p2; vt.flags |= UPDATE_POS; - g_agi->fixPosition(p0); + fixPosition(p0); } -cmd(reposition_to_f) { +void AgiEngine::cmd_reposition_to_f(uint8 *p) { vt.xPos = _v[p1]; vt.yPos = _v[p2]; vt.flags |= UPDATE_POS; - g_agi->fixPosition(p0); + fixPosition(p0); } -cmd(add_to_pic) { - g_sprites->addToPic(p0, p1, p2, p3, p4, p5, p6); +void AgiEngine::cmd_add_to_pic(uint8 *p) { + _sprites->addToPic(p0, p1, p2, p3, p4, p5, p6); } -cmd(add_to_pic_f) { - g_sprites->addToPic(_v[p0], _v[p1], _v[p2], _v[p3], _v[p4], _v[p5], _v[p6]); +void AgiEngine::cmd_add_to_pic_f(uint8 *p) { + _sprites->addToPic(_v[p0], _v[p1], _v[p2], _v[p3], _v[p4], _v[p5], _v[p6]); } -cmd(force_update) { - g_sprites->eraseBoth(); - g_sprites->blitBoth(); - g_sprites->commitBoth(); +void AgiEngine::cmd_force_update(uint8 *p) { + _sprites->eraseBoth(); + _sprites->blitBoth(); + _sprites->commitBoth(); } -cmd(reverse_loop) { +void AgiEngine::cmd_reverse_loop(uint8 *p) { debugC(4, kDebugLevelScripts, "o%d, f%d", p0, p1); vt.cycle = CYCLE_REV_LOOP; vt.flags |= (DONTUPDATE | UPDATE | CYCLING); vt.parm1 = p1; - g_agi->setflag(p1, false); + setflag(p1, false); } -cmd(end_of_loop) { +void AgiEngine::cmd_end_of_loop(uint8 *p) { debugC(4, kDebugLevelScripts, "o%d, f%d", p0, p1); vt.cycle = CYCLE_END_OF_LOOP; vt.flags |= (DONTUPDATE | UPDATE | CYCLING); vt.parm1 = p1; - g_agi->setflag(p1, false); + setflag(p1, false); } -cmd(block) { +void AgiEngine::cmd_block(uint8 *p) { debugC(4, kDebugLevelScripts, "x1=%d, y1=%d, x2=%d, y2=%d", p0, p1, p2, p3); - game.block.active = true; - game.block.x1 = p0; - game.block.y1 = p1; - game.block.x2 = p2; - game.block.y2 = p3; + _game.block.active = true; + _game.block.x1 = p0; + _game.block.y1 = p1; + _game.block.x2 = p2; + _game.block.y2 = p3; } -cmd(unblock) { - game.block.active = false; +void AgiEngine::cmd_unblock(uint8 *p) { + _game.block.active = false; } -cmd(normal_motion) { +void AgiEngine::cmd_normal_motion(uint8 *p) { vt.motion = MOTION_NORMAL; } -cmd(stop_motion) { +void AgiEngine::cmd_stop_motion(uint8 *p) { vt.direction = 0; vt.motion = MOTION_NORMAL; if (p0 == 0) { // ego only _v[vEgoDir] = 0; - game.playerControl = false; + _game.playerControl = false; } } -cmd(start_motion) { +void AgiEngine::cmd_start_motion(uint8 *p) { vt.motion = MOTION_NORMAL; if (p0 == 0) { // ego only _v[vEgoDir] = 0; - game.playerControl = true; + _game.playerControl = true; } } -cmd(player_control) { - game.playerControl = true; - game.viewTable[0].motion = MOTION_NORMAL; +void AgiEngine::cmd_player_control(uint8 *p) { + _game.playerControl = true; + _game.viewTable[0].motion = MOTION_NORMAL; } -cmd(program_control) { - game.playerControl = false; +void AgiEngine::cmd_program_control(uint8 *p) { + _game.playerControl = false; } -cmd(follow_ego) { +void AgiEngine::cmd_follow_ego(uint8 *p) { vt.motion = MOTION_FOLLOW_EGO; vt.parm1 = p1 > vt.stepSize ? p1 : vt.stepSize; vt.parm2 = p2; vt.parm3 = 0xff; - g_agi->setflag(p2, false); + setflag(p2, false); vt.flags |= UPDATE; } -cmd(move_obj) { +void AgiEngine::cmd_move_obj(uint8 *p) { // _D (_D_WARN "o=%d, x=%d, y=%d, s=%d, f=%d", p0, p1, p2, p3, p4); vt.motion = MOTION_MOVE_OBJ; @@ -1068,18 +1054,18 @@ cmd(move_obj) { if (p3 != 0) vt.stepSize = p3; - g_agi->setflag(p4, false); + setflag(p4, false); vt.flags |= UPDATE; if (p0 == 0) - game.playerControl = false; + _game.playerControl = false; // AGI 2.272 (ddp, xmas) doesn't call move_obj! - if (g_agi->getVersion() > 0x2272) - g_agi->moveObj(&vt); + if (getVersion() > 0x2272) + moveObj(&vt); } -cmd(move_obj_f) { +void AgiEngine::cmd_move_obj_f(uint8 *p) { vt.motion = MOTION_MOVE_OBJ; vt.parm1 = _v[p1]; vt.parm2 = _v[p2]; @@ -1089,67 +1075,67 @@ cmd(move_obj_f) { if (_v[p3] != 0) vt.stepSize = _v[p3]; - g_agi->setflag(p4, false); + setflag(p4, false); vt.flags |= UPDATE; if (p0 == 0) - game.playerControl = false; + _game.playerControl = false; // AGI 2.272 (ddp, xmas) doesn't call move_obj! - if (g_agi->getVersion() > 0x2272) - g_agi->moveObj(&vt); + if (getVersion() > 0x2272) + moveObj(&vt); } -cmd(wander) { +void AgiEngine::cmd_wander(uint8 *p) { if (p0 == 0) - game.playerControl = false; + _game.playerControl = false; vt.motion = MOTION_WANDER; vt.flags |= UPDATE; } -cmd(set_game_id) { - if (curLogic->texts && (p0 - 1) <= curLogic->numTexts) - strncpy(game.id, curLogic->texts[p0 - 1], 8); +void AgiEngine::cmd_set_game_id(uint8 *p) { + if (_curLogic->texts && (p0 - 1) <= _curLogic->numTexts) + strncpy(_game.id, _curLogic->texts[p0 - 1], 8); else - game.id[0] = 0; + _game.id[0] = 0; - report("Game ID: \"%s\"\n", game.id); + report("Game ID: \"%s\"\n", _game.id); } -cmd(pause) { - int tmp = game.clockEnabled; +void AgiEngine::cmd_pause(uint8 *p) { + int tmp = _game.clockEnabled; const char *b[] = { "Continue", NULL }; const char *b_ru[] = { "\x8f\xe0\xae\xa4\xae\xab\xa6\xa8\xe2\xec", NULL }; - game.clockEnabled = false; + _game.clockEnabled = false; - switch (g_agi->getLanguage()) { + switch (getLanguage()) { case Common::RU_RUS: - g_agi->selectionBox(" \x88\xa3\xe0\xa0 \xae\xe1\xe2\xa0\xad\xae\xa2\xab\xa5\xad\xa0. \n\n\n", b_ru); + selectionBox(" \x88\xa3\xe0\xa0 \xae\xe1\xe2\xa0\xad\xae\xa2\xab\xa5\xad\xa0. \n\n\n", b_ru); break; default: - g_agi->selectionBox(" Game is paused. \n\n\n", b); + selectionBox(" Game is paused. \n\n\n", b); break; } - game.clockEnabled = tmp; + _game.clockEnabled = tmp; } -cmd(set_menu) { - debugC(4, kDebugLevelScripts, "text %02x of %02x", p0, curLogic->numTexts); +void AgiEngine::cmd_set_menu(uint8 *p) { + debugC(4, kDebugLevelScripts, "text %02x of %02x", p0, _curLogic->numTexts); - if (curLogic->texts != NULL && p0 <= curLogic->numTexts) - g_agi->_menu->add(curLogic->texts[p0 - 1]); + if (_curLogic->texts != NULL && p0 <= _curLogic->numTexts) + _menu->add(_curLogic->texts[p0 - 1]); } -cmd(set_menu_item) { - debugC(4, kDebugLevelScripts, "text %02x of %02x", p0, curLogic->numTexts); +void AgiEngine::cmd_set_menu_item(uint8 *p) { + debugC(4, kDebugLevelScripts, "text %02x of %02x", p0, _curLogic->numTexts); - if (curLogic->texts != NULL && p0 <= curLogic->numTexts) - g_agi->_menu->addItem(curLogic->texts[p0 - 1], p1); + if (_curLogic->texts != NULL && p0 <= _curLogic->numTexts) + _menu->addItem(_curLogic->texts[p0 - 1], p1); } -cmd(version) { +void AgiEngine::cmd_version(uint8 *p) { char verMsg[64]; char ver2Msg[] = "\n" @@ -1168,7 +1154,7 @@ cmd(version) { sprintf(verMsg, TITLE " v%s", gScummVMVersion); - ver = g_agi->getVersion(); + ver = getVersion(); maj = (ver >> 12) & 0xf; min = ver & 0xfff; @@ -1186,88 +1172,87 @@ cmd(version) { strncpy(q + 1 + gap, verMsg, strlen(verMsg)); sprintf(msg, q, maj, min); - g_agi->messageBox(msg); + messageBox(msg); } -cmd(configure_screen) { - game.lineMinPrint = p0; - game.lineUserInput = p1; - game.lineStatus = p2; +void AgiEngine::cmd_configure_screen(uint8 *p) { + _game.lineMinPrint = p0; + _game.lineUserInput = p1; + _game.lineStatus = p2; } -cmd(text_screen) { +void AgiEngine::cmd_text_screen(uint8 *p) { debugC(4, kDebugLevelScripts, "switching to text mode"); - game.gfxMode = false; + _game.gfxMode = false; // Simulates the "bright background bit" of the PC video // controller. - if (game.colorBg) - game.colorBg |= 0x08; + if (_game.colorBg) + _game.colorBg |= 0x08; - g_gfx->clearScreen(game.colorBg); + _gfx->clearScreen(_game.colorBg); } -cmd(graphics) { +void AgiEngine::cmd_graphics(uint8 *p) { debugC(4, kDebugLevelScripts, "switching to graphics mode"); - if (!game.gfxMode) { - game.gfxMode = true; - g_gfx->clearScreen(0); - g_picture->showPic(); - g_agi->writeStatus(); - g_agi->writePrompt(); + if (!_game.gfxMode) { + _game.gfxMode = true; + _gfx->clearScreen(0); + _picture->showPic(); + writeStatus(); + writePrompt(); } } -cmd(set_text_attribute) { - game.colorFg = p0; - game.colorBg = p1; +void AgiEngine::cmd_set_text_attribute(uint8 *p) { + _game.colorFg = p0; + _game.colorBg = p1; - if (game.gfxMode) { - if (game.colorBg != 0) { - game.colorFg = 0; - game.colorBg = 15; + if (_game.gfxMode) { + if (_game.colorBg != 0) { + _game.colorFg = 0; + _game.colorBg = 15; } } } -cmd(status) { - g_agi->inventory(); +void AgiEngine::cmd_status(uint8 *p) { + inventory(); } -cmd(quit) { +void AgiEngine::cmd_quit(uint8 *p) { const char *buttons[] = { "Quit", "Continue", NULL }; - g_sound->stopSound(); + _sound->stopSound(); if (p0) { - g_agi->quitGame(); + quitGame(); } else { - if (g_agi->selectionBox - (" Quit the game, or continue? \n\n\n", buttons) == 0) { - g_agi->quitGame(); + if (selectionBox(" Quit the game, or continue? \n\n\n", buttons) == 0) { + quitGame(); } } } -cmd(restart_game) { +void AgiEngine::cmd_restart_game(uint8 *p) { const char *buttons[] = { "Restart", "Continue", NULL }; int sel; - g_sound->stopSound(); - sel = g_agi->getflag(fAutoRestart) ? 0 : - g_agi->selectionBox(" Restart game, or continue? \n\n\n", buttons); + _sound->stopSound(); + sel = getflag(fAutoRestart) ? 0 : + selectionBox(" Restart _game, or continue? \n\n\n", buttons); if (sel == 0) { - g_agi->_restartGame = true; - g_agi->setflag(fRestartGame, true); - g_agi->_menu->enableAll(); + _restartGame = true; + setflag(fRestartGame, true); + _menu->enableAll(); } } -cmd(distance) { +void AgiEngine::cmd_distance(uint8 *p) { int16 x1, y1, x2, y2, d; - VtEntry *v0 = &game.viewTable[p0]; - VtEntry *v1 = &game.viewTable[p1]; + VtEntry *v0 = &_game.viewTable[p0]; + VtEntry *v1 = &_game.viewTable[p1]; if (v0->flags & DRAWN && v1->flags & DRAWN) { x1 = v0->xPos + v0->xSize / 2; @@ -1290,7 +1275,7 @@ cmd(distance) { // wouldn't chase Rosella around anymore. If it had worked correctly the zombie // wouldn't have come up at all or it would have come up and gone back down // immediately. The latter approach is the one implemented here. - if (g_agi->getGameID() == GID_KQ4 && (_v[vCurRoom] == 16 || _v[vCurRoom] == 18) && p2 >= 221 && p2 <= 223) { + if (getGameID() == GID_KQ4 && (_v[vCurRoom] == 16 || _v[vCurRoom] == 18) && p2 >= 221 && p2 <= 223) { // Rooms 16 and 18 are graveyards where three zombies come up at night. They use logics 16 and 18. // Variables 221-223 are used to save the distance between each zombie and Rosella. // Variables 155, 156 and 162 are used to save the state of each zombie in room 16. @@ -1315,24 +1300,24 @@ cmd(distance) { _v[p2] = (unsigned char)d; } -cmd(accept_input) { +void AgiEngine::cmd_accept_input(uint8 *p) { debugC(4, kDebugLevelScripts | kDebugLevelInput, "input normal"); - g_agi->newInputMode(INPUT_NORMAL); - game.inputEnabled = true; - g_agi->writePrompt(); + newInputMode(INPUT_NORMAL); + _game.inputEnabled = true; + writePrompt(); } -cmd(prevent_input) { +void AgiEngine::cmd_prevent_input(uint8 *p) { debugC(4, kDebugLevelScripts | kDebugLevelInput, "no input"); - g_agi->newInputMode(INPUT_NONE); - game.inputEnabled = false; + newInputMode(INPUT_NONE); + _game.inputEnabled = false; - g_agi->clearPrompt(); + clearPrompt(); } -cmd(get_string) { +void AgiEngine::cmd_get_string(uint8 *p) { int tex, row, col; debugC(4, kDebugLevelScripts, "%d %d %d %d %d", p0, p1, p2, p3, p4); @@ -1348,63 +1333,63 @@ cmd(get_string) { if (col > 39) col = 39; - g_agi->newInputMode(INPUT_GETSTRING); + newInputMode(INPUT_GETSTRING); - if (curLogic->texts != NULL && curLogic->numTexts >= tex) { - int len = strlen(curLogic->texts[tex]); + if (_curLogic->texts != NULL && _curLogic->numTexts >= tex) { + int len = strlen(_curLogic->texts[tex]); - g_agi->printText(curLogic->texts[tex], 0, col, row, len, game.colorFg, game.colorBg); - g_agi->getString(col + len - 1, row, p4, p0); + printText(_curLogic->texts[tex], 0, col, row, len, _game.colorFg, _game.colorBg); + getString(col + len - 1, row, p4, p0); // SGEO: display input char - g_gfx->printCharacter((col + len), row, game.cursorChar, game.colorFg, game.colorBg); + _gfx->printCharacter((col + len), row, _game.cursorChar, _game.colorFg, _game.colorBg); } do { - g_agi->mainCycle(); - } while (game.inputMode == INPUT_GETSTRING && !(g_agi->shouldQuit() || g_agi->_restartGame)); + mainCycle(); + } while (_game.inputMode == INPUT_GETSTRING && !(shouldQuit() || _restartGame)); } -cmd(get_num) { +void AgiEngine::cmd_get_num(uint8 *p) { debugC(4, kDebugLevelScripts, "%d %d", p0, p1); - g_agi->newInputMode(INPUT_GETSTRING); + newInputMode(INPUT_GETSTRING); - if (curLogic->texts != NULL && curLogic->numTexts >= (p0 - 1)) { - int len = strlen(curLogic->texts[p0 - 1]); + if (_curLogic->texts != NULL && _curLogic->numTexts >= (p0 - 1)) { + int len = strlen(_curLogic->texts[p0 - 1]); - g_agi->printText(curLogic->texts[p0 - 1], 0, 0, 22, len, game.colorFg, game.colorBg); - g_agi->getString(len - 1, 22, 3, MAX_STRINGS); + printText(_curLogic->texts[p0 - 1], 0, 0, 22, len, _game.colorFg, _game.colorBg); + getString(len - 1, 22, 3, MAX_STRINGS); // CM: display input char - g_gfx->printCharacter((p3 + len), 22, game.cursorChar, game.colorFg, game.colorBg); + _gfx->printCharacter((p3 + len), 22, _game.cursorChar, _game.colorFg, _game.colorBg); } do { - g_agi->mainCycle(); - } while (game.inputMode == INPUT_GETSTRING && !(g_agi->shouldQuit() || g_agi->_restartGame)); + mainCycle(); + } while (_game.inputMode == INPUT_GETSTRING && !(shouldQuit() || _restartGame)); - _v[p1] = atoi(game.strings[MAX_STRINGS]); + _v[p1] = atoi(_game.strings[MAX_STRINGS]); - debugC(4, kDebugLevelScripts, "[%s] -> %d", game.strings[MAX_STRINGS], _v[p1]); + debugC(4, kDebugLevelScripts, "[%s] -> %d", _game.strings[MAX_STRINGS], _v[p1]); - g_agi->clearLines(22, 22, game.colorBg); - g_agi->flushLines(22, 22); + clearLines(22, 22, _game.colorBg); + flushLines(22, 22); } -cmd(set_cursor_char) { - if (curLogic->texts != NULL && (p0 - 1) <= curLogic->numTexts) { - game.cursorChar = *curLogic->texts[p0 - 1]; +void AgiEngine::cmd_set_cursor_char(uint8 *p) { + if (_curLogic->texts != NULL && (p0 - 1) <= _curLogic->numTexts) { + _game.cursorChar = *_curLogic->texts[p0 - 1]; } else { // default - game.cursorChar = '_'; + _game.cursorChar = '_'; } } -cmd(set_key) { +void AgiEngine::cmd_set_key(uint8 *p) { int key; - if (game.lastController >= MAX_CONTROLLERS) { + if (_game.lastController >= MAX_CONTROLLERS) { warning("Number of set.keys exceeded %d", MAX_CONTROLLERS); return; } @@ -1413,36 +1398,35 @@ cmd(set_key) { key = 256 * p1 + p0; - game.controllers[game.lastController].keycode = key; - game.controllers[game.lastController].controller = p2; - game.lastController++; + _game.controllers[_game.lastController].keycode = key; + _game.controllers[_game.lastController].controller = p2; + _game.lastController++; - game.controllerOccured[p2] = false; + _game.controllerOccured[p2] = false; } -cmd(set_string) { +void AgiEngine::cmd_set_string(uint8 *p) { // CM: to avoid crash in Groza (str = 150) if (p0 > MAX_STRINGS) return; - strcpy(game.strings[p0], curLogic->texts[p1 - 1]); + strcpy(_game.strings[p0], _curLogic->texts[p1 - 1]); } -cmd(display) { +void AgiEngine::cmd_display(uint8 *p) { int len = 40; - char *s = g_agi->wordWrapString(curLogic->texts[p2 - 1], &len); + char *s = wordWrapString(_curLogic->texts[p2 - 1], &len); - g_agi->printText(s, p1, 0, p0, 40, game.colorFg, game.colorBg); + printText(s, p1, 0, p0, 40, _game.colorFg, _game.colorBg); free(s); } -cmd(display_f) { - debugC(4, kDebugLevelScripts, "p0 = %d", p0); - g_agi->printText(curLogic->texts[_v[p2] - 1], _v[p1], 0, _v[p0], 40, game.colorFg, game.colorBg); +void AgiEngine::cmd_display_f(uint8 *p) { + printText(_curLogic->texts[_v[p2] - 1], _v[p1], 0, _v[p0], 40, _game.colorFg, _game.colorBg); } -cmd(clear_text_rect) { +void AgiEngine::cmd_clear_text_rect(uint8 *p) { int c, x1, y1, x2, y2; if ((c = p4) != 0) @@ -1463,21 +1447,21 @@ cmd(clear_text_rect) { if (y2 > GFX_HEIGHT) y2 = GFX_HEIGHT - 1; - g_gfx->drawRectangle(x1, y1, x2, y2, c); - g_gfx->flushBlock(x1, y1, x2, y2); + _gfx->drawRectangle(x1, y1, x2, y2, c); + _gfx->flushBlock(x1, y1, x2, y2); } -cmd(toggle_monitor) { +void AgiEngine::cmd_toggle_monitor(uint8 *p) { report("toggle.monitor\n"); } -cmd(echo_line) { - strcpy((char *)game.inputBuffer, (const char *)game.echoBuffer); - game.cursorPos = strlen((char *)game.inputBuffer); - game.hasPrompt = 0; +void AgiEngine::cmd_echo_line(uint8 *p) { + strcpy((char *)_game.inputBuffer, (const char *)_game.echoBuffer); + _game.cursorPos = strlen((char *)_game.inputBuffer); + _game.hasPrompt = 0; } -cmd(clear_lines) { +void AgiEngine::cmd_clear_lines(uint8 *p) { uint8 l; // Residence 44 calls clear.lines(24,0,0), see Sarien bug #558423 @@ -1487,78 +1471,78 @@ cmd(clear_lines) { // #1935838 and #1935842 l = (l <= 24) ? l : 24; - g_agi->clearLines(p0, l, p2); - g_agi->flushLines(p0, l); + clearLines(p0, l, p2); + flushLines(p0, l); } -cmd(print) { +void AgiEngine::cmd_print(uint8 *p) { int n = p0 < 1 ? 1 : p0; - g_agi->print(curLogic->texts[n - 1], 0, 0, 0); + print(_curLogic->texts[n - 1], 0, 0, 0); } -cmd(print_f) { +void AgiEngine::cmd_print_f(uint8 *p) { int n = _v[p0] < 1 ? 1 : _v[p0]; - g_agi->print(curLogic->texts[n - 1], 0, 0, 0); + print(_curLogic->texts[n - 1], 0, 0, 0); } -cmd(print_at) { +void AgiEngine::cmd_print_at(uint8 *p) { int n = p0 < 1 ? 1 : p0; debugC(4, kDebugLevelScripts, "%d %d %d %d", p0, p1, p2, p3); - g_agi->print(curLogic->texts[n - 1], p1, p2, p3); + print(_curLogic->texts[n - 1], p1, p2, p3); } -cmd(print_at_v) { +void AgiEngine::cmd_print_at_v(uint8 *p) { int n = _v[p0] < 1 ? 1 : _v[p0]; - g_agi->print(curLogic->texts[n - 1], p1, p2, p3); + print(_curLogic->texts[n - 1], p1, p2, p3); } -cmd(push_script) { +void AgiEngine::cmd_push_script(uint8 *p) { // We run AGIMOUSE always as a side effect - if (g_agi->getFeatures() & GF_AGIMOUSE || 1) { - game.vars[27] = g_agi->_mouse.button; - game.vars[28] = g_agi->_mouse.x / 2; - game.vars[29] = g_agi->_mouse.y; + if (getFeatures() & GF_AGIMOUSE || true) { + _game.vars[27] = _mouse.button; + _game.vars[28] = _mouse.x / 2; + _game.vars[29] = _mouse.y; } else { - if (g_agi->getVersion() >= 0x2915) { + if (getVersion() >= 0x2915) { report("push.script\n"); } } } -cmd(set_pri_base) { +void AgiEngine::cmd_set_pri_base(uint8 *p) { int i, x, pri; report("Priority base set to %d\n", p0); - // game.alt_pri = true; + // _game.alt_pri = true; x = (_HEIGHT - p0) * _HEIGHT / 10; for (i = 0; i < _HEIGHT; i++) { pri = (i - p0) < 0 ? 4 : (i - p0) * _HEIGHT / x + 5; if (pri > 15) pri = 15; - game.priTable[i] = pri; + _game.priTable[i] = pri; } } -cmd(mouse_posn) { - _v[p0] = WIN_TO_PIC_X(g_agi->_mouse.x); - _v[p1] = WIN_TO_PIC_Y(g_agi->_mouse.y); +void AgiEngine::cmd_mouse_posn(uint8 *p) { + _v[p0] = WIN_TO_PIC_X(_mouse.x); + _v[p1] = WIN_TO_PIC_Y(_mouse.y); } -cmd(shake_screen) { +void AgiEngine::cmd_shake_screen(uint8 *p) { int i; // AGIPAL uses shake.screen values between 100 and 109 to set the palette // (Checked the original AGIPAL-hack's shake.screen-routine's disassembly). if (p0 >= 100 && p0 < 110) { - if (g_agi->getFeatures() & GF_AGIPAL) { - g_gfx->setAGIPal(p0); + if (getFeatures() & GF_AGIPAL) { + _gfx->setAGIPal(p0); return; } else { warning("It looks like GF_AGIPAL flag is missing"); @@ -1567,208 +1551,213 @@ cmd(shake_screen) { // Disables input while shaking to prevent bug // #1678230: AGI: Entering text while screen is shaking - int originalValue = game.inputEnabled; - game.inputEnabled = 0; + bool originalValue = _game.inputEnabled; + _game.inputEnabled = false; - g_gfx->shakeStart(); + _gfx->shakeStart(); - g_sprites->commitBoth(); // Fixes SQ1 demo + _sprites->commitBoth(); // Fixes SQ1 demo for (i = 4 * p0; i; i--) { - g_gfx->shakeScreen(i & 1); - g_gfx->flushBlock(0, 0, GFX_WIDTH - 1, GFX_HEIGHT - 1); - g_agi->mainCycle(); + _gfx->shakeScreen(i & 1); + _gfx->flushBlock(0, 0, GFX_WIDTH - 1, GFX_HEIGHT - 1); + mainCycle(); } - g_gfx->shakeEnd(); + _gfx->shakeEnd(); // Sets input back to what it was - game.inputEnabled = originalValue; -} - -static void (*agiCommand[183])(AgiEngine *, uint8 *) = { - NULL, // 0x00 - cmd_increment, - cmd_decrement, - cmd_assignn, - cmd_assignv, - cmd_addn, - cmd_addv, - cmd_subn, - cmd_subv, // 0x08 - cmd_lindirectv, - cmd_rindirect, - cmd_lindirectn, - cmd_set, - cmd_reset, - cmd_toggle, - cmd_set_v, - cmd_reset_v, // 0x10 - cmd_toggle_v, - cmd_new_room, - cmd_new_room_f, - cmd_load_logic, - cmd_load_logic_f, - cmd_call, - cmd_call_f, - cmd_load_pic, // 0x18 - cmd_draw_pic, - cmd_show_pic, - cmd_discard_pic, - cmd_overlay_pic, - cmd_show_pri_screen, - cmd_load_view, - cmd_load_view_f, - cmd_discard_view, // 0x20 - cmd_animate_obj, - cmd_unanimate_all, - cmd_draw, - cmd_erase, - cmd_position, - cmd_position_f, - cmd_get_posn, - cmd_reposition, // 0x28 - cmd_set_view, - cmd_set_view_f, - cmd_set_loop, - cmd_set_loop_f, - cmd_fix_loop, - cmd_release_loop, - cmd_set_cel, - cmd_set_cel_f, // 0x30 - cmd_last_cel, - cmd_current_cel, - cmd_current_loop, - cmd_current_view, - cmd_number_of_loops, - cmd_set_priority, - cmd_set_priority_f, - cmd_release_priority, // 0x38 - cmd_get_priority, - cmd_stop_update, - cmd_start_update, - cmd_force_update, - cmd_ignore_horizon, - cmd_observe_horizon, - cmd_set_horizon, - cmd_object_on_water, // 0x40 - cmd_object_on_land, - cmd_object_on_anything, - cmd_ignore_objs, - cmd_observe_objs, - cmd_distance, - cmd_stop_cycling, - cmd_start_cycling, - cmd_normal_cycle, // 0x48 - cmd_end_of_loop, - cmd_reverse_cycle, - cmd_reverse_loop, - cmd_cycle_time, - cmd_stop_motion, - cmd_start_motion, - cmd_step_size, - cmd_step_time, // 0x50 - cmd_move_obj, - cmd_move_obj_f, - cmd_follow_ego, - cmd_wander, - cmd_normal_motion, - cmd_set_dir, - cmd_get_dir, - cmd_ignore_blocks, // 0x58 - cmd_observe_blocks, - cmd_block, - cmd_unblock, - cmd_get, - cmd_get_f, - cmd_drop, - cmd_put, - cmd_put_f, // 0x60 - cmd_get_room_f, - cmd_load_sound, - cmd_sound, - cmd_stop_sound, - cmd_print, - cmd_print_f, - cmd_display, - cmd_display_f, // 0x68 - cmd_clear_lines, - cmd_text_screen, - cmd_graphics, - cmd_set_cursor_char, - cmd_set_text_attribute, - cmd_shake_screen, - cmd_configure_screen, - cmd_status_line_on, // 0x70 - cmd_status_line_off, - cmd_set_string, - cmd_get_string, - cmd_word_to_string, - cmd_parse, - cmd_get_num, - cmd_prevent_input, - cmd_accept_input, // 0x78 - cmd_set_key, - cmd_add_to_pic, - cmd_add_to_pic_f, - cmd_status, - cmd_save_game, - cmd_load_game, - cmd_init_disk, - cmd_restart_game, // 0x80 - cmd_show_obj, - cmd_random, - cmd_program_control, - cmd_player_control, - cmd_obj_status_f, - cmd_quit, - cmd_show_mem, - cmd_pause, // 0x88 - cmd_echo_line, - cmd_cancel_line, - cmd_init_joy, - cmd_toggle_monitor, - cmd_version, - cmd_script_size, - cmd_set_game_id, - cmd_log, // 0x90 - cmd_set_scan_start, - cmd_reset_scan_start, - cmd_reposition_to, - cmd_reposition_to_f, - cmd_trace_on, - cmd_trace_info, - cmd_print_at, - cmd_print_at_v, // 0x98 - cmd_discard_view, - cmd_clear_text_rect, - cmd_set_upper_left, - cmd_set_menu, - cmd_set_menu_item, - cmd_submit_menu, - cmd_enable_item, - cmd_disable_item, // 0xa0 - cmd_menu_input, - cmd_show_obj_v, - cmd_open_dialogue, - cmd_close_dialogue, - cmd_mul_n, - cmd_mul_v, - cmd_div_n, - cmd_div_v, // 0xa8 - cmd_close_window, - cmd_set_simple, - cmd_push_script, - cmd_pop_script, - cmd_hold_key, - cmd_set_pri_base, - cmd_discard_sound, - cmd_hide_mouse, // 0xb0 - cmd_allow_menu, - cmd_show_mouse, - cmd_fence_mouse, - cmd_mouse_posn, - cmd_release_key, - cmd_adj_ego_move_to_x_y -}; + _game.inputEnabled = originalValue; +} + +void AgiEngine::setupOpcodes() { + AgiCommand tmp[] = { + NULL, // 0x00 + &AgiEngine::cmd_increment, + &AgiEngine::cmd_decrement, + &AgiEngine::cmd_assignn, + &AgiEngine::cmd_assignv, + &AgiEngine::cmd_addn, + &AgiEngine::cmd_addv, + &AgiEngine::cmd_subn, + &AgiEngine::cmd_subv, // 0x08 + &AgiEngine::cmd_lindirectv, + &AgiEngine::cmd_rindirect, + &AgiEngine::cmd_lindirectn, + &AgiEngine::cmd_set, + &AgiEngine::cmd_reset, + &AgiEngine::cmd_toggle, + &AgiEngine::cmd_set_v, + &AgiEngine::cmd_reset_v, // 0x10 + &AgiEngine::cmd_toggle_v, + &AgiEngine::cmd_new_room, + &AgiEngine::cmd_new_room_f, + &AgiEngine::cmd_load_logic, + &AgiEngine::cmd_load_logic_f, + &AgiEngine::cmd_call, + &AgiEngine::cmd_call_f, + &AgiEngine::cmd_load_pic, // 0x18 + &AgiEngine::cmd_draw_pic, + &AgiEngine::cmd_show_pic, + &AgiEngine::cmd_discard_pic, + &AgiEngine::cmd_overlay_pic, + &AgiEngine::cmd_show_pri_screen, + &AgiEngine::cmd_load_view, + &AgiEngine::cmd_load_view_f, + &AgiEngine::cmd_discard_view, // 0x20 + &AgiEngine::cmd_animate_obj, + &AgiEngine::cmd_unanimate_all, + &AgiEngine::cmd_draw, + &AgiEngine::cmd_erase, + &AgiEngine::cmd_position, + &AgiEngine::cmd_position_f, + &AgiEngine::cmd_get_posn, + &AgiEngine::cmd_reposition, // 0x28 + &AgiEngine::cmd_set_view, + &AgiEngine::cmd_set_view_f, + &AgiEngine::cmd_set_loop, + &AgiEngine::cmd_set_loop_f, + &AgiEngine::cmd_fix_loop, + &AgiEngine::cmd_release_loop, + &AgiEngine::cmd_set_cel, + &AgiEngine::cmd_set_cel_f, // 0x30 + &AgiEngine::cmd_last_cel, + &AgiEngine::cmd_current_cel, + &AgiEngine::cmd_current_loop, + &AgiEngine::cmd_current_view, + &AgiEngine::cmd_number_of_loops, + &AgiEngine::cmd_set_priority, + &AgiEngine::cmd_set_priority_f, + &AgiEngine::cmd_release_priority, // 0x38 + &AgiEngine::cmd_get_priority, + &AgiEngine::cmd_stop_update, + &AgiEngine::cmd_start_update, + &AgiEngine::cmd_force_update, + &AgiEngine::cmd_ignore_horizon, + &AgiEngine::cmd_observe_horizon, + &AgiEngine::cmd_set_horizon, + &AgiEngine::cmd_object_on_water, // 0x40 + &AgiEngine::cmd_object_on_land, + &AgiEngine::cmd_object_on_anything, + &AgiEngine::cmd_ignore_objs, + &AgiEngine::cmd_observe_objs, + &AgiEngine::cmd_distance, + &AgiEngine::cmd_stop_cycling, + &AgiEngine::cmd_start_cycling, + &AgiEngine::cmd_normal_cycle, // 0x48 + &AgiEngine::cmd_end_of_loop, + &AgiEngine::cmd_reverse_cycle, + &AgiEngine::cmd_reverse_loop, + &AgiEngine::cmd_cycle_time, + &AgiEngine::cmd_stop_motion, + &AgiEngine::cmd_start_motion, + &AgiEngine::cmd_step_size, + &AgiEngine::cmd_step_time, // 0x50 + &AgiEngine::cmd_move_obj, + &AgiEngine::cmd_move_obj_f, + &AgiEngine::cmd_follow_ego, + &AgiEngine::cmd_wander, + &AgiEngine::cmd_normal_motion, + &AgiEngine::cmd_set_dir, + &AgiEngine::cmd_get_dir, + &AgiEngine::cmd_ignore_blocks, // 0x58 + &AgiEngine::cmd_observe_blocks, + &AgiEngine::cmd_block, + &AgiEngine::cmd_unblock, + &AgiEngine::cmd_get, + &AgiEngine::cmd_get_f, + &AgiEngine::cmd_drop, + &AgiEngine::cmd_put, + &AgiEngine::cmd_put_f, // 0x60 + &AgiEngine::cmd_get_room_f, + &AgiEngine::cmd_load_sound, + &AgiEngine::cmd_sound, + &AgiEngine::cmd_stop_sound, + &AgiEngine::cmd_print, + &AgiEngine::cmd_print_f, + &AgiEngine::cmd_display, + &AgiEngine::cmd_display_f, // 0x68 + &AgiEngine::cmd_clear_lines, + &AgiEngine::cmd_text_screen, + &AgiEngine::cmd_graphics, + &AgiEngine::cmd_set_cursor_char, + &AgiEngine::cmd_set_text_attribute, + &AgiEngine::cmd_shake_screen, + &AgiEngine::cmd_configure_screen, + &AgiEngine::cmd_status_line_on, // 0x70 + &AgiEngine::cmd_status_line_off, + &AgiEngine::cmd_set_string, + &AgiEngine::cmd_get_string, + &AgiEngine::cmd_word_to_string, + &AgiEngine::cmd_parse, + &AgiEngine::cmd_get_num, + &AgiEngine::cmd_prevent_input, + &AgiEngine::cmd_accept_input, // 0x78 + &AgiEngine::cmd_set_key, + &AgiEngine::cmd_add_to_pic, + &AgiEngine::cmd_add_to_pic_f, + &AgiEngine::cmd_status, + &AgiEngine::cmd_save_game, + &AgiEngine::cmd_load_game, + &AgiEngine::cmd_init_disk, + &AgiEngine::cmd_restart_game, // 0x80 + &AgiEngine::cmd_show_obj, + &AgiEngine::cmd_random, + &AgiEngine::cmd_program_control, + &AgiEngine::cmd_player_control, + &AgiEngine::cmd_obj_status_f, + &AgiEngine::cmd_quit, + &AgiEngine::cmd_show_mem, + &AgiEngine::cmd_pause, // 0x88 + &AgiEngine::cmd_echo_line, + &AgiEngine::cmd_cancel_line, + &AgiEngine::cmd_init_joy, + &AgiEngine::cmd_toggle_monitor, + &AgiEngine::cmd_version, + &AgiEngine::cmd_script_size, + &AgiEngine::cmd_set_game_id, + &AgiEngine::cmd_log, // 0x90 + &AgiEngine::cmd_set_scan_start, + &AgiEngine::cmd_reset_scan_start, + &AgiEngine::cmd_reposition_to, + &AgiEngine::cmd_reposition_to_f, + &AgiEngine::cmd_trace_on, + &AgiEngine::cmd_trace_info, + &AgiEngine::cmd_print_at, + &AgiEngine::cmd_print_at_v, // 0x98 + &AgiEngine::cmd_discard_view, // Opcode repeated from 0x20 ? + &AgiEngine::cmd_clear_text_rect, + &AgiEngine::cmd_set_upper_left, + &AgiEngine::cmd_set_menu, + &AgiEngine::cmd_set_menu_item, + &AgiEngine::cmd_submit_menu, + &AgiEngine::cmd_enable_item, + &AgiEngine::cmd_disable_item, // 0xa0 + &AgiEngine::cmd_menu_input, + &AgiEngine::cmd_show_obj_v, + &AgiEngine::cmd_open_dialogue, + &AgiEngine::cmd_close_dialogue, + &AgiEngine::cmd_mul_n, + &AgiEngine::cmd_mul_v, + &AgiEngine::cmd_div_n, + &AgiEngine::cmd_div_v, // 0xa8 + &AgiEngine::cmd_close_window, + &AgiEngine::cmd_set_simple, + &AgiEngine::cmd_push_script, + &AgiEngine::cmd_pop_script, + &AgiEngine::cmd_hold_key, + &AgiEngine::cmd_set_pri_base, + &AgiEngine::cmd_discard_sound, + &AgiEngine::cmd_hide_mouse, // 0xb0 + &AgiEngine::cmd_allow_menu, + &AgiEngine::cmd_show_mouse, + &AgiEngine::cmd_fence_mouse, + &AgiEngine::cmd_mouse_posn, + &AgiEngine::cmd_release_key, + &AgiEngine::cmd_adj_ego_move_to_x_y + }; + assert(ARRAYSIZE(_agiCommands) == ARRAYSIZE(tmp)); + for (int i = 0; i < ARRAYSIZE(tmp); ++i) + _agiCommands[i] = tmp[i]; +} /** * Execute a logic script @@ -1781,6 +1770,9 @@ int AgiEngine::runLogic(int n) { int num = 0; ScriptPos sp; + debugC(2, kDebugLevelScripts, "================="); + debugC(2, kDebugLevelScripts, "runLogic(%d)", n); + sp.script = n; sp.curIP = 0; _game.execStack.push_back(sp); @@ -1792,12 +1784,12 @@ int AgiEngine::runLogic(int n) { } _game.lognum = n; - curLogic = &_game.logics[_game.lognum]; + _curLogic = &_game.logics[_game.lognum]; - code = curLogic->data; - curLogic->cIP = curLogic->sIP; + code = _curLogic->data; + _curLogic->cIP = _curLogic->sIP; - timerHack = 0; + _timerHack = 0; while (ip < _game.logics[n].size && !(shouldQuit() || _restartGame)) { if (_debug.enabled) { if (_debug.steps > 0) { @@ -1807,6 +1799,7 @@ int AgiEngine::runLogic(int n) { } } else { _sprites->blitBoth(); + _sprites->commitBoth(); do { mainCycle(); } while (!_debug.steps && _debug.enabled); @@ -1816,6 +1809,11 @@ int AgiEngine::runLogic(int n) { _game.execStack.back().curIP = ip; + char st[101]; + int sz = MIN(_game.execStack.size(), 100u); + memset(st, '.', sz); + st[sz] = 0; + switch (op = *(code + ip++)) { case 0xff: // if (open/close) testIfCode(n); @@ -1826,13 +1824,16 @@ int AgiEngine::runLogic(int n) { // timer must keep running even in goto loops, // but AGI engine can't do that :( - if (timerHack > 20) { + if (_timerHack > 20) { pollTimer(); updateTimer(); - timerHack = 0; + _timerHack = 0; } break; case 0x00: // return + debugC(2, kDebugLevelScripts, "%sreturn() // Logic %d", st, n); + debugC(2, kDebugLevelScripts, "================="); + _game.execStack.pop_back(); return 1; default: @@ -1840,8 +1841,9 @@ int AgiEngine::runLogic(int n) { memmove(p, code + ip, num); memset(p + num, 0, CMD_BSIZE - num); - debugC(2, kDebugLevelScripts, "%s(%d %d %d)", logicNamesCmd[op].name, p[0], p[1], p[2]); - agiCommand[op](this, p); + debugC(2, kDebugLevelScripts, "%s%s(%d %d %d)", st, logicNamesCmd[op].name, p[0], p[1], p[2]); + + (this->*_agiCommands[op])(p); ip += num; } @@ -1857,7 +1859,7 @@ int AgiEngine::runLogic(int n) { void AgiEngine::executeAgiCommand(uint8 op, uint8 *p) { debugC(2, kDebugLevelScripts, "%s(%d %d %d)", logicNamesCmd[op].name, p[0], p[1], p[2]); - agiCommand[op] (this, p); + (this->*_agiCommands[op])(p); } } // End of namespace Agi diff --git a/engines/agi/op_test.cpp b/engines/agi/op_test.cpp index 71d307556b..ab4f6cadc5 100644 --- a/engines/agi/op_test.cpp +++ b/engines/agi/op_test.cpp @@ -29,37 +29,23 @@ namespace Agi { -static uint8 testObjRight(uint8, uint8, uint8, uint8, uint8); -static uint8 testObjCentre(uint8, uint8, uint8, uint8, uint8); -static uint8 testObjInBox(uint8, uint8, uint8, uint8, uint8); -static uint8 testPosn(uint8, uint8, uint8, uint8, uint8); -static uint8 testSaid(uint8, uint8 *); -static uint8 testController(uint8); -static uint8 testKeypressed(); -static uint8 testCompareStrings(uint8, uint8); - -static AgiEngine *g_agi; -#define game g_agi->_game - -#define ip (game.logics[lognum].cIP) -#define code (game.logics[lognum].data) - -#define testEqual(v1, v2) (g_agi->getvar(v1) == (v2)) -#define testLess(v1, v2) (g_agi->getvar(v1) < (v2)) -#define testGreater(v1, v2) (g_agi->getvar(v1) > (v2)) -#define testIsSet(flag) (g_agi->getflag(flag)) -#define testHas(obj) (g_agi->objectGetLocation(obj) == EGO_OWNED) -#define testObjInRoom(obj, v) (g_agi->objectGetLocation(obj) == g_agi->getvar(v)) - -extern int timerHack; // For the timer loop in MH1 logic 153 - -static uint8 testCompareStrings(uint8 s1, uint8 s2) { +#define ip (_game.logics[lognum].cIP) +#define code (_game.logics[lognum].data) + +#define testEqual(v1, v2) (getvar(v1) == (v2)) +#define testLess(v1, v2) (getvar(v1) < (v2)) +#define testGreater(v1, v2) (getvar(v1) > (v2)) +#define testIsSet(flag) (getflag(flag)) +#define testHas(obj) (objectGetLocation(obj) == EGO_OWNED) +#define testObjInRoom(obj, v) (objectGetLocation(obj) == getvar(v)) + +uint8 AgiEngine::testCompareStrings(uint8 s1, uint8 s2) { char ms1[MAX_STRINGLEN]; char ms2[MAX_STRINGLEN]; int j, k, l; - strcpy(ms1, game.strings[s1]); - strcpy(ms2, game.strings[s2]); + strcpy(ms1, _game.strings[s1]); + strcpy(ms2, _game.strings[s2]); l = strlen(ms1); for (k = 0, j = 0; k < l; k++) { @@ -106,16 +92,16 @@ static uint8 testCompareStrings(uint8 s1, uint8 s2) { return !strcmp(ms1, ms2); } -static uint8 testKeypressed() { - int x = game.keypress; +uint8 AgiEngine::testKeypressed() { + int x = _game.keypress; - game.keypress = 0; + _game.keypress = 0; if (!x) { - int mode = game.inputMode; + InputMode mode = _game.inputMode; - game.inputMode = INPUT_NONE; - g_agi->mainCycle(); - game.inputMode = mode; + _game.inputMode = INPUT_NONE; + mainCycle(); + _game.inputMode = mode; } if (x) @@ -124,12 +110,12 @@ static uint8 testKeypressed() { return x; } -static uint8 testController(uint8 cont) { - return (game.controllerOccured[cont] ? 1 : 0); +uint8 AgiEngine::testController(uint8 cont) { + return (_game.controllerOccured[cont] ? 1 : 0); } -static uint8 testPosn(uint8 n, uint8 x1, uint8 y1, uint8 x2, uint8 y2) { - VtEntry *v = &game.viewTable[n]; +uint8 AgiEngine::testPosn(uint8 n, uint8 x1, uint8 y1, uint8 x2, uint8 y2) { + VtEntry *v = &_game.viewTable[n]; uint8 r; r = v->xPos >= x1 && v->yPos >= y1 && v->xPos <= x2 && v->yPos <= y2; @@ -139,35 +125,35 @@ static uint8 testPosn(uint8 n, uint8 x1, uint8 y1, uint8 x2, uint8 y2) { return r; } -static uint8 testObjInBox(uint8 n, uint8 x1, uint8 y1, uint8 x2, uint8 y2) { - VtEntry *v = &game.viewTable[n]; +uint8 AgiEngine::testObjInBox(uint8 n, uint8 x1, uint8 y1, uint8 x2, uint8 y2) { + VtEntry *v = &_game.viewTable[n]; return v->xPos >= x1 && v->yPos >= y1 && v->xPos + v->xSize - 1 <= x2 && v->yPos <= y2; } // if n is in centre of box -static uint8 testObjCentre(uint8 n, uint8 x1, uint8 y1, uint8 x2, uint8 y2) { - VtEntry *v = &game.viewTable[n]; +uint8 AgiEngine::testObjCentre(uint8 n, uint8 x1, uint8 y1, uint8 x2, uint8 y2) { + VtEntry *v = &_game.viewTable[n]; return v->xPos + v->xSize / 2 >= x1 && v->xPos + v->xSize / 2 <= x2 && v->yPos >= y1 && v->yPos <= y2; } // if nect N is in right corner -static uint8 testObjRight(uint8 n, uint8 x1, uint8 y1, uint8 x2, uint8 y2) { - VtEntry *v = &game.viewTable[n]; +uint8 AgiEngine::testObjRight(uint8 n, uint8 x1, uint8 y1, uint8 x2, uint8 y2) { + VtEntry *v = &_game.viewTable[n]; return v->xPos + v->xSize - 1 >= x1 && v->xPos + v->xSize - 1 <= x2 && v->yPos >= y1 && v->yPos <= y2; } // When player has entered something, it is parsed elsewhere -static uint8 testSaid(uint8 nwords, uint8 *cc) { - int c, n = game.numEgoWords; +uint8 AgiEngine::testSaid(uint8 nwords, uint8 *cc) { + int c, n = _game.numEgoWords; int z = 0; - if (g_agi->getflag(fSaidAcceptedInput) || !g_agi->getflag(fEnteredCli)) + if (getflag(fSaidAcceptedInput) || !getflag(fEnteredCli)) return false; // FR: @@ -198,7 +184,7 @@ static uint8 testSaid(uint8 nwords, uint8 *cc) { case 1: // any word break; default: - if (game.egoWords[c].id != z) + if (_game.egoWords[c].id != z) return false; break; } @@ -213,13 +199,12 @@ static uint8 testSaid(uint8 nwords, uint8 *cc) { if (nwords != 0 && READ_LE_UINT16(cc) != 9999) return false; - g_agi->setflag(fSaidAcceptedInput, true); + setflag(fSaidAcceptedInput, true); return true; } int AgiEngine::testIfCode(int lognum) { - g_agi = this; int ec = true; int retval = true; uint8 op = 0; @@ -263,32 +248,32 @@ int AgiEngine::testIfCode(int lognum) { case 0x01: ec = testEqual(p[0], p[1]); if (p[0] == 11) - timerHack++; + _timerHack++; break; case 0x02: ec = testEqual(p[0], getvar(p[1])); if (p[0] == 11 || p[1] == 11) - timerHack++; + _timerHack++; break; case 0x03: ec = testLess(p[0], p[1]); if (p[0] == 11) - timerHack++; + _timerHack++; break; case 0x04: ec = testLess(p[0], getvar(p[1])); if (p[0] == 11 || p[1] == 11) - timerHack++; + _timerHack++; break; case 0x05: ec = testGreater(p[0], p[1]); if (p[0] == 11) - timerHack++; + _timerHack++; break; case 0x06: ec = testGreater(p[0], getvar(p[1])); if (p[0] == 11 || p[1] == 11) - timerHack++; + _timerHack++; break; case 0x07: ec = testIsSet(p[0]); @@ -319,7 +304,7 @@ int AgiEngine::testIfCode(int lognum) { ip++; // skip num_words opcode break; case 0x0F: - debugC(7, kDebugLevelScripts, "comparing [%s], [%s]", game.strings[p[0]], game.strings[p[1]]); + debugC(7, kDebugLevelScripts, "comparing [%s], [%s]", _game.strings[p[0]], _game.strings[p[1]]); ec = testCompareStrings(p[0], p[1]); break; case 0x10: @@ -338,7 +323,7 @@ int AgiEngine::testIfCode(int lognum) { // This command is used at least in the Amiga version of Gold Rush! v2.05 1989-03-09 // (AGI 2.316) in logics 1, 3, 5, 6, 137 and 192 (Logic.192 revealed this command's nature). // TODO: Check this command's implementation using disassembly just to be sure. - ec = game.viewTable[0].flags & ADJ_EGO_XY; + ec = _game.viewTable[0].flags & ADJ_EGO_XY; debugC(7, kDebugLevelScripts, "op_test: in.motion.using.mouse = %s (Amiga-specific testcase 19)", ec ? "true" : "false"); break; default: diff --git a/engines/agi/picture.cpp b/engines/agi/picture.cpp index 60877de430..dc77433cb2 100644 --- a/engines/agi/picture.cpp +++ b/engines/agi/picture.cpp @@ -476,7 +476,7 @@ void PictureMgr::plotPattern(int x, int y) { // new purpose for temp16 - temp16 =( pen_size<<1) +1; // pen size + temp16 = (pen_size << 1) + 1; // pen size pen_final_y += temp16; // the last row of this shape temp16 = temp16 << 1; pen_width = temp16; // width of shape? @@ -495,7 +495,7 @@ void PictureMgr::plotPattern(int x, int y) { } else { circleCond = ((_patCode & 0x10) != 0); counterStep = 4; - ditherCond = 0x02; + ditherCond = 0x01; } for (; pen_y < pen_final_y; pen_y++) { @@ -503,10 +503,12 @@ void PictureMgr::plotPattern(int x, int y) { for (counter = 0; counter <= pen_width; counter += counterStep) { if (circleCond || ((binary_list[counter>>1] & circle_word) != 0)) { - temp8 = t % 2; - t = t >> 1; - if (temp8 != 0) - t = t ^ 0xB8; + if ((_patCode & 0x20) != 0) { + temp8 = t % 2; + t = t >> 1; + if (temp8 != 0) + t = t ^ 0xB8; + } // == box plot, != circle plot if ((_patCode & 0x20) == 0 || (t & 0x03) == ditherCond) diff --git a/engines/agi/preagi.cpp b/engines/agi/preagi.cpp index 35285798d4..1a5698dffc 100644 --- a/engines/agi/preagi.cpp +++ b/engines/agi/preagi.cpp @@ -68,8 +68,8 @@ void PreAgiEngine::initialize() { // drivers, and I'm not sure what they are. For now, they might // as well be called "PC Speaker" and "Not PC Speaker". - switch (MidiDriver::detectMusicDriver(MDT_PCSPK)) { - case MD_PCSPK: + switch (MidiDriver::getMusicType(MidiDriver::detectDevice(MDT_PCSPK))) { + case MT_PCSPK: _soundemu = SOUND_EMU_PC; break; default: diff --git a/engines/agi/saveload.cpp b/engines/agi/saveload.cpp index b7e830dc53..88b14dcfe2 100644 --- a/engines/agi/saveload.cpp +++ b/engines/agi/saveload.cpp @@ -132,7 +132,7 @@ int AgiEngine::saveGame(const char *fileName, const char *description) { out->writeSint16BE((int16)_game.hasPrompt); out->writeSint16BE((int16)_game.gameFlags); - out->writeSint16BE((int16)_game.inputEnabled); + out->writeSint16BE(_game.inputEnabled); for (i = 0; i < _HEIGHT; i++) out->writeByte(_game.priTable[i]); @@ -302,7 +302,7 @@ int AgiEngine::loadGame(const char *fileName, bool checkId) { // TODO: played time } - _game.state = in->readByte(); + _game.state = (State)in->readByte(); in->read(loadId, 8); if (strcmp(loadId, _game.id) && checkId) { @@ -361,7 +361,7 @@ int AgiEngine::loadGame(const char *fileName, bool checkId) { _game.echoBuffer[0] = 0; _game.keypress = 0; - _game.inputMode = in->readSint16BE(); + _game.inputMode = (InputMode)in->readSint16BE(); _game.lognum = in->readSint16BE(); _game.playerControl = in->readSint16BE(); @@ -864,6 +864,10 @@ int AgiEngine::saveGameDialog() { sprintf(fileName, "%s", getSavegameFilename(_firstSlot + slot)); debugC(8, kDebugLevelMain | kDebugLevelResources, "file is [%s]", fileName); + // Make sure all graphics was blitted to screen. This fixes bug + // #2960567: "AGI: Ego partly erased in Load/Save thumbnails" + _gfx->doUpdate(); + int result = saveGame(fileName, desc); if (result == errOK) diff --git a/engines/agi/sound.cpp b/engines/agi/sound.cpp index ca5d42d981..cb4e307ea6 100644 --- a/engines/agi/sound.cpp +++ b/engines/agi/sound.cpp @@ -23,23 +23,21 @@ * */ -#include "common/md5.h" -#include "common/config-manager.h" -#include "common/fs.h" -#include "common/random.h" -#include "common/str-array.h" - #include "agi/agi.h" -namespace Agi { +#include "agi/sound_2gs.h" +#include "agi/sound_coco3.h" +#include "agi/sound_midi.h" +#include "agi/sound_sarien.h" +#include "agi/sound_pcjr.h" -#define USE_INTERPOLATION +namespace Agi { // // TODO: add support for variable sampling rate in the output device // -AgiSound *AgiSound::createFromRawResource(uint8 *data, uint32 len, int resnum, SoundMgr &manager) { +AgiSound *AgiSound::createFromRawResource(uint8 *data, uint32 len, int resnum, SoundMgr &manager, int soundemu) { if (data == NULL || len < 2) // Check for too small resource or no resource at all return NULL; uint16 type = READ_LE_UINT16(data); @@ -48,27 +46,19 @@ AgiSound *AgiSound::createFromRawResource(uint8 *data, uint32 len, int resnum, S case AGI_SOUND_SAMPLE: return new IIgsSample(data, len, resnum, manager); case AGI_SOUND_MIDI: - return new IIgsMidi (data, len, resnum, manager); + return new IIgsMidi(data, len, resnum, manager); case AGI_SOUND_4CHN: - return new PCjrSound (data, len, resnum, manager); + if (soundemu == SOUND_EMU_MIDI) { + return new MIDISound(data, len, resnum, manager); + } else { + return new PCjrSound(data, len, resnum, manager); + } } warning("Sound resource (%d) has unknown type (0x%04x). Not using the sound", resnum, type); return NULL; } -IIgsMidi::IIgsMidi(uint8 *data, uint32 len, int resnum, SoundMgr &manager) : AgiSound(manager) { - _data = data; // Save the resource pointer - _ptr = _data + 2; // Set current position to just after the header - _len = len; // Save the resource's length - _type = READ_LE_UINT16(data); // Read sound resource's type - _midiTicks = _soundBufTicks = 0; - _isValid = (_type == AGI_SOUND_MIDI) && (_data != NULL) && (_len >= 2); - - if (!_isValid) // Check for errors - warning("Error creating Apple IIGS midi sound from resource %d (Type %d, length %d)", resnum, _type, len); -} - PCjrSound::PCjrSound(uint8 *data, uint32 len, int resnum, SoundMgr &manager) : AgiSound(manager) { _data = data; // Save the resource pointer _len = len; // Save the resource's length @@ -86,247 +76,12 @@ const uint8 *PCjrSound::getVoicePointer(uint voiceNum) { return _data + voiceStartOffset; } -IIgsSample::IIgsSample(uint8 *data, uint32 len, int resnum, SoundMgr &manager) : AgiSound(manager) { - Common::MemoryReadStream stream(data, len, DisposeAfterUse::YES); - - // Check that the header was read ok and that it's of the correct type - if (_header.read(stream) && _header.type == AGI_SOUND_SAMPLE) { // An Apple IIGS AGI sample resource - uint32 sampleStartPos = stream.pos(); - uint32 tailLen = stream.size() - sampleStartPos; - - if (tailLen < _header.sampleSize) { // Check if there's no room for the sample data in the stream - // Apple IIGS Manhunter I: Sound resource 16 has only 16074 bytes - // of sample data although header says it should have 16384 bytes. - warning("Apple IIGS sample (%d) too short (%d bytes. Should be %d bytes). Using the part that's left", - resnum, tailLen, _header.sampleSize); - - _header.sampleSize = (uint16) tailLen; // Use the part that's left - } - - if (_header.pitch > 0x7F) { // Check if the pitch is invalid - warning("Apple IIGS sample (%d) has too high pitch (0x%02x)", resnum, _header.pitch); - - _header.pitch &= 0x7F; // Apple IIGS AGI probably did it this way too - } - - // Finalize the header info using the 8-bit unsigned sample data - _header.finalize(stream); - - // Convert sample data from 8-bit unsigned to 8-bit signed format - stream.seek(sampleStartPos); - _sample = new int8[_header.sampleSize]; - - if (_sample != NULL) - _isValid = SoundMgr::convertWave(stream, _sample, _header.sampleSize); - } - - if (!_isValid) // Check for errors - warning("Error creating Apple IIGS sample from resource %d (Type %d, length %d)", resnum, _header.type, len); -} - -/** Reads an Apple IIGS envelope from then given stream. */ -bool IIgsEnvelope::read(Common::SeekableReadStream &stream) { - for (int segNum = 0; segNum < ENVELOPE_SEGMENT_COUNT; segNum++) { - seg[segNum].bp = stream.readByte(); - seg[segNum].inc = stream.readUint16LE(); - } - - return !(stream.eos() || stream.err()); -} - -/** Reads an Apple IIGS wave information structure from the given stream. */ -bool IIgsWaveInfo::read(Common::SeekableReadStream &stream, bool ignoreAddr) { - top = stream.readByte(); - addr = stream.readByte() * 256; - size = (1 << (stream.readByte() & 7)) * 256; - - // Read packed mode byte and parse it into parts - byte packedModeByte = stream.readByte(); - channel = (packedModeByte >> 4) & 1; // Bit 4 - mode = (packedModeByte >> 1) & 3; // Bits 1-2 - halt = (packedModeByte & 1) != 0; // Bit 0 (Converted to boolean) - - relPitch = stream.readSint16LE(); - - // Zero the wave address if we want to ignore the wave address info - if (ignoreAddr) - addr = 0; - - return !(stream.eos() || stream.err()); -} - -bool IIgsWaveInfo::finalize(Common::SeekableReadStream &uint8Wave) { - uint32 startPos = uint8Wave.pos(); // Save stream's starting position - uint8Wave.seek(addr, SEEK_CUR); // Seek to wave's address - - // Calculate the true sample size (A zero ends the sample prematurely) - uint trueSize = size; // Set a default value for the result - for (uint i = 0; i < size; i++) { - if (uint8Wave.readByte() == 0) { - trueSize = i; - // A zero in the sample stream turns off looping - // (At least that's what MESS 0.117 and KEGS32 0.91 seem to do) - if (mode == OSC_MODE_LOOP) - mode = OSC_MODE_ONESHOT; - break; - } - } - size = trueSize; // Set the true sample size - - uint8Wave.seek(startPos); // Seek back to the stream's starting position - - return true; -} - -bool IIgsOscillator::finalize(Common::SeekableReadStream &uint8Wave) { - for (uint i = 0; i < WAVES_PER_OSCILLATOR; i++) - if (!waves[i].finalize(uint8Wave)) - return false; - - return true; -} - -bool IIgsOscillatorList::read(Common::SeekableReadStream &stream, uint oscillatorCount, bool ignoreAddr) { - // First read the A waves and then the B waves for the oscillators - for (uint waveNum = 0; waveNum < WAVES_PER_OSCILLATOR; waveNum++) - for (uint oscNum = 0; oscNum < oscillatorCount; oscNum++) - if (!osc[oscNum].waves[waveNum].read(stream, ignoreAddr)) - return false; - - count = oscillatorCount; // Set the oscillator count - - return true; -} - -bool IIgsOscillatorList::finalize(Common::SeekableReadStream &uint8Wave) { - for (uint i = 0; i < count; i++) - if (!osc[i].finalize(uint8Wave)) - return false; - - return true; -} - -bool IIgsInstrumentHeader::read(Common::SeekableReadStream &stream, bool ignoreAddr) { - env.read(stream); - relseg = stream.readByte(); - /*byte priority =*/ stream.readByte(); // Not needed? 32 in all tested data. - bendrange = stream.readByte(); - vibdepth = stream.readByte(); - vibspeed = stream.readByte(); - /*byte spare =*/ stream.readByte(); // Not needed? 0 in all tested data. - byte wac = stream.readByte(); // Read A wave count - byte wbc = stream.readByte(); // Read B wave count - oscList.read(stream, wac, ignoreAddr); // Read the oscillators - return (wac == wbc) && !(stream.eos() || stream.err()); // A and B wave counts must match -} - -bool IIgsInstrumentHeader::finalize(Common::SeekableReadStream &uint8Wave) { - return oscList.finalize(uint8Wave); -} - -bool IIgsSampleHeader::read(Common::SeekableReadStream &stream) { - type = stream.readUint16LE(); - pitch = stream.readByte(); - unknownByte_Ofs3 = stream.readByte(); - volume = stream.readByte(); - unknownByte_Ofs5 = stream.readByte(); - instrumentSize = stream.readUint16LE(); - sampleSize = stream.readUint16LE(); - // Read the instrument header *ignoring* its wave address info - - return instrument.read(stream, true); -} - -bool IIgsSampleHeader::finalize(Common::SeekableReadStream &uint8Wave) { - return instrument.finalize(uint8Wave); -} - -/** Older Apple IIGS AGI MIDI program change to instrument number mapping. */ -static const MidiProgramChangeMapping progToInstMappingV1 = { - {19, 20, 22, 23, 21, 24, 5, 5, 5, 5, - 6, 7, 10, 9, 11, 9, 15, 8, 5, 5, - 17, 16, 18, 12, 14, 5, 5, 5, 5, 5, - 0, 1, 2, 9, 3, 4, 15, 2, 2, 2, - 25, 13, 13, 25}, - 5 -}; - -/** Newer Apple IIGS AGI MIDI program change to instrument number mapping. */ -static const MidiProgramChangeMapping progToInstMappingV2 = { - {21, 22, 24, 25, 23, 26, 6, 6, 6, 6, - 7, 9, 12, 8, 13, 11, 17, 10, 6, 6, - 19, 18, 20, 14, 16, 6, 6, 6, 6, 6, - 0, 1, 2, 4, 3, 5, 17, 2, 2, 2, - 27, 15, 15, 27}, - 6 -}; - -/** Older Apple IIGS AGI instrument set. Used only by Space Quest I (AGI v1.002). */ -static const InstrumentSetInfo instSetV1 = { - 1192, 26, "7ee16bbc135171ffd6b9120cc7ff1af2", "edd3bf8905d9c238e02832b732fb2e18", progToInstMappingV1 -}; - -/** Newer Apple IIGS AGI instrument set (AGI v1.003+). Used by all others than Space Quest I. */ -static const InstrumentSetInfo instSetV2 = { - 1292, 28, "b7d428955bb90721996de1cbca25e768", "c05fb0b0e11deefab58bc68fbd2a3d07", progToInstMappingV2 -}; - -/** Information about different Apple IIGS AGI executables. */ -static const IIgsExeInfo IIgsExeInfos[] = { - {GID_SQ1, "SQ", 0x1002, 138496, 0x80AD, instSetV1}, - {GID_LSL1, "LL", 0x1003, 141003, 0x844E, instSetV2}, - {GID_AGIDEMO, "DEMO", 0x1005, 141884, 0x8469, instSetV2}, - {GID_KQ1, "KQ", 0x1006, 141894, 0x8469, instSetV2}, - {GID_PQ1, "PQ", 0x1007, 141882, 0x8469, instSetV2}, - {GID_MIXEDUP, "MG", 0x1013, 142552, 0x84B7, instSetV2}, - {GID_KQ2, "KQ2", 0x1013, 143775, 0x84B7, instSetV2}, - {GID_KQ3, "KQ3", 0x1014, 144312, 0x84B7, instSetV2}, - {GID_SQ2, "SQ2", 0x1014, 107882, 0x6563, instSetV2}, - {GID_MH1, "MH", 0x2004, 147678, 0x8979, instSetV2}, - {GID_KQ4, "KQ4", 0x2006, 147652, 0x8979, instSetV2}, - {GID_BC, "BC", 0x3001, 148192, 0x8979, instSetV2}, - {GID_GOLDRUSH, "GR", 0x3003, 148268, 0x8979, instSetV2} -}; - -static const int16 waveformRamp[WAVEFORM_SIZE] = { - 0, 8, 16, 24, 32, 40, 48, 56, - 64, 72, 80, 88, 96, 104, 112, 120, - 128, 136, 144, 152, 160, 168, 176, 184, - 192, 200, 208, 216, 224, 232, 240, 255, - 0, -248, -240, -232, -224, -216, -208, -200, - -192, -184, -176, -168, -160, -152, -144, -136, - -128, -120, -112, -104, -96, -88, -80, -72, - -64, -56, -48, -40, -32, -24, -16, -8 // Ramp up -}; - -static const int16 waveformSquare[WAVEFORM_SIZE] = { - 255, 230, 220, 220, 220, 220, 220, 220, - 220, 220, 220, 220, 220, 220, 220, 220, - 220, 220, 220, 220, 220, 220, 220, 220, - 220, 220, 220, 220, 220, 220, 220, 110, - -255, -230, -220, -220, -220, -220, -220, -220, - -220, -220, -220, -220, -220, -220, -220, -220, - -220, -220, -220, -220, -220, -220, -220, -220, - -220, -220, -220, -110, 0, 0, 0, 0 // Square -}; - -static const int16 waveformMac[WAVEFORM_SIZE] = { - 45, 110, 135, 161, 167, 173, 175, 176, - 156, 137, 123, 110, 91, 72, 35, -2, - -60, -118, -142, -165, -170, -176, -177, -179, - -177, -176, -164, -152, -117, -82, -17, 47, - 92, 137, 151, 166, 170, 173, 171, 169, - 151, 133, 116, 100, 72, 43, -7, -57, - -99, -141, -156, -170, -174, -177, -178, -179, - -175, -172, -165, -159, -137, -114, -67, -19 -}; - +#if 0 static const uint16 period[] = { 1024, 1085, 1149, 1218, 1290, 1367, 1448, 1534, 1625, 1722, 1825, 1933 }; -#if 0 static int noteToPeriod(int note) { return 10 * (period[note % 12] >> (note / 12 - 3)); } @@ -346,8 +101,7 @@ void SoundMgr::unloadSound(int resnum) { } void SoundMgr::startSound(int resnum, int flag) { - int i; - AgiSoundType type; + AgiSoundEmuType type; if (_vm->_game.sounds[resnum] != NULL && _vm->_game.sounds[resnum]->isPlaying()) return; @@ -357,7 +111,7 @@ void SoundMgr::startSound(int resnum, int flag) { if (_vm->_game.sounds[resnum] == NULL) // Is this needed at all? return; - type = (AgiSoundType)_vm->_game.sounds[resnum]->type(); + type = (AgiSoundEmuType)_vm->_game.sounds[resnum]->type(); if (type != AGI_SOUND_SAMPLE && type != AGI_SOUND_MIDI && type != AGI_SOUND_4CHN) return; @@ -367,39 +121,8 @@ void SoundMgr::startSound(int resnum, int flag) { debugC(3, kDebugLevelSound, "startSound(resnum = %d, flag = %d) type = %d", resnum, flag, type); - switch (type) { - case AGI_SOUND_SAMPLE: { - IIgsSample *sampleRes = (IIgsSample *) _vm->_game.sounds[_playingSound]; - _gsSound.playSampleSound(sampleRes->getHeader(), sampleRes->getSample()); - break; - } - case AGI_SOUND_MIDI: - ((IIgsMidi *) _vm->_game.sounds[_playingSound])->rewind(); - break; - case AGI_SOUND_4CHN: - PCjrSound *pcjrSound = (PCjrSound *) _vm->_game.sounds[resnum]; - - // Initialize channel info - for (i = 0; i < NUM_CHANNELS; i++) { - _chn[i].type = type; - _chn[i].flags = AGI_SOUND_LOOP; - - if (_env) { - _chn[i].flags |= AGI_SOUND_ENVELOPE; - _chn[i].adsr = AGI_SOUND_ENV_ATTACK; - } - - _chn[i].ins = _waveform; - _chn[i].size = WAVEFORM_SIZE; - _chn[i].ptr = pcjrSound->getVoicePointer(i % 4); - _chn[i].timer = 0; - _chn[i].vol = 0; - _chn[i].end = 0; - } - break; - } + _soundGen->play(resnum); - memset(_sndBuffer, 0, BUFFER_SIZE << 1); _endflag = flag; // Nat Budin reports that the flag should be reset when sound starts @@ -407,907 +130,68 @@ void SoundMgr::startSound(int resnum, int flag) { } void SoundMgr::stopSound() { - int i; - debugC(3, kDebugLevelSound, "stopSound() --> %d", _playingSound); _endflag = -1; - if (_vm->_soundemu != SOUND_EMU_APPLE2GS) { - for (i = 0; i < NUM_CHANNELS; i++) - stopNote(i); - } if (_playingSound != -1) { if (_vm->_game.sounds[_playingSound]) // sanity checking _vm->_game.sounds[_playingSound]->stop(); - if (_vm->_soundemu == SOUND_EMU_APPLE2GS) { - _gsSound.stopSounds(); - } + _soundGen->stop(); _playingSound = -1; } -} -void IIgsSoundMgr::stopSounds() { - // Stops all sounds on all MIDI channels - for (iterator iter = _midiChannels.begin(); iter != _midiChannels.end(); ++iter) - iter->stopSounds(); + if (_endflag != -1) + _vm->setflag(_endflag, true); } -bool IIgsSoundMgr::playSampleSound(const IIgsSampleHeader &sampleHeader, const int8 *sample) { - stopSounds(); - IIgsMidiChannel &channel = _midiChannels[kSfxMidiChannel]; +int SoundMgr::initSound() { + return -1; +} - channel.setInstrument(&sampleHeader.instrument, sample); - channel.setVolume(sampleHeader.volume); - channel.noteOn(sampleHeader.pitch, 64); // Use default velocity (i.e. 64) +void SoundMgr::deinitSound() { + stopSound(); - return true; + delete _soundGen; } -void IIgsMidiChannel::stopSounds() { - // Stops all sounds on this single MIDI channel - for (iterator iter = _gsChannels.begin(); iter != _gsChannels.end(); ++iter) - iter->stop(); +void SoundMgr::soundIsFinished() { + if (_endflag != -1) + _vm->setflag(_endflag, true); - _gsChannels.clear(); + if (_playingSound != -1) + _vm->_game.sounds[_playingSound]->stop(); + _playingSound = -1; + _endflag = -1; } -int SoundMgr::initSound() { - int r = -1; - - memset(_sndBuffer, 0, BUFFER_SIZE << 1); - _env = false; +SoundMgr::SoundMgr(AgiEngine *agi, Audio::Mixer *pMixer) { + _vm = agi; + _endflag = -1; + _playingSound = -1; switch (_vm->_soundemu) { case SOUND_EMU_NONE: - _waveform = waveformRamp; - _env = true; - break; case SOUND_EMU_AMIGA: - case SOUND_EMU_PC: - _waveform = waveformSquare; - break; case SOUND_EMU_MAC: - _waveform = waveformMac; + _soundGen = new SoundGenSarien(_vm, pMixer); + break; + case SOUND_EMU_PC: + case SOUND_EMU_PCJR: + _soundGen = new SoundGenPCJr(_vm, pMixer); break; case SOUND_EMU_APPLE2GS: - _disabledMidi = !loadInstruments(); + _soundGen = new SoundGen2GS(_vm, pMixer); break; case SOUND_EMU_COCO3: + _soundGen = new SoundGenCoCo3(_vm, pMixer); break; - } - - report("Initializing sound:\n"); - - report("sound: envelopes "); - if (_env) { - report("enabled (decay=%d, sustain=%d)\n", ENV_DECAY, ENV_SUSTAIN); - } else { - report("disabled\n"); - } - - _mixer->playStream(Audio::Mixer::kMusicSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true); - - return r; -} - -void SoundMgr::deinitSound() { - debugC(3, kDebugLevelSound, "()"); - - _mixer->stopHandle(_soundHandle); -} - -void SoundMgr::stopNote(int i) { - _chn[i].adsr = AGI_SOUND_ENV_RELEASE; - - if (_useChorus) { - // Stop chorus ;) - if (_chn[i].type == AGI_SOUND_4CHN && - _vm->_soundemu == SOUND_EMU_NONE && i < 3) { - stopNote(i + 4); - } - } -} - -void SoundMgr::playNote(int i, int freq, int vol) { - if (!_vm->getflag(fSoundOn)) - vol = 0; - else if (vol && _vm->_soundemu == SOUND_EMU_PC) - vol = 160; - - _chn[i].phase = 0; - _chn[i].freq = freq; - _chn[i].vol = vol; - _chn[i].env = 0x10000; - _chn[i].adsr = AGI_SOUND_ENV_ATTACK; - - if (_useChorus) { - // Add chorus ;) - if (_chn[i].type == AGI_SOUND_4CHN && - _vm->_soundemu == SOUND_EMU_NONE && i < 3) { - - int newfreq = freq * 1007 / 1000; - - if (freq == newfreq) - newfreq++; - - playNote(i + 4, newfreq, vol * 2 / 3); - } - } -} - -void SoundMgr::playMidiSound() { - if (_disabledMidi) - return; - - const uint8 *p; - uint8 parm1, parm2; - static uint8 cmd, ch; - - if (_playingSound == -1 || _vm->_game.sounds[_playingSound] == NULL) { - warning("Error playing Apple IIGS MIDI sound resource"); - _playing = false; - - return; - } - - IIgsMidi *midiObj = (IIgsMidi *) _vm->_game.sounds[_playingSound]; - - _playing = true; - p = midiObj->getPtr(); - - midiObj->_soundBufTicks++; - - while (true) { - uint8 readByte = *p; - - // Check for end of MIDI sequence marker (Can also be here before delta-time) - if (readByte == MIDI_BYTE_STOP_SEQUENCE) { - debugC(3, kDebugLevelSound, "End of MIDI sequence (Before reading delta-time)"); - _playing = false; - - midiObj->rewind(); - - return; - } else if (readByte == MIDI_BYTE_TIMER_SYNC) { - debugC(3, kDebugLevelSound, "Timer sync"); - p++; // Jump over the timer sync byte as it's not needed - - continue; - } - - uint8 deltaTime = readByte; - if (midiObj->_midiTicks + deltaTime > midiObj->_soundBufTicks) { - break; - } - midiObj->_midiTicks += deltaTime; - p++; // Jump over the delta-time byte as it was already taken care of - - // Check for end of MIDI sequence marker (This time it after reading delta-time) - if (*p == MIDI_BYTE_STOP_SEQUENCE) { - debugC(3, kDebugLevelSound, "End of MIDI sequence (After reading delta-time)"); - _playing = false; - - midiObj->rewind(); - - return; - } - - // Separate byte into command and channel if it's a command byte. - // Otherwise use running status (i.e. previously set command and channel). - if (*p & 0x80) { - cmd = *p++; - ch = cmd & 0x0f; - cmd >>= 4; - } - - switch (cmd) { - case MIDI_CMD_NOTE_OFF: - parm1 = *p++; - parm2 = *p++; - _gsSound.midiNoteOff(ch, parm1, parm2); - break; - case MIDI_CMD_NOTE_ON: - parm1 = *p++; - parm2 = *p++; - _gsSound.midiNoteOn(ch, parm1, parm2); - break; - case MIDI_CMD_CONTROLLER: - parm1 = *p++; - parm2 = *p++; - _gsSound.midiController(ch, parm1, parm2); - break; - case MIDI_CMD_PROGRAM_CHANGE: - parm1 = *p++; - _gsSound.midiProgramChange(ch, parm1); - break; - case MIDI_CMD_PITCH_WHEEL: - parm1 = *p++; - parm2 = *p++; - - uint16 wheelPos = ((parm2 & 0x7F) << 7) | (parm1 & 0x7F); // 14-bit value - _gsSound.midiPitchWheel(wheelPos); - break; - } - } - - midiObj->setPtr(p); -} - -void IIgsSoundMgr::midiNoteOff(uint8 channel, uint8 note, uint8 velocity) { - _midiChannels[channel].noteOff(note, velocity); - debugC(3, kDebugLevelSound, "note off, channel %02x, note %02x, velocity %02x", channel, note, velocity); -} - -void IIgsSoundMgr::midiNoteOn(uint8 channel, uint8 note, uint8 velocity) { - _midiChannels[channel].noteOn(note, velocity); - debugC(3, kDebugLevelSound, "note on, channel %02x, note %02x, velocity %02x", channel, note, velocity); -} - -// TODO: Check if controllers behave differently on different MIDI channels -// TODO: Doublecheck what other controllers than the volume controller do -void IIgsSoundMgr::midiController(uint8 channel, uint8 controller, uint8 value) { - IIgsMidiChannel &midiChannel = _midiChannels[channel]; - - // The tested Apple IIGS AGI MIDI resources only used - // controllers 0 (Bank select?), 7 (Volume) and 64 (Sustain On/Off). - // Controller 0's parameter was in range 94-127, - // controller 7's parameter was in range 0-127 and - // controller 64's parameter was always 0 (i.e. sustain off). - bool unimplemented = false; - switch (controller) { - case 7: // Volume - midiChannel.setVolume(value); - break; - default: - unimplemented = true; + case SOUND_EMU_MIDI: + _soundGen = new SoundGenMIDI(_vm, pMixer); break; } - debugC(3, kDebugLevelSound, "controller %02x, ch %02x, val %02x%s", controller, channel, value, unimplemented ? " (Unimplemented)" : ""); -} - -void IIgsSoundMgr::midiProgramChange(uint8 channel, uint8 program) { - _midiChannels[channel].setInstrument(getInstrument(program), _wave.begin()); - debugC(3, kDebugLevelSound, "program change %02x, channel %02x", program, channel); -} - -void IIgsSoundMgr::midiPitchWheel(uint8 wheelPos) { - // In all the tested Apple IIGS AGI MIDI resources - // pitch wheel commands always used 0x2000 (Center position). - // Therefore it should be quite safe to ignore this command. - debugC(3, kDebugLevelSound, "pitch wheel position %04x (Unimplemented)", wheelPos); -} - -IIgsSoundMgr::IIgsSoundMgr() { - _midiChannels.resize(16); // Set the amount of available MIDI channels -} - -const IIgsInstrumentHeader* IIgsSoundMgr::getInstrument(uint8 program) const { - return &_instruments[_midiProgToInst->map(program)]; -} - -void IIgsSoundMgr::setProgramChangeMapping(const MidiProgramChangeMapping *mapping) { - _midiProgToInst = mapping; -} - -void IIgsSoundMgr::removeStoppedSounds() { - for (Common::Array<IIgsMidiChannel>::iterator iter = _midiChannels.begin(); iter != _midiChannels.end(); ++iter) - iter->removeStoppedSounds(); -} - -void IIgsMidiChannel::removeStoppedSounds() { - for (int i = _gsChannels.size() - 1; i >= 0; i--) - if (!_gsChannels[i].playing()) - _gsChannels.remove_at(i); -} - -uint IIgsSoundMgr::activeSounds() const { - uint result = 0; - - for (Common::Array<IIgsMidiChannel>::const_iterator iter = _midiChannels.begin(); iter != _midiChannels.end(); ++iter) - result += iter->activeSounds(); - - return result; -} - -uint IIgsMidiChannel::activeSounds() const { - uint result = 0; - - for (const_iterator iter = _gsChannels.begin(); iter != _gsChannels.end(); ++iter) - if (!iter->end) - result++; - - return result; -} - -void IIgsMidiChannel::setInstrument(const IIgsInstrumentHeader *instrument, const int8 *sample) { - _instrument = instrument; - _sample = sample; - - // Set program on each Apple IIGS channel playing on this MIDI channel - for (iterator iter = _gsChannels.begin(); iter != _gsChannels.end(); ++iter) - iter->setInstrument(instrument, sample); -} - -void IIgsMidiChannel::setVolume(uint8 volume) { - _volume = volume; - - // Set volume on each Apple IIGS channel playing on this MIDI channel - for (iterator iter = _gsChannels.begin(); iter != _gsChannels.end(); ++iter) - iter->setChannelVolume(volume); -} - -void IIgsMidiChannel::noteOff(uint8 note, uint8 velocity) { - // Go through all the notes playing on this MIDI channel - // and turn off the ones that are playing the given note - for (iterator iter = _gsChannels.begin(); iter != _gsChannels.end(); ++iter) - if (iter->origNote == note) - iter->noteOff(velocity); -} - -void IIgsMidiChannel::noteOn(uint8 note, uint8 velocity) { - IIgsChannelInfo channel; - - // Use the default channel volume and instrument - channel.setChannelVolume(_volume); - channel.setInstrument(_instrument, _sample); - - // Set the note on and save the channel - channel.noteOn(note, velocity); - _gsChannels.push_back(channel); -} - -void IIgsChannelInfo::rewind() { - this->envVol = this->startEnvVol; - this->envSeg = 0; - this->pos = intToFrac(0); -} - -void IIgsChannelInfo::setChannelVolume(uint8 volume) { - this->chanVol = intToFrac(volume); -} - -void IIgsChannelInfo::setInstrument(const IIgsInstrumentHeader *instrument, const int8 *sample) { - assert(instrument != NULL && sample != NULL); - this->ins = instrument; - this->unrelocatedSample = sample; -} - -// TODO/FIXME: Implement correctly and fully (Take velocity into account etc) -void IIgsChannelInfo::noteOn(uint8 noteParam, uint8 velocity) { - this->origNote = noteParam; - this->startEnvVol = intToFrac(0); - rewind(); - - const IIgsWaveInfo *waveInfo = NULL; - - for (uint i = 0; i < ins->oscList.count; i++) - if (ins->oscList(i).waves[0].top >= noteParam) - waveInfo = &ins->oscList(i).waves[0]; - - assert(waveInfo != NULL); - - this->relocatedSample = this->unrelocatedSample + waveInfo->addr; - this->posAdd = intToFrac(0); - this->note = intToFrac(noteParam) + doubleToFrac(waveInfo->relPitch/256.0); - this->vol = doubleToFrac(fracToDouble(this->envVol) * fracToDouble(this->chanVol) / 127.0); - this->loop = (waveInfo->mode == OSC_MODE_LOOP); - this->size = waveInfo->size - waveInfo->addr; - this->end = waveInfo->halt; -} - -// TODO/FIXME: Implement correctly and fully (Take release time and velocity into account etc) -void IIgsChannelInfo::noteOff(uint8 velocity) { - this->loop = false; - this->envSeg = ins->relseg; -} - -void IIgsChannelInfo::stop() { - this->end = true; -} - -bool IIgsChannelInfo::playing() { - return !this->end; -} - -void SoundMgr::playSampleSound() { - if (_vm->_soundemu != SOUND_EMU_APPLE2GS) { - warning("Trying to play a sample but not using Apple IIGS sound emulation mode"); - return; - } - - if (_playingSound != -1) - _playing = _gsSound.activeSounds() > 0; -} - -static int cocoFrequencies[] = { - 130, 138, 146, 155, 164, 174, 184, 195, 207, 220, 233, 246, - 261, 277, 293, 311, 329, 349, 369, 391, 415, 440, 466, 493, - 523, 554, 587, 622, 659, 698, 739, 783, 830, 880, 932, 987, - 1046, 1108, 1174, 1244, 1318, 1396, 1479, 1567, 1661, 1760, 1864, 1975, - 2093, 2217, 2349, 2489, 2637, 2793, 2959, 3135, 3322, 3520, 3729, 3951 -}; - -void SoundMgr::playCoCoSound() { - int i = 0; - CoCoNote note; - - do { - note.read(_chn[i].ptr); - - if (note.freq != 0xff) { - playNote(0, cocoFrequencies[note.freq], note.volume); - - uint32 start_time = _vm->_system->getMillis(); - - while (_vm->_system->getMillis() < start_time + note.duration) { - _vm->_system->updateScreen(); - - _vm->_system->delayMillis(10); - } - } - } while (note.freq != 0xff); -} - -void SoundMgr::playAgiSound() { - int i; - AgiNote note; - - _playing = false; - for (i = 0; i < (_vm->_soundemu == SOUND_EMU_PC ? 1 : 4); i++) { - _playing |= !_chn[i].end; - note.read(_chn[i].ptr); // Read a single note (Doesn't advance the pointer) - - if (_chn[i].end) - continue; - - if ((--_chn[i].timer) <= 0) { - stopNote(i); - - if (note.freqDiv != 0) { - int volume = (note.attenuation == 0x0F) ? 0 : (0xFF - note.attenuation * 2); - playNote(i, note.freqDiv * 10, volume); - } - - _chn[i].timer = note.duration; - - if (_chn[i].timer == 0xffff) { - _chn[i].end = 1; - _chn[i].vol = 0; - _chn[i].env = 0; - - if (_useChorus) { - // chorus - if (_chn[i].type == AGI_SOUND_4CHN && _vm->_soundemu == SOUND_EMU_NONE && i < 3) { - _chn[i + 4].vol = 0; - _chn[i + 4].env = 0; - } - } - } - _chn[i].ptr += 5; // Advance the pointer to the next note data (5 bytes per note) - } - } -} - -void SoundMgr::playSound() { - int i; - - if (_endflag == -1) - return; - - if (_vm->_soundemu == SOUND_EMU_APPLE2GS) { - if (_playingSound != -1) { - if (_vm->_game.sounds[_playingSound]->type() == AGI_SOUND_MIDI) { - playMidiSound(); - //warning("playSound: Trying to play an Apple IIGS MIDI sound. Not yet implemented"); - } else if (_vm->_game.sounds[_playingSound]->type() == AGI_SOUND_SAMPLE) { - //debugC(3, kDebugLevelSound, "playSound: Trying to play an Apple IIGS sample"); - playSampleSound(); - } - } - } else if (_vm->_soundemu == SOUND_EMU_COCO3) { - playCoCoSound(); - } else { - //debugC(3, kDebugLevelSound, "playSound: Trying to play a PCjr 4-channel sound"); - playAgiSound(); - } - - if (!_playing) { - if (_vm->_soundemu != SOUND_EMU_APPLE2GS) { - for (i = 0; i < NUM_CHANNELS; _chn[i++].vol = 0) - ; - } - - if (_endflag != -1) - _vm->setflag(_endflag, true); - - if (_playingSound != -1) - _vm->_game.sounds[_playingSound]->stop(); - _playingSound = -1; - _endflag = -1; - } -} - -uint32 SoundMgr::mixSound() { - register int i, p; - const int16 *src; - int c, b, m; - - memset(_sndBuffer, 0, BUFFER_SIZE << 1); - - if (!_playing || _playingSound == -1) - return BUFFER_SIZE; - - // Handle Apple IIGS sound mixing here - // TODO: Implement playing both waves in an oscillator - // TODO: Implement swap-mode in an oscillator - if (_vm->_soundemu == SOUND_EMU_APPLE2GS) { - for (uint midiChan = 0; midiChan < _gsSound._midiChannels.size(); midiChan++) { - for (uint gsChan = 0; gsChan < _gsSound._midiChannels[midiChan]._gsChannels.size(); gsChan++) { - IIgsChannelInfo &channel = _gsSound._midiChannels[midiChan]._gsChannels[gsChan]; - if (channel.playing()) { // Only mix in actively playing channels - // Frequency multiplier was 1076.0 based on tests made with MESS 0.117. - // Tests made with KEGS32 averaged the multiplier to around 1045. - // So this is a guess but maybe it's 1046.5... i.e. C6's frequency? - double hertz = C6_FREQ * pow(SEMITONE, fracToDouble(channel.note)); - channel.posAdd = doubleToFrac(hertz / getRate()); - channel.vol = doubleToFrac(fracToDouble(channel.envVol) * fracToDouble(channel.chanVol) / 127.0); - double tempVol = fracToDouble(channel.vol)/127.0; - for (i = 0; i < IIGS_BUFFER_SIZE; i++) { - b = channel.relocatedSample[fracToInt(channel.pos)]; - // TODO: Find out what volume/amplification setting is loud enough - // but still doesn't clip when playing many channels on it. - _sndBuffer[i] += (int16) (b * tempVol * 256/4); - channel.pos += channel.posAdd; - - if (channel.pos >= intToFrac(channel.size)) { - if (channel.loop) { - // Don't divide by zero on zero length samples - channel.pos %= intToFrac(channel.size + (channel.size == 0)); - // Probably we should loop the envelope too - channel.envSeg = 0; - channel.envVol = channel.startEnvVol; - } else { - channel.pos = channel.chanVol = 0; - channel.end = true; - break; - } - } - } - - if (channel.envSeg < ENVELOPE_SEGMENT_COUNT) { - const IIgsEnvelopeSegment &seg = channel.ins->env.seg[channel.envSeg]; - // I currently assume enveloping works with the same speed as the MIDI - // (i.e. with 1/60ths of a second ticks). - // TODO: Check if enveloping really works with the same speed as MIDI - frac_t envVolDelta = doubleToFrac(seg.inc/256.0); - if (intToFrac(seg.bp) >= channel.envVol) { - channel.envVol += envVolDelta; - if (channel.envVol >= intToFrac(seg.bp)) { - channel.envVol = intToFrac(seg.bp); - channel.envSeg += 1; - } - } else { - channel.envVol -= envVolDelta; - if (channel.envVol <= intToFrac(seg.bp)) { - channel.envVol = intToFrac(seg.bp); - channel.envSeg += 1; - } - } - } - } - } - } - _gsSound.removeStoppedSounds(); - return IIGS_BUFFER_SIZE; - } // else ... - - // Handle PCjr 4-channel sound mixing here - for (c = 0; c < NUM_CHANNELS; c++) { - if (!_chn[c].vol) - continue; - - m = _chn[c].flags & AGI_SOUND_ENVELOPE ? - _chn[c].vol * _chn[c].env >> 16 : _chn[c].vol; - - if (_chn[c].type != AGI_SOUND_4CHN || c != 3) { - src = _chn[c].ins; - - p = _chn[c].phase; - for (i = 0; i < BUFFER_SIZE; i++) { - b = src[p >> 8]; -#ifdef USE_INTERPOLATION - b += ((src[((p >> 8) + 1) % _chn[c].size] - src[p >> 8]) * (p & 0xff)) >> 8; -#endif - _sndBuffer[i] += (b * m) >> 4; - - p += (uint32) 118600 *4 / _chn[c].freq; - - // FIXME: Fingolfin asks: why is there a FIXME here? Please either clarify what - // needs fixing, or remove it! - // FIXME - if (_chn[c].flags & AGI_SOUND_LOOP) { - p %= _chn[c].size << 8; - } else { - if (p >= _chn[c].size << 8) { - p = _chn[c].vol = 0; - _chn[c].end = 1; - break; - } - } - - } - _chn[c].phase = p; - } else { - // Add white noise - for (i = 0; i < BUFFER_SIZE; i++) { - b = _vm->_rnd->getRandomNumber(255) - 128; - _sndBuffer[i] += (b * m) >> 4; - } - } - - switch (_chn[c].adsr) { - case AGI_SOUND_ENV_ATTACK: - // not implemented - _chn[c].adsr = AGI_SOUND_ENV_DECAY; - break; - case AGI_SOUND_ENV_DECAY: - if (_chn[c].env > _chn[c].vol * ENV_SUSTAIN + ENV_DECAY) { - _chn[c].env -= ENV_DECAY; - } else { - _chn[c].env = _chn[c].vol * ENV_SUSTAIN; - _chn[c].adsr = AGI_SOUND_ENV_SUSTAIN; - } - break; - case AGI_SOUND_ENV_SUSTAIN: - break; - case AGI_SOUND_ENV_RELEASE: - if (_chn[c].env >= ENV_RELEASE) { - _chn[c].env -= ENV_RELEASE; - } else { - _chn[c].env = 0; - } - } - } - - return BUFFER_SIZE; -} - -/** - * Finds information about an Apple IIGS AGI executable based on the game ID. - * @return A non-null IIgsExeInfo pointer if successful, otherwise NULL. - */ -const IIgsExeInfo *SoundMgr::getIIgsExeInfo(enum AgiGameID gameid) const { - for (int i = 0; i < ARRAYSIZE(IIgsExeInfos); i++) - if (IIgsExeInfos[i].gameid == gameid) - return &IIgsExeInfos[i]; - return NULL; -} - -bool IIgsSoundMgr::loadInstrumentHeaders(const Common::FSNode &exePath, const IIgsExeInfo &exeInfo) { - bool loadedOk = false; // Was loading successful? - Common::File file; - - // Open the executable file and check that it has correct size - file.open(exePath); - if (file.size() != (int32)exeInfo.exeSize) { - debugC(3, kDebugLevelSound, "Apple IIGS executable (%s) has wrong size (Is %d, should be %d)", - exePath.getPath().c_str(), file.size(), exeInfo.exeSize); - } - - // Read the whole executable file into memory - Common::SharedPtr<Common::MemoryReadStream> data(file.readStream(file.size())); - file.close(); - - // Check that we got enough data to be able to parse the instruments - if (data && data->size() >= (int32)(exeInfo.instSetStart + exeInfo.instSet.byteCount)) { - // Check instrument set's length (The info's saved in the executable) - data->seek(exeInfo.instSetStart - 4); - uint16 instSetByteCount = data->readUint16LE(); - if (instSetByteCount != exeInfo.instSet.byteCount) { - debugC(3, kDebugLevelSound, "Wrong instrument set size (Is %d, should be %d) in Apple IIGS executable (%s)", - instSetByteCount, exeInfo.instSet.byteCount, exePath.getPath().c_str()); - } - - // Check instrument set's md5sum - data->seek(exeInfo.instSetStart); - - char md5str[32+1]; - Common::md5_file_string(*data, md5str, exeInfo.instSet.byteCount); - if (scumm_stricmp(md5str, exeInfo.instSet.md5)) { - warning("Unknown Apple IIGS instrument set (md5: %s) in %s, trying to use it nonetheless", - md5str, exePath.getPath().c_str()); - } - - // Read in the instrument set one instrument at a time - data->seek(exeInfo.instSetStart); - - // Load the instruments - _instruments.clear(); - _instruments.reserve(exeInfo.instSet.instCount); - - IIgsInstrumentHeader instrument; - for (uint i = 0; i < exeInfo.instSet.instCount; i++) { - if (!instrument.read(*data)) { - warning("Error loading Apple IIGS instrument (%d. of %d) from %s, not loading more instruments", - i + 1, exeInfo.instSet.instCount, exePath.getPath().c_str()); - break; - } - _instruments.push_back(instrument); // Add the successfully loaded instrument to the instruments array - } - - // Loading was successful only if all instruments were loaded successfully - loadedOk = (_instruments.size() == exeInfo.instSet.instCount); - } else // Couldn't read enough data from the executable file - warning("Error loading instruments from Apple IIGS executable (%s)", exePath.getPath().c_str()); - - return loadedOk; -} - -/** - * Convert sample from 8-bit unsigned to 8-bit signed format. - * @param source Source stream containing the 8-bit unsigned sample data. - * @param dest Destination buffer for the 8-bit signed sample data. - * @param length Length of the sample data to be converted. - */ -bool SoundMgr::convertWave(Common::SeekableReadStream &source, int8 *dest, uint length) { - // Convert the wave from 8-bit unsigned to 8-bit signed format - for (uint i = 0; i < length; i++) - dest[i] = (int8) ((int) source.readByte() - 128); - return !(source.eos() || source.err()); -} - -bool IIgsSoundMgr::loadWaveFile(const Common::FSNode &wavePath, const IIgsExeInfo &exeInfo) { - Common::File file; - - // Open the wave file and read it into memory - file.open(wavePath); - Common::SharedPtr<Common::MemoryReadStream> uint8Wave(file.readStream(file.size())); - file.close(); - - // Check that we got the whole wave file - if (uint8Wave && uint8Wave->size() == SIERRASTANDARD_SIZE) { - // Check wave file's md5sum - char md5str[32+1]; - Common::md5_file_string(*uint8Wave, md5str, SIERRASTANDARD_SIZE); - if (scumm_stricmp(md5str, exeInfo.instSet.waveFileMd5)) { - warning("Unknown Apple IIGS wave file (md5: %s, game: %s).\n" \ - "Please report the information on the previous line to the ScummVM team.\n" \ - "Using the wave file as it is - music may sound weird", md5str, exeInfo.exePrefix); - } - - uint8Wave->seek(0); // Seek wave to its start - // Convert the wave file from 8-bit unsigned to 8-bit signed and save the result - _wave.resize(uint8Wave->size()); - return SoundMgr::convertWave(*uint8Wave, _wave.begin(), uint8Wave->size()); - } else { // Couldn't read the wave file or it had incorrect size - warning("Error loading Apple IIGS wave file (%s), not loading instruments", wavePath.getPath().c_str()); - return false; - } -} - -/** - * A function object (i.e. a functor) for testing if a Common::FSNode - * object's name is equal (Ignoring case) to a string or to at least - * one of the strings in a list of strings. Can be used e.g. with find_if(). - */ -struct fsnodeNameEqualsIgnoreCase : public Common::UnaryFunction<const Common::FSNode&, bool> { -// FIXME: This should be replaced; use SearchMan instead - fsnodeNameEqualsIgnoreCase(const Common::StringArray &str) : _str(str) {} - fsnodeNameEqualsIgnoreCase(const Common::String str) { _str.push_back(str); } - bool operator()(const Common::FSNode ¶m) const { - for (Common::StringArray::const_iterator iter = _str.begin(); iter != _str.end(); ++iter) - if (param.getName().equalsIgnoreCase(*iter)) - return true; - return false; - } -private: - Common::StringArray _str; -}; - -bool SoundMgr::loadInstruments() { - // Check that the platform is Apple IIGS, as only it uses custom instruments - if (_vm->getPlatform() != Common::kPlatformApple2GS) { - debugC(3, kDebugLevelSound, "Platform isn't Apple IIGS so not loading any instruments"); - return true; - } - - // Get info on the particular Apple IIGS AGI game's executable - const IIgsExeInfo *exeInfo = getIIgsExeInfo((enum AgiGameID) _vm->getGameID()); - if (exeInfo == NULL) { - warning("Unsupported Apple IIGS game, not loading instruments"); - return false; - } - - // List files in the game path - Common::FSList fslist; - Common::FSNode dir(ConfMan.get("path")); - if (!dir.getChildren(fslist, Common::FSNode::kListFilesOnly)) { - warning("Invalid game path (\"%s\"), not loading Apple IIGS instruments", dir.getPath().c_str()); - return false; - } - - // Populate executable filenames list (Long filename and short filename) for searching - Common::StringArray exeNames; - exeNames.push_back(Common::String(exeInfo->exePrefix) + ".SYS16"); - exeNames.push_back(Common::String(exeInfo->exePrefix) + ".SYS"); - - // Populate wave filenames list (Long filename and short filename) for searching - Common::StringArray waveNames; - waveNames.push_back("SIERRASTANDARD"); - waveNames.push_back("SIERRAST"); - - // Search for the executable file and the wave file (i.e. check if any of the filenames match) - Common::FSList::const_iterator exeFsnode, waveFsnode; - exeFsnode = Common::find_if(fslist.begin(), fslist.end(), fsnodeNameEqualsIgnoreCase(exeNames)); - waveFsnode = Common::find_if(fslist.begin(), fslist.end(), fsnodeNameEqualsIgnoreCase(waveNames)); - - // Make sure that we found the executable file - if (exeFsnode == fslist.end()) { - warning("Couldn't find Apple IIGS game executable (%s), not loading instruments", exeNames.begin()->c_str()); - return false; - } - - // Make sure that we found the wave file - if (waveFsnode == fslist.end()) { - warning("Couldn't find Apple IIGS wave file (%s), not loading instruments", waveNames.begin()->c_str()); - return false; - } - - // Set the MIDI program change to instrument number mapping and - // load the instrument headers and their sample data. - // None of the tested SIERRASTANDARD-files have zeroes in them so - // there's no need to check for prematurely ending samples here. - _gsSound.setProgramChangeMapping(&exeInfo->instSet.progToInst); - return _gsSound.loadWaveFile(*waveFsnode, *exeInfo) && _gsSound.loadInstrumentHeaders(*exeFsnode, *exeInfo); -} - -void SoundMgr::fillAudio(void *udata, int16 *stream, uint len) { - SoundMgr *soundMgr = (SoundMgr *)udata; - uint32 p = 0; - - // current number of audio bytes in _sndBuffer - static uint32 data_available = 0; - // offset of start of audio bytes in _sndBuffer - static uint32 data_offset = 0; - - len <<= 2; - - debugC(5, kDebugLevelSound, "(%p, %p, %d)", (void *)udata, (void *)stream, len); - - while (len > data_available) { - memcpy((uint8 *)stream + p, (uint8*)_sndBuffer + data_offset, data_available); - p += data_available; - len -= data_available; - - soundMgr->playSound(); - data_available = soundMgr->mixSound() << 1; - data_offset = 0; - } - - memcpy((uint8 *)stream + p, (uint8*)_sndBuffer + data_offset, len); - data_offset += len; - data_available -= len; -} - -SoundMgr::SoundMgr(AgiBase *agi, Audio::Mixer *pMixer) : _chn() { - _vm = agi; - _mixer = pMixer; - _sampleRate = pMixer->getOutputRate(); - _endflag = -1; - _playingSound = -1; - _env = false; - _playing = false; - _sndBuffer = (int16 *)calloc(2, BUFFER_SIZE); - _waveform = 0; - _disabledMidi = false; - _useChorus = true; // FIXME: Currently always true? -} - -void SoundMgr::premixerCall(int16 *data, uint len) { - fillAudio(this, data, len); } void SoundMgr::setVolume(uint8 volume) { @@ -1315,7 +199,6 @@ void SoundMgr::setVolume(uint8 volume) { } SoundMgr::~SoundMgr() { - free(_sndBuffer); } } // End of namespace Agi diff --git a/engines/agi/sound.h b/engines/agi/sound.h index 881e3efd56..63b36e017c 100644 --- a/engines/agi/sound.h +++ b/engines/agi/sound.h @@ -26,160 +26,18 @@ #ifndef AGI_SOUND_H #define AGI_SOUND_H -#include "sound/audiostream.h" #include "sound/mixer.h" -#include "common/frac.h" namespace Agi { -#define BUFFER_SIZE 410 - -// Apple IIGS MIDI uses 60 ticks per second (Based on tests with Apple IIGS -// KQ1 and SQ1 under MESS 0.124a). So we make the audio buffer size to be a -// 1/60th of a second in length. That should be getSampleRate() / 60 samples -// in length but as getSampleRate() is always 22050 at the moment we just use -// the hardcoded value of 368 (22050/60 = 367.5 which rounds up to 368). -// FIXME: Use getSampleRate() / 60 rather than a hardcoded value -#define IIGS_BUFFER_SIZE 368 - #define SOUND_EMU_NONE 0 #define SOUND_EMU_PC 1 -#define SOUND_EMU_TANDY 2 +#define SOUND_EMU_PCJR 2 #define SOUND_EMU_MAC 3 #define SOUND_EMU_AMIGA 4 #define SOUND_EMU_APPLE2GS 5 #define SOUND_EMU_COCO3 6 - -#define WAVEFORM_SIZE 64 -#define ENV_ATTACK 10000 /**< envelope attack rate */ -#define ENV_DECAY 1000 /**< envelope decay rate */ -#define ENV_SUSTAIN 100 /**< envelope sustain level */ -#define ENV_RELEASE 7500 /**< envelope release rate */ -#define NUM_CHANNELS 7 /**< number of sound channels */ - -// MIDI command values (Shifted right by 4 so they're in the lower nibble) -#define MIDI_CMD_NOTE_OFF 0x08 -#define MIDI_CMD_NOTE_ON 0x09 -#define MIDI_CMD_CONTROLLER 0x0B -#define MIDI_CMD_PROGRAM_CHANGE 0x0C -#define MIDI_CMD_PITCH_WHEEL 0x0E -// Whole MIDI byte values (Command and channel info together) -#define MIDI_BYTE_STOP_SEQUENCE 0xFC -#define MIDI_BYTE_TIMER_SYNC 0xF8 - -struct IIgsEnvelopeSegment { - uint8 bp; - uint16 inc; ///< 8b.8b fixed point, very probably little endian -}; - -#define ENVELOPE_SEGMENT_COUNT 8 -struct IIgsEnvelope { - IIgsEnvelopeSegment seg[ENVELOPE_SEGMENT_COUNT]; - - /** Reads an Apple IIGS envelope from then given stream. */ - bool read(Common::SeekableReadStream &stream); -}; - -// 2**(1/12) i.e. the 12th root of 2 -#define SEMITONE 1.059463094359295 - -// C6's frequency is A4's (440 Hz) frequency but one full octave and three semitones higher -// i.e. C6_FREQ = 440 * pow(2.0, 15/12.0) -#define C6_FREQ 1046.502261202395 - -// Size of the SIERRASTANDARD file (i.e. the wave file i.e. the sample data used by the instruments). -#define SIERRASTANDARD_SIZE 65536 - -// Maximum number of instruments in an Apple IIGS instrument set. -// Chosen empirically based on Apple IIGS AGI game data, increase if needed. -#define MAX_INSTRUMENTS 28 - -struct IIgsWaveInfo { - uint8 top; - uint addr; - uint size; -// Oscillator channel -#define OSC_CHANNEL_RIGHT 0 -#define OSC_CHANNEL_LEFT 1 - uint channel; -// Oscillator mode -#define OSC_MODE_LOOP 0 -#define OSC_MODE_ONESHOT 1 -#define OSC_MODE_SYNC_AM 2 -#define OSC_MODE_SWAP 3 - uint mode; - bool halt; - int16 relPitch; ///< Relative pitch in semitones (Signed 8b.8b fixed point) - - /** Reads an Apple IIGS wave information structure from the given stream. */ - bool read(Common::SeekableReadStream &stream, bool ignoreAddr = false); - bool finalize(Common::SeekableReadStream &uint8Wave); -}; - -// Number of waves per Apple IIGS sound oscillator -#define WAVES_PER_OSCILLATOR 2 - -/** An Apple IIGS sound oscillator. Consists always of two waves. */ -struct IIgsOscillator { - IIgsWaveInfo waves[WAVES_PER_OSCILLATOR]; - - bool finalize(Common::SeekableReadStream &uint8Wave); -}; - -// Maximum number of oscillators in an Apple IIGS instrument. -// Chosen empirically based on Apple IIGS AGI game data, increase if needed. -#define MAX_OSCILLATORS 4 - -/** An Apple IIGS sound oscillator list. */ -struct IIgsOscillatorList { - uint count; ///< Oscillator count - IIgsOscillator osc[MAX_OSCILLATORS]; ///< The oscillators - - /** Indexing operators for easier access to the oscillators. */ - const IIgsOscillator &operator()(uint index) const { return osc[index]; } - IIgsOscillator &operator()(uint index) { return osc[index]; } - - /** Reads an Apple IIGS oscillator list from the given stream. */ - bool read(Common::SeekableReadStream &stream, uint oscillatorCount, bool ignoreAddr = false); - bool finalize(Common::SeekableReadStream &uint8Wave); -}; - -struct IIgsInstrumentHeader { - IIgsEnvelope env; - uint8 relseg; - uint8 bendrange; - uint8 vibdepth; - uint8 vibspeed; - IIgsOscillatorList oscList; - - /** - * Read an Apple IIGS instrument header from the given stream. - * @param stream The source stream from which to read the data. - * @param ignoreAddr Should we ignore wave infos' wave address variable's value? - * @return True if successful, false otherwise. - */ - bool read(Common::SeekableReadStream &stream, bool ignoreAddr = false); - bool finalize(Common::SeekableReadStream &uint8Wave); -}; - -struct IIgsSampleHeader { - uint16 type; - uint8 pitch; ///< Logarithmic, base is 2**(1/12), unknown multiplier (Possibly in range 1040-1080) - uint8 unknownByte_Ofs3; // 0x7F in Gold Rush's sound resource 60, 0 in all others. - uint8 volume; ///< Current guess: Logarithmic in 6 dB steps - uint8 unknownByte_Ofs5; ///< 0 in all tested samples. - uint16 instrumentSize; ///< Little endian. 44 in all tested samples. A guess. - uint16 sampleSize; ///< Little endian. Accurate in all tested samples excluding Manhunter I's sound resource 16. - IIgsInstrumentHeader instrument; - - /** - * Read an Apple IIGS AGI sample header from the given stream. - * @param stream The source stream from which to read the data. - * @return True if successful, false otherwise. - */ - bool read(Common::SeekableReadStream &stream); - bool finalize(Common::SeekableReadStream &uint8Wave); -}; +#define SOUND_EMU_MIDI 7 /** * AGI sound note structure. @@ -200,87 +58,38 @@ struct AgiNote { } }; -struct IIgsChannelInfo { - const IIgsInstrumentHeader *ins; ///< Instrument info - const int8 *relocatedSample; ///< Source sample data (8-bit signed format) using relocation - const int8 *unrelocatedSample; ///< Source sample data (8-bit signed format) without relocation - frac_t pos; ///< Current sample position - frac_t posAdd; ///< Current sample position adder (Calculated using note, vibrato etc) - uint8 origNote; ///< The original note without the added relative pitch - frac_t note; ///< Note (With the added relative pitch) - frac_t vol; ///< Current volume (Takes both channel volume and enveloping into account) - frac_t chanVol; ///< Channel volume - frac_t startEnvVol; ///< Starting envelope volume - frac_t envVol; ///< Current envelope volume - uint envSeg; ///< Current envelope segment - uint size; ///< Sample size - bool loop; ///< Should we loop the sample? - bool end; ///< Has the playing ended? - - void rewind(); ///< Rewinds the sound playing on this channel to its start - void setChannelVolume(uint8 volume); ///< Sets the channel volume - void setInstrument(const IIgsInstrumentHeader *instrument, const int8 *sample); ///< Sets the instrument to be used on this channel - void noteOn(uint8 noteParam, uint8 velocity); ///< Starts playing a note on this channel - void noteOff(uint8 velocity); ///< Releases the note on this channel - void stop(); ///< Stops the note playing on this channel instantly - bool playing(); ///< Is there a note playing on this channel? -}; - -struct CoCoNote { - uint8 freq; - uint8 volume; - uint16 duration; ///< Note duration - - /** Reads a CoCoNote through the given pointer. */ - void read(const uint8 *ptr) { - freq = *ptr; - volume = *(ptr + 1); - duration = READ_LE_UINT16(ptr + 2); - } -}; - /** * AGI sound resource types. * It's probably coincidence that all the values here are powers of two * as they're simply the different used values in AGI sound resources' * starts (The first 16-bit little endian word, to be precise). */ -enum AgiSoundType { +enum AgiSoundEmuType { AGI_SOUND_SAMPLE = 0x0001, AGI_SOUND_MIDI = 0x0002, AGI_SOUND_4CHN = 0x0008 }; -enum AgiSoundFlags { - AGI_SOUND_LOOP = 0x0001, - AGI_SOUND_ENVELOPE = 0x0002 -}; -enum AgiSoundEnv { - AGI_SOUND_ENV_ATTACK = 3, - AGI_SOUND_ENV_DECAY = 2, - AGI_SOUND_ENV_SUSTAIN = 1, - AGI_SOUND_ENV_RELEASE = 0 -}; +class SoundMgr; -/** - * AGI engine sound channel structure. - */ -struct ChannelInfo { - AgiSoundType type; - const uint8 *ptr; // Pointer to the AgiNote data - const int16 *ins; - int32 size; - uint32 phase; - uint32 flags; // ORs values from AgiSoundFlags - AgiSoundEnv adsr; - int32 timer; - uint32 end; - uint32 freq; - uint32 vol; - uint32 env; -}; +class SoundGen { +public: + SoundGen(AgiEngine *vm, Audio::Mixer *pMixer) : _vm(vm), _mixer(pMixer) { + _sampleRate = pMixer->getOutputRate(); + } -class SoundMgr; + virtual ~SoundGen() {} + + virtual void play(int resnum) = 0; + virtual void stop(void) = 0; + + AgiEngine *_vm; + + Audio::Mixer *_mixer; + Audio::SoundHandle _soundHandle; + + uint32 _sampleRate; +}; /** * AGI sound resource structure. @@ -302,7 +111,7 @@ public: * from memory using free() or delegate the responsibility onwards to some other * function! */ - static AgiSound *createFromRawResource(uint8 *data, uint32 len, int resnum, SoundMgr &manager); + static AgiSound *createFromRawResource(uint8 *data, uint32 len, int resnum, SoundMgr &manager, int soundemu); protected: SoundMgr &_manager; ///< AGI sound manager object @@ -313,7 +122,7 @@ protected: class PCjrSound : public AgiSound { public: PCjrSound(uint8 *data, uint32 len, int resnum, SoundMgr &manager); - ~PCjrSound() { if (_data != NULL) free(_data); } + ~PCjrSound() { free(_data); } virtual uint16 type() { return _type; } const uint8 *getVoicePointer(uint voiceNum); protected: @@ -322,192 +131,30 @@ protected: uint16 _type; ///< Sound resource type }; -class IIgsMidi : public AgiSound { -public: - IIgsMidi(uint8 *data, uint32 len, int resnum, SoundMgr &manager); - ~IIgsMidi() { if (_data != NULL) free(_data); } - virtual uint16 type() { return _type; } - virtual const uint8 *getPtr() { return _ptr; } - virtual void setPtr(const uint8 *ptr) { _ptr = ptr; } - virtual void rewind() { _ptr = _data + 2; _midiTicks = _soundBufTicks = 0; } -protected: - uint8 *_data; ///< Raw sound resource data - const uint8 *_ptr; ///< Pointer to the current position in the MIDI data - uint32 _len; ///< Length of the raw sound resource - uint16 _type; ///< Sound resource type -public: - uint _midiTicks; ///< MIDI song position in ticks (1/60ths of a second) - uint _soundBufTicks; ///< Sound buffer position in ticks (1/60ths of a second) -}; - -class IIgsSample : public AgiSound { -public: - IIgsSample(uint8 *data, uint32 len, int resnum, SoundMgr &manager); - ~IIgsSample() { delete[] _sample; } - virtual uint16 type() { return _header.type; } - const IIgsSampleHeader &getHeader() const { return _header; } - const int8 *getSample() const { return _sample; } -protected: - IIgsSampleHeader _header; ///< Apple IIGS AGI sample header - int8 *_sample; ///< Sample data (8-bit signed format) -}; - -/** Apple IIGS MIDI program change to instrument number mapping. */ -struct MidiProgramChangeMapping { - byte midiProgToInst[44]; ///< Lookup table for the MIDI program number to instrument number mapping - byte undefinedInst; ///< The undefined instrument number - - // Maps the MIDI program number to an instrument number - byte map(uint midiProg) const { - return midiProg < ARRAYSIZE(midiProgToInst) ? midiProgToInst[midiProg] : undefinedInst; - } -}; - -/** Apple IIGS AGI instrument set information. */ -struct InstrumentSetInfo { - uint byteCount; ///< Length of the whole instrument set in bytes - uint instCount; ///< Amount of instrument in the set - const char *md5; ///< MD5 hex digest of the whole instrument set - const char *waveFileMd5; ///< MD5 hex digest of the wave file (i.e. the sample data used by the instruments) - const MidiProgramChangeMapping &progToInst; ///< Program change to instrument number mapping -}; - -/** Apple IIGS AGI executable file information. */ -struct IIgsExeInfo { - enum AgiGameID gameid; ///< Game ID - const char *exePrefix; ///< Prefix of the Apple IIGS AGI executable (e.g. "SQ", "PQ", "KQ4" etc) - uint agiVer; ///< Apple IIGS AGI version number, not strictly needed - uint exeSize; ///< Size of the Apple IIGS AGI executable file in bytes - uint instSetStart; ///< Starting offset of the instrument set inside the executable file - const InstrumentSetInfo &instSet; ///< Information about the used instrument set -}; - -class IIgsMidiChannel { -public: - IIgsMidiChannel() : _instrument(0), _sample(0), _volume(0) {} - uint activeSounds() const; ///< How many active sounds are playing? - void setInstrument(const IIgsInstrumentHeader *instrument, const int8 *sample); - void setVolume(uint8 volume); - void noteOff(uint8 note, uint8 velocity); - void noteOn(uint8 note, uint8 velocity); - void stopSounds(); ///< Clears the channel of any sounds - void removeStoppedSounds(); ///< Removes all stopped sounds from this MIDI channel -public: - typedef Common::Array<IIgsChannelInfo>::const_iterator const_iterator; - typedef Common::Array<IIgsChannelInfo>::iterator iterator; - Common::Array<IIgsChannelInfo> _gsChannels; ///< Apple IIGS channels playing on this MIDI channel -protected: - const IIgsInstrumentHeader *_instrument; ///< Instrument used on this MIDI channel - const int8 *_sample; ///< Sample data used on this MIDI channel - uint8 _volume; ///< MIDI controller number 7 (Volume) -}; - -/** - * Class for managing Apple IIGS sound channels. - * TODO: Check what instruments are used by default on the MIDI channels - * FIXME: Some instrument choices sound wrong - */ -class IIgsSoundMgr { -public: - typedef Common::Array<IIgsMidiChannel>::const_iterator const_iterator; - typedef Common::Array<IIgsMidiChannel>::iterator iterator; - static const uint kSfxMidiChannel = 0; ///< The MIDI channel used for playing sound effects -public: - // For initializing - IIgsSoundMgr(); - void setProgramChangeMapping(const MidiProgramChangeMapping *mapping); - bool loadInstrumentHeaders(const Common::FSNode &exePath, const IIgsExeInfo &exeInfo); - bool loadWaveFile(const Common::FSNode &wavePath, const IIgsExeInfo &exeInfo); - // Miscellaneous methods - uint activeSounds() const; ///< How many active sounds are playing? - void stopSounds(); ///< Stops all sounds - void removeStoppedSounds(); ///< Removes all stopped sounds from the MIDI channels - // For playing Apple IIGS AGI samples (Sound effects etc) - bool playSampleSound(const IIgsSampleHeader &sampleHeader, const int8 *sample); - // MIDI commands - void midiNoteOff(uint8 channel, uint8 note, uint8 velocity); - void midiNoteOn(uint8 channel, uint8 note, uint8 velocity); - void midiController(uint8 channel, uint8 controller, uint8 value); - void midiProgramChange(uint8 channel, uint8 program); - void midiPitchWheel(uint8 wheelPos); -protected: - const IIgsInstrumentHeader* getInstrument(uint8 program) const; -public: - Common::Array<IIgsMidiChannel> _midiChannels; ///< Information about each MIDI channel -protected: - Common::Array<int8> _wave; ///< Sample data used by the Apple IIGS MIDI instruments - const MidiProgramChangeMapping *_midiProgToInst; ///< MIDI program change to instrument number mapping - Common::Array<IIgsInstrumentHeader> _instruments; ///< Instruments used by the Apple IIGS AGI -}; - -class AgiEngine; -class AgiBase; - -class SoundMgr : public Audio::AudioStream { - AgiBase *_vm; +class SoundMgr { public: - SoundMgr(AgiBase *agi, Audio::Mixer *pMixer); + SoundMgr(AgiEngine *agi, Audio::Mixer *pMixer); ~SoundMgr(); - virtual void setVolume(uint8 volume); - - // AudioStream API - int readBuffer(int16 *buffer, const int numSamples) { - premixerCall(buffer, numSamples / 2); - return numSamples; - } - - bool isStereo() const { - return false; - } - bool endOfData() const { - return false; - } - - int getRate() const { - // FIXME: Ideally, we should use _sampleRate. - return 22050; - } - -private: - Audio::Mixer *_mixer; - Audio::SoundHandle _soundHandle; - uint32 _sampleRate; - - bool _playing; - ChannelInfo _chn[NUM_CHANNELS]; - IIgsSoundMgr _gsSound; - int _endflag; - int _playingSound; - uint8 _env; - bool _disabledMidi; - - int16 *_sndBuffer; - const int16 *_waveform; - - bool _useChorus; - - void premixerCall(int16 *buf, uint len); - void fillAudio(void *udata, int16 *stream, uint len); + void setVolume(uint8 volume); -public: void unloadSound(int); void playSound(); int initSound(); void deinitSound(); void startSound(int, int); void stopSound(); - void stopNote(int i); - void playNote(int i, int freq, int vol); - void playAgiSound(); - void playCoCoSound(); - uint32 mixSound(); - bool loadInstruments(); - void playMidiSound(); - void playSampleSound(); - const IIgsExeInfo *getIIgsExeInfo(enum AgiGameID gameid) const; - static bool convertWave(Common::SeekableReadStream &source, int8 *dest, uint length); + + void soundIsFinished(); + +private: + int _endflag; + AgiEngine *_vm; + + SoundGen *_soundGen; + + int _playingSound; }; } // End of namespace Agi diff --git a/engines/agi/sound_2gs.cpp b/engines/agi/sound_2gs.cpp new file mode 100644 index 0000000000..cc1cd0f6d5 --- /dev/null +++ b/engines/agi/sound_2gs.cpp @@ -0,0 +1,919 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "common/config-manager.h" +#include "common/fs.h" +#include "common/md5.h" +#include "common/str-array.h" + +#include "agi/agi.h" +#include "agi/sound_2gs.h" + +namespace Agi { + +SoundGen2GS::SoundGen2GS(AgiEngine *vm, Audio::Mixer *pMixer) : SoundGen(vm, pMixer) { + _disabledMidi = !loadInstruments(); + + _playingSound = -1; + _playing = false; + + _sndBuffer = (int16 *)calloc(2, BUFFER_SIZE); + + _midiChannels.resize(16); // Set the amount of available MIDI channels + + _mixer->playStream(Audio::Mixer::kMusicSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true); +} + +SoundGen2GS::~SoundGen2GS() { + _mixer->stopHandle(_soundHandle); + + free(_sndBuffer); +} + +int SoundGen2GS::readBuffer(int16 *buffer, const int numSamples) { + fillAudio(buffer, numSamples / 2); + + return numSamples; +} + +void SoundGen2GS::play(int resnum) { + AgiSoundEmuType type; + + _playingSound = resnum; + + type = (AgiSoundEmuType)_vm->_game.sounds[resnum]->type(); + + assert (type == AGI_SOUND_SAMPLE || type == AGI_SOUND_MIDI); + + switch (type) { + case AGI_SOUND_SAMPLE: { + IIgsSample *sampleRes = (IIgsSample *) _vm->_game.sounds[_playingSound]; + playSampleSound(sampleRes->getHeader(), sampleRes->getSample()); + break; + } + case AGI_SOUND_MIDI: + ((IIgsMidi *) _vm->_game.sounds[_playingSound])->rewind(); + break; + default: + break; + } +} + +void SoundGen2GS::stop() { + _playingSound = -1; + + // Stops all sounds on all MIDI channels + for (iterator iter = _midiChannels.begin(); iter != _midiChannels.end(); ++iter) + iter->stopSounds(); +} + +void SoundGen2GS::playSound() { + if (_playingSound == -1) + return; + + if (_vm->_game.sounds[_playingSound]->type() == AGI_SOUND_MIDI) { + playMidiSound(); + //warning("playSound: Trying to play an Apple IIGS MIDI sound. Not yet implemented"); + } else if (_vm->_game.sounds[_playingSound]->type() == AGI_SOUND_SAMPLE) { + //debugC(3, kDebugLevelSound, "playSound: Trying to play an Apple IIGS sample"); + playSampleSound(); + } + + if (!_playing) { + _vm->_sound->soundIsFinished(); + + _playingSound = -1; + } +} + +uint32 SoundGen2GS::mixSound() { + int i, b; + + memset(_sndBuffer, 0, BUFFER_SIZE << 1); + + if (!_playing || _playingSound == -1) + return BUFFER_SIZE; + + // Handle Apple IIGS sound mixing here + // TODO: Implement playing both waves in an oscillator + // TODO: Implement swap-mode in an oscillator + for (uint midiChan = 0; midiChan < _midiChannels.size(); midiChan++) { + for (uint gsChan = 0; gsChan < _midiChannels[midiChan]._gsChannels.size(); gsChan++) { + IIgsChannelInfo &channel = _midiChannels[midiChan]._gsChannels[gsChan]; + if (channel.playing()) { // Only mix in actively playing channels + // Frequency multiplier was 1076.0 based on tests made with MESS 0.117. + // Tests made with KEGS32 averaged the multiplier to around 1045. + // So this is a guess but maybe it's 1046.5... i.e. C6's frequency? + double hertz = C6_FREQ * pow(SEMITONE, fracToDouble(channel.note)); + channel.posAdd = doubleToFrac(hertz / getRate()); + channel.vol = doubleToFrac(fracToDouble(channel.envVol) * fracToDouble(channel.chanVol) / 127.0); + double tempVol = fracToDouble(channel.vol)/127.0; + for (i = 0; i < IIGS_BUFFER_SIZE; i++) { + b = channel.relocatedSample[fracToInt(channel.pos)]; + // TODO: Find out what volume/amplification setting is loud enough + // but still doesn't clip when playing many channels on it. + _sndBuffer[i] += (int16) (b * tempVol * 256/4); + channel.pos += channel.posAdd; + + if (channel.pos >= intToFrac(channel.size)) { + if (channel.loop) { + // Don't divide by zero on zero length samples + channel.pos %= intToFrac(channel.size + (channel.size == 0)); + // Probably we should loop the envelope too + channel.envSeg = 0; + channel.envVol = channel.startEnvVol; + } else { + channel.pos = channel.chanVol = 0; + channel.end = true; + break; + } + } + } + + if (channel.envSeg < ENVELOPE_SEGMENT_COUNT) { + const IIgsEnvelopeSegment &seg = channel.ins->env.seg[channel.envSeg]; + // I currently assume enveloping works with the same speed as the MIDI + // (i.e. with 1/60ths of a second ticks). + // TODO: Check if enveloping really works with the same speed as MIDI + frac_t envVolDelta = doubleToFrac(seg.inc/256.0); + if (intToFrac(seg.bp) >= channel.envVol) { + channel.envVol += envVolDelta; + if (channel.envVol >= intToFrac(seg.bp)) { + channel.envVol = intToFrac(seg.bp); + channel.envSeg += 1; + } + } else { + channel.envVol -= envVolDelta; + if (channel.envVol <= intToFrac(seg.bp)) { + channel.envVol = intToFrac(seg.bp); + channel.envSeg += 1; + } + } + } + } + } + } + + removeStoppedSounds(); + + return IIGS_BUFFER_SIZE; +} + +void SoundGen2GS::fillAudio(int16 *stream, uint len) { + uint32 p = 0; + + // current number of audio bytes in _sndBuffer + static uint32 data_available = 0; + // offset of start of audio bytes in _sndBuffer + static uint32 data_offset = 0; + + len <<= 2; + + debugC(5, kDebugLevelSound, "(%p, %d)", (void *)stream, len); + + while (len > data_available) { + memcpy((uint8 *)stream + p, (uint8*)_sndBuffer + data_offset, data_available); + p += data_available; + len -= data_available; + + playSound(); + data_available = mixSound() << 1; + data_offset = 0; + } + + memcpy((uint8 *)stream + p, (uint8*)_sndBuffer + data_offset, len); + data_offset += len; + data_available -= len; +} + +void SoundGen2GS::playSampleSound() { + if (_vm->_soundemu != SOUND_EMU_APPLE2GS) { + warning("Trying to play a sample but not using Apple IIGS sound emulation mode"); + return; + } + + if (_playingSound != -1) + _playing = activeSounds() > 0; +} + +void SoundGen2GS::stopSounds() { + // Stops all sounds on all MIDI channels + for (iterator iter = _midiChannels.begin(); iter != _midiChannels.end(); ++iter) + iter->stopSounds(); +} + +bool SoundGen2GS::playSampleSound(const IIgsSampleHeader &sampleHeader, const int8 *sample) { + stopSounds(); + IIgsMidiChannel &channel = _midiChannels[kSfxMidiChannel]; + + channel.setInstrument(&sampleHeader.instrument, sample); + channel.setVolume(sampleHeader.volume); + channel.noteOn(sampleHeader.pitch, 64); // Use default velocity (i.e. 64) + + return true; +} + +void SoundGen2GS::playMidiSound() { + if (_disabledMidi) + return; + + const uint8 *p; + uint8 parm1, parm2; + static uint8 cmd, ch; + + if (_playingSound == -1 || _vm->_game.sounds[_playingSound] == NULL) { + warning("Error playing Apple IIGS MIDI sound resource"); + _playing = false; + + return; + } + + IIgsMidi *midiObj = (IIgsMidi *) _vm->_game.sounds[_playingSound]; + + _playing = true; + p = midiObj->getPtr(); + + midiObj->_soundBufTicks++; + + while (true) { + uint8 readByte = *p; + + // Check for end of MIDI sequence marker (Can also be here before delta-time) + if (readByte == MIDI_BYTE_STOP_SEQUENCE) { + debugC(3, kDebugLevelSound, "End of MIDI sequence (Before reading delta-time)"); + _playing = false; + + midiObj->rewind(); + + return; + } else if (readByte == MIDI_BYTE_TIMER_SYNC) { + debugC(3, kDebugLevelSound, "Timer sync"); + p++; // Jump over the timer sync byte as it's not needed + + continue; + } + + uint8 deltaTime = readByte; + if (midiObj->_midiTicks + deltaTime > midiObj->_soundBufTicks) { + break; + } + midiObj->_midiTicks += deltaTime; + p++; // Jump over the delta-time byte as it was already taken care of + + // Check for end of MIDI sequence marker (This time it after reading delta-time) + if (*p == MIDI_BYTE_STOP_SEQUENCE) { + debugC(3, kDebugLevelSound, "End of MIDI sequence (After reading delta-time)"); + _playing = false; + + midiObj->rewind(); + + return; + } + + // Separate byte into command and channel if it's a command byte. + // Otherwise use running status (i.e. previously set command and channel). + if (*p & 0x80) { + cmd = *p++; + ch = cmd & 0x0f; + cmd >>= 4; + } + + switch (cmd) { + case MIDI_CMD_NOTE_OFF: + parm1 = *p++; + parm2 = *p++; + midiNoteOff(ch, parm1, parm2); + break; + case MIDI_CMD_NOTE_ON: + parm1 = *p++; + parm2 = *p++; + midiNoteOn(ch, parm1, parm2); + break; + case MIDI_CMD_CONTROLLER: + parm1 = *p++; + parm2 = *p++; + midiController(ch, parm1, parm2); + break; + case MIDI_CMD_PROGRAM_CHANGE: + parm1 = *p++; + midiProgramChange(ch, parm1); + break; + case MIDI_CMD_PITCH_WHEEL: + parm1 = *p++; + parm2 = *p++; + + uint16 wheelPos = ((parm2 & 0x7F) << 7) | (parm1 & 0x7F); // 14-bit value + midiPitchWheel(wheelPos); + break; + } + } + + midiObj->setPtr(p); +} + +void SoundGen2GS::midiNoteOff(uint8 channel, uint8 note, uint8 velocity) { + _midiChannels[channel].noteOff(note, velocity); + debugC(3, kDebugLevelSound, "note off, channel %02x, note %02x, velocity %02x", channel, note, velocity); +} + +void SoundGen2GS::midiNoteOn(uint8 channel, uint8 note, uint8 velocity) { + _midiChannels[channel].noteOn(note, velocity); + debugC(3, kDebugLevelSound, "note on, channel %02x, note %02x, velocity %02x", channel, note, velocity); +} + +// TODO: Check if controllers behave differently on different MIDI channels +// TODO: Doublecheck what other controllers than the volume controller do +void SoundGen2GS::midiController(uint8 channel, uint8 controller, uint8 value) { + IIgsMidiChannel &midiChannel = _midiChannels[channel]; + + // The tested Apple IIGS AGI MIDI resources only used + // controllers 0 (Bank select?), 7 (Volume) and 64 (Sustain On/Off). + // Controller 0's parameter was in range 94-127, + // controller 7's parameter was in range 0-127 and + // controller 64's parameter was always 0 (i.e. sustain off). + bool unimplemented = false; + switch (controller) { + case 7: // Volume + midiChannel.setVolume(value); + break; + default: + unimplemented = true; + break; + } + debugC(3, kDebugLevelSound, "controller %02x, ch %02x, val %02x%s", controller, channel, value, unimplemented ? " (Unimplemented)" : ""); +} + +void SoundGen2GS::midiProgramChange(uint8 channel, uint8 program) { + _midiChannels[channel].setInstrument(getInstrument(program), _wave.begin()); + debugC(3, kDebugLevelSound, "program change %02x, channel %02x", program, channel); +} + +void SoundGen2GS::midiPitchWheel(uint8 wheelPos) { + // In all the tested Apple IIGS AGI MIDI resources + // pitch wheel commands always used 0x2000 (Center position). + // Therefore it should be quite safe to ignore this command. + debugC(3, kDebugLevelSound, "pitch wheel position %04x (Unimplemented)", wheelPos); +} + +const IIgsInstrumentHeader* SoundGen2GS::getInstrument(uint8 program) const { + return &_instruments[_midiProgToInst->map(program)]; +} + +void SoundGen2GS::setProgramChangeMapping(const MidiProgramChangeMapping *mapping) { + _midiProgToInst = mapping; +} + +void SoundGen2GS::removeStoppedSounds() { + for (Common::Array<IIgsMidiChannel>::iterator iter = _midiChannels.begin(); iter != _midiChannels.end(); ++iter) + iter->removeStoppedSounds(); +} + +uint SoundGen2GS::activeSounds() const { + uint result = 0; + + for (Common::Array<IIgsMidiChannel>::const_iterator iter = _midiChannels.begin(); iter != _midiChannels.end(); ++iter) + result += iter->activeSounds(); + + return result; +} + +IIgsMidi::IIgsMidi(uint8 *data, uint32 len, int resnum, SoundMgr &manager) : AgiSound(manager) { + _data = data; // Save the resource pointer + _ptr = _data + 2; // Set current position to just after the header + _len = len; // Save the resource's length + _type = READ_LE_UINT16(data); // Read sound resource's type + _midiTicks = _soundBufTicks = 0; + _isValid = (_type == AGI_SOUND_MIDI) && (_data != NULL) && (_len >= 2); + + if (!_isValid) // Check for errors + warning("Error creating Apple IIGS midi sound from resource %d (Type %d, length %d)", resnum, _type, len); +} + +/** + * Convert sample from 8-bit unsigned to 8-bit signed format. + * @param source Source stream containing the 8-bit unsigned sample data. + * @param dest Destination buffer for the 8-bit signed sample data. + * @param length Length of the sample data to be converted. + */ +static bool convertWave(Common::SeekableReadStream &source, int8 *dest, uint length) { + // Convert the wave from 8-bit unsigned to 8-bit signed format + for (uint i = 0; i < length; i++) + dest[i] = (int8) ((int) source.readByte() - 128); + return !(source.eos() || source.err()); +} + +IIgsSample::IIgsSample(uint8 *data, uint32 len, int resnum, SoundMgr &manager) : AgiSound(manager) { + Common::MemoryReadStream stream(data, len, DisposeAfterUse::YES); + + // Check that the header was read ok and that it's of the correct type + if (_header.read(stream) && _header.type == AGI_SOUND_SAMPLE) { // An Apple IIGS AGI sample resource + uint32 sampleStartPos = stream.pos(); + uint32 tailLen = stream.size() - sampleStartPos; + + if (tailLen < _header.sampleSize) { // Check if there's no room for the sample data in the stream + // Apple IIGS Manhunter I: Sound resource 16 has only 16074 bytes + // of sample data although header says it should have 16384 bytes. + warning("Apple IIGS sample (%d) too short (%d bytes. Should be %d bytes). Using the part that's left", + resnum, tailLen, _header.sampleSize); + + _header.sampleSize = (uint16) tailLen; // Use the part that's left + } + + if (_header.pitch > 0x7F) { // Check if the pitch is invalid + warning("Apple IIGS sample (%d) has too high pitch (0x%02x)", resnum, _header.pitch); + + _header.pitch &= 0x7F; // Apple IIGS AGI probably did it this way too + } + + // Finalize the header info using the 8-bit unsigned sample data + _header.finalize(stream); + + // Convert sample data from 8-bit unsigned to 8-bit signed format + stream.seek(sampleStartPos); + _sample = new int8[_header.sampleSize]; + + if (_sample != NULL) + _isValid = convertWave(stream, _sample, _header.sampleSize); + } + + if (!_isValid) // Check for errors + warning("Error creating Apple IIGS sample from resource %d (Type %d, length %d)", resnum, _header.type, len); +} + +/** Reads an Apple IIGS envelope from then given stream. */ +bool IIgsEnvelope::read(Common::SeekableReadStream &stream) { + for (int segNum = 0; segNum < ENVELOPE_SEGMENT_COUNT; segNum++) { + seg[segNum].bp = stream.readByte(); + seg[segNum].inc = stream.readUint16LE(); + } + + return !(stream.eos() || stream.err()); +} + +/** Reads an Apple IIGS wave information structure from the given stream. */ +bool IIgsWaveInfo::read(Common::SeekableReadStream &stream, bool ignoreAddr) { + top = stream.readByte(); + addr = stream.readByte() * 256; + size = (1 << (stream.readByte() & 7)) * 256; + + // Read packed mode byte and parse it into parts + byte packedModeByte = stream.readByte(); + channel = (packedModeByte >> 4) & 1; // Bit 4 + mode = (packedModeByte >> 1) & 3; // Bits 1-2 + halt = (packedModeByte & 1) != 0; // Bit 0 (Converted to boolean) + + relPitch = stream.readSint16LE(); + + // Zero the wave address if we want to ignore the wave address info + if (ignoreAddr) + addr = 0; + + return !(stream.eos() || stream.err()); +} + +bool IIgsWaveInfo::finalize(Common::SeekableReadStream &uint8Wave) { + uint32 startPos = uint8Wave.pos(); // Save stream's starting position + uint8Wave.seek(addr, SEEK_CUR); // Seek to wave's address + + // Calculate the true sample size (A zero ends the sample prematurely) + uint trueSize = size; // Set a default value for the result + for (uint i = 0; i < size; i++) { + if (uint8Wave.readByte() == 0) { + trueSize = i; + // A zero in the sample stream turns off looping + // (At least that's what MESS 0.117 and KEGS32 0.91 seem to do) + if (mode == OSC_MODE_LOOP) + mode = OSC_MODE_ONESHOT; + break; + } + } + size = trueSize; // Set the true sample size + + uint8Wave.seek(startPos); // Seek back to the stream's starting position + + return true; +} + +bool IIgsOscillator::finalize(Common::SeekableReadStream &uint8Wave) { + for (uint i = 0; i < WAVES_PER_OSCILLATOR; i++) + if (!waves[i].finalize(uint8Wave)) + return false; + + return true; +} + +bool IIgsOscillatorList::read(Common::SeekableReadStream &stream, uint oscillatorCount, bool ignoreAddr) { + // First read the A waves and then the B waves for the oscillators + for (uint waveNum = 0; waveNum < WAVES_PER_OSCILLATOR; waveNum++) + for (uint oscNum = 0; oscNum < oscillatorCount; oscNum++) + if (!osc[oscNum].waves[waveNum].read(stream, ignoreAddr)) + return false; + + count = oscillatorCount; // Set the oscillator count + + return true; +} + +bool IIgsOscillatorList::finalize(Common::SeekableReadStream &uint8Wave) { + for (uint i = 0; i < count; i++) + if (!osc[i].finalize(uint8Wave)) + return false; + + return true; +} + +bool IIgsInstrumentHeader::read(Common::SeekableReadStream &stream, bool ignoreAddr) { + env.read(stream); + relseg = stream.readByte(); + /*byte priority =*/ stream.readByte(); // Not needed? 32 in all tested data. + bendrange = stream.readByte(); + vibdepth = stream.readByte(); + vibspeed = stream.readByte(); + /*byte spare =*/ stream.readByte(); // Not needed? 0 in all tested data. + byte wac = stream.readByte(); // Read A wave count + byte wbc = stream.readByte(); // Read B wave count + oscList.read(stream, wac, ignoreAddr); // Read the oscillators + return (wac == wbc) && !(stream.eos() || stream.err()); // A and B wave counts must match +} + +bool IIgsInstrumentHeader::finalize(Common::SeekableReadStream &uint8Wave) { + return oscList.finalize(uint8Wave); +} + +bool IIgsSampleHeader::read(Common::SeekableReadStream &stream) { + type = stream.readUint16LE(); + pitch = stream.readByte(); + unknownByte_Ofs3 = stream.readByte(); + volume = stream.readByte(); + unknownByte_Ofs5 = stream.readByte(); + instrumentSize = stream.readUint16LE(); + sampleSize = stream.readUint16LE(); + // Read the instrument header *ignoring* its wave address info + + return instrument.read(stream, true); +} + +bool IIgsSampleHeader::finalize(Common::SeekableReadStream &uint8Wave) { + return instrument.finalize(uint8Wave); +} + +void IIgsMidiChannel::stopSounds() { + // Stops all sounds on this single MIDI channel + for (iterator iter = _gsChannels.begin(); iter != _gsChannels.end(); ++iter) + iter->stop(); + + _gsChannels.clear(); +} + +void IIgsMidiChannel::removeStoppedSounds() { + for (int i = _gsChannels.size() - 1; i >= 0; i--) + if (!_gsChannels[i].playing()) + _gsChannels.remove_at(i); +} + +uint IIgsMidiChannel::activeSounds() const { + uint result = 0; + + for (const_iterator iter = _gsChannels.begin(); iter != _gsChannels.end(); ++iter) + if (!iter->end) + result++; + + return result; +} + +void IIgsMidiChannel::setInstrument(const IIgsInstrumentHeader *instrument, const int8 *sample) { + _instrument = instrument; + _sample = sample; + + // Set program on each Apple IIGS channel playing on this MIDI channel + for (iterator iter = _gsChannels.begin(); iter != _gsChannels.end(); ++iter) + iter->setInstrument(instrument, sample); +} + +void IIgsMidiChannel::setVolume(uint8 volume) { + _volume = volume; + + // Set volume on each Apple IIGS channel playing on this MIDI channel + for (iterator iter = _gsChannels.begin(); iter != _gsChannels.end(); ++iter) + iter->setChannelVolume(volume); +} + +void IIgsMidiChannel::noteOff(uint8 note, uint8 velocity) { + // Go through all the notes playing on this MIDI channel + // and turn off the ones that are playing the given note + for (iterator iter = _gsChannels.begin(); iter != _gsChannels.end(); ++iter) + if (iter->origNote == note) + iter->noteOff(velocity); +} + +void IIgsMidiChannel::noteOn(uint8 note, uint8 velocity) { + IIgsChannelInfo channel; + + // Use the default channel volume and instrument + channel.setChannelVolume(_volume); + channel.setInstrument(_instrument, _sample); + + // Set the note on and save the channel + channel.noteOn(note, velocity); + _gsChannels.push_back(channel); +} + +void IIgsChannelInfo::rewind() { + this->envVol = this->startEnvVol; + this->envSeg = 0; + this->pos = intToFrac(0); +} + +void IIgsChannelInfo::setChannelVolume(uint8 volume) { + this->chanVol = intToFrac(volume); +} + +void IIgsChannelInfo::setInstrument(const IIgsInstrumentHeader *instrument, const int8 *sample) { + assert(instrument != NULL && sample != NULL); + this->ins = instrument; + this->unrelocatedSample = sample; +} + +// TODO/FIXME: Implement correctly and fully (Take velocity into account etc) +void IIgsChannelInfo::noteOn(uint8 noteParam, uint8 velocity) { + this->origNote = noteParam; + this->startEnvVol = intToFrac(0); + rewind(); + + const IIgsWaveInfo *waveInfo = NULL; + + for (uint i = 0; i < ins->oscList.count; i++) + if (ins->oscList(i).waves[0].top >= noteParam) + waveInfo = &ins->oscList(i).waves[0]; + + assert(waveInfo != NULL); + + this->relocatedSample = this->unrelocatedSample + waveInfo->addr; + this->posAdd = intToFrac(0); + this->note = intToFrac(noteParam) + doubleToFrac(waveInfo->relPitch/256.0); + this->vol = doubleToFrac(fracToDouble(this->envVol) * fracToDouble(this->chanVol) / 127.0); + this->loop = (waveInfo->mode == OSC_MODE_LOOP); + this->size = waveInfo->size - waveInfo->addr; + this->end = waveInfo->halt; +} + +// TODO/FIXME: Implement correctly and fully (Take release time and velocity into account etc) +void IIgsChannelInfo::noteOff(uint8 velocity) { + this->loop = false; + this->envSeg = ins->relseg; +} + +void IIgsChannelInfo::stop() { + this->end = true; +} + +bool IIgsChannelInfo::playing() { + return !this->end; +} + +/** + * A function object (i.e. a functor) for testing if a Common::FSNode + * object's name is equal (Ignoring case) to a string or to at least + * one of the strings in a list of strings. Can be used e.g. with find_if(). + */ +struct fsnodeNameEqualsIgnoreCase : public Common::UnaryFunction<const Common::FSNode&, bool> { +// FIXME: This should be replaced; use SearchMan instead + fsnodeNameEqualsIgnoreCase(const Common::StringArray &str) : _str(str) {} + fsnodeNameEqualsIgnoreCase(const Common::String str) { _str.push_back(str); } + bool operator()(const Common::FSNode ¶m) const { + for (Common::StringArray::const_iterator iter = _str.begin(); iter != _str.end(); ++iter) + if (param.getName().equalsIgnoreCase(*iter)) + return true; + return false; + } +private: + Common::StringArray _str; +}; + +bool SoundGen2GS::loadInstruments() { + // Check that the platform is Apple IIGS, as only it uses custom instruments + if (_vm->getPlatform() != Common::kPlatformApple2GS) { + debugC(3, kDebugLevelSound, "Platform isn't Apple IIGS so not loading any instruments"); + return true; + } + + // Get info on the particular Apple IIGS AGI game's executable + const IIgsExeInfo *exeInfo = getIIgsExeInfo((enum AgiGameID) _vm->getGameID()); + if (exeInfo == NULL) { + warning("Unsupported Apple IIGS game, not loading instruments"); + return false; + } + + // List files in the game path + Common::FSList fslist; + Common::FSNode dir(ConfMan.get("path")); + if (!dir.getChildren(fslist, Common::FSNode::kListFilesOnly)) { + warning("Invalid game path (\"%s\"), not loading Apple IIGS instruments", dir.getPath().c_str()); + return false; + } + + // Populate executable filenames list (Long filename and short filename) for searching + Common::StringArray exeNames; + exeNames.push_back(Common::String(exeInfo->exePrefix) + ".SYS16"); + exeNames.push_back(Common::String(exeInfo->exePrefix) + ".SYS"); + + // Populate wave filenames list (Long filename and short filename) for searching + Common::StringArray waveNames; + waveNames.push_back("SIERRASTANDARD"); + waveNames.push_back("SIERRAST"); + + // Search for the executable file and the wave file (i.e. check if any of the filenames match) + Common::FSList::const_iterator exeFsnode, waveFsnode; + exeFsnode = Common::find_if(fslist.begin(), fslist.end(), fsnodeNameEqualsIgnoreCase(exeNames)); + waveFsnode = Common::find_if(fslist.begin(), fslist.end(), fsnodeNameEqualsIgnoreCase(waveNames)); + + // Make sure that we found the executable file + if (exeFsnode == fslist.end()) { + warning("Couldn't find Apple IIGS game executable (%s), not loading instruments", exeNames.begin()->c_str()); + return false; + } + + // Make sure that we found the wave file + if (waveFsnode == fslist.end()) { + warning("Couldn't find Apple IIGS wave file (%s), not loading instruments", waveNames.begin()->c_str()); + return false; + } + + // Set the MIDI program change to instrument number mapping and + // load the instrument headers and their sample data. + // None of the tested SIERRASTANDARD-files have zeroes in them so + // there's no need to check for prematurely ending samples here. + setProgramChangeMapping(&exeInfo->instSet.progToInst); + return loadWaveFile(*waveFsnode, *exeInfo) && loadInstrumentHeaders(*exeFsnode, *exeInfo); +} + +/** Older Apple IIGS AGI MIDI program change to instrument number mapping. */ +static const MidiProgramChangeMapping progToInstMappingV1 = { + {19, 20, 22, 23, 21, 24, 5, 5, 5, 5, + 6, 7, 10, 9, 11, 9, 15, 8, 5, 5, + 17, 16, 18, 12, 14, 5, 5, 5, 5, 5, + 0, 1, 2, 9, 3, 4, 15, 2, 2, 2, + 25, 13, 13, 25}, + 5 +}; + +/** Newer Apple IIGS AGI MIDI program change to instrument number mapping. */ +static const MidiProgramChangeMapping progToInstMappingV2 = { + {21, 22, 24, 25, 23, 26, 6, 6, 6, 6, + 7, 9, 12, 8, 13, 11, 17, 10, 6, 6, + 19, 18, 20, 14, 16, 6, 6, 6, 6, 6, + 0, 1, 2, 4, 3, 5, 17, 2, 2, 2, + 27, 15, 15, 27}, + 6 +}; + +/** Older Apple IIGS AGI instrument set. Used only by Space Quest I (AGI v1.002). */ +static const InstrumentSetInfo instSetV1 = { + 1192, 26, "7ee16bbc135171ffd6b9120cc7ff1af2", "edd3bf8905d9c238e02832b732fb2e18", progToInstMappingV1 +}; + +/** Newer Apple IIGS AGI instrument set (AGI v1.003+). Used by all others than Space Quest I. */ +static const InstrumentSetInfo instSetV2 = { + 1292, 28, "b7d428955bb90721996de1cbca25e768", "c05fb0b0e11deefab58bc68fbd2a3d07", progToInstMappingV2 +}; + +/** Information about different Apple IIGS AGI executables. */ +static const IIgsExeInfo IIgsExeInfos[] = { + {GID_SQ1, "SQ", 0x1002, 138496, 0x80AD, instSetV1}, + {GID_LSL1, "LL", 0x1003, 141003, 0x844E, instSetV2}, + {GID_AGIDEMO, "DEMO", 0x1005, 141884, 0x8469, instSetV2}, + {GID_KQ1, "KQ", 0x1006, 141894, 0x8469, instSetV2}, + {GID_PQ1, "PQ", 0x1007, 141882, 0x8469, instSetV2}, + {GID_MIXEDUP, "MG", 0x1013, 142552, 0x84B7, instSetV2}, + {GID_KQ2, "KQ2", 0x1013, 143775, 0x84B7, instSetV2}, + {GID_KQ3, "KQ3", 0x1014, 144312, 0x84B7, instSetV2}, + {GID_SQ2, "SQ2", 0x1014, 107882, 0x6563, instSetV2}, + {GID_MH1, "MH", 0x2004, 147678, 0x8979, instSetV2}, + {GID_KQ4, "KQ4", 0x2006, 147652, 0x8979, instSetV2}, + {GID_BC, "BC", 0x3001, 148192, 0x8979, instSetV2}, + {GID_GOLDRUSH, "GR", 0x3003, 148268, 0x8979, instSetV2} +}; + +/** + * Finds information about an Apple IIGS AGI executable based on the game ID. + * @return A non-null IIgsExeInfo pointer if successful, otherwise NULL. + */ +const IIgsExeInfo *SoundGen2GS::getIIgsExeInfo(enum AgiGameID gameid) const { + for (int i = 0; i < ARRAYSIZE(IIgsExeInfos); i++) + if (IIgsExeInfos[i].gameid == gameid) + return &IIgsExeInfos[i]; + return NULL; +} + +bool SoundGen2GS::loadInstrumentHeaders(const Common::FSNode &exePath, const IIgsExeInfo &exeInfo) { + bool loadedOk = false; // Was loading successful? + Common::File file; + + // Open the executable file and check that it has correct size + file.open(exePath); + if (file.size() != (int32)exeInfo.exeSize) { + debugC(3, kDebugLevelSound, "Apple IIGS executable (%s) has wrong size (Is %d, should be %d)", + exePath.getPath().c_str(), file.size(), exeInfo.exeSize); + } + + // Read the whole executable file into memory + Common::SharedPtr<Common::MemoryReadStream> data(file.readStream(file.size())); + file.close(); + + // Check that we got enough data to be able to parse the instruments + if (data && data->size() >= (int32)(exeInfo.instSetStart + exeInfo.instSet.byteCount)) { + // Check instrument set's length (The info's saved in the executable) + data->seek(exeInfo.instSetStart - 4); + uint16 instSetByteCount = data->readUint16LE(); + if (instSetByteCount != exeInfo.instSet.byteCount) { + debugC(3, kDebugLevelSound, "Wrong instrument set size (Is %d, should be %d) in Apple IIGS executable (%s)", + instSetByteCount, exeInfo.instSet.byteCount, exePath.getPath().c_str()); + } + + // Check instrument set's md5sum + data->seek(exeInfo.instSetStart); + + char md5str[32+1]; + Common::md5_file_string(*data, md5str, exeInfo.instSet.byteCount); + if (scumm_stricmp(md5str, exeInfo.instSet.md5)) { + warning("Unknown Apple IIGS instrument set (md5: %s) in %s, trying to use it nonetheless", + md5str, exePath.getPath().c_str()); + } + + // Read in the instrument set one instrument at a time + data->seek(exeInfo.instSetStart); + + // Load the instruments + _instruments.clear(); + _instruments.reserve(exeInfo.instSet.instCount); + + IIgsInstrumentHeader instrument; + for (uint i = 0; i < exeInfo.instSet.instCount; i++) { + if (!instrument.read(*data)) { + warning("Error loading Apple IIGS instrument (%d. of %d) from %s, not loading more instruments", + i + 1, exeInfo.instSet.instCount, exePath.getPath().c_str()); + break; + } + _instruments.push_back(instrument); // Add the successfully loaded instrument to the instruments array + } + + // Loading was successful only if all instruments were loaded successfully + loadedOk = (_instruments.size() == exeInfo.instSet.instCount); + } else // Couldn't read enough data from the executable file + warning("Error loading instruments from Apple IIGS executable (%s)", exePath.getPath().c_str()); + + return loadedOk; +} + +bool SoundGen2GS::loadWaveFile(const Common::FSNode &wavePath, const IIgsExeInfo &exeInfo) { + Common::File file; + + // Open the wave file and read it into memory + file.open(wavePath); + Common::SharedPtr<Common::MemoryReadStream> uint8Wave(file.readStream(file.size())); + file.close(); + + // Check that we got the whole wave file + if (uint8Wave && uint8Wave->size() == SIERRASTANDARD_SIZE) { + // Check wave file's md5sum + char md5str[32+1]; + Common::md5_file_string(*uint8Wave, md5str, SIERRASTANDARD_SIZE); + if (scumm_stricmp(md5str, exeInfo.instSet.waveFileMd5)) { + warning("Unknown Apple IIGS wave file (md5: %s, game: %s).\n" \ + "Please report the information on the previous line to the ScummVM team.\n" \ + "Using the wave file as it is - music may sound weird", md5str, exeInfo.exePrefix); + } + + uint8Wave->seek(0); // Seek wave to its start + // Convert the wave file from 8-bit unsigned to 8-bit signed and save the result + _wave.resize(uint8Wave->size()); + return convertWave(*uint8Wave, _wave.begin(), uint8Wave->size()); + } else { // Couldn't read the wave file or it had incorrect size + warning("Error loading Apple IIGS wave file (%s), not loading instruments", wavePath.getPath().c_str()); + return false; + } +} + +} // End of namespace Agi diff --git a/engines/agi/sound_2gs.h b/engines/agi/sound_2gs.h new file mode 100644 index 0000000000..12dede0b69 --- /dev/null +++ b/engines/agi/sound_2gs.h @@ -0,0 +1,353 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef AGI_SOUND_2GS_H +#define AGI_SOUND_2GS_H + +#include "common/frac.h" +#include "sound/audiostream.h" + +namespace Agi { + +#define BUFFER_SIZE 410 + +// Apple IIGS MIDI uses 60 ticks per second (Based on tests with Apple IIGS +// KQ1 and SQ1 under MESS 0.124a). So we make the audio buffer size to be a +// 1/60th of a second in length. That should be getSampleRate() / 60 samples +// in length but as getSampleRate() is always 22050 at the moment we just use +// the hardcoded value of 368 (22050/60 = 367.5 which rounds up to 368). +// FIXME: Use getSampleRate() / 60 rather than a hardcoded value +#define IIGS_BUFFER_SIZE 368 + +// MIDI command values (Shifted right by 4 so they're in the lower nibble) +#define MIDI_CMD_NOTE_OFF 0x08 +#define MIDI_CMD_NOTE_ON 0x09 +#define MIDI_CMD_CONTROLLER 0x0B +#define MIDI_CMD_PROGRAM_CHANGE 0x0C +#define MIDI_CMD_PITCH_WHEEL 0x0E +// Whole MIDI byte values (Command and channel info together) +#define MIDI_BYTE_STOP_SEQUENCE 0xFC +#define MIDI_BYTE_TIMER_SYNC 0xF8 + +struct IIgsEnvelopeSegment { + uint8 bp; + uint16 inc; ///< 8b.8b fixed point, very probably little endian +}; + +#define ENVELOPE_SEGMENT_COUNT 8 +struct IIgsEnvelope { + IIgsEnvelopeSegment seg[ENVELOPE_SEGMENT_COUNT]; + + /** Reads an Apple IIGS envelope from then given stream. */ + bool read(Common::SeekableReadStream &stream); +}; + +// 2**(1/12) i.e. the 12th root of 2 +#define SEMITONE 1.059463094359295 + +// C6's frequency is A4's (440 Hz) frequency but one full octave and three semitones higher +// i.e. C6_FREQ = 440 * pow(2.0, 15/12.0) +#define C6_FREQ 1046.502261202395 + +// Size of the SIERRASTANDARD file (i.e. the wave file i.e. the sample data used by the instruments). +#define SIERRASTANDARD_SIZE 65536 + +// Maximum number of instruments in an Apple IIGS instrument set. +// Chosen empirically based on Apple IIGS AGI game data, increase if needed. +#define MAX_INSTRUMENTS 28 + +struct IIgsWaveInfo { + uint8 top; + uint addr; + uint size; +// Oscillator channel +#define OSC_CHANNEL_RIGHT 0 +#define OSC_CHANNEL_LEFT 1 + uint channel; +// Oscillator mode +#define OSC_MODE_LOOP 0 +#define OSC_MODE_ONESHOT 1 +#define OSC_MODE_SYNC_AM 2 +#define OSC_MODE_SWAP 3 + uint mode; + bool halt; + int16 relPitch; ///< Relative pitch in semitones (Signed 8b.8b fixed point) + + /** Reads an Apple IIGS wave information structure from the given stream. */ + bool read(Common::SeekableReadStream &stream, bool ignoreAddr = false); + bool finalize(Common::SeekableReadStream &uint8Wave); +}; + +// Number of waves per Apple IIGS sound oscillator +#define WAVES_PER_OSCILLATOR 2 + +/** An Apple IIGS sound oscillator. Consists always of two waves. */ +struct IIgsOscillator { + IIgsWaveInfo waves[WAVES_PER_OSCILLATOR]; + + bool finalize(Common::SeekableReadStream &uint8Wave); +}; + +// Maximum number of oscillators in an Apple IIGS instrument. +// Chosen empirically based on Apple IIGS AGI game data, increase if needed. +#define MAX_OSCILLATORS 4 + +/** An Apple IIGS sound oscillator list. */ +struct IIgsOscillatorList { + uint count; ///< Oscillator count + IIgsOscillator osc[MAX_OSCILLATORS]; ///< The oscillators + + /** Indexing operators for easier access to the oscillators. */ + const IIgsOscillator &operator()(uint index) const { return osc[index]; } + IIgsOscillator &operator()(uint index) { return osc[index]; } + + /** Reads an Apple IIGS oscillator list from the given stream. */ + bool read(Common::SeekableReadStream &stream, uint oscillatorCount, bool ignoreAddr = false); + bool finalize(Common::SeekableReadStream &uint8Wave); +}; + +struct IIgsInstrumentHeader { + IIgsEnvelope env; + uint8 relseg; + uint8 bendrange; + uint8 vibdepth; + uint8 vibspeed; + IIgsOscillatorList oscList; + + /** + * Read an Apple IIGS instrument header from the given stream. + * @param stream The source stream from which to read the data. + * @param ignoreAddr Should we ignore wave infos' wave address variable's value? + * @return True if successful, false otherwise. + */ + bool read(Common::SeekableReadStream &stream, bool ignoreAddr = false); + bool finalize(Common::SeekableReadStream &uint8Wave); +}; + +struct IIgsSampleHeader { + uint16 type; + uint8 pitch; ///< Logarithmic, base is 2**(1/12), unknown multiplier (Possibly in range 1040-1080) + uint8 unknownByte_Ofs3; // 0x7F in Gold Rush's sound resource 60, 0 in all others. + uint8 volume; ///< Current guess: Logarithmic in 6 dB steps + uint8 unknownByte_Ofs5; ///< 0 in all tested samples. + uint16 instrumentSize; ///< Little endian. 44 in all tested samples. A guess. + uint16 sampleSize; ///< Little endian. Accurate in all tested samples excluding Manhunter I's sound resource 16. + IIgsInstrumentHeader instrument; + + /** + * Read an Apple IIGS AGI sample header from the given stream. + * @param stream The source stream from which to read the data. + * @return True if successful, false otherwise. + */ + bool read(Common::SeekableReadStream &stream); + bool finalize(Common::SeekableReadStream &uint8Wave); +}; + +struct IIgsChannelInfo { + const IIgsInstrumentHeader *ins; ///< Instrument info + const int8 *relocatedSample; ///< Source sample data (8-bit signed format) using relocation + const int8 *unrelocatedSample; ///< Source sample data (8-bit signed format) without relocation + frac_t pos; ///< Current sample position + frac_t posAdd; ///< Current sample position adder (Calculated using note, vibrato etc) + uint8 origNote; ///< The original note without the added relative pitch + frac_t note; ///< Note (With the added relative pitch) + frac_t vol; ///< Current volume (Takes both channel volume and enveloping into account) + frac_t chanVol; ///< Channel volume + frac_t startEnvVol; ///< Starting envelope volume + frac_t envVol; ///< Current envelope volume + uint envSeg; ///< Current envelope segment + uint size; ///< Sample size + bool loop; ///< Should we loop the sample? + bool end; ///< Has the playing ended? + + void rewind(); ///< Rewinds the sound playing on this channel to its start + void setChannelVolume(uint8 volume); ///< Sets the channel volume + void setInstrument(const IIgsInstrumentHeader *instrument, const int8 *sample); ///< Sets the instrument to be used on this channel + void noteOn(uint8 noteParam, uint8 velocity); ///< Starts playing a note on this channel + void noteOff(uint8 velocity); ///< Releases the note on this channel + void stop(); ///< Stops the note playing on this channel instantly + bool playing(); ///< Is there a note playing on this channel? +}; + +class IIgsMidi : public AgiSound { +public: + IIgsMidi(uint8 *data, uint32 len, int resnum, SoundMgr &manager); + ~IIgsMidi() { if (_data != NULL) free(_data); } + virtual uint16 type() { return _type; } + virtual const uint8 *getPtr() { return _ptr; } + virtual void setPtr(const uint8 *ptr) { _ptr = ptr; } + virtual void rewind() { _ptr = _data + 2; _midiTicks = _soundBufTicks = 0; } +protected: + uint8 *_data; ///< Raw sound resource data + const uint8 *_ptr; ///< Pointer to the current position in the MIDI data + uint32 _len; ///< Length of the raw sound resource + uint16 _type; ///< Sound resource type +public: + uint _midiTicks; ///< MIDI song position in ticks (1/60ths of a second) + uint _soundBufTicks; ///< Sound buffer position in ticks (1/60ths of a second) +}; + +class IIgsSample : public AgiSound { +public: + IIgsSample(uint8 *data, uint32 len, int resnum, SoundMgr &manager); + ~IIgsSample() { delete[] _sample; } + virtual uint16 type() { return _header.type; } + const IIgsSampleHeader &getHeader() const { return _header; } + const int8 *getSample() const { return _sample; } +protected: + IIgsSampleHeader _header; ///< Apple IIGS AGI sample header + int8 *_sample; ///< Sample data (8-bit signed format) +}; + +/** Apple IIGS MIDI program change to instrument number mapping. */ +struct MidiProgramChangeMapping { + byte midiProgToInst[44]; ///< Lookup table for the MIDI program number to instrument number mapping + byte undefinedInst; ///< The undefined instrument number + + // Maps the MIDI program number to an instrument number + byte map(uint midiProg) const { + return midiProg < ARRAYSIZE(midiProgToInst) ? midiProgToInst[midiProg] : undefinedInst; + } +}; + +/** Apple IIGS AGI instrument set information. */ +struct InstrumentSetInfo { + uint byteCount; ///< Length of the whole instrument set in bytes + uint instCount; ///< Amount of instrument in the set + const char *md5; ///< MD5 hex digest of the whole instrument set + const char *waveFileMd5; ///< MD5 hex digest of the wave file (i.e. the sample data used by the instruments) + const MidiProgramChangeMapping &progToInst; ///< Program change to instrument number mapping +}; + +/** Apple IIGS AGI executable file information. */ +struct IIgsExeInfo { + enum AgiGameID gameid; ///< Game ID + const char *exePrefix; ///< Prefix of the Apple IIGS AGI executable (e.g. "SQ", "PQ", "KQ4" etc) + uint agiVer; ///< Apple IIGS AGI version number, not strictly needed + uint exeSize; ///< Size of the Apple IIGS AGI executable file in bytes + uint instSetStart; ///< Starting offset of the instrument set inside the executable file + const InstrumentSetInfo &instSet; ///< Information about the used instrument set +}; + +class IIgsMidiChannel { +public: + IIgsMidiChannel() : _instrument(0), _sample(0), _volume(0) {} + uint activeSounds() const; ///< How many active sounds are playing? + void setInstrument(const IIgsInstrumentHeader *instrument, const int8 *sample); + void setVolume(uint8 volume); + void noteOff(uint8 note, uint8 velocity); + void noteOn(uint8 note, uint8 velocity); + void stopSounds(); ///< Clears the channel of any sounds + void removeStoppedSounds(); ///< Removes all stopped sounds from this MIDI channel +public: + typedef Common::Array<IIgsChannelInfo>::const_iterator const_iterator; + typedef Common::Array<IIgsChannelInfo>::iterator iterator; + Common::Array<IIgsChannelInfo> _gsChannels; ///< Apple IIGS channels playing on this MIDI channel +protected: + const IIgsInstrumentHeader *_instrument; ///< Instrument used on this MIDI channel + const int8 *_sample; ///< Sample data used on this MIDI channel + uint8 _volume; ///< MIDI controller number 7 (Volume) +}; + +class SoundGen2GS : public SoundGen, public Audio::AudioStream { +public: + SoundGen2GS(AgiEngine *vm, Audio::Mixer *pMixer); + ~SoundGen2GS(); + + void play(int resnum); + void stop(void); + + // AudioStream API + int readBuffer(int16 *buffer, const int numSamples); + + bool isStereo() const { + return false; + } + + bool endOfData() const { + return false; + } + + int getRate() const { + // FIXME: Ideally, we should use _sampleRate. + return 22050; + } + +private: + bool _disabledMidi; + int _playingSound; + bool _playing; + + int16 *_sndBuffer; + +/** + * Class for managing Apple IIGS sound channels. + * TODO: Check what instruments are used by default on the MIDI channels + * FIXME: Some instrument choices sound wrong + */ +private: + typedef Common::Array<IIgsMidiChannel>::const_iterator const_iterator; + typedef Common::Array<IIgsMidiChannel>::iterator iterator; + static const uint kSfxMidiChannel = 0; ///< The MIDI channel used for playing sound effects + + bool loadInstruments(); + const IIgsExeInfo *getIIgsExeInfo(enum AgiGameID gameid) const; + + void setProgramChangeMapping(const MidiProgramChangeMapping *mapping); + bool loadInstrumentHeaders(const Common::FSNode &exePath, const IIgsExeInfo &exeInfo); + bool loadWaveFile(const Common::FSNode &wavePath, const IIgsExeInfo &exeInfo); + + // Miscellaneous methods + void fillAudio(int16 *stream, uint len); + uint32 mixSound(); + void playSound(); + uint activeSounds() const; ///< How many active sounds are playing? + void stopSounds(); ///< Stops all sounds + void removeStoppedSounds(); ///< Removes all stopped sounds from the MIDI channels + + // For playing Apple IIGS AGI samples (Sound effects etc) + bool playSampleSound(const IIgsSampleHeader &sampleHeader, const int8 *sample); + void playMidiSound(); + void playSampleSound(); + + // MIDI commands + void midiNoteOff(uint8 channel, uint8 note, uint8 velocity); + void midiNoteOn(uint8 channel, uint8 note, uint8 velocity); + void midiController(uint8 channel, uint8 controller, uint8 value); + void midiProgramChange(uint8 channel, uint8 program); + void midiPitchWheel(uint8 wheelPos); + //protected: + const IIgsInstrumentHeader* getInstrument(uint8 program) const; + //public: + Common::Array<IIgsMidiChannel> _midiChannels; ///< Information about each MIDI channel + //protected: + Common::Array<int8> _wave; ///< Sample data used by the Apple IIGS MIDI instruments + const MidiProgramChangeMapping *_midiProgToInst; ///< MIDI program change to instrument number mapping + Common::Array<IIgsInstrumentHeader> _instruments; ///< Instruments used by the Apple IIGS AGI +}; + +} // End of namespace Agi + +#endif /* AGI_SOUND_2GS_H */ diff --git a/engines/agi/sound_coco3.cpp b/engines/agi/sound_coco3.cpp new file mode 100644 index 0000000000..f054be0682 --- /dev/null +++ b/engines/agi/sound_coco3.cpp @@ -0,0 +1,80 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "agi/agi.h" + +#include "agi/sound_coco3.h" + +namespace Agi { + +static int cocoFrequencies[] = { + 130, 138, 146, 155, 164, 174, 184, 195, 207, 220, 233, 246, + 261, 277, 293, 311, 329, 349, 369, 391, 415, 440, 466, 493, + 523, 554, 587, 622, 659, 698, 739, 783, 830, 880, 932, 987, + 1046, 1108, 1174, 1244, 1318, 1396, 1479, 1567, 1661, 1760, 1864, 1975, + 2093, 2217, 2349, 2489, 2637, 2793, 2959, 3135, 3322, 3520, 3729, 3951 +}; + +SoundGenCoCo3::SoundGenCoCo3(AgiEngine *vm, Audio::Mixer *pMixer) : SoundGen(vm, pMixer) { +} + +SoundGenCoCo3::~SoundGenCoCo3() { +} + +void SoundGenCoCo3::play(int resnum) { + int i = cocoFrequencies[0]; // Silence warning + + i = i + 1; + +#if 0 + int i = 0; + CoCoNote note; + + do { + note.read(_chn[i].ptr); + + if (note.freq != 0xff) { + playNote(0, cocoFrequencies[note.freq], note.volume); + + uint32 start_time = _vm->_system->getMillis(); + + while (_vm->_system->getMillis() < start_time + note.duration) { + _vm->_system->updateScreen(); + + _vm->_system->delayMillis(10); + } + } + } while (note.freq != 0xff); +#endif +} + +void SoundGenCoCo3::stop() { +} + +int SoundGenCoCo3::readBuffer(int16 *buffer, const int numSamples) { + return numSamples; +} + +} // End of namespace Agi diff --git a/engines/agi/sound_coco3.h b/engines/agi/sound_coco3.h new file mode 100644 index 0000000000..b60f1937cd --- /dev/null +++ b/engines/agi/sound_coco3.h @@ -0,0 +1,73 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef AGI_SOUND_COCO3_H +#define AGI_SOUND_COCO3_H + +#include "sound/audiostream.h" + +namespace Agi { + +struct CoCoNote { + uint8 freq; + uint8 volume; + uint16 duration; ///< Note duration + + /** Reads a CoCoNote through the given pointer. */ + void read(const uint8 *ptr) { + freq = *ptr; + volume = *(ptr + 1); + duration = READ_LE_UINT16(ptr + 2); + } +}; + +class SoundGenCoCo3 : public SoundGen, public Audio::AudioStream { +public: + SoundGenCoCo3(AgiEngine *vm, Audio::Mixer *pMixer); + ~SoundGenCoCo3(); + + void play(int resnum); + void stop(void); + + // AudioStream API + int readBuffer(int16 *buffer, const int numSamples); + + bool isStereo() const { + return false; + } + + bool endOfData() const { + return false; + } + + int getRate() const { + // FIXME: Ideally, we should use _sampleRate. + return 22050; + } +}; + +} // End of namespace Agi + +#endif /* AGI_SOUND_COCO3_H */ diff --git a/engines/agi/sound_midi.cpp b/engines/agi/sound_midi.cpp new file mode 100644 index 0000000000..57c5d54b27 --- /dev/null +++ b/engines/agi/sound_midi.cpp @@ -0,0 +1,345 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +// Code is based on: +// +// A very simple program, that converts an AGI-song into a MIDI-song. +// Feel free to use it for anything. +// +// The default instrument is "piano" for all the channels, what gives +// good results on most games. But I found, that some songs are interesting +// with other instruments. If you want to experiment, modify the "instr" +// array. +// +// Timing is not perfect, yet. It plays correct, when I use the +// Gravis-Midiplayer, but the songs are too fast when I use playmidi on +// Linux. +// +// Original program developed by Jens. Christian Restemeier +// + +// MIDI and digital music class + +#include "sound/audiostream.h" +#include "sound/mididrv.h" +#include "sound/midiparser.h" +#include "common/config-manager.h" +#include "common/file.h" +#include "common/stream.h" + +#include "agi/agi.h" + +#include "agi/sound.h" +#include "agi/sound_midi.h" + +#define SPEED_FACTOR 6 + +namespace Agi { + +static uint32 convertSND2MIDI(byte *snddata, byte **data); + +MIDISound::MIDISound(uint8 *data, uint32 len, int resnum, SoundMgr &manager) : AgiSound(manager) { + _data = data; // Save the resource pointer + _len = len; // Save the resource's length + _type = READ_LE_UINT16(data); // Read sound resource's type + _isValid = (_type == AGI_SOUND_4CHN) && (_data != NULL) && (_len >= 2); + + if (!_isValid) // Check for errors + warning("Error creating MIDI sound from resource %d (Type %d, length %d)", resnum, _type, len); +} + +SoundGenMIDI::SoundGenMIDI(AgiEngine *vm, Audio::Mixer *pMixer) : SoundGen(vm, pMixer), _parser(0), _isPlaying(false), _passThrough(false), _isGM(false) { + DeviceHandle dev = MidiDriver::detectDevice(MDT_MIDI | MDT_ADLIB); + _driver = MidiDriver::createMidi(dev); + + memset(_channel, 0, sizeof(_channel)); + memset(_channelVolume, 255, sizeof(_channelVolume)); + _masterVolume = 0; + this->open(); + _smfParser = MidiParser::createParser_SMF(); + _midiMusicData = NULL; +} + +SoundGenMIDI::~SoundGenMIDI() { + _driver->setTimerCallback(NULL, NULL); + stop(); + this->close(); + _smfParser->setMidiDriver(NULL); + delete _smfParser; + delete[] _midiMusicData; +} + +void SoundGenMIDI::setChannelVolume(int channel) { + int newVolume = _channelVolume[channel] * _masterVolume / 255; + _channel[channel]->volume(newVolume); +} + +void SoundGenMIDI::setVolume(int volume) { + Common::StackLock lock(_mutex); + + volume = CLIP(volume, 0, 255); + if (_masterVolume == volume) + return; + _masterVolume = volume; + + for (int i = 0; i < 16; ++i) { + if (_channel[i]) { + setChannelVolume(i); + } + } +} + +int SoundGenMIDI::open() { + // Don't ever call open without first setting the output driver! + if (!_driver) + return 255; + + int ret = _driver->open(); + if (ret) + return ret; + + _driver->setTimerCallback(this, &onTimer); + return 0; +} + +void SoundGenMIDI::close() { + stop(); + if (_driver) + _driver->close(); + _driver = 0; +} + +void SoundGenMIDI::send(uint32 b) { + if (_passThrough) { + _driver->send(b); + return; + } + + byte channel = (byte)(b & 0x0F); + if ((b & 0xFFF0) == 0x07B0) { + // Adjust volume changes by master volume + byte volume = (byte)((b >> 16) & 0x7F); + _channelVolume[channel] = volume; + volume = volume * _masterVolume / 255; + b = (b & 0xFF00FFFF) | (volume << 16); + } else if ((b & 0xF0) == 0xC0 && !_isGM && !_nativeMT32) { + b = (b & 0xFFFF00FF) | MidiDriver::_mt32ToGm[(b >> 8) & 0xFF] << 8; + } + else if ((b & 0xFFF0) == 0x007BB0) { + //Only respond to All Notes Off if this channel + //has currently been allocated + if (_channel[b & 0x0F]) + return; + } + + if (!_channel[channel]) { + _channel[channel] = (channel == 9) ? _driver->getPercussionChannel() : _driver->allocateChannel(); + // If a new channel is allocated during the playback, make sure + // its volume is correctly initialized. + if (_channel[channel]) + setChannelVolume(channel); + } + + if (_channel[channel]) + _channel[channel]->send(b); +} + +void SoundGenMIDI::metaEvent(byte type, byte *data, uint16 length) { + + switch (type) { + case 0x2F: // End of Track + stop(); + _vm->_sound->soundIsFinished(); + break; + default: + //warning("Unhandled meta event: %02x", type); + break; + } +} + +void SoundGenMIDI::onTimer(void *refCon) { + SoundGenMIDI *music = (SoundGenMIDI *)refCon; + Common::StackLock lock(music->_mutex); + + if (music->_parser) + music->_parser->onTimer(); +} + +void SoundGenMIDI::play(int resnum) { + MIDISound *track; + + stop(); + + _isGM = true; + + track = (MIDISound *)_vm->_game.sounds[resnum]; + + // Convert AGI Sound data to MIDI + int midiMusicSize = convertSND2MIDI(track->_data, &_midiMusicData); + + if (_smfParser->loadMusic(_midiMusicData, midiMusicSize)) { + MidiParser *parser = _smfParser; + parser->setTrack(0); + parser->setMidiDriver(this); + parser->setTimerRate(getBaseTempo()); + parser->property(MidiParser::mpCenterPitchWheelOnUnload, 1); + + _parser = parser; + + syncVolume(); + + _isPlaying = true; + } +} + +void SoundGenMIDI::stop() { + Common::StackLock lock(_mutex); + + if (!_isPlaying) + return; + + _isPlaying = false; + if (_parser) { + _parser->unloadMusic(); + _parser = NULL; + } +} + +void SoundGenMIDI::pause() { + setVolume(-1); + _isPlaying = false; +} + +void SoundGenMIDI::resume() { + syncVolume(); + _isPlaying = true; +} + +void SoundGenMIDI::syncVolume() { + int volume = ConfMan.getInt("music_volume"); + if (ConfMan.getBool("mute")) { + volume = -1; + } + setVolume(volume); +} + +/* channel / intrument setup: */ + +/* most songs are good with this: */ +unsigned char instr[] = {0, 0, 0}; + +/* cool for sq2: +unsigned char instr[] = {50, 51, 19}; +*/ + +static void writeDelta(Common::MemoryWriteStreamDynamic *st, int32 delta) { + int32 i; + + i = delta >> 21; if (i > 0) st->writeByte((i & 127) | 128); + i = delta >> 14; if (i > 0) st->writeByte((i & 127) | 128); + i = delta >> 7; if (i > 0) st->writeByte((i & 127) | 128); + st->writeByte(delta & 127); +} + +static uint32 convertSND2MIDI(byte *snddata, byte **data) { + int32 lp, ep; + int n; + double ll; + + Common::MemoryWriteStreamDynamic st; + + ll = log10(pow(2.0, 1.0 / 12.0)); + + /* Header */ + st.write("MThd", 4); + st.writeUint32BE(6); + st.writeUint16BE(1); /* mode */ + st.writeUint16BE(3); /* number of tracks */ + st.writeUint16BE(192); /* ticks / quarter */ + + for (n = 0; n < 3; n++) { + uint16 start, end, pos; + + st.write("MTrk", 4); + lp = st.pos(); + st.writeUint32BE(0); /* chunklength */ + writeDelta(&st, 0); /* set instrument */ + st.writeByte(0xc0 + n); + st.writeByte(instr[n]); + start = snddata[n * 2 + 0] | (snddata[n * 2 + 1] << 8); + end = ((snddata[n * 2 + 2] | (snddata[n * 2 + 3] << 8))) - 5; + + for (pos = start; pos < end; pos += 5) { + uint16 freq, dur; + dur = (snddata[pos + 0] | (snddata[pos + 1] << 8)) * SPEED_FACTOR; + freq = ((snddata[pos + 2] & 0x3F) << 4) + (snddata[pos + 3] & 0x0F); + if (snddata[pos + 2] > 0) { + double fr; + int note; + /* I don't know, what frequency equals midi note 0 ... */ + /* This moves the song 4 octaves down: */ + fr = (log10(111860.0 / (double)freq) / ll) - 48; + note = (int)floor(fr + 0.5); + if (note < 0) note = 0; + if (note > 127) note = 127; + /* note on */ + writeDelta(&st, 0); + st.writeByte(144 + n); + st.writeByte(note); + st.writeByte(100); + /* note off */ + writeDelta(&st, dur); + st.writeByte(128 + n); + st.writeByte(note); + st.writeByte(0); + } else { + /* note on */ + writeDelta(&st, 0); + st.writeByte(144 + n); + st.writeByte(0); + st.writeByte(0); + /* note off */ + writeDelta(&st, dur); + st.writeByte(128 + n); + st.writeByte(0); + st.writeByte(0); + } + } + writeDelta(&st, 0); + st.writeByte(0xff); + st.writeByte(0x2f); + st.writeByte(0x0); + ep = st.pos(); + st.seek(lp, SEEK_SET); + st.writeUint32BE((ep - lp) - 4); + st.seek(ep, SEEK_SET); + } + + *data = st.getData(); + + return st.pos(); +} + +} // End of namespace Agi diff --git a/engines/agi/sound_midi.h b/engines/agi/sound_midi.h new file mode 100644 index 0000000000..26b75e0d70 --- /dev/null +++ b/engines/agi/sound_midi.h @@ -0,0 +1,114 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +// Music class + +#ifndef AGI_SOUND_MIDI_H +#define AGI_SOUND_MIDI_H + +#include "sound/mididrv.h" +#include "sound/midiparser.h" +#include "common/mutex.h" + +namespace Agi { + +class MIDISound : public AgiSound { +public: + MIDISound(uint8 *data, uint32 len, int resnum, SoundMgr &manager); + ~MIDISound() { free(_data); } + virtual uint16 type() { return _type; } + uint8 *_data; ///< Raw sound resource data + uint32 _len; ///< Length of the raw sound resource + +protected: + uint16 _type; ///< Sound resource type +}; + +class SoundGenMIDI : public SoundGen, public MidiDriver { +public: + SoundGenMIDI(AgiEngine *vm, Audio::Mixer *pMixer); + ~SoundGenMIDI(); + + void play(int resnum); + void stop(); + + bool isPlaying() { return _isPlaying; } + void setPlaying(bool playing) { _isPlaying = playing; } + + void setVolume(int volume); + int getVolume() { return _masterVolume; } + void syncVolume(); + + void setNativeMT32(bool b) { _nativeMT32 = b; } + bool hasNativeMT32() { return _nativeMT32; } + void pause(); + void resume(); + void setLoop(bool loop) { _looping = loop; } + void setPassThrough(bool b) { _passThrough = b; } + + void setGM(bool isGM) { _isGM = isGM; } + + // MidiDriver interface implementation + int open(); + void close(); + void send(uint32 b); + + void metaEvent(byte type, byte *data, uint16 length); + + void setTimerCallback(void *timerParam, void (*timerProc)(void *)) { } + uint32 getBaseTempo() { return _driver ? _driver->getBaseTempo() : 0; } + + // Channel allocation functions + MidiChannel *allocateChannel() { return 0; } + MidiChannel *getPercussionChannel() { return 0; } + + MidiParser *_parser; + Common::Mutex _mutex; + +private: + + static void onTimer(void *data); + void setChannelVolume(int channel); + + MidiChannel *_channel[16]; + MidiDriver *_driver; + MidiParser *_smfParser; + byte _channelVolume[16]; + bool _nativeMT32; + bool _isGM; + bool _passThrough; + + bool _isPlaying; + bool _looping; + byte _masterVolume; + + byte *_midiMusicData; + + SoundMgr *_manager; +}; + +} // End of namespace Agi + +#endif diff --git a/engines/agi/sound_pcjr.cpp b/engines/agi/sound_pcjr.cpp new file mode 100644 index 0000000000..b9d701d7f7 --- /dev/null +++ b/engines/agi/sound_pcjr.cpp @@ -0,0 +1,512 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* Heavily based on code from NAGI + * + * COPYRIGHT AND PERMISSION NOTICE + * + * Copyright (c) 2001, 2001, 2002 Nick Sonneveld + * + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, and/or sell copies of the Software, and to permit persons + * to whom the Software is furnished to do so, provided that the above + * copyright notice(s) and this permission notice appear in all copies of + * the Software and that both the above copyright notice(s) and this + * permission notice appear in supporting documentation. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT + * OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL + * INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING + * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION + * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Except as contained in this notice, the name of a copyright holder + * shall not be used in advertising or otherwise to promote the sale, use + * or other dealings in this Software without prior written authorization + * + */ + +#include "agi/agi.h" +#include "agi/sound.h" +#include "agi/sound_pcjr.h" + +namespace Agi { + +// "fade out" or possibly "dissolve" +// v2.9xx +const int8 dissolveDataV2[] = { + -2, -3, -2, -1, + 0x00, 0x00, + 0x01, 0x01, 0x01, 0x01, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x04, 0x04, 0x04, 0x04, + 0x05, 0x05, 0x05, 0x05, + 0x06, 0x06, 0x06, 0x06, 0x06, + 0x07, 0x07, 0x07, 0x07, + 0x08, 0x08, 0x08, 0x08, + 0x09, 0x09, 0x09, 0x09, + 0x0A, 0x0A, 0x0A, 0x0A, + 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, + 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, + 0x0D, + -100 +}; + +// v3 +const int8 dissolveDataV3[] = { + -2, -3, -2, -1, + 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x04, 0x04, 0x04, 0x04, 0x04, + 0x05, 0x05, 0x05, 0x05, 0x05, + 0x06, 0x06, 0x06, 0x06, 0x06, + 0x07, 0x07, 0x07, 0x07, + 0x08, 0x08, 0x08, 0x08, + 0x09, 0x09, 0x09, 0x09, + 0x0A, 0x0A, 0x0A, 0x0A, + 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, + 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, + 0x0D, + -100 +}; + + +SoundGenPCJr::SoundGenPCJr(AgiEngine *vm, Audio::Mixer *pMixer) : SoundGen(vm, pMixer) { + _chanAllocated = 10240; // preallocate something which will most likely fit + _chanData = (int16 *)malloc(_chanAllocated << 1); + + // Pick dissolve method + // + // 0 = no dissolve.. just play for as long as it's meant to be played + // this was used in older v2.4 and under games i THINK + // 1 = not used + // 2 = v2.9+ games used a shorter dissolve + // 3 (default) = v3 games used this dissolve pattern.. slightly longer + if (_vm->getVersion() >= 0x3000) + _dissolveMethod = 3; + else if (_vm->getVersion() >= 0x2900) + _dissolveMethod = 2; + else + _dissolveMethod = 0; + + _dissolveMethod = 3; + + _mixer->playStream(Audio::Mixer::kMusicSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true); +} + +SoundGenPCJr::~SoundGenPCJr() { + free(_chanData); + + _mixer->stopHandle(_soundHandle); +} + +void SoundGenPCJr::play(int resnum) { + PCjrSound *pcjrSound = (PCjrSound *)_vm->_game.sounds[resnum]; + + for (int i = 0; i < CHAN_MAX; i++) { + _channel[i].data = pcjrSound->getVoicePointer(i % 4); + _channel[i].duration = 0; + _channel[i].avail = 0xffff; + _channel[i].dissolveCount = 0xFFFF; + _channel[i].attenuation = 0; + _channel[i].attenuationCopy = 0; + + _tchannel[i].avail = 1; + _tchannel[i].noteCount = 0; + _tchannel[i].freqCount = 250; + _tchannel[i].freqCountPrev = -1; + _tchannel[i].atten = 0xF; // silence + _tchannel[i].genType = kGenTone; + _tchannel[i].genTypePrev = -1; + } +} + +void SoundGenPCJr::stop(void) { + int i; + + for (i = 0; i < CHAN_MAX ; i++) { + _channel[i].avail = 0; + _tchannel[i].avail = 0; + } +} + +int SoundGenPCJr::volumeCalc(SndGenChan *chan) { + int8 attenuation, dissolveValue; + + const int8 *dissolveData; + + switch (_dissolveMethod) { + case 2: + dissolveData = dissolveDataV2; + break; + case 3: + default: + dissolveData = dissolveDataV3; + break; + } + + assert(chan); + + attenuation = chan->attenuation; + if (attenuation != 0x0F) { // != silence + if (chan->dissolveCount != 0xFFFF) { + dissolveValue = dissolveData[chan->dissolveCount]; + if (dissolveValue == -100) { // if at end of list + chan->dissolveCount = 0xFFFF; + chan->attenuation = chan->attenuationCopy; + attenuation = chan->attenuation; + } else { + chan->dissolveCount++; + + attenuation += dissolveValue; + if (attenuation < 0) + attenuation = 0; + if (attenuation > 0x0F) + attenuation = 0x0F; + + chan->attenuationCopy = attenuation; + + attenuation &= 0x0F; + attenuation += _vm->getvar(vVolume); + if (attenuation > 0x0F) + attenuation = 0x0F; + } + } + //if (computer_type == 2) && (attenuation < 8) + if (attenuation < 8) + attenuation += 2; + } + + return attenuation; +} + +// read the next channel data.. fill it in *tone +// if tone isn't touched.. it should be inited so it just plays silence +// return 0 if it's passing more data +// return -1 if it's passing nothing (end of data) +int SoundGenPCJr::getNextNote(int ch, Tone *tone) { + SndGenChan *chan; + const byte *data; + + assert(tone); + assert(ch < CHAN_MAX); + + if (!_vm->getflag(fSoundOn)) + return -1; + + chan = &_channel[ch]; + if (!chan->avail) + return -1; + + while ((chan->duration == 0) && (chan->duration != 0xFFFF)) { + data = chan->data; + + // read the duration of the note + chan->duration = READ_LE_UINT16(data); // duration + + // if it's 0 then it's not going to be played + // if it's 0xFFFF then the channel data has finished. + if ((chan->duration != 0) && (chan->duration != 0xFFFF)) { + // only tone channels dissolve + if ((ch != 3) && (_dissolveMethod != 0)) // != noise?? + chan->dissolveCount = 0; + + // attenuation (volume) + chan->attenuation = data[4] & 0xF; + + // frequency + if (ch < (CHAN_MAX - 1)) { + chan->freqCount = (uint16)data[2] & 0x3F; + chan->freqCount <<= 4; + chan->freqCount |= data[3] & 0x0F; + + chan->genType = kGenTone; + } else { + int noiseFreq; + + // check for white noise (1) or periodic (0) + chan->genType = (data[3] & 0x04) ? kGenWhite : kGenPeriod; + + noiseFreq = data[3] & 0x03; + + switch (noiseFreq) { + case 0: + chan->freqCount = 32; + break; + case 1: + chan->freqCount = 64; + break; + case 2: + chan->freqCount = 128; + break; + case 3: + chan->freqCount = _channel[2].freqCount * 2; + break; + } + } + } + // data now points to the next data seg-a-ment + chan->data += 5; + } + + if (chan->duration != 0xFFFF) { + tone->freqCount = chan->freqCount; + tone->atten = volumeCalc(chan); // calc volume, sent vol is different from saved vol + tone->type = chan->genType; + chan->duration--; + } else { + // kill channel + chan->avail = 0; + chan->attenuation = 0x0F; // silent + chan->attenuationCopy = 0x0F; // dunno really + + return -1; + } + + return 0; +} + +// Formulas for noise generator +// bit0 = output + +// noise feedback for white noise mode +#define FB_WNOISE 0x12000 // bit15.d(16bits) = bit0(out) ^ bit2 +//#define FB_WNOISE 0x14000 // bit15.d(16bits) = bit0(out) ^ bit1 +//#define FB_WNOISE 0x28000 // bit16.d(17bits) = bit0(out) ^ bit2 (same to AY-3-8910) +//#define FB_WNOISE 0x50000 // bit17.d(18bits) = bit0(out) ^ bit2 + +// noise feedback for periodic noise mode +// it is correct maybe (it was in the Megadrive sound manual) +//#define FB_PNOISE 0x10000 // 16bit rorate +#define FB_PNOISE 0x08000 + +// noise generator start preset (for periodic noise) +#define NG_PRESET 0x0f35 + +//#define WAVE_HEIGHT (0x7FFF) + +// Volume table. +// +// 2dB = 20*log(a/b) +// 10^(2/20)*b = a; +// value = 0x7fff; +// value /= 1.258925411794; +const int16 volTable[16] = { + 32767, 26027, 20674, 16422, 13044, 10361, 8230, 6537, 5193, 4125, 3276, 2602, 2067, 1642, 1304, 0 +}; + +#define FREQ_DIV 111844 +#define MULT FREQ_DIV + +// fill buff +int SoundGenPCJr::chanGen(int chan, int16 *stream, int len) { + ToneChan *tpcm; + Tone toneNew; + int fillSize; + int retVal; + + tpcm = &_tchannel[chan]; + + retVal = -1; + + while (len > 0) { + if (tpcm->noteCount <= 0) { + // get new tone data + toneNew.freqCount = 0; + toneNew.atten = 0xF; + toneNew.type = kGenTone; + if ((tpcm->avail) && (getNextNote(chan, &toneNew) == 0)) { + tpcm->atten = toneNew.atten; + tpcm->freqCount = toneNew.freqCount; + tpcm->genType = toneNew.type; + + // setup counters 'n stuff + // SAMPLE_RATE samples per sec.. tone changes 60 times per sec + tpcm->noteCount = SAMPLE_RATE / 60; + retVal = 0; + } else { + // if it doesn't return an + tpcm->genType = kGenSilence; + tpcm->noteCount = len; + tpcm->avail = 0; + } + } + + // write nothing + if ((tpcm->freqCount == 0) || (tpcm->atten == 0xf)) { + tpcm->genType = kGenSilence; + } + + // find which is smaller.. the buffer or the + fillSize = (tpcm->noteCount <= len) ? tpcm->noteCount : len; + + switch (tpcm->genType) { + case kGenTone: + fillSize = fillSquare(tpcm, stream, fillSize); + break; + case kGenPeriod: + case kGenWhite: + fillSize = fillNoise(tpcm, stream, fillSize); + break; + case kGenSilence: + default: + // fill with whitespace + memset(stream, 0, fillSize * sizeof(int16)); + break; + } + + tpcm->noteCount -= fillSize; + stream += fillSize; + len -= fillSize; + } + + return retVal; +} + +int SoundGenPCJr::fillSquare(ToneChan *t, int16 *buf, int len) { + int count; + + if (t->genType != t->genTypePrev) { + // make sure the freqCount is checked + t->freqCountPrev = -1; + t->sign = 1; + t->genTypePrev = t->genType; + } + + if (t->freqCount != t->freqCountPrev) { + //t->scale = (int)( (double)t->samp->freq*t->freqCount/FREQ_DIV * MULT + 0.5); + t->scale = (SAMPLE_RATE / 2) * t->freqCount; + t->count = t->scale; + t->freqCountPrev = t->freqCount; + } + + count = len; + + while (count > 0) { + *(buf++) = t->sign ? volTable[t->atten] : -volTable[t->atten]; + count--; + + // get next sample + t->count -= MULT; + while (t->count <= 0) { + t->sign ^= 1; + t->count += t->scale; + } + } + + return len; +} + +int SoundGenPCJr::fillNoise(ToneChan *t, int16 *buf, int len) { + int count; + + if (t->genType != t->genTypePrev) { + // make sure the freqCount is checked + t->freqCountPrev = -1; + t->genTypePrev = t->genType; + } + + if (t->freqCount != t->freqCountPrev) { + //t->scale = (int)( (double)t->samp->freq*t->freqCount/FREQ_DIV * MULT + 0.5); + t->scale = (SAMPLE_RATE / 2) * t->freqCount; + t->count = t->scale; + t->freqCountPrev = t->freqCount; + + t->feedback = (t->genType == kGenWhite) ? FB_WNOISE : FB_PNOISE; + // reset noise shifter + t->noiseState = NG_PRESET; + t->sign = t->noiseState & 1; + } + + count = len; + + while (count > 0) { + *(buf++) = t->sign ? volTable[t->atten] : -volTable[t->atten]; + count--; + + // get next sample + t->count -= MULT; + while (t->count <= 0) { + if (t->noiseState & 1) + t->noiseState ^= t->feedback; + + t->noiseState >>= 1; + t->sign = t->noiseState & 1; + t->count += t->scale; + } + } + + return len; +} + +int SoundGenPCJr::readBuffer(int16 *stream, const int len) { + int streamCount; + int16 *sPtr, *cPtr; + + if (_chanAllocated < len) { + free(_chanData); + _chanData = (int16 *)malloc(len << 1); + _chanAllocated = len; + } + memset(stream, 0, len << 1); + + assert(stream); + + bool finished = true; + + for (int i = 0; i < CHAN_MAX; i++) { + // get channel data(chan.userdata) + if (chanGen(i, _chanData, len) == 0) { + // divide by number of channels then add to stream + streamCount = len; + sPtr = stream; + cPtr = _chanData; + + while (streamCount--) + *(sPtr++) += *(cPtr++) / CHAN_MAX; + + finished = false; + } + } + + if (finished) + _vm->_sound->soundIsFinished(); + + return len; +} + +} // End of namespace Agi diff --git a/engines/agi/sound_pcjr.h b/engines/agi/sound_pcjr.h new file mode 100644 index 0000000000..fe0e762f4e --- /dev/null +++ b/engines/agi/sound_pcjr.h @@ -0,0 +1,127 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef AGI_SOUND_PCJR_H +#define AGI_SOUND_PCJR_H + +#include "sound/audiostream.h" + +namespace Agi { + +#define CHAN_MAX 4 + +#define SAMPLE_RATE 22050 + +enum GenType { + kGenSilence, + kGenTone, + kGenPeriod, + kGenWhite +}; + +struct SndGenChan { + const byte *data; + uint16 duration; + uint16 avail; // turned on (1) but when the channel's data runs out, it's set to (0) + uint16 dissolveCount; + byte attenuation; + byte attenuationCopy; + + GenType genType; + + // for the sample mixer + int freqCount; +}; + +struct ToneChan { + int avail; + + int noteCount; // length of tone.. duration + + int freqCount; + int freqCountPrev; + int atten; // volume + + GenType genType; + int genTypePrev; + + int count; + int scale; + int sign; + unsigned int noiseState; /* noise generator */ + int feedback; /* noise feedback mask */ +}; + +struct Tone { + int freqCount; + int atten; + GenType type; +}; + +class SoundGenPCJr : public SoundGen, public Audio::AudioStream { +public: + SoundGenPCJr(AgiEngine *vm, Audio::Mixer *pMixer); + ~SoundGenPCJr(); + + void play(int resnum); + void stop(void); + + // AudioStream API + int readBuffer(int16 *buffer, const int numSamples); + + bool isStereo() const { + return false; + } + + bool endOfData() const { + return false; + } + + int getRate() const { + // FIXME: Ideally, we should use _sampleRate. + return 22050; + } + +private: + int getNextNote(int ch, Tone *tone); + int volumeCalc(SndGenChan *chan); + + int chanGen(int chan, int16 *stream, int len); + + int fillNoise(ToneChan *t, int16 *buf, int len); + int fillSquare(ToneChan *t, int16 *buf, int len); + +private: + SndGenChan _channel[CHAN_MAX]; + ToneChan _tchannel[CHAN_MAX]; + int16 *_chanData; + int _chanAllocated; + + int _dissolveMethod; +}; + +} // End of namespace Agi + +#endif /* AGI_SOUND_PCJR_H */ diff --git a/engines/agi/sound_sarien.cpp b/engines/agi/sound_sarien.cpp new file mode 100644 index 0000000000..08bdd47497 --- /dev/null +++ b/engines/agi/sound_sarien.cpp @@ -0,0 +1,357 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "common/md5.h" +#include "common/config-manager.h" +#include "common/fs.h" +#include "common/random.h" +#include "common/str-array.h" + +#include "sound/mididrv.h" + +#include "agi/agi.h" + +#include "agi/sound_sarien.h" + +namespace Agi { + +#define USE_INTERPOLATION + +static const int16 waveformRamp[WAVEFORM_SIZE] = { + 0, 8, 16, 24, 32, 40, 48, 56, + 64, 72, 80, 88, 96, 104, 112, 120, + 128, 136, 144, 152, 160, 168, 176, 184, + 192, 200, 208, 216, 224, 232, 240, 255, + 0, -248, -240, -232, -224, -216, -208, -200, + -192, -184, -176, -168, -160, -152, -144, -136, + -128, -120, -112, -104, -96, -88, -80, -72, + -64, -56, -48, -40, -32, -24, -16, -8 // Ramp up +}; + +static const int16 waveformSquare[WAVEFORM_SIZE] = { + 255, 230, 220, 220, 220, 220, 220, 220, + 220, 220, 220, 220, 220, 220, 220, 220, + 220, 220, 220, 220, 220, 220, 220, 220, + 220, 220, 220, 220, 220, 220, 220, 110, + -255, -230, -220, -220, -220, -220, -220, -220, + -220, -220, -220, -220, -220, -220, -220, -220, + -220, -220, -220, -220, -220, -220, -220, -220, + -220, -220, -220, -110, 0, 0, 0, 0 // Square +}; + +static const int16 waveformMac[WAVEFORM_SIZE] = { + 45, 110, 135, 161, 167, 173, 175, 176, + 156, 137, 123, 110, 91, 72, 35, -2, + -60, -118, -142, -165, -170, -176, -177, -179, + -177, -176, -164, -152, -117, -82, -17, 47, + 92, 137, 151, 166, 170, 173, 171, 169, + 151, 133, 116, 100, 72, 43, -7, -57, + -99, -141, -156, -170, -174, -177, -178, -179, + -175, -172, -165, -159, -137, -114, -67, -19 +}; + +SoundGenSarien::SoundGenSarien(AgiEngine *vm, Audio::Mixer *pMixer) : SoundGen(vm, pMixer), _chn() { + _sndBuffer = (int16 *)calloc(2, BUFFER_SIZE); + + memset(_sndBuffer, 0, BUFFER_SIZE << 1); + _env = false; + _playingSound = -1; + _playing = false; + _useChorus = true; // FIXME: Currently always true? + + switch (_vm->_soundemu) { + case SOUND_EMU_NONE: + _waveform = waveformRamp; + _env = true; + break; + case SOUND_EMU_AMIGA: + case SOUND_EMU_PC: + _waveform = waveformSquare; + break; + case SOUND_EMU_MAC: + _waveform = waveformMac; + break; + } + + report("Initializing sound:\n"); + + report("sound: envelopes "); + if (_env) { + report("enabled (decay=%d, sustain=%d)\n", ENV_DECAY, ENV_SUSTAIN); + } else { + report("disabled\n"); + } + + _mixer->playStream(Audio::Mixer::kMusicSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true); +} + +SoundGenSarien::~SoundGenSarien() { + _mixer->stopHandle(_soundHandle); + + free(_sndBuffer); +} + +int SoundGenSarien::readBuffer(int16 *buffer, const int numSamples) { + fillAudio(buffer, numSamples / 2); + + return numSamples; +} + +void SoundGenSarien::play(int resnum) { + AgiSoundEmuType type; + + type = (AgiSoundEmuType)_vm->_game.sounds[resnum]->type(); + + assert(type == AGI_SOUND_4CHN); + + _playingSound = resnum; + + PCjrSound *pcjrSound = (PCjrSound *) _vm->_game.sounds[resnum]; + + // Initialize channel info + for (int i = 0; i < NUM_CHANNELS; i++) { + _chn[i].type = type; + _chn[i].flags = AGI_SOUND_LOOP; + + if (_env) { + _chn[i].flags |= AGI_SOUND_ENVELOPE; + _chn[i].adsr = AGI_SOUND_ENV_ATTACK; + } + + _chn[i].ins = _waveform; + _chn[i].size = WAVEFORM_SIZE; + _chn[i].ptr = pcjrSound->getVoicePointer(i % 4); + _chn[i].timer = 0; + _chn[i].vol = 0; + _chn[i].end = 0; + } + + memset(_sndBuffer, 0, BUFFER_SIZE << 1); +} + +void SoundGenSarien::stop() { + _playingSound = -1; + + for (int i = 0; i < NUM_CHANNELS; i++) + stopNote(i); +} + +void SoundGenSarien::stopNote(int i) { + _chn[i].adsr = AGI_SOUND_ENV_RELEASE; + + if (_useChorus) { + // Stop chorus ;) + if (_chn[i].type == AGI_SOUND_4CHN && + _vm->_soundemu == SOUND_EMU_NONE && i < 3) { + stopNote(i + 4); + } + } +} + +void SoundGenSarien::playNote(int i, int freq, int vol) { + if (!_vm->getflag(fSoundOn)) + vol = 0; + else if (vol && _vm->_soundemu == SOUND_EMU_PC) + vol = 160; + + _chn[i].phase = 0; + _chn[i].freq = freq; + _chn[i].vol = vol; + _chn[i].env = 0x10000; + _chn[i].adsr = AGI_SOUND_ENV_ATTACK; + + if (_useChorus) { + // Add chorus ;) + if (_chn[i].type == AGI_SOUND_4CHN && + _vm->_soundemu == SOUND_EMU_NONE && i < 3) { + + int newfreq = freq * 1007 / 1000; + + if (freq == newfreq) + newfreq++; + + playNote(i + 4, newfreq, vol * 2 / 3); + } + } +} + +void SoundGenSarien::playSound() { + int i; + AgiNote note; + + if (_playingSound == -1) + return; + + _playing = false; + for (i = 0; i < (_vm->_soundemu == SOUND_EMU_PC ? 1 : 4); i++) { + _playing |= !_chn[i].end; + note.read(_chn[i].ptr); // Read a single note (Doesn't advance the pointer) + + if (_chn[i].end) + continue; + + if ((--_chn[i].timer) <= 0) { + stopNote(i); + + if (note.freqDiv != 0) { + int volume = (note.attenuation == 0x0F) ? 0 : (0xFF - note.attenuation * 2); + playNote(i, note.freqDiv * 10, volume); + } + + _chn[i].timer = note.duration; + + if (_chn[i].timer == 0xffff) { + _chn[i].end = 1; + _chn[i].vol = 0; + _chn[i].env = 0; + + if (_useChorus) { + // chorus + if (_chn[i].type == AGI_SOUND_4CHN && _vm->_soundemu == SOUND_EMU_NONE && i < 3) { + _chn[i + 4].vol = 0; + _chn[i + 4].env = 0; + } + } + } + _chn[i].ptr += 5; // Advance the pointer to the next note data (5 bytes per note) + } + } + + if (!_playing) { + _vm->_sound->soundIsFinished(); + + _playingSound = -1; + } +} + +uint32 SoundGenSarien::mixSound() { + register int i, p; + const int16 *src; + int c, b, m; + + memset(_sndBuffer, 0, BUFFER_SIZE << 1); + + if (!_playing || _playingSound == -1) + return BUFFER_SIZE; + + // Handle PCjr 4-channel sound mixing here + for (c = 0; c < NUM_CHANNELS; c++) { + if (!_chn[c].vol) + continue; + + m = _chn[c].flags & AGI_SOUND_ENVELOPE ? + _chn[c].vol * _chn[c].env >> 16 : _chn[c].vol; + + if (_chn[c].type != AGI_SOUND_4CHN || c != 3) { + src = _chn[c].ins; + + p = _chn[c].phase; + for (i = 0; i < BUFFER_SIZE; i++) { + b = src[p >> 8]; +#ifdef USE_INTERPOLATION + b += ((src[((p >> 8) + 1) % _chn[c].size] - src[p >> 8]) * (p & 0xff)) >> 8; +#endif + _sndBuffer[i] += (b * m) >> 4; + + p += (uint32) 118600 *4 / _chn[c].freq; + + // FIXME: Fingolfin asks: why is there a FIXME here? Please either clarify what + // needs fixing, or remove it! + // FIXME + if (_chn[c].flags & AGI_SOUND_LOOP) { + p %= _chn[c].size << 8; + } else { + if (p >= _chn[c].size << 8) { + p = _chn[c].vol = 0; + _chn[c].end = 1; + break; + } + } + + } + _chn[c].phase = p; + } else { + // Add white noise + for (i = 0; i < BUFFER_SIZE; i++) { + b = _vm->_rnd->getRandomNumber(255) - 128; + _sndBuffer[i] += (b * m) >> 4; + } + } + + switch (_chn[c].adsr) { + case AGI_SOUND_ENV_ATTACK: + // not implemented + _chn[c].adsr = AGI_SOUND_ENV_DECAY; + break; + case AGI_SOUND_ENV_DECAY: + if (_chn[c].env > _chn[c].vol * ENV_SUSTAIN + ENV_DECAY) { + _chn[c].env -= ENV_DECAY; + } else { + _chn[c].env = _chn[c].vol * ENV_SUSTAIN; + _chn[c].adsr = AGI_SOUND_ENV_SUSTAIN; + } + break; + case AGI_SOUND_ENV_SUSTAIN: + break; + case AGI_SOUND_ENV_RELEASE: + if (_chn[c].env >= ENV_RELEASE) { + _chn[c].env -= ENV_RELEASE; + } else { + _chn[c].env = 0; + } + } + } + + return BUFFER_SIZE; +} + +void SoundGenSarien::fillAudio(int16 *stream, uint len) { + uint32 p = 0; + + // current number of audio bytes in _sndBuffer + static uint32 data_available = 0; + // offset of start of audio bytes in _sndBuffer + static uint32 data_offset = 0; + + len <<= 2; + + debugC(5, kDebugLevelSound, "(%p, %d)", (void *)stream, len); + + while (len > data_available) { + memcpy((uint8 *)stream + p, (uint8*)_sndBuffer + data_offset, data_available); + p += data_available; + len -= data_available; + + playSound(); + data_available = mixSound() << 1; + data_offset = 0; + } + + memcpy((uint8 *)stream + p, (uint8*)_sndBuffer + data_offset, len); + data_offset += len; + data_available -= len; +} + +} // End of namespace Agi diff --git a/engines/agi/sound_sarien.h b/engines/agi/sound_sarien.h new file mode 100644 index 0000000000..54222ba624 --- /dev/null +++ b/engines/agi/sound_sarien.h @@ -0,0 +1,120 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef AGI_SOUND_SARIEN_H +#define AGI_SOUND_SARIEN_H + +#include "sound/audiostream.h" + +namespace Agi { + +#define BUFFER_SIZE 410 + +#define WAVEFORM_SIZE 64 +#define ENV_ATTACK 10000 /**< envelope attack rate */ +#define ENV_DECAY 1000 /**< envelope decay rate */ +#define ENV_SUSTAIN 100 /**< envelope sustain level */ +#define ENV_RELEASE 7500 /**< envelope release rate */ +#define NUM_CHANNELS 7 /**< number of sound channels */ + +enum AgiSoundFlags { + AGI_SOUND_LOOP = 0x0001, + AGI_SOUND_ENVELOPE = 0x0002 +}; +enum AgiSoundEnv { + AGI_SOUND_ENV_ATTACK = 3, + AGI_SOUND_ENV_DECAY = 2, + AGI_SOUND_ENV_SUSTAIN = 1, + AGI_SOUND_ENV_RELEASE = 0 +}; + + +/** + * AGI engine sound channel structure. + */ +struct ChannelInfo { + AgiSoundEmuType type; + const uint8 *ptr; // Pointer to the AgiNote data + const int16 *ins; + int32 size; + uint32 phase; + uint32 flags; // ORs values from AgiSoundFlags + AgiSoundEnv adsr; + int32 timer; + uint32 end; + uint32 freq; + uint32 vol; + uint32 env; +}; + +class SoundGenSarien : public SoundGen, public Audio::AudioStream { +public: + SoundGenSarien(AgiEngine *vm, Audio::Mixer *pMixer); + ~SoundGenSarien(); + + void play(int resnum); + void stop(void); + + // AudioStream API + int readBuffer(int16 *buffer, const int numSamples); + + bool isStereo() const { + return false; + } + + bool endOfData() const { + return false; + } + + int getRate() const { + // FIXME: Ideally, we should use _sampleRate. + return 22050; + } + +private: + ChannelInfo _chn[NUM_CHANNELS]; + uint8 _env; + + int16 *_sndBuffer; + const int16 *_waveform; + + bool _useChorus; + + bool _playing; + int _playingSound; + +private: + void playSound(); + uint32 mixSound(); + void fillAudio(int16 *stream, uint len); + + void stopNote(int i); + void playNote(int i, int freq, int vol); + +}; + +} // End of namespace Agi + +#endif /* AGI_SOUND_SARIEN_H */ diff --git a/engines/agi/sprite.cpp b/engines/agi/sprite.cpp index 63ac880267..569481d772 100644 --- a/engines/agi/sprite.cpp +++ b/engines/agi/sprite.cpp @@ -241,6 +241,14 @@ void SpritesMgr::objsRestoreArea(Sprite *s) { q += xSize; pos0 += _WIDTH; } + + // WORKAROUND (see ScummVM bug #1945716) + // When set.view command is called, current code cannot detect this situation while updating + // Thus we force removal of the old sprite + if (s->v && s->v->viewReplaced) { + commitBlock(xPos, yPos, xPos + xSize, yPos + ySize); + s->v->viewReplaced = false; + } } @@ -332,6 +340,8 @@ void SpritesMgr::buildList(SpriteList &l, bool (*test)(VtEntry *, AgiEngine *)) } } + debugC(5, kDebugLevelSprites, "buildList() --> entries %d", i); + // now look for the smallest y value in the array and put that // sprite in the list for (j = 0; j < i; j++) { @@ -381,38 +391,20 @@ void SpritesMgr::freeList(SpriteList &l) { * Copy sprites from the pic buffer to the screen buffer, and check if * sprites of the given list have moved. */ -void SpritesMgr::commitSprites(SpriteList &l) { +void SpritesMgr::commitSprites(SpriteList &l, bool immediate) { SpriteList::iterator iter; for (iter = l.begin(); iter != l.end(); ++iter) { Sprite *s = *iter; - int x1, y1, x2, y2, w, h; + int x1, y1, x2, y2; - w = (s->v->celData->width > s->v->celData2->width) ? - s->v->celData->width : s->v->celData2->width; - - h = (s->v->celData->height > - s->v->celData2->height) ? s->v->celData-> - height : s->v->celData2->height; + x1 = MIN((int)MIN(s->v->xPos, s->v->xPos2), MIN(s->v->xPos + s->v->celData->width, s->v->xPos2 + s->v->celData2->width)); + x2 = MAX((int)MAX(s->v->xPos, s->v->xPos2), MAX(s->v->xPos + s->v->celData->width, s->v->xPos2 + s->v->celData2->width)); + y1 = MIN((int)MIN(s->v->yPos, s->v->yPos2), MIN(s->v->yPos - s->v->celData->height, s->v->yPos2 - s->v->celData2->height)); + y2 = MAX((int)MAX(s->v->yPos, s->v->yPos2), MAX(s->v->yPos - s->v->celData->height, s->v->yPos2 - s->v->celData2->height)); s->v->celData2 = s->v->celData; - if (s->v->xPos < s->v->xPos2) { - x1 = s->v->xPos; - x2 = s->v->xPos2 + w - 1; - } else { - x1 = s->v->xPos2; - x2 = s->v->xPos + w - 1; - } - - if (s->v->yPos < s->v->yPos2) { - y1 = s->v->yPos - h + 1; - y2 = s->v->yPos2; - } else { - y1 = s->v->yPos2 - h + 1; - y2 = s->v->yPos; - } - - commitBlock(x1, y1, x2, y2); + commitBlock(x1, y1, x2, y2, immediate); if (s->v->stepTimeCount != s->v->stepTime) continue; @@ -452,7 +444,7 @@ void SpritesMgr::blitSprites(SpriteList& l) { Sprite *s = *iter; objsSaveArea(s); - debugC(8, kDebugLevelSprites, "s->v->entry = %d (prio %d)", s->v->entry, s->v->priority); + debugC(8, kDebugLevelSprites, "blitSprites(): s->v->entry = %d (prio %d)", s->v->entry, s->v->priority); hidden = blitCel(s->xPos, s->yPos, s->v->priority, s->v->celData, s->v->viewData->agi256_2); if (s->v->entry == 0) { // if ego, update f1 @@ -528,7 +520,7 @@ void SpritesMgr::eraseBoth() { * @see blit_both() */ void SpritesMgr::blitUpdSprites() { - debugC(7, kDebugLevelSprites, "blit updating"); + debugC(7, kDebugLevelSprites, "blitUpdSprites()"); buildUpdBlitlist(); blitSprites(_sprUpd); } @@ -542,7 +534,7 @@ void SpritesMgr::blitUpdSprites() { * @see blit_both() */ void SpritesMgr::blitNonupdSprites() { - debugC(7, kDebugLevelSprites, "blit non-updating"); + debugC(7, kDebugLevelSprites, "blitNonupdSprites()"); buildNonupdBlitlist(); blitSprites(_sprNonupd); } @@ -578,7 +570,7 @@ void SpritesMgr::addToPic(int view, int loop, int cel, int x, int y, int pri, in int x1, y1, x2, y2, y3; uint8 *p1, *p2; - debugC(3, kDebugLevelSprites, "v=%d, l=%d, c=%d, x=%d, y=%d, p=%d, m=%d", view, loop, cel, x, y, pri, mar); + debugC(3, kDebugLevelSprites, "addToPic(view=%d, loop=%d, cel=%d, x=%d, y=%d, pri=%d, mar=%d)", view, loop, cel, x, y, pri, mar); _vm->recordImageStackCall(ADD_VIEW, view, loop, cel, x, y, pri, mar); @@ -609,7 +601,7 @@ void SpritesMgr::addToPic(int view, int loop, int cel, int x, int y, int pri, in eraseBoth(); - debugC(4, kDebugLevelSprites, "blit_cel (%d, %d, %d, c)", x, y, pri); + debugC(4, kDebugLevelSprites, "blitCel(%d, %d, %d, c)", x, y, pri); blitCel(x1, y1, pri, c, _vm->_game.views[view].agi256_2); // If margin is 0, 1, 2, or 3, the base of the cel is @@ -659,8 +651,7 @@ void SpritesMgr::addToPic(int view, int loop, int cel, int x, int y, int pri, in blitBoth(); - debugC(4, kDebugLevelSprites, "commit_block (%d, %d, %d, %d)", x1, y1, x2, y2); - commitBlock(x1, y1, x2, y2); + commitBlock(x1, y1, x2, y2, true); } /** @@ -688,18 +679,19 @@ void SpritesMgr::showObj(int n) { s.xSize = c->width; s.ySize = c->height; s.buffer = (uint8 *)malloc(s.xSize * s.ySize); + s.v = 0; objsSaveArea(&s); blitCel(x1, y1, 15, c, _vm->_game.views[n].agi256_2); - commitBlock(x1, y1, x2, y2); + commitBlock(x1, y1, x2, y2, true); _vm->messageBox(_vm->_game.views[n].descr); objsRestoreArea(&s); - commitBlock(x1, y1, x2, y2); + commitBlock(x1, y1, x2, y2, true); free(s.buffer); } -void SpritesMgr::commitBlock(int x1, int y1, int x2, int y2) { +void SpritesMgr::commitBlock(int x1, int y1, int x2, int y2, bool immediate) { int i, w, offset; uint8 *q; @@ -711,7 +703,7 @@ void SpritesMgr::commitBlock(int x1, int y1, int x2, int y2) { y1 = CLIP(y1, 0, _HEIGHT - 1); y2 = CLIP(y2, 0, _HEIGHT - 1); - debugC(7, kDebugLevelSprites, "%d, %d, %d, %d", x1, y1, x2, y2); + debugC(7, kDebugLevelSprites, "commitBlock(%d, %d, %d, %d)", x1, y1, x2, y2); w = x2 - x1 + 1; q = &_vm->_game.sbuf16c[x1 + _WIDTH * y1]; @@ -723,6 +715,9 @@ void SpritesMgr::commitBlock(int x1, int y1, int x2, int y2) { } _gfx->flushBlockA(x1, y1 + offset, x2, y2 + offset); + + if (immediate) + _gfx->doUpdate(); } SpritesMgr::SpritesMgr(AgiEngine *agi, GfxMgr *gfx) { diff --git a/engines/agi/sprite.h b/engines/agi/sprite.h index 7d6d7bb97e..57fd0dacf2 100644 --- a/engines/agi/sprite.h +++ b/engines/agi/sprite.h @@ -65,7 +65,7 @@ private: void buildUpdBlitlist(); void buildNonupdBlitlist(); void freeList(SpriteList &l); - void commitSprites(SpriteList &l); + void commitSprites(SpriteList &l, bool immediate = false); void eraseSprites(SpriteList &l); void blitSprites(SpriteList &l); static bool testUpdating(VtEntry *v, AgiEngine *); @@ -88,7 +88,7 @@ public: void commitBoth(); void addToPic(int, int, int, int, int, int, int); void showObj(int); - void commitBlock(int, int, int, int); + void commitBlock(int x1, int y1, int x2, int y2, bool immediate = false); }; } // End of namespace Agi diff --git a/engines/agi/text.cpp b/engines/agi/text.cpp index ee9aebf240..778da0a527 100644 --- a/engines/agi/text.cpp +++ b/engines/agi/text.cpp @@ -135,8 +135,8 @@ void AgiEngine::blitTextbox(const char *p, int y, int x, int len) { if (x == 0 && y == 0 && len == 0) x = y = -1; - if (len <= 0 || len >= 40) - len = 32; + if (len <= 0) + len = 30; xoff = x * CHAR_COLS; yoff = y * CHAR_LINES; @@ -214,6 +214,7 @@ void AgiEngine::printTextConsole(const char *msg, int x, int y, int len, int fg, x *= CHAR_COLS; y *= 10; + debugC(4, kDebugLevelText, "printTextConsole(): %s, %d, %d, %d, %d, %d", msg, x, y, len, fg, bg); printText2(1, msg, 0, x, y, len, fg, bg); } @@ -488,7 +489,7 @@ int AgiEngine::print(const char *p, int lin, int col, int len) { _game.keypress = 0; break; } - } while (_game.msgBoxTicks > 0); + } while (_game.msgBoxTicks > 0 && !(shouldQuit() || _restartGame)); setvar(vWindowReset, 0); @@ -655,11 +656,8 @@ void AgiEngine::writePrompt() { int l, fg, bg, pos; int promptLength = strlen(agiSprintf(_game.strings[0])); - if (!_game.inputEnabled || _game.inputMode != INPUT_NORMAL) { - clearPrompt(); - + if (!_game.inputEnabled || _game.inputMode != INPUT_NORMAL) return; - } l = _game.lineUserInput; fg = _game.colorFg; @@ -699,6 +697,8 @@ void AgiEngine::clearLines(int l1, int l2, int c) { // inc for endline so it matches the correct num // ie, from 22 to 24 is 3 lines, not 2 lines. + debugC(4, kDebugLevelText, "clearLines(%d, %d, %d)", l1, l2, c); + l1 *= CHAR_LINES; l2 *= CHAR_LINES; l2 += CHAR_LINES - 1; diff --git a/engines/agi/view.cpp b/engines/agi/view.cpp index fb417e86a9..fcca1e2a79 100644 --- a/engines/agi/view.cpp +++ b/engines/agi/view.cpp @@ -157,8 +157,7 @@ int AgiEngine::decodeView(int n) { return errNoLoopsInView; // allocate memory for all views - _game.views[n].loop = (ViewLoop *) - calloc(_game.views[n].numLoops, sizeof(ViewLoop)); + _game.views[n].loop = (ViewLoop *)calloc(_game.views[n].numLoops, sizeof(ViewLoop)); if (_game.views[n].loop == NULL) return errNotEnoughMemory; @@ -294,37 +293,10 @@ void AgiEngine::setLoop(VtEntry *v, int n) { * @param n number of AGI view resource */ void AgiEngine::setView(VtEntry *v, int n) { - - uint16 viewFlags = 0; - - // When setting a view to the view table, if there's already another view set in that - // view table entry and it's still drawn, erase the existing view before setting the new one - // Fixes bug #1658643: AGI: SQ1 (2.2 DOS ENG) Graphic error, ego leaves behind copy - // Update: Apparently, this makes ego dissapear at times, e.g. when textboxes are shown - // Therefore, it's limited to view 118 in SQ1 (Roger climbing the ladder) - // Fixes bug #1715284: Roger sometimes disappears - if (v->viewData != NULL) { - if (v->currentView == 118 && v->flags & DRAWN && getGameID() == GID_SQ1) { - viewFlags = v->flags; // Store the flags for the view - _sprites->eraseUpdSprites(); - - if (v->flags & UPDATE) { - v->flags &= ~DRAWN; - } else { - _sprites->eraseNonupdSprites(); - v->flags &= ~DRAWN; - _sprites->blitNonupdSprites(); - } - _sprites->blitUpdSprites(); - - _sprites->commitBlock(v->xPos, v->yPos - v->ySize + 1, v->xPos + v->xSize - 1, v->yPos); - v->flags = viewFlags; // Restore the view's flags - } - } - v->viewData = &_game.views[n]; v->currentView = n; v->numLoops = v->viewData->numLoops; + v->viewReplaced = true; setLoop(v, v->currentLoop >= v->numLoops ? 0 : v->currentLoop); } @@ -338,6 +310,7 @@ void AgiEngine::startUpdate(VtEntry *v) { v->flags |= UPDATE; _sprites->blitBoth(); + _sprites->commitBoth(); } } @@ -351,6 +324,7 @@ void AgiEngine::stopUpdate(VtEntry *v) { v->flags &= ~UPDATE; _sprites->blitBoth(); + _sprites->commitBoth(); } } @@ -393,7 +367,7 @@ void AgiEngine::updateViewtable() { break; default: // for KQ4 - if (getVersion() == 0x3086) + if (getVersion() == 0x3086 || getGameID() == GID_KQ4) loop = loopTable4[v->direction]; break; } diff --git a/engines/agi/view.h b/engines/agi/view.h index f9017ec4ae..85f2d6eaf9 100644 --- a/engines/agi/view.h +++ b/engines/agi/view.h @@ -63,6 +63,7 @@ struct VtEntry { int16 xPos; int16 yPos; uint8 currentView; + bool viewReplaced; struct AgiView *viewData; uint8 currentLoop; uint8 numLoops; diff --git a/engines/agi/wagparser.cpp b/engines/agi/wagparser.cpp index 1d60524070..22de66712d 100644 --- a/engines/agi/wagparser.cpp +++ b/engines/agi/wagparser.cpp @@ -100,10 +100,8 @@ void WagProperty::setDefaults() { } void WagProperty::deleteData() { - if (_propData != NULL) { - delete _propData; - _propData = NULL; - } + delete _propData; + _propData = NULL; } WagFileParser::WagFileParser() : diff --git a/engines/agi/words.cpp b/engines/agi/words.cpp index c8b22956f4..464c218ae8 100644 --- a/engines/agi/words.cpp +++ b/engines/agi/words.cpp @@ -76,10 +76,8 @@ int AgiEngine::loadWords(const char *fname) { } void AgiEngine::unloadWords() { - if (words != NULL) { - free(words); - words = NULL; - } + free(words); + words = NULL; } /** |