commit
7876430a71
|
@ -218,7 +218,8 @@ union UDICFG {
|
||||||
static std::unique_ptr<DiscIO::IVolume> s_inserted_volume;
|
static std::unique_ptr<DiscIO::IVolume> s_inserted_volume;
|
||||||
|
|
||||||
// STATE_TO_SAVE
|
// STATE_TO_SAVE
|
||||||
// hardware registers
|
|
||||||
|
// Hardware registers
|
||||||
static UDISR s_DISR;
|
static UDISR s_DISR;
|
||||||
static UDICVR s_DICVR;
|
static UDICVR s_DICVR;
|
||||||
static UDICMDBUF s_DICMDBUF[3];
|
static UDICMDBUF s_DICMDBUF[3];
|
||||||
|
@ -228,24 +229,29 @@ static UDICR s_DICR;
|
||||||
static UDIIMMBUF s_DIIMMBUF;
|
static UDIIMMBUF s_DIIMMBUF;
|
||||||
static UDICFG s_DICFG;
|
static UDICFG s_DICFG;
|
||||||
|
|
||||||
static u32 s_audio_position;
|
// DTK
|
||||||
static u32 s_current_start;
|
|
||||||
static u32 s_current_length;
|
|
||||||
static u32 s_next_start;
|
|
||||||
static u32 s_next_length;
|
|
||||||
|
|
||||||
static u32 s_error_code = 0;
|
|
||||||
static bool s_disc_inside = false;
|
|
||||||
static bool s_stream = false;
|
static bool s_stream = false;
|
||||||
static bool s_stop_at_track_end = false;
|
static bool s_stop_at_track_end = false;
|
||||||
static CoreTiming::EventType* s_finish_executing_command;
|
static u64 s_audio_position;
|
||||||
static CoreTiming::EventType* s_dtk;
|
static u64 s_current_start;
|
||||||
|
static u32 s_current_length;
|
||||||
|
static u64 s_next_start;
|
||||||
|
static u32 s_next_length;
|
||||||
|
static u32 s_pending_samples;
|
||||||
|
|
||||||
|
// Disc drive state
|
||||||
|
static u32 s_error_code = 0;
|
||||||
|
static bool s_disc_inside = false;
|
||||||
|
|
||||||
|
// Disc drive timing
|
||||||
static u64 s_last_read_offset;
|
static u64 s_last_read_offset;
|
||||||
static u64 s_last_read_time;
|
static u64 s_last_read_time;
|
||||||
|
|
||||||
|
// Disc changing
|
||||||
static std::string s_disc_path_to_insert;
|
static std::string s_disc_path_to_insert;
|
||||||
|
|
||||||
|
// Events
|
||||||
|
static CoreTiming::EventType* s_finish_executing_command;
|
||||||
static CoreTiming::EventType* s_eject_disc;
|
static CoreTiming::EventType* s_eject_disc;
|
||||||
static CoreTiming::EventType* s_insert_disc;
|
static CoreTiming::EventType* s_insert_disc;
|
||||||
|
|
||||||
|
@ -260,8 +266,9 @@ void GenerateDIInterrupt(DIInterruptType _DVDInterrupt);
|
||||||
|
|
||||||
void WriteImmediate(u32 value, u32 output_address, bool reply_to_ios);
|
void WriteImmediate(u32 value, u32 output_address, bool reply_to_ios);
|
||||||
bool ExecuteReadCommand(u64 DVD_offset, u32 output_address, u32 DVD_length, u32 output_length,
|
bool ExecuteReadCommand(u64 DVD_offset, u32 output_address, u32 DVD_length, u32 output_length,
|
||||||
bool decrypt, bool reply_to_ios, DIInterruptType* interrupt_type,
|
bool decrypt, ReplyType reply_type, DIInterruptType* interrupt_type);
|
||||||
u64* ticks_until_completion);
|
|
||||||
|
u64 PackFinishExecutingCommandUserdata(ReplyType reply_type, DIInterruptType interrupt_type);
|
||||||
|
|
||||||
u64 SimulateDiscReadTime(u64 offset, u32 length);
|
u64 SimulateDiscReadTime(u64 offset, u32 length);
|
||||||
s64 CalculateRawDiscReadTime(u64 offset, s64 length);
|
s64 CalculateRawDiscReadTime(u64 offset, s64 length);
|
||||||
|
@ -277,24 +284,23 @@ void DoState(PointerWrap& p)
|
||||||
p.Do(s_DIIMMBUF);
|
p.Do(s_DIIMMBUF);
|
||||||
p.DoPOD(s_DICFG);
|
p.DoPOD(s_DICFG);
|
||||||
|
|
||||||
p.Do(s_next_start);
|
p.Do(s_stream);
|
||||||
|
p.Do(s_stop_at_track_end);
|
||||||
p.Do(s_audio_position);
|
p.Do(s_audio_position);
|
||||||
|
p.Do(s_current_start);
|
||||||
|
p.Do(s_current_length);
|
||||||
|
p.Do(s_next_start);
|
||||||
p.Do(s_next_length);
|
p.Do(s_next_length);
|
||||||
|
p.Do(s_pending_samples);
|
||||||
|
|
||||||
p.Do(s_error_code);
|
p.Do(s_error_code);
|
||||||
p.Do(s_disc_inside);
|
p.Do(s_disc_inside);
|
||||||
p.Do(s_stream);
|
|
||||||
|
|
||||||
p.Do(s_current_start);
|
|
||||||
p.Do(s_current_length);
|
|
||||||
|
|
||||||
p.Do(s_last_read_offset);
|
p.Do(s_last_read_offset);
|
||||||
p.Do(s_last_read_time);
|
p.Do(s_last_read_time);
|
||||||
|
|
||||||
p.Do(s_disc_path_to_insert);
|
p.Do(s_disc_path_to_insert);
|
||||||
|
|
||||||
p.Do(s_stop_at_track_end);
|
|
||||||
|
|
||||||
DVDThread::DoState(p);
|
DVDThread::DoState(p);
|
||||||
|
|
||||||
// s_inserted_volume isn't savestated (because it points to
|
// s_inserted_volume isn't savestated (because it points to
|
||||||
|
@ -311,19 +317,35 @@ void DoState(PointerWrap& p)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static u32 ProcessDTKSamples(short* tempPCM, u32 num_samples)
|
static size_t ProcessDTKSamples(std::vector<s16>* temp_pcm, const std::vector<u8>& audio_data)
|
||||||
{
|
{
|
||||||
// TODO: Read audio data using the DVD thread instead of blocking on it?
|
size_t samples_processed = 0;
|
||||||
DVDThread::WaitUntilIdle();
|
size_t bytes_processed = 0;
|
||||||
|
while (samples_processed < temp_pcm->size() / 2 && bytes_processed < audio_data.size())
|
||||||
|
{
|
||||||
|
StreamADPCM::DecodeBlock(&(*temp_pcm)[samples_processed * 2], &audio_data[bytes_processed]);
|
||||||
|
for (size_t i = 0; i < StreamADPCM::SAMPLES_PER_BLOCK * 2; ++i)
|
||||||
|
{
|
||||||
|
// TODO: Fix the mixer so it can accept non-byte-swapped samples.
|
||||||
|
s16* sample = &(*temp_pcm)[samples_processed * 2 + i];
|
||||||
|
*sample = Common::swap16(*sample);
|
||||||
|
}
|
||||||
|
samples_processed += StreamADPCM::SAMPLES_PER_BLOCK;
|
||||||
|
bytes_processed += StreamADPCM::ONE_BLOCK_SIZE;
|
||||||
|
}
|
||||||
|
return samples_processed;
|
||||||
|
}
|
||||||
|
|
||||||
u32 samples_processed = 0;
|
static u32 AdvanceDTK(u32 maximum_samples, u32* samples_to_process)
|
||||||
do
|
{
|
||||||
|
u32 bytes_to_process = 0;
|
||||||
|
*samples_to_process = 0;
|
||||||
|
while (*samples_to_process < maximum_samples)
|
||||||
{
|
{
|
||||||
if (s_audio_position >= s_current_start + s_current_length)
|
if (s_audio_position >= s_current_start + s_current_length)
|
||||||
{
|
{
|
||||||
DEBUG_LOG(DVDINTERFACE, "ProcessDTKSamples: "
|
DEBUG_LOG(DVDINTERFACE, "AdvanceDTK: NextStart=%08x, NextLength=%08x, "
|
||||||
"NextStart=%08x,NextLength=%08x,CurrentStart=%08x,CurrentLength=%08x,"
|
"CurrentStart=%08x, CurrentLength=%08x, AudioPos=%08x",
|
||||||
"AudioPos=%08x",
|
|
||||||
s_next_start, s_next_length, s_current_start, s_current_length, s_audio_position);
|
s_next_start, s_next_length, s_current_start, s_current_length, s_audio_position);
|
||||||
|
|
||||||
s_audio_position = s_next_start;
|
s_audio_position = s_next_start;
|
||||||
|
@ -340,40 +362,49 @@ static u32 ProcessDTKSamples(short* tempPCM, u32 num_samples)
|
||||||
StreamADPCM::InitFilter();
|
StreamADPCM::InitFilter();
|
||||||
}
|
}
|
||||||
|
|
||||||
u8 tempADPCM[StreamADPCM::ONE_BLOCK_SIZE];
|
s_audio_position += StreamADPCM::ONE_BLOCK_SIZE;
|
||||||
// TODO: What if we can't read from s_audio_position?
|
bytes_to_process += StreamADPCM::ONE_BLOCK_SIZE;
|
||||||
s_inserted_volume->Read(s_audio_position, sizeof(tempADPCM), tempADPCM, false);
|
*samples_to_process += StreamADPCM::SAMPLES_PER_BLOCK;
|
||||||
s_audio_position += sizeof(tempADPCM);
|
|
||||||
StreamADPCM::DecodeBlock(tempPCM + samples_processed * 2, tempADPCM);
|
|
||||||
samples_processed += StreamADPCM::SAMPLES_PER_BLOCK;
|
|
||||||
} while (samples_processed < num_samples);
|
|
||||||
for (unsigned i = 0; i < samples_processed * 2; ++i)
|
|
||||||
{
|
|
||||||
// TODO: Fix the mixer so it can accept non-byte-swapped samples.
|
|
||||||
tempPCM[i] = Common::swap16(tempPCM[i]);
|
|
||||||
}
|
}
|
||||||
return samples_processed;
|
|
||||||
|
return bytes_to_process;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void DTKStreamingCallback(u64 userdata, s64 cyclesLate)
|
static void DTKStreamingCallback(const std::vector<u8>& audio_data, s64 cycles_late)
|
||||||
{
|
{
|
||||||
// Send audio to the mixer.
|
// Send audio to the mixer.
|
||||||
static const int NUM_SAMPLES = 48000 / 2000 * 7; // 3.5ms of 48kHz samples
|
std::vector<s16> temp_pcm(s_pending_samples * 2, 0);
|
||||||
short tempPCM[NUM_SAMPLES * 2];
|
ProcessDTKSamples(&temp_pcm, audio_data);
|
||||||
unsigned samples_processed;
|
g_sound_stream->GetMixer()->PushStreamingSamples(temp_pcm.data(), s_pending_samples);
|
||||||
|
|
||||||
|
// Determine which audio data to read next.
|
||||||
|
static const int MAXIMUM_SAMPLES = 48000 / 2000 * 7; // 3.5ms of 48kHz samples
|
||||||
|
u64 read_offset;
|
||||||
|
u32 read_length;
|
||||||
if (s_stream && AudioInterface::IsPlaying())
|
if (s_stream && AudioInterface::IsPlaying())
|
||||||
{
|
{
|
||||||
samples_processed = ProcessDTKSamples(tempPCM, NUM_SAMPLES);
|
read_offset = s_audio_position;
|
||||||
|
read_length = AdvanceDTK(MAXIMUM_SAMPLES, &s_pending_samples);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
memset(tempPCM, 0, sizeof(tempPCM));
|
read_length = 0;
|
||||||
samples_processed = NUM_SAMPLES;
|
s_pending_samples = MAXIMUM_SAMPLES;
|
||||||
}
|
}
|
||||||
g_sound_stream->GetMixer()->PushStreamingSamples(tempPCM, samples_processed);
|
|
||||||
|
|
||||||
int ticks_to_dtk = int(SystemTimers::GetTicksPerSecond() * u64(samples_processed) / 48000);
|
// Read the next chunk of audio data asynchronously.
|
||||||
CoreTiming::ScheduleEvent(ticks_to_dtk - cyclesLate, s_dtk);
|
s64 ticks_to_dtk = SystemTimers::GetTicksPerSecond() * s64(s_pending_samples) / 48000;
|
||||||
|
ticks_to_dtk -= cycles_late;
|
||||||
|
if (read_length > 0)
|
||||||
|
{
|
||||||
|
DVDThread::StartRead(read_offset, read_length, false, ReplyType::DTK, ticks_to_dtk);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// There's nothing to read, so using DVDThread is unnecessary.
|
||||||
|
u64 userdata = PackFinishExecutingCommandUserdata(ReplyType::DTK, DIInterruptType::INT_TCINT);
|
||||||
|
CoreTiming::ScheduleEvent(ticks_to_dtk, s_finish_executing_command, userdata);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Init()
|
void Init()
|
||||||
|
@ -392,16 +423,17 @@ void Init()
|
||||||
s_DICFG.Hex = 0;
|
s_DICFG.Hex = 0;
|
||||||
s_DICFG.CONFIG = 1; // Disable bootrom descrambler
|
s_DICFG.CONFIG = 1; // Disable bootrom descrambler
|
||||||
|
|
||||||
|
s_stream = false;
|
||||||
|
s_stop_at_track_end = false;
|
||||||
s_audio_position = 0;
|
s_audio_position = 0;
|
||||||
s_next_start = 0;
|
s_next_start = 0;
|
||||||
s_next_length = 0;
|
s_next_length = 0;
|
||||||
s_current_start = 0;
|
s_current_start = 0;
|
||||||
s_current_length = 0;
|
s_current_length = 0;
|
||||||
|
s_pending_samples = 0;
|
||||||
|
|
||||||
s_error_code = 0;
|
s_error_code = 0;
|
||||||
s_disc_inside = false;
|
s_disc_inside = false;
|
||||||
s_stream = false;
|
|
||||||
s_stop_at_track_end = false;
|
|
||||||
|
|
||||||
s_last_read_offset = 0;
|
s_last_read_offset = 0;
|
||||||
s_last_read_time = 0;
|
s_last_read_time = 0;
|
||||||
|
@ -413,9 +445,9 @@ void Init()
|
||||||
|
|
||||||
s_finish_executing_command =
|
s_finish_executing_command =
|
||||||
CoreTiming::RegisterEvent("FinishExecutingCommand", FinishExecutingCommandCallback);
|
CoreTiming::RegisterEvent("FinishExecutingCommand", FinishExecutingCommandCallback);
|
||||||
s_dtk = CoreTiming::RegisterEvent("StreamingTimer", DTKStreamingCallback);
|
|
||||||
|
|
||||||
CoreTiming::ScheduleEvent(0, s_dtk);
|
u64 userdata = PackFinishExecutingCommandUserdata(ReplyType::DTK, DIInterruptType::INT_TCINT);
|
||||||
|
CoreTiming::ScheduleEvent(0, s_finish_executing_command, userdata);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Shutdown()
|
void Shutdown()
|
||||||
|
@ -647,8 +679,7 @@ void WriteImmediate(u32 value, u32 output_address, bool reply_to_ios)
|
||||||
|
|
||||||
// Iff false is returned, ScheduleEvent must be used to finish executing the command
|
// Iff false is returned, ScheduleEvent must be used to finish executing the command
|
||||||
bool ExecuteReadCommand(u64 DVD_offset, u32 output_address, u32 DVD_length, u32 output_length,
|
bool ExecuteReadCommand(u64 DVD_offset, u32 output_address, u32 DVD_length, u32 output_length,
|
||||||
bool decrypt, bool reply_to_ios, DIInterruptType* interrupt_type,
|
bool decrypt, ReplyType reply_type, DIInterruptType* interrupt_type)
|
||||||
u64* ticks_until_completion)
|
|
||||||
{
|
{
|
||||||
if (!s_disc_inside)
|
if (!s_disc_inside)
|
||||||
{
|
{
|
||||||
|
@ -665,32 +696,34 @@ bool ExecuteReadCommand(u64 DVD_offset, u32 output_address, u32 DVD_length, u32
|
||||||
|
|
||||||
if (DVD_length > output_length)
|
if (DVD_length > output_length)
|
||||||
{
|
{
|
||||||
WARN_LOG(
|
WARN_LOG(DVDINTERFACE, "Detected an attempt to read more data from the DVD "
|
||||||
DVDINTERFACE,
|
"than what fits inside the out buffer. Clamping.");
|
||||||
"Detected attempt to read more data from the DVD than fit inside the out buffer. Clamp.");
|
|
||||||
DVD_length = output_length;
|
DVD_length = output_length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
u64 ticks_until_completion;
|
||||||
if (SConfig::GetInstance().bFastDiscSpeed)
|
if (SConfig::GetInstance().bFastDiscSpeed)
|
||||||
|
{
|
||||||
// An optional hack to speed up loading times
|
// An optional hack to speed up loading times
|
||||||
*ticks_until_completion =
|
ticks_until_completion =
|
||||||
output_length * (SystemTimers::GetTicksPerSecond() / BUFFER_TRANSFER_RATE);
|
output_length * (SystemTimers::GetTicksPerSecond() / BUFFER_TRANSFER_RATE);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
*ticks_until_completion = SimulateDiscReadTime(DVD_offset, DVD_length);
|
{
|
||||||
|
ticks_until_completion = SimulateDiscReadTime(DVD_offset, DVD_length);
|
||||||
|
}
|
||||||
|
|
||||||
DVDThread::StartRead(DVD_offset, output_address, DVD_length, decrypt, reply_to_ios,
|
DVDThread::StartReadToEmulatedRAM(output_address, DVD_offset, DVD_length, decrypt, reply_type,
|
||||||
(int)*ticks_until_completion);
|
ticks_until_completion);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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,
|
void ExecuteCommand(u32 command_0, u32 command_1, u32 command_2, u32 output_address,
|
||||||
u32 output_length, bool reply_to_ios)
|
u32 output_length, bool reply_to_ios)
|
||||||
{
|
{
|
||||||
|
ReplyType reply_type = reply_to_ios ? ReplyType::IOS_HLE : ReplyType::Interrupt;
|
||||||
DIInterruptType interrupt_type = INT_TCINT;
|
DIInterruptType interrupt_type = INT_TCINT;
|
||||||
u64 ticks_until_completion = SystemTimers::GetTicksPerSecond() / 15000;
|
s64 ticks_until_completion = SystemTimers::GetTicksPerSecond() / 15000;
|
||||||
bool command_handled_by_thread = false;
|
bool command_handled_by_thread = false;
|
||||||
|
|
||||||
// DVDLowRequestError needs access to the error code set by the previous command
|
// DVDLowRequestError needs access to the error code set by the previous command
|
||||||
|
@ -715,9 +748,8 @@ void ExecuteCommand(u32 command_0, u32 command_1, u32 command_2, u32 output_addr
|
||||||
// Only seems to be used from WII_IPC, not through direct access
|
// Only seems to be used from WII_IPC, not through direct access
|
||||||
case DVDLowReadDiskID:
|
case DVDLowReadDiskID:
|
||||||
INFO_LOG(DVDINTERFACE, "DVDLowReadDiskID");
|
INFO_LOG(DVDINTERFACE, "DVDLowReadDiskID");
|
||||||
command_handled_by_thread =
|
command_handled_by_thread = ExecuteReadCommand(0, output_address, 0x20, output_length, false,
|
||||||
ExecuteReadCommand(0, output_address, 0x20, output_length, false, reply_to_ios,
|
reply_type, &interrupt_type);
|
||||||
&interrupt_type, &ticks_until_completion);
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Only used from WII_IPC. This is the only read command that decrypts data
|
// Only used from WII_IPC. This is the only read command that decrypts data
|
||||||
|
@ -726,7 +758,7 @@ void ExecuteCommand(u32 command_0, u32 command_1, u32 command_2, u32 output_addr
|
||||||
command_1);
|
command_1);
|
||||||
command_handled_by_thread =
|
command_handled_by_thread =
|
||||||
ExecuteReadCommand((u64)command_2 << 2, output_address, command_1, output_length, true,
|
ExecuteReadCommand((u64)command_2 << 2, output_address, command_1, output_length, true,
|
||||||
reply_to_ios, &interrupt_type, &ticks_until_completion);
|
reply_type, &interrupt_type);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Probably only used by Wii
|
// Probably only used by Wii
|
||||||
|
@ -808,7 +840,7 @@ void ExecuteCommand(u32 command_0, u32 command_1, u32 command_2, u32 output_addr
|
||||||
{
|
{
|
||||||
command_handled_by_thread =
|
command_handled_by_thread =
|
||||||
ExecuteReadCommand((u64)command_2 << 2, output_address, command_1, output_length, false,
|
ExecuteReadCommand((u64)command_2 << 2, output_address, command_1, output_length, false,
|
||||||
reply_to_ios, &interrupt_type, &ticks_until_completion);
|
reply_type, &interrupt_type);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -856,17 +888,15 @@ void ExecuteCommand(u32 command_0, u32 command_1, u32 command_2, u32 output_addr
|
||||||
", DMABuffer = %08x, SrcLength = %08x, DMALength = %08x",
|
", DMABuffer = %08x, SrcLength = %08x, DMALength = %08x",
|
||||||
iDVDOffset, output_address, command_2, output_length);
|
iDVDOffset, output_address, command_2, output_length);
|
||||||
|
|
||||||
command_handled_by_thread =
|
command_handled_by_thread = ExecuteReadCommand(
|
||||||
ExecuteReadCommand(iDVDOffset, output_address, command_2, output_length, false,
|
iDVDOffset, output_address, command_2, output_length, false, reply_type, &interrupt_type);
|
||||||
reply_to_ios, &interrupt_type, &ticks_until_completion);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x40: // Read DiscID
|
case 0x40: // Read DiscID
|
||||||
INFO_LOG(DVDINTERFACE, "Read DiscID %08x", Memory::Read_U32(output_address));
|
INFO_LOG(DVDINTERFACE, "Read DiscID %08x", Memory::Read_U32(output_address));
|
||||||
command_handled_by_thread =
|
command_handled_by_thread = ExecuteReadCommand(0, output_address, 0x20, output_length, false,
|
||||||
ExecuteReadCommand(0, output_address, 0x20, output_length, false, reply_to_ios,
|
reply_type, &interrupt_type);
|
||||||
&interrupt_type, &ticks_until_completion);
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
@ -951,9 +981,7 @@ void ExecuteCommand(u32 command_0, u32 command_1, u32 command_2, u32 output_addr
|
||||||
}
|
}
|
||||||
else if (!s_stop_at_track_end)
|
else if (!s_stop_at_track_end)
|
||||||
{
|
{
|
||||||
// Setting s_next_start (a u32) like this discards two bits,
|
s_next_start = static_cast<u64>(command_1) << 2;
|
||||||
// but GC games can't be 4 GiB big, so it shouldn't matter
|
|
||||||
s_next_start = command_1 << 2;
|
|
||||||
s_next_length = command_2;
|
s_next_length = command_2;
|
||||||
if (!s_stream)
|
if (!s_stream)
|
||||||
{
|
{
|
||||||
|
@ -986,17 +1014,17 @@ void ExecuteCommand(u32 command_0, u32 command_1, u32 command_2, u32 output_addr
|
||||||
case 0x01: // Returns the current offset
|
case 0x01: // Returns the current offset
|
||||||
INFO_LOG(DVDINTERFACE, "(Audio): Stream Status: Request Audio status AudioPos:%08x",
|
INFO_LOG(DVDINTERFACE, "(Audio): Stream Status: Request Audio status AudioPos:%08x",
|
||||||
s_audio_position);
|
s_audio_position);
|
||||||
WriteImmediate(s_audio_position >> 2, output_address, reply_to_ios);
|
WriteImmediate(static_cast<u32>(s_audio_position >> 2), output_address, reply_to_ios);
|
||||||
break;
|
break;
|
||||||
case 0x02: // Returns the start offset
|
case 0x02: // Returns the start offset
|
||||||
INFO_LOG(DVDINTERFACE, "(Audio): Stream Status: Request Audio status CurrentStart:%08x",
|
INFO_LOG(DVDINTERFACE, "(Audio): Stream Status: Request Audio status CurrentStart:%08x",
|
||||||
s_current_start);
|
s_current_start);
|
||||||
WriteImmediate(s_current_start >> 2, output_address, reply_to_ios);
|
WriteImmediate(static_cast<u32>(s_current_start >> 2), output_address, reply_to_ios);
|
||||||
break;
|
break;
|
||||||
case 0x03: // Returns the total length
|
case 0x03: // Returns the total length
|
||||||
INFO_LOG(DVDINTERFACE, "(Audio): Stream Status: Request Audio status CurrentLength:%08x",
|
INFO_LOG(DVDINTERFACE, "(Audio): Stream Status: Request Audio status CurrentLength:%08x",
|
||||||
s_current_length);
|
s_current_length);
|
||||||
WriteImmediate(s_current_length >> 2, output_address, reply_to_ios);
|
WriteImmediate(static_cast<u32>(s_current_length >> 2), output_address, reply_to_ios);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
INFO_LOG(DVDINTERFACE, "(Audio): Subcommand: %02x Request Audio status %s",
|
INFO_LOG(DVDINTERFACE, "(Audio): Subcommand: %02x Request Audio status %s",
|
||||||
|
@ -1083,33 +1111,52 @@ void ExecuteCommand(u32 command_0, u32 command_1, u32 command_2, u32 output_addr
|
||||||
// to simulate the speed of a real disc drive
|
// to simulate the speed of a real disc drive
|
||||||
if (!command_handled_by_thread)
|
if (!command_handled_by_thread)
|
||||||
{
|
{
|
||||||
u64 userdata = (static_cast<u64>(reply_to_ios) << 32) + static_cast<u32>(interrupt_type);
|
CoreTiming::ScheduleEvent(ticks_until_completion, s_finish_executing_command,
|
||||||
CoreTiming::ScheduleEvent((int)ticks_until_completion, s_finish_executing_command, userdata);
|
PackFinishExecutingCommandUserdata(reply_type, interrupt_type));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
u64 PackFinishExecutingCommandUserdata(ReplyType reply_type, DIInterruptType interrupt_type)
|
||||||
|
{
|
||||||
|
return (static_cast<u64>(reply_type) << 32) + static_cast<u32>(interrupt_type);
|
||||||
|
}
|
||||||
|
|
||||||
void FinishExecutingCommandCallback(u64 userdata, s64 cycles_late)
|
void FinishExecutingCommandCallback(u64 userdata, s64 cycles_late)
|
||||||
{
|
{
|
||||||
bool reply_to_ios = userdata >> 32 != 0;
|
ReplyType reply_type = static_cast<ReplyType>(userdata >> 32);
|
||||||
DIInterruptType interrupt_type = static_cast<DIInterruptType>(userdata & 0xFFFFFFFF);
|
DIInterruptType interrupt_type = static_cast<DIInterruptType>(userdata & 0xFFFFFFFF);
|
||||||
FinishExecutingCommand(reply_to_ios, interrupt_type);
|
FinishExecutingCommand(reply_type, interrupt_type, cycles_late);
|
||||||
}
|
}
|
||||||
|
|
||||||
void FinishExecutingCommand(bool reply_to_ios, DIInterruptType interrupt_type)
|
void FinishExecutingCommand(ReplyType reply_type, DIInterruptType interrupt_type, s64 cycles_late,
|
||||||
|
const std::vector<u8>& data)
|
||||||
{
|
{
|
||||||
if (reply_to_ios)
|
switch (reply_type)
|
||||||
|
{
|
||||||
|
case ReplyType::Interrupt:
|
||||||
|
{
|
||||||
|
if (s_DICR.TSTART)
|
||||||
|
{
|
||||||
|
s_DICR.TSTART = 0;
|
||||||
|
s_DILENGTH.Length = 0;
|
||||||
|
GenerateDIInterrupt(interrupt_type);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case ReplyType::IOS_HLE:
|
||||||
{
|
{
|
||||||
std::shared_ptr<IWII_IPC_HLE_Device> di = WII_IPC_HLE_Interface::GetDeviceByName("/dev/di");
|
std::shared_ptr<IWII_IPC_HLE_Device> di = WII_IPC_HLE_Interface::GetDeviceByName("/dev/di");
|
||||||
if (di)
|
if (di)
|
||||||
std::static_pointer_cast<CWII_IPC_HLE_Device_di>(di)->FinishIOCtl(interrupt_type);
|
std::static_pointer_cast<CWII_IPC_HLE_Device_di>(di)->FinishIOCtl(interrupt_type);
|
||||||
|
break;
|
||||||
// If di == nullptr, IOS was probably shut down, so the command shouldn't be completed
|
|
||||||
}
|
}
|
||||||
else if (s_DICR.TSTART)
|
|
||||||
|
case ReplyType::DTK:
|
||||||
{
|
{
|
||||||
s_DICR.TSTART = 0;
|
DTKStreamingCallback(data, cycles_late);
|
||||||
s_DILENGTH.Length = 0;
|
break;
|
||||||
GenerateDIInterrupt(interrupt_type);
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include "Common/CommonTypes.h"
|
#include "Common/CommonTypes.h"
|
||||||
|
|
||||||
|
@ -92,6 +93,13 @@ enum DIInterruptType : int
|
||||||
INT_CVRINT = 3,
|
INT_CVRINT = 3,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class ReplyType : u32
|
||||||
|
{
|
||||||
|
Interrupt,
|
||||||
|
IOS_HLE,
|
||||||
|
DTK
|
||||||
|
};
|
||||||
|
|
||||||
void Init();
|
void Init();
|
||||||
void Shutdown();
|
void Shutdown();
|
||||||
void DoState(PointerWrap& p);
|
void DoState(PointerWrap& p);
|
||||||
|
@ -115,6 +123,7 @@ void ChangeDiscAsCPU(const std::string& new_path); // Can only be called by th
|
||||||
bool ChangePartition(u64 offset);
|
bool ChangePartition(u64 offset);
|
||||||
void ExecuteCommand(u32 command_0, u32 command_1, u32 command_2, u32 output_address,
|
void ExecuteCommand(u32 command_0, u32 command_1, u32 command_2, u32 output_address,
|
||||||
u32 output_length, bool reply_to_ios);
|
u32 output_length, bool reply_to_ios);
|
||||||
void FinishExecutingCommand(bool reply_to_ios, DIInterruptType interrupt_type);
|
void FinishExecutingCommand(ReplyType reply_type, DIInterruptType interrupt_type, s64 cycles_late,
|
||||||
|
const std::vector<u8>& data = std::vector<u8>());
|
||||||
|
|
||||||
} // end of namespace DVDInterface
|
} // end of namespace DVDInterface
|
||||||
|
|
|
@ -3,13 +3,16 @@
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
#include <cinttypes>
|
#include <cinttypes>
|
||||||
|
#include <map>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "Common/ChunkFile.h"
|
#include "Common/ChunkFile.h"
|
||||||
#include "Common/CommonTypes.h"
|
#include "Common/CommonTypes.h"
|
||||||
#include "Common/Event.h"
|
#include "Common/Event.h"
|
||||||
|
#include "Common/FifoQueue.h"
|
||||||
#include "Common/Flag.h"
|
#include "Common/Flag.h"
|
||||||
#include "Common/Logging/Log.h"
|
#include "Common/Logging/Log.h"
|
||||||
#include "Common/MsgHandler.h"
|
#include "Common/MsgHandler.h"
|
||||||
|
@ -27,132 +30,237 @@
|
||||||
|
|
||||||
namespace DVDThread
|
namespace DVDThread
|
||||||
{
|
{
|
||||||
|
struct ReadRequest
|
||||||
|
{
|
||||||
|
bool copy_to_ram;
|
||||||
|
u32 output_address;
|
||||||
|
u64 dvd_offset;
|
||||||
|
u32 length;
|
||||||
|
bool decrypt;
|
||||||
|
|
||||||
|
// This determines which code DVDInterface will run to reply
|
||||||
|
// to the emulated software. We can't use callbacks,
|
||||||
|
// because function pointers can't be stored in savestates.
|
||||||
|
DVDInterface::ReplyType reply_type;
|
||||||
|
|
||||||
|
// IDs are used to uniquely identify a request. They must not be
|
||||||
|
// identical to IDs of any other requests that currently exist, but
|
||||||
|
// it's fine to re-use IDs of requests that have existed in the past.
|
||||||
|
u64 id;
|
||||||
|
|
||||||
|
// Only used for logging
|
||||||
|
u64 time_started_ticks;
|
||||||
|
u64 realtime_started_us;
|
||||||
|
u64 realtime_done_us;
|
||||||
|
};
|
||||||
|
|
||||||
|
using ReadResult = std::pair<ReadRequest, std::vector<u8>>;
|
||||||
|
|
||||||
|
static void StartDVDThread();
|
||||||
|
static void StopDVDThread();
|
||||||
|
|
||||||
static void DVDThread();
|
static void DVDThread();
|
||||||
|
|
||||||
static void FinishRead(u64 userdata, s64 cycles_late);
|
static void StartReadInternal(bool copy_to_ram, u32 output_address, u64 dvd_offset, u32 length,
|
||||||
|
bool decrypt, DVDInterface::ReplyType reply_type,
|
||||||
|
s64 ticks_until_completion);
|
||||||
|
|
||||||
|
static void FinishRead(u64 id, s64 cycles_late);
|
||||||
static CoreTiming::EventType* s_finish_read;
|
static CoreTiming::EventType* s_finish_read;
|
||||||
|
|
||||||
|
static u64 s_next_id = 0;
|
||||||
|
|
||||||
static std::thread s_dvd_thread;
|
static std::thread s_dvd_thread;
|
||||||
static Common::Event s_dvd_thread_start_working;
|
static Common::Event s_request_queue_expanded; // Is set by CPU thread
|
||||||
static Common::Event s_dvd_thread_done_working;
|
static Common::Event s_result_queue_expanded; // Is set by DVD thread
|
||||||
static Common::Flag s_dvd_thread_exiting(false);
|
static Common::Flag s_dvd_thread_exiting(false); // Is set by CPU thread
|
||||||
|
|
||||||
static std::vector<u8> s_dvd_buffer;
|
static Common::FifoQueue<ReadRequest, false> s_request_queue;
|
||||||
static u64 s_time_read_started;
|
static Common::FifoQueue<ReadResult, false> s_result_queue;
|
||||||
static bool s_dvd_success;
|
static std::map<u64, ReadResult> s_result_map;
|
||||||
|
|
||||||
static u64 s_dvd_offset;
|
|
||||||
static u32 s_output_address;
|
|
||||||
static u32 s_length;
|
|
||||||
static bool s_decrypt;
|
|
||||||
|
|
||||||
// This determines which function will be used as a callback.
|
|
||||||
// We can't have a function pointer here, because they can't be in savestates.
|
|
||||||
static bool s_reply_to_ios;
|
|
||||||
|
|
||||||
// The following time variables are only used for logging
|
|
||||||
static u64 s_realtime_started_us;
|
|
||||||
static u64 s_realtime_done_us;
|
|
||||||
|
|
||||||
void Start()
|
void Start()
|
||||||
{
|
{
|
||||||
s_finish_read = CoreTiming::RegisterEvent("FinishReadDVDThread", FinishRead);
|
s_finish_read = CoreTiming::RegisterEvent("FinishReadDVDThread", FinishRead);
|
||||||
|
|
||||||
|
s_request_queue_expanded.Reset();
|
||||||
|
s_result_queue_expanded.Reset();
|
||||||
|
s_request_queue.Clear();
|
||||||
|
s_result_queue.Clear();
|
||||||
|
|
||||||
|
// This is reset on every launch for determinism, but it doesn't matter
|
||||||
|
// much, because this will never get exposed to the emulated game.
|
||||||
|
s_next_id = 0;
|
||||||
|
|
||||||
|
StartDVDThread();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void StartDVDThread()
|
||||||
|
{
|
||||||
_assert_(!s_dvd_thread.joinable());
|
_assert_(!s_dvd_thread.joinable());
|
||||||
|
s_dvd_thread_exiting.Clear();
|
||||||
s_dvd_thread = std::thread(DVDThread);
|
s_dvd_thread = std::thread(DVDThread);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Stop()
|
void Stop()
|
||||||
|
{
|
||||||
|
StopDVDThread();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void StopDVDThread()
|
||||||
{
|
{
|
||||||
_assert_(s_dvd_thread.joinable());
|
_assert_(s_dvd_thread.joinable());
|
||||||
|
|
||||||
// The DVD thread will return if s_DVD_thread_exiting
|
// By setting s_DVD_thread_exiting, we ask the DVD thread to cleanly exit.
|
||||||
// is set when it starts working
|
// In case the request queue is empty, we need to set s_request_queue_expanded
|
||||||
|
// so that the DVD thread will wake up and check s_DVD_thread_exiting.
|
||||||
s_dvd_thread_exiting.Set();
|
s_dvd_thread_exiting.Set();
|
||||||
s_dvd_thread_start_working.Set();
|
s_request_queue_expanded.Set();
|
||||||
|
|
||||||
s_dvd_thread.join();
|
s_dvd_thread.join();
|
||||||
|
|
||||||
s_dvd_thread_exiting.Clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DoState(PointerWrap& p)
|
void DoState(PointerWrap& p)
|
||||||
{
|
{
|
||||||
|
// By waiting for the DVD thread to be done working, we ensure that
|
||||||
|
// there are no pending requests. The DVD thread won't be touching
|
||||||
|
// s_result_queue, and everything we need to save will be in either
|
||||||
|
// s_result_queue or s_result_map (other than s_next_id).
|
||||||
WaitUntilIdle();
|
WaitUntilIdle();
|
||||||
|
|
||||||
// TODO: Savestates can be smaller if s_DVD_buffer is not saved
|
// Move everything from s_result_queue to s_result_map because
|
||||||
p.Do(s_dvd_buffer);
|
// PointerWrap::Do supports std::map but not Common::FifoQueue.
|
||||||
p.Do(s_time_read_started);
|
// This won't affect the behavior of FinishRead.
|
||||||
p.Do(s_dvd_success);
|
ReadResult result;
|
||||||
|
while (s_result_queue.Pop(result))
|
||||||
|
s_result_map.emplace(result.first.id, std::move(result));
|
||||||
|
|
||||||
p.Do(s_dvd_offset);
|
// Everything is now in s_result_map, so we simply savestate that.
|
||||||
p.Do(s_output_address);
|
// We also savestate s_next_id to avoid ID collisions.
|
||||||
p.Do(s_length);
|
p.Do(s_result_map);
|
||||||
p.Do(s_decrypt);
|
p.Do(s_next_id);
|
||||||
p.Do(s_reply_to_ios);
|
|
||||||
|
|
||||||
// s_realtime_started_us and s_realtime_done_us aren't savestated
|
// TODO: Savestates can be smaller if the buffers of results aren't saved,
|
||||||
// because they rely on the current system's time.
|
// but instead get re-read from the disc when loading the savestate.
|
||||||
// This means that loading a savestate might cause
|
|
||||||
// incorrect times to be logged once.
|
// TODO: It would be possible to create a savestate faster by stopping
|
||||||
|
// the DVD thread regardless of whether there are pending requests.
|
||||||
|
|
||||||
|
// After loading a savestate, the debug log in FinishRead will report
|
||||||
|
// screwed up times for requests that were submitted before the savestate
|
||||||
|
// was made. Handling that properly may be more effort than it's worth.
|
||||||
}
|
}
|
||||||
|
|
||||||
void WaitUntilIdle()
|
void WaitUntilIdle()
|
||||||
{
|
{
|
||||||
_assert_(Core::IsCPUThread());
|
_assert_(Core::IsCPUThread());
|
||||||
|
|
||||||
// Wait until DVD thread isn't working
|
while (!s_request_queue.Empty())
|
||||||
s_dvd_thread_done_working.Wait();
|
s_result_queue_expanded.Wait();
|
||||||
|
|
||||||
// Set the event again so that we still know that the DVD thread isn't working
|
StopDVDThread();
|
||||||
s_dvd_thread_done_working.Set();
|
StartDVDThread();
|
||||||
}
|
}
|
||||||
|
|
||||||
void StartRead(u64 dvd_offset, u32 output_address, u32 length, bool decrypt, bool reply_to_ios,
|
void StartRead(u64 dvd_offset, u32 length, bool decrypt, DVDInterface::ReplyType reply_type,
|
||||||
int ticks_until_completion)
|
s64 ticks_until_completion)
|
||||||
|
{
|
||||||
|
StartReadInternal(false, 0, dvd_offset, length, decrypt, reply_type, ticks_until_completion);
|
||||||
|
}
|
||||||
|
|
||||||
|
void StartReadToEmulatedRAM(u32 output_address, u64 dvd_offset, u32 length, bool decrypt,
|
||||||
|
DVDInterface::ReplyType reply_type, s64 ticks_until_completion)
|
||||||
|
{
|
||||||
|
StartReadInternal(true, output_address, dvd_offset, length, decrypt, reply_type,
|
||||||
|
ticks_until_completion);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void StartReadInternal(bool copy_to_ram, u32 output_address, u64 dvd_offset, u32 length,
|
||||||
|
bool decrypt, DVDInterface::ReplyType reply_type,
|
||||||
|
s64 ticks_until_completion)
|
||||||
{
|
{
|
||||||
_assert_(Core::IsCPUThread());
|
_assert_(Core::IsCPUThread());
|
||||||
|
|
||||||
s_dvd_thread_done_working.Wait();
|
ReadRequest request;
|
||||||
|
|
||||||
s_dvd_offset = dvd_offset;
|
request.copy_to_ram = copy_to_ram;
|
||||||
s_output_address = output_address;
|
request.output_address = output_address;
|
||||||
s_length = length;
|
request.dvd_offset = dvd_offset;
|
||||||
s_decrypt = decrypt;
|
request.length = length;
|
||||||
s_reply_to_ios = reply_to_ios;
|
request.decrypt = decrypt;
|
||||||
|
request.reply_type = reply_type;
|
||||||
|
|
||||||
s_time_read_started = CoreTiming::GetTicks();
|
u64 id = s_next_id++;
|
||||||
s_realtime_started_us = Common::Timer::GetTimeUs();
|
request.id = id;
|
||||||
|
|
||||||
s_dvd_thread_start_working.Set();
|
request.time_started_ticks = CoreTiming::GetTicks();
|
||||||
|
request.realtime_started_us = Common::Timer::GetTimeUs();
|
||||||
|
|
||||||
CoreTiming::ScheduleEvent(ticks_until_completion, s_finish_read);
|
s_request_queue.Push(std::move(request));
|
||||||
|
s_request_queue_expanded.Set();
|
||||||
|
|
||||||
|
CoreTiming::ScheduleEvent(ticks_until_completion, s_finish_read, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void FinishRead(u64 userdata, s64 cycles_late)
|
static void FinishRead(u64 id, s64 cycles_late)
|
||||||
{
|
{
|
||||||
WaitUntilIdle();
|
// We can't simply pop s_result_queue and always get the ReadResult
|
||||||
|
// we want, because the DVD thread may add ReadResults to the queue
|
||||||
|
// in a different order than we want to get them. What we do instead
|
||||||
|
// is to pop the queue until we find the ReadResult we want (the one
|
||||||
|
// whose ID matches userdata), which means we may end up popping
|
||||||
|
// ReadResults that we don't want. We can't add those unwanted results
|
||||||
|
// back to the queue, because the queue can only have one writer.
|
||||||
|
// Instead, we add them to a map that only is used by the CPU thread.
|
||||||
|
// When this function is called again later, it will check the map for
|
||||||
|
// the wanted ReadResult before it starts searching through the queue.
|
||||||
|
ReadResult result;
|
||||||
|
auto it = s_result_map.find(id);
|
||||||
|
if (it != s_result_map.end())
|
||||||
|
{
|
||||||
|
result = std::move(it->second);
|
||||||
|
s_result_map.erase(it);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
while (!s_result_queue.Pop(result))
|
||||||
|
s_result_queue_expanded.Wait();
|
||||||
|
|
||||||
|
if (result.first.id == id)
|
||||||
|
break;
|
||||||
|
else
|
||||||
|
s_result_map.emplace(result.first.id, std::move(result));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// We have now obtained the right ReadResult.
|
||||||
|
|
||||||
|
const ReadRequest& request = result.first;
|
||||||
|
const std::vector<u8>& buffer = result.second;
|
||||||
|
|
||||||
DEBUG_LOG(DVDINTERFACE, "Disc has been read. Real time: %" PRIu64 " us. "
|
DEBUG_LOG(DVDINTERFACE, "Disc has been read. Real time: %" PRIu64 " us. "
|
||||||
"Real time including delay: %" PRIu64
|
"Real time including delay: %" PRIu64 " us. "
|
||||||
" us. Emulated time including delay: %" PRIu64 " us.",
|
"Emulated time including delay: %" PRIu64 " us.",
|
||||||
s_realtime_done_us - s_realtime_started_us,
|
request.realtime_done_us - request.realtime_started_us,
|
||||||
Common::Timer::GetTimeUs() - s_realtime_started_us,
|
Common::Timer::GetTimeUs() - request.realtime_started_us,
|
||||||
(CoreTiming::GetTicks() - s_time_read_started) /
|
(CoreTiming::GetTicks() - request.time_started_ticks) /
|
||||||
(SystemTimers::GetTicksPerSecond() / 1000 / 1000));
|
(SystemTimers::GetTicksPerSecond() / 1000000));
|
||||||
|
|
||||||
if (s_dvd_success)
|
if (buffer.empty())
|
||||||
Memory::CopyToEmu(s_output_address, s_dvd_buffer.data(), s_length);
|
{
|
||||||
|
PanicAlertT("The disc could not be read (at 0x%" PRIx64 " - 0x%" PRIx64 ").",
|
||||||
|
request.dvd_offset, request.dvd_offset + request.length);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
PanicAlertT("The disc could not be read (at 0x%" PRIx64 " - 0x%" PRIx64 ").", s_dvd_offset,
|
{
|
||||||
s_dvd_offset + s_length);
|
if (request.copy_to_ram)
|
||||||
|
Memory::CopyToEmu(request.output_address, buffer.data(), request.length);
|
||||||
// This will make the buffer take less space in savestates.
|
}
|
||||||
// Reducing the size doesn't change the amount of reserved memory,
|
|
||||||
// so this doesn't lead to extra memory allocations.
|
|
||||||
s_dvd_buffer.resize(0);
|
|
||||||
|
|
||||||
// Notify the emulated software that the command has been executed
|
// Notify the emulated software that the command has been executed
|
||||||
DVDInterface::FinishExecutingCommand(s_reply_to_ios, DVDInterface::INT_TCINT);
|
DVDInterface::FinishExecutingCommand(request.reply_type, DVDInterface::INT_TCINT, cycles_late,
|
||||||
|
buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void DVDThread()
|
static void DVDThread()
|
||||||
|
@ -161,19 +269,27 @@ static void DVDThread()
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
s_dvd_thread_done_working.Set();
|
s_request_queue_expanded.Wait();
|
||||||
|
|
||||||
s_dvd_thread_start_working.Wait();
|
|
||||||
|
|
||||||
if (s_dvd_thread_exiting.IsSet())
|
if (s_dvd_thread_exiting.IsSet())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
s_dvd_buffer.resize(s_length);
|
ReadRequest request;
|
||||||
|
while (s_request_queue.Pop(request))
|
||||||
|
{
|
||||||
|
std::vector<u8> buffer(request.length);
|
||||||
|
const DiscIO::IVolume& volume = DVDInterface::GetVolume();
|
||||||
|
if (!volume.Read(request.dvd_offset, request.length, buffer.data(), request.decrypt))
|
||||||
|
buffer.resize(0);
|
||||||
|
|
||||||
s_dvd_success =
|
request.realtime_done_us = Common::Timer::GetTimeUs();
|
||||||
DVDInterface::GetVolume().Read(s_dvd_offset, s_length, s_dvd_buffer.data(), s_decrypt);
|
|
||||||
|
|
||||||
s_realtime_done_us = Common::Timer::GetTimeUs();
|
s_result_queue.Push(ReadResult(std::move(request), std::move(buffer)));
|
||||||
|
s_result_queue_expanded.Set();
|
||||||
|
|
||||||
|
if (s_dvd_thread_exiting.IsSet())
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,9 +4,14 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "Common/ChunkFile.h"
|
|
||||||
#include "Common/CommonTypes.h"
|
#include "Common/CommonTypes.h"
|
||||||
|
|
||||||
|
class PointerWrap;
|
||||||
|
namespace DVDInterface
|
||||||
|
{
|
||||||
|
enum class ReplyType : u32;
|
||||||
|
}
|
||||||
|
|
||||||
namespace DVDThread
|
namespace DVDThread
|
||||||
{
|
{
|
||||||
void Start();
|
void Start();
|
||||||
|
@ -14,6 +19,8 @@ void Stop();
|
||||||
void DoState(PointerWrap& p);
|
void DoState(PointerWrap& p);
|
||||||
|
|
||||||
void WaitUntilIdle();
|
void WaitUntilIdle();
|
||||||
void StartRead(u64 dvd_offset, u32 output_address, u32 length, bool decrypt, bool reply_to_ios,
|
void StartRead(u64 dvd_offset, u32 length, bool decrypt, DVDInterface::ReplyType reply_type,
|
||||||
int ticks_until_completion);
|
s64 ticks_until_completion);
|
||||||
|
void StartReadToEmulatedRAM(u32 output_address, u64 dvd_offset, u32 length, bool decrypt,
|
||||||
|
DVDInterface::ReplyType reply_type, s64 ticks_until_completion);
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,14 +71,14 @@ static Common::Event g_compressAndDumpStateSyncEvent;
|
||||||
static std::thread g_save_thread;
|
static std::thread g_save_thread;
|
||||||
|
|
||||||
// Don't forget to increase this after doing changes on the savestate system
|
// Don't forget to increase this after doing changes on the savestate system
|
||||||
static const u32 STATE_VERSION = 64; // Last changed in PR 4341
|
static const u32 STATE_VERSION = 65; // Last changed in PR 4120
|
||||||
|
|
||||||
// Maps savestate versions to Dolphin versions.
|
// Maps savestate versions to Dolphin versions.
|
||||||
// Versions after 42 don't need to be added to this list,
|
// Versions after 42 don't need to be added to this list,
|
||||||
// beacuse they save the exact Dolphin version to savestates.
|
// beacuse they save the exact Dolphin version to savestates.
|
||||||
static const std::map<u32, std::pair<std::string, std::string>> s_old_versions = {
|
static const std::map<u32, std::pair<std::string, std::string>> s_old_versions = {
|
||||||
// The 16 -> 17 change modified the size of StateHeader,
|
// The 16 -> 17 change modified the size of StateHeader,
|
||||||
// so version older than that can't even be decompressed anymore
|
// so versions older than that can't even be decompressed anymore
|
||||||
{17, {"3.5-1311", "3.5-1364"}}, {18, {"3.5-1366", "3.5-1371"}}, {19, {"3.5-1372", "3.5-1408"}},
|
{17, {"3.5-1311", "3.5-1364"}}, {18, {"3.5-1366", "3.5-1371"}}, {19, {"3.5-1372", "3.5-1408"}},
|
||||||
{20, {"3.5-1409", "4.0-704"}}, {21, {"4.0-705", "4.0-889"}}, {22, {"4.0-905", "4.0-1871"}},
|
{20, {"3.5-1409", "4.0-704"}}, {21, {"4.0-705", "4.0-889"}}, {22, {"4.0-905", "4.0-1871"}},
|
||||||
{23, {"4.0-1873", "4.0-1900"}}, {24, {"4.0-1902", "4.0-1919"}}, {25, {"4.0-1921", "4.0-1936"}},
|
{23, {"4.0-1873", "4.0-1900"}}, {24, {"4.0-1902", "4.0-1919"}}, {25, {"4.0-1921", "4.0-1936"}},
|
||||||
|
|
Loading…
Reference in New Issue