CDROM: Add manual control of lid state

In debug menu, deliberately hidden from the noobs.
pull/3750/head
Stenzek 1 month ago
parent 8ac294d4f9
commit 2c6ac7d4f0
No known key found for this signature in database

@ -85,6 +85,13 @@ enum : u32
DOUBLE_SPEED_SECTORS_PER_SECOND = 150, // 2X speed is 150 sectors per second.
};
enum ManualLidControl : u8
{
MANUAL_LID_CONTROL_DISABLED = 0x00,
MANUAL_LID_CONTROL_ENABLED = 0x01,
MANUAL_LID_CONTROL_OPEN = 0x02,
};
static constexpr u8 INTERRUPT_REGISTER_MASK = 0x1F;
static constexpr TickCount MIN_SEEK_TICKS = 30000;
@ -181,6 +188,7 @@ enum StatBits : u8
enum ErrorReason : u8
{
ERROR_REASON_SHELL_OPEN = 0x08,
ERROR_REASON_INVALID_ARGUMENT = 0x10,
ERROR_REASON_INCORRECT_NUMBER_OF_PARAMETERS = 0x20,
ERROR_REASON_INVALID_COMMAND = 0x40,
@ -416,6 +424,7 @@ struct CDROMState
Command command_second_response = Command::None;
DriveState drive_state = DriveState::Idle;
DiscRegion disc_region = DiscRegion::NonPS1;
ManualLidControl manual_lid_control = MANUAL_LID_CONTROL_DISABLED;
StatusRegister status = {};
@ -555,6 +564,7 @@ static std::array<CommandInfo, 255> s_command_info = {{
void CDROM::Initialize()
{
s_state.disc_region = DiscRegion::NonPS1;
s_state.manual_lid_control = MANUAL_LID_CONTROL_DISABLED;
if (g_settings.cdrom_readahead_sectors > 0)
s_reader.StartThread(g_settings.cdrom_readahead_sectors);
@ -585,7 +595,9 @@ void CDROM::Reset()
s_state.status.bits = 0;
s_state.secondary_status.bits = 0;
s_state.secondary_status.motor_on = CanReadMedia();
s_state.secondary_status.shell_open = !CanReadMedia();
s_state.secondary_status.shell_open = (s_state.manual_lid_control & MANUAL_LID_CONTROL_ENABLED) ?
(s_state.manual_lid_control & MANUAL_LID_CONTROL_OPEN) :
!CanReadMedia();
s_state.mode.bits = 0;
s_state.mode.read_raw_sector = true;
s_state.interrupt_enable_register = INTERRUPT_REGISTER_MASK;
@ -639,7 +651,9 @@ TickCount CDROM::SoftReset()
ClearDriveState();
s_state.secondary_status.bits = 0;
s_state.secondary_status.motor_on = CanReadMedia();
s_state.secondary_status.shell_open = !CanReadMedia();
s_state.secondary_status.shell_open = (s_state.manual_lid_control & MANUAL_LID_CONTROL_ENABLED) ?
(s_state.manual_lid_control & MANUAL_LID_CONTROL_OPEN) :
!CanReadMedia();
s_state.mode.bits = 0;
s_state.mode.read_raw_sector = true;
s_state.request_register.bits = 0;
@ -935,7 +949,8 @@ bool CDROM::IsReadingOrPlaying()
bool CDROM::CanReadMedia()
{
return (s_state.drive_state != DriveState::ShellOpening && s_reader.HasMedia());
return (s_state.drive_state != DriveState::ShellOpening &&
s_state.manual_lid_control != (MANUAL_LID_CONTROL_ENABLED | MANUAL_LID_CONTROL_OPEN) && s_reader.HasMedia());
}
bool CDROM::InsertMedia(std::unique_ptr<CDImage>& media, DiscRegion region, std::string_view serial,
@ -961,7 +976,7 @@ bool CDROM::InsertMedia(std::unique_ptr<CDImage>& media, DiscRegion region, std:
SetHoldPosition(0, 0);
// motor automatically spins up
if (s_state.drive_state != DriveState::ShellOpening)
if (s_state.drive_state != DriveState::ShellOpening && !(s_state.manual_lid_control & MANUAL_LID_CONTROL_OPEN))
StartMotor();
if (s_state.show_current_file)
@ -1002,10 +1017,10 @@ std::unique_ptr<CDImage> CDROM::RemoveMedia(bool for_disc_swap)
// The console sends an interrupt when the shell is opened regardless of whether a command was executing.
ClearAsyncInterrupt();
SendAsyncErrorResponse(STAT_ERROR, 0x08);
SendAsyncErrorResponse(STAT_ERROR, ERROR_REASON_SHELL_OPEN);
// Begin spin-down timer, we can't swap the new disc in immediately for some games (e.g. Metal Gear Solid).
if (for_disc_swap)
if (for_disc_swap && !s_state.manual_lid_control)
{
s_state.drive_state = DriveState::ShellOpening;
s_state.drive_event.SetIntervalAndSchedule(stop_ticks);
@ -1014,6 +1029,35 @@ std::unique_ptr<CDImage> CDROM::RemoveMedia(bool for_disc_swap)
return image;
}
void CDROM::SetLidState(bool manual_control, bool manual_state)
{
INFO_LOG("Setting lid state: manual_control={}, manual_state={}", manual_control, manual_state);
const bool current_state = s_state.secondary_status.shell_open;
s_state.manual_lid_control =
manual_control ?
static_cast<ManualLidControl>(MANUAL_LID_CONTROL_ENABLED | (manual_state ? MANUAL_LID_CONTROL_OPEN : 0)) :
MANUAL_LID_CONTROL_DISABLED;
// Set shell open bit when manually opened, and start motor when closed.
if (!CanReadMedia())
s_state.secondary_status.shell_open = true;
else if (!s_state.secondary_status.motor_on)
StartMotor();
if (current_state == s_state.secondary_status.shell_open)
return;
// Notify when opened.
if (s_state.secondary_status.shell_open)
{
if (IsReadingOrPlaying())
StopReadingWithError(ERROR_REASON_SHELL_OPEN);
else
SendAsyncErrorResponse(STAT_ERROR, ERROR_REASON_SHELL_OPEN);
}
}
bool CDROM::PrecacheMedia()
{
if (!s_reader.HasMedia())
@ -1865,9 +1909,13 @@ void CDROM::ExecuteCommand(void*, TickCount ticks)
// if bit 0 or 2 is set, send an additional byte
SendACKAndStat();
// shell open bit is cleared after sending the status
if (CanReadMedia())
// shell open bit is cleared after sending the status and door is closed
if ((s_state.manual_lid_control & MANUAL_LID_CONTROL_ENABLED) ?
!(s_state.manual_lid_control & MANUAL_LID_CONTROL_OPEN) :
CanReadMedia())
{
s_state.secondary_status.shell_open = false;
}
EndCommand();
return;
@ -4257,6 +4305,12 @@ void CDROM::DrawDebugWindow(float scale)
ImGui::NextColumn();
ImGui::TextColored(s_state.secondary_status.shell_open ? active_color : inactive_color, "Shell Open: %s",
s_state.secondary_status.shell_open ? "Yes" : "No");
if (s_state.manual_lid_control & MANUAL_LID_CONTROL_ENABLED)
{
const bool open = (s_state.manual_lid_control & MANUAL_LID_CONTROL_OPEN);
ImGui::SameLine();
ImGui::TextColored(open ? active_color : inactive_color, "Lid: %s", open ? "Open" : "Closed");
}
ImGui::NextColumn();
ImGui::TextColored(s_state.mode.ignore_bit ? active_color : inactive_color, "Ignore Bit: %s",
s_state.mode.ignore_bit ? "Yes" : "No");

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-FileCopyrightText: 2019-2026 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#pragma once
@ -36,6 +36,9 @@ std::unique_ptr<CDImage> RemoveMedia(bool for_disc_swap);
bool PrecacheMedia();
bool HasNonStandardOrReplacementSubQ();
// nullopt = automatic
void SetLidState(bool manual_control, bool manual_state);
void CPUClockChanged();
// I/O

@ -584,6 +584,7 @@ void MainWindow::onSystemStarting()
s_locals.system_starting = true;
s_locals.system_valid = false;
s_locals.system_paused = false;
m_ui.actionCDROMLidStateAutomatic->setChecked(true);
updateLogWidget();
switchToEmulationView();
@ -2240,6 +2241,8 @@ void MainWindow::updateEmulationActions()
m_ui.actionMemoryScanner->setDisabled(achievements_hardcore_mode);
m_ui.actionFreeCamera->setDisabled(achievements_hardcore_mode);
m_ui.actionReloadTextureReplacements->setDisabled(starting_or_not_running);
m_ui.menuCDROMLidState->setDisabled(starting_or_not_running);
m_ui.actionCDROMLidState->setDisabled(starting_or_not_running);
m_ui.actionDumpRAM->setDisabled(starting_or_not_running || achievements_hardcore_mode);
m_ui.actionDumpVRAM->setDisabled(starting_or_not_running || achievements_hardcore_mode);
m_ui.actionDumpSPURAM->setDisabled(starting_or_not_running || achievements_hardcore_mode);
@ -2666,6 +2669,9 @@ void MainWindow::connectSignals()
&Settings::GetLogLevelName, &Settings::GetLogLevelDisplayName,
Log::DEFAULT_LOG_LEVEL, Log::Level::MaxCount);
connect(m_ui.menuLogChannels, &QMenu::aboutToShow, this, &MainWindow::onDebugLogChannelsMenuAboutToShow);
connect(m_ui.actionCDROMLidStateAutomatic, &QAction::triggered, this, &MainWindow::onDebugCDROMLidStateChanged);
connect(m_ui.actionCDROMLidStateOpen, &QAction::triggered, this, &MainWindow::onDebugCDROMLidStateChanged);
connect(m_ui.actionCDROMLidStateClosed, &QAction::triggered, this, &MainWindow::onDebugCDROMLidStateChanged);
SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, m_ui.actionLogToSystemConsole, "Logging", "LogToConsole",
false);
SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, m_ui.actionLogToFile, "Logging", "LogToFile", false);
@ -3582,6 +3588,13 @@ void MainWindow::onDebugLogChannelsMenuAboutToShow()
LogWindow::populateFilterMenu(m_ui.menuLogChannels);
}
void MainWindow::onDebugCDROMLidStateChanged()
{
const bool manual_control = !m_ui.actionCDROMLidStateAutomatic->isChecked();
const bool manual_open = (manual_control && m_ui.actionCDROMLidStateOpen->isChecked());
g_core_thread->setLidState(manual_control, manual_open);
}
MainWindow::SystemLock MainWindow::pauseAndLockSystem()
{
// To switch out of fullscreen when displaying a popup, or not to?

@ -316,6 +316,7 @@ private:
void onGameListSortIndicatorOrderChanged(int column, Qt::SortOrder order);
void onDebugLogChannelsMenuAboutToShow();
void onDebugCDROMLidStateChanged();
void openCPUDebugger();
Ui::MainWindow m_ui;

@ -157,9 +157,18 @@
<string>Log Channels</string>
</property>
</widget>
<widget class="QMenu" name="menuCDROMLidState">
<property name="title">
<string>CD-ROM Lid Control</string>
</property>
<addaction name="actionCDROMLidStateAutomatic"/>
<addaction name="actionCDROMLidStateClosed"/>
<addaction name="actionCDROMLidStateOpen"/>
</widget>
<addaction name="menuCPUExecutionMode"/>
<addaction name="menuRenderer"/>
<addaction name="menuCropMode"/>
<addaction name="menuCDROMLidState"/>
<addaction name="actionEnableSafeMode"/>
<addaction name="separator"/>
<addaction name="menuLogLevel"/>
@ -1542,6 +1551,35 @@
<string>System Log</string>
</property>
</action>
<actiongroup name="actionCDROMLidState">
<action name="actionCDROMLidStateAutomatic">
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
<property name="text">
<string>Automatic</string>
</property>
</action>
<action name="actionCDROMLidStateClosed">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Closed</string>
</property>
</action>
<action name="actionCDROMLidStateOpen">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Open</string>
</property>
</action>
</actiongroup>
</widget>
<resources>
<include location="resources/duckstation-qt.qrc"/>

@ -15,6 +15,7 @@
#include "core/achievements.h"
#include "core/bus.h"
#include "core/cdrom.h"
#include "core/cheats.h"
#include "core/core.h"
#include "core/core_private.h"
@ -1522,6 +1523,20 @@ void CoreThread::changeDiscFromPlaylist(quint32 index)
errorReported(tr("Error"), tr("Failed to switch to subimage %1").arg(index));
}
void CoreThread::setLidState(bool manual_control, bool manual_state)
{
if (!isCurrentThread())
{
QMetaObject::invokeMethod(this, &CoreThread::setLidState, Qt::QueuedConnection, manual_control, manual_state);
return;
}
if (!System::IsValid())
return;
CDROM::SetLidState(manual_control, manual_state);
}
void CoreThread::reloadCheats(bool reload_files, bool reload_enabled_list, bool verbose, bool verbose_if_changed)
{
if (!isCurrentThread())

@ -153,6 +153,7 @@ public:
void setSystemPaused(bool paused);
void changeDisc(const QString& new_disc_path, bool reset_system, bool check_memcard_busy);
void changeDiscFromPlaylist(quint32 index);
void setLidState(bool manual_control, bool manual_state);
void loadState(const QString& path);
void loadState(bool global, qint32 slot);
void saveState(const QString& path);

Loading…
Cancel
Save