Compare commits

...

8 Commits

Author SHA1 Message Date
Mac 2f4b8a335d shader: allow SDWA byte destinations for bitwise VOP2 ops 2026-10-08 22:12:05 +04:00
nmzik b38b7454a6 Preserve vertex export locations for mixed pixel interpolation 2026-10-08 22:05:53 +04:00
nmzik b65dc12208 Track pad port ownership and reject duplicate opens 2026-10-08 21:58:53 +04:00
Devran Cosmo Uenal 58978d154a font: rasterize glyphs larger than 128 pixels (#1214)
Glyph images were rendered into a fixed 128x128 buffer and their width and
height were clamped to 128. Since glyphs are rasterized at the requested
scale (f96a14a0), large text such as title cards lost every row and column
past 128 pixels, so letters were drawn cut into bands.

The glyph buffers now grow to the glyph's bitmap box and width_byte is the
glyph's width. The VGA fallback font keeps its 128-pixel limit; scalable
glyphs are limited to 4096 pixels only to reject absurd scales.
2026-10-08 20:03:13 +04:00
popcoder565 1f3adf5109 cmake: accept glslang's current tool name and document Python 3 (#1161)
* cmake: accept glslang's current tool name and document Python 3

Official glslang release archives for Windows ship the compiler only as
glslang.exe, so configuring with one of them on PATH could not find
glslangValidator. Fall back to glslang; glslangValidator is still
preferred where both exist.

The bundled SPIRV-Tools needs Python 3 at configure time, which the
Windows build requirements did not mention.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs: list Python 3 in the Linux and macOS prerequisites

The bundled SPIRV-Tools calls find_host_package(Python3 REQUIRED) on every platform, so the apt and Homebrew package lists need it too, not only the Windows requirements.

* cmake: remove glslang naming comment

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: nmzik <Nmzik@mail.ru>
2026-10-08 19:46:09 +04:00
NeySlim 107d5f750a libNet: finish NpCommerce dialogs at open (#1166)
* libNet: finish NpCommerce dialogs at open

* libNet: complete NpCommerce dialog cancellation

---------

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-10-08 19:31:47 +04:00
Dimitar Vagalinski f40755173f ampr: keep resolved host paths in native form (#1175)
* ampr: keep resolved host paths in native form

ResolveOnePath stored each resolved file as a UTF-8 std::string
(Common::PathToString) and the read, stat and size paths turned that
string back into a std::filesystem::path. On POSIX this round trip is a
no-op, but on Windows path(std::string) decodes the bytes with the ANSI
code page, not UTF-8 (/utf-8 only sets the source and execution
charsets). Any non-ASCII character in the game folder therefore produced
a different path: the resolve step found the file through the native
path, then every APR read of it failed with ENOENT.

Tokyo Xtreme Racer (#1156) installed under "D:/Инсталляторы/[ps5]/..."
hits this on its first pak read:

  APR read missing host file: .../tokyoxtremeracer/content/paks/global.utoc
  Apr read failure 80020002 at CB offset 0   (LowLevelFatalError, abort)

even though KernelStat and the directory listing see the same file.

Store std::filesystem::path in ResolvedPathInfo and the file-id table
and pass it straight to Common::File. Convert to UTF-8 only for the log
lines.

* tests: cover APR stat and size under a non-ASCII host folder

Resolve a file inside a Cyrillic directory through APR and check that
the stat and size exports find it by ID, matching the Tokyo Xtreme
Racer install layout from #1156. On POSIX the old string round trip
also passed, so this guards the Windows path.

* tests: read the non-ASCII APR fixture through an APR command

Review on #1175 noted that the Unicode fixture only checked the stat
and size exports. A submitted APR read opens the stored host path in
ExecuteCommand/ReadHostFileToGuest, which the fixture did not reach.

Add AprReadMatches, which submits one APR read command for a file ID
and compares the bytes, and use it on the file under the Cyrillic
folder. As with the stat and size checks, POSIX hosts run this path but
only Windows would show an ANSI code page round trip.

* Remove APR path tests from this change

* Remove unrelated formatting changes

* Remove native path explanatory comments

---------

Co-authored-by: overlordxrz-source <overlordxrz-source@users.noreply.github.com>
Co-authored-by: nmzik <Nmzik@mail.ru>
2026-10-08 19:27:10 +04:00
Romain Hedouin 4e14eed770 kernel: start the semaphore wait timer after the early returns (#1173)
Wait() read the clock and looked up the thread id before checking for an available token. Both are only needed once the thread has to block.
2026-10-08 19:21:36 +04:00
19 changed files with 561 additions and 129 deletions
+1 -1
View File
@@ -244,7 +244,7 @@ file(GLOB kyty_emulator_src CONFIGURE_DEPENDS
"${KYTY_SOURCE_DIR}/loader/*.h"
)
find_program(KYTY_GLSLANG_VALIDATOR glslangValidator REQUIRED)
find_program(KYTY_GLSLANG_VALIDATOR NAMES glslangValidator glslang REQUIRED)
set(gpu_tiler_shader_dir "${KYTY_SOURCE_DIR}/graphics/host_gpu/shaders")
set(gpu_tiler_generated_dir "${PROJECT_BINARY_DIR}/gpu_tiler_shaders")
set(gpu_tiler_shader_names
+4 -3
View File
@@ -141,7 +141,8 @@ the Vulkan/SPIR-V validation rules.
- Visual Studio 2022 or Build Tools 2022 with the **Desktop development with C++** workload and
**C++ Clang tools for Windows** component
- Qt 6 for MSVC 2022 64-bit, including Concurrent, Network, and Widgets
- [glslang](https://github.com/KhronosGroup/glslang/releases) (`glslangValidator`) on `PATH`
- [glslang](https://github.com/KhronosGroup/glslang/releases) (`glslang` or `glslangValidator`) on `PATH`
- Python 3 on `PATH`; the bundled SPIRV-Tools runs it at configure time
The Microsoft C++ compiler (`cl.exe`) is not supported; use `clang-cl`.
@@ -176,7 +177,7 @@ build has no working sound and no gamepad hotplug:
```bash
sudo apt-get install --no-install-recommends \
clang lld ninja-build cmake git glslang-tools pkg-config \
clang lld ninja-build cmake git glslang-tools python3 pkg-config \
libgl1-mesa-dev libx11-dev libxcursor-dev libxext-dev libxfixes-dev \
libxi-dev libxrandr-dev libxss-dev libxtst-dev libxkbcommon-dev \
libasound2-dev libpulse-dev libudev-dev libdbus-1-dev libwayland-dev wayland-protocols
@@ -251,7 +252,7 @@ Requirements:
- An Apple Silicon Mac with Rosetta 2 installed (`softwareupdate --install-rosetta`)
- Xcode (or the Command Line Tools)
- Homebrew packages: `brew install cmake ninja glslang`
- Homebrew packages: `brew install cmake ninja glslang python`
- Qt 6 (Concurrent, Network, Widgets) with x86-64 support. The official Qt installation is
universal and works; Homebrew's Qt is arm64-only and will not link
@@ -617,6 +617,13 @@ PipelineCache::GraphicsPrograms PipelineCache::GetGraphicsPrograms(
ShaderParams pixel_params;
if (pixel_active) {
pixel_params = PrepareProgram(pixel_regs, sh, target_export_mapping, pixel_info);
if (!mesh_active && !tess_active && Prospero::IsRectList(user_config.GetPrimType())) {
pixel_info.parameter_mode = ShaderPixelParameterMode::Rectangle;
} else if (context.GetModeControl().provoking_vtx_last) {
pixel_info.parameter_mode = ShaderPixelParameterMode::LastVertex;
} else {
pixel_info.parameter_mode = ShaderPixelParameterMode::FirstVertex;
}
const auto& blend = context.GetBlendControl(0);
pixel_info.dual_source_blending =
blend.enable && !context.GetRenderTarget(0).info.blend_bypass &&
@@ -177,6 +177,13 @@ uint32_t EmitAttribute(EmitterState& state, uint32_t attr, uint32_t chan) {
return value;
};
if (input->per_vertex) {
if (PixelParameterIsFlat(state, attr)) {
const auto value = load_per_vertex(
state.input_info.pixel->parameter_mode == ShaderPixelParameterMode::LastVertex ? 2u : 0u);
const auto bits = state.builder.AllocateId();
state.builder.AddFunction(spv::OpBitcast, TypeU32(state), bits, value);
return bits;
}
const auto barycentric_kind = state.input_info.pixel->ps_no_perspective
? IR::StageInputKind::BaryCoordNoPerspective
: IR::StageInputKind::BaryCoordSmooth;
@@ -231,8 +238,12 @@ uint32_t EmitInterpolationParameter(ValueEmitContext& ctx, uint32_t attr, uint32
return value;
};
const auto selected_vertex = (mode + 1u) % 3u;
uint32_t value = load_vertex(selected_vertex);
uint32_t selected_vertex = (mode + 1u) % 3u;
if (mode == 2u && PixelParameterIsFlat(state, attr)) {
selected_vertex =
state.input_info.pixel->parameter_mode == ShaderPixelParameterMode::LastVertex ? 2u : 0u;
}
uint32_t value = load_vertex(selected_vertex);
if (!PixelParameterIsCustom(state, attr) && mode < 2u) {
const auto delta = state.builder.AllocateId();
state.builder.AddFunction(spv::OpFSub, TypeF32(state), delta, value, load_vertex(0));
@@ -997,7 +997,8 @@ constexpr Vop2SdwaRule VOP2_SDWA_RULES[] = {
{SdwaSelAll(), SdwaSelAll(), SdwaSelAll(), true, false},
{SdwaSelFull(), SdwaSelAll(), SdwaSelAll(), false, false},
{SdwaSelFull(), SdwaSelAll(), SdwaSelAll(), false, false},
{SdwaSelWords() | SdwaSelFull(), SdwaSelAll(), SdwaSelAll(), true, false},
// Bitwise: WriteRawU32 inserts a byte or word destination like any other partial write.
{SdwaSelAll(), SdwaSelAll(), SdwaSelAll(), true, false},
};
static_assert(sizeof(VOP2_SDWA_RULES) / sizeof(VOP2_SDWA_RULES[0]) ==
static_cast<size_t>(Vop2SdwaProfile::Count));
@@ -227,15 +227,16 @@ void CollectPixelInputs(const Program& program, const ShaderPixelInputInfo* pixe
}
}
}
// Aliases of a vertex output share one SPIR-V interface variable. If any
// alias reads raw vertices, interpolate the other aliases from those too.
// Mixed interpolation modes share raw vertices at their guest export slot.
// Rectangle expansion alone supplies separate flat and smooth outputs.
for (uint32_t input = 0; input < pixel->input_num; input++) {
for (uint32_t alias = 0; alias < pixel->input_num; alias++) {
const bool same_mode = ShaderPixelParameterIsFlat(*pixel, input) ==
ShaderPixelParameterIsFlat(*pixel, alias);
if (ShaderPixelParameterMappedLocation(*pixel, input) ==
ShaderPixelParameterMappedLocation(*pixel, alias) &&
ShaderPixelParameterIsFlat(*pixel, input) ==
ShaderPixelParameterIsFlat(*pixel, alias)) {
per_vertex[input] = per_vertex[input] || per_vertex[alias];
(pixel->parameter_mode != ShaderPixelParameterMode::Rectangle || same_mode)) {
per_vertex[input] = per_vertex[input] || per_vertex[alias] || !same_mode;
}
}
}
@@ -244,7 +245,8 @@ void CollectPixelInputs(const Program& program, const ShaderPixelInputInfo* pixe
per_vertex[input]);
}
for (uint32_t input = 0; input < pixel->input_num; input++) {
if (interpolated[input] && per_vertex[input]) {
if (interpolated[input] && per_vertex[input] &&
!ShaderPixelParameterIsFlat(*pixel, input)) {
const auto kind = pixel->ps_no_perspective ? StageInputKind::BaryCoordNoPerspective
: StageInputKind::BaryCoordSmooth;
AddInput(info, kind, 0, 3,
+1
View File
@@ -663,6 +663,7 @@ void BuildStageStaticKey(const ShaderPixelInputInfo& info, std::vector<uint32_t>
key.push_back(static_cast<uint32_t>(info.ps_front_face));
key.push_back(static_cast<uint32_t>(info.ps_ancillary));
key.push_back(static_cast<uint32_t>(info.ps_no_perspective));
key.push_back(static_cast<uint32_t>(info.parameter_mode));
key.push_back(static_cast<uint32_t>(info.ps_pixel_kill_enable));
key.push_back(static_cast<uint32_t>(info.ps_depth_export_enable));
key.push_back(static_cast<uint32_t>(info.ps_sample_mask_export_enable));
+2
View File
@@ -159,6 +159,7 @@ struct ShaderComputeInputInfo: ShaderWorkgroupInputInfo {
};
enum class ShaderAlphaBlendSource : uint8_t { None, SourceAlpha, SourceAlphaOne, SourceAlphaZero };
enum class ShaderPixelParameterMode : uint8_t { FirstVertex, LastVertex, Rectangle };
struct ShaderPixelInputInfo {
uint32_t interpolator_settings[32] = {0};
@@ -180,6 +181,7 @@ struct ShaderPixelInputInfo {
bool ps_front_face = false;
bool ps_ancillary = false;
bool ps_no_perspective = false;
ShaderPixelParameterMode parameter_mode = ShaderPixelParameterMode::FirstVertex;
bool ps_pixel_kill_enable = false;
bool ps_depth_export_enable = false;
bool ps_sample_mask_export_enable = false;
+4 -2
View File
@@ -30,6 +30,9 @@ uint32_t ShaderPixelParameterMappedLocation(const ShaderPixelInputInfo& info, ui
uint32_t ShaderPixelParameterLocation(const ShaderPixelInputInfo& info,
std::span<const uint32_t> active_inputs, uint32_t input) {
if (info.parameter_mode != ShaderPixelParameterMode::Rectangle) {
return ShaderPixelParameterMappedLocation(info, input);
}
std::array<bool, 32> used_locations {};
for (const auto active_input: active_inputs) {
used_locations[ShaderPixelParameterMappedLocation(info, active_input)] = true;
@@ -42,8 +45,7 @@ uint32_t ShaderPixelParameterLocation(const ShaderPixelInputInfo& info,
auto& location = group_locations[group];
if (location == UINT32_MAX) {
location = mapped;
// Smooth and custom interpolation read the same vertex output. Only
// differing flat/smooth rectangle outputs need separate locations.
// Rectangle expansion broadcasts flat aliases through separate outputs.
if (group_locations[group ^ 1u] != UINT32_MAX) {
location = 0;
while (location < used_locations.size() && used_locations[location]) {
+6 -6
View File
@@ -198,12 +198,6 @@ KernelSemaPrivate::Result KernelSemaPrivate::Wait(int need_count, uint32_t* ptr_
infinitely = false;
}
uint32_t elapsed = 0;
Common::Timer t;
t.Start();
int id = Common::Thread::GetThreadIdUnique();
if (m_count >= need_count) {
m_count -= need_count;
if (ptr_micros != nullptr) {
@@ -216,6 +210,12 @@ KernelSemaPrivate::Result KernelSemaPrivate::Wait(int need_count, uint32_t* ptr_
return Result::TimedOut;
}
uint32_t elapsed = 0;
Common::Timer t;
t.Start();
int id = Common::Thread::GetThreadIdUnique();
WaitingThread waiter {};
waiter.id = id;
waiter.priority = (m_fifo_order ? 0 : Libs::LibKernel::PthreadGetCurrentPriorityForKernel());
+112 -57
View File
@@ -24,8 +24,12 @@ namespace Libs::Controller {
LIB_NAME("Pad", "Pad");
constexpr int PAD_ERROR_INVALID_ARG = -2137915391; /* 0x80920001 */
constexpr int PAD_ERROR_INVALID_HANDLE = -2137915389; /* 0x80920003 */
constexpr int PAD_ERROR_INVALID_ARG = -2137915391; /* 0x80920001 */
constexpr int PAD_ERROR_INVALID_HANDLE = -2137915389; /* 0x80920003 */
constexpr int PAD_ERROR_ALREADY_OPENED = -2137915388; /* 0x80920004 */
constexpr int PAD_ERROR_NOT_INITIALIZED = -2137915387; /* 0x80920005 */
constexpr int PAD_ERROR_NO_HANDLE = -2137915384; /* 0x80920008 */
constexpr int PAD_HANDLE = 1;
constexpr uint32_t RUMBLE_DURATION_MS = 0xffff;
constexpr uint32_t RELEASE_FLUSH_MS = 50;
@@ -107,6 +111,12 @@ public:
KYTY_CLASS_NO_COPY(GameController);
int InitPort();
int OpenPort(int user_id, int type, int index);
int GetPortHandle(int user_id, int type, int index);
int ClosePort(int handle);
int CheckPortHandle(int handle);
void Connect(int id);
void Disconnect(int id);
void Button(int id, uint32_t button, bool down);
@@ -131,6 +141,9 @@ public:
private:
static constexpr uint32_t STATES_MAX = 64;
enum class PortState { Uninitialized, Closed, Open };
int CheckPortHandleLocked(int handle) const;
void CheckActive();
void AddState();
@@ -140,6 +153,7 @@ private:
void UpdateTriggerEffectState();
Common::Mutex m_mutex;
PortState m_port_state = PortState::Uninitialized;
std::vector<int> m_connected_ids;
int m_active_id = -1;
bool m_connected = false;
@@ -925,21 +939,71 @@ int GetActiveControllerId() {
return g_controller != nullptr ? g_controller->GetActiveControllerId() : -1;
}
int KYTY_SYSV_ABI PadInit() {
PRINT_NAME();
static bool PadOpenArgsAreValid(int user_id, int type, int index) {
// The host keyboard and gamepad feed one standard port for the configured user.
return user_id == Config::GetUserId() && type == 0 && index == 0;
}
int GameController::InitPort() {
Common::LockGuard lock(m_mutex);
if (m_port_state == PortState::Uninitialized) {
m_port_state = PortState::Closed;
}
return OK;
}
static bool PadOpenArgsAreValid(int user_id, int type, int index) {
constexpr int user_id_system = 0xff;
constexpr int port_type_standard = 0;
constexpr int port_type_special = 2;
constexpr int port_type_remote = 16;
const bool personal_port =
user_id == Config::GetUserId() && (type == port_type_standard || type == port_type_special);
const bool system_remote_control = user_id == user_id_system && type == port_type_remote;
return index == 0 && (personal_port || system_remote_control);
int GameController::OpenPort(int user_id, int type, int index) {
Common::LockGuard lock(m_mutex);
if (m_port_state == PortState::Uninitialized) {
return PAD_ERROR_NOT_INITIALIZED;
}
if (!PadOpenArgsAreValid(user_id, type, index)) {
return PAD_ERROR_INVALID_ARG;
}
if (m_port_state == PortState::Open) {
return PAD_ERROR_ALREADY_OPENED;
}
m_port_state = PortState::Open;
return PAD_HANDLE;
}
int GameController::GetPortHandle(int user_id, int type, int index) {
Common::LockGuard lock(m_mutex);
if (m_port_state == PortState::Uninitialized) {
return PAD_ERROR_NOT_INITIALIZED;
}
return m_port_state == PortState::Open && PadOpenArgsAreValid(user_id, type, index) ? PAD_HANDLE
: PAD_ERROR_NO_HANDLE;
}
int GameController::CheckPortHandleLocked(int handle) const {
if (m_port_state == PortState::Uninitialized) {
return PAD_ERROR_NOT_INITIALIZED;
}
return m_port_state == PortState::Open && handle == PAD_HANDLE ? OK : PAD_ERROR_INVALID_HANDLE;
}
int GameController::CheckPortHandle(int handle) {
Common::LockGuard lock(m_mutex);
return CheckPortHandleLocked(handle);
}
int GameController::ClosePort(int handle) {
Common::LockGuard lock(m_mutex);
if (const auto error = CheckPortHandleLocked(handle); error != OK) {
return error;
}
m_port_state = PortState::Closed;
return OK;
}
int PadCheckHandle(int handle) {
return g_controller->CheckPortHandle(handle);
}
int KYTY_SYSV_ABI PadInit() {
PRINT_NAME();
return g_controller->InitPort();
}
int KYTY_SYSV_ABI PadOpen(int user_id, int type, int index, const void* param) {
@@ -951,15 +1015,7 @@ int KYTY_SYSV_ABI PadOpen(int user_id, int type, int index, const void* param) {
"\t param = 0x%016" PRIx64 "\n",
user_id, type, index, reinterpret_cast<uint64_t>(param));
constexpr int pad_error_invalid_arg = -2137915391; /* 0x80920001 */
if (!PadOpenArgsAreValid(user_id, type, index)) {
return pad_error_invalid_arg;
}
int handle = 1;
return handle;
return g_controller->OpenPort(user_id, type, index);
}
int KYTY_SYSV_ABI PadGetHandle(int user_id, int type, int index) {
@@ -970,20 +1026,20 @@ int KYTY_SYSV_ABI PadGetHandle(int user_id, int type, int index) {
"\t index = %d\n",
user_id, type, index);
constexpr int pad_error_device_no_handle = -2137915384; /* 0x80920008 */
return g_controller->GetPortHandle(user_id, type, index);
}
if (!PadOpenArgsAreValid(user_id, type, index)) {
return pad_error_device_no_handle;
}
return 1;
int KYTY_SYSV_ABI PadClose(int handle) {
PRINT_NAME();
LOGF("\t handle = %d\n", handle);
return g_controller->ClosePort(handle);
}
int KYTY_SYSV_ABI PadSetMotionSensorState(int handle, bool enable) {
PRINT_NAME();
if (handle != 1) {
return PAD_ERROR_INVALID_HANDLE;
if (const auto error = PadCheckHandle(handle); error != OK) {
return error;
}
LOGF("\t enable = %s\n", (enable ? "true" : "false"));
@@ -995,8 +1051,8 @@ int KYTY_SYSV_ABI PadSetMotionSensorState(int handle, bool enable) {
int KYTY_SYSV_ABI PadSetAngularVelocityDeadbandState(int handle, bool enable) {
PRINT_NAME();
if (handle != 1) {
return PAD_ERROR_INVALID_HANDLE;
if (const auto error = PadCheckHandle(handle); error != OK) {
return error;
}
LOGF("\t enable = %s\n", (enable ? "true" : "false"));
@@ -1007,8 +1063,8 @@ int KYTY_SYSV_ABI PadSetAngularVelocityDeadbandState(int handle, bool enable) {
int KYTY_SYSV_ABI PadResetOrientation(int handle) {
PRINT_NAME();
if (handle != 1) {
return PAD_ERROR_INVALID_HANDLE;
if (const auto error = PadCheckHandle(handle); error != OK) {
return error;
}
g_controller->ResetOrientation();
@@ -1018,18 +1074,17 @@ int KYTY_SYSV_ABI PadResetOrientation(int handle) {
int KYTY_SYSV_ABI PadGetControllerInformation(int handle, PadControllerInformation* info) {
PRINT_NAME();
int connected_count = 0;
bool connected = false;
g_controller->GetConnectionInfo(&connected, &connected_count);
if (handle != 1) {
return PAD_ERROR_INVALID_HANDLE;
if (const auto error = PadCheckHandle(handle); error != OK) {
return error;
}
if (info == nullptr) {
return PAD_ERROR_INVALID_ARG;
}
int connected_count = 0;
bool connected = false;
g_controller->GetConnectionInfo(&connected, &connected_count);
std::memset(info, 0, sizeof(*info));
info->touch_pixel_density = 44.86f;
@@ -1048,8 +1103,8 @@ int KYTY_SYSV_ABI PadGetControllerInformation(int handle, PadControllerInformati
int KYTY_SYSV_ABI PadIsRemoteController(int handle, bool* is_remote) {
PRINT_NAME();
if (handle != 1) {
return PAD_ERROR_INVALID_HANDLE;
if (const auto error = PadCheckHandle(handle); error != OK) {
return error;
}
if (is_remote == nullptr) {
return PAD_ERROR_INVALID_ARG;
@@ -1062,8 +1117,8 @@ int KYTY_SYSV_ABI PadIsRemoteController(int handle, bool* is_remote) {
int KYTY_SYSV_ABI PadReadState(int handle, PadData* data) {
PRINT_NAME();
if (handle != 1) {
return PAD_ERROR_INVALID_HANDLE;
if (const auto error = PadCheckHandle(handle); error != OK) {
return error;
}
if (data == nullptr) {
return PAD_ERROR_INVALID_ARG;
@@ -1084,8 +1139,8 @@ int KYTY_SYSV_ABI PadRead(int handle, PadData* data, int num) {
PRINT_NAME();
EXIT_NOT_IMPLEMENTED(num < 1 || num > 64);
if (handle != 1) {
return PAD_ERROR_INVALID_HANDLE;
if (const auto error = PadCheckHandle(handle); error != OK) {
return error;
}
if (data == nullptr) {
return PAD_ERROR_INVALID_ARG;
@@ -1116,8 +1171,8 @@ int KYTY_SYSV_ABI PadRead(int handle, PadData* data, int num) {
int KYTY_SYSV_ABI PadSetVibration(int handle, const PadVibrationParam* param) {
PRINT_NAME();
if (handle != 1) {
return PAD_ERROR_INVALID_HANDLE;
if (const auto error = PadCheckHandle(handle); error != OK) {
return error;
}
if (param == nullptr) {
return PAD_ERROR_INVALID_ARG;
@@ -1135,8 +1190,8 @@ int KYTY_SYSV_ABI PadSetVibration(int handle, const PadVibrationParam* param) {
int KYTY_SYSV_ABI PadResetLightBar(int handle) {
PRINT_NAME();
if (handle != 1) {
return PAD_ERROR_INVALID_HANDLE;
if (const auto error = PadCheckHandle(handle); error != OK) {
return error;
}
return OK;
@@ -1145,8 +1200,8 @@ int KYTY_SYSV_ABI PadResetLightBar(int handle) {
int KYTY_SYSV_ABI PadSetLightBar(int handle, const PadLightBarParam* param) {
PRINT_NAME();
if (handle != 1) {
return PAD_ERROR_INVALID_HANDLE;
if (const auto error = PadCheckHandle(handle); error != OK) {
return error;
}
if (param == nullptr) {
return PAD_ERROR_INVALID_ARG;
@@ -1160,8 +1215,8 @@ int KYTY_SYSV_ABI PadSetLightBar(int handle, const PadLightBarParam* param) {
int KYTY_SYSV_ABI PadSetTriggerEffect(int handle, const PadTriggerEffectParam* param) {
PRINT_NAME();
if (handle != 1) {
return PAD_ERROR_INVALID_HANDLE;
if (const auto error = PadCheckHandle(handle); error != OK) {
return error;
}
if (param == nullptr) {
return PAD_ERROR_INVALID_ARG;
@@ -1175,8 +1230,8 @@ int KYTY_SYSV_ABI PadGetTriggerEffectState(int handle, PadTriggerEffectStateInfo
LOGF("\t handle = %d\n", handle);
if (handle != 1) {
return PAD_ERROR_INVALID_HANDLE;
if (const auto error = PadCheckHandle(handle); error != OK) {
return error;
}
if (info == nullptr) {
return PAD_ERROR_INVALID_ARG;
+3
View File
@@ -81,9 +81,12 @@ enum class Setting { SpeakerVolume, VibrationIntensity, TriggerEffectIntensity }
void CycleSetting(Setting setting);
float GetSettingScale(Setting setting);
int PadCheckHandle(int handle);
int KYTY_SYSV_ABI PadInit();
int KYTY_SYSV_ABI PadOpen(int user_id, int type, int index, const void* param);
int KYTY_SYSV_ABI PadGetHandle(int user_id, int type, int index);
int KYTY_SYSV_ABI PadClose(int handle);
int KYTY_SYSV_ABI PadSetMotionSensorState(int handle, bool enable);
int KYTY_SYSV_ABI PadSetAngularVelocityDeadbandState(int handle, bool enable);
int KYTY_SYSV_ABI PadResetOrientation(int handle);
+14 -13
View File
@@ -19,6 +19,7 @@
#include <cstdint>
#include <cstring>
#include <deque>
#include <filesystem>
#include <limits>
#include <mutex>
#include <string>
@@ -48,7 +49,7 @@ struct ResolvedPathInfo {
uint32_t file_id = 0xffffffffu;
uint64_t file_size = 0;
bool is_dir = false;
std::string host_path;
std::filesystem::path host_path;
};
static std::mutex g_mutex;
@@ -56,7 +57,7 @@ static uint32_t g_next_submission_id =
static uint32_t g_next_file_id = 1;
static std::unordered_map<uint32_t, bool> g_submissions;
static std::condition_variable g_submission_cv;
static std::unordered_map<uint32_t, std::string> g_files;
static std::unordered_map<uint32_t, std::filesystem::path> g_files;
static std::unordered_map<uint32_t, uint64_t> g_file_sizes;
static std::unordered_map<std::string, ResolvedPathInfo> g_resolved_paths;
@@ -113,7 +114,7 @@ static int ReadGuestCString(uint64_t addr, char* out, size_t out_size) {
return LibKernel::KERNEL_ERROR_ENAMETOOLONG;
}
static bool TryGetHostPath(uint32_t file_id, std::string* out) {
static bool TryGetHostPath(uint32_t file_id, std::filesystem::path* out) {
std::scoped_lock lock(g_mutex);
const auto it = g_files.find(file_id);
if (it == g_files.end()) {
@@ -133,7 +134,7 @@ static bool TryGetHostFileSize(uint32_t file_id, uint64_t* out) {
return true;
}
static int GetHostPathStat(const std::string& host_path, LibKernel::FileSystem::FileStat* st) {
static int GetHostPathStat(const std::filesystem::path& host_path, LibKernel::FileSystem::FileStat* st) {
if (st == nullptr) {
return LibKernel::KERNEL_ERROR_EINVAL;
}
@@ -194,7 +195,7 @@ static int ResolveOnePath(const char* guest_path, uint32_t* id, uint64_t* size)
if (!found) {
const auto real_path = LibKernel::FileSystem::GetRealFilename(path);
info.host_path = Common::PathToString(real_path);
info.host_path = real_path;
if (Common::File::IsDirectoryExisting(real_path)) {
info.is_dir = true;
@@ -225,7 +226,7 @@ static int ResolveOnePath(const char* guest_path, uint32_t* id, uint64_t* size)
}
}
if (log_missing) {
LOGF("\tAPR resolve missing path: %s -> %s\n", guest_path, info.host_path.c_str());
LOGF("\tAPR resolve missing path: %s -> %s\n", guest_path, Common::PathToString(info.host_path).c_str());
}
}
@@ -408,7 +409,7 @@ static int KYTY_SYSV_ABI GetFileStat(uint32_t file_id, LibKernel::FileSystem::Fi
return KernelSyscallResult(LibKernel::KERNEL_ERROR_EINVAL);
}
std::string host_path;
std::filesystem::path host_path;
if (!AprShared::TryGetHostPath(file_id, &host_path)) {
LOGF("\tAPR stat failed for unknown file id: 0x%08" PRIx32 "\n", file_id);
return KernelSyscallResult(LibKernel::KERNEL_ERROR_ENOENT);
@@ -430,7 +431,7 @@ static int KYTY_SYSV_ABI GetFileSize(uint32_t file_id, uint64_t* size) {
return KernelSyscallResult(LibKernel::KERNEL_ERROR_EFAULT);
}
std::string host_path;
std::filesystem::path host_path;
if (!AprShared::TryGetHostPath(file_id, &host_path)) {
LOGF("\tAPR size failed for unknown file id: 0x%08" PRIx32 "\n", file_id);
return KernelSyscallResult(LibKernel::KERNEL_ERROR_ENOENT);
@@ -1161,13 +1162,13 @@ static bool AppendCommandRecord(uint64_t command_buffer, CommandBufferState::Com
return CommitCommandBufferRecord(command_buffer, &state, record_size);
}
static int ReadHostFileToGuest(const std::string& host_path, uint64_t file_offset,
static int ReadHostFileToGuest(const std::filesystem::path& host_path, uint64_t file_offset,
uint64_t destination, uint64_t size, uint64_t* bytes_read);
static int ExecuteCommand(const CommandBufferState::Command& entry) {
if (const auto* payload = std::get_if<CommandBufferState::ReadFileCommand>(&entry.data)) {
const auto& command = *payload;
std::string host_path;
std::filesystem::path host_path;
if (!AprShared::TryGetHostPath(command.file_id, &host_path)) {
LOGF("\tAPR submit failed for unknown file id: 0x%08" PRIx32 "\n", command.file_id);
return LibKernel::KERNEL_ERROR_ENOENT;
@@ -1178,7 +1179,7 @@ static int ExecuteCommand(const CommandBufferState::Command& entry) {
command.size, &bytes_read);
if (result != OK) {
LOGF("\tAPR submit read failed: id=0x%08" PRIx32 ", result=0x%08" PRIx32 ", path=%s\n",
command.file_id, static_cast<uint32_t>(result), host_path.c_str());
command.file_id, static_cast<uint32_t>(result), Common::PathToString(host_path).c_str());
return result;
}
} else if (const auto* payload =
@@ -1380,7 +1381,7 @@ static bool AdvanceCommandBuffer(uint64_t command_buffer, uint64_t record_size)
return CommitCommandBufferRecord(command_buffer, &state, record_size);
}
static int ReadHostFileToGuest(const std::string& host_path, uint64_t file_offset,
static int ReadHostFileToGuest(const std::filesystem::path& host_path, uint64_t file_offset,
uint64_t destination, uint64_t size, uint64_t* bytes_read) {
if (bytes_read == nullptr) {
return LibKernel::KERNEL_ERROR_EINVAL;
@@ -1396,7 +1397,7 @@ static int ReadHostFileToGuest(const std::string& host_path, uint64_t file_offse
Common::File file;
if (!file.Open(host_path, Common::File::Mode::Read)) {
LOGF("\tAPR read missing host file: %s\n", host_path.c_str());
LOGF("\tAPR read missing host file: %s\n", Common::PathToString(host_path).c_str());
return LibKernel::KERNEL_ERROR_ENOENT;
}
const auto file_size = file.Size();
+17 -15
View File
@@ -78,6 +78,8 @@ constexpr int SCE_FONT_TEXT_PARSER_RESULT_TERMINATE = 0;
constexpr int SCE_FONT_TEXT_PARSER_RESULT_ERROR = -1;
constexpr int SCE_FONT_WRITING_FORM_HORIZONTAL = 0x10;
constexpr int FONT_BITMAP_MAX_DIM = 128;
// TrueType glyphs are rasterized at the requested size; the limit only rejects absurd scales.
constexpr int FONT_GLYPH_MAX_DIM = 4096;
constexpr int FONT_ERROR_INVALID_PARAMETER = static_cast<int>(0x80460002u);
constexpr int FONT_ERROR_INVALID_FONT_HANDLE = static_cast<int>(0x80460005u);
constexpr int FONT_ERROR_NO_SUPPORT_CODE = static_cast<int>(0x80460041u);
@@ -206,7 +208,7 @@ struct FontState {
const uint8_t* ttf_data;
uint32_t ttf_size;
bool has_ttf;
std::array<uint8_t, FONT_BITMAP_MAX_DIM * FONT_BITMAP_MAX_DIM> fallback_image;
std::vector<uint8_t> fallback_image;
FontTransImage trans_image;
};
@@ -227,7 +229,7 @@ struct GlyphState {
uint32_t code;
int attribute;
FontGlyphMetrics metrics;
std::array<uint8_t, FONT_BITMAP_MAX_DIM * FONT_BITMAP_MAX_DIM> image;
std::vector<uint8_t> image;
FontTransImage trans_image;
};
@@ -851,8 +853,8 @@ static bool get_stb_metrics(FontState* font, uint32_t code, FontGlyphMetrics* me
return true;
}
static bool init_stb_image(std::array<uint8_t, FONT_BITMAP_MAX_DIM * FONT_BITMAP_MAX_DIM>* image,
FontTransImage* trans_image, uint32_t code, FontState* font) {
static bool init_stb_image(std::vector<uint8_t>* image, FontTransImage* trans_image, uint32_t code,
FontState* font) {
if (image == nullptr || trans_image == nullptr || !ensure_stb_font(font)) {
return false;
}
@@ -868,12 +870,12 @@ static bool init_stb_image(std::array<uint8_t, FONT_BITMAP_MAX_DIM * FONT_BITMAP
stbtt_GetCodepointBitmapBox(&font->font_info, static_cast<int>(code), scale_x, scale_y, &x0, &y0,
&x1, &y1);
image->fill(0);
const auto width = static_cast<uint32_t>(std::clamp(x1 - x0, 0, FONT_BITMAP_MAX_DIM));
const auto height = static_cast<uint32_t>(std::clamp(y1 - y0, 0, FONT_BITMAP_MAX_DIM));
const auto width = static_cast<uint32_t>(std::clamp(x1 - x0, 0, FONT_GLYPH_MAX_DIM));
const auto height = static_cast<uint32_t>(std::clamp(y1 - y0, 0, FONT_GLYPH_MAX_DIM));
// Keep at least one byte so that blank glyphs still get a valid address.
image->assign(std::max<size_t>(static_cast<size_t>(width) * height, 1), 0);
trans_image->address = image->data();
trans_image->width_byte = FONT_BITMAP_MAX_DIM;
trans_image->width_byte = width;
trans_image->image_width = width;
trans_image->image_height = height;
@@ -882,7 +884,7 @@ static bool init_stb_image(std::array<uint8_t, FONT_BITMAP_MAX_DIM * FONT_BITMAP
}
stbtt_MakeCodepointBitmap(&font->font_info, image->data(), static_cast<int>(width),
static_cast<int>(height), FONT_BITMAP_MAX_DIM, scale_x, scale_y,
static_cast<int>(height), static_cast<int>(width), scale_x, scale_y,
static_cast<int>(code));
return true;
@@ -939,8 +941,8 @@ static uint32_t normalize_bitmap_font_code(uint32_t code) {
return static_cast<uint32_t>('?');
}
static void init_image(std::array<uint8_t, FONT_BITMAP_MAX_DIM * FONT_BITMAP_MAX_DIM>* image,
FontTransImage* trans_image, uint32_t code, const FontState* font) {
static void init_image(std::vector<uint8_t>* image, FontTransImage* trans_image, uint32_t code,
const FontState* font) {
EXIT_NOT_IMPLEMENTED(image == nullptr);
EXIT_NOT_IMPLEMENTED(trans_image == nullptr);
@@ -948,11 +950,11 @@ static void init_image(std::array<uint8_t, FONT_BITMAP_MAX_DIM * FONT_BITMAP_MAX
return;
}
image->fill(0);
code = normalize_bitmap_font_code(code);
const auto width = static_cast<uint32_t>(scaled_font_width(font));
const auto height = static_cast<uint32_t>(scaled_font_height(font));
image->assign(static_cast<size_t>(width) * height, 0);
#if LIBAVUTIL_VERSION_MAJOR < 61
const auto* vga_font = avpriv_vga16_font;
#else
@@ -965,13 +967,13 @@ static void init_image(std::array<uint8_t, FONT_BITMAP_MAX_DIM * FONT_BITMAP_MAX
for (uint32_t x = 0; x < width; x++) {
const uint32_t src_x = std::min<uint32_t>((x * 8u) / width, 7u);
if ((row & (0x80u >> src_x)) != 0) {
(*image)[y * FONT_BITMAP_MAX_DIM + x] = 0xff;
(*image)[y * width + x] = 0xff;
}
}
}
trans_image->address = image->data();
trans_image->width_byte = FONT_BITMAP_MAX_DIM;
trans_image->width_byte = width;
trans_image->image_width = width;
trans_image->image_height = height;
}
+73 -3
View File
@@ -1497,13 +1497,43 @@ namespace LibNpCommerce {
LIB_VERSION("NpCommerce", 1, "NpCommerce", 1, 1);
constexpr int COMMERCE_STATUS_NONE = 0;
constexpr int COMMERCE_STATUS_INITIALIZED = 1;
constexpr int COMMERCE_STATUS_NONE = 0;
constexpr int COMMERCE_STATUS_INITIALIZED = 1;
constexpr int COMMERCE_STATUS_FINISHED = 3;
constexpr int COMMERCE_RESULT_USER_CANCELED = 1;
constexpr int COMMERCE_ERROR_NOT_INITIALIZED = static_cast<int>(0x80B80003u);
constexpr int COMMERCE_ERROR_ALREADY_INITIALIZED = static_cast<int>(0x80B80004u);
constexpr int COMMERCE_ERROR_NOT_FINISHED = static_cast<int>(0x80B80005u);
constexpr int COMMERCE_ERROR_PARAM_INVALID = static_cast<int>(0x80B8000Au);
constexpr int COMMERCE_ERROR_ARG_NULL = static_cast<int>(0x80B8000Du);
static int g_commerce_status = COMMERCE_STATUS_NONE;
struct CommerceDialogParam {
uint8_t base_param[48];
int32_t size;
int32_t user_id;
int32_t mode;
uint32_t service_label;
const char* const* targets;
uint32_t num_targets;
uint32_t padding;
uint64_t features;
void* user_data;
uint8_t reserved[32];
};
struct CommerceDialogResult {
int32_t result;
bool authorized;
void* user_data;
uint8_t reserved[32];
};
static_assert(sizeof(CommerceDialogParam) == 128 && offsetof(CommerceDialogParam, user_data) == 88);
static_assert(sizeof(CommerceDialogResult) == 48 && offsetof(CommerceDialogResult, user_data) == 8);
static int g_commerce_status = COMMERCE_STATUS_NONE;
static CommerceDialogResult g_commerce_result {};
static int KYTY_SYSV_ABI NpCommerceDialogInitialize() {
PRINT_NAME();
@@ -1520,9 +1550,47 @@ static int KYTY_SYSV_ABI NpCommerceDialogTerminate() {
return COMMERCE_ERROR_NOT_INITIALIZED;
}
g_commerce_status = COMMERCE_STATUS_NONE;
g_commerce_result = {};
return OK;
}
static int KYTY_SYSV_ABI NpCommerceDialogOpen(const CommerceDialogParam* param) {
PRINT_NAME();
if (g_commerce_status == COMMERCE_STATUS_NONE) {
return COMMERCE_ERROR_NOT_INITIALIZED;
}
if (param == nullptr) {
return COMMERCE_ERROR_ARG_NULL;
}
g_commerce_result = {};
if (param->size == sizeof(CommerceDialogParam)) {
g_commerce_result.user_data = param->user_data;
}
g_commerce_status = COMMERCE_STATUS_FINISHED;
if (param->size != sizeof(CommerceDialogParam) || param->mode < 0 || param->mode > 5 ||
std::ranges::any_of(param->reserved, [](uint8_t value) { return value != 0; })) {
g_commerce_result.result = COMMERCE_ERROR_PARAM_INVALID;
return COMMERCE_ERROR_PARAM_INVALID;
}
g_commerce_result.result = COMMERCE_RESULT_USER_CANCELED;
return OK;
}
static int KYTY_SYSV_ABI NpCommerceDialogGetResult(CommerceDialogResult* result) {
PRINT_NAME();
if (g_commerce_status == COMMERCE_STATUS_NONE) {
return COMMERCE_ERROR_NOT_INITIALIZED;
}
if (result == nullptr) {
return COMMERCE_ERROR_ARG_NULL;
}
if (g_commerce_status != COMMERCE_STATUS_FINISHED) {
return COMMERCE_ERROR_NOT_FINISHED;
}
*result = g_commerce_result;
return result->result;
}
static int KYTY_SYSV_ABI NpCommerceDialogUpdateStatus() {
PRINT_NAME();
@@ -1532,6 +1600,8 @@ static int KYTY_SYSV_ABI NpCommerceDialogUpdateStatus() {
LIB_DEFINE(InitNet_1_NpCommerce) {
LIB_FUNC("0aR2aWmQal4", NpCommerceDialogInitialize);
LIB_FUNC("m-I92Ab50W8", NpCommerceDialogTerminate);
LIB_FUNC("DfSCDRA3EjY", NpCommerceDialogOpen);
LIB_FUNC("r42bWcQbtZY", NpCommerceDialogGetResult);
LIB_FUNC("LR5cwFMMCVE", NpCommerceDialogUpdateStatus);
}
+10 -20
View File
@@ -19,7 +19,7 @@ static int KYTY_SYSV_ABI PadSetVibrationMode(int handle, int mode) {
"\t mode = %d\n",
handle, mode);
return 0;
return Controller::PadCheckHandle(handle);
}
static int KYTY_SYSV_ABI PadSetVibrationTriggerEffectWeakWhileEmbeddedMicInUse(bool enabled) {
@@ -66,11 +66,10 @@ static int KYTY_SYSV_ABI
PadDeviceClassGetExtendedInformation(int handle, PadDeviceClassExtendedInformation* info) {
PRINT_NAME();
constexpr int pad_error_invalid_handle = -2137915389; /* 0x80920003 */
constexpr int pad_error_invalid_arg = -2137915391; /* 0x80920001 */
constexpr int pad_error_invalid_arg = -2137915391; /* 0x80920001 */
if (handle != 1) {
return pad_error_invalid_handle;
if (const auto error = Controller::PadCheckHandle(handle); error != OK) {
return error;
}
if (info == nullptr) {
return pad_error_invalid_arg;
@@ -85,16 +84,15 @@ static int KYTY_SYSV_ABI PadDeviceClassParseData(int handle, const Controller::P
PadDeviceClassData* class_data) {
PRINT_NAME();
constexpr int pad_error_invalid_handle = -2137915389; /* 0x80920003 */
constexpr int pad_error_invalid_arg = -2137915391; /* 0x80920001 */
constexpr int pad_error_invalid_arg = -2137915391; /* 0x80920001 */
LOGF("\t handle = %d\n"
"\t data = 0x%016" PRIx64 "\n"
"\t class_data = 0x%016" PRIx64 "\n",
handle, reinterpret_cast<uint64_t>(data), reinterpret_cast<uint64_t>(class_data));
if (handle != 1) {
return pad_error_invalid_handle;
if (const auto error = Controller::PadCheckHandle(handle); error != OK) {
return error;
}
if (data == nullptr || class_data == nullptr) {
return pad_error_invalid_arg;
@@ -122,15 +120,7 @@ static int KYTY_SYSV_ABI PadSetTiltCorrectionState(int handle, bool enabled) {
"\t enabled = %d\n",
handle, enabled ? 1 : 0);
return 0;
}
static int KYTY_SYSV_ABI PadClose(int handle) {
PRINT_NAME();
LOGF("\t handle = %d\n", handle);
return OK;
return Controller::PadCheckHandle(handle);
}
// PPSA02385
@@ -154,7 +144,7 @@ static int KYTY_SYSV_ABI PadGetExtControllerInformationStub(int handle, void* in
"\t info = 0x%016" PRIx64 "\n",
handle, reinterpret_cast<uint64_t>(info));
return OK;
return Controller::PadCheckHandle(handle);
}
static int KYTY_SYSV_ABI PadUnknownN3kSX62fgNo(uint64_t arg0, uint64_t arg1, uint64_t arg2,
@@ -197,7 +187,7 @@ LIB_DEFINE(InitPad_1) {
LIB_FUNC("znaWI0gpuo8", Controller::PadGetTriggerEffectState);
LIB_FUNC("DscD1i9HX1w", Controller::PadResetLightBar);
LIB_FUNC("RR4novUEENY", Controller::PadSetLightBar);
LIB_FUNC("6ncge5+l5Qs", PadClose);
LIB_FUNC("6ncge5+l5Qs", Controller::PadClose);
LIB_FUNC("n3kSX62fgNo", PadUnknownN3kSX62fgNo);
}
+51
View File
@@ -110,6 +110,8 @@ struct Controller {
now = 1000;
haptics_handles_rumble = false;
Initialize();
Check(PadInit() == 0 && PadOpen(Config::GetUserId(), 0, 0, nullptr) == 1,
"the standard controller port did not open");
Connect(1);
Check(GetSettingScale(Setting::SpeakerVolume) ==
Config::GetControllerSpeakerVolume() / 50.0f &&
@@ -143,6 +145,54 @@ void SetRumble(uint8_t large, uint8_t small) {
Check(PadSetVibration(1, &param) == 0, "vibration request failed");
}
void TestPadPortOwnership() {
Initialize();
const int user = Config::GetUserId();
constexpr auto not_initialized = static_cast<int32_t>(0x80920005u);
constexpr auto already_opened = static_cast<int32_t>(0x80920004u);
constexpr auto invalid_handle = static_cast<int32_t>(0x80920003u);
constexpr auto no_handle = static_cast<int32_t>(0x80920008u);
PadData data {};
Check(PadOpen(user, 0, 0, nullptr) == not_initialized &&
PadGetHandle(user, 0, 0) == not_initialized &&
PadReadState(1, &data) == not_initialized,
"an uninitialized library exposed a controller port");
Check(PadInit() == 0 && PadGetHandle(user, 0, 0) == no_handle,
"initializing the library opened a controller port");
const int handle = PadOpen(user, 0, 0, nullptr);
Check(handle == 1 && PadInit() == 0 && PadGetHandle(user, 0, 0) == handle,
"reinitializing the library lost the open port");
for (int duplicate = 0; duplicate < 4; duplicate++) {
const int rejected = PadOpen(user, 0, 0, nullptr);
Check(rejected == already_opened,
"opening one pad five times exposed duplicate connected controllers");
Check(PadClose(rejected) == invalid_handle && PadGetHandle(user, 0, 0) == handle,
"closing a rejected duplicate released the original owner");
}
Check(PadGetHandle(user + 1, 0, 0) == no_handle &&
PadGetHandle(user, 2, 0) == no_handle && PadGetHandle(user, 0, 1) == no_handle,
"a different port tuple aliased the open standard pad");
SetButton(HOST_INPUT_CONTROLLER_ID, PAD_BUTTON_CROSS, true);
Check(PadReadState(handle, &data) == 0 && data.connected &&
data.buttons == PAD_BUTTON_CROSS,
"keyboard Cross did not reach the owned port");
SetButton(HOST_INPUT_CONTROLLER_ID, PAD_BUTTON_CROSS, false);
Connect(1);
SetButton(1, PAD_BUTTON_CROSS, true);
Check(PadReadState(handle, &data) == 0 && data.buttons == PAD_BUTTON_CROSS,
"the physical controller did not share the owned port");
Disconnect(1);
Check(PadGetHandle(user, 0, 0) == handle && PadOpen(user, 0, 0, nullptr) == already_opened,
"host disconnection released guest port ownership");
Check(PadClose(handle) == 0 && PadGetHandle(user, 0, 0) == no_handle &&
PadReadState(handle, &data) == invalid_handle &&
PadRead(handle, &data, 1) == invalid_handle && PadClose(handle) == invalid_handle,
"a closed port retained a usable handle");
Check(PadOpen(user, 0, 0, nullptr) == handle && PadReadState(handle, &data) == 0,
"a closed port could not reopen");
Shutdown();
}
void TestSettingCycles() {
Controller controller;
for (auto setting:
@@ -503,6 +553,7 @@ void TestTriggerEffectState() {
int main() {
Config::Initialize();
TestPadPortOwnership();
TestSettingCycles();
TestGlobalControllerLevels();
// Each following test initializes another controller and requires strong defaults.
+90
View File
@@ -62,6 +62,10 @@ namespace Libs::LibNpWebApi2 {
void InitNet_1_NpWebApi2(Loader::SymbolDatabase *symbols);
}
namespace Libs::LibNpCommerce {
void InitNet_1_NpCommerce(Loader::SymbolDatabase *symbols);
}
namespace {
namespace FileSystem = Libs::LibKernel::FileSystem;
@@ -385,6 +389,91 @@ void TestAioBatches() {
Check(FileSystem::KernelClose(fd) == OK, "close AIO read fixture");
}
void TestNpCommerceDialog() {
Loader::SymbolDatabase symbols;
Libs::LibNpCommerce::InitNet_1_NpCommerce(&symbols);
const auto find = [&](const char *nid) {
const auto *symbol = symbols.FindByNid(nid, Loader::SymbolType::Func);
Check(symbol != nullptr, "NpCommerce dialog exports resolve");
return symbol->vaddr;
};
using Call = int (KYTY_SYSV_ABI *)();
using Open = int (KYTY_SYSV_ABI *)(const void *);
using GetResult = int (KYTY_SYSV_ABI *)(void *);
const auto initialize = reinterpret_cast<Call>(find("0aR2aWmQal4"));
const auto terminate = reinterpret_cast<Call>(find("m-I92Ab50W8"));
const auto update = reinterpret_cast<Call>(find("LR5cwFMMCVE"));
const auto open = reinterpret_cast<Open>(find("DfSCDRA3EjY"));
const auto get_result = reinterpret_cast<GetResult>(find("r42bWcQbtZY"));
struct BaseParam {
uint64_t size;
uint8_t reserved[36];
uint32_t magic;
};
struct Param {
BaseParam base;
int32_t size, user_id, mode;
uint32_t service_label;
const char *const *targets;
uint32_t num_targets, padding;
uint64_t features;
void *user_data;
uint8_t reserved[32];
} param {};
struct Result {
int32_t result;
bool authorized;
void *user_data;
uint8_t reserved[32];
} result {};
static_assert(sizeof(Param) == 128 && offsetof(Param, user_data) == 88);
static_assert(sizeof(Result) == 48 && offsetof(Result, user_data) == 8);
param.base.size = sizeof(BaseParam);
param.base.magic = static_cast<uint32_t>(0xC0D1A109u + reinterpret_cast<uintptr_t>(&param.base));
param.size = sizeof(param);
param.user_id = 1;
param.user_data = &symbols;
Check(open(&param) == static_cast<int>(0x80B80003u) &&
get_result(&result) == static_cast<int>(0x80B80003u),
"NpCommerce dialog calls require initialization");
Check(initialize() == OK && update() == 1 &&
get_result(&result) == static_cast<int>(0x80B80005u),
"NpCommerce dialog result requires completion");
Check(open(nullptr) == static_cast<int>(0x80B8000Du) && update() == 1 &&
get_result(nullptr) == static_cast<int>(0x80B8000Du),
"NpCommerce dialog rejects null arguments");
Check(open(&param) == OK && update() == 3, "NpCommerce dialog finishes without a store");
param.user_data = nullptr;
result.authorized = true;
Check(get_result(&result) == 1 && result.result == 1 && !result.authorized &&
result.user_data == &symbols,
"NpCommerce cancellation returns the original user data");
param.mode = 5;
Check(open(&param) == OK && get_result(&result) == 1 && !result.authorized &&
result.user_data == nullptr,
"NpCommerce dialog can reopen with fresh result data");
const auto invalid_param = [&] {
Check(open(&param) == static_cast<int>(0x80B8000Au) && update() == 3 &&
get_result(&result) == static_cast<int>(0x80B8000Au) &&
result.result == static_cast<int>(0x80B8000Au) && !result.authorized,
"NpCommerce dialog reports invalid parameters in the completion result");
};
param.mode = 99;
invalid_param();
param.mode = -1;
invalid_param();
param.mode = 0;
param.size = 64;
invalid_param();
param.size = sizeof(param);
param.reserved[0] = 1;
invalid_param();
param.reserved[0] = 0;
Check(open(&param) == OK && get_result(&result) == 1 && terminate() == OK && update() == 0 &&
get_result(&result) == static_cast<int>(0x80B80003u),
"NpCommerce dialog recovers from invalid parameters and terminates");
}
void TestNpWebApi2Memory() {
Loader::SymbolDatabase symbols;
Libs::LibNpWebApi2::InitNet_1_NpWebApi2(&symbols);
@@ -1644,6 +1733,7 @@ int main(int, char**) {
CheckSocketWakeup();
CheckEtherAddressFormatting();
TestNpWebApi2Memory();
TestNpCommerceDialog();
graphics.reset();
subsystems.Destroy();
+143
View File
@@ -1353,6 +1353,7 @@ struct GraphicsCase {
std::vector<u32> push_constants;
std::vector<u32> pixel_interpolator_settings;
bool pixel_no_perspective = false;
bool provoking_vtx_last = false;
std::vector<u32> vertices;
bool pixel_ancillary = false;
bool pixel_front_face = false;
@@ -1449,6 +1450,20 @@ void CheckPixelParameterAliases() {
pixel.interpolator_settings[1] = 0u;
pixel.interpolator_settings[2] = 1u;
const std::array<uint32_t, 3> active = {0, 1, 2};
Require(name, "ordinary mixed aliases",
ShaderPixelParameterLocation(pixel, active, 0) == 0u &&
ShaderPixelParameterLocation(pixel, active, 1) == 0u &&
ShaderPixelParameterLocation(pixel, active, 2) == 1u,
"flat/smooth aliases must retain the real vertex export location");
std::vector<uint32_t> ordinary_key, rectangle_key, provoking_key;
BuildStageStaticKey(pixel, ordinary_key);
pixel.parameter_mode = ShaderPixelParameterMode::LastVertex;
BuildStageStaticKey(pixel, provoking_key);
pixel.parameter_mode = ShaderPixelParameterMode::Rectangle;
BuildStageStaticKey(pixel, rectangle_key);
Require(name, "interpolation specialization",
ordinary_key != rectangle_key && ordinary_key != provoking_key,
"rectangle and provoking-vertex modes must participate in the shader key");
Require(name, "reserved physical locations",
ShaderPixelParameterLocation(pixel, active, 0) == 0u &&
ShaderPixelParameterLocation(pixel, active, 1) == 2u &&
@@ -1473,6 +1488,7 @@ void CheckRectListShaders() {
vertex.stage.program = &vertex_program;
vertex.stage.resources = &empty_snapshot;
ShaderPixelInputInfo pixel{};
pixel.parameter_mode = ShaderPixelParameterMode::Rectangle;
pixel.input_num = 2;
pixel.interpolator_settings[0] = 0x400u;
pixel.interpolator_settings[1] = 0;
@@ -1774,6 +1790,8 @@ CompiledShader CompileFragmentCase(const GraphicsCase &test) {
? 1u
: static_cast<u32>(test.pixel_interpolator_settings.size());
pixel_info.ps_no_perspective = test.pixel_no_perspective;
pixel_info.parameter_mode = test.provoking_vtx_last ? ShaderPixelParameterMode::LastVertex
: ShaderPixelParameterMode::FirstVertex;
pixel_info.ps_ancillary = test.pixel_ancillary;
pixel_info.ps_front_face = test.pixel_front_face;
pixel_info.ps_pos_w = test.pixel_position_w;
@@ -16928,6 +16946,12 @@ public:
raster.frontFace = vk::FrontFace::eCounterClockwise;
raster.lineWidth = 1.0f;
vk::PipelineRasterizationProvokingVertexStateCreateInfoEXT provoking_vertex{};
if (test.provoking_vtx_last) {
provoking_vertex.provokingVertexMode = vk::ProvokingVertexModeEXT::eLastVertex;
raster.pNext = &provoking_vertex;
}
vk::PipelineMultisampleStateCreateInfo multisample{};
multisample.sType = vk::StructureType::ePipelineMultisampleStateCreateInfo;
multisample.rasterizationSamples = test.samples;
@@ -22118,6 +22142,75 @@ TestCase Vop1SdwaNotPartialSourcesAndDestinations() {
return test;
}
TestCase Vop2SdwaBitwiseByteDestinations(u32 wave_size) {
using O = ShaderOpcode;
// Low result bytes of AND, OR, XOR, XNOR for the two runtime inputs.
constexpr std::array<u32, 4> result_bytes{0xa4u, 0xbeu, 0x1au, 0xe5u};
constexpr std::array<u32, 4> alias_expected{
0xc350e5a6u, 0xc3fce5a6u, 0xc3ace5a6u, 0xc353e5a6u};
constexpr u32 sentinel = 0xa1b2c3d4u;
TestCase test;
test.name = wave_size == 64 ? "Vop2SdwaBitwiseByteDestinationsWave64"
: "Vop2SdwaBitwiseByteDestinationsWave32";
test.initial = {0xc3d4e5a6u, 0x9b785abcu};
test.expected = test.initial;
auto &code = test.code;
const auto store = [&](u32 reg, u32 expected) {
AppendStoreVgpr(&code, reg, static_cast<u32>(test.expected.size()));
test.expected.push_back(expected);
};
for (u32 op = 0; op < 4; ++op) {
AppendVMovU32(&code, 30, 0);
AppendBufferLoadDword(&code, 12, 30);
AppendVMovU32(&code, 30, 4);
AppendBufferLoadDword(&code, 13, 30);
for (u32 selector = 0; selector < 4; ++selector) {
for (u32 unused = 0; unused < 3; ++unused) {
AppendVMovLiteral(&code, 26, sentinel);
code.push_back(EncodeVop2(0x1b + op, 26, 249, 13));
code.push_back(EncodeVop2Sdwa(12, selector, unused));
// RDNA2 table 88: pad, sign extend above/zero below, or preserve.
const u32 shift = selector * 8u;
u32 expected = result_bytes[op] << shift;
if (unused == 1 && (result_bytes[op] & 0x80u) != 0) {
expected = (0xffffff00u | result_bytes[op]) << shift;
} else if (unused == 2) {
expected |= sentinel & ~(0xffu << shift);
}
store(26, expected);
}
}
// Read source byte2 and word1 before overwriting source0's byte2.
code.push_back(EncodeVop2(0x1b + op, 12, 249, 13));
code.push_back(EncodeVop2Sdwa(12, 2, 2, 2, 5));
store(12, alias_expected[op]);
// Source1 may also alias the destination.
AppendVMovU32(&code, 30, 0);
AppendBufferLoadDword(&code, 12, 30);
code.push_back(EncodeVop2(0x1b + op, 13, 249, 13));
code.push_back(EncodeVop2Sdwa(12, 1, 2));
store(13, 0x9b7800bcu | (result_bytes[op] << 8u));
for (u32 unused = 0; unused < 3; ++unused) {
AppendVMovLiteral(&code, 26, sentinel);
code.push_back(EncodeSMovB32(126, InlineU32(0)));
code.push_back(EncodeVop2(0x1b + op, 26, 249, 13));
code.push_back(EncodeVop2Sdwa(12, 2, unused));
code.push_back(EncodeSMovB32(126, InlineU32(1)));
store(26, sentinel);
}
}
AppendEnd(&code);
test.initial.resize(test.expected.size());
test.opcodes = {O::V_MOV_B32, O::S_MOV_B32, O::BUFFER_LOAD_DWORD,
O::V_AND_B32, O::V_OR_B32, O::V_XOR_B32, O::V_XNOR_B32,
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
test.required_spirv = {"OpBitwiseAnd", "OpBitwiseOr", "OpBitwiseXor", "OpNot",
"OpBitFieldInsert", "OpBitFieldSExtract"};
test.compute_info.wave_size = wave_size;
test.has_compute_info = true;
return test;
}
TestCase Vop1SdwaMovByteDestinations() {
using O = ShaderOpcode;
@@ -35871,6 +35964,38 @@ GraphicsCase GraphicsSmoothRawInputAlias() {
return test;
}
GraphicsCase GraphicsSmoothFlatInputAlias(bool flat_first, bool provoking_last = false) {
auto test = GraphicsSmoothRawInputAlias();
test.name = provoking_last
? (flat_first ? "GraphicsFlatSmoothAliasLast" : "GraphicsSmoothFlatAliasLast")
: (flat_first ? "GraphicsFlatSmoothInputAlias" : "GraphicsSmoothFlatInputAlias");
test.pixel_custom_interpolation_mask = 0;
test.provoking_vtx_last = provoking_last;
test.pixel_interpolator_settings = flat_first ? std::vector<u32>{0x400u, 0u}
: std::vector<u32>{0u, 0x400u};
const u32 smooth = flat_first ? 1u : 0u;
const u32 flat = smooth ^ 1u;
// One export carries smooth values (2,4,8) and packed light IDs (5,7,11).
// Flat P0 preserves the ID bits; flat P1/P2 uses the same provoking vertex.
test.vertices[4] = 0x3f800005u;
test.vertices[10] = 0x3f800007u;
test.vertices[16] = 0x3f80000bu;
test.fragment_code = {EncodeVintrp(0x00, 0, smooth, 1, 0),
EncodeVintrp(0x01, 0, smooth, 1, 1),
EncodeVintrp(0x02, 1, flat, 2, 2),
EncodeVop2(0x1b, 1, InlineU32(63), 1),
EncodeVop1(0x06, 1, Vgpr(1)),
EncodeVintrp(0x00, 2, flat, 1, 0),
EncodeVintrp(0x01, 2, flat, 1, 1),
EncodeExp0(0x00, 0xf), EncodeExp1(0, 1, 2, 0)};
AppendEnd(&test.fragment_code);
test.expected_pixel = {0x40700000u, provoking_last ? 0x41300000u : 0x40a00000u,
provoking_last ? 0x41000000u : 0x40000000u, 0x40700000u};
test.opcodes.insert(test.opcodes.end(),
{ShaderOpcode::V_AND_B32, ShaderOpcode::V_CVT_F32_U32});
return test;
}
GraphicsCase GraphicsAncillaryLayer(bool front_face) {
GraphicsCase test;
test.name = front_face ? "GraphicsAncillaryAfterFrontFace" : "GraphicsAncillaryLayer";
@@ -36278,6 +36403,8 @@ std::vector<TestCase> MakeCases() {
AddCase(Vop1SdwaNotCapturedByte0Source);
AddCase(Vop1SdwaNotPreservesHighWordDestination);
AddCase(Vop1SdwaNotPartialSourcesAndDestinations);
cases.push_back(Vop2SdwaBitwiseByteDestinations(32));
cases.push_back(Vop2SdwaBitwiseByteDestinations(64));
AddCase(Vop1SdwaMovByteDestinations);
cases.push_back(Vop1SdwaMovByteSources(32));
cases.push_back(Vop1SdwaMovByteSources(64));
@@ -36714,6 +36841,10 @@ std::vector<GraphicsCase> MakeGraphicsCases() {
GraphicsPackedHalfInputAlias(false),
GraphicsPackedHalfInputAlias(true),
GraphicsSmoothRawInputAlias(),
GraphicsSmoothFlatInputAlias(false),
GraphicsSmoothFlatInputAlias(true),
GraphicsSmoothFlatInputAlias(false, true),
GraphicsSmoothFlatInputAlias(true, true),
GraphicsAncillaryLayer(false),
GraphicsAncillaryLayer(true),
GraphicsAncillarySampleId(),
@@ -42196,6 +42327,11 @@ int main(int argc, char **argv) {
RunGraphicsCase(&vulkan, GraphicsPackedHalfInputAlias(false));
RunGraphicsCase(&vulkan, GraphicsPackedHalfInputAlias(true));
RunGraphicsCase(&vulkan, GraphicsSmoothRawInputAlias());
RunGraphicsCase(&vulkan, GraphicsSmoothFlatInputAlias(false));
RunGraphicsCase(&vulkan, GraphicsSmoothFlatInputAlias(true));
RunGraphicsCase(&vulkan, GraphicsSmoothFlatInputAlias(false, true));
RunGraphicsCase(&vulkan, GraphicsSmoothFlatInputAlias(true, true));
RunGraphicsCase(&vulkan, GraphicsFlatInterpolatorExport());
return 0;
}
if (argc == 2 && std::strcmp(argv[1], "--clip-control-only") == 0) {
@@ -42350,6 +42486,13 @@ int main(int argc, char **argv) {
RunCase(&vulkan, ScalarZeroShiftWithRuntimeCount());
return 0;
}
if (argc == 2 && std::strcmp(argv[1], "--sdwa-bitwise-only") == 0) {
VulkanHarness vulkan;
RunCase(&vulkan, Vop2SdwaBitwiseByteDestinations(32));
RunCase(&vulkan, Vop2SdwaBitwiseByteDestinations(64));
RunCase(&vulkan, Vop1SdwaNotPartialSourcesAndDestinations());
return 0;
}
if (argc == 2 && std::strcmp(argv[1], "--sdwa-mov-only") == 0) {
VulkanHarness vulkan;
RunCase(&vulkan, Vop1SdwaMovByteDestinations());