mirror of
https://github.com/mkxp-z/mkxp-z.git
synced 2025-08-14 19:05:34 +02:00
383 lines
11 KiB
Text
383 lines
11 KiB
Text
#import <SDL.h>
|
|
#ifdef __APPLE__
|
|
#import <Foundation/Foundation.h>
|
|
#endif
|
|
#import <ObjFW/ObjFW.h>
|
|
|
|
#ifdef __WIN32__
|
|
#import <SDL_syswm.h>
|
|
#import <windows.h>
|
|
#else
|
|
#import <cstring>
|
|
#endif
|
|
|
|
#import "eventthread.h"
|
|
#import "fake-api.h"
|
|
#import "filesystem.h"
|
|
#import "input.h"
|
|
#import "lang-fun.h"
|
|
#import "sharedstate.h"
|
|
|
|
// Essentials, without edits, needs Win32API. Two problems with that:
|
|
|
|
// 1. Not all Win32API functions work here, and
|
|
// 2. It uses Windows libraries.
|
|
|
|
// So, even on Windows, we need to switch out some of those functions
|
|
// with our own in order to trick the game into doing what we want.
|
|
|
|
// On Windows, we just have to worry about functions that involve
|
|
// thread IDs and window handles mostly. On anything else, every
|
|
// function that an Essentials game uses has to be impersonated.
|
|
|
|
// If you're lucky enough to be using a crossplatform library, you
|
|
// could just load that since MiniFFI should work anywhere, but
|
|
// every single system or windows-specific function you use has
|
|
// to be intercepted.
|
|
|
|
// This would also apply if you were trying to load a macOS .dylib
|
|
// from Windows or a linux .so from macOS. It's just not gonna work,
|
|
// chief.
|
|
|
|
// There's probably not much reason to change this outside of
|
|
// improving compatibility with games in general, since if you just
|
|
// want to fix one specific game and you're smart enough to add to
|
|
// this then you're also smart enough to either use MiniFFI on a
|
|
// library made for your platform or, better yet, just write the
|
|
// functionality into MKXP and don't use MiniFFI/Win32API at all.
|
|
|
|
// A lot of functions here will probably be bound directly to Ruby
|
|
// eventually (so that you can just call Graphics.screenshot or
|
|
// something instead of having to make a messy Win32API call
|
|
// that *now* has to run entirely different function to even work)
|
|
|
|
PREFABI DWORD MKXP_GetCurrentThreadId(void) NOP_VAL(DUMMY_VAL)
|
|
|
|
PREFABI DWORD
|
|
MKXP_GetWindowThreadProcessId(HWND hWnd, LPDWORD lpdwProcessId)
|
|
NOP_VAL(DUMMY_VAL)
|
|
|
|
PREFABI HWND MKXP_FindWindowEx(HWND hWnd, HWND hWndChildAfter,
|
|
LPCSTR lpszClass, LPCSTR lpszWindow)
|
|
#ifdef __WIN32__
|
|
{
|
|
SDL_SysWMinfo wm;
|
|
SDL_GetWindowWMInfo(shState->sdlWindow(), &wm);
|
|
return wm.info.win.window;
|
|
}
|
|
#else
|
|
NOP_VAL((HWND)DUMMY_VAL)
|
|
#endif
|
|
|
|
PREFABI DWORD MKXP_GetForegroundWindow(void) {
|
|
if (SDL_GetWindowFlags(shState->sdlWindow()) & SDL_WINDOW_INPUT_FOCUS) {
|
|
return DUMMY_VAL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
PREFABI BOOL MKXP_GetClientRect(HWND hWnd, LPRECT lpRect) {
|
|
SDL_GetWindowSize(shState->sdlWindow(), (int *)&lpRect->right,
|
|
(int *)&lpRect->bottom);
|
|
return true;
|
|
}
|
|
|
|
// You would think that you could just call GetCursorPos
|
|
// and ScreenToClient with the window handle and lppoint
|
|
// and be fine, but nope
|
|
PREFABI BOOL MKXP_GetCursorPos(LPPOINT lpPoint) {
|
|
SDL_GetGlobalMouseState((int *)&lpPoint->x, (int *)&lpPoint->y);
|
|
return true;
|
|
}
|
|
|
|
PREFABI BOOL MKXP_ScreenToClient(HWND hWnd, LPPOINT lpPoint) {
|
|
lpPoint->x = shState->input().mouseX();
|
|
lpPoint->y = shState->input().mouseY();
|
|
return true;
|
|
}
|
|
|
|
PREFABI BOOL MKXP_SetWindowPos(HWND hWnd, HWND hWndInsertAfter, int X, int Y,
|
|
int cx, int cy, UINT uFlags) {
|
|
// The game calls resize_screen which will automatically
|
|
// ... well, resize the screen
|
|
// shState->eThread().requestWindowResize(cx, cy);
|
|
|
|
shState->eThread().requestWindowReposition(X, Y);
|
|
return true;
|
|
}
|
|
|
|
PREFABI BOOL MKXP_SetWindowTextA(HWND hWnd, LPCSTR lpString) {
|
|
shState->eThread().requestWindowRename((const char *)lpString);
|
|
return true;
|
|
}
|
|
|
|
PREFABI BOOL MKXP_GetWindowRect(HWND hWnd, LPRECT lpRect) {
|
|
int cur_x, cur_y, cur_w, cur_h;
|
|
SDL_GetWindowPosition(shState->sdlWindow(), &cur_x, &cur_y);
|
|
SDL_GetWindowSize(shState->sdlWindow(), &cur_w, &cur_h);
|
|
lpRect->left = cur_x;
|
|
lpRect->right = cur_x + cur_w + 1;
|
|
lpRect->top = cur_y;
|
|
lpRect->bottom = cur_y + cur_h + 1;
|
|
return true;
|
|
}
|
|
|
|
// Shift key with GetKeyboardState doesn't work for whatever reason,
|
|
// so Windows needs this too
|
|
#define ks(sc) shState->eThread().keyStates[SDL_SCANCODE_##sc]
|
|
PREFABI BOOL MKXP_GetKeyboardState(PBYTE lpKeyState) {
|
|
#ifdef __WIN32__
|
|
bool rc = GetKeyboardState(lpKeyState);
|
|
if (rc) {
|
|
lpKeyState[VK_LSHIFT] = ks(LSHIFT) << 7;
|
|
lpKeyState[VK_RSHIFT] = ks(RSHIFT) << 7;
|
|
lpKeyState[VK_SHIFT] =
|
|
(lpKeyState[VK_LSHIFT] || lpKeyState[VK_RSHIFT]) ? 0x80 : 0;
|
|
}
|
|
return rc;
|
|
#else
|
|
for (int i = 254; i > 0; i--) {
|
|
lpKeyState[i] = (MKXP_GetAsyncKeyState(i)) ? 0x80 : 0;
|
|
}
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
// =========================================
|
|
// macOS / Linux only stuff starts here
|
|
// =========================================
|
|
|
|
#ifndef __WIN32__
|
|
|
|
PREFABI VOID MKXP_RtlMoveMemory(VOID *Destination, VOID *Source,
|
|
SIZE_T Length) {
|
|
// I have no idea if this is a good idea or not
|
|
// or why it's even necessary in the first place,
|
|
// getting rid of this is priority #1
|
|
memcpy(Destination, Source, Length);
|
|
}
|
|
|
|
// I don't know who's more crazy, them for writing this stuff
|
|
// or me for being willing to put it here.
|
|
|
|
// Probably me.
|
|
|
|
PREFABI HMODULE MKXP_LoadLibrary(LPCSTR lpLibFileName) {
|
|
return SDL_LoadObject(lpLibFileName);
|
|
}
|
|
|
|
PREFABI BOOL MKXP_FreeLibrary(HMODULE hLibModule) {
|
|
SDL_UnloadObject(hLibModule);
|
|
return true;
|
|
}
|
|
|
|
// Luckily, Essentials only cares about the high-order bit,
|
|
// so SDL's keystates will work perfectly fine
|
|
PREFABI SHORT MKXP_GetAsyncKeyState(int vKey) {
|
|
SHORT result;
|
|
switch (vKey) {
|
|
case 0x10: // Any Shift
|
|
result = (ks(LSHIFT) || ks(RSHIFT)) ? 0x8000 : 0;
|
|
break;
|
|
|
|
case 0x11: // Any Ctrl
|
|
result = (ks(LCTRL) || ks(RCTRL)) ? 0x8000 : 0;
|
|
break;
|
|
|
|
case 0x12: // Any Alt
|
|
result = (ks(LALT) || ks(RALT)) ? 0x8000 : 0;
|
|
break;
|
|
|
|
case 0x1: // Mouse button 1
|
|
result = (SDL_GetMouseState(0, 0) & SDL_BUTTON(1)) ? 0x8000 : 0;
|
|
break;
|
|
|
|
case 0x2: // Mouse button 2
|
|
result = (SDL_GetMouseState(0, 0) & SDL_BUTTON(3)) ? 0x8000 : 0;
|
|
break;
|
|
|
|
case 0x4: // Middle mouse
|
|
result = (SDL_GetMouseState(0, 0) & SDL_BUTTON(2)) ? 0x8000 : 0;
|
|
break;
|
|
|
|
default:
|
|
try {
|
|
// Use EventThread instead of Input because
|
|
// Input.update typically gets overridden
|
|
result = shState->eThread().keyStates[vKeyToScancode[vKey]] << 15;
|
|
} catch (...) {
|
|
result = 0;
|
|
}
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
#undef ks
|
|
|
|
PREFABI BOOL
|
|
MKXP_GetSystemPowerStatus(LPSYSTEM_POWER_STATUS lpSystemPowerStatus) {
|
|
int seconds, percent;
|
|
SDL_PowerState ps;
|
|
ps = SDL_GetPowerInfo(&seconds, &percent);
|
|
|
|
// Setting ACLineStatus
|
|
if (ps == SDL_POWERSTATE_UNKNOWN) {
|
|
lpSystemPowerStatus->ACLineStatus = 0xFF;
|
|
} else {
|
|
lpSystemPowerStatus->ACLineStatus =
|
|
(ps == SDL_POWERSTATE_ON_BATTERY) ? 1 : 0;
|
|
}
|
|
|
|
// Setting BatteryFlag
|
|
if (ps == SDL_POWERSTATE_ON_BATTERY) {
|
|
if (percent == -1) {
|
|
lpSystemPowerStatus->BatteryFlag = 0xFF;
|
|
} else if (percent >= 33) {
|
|
lpSystemPowerStatus->BatteryFlag = 1;
|
|
} else if (4 < percent && percent < 33) {
|
|
lpSystemPowerStatus->BatteryFlag = 2;
|
|
} else {
|
|
lpSystemPowerStatus->BatteryFlag = 4;
|
|
}
|
|
} else if (ps == SDL_POWERSTATE_CHARGING) {
|
|
lpSystemPowerStatus->BatteryFlag = 8;
|
|
} else if (ps == SDL_POWERSTATE_NO_BATTERY) {
|
|
lpSystemPowerStatus->BatteryFlag = (BYTE)128;
|
|
} else {
|
|
lpSystemPowerStatus->BatteryFlag = 0xFF;
|
|
}
|
|
|
|
// Setting BatteryLifePercent
|
|
lpSystemPowerStatus->BatteryLifePercent =
|
|
(percent != -1) ? (BYTE)percent : 0xFF;
|
|
|
|
// Setting SystemStatusFlag
|
|
lpSystemPowerStatus->SystemStatusFlag = 0;
|
|
|
|
// Setting BatteryLifeTime
|
|
lpSystemPowerStatus->BatteryLifeTime = seconds;
|
|
|
|
// Setting BatteryFullLifeTime
|
|
lpSystemPowerStatus->BatteryFullLifeTime = -1;
|
|
|
|
return true;
|
|
}
|
|
|
|
PREFABI BOOL MKXP_ShowWindow(HWND hWnd, int nCmdShow) NOP_VAL(true);
|
|
|
|
// This only currently supports getting screen width/height
|
|
// Not really motivated to do the other ones when I'll be
|
|
// extending Ruby at a later time anyway
|
|
PREFABI int MKXP_GetSystemMetrics(int nIndex) {
|
|
SDL_DisplayMode dm = {0};
|
|
int rc = SDL_GetDesktopDisplayMode(
|
|
SDL_GetWindowDisplayIndex(shState->sdlWindow()), &dm);
|
|
if (!rc) {
|
|
switch (nIndex) {
|
|
case 0:
|
|
return dm.w;
|
|
break;
|
|
|
|
case 1:
|
|
return dm.h;
|
|
break;
|
|
|
|
default:
|
|
return -1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
PREFABI HWND MKXP_SetCapture(HWND hWnd) NOP_VAL((HWND)DUMMY_VAL);
|
|
|
|
PREFABI BOOL MKXP_ReleaseCapture(void) NOP_VAL(true);
|
|
|
|
PREFABI int MKXP_ShowCursor(BOOL bShow) NOP_VAL(DUMMY_VAL);
|
|
|
|
PREFABI DWORD MKXP_GetPrivateProfileString(LPCTSTR lpAppName, LPCTSTR lpKeyName,
|
|
LPCTSTR lpDefault,
|
|
LPTSTR lpReturnedString, DWORD nSize,
|
|
LPCTSTR lpFileName) {
|
|
OFString *filePath = [OFString stringWithUTF8String:lpFileName];
|
|
OFString *ret = 0;
|
|
if ([OFFileManager.defaultManager fileExistsAtPath:filePath]) {
|
|
@try {
|
|
OFINIFile *iniFile = [OFINIFile fileWithPath:filePath];
|
|
OFINICategory *iniCat =
|
|
[iniFile categoryForName:[OFString stringWithUTF8String:lpAppName]];
|
|
ret = [iniCat stringForKey:[OFString stringWithUTF8String:lpKeyName]];
|
|
} @catch (...) {
|
|
}
|
|
}
|
|
if (ret) {
|
|
strncpy(lpReturnedString, ret.UTF8String, nSize);
|
|
} else {
|
|
strncpy(lpReturnedString, lpDefault, nSize);
|
|
}
|
|
return strlen(lpDefault);
|
|
}
|
|
|
|
// Does not handle sublanguages, only returns a few
|
|
// common languages
|
|
|
|
// Use MKXP.user_language instead
|
|
|
|
PREFABI short // I know it's a LANGID but I don't care
|
|
MKXP_GetUserDefaultLangID(void) {
|
|
char buf[50];
|
|
strncpy(buf, getUserLanguage(), sizeof(buf));
|
|
|
|
for (int i = 0; i < strlen(buf); i++) {
|
|
if (buf[i] == '_') {
|
|
buf[i] = 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
#define MATCH(l, c) \
|
|
if (!strcmp(l, buf)) \
|
|
return (c & 0x3ff);
|
|
MATCH("ja", 0x11);
|
|
MATCH("en", 0x09);
|
|
MATCH("fr", 0x0c);
|
|
MATCH("it", 0x10);
|
|
MATCH("de", 0x07);
|
|
MATCH("es", 0x0a);
|
|
MATCH("ko", 0x12);
|
|
MATCH("pt", 0x16);
|
|
MATCH("zh", 0x04);
|
|
#undef MATCH
|
|
return 0x09;
|
|
}
|
|
|
|
PREFABI BOOL MKXP_GetUserName(LPSTR lpBuffer, LPDWORD pcbBuffer) {
|
|
if (*pcbBuffer < 1)
|
|
return false;
|
|
#ifdef __APPLE__
|
|
strncpy(lpBuffer, NSUserName().UTF8String, *pcbBuffer);
|
|
#else
|
|
char *username = getenv("USER");
|
|
strncpy(lpBuffer, (username) ? username : "ditto", *pcbBuffer);
|
|
lpBuffer[0] = toupper(lpBuffer[0]);
|
|
#endif
|
|
return true;
|
|
}
|
|
|
|
PREFABI BOOL MKXP_RegisterHotKey(HWND hWnd, int id, UINT fsModifiers, UINT vk)
|
|
NOP_VAL(true);
|
|
|
|
PREFABI LONG MKXP_SetWindowLong(HWND hWnd, int nIndex, LONG dwNewLong) {
|
|
if (nIndex == -16) {
|
|
if (dwNewLong == 0) {
|
|
shState->eThread().requestFullscreenMode(true);
|
|
} else if (dwNewLong == 0x14ca0000) {
|
|
shState->eThread().requestFullscreenMode(false);
|
|
}
|
|
}
|
|
return DUMMY_VAL;
|
|
};
|
|
|
|
#endif
|