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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

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