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 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;
|
||||
|
|
|
@ -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 CharType>
|
||||
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<char>
|
||||
{
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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())
|
||||
{
|
||||
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();
|
||||
|
|
|
@ -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/WorldEditor/CWorldEditor.h"
|
||||
#include <Core/GameProject/CGameExporter.h>
|
||||
#include <Core/Resource/CResCache.h>
|
||||
|
||||
#include <QFileDialog>
|
||||
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -40,6 +40,7 @@ private slots:
|
|||
|
||||
void LaunchCharacterEditor();
|
||||
void About();
|
||||
void ExportGame();
|
||||
|
||||
private:
|
||||
void FillWorldUI();
|
||||
|
|
|
@ -223,6 +223,7 @@
|
|||
</property>
|
||||
<addaction name="actionOpen_MLVL"/>
|
||||
<addaction name="actionExtract_PAK"/>
|
||||
<addaction name="ActionExportGame"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuTools">
|
||||
<property name="title">
|
||||
|
@ -271,6 +272,11 @@
|
|||
<string>Launch character editor</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="ActionExportGame">
|
||||
<property name="text">
|
||||
<string>Export Game</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
|
|
|
@ -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 += \
|
||||
|
|
Loading…
Reference in New Issue