mirror of
https://github.com/AxioDL/PrimeWorldEditor.git
synced 2025-05-25 00:31:24 +00:00
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:
parent
fbdf9023d1
commit
eca833cf89
@ -9,6 +9,9 @@
|
||||
|
||||
#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)
|
||||
// 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
|
||||
{
|
||||
|
@ -65,3 +65,27 @@ void Serialize(IArchive& rArc, EGame& rGame)
|
||||
rArc.SerializePrimitive(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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -28,4 +28,14 @@ TString GetGameName(EGame Game);
|
||||
TString GetGameShortName(EGame Game);
|
||||
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
|
||||
|
@ -54,7 +54,7 @@ bool IsEmpty(const TWideString& rkDirPath)
|
||||
return is_empty(*rkDirPath);
|
||||
}
|
||||
|
||||
bool CreateDirectory(const TWideString& rkNewDir)
|
||||
bool MakeDirectory(const TWideString& rkNewDir)
|
||||
{
|
||||
if (!IsValidPath(rkNewDir, true))
|
||||
{
|
||||
@ -73,7 +73,7 @@ bool CopyFile(const TWideString& rkOrigPath, const TWideString& rkNewPath)
|
||||
return false;
|
||||
}
|
||||
|
||||
CreateDirectory(rkNewPath.GetFileDirectory());
|
||||
MakeDirectory(rkNewPath.GetFileDirectory());
|
||||
boost::system::error_code Error;
|
||||
copy(*rkOrigPath, *rkNewPath, Error);
|
||||
return (Error == boost::system::errc::success);
|
||||
@ -87,7 +87,7 @@ bool CopyDirectory(const TWideString& rkOrigPath, const TWideString& rkNewPath)
|
||||
return false;
|
||||
}
|
||||
|
||||
CreateDirectory(rkNewPath.GetFileDirectory());
|
||||
MakeDirectory(rkNewPath.GetFileDirectory());
|
||||
boost::system::error_code Error;
|
||||
copy_directory(*rkOrigPath, *rkNewPath, Error);
|
||||
return (Error == boost::system::errc::success);
|
||||
|
@ -14,7 +14,7 @@ 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 MakeDirectory(const TWideString& rkNewDir);
|
||||
bool CopyFile(const TWideString& rkOrigPath, const TWideString& rkNewPath);
|
||||
bool CopyDirectory(const TWideString& rkOrigPath, const TWideString& rkNewPath);
|
||||
bool MoveFile(const TWideString& rkOldPath, const TWideString& rkNewPath);
|
||||
|
@ -29,6 +29,8 @@ CONFIG (debug, debug|release) {
|
||||
-L$$BUILD_DIR/Math/ -lMathd \
|
||||
-L$$EXTERNALS_DIR/assimp/lib/ -lassimp-vc140-mtd \
|
||||
-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/zlib/lib/ -lzlibd
|
||||
|
||||
@ -51,6 +53,8 @@ CONFIG (release, debug|release) {
|
||||
-L$$BUILD_DIR/Math/ -lMath \
|
||||
-L$$EXTERNALS_DIR/assimp/lib/ -lassimp-vc140-mt \
|
||||
-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/zlib/lib/ -lzlib
|
||||
|
||||
@ -72,6 +76,8 @@ INCLUDEPATH += $$PWE_MAIN_INCLUDE \
|
||||
$$EXTERNALS_DIR/glew-2.0.0/include \
|
||||
$$EXTERNALS_DIR/glm/glm \
|
||||
$$EXTERNALS_DIR/lzo-2.09/include \
|
||||
$$EXTERNALS_DIR/nodtool/include \
|
||||
$$EXTERNALS_DIR/nodtool/logvisor/include \
|
||||
$$EXTERNALS_DIR/tinyxml2/include \
|
||||
$$EXTERNALS_DIR/zlib/include
|
||||
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include <memory>
|
||||
|
||||
const TString gkAssetMapPath = "..\\resources\\gameinfo\\AssetNameMap.xml";
|
||||
const TString gkAssetMapExt = "xml";
|
||||
|
||||
class CAssetNameMap
|
||||
{
|
||||
@ -36,6 +37,9 @@ public:
|
||||
void SaveAssetNames(TString Path = gkAssetMapPath);
|
||||
bool GetNameInfo(CAssetID ID, TString& rOutDirectory, TString& rOutName);
|
||||
void CopyFromStore(CResourceStore *pStore);
|
||||
|
||||
inline static TString DefaultNameMapPath() { return gkAssetMapPath; }
|
||||
inline static TString GetExtension() { return gkAssetMapExt; }
|
||||
};
|
||||
|
||||
#endif // CASSETNAMEMAP
|
||||
|
@ -12,65 +12,77 @@
|
||||
#include <Common/Serialization/CXMLWriter.h>
|
||||
#include <tinyxml2.h>
|
||||
|
||||
#define COPY_DISC_DATA 1
|
||||
#define LOAD_PAKS 1
|
||||
#define SAVE_PACKAGE_DEFINITIONS 1
|
||||
#define USE_ASSET_NAME_MAP 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;
|
||||
mBuildVersion = 0.f;
|
||||
|
||||
mGameDir = FileUtil::MakeAbsolute(rkInputDir);
|
||||
mExportDir = FileUtil::MakeAbsolute(rkOutputDir);
|
||||
|
||||
mpProject = new CGameProject(mExportDir);
|
||||
mDiscDir = L"Disc\\";
|
||||
mWorldsDirName = L"Worlds\\";
|
||||
ASSERT(mGame != eUnknownGame);
|
||||
ASSERT(mRegion != eRegion_Unknown);
|
||||
}
|
||||
|
||||
#if PUBLIC_RELEASE
|
||||
#error Fix export directory being cleared!
|
||||
#endif
|
||||
|
||||
bool CGameExporter::Export()
|
||||
bool CGameExporter::Export(nod::DiscBase *pDisc, const TString& rkOutputDir, CAssetNameMap *pNameMap, CGameInfo *pGameInfo)
|
||||
{
|
||||
SCOPED_TIMER(ExportGame);
|
||||
|
||||
FileUtil::CreateDirectory(mExportDir);
|
||||
FileUtil::ClearDirectory(mExportDir);
|
||||
mpDisc = pDisc;
|
||||
mpNameMap = pNameMap;
|
||||
mpGameInfo = pGameInfo;
|
||||
|
||||
// Initial analyze/copy of disc data
|
||||
CopyDiscData();
|
||||
FindBuildVersion();
|
||||
mExportDir = FileUtil::MakeAbsolute(rkOutputDir);
|
||||
mDiscDir = L"Disc\\";
|
||||
mWorldsDirName = L"Worlds\\";
|
||||
|
||||
// Create project
|
||||
mpProject = new CGameProject(this, mExportDir, mGame, mBuildVersion);
|
||||
mpProject->SetProjectName(CMasterTemplate::FindGameName(mGame));
|
||||
FileUtil::MakeDirectory(mExportDir);
|
||||
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();
|
||||
mpStore = mpProject->ResourceStore();
|
||||
mContentDir = mpStore->RawDir(false);
|
||||
mCookedDir = mpStore->CookedDir(false);
|
||||
|
||||
#if USE_ASSET_NAME_MAP
|
||||
mNameMap.LoadAssetNames();
|
||||
#endif
|
||||
|
||||
// Export game data
|
||||
CResourceStore *pOldStore = gpResourceStore;
|
||||
gpResourceStore = mpStore;
|
||||
|
||||
LoadPaks();
|
||||
ExportCookedResources();
|
||||
mpProject->AudioManager()->LoadAssets();
|
||||
ExportResourceEditorData();
|
||||
|
||||
// Export finished!
|
||||
mProjectPath = mpProject->ProjectPath();
|
||||
delete mpProject;
|
||||
gpResourceStore = pOldStore;
|
||||
|
||||
if (pOldActiveProj) pOldActiveProj->SetActive();
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -81,100 +93,85 @@ void CGameExporter::LoadResource(const CAssetID& rkID, std::vector<u8>& rBuffer)
|
||||
}
|
||||
|
||||
// ************ PROTECTED ************
|
||||
void CGameExporter::CopyDiscData()
|
||||
bool CGameExporter::ExtractDiscData()
|
||||
{
|
||||
#if COPY_DISC_DATA
|
||||
SCOPED_TIMER(CopyDiscData);
|
||||
// todo: handle dol, apploader, multiple partitions, wii ticket blob
|
||||
SCOPED_TIMER(ExtractDiscData);
|
||||
|
||||
// Create Disc output folder
|
||||
FileUtil::CreateDirectory(mExportDir + mDiscDir);
|
||||
#endif
|
||||
TWideString AbsDiscDir = mExportDir + mDiscDir;
|
||||
FileUtil::MakeDirectory(AbsDiscDir);
|
||||
|
||||
// Copy data
|
||||
TWideStringList DiscFiles;
|
||||
FileUtil::GetDirectoryContents(mGameDir, DiscFiles);
|
||||
// Extract disc filesystem
|
||||
nod::Partition *pDataPartition = mpDisc->getDataPartition();
|
||||
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;
|
||||
TWideString RelPath = FullPath.ChopFront(mGameDir.Size());
|
||||
|
||||
// Exclude PakTool files and folders
|
||||
if (FullPath.GetFileName(false) == L"PakTool" || FullPath.GetFileName(false) == L"zlib1" || RelPath.Contains(L"-pak"))
|
||||
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
|
||||
mFilesystemAddress = 0;
|
||||
mPartitionHeaderPath = L"partition_header.bin";
|
||||
nod::DiscWii *pDiscWii = static_cast<nod::DiscWii*>(mpDisc);
|
||||
Success = pDiscWii->writeOutDataPartitionHeader(*(mExportDir + mPartitionHeaderPath));
|
||||
if (!Success) return false;
|
||||
}
|
||||
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());
|
||||
|
||||
// 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++)
|
||||
for (nod::Node::DirectoryIterator Iter = pkNode->begin(); Iter != pkNode->end(); ++Iter)
|
||||
{
|
||||
int Match = 0;
|
||||
|
||||
while (FileContents[SearchIdx + Match] == pkSearchText[Match] && Match < SearchTextSize)
|
||||
Match++;
|
||||
|
||||
if (Match == SearchTextSize)
|
||||
if (Iter->getKind() == nod::Node::Kind::File)
|
||||
{
|
||||
// Found the build info string; extract version number
|
||||
TString BuildInfo = &FileContents[SearchIdx + SearchTextSize];
|
||||
int BuildVerStart = BuildInfo.IndexOfPhrase("Build v") + 7;
|
||||
ASSERT(BuildVerStart != 6);
|
||||
TWideString FilePath = rkDir + TString(Iter->getName()).ToUTF16();
|
||||
bool Success = Iter->extractToDirectory(*rkDir, rkContext);
|
||||
if (!Success) return false;
|
||||
|
||||
mBuildVersion = BuildInfo.SubString(BuildVerStart, 5).ToFloat();
|
||||
return;
|
||||
if (FilePath.GetFileExtension() == L"pak")
|
||||
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 ************
|
||||
@ -183,10 +180,13 @@ void CGameExporter::LoadPaks()
|
||||
#if LOAD_PAKS
|
||||
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++)
|
||||
{
|
||||
TWideString PakPath = *It;
|
||||
TWideString PakName = PakPath.GetFileName(false);
|
||||
TString CharPak = PakPath.ToUTF8();
|
||||
CFileInStream Pak(CharPak.ToStdString(), IOUtil::eBigEndian);
|
||||
|
||||
@ -196,7 +196,7 @@ void CGameExporter::LoadPaks()
|
||||
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");
|
||||
|
||||
// MP1-MP3Proto
|
||||
@ -448,7 +448,7 @@ void CGameExporter::ExportCookedResources()
|
||||
{
|
||||
{
|
||||
SCOPED_TIMER(ExportCookedResources);
|
||||
FileUtil::CreateDirectory(mCookedDir);
|
||||
FileUtil::MakeDirectory(mCookedDir);
|
||||
|
||||
for (auto It = mResourceMap.begin(); It != mResourceMap.end(); It++)
|
||||
{
|
||||
@ -517,13 +517,20 @@ void CGameExporter::ExportResource(SResourceInstance& rRes)
|
||||
|
||||
// Register resource and write to file
|
||||
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);
|
||||
|
||||
#if EXPORT_COOKED
|
||||
// Save cooked asset
|
||||
TWideString OutCookedPath = pEntry->CookedAssetPath();
|
||||
FileUtil::CreateDirectory(OutCookedPath.GetFileDirectory());
|
||||
FileUtil::MakeDirectory(OutCookedPath.GetFileDirectory());
|
||||
CFileOutStream Out(OutCookedPath.ToUTF8().ToStdString(), IOUtil::eBigEndian);
|
||||
|
||||
if (Out.IsValid())
|
||||
|
@ -10,17 +10,25 @@
|
||||
#include <Common/TString.h>
|
||||
#include <Common/types.h>
|
||||
#include <map>
|
||||
#include <nod/nod.hpp>
|
||||
|
||||
class CGameExporter
|
||||
{
|
||||
// Project
|
||||
// Project Data
|
||||
CGameProject *mpProject;
|
||||
TWideString mProjectPath;
|
||||
CResourceStore *mpStore;
|
||||
EGame mGame;
|
||||
ERegion mRegion;
|
||||
TString mGameName;
|
||||
TString mGameID;
|
||||
float mBuildVersion;
|
||||
TWideString mDolPath;
|
||||
TWideString mApploaderPath;
|
||||
TWideString mPartitionHeaderPath;
|
||||
u32 mFilesystemAddress;
|
||||
|
||||
// Directories
|
||||
TWideString mGameDir;
|
||||
TWideString mExportDir;
|
||||
TWideString mDiscDir;
|
||||
TWideString mContentDir;
|
||||
@ -29,13 +37,13 @@ class CGameExporter
|
||||
TWideString mWorldsDirName;
|
||||
|
||||
// Files
|
||||
TWideString mDolPath;
|
||||
nod::DiscBase *mpDisc;
|
||||
|
||||
// Resources
|
||||
TWideStringList mPaks;
|
||||
std::map<CAssetID, bool> mAreaDuplicateMap;
|
||||
CAssetNameMap mNameMap;
|
||||
CGameInfo mGameInfo;
|
||||
CAssetNameMap *mpNameMap;
|
||||
CGameInfo *mpGameInfo;
|
||||
|
||||
struct SResourceInstance
|
||||
{
|
||||
@ -50,13 +58,15 @@ class CGameExporter
|
||||
std::map<CAssetID, SResourceInstance> mResourceMap;
|
||||
|
||||
public:
|
||||
CGameExporter(const TString& rkInputDir, const TString& rkOutputDir);
|
||||
bool Export();
|
||||
CGameExporter(EGame Game, ERegion Region, const TString& rkGameName, const TString& rkGameID, float BuildVersion);
|
||||
bool Export(nod::DiscBase *pDisc, const TString& rkOutputDir, CAssetNameMap *pNameMap, CGameInfo *pGameInfo);
|
||||
void LoadResource(const CAssetID& rkID, std::vector<u8>& rBuffer);
|
||||
|
||||
inline TWideString ProjectPath() const { return mProjectPath; }
|
||||
|
||||
protected:
|
||||
void CopyDiscData();
|
||||
void FindBuildVersion();
|
||||
bool ExtractDiscData();
|
||||
bool ExtractDiscNodeRecursive(const nod::Node *pkNode, const TWideString& rkDir, const nod::ExtractionContext& rkContext);
|
||||
void LoadPaks();
|
||||
void LoadResource(const SResourceInstance& rkResource, std::vector<u8>& rBuffer);
|
||||
void ExportCookedResources();
|
||||
|
@ -1,11 +1,14 @@
|
||||
#include "CGameInfo.h"
|
||||
#include <Common/FileUtil.h>
|
||||
|
||||
void CGameInfo::LoadGameInfo(EGame Game)
|
||||
{
|
||||
Game = RoundGame(Game);
|
||||
mGame = Game;
|
||||
|
||||
LoadGameInfo(GetDefaultGameInfoPath(Game));
|
||||
TString Path = GetDefaultGameInfoPath(Game);
|
||||
if (FileUtil::Exists(Path))
|
||||
LoadGameInfo(Path);
|
||||
}
|
||||
|
||||
void CGameInfo::LoadGameInfo(TString Path)
|
||||
@ -59,5 +62,5 @@ TString CGameInfo::GetDefaultGameInfoPath(EGame Game)
|
||||
return "";
|
||||
|
||||
TString GameName = GetGameShortName(Game);
|
||||
return TString::Format("%s\\GameInfo%s.xml", *gkGameInfoDir, *GameName);
|
||||
return TString::Format("%s\\GameInfo%s.%s", *gkGameInfoDir, *GameName, *gkGameInfoExt);
|
||||
}
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include <map>
|
||||
|
||||
const TString gkGameInfoDir = "..\\resources\\gameinfo";
|
||||
const TString gkGameInfoExt = "xml";
|
||||
|
||||
class CGameInfo
|
||||
{
|
||||
@ -34,6 +35,8 @@ public:
|
||||
static CGameInfo* GetGameInfo(EGame Game);
|
||||
static EGame RoundGame(EGame Game);
|
||||
static TString GetDefaultGameInfoPath(EGame Game);
|
||||
|
||||
inline static TString GetExtension() { return gkGameInfoExt; }
|
||||
};
|
||||
|
||||
#endif // CGAMEINFO
|
||||
|
@ -10,30 +10,16 @@ CGameProject::~CGameProject()
|
||||
ASSERT(!mpResourceStore->IsDirty());
|
||||
|
||||
if (IsActive())
|
||||
{
|
||||
mspActiveProject = nullptr;
|
||||
gpResourceStore = nullptr;
|
||||
}
|
||||
|
||||
delete mpAudioManager;
|
||||
delete mpGameInfo;
|
||||
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()
|
||||
{
|
||||
TString ProjPath = ProjectPath().ToUTF8();
|
||||
@ -44,8 +30,19 @@ void CGameProject::Save()
|
||||
void CGameProject::Serialize(IArchive& rArc)
|
||||
{
|
||||
rArc << SERIAL("Name", mProjectName)
|
||||
<< SERIAL("Region", mRegion)
|
||||
<< SERIAL("GameID", mGameID)
|
||||
<< 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
|
||||
std::vector<TString> PackageList;
|
||||
@ -129,3 +126,52 @@ CAssetID CGameProject::FindNamedResource(const TString& rkName) const
|
||||
|
||||
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;
|
||||
}
|
||||
|
@ -14,9 +14,16 @@
|
||||
|
||||
class CGameProject
|
||||
{
|
||||
EGame mGame;
|
||||
float mBuildVersion;
|
||||
TString mProjectName;
|
||||
EGame mGame;
|
||||
ERegion mRegion;
|
||||
TString mGameID;
|
||||
float mBuildVersion;
|
||||
TWideString mDolPath;
|
||||
TWideString mApploaderPath;
|
||||
TWideString mPartitionHeaderPath;
|
||||
u32 mFilesystemAddress;
|
||||
|
||||
TWideString mProjectRoot;
|
||||
TWideString mResourceDBPath;
|
||||
std::vector<CPackage*> mPackages;
|
||||
@ -34,52 +41,45 @@ class CGameProject
|
||||
|
||||
static CGameProject *mspActiveProject;
|
||||
|
||||
public:
|
||||
// Private Constructor
|
||||
CGameProject()
|
||||
: mGame(eUnknownGame)
|
||||
, mProjectName("Unnamed Project")
|
||||
: mProjectName("Unnamed Project")
|
||||
, mGame(eUnknownGame)
|
||||
, mRegion(eRegion_Unknown)
|
||||
, mGameID("000000")
|
||||
, mBuildVersion(0.f)
|
||||
, mResourceDBPath(L"ResourceDB.rdb")
|
||||
, mpResourceStore(nullptr)
|
||||
{
|
||||
mpResourceStore = new CResourceStore(this);
|
||||
mpGameInfo = new CGameInfo();
|
||||
mpAudioManager = new CAudioManager(this);
|
||||
}
|
||||
|
||||
CGameProject(const TWideString& rkProjRootDir)
|
||||
: 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);
|
||||
}
|
||||
|
||||
public:
|
||||
~CGameProject();
|
||||
|
||||
bool Load(const TWideString& rkPath);
|
||||
void Save();
|
||||
void Serialize(IArchive& rArc);
|
||||
void SetActive();
|
||||
void GetWorldList(std::list<CAssetID>& rOut) 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
|
||||
inline TWideString ProjectRoot() const { return mProjectRoot; }
|
||||
inline TWideString ResourceDBPath(bool Relative) const { return Relative ? mResourceDBPath : mProjectRoot + mResourceDBPath; }
|
||||
@ -101,6 +101,7 @@ public:
|
||||
inline EGame Game() const { return mGame; }
|
||||
inline float BuildVersion() const { return mBuildVersion; }
|
||||
inline bool IsActive() const { return mspActiveProject == this; }
|
||||
inline bool IsWiiBuild() const { return mBuildVersion >= 3.f; }
|
||||
|
||||
static inline CGameProject* ActiveProject() { return mspActiveProject; }
|
||||
};
|
||||
|
@ -20,7 +20,7 @@ void CPackage::Load()
|
||||
void CPackage::Save()
|
||||
{
|
||||
TWideString DefPath = DefinitionPath(false);
|
||||
FileUtil::CreateDirectory(DefPath.GetFileDirectory());
|
||||
FileUtil::MakeDirectory(DefPath.GetFileDirectory());
|
||||
|
||||
CXMLWriter Writer(DefPath.ToUTF8(), "PackageDefinition", 0, mpProject ? mpProject->Game() : eUnknownGame);
|
||||
Serialize(Writer);
|
||||
|
@ -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.
|
||||
TString Path = RawAssetPath();
|
||||
TString Dir = Path.GetFileDirectory();
|
||||
FileUtil::CreateDirectory(Dir.ToUTF16());
|
||||
FileUtil::MakeDirectory(Dir.ToUTF16());
|
||||
|
||||
TString SerialName = mpTypeInfo->TypeName();
|
||||
SerialName.RemoveWhitespace();
|
||||
@ -268,7 +268,8 @@ CResource* CResourceEntry::LoadCooked(IInputStream& rInput)
|
||||
gpResourceStore = mpStore;
|
||||
|
||||
mpResource = CResourceFactory::LoadCookedResource(this, rInput);
|
||||
mpStore->TrackLoadedResource(this);
|
||||
if (mpResource)
|
||||
mpStore->TrackLoadedResource(this);
|
||||
|
||||
gpResourceStore = pOldStore;
|
||||
return mpResource;
|
||||
|
@ -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
|
||||
|
||||
public:
|
||||
CAnimSet(CResourceEntry *pEntry = 0) : CResource(pEntry) {}
|
||||
CAnimSet(CResourceEntry *pEntry = 0)
|
||||
: CResource(pEntry)
|
||||
, mpDefaultTransition(nullptr)
|
||||
{}
|
||||
|
||||
~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
|
||||
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];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -42,7 +42,7 @@ void CTemplateWriter::SaveAllTemplates()
|
||||
{
|
||||
// Create directory
|
||||
std::list<CMasterTemplate*> MasterList = CMasterTemplate::MasterList();
|
||||
FileUtil::CreateDirectory(smTemplatesDir);
|
||||
FileUtil::MakeDirectory(smTemplatesDir);
|
||||
|
||||
// Resave property list
|
||||
SavePropertyList();
|
||||
@ -95,7 +95,7 @@ void CTemplateWriter::SaveGameTemplates(CMasterTemplate *pMaster)
|
||||
// Create directory
|
||||
TString OutFile = smTemplatesDir + pMaster->mSourceFile;
|
||||
TString OutDir = OutFile.GetFileDirectory();
|
||||
FileUtil::CreateDirectory(OutDir);
|
||||
FileUtil::MakeDirectory(OutDir);
|
||||
|
||||
// Resave script templates
|
||||
for (auto it = pMaster->mTemplates.begin(); it != pMaster->mTemplates.end(); it++)
|
||||
@ -226,7 +226,7 @@ void CTemplateWriter::SaveScriptTemplate(CScriptTemplate *pTemp)
|
||||
// Create directory
|
||||
TString OutFile = smTemplatesDir + pMaster->GetDirectory() + pTemp->mSourceFile;
|
||||
TString OutDir = OutFile.GetFileDirectory();
|
||||
FileUtil::CreateDirectory(*OutDir);
|
||||
FileUtil::MakeDirectory(*OutDir);
|
||||
|
||||
// Create new document
|
||||
XMLDocument ScriptXML;
|
||||
@ -432,7 +432,7 @@ void CTemplateWriter::SaveStructTemplate(CStructTemplate *pTemp)
|
||||
TString OutFile = smTemplatesDir + pMaster->GetDirectory() + pTemp->mSourceFile;
|
||||
TString OutDir = OutFile.GetFileDirectory();
|
||||
TString Name = OutFile.GetFileName(false);
|
||||
FileUtil::CreateDirectory(OutDir);
|
||||
FileUtil::MakeDirectory(OutDir);
|
||||
|
||||
// Create new document and write struct properties to it
|
||||
XMLDocument StructXML;
|
||||
@ -456,7 +456,7 @@ void CTemplateWriter::SaveEnumTemplate(CEnumTemplate *pTemp)
|
||||
TString OutFile = smTemplatesDir + pMaster->GetDirectory() + pTemp->mSourceFile;
|
||||
TString OutDir = OutFile.GetFileDirectory();
|
||||
TString Name = OutFile.GetFileName(false);
|
||||
FileUtil::CreateDirectory(OutDir);
|
||||
FileUtil::MakeDirectory(OutDir);
|
||||
|
||||
// Create new document and write enumerators to it
|
||||
XMLDocument EnumXML;
|
||||
@ -479,7 +479,7 @@ void CTemplateWriter::SaveBitfieldTemplate(CBitfieldTemplate *pTemp)
|
||||
TString OutFile = smTemplatesDir + pMaster->GetDirectory() + pTemp->mSourceFile;
|
||||
TString OutDir = OutFile.GetFileDirectory();
|
||||
TString Name = pTemp->mSourceFile.GetFileName(false);
|
||||
FileUtil::CreateDirectory(OutDir);
|
||||
FileUtil::MakeDirectory(OutDir);
|
||||
|
||||
// Create new document and write enumerators to it
|
||||
XMLDocument BitfieldXML;
|
||||
|
@ -162,7 +162,8 @@ void CAnimSetLoader::ProcessPrimitives()
|
||||
for (u32 iTrans = 0; iTrans < pSet->mTransitions.size(); iTrans++)
|
||||
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++)
|
||||
pSet->mHalfTransitions[iTrans].pMetaTrans->GetUniquePrimitives(UniquePrimitives);
|
||||
@ -181,17 +182,20 @@ void CAnimSetLoader::ProcessPrimitives()
|
||||
}
|
||||
|
||||
// Add animations referenced by default transition
|
||||
std::set<CAnimPrimitive> DefaultTransPrimitives;
|
||||
pSet->mpDefaultTransition->GetUniquePrimitives(DefaultTransPrimitives);
|
||||
|
||||
for (u32 iChar = 0; iChar < pSet->mCharacters.size(); iChar++)
|
||||
if (pSet->mpDefaultTransition)
|
||||
{
|
||||
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;
|
||||
rChar.UsedAnimationIndices.insert(rkPrim.ID());
|
||||
SSetCharacter& rChar = pSet->mCharacters[iChar];
|
||||
|
||||
for (auto Iter = DefaultTransPrimitives.begin(); Iter != DefaultTransPrimitives.end(); Iter++)
|
||||
{
|
||||
const CAnimPrimitive& rkPrim = *Iter;
|
||||
rChar.UsedAnimationIndices.insert(rkPrim.ID());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -12,6 +12,8 @@ class CResourceEntry;
|
||||
class CWorldEditor;
|
||||
class IEditor;
|
||||
|
||||
const int gkTickFrequencyMS = 8;
|
||||
|
||||
class CEditorApplication : public QApplication
|
||||
{
|
||||
Q_OBJECT
|
||||
@ -35,6 +37,9 @@ public:
|
||||
inline CResourceBrowser* ResourceBrowser() const { return mpResourceBrowser; }
|
||||
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:
|
||||
void AddEditor(IEditor *pEditor);
|
||||
void TickEditors();
|
||||
|
360
src/Editor/CExportGameDialog.cpp
Normal file
360
src/Editor/CExportGameDialog.cpp
Normal 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());
|
||||
}
|
52
src/Editor/CExportGameDialog.h
Normal file
52
src/Editor/CExportGameDialog.h
Normal 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
|
271
src/Editor/CExportGameDialog.ui
Normal file
271
src/Editor/CExportGameDialog.ui
Normal 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>
|
@ -1,6 +1,7 @@
|
||||
#include "CProjectOverviewDialog.h"
|
||||
#include "ui_CProjectOverviewDialog.h"
|
||||
#include "CEditorApplication.h"
|
||||
#include "CExportGameDialog.h"
|
||||
#include "UICommon.h"
|
||||
#include "Editor/ResourceBrowser/CResourceBrowser.h"
|
||||
#include <Common/AssertMacro.h>
|
||||
@ -28,17 +29,13 @@ CProjectOverviewDialog::~CProjectOverviewDialog()
|
||||
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
|
||||
TWideString Path = TO_TWIDESTRING(ProjPath);
|
||||
CGameProject *pNewProj = new CGameProject(Path.GetFileDirectory());
|
||||
TWideString Path = TO_TWIDESTRING(rkPath);
|
||||
CGameProject *pNewProj = CGameProject::LoadProject(Path);
|
||||
|
||||
if (pNewProj->Load(Path))
|
||||
if (pNewProj)
|
||||
{
|
||||
if (mpProject) delete mpProject;
|
||||
mpProject = pNewProj;
|
||||
@ -49,49 +46,34 @@ void CProjectOverviewDialog::OpenProject()
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
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()
|
||||
{
|
||||
// TEMP - hardcoded names for convenience. will remove later!
|
||||
#define USE_HARDCODED_GAME_ROOT 0
|
||||
#define USE_HARDCODED_EXPORT_DIR 0
|
||||
QString IsoPath = UICommon::OpenFileDialog(this, "Select ISO", "*.iso *.gcm *.tgc *.wbfs");
|
||||
if (IsoPath.isEmpty()) return;
|
||||
|
||||
#if USE_HARDCODED_GAME_ROOT
|
||||
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");
|
||||
QString ExportDir = UICommon::OpenDirDialog(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()))
|
||||
CExportGameDialog ExportDialog(IsoPath, ExportDir, this);
|
||||
if (ExportDialog.HasValidDisc()) ExportDialog.exec();
|
||||
|
||||
if (ExportDialog.ExportSucceeded())
|
||||
{
|
||||
QMessageBox::warning(this, "Error", "Error; this is not a valid game root directory!");
|
||||
return;
|
||||
}
|
||||
int OpenChoice = QMessageBox::information(this, "Export complete", "Export finished successfully! Open new project?", QMessageBox::Yes, QMessageBox::No);
|
||||
|
||||
// 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;
|
||||
if (OpenChoice == QMessageBox::Yes)
|
||||
InternalLoadProject(ExportDialog.ProjectPath());
|
||||
}
|
||||
|
||||
CGameExporter Exporter(TO_TSTRING(GameRoot), TO_TSTRING(ExportDir));
|
||||
Exporter.Export();
|
||||
}
|
||||
|
||||
void CProjectOverviewDialog::SetupWorldsList()
|
||||
|
@ -24,6 +24,9 @@ public:
|
||||
explicit CProjectOverviewDialog(QWidget *pParent = 0);
|
||||
~CProjectOverviewDialog();
|
||||
|
||||
protected:
|
||||
void InternalLoadProject(const QString& rkPath);
|
||||
|
||||
public slots:
|
||||
void OpenProject();
|
||||
void ExportGame();
|
||||
|
@ -7,7 +7,6 @@
|
||||
|
||||
#include "Editor/ModelEditor/CModelEditorWindow.h"
|
||||
#include "Editor/WorldEditor/CWorldEditor.h"
|
||||
#include <Core/GameProject/CGameExporter.h>
|
||||
#include <Core/GameProject/CResourceStore.h>
|
||||
|
||||
#include <QFileDialog>
|
||||
@ -26,7 +25,6 @@ CStartWindow::CStartWindow(QWidget *parent)
|
||||
|
||||
connect(ui->ActionAbout, SIGNAL(triggered()), this, SLOT(About()));
|
||||
connect(ui->ActionCharacterEditor, SIGNAL(triggered()), this, SLOT(LaunchCharacterEditor()));
|
||||
connect(ui->ActionExportGame, SIGNAL(triggered()), this, SLOT(ExportGame()));
|
||||
}
|
||||
|
||||
CStartWindow::~CStartWindow()
|
||||
@ -245,35 +243,3 @@ void CStartWindow::About()
|
||||
CAboutDialog Dialog(this);
|
||||
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();
|
||||
}
|
||||
|
@ -40,7 +40,6 @@ private slots:
|
||||
|
||||
void LaunchCharacterEditor();
|
||||
void About();
|
||||
void ExportGame();
|
||||
|
||||
private:
|
||||
void FillWorldUI();
|
||||
|
@ -35,6 +35,8 @@ CONFIG(debug, debug|release) {
|
||||
-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/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/zlib/lib/ -lzlibd
|
||||
|
||||
@ -62,6 +64,8 @@ CONFIG(release, debug|release) {
|
||||
-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/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/zlib/lib/ -lzlib
|
||||
|
||||
@ -84,6 +88,8 @@ INCLUDEPATH += $$PWE_MAIN_INCLUDE \
|
||||
$$EXTERNALS_DIR/glew-2.0.0/include \
|
||||
$$EXTERNALS_DIR/glm/glm \
|
||||
$$EXTERNALS_DIR/lzo-2.09/include \
|
||||
$$EXTERNALS_DIR/nodtool/include \
|
||||
$$EXTERNALS_DIR/nodtool/logvisor/include \
|
||||
$$EXTERNALS_DIR/tinyxml2/include \
|
||||
$$EXTERNALS_DIR/zlib/include
|
||||
|
||||
@ -174,7 +180,8 @@ HEADERS += \
|
||||
ResourceBrowser/CVirtualDirectoryModel.h \
|
||||
CEditorApplication.h \
|
||||
IEditor.h \
|
||||
Widgets/CResourceSelector.h
|
||||
Widgets/CResourceSelector.h \
|
||||
CExportGameDialog.h
|
||||
|
||||
# Source Files
|
||||
SOURCES += \
|
||||
@ -238,7 +245,8 @@ SOURCES += \
|
||||
CProjectOverviewDialog.cpp \
|
||||
ResourceBrowser/CResourceBrowser.cpp \
|
||||
CEditorApplication.cpp \
|
||||
Widgets/CResourceSelector.cpp
|
||||
Widgets/CResourceSelector.cpp \
|
||||
CExportGameDialog.cpp
|
||||
|
||||
# UI Files
|
||||
FORMS += \
|
||||
@ -262,4 +270,5 @@ FORMS += \
|
||||
CharacterEditor/CCharacterEditor.ui \
|
||||
WorldEditor/CCollisionRenderSettingsDialog.ui \
|
||||
CProjectOverviewDialog.ui \
|
||||
ResourceBrowser/CResourceBrowser.ui
|
||||
ResourceBrowser/CResourceBrowser.ui \
|
||||
CExportGameDialog.ui
|
||||
|
@ -292,7 +292,7 @@ void CResourceBrowser::OnResourceSelectionChanged(const QModelIndex& rkNewIndex,
|
||||
|
||||
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;
|
||||
|
||||
foreach(const QString& rkPath, PathList)
|
||||
@ -326,7 +326,7 @@ void CResourceBrowser::OnImportNamesFromAssetNameMap()
|
||||
|
||||
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;
|
||||
|
||||
CAssetNameMap NameMap;
|
||||
|
@ -1,8 +1,11 @@
|
||||
#ifndef UICOMMON
|
||||
#define UICOMMON
|
||||
|
||||
#include "CEditorApplication.h"
|
||||
#include <Common/TString.h>
|
||||
#include <QFileDialog>
|
||||
#include <QMap>
|
||||
#include <QMessageBox>
|
||||
#include <QString>
|
||||
|
||||
// App string variable handling - automatically fill in application name/version
|
||||
@ -57,7 +60,54 @@ inline TWideString ToTWideString(const QString& rkStr)
|
||||
{
|
||||
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
|
||||
|
||||
|
@ -215,7 +215,7 @@ void WResourceSelector::OnBrowseButtonClicked()
|
||||
Filter += UICommon::ExtensionFilterString(mSupportedExtensions[iExt]);
|
||||
}
|
||||
|
||||
QString NewRes = QFileDialog::getOpenFileName(this, "Select resource", "", Filter);
|
||||
QString NewRes = UICommon::OpenFileDialog(this, "Select resource", Filter);
|
||||
|
||||
if (!NewRes.isEmpty())
|
||||
{
|
||||
|
@ -63,7 +63,7 @@ QString CRepackInfoDialog::OutputPak() const
|
||||
// ************ PUBLIC SLOTS ************
|
||||
void CRepackInfoDialog::BrowseFolderClicked()
|
||||
{
|
||||
QString Folder = QFileDialog::getExistingDirectory(this, "Choose directory");
|
||||
QString Folder = UICommon::OpenDirDialog(this, "Choose directory");
|
||||
|
||||
if (!Folder.isEmpty())
|
||||
{
|
||||
@ -74,7 +74,7 @@ void CRepackInfoDialog::BrowseFolderClicked()
|
||||
|
||||
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())
|
||||
{
|
||||
@ -94,7 +94,7 @@ void CRepackInfoDialog::BrowseListClicked()
|
||||
|
||||
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())
|
||||
{
|
||||
|
Loading…
x
Reference in New Issue
Block a user