mirror of
https://github.com/RPCS3/rpcs3.git
synced 2026-03-10 23:45:16 +01:00
Merge branch 'master' into screenshot
This commit is contained in:
commit
81be51f01a
2
3rdparty/ffmpeg
vendored
2
3rdparty/ffmpeg
vendored
|
|
@ -1 +1 @@
|
|||
Subproject commit ec6367d3ba9d0d57b9d22d4b87da8144acaf428f
|
||||
Subproject commit ce81114ed99e5510f6cd983f59a1eac9f33bb73c
|
||||
|
|
@ -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();
|
||||
|
|
@ -257,6 +257,8 @@ namespace fs
|
|||
// Open file with specified mode
|
||||
explicit file(const std::string& path, bs_t<open_mode> mode = ::fs::read);
|
||||
|
||||
file(std::unique_ptr<file_base>&& ptr) : m_file(std::move(ptr)) {}
|
||||
|
||||
static file from_native_handle(native_handle handle);
|
||||
|
||||
// Open memory for read
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
|
|
|
|||
|
|
@ -211,6 +211,7 @@ struct vdec_context final
|
|||
lf_queue<vdec_cmd> in_cmd;
|
||||
|
||||
AVRational log_time_base{}; // Used to reduce log spam
|
||||
AVRational log_framerate{}; // Used to reduce log spam
|
||||
|
||||
vdec_context(s32 type, u32 /*profile*/, u32 addr, u32 size, vm::ptr<CellVdecCbMsg> func, u32 arg)
|
||||
: type(type)
|
||||
|
|
@ -292,6 +293,19 @@ struct vdec_context final
|
|||
sws_freeContext(sws);
|
||||
}
|
||||
|
||||
static u32 freq_to_framerate_code(f64 freq)
|
||||
{
|
||||
if (std::abs(freq - 23.976) < 0.002) return CELL_VDEC_FRC_24000DIV1001;
|
||||
if (std::abs(freq - 24.000) < 0.001) return CELL_VDEC_FRC_24;
|
||||
if (std::abs(freq - 25.000) < 0.001) return CELL_VDEC_FRC_25;
|
||||
if (std::abs(freq - 29.970) < 0.002) return CELL_VDEC_FRC_30000DIV1001;
|
||||
if (std::abs(freq - 30.000) < 0.001) return CELL_VDEC_FRC_30;
|
||||
if (std::abs(freq - 50.000) < 0.001) return CELL_VDEC_FRC_50;
|
||||
if (std::abs(freq - 59.940) < 0.002) return CELL_VDEC_FRC_60000DIV1001;
|
||||
if (std::abs(freq - 60.000) < 0.001) return CELL_VDEC_FRC_60;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void exec(ppu_thread& ppu, u32 vid)
|
||||
{
|
||||
perf_meter<"VDEC"_u32> perf0;
|
||||
|
|
@ -341,6 +355,7 @@ struct vdec_context final
|
|||
|
||||
out_queue.clear(); // Flush image queue
|
||||
log_time_base = {};
|
||||
log_framerate = {};
|
||||
|
||||
frc_set = 0; // TODO: ???
|
||||
next_pts = 0;
|
||||
|
|
@ -471,10 +486,10 @@ struct vdec_context final
|
|||
frame.userdata = au_usrd;
|
||||
frame.attr = attr;
|
||||
|
||||
u64 amend = 0;
|
||||
|
||||
if (frc_set)
|
||||
{
|
||||
u64 amend = 0;
|
||||
|
||||
switch (frc_set)
|
||||
{
|
||||
case CELL_VDEC_FRC_24000DIV1001: amend = 1001 * 90000 / 24000; break;
|
||||
|
|
@ -491,62 +506,45 @@ struct vdec_context final
|
|||
}
|
||||
}
|
||||
|
||||
next_pts += amend;
|
||||
next_dts += amend;
|
||||
frame.frc = frc_set;
|
||||
}
|
||||
else if (ctx->time_base.num == 0)
|
||||
else if (ctx->time_base.den && ctx->time_base.num)
|
||||
{
|
||||
if (log_time_base.den != ctx->time_base.den || log_time_base.num != ctx->time_base.num)
|
||||
const auto freq = 1. * ctx->time_base.den / ctx->time_base.num / ticks_per_frame;
|
||||
|
||||
frame.frc = freq_to_framerate_code(freq);
|
||||
if (frame.frc)
|
||||
{
|
||||
cellVdec.error("time_base.num is 0 (handle=0x%x, seq_id=%d, cmd_id=%d, %d/%d, tpf=%d framerate=%d/%d)", handle, cmd->seq_id, cmd->id, ctx->time_base.num, ctx->time_base.den, ticks_per_frame, ctx->framerate.num, ctx->framerate.den);
|
||||
amend = u64{90000} * ctx->time_base.num * ticks_per_frame / ctx->time_base.den;
|
||||
}
|
||||
}
|
||||
else if (ctx->framerate.den && ctx->framerate.num)
|
||||
{
|
||||
const auto freq = ctx->framerate.num / static_cast<f64>(ctx->framerate.den);
|
||||
|
||||
frame.frc = freq_to_framerate_code(freq);
|
||||
if (frame.frc)
|
||||
{
|
||||
amend = u64{90000} * ctx->framerate.den / ctx->framerate.num;
|
||||
}
|
||||
}
|
||||
|
||||
if (amend == 0 || frame.frc == 0)
|
||||
{
|
||||
if (log_time_base.den != ctx->time_base.den || log_time_base.num != ctx->time_base.num || log_framerate.den != ctx->framerate.den || log_framerate.num != ctx->framerate.num)
|
||||
{
|
||||
cellVdec.error("Invalid frequency (handle=0x%x, seq_id=%d, cmd_id=%d, timebase=%d/%d, tpf=%d framerate=%d/%d)", handle, cmd->seq_id, cmd->id, ctx->time_base.num, ctx->time_base.den, ticks_per_frame, ctx->framerate.num, ctx->framerate.den);
|
||||
log_time_base = ctx->time_base;
|
||||
log_framerate = ctx->framerate;
|
||||
}
|
||||
|
||||
// Hack
|
||||
const u64 amend = u64{90000} / 30;
|
||||
amend = u64{90000} / 30;
|
||||
frame.frc = CELL_VDEC_FRC_30;
|
||||
next_pts += amend;
|
||||
next_dts += amend;
|
||||
}
|
||||
else
|
||||
{
|
||||
u64 amend = u64{90000} * ctx->time_base.num * ticks_per_frame / ctx->time_base.den;
|
||||
const auto freq = 1. * ctx->time_base.den / ctx->time_base.num / ticks_per_frame;
|
||||
|
||||
if (std::abs(freq - 23.976) < 0.002)
|
||||
frame.frc = CELL_VDEC_FRC_24000DIV1001;
|
||||
else if (std::abs(freq - 24.000) < 0.001)
|
||||
frame.frc = CELL_VDEC_FRC_24;
|
||||
else if (std::abs(freq - 25.000) < 0.001)
|
||||
frame.frc = CELL_VDEC_FRC_25;
|
||||
else if (std::abs(freq - 29.970) < 0.002)
|
||||
frame.frc = CELL_VDEC_FRC_30000DIV1001;
|
||||
else if (std::abs(freq - 30.000) < 0.001)
|
||||
frame.frc = CELL_VDEC_FRC_30;
|
||||
else if (std::abs(freq - 50.000) < 0.001)
|
||||
frame.frc = CELL_VDEC_FRC_50;
|
||||
else if (std::abs(freq - 59.940) < 0.002)
|
||||
frame.frc = CELL_VDEC_FRC_60000DIV1001;
|
||||
else if (std::abs(freq - 60.000) < 0.001)
|
||||
frame.frc = CELL_VDEC_FRC_60;
|
||||
else
|
||||
{
|
||||
if (log_time_base.den != ctx->time_base.den || log_time_base.num != ctx->time_base.num)
|
||||
{
|
||||
// 1/1000 usually means that the time stamps are written in 1ms units and that the frame rate may vary.
|
||||
cellVdec.error("Unsupported time_base (handle=0x%x, seq_id=%d, cmd_id=%d, %d/%d, tpf=%d framerate=%d/%d)", handle, cmd->seq_id, cmd->id, ctx->time_base.num, ctx->time_base.den, ticks_per_frame, ctx->framerate.num, ctx->framerate.den);
|
||||
log_time_base = ctx->time_base;
|
||||
}
|
||||
|
||||
// Hack
|
||||
amend = u64{90000} / 30;
|
||||
frame.frc = CELL_VDEC_FRC_30;
|
||||
}
|
||||
|
||||
next_pts += amend;
|
||||
next_dts += amend;
|
||||
}
|
||||
next_pts += amend;
|
||||
next_dts += amend;
|
||||
|
||||
cellVdec.trace("Got picture (handle=0x%x, seq_id=%d, cmd_id=%d, pts=0x%llx[0x%llx], dts=0x%llx[0x%llx])", handle, cmd->seq_id, cmd->id, frame.pts, frame->pts, frame.dts, frame->pkt_dts);
|
||||
|
||||
|
|
|
|||
|
|
@ -219,7 +219,7 @@ pad_capabilities PadHandlerBase::get_capabilities(const std::string& /*pad_id*/)
|
|||
.has_rumble = b_has_rumble,
|
||||
.has_accel = b_has_motion,
|
||||
.has_gyro = b_has_motion,
|
||||
.has_pressure_sensitivity = b_has_pressure_intensity_button
|
||||
.has_pressure_intensity_button = b_has_pressure_intensity_button
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ struct pad_capabilities
|
|||
bool has_rumble = false;
|
||||
bool has_accel = false;
|
||||
bool has_gyro = false;
|
||||
bool has_pressure_sensitivity = false;
|
||||
bool has_pressure_intensity_button = true;
|
||||
};
|
||||
|
||||
using pad_preview_values = std::array<int, 6>;
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ namespace rsx
|
|||
|
||||
private:
|
||||
// Members
|
||||
block_list *block;
|
||||
block_list* block = nullptr;
|
||||
list_iterator list_it = {};
|
||||
size_type idx = u32{umax};
|
||||
size_type array_idx = 0;
|
||||
|
|
@ -705,9 +705,9 @@ namespace rsx
|
|||
private:
|
||||
// Members
|
||||
address_range32 range;
|
||||
section_bounds bounds;
|
||||
section_bounds bounds {};
|
||||
|
||||
block_type *block = nullptr;
|
||||
block_type* block = nullptr;
|
||||
bool needs_overlap_check = true;
|
||||
bool unowned_remaining = false;
|
||||
unowned_iterator unowned_it = {};
|
||||
|
|
|
|||
|
|
@ -218,7 +218,7 @@ void GLGSRender::init_buffers(rsx::framebuffer_creation_context context, bool /*
|
|||
static_cast<gl::framebuffer_holder*>(m_draw_fbo)->release();
|
||||
}
|
||||
|
||||
for (auto &fbo : m_framebuffer_cache)
|
||||
for (auto& fbo : m_framebuffer_cache)
|
||||
{
|
||||
if (fbo.matches(color_targets, depth_stencil_target))
|
||||
{
|
||||
|
|
@ -264,6 +264,8 @@ void GLGSRender::init_buffers(rsx::framebuffer_creation_context context, bool /*
|
|||
}
|
||||
}
|
||||
|
||||
ensure(m_draw_fbo);
|
||||
|
||||
switch (rsx::method_registers.surface_color_target())
|
||||
{
|
||||
case rsx::surface_target::none: break;
|
||||
|
|
|
|||
|
|
@ -289,6 +289,8 @@ namespace gl
|
|||
void* copy_image_to_buffer(gl::command_context& cmd, const pixel_buffer_layout& pack_info, const gl::texture* src, gl::buffer* dst,
|
||||
u32 dst_offset, const int src_level, const coord3u& src_region, image_memory_requirements* mem_info)
|
||||
{
|
||||
ensure(src && dst);
|
||||
|
||||
auto initialize_scratch_mem = [&]() -> bool // skip_transform
|
||||
{
|
||||
const u64 max_mem = (mem_info->memory_required) ? mem_info->memory_required : mem_info->image_size_in_bytes;
|
||||
|
|
|
|||
|
|
@ -669,8 +669,8 @@ namespace gl
|
|||
}
|
||||
else
|
||||
{
|
||||
//TODO: More tests on byte order
|
||||
//ARGB8+native+unswizzled is confirmed with Dark Souls II character preview
|
||||
// TODO: More tests on byte order
|
||||
// ARGB8+native+unswizzled is confirmed with Dark Souls II character preview
|
||||
switch (gcm_format)
|
||||
{
|
||||
case CELL_GCM_TEXTURE_A8R8G8B8:
|
||||
|
|
@ -697,8 +697,7 @@ namespace gl
|
|||
fmt::throw_exception("Unexpected gcm format 0x%X", gcm_format);
|
||||
}
|
||||
|
||||
//NOTE: Protection is handled by the caller
|
||||
cached.set_dimensions(width, height, depth, (rsx_range.length() / height));
|
||||
// NOTE: Protection is handled by the caller
|
||||
no_access_range = cached.get_min_max(no_access_range, rsx::section_bounds::locked_range);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ namespace gl
|
|||
class shader
|
||||
{
|
||||
std::string source;
|
||||
::glsl::program_domain type;
|
||||
::glsl::program_domain type {};
|
||||
GLuint m_id = GL_NONE;
|
||||
|
||||
fence m_compiled_fence;
|
||||
|
|
|
|||
|
|
@ -376,15 +376,12 @@ namespace gl
|
|||
|
||||
GLuint get_bound_texture(GLuint layer, GLenum target)
|
||||
{
|
||||
ensure(layer < 48);
|
||||
return bound_textures[layer][target];
|
||||
return ::at32(bound_textures, layer)[target];
|
||||
}
|
||||
|
||||
void bind_texture(GLuint layer, GLenum target, GLuint name, GLboolean force = GL_FALSE)
|
||||
{
|
||||
ensure(layer < 48);
|
||||
|
||||
auto& bound = bound_textures[layer][target];
|
||||
auto& bound = ::at32(bound_textures, layer)[target];
|
||||
if (bound != name || force)
|
||||
{
|
||||
glActiveTexture(GL_TEXTURE0 + layer);
|
||||
|
|
|
|||
|
|
@ -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"))
|
||||
|
|
@ -931,6 +932,7 @@ game_boot_result Emulator::BootGame(const std::string& path, const std::string&
|
|||
|
||||
if (result != game_boot_result::no_errors)
|
||||
{
|
||||
unload_iso();
|
||||
GetCallbacks().close_gs_frame();
|
||||
}
|
||||
}
|
||||
|
|
@ -1078,6 +1080,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();
|
||||
|
|
@ -1169,12 +1172,16 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch,
|
|||
std::string disc_info;
|
||||
m_ar->serialize(argv.emplace_back(), disc_info, klic.emplace_back(), m_game_dir, hdd1);
|
||||
|
||||
launching_from_disc_archive = is_file_iso(disc_info);
|
||||
|
||||
sys_log.notice("Savestate: is iso archive = %d ('%s')", launching_from_disc_archive, disc_info);
|
||||
|
||||
if (!klic[0])
|
||||
{
|
||||
klic.clear();
|
||||
}
|
||||
|
||||
if (!disc_info.empty() && disc_info[0] != '/')
|
||||
if (!launching_from_disc_archive && !disc_info.empty() && disc_info[0] != '/')
|
||||
{
|
||||
// Restore disc path for disc games (must exist in games.yml i.e. your game library)
|
||||
m_title_id = disc_info;
|
||||
|
|
@ -1182,6 +1189,11 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch,
|
|||
// Load /dev_bdvd/ from game list if available
|
||||
if (std::string game_path = m_games_config.get_path(m_title_id); !game_path.empty())
|
||||
{
|
||||
if (is_file_iso(game_path))
|
||||
{
|
||||
game_path = iso_device::virtual_device_name + "/PS3_GAME/./";
|
||||
}
|
||||
|
||||
if (game_path.ends_with("/./"))
|
||||
{
|
||||
// Marked as PS3_GAME directory
|
||||
|
|
@ -1286,6 +1298,16 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch,
|
|||
|
||||
m_path_old = m_path;
|
||||
resolve_path_as_vfs_path = true;
|
||||
|
||||
if (launching_from_disc_archive)
|
||||
{
|
||||
sys_log.notice("Savestate: Loading iso archive");
|
||||
|
||||
load_iso(disc_info);
|
||||
m_path = iso_device::virtual_device_name + "/" + argv[0];
|
||||
|
||||
resolve_path_as_vfs_path = false;
|
||||
}
|
||||
}
|
||||
else if (m_path.starts_with(vfs_boot_prefix))
|
||||
{
|
||||
|
|
@ -1421,6 +1443,33 @@ 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 (!launching_from_disc_archive && is_file_iso(m_path))
|
||||
{
|
||||
sys_log.notice("Loading iso archive '%s'", m_path);
|
||||
|
||||
load_iso(m_path);
|
||||
|
||||
launching_from_disc_archive = true;
|
||||
|
||||
std::string path = iso_device::virtual_device_name + "/";
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
sys_log.notice("Load: is iso archive = %d (m_path='%s')", launching_from_disc_archive, m_path);
|
||||
|
||||
if (launching_from_disc_archive)
|
||||
{
|
||||
m_dir = "/dev_bdvd/PS3_GAME/";
|
||||
m_cat = "DG"sv;
|
||||
}
|
||||
|
||||
const std::string elf_dir = fs::get_parent_dir(m_path);
|
||||
|
||||
|
|
@ -1595,8 +1644,14 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch,
|
|||
}
|
||||
}
|
||||
|
||||
// ISO PKG INSTALL HACK!
|
||||
if (launching_from_disc_archive && fs::is_dir(m_path))
|
||||
{
|
||||
bdvd_dir = m_path;
|
||||
}
|
||||
|
||||
// Special boot mode (directory scan)
|
||||
if (fs::is_dir(m_path))
|
||||
if (!launching_from_disc_archive && fs::is_dir(m_path))
|
||||
{
|
||||
m_state = system_state::ready;
|
||||
GetCallbacks().on_ready();
|
||||
|
|
@ -1788,6 +1843,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)
|
||||
|
|
@ -1802,6 +1861,15 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch,
|
|||
// Load /dev_bdvd/ from game list if available
|
||||
if (std::string game_path = m_games_config.get_path(m_title_id); !game_path.empty())
|
||||
{
|
||||
if (is_file_iso(game_path))
|
||||
{
|
||||
sys_log.notice("Loading iso archive for patch ('%s')", game_path);
|
||||
|
||||
load_iso(game_path);
|
||||
launching_from_disc_archive = true;
|
||||
game_path = iso_device::virtual_device_name + "/PS3_GAME/./";
|
||||
}
|
||||
|
||||
if (game_path.ends_with("/./"))
|
||||
{
|
||||
// Marked as PS3_GAME directory
|
||||
|
|
@ -1929,21 +1997,28 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch,
|
|||
}
|
||||
|
||||
// TODO: Verify timestamps and error codes with sys_fs
|
||||
vfs::mount("/dev_bdvd", bdvd_dir);
|
||||
|
||||
vfs::mount("/dev_bdvd/PS3_GAME", inherited_ps3_game_path.empty() ? hdd0_game + m_path.substr(hdd0_game.size(), 10) : inherited_ps3_game_path);
|
||||
|
||||
const std::string new_ps3_game = vfs::get("/dev_bdvd/PS3_GAME");
|
||||
sys_log.notice("Game: %s", new_ps3_game);
|
||||
|
||||
// Store /dev_bdvd/PS3_GAME location
|
||||
if (games_config::result res = m_games_config.add_game(m_title_id, new_ps3_game + "/./"); res == games_config::result::success)
|
||||
if (!new_ps3_game.starts_with(iso_device::virtual_device_name))
|
||||
{
|
||||
sys_log.notice("Registered BDVD/PS3_GAME game directory for title '%s': %s", m_title_id, new_ps3_game);
|
||||
// Store /dev_bdvd/PS3_GAME location
|
||||
if (games_config::result res = m_games_config.add_game(m_title_id, new_ps3_game + "/./"); res == games_config::result::success)
|
||||
{
|
||||
sys_log.notice("Registered BDVD/PS3_GAME game directory for title '%s': %s", m_title_id, new_ps3_game);
|
||||
}
|
||||
else if (res == games_config::result::failure)
|
||||
{
|
||||
sys_log.error("Failed to save BDVD/PS3_GAME location of title '%s' (error=%s)", m_title_id, fs::g_tls_error);
|
||||
}
|
||||
|
||||
vfs::mount("/dev_bdvd", bdvd_dir);
|
||||
}
|
||||
else if (res == games_config::result::failure)
|
||||
else
|
||||
{
|
||||
sys_log.error("Failed to save BDVD/PS3_GAME location of title '%s' (error=%s)", m_title_id, fs::g_tls_error);
|
||||
vfs::mount("/dev_bdvd", iso_device::virtual_device_name + "/");
|
||||
}
|
||||
}
|
||||
else if (disc.empty())
|
||||
|
|
@ -2076,6 +2151,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 (launching_from_disc_archive && fs::is_dir(m_path))
|
||||
{
|
||||
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())
|
||||
{
|
||||
|
|
@ -2184,7 +2266,8 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch,
|
|||
// Check game updates
|
||||
if (const std::string hdd0_boot = hdd0_game + m_title_id + "/USRDIR/EBOOT.BIN"; !m_ar
|
||||
&& recursion_count == 0 && disc.empty() && !bdvd_dir.empty() && !m_title_id.empty()
|
||||
&& resolved_path == GetCallbacks().resolve_path(vfs::get("/dev_bdvd/PS3_GAME/USRDIR/EBOOT.BIN"))
|
||||
&& (launching_from_disc_archive ||
|
||||
resolved_path == GetCallbacks().resolve_path(vfs::get("/dev_bdvd/PS3_GAME/USRDIR/EBOOT.BIN")))
|
||||
&& resolved_path != GetCallbacks().resolve_path(hdd0_boot) && fs::is_file(hdd0_boot)
|
||||
&& ppu_exec == elf_error::ok)
|
||||
{
|
||||
|
|
@ -2295,8 +2378,9 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch,
|
|||
else if (!bdvd_dir.empty() && fs::is_dir(bdvd_dir))
|
||||
{
|
||||
// Disc games are on /dev_bdvd/
|
||||
const usz pos = resolved_path.rfind(m_game_dir);
|
||||
argv[0] = "/dev_bdvd/PS3_GAME/" + unescape(resolved_path.substr(pos + m_game_dir.size() + 1));
|
||||
const std::string& disc_path = launching_from_disc_archive ? m_path : resolved_path;
|
||||
const usz pos = disc_path.rfind(m_game_dir);
|
||||
argv[0] = "/dev_bdvd/PS3_GAME/" + unescape(disc_path.substr(pos + m_game_dir.size() + 1));
|
||||
m_dir = "/dev_bdvd/PS3_GAME/";
|
||||
}
|
||||
else if (from_dev_flash)
|
||||
|
|
@ -3368,7 +3452,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 +3630,18 @@ 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 + "/"))
|
||||
{
|
||||
const auto device = fs::get_virtual_device(iso_device::virtual_device_name + "/");
|
||||
ensure(device);
|
||||
|
||||
const auto iso_dev = dynamic_cast<const iso_device*>(device.get());
|
||||
|
||||
ar(m_path.substr(iso_device::virtual_device_name.size() + 1));
|
||||
ar(iso_dev->get_loaded_iso());
|
||||
}
|
||||
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"));
|
||||
|
|
@ -3840,6 +3935,11 @@ void Emulator::Kill(bool allow_autoexit, bool savestate, savestate_stage* save_s
|
|||
m_emu_state_close_pending = false;
|
||||
m_precompilation_option = {};
|
||||
|
||||
if (!m_continuous_mode)
|
||||
{
|
||||
unload_iso();
|
||||
}
|
||||
|
||||
initialize_timebased_time(0, true);
|
||||
|
||||
// Complete the operation
|
||||
|
|
@ -4039,13 +4139,18 @@ u32 Emulator::AddGamesFromDir(const std::string& path)
|
|||
{
|
||||
auto dir_entry = std::move(*path_it);
|
||||
|
||||
if (!dir_entry.is_directory || dir_entry.name == "." || dir_entry.name == "..")
|
||||
if (dir_entry.name == "." || dir_entry.name == "..")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::string dir_path = path + '/' + dir_entry.name;
|
||||
|
||||
if (!dir_entry.is_directory && !is_file_iso(dir_path))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (const game_boot_result error = AddGame(dir_path); error == game_boot_result::no_errors)
|
||||
{
|
||||
games_added++;
|
||||
|
|
@ -4143,10 +4248,17 @@ game_boot_result Emulator::AddGameToYml(const std::string& path)
|
|||
return error;
|
||||
}
|
||||
|
||||
std::unique_ptr<iso_archive> archive;
|
||||
if (is_file_iso(path))
|
||||
{
|
||||
archive = std::make_unique<iso_archive>(path);
|
||||
}
|
||||
|
||||
// Load PARAM.SFO
|
||||
const std::string elf_dir = fs::get_parent_dir(path);
|
||||
std::string sfo_dir = rpcs3::utils::get_sfo_dir_from_game_path(fs::get_parent_dir(elf_dir));
|
||||
const psf::registry _psf = psf::load_object(sfo_dir + "/PARAM.SFO");
|
||||
std::string sfo_dir = archive ? "PS3_GAME" : rpcs3::utils::get_sfo_dir_from_game_path(fs::get_parent_dir(elf_dir));
|
||||
const std::string sfo_path = sfo_dir + "/PARAM.SFO";
|
||||
const psf::registry _psf = archive ? archive->open_psf(sfo_path) : psf::load_object(sfo_path);
|
||||
|
||||
const std::string title_id = std::string(psf::get_string(_psf, "TITLE_ID"));
|
||||
const std::string cat = std::string(psf::get_string(_psf, "CATEGORY"));
|
||||
|
|
@ -4169,6 +4281,23 @@ game_boot_result Emulator::AddGameToYml(const std::string& path)
|
|||
return game_boot_result::invalid_file_or_folder;
|
||||
}
|
||||
|
||||
// Add ISO game
|
||||
if (archive)
|
||||
{
|
||||
if (cat == "DG")
|
||||
{
|
||||
std::string iso_path = path;
|
||||
switch (m_games_config.add_external_hdd_game(title_id, iso_path))
|
||||
{
|
||||
case games_config::result::failure: return game_boot_result::generic_error;
|
||||
case games_config::result::success: return game_boot_result::no_errors;
|
||||
case games_config::result::exists: return game_boot_result::already_added;
|
||||
}
|
||||
|
||||
return game_boot_result::generic_error;
|
||||
}
|
||||
}
|
||||
|
||||
// Set bdvd_dir
|
||||
std::string bdvd_dir;
|
||||
std::string game_dir;
|
||||
|
|
@ -4331,7 +4460,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 + "/")
|
||||
{
|
||||
const auto device = fs::get_virtual_device(iso_device::virtual_device_name + "/");
|
||||
if (!device) return result::failure;
|
||||
|
||||
const auto iso_dev = dynamic_cast<const iso_device*>(device.get());
|
||||
return add_game(key, iso_dev->get_loaded_iso());
|
||||
}
|
||||
|
||||
std::lock_guard lock(m_mutex);
|
||||
|
||||
// Access or create node if does not exist
|
||||
|
|
|
|||
|
|
@ -738,7 +738,7 @@ pad_capabilities sdl_pad_handler::get_capabilities(const std::string& pad_id)
|
|||
capabilities.has_rumble &= dev->sdl.has_rumble;
|
||||
capabilities.has_accel &= dev->sdl.has_accel;
|
||||
capabilities.has_gyro &= dev->sdl.has_gyro;
|
||||
capabilities.has_pressure_sensitivity &= dev->sdl.is_ds3_with_pressure_buttons;
|
||||
capabilities.has_pressure_intensity_button &= !dev->sdl.is_ds3_with_pressure_buttons; // Only allow if there's not pressure sensitivity
|
||||
|
||||
return capabilities;
|
||||
}
|
||||
|
|
|
|||
617
rpcs3/Loader/ISO.cpp
Normal file
617
rpcs3/Loader/ISO.cpp
Normal file
|
|
@ -0,0 +1,617 @@
|
|||
#include "stdafx.h"
|
||||
|
||||
#include "ISO.h"
|
||||
#include "Emu/VFS.h"
|
||||
|
||||
#include <codecvt>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <filesystem>
|
||||
#include <stack>
|
||||
|
||||
LOG_CHANNEL(sys_log, "SYS");
|
||||
|
||||
bool is_file_iso(const std::string& path)
|
||||
{
|
||||
if (path.empty()) return false;
|
||||
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)
|
||||
{
|
||||
const auto start_pos = file.pos();
|
||||
const u8 entry_length = file.read<u8>();
|
||||
|
||||
if (entry_length == 0) return std::nullopt;
|
||||
|
||||
file.seek(1, fs::seek_cur);
|
||||
const u32 start_sector = read_both_endian_int<u32>(file);
|
||||
const 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>();
|
||||
const s16 timezone_value = file.read<u8>();
|
||||
const s16 timezone_offset = (timezone_value - 50) * 15 * 60;
|
||||
|
||||
const std::time_t date_time = std::mktime(&file_date) + timezone_offset;
|
||||
|
||||
const u8 flags = file.read<u8>();
|
||||
|
||||
// 2nd flag bit indicates whether a given fs node is a directory
|
||||
const bool is_directory = flags & 0b00000010;
|
||||
const bool has_more_extents = flags & 0b10000000;
|
||||
|
||||
file.seek(6, fs::seek_cur);
|
||||
|
||||
const 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
|
||||
{
|
||||
// characters are stored in big endian format.
|
||||
std::u16string utf16;
|
||||
utf16.resize(file_name_length / 2);
|
||||
|
||||
const u16* raw = reinterpret_cast<const u16*>(file_name.data());
|
||||
for (size_t i = 0; i < utf16.size(); ++i, raw++)
|
||||
{
|
||||
utf16[i] = *reinterpret_cast<const be_t<u16>*>(raw);
|
||||
}
|
||||
|
||||
file_name = utf16_to_utf8(utf16);
|
||||
}
|
||||
|
||||
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 = std::move(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, const std::string& parent_path = "")
|
||||
{
|
||||
if (!node.metadata.is_directory) return;
|
||||
|
||||
std::vector<usz> 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);
|
||||
|
||||
const 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)
|
||||
{
|
||||
const 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 (usz index : multi_extent_node_indices)
|
||||
{
|
||||
auto& selected_node = ::at32(node.children, index);
|
||||
if (selected_node->metadata.name == entry->name)
|
||||
{
|
||||
// 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 = std::move(*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
|
||||
{
|
||||
const 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)
|
||||
{
|
||||
if (passed_path.empty()) return nullptr;
|
||||
|
||||
const std::string path = std::filesystem::path(passed_path).string();
|
||||
const std::string_view path_sv = path;
|
||||
|
||||
size_t start = 0;
|
||||
size_t end = path_sv.find_first_of(fs::delim);
|
||||
|
||||
std::stack<iso_fs_node*> search_stack;
|
||||
search_stack.push(&m_root);
|
||||
|
||||
do
|
||||
{
|
||||
if (search_stack.empty()) return nullptr;
|
||||
const auto* top_entry = search_stack.top();
|
||||
|
||||
if (end == umax)
|
||||
{
|
||||
end = path.size();
|
||||
}
|
||||
|
||||
const std::string_view path_component = path_sv.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 == path_component)
|
||||
{
|
||||
search_stack.push(entry.get());
|
||||
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) return nullptr;
|
||||
|
||||
start = end + 1;
|
||||
end = path_sv.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)
|
||||
{
|
||||
const 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), *ensure(retrieve(path)));
|
||||
}
|
||||
|
||||
psf::registry iso_archive::open_psf(const std::string& path)
|
||||
{
|
||||
auto* archive_file = retrieve(path);
|
||||
if (!archive_file) return psf::registry();
|
||||
|
||||
const fs::file psf_file(std::make_unique<iso_file>(fs::file(m_path), *archive_file));
|
||||
|
||||
return psf::load_object(psf_file, 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_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 /*length*/)
|
||||
{
|
||||
fs::g_tls_error = fs::error::readonly;
|
||||
return false;
|
||||
}
|
||||
|
||||
std::pair<u64, iso_extent_info> iso_file::get_extent_pos(u64 pos) const
|
||||
{
|
||||
ensure(!m_meta.extents.empty());
|
||||
|
||||
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
|
||||
{
|
||||
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
|
||||
{
|
||||
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)
|
||||
{
|
||||
const auto r = read_at(m_pos, buffer, size);
|
||||
m_pos += r;
|
||||
return r;
|
||||
}
|
||||
|
||||
u64 iso_file::read_at(u64 offset, void* buffer, u64 size)
|
||||
{
|
||||
const u64 local_remaining = local_extent_remaining(offset);
|
||||
|
||||
const u64 total_read = m_file.read_at(file_offset(offset), buffer, std::min(size, local_remaining));
|
||||
|
||||
const auto total_size = this->size();
|
||||
|
||||
if (size > total_read && (offset + total_read) < total_size)
|
||||
{
|
||||
const 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* /*buffer*/, u64 /*size*/)
|
||||
{
|
||||
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 result = m_file.seek(file_offset(m_pos));
|
||||
if (result == umax) return umax;
|
||||
|
||||
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())
|
||||
{
|
||||
const auto& selected = m_node.children[m_pos].get()->metadata;
|
||||
|
||||
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 = selected.size();
|
||||
|
||||
m_pos++;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool iso_device::stat(const std::string& path, fs::stat_t& info)
|
||||
{
|
||||
const auto relative_path = std::filesystem::relative(std::filesystem::path(path), std::filesystem::path(fs_prefix)).string();
|
||||
|
||||
const auto node = m_archive.retrieve(relative_path);
|
||||
if (!node)
|
||||
{
|
||||
fs::g_tls_error = fs::error::noent;
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto& meta = node->metadata;
|
||||
|
||||
info = fs::stat_t
|
||||
{
|
||||
.is_directory = meta.is_directory,
|
||||
.is_symlink = false,
|
||||
.is_writable = false,
|
||||
.size = meta.size(),
|
||||
.atime = meta.time,
|
||||
.mtime = meta.time,
|
||||
.ctime = meta.time
|
||||
};
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool iso_device::statfs(const std::string& path, fs::device_stat& info)
|
||||
{
|
||||
const auto relative_path = std::filesystem::relative(std::filesystem::path(path), std::filesystem::path(fs_prefix)).string();
|
||||
|
||||
const auto node = m_archive.retrieve(relative_path);
|
||||
if (!node)
|
||||
{
|
||||
fs::g_tls_error = fs::error::noent;
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto& meta = node->metadata;
|
||||
const 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)
|
||||
{
|
||||
const auto relative_path = std::filesystem::relative(std::filesystem::path(path), std::filesystem::path(fs_prefix)).string();
|
||||
|
||||
const 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)
|
||||
{
|
||||
const auto relative_path = std::filesystem::relative(std::filesystem::path(path), std::filesystem::path(fs_prefix)).string();
|
||||
|
||||
const 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)
|
||||
{
|
||||
sys_log.notice("Loading iso '%s'", path);
|
||||
|
||||
fs::set_virtual_device("iso_overlay_fs_dev", stx::make_shared<iso_device>(path));
|
||||
|
||||
vfs::mount("/dev_bdvd/"sv, iso_device::virtual_device_name + "/");
|
||||
}
|
||||
|
||||
void unload_iso()
|
||||
{
|
||||
sys_log.notice("Unoading iso");
|
||||
|
||||
fs::set_virtual_device("iso_overlay_fs_dev", stx::shared_ptr<iso_device>());
|
||||
}
|
||||
121
rpcs3/Loader/ISO.h
Normal file
121
rpcs3/Loader/ISO.h
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
#pragma once
|
||||
|
||||
#include "Loader/PSF.h"
|
||||
|
||||
#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
|
||||
{
|
||||
private:
|
||||
fs::file m_file;
|
||||
iso_fs_metadata m_meta;
|
||||
u64 m_pos = 0;
|
||||
|
||||
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
|
||||
{
|
||||
private:
|
||||
const iso_fs_node& m_node;
|
||||
u64 m_pos = 0;
|
||||
|
||||
public:
|
||||
iso_dir(const iso_fs_node& node)
|
||||
: m_node(node)
|
||||
{}
|
||||
|
||||
bool read(fs::dir_entry&) override;
|
||||
void rewind() override;
|
||||
};
|
||||
|
||||
// represents the .iso file itself.
|
||||
class iso_archive
|
||||
{
|
||||
private:
|
||||
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);
|
||||
|
||||
psf::registry open_psf(const std::string& path);
|
||||
};
|
||||
|
||||
class iso_device : public fs::device_base
|
||||
{
|
||||
private:
|
||||
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;
|
||||
};
|
||||
|
|
@ -540,6 +540,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" />
|
||||
|
|
@ -1021,6 +1022,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" />
|
||||
|
|
@ -1096,4 +1098,4 @@
|
|||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -1534,7 +1534,7 @@ void game_list_actions::CreateShortcuts(const std::vector<game_info>& games, con
|
|||
#endif
|
||||
}
|
||||
|
||||
if (!gameid_token_value.empty() && gui::utils::create_shortcut(gameinfo->info.name, gameinfo->info.serial, target_cli_args, gameinfo->info.name, gameinfo->info.icon_path, target_icon_dir, location))
|
||||
if (!gameid_token_value.empty() && gui::utils::create_shortcut(gameinfo->info.name, gameinfo->icon_in_archive ? gameinfo->info.path : "", gameinfo->info.serial, target_cli_args, gameinfo->info.name, gameinfo->info.icon_path, target_icon_dir, location))
|
||||
{
|
||||
game_list_log.success("Created %s shortcut for %s", destination, QString::fromStdString(gameinfo->info.name).simplified());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
#include "stdafx.h"
|
||||
#include "qt_utils.h"
|
||||
#include "game_list_base.h"
|
||||
|
||||
#include <QDir>
|
||||
|
|
@ -48,7 +49,7 @@ void game_list_base::IconLoadFunction(game_info game, qreal device_pixel_ratio,
|
|||
static std::unordered_set<std::string> warn_once_list;
|
||||
static shared_mutex s_mtx;
|
||||
|
||||
if (game->icon.isNull() && (game->info.icon_path.empty() || !game->icon.load(QString::fromStdString(game->info.icon_path))))
|
||||
if (game->icon.isNull() && !gui::utils::load_icon(game->icon, game->info.icon_path, game->icon_in_archive ? game->info.path : ""))
|
||||
{
|
||||
if (game_list_log.warning)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -15,6 +15,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"
|
||||
|
|
@ -522,13 +523,26 @@ 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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
return archive ? archive->is_file(path) : fs::is_file(path);
|
||||
};
|
||||
|
||||
gui_game_info game{};
|
||||
game.info.path = dir_or_elf;
|
||||
|
||||
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 ? "PS3_GAME" : rpcs3::utils::get_sfo_dir_from_game_path(dir_or_elf);
|
||||
const std::string sfo_path = sfo_dir + "/PARAM.SFO";
|
||||
|
||||
const psf::registry psf = archive ? archive->open_psf(sfo_path) : psf::load_object(sfo_path);
|
||||
const std::string_view title_id = psf::get_string(psf, "TITLE_ID", "");
|
||||
|
||||
if (title_id.empty())
|
||||
|
|
@ -596,7 +610,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);
|
||||
}
|
||||
|
|
@ -604,19 +618,20 @@ void game_list_frame::OnParsingFinished()
|
|||
{
|
||||
game.info.icon_path = sfo_dir + "/ICON0.PNG";
|
||||
}
|
||||
game.icon_in_archive = archive && archive->exists(game.info.icon_path);
|
||||
}
|
||||
|
||||
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;
|
||||
|
|
@ -741,6 +756,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);
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@
|
|||
#include "gui_settings.h"
|
||||
#include "qt_utils.h"
|
||||
|
||||
#include "Loader/ISO.h"
|
||||
|
||||
#include <QApplication>
|
||||
|
||||
game_list_grid::game_list_grid()
|
||||
|
|
@ -110,6 +112,11 @@ void game_list_grid::populate(
|
|||
if (play_hover_movies && (game->has_hover_gif || game->has_hover_pam))
|
||||
{
|
||||
item->set_video_path(game->info.movie_path);
|
||||
|
||||
if (!fs::exists(game->info.movie_path) && is_file_iso(game->info.path))
|
||||
{
|
||||
item->set_iso_path(game->info.path);
|
||||
}
|
||||
}
|
||||
|
||||
if (selected_item_ids.contains(game->info.path + game->info.icon_path))
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@
|
|||
#include "Emu/vfs_config.h"
|
||||
#include "Utilities/StrUtil.h"
|
||||
|
||||
#include "Loader/ISO.h"
|
||||
|
||||
#include <QHeaderView>
|
||||
#include <QScrollBar>
|
||||
#include <QStringBuilder>
|
||||
|
|
@ -277,6 +279,13 @@ void game_list_table::populate(
|
|||
// Do not report size of apps inside /dev_flash (it does not make sense to do so)
|
||||
game->info.size_on_disk = 0;
|
||||
}
|
||||
else if (is_file_iso(game->info.path))
|
||||
{
|
||||
fs::stat_t iso_stat;
|
||||
fs::get_stat(game->info.path, iso_stat);
|
||||
|
||||
game->info.size_on_disk = iso_stat.size;
|
||||
}
|
||||
else
|
||||
{
|
||||
game->info.size_on_disk = fs::get_dir_size(game->info.path, 1, cancel.get());
|
||||
|
|
@ -293,6 +302,11 @@ void game_list_table::populate(
|
|||
if (play_hover_movies && (game->has_hover_gif || game->has_hover_pam))
|
||||
{
|
||||
icon_item->set_video_path(game->info.movie_path);
|
||||
|
||||
if (!fs::exists(game->info.movie_path) && is_file_iso(game->info.path))
|
||||
{
|
||||
icon_item->set_iso_path(game->info.path);
|
||||
}
|
||||
}
|
||||
|
||||
icon_item->setData(Qt::UserRole, index, true);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
#include "gui_settings.h"
|
||||
#include "hex_validator.h"
|
||||
#include "memory_viewer_panel.h"
|
||||
#include "syntax_highlighter.h"
|
||||
|
||||
#include "Utilities/lockless.h"
|
||||
#include "util/asm.hpp"
|
||||
|
|
@ -161,6 +162,11 @@ log_frame::log_frame(std::shared_ptr<gui_settings> _gui_settings, QWidget* paren
|
|||
CreateAndConnectActions();
|
||||
LoadSettings();
|
||||
|
||||
if (m_ansi_tty)
|
||||
{
|
||||
m_tty_ansi_highlighter = new AnsiHighlighter(m_tty->document());
|
||||
}
|
||||
|
||||
m_timer = new QTimer(this);
|
||||
connect(m_timer, &QTimer::timeout, this, &log_frame::UpdateUI);
|
||||
}
|
||||
|
|
@ -288,6 +294,16 @@ void log_frame::CreateAndConnectActions()
|
|||
{
|
||||
m_gui_settings->SetValue(gui::l_ansi_code, checked);
|
||||
m_ansi_tty = checked;
|
||||
|
||||
if (m_ansi_tty && !m_tty_ansi_highlighter)
|
||||
{
|
||||
m_tty_ansi_highlighter = new AnsiHighlighter(m_tty->document());
|
||||
}
|
||||
else if (!m_ansi_tty && m_tty_ansi_highlighter)
|
||||
{
|
||||
m_tty_ansi_highlighter->deleteLater();
|
||||
m_tty_ansi_highlighter = nullptr;
|
||||
}
|
||||
});
|
||||
|
||||
m_tty_channel_acts = new QActionGroup(this);
|
||||
|
|
@ -599,8 +615,22 @@ void log_frame::UpdateUI()
|
|||
buf_line.assign(std::string_view(m_tty_buf).substr(str_index, m_tty_buf.find_first_of('\n', str_index) - str_index));
|
||||
str_index += buf_line.size() + 1;
|
||||
|
||||
// Ignore control characters and greater/equal to 0x80
|
||||
buf_line.erase(std::remove_if(buf_line.begin(), buf_line.end(), [](s8 c) { return c <= 0x8 || c == 0x7F || (c >= 0xE && c <= 0x1F); }), buf_line.end());
|
||||
// If ANSI TTY is enabled, remove all control characters except for ESC (0x1B) for ANSI sequences
|
||||
if (m_ansi_tty)
|
||||
{
|
||||
buf_line.erase(std::remove_if(buf_line.begin(), buf_line.end(), [](s8 c)
|
||||
{
|
||||
return c <= 0x8 || c == 0x7F || (c >= 0xE && c <= 0x1F && c != 0x1B);
|
||||
}), buf_line.end());
|
||||
}
|
||||
// Otherwise, remove all control characters to keep the output clean
|
||||
else
|
||||
{
|
||||
buf_line.erase(std::remove_if(buf_line.begin(), buf_line.end(), [](s8 c)
|
||||
{
|
||||
return c <= 0x8 || c == 0x7F || (c >= 0xE && c <= 0x1F);
|
||||
}), buf_line.end());
|
||||
}
|
||||
|
||||
// save old scroll bar state
|
||||
QScrollBar* sb = m_tty->verticalScrollBar();
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@
|
|||
#include <QPlainTextEdit>
|
||||
#include <QActionGroup>
|
||||
|
||||
class AnsiHighlighter;
|
||||
|
||||
class gui_settings;
|
||||
|
||||
class log_frame : public custom_dock_widget
|
||||
|
|
@ -69,6 +71,7 @@ private:
|
|||
QPlainTextEdit* m_tty = nullptr;
|
||||
QLineEdit* m_tty_input = nullptr;
|
||||
int m_tty_channel = -1;
|
||||
AnsiHighlighter* m_tty_ansi_highlighter = nullptr;
|
||||
|
||||
QAction* m_clear_act = nullptr;
|
||||
QAction* m_clear_tty_act = nullptr;
|
||||
|
|
|
|||
|
|
@ -78,6 +78,7 @@
|
|||
#include "Loader/PUP.h"
|
||||
#include "Loader/TAR.h"
|
||||
#include "Loader/PSF.h"
|
||||
#include "Loader/ISO.h"
|
||||
#include "Loader/mself.hpp"
|
||||
|
||||
#include "Utilities/Thread.h"
|
||||
|
|
@ -542,8 +543,16 @@ void main_window::Boot(const std::string& path, const std::string& title_id, boo
|
|||
}
|
||||
else
|
||||
{
|
||||
std::string game_path = Emu.GetBoot();
|
||||
if (game_path.starts_with(iso_device::virtual_device_name))
|
||||
{
|
||||
const auto device = fs::get_virtual_device(iso_device::virtual_device_name + "/");
|
||||
const auto iso_dev = ensure(dynamic_cast<const iso_device*>(device.get()));
|
||||
game_path = iso_dev->get_loaded_iso();
|
||||
}
|
||||
|
||||
gui_log.success("Boot successful.");
|
||||
AddRecentAction(gui::Recent_Game(QString::fromStdString(Emu.GetBoot()), QString::fromStdString(Emu.GetTitleAndTitleID())), false);
|
||||
AddRecentAction(gui::Recent_Game(QString::fromStdString(game_path), QString::fromStdString(Emu.GetTitleAndTitleID())), false);
|
||||
}
|
||||
|
||||
if (refresh_list)
|
||||
|
|
@ -569,6 +578,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);
|
||||
|
|
@ -690,6 +700,34 @@ void main_window::BootGame()
|
|||
Boot(dir_path.toStdString(), "", false, true);
|
||||
}
|
||||
|
||||
void main_window::BootISO()
|
||||
{
|
||||
bool stopped = false;
|
||||
|
||||
if (Emu.IsRunning())
|
||||
{
|
||||
Emu.Pause();
|
||||
stopped = true;
|
||||
}
|
||||
|
||||
const QString path_last_game = m_gui_settings->GetValue(gui::fd_boot_game).toString();
|
||||
const QString path = QFileDialog::getOpenFileName(this, tr("Select ISO"), path_last_game, tr("ISO files (*.iso);;All files (*.*)"));
|
||||
|
||||
if (path.isEmpty())
|
||||
{
|
||||
if (stopped)
|
||||
{
|
||||
Emu.Resume();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
m_gui_settings->SetValue(gui::fd_boot_game, QFileInfo(path).dir().path());
|
||||
|
||||
gui_log.notice("Booting from BootISO...");
|
||||
Boot(path.toStdString(), "", true, true);
|
||||
}
|
||||
|
||||
void main_window::BootVSH()
|
||||
{
|
||||
gui_log.notice("Booting from BootVSH...");
|
||||
|
|
@ -2469,6 +2507,7 @@ void main_window::CreateConnects()
|
|||
connect(ui->bootElfAct, &QAction::triggered, this, &main_window::BootElf);
|
||||
connect(ui->bootTestAct, &QAction::triggered, this, &main_window::BootTest);
|
||||
connect(ui->bootGameAct, &QAction::triggered, this, &main_window::BootGame);
|
||||
connect(ui->bootIsoAct, &QAction::triggered, this, &main_window::BootISO);
|
||||
connect(ui->bootVSHAct, &QAction::triggered, this, &main_window::BootVSH);
|
||||
connect(ui->actionopen_rsx_capture, &QAction::triggered, this, [this](){ BootRsxCapture(); });
|
||||
connect(ui->actionCreate_RSX_Capture, &QAction::triggered, this, []()
|
||||
|
|
@ -2521,6 +2560,22 @@ void main_window::CreateConnects()
|
|||
AddGamesFromDirs(std::move(paths));
|
||||
});
|
||||
|
||||
connect(ui->addIsoGamesAct, &QAction::triggered, this, [this]()
|
||||
{
|
||||
if (!m_gui_settings->GetBootConfirmation(this))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
QStringList paths = QFileDialog::getOpenFileNames(this, tr("Select ISO files to add"), QString::fromStdString(fs::get_config_dir()), tr("ISO files (*.iso);;All files (*.*)"));
|
||||
if (paths.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
AddGamesFromDirs(std::move(paths));
|
||||
});
|
||||
|
||||
connect(ui->bootRecentMenu, &QMenu::aboutToShow, this, [this]()
|
||||
{
|
||||
// Enable/Disable Recent Games List
|
||||
|
|
@ -3958,7 +4013,7 @@ main_window::drop_type main_window::IsValidFile(const QMimeData& md, QStringList
|
|||
const QString suffix_lo = info.suffix().toLower();
|
||||
|
||||
// check for directories first, only valid if all other paths led to directories until now.
|
||||
if (info.isDir())
|
||||
if (info.isDir() || is_file_iso(path.toStdString()))
|
||||
{
|
||||
if (type != drop_type::drop_dir && type != drop_type::drop_error)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -104,6 +104,7 @@ private Q_SLOTS:
|
|||
void BootElf();
|
||||
void BootTest();
|
||||
void BootGame();
|
||||
void BootISO();
|
||||
void BootVSH();
|
||||
void BootSavestate();
|
||||
void BootRsxCapture(std::string path = "");
|
||||
|
|
|
|||
|
|
@ -62,7 +62,6 @@
|
|||
<property name="font">
|
||||
<font>
|
||||
<pointsize>10</pointsize>
|
||||
<weight>50</weight>
|
||||
<bold>false</bold>
|
||||
</font>
|
||||
</property>
|
||||
|
|
@ -211,6 +210,7 @@
|
|||
<addaction name="separator"/>
|
||||
</widget>
|
||||
<addaction name="bootGameAct"/>
|
||||
<addaction name="bootIsoAct"/>
|
||||
<addaction name="bootVSHAct"/>
|
||||
<addaction name="bootElfMenu"/>
|
||||
<addaction name="bootSavestateAct"/>
|
||||
|
|
@ -218,6 +218,7 @@
|
|||
<addaction name="bootRecentMenu"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="addGamesAct"/>
|
||||
<addaction name="addIsoGamesAct"/>
|
||||
<addaction name="bootInstallPkgAct"/>
|
||||
<addaction name="bootInstallPupAct"/>
|
||||
<addaction name="separator"/>
|
||||
|
|
@ -242,17 +243,17 @@
|
|||
<property name="title">
|
||||
<string>Configuration</string>
|
||||
</property>
|
||||
<widget class="QMenu" name="menuMice">
|
||||
<property name="title">
|
||||
<string>Mice</string>
|
||||
</property>
|
||||
<addaction name="actionBasic_Mouse"/>
|
||||
<addaction name="actionRaw_Mouse"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuDevices">
|
||||
<property name="title">
|
||||
<string>Devices</string>
|
||||
</property>
|
||||
<widget class="QMenu" name="menuMice">
|
||||
<property name="title">
|
||||
<string>Mice</string>
|
||||
</property>
|
||||
<addaction name="actionBasic_Mouse"/>
|
||||
<addaction name="actionRaw_Mouse"/>
|
||||
</widget>
|
||||
<addaction name="confPadsAct"/>
|
||||
<addaction name="menuMice"/>
|
||||
<addaction name="confCamerasAct"/>
|
||||
|
|
@ -296,9 +297,6 @@
|
|||
<property name="title">
|
||||
<string>Manage</string>
|
||||
</property>
|
||||
<addaction name="confVFSDialogAct"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionManage_Users"/>
|
||||
<widget class="QMenu" name="menuNetworkServices">
|
||||
<property name="title">
|
||||
<string>Network Services</string>
|
||||
|
|
@ -307,7 +305,6 @@
|
|||
<addaction name="confClansAct"/>
|
||||
<addaction name="confIPCAct"/>
|
||||
</widget>
|
||||
<addaction name="menuNetworkServices"/>
|
||||
<widget class="QMenu" name="menuPortals">
|
||||
<property name="title">
|
||||
<string>Portals and Gates</string>
|
||||
|
|
@ -317,6 +314,10 @@
|
|||
<addaction name="actionManage_Dimensions_ToyPad"/>
|
||||
<addaction name="actionManage_KamenRider_RideGate"/>
|
||||
</widget>
|
||||
<addaction name="confVFSDialogAct"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionManage_Users"/>
|
||||
<addaction name="menuNetworkServices"/>
|
||||
<addaction name="menuPortals"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionManage_RAP_Licenses"/>
|
||||
|
|
@ -1046,7 +1047,7 @@
|
|||
</action>
|
||||
<action name="toolbar_rpcn">
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<iconset resource="../resources.qrc">
|
||||
<normaloff>:/Icons/rpcn.png</normaloff>:/Icons/rpcn.png</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
|
|
@ -1513,6 +1514,16 @@
|
|||
<string>Download Update</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="addIsoGamesAct">
|
||||
<property name="text">
|
||||
<string>Add ISO Games</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="bootIsoAct">
|
||||
<property name="text">
|
||||
<string>Boot ISO</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<layoutdefault spacing="6" margin="11"/>
|
||||
<resources>
|
||||
|
|
|
|||
|
|
@ -482,7 +482,7 @@ void pad_settings_dialog::InitButtons()
|
|||
if ((!is_connected || !m_remap_timer.isActive()) && (
|
||||
is_connected != m_enable_buttons ||
|
||||
(is_connected && (
|
||||
!capabilities.has_pressure_sensitivity != m_enable_pressure_intensity_button ||
|
||||
capabilities.has_pressure_intensity_button != m_enable_pressure_intensity_button ||
|
||||
capabilities.has_rumble != m_enable_rumble ||
|
||||
capabilities.has_battery_led != m_enable_battery_led ||
|
||||
(capabilities.has_led || capabilities.has_mono_led) != m_enable_led ||
|
||||
|
|
@ -490,7 +490,7 @@ void pad_settings_dialog::InitButtons()
|
|||
{
|
||||
if (is_connected)
|
||||
{
|
||||
m_enable_pressure_intensity_button = !capabilities.has_pressure_sensitivity;
|
||||
m_enable_pressure_intensity_button = capabilities.has_pressure_intensity_button;
|
||||
m_enable_rumble = capabilities.has_rumble;
|
||||
m_enable_battery_led = capabilities.has_battery_led;
|
||||
m_enable_led = capabilities.has_led || capabilities.has_mono_led;
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
|
||||
#include "Emu/system_utils.hpp"
|
||||
#include "Utilities/File.h"
|
||||
#include "Loader/ISO.h"
|
||||
#include <cmath>
|
||||
|
||||
LOG_CHANNEL(gui_log, "GUI");
|
||||
|
|
@ -392,27 +393,46 @@ namespace gui
|
|||
std::string icon_path = fs::get_config_dir() + "/Icons/game_icons/" + title_id + "/ICON0.PNG";
|
||||
bool found_file = fs::is_file(icon_path);
|
||||
|
||||
std::unique_ptr<QPixmap> pixmap;
|
||||
|
||||
if (!found_file)
|
||||
{
|
||||
// Get Icon for the gs_frame from path. this handles presumably all possible use cases
|
||||
const QString qpath = QString::fromStdString(path);
|
||||
const std::string path_list[] = { path, qpath.section("/", 0, -2, QString::SectionIncludeTrailingSep).toStdString(),
|
||||
qpath.section("/", 0, -3, QString::SectionIncludeTrailingSep).toStdString() };
|
||||
std::vector<std::string> path_list;
|
||||
|
||||
for (const std::string& pth : path_list)
|
||||
const bool is_archive = is_file_iso(path);
|
||||
if (is_archive)
|
||||
{
|
||||
if (!fs::is_dir(pth))
|
||||
icon_path = "PS3_GAME/ICON0.PNG";
|
||||
|
||||
QPixmap px;
|
||||
if (load_iso_icon(px, icon_path, path))
|
||||
{
|
||||
continue;
|
||||
found_file = true;
|
||||
pixmap = std::make_unique<QPixmap>(std::move(px));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const QString qpath = QString::fromStdString(path);
|
||||
path_list = { path, qpath.section("/", 0, -2, QString::SectionIncludeTrailingSep).toStdString(),
|
||||
qpath.section("/", 0, -3, QString::SectionIncludeTrailingSep).toStdString() };
|
||||
|
||||
const std::string sfo_dir = rpcs3::utils::get_sfo_dir_from_game_path(pth, title_id);
|
||||
icon_path = sfo_dir + "/ICON0.PNG";
|
||||
found_file = fs::is_file(icon_path);
|
||||
|
||||
if (found_file)
|
||||
for (const std::string& pth : path_list)
|
||||
{
|
||||
break;
|
||||
if (!fs::is_dir(pth))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::string sfo_dir = rpcs3::utils::get_sfo_dir_from_game_path(pth, title_id);
|
||||
icon_path = sfo_dir + "/ICON0.PNG";
|
||||
found_file = fs::is_file(icon_path);
|
||||
|
||||
if (found_file)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -420,7 +440,7 @@ namespace gui
|
|||
if (found_file)
|
||||
{
|
||||
// load the image from path. It will most likely be a rectangle
|
||||
const QImage source = QImage(QString::fromStdString(icon_path));
|
||||
const QImage source = pixmap ? pixmap->toImage() : QImage(QString::fromStdString(icon_path));
|
||||
const int edge_max = std::max(source.width(), source.height());
|
||||
|
||||
// create a new transparent image with square size and same format as source (maybe handle other formats than RGB32 as well?)
|
||||
|
|
@ -682,6 +702,36 @@ namespace gui
|
|||
return QString("%1 days ago %2").arg(current_date - exctrated_date).arg(date.toString(fmt_relative));
|
||||
}
|
||||
|
||||
bool load_iso_icon(QPixmap& icon, const std::string& icon_path, const std::string& archive_path)
|
||||
{
|
||||
if (icon_path.empty() || archive_path.empty()) return false;
|
||||
if (!is_file_iso(archive_path)) return false;
|
||||
|
||||
iso_archive archive(archive_path);
|
||||
if (!archive.exists(icon_path)) return false;
|
||||
|
||||
auto icon_file = archive.open(icon_path);
|
||||
const auto icon_size = icon_file.size();
|
||||
if (icon_size == 0) return false;
|
||||
|
||||
QByteArray data(icon_size, 0);
|
||||
icon_file.read(data.data(), icon_size);
|
||||
|
||||
return icon.loadFromData(data);
|
||||
}
|
||||
|
||||
bool load_icon(QPixmap& icon, const std::string& icon_path, const std::string& archive_path)
|
||||
{
|
||||
if (icon_path.empty()) return false;
|
||||
|
||||
if (archive_path.empty())
|
||||
{
|
||||
return icon.load(QString::fromStdString(icon_path));
|
||||
}
|
||||
|
||||
return load_iso_icon(icon, icon_path, archive_path);
|
||||
}
|
||||
|
||||
QString format_timestamp(s64 time, const QString& fmt)
|
||||
{
|
||||
return format_datetime(datetime(time), fmt);
|
||||
|
|
|
|||
|
|
@ -178,6 +178,12 @@ namespace gui
|
|||
return color_scheme() == Qt::ColorScheme::Dark;
|
||||
}
|
||||
|
||||
// Loads an icon from an (ISO) archive file.
|
||||
bool load_iso_icon(QPixmap& icon, const std::string& icon_path, const std::string& archive_path);
|
||||
|
||||
// Loads an icon (optionally from an (ISO) archive file).
|
||||
bool load_icon(QPixmap& icon, const std::string& icon_path, const std::string& archive_path);
|
||||
|
||||
template <typename T>
|
||||
void stop_future_watcher(QFutureWatcher<T>& watcher, bool cancel, std::shared_ptr<atomic_t<bool>> cancel_flag = nullptr)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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_iso_path(const std::string& iso_path)
|
||||
{
|
||||
m_iso_path = iso_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() || (m_iso_path.empty() && !QFile::exists(m_video_path)))
|
||||
{
|
||||
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_iso_path.empty())
|
||||
{
|
||||
m_movie = std::make_unique<QMovie>(m_video_path);
|
||||
}
|
||||
else
|
||||
{
|
||||
iso_archive archive(m_iso_path);
|
||||
auto movie_file = archive.open(m_video_path.toStdString());
|
||||
const auto movie_size = movie_file.size();
|
||||
if (movie_size == 0) return;
|
||||
|
||||
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);
|
||||
m_video_buffer->open(QIODevice::ReadOnly);
|
||||
m_movie = std::make_unique<QMovie>(m_video_buffer.get());
|
||||
|
||||
}
|
||||
|
||||
if (!m_movie->isValid())
|
||||
{
|
||||
|
|
@ -85,14 +109,31 @@ 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_iso_path.empty())
|
||||
{
|
||||
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();
|
||||
}
|
||||
else
|
||||
{
|
||||
iso_archive archive(m_iso_path);
|
||||
auto movie_file = archive.open(m_video_path.toStdString());
|
||||
const auto movie_size = movie_file.size();
|
||||
if (movie_size == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_video_data = QByteArray(movie_size, 0);
|
||||
movie_file.read(m_video_data.data(), movie_size);
|
||||
}
|
||||
|
||||
// TODO: Decode the pam properly before pushing it to the player
|
||||
m_video_data = file.readAll();
|
||||
if (m_video_data.isEmpty())
|
||||
{
|
||||
return;
|
||||
|
|
@ -148,6 +189,7 @@ void qt_video_source::stop_movie()
|
|||
if (m_movie)
|
||||
{
|
||||
m_movie->stop();
|
||||
m_movie.reset();
|
||||
}
|
||||
|
||||
m_video_sink.reset();
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ public:
|
|||
qt_video_source();
|
||||
virtual ~qt_video_source();
|
||||
|
||||
void set_iso_path(const std::string& iso_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;
|
||||
std::string m_iso_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); });
|
||||
|
|
@ -416,9 +418,10 @@ 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;
|
||||
std::string game_icon_path = m_savestate_db[savestate_index]->game_icon_path;
|
||||
std::string game_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 = std::move(game_icon_path), archive_path = std::move(game_archive_path), savestate_index, cancel = item->icon_loading_aborted(), dpr](int index)
|
||||
{
|
||||
if (cancel && cancel->load())
|
||||
{
|
||||
|
|
@ -432,7 +435,7 @@ void savestate_manager_dialog::ResizeGameIcons()
|
|||
if (!item->data(GameUserRole::GamePixmapLoaded).toBool())
|
||||
{
|
||||
// Load game icon
|
||||
if (!icon.load(QString::fromStdString(icon_path)))
|
||||
if (!gui::utils::load_icon(icon, icon_path, archive_path))
|
||||
{
|
||||
gui_log.warning("Could not load savestate game icon from path %s", icon_path);
|
||||
}
|
||||
|
|
@ -617,6 +620,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);
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
#include "Emu/VFS.h"
|
||||
#include "Utilities/File.h"
|
||||
#include "Utilities/StrUtil.h"
|
||||
#include "Loader/ISO.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <Windows.h>
|
||||
|
|
@ -31,7 +32,7 @@ LOG_CHANNEL(sys_log, "SYS");
|
|||
|
||||
namespace gui::utils
|
||||
{
|
||||
bool create_square_shortcut_icon_file(const std::string& src_icon_path, const std::string& target_icon_dir, std::string& target_icon_path, const std::string& extension, int size)
|
||||
bool create_square_shortcut_icon_file(const std::string& path, const std::string& src_icon_path, const std::string& target_icon_dir, std::string& target_icon_path, const std::string& extension, int size)
|
||||
{
|
||||
if (src_icon_path.empty() || target_icon_dir.empty() || extension.empty())
|
||||
{
|
||||
|
|
@ -39,7 +40,15 @@ namespace gui::utils
|
|||
return false;
|
||||
}
|
||||
|
||||
QPixmap icon(QString::fromStdString(src_icon_path));
|
||||
const bool is_archive = is_file_iso(path);
|
||||
|
||||
QPixmap icon;
|
||||
if (!load_icon(icon, src_icon_path, is_archive ? path : ""))
|
||||
{
|
||||
sys_log.error("Failed to create shortcut. Failed to load %sicon: '%s'", is_archive ? "iso " : "", src_icon_path);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!gui::utils::create_square_pixmap(icon, size))
|
||||
{
|
||||
sys_log.error("Failed to create shortcut. Icon empty.");
|
||||
|
|
@ -67,6 +76,7 @@ namespace gui::utils
|
|||
}
|
||||
|
||||
bool create_shortcut(const std::string& name,
|
||||
const std::string& path,
|
||||
[[maybe_unused]] const std::string& serial,
|
||||
[[maybe_unused]] const std::string& target_cli_args,
|
||||
[[maybe_unused]] const std::string& description,
|
||||
|
|
@ -189,7 +199,7 @@ namespace gui::utils
|
|||
if (!src_icon_path.empty() && !target_icon_dir.empty())
|
||||
{
|
||||
std::string target_icon_path;
|
||||
if (!create_square_shortcut_icon_file(src_icon_path, target_icon_dir, target_icon_path, "ico", 512))
|
||||
if (!create_square_shortcut_icon_file(path, src_icon_path, target_icon_dir, target_icon_path, "ico", 512))
|
||||
return cleanup(false, ".ico creation failed");
|
||||
|
||||
const std::wstring w_icon_path = utf8_to_wchar(target_icon_path);
|
||||
|
|
@ -301,7 +311,7 @@ namespace gui::utils
|
|||
if (!src_icon_path.empty())
|
||||
{
|
||||
std::string target_icon_path = resources_dir;
|
||||
if (!create_square_shortcut_icon_file(src_icon_path, resources_dir, target_icon_path, "icns", 512))
|
||||
if (!create_square_shortcut_icon_file(path, src_icon_path, resources_dir, target_icon_path, "icns", 512))
|
||||
{
|
||||
// Error is logged in create_square_shortcut_icon_file
|
||||
return false;
|
||||
|
|
@ -339,7 +349,7 @@ namespace gui::utils
|
|||
if (!src_icon_path.empty() && !target_icon_dir.empty())
|
||||
{
|
||||
std::string target_icon_path;
|
||||
if (!create_square_shortcut_icon_file(src_icon_path, target_icon_dir, target_icon_path, "png", 512))
|
||||
if (!create_square_shortcut_icon_file(path, src_icon_path, target_icon_dir, target_icon_path, "png", 512))
|
||||
{
|
||||
// Error is logged in create_square_shortcut_icon_file
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ namespace gui::utils
|
|||
};
|
||||
|
||||
bool create_shortcut(const std::string& name,
|
||||
const std::string& path,
|
||||
const std::string& serial,
|
||||
const std::string& target_cli_args,
|
||||
const std::string& description,
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
#include "syntax_highlighter.h"
|
||||
#include "qt_utils.h"
|
||||
|
||||
Highlighter::Highlighter(QTextDocument *parent) : QSyntaxHighlighter(parent)
|
||||
Highlighter::Highlighter(QTextDocument* parent) : QSyntaxHighlighter(parent)
|
||||
{
|
||||
}
|
||||
|
||||
void Highlighter::addRule(const QString &pattern, const QBrush &brush)
|
||||
void Highlighter::addRule(const QString& pattern, const QBrush& brush)
|
||||
{
|
||||
HighlightingRule rule;
|
||||
rule.pattern = QRegularExpression(pattern);
|
||||
|
|
@ -13,14 +13,14 @@ void Highlighter::addRule(const QString &pattern, const QBrush &brush)
|
|||
highlightingRules.append(rule);
|
||||
}
|
||||
|
||||
void Highlighter::highlightBlock(const QString &text)
|
||||
void Highlighter::highlightBlock(const QString& text)
|
||||
{
|
||||
for (const HighlightingRule& rule : highlightingRules)
|
||||
{
|
||||
QRegularExpressionMatchIterator matchIterator = rule.pattern.globalMatch(text);
|
||||
while (matchIterator.hasNext())
|
||||
{
|
||||
QRegularExpressionMatch match = matchIterator.next();
|
||||
const QRegularExpressionMatch match = matchIterator.next();
|
||||
setFormat(match.capturedStart(), match.capturedLength(), rule.format);
|
||||
}
|
||||
}
|
||||
|
|
@ -67,7 +67,7 @@ LogHighlighter::LogHighlighter(QTextDocument* parent) : Highlighter(parent)
|
|||
addRule("^·T.*$", gui::utils::get_label_color("log_level_trace", color, color));
|
||||
}
|
||||
|
||||
AsmHighlighter::AsmHighlighter(QTextDocument *parent) : Highlighter(parent)
|
||||
AsmHighlighter::AsmHighlighter(QTextDocument* parent) : Highlighter(parent)
|
||||
{
|
||||
addRule("^\\b[A-Z0-9]+\\b", Qt::darkBlue); // Instructions
|
||||
addRule("-?R\\d[^,;\\s]*", Qt::darkRed); // -R0.*
|
||||
|
|
@ -78,7 +78,7 @@ AsmHighlighter::AsmHighlighter(QTextDocument *parent) : Highlighter(parent)
|
|||
addRule("#[^\\n]*", Qt::darkGreen); // Single line comment
|
||||
}
|
||||
|
||||
GlslHighlighter::GlslHighlighter(QTextDocument *parent) : Highlighter(parent)
|
||||
GlslHighlighter::GlslHighlighter(QTextDocument* parent) : Highlighter(parent)
|
||||
{
|
||||
const QStringList keywordPatterns = QStringList()
|
||||
// Selection-Iteration-Jump Statements:
|
||||
|
|
@ -183,3 +183,100 @@ GlslHighlighter::GlslHighlighter(QTextDocument *parent) : Highlighter(parent)
|
|||
commentStartExpression = QRegularExpression("/\\*");
|
||||
commentEndExpression = QRegularExpression("\\*/");
|
||||
}
|
||||
|
||||
AnsiHighlighter::AnsiHighlighter(QTextDocument* parent) : Highlighter(parent)
|
||||
{
|
||||
m_escape_format.setForeground(Qt::darkGray);
|
||||
m_escape_format.setFontItalic(true);
|
||||
|
||||
m_foreground_color = gui::utils::get_foreground_color();
|
||||
}
|
||||
|
||||
void AnsiHighlighter::highlightBlock(const QString& text)
|
||||
{
|
||||
QTextCharFormat current_format;
|
||||
current_format.setForeground(m_foreground_color);
|
||||
|
||||
int pos = 0;
|
||||
auto it = ansi_re.globalMatch(text);
|
||||
while (it.hasNext())
|
||||
{
|
||||
const auto match = it.next();
|
||||
const int start = match.capturedStart();
|
||||
const int length = match.capturedLength();
|
||||
|
||||
// Apply current format to the chunk before this escape sequence
|
||||
if (start > pos)
|
||||
{
|
||||
setFormat(pos, start - pos, current_format);
|
||||
}
|
||||
|
||||
// Highlight the escape sequence itself
|
||||
setFormat(start, length, m_escape_format);
|
||||
|
||||
// Parse SGR parameters and update currentFormat
|
||||
const QRegularExpressionMatch pm = param_re.match(match.captured());
|
||||
if (pm.hasMatch())
|
||||
{
|
||||
const QString params = pm.captured(1);
|
||||
if (params.isEmpty())
|
||||
{
|
||||
// empty or just \x1b[m = reset
|
||||
current_format = QTextCharFormat();
|
||||
current_format.setForeground(m_foreground_color);
|
||||
}
|
||||
else
|
||||
{
|
||||
const QStringList codes = params.split(';', Qt::SkipEmptyParts);
|
||||
for (const QString& c : codes)
|
||||
{
|
||||
bool ok = false;
|
||||
const int code = c.toInt(&ok);
|
||||
if (!ok) continue;
|
||||
switch (code)
|
||||
{
|
||||
case 0:
|
||||
current_format = QTextCharFormat();
|
||||
current_format.setForeground(m_foreground_color);
|
||||
break;
|
||||
case 1:
|
||||
current_format.setFontWeight(QFont::Bold);
|
||||
break;
|
||||
case 3:
|
||||
current_format.setFontItalic(true);
|
||||
break;
|
||||
case 4:
|
||||
current_format.setFontUnderline(true);
|
||||
break;
|
||||
case 30: current_format.setForeground(Qt::black); break;
|
||||
case 31: current_format.setForeground(Qt::red); break;
|
||||
case 32: current_format.setForeground(Qt::darkGreen); break;
|
||||
case 33: current_format.setForeground(Qt::darkYellow); break;
|
||||
case 34: current_format.setForeground(Qt::darkBlue); break;
|
||||
case 35: current_format.setForeground(Qt::darkMagenta); break;
|
||||
case 36: current_format.setForeground(Qt::darkCyan); break;
|
||||
case 37: current_format.setForeground(Qt::lightGray); break;
|
||||
case 39: current_format.setForeground(m_foreground_color); break;
|
||||
case 90: current_format.setForeground(Qt::darkGray); break;
|
||||
case 91: current_format.setForeground(Qt::red); break;
|
||||
case 92: current_format.setForeground(Qt::green); break;
|
||||
case 93: current_format.setForeground(Qt::yellow); break;
|
||||
case 94: current_format.setForeground(Qt::blue); break;
|
||||
case 95: current_format.setForeground(Qt::magenta); break;
|
||||
case 96: current_format.setForeground(Qt::cyan); break;
|
||||
case 97: current_format.setForeground(Qt::white); break;
|
||||
// Background and extended colors not yet handled
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pos = start + length;
|
||||
}
|
||||
|
||||
// Apply remaining format
|
||||
if (pos < text.length())
|
||||
setFormat(pos, text.length() - pos, current_format);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include <QSyntaxHighlighter>
|
||||
#include <QRegularExpression>
|
||||
#include <QBrush>
|
||||
|
||||
// Inspired by https://doc.qt.io/qt-5/qtwidgets-richtext-syntaxhighlighter-example.html
|
||||
|
||||
|
|
@ -10,11 +11,11 @@ class Highlighter : public QSyntaxHighlighter
|
|||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit Highlighter(QTextDocument *parent = nullptr);
|
||||
explicit Highlighter(QTextDocument* parent = nullptr);
|
||||
|
||||
protected:
|
||||
void highlightBlock(const QString &text) override;
|
||||
void addRule(const QString &pattern, const QBrush &brush);
|
||||
void highlightBlock(const QString& text) override;
|
||||
void addRule(const QString& pattern, const QBrush& brush);
|
||||
|
||||
struct HighlightingRule
|
||||
{
|
||||
|
|
@ -42,7 +43,7 @@ class AsmHighlighter : public Highlighter
|
|||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit AsmHighlighter(QTextDocument *parent = nullptr);
|
||||
explicit AsmHighlighter(QTextDocument* parent = nullptr);
|
||||
};
|
||||
|
||||
class GlslHighlighter : public Highlighter
|
||||
|
|
@ -50,5 +51,22 @@ class GlslHighlighter : public Highlighter
|
|||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit GlslHighlighter(QTextDocument *parent = nullptr);
|
||||
explicit GlslHighlighter(QTextDocument* parent = nullptr);
|
||||
};
|
||||
|
||||
class AnsiHighlighter : public Highlighter
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit AnsiHighlighter(QTextDocument* parent = nullptr);
|
||||
|
||||
protected:
|
||||
const QRegularExpression ansi_re = QRegularExpression("\x1b\\[[0-9;]*m");
|
||||
const QRegularExpression param_re = QRegularExpression("\x1b\\[([0-9;]*)m");
|
||||
|
||||
QTextCharFormat m_escape_format;
|
||||
QColor m_foreground_color;
|
||||
|
||||
void highlightBlock(const QString& text) override;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -649,9 +649,9 @@ void trophy_manager_dialog::ResizeGameIcons()
|
|||
{
|
||||
const qreal dpr = devicePixelRatioF();
|
||||
const int trophy_index = item->data(GameUserRole::GameIndex).toInt();
|
||||
const QString icon_path = QString::fromStdString(m_trophies_db[trophy_index]->path);
|
||||
QString trophy_icon_path = QString::fromStdString(m_trophies_db[trophy_index]->path);
|
||||
|
||||
item->set_icon_load_func([this, icon_path, localized_icon, trophy_index, cancel = item->icon_loading_aborted(), dpr](int index)
|
||||
item->set_icon_load_func([this, icon_path = std::move(trophy_icon_path), localized_icon, trophy_index, cancel = item->icon_loading_aborted(), dpr](int index)
|
||||
{
|
||||
if (cancel && cancel->load())
|
||||
{
|
||||
|
|
|
|||
|
|
@ -83,12 +83,12 @@ welcome_dialog::welcome_dialog(std::shared_ptr<gui_settings> gui_settings, bool
|
|||
{
|
||||
if (ui->create_desktop_shortcut->isChecked())
|
||||
{
|
||||
gui::utils::create_shortcut("RPCS3", "", "", "RPCS3", ":/rpcs3.svg", fs::get_temp_dir(), gui::utils::shortcut_location::desktop);
|
||||
gui::utils::create_shortcut("RPCS3", "", "", "", "RPCS3", ":/rpcs3.svg", fs::get_temp_dir(), gui::utils::shortcut_location::desktop);
|
||||
}
|
||||
|
||||
if (ui->create_applications_menu_shortcut->isChecked())
|
||||
{
|
||||
gui::utils::create_shortcut("RPCS3", "", "", "RPCS3", ":/rpcs3.svg", fs::get_temp_dir(), gui::utils::shortcut_location::applications);
|
||||
gui::utils::create_shortcut("RPCS3", "", "", "", "RPCS3", ":/rpcs3.svg", fs::get_temp_dir(), gui::utils::shortcut_location::applications);
|
||||
}
|
||||
|
||||
if (ui->use_dark_theme->isChecked() && ui->use_dark_theme->isEnabled()) // if checked and also on initial welcome dialog
|
||||
|
|
|
|||
|
|
@ -347,9 +347,22 @@ namespace utils
|
|||
{
|
||||
if (!codec) return false;
|
||||
|
||||
for (const AVSampleFormat* p = codec->sample_fmts; p && *p != AV_SAMPLE_FMT_NONE; p++)
|
||||
const void* sample_formats = nullptr;
|
||||
int num = 0;
|
||||
|
||||
if (const int err = avcodec_get_supported_config(nullptr, codec, AVCodecConfig::AV_CODEC_CONFIG_SAMPLE_FORMAT, 0, &sample_formats, &num))
|
||||
{
|
||||
if (*p == sample_fmt)
|
||||
media_log.error("check_sample_fmt: avcodec_get_supported_config error: %d='%s'", err, av_error_to_string(err));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!sample_formats)
|
||||
return true; // All supported
|
||||
|
||||
int i = 0;
|
||||
for (const AVSampleFormat* fmt = static_cast<const AVSampleFormat*>(sample_formats); fmt && *fmt != AV_SAMPLE_FMT_NONE && i < num; fmt++, i++)
|
||||
{
|
||||
if (*fmt == sample_fmt)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
|
@ -360,18 +373,33 @@ namespace utils
|
|||
// just pick the highest supported samplerate
|
||||
static int select_sample_rate(const AVCodec* codec)
|
||||
{
|
||||
if (!codec || !codec->supported_samplerates)
|
||||
return 48000;
|
||||
constexpr int default_sample_rate = 48000;
|
||||
|
||||
int best_samplerate = 0;
|
||||
for (const int* samplerate = codec->supported_samplerates; samplerate && *samplerate != 0; samplerate++)
|
||||
if (!codec)
|
||||
return default_sample_rate;
|
||||
|
||||
const void* sample_rates = nullptr;
|
||||
int num = 0;
|
||||
|
||||
if (const int err = avcodec_get_supported_config(nullptr, codec, AVCodecConfig::AV_CODEC_CONFIG_SAMPLE_RATE, 0, &sample_rates, &num))
|
||||
{
|
||||
if (!best_samplerate || abs(48000 - *samplerate) < abs(48000 - best_samplerate))
|
||||
media_log.error("select_sample_rate: avcodec_get_supported_config error: %d='%s'", err, av_error_to_string(err));
|
||||
return default_sample_rate;
|
||||
}
|
||||
|
||||
if (!sample_rates)
|
||||
return default_sample_rate;
|
||||
|
||||
int i = 0;
|
||||
int best_sample_rate = 0;
|
||||
for (const int* sample_rate = static_cast<const int*>(sample_rates); sample_rate && *sample_rate != 0 && i < num; sample_rate++, i++)
|
||||
{
|
||||
if (!best_sample_rate || abs(default_sample_rate - *sample_rate) < abs(default_sample_rate - best_sample_rate))
|
||||
{
|
||||
best_samplerate = *samplerate;
|
||||
best_sample_rate = *sample_rate;
|
||||
}
|
||||
}
|
||||
return best_samplerate;
|
||||
return best_sample_rate;
|
||||
}
|
||||
|
||||
AVChannelLayout get_preferred_channel_layout(int channels)
|
||||
|
|
@ -397,12 +425,25 @@ namespace utils
|
|||
{
|
||||
if (!codec) return nullptr;
|
||||
|
||||
const void* ch_layouts = nullptr;
|
||||
int num = 0;
|
||||
|
||||
if (const int err = avcodec_get_supported_config(nullptr, codec, AVCodecConfig::AV_CODEC_CONFIG_CHANNEL_LAYOUT, 0, &ch_layouts, &num))
|
||||
{
|
||||
media_log.error("select_channel_layout: avcodec_get_supported_config error: %d='%s'", err, av_error_to_string(err));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!ch_layouts)
|
||||
return nullptr;
|
||||
|
||||
const AVChannelLayout preferred_ch_layout = get_preferred_channel_layout(channels);
|
||||
const AVChannelLayout* found_ch_layout = nullptr;
|
||||
|
||||
for (const AVChannelLayout* ch_layout = codec->ch_layouts;
|
||||
ch_layout && memcmp(ch_layout, &empty_ch_layout, sizeof(AVChannelLayout)) != 0;
|
||||
ch_layout++)
|
||||
int i = 0;
|
||||
for (const AVChannelLayout* ch_layout = static_cast<const AVChannelLayout*>(ch_layouts);
|
||||
i < num && ch_layout && memcmp(ch_layout, &empty_ch_layout, sizeof(AVChannelLayout)) != 0;
|
||||
ch_layout++, i++)
|
||||
{
|
||||
media_log.notice("select_channel_layout: listing channel layout '%s' with %d channels", channel_layout_name(*ch_layout), ch_layout->nb_channels);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue