Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/dosbox-staging/dosbox-staging.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'src/hardware/reelmagic/driver.cpp')
-rw-r--r--src/hardware/reelmagic/driver.cpp1036
1 files changed, 1036 insertions, 0 deletions
diff --git a/src/hardware/reelmagic/driver.cpp b/src/hardware/reelmagic/driver.cpp
new file mode 100644
index 000000000..369580c15
--- /dev/null
+++ b/src/hardware/reelmagic/driver.cpp
@@ -0,0 +1,1036 @@
+/*
+ * Copyright (C) 2022 Jon Dennis
+ *
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+//
+// This file contains the reelmagic driver and device emulation code...
+//
+// This is where all the interaction with the "DOS world" occurs and is the
+// implementation of the provided driver API notes (in docs/RealMagic).
+//
+
+#include "reelmagic.h"
+
+#include <cerrno>
+#include <cstdarg>
+#include <cstdio>
+#include <cstring>
+#include <exception>
+#include <stack>
+#include <string>
+
+#include "callback.h"
+#include "dos_inc.h"
+#include "dos_system.h"
+#include "math_utils.h"
+#include "mixer.h"
+#include "programs.h"
+#include "regs.h"
+#include "setup.h"
+
+//note: Reported ReelMagic driver version 2.21 seems to be the most common...
+static const uint8_t REELMAGIC_DRIVER_VERSION_MAJOR = 2;
+static const uint8_t REELMAGIC_DRIVER_VERSION_MINOR = 21;
+[[maybe_unused]]
+static const uint16_t REELMAGIC_BASE_IO_PORT = 0x9800; //note: the real deal usually sits at 260h... practically unused for now; XXX should this be configurable!?
+static const uint8_t REELMAGIC_IRQ = 11; //practically unused for now; XXX should this be configurable!?
+static const char REELMAGIC_FMPDRV_EXE_LOCATION[] = "Z:\\"; //the trailing \ is super important!
+
+static Bitu _dosboxCallbackNumber = 0;
+static uint8_t _installedInterruptNumber = 0; //0 means not currently installed
+static bool _unloadAllowed = true;
+
+
+//enable full API logging only when heavy debugging is on...
+#if C_HEAVY_DEBUG
+ static bool _a204debug = true;
+ static bool _a206debug = true;
+ static inline bool IsDebugLogMessageFiltered(const uint8_t command, const uint16_t subfunc) {
+ if (command != 0x0A) return false;
+ if ((subfunc == 0x204) && (!_a204debug)) return true;
+ if ((subfunc == 0x206) && (!_a206debug)) return true;
+ return false;
+ }
+ #define APILOG LOG
+ #define APILOG_DCFILT(COMMAND, SUBFUNC, ...) \
+ if (!IsDebugLogMessageFiltered(COMMAND, SUBFUNC)) \
+ APILOG(LOG_REELMAGIC, LOG_NORMAL)(__VA_ARGS__)
+#else
+ static inline void APILOG_DONOTHING_FUNC(...) {}
+ #define APILOG(A,B) APILOG_DONOTHING_FUNC
+ static inline void APILOG_DCFILT(...) {}
+#endif
+
+//
+// driver -> user callback function stuff...
+//
+namespace {
+struct UserCallbackCall {
+ uint16_t command;
+ uint16_t handle;
+ uint16_t param1;
+ uint16_t param2;
+ bool invokeNext; //set to true if the next queued callback shall be auto-invoked when this one returns
+ //typedef (void* postcbcb_t) (void*);
+ //postcbcb_t postCallbackCallback; //set to non-null to invoke function after callback returns
+ //void * postCallbackCallbackUserPtr;
+ UserCallbackCall() : command(0), handle(0), param1(0), param2(0), invokeNext(false) {}
+ UserCallbackCall(const uint16_t command_, const uint16_t handle_ = 0, const uint16_t param1_ = 0, const uint16_t param2_ = 0, const bool invokeNext_ = false) :
+ command(command_), handle(handle_), param1(param1_), param2(param2_), invokeNext(invokeNext_) {}
+};
+struct UserCallbackPreservedState {
+ struct Segments segs;
+ struct CPU_Regs regs;
+ UserCallbackPreservedState() : segs(Segs), regs(cpu_regs) {}
+};
+}
+static std::stack<UserCallbackCall> _userCallbackStack;
+static std::stack<UserCallbackPreservedState> _preservedUserCallbackStates;
+static RealPt _userCallbackReturnIp = 0; //place to point the return address to after the user callback returns back to us
+static RealPt _userCallbackReturnDetectIp = 0; //used to detect if we are returning from the user registered FMPDRV.EXE callback
+static RealPt _userCallbackFarPtr = 0; // 0 = no callback registered
+static Bitu _userCallbackType = 0; //or rather calling convention
+
+
+namespace {struct RMException : ::std::exception { //XXX currently duplicating this in realmagic_*.cpp files to avoid header pollution... TDB if this is a good idea...
+ std::string _msg = {};
+ RMException(const char *fmt = "General ReelMagic Exception", ...) {
+ va_list vl;
+ va_start(vl, fmt); _msg.resize(vsnprintf(&_msg[0], 0, fmt, vl) + 1); va_end(vl);
+ va_start(vl, fmt); vsnprintf(&_msg[0], _msg.size(), fmt, vl); va_end(vl);
+ LOG(LOG_REELMAGIC, LOG_ERROR)("%s", _msg.c_str());
+ }
+ virtual ~RMException() throw() {}
+ virtual const char* what() const throw() {return _msg.c_str();}
+};}
+
+
+//
+// the file I/O implementations of the "ReelMagic Media Player" begins here...
+//
+namespace {
+ class ReelMagic_MediaPlayerDOSFile : public ReelMagic_MediaPlayerFile {
+ //this class gives the same "look and feel" to reelmagic programs...
+ //as far as I can tell, "FMPDRV.EXE" also opens requested files into the current PSP...
+ const std::string _fileName;
+ const uint16_t _pspEntry;
+ static uint16_t OpenDosFileEntry(const std::string& filename) {
+ uint16_t rv;
+ std::string dosfilepath = &filename[4]; //skip over the "DOS:" prefixed by the constructor
+ const size_t last_slash = dosfilepath.find_last_of('/');
+ if (last_slash != std::string::npos) dosfilepath = dosfilepath.substr(0, last_slash);
+ if (!DOS_OpenFile(dosfilepath.c_str(), OPEN_READ, &rv))
+ throw RMException("DOS File: Open for read failed: %s", filename.c_str());
+ return rv;
+ }
+ static std::string strcpyFromDos(const uint16_t seg, const uint16_t ptr, const bool firstByteIsLen) {
+ PhysPt dosptr = PhysMake(seg, ptr);
+ std::string rv; rv.resize(firstByteIsLen ? ((size_t)mem_readb(dosptr++)) : 256);
+ for (char *rv_ptr = &rv[0]; rv_ptr <= &rv[rv.size()-1]; ++rv_ptr) {
+ (*rv_ptr) = mem_readb(dosptr++);
+ if ((*rv_ptr) == '\0') {
+ rv.resize(rv_ptr - &rv[0]);
+ break;
+ }
+ }
+ return rv;
+ }
+ protected:
+ const char *GetFileName() const {return _fileName.c_str();}
+ uint32_t GetFileSize() const {
+ uint32_t currentPos = 0;
+ if (!DOS_SeekFile(_pspEntry, &currentPos, DOS_SEEK_CUR)) throw RMException("DOS File: Seek failed: Get current position");
+ uint32_t result = 0;
+ if (!DOS_SeekFile(_pspEntry, &result, DOS_SEEK_END)) throw RMException("DOS File: Seek failed: Get current position");
+ if (!DOS_SeekFile(_pspEntry, &currentPos, DOS_SEEK_SET)) throw RMException("DOS File: Seek failed: Reset current position");
+ return result;
+ }
+ uint32_t Read(uint8_t *data, uint32_t amount) {
+ uint32_t bytesRead = 0;
+ uint16_t transactionAmount;
+ while (amount > 0) {
+ transactionAmount = (amount > 0xFFFF) ? 0xFFFF : (uint16_t)amount;
+ if (!DOS_ReadFile(_pspEntry, data, &transactionAmount)) throw RMException("DOS File: Read failed");
+ if (transactionAmount == 0) break;
+ data += transactionAmount;
+ bytesRead += transactionAmount;
+ amount -= transactionAmount;
+ }
+ return bytesRead;
+ }
+ void Seek(uint32_t pos, uint32_t type) {
+ if (!DOS_SeekFile(_pspEntry, &pos, type)) throw RMException("DOS File: Seek failed.");
+ }
+ public:
+ ReelMagic_MediaPlayerDOSFile(const char * const dosFilepath) :
+ _fileName(std::string("DOS:")+dosFilepath),
+ _pspEntry(OpenDosFileEntry(dosFilepath)) {}
+ ReelMagic_MediaPlayerDOSFile(const uint16_t filenameStrSeg, const uint16_t filenameStrPtr, const bool firstByteIsLen = false) :
+ _fileName(std::string("DOS:") + strcpyFromDos(filenameStrSeg, filenameStrPtr, firstByteIsLen)),
+ _pspEntry(OpenDosFileEntry(_fileName)) {}
+ virtual ~ReelMagic_MediaPlayerDOSFile() { DOS_CloseFile(_pspEntry); }
+ };
+
+ class ReelMagic_MediaPlayerHostFile : public ReelMagic_MediaPlayerFile {
+ //this class is really only useful for debugging shit...
+ FILE * const _fp;
+ const std::string _fileName;
+ const uint32_t _fileSize;
+ static uint32_t GetFileSize(FILE * const fp) {
+ if (fseek(fp, 0, SEEK_END) == -1) throw RMException("Host File: fseek() failed: %s", strerror(errno));
+ const long tell_result = ftell(fp);
+ if (tell_result == -1) throw RMException("Host File: ftell() failed: %s", strerror(errno));
+ if (fseek(fp, 0, SEEK_SET) == -1) throw RMException("Host File: fseek() failed: %s", strerror(errno));
+ return (uint32_t)tell_result;
+ }
+ protected:
+ const char *GetFileName() const {return _fileName.c_str();}
+ uint32_t GetFileSize() const { return _fileSize; }
+ uint32_t Read(uint8_t *data, uint32_t amount) {
+ const size_t fread_result = fread(data, 1, amount, _fp);
+ if ((fread_result == 0) && ferror(_fp))
+ throw RMException("Host File: fread() failed: %s", strerror(errno));
+ return (uint32_t)fread_result;
+ }
+ void Seek(uint32_t pos, uint32_t type) {
+ if (fseek(_fp, (long)pos, (type == DOS_SEEK_SET) ? SEEK_SET : SEEK_CUR) == -1)
+ throw RMException("Host File: fseek() failed: %s", strerror(errno));
+ }
+ public:
+ ReelMagic_MediaPlayerHostFile(ReelMagic_MediaPlayerHostFile&) = delete;
+ ReelMagic_MediaPlayerHostFile& operator=(const ReelMagic_MediaPlayerHostFile&) = delete;
+
+ ReelMagic_MediaPlayerHostFile(const char * const hostFilepath) :
+ _fp(fopen(hostFilepath, "rb")), //not using fopen_wrap() as this class is really intended for debug...
+ _fileName(std::string("HOST:") + hostFilepath),
+ _fileSize(GetFileSize(_fp)) {
+ if (_fp == NULL) throw RMException("Host File: fopen(\"%s\")failed: %s", hostFilepath, strerror(errno));
+ }
+ virtual ~ReelMagic_MediaPlayerHostFile() { fclose(_fp); }
+ };
+}
+
+
+
+
+
+
+
+//
+// the implementation of "FMPDRV.EXE" begins here...
+//
+static uint8_t findFreeInt(void) {
+ //"FMPDRV.EXE" installs itself into a free IVT slot starting at 0x80...
+ for (uint8_t intNum = 0x80; intNum; ++intNum) {
+ if (RealGetVec(intNum) == 0) return intNum;
+ }
+ return 0x00; //failed
+}
+
+/*
+ ok so this is some shit...
+ detection of the reelmagic "FMPDRV.EXE" driver TSR presence works as follows:
+ for (int_num = 0x80; int_num < 0x100; ++int_num) {
+ ivt_func_t ivt_callback_ptr = cpu_global_ivt[int_num];
+ if (ivt_callback_ptr == NULL) continue;
+ const char * str = ivt_callback_ptr; //yup you read that correctly,
+ //we are casting a function pointer to a string...
+ if (strcmp(&str[3], "FMPDriver") == 0) {
+ return int_num; //we have found the FMPDriver at INT int_num
+ }
+*/
+static Bitu FMPDRV_INTHandler(void);
+static bool FMPDRV_InstallINTHandler()
+{
+ if (_installedInterruptNumber != 0)
+ return true; // already installed
+ _installedInterruptNumber = findFreeInt();
+ if (_installedInterruptNumber == 0) {
+ LOG(LOG_REELMAGIC, LOG_ERROR)("Unable to install INT handler due to no free IVT slots!");
+ return false; //hard to beleive this could actually happen... but need to account for...
+ }
+
+ //This is the contents of the "FMPDRV.EXE" INT handler which will be put in the ROM region:
+ const uint8_t isr_impl[] = { //Note: this is derrived from "case CB_IRET:" in "../cpu/callback.cpp"
+ 0xEB, 0x1A, // JMP over the check strings like a champ...
+ 9, //9 bytes for "FMPDriver" check string
+ 'F', 'M', 'P', 'D', 'r', 'i', 'v', 'e', 'r', '\0',
+ 13, //13 bytes for "ReelMagic(TM)" check string
+ 'R','e','e','l','M','a','g','i','c','(','T','M',')','\0',
+ 0xFE, 0x38, //GRP 4 + Extra Callback Instruction
+ (uint8_t)(_dosboxCallbackNumber), (uint8_t)(_dosboxCallbackNumber >> 8),
+ 0xCF, //IRET
+
+ //extra "unreachable" callback instruction used to signal end of FMPDRV.EXE registered callback
+ //when invoking the "user callback" from this driver
+ 0xFE, 0x38, //GRP 4 + Extra Callback Instruction
+ (uint8_t)(_dosboxCallbackNumber), (uint8_t)(_dosboxCallbackNumber >> 8)
+ };
+ //Note: checking against double CB_SIZE. This is because we allocate two callbacks to make this fit
+ // within the "callback ROM" region. See comment in ReelMagic_Init() function below
+ if (sizeof(isr_impl) > (CB_SIZE * 2)) E_Exit("CB_SIZE too small to fit ReelMagic driver IVT code. This means that DOSBox was not compiled correctly!");
+
+ CALLBACK_Setup(_dosboxCallbackNumber,
+ &FMPDRV_INTHandler,
+ CB_IRET,
+ "ReelMagic"); // must happen BEFORE we copy to ROM region!
+ for (PhysPt i = 0; i < static_cast<PhysPt>(sizeof(isr_impl)); ++i) // XXX is there an existing function for this?
+ phys_writeb(CALLBACK_PhysPointer(_dosboxCallbackNumber) + i, isr_impl[i]);
+
+ _userCallbackReturnDetectIp = CALLBACK_RealPointer(_dosboxCallbackNumber) + sizeof(isr_impl);
+ _userCallbackReturnIp = _userCallbackReturnDetectIp - 4;
+
+ RealSetVec(_installedInterruptNumber, CALLBACK_RealPointer(_dosboxCallbackNumber));
+ LOG(LOG_REELMAGIC, LOG_NORMAL)("Successfully installed FMPDRV.EXE at INT %xh", _installedInterruptNumber);
+ ReelMagic_SetVideoMixerEnabled(true);
+ return true; //success
+}
+
+static void FMPDRV_UninstallINTHandler()
+{
+ if (_installedInterruptNumber == 0)
+ return; // already uninstalled...
+ if (!_unloadAllowed)
+ return;
+ LOG(LOG_REELMAGIC, LOG_NORMAL)("Uninstalling FMPDRV.EXE from INT %xh", _installedInterruptNumber);
+ ReelMagic_SetVideoMixerEnabled(false);
+ RealSetVec(_installedInterruptNumber, 0);
+ _installedInterruptNumber = 0;
+ _userCallbackFarPtr = 0;
+}
+
+//
+// functions to serialize the player state into the required API format
+//
+static uint16_t GetFileStateValue(const ReelMagic_MediaPlayer& player) {
+ uint16_t value = 0;
+ if (player.HasVideo()) value |= 2;
+ if (player.HasAudio()) value |= 1;
+ return value;
+}
+
+static uint16_t GetPlayStateValue(const ReelMagic_MediaPlayer& player) {
+ //XXX status code 1 = paused
+ //XXX status code 2 = stopped (e.g. never started with function 3)
+ uint16_t value = player.IsPlaying() ? 0x4 : 0x1;
+ if ((_userCallbackType == 0x2000) && player.IsPlaying()) value |= 0x10; //XXX hack for RTZ!!!
+ return value;
+}
+
+static uint16_t GetPlayerSurfaceZOrderValue(const ReelMagic_PlayerConfiguration& cfg) {
+ if (!cfg.VideoOutputVisible) return 1;
+ if (cfg.UnderVga) return 4;
+ return 2;
+}
+
+//
+// This is to invoke the user program driver callback if registered...
+//
+static void EnqueueTopUserCallbackOnCPUResume() {
+ if (_userCallbackStack.empty()) E_Exit("FMPDRV.EXE Asking to enqueue a callback with nothing on the top of the callback stack!");
+ if (_userCallbackFarPtr == 0) E_Exit("FMPDRV.EXE Asking to enqueue a callback with no user callback pointer set!");
+ UserCallbackCall& ucc = _userCallbackStack.top();
+
+ //snapshot the current state...
+ _preservedUserCallbackStates.push(UserCallbackPreservedState());
+
+ //prepare the function call...
+ switch (_userCallbackType) { //AFAIK, _userCallbackType dictates the calling convention... this is the value that was passed in when registering the callback function to us
+ case 0x2000: //RTZ-style; shit is passed on the stack...
+ reg_ax = reg_bx = reg_cx = reg_dx = 0; //clear the GP regs for good measure...
+ mem_writew(PhysMake(SegValue(ss), reg_sp-=2), ucc.param2);
+ mem_writew(PhysMake(SegValue(ss), reg_sp-=2), ucc.param1);
+ mem_writew(PhysMake(SegValue(ss), reg_sp-=2), ucc.handle);
+ mem_writew(PhysMake(SegValue(ss), reg_sp-=2), ucc.command);
+ break;
+ default:
+ LOG(LOG_REELMAGIC, LOG_WARN)("Unknown user callback type %04Xh. Defaulting to 0000. This is probably gonna screw something up!", (unsigned)_userCallbackType);
+ //fall through
+ case 0x0000: //The Horde style... shit is passed in registers...
+ reg_bx = ((ucc.command << 8) & 0xFF00) | (ucc.handle & 0xFF);
+ reg_ax = ucc.param1;
+ reg_dx = ucc.param2;
+ reg_cx = 0; //???
+ break;
+ }
+
+ //push the far-call return address...
+ mem_writew(PhysMake(SegValue(ss), reg_sp-=2), RealSeg(_userCallbackReturnIp)); //return address to invoke CleanupFromUserCallback()
+ mem_writew(PhysMake(SegValue(ss), reg_sp-=2), RealOff(_userCallbackReturnIp)); //return address to invoke CleanupFromUserCallback()
+
+ //then we blast off into the wild blue...
+ SegSet16(cs, RealSeg(_userCallbackFarPtr));
+ reg_ip = RealOff(_userCallbackFarPtr);
+
+ APILOG(LOG_REELMAGIC, LOG_NORMAL)("Post-Invoking registered user-callback on CPU resume. cmd=%04Xh handle=%04Xh p1=%04Xh p2=%04Xh", (unsigned)ucc.command, (unsigned)ucc.handle, (unsigned)ucc.param1, (unsigned)ucc.param2);
+}
+
+static void InvokePlayerStateChangeCallbackOnCPUResumeIfRegistered(const bool isPausing, ReelMagic_MediaPlayer& player) {
+ if (_userCallbackFarPtr == 0) return; //no callback registered...
+
+ const auto cbstackStartSize = _userCallbackStack.size();
+ const ReelMagic_PlayerAttributes& attrs = player.GetAttrs();
+
+ if ((_userCallbackType == 0x2000) && (!isPausing)) {
+ //hack to make RTZ work for now...
+ _userCallbackStack.push(UserCallbackCall(5, attrs.Handles.Master, 0, 0, _userCallbackStack.size() != cbstackStartSize));
+ }
+
+ if (isPausing) {
+ //we are being invoked from a pause command
+ if (attrs.Handles.Demux) //is this the correct "last" handle !?
+ _userCallbackStack.push(UserCallbackCall(7, attrs.Handles.Demux, GetPlayStateValue(player), 0, _userCallbackStack.size() != cbstackStartSize));
+ if (attrs.Handles.Video) //is this the correct "middle" handle !?
+ _userCallbackStack.push(UserCallbackCall(7, attrs.Handles.Video, GetPlayStateValue(player), 0, _userCallbackStack.size() != cbstackStartSize));
+ if (attrs.Handles.Audio) //on the real deal, highest handle always calls back first! I'm assuming its audio!
+ _userCallbackStack.push(UserCallbackCall(7, attrs.Handles.Audio, GetPlayStateValue(player), 0, _userCallbackStack.size() != cbstackStartSize));
+ }
+ else {
+ //we are being invoked from a close command
+ if (player.IsPlaying() && attrs.Handles.Demux) //is this the correct "last" handle !?
+ _userCallbackStack.push(UserCallbackCall(7, attrs.Handles.Demux, GetPlayStateValue(player), 0, _userCallbackStack.size() != cbstackStartSize)); //4 = state of player; since we only get called on pause, this will always be 4
+ if (attrs.Handles.Audio)
+ _userCallbackStack.push(UserCallbackCall(7, attrs.Handles.Audio, GetPlayStateValue(player), 0, _userCallbackStack.size() != cbstackStartSize)); //4 = state of player; since we only get called on pause, this will always be 4
+ if (attrs.Handles.Video)
+ _userCallbackStack.push(UserCallbackCall(7, attrs.Handles.Video, GetPlayStateValue(player), 0, _userCallbackStack.size() != cbstackStartSize)); //4 = state of player; since we only get called on pause, this will always be 4
+ }
+
+ if (_userCallbackStack.size() != cbstackStartSize)
+ EnqueueTopUserCallbackOnCPUResume();
+}
+
+static void CleanupFromUserCallback(void) {
+ if (_preservedUserCallbackStates.empty()) E_Exit("FMPDRV.EXE Asking to cleanup with nothing on preservation stack");
+ if (_userCallbackStack.empty()) E_Exit("FMPDRV.EXE Asking to cleanup with nothing on user callback stack");
+ APILOG(LOG_REELMAGIC, LOG_NORMAL)("Returning from driver_callback()");
+
+ //restore the previous state of things...
+ const UserCallbackCall& ucc = _userCallbackStack.top();
+ const bool invokeNext = ucc.invokeNext;
+ _userCallbackStack.pop();
+
+ const UserCallbackPreservedState& s = _preservedUserCallbackStates.top();
+ Segs = s.segs;
+ cpu_regs = s.regs;
+ _preservedUserCallbackStates.pop();
+
+ if (invokeNext) {
+ APILOG(LOG_REELMAGIC, LOG_NORMAL)("Invoking Next Chained Callback...");
+ EnqueueTopUserCallbackOnCPUResume();
+ }
+}
+
+static uint32_t FMPDRV_driver_call(const uint8_t command, const uint8_t media_handle,
+ const uint16_t subfunc,
+ const uint16_t param1, const uint16_t param2)
+{
+ ReelMagic_MediaPlayer* player;
+ ReelMagic_PlayerConfiguration *cfg;
+ uint32_t rv;
+ switch(command) {
+ //
+ // Open Media Handle (File)
+ //
+ case 0x01:
+ if (media_handle != 0) LOG(LOG_REELMAGIC, LOG_WARN)("Non-zero media handle on open command");
+ if (((subfunc & 0xEFFF) != 1) && (subfunc != 2)) LOG(LOG_REELMAGIC, LOG_WARN)("subfunc not 1 or 2 on open command");
+ //if subfunc (or rather flags) has the 0x1000 bit set, then the first byte of the caller's
+ //pointer is the file path string length
+ rv = ReelMagic_NewPlayer(new ReelMagic_MediaPlayerDOSFile(param2, param1, (subfunc & 0x1000) != 0));
+
+ return rv;
+
+ //
+ // Close Media Handle
+ //
+ case 0x02:
+ player = &ReelMagic_HandleToMediaPlayer(media_handle); // will throw on bad handle
+ InvokePlayerStateChangeCallbackOnCPUResumeIfRegistered(false, *player);
+ ReelMagic_DeletePlayer(media_handle);
+ LOG(LOG_REELMAGIC, LOG_NORMAL)("Closed media player handle=%u", media_handle);
+ return 0;
+
+ //
+ // Play Media Handle
+ //
+ case 0x03:
+ player = &ReelMagic_HandleToMediaPlayer(media_handle); // will throw on bad handle
+ if (subfunc & 0xFFF0) LOG(LOG_REELMAGIC, LOG_WARN)("Ignoring upper 12-bits for play command subfunc: %04X", (unsigned)subfunc);
+ switch (subfunc & 0x000F) {
+ case 0x0000: //start playing -- Stop on completion
+ LOG(LOG_REELMAGIC, LOG_NORMAL)("Start playing handle #%u; stop on completion", (unsigned)media_handle);
+ player->Play(ReelMagic_MediaPlayer::MPPM_STOPONCOMPLETE);
+ return 0; //not sure if this means success or not... nobody seems to actually check this...
+ case 0x0001: //start playing -- Pause on completion
+ LOG(LOG_REELMAGIC, LOG_NORMAL)("Start playing handle #%u; pause on completion", (unsigned)media_handle);
+ player->Play(ReelMagic_MediaPlayer::MPPM_PAUSEONCOMPLETE);
+ return 0; //not sure if this means success or not... nobody seems to actually check this...
+ case 0x0004: //start playing in loop
+ LOG(LOG_REELMAGIC, LOG_NORMAL)("Start playing/looping handle #%u", (unsigned)media_handle);
+ player->Play(ReelMagic_MediaPlayer::MPPM_LOOP);
+ return 0; //not sure if this means success or not... nobody seems to actually check this...
+ default:
+ LOG(LOG_REELMAGIC, LOG_ERROR)("Got unknown play player command. Gonna start playing anyway and hope for the best. handle=%u command=%04Xh", (unsigned)media_handle, (unsigned)subfunc);
+ player->Play();
+ return 0; //not sure if this means success or not... nobody seems to actually check this...
+ }
+ return 0;
+
+ //
+ // Pause Media Handle
+ //
+ case 0x04:
+ player = &ReelMagic_HandleToMediaPlayer(media_handle); // will throw on bad handle
+ if (!player->IsPlaying()) return 0; //nothing to do
+ InvokePlayerStateChangeCallbackOnCPUResumeIfRegistered(true, *player);
+ player->Pause();
+ return 0; //returning zero, nobody seems to actually check this anyways...
+
+ //
+ // Unknown 5
+ //
+ case 0x05:
+ // player = &ReelMagic_HandleToMediaPlayer(media_handle); // currently unused - will throw on bad handle
+ LOG(LOG_REELMAGIC, LOG_WARN)("Ignoring unknown function 5. handle=%u subfunc=%04Xh", (unsigned)media_handle, (unsigned)subfunc);
+ return 0;
+
+ //
+ // Seek to Byte Offset
+ //
+ case 0x06:
+ player = &ReelMagic_HandleToMediaPlayer(media_handle); // will throw on bad handle
+ switch (subfunc) {
+ case 0x201: //not sure exactly what this means, but Crime Patrol is always setting this value
+ player->SeekToByteOffset(static_cast<uint32_t>((param2 << 16) | param1));
+ LOG(LOG_REELMAGIC, LOG_NORMAL)("Seeking player handle #%u to file offset %04X%04Xh", (unsigned)media_handle, (unsigned)param2, (unsigned)param1);
+ return 0;
+ default:
+ LOG(LOG_REELMAGIC, LOG_ERROR)("Got unknown seek subfunc. handle=%u subfunc=%04Xh", (unsigned)media_handle, (unsigned)subfunc);
+ return 0;
+ }
+ return 0;
+
+
+ //
+ // Unknown 7
+ //
+ case 0x07:
+ // player = &ReelMagic_HandleToMediaPlayer(media_handle); // currently unused - will throw on bad handle
+ LOG(LOG_REELMAGIC, LOG_WARN)("Ignoring unknown function 7. handle=%u subfunc=%04Xh", (unsigned)media_handle, (unsigned)subfunc);
+ return 0;
+
+ //
+ // Set Parameter
+ //
+ case 0x09:
+ if (media_handle == 0) { // global
+ cfg = &ReelMagic_GlobalDefaultPlayerConfig();
+ }
+ else { // per player...
+ player = &ReelMagic_HandleToMediaPlayer(media_handle); // will throw on bad handle
+ cfg = &player->Config();
+ }
+ switch (subfunc) {
+ case 0x0208: //user data
+ rv = cfg->UserData;
+ cfg->UserData = static_cast<uint32_t>((param2 << 16) | param1);
+ LOG(LOG_REELMAGIC, LOG_NORMAL)("Setting %s #%u User Data to %08X", (media_handle ? "Player" : "Global"), (unsigned)media_handle, cfg->UserData);
+ break;
+ case 0x0210: //magical decode key
+ rv = cfg->MagicDecodeKey;
+ cfg->MagicDecodeKey = static_cast<uint32_t>((param2 << 16) | param1);
+ LOG(LOG_REELMAGIC, LOG_NORMAL)("Setting %s #%u Magical Decode Key to %08X", (media_handle ? "Player" : "Global"), (unsigned)media_handle, cfg->MagicDecodeKey);
+ break;
+ case 0x040D: //VGA alpha palette index
+ rv = cfg->VgaAlphaIndex;
+ assert(param1 <= UINT8_MAX);
+ cfg->VgaAlphaIndex = static_cast<uint8_t>(param1);
+ LOG(LOG_REELMAGIC, LOG_NORMAL)("Setting %s #%u VGA Alpha Palette Index to %02Xh", (media_handle ? "Player" : "Global"), (unsigned)media_handle, (unsigned)cfg->VgaAlphaIndex);
+ break;
+ case 0x040E:
+ rv = GetPlayerSurfaceZOrderValue(*cfg);
+ cfg->VideoOutputVisible = (param1 & 1) == 0;
+ cfg->UnderVga = (param1 & 4) != 0;
+ LOG(LOG_REELMAGIC, LOG_NORMAL)("Setting %s #%u Surface Z-Order To: %s %s VGA", (media_handle ? "Player" : "Global"), (unsigned)media_handle, cfg->VideoOutputVisible ? "Visible" : "Hidden", cfg->UnderVga ? "Under" : "Over");
+ break;
+ case 0x1409:
+ rv = 0;
+ assert(param1 <= UINT8_MAX);
+ cfg->DisplaySize.Width = static_cast<uint8_t>(param1);
+ cfg->DisplaySize.Height = param2;
+ LOG(LOG_REELMAGIC, LOG_NORMAL)("Setting %s #%u Display Size To: %ux%u", (media_handle ? "Player" : "Global"), (unsigned)media_handle, (unsigned)param1, (unsigned)param2);
+ break;
+ case 0x2408:
+ rv = 0;
+ cfg->DisplayPosition.X = param1;
+ cfg->DisplayPosition.Y = param2;
+ LOG(LOG_REELMAGIC, LOG_NORMAL)("Setting %s #%u Display Position To: %ux%u", (media_handle ? "Player" : "Global"), (unsigned)media_handle, (unsigned)param1, (unsigned)param2);
+ break;
+ default:
+ LOG(LOG_REELMAGIC, LOG_WARN)("FMPDRV.EXE Unimplemented 09h: handle=%u subfunc=%04hXh param1=%hu", (unsigned)media_handle, (unsigned short)subfunc, (unsigned short)param1);
+ return 0;
+ }
+ if (media_handle != 0) player->NotifyConfigChange();
+
+ return rv;
+
+ //
+ // Get Parameter or Status
+ //
+ case 0x0A:
+ if (media_handle == 0) { // global
+ cfg = &ReelMagic_GlobalDefaultPlayerConfig();
+ }
+ else { // per player...
+ player = &ReelMagic_HandleToMediaPlayer(media_handle); // will throw on bad handle
+ cfg = &player->Config();
+ switch (subfunc) {
+ case 0x202: //get file state (bitmap of what streams are available...)
+ return GetFileStateValue(*player);
+ case 0x204: //get play state
+ return GetPlayStateValue(*player);
+ case 0x206: //get bytes decoded
+ return static_cast<uint32_t>(player->GetBytesDecoded());
+ case 0x208: //user data
+ //return cfg->UserData;
+ return 0; //XXX WARNING: Not yet returning this as I fear the consequences will be dire unless DMA streaming is properly implemented!
+ case 0x403:
+ //XXX WARNING: FMPTEST.EXE thinks the display width is 720 instead of 640!
+ return static_cast<uint32_t>((player->GetAttrs().PictureSize.Height << 16) | player->GetAttrs().PictureSize.Width);
+ }
+ }
+ switch (subfunc) {
+ case 0x108: //memory available?
+ return 0x00000032; //XXX FMPTEST wants at least 0x32... WHY !?
+ case 0x0210: //magical key
+ return cfg->MagicDecodeKey;
+ case 0x040D: //VGA alpha palette index
+ return cfg->VgaAlphaIndex;
+ case 0x040E: //surface z-order
+ return GetPlayerSurfaceZOrderValue(*cfg);
+ }
+ LOG(LOG_REELMAGIC, LOG_ERROR)("Got unknown status query. Likely things are gonna fuck up here. handle=%u query_type=%04Xh", (unsigned)media_handle, (unsigned)subfunc);
+ return 0;
+
+
+ //
+ // Set The Driver -> User Application Callback Function
+ //
+ case 0x0b:
+ LOG(LOG_REELMAGIC, LOG_WARN)("Registering driver_callback() as [%04X:%04X]", (unsigned)param2, (unsigned)param1);
+ _userCallbackFarPtr = RealMake(param2, param1);
+ _userCallbackType = subfunc;
+ return 0;
+
+ //
+ // Unload FMPDRV.EXE
+ //
+ case 0x0d:
+ LOG(LOG_REELMAGIC, LOG_NORMAL)("Request to unload FMPDRV.EXE via INT handler.");
+ FMPDRV_UninstallINTHandler();
+ return 0;
+
+ //
+ // Reset
+ //
+ case 0x0e:
+ LOG(LOG_REELMAGIC, LOG_NORMAL)("Reset");
+ ReelMagic_ResetPlayers();
+ ReelMagic_ResetVideoMixer();
+ _userCallbackFarPtr = 0;
+ _userCallbackType = 0;
+ return 0;
+
+ //
+ // Unknown 0x10
+ //
+ case 0x10: // unsure what this is -- RTZ only if we dont respond to the INT 2F 981Eh call...
+ LOG(LOG_REELMAGIC, LOG_WARN)("FMPDRV.EXE Unsure 10h");
+ return 0;
+ }
+ E_Exit("Unknown command %xh caught in ReelMagic driver", command);
+ // throw RMException("Unknown API command %xh caught", command); (unreachable)
+}
+
+static Bitu FMPDRV_INTHandler()
+{
+ if (RealMake(SegValue(cs), reg_ip) == _userCallbackReturnDetectIp) {
+ //if we get here, this is not a driver call, but rather we are cleaning up
+ //and restoring state from us invoking the user callback...
+ CleanupFromUserCallback();
+ return CBRET_NONE;
+ }
+
+ //define what the registers mean up front...
+ const uint8_t command = reg_bh;
+ ReelMagic_MediaPlayer_Handle media_handle = reg_bl;
+ const uint16_t subfunc = reg_cx;
+ const uint16_t param1 = reg_ax; //filename_ptr for command 0x1 & hardcoded to 1 for command 9
+ const uint16_t param2 = reg_dx; //filename_seg for command 0x1
+ try {
+ //clear all regs by default on return...
+ reg_ax = 0; reg_bx = 0; reg_cx = 0; reg_dx = 0;
+
+ const uint32_t driver_call_rv =
+ FMPDRV_driver_call(command, media_handle, subfunc, param1, param2);
+ reg_ax = (uint16_t)(driver_call_rv & 0xFFFF); // low
+ reg_dx = (uint16_t)(driver_call_rv >> 16); // high
+ APILOG_DCFILT(command,
+ subfunc,
+ "driver_call(%02Xh,%02Xh,%Xh,%Xh,%Xh)=%Xh",
+ (unsigned)command,
+ (unsigned)media_handle,
+ (unsigned)subfunc,
+ (unsigned)param1,
+ (unsigned)param2,
+ (unsigned)driver_call_rv);
+ }
+ catch ([[maybe_unused]] std::exception& ex) {
+ LOG(LOG_REELMAGIC, LOG_WARN)("Zeroing out INT return registers due to exception in driver_call(%02Xh,%02Xh,%Xh,%Xh,%Xh)", (unsigned)command, (unsigned)media_handle, (unsigned)subfunc, (unsigned)param1, (unsigned)param2);
+ reg_ax = 0; reg_bx = 0; reg_cx = 0; reg_dx = 0;
+ }
+ return CBRET_NONE;
+}
+
+class FMPDRV final : public Program {
+ public:
+ FMPDRV()
+ {
+ AddMessages();
+ help_detail = {HELP_Filter::Common,
+ HELP_Category::Dosbox,
+ HELP_CmdType::Program,
+ "FMPDRV"};
+ }
+ void Run()
+ {
+ WriteOut(MSG_Get("PROGRAM_FMPDRV_TITLE"),
+ REELMAGIC_DRIVER_VERSION_MAJOR,
+ REELMAGIC_DRIVER_VERSION_MINOR);
+ std::string ignore;
+ if (cmd->FindStringBegin("/u", ignore)) {
+ //unload driver
+ if (_installedInterruptNumber == 0) {
+ WriteOut(MSG_Get("PROGRAM_FMPDRV_UNLOAD_FAILED_NOT_LOADED"));
+ return;
+ }
+ if (!_unloadAllowed) {
+ WriteOut(MSG_Get("PROGRAM_FMPDRV_UNLOAD_FAILED_BLOCKED"));
+ return;
+ }
+ FMPDRV_UninstallINTHandler();
+ WriteOut(MSG_Get("PROGRAM_FMPDRV_UNLOADED"));
+ } else {
+ // load driver
+ if (_installedInterruptNumber != 0) {
+ WriteOut(MSG_Get("PROGRAM_FMPDRV_LOAD_FAILED_ALREADY_LOADED"),
+ _installedInterruptNumber);
+ return;
+ }
+ if (!FMPDRV_InstallINTHandler()) {
+ WriteOut(MSG_Get("PROGRAM_FMPDRV_LOAD_FAILED_INT_CONFLICT"));
+ return;
+ }
+ WriteOut(MSG_Get("PROGRAM_FMPDRV_LOADED"), _installedInterruptNumber);
+ }
+ }
+
+ private:
+ void AddMessages()
+ {
+ MSG_Add("PROGRAM_FMPDRV_TITLE", "Full Motion Player Driver %hhu.%hhu\n");
+
+ MSG_Add("PROGRAM_FMPDRV_LOADED",
+ "Successfully loaded driver at interrupt %xh.\n");
+
+ MSG_Add("PROGRAM_FMPDRV_LOAD_FAILED_ALREADY_LOADED",
+ "Failed loading: already loaded at interrupt %xh.\n");
+
+ MSG_Add("PROGRAM_FMPDRV_LOAD_FAILED_INT_CONFLICT",
+ "Failed loading: No free interrupts!\n");
+
+ MSG_Add("PROGRAM_FMPDRV_UNLOADED", "Successfully unloaded driver.\n");
+
+ MSG_Add("PROGRAM_FMPDRV_UNLOAD_FAILED_NOT_LOADED",
+ "Failed unloading: driver was not loaded.\n");
+
+ MSG_Add("PROGRAM_FMPDRV_UNLOAD_FAILED_BLOCKED",
+ "Failed unloading: the driver was configured to stay resident.\n");
+ }
+};
+
+std::unique_ptr<Program> FMPDRV_ProgramCreate()
+{
+ return ProgramCreate<FMPDRV>();
+}
+
+//
+// the implementation of "RMDEV.SYS" begins here...
+//
+/*
+ AFAIK, the responsibility of the "RMDEV.SYS" file is to point
+ applications to where they can find the ReelMagic driver (RMDRV.EXE)
+ and configuration...
+
+ It also is the sound mixer control API to ReelMagic
+
+ This thing pretty much sits in the DOS multiplexer (INT 2Fh) and responds
+ only to AH = 98h things...
+*/
+static uint16_t GetMixerVolume(const char * const channelName, const bool right) {
+ const auto chan = MIXER_FindChannel(channelName);
+ if (chan == NULL) return 0;
+
+ const auto vol_gain = chan->GetVolumeScale();
+ const auto vol_percentage = gain_to_percentage(vol_gain[right ? 1 : 0]);
+ return check_cast<uint16_t>(iroundf(vol_percentage));
+}
+
+static void SetMixerVolume(const char* const channelName,
+ const uint16_t percentage, const bool right)
+{
+ auto chan = MIXER_FindChannel(channelName);
+ if (chan == NULL)
+ return;
+
+ AudioFrame vol_gain = chan->GetVolumeScale();
+ vol_gain[right ? 1 : 0] = percentage_to_gain(percentage);
+ chan->SetVolumeScale(vol_gain.left, vol_gain.right);
+}
+
+static bool RMDEV_SYS_int2fHandler() {
+ if ((reg_ax & 0xFF00) != 0x9800) return false;
+ APILOG(LOG_REELMAGIC, LOG_NORMAL)("RMDEV.SYS ax = 0x%04X bx = 0x%04X cx = 0x%04X dx = 0x%04X", (unsigned)reg_ax, (unsigned)reg_bx, (unsigned)reg_cx, (unsigned)reg_dx);
+ switch (reg_ax) {
+ case 0x9800:
+ switch (reg_bx) {
+ case 0x0000: //query driver magic number
+ reg_ax = 0x524D; //"RM" magic number
+ return true;
+ case 0x0001: //query driver version
+ //AH is major and AL is minor
+ reg_ax = (REELMAGIC_DRIVER_VERSION_MAJOR << 8) | REELMAGIC_DRIVER_VERSION_MINOR;
+ return true;
+ case 0x0002: //query port i/o base address -- stock "FMPDRV.EXE" only
+ assert(reg_ax == REELMAGIC_BASE_IO_PORT); // reg_ax already is assigned 0x9800
+ LOG(LOG_REELMAGIC, LOG_WARN)("RMDEV.SYS Telling whoever an invalid base port I/O address of %04Xh... This is unlikely to end well...", (unsigned)reg_ax);
+ return true;
+ case 0x0003: //UNKNOWN!!! REAL DEAL COMES BACK WITH 5...
+ reg_ax = 5;
+ return true;
+
+ case 0x0007: // query if PCM and CD audio channel is enabled ?
+ case 0x0004: // query if MPEG audio channel is enabled ?
+ reg_ax = 0x0001; //yes ?
+ return true;
+ case 0x0006: // query ReelMagic board IRQ
+ reg_ax = REELMAGIC_IRQ;
+ LOG(LOG_REELMAGIC, LOG_WARN)("RMDEV.SYS Telling whoever an invalid IRQ of %u... This is unlikely to end well", (unsigned)reg_ax);
+ return true;
+
+ case 0x0008: // query sound card port
+ reg_ax = 0x220;
+ return true;
+ case 0x0009: // query sound card IRQ
+ reg_ax = 7;
+ return true;
+ case 0x000A: // query sound card DMA channel
+ reg_ax = 1;
+ return true;
+
+ case 0x0010: //query MAIN left volume
+ case 0x0011: //query MAIN right volume
+ reg_ax = 100; //can't touch this
+ return true;
+ case 0x0012: //query MPEG left volume
+ reg_ax = GetMixerVolume(reelmagic_channel_name, false);
+ return true;
+ case 0x0013: //query MPEG right volume
+ reg_ax = GetMixerVolume(reelmagic_channel_name, true);
+ return true;
+ case 0x0014: //query SYNT left volume
+ reg_ax = GetMixerVolume("OPL", false);
+ return true;
+ case 0x0015: //query SYNT right volume
+ reg_ax = GetMixerVolume("OPL", true);
+ return true;
+ case 0x0016: //query PCM left volume
+ reg_ax = GetMixerVolume("SB", false);
+ return true;
+ case 0x0017: //query PCM right volume
+ reg_ax = GetMixerVolume("SB", true);
+ return true;
+ case 0x001C: //query CD left volume
+ reg_ax = GetMixerVolume("CDAUDIO", false);
+ return true;
+ case 0x001D: //query CD right volume
+ reg_ax = GetMixerVolume("CDAUDIO", true);
+ return true;
+ }
+ break;
+ case 0x9801:
+ switch (reg_bx) {
+ case 0x0010: //set MAIN left volume
+ LOG(LOG_REELMAGIC, LOG_ERROR)("RMDEV.SYS: Can't update MAIN Left Volume");
+ return true;
+ case 0x0011: //set MAIN right volume
+ LOG(LOG_REELMAGIC, LOG_ERROR)("RMDEV.SYS: Can't update MAIN Right Volume");
+ return true;
+ case 0x0012: //set MPEG left volume
+ SetMixerVolume(reelmagic_channel_name, reg_dx, false);
+ return true;
+ case 0x0013: //set MPEG right volume
+ SetMixerVolume(reelmagic_channel_name, reg_dx, true);
+ return true;
+ case 0x0014: //set SYNT left volume
+ SetMixerVolume("OPL", reg_dx, false);
+ return true;
+ case 0x0015: //set SYNT right volume
+ SetMixerVolume("OPL", reg_dx, true);
+ return true;
+ case 0x0016: //set PCM left volume
+ SetMixerVolume("SB", reg_dx, false);
+ return true;
+ case 0x0017: //set PCM right volume
+ SetMixerVolume("SB", reg_dx, true);
+ return true;
+ case 0x001C: //set CD left volume
+ SetMixerVolume("CDAUDIO", reg_dx, false);
+ return true;
+ case 0x001D: //set CD right volume
+ SetMixerVolume("CDAUDIO", reg_dx, true);
+ return true;
+
+ }
+ break;
+ case 0x9803: //output a '\' terminated path string to "FMPDRV.EXE" to DX:BX
+ //Note: observing the behavior of "FMPLOAD.COM", a "mov dx,ds" occurs right
+ // before the "INT 2fh" call... therfore, I am assuming the segment to
+ // output the string to is indeed DX and not DS...
+ reg_ax = 0;
+ MEM_BlockWrite(PhysMake(reg_dx, reg_bx), REELMAGIC_FMPDRV_EXE_LOCATION, sizeof(REELMAGIC_FMPDRV_EXE_LOCATION));
+ return true;
+ case 0x981E: //stock "FMPDRV.EXE" and "RTZ" does this... it might mean reset, but probably not
+ //Note: if this handler is commented out (ax=981E), then we get a lot of unhandled 10h from RTZ
+ ReelMagic_DeleteAllPlayers();
+ reg_ax = 0;
+ return true;
+ case 0x98FF: //this always seems to be invoked when an "FMPLOAD /u" happens... so some kind of cleanup i guess?
+ ReelMagic_DeleteAllPlayers();
+ reg_ax = 0x0; //clearing AX for good measure, although it does not appear anything is checked after this called
+ return true;
+ }
+ LOG(LOG_REELMAGIC, LOG_WARN)("RMDEV.SYS Caught a likely unhandled ReelMagic destined INT 2F!! ax = 0x%04X bx = 0x%04X cx = 0x%04X dx = 0x%04X", (unsigned)reg_ax, (unsigned)reg_bx, (unsigned)reg_cx, (unsigned)reg_dx);
+ return false;
+}
+
+
+
+
+// #include "inout.h"
+// static IO_ReadHandleObject _readHandler;
+// static IO_WriteHandleObject _writeHandler;
+// static Bitu read_rm(Bitu port, Bitu /*iolen*/) {
+// LOG(LOG_REELMAGIC, LOG_WARN)("Caught port I/O read @ addr %04X", (unsigned)port);
+// switch (port & 0xff) {
+// case 0x02:
+// return 'R'; //no idea what this does... choosing a value from the magic number... might as well be random
+// case 0x03:
+// return 'M'; //no idea what this does... choosing a value from the magic number... might as well be random
+// }
+// return 0;
+// }
+// static void write_rm(Bitu port, Bitu val, Bitu /*iolen*/) {
+// LOG(LOG_REELMAGIC, LOG_WARN)("Caught port I/O write @ addr %04X", (unsigned)port);
+// }
+static void reelmagic_destroy([[maybe_unused]] Section* sec)
+{
+ // unload the software driver
+ _unloadAllowed = true;
+ FMPDRV_UninstallINTHandler();
+
+ // un-register the interrupt handlers
+ DOS_DelMultiplexHandler(&RMDEV_SYS_int2fHandler);
+
+ // stop mixing VGA and MPEG signals; use pass-through mode
+ ReelMagic_SetVideoMixerEnabled(false);
+
+ // un-register the audio channel
+ ReelMagic_EnableAudioChannel(false);
+
+ // un-register the callbacks. The presence of a non-zero callback number indicates the card is currently active
+ if (_dosboxCallbackNumber != 0) {
+ CALLBACK_DeAllocate(_dosboxCallbackNumber + 1);
+ CALLBACK_DeAllocate(_dosboxCallbackNumber);
+ _dosboxCallbackNumber = 0;
+ LOG_MSG("REELMAGIC: Shutting down ReelMagic MPEG playback card");
+ }
+}
+
+void ReelMagic_Init(Section* sec)
+{
+ assert(sec);
+ const auto section = static_cast<Section_prop*>(sec);
+
+ // Does the user want ReelMagic emulation?
+ const auto reelmagic_choice = std::string_view(section->Get_string("reelmagic"));
+ const auto wants_reelmagic = (reelmagic_choice == "on" || reelmagic_choice == "cardonly");
+
+ if (!wants_reelmagic) {
+ if (reelmagic_choice != "off") {
+ LOG_WARNING("REELMAGIC: Invalid 'reelmagic' value: '%s', shutting down.",
+ reelmagic_choice.data());
+ }
+ return;
+ }
+
+ // Player Initialization...
+ ReelMagic_InitPlayer(sec);
+
+ //Video Mixer Initialization...
+ ReelMagic_InitVideoMixer(sec);
+
+ //Driver/Hardware Initialization...
+ if (_dosboxCallbackNumber == 0) {
+ _dosboxCallbackNumber = CALLBACK_Allocate();
+ [[maybe_unused]] const auto second_callback = CALLBACK_Allocate();
+ assert(second_callback == _dosboxCallbackNumber + 1);
+ // this is so damn hacky! basically the code that the IVT points to for
+ // this driver needs more than 32-bytes of code to fit the check strings
+ // therefore, we are allocating two adjacent callbacks... seems kinda
+ // wasteful... need to explore a better way of doing this...
+ }
+ DOS_AddMultiplexHandler(&RMDEV_SYS_int2fHandler);
+ LOG(LOG_REELMAGIC, LOG_NORMAL)("\"RMDEV.SYS\" and \"Z:\\FMPDRV.EXE\" successfully installed");
+
+ //_readHandler.Install(REELMAGIC_BASE_IO_PORT, &read_rm, IO_MB, 0x3);
+ //_writeHandler.Install(REELMAGIC_BASE_IO_PORT, &write_rm, IO_MB, 0x3);
+
+ // User wants the hardware and the driver
+ if (reelmagic_choice == "on") {
+ _unloadAllowed = false;
+ FMPDRV_InstallINTHandler();
+ }
+
+#if C_HEAVY_DEBUG
+ _a204debug = true;
+ _a206debug = true;
+#endif
+
+ LOG_MSG("REELMAGIC: Initialized ReelMagic MPEG playback card");
+ sec->AddDestroyFunction(&reelmagic_destroy, true);
+}