Merge pull request #4883 from JosJuice/dvd-timing-address-translation-2
DVDInterface: Translate Wii partition offsets for timing purposes
This commit is contained in:
commit
f80f7b6f9c
|
@ -2,6 +2,7 @@
|
|||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <cinttypes>
|
||||
#include <cmath>
|
||||
#include <memory>
|
||||
|
@ -31,6 +32,7 @@
|
|||
#include "DiscIO/Enums.h"
|
||||
#include "DiscIO/Volume.h"
|
||||
#include "DiscIO/VolumeCreator.h"
|
||||
#include "DiscIO/VolumeWiiCrypted.h"
|
||||
|
||||
// The minimum time it takes for the DVD drive to process a command (in
|
||||
// microseconds)
|
||||
|
@ -273,8 +275,7 @@ bool ExecuteReadCommand(u64 DVD_offset, u32 output_address, u32 DVD_length, u32
|
|||
|
||||
u64 PackFinishExecutingCommandUserdata(ReplyType reply_type, DIInterruptType interrupt_type);
|
||||
|
||||
void ScheduleReads(u64 dvd_offset, u32 length, bool decrypt, u32 output_address,
|
||||
ReplyType reply_type);
|
||||
void ScheduleReads(u64 offset, u32 length, bool decrypt, u32 output_address, ReplyType reply_type);
|
||||
double CalculatePhysicalDiscPosition(u64 offset);
|
||||
u64 CalculateSeekTime(u64 offset_from, u64 offset_to);
|
||||
u64 CalculateRawDiscReadTime(u64 offset, u64 length);
|
||||
|
@ -1174,8 +1175,7 @@ void FinishExecutingCommand(ReplyType reply_type, DIInterruptType interrupt_type
|
|||
|
||||
// Determines from a given read request how much of the request is buffered,
|
||||
// and how much is required to be read from disc.
|
||||
void ScheduleReads(u64 dvd_offset, u32 dvd_length, bool decrypt, u32 output_address,
|
||||
ReplyType reply_type)
|
||||
void ScheduleReads(u64 offset, u32 length, bool decrypt, u32 output_address, ReplyType reply_type)
|
||||
{
|
||||
// The drive continues to read 1 MiB beyond the last read position when idle.
|
||||
// If a future read falls within this window, part of the read may be returned
|
||||
|
@ -1196,6 +1196,15 @@ void ScheduleReads(u64 dvd_offset, u32 dvd_length, bool decrypt, u32 output_addr
|
|||
// If we fall within its bounds, we get DMA-speed reads.
|
||||
u64 buffer_start, buffer_end;
|
||||
|
||||
// The variable offset uses the same addressing as games do.
|
||||
// The variable dvd_offset tracks the actual offset on the DVD
|
||||
// that the disc drive starts reading at, which differs in two ways:
|
||||
// It's rounded to a whole ECC block and never uses Wii partition addressing.
|
||||
u64 dvd_offset = offset;
|
||||
if (decrypt)
|
||||
dvd_offset = s_inserted_volume->PartitionOffsetToRawOffset(offset);
|
||||
dvd_offset = Common::AlignDown(dvd_offset, DVD_ECC_BLOCK_SIZE);
|
||||
|
||||
if (SConfig::GetInstance().bFastDiscSpeed)
|
||||
{
|
||||
// The SUDTR setting makes us act as if all reads are buffered
|
||||
|
@ -1250,8 +1259,8 @@ void ScheduleReads(u64 dvd_offset, u32 dvd_length, bool decrypt, u32 output_addr
|
|||
buffer_start, buffer_end, buffer_end - buffer_start);
|
||||
|
||||
DEBUG_LOG(DVDINTERFACE,
|
||||
"Schedule reads: offset=0x%" PRIx64 " length=0x%" PRIx32 " address=0x%" PRIx32,
|
||||
dvd_offset, dvd_length, output_address);
|
||||
"Schedule reads: offset=0x%" PRIx64 " length=0x%" PRIx32 " address=0x%" PRIx32, offset,
|
||||
length, output_address);
|
||||
|
||||
// The DVD drive's minimum turnaround time on a command, based on a hardware test.
|
||||
s64 ticks_until_completion = COMMAND_LATENCY_US * (SystemTimers::GetTicksPerSecond() / 1000000);
|
||||
|
@ -1259,23 +1268,23 @@ void ScheduleReads(u64 dvd_offset, u32 dvd_length, bool decrypt, u32 output_addr
|
|||
u32 buffered_blocks = 0;
|
||||
u32 unbuffered_blocks = 0;
|
||||
|
||||
while (dvd_length > 0)
|
||||
{
|
||||
// Where the read actually takes place on disc
|
||||
u64 rounded_offset = Common::AlignDown(dvd_offset, DVD_ECC_BLOCK_SIZE);
|
||||
const u32 bytes_per_chunk =
|
||||
decrypt ? DiscIO::CVolumeWiiCrypted::BLOCK_DATA_SIZE : DVD_ECC_BLOCK_SIZE;
|
||||
|
||||
while (length > 0)
|
||||
{
|
||||
// The length of this read - "+1" so that if this read is already
|
||||
// aligned to an ECC block we'll read the entire block.
|
||||
u32 chunk_length =
|
||||
static_cast<u32>(Common::AlignUp(dvd_offset + 1, DVD_ECC_BLOCK_SIZE) - dvd_offset);
|
||||
// aligned to a block we'll read the entire block.
|
||||
u32 chunk_length = static_cast<u32>(Common::AlignUp(offset + 1, bytes_per_chunk) - offset);
|
||||
|
||||
// The last chunk may be short
|
||||
if (chunk_length > dvd_length)
|
||||
chunk_length = dvd_length;
|
||||
chunk_length = std::min(chunk_length, length);
|
||||
|
||||
if (rounded_offset >= buffer_start && rounded_offset < buffer_end)
|
||||
if (dvd_offset >= buffer_start && dvd_offset < buffer_end)
|
||||
{
|
||||
// Number of ticks it takes to transfer the data from the buffer to memory.
|
||||
// TODO: This calculation is slightly wrong when decrypt is true - it uses the size of
|
||||
// the copy from IOS to PPC but is supposed to model the copy from the disc drive to IOS.
|
||||
ticks_until_completion +=
|
||||
static_cast<u64>(chunk_length) * SystemTimers::GetTicksPerSecond() / BUFFER_TRANSFER_RATE;
|
||||
buffered_blocks++;
|
||||
|
@ -1284,39 +1293,40 @@ void ScheduleReads(u64 dvd_offset, u32 dvd_length, bool decrypt, u32 output_addr
|
|||
{
|
||||
// In practice we'll only ever seek if this is the first time
|
||||
// through this loop.
|
||||
if (rounded_offset != head_position)
|
||||
if (dvd_offset != head_position)
|
||||
{
|
||||
// Unbuffered seek+read
|
||||
ticks_until_completion += CalculateSeekTime(head_position, rounded_offset);
|
||||
ticks_until_completion += CalculateSeekTime(head_position, dvd_offset);
|
||||
DEBUG_LOG(DVDINTERFACE, "Seek+read 0x%" PRIx32 " bytes @ 0x%" PRIx64 " ticks=%" PRId64,
|
||||
chunk_length, rounded_offset, ticks_until_completion);
|
||||
chunk_length, offset, ticks_until_completion);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Unbuffered read
|
||||
ticks_until_completion += CalculateRawDiscReadTime(rounded_offset, DVD_ECC_BLOCK_SIZE);
|
||||
ticks_until_completion += CalculateRawDiscReadTime(dvd_offset, DVD_ECC_BLOCK_SIZE);
|
||||
}
|
||||
|
||||
unbuffered_blocks++;
|
||||
head_position = rounded_offset + DVD_ECC_BLOCK_SIZE;
|
||||
head_position = dvd_offset + DVD_ECC_BLOCK_SIZE;
|
||||
}
|
||||
|
||||
// Schedule this read to complete at the appropriate time
|
||||
const ReplyType chunk_reply_type = chunk_length == dvd_length ? reply_type : ReplyType::NoReply;
|
||||
DVDThread::StartReadToEmulatedRAM(output_address, dvd_offset, chunk_length, decrypt,
|
||||
const ReplyType chunk_reply_type = chunk_length == length ? reply_type : ReplyType::NoReply;
|
||||
DVDThread::StartReadToEmulatedRAM(output_address, offset, chunk_length, decrypt,
|
||||
chunk_reply_type, ticks_until_completion);
|
||||
|
||||
// Advance the read window
|
||||
output_address += chunk_length;
|
||||
dvd_offset += chunk_length;
|
||||
dvd_length -= chunk_length;
|
||||
offset += chunk_length;
|
||||
length -= chunk_length;
|
||||
dvd_offset += DVD_ECC_BLOCK_SIZE;
|
||||
}
|
||||
|
||||
// Update the buffer based on this read. Based on experimental testing,
|
||||
// we will only reuse the old buffer while reading forward. Note that the
|
||||
// buffer start we calculate here is not the actual start of the buffer -
|
||||
// it is just the start of the portion we need to read.
|
||||
u64 last_block = Common::AlignUp(dvd_offset, DVD_ECC_BLOCK_SIZE);
|
||||
const u64 last_block = dvd_offset;
|
||||
if (last_block == buffer_start + DVD_ECC_BLOCK_SIZE && buffer_start != buffer_end)
|
||||
{
|
||||
// Special case: reading less than one block at the start of the
|
||||
|
|
|
@ -37,6 +37,7 @@ public:
|
|||
|
||||
virtual bool GetTitleID(u64*) const { return false; }
|
||||
virtual std::vector<u8> GetTMD() const { return {}; }
|
||||
virtual u64 PartitionOffsetToRawOffset(u64 offset) const { return offset; }
|
||||
virtual std::string GetGameID() const = 0;
|
||||
virtual std::string GetMakerID() const = 0;
|
||||
virtual u16 GetRevision() const = 0;
|
||||
|
|
|
@ -60,26 +60,26 @@ bool CVolumeWiiCrypted::Read(u64 _ReadOffset, u64 _Length, u8* _pBuffer, bool de
|
|||
|
||||
FileMon::FindFilename(_ReadOffset);
|
||||
|
||||
std::vector<u8> read_buffer(s_block_total_size);
|
||||
std::vector<u8> read_buffer(BLOCK_TOTAL_SIZE);
|
||||
while (_Length > 0)
|
||||
{
|
||||
// Calculate block offset
|
||||
u64 Block = _ReadOffset / s_block_data_size;
|
||||
u64 Offset = _ReadOffset % s_block_data_size;
|
||||
u64 Block = _ReadOffset / BLOCK_DATA_SIZE;
|
||||
u64 Offset = _ReadOffset % BLOCK_DATA_SIZE;
|
||||
|
||||
if (m_LastDecryptedBlockOffset != Block)
|
||||
{
|
||||
// Read the current block
|
||||
if (!m_pReader->Read(m_VolumeOffset + m_dataOffset + Block * s_block_total_size,
|
||||
s_block_total_size, read_buffer.data()))
|
||||
if (!m_pReader->Read(m_VolumeOffset + m_dataOffset + Block * BLOCK_TOTAL_SIZE,
|
||||
BLOCK_TOTAL_SIZE, read_buffer.data()))
|
||||
return false;
|
||||
|
||||
// Decrypt the block's data.
|
||||
// 0x3D0 - 0x3DF in m_pBuffer will be overwritten,
|
||||
// but that won't affect anything, because we won't
|
||||
// use the content of m_pBuffer anymore after this
|
||||
mbedtls_aes_crypt_cbc(m_AES_ctx.get(), MBEDTLS_AES_DECRYPT, s_block_data_size,
|
||||
&read_buffer[0x3D0], &read_buffer[s_block_header_size],
|
||||
mbedtls_aes_crypt_cbc(m_AES_ctx.get(), MBEDTLS_AES_DECRYPT, BLOCK_DATA_SIZE,
|
||||
&read_buffer[0x3D0], &read_buffer[BLOCK_HEADER_SIZE],
|
||||
m_LastDecryptedBlock);
|
||||
m_LastDecryptedBlockOffset = Block;
|
||||
|
||||
|
@ -90,7 +90,7 @@ bool CVolumeWiiCrypted::Read(u64 _ReadOffset, u64 _Length, u8* _pBuffer, bool de
|
|||
}
|
||||
|
||||
// Copy the decrypted data
|
||||
u64 MaxSizeToCopy = s_block_data_size - Offset;
|
||||
u64 MaxSizeToCopy = BLOCK_DATA_SIZE - Offset;
|
||||
u64 CopySize = (_Length > MaxSizeToCopy) ? MaxSizeToCopy : _Length;
|
||||
memcpy(_pBuffer, &m_LastDecryptedBlock[Offset], (size_t)CopySize);
|
||||
|
||||
|
@ -140,6 +140,12 @@ std::vector<u8> CVolumeWiiCrypted::GetTMD() const
|
|||
return buffer;
|
||||
}
|
||||
|
||||
u64 CVolumeWiiCrypted::PartitionOffsetToRawOffset(u64 offset) const
|
||||
{
|
||||
return m_VolumeOffset + m_dataOffset + (offset / BLOCK_DATA_SIZE * BLOCK_TOTAL_SIZE) +
|
||||
(offset % BLOCK_DATA_SIZE);
|
||||
}
|
||||
|
||||
std::string CVolumeWiiCrypted::GetGameID() const
|
||||
{
|
||||
if (m_pReader == nullptr)
|
||||
|
|
|
@ -32,6 +32,7 @@ public:
|
|||
bool Read(u64 _Offset, u64 _Length, u8* _pBuffer, bool decrypt) const override;
|
||||
bool GetTitleID(u64* buffer) const override;
|
||||
std::vector<u8> GetTMD() const override;
|
||||
u64 PartitionOffsetToRawOffset(u64 offset) const override;
|
||||
std::string GetGameID() const override;
|
||||
std::string GetMakerID() const override;
|
||||
u16 GetRevision() const override;
|
||||
|
@ -53,11 +54,11 @@ public:
|
|||
u64 GetSize() const override;
|
||||
u64 GetRawSize() const override;
|
||||
|
||||
private:
|
||||
static const unsigned int s_block_header_size = 0x0400;
|
||||
static const unsigned int s_block_data_size = 0x7C00;
|
||||
static const unsigned int s_block_total_size = s_block_header_size + s_block_data_size;
|
||||
static constexpr unsigned int BLOCK_HEADER_SIZE = 0x0400;
|
||||
static constexpr unsigned int BLOCK_DATA_SIZE = 0x7C00;
|
||||
static constexpr unsigned int BLOCK_TOTAL_SIZE = BLOCK_HEADER_SIZE + BLOCK_DATA_SIZE;
|
||||
|
||||
private:
|
||||
std::unique_ptr<IBlobReader> m_pReader;
|
||||
std::unique_ptr<mbedtls_aes_context> m_AES_ctx;
|
||||
|
||||
|
@ -65,7 +66,7 @@ private:
|
|||
u64 m_dataOffset;
|
||||
|
||||
mutable u64 m_LastDecryptedBlockOffset;
|
||||
mutable unsigned char m_LastDecryptedBlock[s_block_data_size];
|
||||
mutable unsigned char m_LastDecryptedBlock[BLOCK_DATA_SIZE];
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
|
Loading…
Reference in New Issue