diff --git a/OpenKh.Research.Panacea/Panacea.cpp b/OpenKh.Research.Panacea/Panacea.cpp index 8035c8786..57b2effda 100644 --- a/OpenKh.Research.Panacea/Panacea.cpp +++ b/OpenKh.Research.Panacea/Panacea.cpp @@ -419,7 +419,7 @@ void GetIMZOffsets(void* addr, int baseoff, std::vector& entries) } } -void GetTM2Offsets(void* addr, int baseoff, std::vector& entries) +void GetTM2Offsets(void* addr, int baseoff, std::vector& entries, int flags = 0x20000000) { if (*(int*)addr == '2MIT') { @@ -429,7 +429,7 @@ void GetTM2Offsets(void* addr, int baseoff, std::vector& entries) while (texcnt-- > 0) { int next = *(int*)off; - entries.push_back(*(short*)(off + 12) + baseoff + 0x20000000); + entries.push_back(*(short*)(off + 12) + baseoff + flags); off += next; baseoff += next; } @@ -722,6 +722,184 @@ void GetBAROffsets(void* addr, int baseoff, std::vector& entries) } } +void GetPMOOffsets(void* addr, int baseoff, std::vector& entries) +{ + if (*(int*)addr == '\0OMP') + { + int texCount = ((short*)addr)[4]; + char* texOffset = (char*)addr + 0xA0; + for (int i = 0; i < texCount; i++) + { + int oTm2 = *(int*)(texOffset + (0x20 * i)); + if (oTm2 != 0) + GetTM2Offsets((char*)addr + oTm2, baseoff + oTm2, entries, 0); + } + } +} + +void GetPMPOffsets(void* addr, int baseoff, std::vector& entries) +{ + if (*(int*)addr == '\0PMP') + { + int texCount = ((short*)addr)[13]; + int texOffset = ((int*)addr)[7]; + char* pTex = ((char*)addr + texOffset); + for (int i = 0; i < texCount; i++) + { + int oTm2 = *(int*)(pTex + (0x20 * i)); + if (oTm2 != 0) + GetTM2Offsets((char*)addr + oTm2, baseoff + oTm2, entries, 0); + } + } +} + +void GetL2DOffsets(void* addr, int baseoff, std::vector& entries) +{ + if (*(int*)addr == '@D2L') + { + int sq2pCount = ((int*)addr)[8]; + int oOffsets = ((int*)addr)[9]; + int* offsets = (int*)((char*)addr + oOffsets); + for (int i = 0; i < sq2pCount; i++) + { + int oSQ2P = offsets[i]; + char* pSQ2P = (char*)offsets + oSQ2P; + int oTM2 = ((int*)pSQ2P)[6]; + char* pTM2 = pSQ2P + oTM2; + GetTM2Offsets(pTM2, baseoff + oOffsets + oSQ2P + oTM2, entries, 0); + } + } +} + +void GetTXAOffsets(void* addr, int baseoff, std::vector& entries) +{ + // TODO: Recheck all the sizes and offsets here + if (*(int*)addr == '\0AXT') + { + // Animations can reuse frames. We only want each frame once. + std::set frames = std::set(); + short tex0Count = ((short*)addr)[3]; + int* pTex0 = (int*)((char*)addr + 0x10); + for (int i = 0; i < tex0Count; i++) + { + short tex1Count = ((short*)pTex0)[24]; + int* pTex1 = (int*)((char*)addr + pTex0[13]); + + for (int j = 0; j < tex1Count; j++) + { + short tex2Count = ((short*)pTex1)[9]; + int* pTex2 = (int*)((char*)addr + pTex1[5]); + + for (int k = 0; k < tex2Count; k++) + { + int pframe = *pTex2; + if (pframe != 0) + frames.emplace(baseoff + pframe); + pTex2 += 3; + } + + pTex1 += 6; + } + + pTex0 += 14; + } + entries.insert(entries.end(), frames.begin(), frames.end()); + } +} + +void GetFEPOffsets(void* addr, int baseoff, std::vector& entries) +{ + if ((*(int*)addr & 0x00FFFFFF) == '\0PEF') + { + std::set frames = std::set(); + unsigned short tex_c = ((unsigned short*)addr)[16]; + int* pTexResArr = (int*)((char*)addr + ((int*)addr)[12]); + for (int i = 0; i < tex_c; i++) + { + char* pTexRes = (char*)addr + pTexResArr[i]; + int texSize = ((int*)pTexRes)[2]; + if (texSize > 0) + { + int oTex = ((int*)pTexRes)[3]; + int* pTex = (int*)((char*)addr + oTex); + int oPixels = pTex[9]; + frames.emplace(baseoff + oTex + oPixels); + } + } + + entries.insert(entries.end(), frames.begin(), frames.end()); + } + + return; +} + +void GetARCOffsets(void* addr, int baseoff, std::vector& entries) +{ + if (*(int*)addr == '\0CRA') + { + short count = ((short*)addr)[3]; + char* pEntry = (char*)addr + 0x10; + char nameBuf[17] = { 0 }; + std::unordered_map> fileMap{}; + std::vector>> fileList{}; + for (int i = 0; i < count; i++) + { + std::vector currOffs{}; + + int offset = ((int*)pEntry)[1]; + char* pName = pEntry + 0x10; + strncpy_s(nameBuf, 17, pName, 16); + char* ext = PathFindExtensionA(nameBuf); + if (!_stricmp(ext, ".pmo")) + { + GetPMOOffsets((char*)addr + offset, baseoff + offset, currOffs); + } + else if (!_stricmp(ext, ".pmp")) + { + GetPMPOffsets((char*)addr + offset, baseoff + offset, currOffs); + } + else if (!_stricmp(ext, ".tm2")) + { + GetTM2Offsets((char*)addr + offset, baseoff + offset, currOffs, 0); + } + else if (!_stricmp(ext, ".l2d")) + { + GetL2DOffsets((char*)addr + offset, baseoff + offset, currOffs); + } + else if (!_stricmp(ext, ".txa")) + { + GetTXAOffsets((char*)addr + offset, baseoff + offset, currOffs); + } + else if (!_stricmp(ext, ".fep")) + { + GetFEPOffsets((char*)addr + offset, baseoff + offset, currOffs); + } + pEntry += 0x20; + auto name = std::string(nameBuf); + fileMap.emplace(name, currOffs); + fileList.emplace_back(name, currOffs); + } + + // TXA files immediatly preceed the file they're attatched to. Everything else shows up in order. + for (auto& pair : fileList) + { + auto path = PathFindExtensionA(pair.first.c_str()); + if (!_stricmp(path, ".txa")) continue; + if (!_stricmp(path, ".pmo") || !_stricmp(path, ".pmp")) + { + std::string name2 = pair.first.substr(0, pair.first.length() - 4) + ".txa"; + auto itr = fileMap.find(name2); + if (itr != fileMap.end()) + { + entries.insert(entries.end(), itr->second.begin(), itr->second.end()); + } + } + entries.insert(entries.end(), pair.second.begin(), pair.second.end()); + } + + } +} + void ScanFolder(const std::wstring& folder, std::vector& files) { std::queue folq; @@ -806,8 +984,42 @@ bool sortRemasteredFiles(const Axa::RemasteredEntry& a, const Axa::RemasteredEnt return false; } +bool sortRemasteredFiles_LexBBS(const Axa::RemasteredEntry& a, const Axa::RemasteredEntry& b) +{ + std::string na = std::string(a.name); + std::string nb = std::string(b.name); + return na < nb; +} + +bool sortRemasteredFiles_NumBBS(const Axa::RemasteredEntry& a, const Axa::RemasteredEntry& b) +{ + // this doesn't work, we need to start at the end and walk back until we find a digit (.dds), then until we find not a digit. + /*const char* pa = a.name; + while (*pa != '\0' && !isdigit(*pa)) pa++; + const char* pb = b.name; + while (*pb != '\0' && !isdigit(*pb)) pb++; + if (*pa == '\0' || *pb == '\0') return false; + int ia = atoi(pa); + int ib = atoi(pb); + return ia < ib;*/ + + char* pa = PathFindExtensionA(a.name) - 1; + while (isdigit(*(pa - 1)) && pa > a.name) pa--; + + char* pb = PathFindExtensionA(b.name) - 1; + while (isdigit(*(pb - 1)) && pb > b.name) pb--; + + int ia = atoi(pa); + int ib = atoi(pb); + return ia < ib; +} + void ScanRemasteredFolder(const wchar_t* path, void* addr, const wchar_t* remasteredFolder, std::vector& entries) { + // BBS wants it's files as 0, 1, 10, 11, 12, 2, 3..., so we need to sort them numerically to work with them then sort them lexically to send to the game. + if (OpenKH::m_GameID == OpenKH::GameId::KingdomHeartsBbs) + std::stable_sort(entries.begin(), entries.end(), sortRemasteredFiles_NumBBS); + std::vector assetoffs{}; const wchar_t* ext = PathFindExtensionW(path); switch (OpenKH::m_GameID) @@ -845,6 +1057,27 @@ void ScanRemasteredFolder(const wchar_t* path, void* addr, const wchar_t* remas for (int i = 0; i < entries.size(); ++i) assetoffs.push_back(entries[i].origOffset); break; + case OpenKH::GameId::KingdomHeartsBbs: + if (!_wcsicmp(ext, L".arc")) + GetARCOffsets(addr, 0, assetoffs); + else if (!_wcsicmp(ext, L".pmo")) + GetPMOOffsets(addr, 0, assetoffs); + else if (!_wcsicmp(ext, L".pmp")) + GetPMPOffsets(addr, 0, assetoffs); + else if (!_wcsicmp(ext, L".tm2")) + GetTM2Offsets(addr, 0, assetoffs, 0); + else if (!_wcsicmp(ext, L".l2d")) + GetL2DOffsets(addr, 0, assetoffs); + else if (!_wcsicmp(ext, L".txa")) + GetTXAOffsets(addr, 0, assetoffs); + else if (!_wcsicmp(ext, L".fep")) + GetFEPOffsets(addr, 0, assetoffs); + // TODO: .frr + // TODO: Font support: CLU COD INF MTX + else // unknown file type + for (int i = 0; i < entries.size(); ++i) + assetoffs.push_back(entries[i].origOffset); + break; default: for (int i = 0; i < entries.size(); ++i) assetoffs.push_back(entries[i].origOffset); @@ -854,7 +1087,10 @@ void ScanRemasteredFolder(const wchar_t* path, void* addr, const wchar_t* remas ScanFolder(remasteredFolder, modfiles); if (modfiles.size() >= assetoffs.size()) { - std::stable_sort(modfiles.begin(), modfiles.end(), sortRemasteredFiles); + if (OpenKH::m_GameID == OpenKH::GameId::KingdomHeartsBbs) + std::stable_sort(modfiles.begin(), modfiles.end(), sortRemasteredFiles_NumBBS); + else + std::stable_sort(modfiles.begin(), modfiles.end(), sortRemasteredFiles); entries = modfiles; entries.resize(assetoffs.size()); for (int i = 0; i < entries.size(); ++i) @@ -883,6 +1119,9 @@ void ScanRemasteredFolder(const wchar_t* path, void* addr, const wchar_t* remas } } } + + if (OpenKH::m_GameID == OpenKH::GameId::KingdomHeartsBbs) + std::stable_sort(entries.begin(), entries.end(), sortRemasteredFiles_LexBBS); } void GetRemasteredFiles(Axa::PackageFile* fileinfo, const wchar_t* path, void* addr)