Added functionality to generate asset names

This commit is contained in:
parax0 2016-12-12 01:33:46 -07:00
parent efa85036c2
commit 2e44e5b119
35 changed files with 82035 additions and 311 deletions

File diff suppressed because it is too large Load Diff

View File

@ -24,6 +24,7 @@ public:
inline CFourCC() { mFourCC = 0; }
inline CFourCC(const char *pkSrc) { mFourCC = FOURCC(pkSrc); }
inline CFourCC(const TString& rkSrc) { ASSERT(rkSrc.Length() == 4); mFourCC = FOURCC(rkSrc); }
inline CFourCC(const TWideString& rkSrc){ ASSERT(rkSrc.Length() == 4); mFourCC = FOURCC(rkSrc); }
inline CFourCC(u32 Src) { mFourCC = Src; }
inline CFourCC(IInputStream& rSrc) { Read(rSrc); }

View File

@ -29,6 +29,36 @@ EGame GetGameForID(const CFourCC& rkID)
return eUnknownGame;
}
TString GetGameName(EGame Game)
{
switch (Game)
{
case ePrimeDemo: return "Metroid Prime Kiosk Demo";
case ePrime: return "Metroid Prime";
case eEchoesDemo: return "Metroid Prime 2: Echoes Demo";
case eEchoes: return "Metroid Prime 2: Echoes";
case eCorruptionProto: return "Metroid Prime 3: Corruption Prototype";
case eCorruption: return "Metroid Prime 3: Corruption";
case eReturns: return "Donkey Kong Country Returns";
default: return "Unknown Game";
}
}
TString GetGameShortName(EGame Game)
{
switch (Game)
{
case ePrimeDemo: return "MP1Demo";
case ePrime: return "MP1";
case eEchoesDemo: return "MP2Demo";
case eEchoes: return "MP2";
case eCorruptionProto: return "MP3Proto";
case eCorruption: return "MP3";
case eReturns: return "DKCR";
default: return "Unknown";
}
}
void Serialize(IArchive& rArc, EGame& rGame)
{
CFourCC GameID = GetGameID(rGame);

View File

@ -7,6 +7,9 @@
class CFourCC;
class IArchive;
// Note: The reason why the EGame value isn't just the fourCC game ID is because a lot of code does inequality
// comparisons on EGame for version checking ie. "if (Game <= eEchoes)", which means the enum values need to be
// in chronological order.
enum EGame
{
ePrimeDemo,
@ -21,6 +24,8 @@ enum EGame
CFourCC GetGameID(EGame Game);
EGame GetGameForID(const CFourCC& rkID);
TString GetGameName(EGame Game);
TString GetGameShortName(EGame Game);
void Serialize(IArchive& rArc, EGame& rGame);
#endif // EGAME_H

View File

@ -42,6 +42,18 @@ bool IsRelative(const TWideString& rkDirPath)
return !boost::filesystem::path(*rkDirPath).is_relative();
}
bool IsEmpty(const TWideString& rkDirPath)
{
if (!IsDirectory(rkDirPath))
{
Log::Error("Non-directory path passed to IsEmpty(): " + rkDirPath.ToUTF8());
DEBUG_BREAK;
return false;
}
return is_empty(*rkDirPath);
}
bool CreateDirectory(const TWideString& rkNewDir)
{
if (!IsValidPath(rkNewDir, true))
@ -61,6 +73,7 @@ bool CopyFile(const TWideString& rkOrigPath, const TWideString& rkNewPath)
return false;
}
CreateDirectory(rkNewPath.GetFileDirectory());
boost::system::error_code Error;
copy(*rkOrigPath, *rkNewPath, Error);
return (Error == boost::system::errc::success);
@ -74,6 +87,7 @@ bool CopyDirectory(const TWideString& rkOrigPath, const TWideString& rkNewPath)
return false;
}
CreateDirectory(rkNewPath.GetFileDirectory());
boost::system::error_code Error;
copy_directory(*rkOrigPath, *rkNewPath, Error);
return (Error == boost::system::errc::success);
@ -317,7 +331,7 @@ TWideString SanitizePath(TWideString Path, bool Directory)
TWideString Comp = *It;
bool IsDir = Directory || CompIdx < Components.size() - 1;
bool IsRoot = CompIdx == 0;
SanitizeName(Comp, IsDir, IsRoot);
Comp = SanitizeName(Comp, IsDir, IsRoot);
Path += Comp;
if (IsDir) Path += L'\\';
@ -330,6 +344,9 @@ TWideString SanitizePath(TWideString Path, bool Directory)
bool IsValidName(const TWideString& rkName, bool Directory, bool RootDir /*= false*/)
{
// Windows only atm
if (rkName.IsEmpty())
return false;
u32 NumIllegalChars = sizeof(gskIllegalNameChars) / sizeof(wchar_t);
if (Directory && (rkName == L"." || rkName == L".."))

View File

@ -13,6 +13,7 @@ bool IsFile(const TWideString& rkFilePath);
bool IsDirectory(const TWideString& rkDirPath);
bool IsAbsolute(const TWideString& rkDirPath);
bool IsRelative(const TWideString& rkDirPath);
bool IsEmpty(const TWideString& rkDirPath);
bool CreateDirectory(const TWideString& rkNewDir);
bool CopyFile(const TWideString& rkOrigPath, const TWideString& rkNewPath);
bool CopyDirectory(const TWideString& rkOrigPath, const TWideString& rkNewPath);

View File

@ -426,6 +426,7 @@ public:
// Container serialize methods
#include <list>
#include <map>
#include <set>
#include <vector>
@ -528,4 +529,77 @@ inline void SerializeContainer(IArchive& rArc, std::set<ValType>& rSet, const TS
}
}
// std::map
template<typename KeyType, typename ValType>
inline void SerializeContainer(IArchive& rArc, std::map<KeyType,ValType>& rMap, const TString& rkElemName)
{
u32 Size = rMap.size();
rArc.SerializeContainerSize(Size);
if (rArc.IsReader())
{
for (u32 iElem = 0; iElem < Size; iElem++)
{
KeyType Key;
ValType Val;
rArc.ParamBegin(*rkElemName);
rArc << SERIAL("Key", Key) << SERIAL("Value", Val);
rArc.ParamEnd();
ASSERT(rMap.find(Key) == rMap.end());
rMap[Key] = Val;
}
}
else
{
for (auto Iter = rMap.begin(); Iter != rMap.end(); Iter++)
{
KeyType Key = Iter->first;
ValType Val = Iter->second;
rArc.ParamBegin(*rkElemName);
rArc << SERIAL("Key", Key) << SERIAL("Value", Val);
rArc.ParamEnd();
}
}
}
template<typename KeyType, typename ValType, typename FactoryType>
inline void SerializeContainer(IArchive& rArc, std::map<KeyType,ValType>& rMap, const TString& rkElemName, FactoryType *pFactory)
{
u32 Size = rMap.size();
rArc.SerializeContainerSize(Size);
if (rArc.IsReader())
{
for (u32 iElem = 0; iElem < Size; iElem++)
{
KeyType Key;
ValType Val;
rArc.ParamBegin(*rkElemName);
rArc << SERIAL_ABSTRACT("Key", Key, pFactory) << SERIAL("Value", Val);
rArc.ParamEnd();
ASSERT(rMap.find(Key) == rMap.end());
rMap[Key] = Val;
}
}
else
{
for (auto Iter = rMap.begin(); Iter != rMap.end(); Iter++)
{
KeyType Key = Iter->first;
ValType Val = Iter->second;
rArc.ParamBegin(*rkElemName);
rArc << SERIAL("Key", Key) << SERIAL("Value", Val);
rArc.ParamEnd();
}
}
}
#endif // IARCHIVE

View File

@ -4,11 +4,12 @@
#include "types.h"
#include <FileIO/IOUtil.h>
#include <string>
#include <list>
#include <vector>
#include <sstream>
#include <cstdarg>
#include <iomanip>
#include <list>
#include <sstream>
#include <string>
#include <vector>
/* This is a string class which is essentially a wrapper around std::basic_string.
* The reason for this is because there are a lot of string functions I use very
@ -149,24 +150,22 @@ public:
return (u32) Pos;
}
u32 IndexOfPhrase(_TString Str, u32 Offset, bool CaseSensitive = true) const
u32 IndexOfPhrase(const _TString& rkStr, 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();
if (Size() < rkStr.Size()) return -1;
// Now loop from the offset provided by the user.
u32 Pos = Offset;
u32 LatestPossibleStart = Size() - Str.Size();
u32 LatestPossibleStart = Size() - rkStr.Size();
u32 MatchStart = -1;
u32 Matched = 0;
while (Pos < Size())
{
// If this character matches, increment Matched!
if (CheckStr[Pos] == Str[Matched])
bool Match = CaseSensitive ? (At(Pos) == rkStr[Matched]) : (CharToUpper(At(Pos)) == CharToUpper(rkStr[Matched]));
if (Match)
{
Matched++;
@ -174,7 +173,7 @@ public:
MatchStart = Pos;
// If we matched the entire string, we can return.
if (Matched == Str.Size())
if (Matched == rkStr.Size())
return MatchStart;
}
@ -198,9 +197,9 @@ public:
return -1;
}
inline u32 IndexOfPhrase(_TString Str, bool CaseSensitive = true) const
inline u32 IndexOfPhrase(const _TString& rkStr, bool CaseSensitive = true) const
{
return IndexOfPhrase(Str, 0, CaseSensitive);
return IndexOfPhrase(rkStr, 0, CaseSensitive);
}
// Modify String
@ -318,18 +317,10 @@ public:
_TString ToUpper() const
{
// todo: doesn't handle accented characters
_TString Out(Size());
for (u32 iChar = 0; iChar < Size(); iChar++)
{
CharType Chr = At(iChar);
if (Chr >= CHAR_LITERAL('a') && Chr <= CHAR_LITERAL('z'))
Out[iChar] = Chr - 0x20;
else
Out[iChar] = Chr;
}
Out[iChar] = CharToUpper( At(iChar) );
return Out;
}
@ -340,14 +331,7 @@ public:
_TString Out(Size());
for (u32 iChar = 0; iChar < Size(); iChar++)
{
CharType Chr = At(iChar);
if (Chr >= 'A' && Chr <= 'Z')
Out[iChar] = Chr + 0x20;
else
Out[iChar] = Chr;
}
Out[iChar] = CharToLower( At(iChar) );
return Out;
}
@ -509,26 +493,22 @@ public:
return (Size() == 0);
}
bool StartsWith(_TString Str, bool CaseSensitive = true) const
bool StartsWith(const _TString& rkStr, bool CaseSensitive = true) const
{
if (Size() < Str.Size())
if (Size() < rkStr.Size())
return false;
_TString CompStr = (CaseSensitive ? *this : ToUpper());
if (!CaseSensitive) Str = Str.ToUpper();
return (CompStr.SubString(0, Str.Size()) == Str);
_TString SubStr = SubString(0, rkStr.Size());
return CaseSensitive ? SubStr == rkStr : SubStr.CaseInsensitiveCompare(rkStr);
}
bool EndsWith(_TString Str, bool CaseSensitive = true) const
bool EndsWith(const _TString& rkStr, bool CaseSensitive = true) const
{
if (Size() < Str.Size())
if (Size() < rkStr.Size())
return false;
_TString CompStr = (CaseSensitive ? *this : ToUpper());
if (!CaseSensitive) Str = Str.ToUpper();
return (CompStr.SubString(CompStr.Size() - Str.Size(), Str.Size()) == Str);
_TString SubStr = SubString(Size() - rkStr.Size(), rkStr.Size());
return CaseSensitive ? SubStr == rkStr : SubStr.CaseInsensitiveCompare(rkStr);
}
bool Contains(_TString Str, bool CaseSensitive = true) const
@ -577,7 +557,14 @@ public:
inline bool CaseInsensitiveCompare(const _TString& rkOther) const
{
return (ToUpper() == rkOther.ToUpper());
if (Size() != rkOther.Size())
return false;
for (u32 iChr = 0; iChr < Size(); iChr++)
if (CharToUpper(At(iChr)) != CharToUpper(rkOther[iChr]))
return false;
return true;
}
// Get Filename Components
@ -615,10 +602,8 @@ public:
return SubString(0, EndName);
}
_TString GetParentDirectoryPath(_TString ParentDirName, bool CaseSensitive = true)
_TString GetParentDirectoryPath(const _TString& rkParentDirName, bool CaseSensitive = true)
{
if (!CaseSensitive) ParentDirName = ParentDirName.ToUpper();
int IdxA = 0;
int IdxB = IndexOf(LITERAL("\\/"));
if (IdxB == -1) return _TString();
@ -626,9 +611,8 @@ public:
while (IdxB != -1)
{
_TString DirName = SubString(IdxA, IdxB - IdxA);
if (!CaseSensitive) DirName = DirName.ToUpper();
if (DirName == ParentDirName)
if (CaseSensitive ? (DirName == rkParentDirName) : (DirName.CaseInsensitiveCompare(rkParentDirName)))
return Truncate(IdxB + 1);
IdxA = IdxB + 1;
@ -639,6 +623,12 @@ public:
}
// Operators
inline _TString& operator=(CharType Char)
{
mInternalString = Char;
return *this;
}
inline _TString& operator=(const CharType* pkText)
{
mInternalString = pkText;
@ -666,6 +656,14 @@ public:
return CString();
}
_TString operator+(CharType Other) const
{
_TString Out(Size() + 1);
memcpy(&Out[0], mInternalString.data(), Size() * sizeof(CharType));
memcpy(&Out[Size()], &Other, sizeof(CharType));
return Out;
}
_TString operator+(const CharType* pkOther) const
{
u32 Len = CStringLength(pkOther);
@ -681,6 +679,11 @@ public:
return (*this + rkOther.CString());
}
inline void operator+=(CharType Other)
{
*this = *this + Other;
}
inline void operator+=(const CharType* pkOther)
{
*this = *this + pkOther;
@ -691,6 +694,14 @@ public:
*this = *this + rkOther;
}
inline friend _TString operator+(CharType Left, const _TString& rkRight)
{
_TString Out(rkRight.Size() + 1);
memcpy(&Out[0], &Left, sizeof(CharType));
memcpy(&Out[sizeof(CharType)], rkRight.CString(), rkRight.Size() * sizeof(CharType));
return Out;
}
inline friend _TString operator+(const CharType* pkLeft, const _TString& rkRight)
{
u32 Len = CStringLength(pkLeft);
@ -709,6 +720,11 @@ public:
return Out;
}
inline bool operator==(CharType Other) const
{
return Size() == 1 && At(0) == Other;
}
inline bool operator==(const CharType *pkText) const
{
return CompareCStrings(pkText, CString());
@ -719,6 +735,11 @@ public:
return (mInternalString == rkOther.mInternalString);
}
inline friend bool operator==(CharType Other, const _TString& rkString)
{
return (rkString == Other);
}
inline friend bool operator==(const CharType *pkText, const _TString& rkString)
{
return (rkString == pkText);
@ -729,6 +750,11 @@ public:
return (rkStringB == rkStringA);
}
inline bool operator!=(CharType Other) const
{
return (!(*this == Other));
}
inline bool operator!=(const CharType *pkText) const
{
return (!(*this == pkText));
@ -739,6 +765,11 @@ public:
return (!(*this == rkOther));
}
inline friend bool operator!=(CharType Other, const _TString& rkString)
{
return (rkString != Other);
}
inline friend bool operator!=(const CharType *pkText, const _TString& rkString)
{
return (rkString != pkText);
@ -842,21 +873,39 @@ public:
}
// Static
static TBasicString<CharType> FromInt32(s32 Value, int Width = 0, int Base = 16)
static _TString Format(const CharType *pkFmt, ...)
{
// Probably better to rewrite this at some point for better error handling + avoiding all the C-style syntax
const int kBufferSize = 4096;
CharType StringBuffer[kBufferSize];
std::va_list Args;
va_start(Args, pkFmt);
if (typeid(CharType) == typeid(char))
vsprintf_s((char*) StringBuffer, kBufferSize, (char*) pkFmt, Args);
else
vswprintf_s((wchar_t*) StringBuffer, kBufferSize, (wchar_t*) pkFmt, Args);
va_end(Args);
return _TString(StringBuffer);
}
static _TString FromInt32(s32 Value, int Width = 0, int Base = 16)
{
std::basic_stringstream<CharType> SStream;
SStream << std::setbase(Base) << std::setw(Width) << std::setfill(CHAR_LITERAL('0')) << Value;
return SStream.str();
}
static TBasicString<CharType> FromInt64(s64 Value, int Width = 0, int Base = 16)
static _TString FromInt64(s64 Value, int Width = 0, int Base = 16)
{
std::basic_stringstream<CharType> SStream;
SStream << std::setbase(Base) << std::setw(Width) << std::setfill(CHAR_LITERAL('0')) << Value;
return SStream.str();
}
static TBasicString<CharType> FromFloat(float Value, int MinDecimals = 1)
static _TString FromFloat(float Value, int MinDecimals = 1)
{
// Initial float -> string conversion
std::basic_stringstream<CharType> SStream;
@ -900,7 +949,7 @@ public:
return Out;
}
static TBasicString<CharType> FileSizeString(u64 Size, u32 NumDecimals = 2)
static _TString FileSizeString(u64 Size, u32 NumDecimals = 2)
{
_TString Out;
_TString Type;
@ -933,17 +982,17 @@ public:
return Out + Type;
}
static TBasicString<CharType> HexString(unsigned char Num, int Width = 8, bool AddPrefix = true, bool Uppercase = true)
static _TString HexString(unsigned char Num, int Width = 8, bool AddPrefix = true, bool Uppercase = true)
{
return HexString((unsigned long) Num, Width, AddPrefix, Uppercase);
}
static TBasicString<CharType> HexString(unsigned short Num, int Width = 8, bool AddPrefix = true, bool Uppercase = true)
static _TString HexString(unsigned short Num, int Width = 8, bool AddPrefix = true, bool Uppercase = true)
{
return HexString((unsigned long) Num, Width, AddPrefix, Uppercase);
}
static TBasicString<CharType> HexString(unsigned long Num, int Width = 8, bool AddPrefix = true, bool Uppercase = true)
static _TString HexString(unsigned long Num, int Width = 8, bool AddPrefix = true, bool Uppercase = true)
{
std::basic_stringstream<CharType> SStream;
SStream << std::hex << std::setw(Width) << std::setfill('0') << Num;
@ -979,6 +1028,18 @@ public:
}
}
inline static CharType CharToLower(CharType Chr)
{
// todo: doesn't handle accented characters
return (Chr >= CHAR_LITERAL('A') && Chr <= CHAR_LITERAL('Z')) ? Chr + 0x20 : Chr;
}
inline static CharType CharToUpper(CharType Chr)
{
// todo: doesn't handle accented characters
return (Chr >= CHAR_LITERAL('a') && Chr <= CHAR_LITERAL('z')) ? Chr - 0x20 : Chr;
}
static bool IsWhitespace(CharType Chr)
{
return ( (Chr == CHAR_LITERAL('\t')) ||
@ -988,6 +1049,11 @@ public:
(Chr == CHAR_LITERAL('\r')) ||
(Chr == CHAR_LITERAL(' ')) );
}
static inline bool IsNumerical(CharType Chr)
{
return (Chr >= CHAR_LITERAL('0') && Chr <= CHAR_LITERAL('9'));
}
};
#undef LITERAL

View File

@ -17,7 +17,7 @@ void CAudioManager::LoadAssets()
mSfxIdMap.clear();
// Load/sort all audio groups
for (TResourceIterator<CAudioGroup> It(mpProject->ResourceStore()); It; ++It)
for (TResourceIterator<eAudioGroup> It(mpProject->ResourceStore()); It; ++It)
{
CAudioGroup *pGroup = (CAudioGroup*) It->Load();
if (pGroup) mAudioGroups.push_back(pGroup);

View File

@ -213,7 +213,9 @@ HEADERS += \
Resource/Animation/CSkeleton.h \
Resource/Animation/CSkin.h \
Resource/Animation/IMetaTransition.h \
Resource/Animation/IMetaAnimation.h
Resource/Animation/IMetaAnimation.h \
GameProject/CAssetNameMap.h \
GameProject/AssetNameGeneration.h
# Source Files
SOURCES += \
@ -314,4 +316,5 @@ SOURCES += \
Resource/Animation/CAnimationParameters.cpp \
Resource/Animation/CSkeleton.cpp \
Resource/Animation/IMetaAnimation.cpp \
Resource/Animation/IMetaTransition.cpp
Resource/Animation/IMetaTransition.cpp \
GameProject/AssetNameGeneration.cpp

View File

@ -0,0 +1,438 @@
#include "AssetNameGeneration.h"
#include "CGameProject.h"
#include "CResourceIterator.h"
#include "Core/Resource/CFont.h"
#include "Core/Resource/CScan.h"
#include "Core/Resource/CWorld.h"
#include "Core/Resource/Script/CScriptLayer.h"
#include <Math/MathUtil.h>
#define PROCESS_WORLDS 1
#define PROCESS_AREAS 1
#define PROCESS_MODELS 1
#define PROCESS_AUDIO_GROUPS 1
#define PROCESS_ANIM_CHAR_SETS 1
#define PROCESS_STRINGS 1
#define PROCESS_SCANS 1
#define PROCESS_FONTS 1
void ApplyGeneratedName(CResourceEntry *pEntry, const TWideString& rkDir, const TWideString& rkName)
{
TWideString SanitizedName = FileUtil::SanitizeName(rkName, false);
TWideString SanitizedDir = FileUtil::SanitizePath(rkDir, true);
if (SanitizedName.IsEmpty()) return;
CVirtualDirectory *pNewDir = pEntry->ResourceStore()->GetVirtualDirectory(SanitizedDir, false, true);
if (pEntry->Directory() == pNewDir && pEntry->Name() == SanitizedName) return;
TWideString Name = SanitizedName;
int AppendNum = 0;
while (pNewDir->FindChildResource(Name, pEntry->ResourceType()) != nullptr)
{
Name = TWideString::Format(L"%s_%d", *SanitizedName, AppendNum);
AppendNum++;
}
bool Success = pEntry->Move(SanitizedDir, Name);
ASSERT(Success);
}
void GenerateAssetNames(CGameProject *pProj)
{
// todo: CAUD/CSMP
CResourceStore *pStore = pProj->ResourceStore();
// Generate names for package named resources first
for (u32 iPkg = 0; iPkg < pProj->NumPackages(); iPkg++)
{
CPackage *pPkg = pProj->PackageByIndex(iPkg);
for (u32 iCol = 0; iCol < pPkg->NumCollections(); iCol++)
{
CResourceCollection *pCol = pPkg->CollectionByIndex(iCol);
for (u32 iRes = 0; iRes < pCol->NumResources(); iRes++)
{
const SNamedResource& rkRes = pCol->ResourceByIndex(iRes);
if (rkRes.Name.EndsWith("NODEPEND")) continue;
CResourceEntry *pRes = pStore->FindEntry(rkRes.ID);
ApplyGeneratedName(pRes, pPkg->Name().ToUTF16(), rkRes.Name.ToUTF16());
}
}
}
#if PROCESS_WORLDS
// Generate world/area names
const TWideString kWorldsRoot = L"Worlds\\";
for (TResourceIterator<eWorld> It(pStore); It; ++It)
{
// World common stuff
TWideString WorldName = It->Name();
// Remove date from the end of the world name
if (WorldName.EndsWith(L"_#SERIAL#"))
WorldName = WorldName.ChopBack(9);
// Verify the second-to-last character is a number to make sure there is actually a date in the world name
// note MP2 multiplayer worlds do not have dates in their names
else if (WorldName[WorldName.Size() - 2] >= '0' && WorldName[WorldName.Size() - 2] <= '9')
{
bool StartedDate = false;
while (!WorldName.IsEmpty())
{
wchar_t Chr = WorldName.Back();
if (!StartedDate && Chr >= L'0' && Chr <= L'9')
StartedDate = true;
else if (StartedDate && Chr != L'_' && (Chr < L'0' || Chr > L'9'))
break;
WorldName = WorldName.ChopBack(1);
}
}
TWideString WorldDir = kWorldsRoot + WorldName + L'\\';
ApplyGeneratedName(*It, WorldDir, WorldName);
CWorld *pWorld = (CWorld*) It->Load();
CModel *pSkyModel = pWorld->DefaultSkybox();
CStringTable *pWorldNameTable = pWorld->WorldName();
CStringTable *pDarkWorldNameTable = pWorld->DarkWorldName();
CResource *pSaveWorld = pWorld->SaveWorld();
CResource *pMapWorld = pWorld->MapWorld();
if (pSkyModel && !pSkyModel->Entry()->IsCategorized())
{
CResourceEntry *pSkyEntry = pSkyModel->Entry();
ApplyGeneratedName(pSkyEntry, WorldDir, TWideString::Format(L"%s_Sky", *WorldName));
}
if (pWorldNameTable)
{
CResourceEntry *pNameEntry = pWorldNameTable->Entry();
ApplyGeneratedName(pNameEntry, WorldDir, pNameEntry->Name());
}
if (pDarkWorldNameTable)
{
CResourceEntry *pDarkNameEntry = pDarkWorldNameTable->Entry();
ApplyGeneratedName(pDarkNameEntry, WorldDir, pDarkNameEntry->Name());
}
if (pSaveWorld)
ApplyGeneratedName(pSaveWorld->Entry(), WorldDir, TWideString::Format(L"%s_SaveInfo", *WorldName));
if (pMapWorld)
ApplyGeneratedName(pMapWorld->Entry(), WorldDir, TWideString::Format(L"%s_Map", *WorldName));
// Areas
for (u32 iArea = 0; iArea < pWorld->NumAreas(); iArea++)
{
// Determine area name
CAssetID AreaID = pWorld->AreaResourceID(iArea);
TString InternalAreaName = pWorld->AreaInternalName(iArea);
CStringTable *pAreaNameTable = pWorld->AreaName(iArea);
TWideString AreaName;
if (pAreaNameTable)
AreaName = pAreaNameTable->String("ENGL", 0);
else if (!InternalAreaName.IsEmpty())
AreaName = L"!!" + InternalAreaName.ToUTF16();
else
AreaName = L"!!" + AreaID.ToString().ToUTF16();
TWideString AreaDir = TWideString::Format(L"%s%02d_%s\\", *WorldDir, iArea, *AreaName);
// Rename area stuff
CResourceEntry *pAreaEntry = pStore->FindEntry(AreaID);
ASSERT(pAreaEntry != nullptr);
ApplyGeneratedName(pAreaEntry, AreaDir, AreaName);
if (pAreaNameTable)
ApplyGeneratedName(pAreaNameTable->Entry(), AreaDir, pAreaNameTable->Entry()->Name());
if (pMapWorld)
{
ASSERT(pMapWorld->Type() == eDependencyGroup);
CDependencyGroup *pGroup = static_cast<CDependencyGroup*>(pMapWorld);
CAssetID MapID = pGroup->DependencyByIndex(iArea);
CResourceEntry *pMapEntry = pStore->FindEntry(MapID);
ASSERT(pMapEntry != nullptr);
ApplyGeneratedName(pMapEntry, AreaDir, TWideString::Format(L"%s_Map", *AreaName));
}
}
}
#endif
#if PROCESS_AREAS
// Generate area stuff
for (TResourceIterator<eArea> It(pStore); It; ++It)
{
TWideString AreaDir = It->DirectoryPath();
TWideString AreaName = It->Name();
CGameArea *pArea = (CGameArea*) It->Load();
// Area lightmaps
TWideString LightmapDir = AreaDir + L"Lightmaps\\";
CMaterialSet *pMaterials = pArea->Materials();
for (u32 iMat = 0; iMat < pMaterials->NumMaterials(); iMat++)
{
CMaterial *pMat = pMaterials->MaterialByIndex(iMat);
if (pMat->Options().HasFlag(CMaterial::eLightmap))
{
CTexture *pLightmapTex = pMat->Pass(0)->Texture();
CResourceEntry *pTexEntry = pLightmapTex->Entry();
TWideString TexName = TWideString::Format(L"lit_%s_%dx%d", *pLightmapTex->ID().ToString().ToUTF16(), pLightmapTex->Width(), pLightmapTex->Height());
ApplyGeneratedName(pTexEntry, LightmapDir, TexName);
pTexEntry->SetHidden(true);
}
}
// Generate names from script instance names
for (u32 iLyr = 0; iLyr < pArea->NumScriptLayers(); iLyr++)
{
CScriptLayer *pLayer = pArea->ScriptLayer(iLyr);
for (u32 iInst = 0; iInst < pLayer->NumInstances(); iInst++)
{
CScriptObject *pInst = pLayer->InstanceByIndex(iInst);
if (pInst->ObjectTypeID() == 0x42 || pInst->ObjectTypeID() == FOURCC("POIN"))
{
TString Name = pInst->InstanceName();
if (Name.EndsWith(".scan"))
{
TIDString ScanIDString = (pProj->Game() <= ePrime ? "0x4:0x0" : "0xBDBEC295:0xB94E9BE7");
TAssetProperty *pScanProperty = TPropCast<TAssetProperty>(pInst->PropertyByIDString(ScanIDString));
if (pScanProperty)
{
CAssetID ScanID = pScanProperty->Get();
CResourceEntry *pEntry = pStore->FindEntry(ScanID);
if (pEntry && !pEntry->IsNamed())
{
TWideString ScanName = Name.ToUTF16().ChopBack(5);
ApplyGeneratedName(pEntry, pEntry->DirectoryPath(), ScanName);
CScan *pScan = (CScan*) pEntry->Load();
if (pScan && pScan->ScanText())
{
CResourceEntry *pStringEntry = pScan->ScanText()->Entry();
ApplyGeneratedName(pStringEntry, pStringEntry->DirectoryPath(), ScanName);
}
}
}
}
}
}
}
// Other area assets
CResourceEntry *pPathEntry = pStore->FindEntry(pArea->PathID());
CResourceEntry *pPoiMapEntry = pArea->PoiToWorldMap() ? pArea->PoiToWorldMap()->Entry() : nullptr;
CResourceEntry *pPortalEntry = pStore->FindEntry(pArea->PortalAreaID());
if (pPathEntry)
ApplyGeneratedName(pPathEntry, AreaDir, TWideString::Format(L"%s_Path", *AreaName));
if (pPoiMapEntry)
ApplyGeneratedName(pPoiMapEntry, AreaDir, TWideString::Format(L"%s_EGMC", *AreaName));
if (pPortalEntry)
ApplyGeneratedName(pPortalEntry, AreaDir, TWideString::Format(L"%s_PortalArea", *AreaName));
pStore->DestroyUnreferencedResources();
}
#endif
#if PROCESS_MODELS
// Generate Model Lightmap names
for (TResourceIterator<eModel> It(pStore); It; ++It)
{
CModel *pModel = (CModel*) It->Load();
for (u32 iSet = 0; iSet < pModel->GetMatSetCount(); iSet++)
{
CMaterialSet *pSet = pModel->GetMatSet(iSet);
for (u32 iMat = 0; iMat < pSet->NumMaterials(); iMat++)
{
CMaterial *pMat = pSet->MaterialByIndex(iMat);
if (pMat->Options().HasFlag(CMaterial::eLightmap))
{
CTexture *pLightmapTex = pMat->Pass(0)->Texture();
CResourceEntry *pTexEntry = pLightmapTex->Entry();
if (pTexEntry->IsNamed() || pTexEntry->IsCategorized()) continue;
TWideString TexName = TWideString::Format(L"lit_%s_%dx%d", *pLightmapTex->ID().ToString().ToUTF16(), pLightmapTex->Width(), pLightmapTex->Height());
ApplyGeneratedName(pTexEntry, pModel->Entry()->DirectoryPath(), TexName);
pTexEntry->SetHidden(true);
}
}
}
pStore->DestroyUnreferencedResources();
}
#endif
#if PROCESS_AUDIO_GROUPS
// Generate Audio Group names
for (TResourceIterator<eAudioGroup> It(pStore); It; ++It)
{
CAudioGroup *pGroup = (CAudioGroup*) It->Load();
TWideString GroupName = pGroup->GroupName().ToUTF16();
ApplyGeneratedName(*It, L"AudioGrp\\", GroupName);
}
#endif
#if PROCESS_ANIM_CHAR_SETS
// Generate animation format names
for (TResourceIterator<eAnimSet> It(pStore); It; ++It)
{
TWideString SetDir = It->DirectoryPath();
TWideString NewSetName;
CAnimSet *pSet = (CAnimSet*) It->Load();
for (u32 iChar = 0; iChar < pSet->NumCharacters(); iChar++)
{
const SSetCharacter *pkChar = pSet->Character(iChar);
TWideString CharName = pkChar->Name.ToUTF16();
if (iChar == 0) NewSetName = CharName;
if (pkChar->pModel) ApplyGeneratedName(pkChar->pModel->Entry(), SetDir, CharName);
if (pkChar->pSkeleton) ApplyGeneratedName(pkChar->pSkeleton->Entry(), SetDir, CharName);
if (pkChar->pSkin) ApplyGeneratedName(pkChar->pSkin->Entry(), SetDir, CharName);
if (pkChar->IceModel.IsValid() || pkChar->IceSkin.IsValid())
{
TWideString IceName = TWideString::Format(L"%s_IceOverlay", *CharName);
if (pkChar->IceModel.IsValid())
{
CResourceEntry *pIceModelEntry = pStore->FindEntry(pkChar->IceModel);
ApplyGeneratedName(pIceModelEntry, SetDir, IceName);
}
if (pkChar->IceSkin.IsValid())
{
CResourceEntry *pIceSkinEntry = pStore->FindEntry(pkChar->IceSkin);
ApplyGeneratedName(pIceSkinEntry, SetDir, IceName);
}
}
}
if (!NewSetName.IsEmpty())
ApplyGeneratedName(*It, SetDir, NewSetName);
std::set<CAnimPrimitive> AnimPrimitives;
pSet->GetUniquePrimitives(AnimPrimitives);
for (auto It = AnimPrimitives.begin(); It != AnimPrimitives.end(); It++)
{
const CAnimPrimitive& rkPrim = *It;
CAnimation *pAnim = rkPrim.Animation();
if (pAnim)
{
ApplyGeneratedName(pAnim->Entry(), SetDir, rkPrim.Name().ToUTF16());
CAnimEventData *pEvents = pAnim->EventData();
if (pEvents)
ApplyGeneratedName(pEvents->Entry(), SetDir, rkPrim.Name().ToUTF16());
}
}
}
#endif
#if PROCESS_STRINGS
// Generate string names
for (TResourceIterator<eStringTable> It(pStore); It; ++It)
{
CStringTable *pString = (CStringTable*) It->Load();
if (pString->Entry()->IsNamed()) continue;
TWideString String;
for (u32 iStr = 0; iStr < pString->NumStrings() && String.IsEmpty(); iStr++)
String = CStringTable::StripFormatting( pString->String("ENGL", iStr) ).Trimmed();
if (!String.IsEmpty())
{
TWideString Name = String.SubString(0, Math::Min<u32>(String.Size(), 50)).Trimmed();
Name.Replace(L"\n", L" ");
while (Name.EndsWith(L".") || TWideString::IsWhitespace(Name.Back()))
Name = Name.ChopBack(1);
ApplyGeneratedName(pString->Entry(), pString->Entry()->DirectoryPath(), Name);
}
}
#endif
#if PROCESS_SCANS
// Generate scan names
for (TResourceIterator<eScan> It(pStore); It; ++It)
{
if (It->IsNamed()) continue;
CScan *pScan = (CScan*) It->Load();
TWideString ScanName;
if (pProj->Game() >= eEchoesDemo)
{
CAssetID DisplayAsset = pScan->LogbookDisplayAssetID();
CResourceEntry *pEntry = pStore->FindEntry(DisplayAsset);
if (pEntry && pEntry->IsNamed()) ScanName = pEntry->Name();
}
if (ScanName.IsEmpty())
{
CStringTable *pString = pScan->ScanText();
if (pString) ScanName = pString->Entry()->Name();
}
ApplyGeneratedName(pScan->Entry(), It->DirectoryPath(), ScanName);
if (!ScanName.IsEmpty() && pProj->Game() <= ePrime)
{
CAssetID FrameID = pScan->GuiFrame();
CResourceEntry *pEntry = pStore->FindEntry(FrameID);
if (pEntry) ApplyGeneratedName(pEntry, pEntry->DirectoryPath(), L"ScanFrame");
for (u32 iImg = 0; iImg < 4; iImg++)
{
CAssetID ImageID = pScan->ScanImage(iImg);
CResourceEntry *pImgEntry = pStore->FindEntry(ImageID);
if (pImgEntry) ApplyGeneratedName(pImgEntry, pImgEntry->DirectoryPath(), TWideString::Format(L"%s_Image%d", *ScanName, iImg));
}
}
}
#endif
#if PROCESS_FONTS
// Generate font names
for (TResourceIterator<eFont> It(pStore); It; ++It)
{
CFont *pFont = (CFont*) It->Load();
if (pFont)
{
ApplyGeneratedName(pFont->Entry(), pFont->Entry()->DirectoryPath(), pFont->FontName().ToUTF16());
CTexture *pFontTex = pFont->Texture();
if (pFontTex)
ApplyGeneratedName(pFontTex->Entry(), pFont->Entry()->DirectoryPath(), pFont->Entry()->Name());
}
}
#endif
pStore->ConditionalSaveStore();
}

View File

@ -0,0 +1,8 @@
#ifndef ASSETNAMEGENERATION
#define ASSETNAMEGENERATION
class CGameProject;
void GenerateAssetNames(CGameProject *pProj);
#endif // ASSETNAMEGENERATION

View File

@ -0,0 +1,95 @@
#ifndef CASSETNAMEMAP
#define CASSETNAMEMAP
#include "CResourceIterator.h"
#include "CResourceStore.h"
#include <Common/CAssetID.h>
#include <Common/Serialization/XML.h>
#include <map>
#include <memory>
const TString gkAssetListDir = "..\\resources\\list\\";
struct SAssetNameInfo
{
TWideString Name;
TWideString Directory;
void Serialize(IArchive& rArc)
{
rArc << SERIAL_AUTO(Name) << SERIAL_AUTO(Directory);
}
};
class CAssetNameMap
{
typedef std::map<CAssetID, SAssetNameInfo> TAssetMap;
std::shared_ptr<TAssetMap> mpMap;
void Serialize(IArchive& rArc)
{
rArc << SERIAL_CONTAINER("AssetNameMap", *mpMap.get(), "Asset");
}
public:
CAssetNameMap()
{
mpMap = std::make_shared<TAssetMap>(TAssetMap());
}
void GetNameInfo(CAssetID ID, TString& rOutDirectory, TString& rOutName)
{
auto It = mpMap->find(ID);
if (It != mpMap->end())
{
SAssetNameInfo& rInfo = It->second;
rOutName = rInfo.Name;
rOutDirectory = rInfo.Directory;
}
else
{
rOutDirectory = "Uncategorized\\";
rOutName = ID.ToString();
}
}
static TString GetAssetListPath(EGame Game)
{
return gkAssetListDir + "AssetList" + GetGameShortName(Game) + ".xml";
}
static CAssetNameMap LoadAssetNames(EGame Game)
{
TString ListPath = GetAssetListPath(Game);
CXMLReader Reader(ListPath);
CAssetNameMap Map;
Map.Serialize(Reader);
return Map;
}
static void SaveAssetNames(CResourceStore *pStore = gpResourceStore)
{
CAssetNameMap Map;
for (CResourceIterator It(pStore); It; ++It)
{
if (It->IsCategorized() || It->IsNamed())
{
CAssetID ID = It->ID();
TWideString Name = It->Name();
TWideString Directory = It->Directory()->FullPath();
(*Map.mpMap)[ID] = SAssetNameInfo { Name, Directory };
}
}
TString ListPath = GetAssetListPath(pStore->Game());
CXMLWriter Writer(ListPath, "AssetList", 0, pStore->Game());
Map.Serialize(Writer);
}
};
#endif // CASSETNAMEMAP

View File

@ -14,7 +14,7 @@
#define COPY_DISC_DATA 1
#define LOAD_PAKS 1
#define SAVE_PACKAGE_DEFINITIONS 1
#define EXPORT_WORLDS 1
#define USE_ASSET_NAME_MAP 0
#define EXPORT_COOKED 1
CGameExporter::CGameExporter(const TString& rkInputDir, const TString& rkOutputDir)
@ -50,13 +50,15 @@ bool CGameExporter::Export()
mContentDir = mpStore->RawDir(false);
mCookedDir = mpStore->CookedDir(false);
#if USE_ASSET_NAME_MAP
mNameMap = CAssetNameMap::LoadAssetNames(mGame);
#endif
// Export game data
CResourceStore *pOldStore = gpResourceStore;
gpResourceStore = mpStore;
LoadAssetList();
LoadPaks();
ExportWorlds();
ExportCookedResources();
mpProject->AudioManager()->LoadAssets();
ExportResourceEditorData();
@ -128,57 +130,6 @@ void CGameExporter::CopyDiscData()
ASSERT(mGame != eUnknownGame);
}
void CGameExporter::LoadAssetList()
{
SCOPED_TIMER(LoadAssetList);
// Determine the asset list to use
TString ListFile = "../resources/list/AssetList";
switch (mGame)
{
case ePrimeDemo: ListFile += "MP1Demo"; break;
case ePrime: ListFile += "MP1"; break;
case eEchoesDemo: ListFile += "MP2Demo"; break;
case eEchoes: ListFile += "MP2"; break;
case eCorruptionProto: ListFile += "MP3Proto"; break;
case eCorruption: ListFile += "MP3"; break;
case eReturns: ListFile += "DKCR"; break;
default: ASSERT(false);
}
ListFile += ".xml";
// Load list
tinyxml2::XMLDocument List;
List.LoadFile(*ListFile);
if (List.Error())
{
Log::Error("Couldn't open asset list: " + ListFile);
return;
}
tinyxml2::XMLElement *pRoot = List.FirstChildElement("AssetList");
tinyxml2::XMLElement *pAsset = pRoot->FirstChildElement("Asset");
while (pAsset)
{
CAssetID ResourceID = TString(pAsset->Attribute("ID")).ToInt64(16);
tinyxml2::XMLElement *pDir = pAsset->FirstChildElement("Dir");
TString Dir = pDir ? pDir->GetText() : "";
tinyxml2::XMLElement *pName = pAsset->FirstChildElement("Name");
TString Name = pName ? pName->GetText() : "";
if (!Dir.EndsWith("/") && !Dir.EndsWith("\\")) Dir.Append("\\");
SetResourcePath(ResourceID, Dir.ToUTF16(), Name.ToUTF16());
pAsset = pAsset->NextSiblingElement("Asset");
}
}
// ************ RESOURCE LOADING ************
void CGameExporter::LoadPaks()
{
@ -221,7 +172,6 @@ void CGameExporter::LoadPaks()
u32 NameLen = Pak.ReadLong();
TString Name = Pak.ReadString(NameLen);
pCollection->AddResource(Name, ResID, ResType);
SetResourcePath(ResID, PakName + L"\\", Name.ToUTF16());
}
u32 NumResources = Pak.ReadLong();
@ -296,7 +246,6 @@ void CGameExporter::LoadPaks()
CFourCC ResType = Pak.ReadLong();
CAssetID ResID(Pak, mGame);
pCollection->AddResource(Name, ResID, ResType);
SetResourcePath(ResID, PakName + L"\\", Name.ToUTF16());
}
}
@ -448,84 +397,6 @@ void CGameExporter::LoadResource(const SResourceInstance& rkResource, std::vecto
}
}
void CGameExporter::ExportWorlds()
{
#if EXPORT_WORLDS
SCOPED_TIMER(ExportWorlds);
for (u32 iPak = 0; iPak < mpProject->NumPackages(); iPak++)
{
CPackage *pPak = mpProject->PackageByIndex(iPak);
// Get output path. DKCR paks are stored in a Worlds folder so we should get the path relative to that so we don't have Worlds\Worlds\.
// Other games have all paks in the game root dir so we're fine just taking the original root dir-relative directory.
TWideString PakPath = pPak->Path();
TWideString GameWorldsDir = PakPath.GetParentDirectoryPath(L"Worlds", false);
if (!GameWorldsDir.IsEmpty())
PakPath = FileUtil::MakeRelative(PakPath, GameWorldsDir);
// Note since there's no collections in the cooked data we're guaranteed that every pak will have exactly one collection.
CResourceCollection *pCollection = pPak->CollectionByIndex(0);
for (u32 iRes = 0; iRes < pCollection->NumResources(); iRes++)
{
const SNamedResource& rkRes = pCollection->ResourceByIndex(iRes);
if (rkRes.Type == "MLVL" && !rkRes.Name.EndsWith("NODEPEND"))
{
// Load world
CWorld *pWorld = (CWorld*) mpStore->LoadResource(rkRes.ID, rkRes.Type);
if (!pWorld)
{
Log::Error("Couldn't load world " + rkRes.Name + " from package " + pPak->Name() + "; unable to export");
continue;
}
// Export world
TWideString Name = rkRes.Name.ToUTF16();
TWideString WorldDir = mWorldsDirName + PakPath + FileUtil::SanitizeName(Name, true) + L"\\";
FileUtil::CreateDirectory(mCookedDir + WorldDir);
SResourceInstance *pInst = FindResourceInstance(rkRes.ID);
ASSERT(pInst != nullptr);
SetResourcePath(rkRes.ID, WorldDir, Name);
ExportResource(*pInst);
// Export areas
for (u32 iArea = 0; iArea < pWorld->NumAreas(); iArea++)
{
// Determine area names
TWideString InternalAreaName = pWorld->AreaInternalName(iArea).ToUTF16();
bool HasInternalName = !InternalAreaName.IsEmpty();
if (!HasInternalName) InternalAreaName = TWideString::FromInt32(iArea, 2, 10);
TWideString GameAreaName;
CStringTable *pTable = pWorld->AreaName(iArea);
if (pTable) GameAreaName = pTable->String("ENGL", 0);
if (GameAreaName.IsEmpty()) GameAreaName = InternalAreaName;
// Export area
TWideString AreaDir = WorldDir + TWideString::FromInt32(iArea, 2, 10) + L"_" + FileUtil::SanitizeName(GameAreaName, true) + L"\\";
FileUtil::CreateDirectory(mCookedDir + AreaDir);
CAssetID AreaID = pWorld->AreaResourceID(iArea);
SResourceInstance *pInst = FindResourceInstance(AreaID);
ASSERT(pInst != nullptr);
SetResourcePath(AreaID, AreaDir, GameAreaName);
ExportResource(*pInst);
}
}
}
mpStore->DestroyUnreferencedResources();
}
#endif
}
void CGameExporter::ExportCookedResources()
{
{
@ -597,21 +468,10 @@ void CGameExporter::ExportResource(SResourceInstance& rRes)
std::vector<u8> ResourceData;
LoadResource(rRes, ResourceData);
// Determine output path
SResourcePath *pPath = FindResourcePath(rRes.ResourceID);
TWideString OutName, OutDir;
if (pPath)
{
OutName = pPath->Name;
OutDir = pPath->Dir;
}
if (OutName.IsEmpty()) OutName = rRes.ResourceID.ToString().ToUTF16();
if (OutDir.IsEmpty()) OutDir = L"Uncategorized\\";
// Register resource and write to file
CResourceEntry *pEntry = mpStore->RegisterResource(rRes.ResourceID, CResource::ResTypeForExtension(rRes.ResourceType), OutDir, OutName);
TString Directory, Name;
mNameMap.GetNameInfo(rRes.ResourceID, Directory, Name);
CResourceEntry *pEntry = mpStore->RegisterResource(rRes.ResourceID, CResource::ResTypeForExtension(rRes.ResourceType), Directory, Name);
#if EXPORT_COOKED
// Save cooked asset

View File

@ -1,6 +1,7 @@
#ifndef CGAMEEXPORTER_H
#define CGAMEEXPORTER_H
#include "CAssetNameMap.h"
#include "CGameProject.h"
#include "CResourceStore.h"
#include <Common/CAssetID.h>
@ -28,6 +29,7 @@ class CGameExporter
// Resources
TWideStringList mPaks;
std::map<CAssetID, bool> mAreaDuplicateMap;
CAssetNameMap mNameMap;
struct SResourceInstance
{
@ -41,13 +43,6 @@ class CGameExporter
};
std::map<CAssetID, SResourceInstance> mResourceMap;
struct SResourcePath
{
TWideString Dir;
TWideString Name;
};
std::map<CAssetID, SResourcePath> mResourcePaths;
public:
CGameExporter(const TString& rkInputDir, const TString& rkOutputDir);
bool Export();
@ -55,10 +50,8 @@ public:
protected:
void CopyDiscData();
void LoadAssetList();
void LoadPaks();
void LoadResource(const SResourceInstance& rkResource, std::vector<u8>& rBuffer);
void ExportWorlds();
void ExportCookedResources();
void ExportResourceEditorData();
void ExportResource(SResourceInstance& rRes);
@ -70,18 +63,6 @@ protected:
auto Found = mResourceMap.find(IntegralID);
return (Found == mResourceMap.end() ? nullptr : &Found->second);
}
inline SResourcePath* FindResourcePath(const CAssetID& rkID)
{
u64 IntegralID = rkID.ToLongLong();
auto Found = mResourcePaths.find(IntegralID);
return (Found == mResourcePaths.end() ? nullptr : &Found->second);
}
inline void SetResourcePath(const CAssetID& rkID, const TWideString& rkDir, const TWideString& rkName)
{
mResourcePaths[rkID] = SResourcePath { rkDir, rkName };
}
};
#endif // CGAMEEXPORTER_H

View File

@ -7,6 +7,8 @@ CGameProject *CGameProject::mspActiveProject = nullptr;
CGameProject::~CGameProject()
{
ASSERT(!mpResourceStore->IsDirty());
if (IsActive())
mspActiveProject = nullptr;

View File

@ -47,9 +47,9 @@ public:
, mProjectRoot(rkProjRootDir)
, mResourceDBPath(L"ResourceDB.rdb")
{
mProjectRoot.Replace(L"/", L"\\");
mpResourceStore = new CResourceStore(this);
mpAudioManager = new CAudioManager(this);
mProjectRoot.Replace(L"/", L"\\");
}
CGameProject(CGameExporter *pExporter, const TWideString& rkProjRootDir, EGame Game)
@ -58,9 +58,9 @@ public:
, mProjectRoot(rkProjRootDir)
, mResourceDBPath(L"ResourceDB.rdb")
{
mProjectRoot.Replace(L"/", L"\\");
mpResourceStore = new CResourceStore(this, pExporter, L"Content\\", L"Cooked\\", Game);
mpAudioManager = new CAudioManager(this);
mProjectRoot.Replace(L"/", L"\\");
}
~CGameProject();
@ -95,6 +95,4 @@ public:
static inline CGameProject* ActiveProject() { return mspActiveProject; }
};
extern CGameProject *gpProject;
#endif // CGAMEPROJECT_H

View File

@ -196,9 +196,10 @@ bool CResourceEntry::Save(bool SkipCacheSave /*= false*/)
// Resource has been saved, now update dependencies + cache file
UpdateDependencies();
mpStore->SetCacheDataDirty();
if (!SkipCacheSave)
mpStore->SaveCacheFile();
mpStore->ConditionalSaveStore();
if (ShouldCollectGarbage)
mpStore->DestroyUnreferencedResources();
@ -277,40 +278,92 @@ bool CResourceEntry::Unload()
return true;
}
void CResourceEntry::Move(const TWideString& rkDir, const TWideString& rkName)
bool CResourceEntry::CanMoveTo(const TWideString& rkDir, const TWideString& rkName)
{
// Transient resources can't be moved
if (IsTransient()) return false;
// Validate that the path/name are valid file paths
if (!FileUtil::IsValidPath(rkDir, true) || !FileUtil::IsValidName(rkName, false)) return false;
// We need to validate the path isn't taken already - either the directory doesn't exist, or doesn't have a resource by this name
CVirtualDirectory *pDir = mpStore->GetVirtualDirectory(rkDir, false, false);
if (pDir && pDir->FindChildResource(rkName, mType)) return false;
// All checks are true
return true;
}
bool CResourceEntry::Move(const TWideString& rkDir, const TWideString& rkName)
{
if (!CanMoveTo(rkDir, rkName)) return false;
// Store old paths
CVirtualDirectory *pOldDir = mpDirectory;
TWideString OldName = mName;
TString OldCookedPath = CookedAssetPath();
TString OldRawPath = RawAssetPath();
// Set new directory and name
bool HasDirectory = mpDirectory != nullptr;
CVirtualDirectory *pNewDir = mpStore->GetVirtualDirectory(rkDir, IsTransient(), true);
if (pNewDir == mpDirectory && rkName == mName) return false;
if (pNewDir != mpDirectory)
// Check if we can legally move to this spot
ASSERT(pNewDir->FindChildResource(rkName, mType) == nullptr); // this check should be guaranteed to pass due to CanMoveTo() having already checked it
mpDirectory = pNewDir;
mName = rkName;
TString NewCookedPath = CookedAssetPath();
TString NewRawPath = RawAssetPath();
// If the old/new paths are the same then we should have already exited as CanMoveTo() should have returned false
ASSERT(OldCookedPath != NewCookedPath && OldRawPath != NewRawPath);
// The cooked/raw asset paths should not exist right now!!!
bool FSMoveSuccess = false;
if (!HasRawVersion() && !HasCookedVersion())
{
if (mpDirectory)
mpDirectory->RemoveChildResource(this);
mpDirectory = pNewDir;
FSMoveSuccess = true;
if (FileUtil::Exists(OldRawPath))
{
FSMoveSuccess = FileUtil::CopyFile(OldRawPath, NewRawPath);
if (!FSMoveSuccess) FileUtil::DeleteFile(NewRawPath);
}
if (FSMoveSuccess && FileUtil::Exists(OldCookedPath))
{
FSMoveSuccess = FileUtil::CopyFile(OldCookedPath, NewCookedPath);
if (!FSMoveSuccess) FileUtil::DeleteFile(NewCookedPath);
}
}
if (mName != rkName)
ASSERT(mpDirectory->FindChildResource(rkName) == nullptr);
mName = rkName;
mCachedUppercaseName = rkName.ToUpper();
// Move files
if (HasDirectory)
// If we succeeded, finish the move
if (FSMoveSuccess)
{
TString CookedPath = CookedAssetPath();
TString RawPath = RawAssetPath();
if (mpDirectory != pOldDir)
{
FSMoveSuccess = pOldDir->RemoveChildResource(this);
ASSERT(FSMoveSuccess == true); // this shouldn't be able to fail
mpDirectory->AddChild(L"", this);
mpStore->ConditionalDeleteDirectory(pOldDir);
}
if (FileUtil::Exists(OldCookedPath) && CookedPath != OldCookedPath)
FileUtil::MoveFile(OldCookedPath, CookedPath);
mpStore->SetDatabaseDirty();
mCachedUppercaseName = rkName.ToUpper();
FileUtil::DeleteFile(OldRawPath);
FileUtil::DeleteFile(OldCookedPath);
return true;
}
if (FileUtil::Exists(OldRawPath) && RawPath != OldRawPath)
FileUtil::MoveFile(OldRawPath, RawPath);
// Otherwise, revert changes and let the caller know the move failed
else
{
mpDirectory = pOldDir;
mName = OldName;
mpStore->ConditionalDeleteDirectory(pNewDir);
return false;
}
}

View File

@ -14,12 +14,13 @@ class CDependencyTree;
enum EResEntryFlag
{
eREF_NeedsRecook = 0x1, // Resource has been updated but not recooked
eREF_Transient = 0x2, // Resource is transient (not part of a game project resource DB)
eREF_HasThumbnail = 0x4, // Resource has a unique thumbnail
eREF_ThumbnailLoaded = 0x8, // Resource thumbnail is currently in memory
eREF_NeedsRecook = 0x00000001, // Resource has been updated but not recooked
eREF_Transient = 0x00000002, // Resource is transient (not part of a game project resource DB)
eREF_HasThumbnail = 0x00000004, // Resource has a unique thumbnail
eREF_ThumbnailLoaded = 0x00000008, // Resource thumbnail is currently in memory
eREF_Hidden = 0x00000010, // Resource is hidden, doesn't show up in resource browser
// Flags that save to the cache file
eREF_SavedFlags = eREF_NeedsRecook | eREF_HasThumbnail
eREF_SavedFlags = eREF_NeedsRecook | eREF_HasThumbnail | eREF_Hidden
};
DECLARE_FLAGS(EResEntryFlag, FResEntryFlags)
@ -61,24 +62,30 @@ public:
CResource* Load();
CResource* LoadCooked(IInputStream& rInput);
bool Unload();
void Move(const TWideString& rkDir, const TWideString& rkName);
bool CanMoveTo(const TWideString& rkDir, const TWideString& rkName);
bool Move(const TWideString& rkDir, const TWideString& rkName);
void AddToProject(const TWideString& rkDir, const TWideString& rkName);
void RemoveFromProject();
// Accessors
void SetDirty() { mFlags.SetFlag(eREF_NeedsRecook); }
void SetHidden(bool Hidden) { Hidden ? mFlags.SetFlag(eREF_Hidden) : mFlags.ClearFlag(eREF_Hidden); }
inline bool IsLoaded() const { return mpResource != nullptr; }
inline bool IsCategorized() const { return mpDirectory && mpDirectory->FullPath() != L"Uncategorized\\"; }
inline bool IsNamed() const { return mName != mID.ToString().ToUTF16(); }
inline CResource* Resource() const { return mpResource; }
inline CResourceStore* ResourceStore() const { return mpStore; }
inline CDependencyTree* Dependencies() const { return mpDependencies; }
inline CAssetID ID() const { return mID; }
inline EGame Game() const { return mGame; }
inline CVirtualDirectory* Directory() const { return mpDirectory; }
inline TWideString DirectoryPath() const { return mpDirectory->FullPath(); }
inline TWideString Name() const { return mName; }
inline const TWideString& UppercaseName() const { return mCachedUppercaseName; }
inline EResType ResourceType() const { return mType; }
inline bool IsTransient() const { return mFlags.HasFlag(eREF_Transient); }
inline bool IsHidden() const { return mFlags.HasFlag(eREF_Hidden); }
protected:
CResource* InternalLoad(IInputStream& rInput);

View File

@ -66,14 +66,14 @@ public:
}
};
template<class ResType>
template<EResType ResType>
class TResourceIterator : public CResourceIterator
{
public:
TResourceIterator(CResourceStore *pStore = gpResourceStore)
: CResourceIterator(pStore)
{
if (mpCurEntry->ResourceType() != ResType::StaticType())
if (mpCurEntry->ResourceType() != ResType)
Next();
}
@ -81,7 +81,7 @@ public:
{
do {
CResourceIterator::Next();
} while (mpCurEntry && mpCurEntry->ResourceType() != ResType::StaticType());
} while (mpCurEntry && mpCurEntry->ResourceType() != ResType);
return mpCurEntry;
}

View File

@ -18,6 +18,8 @@ CResourceStore::CResourceStore(const TWideString& rkDatabasePath)
: mpProj(nullptr)
, mGame(eUnknownGame)
, mpExporter(nullptr)
, mDatabaseDirty(false)
, mCacheFileDirty(false)
{
mpDatabaseRoot = new CVirtualDirectory();
mDatabasePath = FileUtil::MakeAbsolute(rkDatabasePath.GetFileDirectory());
@ -30,6 +32,8 @@ CResourceStore::CResourceStore(CGameProject *pProject, CGameExporter *pExporter,
, mRawDir(rkRawDir)
, mCookedDir(rkCookedDir)
, mpExporter(pExporter)
, mDatabaseDirty(false)
, mCacheFileDirty(false)
{
SetProject(pProject);
}
@ -39,6 +43,8 @@ CResourceStore::CResourceStore(CGameProject *pProject)
, mGame(eUnknownGame)
, mpDatabaseRoot(nullptr)
, mpExporter(nullptr)
, mDatabaseDirty(false)
, mCacheFileDirty(false)
{
SetProject(pProject);
}
@ -122,6 +128,7 @@ void CResourceStore::SaveResourceDatabase()
TString Path = DatabasePath().ToUTF8();
CXMLWriter Writer(Path, "ResourceDB", 0, mGame);
SerializeResourceDatabase(Writer);
mDatabaseDirty = false;
}
void CResourceStore::LoadCacheFile()
@ -208,6 +215,13 @@ void CResourceStore::SaveCacheFile()
CacheFile.Seek(ResCountOffset, SEEK_SET);
CacheFile.WriteLong(ResCount);
mCacheFileDirty = false;
}
void CResourceStore::ConditionalSaveStore()
{
if (mDatabaseDirty) SaveResourceDatabase();
if (mCacheFileDirty) SaveCacheFile();
}
void CResourceStore::SetProject(CGameProject *pProj)
@ -298,6 +312,21 @@ CVirtualDirectory* CResourceStore::GetVirtualDirectory(const TWideString& rkPath
else return nullptr;
}
void CResourceStore::ConditionalDeleteDirectory(CVirtualDirectory *pDir)
{
if (pDir->IsEmpty())
{
// If this directory is part of the project, then we should delete the corresponding filesystem directories
if (pDir->GetRoot() == mpDatabaseRoot)
{
FileUtil::DeleteDirectory(RawDir(false) + pDir->FullPath());
FileUtil::DeleteDirectory(CookedDir(false) + pDir->FullPath());
}
pDir->Parent()->RemoveChildDirectory(pDir);
}
}
CResourceEntry* CResourceStore::FindEntry(const CAssetID& rkID) const
{
if (!rkID.IsValid()) return nullptr;
@ -316,7 +345,7 @@ bool CResourceStore::IsResourceRegistered(const CAssetID& rkID) const
return FindEntry(rkID) != nullptr;
}
CResourceEntry* CResourceStore::RegisterResource(const CAssetID& rkID, EResType Type, const TWideString& rkDir, const TWideString& rkFileName)
CResourceEntry* CResourceStore::RegisterResource(const CAssetID& rkID, EResType Type, const TWideString& rkDir, const TWideString& rkName)
{
CResourceEntry *pEntry = FindEntry(rkID);
@ -325,16 +354,16 @@ CResourceEntry* CResourceStore::RegisterResource(const CAssetID& rkID, EResType
if (pEntry->IsTransient())
{
ASSERT(pEntry->ResourceType() == Type);
pEntry->AddToProject(rkDir, rkFileName);
pEntry->AddToProject(rkDir, rkName);
}
else
Log::Error("Attempted to register resource that's already tracked in the database: " + rkID.ToString() + " / " + rkDir.ToUTF8() + " / " + rkFileName.ToUTF8());
Log::Error("Attempted to register resource that's already tracked in the database: " + rkID.ToString() + " / " + rkDir.ToUTF8() + " / " + rkName.ToUTF8());
}
else
{
pEntry = new CResourceEntry(this, rkID, rkDir, rkFileName.GetFileName(false), Type);
pEntry = new CResourceEntry(this, rkID, rkDir, rkName, Type);
mResourceEntries[rkID] = pEntry;
}

View File

@ -25,6 +25,8 @@ class CResourceStore
std::vector<CVirtualDirectory*> mTransientRoots;
std::map<CAssetID, CResourceEntry*> mResourceEntries;
std::map<CAssetID, CResourceEntry*> mLoadedResources;
bool mDatabaseDirty;
bool mCacheFileDirty;
// Directory paths
TWideString mDatabasePath;
@ -54,12 +56,14 @@ public:
void SaveResourceDatabase();
void LoadCacheFile();
void SaveCacheFile();
void ConditionalSaveStore();
void SetProject(CGameProject *pProj);
void CloseProject();
CVirtualDirectory* GetVirtualDirectory(const TWideString& rkPath, bool Transient, bool AllowCreate);
void ConditionalDeleteDirectory(CVirtualDirectory *pDir);
bool IsResourceRegistered(const CAssetID& rkID) const;
CResourceEntry* RegisterResource(const CAssetID& rkID, EResType Type, const TWideString& rkDir, const TWideString& rkFileName);
CResourceEntry* RegisterResource(const CAssetID& rkID, EResType Type, const TWideString& rkDir, const TWideString& rkName);
CResourceEntry* FindEntry(const CAssetID& rkID) const;
CResourceEntry* FindEntry(const TWideString& rkPath) const;
CResourceEntry* RegisterTransientResource(EResType Type, const TWideString& rkDir = L"", const TWideString& rkFileName = L"");
@ -84,6 +88,10 @@ public:
inline CVirtualDirectory* RootDirectory() const { return mpDatabaseRoot; }
inline u32 NumTotalResources() const { return mResourceEntries.size(); }
inline u32 NumLoadedResources() const { return mLoadedResources.size(); }
inline bool IsDirty() const { return mDatabaseDirty || mCacheFileDirty; }
inline void SetDatabaseDirty() { mDatabaseDirty = true; }
inline void SetCacheDataDirty() { mCacheFileDirty = true; }
};
extern CResourceStore *gpResourceStore;

View File

@ -1,6 +1,7 @@
#include "CVirtualDirectory.h"
#include "CResourceEntry.h"
#include "CResourceStore.h"
#include "Core/Resource/CResource.h"
#include <algorithm>
CVirtualDirectory::CVirtualDirectory()
@ -33,7 +34,7 @@ bool CVirtualDirectory::IsEmpty() const
TWideString CVirtualDirectory::FullPath() const
{
return (mpParent && !mpParent->IsRoot() ? mpParent->FullPath() + L'\\' + mName + L"\\" : mName + L"\\");
return (mpParent && !mpParent->IsRoot() ? mpParent->FullPath() + mName + L'\\' : mName + L'\\');
}
CVirtualDirectory* CVirtualDirectory::GetRoot()
@ -50,7 +51,7 @@ CVirtualDirectory* CVirtualDirectory::FindChildDirectory(const TWideString& rkNa
{
CVirtualDirectory *pChild = mSubdirectories[iSub];
if (pChild->Name() == DirName)
if (pChild->Name().CaseInsensitiveCompare(DirName))
{
if (SlashIdx == -1)
return pChild;
@ -81,7 +82,7 @@ CVirtualDirectory* CVirtualDirectory::FindChildDirectory(const TWideString& rkNa
CResourceEntry* CVirtualDirectory::FindChildResource(const TWideString& rkPath)
{
TWideString Dir = rkPath.GetFileDirectory();
TWideString Name = rkPath.GetFileName(false);
TWideString Name = rkPath.GetFileName();
if (!Dir.IsEmpty())
{
@ -89,13 +90,22 @@ CResourceEntry* CVirtualDirectory::FindChildResource(const TWideString& rkPath)
if (pDir) return pDir->FindChildResource(Name);
}
else
else if (!Name.IsEmpty())
{
for (u32 iRes = 0; iRes < mResources.size(); iRes++)
{
if (mResources[iRes]->Name() == Name)
return mResources[iRes];
}
TWideString Ext = Name.GetFileExtension();
EResType Type = CResource::ResTypeForExtension(Ext);
return FindChildResource(Name.GetFileName(false), Type);
}
return nullptr;
}
CResourceEntry* CVirtualDirectory::FindChildResource(const TWideString& rkName, EResType Type)
{
for (u32 iRes = 0; iRes < mResources.size(); iRes++)
{
if (rkName.CaseInsensitiveCompare(mResources[iRes]->Name()) && mResources[iRes]->ResourceType() == Type)
return mResources[iRes];
}
return nullptr;
@ -168,10 +178,6 @@ bool CVirtualDirectory::RemoveChildResource(CResourceEntry *pEntry)
if (*It == pEntry)
{
mResources.erase(It);
if (mpParent && IsEmpty())
mpParent->RemoveChildDirectory(this);
return true;
}
}

View File

@ -2,6 +2,7 @@
#define CVIRTUALDIRECTORY
/* Virtual directory system used to look up resources by their location in the filesystem. */
#include "Core/Resource/EResType.h"
#include <Common/AssertMacro.h>
#include <Common/TString.h>
#include <vector>
@ -26,6 +27,7 @@ public:
CVirtualDirectory* GetRoot();
CVirtualDirectory* FindChildDirectory(const TWideString& rkName, bool AllowCreate);
CResourceEntry* FindChildResource(const TWideString& rkPath);
CResourceEntry* FindChildResource(const TWideString& rkName, EResType Type);
void AddChild(const TWideString& rkPath, CResourceEntry *pEntry);
bool RemoveChildDirectory(CVirtualDirectory *pSubdir);
bool RemoveChildResource(CResourceEntry *pEntry);

View File

@ -129,6 +129,12 @@ public:
return nullptr;
}
void GetUniquePrimitives(std::set<CAnimPrimitive>& rPrimSet) const
{
for (u32 iAnim = 0; iAnim < mAnimPrimitives.size(); iAnim++)
rPrimSet.insert(mAnimPrimitives[iAnim]);
}
// Accessors
inline u32 NumCharacters() const { return mCharacters.size(); }
inline u32 NumAnimations() const { return mAnimations.size(); }

View File

@ -84,6 +84,7 @@ public:
// Inline Accessors
inline u32 WorldIndex() const { return mWorldIndex; }
inline CTransform4f Transform() const { return mTransform; }
inline CMaterialSet* Materials() const { return mpMaterialSet; }
inline u32 NumWorldModels() const { return mWorldModels.size(); }
inline u32 NumStaticModels() const { return mStaticWorldModels.size(); }
inline CModel* TerrainModel(u32 iMdl) const { return mWorldModels[iMdl]; }
@ -94,7 +95,9 @@ public:
inline u32 NumLightLayers() const { return mLightLayers.size(); }
inline u32 NumLights(u32 LayerIndex) const { return (LayerIndex < mLightLayers.size() ? mLightLayers[LayerIndex].size() : 0); }
inline CLight* Light(u32 LayerIndex, u32 LightIndex) const { return mLightLayers[LayerIndex][LightIndex]; }
inline CAssetID PathID() const { return mPathID; }
inline CPoiToWorld* PoiToWorldMap() const { return mpPoiToWorldMap; }
inline CAssetID PortalAreaID() const { return mPortalAreaID; }
inline CAABox AABox() const { return mAABox; }
inline void SetWorldIndex(u32 NewWorldIndex) { mWorldIndex = NewWorldIndex; }

View File

@ -65,6 +65,10 @@ public:
CVector2f Position = CVector2f(0,0),
CColor FillColor = CColor::skWhite, CColor StrokeColor = CColor::skBlack,
u32 FontSize = CFONT_DEFAULT_SIZE);
// Accessors
inline TString FontName() const { return mFontName; }
inline CTexture* Texture() const { return mpFontTexture; }
private:
void InitBuffers();
};

View File

@ -14,7 +14,7 @@ u32 GetGameTypeID(EGame Game, EResType ResType)
// ************ STATIC ************
EResType CResource::ResTypeForExtension(CFourCC Extension)
{
auto Find = gExtensionTypeMap.find(Extension.ToLong());
auto Find = gExtensionTypeMap.find(Extension.ToUpper().ToLong());
if (Find == gExtensionTypeMap.end())
{

View File

@ -98,10 +98,14 @@ public:
return pTree;
}
CStringTable* ScanText() const { return mpStringTable; }
bool IsImportant() const { return mIsImportant; }
bool IsSlow() const { return mIsSlow; }
ELogbookCategory LogbookCategory() const { return mCategory; }
// Accessors
inline CStringTable* ScanText() const { return mpStringTable; }
inline bool IsImportant() const { return mIsImportant; }
inline bool IsSlow() const { return mIsSlow; }
inline ELogbookCategory LogbookCategory() const { return mCategory; }
inline CAssetID GuiFrame() const { return mFrameID; }
inline CAssetID ScanImage(u32 ImgIndex) const { return mScanImageTextures[ImgIndex]; }
inline CAssetID LogbookDisplayAssetID() const { return (mLogbookAnimParams.ID().IsValid() ? mLogbookAnimParams.ID() : mLogbookModel); }
};
#endif // CSCAN_H

View File

@ -126,6 +126,39 @@ public:
return pTree;
}
static TWideString StripFormatting(const TWideString& rkStr)
{
TWideString Out = rkStr;
int TagStart = -1;
for (u32 iChr = 0; iChr < Out.Size(); iChr++)
{
if (Out[iChr] == L'&')
{
if (TagStart == -1)
TagStart = iChr;
else
{
Out.Remove(TagStart, 1);
TagStart = -1;
iChr--;
}
}
else if (TagStart != -1 && Out[iChr] == L';')
{
int TagEnd = iChr + 1;
int TagLen = TagEnd - TagStart;
Out.Remove(TagStart, TagLen);
iChr = TagStart - 1;
TagStart = -1;
}
}
return Out;
}
};
#endif // CSTRINGTABLE_H

View File

@ -4,6 +4,7 @@
#include "Editor/WorldEditor/CWorldEditor.h"
#include <Common/AssertMacro.h>
#include <Common/CTimer.h>
#include <Core/GameProject/CGameProject.h>
CEditorApplication::CEditorApplication(int& rArgc, char **ppArgv)
: QApplication(rArgc, ppArgv)
@ -26,6 +27,15 @@ void CEditorApplication::TickEditors()
mLastUpdate = CTimer::GlobalTime();
double DeltaTime = mLastUpdate - LastUpdate;
// The resource store should NOT be dirty at the beginning of a tick - this indicates we forgot to save it after updating somewhere
if (gpResourceStore && gpResourceStore->IsDirty())
{
Log::Error("ERROR: Resource store is dirty at the beginning of a tick! Call ConditionalSaveStore() after making any significant changes to assets!");
DEBUG_BREAK;
gpResourceStore->ConditionalSaveStore();
}
// Tick each editor window and redraw their viewports
foreach(IEditor *pEditor, mEditorWindows)
{
if (pEditor->isVisible())

View File

@ -83,6 +83,13 @@ void CProjectOverviewDialog::ExportGame()
return;
}
// Verify export directory is empty
if (!FileUtil::IsEmpty(TO_TSTRING(ExportDir)))
{
QMessageBox::Button Button = QMessageBox::warning(this, "Warning", "<b>Warning:</b> The specified directory is not empty. Export anyway?", QMessageBox::Ok | QMessageBox::Cancel, QMessageBox::NoButton);
if (Button != QMessageBox::Ok) return;
}
CGameExporter Exporter(TO_TSTRING(GameRoot), TO_TSTRING(ExportDir));
Exporter.Export();
}

View File

@ -129,7 +129,7 @@ void WResourceSelector::SetResource(CResourceEntry *pRes)
// when the user types in a resource path so I'd prefer for the text not to be cleared out in that case
if (mpResource)
{
TWideString Path = mpResource->HasRawVersion() ? mpResource->RawAssetPath(true) : mpResource->CookedAssetPath(true);
TWideString Path = mpResource->CookedAssetPath(true);
mUI.LineEdit->setText(TO_QSTRING(Path));
mResourceValid = HasSupportedExtension(mpResource);
}

View File

@ -3,29 +3,20 @@
<name>DistanceFog</name>
<properties>
<property ID="0x00" name="Name" type="string"/>
<property ID="0x01" name="Unknown 1" type="long"/>
<property ID="0x02" name="Unknown 2" type="color"/>
<struct ID="0x03" name="FloatFloat 1" type="single">
<properties>
<property ID="0x00" name="Unknown 3" type="float"/>
<property ID="0x01" name="Unknown 4" type="float"/>
</properties>
</struct>
<struct ID="0x04" name="FloatFloat 2" type="single">
<properties>
<property ID="0x00" name="Unknown 5" type="float"/>
<property ID="0x01" name="Unknown 6" type="float"/>
</properties>
</struct>
<property ID="0x05" name="Unknown 7" type="float"/>
<property ID="0x06" name="Unknown 8" type="bool"/>
<property ID="0x07" name="Unknown 9" type="bool"/>
<property ID="0x01" name="Mode" type="long"/>
<property ID="0x02" name="Color" type="color"/>
<struct ID="0x03" name="Range" template="Structs/Vector2f.xml"/>
<property ID="0x04" name="Color Delta" type="float"/>
<struct ID="0x05" name="Range Delta" template="Structs/Vector2f.xml"/>
<property ID="0x06" name="Explicit" type="bool"/>
<property ID="0x07" name="Active" type="bool"/>
</properties>
<states/>
<messages/>
<editor>
<properties>
<property name="InstanceName" ID="0x00"/>
<property name="Active" ID="0x07"/>
</properties>
<assets>
<billboard source="file">script/common/DistanceFog.txtr</billboard>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<struct name="Vector2f" type="single">
<properties>
<property ID="0x00" name="X" type="float"/>
<property ID="0x01" name="Y" type="float"/>
</properties>
</struct>