mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-01-20 13:01:16 +01:00
58743416bb
These disc images are only used on dev units and not retail units. There are two important differences compared to normal Wii disc images: - The data starts 0x8000 bytes into each partition instead of 0x20000 - The data of a partition is stored unencrypted and contains no hashes Our old implementation was just guesswork and doesn't work at all. According to testing by GerbilSoft, this commit's implementation is able to read and extract files in the filesystem correctly, but the tested game still isn't able to boot. (It's thanks to their info about unencrypted disc images that I was able to make this commit.)
435 lines
14 KiB
C++
435 lines
14 KiB
C++
// Copyright 2016 Dolphin Emulator Project
|
|
// Licensed under GPLv2+
|
|
// Refer to the license.txt file included.
|
|
|
|
#include "DolphinWX/ISOProperties/FilesystemPanel.h"
|
|
|
|
#include <array>
|
|
#include <chrono>
|
|
#include <future>
|
|
#include <memory>
|
|
#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/DiscExtractor.h"
|
|
#include "DiscIO/Enums.h"
|
|
#include "DiscIO/Filesystem.h"
|
|
#include "DiscIO/Volume.h"
|
|
#include "DolphinWX/WxUtils.h"
|
|
|
|
namespace
|
|
{
|
|
class WiiPartition final : public wxTreeItemData
|
|
{
|
|
public:
|
|
WiiPartition(const DiscIO::Partition& partition_) : partition(partition_) {}
|
|
DiscIO::Partition 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;
|
|
}
|
|
|
|
void CreateDirectoryTree(wxTreeCtrl* tree_ctrl, wxTreeItemId parent,
|
|
const DiscIO::FileInfo& directory)
|
|
{
|
|
for (const DiscIO::FileInfo& file_info : directory)
|
|
{
|
|
const wxString name = StrToWxStr(file_info.GetName());
|
|
if (file_info.IsDirectory())
|
|
{
|
|
wxTreeItemId item = tree_ctrl->AppendItem(parent, name, ICON_FOLDER);
|
|
CreateDirectoryTree(tree_ctrl, item, file_info);
|
|
}
|
|
else
|
|
{
|
|
tree_ctrl->AppendItem(parent, name, ICON_FILE);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CreateDirectoryTree(wxTreeCtrl* tree_ctrl, wxTreeItemId parent,
|
|
const DiscIO::FileSystem* file_system)
|
|
{
|
|
if (file_system)
|
|
CreateDirectoryTree(tree_ctrl, parent, file_system->GetRoot());
|
|
}
|
|
|
|
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 std::unique_ptr<DiscIO::Volume>& opened_iso)
|
|
: wxPanel{parent, id}, m_opened_iso{opened_iso}
|
|
{
|
|
CreateGUI();
|
|
if (PopulateFileSystemTree())
|
|
{
|
|
BindEvents();
|
|
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::OnExtractAll, this, ID_EXTRACT_ALL);
|
|
Bind(wxEVT_MENU, &FilesystemPanel::OnExtractDirectories, this, ID_EXTRACT_DIR);
|
|
Bind(wxEVT_MENU, &FilesystemPanel::OnExtractSystemData, this, ID_EXTRACT_SYSTEM_DATA);
|
|
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);
|
|
}
|
|
|
|
bool FilesystemPanel::PopulateFileSystemTree()
|
|
{
|
|
const std::vector<DiscIO::Partition> partitions = m_opened_iso->GetPartitions();
|
|
m_has_partitions = !partitions.empty();
|
|
|
|
if (m_has_partitions)
|
|
{
|
|
for (size_t i = 0; i < partitions.size(); ++i)
|
|
{
|
|
wxTreeItemId partition_root = m_tree_ctrl->AppendItem(
|
|
m_tree_ctrl->GetRootItem(), wxString::Format(_("Partition %zu"), i), ICON_DISC);
|
|
|
|
WiiPartition* const partition = new WiiPartition(partitions[i]);
|
|
|
|
m_tree_ctrl->SetItemData(partition_root, partition);
|
|
CreateDirectoryTree(m_tree_ctrl, partition_root, m_opened_iso->GetFileSystem(partitions[i]));
|
|
|
|
if (partitions[i] == m_opened_iso->GetGamePartition())
|
|
m_tree_ctrl->Expand(partition_root);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CreateDirectoryTree(m_tree_ctrl, m_tree_ctrl->GetRootItem(),
|
|
m_opened_iso->GetFileSystem(DiscIO::PARTITION_NONE));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void FilesystemPanel::OnRightClickTree(wxTreeEvent& event)
|
|
{
|
|
m_tree_ctrl->SelectItem(event.GetItem());
|
|
|
|
wxMenu menu;
|
|
|
|
const wxTreeItemId selection = m_tree_ctrl->GetSelection();
|
|
const wxTreeItemId first_visible_item = m_tree_ctrl->GetFirstVisibleItem();
|
|
const int image_type = m_tree_ctrl->GetItemImage(selection);
|
|
const bool is_parent_of_partitions = m_has_partitions && first_visible_item == selection;
|
|
|
|
if (image_type == ICON_FILE)
|
|
menu.Append(ID_EXTRACT_FILE, _("Extract File..."));
|
|
else if (!is_parent_of_partitions)
|
|
menu.Append(ID_EXTRACT_DIR, _("Extract Files..."));
|
|
|
|
if (image_type == ICON_DISC)
|
|
{
|
|
if (!is_parent_of_partitions)
|
|
menu.Append(ID_EXTRACT_SYSTEM_DATA, _("Extract System Data..."));
|
|
|
|
if (first_visible_item == selection)
|
|
menu.Append(ID_EXTRACT_ALL, _("Extract Entire Disc..."));
|
|
else
|
|
menu.Append(ID_EXTRACT_ALL, _("Extract Entire Partition..."));
|
|
|
|
if (first_visible_item != selection && m_opened_iso->IsEncryptedAndHashed())
|
|
{
|
|
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())
|
|
ExtractSingleDirectory(extract_path);
|
|
}
|
|
|
|
void FilesystemPanel::OnExtractSystemData(wxCommandEvent& event)
|
|
{
|
|
const wxString path = wxDirSelector(_("Choose the folder to extract to"));
|
|
|
|
if (path.empty())
|
|
return;
|
|
|
|
DiscIO::Partition partition;
|
|
if (m_has_partitions)
|
|
{
|
|
const auto* const selection_data = m_tree_ctrl->GetItemData(m_tree_ctrl->GetSelection());
|
|
const auto* const wii_partition = static_cast<const WiiPartition*>(selection_data);
|
|
|
|
partition = wii_partition->partition;
|
|
}
|
|
else
|
|
{
|
|
partition = DiscIO::PARTITION_NONE;
|
|
}
|
|
|
|
if (!DiscIO::ExportSystemData(*m_opened_iso, partition, WxStrToStr(path)))
|
|
{
|
|
WxUtils::ShowErrorDialog(
|
|
wxString::Format(_("Failed to extract to %s!"), WxStrToStr(path).c_str()));
|
|
}
|
|
}
|
|
|
|
void FilesystemPanel::OnExtractAll(wxCommandEvent& event)
|
|
{
|
|
const wxString extract_path = wxDirSelector(_("Choose the folder to extract to"));
|
|
|
|
if (extract_path.empty())
|
|
return;
|
|
|
|
const std::string std_extract_path = WxStrToStr(extract_path);
|
|
|
|
const wxTreeItemId selection = m_tree_ctrl->GetSelection();
|
|
const bool first_item_selected = m_tree_ctrl->GetFirstVisibleItem() == selection;
|
|
|
|
if (m_has_partitions && first_item_selected)
|
|
{
|
|
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));
|
|
const std::optional<u32> partition_type =
|
|
*m_opened_iso->GetPartitionType(partition->partition);
|
|
if (partition_type)
|
|
{
|
|
const std::string partition_name = DiscIO::DirectoryNameForPartitionType(*partition_type);
|
|
ExtractPartition(std_extract_path + '/' + partition_name, partition->partition);
|
|
}
|
|
item = m_tree_ctrl->GetNextChild(root, cookie);
|
|
}
|
|
}
|
|
else if (m_has_partitions && !first_item_selected)
|
|
{
|
|
const auto* const partition = static_cast<WiiPartition*>(m_tree_ctrl->GetItemData(selection));
|
|
ExtractPartition(std_extract_path, partition->partition);
|
|
}
|
|
else
|
|
{
|
|
ExtractPartition(std_extract_path, DiscIO::PARTITION_NONE);
|
|
}
|
|
}
|
|
|
|
void FilesystemPanel::OnCheckPartitionIntegrity(wxCommandEvent& WXUNUSED(event))
|
|
{
|
|
// Normally we can't enter this function if we're analyzing a volume that
|
|
// doesn't have partitions anyway, but let's still check to be sure.
|
|
if (!m_has_partitions)
|
|
return;
|
|
|
|
wxProgressDialog dialog(_("Checking integrity..."), _("Working..."), 1000, this,
|
|
wxPD_APP_MODAL | wxPD_ELAPSED_TIME | wxPD_SMOOTH);
|
|
|
|
const auto selection = m_tree_ctrl->GetSelection();
|
|
WiiPartition* partition =
|
|
static_cast<WiiPartition*>(m_tree_ctrl->GetItemData(m_tree_ctrl->GetSelection()));
|
|
std::future<bool> is_valid = std::async(
|
|
std::launch::async, [&] { return m_opened_iso->CheckIntegrity(partition->partition); });
|
|
|
|
while (is_valid.wait_for(std::chrono::milliseconds(50)) != std::future_status::ready)
|
|
dialog.Pulse();
|
|
dialog.Hide();
|
|
|
|
if (is_valid.get())
|
|
{
|
|
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::ExtractSingleFile(const wxString& output_file_path) const
|
|
{
|
|
const std::pair<wxString, DiscIO::Partition> path = BuildFilePathFromSelection();
|
|
DiscIO::ExportFile(*m_opened_iso, path.second, WxStrToStr(path.first),
|
|
WxStrToStr(output_file_path));
|
|
}
|
|
|
|
void FilesystemPanel::ExtractSingleDirectory(const wxString& output_folder)
|
|
{
|
|
const std::pair<wxString, DiscIO::Partition> path = BuildDirectoryPathFromSelection();
|
|
ExtractDirectories(WxStrToStr(path.first), WxStrToStr(output_folder), path.second);
|
|
}
|
|
|
|
void FilesystemPanel::ExtractDirectories(const std::string& full_path,
|
|
const std::string& output_folder,
|
|
const DiscIO::Partition& partition)
|
|
{
|
|
const DiscIO::FileSystem* file_system = m_opened_iso->GetFileSystem(partition);
|
|
if (!file_system)
|
|
return;
|
|
|
|
std::unique_ptr<DiscIO::FileInfo> file_info = file_system->FindFileInfo(full_path);
|
|
u32 size = file_info->GetTotalChildren();
|
|
u32 progress = 0;
|
|
|
|
wxString dialog_title = full_path.empty() ? _("Extracting All Files") : _("Extracting Directory");
|
|
wxProgressDialog dialog(dialog_title, _("Extracting..."), size, this,
|
|
wxPD_APP_MODAL | wxPD_AUTO_HIDE | wxPD_CAN_ABORT | wxPD_ELAPSED_TIME |
|
|
wxPD_ESTIMATED_TIME | wxPD_REMAINING_TIME | wxPD_SMOOTH);
|
|
|
|
DiscIO::ExportDirectory(
|
|
*m_opened_iso, partition, *file_info, true, full_path, output_folder,
|
|
[&](const std::string& path) {
|
|
dialog.SetTitle(wxString::Format(
|
|
"%s : %d%%", dialog_title.c_str(),
|
|
static_cast<u32>((static_cast<float>(progress) / static_cast<float>(size)) * 100)));
|
|
dialog.Update(progress, wxString::Format(_("Extracting %s"), StrToWxStr(path)));
|
|
++progress;
|
|
return dialog.WasCancelled();
|
|
});
|
|
}
|
|
|
|
void FilesystemPanel::ExtractPartition(const std::string& output_folder,
|
|
const DiscIO::Partition& partition)
|
|
{
|
|
ExtractDirectories("", output_folder + "/files", partition);
|
|
DiscIO::ExportSystemData(*m_opened_iso, partition, output_folder);
|
|
}
|
|
|
|
std::pair<wxString, DiscIO::Partition> FilesystemPanel::BuildFilePathFromSelection() const
|
|
{
|
|
const wxTreeItemId root_node = m_tree_ctrl->GetRootItem();
|
|
wxTreeItemId node = m_tree_ctrl->GetSelection();
|
|
|
|
wxString file_path;
|
|
|
|
if (node != root_node)
|
|
{
|
|
file_path = m_tree_ctrl->GetItemText(node);
|
|
node = m_tree_ctrl->GetItemParent(node);
|
|
|
|
while (node != root_node)
|
|
{
|
|
file_path = m_tree_ctrl->GetItemText(node) + DIR_SEP_CHR + file_path;
|
|
node = m_tree_ctrl->GetItemParent(node);
|
|
}
|
|
}
|
|
|
|
if (m_has_partitions)
|
|
{
|
|
const size_t slash_index = file_path.find('/');
|
|
const wxString partition_label = file_path.substr(0, slash_index);
|
|
const WiiPartition* const partition = FindWiiPartition(m_tree_ctrl, partition_label);
|
|
|
|
// Remove "Partition x/"
|
|
file_path.erase(0, slash_index + 1);
|
|
|
|
return {file_path, partition->partition};
|
|
}
|
|
else
|
|
{
|
|
return {file_path, DiscIO::PARTITION_NONE};
|
|
}
|
|
}
|
|
|
|
std::pair<wxString, DiscIO::Partition> FilesystemPanel::BuildDirectoryPathFromSelection() const
|
|
{
|
|
const std::pair<wxString, DiscIO::Partition> result = BuildFilePathFromSelection();
|
|
return {result.first + DIR_SEP_CHR, result.second};
|
|
}
|