summary refs log tree commit diff
path: root/src/Window_GCWii.c
diff options
context:
space:
mode:
authorWlodekM <[email protected]>2024-06-16 10:35:45 +0300
committerWlodekM <[email protected]>2024-06-16 10:35:45 +0300
commitabef6da56913f1c55528103e60a50451a39628b1 (patch)
treeb3c8092471ecbb73e568cd0d336efa0e7871ee8d /src/Window_GCWii.c
initial commit
Diffstat (limited to 'src/Window_GCWii.c')
-rw-r--r--src/Window_GCWii.c554
1 files changed, 554 insertions, 0 deletions
diff --git a/src/Window_GCWii.c b/src/Window_GCWii.c
new file mode 100644
index 0000000..cae2f69
--- /dev/null
+++ b/src/Window_GCWii.c
@@ -0,0 +1,554 @@
+#include "Core.h"
+#if defined CC_BUILD_GCWII
+#include "Window.h"
+#include "Platform.h"
+#include "Input.h"
+#include "Event.h"
+#include "String.h"
+#include "Funcs.h"
+#include "Bitmap.h"
+#include "Errors.h"
+#include "ExtMath.h"
+#include "Graphics.h"
+#include "VirtualKeyboard.h"
+#include <gccore.h>
+#if defined HW_RVL
+#include <wiiuse/wpad.h>
+#include <wiikeyboard/keyboard.h>
+#endif
+
+static cc_bool needsFBUpdate;
+static cc_bool launcherMode;
+static void* xfb;
+static GXRModeObj* rmode;
+void* Window_XFB;
+struct _DisplayData DisplayInfo;
+struct _WindowData WindowInfo;
+
+
+static void OnPowerOff(void) {
+	Window_Main.Exists = false;
+	Window_RequestClose();
+}
+static void InitVideo(void) {
+	// Initialise the video system
+	VIDEO_Init();
+
+	// Obtain the preferred video mode from the system
+	// This will correspond to the settings in the Wii menu
+	rmode = VIDEO_GetPreferredMode(NULL);
+	// Allocate memory for the display in the uncached region
+	xfb = MEM_K0_TO_K1(SYS_AllocateFramebuffer(rmode));
+	
+	Window_XFB = xfb;	
+	//console_init(xfb,20,20,rmode->fbWidth,rmode->xfbHeight,rmode->fbWidth*VI_DISPLAY_PIX_SZ);
+
+	// Set up the video registers with the chosen mode
+	VIDEO_Configure(rmode);
+	// Tell the video hardware where our display memory is
+	VIDEO_SetNextFramebuffer(xfb);
+
+	// Make the display visible
+	VIDEO_SetBlack(FALSE);
+	// Flush the video register changes to the hardware
+	VIDEO_Flush();
+	// Wait for Video setup to complete
+	VIDEO_WaitVSync();
+}
+
+void Window_PreInit(void) {
+	// TODO: SYS_SetResetCallback(reload); too? not sure how reset differs on GC/WII
+	#if defined HW_RVL
+	SYS_SetPowerCallback(OnPowerOff);
+	#endif
+	InitVideo();
+}
+
+void Window_Init(void) {
+	DisplayInfo.Width  = rmode->fbWidth;
+	DisplayInfo.Height = rmode->xfbHeight;
+	DisplayInfo.ScaleX = 1;
+	DisplayInfo.ScaleY = 1;
+	
+	Window_Main.Width   = rmode->fbWidth;
+	Window_Main.Height  = rmode->xfbHeight;
+	Window_Main.Focused = true;
+	Window_Main.Exists  = true;
+
+	Input.Sources = INPUT_SOURCE_GAMEPAD;
+	DisplayInfo.ContentOffsetX = 10;
+	DisplayInfo.ContentOffsetY = 10;
+
+	#if defined HW_RVL
+	WPAD_Init();
+	WPAD_SetDataFormat(0, WPAD_FMT_BTNS_ACC_IR);
+	KEYBOARD_Init(NULL);
+	#endif
+	PAD_Init();
+}
+
+void Window_Free(void) { }
+
+void Window_Create2D(int width, int height) {
+	needsFBUpdate = true;
+	launcherMode  = true;  
+}
+
+void Window_Create3D(int width, int height) { 
+	launcherMode = false; 
+}
+
+void Window_RequestClose(void) {
+	Event_RaiseVoid(&WindowEvents.Closing);
+}
+
+
+/*########################################################################################################################*
+*---------------------------------------------GameCube controller processing----------------------------------------------*
+*#########################################################################################################################*/
+static PADStatus gc_pads[INPUT_MAX_GAMEPADS];
+
+#define PAD_AXIS_SCALE 8.0f
+static void ProcessPAD_Joystick(int port, int axis, int x, int y, float delta) {
+	// May not be exactly 0 on actual hardware
+	if (Math_AbsI(x) <= 8) x = 0;
+	if (Math_AbsI(y) <= 8) y = 0;
+	
+	Gamepad_SetAxis(port, axis, x / PAD_AXIS_SCALE, -y / PAD_AXIS_SCALE, delta);
+}
+
+static void ProcessPAD_Buttons(int port, int mods) {
+	Gamepad_SetButton(port, CCPAD_L, mods & PAD_TRIGGER_L);
+	Gamepad_SetButton(port, CCPAD_R, mods & PAD_TRIGGER_R);
+	
+	Gamepad_SetButton(port, CCPAD_A, mods & PAD_BUTTON_A);
+	Gamepad_SetButton(port, CCPAD_B, mods & PAD_BUTTON_B);
+	Gamepad_SetButton(port, CCPAD_X, mods & PAD_BUTTON_X);
+	Gamepad_SetButton(port, CCPAD_Y, mods & PAD_BUTTON_Y);
+	
+	Gamepad_SetButton(port, CCPAD_START,  mods & PAD_BUTTON_START);
+	Gamepad_SetButton(port, CCPAD_SELECT, mods & PAD_TRIGGER_Z);
+	
+	Gamepad_SetButton(port, CCPAD_LEFT,   mods & PAD_BUTTON_LEFT);
+	Gamepad_SetButton(port, CCPAD_RIGHT,  mods & PAD_BUTTON_RIGHT);
+	Gamepad_SetButton(port, CCPAD_UP,     mods & PAD_BUTTON_UP);
+	Gamepad_SetButton(port, CCPAD_DOWN,   mods & PAD_BUTTON_DOWN);
+}
+
+static void ProcessPADInput(int port, float delta) {
+	PADStatus pads[4];
+	PAD_Read(pads);
+	int error = pads[port].err;
+
+	if (error == 0) {
+		gc_pads[port] = pads[port]; // new state arrived
+	} else if (error == PAD_ERR_TRANSFER) {
+		// usually means still busy transferring state - use last state
+	} else {
+		return; // not connected, still busy, etc
+	}
+	
+	ProcessPAD_Buttons(0, gc_pads[port].button);
+	ProcessPAD_Joystick(0, PAD_AXIS_LEFT,  gc_pads[port].stickX,    gc_pads[port].stickY,    delta);
+	ProcessPAD_Joystick(0, PAD_AXIS_RIGHT, gc_pads[port].substickX, gc_pads[port].substickY, delta);
+}
+
+
+/*########################################################################################################################*
+*--------------------------------------------------Kebyaord processing----------------------------------------------------*
+*#########################################################################################################################*/
+#if defined HW_RVL
+static const cc_uint8 key_map[] = {
+/* 0x00 */ 0,0,0,0,         'A','B','C','D', 
+/* 0x08 */ 'E','F','G','H', 'I','J','K','L',
+/* 0x10 */ 'M','N','O','P', 'Q','R','S','T',
+/* 0x18 */ 'U','V','W','X', 'Y','Z','1','2',
+/* 0x20 */ '3','4','5','6', '7','8','9','0',
+/* 0x28 */ CCKEY_ENTER,CCKEY_ESCAPE,CCKEY_BACKSPACE,CCKEY_TAB, CCKEY_SPACE,CCKEY_MINUS,CCKEY_EQUALS,CCKEY_LBRACKET,
+/* 0x30 */ CCKEY_RBRACKET,CCKEY_BACKSLASH,0,CCKEY_SEMICOLON, CCKEY_QUOTE,CCKEY_TILDE,CCKEY_COMMA,CCKEY_PERIOD,
+/* 0x38 */ CCKEY_SLASH,CCKEY_CAPSLOCK,CCKEY_F1,CCKEY_F2, CCKEY_F3,CCKEY_F4,CCKEY_F5,CCKEY_F6,
+/* 0x40 */ CCKEY_F7,CCKEY_F8,CCKEY_F9,CCKEY_F10, CCKEY_F11,CCKEY_F12,CCKEY_PRINTSCREEN,CCKEY_SCROLLLOCK,
+/* 0x48 */ CCKEY_PAUSE,CCKEY_INSERT,CCKEY_HOME,CCKEY_PAGEUP, CCKEY_DELETE,CCKEY_END,CCKEY_PAGEDOWN,CCKEY_RIGHT,
+/* 0x50 */ CCKEY_LEFT,CCKEY_DOWN,CCKEY_UP,CCKEY_NUMLOCK, CCKEY_KP_DIVIDE,CCKEY_KP_MULTIPLY,CCKEY_KP_MINUS,CCKEY_KP_PLUS,
+/* 0x58 */ CCKEY_KP_ENTER,CCKEY_KP1,CCKEY_KP2,CCKEY_KP3, CCKEY_KP4,CCKEY_KP5,CCKEY_KP6,CCKEY_KP7,
+/* 0x60 */ CCKEY_KP8,CCKEY_KP9,CCKEY_KP0,CCKEY_KP_DECIMAL, 0,0,0,0,
+/* 0x68 */ 0,0,0,0, 0,0,0,0,
+/* 0x70 */ 0,0,0,0, 0,0,0,0,
+/* 0x78 */ 0,0,0,0, 0,0,0,0,
+/* 0x80 */ 0,0,0,0, 0,0,0,0,
+/* 0x88 */ 0,0,0,0, 0,0,0,0,
+/* 0x90 */ 0,0,0,0, 0,0,0,0,
+/* 0x98 */ 0,0,0,0, 0,0,0,0,
+/* 0xA0 */ 0,0,0,0, 0,0,0,0,
+/* 0xA8 */ 0,0,0,0, 0,0,0,0,
+/* 0xB0 */ 0,0,0,0, 0,0,0,0,
+/* 0xB8 */ 0,0,0,0, 0,0,0,0,
+/* 0xC0 */ 0,0,0,0, 0,0,0,0,
+/* 0xC8 */ 0,0,0,0, 0,0,0,0,
+/* 0xD0 */ 0,0,0,0, 0,0,0,0,
+/* 0xD8 */ 0,0,0,0, 0,0,0,0,
+/* 0xE0 */ CCKEY_LCTRL,CCKEY_LSHIFT,CCKEY_LALT,CCKEY_LWIN, CCKEY_RCTRL,CCKEY_RSHIFT,CCKEY_RALT,CCKEY_RWIN
+};
+
+static int MapNativeKey(unsigned key) {
+	return key < Array_Elems(key_map) ? key_map[key] : 0;
+}
+
+static void ProcessKeyboardInput(void) {
+	keyboard_event ke;
+	int res = KEYBOARD_GetEvent(&ke);
+	int key;
+	
+	if (!res) return;
+	Input.Sources |= INPUT_SOURCE_NORMAL;
+	
+	if (res && ke.type == KEYBOARD_PRESSED)
+	{
+		key = MapNativeKey(ke.keycode);
+		if (key) Input_SetPressed(key);
+		//Platform_Log2("KEYCODE: %i (%i)", &ke.keycode, &ke.type);
+		if (ke.symbol) Event_RaiseInt(&InputEvents.Press, ke.symbol);
+	}
+	if (res && ke.type == KEYBOARD_RELEASED)
+	{
+		key = MapNativeKey(ke.keycode);
+		if (key) Input_SetReleased(key);
+	}
+}
+#endif
+
+
+/*########################################################################################################################*
+*----------------------------------------------------Input processing-----------------------------------------------------*
+*#########################################################################################################################*/
+#if defined HW_RVL
+static int dragCurX, dragCurY;
+static int dragStartX, dragStartY;
+static cc_bool dragActive;
+
+void Window_ProcessEvents(float delta) {
+	ProcessKeyboardInput();
+}
+
+static void GetIRPos(int res, int* x, int* y) {
+	if (res == WPAD_ERR_NONE) {
+		WPADData* wd = WPAD_Data(0);
+
+		*x = wd->ir.x;
+		*y = wd->ir.y;
+	} else {
+		*x = 0; 
+		*y = 0;
+	}
+}
+
+static void ScanAndGetIRPos(int* x, int* y) {
+	u32 type;
+	WPAD_ScanPads();
+	
+	int res = WPAD_Probe(0, &type);
+	GetIRPos(res, x, y);
+}
+
+
+static void ProcessWPADDrag(int res, u32 mods) {
+	int x, y;
+	GetIRPos(res, &x, &y);
+	
+	if (mods & WPAD_BUTTON_B) {
+		if (!dragActive) {
+			dragStartX = dragCurX = x;
+			dragStartY = dragCurY = y;
+		}
+		dragActive = true;
+	} else {
+		dragActive = false;
+	}
+	Pointer_SetPosition(0, x, y);
+}
+
+#define FACTOR 2
+void Window_UpdateRawMouse(void)  {
+	if (!dragActive) return;
+	int x, y;
+	ScanAndGetIRPos(&x, &y);
+   
+	// TODO: Refactor the logic. is it 100% right too?
+	dragCurX = dragStartX + (dragCurX - dragStartX) / FACTOR;
+	dragCurY = dragStartY + (dragCurY - dragStartY) / FACTOR;
+	
+	int dx = x - dragCurX; Math_Clamp(dx, -40, 40);
+	int dy = y - dragCurY; Math_Clamp(dy, -40, 40);
+	Event_RaiseRawMove(&PointerEvents.RawMoved, dx, dy);
+	
+	dragCurX = x; dragCurY = y;
+}
+#else
+void Window_ProcessEvents(float delta) {
+}
+
+void Window_UpdateRawMouse(void) { }
+#endif
+
+void Cursor_SetPosition(int x, int y) { } // No point in GameCube/Wii
+// TODO: Display cursor on Wii when not raw mode
+void Window_EnableRawMouse(void)  { Input.RawMode = true;  }
+void Window_DisableRawMouse(void) { Input.RawMode = false; }
+
+
+/*########################################################################################################################*
+*-------------------------------------------------------Gamepads----------------------------------------------------------*
+*#########################################################################################################################*/
+#if defined HW_RVL
+static int dragCurX, dragCurY;
+static int dragStartX, dragStartY;
+static cc_bool dragActive;
+
+static void ProcessWPAD_Buttons(int port, int mods) {
+	Gamepad_SetButton(port, CCPAD_L, mods & WPAD_BUTTON_1);
+	Gamepad_SetButton(port, CCPAD_R, mods & WPAD_BUTTON_2);
+      
+	Gamepad_SetButton(port, CCPAD_A, mods & WPAD_BUTTON_A);
+	Gamepad_SetButton(port, CCPAD_B, mods & WPAD_BUTTON_B);
+	Gamepad_SetButton(port, CCPAD_X, mods & WPAD_BUTTON_PLUS);
+      
+	Gamepad_SetButton(port, CCPAD_START,  mods & WPAD_BUTTON_HOME);
+	Gamepad_SetButton(port, CCPAD_SELECT, mods & WPAD_BUTTON_MINUS);
+
+	Gamepad_SetButton(port, CCPAD_LEFT,   mods & WPAD_BUTTON_LEFT);
+	Gamepad_SetButton(port, CCPAD_RIGHT,  mods & WPAD_BUTTON_RIGHT);
+	Gamepad_SetButton(port, CCPAD_UP,     mods & WPAD_BUTTON_UP);
+	Gamepad_SetButton(port, CCPAD_DOWN,   mods & WPAD_BUTTON_DOWN);
+}
+
+static void ProcessNunchuck_Game(int port, int mods, float delta) {
+	WPADData* wd = WPAD_Data(0);
+	joystick_t analog = wd->exp.nunchuk.js;
+
+	Gamepad_SetButton(port, CCPAD_L, mods & WPAD_NUNCHUK_BUTTON_C);
+	Gamepad_SetButton(port, CCPAD_R, mods & WPAD_NUNCHUK_BUTTON_Z);
+      
+	Gamepad_SetButton(port, CCPAD_A, mods & WPAD_BUTTON_A);
+	Gamepad_SetButton(port, CCPAD_Y, mods & WPAD_BUTTON_1);
+	Gamepad_SetButton(port, CCPAD_X, mods & WPAD_BUTTON_2);
+
+	Gamepad_SetButton(port, CCPAD_START,  mods & WPAD_BUTTON_HOME);
+	Gamepad_SetButton(port, CCPAD_SELECT, mods & WPAD_BUTTON_MINUS);
+
+	Input_SetNonRepeatable(KeyBind_Mappings[BIND_FLY].button1, mods & WPAD_BUTTON_LEFT);
+
+	if (mods & WPAD_BUTTON_RIGHT) {
+		Mouse_ScrollVWheel(1.0*delta);
+	}
+
+	Input_SetNonRepeatable(KeyBind_Mappings[BIND_THIRD_PERSON].button1, mods & WPAD_BUTTON_UP);
+	Input_SetNonRepeatable(KeyBind_Mappings[BIND_FLY_DOWN].button1,     mods & WPAD_BUTTON_DOWN);
+
+	const float ANGLE_DELTA = 50;
+	bool nunchuckUp    = (analog.ang > -ANGLE_DELTA)    && (analog.ang < ANGLE_DELTA)     && (analog.mag > 0.5);
+	bool nunchuckDown  = (analog.ang > 180-ANGLE_DELTA) && (analog.ang < 180+ANGLE_DELTA) && (analog.mag > 0.5);
+	bool nunchuckLeft  = (analog.ang > -90-ANGLE_DELTA) && (analog.ang < -90+ANGLE_DELTA) && (analog.mag > 0.5);
+	bool nunchuckRight = (analog.ang > 90-ANGLE_DELTA)  && (analog.ang < 90+ANGLE_DELTA)  && (analog.mag > 0.5);
+
+	Gamepad_SetButton(port, CCPAD_LEFT,  nunchuckLeft);
+	Gamepad_SetButton(port, CCPAD_RIGHT, nunchuckRight);
+	Gamepad_SetButton(port, CCPAD_UP,    nunchuckUp);
+	Gamepad_SetButton(port, CCPAD_DOWN,  nunchuckDown);
+}
+
+#define CLASSIC_AXIS_SCALE 2.0f
+static void ProcessClassic_Joystick(int port, int axis, struct joystick_t* js, float delta) {
+	// TODO: need to account for min/max?? see libogc
+	int x = js->pos.x - js->center.x;
+	int y = js->pos.y - js->center.y;
+	
+	if (Math_AbsI(x) <= 8) x = 0;
+	if (Math_AbsI(y) <= 8) y = 0;
+	
+	Gamepad_SetAxis(port, axis, x / CLASSIC_AXIS_SCALE, -y / CLASSIC_AXIS_SCALE, delta);
+}
+
+static void ProcessClassicButtons(int port, int mods) {
+	Gamepad_SetButton(port, CCPAD_L, mods & CLASSIC_CTRL_BUTTON_FULL_L);
+	Gamepad_SetButton(port, CCPAD_R, mods & CLASSIC_CTRL_BUTTON_FULL_R);
+      
+	Gamepad_SetButton(port, CCPAD_A, mods & CLASSIC_CTRL_BUTTON_A);
+	Gamepad_SetButton(port, CCPAD_B, mods & CLASSIC_CTRL_BUTTON_B);
+	Gamepad_SetButton(port, CCPAD_X, mods & CLASSIC_CTRL_BUTTON_X);
+	Gamepad_SetButton(port, CCPAD_Y, mods & CLASSIC_CTRL_BUTTON_Y);
+      
+	Gamepad_SetButton(port, CCPAD_START,  mods & CLASSIC_CTRL_BUTTON_PLUS);
+	Gamepad_SetButton(port, CCPAD_SELECT, mods & CLASSIC_CTRL_BUTTON_MINUS);
+
+	Gamepad_SetButton(port, CCPAD_LEFT,   mods & CLASSIC_CTRL_BUTTON_LEFT);
+	Gamepad_SetButton(port, CCPAD_RIGHT,  mods & CLASSIC_CTRL_BUTTON_RIGHT);
+	Gamepad_SetButton(port, CCPAD_UP,     mods & CLASSIC_CTRL_BUTTON_UP);
+	Gamepad_SetButton(port, CCPAD_DOWN,   mods & CLASSIC_CTRL_BUTTON_DOWN);
+	
+	Gamepad_SetButton(port, CCPAD_ZL, mods & CLASSIC_CTRL_BUTTON_ZL);
+	Gamepad_SetButton(port, CCPAD_ZR, mods & CLASSIC_CTRL_BUTTON_ZR);
+}
+
+static void ProcessClassicInput(int port, float delta) {
+	WPADData* wd = WPAD_Data(0);
+	classic_ctrl_t ctrls = wd->exp.classic;
+	int mods = ctrls.btns | ctrls.btns_held;
+
+	ProcessClassicButtons(port, mods);
+	ProcessClassic_Joystick(port, PAD_AXIS_LEFT,  &ctrls.ljs, delta);
+	ProcessClassic_Joystick(port, PAD_AXIS_RIGHT, &ctrls.rjs, delta);
+}
+
+static void ProcessWPADInput(int port, float delta) {
+	WPAD_ScanPads();
+	u32 type;
+	int res  = WPAD_Probe(port, &type);
+	if (res) return;
+	u32 mods = WPAD_ButtonsDown(port) | WPAD_ButtonsHeld(port);
+
+	if (type == WPAD_EXP_CLASSIC) {
+		ProcessClassicInput(port, delta);
+	} else if (launcherMode) {
+		ProcessWPAD_Buttons(port, mods);
+	} else if (type == WPAD_EXP_NUNCHUK) {
+		ProcessNunchuck_Game(port, mods, delta);
+	} else {
+		ProcessWPAD_Buttons(port, mods);
+	}
+
+	ProcessWPADDrag(res, mods);
+}
+
+void Window_ProcessGamepads(float delta) {
+	for (int port = 0; port < INPUT_MAX_GAMEPADS; port++)
+	{
+		ProcessWPADInput(port, delta);
+		ProcessPADInput( port, delta);
+	}
+}
+#else
+void Window_ProcessGamepads(float delta) {
+	for (int port = 0; port < INPUT_MAX_GAMEPADS; port++)
+	{
+		ProcessPADInput(port, delta);
+	}
+}
+#endif
+
+
+/*########################################################################################################################*
+*------------------------------------------------------Framebuffer--------------------------------------------------------*
+*#########################################################################################################################*/
+void Window_AllocFramebuffer(struct Bitmap* bmp, int width, int height) {
+	bmp->scan0  = (BitmapCol*)Mem_Alloc(width * height, 4, "window pixels");
+	bmp->width  = width;
+	bmp->height = height;
+}
+
+// TODO: Get rid of this complexity and use the 3D API instead..
+// https://github.com/devkitPro/gamecube-examples/blob/master/graphics/fb/pageflip/source/flip.c
+static u32 CvtRGB (u8 r1, u8 g1, u8 b1, u8 r2, u8 g2, u8 b2)
+{
+  int y1, cb1, cr1, y2, cb2, cr2, cb, cr;
+ 
+  y1  = (299    * r1 +   587 * g1 +   114 * b1) / 1000;
+  cb1 = (-16874 * r1 - 33126 * g1 + 50000 * b1 + 12800000) / 100000;
+  cr1 = (50000  * r1 - 41869 * g1 -  8131 * b1 + 12800000) / 100000;
+ 
+  y2  = (299    * r2 +   587 * g2 +   114 * b2) / 1000;
+  cb2 = (-16874 * r2 - 33126 * g2 + 50000 * b2 + 12800000) / 100000;
+  cr2 = (50000  * r2 - 41869 * g2 -  8131 * b2 + 12800000) / 100000;
+ 
+  cb = (cb1 + cb2) >> 1;
+  cr = (cr1 + cr2) >> 1;
+ 
+  return (y1 << 24) | (cb << 16) | (y2 << 8) | cr;
+}
+
+void Window_DrawFramebuffer(Rect2D r, struct Bitmap* bmp) {
+	// When coming back from the 3D game, framebuffer might have changed
+	if (needsFBUpdate) {
+		VIDEO_SetNextFramebuffer(xfb);
+		VIDEO_Flush();
+		needsFBUpdate = false;
+	}
+	
+	VIDEO_WaitVSync();
+	r.x &= ~0x01; // round down to nearest even horizontal index
+	
+	// TODO XFB is raw yuv, but is absolutely a pain to work with..
+	for (int y = r.y; y < r.y + r.height; y++) 
+	{
+		cc_uint32* src = Bitmap_GetRow(bmp, y)           + r.x;
+		u16* dst       = (u16*)xfb  + y * rmode->fbWidth + r.x;
+		
+		for (int x = 0; x < r.width / 2; x++) {
+			cc_uint32 rgb0 = src[(x<<1) + 0];
+			cc_uint32 rgb1 = src[(x<<1) + 1];
+			
+			((u32*)dst)[x] = CvtRGB(BitmapCol_R(rgb0), BitmapCol_G(rgb0), BitmapCol_B(rgb0),
+					BitmapCol_R(rgb1),  BitmapCol_G(rgb1), BitmapCol_B(rgb1));
+		}
+	}
+}
+
+void Window_FreeFramebuffer(struct Bitmap* bmp) {
+	Mem_Free(bmp->scan0);
+}
+
+
+/*########################################################################################################################*
+*------------------------------------------------------Soft keyboard------------------------------------------------------*
+*#########################################################################################################################*/
+void OnscreenKeyboard_Open(struct OpenKeyboardArgs* args) {
+	if (Input.Sources & INPUT_SOURCE_NORMAL) return;
+	VirtualKeyboard_Open(args, launcherMode);
+}
+
+void OnscreenKeyboard_SetText(const cc_string* text) {
+	VirtualKeyboard_SetText(text);
+}
+
+void OnscreenKeyboard_Draw2D(Rect2D* r, struct Bitmap* bmp) {
+	VirtualKeyboard_Display2D(r, bmp);
+}
+
+void OnscreenKeyboard_Draw3D(void) {
+	VirtualKeyboard_Display3D();
+}
+
+void OnscreenKeyboard_Close(void) {
+	VirtualKeyboard_Close();
+}
+
+
+/*########################################################################################################################*
+*-------------------------------------------------------Misc/Other--------------------------------------------------------*
+*#########################################################################################################################*/
+void Window_SetTitle(const cc_string* title)   { }
+void Clipboard_GetText(cc_string* value)       { }
+void Clipboard_SetText(const cc_string* value) { }
+
+int Window_GetWindowState(void) { return WINDOW_STATE_FULLSCREEN; }
+cc_result Window_EnterFullscreen(void) { return 0; }
+cc_result Window_ExitFullscreen(void)  { return 0; }
+int Window_IsObscured(void)            { return 0; }
+
+void Window_Show(void) { }
+void Window_SetSize(int width, int height) { }
+
+
+void Window_ShowDialog(const char* title, const char* msg) {
+	/* TODO implement */
+	Platform_LogConst(title);
+	Platform_LogConst(msg);
+}
+
+cc_result Window_OpenFileDialog(const struct OpenFileDialogArgs* args) {
+	return ERR_NOT_SUPPORTED;
+}
+
+cc_result Window_SaveFileDialog(const struct SaveFileDialogArgs* args) {
+	return ERR_NOT_SUPPORTED;
+}
+#endif