Compare commits

...
Author SHA1 Message Date
nmzik aa3b2cd57f Implement AGC async rewind imports 2026-10-08 17:43:02 +04:00
4865dd92ca libNet: answer sceNpGetAccountLanguage2 as a signed-out user (#1186)
* libNet: answer sceNpGetAccountLanguage2 as a signed-out user

sceNpGetAccountLanguage2 was not registered, so games that call it hit
the unresolved-import stub. The stub returns 0, which tells the game the
request succeeded and that the language code buffer now holds the
account's language, but nothing was written to it.

Saros (#1053) calls it from its analytics thread right after
sceNpGetAccountAge, each on its own request. sceNpGetAccountAge already
reports SCE_NP_ERROR_SIGNED_OUT there, so the game is told the user is
signed out for one query and signed in for the next. Demon's Souls
(#890), Ghost of Yotei (#1095) and NHL 27 (#1101) import it as well.

Register it next to the other NpManager account queries and answer the
way they do: SCE_NP_ERROR_INVALID_ARGUMENT for a request id <= 0 or a
null output pointer, SCE_NP_ERROR_SIGNED_OUT otherwise. The output is
left untouched because nothing was looked up.

* libNet: reject missing account language requests

---------

Co-authored-by: overlordxrz-source <overlordxrz-source@users.noreply.github.com>
Co-authored-by: nmzik <Nmzik@mail.ru>
2026-10-08 16:48:01 +04:00
7 changed files with 127 additions and 27 deletions

No files matched your search

+8
View File
@@ -3217,6 +3217,14 @@ uint32_t KYTY_SYSV_ABI AgcAcbJumpGetSize() {
return 0x10u;
}
uint32_t* KYTY_SYSV_ABI AgcAcbRewind(CommandBuffer* buf, uint8_t initial_state, uint8_t offload) {
auto* cmd = AgcDcbRewind(buf, initial_state);
if (cmd != nullptr) {
cmd[1] |= (static_cast<uint32_t>(offload & 0x1u) << 24u);
}
return cmd;
}
uint32_t* KYTY_SYSV_ABI AgcAcbWaitRegMem(CommandBuffer* buf, uint8_t size, uint8_t compare_function,
uint8_t cache_policy, const volatile void* address,
uint64_t reference, uint64_t mask, uint32_t poll_cycles) {
+1
View File
@@ -206,6 +206,7 @@ uint32_t KYTY_SYSV_ABI AgcAcbCondExecGetSize();
uint32_t* KYTY_SYSV_ABI AgcAcbJump(CommandBuffer* buf, uint8_t cache_policy,
const uint32_t* target, uint32_t size_in_dwords);
uint32_t KYTY_SYSV_ABI AgcAcbJumpGetSize();
uint32_t* KYTY_SYSV_ABI AgcAcbRewind(CommandBuffer* buf, uint8_t initial_state, uint8_t offload);
uint32_t* KYTY_SYSV_ABI AgcAcbWaitRegMem(CommandBuffer* buf, uint8_t size, uint8_t compare_function,
uint8_t cache_policy, const volatile void* address,
uint64_t reference, uint64_t mask, uint32_t poll_cycles);
+2
View File
@@ -74,6 +74,7 @@ LIB_DEFINE(InitAgcDriver_1) {
LIB_FUNC("ozKzBP4aki4", Gen5::AgcAcbCondExecGetSize);
LIB_FUNC("e1DFTg+Sd8U", Gen5::AgcAcbJump);
LIB_FUNC("b-oySn+G2tE", Gen5::AgcAcbJumpGetSize);
LIB_FUNC("DwICrVxerkY", Gen5::AgcAcbRewind);
LIB_FUNC("htn36gPnBk4", Gen5::AgcAcbWaitRegMem);
LIB_FUNC("idlaArvdXEs", Gen5::AgcAcbWaitOnAddressGetSize);
LIB_FUNC("-RnpfpxIhec", Gen5::AgcAcbDmaData);
@@ -156,6 +157,7 @@ LIB_DEFINE(InitAgcDriver_1) {
LIB_FUNC("k-JpyR2dYAM", Gen5::AgcCondExecPatchSetEnd);
LIB_FUNC("3ZWa3AoyWZQ", Gen5::AgcCondExecPatchSetCommandAddress);
LIB_FUNC("ziVA3whp3p4", Gen5::AgcRewindPatchSetRewindState);
LIB_FUNC("eWaWyFegzgQ", Gen5::AgcRewindPatchSetRewindState);
LIB_FUNC("YUeqkyT7mEQ", Gen5::AgcDcbSetFlip);
}
+1
View File
@@ -1559,6 +1559,7 @@ LIB_DEFINE(InitNet_1_NpManager) {
LIB_FUNC("rbknaUjpqWo", NpManager::NpGetAccountIdA);
LIB_FUNC("JT+t00a3TxA", NpManager::NpGetAccountCountryA);
LIB_FUNC("+4DegjBqV1g", NpManager::NpGetAccountAge);
LIB_FUNC("3Tcz5bNCfZQ", NpManager::NpGetAccountLanguage2);
LIB_FUNC("GpLQDNKICac", NpManager::NpCreateRequest);
LIB_FUNC("eiqMCt9UshI", NpManager::NpCreateAsyncRequest);
LIB_FUNC("S7QTn72PrDw", NpManager::NpDeleteRequest);
+20
View File
@@ -4106,6 +4106,26 @@ int KYTY_SYSV_ABI NpGetAccountAge(int req_id, int user_id, uint8_t* age) {
return np_error_signed_out;
}
int KYTY_SYSV_ABI NpGetAccountLanguage2(int req_id, int user_id, void* language_code) {
PRINT_NAME();
LOGF("\t req_id = %d\n", req_id);
LOGF("\t user_id = %d\n", user_id);
LOGF("\t language_code = 0x%016" PRIx64 "\n", reinterpret_cast<uint64_t>(language_code));
if (req_id <= 0 || language_code == nullptr) {
return np_error_invalid_argument;
}
std::lock_guard lock(g_np_request_mutex);
if (np_get_request_locked(req_id) == nullptr) {
return np_error_request_not_found;
}
return np_error_signed_out;
}
int KYTY_SYSV_ABI NpCreateRequest() {
PRINT_NAME();
+1
View File
@@ -187,6 +187,7 @@ int KYTY_SYSV_ABI NpGetOnlineId(int user_id, NpOnlineId* online_id);
int KYTY_SYSV_ABI NpGetAccountIdA(int user_id, uint64_t* account_id);
int KYTY_SYSV_ABI NpGetAccountCountryA(int user_id, void* country_code);
int KYTY_SYSV_ABI NpGetAccountAge(int req_id, int user_id, uint8_t* age);
int KYTY_SYSV_ABI NpGetAccountLanguage2(int req_id, int user_id, void* language_code);
int KYTY_SYSV_ABI NpCreateRequest();
int KYTY_SYSV_ABI NpCreateAsyncRequest(const NpCreateAsyncRequestParameter* param);
int KYTY_SYSV_ABI NpDeleteRequest(int req_id);
+94 -27
View File
@@ -50,6 +50,7 @@
#include "libs/agc.h"
#include "libs/dialog.h"
#include "libs/errno.h"
#include "loader/symbolDatabase.h"
#include "spirv-tools/libspirv.hpp"
#if __has_include("graphics/host_gpu/renderer/renderTargetBarriers.h")
@@ -104,6 +105,10 @@
#undef max
#endif
namespace Libs {
void InitAgcDriver_1(Loader::SymbolDatabase *symbols);
}
namespace Libs::Graphics {
template <typename Cache>
@@ -41645,35 +41650,97 @@ void CheckPm4WaitResume(RenderContext &renderer) {
}
void CheckPm4RewindResume(RenderContext &renderer) {
constexpr const char *name = "Pm4RewindResume";
Loader::SymbolDatabase symbols;
Libs::InitAgcDriver_1(&symbols);
const auto *emit_symbol = symbols.FindByNid("DwICrVxerkY", Loader::SymbolType::Func);
const auto *patch_symbol = symbols.FindByNid("eWaWyFegzgQ", Loader::SymbolType::Func);
Require(name, "async exports",
emit_symbol != nullptr && patch_symbol != nullptr &&
emit_symbol->vaddr == reinterpret_cast<uint64_t>(&Gen5::AgcAcbRewind) &&
patch_symbol->vaddr ==
reinterpret_cast<uint64_t>(&Gen5::AgcRewindPatchSetRewindState),
"async rewind imports did not resolve to the emitter and state patch");
const auto async_patch = reinterpret_cast<decltype(&Gen5::AgcRewindPatchSetRewindState)>(
patch_symbol->vaddr);
constexpr uint32_t sentinel = 0x12345678u;
std::array<uint32_t, 4> packet{sentinel, sentinel, sentinel, sentinel};
CommandBufferLayout acb{packet.data(), packet.data() + packet.size(),
packet.data() + 1, packet.data() + 3, nullptr, nullptr, 0};
auto *buffer = reinterpret_cast<Gen5::CommandBuffer *>(&acb);
for (const uint8_t state : {0, 1}) {
for (const uint8_t offload : {0, 1}) {
acb.cursor_up = packet.data() + 1;
Require(name, "async packet",
Gen5::AgcAcbRewind(buffer, state, offload) == packet.data() + 1 &&
acb.cursor_up == packet.data() + 3 &&
packet[0] == sentinel && packet[3] == sentinel &&
packet[1] == 0xc0005900u &&
packet[2] == ((uint32_t{state} << 31u) | (uint32_t{offload} << 24u)),
"async rewind packet or two-DWORD allocation differs from the native command");
}
}
const auto before = packet;
acb.cursor_up = packet.data() + 1;
acb.reserved_dw = 1;
Require(name, "bounded allocation",
Gen5::AgcAcbRewind(buffer, 1, 1) == nullptr &&
Gen5::AgcAcbRewind(nullptr, 1, 1) == nullptr &&
acb.cursor_up == packet.data() + 1 && packet == before,
"failed rewind allocation changed the cursor or command storage");
// Native patchers inspect only the opcode, preserving the header and low 31 bits.
std::array<uint32_t, 2> command{0xa5ff591fu, 0x0155aa55u};
Require(name, "patch preserves fields",
async_patch(command.data(), 1) == 0 && command[0] == 0xa5ff591fu &&
command[1] == 0x8155aa55u && async_patch(command.data(), 0) == 0 &&
command[0] == 0xa5ff591fu && command[1] == 0x0155aa55u,
"rewind patch changed the offload bit, header, or unrelated payload bits");
command[0] = 0xc0001000u;
const auto invalid = command;
Require(name, "wrong opcode",
async_patch(command.data(), 1) == static_cast<int>(0x8a6c000cu) &&
command == invalid,
"rewind patch accepted or modified a packet with the wrong opcode");
GraphicsInitJmpTables();
CommandProcessor processor(renderer, 0);
uint32_t suffix = 0;
const auto address = reinterpret_cast<uint64_t>(&suffix);
std::array<uint32_t, 7> commands{};
commands[0] = KYTY_PM4(2, Pm4::IT_REWIND, 0);
commands[1] = 0;
commands[2] = KYTY_PM4(5, Pm4::IT_WRITE_DATA, 0);
commands[3] = 0;
commands[4] = static_cast<uint32_t>(address);
commands[5] = static_cast<uint32_t>(address >> 32u);
commands[6] = 33;
Pm4Execution execution;
Require("Pm4RewindResume", "pending",
processor.Process(execution, commands) == Pm4ProcessResult::Blocked &&
suffix == 0,
"pending rewind did not preserve its command position");
Require("Pm4RewindResume", "patch valid",
Gen5::AgcRewindPatchSetRewindState(commands.data(), 1) == 0 &&
commands[1] == 0x80000000u,
"rewind state patch did not set the valid bit");
Require("Pm4RewindResume", "resume",
processor.Process(execution, commands) ==
Pm4ProcessResult::Complete &&
suffix == 33,
"valid rewind did not resume at the following packet");
std::printf("[host] %-32s ok\n", "Pm4RewindResume");
for (const bool async : {false, true}) {
for (const uint8_t offload : {0, 1}) {
if (!async && offload != 0) {
continue;
}
uint32_t suffix = 0;
const auto address = reinterpret_cast<uint64_t>(&suffix);
std::array<uint32_t, 7> commands{
0, 0, KYTY_PM4(5, Pm4::IT_WRITE_DATA, 0), 0,
static_cast<uint32_t>(address), static_cast<uint32_t>(address >> 32u), 33};
CommandBufferLayout cb{commands.data(), commands.data() + 2,
commands.data(), commands.data() + 2, nullptr, nullptr, 0};
auto *command_buffer = reinterpret_cast<Gen5::CommandBuffer *>(&cb);
const auto *emitted = async ? Gen5::AgcAcbRewind(command_buffer, 0, offload)
: Gen5::AgcDcbRewind(command_buffer, 0);
const auto patch = async ? async_patch : &Gen5::AgcRewindPatchSetRewindState;
Pm4Execution execution;
Require(name, "pending",
emitted == commands.data() &&
processor.Process(execution, commands) == Pm4ProcessResult::Blocked &&
suffix == 0,
"pending rewind did not preserve its command position");
Require(name, "still pending",
processor.Process(execution, commands) == Pm4ProcessResult::Blocked &&
!execution.MadeProgress() && suffix == 0,
"pending rewind advanced before its state was patched");
Require(name, "patch and resume",
patch(commands.data(), 1) == 0 &&
commands[1] == (0x80000000u | (uint32_t{offload} << 24u)) &&
processor.Process(execution, commands) == Pm4ProcessResult::Complete &&
suffix == 33,
"patched rewind lost the offload bit or failed to resume");
}
}
std::printf("[host] %-32s ok\n", name);
}
void CheckPm4CeCompletion(RenderContext &renderer) {