From a695b05b21f3fb3b5dfc680f00f95a873c51dd13 Mon Sep 17 00:00:00 2001 From: Pokechu22 Date: Tue, 3 Dec 2019 19:15:30 -0800 Subject: [PATCH 01/17] Add support for std::optional to PointerWrap --- Source/Core/Common/ChunkFile.h | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/Source/Core/Common/ChunkFile.h b/Source/Core/Common/ChunkFile.h index aef3a2ec4a..d947339fcd 100644 --- a/Source/Core/Common/ChunkFile.h +++ b/Source/Core/Common/ChunkFile.h @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -144,6 +145,36 @@ public: Do(x.second); } + template + void Do(std::optional& x) + { + bool present = x.has_value(); + Do(present); + + switch (mode) + { + case MODE_READ: + if (present) + { + x = std::make_optional(); + Do(x.value()); + } + else + { + x = std::nullopt; + } + break; + + case MODE_WRITE: + case MODE_MEASURE: + case MODE_VERIFY: + if (present) + Do(x.value()); + + break; + } + } + template void DoArray(std::array& x) { From 77189e74cd6d86d6e9dd4098968533ec82a56525 Mon Sep 17 00:00:00 2001 From: Pokechu22 Date: Wed, 21 Aug 2019 13:05:21 -0700 Subject: [PATCH 02/17] Implement Broadway GPIOs SLOT_LED and the AVE ones are not implemented yet, but the other Broadway ones are. --- Source/Core/Common/BitUtils.h | 41 +++++++++++++++++++++++ Source/Core/Core/HW/WII_IPC.cpp | 36 ++++++++++++++++---- Source/Core/Core/HW/WII_IPC.h | 31 +++++++++++++++++ Source/Core/Core/HW/WiimoteEmu/Camera.cpp | 35 ++++++++++++------- 4 files changed, 123 insertions(+), 20 deletions(-) diff --git a/Source/Core/Common/BitUtils.h b/Source/Core/Common/BitUtils.h index 79c1a7799e..538718f3aa 100644 --- a/Source/Core/Common/BitUtils.h +++ b/Source/Core/Common/BitUtils.h @@ -8,6 +8,7 @@ #include #include #include +#include #include namespace Common @@ -299,4 +300,44 @@ void SetBit(T& value, size_t bit_number, bool bit_value) value &= ~(T{1} << bit_number); } +template +class FlagBit +{ +public: + FlagBit(std::underlying_type_t& bits, T bit) : m_bits(bits), m_bit(bit) {} + explicit operator bool() const + { + return (m_bits & static_cast>(m_bit)) != 0; + } + FlagBit& operator=(const bool rhs) + { + if (rhs) + m_bits |= static_cast>(m_bit); + else + m_bits &= ~static_cast>(m_bit); + return *this; + } + +private: + std::underlying_type_t& m_bits; + T m_bit; +}; + +template +class Flags +{ +public: + constexpr Flags() = default; + constexpr Flags(std::initializer_list bits) + { + for (auto bit : bits) + { + m_hex |= static_cast>(bit); + } + } + FlagBit operator[](T bit) { return FlagBit(m_hex, bit); } + + std::underlying_type_t m_hex = 0; +}; + } // namespace Common diff --git a/Source/Core/Core/HW/WII_IPC.cpp b/Source/Core/Core/HW/WII_IPC.cpp index d231adcfd2..e6a48cb31b 100644 --- a/Source/Core/Core/HW/WII_IPC.cpp +++ b/Source/Core/Core/HW/WII_IPC.cpp @@ -8,6 +8,7 @@ #include "Common/CommonTypes.h" #include "Common/Logging/Log.h" #include "Core/CoreTiming.h" +#include "Core/HW/DVD/DVDInterface.h" #include "Core/HW/MMIO.h" #include "Core/HW/ProcessorInterface.h" #include "Core/IOS/IOS.h" @@ -97,7 +98,11 @@ static u32 ppc_irq_masks; static u32 arm_irq_flags; static u32 arm_irq_masks; -static u32 sensorbar_power; // do we need to care about this? +// Indicates which pins are accessible by broadway. Writable by starlet only. +static constexpr Common::Flags gpio_owner = {GPIO::SLOT_LED, GPIO::SLOT_IN, GPIO::SENSOR_BAR, + GPIO::DO_EJECT, GPIO::AVE_SCL, GPIO::AVE_SDA}; +static Common::Flags gpio_dir; +Common::Flags g_gpio_out; static CoreTiming::EventType* updateInterrupts; static void UpdateInterrupts(u64 = 0, s64 cyclesLate = 0); @@ -111,7 +116,7 @@ void DoState(PointerWrap& p) p.Do(ppc_irq_masks); p.Do(arm_irq_flags); p.Do(arm_irq_masks); - p.Do(sensorbar_power); + p.Do(g_gpio_out); } static void InitState() @@ -125,7 +130,9 @@ static void InitState() arm_irq_flags = 0; arm_irq_masks = 0; - sensorbar_power = 0; + // The only input broadway has is SLOT_IN; all the others it has access to are outputs + gpio_dir = {GPIO::SLOT_LED, GPIO::SENSOR_BAR, GPIO::DO_EJECT, GPIO::AVE_SCL, GPIO::AVE_SDA}; + g_gpio_out = {}; ppc_irq_masks |= INT_CAUSE_IPC_BROADWAY; } @@ -181,14 +188,29 @@ void RegisterMMIO(MMIO::Mapping* mmio, u32 base) CoreTiming::ScheduleEvent(0, updateInterrupts, 0); })); - mmio->Register(base | GPIOB_OUT, MMIO::Constant(0), - MMIO::DirectWrite(&sensorbar_power)); + mmio->Register(base | GPIOB_OUT, MMIO::DirectRead(&g_gpio_out.m_hex), + MMIO::ComplexWrite([](u32, u32 val) { + g_gpio_out.m_hex = val & gpio_owner.m_hex; + if (g_gpio_out[GPIO::DO_EJECT]) + { + INFO_LOG(WII_IPC, "Ejecting disc due to GPIO write"); + DVDInterface::EjectDisc(); + } + // SENSOR_BAR is checked by WiimoteEmu::CameraLogic + // TODO: AVE, SLOT_LED + })); + mmio->Register(base | GPIOB_DIR, MMIO::DirectRead(&gpio_dir.m_hex), + MMIO::DirectWrite(&gpio_dir.m_hex)); + mmio->Register(base | GPIOB_IN, MMIO::ComplexRead([](u32) { + Common::Flags gpio_in; + gpio_in[GPIO::SLOT_IN] = DVDInterface::IsDiscInside(); + return gpio_in.m_hex; + }), + MMIO::Nop()); // Register some stubbed/unknown MMIOs required to make Wii games work. mmio->Register(base | PPCSPEED, MMIO::InvalidRead(), MMIO::Nop()); mmio->Register(base | VISOLID, MMIO::InvalidRead(), MMIO::Nop()); - mmio->Register(base | GPIOB_DIR, MMIO::Constant(0), MMIO::Nop()); - mmio->Register(base | GPIOB_IN, MMIO::Constant(0), MMIO::Nop()); mmio->Register(base | UNK_180, MMIO::Constant(0), MMIO::Nop()); mmio->Register(base | UNK_1CC, MMIO::Constant(0), MMIO::Nop()); mmio->Register(base | UNK_1D0, MMIO::Constant(0), MMIO::Nop()); diff --git a/Source/Core/Core/HW/WII_IPC.h b/Source/Core/Core/HW/WII_IPC.h index 34419b8988..f13d824f26 100644 --- a/Source/Core/Core/HW/WII_IPC.h +++ b/Source/Core/Core/HW/WII_IPC.h @@ -4,6 +4,7 @@ #pragma once +#include "Common/BitUtils.h" #include "Common/CommonTypes.h" class PointerWrap; @@ -35,6 +36,36 @@ enum StarletInterruptCause INT_CAUSE_IPC_STARLET = 0x80000000 }; +enum class GPIO : u32 +{ + POWER = 0x1, + SHUTDOWN = 0x2, + FAN = 0x4, + DC_DC = 0x8, + DI_SPIN = 0x10, + SLOT_LED = 0x20, + EJECT_BTN = 0x40, + SLOT_IN = 0x80, + SENSOR_BAR = 0x100, + DO_EJECT = 0x200, + EEP_CS = 0x400, + EEP_CLK = 0x800, + EEP_MOSI = 0x1000, + EEP_MISO = 0x2000, + AVE_SCL = 0x4000, + AVE_SDA = 0x8000, + DEBUG0 = 0x10000, + DEBUG1 = 0x20000, + DEBUG2 = 0x40000, + DEBUG3 = 0x80000, + DEBUG4 = 0x100000, + DEBUG5 = 0x200000, + DEBUG6 = 0x400000, + DEBUG7 = 0x800000, +}; + +extern Common::Flags g_gpio_out; + void Init(); void Reset(); void Shutdown(); diff --git a/Source/Core/Core/HW/WiimoteEmu/Camera.cpp b/Source/Core/Core/HW/WiimoteEmu/Camera.cpp index 10966ce2a2..25b542bc8a 100644 --- a/Source/Core/Core/HW/WiimoteEmu/Camera.cpp +++ b/Source/Core/Core/HW/WiimoteEmu/Camera.cpp @@ -12,6 +12,7 @@ #include "Common/MathUtil.h" #include "Common/Matrix.h" +#include "Core/HW/WII_IPC.h" #include "Core/HW/WiimoteCommon/WiimoteReport.h" namespace WiimoteEmu @@ -103,23 +104,31 @@ void CameraLogic::Update(const Common::Matrix44& transform) std::array camera_points; - std::transform(leds.begin(), leds.end(), camera_points.begin(), [&](const Vec3& v) { - const auto point = camera_view * Vec4(v, 1.0); + if (IOS::g_gpio_out[IOS::GPIO::SENSOR_BAR]) + { + std::transform(leds.begin(), leds.end(), camera_points.begin(), [&](const Vec3& v) { + const auto point = camera_view * Vec4(v, 1.0); - if (point.z > 0) - { - // FYI: Casting down vs. rounding seems to produce more symmetrical output. - const auto x = s32((1 - point.x / point.w) * CAMERA_WIDTH / 2); - const auto y = s32((1 - point.y / point.w) * CAMERA_HEIGHT / 2); + if (point.z > 0) + { + // FYI: Casting down vs. rounding seems to produce more symmetrical output. + const auto x = s32((1 - point.x / point.w) * CAMERA_WIDTH / 2); + const auto y = s32((1 - point.y / point.w) * CAMERA_HEIGHT / 2); - const auto point_size = std::lround(MAX_POINT_SIZE / point.w / 2); + const auto point_size = std::lround(MAX_POINT_SIZE / point.w / 2); - if (x >= 0 && y >= 0 && x < CAMERA_WIDTH && y < CAMERA_HEIGHT) - return CameraPoint{u16(x), u16(y), u8(point_size)}; - } + if (x >= 0 && y >= 0 && x < CAMERA_WIDTH && y < CAMERA_HEIGHT) + return CameraPoint{u16(x), u16(y), u8(point_size)}; + } - return INVISIBLE_POINT; - }); + return INVISIBLE_POINT; + }); + } + else + { + // Sensor bar is off + camera_points.fill(INVISIBLE_POINT); + } // IR data is read from offset 0x37 on real hardware auto& data = reg_data.camera_data; From 11bd132650b0f5af784677c022649f111c2091a5 Mon Sep 17 00:00:00 2001 From: Pokechu22 Date: Tue, 1 Oct 2019 10:44:14 -0700 Subject: [PATCH 03/17] Implement RTC flag, which is counter-intuitively disc drive related --- Source/Core/Core/Boot/Boot_BS2Emu.cpp | 4 ++++ Source/Core/Core/HW/DVD/DVDInterface.cpp | 17 +++++++++++++---- Source/Core/Core/HW/DVD/DVDInterface.h | 8 +++++++- Source/Core/Core/HW/EXI/EXI_DeviceIPL.cpp | 12 +++++++++--- Source/Core/Core/HW/EXI/EXI_DeviceIPL.h | 14 ++++++++++++++ Source/Core/Core/HW/WII_IPC.cpp | 2 +- Source/Core/DolphinQt/MainWindow.cpp | 2 +- 7 files changed, 49 insertions(+), 10 deletions(-) diff --git a/Source/Core/Core/Boot/Boot_BS2Emu.cpp b/Source/Core/Core/Boot/Boot_BS2Emu.cpp index d73884df51..b265473dd7 100644 --- a/Source/Core/Core/Boot/Boot_BS2Emu.cpp +++ b/Source/Core/Core/Boot/Boot_BS2Emu.cpp @@ -19,6 +19,7 @@ #include "Core/ConfigManager.h" #include "Core/Core.h" #include "Core/HLE/HLE.h" +#include "Core/HW/DVD/DVDInterface.h" #include "Core/HW/EXI/EXI_DeviceIPL.h" #include "Core/HW/Memmap.h" #include "Core/IOS/ES/ES.h" @@ -390,6 +391,9 @@ bool CBoot::EmulatedBS2_Wii(const DiscIO::VolumeDisc& volume) state->discstate = 0x01; }); + // The system menu clears the RTC flags + ExpansionInterface::g_rtc_flags.m_hex = 0; + // While reading a disc, the system menu reads the first partition table // (0x20 bytes from 0x00040020) and stores a pointer to the data partition entry. // When launching the disc game, it copies the partition type and offset to 0x3194 diff --git a/Source/Core/Core/HW/DVD/DVDInterface.cpp b/Source/Core/Core/HW/DVD/DVDInterface.cpp index 97344187e5..4900727f36 100644 --- a/Source/Core/Core/HW/DVD/DVDInterface.cpp +++ b/Source/Core/Core/HW/DVD/DVDInterface.cpp @@ -25,6 +25,7 @@ #include "Core/HW/AudioInterface.h" #include "Core/HW/DVD/DVDMath.h" #include "Core/HW/DVD/DVDThread.h" +#include "Core/HW/EXI/EXI_DeviceIPL.h" #include "Core/HW/MMIO.h" #include "Core/HW/Memmap.h" #include "Core/HW/ProcessorInterface.h" @@ -473,7 +474,9 @@ void Shutdown() void SetDisc(std::unique_ptr disc, std::optional> auto_disc_change_paths = {}) { - if (disc) + bool had_disc = IsDiscInside(); + bool has_disc = static_cast(disc); + if (has_disc) s_current_partition = disc->GetGamePartition(); if (auto_disc_change_paths) @@ -485,6 +488,10 @@ void SetDisc(std::unique_ptr disc, s_auto_disc_change_index = 0; } + // Assume that inserting a disc requires having an empty disc before + if (had_disc != has_disc) + ExpansionInterface::g_rtc_flags[ExpansionInterface::RTCFlag::DiscChanged] = true; + DVDThread::SetDisc(std::move(disc)); SetLidOpen(); } @@ -517,9 +524,11 @@ static void InsertDiscCallback(u64 userdata, s64 cyclesLate) } // Must only be called on the CPU thread -void EjectDisc() +void EjectDisc(EjectCause cause) { CoreTiming::ScheduleEvent(0, s_eject_disc); + if (cause == EjectCause::User) + ExpansionInterface::g_rtc_flags[ExpansionInterface::RTCFlag::EjectButton] = true; } // Must only be called on the CPU thread @@ -545,7 +554,7 @@ void ChangeDisc(const std::string& new_path) return; } - EjectDisc(); + EjectDisc(EjectCause::User); s_disc_path_to_insert = new_path; CoreTiming::ScheduleEvent(SystemTimers::GetTicksPerSecond(), s_insert_disc); @@ -1072,7 +1081,7 @@ void ExecuteCommand(u32 command_0, u32 command_1, u32 command_2, u32 output_addr } else if (force_eject) { - EjectDiscCallback(0, 0); + EjectDisc(EjectCause::Software); } break; } diff --git a/Source/Core/Core/HW/DVD/DVDInterface.h b/Source/Core/Core/HW/DVD/DVDInterface.h index f7da0728c9..edcaad6d9d 100644 --- a/Source/Core/Core/HW/DVD/DVDInterface.h +++ b/Source/Core/Core/HW/DVD/DVDInterface.h @@ -74,6 +74,12 @@ enum class ReplyType : u32 DTK }; +enum class EjectCause +{ + User, + Software, +}; + void Init(); void Reset(); void Shutdown(); @@ -84,7 +90,7 @@ void RegisterMMIO(MMIO::Mapping* mmio, u32 base); void SetDisc(std::unique_ptr disc, std::optional> auto_disc_change_paths); bool IsDiscInside(); -void EjectDisc(); // Must only be called on the CPU thread +void EjectDisc(EjectCause cause); // Must only be called on the CPU thread void ChangeDisc(const std::vector& paths); // Must only be called on the CPU thread void ChangeDisc(const std::string& new_path); // Must only be called on the CPU thread bool AutoChangeDisc(); // Must only be called on the CPU thread diff --git a/Source/Core/Core/HW/EXI/EXI_DeviceIPL.cpp b/Source/Core/Core/HW/EXI/EXI_DeviceIPL.cpp index 6839ec92ef..7ca1e007b3 100644 --- a/Source/Core/Core/HW/EXI/EXI_DeviceIPL.cpp +++ b/Source/Core/Core/HW/EXI/EXI_DeviceIPL.cpp @@ -3,6 +3,7 @@ // Refer to the license.txt file included. #include "Core/HW/EXI/EXI_DeviceIPL.h" +#include "Core/HW/DVD/DVDInterface.h" #include #include @@ -43,6 +44,8 @@ static const char iplverPAL[0x100] = "(C) 1999-2001 Nintendo. All rights reserv static const char iplverNTSC[0x100] = "(C) 1999-2001 Nintendo. All rights reserved." "(C) 1999 ArtX Inc. All rights reserved."; +Common::Flags g_rtc_flags; + // bootrom descrambler reversed by segher // Copyright 2008 Segher Boessenkool void CEXIIPL::Descrambler(u8* data, u32 size) @@ -149,6 +152,7 @@ CEXIIPL::~CEXIIPL() void CEXIIPL::DoState(PointerWrap& p) { p.Do(g_SRAM.rtc); + p.Do(g_rtc_flags); p.Do(m_command); p.Do(m_command_bytes_received); p.Do(m_cursor); @@ -361,10 +365,12 @@ void CEXIIPL::TransferByte(u8& data) break; } } - else if (IN_RANGE(WII_RTC)) + else if (IN_RANGE(WII_RTC) && DEV_ADDR(WII_RTC) == 0x20) { - // Wii only RTC flags... afaik only the Wii Menu initializes it - // Seems to be 4bytes at dev_addr 0x20 + if (m_command.is_write()) + g_rtc_flags.m_hex = data; + else + data = g_rtc_flags.m_hex; } else if (IN_RANGE(EUART)) { diff --git a/Source/Core/Core/HW/EXI/EXI_DeviceIPL.h b/Source/Core/Core/HW/EXI/EXI_DeviceIPL.h index 4e1c0d9c44..cfbf1f7a3b 100644 --- a/Source/Core/Core/HW/EXI/EXI_DeviceIPL.h +++ b/Source/Core/Core/HW/EXI/EXI_DeviceIPL.h @@ -7,6 +7,7 @@ #include #include +#include "Common/BitUtils.h" #include "Core/HW/EXI/EXI_Device.h" class PointerWrap; @@ -78,4 +79,17 @@ private: static std::string FindIPLDump(const std::string& path_prefix); }; + +// Used to indicate disc changes on the Wii, as insane as that sounds. +// However, the name is definitely RTCFlag, as the code that gets it is __OSGetRTCFlags and +// __OSClearRTCFlags in OSRtc.o (based on symbols from Kirby's Dream Collection) +// This may simply be a single byte that gets repeated 4 times by some EXI quirk, +// as reading it gives the value repeated 4 times but code only checks the first bit. +enum class RTCFlag : u32 +{ + EjectButton = 0x01010101, + DiscChanged = 0x02020202, +}; + +extern Common::Flags g_rtc_flags; } // namespace ExpansionInterface diff --git a/Source/Core/Core/HW/WII_IPC.cpp b/Source/Core/Core/HW/WII_IPC.cpp index e6a48cb31b..bf0aef3e53 100644 --- a/Source/Core/Core/HW/WII_IPC.cpp +++ b/Source/Core/Core/HW/WII_IPC.cpp @@ -194,7 +194,7 @@ void RegisterMMIO(MMIO::Mapping* mmio, u32 base) if (g_gpio_out[GPIO::DO_EJECT]) { INFO_LOG(WII_IPC, "Ejecting disc due to GPIO write"); - DVDInterface::EjectDisc(); + DVDInterface::EjectDisc(DVDInterface::EjectCause::Software); } // SENSOR_BAR is checked by WiimoteEmu::CameraLogic // TODO: AVE, SLOT_LED diff --git a/Source/Core/DolphinQt/MainWindow.cpp b/Source/Core/DolphinQt/MainWindow.cpp index 7216b00206..88e5aa3f9a 100644 --- a/Source/Core/DolphinQt/MainWindow.cpp +++ b/Source/Core/DolphinQt/MainWindow.cpp @@ -686,7 +686,7 @@ void MainWindow::ChangeDisc() void MainWindow::EjectDisc() { - Core::RunAsCPUThread(DVDInterface::EjectDisc); + Core::RunAsCPUThread([] { DVDInterface::EjectDisc(DVDInterface::EjectCause::User); }); } void MainWindow::Open() From 84f099cf62ab03c7a02d92e4d0bd924bbf34f897 Mon Sep 17 00:00:00 2001 From: Pokechu22 Date: Fri, 4 Oct 2019 17:30:47 -0700 Subject: [PATCH 04/17] Tidy and eliminate some of the DI register unions --- Source/Core/Core/HW/DVD/DVDInterface.cpp | 122 +++++++---------------- 1 file changed, 34 insertions(+), 88 deletions(-) diff --git a/Source/Core/Core/HW/DVD/DVDInterface.cpp b/Source/Core/Core/HW/DVD/DVDInterface.cpp index 4900727f36..498de9b42c 100644 --- a/Source/Core/Core/HW/DVD/DVDInterface.cpp +++ b/Source/Core/Core/HW/DVD/DVDInterface.cpp @@ -110,11 +110,11 @@ union UDISR u32 Hex; struct { - u32 BREAK : 1; // Stop the Device + Interrupt - u32 DEINITMASK : 1; // Access Device Error Int Mask - u32 DEINT : 1; // Access Device Error Int - u32 TCINTMASK : 1; // Transfer Complete Int Mask - u32 TCINT : 1; // Transfer Complete Int + u32 BREAK : 1; // Stop the Device + Interrupt + u32 DEINTMASK : 1; // Access Device Error Int Mask + u32 DEINT : 1; // Access Device Error Int + u32 TCINTMASK : 1; // Transfer Complete Int Mask + u32 TCINT : 1; // Transfer Complete Int u32 BRKINTMASK : 1; u32 BRKINT : 1; // w 1: clear brkint u32 : 25; @@ -138,50 +138,6 @@ union UDICVR UDICVR(u32 _hex) { Hex = _hex; } }; -union UDICMDBUF -{ - u32 Hex; - struct - { - u8 CMDBYTE3; - u8 CMDBYTE2; - u8 CMDBYTE1; - u8 CMDBYTE0; - }; -}; - -// DI DMA Address Register -union UDIMAR -{ - u32 Hex; - struct - { - u32 Zerobits : 5; // Must be zero (32byte aligned) - u32 : 27; - }; - struct - { - u32 Address : 26; - u32 : 6; - }; -}; - -// DI DMA Address Length Register -union UDILENGTH -{ - u32 Hex; - struct - { - u32 Zerobits : 5; // Must be zero (32byte aligned) - u32 : 27; - }; - struct - { - u32 Length : 26; - u32 : 6; - }; -}; - // DI DMA Control Register union UDICR { @@ -195,18 +151,6 @@ union UDICR }; }; -union UDIIMMBUF -{ - u32 Hex; - struct - { - u8 REGVAL3; - u8 REGVAL2; - u8 REGVAL1; - u8 REGVAL0; - }; -}; - // DI Config Register union UDICFG { @@ -225,11 +169,11 @@ union UDICFG // Hardware registers static UDISR s_DISR; static UDICVR s_DICVR; -static UDICMDBUF s_DICMDBUF[3]; -static UDIMAR s_DIMAR; -static UDILENGTH s_DILENGTH; +static u32 s_DICMDBUF[3]; +static u32 s_DIMAR; +static u32 s_DILENGTH; static UDICR s_DICR; -static UDIIMMBUF s_DIIMMBUF; +static u32 s_DIIMMBUF; static UDICFG s_DICFG; static StreamADPCM::ADPCMDecoder s_adpcm_decoder; @@ -436,13 +380,13 @@ void Init() void Reset() { s_DISR.Hex = 0; - s_DICMDBUF[0].Hex = 0; - s_DICMDBUF[1].Hex = 0; - s_DICMDBUF[2].Hex = 0; - s_DIMAR.Hex = 0; - s_DILENGTH.Hex = 0; + s_DICMDBUF[0] = 0; + s_DICMDBUF[1] = 0; + s_DICMDBUF[2] = 0; + s_DIMAR = 0; + s_DILENGTH = 0; s_DICR.Hex = 0; - s_DIIMMBUF.Hex = 0; + s_DIIMMBUF = 0; s_DICFG.Hex = 0; s_DICFG.CONFIG = 1; // Disable bootrom descrambler @@ -610,7 +554,7 @@ void RegisterMMIO(MMIO::Mapping* mmio, u32 base) MMIO::ComplexWrite([](u32, u32 val) { UDISR tmpStatusReg(val); - s_DISR.DEINITMASK = tmpStatusReg.DEINITMASK; + s_DISR.DEINTMASK = tmpStatusReg.DEINTMASK; s_DISR.TCINTMASK = tmpStatusReg.TCINTMASK; s_DISR.BRKINTMASK = tmpStatusReg.BRKINTMASK; s_DISR.BREAK = tmpStatusReg.BREAK; @@ -644,31 +588,33 @@ void RegisterMMIO(MMIO::Mapping* mmio, u32 base) UpdateInterrupts(); })); - // Command registers are very similar and we can register them with a - // simple loop. - for (int i = 0; i < 3; ++i) - mmio->Register(base | (DI_COMMAND_0 + 4 * i), MMIO::DirectRead(&s_DICMDBUF[i].Hex), - MMIO::DirectWrite(&s_DICMDBUF[i].Hex)); + // Command registers, which have no special logic + mmio->Register(base | DI_COMMAND_0, MMIO::DirectRead(&s_DICMDBUF[0]), + MMIO::DirectWrite(&s_DICMDBUF[0])); + mmio->Register(base | DI_COMMAND_1, MMIO::DirectRead(&s_DICMDBUF[1]), + MMIO::DirectWrite(&s_DICMDBUF[1])); + mmio->Register(base | DI_COMMAND_2, MMIO::DirectRead(&s_DICMDBUF[2]), + MMIO::DirectWrite(&s_DICMDBUF[2])); // DMA related registers. Mostly direct accesses (+ masking for writes to // handle things like address alignment) and complex write on the DMA // control register that will trigger the DMA. - mmio->Register(base | DI_DMA_ADDRESS_REGISTER, MMIO::DirectRead(&s_DIMAR.Hex), - MMIO::DirectWrite(&s_DIMAR.Hex, ~0xFC00001F)); - mmio->Register(base | DI_DMA_LENGTH_REGISTER, MMIO::DirectRead(&s_DILENGTH.Hex), - MMIO::DirectWrite(&s_DILENGTH.Hex, ~0x1F)); + mmio->Register(base | DI_DMA_ADDRESS_REGISTER, MMIO::DirectRead(&s_DIMAR), + MMIO::DirectWrite(&s_DIMAR, ~0xFC00001F)); + mmio->Register(base | DI_DMA_LENGTH_REGISTER, MMIO::DirectRead(&s_DILENGTH), + MMIO::DirectWrite(&s_DILENGTH, ~0x1F)); mmio->Register(base | DI_DMA_CONTROL_REGISTER, MMIO::DirectRead(&s_DICR.Hex), MMIO::ComplexWrite([](u32, u32 val) { s_DICR.Hex = val & 7; if (s_DICR.TSTART) { - ExecuteCommand(s_DICMDBUF[0].Hex, s_DICMDBUF[1].Hex, s_DICMDBUF[2].Hex, - s_DIMAR.Hex, s_DILENGTH.Hex, false); + ExecuteCommand(s_DICMDBUF[0], s_DICMDBUF[1], s_DICMDBUF[2], s_DIMAR, + s_DILENGTH, false); } })); - mmio->Register(base | DI_IMMEDIATE_DATA_BUFFER, MMIO::DirectRead(&s_DIIMMBUF.Hex), - MMIO::DirectWrite(&s_DIIMMBUF.Hex)); + mmio->Register(base | DI_IMMEDIATE_DATA_BUFFER, MMIO::DirectRead(&s_DIIMMBUF), + MMIO::DirectWrite(&s_DIIMMBUF)); // DI config register is read only. mmio->Register(base | DI_CONFIG_REGISTER, MMIO::DirectRead(&s_DICFG.Hex), @@ -677,7 +623,7 @@ void RegisterMMIO(MMIO::Mapping* mmio, u32 base) void UpdateInterrupts() { - const bool set_mask = (s_DISR.DEINT & s_DISR.DEINITMASK) || (s_DISR.TCINT & s_DISR.TCINTMASK) || + const bool set_mask = (s_DISR.DEINT & s_DISR.DEINTMASK) || (s_DISR.TCINT & s_DISR.TCINTMASK) || (s_DISR.BRKINT & s_DISR.BRKINTMASK) || (s_DICVR.CVRINT & s_DICVR.CVRINTMASK); @@ -713,7 +659,7 @@ void WriteImmediate(u32 value, u32 output_address, bool reply_to_ios) if (reply_to_ios) Memory::Write_U32(value, output_address); else - s_DIIMMBUF.Hex = value; + s_DIIMMBUF = value; } // Iff false is returned, ScheduleEvent must be used to finish executing the command @@ -1178,7 +1124,7 @@ void FinishExecutingCommand(ReplyType reply_type, DIInterruptType interrupt_type if (s_DICR.TSTART) { s_DICR.TSTART = 0; - s_DILENGTH.Length = 0; + s_DILENGTH = 0; GenerateDIInterrupt(interrupt_type); } break; From c564d6410402d93dc81ed146e09657e434722e46 Mon Sep 17 00:00:00 2001 From: Pokechu22 Date: Fri, 15 Nov 2019 16:41:33 -0800 Subject: [PATCH 05/17] Remove unused drive debug command constants The corresponding code that used these was removed in 2009 with 93b83f8d659. Now their only purpose is to generate warnings on osx. Minimalistic documentation on these commands can be found at http://hitmen.c02.at/files/yagcd/yagcd/chap5.html#sec5.7.2 and https://web.archive.org/web/20070328200323/http://tmb.elitedvb.net/dvd-game/index.php/CMDFE. Those constants only relate to the 0x11 subcommand, which is one of many. Most can't be properly emulated unless we LLE the drive firmware (in which case, they don't need to be reimplemented). --- Source/Core/Core/HW/DVD/DVDInterface.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Source/Core/Core/HW/DVD/DVDInterface.cpp b/Source/Core/Core/HW/DVD/DVDInterface.cpp index 498de9b42c..5a797d0dd5 100644 --- a/Source/Core/Core/HW/DVD/DVDInterface.cpp +++ b/Source/Core/Core/HW/DVD/DVDInterface.cpp @@ -98,12 +98,6 @@ constexpr u32 DI_DMA_CONTROL_REGISTER = 0x1C; constexpr u32 DI_IMMEDIATE_DATA_BUFFER = 0x20; constexpr u32 DI_CONFIG_REGISTER = 0x24; -// debug commands which may be ORd -constexpr u32 STOP_DRIVE = 0; -constexpr u32 START_DRIVE = 0x100; -constexpr u32 ACCEPT_COPY = 0x4000; -constexpr u32 DISC_CHECK = 0x8000; - // DI Status Register union UDISR { From d3aad1d6d527a06166806d38bd065ab1d7484d50 Mon Sep 17 00:00:00 2001 From: Pokechu22 Date: Sat, 26 Oct 2019 15:35:29 -0700 Subject: [PATCH 06/17] DIMAR only ignores bits 0-4, not the upper bits Based on a hardware test on a Wii. The alignment code was originally added in 743641965a2. --- Source/Core/Core/HW/DVD/DVDInterface.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Core/Core/HW/DVD/DVDInterface.cpp b/Source/Core/Core/HW/DVD/DVDInterface.cpp index 5a797d0dd5..41ba2a8660 100644 --- a/Source/Core/Core/HW/DVD/DVDInterface.cpp +++ b/Source/Core/Core/HW/DVD/DVDInterface.cpp @@ -594,7 +594,7 @@ void RegisterMMIO(MMIO::Mapping* mmio, u32 base) // handle things like address alignment) and complex write on the DMA // control register that will trigger the DMA. mmio->Register(base | DI_DMA_ADDRESS_REGISTER, MMIO::DirectRead(&s_DIMAR), - MMIO::DirectWrite(&s_DIMAR, ~0xFC00001F)); + MMIO::DirectWrite(&s_DIMAR, ~0x1F)); mmio->Register(base | DI_DMA_LENGTH_REGISTER, MMIO::DirectRead(&s_DILENGTH), MMIO::DirectWrite(&s_DILENGTH, ~0x1F)); mmio->Register(base | DI_DMA_CONTROL_REGISTER, MMIO::DirectRead(&s_DICR.Hex), From 31105995591bb84092b13795064874535c61be4f Mon Sep 17 00:00:00 2001 From: Pokechu22 Date: Sat, 26 Oct 2019 15:53:29 -0700 Subject: [PATCH 07/17] Increase DIMAR by DILENGTH after a command --- Source/Core/Core/HW/DVD/DVDInterface.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Source/Core/Core/HW/DVD/DVDInterface.cpp b/Source/Core/Core/HW/DVD/DVDInterface.cpp index 41ba2a8660..f22e0bd1f1 100644 --- a/Source/Core/Core/HW/DVD/DVDInterface.cpp +++ b/Source/Core/Core/HW/DVD/DVDInterface.cpp @@ -1106,6 +1106,9 @@ void FinishExecutingCommandCallback(u64 userdata, s64 cycles_late) void FinishExecutingCommand(ReplyType reply_type, DIInterruptType interrupt_type, s64 cycles_late, const std::vector& data) { + s_DIMAR += s_DILENGTH; + s_DILENGTH = 0; + switch (reply_type) { case ReplyType::NoReply: @@ -1118,7 +1121,6 @@ void FinishExecutingCommand(ReplyType reply_type, DIInterruptType interrupt_type if (s_DICR.TSTART) { s_DICR.TSTART = 0; - s_DILENGTH = 0; GenerateDIInterrupt(interrupt_type); } break; From a8ae5fa21a5db611d3a735483700a1c33779f139 Mon Sep 17 00:00:00 2001 From: Pokechu22 Date: Fri, 4 Oct 2019 17:37:40 -0700 Subject: [PATCH 08/17] Expose setting DVDInterface errors and split setting into two parts --- Source/Core/Core/HW/DVD/DVDInterface.cpp | 46 +++++++++--------------- Source/Core/Core/HW/DVD/DVDInterface.h | 31 ++++++++++++++++ 2 files changed, 47 insertions(+), 30 deletions(-) diff --git a/Source/Core/Core/HW/DVD/DVDInterface.cpp b/Source/Core/Core/HW/DVD/DVDInterface.cpp index f22e0bd1f1..7978661b80 100644 --- a/Source/Core/Core/HW/DVD/DVDInterface.cpp +++ b/Source/Core/Core/HW/DVD/DVDInterface.cpp @@ -60,32 +60,6 @@ constexpr u64 BUFFER_TRANSFER_RATE = 32 * 1024 * 1024; namespace DVDInterface { -// "low" error codes -constexpr u32 ERROR_READY = 0x0000000; // Ready. -constexpr u32 ERROR_COVER_L = 0x01000000; // Cover is opened. -constexpr u32 ERROR_CHANGE_DISK = 0x02000000; // Disk change. -constexpr u32 ERROR_NO_DISK = 0x03000000; // No disk. -constexpr u32 ERROR_MOTOR_STOP_L = 0x04000000; // Motor stop. -constexpr u32 ERROR_NO_DISKID_L = 0x05000000; // Disk ID not read. - -// "high" error codes -constexpr u32 ERROR_NONE = 0x000000; // No error. -constexpr u32 ERROR_MOTOR_STOP_H = 0x020400; // Motor stopped. -constexpr u32 ERROR_NO_DISKID_H = 0x020401; // Disk ID not read. -constexpr u32 ERROR_COVER_H = 0x023a00; // Medium not present / Cover opened. -constexpr u32 ERROR_SEEK_NDONE = 0x030200; // No seek complete. -constexpr u32 ERROR_READ = 0x031100; // Unrecovered read error. -constexpr u32 ERROR_PROTOCOL = 0x040800; // Transfer protocol error. -constexpr u32 ERROR_INV_CMD = 0x052000; // Invalid command operation code. -constexpr u32 ERROR_AUDIO_BUF = 0x052001; // Audio Buffer not set. -constexpr u32 ERROR_BLOCK_OOB = 0x052100; // Logical block address out of bounds. -constexpr u32 ERROR_INV_FIELD = 0x052400; // Invalid field in command packet. -constexpr u32 ERROR_INV_AUDIO = 0x052401; // Invalid audio command. -constexpr u32 ERROR_INV_PERIOD = 0x052402; // Configuration out of permitted period. -constexpr u32 ERROR_END_USR_AREA = 0x056300; // End of user area encountered on this track. -constexpr u32 ERROR_MEDIUM = 0x062800; // Medium may have changed. -constexpr u32 ERROR_MEDIUM_REQ = 0x0b5a01; // Operator medium removal request. - // internal hardware addresses constexpr u32 DI_STATUS_REGISTER = 0x00; constexpr u32 DI_COVER_REGISTER = 0x04; @@ -664,7 +638,7 @@ bool ExecuteReadCommand(u64 dvd_offset, u32 output_address, u32 dvd_length, u32 if (!IsDiscInside()) { // Disc read fails - s_error_code = ERROR_NO_DISK | ERROR_COVER_H; + SetHighError(ERROR_NO_DISK_H); *interrupt_type = INT_DEINT; return false; } @@ -697,7 +671,7 @@ void ExecuteCommand(u32 command_0, u32 command_1, u32 command_2, u32 output_addr // DVDLowRequestError needs access to the error code set by the previous command if (command_0 >> 24 != DVDLowRequestError) - s_error_code = 0; + SetHighError(0); switch (command_0 >> 24) { @@ -842,7 +816,7 @@ void ExecuteCommand(u32 command_0, u32 command_1, u32 command_2, u32 output_addr INFO_LOG(DVDINTERFACE, "DVDLowReportKey"); // Does not work on retail discs/drives // Retail games send this command to see if they are running on real retail hw - s_error_code = ERROR_READY | ERROR_INV_CMD; + SetHighError(ERROR_INV_CMD); interrupt_type = INT_BRKINT; break; @@ -926,7 +900,7 @@ void ExecuteCommand(u32 command_0, u32 command_1, u32 command_2, u32 output_addr case DVDLowRequestError: INFO_LOG(DVDINTERFACE, "Requesting error... (0x%08x)", s_error_code); WriteImmediate(s_error_code, output_address, reply_to_ios); - s_error_code = 0; + SetHighError(0); break; // Audio Stream (Immediate). Only seems to be used by some GC games @@ -1103,6 +1077,18 @@ void FinishExecutingCommandCallback(u64 userdata, s64 cycles_late) FinishExecutingCommand(reply_type, interrupt_type, cycles_late); } +void SetLowError(u32 low_error) +{ + DEBUG_ASSERT((low_error & HIGH_ERROR_MASK) == 0); + s_error_code = (s_error_code & HIGH_ERROR_MASK) | (low_error & LOW_ERROR_MASK); +} + +void SetHighError(u32 high_error) +{ + DEBUG_ASSERT((high_error & LOW_ERROR_MASK) == 0); + s_error_code = (s_error_code & LOW_ERROR_MASK) | (high_error & HIGH_ERROR_MASK); +} + void FinishExecutingCommand(ReplyType reply_type, DIInterruptType interrupt_type, s64 cycles_late, const std::vector& data) { diff --git a/Source/Core/Core/HW/DVD/DVDInterface.h b/Source/Core/Core/HW/DVD/DVDInterface.h index edcaad6d9d..b2144f2a0b 100644 --- a/Source/Core/Core/HW/DVD/DVDInterface.h +++ b/Source/Core/Core/HW/DVD/DVDInterface.h @@ -58,6 +58,34 @@ enum DICommand DVDLowAudioBufferConfig = 0xe4 }; +// "low" error codes +constexpr u32 ERROR_READY = 0x0000000; // Ready. +constexpr u32 ERROR_COVER = 0x01000000; // Cover is opened. +constexpr u32 ERROR_CHANGE_DISK = 0x02000000; // Disk change. +constexpr u32 ERROR_NO_DISK_L = 0x03000000; // No disk. +constexpr u32 ERROR_MOTOR_STOP_L = 0x04000000; // Motor stop. +constexpr u32 ERROR_NO_DISKID_L = 0x05000000; // Disk ID not read. +constexpr u32 LOW_ERROR_MASK = 0xff000000; + +// "high" error codes +constexpr u32 ERROR_NONE = 0x000000; // No error. +constexpr u32 ERROR_MOTOR_STOP_H = 0x020400; // Motor stopped. +constexpr u32 ERROR_NO_DISKID_H = 0x020401; // Disk ID not read. +constexpr u32 ERROR_NO_DISK_H = 0x023a00; // Medium not present / Cover opened. +constexpr u32 ERROR_SEEK_NDONE = 0x030200; // No seek complete. +constexpr u32 ERROR_READ = 0x031100; // Unrecovered read error. +constexpr u32 ERROR_PROTOCOL = 0x040800; // Transfer protocol error. +constexpr u32 ERROR_INV_CMD = 0x052000; // Invalid command operation code. +constexpr u32 ERROR_AUDIO_BUF = 0x052001; // Audio Buffer not set. +constexpr u32 ERROR_BLOCK_OOB = 0x052100; // Logical block address out of bounds. +constexpr u32 ERROR_INV_FIELD = 0x052400; // Invalid field in command packet. +constexpr u32 ERROR_INV_AUDIO = 0x052401; // Invalid audio command. +constexpr u32 ERROR_INV_PERIOD = 0x052402; // Configuration out of permitted period. +constexpr u32 ERROR_END_USR_AREA = 0x056300; // End of user area encountered on this track. +constexpr u32 ERROR_MEDIUM = 0x062800; // Medium may have changed. +constexpr u32 ERROR_MEDIUM_REQ = 0x0b5a01; // Operator medium removal request. +constexpr u32 HIGH_ERROR_MASK = 0x00ffffff; + enum DIInterruptType : int { INT_DEINT = 0, @@ -107,6 +135,9 @@ void ChangePartition(const DiscIO::Partition& partition); void ExecuteCommand(u32 command_0, u32 command_1, u32 command_2, u32 output_address, u32 output_length, bool reply_to_ios); +void SetLowError(u32 low_error); +void SetHighError(u32 high_error); + // Used by DVDThread void FinishExecutingCommand(ReplyType reply_type, DIInterruptType interrupt_type, s64 cycles_late, const std::vector& data = std::vector()); From ef2fc5a49bf390411bd08d4a7eb6cb3923f6187f Mon Sep 17 00:00:00 2001 From: Pokechu22 Date: Sun, 1 Sep 2019 20:25:33 -0700 Subject: [PATCH 09/17] Split /dev/di commands from DVDInterface The various ioctls sometimes have different arguments than the DI command registers, though they generally overlap. There are also a bunch of ioctls that don't even normally go into DVDInterface, just returning various data. Some of the implemented ioctls are new to Dolphin. --- Source/Core/Core/HW/DVD/DVDInterface.cpp | 406 ++++++--------- Source/Core/Core/HW/DVD/DVDInterface.h | 75 ++- Source/Core/Core/HW/DVD/DVDThread.cpp | 4 +- Source/Core/Core/IOS/DI/DI.cpp | 638 +++++++++++++++++++++-- Source/Core/Core/IOS/DI/DI.h | 107 +++- Source/Core/Core/IOS/IOS.cpp | 3 + 6 files changed, 918 insertions(+), 315 deletions(-) diff --git a/Source/Core/Core/HW/DVD/DVDInterface.cpp b/Source/Core/Core/HW/DVD/DVDInterface.cpp index 7978661b80..5c6ab9c9c8 100644 --- a/Source/Core/Core/HW/DVD/DVDInterface.cpp +++ b/Source/Core/Core/HW/DVD/DVDInterface.cpp @@ -187,7 +187,6 @@ void SetLidOpen(); void UpdateInterrupts(); void GenerateDIInterrupt(DIInterruptType _DVDInterrupt); -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, const DiscIO::Partition& partition, ReplyType reply_type, DIInterruptType* interrupt_type); @@ -319,7 +318,7 @@ static void DTKStreamingCallback(const std::vector& audio_data, s64 cycles_l else { // There's nothing to read, so using DVDThread is unnecessary. - u64 userdata = PackFinishExecutingCommandUserdata(ReplyType::DTK, DIInterruptType::INT_TCINT); + u64 userdata = PackFinishExecutingCommandUserdata(ReplyType::DTK, DIInterruptType::TCINT); CoreTiming::ScheduleEvent(ticks_to_dtk, s_finish_executing_command, userdata); } } @@ -340,7 +339,7 @@ void Init() s_finish_executing_command = CoreTiming::RegisterEvent("FinishExecutingCommand", FinishExecutingCommandCallback); - u64 userdata = PackFinishExecutingCommandUserdata(ReplyType::DTK, DIInterruptType::INT_TCINT); + u64 userdata = PackFinishExecutingCommandUserdata(ReplyType::DTK, DIInterruptType::TCINT); CoreTiming::ScheduleEvent(0, s_finish_executing_command, userdata); } @@ -500,7 +499,7 @@ void SetLidOpen() u32 old_value = s_DICVR.CVR; s_DICVR.CVR = IsDiscInside() ? 0 : 1; if (s_DICVR.CVR != old_value) - GenerateDIInterrupt(INT_CVRINT); + GenerateDIInterrupt(DIInterruptType::CVRINT); } bool UpdateRunningGameMetadata(std::optional title_id) @@ -576,8 +575,7 @@ void RegisterMMIO(MMIO::Mapping* mmio, u32 base) s_DICR.Hex = val & 7; if (s_DICR.TSTART) { - ExecuteCommand(s_DICMDBUF[0], s_DICMDBUF[1], s_DICMDBUF[2], s_DIMAR, - s_DILENGTH, false); + ExecuteCommand(ReplyType::Interrupt); } })); @@ -605,29 +603,59 @@ void GenerateDIInterrupt(DIInterruptType dvd_interrupt) { switch (dvd_interrupt) { - case INT_DEINT: - s_DISR.DEINT = 1; + case DIInterruptType::DEINT: + s_DISR.DEINT = true; break; - case INT_TCINT: - s_DISR.TCINT = 1; + case DIInterruptType::TCINT: + s_DISR.TCINT = true; break; - case INT_BRKINT: - s_DISR.BRKINT = 1; + case DIInterruptType::BRKINT: + s_DISR.BRKINT = true; break; - case INT_CVRINT: - s_DICVR.CVRINT = 1; + case DIInterruptType::CVRINT: + s_DICVR.CVRINT = true; break; } UpdateInterrupts(); } -void WriteImmediate(u32 value, u32 output_address, bool reply_to_ios) +void SetInterruptEnabled(DIInterruptType interrupt, bool enabled) { - if (reply_to_ios) - Memory::Write_U32(value, output_address); - else - s_DIIMMBUF = value; + switch (interrupt) + { + case DIInterruptType::DEINT: + s_DISR.DEINTMASK = enabled; + break; + case DIInterruptType::TCINT: + s_DISR.TCINTMASK = enabled; + break; + case DIInterruptType::BRKINT: + s_DISR.BRKINTMASK = enabled; + break; + case DIInterruptType::CVRINT: + s_DICVR.CVRINTMASK = enabled; + break; + } +} + +void ClearInterrupt(DIInterruptType interrupt) +{ + switch (interrupt) + { + case DIInterruptType::DEINT: + s_DISR.DEINT = false; + break; + case DIInterruptType::TCINT: + s_DISR.TCINT = false; + break; + case DIInterruptType::BRKINT: + s_DISR.BRKINT = false; + break; + case DIInterruptType::CVRINT: + s_DICVR.CVRINT = false; + break; + } } // Iff false is returned, ScheduleEvent must be used to finish executing the command @@ -639,13 +667,13 @@ bool ExecuteReadCommand(u64 dvd_offset, u32 output_address, u32 dvd_length, u32 { // Disc read fails SetHighError(ERROR_NO_DISK_H); - *interrupt_type = INT_DEINT; + *interrupt_type = DIInterruptType::DEINT; return false; } else { // Disc read succeeds - *interrupt_type = INT_TCINT; + *interrupt_type = DIInterruptType::TCINT; } if (dvd_length > output_length) @@ -662,254 +690,150 @@ bool ExecuteReadCommand(u64 dvd_offset, u32 output_address, u32 dvd_length, u32 // 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, - u32 output_length, bool reply_to_ios) +void ExecuteCommand(ReplyType reply_type) { - ReplyType reply_type = reply_to_ios ? ReplyType::IOS : ReplyType::Interrupt; - DIInterruptType interrupt_type = INT_TCINT; + DIInterruptType interrupt_type = DIInterruptType::TCINT; bool command_handled_by_thread = false; // DVDLowRequestError needs access to the error code set by the previous command - if (command_0 >> 24 != DVDLowRequestError) + if (static_cast(s_DICMDBUF[0] >> 24) != DICommand::RequestError) SetHighError(0); - switch (command_0 >> 24) + switch (static_cast(s_DICMDBUF[0] >> 24)) { - // Seems to be used by both GC and Wii - case DVDLowInquiry: + // Used by both GC and Wii + case DICommand::Inquiry: // (shuffle2) Taken from my Wii - Memory::Write_U32(0x00000002, output_address); - Memory::Write_U32(0x20060526, output_address + 4); - // This was in the oubuf even though this cmd is only supposed to reply with 64bits - // However, this and other tests strongly suggest that the buffer is static, and it's never - - // or rarely cleared. - Memory::Write_U32(0x41000000, output_address + 8); + Memory::Write_U32(0x00000002, s_DIMAR); // Revision level, device code + Memory::Write_U32(0x20060526, s_DIMAR + 4); // Release date + Memory::Write_U32(0x41000000, s_DIMAR + 8); // Version - INFO_LOG(DVDINTERFACE, "DVDLowInquiry (Buffer 0x%08x, 0x%x)", output_address, output_length); + INFO_LOG(DVDINTERFACE, "DVDLowInquiry (Buffer 0x%08x, 0x%x)", s_DIMAR, s_DILENGTH); break; - // Only seems to be used from WII_IPC, not through direct access - case DVDLowReadDiskID: - INFO_LOG(DVDINTERFACE, "DVDLowReadDiskID"); - command_handled_by_thread = - ExecuteReadCommand(0, output_address, 0x20, output_length, DiscIO::PARTITION_NONE, - reply_type, &interrupt_type); - break; - - // Only used from WII_IPC. This is the only read command that decrypts data - case DVDLowRead: - INFO_LOG(DVDINTERFACE, "DVDLowRead: DVDAddr: 0x%09" PRIx64 ", Size: 0x%x", (u64)command_2 << 2, - command_1); - command_handled_by_thread = - ExecuteReadCommand((u64)command_2 << 2, output_address, command_1, output_length, - s_current_partition, reply_type, &interrupt_type); - break; - - // Probably only used by Wii - case DVDLowWaitForCoverClose: - INFO_LOG(DVDINTERFACE, "DVDLowWaitForCoverClose"); - interrupt_type = (DIInterruptType)4; // ??? - break; - - // "Set Extension"...not sure what it does. GC only? - case 0x55: + // GC-only patched drive firmware command, used by libogc + case DICommand::Unknown55: INFO_LOG(DVDINTERFACE, "SetExtension"); break; - // Probably only used though WII_IPC - case DVDLowGetCoverReg: - WriteImmediate(s_DICVR.Hex, output_address, reply_to_ios); - DEBUG_LOG(DVDINTERFACE, "DVDLowGetCoverReg 0x%08x", s_DICVR.Hex); - break; - - // Probably only used by Wii - case DVDLowNotifyReset: - ERROR_LOG(DVDINTERFACE, "DVDLowNotifyReset"); - PanicAlert("DVDLowNotifyReset"); - break; - // Probably only used by Wii - case DVDLowReadDvdPhysical: - ERROR_LOG(DVDINTERFACE, "DVDLowReadDvdPhysical"); - PanicAlert("DVDLowReadDvdPhysical"); - break; - // Probably only used by Wii - case DVDLowReadDvdCopyright: - ERROR_LOG(DVDINTERFACE, "DVDLowReadDvdCopyright"); - PanicAlert("DVDLowReadDvdCopyright"); - break; - // Probably only used by Wii - case DVDLowReadDvdDiscKey: - ERROR_LOG(DVDINTERFACE, "DVDLowReadDvdDiscKey"); - PanicAlert("DVDLowReadDvdDiscKey"); - break; - - // Probably only used by Wii - case DVDLowClearCoverInterrupt: - DEBUG_LOG(DVDINTERFACE, "DVDLowClearCoverInterrupt"); - s_DICVR.CVRINT = 0; - break; - - // Probably only used by Wii - case DVDLowGetCoverStatus: - WriteImmediate(IsDiscInside() ? 2 : 1, output_address, reply_to_ios); - INFO_LOG(DVDINTERFACE, "DVDLowGetCoverStatus: Disc %sInserted", IsDiscInside() ? "" : "Not "); - break; - - // Probably only used by Wii - case DVDLowReset: - INFO_LOG(DVDINTERFACE, "DVDLowReset"); - break; - - // Probably only used by Wii - case DVDLowClosePartition: - INFO_LOG(DVDINTERFACE, "DVDLowClosePartition"); - break; - - // Probably only used by Wii - case DVDLowUnencryptedRead: - INFO_LOG(DVDINTERFACE, "DVDLowUnencryptedRead: DVDAddr: 0x%09" PRIx64 ", Size: 0x%x", - (u64)command_2 << 2, command_1); - - // We must make sure it is in a valid area! (#001 check) - // Are these checks correct? They seem to mix 32-bit offsets and 8-bit lengths - // * 0x00000000 - 0x00014000 (limit of older IOS versions) - // * 0x460a0000 - 0x460a0008 - // * 0x7ed40000 - 0x7ed40008 - if (((command_2 > 0x00000000 && command_2 < 0x00014000) || - (((command_2 + command_1) > 0x00000000) && (command_2 + command_1) < 0x00014000) || - (command_2 > 0x460a0000 && command_2 < 0x460a0008) || - (((command_2 + command_1) > 0x460a0000) && (command_2 + command_1) < 0x460a0008) || - (command_2 > 0x7ed40000 && command_2 < 0x7ed40008) || - (((command_2 + command_1) > 0x7ed40000) && (command_2 + command_1) < 0x7ed40008))) - { - command_handled_by_thread = - ExecuteReadCommand((u64)command_2 << 2, output_address, command_1, output_length, - DiscIO::PARTITION_NONE, reply_type, &interrupt_type); - } - else - { - WARN_LOG(DVDINTERFACE, "DVDLowUnencryptedRead: trying to read out of bounds @ %09" PRIx64, - (u64)command_2 << 2); - s_error_code = ERROR_READY | ERROR_BLOCK_OOB; - // Should cause software to call DVDLowRequestError - interrupt_type = INT_BRKINT; - } - - break; - - // Probably only used by Wii - case DVDLowEnableDvdVideo: - ERROR_LOG(DVDINTERFACE, "DVDLowEnableDvdVideo"); - break; - - // New Super Mario Bros. Wii sends these commands, - // but it seems we don't need to implement anything. - // Probably only used by Wii - case 0x95: - case 0x96: - ERROR_LOG(DVDINTERFACE, "Unimplemented BCA command 0x%08x (Buffer 0x%08x, 0x%x)", command_0, - output_address, output_length); - break; - - // Probably only used by Wii - case DVDLowReportKey: + // Wii-exclusive + case DICommand::ReportKey: INFO_LOG(DVDINTERFACE, "DVDLowReportKey"); // Does not work on retail discs/drives // Retail games send this command to see if they are running on real retail hw SetHighError(ERROR_INV_CMD); - interrupt_type = INT_BRKINT; + interrupt_type = DIInterruptType::DEINT; break; - // DMA Read from Disc. Only seems to be used through direct access, not WII_IPC - case 0xA8: - switch (command_0 & 0xFF) + // DMA Read from Disc. Only used through direct access on GC; direct use is prohibited by + // IOS (which uses it internally) + case DICommand::Read: + switch (s_DICMDBUF[0] & 0xFF) { case 0x00: // Read Sector { - u64 iDVDOffset = (u64)command_1 << 2; + u64 iDVDOffset = static_cast(s_DICMDBUF[1]) << 2; INFO_LOG(DVDINTERFACE, "Read: DVDOffset=%08" PRIx64 ", DMABuffer = %08x, SrcLength = %08x, DMALength = %08x", - iDVDOffset, output_address, command_2, output_length); + iDVDOffset, s_DIMAR, s_DICMDBUF[2], s_DILENGTH); command_handled_by_thread = - ExecuteReadCommand(iDVDOffset, output_address, command_2, output_length, - DiscIO::PARTITION_NONE, reply_type, &interrupt_type); + ExecuteReadCommand(iDVDOffset, s_DIMAR, s_DICMDBUF[2], s_DILENGTH, DiscIO::PARTITION_NONE, + reply_type, &interrupt_type); } break; case 0x40: // Read DiscID - INFO_LOG(DVDINTERFACE, "Read DiscID %08x", Memory::Read_U32(output_address)); - command_handled_by_thread = - ExecuteReadCommand(0, output_address, 0x20, output_length, DiscIO::PARTITION_NONE, - reply_type, &interrupt_type); + INFO_LOG(DVDINTERFACE, "Read DiscID: buffer %08x", s_DIMAR); + command_handled_by_thread = ExecuteReadCommand( + 0, s_DIMAR, 0x20, s_DILENGTH, DiscIO::PARTITION_NONE, reply_type, &interrupt_type); break; default: - ERROR_LOG(DVDINTERFACE, "Unknown read subcommand: %08x", command_0); + ERROR_LOG(DVDINTERFACE, "Unknown read subcommand: %08x", s_DICMDBUF[0]); break; } break; - // Seems to be used by both GC and Wii - case DVDLowSeek: + // Used by both GC and Wii + case DICommand::Seek: // Currently unimplemented - INFO_LOG(DVDINTERFACE, "Seek: offset=%09" PRIx64 " (ignoring)", (u64)command_1 << 2); + INFO_LOG(DVDINTERFACE, "Seek: offset=%09" PRIx64 " (ignoring)", + static_cast(s_DICMDBUF[1]) << 2); break; - // Probably only used by Wii - case DVDLowReadDvd: + // Wii-exclusive + case DICommand::ReadDVDMetadata: + switch ((s_DICMDBUF[0] >> 16) & 0xFF) + { + case 0: + ERROR_LOG(DVDINTERFACE, "DVDLowReadDvdPhysical"); + break; + case 1: + ERROR_LOG(DVDINTERFACE, "DVDLowReadDvdCopyright"); + break; + case 2: + ERROR_LOG(DVDINTERFACE, "DVDLowReadDvdDiscKey"); + break; + default: + ERROR_LOG(DVDINTERFACE, "Unknown 0xAD subcommand in %08x", s_DICMDBUF[0]); + break; + } + break; + // Wii-exclusive + case DICommand::ReadDVD: ERROR_LOG(DVDINTERFACE, "DVDLowReadDvd"); break; - // Probably only used by Wii - case DVDLowReadDvdConfig: + // Wii-exclusive + case DICommand::ReadDVDConfig: ERROR_LOG(DVDINTERFACE, "DVDLowReadDvdConfig"); break; - // Probably only used by Wii - case DVDLowStopLaser: + // Wii-exclusive + case DICommand::StopLaser: ERROR_LOG(DVDINTERFACE, "DVDLowStopLaser"); break; - // Probably only used by Wii - case DVDLowOffset: + // Wii-exclusive + case DICommand::Offset: ERROR_LOG(DVDINTERFACE, "DVDLowOffset"); break; - // Probably only used by Wii - case DVDLowReadDiskBca: + // Wii-exclusive + case DICommand::ReadBCA: WARN_LOG(DVDINTERFACE, "DVDLowReadDiskBca"); - Memory::Write_U32(1, output_address + 0x30); + Memory::Write_U32(1, s_DIMAR + 0x30); break; - // Probably only used by Wii - case DVDLowRequestDiscStatus: + // Wii-exclusive + case DICommand::RequestDiscStatus: ERROR_LOG(DVDINTERFACE, "DVDLowRequestDiscStatus"); break; - // Probably only used by Wii - case DVDLowRequestRetryNumber: + // Wii-exclusive + case DICommand::RequestRetryNumber: ERROR_LOG(DVDINTERFACE, "DVDLowRequestRetryNumber"); break; - // Probably only used by Wii - case DVDLowSetMaximumRotation: + // Wii-exclusive + case DICommand::SetMaximumRotation: ERROR_LOG(DVDINTERFACE, "DVDLowSetMaximumRotation"); break; - // Probably only used by Wii - case DVDLowSerMeasControl: + // Wii-exclusive + case DICommand::SerMeasControl: ERROR_LOG(DVDINTERFACE, "DVDLowSerMeasControl"); break; // Used by both GC and Wii - case DVDLowRequestError: + case DICommand::RequestError: INFO_LOG(DVDINTERFACE, "Requesting error... (0x%08x)", s_error_code); - WriteImmediate(s_error_code, output_address, reply_to_ios); + s_DIIMMBUF = s_error_code; SetHighError(0); break; - // Audio Stream (Immediate). Only seems to be used by some GC games + // Audio Stream (Immediate). Only used by some GC games, but does exist on the Wii // (command_0 >> 16) & 0xFF = Subcommand // command_1 << 2 = Offset on disc // command_2 = Length of the stream - case 0xE1: + case DICommand::AudioStream: { - u8 cancel_stream = (command_0 >> 16) & 0xFF; + u8 cancel_stream = (s_DICMDBUF[0] >> 16) & 0xFF; if (cancel_stream) { s_stop_at_track_end = false; @@ -917,14 +841,14 @@ void ExecuteCommand(u32 command_0, u32 command_1, u32 command_2, u32 output_addr } else { - if ((command_1 == 0) && (command_2 == 0)) + if ((s_DICMDBUF[1] == 0) && (s_DICMDBUF[2] == 0)) { s_stop_at_track_end = true; } else if (!s_stop_at_track_end) { - s_next_start = static_cast(command_1) << 2; - s_next_length = command_2; + s_next_start = static_cast(s_DICMDBUF[1]) << 2; + s_next_length = s_DICMDBUF[2]; if (!s_stream) { s_current_start = s_next_start; @@ -936,15 +860,15 @@ void ExecuteCommand(u32 command_0, u32 command_1, u32 command_2, u32 output_addr } } - INFO_LOG(DVDINTERFACE, "(Audio) Stream cmd: %08x offset: %08" PRIx64 " length: %08x", command_0, - (u64)command_1 << 2, command_2); + INFO_LOG(DVDINTERFACE, "(Audio) Stream cmd: %08x offset: %08" PRIx64 " length: %08x", + s_DICMDBUF[0], (u64)s_DICMDBUF[1] << 2, s_DICMDBUF[2]); } break; - // Request Audio Status (Immediate). Only seems to be used by some GC games - case 0xE2: + // Request Audio Status (Immediate). Only used by some GC games, but does exist on the Wii + case DICommand::RequestAudioStatus: { - switch (command_0 >> 16 & 0xFF) + switch (s_DICMDBUF[0] >> 16 & 0xFF) { case 0x00: // Returns streaming status INFO_LOG(DVDINTERFACE, @@ -953,38 +877,38 @@ void ExecuteCommand(u32 command_0, u32 command_1, u32 command_2, u32 output_addr "CurrentStart:%08" PRIx64 " CurrentLength:%08x", s_audio_position, s_current_start + s_current_length, s_current_start, s_current_length); - WriteImmediate(s_stream ? 1 : 0, output_address, reply_to_ios); + s_DIIMMBUF = (s_stream ? 1 : 0); break; case 0x01: // Returns the current offset INFO_LOG(DVDINTERFACE, "(Audio): Stream Status: Request Audio status AudioPos:%08" PRIx64, s_audio_position); - WriteImmediate(static_cast((s_audio_position & 0xffffffffffff8000ull) >> 2), - output_address, reply_to_ios); + s_DIIMMBUF = static_cast((s_audio_position & 0xffffffffffff8000ull) >> 2); break; case 0x02: // Returns the start offset INFO_LOG(DVDINTERFACE, "(Audio): Stream Status: Request Audio status CurrentStart:%08" PRIx64, s_current_start); - WriteImmediate(static_cast(s_current_start >> 2), output_address, reply_to_ios); + s_DIIMMBUF = static_cast(s_current_start >> 2); break; case 0x03: // Returns the total length INFO_LOG(DVDINTERFACE, "(Audio): Stream Status: Request Audio status CurrentLength:%08x", s_current_length); - WriteImmediate(s_current_length, output_address, reply_to_ios); + s_DIIMMBUF = s_current_length; break; default: INFO_LOG(DVDINTERFACE, "(Audio): Subcommand: %02x Request Audio status %s", - command_0 >> 16 & 0xFF, s_stream ? "on" : "off"); + s_DICMDBUF[0] >> 16 & 0xFF, s_stream ? "on" : "off"); break; } } break; - case DVDLowStopMotor: + // Used by both GC and Wii + case DICommand::StopMotor: { - INFO_LOG(DVDINTERFACE, "DVDLowStopMotor %s %s", command_1 ? "eject" : "", - command_2 ? "kill!" : ""); + INFO_LOG(DVDINTERFACE, "DVDLowStopMotor %s %s", s_DICMDBUF[1] ? "eject" : "", + s_DICMDBUF[2] ? "kill!" : ""); - const bool force_eject = command_1 && !command_2; + const bool force_eject = s_DICMDBUF[1] && !s_DICMDBUF[2]; if (Config::Get(Config::MAIN_AUTO_DISC_CHANGE) && !Movie::IsPlayingInput() && DVDThread::IsInsertedDiscRunning() && !s_auto_disc_change_paths.empty()) @@ -1001,7 +925,7 @@ void ExecuteCommand(u32 command_0, u32 command_1, u32 command_2, u32 output_addr } // DVD Audio Enable/Disable (Immediate). GC uses this, and apparently Wii also does...? - case DVDLowAudioBufferConfig: + case DICommand::AudioBufferConfig: // The IPL uses this command to enable or disable DTK audio depending on the value of byte 0x8 // in the disc header. See http://www.crazynation.org/GC/GC_DD_TECH/GCTech.htm for more info. // The link is dead, but you can access the page using the Wayback Machine at archive.org. @@ -1010,35 +934,36 @@ void ExecuteCommand(u32 command_0, u32 command_1, u32 command_2, u32 output_addr // Should we be failing with an error code when the game tries to use the 0xE1 command? // (Not that this should matter normally, since games that use DTK set the header byte to 1) - if ((command_0 >> 16) & 0xFF) + if ((s_DICMDBUF[0] >> 16) & 0xFF) INFO_LOG(DVDINTERFACE, "DTK enabled"); else INFO_LOG(DVDINTERFACE, "DTK disabled"); break; - // yet another (GC?) command we prolly don't care about - case 0xEE: + // GC-only patched drive firmware command, used by libogc + case DICommand::UnknownEE: INFO_LOG(DVDINTERFACE, "SetStatus"); break; // Debug commands; see yagcd. We don't really care // NOTE: commands to stream data will send...a raw data stream // This will appear as unknown commands, unless the check is re-instated to catch such data. - // Can probably only be used through direct access - case 0xFE: - ERROR_LOG(DVDINTERFACE, "Unsupported DVD Drive debug command 0x%08x", command_0); + // Can only be used through direct access and only after unlocked. + case DICommand::Debug: + ERROR_LOG(DVDINTERFACE, "Unsupported DVD Drive debug command 0x%08x", s_DICMDBUF[0]); break; // Unlock Commands. 1: "MATSHITA" 2: "DVD-GAME" // Just for fun - // Can probably only be used through direct access - case 0xFF: + // Can only be used through direct access. The unlock command doesn't seem to work on the Wii. + case DICommand::DebugUnlock: { - if (command_0 == 0xFF014D41 && command_1 == 0x54534849 && command_2 == 0x54410200) + if (s_DICMDBUF[0] == 0xFF014D41 && s_DICMDBUF[1] == 0x54534849 && s_DICMDBUF[2] == 0x54410200) { INFO_LOG(DVDINTERFACE, "Unlock test 1 passed"); } - else if (command_0 == 0xFF004456 && command_1 == 0x442D4741 && command_2 == 0x4D450300) + else if (s_DICMDBUF[0] == 0xFF004456 && s_DICMDBUF[1] == 0x442D4741 && + s_DICMDBUF[2] == 0x4D450300) { INFO_LOG(DVDINTERFACE, "Unlock test 2 passed"); } @@ -1050,9 +975,9 @@ void ExecuteCommand(u32 command_0, u32 command_1, u32 command_2, u32 output_addr break; default: - ERROR_LOG(DVDINTERFACE, "Unknown command 0x%08x (Buffer 0x%08x, 0x%x)", command_0, - output_address, output_length); - PanicAlertT("Unknown DVD command %08x - fatal error", command_0); + ERROR_LOG(DVDINTERFACE, "Unknown command 0x%08x (Buffer 0x%08x, 0x%x)", s_DICMDBUF[0], s_DIMAR, + s_DILENGTH); + PanicAlertT("Unknown DVD command %08x - fatal error", s_DICMDBUF[0]); break; } @@ -1065,6 +990,23 @@ void ExecuteCommand(u32 command_0, u32 command_1, u32 command_2, u32 output_addr } } +void PerformDecryptingRead(u32 position, u32 length, u32 output_address, ReplyType reply_type) +{ + DIInterruptType interrupt_type = DIInterruptType::TCINT; + + const bool command_handled_by_thread = + ExecuteReadCommand(static_cast(position) << 2, output_address, length, length, + s_current_partition, reply_type, &interrupt_type); + + if (!command_handled_by_thread) + { + // TODO: Needs testing to determine if COMMAND_LATENCY_US is accurate for this + CoreTiming::ScheduleEvent(COMMAND_LATENCY_US * (SystemTimers::GetTicksPerSecond() / 1000000), + s_finish_executing_command, + PackFinishExecutingCommandUserdata(reply_type, interrupt_type)); + } +} + u64 PackFinishExecutingCommandUserdata(ReplyType reply_type, DIInterruptType interrupt_type) { return (static_cast(reply_type) << 32) + static_cast(interrupt_type); @@ -1114,9 +1056,7 @@ void FinishExecutingCommand(ReplyType reply_type, DIInterruptType interrupt_type case ReplyType::IOS: { - auto di = IOS::HLE::GetIOS()->GetDeviceByName("/dev/di"); - if (di) - std::static_pointer_cast(di)->FinishIOCtl(interrupt_type); + IOS::HLE::Device::DI::InterruptFromDVDInterface(interrupt_type); break; } diff --git a/Source/Core/Core/HW/DVD/DVDInterface.h b/Source/Core/Core/HW/DVD/DVDInterface.h index b2144f2a0b..6007359e83 100644 --- a/Source/Core/Core/HW/DVD/DVDInterface.h +++ b/Source/Core/Core/HW/DVD/DVDInterface.h @@ -24,38 +24,31 @@ class Mapping; namespace DVDInterface { -enum DICommand +enum class DICommand : u8 { - DVDLowInquiry = 0x12, - DVDLowReadDiskID = 0x70, - DVDLowRead = 0x71, - DVDLowWaitForCoverClose = 0x79, - DVDLowGetCoverReg = 0x7a, // DVDLowPrepareCoverRegister? - DVDLowNotifyReset = 0x7e, - DVDLowReadDvdPhysical = 0x80, - DVDLowReadDvdCopyright = 0x81, - DVDLowReadDvdDiscKey = 0x82, - DVDLowClearCoverInterrupt = 0x86, - DVDLowGetCoverStatus = 0x88, - DVDLowReset = 0x8a, - DVDLowOpenPartition = 0x8b, - DVDLowClosePartition = 0x8c, - DVDLowUnencryptedRead = 0x8d, - DVDLowEnableDvdVideo = 0x8e, - DVDLowReportKey = 0xa4, - DVDLowSeek = 0xab, - DVDLowReadDvd = 0xd0, - DVDLowReadDvdConfig = 0xd1, - DVDLowStopLaser = 0xd2, - DVDLowOffset = 0xd9, - DVDLowReadDiskBca = 0xda, - DVDLowRequestDiscStatus = 0xdb, - DVDLowRequestRetryNumber = 0xdc, - DVDLowSetMaximumRotation = 0xdd, - DVDLowSerMeasControl = 0xdf, - DVDLowRequestError = 0xe0, - DVDLowStopMotor = 0xe3, - DVDLowAudioBufferConfig = 0xe4 + Inquiry = 0x12, + ReportKey = 0xa4, + Read = 0xa8, + Seek = 0xab, + ReadDVDMetadata = 0xad, + ReadDVD = 0xd0, + ReadDVDConfig = 0xd1, + StopLaser = 0xd2, + Offset = 0xd9, + ReadBCA = 0xda, + RequestDiscStatus = 0xdb, + RequestRetryNumber = 0xdc, + SetMaximumRotation = 0xdd, + SerMeasControl = 0xdf, + RequestError = 0xe0, + AudioStream = 0xe1, + RequestAudioStatus = 0xe2, + StopMotor = 0xe3, + AudioBufferConfig = 0xe4, + Debug = 0xfe, + DebugUnlock = 0xff, + Unknown55 = 0x55, + UnknownEE = 0xee, }; // "low" error codes @@ -86,12 +79,12 @@ constexpr u32 ERROR_MEDIUM = 0x062800; // Medium may have changed. constexpr u32 ERROR_MEDIUM_REQ = 0x0b5a01; // Operator medium removal request. constexpr u32 HIGH_ERROR_MASK = 0x00ffffff; -enum DIInterruptType : int +enum class DIInterruptType : int { - INT_DEINT = 0, - INT_TCINT = 1, - INT_BRKINT = 2, - INT_CVRINT = 3, + DEINT = 0, + TCINT = 1, + BRKINT = 2, + CVRINT = 3, }; enum class ReplyType : u32 @@ -99,7 +92,7 @@ enum class ReplyType : u32 NoReply, Interrupt, IOS, - DTK + DTK, }; enum class EjectCause @@ -132,8 +125,8 @@ bool UpdateRunningGameMetadata(std::optional title_id = {}); // Direct access to DI for IOS HLE (simpler to implement than how real IOS accesses DI, // and lets us skip encrypting/decrypting in some cases) void ChangePartition(const DiscIO::Partition& partition); -void ExecuteCommand(u32 command_0, u32 command_1, u32 command_2, u32 output_address, - u32 output_length, bool reply_to_ios); +void ExecuteCommand(ReplyType reply_type); +void PerformDecryptingRead(u32 position, u32 length, u32 output_address, ReplyType reply_type); void SetLowError(u32 low_error); void SetHighError(u32 high_error); @@ -142,4 +135,8 @@ void SetHighError(u32 high_error); void FinishExecutingCommand(ReplyType reply_type, DIInterruptType interrupt_type, s64 cycles_late, const std::vector& data = std::vector()); +// Used by IOS HLE +void SetInterruptEnabled(DIInterruptType interrupt, bool enabled); +void ClearInterrupt(DIInterruptType interrupt); + } // end of namespace DVDInterface diff --git a/Source/Core/Core/HW/DVD/DVDThread.cpp b/Source/Core/Core/HW/DVD/DVDThread.cpp index a75f7a4306..af60fb16c6 100644 --- a/Source/Core/Core/HW/DVD/DVDThread.cpp +++ b/Source/Core/Core/HW/DVD/DVDThread.cpp @@ -354,8 +354,8 @@ static void FinishRead(u64 id, s64 cycles_late) } // Notify the emulated software that the command has been executed - DVDInterface::FinishExecutingCommand(request.reply_type, DVDInterface::INT_TCINT, cycles_late, - buffer); + DVDInterface::FinishExecutingCommand(request.reply_type, DVDInterface::DIInterruptType::TCINT, + cycles_late, buffer); } static void DVDThread() diff --git a/Source/Core/Core/IOS/DI/DI.cpp b/Source/Core/Core/IOS/DI/DI.cpp index 21a4f71e46..f8e6514992 100644 --- a/Source/Core/Core/IOS/DI/DI.cpp +++ b/Source/Core/Core/IOS/DI/DI.cpp @@ -13,15 +13,36 @@ #include "Common/CommonTypes.h" #include "Common/Logging/Log.h" #include "Common/MsgHandler.h" +#include "Core/CoreTiming.h" #include "Core/HW/DVD/DVDInterface.h" #include "Core/HW/DVD/DVDThread.h" +#include "Core/HW/MMIO.h" #include "Core/HW/Memmap.h" #include "Core/IOS/ES/ES.h" #include "Core/IOS/ES/Formats.h" #include "DiscIO/Volume.h" +template +class RegisterWrapper +{ +public: + operator u32() const { return Memory::mmio_mapping->Read(addr); } + void operator=(u32 rhs) { Memory::mmio_mapping->Write(addr, rhs); } +}; +static RegisterWrapper<0x0D806000> DISR; +static RegisterWrapper<0x0D806004> DICVR; +static RegisterWrapper<0x0D806008> DICMDBUF0; +static RegisterWrapper<0x0D80600C> DICMDBUF1; +static RegisterWrapper<0x0D806010> DICMDBUF2; +static RegisterWrapper<0x0D806014> DIMAR; +static RegisterWrapper<0x0D806018> DILENGTH; +static RegisterWrapper<0x0D80601C> DICR; +static RegisterWrapper<0x0D806020> DIIMMBUF; + namespace IOS::HLE::Device { +CoreTiming::EventType* DI::s_finish_executing_di_command; + DI::DI(Kernel& ios, const std::string& device_name) : Device(ios, device_name) { } @@ -30,40 +51,49 @@ void DI::DoState(PointerWrap& p) { DoStateShared(p); p.Do(m_commands_to_execute); + p.Do(m_executing_command); + p.Do(m_has_initialized); + p.Do(m_last_length); +} + +IPCCommandResult DI::Open(const OpenRequest& request) +{ + InitializeIfFirstTime(); + return Device::Open(request); } IPCCommandResult DI::IOCtl(const IOCtlRequest& request) { - // DI IOCtls are handled in a special way by Dolphin - // compared to other IOS functions. - // This is a wrapper around DVDInterface's ExecuteCommand, - // which will execute commands more or less asynchronously. - // Only one command can be executed at a time, so commands - // are queued until DVDInterface is ready to handle them. + InitializeIfFirstTime(); - bool ready_to_execute = m_commands_to_execute.empty(); + // DI IOCtls are handled in a special way by Dolphin compared to other IOS functions. + // Many of them use DVDInterface's ExecuteCommand, which will execute commands more or less + // asynchronously. The rest are also treated as async to match this. Only one command can be + // executed at a time, so commands are queued until DVDInterface is ready to handle them. + + const u8 command = Memory::Read_U8(request.buffer_in); + if (request.request != command) + { + WARN_LOG( + IOS_DI, + "IOCtl: Received conflicting commands: ioctl 0x%02x, buffer 0x%02x. Using ioctl command.", + request.request, command); + } + + bool ready_to_execute = !m_executing_command.has_value(); m_commands_to_execute.push_back(request.address); - if (ready_to_execute) - StartIOCtl(request); - // DVDInterface handles the timing and will call FinishIOCtl after the command - // has been executed to reply to the request, so we shouldn't reply here. + if (ready_to_execute) + { + ProcessQueuedIOCtl(); + } + + // FinishIOCtl will be called after the command has been executed + // to reply to the request, so we shouldn't reply here. return GetNoReply(); } -void DI::StartIOCtl(const IOCtlRequest& request) -{ - const u32 command_0 = Memory::Read_U32(request.buffer_in); - const u32 command_1 = Memory::Read_U32(request.buffer_in + 4); - const u32 command_2 = Memory::Read_U32(request.buffer_in + 8); - - // DVDInterface's ExecuteCommand handles most of the work. - // The IOCtl callback is used to generate a reply afterwards. - DVDInterface::ExecuteCommand(command_0, command_1, command_2, request.buffer_out, - request.buffer_out_size, true); -} - -void DI::FinishIOCtl(DVDInterface::DIInterruptType interrupt_type) +void DI::ProcessQueuedIOCtl() { if (m_commands_to_execute.empty()) { @@ -71,29 +101,512 @@ void DI::FinishIOCtl(DVDInterface::DIInterruptType interrupt_type) return; } - // This command has been executed, so it's removed from the queue - u32 command_address = m_commands_to_execute.front(); + m_executing_command = {m_commands_to_execute.front()}; m_commands_to_execute.pop_front(); - m_ios.EnqueueIPCReply(IOCtlRequest{command_address}, interrupt_type); + + IOCtlRequest request{m_executing_command->m_request_address}; + auto finished = StartIOCtl(request); + if (finished) + { + CoreTiming::ScheduleEvent(2700 * SystemTimers::TIMER_RATIO, s_finish_executing_di_command, + static_cast(finished.value())); + return; + } +} + +std::optional DI::WriteIfFits(const IOCtlRequest& request, u32 value) +{ + if (request.buffer_out_size < 4) + { + WARN_LOG(IOS_DI, "Output buffer is too small to contain result; returning security error"); + return DIResult::SecurityError; + } + else + { + Memory::Write_U32(value, request.buffer_out); + return DIResult::Success; + } +} + +std::optional DI::StartIOCtl(const IOCtlRequest& request) +{ + if (request.buffer_in_size != 0x20) + { + ERROR_LOG(IOS_DI, "IOCtl: Received bad input buffer size 0x%02x, should be 0x20", + request.buffer_in_size); + return DIResult::SecurityError; + } + + // DVDInterface's ExecuteCommand handles most of the work for most of these. + // The IOCtl callback is used to generate a reply afterwards. + switch (static_cast(request.request)) + { + case DIIoctl::DVDLowInquiry: + INFO_LOG(IOS_DI, "DVDLowInquiry"); + DICMDBUF0 = 0x12000000; + DICMDBUF1 = 0; + return StartDMATransfer(0x20, request); + case DIIoctl::DVDLowReadDiskID: + INFO_LOG(IOS_DI, "DVDLowReadDiskID"); + DICMDBUF0 = 0xA8000040; + DICMDBUF1 = 0; + DICMDBUF2 = 0x20; + return StartDMATransfer(0x20, request); + // TODO: also include the post-read second read + case DIIoctl::DVDLowRead: + { + // TODO. Needs to include decryption. + const u32 length = Memory::Read_U32(request.buffer_in + 4); + const u32 position = Memory::Read_U32(request.buffer_in + 8); + INFO_LOG(IOS_DI, "DVDLowRead: offset 0x%08x (byte 0x%09" PRIx64 "), length 0x%x", position, + static_cast(position) << 2, length); + if (request.buffer_out_size < length) + { + WARN_LOG(IOS_DI, + "Output buffer is too small for the result of the read (%d bytes given, needed at " + "least %d); returning security error", + request.buffer_out_size, length); + return DIResult::SecurityError; + } + m_last_length = position; // An actual mistake in IOS + DVDInterface::PerformDecryptingRead(position, length, request.buffer_out, + DVDInterface::ReplyType::IOS); + return {}; + } + case DIIoctl::DVDLowWaitForCoverClose: + // This is a bit awkward to implement, as it doesn't return for a long time, so just act as if + // the cover was immediately closed + INFO_LOG(IOS_DI, "DVDLowWaitForCoverClose - skipping"); + return DIResult::CoverClosed; + case DIIoctl::DVDLowGetCoverRegister: + { + u32 dicvr = DICVR; + DEBUG_LOG(IOS_DI, "DVDLowGetCoverRegister 0x%08x", dicvr); + return WriteIfFits(request, dicvr); + } + case DIIoctl::DVDLowNotifyReset: + INFO_LOG(IOS_DI, "DVDLowNotifyReset"); + ResetDIRegisters(); + // Should also reset current partition and such + return DIResult::Success; + case DIIoctl::DVDLowSetSpinupFlag: + ERROR_LOG(IOS_DI, "DVDLowSetSpinupFlag - not a valid command, rejecting"); + return DIResult::BadArgument; + case DIIoctl::DVDLowReadDvdPhysical: + { + const u8 position = Memory::Read_U8(request.buffer_in + 7); + INFO_LOG(IOS_DI, "DVDLowReadDvdPhysical: position 0x%02x", position); + DICMDBUF0 = 0xAD000000 | (position << 8); + DICMDBUF1 = 0; + DICMDBUF2 = 0; + return StartDMATransfer(0x800, request); + } + case DIIoctl::DVDLowReadDvdCopyright: + { + const u8 position = Memory::Read_U8(request.buffer_in + 7); + INFO_LOG(IOS_DI, "DVDLowReadDvdCopyright: position 0x%02x", position); + DICMDBUF0 = 0xAD010000 | (position << 8); + DICMDBUF1 = 0; + DICMDBUF2 = 0; + return StartImmediateTransfer(request); + } + case DIIoctl::DVDLowReadDvdDiscKey: + { + const u8 position = Memory::Read_U8(request.buffer_in + 7); + INFO_LOG(IOS_DI, "DVDLowReadDvdDiscKey: position 0x%02x", position); + DICMDBUF0 = 0xAD020000 | (position << 8); + DICMDBUF1 = 0; + DICMDBUF2 = 0; + return StartDMATransfer(0x800, request); + } + case DIIoctl::DVDLowGetLength: + INFO_LOG(IOS_DI, "DVDLowGetLength 0x%08x", m_last_length); + return WriteIfFits(request, m_last_length); + case DIIoctl::DVDLowGetImmBuf: + { + u32 diimmbuf = DIIMMBUF; + INFO_LOG(IOS_DI, "DVDLowGetImmBuf 0x%08x", diimmbuf); + return WriteIfFits(request, diimmbuf); + } + case DIIoctl::DVDLowUnmaskCoverInterrupt: + INFO_LOG(IOS_DI, "DVDLowUnmaskCoverInterrupt"); + DVDInterface::SetInterruptEnabled(DVDInterface::DIInterruptType::CVRINT, false); + return DIResult::Success; + case DIIoctl::DVDLowClearCoverInterrupt: + DEBUG_LOG(IOS_DI, "DVDLowClearCoverInterrupt"); + DVDInterface::ClearInterrupt(DVDInterface::DIInterruptType::CVRINT); + return DIResult::Success; + case DIIoctl::DVDLowGetCoverStatus: + // TODO: handle resetting case + INFO_LOG(IOS_DI, "DVDLowGetCoverStatus: Disc %sInserted", + DVDInterface::IsDiscInside() ? "" : "Not "); + return WriteIfFits(request, DVDInterface::IsDiscInside() ? 2 : 1); + case DIIoctl::DVDLowEnableCoverInterrupt: + INFO_LOG(IOS_DI, "DVDLowEnableCoverInterrupt"); + DVDInterface::SetInterruptEnabled(DVDInterface::DIInterruptType::CVRINT, true); + return DIResult::Success; + case DIIoctl::DVDLowReset: + { + const bool spinup = Memory::Read_U32(request.address + 4); + INFO_LOG(IOS_DI, "DVDLowReset %s spinup", spinup ? "with" : "without"); + DVDInterface::Reset(); + ResetDIRegisters(); + // Should also reset current partition and such + return DIResult::Success; + } + case DIIoctl::DVDLowOpenPartition: + ERROR_LOG(IOS_DI, "DVDLowOpenPartition as an ioctl - rejecting"); + return DIResult::SecurityError; + case DIIoctl::DVDLowClosePartition: + INFO_LOG(IOS_DI, "DVDLowClosePartition"); + DVDInterface::ChangePartition(DiscIO::PARTITION_NONE); + return DIResult::Success; + case DIIoctl::DVDLowUnencryptedRead: + { + const u32 length = Memory::Read_U32(request.buffer_in + 4); + const u32 position = Memory::Read_U32(request.buffer_in + 8); + const u32 end = position + (length >> 2); // a 32-bit offset + INFO_LOG(IOS_DI, "DVDLowUnencryptedRead: offset 0x%08x (byte 0x%09" PRIx64 "), length 0x%x", + position, static_cast(position) << 2, length); + // (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), + }; + for (auto range : valid_ranges) + { + if (range.first <= position && position <= range.second && range.first <= end && + end <= range.second) + { + DICMDBUF0 = 0xA8000000; + DICMDBUF1 = position; + DICMDBUF2 = length; + return StartDMATransfer(length, request); + } + } + WARN_LOG(IOS_DI, "DVDLowUnencryptedRead: trying to read from an illegal region!"); + return DIResult::SecurityError; + } + case DIIoctl::DVDLowEnableDvdVideo: + ERROR_LOG(IOS_DI, "DVDLowEnableDvdVideo - rejecting"); + return DIResult::SecurityError; + // There are a bunch of ioctlvs that are also (unintentionally) usable as ioctls; reject these in + // Dolphin as games are unlikely to use them. + case DIIoctl::DVDLowGetNoDiscOpenPartitionParams: + ERROR_LOG(IOS_DI, "DVDLowGetNoDiscOpenPartitionParams as an ioctl - rejecting"); + return DIResult::SecurityError; + case DIIoctl::DVDLowNoDiscOpenPartition: + ERROR_LOG(IOS_DI, "DVDLowNoDiscOpenPartition as an ioctl - rejecting"); + return DIResult::SecurityError; + case DIIoctl::DVDLowGetNoDiscBufferSizes: + ERROR_LOG(IOS_DI, "DVDLowGetNoDiscBufferSizes as an ioctl - rejecting"); + return DIResult::SecurityError; + case DIIoctl::DVDLowOpenPartitionWithTmdAndTicket: + ERROR_LOG(IOS_DI, "DVDLowOpenPartitionWithTmdAndTicket as an ioctl - rejecting"); + return DIResult::SecurityError; + case DIIoctl::DVDLowOpenPartitionWithTmdAndTicketView: + ERROR_LOG(IOS_DI, "DVDLowOpenPartitionWithTmdAndTicketView as an ioctl - rejecting"); + return DIResult::SecurityError; + case DIIoctl::DVDLowGetStatusRegister: + { + u32 disr = DISR; + INFO_LOG(IOS_DI, "DVDLowGetStatusRegister: 0x%08x", disr); + return WriteIfFits(request, disr); + } + case DIIoctl::DVDLowGetControlRegister: + { + u32 dicr = DICR; + INFO_LOG(IOS_DI, "DVDLowGetControlRegister: 0x%08x", dicr); + return WriteIfFits(request, dicr); + } + case DIIoctl::DVDLowReportKey: + { + const u8 param1 = Memory::Read_U8(request.buffer_in + 7); + const u32 param2 = Memory::Read_U32(request.buffer_in + 8); + INFO_LOG(IOS_DI, "DVDLowReportKey: param1 0x%02x, param2 0x%06x", param1, param2); + DICMDBUF0 = 0xA4000000 | (param1 << 16); + DICMDBUF1 = param2 & 0xFFFFFF; + DICMDBUF2 = 0; + return StartDMATransfer(0x20, request); + } + case DIIoctl::DVDLowSeek: + { + const u32 position = Memory::Read_U32(request.buffer_in + 4); // 32-bit offset + INFO_LOG(IOS_DI, "DVDLowSeek: position 0x%08x, translated to 0x%08x", position, + position); // TODO: do partition translation! + DICMDBUF0 = 0xAB000000; + DICMDBUF1 = position; + return StartImmediateTransfer(request, false); + } + case DIIoctl::DVDLowReadDvd: + { + const u8 flag1 = Memory::Read_U8(request.buffer_in + 7); + const u8 flag2 = Memory::Read_U8(request.buffer_in + 11); + const u32 length = Memory::Read_U32(request.buffer_in + 12); + const u32 position = Memory::Read_U32(request.buffer_in + 16); + INFO_LOG(IOS_DI, "DVDLowReadDvd(%d, %d): position 0x%06x, length 0x%06x", flag1, flag2, + position, length); + DICMDBUF0 = 0xD0000000 | ((flag1 & 1) << 7) | ((flag2 & 1) << 6); + DICMDBUF1 = position & 0xFFFFFF; + DICMDBUF2 = length & 0xFFFFFF; + return StartDMATransfer(0x800 * length, request); + } + case DIIoctl::DVDLowReadDvdConfig: + { + const u8 flag1 = Memory::Read_U8(request.buffer_in + 7); + const u8 param2 = Memory::Read_U8(request.buffer_in + 11); + const u32 position = Memory::Read_U32(request.buffer_in + 12); + INFO_LOG(IOS_DI, "DVDLowReadDvdConfig(%d, %d): position 0x%06x", flag1, param2, position); + DICMDBUF0 = 0xD1000000 | ((flag1 & 1) << 16) | param2; + DICMDBUF1 = position & 0xFFFFFF; + DICMDBUF2 = 0; + return StartImmediateTransfer(request); + } + case DIIoctl::DVDLowStopLaser: + INFO_LOG(IOS_DI, "DVDLowStopLaser"); + DICMDBUF0 = 0xD2000000; + return StartImmediateTransfer(request); + case DIIoctl::DVDLowOffset: + { + const u8 flag = Memory::Read_U8(request.buffer_in + 7); + const u32 offset = Memory::Read_U32(request.buffer_in + 8); + INFO_LOG(IOS_DI, "DVDLowOffset(%d): offset 0x%08x", flag, offset); + DICMDBUF0 = 0xD9000000 | ((flag & 1) << 16); + DICMDBUF1 = offset; + return StartImmediateTransfer(request); + } + case DIIoctl::DVDLowReadDiskBca: + INFO_LOG(IOS_DI, "DVDLowReadDiskBca"); + DICMDBUF0 = 0xDA000000; + return StartDMATransfer(0x40, request); + case DIIoctl::DVDLowRequestDiscStatus: + INFO_LOG(IOS_DI, "DVDLowRequestDiscStatus"); + DICMDBUF0 = 0xDB000000; + return StartImmediateTransfer(request); + case DIIoctl::DVDLowRequestRetryNumber: + INFO_LOG(IOS_DI, "DVDLowRequestRetryNumber"); + DICMDBUF0 = 0xDC000000; + return StartImmediateTransfer(request); + case DIIoctl::DVDLowSetMaximumRotation: + { + const u8 speed = Memory::Read_U8(request.buffer_in + 7); + INFO_LOG(IOS_DI, "DVDLowSetMaximumRotation: speed %d", speed); + DICMDBUF0 = 0xDD000000 | ((speed & 3) << 16); + return StartImmediateTransfer(request, false); + } + case DIIoctl::DVDLowSerMeasControl: + { + const u8 flag1 = Memory::Read_U8(request.buffer_in + 7); + const u8 flag2 = Memory::Read_U8(request.buffer_in + 11); + INFO_LOG(IOS_DI, "DVDLowSerMeasControl(%d, %d)", flag1, flag2); + DICMDBUF0 = 0xDF000000 | ((flag1 & 1) << 17) | ((flag2 & 1) << 16); + return StartDMATransfer(0x20, request); + } + case DIIoctl::DVDLowRequestError: + INFO_LOG(IOS_DI, "DVDLowRequestError"); + DICMDBUF0 = 0xE0000000; + return StartImmediateTransfer(request); + case DIIoctl::DVDLowAudioStream: + { + const u8 mode = Memory::Read_U8(request.buffer_in + 7); + const u32 length = Memory::Read_U32(request.buffer_in + 8); + const u32 position = Memory::Read_U32(request.buffer_in + 12); + INFO_LOG(IOS_DI, "DVDLowAudioStream(%d): offset 0x%08x (byte 0x%09" PRIx64 "), length 0x%x", + mode, position, static_cast(position) << 2, length); + DICMDBUF0 = 0xE1000000 | ((mode & 3) << 16); + DICMDBUF1 = position; + DICMDBUF2 = length; + return StartImmediateTransfer(request, false); + } + case DIIoctl::DVDLowRequestAudioStatus: + { + const u8 mode = Memory::Read_U8(request.buffer_in + 7); + INFO_LOG(IOS_DI, "DVDLowRequestAudioStatus(%d)", mode); + DICMDBUF0 = 0xE2000000 | ((mode & 3) << 16); + DICMDBUF1 = 0; + // Note that this command does not copy the value written to DIIMMBUF, which makes it rather + // useless (to actually use it, DVDLowGetImmBuf would need to be used afterwards) + return StartImmediateTransfer(request, false); + } + case DIIoctl::DVDLowStopMotor: + { + const u8 eject = Memory::Read_U8(request.buffer_in + 7); + const u8 kill = Memory::Read_U8(request.buffer_in + 11); + INFO_LOG(IOS_DI, "DVDLowStopMotor(%d, %d)", eject, kill); + DICMDBUF0 = 0xE3000000 | ((eject & 1) << 17) | ((kill & 1) << 20); + DICMDBUF1 = 0; + return StartImmediateTransfer(request); + } + case DIIoctl::DVDLowAudioBufferConfig: + { + const u8 enable = Memory::Read_U8(request.buffer_in + 7); + const u8 buffer_size = Memory::Read_U8(request.buffer_in + 11); + INFO_LOG(IOS_DI, "DVDLowAudioBufferConfig: %s, buffer size %d", enable ? "enabled" : "disabled", + buffer_size); + DICMDBUF0 = 0xE4000000 | ((enable & 1) << 16) | (buffer_size & 0xf); + DICMDBUF1 = 0; + // On the other hand, this command *does* copy DIIMMBUF, but the actual code in the drive never + // writes anything to it, so it just copies over a stale value (e.g. from DVDLowRequestError). + return StartImmediateTransfer(request); + } + default: + ERROR_LOG(IOS_DI, "Unknown ioctl 0x%02x", request.request); + return DIResult::SecurityError; + } +} + +std::optional DI::StartDMATransfer(u32 command_length, const IOCtlRequest& request) +{ + if (request.buffer_out_size < command_length) + { + // Actual /dev/di will still send a command, but won't write the length or output address, + // causing it to eventually time out after 15 seconds. Just immediately time out here, + // instead. + WARN_LOG(IOS_DI, + "Output buffer is too small for the result of the command (%d bytes given, needed at " + "least %d); returning read timed out (immediately, instead of waiting)", + request.buffer_out_size, command_length); + return DIResult::ReadTimedOut; + } + + if ((command_length & 0x1f) != 0 || (request.buffer_out & 0x1f) != 0) + { + // In most cases, the actual DI driver just hangs for unaligned data, but let's be a bit more + // gentle. + WARN_LOG(IOS_DI, + "Output buffer or length is incorrectly aligned (buffer 0x%08x, buffer length %x, " + "command length %x)", + request.buffer_out, request.buffer_out_size, command_length); + return DIResult::BadArgument; + } + + DIMAR = request.buffer_out; + m_last_length = command_length; + DILENGTH = command_length; + + DVDInterface::ExecuteCommand(DVDInterface::ReplyType::IOS); + // Reply will be posted when done by FinishIOCtl. + return {}; +} +std::optional DI::StartImmediateTransfer(const IOCtlRequest& request, + bool write_to_buf) +{ + if (write_to_buf && request.buffer_out_size < 4) + { + WARN_LOG(IOS_DI, + "Output buffer size is too small for an immediate transfer (%d bytes, should be at " + "least 4). Performing transfer anyways.", + request.buffer_out_size); + } + + m_executing_command->m_copy_diimmbuf = write_to_buf; + + DVDInterface::ExecuteCommand(DVDInterface::ReplyType::IOS); + // Reply will be posted when done by FinishIOCtl. + return {}; +} + +void DI::InterruptFromDVDInterface(DVDInterface::DIInterruptType interrupt_type) +{ + DIResult result; + switch (interrupt_type) + { + case DVDInterface::DIInterruptType::TCINT: + result = DIResult::Success; + break; + case DVDInterface::DIInterruptType::DEINT: + result = DIResult::DriveError; + break; + default: + PanicAlert("IOS::HLE::Device::DI: Unexpected DVDInterface interrupt %d!", + static_cast(interrupt_type)); + result = DIResult::DriveError; + break; + } + + auto di = IOS::HLE::GetIOS()->GetDeviceByName("/dev/di"); + if (di) + { + std::static_pointer_cast(di)->FinishDICommand(result); + } + else + { + PanicAlert("IOS::HLE::Device::DI: Received interrupt from DVDInterface when device wasn't " + "registered!"); + } +} + +void DI::FinishDICommandCallback(u64 userdata, s64 ticksbehind) +{ + const DIResult result = static_cast(userdata); + + auto di = IOS::HLE::GetIOS()->GetDeviceByName("/dev/di"); + if (di) + std::static_pointer_cast(di)->FinishDICommand(result); + else + PanicAlert("IOS::HLE::Device::DI: Received interrupt from DI when device wasn't registered!"); +} + +void DI::FinishDICommand(DIResult result) +{ + if (!m_executing_command.has_value()) + { + PanicAlert("IOS::HLE::Device::DI: There is no command to finish!"); + return; + } + + IOCtlRequest request{m_executing_command->m_request_address}; + if (m_executing_command->m_copy_diimmbuf) + Memory::Write_U32(DIIMMBUF, request.buffer_out); + + m_ios.EnqueueIPCReply(request, static_cast(result)); + + m_executing_command.reset(); // DVDInterface is now ready to execute another command, // so we start executing a command from the queue if there is one if (!m_commands_to_execute.empty()) { - IOCtlRequest next_request{m_commands_to_execute.front()}; - StartIOCtl(next_request); + ProcessQueuedIOCtl(); } } IPCCommandResult DI::IOCtlV(const IOCtlVRequest& request) { - for (const auto& vector : request.io_vectors) - Memory::Memset(vector.address, 0, vector.size); - s32 return_value = IPC_SUCCESS; - switch (request.request) + // IOCtlVs are not queued since they don't (currently) go into DVDInterface and act + // asynchronously. This does mean that an IOCtlV can be executed while an IOCtl is in progress, + // which isn't ideal. + InitializeIfFirstTime(); + + if (request.in_vectors[0].size != 0x20) { - case DVDInterface::DVDLowOpenPartition: + ERROR_LOG(IOS_DI, "IOCtlV: Received bad input buffer size 0x%02x, should be 0x20", + request.in_vectors[0].size); + return GetDefaultReply(static_cast(DIResult::BadArgument)); + } + const u8 command = Memory::Read_U8(request.in_vectors[0].address); + if (request.request != command) { + WARN_LOG(IOS_DI, + "IOCtlV: Received conflicting commands: ioctl 0x%02x, buffer 0x%02x. Using ioctlv " + "command.", + request.request, command); + } + + DIResult return_value = DIResult::BadArgument; + switch (static_cast(request.request)) + { + case DIIoctl::DVDLowOpenPartition: + { + if (request.in_vectors.size() != 3 || request.io_vectors.size() != 2) + { + ERROR_LOG(IOS_DI, "DVDLowOpenPartition: bad vector count %zu in/%zu out", + request.in_vectors.size(), request.io_vectors.size()); + break; + } DEBUG_ASSERT_MSG(IOS_DI, request.in_vectors[1].address == 0, "DVDLowOpenPartition with ticket"); DEBUG_ASSERT_MSG(IOS_DI, request.in_vectors[2].address == 0, "DVDLowOpenPartition with cert chain"); @@ -103,20 +616,69 @@ IPCCommandResult DI::IOCtlV(const IOCtlVRequest& request) const DiscIO::Partition partition(partition_offset); DVDInterface::ChangePartition(partition); - INFO_LOG(IOS_DI, "DVDLowOpenPartition: partition_offset 0x%016" PRIx64, partition_offset); + INFO_LOG(IOS_DI, "DVDLowOpenPartition: partition_offset 0x%09" PRIx64, partition_offset); // Read TMD to the buffer const IOS::ES::TMDReader tmd = DVDThread::GetTMD(partition); const std::vector& raw_tmd = tmd.GetBytes(); Memory::CopyToEmu(request.io_vectors[0].address, raw_tmd.data(), raw_tmd.size()); - m_ios.GetES()->DIVerify(tmd, DVDThread::GetTicket(partition)); - return_value = 1; + ReturnCode es_result = m_ios.GetES()->DIVerify(tmd, DVDThread::GetTicket(partition)); + Memory::Write_U32(es_result, request.io_vectors[1].address); + + return_value = DIResult::Success; break; } + case DIIoctl::DVDLowGetNoDiscOpenPartitionParams: + ERROR_LOG(IOS_DI, "DVDLowGetNoDiscOpenPartitionParams - dummied out"); + request.DumpUnknown(GetDeviceName(), Common::Log::IOS_DI); + break; + case DIIoctl::DVDLowNoDiscOpenPartition: + ERROR_LOG(IOS_DI, "DVDLowNoDiscOpenPartition - dummied out"); + request.DumpUnknown(GetDeviceName(), Common::Log::IOS_DI); + break; + case DIIoctl::DVDLowGetNoDiscBufferSizes: + ERROR_LOG(IOS_DI, "DVDLowGetNoDiscBufferSizes - dummied out"); + request.DumpUnknown(GetDeviceName(), Common::Log::IOS_DI); + break; + case DIIoctl::DVDLowOpenPartitionWithTmdAndTicket: + ERROR_LOG(IOS_DI, "DVDLowOpenPartitionWithTmdAndTicket - not implemented"); + break; + case DIIoctl::DVDLowOpenPartitionWithTmdAndTicketView: + ERROR_LOG(IOS_DI, "DVDLowOpenPartitionWithTmdAndTicketView - not implemented"); + break; default: + ERROR_LOG(IOS_DI, "Unknown ioctlv 0x%02x", request.request); request.DumpUnknown(GetDeviceName(), Common::Log::IOS_DI); } - return GetDefaultReply(return_value); + return GetDefaultReply(static_cast(return_value)); +} + +void DI::InitializeIfFirstTime() +{ + // Match the behavior of Nintendo's initDvdDriverStage2, which is called the first time + // an open/ioctl/ioctlv occurs. This behavior is observable by directly reading the DI registers, + // so it shouldn't be handled in the constructor. + // Note that ResetDIRegisters also clears the current partition, which actually normally happens + // earlier; however, that is not observable. + // Also, registers are not cleared if syscall_check_di_reset (0x46) returns true (bit 10 of + // HW_RESETS is set), which is not currently emulated. + if (!m_has_initialized) + { + ResetDIRegisters(); + m_has_initialized = true; + } +} + +void DI::ResetDIRegisters() +{ + // Clear transfer complete and error interrupts (normally r/z, but here we just directly write + // zero) + DVDInterface::ClearInterrupt(DVDInterface::DIInterruptType::TCINT); + DVDInterface::ClearInterrupt(DVDInterface::DIInterruptType::DEINT); + // Enable transfer complete and error interrupts, and disable cover interrupt + DVDInterface::SetInterruptEnabled(DVDInterface::DIInterruptType::TCINT, true); + DVDInterface::SetInterruptEnabled(DVDInterface::DIInterruptType::DEINT, true); + DVDInterface::SetInterruptEnabled(DVDInterface::DIInterruptType::CVRINT, false); } } // namespace IOS::HLE::Device diff --git a/Source/Core/Core/IOS/DI/DI.h b/Source/Core/Core/IOS/DI/DI.h index 2b01784cc1..f52980f3ad 100644 --- a/Source/Core/Core/IOS/DI/DI.h +++ b/Source/Core/Core/IOS/DI/DI.h @@ -5,6 +5,7 @@ #pragma once #include +#include #include #include "Common/CommonTypes.h" @@ -15,7 +16,16 @@ class PointerWrap; namespace DVDInterface { -enum DIInterruptType : int; +enum class DIInterruptType : int; +} +namespace CoreTiming +{ +struct EventType; +} + +namespace IOS::HLE +{ +void Init(); } namespace IOS::HLE::Device @@ -25,16 +35,107 @@ class DI : public Device public: DI(Kernel& ios, const std::string& device_name); + static void InterruptFromDVDInterface(DVDInterface::DIInterruptType interrupt_type); + void DoState(PointerWrap& p) override; + IPCCommandResult Open(const OpenRequest& request) override; IPCCommandResult IOCtl(const IOCtlRequest& request) override; IPCCommandResult IOCtlV(const IOCtlVRequest& request) override; - void FinishIOCtl(DVDInterface::DIInterruptType interrupt_type); + enum class DIIoctl : u32 + { + DVDLowInquiry = 0x12, + DVDLowReadDiskID = 0x70, + DVDLowRead = 0x71, + DVDLowWaitForCoverClose = 0x79, + DVDLowGetCoverRegister = 0x7a, // DVDLowPrepareCoverRegister + DVDLowNotifyReset = 0x7e, + DVDLowSetSpinupFlag = 0x7f, + DVDLowReadDvdPhysical = 0x80, + DVDLowReadDvdCopyright = 0x81, + DVDLowReadDvdDiscKey = 0x82, + DVDLowGetLength = 0x83, + DVDLowGetImmBuf = 0x84, // Unconfirmed name + DVDLowUnmaskCoverInterrupt = 0x85, + DVDLowClearCoverInterrupt = 0x86, + // 0x87 is a dummied out command + DVDLowGetCoverStatus = 0x88, + DVDLowEnableCoverInterrupt = 0x89, // Unconfirmed name + DVDLowReset = 0x8a, + DVDLowOpenPartition = 0x8b, // ioctlv only + DVDLowClosePartition = 0x8c, + DVDLowUnencryptedRead = 0x8d, + DVDLowEnableDvdVideo = 0x8e, + DVDLowGetNoDiscOpenPartitionParams = 0x90, // ioctlv, dummied out + DVDLowNoDiscOpenPartition = 0x91, // ioctlv, dummied out + DVDLowGetNoDiscBufferSizes = 0x92, // ioctlv, dummied out + DVDLowOpenPartitionWithTmdAndTicket = 0x93, // ioctlv + DVDLowOpenPartitionWithTmdAndTicketView = 0x94, // ioctlv + DVDLowGetStatusRegister = 0x95, // DVDLowPrepareStatusRegsiter + DVDLowGetControlRegister = 0x96, // DVDLowPrepareControlRegister + DVDLowReportKey = 0xa4, + // 0xa8 is unusable externally + DVDLowSeek = 0xab, + DVDLowReadDvd = 0xd0, + DVDLowReadDvdConfig = 0xd1, + DVDLowStopLaser = 0xd2, + DVDLowOffset = 0xd9, + DVDLowReadDiskBca = 0xda, + DVDLowRequestDiscStatus = 0xdb, + DVDLowRequestRetryNumber = 0xdc, + DVDLowSetMaximumRotation = 0xdd, + DVDLowSerMeasControl = 0xdf, + DVDLowRequestError = 0xe0, + DVDLowAudioStream = 0xe1, + DVDLowRequestAudioStatus = 0xe2, + DVDLowStopMotor = 0xe3, + DVDLowAudioBufferConfig = 0xe4, + }; + + enum class DIResult : s32 + { + Success = 1, + DriveError = 2, + CoverClosed = 4, + ReadTimedOut = 16, + SecurityError = 32, + VerifyError = 64, + BadArgument = 128, + }; private: - void StartIOCtl(const IOCtlRequest& request); + struct ExecutingCommandInfo + { + ExecutingCommandInfo() {} + ExecutingCommandInfo(u32 request_address) + : m_request_address(request_address), m_copy_diimmbuf(false) + { + } + u32 m_request_address; + bool m_copy_diimmbuf; + }; + friend void ::IOS::HLE::Init(); + + void ProcessQueuedIOCtl(); + std::optional StartIOCtl(const IOCtlRequest& request); + std::optional WriteIfFits(const IOCtlRequest& request, u32 value); + std::optional StartDMATransfer(u32 command_length, const IOCtlRequest& request); + std::optional StartImmediateTransfer(const IOCtlRequest& request, + bool write_to_buf = true); + + void InitializeIfFirstTime(); + void ResetDIRegisters(); + static void FinishDICommandCallback(u64 userdata, s64 ticksbehind); + void FinishDICommand(DIResult result); + + static CoreTiming::EventType* s_finish_executing_di_command; + + std::optional m_executing_command; std::deque m_commands_to_execute; + + bool m_has_initialized = false; + u32 m_last_length = 0; }; } // namespace IOS::HLE::Device diff --git a/Source/Core/Core/IOS/IOS.cpp b/Source/Core/Core/IOS/IOS.cpp index 72c304b772..7ce51f2338 100644 --- a/Source/Core/Core/IOS/IOS.cpp +++ b/Source/Core/Core/IOS/IOS.cpp @@ -782,6 +782,9 @@ void Init() device->EventNotify(); }); + Device::DI::s_finish_executing_di_command = + CoreTiming::RegisterEvent("FinishDICommand", Device::DI::FinishDICommandCallback); + // Start with IOS80 to simulate part of the Wii boot process. s_ios = std::make_unique(Titles::SYSTEM_MENU_IOS); // On a Wii, boot2 launches the system menu IOS, which then launches the system menu From 7d6b9bcb40b6ace3cbc82324e5f9bb194406d00a Mon Sep 17 00:00:00 2001 From: Pokechu22 Date: Mon, 7 Oct 2019 10:37:33 -0700 Subject: [PATCH 10/17] Check for error 001 out of bounds reads in DVDThread All out of bounds reads should return the appropriate DI error, but it also makes sense to have the error 001 read happen there. --- Source/Core/Core/HW/DVD/DVDThread.cpp | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/Source/Core/Core/HW/DVD/DVDThread.cpp b/Source/Core/Core/HW/DVD/DVDThread.cpp index af60fb16c6..5f28518a91 100644 --- a/Source/Core/Core/HW/DVD/DVDThread.cpp +++ b/Source/Core/Core/HW/DVD/DVDThread.cpp @@ -342,20 +342,34 @@ static void FinishRead(u64 id, s64 cycles_late) (CoreTiming::GetTicks() - request.time_started_ticks) / (SystemTimers::GetTicksPerSecond() / 1000000)); + DVDInterface::DIInterruptType interrupt; if (buffer.size() != request.length) { - PanicAlertT("The disc could not be read (at 0x%" PRIx64 " - 0x%" PRIx64 ").", - request.dvd_offset, request.dvd_offset + request.length); + if (request.dvd_offset != 0x118280000 && request.dvd_offset != 0x1FB500000) + { + PanicAlertT("The disc could not be read (at 0x%" PRIx64 " - 0x%" PRIx64 ").", + request.dvd_offset, request.dvd_offset + request.length); + } + else + { + // Part of the error 001 check. + INFO_LOG(DVDINTERFACE, "Ignoring out of bounds test read (at 0x%" PRIx64 " - 0x%" PRIx64 ")", + request.dvd_offset, request.dvd_offset + request.length); + } + + DVDInterface::SetHighError(DVDInterface::ERROR_BLOCK_OOB); + interrupt = DVDInterface::DIInterruptType::DEINT; } else { if (request.copy_to_ram) Memory::CopyToEmu(request.output_address, buffer.data(), request.length); + + interrupt = DVDInterface::DIInterruptType::TCINT; } // Notify the emulated software that the command has been executed - DVDInterface::FinishExecutingCommand(request.reply_type, DVDInterface::DIInterruptType::TCINT, - cycles_late, buffer); + DVDInterface::FinishExecutingCommand(request.reply_type, interrupt, cycles_late, buffer); } static void DVDThread() From 55a88ba2ed61cfa68a6fde705f4a5af6138fcd88 Mon Sep 17 00:00:00 2001 From: Pokechu22 Date: Sat, 5 Oct 2019 14:55:14 -0700 Subject: [PATCH 11/17] Track drive state better, reporting errors if the state is wrong Also, fix DVDLowStopMotor logging (which was based on the ioctl parameters) --- Source/Core/Core/Boot/Boot.cpp | 11 ++++ Source/Core/Core/Boot/Boot.h | 1 + Source/Core/Core/Boot/Boot_BS2Emu.cpp | 4 +- Source/Core/Core/HW/DVD/DVDInterface.cpp | 81 +++++++++++++++++++++--- Source/Core/Core/HW/DVD/DVDInterface.h | 2 +- Source/Core/Core/IOS/DI/DI.cpp | 2 +- 6 files changed, 88 insertions(+), 13 deletions(-) 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; From 71e8fb278f72443a098bf4bd6d900cefd54e84ec Mon Sep 17 00:00:00 2001 From: Pokechu22 Date: Wed, 4 Sep 2019 15:42:22 -0700 Subject: [PATCH 12/17] Return more errors from DTK --- Source/Core/Core/Boot/Boot_BS2Emu.cpp | 15 +++ Source/Core/Core/HW/DVD/DVDInterface.cpp | 145 ++++++++++++++++++----- Source/Core/Core/HW/DVD/DVDInterface.h | 2 + Source/Core/Core/IOS/DI/DI.cpp | 9 +- 4 files changed, 139 insertions(+), 32 deletions(-) diff --git a/Source/Core/Core/Boot/Boot_BS2Emu.cpp b/Source/Core/Core/Boot/Boot_BS2Emu.cpp index 25efebb12b..77fb61170e 100644 --- a/Source/Core/Core/Boot/Boot_BS2Emu.cpp +++ b/Source/Core/Core/Boot/Boot_BS2Emu.cpp @@ -212,6 +212,21 @@ bool CBoot::EmulatedBS2_GC(const DiscIO::VolumeDisc& volume) DVDReadDiscID(volume, 0x00000000); + bool streaming = Memory::Read_U8(0x80000008); + if (streaming) + { + u8 streaming_size = Memory::Read_U8(0x80000009); + // If the streaming buffer size is 0, then BS2 uses a default size of 10 instead. + // No known game uses a size other than the default. + if (streaming_size == 0) + streaming_size = 10; + DVDInterface::AudioBufferConfig(true, streaming_size); + } + else + { + DVDInterface::AudioBufferConfig(false, 0); + } + const bool ntsc = DiscIO::IsNTSC(SConfig::GetInstance().m_region); // Setup pointers like real BS2 does diff --git a/Source/Core/Core/HW/DVD/DVDInterface.cpp b/Source/Core/Core/HW/DVD/DVDInterface.cpp index 9f54563fb7..ad45814fee 100644 --- a/Source/Core/Core/HW/DVD/DVDInterface.cpp +++ b/Source/Core/Core/HW/DVD/DVDInterface.cpp @@ -155,6 +155,9 @@ static u32 s_current_length; static u64 s_next_start; static u32 s_next_length; static u32 s_pending_samples; +static bool s_can_configure_dtk = true; +static bool s_enable_dtk = false; +static u8 s_dtk_buffer_length = 0; // TODO: figure out how this affects the regular buffer // Disc drive state static u32 s_error_code = 0; @@ -215,6 +218,9 @@ void DoState(PointerWrap& p) p.Do(s_next_start); p.Do(s_next_length); p.Do(s_pending_samples); + p.Do(s_can_configure_dtk); + p.Do(s_enable_dtk); + p.Do(s_dtk_buffer_length); p.Do(s_error_code); p.Do(s_current_partition); @@ -285,21 +291,31 @@ static u32 AdvanceDTK(u32 maximum_samples, u32* samples_to_process) return bytes_to_process; } -static void DTKStreamingCallback(const std::vector& audio_data, s64 cycles_late) +static void DTKStreamingCallback(DIInterruptType interrupt_type, const std::vector& audio_data, + s64 cycles_late) { - // Send audio to the mixer. - std::vector temp_pcm(s_pending_samples * 2, 0); - ProcessDTKSamples(&temp_pcm, audio_data); - 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 = 0; u32 read_length = 0; - if (s_stream && AudioInterface::IsPlaying()) + + if (interrupt_type == DIInterruptType::TCINT) { - read_offset = s_audio_position; - read_length = AdvanceDTK(MAXIMUM_SAMPLES, &s_pending_samples); + // Send audio to the mixer. + std::vector temp_pcm(s_pending_samples * 2, 0); + ProcessDTKSamples(&temp_pcm, audio_data); + g_sound_stream->GetMixer()->PushStreamingSamples(temp_pcm.data(), s_pending_samples); + + if (s_stream && AudioInterface::IsPlaying()) + { + read_offset = s_audio_position; + read_length = AdvanceDTK(MAXIMUM_SAMPLES, &s_pending_samples); + } + else + { + read_length = 0; + s_pending_samples = MAXIMUM_SAMPLES; + } } else { @@ -367,6 +383,9 @@ void Reset(bool spinup) s_current_start = 0; s_current_length = 0; s_pending_samples = 0; + s_can_configure_dtk = true; + s_enable_dtk = false; + s_dtk_buffer_length = 0; if (!IsDiscInside()) { @@ -792,6 +811,7 @@ void ExecuteCommand(ReplyType reply_type) ", DMABuffer = %08x, SrcLength = %08x, DMALength = %08x", iDVDOffset, s_DIMAR, s_DICMDBUF[2], s_DILENGTH); + s_can_configure_dtk = false; command_handled_by_thread = ExecuteReadCommand(iDVDOffset, s_DIMAR, s_DICMDBUF[2], s_DILENGTH, DiscIO::PARTITION_NONE, reply_type, &interrupt_type); @@ -808,6 +828,14 @@ void ExecuteCommand(ReplyType reply_type) { SetLowError(ERROR_READY); } + else + { + // The first disc ID reading is required before DTK can be configured. + // If the disc ID is read again (or any other read occurs), it no longer can + // be configured. + s_can_configure_dtk = false; + } + command_handled_by_thread = ExecuteReadCommand( 0, s_DIMAR, 0x20, s_DILENGTH, DiscIO::PARTITION_NONE, reply_type, &interrupt_type); break; @@ -894,22 +922,42 @@ void ExecuteCommand(ReplyType reply_type) // command_2 = Length of the stream case DICommand::AudioStream: { - u8 cancel_stream = (s_DICMDBUF[0] >> 16) & 0xFF; - if (cancel_stream) + if (!CheckReadPreconditions()) { - s_stop_at_track_end = false; - s_stream = false; + ERROR_LOG(DVDINTERFACE, "Cannot play audio (command %08x)", s_DICMDBUF[0]); + SetHighError(ERROR_AUDIO_BUF); + interrupt_type = DIInterruptType::DEINT; + break; } - else + if (!s_enable_dtk) { - if ((s_DICMDBUF[1] == 0) && (s_DICMDBUF[2] == 0)) + ERROR_LOG(DVDINTERFACE, + "Attempted to change playing audio while audio is disabled! (%08x %08x %08x)", + s_DICMDBUF[0], s_DICMDBUF[1], s_DICMDBUF[2]); + SetHighError(ERROR_AUDIO_BUF); + interrupt_type = DIInterruptType::DEINT; + break; + } + + s_can_configure_dtk = false; + + switch ((s_DICMDBUF[0] >> 16) & 0xFF) + { + case 0x00: + { + u64 offset = static_cast(s_DICMDBUF[1]) << 2; + u32 length = s_DICMDBUF[2]; + INFO_LOG(DVDINTERFACE, "(Audio) Start stream: offset: %08" PRIx64 " length: %08x", offset, + length); + + if ((offset == 0) && (length == 0)) { s_stop_at_track_end = true; } else if (!s_stop_at_track_end) { - s_next_start = static_cast(s_DICMDBUF[1]) << 2; - s_next_length = s_DICMDBUF[2]; + s_next_start = offset; + s_next_length = length; if (!s_stream) { s_current_start = s_next_start; @@ -919,16 +967,34 @@ void ExecuteCommand(ReplyType reply_type) s_stream = true; } } + break; + } + case 0x01: + INFO_LOG(DVDINTERFACE, "(Audio) Stop stream"); + s_stop_at_track_end = false; + s_stream = false; + break; + default: + ERROR_LOG(DVDINTERFACE, "Invalid audio command! (%08x %08x %08x)", s_DICMDBUF[0], + s_DICMDBUF[1], s_DICMDBUF[2]); + SetHighError(ERROR_INV_AUDIO); + interrupt_type = DIInterruptType::DEINT; + break; } - - INFO_LOG(DVDINTERFACE, "(Audio) Stream cmd: %08x offset: %08" PRIx64 " length: %08x", - s_DICMDBUF[0], (u64)s_DICMDBUF[1] << 2, s_DICMDBUF[2]); } break; // Request Audio Status (Immediate). Only used by some GC games, but does exist on the Wii case DICommand::RequestAudioStatus: { + if (!s_enable_dtk) + { + ERROR_LOG(DVDINTERFACE, "Attempted to request audio status while audio is disabled!"); + SetHighError(ERROR_AUDIO_BUF); + interrupt_type = DIInterruptType::DEINT; + break; + } + switch (s_DICMDBUF[0] >> 16 & 0xFF) { case 0x00: // Returns streaming status @@ -956,8 +1022,10 @@ void ExecuteCommand(ReplyType reply_type) s_DIIMMBUF = s_current_length; break; default: - INFO_LOG(DVDINTERFACE, "(Audio): Subcommand: %02x Request Audio status %s", - s_DICMDBUF[0] >> 16 & 0xFF, s_stream ? "on" : "off"); + ERROR_LOG(DVDINTERFACE, "Invalid audio status command! (%08x %08x %08x)", s_DICMDBUF[0], + s_DICMDBUF[1], s_DICMDBUF[2]); + SetHighError(ERROR_INV_AUDIO); + interrupt_type = DIInterruptType::DEINT; break; } } @@ -987,20 +1055,24 @@ void ExecuteCommand(ReplyType reply_type) break; } - // DVD Audio Enable/Disable (Immediate). GC uses this, and apparently Wii also does...? + // DVD Audio Enable/Disable (Immediate). GC uses this, and the Wii can use it to configure GC + // games. case DICommand::AudioBufferConfig: // The IPL uses this command to enable or disable DTK audio depending on the value of byte 0x8 // in the disc header. See http://www.crazynation.org/GC/GC_DD_TECH/GCTech.htm for more info. // The link is dead, but you can access the page using the Wayback Machine at archive.org. - // TODO: Dolphin doesn't prevent the game from using DTK when the IPL doesn't enable it. - // Should we be failing with an error code when the game tries to use the 0xE1 command? - // (Not that this should matter normally, since games that use DTK set the header byte to 1) + // This command can only be used immediately after reading the disc ID, before any other + // reads. Too early, and you get ERROR_NO_DISKID. Too late, and you get ERROR_INV_PERIOD. + if (!s_can_configure_dtk) + { + ERROR_LOG(DVDINTERFACE, "Attempted to change DTK configuration after a read has been made!"); + SetHighError(ERROR_INV_PERIOD); + interrupt_type = DIInterruptType::DEINT; + break; + } - if ((s_DICMDBUF[0] >> 16) & 0xFF) - INFO_LOG(DVDINTERFACE, "DTK enabled"); - else - INFO_LOG(DVDINTERFACE, "DTK disabled"); + AudioBufferConfig((s_DICMDBUF[0] >> 16) & 1, s_DICMDBUF[0] & 0xf); break; // GC-only patched drive firmware command, used by libogc @@ -1056,6 +1128,7 @@ void ExecuteCommand(ReplyType reply_type) void PerformDecryptingRead(u32 position, u32 length, u32 output_address, ReplyType reply_type) { DIInterruptType interrupt_type = DIInterruptType::TCINT; + s_can_configure_dtk = false; const bool command_handled_by_thread = ExecuteReadCommand(static_cast(position) << 2, output_address, length, length, @@ -1070,6 +1143,16 @@ void PerformDecryptingRead(u32 position, u32 length, u32 output_address, ReplyTy } } +void AudioBufferConfig(bool enable_dtk, u8 dtk_buffer_length) +{ + s_enable_dtk = enable_dtk; + s_dtk_buffer_length = dtk_buffer_length; + if (s_enable_dtk) + INFO_LOG(DVDINTERFACE, "DTK enabled: buffer size %d", s_dtk_buffer_length); + else + INFO_LOG(DVDINTERFACE, "DTK disabled"); +} + u64 PackFinishExecutingCommandUserdata(ReplyType reply_type, DIInterruptType interrupt_type) { return (static_cast(reply_type) << 32) + static_cast(interrupt_type); @@ -1125,7 +1208,7 @@ void FinishExecutingCommand(ReplyType reply_type, DIInterruptType interrupt_type case ReplyType::DTK: { - DTKStreamingCallback(data, cycles_late); + DTKStreamingCallback(interrupt_type, data, cycles_late); break; } } diff --git a/Source/Core/Core/HW/DVD/DVDInterface.h b/Source/Core/Core/HW/DVD/DVDInterface.h index 37a7bbf8d8..0242c29b29 100644 --- a/Source/Core/Core/HW/DVD/DVDInterface.h +++ b/Source/Core/Core/HW/DVD/DVDInterface.h @@ -127,6 +127,8 @@ bool UpdateRunningGameMetadata(std::optional title_id = {}); void ChangePartition(const DiscIO::Partition& partition); void ExecuteCommand(ReplyType reply_type); void PerformDecryptingRead(u32 position, u32 length, u32 output_address, 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); void SetLowError(u32 low_error); void SetHighError(u32 high_error); diff --git a/Source/Core/Core/IOS/DI/DI.cpp b/Source/Core/Core/IOS/DI/DI.cpp index e4eeff686a..70a6038a47 100644 --- a/Source/Core/Core/IOS/DI/DI.cpp +++ b/Source/Core/Core/IOS/DI/DI.cpp @@ -152,7 +152,14 @@ std::optional DI::StartIOCtl(const IOCtlRequest& request) DICMDBUF1 = 0; DICMDBUF2 = 0x20; return StartDMATransfer(0x20, request); - // TODO: also include the post-read second read + // TODO: Include an additional read that happens on Wii discs, or at least + // emulate its side effect of disabling DTK configuration + // if (Memory::Read_U32(request.buffer_out + 24) == 0x5d1c9ea3) { // Wii Magic + // if (!m_has_read_encryption_info) { + // // Read 0x44 (=> 0x60) bytes starting from offset 8 or byte 0x20; + // // byte 0x60 is disable hashing and byte 0x61 is disable encryption + // } + // } case DIIoctl::DVDLowRead: { // TODO. Needs to include decryption. From 6c0399103fc430a663b48211fafdcef1a4f79039 Mon Sep 17 00:00:00 2001 From: Pokechu22 Date: Tue, 17 Sep 2019 13:57:57 -0700 Subject: [PATCH 13/17] Handle partitions in /dev/di, not DVDInterface Partitions are Wii-exclusive, and don't happen at the DVDInterface level in IOS. This isn't quite the cleanest fix, but it gets rid of the assumption that a partition is open on starting the game at least. --- Source/Core/Core/Boot/Boot_BS2Emu.cpp | 7 ++++ Source/Core/Core/HW/DVD/DVDInterface.cpp | 19 +++------ Source/Core/Core/HW/DVD/DVDInterface.h | 4 +- Source/Core/Core/IOS/DI/DI.cpp | 50 ++++++++++++++++++------ Source/Core/Core/IOS/DI/DI.h | 7 ++++ 5 files changed, 60 insertions(+), 27 deletions(-) diff --git a/Source/Core/Core/Boot/Boot_BS2Emu.cpp b/Source/Core/Core/Boot/Boot_BS2Emu.cpp index 77fb61170e..a85a939e79 100644 --- a/Source/Core/Core/Boot/Boot_BS2Emu.cpp +++ b/Source/Core/Core/Boot/Boot_BS2Emu.cpp @@ -22,6 +22,7 @@ #include "Core/HW/DVD/DVDInterface.h" #include "Core/HW/EXI/EXI_DeviceIPL.h" #include "Core/HW/Memmap.h" +#include "Core/IOS/DI/DI.h" #include "Core/IOS/ES/ES.h" #include "Core/IOS/ES/Formats.h" #include "Core/IOS/FS/FileSystem.h" @@ -421,6 +422,12 @@ bool CBoot::EmulatedBS2_Wii(const DiscIO::VolumeDisc& volume) if (!SetupWiiMemory(console_type) || !IOS::HLE::GetIOS()->BootIOS(tmd.GetIOSId())) return false; + auto di = std::static_pointer_cast( + IOS::HLE::GetIOS()->GetDeviceByName("/dev/di")); + + di->InitializeIfFirstTime(); + di->ChangePartition(data_partition); + DVDReadDiscID(volume, 0x00000000); // This is some kind of consistency check that is compared to the 0x00 diff --git a/Source/Core/Core/HW/DVD/DVDInterface.cpp b/Source/Core/Core/HW/DVD/DVDInterface.cpp index ad45814fee..0e4fdc5b5f 100644 --- a/Source/Core/Core/HW/DVD/DVDInterface.cpp +++ b/Source/Core/Core/HW/DVD/DVDInterface.cpp @@ -161,7 +161,6 @@ static u8 s_dtk_buffer_length = 0; // TODO: figure out how this affects the reg // Disc drive state static u32 s_error_code = 0; -static DiscIO::Partition s_current_partition; // Disc drive timing static u64 s_read_buffer_start_time; @@ -223,7 +222,6 @@ void DoState(PointerWrap& p) p.Do(s_dtk_buffer_length); p.Do(s_error_code); - p.Do(s_current_partition); p.Do(s_read_buffer_start_time); p.Do(s_read_buffer_end_time); @@ -426,8 +424,6 @@ void SetDisc(std::unique_ptr disc, { bool had_disc = IsDiscInside(); bool has_disc = static_cast(disc); - if (has_disc) - s_current_partition = disc->GetGamePartition(); if (auto_disc_change_paths) { @@ -548,12 +544,8 @@ bool UpdateRunningGameMetadata(std::optional title_id) if (!DVDThread::HasDisc()) return false; - return DVDThread::UpdateRunningGameMetadata(s_current_partition, title_id); -} - -void ChangePartition(const DiscIO::Partition& partition) -{ - s_current_partition = partition; + return DVDThread::UpdateRunningGameMetadata(IOS::HLE::Device::DI::GetCurrentPartition(), + title_id); } void RegisterMMIO(MMIO::Mapping* mmio, u32 base) @@ -1125,14 +1117,15 @@ void ExecuteCommand(ReplyType reply_type) } } -void PerformDecryptingRead(u32 position, u32 length, u32 output_address, ReplyType reply_type) +void PerformDecryptingRead(u32 position, u32 length, u32 output_address, + const DiscIO::Partition& partition, ReplyType reply_type) { DIInterruptType interrupt_type = DIInterruptType::TCINT; s_can_configure_dtk = false; const bool command_handled_by_thread = - ExecuteReadCommand(static_cast(position) << 2, output_address, length, length, - s_current_partition, reply_type, &interrupt_type); + ExecuteReadCommand(static_cast(position) << 2, output_address, length, length, partition, + reply_type, &interrupt_type); if (!command_handled_by_thread) { diff --git a/Source/Core/Core/HW/DVD/DVDInterface.h b/Source/Core/Core/HW/DVD/DVDInterface.h index 0242c29b29..c0bd1bd4d0 100644 --- a/Source/Core/Core/HW/DVD/DVDInterface.h +++ b/Source/Core/Core/HW/DVD/DVDInterface.h @@ -124,9 +124,9 @@ bool UpdateRunningGameMetadata(std::optional title_id = {}); // Direct access to DI for IOS HLE (simpler to implement than how real IOS accesses DI, // and lets us skip encrypting/decrypting in some cases) -void ChangePartition(const DiscIO::Partition& partition); void ExecuteCommand(ReplyType reply_type); -void PerformDecryptingRead(u32 position, u32 length, u32 output_address, ReplyType reply_type); +void PerformDecryptingRead(u32 position, u32 length, u32 output_address, + const DiscIO::Partition& partition, 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 70a6038a47..7dc2f714e4 100644 --- a/Source/Core/Core/IOS/DI/DI.cpp +++ b/Source/Core/Core/IOS/DI/DI.cpp @@ -52,6 +52,7 @@ void DI::DoState(PointerWrap& p) DoStateShared(p); p.Do(m_commands_to_execute); p.Do(m_executing_command); + p.Do(m_current_partition); p.Do(m_has_initialized); p.Do(m_last_length); } @@ -162,11 +163,15 @@ std::optional DI::StartIOCtl(const IOCtlRequest& request) // } case DIIoctl::DVDLowRead: { - // TODO. Needs to include decryption. const u32 length = Memory::Read_U32(request.buffer_in + 4); const u32 position = Memory::Read_U32(request.buffer_in + 8); INFO_LOG(IOS_DI, "DVDLowRead: offset 0x%08x (byte 0x%09" PRIx64 "), length 0x%x", position, static_cast(position) << 2, length); + if (m_current_partition == DiscIO::PARTITION_NONE) + { + ERROR_LOG(IOS_DI, "Attempted to perform a decrypting read when no partition is open!"); + return DIResult::SecurityError; + } if (request.buffer_out_size < length) { WARN_LOG(IOS_DI, @@ -176,7 +181,7 @@ std::optional DI::StartIOCtl(const IOCtlRequest& request) return DIResult::SecurityError; } m_last_length = position; // An actual mistake in IOS - DVDInterface::PerformDecryptingRead(position, length, request.buffer_out, + DVDInterface::PerformDecryptingRead(position, length, request.buffer_out, m_current_partition, DVDInterface::ReplyType::IOS); return {}; } @@ -194,7 +199,6 @@ std::optional DI::StartIOCtl(const IOCtlRequest& request) case DIIoctl::DVDLowNotifyReset: INFO_LOG(IOS_DI, "DVDLowNotifyReset"); ResetDIRegisters(); - // Should also reset current partition and such return DIResult::Success; case DIIoctl::DVDLowSetSpinupFlag: ERROR_LOG(IOS_DI, "DVDLowSetSpinupFlag - not a valid command, rejecting"); @@ -258,7 +262,6 @@ std::optional DI::StartIOCtl(const IOCtlRequest& request) INFO_LOG(IOS_DI, "DVDLowReset %s spinup", spinup ? "with" : "without"); DVDInterface::Reset(spinup); ResetDIRegisters(); - // Should also reset current partition and such return DIResult::Success; } case DIIoctl::DVDLowOpenPartition: @@ -266,7 +269,7 @@ std::optional DI::StartIOCtl(const IOCtlRequest& request) return DIResult::SecurityError; case DIIoctl::DVDLowClosePartition: INFO_LOG(IOS_DI, "DVDLowClosePartition"); - DVDInterface::ChangePartition(DiscIO::PARTITION_NONE); + ChangePartition(DiscIO::PARTITION_NONE); return DIResult::Success; case DIIoctl::DVDLowUnencryptedRead: { @@ -614,23 +617,29 @@ IPCCommandResult DI::IOCtlV(const IOCtlVRequest& request) request.in_vectors.size(), request.io_vectors.size()); break; } - DEBUG_ASSERT_MSG(IOS_DI, request.in_vectors[1].address == 0, "DVDLowOpenPartition with ticket"); - DEBUG_ASSERT_MSG(IOS_DI, request.in_vectors[2].address == 0, - "DVDLowOpenPartition with cert chain"); + if (request.in_vectors[1].address != 0) + { + ERROR_LOG(IOS_DI, + "DVDLowOpenPartition with ticket - not implemented, ignoring ticket parameter"); + } + if (request.in_vectors[2].address != 0) + { + ERROR_LOG(IOS_DI, + "DVDLowOpenPartition with cert chain - not implemented, ignoring certs parameter"); + } const u64 partition_offset = static_cast(Memory::Read_U32(request.in_vectors[0].address + 4)) << 2; - const DiscIO::Partition partition(partition_offset); - DVDInterface::ChangePartition(partition); + ChangePartition(DiscIO::Partition(partition_offset)); INFO_LOG(IOS_DI, "DVDLowOpenPartition: partition_offset 0x%09" PRIx64, partition_offset); // Read TMD to the buffer - const IOS::ES::TMDReader tmd = DVDThread::GetTMD(partition); + const IOS::ES::TMDReader tmd = DVDThread::GetTMD(m_current_partition); const std::vector& raw_tmd = tmd.GetBytes(); Memory::CopyToEmu(request.io_vectors[0].address, raw_tmd.data(), raw_tmd.size()); - ReturnCode es_result = m_ios.GetES()->DIVerify(tmd, DVDThread::GetTicket(partition)); + ReturnCode es_result = m_ios.GetES()->DIVerify(tmd, DVDThread::GetTicket(m_current_partition)); Memory::Write_U32(es_result, request.io_vectors[1].address); return_value = DIResult::Success; @@ -661,6 +670,21 @@ IPCCommandResult DI::IOCtlV(const IOCtlVRequest& request) return GetDefaultReply(static_cast(return_value)); } +void DI::ChangePartition(const DiscIO::Partition partition) +{ + m_current_partition = partition; +} + +DiscIO::Partition DI::GetCurrentPartition() +{ + auto di = IOS::HLE::GetIOS()->GetDeviceByName("/dev/di"); + // Note that this function is called in Gamecube mode for UpdateRunningGameMetadata, + // so both cases are hit in normal circumstances. + if (!di) + return DiscIO::PARTITION_NONE; + return std::static_pointer_cast(di)->m_current_partition; +} + void DI::InitializeIfFirstTime() { // Match the behavior of Nintendo's initDvdDriverStage2, which is called the first time @@ -687,5 +711,7 @@ void DI::ResetDIRegisters() DVDInterface::SetInterruptEnabled(DVDInterface::DIInterruptType::TCINT, true); DVDInterface::SetInterruptEnabled(DVDInterface::DIInterruptType::DEINT, true); DVDInterface::SetInterruptEnabled(DVDInterface::DIInterruptType::CVRINT, false); + // Close the current partition, if there is one + ChangePartition(DiscIO::PARTITION_NONE); } } // namespace IOS::HLE::Device diff --git a/Source/Core/Core/IOS/DI/DI.h b/Source/Core/Core/IOS/DI/DI.h index f52980f3ad..975b0adc51 100644 --- a/Source/Core/Core/IOS/DI/DI.h +++ b/Source/Core/Core/IOS/DI/DI.h @@ -11,7 +11,9 @@ #include "Common/CommonTypes.h" #include "Core/IOS/Device.h" #include "Core/IOS/IOS.h" +#include "DiscIO/Volume.h" +class CBoot; class PointerWrap; namespace DVDInterface @@ -36,6 +38,7 @@ public: DI(Kernel& ios, const std::string& device_name); static void InterruptFromDVDInterface(DVDInterface::DIInterruptType interrupt_type); + static DiscIO::Partition GetCurrentPartition(); void DoState(PointerWrap& p) override; @@ -116,6 +119,7 @@ private: bool m_copy_diimmbuf; }; + friend class ::CBoot; friend void ::IOS::HLE::Init(); void ProcessQueuedIOCtl(); @@ -125,6 +129,7 @@ private: std::optional StartImmediateTransfer(const IOCtlRequest& request, bool write_to_buf = true); + void ChangePartition(const DiscIO::Partition partition); void InitializeIfFirstTime(); void ResetDIRegisters(); static void FinishDICommandCallback(u64 userdata, s64 ticksbehind); @@ -135,6 +140,8 @@ private: std::optional m_executing_command; std::deque m_commands_to_execute; + DiscIO::Partition m_current_partition = DiscIO::PARTITION_NONE; + bool m_has_initialized = false; u32 m_last_length = 0; }; From f1dc908883e14cdef07ba09bff8c286d53b7e8bf Mon Sep 17 00:00:00 2001 From: Pokechu22 Date: Thu, 3 Oct 2019 15:53:22 -0700 Subject: [PATCH 14/17] Clarify emulated behavior for ReadBCA --- Source/Core/Core/HW/DVD/DVDInterface.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/Source/Core/Core/HW/DVD/DVDInterface.cpp b/Source/Core/Core/HW/DVD/DVDInterface.cpp index 0e4fdc5b5f..fbb58b48d5 100644 --- a/Source/Core/Core/HW/DVD/DVDInterface.cpp +++ b/Source/Core/Core/HW/DVD/DVDInterface.cpp @@ -881,8 +881,15 @@ void ExecuteCommand(ReplyType reply_type) break; // Wii-exclusive case DICommand::ReadBCA: - WARN_LOG(DVDINTERFACE, "DVDLowReadDiskBca"); - Memory::Write_U32(1, s_DIMAR + 0x30); + WARN_LOG(DVDINTERFACE, "DVDLowReadDiskBca - supplying dummy data to appease NSMBW"); + // NSMBW checks that the first 0x33 bytes of the BCA are 0, then it expects a 1. + // Most (all?) other games have 0x34 0's at the start of the BCA, but don't actually + // read it. NSMBW doesn't care about the other 12 bytes (which contain manufacturing data?) + + // TODO: Read the .bca file that cleanrip generates, if it exists + // Memory::CopyToEmu(output_address, bca_data, 0x40); + Memory::Memset(s_DIMAR, 0, 0x40); + Memory::Write_U8(1, s_DIMAR + 0x33); break; // Wii-exclusive case DICommand::RequestDiscStatus: From af5f0b20bbc5441c2a5787480ecd3749ce5e61b5 Mon Sep 17 00:00:00 2001 From: Pokechu22 Date: Fri, 4 Oct 2019 12:55:57 -0700 Subject: [PATCH 15/17] Report use of various unimplemented DI commands as game quirks --- Source/Core/Core/Analytics.cpp | 9 ++++++++- Source/Core/Core/Analytics.h | 12 ++++++++++++ Source/Core/Core/HW/DVD/DVDInterface.cpp | 7 +++++++ Source/Core/Core/IOS/DI/DI.cpp | 14 ++++++++++++++ 4 files changed, 41 insertions(+), 1 deletion(-) diff --git a/Source/Core/Core/Analytics.cpp b/Source/Core/Core/Analytics.cpp index c5349fe28e..3b49173ed2 100644 --- a/Source/Core/Core/Analytics.cpp +++ b/Source/Core/Core/Analytics.cpp @@ -132,9 +132,16 @@ void DolphinAnalytics::ReportGameStart() } // Keep in sync with enum class GameQuirk definition. -constexpr std::array GAME_QUIRKS_NAMES{ +constexpr std::array GAME_QUIRKS_NAMES{ "icache-matters", "directly-reads-wiimote-input", + "uses-DVDLowStopLaser", + "uses-DVDLowOffset", + "uses-DVDLowReadDiskBca", + "uses-DVDLowRequestDiscStatus", + "uses-DVDLowRequestRetryNumber", + "uses-DVDLowSerMeasControl", + "uses-different-partition-command", }; static_assert(GAME_QUIRKS_NAMES.size() == static_cast(GameQuirk::COUNT), "Game quirks names and enum definition are out of sync."); diff --git a/Source/Core/Core/Analytics.h b/Source/Core/Core/Analytics.h index 98f26900b0..2d35029a46 100644 --- a/Source/Core/Core/Analytics.h +++ b/Source/Core/Core/Analytics.h @@ -29,6 +29,18 @@ enum class GameQuirk // "read" extension or IR data. This would break our current TAS/NetPlay implementation. DIRECTLY_READS_WIIMOTE_INPUT, + // Several Wii DI commands that are rarely/never used and not implemented by Dolphin + USES_DVD_LOW_STOP_LASER, + USES_DVD_LOW_OFFSET, + USES_DVD_LOW_READ_DISK_BCA, // NSMBW known to use this + USES_DVD_LOW_REQUEST_DISC_STATUS, + USES_DVD_LOW_REQUEST_RETRY_NUMBER, + USES_DVD_LOW_SER_MEAS_CONTROL, + + // Dolphin only implements the simple DVDLowOpenPartition, not any of the variants where some + // already-read data is provided + USES_DIFFERENT_PARTITION_COMMAND, + COUNT, }; diff --git a/Source/Core/Core/HW/DVD/DVDInterface.cpp b/Source/Core/Core/HW/DVD/DVDInterface.cpp index fbb58b48d5..e5cfe77387 100644 --- a/Source/Core/Core/HW/DVD/DVDInterface.cpp +++ b/Source/Core/Core/HW/DVD/DVDInterface.cpp @@ -19,6 +19,7 @@ #include "Common/Config/Config.h" #include "Common/Logging/Log.h" +#include "Core/Analytics.h" #include "Core/Config/MainSettings.h" #include "Core/ConfigManager.h" #include "Core/CoreTiming.h" @@ -874,14 +875,17 @@ void ExecuteCommand(ReplyType reply_type) // Wii-exclusive case DICommand::StopLaser: ERROR_LOG(DVDINTERFACE, "DVDLowStopLaser"); + DolphinAnalytics::Instance().ReportGameQuirk(GameQuirk::USES_DVD_LOW_STOP_LASER); break; // Wii-exclusive case DICommand::Offset: ERROR_LOG(DVDINTERFACE, "DVDLowOffset"); + DolphinAnalytics::Instance().ReportGameQuirk(GameQuirk::USES_DVD_LOW_OFFSET); break; // Wii-exclusive case DICommand::ReadBCA: WARN_LOG(DVDINTERFACE, "DVDLowReadDiskBca - supplying dummy data to appease NSMBW"); + DolphinAnalytics::Instance().ReportGameQuirk(GameQuirk::USES_DVD_LOW_READ_DISK_BCA); // NSMBW checks that the first 0x33 bytes of the BCA are 0, then it expects a 1. // Most (all?) other games have 0x34 0's at the start of the BCA, but don't actually // read it. NSMBW doesn't care about the other 12 bytes (which contain manufacturing data?) @@ -894,10 +898,12 @@ void ExecuteCommand(ReplyType reply_type) // Wii-exclusive case DICommand::RequestDiscStatus: ERROR_LOG(DVDINTERFACE, "DVDLowRequestDiscStatus"); + DolphinAnalytics::Instance().ReportGameQuirk(GameQuirk::USES_DVD_LOW_REQUEST_DISC_STATUS); break; // Wii-exclusive case DICommand::RequestRetryNumber: ERROR_LOG(DVDINTERFACE, "DVDLowRequestRetryNumber"); + DolphinAnalytics::Instance().ReportGameQuirk(GameQuirk::USES_DVD_LOW_REQUEST_RETRY_NUMBER); break; // Wii-exclusive case DICommand::SetMaximumRotation: @@ -906,6 +912,7 @@ void ExecuteCommand(ReplyType reply_type) // Wii-exclusive case DICommand::SerMeasControl: ERROR_LOG(DVDINTERFACE, "DVDLowSerMeasControl"); + DolphinAnalytics::Instance().ReportGameQuirk(GameQuirk::USES_DVD_LOW_SER_MEAS_CONTROL); break; // Used by both GC and Wii diff --git a/Source/Core/Core/IOS/DI/DI.cpp b/Source/Core/Core/IOS/DI/DI.cpp index 7dc2f714e4..5a53e76446 100644 --- a/Source/Core/Core/IOS/DI/DI.cpp +++ b/Source/Core/Core/IOS/DI/DI.cpp @@ -13,6 +13,7 @@ #include "Common/CommonTypes.h" #include "Common/Logging/Log.h" #include "Common/MsgHandler.h" +#include "Core/Analytics.h" #include "Core/CoreTiming.h" #include "Core/HW/DVD/DVDInterface.h" #include "Core/HW/DVD/DVDThread.h" @@ -266,6 +267,7 @@ std::optional DI::StartIOCtl(const IOCtlRequest& request) } case DIIoctl::DVDLowOpenPartition: ERROR_LOG(IOS_DI, "DVDLowOpenPartition as an ioctl - rejecting"); + DolphinAnalytics::Instance().ReportGameQuirk(GameQuirk::USES_DIFFERENT_PARTITION_COMMAND); return DIResult::SecurityError; case DIIoctl::DVDLowClosePartition: INFO_LOG(IOS_DI, "DVDLowClosePartition"); @@ -307,18 +309,23 @@ std::optional DI::StartIOCtl(const IOCtlRequest& request) // Dolphin as games are unlikely to use them. case DIIoctl::DVDLowGetNoDiscOpenPartitionParams: ERROR_LOG(IOS_DI, "DVDLowGetNoDiscOpenPartitionParams as an ioctl - rejecting"); + DolphinAnalytics::Instance().ReportGameQuirk(GameQuirk::USES_DIFFERENT_PARTITION_COMMAND); return DIResult::SecurityError; case DIIoctl::DVDLowNoDiscOpenPartition: ERROR_LOG(IOS_DI, "DVDLowNoDiscOpenPartition as an ioctl - rejecting"); + DolphinAnalytics::Instance().ReportGameQuirk(GameQuirk::USES_DIFFERENT_PARTITION_COMMAND); return DIResult::SecurityError; case DIIoctl::DVDLowGetNoDiscBufferSizes: ERROR_LOG(IOS_DI, "DVDLowGetNoDiscBufferSizes as an ioctl - rejecting"); + DolphinAnalytics::Instance().ReportGameQuirk(GameQuirk::USES_DIFFERENT_PARTITION_COMMAND); return DIResult::SecurityError; case DIIoctl::DVDLowOpenPartitionWithTmdAndTicket: ERROR_LOG(IOS_DI, "DVDLowOpenPartitionWithTmdAndTicket as an ioctl - rejecting"); + DolphinAnalytics::Instance().ReportGameQuirk(GameQuirk::USES_DIFFERENT_PARTITION_COMMAND); return DIResult::SecurityError; case DIIoctl::DVDLowOpenPartitionWithTmdAndTicketView: ERROR_LOG(IOS_DI, "DVDLowOpenPartitionWithTmdAndTicketView as an ioctl - rejecting"); + DolphinAnalytics::Instance().ReportGameQuirk(GameQuirk::USES_DIFFERENT_PARTITION_COMMAND); return DIResult::SecurityError; case DIIoctl::DVDLowGetStatusRegister: { @@ -621,11 +628,13 @@ IPCCommandResult DI::IOCtlV(const IOCtlVRequest& request) { ERROR_LOG(IOS_DI, "DVDLowOpenPartition with ticket - not implemented, ignoring ticket parameter"); + DolphinAnalytics::Instance().ReportGameQuirk(GameQuirk::USES_DIFFERENT_PARTITION_COMMAND); } if (request.in_vectors[2].address != 0) { ERROR_LOG(IOS_DI, "DVDLowOpenPartition with cert chain - not implemented, ignoring certs parameter"); + DolphinAnalytics::Instance().ReportGameQuirk(GameQuirk::USES_DIFFERENT_PARTITION_COMMAND); } const u64 partition_offset = @@ -647,21 +656,26 @@ IPCCommandResult DI::IOCtlV(const IOCtlVRequest& request) } case DIIoctl::DVDLowGetNoDiscOpenPartitionParams: ERROR_LOG(IOS_DI, "DVDLowGetNoDiscOpenPartitionParams - dummied out"); + DolphinAnalytics::Instance().ReportGameQuirk(GameQuirk::USES_DIFFERENT_PARTITION_COMMAND); request.DumpUnknown(GetDeviceName(), Common::Log::IOS_DI); break; case DIIoctl::DVDLowNoDiscOpenPartition: ERROR_LOG(IOS_DI, "DVDLowNoDiscOpenPartition - dummied out"); + DolphinAnalytics::Instance().ReportGameQuirk(GameQuirk::USES_DIFFERENT_PARTITION_COMMAND); request.DumpUnknown(GetDeviceName(), Common::Log::IOS_DI); break; case DIIoctl::DVDLowGetNoDiscBufferSizes: ERROR_LOG(IOS_DI, "DVDLowGetNoDiscBufferSizes - dummied out"); + DolphinAnalytics::Instance().ReportGameQuirk(GameQuirk::USES_DIFFERENT_PARTITION_COMMAND); request.DumpUnknown(GetDeviceName(), Common::Log::IOS_DI); break; case DIIoctl::DVDLowOpenPartitionWithTmdAndTicket: ERROR_LOG(IOS_DI, "DVDLowOpenPartitionWithTmdAndTicket - not implemented"); + DolphinAnalytics::Instance().ReportGameQuirk(GameQuirk::USES_DIFFERENT_PARTITION_COMMAND); break; case DIIoctl::DVDLowOpenPartitionWithTmdAndTicketView: ERROR_LOG(IOS_DI, "DVDLowOpenPartitionWithTmdAndTicketView - not implemented"); + DolphinAnalytics::Instance().ReportGameQuirk(GameQuirk::USES_DIFFERENT_PARTITION_COMMAND); break; default: ERROR_LOG(IOS_DI, "Unknown ioctlv 0x%02x", request.request); From 51f8a3606e8509037cf1f4014139a1ea6555eb51 Mon Sep 17 00:00:00 2001 From: Pokechu22 Date: Fri, 4 Oct 2019 16:04:27 -0700 Subject: [PATCH 16/17] Return error code to game when using unimplemented commands --- Source/Core/Core/HW/DVD/DVDInterface.cpp | 26 ++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/Source/Core/Core/HW/DVD/DVDInterface.cpp b/Source/Core/Core/HW/DVD/DVDInterface.cpp index e5cfe77387..5050cd906e 100644 --- a/Source/Core/Core/HW/DVD/DVDInterface.cpp +++ b/Source/Core/Core/HW/DVD/DVDInterface.cpp @@ -779,6 +779,8 @@ void ExecuteCommand(ReplyType reply_type) // GC-only patched drive firmware command, used by libogc case DICommand::Unknown55: INFO_LOG(DVDINTERFACE, "SetExtension"); + SetHighError(ERROR_INV_CMD); + interrupt_type = DIInterruptType::DEINT; break; // Wii-exclusive @@ -863,24 +865,34 @@ void ExecuteCommand(ReplyType reply_type) ERROR_LOG(DVDINTERFACE, "Unknown 0xAD subcommand in %08x", s_DICMDBUF[0]); break; } + SetHighError(ERROR_INV_CMD); + interrupt_type = DIInterruptType::DEINT; break; // Wii-exclusive case DICommand::ReadDVD: ERROR_LOG(DVDINTERFACE, "DVDLowReadDvd"); + SetHighError(ERROR_INV_CMD); + interrupt_type = DIInterruptType::DEINT; break; // Wii-exclusive case DICommand::ReadDVDConfig: ERROR_LOG(DVDINTERFACE, "DVDLowReadDvdConfig"); + SetHighError(ERROR_INV_CMD); + interrupt_type = DIInterruptType::DEINT; break; // Wii-exclusive case DICommand::StopLaser: ERROR_LOG(DVDINTERFACE, "DVDLowStopLaser"); DolphinAnalytics::Instance().ReportGameQuirk(GameQuirk::USES_DVD_LOW_STOP_LASER); + SetHighError(ERROR_INV_CMD); + interrupt_type = DIInterruptType::DEINT; break; // Wii-exclusive case DICommand::Offset: ERROR_LOG(DVDINTERFACE, "DVDLowOffset"); DolphinAnalytics::Instance().ReportGameQuirk(GameQuirk::USES_DVD_LOW_OFFSET); + SetHighError(ERROR_INV_CMD); + interrupt_type = DIInterruptType::DEINT; break; // Wii-exclusive case DICommand::ReadBCA: @@ -899,20 +911,28 @@ void ExecuteCommand(ReplyType reply_type) case DICommand::RequestDiscStatus: ERROR_LOG(DVDINTERFACE, "DVDLowRequestDiscStatus"); DolphinAnalytics::Instance().ReportGameQuirk(GameQuirk::USES_DVD_LOW_REQUEST_DISC_STATUS); + SetHighError(ERROR_INV_CMD); + interrupt_type = DIInterruptType::DEINT; break; // Wii-exclusive case DICommand::RequestRetryNumber: ERROR_LOG(DVDINTERFACE, "DVDLowRequestRetryNumber"); DolphinAnalytics::Instance().ReportGameQuirk(GameQuirk::USES_DVD_LOW_REQUEST_RETRY_NUMBER); + SetHighError(ERROR_INV_CMD); + interrupt_type = DIInterruptType::DEINT; break; // Wii-exclusive case DICommand::SetMaximumRotation: ERROR_LOG(DVDINTERFACE, "DVDLowSetMaximumRotation"); + SetHighError(ERROR_INV_CMD); + interrupt_type = DIInterruptType::DEINT; break; // Wii-exclusive case DICommand::SerMeasControl: ERROR_LOG(DVDINTERFACE, "DVDLowSerMeasControl"); DolphinAnalytics::Instance().ReportGameQuirk(GameQuirk::USES_DVD_LOW_SER_MEAS_CONTROL); + SetHighError(ERROR_INV_CMD); + interrupt_type = DIInterruptType::DEINT; break; // Used by both GC and Wii @@ -1084,6 +1104,8 @@ void ExecuteCommand(ReplyType reply_type) // GC-only patched drive firmware command, used by libogc case DICommand::UnknownEE: INFO_LOG(DVDINTERFACE, "SetStatus"); + SetHighError(ERROR_INV_CMD); + interrupt_type = DIInterruptType::DEINT; break; // Debug commands; see yagcd. We don't really care @@ -1092,6 +1114,8 @@ void ExecuteCommand(ReplyType reply_type) // Can only be used through direct access and only after unlocked. case DICommand::Debug: ERROR_LOG(DVDINTERFACE, "Unsupported DVD Drive debug command 0x%08x", s_DICMDBUF[0]); + SetHighError(ERROR_INV_CMD); + interrupt_type = DIInterruptType::DEINT; break; // Unlock Commands. 1: "MATSHITA" 2: "DVD-GAME" @@ -1119,6 +1143,8 @@ void ExecuteCommand(ReplyType reply_type) ERROR_LOG(DVDINTERFACE, "Unknown command 0x%08x (Buffer 0x%08x, 0x%x)", s_DICMDBUF[0], s_DIMAR, s_DILENGTH); PanicAlertT("Unknown DVD command %08x - fatal error", s_DICMDBUF[0]); + SetHighError(ERROR_INV_CMD); + interrupt_type = DIInterruptType::DEINT; break; } From 3b5d20e12c9d911e32ad269b37a984ee060357b9 Mon Sep 17 00:00:00 2001 From: Pokechu22 Date: Tue, 8 Oct 2019 11:32:26 -0700 Subject: [PATCH 17/17] Bump state version --- Source/Core/Core/State.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Core/Core/State.cpp b/Source/Core/Core/State.cpp index 93f0b33318..db97cea52b 100644 --- a/Source/Core/Core/State.cpp +++ b/Source/Core/Core/State.cpp @@ -73,7 +73,7 @@ static Common::Event g_compressAndDumpStateSyncEvent; static std::thread g_save_thread; // Don't forget to increase this after doing changes on the savestate system -constexpr u32 STATE_VERSION = 113; // Last changed in PR 8506 +constexpr u32 STATE_VERSION = 114; // Last changed in PR 8394 // Maps savestate versions to Dolphin versions. // Versions after 42 don't need to be added to this list,