Skip to content

Commit

Permalink
DVDInterface: Translate and align offsets for timing purposes
Browse files Browse the repository at this point in the history
This is a big accuracy improvement for decrypted reads
and a small accuracy improvement for non-decrypted reads.
  • Loading branch information
JosJuice committed Dec 14, 2015
1 parent 2b60eb0 commit d69f44e
Show file tree
Hide file tree
Showing 5 changed files with 84 additions and 16 deletions.
87 changes: 73 additions & 14 deletions Source/Core/Core/HW/DVDInterface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@

#include "DiscIO/Volume.h"
#include "DiscIO/VolumeCreator.h"
#include "DiscIO/VolumeWiiCrypted.h"

static const double PI = 3.14159265358979323846264338328;

Expand All @@ -39,7 +40,7 @@ static const u32 BUFFER_TRANSFER_RATE = 1024 * 1024 * 16;
// Disc access time measured in milliseconds
static const u32 DISC_ACCESS_TIME_MS = 50;

// The size of a Wii disc layer in bytes (is this correct?)
// The size of the first Wii disc layer in bytes
static const u64 WII_DISC_LAYER_SIZE = 4699979776;

// By knowing the disc read speed at two locations defined here,
Expand Down Expand Up @@ -248,6 +249,7 @@ static bool g_bStopAtTrackEnd = false;
static int finish_execute_command = 0;
static int dtk = 0;

static u64 g_last_decrypted_block;
static u64 g_last_read_offset;
static u64 g_last_read_time;

Expand All @@ -269,7 +271,8 @@ void WriteImmediate(u32 value, u32 output_address, bool write_to_DIIMMBUF);
bool ExecuteReadCommand(u64 DVD_offset, u32 output_address, u32 DVD_length, u32 output_length, bool decrypt,
int callback_event_type, DIInterruptType* interrupt_type, u64* ticks_until_completion);

u64 SimulateDiscReadTime(u64 offset, u32 length);
u64 SimulateDiscReadTimeWii(u64 offset, u32 length, bool decrypt);
u64 SimulateDiscReadTime(u64 offset, u64 length);
s64 CalculateRawDiscReadTime(u64 offset, s64 length);

void DoState(PointerWrap &p)
Expand All @@ -294,6 +297,7 @@ void DoState(PointerWrap &p)
p.Do(CurrentStart);
p.Do(CurrentLength);

p.Do(g_last_decrypted_block);
p.Do(g_last_read_offset);
p.Do(g_last_read_time);

Expand Down Expand Up @@ -403,6 +407,7 @@ void Init()
g_bStream = false;
g_bStopAtTrackEnd = false;

g_last_decrypted_block = 0;
g_last_read_offset = 0;
g_last_read_time = 0;

Expand Down Expand Up @@ -673,7 +678,7 @@ bool ExecuteReadCommand(u64 DVD_offset, u32 output_address, u32 DVD_length, u32
// An optional hack to speed up loading times
*ticks_until_completion = output_length * (SystemTimers::GetTicksPerSecond() / BUFFER_TRANSFER_RATE);
else
*ticks_until_completion = SimulateDiscReadTime(DVD_offset, DVD_length);
*ticks_until_completion = SimulateDiscReadTimeWii(DVD_offset, DVD_length, decrypt);

DVDThread::StartRead(DVD_offset, output_address, DVD_length, decrypt,
callback_event_type, (int)*ticks_until_completion);
Expand Down Expand Up @@ -1269,8 +1274,56 @@ void ExecuteCommand(u32 command_0, u32 command_1, u32 command_2, u32 output_addr
// Simulates the timing aspects of reading data from a disc.
// Returns the amount of ticks needed to finish executing the command,
// and sets some state that is used the next time this function runs.
u64 SimulateDiscReadTime(u64 offset, u32 length)
//
// This function does some extra handling for Wii discs on
// top of what SimulateDiscReadTime does. When using a GC disc,
// (or rather, when no decryption is done and no data past
// 4,7 GB is accessed,) both functions do the same thing.
u64 SimulateDiscReadTimeWii(u64 offset, u32 length, bool decrypt)
{
u64 end_offset = offset + length;

if (decrypt)
{
const unsigned int BLOCK_TOTAL_SIZE = DiscIO::CVolumeWiiCrypted::s_block_total_size;
offset = ROUND_DOWN(s_inserted_volume->OffsetToRawOffset(offset), BLOCK_TOTAL_SIZE);

if (length == 0)
{
end_offset = offset;
}
else
{
end_offset = ROUND_UP(s_inserted_volume->OffsetToRawOffset(end_offset), BLOCK_TOTAL_SIZE);

if (g_last_decrypted_block == offset)
{
// This simulates IOS having a 32 KiB buffer where it keeps
// the last decrypted block, avoiding to re-decrypt it.
// This behavior is an unconfirmed guess.
offset += BLOCK_TOTAL_SIZE;
}

g_last_decrypted_block = end_offset - BLOCK_TOTAL_SIZE;
}
}

return SimulateDiscReadTime(offset, end_offset - offset);
}

// Simulates the timing aspects of reading data from a disc.
// Returns the amount of ticks needed to finish executing the command,
// and sets some state that is used the next time this function runs.
u64 SimulateDiscReadTime(u64 offset, u64 length)
{
// Data from a DVD sector can only be used once the entire sector
// has been read, so we align the read to sectors (2048 bytes).
static const u64 SECTOR_SIZE = 2048;
u64 end_offset = offset + length;
offset = ROUND_DOWN(offset, SECTOR_SIZE);
end_offset = (length == 0) ? offset : ROUND_UP(end_offset, SECTOR_SIZE);
length = end_offset - offset;

// The drive buffers 1 MiB (?) of data after every read request;
// if a read request is covered by this buffer (or if it's
// faster to wait for the data to be buffered), the drive
Expand Down Expand Up @@ -1301,7 +1354,7 @@ u64 SimulateDiscReadTime(u64 offset, u32 length)
u64 disk_read_duration = CalculateRawDiscReadTime(offset, length) +
SystemTimers::GetTicksPerSecond() / 1000 * DISC_ACCESS_TIME_MS;

if (offset + length - g_last_read_offset > 1024 * 1024)
if (end_offset - g_last_read_offset > 1024 * 1024)
{
// No buffer; just use the simple seek time + read time.
DEBUG_LOG(DVDINTERFACE, "Seeking %" PRId64 " bytes",
Expand All @@ -1312,16 +1365,15 @@ u64 SimulateDiscReadTime(u64 offset, u32 length)
else
{
// Possibly buffered; use the buffer if it saves time.
// It's not proven that the buffer actually behaves like this, but
// it appears to be a decent approximation.
// It's not proven that the buffer actually behaves like this,
// but it appears to be a decent approximation.

// Time at which the buffer will contain the data we need.
u64 buffer_fill_time = g_last_read_time +
CalculateRawDiscReadTime(g_last_read_offset,
offset + length - g_last_read_offset);
end_offset - g_last_read_offset);
// Number of ticks it takes to transfer the data from the buffer to memory.
u64 buffer_read_duration = length *
(SystemTimers::GetTicksPerSecond() / BUFFER_TRANSFER_RATE);
u64 buffer_read_duration = length * (SystemTimers::GetTicksPerSecond() / BUFFER_TRANSFER_RATE);

if (current_time > buffer_fill_time)
{
Expand All @@ -1345,7 +1397,9 @@ u64 SimulateDiscReadTime(u64 offset, u32 length)
}
}

g_last_read_offset = ROUND_DOWN(offset + length - 2048, 2048);
// The sector size subtraction exists to make sure that very small
// sequential reads are covered by the code path for buffered reads.
g_last_read_offset = end_offset - SECTOR_SIZE;

return ticks_until_completion;
}
Expand All @@ -1360,10 +1414,15 @@ s64 CalculateRawDiscReadTime(u64 offset, s64 length)
// but since reads only span a small part of the disc, it's insignificant.
u64 average_offset = offset + (length / 2);

// Here, addresses on the second layer of Wii discs are replaced with equivalent
// addresses on the first layer so that the speed calculation works correctly.
// This is wrong for reads spanning two layers, but those should be rare.
// Offsets on layers beyond layer 1 are replaced with equivalent offsets
// on layer 1 so that the speed calculation will work correctly.
// Layer 2 starts where layer 1 ends and goes backwards.
// This code also supports layers beyond layer 2,
// but discs with that many layers don't exist in reality.
bool layer_goes_backwards = (average_offset / WII_DISC_LAYER_SIZE) % 2 == 1;
average_offset %= WII_DISC_LAYER_SIZE;
if (layer_goes_backwards)
average_offset = WII_DISC_LAYER_SIZE - average_offset;

// The area on the disc between position 1 and the arbitrary position X is:
// LOCATION_X_SPEED * LOCATION_X_SPEED * pi - AREA_UP_TO_LOCATION_1
Expand Down
2 changes: 1 addition & 1 deletion Source/Core/Core/State.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,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
static const u32 STATE_VERSION = 49; // Last changed in PR 2149
static const u32 STATE_VERSION = 50; // Last changed in PR 1900

// Maps savestate versions to Dolphin versions.
// Versions after 42 don't need to be added to this list,
Expand Down
1 change: 1 addition & 0 deletions Source/Core/DiscIO/Volume.h
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ class IVolume
*_sz = 0;
return std::unique_ptr<u8[]>();
}
virtual u64 OffsetToRawOffset(u64 offset) const { return 0; }
virtual std::string GetUniqueID() const = 0;
virtual std::string GetMakerID() const = 0;
virtual u16 GetRevision() const = 0;
Expand Down
7 changes: 7 additions & 0 deletions Source/Core/DiscIO/VolumeWiiCrypted.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,13 @@ std::unique_ptr<u8[]> CVolumeWiiCrypted::GetTMD(u32 *size) const
return buf;
}

u64 CVolumeWiiCrypted::OffsetToRawOffset(u64 offset) const
{
return m_VolumeOffset + m_dataOffset +
(offset / s_block_data_size * s_block_total_size) +
(offset % s_block_data_size);
}

std::string CVolumeWiiCrypted::GetUniqueID() const
{
if (m_pReader == nullptr)
Expand Down
3 changes: 2 additions & 1 deletion Source/Core/DiscIO/VolumeWiiCrypted.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ class CVolumeWiiCrypted : public IVolume
bool Read(u64 _Offset, u64 _Length, u8* _pBuffer, bool decrypt) const override;
bool GetTitleID(u64* buffer) const override;
std::unique_ptr<u8[]> GetTMD(u32 *_sz) const override;
u64 OffsetToRawOffset(u64 offset) const override;
std::string GetUniqueID() const override;
std::string GetMakerID() const override;
u16 GetRevision() const override;
Expand All @@ -45,11 +46,11 @@ class CVolumeWiiCrypted : public IVolume
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;

private:
std::unique_ptr<IBlobReader> m_pReader;
std::unique_ptr<mbedtls_aes_context> m_AES_ctx;

Expand Down

0 comments on commit d69f44e

Please sign in to comment.