diff --git a/src/Common/CUniqueID.h b/src/Common/CUniqueID.h index 8f5e6d66..72f26928 100644 --- a/src/Common/CUniqueID.h +++ b/src/Common/CUniqueID.h @@ -24,7 +24,7 @@ public: CUniqueID(u64 ID, EUIDLength Length); CUniqueID(u64 Part1, u64 Part2); CUniqueID(const char* pkID); - CUniqueID(IInputStream& Input, EUIDLength Length); + CUniqueID(IInputStream& rInput, EUIDLength Length); u32 ToLong() const; u64 ToLongLong() const; TString ToString() const; diff --git a/src/Common/TString.h b/src/Common/TString.h index 5b5aea0c..029ec0cf 100644 --- a/src/Common/TString.h +++ b/src/Common/TString.h @@ -29,6 +29,9 @@ * be encoded in UTF-16. */ +// Helper macro for creating string literals of the correct char type. Internal use only! Invalid outside of this header! +#define LITERAL(Text) (typeid(CharType) == typeid(char)) ? (const CharType*) ##Text : (const CharType*) L##Text + // ************ TBasicString ************ template class TBasicString @@ -86,7 +89,7 @@ public: inline CharType At(u32 Pos) const { -#if _DEBUG +#ifdef _DEBUG if (Size() <= Pos) throw std::out_of_range("Invalid position passed to TBasicString::At()"); #endif @@ -113,9 +116,9 @@ public: return Size(); } - inline u32 IndexOf(const CharType* pkCharacters) const + inline u32 IndexOf(const CharType* pkCharacters, u32 Offset) const { - size_t Pos = mInternalString.find_first_of(pkCharacters); + size_t Pos = mInternalString.find_first_of(pkCharacters, Offset); if (Pos == _TStdString::npos) return -1; @@ -123,6 +126,11 @@ public: return (u32) Pos; } + inline u32 IndexOf(const CharType* pkCharacters) const + { + return IndexOf(pkCharacters, 0); + } + inline u32 LastIndexOf(const CharType* pkCharacters) const { size_t Pos = mInternalString.find_last_of(pkCharacters); @@ -133,6 +141,60 @@ public: return (u32) Pos; } + u32 IndexOfPhrase(_TString Str, u32 Offset, bool CaseSensitive = true) const + { + if (Size() < Str.Size()) return -1; + + // Apply case sensitivity + _TString CheckStr(CaseSensitive ? *this : ToUpper()); + if (!CaseSensitive) Str = Str.ToUpper(); + + // Now loop from the offset provided by the user. + u32 Pos = Offset; + u32 LatestPossibleStart = Size() - Str.Size(); + u32 MatchStart = -1; + u32 Matched = 0; + + while (Pos < Size()) + { + // If this character matches, increment Matched! + if (CheckStr[Pos] == Str[Matched]) + { + Matched++; + + if (MatchStart == -1) + MatchStart = Pos; + + // If we matched the entire string, we can return. + if (Matched == Str.Size()) + return MatchStart; + } + + else + { + // If we didn't match, clear our existing match check. + if (Matched > 0) + { + Pos = MatchStart; + Matched = 0; + MatchStart = -1; + } + + // Check if we're too far in to find another match. + if (Pos > LatestPossibleStart) break; + } + + Pos++; + } + + return -1; + } + + inline u32 IndexOfPhrase(_TString Str, bool CaseSensitive = true) const + { + return IndexOfPhrase(Str, 0, CaseSensitive); + } + // Modify String inline _TString SubString(int StartPos, int Length) const { @@ -142,8 +204,8 @@ public: inline void Insert(u32 Pos, CharType Chr) { #ifdef _DEBUG - if (Size() < Pos) - throw std::out_of_range("Invalid pos passed to TBasicString::Insert(CharType)"); + if (Size() <= Pos) + throw std::out_of_range("Invalid position passed to TBasicString::Insert()"); #endif mInternalString.insert(Pos, 1, Chr); } @@ -151,8 +213,8 @@ public: inline void Insert(u32 Pos, const CharType* pkStr) { #ifdef _DEBUG - if (Size() < Pos) - throw std::out_of_range("Invalid pos passed to TBasicString::Insert(const CharType*)"); + if (Size() <= Pos) + throw std::out_of_range("Invalid position passed to TBasicString::Insert()"); #endif mInternalString.insert(Pos, pkStr); } @@ -162,6 +224,34 @@ public: Insert(Pos, rkStr.CString()); } + inline void Remove(u32 Pos, u32 Len) + { +#ifdef _DEBUG + if (Size() <= Pos) + throw std::out_of_range("Invalid position passed to TBasicString::Remove()"); +#endif + mInternalString.erase(Pos, Len); + } + + inline void Replace(const CharType* pkStr, const CharType *pkReplacement, bool CaseSensitive = false) + { + u32 Offset = 0; + u32 InStrLen = CStringLength(pkStr); + u32 ReplaceStrLen = CStringLength(pkReplacement); + + for (u32 Idx = IndexOfPhrase(pkStr, CaseSensitive); Idx != -1; Idx = IndexOfPhrase(pkStr, Offset, CaseSensitive)) + { + Remove(Idx, InStrLen); + Insert(Idx, pkReplacement); + Offset = Idx + ReplaceStrLen; + } + } + + inline void Replace(const _TString& rkStr, const _TString& rkReplacement, bool CaseSensitive) + { + Replace(rkStr.CString(), rkReplacement.CString(), CaseSensitive); + } + inline void Append(CharType Chr) { mInternalString.append(1, Chr); @@ -242,7 +332,7 @@ public: } // If start is still -1 then there are no non-whitespace characters in this string. Return early. - if (Start == -1) return ""; + if (Start == -1) return _TString(); for (int iChar = Size() - 1; iChar >= 0; iChar--) { @@ -263,13 +353,13 @@ public: inline _TString ChopFront(u32 Amount) const { - if (Size() <= Amount) return ""; + if (Size() <= Amount) return _TString(); return SubString(Amount, Size() - Amount); } inline _TString ChopBack(u32 Amount) const { - if (Size() <= Amount) return ""; + if (Size() <= Amount) return _TString(); return SubString(0, Size() - Amount); } @@ -385,63 +475,37 @@ public: return (Size() == 0); } - bool StartsWith(const _TString& rkStr) const + bool StartsWith(_TString Str, bool CaseSensitive = true) const { - if (Size() < rkStr.Size()) + if (Size() < Str.Size()) return false; - return (SubString(0, rkStr.Size()) == rkStr); + _TString CompStr = (CaseSensitive ? *this : ToUpper()); + if (!CaseSensitive) Str = Str.ToUpper(); + + return (CompStr.SubString(0, Str.Size()) == Str); } - bool EndsWith(const _TString& rkStr) const + bool EndsWith(_TString Str, bool CaseSensitive = true) const { - if (Size() < rkStr.Size()) + if (Size() < Str.Size()) return false; - return (SubString(Size() - rkStr.Size(), rkStr.Size()) == rkStr); + _TString CompStr = (CaseSensitive ? *this : ToUpper()); + if (!CaseSensitive) Str = Str.ToUpper(); + + return (CompStr.SubString(CompStr.Size() - Str.Size(), Str.Size()) == Str); } bool Contains(_TString Str, bool CaseSensitive = true) const { - if (Size() < Str.Size()) return false; - - _TString CheckStr(CaseSensitive ? *this : ToUpper()); - if (CaseSensitive) Str = Str.ToUpper(); - - u32 LatestPossibleStart = Size() - Str.Size(); - u32 Match = 0; - - for (u32 iChr = 0; iChr < Size() && iChr < Str.Size(); iChr++) - { - // If the current character matches, increment match - if (CheckStr.At(iChr) == Str.At(Match)) - Match++; - - // Otherwise... - else - { - // We need to also compare this character to the first - // character of the string (unless we just did that) - if (Match > 0) - iChr--; - - Match = 0; - - if (iChr > LatestPossibleStart) - break; - } - - // If we've matched the entire string, then we can return true - if (Match == Str.Size()) return true; - } - - return false; + return (IndexOfPhrase(Str, CaseSensitive) != -1); } bool IsHexString(bool RequirePrefix = false, u32 Width = -1) const { _TString Str(*this); - bool HasPrefix = Str.StartsWith("0x"); + bool HasPrefix = Str.StartsWith(LITERAL("0x")); // If we're required to match the prefix and prefix is missing, return false if (RequirePrefix && !HasPrefix) @@ -485,13 +549,13 @@ public: // Get Filename Components _TString GetFileDirectory() const { - size_t EndPath = mInternalString.find_last_of("\\/"); + size_t EndPath = mInternalString.find_last_of(LITERAL("\\/")); return SubString(0, EndPath + 1); } _TString GetFileName(bool WithExtension = true) const { - size_t EndPath = mInternalString.find_last_of("\\/") + 1; + size_t EndPath = mInternalString.find_last_of(LITERAL("\\/")) + 1; if (WithExtension) { @@ -500,20 +564,20 @@ public: else { - size_t EndName = mInternalString.find_last_of("."); + size_t EndName = mInternalString.find_last_of(LITERAL(".")); return SubString(EndPath, EndName - EndPath); } } _TString GetFileExtension() const { - size_t EndName = mInternalString.find_last_of("."); + size_t EndName = mInternalString.find_last_of(LITERAL(".")); return SubString(EndName + 1, Size() - EndName); } _TString GetFilePathWithoutExtension() const { - size_t EndName = mInternalString.find_last_of("."); + size_t EndName = mInternalString.find_last_of(LITERAL(".")); return SubString(0, EndName); } @@ -806,6 +870,8 @@ public: } }; +#undef LITERAL + // ************ TString ************ class TString : public TBasicString { diff --git a/src/Core/Core.pro b/src/Core/Core.pro index bd08594c..7f032fa6 100644 --- a/src/Core/Core.pro +++ b/src/Core/Core.pro @@ -122,7 +122,6 @@ HEADERS += \ Resource/CMaterial.h \ Resource/CMaterialPass.h \ Resource/CMaterialSet.h \ - Resource/CPakFile.h \ Resource/CResCache.h \ Resource/CResource.h \ Resource/CScan.h \ @@ -133,8 +132,6 @@ HEADERS += \ Resource/ETevEnums.h \ Resource/ETexelFormat.h \ Resource/SDependency.h \ - Resource/SNamedResource.h \ - Resource/SResInfo.h \ Resource/TResPtr.h \ Scene/CCollisionNode.h \ Scene/CLightNode.h \ @@ -193,7 +190,11 @@ HEADERS += \ Resource/Factory/CSkinLoader.h \ Render/EDepthGroup.h \ Scene/CScriptAttachNode.h \ - ScriptExtra/CSandwormExtra.h + ScriptExtra/CSandwormExtra.h \ + GameProject/CGameProject.h \ + GameProject/CResourceDatabase.h \ + GameProject/CPackage.h \ + GameProject/CGameExporter.h # Source Files SOURCES += \ @@ -233,7 +234,6 @@ SOURCES += \ Resource/CLight.cpp \ Resource/CMaterial.cpp \ Resource/CMaterialPass.cpp \ - Resource/CPakFile.cpp \ Resource/CResCache.cpp \ Resource/CResource.cpp \ Resource/CTexture.cpp \ @@ -280,4 +280,7 @@ SOURCES += \ Resource/Factory/CSkinLoader.cpp \ Resource/Model/EVertexAttribute.cpp \ Scene/CScriptAttachNode.cpp \ - ScriptExtra/CSandwormExtra.cpp + ScriptExtra/CSandwormExtra.cpp \ + GameProject/CGameProject.cpp \ + GameProject/CResourceDatabase.cpp \ + GameProject/CGameExporter.cpp diff --git a/src/Core/GameProject/CGameExporter.cpp b/src/Core/GameProject/CGameExporter.cpp new file mode 100644 index 00000000..678ec5eb --- /dev/null +++ b/src/Core/GameProject/CGameExporter.cpp @@ -0,0 +1,336 @@ +#include "CGameExporter.h" +#include +#include +#include +#include + +#define COPY_DISC_DATA 1 +#define LOAD_PAKS 1 +#define EXPORT_COOKED 1 + +CGameExporter::CGameExporter(const TString& rkInputDir, const TString& rkOutputDir) + : mGameDir( FileUtil::MakeAbsolute(rkInputDir) ) + , mExportDir( FileUtil::MakeAbsolute(rkOutputDir) ) + , mDiscDir(mExportDir + L"Disc\\") + , mCookedResDir(mExportDir + L"Cooked\\Resources\\") + , mCookedWorldsDir(mExportDir + L"Cooked\\Worlds\\") + , mRawResDir(mExportDir + L"Raw\\Resources\\") + , mRawWorldsDir(mExportDir + L"Raw\\Worlds\\") + , mpProject(new CGameProject) +{ +} + +bool CGameExporter::Export() +{ + FileUtil::CreateDirectory(mExportDir); + CopyDiscData(); + LoadPaks(); + ExportCookedResources(); + return true; +} + +// ************ PROTECTED ************ +void CGameExporter::CopyDiscData() +{ +#if COPY_DISC_DATA + // Create Disc output folder + FileUtil::CreateDirectory(mDiscDir); +#endif + + // Copy data + TWideStringList DiscFiles; + FileUtil::GetDirectoryContents(mGameDir, DiscFiles); + + for (auto It = DiscFiles.begin(); It != DiscFiles.end(); It++) + { + TWideString FullPath = *It; + TWideString RelPath = FullPath.ChopFront(mGameDir.Size()); + + // Exclude PakTool files and folders + if (FullPath.GetFileName(false) == L"PakTool" || FullPath.GetFileName() == L"zlib1" || RelPath.Contains(L"-pak")) + continue; + + // Hack to determine game + if (Game() == eUnknownVersion) + { + TWideString Name = FullPath.GetFileName(false); + if (Name == L"MetroidCWP") SetGame(ePrimeDemo); + else if (Name == L"NESemu") SetGame(ePrime); + else if (Name == L"PirateGun") SetGame(eEchoesDemo); + else if (Name == L"AtomicBeta") SetGame(eEchoes); + else if (Name == L"InGameAudio") SetGame(eCorruptionProto); + else if (Name == L"GuiDVD") SetGame(eCorruption); + else if (Name == L"PreloadData") SetGame(eReturns); + } + + // Detect paks + if (FullPath.GetFileExtension() == L"pak") + { + if (FullPath.GetFileName(false).StartsWith(L"Metroid", false) || RelPath.Contains(L"Worlds", false)) + mWorldPaks.push_back(FullPath); + else + mResourcePaks.push_back(FullPath); + } + +#if COPY_DISC_DATA + // Create directory + TWideString OutFile = mDiscDir + RelPath; + FileUtil::CreateDirectory(OutFile.GetFileDirectory()); + + // Copy file + if (FileUtil::IsFile(FullPath)) + FileUtil::CopyFile(FullPath, OutFile); +#endif + } + + ASSERT(Game() != eUnknownVersion); +} + +// ************ RESOURCE LOADING ************ +void CGameExporter::LoadPaks() +{ +#if LOAD_PAKS + for (u32 iList = 0; iList < 2; iList++) + { + const TWideStringList& rkList = (iList == 0 ? mWorldPaks : mResourcePaks); + bool IsWorldPak = (iList == 0); + EUIDLength IDLength = (Game() < eCorruptionProto ? e32Bit : e64Bit); + + for (auto It = rkList.begin(); It != rkList.end(); It++) + { + TWideString PakPath = *It; + TString CharPak = PakPath.ToUTF8(); + CFileInStream Pak(CharPak.ToStdString(), IOUtil::eBigEndian); + + if (!Pak.IsValid()) + { + Log::Error("Couldn't open pak: " + CharPak); + continue; + } + + CPackage *pPackage = new CPackage(CharPak.GetFileName(false)); + + // MP1-MP3Proto + if (Game() < eCorruption) + { + u32 PakVersion = Pak.ReadLong(); + Pak.Seek(0x4, SEEK_CUR); + ASSERT(PakVersion == 0x00030005); + + u32 NumNamedResources = Pak.ReadLong(); + ASSERT(NumNamedResources > 0); + + for (u32 iName = 0; iName < NumNamedResources; iName++) + { + Pak.Seek(0x4, SEEK_CUR); // Skip resource type + CUniqueID ResID(Pak, IDLength); + u32 NameLen = Pak.ReadLong(); + TString Name = Pak.ReadString(NameLen); + pPackage->AddNamedResource(Name, ResID); + } + + u32 NumResources = Pak.ReadLong(); + + for (u32 iRes = 0; iRes < NumResources; iRes++) + { + bool Compressed = (Pak.ReadLong() == 1); + CFourCC ResType = Pak.ReadLong(); + CUniqueID ResID(Pak, IDLength); + u32 ResSize = Pak.ReadLong(); + u32 ResOffset = Pak.ReadLong(); + + u64 IntegralID = ResID.ToLongLong(); + if (mResourceMap.find(IntegralID) == mResourceMap.end()) + mResourceMap[IntegralID] = SResourceInstance { PakPath, ResID, ResType, ResOffset, ResSize, Compressed }; + } + } + + // MP3 + DKCR + else + { + u32 PakVersion = Pak.ReadLong(); + u32 PakHeaderLen = Pak.ReadLong(); + Pak.Seek(PakHeaderLen - 0x8, SEEK_CUR); + ASSERT(PakVersion == 2); + + struct SPakSection { + CFourCC Type; u32 Size; + }; + std::vector PakSections; + + u32 NumPakSections = Pak.ReadLong(); + ASSERT(NumPakSections == 3); + + for (u32 iSec = 0; iSec < NumPakSections; iSec++) + { + CFourCC Type = Pak.ReadLong(); + u32 Size = Pak.ReadLong(); + PakSections.push_back(SPakSection { Type, Size }); + } + Pak.SeekToBoundary(64); + + for (u32 iSec = 0; iSec < NumPakSections; iSec++) + { + u32 Next = Pak.Tell() + PakSections[iSec].Size; + + // Named Resources + if (PakSections[iSec].Type == "STRG") + { + u32 NumNamedResources = Pak.ReadLong(); + + for (u32 iName = 0; iName < NumNamedResources; iName++) + { + TString Name = Pak.ReadString(); + Pak.Seek(0x4, SEEK_CUR); // Skip type + CUniqueID ResID(Pak, IDLength); + pPackage->AddNamedResource(Name, ResID); + } + } + + else if (PakSections[iSec].Type == "RSHD") + { + ASSERT(PakSections[iSec + 1].Type == "DATA"); + u32 DataStart = Next; + u32 NumResources = Pak.ReadLong(); + + for (u32 iRes = 0; iRes < NumResources; iRes++) + { + bool Compressed = (Pak.ReadLong() == 1); + CFourCC Type = Pak.ReadLong(); + CUniqueID ResID(Pak, IDLength); + u32 Size = Pak.ReadLong(); + u32 Offset = DataStart + Pak.ReadLong(); + + u64 IntegralID = ResID.ToLongLong(); + if (mResourceMap.find(IntegralID) == mResourceMap.end()) + mResourceMap[IntegralID] = SResourceInstance { PakPath, ResID, Type, Offset, Size, Compressed }; + } + } + + Pak.Seek(Next, SEEK_SET); + } + } + + // Add package to project + mpProject->AddPackage(pPackage, IsWorldPak); + } + } +#endif +} + +void CGameExporter::LoadPakResource(const SResourceInstance& rkResource, std::vector& rBuffer) +{ + CFileInStream Pak(rkResource.PakFile.ToUTF8().ToStdString(), IOUtil::eBigEndian); + + if (Pak.IsValid()) + { + Pak.Seek(rkResource.PakOffset, SEEK_SET); + + // Handle compression + if (rkResource.Compressed) + { + bool ZlibCompressed = (Game() <= eEchoesDemo || Game() == eReturns); + + if (Game() <= eCorruptionProto) + { + std::vector CompressedData(rkResource.PakSize); + + u32 UncompressedSize = Pak.ReadLong(); + rBuffer.resize(UncompressedSize); + Pak.ReadBytes(CompressedData.data(), CompressedData.size()); + + if (ZlibCompressed) + { + u32 TotalOut; + CompressionUtil::DecompressZlib(CompressedData.data(), CompressedData.size(), rBuffer.data(), rBuffer.size(), TotalOut); + } + else + { + CompressionUtil::DecompressSegmentedData(CompressedData.data(), CompressedData.size(), rBuffer.data(), rBuffer.size()); + } + } + + else + { + CFourCC Magic = Pak.ReadLong(); + ASSERT(Magic == "CMPD"); + + u32 NumBlocks = Pak.ReadLong(); + + struct SCompressedBlock { + u32 CompressedSize; u32 UncompressedSize; + }; + std::vector CompressedBlocks; + + u32 TotalUncompressedSize = 0; + for (u32 iBlock = 0; iBlock < NumBlocks; iBlock++) + { + u32 CompressedSize = (Pak.ReadLong() & 0x00FFFFFF); + u32 UncompressedSize = Pak.ReadLong(); + + TotalUncompressedSize += UncompressedSize; + CompressedBlocks.push_back( SCompressedBlock { CompressedSize, UncompressedSize } ); + } + + rBuffer.resize(TotalUncompressedSize); + u32 Offset = 0; + + for (u32 iBlock = 0; iBlock < NumBlocks; iBlock++) + { + u32 CompressedSize = CompressedBlocks[iBlock].CompressedSize; + u32 UncompressedSize = CompressedBlocks[iBlock].UncompressedSize; + + // Block is compressed + if (CompressedSize != UncompressedSize) + { + std::vector CompressedData(CompressedBlocks[iBlock].CompressedSize); + Pak.ReadBytes(CompressedData.data(), CompressedData.size()); + + if (ZlibCompressed) + { + u32 TotalOut; + CompressionUtil::DecompressZlib(CompressedData.data(), CompressedData.size(), rBuffer.data() + Offset, UncompressedSize, TotalOut); + } + else + { + CompressionUtil::DecompressSegmentedData(CompressedData.data(), CompressedData.size(), rBuffer.data() + Offset, UncompressedSize); + } + } + // Block is uncompressed + else + Pak.ReadBytes(rBuffer.data() + Offset, UncompressedSize); + + Offset += UncompressedSize; + } + } + } + + // Handle uncompressed + else + { + rBuffer.resize(rkResource.PakSize); + Pak.ReadBytes(rBuffer.data(), rBuffer.size()); + } + } +} + +void CGameExporter::ExportCookedResources() +{ +#if EXPORT_COOKED + FileUtil::CreateDirectory(mCookedResDir); + + for (auto It = mResourceMap.begin(); It != mResourceMap.end(); It++) + { + const SResourceInstance& rkRes = It->second; + std::vector ResourceData; + LoadPakResource(rkRes, ResourceData); + + TString OutName = rkRes.ResourceID.ToString() + "." + rkRes.ResourceType.ToString(); + TString OutPath = mCookedResDir.ToUTF8() + "/" + OutName; + CFileOutStream Out(OutPath.ToStdString(), IOUtil::eBigEndian); + + if (Out.IsValid()) + Out.WriteBytes(ResourceData.data(), ResourceData.size()); + } +#endif +} diff --git a/src/Core/GameProject/CGameExporter.h b/src/Core/GameProject/CGameExporter.h new file mode 100644 index 00000000..2380de2a --- /dev/null +++ b/src/Core/GameProject/CGameExporter.h @@ -0,0 +1,54 @@ +#ifndef CGAMEEXPORTER_H +#define CGAMEEXPORTER_H + +#include "CGameProject.h" +#include +#include +#include +#include +#include + +class CGameExporter +{ + // Project + CGameProject *mpProject; + + // Directories + TWideString mGameDir; + TWideString mExportDir; + TWideString mDiscDir; + TWideString mCookedResDir; + TWideString mCookedWorldsDir; + TWideString mRawResDir; + TWideString mRawWorldsDir; + + // Resources + TWideStringList mWorldPaks; + TWideStringList mResourcePaks; + + struct SResourceInstance + { + TWideString PakFile; + CUniqueID ResourceID; + CFourCC ResourceType; + u32 PakOffset; + u32 PakSize; + bool Compressed; + }; + std::map mResourceMap; + +public: + CGameExporter(const TString& rkInputDir, const TString& rkOutputDir); + bool Export(); + +protected: + void CopyDiscData(); + void LoadPaks(); + void LoadPakResource(const SResourceInstance& rkResource, std::vector& rBuffer); + void ExportCookedResources(); + + inline EGame Game() const { return mpProject->Game(); } + inline void SetGame(EGame Game) { mpProject->SetGame(Game); } +}; + +#endif // CGAMEEXPORTER_H diff --git a/src/Core/GameProject/CGameProject.cpp b/src/Core/GameProject/CGameProject.cpp new file mode 100644 index 00000000..6b183e9a --- /dev/null +++ b/src/Core/GameProject/CGameProject.cpp @@ -0,0 +1,10 @@ +#include "CGameProject.h" + +void CGameProject::AddPackage(CPackage *pPackage, bool WorldPak) +{ + if (WorldPak) + mWorldPaks.push_back(pPackage); + else + mResourcePaks.push_back(pPackage); +} + diff --git a/src/Core/GameProject/CGameProject.h b/src/Core/GameProject/CGameProject.h new file mode 100644 index 00000000..6a7143ec --- /dev/null +++ b/src/Core/GameProject/CGameProject.h @@ -0,0 +1,38 @@ +#ifndef CGAMEPROJECT_H +#define CGAMEPROJECT_H + +#include "CPackage.h" +#include "CResourceDatabase.h" +#include "Core/Resource/EGame.h" +#include +#include +#include + +class CGameProject +{ + EGame mGame; + TString mProjectName; + TString mProjectRoot; + CResourceDatabase *mpResourceDatabase; + std::vector mWorldPaks; + std::vector mResourcePaks; + +public: + CGameProject() + : mGame(eUnknownVersion) + , mProjectName("UnnamedProject") + , mpResourceDatabase(new CResourceDatabase) + {} + + void AddPackage(CPackage *pPackage, bool WorldPak); + + inline void SetGame(EGame Game) { mGame = Game; } + inline void SetProjectName(const TString& rkName) { mProjectName = rkName; } + + inline EGame Game() const { return mGame; } + inline CResourceDatabase* ResourceDatabase() const { return mpResourceDatabase; } +}; + +extern CGameProject *gpProject; + +#endif // CGAMEPROJECT_H diff --git a/src/Core/GameProject/CPackage.h b/src/Core/GameProject/CPackage.h new file mode 100644 index 00000000..cef6f244 --- /dev/null +++ b/src/Core/GameProject/CPackage.h @@ -0,0 +1,34 @@ +#ifndef CPACKAGE +#define CPACKAGE + +#include +#include +#include + +struct SNamedResource +{ + TString Name; + CUniqueID ID; +}; + +class CPackage +{ + TString mPakName; + std::vector mNamedResources; + std::vector mPakResources; + +public: + CPackage() {} + CPackage(const TString& rkName) : mPakName(rkName) {} + + inline TString PakName() const { return mPakName; } + inline u32 NumNamedResources() const { return mNamedResources.size(); } + inline const SNamedResource& NamedResourceByIndex(u32 Index) const { return mNamedResources[Index]; } + + inline void SetPakName(TString NewName) { mPakName = NewName; } + inline void AddNamedResource(TString Name, const CUniqueID& rkID) { mNamedResources.push_back( SNamedResource { Name, rkID } ); } + inline void AddPakResource(const CUniqueID& rkID) { mPakResources.push_back(rkID); } +}; + +#endif // CPACKAGE + diff --git a/src/Core/GameProject/CResourceDatabase.cpp b/src/Core/GameProject/CResourceDatabase.cpp new file mode 100644 index 00000000..5274a4e1 --- /dev/null +++ b/src/Core/GameProject/CResourceDatabase.cpp @@ -0,0 +1 @@ +#include "CResourceDatabase.h" diff --git a/src/Core/GameProject/CResourceDatabase.h b/src/Core/GameProject/CResourceDatabase.h new file mode 100644 index 00000000..78fcdf4e --- /dev/null +++ b/src/Core/GameProject/CResourceDatabase.h @@ -0,0 +1,29 @@ +#ifndef CRESOURCEDATABASE_H +#define CRESOURCEDATABASE_H + +#include +#include +#include + +class CResourceEntry +{ + CUniqueID ID; + TString DataPath; + +public: +}; + +class CResourceDatabase +{ + struct SResEntry + { + CUniqueID ID; + TString DataPath; + }; + +public: + CResourceDatabase() {} + ~CResourceDatabase() {} +}; + +#endif // CRESOURCEDATABASE_H diff --git a/src/Core/Resource/CPakFile.cpp b/src/Core/Resource/CPakFile.cpp deleted file mode 100644 index ae2e285a..00000000 --- a/src/Core/Resource/CPakFile.cpp +++ /dev/null @@ -1,180 +0,0 @@ -#include "CPakFile.h" -#include -#include -#include -#include - -#include -#include -#include -#include - -CPakFile::CPakFile() - : mpPak(nullptr) -{ -} - -CPakFile::CPakFile(IInputStream* pPakFile) -{ - mpPak = pPakFile; - if (!mpPak->IsValid()) return; - - mVersion = mpPak->ReadLong(); - mpPak->Seek(0x4, SEEK_CUR); - - u32 NamedResCount = mpPak->ReadLong(); - mNamedResTable.resize(NamedResCount); - - for (u32 iName = 0; iName < NamedResCount; iName++) - { - SNamedResource *pRes = &mNamedResTable[iName]; - pRes->Type = CFourCC(*mpPak); - pRes->ID = (u64) mpPak->ReadLong(); - u32 resNameLength = mpPak->ReadLong(); - pRes->Name = mpPak->ReadString(resNameLength); - } - - u32 ResCount = mpPak->ReadLong(); - mResInfoTable.resize(ResCount); - - for (u32 iRes = 0; iRes < ResCount; iRes++) - { - SResInfo *pRes = &mResInfoTable[iRes]; - pRes->Compressed = (mpPak->ReadLong() != 0); - pRes->Type = CFourCC(*mpPak); - pRes->ID = (u64) mpPak->ReadLong(); - pRes->Size = mpPak->ReadLong(); - pRes->Offset = mpPak->ReadLong(); - } -} - -CPakFile::~CPakFile() -{ - if (mpPak) delete mpPak; -} - -std::vector CPakFile::NamedResources() -{ - return mNamedResTable; -} - -SResInfo CPakFile::ResourceInfo(u64 AssetID, CFourCC AssetType) -{ - // TODO: figure out how the game finds assets in paks, implement similar system to speed things up - if (mResInfoTable.empty()) - return SResInfo(); - - for (u32 iRes = 0; iRes < mResInfoTable.size(); iRes++) - { - if (((u64) (mResInfoTable[iRes].ID & 0xFFFFFFFF) == (u64) (AssetID & 0xFFFFFFFF)) && (mResInfoTable[iRes].Type == AssetType)) - return mResInfoTable[iRes]; - } - - return SResInfo(); -} - -std::vector* CPakFile::Resource(u64 AssetID, CFourCC AssetType) -{ - SResInfo Info = ResourceInfo(AssetID, AssetType); - - // make sure SResInfo is valid - if ((u64) (Info.ID & 0xFFFFFFFF) != (u64) (AssetID & 0xFFFFFFFF)) return nullptr; - else return Resource(Info); -} - -std::vector* CPakFile::Resource(SResInfo& rInfo) -{ - mpPak->Seek(rInfo.Offset, SEEK_SET); - std::vector *pResBuf = new std::vector; - - if (rInfo.Compressed) - { - u32 DecmpSize = mpPak->ReadLong(); - pResBuf->resize(DecmpSize); - - std::vector CmpBuf(rInfo.Size - 4); - mpPak->ReadBytes(&CmpBuf[0], rInfo.Size - 4); - - bool Success = Decompress(CmpBuf.data(), CmpBuf.size(), pResBuf->data(), pResBuf->size()); - - if (!Success) - { - delete pResBuf; - return nullptr; - } - } - - else - { - pResBuf->resize(rInfo.Size); - mpPak->ReadBytes(pResBuf->data(), rInfo.Size); - } - - return pResBuf; -} - -bool CPakFile::Decompress(u8 *pSrc, u32 SrcLen, u8 *pDst, u32 DstLen) -{ - if ((pSrc[0] == 0x78) && (pSrc[1] == 0xda)) - { - // zlib - z_stream z; - z.zalloc = Z_NULL; - z.zfree = Z_NULL; - z.opaque = Z_NULL; - z.avail_in = SrcLen; - z.next_in = pSrc; - z.avail_out = DstLen; - z.next_out = pDst; - - s32 Ret = inflateInit(&z); - - if (Ret == Z_OK) - { - Ret = inflate(&z, Z_NO_FLUSH); - - if ((Ret == Z_OK) || (Ret == Z_STREAM_END)) - Ret = inflateEnd(&z); - } - - if ((Ret != Z_OK) && (Ret != Z_STREAM_END)) { - Log::Error("zlib error: " + TString::FromInt32(Ret, 0, 10)); - return false; - } - - else return true; - } - - else - { - // LZO - lzo_uint Decmp; - s32 Ret; - u8 *pSrcEnd = pSrc + SrcLen; - u8 *pDstEnd = pDst + DstLen; - lzo_init(); - - while ((pSrc < pSrcEnd) && (pDst < pDstEnd)) - { - short BlockSize; - memcpy(&BlockSize, pSrc, 2); - if (IOUtil::kSystemEndianness == IOUtil::eLittleEndian) IOUtil::SwapBytes(BlockSize); - pSrc += 2; - - Ret = lzo1x_decompress(pSrc, BlockSize, pDst, &Decmp, LZO1X_MEM_DECOMPRESS); - if (Ret != LZO_E_OK) break; - pSrc += BlockSize; - pDst += Decmp; - } - - if (Ret != LZO_E_OK) - { - Log::Error("LZO error: " + TString::FromInt32(Ret, 0, 10)); - return false; - } - - else return true; - } - - return false; -} diff --git a/src/Core/Resource/CPakFile.h b/src/Core/Resource/CPakFile.h deleted file mode 100644 index 56dd0d0e..00000000 --- a/src/Core/Resource/CPakFile.h +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef CPAKFILE_H -#define CPAKFILE_H - -#include "SNamedResource.h" -#include "SResInfo.h" -#include -#include -#include - -class CPakFile -{ -private: - u32 mVersion; - std::vector mNamedResTable; - std::vector mResInfoTable; - IInputStream* mpPak; - - bool Decompress(u8 *pSrc, u32 SrcLen, u8 *pDst, u32 DstLen); - -public: - CPakFile(); - CPakFile(IInputStream* pPakFile); - ~CPakFile(); - - std::vector NamedResources(); - SResInfo ResourceInfo(u64 AssetID, CFourCC AssetType); - std::vector* Resource(u64 AssetID, CFourCC AssetType); - std::vector* Resource(SResInfo& rInfo); -}; - -#endif // CPAKFILE_H diff --git a/src/Core/Resource/Factory/CTemplateLoader.cpp b/src/Core/Resource/Factory/CTemplateLoader.cpp index dc9fd1db..30e2ed26 100644 --- a/src/Core/Resource/Factory/CTemplateLoader.cpp +++ b/src/Core/Resource/Factory/CTemplateLoader.cpp @@ -484,15 +484,33 @@ CScriptTemplate* CTemplateLoader::LoadScriptTemplate(XMLDocument *pDoc, const TS if (!Source.IsEmpty() && !ID.IsEmpty()) { CScriptTemplate::SEditorAsset Asset; + TStringList AcceptedExtensions; if (Type == "animparams") + { Asset.AssetType = CScriptTemplate::SEditorAsset::eAnimParams; + AcceptedExtensions.push_back("ANCS"); + AcceptedExtensions.push_back("CHAR"); + } + else if (Type == "model") + { Asset.AssetType = CScriptTemplate::SEditorAsset::eModel; + AcceptedExtensions.push_back("CMDL"); + } + else if (Type == "billboard") + { Asset.AssetType = CScriptTemplate::SEditorAsset::eBillboard; + AcceptedExtensions.push_back("TXTR"); + } + else if (Type == "collision") + { Asset.AssetType = CScriptTemplate::SEditorAsset::eCollision; + AcceptedExtensions.push_back("DCLN"); + } + else { pAsset = pAsset->NextSiblingElement(); diff --git a/src/Core/Resource/SNamedResource.h b/src/Core/Resource/SNamedResource.h deleted file mode 100644 index cd2c5b3f..00000000 --- a/src/Core/Resource/SNamedResource.h +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef SNAMEDRESOURCE_H -#define SNAMEDRESOURCE_H - -#include -#include - -struct SNamedResource -{ - CFourCC Type; - TString Name; - u64 ID; -}; - -#endif // SNAMEDRESOURCE_H diff --git a/src/Core/Resource/SResInfo.h b/src/Core/Resource/SResInfo.h deleted file mode 100644 index 9ed21ba0..00000000 --- a/src/Core/Resource/SResInfo.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef SRESINFO_H -#define SRESINFO_H - -#include -#include - -struct SResInfo -{ - bool Compressed; - CFourCC Type; - u64 ID; - u32 Offset; - u32 Size; - - SResInfo() - : Compressed(false), Type("NULL"), ID(0), Offset(0), Size(0) {} -}; - -#endif // SRESINFO_H diff --git a/src/Editor/CStartWindow.cpp b/src/Editor/CStartWindow.cpp index 25c15aad..8114ff14 100644 --- a/src/Editor/CStartWindow.cpp +++ b/src/Editor/CStartWindow.cpp @@ -7,6 +7,7 @@ #include "Editor/ModelEditor/CModelEditorWindow.h" #include "Editor/WorldEditor/CWorldEditor.h" +#include #include #include @@ -27,6 +28,7 @@ CStartWindow::CStartWindow(QWidget *parent) connect(ui->ActionAbout, SIGNAL(triggered()), this, SLOT(About())); connect(ui->ActionCharacterEditor, SIGNAL(triggered()), this, SLOT(LaunchCharacterEditor())); + connect(ui->ActionExportGame, SIGNAL(triggered()), this, SLOT(ExportGame())); } CStartWindow::~CStartWindow() @@ -252,3 +254,23 @@ void CStartWindow::About() CAboutDialog Dialog(this); Dialog.exec(); } + +void CStartWindow::ExportGame() +{ + // TEMP - hardcoded names for convenience. will remove later! +#define USE_HARDCODED_NAMES 1 + +#if USE_HARDCODED_NAMES + QString GameRoot = "E:/Unpacked/DKCR Dolphin"; + QString ExportDir = "E:/Unpacked/ExportTest"; +#else + QString GameRoot = QFileDialog::getExistingDirectory(this, "Select game root directory"); + if (GameRoot.isEmpty()) return; + + QString ExportDir = QFileDialog::getExistingDirectory(this, "Select output export directory"); + if (ExportDir.isEmpty()) return; +#endif + + CGameExporter Exporter(TO_TSTRING(GameRoot), TO_TSTRING(ExportDir)); + Exporter.Export(); +} diff --git a/src/Editor/CStartWindow.h b/src/Editor/CStartWindow.h index 3d1b2e39..e05f19d4 100644 --- a/src/Editor/CStartWindow.h +++ b/src/Editor/CStartWindow.h @@ -40,6 +40,7 @@ private slots: void LaunchCharacterEditor(); void About(); + void ExportGame(); private: void FillWorldUI(); diff --git a/src/Editor/CStartWindow.ui b/src/Editor/CStartWindow.ui index 2ac7b08a..2b0dd2cf 100644 --- a/src/Editor/CStartWindow.ui +++ b/src/Editor/CStartWindow.ui @@ -223,6 +223,7 @@ + @@ -271,6 +272,11 @@ Launch character editor + + + Export Game + + diff --git a/src/Editor/Editor.pro b/src/Editor/Editor.pro index 71b8b014..e79a1921 100644 --- a/src/Editor/Editor.pro +++ b/src/Editor/Editor.pro @@ -163,8 +163,7 @@ HEADERS += \ CharacterEditor/CCharacterEditorViewport.h \ CGridRenderable.h \ CharacterEditor/CSkeletonHierarchyModel.h \ - CLineRenderable.h \ - CGameExporter.h + CLineRenderable.h # Source Files SOURCES += \ @@ -224,8 +223,7 @@ SOURCES += \ CAboutDialog.cpp \ CharacterEditor/CCharacterEditor.cpp \ CharacterEditor/CCharacterEditorViewport.cpp \ - CharacterEditor/CSkeletonHierarchyModel.cpp \ - CGameExporter.cpp + CharacterEditor/CSkeletonHierarchyModel.cpp # UI Files FORMS += \