/* Code for mounting the Dolphin SDK folder as a virtual disk. All the necessary data (BI2, Appldr, some DOL executable, we take from the SDK). If they are not there, then the disk is simply not mounted. */ #include "pch.h" using namespace Debug; namespace DVD { MountDolphinSdk::MountDolphinSdk(const wchar_t * DolphinSDKPath) { wcscpy(directory, DolphinSDKPath); // Load dvddata structure. auto dvdDataInfoText = Util::FileLoad(DvdDataJson); if (dvdDataInfoText.empty()) { Report(Channel::Norm, "Failed to load DolphinSDK dvddata json: %s\n", Util::WstringToString(DvdDataJson).c_str()); return; } try { DvdDataInfo.Deserialize(dvdDataInfoText.data(), dvdDataInfoText.size()); } catch (...) { Report(Channel::Norm, "Failed to Deserialize DolphinSDK dvddata json: %s\n", Util::WstringToString(DvdDataJson).c_str()); return; } // Generate data blobs if (!GenDiskId()) { Report(Channel::Norm, "Failed to GenDiskId\n"); return; } if (!GenApploader()) { Report(Channel::Norm, "Failed to GenApploader\n"); return; } if (!GenBi2()) { Report(Channel::Norm, "Failed to GenBi2\n"); return; } if (!GenFst()) { Report(Channel::Norm, "Failed to GenFst\n"); return; } if (!GenDol()) { Report(Channel::Norm, "Failed to GenDol\n"); return; } if (!GenBb2()) { Report(Channel::Norm, "Failed to GenBb2\n"); return; } // Generate mapping if (!GenMap()) { Report(Channel::Norm, "Failed to GenMap\n"); return; } if (!GenFileMap()) { Report(Channel::Norm, "Failed to GenFileMap\n"); return; } Report(Channel::DVD, "DolphinSDK mounted!\n"); mounted = true; } MountDolphinSdk::~MountDolphinSdk() { } void MountDolphinSdk::MapVector(std::vector& v, uint32_t offset) { std::tuple&, uint32_t, size_t> entry(v, offset, v.size()); mapping.push_back(entry); } void MountDolphinSdk::MapFile(wchar_t* path, uint32_t offset) { size_t size = Util::FileSize(path); std::tuple entry(path, offset, size); fileMapping.push_back(entry); } // Check memory mapping uint8_t* MountDolphinSdk::TranslateMemory(uint32_t offset, size_t requestedSize, size_t& maxSize) { for (auto it = mapping.begin(); it != mapping.end(); ++it) { uint8_t * ptr = std::get<0>(*it).data(); uint32_t startingOffset = std::get<1>(*it); size_t size = std::get<2>(*it); if (startingOffset <= offset && offset < (startingOffset + size)) { maxSize = my_min(requestedSize, (startingOffset + size) - offset); return ptr + (offset - startingOffset); } } return nullptr; } // Check file mapping FILE * MountDolphinSdk::TranslateFile(uint32_t offset, size_t requestedSize, size_t& maxSize) { for (auto it = fileMapping.begin(); it != fileMapping.end(); ++it) { wchar_t* file = std::get<0>(*it); uint32_t startingOffset = std::get<1>(*it); size_t size = std::get<2>(*it); if (startingOffset <= offset && offset < (startingOffset + size)) { maxSize = my_min(requestedSize, (startingOffset + size) - offset); FILE* f; f = fopen( Util::WstringToString(file).c_str(), "rb"); assert(f); fseek(f, offset - startingOffset, SEEK_SET); return f; } } return nullptr; } void MountDolphinSdk::Seek(int position) { if (!mounted) return; assert(position >= 0 && position < DVD_SIZE); currentSeek = (uint32_t)position; } bool MountDolphinSdk::Read(void* buffer, size_t length) { bool result = true; assert(buffer); if (!mounted) { memset(buffer, 0, length); return true; } if (currentSeek >= DVD_SIZE) { memset(buffer, 0, length); return false; } size_t maxLength = 0; uint8_t* ptr = TranslateMemory(currentSeek, length, maxLength); if (ptr != nullptr) { memcpy(buffer, ptr, maxLength); if (maxLength < length) { memset((uint8_t *)buffer + maxLength, 0, length - maxLength); } } else { FILE* f = TranslateFile(currentSeek, length, maxLength); if (f != nullptr) { fread(buffer, 1, maxLength, f); if (maxLength < length) { memset((uint8_t*)buffer + maxLength, 0, length - maxLength); } fclose(f); } else { memset(buffer, 0, length); result = false; } } currentSeek += (uint32_t)length; return result; } #pragma region "Data Generators" bool MountDolphinSdk::GenDiskId() { DiskId.resize(sizeof(DiskID)); DiskID* id = (DiskID*)DiskId.data(); id->gameName[0] = 'S'; id->gameName[1] = 'D'; id->gameName[2] = 'K'; id->gameName[3] = 'E'; id->company[0] = '0'; id->company[1] = '1'; id->magicNumber = _BYTESWAP_UINT32(DVD_DISKID_MAGIC); GameName.resize(0x400); memset(GameName.data(), 0, GameName.size()); strcpy((char *)GameName.data(), "GameCube SDK"); return true; } bool MountDolphinSdk::GenApploader() { auto path = fmt::format(L"{:s}{:s}", directory, AppldrPath); AppldrData = Util::FileLoad(path); return true; } bool MountDolphinSdk::GenDol() { Dol = Util::FileLoad(DolPath); return true; } bool MountDolphinSdk::GenBi2() { auto path = fmt::format(L"{:s}{:s}", directory, Bi2Path); Bi2Data = Util::FileLoad(path); return true; } void MountDolphinSdk::AddString(std::string str) { for (auto& c : str) { NameTableData.push_back(c); } NameTableData.push_back(0); } void MountDolphinSdk::ParseDvdDataEntryForFst(Json::Value* entry) { if (entry->type == Json::ValueType::Object) { // Directory // Save directory name offset size_t nameOffset = NameTableData.size(); if (entry->name) { AddString(entry->name); } entry->AddInt("nameOffset", (int)nameOffset); // Save current FST index for directory entry->AddInt("entryId", entryCounter); entryCounter++; // Reset totalChildren counter entry->AddInt("totalChildren", 0); // Update parent totalChildren counters Json::Value * parent = entry->parent; while (parent) { Json::Value* totalChildren = parent->ByName("totalChildren"); if (totalChildren) totalChildren->value.AsInt++; parent = parent->parent; // :p } } else if (entry->type == Json::ValueType::String) { bool skipMeta = false; if (entry->parent->name && entry->parent->type == Json::ValueType::Array) { if (!_stricmp(entry->parent->name, "filePaths")) { skipMeta = true; } } // File if (!skipMeta) { std::string path = Util::WstringToString(entry->value.AsString); size_t nameOffset = NameTableData.size(); AddString(path); Json::Value* parent = entry->parent; // Save file name offset Json::Value* nameOffsets = entry->parent->parent->ByName("nameOffsets"); if (nameOffsets == nullptr) { nameOffsets = entry->parent->parent->AddArray("nameOffsets"); } assert(nameOffsets); nameOffsets->AddInt(nullptr, (int)nameOffset); do { if (parent) { if (parent->type == Json::ValueType::Object) { path = (parent->name ? parent->name + std::string("/") : "/") + path; } parent = parent->parent; } } while (parent != nullptr); assert(path.size() < DVD_MAXPATH); // Save file offset and size //DBReport("Processing file: %s\n", path.c_str()); wchar_t filePath[0x1000] = { 0, }; wcscat(filePath, directory); wcscat(filePath, L"/dvddata"); wchar_t* filePathPtr = filePath + wcslen(filePath); for (size_t i = 0; i < path.size(); i++) { *filePathPtr++ = (wchar_t)path[i]; } *filePathPtr++ = 0; Json::Value* fileOffsets = entry->parent->parent->ByName("fileOffsets"); if (fileOffsets == nullptr) { fileOffsets = entry->parent->parent->AddArray("fileOffsets"); } assert(fileOffsets); fileOffsets->AddInt(nullptr, userFilesStart + userFilesOffset); size_t fileSize = Util::FileSize(filePath); Json::Value* fileSizes = entry->parent->parent->ByName("fileSizes"); if (fileSizes == nullptr) { fileSizes = entry->parent->parent->AddArray("fileSizes"); } assert(fileSizes); fileSizes->AddInt(nullptr, (int)fileSize); userFilesOffset += RoundUp32((uint32_t)fileSize); Json::Value* filePaths = entry->parent->parent->ByName("filePaths"); if (filePaths == nullptr) { filePaths = entry->parent->parent->AddArray("filePaths"); } assert(filePaths); filePaths->AddString(nullptr, filePath); // Adjust counters entryCounter++; // Update parent sibling counters parent = entry->parent; while (parent) { Json::Value* totalChildren = parent->ByName("totalChildren"); if (totalChildren) totalChildren->value.AsInt++; parent = parent->parent; // :p } } } Json::Value* lastObject = nullptr; for (auto it = entry->children.begin(); it != entry->children.end(); ++it) { Json::Value * child = *it; ParseDvdDataEntryForFst(child); if (child->type == Json::ValueType::Object) { lastObject = child; } } if (lastObject) { lastObject->AddBool("last", true); } } void MountDolphinSdk::WalkAndGenerateFst(Json::Value* entry) { DVDFileEntry fstEntry = { 0 }; if (entry->type == Json::ValueType::Object) { // Directory fstEntry.isDir = 1; Json::Value* nameOffset = entry->ByName("nameOffset"); assert(nameOffset); if (nameOffset) { fstEntry.nameOffsetHi = (uint8_t)(nameOffset->value.AsInt >> 16); fstEntry.nameOffsetLo = _BYTESWAP_UINT16((uint16_t)nameOffset->value.AsInt); } if (entry->parent) { Json::Value* parentId = entry->parent->ByName("entryId"); if (parentId) fstEntry.parentOffset = _BYTESWAP_UINT32((uint32_t)parentId->value.AsInt); } Json::Value* entryId = entry->ByName("entryId"); Json::Value* totalChildren = entry->ByName("totalChildren"); if (entryId && totalChildren) { Json::Value* last = entry->ByName("last"); fstEntry.nextOffset = _BYTESWAP_UINT32((uint32_t)(entryId->value.AsInt + totalChildren->value.AsInt) + 1); } FstData.insert(FstData.end(), (uint8_t*)&fstEntry, (uint8_t*)&fstEntry + sizeof(fstEntry)); } else if (entry->type == Json::ValueType::Array) { // Files if (!_stricmp(entry->name, "files")) { Json::Value* nameOffsets = entry->parent->ByName("nameOffsets"); Json::Value* fileOffsets = entry->parent->ByName("fileOffsets"); Json::Value* fileSizes = entry->parent->ByName("fileSizes"); assert(nameOffsets && fileOffsets && fileSizes); if (nameOffsets && fileOffsets && fileSizes) { auto nameOffsetsIt = nameOffsets->children.begin(); auto fileOffsetsIt = fileOffsets->children.begin(); auto fileSizesIt = fileSizes->children.begin(); while (nameOffsetsIt != nameOffsets->children.end()) { fstEntry.isDir = 0; uint32_t nameOffset = (uint32_t)(*nameOffsetsIt)->value.AsInt; uint32_t fileOffset = (uint32_t)(*fileOffsetsIt)->value.AsInt; uint32_t fileSize = (uint32_t)(*fileSizesIt)->value.AsInt; fstEntry.nameOffsetHi = (uint8_t)(nameOffset >> 16); fstEntry.nameOffsetLo = _BYTESWAP_UINT16((uint16_t)nameOffset); fstEntry.fileOffset = _BYTESWAP_UINT32(fileOffset); fstEntry.fileLength = _BYTESWAP_UINT32(fileSize); FstData.insert(FstData.end(), (uint8_t*)&fstEntry, (uint8_t*)&fstEntry + sizeof(fstEntry)); nameOffsetsIt++; fileOffsetsIt++; fileSizesIt++; } } } return; } for (auto it = entry->children.begin(); it != entry->children.end(); ++it) { WalkAndGenerateFst(*it); } } // The basic idea behind generating FST is to walk by DvdDataJson. // When traversing a structure a specific userData is attached to each node. // After generation, this userData is collected in a common collection (FST). bool MountDolphinSdk::GenFst() { try { ParseDvdDataEntryForFst(DvdDataInfo.root.children.back()); } catch (...) { Report(Channel::Norm, "ParseDvdDataEntryForFst failed!\n"); return false; } JDI::Hub.Dump(DvdDataInfo.root.children.back()); try { WalkAndGenerateFst(DvdDataInfo.root.children.back()); } catch (...) { Report(Channel::Norm, "WalkAndGenerateFst failed!\n"); return false; } FstData.insert(FstData.end(), NameTableData.begin(), NameTableData.end()); Util::FileSave(L"Data\\DolphinSdkFST.bin", FstData); return true; } bool MountDolphinSdk::GenBb2() { DVDBB2 bb2 = { 0 }; bb2.bootFilePosition = RoundUpSector(DVD_APPLDR_OFFSET + (uint32_t)AppldrData.size()); bb2.FSTLength = (uint32_t)FstData.size(); bb2.FSTMaxLength = bb2.FSTLength; bb2.FSTPosition = RoundUpSector(bb2.bootFilePosition + (uint32_t)Dol.size() + DVD_SECTOR_SIZE); bb2.userPosition = 0x80030000; // Ignored bb2.userLength = RoundUpSector(bb2.FSTLength); Bb2Data.resize(sizeof(DVDBB2)); memcpy(Bb2Data.data(), &bb2, sizeof(bb2)); return true; } bool MountDolphinSdk::GenMap() { MapVector(DiskId, DVD_ID_OFFSET); MapVector(GameName, sizeof(DiskID)); MapVector(Bb2Data, DVD_BB2_OFFSET); MapVector(Bi2Data, DVD_BI2_OFFSET); MapVector(AppldrData, DVD_APPLDR_OFFSET); DVDBB2* bb2 = (DVDBB2 *)Bb2Data.data(); MapVector(Dol, bb2->bootFilePosition); MapVector(FstData, bb2->FSTPosition); SwapArea(bb2, sizeof(DVDBB2)); return true; } void MountDolphinSdk::WalkAndMapFiles(Json::Value* entry) { if (entry->type == Json::ValueType::Array) { // Files if (!_stricmp(entry->name, "files")) { Json::Value* filePaths = entry->parent->ByName("filePaths"); Json::Value* fileOffsets = entry->parent->ByName("fileOffsets"); assert(filePaths && fileOffsets); if (filePaths && fileOffsets) { auto filePathsIt = filePaths->children.begin(); auto fileOffsetsIt = fileOffsets->children.begin(); while (filePathsIt != filePaths->children.end()) { MapFile((*filePathsIt)->value.AsString, (uint32_t)(*fileOffsetsIt)->value.AsInt); filePathsIt++; fileOffsetsIt++; } } } return; } for (auto it = entry->children.begin(); it != entry->children.end(); ++it) { WalkAndMapFiles(*it); } } bool MountDolphinSdk::GenFileMap() { userFilesOffset = 0; try { WalkAndMapFiles(DvdDataInfo.root.children.back()); } catch (...) { Report(Channel::Norm, "WalkAndMapFiles failed!\n"); return false; } return true; } void MountDolphinSdk::SwapArea(void* _addr, int sizeInBytes) { uint32_t* addr = (uint32_t*)_addr; uint32_t* until = addr + sizeInBytes / sizeof(uint32_t); while (addr != until) { *addr = _BYTESWAP_UINT32(*addr); addr++; } } #pragma endregion "Data Generators" }