Online fix

This commit is contained in:
iChris4 committed 2026-09-10 03:30:03 +02:00
1 parent 8a510695dc
commit 0353b76baa
2 files changed
+147 -77

No files matched your search

+12 -3
View File
@@ -130,11 +130,20 @@ inline std::array<uint8_t, 20> Sha1(const uint8_t* data, size_t size) {
using CryptoEcdsa = CryptoPP::ECDSA<CryptoPP::EC2N, CryptoPP::SHA1>;
// ng_priv is a raw 233-bit field and the sect233r1 subgroup order is itself 233
// bits wide, so a genuine keys.bin dump can hold a scalar numerically above the
// order. IOS does not reject those, and Dolphin reduces the key with a single
// conditional subtraction before using it (Common::ec::Sign, the bn_sub_modulus
// on its kk copy), so rejecting one here refuses to build the ES device
// certificate and takes the whole online login down with it. Reduce instead.
// The certificate is unaffected: G has order n, so k*G == (k mod n)*G and the
// published public key is the same point Dolphin derives from the raw scalar.
inline CryptoEcdsa::PrivateKey MakePrivateKey(const uint8_t* key) {
CryptoPP::DL_GroupParameters_EC<CryptoPP::EC2N> parameters(CryptoPP::ASN1::sect233r1());
const CryptoPP::Integer exponent(key, 30);
if (exponent <= CryptoPP::Integer::Zero() || exponent >= parameters.GetSubgroupOrder()) {
throw std::invalid_argument("Wii ES private key is outside the sect233r1 subgroup");
CryptoPP::Integer exponent(key, 30);
exponent %= parameters.GetSubgroupOrder();
if (exponent <= CryptoPP::Integer::Zero()) {
throw std::invalid_argument("Wii ES private key is zero modulo the sect233r1 subgroup order");
}
CryptoEcdsa::PrivateKey privateKey;
+135 -74
View File
@@ -4,6 +4,7 @@
#include "hle_stubs.h"
#include "host_context.h"
#include "runtime_log.h"
#include "system_bridge.h"
// Defined in hle/os/os_sleep.cpp; the sleep-timer table is file-local there.
@@ -13,6 +14,7 @@
#include <iostream>
#include <iomanip>
#include <sstream>
#include <string_view>
namespace Fiber {
@@ -525,6 +527,44 @@ void GuestFiberManager::ProcessTimerEvents(CpuContext* cpu) {
}
}
namespace {
// Windows cannot unwind an exception out of a fiber entry point. Past
// FiberProc there is only RtlUserFiberStart, which terminates the frame chain
// without a handler, so RtlUnwindEx fails and raises STATUS_BAD_FUNCTION_TABLE
// (0xC00000FF) - a noncontinuable exception that kills the process and reports
// the fiber's entry symbol instead of the fault that actually happened. main()
// already guards the primary guest thread with the same catch set; a guest
// OSThread runs on its own fiber and needs the boundary here rather than there.
[[noreturn]] void ReportFatalGuestThreadException(uint32_t guestThreadAddr,
uint32_t entryPoint,
const CpuContext* cpu,
std::string_view category,
std::string_view details) noexcept
{
std::ostringstream message;
message << "guest thread 0x" << std::hex << std::uppercase << guestThreadAddr
<< " (entry 0x" << entryPoint << ")" << std::dec << std::nouppercase
<< " stopped with an unhandled " << category << ".\n" << details;
const std::string text = message.str();
RT_LOG(RT_TAG_OS) << "unhandled " << category << " on guest thread 0x" << std::hex
<< std::uppercase << guestThreadAddr << " (entry 0x" << entryPoint << ")"
<< std::dec << std::nouppercase << ": " << details << std::endl;
SystemBridge::DumpCpuState(cpu);
std::cerr.flush();
// Same artifact set as main()'s handler: MarkFatalErrorReported stops the
// atexit reporter, so the crash log has to be written here.
RuntimeCrash::WriteCrashArtifacts("guest_thread_exception", text);
SetRuntimeExitCode(EXIT_FAILURE);
ShowRuntimeFatalPopup(category, text);
MarkFatalErrorReported();
std::exit(EXIT_FAILURE);
}
} // namespace
#if defined(_WIN32)
void CALLBACK GuestFiberManager::FiberProc(void* param)
#else
@@ -583,84 +623,105 @@ void GuestFiberManager::FiberProc(void* param)
// Create a CpuContextScope for this fiber
CpuContextScope scope(cpu);
int startDeferAttempts = 0;
while (entryPoint == 0x8024373c) { // EGG::Thread::start
uint32_t vtable = 0;
uint32_t startFn = 0;
try {
vtable = Memory::Read32(entryArg);
if (vtable >= 0x80000000u) {
startFn = Memory::Read32(vtable + 0x0Cu);
}
} catch (const Memory::AccessViolation&) {
vtable = 0;
startFn = 0;
}
if (vtable >= 0x80000000u && startFn >= 0x80000000u) {
break;
}
if (startDeferAttempts++ > 50) {
RT_LOG(RT_TAG_OS) << "EGG::Thread::start target still invalid (vtable=0x" << std::hex << vtable
<< ", fn=0x" << startFn << ") after retries; continuing anyway." << std::dec << std::endl;
break;
}
HostContext::Switch(s_schedulerFiber);
}
// The deferral loop above yields to the scheduler and therefore can resume
// with registers from a different guest fiber in the shared CpuContext.
cpu->gpr[3] = entryArg;
cpu->pc = entryPoint;
cpu->srr0 = entryPoint;
// Call the translated thread entry function
const auto* info = TranslatedFunctionRegistry::FindByAddressPtr(entryPoint);
if (info) {
InvokeIndirectCpu(entryPoint, cpu);
} else {
RT_LOG(RT_TAG_OS) << "Thread entry 0x" << std::hex << entryPoint
<< " not found in registry!" << std::dec << std::endl;
}
// Thread entry functions normally return into OSExitThread on hardware.
// Our host fiber call boundary observes the return directly, so complete the
// guest OSThread lifecycle here before handing control back to the scheduler.
// A fault anywhere in this guest thread - translated code, an HLE hook or a
// guest callback - must be reported here. See
// ReportFatalGuestThreadException: unwinding past a fiber entry is not
// representable on Windows and destroys the diagnostic.
try {
RemoveGuestThreadFromQueue(guestThreadAddr);
const uint16_t attributes = Memory::Read16(guestThreadAddr + kThreadAttrOffset);
const bool detached = (attributes & 1u) != 0;
const uint16_t finalState = detached ? 0u : static_cast<uint16_t>(ThreadState::MORIBUND);
if (!detached) {
Memory::Write32(guestThreadAddr + kThreadExitValueOffset, 0);
int startDeferAttempts = 0;
while (entryPoint == 0x8024373c) { // EGG::Thread::start
uint32_t vtable = 0;
uint32_t startFn = 0;
try {
vtable = Memory::Read32(entryArg);
if (vtable >= 0x80000000u) {
startFn = Memory::Read32(vtable + 0x0Cu);
}
} catch (const Memory::AccessViolation&) {
vtable = 0;
startFn = 0;
}
if (vtable >= 0x80000000u && startFn >= 0x80000000u) {
break;
}
if (startDeferAttempts++ > 50) {
RT_LOG(RT_TAG_OS) << "EGG::Thread::start target still invalid (vtable=0x" << std::hex << vtable
<< ", fn=0x" << startFn << ") after retries; continuing anyway." << std::dec << std::endl;
break;
}
HostContext::Switch(s_schedulerFiber);
}
Memory::Write16(guestThreadAddr + kThreadStateOffset, finalState);
WakeGuestThreadsOnQueueNoSwitch(guestThreadAddr + kThreadJoinQueueOffset);
if (Memory::Read32(kOSRunningContextAddr) == guestThreadAddr) {
Memory::Write32(kOSRunningContextAddr, 0);
}
if (Memory::Read32(kOSCurrentContextAddr) == guestThreadAddr) {
Memory::Write32(kOSCurrentContextAddr, 0);
}
Memory::Write32(kSchedulerReschedCounterAddr, 1);
} catch (const Memory::AccessViolation& e) {
RT_LOG(RT_TAG_OS) << "Thread return cleanup failed for 0x" << std::hex
<< guestThreadAddr << " at 0x" << e.address() << std::dec
<< " (" << e.reason() << ")" << std::endl;
}
// The deferral loop above yields to the scheduler and therefore can resume
// with registers from a different guest fiber in the shared CpuContext.
cpu->gpr[3] = entryArg;
cpu->pc = entryPoint;
cpu->srr0 = entryPoint;
{
std::lock_guard<std::mutex> lock(s_mutex);
auto it = s_fibers.find(guestThreadAddr);
if (it != s_fibers.end()) {
it->second.terminated = true;
it->second.state = ThreadState::MORIBUND;
// Call the translated thread entry function
const auto* info = TranslatedFunctionRegistry::FindByAddressPtr(entryPoint);
if (info) {
InvokeIndirectCpu(entryPoint, cpu);
} else {
RT_LOG(RT_TAG_OS) << "Thread entry 0x" << std::hex << entryPoint
<< " not found in registry!" << std::dec << std::endl;
}
s_currentGuestThread = 0;
// Thread entry functions normally return into OSExitThread on hardware.
// Our host fiber call boundary observes the return directly, so complete the
// guest OSThread lifecycle here before handing control back to the scheduler.
try {
RemoveGuestThreadFromQueue(guestThreadAddr);
const uint16_t attributes = Memory::Read16(guestThreadAddr + kThreadAttrOffset);
const bool detached = (attributes & 1u) != 0;
const uint16_t finalState = detached ? 0u : static_cast<uint16_t>(ThreadState::MORIBUND);
if (!detached) {
Memory::Write32(guestThreadAddr + kThreadExitValueOffset, 0);
}
Memory::Write16(guestThreadAddr + kThreadStateOffset, finalState);
WakeGuestThreadsOnQueueNoSwitch(guestThreadAddr + kThreadJoinQueueOffset);
if (Memory::Read32(kOSRunningContextAddr) == guestThreadAddr) {
Memory::Write32(kOSRunningContextAddr, 0);
}
if (Memory::Read32(kOSCurrentContextAddr) == guestThreadAddr) {
Memory::Write32(kOSCurrentContextAddr, 0);
}
Memory::Write32(kSchedulerReschedCounterAddr, 1);
} catch (const Memory::AccessViolation& e) {
RT_LOG(RT_TAG_OS) << "Thread return cleanup failed for 0x" << std::hex
<< guestThreadAddr << " at 0x" << e.address() << std::dec
<< " (" << e.reason() << ")" << std::endl;
}
{
std::lock_guard<std::mutex> lock(s_mutex);
auto it = s_fibers.find(guestThreadAddr);
if (it != s_fibers.end()) {
it->second.terminated = true;
it->second.state = ThreadState::MORIBUND;
}
s_currentGuestThread = 0;
}
} catch (const Memory::AccessViolation& ex) {
std::ostringstream details;
details << "addr=0x" << std::hex << std::uppercase << ex.address()
<< " len=0x" << ex.length() << std::dec << std::nouppercase
<< " reason=" << ex.reason();
ReportFatalGuestThreadException(guestThreadAddr, entryPoint, cpu,
"a guest memory access was out of bounds",
details.str());
} catch (const std::exception& ex) {
ReportFatalGuestThreadException(guestThreadAddr, entryPoint, cpu,
"a runtime exception occurred", ex.what());
} catch (...) {
ReportFatalGuestThreadException(guestThreadAddr, entryPoint, cpu,
"a runtime exception occurred",
"the exception carried no details");
}
// Return to scheduler