Ex-Board support
- Based on work by zxmarcos - Requires clean-up - Requires button injection from TPUI - Requires AH30 and Suggoi Arcana Heart 2 fixing (does not boot and/or crashes) - Daemon Bride boots ok
This commit is contained in:
parent
5b70d00348
commit
574f456460
480
OpenParrot/src/Functions/Games/Ex-Board/Ex-BoardGeneric.cpp
Normal file
480
OpenParrot/src/Functions/Games/Ex-Board/Ex-BoardGeneric.cpp
Normal file
@ -0,0 +1,480 @@
|
|||||||
|
#include <StdInc.h>
|
||||||
|
#include "Utility/InitFunction.h"
|
||||||
|
#include "Functions/Global.h"
|
||||||
|
#include <deque>
|
||||||
|
// BASED ON xb_monitor by zxmarcos https://github.com/zxmarcos/xb_monitor
|
||||||
|
#if _M_IX86
|
||||||
|
|
||||||
|
#define SRAM_SIZE 0xffff
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
static int isAddressedExBoard = 0;
|
||||||
|
int is_addressedExBoard() {
|
||||||
|
return isAddressedExBoard;
|
||||||
|
}
|
||||||
|
void reset_addressedExBoard()
|
||||||
|
{
|
||||||
|
isAddressedExBoard = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static vector<char> r(1024);
|
||||||
|
|
||||||
|
unsigned char SRAM[SRAM_SIZE];
|
||||||
|
|
||||||
|
void SRAM_save()
|
||||||
|
{
|
||||||
|
FILE *fp = NULL;
|
||||||
|
fp = fopen("sram.bin", "wb");
|
||||||
|
if (!fp) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fwrite(SRAM, 1, SRAM_SIZE, fp);
|
||||||
|
fclose(fp);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SRAM_load()
|
||||||
|
{
|
||||||
|
FILE *fp = NULL;
|
||||||
|
fp = fopen("sram.bin", "rb");
|
||||||
|
memset(SRAM, 0, SRAM_SIZE);
|
||||||
|
if (!fp) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fread(SRAM, 1, SRAM_SIZE, fp);
|
||||||
|
fclose(fp);
|
||||||
|
}
|
||||||
|
|
||||||
|
DWORD process_streamExBoard(UINT8 *stream, DWORD srcsize, BYTE *dst, DWORD dstsize)
|
||||||
|
{
|
||||||
|
|
||||||
|
r.clear();
|
||||||
|
|
||||||
|
switch (stream[1]) {
|
||||||
|
case 0xfa:
|
||||||
|
{
|
||||||
|
unsigned int addr = 0;
|
||||||
|
unsigned int size = 0;
|
||||||
|
r.push_back(0x76);
|
||||||
|
r.push_back(0xfa);
|
||||||
|
r.push_back(0x05);
|
||||||
|
r.push_back(0x70);
|
||||||
|
r.push_back(0x42);
|
||||||
|
//A5 FA 17 00 58 10 02 03 04 05 06 00 01 02 03 04 05 06 00 00 00 00 5A
|
||||||
|
addr = (stream[3] << 8) | (stream[4]);
|
||||||
|
size = stream[5];
|
||||||
|
|
||||||
|
if (size >= (srcsize - 1))
|
||||||
|
size = srcsize - 1;
|
||||||
|
if ((addr + size) >= 0xffff)
|
||||||
|
size = 0xffff - addr;
|
||||||
|
|
||||||
|
//logmsg("SRAM WRITE %d : %x\n", size, addr);
|
||||||
|
memcpy(&SRAM[addr], &stream[6], size);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
#if 0
|
||||||
|
case 0xfb:
|
||||||
|
r.push_back(0x76);
|
||||||
|
r.push_back(0xfb);
|
||||||
|
|
||||||
|
if (stream[4] == 0)
|
||||||
|
{
|
||||||
|
r.push_back(80 + 80 + 5);
|
||||||
|
int a3 = 0;
|
||||||
|
int a5 = 3;
|
||||||
|
for (int i = 0; i < 20; i++) {
|
||||||
|
// 3
|
||||||
|
if (i & 1) {
|
||||||
|
for (int k = 0; k < 3; k++)
|
||||||
|
r.push_back(tbl0[a3 + k]);
|
||||||
|
a3 += 12;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
for (int k = 0; k < 5; k++)
|
||||||
|
r.push_back(tbl0[a5 + k]);
|
||||||
|
a5 += 12;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
a3 = 1;
|
||||||
|
a5 = 4;
|
||||||
|
for (int i = 0; i < 20; i++) {
|
||||||
|
// 3
|
||||||
|
if (i & 1) {
|
||||||
|
for (int k = 0; k < 3; k++)
|
||||||
|
r.push_back(tbl0[a3 + k]);
|
||||||
|
a3 += 12;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
for (int k = 0; k < 5; k++)
|
||||||
|
r.push_back(tbl0[a5 + k]);
|
||||||
|
a5 += 12;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
r.push_back(0xf4);
|
||||||
|
for (int i = 0; i < 0xef; i++)
|
||||||
|
r.push_back(0);
|
||||||
|
}
|
||||||
|
r.push_back(0x70);
|
||||||
|
r.push_back(0x42);
|
||||||
|
break;
|
||||||
|
#else
|
||||||
|
case 0xfb:
|
||||||
|
{
|
||||||
|
//A5 FB 07 00 00 EF 5A
|
||||||
|
r.push_back(0x76);
|
||||||
|
r.push_back(0xfb);
|
||||||
|
|
||||||
|
unsigned int addr = 0;
|
||||||
|
unsigned int size = stream[5];
|
||||||
|
unsigned pos = r.size();
|
||||||
|
r.push_back(5);
|
||||||
|
|
||||||
|
addr = (stream[3] << 8) | (stream[4]);
|
||||||
|
size = stream[5];
|
||||||
|
|
||||||
|
if ((addr + size) >= 0xffff)
|
||||||
|
size = 0xffff - addr;
|
||||||
|
|
||||||
|
for (int i = 0; i < size; i++)
|
||||||
|
r.push_back(SRAM[addr++]);
|
||||||
|
|
||||||
|
r[pos] += size;
|
||||||
|
//logmsg("SRAM READ %d : %x\n", size, addr);
|
||||||
|
|
||||||
|
r.push_back(0x70);
|
||||||
|
r.push_back(0x42);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
case 0xfe:
|
||||||
|
r.push_back(0x76);
|
||||||
|
r.push_back(0xfe);
|
||||||
|
r.push_back(0x06);
|
||||||
|
if (stream[3])
|
||||||
|
r.push_back(0x01);
|
||||||
|
else
|
||||||
|
r.push_back(0);
|
||||||
|
r.push_back(0x70);
|
||||||
|
r.push_back(0x42);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x01:
|
||||||
|
r.push_back(0x76);
|
||||||
|
r.push_back(0x01);
|
||||||
|
r.push_back(0x06);
|
||||||
|
r.push_back(0x00);
|
||||||
|
r.push_back(0x70);
|
||||||
|
r.push_back(0x42);
|
||||||
|
isAddressedExBoard = 1;
|
||||||
|
break;
|
||||||
|
case 0x08: // error
|
||||||
|
isAddressedExBoard = 0;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
BYTE *pdst = dst;
|
||||||
|
unsigned i = 0;
|
||||||
|
unsigned maxv = r.size();
|
||||||
|
|
||||||
|
if (maxv > dstsize)
|
||||||
|
maxv = dstsize;
|
||||||
|
|
||||||
|
for (i = 0; i < maxv; i++)
|
||||||
|
*pdst++ = r[i];
|
||||||
|
|
||||||
|
unsigned sz = r.size();
|
||||||
|
//r.clear();
|
||||||
|
return sz;
|
||||||
|
}
|
||||||
|
|
||||||
|
using namespace std::string_literals;
|
||||||
|
static std::map<HANDLE, std::deque<BYTE>> g_replyBuffers;
|
||||||
|
void AddCommOverride(HANDLE hFile);
|
||||||
|
|
||||||
|
static BOOL __stdcall ReadFileWrapExBoard(HANDLE hFile,
|
||||||
|
LPVOID lpBuffer,
|
||||||
|
DWORD nNumberOfBytesToRead,
|
||||||
|
LPDWORD lpNumberOfBytesRead,
|
||||||
|
LPOVERLAPPED lpOverlapped)
|
||||||
|
{
|
||||||
|
if (hFile == (HANDLE)0x8001)
|
||||||
|
{
|
||||||
|
OutputDebugStringA("COM1 READ");
|
||||||
|
auto& outQueue = g_replyBuffers[hFile];
|
||||||
|
|
||||||
|
int toRead = min(outQueue.size(), nNumberOfBytesToRead);
|
||||||
|
|
||||||
|
std::copy(outQueue.begin(), outQueue.begin() + toRead, reinterpret_cast<uint8_t*>(lpBuffer));
|
||||||
|
outQueue.erase(outQueue.begin(), outQueue.begin() + toRead);
|
||||||
|
|
||||||
|
*lpNumberOfBytesRead = toRead;
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
return ReadFile(hFile, lpBuffer, nNumberOfBytesToRead, lpNumberOfBytesRead, lpOverlapped);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static HANDLE __stdcall CreateFileAWrapExBoard(LPCSTR lpFileName,
|
||||||
|
DWORD dwDesiredAccess,
|
||||||
|
DWORD dwShareMode,
|
||||||
|
LPSECURITY_ATTRIBUTES lpSecurityAttributes,
|
||||||
|
DWORD dwCreationDisposition,
|
||||||
|
DWORD dwFlagsAndAttributes,
|
||||||
|
HANDLE hTemplateFile)
|
||||||
|
{
|
||||||
|
if (strnicmp(lpFileName, "D:\\", 3) == 0)
|
||||||
|
{
|
||||||
|
if (GetFileAttributesA(lpFileName) == INVALID_FILE_ATTRIBUTES)
|
||||||
|
{
|
||||||
|
wchar_t pathRoot[MAX_PATH];
|
||||||
|
GetModuleFileNameW(GetModuleHandle(nullptr), pathRoot, _countof(pathRoot));
|
||||||
|
|
||||||
|
wcsrchr(pathRoot, L'\\')[0] = L'\0';
|
||||||
|
|
||||||
|
// assume just ASCII
|
||||||
|
std::string fn = lpFileName;
|
||||||
|
std::wstring wfn(fn.begin(), fn.end());
|
||||||
|
|
||||||
|
CreateDirectoryW((pathRoot + L"\\TeknoParrot\\"s).c_str(), nullptr);
|
||||||
|
|
||||||
|
return CreateFileW((pathRoot + L"\\TeknoParrot\\"s + wfn.substr(3)).c_str(),
|
||||||
|
dwDesiredAccess,
|
||||||
|
dwShareMode,
|
||||||
|
lpSecurityAttributes,
|
||||||
|
dwCreationDisposition,
|
||||||
|
dwFlagsAndAttributes,
|
||||||
|
hTemplateFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strncmp(lpFileName, "COM1", 4) == 0)
|
||||||
|
{
|
||||||
|
OutputDebugStringA("COM1 HOOK");
|
||||||
|
HANDLE hFile = (HANDLE)0x8001;
|
||||||
|
|
||||||
|
AddCommOverride(hFile);
|
||||||
|
|
||||||
|
return hFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
return CreateFileA(lpFileName,
|
||||||
|
dwDesiredAccess,
|
||||||
|
dwShareMode,
|
||||||
|
lpSecurityAttributes,
|
||||||
|
dwCreationDisposition,
|
||||||
|
dwFlagsAndAttributes,
|
||||||
|
hTemplateFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
extern int* wheelSection;
|
||||||
|
|
||||||
|
BOOL __stdcall WriteFileWrapExBoard(HANDLE hFile,
|
||||||
|
LPVOID lpBuffer,
|
||||||
|
DWORD nNumberOfBytesToWrite,
|
||||||
|
LPDWORD lpNumberOfBytesWritten,
|
||||||
|
LPOVERLAPPED lpOverlapped)
|
||||||
|
{
|
||||||
|
if (hFile == (HANDLE)0x8001)
|
||||||
|
{
|
||||||
|
OutputDebugStringA("COM1 WRITE");
|
||||||
|
static BYTE rbuffer[1024];
|
||||||
|
DWORD sz = process_streamExBoard((LPBYTE)lpBuffer, nNumberOfBytesToWrite, rbuffer, 1024);
|
||||||
|
if (sz != 1) {
|
||||||
|
for (DWORD i = 0; i < sz; i++)
|
||||||
|
g_replyBuffers[hFile].push_back(rbuffer[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
*lpNumberOfBytesWritten = nNumberOfBytesToWrite;
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
return WriteFile(hFile, lpBuffer, nNumberOfBytesToWrite, lpNumberOfBytesWritten, lpOverlapped);
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOL __stdcall ClearCommErrorWrap(HANDLE hFile, LPDWORD lpErrors, LPCOMSTAT lpStat)
|
||||||
|
{
|
||||||
|
if (hFile != (HANDLE)0x8001)
|
||||||
|
{
|
||||||
|
return ClearCommError(hFile, lpErrors, lpStat);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(lpStat)
|
||||||
|
{
|
||||||
|
OutputDebugStringA("CLEAR COMM ERROR COM1");
|
||||||
|
if(g_replyBuffers[hFile].empty())
|
||||||
|
{
|
||||||
|
lpStat->cbInQue = g_replyBuffers[hFile].size();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
lpStat->cbInQue = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
lpStat->cbInQue += 8;
|
||||||
|
|
||||||
|
g_replyBuffers[hFile].push_back(0x76);
|
||||||
|
g_replyBuffers[hFile].push_back(0xFD);
|
||||||
|
g_replyBuffers[hFile].push_back(0x08);
|
||||||
|
g_replyBuffers[hFile].push_back(0x00); // Control Byte 1
|
||||||
|
g_replyBuffers[hFile].push_back(0x00); // Control Byte 2
|
||||||
|
g_replyBuffers[hFile].push_back(0x00); // Control Byte 3
|
||||||
|
g_replyBuffers[hFile].push_back(0x00); // Control Byte 4
|
||||||
|
g_replyBuffers[hFile].push_back(0x42);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOL __stdcall GetCommModemStatusWrap(HANDLE hFile, LPDWORD lpModemStat)
|
||||||
|
{
|
||||||
|
if (hFile != (HANDLE)0x8001) {
|
||||||
|
return GetCommModemStatus(hFile, lpModemStat);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_addressedExBoard())
|
||||||
|
*lpModemStat = 0x10;
|
||||||
|
else
|
||||||
|
*lpModemStat = 0;
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOL WINAPI CloseHandleWrap(
|
||||||
|
_In_ HANDLE hObject
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (hObject == (HANDLE)0x8001)
|
||||||
|
return TRUE;
|
||||||
|
CloseHandle(hObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
int __stdcall GetKeyLicenseWrap(void)
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
LONG __stdcall ChangeDisplaySettingsWrap(DEVMODE *lpDevMode, DWORD dwflags)
|
||||||
|
{
|
||||||
|
return DISP_CHANGE_SUCCESSFUL;
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOL __stdcall ExitWindowsExWrap(UINT uFlags, DWORD dwReason)
|
||||||
|
{
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
int __stdcall ShowCursorWrap(BOOL bShow)
|
||||||
|
{
|
||||||
|
return ShowCursor(TRUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
HCURSOR __stdcall SetCursorWrap(HCURSOR hCursor)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
SHORT __stdcall GetAsyncKeyStateWrap(int vKey)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOL __stdcall SetWindowPosWrap(HWND hWnd,
|
||||||
|
HWND hWndInsertAfter,
|
||||||
|
int X,
|
||||||
|
int Y,
|
||||||
|
int cx,
|
||||||
|
int cy,
|
||||||
|
UINT uFlags)
|
||||||
|
{
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
HWND __stdcall CreateWindowExWWrap(DWORD dwExStyle,
|
||||||
|
LPCWSTR lpClassName,
|
||||||
|
LPCWSTR lpWindowName,
|
||||||
|
DWORD dwStyle,
|
||||||
|
int x,
|
||||||
|
int y,
|
||||||
|
int nWidth,
|
||||||
|
int nHeight,
|
||||||
|
HWND hWndParent,
|
||||||
|
HMENU hMenu,
|
||||||
|
HINSTANCE hInstance,
|
||||||
|
LPVOID lpParam)
|
||||||
|
{
|
||||||
|
dwExStyle = 0;
|
||||||
|
dwStyle = WS_OVERLAPPEDWINDOW;
|
||||||
|
|
||||||
|
RECT r;
|
||||||
|
r.bottom = nHeight;
|
||||||
|
r.top = 0;
|
||||||
|
r.right = nWidth;
|
||||||
|
r.left = 0;
|
||||||
|
AdjustWindowRect(&r, WS_OVERLAPPEDWINDOW, FALSE);
|
||||||
|
|
||||||
|
return CreateWindowExW(dwExStyle, lpClassName, lpWindowName, dwStyle, x, y, r.right, r.bottom, hWndParent,
|
||||||
|
hMenu, hInstance, lpParam);
|
||||||
|
}
|
||||||
|
|
||||||
|
HWND __stdcall CreateWindowExAWrap(DWORD dwExStyle,
|
||||||
|
LPCSTR lpClassName,
|
||||||
|
LPCSTR lpWindowName,
|
||||||
|
DWORD dwStyle,
|
||||||
|
int x,
|
||||||
|
int y,
|
||||||
|
int nWidth,
|
||||||
|
int nHeight,
|
||||||
|
HWND hWndParent,
|
||||||
|
HMENU hMenu,
|
||||||
|
HINSTANCE hInstance,
|
||||||
|
LPVOID lpParam)
|
||||||
|
{
|
||||||
|
dwExStyle = 0;
|
||||||
|
dwStyle = WS_OVERLAPPEDWINDOW;
|
||||||
|
|
||||||
|
RECT r;
|
||||||
|
r.right = nWidth;
|
||||||
|
r.bottom = nHeight;
|
||||||
|
r.top = 0;
|
||||||
|
r.left = 0;
|
||||||
|
AdjustWindowRect(&r, dwStyle, FALSE);
|
||||||
|
|
||||||
|
nWidth = r.right - r.left;
|
||||||
|
nHeight = r.bottom - r.top;
|
||||||
|
|
||||||
|
return CreateWindowExA(dwExStyle, lpClassName, lpWindowName, dwStyle, 0, 0, nWidth, nHeight, hWndParent,
|
||||||
|
hMenu, hInstance, lpParam);
|
||||||
|
}
|
||||||
|
|
||||||
|
static InitFunction ExBoardGenericFunc([]()
|
||||||
|
{
|
||||||
|
iatHook("kernel32.dll", CreateFileAWrapExBoard, "CreateFileA");
|
||||||
|
iatHook("kernel32.dll", ReadFileWrapExBoard, "ReadFile");
|
||||||
|
iatHook("kernel32.dll", WriteFileWrapExBoard, "WriteFile");
|
||||||
|
iatHook("kernel32.dll", ClearCommErrorWrap, "ClearCommError");
|
||||||
|
iatHook("kernel32.dll", GetCommModemStatusWrap, "GetCommModemStatus");
|
||||||
|
iatHook("kernel32.dll", CloseHandleWrap, "CloseHandle");
|
||||||
|
iatHook("IpgExKey.dll", GetKeyLicenseWrap, "_GetKeyLicense@0");
|
||||||
|
iatHook("user32.dll", ChangeDisplaySettingsWrap, "ChangeDisplaySettingsA");
|
||||||
|
iatHook("user32.dll", ExitWindowsExWrap, "ExitWindowsEx");
|
||||||
|
iatHook("user32.dll", ShowCursorWrap, "ShowCursor");
|
||||||
|
iatHook("user32.dll", SetCursorWrap, "SetCursor");
|
||||||
|
iatHook("user32.dll", GetAsyncKeyStateWrap, "GetAsyncKeyState");
|
||||||
|
iatHook("user32.dll", SetWindowPosWrap, "SetWindowPos");
|
||||||
|
iatHook("user32.dll", CreateWindowExWWrap, "CreateWindowExW");
|
||||||
|
iatHook("user32.dll", CreateWindowExAWrap, "CreateWindowExA");
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
SRAM_load();
|
||||||
|
|
||||||
|
}, GameID::ExBoardGeneric);
|
||||||
|
#endif
|
@ -197,6 +197,18 @@ void GameDetect::DetectCurrentGame()
|
|||||||
currentGame = GameID::TypeXGeneric;
|
currentGame = GameID::TypeXGeneric;
|
||||||
X2Type = X2Type::BattleFantasia;
|
X2Type = X2Type::BattleFantasia;
|
||||||
break;
|
break;
|
||||||
|
case 0x521d6765: // Arcana Heart 3
|
||||||
|
currentGame = GameID::ExBoardGeneric;
|
||||||
|
break;
|
||||||
|
case 0x581aa812: // Daemon Bride
|
||||||
|
currentGame = GameID::ExBoardGeneric;
|
||||||
|
break;
|
||||||
|
case 0xbb359a1a: // Suggoi! Arcana Heart 2
|
||||||
|
currentGame = GameID::ExBoardGeneric;
|
||||||
|
break;
|
||||||
|
//case 0xea1984ff:
|
||||||
|
// currentGame = GameID::ExBoardGeneric;
|
||||||
|
// break;
|
||||||
#endif
|
#endif
|
||||||
default:
|
default:
|
||||||
auto moduleBase = (uintptr_t)GetModuleHandle(nullptr);
|
auto moduleBase = (uintptr_t)GetModuleHandle(nullptr);
|
||||||
|
@ -39,4 +39,5 @@ enum class GameID
|
|||||||
VirtuaRLimit,
|
VirtuaRLimit,
|
||||||
SchoolOfRagnarok,
|
SchoolOfRagnarok,
|
||||||
PokkenTournament,
|
PokkenTournament,
|
||||||
|
ExBoardGeneric
|
||||||
};
|
};
|
Loading…
Reference in New Issue
Block a user