From 8d0f52032b572bcd63e6da14d3d3ee571c88d08a Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Sun, 15 May 2022 23:22:08 +0200 Subject: [PATCH] IOS/DI: Fake the error 001 read when running DirectoryBlobs or Riivolution-patched games. --- Source/Core/Core/Config/SessionSettings.cpp | 2 ++ Source/Core/Core/Config/SessionSettings.h | 1 + Source/Core/Core/HW/DVD/DVDInterface.cpp | 25 ++++++++++++++ Source/Core/Core/HW/DVD/DVDInterface.h | 4 +++ Source/Core/Core/IOS/DI/DI.cpp | 36 +++++++++++++++++---- 5 files changed, 61 insertions(+), 7 deletions(-) diff --git a/Source/Core/Core/Config/SessionSettings.cpp b/Source/Core/Core/Config/SessionSettings.cpp index b2b08677b8..7ccd387360 100644 --- a/Source/Core/Core/Config/SessionSettings.cpp +++ b/Source/Core/Core/Config/SessionSettings.cpp @@ -15,4 +15,6 @@ const Info SESSION_GCI_FOLDER_CURRENT_GAME_ONLY{ {System::Session, "Core", "GCIFolderCurrentGameOnly"}, false}; const Info SESSION_CODE_SYNC_OVERRIDE{{System::Session, "Core", "CheatSyncOverride"}, false}; const Info SESSION_SAVE_DATA_WRITABLE{{System::Session, "Core", "SaveDataWritable"}, true}; +const Info SESSION_SHOULD_FAKE_ERROR_001{{System::Session, "Core", "ShouldFakeError001"}, + false}; } // namespace Config diff --git a/Source/Core/Core/Config/SessionSettings.h b/Source/Core/Core/Config/SessionSettings.h index 835a3b86a7..a81235c22d 100644 --- a/Source/Core/Core/Config/SessionSettings.h +++ b/Source/Core/Core/Config/SessionSettings.h @@ -13,4 +13,5 @@ extern const Info SESSION_LOAD_IPL_DUMP; extern const Info SESSION_GCI_FOLDER_CURRENT_GAME_ONLY; extern const Info SESSION_CODE_SYNC_OVERRIDE; extern const Info SESSION_SAVE_DATA_WRITABLE; +extern const Info SESSION_SHOULD_FAKE_ERROR_001; } // namespace Config diff --git a/Source/Core/Core/HW/DVD/DVDInterface.cpp b/Source/Core/Core/HW/DVD/DVDInterface.cpp index 338a3b009c..bbd1d27e3e 100644 --- a/Source/Core/Core/HW/DVD/DVDInterface.cpp +++ b/Source/Core/Core/HW/DVD/DVDInterface.cpp @@ -19,6 +19,7 @@ #include "Common/Logging/Log.h" #include "Core/Config/MainSettings.h" +#include "Core/Config/SessionSettings.h" #include "Core/CoreTiming.h" #include "Core/DolphinAnalytics.h" #include "Core/HW/AudioInterface.h" @@ -462,6 +463,14 @@ void SetDisc(std::unique_ptr disc, WARN_LOG_FMT(DVDINTERFACE, "Unknown disc size, guessing {0} bytes", s_disc_end_offset); const DiscIO::BlobReader& blob = disc->GetBlobReader(); + + // DirectoryBlobs (including Riivolution-patched discs) may end up larger than a real physical + // Wii disc, which triggers Error #001. In those cases we manually make the check succeed to + // avoid problems. + const bool should_fake_error_001 = + SConfig::GetInstance().bWii && blob.GetBlobType() == DiscIO::BlobType::DIRECTORY; + Config::SetCurrent(Config::SESSION_SHOULD_FAKE_ERROR_001, should_fake_error_001); + if (!blob.HasFastRandomAccessInBlock() && blob.GetBlockSize() > 0x200000) { OSD::AddMessage("You are running a disc image with a very large block size.", 60000); @@ -1263,6 +1272,22 @@ void PerformDecryptingRead(u32 position, u32 length, u32 output_address, } } +void ForceOutOfBoundsRead(ReplyType reply_type) +{ + INFO_LOG_FMT(DVDINTERFACE, "Forcing an out-of-bounds disc read."); + + if (s_drive_state == DriveState::ReadyNoReadsMade) + SetDriveState(DriveState::Ready); + + SetDriveError(DriveError::BlockOOB); + + // TODO: Needs testing to determine if MINIMUM_COMMAND_LATENCY_US is accurate for this + const DIInterruptType interrupt_type = DIInterruptType::DEINT; + CoreTiming::ScheduleEvent( + MINIMUM_COMMAND_LATENCY_US * (SystemTimers::GetTicksPerSecond() / 1000000), + s_finish_executing_command, PackFinishExecutingCommandUserdata(reply_type, interrupt_type)); +} + void AudioBufferConfig(bool enable_dtk, u8 dtk_buffer_length) { s_enable_dtk = enable_dtk; diff --git a/Source/Core/Core/HW/DVD/DVDInterface.h b/Source/Core/Core/HW/DVD/DVDInterface.h index b7cc321e8b..80dbbc5f1b 100644 --- a/Source/Core/Core/HW/DVD/DVDInterface.h +++ b/Source/Core/Core/HW/DVD/DVDInterface.h @@ -134,6 +134,10 @@ bool UpdateRunningGameMetadata(std::optional title_id = {}); void ExecuteCommand(ReplyType reply_type); void PerformDecryptingRead(u32 position, u32 length, u32 output_address, const DiscIO::Partition& partition, ReplyType reply_type); + +// For circumventing Error #001 in DirectoryBlobs, which may have data in the offsets being checked. +void ForceOutOfBoundsRead(ReplyType reply_type); + // Exposed for use by emulated BS2; does not perform any checks on drive state void AudioBufferConfig(bool enable_dtk, u8 dtk_buffer_length); diff --git a/Source/Core/Core/IOS/DI/DI.cpp b/Source/Core/Core/IOS/DI/DI.cpp index a04d18726a..532aea061e 100644 --- a/Source/Core/Core/IOS/DI/DI.cpp +++ b/Source/Core/Core/IOS/DI/DI.cpp @@ -11,6 +11,7 @@ #include "Common/CommonTypes.h" #include "Common/Logging/Log.h" #include "Common/MsgHandler.h" +#include "Core/Config/SessionSettings.h" #include "Core/CoreTiming.h" #include "Core/DolphinAnalytics.h" #include "Core/HW/DVD/DVDInterface.h" @@ -132,6 +133,16 @@ std::optional DIDevice::WriteIfFits(const IOCtlRequest& requ } } +namespace +{ +struct DiscRange +{ + u32 start; + u32 end; + bool is_error_001_range; +}; +} // namespace + std::optional DIDevice::StartIOCtl(const IOCtlRequest& request) { if (request.buffer_in_size != 0x20) @@ -315,20 +326,31 @@ std::optional DIDevice::StartIOCtl(const IOCtlRequest& reque // (start, end) as 32-bit offsets // Older IOS versions only accept the first range. Later versions added the extra ranges to // check how the drive responds to out of bounds reads (an error 001 check). - constexpr std::array, 3> valid_ranges = { - std::make_pair(0, 0x14000), // "System area" - std::make_pair(0x460A0000, 0x460A0008), - std::make_pair(0x7ED40000, 0x7ED40008), + constexpr std::array valid_ranges = { + DiscRange{0, 0x14000, false}, // "System area" + DiscRange{0x460A0000, 0x460A0008, true}, + DiscRange{0x7ED40000, 0x7ED40008, true}, }; for (auto range : valid_ranges) { - if (range.first <= position && position <= range.second && range.first <= end && - end <= range.second) + if (range.start <= position && position <= range.end && range.start <= end && + end <= range.end) { DICMDBUF0 = 0xA8000000; DICMDBUF1 = position; DICMDBUF2 = length; - return StartDMATransfer(length, request); + if (range.is_error_001_range && Config::Get(Config::SESSION_SHOULD_FAKE_ERROR_001)) + { + DIMAR = request.buffer_out; + m_last_length = length; + DILENGTH = length; + DVDInterface::ForceOutOfBoundsRead(DVDInterface::ReplyType::IOS); + return {}; + } + else + { + return StartDMATransfer(length, request); + } } } WARN_LOG_FMT(IOS_DI, "DVDLowUnencryptedRead: trying to read from an illegal region!");