Changed game exporter to export from a full disc image using nod instead of a pre-extracted disc filesystem; also fixed issue with tabbing in/out with a file dialog open, and fixed a memory leak in CAnimSet

This commit is contained in:
Aruki 2017-02-06 09:20:18 -07:00
parent fbdf9023d1
commit eca833cf89
31 changed files with 1115 additions and 285 deletions

View File

@ -9,6 +9,9 @@
#define FOURCC(Text) (Text[0] << 24 | Text[1] << 16 | Text[2] << 8 | Text[3]) #define FOURCC(Text) (Text[0] << 24 | Text[1] << 16 | Text[2] << 8 | Text[3])
#define FOURCC_CONSTEXPR(A, B, C, D) (A << 24 | B << 16 | C << 8 | D) #define FOURCC_CONSTEXPR(A, B, C, D) (A << 24 | B << 16 | C << 8 | D)
// todo: replace usages of FOURCC and FOURCC_CONSTEXPR with this macro
// (it should be renamed to just FOURCC when the others aren't in use anymore)
#define IFOURCC(Value) Value
class CFourCC class CFourCC
{ {

View File

@ -65,3 +65,27 @@ void Serialize(IArchive& rArc, EGame& rGame)
rArc.SerializePrimitive(GameID); rArc.SerializePrimitive(GameID);
if (rArc.IsReader()) rGame = GetGameForID(GameID); if (rArc.IsReader()) rGame = GetGameForID(GameID);
} }
// ERegion
void Serialize(IArchive& rArc, ERegion& rRegion)
{
static const TString skRegionNames[] = { "NTSC", "PAL", "JPN" };
TString Name;
if (rArc.IsWriter())
Name = skRegionNames[rRegion];
rArc.SerializePrimitive(Name);
if (rArc.IsReader())
{
for (u32 iReg = 0; iReg < 3; iReg++)
{
if (skRegionNames[iReg] == Name)
{
rRegion = (ERegion) iReg;
break;
}
}
}
}

View File

@ -28,4 +28,14 @@ TString GetGameName(EGame Game);
TString GetGameShortName(EGame Game); TString GetGameShortName(EGame Game);
void Serialize(IArchive& rArc, EGame& rGame); void Serialize(IArchive& rArc, EGame& rGame);
// ERegion
enum ERegion
{
eRegion_NTSC,
eRegion_PAL,
eRegion_JPN,
eRegion_Unknown = -1
};
void Serialize(IArchive& rArc, ERegion& rRegion);
#endif // EGAME_H #endif // EGAME_H

View File

@ -54,7 +54,7 @@ bool IsEmpty(const TWideString& rkDirPath)
return is_empty(*rkDirPath); return is_empty(*rkDirPath);
} }
bool CreateDirectory(const TWideString& rkNewDir) bool MakeDirectory(const TWideString& rkNewDir)
{ {
if (!IsValidPath(rkNewDir, true)) if (!IsValidPath(rkNewDir, true))
{ {
@ -73,7 +73,7 @@ bool CopyFile(const TWideString& rkOrigPath, const TWideString& rkNewPath)
return false; return false;
} }
CreateDirectory(rkNewPath.GetFileDirectory()); MakeDirectory(rkNewPath.GetFileDirectory());
boost::system::error_code Error; boost::system::error_code Error;
copy(*rkOrigPath, *rkNewPath, Error); copy(*rkOrigPath, *rkNewPath, Error);
return (Error == boost::system::errc::success); return (Error == boost::system::errc::success);
@ -87,7 +87,7 @@ bool CopyDirectory(const TWideString& rkOrigPath, const TWideString& rkNewPath)
return false; return false;
} }
CreateDirectory(rkNewPath.GetFileDirectory()); MakeDirectory(rkNewPath.GetFileDirectory());
boost::system::error_code Error; boost::system::error_code Error;
copy_directory(*rkOrigPath, *rkNewPath, Error); copy_directory(*rkOrigPath, *rkNewPath, Error);
return (Error == boost::system::errc::success); return (Error == boost::system::errc::success);

View File

@ -14,7 +14,7 @@ bool IsDirectory(const TWideString& rkDirPath);
bool IsAbsolute(const TWideString& rkDirPath); bool IsAbsolute(const TWideString& rkDirPath);
bool IsRelative(const TWideString& rkDirPath); bool IsRelative(const TWideString& rkDirPath);
bool IsEmpty(const TWideString& rkDirPath); bool IsEmpty(const TWideString& rkDirPath);
bool CreateDirectory(const TWideString& rkNewDir); bool MakeDirectory(const TWideString& rkNewDir);
bool CopyFile(const TWideString& rkOrigPath, const TWideString& rkNewPath); bool CopyFile(const TWideString& rkOrigPath, const TWideString& rkNewPath);
bool CopyDirectory(const TWideString& rkOrigPath, const TWideString& rkNewPath); bool CopyDirectory(const TWideString& rkOrigPath, const TWideString& rkNewPath);
bool MoveFile(const TWideString& rkOldPath, const TWideString& rkNewPath); bool MoveFile(const TWideString& rkOldPath, const TWideString& rkNewPath);

View File

@ -29,6 +29,8 @@ CONFIG (debug, debug|release) {
-L$$BUILD_DIR/Math/ -lMathd \ -L$$BUILD_DIR/Math/ -lMathd \
-L$$EXTERNALS_DIR/assimp/lib/ -lassimp-vc140-mtd \ -L$$EXTERNALS_DIR/assimp/lib/ -lassimp-vc140-mtd \
-L$$EXTERNALS_DIR/lzo-2.09/lib/ -llzo2d \ -L$$EXTERNALS_DIR/lzo-2.09/lib/ -llzo2d \
-L$$EXTERNALS_DIR/nodtool/build/debug/lib/ -lnod \
-L$$EXTERNALS_DIR/nodtool/build/debug/logvisor/ -llogvisor \
-L$$EXTERNALS_DIR/tinyxml2/lib/ -ltinyxml2d \ -L$$EXTERNALS_DIR/tinyxml2/lib/ -ltinyxml2d \
-L$$EXTERNALS_DIR/zlib/lib/ -lzlibd -L$$EXTERNALS_DIR/zlib/lib/ -lzlibd
@ -51,6 +53,8 @@ CONFIG (release, debug|release) {
-L$$BUILD_DIR/Math/ -lMath \ -L$$BUILD_DIR/Math/ -lMath \
-L$$EXTERNALS_DIR/assimp/lib/ -lassimp-vc140-mt \ -L$$EXTERNALS_DIR/assimp/lib/ -lassimp-vc140-mt \
-L$$EXTERNALS_DIR/lzo-2.09/lib/ -llzo2 \ -L$$EXTERNALS_DIR/lzo-2.09/lib/ -llzo2 \
-L$$EXTERNALS_DIR/nodtool/build/release/lib/ -lnod \
-L$$EXTERNALS_DIR/nodtool/build/release/logvisor -llogvisor \
-L$$EXTERNALS_DIR/tinyxml2/lib/ -ltinyxml2 \ -L$$EXTERNALS_DIR/tinyxml2/lib/ -ltinyxml2 \
-L$$EXTERNALS_DIR/zlib/lib/ -lzlib -L$$EXTERNALS_DIR/zlib/lib/ -lzlib
@ -72,6 +76,8 @@ INCLUDEPATH += $$PWE_MAIN_INCLUDE \
$$EXTERNALS_DIR/glew-2.0.0/include \ $$EXTERNALS_DIR/glew-2.0.0/include \
$$EXTERNALS_DIR/glm/glm \ $$EXTERNALS_DIR/glm/glm \
$$EXTERNALS_DIR/lzo-2.09/include \ $$EXTERNALS_DIR/lzo-2.09/include \
$$EXTERNALS_DIR/nodtool/include \
$$EXTERNALS_DIR/nodtool/logvisor/include \
$$EXTERNALS_DIR/tinyxml2/include \ $$EXTERNALS_DIR/tinyxml2/include \
$$EXTERNALS_DIR/zlib/include $$EXTERNALS_DIR/zlib/include

View File

@ -9,6 +9,7 @@
#include <memory> #include <memory>
const TString gkAssetMapPath = "..\\resources\\gameinfo\\AssetNameMap.xml"; const TString gkAssetMapPath = "..\\resources\\gameinfo\\AssetNameMap.xml";
const TString gkAssetMapExt = "xml";
class CAssetNameMap class CAssetNameMap
{ {
@ -36,6 +37,9 @@ public:
void SaveAssetNames(TString Path = gkAssetMapPath); void SaveAssetNames(TString Path = gkAssetMapPath);
bool GetNameInfo(CAssetID ID, TString& rOutDirectory, TString& rOutName); bool GetNameInfo(CAssetID ID, TString& rOutDirectory, TString& rOutName);
void CopyFromStore(CResourceStore *pStore); void CopyFromStore(CResourceStore *pStore);
inline static TString DefaultNameMapPath() { return gkAssetMapPath; }
inline static TString GetExtension() { return gkAssetMapExt; }
}; };
#endif // CASSETNAMEMAP #endif // CASSETNAMEMAP

View File

@ -12,65 +12,77 @@
#include <Common/Serialization/CXMLWriter.h> #include <Common/Serialization/CXMLWriter.h>
#include <tinyxml2.h> #include <tinyxml2.h>
#define COPY_DISC_DATA 1
#define LOAD_PAKS 1 #define LOAD_PAKS 1
#define SAVE_PACKAGE_DEFINITIONS 1 #define SAVE_PACKAGE_DEFINITIONS 1
#define USE_ASSET_NAME_MAP 1 #define USE_ASSET_NAME_MAP 1
#define EXPORT_COOKED 1 #define EXPORT_COOKED 1
CGameExporter::CGameExporter(const TString& rkInputDir, const TString& rkOutputDir) CGameExporter::CGameExporter(EGame Game, ERegion Region, const TString& rkGameName, const TString& rkGameID, float BuildVersion)
: mGame(Game)
, mRegion(Region)
, mGameName(rkGameName)
, mGameID(rkGameID)
, mBuildVersion(BuildVersion)
{ {
mGame = eUnknownGame; ASSERT(mGame != eUnknownGame);
mBuildVersion = 0.f; ASSERT(mRegion != eRegion_Unknown);
mGameDir = FileUtil::MakeAbsolute(rkInputDir);
mExportDir = FileUtil::MakeAbsolute(rkOutputDir);
mpProject = new CGameProject(mExportDir);
mDiscDir = L"Disc\\";
mWorldsDirName = L"Worlds\\";
} }
#if PUBLIC_RELEASE #if PUBLIC_RELEASE
#error Fix export directory being cleared! #error Fix export directory being cleared!
#endif #endif
bool CGameExporter::Export() bool CGameExporter::Export(nod::DiscBase *pDisc, const TString& rkOutputDir, CAssetNameMap *pNameMap, CGameInfo *pGameInfo)
{ {
SCOPED_TIMER(ExportGame); SCOPED_TIMER(ExportGame);
FileUtil::CreateDirectory(mExportDir); mpDisc = pDisc;
FileUtil::ClearDirectory(mExportDir); mpNameMap = pNameMap;
mpGameInfo = pGameInfo;
// Initial analyze/copy of disc data mExportDir = FileUtil::MakeAbsolute(rkOutputDir);
CopyDiscData(); mDiscDir = L"Disc\\";
FindBuildVersion(); mWorldsDirName = L"Worlds\\";
// Create project // Create project
mpProject = new CGameProject(this, mExportDir, mGame, mBuildVersion); FileUtil::MakeDirectory(mExportDir);
mpProject->SetProjectName(CMasterTemplate::FindGameName(mGame)); FileUtil::ClearDirectory(mExportDir);
// Extract disc
if (!ExtractDiscData())
return false;
// Create project
CGameProject *pOldActiveProj = CGameProject::ActiveProject();
mpProject = CGameProject::CreateProjectForExport(
this,
mExportDir,
mGame,
mRegion,
mGameID,
mBuildVersion,
mDolPath,
mApploaderPath,
mPartitionHeaderPath,
mFilesystemAddress);
mpProject->SetProjectName(mGameName);
mpProject->SetActive(); mpProject->SetActive();
mpStore = mpProject->ResourceStore(); mpStore = mpProject->ResourceStore();
mContentDir = mpStore->RawDir(false); mContentDir = mpStore->RawDir(false);
mCookedDir = mpStore->CookedDir(false); mCookedDir = mpStore->CookedDir(false);
#if USE_ASSET_NAME_MAP
mNameMap.LoadAssetNames();
#endif
// Export game data // Export game data
CResourceStore *pOldStore = gpResourceStore;
gpResourceStore = mpStore;
LoadPaks(); LoadPaks();
ExportCookedResources(); ExportCookedResources();
mpProject->AudioManager()->LoadAssets(); mpProject->AudioManager()->LoadAssets();
ExportResourceEditorData(); ExportResourceEditorData();
// Export finished! // Export finished!
mProjectPath = mpProject->ProjectPath();
delete mpProject; delete mpProject;
gpResourceStore = pOldStore; if (pOldActiveProj) pOldActiveProj->SetActive();
return true; return true;
} }
@ -81,100 +93,85 @@ void CGameExporter::LoadResource(const CAssetID& rkID, std::vector<u8>& rBuffer)
} }
// ************ PROTECTED ************ // ************ PROTECTED ************
void CGameExporter::CopyDiscData() bool CGameExporter::ExtractDiscData()
{ {
#if COPY_DISC_DATA // todo: handle dol, apploader, multiple partitions, wii ticket blob
SCOPED_TIMER(CopyDiscData); SCOPED_TIMER(ExtractDiscData);
// Create Disc output folder // Create Disc output folder
FileUtil::CreateDirectory(mExportDir + mDiscDir); TWideString AbsDiscDir = mExportDir + mDiscDir;
#endif FileUtil::MakeDirectory(AbsDiscDir);
// Copy data // Extract disc filesystem
TWideStringList DiscFiles; nod::Partition *pDataPartition = mpDisc->getDataPartition();
FileUtil::GetDirectoryContents(mGameDir, DiscFiles); nod::ExtractionContext Context;
Context.force = false;
Context.verbose = false;
Context.progressCB = nullptr;
bool Success = ExtractDiscNodeRecursive(&pDataPartition->getFSTRoot(), AbsDiscDir, Context);
if (!Success) return false;
for (auto It = DiscFiles.begin(); It != DiscFiles.end(); It++) // Extract dol
mDolPath = L"boot.dol";
CFileOutStream DolFile(TWideString(mExportDir + mDolPath).ToUTF8().ToStdString());
if (!DolFile.IsValid()) return false;
std::unique_ptr<uint8_t[]> pDolBuffer = pDataPartition->getDOLBuf();
DolFile.WriteBytes(pDolBuffer.get(), (u32) pDataPartition->getDOLSize());
DolFile.Close();
// Extract apploader
mApploaderPath = L"apploader.img";
CFileOutStream ApploaderFile(TWideString(mExportDir + mApploaderPath).ToUTF8().ToStdString());
if (!ApploaderFile.IsValid()) return false;
std::unique_ptr<uint8_t[]> pApploaderBuffer = pDataPartition->getApploaderBuf();
ApploaderFile.WriteBytes(pApploaderBuffer.get(), (u32) pDataPartition->getApploaderSize());
ApploaderFile.Close();
// Extract Wii partition header
bool IsWii = (mBuildVersion >= 3.f);
if (IsWii)
{ {
TWideString FullPath = *It; mFilesystemAddress = 0;
TWideString RelPath = FullPath.ChopFront(mGameDir.Size()); mPartitionHeaderPath = L"partition_header.bin";
nod::DiscWii *pDiscWii = static_cast<nod::DiscWii*>(mpDisc);
// Exclude PakTool files and folders Success = pDiscWii->writeOutDataPartitionHeader(*(mExportDir + mPartitionHeaderPath));
if (FullPath.GetFileName(false) == L"PakTool" || FullPath.GetFileName(false) == L"zlib1" || RelPath.Contains(L"-pak")) if (!Success) return false;
continue;
// Hack to determine game
if (mGame == eUnknownGame)
{
TWideString Name = FullPath.GetFileName(false);
if (Name == L"MetroidCWP") mGame = ePrimeDemo;
else if (Name == L"NESemu") mGame = ePrime;
else if (Name == L"PirateGun") mGame = eEchoesDemo;
else if (Name == L"AtomicBeta") mGame = eEchoes;
else if (Name == L"InGameAudio") mGame = eCorruptionProto;
else if (Name == L"GuiDVD") mGame = eCorruption;
else if (Name == L"PreloadData") mGame = eReturns;
}
// Mark dol path. Note size != 0 check is needed because some ISO unpackers (*cough* GCRebuilder) can export bad dol files
if (mDolPath.IsEmpty() && FullPath.EndsWith(L".dol", false) && FileUtil::FileSize(FullPath) != 0)
mDolPath = FullPath;
// Detect paks
if (FullPath.GetFileExtension().ToLower() == L"pak")
mPaks.push_back(FullPath);
#if COPY_DISC_DATA
// Create directory
TWideString OutFile = mExportDir + mDiscDir + RelPath;
FileUtil::CreateDirectory(OutFile.GetFileDirectory());
// Copy file
if (FileUtil::IsFile(FullPath))
FileUtil::CopyFile(FullPath, OutFile);
#endif
} }
else
mFilesystemAddress = (u32) pDataPartition->getFSTMemoryAddr();
ASSERT(mGame != eUnknownGame); return true;
} }
void CGameExporter::FindBuildVersion() bool CGameExporter::ExtractDiscNodeRecursive(const nod::Node *pkNode, const TWideString& rkDir, const nod::ExtractionContext& rkContext)
{ {
ASSERT(!mDolPath.IsEmpty()); for (nod::Node::DirectoryIterator Iter = pkNode->begin(); Iter != pkNode->end(); ++Iter)
// MP1 demo build doesn't have a build version
if (mGame == ePrimeDemo) return;
// Read entire file into a big buffer
CFileInStream File(mDolPath.ToUTF8().ToStdString(), IOUtil::eBigEndian);
std::vector<char> FileContents(File.Size());
File.ReadBytes(FileContents.data(), FileContents.size());
File.Close();
// Find build info string
const char *pkSearchText = "!#$MetroidBuildInfo!#$";
const int SearchTextSize = strlen(pkSearchText);
for (u32 SearchIdx = 0; SearchIdx < FileContents.size() - SearchTextSize + 1; SearchIdx++)
{ {
int Match = 0; if (Iter->getKind() == nod::Node::Kind::File)
while (FileContents[SearchIdx + Match] == pkSearchText[Match] && Match < SearchTextSize)
Match++;
if (Match == SearchTextSize)
{ {
// Found the build info string; extract version number TWideString FilePath = rkDir + TString(Iter->getName()).ToUTF16();
TString BuildInfo = &FileContents[SearchIdx + SearchTextSize]; bool Success = Iter->extractToDirectory(*rkDir, rkContext);
int BuildVerStart = BuildInfo.IndexOfPhrase("Build v") + 7; if (!Success) return false;
ASSERT(BuildVerStart != 6);
mBuildVersion = BuildInfo.SubString(BuildVerStart, 5).ToFloat(); if (FilePath.GetFileExtension() == L"pak")
return; mPaks.push_back(FilePath);
}
else
{
TWideString Subdir = rkDir + TString(Iter->getName()).ToUTF16() + L"\\";
bool Success = FileUtil::MakeDirectory(Subdir);
if (!Success) return false;
Success = ExtractDiscNodeRecursive(&*Iter, Subdir, rkContext);
if (!Success) return false;
} }
} }
Log::Error("Failed to find MetroidBuildInfo string. Build Version will be set to 0. DOL file: " + mDolPath.ToUTF8()); return true;
} }
// ************ RESOURCE LOADING ************ // ************ RESOURCE LOADING ************
@ -183,10 +180,13 @@ void CGameExporter::LoadPaks()
#if LOAD_PAKS #if LOAD_PAKS
SCOPED_TIMER(LoadPaks); SCOPED_TIMER(LoadPaks);
mPaks.sort([](const TWideString& rkLeft, const TWideString& rkRight) -> bool {
return rkLeft.ToUpper() < rkRight.ToUpper();
});
for (auto It = mPaks.begin(); It != mPaks.end(); It++) for (auto It = mPaks.begin(); It != mPaks.end(); It++)
{ {
TWideString PakPath = *It; TWideString PakPath = *It;
TWideString PakName = PakPath.GetFileName(false);
TString CharPak = PakPath.ToUTF8(); TString CharPak = PakPath.ToUTF8();
CFileInStream Pak(CharPak.ToStdString(), IOUtil::eBigEndian); CFileInStream Pak(CharPak.ToStdString(), IOUtil::eBigEndian);
@ -196,7 +196,7 @@ void CGameExporter::LoadPaks()
continue; continue;
} }
CPackage *pPackage = new CPackage(mpProject, CharPak.GetFileName(false), FileUtil::MakeRelative(PakPath.GetFileDirectory(), mGameDir)); CPackage *pPackage = new CPackage(mpProject, CharPak.GetFileName(false), FileUtil::MakeRelative(PakPath.GetFileDirectory(), mExportDir + mDiscDir));
CResourceCollection *pCollection = pPackage->AddCollection("Default"); CResourceCollection *pCollection = pPackage->AddCollection("Default");
// MP1-MP3Proto // MP1-MP3Proto
@ -448,7 +448,7 @@ void CGameExporter::ExportCookedResources()
{ {
{ {
SCOPED_TIMER(ExportCookedResources); SCOPED_TIMER(ExportCookedResources);
FileUtil::CreateDirectory(mCookedDir); FileUtil::MakeDirectory(mCookedDir);
for (auto It = mResourceMap.begin(); It != mResourceMap.end(); It++) for (auto It = mResourceMap.begin(); It != mResourceMap.end(); It++)
{ {
@ -517,13 +517,20 @@ void CGameExporter::ExportResource(SResourceInstance& rRes)
// Register resource and write to file // Register resource and write to file
TString Directory, Name; TString Directory, Name;
mNameMap.GetNameInfo(rRes.ResourceID, Directory, Name);
#if USE_ASSET_NAME_MAP
mpNameMap->GetNameInfo(rRes.ResourceID, Directory, Name);
#else
Directory = "Uncategorized";
Name = rRes.ResourceID.ToString();
#endif
CResourceEntry *pEntry = mpStore->RegisterResource(rRes.ResourceID, CResTypeInfo::TypeForCookedExtension(mGame, rRes.ResourceType)->Type(), Directory, Name); CResourceEntry *pEntry = mpStore->RegisterResource(rRes.ResourceID, CResTypeInfo::TypeForCookedExtension(mGame, rRes.ResourceType)->Type(), Directory, Name);
#if EXPORT_COOKED #if EXPORT_COOKED
// Save cooked asset // Save cooked asset
TWideString OutCookedPath = pEntry->CookedAssetPath(); TWideString OutCookedPath = pEntry->CookedAssetPath();
FileUtil::CreateDirectory(OutCookedPath.GetFileDirectory()); FileUtil::MakeDirectory(OutCookedPath.GetFileDirectory());
CFileOutStream Out(OutCookedPath.ToUTF8().ToStdString(), IOUtil::eBigEndian); CFileOutStream Out(OutCookedPath.ToUTF8().ToStdString(), IOUtil::eBigEndian);
if (Out.IsValid()) if (Out.IsValid())

View File

@ -10,17 +10,25 @@
#include <Common/TString.h> #include <Common/TString.h>
#include <Common/types.h> #include <Common/types.h>
#include <map> #include <map>
#include <nod/nod.hpp>
class CGameExporter class CGameExporter
{ {
// Project // Project Data
CGameProject *mpProject; CGameProject *mpProject;
TWideString mProjectPath;
CResourceStore *mpStore; CResourceStore *mpStore;
EGame mGame; EGame mGame;
ERegion mRegion;
TString mGameName;
TString mGameID;
float mBuildVersion; float mBuildVersion;
TWideString mDolPath;
TWideString mApploaderPath;
TWideString mPartitionHeaderPath;
u32 mFilesystemAddress;
// Directories // Directories
TWideString mGameDir;
TWideString mExportDir; TWideString mExportDir;
TWideString mDiscDir; TWideString mDiscDir;
TWideString mContentDir; TWideString mContentDir;
@ -29,13 +37,13 @@ class CGameExporter
TWideString mWorldsDirName; TWideString mWorldsDirName;
// Files // Files
TWideString mDolPath; nod::DiscBase *mpDisc;
// Resources // Resources
TWideStringList mPaks; TWideStringList mPaks;
std::map<CAssetID, bool> mAreaDuplicateMap; std::map<CAssetID, bool> mAreaDuplicateMap;
CAssetNameMap mNameMap; CAssetNameMap *mpNameMap;
CGameInfo mGameInfo; CGameInfo *mpGameInfo;
struct SResourceInstance struct SResourceInstance
{ {
@ -50,13 +58,15 @@ class CGameExporter
std::map<CAssetID, SResourceInstance> mResourceMap; std::map<CAssetID, SResourceInstance> mResourceMap;
public: public:
CGameExporter(const TString& rkInputDir, const TString& rkOutputDir); CGameExporter(EGame Game, ERegion Region, const TString& rkGameName, const TString& rkGameID, float BuildVersion);
bool Export(); bool Export(nod::DiscBase *pDisc, const TString& rkOutputDir, CAssetNameMap *pNameMap, CGameInfo *pGameInfo);
void LoadResource(const CAssetID& rkID, std::vector<u8>& rBuffer); void LoadResource(const CAssetID& rkID, std::vector<u8>& rBuffer);
inline TWideString ProjectPath() const { return mProjectPath; }
protected: protected:
void CopyDiscData(); bool ExtractDiscData();
void FindBuildVersion(); bool ExtractDiscNodeRecursive(const nod::Node *pkNode, const TWideString& rkDir, const nod::ExtractionContext& rkContext);
void LoadPaks(); void LoadPaks();
void LoadResource(const SResourceInstance& rkResource, std::vector<u8>& rBuffer); void LoadResource(const SResourceInstance& rkResource, std::vector<u8>& rBuffer);
void ExportCookedResources(); void ExportCookedResources();

View File

@ -1,11 +1,14 @@
#include "CGameInfo.h" #include "CGameInfo.h"
#include <Common/FileUtil.h>
void CGameInfo::LoadGameInfo(EGame Game) void CGameInfo::LoadGameInfo(EGame Game)
{ {
Game = RoundGame(Game); Game = RoundGame(Game);
mGame = Game; mGame = Game;
LoadGameInfo(GetDefaultGameInfoPath(Game)); TString Path = GetDefaultGameInfoPath(Game);
if (FileUtil::Exists(Path))
LoadGameInfo(Path);
} }
void CGameInfo::LoadGameInfo(TString Path) void CGameInfo::LoadGameInfo(TString Path)
@ -59,5 +62,5 @@ TString CGameInfo::GetDefaultGameInfoPath(EGame Game)
return ""; return "";
TString GameName = GetGameShortName(Game); TString GameName = GetGameShortName(Game);
return TString::Format("%s\\GameInfo%s.xml", *gkGameInfoDir, *GameName); return TString::Format("%s\\GameInfo%s.%s", *gkGameInfoDir, *GameName, *gkGameInfoExt);
} }

View File

@ -9,6 +9,7 @@
#include <map> #include <map>
const TString gkGameInfoDir = "..\\resources\\gameinfo"; const TString gkGameInfoDir = "..\\resources\\gameinfo";
const TString gkGameInfoExt = "xml";
class CGameInfo class CGameInfo
{ {
@ -34,6 +35,8 @@ public:
static CGameInfo* GetGameInfo(EGame Game); static CGameInfo* GetGameInfo(EGame Game);
static EGame RoundGame(EGame Game); static EGame RoundGame(EGame Game);
static TString GetDefaultGameInfoPath(EGame Game); static TString GetDefaultGameInfoPath(EGame Game);
inline static TString GetExtension() { return gkGameInfoExt; }
}; };
#endif // CGAMEINFO #endif // CGAMEINFO

View File

@ -10,30 +10,16 @@ CGameProject::~CGameProject()
ASSERT(!mpResourceStore->IsDirty()); ASSERT(!mpResourceStore->IsDirty());
if (IsActive()) if (IsActive())
{
mspActiveProject = nullptr; mspActiveProject = nullptr;
gpResourceStore = nullptr;
}
delete mpAudioManager; delete mpAudioManager;
delete mpGameInfo; delete mpGameInfo;
delete mpResourceStore; delete mpResourceStore;
} }
bool CGameProject::Load(const TWideString& rkPath)
{
mProjectRoot = rkPath.GetFileDirectory();
mProjectRoot.Replace(L"/", L"\\");
TString ProjPath = rkPath.ToUTF8();
CXMLReader Reader(ProjPath);
mGame = Reader.Game();
Serialize(Reader);
CTemplateLoader::LoadGameTemplates(mGame);
mpResourceStore->LoadResourceDatabase();
mpGameInfo->LoadGameInfo(mGame);
mpAudioManager->LoadAssets();
return true;
}
void CGameProject::Save() void CGameProject::Save()
{ {
TString ProjPath = ProjectPath().ToUTF8(); TString ProjPath = ProjectPath().ToUTF8();
@ -44,8 +30,19 @@ void CGameProject::Save()
void CGameProject::Serialize(IArchive& rArc) void CGameProject::Serialize(IArchive& rArc)
{ {
rArc << SERIAL("Name", mProjectName) rArc << SERIAL("Name", mProjectName)
<< SERIAL("Region", mRegion)
<< SERIAL("GameID", mGameID)
<< SERIAL("BuildVersion", mBuildVersion) << SERIAL("BuildVersion", mBuildVersion)
<< SERIAL("ResourceDB", mResourceDBPath); << SERIAL("DolPath", mDolPath)
<< SERIAL("ApploaderPath", mApploaderPath);
if (rArc.Game() >= eCorruption)
rArc << SERIAL("PartitionHeaderPath", mPartitionHeaderPath);
if (!IsWiiBuild())
rArc << SERIAL("FstAddress", mFilesystemAddress);
rArc << SERIAL("ResourceDB", mResourceDBPath);
// Packages // Packages
std::vector<TString> PackageList; std::vector<TString> PackageList;
@ -129,3 +126,52 @@ CAssetID CGameProject::FindNamedResource(const TString& rkName) const
return CAssetID::InvalidID(mGame); return CAssetID::InvalidID(mGame);
} }
CGameProject* CGameProject::CreateProjectForExport(
CGameExporter *pExporter,
const TWideString& rkProjRootDir,
EGame Game,
ERegion Region,
const TString& rkGameID,
float BuildVer,
const TWideString& rkDolPath,
const TWideString& rkApploaderPath,
const TWideString& rkPartitionHeaderPath,
u32 FstAddress
)
{
CGameProject *pProj = new CGameProject;
pProj->mGame = Game;
pProj->mRegion = Region;
pProj->mGameID = rkGameID;
pProj->mBuildVersion = BuildVer;
pProj->mDolPath = rkDolPath;
pProj->mApploaderPath = rkApploaderPath;
pProj->mPartitionHeaderPath = rkPartitionHeaderPath;
pProj->mFilesystemAddress = FstAddress;
pProj->mProjectRoot = rkProjRootDir;
pProj->mProjectRoot.Replace(L"/", L"\\");
pProj->mpResourceStore = new CResourceStore(pProj, pExporter, L"Content\\", L"Cooked\\", Game);
pProj->mpGameInfo->LoadGameInfo(Game);
return pProj;
}
CGameProject* CGameProject::LoadProject(const TWideString& rkProjPath)
{
CGameProject *pProj = new CGameProject;
pProj->mProjectRoot = rkProjPath.GetFileDirectory();
pProj->mProjectRoot.Replace(L"/", L"\\");
TString ProjPath = rkProjPath.ToUTF8();
CXMLReader Reader(ProjPath);
pProj->mGame = Reader.Game();
pProj->Serialize(Reader);
CTemplateLoader::LoadGameTemplates(pProj->mGame);
pProj->mpResourceStore = new CResourceStore(pProj);
pProj->mpResourceStore->LoadResourceDatabase();
pProj->mpGameInfo->LoadGameInfo(pProj->mGame);
pProj->mpAudioManager->LoadAssets();
return pProj;
}

View File

@ -14,9 +14,16 @@
class CGameProject class CGameProject
{ {
EGame mGame;
float mBuildVersion;
TString mProjectName; TString mProjectName;
EGame mGame;
ERegion mRegion;
TString mGameID;
float mBuildVersion;
TWideString mDolPath;
TWideString mApploaderPath;
TWideString mPartitionHeaderPath;
u32 mFilesystemAddress;
TWideString mProjectRoot; TWideString mProjectRoot;
TWideString mResourceDBPath; TWideString mResourceDBPath;
std::vector<CPackage*> mPackages; std::vector<CPackage*> mPackages;
@ -34,52 +41,45 @@ class CGameProject
static CGameProject *mspActiveProject; static CGameProject *mspActiveProject;
public: // Private Constructor
CGameProject() CGameProject()
: mGame(eUnknownGame) : mProjectName("Unnamed Project")
, mProjectName("Unnamed Project") , mGame(eUnknownGame)
, mRegion(eRegion_Unknown)
, mGameID("000000")
, mBuildVersion(0.f)
, mResourceDBPath(L"ResourceDB.rdb") , mResourceDBPath(L"ResourceDB.rdb")
, mpResourceStore(nullptr)
{ {
mpResourceStore = new CResourceStore(this);
mpGameInfo = new CGameInfo(); mpGameInfo = new CGameInfo();
mpAudioManager = new CAudioManager(this); mpAudioManager = new CAudioManager(this);
} }
CGameProject(const TWideString& rkProjRootDir) public:
: mGame(eUnknownGame)
, mProjectName("Unnamed Project")
, mProjectRoot(rkProjRootDir)
, mResourceDBPath(L"ResourceDB.rdb")
{
mProjectRoot.Replace(L"/", L"\\");
mpResourceStore = new CResourceStore(this);
mpGameInfo = new CGameInfo();
mpAudioManager = new CAudioManager(this);
}
CGameProject(CGameExporter *pExporter, const TWideString& rkProjRootDir, EGame Game, float BuildVer)
: mGame(Game)
, mBuildVersion(BuildVer)
, mProjectName(CMasterTemplate::FindGameName(Game))
, mProjectRoot(rkProjRootDir)
, mResourceDBPath(L"ResourceDB.rdb")
{
mProjectRoot.Replace(L"/", L"\\");
mpResourceStore = new CResourceStore(this, pExporter, L"Content\\", L"Cooked\\", Game);
mpGameInfo = new CGameInfo();
mpGameInfo->LoadGameInfo(mGame);
mpAudioManager = new CAudioManager(this);
}
~CGameProject(); ~CGameProject();
bool Load(const TWideString& rkPath);
void Save(); void Save();
void Serialize(IArchive& rArc); void Serialize(IArchive& rArc);
void SetActive(); void SetActive();
void GetWorldList(std::list<CAssetID>& rOut) const; void GetWorldList(std::list<CAssetID>& rOut) const;
CAssetID FindNamedResource(const TString& rkName) const; CAssetID FindNamedResource(const TString& rkName) const;
// Static
static CGameProject* CreateProjectForExport(
CGameExporter *pExporter,
const TWideString& rkProjRootDir,
EGame Game,
ERegion Region,
const TString& rkGameID,
float BuildVer,
const TWideString& rkDolPath,
const TWideString& rkApploaderPath,
const TWideString& rkPartitionHeaderPath,
u32 FstAddress
);
static CGameProject* LoadProject(const TWideString& rkProjPath);
// Directory Handling // Directory Handling
inline TWideString ProjectRoot() const { return mProjectRoot; } inline TWideString ProjectRoot() const { return mProjectRoot; }
inline TWideString ResourceDBPath(bool Relative) const { return Relative ? mResourceDBPath : mProjectRoot + mResourceDBPath; } inline TWideString ResourceDBPath(bool Relative) const { return Relative ? mResourceDBPath : mProjectRoot + mResourceDBPath; }
@ -101,6 +101,7 @@ public:
inline EGame Game() const { return mGame; } inline EGame Game() const { return mGame; }
inline float BuildVersion() const { return mBuildVersion; } inline float BuildVersion() const { return mBuildVersion; }
inline bool IsActive() const { return mspActiveProject == this; } inline bool IsActive() const { return mspActiveProject == this; }
inline bool IsWiiBuild() const { return mBuildVersion >= 3.f; }
static inline CGameProject* ActiveProject() { return mspActiveProject; } static inline CGameProject* ActiveProject() { return mspActiveProject; }
}; };

View File

@ -20,7 +20,7 @@ void CPackage::Load()
void CPackage::Save() void CPackage::Save()
{ {
TWideString DefPath = DefinitionPath(false); TWideString DefPath = DefinitionPath(false);
FileUtil::CreateDirectory(DefPath.GetFileDirectory()); FileUtil::MakeDirectory(DefPath.GetFileDirectory());
CXMLWriter Writer(DefPath.ToUTF8(), "PackageDefinition", 0, mpProject ? mpProject->Game() : eUnknownGame); CXMLWriter Writer(DefPath.ToUTF8(), "PackageDefinition", 0, mpProject ? mpProject->Game() : eUnknownGame);
Serialize(Writer); Serialize(Writer);

View File

@ -190,7 +190,7 @@ bool CResourceEntry::Save(bool SkipCacheSave /*= false*/)
// Note: We call Serialize directly for resources to avoid having a redundant resource root node in the output file. // Note: We call Serialize directly for resources to avoid having a redundant resource root node in the output file.
TString Path = RawAssetPath(); TString Path = RawAssetPath();
TString Dir = Path.GetFileDirectory(); TString Dir = Path.GetFileDirectory();
FileUtil::CreateDirectory(Dir.ToUTF16()); FileUtil::MakeDirectory(Dir.ToUTF16());
TString SerialName = mpTypeInfo->TypeName(); TString SerialName = mpTypeInfo->TypeName();
SerialName.RemoveWhitespace(); SerialName.RemoveWhitespace();
@ -268,7 +268,8 @@ CResource* CResourceEntry::LoadCooked(IInputStream& rInput)
gpResourceStore = mpStore; gpResourceStore = mpStore;
mpResource = CResourceFactory::LoadCookedResource(this, rInput); mpResource = CResourceFactory::LoadCookedResource(this, rInput);
mpStore->TrackLoadedResource(this); if (mpResource)
mpStore->TrackLoadedResource(this);
gpResourceStore = pOldStore; gpResourceStore = pOldStore;
return mpResource; return mpResource;

View File

@ -81,18 +81,29 @@ class CAnimSet : public CResource
std::vector<CAnimEventData*> mAnimEvents; // note: these are for MP2, where event data isn't a standalone resource; these are owned by the animset std::vector<CAnimEventData*> mAnimEvents; // note: these are for MP2, where event data isn't a standalone resource; these are owned by the animset
public: public:
CAnimSet(CResourceEntry *pEntry = 0) : CResource(pEntry) {} CAnimSet(CResourceEntry *pEntry = 0)
: CResource(pEntry)
, mpDefaultTransition(nullptr)
{}
~CAnimSet() ~CAnimSet()
{ {
for (u32 iAnim = 0; iAnim < mAnimations.size(); iAnim++)
delete mAnimations[iAnim].pMetaAnim;
for (u32 iTrans = 0; iTrans < mTransitions.size(); iTrans++)
delete mTransitions[iTrans].pMetaTrans;
for (u32 iHalf = 0; iHalf < mHalfTransitions.size(); iHalf++)
delete mHalfTransitions[iHalf].pMetaTrans;
delete mpDefaultTransition;
// For MP2, anim events need to be cleaned up manually // For MP2, anim events need to be cleaned up manually
if (Game() >= eEchoesDemo) for (u32 iEvent = 0; iEvent < mAnimEvents.size(); iEvent++)
{ {
for (u32 iEvent = 0; iEvent < mAnimEvents.size(); iEvent++) ASSERT(mAnimEvents[iEvent] && !mAnimEvents[iEvent]->Entry());
{ delete mAnimEvents[iEvent];
ASSERT(mAnimEvents[iEvent] && !mAnimEvents[iEvent]->Entry());
delete mAnimEvents[iEvent];
}
} }
} }

View File

@ -42,7 +42,7 @@ void CTemplateWriter::SaveAllTemplates()
{ {
// Create directory // Create directory
std::list<CMasterTemplate*> MasterList = CMasterTemplate::MasterList(); std::list<CMasterTemplate*> MasterList = CMasterTemplate::MasterList();
FileUtil::CreateDirectory(smTemplatesDir); FileUtil::MakeDirectory(smTemplatesDir);
// Resave property list // Resave property list
SavePropertyList(); SavePropertyList();
@ -95,7 +95,7 @@ void CTemplateWriter::SaveGameTemplates(CMasterTemplate *pMaster)
// Create directory // Create directory
TString OutFile = smTemplatesDir + pMaster->mSourceFile; TString OutFile = smTemplatesDir + pMaster->mSourceFile;
TString OutDir = OutFile.GetFileDirectory(); TString OutDir = OutFile.GetFileDirectory();
FileUtil::CreateDirectory(OutDir); FileUtil::MakeDirectory(OutDir);
// Resave script templates // Resave script templates
for (auto it = pMaster->mTemplates.begin(); it != pMaster->mTemplates.end(); it++) for (auto it = pMaster->mTemplates.begin(); it != pMaster->mTemplates.end(); it++)
@ -226,7 +226,7 @@ void CTemplateWriter::SaveScriptTemplate(CScriptTemplate *pTemp)
// Create directory // Create directory
TString OutFile = smTemplatesDir + pMaster->GetDirectory() + pTemp->mSourceFile; TString OutFile = smTemplatesDir + pMaster->GetDirectory() + pTemp->mSourceFile;
TString OutDir = OutFile.GetFileDirectory(); TString OutDir = OutFile.GetFileDirectory();
FileUtil::CreateDirectory(*OutDir); FileUtil::MakeDirectory(*OutDir);
// Create new document // Create new document
XMLDocument ScriptXML; XMLDocument ScriptXML;
@ -432,7 +432,7 @@ void CTemplateWriter::SaveStructTemplate(CStructTemplate *pTemp)
TString OutFile = smTemplatesDir + pMaster->GetDirectory() + pTemp->mSourceFile; TString OutFile = smTemplatesDir + pMaster->GetDirectory() + pTemp->mSourceFile;
TString OutDir = OutFile.GetFileDirectory(); TString OutDir = OutFile.GetFileDirectory();
TString Name = OutFile.GetFileName(false); TString Name = OutFile.GetFileName(false);
FileUtil::CreateDirectory(OutDir); FileUtil::MakeDirectory(OutDir);
// Create new document and write struct properties to it // Create new document and write struct properties to it
XMLDocument StructXML; XMLDocument StructXML;
@ -456,7 +456,7 @@ void CTemplateWriter::SaveEnumTemplate(CEnumTemplate *pTemp)
TString OutFile = smTemplatesDir + pMaster->GetDirectory() + pTemp->mSourceFile; TString OutFile = smTemplatesDir + pMaster->GetDirectory() + pTemp->mSourceFile;
TString OutDir = OutFile.GetFileDirectory(); TString OutDir = OutFile.GetFileDirectory();
TString Name = OutFile.GetFileName(false); TString Name = OutFile.GetFileName(false);
FileUtil::CreateDirectory(OutDir); FileUtil::MakeDirectory(OutDir);
// Create new document and write enumerators to it // Create new document and write enumerators to it
XMLDocument EnumXML; XMLDocument EnumXML;
@ -479,7 +479,7 @@ void CTemplateWriter::SaveBitfieldTemplate(CBitfieldTemplate *pTemp)
TString OutFile = smTemplatesDir + pMaster->GetDirectory() + pTemp->mSourceFile; TString OutFile = smTemplatesDir + pMaster->GetDirectory() + pTemp->mSourceFile;
TString OutDir = OutFile.GetFileDirectory(); TString OutDir = OutFile.GetFileDirectory();
TString Name = pTemp->mSourceFile.GetFileName(false); TString Name = pTemp->mSourceFile.GetFileName(false);
FileUtil::CreateDirectory(OutDir); FileUtil::MakeDirectory(OutDir);
// Create new document and write enumerators to it // Create new document and write enumerators to it
XMLDocument BitfieldXML; XMLDocument BitfieldXML;

View File

@ -162,7 +162,8 @@ void CAnimSetLoader::ProcessPrimitives()
for (u32 iTrans = 0; iTrans < pSet->mTransitions.size(); iTrans++) for (u32 iTrans = 0; iTrans < pSet->mTransitions.size(); iTrans++)
pSet->mTransitions[iTrans].pMetaTrans->GetUniquePrimitives(UniquePrimitives); pSet->mTransitions[iTrans].pMetaTrans->GetUniquePrimitives(UniquePrimitives);
pSet->mpDefaultTransition->GetUniquePrimitives(UniquePrimitives); if (pSet->mpDefaultTransition)
pSet->mpDefaultTransition->GetUniquePrimitives(UniquePrimitives);
for (u32 iTrans = 0; iTrans < pSet->mHalfTransitions.size(); iTrans++) for (u32 iTrans = 0; iTrans < pSet->mHalfTransitions.size(); iTrans++)
pSet->mHalfTransitions[iTrans].pMetaTrans->GetUniquePrimitives(UniquePrimitives); pSet->mHalfTransitions[iTrans].pMetaTrans->GetUniquePrimitives(UniquePrimitives);
@ -181,17 +182,20 @@ void CAnimSetLoader::ProcessPrimitives()
} }
// Add animations referenced by default transition // Add animations referenced by default transition
std::set<CAnimPrimitive> DefaultTransPrimitives; if (pSet->mpDefaultTransition)
pSet->mpDefaultTransition->GetUniquePrimitives(DefaultTransPrimitives);
for (u32 iChar = 0; iChar < pSet->mCharacters.size(); iChar++)
{ {
SSetCharacter& rChar = pSet->mCharacters[iChar]; std::set<CAnimPrimitive> DefaultTransPrimitives;
pSet->mpDefaultTransition->GetUniquePrimitives(DefaultTransPrimitives);
for (auto Iter = DefaultTransPrimitives.begin(); Iter != DefaultTransPrimitives.end(); Iter++) for (u32 iChar = 0; iChar < pSet->mCharacters.size(); iChar++)
{ {
const CAnimPrimitive& rkPrim = *Iter; SSetCharacter& rChar = pSet->mCharacters[iChar];
rChar.UsedAnimationIndices.insert(rkPrim.ID());
for (auto Iter = DefaultTransPrimitives.begin(); Iter != DefaultTransPrimitives.end(); Iter++)
{
const CAnimPrimitive& rkPrim = *Iter;
rChar.UsedAnimationIndices.insert(rkPrim.ID());
}
} }
} }

View File

@ -12,6 +12,8 @@ class CResourceEntry;
class CWorldEditor; class CWorldEditor;
class IEditor; class IEditor;
const int gkTickFrequencyMS = 8;
class CEditorApplication : public QApplication class CEditorApplication : public QApplication
{ {
Q_OBJECT Q_OBJECT
@ -35,6 +37,9 @@ public:
inline CResourceBrowser* ResourceBrowser() const { return mpResourceBrowser; } inline CResourceBrowser* ResourceBrowser() const { return mpResourceBrowser; }
inline CProjectOverviewDialog* ProjectDialog() const { return mpProjectDialog; } inline CProjectOverviewDialog* ProjectDialog() const { return mpProjectDialog; }
inline void SetEditorTicksEnabled(bool Enabled) { Enabled ? mRefreshTimer.start(gkTickFrequencyMS) : mRefreshTimer.stop(); }
inline bool AreEditorTicksEnabled() const { return mRefreshTimer.isActive(); }
public slots: public slots:
void AddEditor(IEditor *pEditor); void AddEditor(IEditor *pEditor);
void TickEditors(); void TickEditors();

View File

@ -0,0 +1,360 @@
#include "CExportGameDialog.h"
#include "ui_CExportGameDialog.h"
#include "UICommon.h"
#include <Common/AssertMacro.h>
#include <Core/GameProject/CAssetNameMap.h>
#include <Core/GameProject/CGameExporter.h>
#include <Core/GameProject/CGameInfo.h>
#include <Core/Resource/Script/CMasterTemplate.h>
#include <QComboBox>
#include <QDialogButtonBox>
#include <QFileDialog>
#include <QLabel>
#include <QVBoxLayout>
#include <nod/nod.hpp>
CExportGameDialog::CExportGameDialog(const QString& rkIsoPath, const QString& rkExportDir, QWidget *pParent /*= 0*/)
: QDialog(pParent)
, mpUI(new Ui::CExportGameDialog)
, mGame(eUnknownGame)
, mRegion(eRegion_Unknown)
, mTrilogy(false)
, mExportSuccess(true)
{
mpUI->setupUi(this);
// Set up disc
TWideString StrPath = TO_TWIDESTRING(rkIsoPath);
mpDisc = nod::OpenDiscFromImage(*StrPath).release();
if (ValidateGame())
{
mBuildVer = FindBuildVersion();
InitUI(rkExportDir);
TString IsoName = TO_TSTRING(rkIsoPath).GetFileName();
setWindowTitle(QString("Export Settings - %1").arg( TO_QSTRING(IsoName) ));
}
else
{
if (!mTrilogy)
UICommon::ErrorMsg(this, "Invalid ISO!");
delete mpDisc;
mpDisc = nullptr;
}
}
CExportGameDialog::~CExportGameDialog()
{
delete mpUI;
delete mpDisc;
}
void RecursiveAddToTree(const nod::Node *pkNode, QTreeWidgetItem *pParent);
void CExportGameDialog::InitUI(QString ExportDir)
{
ASSERT(mpDisc != nullptr);
// Export settings
ExportDir.replace('/', '\\');
TWideString DefaultNameMapPath = CAssetNameMap::DefaultNameMapPath();
if (!FileUtil::Exists(DefaultNameMapPath)) DefaultNameMapPath = L"";
TWideString DefaultGameInfoPath = CGameInfo::GetDefaultGameInfoPath(mGame);
if (!FileUtil::Exists(DefaultGameInfoPath)) DefaultGameInfoPath = L"";
mpUI->OutputDirectoryLineEdit->setText(ExportDir);
mpUI->AssetNameMapLineEdit->setText(TO_QSTRING(DefaultNameMapPath));
mpUI->GameEditorInfoLineEdit->setText(TO_QSTRING(DefaultGameInfoPath));
// Info boxes
mpUI->GameTitleLineEdit->setText( TO_QSTRING(mGameTitle) );
mpUI->GameIdLineEdit->setText( TO_QSTRING(mGameID) );
mpUI->BuildVersionLineEdit->setText( QString::number(mBuildVer) );
mpUI->RegionLineEdit->setText( mRegion == eRegion_NTSC ? "NTSC" :
mRegion == eRegion_PAL ? "PAL" : "JPN" );
// Disc tree widget
nod::Partition *pPartition = mpDisc->getDataPartition();
ASSERT(pPartition);
const nod::Node *pkDiscRoot = &pPartition->getFSTRoot();
if (mTrilogy)
pkDiscRoot = &*pkDiscRoot->find( GetGameShortName(mGame).ToStdString() );
QTreeWidgetItem *pTreeRoot = new QTreeWidgetItem((QTreeWidgetItem*) nullptr, QStringList(QString("Disc")));
mpUI->DiscFstTreeWidget->addTopLevelItem(pTreeRoot);
RecursiveAddToTree(pkDiscRoot, pTreeRoot);
pTreeRoot->setExpanded(true);
// Signals and slots
connect(mpUI->OutputDirectoryBrowseButton, SIGNAL(pressed()), this, SLOT(BrowseOutputDirectory()));
connect(mpUI->AssetNameMapBrowseButton, SIGNAL(pressed()), this, SLOT(BrowseAssetNameMap()));
connect(mpUI->GameEditorInfoBrowseButton, SIGNAL(pressed()), this, SLOT(BrowseGameEditorInfo()));
connect(mpUI->CancelButton, SIGNAL(pressed()), this, SLOT(close()));
connect(mpUI->ExportButton, SIGNAL(pressed()), this, SLOT(Export()));
}
bool CExportGameDialog::ValidateGame()
{
if (!mpDisc) return false;
const nod::Header& rkHeader = mpDisc->getHeader();
mGameTitle = rkHeader.m_gameTitle;
mGameID = TString(6, 0);
memcpy(&mGameID[0], rkHeader.m_gameID, 6);
// Check region byte
switch (mGameID[3])
{
case 'E':
mRegion = eRegion_NTSC;
break;
case 'P':
mRegion = eRegion_PAL;
break;
case 'J':
mRegion = eRegion_JPN;
break;
default:
return false;
}
// Set region byte to X so we don't need to compare every regional variant of the ID
// Then figure out what game this is
CFourCC GameID(&mGameID[0]);
GameID[3] = 'X';
switch (GameID.ToLong())
{
case IFOURCC('GM8X'):
// This ID is normally MP1, but it's used by the MP1 NTSC demo and the MP2 bonus disc demo as well
if (strcmp(rkHeader.m_gameTitle, "Long Game Name") == 0)
{
// todo - not handling demos yet
return false;
}
mGame = ePrime;
break;
case IFOURCC('G2MX'):
// Echoes, but also appears in the MP3 proto
if (mGameID[4] == 'A' && mGameID[5] == 'B')
mGame = eCorruptionProto;
else
mGame = eEchoes;
break;
case IFOURCC('RM3X'):
mGame = eCorruption;
break;
case IFOURCC('SF8X'):
mGame = eReturns;
break;
case IFOURCC('R3MX'):
// Trilogy
mTrilogy = true;
if (!RequestTrilogyGame()) return false;
break;
case IFOURCC('R3IX'):
// MP1 Wii de Asobu
case IFOURCC('R32X'):
// MP2 Wii de Asobu
default:
// Unrecognized game ID
return false;
}
return true;
}
bool CExportGameDialog::RequestTrilogyGame()
{
QDialog Dialog;
Dialog.setWindowTitle("Select Trilogy Game");
QLabel Label("You have selected a Metroid Prime: Trilogy ISO. Please pick a game to export:", &Dialog);
QComboBox ComboBox(&Dialog);
ComboBox.addItem("Metroid Prime");
ComboBox.addItem("Metroid Prime 2: Echoes");
ComboBox.addItem("Metroid Prime 3: Corruption");
QDialogButtonBox ButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, &Dialog);
connect(&ButtonBox, SIGNAL(accepted()), &Dialog, SLOT(accept()));
connect(&ButtonBox, SIGNAL(rejected()), &Dialog, SLOT(reject()));
QVBoxLayout Layout;
Layout.addWidget(&Label);
Layout.addWidget(&ComboBox);
Layout.addWidget(&ButtonBox);
Dialog.setLayout(&Layout);
int Result = Dialog.exec();
if (Result == QDialog::Accepted)
{
switch (ComboBox.currentIndex())
{
case 0: mGame = ePrime; break;
case 1: mGame = eEchoes; break;
case 2: mGame = eCorruption; break;
}
return true;
}
else return false;
}
float CExportGameDialog::FindBuildVersion()
{
ASSERT(mpDisc != nullptr);
// MP1 demo build doesn't have a build version
if (mGame == ePrimeDemo) return 0.f;
// Get DOL buffer
std::unique_ptr<uint8_t[]> pDolData = mpDisc->getDataPartition()->getDOLBuf();
u32 DolSize = (u32) mpDisc->getDataPartition()->getDOLSize();
// Find build info string
const char *pkSearchText = "!#$MetroidBuildInfo!#$";
const int SearchTextSize = strlen(pkSearchText);
for (u32 SearchIdx = 0; SearchIdx < DolSize - SearchTextSize + 1; SearchIdx++)
{
int Match = 0;
while (pDolData[SearchIdx + Match] == pkSearchText[Match] && Match < SearchTextSize)
Match++;
if (Match == SearchTextSize)
{
// Found the build info string; extract version number
TString BuildInfo = (char*) &pDolData[SearchIdx + SearchTextSize];
int BuildVerStart = BuildInfo.IndexOfPhrase("Build v") + 7;
ASSERT(BuildVerStart != 6);
return BuildInfo.SubString(BuildVerStart, 5).ToFloat();
}
}
Log::Error("Failed to find MetroidBuildInfo string. Build Version will be set to 0.");
return 0.f;
}
void RecursiveAddToTree(const nod::Node *pkNode, QTreeWidgetItem *pParent)
{
// Get sorted list of nodes
std::list<const nod::Node*> NodeList;
for (nod::Node::DirectoryIterator Iter = pkNode->begin(); Iter != pkNode->end(); ++Iter)
NodeList.push_back(&*Iter);
NodeList.sort([](const nod::Node *pkLeft, const nod::Node *pkRight) -> bool
{
if (pkLeft->getKind() != pkRight->getKind())
return pkLeft->getKind() == nod::Node::Kind::Directory;
else
return TString(pkLeft->getName()).ToUpper() < TString(pkRight->getName()).ToUpper();
});
// Add nodes to tree
static const QIcon skFileIcon = QIcon(":/icons/New.png");
static const QIcon skDirIcon = QIcon(":/icons/Open_24px.png");
for (auto Iter = NodeList.begin(); Iter != NodeList.end(); Iter++)
{
const nod::Node *pkNode = *Iter;
bool IsDir = pkNode->getKind() == nod::Node::Kind::Directory;
QTreeWidgetItem *pItem = new QTreeWidgetItem(pParent, QStringList(QString::fromStdString(pkNode->getName())) );
pItem->setIcon(0, QIcon(IsDir ? skDirIcon : skFileIcon));
if (IsDir)
RecursiveAddToTree(pkNode, pItem);
}
}
void CExportGameDialog::BrowseOutputDirectory()
{
QString NewOutputDir = UICommon::OpenDirDialog(this, "Choose export directory");
if (!NewOutputDir.isEmpty()) mpUI->OutputDirectoryLineEdit->setText(NewOutputDir);
}
void CExportGameDialog::BrowseAssetNameMap()
{
QString Filter = "*." + TO_QSTRING(CAssetNameMap::GetExtension());
QString NewNameMap = UICommon::OpenFileDialog(this, "Choose Asset Name Map", Filter);
if (!NewNameMap.isEmpty()) mpUI->AssetNameMapLineEdit->setText(NewNameMap);
}
void CExportGameDialog::BrowseGameEditorInfo()
{
QString Filter = "*." + TO_QSTRING(CGameInfo::GetExtension());
QString NewGameInfo = UICommon::OpenFileDialog(this, "Choose Game Editor Info", Filter);
if (!NewGameInfo.isEmpty()) mpUI->GameEditorInfoLineEdit->setText(NewGameInfo);
}
void CExportGameDialog::Export()
{
QString ExportDir = mpUI->OutputDirectoryLineEdit->text();
QString NameMapPath = mpUI->AssetNameMapLineEdit->text();
QString GameInfoPath = mpUI->GameEditorInfoLineEdit->text();
// Validate export dir
if (ExportDir.isEmpty())
{
UICommon::ErrorMsg(this, "Please specify an output directory!");
return;
}
else 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;
}
// Verify name map path and game info path
if (!NameMapPath.isEmpty() && !FileUtil::Exists(TO_TSTRING(NameMapPath)))
{
UICommon::ErrorMsg(this, "The Asset Name Map path is invalid!");
return;
}
if (!GameInfoPath.isEmpty() && !FileUtil::Exists(TO_TSTRING(GameInfoPath)))
{
UICommon::ErrorMsg(this, "The Game Editor Info path is invalid!");
return;
}
// Do export
close();
CAssetNameMap NameMap;
if (!NameMapPath.isEmpty())
NameMap.LoadAssetNames( TO_TSTRING(NameMapPath) );
CGameInfo GameInfo;
if (!GameInfoPath.isEmpty())
GameInfo.LoadGameInfo( TO_TSTRING(GameInfoPath) );
CGameExporter Exporter(mGame, mRegion, mGameTitle, mGameID, mBuildVer);
TString StrExportDir = TO_TSTRING(ExportDir);
StrExportDir.EnsureEndsWith('\\');
mExportSuccess = Exporter.Export(mpDisc, StrExportDir, &NameMap, &GameInfo);
if (!mExportSuccess)
UICommon::ErrorMsg(this, "Export failed!");
else
mNewProjectPath = TO_QSTRING(Exporter.ProjectPath());
}

View File

@ -0,0 +1,52 @@
#ifndef CEXPORTGAMEDIALOG_H
#define CEXPORTGAMEDIALOG_H
#include <Common/EGame.h>
#include <Core/GameProject/CGameProject.h>
#include <QDialog>
#include <QString>
#include <nod/DiscBase.hpp>
namespace Ui {
class CExportGameDialog;
}
class CExportGameDialog : public QDialog
{
Q_OBJECT
Ui::CExportGameDialog *mpUI;
nod::DiscBase *mpDisc;
TString mGameTitle;
TString mGameID;
EGame mGame;
ERegion mRegion;
float mBuildVer;
bool mTrilogy;
bool mExportSuccess;
QString mNewProjectPath;
public:
explicit CExportGameDialog(const QString& rkIsoPath, const QString& rkExportDir, QWidget *pParent = 0);
~CExportGameDialog();
void InitUI(QString ExportDir);
bool ValidateGame();
bool RequestTrilogyGame();
float FindBuildVersion();
// Accessors
inline bool HasValidDisc() const { return mpDisc != nullptr; }
inline bool ExportSucceeded() const { return mExportSuccess; }
inline QString ProjectPath() const { return mNewProjectPath; }
public slots:
void BrowseOutputDirectory();
void BrowseAssetNameMap();
void BrowseGameEditorInfo();
void Export();
};
#endif // CEXPORTGAMEDIALOG_H

View File

@ -0,0 +1,271 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>CExportGameDialog</class>
<widget class="QDialog" name="CExportGameDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>339</width>
<height>642</height>
</rect>
</property>
<property name="windowTitle">
<string>Export Settings</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="HeaderLabel">
<property name="font">
<font>
<pointsize>14</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Game Export Settings</string>
</property>
</widget>
</item>
<item>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="OutputDirectoryLabel">
<property name="text">
<string>Output Directory:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="OutputDirectoryLineEdit">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QPushButton" name="OutputDirectoryBrowseButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>20</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="AssetNameMapLabel">
<property name="text">
<string>Asset Name Map:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="AssetNameMapLineEdit">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QPushButton" name="AssetNameMapBrowseButton">
<property name="maximumSize">
<size>
<width>20</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="GameEditorInfoLabel">
<property name="text">
<string>Game Editor Info:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="GameEditorInfoLineEdit">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QPushButton" name="GameEditorInfoBrowseButton">
<property name="maximumSize">
<size>
<width>20</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>...</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QGroupBox" name="GameInfoGroupBox">
<property name="title">
<string>Game</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="GameTitleLabel">
<property name="text">
<string>Game Title:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="GameTitleLineEdit">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="GameIdLabel">
<property name="text">
<string>Game ID:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="GameIdLineEdit">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="RegionLabel">
<property name="text">
<string>Region:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="RegionLineEdit">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="BuildVersionLabel">
<property name="text">
<string>Build Version:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLineEdit" name="BuildVersionLineEdit">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QTreeWidget" name="DiscFstTreeWidget">
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="verticalScrollMode">
<enum>QAbstractItemView::ScrollPerPixel</enum>
</property>
<property name="indentation">
<number>10</number>
</property>
<attribute name="headerVisible">
<bool>false</bool>
</attribute>
<column>
<property name="text">
<string notr="true">1</string>
</property>
</column>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="ExportButton">
<property name="text">
<string>Export</string>
</property>
<property name="default">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="CancelButton">
<property name="text">
<string>Cancel</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -1,6 +1,7 @@
#include "CProjectOverviewDialog.h" #include "CProjectOverviewDialog.h"
#include "ui_CProjectOverviewDialog.h" #include "ui_CProjectOverviewDialog.h"
#include "CEditorApplication.h" #include "CEditorApplication.h"
#include "CExportGameDialog.h"
#include "UICommon.h" #include "UICommon.h"
#include "Editor/ResourceBrowser/CResourceBrowser.h" #include "Editor/ResourceBrowser/CResourceBrowser.h"
#include <Common/AssertMacro.h> #include <Common/AssertMacro.h>
@ -28,17 +29,13 @@ CProjectOverviewDialog::~CProjectOverviewDialog()
delete mpUI; delete mpUI;
} }
void CProjectOverviewDialog::OpenProject() void CProjectOverviewDialog::InternalLoadProject(const QString& rkPath)
{ {
// Open project file
QString ProjPath = QFileDialog::getOpenFileName(this, "Open Project", "", "Game Project (*.prj)");
if (ProjPath.isEmpty()) return;
// Load project // Load project
TWideString Path = TO_TWIDESTRING(ProjPath); TWideString Path = TO_TWIDESTRING(rkPath);
CGameProject *pNewProj = new CGameProject(Path.GetFileDirectory()); CGameProject *pNewProj = CGameProject::LoadProject(Path);
if (pNewProj->Load(Path)) if (pNewProj)
{ {
if (mpProject) delete mpProject; if (mpProject) delete mpProject;
mpProject = pNewProj; mpProject = pNewProj;
@ -49,49 +46,34 @@ void CProjectOverviewDialog::OpenProject()
} }
else else
{
Log::Error("Failed to load project"); Log::Error("Failed to load project");
delete pNewProj; }
}
void CProjectOverviewDialog::OpenProject()
{
// Open project file
QString ProjPath = UICommon::OpenFileDialog(this, "Open Project", "Game Project (*.prj)");
if (!ProjPath.isEmpty()) InternalLoadProject(ProjPath);
} }
void CProjectOverviewDialog::ExportGame() void CProjectOverviewDialog::ExportGame()
{ {
// TEMP - hardcoded names for convenience. will remove later! QString IsoPath = UICommon::OpenFileDialog(this, "Select ISO", "*.iso *.gcm *.tgc *.wbfs");
#define USE_HARDCODED_GAME_ROOT 0 if (IsoPath.isEmpty()) return;
#define USE_HARDCODED_EXPORT_DIR 0
#if USE_HARDCODED_GAME_ROOT QString ExportDir = UICommon::OpenDirDialog(this, "Select output export directory");
QString GameRoot = "E:/Unpacked/Metroid Prime";
#else
QString GameRoot = QFileDialog::getExistingDirectory(this, "Select game root directory");
if (GameRoot.isEmpty()) return;
#endif
#if USE_HARDCODED_EXPORT_DIR
QString ExportDir = "E:/Unpacked/ExportTest";
#else
QString ExportDir = QFileDialog::getExistingDirectory(this, "Select output export directory");
if (ExportDir.isEmpty()) return; if (ExportDir.isEmpty()) return;
#endif
// Verify valid game root by checking if opening.bnr exists CExportGameDialog ExportDialog(IsoPath, ExportDir, this);
TString OpeningBNR = TO_TSTRING(GameRoot) + "/opening.bnr"; if (ExportDialog.HasValidDisc()) ExportDialog.exec();
if (!FileUtil::Exists(OpeningBNR.ToUTF16()))
if (ExportDialog.ExportSucceeded())
{ {
QMessageBox::warning(this, "Error", "Error; this is not a valid game root directory!"); int OpenChoice = QMessageBox::information(this, "Export complete", "Export finished successfully! Open new project?", QMessageBox::Yes, QMessageBox::No);
return;
}
// Verify export directory is empty if (OpenChoice == QMessageBox::Yes)
if (!FileUtil::IsEmpty(TO_TSTRING(ExportDir))) InternalLoadProject(ExportDialog.ProjectPath());
{
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();
} }
void CProjectOverviewDialog::SetupWorldsList() void CProjectOverviewDialog::SetupWorldsList()

View File

@ -24,6 +24,9 @@ public:
explicit CProjectOverviewDialog(QWidget *pParent = 0); explicit CProjectOverviewDialog(QWidget *pParent = 0);
~CProjectOverviewDialog(); ~CProjectOverviewDialog();
protected:
void InternalLoadProject(const QString& rkPath);
public slots: public slots:
void OpenProject(); void OpenProject();
void ExportGame(); void ExportGame();

View File

@ -7,7 +7,6 @@
#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/GameProject/CResourceStore.h> #include <Core/GameProject/CResourceStore.h>
#include <QFileDialog> #include <QFileDialog>
@ -26,7 +25,6 @@ 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()
@ -245,35 +243,3 @@ 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_GAME_ROOT 0
#define USE_HARDCODED_EXPORT_DIR 1
#if USE_HARDCODED_GAME_ROOT
QString GameRoot = "E:/Unpacked/Metroid Prime 2";
#else
QString GameRoot = QFileDialog::getExistingDirectory(this, "Select game root directory");
if (GameRoot.isEmpty()) return;
#endif
#if USE_HARDCODED_EXPORT_DIR
QString ExportDir = "E:/Unpacked/ExportTest";
#else
QString ExportDir = QFileDialog::getExistingDirectory(this, "Select output export directory");
if (ExportDir.isEmpty()) return;
#endif
// Verify valid game root by checking if opening.bnr exists
TString OpeningBNR = TO_TSTRING(GameRoot) + "/opening.bnr";
if (!FileUtil::Exists(OpeningBNR.ToUTF16()))
{
QMessageBox::warning(this, "Error", "Error; this is not a valid game root directory!");
return;
}
CGameExporter Exporter(TO_TSTRING(GameRoot), TO_TSTRING(ExportDir));
Exporter.Export();
}

View File

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

View File

@ -35,6 +35,8 @@ CONFIG(debug, debug|release) {
-L$$EXTERNALS_DIR/assimp/lib/ -lassimp-vc140-mtd \ -L$$EXTERNALS_DIR/assimp/lib/ -lassimp-vc140-mtd \
-L$$EXTERNALS_DIR/boost_1_63_0/lib64-msvc-14.0 -llibboost_filesystem-vc140-mt-gd-1_63 \ -L$$EXTERNALS_DIR/boost_1_63_0/lib64-msvc-14.0 -llibboost_filesystem-vc140-mt-gd-1_63 \
-L$$EXTERNALS_DIR/lzo-2.09/lib/ -llzo2d \ -L$$EXTERNALS_DIR/lzo-2.09/lib/ -llzo2d \
-L$$EXTERNALS_DIR/nodtool/build/debug/lib/ -lnod \
-L$$EXTERNALS_DIR/nodtool/build/debug/logvisor/ -llogvisor \
-L$$EXTERNALS_DIR/tinyxml2/lib/ -ltinyxml2d \ -L$$EXTERNALS_DIR/tinyxml2/lib/ -ltinyxml2d \
-L$$EXTERNALS_DIR/zlib/lib/ -lzlibd -L$$EXTERNALS_DIR/zlib/lib/ -lzlibd
@ -62,6 +64,8 @@ CONFIG(release, debug|release) {
-L$$EXTERNALS_DIR/assimp/lib/ -lassimp-vc140-mt \ -L$$EXTERNALS_DIR/assimp/lib/ -lassimp-vc140-mt \
-L$$EXTERNALS_DIR/boost_1_63_0/lib64-msvc-14.0 -llibboost_filesystem-vc140-mt-1_63 \ -L$$EXTERNALS_DIR/boost_1_63_0/lib64-msvc-14.0 -llibboost_filesystem-vc140-mt-1_63 \
-L$$EXTERNALS_DIR/lzo-2.09/lib/ -llzo2 \ -L$$EXTERNALS_DIR/lzo-2.09/lib/ -llzo2 \
-L$$EXTERNALS_DIR/nodtool/build/release/lib/ -lnod \
-L$$EXTERNALS_DIR/nodtool/build/release/logvisor -llogvisor \
-L$$EXTERNALS_DIR/tinyxml2/lib/ -ltinyxml2 \ -L$$EXTERNALS_DIR/tinyxml2/lib/ -ltinyxml2 \
-L$$EXTERNALS_DIR/zlib/lib/ -lzlib -L$$EXTERNALS_DIR/zlib/lib/ -lzlib
@ -84,6 +88,8 @@ INCLUDEPATH += $$PWE_MAIN_INCLUDE \
$$EXTERNALS_DIR/glew-2.0.0/include \ $$EXTERNALS_DIR/glew-2.0.0/include \
$$EXTERNALS_DIR/glm/glm \ $$EXTERNALS_DIR/glm/glm \
$$EXTERNALS_DIR/lzo-2.09/include \ $$EXTERNALS_DIR/lzo-2.09/include \
$$EXTERNALS_DIR/nodtool/include \
$$EXTERNALS_DIR/nodtool/logvisor/include \
$$EXTERNALS_DIR/tinyxml2/include \ $$EXTERNALS_DIR/tinyxml2/include \
$$EXTERNALS_DIR/zlib/include $$EXTERNALS_DIR/zlib/include
@ -174,7 +180,8 @@ HEADERS += \
ResourceBrowser/CVirtualDirectoryModel.h \ ResourceBrowser/CVirtualDirectoryModel.h \
CEditorApplication.h \ CEditorApplication.h \
IEditor.h \ IEditor.h \
Widgets/CResourceSelector.h Widgets/CResourceSelector.h \
CExportGameDialog.h
# Source Files # Source Files
SOURCES += \ SOURCES += \
@ -238,7 +245,8 @@ SOURCES += \
CProjectOverviewDialog.cpp \ CProjectOverviewDialog.cpp \
ResourceBrowser/CResourceBrowser.cpp \ ResourceBrowser/CResourceBrowser.cpp \
CEditorApplication.cpp \ CEditorApplication.cpp \
Widgets/CResourceSelector.cpp Widgets/CResourceSelector.cpp \
CExportGameDialog.cpp
# UI Files # UI Files
FORMS += \ FORMS += \
@ -262,4 +270,5 @@ FORMS += \
CharacterEditor/CCharacterEditor.ui \ CharacterEditor/CCharacterEditor.ui \
WorldEditor/CCollisionRenderSettingsDialog.ui \ WorldEditor/CCollisionRenderSettingsDialog.ui \
CProjectOverviewDialog.ui \ CProjectOverviewDialog.ui \
ResourceBrowser/CResourceBrowser.ui ResourceBrowser/CResourceBrowser.ui \
CExportGameDialog.ui

View File

@ -292,7 +292,7 @@ void CResourceBrowser::OnResourceSelectionChanged(const QModelIndex& rkNewIndex,
void CResourceBrowser::OnImportPakContentsTxt() void CResourceBrowser::OnImportPakContentsTxt()
{ {
QStringList PathList = QFileDialog::getOpenFileNames(this, "Open pak contents list", "", "*.pak.contents.txt"); QStringList PathList = UICommon::OpenFilesDialog(this, "Open pak contents list", "*.pak.contents.txt");
if (PathList.isEmpty()) return; if (PathList.isEmpty()) return;
foreach(const QString& rkPath, PathList) foreach(const QString& rkPath, PathList)
@ -326,7 +326,7 @@ void CResourceBrowser::OnImportNamesFromAssetNameMap()
void CResourceBrowser::ExportAssetNames() void CResourceBrowser::ExportAssetNames()
{ {
QString OutFile = QFileDialog::getSaveFileName(this, "Export asset name map", "../resources/gameinfo/", "*.xml"); QString OutFile = UICommon::SaveFileDialog(this, "Export asset name map", "*.xml", "../resources/gameinfo/");
if (OutFile.isEmpty()) return; if (OutFile.isEmpty()) return;
CAssetNameMap NameMap; CAssetNameMap NameMap;

View File

@ -1,8 +1,11 @@
#ifndef UICOMMON #ifndef UICOMMON
#define UICOMMON #define UICOMMON
#include "CEditorApplication.h"
#include <Common/TString.h> #include <Common/TString.h>
#include <QFileDialog>
#include <QMap> #include <QMap>
#include <QMessageBox>
#include <QString> #include <QString>
// App string variable handling - automatically fill in application name/version // App string variable handling - automatically fill in application name/version
@ -57,7 +60,54 @@ inline TWideString ToTWideString(const QString& rkStr)
{ {
return TWideString(rkStr.toStdWString()); return TWideString(rkStr.toStdWString());
} }
// QFileDialog wrappers
// Note: pause editor ticks while file dialogs are open because otherwise there's a bug that makes it really difficult to tab out and back in
#define PUSH_TICKS_ENABLED \
bool TicksEnabled = gpEdApp->AreEditorTicksEnabled(); \
gpEdApp->SetEditorTicksEnabled(false);
#define POP_TICKS_ENABLED \
gpEdApp->SetEditorTicksEnabled(TicksEnabled);
inline QString OpenFileDialog(QWidget *pParent, const QString& rkCaption, const QString& rkFilter, const QString& rkStartingDir = "")
{
PUSH_TICKS_ENABLED;
QString Result = QFileDialog::getOpenFileName(pParent, rkCaption, rkStartingDir, rkFilter);
POP_TICKS_ENABLED;
return Result;
} }
inline QStringList OpenFilesDialog(QWidget *pParent, const QString& rkCaption, const QString& rkFilter, const QString& rkStartingDir = "")
{
PUSH_TICKS_ENABLED;
QStringList Result = QFileDialog::getOpenFileNames(pParent, rkCaption, rkStartingDir, rkFilter);
POP_TICKS_ENABLED;
return Result;
}
inline QString SaveFileDialog(QWidget *pParent, const QString& rkCaption, const QString& rkFilter, const QString& rkStartingDir = "")
{
PUSH_TICKS_ENABLED;
QString Result = QFileDialog::getSaveFileName(pParent, rkCaption, rkStartingDir, rkFilter);
POP_TICKS_ENABLED;
return Result;
}
inline QString OpenDirDialog(QWidget *pParent, const QString& rkCaption, const QString& rkStartingDir = "")
{
PUSH_TICKS_ENABLED;
QString Result = QFileDialog::getExistingDirectory(pParent, rkCaption, rkStartingDir);
POP_TICKS_ENABLED;
return Result;
}
// QMessageBox wrappers
inline void ErrorMsg(QWidget *pParent, QString ErrorText)
{
QMessageBox::warning(pParent, "Error", ErrorText);
}
} // UICommon Namespace End
#endif // UICOMMON #endif // UICOMMON

View File

@ -215,7 +215,7 @@ void WResourceSelector::OnBrowseButtonClicked()
Filter += UICommon::ExtensionFilterString(mSupportedExtensions[iExt]); Filter += UICommon::ExtensionFilterString(mSupportedExtensions[iExt]);
} }
QString NewRes = QFileDialog::getOpenFileName(this, "Select resource", "", Filter); QString NewRes = UICommon::OpenFileDialog(this, "Select resource", Filter);
if (!NewRes.isEmpty()) if (!NewRes.isEmpty())
{ {

View File

@ -63,7 +63,7 @@ QString CRepackInfoDialog::OutputPak() const
// ************ PUBLIC SLOTS ************ // ************ PUBLIC SLOTS ************
void CRepackInfoDialog::BrowseFolderClicked() void CRepackInfoDialog::BrowseFolderClicked()
{ {
QString Folder = QFileDialog::getExistingDirectory(this, "Choose directory"); QString Folder = UICommon::OpenDirDialog(this, "Choose directory");
if (!Folder.isEmpty()) if (!Folder.isEmpty())
{ {
@ -74,7 +74,7 @@ void CRepackInfoDialog::BrowseFolderClicked()
void CRepackInfoDialog::BrowseListClicked() void CRepackInfoDialog::BrowseListClicked()
{ {
QString List = QFileDialog::getOpenFileName(this, "Open list file", "", "All supported files (*.txt *.pak);;Text file (*.txt);;Pak file (*.pak)"); QString List = UICommon::OpenFileDialog(this, "Open list file", "All supported files (*.txt *.pak);;Text file (*.txt);;Pak file (*.pak)");
if (!List.isEmpty()) if (!List.isEmpty())
{ {
@ -94,7 +94,7 @@ void CRepackInfoDialog::BrowseListClicked()
void CRepackInfoDialog::BrowseOutPakClicked() void CRepackInfoDialog::BrowseOutPakClicked()
{ {
QString Pak = QFileDialog::getSaveFileName(this, "Save pak", "", "Pak File (*.pak)"); QString Pak = UICommon::SaveFileDialog(this, "Save pak", "Pak File (*.pak)");
if (!Pak.isEmpty()) if (!Pak.isEmpty())
{ {