CDROM: Add delay when swapping discs

Fixes broken disc swap detection in Metal Gear Solid.
This commit is contained in:
Connor McLaughlin 2020-05-20 02:26:06 +10:00
parent 415880fc40
commit 340640821e
3 changed files with 61 additions and 38 deletions

View File

@ -48,7 +48,7 @@ void CDROM::SoftReset()
m_drive_event->Deactivate(); m_drive_event->Deactivate();
m_status.bits = 0; m_status.bits = 0;
m_secondary_status.bits = 0; m_secondary_status.bits = 0;
m_secondary_status.motor_on = HasMedia(); m_secondary_status.motor_on = CanReadMedia();
m_mode.bits = 0; m_mode.bits = 0;
m_current_double_speed = false; m_current_double_speed = false;
m_interrupt_enable_register = INTERRUPT_REGISTER_MASK; m_interrupt_enable_register = INTERRUPT_REGISTER_MASK;
@ -164,19 +164,9 @@ bool CDROM::DoState(StateWrapper& sw)
return !sw.HasError(); return !sw.HasError();
} }
bool CDROM::HasMedia() const
{
return m_reader.HasMedia();
}
std::string CDROM::GetMediaFileName() const
{
return m_reader.GetMediaFileName();
}
void CDROM::InsertMedia(std::unique_ptr<CDImage> media) void CDROM::InsertMedia(std::unique_ptr<CDImage> media)
{ {
if (HasMedia()) if (CanReadMedia())
RemoveMedia(); RemoveMedia();
// set the region from the system area of the disc // set the region from the system area of the disc
@ -185,7 +175,8 @@ void CDROM::InsertMedia(std::unique_ptr<CDImage> media)
Settings::GetConsoleRegionName(m_system->GetRegion())); Settings::GetConsoleRegionName(m_system->GetRegion()));
// motor automatically spins up // motor automatically spins up
m_secondary_status.motor_on = true; if (m_drive_state != DriveState::ShellOpening)
m_secondary_status.motor_on = true;
// reading TOC? interestingly this doesn't work for GetlocL though... // reading TOC? interestingly this doesn't work for GetlocL though...
CDImage::SubChannelQ subq; CDImage::SubChannelQ subq;
@ -197,9 +188,11 @@ void CDROM::InsertMedia(std::unique_ptr<CDImage> media)
void CDROM::RemoveMedia(bool force /* = false */) void CDROM::RemoveMedia(bool force /* = false */)
{ {
if (!m_reader.HasMedia() && !force) if (!HasMedia() && !force)
return; return;
const TickCount stop_ticks = GetTicksForStop(true);
Log_InfoPrintf("Removing CD..."); Log_InfoPrintf("Removing CD...");
m_reader.RemoveMedia(); m_reader.RemoveMedia();
@ -218,6 +211,13 @@ void CDROM::RemoveMedia(bool force /* = false */)
// The console sends an interrupt when the shell is opened regardless of whether a command was executing. // The console sends an interrupt when the shell is opened regardless of whether a command was executing.
SendAsyncErrorResponse(STAT_ERROR, 0x08); SendAsyncErrorResponse(STAT_ERROR, 0x08);
// Begin spin-down timer, we can't swap the new disc in immediately for some games (e.g. Metal Gear Solid).
if (!force)
{
m_drive_state = DriveState::ShellOpening;
m_drive_event->SetIntervalAndSchedule(stop_ticks);
}
} }
void CDROM::SetUseReadThread(bool enabled) void CDROM::SetUseReadThread(bool enabled)
@ -541,7 +541,7 @@ TickCount CDROM::GetAckDelayForCommand(Command command)
// presumably because the controller is busy doing discy-things. // presumably because the controller is busy doing discy-things.
constexpr u32 default_ack_delay_no_disc = 15000; constexpr u32 default_ack_delay_no_disc = 15000;
constexpr u32 default_ack_delay_with_disc = 25000; constexpr u32 default_ack_delay_with_disc = 25000;
return HasMedia() ? default_ack_delay_with_disc : default_ack_delay_no_disc; return CanReadMedia() ? default_ack_delay_with_disc : default_ack_delay_no_disc;
} }
TickCount CDROM::GetTicksForRead() TickCount CDROM::GetTicksForRead()
@ -580,6 +580,11 @@ TickCount CDROM::GetTicksForSeek(CDImage::LBA new_lba)
return ticks; return ticks;
} }
TickCount CDROM::GetTicksForStop(bool motor_was_on)
{
return motor_was_on ? (m_mode.double_speed ? 25000000 : 13000000) : 7000;
}
void CDROM::BeginCommand(Command command) void CDROM::BeginCommand(Command command)
{ {
m_command = command; m_command = command;
@ -624,7 +629,7 @@ void CDROM::ExecuteCommand()
SendACKAndStat(); SendACKAndStat();
// shell open bit is cleared after sending the status // shell open bit is cleared after sending the status
if (HasMedia()) if (CanReadMedia())
m_secondary_status.shell_open = false; m_secondary_status.shell_open = false;
EndCommand(); EndCommand();
@ -653,7 +658,7 @@ void CDROM::ExecuteCommand()
case Command::ReadTOC: case Command::ReadTOC:
{ {
Log_DebugPrintf("CDROM ReadTOC command"); Log_DebugPrintf("CDROM ReadTOC command");
if (!HasMedia()) if (!CanReadMedia())
{ {
SendErrorResponse(STAT_ERROR, 0x80); SendErrorResponse(STAT_ERROR, 0x80);
} }
@ -712,7 +717,7 @@ void CDROM::ExecuteCommand()
{ {
const bool logical = (m_command == Command::SeekL); const bool logical = (m_command == Command::SeekL);
Log_DebugPrintf("CDROM %s command", logical ? "SeekL" : "SeekP"); Log_DebugPrintf("CDROM %s command", logical ? "SeekL" : "SeekP");
if (!HasMedia()) if (!CanReadMedia())
{ {
SendErrorResponse(STAT_ERROR, 0x80); SendErrorResponse(STAT_ERROR, 0x80);
} }
@ -731,7 +736,7 @@ void CDROM::ExecuteCommand()
const u8 session = m_param_fifo.IsEmpty() ? 0 : m_param_fifo.Peek(0); const u8 session = m_param_fifo.IsEmpty() ? 0 : m_param_fifo.Peek(0);
Log_DebugPrintf("CDROM SetSession command, session=%u", session); Log_DebugPrintf("CDROM SetSession command, session=%u", session);
if (!HasMedia() || m_drive_state == DriveState::Reading || m_drive_state == DriveState::Playing) if (!CanReadMedia() || m_drive_state == DriveState::Reading || m_drive_state == DriveState::Playing)
{ {
SendErrorResponse(STAT_ERROR, 0x80); SendErrorResponse(STAT_ERROR, 0x80);
} }
@ -756,7 +761,7 @@ void CDROM::ExecuteCommand()
case Command::ReadS: case Command::ReadS:
{ {
Log_DebugPrintf("CDROM read command"); Log_DebugPrintf("CDROM read command");
if (!HasMedia()) if (!CanReadMedia())
{ {
SendErrorResponse(STAT_ERROR, 0x80); SendErrorResponse(STAT_ERROR, 0x80);
} }
@ -786,7 +791,7 @@ void CDROM::ExecuteCommand()
u8 track = m_param_fifo.IsEmpty() ? 0 : m_param_fifo.Peek(0); u8 track = m_param_fifo.IsEmpty() ? 0 : m_param_fifo.Peek(0);
Log_DebugPrintf("CDROM play command, track=%u", track); Log_DebugPrintf("CDROM play command, track=%u", track);
if (!HasMedia()) if (!CanReadMedia())
{ {
SendErrorResponse(STAT_ERROR, 0x80); SendErrorResponse(STAT_ERROR, 0x80);
} }
@ -836,8 +841,7 @@ void CDROM::ExecuteCommand()
case Command::Stop: case Command::Stop:
{ {
const bool was_motor_on = m_secondary_status.motor_on; const TickCount stop_time = GetTicksForStop(m_secondary_status.motor_on);
const TickCount stop_time = was_motor_on ? (m_mode.double_speed ? 25000000 : 13000000) : 7000;
Log_DebugPrintf("CDROM stop command"); Log_DebugPrintf("CDROM stop command");
SendACKAndStat(); SendACKAndStat();
@ -922,7 +926,7 @@ void CDROM::ExecuteCommand()
case Command::GetlocP: case Command::GetlocP:
{ {
if (!HasMedia()) if (!CanReadMedia())
{ {
Log_DebugPrintf("CDROM GetlocP command - not ready"); Log_DebugPrintf("CDROM GetlocP command - not ready");
SendErrorResponse(STAT_ERROR, ERROR_NOT_READY); SendErrorResponse(STAT_ERROR, ERROR_NOT_READY);
@ -956,7 +960,7 @@ void CDROM::ExecuteCommand()
case Command::GetTN: case Command::GetTN:
{ {
Log_DebugPrintf("CDROM GetTN command"); Log_DebugPrintf("CDROM GetTN command");
if (HasMedia()) if (CanReadMedia())
{ {
m_reader.WaitForReadToComplete(); m_reader.WaitForReadToComplete();
@ -980,7 +984,7 @@ void CDROM::ExecuteCommand()
Assert(m_param_fifo.GetSize() >= 1); Assert(m_param_fifo.GetSize() >= 1);
const u8 track = PackedBCDToBinary(m_param_fifo.Peek()); const u8 track = PackedBCDToBinary(m_param_fifo.Peek());
if (!HasMedia()) if (!CanReadMedia())
{ {
SendErrorResponse(STAT_ERROR, 0x80); SendErrorResponse(STAT_ERROR, 0x80);
} }
@ -1141,6 +1145,10 @@ void CDROM::ExecuteDrive(TickCount ticks_late)
DoResetComplete(ticks_late); DoResetComplete(ticks_late);
break; break;
case DriveState::ShellOpening:
DoShellOpenComplete(ticks_late);
break;
case DriveState::SeekingPhysical: case DriveState::SeekingPhysical:
case DriveState::SeekingLogical: case DriveState::SeekingLogical:
DoSeekComplete(ticks_late); DoSeekComplete(ticks_late);
@ -1303,17 +1311,27 @@ void CDROM::UpdatePositionWhileSeeking()
} }
} }
void CDROM::DoShellOpenComplete(TickCount ticks_late)
{
// media is now readable (if any)
m_drive_state = DriveState::Idle;
m_drive_event->Deactivate();
if (m_reader.HasMedia())
m_secondary_status.motor_on = true;
}
void CDROM::DoResetComplete(TickCount ticks_late) void CDROM::DoResetComplete(TickCount ticks_late)
{ {
m_drive_state = DriveState::Idle; m_drive_state = DriveState::Idle;
m_drive_event->Deactivate(); m_drive_event->Deactivate();
m_secondary_status.bits = 0; m_secondary_status.bits = 0;
m_secondary_status.motor_on = HasMedia(); m_secondary_status.motor_on = CanReadMedia();
m_mode.bits = 0; m_mode.bits = 0;
m_mode.read_raw_sector = true; m_mode.read_raw_sector = true;
if (!HasMedia()) if (!CanReadMedia())
{ {
Log_DevPrintf("CDROM reset - no disc"); Log_DevPrintf("CDROM reset - no disc");
m_secondary_status.shell_open = true; m_secondary_status.shell_open = true;
@ -1325,7 +1343,7 @@ void CDROM::DoResetComplete(TickCount ticks_late)
m_async_response_fifo.Push(m_secondary_status.bits); m_async_response_fifo.Push(m_secondary_status.bits);
SetAsyncInterrupt(Interrupt::Complete); SetAsyncInterrupt(Interrupt::Complete);
if (!HasMedia()) if (!CanReadMedia())
{ {
m_secondary_status.motor_on = false; m_secondary_status.motor_on = false;
m_secondary_status.shell_open = true; m_secondary_status.shell_open = true;
@ -1464,12 +1482,12 @@ void CDROM::DoIDRead()
m_drive_state = DriveState::Idle; m_drive_state = DriveState::Idle;
m_drive_event->Deactivate(); m_drive_event->Deactivate();
m_secondary_status.ClearActiveBits(); m_secondary_status.ClearActiveBits();
m_secondary_status.motor_on = HasMedia(); m_secondary_status.motor_on = CanReadMedia();
// TODO: Audio CD. // TODO: Audio CD.
u8 stat_byte = m_secondary_status.bits; u8 stat_byte = m_secondary_status.bits;
u8 flags_byte = 0; u8 flags_byte = 0;
if (!HasMedia()) if (!CanReadMedia())
{ {
flags_byte |= (1 << 6); // Disc Missing flags_byte |= (1 << 6); // Disc Missing
} }
@ -1964,7 +1982,7 @@ void CDROM::DrawDebugWindow()
// draw voice states // draw voice states
if (ImGui::CollapsingHeader("Media", ImGuiTreeNodeFlags_DefaultOpen)) if (ImGui::CollapsingHeader("Media", ImGuiTreeNodeFlags_DefaultOpen))
{ {
if (HasMedia()) if (m_reader.HasMedia())
{ {
const CDImage* media = m_reader.GetMedia(); const CDImage* media = m_reader.GetMedia();
const auto [disc_minute, disc_second, disc_frame] = media->GetMSFPositionOnDisc(); const auto [disc_minute, disc_second, disc_frame] = media->GetMSFPositionOnDisc();
@ -1986,9 +2004,9 @@ void CDROM::DrawDebugWindow()
if (ImGui::CollapsingHeader("Status/Mode", ImGuiTreeNodeFlags_DefaultOpen)) if (ImGui::CollapsingHeader("Status/Mode", ImGuiTreeNodeFlags_DefaultOpen))
{ {
static constexpr std::array<const char*, 11> drive_state_names = { static constexpr std::array<const char*, 12> drive_state_names = {
{"Idle", "Resetting", "Seeking (Physical)", "Seeking (Logical)", "Reading ID", "Reading TOC", "Reading", {"Idle", "Opening Shell", "Resetting", "Seeking (Physical)", "Seeking (Logical)", "Reading ID", "Reading TOC",
"Playing", "Pausing", "Stopping", "Changing Session"}}; "Reading", "Playing", "Pausing", "Stopping", "Changing Session"}};
ImGui::Columns(3); ImGui::Columns(3);

View File

@ -28,8 +28,9 @@ public:
void Reset(); void Reset();
bool DoState(StateWrapper& sw); bool DoState(StateWrapper& sw);
bool HasMedia() const; bool HasMedia() const { return m_reader.HasMedia(); }
std::string GetMediaFileName() const; std::string GetMediaFileName() const { return m_reader.GetMediaFileName(); }
void InsertMedia(std::unique_ptr<CDImage> media); void InsertMedia(std::unique_ptr<CDImage> media);
void RemoveMedia(bool force = false); void RemoveMedia(bool force = false);
@ -112,6 +113,7 @@ private:
enum class DriveState : u8 enum class DriveState : u8
{ {
Idle, Idle,
ShellOpening,
Resetting, Resetting,
SeekingPhysical, SeekingPhysical,
SeekingLogical, SeekingLogical,
@ -200,6 +202,7 @@ private:
{ {
return (m_drive_state == DriveState::SeekingLogical || m_drive_state == DriveState::SeekingPhysical); return (m_drive_state == DriveState::SeekingLogical || m_drive_state == DriveState::SeekingPhysical);
} }
bool CanReadMedia() const { return (m_drive_state != DriveState::ShellOpening && m_reader.HasMedia()); }
bool HasPendingCommand() const { return m_command != Command::None; } bool HasPendingCommand() const { return m_command != Command::None; }
bool HasPendingInterrupt() const { return m_interrupt_flag_register != 0; } bool HasPendingInterrupt() const { return m_interrupt_flag_register != 0; }
bool HasPendingAsyncInterrupt() const { return m_pending_async_interrupt != 0; } bool HasPendingAsyncInterrupt() const { return m_pending_async_interrupt != 0; }
@ -216,6 +219,7 @@ private:
TickCount GetAckDelayForCommand(Command command); TickCount GetAckDelayForCommand(Command command);
TickCount GetTicksForRead(); TickCount GetTicksForRead();
TickCount GetTicksForSeek(CDImage::LBA new_lba); TickCount GetTicksForSeek(CDImage::LBA new_lba);
TickCount GetTicksForStop(bool motor_was_on);
void BeginCommand(Command command); // also update status register void BeginCommand(Command command); // also update status register
void EndCommand(); // also updates status register void EndCommand(); // also updates status register
void AbortCommand(); void AbortCommand();
@ -225,6 +229,7 @@ private:
void ExecuteDrive(TickCount ticks_late); void ExecuteDrive(TickCount ticks_late);
void BeginReading(TickCount ticks_late = 0, bool after_seek = false); void BeginReading(TickCount ticks_late = 0, bool after_seek = false);
void BeginPlaying(u8 track_bcd, TickCount ticks_late = 0, bool after_seek = false); void BeginPlaying(u8 track_bcd, TickCount ticks_late = 0, bool after_seek = false);
void DoShellOpenComplete(TickCount ticks_late);
void DoResetComplete(TickCount ticks_late); void DoResetComplete(TickCount ticks_late);
void DoSeekComplete(TickCount ticks_late); void DoSeekComplete(TickCount ticks_late);
void DoPauseComplete(); void DoPauseComplete();

View File

@ -2,7 +2,7 @@
#include "types.h" #include "types.h"
static constexpr u32 SAVE_STATE_MAGIC = 0x43435544; static constexpr u32 SAVE_STATE_MAGIC = 0x43435544;
static constexpr u32 SAVE_STATE_VERSION = 34; static constexpr u32 SAVE_STATE_VERSION = 35;
#pragma pack(push, 4) #pragma pack(push, 4)
struct SAVE_STATE_HEADER struct SAVE_STATE_HEADER