Began initial implementation of the game exporter and game project classes

This commit is contained in:
parax0 2016-05-22 00:58:52 -06:00
parent 3009f06d11
commit 5f2064178c
19 changed files with 682 additions and 310 deletions

View File

@ -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;

View File

@ -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>
{ {

View File

@ -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

View File

@ -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
}

View File

@ -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

View File

@ -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);
}

View File

@ -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

View File

@ -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

View File

@ -0,0 +1 @@
#include "CResourceDatabase.h"

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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();

View File

@ -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

View File

@ -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

View File

@ -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();
}

View File

@ -40,6 +40,7 @@ private slots:
void LaunchCharacterEditor(); void LaunchCharacterEditor();
void About(); void About();
void ExportGame();
private: private:
void FillWorldUI(); void FillWorldUI();

View File

@ -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/>

View File

@ -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 += \