IOS LLE: checkpoint NAND conformance and diagnostic tooling
Preserve formatted NAND boot and Wii Remote pairing, add DMA/SRAM conformance tests and a Wii comparison probe. Keep P1 profiling tools and native update A/B evidence; reject the unhelpful targeted SRAM shortcut.
This commit is contained in:
1 parent
b931671fde
commit
c230971f07
64 files changed
+6488
-238
No files matched your search
@@ -0,0 +1,204 @@
|
||||
// Copyright 2026 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// Offline ETL decoder. No process attach, memory read or thread suspension.
|
||||
#define NOMINMAX
|
||||
#include <windows.h>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <cwchar>
|
||||
#include <evntrace.h>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <tdh.h>
|
||||
#include <vector>
|
||||
|
||||
static constexpr GUID NATIVE = {
|
||||
0x54ee9231, 0x28be, 0x4ab0, {0x98, 0x1e, 0xb2, 0x6f, 0x97, 0x9c, 0x56, 0xa4}};
|
||||
static FILE* output;
|
||||
static unsigned long long decoded = 0, errors = 0, total = 0;
|
||||
static std::map<std::string, std::vector<unsigned char>> schemas;
|
||||
static bool inspect_system = false;
|
||||
static unsigned inspected_schemas = 0, failed_schemas = 0;
|
||||
static std::string UTF8(const wchar_t* value)
|
||||
{
|
||||
if (!value || !*value)
|
||||
return {};
|
||||
int n = WideCharToMultiByte(CP_UTF8, 0, value, -1, nullptr, 0, nullptr, nullptr);
|
||||
std::string text(n, '\0');
|
||||
WideCharToMultiByte(CP_UTF8, 0, value, -1, text.data(), n, nullptr, nullptr);
|
||||
text.pop_back();
|
||||
return text;
|
||||
}
|
||||
static std::string Quote(const std::string& value)
|
||||
{
|
||||
std::string result = "\"";
|
||||
for (unsigned char c : value)
|
||||
{
|
||||
if (c == '"' || c == '\\')
|
||||
{
|
||||
result += '\\';
|
||||
result += c;
|
||||
}
|
||||
else if (c < 32)
|
||||
{
|
||||
char escaped[7];
|
||||
std::snprintf(escaped, sizeof(escaped), "\\u%04x", c);
|
||||
result += escaped;
|
||||
}
|
||||
else
|
||||
result += c;
|
||||
}
|
||||
return result + '"';
|
||||
}
|
||||
static void WINAPI Event(PEVENT_RECORD record)
|
||||
{
|
||||
++total;
|
||||
const bool native = IsEqualGUID(record->EventHeader.ProviderId, NATIVE);
|
||||
std::string key(reinterpret_cast<const char*>(&record->EventHeader.ProviderId), sizeof(GUID));
|
||||
key.append(reinterpret_cast<const char*>(&record->EventHeader.EventDescriptor),
|
||||
sizeof(EVENT_DESCRIPTOR));
|
||||
auto& buffer = schemas[key];
|
||||
const bool first_schema = buffer.empty();
|
||||
if (native || buffer.empty())
|
||||
{
|
||||
ULONG bytes = 0;
|
||||
const ULONG status = TdhGetEventInformation(record, 0, nullptr, nullptr, &bytes);
|
||||
if (status != ERROR_INSUFFICIENT_BUFFER)
|
||||
{
|
||||
if (native)
|
||||
++errors;
|
||||
else if (inspect_system && failed_schemas++ < 20)
|
||||
std::fprintf(stderr, "TDH missing: event=%u opcode=%u status=%lu\n",
|
||||
record->EventHeader.EventDescriptor.Id,
|
||||
record->EventHeader.EventDescriptor.Opcode, status);
|
||||
return;
|
||||
}
|
||||
buffer.resize(bytes);
|
||||
if (TdhGetEventInformation(record, 0, nullptr,
|
||||
reinterpret_cast<PTRACE_EVENT_INFO>(buffer.data()),
|
||||
&bytes) != ERROR_SUCCESS)
|
||||
{
|
||||
if (native)
|
||||
++errors;
|
||||
buffer.clear();
|
||||
return;
|
||||
}
|
||||
}
|
||||
auto* info = reinterpret_cast<PTRACE_EVENT_INFO>(buffer.data());
|
||||
const auto text = [&](ULONG offset) -> std::string {
|
||||
return offset ? UTF8(reinterpret_cast<wchar_t*>(buffer.data() + offset)) : "";
|
||||
};
|
||||
std::string name = text(info->EventNameOffset);
|
||||
if (name.empty())
|
||||
name = text(info->TaskNameOffset) + "/" + text(info->OpcodeNameOffset);
|
||||
if (inspect_system && !native && first_schema && inspected_schemas++ < 300)
|
||||
{
|
||||
std::fprintf(stderr, "SCHEMA event=%u opcode=%u name=%s fields=",
|
||||
record->EventHeader.EventDescriptor.Id,
|
||||
record->EventHeader.EventDescriptor.Opcode, name.c_str());
|
||||
for (ULONG i = 0; i < info->TopLevelPropertyCount && i < 30; ++i)
|
||||
std::fprintf(stderr, "%s%s", i ? "," : "", text(info->EventPropertyInfoArray[i].NameOffset).c_str());
|
||||
std::fprintf(stderr, "\n");
|
||||
}
|
||||
bool interesting = native;
|
||||
// Keep scheduling and instruction pointer records for independent qualification.
|
||||
for (ULONG i = 0; i < info->TopLevelPropertyCount; ++i)
|
||||
{
|
||||
const auto field = text(info->EventPropertyInfoArray[i].NameOffset);
|
||||
if (field == "InstructionPointer" || field == "OldThreadId" || field == "TThreadId")
|
||||
interesting = true;
|
||||
}
|
||||
if (!interesting)
|
||||
return;
|
||||
std::string fields;
|
||||
for (ULONG i = 0; i < info->TopLevelPropertyCount; ++i)
|
||||
{
|
||||
const auto& property = info->EventPropertyInfoArray[i];
|
||||
if (property.Flags & PropertyStruct)
|
||||
continue;
|
||||
const auto field = text(property.NameOffset);
|
||||
PROPERTY_DATA_DESCRIPTOR descriptor{};
|
||||
descriptor.PropertyName = reinterpret_cast<ULONGLONG>(buffer.data() + property.NameOffset);
|
||||
descriptor.ArrayIndex = ULONG_MAX;
|
||||
ULONG size = 0;
|
||||
if (TdhGetPropertySize(record, 0, nullptr, 1, &descriptor, &size) != ERROR_SUCCESS ||
|
||||
size > 65536)
|
||||
{
|
||||
if (native)
|
||||
++errors;
|
||||
continue;
|
||||
}
|
||||
std::vector<unsigned char> bytes(size + 2, 0);
|
||||
if (TdhGetProperty(record, 0, nullptr, 1, &descriptor, size, bytes.data()) != ERROR_SUCCESS)
|
||||
{
|
||||
if (native)
|
||||
++errors;
|
||||
continue;
|
||||
}
|
||||
std::string value;
|
||||
const auto type = property.nonStructType.InType;
|
||||
if (type == TDH_INTYPE_UNICODESTRING)
|
||||
value = Quote(UTF8(reinterpret_cast<wchar_t*>(bytes.data())));
|
||||
else if (type == TDH_INTYPE_ANSISTRING)
|
||||
value = Quote(reinterpret_cast<char*>(bytes.data()));
|
||||
else if (size == 1 || size == 2 || size == 4 || size == 8)
|
||||
{
|
||||
unsigned long long number = 0;
|
||||
std::memcpy(&number, bytes.data(), size);
|
||||
value = std::to_string(number);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (native)
|
||||
++errors;
|
||||
continue;
|
||||
}
|
||||
if (!fields.empty())
|
||||
fields += ',';
|
||||
fields += Quote(field) + ':' + value;
|
||||
}
|
||||
std::fprintf(output,
|
||||
"{\"native\":%s,\"event\":%s,\"qpc\":%lld,\"pid\":%lu,\"tid\":%lu,\"cpu\":%u,"
|
||||
"\"opcode\":%u,\"data\":{%s}}\n",
|
||||
native ? "true" : "false", Quote(name).c_str(),
|
||||
record->EventHeader.TimeStamp.QuadPart, record->EventHeader.ProcessId,
|
||||
record->EventHeader.ThreadId, record->BufferContext.ProcessorNumber,
|
||||
record->EventHeader.EventDescriptor.Opcode, fields.c_str());
|
||||
++decoded;
|
||||
}
|
||||
int wmain(int argc, wchar_t** argv)
|
||||
{
|
||||
if (argc != 3 && (argc != 4 || std::wcscmp(argv[3], L"--inspect-system") != 0))
|
||||
{
|
||||
std::fprintf(stderr, "DecodeP1 input.etl NEW-output.jsonl [--inspect-system]\n");
|
||||
return 2;
|
||||
}
|
||||
inspect_system = argc == 4;
|
||||
if (GetFileAttributesW(argv[2]) != INVALID_FILE_ATTRIBUTES)
|
||||
{
|
||||
std::fprintf(stderr, "Refusing overwrite\n");
|
||||
return 2;
|
||||
}
|
||||
if (_wfopen_s(&output, argv[2], L"wb"))
|
||||
return 2;
|
||||
EVENT_TRACE_LOGFILEW logfile{};
|
||||
logfile.LogFileName = argv[1];
|
||||
logfile.ProcessTraceMode = PROCESS_TRACE_MODE_EVENT_RECORD | PROCESS_TRACE_MODE_RAW_TIMESTAMP;
|
||||
logfile.EventRecordCallback = Event;
|
||||
TRACEHANDLE trace = OpenTraceW(&logfile);
|
||||
if (trace == INVALID_PROCESSTRACE_HANDLE)
|
||||
{
|
||||
std::fclose(output);
|
||||
return 3;
|
||||
}
|
||||
const ULONG result = ProcessTrace(&trace, 1, nullptr, nullptr);
|
||||
CloseTrace(trace);
|
||||
std::fclose(output);
|
||||
std::printf("{\"status\":%lu,\"total\":%llu,\"decoded\":%llu,\"native_decode_errors\":%llu,"
|
||||
"\"events_lost\":%lu,\"buffers_lost\":%lu,\"qpc_frequency\":%lld}\n",
|
||||
result, total, decoded, errors, logfile.LogfileHeader.EventsLost,
|
||||
logfile.LogfileHeader.BuffersLost, logfile.LogfileHeader.PerfFreq.QuadPart);
|
||||
return result != ERROR_SUCCESS || errors != 0 || logfile.LogfileHeader.EventsLost != 0 ||
|
||||
logfile.LogfileHeader.BuffersLost != 0;
|
||||
}
|
||||
Reference in new issue
Block a user