mirror of
https://github.com/RPCS3/rpcs3.git
synced 2025-12-16 04:09:07 +00:00
Merge 33371473fe into cf87f24587
This commit is contained in:
commit
bc24586a1b
@ -155,7 +155,7 @@ namespace fs
|
||||
// Virtual device
|
||||
struct device_base
|
||||
{
|
||||
const std::string fs_prefix;
|
||||
std::string fs_prefix;
|
||||
|
||||
device_base();
|
||||
virtual ~device_base();
|
||||
|
||||
@ -125,6 +125,7 @@ target_sources(rpcs3_emu PRIVATE
|
||||
../Loader/PSF.cpp
|
||||
../Loader/PUP.cpp
|
||||
../Loader/TAR.cpp
|
||||
../Loader/ISO.cpp
|
||||
../Loader/TROPUSR.cpp
|
||||
../Loader/TRP.cpp
|
||||
)
|
||||
|
||||
@ -34,6 +34,7 @@
|
||||
|
||||
#include "Loader/PSF.h"
|
||||
#include "Loader/TAR.h"
|
||||
#include "Loader/ISO.h"
|
||||
#include "Loader/ELF.h"
|
||||
#include "Loader/disc.h"
|
||||
|
||||
@ -782,7 +783,7 @@ bool Emulator::BootRsxCapture(const std::string& path)
|
||||
std::unique_ptr<rsx::frame_capture_data> frame = std::make_unique<rsx::frame_capture_data>();
|
||||
utils::serial load;
|
||||
load.set_reading_state();
|
||||
|
||||
|
||||
const std::string lower = fmt::to_lower(path);
|
||||
|
||||
if (lower.ends_with(".gz") || lower.ends_with(".zst"))
|
||||
@ -1078,6 +1079,7 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch,
|
||||
sys_log.notice("Path: %s", m_path);
|
||||
|
||||
std::string inherited_ps3_game_path;
|
||||
bool launching_from_disc_archive = false;
|
||||
|
||||
{
|
||||
Init();
|
||||
@ -1406,6 +1408,10 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch,
|
||||
m_path = rpcs3::utils::get_hdd1_dir();
|
||||
m_path += std::string_view(argv[0]).substr(9);
|
||||
}
|
||||
else if (is_file_iso(argv[0]))
|
||||
{
|
||||
m_path = argv[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
sys_log.error("Unknown source for path redirection: %s", argv[0]);
|
||||
@ -1421,6 +1427,28 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch,
|
||||
}
|
||||
|
||||
const std::string resolved_path = GetCallbacks().resolve_path(m_path);
|
||||
if (is_file_iso(m_path))
|
||||
{
|
||||
load_iso(m_path);
|
||||
|
||||
std::string path = iso_device::virtual_device_name + "/";
|
||||
|
||||
vfs::mount("/dev_bdvd/"sv, path);
|
||||
|
||||
// ISOs that are install discs will error if set to EBOOT.BIN
|
||||
// so this should cover both of them
|
||||
if (fs::exists(path + "PS3_GAME/USRDIR/EBOOT.BIN"))
|
||||
{
|
||||
path = path + "PS3_GAME/USRDIR/EBOOT.BIN";
|
||||
}
|
||||
|
||||
m_path = path;
|
||||
|
||||
m_dir = "/dev_bdvd/PS3_GAME/";
|
||||
m_cat = "DG"sv;
|
||||
|
||||
launching_from_disc_archive = true;
|
||||
}
|
||||
|
||||
const std::string elf_dir = fs::get_parent_dir(m_path);
|
||||
|
||||
@ -1595,8 +1623,14 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch,
|
||||
}
|
||||
}
|
||||
|
||||
// ISO PKG INSTALL HACK!
|
||||
if (!m_path.ends_with("EBOOT.BIN") && launching_from_disc_archive)
|
||||
{
|
||||
bdvd_dir = m_path;
|
||||
}
|
||||
|
||||
// Special boot mode (directory scan)
|
||||
if (fs::is_dir(m_path))
|
||||
if (fs::is_dir(m_path) && !launching_from_disc_archive)
|
||||
{
|
||||
m_state = system_state::ready;
|
||||
GetCallbacks().on_ready();
|
||||
@ -1788,6 +1822,10 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch,
|
||||
sys_log.error("Failed to move disc game %s to '%s' (%s)", m_title_id, dst_dir, fs::g_tls_error);
|
||||
return game_boot_result::wrong_disc_location;
|
||||
}
|
||||
else if (launching_from_disc_archive)
|
||||
{
|
||||
bdvd_dir = iso_device::virtual_device_name + "/";
|
||||
}
|
||||
}
|
||||
|
||||
if (bdvd_dir.empty() && disc.empty() && !is_disc_patch)
|
||||
@ -2076,6 +2114,13 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch,
|
||||
}
|
||||
}
|
||||
|
||||
// ISO has no USRDIR/EBOOT.BIN, and we've examined its PKGDIR and extras.
|
||||
// time to wrap up
|
||||
if (!m_path.ends_with("EBOOT.BIN") && launching_from_disc_archive)
|
||||
{
|
||||
return game_boot_result::nothing_to_boot;
|
||||
}
|
||||
|
||||
// Check firmware version
|
||||
if (const std::string_view game_fw_version = psf::get_string(_psf, "PS3_SYSTEM_VER", ""); !game_fw_version.empty())
|
||||
{
|
||||
@ -3368,7 +3413,7 @@ void Emulator::Kill(bool allow_autoexit, bool savestate, savestate_stage* save_s
|
||||
{
|
||||
if (spu.first->pc != spu.second || spu.first->unsavable)
|
||||
{
|
||||
std::string dump;
|
||||
std::string dump;
|
||||
spu.first->dump_all(dump);
|
||||
|
||||
sys_log.error("SPU thread continued after being paused. (old_pc=0x%x, pc=0x%x, unsavable=%d)", spu.second, spu.first->pc, spu.first->unsavable);
|
||||
@ -3546,7 +3591,17 @@ void Emulator::Kill(bool allow_autoexit, bool savestate, savestate_stage* save_s
|
||||
|
||||
ar(std::array<u8, 32>{}); // Reserved for future use
|
||||
|
||||
if (auto dir = vfs::get("/dev_bdvd/PS3_GAME"); fs::is_dir(dir) && !fs::is_file(fs::get_parent_dir(dir) + "/PS3_DISC.SFB"))
|
||||
// Game mounted from archive
|
||||
if (m_path.starts_with(iso_device::virtual_device_name + "/"))
|
||||
{
|
||||
auto device = fs::get_virtual_device(iso_device::virtual_device_name + "/");
|
||||
ensure(device);
|
||||
|
||||
auto iso_device = dynamic_cast<class iso_device*>(device.get());
|
||||
ar(iso_device->get_loaded_iso());
|
||||
ar(m_title_id);
|
||||
}
|
||||
else if (auto dir = vfs::get("/dev_bdvd/PS3_GAME"); fs::is_dir(dir) && !fs::is_file(fs::get_parent_dir(dir) + "/PS3_DISC.SFB"))
|
||||
{
|
||||
// Fake /dev_bdvd/PS3_GAME detected, use HDD0 for m_path restoration
|
||||
ensure(vfs::unmount("/dev_bdvd/PS3_GAME"));
|
||||
@ -3831,6 +3886,7 @@ void Emulator::Kill(bool allow_autoexit, bool savestate, savestate_stage* save_s
|
||||
disc.clear();
|
||||
klic.clear();
|
||||
hdd1.clear();
|
||||
unload_iso();
|
||||
init_mem_containers = nullptr;
|
||||
m_config_path.clear();
|
||||
m_config_mode = cfg_mode::custom;
|
||||
@ -4331,7 +4387,7 @@ const std::string& Emulator::GetFakeCat() const
|
||||
{
|
||||
const std::string mount_point = vfs::get("/dev_bdvd");
|
||||
|
||||
if (mount_point.empty() || !IsPathInsideDir(m_path, mount_point))
|
||||
if (mount_point.empty() || (!IsPathInsideDir(m_path, mount_point) && !m_path.starts_with(iso_device::virtual_device_name)))
|
||||
{
|
||||
static const std::string s_hg = "HG";
|
||||
return s_hg;
|
||||
|
||||
@ -4,6 +4,8 @@
|
||||
#include "util/yaml.hpp"
|
||||
#include "Utilities/File.h"
|
||||
|
||||
#include "Loader/ISO.h"
|
||||
|
||||
LOG_CHANNEL(cfg_log, "CFG");
|
||||
|
||||
games_config::games_config()
|
||||
@ -44,6 +46,15 @@ std::string games_config::get_path(const std::string& title_id) const
|
||||
|
||||
games_config::result games_config::add_game(const std::string& key, const std::string& path)
|
||||
{
|
||||
if (path == iso_device::virtual_device_name + "/")
|
||||
{
|
||||
auto device = fs::get_virtual_device(iso_device::virtual_device_name + "/");
|
||||
if (!device) return result::failure;
|
||||
|
||||
auto iso_device = dynamic_cast<class iso_device*>(device.get());
|
||||
return add_game(key, iso_device->get_loaded_iso());
|
||||
}
|
||||
|
||||
std::lock_guard lock(m_mutex);
|
||||
|
||||
// Access or create node if does not exist
|
||||
|
||||
613
rpcs3/Loader/ISO.cpp
Normal file
613
rpcs3/Loader/ISO.cpp
Normal file
@ -0,0 +1,613 @@
|
||||
#include "stdafx.h"
|
||||
|
||||
#include "ISO.h"
|
||||
|
||||
#include <codecvt>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <filesystem>
|
||||
#include <stack>
|
||||
|
||||
bool is_file_iso(const std::string& path)
|
||||
{
|
||||
if (fs::is_dir(path)) return false;
|
||||
|
||||
return is_file_iso(fs::file(path));
|
||||
}
|
||||
|
||||
bool is_file_iso(const fs::file& file)
|
||||
{
|
||||
if (!file) return false;
|
||||
if (file.size() < 32768 + 6) return false;
|
||||
|
||||
file.seek(32768);
|
||||
|
||||
char magic[5];
|
||||
file.read_at(32768 + 1, magic, 5);
|
||||
|
||||
return magic[0] == 'C' && magic[1] == 'D'
|
||||
&& magic[2] == '0' && magic[3] == '0'
|
||||
&& magic[4] == '1';
|
||||
}
|
||||
|
||||
const int ISO_BLOCK_SIZE = 2048;
|
||||
|
||||
template<typename T>
|
||||
inline T read_both_endian_int(fs::file& file)
|
||||
{
|
||||
T out;
|
||||
|
||||
if (std::endian::little == std::endian::native)
|
||||
{
|
||||
out = file.read<T>();
|
||||
file.seek(sizeof(T), fs::seek_cur);
|
||||
}
|
||||
else
|
||||
{
|
||||
file.seek(sizeof(T), fs::seek_cur);
|
||||
out = file.read<T>();
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
// assumed that directory_entry is at file head
|
||||
std::optional<iso_fs_metadata> iso_read_directory_entry(fs::file& file, bool names_in_ucs2 = false)
|
||||
{
|
||||
auto start_pos = file.pos();
|
||||
u8 entry_length = file.read<u8>();
|
||||
|
||||
if (entry_length == 0) return std::nullopt;
|
||||
|
||||
file.seek(1, fs::seek_cur);
|
||||
u32 start_sector = read_both_endian_int<u32>(file);
|
||||
u32 file_size = read_both_endian_int<u32>(file);
|
||||
|
||||
std::tm file_date = {};
|
||||
file_date.tm_year = file.read<u8>();
|
||||
file_date.tm_mon = file.read<u8>() - 1;
|
||||
file_date.tm_mday = file.read<u8>();
|
||||
file_date.tm_hour = file.read<u8>();
|
||||
file_date.tm_min = file.read<u8>();
|
||||
file_date.tm_sec = file.read<u8>();
|
||||
s16 timezone_value = file.read<u8>();
|
||||
s16 timezone_offset = (timezone_value - 50) * 15 * 60;
|
||||
|
||||
std::time_t date_time = std::mktime(&file_date) + timezone_offset;
|
||||
|
||||
u8 flags = file.read<u8>();
|
||||
|
||||
// 2nd flag bit indicates whether a given fs node is a directory
|
||||
bool is_directory = flags & 0b00000010;
|
||||
bool has_more_extents = flags & 0b10000000;
|
||||
|
||||
file.seek(6, fs::seek_cur);
|
||||
|
||||
u8 file_name_length = file.read<u8>();
|
||||
|
||||
std::string file_name;
|
||||
file.read(file_name, file_name_length);
|
||||
|
||||
if (file_name_length == 1 && file_name[0] == 0)
|
||||
{
|
||||
file_name = ".";
|
||||
}
|
||||
else if (file_name == "\1")
|
||||
{
|
||||
file_name = "..";
|
||||
}
|
||||
else if (names_in_ucs2) // for strings in joliet descriptor
|
||||
{
|
||||
std::string new_file_name = "";
|
||||
int read = 0;
|
||||
const u8* raw_str = reinterpret_cast<const u8*>(file_name.c_str());
|
||||
while(read < file_name_length)
|
||||
{
|
||||
// characters are stored in big endian format.
|
||||
const u16 upper = raw_str[read];
|
||||
const u8 lower = raw_str[read + 1];
|
||||
|
||||
const u16 code_point = (upper << 8) + lower;
|
||||
|
||||
std::wstring_convert<std::codecvt_utf8<char16_t>, char16_t> convert;
|
||||
new_file_name += convert.to_bytes(code_point);
|
||||
|
||||
read += 2;
|
||||
}
|
||||
|
||||
file_name = new_file_name;
|
||||
}
|
||||
|
||||
if (file_name.ends_with(";1"))
|
||||
{
|
||||
file_name.erase(file_name.end() - 2, file_name.end());
|
||||
}
|
||||
|
||||
if (file_name_length > 1 && file_name.ends_with("."))
|
||||
{
|
||||
file_name.pop_back();
|
||||
}
|
||||
|
||||
// skip the rest of the entry.
|
||||
file.seek(entry_length + start_pos);
|
||||
|
||||
return iso_fs_metadata
|
||||
{
|
||||
.name = file_name,
|
||||
.time = date_time,
|
||||
.is_directory = is_directory,
|
||||
.has_multiple_extents = has_more_extents,
|
||||
.extents =
|
||||
{
|
||||
iso_extent_info
|
||||
{
|
||||
.start = start_sector,
|
||||
.size = file_size
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void iso_form_hierarchy(fs::file& file, iso_fs_node& node,
|
||||
bool use_ucs2_decoding = false, std::string parent_path = "")
|
||||
{
|
||||
if (!node.metadata.is_directory) return;
|
||||
|
||||
std::vector<int> multi_extent_node_indices;
|
||||
|
||||
// assuming the directory spans a single extent
|
||||
const auto& directory_extent = node.metadata.extents[0];
|
||||
|
||||
file.seek(directory_extent.start * ISO_BLOCK_SIZE);
|
||||
|
||||
u64 end_pos = directory_extent.size + (directory_extent.start * ISO_BLOCK_SIZE);
|
||||
|
||||
while(file.pos() < end_pos)
|
||||
{
|
||||
auto entry = iso_read_directory_entry(file, use_ucs2_decoding);
|
||||
if (!entry)
|
||||
{
|
||||
u64 new_sector = (file.pos() / ISO_BLOCK_SIZE) + 1;
|
||||
file.seek(new_sector * ISO_BLOCK_SIZE);
|
||||
continue;
|
||||
}
|
||||
|
||||
bool extent_added = false;
|
||||
|
||||
// find previous extent and merge into it, otherwise we push this node's index
|
||||
for (int index : multi_extent_node_indices)
|
||||
{
|
||||
auto& selected_node = node.children.at(index);
|
||||
if (selected_node->metadata.name.compare(entry->name) == 0)
|
||||
{
|
||||
// merge into selected_node
|
||||
selected_node->metadata.extents.push_back(entry->extents[0]);
|
||||
|
||||
extent_added = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (extent_added) continue;
|
||||
|
||||
if (entry->has_multiple_extents)
|
||||
{
|
||||
// haven't pushed entry to node.children yet so node.children::size() == entry_index
|
||||
multi_extent_node_indices.push_back(node.children.size());
|
||||
}
|
||||
|
||||
node.children.push_back(std::make_unique<iso_fs_node>(iso_fs_node{
|
||||
.metadata = *entry
|
||||
}));
|
||||
}
|
||||
|
||||
for (auto& child_node : node.children)
|
||||
{
|
||||
if (child_node->metadata.name != "." && child_node->metadata.name != "..")
|
||||
{
|
||||
iso_form_hierarchy(file, *child_node, use_ucs2_decoding, parent_path + "/" + node.metadata.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
u64 iso_fs_metadata::size() const
|
||||
{
|
||||
u64 total_size = 0;
|
||||
for (const auto& extent : extents)
|
||||
{
|
||||
total_size += extent.size;
|
||||
}
|
||||
|
||||
return total_size;
|
||||
}
|
||||
|
||||
iso_archive::iso_archive(const std::string& path)
|
||||
{
|
||||
m_path = path;
|
||||
m_file = fs::file(path);
|
||||
|
||||
if (!is_file_iso(m_file))
|
||||
{
|
||||
// not iso... TODO: throw something??
|
||||
return;
|
||||
}
|
||||
|
||||
u8 descriptor_type = -2;
|
||||
bool use_ucs2_decoding = false;
|
||||
|
||||
do
|
||||
{
|
||||
auto descriptor_start = m_file.pos();
|
||||
|
||||
descriptor_type = m_file.read<u8>();
|
||||
|
||||
// 1 = primary vol descriptor, 2 = joliet SVD
|
||||
if (descriptor_type == 1 || descriptor_type == 2)
|
||||
{
|
||||
use_ucs2_decoding = descriptor_type == 2;
|
||||
|
||||
// skip the rest of descriptor's data
|
||||
m_file.seek(155, fs::seek_cur);
|
||||
|
||||
m_root = iso_fs_node
|
||||
{
|
||||
.metadata = iso_read_directory_entry(m_file, use_ucs2_decoding).value(),
|
||||
};
|
||||
}
|
||||
|
||||
m_file.seek(descriptor_start + ISO_BLOCK_SIZE);
|
||||
}
|
||||
while(descriptor_type != 255);
|
||||
|
||||
iso_form_hierarchy(m_file, m_root, use_ucs2_decoding);
|
||||
}
|
||||
|
||||
iso_fs_node* iso_archive::retrieve(const std::string& passed_path)
|
||||
{
|
||||
std::string path = std::filesystem::path(passed_path).string();
|
||||
|
||||
size_t start = 0;
|
||||
size_t end = path.find_first_of(fs::delim);
|
||||
|
||||
std::stack<iso_fs_node*> search_stack;
|
||||
search_stack.push(&m_root);
|
||||
|
||||
do
|
||||
{
|
||||
if (search_stack.empty()) return nullptr;
|
||||
auto* top_entry = search_stack.top();
|
||||
|
||||
if (end == std::string::npos)
|
||||
{
|
||||
end = path.size();
|
||||
}
|
||||
|
||||
auto path_component = path.substr(start, end-start);
|
||||
|
||||
bool found = false;
|
||||
|
||||
if (path_component == ".")
|
||||
{
|
||||
found = true;
|
||||
}
|
||||
else if (path_component == "..")
|
||||
{
|
||||
search_stack.pop();
|
||||
found = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (const auto& entry : top_entry->children)
|
||||
{
|
||||
if (entry->metadata.name.compare(path_component) == 0)
|
||||
{
|
||||
search_stack.push(entry.get());
|
||||
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) return nullptr;
|
||||
|
||||
start = end + 1;
|
||||
end = path.find_first_of(fs::delim, start);
|
||||
}
|
||||
while(start < path.size());
|
||||
|
||||
if (search_stack.empty()) return nullptr;
|
||||
|
||||
return search_stack.top();
|
||||
}
|
||||
|
||||
bool iso_archive::exists(const std::string& path)
|
||||
{
|
||||
return retrieve(path) != nullptr;
|
||||
}
|
||||
|
||||
bool iso_archive::is_file(const std::string& path)
|
||||
{
|
||||
auto file_node = retrieve(path);
|
||||
if (!file_node) return false;
|
||||
|
||||
return !file_node->metadata.is_directory;
|
||||
}
|
||||
|
||||
iso_file iso_archive::open(const std::string& path)
|
||||
{
|
||||
return iso_file(fs::file(m_path), *retrieve(path));
|
||||
}
|
||||
|
||||
iso_file::iso_file(fs::file&& iso_handle, const iso_fs_node& node)
|
||||
: m_file(std::move(iso_handle)), m_meta(node.metadata), m_pos(0)
|
||||
{
|
||||
m_file.seek(ISO_BLOCK_SIZE * node.metadata.extents[0].start);
|
||||
}
|
||||
|
||||
fs::stat_t iso_file::get_stat()
|
||||
{
|
||||
return fs::stat_t
|
||||
{
|
||||
.is_directory = false,
|
||||
.is_symlink = false,
|
||||
.is_writable = false,
|
||||
.size = size(),
|
||||
.atime = m_meta.time,
|
||||
.mtime = m_meta.time,
|
||||
.ctime = m_meta.time
|
||||
};
|
||||
}
|
||||
|
||||
bool iso_file::trunc(u64)
|
||||
{
|
||||
fs::g_tls_error = fs::error::readonly;
|
||||
return false;
|
||||
}
|
||||
|
||||
std::pair<u64, iso_extent_info> iso_file::get_extent_pos(u64 pos) const
|
||||
{
|
||||
auto it = m_meta.extents.begin();
|
||||
|
||||
while(pos >= it->size && it != m_meta.extents.end() - 1)
|
||||
{
|
||||
pos -= it->size;
|
||||
|
||||
it++;
|
||||
}
|
||||
|
||||
return {pos, *it};
|
||||
}
|
||||
|
||||
// assumed valid and in bounds.
|
||||
u64 iso_file::file_offset(u64 pos) const
|
||||
{
|
||||
auto [local_pos, extent] = get_extent_pos(pos);
|
||||
|
||||
return (extent.start * ISO_BLOCK_SIZE) + local_pos;
|
||||
}
|
||||
|
||||
u64 iso_file::local_extent_remaining(u64 pos) const
|
||||
{
|
||||
auto [local_pos, extent] = get_extent_pos(pos);
|
||||
|
||||
return extent.size - local_pos;
|
||||
}
|
||||
|
||||
u64 iso_file::local_extent_size(u64 pos) const
|
||||
{
|
||||
return get_extent_pos(pos).second.size;
|
||||
}
|
||||
|
||||
u64 iso_file::read(void* buffer, u64 size)
|
||||
{
|
||||
auto r = read_at(m_pos, buffer, size);
|
||||
m_pos += r;
|
||||
return r;
|
||||
}
|
||||
|
||||
u64 iso_file::read_at(u64 offset, void* buffer, u64 size)
|
||||
{
|
||||
u64 local_remaining = local_extent_remaining(offset);
|
||||
|
||||
u64 total_read = m_file.read_at(file_offset(offset), buffer, std::min(size, local_remaining));
|
||||
|
||||
auto total_size = this->size();
|
||||
|
||||
if (size > total_read && (offset + total_read) < total_size)
|
||||
{
|
||||
u64 second_total_read = read_at(offset + total_read,
|
||||
reinterpret_cast<u8*>(buffer) + total_read,
|
||||
size - total_read
|
||||
);
|
||||
|
||||
return total_read + second_total_read;
|
||||
}
|
||||
|
||||
return total_read;
|
||||
}
|
||||
|
||||
u64 iso_file::write(const void*, u64)
|
||||
{
|
||||
fs::g_tls_error = fs::error::readonly;
|
||||
return 0;
|
||||
}
|
||||
|
||||
u64 iso_file::seek(s64 offset, fs::seek_mode whence)
|
||||
{
|
||||
const s64 total_size = size();
|
||||
const s64 new_pos =
|
||||
whence == fs::seek_set ? offset :
|
||||
whence == fs::seek_cur ? offset + m_pos :
|
||||
whence == fs::seek_end ? offset + total_size : -1;
|
||||
|
||||
if (new_pos < 0)
|
||||
{
|
||||
fs::g_tls_error = fs::error::inval;
|
||||
return -1;
|
||||
}
|
||||
|
||||
const u64 bad_res = -1;
|
||||
|
||||
u64 result = m_file.seek(file_offset(m_pos));
|
||||
if (result == bad_res) return -1;
|
||||
|
||||
m_pos = new_pos;
|
||||
return m_pos;
|
||||
}
|
||||
|
||||
u64 iso_file::size()
|
||||
{
|
||||
u64 extent_sizes = 0;
|
||||
for (const auto& extent : m_meta.extents)
|
||||
{
|
||||
extent_sizes += extent.size;
|
||||
}
|
||||
|
||||
return extent_sizes;
|
||||
}
|
||||
|
||||
void iso_file::release()
|
||||
{
|
||||
m_file.release();
|
||||
}
|
||||
|
||||
bool iso_dir::read(fs::dir_entry& entry)
|
||||
{
|
||||
if (m_pos < m_node.children.size())
|
||||
{
|
||||
auto& selected = m_node.children[m_pos].get()->metadata;
|
||||
u64 size = selected.size();
|
||||
|
||||
entry.name = selected.name;
|
||||
entry.atime = selected.time;
|
||||
entry.mtime = selected.time;
|
||||
entry.ctime = selected.time;
|
||||
entry.is_directory = selected.is_directory;
|
||||
entry.is_symlink = false;
|
||||
entry.is_writable = false;
|
||||
entry.size = size;
|
||||
|
||||
m_pos++;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool iso_device::stat(const std::string& path, fs::stat_t& info)
|
||||
{
|
||||
auto relative_path = std::filesystem::relative(std::filesystem::path(path),
|
||||
std::filesystem::path(fs_prefix)).string();
|
||||
|
||||
auto node = m_archive.retrieve(relative_path);
|
||||
if (!node)
|
||||
{
|
||||
fs::g_tls_error = fs::error::noent;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto& meta = node->metadata;
|
||||
u64 size = meta.size();
|
||||
|
||||
info = fs::stat_t
|
||||
{
|
||||
.is_directory = meta.is_directory,
|
||||
.is_symlink = false,
|
||||
.is_writable = false,
|
||||
.size = size,
|
||||
.atime = meta.time,
|
||||
.mtime = meta.time,
|
||||
.ctime = meta.time
|
||||
};
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool iso_device::statfs(const std::string& path, fs::device_stat& info)
|
||||
{
|
||||
auto relative_path = std::filesystem::relative(std::filesystem::path(path),
|
||||
std::filesystem::path(fs_prefix)).string();
|
||||
|
||||
auto node = m_archive.retrieve(relative_path);
|
||||
if (!node)
|
||||
{
|
||||
fs::g_tls_error = fs::error::noent;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto& meta = node->metadata;
|
||||
u64 size = meta.size();
|
||||
|
||||
info = fs::device_stat
|
||||
{
|
||||
.block_size=size,
|
||||
.total_size=size,
|
||||
.total_free=0,
|
||||
.avail_free=0
|
||||
};
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::unique_ptr<fs::file_base> iso_device::open(const std::string& path, bs_t<fs::open_mode> mode)
|
||||
{
|
||||
auto relative_path = std::filesystem::relative(std::filesystem::path(path),
|
||||
std::filesystem::path(fs_prefix)).string();
|
||||
|
||||
auto node = m_archive.retrieve(relative_path);
|
||||
if (!node)
|
||||
{
|
||||
fs::g_tls_error = fs::error::noent;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (node->metadata.is_directory)
|
||||
{
|
||||
fs::g_tls_error = fs::error::isdir;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return std::make_unique<iso_file>(fs::file(iso_path), *node);
|
||||
}
|
||||
|
||||
std::unique_ptr<fs::dir_base> iso_device::open_dir(const std::string& path)
|
||||
{
|
||||
auto relative_path = std::filesystem::relative(std::filesystem::path(path),
|
||||
std::filesystem::path(fs_prefix)).string();
|
||||
|
||||
auto node = m_archive.retrieve(relative_path);
|
||||
if (!node)
|
||||
{
|
||||
fs::g_tls_error = fs::error::noent;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!node->metadata.is_directory)
|
||||
{
|
||||
// fs::dir::open -> ::readdir should return ENOTDIR when path is
|
||||
// pointing to a file instead of a folder, which translates to error::unknown.
|
||||
// doing the same here.
|
||||
fs::g_tls_error = fs::error::unknown;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return std::make_unique<iso_dir>(*node);
|
||||
}
|
||||
|
||||
void iso_dir::rewind()
|
||||
{
|
||||
m_pos = 0;
|
||||
}
|
||||
|
||||
void load_iso(const std::string& path)
|
||||
{
|
||||
fs::set_virtual_device("iso_overlay_fs_dev",
|
||||
stx::make_shared<iso_device>(path));
|
||||
}
|
||||
|
||||
void unload_iso()
|
||||
{
|
||||
fs::set_virtual_device("iso_overlay_fs_dev",
|
||||
stx::shared_ptr<iso_device>());
|
||||
}
|
||||
113
rpcs3/Loader/ISO.h
Normal file
113
rpcs3/Loader/ISO.h
Normal file
@ -0,0 +1,113 @@
|
||||
#pragma once
|
||||
|
||||
#include "Utilities/File.h"
|
||||
#include "util/types.hpp"
|
||||
|
||||
bool is_file_iso(const std::string& path);
|
||||
bool is_file_iso(const fs::file& path);
|
||||
|
||||
void load_iso(const std::string& path);
|
||||
void unload_iso();
|
||||
|
||||
struct iso_extent_info
|
||||
{
|
||||
u64 start;
|
||||
u64 size;
|
||||
};
|
||||
|
||||
struct iso_fs_metadata
|
||||
{
|
||||
std::string name;
|
||||
s64 time;
|
||||
bool is_directory;
|
||||
bool has_multiple_extents;
|
||||
std::vector<iso_extent_info> extents;
|
||||
|
||||
u64 size() const;
|
||||
};
|
||||
|
||||
struct iso_fs_node
|
||||
{
|
||||
iso_fs_metadata metadata;
|
||||
std::vector<std::unique_ptr<iso_fs_node>> children;
|
||||
};
|
||||
|
||||
class iso_file : public fs::file_base
|
||||
{
|
||||
fs::file m_file;
|
||||
iso_fs_metadata m_meta;
|
||||
u64 m_pos;
|
||||
|
||||
std::pair<u64, iso_extent_info> get_extent_pos(u64 pos) const;
|
||||
u64 file_offset(u64 pos) const;
|
||||
u64 local_extent_remaining(u64 pos) const;
|
||||
u64 local_extent_size(u64 pos) const;
|
||||
|
||||
public:
|
||||
iso_file(fs::file&& iso_handle, const iso_fs_node& node);
|
||||
|
||||
fs::stat_t get_stat() override;
|
||||
bool trunc(u64 length) override;
|
||||
u64 read(void* buffer, u64 size) override;
|
||||
u64 read_at(u64 offset, void* buffer, u64 size) override;
|
||||
u64 write(const void* buffer, u64 size) override;
|
||||
u64 seek(s64 offset, fs::seek_mode whence) override;
|
||||
u64 size() override;
|
||||
|
||||
void release() override;
|
||||
};
|
||||
|
||||
class iso_dir : public fs::dir_base
|
||||
{
|
||||
const iso_fs_node& m_node;
|
||||
u64 m_pos;
|
||||
|
||||
public:
|
||||
iso_dir(const iso_fs_node& node)
|
||||
: m_node(node), m_pos(0)
|
||||
{}
|
||||
|
||||
bool read(fs::dir_entry&) override;
|
||||
void rewind() override;
|
||||
};
|
||||
|
||||
// represents the .iso file itself.
|
||||
class iso_archive
|
||||
{
|
||||
std::string m_path;
|
||||
iso_fs_node m_root;
|
||||
fs::file m_file;
|
||||
|
||||
public:
|
||||
iso_archive(const std::string& path);
|
||||
|
||||
iso_fs_node* retrieve(const std::string& path);
|
||||
bool exists(const std::string& path);
|
||||
bool is_file(const std::string& path);
|
||||
|
||||
iso_file open(const std::string& path);
|
||||
};
|
||||
|
||||
class iso_device : public fs::device_base
|
||||
{
|
||||
iso_archive m_archive;
|
||||
std::string iso_path;
|
||||
|
||||
public:
|
||||
inline static std::string virtual_device_name = "/vfsv0_virtual_iso_overlay_fs_dev";
|
||||
|
||||
iso_device(const std::string& iso_path, const std::string& device_name = virtual_device_name)
|
||||
: m_archive(iso_path), iso_path(iso_path)
|
||||
{
|
||||
fs_prefix = device_name;
|
||||
}
|
||||
~iso_device() override = default;
|
||||
|
||||
const std::string& get_loaded_iso() const { return iso_path; }
|
||||
|
||||
bool stat(const std::string& path, fs::stat_t& info) override;
|
||||
bool statfs(const std::string& path, fs::device_stat& info) override;
|
||||
|
||||
std::unique_ptr<fs::file_base> open(const std::string& path, bs_t<fs::open_mode> mode) override;
|
||||
std::unique_ptr<fs::dir_base> open_dir(const std::string& path) override;
|
||||
};
|
||||
@ -538,6 +538,7 @@
|
||||
<ClCompile Include="Loader\PSF.cpp" />
|
||||
<ClCompile Include="Loader\PUP.cpp" />
|
||||
<ClCompile Include="Loader\TAR.cpp" />
|
||||
<ClCompile Include="Loader\ISO.cpp" />
|
||||
<ClCompile Include="Loader\mself.cpp" />
|
||||
<ClCompile Include="Loader\TROPUSR.cpp" />
|
||||
<ClCompile Include="Loader\TRP.cpp" />
|
||||
@ -1017,6 +1018,7 @@
|
||||
<ClInclude Include="Loader\PSF.h" />
|
||||
<ClInclude Include="Loader\PUP.h" />
|
||||
<ClInclude Include="Loader\TAR.h" />
|
||||
<ClInclude Include="Loader\ISO.h" />
|
||||
<ClInclude Include="Loader\TROPUSR.h" />
|
||||
<ClInclude Include="Loader\TRP.h" />
|
||||
<ClInclude Include="rpcs3_version.h" />
|
||||
@ -1092,4 +1094,4 @@
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
#include "stdafx.h"
|
||||
#include "game_list_base.h"
|
||||
|
||||
#include "Loader/ISO.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QPainter>
|
||||
|
||||
@ -50,7 +52,20 @@ void game_list_base::IconLoadFunction(game_info game, qreal device_pixel_ratio,
|
||||
|
||||
if (game->icon.isNull() && (game->info.icon_path.empty() || !game->icon.load(QString::fromStdString(game->info.icon_path))))
|
||||
{
|
||||
if (game_list_log.warning)
|
||||
if (game->icon_in_archive)
|
||||
{
|
||||
iso_archive archive(game->info.path);
|
||||
auto icon_file = archive.open(game->info.icon_path);
|
||||
auto icon_size = icon_file.size();
|
||||
QByteArray data(icon_size, 0);
|
||||
icon_file.read(data.data(), icon_size);
|
||||
QImage iconImage;
|
||||
if (iconImage.loadFromData(data))
|
||||
{
|
||||
game->icon = QPixmap::fromImage(iconImage);
|
||||
}
|
||||
}
|
||||
else if (game_list_log.warning)
|
||||
{
|
||||
bool logged = false;
|
||||
{
|
||||
|
||||
@ -18,6 +18,7 @@
|
||||
#include "Emu/vfs_config.h"
|
||||
#include "Emu/system_utils.hpp"
|
||||
#include "Loader/PSF.h"
|
||||
#include "Loader/ISO.h"
|
||||
#include "util/types.hpp"
|
||||
#include "Utilities/File.h"
|
||||
#include "util/sysinfo.hpp"
|
||||
@ -634,13 +635,46 @@ void game_list_frame::OnParsingFinished()
|
||||
|
||||
const auto add_game = [this, localized_title, localized_icon, localized_movie, dev_flash, cat_unknown_localized = localized.category.unknown.toStdString(), cat_unknown = cat::cat_unknown.toStdString(), game_icon_path, _hdd, play_hover_movies = m_play_hover_movies, show_custom_icons = m_show_custom_icons](const std::string& dir_or_elf)
|
||||
{
|
||||
const auto load_game_psf = [&dir_or_elf](const std::string& sfo_path, const std::unique_ptr<iso_archive>& archive)
|
||||
{
|
||||
if (!archive) return psf::load_object(sfo_path);
|
||||
|
||||
// HACK: psf does not accept a file_base argument,
|
||||
// so we are creating a dummy fs:file and replacing the internal file_base handle with an iso_file
|
||||
// instead.
|
||||
fs::file psf_file(sfo_path);
|
||||
psf_file.reset(std::make_unique<iso_file>(fs::file(dir_or_elf), *archive->retrieve(sfo_path)));
|
||||
|
||||
return psf::load_object(psf_file, sfo_path);
|
||||
};
|
||||
|
||||
std::unique_ptr<iso_archive> archive;
|
||||
if (is_file_iso(dir_or_elf))
|
||||
{
|
||||
archive = std::make_unique<iso_archive>(dir_or_elf);
|
||||
}
|
||||
|
||||
const auto file_exists = [&archive](const std::string& path)
|
||||
{
|
||||
if (!archive) return fs::is_file(path);
|
||||
return archive->is_file(path);
|
||||
};
|
||||
|
||||
gui_game_info game{};
|
||||
game.info.path = dir_or_elf;
|
||||
|
||||
if (archive)
|
||||
{
|
||||
fs::stat_t iso_stat;
|
||||
fs::get_stat(game.info.path, iso_stat);
|
||||
|
||||
game.info.size_on_disk = iso_stat.size;
|
||||
}
|
||||
|
||||
const Localized thread_localized;
|
||||
|
||||
const std::string sfo_dir = rpcs3::utils::get_sfo_dir_from_game_path(dir_or_elf);
|
||||
const psf::registry psf = psf::load_object(sfo_dir + "/PARAM.SFO");
|
||||
const std::string sfo_dir = !archive ? rpcs3::utils::get_sfo_dir_from_game_path(dir_or_elf) : "PS3_GAME";
|
||||
const psf::registry psf = load_game_psf(sfo_dir + "/PARAM.SFO", archive);
|
||||
const std::string_view title_id = psf::get_string(psf, "TITLE_ID", "");
|
||||
|
||||
if (title_id.empty())
|
||||
@ -708,7 +742,7 @@ void game_list_frame::OnParsingFinished()
|
||||
|
||||
if (game.info.icon_path.empty())
|
||||
{
|
||||
if (std::string icon_path = sfo_dir + "/" + localized_icon; fs::is_file(icon_path))
|
||||
if (std::string icon_path = sfo_dir + "/" + localized_icon; file_exists(icon_path))
|
||||
{
|
||||
game.info.icon_path = std::move(icon_path);
|
||||
}
|
||||
@ -716,19 +750,26 @@ void game_list_frame::OnParsingFinished()
|
||||
{
|
||||
game.info.icon_path = sfo_dir + "/ICON0.PNG";
|
||||
}
|
||||
|
||||
if (!game.info.icon_path.empty() && archive)
|
||||
{
|
||||
if (!archive->exists(game.info.icon_path)) return;
|
||||
|
||||
game.icon_in_archive = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (std::string movie_path = game_icon_path + game.info.serial + "/hover.gif"; fs::is_file(movie_path))
|
||||
if (std::string movie_path = game_icon_path + game.info.serial + "/hover.gif"; file_exists(movie_path))
|
||||
{
|
||||
game.info.movie_path = std::move(movie_path);
|
||||
game.has_hover_gif = true;
|
||||
}
|
||||
else if (std::string movie_path = sfo_dir + "/" + localized_movie; fs::is_file(movie_path))
|
||||
else if (std::string movie_path = sfo_dir + "/" + localized_movie; file_exists(movie_path))
|
||||
{
|
||||
game.info.movie_path = std::move(movie_path);
|
||||
game.has_hover_pam = true;
|
||||
}
|
||||
else if (std::string movie_path = sfo_dir + "/ICON1.PAM"; fs::is_file(movie_path))
|
||||
else if (std::string movie_path = sfo_dir + "/ICON1.PAM"; file_exists(movie_path))
|
||||
{
|
||||
game.info.movie_path = std::move(movie_path);
|
||||
game.has_hover_pam = true;
|
||||
@ -853,6 +894,10 @@ void game_list_frame::OnParsingFinished()
|
||||
|
||||
add_disc_dir(entry.path, legit_paths);
|
||||
}
|
||||
else if (is_file_iso(entry.path))
|
||||
{
|
||||
push_path(entry.path, legit_paths);
|
||||
}
|
||||
else
|
||||
{
|
||||
game_list_log.trace("Invalid game path registered: %s", entry.path);
|
||||
|
||||
@ -11,6 +11,8 @@
|
||||
#include "Emu/vfs_config.h"
|
||||
#include "Utilities/StrUtil.h"
|
||||
|
||||
#include "Loader/ISO.h"
|
||||
|
||||
#include <QHeaderView>
|
||||
#include <QScrollBar>
|
||||
#include <QStringBuilder>
|
||||
@ -242,6 +244,12 @@ void game_list_table::populate(
|
||||
custom_table_widget_item* icon_item = new custom_table_widget_item;
|
||||
game->item = icon_item;
|
||||
|
||||
if (is_file_iso(game->info.path) && !game->info.movie_path.empty()
|
||||
&& !fs::exists(game->info.movie_path))
|
||||
{
|
||||
icon_item->set_source_path(game->info.path);
|
||||
}
|
||||
|
||||
icon_item->set_image_change_callback([this, icon_item, game](const QVideoFrame& frame)
|
||||
{
|
||||
if (!icon_item || !game)
|
||||
|
||||
@ -20,6 +20,7 @@ struct gui_game_info
|
||||
bool has_custom_icon = false;
|
||||
bool has_hover_gif = false;
|
||||
bool has_hover_pam = false;
|
||||
bool icon_in_archive = false;
|
||||
movie_item_base* item = nullptr;
|
||||
|
||||
// Returns the visible version string in the game list
|
||||
|
||||
@ -568,6 +568,7 @@ void main_window::BootElf()
|
||||
"SELF files (EBOOT.BIN *.self);;"
|
||||
"BOOT files (*BOOT.BIN);;"
|
||||
"BIN files (*.bin);;"
|
||||
"ISO files (*.iso);;"
|
||||
"All executable files (*.SAVESTAT.zst *.SAVESTAT.gz *.SAVESTAT *.sprx *.SPRX *.self *.SELF *.bin *.BIN *.prx *.PRX *.elf *.ELF *.o *.O);;"
|
||||
"All files (*.*)"),
|
||||
Q_NULLPTR, QFileDialog::DontResolveSymlinks);
|
||||
@ -4122,7 +4123,7 @@ main_window::drop_type main_window::IsValidFile(const QMimeData& md, QStringList
|
||||
type = drop_type::drop_rrc;
|
||||
}
|
||||
// The emulator allows to execute ANY filetype, just not from drag-and-drop because it is confusing to users
|
||||
else if (path.toLower().endsWith(".savestat.gz") || path.toLower().endsWith(".savestat.zst") || suffix_lo == "savestat" || suffix_lo == "sprx" || suffix_lo == "self" || suffix_lo == "bin" || suffix_lo == "prx" || suffix_lo == "elf" || suffix_lo == "o")
|
||||
else if (path.toLower().endsWith(".savestat.gz") || path.toLower().endsWith(".savestat.zst") || suffix_lo == "savestat" || suffix_lo == "sprx" || suffix_lo == "self" || suffix_lo == "bin" || suffix_lo == "prx" || suffix_lo == "elf" || suffix_lo == "o" || suffix_lo == "iso")
|
||||
{
|
||||
type = drop_type::drop_game;
|
||||
}
|
||||
|
||||
@ -2,6 +2,8 @@
|
||||
#include "Emu/System.h"
|
||||
#include "qt_video_source.h"
|
||||
|
||||
#include "Loader/ISO.h"
|
||||
|
||||
#include <QFile>
|
||||
|
||||
qt_video_source::qt_video_source()
|
||||
@ -19,6 +21,11 @@ void qt_video_source::set_video_path(const std::string& video_path)
|
||||
m_video_path = QString::fromStdString(video_path);
|
||||
}
|
||||
|
||||
void qt_video_source::set_source_path(const std::string& source_path)
|
||||
{
|
||||
m_source_path = QString::fromStdString(source_path);
|
||||
}
|
||||
|
||||
void qt_video_source::set_active(bool active)
|
||||
{
|
||||
if (m_active.exchange(active) == active) return;
|
||||
@ -55,7 +62,7 @@ void qt_video_source::init_movie()
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_image_change_callback || m_video_path.isEmpty() || !QFile::exists(m_video_path))
|
||||
if (!m_image_change_callback || m_video_path.isEmpty() || (!QFile::exists(m_video_path) && m_source_path.isEmpty()))
|
||||
{
|
||||
m_video_path.clear();
|
||||
return;
|
||||
@ -65,8 +72,25 @@ void qt_video_source::init_movie()
|
||||
|
||||
if (lower.endsWith(".gif"))
|
||||
{
|
||||
m_movie = std::make_unique<QMovie>(m_video_path);
|
||||
m_video_path.clear();
|
||||
if (m_source_path.isEmpty())
|
||||
{
|
||||
m_movie = std::make_unique<QMovie>(m_video_path);
|
||||
m_video_path.clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
iso_archive archive(m_source_path.toStdString());
|
||||
auto movie_file = archive.open(m_video_path.toStdString());
|
||||
auto movie_size = movie_file.size();
|
||||
m_video_data = QByteArray(movie_size, 0);
|
||||
movie_file.read(m_video_data.data(), movie_size);
|
||||
|
||||
QBuffer buffer(&m_video_data);
|
||||
buffer.open(QIODevice::ReadOnly);
|
||||
m_movie = std::make_unique<QMovie>(&buffer);
|
||||
|
||||
m_video_path.clear();
|
||||
}
|
||||
|
||||
if (!m_movie->isValid())
|
||||
{
|
||||
@ -85,17 +109,30 @@ void qt_video_source::init_movie()
|
||||
if (lower.endsWith(".pam"))
|
||||
{
|
||||
// We can't set PAM files as source of the video player, so we have to feed them as raw data.
|
||||
QFile file(m_video_path);
|
||||
if (!file.open(QFile::OpenModeFlag::ReadOnly))
|
||||
if (m_source_path.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
QFile file(m_video_path);
|
||||
if (!file.open(QFile::OpenModeFlag::ReadOnly))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Decode the pam properly before pushing it to the player
|
||||
m_video_data = file.readAll();
|
||||
if (m_video_data.isEmpty())
|
||||
// TODO: Decode the pam properly before pushing it to the player
|
||||
m_video_data = file.readAll();
|
||||
if (m_video_data.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
auto source_path = m_source_path.toStdString();
|
||||
auto video_path = m_video_path.toStdString();
|
||||
iso_archive archive(source_path.c_str());
|
||||
auto movie_file = archive.open(video_path);
|
||||
auto movie_size = movie_file.size();
|
||||
m_video_data = QByteArray(movie_size, 0);
|
||||
movie_file.read(m_video_data.data(), movie_size);
|
||||
}
|
||||
|
||||
m_video_buffer = std::make_unique<QBuffer>(&m_video_data);
|
||||
|
||||
@ -17,6 +17,7 @@ public:
|
||||
qt_video_source();
|
||||
virtual ~qt_video_source();
|
||||
|
||||
void set_source_path(const std::string& source_path);
|
||||
void set_video_path(const std::string& video_path) override;
|
||||
const QString& video_path() const { return m_video_path; }
|
||||
|
||||
@ -43,6 +44,7 @@ protected:
|
||||
atomic_t<bool> m_has_new = false;
|
||||
|
||||
QString m_video_path;
|
||||
QString m_source_path; // path of the source archive
|
||||
QByteArray m_video_data{};
|
||||
QImage m_image{};
|
||||
std::vector<u8> m_image_path;
|
||||
|
||||
@ -7,6 +7,8 @@
|
||||
#include "gui_settings.h"
|
||||
#include "progress_dialog.h"
|
||||
|
||||
#include "Loader/ISO.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QClipboard>
|
||||
#include <QScrollBar>
|
||||
@ -208,7 +210,7 @@ savestate_manager_dialog::savestate_manager_dialog(std::shared_ptr<gui_settings>
|
||||
m_savestate_table->create_header_actions(m_savestate_column_acts,
|
||||
[this](int col) { return m_gui_settings->GetSavestateListColVisibility(static_cast<gui::savestate_list_columns>(col)); },
|
||||
[this](int col, bool visible) { m_gui_settings->SetSavestateListColVisibility(static_cast<gui::savestate_list_columns>(col), visible); });
|
||||
|
||||
|
||||
m_game_table->create_header_actions(m_game_column_acts,
|
||||
[this](int col) { return m_gui_settings->GetSavestateGamelistColVisibility(static_cast<gui::savestate_game_list_columns>(col)); },
|
||||
[this](int col, bool visible) { m_gui_settings->SetSavestateGamelistColVisibility(static_cast<gui::savestate_game_list_columns>(col), visible); });
|
||||
@ -417,8 +419,9 @@ void savestate_manager_dialog::ResizeGameIcons()
|
||||
const qreal dpr = devicePixelRatioF();
|
||||
const int savestate_index = item->data(GameUserRole::GameIndex).toInt();
|
||||
const std::string icon_path = m_savestate_db[savestate_index]->game_icon_path;
|
||||
const std::string archive_path = m_savestate_db[savestate_index]->archive_path;
|
||||
|
||||
item->set_icon_load_func([this, icon_path, savestate_index, cancel = item->icon_loading_aborted(), dpr](int index)
|
||||
item->set_icon_load_func([this, icon_path, archive_path, savestate_index, cancel = item->icon_loading_aborted(), dpr](int index)
|
||||
{
|
||||
if (cancel && cancel->load())
|
||||
{
|
||||
@ -445,6 +448,20 @@ void savestate_manager_dialog::ResizeGameIcons()
|
||||
}
|
||||
}
|
||||
|
||||
if (!archive_path.empty())
|
||||
{
|
||||
iso_archive archive(archive_path);
|
||||
auto icon_file = archive.open(icon_path);
|
||||
auto icon_size = icon_file.size();
|
||||
QByteArray data(icon_size, 0);
|
||||
icon_file.read(data.data(), icon_size);
|
||||
QImage iconImage;
|
||||
if (iconImage.loadFromData(data))
|
||||
{
|
||||
icon = QPixmap::fromImage(iconImage);
|
||||
}
|
||||
}
|
||||
|
||||
if (cancel && cancel->load())
|
||||
{
|
||||
return;
|
||||
@ -617,6 +634,11 @@ void savestate_manager_dialog::StartSavestateLoadThreads()
|
||||
{
|
||||
game_data_ptr->game_name = gameinfo->info.name;
|
||||
game_data_ptr->game_icon_path = gameinfo->info.icon_path;
|
||||
if (gameinfo->icon_in_archive)
|
||||
{
|
||||
game_data_ptr->archive_path = gameinfo->info.path;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -48,6 +48,7 @@ private:
|
||||
std::string game_name;
|
||||
std::string game_icon_path;
|
||||
std::string dir_path;
|
||||
std::string archive_path;
|
||||
};
|
||||
|
||||
bool LoadSavestateFolderToDB(std::unique_ptr<game_savestates_data>&& game_savestates);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user