Merge branch 'main' into fontlib

This commit is contained in:
Valdis Bogdāns 2026-01-23 13:19:49 +02:00 committed by GitHub
commit a254fe762f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
50 changed files with 1817 additions and 763 deletions

View File

@ -748,6 +748,8 @@ set(COMMON src/common/logging/backend.cpp
src/common/memory_patcher.cpp
${CMAKE_CURRENT_BINARY_DIR}/src/common/scm_rev.cpp
src/common/scm_rev.h
src/common/key_manager.cpp
src/common/key_manager.h
)
if (ENABLE_DISCORD_RPC)
@ -791,6 +793,8 @@ set(CORE src/core/aerolib/stubs.cpp
src/core/file_format/playgo_chunk.h
src/core/file_format/trp.cpp
src/core/file_format/trp.h
src/core/file_format/npbind.cpp
src/core/file_format/npbind.h
src/core/file_sys/fs.cpp
src/core/file_sys/fs.h
src/core/ipc/ipc.cpp

161
src/common/key_manager.cpp Normal file
View File

@ -0,0 +1,161 @@
// SPDX-FileCopyrightText: Copyright 2025-2026 shadLauncher4 Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <fstream>
#include <iomanip>
#include <iostream>
#include <stdexcept>
#include "common/logging/log.h"
#include "key_manager.h"
#include "path_util.h"
std::shared_ptr<KeyManager> KeyManager::s_instance = nullptr;
std::mutex KeyManager::s_mutex;
// ------------------- Constructor & Singleton -------------------
KeyManager::KeyManager() {
SetDefaultKeys();
}
KeyManager::~KeyManager() {
SaveToFile();
}
std::shared_ptr<KeyManager> KeyManager::GetInstance() {
std::lock_guard<std::mutex> lock(s_mutex);
if (!s_instance)
s_instance = std::make_shared<KeyManager>();
return s_instance;
}
void KeyManager::SetInstance(std::shared_ptr<KeyManager> instance) {
std::lock_guard<std::mutex> lock(s_mutex);
s_instance = instance;
}
// ------------------- Load / Save -------------------
bool KeyManager::LoadFromFile() {
try {
const auto userDir = Common::FS::GetUserPath(Common::FS::PathType::UserDir);
const auto keysPath = userDir / "keys.json";
if (!std::filesystem::exists(keysPath)) {
SetDefaultKeys();
SaveToFile();
LOG_DEBUG(KeyManager, "Created default key file: {}", keysPath.string());
return true;
}
std::ifstream file(keysPath);
if (!file.is_open()) {
LOG_ERROR(KeyManager, "Could not open key file: {}", keysPath.string());
return false;
}
json j;
file >> j;
SetDefaultKeys(); // start from defaults
if (j.contains("TrophyKeySet"))
j.at("TrophyKeySet").get_to(m_keys.TrophyKeySet);
LOG_DEBUG(KeyManager, "Successfully loaded keys from: {}", keysPath.string());
return true;
} catch (const std::exception& e) {
LOG_ERROR(KeyManager, "Error loading keys, using defaults: {}", e.what());
SetDefaultKeys();
return false;
}
}
bool KeyManager::SaveToFile() {
try {
const auto userDir = Common::FS::GetUserPath(Common::FS::PathType::UserDir);
const auto keysPath = userDir / "keys.json";
json j;
KeysToJson(j);
std::ofstream file(keysPath);
if (!file.is_open()) {
LOG_ERROR(KeyManager, "Could not open key file for writing: {}", keysPath.string());
return false;
}
file << std::setw(4) << j;
file.flush();
if (file.fail()) {
LOG_ERROR(KeyManager, "Failed to write keys to: {}", keysPath.string());
return false;
}
LOG_DEBUG(KeyManager, "Successfully saved keys to: {}", keysPath.string());
return true;
} catch (const std::exception& e) {
LOG_ERROR(KeyManager, "Error saving keys: {}", e.what());
return false;
}
}
// ------------------- JSON conversion -------------------
void KeyManager::KeysToJson(json& j) const {
j = m_keys;
}
void KeyManager::JsonToKeys(const json& j) {
json current = m_keys; // serialize current defaults
current.update(j); // merge only fields present in file
m_keys = current.get<AllKeys>(); // deserialize back
}
// ------------------- Defaults / Checks -------------------
void KeyManager::SetDefaultKeys() {
m_keys = AllKeys{};
}
bool KeyManager::HasKeys() const {
return !m_keys.TrophyKeySet.ReleaseTrophyKey.empty();
}
// ------------------- Hex conversion -------------------
std::vector<u8> KeyManager::HexStringToBytes(const std::string& hexStr) {
std::vector<u8> bytes;
if (hexStr.empty())
return bytes;
if (hexStr.size() % 2 != 0)
throw std::runtime_error("Invalid hex string length");
bytes.reserve(hexStr.size() / 2);
auto hexCharToInt = [](char c) -> u8 {
if (c >= '0' && c <= '9')
return c - '0';
if (c >= 'A' && c <= 'F')
return c - 'A' + 10;
if (c >= 'a' && c <= 'f')
return c - 'a' + 10;
throw std::runtime_error("Invalid hex character");
};
for (size_t i = 0; i < hexStr.size(); i += 2) {
u8 high = hexCharToInt(hexStr[i]);
u8 low = hexCharToInt(hexStr[i + 1]);
bytes.push_back((high << 4) | low);
}
return bytes;
}
std::string KeyManager::BytesToHexString(const std::vector<u8>& bytes) {
static const char hexDigits[] = "0123456789ABCDEF";
std::string hexStr;
hexStr.reserve(bytes.size() * 2);
for (u8 b : bytes) {
hexStr.push_back(hexDigits[(b >> 4) & 0xF]);
hexStr.push_back(hexDigits[b & 0xF]);
}
return hexStr;
}

79
src/common/key_manager.h Normal file
View File

@ -0,0 +1,79 @@
// SPDX-FileCopyrightText: Copyright 2025-2026 shadLauncher4 Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <cstdint>
#include <filesystem>
#include <memory>
#include <mutex>
#include <string>
#include <vector>
#include "common/types.h"
#include "nlohmann/json.hpp"
using json = nlohmann::json;
class KeyManager {
public:
// ------------------- Nested keysets -------------------
struct TrophyKeySet {
std::vector<u8> ReleaseTrophyKey;
};
struct AllKeys {
KeyManager::TrophyKeySet TrophyKeySet;
};
// ------------------- Construction -------------------
KeyManager();
~KeyManager();
// ------------------- Singleton -------------------
static std::shared_ptr<KeyManager> GetInstance();
static void SetInstance(std::shared_ptr<KeyManager> instance);
// ------------------- File operations -------------------
bool LoadFromFile();
bool SaveToFile();
// ------------------- Key operations -------------------
void SetDefaultKeys();
bool HasKeys() const;
// ------------------- Getters / Setters -------------------
const AllKeys& GetAllKeys() const {
return m_keys;
}
void SetAllKeys(const AllKeys& keys) {
m_keys = keys;
}
static std::vector<u8> HexStringToBytes(const std::string& hexStr);
static std::string BytesToHexString(const std::vector<u8>& bytes);
private:
void KeysToJson(json& j) const;
void JsonToKeys(const json& j);
AllKeys m_keys{};
static std::shared_ptr<KeyManager> s_instance;
static std::mutex s_mutex;
};
// ------------------- NLOHMANN macros -------------------
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(KeyManager::TrophyKeySet, ReleaseTrophyKey)
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(KeyManager::AllKeys, TrophyKeySet)
namespace nlohmann {
template <>
struct adl_serializer<std::vector<u8>> {
static void to_json(json& j, const std::vector<u8>& vec) {
j = KeyManager::BytesToHexString(vec);
}
static void from_json(const json& j, std::vector<u8>& vec) {
vec = KeyManager::HexStringToBytes(j.get<std::string>());
}
};
} // namespace nlohmann

View File

@ -1,4 +1,5 @@
// SPDX-FileCopyrightText: Copyright 2014 Citra Emulator Project
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
@ -159,6 +160,7 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) {
CLS(ImGui) \
CLS(Input) \
CLS(Tty) \
CLS(KeyManager) \
CLS(Loader)
// GetClassName is a macro defined by Windows.h, grrr...

View File

@ -1,4 +1,5 @@
// SPDX-FileCopyrightText: Copyright 2023 Citra Emulator Project
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@ -127,6 +128,7 @@ enum class Class : u8 {
Loader, ///< ROM loader
Input, ///< Input emulation
Tty, ///< Debug output from emu
KeyManager, ///< Key management system
Count ///< Total number of logging classes
};

View File

@ -93,7 +93,10 @@ static u64 BackingSize = ORBIS_KERNEL_TOTAL_MEM_DEV_PRO;
struct MemoryRegion {
VAddr base;
size_t size;
PAddr phys_base;
u64 size;
u32 prot;
s32 fd;
bool is_mapped;
};
@ -159,7 +162,8 @@ struct AddressSpace::Impl {
// Restrict region size to avoid overly fragmenting the virtual memory space.
if (info.State == MEM_FREE && info.RegionSize > 0x1000000) {
VAddr addr = Common::AlignUp(reinterpret_cast<VAddr>(info.BaseAddress), alignment);
regions.emplace(addr, MemoryRegion{addr, size, false});
regions.emplace(addr,
MemoryRegion{addr, PAddr(-1), size, PAGE_NOACCESS, -1, false});
}
}
@ -207,29 +211,32 @@ struct AddressSpace::Impl {
~Impl() {
if (virtual_base) {
if (!VirtualFree(virtual_base, 0, MEM_RELEASE)) {
LOG_CRITICAL(Render, "Failed to free virtual memory");
LOG_CRITICAL(Core, "Failed to free virtual memory");
}
}
if (backing_base) {
if (!UnmapViewOfFile2(process, backing_base, MEM_PRESERVE_PLACEHOLDER)) {
LOG_CRITICAL(Render, "Failed to unmap backing memory placeholder");
LOG_CRITICAL(Core, "Failed to unmap backing memory placeholder");
}
if (!VirtualFreeEx(process, backing_base, 0, MEM_RELEASE)) {
LOG_CRITICAL(Render, "Failed to free backing memory");
LOG_CRITICAL(Core, "Failed to free backing memory");
}
}
if (!CloseHandle(backing_handle)) {
LOG_CRITICAL(Render, "Failed to free backing memory file handle");
LOG_CRITICAL(Core, "Failed to free backing memory file handle");
}
}
void* Map(VAddr virtual_addr, PAddr phys_addr, size_t size, ULONG prot, uintptr_t fd = 0) {
// Before mapping we must carve a placeholder with the exact properties of our mapping.
auto* region = EnsureSplitRegionForMapping(virtual_addr, size);
region->is_mapped = true;
void* MapRegion(MemoryRegion* region) {
VAddr virtual_addr = region->base;
PAddr phys_addr = region->phys_base;
u64 size = region->size;
ULONG prot = region->prot;
s32 fd = region->fd;
void* ptr = nullptr;
if (phys_addr != -1) {
HANDLE backing = fd ? reinterpret_cast<HANDLE>(fd) : backing_handle;
HANDLE backing = fd != -1 ? reinterpret_cast<HANDLE>(fd) : backing_handle;
if (fd && prot == PAGE_READONLY) {
DWORD resultvar;
ptr = VirtualAlloc2(process, reinterpret_cast<PVOID>(virtual_addr), size,
@ -257,110 +264,136 @@ struct AddressSpace::Impl {
return ptr;
}
void Unmap(VAddr virtual_addr, size_t size, bool has_backing) {
bool ret;
if (has_backing) {
void UnmapRegion(MemoryRegion* region) {
VAddr virtual_addr = region->base;
PAddr phys_base = region->phys_base;
u64 size = region->size;
bool ret = false;
if (phys_base != -1) {
ret = UnmapViewOfFile2(process, reinterpret_cast<PVOID>(virtual_addr),
MEM_PRESERVE_PLACEHOLDER);
} else {
ret = VirtualFreeEx(process, reinterpret_cast<PVOID>(virtual_addr), size,
MEM_RELEASE | MEM_PRESERVE_PLACEHOLDER);
}
ASSERT_MSG(ret, "Unmap operation on virtual_addr={:#X} failed: {}", virtual_addr,
ASSERT_MSG(ret, "Unmap on virtual_addr {:#x}, size {:#x} failed: {}", virtual_addr, size,
Common::GetLastErrorMsg());
// The unmap call will create a new placeholder region. We need to see if we can coalesce it
// with neighbors.
JoinRegionsAfterUnmap(virtual_addr, size);
}
// The following code is inspired from Dolphin's MemArena
// https://github.com/dolphin-emu/dolphin/blob/deee3ee4/Source/Core/Common/MemArenaWin.cpp#L212
MemoryRegion* EnsureSplitRegionForMapping(VAddr address, size_t size) {
// Find closest region that is <= the given address by using upper bound and decrementing
auto it = regions.upper_bound(address);
ASSERT_MSG(it != regions.begin(), "Invalid address {:#x}", address);
--it;
ASSERT_MSG(!it->second.is_mapped,
"Attempt to map {:#x} with size {:#x} which overlaps with {:#x} mapping",
address, size, it->second.base);
auto& [base, region] = *it;
void SplitRegion(VAddr virtual_addr, u64 size) {
// First, get the region this range covers
auto it = std::prev(regions.upper_bound(virtual_addr));
const VAddr mapping_address = region.base;
const size_t region_size = region.size;
if (mapping_address == address) {
// If this region is already split up correctly we don't have to do anything
if (region_size == size) {
return &region;
// All unmapped areas will coalesce, so there should be a region
// containing the full requested range. If not, then something is mapped here.
ASSERT_MSG(it->second.base + it->second.size >= virtual_addr + size,
"Cannot fit region into one placeholder");
// If the region is mapped, we need to unmap first before we can modify the placeholders.
if (it->second.is_mapped) {
ASSERT_MSG(it->second.phys_base != -1 || !it->second.is_mapped,
"Cannot split unbacked mapping");
UnmapRegion(&it->second);
}
// We need to split this region to create a matching placeholder.
if (it->second.base != virtual_addr) {
// Requested address is not the start of the containing region,
// create a new region to represent the memory before the requested range.
auto& region = it->second;
u64 base_offset = virtual_addr - region.base;
u64 next_region_size = region.size - base_offset;
PAddr next_region_phys_base = -1;
if (region.is_mapped) {
next_region_phys_base = region.phys_base + base_offset;
}
region.size = base_offset;
ASSERT_MSG(region_size >= size,
"Region with address {:#x} and size {:#x} can't fit {:#x}", mapping_address,
region_size, size);
// Split the placeholder.
if (!VirtualFreeEx(process, LPVOID(address), size,
// Use VirtualFreeEx to create the split.
if (!VirtualFreeEx(process, LPVOID(region.base), region.size,
MEM_RELEASE | MEM_PRESERVE_PLACEHOLDER)) {
UNREACHABLE_MSG("Region splitting failed: {}", Common::GetLastErrorMsg());
return nullptr;
}
// Update tracked mappings and return the first of the two
// If the mapping was mapped, remap the region.
if (region.is_mapped) {
MapRegion(&region);
}
// Store a new region matching the removed area.
it = regions.emplace_hint(std::next(it), virtual_addr,
MemoryRegion(virtual_addr, next_region_phys_base,
next_region_size, region.prot, region.fd,
region.is_mapped));
}
// At this point, the region's base will match virtual_addr.
// Now check for a size difference.
if (it->second.size != size) {
// The requested size is smaller than the current region placeholder.
// Update region to match the requested region,
// then make a new region to represent the remaining space.
auto& region = it->second;
VAddr next_region_addr = region.base + size;
u64 next_region_size = region.size - size;
PAddr next_region_phys_base = -1;
if (region.is_mapped) {
next_region_phys_base = region.phys_base + size;
}
region.size = size;
const VAddr new_mapping_start = address + size;
regions.emplace_hint(std::next(it), new_mapping_start,
MemoryRegion(new_mapping_start, region_size - size, false));
return &region;
// Store the new region matching the remaining space
regions.emplace_hint(std::next(it), next_region_addr,
MemoryRegion(next_region_addr, next_region_phys_base,
next_region_size, region.prot, region.fd,
region.is_mapped));
// Use VirtualFreeEx to create the split.
if (!VirtualFreeEx(process, LPVOID(region.base), region.size,
MEM_RELEASE | MEM_PRESERVE_PLACEHOLDER)) {
UNREACHABLE_MSG("Region splitting failed: {}", Common::GetLastErrorMsg());
}
// If these regions were mapped, then map the unmapped area beyond the requested range.
if (region.is_mapped) {
MapRegion(&std::next(it)->second);
}
}
ASSERT(mapping_address < address);
// Is there enough space to map this?
const size_t offset_in_region = address - mapping_address;
const size_t minimum_size = size + offset_in_region;
ASSERT(region_size >= minimum_size);
// Split the placeholder.
if (!VirtualFreeEx(process, LPVOID(address), size,
MEM_RELEASE | MEM_PRESERVE_PLACEHOLDER)) {
UNREACHABLE_MSG("Region splitting failed: {}", Common::GetLastErrorMsg());
return nullptr;
}
// Do we now have two regions or three regions?
if (region_size == minimum_size) {
// Split into two; update tracked mappings and return the second one
region.size = offset_in_region;
it = regions.emplace_hint(std::next(it), address, MemoryRegion(address, size, false));
return &it->second;
} else {
// Split into three; update tracked mappings and return the middle one
region.size = offset_in_region;
const VAddr middle_mapping_start = address;
const size_t middle_mapping_size = size;
const VAddr after_mapping_start = address + size;
const size_t after_mapping_size = region_size - minimum_size;
it = regions.emplace_hint(std::next(it), after_mapping_start,
MemoryRegion(after_mapping_start, after_mapping_size, false));
it = regions.emplace_hint(
it, middle_mapping_start,
MemoryRegion(middle_mapping_start, middle_mapping_size, false));
return &it->second;
// If the requested region was mapped, remap it.
if (it->second.is_mapped) {
MapRegion(&it->second);
}
}
void JoinRegionsAfterUnmap(VAddr address, size_t size) {
// There should be a mapping that matches the request exactly, find it
auto it = regions.find(address);
ASSERT_MSG(it != regions.end() && it->second.size == size,
"Invalid address/size given to unmap.");
void* Map(VAddr virtual_addr, PAddr phys_addr, u64 size, ULONG prot, s32 fd = -1) {
// Split surrounding regions to create a placeholder
SplitRegion(virtual_addr, size);
// Get the region this range covers
auto it = std::prev(regions.upper_bound(virtual_addr));
auto& [base, region] = *it;
region.is_mapped = false;
ASSERT_MSG(!region.is_mapped, "Cannot overwrite mapped region");
// Now we have a region matching the requested region, perform the actual mapping.
region.is_mapped = true;
region.phys_base = phys_addr;
region.prot = prot;
region.fd = fd;
return MapRegion(&region);
}
void CoalesceFreeRegions(VAddr virtual_addr) {
// First, get the region to update
auto it = std::prev(regions.upper_bound(virtual_addr));
ASSERT_MSG(!it->second.is_mapped, "Cannot coalesce mapped regions");
// Check if a placeholder exists right before us.
auto it_prev = it != regions.begin() ? std::prev(it) : regions.end();
if (it_prev != regions.end() && !it_prev->second.is_mapped) {
const size_t total_size = it_prev->second.size + size;
const u64 total_size = it_prev->second.size + it->second.size;
if (!VirtualFreeEx(process, LPVOID(it_prev->first), total_size,
MEM_RELEASE | MEM_COALESCE_PLACEHOLDERS)) {
UNREACHABLE_MSG("Region coalescing failed: {}", Common::GetLastErrorMsg());
@ -374,7 +407,7 @@ struct AddressSpace::Impl {
// Check if a placeholder exists right after us.
auto it_next = std::next(it);
if (it_next != regions.end() && !it_next->second.is_mapped) {
const size_t total_size = it->second.size + it_next->second.size;
const u64 total_size = it->second.size + it_next->second.size;
if (!VirtualFreeEx(process, LPVOID(it->first), total_size,
MEM_RELEASE | MEM_COALESCE_PLACEHOLDERS)) {
UNREACHABLE_MSG("Region coalescing failed: {}", Common::GetLastErrorMsg());
@ -385,7 +418,47 @@ struct AddressSpace::Impl {
}
}
void Protect(VAddr virtual_addr, size_t size, bool read, bool write, bool execute) {
void Unmap(VAddr virtual_addr, u64 size) {
// Loop through all regions in the requested range
u64 remaining_size = size;
VAddr current_addr = virtual_addr;
while (remaining_size > 0) {
// Get the region containing our current address.
auto it = std::prev(regions.upper_bound(current_addr));
// If necessary, split regions to ensure a valid unmap.
// To prevent complication, ensure size is within the bounds of the current region.
u64 base_offset = current_addr - it->second.base;
u64 size_to_unmap = std::min<u64>(it->second.size - base_offset, remaining_size);
if (current_addr != it->second.base || size_to_unmap != it->second.size) {
SplitRegion(current_addr, size_to_unmap);
}
// Repair the region pointer, as SplitRegion modifies the regions map.
it = std::prev(regions.upper_bound(current_addr));
auto& [base, region] = *it;
// Unmap the region if it was previously mapped
if (region.is_mapped) {
UnmapRegion(&region);
}
// Update region data
region.is_mapped = false;
region.fd = -1;
region.phys_base = -1;
region.prot = PAGE_NOACCESS;
// Coalesce any free space
CoalesceFreeRegions(current_addr);
// Update loop variables
remaining_size -= size_to_unmap;
current_addr += size_to_unmap;
}
}
void Protect(VAddr virtual_addr, u64 size, bool read, bool write, bool execute) {
DWORD new_flags{};
if (write && !read) {
@ -415,7 +488,7 @@ struct AddressSpace::Impl {
// If no flags are assigned, then something's gone wrong.
if (new_flags == 0) {
LOG_CRITICAL(Common_Memory,
LOG_CRITICAL(Core,
"Unsupported protection flag combination for address {:#x}, size {}, "
"read={}, write={}, execute={}",
virtual_addr, size, read, write, execute);
@ -429,8 +502,8 @@ struct AddressSpace::Impl {
continue;
}
const auto& region = it->second;
const size_t range_addr = std::max(region.base, virtual_addr);
const size_t range_size = std::min(region.base + region.size, virtual_end) - range_addr;
const u64 range_addr = std::max(region.base, virtual_addr);
const u64 range_size = std::min(region.base + region.size, virtual_end) - range_addr;
DWORD old_flags{};
if (!VirtualProtectEx(process, LPVOID(range_addr), range_size, new_flags, &old_flags)) {
UNREACHABLE_MSG(
@ -453,11 +526,11 @@ struct AddressSpace::Impl {
u8* backing_base{};
u8* virtual_base{};
u8* system_managed_base{};
size_t system_managed_size{};
u64 system_managed_size{};
u8* system_reserved_base{};
size_t system_reserved_size{};
u64 system_reserved_size{};
u8* user_base{};
size_t user_size{};
u64 user_size{};
std::map<VAddr, MemoryRegion> regions;
};
#else
@ -601,7 +674,7 @@ struct AddressSpace::Impl {
}
}
void* Map(VAddr virtual_addr, PAddr phys_addr, size_t size, PosixPageProtection prot,
void* Map(VAddr virtual_addr, PAddr phys_addr, u64 size, PosixPageProtection prot,
int fd = -1) {
m_free_regions.subtract({virtual_addr, virtual_addr + size});
const int handle = phys_addr != -1 ? (fd == -1 ? backing_fd : fd) : -1;
@ -613,10 +686,10 @@ struct AddressSpace::Impl {
return ret;
}
void Unmap(VAddr virtual_addr, size_t size, bool) {
void Unmap(VAddr virtual_addr, u64 size, bool) {
// Check to see if we are adjacent to any regions.
auto start_address = virtual_addr;
auto end_address = start_address + size;
VAddr start_address = virtual_addr;
VAddr end_address = start_address + size;
auto it = m_free_regions.find({start_address - 1, end_address + 1});
// If we are, join with them, ensuring we stay in bounds.
@ -634,7 +707,7 @@ struct AddressSpace::Impl {
ASSERT_MSG(ret != MAP_FAILED, "mmap failed: {}", strerror(errno));
}
void Protect(VAddr virtual_addr, size_t size, bool read, bool write, bool execute) {
void Protect(VAddr virtual_addr, u64 size, bool read, bool write, bool execute) {
int flags = PROT_NONE;
if (read) {
flags |= PROT_READ;
@ -654,11 +727,11 @@ struct AddressSpace::Impl {
int backing_fd;
u8* backing_base{};
u8* system_managed_base{};
size_t system_managed_size{};
u64 system_managed_size{};
u8* system_reserved_base{};
size_t system_reserved_size{};
u64 system_reserved_size{};
u8* user_base{};
size_t user_size{};
u64 user_size{};
boost::icl::interval_set<VAddr> m_free_regions;
};
#endif
@ -675,8 +748,7 @@ AddressSpace::AddressSpace() : impl{std::make_unique<Impl>()} {
AddressSpace::~AddressSpace() = default;
void* AddressSpace::Map(VAddr virtual_addr, size_t size, u64 alignment, PAddr phys_addr,
bool is_exec) {
void* AddressSpace::Map(VAddr virtual_addr, u64 size, PAddr phys_addr, bool is_exec) {
#if ARCH_X86_64
const auto prot = is_exec ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE;
#else
@ -687,8 +759,7 @@ void* AddressSpace::Map(VAddr virtual_addr, size_t size, u64 alignment, PAddr ph
return impl->Map(virtual_addr, phys_addr, size, prot);
}
void* AddressSpace::MapFile(VAddr virtual_addr, size_t size, size_t offset, u32 prot,
uintptr_t fd) {
void* AddressSpace::MapFile(VAddr virtual_addr, u64 size, u64 offset, u32 prot, uintptr_t fd) {
#ifdef _WIN32
return impl->Map(virtual_addr, offset, size,
ToWindowsProt(std::bit_cast<Core::MemoryProt>(prot)), fd);
@ -698,31 +769,15 @@ void* AddressSpace::MapFile(VAddr virtual_addr, size_t size, size_t offset, u32
#endif
}
void AddressSpace::Unmap(VAddr virtual_addr, size_t size, VAddr start_in_vma, VAddr end_in_vma,
PAddr phys_base, bool is_exec, bool has_backing, bool readonly_file) {
void AddressSpace::Unmap(VAddr virtual_addr, u64 size, bool has_backing) {
#ifdef _WIN32
// There does not appear to be comparable support for partial unmapping on Windows.
// Unfortunately, a least one title was found to require this. The workaround is to unmap
// the entire allocation and remap the portions outside of the requested unmapping range.
impl->Unmap(virtual_addr, size, has_backing && !readonly_file);
// TODO: Determine if any titles require partial unmapping support for un-backed allocations.
ASSERT_MSG(has_backing || (start_in_vma == 0 && end_in_vma == size),
"Partial unmapping of un-backed allocations is not supported");
if (start_in_vma != 0) {
Map(virtual_addr, start_in_vma, 0, phys_base, is_exec);
}
if (end_in_vma != size) {
Map(virtual_addr + end_in_vma, size - end_in_vma, 0, phys_base + end_in_vma, is_exec);
}
impl->Unmap(virtual_addr, size);
#else
impl->Unmap(virtual_addr + start_in_vma, end_in_vma - start_in_vma, has_backing);
impl->Unmap(virtual_addr, size, has_backing);
#endif
}
void AddressSpace::Protect(VAddr virtual_addr, size_t size, MemoryPermission perms) {
void AddressSpace::Protect(VAddr virtual_addr, u64 size, MemoryPermission perms) {
const bool read = True(perms & MemoryPermission::Read);
const bool write = True(perms & MemoryPermission::Write);
const bool execute = True(perms & MemoryPermission::Execute);

View File

@ -39,7 +39,7 @@ public:
[[nodiscard]] const u8* SystemManagedVirtualBase() const noexcept {
return system_managed_base;
}
[[nodiscard]] size_t SystemManagedVirtualSize() const noexcept {
[[nodiscard]] u64 SystemManagedVirtualSize() const noexcept {
return system_managed_size;
}
@ -49,7 +49,7 @@ public:
[[nodiscard]] const u8* SystemReservedVirtualBase() const noexcept {
return system_reserved_base;
}
[[nodiscard]] size_t SystemReservedVirtualSize() const noexcept {
[[nodiscard]] u64 SystemReservedVirtualSize() const noexcept {
return system_reserved_size;
}
@ -59,7 +59,7 @@ public:
[[nodiscard]] const u8* UserVirtualBase() const noexcept {
return user_base;
}
[[nodiscard]] size_t UserVirtualSize() const noexcept {
[[nodiscard]] u64 UserVirtualSize() const noexcept {
return user_size;
}
@ -73,17 +73,15 @@ public:
* If zero is provided the mapping is considered as private.
* @return A pointer to the mapped memory.
*/
void* Map(VAddr virtual_addr, size_t size, u64 alignment = 0, PAddr phys_addr = -1,
bool exec = false);
void* Map(VAddr virtual_addr, u64 size, PAddr phys_addr = -1, bool exec = false);
/// Memory maps a specified file descriptor.
void* MapFile(VAddr virtual_addr, size_t size, size_t offset, u32 prot, uintptr_t fd);
void* MapFile(VAddr virtual_addr, u64 size, u64 offset, u32 prot, uintptr_t fd);
/// Unmaps specified virtual memory area.
void Unmap(VAddr virtual_addr, size_t size, VAddr start_in_vma, VAddr end_in_vma,
PAddr phys_base, bool is_exec, bool has_backing, bool readonly_file);
void Unmap(VAddr virtual_addr, u64 size, bool has_backing);
void Protect(VAddr virtual_addr, size_t size, MemoryPermission perms);
void Protect(VAddr virtual_addr, u64 size, MemoryPermission perms);
// Returns an interval set containing all usable regions.
boost::icl::interval_set<VAddr> GetUsableRegions();
@ -93,11 +91,11 @@ private:
std::unique_ptr<Impl> impl;
u8* backing_base{};
u8* system_managed_base{};
size_t system_managed_size{};
u64 system_managed_size{};
u8* system_reserved_base{};
size_t system_reserved_size{};
u64 system_reserved_size{};
u8* user_base{};
size_t user_size{};
u64 user_size{};
};
} // namespace Core

View File

@ -32,7 +32,7 @@ bool MemoryMapViewer::Iterator::DrawLine() {
TableNextColumn();
Text("%s", magic_enum::enum_name(m.prot).data());
TableNextColumn();
if (m.is_exec) {
if (True(m.prot & MemoryProt::CpuExec)) {
Text("X");
}
TableNextColumn();
@ -44,7 +44,7 @@ bool MemoryMapViewer::Iterator::DrawLine() {
return false;
}
auto m = dmem.it->second;
if (m.dma_type == DMAType::Free) {
if (m.dma_type == PhysicalMemoryType::Free) {
++dmem.it;
return DrawLine();
}
@ -56,7 +56,8 @@ bool MemoryMapViewer::Iterator::DrawLine() {
auto type = static_cast<::Libraries::Kernel::MemoryTypes>(m.memory_type);
Text("%s", magic_enum::enum_name(type).data());
TableNextColumn();
Text("%d", m.dma_type == DMAType::Pooled || m.dma_type == DMAType::Committed);
Text("%d",
m.dma_type == PhysicalMemoryType::Pooled || m.dma_type == PhysicalMemoryType::Committed);
++dmem.it;
return true;
}

View File

@ -11,8 +11,8 @@ class MemoryMapViewer {
struct Iterator {
bool is_vma;
struct {
MemoryManager::DMemMap::iterator it;
MemoryManager::DMemMap::iterator end;
MemoryManager::PhysMap::iterator it;
MemoryManager::PhysMap::iterator end;
} dmem;
struct {
MemoryManager::VMAMap::iterator it;

View File

@ -0,0 +1,114 @@
// SPDX-FileCopyrightText: Copyright 2025-2026 shadLauncher4 Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <cstring>
#include <fstream>
#include <iomanip>
#include <sstream>
#include <vector>
#include "npbind.h"
bool NPBindFile::Load(const std::string& path) {
Clear(); // Clear any existing data
std::ifstream f(path, std::ios::binary | std::ios::ate);
if (!f)
return false;
std::streamsize sz = f.tellg();
if (sz <= 0)
return false;
f.seekg(0, std::ios::beg);
std::vector<u8> buf(static_cast<size_t>(sz));
if (!f.read(reinterpret_cast<char*>(buf.data()), sz))
return false;
const u64 size = buf.size();
if (size < sizeof(NpBindHeader))
return false;
// Read header
memcpy(&m_header, buf.data(), sizeof(NpBindHeader));
if (m_header.magic != NPBIND_MAGIC)
return false;
// offset start of bodies
size_t offset = sizeof(NpBindHeader);
m_bodies.reserve(static_cast<size_t>(m_header.num_entries));
// For each body: read 4 TLV entries then skip padding (0x98 = 152 bytes)
const u64 body_padding = 0x98; // 152
for (u64 bi = 0; bi < m_header.num_entries; ++bi) {
// Ensure we have room for 4 entries' headers at least
if (offset + 4 * 4 > size)
return false; // 4 entries x (type+size)
NPBindBody body;
// helper lambda to read one entry
auto read_entry = [&](NPBindEntryRaw& e) -> bool {
if (offset + 4 > size)
return false;
memcpy(&e.type, &buf[offset], 2);
memcpy(&e.size, &buf[offset + 2], 2);
offset += 4;
if (offset + e.size > size)
return false;
e.data.assign(buf.begin() + offset, buf.begin() + offset + e.size);
offset += e.size;
return true;
};
// read 4 entries in order
if (!read_entry(body.npcommid))
return false;
if (!read_entry(body.trophy))
return false;
if (!read_entry(body.unk1))
return false;
if (!read_entry(body.unk2))
return false;
// skip fixed padding after body if present (but don't overrun)
if (offset + body_padding <= size) {
offset += body_padding;
} else {
// If padding not fully present, allow file to end (some variants may omit)
offset = size;
}
m_bodies.push_back(std::move(body));
}
// Read digest if available
if (size >= 20) {
// Digest is typically the last 20 bytes, independent of offset
memcpy(m_digest, &buf[size - 20], 20);
} else {
memset(m_digest, 0, 20);
}
return true;
}
std::vector<std::string> NPBindFile::GetNpCommIds() const {
std::vector<std::string> npcommids;
npcommids.reserve(m_bodies.size());
for (const auto& body : m_bodies) {
// Convert binary data to string directly
if (!body.npcommid.data.empty()) {
std::string raw_string(reinterpret_cast<const char*>(body.npcommid.data.data()),
body.npcommid.data.size());
npcommids.push_back(raw_string);
}
}
return npcommids;
}

View File

@ -0,0 +1,87 @@
// SPDX-FileCopyrightText: Copyright 2025-2026 shadLauncher4 Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <cstdint>
#include <string>
#include <vector>
#include "common/endian.h"
#include "common/types.h"
#define NPBIND_MAGIC 0xD294A018u
#pragma pack(push, 1)
struct NpBindHeader {
u32_be magic;
u32_be version;
u64_be file_size;
u64_be entry_size;
u64_be num_entries;
char padding[0x60]; // 96 bytes
};
#pragma pack(pop)
struct NPBindEntryRaw {
u16_be type;
u16_be size; // includes internal padding
std::vector<u8> data;
};
struct NPBindBody {
NPBindEntryRaw npcommid; // expected type 0x0010, size 12
NPBindEntryRaw trophy; // expected type 0x0011, size 12
NPBindEntryRaw unk1; // expected type 0x0012, size 176
NPBindEntryRaw unk2; // expected type 0x0013, size 16
// The 0x98 padding after these entries is skipped while parsing
};
class NPBindFile {
private:
NpBindHeader m_header;
std::vector<NPBindBody> m_bodies;
u8 m_digest[20]; // zeroed if absent
public:
NPBindFile() {
memset(m_digest, 0, sizeof(m_digest));
}
// Load from file
bool Load(const std::string& path);
// Accessors
const NpBindHeader& Header() const {
return m_header;
}
const std::vector<NPBindBody>& Bodies() const {
return m_bodies;
}
const u8* Digest() const {
return m_digest;
}
// Get npcommid data
std::vector<std::string> GetNpCommIds() const;
// Get specific body
const NPBindBody& GetBody(size_t index) const {
return m_bodies.at(index);
}
// Get number of bodies
u64 BodyCount() const {
return m_bodies.size();
}
// Check if file was loaded successfully
bool IsValid() const {
return m_header.magic == NPBIND_MAGIC;
}
// Clear all data
void Clear() {
m_header = NpBindHeader{};
m_bodies.clear();
memset(m_digest, 0, sizeof(m_digest));
}
};

View File

@ -1,44 +1,31 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/aes.h"
#include "common/config.h"
#include "common/key_manager.h"
#include "common/logging/log.h"
#include "common/path_util.h"
#include "core/file_format/npbind.h"
#include "core/file_format/trp.h"
static void DecryptEFSM(std::span<u8, 16> trophyKey, std::span<u8, 16> NPcommID,
std::span<u8, 16> efsmIv, std::span<u8> ciphertext,
static void DecryptEFSM(std::span<const u8, 16> trophyKey, std::span<const u8, 16> NPcommID,
std::span<const u8, 16> efsmIv, std::span<const u8> ciphertext,
std::span<u8> decrypted) {
// Step 1: Encrypt NPcommID
std::array<u8, 16> trophyIv{};
std::array<u8, 16> trpKey;
// Convert spans to pointers for the aes functions
aes::encrypt_cbc(NPcommID.data(), NPcommID.size(), trophyKey.data(), trophyKey.size(),
trophyIv.data(), trpKey.data(), trpKey.size(), false);
// Step 2: Decrypt EFSM
aes::decrypt_cbc(ciphertext.data(), ciphertext.size(), trpKey.data(), trpKey.size(),
efsmIv.data(), decrypted.data(), decrypted.size(), nullptr);
const_cast<u8*>(efsmIv.data()), decrypted.data(), decrypted.size(), nullptr);
}
TRP::TRP() = default;
TRP::~TRP() = default;
void TRP::GetNPcommID(const std::filesystem::path& trophyPath, int index) {
std::filesystem::path trpPath = trophyPath / "sce_sys/npbind.dat";
Common::FS::IOFile npbindFile(trpPath, Common::FS::FileAccessMode::Read);
if (!npbindFile.IsOpen()) {
LOG_CRITICAL(Common_Filesystem, "Failed to open npbind.dat file");
return;
}
if (!npbindFile.Seek(0x84 + (index * 0x180))) {
LOG_CRITICAL(Common_Filesystem, "Failed to seek to NPbind offset");
return;
}
npbindFile.ReadRaw<u8>(np_comm_id.data(), 12);
std::fill(np_comm_id.begin() + 12, np_comm_id.end(), 0); // fill with 0, we need 16 bytes.
}
static void removePadding(std::vector<u8>& vec) {
for (auto it = vec.rbegin(); it != vec.rend(); ++it) {
if (*it == '>') {
@ -63,91 +50,232 @@ bool TRP::Extract(const std::filesystem::path& trophyPath, const std::string tit
return false;
}
const auto user_key_str = Config::getTrophyKey();
if (user_key_str.size() != 32) {
const auto& user_key_vec =
KeyManager::GetInstance()->GetAllKeys().TrophyKeySet.ReleaseTrophyKey;
if (user_key_vec.size() != 16) {
LOG_INFO(Common_Filesystem, "Trophy decryption key is not specified");
return false;
}
std::array<u8, 16> user_key{};
hexToBytes(user_key_str.c_str(), user_key.data());
std::copy(user_key_vec.begin(), user_key_vec.end(), user_key.begin());
for (int index = 0; const auto& it : std::filesystem::directory_iterator(gameSysDir)) {
if (it.is_regular_file()) {
GetNPcommID(trophyPath, index);
// Load npbind.dat using the new class
std::filesystem::path npbindPath = trophyPath / "sce_sys/npbind.dat";
NPBindFile npbind;
if (!npbind.Load(npbindPath.string())) {
LOG_WARNING(Common_Filesystem, "Failed to load npbind.dat file");
}
auto npCommIds = npbind.GetNpCommIds();
if (npCommIds.empty()) {
LOG_WARNING(Common_Filesystem, "No NPComm IDs found in npbind.dat");
}
bool success = true;
int trpFileIndex = 0;
try {
// Process each TRP file in the trophy directory
for (const auto& it : std::filesystem::directory_iterator(gameSysDir)) {
if (!it.is_regular_file() || it.path().extension() != ".trp") {
continue; // Skip non-TRP files
}
// Get NPCommID for this TRP file (if available)
std::string npCommId;
if (trpFileIndex < static_cast<int>(npCommIds.size())) {
npCommId = npCommIds[trpFileIndex];
LOG_DEBUG(Common_Filesystem, "Using NPCommID: {} for {}", npCommId,
it.path().filename().string());
} else {
LOG_WARNING(Common_Filesystem, "No NPCommID found for TRP file index {}",
trpFileIndex);
}
Common::FS::IOFile file(it.path(), Common::FS::FileAccessMode::Read);
if (!file.IsOpen()) {
LOG_CRITICAL(Common_Filesystem, "Unable to open trophy file for read");
return false;
LOG_ERROR(Common_Filesystem, "Unable to open trophy file: {}", it.path().string());
success = false;
continue;
}
TrpHeader header;
file.Read(header);
if (header.magic != 0xDCA24D00) {
LOG_CRITICAL(Common_Filesystem, "Wrong trophy magic number");
return false;
if (!file.Read(header)) {
LOG_ERROR(Common_Filesystem, "Failed to read TRP header from {}",
it.path().string());
success = false;
continue;
}
if (header.magic != TRP_MAGIC) {
LOG_ERROR(Common_Filesystem, "Wrong trophy magic number in {}", it.path().string());
success = false;
continue;
}
s64 seekPos = sizeof(TrpHeader);
std::filesystem::path trpFilesPath(
Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / titleId /
"TrophyFiles" / it.path().stem());
std::filesystem::create_directories(trpFilesPath / "Icons");
std::filesystem::create_directory(trpFilesPath / "Xml");
// Create output directories
if (!std::filesystem::create_directories(trpFilesPath / "Icons") ||
!std::filesystem::create_directories(trpFilesPath / "Xml")) {
LOG_ERROR(Common_Filesystem, "Failed to create output directories for {}", titleId);
success = false;
continue;
}
// Process each entry in the TRP file
for (int i = 0; i < header.entry_num; i++) {
if (!file.Seek(seekPos)) {
LOG_CRITICAL(Common_Filesystem, "Failed to seek to TRP entry offset");
return false;
LOG_ERROR(Common_Filesystem, "Failed to seek to TRP entry offset");
success = false;
break;
}
seekPos += (s64)header.entry_size;
seekPos += static_cast<s64>(header.entry_size);
TrpEntry entry;
file.Read(entry);
std::string_view name(entry.entry_name);
if (entry.flag == 0) { // PNG
if (!file.Seek(entry.entry_pos)) {
LOG_CRITICAL(Common_Filesystem, "Failed to seek to TRP entry offset");
return false;
}
std::vector<u8> icon(entry.entry_len);
file.Read(icon);
Common::FS::IOFile::WriteBytes(trpFilesPath / "Icons" / name, icon);
if (!file.Read(entry)) {
LOG_ERROR(Common_Filesystem, "Failed to read TRP entry");
success = false;
break;
}
if (entry.flag == 3 && np_comm_id[0] == 'N' &&
np_comm_id[1] == 'P') { // ESFM, encrypted.
if (!file.Seek(entry.entry_pos)) {
LOG_CRITICAL(Common_Filesystem, "Failed to seek to TRP entry offset");
return false;
std::string_view name(entry.entry_name);
if (entry.flag == ENTRY_FLAG_PNG) {
if (!ProcessPngEntry(file, entry, trpFilesPath, name)) {
success = false;
// Continue with next entry
}
file.Read(esfmIv); // get iv key.
// Skip the first 16 bytes which are the iv key on every entry as we want a
// clean xml file.
std::vector<u8> ESFM(entry.entry_len - iv_len);
std::vector<u8> XML(entry.entry_len - iv_len);
if (!file.Seek(entry.entry_pos + iv_len)) {
LOG_CRITICAL(Common_Filesystem, "Failed to seek to TRP entry + iv offset");
return false;
}
file.Read(ESFM);
DecryptEFSM(user_key, np_comm_id, esfmIv, ESFM, XML); // decrypt
removePadding(XML);
std::string xml_name = entry.entry_name;
size_t pos = xml_name.find("ESFM");
if (pos != std::string::npos)
xml_name.replace(pos, xml_name.length(), "XML");
std::filesystem::path path = trpFilesPath / "Xml" / xml_name;
size_t written = Common::FS::IOFile::WriteBytes(path, XML);
if (written != XML.size()) {
LOG_CRITICAL(
Common_Filesystem,
"Trophy XML {} write failed, wanted to write {} bytes, wrote {}",
fmt::UTF(path.u8string()), XML.size(), written);
} else if (entry.flag == ENTRY_FLAG_ENCRYPTED_XML) {
// Check if we have a valid NPCommID for decryption
if (npCommId.size() >= 12 && npCommId[0] == 'N' && npCommId[1] == 'P') {
if (!ProcessEncryptedXmlEntry(file, entry, trpFilesPath, name, user_key,
npCommId)) {
success = false;
// Continue with next entry
}
} else {
LOG_WARNING(Common_Filesystem,
"Skipping encrypted XML entry - invalid NPCommID");
// Skip this entry but continue
}
} else {
LOG_DEBUG(Common_Filesystem, "Unknown entry flag: {} for {}",
static_cast<unsigned int>(entry.flag), name);
}
}
trpFileIndex++;
}
index++;
} catch (const std::filesystem::filesystem_error& e) {
LOG_CRITICAL(Common_Filesystem, "Filesystem error during trophy extraction: {}", e.what());
return false;
} catch (const std::exception& e) {
LOG_CRITICAL(Common_Filesystem, "Error during trophy extraction: {}", e.what());
return false;
}
if (success) {
LOG_INFO(Common_Filesystem, "Successfully extracted {} trophy files for {}", trpFileIndex,
titleId);
}
return success;
}
bool TRP::ProcessPngEntry(Common::FS::IOFile& file, const TrpEntry& entry,
const std::filesystem::path& outputPath, std::string_view name) {
if (!file.Seek(entry.entry_pos)) {
LOG_ERROR(Common_Filesystem, "Failed to seek to PNG entry offset");
return false;
}
std::vector<u8> icon(entry.entry_len);
if (!file.Read(icon)) {
LOG_ERROR(Common_Filesystem, "Failed to read PNG data");
return false;
}
auto outputFile = outputPath / "Icons" / name;
size_t written = Common::FS::IOFile::WriteBytes(outputFile, icon);
if (written != icon.size()) {
LOG_ERROR(Common_Filesystem, "PNG write failed: wanted {} bytes, wrote {}", icon.size(),
written);
return false;
}
return true;
}
bool TRP::ProcessEncryptedXmlEntry(Common::FS::IOFile& file, const TrpEntry& entry,
const std::filesystem::path& outputPath, std::string_view name,
const std::array<u8, 16>& user_key,
const std::string& npCommId) {
constexpr size_t IV_LEN = 16;
if (!file.Seek(entry.entry_pos)) {
LOG_ERROR(Common_Filesystem, "Failed to seek to encrypted XML entry offset");
return false;
}
std::array<u8, IV_LEN> esfmIv;
if (!file.Read(esfmIv)) {
LOG_ERROR(Common_Filesystem, "Failed to read IV for encrypted XML");
return false;
}
if (entry.entry_len <= IV_LEN) {
LOG_ERROR(Common_Filesystem, "Encrypted XML entry too small");
return false;
}
// Skip to the encrypted data (after IV)
if (!file.Seek(entry.entry_pos + IV_LEN)) {
LOG_ERROR(Common_Filesystem, "Failed to seek to encrypted data");
return false;
}
std::vector<u8> ESFM(entry.entry_len - IV_LEN);
std::vector<u8> XML(entry.entry_len - IV_LEN);
if (!file.Read(ESFM)) {
LOG_ERROR(Common_Filesystem, "Failed to read encrypted XML data");
return false;
}
// Decrypt the data - FIX: Don't check return value since DecryptEFSM returns void
std::span<const u8, 16> key_span(user_key);
// Convert npCommId string to span (pad or truncate to 16 bytes)
std::array<u8, 16> npcommid_array{};
size_t copy_len = std::min(npCommId.size(), npcommid_array.size());
std::memcpy(npcommid_array.data(), npCommId.data(), copy_len);
std::span<const u8, 16> npcommid_span(npcommid_array);
DecryptEFSM(key_span, npcommid_span, esfmIv, ESFM, XML);
// Remove padding
removePadding(XML);
// Create output filename (replace ESFM with XML)
std::string xml_name(entry.entry_name);
size_t pos = xml_name.find("ESFM");
if (pos != std::string::npos) {
xml_name.replace(pos, 4, "XML");
}
auto outputFile = outputPath / "Xml" / xml_name;
size_t written = Common::FS::IOFile::WriteBytes(outputFile, XML);
if (written != XML.size()) {
LOG_ERROR(Common_Filesystem, "XML write failed: wanted {} bytes, wrote {}", XML.size(),
written);
return false;
}
return true;
}

View File

@ -8,6 +8,10 @@
#include "common/io_file.h"
#include "common/types.h"
static constexpr u32 TRP_MAGIC = 0xDCA24D00;
static constexpr u8 ENTRY_FLAG_PNG = 0;
static constexpr u8 ENTRY_FLAG_ENCRYPTED_XML = 3;
struct TrpHeader {
u32_be magic; // (0xDCA24D00)
u32_be version;
@ -33,9 +37,14 @@ public:
TRP();
~TRP();
bool Extract(const std::filesystem::path& trophyPath, const std::string titleId);
void GetNPcommID(const std::filesystem::path& trophyPath, int index);
private:
bool ProcessPngEntry(Common::FS::IOFile& file, const TrpEntry& entry,
const std::filesystem::path& outputPath, std::string_view name);
bool ProcessEncryptedXmlEntry(Common::FS::IOFile& file, const TrpEntry& entry,
const std::filesystem::path& outputPath, std::string_view name,
const std::array<u8, 16>& user_key, const std::string& npCommId);
std::vector<u8> NPcommID = std::vector<u8>(12);
std::array<u8, 16> np_comm_id{};
std::array<u8, 16> esfmIv{};

View File

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <cmath>
@ -345,7 +345,7 @@ int PS4_SYSV_ABI sceAppContentInitialize(const OrbisAppContentInitParam* initPar
if (addcont_count > 0) {
SystemService::OrbisSystemServiceEvent event{};
event.event_type = SystemService::OrbisSystemServiceEventType::EntitlementUpdate;
event.service_entitlement_update.user_id = 0;
event.service_entitlement_update.userId = 0;
event.service_entitlement_update.np_service_label = 0;
SystemService::PushSystemServiceEvent(event);
}

View File

@ -29,10 +29,10 @@ s32 PS4_SYSV_ABI sceAudio3dAudioOutClose(const s32 handle) {
return AudioOut::sceAudioOutClose(handle);
}
s32 PS4_SYSV_ABI
sceAudio3dAudioOutOpen(const OrbisAudio3dPortId port_id, const OrbisUserServiceUserId user_id,
s32 type, const s32 index, const u32 len, const u32 freq,
const AudioOut::OrbisAudioOutParamExtendedInformation param) {
s32 PS4_SYSV_ABI sceAudio3dAudioOutOpen(
const OrbisAudio3dPortId port_id, const Libraries::UserService::OrbisUserServiceUserId user_id,
s32 type, const s32 index, const u32 len, const u32 freq,
const AudioOut::OrbisAudioOutParamExtendedInformation param) {
LOG_INFO(Lib_Audio3d,
"called, port_id = {}, user_id = {}, type = {}, index = {}, len = {}, freq = {}",
port_id, user_id, type, index, len, freq);
@ -422,7 +422,7 @@ s32 PS4_SYSV_ABI sceAudio3dPortGetStatus() {
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceAudio3dPortOpen(const OrbisUserServiceUserId user_id,
s32 PS4_SYSV_ABI sceAudio3dPortOpen(const Libraries::UserService::OrbisUserServiceUserId user_id,
const OrbisAudio3dOpenParameters* parameters,
OrbisAudio3dPortId* port_id) {
LOG_INFO(Lib_Audio3d, "called, user_id = {}, parameters = {}, id = {}", user_id,

View File

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@ -15,8 +15,6 @@ class SymbolsResolver;
namespace Libraries::Audio3d {
using OrbisUserServiceUserId = s32;
enum class OrbisAudio3dRate : u32 {
ORBIS_AUDIO3D_RATE_48000 = 0,
};
@ -91,7 +89,8 @@ struct Audio3dState {
};
s32 PS4_SYSV_ABI sceAudio3dAudioOutClose(s32 handle);
s32 PS4_SYSV_ABI sceAudio3dAudioOutOpen(OrbisAudio3dPortId port_id, OrbisUserServiceUserId user_id,
s32 PS4_SYSV_ABI sceAudio3dAudioOutOpen(OrbisAudio3dPortId port_id,
Libraries::UserService::OrbisUserServiceUserId user_id,
s32 type, s32 index, u32 len, u32 freq,
AudioOut::OrbisAudioOutParamExtendedInformation param);
s32 PS4_SYSV_ABI sceAudio3dAudioOutOutput(s32 handle, void* ptr);
@ -127,7 +126,7 @@ s32 PS4_SYSV_ABI sceAudio3dPortGetQueueLevel(OrbisAudio3dPortId port_id, u32* qu
u32* queue_available);
s32 PS4_SYSV_ABI sceAudio3dPortGetState();
s32 PS4_SYSV_ABI sceAudio3dPortGetStatus();
s32 PS4_SYSV_ABI sceAudio3dPortOpen(OrbisUserServiceUserId user_id,
s32 PS4_SYSV_ABI sceAudio3dPortOpen(Libraries::UserService::OrbisUserServiceUserId user_id,
const OrbisAudio3dOpenParameters* parameters,
OrbisAudio3dPortId* port_id);
s32 PS4_SYSV_ABI sceAudio3dPortPush(OrbisAudio3dPortId port_id, OrbisAudio3dBlocking blocking);

View File

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/logging/log.h"
@ -16,7 +16,7 @@ s32 PS4_SYSV_ABI sceCompanionHttpdAddHeader(const char* key, const char* value,
}
s32 PS4_SYSV_ABI
sceCompanionHttpdGet2ndScreenStatus(Libraries::UserService::OrbisUserServiceUserId) {
sceCompanionHttpdGet2ndScreenStatus(Libraries::UserService::OrbisUserServiceUserId userId) {
LOG_ERROR(Lib_CompanionHttpd, "(STUBBED) called");
return ORBIS_OK;
}

View File

@ -1,9 +1,10 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/types.h"
#include "core/libraries/system/userservice.h"
namespace Core::Loader {
class SymbolsResolver;
@ -15,11 +16,11 @@ struct OrbisGameLiveStreamingStatus {
bool isOnAir;
u8 align[3];
u32 spectatorCounts;
s32 userId;
Libraries::UserService::OrbisUserServiceUserId userId;
u8 reserved[60];
};
struct OrbisGameLiveStreamingStatus2 {
s32 userId;
Libraries::UserService::OrbisUserServiceUserId userId;
bool isOnAir;
u8 align[3];
u32 spectatorCounts;

View File

@ -115,9 +115,199 @@ Error PS4_SYSV_ABI sceImeDialogGetPanelSize(const OrbisImeDialogParam* param, u3
return Error::OK;
}
int PS4_SYSV_ABI sceImeDialogGetPanelSizeExtended() {
LOG_ERROR(Lib_ImeDialog, "(STUBBED) called");
return ORBIS_OK;
Error PS4_SYSV_ABI sceImeDialogGetPanelSizeExtended(const OrbisImeDialogParam* param,
const OrbisImeParamExtended* extended,
u32* width, u32* height) {
if (!param || !width || !height) {
return Error::INVALID_ADDRESS;
}
// Check parameter bounds
if (static_cast<uint32_t>(param->type) > 4) {
return Error::INVALID_ARG;
}
if (extended) {
// Check panel priority for full panel mode (Accent = 3)
if (extended->priority == OrbisImePanelPriority::Accent) {
// Full panel mode - return maximum size
if ((param->option & OrbisImeOption::USE_OVER_2K_COORDINATES) !=
OrbisImeOption::DEFAULT) {
*width = 2560; // For 4K/5K displays
*height = 1440;
} else {
*width = 1920;
*height = 1080;
}
LOG_DEBUG(Lib_ImeDialog, "Full panel mode: width={}, height={}", *width, *height);
return Error::OK;
}
}
// First get the base panel size from the basic function
Error result = sceImeDialogGetPanelSize(param, width, height);
if (result != Error::OK) {
return result;
}
// Adjust based on IME type
switch (param->type) {
case OrbisImeType::Default:
case OrbisImeType::BasicLatin:
case OrbisImeType::Url:
case OrbisImeType::Mail:
// Standard IME types
if ((param->option & OrbisImeOption::PASSWORD) != OrbisImeOption::DEFAULT) {
*height = *height + 20;
}
if ((param->option & OrbisImeOption::MULTILINE) != OrbisImeOption::DEFAULT) {
*height = *height * 3 / 2;
}
break;
case OrbisImeType::Number:
*width = *width * 3 / 4;
*height = *height * 2 / 3;
break;
default:
// Unknown type, use default size
break;
}
// Apply extended options if provided
if (extended) {
// Handle extended option flags
if ((extended->option & OrbisImeExtOption::PRIORITY_FULL_WIDTH) !=
OrbisImeExtOption::DEFAULT) {
// Full width priority
bool use_2k = (param->option & OrbisImeOption::USE_OVER_2K_COORDINATES) !=
OrbisImeOption::DEFAULT;
*width = use_2k ? 1200 : 800;
LOG_DEBUG(Lib_ImeDialog, "Full width priority: width={}", *width);
}
if ((extended->option & OrbisImeExtOption::PRIORITY_FIXED_PANEL) !=
OrbisImeExtOption::DEFAULT) {
// Fixed panel size
*width = 600;
*height = 400;
LOG_DEBUG(Lib_ImeDialog, "Fixed panel: width={}, height={}", *width, *height);
}
switch (extended->priority) {
case OrbisImePanelPriority::Alphabet:
*width = 600;
*height = 400;
break;
case OrbisImePanelPriority::Symbol:
*width = 500;
*height = 300;
break;
case OrbisImePanelPriority::Accent:
// Already handled
break;
case OrbisImePanelPriority::Default:
default:
// Use calculated sizes
break;
}
if ((extended->option & OrbisImeExtOption::INIT_EXT_KEYBOARD_MODE) !=
OrbisImeExtOption::DEFAULT) {
if (extended->ext_keyboard_mode != 0) {
// Check for high-res mode flags
if ((extended->ext_keyboard_mode &
static_cast<uint32_t>(
OrbisImeInitExtKeyboardMode::INPUT_METHOD_STATE_FULL_WIDTH)) != 0) {
*width = *width * 5 / 4;
}
// Check for format characters enabled
if ((extended->ext_keyboard_mode &
static_cast<uint32_t>(
OrbisImeInitExtKeyboardMode::ENABLE_FORMAT_CHARACTERS)) != 0) {
*height = *height + 30;
}
}
}
// Check for accessibility mode
if ((extended->option & OrbisImeExtOption::ENABLE_ACCESSIBILITY) !=
OrbisImeExtOption::DEFAULT) {
*width = *width * 5 / 4; // 25% larger for accessibility
*height = *height * 5 / 4;
LOG_DEBUG(Lib_ImeDialog, "Accessibility mode: width={}, height={}", *width, *height);
}
// Check for forced accessibility panel
if ((extended->option & OrbisImeExtOption::ACCESSIBILITY_PANEL_FORCED) !=
OrbisImeExtOption::DEFAULT) {
*width = 800;
*height = 600;
LOG_DEBUG(Lib_ImeDialog, "Forced accessibility panel: width={}, height={}", *width,
*height);
}
}
if ((param->option & static_cast<OrbisImeOption>(0x8)) != OrbisImeOption::DEFAULT) { //?
*width *= 2;
*height *= 2;
LOG_DEBUG(Lib_ImeDialog, "Size mode: width={}, height={}", *width, *height);
}
// Adjust for supported languages if specified
if (param->supported_languages != static_cast<OrbisImeLanguage>(0)) {
// Check if CJK languages are supported (need larger panel)
OrbisImeLanguage cjk_mask = OrbisImeLanguage::JAPANESE | OrbisImeLanguage::KOREAN |
OrbisImeLanguage::SIMPLIFIED_CHINESE |
OrbisImeLanguage::TRADITIONAL_CHINESE;
if ((param->supported_languages & cjk_mask) != static_cast<OrbisImeLanguage>(0)) {
*width = *width * 5 / 4; // 25% wider for CJK input
*height = *height * 6 / 5; // 20% taller
LOG_DEBUG(Lib_ImeDialog, "CJK language support: width={}, height={}", *width, *height);
}
// Check if Arabic is supported (right-to-left layout)
if ((param->supported_languages & OrbisImeLanguage::ARABIC) !=
static_cast<OrbisImeLanguage>(0)) {
*width = *width * 11 / 10; // 10% wider for Arabic
LOG_DEBUG(Lib_ImeDialog, "Arabic language support: width={}", *width);
}
}
// Ensure minimum sizes
const uint32_t min_width = 200;
const uint32_t min_height = 100;
if (*width < min_width)
*width = min_width;
if (*height < min_height)
*height = min_height;
// Ensure maximum sizes (don't exceed screen bounds)
bool use_2k_coords =
(param->option & OrbisImeOption::USE_OVER_2K_COORDINATES) != OrbisImeOption::DEFAULT;
const uint32_t max_width = use_2k_coords ? 2560 : 1920;
const uint32_t max_height = use_2k_coords ? 1440 : 1080;
if (*width > max_width)
*width = max_width;
if (*height > max_height)
*height = max_height;
// Check for fixed position option
if ((param->option & OrbisImeOption::FIXED_POSITION) != OrbisImeOption::DEFAULT) {
if (*width > 800)
*width = 800;
if (*height > 600)
*height = 600;
}
LOG_DEBUG(Lib_ImeDialog, "Final panel size: width={}, height={}", *width, *height);
return Error::OK;
}
Error PS4_SYSV_ABI sceImeDialogGetResult(OrbisImeDialogResult* result) {

View File

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@ -37,7 +37,9 @@ int PS4_SYSV_ABI sceImeDialogGetCurrentStarState();
int PS4_SYSV_ABI sceImeDialogGetPanelPositionAndForm();
Error PS4_SYSV_ABI sceImeDialogGetPanelSize(const OrbisImeDialogParam* param, u32* width,
u32* height);
int PS4_SYSV_ABI sceImeDialogGetPanelSizeExtended();
Error PS4_SYSV_ABI sceImeDialogGetPanelSizeExtended(const OrbisImeDialogParam* param,
const OrbisImeParamExtended* extended,
u32* width, u32* height);
Error PS4_SYSV_ABI sceImeDialogGetResult(OrbisImeDialogResult* result);
OrbisImeDialogStatus PS4_SYSV_ABI sceImeDialogGetStatus();
Error PS4_SYSV_ABI sceImeDialogInit(OrbisImeDialogParam* param, OrbisImeParamExtended* extended);

View File

@ -102,6 +102,7 @@ s32 PS4_SYSV_ABI sceKernelReleaseDirectMemory(u64 start, u64 len) {
if (len == 0) {
return ORBIS_OK;
}
LOG_INFO(Kernel_Vmm, "called start = {:#x}, len = {:#x}", start, len);
auto* memory = Core::Memory::Instance();
memory->Free(start, len);
return ORBIS_OK;

View File

@ -655,10 +655,17 @@ int PS4_SYSV_ABI sceNetEpollControl(OrbisNetId epollid, OrbisNetEpollFlag op, Or
switch (file->type) {
case Core::FileSys::FileType::Socket: {
auto native_handle = file->socket->Native();
if (!native_handle) {
// P2P socket, cannot be added to epoll
LOG_ERROR(Lib_Net, "P2P socket cannot be added to epoll (unimplemented)");
*sceNetErrnoLoc() = ORBIS_NET_EBADF;
return ORBIS_NET_ERROR_EBADF;
}
epoll_event native_event = {.events = ConvertEpollEventsIn(event->events),
.data = {.fd = id}};
ASSERT(epoll_ctl(epoll->epoll_fd, EPOLL_CTL_ADD, *file->socket->Native(),
&native_event) == 0);
ASSERT(epoll_ctl(epoll->epoll_fd, EPOLL_CTL_ADD, *native_handle, &native_event) == 0);
epoll->events.emplace_back(id, *event);
break;
}
@ -696,10 +703,17 @@ int PS4_SYSV_ABI sceNetEpollControl(OrbisNetId epollid, OrbisNetEpollFlag op, Or
switch (file->type) {
case Core::FileSys::FileType::Socket: {
auto native_handle = file->socket->Native();
if (!native_handle) {
// P2P socket, cannot be modified in epoll
LOG_ERROR(Lib_Net, "P2P socket cannot be modified in epoll (unimplemented)");
*sceNetErrnoLoc() = ORBIS_NET_EBADF;
return ORBIS_NET_ERROR_EBADF;
}
epoll_event native_event = {.events = ConvertEpollEventsIn(event->events),
.data = {.fd = id}};
ASSERT(epoll_ctl(epoll->epoll_fd, EPOLL_CTL_MOD, *file->socket->Native(),
&native_event) == 0);
ASSERT(epoll_ctl(epoll->epoll_fd, EPOLL_CTL_MOD, *native_handle, &native_event) == 0);
*it = {id, *event};
break;
}
@ -731,8 +745,15 @@ int PS4_SYSV_ABI sceNetEpollControl(OrbisNetId epollid, OrbisNetEpollFlag op, Or
switch (file->type) {
case Core::FileSys::FileType::Socket: {
ASSERT(epoll_ctl(epoll->epoll_fd, EPOLL_CTL_DEL, *file->socket->Native(), nullptr) ==
0);
auto native_handle = file->socket->Native();
if (!native_handle) {
// P2P socket, cannot be removed from epoll
LOG_ERROR(Lib_Net, "P2P socket cannot be removed from epoll (unimplemented)");
*sceNetErrnoLoc() = ORBIS_NET_EBADF;
return ORBIS_NET_ERROR_EBADF;
}
ASSERT(epoll_ctl(epoll->epoll_fd, EPOLL_CTL_DEL, *native_handle, nullptr) == 0);
epoll->events.erase(it);
break;
}

View File

@ -1,10 +1,11 @@
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/types.h"
#include "core/libraries/np/np_types.h"
#include "core/libraries/system/userservice.h"
namespace Core::Loader {
class SymbolsResolver;
@ -31,7 +32,7 @@ struct OrbisNpAuthGetAuthorizationCodeParameter {
struct OrbisNpAuthGetAuthorizationCodeParameterA {
u64 size;
s32 user_id;
Libraries::UserService::OrbisUserServiceUserId user_id;
u8 padding[4];
const OrbisNpClientId* client_id;
const char* scope;
@ -47,7 +48,7 @@ struct OrbisNpAuthGetIdTokenParameter {
struct OrbisNpAuthGetIdTokenParameterA {
u64 size;
s32 user_id;
Libraries::UserService::OrbisUserServiceUserId user_id;
u8 padding[4];
const OrbisNpClientId* client_id;
const OrbisNpClientSecret* client_secret;

View File

@ -23,8 +23,8 @@ enum class OrbisNpState : u32 {
SignedIn = 2,
};
using OrbisNpStateCallbackForNpToolkit = PS4_SYSV_ABI void (*)(s32 userId, OrbisNpState state,
void* userdata);
using OrbisNpStateCallbackForNpToolkit = PS4_SYSV_ABI void (*)(
Libraries::UserService::OrbisUserServiceUserId userId, OrbisNpState state, void* userdata);
enum class OrbisNpGamePresenseStatus {
Offline = 0,

View File

@ -149,7 +149,8 @@ int PS4_SYSV_ABI sceNpTrophyConfigHasGroupFeature() {
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpTrophyCreateContext(OrbisNpTrophyContext* context, s32 user_id,
s32 PS4_SYSV_ABI sceNpTrophyCreateContext(OrbisNpTrophyContext* context,
Libraries::UserService::OrbisUserServiceUserId user_id,
uint32_t service_label, u64 options) {
ASSERT(options == 0ull);
if (!context) {

View File

@ -1,8 +1,9 @@
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <core/libraries/system/userservice.h>
#include "common/types.h"
#include "core/libraries/rtc/rtc.h"
@ -132,7 +133,8 @@ int PS4_SYSV_ABI sceNpTrophyConfigGetTrophySetInfoInGroup();
int PS4_SYSV_ABI sceNpTrophyConfigGetTrophySetVersion();
int PS4_SYSV_ABI sceNpTrophyConfigGetTrophyTitleDetails();
int PS4_SYSV_ABI sceNpTrophyConfigHasGroupFeature();
s32 PS4_SYSV_ABI sceNpTrophyCreateContext(OrbisNpTrophyContext* context, s32 user_id,
s32 PS4_SYSV_ABI sceNpTrophyCreateContext(OrbisNpTrophyContext* context,
Libraries::UserService::OrbisUserServiceUserId user_id,
u32 service_label, u64 options);
s32 PS4_SYSV_ABI sceNpTrophyCreateHandle(OrbisNpTrophyHandle* handle);
int PS4_SYSV_ABI sceNpTrophyDestroyContext(OrbisNpTrophyContext context);

View File

@ -159,7 +159,8 @@ int PS4_SYSV_ABI scePadGetFeatureReport() {
return ORBIS_OK;
}
int PS4_SYSV_ABI scePadGetHandle(s32 userId, s32 type, s32 index) {
int PS4_SYSV_ABI scePadGetHandle(Libraries::UserService::OrbisUserServiceUserId userId, s32 type,
s32 index) {
if (!g_initialized) {
return ORBIS_PAD_ERROR_NOT_INITIALIZED;
}
@ -256,7 +257,8 @@ int PS4_SYSV_ABI scePadMbusTerm() {
return ORBIS_OK;
}
int PS4_SYSV_ABI scePadOpen(s32 userId, s32 type, s32 index, const OrbisPadOpenParam* pParam) {
int PS4_SYSV_ABI scePadOpen(Libraries::UserService::OrbisUserServiceUserId userId, s32 type,
s32 index, const OrbisPadOpenParam* pParam) {
if (!g_initialized) {
return ORBIS_PAD_ERROR_NOT_INITIALIZED;
}
@ -277,8 +279,8 @@ int PS4_SYSV_ABI scePadOpen(s32 userId, s32 type, s32 index, const OrbisPadOpenP
return 1; // dummy
}
int PS4_SYSV_ABI scePadOpenExt(s32 userId, s32 type, s32 index,
const OrbisPadOpenExtParam* pParam) {
int PS4_SYSV_ABI scePadOpenExt(Libraries::UserService::OrbisUserServiceUserId userId, s32 type,
s32 index, const OrbisPadOpenExtParam* pParam) {
LOG_ERROR(Lib_Pad, "(STUBBED) called");
if (Config::getUseSpecialPad()) {
if (type != ORBIS_PAD_PORT_TYPE_SPECIAL)

View File

@ -1,8 +1,9 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <core/libraries/system/userservice.h>
#include "common/enum.h"
#include "common/types.h"
@ -276,7 +277,8 @@ int PS4_SYSV_ABI scePadGetExtControllerInformation(s32 handle,
OrbisPadExtendedControllerInformation* pInfo);
int PS4_SYSV_ABI scePadGetExtensionUnitInfo();
int PS4_SYSV_ABI scePadGetFeatureReport();
int PS4_SYSV_ABI scePadGetHandle(s32 userId, s32 type, s32 index);
int PS4_SYSV_ABI scePadGetHandle(Libraries::UserService::OrbisUserServiceUserId userId, s32 type,
s32 index);
int PS4_SYSV_ABI scePadGetIdleCount();
int PS4_SYSV_ABI scePadGetInfo();
int PS4_SYSV_ABI scePadGetInfoByPortType();
@ -294,8 +296,10 @@ int PS4_SYSV_ABI scePadIsMoveReproductionModel();
int PS4_SYSV_ABI scePadIsValidHandle();
int PS4_SYSV_ABI scePadMbusInit();
int PS4_SYSV_ABI scePadMbusTerm();
int PS4_SYSV_ABI scePadOpen(s32 userId, s32 type, s32 index, const OrbisPadOpenParam* pParam);
int PS4_SYSV_ABI scePadOpenExt(s32 userId, s32 type, s32 index, const OrbisPadOpenExtParam* pParam);
int PS4_SYSV_ABI scePadOpen(Libraries::UserService::OrbisUserServiceUserId userId, s32 type,
s32 index, const OrbisPadOpenParam* pParam);
int PS4_SYSV_ABI scePadOpenExt(Libraries::UserService::OrbisUserServiceUserId userId, s32 type,
s32 index, const OrbisPadOpenExtParam* pParam);
int PS4_SYSV_ABI scePadOpenExt2();
int PS4_SYSV_ABI scePadOutputReport();
int PS4_SYSV_ABI scePadRead(s32 handle, OrbisPadData* pData, s32 num);

View File

@ -54,7 +54,8 @@ int PS4_SYSV_ABI sceRemoteplayGetConnectHistory() {
return ORBIS_OK;
}
int PS4_SYSV_ABI sceRemoteplayGetConnectionStatus(s32 userId, int* pStatus) {
int PS4_SYSV_ABI sceRemoteplayGetConnectionStatus(
Libraries::UserService::OrbisUserServiceUserId userId, int* pStatus) {
*pStatus = ORBIS_REMOTEPLAY_CONNECTION_STATUS_DISCONNECT;
return ORBIS_OK;
}

View File

@ -1,8 +1,9 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <core/libraries/system/userservice.h>
#include "common/types.h"
namespace Core::Loader {
@ -24,7 +25,8 @@ int PS4_SYSV_ABI sceRemoteplayDisconnect();
int PS4_SYSV_ABI sceRemoteplayGeneratePinCode();
int PS4_SYSV_ABI sceRemoteplayGetApMode();
int PS4_SYSV_ABI sceRemoteplayGetConnectHistory();
int PS4_SYSV_ABI sceRemoteplayGetConnectionStatus(s32 userId, int* pStatus);
int PS4_SYSV_ABI sceRemoteplayGetConnectionStatus(
Libraries::UserService::OrbisUserServiceUserId userId, int* pStatus);
int PS4_SYSV_ABI sceRemoteplayGetConnectUserId();
int PS4_SYSV_ABI sceRemoteplayGetMbusDeviceInfo();
int PS4_SYSV_ABI sceRemoteplayGetOperationStatus();

View File

@ -167,7 +167,7 @@ void StopThread() {
g_backup_thread_semaphore.release();
}
bool NewRequest(OrbisUserServiceUserId user_id, std::string_view title_id,
bool NewRequest(Libraries::UserService::OrbisUserServiceUserId user_id, std::string_view title_id,
std::string_view dir_name, OrbisSaveDataEventType origin) {
auto save_path = SaveInstance::MakeDirSavePath(user_id, title_id, dir_name);

View File

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@ -6,12 +6,11 @@
#include <filesystem>
#include <optional>
#include <core/libraries/system/userservice.h>
#include "common/types.h"
namespace Libraries::SaveData {
using OrbisUserServiceUserId = s32;
namespace Backup {
enum class WorkerStatus {
@ -32,7 +31,7 @@ enum class OrbisSaveDataEventType : u32 {
struct BackupRequest {
bool done{};
OrbisUserServiceUserId user_id{};
Libraries::UserService::OrbisUserServiceUserId user_id{};
std::string title_id{};
std::string dir_name{};
OrbisSaveDataEventType origin{};
@ -45,7 +44,7 @@ void StartThread();
void StopThread();
bool NewRequest(OrbisUserServiceUserId user_id, std::string_view title_id,
bool NewRequest(Libraries::UserService::OrbisUserServiceUserId user_id, std::string_view title_id,
std::string_view dir_name, OrbisSaveDataEventType origin);
bool Restore(const std::filesystem::path& save_path);

View File

@ -46,13 +46,13 @@ static const std::unordered_map<int, std::string> default_title = {
namespace Libraries::SaveData {
fs::path SaveInstance::MakeTitleSavePath(OrbisUserServiceUserId user_id,
fs::path SaveInstance::MakeTitleSavePath(Libraries::UserService::OrbisUserServiceUserId user_id,
std::string_view game_serial) {
return Config::GetSaveDataPath() / std::to_string(user_id) / game_serial;
}
fs::path SaveInstance::MakeDirSavePath(OrbisUserServiceUserId user_id, std::string_view game_serial,
std::string_view dir_name) {
fs::path SaveInstance::MakeDirSavePath(Libraries::UserService::OrbisUserServiceUserId user_id,
std::string_view game_serial, std::string_view dir_name) {
return Config::GetSaveDataPath() / std::to_string(user_id) / game_serial / dir_name;
}
@ -89,8 +89,8 @@ void SaveInstance::SetupDefaultParamSFO(PSF& param_sfo, std::string dir_name,
#undef P
}
SaveInstance::SaveInstance(int slot_num, OrbisUserServiceUserId user_id, std::string _game_serial,
std::string_view _dir_name, int max_blocks)
SaveInstance::SaveInstance(int slot_num, Libraries::UserService::OrbisUserServiceUserId user_id,
std::string _game_serial, std::string_view _dir_name, int max_blocks)
: slot_num(slot_num), user_id(user_id), game_serial(std::move(_game_serial)),
dir_name(_dir_name),
max_blocks(std::clamp(max_blocks, OrbisSaveDataBlocksMin2, OrbisSaveDataBlocksMax)) {

View File

@ -1,10 +1,11 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <filesystem>
#include <core/libraries/system/userservice.h>
#include "common/io_file.h"
#include "core/file_format/psf.h"
@ -52,13 +53,13 @@ class SaveInstance {
public:
// Location of all save data for a title
static std::filesystem::path MakeTitleSavePath(OrbisUserServiceUserId user_id,
std::string_view game_serial);
static std::filesystem::path MakeTitleSavePath(
Libraries::UserService::OrbisUserServiceUserId user_id, std::string_view game_serial);
// Location of a specific save data directory
static std::filesystem::path MakeDirSavePath(OrbisUserServiceUserId user_id,
std::string_view game_serial,
std::string_view dir_name);
static std::filesystem::path MakeDirSavePath(
Libraries::UserService::OrbisUserServiceUserId user_id, std::string_view game_serial,
std::string_view dir_name);
static uint64_t GetMaxBlockFromSFO(const PSF& psf);
@ -67,8 +68,8 @@ public:
static void SetupDefaultParamSFO(PSF& param_sfo, std::string dir_name, std::string game_serial);
explicit SaveInstance(int slot_num, OrbisUserServiceUserId user_id, std::string game_serial,
std::string_view dir_name, int max_blocks = 0);
explicit SaveInstance(int slot_num, Libraries::UserService::OrbisUserServiceUserId user_id,
std::string game_serial, std::string_view dir_name, int max_blocks = 0);
~SaveInstance();

View File

@ -88,8 +88,8 @@ std::string GetSaveDir(u32 slot_id) {
return dir;
}
std::filesystem::path GetSavePath(OrbisUserServiceUserId user_id, u32 slot_id,
std::string_view game_serial) {
std::filesystem::path GetSavePath(Libraries::UserService::OrbisUserServiceUserId user_id,
u32 slot_id, std::string_view game_serial) {
std::string dir(StandardDirnameSaveDataMemory);
if (slot_id > 0) {
dir += std::to_string(slot_id);
@ -97,8 +97,8 @@ std::filesystem::path GetSavePath(OrbisUserServiceUserId user_id, u32 slot_id,
return SaveInstance::MakeDirSavePath(user_id, game_serial, dir);
}
size_t SetupSaveMemory(OrbisUserServiceUserId user_id, u32 slot_id, std::string_view game_serial,
size_t memory_size) {
size_t SetupSaveMemory(Libraries::UserService::OrbisUserServiceUserId user_id, u32 slot_id,
std::string_view game_serial, size_t memory_size) {
std::lock_guard lck{g_slot_mtx};
const auto save_dir = GetSavePath(user_id, slot_id, game_serial);

View File

@ -4,26 +4,24 @@
#pragma once
#include <vector>
#include <core/libraries/system/userservice.h>
#include "core/libraries/save_data/save_backup.h"
class PSF;
namespace Libraries::SaveData {
using OrbisUserServiceUserId = s32;
} // namespace Libraries::SaveData
namespace Libraries::SaveData::SaveMemory {
void PersistMemory(u32 slot_id, bool lock = true);
[[nodiscard]] std::string GetSaveDir(u32 slot_id);
[[nodiscard]] std::filesystem::path GetSavePath(OrbisUserServiceUserId user_id, u32 slot_id,
std::string_view game_serial);
[[nodiscard]] std::filesystem::path GetSavePath(
Libraries::UserService::OrbisUserServiceUserId user_id, u32 slot_id,
std::string_view game_serial);
// returns the size of the save memory if exists
size_t SetupSaveMemory(OrbisUserServiceUserId user_id, u32 slot_id, std::string_view game_serial,
size_t memory_size);
size_t SetupSaveMemory(Libraries::UserService::OrbisUserServiceUserId user_id, u32 slot_id,
std::string_view game_serial, size_t memory_size);
// Write the icon. Set buf to null to read the standard icon.
void SetIcon(u32 slot_id, void* buf = nullptr, size_t buf_size = 0);

View File

@ -42,7 +42,6 @@ enum class OrbisSaveDataSaveDataMemoryOption : u32 {
UNLOCK_LIMITATIONS = 1 << 2,
};
using OrbisUserServiceUserId = s32;
using OrbisSaveDataBlocks = u64;
constexpr u32 OrbisSaveDataBlockSize = 32768; // 32 KiB
@ -97,7 +96,7 @@ struct OrbisSaveDataFingerprint {
};
struct OrbisSaveDataBackup {
OrbisUserServiceUserId userId;
Libraries::UserService::OrbisUserServiceUserId userId;
s32 : 32;
const OrbisSaveDataTitleId* titleId;
const OrbisSaveDataDirName* dirName;
@ -106,7 +105,7 @@ struct OrbisSaveDataBackup {
};
struct OrbisSaveDataCheckBackupData {
OrbisUserServiceUserId userId;
Libraries::UserService::OrbisUserServiceUserId userId;
s32 : 32;
const OrbisSaveDataTitleId* titleId;
const OrbisSaveDataDirName* dirName;
@ -116,7 +115,7 @@ struct OrbisSaveDataCheckBackupData {
};
struct OrbisSaveDataDelete {
OrbisUserServiceUserId userId;
Libraries::UserService::OrbisUserServiceUserId userId;
s32 : 32;
const OrbisSaveDataTitleId* titleId;
const OrbisSaveDataDirName* dirName;
@ -153,7 +152,7 @@ struct OrbisSaveDataMemoryData {
};
struct OrbisSaveDataMemoryGet2 {
OrbisUserServiceUserId userId;
Libraries::UserService::OrbisUserServiceUserId userId;
std::array<u8, 4> _pad;
OrbisSaveDataMemoryData* data;
OrbisSaveDataParam* param;
@ -163,7 +162,7 @@ struct OrbisSaveDataMemoryGet2 {
};
struct OrbisSaveDataMemorySet2 {
OrbisUserServiceUserId userId;
Libraries::UserService::OrbisUserServiceUserId userId;
std::array<u8, 4> _pad;
const OrbisSaveDataMemoryData* data;
const OrbisSaveDataParam* param;
@ -175,7 +174,7 @@ struct OrbisSaveDataMemorySet2 {
struct OrbisSaveDataMemorySetup2 {
OrbisSaveDataSaveDataMemoryOption option;
OrbisUserServiceUserId userId;
Libraries::UserService::OrbisUserServiceUserId userId;
size_t memorySize;
size_t iconMemorySize;
// +4.5
@ -197,14 +196,14 @@ enum OrbisSaveDataMemorySyncOption : u32 {
};
struct OrbisSaveDataMemorySync {
OrbisUserServiceUserId userId;
Libraries::UserService::OrbisUserServiceUserId userId;
u32 slotId;
OrbisSaveDataMemorySyncOption option;
std::array<u8, 28> _reserved;
};
struct OrbisSaveDataMount2 {
OrbisUserServiceUserId userId;
Libraries::UserService::OrbisUserServiceUserId userId;
s32 : 32;
const OrbisSaveDataDirName* dirName;
OrbisSaveDataBlocks blocks;
@ -214,7 +213,7 @@ struct OrbisSaveDataMount2 {
};
struct OrbisSaveDataMount {
OrbisUserServiceUserId userId;
Libraries::UserService::OrbisUserServiceUserId userId;
s32 : 32;
const OrbisSaveDataTitleId* titleId;
const OrbisSaveDataDirName* dirName;
@ -245,7 +244,7 @@ struct OrbisSaveDataMountResult {
};
struct OrbisSaveDataRestoreBackupData {
OrbisUserServiceUserId userId;
Libraries::UserService::OrbisUserServiceUserId userId;
s32 : 32;
const OrbisSaveDataTitleId* titleId;
const OrbisSaveDataDirName* dirName;
@ -256,7 +255,7 @@ struct OrbisSaveDataRestoreBackupData {
};
struct OrbisSaveDataTransferringMount {
OrbisUserServiceUserId userId;
Libraries::UserService::OrbisUserServiceUserId userId;
const OrbisSaveDataTitleId* titleId;
const OrbisSaveDataDirName* dirName;
const OrbisSaveDataFingerprint* fingerprint;
@ -264,7 +263,7 @@ struct OrbisSaveDataTransferringMount {
};
struct OrbisSaveDataDirNameSearchCond {
OrbisUserServiceUserId userId;
Libraries::UserService::OrbisUserServiceUserId userId;
int : 32;
const OrbisSaveDataTitleId* titleId;
const OrbisSaveDataDirName* dirName;
@ -303,7 +302,7 @@ using OrbisSaveDataEventType = Backup::OrbisSaveDataEventType;
struct OrbisSaveDataEvent {
OrbisSaveDataEventType type;
s32 errorCode;
OrbisUserServiceUserId userId;
Libraries::UserService::OrbisUserServiceUserId userId;
std::array<u8, 4> _pad;
OrbisSaveDataTitleId titleId;
OrbisSaveDataDirName dirName;
@ -1106,8 +1105,9 @@ int PS4_SYSV_ABI sceSaveDataGetSaveDataCount() {
return ORBIS_OK;
}
Error PS4_SYSV_ABI sceSaveDataGetSaveDataMemory(const OrbisUserServiceUserId userId, void* buf,
const size_t bufSize, const int64_t offset) {
Error PS4_SYSV_ABI
sceSaveDataGetSaveDataMemory(const Libraries::UserService::OrbisUserServiceUserId userId, void* buf,
const size_t bufSize, const int64_t offset) {
LOG_DEBUG(Lib_SaveData, "Redirecting to sceSaveDataGetSaveDataMemory2");
OrbisSaveDataMemoryData data{};
data.buf = buf;
@ -1469,8 +1469,9 @@ int PS4_SYSV_ABI sceSaveDataSetSaveDataLibraryUser() {
return ORBIS_OK;
}
Error PS4_SYSV_ABI sceSaveDataSetSaveDataMemory(OrbisUserServiceUserId userId, void* buf,
size_t bufSize, int64_t offset) {
Error PS4_SYSV_ABI
sceSaveDataSetSaveDataMemory(Libraries::UserService::OrbisUserServiceUserId userId, void* buf,
size_t bufSize, int64_t offset) {
LOG_DEBUG(Lib_SaveData, "Redirecting to sceSaveDataSetSaveDataMemory2");
OrbisSaveDataMemoryData data{};
data.buf = buf;
@ -1527,8 +1528,9 @@ Error PS4_SYSV_ABI sceSaveDataSetSaveDataMemory2(const OrbisSaveDataMemorySet2*
return Error::OK;
}
Error PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory(OrbisUserServiceUserId userId, size_t memorySize,
OrbisSaveDataParam* param) {
Error PS4_SYSV_ABI
sceSaveDataSetupSaveDataMemory(Libraries::UserService::OrbisUserServiceUserId userId,
size_t memorySize, OrbisSaveDataParam* param) {
LOG_DEBUG(Lib_SaveData, "called: userId = {}, memorySize = {}", userId, memorySize);
OrbisSaveDataMemorySetup2 setupParam{};
setupParam.userId = userId;

View File

@ -3,6 +3,7 @@
#pragma once
#include <core/libraries/system/userservice.h>
#include "common/cstring.h"
#include "common/types.h"
@ -21,8 +22,6 @@ constexpr size_t OrbisSaveDataDetailMaxsize = 1024; // Maximum detail name size
enum class Error : u32;
enum class OrbisSaveDataParamType : u32;
using OrbisUserServiceUserId = s32;
// Maximum size for a title ID (4 uppercase letters + 5 digits)
constexpr int OrbisSaveDataTitleIdDataSize = 10;
// Maximum save directory name size
@ -126,8 +125,9 @@ Error PS4_SYSV_ABI sceSaveDataGetParam(const OrbisSaveDataMountPoint* mountPoint
size_t paramBufSize, size_t* gotSize);
Error PS4_SYSV_ABI sceSaveDataGetProgress(float* progress);
int PS4_SYSV_ABI sceSaveDataGetSaveDataCount();
Error PS4_SYSV_ABI sceSaveDataGetSaveDataMemory(OrbisUserServiceUserId userId, void* buf,
size_t bufSize, int64_t offset);
Error PS4_SYSV_ABI
sceSaveDataGetSaveDataMemory(Libraries::UserService::OrbisUserServiceUserId userId, void* buf,
size_t bufSize, int64_t offset);
Error PS4_SYSV_ABI sceSaveDataGetSaveDataMemory2(OrbisSaveDataMemoryGet2* getParam);
int PS4_SYSV_ABI sceSaveDataGetSaveDataRootDir();
int PS4_SYSV_ABI sceSaveDataGetSaveDataRootPath();
@ -163,11 +163,13 @@ Error PS4_SYSV_ABI sceSaveDataSetParam(const OrbisSaveDataMountPoint* mountPoint
OrbisSaveDataParamType paramType, const void* paramBuf,
size_t paramBufSize);
int PS4_SYSV_ABI sceSaveDataSetSaveDataLibraryUser();
Error PS4_SYSV_ABI sceSaveDataSetSaveDataMemory(OrbisUserServiceUserId userId, void* buf,
size_t bufSize, int64_t offset);
Error PS4_SYSV_ABI
sceSaveDataSetSaveDataMemory(Libraries::UserService::OrbisUserServiceUserId userId, void* buf,
size_t bufSize, int64_t offset);
Error PS4_SYSV_ABI sceSaveDataSetSaveDataMemory2(const OrbisSaveDataMemorySet2* setParam);
Error PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory(OrbisUserServiceUserId userId, size_t memorySize,
OrbisSaveDataParam* param);
Error PS4_SYSV_ABI
sceSaveDataSetupSaveDataMemory(Libraries::UserService::OrbisUserServiceUserId userId,
size_t memorySize, OrbisSaveDataParam* param);
Error PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory2(const OrbisSaveDataMemorySetup2* setupParam,
OrbisSaveDataMemorySetupResult* result);
int PS4_SYSV_ABI sceSaveDataShutdownStart();

View File

@ -1,8 +1,9 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <core/libraries/system/userservice.h>
#include "common/types.h"
#include "core/libraries/np/np_types.h"
@ -21,8 +22,8 @@ struct OrbisSharePlayConnectionInfo {
int mode;
Libraries::Np::OrbisNpOnlineId hostOnlineId;
Libraries::Np::OrbisNpOnlineId visitorOnlineId;
s32 hostUserId;
s32 visitorUserId;
Libraries::UserService::OrbisUserServiceUserId hostUserId;
Libraries::UserService::OrbisUserServiceUserId visitorUserId;
};
int PS4_SYSV_ABI sceSharePlayCrashDaemon();

View File

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
// reference
// https://github.com/OpenOrbis/OpenOrbis-PS4-Toolchain/blob/master/include/orbis/_types/sys_service.h
@ -7,6 +7,7 @@
#include <mutex>
#include <queue>
#include "common/types.h"
#include "userservice.h"
namespace Core::Loader {
class SymbolsResolver;
@ -119,12 +120,12 @@ struct OrbisSystemServiceEvent {
char boot_argument[7169];
} join_event;
struct {
s32 user_id;
Libraries::UserService::OrbisUserServiceUserId userId;
u32 np_service_label;
u8 reserved[8184];
} service_entitlement_update;
struct {
s32 user_id;
Libraries::UserService::OrbisUserServiceUserId userId;
u32 np_service_label;
u8 reserved[8184];
} unified_entitlement_update;

View File

@ -291,8 +291,8 @@ s32 PS4_SYSV_ABI sceVideoOutGetResolutionStatus(s32 handle, SceVideoOutResolutio
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceVideoOutOpen(SceUserServiceUserId userId, s32 busType, s32 index,
const void* param) {
s32 PS4_SYSV_ABI sceVideoOutOpen(Libraries::UserService::OrbisUserServiceUserId userId, s32 busType,
s32 index, const void* param) {
LOG_INFO(Lib_VideoOut, "called");
ASSERT(busType == SCE_VIDEO_OUT_BUS_TYPE_MAIN);

View File

@ -1,8 +1,9 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <core/libraries/system/userservice.h>
#include "core/libraries/kernel/equeue.h"
#include "core/libraries/videoout/buffer.h"
@ -12,8 +13,6 @@ class SymbolsResolver;
namespace Libraries::VideoOut {
using SceUserServiceUserId = s32; // TODO move it to proper place
// SceVideoOutBusType
constexpr int SCE_VIDEO_OUT_BUS_TYPE_MAIN = 0; // Main output
constexpr int SCE_VIDEO_OUT_BUS_TYPE_AUX_SOCIAL_SCREEN = 5; // Aux output for social
@ -131,8 +130,8 @@ s32 PS4_SYSV_ABI sceVideoOutWaitVblank(s32 handle);
s32 PS4_SYSV_ABI sceVideoOutSubmitFlip(s32 handle, s32 bufferIndex, s32 flipMode, s64 flipArg);
s32 PS4_SYSV_ABI sceVideoOutGetFlipStatus(s32 handle, FlipStatus* status);
s32 PS4_SYSV_ABI sceVideoOutGetResolutionStatus(s32 handle, SceVideoOutResolutionStatus* status);
s32 PS4_SYSV_ABI sceVideoOutOpen(SceUserServiceUserId userId, s32 busType, s32 index,
const void* param);
s32 PS4_SYSV_ABI sceVideoOutOpen(Libraries::UserService::OrbisUserServiceUserId userId, s32 busType,
s32 index, const void* param);
s32 PS4_SYSV_ABI sceVideoOutClose(s32 handle);
s32 PS4_SYSV_ABI sceVideoOutGetEventId(const Kernel::SceKernelEvent* ev);
s32 PS4_SYSV_ABI sceVideoOutGetEventData(const Kernel::SceKernelEvent* ev, s64* data);

View File

@ -5,9 +5,12 @@
#include "core/libraries/error_codes.h"
#include "core/libraries/libs.h"
#include "core/libraries/web_browser_dialog/webbrowserdialog.h"
#include "magic_enum/magic_enum.hpp"
namespace Libraries::WebBrowserDialog {
static auto g_status = Libraries::CommonDialog::Status::NONE;
s32 PS4_SYSV_ABI sceWebBrowserDialogClose() {
LOG_ERROR(Lib_WebBrowserDialog, "(STUBBED) called");
return ORBIS_OK;
@ -23,14 +26,19 @@ s32 PS4_SYSV_ABI sceWebBrowserDialogGetResult() {
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceWebBrowserDialogGetStatus() {
LOG_ERROR(Lib_WebBrowserDialog, "(STUBBED) called");
return ORBIS_OK;
Libraries::CommonDialog::Status PS4_SYSV_ABI sceWebBrowserDialogGetStatus() {
LOG_TRACE(Lib_MsgDlg, "called status={}", magic_enum::enum_name(g_status));
return g_status;
}
s32 PS4_SYSV_ABI sceWebBrowserDialogInitialize() {
LOG_ERROR(Lib_WebBrowserDialog, "(STUBBED) called");
return ORBIS_OK;
Libraries::CommonDialog::Error PS4_SYSV_ABI sceWebBrowserDialogInitialize() {
if (CommonDialog::g_isInitialized) {
LOG_INFO(Lib_WebBrowserDialog, "already initialized");
return Libraries::CommonDialog::Error::ALREADY_SYSTEM_INITIALIZED;
}
LOG_DEBUG(Lib_WebBrowserDialog, "initialized");
CommonDialog::g_isInitialized = true;
return Libraries::CommonDialog::Error::OK;
}
s32 PS4_SYSV_ABI sceWebBrowserDialogNavigate() {
@ -63,14 +71,22 @@ s32 PS4_SYSV_ABI sceWebBrowserDialogSetZoom() {
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceWebBrowserDialogTerminate() {
LOG_ERROR(Lib_WebBrowserDialog, "(STUBBED) called");
return ORBIS_OK;
Libraries::CommonDialog::Error PS4_SYSV_ABI sceWebBrowserDialogTerminate() {
if (g_status == Libraries::CommonDialog::Status::RUNNING) {
LOG_ERROR(Lib_WebBrowserDialog,
"CloseWebBrowser Dialog unimplemented"); // sceWebBrowserDialogClose();
}
if (g_status == Libraries::CommonDialog::Status::NONE) {
return Libraries::CommonDialog::Error::NOT_INITIALIZED;
}
g_status = Libraries::CommonDialog::Status::NONE;
CommonDialog::g_isUsed = false;
return Libraries::CommonDialog::Error::OK;
}
s32 PS4_SYSV_ABI sceWebBrowserDialogUpdateStatus() {
LOG_ERROR(Lib_WebBrowserDialog, "(STUBBED) called");
return ORBIS_OK;
Libraries::CommonDialog::Status PS4_SYSV_ABI sceWebBrowserDialogUpdateStatus() {
LOG_TRACE(Lib_MsgDlg, "called status={}", magic_enum::enum_name(g_status));
return g_status;
}
s32 PS4_SYSV_ABI Func_F2BE042771625F8C() {

View File

@ -3,6 +3,7 @@
#pragma once
#include <core/libraries/system/commondialog.h>
#include "common/types.h"
namespace Core::Loader {
@ -14,16 +15,16 @@ namespace Libraries::WebBrowserDialog {
s32 PS4_SYSV_ABI sceWebBrowserDialogClose();
s32 PS4_SYSV_ABI sceWebBrowserDialogGetEvent();
s32 PS4_SYSV_ABI sceWebBrowserDialogGetResult();
s32 PS4_SYSV_ABI sceWebBrowserDialogGetStatus();
s32 PS4_SYSV_ABI sceWebBrowserDialogInitialize();
Libraries::CommonDialog::Status PS4_SYSV_ABI sceWebBrowserDialogGetStatus();
Libraries::CommonDialog::Error PS4_SYSV_ABI sceWebBrowserDialogInitialize();
s32 PS4_SYSV_ABI sceWebBrowserDialogNavigate();
s32 PS4_SYSV_ABI sceWebBrowserDialogOpen();
s32 PS4_SYSV_ABI sceWebBrowserDialogOpenForPredeterminedContent();
s32 PS4_SYSV_ABI sceWebBrowserDialogResetCookie();
s32 PS4_SYSV_ABI sceWebBrowserDialogSetCookie();
s32 PS4_SYSV_ABI sceWebBrowserDialogSetZoom();
s32 PS4_SYSV_ABI sceWebBrowserDialogTerminate();
s32 PS4_SYSV_ABI sceWebBrowserDialogUpdateStatus();
Libraries::CommonDialog::Error PS4_SYSV_ABI sceWebBrowserDialogTerminate();
Libraries::CommonDialog::Status PS4_SYSV_ABI sceWebBrowserDialogUpdateStatus();
s32 PS4_SYSV_ABI Func_F2BE042771625F8C();
void RegisterLib(Core::Loader::SymbolsResolver* sym);

File diff suppressed because it is too large Load Diff

View File

@ -8,6 +8,7 @@
#include <string>
#include <string_view>
#include "common/enum.h"
#include "common/shared_first_mutex.h"
#include "common/singleton.h"
#include "common/types.h"
#include "core/address_space.h"
@ -54,12 +55,37 @@ enum class MemoryMapFlags : u32 {
};
DECLARE_ENUM_FLAG_OPERATORS(MemoryMapFlags)
enum class DMAType : u32 {
enum class PhysicalMemoryType : u32 {
Free = 0,
Allocated = 1,
Mapped = 2,
Pooled = 3,
Committed = 4,
Flexible = 5,
};
struct PhysicalMemoryArea {
PAddr base = 0;
u64 size = 0;
s32 memory_type = 0;
PhysicalMemoryType dma_type = PhysicalMemoryType::Free;
PAddr GetEnd() const {
return base + size;
}
bool CanMergeWith(const PhysicalMemoryArea& next) const {
if (base + size != next.base) {
return false;
}
if (memory_type != next.memory_type) {
return false;
}
if (dma_type != next.dma_type) {
return false;
}
return true;
}
};
enum class VMAType : u32 {
@ -74,60 +100,15 @@ enum class VMAType : u32 {
File = 8,
};
struct DirectMemoryArea {
PAddr base = 0;
u64 size = 0;
s32 memory_type = 0;
DMAType dma_type = DMAType::Free;
PAddr GetEnd() const {
return base + size;
}
bool CanMergeWith(const DirectMemoryArea& next) const {
if (base + size != next.base) {
return false;
}
if (memory_type != next.memory_type) {
return false;
}
if (dma_type != next.dma_type) {
return false;
}
return true;
}
};
struct FlexibleMemoryArea {
PAddr base = 0;
u64 size = 0;
bool is_free = true;
PAddr GetEnd() const {
return base + size;
}
bool CanMergeWith(const FlexibleMemoryArea& next) const {
if (base + size != next.base) {
return false;
}
if (is_free != next.is_free) {
return false;
}
return true;
}
};
struct VirtualMemoryArea {
VAddr base = 0;
u64 size = 0;
PAddr phys_base = 0;
std::map<uintptr_t, PhysicalMemoryArea> phys_areas;
VMAType type = VMAType::Free;
MemoryProt prot = MemoryProt::NoAccess;
bool disallow_merge = false;
std::string name = "";
uintptr_t fd = 0;
bool is_exec = false;
s32 fd = 0;
bool disallow_merge = false;
bool Contains(VAddr addr, u64 size) const {
return addr >= base && (addr + size) <= (base + this->size);
@ -141,30 +122,32 @@ struct VirtualMemoryArea {
return type != VMAType::Free && type != VMAType::Reserved && type != VMAType::PoolReserved;
}
bool CanMergeWith(const VirtualMemoryArea& next) const {
bool CanMergeWith(VirtualMemoryArea& next) {
if (disallow_merge || next.disallow_merge) {
return false;
}
if (base + size != next.base) {
return false;
}
if ((type == VMAType::Direct || type == VMAType::Flexible || type == VMAType::Pooled) &&
phys_base + size != next.phys_base) {
return false;
if (type == VMAType::Direct && next.type == VMAType::Direct) {
auto& last_phys = std::prev(phys_areas.end())->second;
auto& first_next_phys = next.phys_areas.begin()->second;
if (last_phys.base + last_phys.size != first_next_phys.base ||
last_phys.memory_type != first_next_phys.memory_type) {
return false;
}
}
if (prot != next.prot || type != next.type) {
return false;
}
return true;
}
};
class MemoryManager {
using DMemMap = std::map<PAddr, DirectMemoryArea>;
using DMemHandle = DMemMap::iterator;
using FMemMap = std::map<PAddr, FlexibleMemoryArea>;
using FMemHandle = FMemMap::iterator;
using PhysMap = std::map<PAddr, PhysicalMemoryArea>;
using PhysHandle = PhysMap::iterator;
using VMAMap = std::map<VAddr, VirtualMemoryArea>;
using VMAHandle = VMAMap::iterator;
@ -220,10 +203,11 @@ public:
// Now make sure the full address range is contained in vma_map.
auto vma_handle = FindVMA(virtual_addr);
auto addr_to_check = virtual_addr;
s64 size_to_validate = size;
u64 size_to_validate = size;
while (vma_handle != vma_map.end() && size_to_validate > 0) {
const auto offset_in_vma = addr_to_check - vma_handle->second.base;
const auto size_in_vma = vma_handle->second.size - offset_in_vma;
const auto size_in_vma =
std::min<u64>(vma_handle->second.size - offset_in_vma, size_to_validate);
size_to_validate -= size_in_vma;
addr_to_check += size_in_vma;
vma_handle++;
@ -245,7 +229,7 @@ public:
void CopySparseMemory(VAddr source, u8* dest, u64 size);
bool TryWriteBacking(void* address, const void* data, u32 num_bytes);
bool TryWriteBacking(void* address, const void* data, u64 size);
void SetupMemoryRegions(u64 flexible_size, bool use_extended_mem1, bool use_extended_mem2);
@ -300,34 +284,14 @@ private:
return std::prev(vma_map.upper_bound(target));
}
DMemHandle FindDmemArea(PAddr target) {
PhysHandle FindDmemArea(PAddr target) {
return std::prev(dmem_map.upper_bound(target));
}
FMemHandle FindFmemArea(PAddr target) {
PhysHandle FindFmemArea(PAddr target) {
return std::prev(fmem_map.upper_bound(target));
}
template <typename Handle>
Handle MergeAdjacent(auto& handle_map, Handle iter) {
const auto next_vma = std::next(iter);
if (next_vma != handle_map.end() && iter->second.CanMergeWith(next_vma->second)) {
iter->second.size += next_vma->second.size;
handle_map.erase(next_vma);
}
if (iter != handle_map.begin()) {
auto prev_vma = std::prev(iter);
if (prev_vma->second.CanMergeWith(iter->second)) {
prev_vma->second.size += iter->second.size;
handle_map.erase(iter);
iter = prev_vma;
}
}
return iter;
}
bool HasPhysicalBacking(VirtualMemoryArea vma) {
return vma.type == VMAType::Direct || vma.type == VMAType::Flexible ||
vma.type == VMAType::Pooled;
@ -335,17 +299,17 @@ private:
VAddr SearchFree(VAddr virtual_addr, u64 size, u32 alignment);
VMAHandle MergeAdjacent(VMAMap& map, VMAHandle iter);
PhysHandle MergeAdjacent(PhysMap& map, PhysHandle iter);
VMAHandle CarveVMA(VAddr virtual_addr, u64 size);
DMemHandle CarveDmemArea(PAddr addr, u64 size);
FMemHandle CarveFmemArea(PAddr addr, u64 size);
PhysHandle CarvePhysArea(PhysMap& map, PAddr addr, u64 size);
VMAHandle Split(VMAHandle vma_handle, u64 offset_in_vma);
DMemHandle Split(DMemHandle dmem_handle, u64 offset_in_area);
FMemHandle Split(FMemHandle fmem_handle, u64 offset_in_area);
PhysHandle Split(PhysMap& map, PhysHandle dmem_handle, u64 offset_in_area);
u64 UnmapBytesFromEntry(VAddr virtual_addr, VirtualMemoryArea vma_base, u64 size);
@ -353,14 +317,15 @@ private:
private:
AddressSpace impl;
DMemMap dmem_map;
FMemMap fmem_map;
PhysMap dmem_map;
PhysMap fmem_map;
VMAMap vma_map;
std::mutex mutex;
Common::SharedFirstMutex mutex{};
u64 total_direct_size{};
u64 total_flexible_size{};
u64 flexible_usage{};
u64 pool_budget{};
s32 sdk_version{};
Vulkan::Rasterizer* rasterizer{};
struct PrtArea {

View File

@ -23,6 +23,7 @@
#include <windows.h>
#endif
#include <core/emulator_settings.h>
#include <common/key_manager.h>
int main(int argc, char* argv[]) {
#ifdef _WIN32
@ -39,7 +40,17 @@ int main(int argc, char* argv[]) {
const auto user_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir);
Config::load(user_dir / "config.toml");
// temp copy the trophy key from old config to key manager if exists
auto key_manager = KeyManager::GetInstance();
if (key_manager->GetAllKeys().TrophyKeySet.ReleaseTrophyKey.empty()) {
if (!Config::getTrophyKey().empty()) {
key_manager->SetAllKeys(
{.TrophyKeySet = {.ReleaseTrophyKey =
KeyManager::HexStringToBytes(Config::getTrophyKey())}});
key_manager->SaveToFile();
}
}
bool has_game_argument = false;
std::string game_path;
std::vector<std::string> game_args{};

View File

@ -660,6 +660,7 @@ void PatchGlobalDataShareAccess(IR::Block& block, IR::Inst& inst, Info& info,
inst.SetArg(1, ir.Imm32(binding));
} else {
// Convert shared memory opcode to storage buffer atomic to GDS buffer.
auto& buffer = info.buffers[binding];
const IR::U32 offset = IR::U32{inst.Arg(0)};
const IR::U32 address_words = ir.ShiftRightLogical(offset, ir.Imm32(1));
const IR::U32 address_dwords = ir.ShiftRightLogical(offset, ir.Imm32(2));
@ -705,27 +706,35 @@ void PatchGlobalDataShareAccess(IR::Block& block, IR::Inst& inst, Info& info,
case IR::Opcode::SharedAtomicXor32:
inst.ReplaceUsesWith(ir.BufferAtomicXor(handle, address_dwords, inst.Arg(1), {}));
break;
case IR::Opcode::LoadSharedU16:
case IR::Opcode::LoadSharedU16: {
inst.ReplaceUsesWith(ir.LoadBufferU16(handle, address_words, {}));
buffer.used_types |= IR::Type::U16;
break;
}
case IR::Opcode::LoadSharedU32:
inst.ReplaceUsesWith(ir.LoadBufferU32(1, handle, address_dwords, {}));
break;
case IR::Opcode::LoadSharedU64:
case IR::Opcode::LoadSharedU64: {
inst.ReplaceUsesWith(ir.LoadBufferU64(handle, address_qwords, {}));
buffer.used_types |= IR::Type::U64;
break;
case IR::Opcode::WriteSharedU16:
}
case IR::Opcode::WriteSharedU16: {
ir.StoreBufferU16(handle, address_words, IR::U16{inst.Arg(1)}, {});
inst.Invalidate();
buffer.used_types |= IR::Type::U16;
break;
}
case IR::Opcode::WriteSharedU32:
ir.StoreBufferU32(1, handle, address_dwords, inst.Arg(1), {});
inst.Invalidate();
break;
case IR::Opcode::WriteSharedU64:
case IR::Opcode::WriteSharedU64: {
ir.StoreBufferU64(handle, address_qwords, IR::U64{inst.Arg(1)}, {});
inst.Invalidate();
buffer.used_types |= IR::Type::U64;
break;
}
default:
UNREACHABLE();
}

View File

@ -830,7 +830,14 @@ Liverpool::Task Liverpool::ProcessCompute(std::span<const u32> acb, u32 vqid) {
FIBER_ENTER(acb_task_name[vqid]);
auto& queue = asc_queues[{vqid}];
struct IndirectPatch {
const PM4Header* header;
VAddr indirect_addr;
};
boost::container::small_vector<IndirectPatch, 4> indirect_patches;
auto base_addr = reinterpret_cast<VAddr>(acb.data());
size_t acb_size = acb.size_bytes();
while (!acb.empty()) {
ProcessCommands();
@ -919,8 +926,18 @@ Liverpool::Task Liverpool::ProcessCompute(std::span<const u32> acb, u32 vqid) {
dma_data->src_sel == DmaDataSrc::MemoryUsingL2) &&
(dma_data->dst_sel == DmaDataDst::Memory ||
dma_data->dst_sel == DmaDataDst::MemoryUsingL2)) {
rasterizer->CopyBuffer(dma_data->DstAddress<VAddr>(), dma_data->SrcAddress<VAddr>(),
dma_data->NumBytes(), false, false);
const u32 num_bytes = dma_data->NumBytes();
const VAddr src_addr = dma_data->SrcAddress<VAddr>();
const VAddr dst_addr = dma_data->DstAddress<VAddr>();
const PM4Header* header =
reinterpret_cast<const PM4Header*>(dst_addr - sizeof(PM4Header));
if (dst_addr >= base_addr && dst_addr < base_addr + acb_size &&
num_bytes == sizeof(PM4CmdDispatchIndirect::GroupDimensions) &&
header->type == 3 && header->type3.opcode == PM4ItOpcode::DispatchDirect) {
indirect_patches.emplace_back(header, src_addr);
} else {
rasterizer->CopyBuffer(dst_addr, src_addr, num_bytes, false, false);
}
} else {
UNREACHABLE_MSG("WriteData src_sel = {}, dst_sel = {}",
u32(dma_data->src_sel.Value()), u32(dma_data->dst_sel.Value()));
@ -964,6 +981,12 @@ Liverpool::Task Liverpool::ProcessCompute(std::span<const u32> acb, u32 vqid) {
}
case PM4ItOpcode::DispatchDirect: {
const auto* dispatch_direct = reinterpret_cast<const PM4CmdDispatchDirect*>(header);
if (auto it = std::ranges::find(indirect_patches, header, &IndirectPatch::header);
it != indirect_patches.end()) {
const auto size = sizeof(PM4CmdDispatchIndirect::GroupDimensions);
rasterizer->DispatchIndirect(it->indirect_addr, 0, size);
break;
}
auto& cs_program = GetCsRegs();
cs_program.dim_x = dispatch_direct->dim_x;
cs_program.dim_y = dispatch_direct->dim_y;
@ -1034,9 +1057,13 @@ Liverpool::Task Liverpool::ProcessCompute(std::span<const u32> acb, u32 vqid) {
}
case PM4ItOpcode::ReleaseMem: {
const auto* release_mem = reinterpret_cast<const PM4CmdReleaseMem*>(header);
release_mem->SignalFence([pipe_id = queue.pipe_id] {
Platform::IrqC::Instance()->Signal(static_cast<Platform::InterruptId>(pipe_id));
});
release_mem->SignalFence(
[pipe_id = queue.pipe_id] {
Platform::IrqC::Instance()->Signal(static_cast<Platform::InterruptId>(pipe_id));
},
[this](VAddr dst, u16 gds_index, u16 num_dwords) {
rasterizer->CopyBuffer(dst, gds_index, num_dwords * sizeof(u32), false, true);
});
break;
}
case PM4ItOpcode::EventWrite: {

View File

@ -50,7 +50,7 @@ union PM4Type3Header {
}
u32 NumWords() const {
return count + 1;
return (count + 1) & 0x3fff;
}
u32 raw;
@ -327,6 +327,7 @@ enum class DataSelect : u32 {
Data64 = 2,
GpuClock64 = 3,
PerfCounter = 4,
GdsMemStore = 5,
};
enum class InterruptSelect : u32 {
@ -920,8 +921,9 @@ struct PM4CmdReleaseMem {
u32 data_hi;
template <typename T>
T* Address() const {
return reinterpret_cast<T*>(address_lo | u64(address_hi) << 32);
T Address() const {
u64 full_address = address_lo | (u64(address_hi) << 32);
return std::bit_cast<T>(full_address);
}
u32 DataDWord() const {
@ -932,22 +934,26 @@ struct PM4CmdReleaseMem {
return data_lo | u64(data_hi) << 32;
}
void SignalFence(auto&& signal_irq) const {
void SignalFence(auto&& signal_irq, auto&& gds_to_mem) const {
switch (data_sel.Value()) {
case DataSelect::Data32Low: {
*Address<u32>() = DataDWord();
*Address<u32*>() = DataDWord();
break;
}
case DataSelect::Data64: {
*Address<u64>() = DataQWord();
*Address<u64*>() = DataQWord();
break;
}
case DataSelect::GpuClock64: {
*Address<u64>() = GetGpuClock64();
*Address<u64*>() = GetGpuClock64();
break;
}
case DataSelect::PerfCounter: {
*Address<u64>() = GetGpuPerfCounter();
*Address<u64*>() = GetGpuPerfCounter();
break;
}
case DataSelect::GdsMemStore: {
gds_to_mem(Address<VAddr>(), gds_index, num_dw);
break;
}
default: {