Began initial implementation of the game exporter and game project classes
This commit is contained in:
parent
3009f06d11
commit
5f2064178c
|
@ -24,7 +24,7 @@ public:
|
||||||
CUniqueID(u64 ID, EUIDLength Length);
|
CUniqueID(u64 ID, EUIDLength Length);
|
||||||
CUniqueID(u64 Part1, u64 Part2);
|
CUniqueID(u64 Part1, u64 Part2);
|
||||||
CUniqueID(const char* pkID);
|
CUniqueID(const char* pkID);
|
||||||
CUniqueID(IInputStream& Input, EUIDLength Length);
|
CUniqueID(IInputStream& rInput, EUIDLength Length);
|
||||||
u32 ToLong() const;
|
u32 ToLong() const;
|
||||||
u64 ToLongLong() const;
|
u64 ToLongLong() const;
|
||||||
TString ToString() const;
|
TString ToString() const;
|
||||||
|
|
|
@ -29,6 +29,9 @@
|
||||||
* be encoded in UTF-16.
|
* 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 ************
|
// ************ TBasicString ************
|
||||||
template<class CharType>
|
template<class CharType>
|
||||||
class TBasicString
|
class TBasicString
|
||||||
|
@ -86,7 +89,7 @@ public:
|
||||||
|
|
||||||
inline CharType At(u32 Pos) const
|
inline CharType At(u32 Pos) const
|
||||||
{
|
{
|
||||||
#if _DEBUG
|
#ifdef _DEBUG
|
||||||
if (Size() <= Pos)
|
if (Size() <= Pos)
|
||||||
throw std::out_of_range("Invalid position passed to TBasicString::At()");
|
throw std::out_of_range("Invalid position passed to TBasicString::At()");
|
||||||
#endif
|
#endif
|
||||||
|
@ -113,9 +116,9 @@ public:
|
||||||
return Size();
|
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)
|
if (Pos == _TStdString::npos)
|
||||||
return -1;
|
return -1;
|
||||||
|
@ -123,6 +126,11 @@ public:
|
||||||
return (u32) Pos;
|
return (u32) Pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline u32 IndexOf(const CharType* pkCharacters) const
|
||||||
|
{
|
||||||
|
return IndexOf(pkCharacters, 0);
|
||||||
|
}
|
||||||
|
|
||||||
inline u32 LastIndexOf(const CharType* pkCharacters) const
|
inline u32 LastIndexOf(const CharType* pkCharacters) const
|
||||||
{
|
{
|
||||||
size_t Pos = mInternalString.find_last_of(pkCharacters);
|
size_t Pos = mInternalString.find_last_of(pkCharacters);
|
||||||
|
@ -133,6 +141,60 @@ public:
|
||||||
return (u32) Pos;
|
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
|
// Modify String
|
||||||
inline _TString SubString(int StartPos, int Length) const
|
inline _TString SubString(int StartPos, int Length) const
|
||||||
{
|
{
|
||||||
|
@ -142,8 +204,8 @@ public:
|
||||||
inline void Insert(u32 Pos, CharType Chr)
|
inline void Insert(u32 Pos, CharType Chr)
|
||||||
{
|
{
|
||||||
#ifdef _DEBUG
|
#ifdef _DEBUG
|
||||||
if (Size() < Pos)
|
if (Size() <= Pos)
|
||||||
throw std::out_of_range("Invalid pos passed to TBasicString::Insert(CharType)");
|
throw std::out_of_range("Invalid position passed to TBasicString::Insert()");
|
||||||
#endif
|
#endif
|
||||||
mInternalString.insert(Pos, 1, Chr);
|
mInternalString.insert(Pos, 1, Chr);
|
||||||
}
|
}
|
||||||
|
@ -151,8 +213,8 @@ public:
|
||||||
inline void Insert(u32 Pos, const CharType* pkStr)
|
inline void Insert(u32 Pos, const CharType* pkStr)
|
||||||
{
|
{
|
||||||
#ifdef _DEBUG
|
#ifdef _DEBUG
|
||||||
if (Size() < Pos)
|
if (Size() <= Pos)
|
||||||
throw std::out_of_range("Invalid pos passed to TBasicString::Insert(const CharType*)");
|
throw std::out_of_range("Invalid position passed to TBasicString::Insert()");
|
||||||
#endif
|
#endif
|
||||||
mInternalString.insert(Pos, pkStr);
|
mInternalString.insert(Pos, pkStr);
|
||||||
}
|
}
|
||||||
|
@ -162,6 +224,34 @@ public:
|
||||||
Insert(Pos, rkStr.CString());
|
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)
|
inline void Append(CharType Chr)
|
||||||
{
|
{
|
||||||
mInternalString.append(1, 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 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--)
|
for (int iChar = Size() - 1; iChar >= 0; iChar--)
|
||||||
{
|
{
|
||||||
|
@ -263,13 +353,13 @@ public:
|
||||||
|
|
||||||
inline _TString ChopFront(u32 Amount) const
|
inline _TString ChopFront(u32 Amount) const
|
||||||
{
|
{
|
||||||
if (Size() <= Amount) return "";
|
if (Size() <= Amount) return _TString();
|
||||||
return SubString(Amount, Size() - Amount);
|
return SubString(Amount, Size() - Amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline _TString ChopBack(u32 Amount) const
|
inline _TString ChopBack(u32 Amount) const
|
||||||
{
|
{
|
||||||
if (Size() <= Amount) return "";
|
if (Size() <= Amount) return _TString();
|
||||||
return SubString(0, Size() - Amount);
|
return SubString(0, Size() - Amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -385,63 +475,37 @@ public:
|
||||||
return (Size() == 0);
|
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 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 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
|
bool Contains(_TString Str, bool CaseSensitive = true) const
|
||||||
{
|
{
|
||||||
if (Size() < Str.Size()) return false;
|
return (IndexOfPhrase(Str, CaseSensitive) != -1);
|
||||||
|
|
||||||
_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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IsHexString(bool RequirePrefix = false, u32 Width = -1) const
|
bool IsHexString(bool RequirePrefix = false, u32 Width = -1) const
|
||||||
{
|
{
|
||||||
_TString Str(*this);
|
_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 we're required to match the prefix and prefix is missing, return false
|
||||||
if (RequirePrefix && !HasPrefix)
|
if (RequirePrefix && !HasPrefix)
|
||||||
|
@ -485,13 +549,13 @@ public:
|
||||||
// Get Filename Components
|
// Get Filename Components
|
||||||
_TString GetFileDirectory() const
|
_TString GetFileDirectory() const
|
||||||
{
|
{
|
||||||
size_t EndPath = mInternalString.find_last_of("\\/");
|
size_t EndPath = mInternalString.find_last_of(LITERAL("\\/"));
|
||||||
return SubString(0, EndPath + 1);
|
return SubString(0, EndPath + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
_TString GetFileName(bool WithExtension = true) const
|
_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)
|
if (WithExtension)
|
||||||
{
|
{
|
||||||
|
@ -500,20 +564,20 @@ public:
|
||||||
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
size_t EndName = mInternalString.find_last_of(".");
|
size_t EndName = mInternalString.find_last_of(LITERAL("."));
|
||||||
return SubString(EndPath, EndName - EndPath);
|
return SubString(EndPath, EndName - EndPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_TString GetFileExtension() const
|
_TString GetFileExtension() const
|
||||||
{
|
{
|
||||||
size_t EndName = mInternalString.find_last_of(".");
|
size_t EndName = mInternalString.find_last_of(LITERAL("."));
|
||||||
return SubString(EndName + 1, Size() - EndName);
|
return SubString(EndName + 1, Size() - EndName);
|
||||||
}
|
}
|
||||||
|
|
||||||
_TString GetFilePathWithoutExtension() const
|
_TString GetFilePathWithoutExtension() const
|
||||||
{
|
{
|
||||||
size_t EndName = mInternalString.find_last_of(".");
|
size_t EndName = mInternalString.find_last_of(LITERAL("."));
|
||||||
return SubString(0, EndName);
|
return SubString(0, EndName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -806,6 +870,8 @@ public:
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#undef LITERAL
|
||||||
|
|
||||||
// ************ TString ************
|
// ************ TString ************
|
||||||
class TString : public TBasicString<char>
|
class TString : public TBasicString<char>
|
||||||
{
|
{
|
||||||
|
|
|
@ -122,7 +122,6 @@ HEADERS += \
|
||||||
Resource/CMaterial.h \
|
Resource/CMaterial.h \
|
||||||
Resource/CMaterialPass.h \
|
Resource/CMaterialPass.h \
|
||||||
Resource/CMaterialSet.h \
|
Resource/CMaterialSet.h \
|
||||||
Resource/CPakFile.h \
|
|
||||||
Resource/CResCache.h \
|
Resource/CResCache.h \
|
||||||
Resource/CResource.h \
|
Resource/CResource.h \
|
||||||
Resource/CScan.h \
|
Resource/CScan.h \
|
||||||
|
@ -133,8 +132,6 @@ HEADERS += \
|
||||||
Resource/ETevEnums.h \
|
Resource/ETevEnums.h \
|
||||||
Resource/ETexelFormat.h \
|
Resource/ETexelFormat.h \
|
||||||
Resource/SDependency.h \
|
Resource/SDependency.h \
|
||||||
Resource/SNamedResource.h \
|
|
||||||
Resource/SResInfo.h \
|
|
||||||
Resource/TResPtr.h \
|
Resource/TResPtr.h \
|
||||||
Scene/CCollisionNode.h \
|
Scene/CCollisionNode.h \
|
||||||
Scene/CLightNode.h \
|
Scene/CLightNode.h \
|
||||||
|
@ -193,7 +190,11 @@ HEADERS += \
|
||||||
Resource/Factory/CSkinLoader.h \
|
Resource/Factory/CSkinLoader.h \
|
||||||
Render/EDepthGroup.h \
|
Render/EDepthGroup.h \
|
||||||
Scene/CScriptAttachNode.h \
|
Scene/CScriptAttachNode.h \
|
||||||
ScriptExtra/CSandwormExtra.h
|
ScriptExtra/CSandwormExtra.h \
|
||||||
|
GameProject/CGameProject.h \
|
||||||
|
GameProject/CResourceDatabase.h \
|
||||||
|
GameProject/CPackage.h \
|
||||||
|
GameProject/CGameExporter.h
|
||||||
|
|
||||||
# Source Files
|
# Source Files
|
||||||
SOURCES += \
|
SOURCES += \
|
||||||
|
@ -233,7 +234,6 @@ SOURCES += \
|
||||||
Resource/CLight.cpp \
|
Resource/CLight.cpp \
|
||||||
Resource/CMaterial.cpp \
|
Resource/CMaterial.cpp \
|
||||||
Resource/CMaterialPass.cpp \
|
Resource/CMaterialPass.cpp \
|
||||||
Resource/CPakFile.cpp \
|
|
||||||
Resource/CResCache.cpp \
|
Resource/CResCache.cpp \
|
||||||
Resource/CResource.cpp \
|
Resource/CResource.cpp \
|
||||||
Resource/CTexture.cpp \
|
Resource/CTexture.cpp \
|
||||||
|
@ -280,4 +280,7 @@ SOURCES += \
|
||||||
Resource/Factory/CSkinLoader.cpp \
|
Resource/Factory/CSkinLoader.cpp \
|
||||||
Resource/Model/EVertexAttribute.cpp \
|
Resource/Model/EVertexAttribute.cpp \
|
||||||
Scene/CScriptAttachNode.cpp \
|
Scene/CScriptAttachNode.cpp \
|
||||||
ScriptExtra/CSandwormExtra.cpp
|
ScriptExtra/CSandwormExtra.cpp \
|
||||||
|
GameProject/CGameProject.cpp \
|
||||||
|
GameProject/CResourceDatabase.cpp \
|
||||||
|
GameProject/CGameExporter.cpp
|
||||||
|
|
|
@ -0,0 +1,336 @@
|
||||||
|
#include "CGameExporter.h"
|
||||||
|
#include <FileIO/FileIO.h>
|
||||||
|
#include <Common/AssertMacro.h>
|
||||||
|
#include <Common/CompressionUtil.h>
|
||||||
|
#include <Common/FileUtil.h>
|
||||||
|
|
||||||
|
#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<SPakSection> 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<u8>& 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<u8> 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<SCompressedBlock> 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<u8> 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<u8> 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
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
#ifndef CGAMEEXPORTER_H
|
||||||
|
#define CGAMEEXPORTER_H
|
||||||
|
|
||||||
|
#include "CGameProject.h"
|
||||||
|
#include <Common/CUniqueID.h>
|
||||||
|
#include <Common/Flags.h>
|
||||||
|
#include <Common/TString.h>
|
||||||
|
#include <Common/types.h>
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
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<u64, SResourceInstance> mResourceMap;
|
||||||
|
|
||||||
|
public:
|
||||||
|
CGameExporter(const TString& rkInputDir, const TString& rkOutputDir);
|
||||||
|
bool Export();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void CopyDiscData();
|
||||||
|
void LoadPaks();
|
||||||
|
void LoadPakResource(const SResourceInstance& rkResource, std::vector<u8>& rBuffer);
|
||||||
|
void ExportCookedResources();
|
||||||
|
|
||||||
|
inline EGame Game() const { return mpProject->Game(); }
|
||||||
|
inline void SetGame(EGame Game) { mpProject->SetGame(Game); }
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // CGAMEEXPORTER_H
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
#ifndef CGAMEPROJECT_H
|
||||||
|
#define CGAMEPROJECT_H
|
||||||
|
|
||||||
|
#include "CPackage.h"
|
||||||
|
#include "CResourceDatabase.h"
|
||||||
|
#include "Core/Resource/EGame.h"
|
||||||
|
#include <Common/CUniqueID.h>
|
||||||
|
#include <Common/TString.h>
|
||||||
|
#include <Common/types.h>
|
||||||
|
|
||||||
|
class CGameProject
|
||||||
|
{
|
||||||
|
EGame mGame;
|
||||||
|
TString mProjectName;
|
||||||
|
TString mProjectRoot;
|
||||||
|
CResourceDatabase *mpResourceDatabase;
|
||||||
|
std::vector<CPackage*> mWorldPaks;
|
||||||
|
std::vector<CPackage*> 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
|
|
@ -0,0 +1,34 @@
|
||||||
|
#ifndef CPACKAGE
|
||||||
|
#define CPACKAGE
|
||||||
|
|
||||||
|
#include <Common/CFourCC.h>
|
||||||
|
#include <Common/CUniqueID.h>
|
||||||
|
#include <Common/TString.h>
|
||||||
|
|
||||||
|
struct SNamedResource
|
||||||
|
{
|
||||||
|
TString Name;
|
||||||
|
CUniqueID ID;
|
||||||
|
};
|
||||||
|
|
||||||
|
class CPackage
|
||||||
|
{
|
||||||
|
TString mPakName;
|
||||||
|
std::vector<SNamedResource> mNamedResources;
|
||||||
|
std::vector<CUniqueID> 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
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
#include "CResourceDatabase.h"
|
|
@ -0,0 +1,29 @@
|
||||||
|
#ifndef CRESOURCEDATABASE_H
|
||||||
|
#define CRESOURCEDATABASE_H
|
||||||
|
|
||||||
|
#include <Common/CUniqueID.h>
|
||||||
|
#include <Common/TString.h>
|
||||||
|
#include <Common/types.h>
|
||||||
|
|
||||||
|
class CResourceEntry
|
||||||
|
{
|
||||||
|
CUniqueID ID;
|
||||||
|
TString DataPath;
|
||||||
|
|
||||||
|
public:
|
||||||
|
};
|
||||||
|
|
||||||
|
class CResourceDatabase
|
||||||
|
{
|
||||||
|
struct SResEntry
|
||||||
|
{
|
||||||
|
CUniqueID ID;
|
||||||
|
TString DataPath;
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
CResourceDatabase() {}
|
||||||
|
~CResourceDatabase() {}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // CRESOURCEDATABASE_H
|
|
@ -1,180 +0,0 @@
|
||||||
#include "CPakFile.h"
|
|
||||||
#include <Common/Log.h>
|
|
||||||
#include <Common/types.h>
|
|
||||||
#include <FileIO/CMemoryInStream.h>
|
|
||||||
#include <FileIO/FileIO.h>
|
|
||||||
|
|
||||||
#include <zlib.h>
|
|
||||||
#include <lzo/lzo1x.h>
|
|
||||||
#include <iostream>
|
|
||||||
#include <iomanip>
|
|
||||||
|
|
||||||
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<SNamedResource> 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<u8>* 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<u8>* CPakFile::Resource(SResInfo& rInfo)
|
|
||||||
{
|
|
||||||
mpPak->Seek(rInfo.Offset, SEEK_SET);
|
|
||||||
std::vector<u8> *pResBuf = new std::vector<u8>;
|
|
||||||
|
|
||||||
if (rInfo.Compressed)
|
|
||||||
{
|
|
||||||
u32 DecmpSize = mpPak->ReadLong();
|
|
||||||
pResBuf->resize(DecmpSize);
|
|
||||||
|
|
||||||
std::vector<u8> 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;
|
|
||||||
}
|
|
|
@ -1,31 +0,0 @@
|
||||||
#ifndef CPAKFILE_H
|
|
||||||
#define CPAKFILE_H
|
|
||||||
|
|
||||||
#include "SNamedResource.h"
|
|
||||||
#include "SResInfo.h"
|
|
||||||
#include <Common/types.h>
|
|
||||||
#include <FileIO/CFileInStream.h>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
class CPakFile
|
|
||||||
{
|
|
||||||
private:
|
|
||||||
u32 mVersion;
|
|
||||||
std::vector<SNamedResource> mNamedResTable;
|
|
||||||
std::vector<SResInfo> mResInfoTable;
|
|
||||||
IInputStream* mpPak;
|
|
||||||
|
|
||||||
bool Decompress(u8 *pSrc, u32 SrcLen, u8 *pDst, u32 DstLen);
|
|
||||||
|
|
||||||
public:
|
|
||||||
CPakFile();
|
|
||||||
CPakFile(IInputStream* pPakFile);
|
|
||||||
~CPakFile();
|
|
||||||
|
|
||||||
std::vector<SNamedResource> NamedResources();
|
|
||||||
SResInfo ResourceInfo(u64 AssetID, CFourCC AssetType);
|
|
||||||
std::vector<u8>* Resource(u64 AssetID, CFourCC AssetType);
|
|
||||||
std::vector<u8>* Resource(SResInfo& rInfo);
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // CPAKFILE_H
|
|
|
@ -484,15 +484,33 @@ CScriptTemplate* CTemplateLoader::LoadScriptTemplate(XMLDocument *pDoc, const TS
|
||||||
if (!Source.IsEmpty() && !ID.IsEmpty())
|
if (!Source.IsEmpty() && !ID.IsEmpty())
|
||||||
{
|
{
|
||||||
CScriptTemplate::SEditorAsset Asset;
|
CScriptTemplate::SEditorAsset Asset;
|
||||||
|
TStringList AcceptedExtensions;
|
||||||
|
|
||||||
if (Type == "animparams")
|
if (Type == "animparams")
|
||||||
|
{
|
||||||
Asset.AssetType = CScriptTemplate::SEditorAsset::eAnimParams;
|
Asset.AssetType = CScriptTemplate::SEditorAsset::eAnimParams;
|
||||||
|
AcceptedExtensions.push_back("ANCS");
|
||||||
|
AcceptedExtensions.push_back("CHAR");
|
||||||
|
}
|
||||||
|
|
||||||
else if (Type == "model")
|
else if (Type == "model")
|
||||||
|
{
|
||||||
Asset.AssetType = CScriptTemplate::SEditorAsset::eModel;
|
Asset.AssetType = CScriptTemplate::SEditorAsset::eModel;
|
||||||
|
AcceptedExtensions.push_back("CMDL");
|
||||||
|
}
|
||||||
|
|
||||||
else if (Type == "billboard")
|
else if (Type == "billboard")
|
||||||
|
{
|
||||||
Asset.AssetType = CScriptTemplate::SEditorAsset::eBillboard;
|
Asset.AssetType = CScriptTemplate::SEditorAsset::eBillboard;
|
||||||
|
AcceptedExtensions.push_back("TXTR");
|
||||||
|
}
|
||||||
|
|
||||||
else if (Type == "collision")
|
else if (Type == "collision")
|
||||||
|
{
|
||||||
Asset.AssetType = CScriptTemplate::SEditorAsset::eCollision;
|
Asset.AssetType = CScriptTemplate::SEditorAsset::eCollision;
|
||||||
|
AcceptedExtensions.push_back("DCLN");
|
||||||
|
}
|
||||||
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
pAsset = pAsset->NextSiblingElement();
|
pAsset = pAsset->NextSiblingElement();
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
#ifndef SNAMEDRESOURCE_H
|
|
||||||
#define SNAMEDRESOURCE_H
|
|
||||||
|
|
||||||
#include <Common/CFourCC.h>
|
|
||||||
#include <Common/types.h>
|
|
||||||
|
|
||||||
struct SNamedResource
|
|
||||||
{
|
|
||||||
CFourCC Type;
|
|
||||||
TString Name;
|
|
||||||
u64 ID;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // SNAMEDRESOURCE_H
|
|
|
@ -1,19 +0,0 @@
|
||||||
#ifndef SRESINFO_H
|
|
||||||
#define SRESINFO_H
|
|
||||||
|
|
||||||
#include <Common/CFourCC.h>
|
|
||||||
#include <Common/types.h>
|
|
||||||
|
|
||||||
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
|
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
#include "Editor/ModelEditor/CModelEditorWindow.h"
|
#include "Editor/ModelEditor/CModelEditorWindow.h"
|
||||||
#include "Editor/WorldEditor/CWorldEditor.h"
|
#include "Editor/WorldEditor/CWorldEditor.h"
|
||||||
|
#include <Core/GameProject/CGameExporter.h>
|
||||||
#include <Core/Resource/CResCache.h>
|
#include <Core/Resource/CResCache.h>
|
||||||
|
|
||||||
#include <QFileDialog>
|
#include <QFileDialog>
|
||||||
|
@ -27,6 +28,7 @@ CStartWindow::CStartWindow(QWidget *parent)
|
||||||
|
|
||||||
connect(ui->ActionAbout, SIGNAL(triggered()), this, SLOT(About()));
|
connect(ui->ActionAbout, SIGNAL(triggered()), this, SLOT(About()));
|
||||||
connect(ui->ActionCharacterEditor, SIGNAL(triggered()), this, SLOT(LaunchCharacterEditor()));
|
connect(ui->ActionCharacterEditor, SIGNAL(triggered()), this, SLOT(LaunchCharacterEditor()));
|
||||||
|
connect(ui->ActionExportGame, SIGNAL(triggered()), this, SLOT(ExportGame()));
|
||||||
}
|
}
|
||||||
|
|
||||||
CStartWindow::~CStartWindow()
|
CStartWindow::~CStartWindow()
|
||||||
|
@ -252,3 +254,23 @@ void CStartWindow::About()
|
||||||
CAboutDialog Dialog(this);
|
CAboutDialog Dialog(this);
|
||||||
Dialog.exec();
|
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();
|
||||||
|
}
|
||||||
|
|
|
@ -40,6 +40,7 @@ private slots:
|
||||||
|
|
||||||
void LaunchCharacterEditor();
|
void LaunchCharacterEditor();
|
||||||
void About();
|
void About();
|
||||||
|
void ExportGame();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void FillWorldUI();
|
void FillWorldUI();
|
||||||
|
|
|
@ -223,6 +223,7 @@
|
||||||
</property>
|
</property>
|
||||||
<addaction name="actionOpen_MLVL"/>
|
<addaction name="actionOpen_MLVL"/>
|
||||||
<addaction name="actionExtract_PAK"/>
|
<addaction name="actionExtract_PAK"/>
|
||||||
|
<addaction name="ActionExportGame"/>
|
||||||
</widget>
|
</widget>
|
||||||
<widget class="QMenu" name="menuTools">
|
<widget class="QMenu" name="menuTools">
|
||||||
<property name="title">
|
<property name="title">
|
||||||
|
@ -271,6 +272,11 @@
|
||||||
<string>Launch character editor</string>
|
<string>Launch character editor</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
|
<action name="ActionExportGame">
|
||||||
|
<property name="text">
|
||||||
|
<string>Export Game</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
</widget>
|
</widget>
|
||||||
<resources/>
|
<resources/>
|
||||||
<connections/>
|
<connections/>
|
||||||
|
|
|
@ -163,8 +163,7 @@ HEADERS += \
|
||||||
CharacterEditor/CCharacterEditorViewport.h \
|
CharacterEditor/CCharacterEditorViewport.h \
|
||||||
CGridRenderable.h \
|
CGridRenderable.h \
|
||||||
CharacterEditor/CSkeletonHierarchyModel.h \
|
CharacterEditor/CSkeletonHierarchyModel.h \
|
||||||
CLineRenderable.h \
|
CLineRenderable.h
|
||||||
CGameExporter.h
|
|
||||||
|
|
||||||
# Source Files
|
# Source Files
|
||||||
SOURCES += \
|
SOURCES += \
|
||||||
|
@ -224,8 +223,7 @@ SOURCES += \
|
||||||
CAboutDialog.cpp \
|
CAboutDialog.cpp \
|
||||||
CharacterEditor/CCharacterEditor.cpp \
|
CharacterEditor/CCharacterEditor.cpp \
|
||||||
CharacterEditor/CCharacterEditorViewport.cpp \
|
CharacterEditor/CCharacterEditorViewport.cpp \
|
||||||
CharacterEditor/CSkeletonHierarchyModel.cpp \
|
CharacterEditor/CSkeletonHierarchyModel.cpp
|
||||||
CGameExporter.cpp
|
|
||||||
|
|
||||||
# UI Files
|
# UI Files
|
||||||
FORMS += \
|
FORMS += \
|
||||||
|
|
Loading…
Reference in New Issue