mirror of
https://github.com/hedge-dev/UnleashedRecomp.git
synced 2025-10-30 07:11:05 +00:00
648 lines
20 KiB
C++
648 lines
20 KiB
C++
// Referenced from: https://github.com/xenia-canary/xenia-canary/blob/canary_experimental/src/xenia/vfs/devices/xcontent_container_device.cc
|
|
|
|
/**
|
|
******************************************************************************
|
|
* Xenia : Xbox 360 Emulator Research Project *
|
|
******************************************************************************
|
|
* Copyright 2023 Ben Vanik. All rights reserved. *
|
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
|
******************************************************************************
|
|
*/
|
|
|
|
|
|
#include "xcontent_file_system.h"
|
|
|
|
#include <bit>
|
|
#include <set>
|
|
#include <stack>
|
|
|
|
enum class XContentPackageType
|
|
{
|
|
CON = 0x434F4E20,
|
|
PIRS = 0x50495253,
|
|
LIVE = 0x4C495645,
|
|
};
|
|
|
|
struct XContentLicense
|
|
{
|
|
be<uint64_t> licenseId;
|
|
be<uint32_t> licenseBits;
|
|
be<uint32_t> licenseFlags;
|
|
};
|
|
|
|
#pragma pack(push, 1)
|
|
struct XContentHeader
|
|
{
|
|
be<uint32_t> magic;
|
|
uint8_t signature[0x228];
|
|
XContentLicense licenses[0x10];
|
|
uint8_t contentId[0x14];
|
|
be<uint32_t> headerSize;
|
|
};
|
|
static_assert(sizeof(XContentHeader) == 0x344);
|
|
|
|
struct StfsVolumeDescriptor
|
|
{
|
|
uint8_t descriptorLength;
|
|
uint8_t version;
|
|
|
|
union
|
|
{
|
|
uint8_t asByte;
|
|
struct
|
|
{
|
|
uint8_t readOnlyFormat : 1;
|
|
uint8_t rootActiveIndex : 1;
|
|
uint8_t directoryOverallocated : 1;
|
|
uint8_t directoryIndexBoundsValid : 1;
|
|
} bits;
|
|
} flags;
|
|
|
|
uint16_t fileTableBlockCount;
|
|
uint8_t fileTableBlockNumberRaw[3];
|
|
uint8_t topHashTableHash[0x14];
|
|
be<uint32_t> totalBlockCount;
|
|
be<uint32_t> freeBlockCount;
|
|
};
|
|
static_assert(sizeof(StfsVolumeDescriptor) == 0x24);
|
|
|
|
struct StfsDirectoryEntry {
|
|
char name[40];
|
|
|
|
struct
|
|
{
|
|
uint8_t nameLength : 6;
|
|
uint8_t contiguous : 1;
|
|
uint8_t directory : 1;
|
|
} flags;
|
|
|
|
uint8_t validDataBlocksRaw[3];
|
|
uint8_t allocatedDataBlocksRaw[3];
|
|
uint8_t startBlockNumberRaw[3];
|
|
be<uint16_t> directoryIndex;
|
|
be<uint32_t> length;
|
|
be<uint16_t> createDate;
|
|
be<uint16_t> createTime;
|
|
be<uint16_t> modifiedDate;
|
|
be<uint16_t> modifiedTime;
|
|
};
|
|
static_assert(sizeof(StfsDirectoryEntry) == 0x40);
|
|
|
|
struct StfsDirectoryBlock {
|
|
StfsDirectoryEntry entries[0x40];
|
|
};
|
|
static_assert(sizeof(StfsDirectoryBlock) == 0x1000);
|
|
|
|
struct StfsHashEntry {
|
|
uint8_t sha1[0x14];
|
|
be<uint32_t> infoRaw;
|
|
};
|
|
static_assert(sizeof(StfsHashEntry) == 0x18);
|
|
|
|
struct StfsHashTable {
|
|
StfsHashEntry entries[170];
|
|
be<uint32_t> numBlocks;
|
|
uint8_t padding[12];
|
|
};
|
|
static_assert(sizeof(StfsHashTable) == 0x1000);
|
|
|
|
struct SvodDeviceDescriptor {
|
|
uint8_t descriptorLength;
|
|
uint8_t blockCacheElementCount;
|
|
uint8_t workerThreadProcessor;
|
|
uint8_t workerThreadPriority;
|
|
uint8_t firstFragmentHashEntry[0x14];
|
|
union {
|
|
uint8_t asByte;
|
|
struct {
|
|
uint8_t mustBeZeroForFutureUsage : 6;
|
|
uint8_t enhancedGdfLayout : 1;
|
|
uint8_t zeroForDownlevelClients : 1;
|
|
} bits;
|
|
} features;
|
|
uint8_t numDataBlocksRaw[3];
|
|
uint8_t startDataBlockRaw[3];
|
|
uint8_t reserved[5];
|
|
};
|
|
static_assert(sizeof(SvodDeviceDescriptor) == 0x24);
|
|
|
|
struct SvodDirectoryEntry {
|
|
uint16_t nodeL;
|
|
uint16_t nodeR;
|
|
uint32_t dataBlock;
|
|
uint32_t length;
|
|
uint8_t attributes;
|
|
uint8_t nameLength;
|
|
};
|
|
static_assert(sizeof(SvodDirectoryEntry) == 0xE);
|
|
|
|
struct XContentMetadata
|
|
{
|
|
be<uint32_t> contentType;
|
|
be<uint32_t> metadataVersion;
|
|
be<uint64_t> contentSize;
|
|
uint8_t executionInfo[24];
|
|
uint8_t consoleId[5];
|
|
be<uint64_t> profileId;
|
|
|
|
union {
|
|
StfsVolumeDescriptor stfsVolumeDescriptor;
|
|
SvodDeviceDescriptor svodDeviceDescriptor;
|
|
};
|
|
|
|
be<uint32_t> dataFileCount;
|
|
be<uint64_t> dataFileSize;
|
|
be<uint32_t> volumeType;
|
|
be<uint64_t> onlineCreator;
|
|
be<uint32_t> category;
|
|
};
|
|
static_assert(sizeof(XContentMetadata) == 0x75);
|
|
|
|
#pragma pack(pop)
|
|
|
|
struct XContentContainerHeader
|
|
{
|
|
XContentHeader contentHeader;
|
|
XContentMetadata contentMetadata;
|
|
};
|
|
|
|
const uint32_t StfsBlockSize = 0x1000;
|
|
const uint32_t StfsBlocksHashLevelAmount = 3;
|
|
const uint32_t StfsBlocksPerHashLevel[StfsBlocksHashLevelAmount] = { 170, 28900, 4913000 };
|
|
const uint32_t StfsEndOfChain = 0xFFFFFF;
|
|
const uint32_t StfsEntriesPerDirectoryBlock = StfsBlockSize / sizeof(StfsDirectoryEntry);
|
|
|
|
uint32_t parseUint24(const uint8_t *bytes) {
|
|
return bytes[0] | (bytes[1] << 8U) | (bytes[2] << 16U);
|
|
}
|
|
|
|
size_t blockIndexToOffset(uint64_t baseOffset, uint64_t blockIndex)
|
|
{
|
|
uint64_t block = blockIndex;
|
|
for (uint32_t i = 0; i < StfsBlocksHashLevelAmount; i++)
|
|
{
|
|
uint32_t levelBase = StfsBlocksPerHashLevel[i];
|
|
block += ((blockIndex + levelBase) / levelBase);
|
|
if (blockIndex < levelBase)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
return baseOffset + (block << 12);
|
|
}
|
|
|
|
uint32_t blockIndexToHashBlockNumber(uint32_t blockIndex) {
|
|
if (blockIndex < StfsBlocksPerHashLevel[0])
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
uint32_t block = (blockIndex / StfsBlocksPerHashLevel[0]) * (StfsBlocksPerHashLevel[0] + 1);
|
|
block += ((blockIndex / StfsBlocksPerHashLevel[1]) + 1);
|
|
if (blockIndex < StfsBlocksPerHashLevel[1])
|
|
{
|
|
return block;
|
|
}
|
|
|
|
return block + 1;
|
|
}
|
|
|
|
size_t blockIndexToHashBlockOffset(uint64_t baseOffset, uint32_t blockIndex)
|
|
{
|
|
size_t blockNumber = blockIndexToHashBlockNumber(blockIndex);
|
|
return baseOffset + (blockNumber << 12);
|
|
}
|
|
|
|
const StfsHashEntry *hashEntryFromBlockIndex(const uint8_t *fileData, uint64_t baseOffset, uint64_t blockIndex)
|
|
{
|
|
size_t hashOffset = blockIndexToHashBlockOffset(baseOffset, blockIndex);
|
|
const StfsHashTable *hashTable = (const StfsHashTable *)(&fileData[hashOffset]);
|
|
return &hashTable->entries[blockIndex % StfsBlocksPerHashLevel[0]];
|
|
}
|
|
|
|
void blockToOffsetAndFile(SvodLayoutType svodLayoutType, size_t svodStartDataBlock, size_t svodBaseOffset, size_t block, size_t &outOffset, size_t &outFileIndex)
|
|
{
|
|
const size_t BlockSize = 0x800;
|
|
const size_t HashBlockSize = 0x1000;
|
|
const size_t BlocksPerL0Hash = 0x198;
|
|
const size_t HashesPerL1Hash = 0xA1C4;
|
|
const size_t BlocksPerFile = 0x14388;
|
|
const size_t MaxFileSize = 0xA290000;
|
|
size_t trueBlock = block - (svodStartDataBlock * 2);
|
|
if (svodLayoutType == SvodLayoutType::EnhancedGDF)
|
|
{
|
|
trueBlock += 0x2;
|
|
}
|
|
|
|
size_t fileBlock = trueBlock % BlocksPerFile;
|
|
outFileIndex = trueBlock / BlocksPerFile;
|
|
|
|
size_t offset = 0;
|
|
size_t level0TableCount = (fileBlock / BlocksPerL0Hash) + 1;
|
|
offset += level0TableCount * HashBlockSize;
|
|
|
|
size_t level1TableCount = (level0TableCount / HashesPerL1Hash) + 1;
|
|
offset += level1TableCount * HashBlockSize;
|
|
|
|
if (svodLayoutType == SvodLayoutType::SingleFile)
|
|
{
|
|
offset += svodBaseOffset;
|
|
}
|
|
|
|
outOffset = (fileBlock * BlockSize) + offset;
|
|
if (outOffset >= MaxFileSize)
|
|
{
|
|
outOffset = (outOffset % MaxFileSize) + 0x2000;
|
|
outFileIndex++;
|
|
}
|
|
}
|
|
|
|
XContentFileSystem::XContentFileSystem(const std::filesystem::path &contentPath)
|
|
{
|
|
mappedFiles.emplace_back();
|
|
|
|
MemoryMappedFile &rootMappedFile = mappedFiles.back();
|
|
rootMappedFile.open(contentPath);
|
|
if (!rootMappedFile.isOpen())
|
|
{
|
|
return;
|
|
}
|
|
|
|
name = (const char *)(contentPath.filename().u8string().data());
|
|
|
|
const uint8_t *rootMappedFileData = rootMappedFile.data();
|
|
if (sizeof(XContentContainerHeader) > rootMappedFile.size())
|
|
{
|
|
mappedFiles.clear();
|
|
return;
|
|
}
|
|
|
|
XContentContainerHeader contentContainerHeader = *(const XContentContainerHeader *)(rootMappedFileData);
|
|
XContentPackageType packageType = XContentPackageType(contentContainerHeader.contentHeader.magic.get());
|
|
if (packageType != XContentPackageType::CON && packageType != XContentPackageType::LIVE && packageType != XContentPackageType::PIRS)
|
|
{
|
|
mappedFiles.clear();
|
|
return;
|
|
}
|
|
|
|
const XContentMetadata &metadata = contentContainerHeader.contentMetadata;
|
|
volumeType = XContentVolumeType(metadata.volumeType.get());
|
|
if (volumeType == XContentVolumeType::STFS)
|
|
{
|
|
const StfsVolumeDescriptor &descriptor = metadata.stfsVolumeDescriptor;
|
|
if (descriptor.descriptorLength != sizeof(StfsVolumeDescriptor) || !descriptor.flags.bits.readOnlyFormat)
|
|
{
|
|
mappedFiles.clear();
|
|
return;
|
|
}
|
|
|
|
baseOffset = ((contentContainerHeader.contentHeader.headerSize + StfsBlockSize - 1) / StfsBlockSize) * StfsBlockSize;
|
|
|
|
uint32_t entryCount = 0;
|
|
uint32_t tableBlockIndex = parseUint24(descriptor.fileTableBlockNumberRaw);
|
|
uint32_t tableBlockCount = descriptor.fileTableBlockCount;
|
|
std::map<uint32_t, std::string> directoryNames;
|
|
for (uint32_t i = 0; i < tableBlockCount; i++)
|
|
{
|
|
size_t offset = blockIndexToOffset(baseOffset, tableBlockIndex);
|
|
if (offset + sizeof(StfsDirectoryBlock) > rootMappedFile.size())
|
|
{
|
|
mappedFiles.clear();
|
|
return;
|
|
}
|
|
|
|
StfsDirectoryBlock *directoryBlock = (StfsDirectoryBlock *)(&rootMappedFileData[offset]);
|
|
for (uint32_t j = 0; j < StfsEntriesPerDirectoryBlock; j++)
|
|
{
|
|
const StfsDirectoryEntry &directoryEntry = directoryBlock->entries[j];
|
|
if (directoryEntry.name[0] == '\0')
|
|
{
|
|
break;
|
|
}
|
|
|
|
std::string fileNameBase = directoryNames[directoryEntry.directoryIndex];
|
|
std::string fileName(directoryEntry.name, directoryEntry.flags.nameLength & 0x3F);
|
|
if (directoryEntry.flags.directory)
|
|
{
|
|
directoryNames[entryCount++] = fileNameBase + fileName + "/";
|
|
continue;
|
|
}
|
|
|
|
uint32_t fileBlockIndex = parseUint24(directoryEntry.startBlockNumberRaw);
|
|
uint32_t fileBlockCount = parseUint24(directoryEntry.allocatedDataBlocksRaw);
|
|
fileMap[fileNameBase + fileName] = { directoryEntry.length, fileBlockIndex, fileBlockCount };
|
|
entryCount++;
|
|
}
|
|
|
|
const StfsHashEntry *hashEntry = hashEntryFromBlockIndex(rootMappedFileData, baseOffset, tableBlockIndex);
|
|
tableBlockIndex = hashEntry->infoRaw & 0xFFFFFF;
|
|
if (tableBlockIndex == StfsEndOfChain)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else if (volumeType == XContentVolumeType::SVOD)
|
|
{
|
|
mappedFiles.clear();
|
|
|
|
// Close the root file and open all the files inside the directory with the same name instead.
|
|
std::filesystem::path dataDirectory(contentPath.u8string() + u8".data");
|
|
if (!std::filesystem::is_directory(dataDirectory))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Find all data files inside the directory.
|
|
std::set<std::filesystem::path> orderedPaths;
|
|
for (auto &entry : std::filesystem::directory_iterator(dataDirectory))
|
|
{
|
|
if (!entry.is_regular_file())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
orderedPaths.emplace(entry.path());
|
|
}
|
|
|
|
// Memory map all the files that were found.
|
|
for (auto &path : orderedPaths)
|
|
{
|
|
mappedFiles.emplace_back();
|
|
if (!mappedFiles.back().open(path))
|
|
{
|
|
mappedFiles.clear();
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (mappedFiles.empty())
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Determine the layout of the SVOD from the first file.
|
|
MemoryMappedFile &firstMappedFile = mappedFiles.front();
|
|
const uint8_t *firstMappedFileData = firstMappedFile.data();
|
|
const char *RefMagic = "MICROSOFT*XBOX*MEDIA";
|
|
size_t RefXSFMagicOffset = 0x12000;
|
|
size_t SingleFileMagicOffset = 0xD000;
|
|
if (metadata.svodDeviceDescriptor.features.bits.enhancedGdfLayout)
|
|
{
|
|
size_t EGDFMagicOffset = 0x2000;
|
|
if (EGDFMagicOffset >= firstMappedFile.size() || std::memcmp(&firstMappedFileData[EGDFMagicOffset], RefMagic, strlen(RefMagic)) != 0)
|
|
{
|
|
mappedFiles.clear();
|
|
return;
|
|
}
|
|
|
|
svodBaseOffset = 0;
|
|
svodMagicOffset = EGDFMagicOffset;
|
|
svodLayoutType = SvodLayoutType::EnhancedGDF;
|
|
}
|
|
else if (RefXSFMagicOffset < firstMappedFile.size() && std::memcmp(&firstMappedFileData[RefXSFMagicOffset], RefMagic, strlen(RefMagic)) == 0)
|
|
{
|
|
const char *XSFMagic = "XSF";
|
|
size_t XSFMagicOffset = 0x2000;
|
|
svodBaseOffset = 0x10000;
|
|
svodMagicOffset = 0x12000;
|
|
|
|
if (std::memcmp(&firstMappedFileData[XSFMagicOffset], XSFMagic, strlen(XSFMagic)) == 0)
|
|
{
|
|
svodLayoutType = SvodLayoutType::XSF;
|
|
}
|
|
else
|
|
{
|
|
svodLayoutType = SvodLayoutType::Unknown;
|
|
}
|
|
}
|
|
else if (SingleFileMagicOffset < firstMappedFile.size() && std::memcmp(&firstMappedFileData[SingleFileMagicOffset], RefMagic, strlen(RefMagic)) == 0)
|
|
{
|
|
svodBaseOffset = 0xB000;
|
|
svodMagicOffset = 0xD000;
|
|
svodLayoutType = SvodLayoutType::SingleFile;
|
|
}
|
|
else {
|
|
mappedFiles.clear();
|
|
return;
|
|
}
|
|
|
|
svodStartDataBlock = parseUint24(metadata.svodDeviceDescriptor.startDataBlockRaw);
|
|
|
|
struct IterationStep
|
|
{
|
|
std::string fileNameBase;
|
|
uint32_t blockIndex = 0;
|
|
uint32_t ordinalIndex = 0;
|
|
|
|
IterationStep() = default;
|
|
IterationStep(std::string fileNameBase, uint32_t blockIndex, uint32_t ordinalIndex) : fileNameBase(fileNameBase), blockIndex(blockIndex), ordinalIndex(ordinalIndex) { }
|
|
};
|
|
|
|
std::stack<IterationStep> iterationStack;
|
|
uint32_t rootBlock = *(uint32_t *)(&firstMappedFileData[svodMagicOffset + 0x14]);
|
|
iterationStack.emplace("", rootBlock, 0);
|
|
|
|
IterationStep step;
|
|
size_t fileOffset, fileIndex;
|
|
char fileName[256];
|
|
const uint8_t FileAttributeDirectory = 0x10;
|
|
while (!iterationStack.empty())
|
|
{
|
|
step = iterationStack.top();
|
|
iterationStack.pop();
|
|
|
|
size_t ordinalOffset = step.ordinalIndex * 0x4;
|
|
size_t blockOffset = ordinalOffset / 0x800;
|
|
size_t trueOrdinalOffset = ordinalOffset % 0x800;
|
|
blockToOffsetAndFile(svodLayoutType, svodStartDataBlock, svodBaseOffset, step.blockIndex + blockOffset, fileOffset, fileIndex);
|
|
fileOffset += trueOrdinalOffset;
|
|
if (fileIndex >= mappedFiles.size())
|
|
{
|
|
mappedFiles.clear();
|
|
return;
|
|
}
|
|
|
|
const MemoryMappedFile &mappedFile = mappedFiles[fileIndex];
|
|
if ((fileOffset + sizeof(SvodDirectoryEntry)) > mappedFile.size())
|
|
{
|
|
mappedFiles.clear();
|
|
return;
|
|
}
|
|
|
|
const uint8_t *mappedFileData = mappedFile.data();
|
|
const SvodDirectoryEntry *directoryEntry = (const SvodDirectoryEntry *)(&mappedFileData[fileOffset]);
|
|
size_t nameOffset = fileOffset + sizeof(SvodDirectoryEntry);
|
|
if ((nameOffset + directoryEntry->nameLength) > mappedFile.size())
|
|
{
|
|
mappedFiles.clear();
|
|
return;
|
|
}
|
|
|
|
memcpy(fileName, &mappedFileData[nameOffset], directoryEntry->nameLength);
|
|
fileName[directoryEntry->nameLength] = '\0';
|
|
|
|
if (directoryEntry->nodeL)
|
|
{
|
|
iterationStack.emplace(step.fileNameBase, step.blockIndex, directoryEntry->nodeL);
|
|
}
|
|
|
|
if (directoryEntry->nodeR)
|
|
{
|
|
iterationStack.emplace(step.fileNameBase, step.blockIndex, directoryEntry->nodeR);
|
|
}
|
|
|
|
std::string fileNameUTF8 = step.fileNameBase + fileName;
|
|
if (directoryEntry->attributes & FileAttributeDirectory)
|
|
{
|
|
if (directoryEntry->length > 0)
|
|
{
|
|
iterationStack.emplace(fileNameUTF8 + "/", directoryEntry->dataBlock, 0);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
fileMap[fileNameUTF8] = { directoryEntry->length, directoryEntry->dataBlock, 0 };
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
mappedFiles.clear();
|
|
}
|
|
}
|
|
|
|
bool XContentFileSystem::load(const std::string &path, uint8_t *fileData, size_t fileDataMaxByteCount) const
|
|
{
|
|
auto it = fileMap.find(path);
|
|
if (it != fileMap.end())
|
|
{
|
|
if (fileDataMaxByteCount < it->second.size)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (volumeType == XContentVolumeType::STFS)
|
|
{
|
|
const MemoryMappedFile &rootMappedFile = mappedFiles.back();
|
|
const uint8_t *rootMappedFileData = rootMappedFile.data();
|
|
size_t fileDataOffset = 0;
|
|
size_t remainingSize = it->second.size;
|
|
uint32_t fileBlockIndex = it->second.blockIndex;
|
|
for (uint32_t i = 0; i < it->second.blockCount && fileBlockIndex != StfsEndOfChain; i++)
|
|
{
|
|
size_t blockSize = std::min(size_t(StfsBlockSize), remainingSize);
|
|
size_t blockOffset = blockIndexToOffset(baseOffset, fileBlockIndex);
|
|
if (blockOffset + blockSize > rootMappedFile.size())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
memcpy(&fileData[fileDataOffset], &rootMappedFileData[blockOffset], blockSize);
|
|
|
|
const StfsHashEntry *hashEntry = hashEntryFromBlockIndex(rootMappedFileData, baseOffset, fileBlockIndex);
|
|
fileBlockIndex = hashEntry->infoRaw & 0xFFFFFF;
|
|
fileDataOffset += blockSize;
|
|
remainingSize -= blockSize;
|
|
}
|
|
|
|
return remainingSize == 0;
|
|
}
|
|
else if (volumeType == XContentVolumeType::SVOD)
|
|
{
|
|
size_t fileDataOffset = 0;
|
|
size_t remainingSize = it->second.size;
|
|
size_t currentBlock = it->second.blockIndex;
|
|
while (remainingSize > 0)
|
|
{
|
|
size_t blockFileOffset, blockFileIndex;
|
|
blockToOffsetAndFile(svodLayoutType, svodStartDataBlock, svodBaseOffset, currentBlock, blockFileOffset, blockFileIndex);
|
|
if (blockFileIndex >= mappedFiles.size())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const MemoryMappedFile &mappedFile = mappedFiles[blockFileIndex];
|
|
const uint8_t *mappedFileData = mappedFile.data();
|
|
size_t blockSize = std::min(size_t(0x800), remainingSize);
|
|
if (blockFileOffset + blockSize > mappedFile.size())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
memcpy(&fileData[fileDataOffset], &mappedFileData[blockFileOffset], blockSize);
|
|
|
|
fileDataOffset += blockSize;
|
|
remainingSize -= blockSize;
|
|
currentBlock++;
|
|
}
|
|
|
|
return remainingSize == 0;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
size_t XContentFileSystem::getSize(const std::string &path) const
|
|
{
|
|
auto it = fileMap.find(path);
|
|
if (it != fileMap.end())
|
|
{
|
|
return it->second.size;
|
|
}
|
|
else
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
bool XContentFileSystem::exists(const std::string &path) const
|
|
{
|
|
return fileMap.find(path) != fileMap.end();
|
|
}
|
|
|
|
const std::string &XContentFileSystem::getName() const
|
|
{
|
|
return name;
|
|
}
|
|
|
|
bool XContentFileSystem::empty() const
|
|
{
|
|
return mappedFiles.empty();
|
|
}
|
|
|
|
std::unique_ptr<XContentFileSystem> XContentFileSystem::create(const std::filesystem::path &contentPath)
|
|
{
|
|
std::unique_ptr<XContentFileSystem> xContentFS = std::make_unique<XContentFileSystem>(contentPath);
|
|
if (!xContentFS->empty())
|
|
{
|
|
return xContentFS;
|
|
}
|
|
else
|
|
{
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
bool XContentFileSystem::check(const std::filesystem::path &contentPath)
|
|
{
|
|
std::ifstream contentStream(contentPath, std::ios::binary);
|
|
if (!contentStream.is_open())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
uint32_t packageTypeUint = 0;
|
|
contentStream.read((char *)(&packageTypeUint), sizeof(uint32_t));
|
|
packageTypeUint = ByteSwap(packageTypeUint);
|
|
XContentPackageType packageType = XContentPackageType(packageTypeUint);
|
|
return packageType == XContentPackageType::CON || packageType == XContentPackageType::LIVE || packageType == XContentPackageType::PIRS;
|
|
}
|