Combine guest memory and function table into one virtual allocation.

This commit is contained in:
Skyth 2025-01-02 14:45:42 +03:00
parent 038edfdebd
commit 967a0ce17f
27 changed files with 46 additions and 199 deletions

View file

@ -112,7 +112,6 @@ endif()
set(SWA_CPU_CXX_SOURCES set(SWA_CPU_CXX_SOURCES
"cpu/guest_thread.cpp" "cpu/guest_thread.cpp"
"cpu/code_cache.cpp"
) )
set(SWA_GPU_CXX_SOURCES set(SWA_GPU_CXX_SOURCES

View file

@ -1,6 +1,5 @@
#pragma once #pragma once
#include <cpu/guest_code.h>
#include <cpu/guest_stack_var.h> #include <cpu/guest_stack_var.h>
#include <kernel/function.h> #include <kernel/function.h>

View file

@ -3,7 +3,7 @@
#include <bit> #include <bit>
#include "audio.h" #include "audio.h"
#include "cpu/code_cache.h" #include <kernel/memory.h>
#define AUDIO_DRIVER_KEY (uint32_t)('DAUD') #define AUDIO_DRIVER_KEY (uint32_t)('DAUD')
@ -21,7 +21,7 @@ uint32_t XAudioRegisterRenderDriverClient(be<uint32_t>* callback, be<uint32_t>*
#endif #endif
*driver = AUDIO_DRIVER_KEY; *driver = AUDIO_DRIVER_KEY;
XAudioRegisterClient(KeFindHostFunction(*callback), callback[1]); XAudioRegisterClient(g_memory.FindFunction(*callback), callback[1]);
return 0; return 0;
} }

View file

@ -1,7 +1,5 @@
#include "sdl2_driver.h" #include "sdl2_driver.h"
#include <cpu/code_cache.h>
#include <cpu/guest_thread.h> #include <cpu/guest_thread.h>
#include <cpu/guest_code.h>
#include <kernel/heap.h> #include <kernel/heap.h>
static PPCFunc* g_clientCallback{}; static PPCFunc* g_clientCallback{};
@ -50,7 +48,7 @@ static void AudioThread()
if ((queuedAudioSize / callbackAudioSize) <= MAX_LATENCY) if ((queuedAudioSize / callbackAudioSize) <= MAX_LATENCY)
{ {
ctx.ppcContext.r3.u32 = g_clientCallbackParam; ctx.ppcContext.r3.u32 = g_clientCallbackParam;
g_clientCallback(ctx.ppcContext, reinterpret_cast<uint8_t*>(g_memory.base)); g_clientCallback(ctx.ppcContext, g_memory.base);
} }
auto now = std::chrono::steady_clock::now(); auto now = std::chrono::steady_clock::now();

View file

@ -1,9 +1,7 @@
#include <stdafx.h> #include <stdafx.h>
#include "xaudio_driver.h" #include "xaudio_driver.h"
#include <xaudio2.h> #include <xaudio2.h>
#include <cpu/code_cache.h>
#include <cpu/guest_thread.h> #include <cpu/guest_thread.h>
#include <cpu/guest_code.h>
#include <cpu/ppc_context.h> #include <cpu/ppc_context.h>
#include <kernel/heap.h> #include <kernel/heap.h>
@ -55,7 +53,7 @@ PPC_FUNC(DriverLoop)
WaitForSingleObject(g_audioSemaphore, INFINITE); WaitForSingleObject(g_audioSemaphore, INFINITE);
ctx.r3.u64 = g_clientCallbackParam; ctx.r3.u64 = g_clientCallbackParam;
GuestCode::Run((void*)g_clientCallback, &ctx); g_clientCallback(ctx, g_memory.base);
} }
} }

View file

@ -1,60 +0,0 @@
#include <stdafx.h>
#include "code_cache.h"
#include "ppc_context.h"
CodeCache::CodeCache()
{
#ifdef _WIN32
bucket = (char*)VirtualAlloc(nullptr, 0x200000000, MEM_RESERVE, PAGE_READWRITE);
assert(bucket != nullptr);
#else
bucket = (char*)mmap(NULL, 0x200000000, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0);
assert(bucket != (char*)MAP_FAILED);
#endif
}
CodeCache::~CodeCache()
{
#ifdef _WIN32
VirtualFree(bucket, 0, MEM_RELEASE);
#else
munmap(bucket, 0x200000000);
#endif
}
void CodeCache::Init()
{
for (size_t i = 0; PPCFuncMappings[i].guest != 0; i++)
{
if (PPCFuncMappings[i].host != nullptr)
{
#ifdef _WIN32
VirtualAlloc(bucket + PPCFuncMappings[i].guest * 2, sizeof(void*), MEM_COMMIT, PAGE_READWRITE);
#endif
*(void**)(bucket + PPCFuncMappings[i].guest * 2) = (void*)PPCFuncMappings[i].host;
}
}
}
void CodeCache::Insert(uint32_t guest, PPCFunc* host)
{
#ifdef _WIN32
VirtualAlloc(bucket + static_cast<uint64_t>(guest) * 2, sizeof(void*), MEM_COMMIT, PAGE_READWRITE);
#endif
*reinterpret_cast<PPCFunc**>(bucket + static_cast<uint64_t>(guest) * 2) = host;
}
void* CodeCache::Find(uint32_t guest) const
{
return *reinterpret_cast<void**>(bucket + static_cast<uint64_t>(guest) * 2);
}
SWA_API PPCFunc* KeFindHostFunction(uint32_t guest)
{
return reinterpret_cast<PPCFunc*>(g_codeCache.Find(guest));
}
SWA_API void KeInsertHostFunction(uint32_t guest, PPCFunc* function)
{
g_codeCache.Insert(guest, function);
}

View file

@ -1,19 +0,0 @@
#pragma once
struct CodeCache
{
char* bucket{};
CodeCache();
~CodeCache();
void Init();
void Insert(uint32_t guest, PPCFunc* host);
void* Find(uint32_t guest) const;
};
SWA_API PPCFunc* KeFindHostFunction(uint32_t guest);
SWA_API void KeInsertHostFunction(uint32_t guest, PPCFunc* function);
extern CodeCache g_codeCache;

View file

@ -1,23 +0,0 @@
#pragma once
#include "ppc_context.h"
#include <kernel/memory.h>
struct GuestCode
{
inline static void Run(void* hostAddress, PPCContext* ctx, void* baseAddress)
{
ctx->fpscr.loadFromHost();
reinterpret_cast<PPCFunc*>(hostAddress)(*ctx, reinterpret_cast<uint8_t*>(baseAddress));
}
inline static void Run(void* hostAddress, PPCContext* ctx)
{
ctx->fpscr.loadFromHost();
reinterpret_cast<PPCFunc*>(hostAddress)(*ctx, reinterpret_cast<uint8_t*>(g_memory.base));
}
inline static void Run(void* hostAddress)
{
Run(hostAddress, GetPPCContext());
}
};

View file

@ -3,8 +3,6 @@
#include <kernel/memory.h> #include <kernel/memory.h>
#include <kernel/heap.h> #include <kernel/heap.h>
#include <kernel/function.h> #include <kernel/function.h>
#include "code_cache.h"
#include "guest_code.h"
#include "ppc_context.h" #include "ppc_context.h"
constexpr size_t PCR_SIZE = 0xAB0; constexpr size_t PCR_SIZE = 0xAB0;
@ -29,7 +27,6 @@ GuestThreadContext::GuestThreadContext(uint32_t cpuNumber)
*(uint32_t*)(thread + PCR_SIZE + 0x10) = 0xFFFFFFFF; // that one TLS entry that felt quirky *(uint32_t*)(thread + PCR_SIZE + 0x10) = 0xFFFFFFFF; // that one TLS entry that felt quirky
*(uint32_t*)(thread + PCR_SIZE + TLS_SIZE + 0x14C) = ByteSwap(GuestThread::GetCurrentThreadId()); // thread id *(uint32_t*)(thread + PCR_SIZE + TLS_SIZE + 0x14C) = ByteSwap(GuestThread::GetCurrentThreadId()); // thread id
ppcContext.fn = (uint8_t*)g_codeCache.bucket;
ppcContext.r1.u64 = g_memory.MapVirtual(thread + PCR_SIZE + TLS_SIZE + TEB_SIZE + STACK_SIZE); // stack pointer ppcContext.r1.u64 = g_memory.MapVirtual(thread + PCR_SIZE + TLS_SIZE + TEB_SIZE + STACK_SIZE); // stack pointer
ppcContext.r13.u64 = g_memory.MapVirtual(thread); ppcContext.r13.u64 = g_memory.MapVirtual(thread);
ppcContext.fpscr.loadFromHost(); ppcContext.fpscr.loadFromHost();
@ -78,7 +75,7 @@ uint32_t GuestThread::Start(const GuestThreadParams& params)
GuestThreadContext ctx(cpuNumber); GuestThreadContext ctx(cpuNumber);
ctx.ppcContext.r3.u64 = params.value; ctx.ppcContext.r3.u64 = params.value;
reinterpret_cast<PPCFunc*>(g_codeCache.Find(params.function))(ctx.ppcContext, reinterpret_cast<uint8_t*>(g_memory.base)); g_memory.FindFunction(params.function)(ctx.ppcContext, g_memory.base);
return ctx.ppcContext.r3.u32; return ctx.ppcContext.r3.u32;
} }

View file

@ -6,8 +6,6 @@
#include <app.h> #include <app.h>
#include <bc_diff.h> #include <bc_diff.h>
#include <cpu/code_cache.h>
#include <cpu/guest_code.h>
#include <cpu/guest_thread.h> #include <cpu/guest_thread.h>
#include <decompressor.h> #include <decompressor.h>
#include <kernel/function.h> #include <kernel/function.h>
@ -1682,7 +1680,7 @@ static uint32_t CreateDevice(uint32_t a1, uint32_t a2, uint32_t a3, uint32_t a4,
memset(device, 0, sizeof(*device)); memset(device, 0, sizeof(*device));
uint32_t functionOffset = 0x443344; // D3D uint32_t functionOffset = 0x443344; // D3D
g_codeCache.Insert(functionOffset, HostToGuestFunction<SetRenderStateUnimplemented>); g_memory.InsertFunction(functionOffset, HostToGuestFunction<SetRenderStateUnimplemented>);
for (size_t i = 0; i < std::size(device->setRenderStateFunctions); i++) for (size_t i = 0; i < std::size(device->setRenderStateFunctions); i++)
device->setRenderStateFunctions[i] = functionOffset; device->setRenderStateFunctions[i] = functionOffset;
@ -1690,7 +1688,7 @@ static uint32_t CreateDevice(uint32_t a1, uint32_t a2, uint32_t a3, uint32_t a4,
for (auto& [state, function] : g_setRenderStateFunctions) for (auto& [state, function] : g_setRenderStateFunctions)
{ {
functionOffset += 4; functionOffset += 4;
g_codeCache.Insert(functionOffset, function); g_memory.InsertFunction(functionOffset, function);
device->setRenderStateFunctions[state / 4] = functionOffset; device->setRenderStateFunctions[state / 4] = functionOffset;
} }

View file

@ -314,19 +314,18 @@ T GuestToHostFunction(const TFunction& func, TArgs&&... argv)
auto& currentCtx = *GetPPCContext(); auto& currentCtx = *GetPPCContext();
PPCContext newCtx; // NOTE: No need for zero initialization, has lots of unnecessary code generation. PPCContext newCtx; // NOTE: No need for zero initialization, has lots of unnecessary code generation.
newCtx.fn = currentCtx.fn;
newCtx.r1 = currentCtx.r1; newCtx.r1 = currentCtx.r1;
newCtx.r13 = currentCtx.r13; newCtx.r13 = currentCtx.r13;
newCtx.fpscr = currentCtx.fpscr; newCtx.fpscr = currentCtx.fpscr;
_translate_args_to_guest(newCtx, (uint8_t*)g_memory.base, args); _translate_args_to_guest(newCtx, g_memory.base, args);
SetPPCContext(newCtx); SetPPCContext(newCtx);
if constexpr (std::is_function_v<TFunction>) if constexpr (std::is_function_v<TFunction>)
func(newCtx, (uint8_t*)g_memory.base); func(newCtx, g_memory.base);
else else
(*(PPCFunc**)(newCtx.fn + uint64_t(func) * 2))(newCtx, (uint8_t*)g_memory.base); g_memory.FindFunction(func)(newCtx, g_memory.base);
currentCtx.fpscr = newCtx.fpscr; currentCtx.fpscr = newCtx.fpscr;
SetPPCContext(currentCtx); SetPPCContext(currentCtx);

View file

@ -8,10 +8,7 @@ constexpr size_t RESERVED_END = 0xA0000000;
void Heap::Init() void Heap::Init()
{ {
g_memory.Alloc(0x20000, RESERVED_BEGIN - 0x20000, MEM_COMMIT);
heap = o1heapInit(g_memory.Translate(0x20000), RESERVED_BEGIN - 0x20000); heap = o1heapInit(g_memory.Translate(0x20000), RESERVED_BEGIN - 0x20000);
g_memory.Alloc(RESERVED_END, 0x100000000 - RESERVED_END, MEM_COMMIT);
physicalHeap = o1heapInit(g_memory.Translate(RESERVED_END), 0x100000000 - RESERVED_END); physicalHeap = o1heapInit(g_memory.Translate(RESERVED_END), 0x100000000 - RESERVED_END);
} }

View file

@ -1,48 +1,30 @@
#include <stdafx.h> #include <stdafx.h>
#include "memory.h" #include "memory.h"
Memory::Memory(void* address, size_t size) : size(size) Memory::Memory()
{ {
#ifdef _WIN32 #ifdef _WIN32
base = (char*)VirtualAlloc(address, size, MEM_RESERVE, PAGE_READWRITE); base = (uint8_t*)VirtualAlloc((void*)0x100000000ull, PPC_MEMORY_SIZE + PPC_FUNC_TABLE_SIZE, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (base == nullptr) if (base == nullptr)
base = (char*)VirtualAlloc(nullptr, size, MEM_RESERVE, PAGE_READWRITE); base = (uint8_t*)VirtualAlloc(nullptr, PPC_MEMORY_SIZE + PPC_FUNC_TABLE_SIZE, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
#else
base = (char*)mmap(address, size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0);
if (base == (char*)MAP_FAILED) DWORD oldProtect;
base = (char*)mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0); VirtualProtect(base, 4096, PAGE_NOACCESS, &oldProtect);
#else
base = (uint8_t*)mmap((void*)0x100000000ull, PPC_MEMORY_SIZE + PPC_FUNC_TABLE_SIZE, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0);
if (base == (uint8_t*)MAP_FAILED)
base = (uint8_t*)mmap(NULL, PPC_MEMORY_SIZE + PPC_FUNC_TABLE_SIZE, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0);
mprotect(base, 4096, PROT_NONE); mprotect(base, 4096, PROT_NONE);
#endif #endif
}
void* Memory::Alloc(size_t offset, size_t size, uint32_t type) for (size_t i = 0; PPCFuncMappings[i].guest != 0; i++)
{ {
#ifdef _WIN32 if (PPCFuncMappings[i].host != nullptr)
return VirtualAlloc(base + offset, size, type, PAGE_READWRITE); InsertFunction(PPCFuncMappings[i].guest, PPCFuncMappings[i].host);
#else }
return base + offset;
#endif
}
void* Memory::Commit(size_t offset, size_t size)
{
#ifdef _WIN32
return Alloc(offset, size, MEM_COMMIT);
#else
return base + offset;
#endif
}
void* Memory::Reserve(size_t offset, size_t size)
{
#ifdef _WIN32
return Alloc(offset, size, MEM_RESERVE);
#else
return base + offset;
#endif
} }
void* MmGetHostAddress(uint32_t ptr) void* MmGetHostAddress(uint32_t ptr)

View file

@ -5,29 +5,21 @@
#define MEM_RESERVE 0x00002000 #define MEM_RESERVE 0x00002000
#endif #endif
class Memory struct Memory
{ {
public: uint8_t* base{};
char* base{};
size_t size{};
size_t guestBase{};
Memory(void* address, size_t size); Memory();
void* Alloc(size_t offset, size_t size, uint32_t type);
void* Commit(size_t offset, size_t size);
void* Reserve(size_t offset, size_t size);
bool IsInMemoryRange(const void* host) const noexcept bool IsInMemoryRange(const void* host) const noexcept
{ {
return host >= base && host < (base + size); return host >= base && host < (base + PPC_MEMORY_SIZE);
} }
void* Translate(size_t offset) const noexcept void* Translate(size_t offset) const noexcept
{ {
if (offset) if (offset)
assert(offset < 0x100000000ull); assert(offset < PPC_MEMORY_SIZE);
return base + offset; return base + offset;
} }
@ -37,7 +29,17 @@ public:
if (host) if (host)
assert(IsInMemoryRange(host)); assert(IsInMemoryRange(host));
return static_cast<uint32_t>(static_cast<const char*>(host) - base); return static_cast<uint32_t>(static_cast<const uint8_t*>(host) - base);
}
PPCFunc* FindFunction(uint32_t guest) const noexcept
{
return *reinterpret_cast<PPCFunc**>(base + PPC_FUNC_TABLE_OFFSET + (uint64_t(guest) * 2));
}
void InsertFunction(uint32_t guest, PPCFunc* host)
{
*reinterpret_cast<PPCFunc**>(base + PPC_FUNC_TABLE_OFFSET + (uint64_t(guest) * 2)) = host;
} }
}; };

View file

@ -1,5 +1,4 @@
#include <stdafx.h> #include <stdafx.h>
#include <cpu/code_cache.h>
#include <cpu/guest_thread.h> #include <cpu/guest_thread.h>
#include <gpu/video.h> #include <gpu/video.h>
#include <kernel/function.h> #include <kernel/function.h>
@ -25,9 +24,8 @@
const size_t XMAIOBegin = 0x7FEA0000; const size_t XMAIOBegin = 0x7FEA0000;
const size_t XMAIOEnd = XMAIOBegin + 0x0000FFFF; const size_t XMAIOEnd = XMAIOBegin + 0x0000FFFF;
Memory g_memory{ reinterpret_cast<void*>(0x100000000), 0x100000000 }; Memory g_memory;
Heap g_userHeap; Heap g_userHeap;
CodeCache g_codeCache;
XDBFWrapper g_xdbfWrapper; XDBFWrapper g_xdbfWrapper;
std::unordered_map<uint16_t, GuestTexture*> g_xdbfTextureCache; std::unordered_map<uint16_t, GuestTexture*> g_xdbfTextureCache;
@ -37,11 +35,7 @@ void HostStartup()
CoInitializeEx(nullptr, COINIT_MULTITHREADED); CoInitializeEx(nullptr, COINIT_MULTITHREADED);
#endif #endif
g_memory.Alloc(0x10000, 0x1000, MEM_COMMIT);
g_userHeap.Init(); g_userHeap.Init();
g_codeCache.Init();
g_memory.Alloc(XMAIOBegin, 0xFFFF, MEM_COMMIT);
hid::Init(); hid::Init();
} }
@ -109,8 +103,6 @@ uint32_t LdrLoadModule(const std::filesystem::path &path)
auto* xex = reinterpret_cast<XEX_HEADER*>(loadResult.data()); auto* xex = reinterpret_cast<XEX_HEADER*>(loadResult.data());
auto security = reinterpret_cast<XEX2_SECURITY_INFO*>((char*)xex + xex->AddressOfSecurityInfo); auto security = reinterpret_cast<XEX2_SECURITY_INFO*>((char*)xex + xex->AddressOfSecurityInfo);
g_memory.Alloc(security->ImageBase, security->SizeOfImage, MEM_COMMIT);
auto format = Xex2FindOptionalHeader<XEX_FILE_FORMAT_INFO>(xex, XEX_HEADER_FILE_FORMAT_INFO); auto format = Xex2FindOptionalHeader<XEX_FILE_FORMAT_INFO>(xex, XEX_HEADER_FILE_FORMAT_INFO);
auto entry = *Xex2FindOptionalHeader<uint32_t>(xex, XEX_HEADER_ENTRY_POINT); auto entry = *Xex2FindOptionalHeader<uint32_t>(xex, XEX_HEADER_ENTRY_POINT);
ByteSwapInplace(entry); ByteSwapInplace(entry);

View file

@ -1,4 +1,3 @@
#include <cpu/guest_code.h>
#include <user/config.h> #include <user/config.h>
#include <kernel/function.h> #include <kernel/function.h>
#include <os/media.h> #include <os/media.h>

View file

@ -1,4 +1,3 @@
#include <cpu/guest_code.h>
#include <api/SWA.h> #include <api/SWA.h>
#include <ui/game_window.h> #include <ui/game_window.h>
#include <user/config.h> #include <user/config.h>

View file

@ -1,5 +1,4 @@
#include <cpu/code_cache.h> #include <kernel/memory.h>
#include <cpu/guest_code.h>
#include <api/SWA.h> #include <api/SWA.h>
#include <ui/game_window.h> #include <ui/game_window.h>
#include <user/config.h> #include <user/config.h>
@ -87,13 +86,13 @@ bool LoadingUpdateMidAsmHook(PPCRegister& r31)
double deltaTime = std::min(std::chrono::duration<double>(now - g_prev).count(), 1.0 / 15.0); double deltaTime = std::min(std::chrono::duration<double>(now - g_prev).count(), 1.0 / 15.0);
g_prev = now; g_prev = now;
uint8_t* base = reinterpret_cast<uint8_t*>(g_memory.base); uint8_t* base = g_memory.base;
uint32_t application = PPC_LOAD_U32(PPC_LOAD_U32(r31.u32 + 4)); uint32_t application = PPC_LOAD_U32(PPC_LOAD_U32(r31.u32 + 4));
uint32_t update = PPC_LOAD_U32(PPC_LOAD_U32(application) + 20); uint32_t update = PPC_LOAD_U32(PPC_LOAD_U32(application) + 20);
g_ppcContext->r3.u32 = application; g_ppcContext->r3.u32 = application;
g_ppcContext->f1.f64 = deltaTime; g_ppcContext->f1.f64 = deltaTime;
reinterpret_cast<PPCFunc*>(g_codeCache.Find(update))(*g_ppcContext, base); g_memory.FindFunction(update)(*g_ppcContext, base);
bool loading = PPC_LOAD_U8(0x83367A4C); bool loading = PPC_LOAD_U8(0x83367A4C);
if (loading) if (loading)

View file

@ -1,4 +1,3 @@
#include <cpu/guest_code.h>
#include <api/SWA.h> #include <api/SWA.h>
#include <ui/game_window.h> #include <ui/game_window.h>
#include <user/achievement_data.h> #include <user/achievement_data.h>

View file

@ -1,5 +1,3 @@
#include <cpu/guest_code.h>
// CObjFlame::CObjFlame // CObjFlame::CObjFlame
// A field is not zero initialized, // A field is not zero initialized,
// causing collisions to constantly get created // causing collisions to constantly get created

View file

@ -1,4 +1,3 @@
#include <cpu/guest_code.h>
#include <api/SWA.h> #include <api/SWA.h>
#include <ui/game_window.h> #include <ui/game_window.h>
#include <ui/window_events.h> #include <ui/window_events.h>

View file

@ -1,4 +1,3 @@
#include <cpu/guest_code.h>
#include <user/achievement_data.h> #include <user/achievement_data.h>
#include <user/config.h> #include <user/config.h>
#include <api/SWA.h> #include <api/SWA.h>

View file

@ -1,4 +1,3 @@
#include <cpu/guest_code.h>
#include <kernel/function.h> #include <kernel/function.h>
#include <api/SWA.h> #include <api/SWA.h>
#include <ui/achievement_menu.h> #include <ui/achievement_menu.h>

View file

@ -1,4 +1,3 @@
#include <cpu/guest_code.h>
#include <api/SWA.h> #include <api/SWA.h>
#include <locale/locale.h> #include <locale/locale.h>
#include <ui/fader.h> #include <ui/fader.h>

View file

@ -1,4 +1,3 @@
#include <cpu/guest_code.h>
#include <api/SWA.h> #include <api/SWA.h>
#include <locale/locale.h> #include <locale/locale.h>
#include <ui/button_guide.h> #include <ui/button_guide.h>

View file

@ -1,4 +1,3 @@
#include <cpu/guest_code.h>
#include <user/config.h> #include <user/config.h>
#include <api/SWA.h> #include <api/SWA.h>
#include <ui/game_window.h> #include <ui/game_window.h>

@ -1 +1 @@
Subproject commit 4650dc69fb40d0e5857fe28c40e85eec7c97edf7 Subproject commit de2840970ffc3161a4cb8743b10ddd4da93bdc9f