320 lines
9.8 KiB
C++
320 lines
9.8 KiB
C++
#include "CGameProject.h"
|
|
#include "CResourceIterator.h"
|
|
#include "IUIRelay.h"
|
|
#include "Core/Resource/Script/CGameTemplate.h"
|
|
#include <Common/Serialization/XML.h>
|
|
#include <nod/DiscGCN.hpp>
|
|
#include <nod/DiscWii.hpp>
|
|
|
|
#if NOD_UCS2
|
|
#define TStringToNodString(string) ToWChar(string)
|
|
#else
|
|
#define TStringToNodString(string) *string
|
|
#endif
|
|
|
|
CGameProject::~CGameProject()
|
|
{
|
|
if (mpResourceStore)
|
|
{
|
|
ASSERT(!mpResourceStore->IsCacheDirty());
|
|
|
|
if (gpResourceStore == mpResourceStore.get())
|
|
gpResourceStore = nullptr;
|
|
}
|
|
|
|
for (uint32 iPkg = 0; iPkg < mPackages.size(); iPkg++)
|
|
delete mPackages[iPkg];
|
|
}
|
|
|
|
bool CGameProject::Save()
|
|
{
|
|
mProjFileLock.Release();
|
|
TString ProjPath = ProjectPath();
|
|
CXMLWriter Writer(ProjPath, "GameProject", (int) EProjectVersion::Current, mGame);
|
|
Serialize(Writer);
|
|
bool SaveSuccess = Writer.Save();
|
|
mProjFileLock.Lock(*ProjPath);
|
|
return SaveSuccess;
|
|
}
|
|
|
|
bool CGameProject::Serialize(IArchive& rArc)
|
|
{
|
|
rArc << SerialParameter("Name", mProjectName)
|
|
<< SerialParameter("Region", mRegion)
|
|
<< SerialParameter("GameID", mGameID)
|
|
<< SerialParameter("BuildVersion", mBuildVersion);
|
|
|
|
// Serialize package list
|
|
std::vector<TString> PackageList;
|
|
|
|
if (!rArc.IsReader())
|
|
{
|
|
for (uint32 iPkg = 0; iPkg < mPackages.size(); iPkg++)
|
|
PackageList.push_back( mPackages[iPkg]->DefinitionPath(true) );
|
|
}
|
|
|
|
rArc << SerialParameter("Packages", PackageList);
|
|
|
|
// Load packages
|
|
if (rArc.IsReader())
|
|
{
|
|
ASSERT(mPackages.empty());
|
|
|
|
for (uint32 iPkg = 0; iPkg < PackageList.size(); iPkg++)
|
|
{
|
|
const TString& rkPackagePath = PackageList[iPkg];
|
|
TString PackageName = rkPackagePath.GetFileName(false);
|
|
TString PackageDir = rkPackagePath.GetFileDirectory();
|
|
|
|
CPackage *pPackage = new CPackage(this, PackageName, PackageDir);
|
|
bool PackageLoadSuccess = pPackage->Load();
|
|
mPackages.push_back(pPackage);
|
|
|
|
if (!PackageLoadSuccess)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CGameProject::BuildISO(const TString& rkIsoPath, IProgressNotifier *pProgress)
|
|
{
|
|
ASSERT( FileUtil::IsValidPath(rkIsoPath, false) );
|
|
ASSERT( !IsWiiDeAsobu() && !IsTrilogy() );
|
|
|
|
auto ProgressCallback = [&](float ProgressPercent, const nod::SystemStringView& rkInfoString, size_t)
|
|
{
|
|
pProgress->Report((int) (ProgressPercent * 10000), 10000, nod::SystemUTF8Conv(rkInfoString).c_str());
|
|
};
|
|
|
|
pProgress->SetTask(0, "Building " + rkIsoPath.GetFileName());
|
|
TString DiscRoot = DiscDir(false);
|
|
|
|
if (!IsWiiBuild())
|
|
{
|
|
nod::DiscBuilderGCN Builder(TStringToNodString(rkIsoPath), ProgressCallback);
|
|
return Builder.buildFromDirectory(TStringToNodString(DiscRoot)) == nod::EBuildResult::Success;
|
|
}
|
|
else
|
|
{
|
|
nod::DiscBuilderWii Builder(TStringToNodString(rkIsoPath), IsTrilogy(), ProgressCallback);
|
|
return Builder.buildFromDirectory(TStringToNodString(DiscRoot)) == nod::EBuildResult::Success;
|
|
}
|
|
}
|
|
|
|
bool CGameProject::MergeISO(const TString& rkIsoPath, nod::DiscWii *pOriginalIso, IProgressNotifier *pProgress)
|
|
{
|
|
ASSERT( FileUtil::IsValidPath(rkIsoPath, false) );
|
|
ASSERT( IsWiiDeAsobu() || IsTrilogy() );
|
|
ASSERT( pOriginalIso != nullptr );
|
|
|
|
auto ProgressCallback = [&](float ProgressPercent, const nod::SystemStringView& rkInfoString, size_t)
|
|
{
|
|
pProgress->Report((int) (ProgressPercent * 10000), 10000, nod::SystemUTF8Conv(rkInfoString).c_str());
|
|
};
|
|
|
|
pProgress->SetTask(0, "Building " + rkIsoPath.GetFileName());
|
|
|
|
TString DiscRoot = DiscFilesystemRoot(false);
|
|
|
|
nod::DiscMergerWii Merger(TStringToNodString(rkIsoPath), *pOriginalIso, IsTrilogy(), ProgressCallback);
|
|
return Merger.mergeFromDirectory(TStringToNodString(DiscRoot)) == nod::EBuildResult::Success;
|
|
}
|
|
|
|
void CGameProject::GetWorldList(std::list<CAssetID>& rOut) const
|
|
{
|
|
for (uint32 iPkg = 0; iPkg < mPackages.size(); iPkg++)
|
|
{
|
|
CPackage *pPkg = mPackages[iPkg];
|
|
|
|
// Little workaround to fix some of Retro's paks having worlds listed in the wrong order...
|
|
// Construct a sorted list of worlds in this package
|
|
std::list<const SNamedResource*> PackageWorlds;
|
|
|
|
for (uint32 iRes = 0; iRes < pPkg->NumNamedResources(); iRes++)
|
|
{
|
|
const SNamedResource& rkRes = pPkg->NamedResourceByIndex(iRes);
|
|
|
|
if (rkRes.Type == "MLVL" && !rkRes.Name.EndsWith("NODEPEND"))
|
|
PackageWorlds.push_back(&rkRes);
|
|
}
|
|
|
|
PackageWorlds.sort([](const SNamedResource *pkLeft, const SNamedResource *pkRight) -> bool {
|
|
return pkLeft->Name.ToUpper() < pkRight->Name.ToUpper();
|
|
});
|
|
|
|
// Add sorted worlds to the output world list
|
|
for (auto Iter = PackageWorlds.begin(); Iter != PackageWorlds.end(); Iter++)
|
|
{
|
|
const SNamedResource *pkRes = *Iter;
|
|
rOut.push_back(pkRes->ID);
|
|
}
|
|
}
|
|
}
|
|
|
|
CAssetID CGameProject::FindNamedResource(const TString& rkName) const
|
|
{
|
|
for (uint32 iPkg = 0; iPkg < mPackages.size(); iPkg++)
|
|
{
|
|
CPackage *pPkg = mPackages[iPkg];
|
|
|
|
for (uint32 iRes = 0; iRes < pPkg->NumNamedResources(); iRes++)
|
|
{
|
|
const SNamedResource& rkRes = pPkg->NamedResourceByIndex(iRes);
|
|
|
|
if (rkRes.Name == rkName)
|
|
return rkRes.ID;
|
|
}
|
|
}
|
|
|
|
return CAssetID::InvalidID(mGame);
|
|
}
|
|
|
|
CPackage* CGameProject::FindPackage(const TString& rkName) const
|
|
{
|
|
for (uint32 iPkg = 0; iPkg < mPackages.size(); iPkg++)
|
|
{
|
|
CPackage *pPackage = mPackages[iPkg];
|
|
|
|
if (pPackage->Name() == rkName)
|
|
{
|
|
return pPackage;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
CGameProject* CGameProject::CreateProjectForExport(
|
|
const TString& rkProjRootDir,
|
|
EGame Game,
|
|
ERegion Region,
|
|
const TString& rkGameID,
|
|
float BuildVer
|
|
)
|
|
{
|
|
CGameProject *pProj = new CGameProject;
|
|
pProj->mGame = Game;
|
|
pProj->mRegion = Region;
|
|
pProj->mGameID = rkGameID;
|
|
pProj->mBuildVersion = BuildVer;
|
|
|
|
pProj->mProjectRoot = rkProjRootDir;
|
|
pProj->mProjectRoot.Replace("\\", "/");
|
|
pProj->mpResourceStore = std::make_unique<CResourceStore>(pProj);
|
|
pProj->mpGameInfo->LoadGameInfo(Game);
|
|
return pProj;
|
|
}
|
|
|
|
CGameProject* CGameProject::LoadProject(const TString& rkProjPath, IProgressNotifier *pProgress)
|
|
{
|
|
// Init project
|
|
CGameProject *pProj = new CGameProject;
|
|
pProj->mProjectRoot = rkProjPath.GetFileDirectory();
|
|
pProj->mProjectRoot.Replace("\\", "/");
|
|
|
|
// Init progress
|
|
pProgress->SetTask(0, "Loading project: " + rkProjPath.GetFileName());
|
|
|
|
// Load main project file
|
|
pProgress->Report("Loading project settings");
|
|
bool LoadSuccess = false;
|
|
|
|
TString ProjPath = rkProjPath;
|
|
CXMLReader Reader(ProjPath);
|
|
|
|
if (!Reader.IsValid())
|
|
{
|
|
delete pProj;
|
|
return nullptr;
|
|
}
|
|
|
|
pProj->mGame = Reader.Game();
|
|
|
|
if (pProj->Serialize(Reader))
|
|
{
|
|
// Load resource database
|
|
pProgress->Report("Loading resource database");
|
|
pProj->mpResourceStore = std::make_unique<CResourceStore>(pProj);
|
|
LoadSuccess = pProj->mpResourceStore->LoadDatabaseCache();
|
|
|
|
// Removed database validation step. We used to do this on project load to make sure all data was correct, but this takes a long
|
|
// time and significantly extends how long it takes to open a project. In actual practice, this isn't needed most of the time, and
|
|
// in the odd case that it is needed, there is a button in the resource browser to rebuild the database. So in the interest of
|
|
// making project startup faster, we no longer validate the database.
|
|
#if 0
|
|
if (LoadSuccess)
|
|
{
|
|
pProgress->Report("Validating resource database");
|
|
bool DatabaseIsValid = pProj->mpResourceStore->AreAllEntriesValid();
|
|
|
|
// Resource database is corrupt. Ask the user if they want to rebuild it.
|
|
if (!DatabaseIsValid)
|
|
{
|
|
bool ShouldRebuild = gpUIRelay->AskYesNoQuestion("Error", "The resource database is corrupt. Attempt to repair it?");
|
|
|
|
if (ShouldRebuild)
|
|
{
|
|
pProgress->Report("Repairing resource database");
|
|
pProj->mpResourceStore->RebuildFromDirectory();
|
|
}
|
|
else
|
|
LoadSuccess = false;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
if (!LoadSuccess)
|
|
{
|
|
delete pProj;
|
|
return nullptr;
|
|
}
|
|
|
|
pProj->mProjFileLock.Lock(ProjPath);
|
|
pProj->mpGameInfo->LoadGameInfo(pProj->mGame);
|
|
|
|
// Perform update
|
|
if (Reader.FileVersion() < (uint16) EProjectVersion::Current)
|
|
{
|
|
pProgress->Report("Updating project");
|
|
|
|
CResourceStore* pOldStore = gpResourceStore;
|
|
gpResourceStore = pProj->mpResourceStore.get();
|
|
|
|
for (CResourceIterator It; It; ++It)
|
|
{
|
|
if (It->TypeInfo()->CanBeSerialized() && !It->HasRawVersion())
|
|
{
|
|
It->Save(true, false);
|
|
|
|
// Touch the cooked file to update its last modified time.
|
|
// This prevents PWE from erroneously thinking the cooked file is outdated
|
|
// (due to the raw file we just made having a more recent last modified time)
|
|
FileUtil::UpdateLastModifiedTime( It->CookedAssetPath() );
|
|
}
|
|
}
|
|
|
|
pProj->mpResourceStore->ConditionalSaveStore();
|
|
pProj->Save();
|
|
|
|
gpResourceStore = pOldStore;
|
|
}
|
|
|
|
// Create hidden files directory, if needed
|
|
TString HiddenDir = pProj->HiddenFilesDir();
|
|
|
|
if (!FileUtil::Exists(HiddenDir))
|
|
{
|
|
FileUtil::MakeDirectory(HiddenDir);
|
|
FileUtil::MarkHidden(HiddenDir, true);
|
|
}
|
|
|
|
pProj->mpAudioManager->LoadAssets();
|
|
pProj->mpTweakManager->LoadTweaks();
|
|
return pProj;
|
|
}
|