summaryrefslogtreecommitdiff
path: root/Source/Core/DiscIO/TGCBlob.cpp
diff options
context:
space:
mode:
authorJosJuice <josjuice@gmail.com>2020-02-23 20:56:18 +0100
committerJosJuice <josjuice@gmail.com>2020-06-17 12:32:39 +0200
commit75d032161f5d9ca450d21520eaa75a5be2f1f9ee (patch)
treecaec92efd4eda45dd16c24eac0674df655321f80 /Source/Core/DiscIO/TGCBlob.cpp
parent7cdd0e243080f791bc48d89aa94e5886e6a6ebd9 (diff)
DiscIO: Rework the implementation of TGC reading
Fixes https://bugs.dolphin-emu.org/issues/10654. To quote the documenation file included with the program tgctogcm: "TGC's are miniaturized .gcm images with a 32kB header. The embedded gcm contains some bogus data, namely: -FST Location (0x424 in gcm) -DOL Location (0x420 in gcm) -FST File offsets (all files are offset/spoofed by a certain amount)" Dolphin has been handling the values at 0x420 and 0x424 by simply overwriting them with a working value (just like tgctogcm does), but it has used a different approach for the file offsets in the FST. Instead of changing the offsets that are stored in the FST, Dolphin changed where the files actually are placed on the virtual disc. My hope was that this would make the loading times more accurate to how they are when running a TGC file as part of a larger disc. However, there are TGC files where we would need to move files backwards on the disc in order to do this (this is what issue 10654 is about), so the approach we have been using is flawed. This change makes Dolphin overwrite offsets in the FST instead, like tgctogcm does. Other than making Dolphin handle the affected TGC files correctly, this change also makes it so that unnecessary padding data isn't written if you use Dolphin to convert a TGC file to an ISO file. This feature is not actually implemented in Dolphin as of now, but I'm planning to add it in the near future as part of a larger feature.
Diffstat (limited to 'Source/Core/DiscIO/TGCBlob.cpp')
-rw-r--r--Source/Core/DiscIO/TGCBlob.cpp141
1 files changed, 57 insertions, 84 deletions
diff --git a/Source/Core/DiscIO/TGCBlob.cpp b/Source/Core/DiscIO/TGCBlob.cpp
index 8533e38df7..b14e0017b1 100644
--- a/Source/Core/DiscIO/TGCBlob.cpp
+++ b/Source/Core/DiscIO/TGCBlob.cpp
@@ -4,59 +4,43 @@
#include "DiscIO/TGCBlob.h"
+#include <algorithm>
#include <memory>
#include <string>
+#include <type_traits>
#include <utility>
+#include <vector>
#include "Common/File.h"
#include "Common/Swap.h"
namespace
{
-template <typename T>
-struct Interval
+u32 SubtractBE32(u32 minuend_be, u32 subtrahend_le)
{
- T start;
- T length;
-
- T End() const { return start + length; }
- bool IsEmpty() const { return length == 0; }
-};
+ return Common::swap32(Common::swap32(minuend_be) - subtrahend_le);
+}
-template <typename T>
-void SplitInterval(T split_point, Interval<T> interval, Interval<T>* out_1, Interval<T>* out_2)
+void Replace(u64 offset, u64 size, u8* out_ptr, u64 replace_offset, u64 replace_size,
+ const u8* replace_ptr)
{
- if (interval.start < split_point)
- *out_1 = {interval.start, std::min(interval.length, split_point - interval.start)};
- else
- *out_1 = {0, 0};
+ const u64 replace_start = std::max(offset, replace_offset);
+ const u64 replace_end = std::min(offset + size, replace_offset + replace_size);
- if (interval.End() > split_point)
- {
- *out_2 = {std::max(interval.start, split_point),
- std::min(interval.length, interval.End() - split_point)};
- }
- else
+ if (replace_end > replace_start)
{
- *out_2 = {0, 0};
+ std::copy(replace_ptr + (replace_start - replace_offset),
+ replace_ptr + (replace_end - replace_offset), out_ptr + (replace_start - offset));
}
}
-u32 SubtractBE32(u32 minuend_be, u32 subtrahend_le)
-{
- return Common::swap32(Common::swap32(minuend_be) - subtrahend_le);
-}
-
-void Replace8(u64 offset, u64 nbytes, u8* out_ptr, u64 replace_offset, u8 replace_value)
+template <typename T>
+void Replace(u64 offset, u64 size, u8* out_ptr, u64 replace_offset, const T& replace_value)
{
- if (offset <= replace_offset && offset + nbytes > replace_offset)
- out_ptr[replace_offset - offset] = replace_value;
-}
+ static_assert(std::is_trivially_copyable_v<T>);
-void Replace32(u64 offset, u64 nbytes, u8* out_ptr, u64 replace_offset, u32 replace_value)
-{
- for (size_t i = 0; i < sizeof(u32); ++i)
- Replace8(offset, nbytes, out_ptr, replace_offset + i, reinterpret_cast<u8*>(&replace_value)[i]);
+ const u8* replace_ptr = reinterpret_cast<const u8*>(&replace_value);
+ Replace(offset, size, out_ptr, replace_offset, sizeof(T), replace_ptr);
}
} // namespace
@@ -75,70 +59,59 @@ TGCFileReader::TGCFileReader(File::IOFile file) : m_file(std::move(file))
{
m_file.Seek(0, SEEK_SET);
m_file.ReadArray(&m_header, 1);
- u32 header_size = Common::swap32(m_header.tgc_header_size);
- m_size = m_file.GetSize();
- m_file_area_shift = static_cast<s64>(Common::swap32(m_header.file_area_virtual_offset)) -
- Common::swap32(m_header.file_area_real_offset) + header_size;
-}
-u64 TGCFileReader::GetDataSize() const
-{
- return m_size + Common::swap32(m_header.file_area_virtual_offset) -
- Common::swap32(m_header.file_area_real_offset);
-}
-
-bool TGCFileReader::Read(u64 offset, u64 nbytes, u8* out_ptr)
-{
- Interval<u64> first_part = {0, 0};
- Interval<u64> empty_part = {0, 0};
- Interval<u64> file_part = {0, 0};
-
- const u32 tgc_header_size = Common::swap32(m_header.tgc_header_size);
- const u64 split_point = Common::swap32(m_header.file_area_real_offset) - tgc_header_size;
- SplitInterval(split_point, Interval<u64>{offset, nbytes}, &first_part, &file_part);
- if (m_file_area_shift > tgc_header_size)
- {
- SplitInterval(static_cast<u64>(m_file_area_shift - tgc_header_size), file_part, &empty_part,
- &file_part);
- }
-
- // Offsets in the initial areas of the disc are unshifted
- // (except for InternalRead's constant shift by tgc_header_size).
- if (!first_part.IsEmpty())
- {
- if (!InternalRead(first_part.start, first_part.length, out_ptr + (first_part.start - offset)))
- return false;
- }
-
- // The data between the file area and the area that precedes it is treated as all zeroes.
- // The game normally won't attempt to access this part of the virtual disc, but let's not return
- // an error if it gets accessed, in case someone wants to copy or hash the whole virtual disc.
- if (!empty_part.IsEmpty())
- std::fill_n(out_ptr + (empty_part.start - offset), empty_part.length, 0);
+ m_size = m_file.GetSize();
- // Offsets in the file area are shifted by m_file_area_shift.
- if (!file_part.IsEmpty())
+ const u32 fst_offset = Common::swap32(m_header.fst_real_offset);
+ const u32 fst_size = Common::swap32(m_header.fst_size);
+ m_fst.resize(fst_size);
+ if (!m_file.Seek(fst_offset, SEEK_SET) || !m_file.ReadBytes(m_fst.data(), m_fst.size()))
+ m_fst.clear();
+
+ constexpr size_t FST_ENTRY_SIZE = 12;
+ if (m_fst.size() < FST_ENTRY_SIZE)
+ return;
+
+ // This calculation can overflow, but this is not a problem, because in that case
+ // the old_offset + file_area_shift calculation later also overflows, cancelling it out
+ const u32 file_area_shift = Common::swap32(m_header.file_area_real_offset) -
+ Common::swap32(m_header.file_area_virtual_offset) -
+ Common::swap32(m_header.tgc_header_size);
+
+ const size_t claimed_fst_entries = Common::swap32(m_fst.data() + 8);
+ const size_t fst_entries = std::min(claimed_fst_entries, m_fst.size() / FST_ENTRY_SIZE);
+ for (size_t i = 0; i < fst_entries; ++i)
{
- if (!InternalRead(file_part.start - m_file_area_shift, file_part.length,
- out_ptr + (file_part.start - offset)))
+ // If this is a file (as opposed to a directory)...
+ if (m_fst[i * FST_ENTRY_SIZE] == 0)
{
- return false;
+ // ...change its offset
+ const u32 old_offset = Common::swap32(m_fst.data() + i * FST_ENTRY_SIZE + 4);
+ const u32 new_offset = Common::swap32(old_offset + file_area_shift);
+ Replace<u32>(0, m_fst.size(), m_fst.data(), i * FST_ENTRY_SIZE + 4, new_offset);
}
}
+}
- return true;
+u64 TGCFileReader::GetDataSize() const
+{
+ return m_size - Common::swap32(m_header.tgc_header_size);
}
-bool TGCFileReader::InternalRead(u64 offset, u64 nbytes, u8* out_ptr)
+bool TGCFileReader::Read(u64 offset, u64 nbytes, u8* out_ptr)
{
const u32 tgc_header_size = Common::swap32(m_header.tgc_header_size);
if (m_file.Seek(offset + tgc_header_size, SEEK_SET) && m_file.ReadBytes(out_ptr, nbytes))
{
- Replace32(offset, nbytes, out_ptr, 0x420,
- SubtractBE32(m_header.dol_real_offset, tgc_header_size));
- Replace32(offset, nbytes, out_ptr, 0x424,
- SubtractBE32(m_header.fst_real_offset, tgc_header_size));
+ const u32 replacement_dol_offset = SubtractBE32(m_header.dol_real_offset, tgc_header_size);
+ const u32 replacement_fst_offset = SubtractBE32(m_header.fst_real_offset, tgc_header_size);
+
+ Replace<u32>(offset, nbytes, out_ptr, 0x0420, replacement_dol_offset);
+ Replace<u32>(offset, nbytes, out_ptr, 0x0424, replacement_fst_offset);
+ Replace(offset, nbytes, out_ptr, Common::swap32(replacement_fst_offset), m_fst.size(),
+ m_fst.data());
+
return true;
}