mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2026-08-11 08:01:29 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c77bbaa0f3 | ||
|
|
7345c016c9 | ||
|
|
d3c420c6a4 | ||
|
|
5ea9d7f8a3 | ||
|
|
40cdf26dc6 | ||
|
|
47bb60e564 | ||
|
|
690b51b396 | ||
|
|
1496e7a06c | ||
|
|
289ff60cc2 | ||
|
|
fe4b08bc37 | ||
|
|
22f15fbb97 | ||
|
|
7bcf78b7ea | ||
|
|
87d5e731af | ||
|
|
74857b28bb | ||
|
|
5888eb6eb6 | ||
|
|
44dd6aad30 | ||
|
|
02f2a23f89 |
@@ -69,6 +69,9 @@ DEFAULT_CONFIG = {
|
||||
# Whether our autoupdate functionality is enabled or not.
|
||||
"autoupdate": True,
|
||||
|
||||
# Whether CCache is used for the build or not.
|
||||
"ccache": False,
|
||||
|
||||
# The distributor for this build.
|
||||
"distributor": "None"
|
||||
}
|
||||
@@ -119,6 +122,12 @@ def parse_args(conf=DEFAULT_CONFIG):
|
||||
action=argparse.BooleanOptionalAction,
|
||||
default=conf["autoupdate"])
|
||||
|
||||
parser.add_argument(
|
||||
"--ccache",
|
||||
help="Enables CCache",
|
||||
action=argparse.BooleanOptionalAction,
|
||||
default=conf["ccache"])
|
||||
|
||||
parser.add_argument(
|
||||
"--distributor",
|
||||
help="Sets the distributor for this build",
|
||||
@@ -304,7 +313,9 @@ def build(config):
|
||||
# iconv, bzip2, and curl
|
||||
"-DUSE_SYSTEM_ICONV=ON",
|
||||
"-DUSE_SYSTEM_BZIP2=ON",
|
||||
"-DUSE_SYSTEM_CURL=ON"
|
||||
"-DUSE_SYSTEM_CURL=ON",
|
||||
"-DENABLE_CCACHE="
|
||||
+ python_to_cmake_bool(config["ccache"]),
|
||||
],
|
||||
env=env, cwd=arch)
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ string(TIMESTAMP DOLPHIN_WC_BUILD_DATE "%Y-%m-%d" UTC)
|
||||
|
||||
# version number
|
||||
set(DOLPHIN_VERSION_MAJOR "2606")
|
||||
set(DOLPHIN_VERSION_MINOR "0")
|
||||
set(DOLPHIN_VERSION_MINOR "1")
|
||||
set(DOLPHIN_VERSION_PATCH ${DOLPHIN_WC_REVISION})
|
||||
|
||||
# If Dolphin is not built from a Git repository, default the version info to
|
||||
|
||||
@@ -53,11 +53,22 @@ bool DolReader::Initialize(std::span<const u8> buffer)
|
||||
{
|
||||
if (m_dolheader.textSize[i] != 0)
|
||||
{
|
||||
if (buffer.size() < m_dolheader.textOffset[i] + m_dolheader.textSize[i])
|
||||
if ((m_dolheader.textAddress[i] & 31) != 0 || (m_dolheader.textSize[i] & 31) != 0)
|
||||
{
|
||||
ERROR_LOG_FMT(BOOT,
|
||||
"Text section {} is not 32-byte aligned: address = 0x{:08x}, size = 0x{:x}",
|
||||
i, m_dolheader.textAddress[i], m_dolheader.textSize[i]);
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::size_t section_offset = m_dolheader.textOffset[i];
|
||||
const std::size_t section_size = m_dolheader.textSize[i];
|
||||
|
||||
if (buffer.size() < section_offset || (buffer.size() - section_offset) < section_size)
|
||||
return false;
|
||||
|
||||
const u8* text_start = &buffer[m_dolheader.textOffset[i]];
|
||||
m_text_sections.emplace_back(text_start, &text_start[m_dolheader.textSize[i]]);
|
||||
const u8* text_start = &buffer[section_offset];
|
||||
m_text_sections.emplace_back(text_start, &text_start[section_size]);
|
||||
|
||||
for (unsigned int j = 0; !m_is_wii && j < (m_dolheader.textSize[i] / sizeof(u32)); ++j)
|
||||
{
|
||||
@@ -78,15 +89,22 @@ bool DolReader::Initialize(std::span<const u8> buffer)
|
||||
{
|
||||
if (m_dolheader.dataSize[i] != 0)
|
||||
{
|
||||
u32 section_size = m_dolheader.dataSize[i];
|
||||
u32 section_offset = m_dolheader.dataOffset[i];
|
||||
const std::size_t section_size = m_dolheader.dataSize[i];
|
||||
const std::size_t section_offset = m_dolheader.dataOffset[i];
|
||||
if ((m_dolheader.dataAddress[i] & 31) != 0 || (section_size & 31) != 0)
|
||||
{
|
||||
ERROR_LOG_FMT(BOOT,
|
||||
"Data section {} is not 32-byte aligned: address = 0x{:08x}, size = 0x{:x}",
|
||||
i, m_dolheader.dataAddress[i], section_size);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (buffer.size() < section_offset)
|
||||
return false;
|
||||
|
||||
std::vector<u8> data(section_size);
|
||||
const u8* data_start = &buffer[section_offset];
|
||||
std::memcpy(&data[0], data_start,
|
||||
std::min((size_t)section_size, buffer.size() - section_offset));
|
||||
std::memcpy(&data[0], data_start, std::min(section_size, buffer.size() - section_offset));
|
||||
m_data_sections.emplace_back(data);
|
||||
}
|
||||
else
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include "Core/Boot/ElfReader.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
@@ -70,62 +71,113 @@ static void byteswapSection(Elf32_Shdr& sec)
|
||||
|
||||
ElfReader::ElfReader(std::vector<u8> buffer) : BootExecutableReader(std::move(buffer))
|
||||
{
|
||||
Initialize(m_bytes.data());
|
||||
m_is_valid = Initialize();
|
||||
}
|
||||
|
||||
ElfReader::ElfReader(File::IOFile file) : BootExecutableReader(std::move(file))
|
||||
{
|
||||
Initialize(m_bytes.data());
|
||||
m_is_valid = Initialize();
|
||||
}
|
||||
|
||||
ElfReader::ElfReader(const std::string& filename) : BootExecutableReader(filename)
|
||||
{
|
||||
Initialize(m_bytes.data());
|
||||
m_is_valid = Initialize();
|
||||
}
|
||||
|
||||
ElfReader::~ElfReader() = default;
|
||||
|
||||
void ElfReader::Initialize(u8* ptr)
|
||||
bool ElfReader::Initialize()
|
||||
{
|
||||
base = (char*)ptr;
|
||||
base32 = (u32*)ptr;
|
||||
header = (Elf32_Ehdr*)ptr;
|
||||
if (m_bytes.size() < sizeof(Elf32_Ehdr))
|
||||
{
|
||||
ERROR_LOG_FMT(BOOT, "ELF file is too small.");
|
||||
return false;
|
||||
}
|
||||
|
||||
base = reinterpret_cast<char*>(m_bytes.data());
|
||||
base32 = reinterpret_cast<u32*>(m_bytes.data());
|
||||
header = reinterpret_cast<Elf32_Ehdr*>(m_bytes.data());
|
||||
if (header->e_ident[EI_MAG0] != ELFMAG0 || header->e_ident[EI_MAG1] != ELFMAG1 ||
|
||||
header->e_ident[EI_MAG2] != ELFMAG2 || header->e_ident[EI_MAG3] != ELFMAG3 ||
|
||||
header->e_ident[EI_CLASS] != ELFCLASS32 || header->e_ident[EI_DATA] != ELFDATA2MSB)
|
||||
{
|
||||
ERROR_LOG_FMT(BOOT, "Invalid ELF header.");
|
||||
return false;
|
||||
}
|
||||
|
||||
byteswapHeader(*header);
|
||||
|
||||
segments = (Elf32_Phdr*)(base + header->e_phoff);
|
||||
sections = (Elf32_Shdr*)(base + header->e_shoff);
|
||||
const auto is_range_valid = [this](size_t offset, size_t size) {
|
||||
return offset <= m_bytes.size() && size <= m_bytes.size() - offset;
|
||||
};
|
||||
if (header->e_ehsize != sizeof(Elf32_Ehdr) ||
|
||||
(header->e_phnum != 0 && header->e_phentsize != sizeof(Elf32_Phdr)) ||
|
||||
(header->e_shnum != 0 && header->e_shentsize != sizeof(Elf32_Shdr)) ||
|
||||
!is_range_valid(header->e_phoff, sizeof(Elf32_Phdr) * header->e_phnum) ||
|
||||
!is_range_valid(header->e_shoff, sizeof(Elf32_Shdr) * header->e_shnum) ||
|
||||
(header->e_shstrndx != SHN_UNDEF && header->e_shstrndx >= header->e_shnum))
|
||||
{
|
||||
ERROR_LOG_FMT(BOOT, "Invalid ELF header table.");
|
||||
return false;
|
||||
}
|
||||
|
||||
segments = reinterpret_cast<Elf32_Phdr*>(base + header->e_phoff);
|
||||
sections = reinterpret_cast<Elf32_Shdr*>(base + header->e_shoff);
|
||||
|
||||
for (int i = 0; i < GetNumSegments(); i++)
|
||||
{
|
||||
byteswapSegment(segments[i]);
|
||||
if (!is_range_valid(segments[i].p_offset, segments[i].p_filesz) ||
|
||||
segments[i].p_filesz > segments[i].p_memsz)
|
||||
{
|
||||
ERROR_LOG_FMT(BOOT, "Invalid ELF program header {}.", i);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < GetNumSections(); i++)
|
||||
{
|
||||
byteswapSection(sections[i]);
|
||||
if (sections[i].sh_type != SHT_NOBITS &&
|
||||
!is_range_valid(sections[i].sh_offset, sections[i].sh_size))
|
||||
{
|
||||
ERROR_LOG_FMT(BOOT, "Invalid ELF section header {}.", i);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
entryPoint = header->e_entry;
|
||||
|
||||
bRelocate = (header->e_type != ET_EXEC);
|
||||
return true;
|
||||
}
|
||||
|
||||
const char* ElfReader::GetSectionName(int section) const
|
||||
{
|
||||
if (sections[section].sh_type == SHT_NULL)
|
||||
if (!m_is_valid || section < 0 || section >= header->e_shnum ||
|
||||
sections[section].sh_type == SHT_NULL)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int nameOffset = sections[section].sh_name;
|
||||
char* ptr = (char*)GetSectionDataPtr(header->e_shstrndx);
|
||||
const Elf32_Shdr& string_section = sections[header->e_shstrndx];
|
||||
const size_t name_offset = sections[section].sh_name;
|
||||
const char* const ptr = reinterpret_cast<const char*>(GetSectionDataPtr(header->e_shstrndx));
|
||||
|
||||
if (ptr)
|
||||
return ptr + nameOffset;
|
||||
else
|
||||
if (!ptr || name_offset >= string_section.sh_size ||
|
||||
!std::memchr(ptr + name_offset, '\0', string_section.sh_size - name_offset))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return ptr + name_offset;
|
||||
}
|
||||
|
||||
// This is just a simple elf loader, good enough to load elfs generated by devkitPPC
|
||||
bool ElfReader::LoadIntoMemory(Core::System& system, bool only_in_mem1) const
|
||||
{
|
||||
if (!m_is_valid)
|
||||
return false;
|
||||
|
||||
INFO_LOG_FMT(BOOT, "String section: {}", header->e_shstrndx);
|
||||
|
||||
if (bRelocate)
|
||||
@@ -183,15 +235,36 @@ SectionID ElfReader::GetSectionByName(const char* name, int firstSection) const
|
||||
bool ElfReader::LoadSymbols(const Core::CPUThreadGuard& guard, PPCSymbolDB& ppc_symbol_db,
|
||||
const std::string& filename) const
|
||||
{
|
||||
if (!m_is_valid)
|
||||
return false;
|
||||
|
||||
bool hasSymbols = false;
|
||||
SectionID sec = GetSectionByName(".symtab");
|
||||
if (sec != -1)
|
||||
{
|
||||
int stringSection = sections[sec].sh_link;
|
||||
const char* stringBase = (const char*)GetSectionDataPtr(stringSection);
|
||||
const u32 string_section_index = sections[sec].sh_link;
|
||||
if (string_section_index >= header->e_shnum)
|
||||
{
|
||||
ERROR_LOG_FMT(BOOT, "Invalid ELF symbol string table.");
|
||||
return false;
|
||||
}
|
||||
|
||||
const Elf32_Shdr& string_section = sections[string_section_index];
|
||||
const char* stringBase = (const char*)GetSectionDataPtr(string_section_index);
|
||||
if (!stringBase)
|
||||
{
|
||||
ERROR_LOG_FMT(BOOT, "ELF symbol string table has no data.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// We have a symbol table!
|
||||
Elf32_Sym* symtab = (Elf32_Sym*)(GetSectionDataPtr(sec));
|
||||
if (!symtab)
|
||||
{
|
||||
ERROR_LOG_FMT(BOOT, "ELF symbol table has no data.");
|
||||
return false;
|
||||
}
|
||||
|
||||
int numSymbols = sections[sec].sh_size / sizeof(Elf32_Sym);
|
||||
for (int sym = 0; sym < numSymbols; sym++)
|
||||
{
|
||||
@@ -203,7 +276,14 @@ bool ElfReader::LoadSymbols(const Core::CPUThreadGuard& guard, PPCSymbolDB& ppc_
|
||||
int type = symtab[sym].st_info & 0xF;
|
||||
int sectionIndex = Common::swap16(symtab[sym].st_shndx);
|
||||
int value = Common::swap32(symtab[sym].st_value);
|
||||
const char* name = stringBase + Common::swap32(symtab[sym].st_name);
|
||||
const size_t name_offset = Common::swap32(symtab[sym].st_name);
|
||||
if (name_offset >= string_section.sh_size ||
|
||||
!std::memchr(stringBase + name_offset, '\0', string_section.sh_size - name_offset))
|
||||
{
|
||||
ERROR_LOG_FMT(BOOT, "Invalid ELF symbol name {}.", sym);
|
||||
return false;
|
||||
}
|
||||
const char* name = stringBase + name_offset;
|
||||
if (bRelocate)
|
||||
value += sectionAddrs[sectionIndex];
|
||||
|
||||
@@ -229,6 +309,9 @@ bool ElfReader::LoadSymbols(const Core::CPUThreadGuard& guard, PPCSymbolDB& ppc_
|
||||
|
||||
bool ElfReader::IsWii() const
|
||||
{
|
||||
if (!m_is_valid)
|
||||
return false;
|
||||
|
||||
// Use the same method as the DOL loader uses: search for mfspr from HID4,
|
||||
// which should only be used in Wii ELFs.
|
||||
//
|
||||
|
||||
@@ -38,8 +38,7 @@ public:
|
||||
bool LoadIntoMemory(Core::System& system, bool only_in_mem1 = false) const override;
|
||||
bool LoadSymbols(const Core::CPUThreadGuard& guard, PPCSymbolDB& ppc_symbol_db,
|
||||
const std::string& filename) const override;
|
||||
// TODO: actually check for validity.
|
||||
bool IsValid() const override { return true; }
|
||||
bool IsValid() const override { return m_is_valid; }
|
||||
bool IsWii() const override;
|
||||
|
||||
int GetNumSegments() const { return (int)(header->e_phnum); }
|
||||
@@ -65,16 +64,17 @@ public:
|
||||
bool DidRelocate() const { return bRelocate; }
|
||||
|
||||
private:
|
||||
void Initialize(u8* bytes);
|
||||
bool Initialize();
|
||||
|
||||
char* base;
|
||||
u32* base32;
|
||||
char* base = nullptr;
|
||||
u32* base32 = nullptr;
|
||||
|
||||
Elf32_Ehdr* header;
|
||||
Elf32_Phdr* segments;
|
||||
Elf32_Shdr* sections;
|
||||
Elf32_Ehdr* header = nullptr;
|
||||
Elf32_Phdr* segments = nullptr;
|
||||
Elf32_Shdr* sections = nullptr;
|
||||
|
||||
u32* sectionAddrs;
|
||||
bool bRelocate;
|
||||
u32 entryPoint;
|
||||
u32* sectionAddrs = nullptr;
|
||||
bool bRelocate = false;
|
||||
u32 entryPoint = 0;
|
||||
bool m_is_valid = false;
|
||||
};
|
||||
|
||||
@@ -59,21 +59,26 @@ PBUpdateData LoadPBUpdates(Memory::MemoryManager& memory, const PB_TYPE& pb)
|
||||
// Apply updates to a PB.
|
||||
void ApplyUpdatesForMs(int curr_ms, PB_TYPE& pb, u16* num_updates, const PBUpdateData& updates)
|
||||
{
|
||||
auto pb_mem = Common::BitCastToArray<u16>(pb);
|
||||
|
||||
u32 start_idx = 0;
|
||||
for (int i = 0; i < curr_ms; ++i)
|
||||
start_idx += num_updates[i];
|
||||
|
||||
for (u32 i = start_idx; i < start_idx + num_updates[curr_ms]; ++i)
|
||||
if (start_idx < updates.size())
|
||||
{
|
||||
u16 update_off = updates[i].pb_offset;
|
||||
u16 update_val = updates[i].new_value;
|
||||
const u16 count = num_updates[curr_ms];
|
||||
if (count <= updates.size() - start_idx)
|
||||
{
|
||||
const u32 end_idx = start_idx + count;
|
||||
for (u32 i = start_idx; i < end_idx; ++i)
|
||||
{
|
||||
const u16 update_off = updates[i].pb_offset;
|
||||
const u16 update_val = updates[i].new_value;
|
||||
|
||||
pb_mem[update_off] = update_val;
|
||||
if (update_off < (sizeof(pb) / sizeof(u16)))
|
||||
Common::BitCastPtr<u16>(&pb)[update_off] = update_val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pb = std::bit_cast<PB_TYPE>(pb_mem);
|
||||
}
|
||||
|
||||
// Used to pass a large amount of buffers to the mixing function.
|
||||
|
||||
@@ -1468,9 +1468,16 @@ void ZeldaAudioRenderer::LoadInputSamples(MixingBuffer* buffer, VPB* vpb)
|
||||
// the end of processing, if needed.
|
||||
//
|
||||
// Maximum of 0x500 samples here - see NeededRawSamplesCount to understand
|
||||
// this practical limit (resampling_ratio = 0xFFFF -> 0x500 samples). Add a
|
||||
// margin of 4 that is needed for samples source that do resampling.
|
||||
std::array<s16, 0x500 + 4> raw_input_samples;
|
||||
// this practical limit (resampling_ratio = 0xFFFF -> 0x500 samples).
|
||||
//
|
||||
// If current_pos_frac contains an (invalid) non-fractional part, it can push
|
||||
// this up by another 15 samples. Which DownloadAFCSamplesFromARAM then rounds
|
||||
// up to the next multiple of 16. So add an extra 0x10 samples to be safe.
|
||||
//
|
||||
// Plus we need an extra four samples at the start to hold the last four
|
||||
// samples from the previous frame.
|
||||
|
||||
std::array<s16, 4 + 0x500 + 0x10> raw_input_samples;
|
||||
for (size_t i = 0; i < 4; ++i)
|
||||
raw_input_samples[i] = vpb->resample_buffer[i];
|
||||
|
||||
@@ -1715,6 +1722,12 @@ void ZeldaAudioRenderer::DownloadAFCSamplesFromARAM(s16* dst, VPB* vpb, u16 requ
|
||||
return;
|
||||
}
|
||||
|
||||
if (vpb->afc_remaining_decoded_samples > 0x10) [[unlikely]]
|
||||
{
|
||||
ERROR_LOG_FMT(DSPHLE, "afc_remaining_decoded_samples > 0x10");
|
||||
vpb->afc_remaining_decoded_samples = 0x10;
|
||||
}
|
||||
|
||||
// Try several things until we have output enough samples.
|
||||
while (true)
|
||||
{
|
||||
|
||||
@@ -279,8 +279,18 @@ HostFileSystem::FstEntry* HostFileSystem::GetFstEntryForPath(const std::string&
|
||||
|
||||
void HostFileSystem::DoState(PointerWrap& p)
|
||||
{
|
||||
// Temporarily close the file, to prevent any issues with the savestating of files/folders.
|
||||
for (Handle& handle : m_handles)
|
||||
// This piece of code is handling four separate problems:
|
||||
// 1. Close host handles by calling reset on them, in case DoStateRead needs to modify a file that
|
||||
// was open.
|
||||
// 2. Close guest handles by setting opened to false on each element in m_handles, in case
|
||||
// DoStateRead needs to modify a file that was open.
|
||||
// 3. Close guest handles by setting opened to false on each element in m_handles, because if all
|
||||
// of them were open, it would make DoStateRead/DoStateWriteOrMeasure's calls to OpenFile fail.
|
||||
// 4. Create a copy of m_handles that we can restore later in case we're writing/measuring,
|
||||
// because OpenFile happily stomps over elements in m_handles that have opened set to false.
|
||||
auto handles_copy = std::move(m_handles);
|
||||
m_handles = {};
|
||||
for (Handle& handle : handles_copy)
|
||||
handle.host_file.reset();
|
||||
|
||||
// The format for the next part of the save state is follows:
|
||||
@@ -316,6 +326,8 @@ void HostFileSystem::DoState(PointerWrap& p)
|
||||
memcpy(nand_size_ptr, &size_of_nand, sizeof(size_of_nand));
|
||||
}
|
||||
}
|
||||
|
||||
m_handles = std::move(handles_copy);
|
||||
}
|
||||
else // case where we're in read mode.
|
||||
{
|
||||
|
||||
@@ -730,7 +730,7 @@ IPCReply NetIPTopDevice::HandleGetSockNameRequest(const IOCtlRequest& request)
|
||||
|
||||
request.Log(GetDeviceName(), Common::Log::LogType::IOS_WC24);
|
||||
|
||||
sockaddr sa;
|
||||
sockaddr sa{};
|
||||
socklen_t sa_len = sizeof(sa);
|
||||
const int ret =
|
||||
getsockname(GetEmulationKernel().GetSocketManager()->GetHostSocket(fd), &sa, &sa_len);
|
||||
@@ -758,7 +758,7 @@ IPCReply NetIPTopDevice::HandleGetPeerNameRequest(const IOCtlRequest& request)
|
||||
|
||||
u32 fd = memory.Read_U32(request.buffer_in);
|
||||
|
||||
sockaddr sa;
|
||||
sockaddr sa{};
|
||||
socklen_t sa_len = sizeof(sa);
|
||||
const int ret =
|
||||
getpeername(GetEmulationKernel().GetSocketManager()->GetHostSocket(fd), &sa, &sa_len);
|
||||
|
||||
@@ -173,7 +173,7 @@ bool CompressBufferIntoPacket(std::span<const u8> in_buffer, sf::Packet& packet)
|
||||
|
||||
bool DecompressPacketIntoFile(sf::Packet& packet, const std::string& file_path)
|
||||
{
|
||||
u64 file_size = Common::PacketReadU64(packet);
|
||||
const u64 file_size = Common::PacketReadU64(packet);
|
||||
|
||||
if (file_size == 0)
|
||||
return true;
|
||||
@@ -187,11 +187,12 @@ bool DecompressPacketIntoFile(sf::Packet& packet, const std::string& file_path)
|
||||
|
||||
std::vector<u8> in_buffer(LZO_OUT_LEN);
|
||||
std::vector<u8> out_buffer(LZO_IN_LEN);
|
||||
u64 bytes_written = 0;
|
||||
|
||||
while (true)
|
||||
{
|
||||
u32 cur_len = 0; // number of bytes to read
|
||||
lzo_uint new_len = 0; // number of bytes to write
|
||||
u32 cur_len = 0; // number of bytes to read
|
||||
lzo_uint new_len = out_buffer.size(); // output buffer capacity
|
||||
|
||||
packet >> cur_len;
|
||||
if (!cur_len)
|
||||
@@ -208,21 +209,29 @@ bool DecompressPacketIntoFile(sf::Packet& packet, const std::string& file_path)
|
||||
packet >> in_buffer[j];
|
||||
}
|
||||
|
||||
if (lzo1x_decompress(in_buffer.data(), cur_len, out_buffer.data(), &new_len, nullptr) !=
|
||||
if (lzo1x_decompress_safe(in_buffer.data(), cur_len, out_buffer.data(), &new_len, nullptr) !=
|
||||
LZO_E_OK)
|
||||
{
|
||||
PanicAlertFmtT("Internal LZO Error - decompression failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (new_len > file_size - bytes_written)
|
||||
{
|
||||
PanicAlertFmtT("LZO error - output is too large");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!file.WriteBytes(out_buffer.data(), new_len))
|
||||
{
|
||||
PanicAlertFmtT("Error writing file: {0}", file_path);
|
||||
return false;
|
||||
}
|
||||
|
||||
bytes_written += new_len;
|
||||
}
|
||||
|
||||
return true;
|
||||
return bytes_written == file_size;
|
||||
}
|
||||
|
||||
static bool DecompressPacketIntoFolderInternal(sf::Packet& packet, const std::string& folder_path)
|
||||
@@ -268,20 +277,21 @@ bool DecompressPacketIntoFolder(sf::Packet& packet, const std::string& folder_pa
|
||||
|
||||
std::optional<std::vector<u8>> DecompressPacketIntoBuffer(sf::Packet& packet)
|
||||
{
|
||||
u64 size = Common::PacketReadU64(packet);
|
||||
const u64 size = Common::PacketReadU64(packet);
|
||||
|
||||
std::vector<u8> out_buffer(size);
|
||||
std::vector<u8> out_buffer;
|
||||
|
||||
if (size == 0)
|
||||
return out_buffer;
|
||||
|
||||
std::vector<u8> in_buffer(LZO_OUT_LEN);
|
||||
std::vector<u8> decompressed_buffer(LZO_IN_LEN);
|
||||
|
||||
lzo_uint i = 0;
|
||||
u64 decompressed_size = 0;
|
||||
while (true)
|
||||
{
|
||||
u32 cur_len = 0; // number of bytes to read
|
||||
lzo_uint new_len = 0; // number of bytes to write
|
||||
u32 cur_len = 0; // number of bytes to read
|
||||
lzo_uint new_len = decompressed_buffer.size(); // output buffer capacity
|
||||
|
||||
packet >> cur_len;
|
||||
if (!cur_len)
|
||||
@@ -298,13 +308,28 @@ std::optional<std::vector<u8>> DecompressPacketIntoBuffer(sf::Packet& packet)
|
||||
packet >> in_buffer[j];
|
||||
}
|
||||
|
||||
if (lzo1x_decompress(in_buffer.data(), cur_len, &out_buffer[i], &new_len, nullptr) != LZO_E_OK)
|
||||
if (lzo1x_decompress_safe(in_buffer.data(), cur_len, decompressed_buffer.data(), &new_len,
|
||||
nullptr) != LZO_E_OK)
|
||||
{
|
||||
PanicAlertFmtT("Internal LZO Error - decompression failed");
|
||||
return {};
|
||||
}
|
||||
|
||||
i += new_len;
|
||||
if (new_len > size - decompressed_size || new_len > out_buffer.max_size() - out_buffer.size())
|
||||
{
|
||||
PanicAlertFmtT("LZO error - output is too large");
|
||||
return {};
|
||||
}
|
||||
|
||||
out_buffer.insert(out_buffer.end(), decompressed_buffer.begin(),
|
||||
decompressed_buffer.begin() + new_len);
|
||||
decompressed_size += new_len;
|
||||
}
|
||||
|
||||
if (decompressed_size != size)
|
||||
{
|
||||
PanicAlertFmtT("LZO error - output size mismatch");
|
||||
return {};
|
||||
}
|
||||
|
||||
return out_buffer;
|
||||
|
||||
@@ -31,42 +31,84 @@
|
||||
|
||||
namespace DiscIO
|
||||
{
|
||||
static constexpr u64 uncompressed_flag = 1ULL << 63;
|
||||
|
||||
bool IsGCZBlob(File::DirectIOFile& file);
|
||||
|
||||
CompressedBlobReader::CompressedBlobReader(File::DirectIOFile file, std::string filename)
|
||||
: m_file(std::move(file)), m_file_name(std::move(filename))
|
||||
{
|
||||
m_valid = Initialize();
|
||||
}
|
||||
|
||||
bool CompressedBlobReader::Initialize()
|
||||
{
|
||||
m_file_size = m_file.GetSize();
|
||||
m_file.Seek(0, File::SeekOrigin::Begin);
|
||||
m_file.Read(Common::AsWritableU8Span(m_header));
|
||||
if (!m_file.Read(Common::AsWritableU8Span(m_header)))
|
||||
return false;
|
||||
|
||||
SetSectorSize(m_header.block_size);
|
||||
if (m_header.magic_cookie != GCZ_MAGIC)
|
||||
return false;
|
||||
|
||||
size_t block_pointers_size = m_header.num_blocks * sizeof(u64);
|
||||
size_t hashes_size = m_header.num_blocks * sizeof(u32);
|
||||
|
||||
size_t header_size = sizeof(CompressedBlobHeader) + block_pointers_size + hashes_size;
|
||||
|
||||
// Basic sanity check for size before we start allocating
|
||||
if (header_size > m_file_size)
|
||||
{
|
||||
ERROR_LOG_FMT(DISCIO, "Headers' size is larger than file size");
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((header_size + m_header.compressed_data_size) > m_file_size)
|
||||
{
|
||||
ERROR_LOG_FMT(DISCIO, "Data size is larger than file size.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_header.num_blocks == 0)
|
||||
{
|
||||
ERROR_LOG_FMT(DISCIO, "GCZ file has zero blocks");
|
||||
return false;
|
||||
}
|
||||
|
||||
// cache block pointers and hashes
|
||||
m_block_pointers.resize(m_header.num_blocks);
|
||||
m_file.Read(Common::AsWritableU8Span(m_block_pointers));
|
||||
if (!m_file.Read(Common::AsWritableU8Span(m_block_pointers)))
|
||||
return false;
|
||||
|
||||
m_hashes.resize(m_header.num_blocks);
|
||||
m_file.Read(Common::AsWritableU8Span(m_hashes));
|
||||
if (!m_file.Read(Common::AsWritableU8Span(m_hashes)))
|
||||
return false;
|
||||
|
||||
m_data_offset = (sizeof(CompressedBlobHeader)) +
|
||||
(sizeof(u64)) * m_header.num_blocks // skip block pointers
|
||||
+ (sizeof(u32)) * m_header.num_blocks; // skip hashes
|
||||
m_data_offset = header_size;
|
||||
|
||||
// A compressed block is never ever longer than a decompressed block, so just header.block_size
|
||||
// should be fine.
|
||||
// I still add some safety margin.
|
||||
const u32 zlib_buffer_size = m_header.block_size + 64;
|
||||
m_zlib_buffer.resize(zlib_buffer_size);
|
||||
|
||||
SetSectorSize(m_header.block_size);
|
||||
|
||||
return ValidateBlockPointers();
|
||||
}
|
||||
|
||||
std::unique_ptr<CompressedBlobReader> CompressedBlobReader::Create(File::DirectIOFile file,
|
||||
const std::string& filename)
|
||||
{
|
||||
if (IsGCZBlob(file))
|
||||
return std::unique_ptr<CompressedBlobReader>(
|
||||
{
|
||||
std::unique_ptr<CompressedBlobReader> reader(
|
||||
new CompressedBlobReader(std::move(file), filename));
|
||||
|
||||
if (reader->m_valid)
|
||||
return reader;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -80,9 +122,9 @@ std::unique_ptr<BlobReader> CompressedBlobReader::CopyReader() const
|
||||
// IMPORTANT: Calling this function invalidates all earlier pointers gotten from this function.
|
||||
u64 CompressedBlobReader::GetBlockCompressedSize(u64 block_num) const
|
||||
{
|
||||
u64 start = m_block_pointers[block_num];
|
||||
u64 start = m_block_pointers[block_num] & ~uncompressed_flag;
|
||||
if (block_num < m_header.num_blocks - 1)
|
||||
return m_block_pointers[block_num + 1] - start;
|
||||
return (m_block_pointers[block_num + 1] & ~uncompressed_flag) - start;
|
||||
else if (block_num == m_header.num_blocks - 1)
|
||||
return m_header.compressed_data_size - start;
|
||||
else
|
||||
@@ -92,22 +134,33 @@ u64 CompressedBlobReader::GetBlockCompressedSize(u64 block_num) const
|
||||
|
||||
bool CompressedBlobReader::GetBlock(u64 block_num, u8* out_ptr)
|
||||
{
|
||||
if (block_num >= m_header.num_blocks)
|
||||
return false;
|
||||
|
||||
bool uncompressed = false;
|
||||
u32 comp_block_size = (u32)GetBlockCompressedSize(block_num);
|
||||
u64 read_size = GetBlockCompressedSize(block_num);
|
||||
u64 offset = m_block_pointers[block_num] + m_data_offset;
|
||||
|
||||
if (offset & (1ULL << 63))
|
||||
if (offset & uncompressed_flag)
|
||||
{
|
||||
if (comp_block_size != m_header.block_size)
|
||||
if (read_size != m_header.block_size)
|
||||
{
|
||||
ERROR_LOG_FMT(DISCIO, "Uncompressed block with wrong size");
|
||||
return false;
|
||||
}
|
||||
uncompressed = true;
|
||||
offset &= ~(1ULL << 63);
|
||||
offset &= ~uncompressed_flag;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (read_size > m_zlib_buffer.size())
|
||||
{
|
||||
ERROR_LOG_FMT(DISCIO, "Compressed block is too large");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// clear unused part of zlib buffer. maybe this can be deleted when it works fully.
|
||||
memset(&m_zlib_buffer[comp_block_size], 0, m_zlib_buffer.size() - comp_block_size);
|
||||
|
||||
if (!m_file.OffsetRead(offset, m_zlib_buffer.data(), comp_block_size))
|
||||
if (!m_file.OffsetRead(offset, m_zlib_buffer.data(), read_size))
|
||||
{
|
||||
ERROR_LOG_FMT(DISCIO, "The disc image \"{}\" is truncated, some of the data is missing.",
|
||||
m_file_name);
|
||||
@@ -115,7 +168,7 @@ bool CompressedBlobReader::GetBlock(u64 block_num, u8* out_ptr)
|
||||
}
|
||||
|
||||
// First, check hash.
|
||||
const u32 block_hash = Common::HashAdler32(m_zlib_buffer.data(), comp_block_size);
|
||||
const u32 block_hash = Common::HashAdler32(m_zlib_buffer.data(), read_size);
|
||||
if (block_hash != m_hashes[block_num])
|
||||
{
|
||||
ERROR_LOG_FMT(DISCIO,
|
||||
@@ -126,13 +179,13 @@ bool CompressedBlobReader::GetBlock(u64 block_num, u8* out_ptr)
|
||||
|
||||
if (uncompressed)
|
||||
{
|
||||
std::copy_n(m_zlib_buffer.begin(), comp_block_size, out_ptr);
|
||||
std::copy_n(m_zlib_buffer.begin(), m_header.block_size, out_ptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
z_stream z = {};
|
||||
z.next_in = m_zlib_buffer.data();
|
||||
z.avail_in = comp_block_size;
|
||||
z.avail_in = read_size;
|
||||
if (z.avail_in > m_header.block_size)
|
||||
{
|
||||
ERROR_LOG_FMT(DISCIO, "Compressed block size is larger than uncompressed block size");
|
||||
@@ -158,6 +211,46 @@ bool CompressedBlobReader::GetBlock(u64 block_num, u8* out_ptr)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CompressedBlobReader::ValidateBlockPointers() const
|
||||
{
|
||||
size_t valid_pointers = 0;
|
||||
|
||||
// Validate block pointers
|
||||
for (u32 i = 0; i < m_header.num_blocks; ++i)
|
||||
{
|
||||
u64 next;
|
||||
if (i + 1 < m_header.num_blocks)
|
||||
next = m_block_pointers[i + 1] & ~uncompressed_flag;
|
||||
else
|
||||
next = m_header.compressed_data_size;
|
||||
|
||||
if (next > m_header.compressed_data_size)
|
||||
continue;
|
||||
|
||||
u64 offset = m_block_pointers[i] & ~uncompressed_flag;
|
||||
if (offset > m_header.compressed_data_size)
|
||||
continue;
|
||||
|
||||
bool uncompressed = m_block_pointers[i] & uncompressed_flag;
|
||||
u64 size = next - offset;
|
||||
|
||||
if (uncompressed && size != m_header.block_size)
|
||||
continue;
|
||||
|
||||
if (!uncompressed && size > m_zlib_buffer.size())
|
||||
continue;
|
||||
|
||||
valid_pointers++;
|
||||
}
|
||||
|
||||
size_t invalid_pointers = m_header.num_blocks - valid_pointers;
|
||||
|
||||
if (invalid_pointers > 0)
|
||||
ERROR_LOG_FMT(DISCIO, "GCZ file has {} invalid block pointers", invalid_pointers);
|
||||
|
||||
return invalid_pointers == 0;
|
||||
}
|
||||
|
||||
struct CompressThreadState
|
||||
{
|
||||
CompressThreadState() : z{} {}
|
||||
@@ -245,7 +338,7 @@ static ConversionResultCode Output(OutputParameters parameters, File::DirectIOFi
|
||||
{
|
||||
u64 offset = *position;
|
||||
if (!parameters.compressed)
|
||||
offset |= 0x8000000000000000ULL;
|
||||
offset |= uncompressed_flag;
|
||||
(*offsets)[parameters.block_number] = offset;
|
||||
|
||||
*position += parameters.data.size();
|
||||
@@ -293,10 +386,10 @@ bool ConvertToGCZ(BlobReader* infile, const std::string& infile_path,
|
||||
header.magic_cookie = GCZ_MAGIC;
|
||||
header.sub_type = sub_type;
|
||||
header.block_size = block_size;
|
||||
header.data_size = infile->GetDataSize();
|
||||
header.disc_size = infile->GetDataSize();
|
||||
|
||||
// round upwards!
|
||||
header.num_blocks = (u32)((header.data_size + (block_size - 1)) / block_size);
|
||||
header.num_blocks = (u32)((header.disc_size + (block_size - 1)) / block_size);
|
||||
|
||||
std::vector<u64> offsets(header.num_blocks);
|
||||
std::vector<u32> hashes(header.num_blocks);
|
||||
@@ -332,7 +425,7 @@ bool ConvertToGCZ(BlobReader* infile, const std::string& infile_path,
|
||||
if (compressor.GetStatus() != ConversionResultCode::Success)
|
||||
break;
|
||||
|
||||
const u64 bytes_to_read = std::min<u64>(block_size, header.data_size - inpos);
|
||||
const u64 bytes_to_read = std::min<u64>(block_size, header.disc_size - inpos);
|
||||
|
||||
if (!infile->Read(inpos, bytes_to_read, in_buf.data()))
|
||||
{
|
||||
|
||||
@@ -35,7 +35,7 @@ struct CompressedBlobHeader // 32 bytes
|
||||
u32 magic_cookie; // 0xB10BB10B
|
||||
u32 sub_type; // GC image, whatever
|
||||
u64 compressed_data_size;
|
||||
u64 data_size;
|
||||
u64 disc_size;
|
||||
u32 block_size;
|
||||
u32 num_blocks;
|
||||
};
|
||||
@@ -53,7 +53,7 @@ public:
|
||||
std::unique_ptr<BlobReader> CopyReader() const override;
|
||||
|
||||
u64 GetRawSize() const override { return m_file_size; }
|
||||
u64 GetDataSize() const override { return m_header.data_size; }
|
||||
u64 GetDataSize() const override { return m_header.disc_size; }
|
||||
DataSizeType GetDataSizeType() const override { return DataSizeType::Accurate; }
|
||||
|
||||
u64 GetBlockSize() const override { return m_header.block_size; }
|
||||
@@ -66,15 +66,18 @@ public:
|
||||
|
||||
private:
|
||||
CompressedBlobReader(File::DirectIOFile file, std::string filename);
|
||||
bool Initialize();
|
||||
bool ValidateBlockPointers() const;
|
||||
|
||||
CompressedBlobHeader m_header;
|
||||
std::vector<u64> m_block_pointers;
|
||||
std::vector<u32> m_hashes;
|
||||
int m_data_offset;
|
||||
File::DirectIOFile m_file;
|
||||
u64 m_file_size;
|
||||
std::vector<u8> m_zlib_buffer;
|
||||
std::string m_file_name;
|
||||
CompressedBlobHeader m_header = {};
|
||||
std::vector<u64> m_block_pointers = {};
|
||||
std::vector<u32> m_hashes = {};
|
||||
u64 m_data_offset = 0;
|
||||
File::DirectIOFile m_file = {};
|
||||
u64 m_file_size = 0;
|
||||
std::vector<u8> m_zlib_buffer = {};
|
||||
std::string m_file_name = {};
|
||||
bool m_valid = false;
|
||||
};
|
||||
|
||||
} // namespace DiscIO
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "Common/ChunkFile.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/FileUtil.h"
|
||||
#include "Core/IOS/FS/FileSystem.h"
|
||||
@@ -469,3 +470,108 @@ TEST_F(FileSystemTest, CreateFullPath)
|
||||
EXPECT_EQ(m_fs->CreateFullPath(Uid{0x1000}, Gid{1}, "/shared2/wc24/mbox/Readme.txt", 0, modes),
|
||||
ResultCode::Success);
|
||||
}
|
||||
|
||||
TEST_F(FileSystemTest, DoState)
|
||||
{
|
||||
const std::string TEST_DATA_1 = "123";
|
||||
const std::string TEST_DATA_2 = "4567";
|
||||
|
||||
std::array<u8, 4> read_buffer;
|
||||
|
||||
ASSERT_EQ(m_fs->CreateDirectory(Uid{1}, Gid{2}, "/tmp/a", 0, modes), ResultCode::Success);
|
||||
ASSERT_EQ(m_fs->CreateDirectory(Uid{1}, Gid{2}, "/tmp/a/b", 0, modes), ResultCode::Success);
|
||||
ASSERT_EQ(m_fs->CreateDirectory(Uid{0}, Gid{0}, "/tmp/a/c", 0, modes), ResultCode::Success);
|
||||
|
||||
ASSERT_EQ(m_fs->CreateFile(Uid{1}, Gid{2}, "/tmp/a/d", 0, modes), ResultCode::Success);
|
||||
ASSERT_EQ(m_fs->CreateFile(Uid{3}, Gid{4}, "/tmp/e", 0, modes), ResultCode::Success);
|
||||
|
||||
{
|
||||
Result<FileHandle> file1 = m_fs->OpenFile(Uid{1}, Gid{2}, "/tmp/a/d", Mode::ReadWrite);
|
||||
ASSERT_TRUE(file1.has_value());
|
||||
ASSERT_TRUE(file1->Write(TEST_DATA_1.data(), TEST_DATA_1.size()).has_value());
|
||||
}
|
||||
|
||||
std::array<u8, 1024> state_buffer;
|
||||
size_t state_size;
|
||||
|
||||
{
|
||||
Result<FileHandle> file2 = m_fs->OpenFile(Uid{3}, Gid{4}, "/tmp/e", Mode::ReadWrite);
|
||||
ASSERT_TRUE(file2.has_value());
|
||||
ASSERT_TRUE(file2->Write(TEST_DATA_2.data(), TEST_DATA_2.size()).has_value());
|
||||
|
||||
u8* state_pointer = state_buffer.data();
|
||||
PointerWrap p(&state_pointer, state_buffer.size(), PointerWrap::Mode::Write);
|
||||
m_fs->DoState(p);
|
||||
ASSERT_TRUE(p.IsWriteMode());
|
||||
|
||||
ASSERT_TRUE(file2->Seek(2, SeekMode::Set).has_value());
|
||||
ASSERT_TRUE(file2->Write("_", 1).has_value());
|
||||
|
||||
Fd fd = file2->Release();
|
||||
p.Do(fd);
|
||||
ASSERT_TRUE(p.IsWriteMode());
|
||||
|
||||
state_size = state_pointer - state_buffer.data();
|
||||
}
|
||||
|
||||
ASSERT_EQ(m_fs->Delete(Uid{0}, Gid{0}, "/tmp/a"), ResultCode::Success);
|
||||
ASSERT_EQ(m_fs->GetMetadata(Uid{0}, Gid{0}, "/tmp/a").error(), ResultCode::NotFound);
|
||||
ASSERT_EQ(m_fs->GetMetadata(Uid{0}, Gid{0}, "/tmp/a/b").error(), ResultCode::NotFound);
|
||||
ASSERT_EQ(m_fs->GetMetadata(Uid{0}, Gid{0}, "/tmp/a/c").error(), ResultCode::NotFound);
|
||||
ASSERT_EQ(m_fs->GetMetadata(Uid{0}, Gid{0}, "/tmp/a/d").error(), ResultCode::NotFound);
|
||||
|
||||
ASSERT_EQ(m_fs->CreateFile(Uid{5}, Gid{6}, "/tmp/f", 0, modes), ResultCode::Success);
|
||||
|
||||
ASSERT_EQ(m_fs->CreateDirectory(Uid{7}, Gid{8}, "/tmp/g", 0, modes), ResultCode::Success);
|
||||
|
||||
u8* state_pointer = state_buffer.data();
|
||||
PointerWrap p(&state_pointer, state_size, PointerWrap::Mode::Read);
|
||||
m_fs->DoState(p);
|
||||
ASSERT_TRUE(p.IsReadMode());
|
||||
|
||||
constexpr auto check_directory_metadata = [](const Result<Metadata>& metadata, Uid uid, Gid gid) {
|
||||
ASSERT_TRUE(metadata.has_value());
|
||||
ASSERT_EQ(metadata->uid, uid);
|
||||
ASSERT_EQ(metadata->gid, gid);
|
||||
ASSERT_FALSE(metadata->is_file);
|
||||
};
|
||||
|
||||
constexpr auto check_file_metadata = [](const Result<Metadata>& metadata, Uid uid, Gid gid,
|
||||
u32 size) {
|
||||
ASSERT_TRUE(metadata.has_value());
|
||||
ASSERT_EQ(metadata->uid, uid);
|
||||
ASSERT_EQ(metadata->gid, gid);
|
||||
ASSERT_TRUE(metadata->is_file);
|
||||
ASSERT_EQ(metadata->size, size);
|
||||
};
|
||||
|
||||
check_directory_metadata(m_fs->GetMetadata(Uid{0}, Gid{0}, "/tmp/a"), Uid{1}, Gid{2});
|
||||
check_directory_metadata(m_fs->GetMetadata(Uid{0}, Gid{0}, "/tmp/a/b"), Uid{1}, Gid{2});
|
||||
check_directory_metadata(m_fs->GetMetadata(Uid{0}, Gid{0}, "/tmp/a/c"), Uid{0}, Gid{0});
|
||||
|
||||
check_file_metadata(m_fs->GetMetadata(Uid{0}, Gid{0}, "/tmp/a/d"), Uid{1}, Gid{2}, 3);
|
||||
check_file_metadata(m_fs->GetMetadata(Uid{0}, Gid{0}, "/tmp/e"), Uid{3}, Gid{4}, 4);
|
||||
|
||||
ASSERT_EQ(m_fs->GetMetadata(Uid{0}, Gid{0}, "/tmp/f").error(), ResultCode::NotFound);
|
||||
ASSERT_EQ(m_fs->GetMetadata(Uid{0}, Gid{0}, "/tmp/g").error(), ResultCode::NotFound);
|
||||
|
||||
Fd fd{};
|
||||
p.Do(fd);
|
||||
ASSERT_TRUE(p.IsReadMode());
|
||||
|
||||
{
|
||||
FileHandle file2(m_fs.get(), fd);
|
||||
ASSERT_EQ(file2.GetStatus()->offset, 4u);
|
||||
ASSERT_TRUE(file2.Seek(0, SeekMode::Set).has_value());
|
||||
ASSERT_TRUE(file2.Read(read_buffer.data(), TEST_DATA_2.size()).has_value());
|
||||
for (size_t i = 0; i < TEST_DATA_2.size(); ++i)
|
||||
ASSERT_EQ(read_buffer[i], TEST_DATA_2[i]);
|
||||
}
|
||||
|
||||
{
|
||||
Result<FileHandle> file1 = m_fs->OpenFile(Uid{5}, Gid{6}, "/tmp/a/d", Mode::Read);
|
||||
ASSERT_TRUE(file1->Read(read_buffer.data(), TEST_DATA_1.size()).has_value());
|
||||
for (size_t i = 0; i < TEST_DATA_1.size(); ++i)
|
||||
ASSERT_EQ(read_buffer[i], TEST_DATA_1[i]);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user