code:
#include <time.h>
#include <limits.h>

// This is the common include for platform/renderer specific skeletons(glfw.cpp, win.cpp etc.) and using cross platform things (like Windows directories wrapper, platform specific global arrays etc.)
// Functions that's different on glfw and win but have same signature, should be located on platform.h.

enum eWinVersion
{
    OS_WIN95 = 0,
    OS_WIN98,
    OS_WINNT,
    OS_WIN2000,
    OS_WINXP,
};

#ifdef _WIN32

// As long as WITHWINDOWS isn't defined / <Windows.h> isn't included, we only need type definitions so let's include <IntSafe.h>.
// NOTE: It's perfectly fine to include <Windows.h> here, but it can increase build size and time in *some* conditions, and maybe substantially in future if we'll use crossplatform.h more.
#ifndef _INC_WINDOWS
	#ifndef __MWERKS__
		#include <IntSafe.h>
	#else
		#include <Windows.h>
	#endif
#endif
#if defined RW_D3D9 || defined RWLIBS
#include "win.h"
#endif
extern DWORD _dwOperatingSystemVersion;
#define fcaseopen fopen
#define caserename rename
#else
char *strupr(char *str);
char *strlwr(char *str);

enum {
	LANG_OTHER,
	LANG_GERMAN,
	LANG_FRENCH,
	LANG_ENGLISH,
	LANG_ITALIAN,
	LANG_SPANISH,
};

enum {
	SUBLANG_OTHER,
	SUBLANG_ENGLISH_AUS
};

extern long _dwOperatingSystemVersion;
char *casepath(char const *path, bool checkPathFirst = true);
FILE *_fcaseopen(char const *filename, char const *mode);
#define fcaseopen _fcaseopen
int _caserename(const char *old_filename, const char *new_filename);
#define caserename _caserename
#endif

#ifdef RW_GL3
typedef struct
{
    GLFWwindow* window;
    RwBool		fullScreen;
    RwV2d		lastMousePos;
    double      mouseWheel; // glfw doesn't cache it
    bool        cursorIsInWindow;
    RwInt8        joy1id;
    RwInt8        joy2id;
}
psGlobalType;

#define PSGLOBAL(var) (((psGlobalType *)(RsGlobal.ps))->var)

void CapturePad(RwInt32 padID);
void joysChangeCB(int jid, int event);
#endif

#ifdef DETECT_JOYSTICK_MENU
extern char gSelectedJoystickName[128];
#endif

enum eGameState
{
    GS_START_UP = 0,
    GS_INIT_LOGO_MPEG,
    GS_LOGO_MPEG,
    GS_INIT_INTRO_MPEG,
    GS_INTRO_MPEG,
    GS_INIT_ONCE,
    GS_INIT_FRONTEND,
    GS_FRONTEND,
    GS_INIT_PLAYING_GAME,
    GS_PLAYING_GAME,
};
extern RwUInt32 gGameState;

RwBool IsForegroundApp();

#ifndef MAX_PATH
    #if !defined _WIN32 || defined __MINGW32__
    #define MAX_PATH PATH_MAX
    #else
    #define MAX_PATH 260
    #endif
#endif

// Codes compatible with Windows and Linux
#ifndef _WIN32
#define DeleteFile unlink

// Needed for save games
struct SYSTEMTIME {
    RwUInt16 wYear;
    RwUInt16 wMonth;
    RwUInt16 wDayOfWeek;
    RwUInt16 wDay;
    RwUInt16 wHour;
    RwUInt16 wMinute;
    RwUInt16 wSecond;
    RwUInt16 wMilliseconds;
};

void GetLocalTime_CP(SYSTEMTIME* out);
#define GetLocalTime GetLocalTime_CP
#define OutputDebugString(s) re3_debug("[DBG-2]: %s\n",s)
#endif

// Compatible with Linux/POSIX and MinGW on Windows
#ifndef _WIN32
#include <iostream>
#include <dirent.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <langinfo.h>
#include <unistd.h>

typedef void* HANDLE;
#define INVALID_HANDLE_VALUE NULL
#define FindClose(h) \
    do { \
        if (h != nil) \
            closedir((DIR*)h); \
    } while(0)

#define LOCALE_USER_DEFAULT 0
#define DATE_SHORTDATE 0

struct WIN32_FIND_DATA {
    char extension[32]; // for searching
    char folder[MAX_PATH];	// for searching
    char cFileName[256]; // because tSkinInfo has it 256
    time_t ftLastWriteTime;
};

HANDLE FindFirstFile(const char*, WIN32_FIND_DATA*);
bool FindNextFile(HANDLE, WIN32_FIND_DATA*);
void FileTimeToSystemTime(time_t*, SYSTEMTIME*);
void GetDateFormat(int, int, SYSTEMTIME*, int, char*, int);
#endif

#ifdef __SWITCH__

// tweak glfw values for switch to match expected pc bindings
#ifdef GLFW_GAMEPAD_BUTTON_A
    #undef GLFW_GAMEPAD_BUTTON_A
#endif
#define GLFW_GAMEPAD_BUTTON_A 1

#ifdef GLFW_GAMEPAD_BUTTON_B
    #undef GLFW_GAMEPAD_BUTTON_B
#endif
#define GLFW_GAMEPAD_BUTTON_B 0

#ifdef GLFW_GAMEPAD_BUTTON_X
    #undef GLFW_GAMEPAD_BUTTON_X
#endif
#define GLFW_GAMEPAD_BUTTON_X 3

#ifdef GLFW_GAMEPAD_BUTTON_Y
    #undef GLFW_GAMEPAD_BUTTON_Y
#endif
#define GLFW_GAMEPAD_BUTTON_Y 2

#endif


code:
#include "common.h"
#include "Pad.h"
#include "ControllerConfig.h"
#include "Frontend.h"
#include "Camera.h"

#include "rwcore.h"
#include "skeleton.h"
#include "events.h"

/*
 *****************************************************************************
 */
static RsEventStatus
HandleKeyDown(RsKeyStatus *keyStatus)
{
	CPad *pad0 = CPad::GetPad(0);
	CPad *pad1 = CPad::GetPad(1);

	RwInt32 c = keyStatus->keyCharCode;

	if ( c != rsNULL )
	{
		switch (c)
		{
			case rsESC:
				{
					CPad::TempKeyState.ESC = 255;
					break;
				}

			case rsINS:
				{
					CPad::TempKeyState.INS = 255;
					break;
				}

			case rsDEL:
				{
					CPad::TempKeyState.DEL = 255;
					break;
				}

			case rsHOME:
				{
					CPad::TempKeyState.HOME = 255;
					break;
				}

			case rsEND:
				{
					CPad::TempKeyState.END = 255;
					break;
				}

			case rsPGUP:
				{
					CPad::TempKeyState.PGUP = 255;
					break;
				}

			case rsPGDN:
				{
					CPad::TempKeyState.PGDN = 255;
					break;
				}

			case rsUP:
				{
					CPad::TempKeyState.UP = 255;
					break;
				}

			case rsDOWN:
				{
					CPad::TempKeyState.DOWN = 255;
					break;
				}

			case rsLEFT:
				{
					CPad::TempKeyState.LEFT = 255;
					break;
				}

			case rsRIGHT:
				{
					CPad::TempKeyState.RIGHT = 255;
					break;
				}

			case rsNUMLOCK:
				{
					CPad::TempKeyState.NUMLOCK = 255;
					break;
				}

			case rsPADDEL:
				{
					CPad::TempKeyState.DECIMAL = 255;
					break;
				}

			case rsPADEND:
				{
					CPad::TempKeyState.NUM1 = 255;
					break;
				}

			case rsPADDOWN:
				{
					CPad::TempKeyState.NUM2 = 255;
					break;
				}

			case rsPADPGDN:
				{
					CPad::TempKeyState.NUM3 = 255;
					break;
				}

			case rsPADLEFT:
				{
					CPad::TempKeyState.NUM4 = 255;
					break;
				}

			case rsPAD5:
				{
					CPad::TempKeyState.NUM5 = 255;
					break;
				}

			case rsPADRIGHT:
				{
					CPad::TempKeyState.NUM6 = 255;
					break;
				}

			case rsPADHOME:
				{
					CPad::TempKeyState.NUM7 = 255;
					break;
				}

			case rsPADUP:
				{
					CPad::TempKeyState.NUM8 = 255;
					break;
				}

			case rsPADPGUP:
				{
					CPad::TempKeyState.NUM9 = 255;
					break;
				}

			case rsPADINS:
				{
					CPad::TempKeyState.NUM0 = 255;
					break;
				}

			case rsDIVIDE:
				{
					CPad::TempKeyState.DIV = 255;
					break;
				}

			case rsTIMES:
				{
					CPad::TempKeyState.MUL = 255;
					break;
				}

			case rsMINUS:
				{
					CPad::TempKeyState.SUB = 255;
					break;
				}

			case rsPADENTER:
				{
					CPad::TempKeyState.ENTER = 255;
					break;
				}

			case rsPLUS:
				{
					CPad::TempKeyState.ADD = 255;
					break;
				}

			case rsENTER:
				{
					CPad::TempKeyState.EXTENTER = 255;
					break;
				}

			case rsSCROLL:
				{
					CPad::TempKeyState.SCROLLLOCK = 255;
					break;
				}

			case rsPAUSE:
				{
					CPad::TempKeyState.PAUSE = 255;
					break;
				}

			case rsBACKSP:
				{
					CPad::TempKeyState.BACKSP = 255;
					break;
				}

			case rsTAB:
				{
					CPad::TempKeyState.TAB = 255;
					break;
				}

			case rsCAPSLK:
				{
					CPad::TempKeyState.CAPSLOCK = 255;
					break;
				}

			case rsLSHIFT:
				{
					CPad::TempKeyState.LSHIFT = 255;
					break;
				}

			case rsSHIFT:
				{
					CPad::TempKeyState.SHIFT = 255;
					break;
				}

			case rsRSHIFT:
				{
					CPad::TempKeyState.RSHIFT = 255;
					break;
				}

			case rsLCTRL:
				{
					CPad::TempKeyState.LCTRL = 255;
					break;
				}

			case rsRCTRL:
				{
					CPad::TempKeyState.RCTRL = 255;
					break;
				}

			case rsLALT:
				{
					CPad::TempKeyState.LALT = 255;
					break;
				}

			case rsRALT:
				{
					CPad::TempKeyState.RALT = 255;
					break;
				}

			case rsLWIN:
				{
					CPad::TempKeyState.LWIN = 255;
					break;
				}

			case rsRWIN:
				{
					CPad::TempKeyState.RWIN = 255;
					break;
				}

			case rsAPPS:
				{
					CPad::TempKeyState.APPS = 255;
					break;
				}

			case rsF1:
			case rsF2:
			case rsF3:
			case rsF4:
			case rsF5:
			case rsF6:
			case rsF7:
			case rsF8:
			case rsF9:
			case rsF10:
			case rsF11:
			case rsF12:
				{
					CPad::TempKeyState.F[c - rsF1] = 255;
					break;
				}

			default:
				{
					if ( c < 255 )
					{
						CPad::TempKeyState.VK_KEYS[c] = 255;
						pad0->AddToPCCheatString(c);
					}
					break;
				}
		}

		if ( CPad::m_bMapPadOneToPadTwo )
		{
			if ( c == 'D' ) pad1->PCTempKeyState.LeftStickX = 128;
			if ( c == 'A' ) pad1->PCTempKeyState.LeftStickX = -128;
			if ( c == 'W' ) pad1->PCTempKeyState.LeftStickY = 128;
			if ( c == 'S' ) pad1->PCTempKeyState.LeftStickY = -128;
			if ( c == 'J' ) pad1->PCTempKeyState.RightStickX = 128;
			if ( c == 'G' ) pad1->PCTempKeyState.RightStickX = -128;
			if ( c == 'Y' ) pad1->PCTempKeyState.RightStickY = 128;
			if ( c == 'H' ) pad1->PCTempKeyState.RightStickY = -128;
			if ( c == 'Z' ) pad1->PCTempKeyState.LeftShoulder1 = 255;
			if ( c == 'X' ) pad1->PCTempKeyState.LeftShoulder2 = 255;
			if ( c == 'C' ) pad1->PCTempKeyState.RightShoulder1 = 255;
			if ( c == 'V' ) pad1->PCTempKeyState.RightShoulder2 = 255;
			if ( c == 'O' ) pad1->PCTempKeyState.DPadUp = 255;
			if ( c == 'L' ) pad1->PCTempKeyState.DPadDown = 255;
			if ( c == 'K' ) pad1->PCTempKeyState.DPadLeft = 255;
			if ( c == ';' ) pad1->PCTempKeyState.DPadRight = 255;
			if ( c == 'B' ) pad1->PCTempKeyState.Start = 255;
			if ( c == 'N' ) pad1->PCTempKeyState.Select = 255;
			if ( c == 'M' ) pad1->PCTempKeyState.Square = 255;
			if ( c == ',' ) pad1->PCTempKeyState.Triangle = 255;
			if ( c == '.' ) pad1->PCTempKeyState.Cross = 255;
			if ( c == '/' ) pad1->PCTempKeyState.Circle = 255;
			if ( c == rsRSHIFT ) pad1->PCTempKeyState.LeftShock = 255;
			if ( c == rsRCTRL ) pad1->PCTempKeyState.RightShock = 255;
		}
	}

	return rsEVENTPROCESSED;
}

static RsEventStatus
HandleKeyUp(RsKeyStatus *keyStatus)
{
	CPad *pad0 = CPad::GetPad(0);
	CPad *pad1 = CPad::GetPad(1);

	RwInt32 c = keyStatus->keyCharCode;

	if ( c != rsNULL )
	{
		switch (c)
		{
			case rsESC:
				{
					CPad::TempKeyState.ESC = 0;
					break;
				}

			case rsINS:
				{
					CPad::TempKeyState.INS = 0;
					break;
				}

			case rsDEL:
				{
					CPad::TempKeyState.DEL = 0;
					break;
				}

			case rsHOME:
				{
					CPad::TempKeyState.HOME = 0;
					break;
				}

			case rsEND:
				{
					CPad::TempKeyState.END = 0;
					break;
				}

			case rsPGUP:
				{
					CPad::TempKeyState.PGUP = 0;
					break;
				}

			case rsPGDN:
				{
					CPad::TempKeyState.PGDN = 0;
					break;
				}

			case rsUP:
				{
					CPad::TempKeyState.UP = 0;
					break;
				}

			case rsDOWN:
				{
					CPad::TempKeyState.DOWN = 0;
					break;
				}

			case rsLEFT:
				{
					CPad::TempKeyState.LEFT = 0;
					break;
				}

			case rsRIGHT:
				{
					CPad::TempKeyState.RIGHT = 0;
					break;
				}

			case rsNUMLOCK:
				{
					CPad::TempKeyState.NUMLOCK = 0;
					break;
				}

			case rsPADDEL:
				{
					CPad::TempKeyState.DECIMAL = 0;
					break;
				}

			case rsPADEND:
				{
					CPad::TempKeyState.NUM1 = 0;
					break;
				}

			case rsPADDOWN:
				{
					CPad::TempKeyState.NUM2 = 0;
					break;
				}

			case rsPADPGDN:
				{
					CPad::TempKeyState.NUM3 = 0;
					break;
				}

			case rsPADLEFT:
				{
					CPad::TempKeyState.NUM4 = 0;
					break;
				}

			case rsPAD5:
				{
					CPad::TempKeyState.NUM5 = 0;
					break;
				}

			case rsPADRIGHT:
				{
					CPad::TempKeyState.NUM6 = 0;
					break;
				}

			case rsPADHOME:
				{
					CPad::TempKeyState.NUM7 = 0;
					break;
				}

			case rsPADUP:
				{
					CPad::TempKeyState.NUM8 = 0;
					break;
				}

			case rsPADPGUP:
				{
					CPad::TempKeyState.NUM9 = 0;
					break;
				}

			case rsPADINS:
				{
					CPad::TempKeyState.NUM0 = 0;
					break;
				}

			case rsDIVIDE:
				{
					CPad::TempKeyState.DIV = 0;
					break;
				}

			case rsTIMES:
				{
					CPad::TempKeyState.MUL = 0;
					break;
				}

			case rsMINUS:
				{
					CPad::TempKeyState.SUB = 0;
					break;
				}

			case rsPADENTER:
				{
					CPad::TempKeyState.ENTER = 0;
					break;
				}

			case rsPLUS:
				{
					CPad::TempKeyState.ADD = 0;
					break;
				}

			case rsENTER:
				{
					CPad::TempKeyState.EXTENTER = 0;
					break;
				}

			case rsSCROLL:
				{
					CPad::TempKeyState.SCROLLLOCK = 0;
					break;
				}

			case rsPAUSE:
				{
					CPad::TempKeyState.PAUSE = 0;
					break;
				}

			case rsBACKSP:
				{
					CPad::TempKeyState.BACKSP = 0;
					break;
				}

			case rsTAB:
				{
					CPad::TempKeyState.TAB = 0;
					break;
				}

			case rsCAPSLK:
				{
					CPad::TempKeyState.CAPSLOCK = 0;
					break;
				}

			case rsLSHIFT:
				{
					CPad::TempKeyState.LSHIFT = 0;
					break;
				}

			case rsSHIFT:
				{
					CPad::TempKeyState.SHIFT = 0;
					break;
				}

			case rsRSHIFT:
				{
					CPad::TempKeyState.RSHIFT = 0;
					break;
				}

			case rsLCTRL:
				{
					CPad::TempKeyState.LCTRL = 0;
					break;
				}

			case rsRCTRL:
				{
					CPad::TempKeyState.RCTRL = 0;
					break;
				}

			case rsLALT:
				{
					CPad::TempKeyState.LALT = 0;
					break;
				}

			case rsRALT:
				{
					CPad::TempKeyState.RALT = 0;
					break;
				}

			case rsLWIN:
				{
					CPad::TempKeyState.LWIN = 0;
					break;
				}

			case rsRWIN:
				{
					CPad::TempKeyState.RWIN = 0;
					break;
				}

			case rsAPPS:
				{
					CPad::TempKeyState.APPS = 0;
					break;
				}

			case rsF1:
			case rsF2:
			case rsF3:
			case rsF4:
			case rsF5:
			case rsF6:
			case rsF7:
			case rsF8:
			case rsF9:
			case rsF10:
			case rsF11:
			case rsF12:
				{
					CPad::TempKeyState.F[c - rsF1] = 0;
					break;
				}

			default:
				{
					if ( c < 255 )
					{
						CPad::TempKeyState.VK_KEYS[c] = 0;
					}
					break;
				}
		}

		if ( CPad::m_bMapPadOneToPadTwo )
		{
			if ( c == 'D' ) pad1->PCTempKeyState.LeftStickX = 0;
			if ( c == 'A' ) pad1->PCTempKeyState.LeftStickX = 0;
			if ( c == 'W' ) pad1->PCTempKeyState.LeftStickY = 0;
			if ( c == 'S' ) pad1->PCTempKeyState.LeftStickY = 0;
			if ( c == 'J' ) pad1->PCTempKeyState.RightStickX = 0;
			if ( c == 'G' ) pad1->PCTempKeyState.RightStickX = 0;
			if ( c == 'Y' ) pad1->PCTempKeyState.RightStickY = 0;
			if ( c == 'H' ) pad1->PCTempKeyState.RightStickY = 0;
			if ( c == 'Z' ) pad1->PCTempKeyState.LeftShoulder1 = 0;
			if ( c == 'X' ) pad1->PCTempKeyState.LeftShoulder2 = 0;
			if ( c == 'C' ) pad1->PCTempKeyState.RightShoulder1 = 0;
			if ( c == 'V' ) pad1->PCTempKeyState.RightShoulder2 = 0;
			if ( c == 'O' ) pad1->PCTempKeyState.DPadUp = 0;
			if ( c == 'L' ) pad1->PCTempKeyState.DPadDown = 0;
			if ( c == 'K' ) pad1->PCTempKeyState.DPadLeft = 0;
			if ( c == ';' ) pad1->PCTempKeyState.DPadRight = 0;
			if ( c == 'B' ) pad1->PCTempKeyState.Start = 0;
			if ( c == 'N' ) pad1->PCTempKeyState.Select = 0;
			if ( c == 'M' ) pad1->PCTempKeyState.Square = 0;
			if ( c == ',' ) pad1->PCTempKeyState.Triangle = 0;
			if ( c == '.' ) pad1->PCTempKeyState.Cross = 0;
			if ( c == '/' ) pad1->PCTempKeyState.Circle = 0;
			if ( c == rsRSHIFT ) pad1->PCTempKeyState.LeftShock = 0;
			if ( c == rsRCTRL ) pad1->PCTempKeyState.RightShock = 0;
		}
	}

	return rsEVENTPROCESSED;
}

/*
 *****************************************************************************
 */
static RsEventStatus
KeyboardHandler(RsEvent event, void *param)
{
	/*
	 * ...then the application events, if necessary...
	 */
	switch( event )
	{
		case rsKEYDOWN:
		{
			return HandleKeyDown((RsKeyStatus *)param);
		}

		case rsKEYUP:
		{
			return HandleKeyUp((RsKeyStatus *)param);
		}

		default:
		{
			return rsEVENTNOTPROCESSED;
		}
	}
}

/*
 *****************************************************************************
 */
static RsEventStatus
HandlePadButtonDown(RsPadButtonStatus *padButtonStatus)
{
	bool bPadTwo = false;
	int32 padNumber = padButtonStatus->padID;

	CPad *pad = CPad::GetPad(padNumber);

	if ( CPad::m_bMapPadOneToPadTwo )
		padNumber = 1;

	if ( padNumber == 1 )
		bPadTwo = true;

	ControlsManager.UpdateJoyButtonState(padNumber);

	for ( int32 i = 0; i < _TODOCONST(16); i++ )
	{
		RsPadButtons btn = rsPADNULL;
		if ( ControlsManager.m_aButtonStates[i] == TRUE )
			btn = (RsPadButtons)(i + 1);

		if ( FrontEndMenuManager.m_bMenuActive || bPadTwo )
			ControlsManager.UpdateJoyInConfigMenus_ButtonDown(btn, padNumber);
		else
			ControlsManager.AffectControllerStateOn_ButtonDown(btn, JOYSTICK);
	}

	return rsEVENTPROCESSED;
}

/*
 *****************************************************************************
 */
static RsEventStatus
HandlePadButtonUp(RsPadButtonStatus *padButtonStatus)
{
	bool bPadTwo = false;
	int32 padNumber = padButtonStatus->padID;

	CPad *pad = CPad::GetPad(padNumber);

	if ( CPad::m_bMapPadOneToPadTwo )
		padNumber = 1;

	if ( padNumber == 1 )
		bPadTwo = true;

	bool bCam = false;
	int16 mode = TheCamera.Cams[TheCamera.ActiveCam].Mode;
	if ( mode == CCam::MODE_FLYBY || mode == CCam::MODE_FIXED )
		bCam = true;

	ControlsManager.UpdateJoyButtonState(padNumber);

	for ( int32 i = 1; i < _TODOCONST(16); i++ )
	{
		RsPadButtons btn = rsPADNULL;
		if ( ControlsManager.m_aButtonStates[i] == FALSE )
			btn = (RsPadButtons)(i + 1); // bug ?, cycle begins from 1(not zero), 1+1==2==rsPADBUTTON2, so we skip rsPADBUTTON1, right ?

		if ( FrontEndMenuManager.m_bMenuActive || bPadTwo || bCam )
			ControlsManager.UpdateJoyInConfigMenus_ButtonUp(btn, padNumber);
		else
			ControlsManager.AffectControllerStateOn_ButtonUp(btn, JOYSTICK);
	}

	return rsEVENTPROCESSED;
}

/*
 *****************************************************************************
 */
static RsEventStatus
PadHandler(RsEvent event, void *param)
{
	switch( event )
	{
		case rsPADBUTTONDOWN:
		{
			return HandlePadButtonDown((RsPadButtonStatus *)param);
		}

		case rsPADBUTTONUP:
		{
			return HandlePadButtonUp((RsPadButtonStatus *)param);
		}

		default:
		{
			return rsEVENTNOTPROCESSED;
		}
	}
}

/*
 *****************************************************************************
 */
RwBool
AttachInputDevices(void)
{
#ifndef IGNORE_MOUSE_KEYBOARD
	RsInputDeviceAttach(rsKEYBOARD, KeyboardHandler);
#endif

	RsInputDeviceAttach(rsPAD, PadHandler);

	return TRUE;
}

license: mit


code:
#ifndef EVENTS_H
#define EVENTS_H

#include <rwcore.h>
#include "skeleton.h"

#endif /* EVENTS_H */


code:
#if defined RW_GL3 && !defined LIBRW_SDL2

#ifdef _WIN32
#include <shlobj.h>
#include <basetsd.h>
#include <mmsystem.h>
#include <regstr.h>
#include <shellapi.h>
#include <windowsx.h>

DWORD _dwOperatingSystemVersion;
#include "resource.h"
#else
long _dwOperatingSystemVersion;
#ifndef __SWITCH__
#ifndef __APPLE__
#include <sys/sysinfo.h>
#else
#include <mach/mach_host.h>
#include <sys/sysctl.h>
#endif
#endif
#include <errno.h>
#include <locale.h>
#include <signal.h>
#include <stddef.h>
#endif

#include "common.h"
#if (defined(_MSC_VER))
#include <tchar.h>
#endif /* (defined(_MSC_VER)) */
#include <stdio.h>
#include "rwcore.h"
#include "skeleton.h"
#include "platform.h"
#include "crossplatform.h"

#include "main.h"
#include "FileMgr.h"
#include "Text.h"
#include "Pad.h"
#include "Timer.h"
#include "DMAudio.h"
#include "ControllerConfig.h"
#include "Frontend.h"
#include "Game.h"
#include "PCSave.h"
#include "MemoryCard.h"
#include "Sprite2d.h"
#include "AnimViewer.h"
#include "Font.h"
#include "MemoryMgr.h"

// This is defined on project-level, via premake5 or cmake
#ifdef GET_KEYBOARD_INPUT_FROM_X11
#include <X11/Xlib.h>
#include <X11/XKBlib.h>
#define GLFW_EXPOSE_NATIVE_X11
#include <GLFW/glfw3native.h>
#endif

#ifdef _WIN32
#define GLFW_EXPOSE_NATIVE_WIN32
#include <GLFW/glfw3native.h>
#endif

#define MAX_SUBSYSTEMS		(16)

rw::EngineOpenParams openParams;

static RwBool		  ForegroundApp = TRUE;
static RwBool		  WindowIconified = FALSE;
static RwBool		  WindowFocused = TRUE;

static RwBool		  RwInitialised = FALSE;

static RwSubSystemInfo GsubSysInfo[MAX_SUBSYSTEMS];
static RwInt32		GnumSubSystems = 0;
static RwInt32		GcurSel = 0, GcurSelVM = 0;

static RwBool useDefault;

// What is that for anyway?
#ifndef IMPROVED_VIDEOMODE
static RwBool defaultFullscreenRes = TRUE;
#else
static RwBool defaultFullscreenRes = FALSE;
static RwInt32 bestWndMode = -1;
#endif

static psGlobalType PsGlobal;

#define PSGLOBAL(var) (((psGlobalType *)(RsGlobal.ps))->var)

size_t _dwMemAvailPhys;
RwUInt32 gGameState;

#ifdef DETECT_JOYSTICK_MENU
char gSelectedJoystickName[128] = "";
#endif

/*
 *****************************************************************************
 */
void _psCreateFolder(const char *path)
{
#ifdef _WIN32
	HANDLE hfle = CreateFile(path, GENERIC_READ,
									FILE_SHARE_READ,
									nil,
									OPEN_EXISTING,
									FILE_FLAG_BACKUP_SEMANTICS | FILE_ATTRIBUTE_NORMAL,
									nil);

	if ( hfle == INVALID_HANDLE_VALUE )
		CreateDirectory(path, nil);
	else
		CloseHandle(hfle);
#else
	struct stat info;
	char fullpath[PATH_MAX];
	realpath(path, fullpath);

	if (lstat(fullpath, &info) != 0) {
		if (errno == ENOENT || (errno != EACCES && !S_ISDIR(info.st_mode))) {
			mkdir(fullpath, 0755);
		}
	}
#endif
}

/*
 *****************************************************************************
 */
const char *_psGetUserFilesFolder()
{
#if defined USE_MY_DOCUMENTS && defined _WIN32
	HKEY hKey = NULL;

	static CHAR szUserFiles[256];

	if ( RegOpenKeyEx(HKEY_CURRENT_USER,
						REGSTR_PATH_SPECIAL_FOLDERS,
						REG_OPTION_RESERVED,
						KEY_READ,
						&hKey) == ERROR_SUCCESS )
	{
		DWORD KeyType;
		DWORD KeycbData = sizeof(szUserFiles);
		if ( RegQueryValueEx(hKey,
							"Personal",
							NULL,
							&KeyType,
							(LPBYTE)szUserFiles,
							&KeycbData) == ERROR_SUCCESS )
		{
			RegCloseKey(hKey);
			strcat(szUserFiles, "\\GTA3 User Files");
			_psCreateFolder(szUserFiles);
			return szUserFiles;
		}

		RegCloseKey(hKey);
	}

	strcpy(szUserFiles, "data");
	return szUserFiles;
#else
	static char szUserFiles[256];
	strcpy(szUserFiles, "userfiles");
	_psCreateFolder(szUserFiles);
	return szUserFiles;
#endif
}

/*
 *****************************************************************************
 */
RwBool
psCameraBeginUpdate(RwCamera *camera)
{
	if ( !RwCameraBeginUpdate(Scene.camera) )
	{
		ForegroundApp = FALSE;
		RsEventHandler(rsACTIVATE, (void *)FALSE);
		return FALSE;
	}

	return TRUE;
}

/*
 *****************************************************************************
 */
void
psCameraShowRaster(RwCamera *camera)
{
	if (CMenuManager::m_PrefsVsync)
		RwCameraShowRaster(camera, PSGLOBAL(window), rwRASTERFLIPWAITVSYNC);
	else
		RwCameraShowRaster(camera, PSGLOBAL(window), rwRASTERFLIPDONTWAIT);

	return;
}

/*
 *****************************************************************************
 */
RwImage *
psGrabScreen(RwCamera *pCamera)
{
#ifndef LIBRW
	RwRaster *pRaster = RwCameraGetRaster(pCamera);
	if (RwImage *pImage = RwImageCreate(pRaster->width, pRaster->height, 32)) {
		RwImageAllocatePixels(pImage);
		RwImageSetFromRaster(pImage, pRaster);
		return pImage;
	}
#else
	rw::Image *image = RwCameraGetRaster(pCamera)->toImage();
	image->removeMask();
	if(image)
		return image;
#endif
	return nil;
}

/*
 *****************************************************************************
 */
#ifdef _WIN32
#pragma comment( lib, "Winmm.lib" ) // Needed for time
RwUInt32
psTimer(void)
{
	RwUInt32 time;

	TIMECAPS TimeCaps;

	timeGetDevCaps(&TimeCaps, sizeof(TIMECAPS));

	timeBeginPeriod(TimeCaps.wPeriodMin);

	time = (RwUInt32) timeGetTime();

	timeEndPeriod(TimeCaps.wPeriodMin);

	return time;
}
#else
double
psTimer(void)
{
	struct timespec start;
#if defined(CLOCK_MONOTONIC_RAW)
	clock_gettime(CLOCK_MONOTONIC_RAW, &start);
#elif defined(CLOCK_MONOTONIC_FAST)
	clock_gettime(CLOCK_MONOTONIC_FAST, &start);
#else
	clock_gettime(CLOCK_MONOTONIC, &start);
#endif
	return start.tv_sec * 1000.0 + start.tv_nsec/1000000.0;
}
#endif

/*
 *****************************************************************************
 */
void
psMouseSetPos(RwV2d *pos)
{
	glfwSetCursorPos(PSGLOBAL(window), pos->x, pos->y);

	PSGLOBAL(lastMousePos.x) = (RwInt32)pos->x;

	PSGLOBAL(lastMousePos.y) = (RwInt32)pos->y;

	return;
}

/*
 *****************************************************************************
 */
RwMemoryFunctions*
psGetMemoryFunctions(void)
{
#ifdef USE_CUSTOM_ALLOCATOR
	return &memFuncs;
#else
	return nil;
#endif
}

/*
 *****************************************************************************
 */
RwBool
psInstallFileSystem(void)
{
	return (TRUE);
}

/*
 *****************************************************************************
 */
RwBool
psNativeTextureSupport(void)
{
	return true;
}

/*
 *****************************************************************************
 */
#ifdef UNDER_CE
#define CMDSTR	LPWSTR
#else
#define CMDSTR	LPSTR
#endif

/*
 *****************************************************************************
 */

#ifdef __SWITCH__

static HidVibrationValue SwitchVibrationValues[2];
static HidVibrationDeviceHandle SwitchVibrationDeviceHandles[2][2];
static HidVibrationDeviceHandle SwitchVibrationDeviceGC;

static PadState SwitchPad;

static Result HidInitializationResult[2];
static Result HidInitializationGCResult;

static void _psInitializeVibration()
{
	HidInitializationResult[0] = hidInitializeVibrationDevices(SwitchVibrationDeviceHandles[0], 2, HidNpadIdType_Handheld, HidNpadStyleTag_NpadHandheld);
	if(R_FAILED(HidInitializationResult[0])) {
		printf("Failed to initialize VibrationDevice for Handheld Mode\n");
	}
	HidInitializationResult[1] = hidInitializeVibrationDevices(SwitchVibrationDeviceHandles[1], 2, HidNpadIdType_No1, HidNpadStyleSet_NpadFullCtrl);
	if(R_FAILED(HidInitializationResult[1])) {
		printf("Failed to initialize VibrationDevice for Detached Mode\n");
	}
	HidInitializationGCResult = hidInitializeVibrationDevices(&SwitchVibrationDeviceGC, 1, HidNpadIdType_No1, HidNpadStyleTag_NpadGc);
	if(R_FAILED(HidInitializationResult[1])) {
		printf("Failed to initialize VibrationDevice for GC Mode\n");
	}

	SwitchVibrationValues[0].freq_low  = 160.0f;
	SwitchVibrationValues[0].freq_high = 320.0f;

	padConfigureInput(1, HidNpadStyleSet_NpadFullCtrl);
	padInitializeDefault(&SwitchPad);
}

static void _psHandleVibration()
{
	padUpdate(&SwitchPad);

	uint8 target_device = padIsHandheld(&SwitchPad) ? 0 : 1;

	if(R_SUCCEEDED(HidInitializationResult[target_device])) {
		CPad* pad = CPad::GetPad(0);

		// value conversion based on SDL2 switch port
		SwitchVibrationValues[0].amp_high = SwitchVibrationValues[0].amp_low = pad->ShakeFreq == 0 ? 0.0f : 320.0f;
		SwitchVibrationValues[0].freq_low = pad->ShakeFreq == 0.0 ? 160.0f : (float)pad->ShakeFreq * 1.26f;
		SwitchVibrationValues[0].freq_high = pad->ShakeFreq == 0.0 ? 320.0f : (float)pad->ShakeFreq * 1.26f;

		if (pad->ShakeDur < CTimer::GetTimeStepInMilliseconds())
			pad->ShakeDur = 0;
		else
			pad->ShakeDur -= CTimer::GetTimeStepInMilliseconds();
		if (pad->ShakeDur == 0) pad->ShakeFreq = 0;

		if(target_device == 1 && R_SUCCEEDED(HidInitializationGCResult)) {
			// gamecube rumble
			hidSendVibrationGcErmCommand(SwitchVibrationDeviceGC, pad->ShakeFreq > 0 ? HidVibrationGcErmCommand_Start : HidVibrationGcErmCommand_Stop);
		}

		memcpy(&SwitchVibrationValues[1], &SwitchVibrationValues[0], sizeof(HidVibrationValue));
		hidSendVibrationValues(SwitchVibrationDeviceHandles[target_device], SwitchVibrationValues, 2);
	}
}
#else
static void _psInitializeVibration() {}
static void _psHandleVibration() {}
#endif

/*
 *****************************************************************************
 */
RwBool
psInitialize(void)
{
	PsGlobal.lastMousePos.x = PsGlobal.lastMousePos.y = 0.0f;

	RsGlobal.ps = &PsGlobal;

	PsGlobal.fullScreen = FALSE;
	PsGlobal.cursorIsInWindow = FALSE;
	WindowFocused = TRUE;
	WindowIconified = FALSE;

	PsGlobal.joy1id	= -1;
	PsGlobal.joy2id	= -1;

	CFileMgr::Initialise();

#ifdef PS2_MENU
	CPad::Initialise();
	CPad::GetPad(0)->Mode = 0;

	CGame::frenchGame = false;
	CGame::germanGame = false;
	CGame::nastyGame = true;
	CMenuManager::m_PrefsAllowNastyGame = true;

#ifndef _WIN32
	// Mandatory for Linux(Unix? Posix?) to set lang. to environment lang.
	setlocale(LC_ALL, "");

	char *systemLang, *keyboardLang;

	systemLang = setlocale (LC_ALL, NULL);
	keyboardLang = setlocale (LC_CTYPE, NULL);

	short lang;
	lang = !strncmp(systemLang, "fr_",3) ? LANG_FRENCH :
					!strncmp(systemLang, "de_",3) ? LANG_GERMAN :
					!strncmp(systemLang, "en_",3) ? LANG_ENGLISH :
					!strncmp(systemLang, "it_",3) ? LANG_ITALIAN :
					!strncmp(systemLang, "es_",3) ? LANG_SPANISH :
					LANG_OTHER;
#else
	WORD lang	= PRIMARYLANGID(GetSystemDefaultLCID());
#endif

	if ( lang  == LANG_ITALIAN )
		CMenuManager::m_PrefsLanguage = CMenuManager::LANGUAGE_ITALIAN;
	else if ( lang  == LANG_SPANISH )
		CMenuManager::m_PrefsLanguage = CMenuManager::LANGUAGE_SPANISH;
	else if ( lang  == LANG_GERMAN )
	{
		CGame::germanGame = true;
		CGame::nastyGame = false;
		CMenuManager::m_PrefsAllowNastyGame = false;
		CMenuManager::m_PrefsLanguage = CMenuManager::LANGUAGE_GERMAN;
	}
	else if ( lang  == LANG_FRENCH )
	{
		CGame::frenchGame = true;
		CGame::nastyGame = false;
		CMenuManager::m_PrefsAllowNastyGame = false;
		CMenuManager::m_PrefsLanguage = CMenuManager::LANGUAGE_FRENCH;
	}
	else
		CMenuManager::m_PrefsLanguage = CMenuManager::LANGUAGE_AMERICAN;

	FrontEndMenuManager.InitialiseMenuContentsAfterLoadingGame();

	TheMemoryCard.Init();
#else
	C_PcSave::SetSaveDirectory(_psGetUserFilesFolder());

	InitialiseLanguage();

#if GTA_VERSION < GTA3_PC_11
	FrontEndMenuManager.LoadSettings();
#endif

#endif

	_psInitializeVibration();

	gGameState = GS_START_UP;
	TRACE("gGameState = GS_START_UP");
#ifdef _WIN32
	OSVERSIONINFO verInfo;
	verInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);

	GetVersionEx(&verInfo);

	_dwOperatingSystemVersion = OS_WIN95;

	if ( verInfo.dwPlatformId == VER_PLATFORM_WIN32_NT )
	{
		if ( verInfo.dwMajorVersion == 4 )
		{
			debug("Operating System is WinNT\n");
			_dwOperatingSystemVersion = OS_WINNT;
		}
		else if ( verInfo.dwMajorVersion == 5 )
		{
			debug("Operating System is Win2000\n");
			_dwOperatingSystemVersion = OS_WIN2000;
		}
		else if ( verInfo.dwMajorVersion > 5 )
		{
			debug("Operating System is WinXP or greater\n");
			_dwOperatingSystemVersion = OS_WINXP;
		}
	}
	else if ( verInfo.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS )
	{
		if ( verInfo.dwMajorVersion > 4 || verInfo.dwMajorVersion == 4 && verInfo.dwMinorVersion != 0 )
		{
			debug("Operating System is Win98\n");
			_dwOperatingSystemVersion = OS_WIN98;
		}
		else
		{
			debug("Operating System is Win95\n");
			_dwOperatingSystemVersion = OS_WIN95;
		}
	}
#else
	_dwOperatingSystemVersion = OS_WINXP; // To fool other classes
#endif

#ifndef PS2_MENU

#if GTA_VERSION >= GTA3_PC_11
	FrontEndMenuManager.LoadSettings();
#endif

#endif

#ifdef _WIN32
	MEMORYSTATUS memstats;
	GlobalMemoryStatus(&memstats);

	_dwMemAvailPhys = memstats.dwAvailPhys;

	debug("Physical memory size %u\n", memstats.dwTotalPhys);
	debug("Available physical memory %u\n", memstats.dwAvailPhys);
#elif defined (__APPLE__)
	uint64_t size = 0;
	uint64_t page_size = 0;
	size_t uint64_len = sizeof(uint64_t);
	size_t ull_len = sizeof(unsigned long long);
	sysctl((int[]){CTL_HW, HW_PAGESIZE}, 2, &page_size, &ull_len, NULL, 0);
	sysctl((int[]){CTL_HW, HW_MEMSIZE}, 2, &size, &uint64_len, NULL, 0);
	vm_statistics_data_t vm_stat;
	mach_msg_type_number_t count = HOST_VM_INFO_COUNT;
	host_statistics(mach_host_self(), HOST_VM_INFO, (host_info_t)&vm_stat, &count);
	_dwMemAvailPhys = (uint64_t)(vm_stat.free_count * page_size);
	debug("Physical memory size %llu\n", _dwMemAvailPhys);
	debug("Available physical memory %llu\n", size);
#elif defined (__SWITCH__)
	svcGetInfo(&_dwMemAvailPhys, InfoType_UsedMemorySize, CUR_PROCESS_HANDLE, 0);
	debug("Physical memory size %llu\n", _dwMemAvailPhys);
#else
 	struct sysinfo systemInfo;
	sysinfo(&systemInfo);
	_dwMemAvailPhys = systemInfo.freeram;
	debug("Physical memory size %u\n", systemInfo.totalram);
	debug("Available physical memory %u\n", systemInfo.freeram);
#endif

  TheText.Unload();

	return TRUE;
}

/*
 *****************************************************************************
 */
void
psTerminate(void)
{
	return;
}

/*
 *****************************************************************************
 */
static RwChar **_VMList;

RwInt32 _psGetNumVideModes()
{
	return RwEngineGetNumVideoModes();
}

/*
 *****************************************************************************
 */
RwBool _psFreeVideoModeList()
{
	RwInt32 numModes;
	RwInt32 i;

	numModes = _psGetNumVideModes();

	if ( _VMList == nil )
		return TRUE;

	for ( i = 0; i < numModes; i++ )
	{
		RwFree(_VMList[i]);
	}

	RwFree(_VMList);

	_VMList = nil;

	return TRUE;
}

/*
 *****************************************************************************
 */
RwChar **_psGetVideoModeList()
{
	RwInt32 numModes;
	RwInt32 i;

	if ( _VMList != nil )
	{
		return _VMList;
	}

	numModes = RwEngineGetNumVideoModes();

	_VMList = (RwChar **)RwCalloc(numModes, sizeof(RwChar*));

	for ( i = 0; i < numModes; i++	)
	{
		RwVideoMode			vm;

		RwEngineGetVideoModeInfo(&vm, i);

		if ( vm.flags & rwVIDEOMODEEXCLUSIVE )
		{
			_VMList[i] = (RwChar*)RwCalloc(100, sizeof(RwChar));
			rwsprintf(_VMList[i],"%d X %d X %d", vm.width, vm.height, vm.depth);
		}
		else
			_VMList[i] = nil;
	}

	return _VMList;
}

/*
 *****************************************************************************
 */
void _psSelectScreenVM(RwInt32 videoMode)
{
	RwTexDictionarySetCurrent( nil );

	FrontEndMenuManager.UnloadTextures();

	if (!_psSetVideoMode(RwEngineGetCurrentSubSystem(), videoMode))
	{
		RsGlobal.quit = TRUE;

		printf("ERROR: Failed to select new screen resolution\n");
	}
	else
		FrontEndMenuManager.LoadAllTextures();
}

/*
 *****************************************************************************
 */

RwBool IsForegroundApp()
{
	return !!ForegroundApp;
}
/*
UINT GetBestRefreshRate(UINT width, UINT height, UINT depth)
{
	LPDIRECT3D8 d3d = Direct3DCreate8(D3D_SDK_VERSION);

	ASSERT(d3d != nil);

	UINT refreshRate = INT_MAX;
	D3DFORMAT format;

	if ( depth == 32 )
		format = D3DFMT_X8R8G8B8;
	else if ( depth == 24 )
		format = D3DFMT_R8G8B8;
	else
		format = D3DFMT_R5G6B5;

	UINT modeCount = d3d->GetAdapterModeCount(GcurSel);

	for ( UINT i = 0; i < modeCount; i++ )
	{
		D3DDISPLAYMODE mode;

		d3d->EnumAdapterModes(GcurSel, i, &mode);

		if ( mode.Width == width && mode.Height == height && mode.Format == format )
		{
			if ( mode.RefreshRate == 0 )
				return 0;

			if ( mode.RefreshRate < refreshRate && mode.RefreshRate >= 60 )
				refreshRate = mode.RefreshRate;
		}
	}

#ifdef FIX_BUGS
	d3d->Release();
#endif

	if ( refreshRate == -1 )
		return -1;

	return refreshRate;
}
*/
/*
 *****************************************************************************
 */
RwBool
psSelectDevice()
{
	RwVideoMode			vm;
	RwInt32				subSysNum;
	RwInt32				AutoRenderer = 0;

	RwBool modeFound = FALSE;

	if ( !useDefault )
	{
		GnumSubSystems = RwEngineGetNumSubSystems();
		if ( !GnumSubSystems )
		{
			 return FALSE;
		}

		/* Just to be sure ... */
		GnumSubSystems = (GnumSubSystems > MAX_SUBSYSTEMS) ? MAX_SUBSYSTEMS : GnumSubSystems;

		/* Get the names of all the sub systems */
		for (subSysNum = 0; subSysNum < GnumSubSystems; subSysNum++)
		{
			RwEngineGetSubSystemInfo(&GsubSysInfo[subSysNum], subSysNum);
		}

		/* Get the default selection */
		GcurSel = RwEngineGetCurrentSubSystem();
#ifdef IMPROVED_VIDEOMODE
		if(FrontEndMenuManager.m_nPrefsSubsystem < GnumSubSystems)
			GcurSel = FrontEndMenuManager.m_nPrefsSubsystem;
#endif
	}

	/* Set the driver to use the correct sub system */
	if (!RwEngineSetSubSystem(GcurSel))
	{
		return FALSE;
	}

#ifdef IMPROVED_VIDEOMODE
	FrontEndMenuManager.m_nPrefsSubsystem = GcurSel;
#endif

#ifndef IMPROVED_VIDEOMODE
	if ( !useDefault )
	{
		if ( _psGetVideoModeList()[FrontEndMenuManager.m_nDisplayVideoMode] && FrontEndMenuManager.m_nDisplayVideoMode )
		{
			FrontEndMenuManager.m_nPrefsVideoMode = FrontEndMenuManager.m_nDisplayVideoMode;
			GcurSelVM = FrontEndMenuManager.m_nDisplayVideoMode;
		}
		else
		{
#ifdef DEFAULT_NATIVE_RESOLUTION
			// get the native video mode
			HDC hDevice = GetDC(NULL);
			int w = GetDeviceCaps(hDevice, HORZRES);
			int h = GetDeviceCaps(hDevice, VERTRES);
			int d = GetDeviceCaps(hDevice, BITSPIXEL);
#else
			const int w = 640;
			const int h = 480;
			const int d = 16;
#endif
			while ( !modeFound && GcurSelVM < RwEngineGetNumVideoModes() )
			{
				RwEngineGetVideoModeInfo(&vm, GcurSelVM);
				if ( defaultFullscreenRes	&& vm.width	 != w
											|| vm.height != h
											|| vm.depth	 != d
											|| !(vm.flags & rwVIDEOMODEEXCLUSIVE) )
					++GcurSelVM;
				else
					modeFound = TRUE;
			}

			if ( !modeFound )
			{
#ifdef DEFAULT_NATIVE_RESOLUTION
				GcurSelVM = 1;
#else
				printf("WARNING: Cannot find 640x480 video mode, selecting device cancelled\n");
				return FALSE;
#endif
			}
		}
	}
#else
	if ( !useDefault )
	{
		if(FrontEndMenuManager.m_nPrefsWidth == 0 ||
		   FrontEndMenuManager.m_nPrefsHeight == 0 ||
		   FrontEndMenuManager.m_nPrefsDepth == 0){
			// Defaults if nothing specified
			const GLFWvidmode *mode = glfwGetVideoMode(glfwGetPrimaryMonitor());
			FrontEndMenuManager.m_nPrefsWidth = mode->width;
			FrontEndMenuManager.m_nPrefsHeight = mode->height;
			FrontEndMenuManager.m_nPrefsDepth = 32;
			FrontEndMenuManager.m_nPrefsWindowed = 0;
		}

		// Find the videomode that best fits what we got from the settings file
		RwInt32 bestFsMode = -1;
		RwInt32 bestWidth = -1;
		RwInt32 bestHeight = -1;
		RwInt32 bestDepth = -1;
		for(GcurSelVM = 0; GcurSelVM < RwEngineGetNumVideoModes(); GcurSelVM++){
			RwEngineGetVideoModeInfo(&vm, GcurSelVM);

			if (!(vm.flags & rwVIDEOMODEEXCLUSIVE)){
				bestWndMode = GcurSelVM;
			} else {
				// try the largest one that isn't larger than what we wanted
				if(vm.width >= bestWidth && vm.width <= FrontEndMenuManager.m_nPrefsWidth &&
				   vm.height >= bestHeight && vm.height <= FrontEndMenuManager.m_nPrefsHeight &&
				   vm.depth >= bestDepth && vm.depth <= FrontEndMenuManager.m_nPrefsDepth){
					bestWidth = vm.width;
					bestHeight = vm.height;
					bestDepth = vm.depth;
					bestFsMode = GcurSelVM;
				}
			}
		}

		if(bestFsMode < 0){
			printf("WARNING: Cannot find desired video mode, selecting device cancelled\n");
			return FALSE;
		}
		GcurSelVM = bestFsMode;

		FrontEndMenuManager.m_nDisplayVideoMode = GcurSelVM;
		FrontEndMenuManager.m_nPrefsVideoMode = FrontEndMenuManager.m_nDisplayVideoMode;

		FrontEndMenuManager.m_nSelectedScreenMode = FrontEndMenuManager.m_nPrefsWindowed;
	}
#endif

	RwEngineGetVideoModeInfo(&vm, GcurSelVM);

#ifdef IMPROVED_VIDEOMODE
	if (FrontEndMenuManager.m_nPrefsWindowed)
		GcurSelVM = bestWndMode;

	// Now GcurSelVM is 0 but vm has sizes(and fullscreen flag) of the video mode we want, that's why we changed the rwVIDEOMODEEXCLUSIVE conditions below
	FrontEndMenuManager.m_nPrefsWidth = vm.width;
	FrontEndMenuManager.m_nPrefsHeight = vm.height;
	FrontEndMenuManager.m_nPrefsDepth = vm.depth;
#endif

#ifndef PS2_MENU
	FrontEndMenuManager.m_nCurrOption = 0;
#endif

	/* Set up the video mode and set the apps window
	* dimensions to match */
	if (!RwEngineSetVideoMode(GcurSelVM))
	{
		return FALSE;
	}
	/*
	TODO
	if (vm.flags & rwVIDEOMODEEXCLUSIVE)
	{
		debug("%dx%dx%d", vm.width, vm.height, vm.depth);

		UINT refresh = GetBestRefreshRate(vm.width, vm.height, vm.depth);

		if ( refresh != (UINT)-1 )
		{
			debug("refresh %d", refresh);
			RwD3D8EngineSetRefreshRate((RwUInt32)refresh);
		}
	}
	*/
#ifndef IMPROVED_VIDEOMODE
	if (vm.flags & rwVIDEOMODEEXCLUSIVE)
	{
		RsGlobal.maximumWidth = vm.width;
		RsGlobal.maximumHeight = vm.height;
		RsGlobal.width = vm.width;
		RsGlobal.height = vm.height;

		PSGLOBAL(fullScreen) = TRUE;
	}
#else
		RsGlobal.maximumWidth = FrontEndMenuManager.m_nPrefsWidth;
		RsGlobal.maximumHeight = FrontEndMenuManager.m_nPrefsHeight;
		RsGlobal.width = FrontEndMenuManager.m_nPrefsWidth;
		RsGlobal.height = FrontEndMenuManager.m_nPrefsHeight;

		PSGLOBAL(fullScreen) = !FrontEndMenuManager.m_nPrefsWindowed;
#endif

#ifdef MULTISAMPLING
	RwD3D8EngineSetMultiSamplingLevels(1 << FrontEndMenuManager.m_nPrefsMSAALevel);
#endif
	return TRUE;
}

#ifndef GET_KEYBOARD_INPUT_FROM_X11
void keypressCB(GLFWwindow* window, int key, int scancode, int action, int mods);
#endif
void resizeCB(GLFWwindow* window, int width, int height);
void scrollCB(GLFWwindow* window, double xoffset, double yoffset);
void cursorCB(GLFWwindow* window, double xpos, double ypos);
void cursorEnterCB(GLFWwindow* window, int entered);
void windowFocusCB(GLFWwindow* window, int focused);
void windowIconifyCB(GLFWwindow* window, int iconified);
void joysChangeCB(int jid, int event);

bool IsThisJoystickBlacklisted(int i)
{
#ifndef DETECT_JOYSTICK_MENU
	return false;
#else
	if (glfwJoystickIsGamepad(i))
		return false;

	const char* joyname = glfwGetJoystickName(i);

	if (gSelectedJoystickName[0] != '\0' &&
		strncmp(joyname, gSelectedJoystickName, strlen(gSelectedJoystickName)) == 0)
		return false;

	return true;
#endif
}

void _InputInitialiseJoys()
{
	PSGLOBAL(joy1id) = -1;
	PSGLOBAL(joy2id) = -1;

	// Load our gamepad mappings.
#define SDL_GAMEPAD_DB_PATH "gamecontrollerdb.txt"
	FILE *f = fopen(SDL_GAMEPAD_DB_PATH, "rb");
	if (f) {
		fseek(f, 0, SEEK_END);
		size_t fsize = ftell(f);
		fseek(f, 0, SEEK_SET);

		char *db = (char*)malloc(fsize + 1);
		if (fread(db, 1, fsize, f) == fsize) {
			db[fsize] = '\0';

			if (glfwUpdateGamepadMappings(db) == GLFW_FALSE)
				Error("glfwUpdateGamepadMappings didn't succeed, check " SDL_GAMEPAD_DB_PATH ".\n");
		} else
			Error("fread on " SDL_GAMEPAD_DB_PATH " wasn't successful.\n");

		free(db);
		fclose(f);
	} else
		printf("You don't seem to have copied " SDL_GAMEPAD_DB_PATH " file from re3/gamefiles to GTA3 directory. Some gamepads may not be recognized.\n");

#undef SDL_GAMEPAD_DB_PATH

	// But always overwrite it with the one in SDL_GAMECONTROLLERCONFIG.
	char const* EnvControlConfig = getenv("SDL_GAMECONTROLLERCONFIG");
	if (EnvControlConfig != nil) {
		glfwUpdateGamepadMappings(EnvControlConfig);
	}

	for (int i = 0; i <= GLFW_JOYSTICK_LAST; i++) {
		if (glfwJoystickPresent(i) && !IsThisJoystickBlacklisted(i)) {
			if (PSGLOBAL(joy1id) == -1)
				PSGLOBAL(joy1id) = i;
			else if (PSGLOBAL(joy2id) == -1)
				PSGLOBAL(joy2id) = i;
			else
				break;
		}
	}

	if (PSGLOBAL(joy1id) != -1) {
		int count;
		glfwGetJoystickButtons(PSGLOBAL(joy1id), &count);
#ifdef DETECT_JOYSTICK_MENU
		strcpy(gSelectedJoystickName, glfwGetJoystickName(PSGLOBAL(joy1id)));
#endif
		ControlsManager.InitDefaultControlConfigJoyPad(count);
	}
}

long _InputInitialiseMouse()
{
	glfwSetInputMode(PSGLOBAL(window), GLFW_CURSOR, GLFW_CURSOR_HIDDEN);
	return 0;
}

void psPostRWinit(void)
{
	RwVideoMode vm;
	RwEngineGetVideoModeInfo(&vm, GcurSelVM);

	glfwSetFramebufferSizeCallback(PSGLOBAL(window), resizeCB);
#ifndef IGNORE_MOUSE_KEYBOARD
#ifndef GET_KEYBOARD_INPUT_FROM_X11
	glfwSetKeyCallback(PSGLOBAL(window), keypressCB);
#endif
	glfwSetScrollCallback(PSGLOBAL(window), scrollCB);
	glfwSetCursorPosCallback(PSGLOBAL(window), cursorCB);
	glfwSetCursorEnterCallback(PSGLOBAL(window), cursorEnterCB);
#endif
	glfwSetWindowIconifyCallback(PSGLOBAL(window), windowIconifyCB);
	glfwSetWindowFocusCallback(PSGLOBAL(window), windowFocusCB);
	glfwSetJoystickCallback(joysChangeCB);

	_InputInitialiseJoys();
	_InputInitialiseMouse();

	if(!(vm.flags & rwVIDEOMODEEXCLUSIVE))
		glfwSetWindowSize(PSGLOBAL(window), RsGlobal.maximumWidth, RsGlobal.maximumHeight);

	// Make sure all keys are released
	CPad::GetPad(0)->Clear(true);
	CPad::GetPad(1)->Clear(true);
}

/*
 *****************************************************************************
 */
RwBool _psSetVideoMode(RwInt32 subSystem, RwInt32 videoMode)
{
	RwInitialised = FALSE;

	RsEventHandler(rsRWTERMINATE, nil);

	GcurSel = subSystem;
	GcurSelVM = videoMode;

	useDefault = TRUE;

	if ( RsEventHandler(rsRWINITIALIZE, &openParams) == rsEVENTERROR )
		return FALSE;

	RwInitialised = TRUE;
	useDefault = FALSE;

	RwRect r;

	r.x = 0;
	r.y = 0;
	r.w = RsGlobal.maximumWidth;
	r.h = RsGlobal.maximumHeight;

	RsEventHandler(rsCAMERASIZE, &r);

	psPostRWinit();

	return TRUE;
}

/*
 *****************************************************************************
 */
static RwChar **
CommandLineToArgv(RwChar *cmdLine, RwInt32 *argCount)
{
	RwInt32 numArgs = 0;
	RwBool inArg, inString;
	RwInt32 i, len;
	RwChar *res, *str, **aptr;

	len = strlen(cmdLine);

	/*
	 * Count the number of arguments...
	 */
	inString = FALSE;
	inArg = FALSE;

	for(i=0; i<=len; i++)
	{
		if( cmdLine[i] == '"' )
		{
			inString = !inString;
		}

		if( (cmdLine[i] <= ' ' && !inString) || i == len )
		{
			if( inArg )
			{
				inArg = FALSE;

				numArgs++;
			}
		}
		else if( !inArg )
		{
			inArg = TRUE;
		}
	}

	/*
	 * Allocate memory for result...
	 */
	res = (RwChar *)malloc(sizeof(RwChar *) * numArgs + len + 1);
	str = res + sizeof(RwChar *) * numArgs;
	aptr = (RwChar **)res;

	strcpy(str, cmdLine);

	/*
	 * Walk through cmdLine again this time setting pointer to each arg...
	 */
	inArg = FALSE;
	inString = FALSE;

	for(i=0; i<=len; i++)
	{
		if( cmdLine[i] == '"' )
		{
			inString = !inString;
		}

		if( (cmdLine[i] <= ' ' && !inString) || i == len )
		{
			if( inArg )
			{
				if( str[i-1] == '"' )
				{
					str[i-1] = '\0';
				}
				else
				{
					str[i] = '\0';
				}

				inArg = FALSE;
			}
		}
		else if( !inArg && cmdLine[i] != '"' )
		{
			inArg = TRUE;

			*aptr++ = &str[i];
		}
	}

	*argCount = numArgs;

	return (RwChar **)res;
}

/*
 *****************************************************************************
 */
void InitialiseLanguage()
{
#ifndef _WIN32
	// Mandatory for Linux(Unix? Posix?) to set lang. to environment lang.
	setlocale(LC_ALL, "");

	char *systemLang, *keyboardLang;

	systemLang = setlocale (LC_ALL, NULL);
	keyboardLang = setlocale (LC_CTYPE, NULL);

	short primUserLCID, primSystemLCID;
	primUserLCID = primSystemLCID = !strncmp(systemLang, "fr_",3) ? LANG_FRENCH :
					!strncmp(systemLang, "de_",3) ? LANG_GERMAN :
					!strncmp(systemLang, "en_",3) ? LANG_ENGLISH :
					!strncmp(systemLang, "it_",3) ? LANG_ITALIAN :
					!strncmp(systemLang, "es_",3) ? LANG_SPANISH :
					LANG_OTHER;

	short primLayout = !strncmp(keyboardLang, "fr_",3) ? LANG_FRENCH : (!strncmp(keyboardLang, "de_",3) ? LANG_GERMAN : LANG_ENGLISH);

	short subUserLCID, subSystemLCID;
	subUserLCID = subSystemLCID = !strncmp(systemLang, "en_AU",5) ? SUBLANG_ENGLISH_AUS : SUBLANG_OTHER;
	short subLayout = !strncmp(keyboardLang, "en_AU",5) ? SUBLANG_ENGLISH_AUS : SUBLANG_OTHER;

#else
	WORD primUserLCID	= PRIMARYLANGID(GetSystemDefaultLCID());
	WORD primSystemLCID = PRIMARYLANGID(GetUserDefaultLCID());
	WORD primLayout		= PRIMARYLANGID((DWORD)GetKeyboardLayout(0));

	WORD subUserLCID	= SUBLANGID(GetSystemDefaultLCID());
	WORD subSystemLCID	= SUBLANGID(GetUserDefaultLCID());
	WORD subLayout		= SUBLANGID((DWORD)GetKeyboardLayout(0));
#endif
	if (   primUserLCID	  == LANG_GERMAN
		|| primSystemLCID == LANG_GERMAN
		|| primLayout	  == LANG_GERMAN )
	{
		CGame::nastyGame = false;
		CMenuManager::m_PrefsAllowNastyGame = false;
		CGame::germanGame = true;
	}

	if (   primUserLCID	  == LANG_FRENCH
		|| primSystemLCID == LANG_FRENCH
		|| primLayout	  == LANG_FRENCH )
	{
		CGame::nastyGame = false;
		CMenuManager::m_PrefsAllowNastyGame = false;
		CGame::frenchGame = true;
	}

	if (   subUserLCID	 == SUBLANG_ENGLISH_AUS
		|| subSystemLCID == SUBLANG_ENGLISH_AUS
		|| subLayout	 == SUBLANG_ENGLISH_AUS )
		CGame::noProstitutes = true;

#ifdef NASTY_GAME
	CGame::nastyGame = true;
	CMenuManager::m_PrefsAllowNastyGame = true;
	CGame::noProstitutes = false;
#endif

	int32 lang;

	switch ( primSystemLCID )
	{
		case LANG_GERMAN:
		{
			lang = LANG_GERMAN;
			break;
		}
		case LANG_FRENCH:
		{
			lang = LANG_FRENCH;
			break;
		}
		case LANG_SPANISH:
		{
			lang = LANG_SPANISH;
			break;
		}
		case LANG_ITALIAN:
		{
			lang = LANG_ITALIAN;
			break;
		}
		default:
		{
			lang = ( subSystemLCID == SUBLANG_ENGLISH_AUS ) ? -99 : LANG_ENGLISH;
			break;
		}
	}

	CMenuManager::OS_Language = primUserLCID;

	switch ( lang )
	{
		case LANG_GERMAN:
		{
			CMenuManager::m_PrefsLanguage = CMenuManager::LANGUAGE_GERMAN;
			break;
		}
		case LANG_SPANISH:
		{
			CMenuManager::m_PrefsLanguage = CMenuManager::LANGUAGE_SPANISH;
			break;
		}
		case LANG_FRENCH:
		{
			CMenuManager::m_PrefsLanguage = CMenuManager::LANGUAGE_FRENCH;
			break;
		}
		case LANG_ITALIAN:
		{
			CMenuManager::m_PrefsLanguage = CMenuManager::LANGUAGE_ITALIAN;
			break;
		}
		default:
		{
			CMenuManager::m_PrefsLanguage = CMenuManager::LANGUAGE_AMERICAN;
			break;
		}
	}

#ifndef _WIN32
	// TODO this is needed for strcasecmp to work correctly across all languages, but can these cause other problems??
	setlocale(LC_CTYPE, "C");
	setlocale(LC_COLLATE, "C");
	setlocale(LC_NUMERIC, "C");
#endif

	TheText.Unload();
	TheText.Load();
}

/*
 *****************************************************************************
 */

void HandleExit()
{
#ifdef _WIN32
	MSG message;
	while ( PeekMessage(&message, nil, 0U, 0U, PM_REMOVE|PM_NOYIELD) )
	{
		if( message.message == WM_QUIT )
		{
			RsGlobal.quit = TRUE;
		}
		else
		{
			TranslateMessage(&message);
			DispatchMessage(&message);
		}
	}
#else
	// We now handle terminate message always, why handle on some cases?
	return;
#endif
}

#ifndef _WIN32
void terminateHandler(int sig, siginfo_t *info, void *ucontext) {
	RsGlobal.quit = TRUE;
}

#ifdef FLUSHABLE_STREAMING
void dummyHandler(int sig){
	// Don't kill the app pls
}
#endif
#endif

void resizeCB(GLFWwindow* window, int width, int height) {
	/*
	* Handle event to ensure window contents are displayed during re-size
	* as this can be disabled by the user, then if there is not enough
	* memory things don't work.
	*/
	/* redraw window */

	if (RwInitialised && gGameState == GS_PLAYING_GAME)
	{
		RsEventHandler(rsIDLE, (void *)TRUE);
	}

	if (RwInitialised && height > 0 && width > 0) {
		RwRect r;

		// TODO fix artifacts of resizing with mouse
		RsGlobal.maximumHeight = height;
		RsGlobal.maximumWidth = width;

		r.x = 0;
		r.y = 0;
		r.w = width;
		r.h = height;

		RsEventHandler(rsCAMERASIZE, &r);
	}
//	glfwSetWindowPos(window, 0, 0);
}

void scrollCB(GLFWwindow* window, double xoffset, double yoffset) {
	PSGLOBAL(mouseWheel) = yoffset;
}

bool lshiftStatus = false;
bool rshiftStatus = false;

#ifndef GET_KEYBOARD_INPUT_FROM_X11
int keymap[GLFW_KEY_LAST + 1];

static void
initkeymap(void)
{
	int i;
	for (i = 0; i < GLFW_KEY_LAST + 1; i++)
		keymap[i] = rsNULL;

	keymap[GLFW_KEY_SPACE] = ' ';
	keymap[GLFW_KEY_APOSTROPHE] = '\'';
	keymap[GLFW_KEY_COMMA] = ',';
	keymap[GLFW_KEY_MINUS] = '-';
	keymap[GLFW_KEY_PERIOD] = '.';
	keymap[GLFW_KEY_SLASH] = '/';
	keymap[GLFW_KEY_0] = '0';
	keymap[GLFW_KEY_1] = '1';
	keymap[GLFW_KEY_2] = '2';
	keymap[GLFW_KEY_3] = '3';
	keymap[GLFW_KEY_4] = '4';
	keymap[GLFW_KEY_5] = '5';
	keymap[GLFW_KEY_6] = '6';
	keymap[GLFW_KEY_7] = '7';
	keymap[GLFW_KEY_8] = '8';
	keymap[GLFW_KEY_9] = '9';
	keymap[GLFW_KEY_SEMICOLON] = ';';
	keymap[GLFW_KEY_EQUAL] = '=';
	keymap[GLFW_KEY_A] = 'A';
	keymap[GLFW_KEY_B] = 'B';
	keymap[GLFW_KEY_C] = 'C';
	keymap[GLFW_KEY_D] = 'D';
	keymap[GLFW_KEY_E] = 'E';
	keymap[GLFW_KEY_F] = 'F';
	keymap[GLFW_KEY_G] = 'G';
	keymap[GLFW_KEY_H] = 'H';
	keymap[GLFW_KEY_I] = 'I';
	keymap[GLFW_KEY_J] = 'J';
	keymap[GLFW_KEY_K] = 'K';
	keymap[GLFW_KEY_L] = 'L';
	keymap[GLFW_KEY_M] = 'M';
	keymap[GLFW_KEY_N] = 'N';
	keymap[GLFW_KEY_O] = 'O';
	keymap[GLFW_KEY_P] = 'P';
	keymap[GLFW_KEY_Q] = 'Q';
	keymap[GLFW_KEY_R] = 'R';
	keymap[GLFW_KEY_S] = 'S';
	keymap[GLFW_KEY_T] = 'T';
	keymap[GLFW_KEY_U] = 'U';
	keymap[GLFW_KEY_V] = 'V';
	keymap[GLFW_KEY_W] = 'W';
	keymap[GLFW_KEY_X] = 'X';
	keymap[GLFW_KEY_Y] = 'Y';
	keymap[GLFW_KEY_Z] = 'Z';
	keymap[GLFW_KEY_LEFT_BRACKET] = '[';
	keymap[GLFW_KEY_BACKSLASH] = '\\';
	keymap[GLFW_KEY_RIGHT_BRACKET] = ']';
	keymap[GLFW_KEY_GRAVE_ACCENT] = '`';
	keymap[GLFW_KEY_ESCAPE] = rsESC;
	keymap[GLFW_KEY_ENTER] = rsENTER;
	keymap[GLFW_KEY_TAB] = rsTAB;
	keymap[GLFW_KEY_BACKSPACE] = rsBACKSP;
	keymap[GLFW_KEY_INSERT] = rsINS;
	keymap[GLFW_KEY_DELETE] = rsDEL;
	keymap[GLFW_KEY_RIGHT] = rsRIGHT;
	keymap[GLFW_KEY_LEFT] = rsLEFT;
	keymap[GLFW_KEY_DOWN] = rsDOWN;
	keymap[GLFW_KEY_UP] = rsUP;
	keymap[GLFW_KEY_PAGE_UP] = rsPGUP;
	keymap[GLFW_KEY_PAGE_DOWN] = rsPGDN;
	keymap[GLFW_KEY_HOME] = rsHOME;
	keymap[GLFW_KEY_END] = rsEND;
	keymap[GLFW_KEY_CAPS_LOCK] = rsCAPSLK;
	keymap[GLFW_KEY_SCROLL_LOCK] = rsSCROLL;
	keymap[GLFW_KEY_NUM_LOCK] = rsNUMLOCK;
	keymap[GLFW_KEY_PRINT_SCREEN] = rsNULL;
	keymap[GLFW_KEY_PAUSE] = rsPAUSE;

	keymap[GLFW_KEY_F1] = rsF1;
	keymap[GLFW_KEY_F2] = rsF2;
	keymap[GLFW_KEY_F3] = rsF3;
	keymap[GLFW_KEY_F4] = rsF4;
	keymap[GLFW_KEY_F5] = rsF5;
	keymap[GLFW_KEY_F6] = rsF6;
	keymap[GLFW_KEY_F7] = rsF7;
	keymap[GLFW_KEY_F8] = rsF8;
	keymap[GLFW_KEY_F9] = rsF9;
	keymap[GLFW_KEY_F10] = rsF10;
	keymap[GLFW_KEY_F11] = rsF11;
	keymap[GLFW_KEY_F12] = rsF12;
	keymap[GLFW_KEY_F13] = rsNULL;
	keymap[GLFW_KEY_F14] = rsNULL;
	keymap[GLFW_KEY_F15] = rsNULL;
	keymap[GLFW_KEY_F16] = rsNULL;
	keymap[GLFW_KEY_F17] = rsNULL;
	keymap[GLFW_KEY_F18] = rsNULL;
	keymap[GLFW_KEY_F19] = rsNULL;
	keymap[GLFW_KEY_F20] = rsNULL;
	keymap[GLFW_KEY_F21] = rsNULL;
	keymap[GLFW_KEY_F22] = rsNULL;
	keymap[GLFW_KEY_F23] = rsNULL;
	keymap[GLFW_KEY_F24] = rsNULL;
	keymap[GLFW_KEY_F25] = rsNULL;
	keymap[GLFW_KEY_KP_0] = rsPADINS;
	keymap[GLFW_KEY_KP_1] = rsPADEND;
	keymap[GLFW_KEY_KP_2] = rsPADDOWN;
	keymap[GLFW_KEY_KP_3] = rsPADPGDN;
	keymap[GLFW_KEY_KP_4] = rsPADLEFT;
	keymap[GLFW_KEY_KP_5] = rsPAD5;
	keymap[GLFW_KEY_KP_6] = rsPADRIGHT;
	keymap[GLFW_KEY_KP_7] = rsPADHOME;
	keymap[GLFW_KEY_KP_8] = rsPADUP;
	keymap[GLFW_KEY_KP_9] = rsPADPGUP;
	keymap[GLFW_KEY_KP_DECIMAL] = rsPADDEL;
	keymap[GLFW_KEY_KP_DIVIDE] = rsDIVIDE;
	keymap[GLFW_KEY_KP_MULTIPLY] = rsTIMES;
	keymap[GLFW_KEY_KP_SUBTRACT] = rsMINUS;
	keymap[GLFW_KEY_KP_ADD] = rsPLUS;
	keymap[GLFW_KEY_KP_ENTER] = rsPADENTER;
	keymap[GLFW_KEY_KP_EQUAL] = rsNULL;
	keymap[GLFW_KEY_LEFT_SHIFT] = rsLSHIFT;
	keymap[GLFW_KEY_LEFT_CONTROL] = rsLCTRL;
	keymap[GLFW_KEY_LEFT_ALT] = rsLALT;
	keymap[GLFW_KEY_LEFT_SUPER] = rsLWIN;
	keymap[GLFW_KEY_RIGHT_SHIFT] = rsRSHIFT;
	keymap[GLFW_KEY_RIGHT_CONTROL] = rsRCTRL;
	keymap[GLFW_KEY_RIGHT_ALT] = rsRALT;
	keymap[GLFW_KEY_RIGHT_SUPER] = rsRWIN;
	keymap[GLFW_KEY_MENU] = rsNULL;
}

void
keypressCB(GLFWwindow* window, int key, int scancode, int action, int mods)
{
	if (key >= 0 && key <= GLFW_KEY_LAST && action != GLFW_REPEAT) {
		RsKeyCodes ks = (RsKeyCodes)keymap[key];

		if (key == GLFW_KEY_LEFT_SHIFT)
			lshiftStatus = action != GLFW_RELEASE;

		if (key == GLFW_KEY_RIGHT_SHIFT)
			rshiftStatus = action != GLFW_RELEASE;

		if (action == GLFW_RELEASE) RsKeyboardEventHandler(rsKEYUP, &ks);
		else if (action == GLFW_PRESS) RsKeyboardEventHandler(rsKEYDOWN, &ks);
	}
}

#else

uint32 keymap[512]; // 256 ascii + 256 KeySyms between 0xff00 - 0xffff
bool keyStates[512];
uint32 keyCodeToKeymapIndex[256]; // cache for physical keys

#define KEY_MAP_OFFSET (0xff00 - 256)
static void
initkeymap(void)
{
	Display *display = glfwGetX11Display();
	int i;

	for (i = 0; i < ARRAY_SIZE(keymap); i++)
		keymap[i] = rsNULL;

	// You can add new ASCII mappings to here freely (but beware that if right hand side of assignment isn't supported on CFont, it'll be blank/won't work on binding screen)
	// Right hand side of assigments should always be uppercase counterpart of character
	keymap[XK_space] = ' ';
	keymap[XK_apostrophe] = '\'';
	keymap[XK_ampersand] = '&';
	keymap[XK_percent] = '%';
	keymap[XK_dollar] = '$';
	keymap[XK_comma] = ',';
	keymap[XK_minus] = '-';
	keymap[XK_period] = '.';
	keymap[XK_slash] = '/';
	keymap[XK_question] = '?';
	keymap[XK_exclam] = '!';
	keymap[XK_quotedbl] = '"';
	keymap[XK_colon] = ':';
	keymap[XK_semicolon] = ';';
	keymap[XK_equal] = '=';
	keymap[XK_bracketleft] = '[';
	keymap[XK_backslash] = '\\';
	keymap[XK_bracketright] = ']';
	keymap[XK_grave] = '`';
	keymap[XK_0] = '0';
	keymap[XK_1] = '1';
	keymap[XK_2] = '2';
	keymap[XK_3] = '3';
	keymap[XK_4] = '4';
	keymap[XK_5] = '5';
	keymap[XK_6] = '6';
	keymap[XK_7] = '7';
	keymap[XK_8] = '8';
	keymap[XK_9] = '9';
	keymap[XK_a] = 'A';
	keymap[XK_b] = 'B';
	keymap[XK_c] = 'C';
	keymap[XK_d] = 'D';
	keymap[XK_e] = 'E';
	keymap[XK_f] = 'F';
	keymap[XK_g] = 'G';
	keymap[XK_h] = 'H';
	keymap[XK_i] = 'I';
	keymap[XK_I] = 'I'; // Turkish I problem
	keymap[XK_j] = 'J';
	keymap[XK_k] = 'K';
	keymap[XK_l] = 'L';
	keymap[XK_m] = 'M';
	keymap[XK_n] = 'N';
	keymap[XK_o] = 'O';
	keymap[XK_p] = 'P';
	keymap[XK_q] = 'Q';
	keymap[XK_r] = 'R';
	keymap[XK_s] = 'S';
	keymap[XK_t] = 'T';
	keymap[XK_u] = 'U';
	keymap[XK_v] = 'V';
	keymap[XK_w] = 'W';
	keymap[XK_x] = 'X';
	keymap[XK_y] = 'Y';
	keymap[XK_z] = 'Z';

	// Some of regional but ASCII characters that GTA supports
	keymap[XK_agrave] = 0x00c0;
	keymap[XK_aacute] = 0x00c1;
	keymap[XK_acircumflex] = 0x00c2;
	keymap[XK_adiaeresis] = 0x00c4;

	keymap[XK_ae] = 0x00c6;

	keymap[XK_egrave] = 0x00c8;
	keymap[XK_eacute] = 0x00c9;
	keymap[XK_ecircumflex] = 0x00ca;
	keymap[XK_ediaeresis] = 0x00cb;

	keymap[XK_igrave] = 0x00cc;
	keymap[XK_iacute] = 0x00cd;
	keymap[XK_icircumflex] = 0x00ce;
	keymap[XK_idiaeresis] = 0x00cf;

	keymap[XK_ccedilla] = 0x00c7;
	keymap[XK_odiaeresis] = 0x00d6;
	keymap[XK_udiaeresis] = 0x00dc;

	// These are 0xff00 - 0xffff range of KeySym's, and subtracting KEY_MAP_OFFSET is needed
	keymap[XK_Escape - KEY_MAP_OFFSET] = rsESC;
	keymap[XK_Return - KEY_MAP_OFFSET] = rsENTER;
	keymap[XK_Tab - KEY_MAP_OFFSET] = rsTAB;
	keymap[XK_BackSpace - KEY_MAP_OFFSET] = rsBACKSP;
	keymap[XK_Insert - KEY_MAP_OFFSET] = rsINS;
	keymap[XK_Delete - KEY_MAP_OFFSET] = rsDEL;
	keymap[XK_Right - KEY_MAP_OFFSET] = rsRIGHT;
	keymap[XK_Left - KEY_MAP_OFFSET] = rsLEFT;
	keymap[XK_Down - KEY_MAP_OFFSET] = rsDOWN;
	keymap[XK_Up - KEY_MAP_OFFSET] = rsUP;
	keymap[XK_Page_Up - KEY_MAP_OFFSET] = rsPGUP;
	keymap[XK_Page_Down - KEY_MAP_OFFSET] = rsPGDN;
	keymap[XK_Home - KEY_MAP_OFFSET] = rsHOME;
	keymap[XK_End - KEY_MAP_OFFSET] = rsEND;
	keymap[XK_Caps_Lock - KEY_MAP_OFFSET] = rsCAPSLK;
	keymap[XK_Scroll_Lock - KEY_MAP_OFFSET] = rsSCROLL;
	keymap[XK_Num_Lock - KEY_MAP_OFFSET] = rsNUMLOCK;
	keymap[XK_Pause - KEY_MAP_OFFSET] = rsPAUSE;

	keymap[XK_F1 - KEY_MAP_OFFSET] = rsF1;
	keymap[XK_F2 - KEY_MAP_OFFSET] = rsF2;
	keymap[XK_F3 - KEY_MAP_OFFSET] = rsF3;
	keymap[XK_F4 - KEY_MAP_OFFSET] = rsF4;
	keymap[XK_F5 - KEY_MAP_OFFSET] = rsF5;
	keymap[XK_F6 - KEY_MAP_OFFSET] = rsF6;
	keymap[XK_F7 - KEY_MAP_OFFSET] = rsF7;
	keymap[XK_F8 - KEY_MAP_OFFSET] = rsF8;
	keymap[XK_F9 - KEY_MAP_OFFSET] = rsF9;
	keymap[XK_F10 - KEY_MAP_OFFSET] = rsF10;
	keymap[XK_F11 - KEY_MAP_OFFSET] = rsF11;
	keymap[XK_F12 - KEY_MAP_OFFSET] = rsF12;
	keymap[XK_F13 - KEY_MAP_OFFSET] = rsNULL;
	keymap[XK_F14 - KEY_MAP_OFFSET] = rsNULL;
	keymap[XK_F15 - KEY_MAP_OFFSET] = rsNULL;
	keymap[XK_F16 - KEY_MAP_OFFSET] = rsNULL;
	keymap[XK_F17 - KEY_MAP_OFFSET] = rsNULL;
	keymap[XK_F18 - KEY_MAP_OFFSET] = rsNULL;
	keymap[XK_F19 - KEY_MAP_OFFSET] = rsNULL;
	keymap[XK_F20 - KEY_MAP_OFFSET] = rsNULL;
	keymap[XK_F21 - KEY_MAP_OFFSET] = rsNULL;
	keymap[XK_F22 - KEY_MAP_OFFSET] = rsNULL;
	keymap[XK_F23 - KEY_MAP_OFFSET] = rsNULL;
	keymap[XK_F24 - KEY_MAP_OFFSET] = rsNULL;
	keymap[XK_F25 - KEY_MAP_OFFSET] = rsNULL;

	keymap[XK_KP_0 - KEY_MAP_OFFSET] = rsPADINS;
	keymap[XK_KP_1 - KEY_MAP_OFFSET] = rsPADEND;
	keymap[XK_KP_2 - KEY_MAP_OFFSET] = rsPADDOWN;
	keymap[XK_KP_3 - KEY_MAP_OFFSET] = rsPADPGDN;
	keymap[XK_KP_4 - KEY_MAP_OFFSET] = rsPADLEFT;
	keymap[XK_KP_5 - KEY_MAP_OFFSET] = rsPAD5;
	keymap[XK_KP_6 - KEY_MAP_OFFSET] = rsPADRIGHT;
	keymap[XK_KP_7 - KEY_MAP_OFFSET] = rsPADHOME;
	keymap[XK_KP_8 - KEY_MAP_OFFSET] = rsPADUP;
	keymap[XK_KP_9 - KEY_MAP_OFFSET] = rsPADPGUP;
	keymap[XK_KP_Insert - KEY_MAP_OFFSET] = rsPADINS;
	keymap[XK_KP_End - KEY_MAP_OFFSET] = rsPADEND;
	keymap[XK_KP_Down - KEY_MAP_OFFSET] = rsPADDOWN;
	keymap[XK_KP_Page_Down - KEY_MAP_OFFSET] = rsPADPGDN;
	keymap[XK_KP_Left - KEY_MAP_OFFSET] = rsPADLEFT;
	keymap[XK_KP_Begin - KEY_MAP_OFFSET] = rsPAD5;
	keymap[XK_KP_Right - KEY_MAP_OFFSET] = rsPADRIGHT;
	keymap[XK_KP_Home - KEY_MAP_OFFSET] = rsPADHOME;
	keymap[XK_KP_Up - KEY_MAP_OFFSET] = rsPADUP;
	keymap[XK_KP_Page_Up - KEY_MAP_OFFSET] = rsPADPGUP;

	keymap[XK_KP_Decimal - KEY_MAP_OFFSET] = rsPADDEL;
	keymap[XK_KP_Divide - KEY_MAP_OFFSET] = rsDIVIDE;
	keymap[XK_KP_Multiply - KEY_MAP_OFFSET] = rsTIMES;
	keymap[XK_KP_Subtract - KEY_MAP_OFFSET] = rsMINUS;
	keymap[XK_KP_Add - KEY_MAP_OFFSET] = rsPLUS;
	keymap[XK_KP_Enter - KEY_MAP_OFFSET] = rsPADENTER;
	keymap[XK_KP_Equal - KEY_MAP_OFFSET] = rsNULL;
	keymap[XK_Shift_L - KEY_MAP_OFFSET] = rsLSHIFT;
	keymap[XK_Control_L - KEY_MAP_OFFSET] = rsLCTRL;
	keymap[XK_Alt_L - KEY_MAP_OFFSET] = rsLALT;
	keymap[XK_Super_L - KEY_MAP_OFFSET] = rsLWIN;
	keymap[XK_Shift_R - KEY_MAP_OFFSET] = rsRSHIFT;
	keymap[XK_Control_R - KEY_MAP_OFFSET] = rsRCTRL;
	keymap[XK_Alt_R - KEY_MAP_OFFSET] = rsRALT;
	keymap[XK_Super_R - KEY_MAP_OFFSET] = rsRWIN;
	keymap[XK_Menu - KEY_MAP_OFFSET] = rsNULL;

	// Cache the key codes' key symbol equivelants, otherwise we will have to do it on each frame
	// KeyCode is always in [0,255], and represents a physical key

	int min_keycode, max_keycode, keysyms_per_keycode;
	KeySym *keymap, *origkeymap;

	char *keyboardLang = setlocale (LC_CTYPE, NULL);
	setlocale(LC_CTYPE, "");

	XDisplayKeycodes(display, &min_keycode, &max_keycode);
	origkeymap = XGetKeyboardMapping(display, min_keycode, (max_keycode - min_keycode + 1), &keysyms_per_keycode);
	keymap = origkeymap;
	for (int i = min_keycode; i <= max_keycode; i++) {
		int  j, lastKeysym;

		lastKeysym = keysyms_per_keycode - 1;
		while ((lastKeysym >= 0) && (keymap[lastKeysym] == NoSymbol))
			lastKeysym--;

		for (j = 0; j <= lastKeysym; j++) {
			KeySym ks = keymap[j];

			if (ks == NoSymbol)
				continue;

			if (ks < 256) {
				keyCodeToKeymapIndex[i] = ks;
				break;
			} else if (ks >= 0xff00 && ks < 0xffff) {
				keyCodeToKeymapIndex[i] = ks - KEY_MAP_OFFSET;
				break;
			}
		}
		keymap += keysyms_per_keycode;
	}
	XFree(origkeymap);

	setlocale(LC_CTYPE, keyboardLang);
}
#undef KEY_MAP_OFFSET

void checkKeyPresses()
{
	Display *display = glfwGetX11Display();
	char keys[32];
	XQueryKeymap(display, keys);
	for (int i = 0; i < sizeof(keys); i++) {
		for (int j = 0; j < 8; j++) {
			KeyCode keycode = 8 * i + j;
			uint32 keymapIndex = keyCodeToKeymapIndex[keycode];
			if (keymapIndex != 0) {
				int rsCode = keymap[keymapIndex];
				if (rsCode == rsNULL)
					continue;

				bool pressed = WindowFocused && !!(keys[i] & (1 << j));

				// idk why R* does that
				if (rsCode == rsLSHIFT)
					lshiftStatus = pressed;
				else if (rsCode == rsRSHIFT)
					rshiftStatus = pressed;

				if (keyStates[keymapIndex] != pressed) {
					if (pressed) {
						RsKeyboardEventHandler(rsKEYDOWN, &rsCode);
					} else {
						RsKeyboardEventHandler(rsKEYUP, &rsCode);
					}
				}

				keyStates[keymapIndex] = pressed;
			}
		}
	}

}
#endif

// R* calls that in ControllerConfig, idk why
void
_InputTranslateShiftKeyUpDown(RsKeyCodes *rs) {
	RsKeyboardEventHandler(lshiftStatus ? rsKEYDOWN : rsKEYUP, &(*rs = rsLSHIFT));
	RsKeyboardEventHandler(rshiftStatus ? rsKEYDOWN : rsKEYUP, &(*rs = rsRSHIFT));
}

// TODO this only works in frontend(and luckily only frontend use this). Fun fact: if I get pos manually in game, glfw reports that it's > 32000
void
cursorCB(GLFWwindow* window, double xpos, double ypos) {
	if (!FrontEndMenuManager.m_bMenuActive)
		return;

	int winw, winh;
	glfwGetWindowSize(PSGLOBAL(window), &winw, &winh);
	FrontEndMenuManager.m_nMouseTempPosX = xpos * (RsGlobal.maximumWidth / winw);
	FrontEndMenuManager.m_nMouseTempPosY = ypos * (RsGlobal.maximumHeight / winh);
}

void
cursorEnterCB(GLFWwindow* window, int entered) {
	PSGLOBAL(cursorIsInWindow) = !!entered;
}

void
windowFocusCB(GLFWwindow* window, int focused) {
	WindowFocused = !!focused;
}

void
windowIconifyCB(GLFWwindow* window, int iconified) {
	WindowIconified = !!iconified;
}

/*
 *****************************************************************************
 */
#ifdef _WIN32
int PASCAL
WinMain(HINSTANCE instance,
	HINSTANCE prevInstance	__RWUNUSED__,
	CMDSTR cmdLine,
	int cmdShow)
{

	RwInt32 argc;
	RwChar** argv;
	SystemParametersInfo(SPI_SETFOREGROUNDLOCKTIMEOUT, 0, nil, SPIF_SENDCHANGE);

#ifndef MASTER
	if (strstr(cmdLine, "-console"))
	{
		AllocConsole();
		freopen("CONIN$", "r", stdin);
		freopen("CONOUT$", "w", stdout);
		freopen("CONOUT$", "w", stderr);
	}
#endif

#else
int
main(int argc, char *argv[])
{
#endif
	RwV2d pos;
	RwInt32 i;

#ifdef USE_CUSTOM_ALLOCATOR
	InitMemoryMgr();
#endif

#if !defined(_WIN32) && !defined(__SWITCH__)
	struct sigaction act;
	act.sa_sigaction = terminateHandler;
	act.sa_flags = SA_SIGINFO;
	sigaction(SIGTERM, &act, NULL);
#ifdef FLUSHABLE_STREAMING
	struct sigaction sa;
	sigemptyset(&sa.sa_mask);
	sa.sa_handler = dummyHandler;
	sa.sa_flags = 0;
	sigaction(SIGUSR1, &sa, NULL);
#endif
#endif

	/*
	 * Initialize the platform independent data.
	 * This will in turn initialize the platform specific data...
	 */
	if( RsEventHandler(rsINITIALIZE, nil) == rsEVENTERROR )
	{
		return FALSE;
	}

#ifdef _WIN32
	/*
	 * Get proper command line params, cmdLine passed to us does not
	 * work properly under all circumstances...
	 */
	cmdLine = GetCommandLine();

	/*
	 * Parse command line into standard (argv, argc) parameters...
	 */
	argv = CommandLineToArgv(cmdLine, &argc);

	/*
	 * Parse command line parameters (except program name) one at
	 * a time BEFORE RenderWare initialization...
	 */
#endif
	for(i=1; i<argc; i++)
	{
		RsEventHandler(rsPREINITCOMMANDLINE, argv[i]);
	}

	/*
	 * Parameters to be used in RwEngineOpen / rsRWINITIALISE event
	 */

	openParams.width = RsGlobal.maximumWidth;
	openParams.height = RsGlobal.maximumHeight;
	openParams.windowtitle = RsGlobal.appName;
	openParams.window = &PSGLOBAL(window);

	ControlsManager.MakeControllerActionsBlank();
	ControlsManager.InitDefaultControlConfiguration();

	/*
	 * Initialize the 3D (RenderWare) components of the app...
	 */
	if( rsEVENTERROR == RsEventHandler(rsRWINITIALIZE, &openParams) )
	{
		RsEventHandler(rsTERMINATE, nil);

		return 0;
	}

#ifdef _WIN32
	HWND wnd = glfwGetWin32Window(PSGLOBAL(window));

	HICON icon = LoadIcon(instance, MAKEINTRESOURCE(IDI_MAIN_ICON));

	SendMessage(wnd, WM_SETICON, ICON_BIG, (LPARAM)icon);
	SendMessage(wnd, WM_SETICON, ICON_SMALL, (LPARAM)icon);
#endif

	psPostRWinit();

	ControlsManager.InitDefaultControlConfigMouse(MousePointerStateHelper.GetMouseSetUp());

//	glfwSetWindowPos(PSGLOBAL(window), 0, 0);

	/*
	 * Parse command line parameters (except program name) one at
	 * a time AFTER RenderWare initialization...
	 */
	for(i=1; i<argc; i++)
	{
		RsEventHandler(rsCOMMANDLINE, argv[i]);
	}

	/*
	 * Force a camera resize event...
	 */
	{
		RwRect r;

		r.x = 0;
		r.y = 0;
		r.w = RsGlobal.maximumWidth;
		r.h = RsGlobal.maximumHeight;

		RsEventHandler(rsCAMERASIZE, &r);
	}
#ifdef _WIN32
	SystemParametersInfo(SPI_SETPOWEROFFACTIVE, FALSE, nil, SPIF_SENDCHANGE);
	SystemParametersInfo(SPI_SETLOWPOWERACTIVE, FALSE, nil, SPIF_SENDCHANGE);

	STICKYKEYS SavedStickyKeys;
	SavedStickyKeys.cbSize = sizeof(STICKYKEYS);

	SystemParametersInfo(SPI_GETSTICKYKEYS, sizeof(STICKYKEYS), &SavedStickyKeys, SPIF_SENDCHANGE);

	STICKYKEYS NewStickyKeys;
	NewStickyKeys.cbSize = sizeof(STICKYKEYS);
	NewStickyKeys.dwFlags = SKF_TWOKEYSOFF;

	SystemParametersInfo(SPI_SETSTICKYKEYS, sizeof(STICKYKEYS), &NewStickyKeys, SPIF_SENDCHANGE);
#endif

	{
		CFileMgr::SetDirMyDocuments();

#ifdef LOAD_INI_SETTINGS
		// At this point InitDefaultControlConfigJoyPad must have set all bindings to default and ms_padButtonsInited to number of detected buttons.
		// We will load stored bindings below, but let's cache ms_padButtonsInited before LoadINIControllerSettings and LoadSettings clears it,
		// so we can add new joy bindings **on top of** stored bindings.
		int connectedPadButtons = ControlsManager.ms_padButtonsInited;
#endif

		int32 gta3set = CFileMgr::OpenFile("gta3.set", "r");

		if ( gta3set )
		{
			ControlsManager.LoadSettings(gta3set);
			CFileMgr::CloseFile(gta3set);
		}

		CFileMgr::SetDir("");

#ifdef LOAD_INI_SETTINGS
		LoadINIControllerSettings();
		if (connectedPadButtons != 0)
			ControlsManager.InitDefaultControlConfigJoyPad(connectedPadButtons); // add (connected-saved) amount of new button assignments on top of ours

		// these have 2 purposes: creating .ini at the start, and adding newly introduced settings to old .ini at the start
		SaveINISettings();
		SaveINIControllerSettings();
#endif
	}

#ifdef _WIN32
	SetErrorMode(SEM_FAILCRITICALERRORS);
#endif

#ifdef PS2_MENU
	int32 r = TheMemoryCard.CheckCardStateAtGameStartUp(CARD_ONE);
	if (   r == CMemoryCard::ERR_DIRNOENTRY  || r == CMemoryCard::ERR_NOFORMAT
		&& r != CMemoryCard::ERR_OPENNOENTRY && r != CMemoryCard::ERR_NONE )
	{
		LoadingScreen(nil, nil, "loadsc0");

		TheText.Unload();
		TheText.Load();

		CFont::Initialise();

		FrontEndMenuManager.DrawMemoryCardStartUpMenus();
	}
#endif

	initkeymap();

	while ( TRUE )
	{
		RwInitialised = TRUE;

		/*
		* Set the initial mouse position...
		*/
		pos.x = RsGlobal.maximumWidth * 0.5f;
		pos.y = RsGlobal.maximumHeight * 0.5f;

		RsMouseSetPos(&pos);

		/*
		* Enter the message processing loop...
		*/

#ifndef MASTER
		if (gbModelViewer) {
			// This is TheModelViewer in LCS, but not compiled on III Mobile.
			LoadingScreen("Loading the ModelViewer", NULL, GetRandomSplashScreen());
			CAnimViewer::Initialise();
			CTimer::Update();
#ifndef PS2_MENU
			FrontEndMenuManager.m_bGameNotLoaded = false;
#endif
		}
#endif

#ifdef PS2_MENU
		if (TheMemoryCard.m_bWantToLoad)
			LoadSplash(GetLevelSplashScreen(CGame::currLevel));

		TheMemoryCard.m_bWantToLoad = false;

		CTimer::Update();

		while( !RsGlobal.quit && !(FrontEndMenuManager.m_bWantToRestart || TheMemoryCard.b_FoundRecentSavedGameWantToLoad) && !glfwWindowShouldClose(PSGLOBAL(window)) )
#else
		while( !RsGlobal.quit && !FrontEndMenuManager.m_bWantToRestart && !glfwWindowShouldClose(PSGLOBAL(window)))
#endif
		{
			glfwPollEvents();
#ifdef GET_KEYBOARD_INPUT_FROM_X11
			checkKeyPresses();
#endif
#ifndef MASTER
			if (gbModelViewer) {
				// This is TheModelViewerCore in LCS, but TheModelViewer on other state-machine III-VCs.
				TheModelViewer();
			} else
#endif
			if ( ForegroundApp )
			{
				switch ( gGameState )
				{
					case GS_START_UP:
					{
#ifdef NO_MOVIES
						gGameState = GS_INIT_ONCE;
#else
						gGameState = GS_INIT_LOGO_MPEG;
#endif
						TRACE("gGameState = GS_INIT_ONCE");
						break;
					}

				    case GS_INIT_LOGO_MPEG:
					{
					    //if (!startupDeactivate)
						//    PlayMovieInWindow(cmdShow, "movies\\Logo.mpg");
					    gGameState = GS_LOGO_MPEG;
					    TRACE("gGameState = GS_LOGO_MPEG;");
					    break;
				    }

				    case GS_LOGO_MPEG:
					{
//					    CPad::UpdatePads();

//					    if (startupDeactivate || ControlsManager.GetJoyButtonJustDown() != 0)
						    ++gGameState;
//					    else if (CPad::GetPad(0)->GetLeftMouseJustDown())
//						    ++gGameState;
//					    else if (CPad::GetPad(0)->GetEnterJustDown())
//						    ++gGameState;
//					    else if (CPad::GetPad(0)->GetCharJustDown(' '))
//						    ++gGameState;
//					    else if (CPad::GetPad(0)->GetAltJustDown())
//						    ++gGameState;
//					    else if (CPad::GetPad(0)->GetTabJustDown())
//						    ++gGameState;

					    break;
				    }

				    case GS_INIT_INTRO_MPEG:
					{
//#ifndef NO_MOVIES
//					    CloseClip();
//					    CoUninitialize();
//#endif
//
//					    if (CMenuManager::OS_Language == LANG_FRENCH || CMenuManager::OS_Language == LANG_GERMAN)
//						    PlayMovieInWindow(cmdShow, "movies\\GTAtitlesGER.mpg");
//					    else
//						    PlayMovieInWindow(cmdShow, "movies\\GTAtitles.mpg");

					    gGameState = GS_INTRO_MPEG;
					    TRACE("gGameState = GS_INTRO_MPEG;");
					    break;
				    }

				    case GS_INTRO_MPEG:
					{
//					    CPad::UpdatePads();
//
//					    if (startupDeactivate || ControlsManager.GetJoyButtonJustDown() != 0)
						    ++gGameState;
//					    else if (CPad::GetPad(0)->GetLeftMouseJustDown())
//						    ++gGameState;
//					    else if (CPad::GetPad(0)->GetEnterJustDown())
//						    ++gGameState;
//					    else if (CPad::GetPad(0)->GetCharJustDown(' '))
//						    ++gGameState;
//					    else if (CPad::GetPad(0)->GetAltJustDown())
//						    ++gGameState;
//					    else if (CPad::GetPad(0)->GetTabJustDown())
//						    ++gGameState;

					    break;
				    }

					case GS_INIT_ONCE:
					{
						//CoUninitialize();

#ifdef PS2_MENU
						extern char version_name[64];
						if ( CGame::frenchGame || CGame::germanGame )
							LoadingScreen(NULL, version_name, "loadsc24");
						else
							LoadingScreen(NULL, version_name, "loadsc0");

						printf("Into TheGame!!!\n");
#else
						LoadingScreen(nil, nil, "loadsc0");
#endif
						if ( !CGame::InitialiseOnceAfterRW() )
							RsGlobal.quit = TRUE;

#ifdef PS2_MENU
						gGameState = GS_INIT_PLAYING_GAME;
#else
						gGameState = GS_INIT_FRONTEND;
						TRACE("gGameState = GS_INIT_FRONTEND;");
#endif
						break;
					}

#ifndef PS2_MENU
					case GS_INIT_FRONTEND:
					{
						LoadingScreen(nil, nil, "loadsc0");

						FrontEndMenuManager.m_bGameNotLoaded = true;

						CMenuManager::m_bStartUpFrontEndRequested = true;

						if ( defaultFullscreenRes )
						{
							defaultFullscreenRes = FALSE;
							FrontEndMenuManager.m_nPrefsVideoMode = GcurSelVM;
							FrontEndMenuManager.m_nDisplayVideoMode = GcurSelVM;
						}

						gGameState = GS_FRONTEND;
						TRACE("gGameState = GS_FRONTEND;");
						break;
					}

					case GS_FRONTEND:
					{
						if(!WindowIconified)
							RsEventHandler(rsFRONTENDIDLE, nil);

#ifdef PS2_MENU
						if ( !FrontEndMenuManager.m_bMenuActive || TheMemoryCard.m_bWantToLoad )
#else
						if ( !FrontEndMenuManager.m_bMenuActive || FrontEndMenuManager.m_bWantToLoad )
#endif
						{
							gGameState = GS_INIT_PLAYING_GAME;
							TRACE("gGameState = GS_INIT_PLAYING_GAME;");
						}

#ifdef PS2_MENU
						if (TheMemoryCard.m_bWantToLoad )
#else
						if ( FrontEndMenuManager.m_bWantToLoad )
#endif
						{
							InitialiseGame();
							FrontEndMenuManager.m_bGameNotLoaded = false;
							gGameState = GS_PLAYING_GAME;
							TRACE("gGameState = GS_PLAYING_GAME;");
						}
						break;
					}
#endif

					case GS_INIT_PLAYING_GAME:
					{
#ifdef PS2_MENU
						CGame::Initialise("DATA\\GTA3.DAT");

						//LoadingScreen("Starting Game", NULL, GetRandomSplashScreen());

						if (   TheMemoryCard.CheckCardInserted(CARD_ONE) == CMemoryCard::NO_ERR_SUCCESS
							&& TheMemoryCard.ChangeDirectory(CARD_ONE, TheMemoryCard.Cards[CARD_ONE].dir)
							&& TheMemoryCard.FindMostRecentFileName(CARD_ONE, TheMemoryCard.MostRecentFile) == true
							&& TheMemoryCard.CheckDataNotCorrupt(TheMemoryCard.MostRecentFile))
						{
							strcpy(TheMemoryCard.LoadFileName, TheMemoryCard.MostRecentFile);
							TheMemoryCard.b_FoundRecentSavedGameWantToLoad = true;

							if (CMenuManager::m_PrefsLanguage != TheMemoryCard.GetLanguageToLoad())
							{
								CMenuManager::m_PrefsLanguage = TheMemoryCard.GetLanguageToLoad();
								TheText.Unload();
								TheText.Load();
							}

							CGame::currLevel = (eLevelName)TheMemoryCard.GetLevelToLoad();
						}
#else
						InitialiseGame();

						FrontEndMenuManager.m_bGameNotLoaded = false;
#endif
						gGameState = GS_PLAYING_GAME;
						TRACE("gGameState = GS_PLAYING_GAME;");
						break;
					}

					case GS_PLAYING_GAME:
					{
						float ms = (float)CTimer::GetCurrentTimeInCycles() / (float)CTimer::GetCyclesPerMillisecond();
						if ( RwInitialised )
						{
							if (!CMenuManager::m_PrefsFrameLimiter || (1000.0f / (float)RsGlobal.maxFPS) < ms)
								RsEventHandler(rsIDLE, (void *)TRUE);
						}
						break;
					}
				}
			}
			else
			{
				if ( RwCameraBeginUpdate(Scene.camera) )
				{
					RwCameraEndUpdate(Scene.camera);
					ForegroundApp = TRUE;
					RsEventHandler(rsACTIVATE, (void *)TRUE);
				}

			}
		}

		/*
		* About to shut down - block resize events again...
		*/
		RwInitialised = FALSE;

		FrontEndMenuManager.UnloadTextures();
#ifdef PS2_MENU
		if ( !(FrontEndMenuManager.m_bWantToRestart || TheMemoryCard.b_FoundRecentSavedGameWantToLoad))
			break;
#else
		if ( !FrontEndMenuManager.m_bWantToRestart )
			break;
#endif

		CPad::ResetCheats();
		CPad::StopPadsShaking();

		DMAudio.ChangeMusicMode(MUSICMODE_DISABLE);

#ifdef PS2_MENU
		CGame::ShutDownForRestart();
#endif

		CTimer::Stop();

#ifdef PS2_MENU
		if (FrontEndMenuManager.m_bWantToRestart || TheMemoryCard.b_FoundRecentSavedGameWantToLoad)
		{
			if (TheMemoryCard.b_FoundRecentSavedGameWantToLoad)
			{
				FrontEndMenuManager.m_bWantToRestart = true;
				TheMemoryCard.m_bWantToLoad = true;
			}

			CGame::InitialiseWhenRestarting();
			DMAudio.ChangeMusicMode(MUSICMODE_GAME);
			FrontEndMenuManager.m_bWantToRestart = false;

			continue;
		}

		CGame::ShutDown();
		CTimer::Stop();

		break;
#else
		if ( FrontEndMenuManager.m_bWantToLoad )
		{
			CGame::ShutDownForRestart();
			CGame::InitialiseWhenRestarting();
			DMAudio.ChangeMusicMode(MUSICMODE_GAME);
			LoadSplash(GetLevelSplashScreen(CGame::currLevel));
			FrontEndMenuManager.m_bWantToLoad = false;
		}
		else
		{
#ifndef MASTER
			if ( gbModelViewer )
				CAnimViewer::Shutdown();
			else
#endif
			if ( gGameState == GS_PLAYING_GAME )
				CGame::ShutDown();

			CTimer::Stop();

			if ( FrontEndMenuManager.m_bFirstTime == true )
			{
				gGameState = GS_INIT_FRONTEND;
				TRACE("gGameState = GS_INIT_FRONTEND;");
			}
			else
			{
				gGameState = GS_INIT_PLAYING_GAME;
				TRACE("gGameState = GS_INIT_PLAYING_GAME;");
			}
		}

		FrontEndMenuManager.m_bFirstTime = false;
		FrontEndMenuManager.m_bWantToRestart = false;
#endif
	}

#ifndef MASTER
	if ( gbModelViewer )
		CAnimViewer::Shutdown();
	else
#endif
	if ( gGameState == GS_PLAYING_GAME )
		CGame::ShutDown();

	DMAudio.Terminate();

	_psFreeVideoModeList();

	/*
	 * Tidy up the 3D (RenderWare) components of the application...
	 */
	RsEventHandler(rsRWTERMINATE, nil);

	/*
	 * Free the platform dependent data...
	 */
	RsEventHandler(rsTERMINATE, nil);

#ifdef _WIN32
	/*
	 * Free the argv strings...
	 */
	free(argv);

	SystemParametersInfo(SPI_SETSTICKYKEYS, sizeof(STICKYKEYS), &SavedStickyKeys, SPIF_SENDCHANGE);
	SystemParametersInfo(SPI_SETPOWEROFFACTIVE, TRUE, nil, SPIF_SENDCHANGE);
	SystemParametersInfo(SPI_SETLOWPOWERACTIVE, TRUE, nil, SPIF_SENDCHANGE);
	SetErrorMode(0);
#endif

	return 0;
}

/*
 *****************************************************************************
 */

RwV2d leftStickPos;
RwV2d rightStickPos;

void CapturePad(RwInt32 padID)
{
	int8 glfwPad = -1;

	if( padID == 0 )
		glfwPad = PSGLOBAL(joy1id);
	else if( padID == 1)
		glfwPad = PSGLOBAL(joy2id);
	else
		assert("invalid padID");

	if ( glfwPad == -1 )
		return;

	int numButtons, numAxes;
	const uint8 *buttons = glfwGetJoystickButtons(glfwPad, &numButtons);
	const float *axes = glfwGetJoystickAxes(glfwPad, &numAxes);
	GLFWgamepadstate gamepadState;

	if (ControlsManager.m_bFirstCapture == false) {
		memcpy(&ControlsManager.m_OldState, &ControlsManager.m_NewState, sizeof(ControlsManager.m_NewState));
	} else {
		// In case connected gamepad doesn't have L-R trigger axes.
		ControlsManager.m_NewState.mappedButtons[15] = ControlsManager.m_NewState.mappedButtons[16] = 0;
	}

	ControlsManager.m_NewState.buttons = (uint8*)buttons;
	ControlsManager.m_NewState.numButtons = numButtons;
	ControlsManager.m_NewState.id = glfwPad;
	ControlsManager.m_NewState.isGamepad = glfwGetGamepadState(glfwPad, &gamepadState);
	if (ControlsManager.m_NewState.isGamepad) {
		memcpy(&ControlsManager.m_NewState.mappedButtons, gamepadState.buttons, sizeof(gamepadState.buttons));
		float lt = gamepadState.axes[GLFW_GAMEPAD_AXIS_LEFT_TRIGGER], rt = gamepadState.axes[GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER];

		// glfw returns 0.0 for non-existent axises(which is bullocks) so we treat it as deadzone, and keep value of previous frame.
		// otherwise if this axis is present, -1 = released, 1 = pressed
		if (lt != 0.0f)
			ControlsManager.m_NewState.mappedButtons[15] = lt > -0.8f;

		if (rt != 0.0f)
			ControlsManager.m_NewState.mappedButtons[16] = rt > -0.8f;
	}
	// TODO? L2-R2 axes(not buttons-that's fine) on joysticks that don't have SDL gamepad mapping AREN'T handled, and I think it's impossible to do without mapping.

	if (ControlsManager.m_bFirstCapture == true) {
		memcpy(&ControlsManager.m_OldState, &ControlsManager.m_NewState, sizeof(ControlsManager.m_NewState));

		ControlsManager.m_bFirstCapture = false;
	}

	RsPadButtonStatus bs;
	bs.padID = padID;

	RsPadEventHandler(rsPADBUTTONUP, (void *)&bs);

	// Gamepad axes are guaranteed to return 0.0f if that particular gamepad doesn't have that axis.
	// And that's really good for sticks, because gamepads return 0.0 for them when sticks are in released state.
	if ( glfwPad != -1 ) {
		leftStickPos.x = ControlsManager.m_NewState.isGamepad ? gamepadState.axes[GLFW_GAMEPAD_AXIS_LEFT_X] : numAxes >= 1 ? axes[0] : 0.0f;
		leftStickPos.y = ControlsManager.m_NewState.isGamepad ? gamepadState.axes[GLFW_GAMEPAD_AXIS_LEFT_Y] : numAxes >= 2 ? axes[1] : 0.0f;

		rightStickPos.x = ControlsManager.m_NewState.isGamepad ? gamepadState.axes[GLFW_GAMEPAD_AXIS_RIGHT_X] : numAxes >= 3 ? axes[2] : 0.0f;
		rightStickPos.y = ControlsManager.m_NewState.isGamepad ? gamepadState.axes[GLFW_GAMEPAD_AXIS_RIGHT_Y] : numAxes >= 4 ? axes[3] : 0.0f;
	}

	{
		if (CPad::m_bMapPadOneToPadTwo)
			bs.padID = 1;

		RsPadEventHandler(rsPADBUTTONUP,   (void *)&bs);
		RsPadEventHandler(rsPADBUTTONDOWN, (void *)&bs);
	}

	{
		if (CPad::m_bMapPadOneToPadTwo)
			bs.padID = 1;

		CPad *pad = CPad::GetPad(bs.padID);

		if ( Abs(leftStickPos.x)  > 0.3f )
			pad->PCTempJoyState.LeftStickX	= (int32)(leftStickPos.x  * 128.0f);

		if ( Abs(leftStickPos.y)  > 0.3f )
			pad->PCTempJoyState.LeftStickY	= (int32)(leftStickPos.y  * 128.0f);

		if ( Abs(rightStickPos.x) > 0.3f )
			pad->PCTempJoyState.RightStickX = (int32)(rightStickPos.x * 128.0f);

		if ( Abs(rightStickPos.y) > 0.3f )
			pad->PCTempJoyState.RightStickY = (int32)(rightStickPos.y * 128.0f);
	}

	_psHandleVibration();

	return;
}

void joysChangeCB(int jid, int event)
{
	if (event == GLFW_CONNECTED && !IsThisJoystickBlacklisted(jid)) {
		if (PSGLOBAL(joy1id) == -1) {
			PSGLOBAL(joy1id) = jid;
#ifdef DETECT_JOYSTICK_MENU
			strcpy(gSelectedJoystickName, glfwGetJoystickName(jid));
#endif
			// This is behind LOAD_INI_SETTINGS, because otherwise the Init call below will destroy/overwrite your bindings.
#ifdef LOAD_INI_SETTINGS
			int count;
			glfwGetJoystickButtons(PSGLOBAL(joy1id), &count);
			ControlsManager.InitDefaultControlConfigJoyPad(count);
#endif
		} else if (PSGLOBAL(joy2id) == -1)
			PSGLOBAL(joy2id) = jid;

	} else if (event == GLFW_DISCONNECTED) {
		if (PSGLOBAL(joy1id) == jid) {
			PSGLOBAL(joy1id) = -1;
		} else if (PSGLOBAL(joy2id) == jid)
			PSGLOBAL(joy2id) = -1;
	}
}

#if (defined(_MSC_VER))
int strcasecmp(const char* str1, const char* str2)
{
	return _strcmpi(str1, str2);
}
#endif
#endif


code:
#ifndef PLATFORM_H
#define PLATFORM_H

// Functions that's different on glfw/win etc. but have same signature (but if a function only used in win.cpp you can keep in win.h)

#include "rwcore.h"
#include "skeleton.h"

#ifdef    __cplusplus
extern "C"
{
#endif                          /* __cplusplus */

#ifdef _WIN32
extern RwUInt32 psTimer(void);
#else
extern double psTimer(void);
#endif

extern RwBool psInitialize(void);
extern void   psTerminate(void);

extern void psCameraShowRaster(RwCamera *camera);
extern RwBool psCameraBeginUpdate(RwCamera *camera);
extern RwImage *psGrabScreen(RwCamera *camera);

extern void psMouseSetPos(RwV2d *pos);

extern RwBool psSelectDevice();

extern RwMemoryFunctions *psGetMemoryFunctions(void);

/* install the platform specific file system */
extern RwBool psInstallFileSystem(void);

/* Handle native texture support */
extern RwBool psNativeTextureSupport(void);

extern void _InputTranslateShiftKeyUpDown(RsKeyCodes* rs);
extern long _InputInitialiseMouse(); // returns HRESULT on Windows actually
extern void _InputInitialiseJoys();

extern void HandleExit();

extern void _psSelectScreenVM(RwInt32 videoMode);

extern void InitialiseLanguage();

extern RwBool _psSetVideoMode(RwInt32 subSystem, RwInt32 videoMode);

extern RwChar** _psGetVideoModeList();

extern RwInt32 _psGetNumVideModes();
#ifdef    __cplusplus
}
#endif                          /* __cplusplus */

#endif /* PLATFORM_H */


code:
#include "common.h"

#include <stdio.h>
#include <math.h>
#include <ctype.h>
#include <string.h>

#include "rwcore.h"

#include "skeleton.h"
#include "platform.h"
#include "main.h"
#include "MemoryHeap.h"

static RwBool               DefaultVideoMode = TRUE;

RsGlobalType                RsGlobal;

#ifdef _WIN32
RwUInt32
#else
double
#endif
RsTimer(void)
{
	return psTimer();
}

/*
 *****************************************************************************
 */
void
RsCameraShowRaster(RwCamera * camera)
{
	psCameraShowRaster(camera);

	return;
}

/*
 *****************************************************************************
 */
RwBool
RsCameraBeginUpdate(RwCamera * camera)
{
	return psCameraBeginUpdate(camera);
}

/*
 *****************************************************************************
 */
RwImage*
RsGrabScreen(RwCamera *camera)
{
	return psGrabScreen(camera);
}

/*
 *****************************************************************************
 */
RwBool
RsRegisterImageLoader(void)
{
	return TRUE;
}

/*
 *****************************************************************************
 */
static              RwBool
RsSetDebug(void)
{
	return TRUE;
}

/*
 *****************************************************************************
 */
void
RsMouseSetPos(RwV2d * pos)
{
	psMouseSetPos(pos);

	return;
}

/*
 *****************************************************************************
 */
RwBool
RsSelectDevice(void)
{
	return psSelectDevice();
}

/*
 *****************************************************************************
 */
RwBool
RsInputDeviceAttach(RsInputDeviceType inputDevice,
					RsInputEventHandler inputEventHandler)
{
	switch (inputDevice)
	{
		case rsKEYBOARD:
			{
				RsGlobal.keyboard.inputEventHandler = inputEventHandler;
				RsGlobal.keyboard.used = TRUE;
				break;
			}
		case rsMOUSE:
			{
				RsGlobal.mouse.inputEventHandler = inputEventHandler;
				RsGlobal.mouse.used = TRUE;
				break;
			}
		case rsPAD:
			{
				RsGlobal.pad.inputEventHandler = inputEventHandler;
				RsGlobal.pad.used = TRUE;
				break;
			}
		default:
			{
				return FALSE;
			}
	}

	return TRUE;
}

/*
 *****************************************************************************
 */
static RwBool
rsCommandLine(RwChar *arg)
{
	RsEventHandler(rsFILELOAD, arg);

	return TRUE;
}

/*
 *****************************************************************************
 */
static RwBool
rsPreInitCommandLine(RwChar *arg)
{
	if( !strcmp(arg, RWSTRING("-vms")) )
	{
		DefaultVideoMode = FALSE;

		return TRUE;
	}
#ifndef MASTER
	if (!strcmp(arg, RWSTRING("-animviewer")))
	{
		gbModelViewer = TRUE;

		return TRUE;
	}
#endif
	return FALSE;
}

/*
 *****************************************************************************
 */
RsEventStatus
RsKeyboardEventHandler(RsEvent event, void *param)
{
	if (RsGlobal.keyboard.used)
	{
		return RsGlobal.keyboard.inputEventHandler(event, param);
	}

	return rsEVENTNOTPROCESSED;
}

/*
 *****************************************************************************
 */
RsEventStatus
RsPadEventHandler(RsEvent event, void *param)
{
	if (RsGlobal.pad.used)
	{
		return RsGlobal.pad.inputEventHandler(event, param);
	}

	return rsEVENTNOTPROCESSED;
}

/*
 *****************************************************************************
 */
RsEventStatus
RsEventHandler(RsEvent event, void *param)
{
	RsEventStatus       result;
	RsEventStatus       es;

	/*
	 * Give the application an opportunity to override any events...
	 */
	es = AppEventHandler(event, param);

	/*
	 * We never allow the app to replace the quit behaviour,
	 * only to intercept...
	 */
	if (event == rsQUITAPP)
	{
		/*
		 * Set the flag which causes the event loop to exit...
		 */
		RsGlobal.quit = TRUE;
	}

	if (es == rsEVENTNOTPROCESSED)
	{
		switch (event)
		{
			case rsSELECTDEVICE:
				result =
					(RsSelectDevice()? rsEVENTPROCESSED : rsEVENTERROR);
				break;

			case rsCOMMANDLINE:
				result = (rsCommandLine((RwChar *) param) ?
						  rsEVENTPROCESSED : rsEVENTERROR);
				break;
			case rsPREINITCOMMANDLINE:
				result = (rsPreInitCommandLine((RwChar *) param) ?
						  rsEVENTPROCESSED : rsEVENTERROR);
				break;
			case rsINITDEBUG:
				result =
					(RsSetDebug()? rsEVENTPROCESSED : rsEVENTERROR);
				break;

			case rsREGISTERIMAGELOADER:
				result = (RsRegisterImageLoader()?
						  rsEVENTPROCESSED : rsEVENTERROR);
				break;

			case rsRWTERMINATE:
				RsRwTerminate();
				result = (rsEVENTPROCESSED);
				break;

			case rsRWINITIALIZE:
				result = (RsRwInitialize(param) ?
						  rsEVENTPROCESSED : rsEVENTERROR);
				break;

			case rsTERMINATE:
				RsTerminate();
				result = (rsEVENTPROCESSED);
				break;

			case rsINITIALIZE:
				result =
					(RsInitialize()? rsEVENTPROCESSED : rsEVENTERROR);
				break;

			default:
				result = (es);
				break;

		}
	}
	else
	{
		result = (es);
	}

	return result;
}

/*
 *****************************************************************************
 */
void
RsRwTerminate(void)
{
	/* Close RenderWare */

	RwEngineStop();
	RwEngineClose();
	RwEngineTerm();

	return;
}

/*
 *****************************************************************************
 */
RwBool
RsRwInitialize(void *displayID)
{
	RwEngineOpenParams  openParams;

	PUSH_MEMID(MEMID_RENDER);	// NB: not popped on failed return

	/*
	 * Start RenderWare...
	 */

	if (!RwEngineInit(psGetMemoryFunctions(), 0, rsRESOURCESDEFAULTARENASIZE))
	{
		return (FALSE);
	}

	/*
	 * Install any platform specific file systems...
	 */
	psInstallFileSystem();

	/*
	 * Initialize debug message handling...
	 */
	RsEventHandler(rsINITDEBUG, nil);

	/*
	 * Attach all plugins...
	 */
	if (RsEventHandler(rsPLUGINATTACH, nil) == rsEVENTERROR)
	{
		return (FALSE);
	}

	/*
	 * Attach input devices...
	 */
	if (RsEventHandler(rsINPUTDEVICEATTACH, nil) == rsEVENTERROR)
	{
		return (FALSE);
	}

	openParams.displayID = displayID;

	if (!RwEngineOpen(&openParams))
	{
		RwEngineTerm();
		return (FALSE);
	}

	if (RsEventHandler(rsSELECTDEVICE, displayID) == rsEVENTERROR)
	{
		RwEngineClose();
		RwEngineTerm();
		return (FALSE);
	}

	if (!RwEngineStart())
	{
		RwEngineClose();
		RwEngineTerm();
		return (FALSE);
	}

	/*
	 * Register loaders for an image with a particular file extension...
	 */
	RsEventHandler(rsREGISTERIMAGELOADER, nil);

	psNativeTextureSupport();

	RwTextureSetMipmapping(FALSE);
	RwTextureSetAutoMipmapping(FALSE);

	POP_MEMID();

	return TRUE;
}

/*
 *****************************************************************************
 */
void
RsTerminate(void)
{
	psTerminate();

	return;
}

/*
 *****************************************************************************
 */
RwBool
RsInitialize(void)
{
	/*
	 * Initialize Platform independent data...
	 */
	RwBool              result;

	RsGlobal.appName = RWSTRING("GTA3");
	RsGlobal.maximumWidth = DEFAULT_SCREEN_WIDTH;
	RsGlobal.maximumHeight = DEFAULT_SCREEN_HEIGHT;
	RsGlobal.width = DEFAULT_SCREEN_WIDTH;
	RsGlobal.height = DEFAULT_SCREEN_HEIGHT;

	RsGlobal.maxFPS = 30;

	RsGlobal.quit = FALSE;

	/* setup the keyboard */
	RsGlobal.keyboard.inputDeviceType = rsKEYBOARD;
	RsGlobal.keyboard.inputEventHandler = nil;
	RsGlobal.keyboard.used = FALSE;

	/* setup the mouse */
	RsGlobal.mouse.inputDeviceType = rsMOUSE;
	RsGlobal.mouse.inputEventHandler = nil;
	RsGlobal.mouse.used = FALSE;

	/* setup the pad */
	RsGlobal.pad.inputDeviceType = rsPAD;
	RsGlobal.pad.inputEventHandler = nil;
	RsGlobal.pad.used = FALSE;

	result = psInitialize();

	return result;
}


code:
#ifndef SKELETON_H
#define SKELETON_H

#include "rwcore.h"

/* Default arena size depending on platform. */
#define rsRESOURCESDEFAULTARENASIZE (1 << 20)

#if (!defined(RsSprintf))
#define RsSprintf rwsprintf
#endif /* (!defined(RsSprintf)) */

#ifdef    __cplusplus
extern "C"
{
#endif                          /* __cplusplus */

#if (!defined(RSASSERT))
#define RSASSERT(_condition) /* No-op */
#endif /* (!defined(RSASSERT)) */

#define RSASSERTISTYPE(_f, _t) \
   RSASSERT( (!(_f)) || ((((const RwObject *)(_f))->type)==(_t)) )

enum RsInputDeviceType
{
	rsKEYBOARD,
	rsMOUSE,
	rsPAD
};
typedef enum RsInputDeviceType RsInputDeviceType;

enum RsEventStatus
{
	rsEVENTERROR,
	rsEVENTPROCESSED,
	rsEVENTNOTPROCESSED
};
typedef enum RsEventStatus RsEventStatus;

enum RsEvent
{
	rsCAMERASIZE,
	rsCOMMANDLINE,
	rsFILELOAD,
	rsINITDEBUG,
	rsINPUTDEVICEATTACH,
	rsLEFTBUTTONDOWN,
	rsLEFTBUTTONUP,
	rsMOUSEMOVE,
	rsMOUSEWHEELMOVE,
	rsPLUGINATTACH,
	rsREGISTERIMAGELOADER,
	rsRIGHTBUTTONDOWN,
	rsRIGHTBUTTONUP,
	_rs_13,
	_rs_14,
	_rs_15,
	_rs_16,
	_rs_17,
	_rs_18,
	_rs_19,
	_rs_20,
	rsRWINITIALIZE,
	rsRWTERMINATE,
	rsSELECTDEVICE,
	rsINITIALIZE,
	rsTERMINATE,
	rsIDLE,
	rsFRONTENDIDLE,
	rsKEYDOWN,
	rsKEYUP,
	rsQUITAPP,
	rsPADBUTTONDOWN,
	rsPADBUTTONUP,
	rsPADANALOGUELEFT,
	rsPADANALOGUELEFTRESET,
	rsPADANALOGUERIGHT,
	rsPADANALOGUERIGHTRESET,
	rsPREINITCOMMANDLINE,
	rsACTIVATE,
};

typedef enum RsEvent RsEvent;

typedef RsEventStatus (*RsInputEventHandler)(RsEvent event, void *param);

typedef struct RsInputDevice RsInputDevice;
struct RsInputDevice
{
	RsInputDeviceType inputDeviceType;
	RwBool used;
	RsInputEventHandler inputEventHandler;
};

typedef struct RsGlobalType RsGlobalType;
struct RsGlobalType
{
	const RwChar *appName;
	RwInt32 width;
	RwInt32 height;
	RwInt32 maximumWidth;
	RwInt32 maximumHeight;
	RwInt32 maxFPS;
	RwBool  quit;

	void   *ps; /* platform specific data */

	RsInputDevice keyboard;
	RsInputDevice mouse;
	RsInputDevice pad;
};

enum RsKeyCodes
{
	rsESC            = 1000,

	rsF1             = 1001,
	rsF2             = 1002,
	rsF3             = 1003,
	rsF4             = 1004,
	rsF5             = 1005,
	rsF6             = 1006,
	rsF7             = 1007,
	rsF8             = 1008,
	rsF9             = 1009,
	rsF10            = 1010,
	rsF11            = 1011,
	rsF12            = 1012,

	rsINS            = 1013,
	rsDEL            = 1014,
	rsHOME           = 1015,
	rsEND            = 1016,
	rsPGUP           = 1017,
	rsPGDN           = 1018,

	rsUP             = 1019,
	rsDOWN           = 1020,
	rsLEFT           = 1021,
	rsRIGHT          = 1022,

	rsDIVIDE         = 1023,
	rsTIMES          = 1024,
	rsPLUS           = 1025,
	rsMINUS          = 1026,
	rsPADDEL         = 1027,
	rsPADEND         = 1028,
	rsPADDOWN        = 1029,
	rsPADPGDN        = 1030,
	rsPADLEFT        = 1031,
	rsPAD5           = 1032,
	rsNUMLOCK        = 1033,
	rsPADRIGHT       = 1034,
	rsPADHOME        = 1035,
	rsPADUP          = 1036,
	rsPADPGUP        = 1037,
	rsPADINS         = 1038,
	rsPADENTER       = 1039,

	rsSCROLL         = 1040,
	rsPAUSE          = 1041,

	rsBACKSP         = 1042,
	rsTAB            = 1043,
	rsCAPSLK         = 1044,
	rsENTER          = 1045,
	rsLSHIFT         = 1046,
	rsRSHIFT         = 1047,
	rsSHIFT          = 1048,
	rsLCTRL          = 1049,
	rsRCTRL          = 1050,
	rsLALT           = 1051,
	rsRALT           = 1052,
	rsLWIN           = 1053,
	rsRWIN           = 1054,
	rsAPPS           = 1055,

	rsNULL           = 1056,

	rsMOUSELEFTBUTTON      = 1,
	rsMOUSMIDDLEBUTTON     = 2,
	rsMOUSERIGHTBUTTON     = 3,
	rsMOUSEWHEELUPBUTTON   = 4,
	rsMOUSEWHEELDOWNBUTTON = 5,
	rsMOUSEX1BUTTON        = 6,
	rsMOUSEX2BUTTON        = 7,
};
typedef enum RsKeyCodes RsKeyCodes;

typedef struct RsKeyStatus RsKeyStatus;
struct RsKeyStatus
{
	RwInt32     keyCharCode;
};

typedef struct RsPadButtonStatus RsPadButtonStatus;
struct RsPadButtonStatus
{
	RwInt32     padID;
};

enum RsPadButtons
{
	rsPADNULL       = 0,

	rsPADBUTTON1    = 1,
	rsPADBUTTON2    = 2,
	rsPADBUTTON3    = 3,
	rsPADBUTTON4    = 4,

	rsPADBUTTON5    = 5,
	rsPADBUTTON6    = 6,
	rsPADBUTTON7    = 7,
	rsPADBUTTON8    = 8,

	rsPADSELECT     = 9,

	rsPADBUTTONA1   = 10,
	rsPADBUTTONA2   = 11,

	rsPADSTART      = 12,

	rsPADDPADUP     = 13,
	rsPADDPADRIGHT  = 14,
	rsPADDPADDOWN   = 15,
	rsPADDPADLEFT   = 16,
};
typedef enum RsPadButtons RsPadButtons;

extern RsGlobalType RsGlobal;

extern RsEventStatus AppEventHandler(RsEvent event, void *param);
extern RwBool        AttachInputDevices(void);

extern RsEventStatus RsEventHandler(RsEvent event, void *param);
extern RsEventStatus RsKeyboardEventHandler(RsEvent event, void *param);
extern RsEventStatus RsPadEventHandler(RsEvent event, void *param);

extern RwBool
RsInitialize(void);

extern RwBool
RsRegisterImageLoader(void);

extern RwBool
RsRwInitialize(void *param);

extern RwBool
RsSelectDevice(void);

extern RwBool
RsInputDeviceAttach(RsInputDeviceType inputDevice,
					RsInputEventHandler inputEventHandler);

#ifdef _WIN32
extern RwUInt32
#else
extern double
#endif
RsTimer(void);

extern void
RsCameraShowRaster(RwCamera *camera);

extern RwBool
RsCameraBeginUpdate(RwCamera *camera);

//TODO
//extern void
//RsMouseSetVisibility(RwBool visible);

extern RwImage*
RsGrabScreen(RwCamera *camera);

extern void
RsMouseSetPos(RwV2d *pos);

extern void
RsRwTerminate(void);

extern void
RsTerminate(void);

#ifdef    __cplusplus
}
#endif                          /* __cplusplus */

#endif /* SKELETON_H */


code:
//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by dungeon.rc
//
#define IDD_DIALOG1                     104
#define IDC_DEVICESEL                   1000
#define IDC_VIDMODE                     1001
#define IDEXIT                          1002
#define IDC_SELECTDEVICE                1005

#define IDI_MAIN_ICON                   1042
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        104
#define _APS_NEXT_COMMAND_VALUE         40001
#define _APS_NEXT_CONTROL_VALUE         1000
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif