Merge pull request #1854 from JosJuice/read-disc-after-delay

DVDInterface: Read disc after delay, not before
This commit is contained in:
Pierre Bourdon 2015-02-18 20:35:07 +00:00
commit 8cc6e5cff9
9 changed files with 236 additions and 88 deletions

View File

@ -234,8 +234,14 @@ void ScheduleEvent_Threadsafe(int cyclesIntoFuture, int event_type, u64 userdata
tsQueue.Push(ne);
}
// Executes an event immediately, then returns.
void ScheduleEvent_Immediate(int event_type, u64 userdata)
{
event_types[event_type].callback(userdata, 0);
}
// Same as ScheduleEvent_Threadsafe(0, ...) EXCEPT if we are already on the CPU thread
// in which case the event will get handled immediately, before returning.
// in which case this is the same as ScheduleEvent_Immediate.
void ScheduleEvent_Threadsafe_Immediate(int event_type, u64 userdata)
{
if (Core::IsCPUThread())

View File

@ -43,11 +43,11 @@ void DoState(PointerWrap &p);
int RegisterEvent(const std::string& name, TimedCallback callback);
void UnregisterAllEvents();
// userdata MAY NOT CONTAIN POINTERS. userdata might get written and reloaded from disk,
// when we implement state saves.
void ScheduleEvent(int cyclesIntoFuture, int event_type, u64 userdata=0);
void ScheduleEvent_Threadsafe(int cyclesIntoFuture, int event_type, u64 userdata=0);
void ScheduleEvent_Threadsafe_Immediate(int event_type, u64 userdata=0);
// userdata MAY NOT CONTAIN POINTERS. userdata might get written and reloaded from savestates.
void ScheduleEvent(int cyclesIntoFuture, int event_type, u64 userdata = 0);
void ScheduleEvent_Immediate(int event_type, u64 userdata = 0);
void ScheduleEvent_Threadsafe(int cyclesIntoFuture, int event_type, u64 userdata = 0);
void ScheduleEvent_Threadsafe_Immediate(int event_type, u64 userdata = 0);
// We only permit one event of each type in the queue at a time.
void RemoveEvent(int event_type);

View File

@ -213,6 +213,22 @@ union UDICFG
UDICFG(u32 _hex) {Hex = _hex;}
};
struct DVDReadCommand
{
bool is_valid;
u64 DVD_offset;
u32 output_address;
u32 length;
bool decrypt;
DIInterruptType interrupt_type;
// Used to notify emulated software after executing command.
// Pointers don't work with savestates, so CoreTiming events are used instead
int callback_event_type;
};
// STATE_TO_SAVE
// hardware registers
@ -225,6 +241,8 @@ static UDICR m_DICR;
static UDIIMMBUF m_DIIMMBUF;
static UDICFG m_DICFG;
static DVDReadCommand current_read_command;
static u32 AudioPos;
static u32 CurrentStart;
static u32 CurrentLength;
@ -236,7 +254,8 @@ static u32 g_ErrorCode = 0;
static bool g_bDiscInside = false;
bool g_bStream = false;
static bool g_bStopAtTrackEnd = false;
static int tc = 0;
static int finish_execute_command = 0;
static int finish_execute_read_command = 0;
static int dtk = 0;
static u64 g_last_read_offset;
@ -257,8 +276,8 @@ void UpdateInterrupts();
void GenerateDIInterrupt(DIInterruptType _DVDInterrupt);
void WriteImmediate(u32 value, u32 output_address, bool write_to_DIIMMBUF);
DVDCommandResult ExecuteReadCommand(u64 DVD_offset, u32 output_address,
u32 DVD_length, u32 output_length, bool decrypt);
DVDReadCommand ExecuteReadCommand(u64 DVD_offset, u32 output_address, u32 DVD_length, u32 output_length,
bool decrypt, DIInterruptType* interrupt_type, u64* ticks_until_completion);
u64 SimulateDiscReadTime(u64 offset, u32 length);
s64 CalculateRawDiscReadTime(u64 offset, s64 length);
@ -274,6 +293,8 @@ void DoState(PointerWrap &p)
p.Do(m_DIIMMBUF);
p.DoPOD(m_DICFG);
p.Do(current_read_command);
p.Do(NextStart);
p.Do(AudioPos);
p.Do(NextLength);
@ -291,7 +312,7 @@ void DoState(PointerWrap &p)
p.Do(g_bStopAtTrackEnd);
}
static void TransferComplete(u64 userdata, int cyclesLate)
static void FinishExecuteCommand(u64 userdata, int cyclesLate)
{
if (m_DICR.TSTART)
{
@ -301,6 +322,30 @@ static void TransferComplete(u64 userdata, int cyclesLate)
}
}
static void FinishExecuteReadCommand(u64 userdata, int cyclesLate)
{
if (!current_read_command.is_valid)
{
PanicAlertT("DVDInterface tried to execute non-existing command");
}
else
{
// Here is the actual disc reading
if (!DVDRead(current_read_command.DVD_offset, current_read_command.output_address,
current_read_command.length, current_read_command.decrypt))
{
PanicAlertT("Can't read from DVD_Plugin - DVD-Interface: Fatal Error");
}
}
// The command is marked as invalid because it shouldn't be used again
current_read_command.is_valid = false;
// The final step is to notify the emulated software that the command has been executed
CoreTiming::ScheduleEvent_Immediate(current_read_command.callback_event_type,
current_read_command.interrupt_type);
}
static u32 ProcessDTKSamples(short *tempPCM, u32 num_samples)
{
u32 samples_processed = 0;
@ -376,6 +421,8 @@ void Init()
m_DICFG.Hex = 0;
m_DICFG.CONFIG = 1; // Disable bootrom descrambler
current_read_command.is_valid = false;
AudioPos = 0;
NextStart = 0;
NextLength = 0;
@ -393,7 +440,8 @@ void Init()
ejectDisc = CoreTiming::RegisterEvent("EjectDisc", EjectDiscCallback);
insertDisc = CoreTiming::RegisterEvent("InsertDisc", InsertDiscCallback);
tc = CoreTiming::RegisterEvent("TransferComplete", TransferComplete);
finish_execute_command = CoreTiming::RegisterEvent("FinishExecuteCommand", FinishExecuteCommand);
finish_execute_read_command = CoreTiming::RegisterEvent("FinishExecuteReadCommand", FinishExecuteReadCommand);
dtk = CoreTiming::RegisterEvent("StreamingTimer", DTKStreamingCallback);
CoreTiming::ScheduleEvent(0, dtk);
@ -542,21 +590,8 @@ void RegisterMMIO(MMIO::Mapping* mmio, u32 base)
m_DICR.Hex = val & 7;
if (m_DICR.TSTART)
{
DVDCommandResult result = ExecuteCommand(
m_DICMDBUF[0].Hex, m_DICMDBUF[1].Hex, m_DICMDBUF[2].Hex,
m_DIMAR.Hex, m_DILENGTH.Hex, true);
if (SConfig::GetInstance().m_LocalCoreStartupParameter.bFastDiscSpeed)
{
// Make sure fast disc speed performs "instant" reads; in addition
// to being used to speed up games, fast disc speed is used as a
// workaround for crashes in Star Wars Rogue Leader.
TransferComplete(result.interrupt_type, 0);
}
else
{
// The transfer is finished after a delay
CoreTiming::ScheduleEvent((int)result.ticks_until_completion, tc, result.interrupt_type);
}
ExecuteCommand(m_DICMDBUF[0].Hex, m_DICMDBUF[1].Hex, m_DICMDBUF[2].Hex,
m_DIMAR.Hex, m_DILENGTH.Hex, true, finish_execute_command);
}
})
);
@ -612,38 +647,51 @@ void WriteImmediate(u32 value, u32 output_address, bool write_to_DIIMMBUF)
Memory::Write_U32(value, output_address);
}
DVDCommandResult ExecuteReadCommand(u64 DVD_offset, u32 output_address,
u32 DVD_length, u32 output_length, bool decrypt)
// If the returned DVDReadCommand has is_valid set to true,
// FinishExecuteReadCommand must be used to finish executing it
DVDReadCommand ExecuteReadCommand(u64 DVD_offset, u32 output_address, u32 DVD_length, u32 output_length,
bool decrypt, DIInterruptType* interrupt_type, u64* ticks_until_completion)
{
DVDReadCommand command;
if (!g_bDiscInside)
{
g_ErrorCode = ERROR_NO_DISK | ERROR_COVER_H;
*interrupt_type = INT_DEINT;
command.is_valid = false;
return command;
}
if (DVD_length > output_length)
{
WARN_LOG(DVDINTERFACE, "Detected attempt to read more data from the DVD than fit inside the out buffer. Clamp.");
DVD_length = output_length;
}
DVDCommandResult result;
result.ticks_until_completion = SimulateDiscReadTime(DVD_offset, DVD_length);
if (SConfig::GetInstance().m_LocalCoreStartupParameter.bFastDiscSpeed)
*ticks_until_completion = 0; // An optional hack to speed up loading times
else
*ticks_until_completion = SimulateDiscReadTime(DVD_offset, DVD_length);
if (!g_bDiscInside)
{
g_ErrorCode = ERROR_NO_DISK | ERROR_COVER_H;
result.interrupt_type = INT_DEINT;
return result;
}
if (!DVDRead(DVD_offset, output_address, DVD_length, decrypt))
PanicAlertT("Can't read from DVD_Plugin - DVD-Interface: Fatal Error");
result.interrupt_type = INT_TCINT;
return result;
*interrupt_type = INT_TCINT;
command.is_valid = true;
command.DVD_offset = DVD_offset;
command.output_address = output_address;
command.length = DVD_length;
command.decrypt = decrypt;
return command;
}
DVDCommandResult ExecuteCommand(u32 command_0, u32 command_1, u32 command_2,
u32 output_address, u32 output_length, bool write_to_DIIMMBUF)
// When the command has finished executing, callback_event_type
// will be called using CoreTiming::ScheduleEvent,
// with the userdata set to the interrupt type.
void ExecuteCommand(u32 command_0, u32 command_1, u32 command_2, u32 output_address, u32 output_length,
bool write_to_DIIMMBUF, int callback_event_type)
{
DVDCommandResult result;
result.interrupt_type = INT_TCINT;
result.ticks_until_completion = SystemTimers::GetTicksPerSecond() / 15000;
DIInterruptType interrupt_type = INT_TCINT;
u64 ticks_until_completion = SystemTimers::GetTicksPerSecond() / 15000;
DVDReadCommand read_command;
read_command.is_valid = false;
bool GCAM = (SConfig::GetInstance().m_SIDevice[0] == SIDEVICE_AM_BASEBOARD) &&
(SConfig::GetInstance().m_EXIDevice[2] == EXIDEVICE_AM_BASEBOARD);
@ -688,19 +736,21 @@ DVDCommandResult ExecuteCommand(u32 command_0, u32 command_1, u32 command_2,
// Only seems to be used from WII_IPC, not through direct access
case DVDLowReadDiskID:
INFO_LOG(DVDINTERFACE, "DVDLowReadDiskID");
result = ExecuteReadCommand(0, output_address, 0x20, output_length, false);
read_command = ExecuteReadCommand(0, output_address, 0x20, output_length,
false, &interrupt_type, &ticks_until_completion);
break;
// Only used from WII_IPC. This is the only read command that decrypts data
case DVDLowRead:
INFO_LOG(DVDINTERFACE, "DVDLowRead: DVDAddr: 0x%09" PRIx64 ", Size: 0x%x", (u64)command_2 << 2, command_1);
result = ExecuteReadCommand((u64)command_2 << 2, output_address, command_1, output_length, true);
read_command = ExecuteReadCommand((u64)command_2 << 2, output_address, command_1, output_length,
true, &interrupt_type, &ticks_until_completion);
break;
// Probably only used by Wii
case DVDLowWaitForCoverClose:
INFO_LOG(DVDINTERFACE, "DVDLowWaitForCoverClose");
result.interrupt_type = (DIInterruptType)4; // ???
interrupt_type = (DIInterruptType)4; // ???
break;
// "Set Extension"...not sure what it does. GC only?
@ -773,14 +823,15 @@ DVDCommandResult ExecuteCommand(u32 command_0, u32 command_1, u32 command_2,
(command_2 > 0x7ed40000 && command_2 < 0x7ed40008) ||
(((command_2 + command_1) > 0x7ed40000) && (command_2 + command_1) < 0x7ed40008)))
{
result = ExecuteReadCommand((u64)command_2 << 2, output_address, command_1, output_length, false);
read_command = ExecuteReadCommand((u64)command_2 << 2, output_address, command_1, output_length,
false, &interrupt_type, &ticks_until_completion);
}
else
{
WARN_LOG(DVDINTERFACE, "DVDLowUnencryptedRead: trying to read out of bounds @ %09" PRIx64, (u64)command_2 << 2);
g_ErrorCode = ERROR_READY | ERROR_BLOCK_OOB;
// Should cause software to call DVDLowRequestError
result.interrupt_type = INT_BRKINT;
interrupt_type = INT_BRKINT;
}
break;
@ -805,7 +856,7 @@ DVDCommandResult ExecuteCommand(u32 command_0, u32 command_1, u32 command_2,
// Does not work on retail discs/drives
// Retail games send this command to see if they are running on real retail hw
g_ErrorCode = ERROR_READY | ERROR_INV_CMD;
result.interrupt_type = INT_BRKINT;
interrupt_type = INT_BRKINT;
break;
// DMA Read from Disc. Only seems to be used through direct access, not WII_IPC
@ -869,13 +920,15 @@ DVDCommandResult ExecuteCommand(u32 command_0, u32 command_1, u32 command_2,
}
}
result = ExecuteReadCommand(iDVDOffset, output_address, command_2, output_length, false);
read_command = ExecuteReadCommand(iDVDOffset, output_address, command_2, output_length,
false, &interrupt_type, &ticks_until_completion);
}
break;
case 0x40: // Read DiscID
INFO_LOG(DVDINTERFACE, "Read DiscID %08x", Memory::Read_U32(output_address));
result = ExecuteReadCommand(0, output_address, 0x20, output_length, false);
read_command = ExecuteReadCommand(0, output_address, 0x20, output_length,
false, &interrupt_type, &ticks_until_completion);
break;
default:
@ -1210,7 +1263,23 @@ DVDCommandResult ExecuteCommand(u32 command_0, u32 command_1, u32 command_2,
break;
}
return result;
// The command will finish executing after a delay,
// to simulate the speed of a real disc drive
if (read_command.is_valid)
{
// We schedule a FinishExecuteReadCommand (which will call the actual callback
// once it's done) so that the data transfer isn't completed too early.
// Most games don't care about it, but if it's done wrong, Resident Evil 3
// plays some extra noise when playing the menu selection sound effect.
read_command.callback_event_type = callback_event_type;
read_command.interrupt_type = interrupt_type;
current_read_command = read_command;
CoreTiming::ScheduleEvent((int)ticks_until_completion, finish_execute_read_command);
}
else
{
CoreTiming::ScheduleEvent((int)ticks_until_completion, callback_event_type, interrupt_type);
}
}
// Simulates the timing aspects of reading data from a disc.

View File

@ -85,12 +85,6 @@ enum DIInterruptType
INT_CVRINT = 3,
};
struct DVDCommandResult
{
DIInterruptType interrupt_type;
u64 ticks_until_completion;
};
void Init();
void Shutdown();
void DoState(PointerWrap &p);
@ -105,7 +99,7 @@ void ChangeDisc(const std::string& fileName);
// DVD Access Functions
bool DVDRead(u64 _iDVDOffset, u32 _iRamAddress, u32 _iLength, bool decrypt);
extern bool g_bStream;
DVDCommandResult ExecuteCommand(u32 command_0, u32 command_1, u32 command_2,
u32 output_address, u32 output_length, bool write_to_DIIMMBUF);
void ExecuteCommand(u32 command_0, u32 command_1, u32 command_2, u32 output_address, u32 output_length,
bool write_to_DIIMMBUF, int callback_event_type);
} // end of namespace DVDInterface

View File

@ -85,7 +85,7 @@ static u64 last_reply_time;
static const u64 ENQUEUE_REQUEST_FLAG = 0x100000000ULL;
static const u64 ENQUEUE_ACKNOWLEDGEMENT_FLAG = 0x200000000ULL;
static void EnqueueEventCallback(u64 userdata, int)
static void EnqueueEvent(u64 userdata, int cycles_late = 0)
{
if (userdata & ENQUEUE_ACKNOWLEDGEMENT_FLAG)
{
@ -144,7 +144,7 @@ void Init()
g_DeviceMap[i] = new CWII_IPC_HLE_Device_stub(i, "/dev/usb/oh1"); i++;
g_DeviceMap[i] = new IWII_IPC_HLE_Device(i, "_Unimplemented_Device_"); i++;
event_enqueue = CoreTiming::RegisterEvent("IPCEvent", EnqueueEventCallback);
event_enqueue = CoreTiming::RegisterEvent("IPCEvent", EnqueueEvent);
}
void Reset(bool _bHard)
@ -563,6 +563,11 @@ void EnqueueReply_Threadsafe(u32 address, int cycles_in_future)
CoreTiming::ScheduleEvent_Threadsafe(cycles_in_future, event_enqueue, address);
}
void EnqueueReply_Immediate(u32 address)
{
EnqueueEvent(address);
}
void EnqueueCommandAcknowledgement(u32 address, int cycles_in_future)
{
CoreTiming::ScheduleEvent(cycles_in_future, event_enqueue,

View File

@ -80,6 +80,7 @@ void ExecuteCommand(u32 _Address);
void EnqueueRequest(u32 address);
void EnqueueReply(u32 address, int cycles_in_future = 0);
void EnqueueReply_Threadsafe(u32 address, int cycles_in_future = 0);
void EnqueueReply_Immediate(u32 address);
void EnqueueCommandAcknowledgement(u32 _Address, int cycles_in_future = 0);
} // end of namespace WII_IPC_HLE_Interface

View File

@ -9,6 +9,7 @@
#include "Common/Logging/LogManager.h"
#include "Core/ConfigManager.h"
#include "Core/CoreTiming.h"
#include "Core/VolumeHandler.h"
#include "Core/HW/DVDInterface.h"
#include "Core/HW/Memmap.h"
@ -17,14 +18,38 @@
#include "Core/IPC_HLE/WII_IPC_HLE.h"
#include "Core/IPC_HLE/WII_IPC_HLE_Device_DI.h"
using namespace DVDInterface;
static CWII_IPC_HLE_Device_di* g_di_pointer;
static int ioctl_callback;
CWII_IPC_HLE_Device_di::CWII_IPC_HLE_Device_di(u32 _DeviceID, const std::string& _rDeviceName )
static void IOCtlCallback(u64 userdata, int cycles_late)
{
if (g_di_pointer != nullptr)
g_di_pointer->FinishIOCtl((DVDInterface::DIInterruptType)userdata);
// If g_di_pointer == nullptr, IOS was probably shut down,
// so the command shouldn't be completed
}
CWII_IPC_HLE_Device_di::CWII_IPC_HLE_Device_di(u32 _DeviceID, const std::string& _rDeviceName)
: IWII_IPC_HLE_Device(_DeviceID, _rDeviceName)
{}
{
if (g_di_pointer == nullptr)
ERROR_LOG(WII_IPC_DVD, "Trying to run two DI devices at once. IOCtl may not behave as expected.");
g_di_pointer = this;
ioctl_callback = CoreTiming::RegisterEvent("IOCtlCallbackDI", IOCtlCallback);
}
CWII_IPC_HLE_Device_di::~CWII_IPC_HLE_Device_di()
{}
{
g_di_pointer = nullptr;
}
void CWII_IPC_HLE_Device_di::DoState(PointerWrap& p)
{
DoStateShared(p);
p.Do(m_commands_to_execute);
}
IPCCommandResult CWII_IPC_HLE_Device_di::Open(u32 _CommandAddress, u32 _Mode)
{
@ -43,10 +68,29 @@ IPCCommandResult CWII_IPC_HLE_Device_di::Close(u32 _CommandAddress, bool _bForce
IPCCommandResult CWII_IPC_HLE_Device_di::IOCtl(u32 _CommandAddress)
{
u32 BufferIn = Memory::Read_U32(_CommandAddress + 0x10);
u32 BufferInSize = Memory::Read_U32(_CommandAddress + 0x14);
u32 BufferOut = Memory::Read_U32(_CommandAddress + 0x18);
u32 BufferOutSize = Memory::Read_U32(_CommandAddress + 0x1C);
// DI IOCtls are handled in a special way by Dolphin
// compared to other WII_IPC_HLE functions.
// This is a wrapper around DVDInterface's ExecuteCommand,
// which will execute commands more or less asynchronously.
// Only one command can be executed at a time, so commands
// are queued until DVDInterface is ready to handle them.
bool ready_to_execute = m_commands_to_execute.empty();
m_commands_to_execute.push_back(_CommandAddress);
if (ready_to_execute)
StartIOCtl(_CommandAddress);
// DVDInterface handles the timing, and we handle the reply,
// so WII_IPC_HLE shouldn't do any of that.
return IPC_NO_REPLY;
}
void CWII_IPC_HLE_Device_di::StartIOCtl(u32 command_address)
{
u32 BufferIn = Memory::Read_U32(command_address + 0x10);
u32 BufferInSize = Memory::Read_U32(command_address + 0x14);
u32 BufferOut = Memory::Read_U32(command_address + 0x18);
u32 BufferOutSize = Memory::Read_U32(command_address + 0x1C);
u32 command_0 = Memory::Read_U32(BufferIn);
u32 command_1 = Memory::Read_U32(BufferIn + 4);
@ -63,14 +107,38 @@ IPCCommandResult CWII_IPC_HLE_Device_di::IOCtl(u32 _CommandAddress)
Memory::Memset(BufferOut, 0, BufferOutSize);
}
DVDCommandResult result = ExecuteCommand(command_0, command_1, command_2,
BufferOut, BufferOutSize, false);
Memory::Write_U32(result.interrupt_type, _CommandAddress + 0x4);
// DVDInterface's ExecuteCommand handles most of the work.
// The IOCtl callback is used to generate a reply afterwards.
DVDInterface::ExecuteCommand(command_0, command_1, command_2, BufferOut, BufferOutSize,
false, ioctl_callback);
}
if (SConfig::GetInstance().m_LocalCoreStartupParameter.bFastDiscSpeed)
result.ticks_until_completion = 0; // An optional hack to speed up loading times
void CWII_IPC_HLE_Device_di::FinishIOCtl(DVDInterface::DIInterruptType interrupt_type)
{
if (m_commands_to_execute.empty())
{
PanicAlertT("WII_IPC_HLE_Device_DI tried to reply to non-existing command");
return;
}
return { true, result.ticks_until_completion };
// This command has been executed, so it's removed from the queue
u32 command_address = m_commands_to_execute.front();
m_commands_to_execute.pop_front();
// The DI interrupt type is used as a return value
Memory::Write_U32(interrupt_type, command_address + 4);
// The original hardware overwrites the command type with the async reply type.
Memory::Write_U32(IPC_REP_ASYNC, command_address);
// IOS also seems to write back the command that was responded to in the FD field.
Memory::Write_U32(Memory::Read_U32(command_address), command_address + 8);
// Generate a reply to the IPC command
WII_IPC_HLE_Interface::EnqueueReply_Immediate(command_address);
// DVDInterface is now ready to execute another command,
// so we start executing a command from the queue if there is one
if (!m_commands_to_execute.empty())
StartIOCtl(m_commands_to_execute.front());
}
IPCCommandResult CWII_IPC_HLE_Device_di::IOCtlV(u32 _CommandAddress)
@ -88,7 +156,7 @@ IPCCommandResult CWII_IPC_HLE_Device_di::IOCtlV(u32 _CommandAddress)
u32 ReturnValue = 0;
switch (CommandBuffer.Parameter)
{
case DVDLowOpenPartition:
case DVDInterface::DVDLowOpenPartition:
{
_dbg_assert_msg_(WII_IPC_DVD, CommandBuffer.InBuffer[1].m_Address == 0, "DVDLowOpenPartition with ticket");
_dbg_assert_msg_(WII_IPC_DVD, CommandBuffer.InBuffer[2].m_Address == 0, "DVDLowOpenPartition with cert chain");

View File

@ -4,14 +4,10 @@
#pragma once
#include <deque>
#include "Core/HW/DVDInterface.h"
#include "Core/IPC_HLE/WII_IPC_HLE_Device.h"
namespace DiscIO
{
class IVolume;
class IFileSystem;
}
class CWII_IPC_HLE_Device_di : public IWII_IPC_HLE_Device
{
public:
@ -20,9 +16,18 @@ public:
virtual ~CWII_IPC_HLE_Device_di();
void DoState(PointerWrap& p) override;
IPCCommandResult Open(u32 _CommandAddress, u32 _Mode) override;
IPCCommandResult Close(u32 _CommandAddress, bool _bForce) override;
IPCCommandResult IOCtl(u32 _CommandAddress) override;
IPCCommandResult IOCtlV(u32 _CommandAddress) override;
void FinishIOCtl(DVDInterface::DIInterruptType interrupt_type);
private:
void StartIOCtl(u32 command_address);
std::deque<u32> m_commands_to_execute;
};

View File

@ -64,7 +64,7 @@ static Common::Event g_compressAndDumpStateSyncEvent;
static std::thread g_save_thread;
// Don't forget to increase this after doing changes on the savestate system
static const u32 STATE_VERSION = 39;
static const u32 STATE_VERSION = 40;
enum
{