mirror of
https://github.com/AxioDL/PrimeWorldEditor.git
synced 2025-12-17 00:47:05 +00:00
Added progress bars for most major blocking operations
This commit is contained in:
@@ -23,6 +23,7 @@ CGameExporter::CGameExporter(EGame Game, ERegion Region, const TString& rkGameNa
|
||||
, mGameName(rkGameName)
|
||||
, mGameID(rkGameID)
|
||||
, mBuildVersion(BuildVersion)
|
||||
, mpProgress(nullptr)
|
||||
{
|
||||
ASSERT(mGame != eUnknownGame);
|
||||
ASSERT(mRegion != eRegion_Unknown);
|
||||
@@ -32,7 +33,7 @@ CGameExporter::CGameExporter(EGame Game, ERegion Region, const TString& rkGameNa
|
||||
#error Fix export directory being cleared!
|
||||
#endif
|
||||
|
||||
bool CGameExporter::Export(nod::DiscBase *pDisc, const TString& rkOutputDir, CAssetNameMap *pNameMap, CGameInfo *pGameInfo)
|
||||
bool CGameExporter::Export(nod::DiscBase *pDisc, const TString& rkOutputDir, CAssetNameMap *pNameMap, CGameInfo *pGameInfo, IProgressNotifier *pProgress)
|
||||
{
|
||||
SCOPED_TIMER(ExportGame);
|
||||
|
||||
@@ -44,6 +45,10 @@ bool CGameExporter::Export(nod::DiscBase *pDisc, const TString& rkOutputDir, CAs
|
||||
mDiscDir = "Disc/";
|
||||
mWorldsDirName = "Worlds/";
|
||||
|
||||
// Init progress
|
||||
mpProgress = pProgress;
|
||||
mpProgress->SetNumTasks(eES_NumSteps);
|
||||
|
||||
// Create project
|
||||
FileUtil::MakeDirectory(mExportDir);
|
||||
FileUtil::ClearDirectory(mExportDir);
|
||||
@@ -72,17 +77,22 @@ bool CGameExporter::Export(nod::DiscBase *pDisc, const TString& rkOutputDir, CAs
|
||||
CResourceStore *pOldStore = gpResourceStore;
|
||||
gpResourceStore = mpStore;
|
||||
|
||||
// Export game data
|
||||
// Export cooked data
|
||||
LoadPaks();
|
||||
ExportCookedResources();
|
||||
mpProject->AudioManager()->LoadAssets();
|
||||
ExportResourceEditorData();
|
||||
|
||||
// Export editor data
|
||||
if (!mpProgress->ShouldCancel())
|
||||
{
|
||||
mpProject->AudioManager()->LoadAssets();
|
||||
ExportResourceEditorData();
|
||||
}
|
||||
|
||||
// Export finished!
|
||||
mProjectPath = mpProject->ProjectPath();
|
||||
delete mpProject;
|
||||
if (pOldStore) gpResourceStore = pOldStore;
|
||||
return true;
|
||||
return !mpProgress->ShouldCancel();
|
||||
}
|
||||
|
||||
void CGameExporter::LoadResource(const CAssetID& rkID, std::vector<u8>& rBuffer)
|
||||
@@ -97,6 +107,9 @@ bool CGameExporter::ExtractDiscData()
|
||||
// todo: handle dol, apploader, multiple partitions, wii ticket blob
|
||||
SCOPED_TIMER(ExtractDiscData);
|
||||
|
||||
// Init progress
|
||||
mpProgress->SetTask(eES_ExtractDisc, "Extracting disc files");
|
||||
|
||||
// Create Disc output folder
|
||||
TString AbsDiscDir = mExportDir + mDiscDir;
|
||||
FileUtil::MakeDirectory(AbsDiscDir);
|
||||
@@ -106,43 +119,52 @@ bool CGameExporter::ExtractDiscData()
|
||||
nod::ExtractionContext Context;
|
||||
Context.force = false;
|
||||
Context.verbose = false;
|
||||
Context.progressCB = nullptr;
|
||||
Context.progressCB = [&](const std::string& rkDesc) {
|
||||
mpProgress->Report(0, 1, rkDesc);
|
||||
};
|
||||
|
||||
bool Success = ExtractDiscNodeRecursive(&pDataPartition->getFSTRoot(), AbsDiscDir, Context);
|
||||
if (!Success) return false;
|
||||
|
||||
// Extract dol
|
||||
mDolPath = "boot.dol";
|
||||
CFileOutStream DolFile(mExportDir + mDolPath);
|
||||
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 = "apploader.img";
|
||||
CFileOutStream ApploaderFile(mExportDir + mApploaderPath);
|
||||
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)
|
||||
// Extract the remaining disc data
|
||||
if (!mpProgress->ShouldCancel())
|
||||
{
|
||||
mFilesystemAddress = 0;
|
||||
mPartitionHeaderPath = "partition_header.bin";
|
||||
nod::DiscWii *pDiscWii = static_cast<nod::DiscWii*>(mpDisc);
|
||||
Success = pDiscWii->writeOutDataPartitionHeader(*TString(mExportDir + mPartitionHeaderPath).ToUTF16());
|
||||
if (!Success) return false;
|
||||
// Extract dol
|
||||
mDolPath = "boot.dol";
|
||||
CFileOutStream DolFile(mExportDir + mDolPath);
|
||||
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 = "apploader.img";
|
||||
CFileOutStream ApploaderFile(mExportDir + mApploaderPath);
|
||||
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)
|
||||
{
|
||||
mFilesystemAddress = 0;
|
||||
mPartitionHeaderPath = "partition_header.bin";
|
||||
nod::DiscWii *pDiscWii = static_cast<nod::DiscWii*>(mpDisc);
|
||||
Success = pDiscWii->writeOutDataPartitionHeader(*TString(mExportDir + mPartitionHeaderPath).ToUTF16());
|
||||
if (!Success) return false;
|
||||
}
|
||||
else
|
||||
mFilesystemAddress = (u32) pDataPartition->getFSTMemoryAddr();
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
mFilesystemAddress = (u32) pDataPartition->getFSTMemoryAddr();
|
||||
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CGameExporter::ExtractDiscNodeRecursive(const nod::Node *pkNode, const TString& rkDir, const nod::ExtractionContext& rkContext)
|
||||
@@ -447,9 +469,18 @@ void CGameExporter::ExportCookedResources()
|
||||
SCOPED_TIMER(ExportCookedResources);
|
||||
FileUtil::MakeDirectory(mCookedDir);
|
||||
|
||||
for (auto It = mResourceMap.begin(); It != mResourceMap.end(); It++)
|
||||
mpProgress->SetTask(eES_ExportCooked, "Unpacking cooked assets");
|
||||
int ResIndex = 0;
|
||||
|
||||
for (auto It = mResourceMap.begin(); It != mResourceMap.end() && !mpProgress->ShouldCancel(); It++, ResIndex++)
|
||||
{
|
||||
SResourceInstance& rRes = It->second;
|
||||
|
||||
// Update progress
|
||||
if ((ResIndex & 0x3) == 0)
|
||||
mpProgress->Report(ResIndex, mResourceMap.size(), TString::Format("Unpacking asset %d/%d", ResIndex, mResourceMap.size()) );
|
||||
|
||||
// Export resource
|
||||
ExportResource(rRes);
|
||||
}
|
||||
}
|
||||
@@ -462,12 +493,19 @@ void CGameExporter::ExportResourceEditorData()
|
||||
// because we have to load the resource to build its dependency tree and
|
||||
// some resources will fail to load if their dependencies don't exist
|
||||
SCOPED_TIMER(SaveRawResources);
|
||||
mpProgress->SetTask(eES_GenerateRaw, "Generating editor data");
|
||||
int ResIndex = 0;
|
||||
|
||||
// todo: we're wasting a ton of time loading the same resources over and over because most resources automatically
|
||||
// load all their dependencies and then we just clear it out from memory even though we'll need it again later. we
|
||||
// should really be doing this by dependency order instead of by ID order.
|
||||
for (CResourceIterator It(mpStore); It; ++It)
|
||||
for (CResourceIterator It(mpStore); It && !mpProgress->ShouldCancel(); ++It, ++ResIndex)
|
||||
{
|
||||
// Update progress
|
||||
if ((ResIndex & 0x3) == 0)
|
||||
mpProgress->Report(ResIndex, mpStore->NumTotalResources(), TString::Format("Processing asset %d/%d: %s",
|
||||
ResIndex, mpStore->NumTotalResources(), *It->CookedAssetPath(true).GetFileName()) );
|
||||
|
||||
// Worlds need some info we can only get from the pak at export time; namely, which areas can
|
||||
// have duplicates, as well as the world's internal name.
|
||||
if (It->ResourceType() == eWorld)
|
||||
@@ -496,6 +534,8 @@ void CGameExporter::ExportResourceEditorData()
|
||||
It->UpdateDependencies();
|
||||
}
|
||||
}
|
||||
|
||||
if (!mpProgress->ShouldCancel())
|
||||
{
|
||||
// All resources should have dependencies generated, so save the project files
|
||||
SCOPED_TIMER(SaveResourceDatabase);
|
||||
|
||||
@@ -57,9 +57,21 @@ class CGameExporter
|
||||
};
|
||||
std::map<CAssetID, SResourceInstance> mResourceMap;
|
||||
|
||||
// Progress
|
||||
IProgressNotifier *mpProgress;
|
||||
|
||||
enum EExportStep
|
||||
{
|
||||
eES_ExtractDisc,
|
||||
eES_ExportCooked,
|
||||
eES_GenerateRaw,
|
||||
// eES_Max must be last
|
||||
eES_NumSteps
|
||||
};
|
||||
|
||||
public:
|
||||
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);
|
||||
bool Export(nod::DiscBase *pDisc, const TString& rkOutputDir, CAssetNameMap *pNameMap, CGameInfo *pGameInfo, IProgressNotifier *pProgress);
|
||||
void LoadResource(const CAssetID& rkID, std::vector<u8>& rBuffer);
|
||||
|
||||
inline TString ProjectPath() const { return mProjectPath; }
|
||||
|
||||
@@ -95,9 +95,7 @@ void CGameProject::Serialize(IArchive& rArc)
|
||||
}
|
||||
}
|
||||
|
||||
void ProgressDummy(size_t, const nod::SystemString&, size_t) {}
|
||||
|
||||
bool CGameProject::BuildISO(const TString& rkIsoPath)
|
||||
bool CGameProject::BuildISO(const TString& rkIsoPath, IProgressNotifier *pProgress)
|
||||
{
|
||||
ASSERT( FileUtil::IsValidPath(rkIsoPath, false) );
|
||||
|
||||
@@ -109,7 +107,13 @@ bool CGameProject::BuildISO(const TString& rkIsoPath)
|
||||
|
||||
else
|
||||
{
|
||||
nod::DiscBuilderGCN *pBuilder = new nod::DiscBuilderGCN(*rkIsoPath.ToUTF16(), *mGameID, *mProjectName, mFilesystemAddress, &ProgressDummy);
|
||||
auto ProgressCallback = [&](size_t, const nod::SystemString& rkInfoString, size_t)
|
||||
{
|
||||
pProgress->Report(0, 0, TWideString(rkInfoString).ToUTF8());
|
||||
};
|
||||
|
||||
nod::DiscBuilderGCN *pBuilder = new nod::DiscBuilderGCN(*rkIsoPath.ToUTF16(), *mGameID, *mProjectName, mFilesystemAddress, ProgressCallback);
|
||||
pProgress->SetTask(0, "Building " + rkIsoPath.GetFileName());
|
||||
|
||||
TWideString ProjRoot = ProjectRoot().ToUTF16();
|
||||
TWideString DiscRoot = DiscDir(false).ToUTF16();
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "CPackage.h"
|
||||
#include "CResourceStore.h"
|
||||
#include "Core/CAudioManager.h"
|
||||
#include "Core/IProgressNotifier.h"
|
||||
#include "Core/Resource/Script/CMasterTemplate.h"
|
||||
#include <Common/CAssetID.h>
|
||||
#include <Common/EGame.h>
|
||||
@@ -65,7 +66,7 @@ public:
|
||||
|
||||
bool Save();
|
||||
void Serialize(IArchive& rArc);
|
||||
bool BuildISO(const TString& rkIsoPath);
|
||||
bool BuildISO(const TString& rkIsoPath, IProgressNotifier *pProgress);
|
||||
void GetWorldList(std::list<CAssetID>& rOut) const;
|
||||
CAssetID FindNamedResource(const TString& rkName) const;
|
||||
CPackage* FindPackage(const TString& rkName) const;
|
||||
|
||||
@@ -59,9 +59,13 @@ void CPackage::UpdateDependencyCache() const
|
||||
mCacheDirty = false;
|
||||
}
|
||||
|
||||
void CPackage::Cook()
|
||||
void CPackage::Cook(IProgressNotifier *pProgress)
|
||||
{
|
||||
SCOPED_TIMER(CookPackage);
|
||||
|
||||
// Build asset list
|
||||
pProgress->Report(-1, -1, "Building dependency list");
|
||||
|
||||
CPackageDependencyListBuilder Builder(this);
|
||||
std::list<CAssetID> AssetList;
|
||||
Builder.BuildDependencyList(true, AssetList);
|
||||
@@ -161,7 +165,7 @@ void CPackage::Cook()
|
||||
u32 ResIdx = 0;
|
||||
u32 ResDataOffset = Pak.Tell();
|
||||
|
||||
for (auto Iter = AssetList.begin(); Iter != AssetList.end(); Iter++, ResIdx++)
|
||||
for (auto Iter = AssetList.begin(); Iter != AssetList.end() && !pProgress->ShouldCancel(); Iter++, ResIdx++)
|
||||
{
|
||||
// Initialize entry, recook assets if needed
|
||||
u32 AssetOffset = Pak.Tell();
|
||||
@@ -170,8 +174,18 @@ void CPackage::Cook()
|
||||
ASSERT(pEntry != nullptr);
|
||||
|
||||
if (pEntry->NeedsRecook())
|
||||
{
|
||||
pProgress->Report(ResIdx, AssetList.size(), "Cooking asset: " + pEntry->Name() + "." + pEntry->CookedExtension());
|
||||
pEntry->Cook();
|
||||
}
|
||||
|
||||
// Update progress bar
|
||||
if (ResIdx & 0x1 || ResIdx == AssetList.size() - 1)
|
||||
{
|
||||
pProgress->Report(ResIdx, AssetList.size(), TString::Format("Writing asset %d/%d: %s", ResIdx+1, AssetList.size(), *(pEntry->Name() + "." + pEntry->CookedExtension())));
|
||||
}
|
||||
|
||||
// Update table info
|
||||
SResourceTableInfo& rTableInfo = ResourceTableData[ResIdx];
|
||||
rTableInfo.pEntry = pEntry;
|
||||
rTableInfo.Offset = (Game <= eEchoes ? AssetOffset : AssetOffset - ResDataOffset);
|
||||
@@ -267,40 +281,51 @@ void CPackage::Cook()
|
||||
}
|
||||
ResDataSize = Pak.Tell() - ResDataOffset;
|
||||
|
||||
// Write table of contents for real
|
||||
if (Game >= eCorruption)
|
||||
// If we cancelled, don't finish writing the pak; delete the file instead and make sure the package is flagged for recook
|
||||
if (pProgress->ShouldCancel())
|
||||
{
|
||||
Pak.Seek(TocOffset, SEEK_SET);
|
||||
Pak.WriteLong(3); // Always 3 pak sections
|
||||
Pak.WriteFourCC( FOURCC('STRG') );
|
||||
Pak.WriteLong(NamesSize);
|
||||
Pak.WriteFourCC( FOURCC('RSHD') );
|
||||
Pak.WriteLong(ResTableSize);
|
||||
Pak.WriteFourCC( FOURCC('DATA') );
|
||||
Pak.WriteLong(ResDataSize);
|
||||
Pak.Close();
|
||||
FileUtil::DeleteFile(PakPath);
|
||||
mNeedsRecook = true;
|
||||
}
|
||||
|
||||
// Write resource table for real
|
||||
Pak.Seek(ResTableOffset+4, SEEK_SET);
|
||||
|
||||
for (u32 iRes = 0; iRes < AssetList.size(); iRes++)
|
||||
else
|
||||
{
|
||||
const SResourceTableInfo& rkInfo = ResourceTableData[iRes];
|
||||
CResourceEntry *pEntry = rkInfo.pEntry;
|
||||
// Write table of contents for real
|
||||
if (Game >= eCorruption)
|
||||
{
|
||||
Pak.Seek(TocOffset, SEEK_SET);
|
||||
Pak.WriteLong(3); // Always 3 pak sections
|
||||
Pak.WriteFourCC( FOURCC('STRG') );
|
||||
Pak.WriteLong(NamesSize);
|
||||
Pak.WriteFourCC( FOURCC('RSHD') );
|
||||
Pak.WriteLong(ResTableSize);
|
||||
Pak.WriteFourCC( FOURCC('DATA') );
|
||||
Pak.WriteLong(ResDataSize);
|
||||
}
|
||||
|
||||
Pak.WriteLong( rkInfo.Compressed ? 1 : 0 );
|
||||
pEntry->CookedExtension().Write(Pak);
|
||||
pEntry->ID().Write(Pak);
|
||||
Pak.WriteLong(rkInfo.Size);
|
||||
Pak.WriteLong(rkInfo.Offset);
|
||||
// Write resource table for real
|
||||
Pak.Seek(ResTableOffset+4, SEEK_SET);
|
||||
|
||||
for (u32 iRes = 0; iRes < AssetList.size(); iRes++)
|
||||
{
|
||||
const SResourceTableInfo& rkInfo = ResourceTableData[iRes];
|
||||
CResourceEntry *pEntry = rkInfo.pEntry;
|
||||
|
||||
Pak.WriteLong( rkInfo.Compressed ? 1 : 0 );
|
||||
pEntry->CookedExtension().Write(Pak);
|
||||
pEntry->ID().Write(Pak);
|
||||
Pak.WriteLong(rkInfo.Size);
|
||||
Pak.WriteLong(rkInfo.Offset);
|
||||
}
|
||||
|
||||
// Clear recook flag
|
||||
mNeedsRecook = false;
|
||||
Log::Write("Finished writing " + PakPath);
|
||||
}
|
||||
|
||||
// Clear recook flag
|
||||
mNeedsRecook = false;
|
||||
Save();
|
||||
|
||||
Log::Write("Finished writing " + PakPath);
|
||||
|
||||
// Update resource store in case we recooked any assets
|
||||
mpProject->ResourceStore()->ConditionalSaveStore();
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <Common/CFourCC.h>
|
||||
#include <Common/TString.h>
|
||||
#include <Common/Serialization/IArchive.h>
|
||||
#include "Core/IProgressNotifier.h"
|
||||
|
||||
class CGameProject;
|
||||
|
||||
@@ -57,7 +58,7 @@ public:
|
||||
void AddResource(const TString& rkName, const CAssetID& rkID, const CFourCC& rkType);
|
||||
void UpdateDependencyCache() const;
|
||||
|
||||
void Cook();
|
||||
void Cook(IProgressNotifier *pProgress);
|
||||
void CompareOriginalAssetList(const std::list<CAssetID>& rkNewList);
|
||||
bool ContainsAsset(const CAssetID& rkID) const;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user