ISOProperties: Separate the filesystem tab out into its own class

This commit is contained in:
Lioncash 2016-12-04 02:04:35 -05:00
parent d1c89db8c8
commit ddeccf2622
7 changed files with 735 additions and 496 deletions

View file

@ -34,6 +34,7 @@ set(GUI_SRCS
Debugger/RegisterWindow.cpp
Debugger/WatchView.cpp
Debugger/WatchWindow.cpp
ISOProperties/FilesystemPanel.cpp
ISOProperties/InfoPanel.cpp
ISOProperties/ISOProperties.cpp
NetPlay/ChangeGameDialog.cpp

View file

@ -90,6 +90,7 @@
<ClCompile Include="Debugger\WatchView.cpp" />
<ClCompile Include="Debugger\WatchWindow.cpp" />
<ClCompile Include="DolphinSlider.cpp" />
<ClCompile Include="ISOProperties\FilesystemPanel.cpp" />
<ClCompile Include="ISOProperties\InfoPanel.cpp" />
<ClCompile Include="ISOProperties\ISOProperties.cpp" />
<ClCompile Include="NetPlay\ChangeGameDialog.cpp" />
@ -146,6 +147,7 @@
<ClInclude Include="Config\PathConfigPane.h" />
<ClInclude Include="Config\WiiConfigPane.h" />
<ClInclude Include="DolphinSlider.h" />
<ClInclude Include="ISOProperties\FilesystemPanel.h" />
<ClInclude Include="ISOProperties\InfoPanel.h" />
<ClInclude Include="ISOProperties\ISOProperties.h" />
<ClInclude Include="NetPlay\ChangeGameDialog.h" />

View file

@ -224,6 +224,9 @@
<ClCompile Include="Config\AdvancedConfigPane.cpp">
<Filter>GUI\Config</Filter>
</ClCompile>
<ClCompile Include="ISOProperties\FilesystemPanel.cpp">
<Filter>GUI\ISOProperties</Filter>
</ClCompile>
<ClCompile Include="ISOProperties\InfoPanel.cpp">
<Filter>GUI\ISOProperties</Filter>
</ClCompile>
@ -437,6 +440,9 @@
<ClInclude Include="Config\AdvancedConfigPane.h">
<Filter>GUI\Config</Filter>
</ClInclude>
<ClInclude Include="ISOProperties\FilesystemPanel.h">
<Filter>GUI\ISOProperties</Filter>
</ClInclude>
<ClInclude Include="ISOProperties\InfoPanel.h">
<Filter>GUI\ISOProperties</Filter>
</ClInclude>

View file

@ -0,0 +1,641 @@
// Copyright 2016 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include "DolphinWX/ISOProperties/FilesystemPanel.h"
#include <array>
#include <vector>
#include <wx/bitmap.h>
#include <wx/button.h>
#include <wx/filepicker.h>
#include <wx/imaglist.h>
#include <wx/menu.h>
#include <wx/msgdlg.h>
#include <wx/progdlg.h>
#include <wx/sizer.h>
#include <wx/textctrl.h>
#include <wx/treectrl.h>
#include "Common/CommonPaths.h"
#include "Common/FileUtil.h"
#include "Common/Logging/Log.h"
#include "DiscIO/Enums.h"
#include "DiscIO/Filesystem.h"
#include "DiscIO/Volume.h"
#include "DiscIO/VolumeCreator.h"
#include "DolphinWX/ISOFile.h"
#include "DolphinWX/WxUtils.h"
namespace
{
class WiiPartition final : public wxTreeItemData
{
public:
WiiPartition(std::unique_ptr<DiscIO::IVolume> volume_,
std::unique_ptr<DiscIO::IFileSystem> filesystem_)
: volume{std::move(volume_)}, filesystem{std::move(filesystem_)}
{
}
std::unique_ptr<DiscIO::IVolume> volume;
std::unique_ptr<DiscIO::IFileSystem> filesystem;
};
class IntegrityCheckThread final : public wxThread
{
public:
explicit IntegrityCheckThread(const WiiPartition& partition)
: wxThread{wxTHREAD_JOINABLE}, m_partition{partition}
{
Create();
}
ExitCode Entry() override
{
return reinterpret_cast<ExitCode>(m_partition.volume->CheckIntegrity());
}
private:
const WiiPartition& m_partition;
};
enum : int
{
ICON_DISC,
ICON_FOLDER,
ICON_FILE
};
wxImageList* LoadIconBitmaps(const wxWindow* context)
{
static constexpr std::array<const char*, 3> icon_names{
{"isoproperties_disc", "isoproperties_folder", "isoproperties_file"}};
const wxSize icon_size = context->FromDIP(wxSize(16, 16));
auto* const icon_list = new wxImageList(icon_size.GetWidth(), icon_size.GetHeight());
for (const auto& name : icon_names)
{
icon_list->Add(
WxUtils::LoadScaledResourceBitmap(name, context, icon_size, wxDefaultSize,
WxUtils::LSI_SCALE_DOWN | WxUtils::LSI_ALIGN_CENTER));
}
return icon_list;
}
size_t CreateDirectoryTree(wxTreeCtrl* tree_ctrl, wxTreeItemId parent,
const std::vector<DiscIO::SFileInfo>& file_infos,
const size_t first_index, const size_t last_index)
{
size_t current_index = first_index;
while (current_index < last_index)
{
const DiscIO::SFileInfo& file_info = file_infos[current_index];
std::string file_path = file_info.m_FullPath;
// Trim the trailing '/' if it exists.
if (file_path.back() == DIR_SEP_CHR)
{
file_path.pop_back();
}
// Cut off the path up to the actual filename or folder.
// Say we have "/music/stream/stream1.strm", the result will be "stream1.strm".
const size_t dir_sep_index = file_path.rfind(DIR_SEP_CHR);
if (dir_sep_index != std::string::npos)
{
file_path = file_path.substr(dir_sep_index + 1);
}
// check next index
if (file_info.IsDirectory())
{
const wxTreeItemId item = tree_ctrl->AppendItem(parent, StrToWxStr(file_path), ICON_FOLDER);
current_index = CreateDirectoryTree(tree_ctrl, item, file_infos, current_index + 1,
static_cast<size_t>(file_info.m_FileSize));
}
else
{
tree_ctrl->AppendItem(parent, StrToWxStr(file_path), ICON_FILE);
current_index++;
}
}
return current_index;
}
size_t CreateDirectoryTree(wxTreeCtrl* tree_ctrl, wxTreeItemId parent,
const std::vector<DiscIO::SFileInfo>& file_infos)
{
if (file_infos.empty())
return 0;
return CreateDirectoryTree(tree_ctrl, parent, file_infos, 1, file_infos.at(0).m_FileSize);
}
WiiPartition* FindWiiPartition(wxTreeCtrl* tree_ctrl, const wxString& label)
{
wxTreeItemIdValue cookie;
auto partition = tree_ctrl->GetFirstChild(tree_ctrl->GetRootItem(), cookie);
while (partition.IsOk())
{
const wxString partition_label = tree_ctrl->GetItemText(partition);
if (partition_label == label)
return static_cast<WiiPartition*>(tree_ctrl->GetItemData(partition));
partition = tree_ctrl->GetNextSibling(partition);
}
return nullptr;
}
} // Anonymous namespace
FilesystemPanel::FilesystemPanel(wxWindow* parent, wxWindowID id, const GameListItem& item,
const std::unique_ptr<DiscIO::IVolume>& opened_iso)
: wxPanel{parent, id}, m_game_list_item{item}, m_opened_iso{opened_iso}
{
CreateGUI();
BindEvents();
PopulateFileSystemTree();
m_tree_ctrl->Expand(m_tree_ctrl->GetRootItem());
}
FilesystemPanel::~FilesystemPanel() = default;
void FilesystemPanel::BindEvents()
{
m_tree_ctrl->Bind(wxEVT_TREE_ITEM_RIGHT_CLICK, &FilesystemPanel::OnRightClickTree, this);
Bind(wxEVT_MENU, &FilesystemPanel::OnExtractFile, this, ID_EXTRACT_FILE);
Bind(wxEVT_MENU, &FilesystemPanel::OnExtractDirectories, this, ID_EXTRACT_ALL);
Bind(wxEVT_MENU, &FilesystemPanel::OnExtractDirectories, this, ID_EXTRACT_DIR);
Bind(wxEVT_MENU, &FilesystemPanel::OnExtractHeaderData, this, ID_EXTRACT_APPLOADER);
Bind(wxEVT_MENU, &FilesystemPanel::OnExtractHeaderData, this, ID_EXTRACT_DOL);
Bind(wxEVT_MENU, &FilesystemPanel::OnCheckPartitionIntegrity, this, ID_CHECK_INTEGRITY);
}
void FilesystemPanel::CreateGUI()
{
m_tree_ctrl = new wxTreeCtrl(this);
m_tree_ctrl->AssignImageList(LoadIconBitmaps(this));
m_tree_ctrl->AddRoot(_("Disc"), ICON_DISC);
const auto space_5 = FromDIP(5);
auto* const main_sizer = new wxBoxSizer(wxVERTICAL);
main_sizer->AddSpacer(space_5);
main_sizer->Add(m_tree_ctrl, 1, wxEXPAND | wxLEFT | wxRIGHT, space_5);
main_sizer->AddSpacer(space_5);
SetSizer(main_sizer);
}
void FilesystemPanel::PopulateFileSystemTree()
{
switch (m_opened_iso->GetVolumeType())
{
case DiscIO::Platform::GAMECUBE_DISC:
PopulateFileSystemTreeGC();
break;
case DiscIO::Platform::WII_DISC:
PopulateFileSystemTreeWii();
break;
case DiscIO::Platform::ELF_DOL:
case DiscIO::Platform::NUMBER_OF_PLATFORMS:
case DiscIO::Platform::WII_WAD:
break;
}
}
void FilesystemPanel::PopulateFileSystemTreeGC()
{
m_filesystem = DiscIO::CreateFileSystem(m_opened_iso.get());
if (!m_filesystem)
return;
CreateDirectoryTree(m_tree_ctrl, m_tree_ctrl->GetRootItem(), m_filesystem->GetFileList());
}
void FilesystemPanel::PopulateFileSystemTreeWii() const
{
u32 partition_count = 0;
for (u32 group = 0; group < 4; group++)
{
// yes, technically there can be OVER NINE THOUSAND partitions...
for (u32 i = 0; i < 0xFFFFFFFF; i++)
{
auto volume = DiscIO::CreateVolumeFromFilename(m_game_list_item.GetFileName(), group, i);
if (volume == nullptr)
break;
auto file_system = DiscIO::CreateFileSystem(volume.get());
if (file_system != nullptr)
{
auto* const partition = new WiiPartition(std::move(volume), std::move(file_system));
const wxTreeItemId partition_root = m_tree_ctrl->AppendItem(
m_tree_ctrl->GetRootItem(), wxString::Format(_("Partition %u"), partition_count),
ICON_DISC);
m_tree_ctrl->SetItemData(partition_root, partition);
CreateDirectoryTree(m_tree_ctrl, partition_root, partition->filesystem->GetFileList());
if (partition_count == 1)
m_tree_ctrl->Expand(partition_root);
partition_count++;
}
}
}
}
void FilesystemPanel::OnRightClickTree(wxTreeEvent& event)
{
m_tree_ctrl->SelectItem(event.GetItem());
wxMenu menu;
const auto selection = m_tree_ctrl->GetSelection();
const auto first_visible_item = m_tree_ctrl->GetFirstVisibleItem();
const int image_type = m_tree_ctrl->GetItemImage(selection);
if (image_type == ICON_DISC && first_visible_item != selection)
{
menu.Append(ID_EXTRACT_DIR, _("Extract Partition..."));
}
else if (image_type == ICON_FOLDER)
{
menu.Append(ID_EXTRACT_DIR, _("Extract Directory..."));
}
else if (image_type == ICON_FILE)
{
menu.Append(ID_EXTRACT_FILE, _("Extract File..."));
}
menu.Append(ID_EXTRACT_ALL, _("Extract All Files..."));
if (m_opened_iso->GetVolumeType() != DiscIO::Platform::WII_DISC ||
(image_type == ICON_DISC && first_visible_item != selection))
{
menu.AppendSeparator();
menu.Append(ID_EXTRACT_APPLOADER, _("Extract Apploader..."));
menu.Append(ID_EXTRACT_DOL, _("Extract DOL..."));
}
if (image_type == ICON_DISC && first_visible_item != selection)
{
menu.AppendSeparator();
menu.Append(ID_CHECK_INTEGRITY, _("Check Partition Integrity"));
}
PopupMenu(&menu);
event.Skip();
}
void FilesystemPanel::OnExtractFile(wxCommandEvent& WXUNUSED(event))
{
const wxString selection_label = m_tree_ctrl->GetItemText(m_tree_ctrl->GetSelection());
const wxString output_file_path =
wxFileSelector(_("Extract File"), wxEmptyString, selection_label, wxEmptyString,
wxGetTranslation(wxALL_FILES), wxFD_SAVE, this);
if (output_file_path.empty() || selection_label.empty())
return;
ExtractSingleFile(output_file_path);
}
void FilesystemPanel::OnExtractDirectories(wxCommandEvent& event)
{
const wxString selected_directory_label = m_tree_ctrl->GetItemText(m_tree_ctrl->GetSelection());
const wxString extract_path = wxDirSelector(_("Choose the folder to extract to"));
if (extract_path.empty() || selected_directory_label.empty())
return;
switch (event.GetId())
{
case ID_EXTRACT_ALL:
ExtractAllFiles(extract_path);
break;
case ID_EXTRACT_DIR:
ExtractSingleDirectory(extract_path);
break;
}
}
void FilesystemPanel::OnExtractHeaderData(wxCommandEvent& event)
{
DiscIO::IFileSystem* file_system = nullptr;
const wxString path = wxDirSelector(_("Choose the folder to extract to"));
if (path.empty())
return;
if (m_opened_iso->GetVolumeType() == DiscIO::Platform::WII_DISC)
{
const auto* const selection_data = m_tree_ctrl->GetItemData(m_tree_ctrl->GetSelection());
const auto* const partition = static_cast<const WiiPartition*>(selection_data);
file_system = partition->filesystem.get();
}
else
{
file_system = m_filesystem.get();
}
bool ret = false;
if (event.GetId() == ID_EXTRACT_APPLOADER)
{
ret = file_system->ExportApploader(WxStrToStr(path));
}
else if (event.GetId() == ID_EXTRACT_DOL)
{
ret = file_system->ExportDOL(WxStrToStr(path));
}
if (!ret)
{
WxUtils::ShowErrorDialog(
wxString::Format(_("Failed to extract to %s!"), WxStrToStr(path).c_str()));
}
}
void FilesystemPanel::OnCheckPartitionIntegrity(wxCommandEvent& WXUNUSED(event))
{
// Normally we can't enter this function if we aren't analyzing a Wii disc
// anyway, but let's still check to be sure.
if (m_opened_iso->GetVolumeType() != DiscIO::Platform::WII_DISC)
return;
wxProgressDialog dialog(_("Checking integrity..."), _("Working..."), 1000, this,
wxPD_APP_MODAL | wxPD_ELAPSED_TIME | wxPD_SMOOTH);
const auto selection = m_tree_ctrl->GetSelection();
IntegrityCheckThread thread(*static_cast<WiiPartition*>(m_tree_ctrl->GetItemData(selection)));
thread.Run();
while (thread.IsAlive())
{
dialog.Pulse();
wxThread::Sleep(50);
}
dialog.Destroy();
if (thread.Wait())
{
wxMessageBox(_("Integrity check completed. No errors have been found."),
_("Integrity check completed"), wxOK | wxICON_INFORMATION, this);
}
else
{
wxMessageBox(wxString::Format(_("Integrity check for %s failed. The disc image is most "
"likely corrupted or has been patched incorrectly."),
m_tree_ctrl->GetItemText(selection)),
_("Integrity Check Error"), wxOK | wxICON_ERROR, this);
}
}
void FilesystemPanel::ExtractAllFiles(const wxString& output_folder)
{
switch (m_opened_iso->GetVolumeType())
{
case DiscIO::Platform::GAMECUBE_DISC:
ExtractAllFilesGC(output_folder);
break;
case DiscIO::Platform::WII_DISC:
ExtractAllFilesWii(output_folder);
break;
case DiscIO::Platform::ELF_DOL:
case DiscIO::Platform::NUMBER_OF_PLATFORMS:
case DiscIO::Platform::WII_WAD:
break;
}
}
void FilesystemPanel::ExtractAllFilesGC(const wxString& output_folder)
{
ExtractDirectories("", WxStrToStr(output_folder), m_filesystem.get());
}
void FilesystemPanel::ExtractAllFilesWii(const wxString& output_folder)
{
const wxTreeItemId root = m_tree_ctrl->GetRootItem();
wxTreeItemIdValue cookie;
wxTreeItemId item = m_tree_ctrl->GetFirstChild(root, cookie);
while (item.IsOk())
{
const auto* const partition = static_cast<WiiPartition*>(m_tree_ctrl->GetItemData(item));
ExtractDirectories("", WxStrToStr(output_folder), partition->filesystem.get());
item = m_tree_ctrl->GetNextChild(root, cookie);
}
}
void FilesystemPanel::ExtractSingleFile(const wxString& output_file_path) const
{
const auto selection_file_path = BuildFilePathFromSelection();
switch (m_opened_iso->GetVolumeType())
{
case DiscIO::Platform::GAMECUBE_DISC:
ExtractSingleFileGC(selection_file_path, output_file_path);
break;
case DiscIO::Platform::WII_DISC:
ExtractSingleFileWii(selection_file_path, output_file_path);
break;
case DiscIO::Platform::ELF_DOL:
case DiscIO::Platform::NUMBER_OF_PLATFORMS:
case DiscIO::Platform::WII_WAD:
break;
}
}
void FilesystemPanel::ExtractSingleFileGC(const wxString& file_path,
const wxString& output_file_path) const
{
m_filesystem->ExportFile(WxStrToStr(file_path), WxStrToStr(output_file_path));
}
void FilesystemPanel::ExtractSingleFileWii(wxString file_path,
const wxString& output_file_path) const
{
const size_t slash_index = file_path.find('/');
const wxString partition_label = file_path.substr(0, slash_index);
const auto* const partition = FindWiiPartition(m_tree_ctrl, partition_label);
// Remove "Partition x/"
file_path.erase(0, slash_index + 1);
partition->filesystem->ExportFile(WxStrToStr(file_path), WxStrToStr(output_file_path));
}
void FilesystemPanel::ExtractSingleDirectory(const wxString& output_folder)
{
const wxString directory_path = BuildDirectoryPathFromSelection();
switch (m_opened_iso->GetVolumeType())
{
case DiscIO::Platform::GAMECUBE_DISC:
ExtractSingleDirectoryGC(directory_path, output_folder);
break;
case DiscIO::Platform::WII_DISC:
ExtractSingleDirectoryWii(directory_path, output_folder);
break;
case DiscIO::Platform::ELF_DOL:
case DiscIO::Platform::NUMBER_OF_PLATFORMS:
case DiscIO::Platform::WII_WAD:
break;
}
}
void FilesystemPanel::ExtractSingleDirectoryGC(const wxString& directory_path,
const wxString& output_folder)
{
ExtractDirectories(WxStrToStr(directory_path), WxStrToStr(output_folder), m_filesystem.get());
}
void FilesystemPanel::ExtractSingleDirectoryWii(wxString directory_path,
const wxString& output_folder)
{
const size_t slash_index = directory_path.find('/');
const wxString partition_label = directory_path.substr(0, slash_index);
const auto* const partition = FindWiiPartition(m_tree_ctrl, partition_label);
// Remove "Partition x/"
directory_path.erase(0, slash_index + 1);
ExtractDirectories(WxStrToStr(directory_path), WxStrToStr(output_folder),
partition->filesystem.get());
}
void FilesystemPanel::ExtractDirectories(const std::string& full_path,
const std::string& output_folder,
DiscIO::IFileSystem* filesystem)
{
const std::vector<DiscIO::SFileInfo>& fst = filesystem->GetFileList();
u32 index = 0;
u32 size = 0;
// Extract all
if (full_path.empty())
{
size = static_cast<u32>(fst.size());
filesystem->ExportApploader(output_folder);
if (m_opened_iso->GetVolumeType() == DiscIO::Platform::GAMECUBE_DISC)
filesystem->ExportDOL(output_folder);
}
else
{
// Look for the dir we are going to extract
for (index = 0; index < fst.size(); ++index)
{
if (fst[index].m_FullPath == full_path)
{
INFO_LOG(DISCIO, "Found the directory at %u", index);
size = static_cast<u32>(fst[index].m_FileSize);
break;
}
}
INFO_LOG(DISCIO, "Directory found from %u to %u\nextracting to: %s", index, size,
output_folder.c_str());
}
const auto dialog_title = (index != 0) ? _("Extracting Directory") : _("Extracting All Files");
wxProgressDialog dialog(dialog_title, _("Extracting..."), static_cast<int>(size - 1), this,
wxPD_APP_MODAL | wxPD_AUTO_HIDE | wxPD_CAN_ABORT | wxPD_ELAPSED_TIME |
wxPD_ESTIMATED_TIME | wxPD_REMAINING_TIME | wxPD_SMOOTH);
// Extraction
for (u32 i = index; i < size; i++)
{
dialog.SetTitle(wxString::Format(
"%s : %u%%", dialog_title.c_str(),
static_cast<u32>((static_cast<float>(i - index) / static_cast<float>(size - index)) *
100)));
dialog.Update(i, wxString::Format(_("Extracting %s"), StrToWxStr(fst[i].m_FullPath)));
if (dialog.WasCancelled())
break;
if (fst[i].IsDirectory())
{
const std::string export_name =
StringFromFormat("%s/%s/", output_folder.c_str(), fst[i].m_FullPath.c_str());
INFO_LOG(DISCIO, "%s", export_name.c_str());
if (!File::Exists(export_name) && !File::CreateFullPath(export_name))
{
ERROR_LOG(DISCIO, "Could not create the path %s", export_name.c_str());
}
else
{
if (!File::IsDirectory(export_name))
ERROR_LOG(DISCIO, "%s already exists and is not a directory", export_name.c_str());
ERROR_LOG(DISCIO, "Folder %s already exists", export_name.c_str());
}
}
else
{
const std::string export_name =
StringFromFormat("%s/%s", output_folder.c_str(), fst[i].m_FullPath.c_str());
INFO_LOG(DISCIO, "%s", export_name.c_str());
if (!File::Exists(export_name) && !filesystem->ExportFile(fst[i].m_FullPath, export_name))
{
ERROR_LOG(DISCIO, "Could not export %s", export_name.c_str());
}
else
{
ERROR_LOG(DISCIO, "%s already exists", export_name.c_str());
}
}
}
}
wxString FilesystemPanel::BuildFilePathFromSelection() const
{
wxString file_path = m_tree_ctrl->GetItemText(m_tree_ctrl->GetSelection());
const auto root_node = m_tree_ctrl->GetRootItem();
auto node = m_tree_ctrl->GetItemParent(m_tree_ctrl->GetSelection());
while (node != root_node)
{
file_path = m_tree_ctrl->GetItemText(node) + DIR_SEP_CHR + file_path;
node = m_tree_ctrl->GetItemParent(node);
}
return file_path;
}
wxString FilesystemPanel::BuildDirectoryPathFromSelection() const
{
wxString directory_path = BuildFilePathFromSelection();
directory_path += DIR_SEP_CHR;
return directory_path;
}

View file

@ -0,0 +1,76 @@
// Copyright 2016 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <string>
#include <wx/panel.h>
class GameListItem;
class wxTreeCtrl;
class wxTreeEvent;
namespace DiscIO
{
class IFileSystem;
class IVolume;
}
class FilesystemPanel final : public wxPanel
{
public:
explicit FilesystemPanel(wxWindow* parent, wxWindowID id, const GameListItem& item,
const std::unique_ptr<DiscIO::IVolume>& opened_iso);
~FilesystemPanel();
private:
enum
{
ID_EXTRACT_DIR = 20000,
ID_EXTRACT_ALL,
ID_EXTRACT_FILE,
ID_EXTRACT_APPLOADER,
ID_EXTRACT_DOL,
ID_CHECK_INTEGRITY,
};
void CreateGUI();
void BindEvents();
void PopulateFileSystemTree();
void PopulateFileSystemTreeGC();
void PopulateFileSystemTreeWii() const;
void OnRightClickTree(wxTreeEvent&);
void OnExtractFile(wxCommandEvent&);
void OnExtractDirectories(wxCommandEvent&);
void OnExtractHeaderData(wxCommandEvent&);
void OnCheckPartitionIntegrity(wxCommandEvent&);
void ExtractAllFiles(const wxString& output_folder);
void ExtractAllFilesGC(const wxString& output_folder);
void ExtractAllFilesWii(const wxString& output_folder);
void ExtractSingleFile(const wxString& output_file_path) const;
void ExtractSingleFileGC(const wxString& file_path, const wxString& output_file_path) const;
void ExtractSingleFileWii(wxString file_path, const wxString& output_file_path) const;
void ExtractSingleDirectory(const wxString& output_folder);
void ExtractSingleDirectoryGC(const wxString& directory_path, const wxString& output_folder);
void ExtractSingleDirectoryWii(wxString directory_path, const wxString& output_folder);
void ExtractDirectories(const std::string& full_path, const std::string& output_folder,
DiscIO::IFileSystem* filesystem);
wxString BuildFilePathFromSelection() const;
wxString BuildDirectoryPathFromSelection() const;
wxTreeCtrl* m_tree_ctrl;
const GameListItem& m_game_list_item;
const std::unique_ptr<DiscIO::IVolume>& m_opened_iso;
std::unique_ptr<DiscIO::IFileSystem> m_filesystem;
};

View file

@ -4,13 +4,11 @@
#include "DolphinWX/ISOProperties/ISOProperties.h"
#include <array>
#include <cinttypes>
#include <cstddef>
#include <cstdio>
#include <cstring>
#include <fstream>
#include <set>
#include <string>
#include <type_traits>
#include <vector>
@ -22,25 +20,18 @@
#include <wx/checklst.h>
#include <wx/choice.h>
#include <wx/dialog.h>
#include <wx/dirdlg.h>
#include <wx/filedlg.h>
#include <wx/image.h>
#include <wx/imaglist.h>
#include <wx/itemid.h>
#include <wx/menu.h>
#include <wx/mimetype.h>
#include <wx/msgdlg.h>
#include <wx/notebook.h>
#include <wx/panel.h>
#include <wx/progdlg.h>
#include <wx/sizer.h>
#include <wx/spinctrl.h>
#include <wx/statbmp.h>
#include <wx/stattext.h>
#include <wx/textctrl.h>
#include <wx/thread.h>
#include <wx/treebase.h>
#include <wx/treectrl.h>
#include <wx/utils.h>
#include "Common/CommonPaths.h"
@ -48,14 +39,12 @@
#include "Common/FileUtil.h"
#include "Common/IniFile.h"
#include "Common/StringUtil.h"
#include "Common/SysConf.h"
#include "Core/ConfigManager.h"
#include "Core/Core.h"
#include "Core/GeckoCodeConfig.h"
#include "Core/PatchEngine.h"
#include "DiscIO/Blob.h"
#include "DiscIO/Enums.h"
#include "DiscIO/Filesystem.h"
#include "DiscIO/Volume.h"
#include "DiscIO/VolumeCreator.h"
#include "DolphinWX/Cheats/ActionReplayCodesPanel.h"
@ -65,6 +54,7 @@
#include "DolphinWX/Frame.h"
#include "DolphinWX/Globals.h"
#include "DolphinWX/ISOFile.h"
#include "DolphinWX/ISOProperties/FilesystemPanel.h"
#include "DolphinWX/ISOProperties/InfoPanel.h"
#include "DolphinWX/Main.h"
#include "DolphinWX/PatchAddEdit.h"
@ -197,13 +187,6 @@ EVT_LISTBOX(ID_PATCHES_LIST, CISOProperties::PatchListSelectionChanged)
EVT_BUTTON(ID_EDITPATCH, CISOProperties::PatchButtonClicked)
EVT_BUTTON(ID_ADDPATCH, CISOProperties::PatchButtonClicked)
EVT_BUTTON(ID_REMOVEPATCH, CISOProperties::PatchButtonClicked)
EVT_TREE_ITEM_RIGHT_CLICK(ID_TREECTRL, CISOProperties::OnRightClickOnTree)
EVT_MENU(IDM_EXTRACTFILE, CISOProperties::OnExtractFile)
EVT_MENU(IDM_EXTRACTDIR, CISOProperties::OnExtractDir)
EVT_MENU(IDM_EXTRACTALL, CISOProperties::OnExtractDir)
EVT_MENU(IDM_EXTRACTAPPLOADER, CISOProperties::OnExtractDataFromHeader)
EVT_MENU(IDM_EXTRACTDOL, CISOProperties::OnExtractDataFromHeader)
EVT_MENU(IDM_CHECKINTEGRITY, CISOProperties::CheckPartitionIntegrity)
END_EVENT_TABLE()
CISOProperties::CISOProperties(const GameListItem& game_list_item, wxWindow* parent, wxWindowID id,
@ -227,58 +210,6 @@ CISOProperties::CISOProperties(const GameListItem& game_list_item, wxWindow* par
CreateGUIControls();
LoadGameConfig();
// Filesystem browser/dumper
// TODO : Should we add a way to browse the wad file ?
if (m_open_iso->GetVolumeType() != DiscIO::Platform::WII_WAD)
{
if (m_open_iso->GetVolumeType() == DiscIO::Platform::WII_DISC)
{
int partition_count = 0;
for (int group = 0; group < 4; group++)
{
for (u32 i = 0; i < 0xFFFFFFFF;
i++) // yes, technically there can be OVER NINE THOUSAND partitions...
{
std::unique_ptr<DiscIO::IVolume> volume(
DiscIO::CreateVolumeFromFilename(OpenGameListItem.GetFileName(), group, i));
if (volume != nullptr)
{
std::unique_ptr<DiscIO::IFileSystem> file_system(
DiscIO::CreateFileSystem(volume.get()));
if (file_system != nullptr)
{
WiiPartition* const partition =
new WiiPartition(std::move(volume), std::move(file_system));
wxTreeItemId PartitionRoot = m_Treectrl->AppendItem(
RootId, wxString::Format(_("Partition %i"), partition_count), 0, 0);
m_Treectrl->SetItemData(PartitionRoot, partition);
CreateDirectoryTree(PartitionRoot, partition->FileSystem->GetFileList());
if (partition_count == 1)
m_Treectrl->Expand(PartitionRoot);
partition_count++;
}
}
else
{
break;
}
}
}
}
else
{
m_filesystem = DiscIO::CreateFileSystem(m_open_iso.get());
if (m_filesystem)
CreateDirectoryTree(RootId, m_filesystem->GetFileList());
}
m_Treectrl->Expand(RootId);
}
wxTheApp->Bind(DOLPHIN_EVT_LOCAL_INI_CHANGED, &CISOProperties::OnLocalIniModified, this);
}
@ -286,57 +217,6 @@ CISOProperties::~CISOProperties()
{
}
size_t CISOProperties::CreateDirectoryTree(wxTreeItemId& parent,
const std::vector<DiscIO::SFileInfo>& fileInfos)
{
if (fileInfos.empty())
return 0;
else
return CreateDirectoryTree(parent, fileInfos, 1, fileInfos.at(0).m_FileSize);
}
size_t CISOProperties::CreateDirectoryTree(wxTreeItemId& parent,
const std::vector<DiscIO::SFileInfo>& fileInfos,
const size_t _FirstIndex, const size_t _LastIndex)
{
size_t CurrentIndex = _FirstIndex;
while (CurrentIndex < _LastIndex)
{
const DiscIO::SFileInfo rFileInfo = fileInfos[CurrentIndex];
std::string filePath = rFileInfo.m_FullPath;
// Trim the trailing '/' if it exists.
if (filePath[filePath.length() - 1] == DIR_SEP_CHR)
{
filePath.pop_back();
}
// Cut off the path up to the actual filename or folder.
// Say we have "/music/stream/stream1.strm", the result will be "stream1.strm".
size_t dirSepIndex = filePath.find_last_of(DIR_SEP_CHR);
if (dirSepIndex != std::string::npos)
{
filePath = filePath.substr(dirSepIndex + 1);
}
// check next index
if (rFileInfo.IsDirectory())
{
wxTreeItemId item = m_Treectrl->AppendItem(parent, StrToWxStr(filePath), 1, 1);
CurrentIndex =
CreateDirectoryTree(item, fileInfos, CurrentIndex + 1, (size_t)rFileInfo.m_FileSize);
}
else
{
m_Treectrl->AppendItem(parent, StrToWxStr(filePath), 2, 2);
CurrentIndex++;
}
}
return CurrentIndex;
}
long CISOProperties::GetElementStyle(const char* section, const char* key)
{
// Disable 3rd state if default gameini overrides the setting
@ -554,29 +434,9 @@ void CISOProperties::CreateGUIControls()
if (m_open_iso->GetVolumeType() != DiscIO::Platform::WII_WAD)
{
wxPanel* const filesystem_panel = new wxPanel(m_Notebook, ID_FILESYSTEM);
m_Notebook->AddPage(filesystem_panel, _("Filesystem"));
// Filesystem icons
wxSize icon_size = FromDIP(wxSize(16, 16));
wxImageList* const m_iconList = new wxImageList(icon_size.GetWidth(), icon_size.GetHeight());
static const std::array<const char* const, 3> s_icon_names{
{"isoproperties_disc", "isoproperties_folder", "isoproperties_file"}};
for (const auto& name : s_icon_names)
m_iconList->Add(
WxUtils::LoadScaledResourceBitmap(name, this, icon_size, wxDefaultSize,
WxUtils::LSI_SCALE_DOWN | WxUtils::LSI_ALIGN_CENTER));
// Filesystem tree
m_Treectrl = new wxTreeCtrl(filesystem_panel, ID_TREECTRL);
m_Treectrl->AssignImageList(m_iconList);
RootId = m_Treectrl->AddRoot(_("Disc"), 0, 0, nullptr);
wxBoxSizer* sTreePage = new wxBoxSizer(wxVERTICAL);
sTreePage->AddSpacer(space5);
sTreePage->Add(m_Treectrl, 1, wxEXPAND | wxLEFT | wxRIGHT, space5);
sTreePage->AddSpacer(space5);
filesystem_panel->SetSizer(sTreePage);
m_Notebook->AddPage(
new FilesystemPanel(m_Notebook, ID_FILESYSTEM, OpenGameListItem, m_open_iso),
_("Filesystem"));
}
wxStdDialogButtonSizer* sButtons = CreateStdDialogButtonSizer(wxOK | wxNO_DEFAULT);
@ -626,313 +486,6 @@ void CISOProperties::OnCloseClick(wxCommandEvent& WXUNUSED(event))
Close();
}
void CISOProperties::OnRightClickOnTree(wxTreeEvent& event)
{
m_Treectrl->SelectItem(event.GetItem());
wxMenu popupMenu;
if (m_Treectrl->GetItemImage(m_Treectrl->GetSelection()) == 0 &&
m_Treectrl->GetFirstVisibleItem() != m_Treectrl->GetSelection())
{
popupMenu.Append(IDM_EXTRACTDIR, _("Extract Partition..."));
}
else if (m_Treectrl->GetItemImage(m_Treectrl->GetSelection()) == 1)
{
popupMenu.Append(IDM_EXTRACTDIR, _("Extract Directory..."));
}
else if (m_Treectrl->GetItemImage(m_Treectrl->GetSelection()) == 2)
{
popupMenu.Append(IDM_EXTRACTFILE, _("Extract File..."));
}
popupMenu.Append(IDM_EXTRACTALL, _("Extract All Files..."));
if (m_open_iso->GetVolumeType() != DiscIO::Platform::WII_DISC ||
(m_Treectrl->GetItemImage(m_Treectrl->GetSelection()) == 0 &&
m_Treectrl->GetFirstVisibleItem() != m_Treectrl->GetSelection()))
{
popupMenu.AppendSeparator();
popupMenu.Append(IDM_EXTRACTAPPLOADER, _("Extract Apploader..."));
popupMenu.Append(IDM_EXTRACTDOL, _("Extract DOL..."));
}
if (m_Treectrl->GetItemImage(m_Treectrl->GetSelection()) == 0 &&
m_Treectrl->GetFirstVisibleItem() != m_Treectrl->GetSelection())
{
popupMenu.AppendSeparator();
popupMenu.Append(IDM_CHECKINTEGRITY, _("Check Partition Integrity"));
}
PopupMenu(&popupMenu);
event.Skip();
}
void CISOProperties::OnExtractFile(wxCommandEvent& WXUNUSED(event))
{
wxString File = m_Treectrl->GetItemText(m_Treectrl->GetSelection());
wxString Path = wxFileSelector(_("Export File"), wxEmptyString, File, wxEmptyString,
wxGetTranslation(wxALL_FILES), wxFD_SAVE, this);
if (!Path || !File)
return;
while (m_Treectrl->GetItemParent(m_Treectrl->GetSelection()) != m_Treectrl->GetRootItem())
{
wxString temp = m_Treectrl->GetItemText(m_Treectrl->GetItemParent(m_Treectrl->GetSelection()));
File = temp + DIR_SEP_CHR + File;
m_Treectrl->SelectItem(m_Treectrl->GetItemParent(m_Treectrl->GetSelection()));
}
if (m_open_iso->GetVolumeType() == DiscIO::Platform::WII_DISC)
{
const wxTreeItemId tree_selection = m_Treectrl->GetSelection();
WiiPartition* partition =
reinterpret_cast<WiiPartition*>(m_Treectrl->GetItemData(tree_selection));
File.erase(0, m_Treectrl->GetItemText(tree_selection).length() + 1); // Remove "Partition x/"
partition->FileSystem->ExportFile(WxStrToStr(File), WxStrToStr(Path));
}
else
{
m_filesystem->ExportFile(WxStrToStr(File), WxStrToStr(Path));
}
}
void CISOProperties::ExportDir(const std::string& _rFullPath, const std::string& _rExportFolder,
const WiiPartition* partition)
{
bool is_wii = m_open_iso->GetVolumeType() == DiscIO::Platform::WII_DISC;
DiscIO::IFileSystem* const fs = is_wii ? partition->FileSystem.get() : m_filesystem.get();
const std::vector<DiscIO::SFileInfo>& fst = fs->GetFileList();
u32 index = 0;
u32 size = 0;
// Extract all
if (_rFullPath.empty())
{
index = 0;
size = (u32)fst.size();
fs->ExportApploader(_rExportFolder);
if (m_open_iso->GetVolumeType() != DiscIO::Platform::WII_DISC)
fs->ExportDOL(_rExportFolder);
}
else
{
// Look for the dir we are going to extract
for (index = 0; index != fst.size(); ++index)
{
if (fst[index].m_FullPath == _rFullPath)
{
INFO_LOG(DISCIO, "Found the directory at %u", index);
size = (u32)fst[index].m_FileSize;
break;
}
}
INFO_LOG(DISCIO, "Directory found from %u to %u\nextracting to: %s", index, size,
_rExportFolder.c_str());
}
wxString dialogTitle = (index != 0) ? _("Extracting Directory") : _("Extracting All Files");
wxProgressDialog dialog(dialogTitle, _("Extracting..."), size - 1, this,
wxPD_APP_MODAL | wxPD_AUTO_HIDE | wxPD_CAN_ABORT | wxPD_ELAPSED_TIME |
wxPD_ESTIMATED_TIME | wxPD_REMAINING_TIME | wxPD_SMOOTH);
// Extraction
for (u32 i = index; i < size; i++)
{
dialog.SetTitle(wxString::Format("%s : %d%%", dialogTitle.c_str(),
(u32)(((float)(i - index) / (float)(size - index)) * 100)));
dialog.Update(i, wxString::Format(_("Extracting %s"), StrToWxStr(fst[i].m_FullPath)));
if (dialog.WasCancelled())
break;
if (fst[i].IsDirectory())
{
const std::string exportName =
StringFromFormat("%s/%s/", _rExportFolder.c_str(), fst[i].m_FullPath.c_str());
INFO_LOG(DISCIO, "%s", exportName.c_str());
if (!File::Exists(exportName) && !File::CreateFullPath(exportName))
{
ERROR_LOG(DISCIO, "Could not create the path %s", exportName.c_str());
}
else
{
if (!File::IsDirectory(exportName))
ERROR_LOG(DISCIO, "%s already exists and is not a directory", exportName.c_str());
ERROR_LOG(DISCIO, "Folder %s already exists", exportName.c_str());
}
}
else
{
const std::string exportName =
StringFromFormat("%s/%s", _rExportFolder.c_str(), fst[i].m_FullPath.c_str());
INFO_LOG(DISCIO, "%s", exportName.c_str());
if (!File::Exists(exportName) && !fs->ExportFile(fst[i].m_FullPath, exportName))
{
ERROR_LOG(DISCIO, "Could not export %s", exportName.c_str());
}
else
{
ERROR_LOG(DISCIO, "%s already exists", exportName.c_str());
}
}
}
}
void CISOProperties::OnExtractDir(wxCommandEvent& event)
{
wxString Directory = m_Treectrl->GetItemText(m_Treectrl->GetSelection());
wxString Path = wxDirSelector(_("Choose the folder to extract to"));
if (!Path || !Directory)
return;
if (event.GetId() == IDM_EXTRACTALL)
{
if (m_open_iso->GetVolumeType() == DiscIO::Platform::WII_DISC)
{
wxTreeItemIdValue cookie;
wxTreeItemId root = m_Treectrl->GetRootItem();
wxTreeItemId item = m_Treectrl->GetFirstChild(root, cookie);
while (item.IsOk())
{
ExportDir("", WxStrToStr(Path),
reinterpret_cast<WiiPartition*>(m_Treectrl->GetItemData(item)));
item = m_Treectrl->GetNextChild(root, cookie);
}
}
else
{
ExportDir("", WxStrToStr(Path));
}
return;
}
while (m_Treectrl->GetItemParent(m_Treectrl->GetSelection()) != m_Treectrl->GetRootItem())
{
wxString temp = m_Treectrl->GetItemText(m_Treectrl->GetItemParent(m_Treectrl->GetSelection()));
Directory = temp + DIR_SEP_CHR + Directory;
m_Treectrl->SelectItem(m_Treectrl->GetItemParent(m_Treectrl->GetSelection()));
}
Directory += DIR_SEP_CHR;
if (m_open_iso->GetVolumeType() == DiscIO::Platform::WII_DISC)
{
const wxTreeItemId tree_selection = m_Treectrl->GetSelection();
WiiPartition* partition =
reinterpret_cast<WiiPartition*>(m_Treectrl->GetItemData(tree_selection));
Directory.erase(0,
m_Treectrl->GetItemText(tree_selection).length() + 1); // Remove "Partition x/"
ExportDir(WxStrToStr(Directory), WxStrToStr(Path), partition);
}
else
{
ExportDir(WxStrToStr(Directory), WxStrToStr(Path));
}
}
void CISOProperties::OnExtractDataFromHeader(wxCommandEvent& event)
{
DiscIO::IFileSystem* FS = nullptr;
wxString Path = wxDirSelector(_("Choose the folder to extract to"));
if (Path.empty())
return;
if (m_open_iso->GetVolumeType() == DiscIO::Platform::WII_DISC)
{
WiiPartition* partition =
reinterpret_cast<WiiPartition*>(m_Treectrl->GetItemData(m_Treectrl->GetSelection()));
FS = partition->FileSystem.get();
}
else
{
FS = m_filesystem.get();
}
bool ret = false;
if (event.GetId() == IDM_EXTRACTAPPLOADER)
{
ret = FS->ExportApploader(WxStrToStr(Path));
}
else if (event.GetId() == IDM_EXTRACTDOL)
{
ret = FS->ExportDOL(WxStrToStr(Path));
}
if (!ret)
WxUtils::ShowErrorDialog(
wxString::Format(_("Failed to extract to %s!"), WxStrToStr(Path).c_str()));
}
class IntegrityCheckThread : public wxThread
{
public:
IntegrityCheckThread(const WiiPartition& Partition)
: wxThread(wxTHREAD_JOINABLE), m_Partition(Partition)
{
Create();
}
ExitCode Entry() override { return (ExitCode)m_Partition.Partition->CheckIntegrity(); }
private:
const WiiPartition& m_Partition;
};
void CISOProperties::CheckPartitionIntegrity(wxCommandEvent& event)
{
// Normally we can't enter this function if we aren't analyzing a Wii disc
// anyway, but let's still check to be sure.
if (m_open_iso->GetVolumeType() != DiscIO::Platform::WII_DISC)
return;
wxProgressDialog dialog(_("Checking integrity..."), _("Working..."), 1000, this,
wxPD_APP_MODAL | wxPD_ELAPSED_TIME | wxPD_SMOOTH);
WiiPartition* partition =
reinterpret_cast<WiiPartition*>(m_Treectrl->GetItemData(m_Treectrl->GetSelection()));
IntegrityCheckThread thread(*partition);
thread.Run();
while (thread.IsAlive())
{
dialog.Pulse();
wxThread::Sleep(50);
}
dialog.Destroy();
if (!thread.Wait())
{
wxMessageBox(wxString::Format(_("Integrity check for %s failed. The disc image is most "
"likely corrupted or has been patched incorrectly."),
m_Treectrl->GetItemText(m_Treectrl->GetSelection())),
_("Integrity Check Error"), wxOK | wxICON_ERROR, this);
}
else
{
wxMessageBox(_("Integrity check completed. No errors have been found."),
_("Integrity check completed"), wxOK | wxICON_INFORMATION, this);
}
}
void CISOProperties::OnEmustateChanged(wxCommandEvent& event)
{
EmuIssues->Enable(event.GetSelection() != 0);

View file

@ -14,11 +14,11 @@
#include <wx/treebase.h>
#include "Common/IniFile.h"
#include "DiscIO/Filesystem.h"
#include "DiscIO/Volume.h"
#include "DolphinWX/ISOFile.h"
#include "DolphinWX/PatchAddEdit.h"
class ActionReplayCodesPanel;
class CheatWarningMessage;
class DolphinSlider;
class wxButton;
class wxCheckBox;
@ -27,32 +27,16 @@ class wxChoice;
class wxSpinCtrl;
class wxStaticBitmap;
class wxTextCtrl;
class wxTreeCtrl;
namespace DiscIO
{
enum class Language;
class IVolume;
}
namespace Gecko
{
class CodeConfigPanel;
}
class ActionReplayCodesPanel;
class CheatWarningMessage;
class GameListItem;
class WiiPartition final : public wxTreeItemData
{
public:
WiiPartition(std::unique_ptr<DiscIO::IVolume> partition,
std::unique_ptr<DiscIO::IFileSystem> file_system)
: Partition(std::move(partition)), FileSystem(std::move(file_system))
{
}
std::unique_ptr<DiscIO::IVolume> Partition;
std::unique_ptr<DiscIO::IFileSystem> FileSystem;
};
struct PHackData
{
@ -77,7 +61,6 @@ private:
DECLARE_EVENT_TABLE();
std::unique_ptr<DiscIO::IVolume> m_open_iso;
std::unique_ptr<DiscIO::IFileSystem> m_filesystem;
std::vector<PatchEngine::Patch> onFrame;
PHackData m_PHack_Data;
@ -104,9 +87,6 @@ private:
wxButton* EditPatch;
wxButton* RemovePatch;
wxTreeCtrl* m_Treectrl;
wxTreeItemId RootId;
ActionReplayCodesPanel* m_ar_code_panel;
Gecko::CodeConfigPanel* m_geckocode_panel;
@ -115,9 +95,7 @@ private:
enum
{
ID_TREECTRL = 1000,
ID_NOTEBOOK,
ID_NOTEBOOK = 1000,
ID_GAMECONFIG,
ID_PATCH_PAGE,
ID_ARCODE_PAGE,
@ -147,13 +125,6 @@ private:
ID_DEPTHPERCENTAGE,
ID_CONVERGENCE,
ID_MONODEPTH,
IDM_EXTRACTDIR,
IDM_EXTRACTALL,
IDM_EXTRACTFILE,
IDM_EXTRACTAPPLOADER,
IDM_EXTRACTDOL,
IDM_CHECKINTEGRITY,
};
void LaunchExternalEditor(const std::string& filename, bool wait_until_closed);
@ -165,23 +136,12 @@ private:
void OnShowDefaultConfig(wxCommandEvent& event);
void PatchListSelectionChanged(wxCommandEvent& event);
void PatchButtonClicked(wxCommandEvent& event);
void OnRightClickOnTree(wxTreeEvent& event);
void OnExtractFile(wxCommandEvent& event);
void OnExtractDir(wxCommandEvent& event);
void OnExtractDataFromHeader(wxCommandEvent& event);
void CheckPartitionIntegrity(wxCommandEvent& event);
void OnEmustateChanged(wxCommandEvent& event);
void OnCheatCodeToggled(wxCommandEvent& event);
void OnChangeTitle(wxCommandEvent& event);
const GameListItem OpenGameListItem;
size_t CreateDirectoryTree(wxTreeItemId& parent, const std::vector<DiscIO::SFileInfo>& fileInfos);
size_t CreateDirectoryTree(wxTreeItemId& parent, const std::vector<DiscIO::SFileInfo>& fileInfos,
const size_t _FirstIndex, const size_t _LastIndex);
void ExportDir(const std::string& _rFullPath, const std::string& _rExportFilename,
const WiiPartition* partition = nullptr);
IniFile GameIniDefault;
IniFile GameIniLocal;
std::string GameIniFileLocal;