diff --git a/Source/Core/Core/Boot/Boot.cpp b/Source/Core/Core/Boot/Boot.cpp index 179afdaa92..a4f85208e3 100644 --- a/Source/Core/Core/Boot/Boot.cpp +++ b/Source/Core/Core/Boot/Boot.cpp @@ -244,6 +244,17 @@ bool CBoot::DVDRead(const DiscIO::VolumeDisc& disc, u64 dvd_offset, u32 output_a return true; } +bool CBoot::DVDReadDiscID(const DiscIO::VolumeDisc& disc, u32 output_address) +{ + std::array buffer; + if (!disc.Read(0, buffer.size(), buffer.data(), DiscIO::PARTITION_NONE)) + return false; + Memory::CopyToEmu(output_address, buffer.data(), buffer.size()); + // Clear ERROR_NO_DISKID_L, probably should check if that's currently set + DVDInterface::SetLowError(DVDInterface::ERROR_READY); + return true; +} + void CBoot::UpdateDebugger_MapLoaded() { Host_NotifyMapLoaded(); diff --git a/Source/Core/Core/Boot/Boot.h b/Source/Core/Core/Boot/Boot.h index c34e1738fe..48462c768d 100644 --- a/Source/Core/Core/Boot/Boot.h +++ b/Source/Core/Core/Boot/Boot.h @@ -104,6 +104,7 @@ public: private: static bool DVDRead(const DiscIO::VolumeDisc& disc, u64 dvd_offset, u32 output_address, u32 length, const DiscIO::Partition& partition); + static bool DVDReadDiscID(const DiscIO::VolumeDisc& disc, u32 output_address); static void RunFunction(u32 address); static void UpdateDebugger_MapLoaded(); diff --git a/Source/Core/Core/Boot/Boot_BS2Emu.cpp b/Source/Core/Core/Boot/Boot_BS2Emu.cpp index b265473dd7..25efebb12b 100644 --- a/Source/Core/Core/Boot/Boot_BS2Emu.cpp +++ b/Source/Core/Core/Boot/Boot_BS2Emu.cpp @@ -210,7 +210,7 @@ bool CBoot::EmulatedBS2_GC(const DiscIO::VolumeDisc& volume) SetupGCMemory(); - DVDRead(volume, /*offset*/ 0x00000000, /*address*/ 0x00000000, 0x20, DiscIO::PARTITION_NONE); + DVDReadDiscID(volume, 0x00000000); const bool ntsc = DiscIO::IsNTSC(SConfig::GetInstance().m_region); @@ -406,7 +406,7 @@ bool CBoot::EmulatedBS2_Wii(const DiscIO::VolumeDisc& volume) if (!SetupWiiMemory(console_type) || !IOS::HLE::GetIOS()->BootIOS(tmd.GetIOSId())) return false; - DVDRead(volume, 0x00000000, 0x00000000, 0x20, DiscIO::PARTITION_NONE); // Game Code + DVDReadDiscID(volume, 0x00000000); // This is some kind of consistency check that is compared to the 0x00 // values as the game boots. This location keeps the 4 byte ID for as long diff --git a/Source/Core/Core/HW/DVD/DVDInterface.cpp b/Source/Core/Core/HW/DVD/DVDInterface.cpp index 5c6ab9c9c8..9f54563fb7 100644 --- a/Source/Core/Core/HW/DVD/DVDInterface.cpp +++ b/Source/Core/Core/HW/DVD/DVDInterface.cpp @@ -344,8 +344,10 @@ void Init() } // This doesn't reset any inserted disc or the cover state. -void Reset() +void Reset(bool spinup) { + INFO_LOG(DVDINTERFACE, "Reset %s spinup", spinup ? "with" : "without"); + s_DISR.Hex = 0; s_DICMDBUF[0] = 0; s_DICMDBUF[1] = 0; @@ -366,15 +368,33 @@ void Reset() s_current_length = 0; s_pending_samples = 0; - s_error_code = 0; + if (!IsDiscInside()) + { + // ERROR_COVER is used when the cover is open; + // ERROR_NO_DISK_L is used when the cover is closed but there is no disc. + // On the Wii, this can only happen if something other than a DVD is inserted into the disc + // drive (for instance, an audio CD) and only after it attempts to read it. Otherwise, it will + // report the cover as opened. + SetLowError(ERROR_COVER); + } + else if (!spinup) + { + // Wii hardware tests indicate that this is used when ejecting and inserting a new disc, or + // performing a reset without spinup. + SetLowError(ERROR_CHANGE_DISK); + } + else + { + SetLowError(ERROR_NO_DISKID_L); + } + + SetHighError(ERROR_NONE); // The buffer is empty at start s_read_buffer_start_offset = 0; s_read_buffer_end_offset = 0; s_read_buffer_start_time = 0; s_read_buffer_end_time = 0; - - s_disc_path_to_insert.clear(); } void Shutdown() @@ -405,6 +425,8 @@ void SetDisc(std::unique_ptr disc, DVDThread::SetDisc(std::move(disc)); SetLidOpen(); + + Reset(false); } bool IsDiscInside() @@ -658,15 +680,46 @@ void ClearInterrupt(DIInterruptType interrupt) } } +// Checks the drive state to make sure a read-like command can be performed. +// If false is returned, SetHighError will have been called, and the caller +// should issue a DEINT interrupt. +static bool CheckReadPreconditions() +{ + if (!IsDiscInside()) // Implies ERROR_COVER or ERROR_NO_DISK + { + ERROR_LOG(DVDINTERFACE, "No disc inside."); + SetHighError(ERROR_NO_DISK_H); + return false; + } + if ((s_error_code & LOW_ERROR_MASK) == ERROR_CHANGE_DISK) + { + ERROR_LOG(DVDINTERFACE, "Disc changed (motor stopped)."); + SetHighError(ERROR_MEDIUM); + return false; + } + if ((s_error_code & LOW_ERROR_MASK) == ERROR_MOTOR_STOP_L) + { + ERROR_LOG(DVDINTERFACE, "Motor stopped."); + SetHighError(ERROR_MOTOR_STOP_H); + return false; + } + if ((s_error_code & LOW_ERROR_MASK) == ERROR_NO_DISKID_L) + { + ERROR_LOG(DVDINTERFACE, "Disc id not read."); + SetHighError(ERROR_NO_DISKID_H); + return false; + } + return true; +} + // 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, const DiscIO::Partition& partition, ReplyType reply_type, DIInterruptType* interrupt_type) { - if (!IsDiscInside()) + if (!CheckReadPreconditions()) { // Disc read fails - SetHighError(ERROR_NO_DISK_H); *interrupt_type = DIInterruptType::DEINT; return false; } @@ -747,6 +800,14 @@ void ExecuteCommand(ReplyType reply_type) case 0x40: // Read DiscID INFO_LOG(DVDINTERFACE, "Read DiscID: buffer %08x", s_DIMAR); + // TODO: It doesn't make sense to include ERROR_CHANGE_DISK here, as it implies that the drive + // is not spinning and reading the disc ID shouldn't change it. However, the Wii Menu breaks + // without it. + if ((s_error_code & LOW_ERROR_MASK) == ERROR_NO_DISKID_L || + (s_error_code & LOW_ERROR_MASK) == ERROR_CHANGE_DISK) + { + SetLowError(ERROR_READY); + } command_handled_by_thread = ExecuteReadCommand( 0, s_DIMAR, 0x20, s_DILENGTH, DiscIO::PARTITION_NONE, reply_type, &interrupt_type); break; @@ -905,10 +966,12 @@ void ExecuteCommand(ReplyType reply_type) // Used by both GC and Wii case DICommand::StopMotor: { - INFO_LOG(DVDINTERFACE, "DVDLowStopMotor %s %s", s_DICMDBUF[1] ? "eject" : "", - s_DICMDBUF[2] ? "kill!" : ""); + const bool eject = (s_DICMDBUF[0] & (1 << 17)); + const bool kill = (s_DICMDBUF[0] & (1 << 20)); + INFO_LOG(DVDINTERFACE, "DVDLowStopMotor%s%s", eject ? " eject" : "", kill ? " kill!" : ""); - const bool force_eject = s_DICMDBUF[1] && !s_DICMDBUF[2]; + SetLowError(ERROR_MOTOR_STOP_L); + const bool force_eject = eject && !kill; if (Config::Get(Config::MAIN_AUTO_DISC_CHANGE) && !Movie::IsPlayingInput() && DVDThread::IsInsertedDiscRunning() && !s_auto_disc_change_paths.empty()) diff --git a/Source/Core/Core/HW/DVD/DVDInterface.h b/Source/Core/Core/HW/DVD/DVDInterface.h index 6007359e83..37a7bbf8d8 100644 --- a/Source/Core/Core/HW/DVD/DVDInterface.h +++ b/Source/Core/Core/HW/DVD/DVDInterface.h @@ -102,7 +102,7 @@ enum class EjectCause }; void Init(); -void Reset(); +void Reset(bool spinup = true); void Shutdown(); void DoState(PointerWrap& p); diff --git a/Source/Core/Core/IOS/DI/DI.cpp b/Source/Core/Core/IOS/DI/DI.cpp index f8e6514992..e4eeff686a 100644 --- a/Source/Core/Core/IOS/DI/DI.cpp +++ b/Source/Core/Core/IOS/DI/DI.cpp @@ -249,7 +249,7 @@ std::optional DI::StartIOCtl(const IOCtlRequest& request) { const bool spinup = Memory::Read_U32(request.address + 4); INFO_LOG(IOS_DI, "DVDLowReset %s spinup", spinup ? "with" : "without"); - DVDInterface::Reset(); + DVDInterface::Reset(spinup); ResetDIRegisters(); // Should also reset current partition and such return DIResult::Success;