Added progress bars for most major blocking operations

This commit is contained in:
Aruki
2017-05-21 18:01:09 -06:00
parent 31621874a6
commit 0a9b052413
23 changed files with 569 additions and 655 deletions

View File

@@ -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);

View File

@@ -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; }

View File

@@ -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();

View File

@@ -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;

View File

@@ -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();
}

View File

@@ -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;