Added support for exporting Trilogy and Wii de Asobu builds

This commit is contained in:
Aruki
2017-07-24 21:08:12 -06:00
parent 305fbbdeed
commit e4d7c37541
13 changed files with 11430 additions and 494 deletions

View File

@@ -17,12 +17,14 @@
#define USE_ASSET_NAME_MAP 1
#define EXPORT_COOKED 1
CGameExporter::CGameExporter(EGame Game, ERegion Region, const TString& rkGameName, const TString& rkGameID, float BuildVersion)
CGameExporter::CGameExporter(EDiscType DiscType, EGame Game, bool FrontEnd, ERegion Region, const TString& rkGameName, const TString& rkGameID, float BuildVersion)
: mGame(Game)
, mRegion(Region)
, mGameName(rkGameName)
, mGameID(rkGameID)
, mBuildVersion(BuildVersion)
, mDiscType(DiscType)
, mFrontEnd(FrontEnd)
, mpProgress(nullptr)
{
ASSERT(mGame != eUnknownGame);
@@ -96,6 +98,70 @@ void CGameExporter::LoadResource(const CAssetID& rkID, std::vector<u8>& rBuffer)
if (pInst) LoadResource(*pInst, rBuffer);
}
bool CGameExporter::ShouldExportDiscNode(const nod::Node *pkNode, bool IsInRoot)
{
if (IsInRoot && mDiscType != eDT_Normal)
{
// Directories - exclude the filesystem for other games
if (pkNode->getKind() == nod::Node::Kind::Directory)
{
// Frontend is always included; this is for compatibility with Dolphin
if (pkNode->getName() == "fe")
return true;
else if (mFrontEnd)
return false;
switch (mGame)
{
case ePrime:
return ( (mDiscType == eDT_WiiDeAsobu && pkNode->getName() == "MP1JPN") ||
(mDiscType == eDT_Trilogy && pkNode->getName() == "MP1") );
case eEchoes:
return ( (mDiscType == eDT_WiiDeAsobu && pkNode->getName() == "MP2JPN") ||
(mDiscType == eDT_Trilogy && pkNode->getName() == "MP2") );
case eCorruption:
return (mDiscType == eDT_Trilogy && pkNode->getName() == "MP3");
default:
return false;
}
}
// Files - exclude the DOLs for other games
else
{
// Again - always include frontend. Always include opening.bnr as well.
if (pkNode->getName() == "rs5fe_p.dol" || pkNode->getName() == "opening.bnr")
return true;
else if (mFrontEnd)
return false;
switch (mGame)
{
case ePrime:
return ( (mDiscType == eDT_WiiDeAsobu && pkNode->getName() == "rs5mp1jpn_p.dol") ||
(mDiscType == eDT_Trilogy && pkNode->getName() == "rs5mp1_p.dol") );
case eEchoes:
return ( (mDiscType == eDT_WiiDeAsobu && pkNode->getName() == "rs5mp2jpn_p.dol") ||
(mDiscType == eDT_Trilogy && pkNode->getName() == "rs5mp2_p.dol") );
case eCorruption:
return (mDiscType == eDT_Trilogy && pkNode->getName() == "rs5mp3_p.dol");
default:
return false;
}
}
}
return true;
}
// ************ PROTECTED ************
bool CGameExporter::ExtractDiscData()
{
@@ -122,7 +188,7 @@ bool CGameExporter::ExtractDiscData()
TString FilesDir = AbsDiscDir + "files/";
FileUtil::MakeDirectory(FilesDir);
bool Success = ExtractDiscNodeRecursive(&pDataPartition->getFSTRoot(), FilesDir, Context);
bool Success = ExtractDiscNodeRecursive(&pDataPartition->getFSTRoot(), FilesDir, true, Context);
if (!Success) return false;
if (!mpProgress->ShouldCancel())
@@ -150,18 +216,25 @@ bool CGameExporter::ExtractDiscData()
return false;
}
bool CGameExporter::ExtractDiscNodeRecursive(const nod::Node *pkNode, const TString& rkDir, const nod::ExtractionContext& rkContext)
bool CGameExporter::ExtractDiscNodeRecursive(const nod::Node *pkNode, const TString& rkDir, bool RootNode, const nod::ExtractionContext& rkContext)
{
for (nod::Node::DirectoryIterator Iter = pkNode->begin(); Iter != pkNode->end(); ++Iter)
{
if (!ShouldExportDiscNode(&*Iter, RootNode))
continue;
if (Iter->getKind() == nod::Node::Kind::File)
{
TString FilePath = rkDir + Iter->getName();
bool Success = Iter->extractToDirectory(*rkDir.ToUTF16(), rkContext);
if (!Success) return false;
if (FilePath.GetFileExtension() == "pak")
mPaks.push_back(FilePath);
if (FilePath.GetFileExtension().CaseInsensitiveCompare("pak"))
{
// For multi-game Wii discs, don't track packages for frontend unless we're exporting frontend
if (mDiscType == eDT_Normal || mFrontEnd || pkNode->getName() != "fe")
mPaks.push_back(FilePath);
}
}
else
@@ -170,7 +243,7 @@ bool CGameExporter::ExtractDiscNodeRecursive(const nod::Node *pkNode, const TStr
bool Success = FileUtil::MakeDirectory(Subdir);
if (!Success) return false;
Success = ExtractDiscNodeRecursive(&*Iter, Subdir, rkContext);
Success = ExtractDiscNodeRecursive(&*Iter, Subdir, false, rkContext);
if (!Success) return false;
}
}

View File

@@ -12,6 +12,13 @@
#include <map>
#include <nod/nod.hpp>
enum EDiscType
{
eDT_Normal,
eDT_WiiDeAsobu,
eDT_Trilogy
};
class CGameExporter
{
// Project Data
@@ -33,6 +40,8 @@ class CGameExporter
// Files
nod::DiscBase *mpDisc;
EDiscType mDiscType;
bool mFrontEnd;
// Resources
TStringList mPaks;
@@ -65,15 +74,16 @@ class CGameExporter
};
public:
CGameExporter(EGame Game, ERegion Region, const TString& rkGameName, const TString& rkGameID, float BuildVersion);
CGameExporter(EDiscType DiscType, EGame Game, bool FrontEnd, ERegion Region, const TString& rkGameName, const TString& rkGameID, float BuildVersion);
bool Export(nod::DiscBase *pDisc, const TString& rkOutputDir, CAssetNameMap *pNameMap, CGameInfo *pGameInfo, IProgressNotifier *pProgress);
void LoadResource(const CAssetID& rkID, std::vector<u8>& rBuffer);
bool ShouldExportDiscNode(const nod::Node *pkNode, bool IsInRoot);
inline TString ProjectPath() const { return mProjectPath; }
protected:
bool ExtractDiscData();
bool ExtractDiscNodeRecursive(const nod::Node *pkNode, const TString& rkDir, const nod::ExtractionContext& rkContext);
bool ExtractDiscNodeRecursive(const nod::Node *pkNode, const TString& rkDir, bool RootNode, const nod::ExtractionContext& rkContext);
void LoadPaks();
void LoadResource(const SResourceInstance& rkResource, std::vector<u8>& rBuffer);
void ExportCookedResources();

View File

@@ -100,6 +100,7 @@ public:
inline float BuildVersion() const { return mBuildVersion; }
inline bool IsWiiBuild() const { return mBuildVersion >= 3.f; }
inline bool IsTrilogy() const { return mGame <= eCorruption && mBuildVersion >= 3.593f; }
inline bool IsWiiDeAsobu() const { return mGame < eCorruption && mBuildVersion >= 3.f && mBuildVersion < 3.593f; }
};
#endif // CGAMEPROJECT_H

View File

@@ -170,9 +170,14 @@ bool CVirtualDirectory::AddChild(const TString &rkPath, CResourceEntry *pEntry)
{
// Create new subdirectory
pSubdir = new CVirtualDirectory(this, DirName, mpStore);
FileUtil::MakeDirectory(mpStore->ResourcesDir() + pSubdir->FullPath());
mSubdirectories.push_back(pSubdir);
if (!pSubdir->CreateFilesystemDirectory())
{
delete pSubdir;
return false;
}
mSubdirectories.push_back(pSubdir);
std::sort(mSubdirectories.begin(), mSubdirectories.end(), [](CVirtualDirectory *pLeft, CVirtualDirectory *pRight) -> bool {
return (pLeft->Name().ToUpper() < pRight->Name().ToUpper());
});
@@ -184,6 +189,13 @@ bool CVirtualDirectory::AddChild(const TString &rkPath, CResourceEntry *pEntry)
for (auto Iter = Components.begin(); Iter != Components.end(); Iter++)
{
pSubdir = new CVirtualDirectory(pSubdir, *Iter, mpStore);
if (!pSubdir->CreateFilesystemDirectory())
{
delete pSubdir;
return false;
}
pSubdir->Parent()->mSubdirectories.push_back(pSubdir);
}
@@ -307,6 +319,23 @@ void CVirtualDirectory::DeleteEmptySubdirectories()
}
}
bool CVirtualDirectory::CreateFilesystemDirectory()
{
TString AbsPath = AbsolutePath();
if (!FileUtil::Exists(AbsPath))
{
bool CreateSuccess = FileUtil::MakeDirectory(AbsPath);
if (!CreateSuccess)
Log::Error("FAILED to create filesystem directory: " + AbsPath);
return CreateSuccess;
}
return true;
}
bool CVirtualDirectory::SetParent(CVirtualDirectory *pParent)
{
ASSERT(!pParent->IsDescendantOf(this));

View File

@@ -39,6 +39,7 @@ public:
bool Rename(const TString& rkNewName);
bool Delete();
void DeleteEmptySubdirectories();
bool CreateFilesystemDirectory();
bool SetParent(CVirtualDirectory *pParent);
static bool IsValidDirectoryName(const TString& rkName);

View File

@@ -21,9 +21,13 @@
CExportGameDialog::CExportGameDialog(const QString& rkIsoPath, const QString& rkExportDir, QWidget *pParent /*= 0*/)
: QDialog(pParent)
, mpUI(new Ui::CExportGameDialog)
, mpDisc(nullptr)
, mpExporter(nullptr)
, mDiscType(eDT_Normal)
, mGame(eUnknownGame)
, mRegion(eRegion_Unknown)
, mTrilogy(false)
, mBuildVer(0.f)
, mWiiFrontend(false)
, mExportSuccess(false)
{
mpUI->setupUi(this);
@@ -35,25 +39,19 @@ CExportGameDialog::CExportGameDialog(const QString& rkIsoPath, const QString& rk
if (ValidateGame())
{
mBuildVer = FindBuildVersion();
mpExporter = new CGameExporter(mDiscType, mGame, mWiiFrontend, mRegion, mGameTitle, mGameID, mBuildVer);
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;
delete mpExporter;
}
void RecursiveAddToTree(const nod::Node *pkNode, QTreeWidgetItem *pParent);
@@ -89,13 +87,12 @@ void CExportGameDialog::InitUI(QString ExportDir)
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);
const nod::Node *pkDiscRoot = &pPartition->getFSTRoot();
RecursiveAddToTree(pkDiscRoot, pTreeRoot);
pTreeRoot->setIcon(0, QIcon(":/icons/Disc_16px.png"));
pTreeRoot->setExpanded(true);
@@ -175,14 +172,28 @@ bool CExportGameDialog::ValidateGame()
case FOURCC('R3MX'):
// Trilogy
mTrilogy = true;
if (!RequestTrilogyGame()) return false;
mDiscType = eDT_Trilogy;
if (!RequestWiiPortGame()) return false;
// Force change game name so it isn't "Metroid Prime Trilogy"
if (!mWiiFrontend)
mGameTitle = GetGameName(mGame);
break;
case FOURCC('R3IX'):
// MP1 Wii de Asobu
mGame = ePrime;
mDiscType = eDT_WiiDeAsobu;
if (!RequestWiiPortGame()) return false;
break;
case FOURCC('R32X'):
// MP2 Wii de Asobu
mGame = eEchoes;
mDiscType = eDT_WiiDeAsobu;
if (!RequestWiiPortGame()) return false;
break;
default:
// Unrecognized game ID
return false;
@@ -191,16 +202,25 @@ bool CExportGameDialog::ValidateGame()
return true;
}
bool CExportGameDialog::RequestTrilogyGame()
bool CExportGameDialog::RequestWiiPortGame()
{
QDialog Dialog;
Dialog.setWindowTitle("Select Trilogy Game");
Dialog.setWindowTitle("Select Game");
bool IsTrilogy = (mGame == eUnknownGame);
bool HasMP1 = (IsTrilogy || mGame == ePrime);
bool HasMP2 = (IsTrilogy || mGame == eEchoes);
bool HasMP3 = IsTrilogy;
QString GameName = (IsTrilogy ? "Metroid Prime: Trilogy" : "Wii de Asobu");
QString LabelText = QString("You have selected a %1 ISO. Please pick a game to export:").arg(GameName);
QLabel Label(LabelText, &Dialog);
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");
ComboBox.addItem("Front End");
if (HasMP1) ComboBox.addItem("Metroid Prime");
if (HasMP2) ComboBox.addItem("Metroid Prime 2: Echoes");
if (HasMP3) 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()));
@@ -217,10 +237,24 @@ bool CExportGameDialog::RequestTrilogyGame()
{
switch (ComboBox.currentIndex())
{
case 0: mGame = ePrime; break;
case 1: mGame = eEchoes; break;
case 2: mGame = eCorruption; break;
case 0:
mGame = eCorruption;
mWiiFrontend = true;
break;
case 1:
mGame = (HasMP1 ? ePrime : eEchoes);
break;
case 2:
mGame = eEchoes;
break;
case 3:
mGame = eCorruption;
break;
}
return true;
}
else return false;
@@ -263,7 +297,7 @@ float CExportGameDialog::FindBuildVersion()
return 0.f;
}
void RecursiveAddToTree(const nod::Node *pkNode, QTreeWidgetItem *pParent)
void CExportGameDialog::RecursiveAddToTree(const nod::Node *pkNode, QTreeWidgetItem *pParent)
{
// Get sorted list of nodes
std::list<const nod::Node*> NodeList;
@@ -285,6 +319,10 @@ void RecursiveAddToTree(const nod::Node *pkNode, QTreeWidgetItem *pParent)
for (auto Iter = NodeList.begin(); Iter != NodeList.end(); Iter++)
{
const nod::Node *pkNode = *Iter;
if (!mpExporter->ShouldExportDiscNode(pkNode, pParent->parent() == nullptr))
continue;
bool IsDir = pkNode->getKind() == nod::Node::Kind::Directory;
QTreeWidgetItem *pItem = new QTreeWidgetItem(pParent, QStringList(QString::fromStdString(pkNode->getName())) );
@@ -382,12 +420,11 @@ void CExportGameDialog::Export()
// Do export
close();
CGameExporter Exporter(mGame, mRegion, mGameTitle, mGameID, mBuildVer);
TString StrExportDir = TO_TSTRING(ExportDir);
StrExportDir.EnsureEndsWith('/');
CProgressDialog Dialog("Creating new game project", false, true, parentWidget());
QFuture<bool> Future = QtConcurrent::run(&Exporter, &CGameExporter::Export, mpDisc, StrExportDir, &NameMap, &GameInfo, &Dialog);
QFuture<bool> Future = QtConcurrent::run(mpExporter, &CGameExporter::Export, mpDisc, StrExportDir, &NameMap, &GameInfo, &Dialog);
mExportSuccess = Dialog.WaitForResults(Future);
if (!mExportSuccess)
@@ -396,5 +433,5 @@ void CExportGameDialog::Export()
UICommon::ErrorMsg(this, "Export failed!");
}
else
mNewProjectPath = TO_QSTRING(Exporter.ProjectPath());
mNewProjectPath = TO_QSTRING(mpExporter->ProjectPath());
}

View File

@@ -2,6 +2,7 @@
#define CEXPORTGAMEDIALOG_H
#include <Common/EGame.h>
#include <Core/GameProject/CGameExporter.h>
#include <Core/GameProject/CGameProject.h>
#include <QDialog>
#include <QString>
@@ -17,13 +18,17 @@ class CExportGameDialog : public QDialog
Ui::CExportGameDialog *mpUI;
nod::DiscBase *mpDisc;
CGameExporter *mpExporter;
TString mGameTitle;
TString mGameID;
// Build Info
EDiscType mDiscType;
EGame mGame;
ERegion mRegion;
float mBuildVer;
bool mTrilogy;
bool mWiiFrontend;
bool mExportSuccess;
QString mNewProjectPath;
@@ -34,9 +39,12 @@ public:
void InitUI(QString ExportDir);
bool ValidateGame();
bool RequestTrilogyGame();
bool RequestWiiPortGame();
float FindBuildVersion();
// Disc Tree
void RecursiveAddToTree(const nod::Node *pkNode, class QTreeWidgetItem *pParent);
// Accessors
inline bool HasValidDisc() const { return mpDisc != nullptr; }
inline bool ExportSucceeded() const { return mExportSuccess; }

View File

@@ -18,15 +18,19 @@ CProgressDialog::CProgressDialog(QString OperationName, bool UseBusyIndicator, b
#if WIN32
QWinTaskbarButton *pButton = new QWinTaskbarButton(this);
QWindow *pWindow = parentWidget()->windowHandle();
ASSERT(pWindow);
pButton->setWindow(pWindow);
mpTaskbarProgress = pButton->progress();
mpTaskbarProgress->setMinimum(0);
mpTaskbarProgress->setMaximum(UseBusyIndicator ? 0 : 10000);
mpTaskbarProgress->show();
QWindow *pWindow = UICommon::FindWidgetWindowHandle( parentWidget() );
if (pWindow)
{
pButton->setWindow(pWindow);
mpTaskbarProgress = pButton->progress();
mpTaskbarProgress->setMinimum(0);
mpTaskbarProgress->setMaximum(UseBusyIndicator ? 0 : 10000);
mpTaskbarProgress->show();
}
else
mpTaskbarProgress = nullptr;
setWindowFlags(windowFlags() | Qt::MSWindowsFixedSizeDialogHint);
#endif
@@ -60,8 +64,11 @@ void CProgressDialog::closeEvent(QCloseEvent *pEvent)
pEvent->accept();
#if WIN32
mpTaskbarProgress->reset();
mpTaskbarProgress->hide();
if (mpTaskbarProgress)
{
mpTaskbarProgress->reset();
mpTaskbarProgress->hide();
}
#endif
}
}
@@ -102,7 +109,8 @@ void CProgressDialog::UpdateUI(const QString& rkTaskDesc, const QString& rkStepD
mpUI->ProgressBar->setValue(ProgressValue);
#if WIN32
mpTaskbarProgress->setValue(ProgressValue);
if (mpTaskbarProgress)
mpTaskbarProgress->setValue(ProgressValue);
#endif
}
}

View File

@@ -107,7 +107,7 @@ public:
for (u32 Shift = 0; Shift <= MaxShift; Shift += 4)
{
u64 ShiftCompare = mCompareID << Shift;
u32 Mask = mCompareMask << Shift;
u64 Mask = mCompareMask << Shift;
if ((ID & Mask) == ShiftCompare)
{

View File

@@ -5,6 +5,14 @@
namespace UICommon
{
QWindow* FindWidgetWindowHandle(QWidget *pWidget)
{
while (pWidget && !pWidget->windowHandle())
pWidget = pWidget->parentWidget();
return pWidget ? pWidget->windowHandle() : nullptr;
}
void OpenContainingFolder(const QString& rkPath)
{
#if WIN32

View File

@@ -40,6 +40,7 @@ namespace UICommon
{
// Utility
QWindow* FindWidgetWindowHandle(QWidget *pWidget);
void OpenContainingFolder(const QString& rkPath);
bool OpenInExternalApplication(const QString& rkPath);