summary refs log tree commit diff
path: root/src/LScreens.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/LScreens.c
initial commit
Diffstat (limited to 'src/LScreens.c')
-rw-r--r--src/LScreens.c1856
1 files changed, 1856 insertions, 0 deletions
diff --git a/src/LScreens.c b/src/LScreens.c
new file mode 100644
index 0000000..3362389
--- /dev/null
+++ b/src/LScreens.c
@@ -0,0 +1,1856 @@
+#include "LScreens.h"
+#ifndef CC_BUILD_WEB
+#include "String.h"
+#include "LWidgets.h"
+#include "LWeb.h"
+#include "Launcher.h"
+#include "Gui.h"
+#include "Drawer2D.h"
+#include "ExtMath.h"
+#include "Platform.h"
+#include "Stream.h"
+#include "Funcs.h"
+#include "Resources.h"
+#include "Logger.h"
+#include "Window.h"
+#include "Input.h"
+#include "Options.h"
+#include "Utils.h"
+#include "LBackend.h"
+#include "Http.h"
+#include "Game.h"
+
+#define LAYOUTS static const struct LLayout
+#define IsEnterButton(btn) (btn == CCKEY_ENTER  || btn == CCPAD_START  || btn == CCPAD_A || btn == CCKEY_KP_ENTER)
+#define IsBackButton(btn)  (btn == CCKEY_ESCAPE || btn == CCPAD_SELECT || btn == CCPAD_B)
+
+/*########################################################################################################################*
+*---------------------------------------------------------Screen base-----------------------------------------------------*
+*#########################################################################################################################*/
+static void LScreen_NullFunc(struct LScreen* s) { }
+CC_NOINLINE static int LScreen_IndexOf(struct LScreen* s, void* w) {
+	int i;
+	for (i = 0; i < s->numWidgets; i++) {
+		if (s->widgets[i] == w) return i;
+	}
+	return -1;
+}
+
+static void LScreen_DoLayout(struct LScreen* s) {
+	int i;
+	for (i = 0; i < s->numWidgets; i++) 
+	{
+		LBackend_LayoutWidget(s->widgets[i]);
+	}
+}
+
+static void LScreen_Tick(struct LScreen* s) {
+	struct LWidget* w = s->selectedWidget;
+	if (w && w->VTABLE->Tick) w->VTABLE->Tick(w);
+}
+
+void LScreen_SelectWidget(struct LScreen* s, int idx, struct LWidget* w, cc_bool was) {
+	if (!w) return;
+	w->selected       = true;
+	s->selectedWidget = w;
+	if (w->VTABLE->OnSelect) w->VTABLE->OnSelect(w, idx, was);
+}
+
+void LScreen_UnselectWidget(struct LScreen* s, int idx, struct LWidget* w) {
+	if (!w) return;
+	w->selected       = false;
+	s->selectedWidget = NULL;
+	if (w->VTABLE->OnUnselect) w->VTABLE->OnUnselect(w, idx);
+}
+
+static void LScreen_CycleSelected(struct LScreen* s, int dir) {
+	struct LWidget* w;
+	int index = 0, i, j;
+
+	if (s->selectedWidget) {
+		index = LScreen_IndexOf(s, s->selectedWidget) + dir;
+	}
+
+	for (j = 0; j < s->numWidgets; j++) {
+		i = (index + j * dir) % s->numWidgets;
+		if (i < 0) i += s->numWidgets;
+
+		w = s->widgets[i];
+		if (!w->autoSelectable) continue;
+
+		LScreen_UnselectWidget(s, 0, s->selectedWidget);
+		LScreen_SelectWidget(s, 0, w, false);
+		return;
+	}
+}
+
+static void LScreen_KeyDown(struct LScreen* s, int key, cc_bool was) {
+	if (IsEnterButton(key)) {
+		/* Shouldn't multi click when holding down Enter */
+		if (was) return;
+
+		if (s->selectedWidget && s->selectedWidget->OnClick) {
+			s->selectedWidget->OnClick(s->selectedWidget);
+		} else if (s->hoveredWidget && s->hoveredWidget->OnClick) {
+			s->hoveredWidget->OnClick(s->hoveredWidget);
+		} else if (s->onEnterWidget) {
+			s->onEnterWidget->OnClick(s->onEnterWidget);
+		}
+		return;
+	}
+	
+	/* Active widget takes input priority over default behaviour */
+	if (s->selectedWidget && s->selectedWidget->VTABLE->KeyDown) {
+		if (s->selectedWidget->VTABLE->KeyDown(s->selectedWidget, key, was)) return;
+	}
+
+	if (key == CCKEY_TAB || key == CCPAD_X) {
+		LScreen_CycleSelected(s, Input_IsShiftPressed() ? -1 : 1);
+	} else if (Input_IsUpButton(key)) {
+		LScreen_CycleSelected(s, -1);
+	} else if (Input_IsDownButton(key)) {
+		LScreen_CycleSelected(s,  1);
+	} else if (IsBackButton(key) && s->onEscapeWidget) {
+		s->onEscapeWidget->OnClick(s->onEscapeWidget);
+	}
+}
+
+static void LScreen_MouseUp(struct LScreen* s, int idx) { }
+static void LScreen_MouseWheel(struct LScreen* s, float delta) { }
+
+static void LScreen_DrawBackground(struct LScreen* s, struct Context2D* ctx) {
+	if (!s->title) {
+		Launcher_DrawBackground(ctx, 0, 0, ctx->width, ctx->height);
+		return;
+	}
+	Launcher_DrawBackgroundAll(ctx);
+	LBackend_DrawTitle(ctx, s->title);
+}
+
+CC_NOINLINE static void LScreen_Reset(struct LScreen* s) {
+	int i;
+
+	s->Activated   = LScreen_NullFunc;
+	s->LoadState   = LScreen_NullFunc;
+	s->Deactivated = LScreen_NullFunc;
+	s->Layout      = LScreen_DoLayout;
+	s->Tick        = LScreen_Tick;
+	s->KeyDown     = LScreen_KeyDown;
+	s->MouseUp     = LScreen_MouseUp;
+	s->MouseWheel  = LScreen_MouseWheel;
+	s->DrawBackground = LScreen_DrawBackground;
+	s->ResetArea      = Launcher_DrawBackground;
+
+	/* reset all widgets mouse state */
+	for (i = 0; i < s->numWidgets; i++) 
+	{ 
+		s->widgets[i]->hovered  = false;
+		s->widgets[i]->selected = false;
+	}
+
+	s->numWidgets     = 0;
+	s->hoveredWidget  = NULL;
+	s->selectedWidget = NULL;
+}
+
+
+void LScreen_AddWidget(void* screen, void* widget) {
+	struct LScreen* s = (struct LScreen*)screen;
+	struct LWidget* w = (struct LWidget*)widget;
+
+	if (s->numWidgets >= s->maxWidgets)
+		Logger_Abort("Can't add anymore widgets to this LScreen");
+	s->widgets[s->numWidgets++] = w;
+}
+
+
+static void SwitchToChooseMode(void* w)    { ChooseModeScreen_SetActive(false); }
+static void SwitchToColours(void* w)       { ColoursScreen_SetActive(); }
+static void SwitchToDirectConnect(void* w) { DirectConnectScreen_SetActive(); }
+static void SwitchToMain(void* w)          { MainScreen_SetActive(); }
+static void SwitchToSettings(void* w)      { SettingsScreen_SetActive(); }
+static void SwitchToThemes(void* w)        { ThemesScreen_SetActive(); }
+static void SwitchToUpdates(void* w)       { UpdatesScreen_SetActive(); }
+
+
+/*########################################################################################################################*
+*-------------------------------------------------------ChooseModeScreen--------------------------------------------------*
+*#########################################################################################################################*/
+static struct ChooseModeScreen {
+	LScreen_Layout
+	struct LLine seps[2];
+	struct LButton btnEnhanced, btnClassicHax, btnClassic, btnBack;
+	struct LLabel  lblHelp, lblEnhanced[2], lblClassicHax[2], lblClassic[2];
+	cc_bool firstTime;
+} ChooseModeScreen;
+
+#define CHOOSEMODE_SCREEN_MAX_WIDGETS 12
+static struct LWidget* chooseMode_widgets[CHOOSEMODE_SCREEN_MAX_WIDGETS];
+
+LAYOUTS mode_seps0[] = { { ANCHOR_CENTRE, -5 }, { ANCHOR_CENTRE, -85 } };
+LAYOUTS mode_seps1[] = { { ANCHOR_CENTRE, -5 }, { ANCHOR_CENTRE, -15 } };
+
+LAYOUTS mode_btnEnhanced[]    = { { ANCHOR_CENTRE_MIN, -250 }, { ANCHOR_CENTRE, -120      } };
+LAYOUTS mode_lblEnhanced0[]   = { { ANCHOR_CENTRE_MIN,  -85 }, { ANCHOR_CENTRE, -120 - 12 } };
+LAYOUTS mode_lblEnhanced1[]   = { { ANCHOR_CENTRE_MIN,  -85 }, { ANCHOR_CENTRE, -120 + 12 } };
+LAYOUTS mode_btnClassicHax[]  = { { ANCHOR_CENTRE_MIN, -250 }, { ANCHOR_CENTRE,  -50      } };
+LAYOUTS mode_lblClassicHax0[] = { { ANCHOR_CENTRE_MIN,  -85 }, { ANCHOR_CENTRE,  -50 - 12 } };
+LAYOUTS mode_lblClassicHax1[] = { { ANCHOR_CENTRE_MIN,  -85 }, { ANCHOR_CENTRE,  -50 + 12 } };
+LAYOUTS mode_btnClassic[]     = { { ANCHOR_CENTRE_MIN, -250 }, { ANCHOR_CENTRE,   20      } };
+LAYOUTS mode_lblClassic0[]    = { { ANCHOR_CENTRE_MIN,  -85 }, { ANCHOR_CENTRE,   20 - 12 } };
+LAYOUTS mode_lblClassic1[]    = { { ANCHOR_CENTRE_MIN,  -85 }, { ANCHOR_CENTRE,   20 + 12 } };
+
+LAYOUTS mode_lblHelp[] = { { ANCHOR_CENTRE, 0 }, { ANCHOR_CENTRE, 160 } };
+LAYOUTS mode_btnBack[] = { { ANCHOR_CENTRE, 0 }, { ANCHOR_CENTRE, 170 } };
+
+
+CC_NOINLINE static void ChooseMode_Click(cc_bool classic, cc_bool classicHacks) {
+	Options_SetBool(OPT_CLASSIC_MODE, classic);
+	if (classic) Options_SetBool(OPT_CLASSIC_HACKS, classicHacks);
+
+	Options_SetBool(OPT_CUSTOM_BLOCKS,   !classic);
+	Options_SetBool(OPT_CPE,             !classic);
+	Options_SetBool(OPT_SERVER_TEXTURES, !classic);
+	Options_SetBool(OPT_CLASSIC_TABLIST, classic);
+	Options_SetBool(OPT_CLASSIC_OPTIONS, classic);
+
+	Options_SaveIfChanged();
+	Launcher_LoadTheme();
+	LBackend_UpdateTitleFont();
+	MainScreen_SetActive();
+}
+
+static void UseModeEnhanced(void* w)   { ChooseMode_Click(false, false); }
+static void UseModeClassicHax(void* w) { ChooseMode_Click(true,  true);  }
+static void UseModeClassic(void* w)    { ChooseMode_Click(true,  false); }
+
+static void ChooseModeScreen_Activated(struct LScreen* s_) {
+	struct ChooseModeScreen* s = (struct ChooseModeScreen*)s_;
+	LLine_Add(s,   &s->seps[0], 490, mode_seps0);
+	LLine_Add(s,   &s->seps[1], 490, mode_seps1);
+
+	LButton_Add(s, &s->btnEnhanced, 145, 35, "Enhanced",                        
+				UseModeEnhanced,   mode_btnEnhanced);
+	LLabel_Add(s,  &s->lblEnhanced[0], "&eEnables custom blocks, changing env", mode_lblEnhanced0);
+	LLabel_Add(s,  &s->lblEnhanced[1], "&esettings, longer messages, and more", mode_lblEnhanced1);
+
+	LButton_Add(s, &s->btnClassicHax, 145, 35, "Classic +hax",                     
+				UseModeClassicHax, mode_btnClassicHax);
+	LLabel_Add(s,  &s->lblClassicHax[0], "&eSame as Classic mode, except that",    mode_lblClassicHax0);
+	LLabel_Add(s,  &s->lblClassicHax[1], "&ehacks (noclip/fly/speed) are enabled", mode_lblClassicHax1);
+
+	LButton_Add(s, &s->btnClassic, 145, 35, "Classic",                        
+				UseModeClassic,    mode_btnClassic);
+	LLabel_Add(s,  &s->lblClassic[0], "&eOnly uses blocks and features from", mode_lblClassic0);
+	LLabel_Add(s,  &s->lblClassic[1], "&ethe original minecraft classic",     mode_lblClassic1);
+
+	if (s->firstTime) {
+		LLabel_Add(s,  &s->lblHelp, "&eClick &fEnhanced &eif you're not sure which mode to choose.", mode_lblHelp);
+	} else {
+		LButton_Add(s, &s->btnBack, 80, 35, "Back", 
+					SwitchToSettings, mode_btnBack);
+	}
+}
+
+void ChooseModeScreen_SetActive(cc_bool firstTime) {
+	struct ChooseModeScreen* s = &ChooseModeScreen;
+	LScreen_Reset((struct LScreen*)s);
+
+	s->widgets    = chooseMode_widgets;
+	s->maxWidgets = Array_Elems(chooseMode_widgets);
+	s->firstTime  = firstTime;
+
+	s->Activated      = ChooseModeScreen_Activated;
+	s->title          = "Choose mode";
+	s->onEnterWidget  = (struct LWidget*)&s->btnEnhanced;
+	s->onEscapeWidget = firstTime ? NULL : (struct LWidget*)&s->btnBack;
+
+	Launcher_SetScreen((struct LScreen*)s);
+}
+
+
+/*########################################################################################################################*
+*---------------------------------------------------------ColoursScreen---------------------------------------------------*
+*#########################################################################################################################*/
+#define COLOURS_NUM_ROWS 5 /* Background, border, etc */
+#define COLOURS_NUM_COLS 3 /* R, G, B widgets */
+#define COLOURS_NUM_ENTRIES (COLOURS_NUM_ROWS * COLOURS_NUM_COLS)
+
+static struct ColoursScreen {
+	LScreen_Layout
+	struct LButton btnBack;
+	struct LLabel lblNames[COLOURS_NUM_ROWS];
+	struct LLabel lblRGB[COLOURS_NUM_COLS];
+	struct LInput iptColours[COLOURS_NUM_ENTRIES];
+	struct LCheckbox cbClassic;
+} ColoursScreen;
+
+#define COLOURSSCREEN_MAX_WIDGETS 25
+static struct LWidget* colours_widgets[COLOURSSCREEN_MAX_WIDGETS];
+
+#define IptColor_Layout(xx, yy) { { ANCHOR_CENTRE, xx }, { ANCHOR_CENTRE, yy } }
+LAYOUTS clr_iptColours[COLOURS_NUM_ENTRIES][2] = {
+	IptColor_Layout(30, -100), IptColor_Layout(95, -100), IptColor_Layout(160, -100),
+	IptColor_Layout(30,  -60), IptColor_Layout(95,  -60), IptColor_Layout(160,  -60),
+	IptColor_Layout(30,  -20), IptColor_Layout(95,  -20), IptColor_Layout(160,  -20),
+	IptColor_Layout(30,   20), IptColor_Layout(95,   20), IptColor_Layout(160,   20),
+	IptColor_Layout(30,   60), IptColor_Layout(95,   60), IptColor_Layout(160,   60),
+};
+LAYOUTS clr_lblNames0[] = { { ANCHOR_CENTRE_MAX, 10 }, { ANCHOR_CENTRE, -100 } };
+LAYOUTS clr_lblNames1[] = { { ANCHOR_CENTRE_MAX, 10 }, { ANCHOR_CENTRE,  -60 } };
+LAYOUTS clr_lblNames2[] = { { ANCHOR_CENTRE_MAX, 10 }, { ANCHOR_CENTRE,  -20 } };
+LAYOUTS clr_lblNames3[] = { { ANCHOR_CENTRE_MAX, 10 }, { ANCHOR_CENTRE,   20 } };
+LAYOUTS clr_lblNames4[] = { { ANCHOR_CENTRE_MAX, 10 }, { ANCHOR_CENTRE,   60 } };
+
+LAYOUTS clr_lblRGB0[]   = { { ANCHOR_CENTRE,  30 }, { ANCHOR_CENTRE, -130 } };
+LAYOUTS clr_lblRGB1[]   = { { ANCHOR_CENTRE,  95 }, { ANCHOR_CENTRE, -130 } };
+LAYOUTS clr_lblRGB2[]   = { { ANCHOR_CENTRE, 160 }, { ANCHOR_CENTRE, -130 } };
+LAYOUTS clr_cbClassic[] = { { ANCHOR_CENTRE, -16 }, { ANCHOR_CENTRE,  130 } };
+LAYOUTS clr_btnBack[]   = { { ANCHOR_CENTRE,   0 }, { ANCHOR_CENTRE,  170 } };
+
+
+CC_NOINLINE static void ColoursScreen_Set(struct LInput* w, cc_uint8 value) {
+	cc_string tmp; char tmpBuffer[STRING_SIZE];
+	String_InitArray(tmp, tmpBuffer);
+
+	String_AppendInt(&tmp, value);
+	LInput_SetText(w, &tmp);
+}
+
+CC_NOINLINE static void ColoursScreen_Update(struct ColoursScreen* s, int i, BitmapCol color) {
+	ColoursScreen_Set(&s->iptColours[i + 0], BitmapCol_R(color));
+	ColoursScreen_Set(&s->iptColours[i + 1], BitmapCol_G(color));
+	ColoursScreen_Set(&s->iptColours[i + 2], BitmapCol_B(color));
+}
+
+CC_NOINLINE static void ColoursScreen_UpdateAll(struct ColoursScreen* s) {
+	ColoursScreen_Update(s,  0, Launcher_Theme.BackgroundColor);
+	ColoursScreen_Update(s,  3, Launcher_Theme.ButtonBorderColor);
+	ColoursScreen_Update(s,  6, Launcher_Theme.ButtonHighlightColor);
+	ColoursScreen_Update(s,  9, Launcher_Theme.ButtonForeColor);
+	ColoursScreen_Update(s, 12, Launcher_Theme.ButtonForeActiveColor);
+}
+
+static void ColoursScreen_TextChanged(struct LInput* w) {
+	struct ColoursScreen* s = &ColoursScreen;
+	int index = LScreen_IndexOf((struct LScreen*)s, w);
+	BitmapCol* color;
+	cc_uint8 r, g, b;
+
+	if (index < 3)       color = &Launcher_Theme.BackgroundColor;
+	else if (index < 6)  color = &Launcher_Theme.ButtonBorderColor;
+	else if (index < 9)  color = &Launcher_Theme.ButtonHighlightColor;
+	else if (index < 12) color = &Launcher_Theme.ButtonForeColor;
+	else                 color = &Launcher_Theme.ButtonForeActiveColor;
+
+	/* if index of G input, changes to index of R input */
+	index = (index / 3) * 3;
+	if (!Convert_ParseUInt8(&s->iptColours[index + 0].text, &r)) return;
+	if (!Convert_ParseUInt8(&s->iptColours[index + 1].text, &g)) return;
+	if (!Convert_ParseUInt8(&s->iptColours[index + 2].text, &b)) return;
+
+	*color = BitmapColor_RGB(r, g, b);
+	Launcher_SaveTheme();
+	LBackend_ThemeChanged();
+}
+
+static void ColoursScreen_AdjustSelected(struct LScreen* s, int delta) {
+	struct LInput* w;
+	int index, newVal;
+	cc_uint8 value;
+
+	if (!s->selectedWidget) return;
+	index = LScreen_IndexOf(s, s->selectedWidget);
+	if (index >= 15) return;
+
+	w = (struct LInput*)s->selectedWidget;
+	if (!Convert_ParseUInt8(&w->text, &value)) return;
+	newVal = value + delta;
+
+	Math_Clamp(newVal, 0, 255);
+	ColoursScreen_Set(w, newVal);
+	ColoursScreen_TextChanged(w);
+}
+
+static void ColoursScreen_KeyDown(struct LScreen* s, int key, cc_bool was) {
+	int delta = Input_CalcDelta(key, 1, 10);
+	if (key == CCWHEEL_UP)   delta = +1;
+	if (key == CCWHEEL_DOWN) delta = -1;
+	
+	if (delta) {
+		ColoursScreen_AdjustSelected(s, delta);
+	} else {
+		LScreen_KeyDown(s, key, was);
+	}
+}
+
+static void ColoursScreen_ToggleBG(struct LCheckbox* w) {
+	Launcher_Theme.ClassicBackground = w->value;
+	Launcher_SaveTheme();
+	LBackend_ThemeChanged();
+}
+
+static void ColoursScreen_AddWidgets(struct ColoursScreen* s) {
+	int i;
+	for (i = 0; i < COLOURS_NUM_ENTRIES; i++)
+	{
+		s->iptColours[i].inputType   = KEYBOARD_TYPE_INTEGER;
+		s->iptColours[i].TextChanged = ColoursScreen_TextChanged;
+		LInput_Add(s, &s->iptColours[i], 55, NULL, clr_iptColours[i]);
+	}
+
+	LLabel_Add(s,  &s->lblNames[0], "Background",       clr_lblNames0);
+	LLabel_Add(s,  &s->lblNames[1], "Button border",    clr_lblNames1);
+	LLabel_Add(s,  &s->lblNames[2], "Button highlight", clr_lblNames2);
+	LLabel_Add(s,  &s->lblNames[3], "Button",           clr_lblNames3);
+	LLabel_Add(s,  &s->lblNames[4], "Active button",    clr_lblNames4);
+
+	LLabel_Add(s,  &s->lblRGB[0], "Red",        clr_lblRGB0);
+	LLabel_Add(s,  &s->lblRGB[1], "Green",      clr_lblRGB1);
+	LLabel_Add(s,  &s->lblRGB[2], "Blue",       clr_lblRGB2);
+	LButton_Add(s, &s->btnBack, 80, 35, "Back",
+				SwitchToThemes, clr_btnBack);
+
+	LCheckbox_Add(s, &s->cbClassic, "Classic style", 
+					ColoursScreen_ToggleBG, clr_cbClassic);
+}
+
+static void ColoursScreen_Activated(struct LScreen* s_) {
+	struct ColoursScreen* s = (struct ColoursScreen*)s_;
+	ColoursScreen_AddWidgets(s);
+
+	LCheckbox_Set(&s->cbClassic, Launcher_Theme.ClassicBackground);
+	ColoursScreen_UpdateAll(s);
+}
+
+void ColoursScreen_SetActive(void) {
+	struct ColoursScreen* s = &ColoursScreen;
+	LScreen_Reset((struct LScreen*)s);
+
+	s->widgets    = colours_widgets;
+	s->maxWidgets = Array_Elems(colours_widgets);
+
+	s->Activated  = ColoursScreen_Activated;
+	s->KeyDown    = ColoursScreen_KeyDown;
+
+	s->title          = "Custom theme";
+	s->onEscapeWidget = (struct LWidget*)&s->btnBack;
+	Launcher_SetScreen((struct LScreen*)s);
+}
+
+
+/*########################################################################################################################*
+*------------------------------------------------------DirectConnectScreen------------------------------------------------*
+*#########################################################################################################################*/
+static struct DirectConnectScreen {
+	LScreen_Layout
+	struct LButton btnConnect, btnBack;
+	struct LInput iptUsername, iptAddress, iptMppass;
+	struct LLabel lblStatus;
+} DirectConnectScreen;
+
+#define DIRECTCONNECT_SCREEN_MAXWIDGETS 6
+static struct LWidget* directConnect_widgets[DIRECTCONNECT_SCREEN_MAXWIDGETS];
+
+LAYOUTS dc_iptUsername[] = { { ANCHOR_CENTRE_MIN, -165 }, { ANCHOR_CENTRE, -120 } };
+LAYOUTS dc_iptAddress[]  = { { ANCHOR_CENTRE_MIN, -165 }, { ANCHOR_CENTRE,  -75 } };
+LAYOUTS dc_iptMppass[]   = { { ANCHOR_CENTRE_MIN, -165 }, { ANCHOR_CENTRE,  -30 } };
+
+LAYOUTS dc_btnConnect[]  = { { ANCHOR_CENTRE, -110 }, { ANCHOR_CENTRE, 20 } };
+LAYOUTS dc_btnBack[]     = { { ANCHOR_CENTRE,  125 }, { ANCHOR_CENTRE, 20 } };
+LAYOUTS dc_lblStatus[]   = { { ANCHOR_CENTRE,    0 }, { ANCHOR_CENTRE, 70 } };
+
+
+static void DirectConnectScreen_UrlFilter(cc_string* str) {
+	static const cc_string prefix = String_FromConst("mc://");
+	cc_string parts[6];
+	if (!String_CaselessStarts(str, &prefix)) return;
+	/* mc://[ip:port]/[username]/[mppass] */
+	if (String_UNSAFE_Split(str, '/', parts, 6) != 5) return;
+	
+	LInput_SetString(&DirectConnectScreen.iptAddress,  &parts[2]);
+	LInput_SetString(&DirectConnectScreen.iptUsername, &parts[3]);
+	LInput_SetString(&DirectConnectScreen.iptMppass,   &parts[4]);
+	str->length = 0;
+}
+
+static void DirectConnectScreen_StartClient(void* w) {
+	static const cc_string defMppass = String_FromConst("(none)");
+	static const cc_string defPort   = String_FromConst("25565");
+	const cc_string* user   = &DirectConnectScreen.iptUsername.text;
+	const cc_string* addr   = &DirectConnectScreen.iptAddress.text;
+	const cc_string* mppass = &DirectConnectScreen.iptMppass.text;
+	struct LLabel* status   = &DirectConnectScreen.lblStatus;
+
+	cc_string ip, port;
+	cc_uint16 raw_port;
+	cc_sockaddr addrs[SOCKET_MAX_ADDRS];
+	int numAddrs;
+
+	int index = String_LastIndexOf(addr, ':');
+	if (index == 0 || index == addr->length - 1) {
+		LLabel_SetConst(status, "&cInvalid address"); return;
+	}
+
+	/* support either "[IP]" or "[IP]:[PORT]" */
+	if (index == -1) {
+		ip   = *addr;
+		port = defPort;
+	} else {
+		ip   = String_UNSAFE_Substring(addr, 0, index);
+		port = String_UNSAFE_SubstringAt(addr, index + 1);
+	}
+
+	if (!user->length) {
+		LLabel_SetConst(status, "&cUsername required"); return;
+	}
+	if (Socket_ParseAddress(&ip, 0, addrs, &numAddrs)) {
+		LLabel_SetConst(status, "&cInvalid ip"); return;
+	}
+	if (!Convert_ParseUInt16(&port, &raw_port)) {
+		LLabel_SetConst(status, "&cInvalid port"); return;
+	}
+	if (!mppass->length) mppass = &defMppass;
+
+	Options_PauseSaving();
+		Options_Set("launcher-dc-username", user);
+		Options_Set("launcher-dc-ip",       &ip);
+		Options_Set("launcher-dc-port",     &port);
+		Options_SetSecure("launcher-dc-mppass", mppass);
+	Options_ResumeSaving();
+
+	LLabel_SetConst(status, "");
+	Launcher_StartGame(user, mppass, &ip, &port, &String_Empty);
+}
+
+static void DirectConnectScreen_Activated(struct LScreen* s_) {
+	struct DirectConnectScreen* s = (struct DirectConnectScreen*)s_;
+
+	LInput_Add(s,  &s->iptUsername, 330, "Username..",               dc_iptUsername);
+	LInput_Add(s,  &s->iptAddress,  330, "IP address:Port number..", dc_iptAddress);
+	LInput_Add(s,  &s->iptMppass,   330, "Mppass..",                 dc_iptMppass);
+
+	LButton_Add(s, &s->btnConnect, 110, 35, "Connect", 
+				DirectConnectScreen_StartClient, dc_btnConnect);
+	LButton_Add(s, &s->btnBack,     80, 35, "Back",    
+				SwitchToMain, dc_btnBack);
+	LLabel_Add(s,  &s->lblStatus,  "", dc_lblStatus);
+
+	s->iptUsername.ClipboardFilter = DirectConnectScreen_UrlFilter;
+	s->iptAddress.ClipboardFilter  = DirectConnectScreen_UrlFilter;
+	s->iptMppass.ClipboardFilter   = DirectConnectScreen_UrlFilter;
+}
+
+static void DirectConnectScreen_Load(struct LScreen* s_) {
+	struct DirectConnectScreen* s = (struct DirectConnectScreen*)s_;
+	cc_string addr; char addrBuffer[STRING_SIZE];
+	cc_string mppass; char mppassBuffer[STRING_SIZE];
+	cc_string user, ip, port;
+	Options_Reload();
+
+	Options_UNSAFE_Get("launcher-dc-username", &user);
+	Options_UNSAFE_Get("launcher-dc-ip",       &ip);
+	Options_UNSAFE_Get("launcher-dc-port",     &port);
+	
+	String_InitArray(mppass, mppassBuffer);
+	Options_GetSecure("launcher-dc-mppass", &mppass);
+	String_InitArray(addr, addrBuffer);
+	String_Format2(&addr, "%s:%s", &ip, &port);
+
+	/* don't want just ':' for address */
+	if (addr.length == 1) addr.length = 0;
+	
+	LInput_SetText(&s->iptUsername, &user);
+	LInput_SetText(&s->iptAddress,  &addr);
+	LInput_SetText(&s->iptMppass,   &mppass);
+}
+
+void DirectConnectScreen_SetActive(void) {
+	struct DirectConnectScreen* s = &DirectConnectScreen;
+	LScreen_Reset((struct LScreen*)s);
+
+	s->widgets    = directConnect_widgets;
+	s->maxWidgets = Array_Elems(directConnect_widgets);
+
+	s->Activated      = DirectConnectScreen_Activated;
+	s->LoadState      = DirectConnectScreen_Load;
+	s->title          = "Direct connect";
+	s->onEnterWidget  = (struct LWidget*)&s->btnConnect;
+	s->onEscapeWidget = (struct LWidget*)&s->btnBack;
+
+	Launcher_SetScreen((struct LScreen*)s);
+}
+
+
+/*########################################################################################################################*
+*----------------------------------------------------------MFAScreen------------------------------------------------------*
+*#########################################################################################################################*/
+static struct MFAScreen {
+	LScreen_Layout
+	struct LInput iptCode;
+	struct LButton btnSignIn, btnCancel;
+	struct LLabel  lblTitle;
+} MFAScreen;
+
+#define MFA_SCREEN_MAX_WIDGETS 4
+static struct LWidget* mfa_widgets[MFA_SCREEN_MAX_WIDGETS];
+
+LAYOUTS mfa_lblTitle[]  = { { ANCHOR_CENTRE,   0 }, { ANCHOR_CENTRE, -115 } };
+LAYOUTS mfa_iptCode[]   = { { ANCHOR_CENTRE,   0 }, { ANCHOR_CENTRE,  -75 } };
+LAYOUTS mfa_btnSignIn[] = { { ANCHOR_CENTRE, -90 }, { ANCHOR_CENTRE,  -25 } };
+LAYOUTS mfa_btnCancel[] = { { ANCHOR_CENTRE,  90 }, { ANCHOR_CENTRE,  -25 } };
+
+
+static void MainScreen_DoLogin(void);
+static void MFAScreen_SignIn(void* w) {
+	MainScreen_SetActive();
+	MainScreen_DoLogin();
+}
+static void MFAScreen_Cancel(void* w) {
+	LInput_ClearText(&MFAScreen.iptCode);
+	MainScreen_SetActive();
+}
+
+static void MFAScreen_Activated(struct LScreen* s_) {
+	struct MFAScreen* s = (struct MFAScreen*)s_;
+	s->iptCode.inputType = KEYBOARD_TYPE_INTEGER;
+	
+	LLabel_Add(s,  &s->lblTitle,  "",                  mfa_lblTitle);
+	LInput_Add(s,  &s->iptCode,   280, "Login code..", mfa_iptCode);
+	LButton_Add(s, &s->btnSignIn, 100, 35, "Sign in",  
+				MFAScreen_SignIn, mfa_btnSignIn);
+	LButton_Add(s, &s->btnCancel, 100, 35, "Cancel",   
+				MFAScreen_Cancel, mfa_btnCancel);
+
+	LLabel_SetConst(&s->lblTitle, s->iptCode.text.length ?
+		"&cWrong code entered  (Check emails)" :
+		"&cLogin code required (Check emails)");
+}
+
+void MFAScreen_SetActive(void) {
+	struct MFAScreen* s = &MFAScreen;
+	LScreen_Reset((struct LScreen*)s);
+
+	s->widgets    = mfa_widgets;
+	s->maxWidgets = Array_Elems(mfa_widgets);
+
+	s->Activated     = MFAScreen_Activated;
+	s->title         = "Enter login code";
+	s->onEnterWidget = (struct LWidget*)&s->btnSignIn;
+
+	Launcher_SetScreen((struct LScreen*)s);
+}
+
+
+/*########################################################################################################################*
+*----------------------------------------------------------SplitScreen----------------------------------------------------*
+*#########################################################################################################################*/
+#ifdef CC_BUILD_SPLITSCREEN
+static struct SplitScreen {
+	LScreen_Layout
+	struct LButton btnPlayers[3], btnBack;
+	cc_bool signingIn;
+} SplitScreen;
+
+#define SPLITSCREEN_MAX_WIDGETS 4
+static struct LWidget* split_widgets[SPLITSCREEN_MAX_WIDGETS];
+
+LAYOUTS sps_btnPlayers2[] = { { ANCHOR_CENTRE, 0 }, { ANCHOR_CENTRE, -120 } };
+LAYOUTS sps_btnPlayers3[] = { { ANCHOR_CENTRE, 0 }, { ANCHOR_CENTRE,  -70 } };
+LAYOUTS sps_btnPlayers4[] = { { ANCHOR_CENTRE, 0 }, { ANCHOR_CENTRE,  -20 } };
+LAYOUTS sps_btnBack[]     = { { ANCHOR_CENTRE, 0 }, { ANCHOR_CENTRE,  170 } };
+
+static void SplitScreen_Start(int players) {
+	static const cc_string user = String_FromConst(DEFAULT_USERNAME);
+	Game_NumLocalPlayers = players;
+	
+	Launcher_StartGame(&user, &String_Empty, &String_Empty, &String_Empty, &String_Empty);
+}
+
+static void SplitScreen_Players2(void* w) { SplitScreen_Start(2); }
+static void SplitScreen_Players3(void* w) { SplitScreen_Start(3); }
+static void SplitScreen_Players4(void* w) { SplitScreen_Start(4); }
+
+static void SplitScreen_Activated(struct LScreen* s_) {
+	struct SplitScreen* s = (struct SplitScreen*)s_;
+
+	LButton_Add(s, &s->btnPlayers[0], 300, 35, "2 player splitscreen", 
+				SplitScreen_Players2, sps_btnPlayers2);
+	LButton_Add(s, &s->btnPlayers[1], 300, 35, "3 player splitscreen", 
+				SplitScreen_Players3, sps_btnPlayers3);
+	LButton_Add(s, &s->btnPlayers[2], 300, 35, "4 player splitscreen", 
+				SplitScreen_Players4, sps_btnPlayers4);
+
+	LButton_Add(s, &s->btnBack, 100, 35, "Back", 
+				SwitchToMain, sps_btnBack);
+}
+
+void SplitScreen_SetActive(void) {
+	struct SplitScreen* s = &SplitScreen;
+	LScreen_Reset((struct LScreen*)s);
+	
+	s->widgets    = split_widgets;
+	s->maxWidgets = Array_Elems(split_widgets);
+
+	s->Activated     = SplitScreen_Activated;
+	s->title         = "Splitscreen mode";
+
+	Launcher_SetScreen((struct LScreen*)s);
+}
+
+static void SwitchToSplitScreen(void* w) { SplitScreen_SetActive(); }
+#endif
+
+
+/*########################################################################################################################*
+*----------------------------------------------------------MainScreen-----------------------------------------------------*
+*#########################################################################################################################*/
+static struct MainScreen {
+	LScreen_Layout
+	struct LButton btnLogin, btnResume, btnDirect, btnSPlayer, btnSplit;
+	struct LButton btnRegister, btnOptions, btnUpdates;
+	struct LInput iptUsername, iptPassword;
+	struct LLabel lblStatus, lblUpdate;
+	cc_bool signingIn;
+} MainScreen;
+
+#define MAINSCREEN_MAX_WIDGETS 12
+static struct LWidget* main_widgets[MAINSCREEN_MAX_WIDGETS];
+
+LAYOUTS main_iptUsername[] = { { ANCHOR_CENTRE_MIN, -140 }, { ANCHOR_CENTRE, -120 } };
+LAYOUTS main_iptPassword[] = { { ANCHOR_CENTRE_MIN, -140 }, { ANCHOR_CENTRE,  -75 } };
+
+LAYOUTS main_btnLogin[]  = { { ANCHOR_CENTRE, -90 }, { ANCHOR_CENTRE, -25 } };
+LAYOUTS main_lblStatus[] = { { ANCHOR_CENTRE,   0 }, { ANCHOR_CENTRE,  20 } };
+
+LAYOUTS main_btnResume[]  = { { ANCHOR_CENTRE, 90 }, { ANCHOR_CENTRE, -25 } };
+LAYOUTS main_btnDirect[]  = { { ANCHOR_CENTRE,  0 }, { ANCHOR_CENTRE,  60 } };
+LAYOUTS main_btnSPlayer[] = { { ANCHOR_CENTRE,  0 }, { ANCHOR_CENTRE, 110 } };
+LAYOUTS main_btnSplit[]   = { { ANCHOR_CENTRE,  0 }, { ANCHOR_CENTRE, 160 } };
+
+LAYOUTS main_btnRegister[] = { { ANCHOR_MIN,    6 }, { ANCHOR_MAX,  6 } };
+LAYOUTS main_btnOptions[]  = { { ANCHOR_CENTRE, 0 }, { ANCHOR_MAX,  6 } };
+LAYOUTS main_btnUpdates[]  = { { ANCHOR_MAX,    6 }, { ANCHOR_MAX,  6 } };
+
+LAYOUTS main_lblUpdate_N[] = { { ANCHOR_MAX,   10 }, { ANCHOR_MAX, 45 } };
+LAYOUTS main_lblUpdate_H[] = { { ANCHOR_MAX,   10 }, { ANCHOR_MAX, 6 } };
+
+
+struct ResumeInfo {
+	cc_string user, ip, port, server, mppass;
+	char _userBuffer[STRING_SIZE], _serverBuffer[STRING_SIZE];
+	char _ipBuffer[16], _portBuffer[16], _mppassBuffer[STRING_SIZE];
+	cc_bool valid;
+};
+
+CC_NOINLINE static void MainScreen_GetResume(struct ResumeInfo* info, cc_bool full) {
+	String_InitArray(info->server, info->_serverBuffer);
+	Options_Get(ROPT_SERVER,       &info->server, "");
+	String_InitArray(info->user,   info->_userBuffer);
+	Options_Get(ROPT_USER,         &info->user, "");
+
+	String_InitArray(info->ip,   info->_ipBuffer);
+	Options_Get(ROPT_IP,         &info->ip, "");
+	String_InitArray(info->port, info->_portBuffer);
+	Options_Get(ROPT_PORT,       &info->port, "");
+
+	if (!full) return;
+	String_InitArray(info->mppass, info->_mppassBuffer);
+	Options_GetSecure(ROPT_MPPASS, &info->mppass);
+
+	info->valid = 
+		info->user.length && info->mppass.length &&
+		info->ip.length   && info->port.length;
+}
+
+CC_NOINLINE static void MainScreen_Error(struct HttpRequest* req, const char* action) {
+	cc_string str; char strBuffer[STRING_SIZE];
+	struct MainScreen* s = &MainScreen;
+	String_InitArray(str, strBuffer);
+
+	Launcher_DisplayHttpError(req, action, &str);
+	LLabel_SetText(&s->lblStatus, &str);
+	s->signingIn = false;
+}
+
+static void MainScreen_DoLogin(void) {
+	struct MainScreen* s = &MainScreen;
+	cc_string* user = &s->iptUsername.text;
+	cc_string* pass = &s->iptPassword.text;
+
+	if (!user->length) {
+		LLabel_SetConst(&s->lblStatus, "&cUsername required"); return;
+	}
+	if (!pass->length) {
+		LLabel_SetConst(&s->lblStatus, "&cPassword required"); return;
+	}
+
+	if (GetTokenTask.Base.working) return;
+	Options_Set(LOPT_USERNAME, user);
+	Options_SetSecure(LOPT_PASSWORD, pass);
+
+	GetTokenTask_Run();
+	LLabel_SetConst(&s->lblStatus, "&eSigning in..");
+	s->signingIn = true;
+}
+static void MainScreen_Login(void* w) { MainScreen_DoLogin(); }
+
+static void MainScreen_Register(void* w) {
+	static const cc_string regUrl = String_FromConst(REGISTERNEW_URL);
+	cc_result res = Process_StartOpen(&regUrl);
+	if (res) Logger_SimpleWarn(res, "opening register webpage in browser");
+}
+
+static void MainScreen_Resume(void* w) {
+	struct ResumeInfo info;
+	MainScreen_GetResume(&info, true);
+
+	if (!info.valid) return;
+	Launcher_StartGame(&info.user, &info.mppass, &info.ip, &info.port, &info.server);
+}
+
+static void MainScreen_Singleplayer(void* w) {
+	static const cc_string defUser = String_FromConst(DEFAULT_USERNAME);
+	const cc_string* user = &MainScreen.iptUsername.text;
+
+	if (!user->length) user = &defUser;
+	Launcher_StartGame(user, &String_Empty, &String_Empty, &String_Empty, &String_Empty);
+}
+
+static void MainScreen_ResumeHover(void* w) {
+	cc_string str; char strBuffer[STRING_SIZE];
+	struct MainScreen* s = &MainScreen;
+	struct ResumeInfo info;
+	if (s->signingIn) return;
+
+	MainScreen_GetResume(&info, false);
+	if (!info.user.length) return;
+	String_InitArray(str, strBuffer);
+
+	if (info.server.length && String_Equals(&info.user,	&s->iptUsername.text)) {
+		String_Format1(&str, "&eResume to %s", &info.server);
+	} else if (info.server.length) {
+		String_Format2(&str, "&eResume as %s to %s", &info.user, &info.server);
+	} else {
+		String_Format3(&str, "&eResume as %s to %s:%s", &info.user, &info.ip, &info.port);
+	}
+	LLabel_SetText(&s->lblStatus, &str);
+}
+
+static void MainScreen_ResumeUnhover(void* w) {
+	struct MainScreen* s = &MainScreen;
+	if (s->signingIn) return;
+
+	LLabel_SetConst(&s->lblStatus, "");
+}
+
+CC_NOINLINE static cc_uint32 MainScreen_GetVersion(const cc_string* version) {
+	cc_uint8 raw[4] = { 0, 0, 0, 0 };
+	cc_string parts[4];
+	int i, count;
+	
+	/* 1.0.1 -> { 1, 0, 1, 0 } */
+	count = String_UNSAFE_Split(version, '.', parts, 4);
+	for (i = 0; i < count; i++) 
+	{
+		Convert_ParseUInt8(&parts[i], &raw[i]);
+	}
+	return Stream_GetU32_BE(raw);
+}
+
+static void MainScreen_ApplyUpdateLabel(struct MainScreen* s) {
+	static const cc_string currentStr = String_FromConst(GAME_APP_VER);
+	cc_uint32 latest, current;
+
+	if (CheckUpdateTask.Base.success) {
+		latest  = MainScreen_GetVersion(&CheckUpdateTask.latestRelease);
+		current = MainScreen_GetVersion(&currentStr);
+#ifdef CC_BUILD_FLATPAK
+		LLabel_SetConst(&s->lblUpdate, false ? "&aUpdate available" : "&eUp to date");
+#else
+		LLabel_SetConst(&s->lblUpdate, false ? "&aNew release" : "&eUp to date");
+#endif
+	} else {
+		LLabel_SetConst(&s->lblUpdate, "&cCheck failed");
+	}
+}
+
+#ifdef CC_BUILD_CONSOLE
+static void MainScreen_ExitApp(void* w) {
+	Window_Main.Exists = false;
+}
+#endif
+
+static void MainScreen_Activated(struct LScreen* s_) {
+	struct MainScreen* s = (struct MainScreen*)s_;
+
+	s->iptPassword.inputType = KEYBOARD_TYPE_PASSWORD;
+	s->lblUpdate.small       = true;
+
+#ifdef CC_BUILD_NETWORKING
+	LInput_Add(s,  &s->iptUsername, 280, "Username..",  main_iptUsername);
+	LInput_Add(s,  &s->iptPassword, 280, "Password..",  main_iptPassword);
+	LButton_Add(s, &s->btnLogin,    100, 35, "Sign in", 
+				MainScreen_Login,  main_btnLogin);
+	LButton_Add(s, &s->btnResume,   100, 35, "Resume",  
+				MainScreen_Resume, main_btnResume);
+#endif
+
+	LLabel_Add(s,  &s->lblStatus,  "",  main_lblStatus);
+#ifdef CC_BUILD_NETWORKING
+	LButton_Add(s, &s->btnDirect,  200, 35, "Direct connect", 
+				SwitchToDirectConnect,   main_btnDirect);
+#endif
+	LButton_Add(s, &s->btnSPlayer, 200, 35, "Singleplayer",
+				MainScreen_Singleplayer, main_btnSPlayer);
+#ifdef CC_BUILD_SPLITSCREEN
+	LButton_Add(s, &s->btnSplit,   200, 35, "Splitscreen (WIP)", 
+				SwitchToSplitScreen,     main_btnSplit);
+#endif
+
+	if (Process_OpenSupported) {
+		LButton_Add(s, &s->btnRegister, 100, 35, "Register", 
+					MainScreen_Register, main_btnRegister);
+	}
+
+	LButton_Add(s, &s->btnOptions, 100, 35, "Options", 
+				SwitchToSettings, main_btnOptions);
+
+#ifdef CC_BUILD_CONSOLE
+	LLabel_Add(s,  &s->lblUpdate,  "&eChecking..", main_lblUpdate_N);
+	LButton_Add(s, &s->btnUpdates,  100, 35, "Exit", 
+				MainScreen_ExitApp, main_btnUpdates);
+#else
+	LLabel_Add(s,  &s->lblUpdate,  "&eChecking..",      
+				Updater_Supported ? main_lblUpdate_N : main_lblUpdate_H);
+	if (Updater_Supported) {
+		LButton_Add(s, &s->btnUpdates,  100, 35, "Updates", 
+					SwitchToUpdates, main_btnUpdates);
+	}
+#endif
+
+	s->btnResume.OnHover   = MainScreen_ResumeHover;
+	s->btnResume.OnUnhover = MainScreen_ResumeUnhover;
+
+	if (CheckUpdateTask.Base.completed)
+		MainScreen_ApplyUpdateLabel(s);
+}
+
+static void MainScreen_Load(struct LScreen* s_) {
+	cc_string user, pass; char passBuffer[STRING_SIZE];
+	struct MainScreen* s = (struct MainScreen*)s_;
+
+	String_InitArray(pass, passBuffer);
+	Options_UNSAFE_Get(LOPT_USERNAME, &user);
+	Options_GetSecure(LOPT_PASSWORD, &pass);
+
+	LInput_SetText(&s->iptUsername, &user);
+	LInput_SetText(&s->iptPassword, &pass);
+
+	/* Auto sign in when automatically joining a server */
+	if (!Launcher_AutoHash.length)    return;
+	if (!user.length || !pass.length) return;
+	MainScreen_DoLogin();
+}
+
+static void MainScreen_TickCheckUpdates(struct MainScreen* s) {
+	if (!CheckUpdateTask.Base.working)   return;
+	LWebTask_Tick(&CheckUpdateTask.Base, NULL);
+
+	if (!CheckUpdateTask.Base.completed) return;
+	MainScreen_ApplyUpdateLabel(s);
+}
+
+static void MainScreen_LoginPhase2(struct MainScreen* s, const cc_string* user) {
+	/* website returns case correct username */
+	if (!String_Equals(&s->iptUsername.text, user)) {
+		LInput_SetText(&s->iptUsername, user);
+	}
+	String_Copy(&Launcher_Username, user);
+
+	FetchServersTask_Run();
+	LLabel_SetConst(&s->lblStatus, "&eRetrieving servers list..");
+}
+
+static void MainScreen_SignInError(struct HttpRequest* req) {
+	MainScreen_Error(req, "signing in");
+}
+
+static void MainScreen_TickGetToken(struct MainScreen* s) {
+	if (!GetTokenTask.Base.working)   return;
+	LWebTask_Tick(&GetTokenTask.Base, MainScreen_SignInError);
+
+	if (!GetTokenTask.Base.completed) return;
+	if (!GetTokenTask.Base.success)   return;
+
+	if (!GetTokenTask.error && String_CaselessEquals(&GetTokenTask.username, &s->iptUsername.text)) {
+		/* Already logged in, go straight to fetching servers */
+		MainScreen_LoginPhase2(s, &GetTokenTask.username);
+	} else {
+		SignInTask_Run(&s->iptUsername.text, &s->iptPassword.text,
+						&MFAScreen.iptCode.text);
+	}
+}
+
+static void MainScreen_TickSignIn(struct MainScreen* s) {
+	if (!SignInTask.Base.working)   return;
+	LWebTask_Tick(&SignInTask.Base, MainScreen_SignInError);
+	if (!SignInTask.Base.completed) return;
+
+	if (SignInTask.needMFA) { MFAScreen_SetActive(); return; }
+
+	if (SignInTask.error) {
+		LLabel_SetConst(&s->lblStatus, SignInTask.error);
+	} else if (SignInTask.Base.success) {
+		MainScreen_LoginPhase2(s, &SignInTask.username);
+	}
+}
+
+static void MainScreen_ServersError(struct HttpRequest* req) {
+	MainScreen_Error(req, "retrieving servers list");
+}
+
+static void MainScreen_TickFetchServers(struct MainScreen* s) {
+	if (!FetchServersTask.Base.working)   return;
+	LWebTask_Tick(&FetchServersTask.Base, MainScreen_ServersError);
+
+	if (!FetchServersTask.Base.completed) return;
+	if (!FetchServersTask.Base.success)   return;
+
+	s->signingIn = false;
+	if (Launcher_AutoHash.length) {
+		Launcher_ConnectToServer(&Launcher_AutoHash);
+		Launcher_AutoHash.length = 0;
+	} else {
+		ServersScreen_SetActive();
+	}
+}
+
+static void MainScreen_Tick(struct LScreen* s_) {
+	struct MainScreen* s = (struct MainScreen*)s_;
+	LScreen_Tick(s_);
+
+	MainScreen_TickCheckUpdates(s);
+	MainScreen_TickGetToken(s);
+	MainScreen_TickSignIn(s);
+	MainScreen_TickFetchServers(s);
+}
+
+void MainScreen_SetActive(void) {
+	struct MainScreen* s = &MainScreen;
+	LScreen_Reset((struct LScreen*)s);
+	
+	s->widgets    = main_widgets;
+	s->maxWidgets = Array_Elems(main_widgets);
+
+	s->Activated     = MainScreen_Activated;
+	s->LoadState     = MainScreen_Load;
+	s->Tick          = MainScreen_Tick;
+	s->title         = "ClassiHax";
+
+#ifdef CC_BUILD_NETWORKING
+	s->onEnterWidget = (struct LWidget*)&s->btnLogin;
+#else
+	s->onEnterWidget = (struct LWidget*)&s->btnSPlayer;
+#endif
+
+	Launcher_SetScreen((struct LScreen*)s);
+}
+
+
+#ifdef CC_BUILD_RESOURCES
+/*########################################################################################################################*
+*----------------------------------------------------CheckResourcesScreen-------------------------------------------------*
+*#########################################################################################################################*/
+static struct CheckResourcesScreen {
+	LScreen_Layout
+	struct LLabel  lblLine1, lblLine2, lblStatus;
+	struct LButton btnYes, btnNo;
+} CheckResourcesScreen;
+
+#define CHECKRESOURCES_SCREEN_MAX_WIDGET 5
+static struct LWidget* checkResources_widgets[CHECKRESOURCES_SCREEN_MAX_WIDGET];
+
+LAYOUTS cres_lblLine1[]  = { { ANCHOR_CENTRE, 0 }, { ANCHOR_CENTRE, -50 } };
+LAYOUTS cres_lblLine2[]  = { { ANCHOR_CENTRE, 0 }, { ANCHOR_CENTRE, -30 } };
+LAYOUTS cres_lblStatus[] = { { ANCHOR_CENTRE, 0 }, { ANCHOR_CENTRE,  10 } };
+
+LAYOUTS cres_btnYes[] = { { ANCHOR_CENTRE, -70 }, { ANCHOR_CENTRE, 45 } };
+LAYOUTS cres_btnNo[]  = { { ANCHOR_CENTRE,  70 }, { ANCHOR_CENTRE, 45 } };
+
+
+static void CheckResourcesScreen_Yes(void*  w) { FetchResourcesScreen_SetActive(); }
+static void CheckResourcesScreen_Next(void* w) {
+	Http_ClearPending();
+	if (Options_LoadResult != ReturnCode_FileNotFound) {
+		MainScreen_SetActive();
+	} else {
+		ChooseModeScreen_SetActive(true);
+	}
+}
+
+static void CheckResourcesScreen_AddWidgets(struct CheckResourcesScreen* s) {
+	const char* line1_msg = Resources_MissingRequired ? "Some required resources weren't found" 
+														: "Some optional resources weren't found";
+	s->lblStatus.small = true;
+
+	LLabel_Add(s,  &s->lblLine1,  line1_msg,           cres_lblLine1);
+	LLabel_Add(s,  &s->lblLine2,  "Okay to download?", cres_lblLine2);
+	LLabel_Add(s,  &s->lblStatus, "",                  cres_lblStatus);
+
+	LButton_Add(s, &s->btnYes, 70, 35, "Yes", 
+				CheckResourcesScreen_Yes,  cres_btnYes);
+	LButton_Add(s, &s->btnNo,  70, 35, "No",  
+				CheckResourcesScreen_Next, cres_btnNo);
+}
+
+static void CheckResourcesScreen_Activated(struct LScreen* s_) {
+	struct CheckResourcesScreen* s = (struct CheckResourcesScreen*)s_;
+	cc_string str; char buffer[STRING_SIZE];
+	float size;
+	CheckResourcesScreen_AddWidgets(s);
+		
+	size = Resources_MissingSize / 1024.0f;
+	String_InitArray(str, buffer);
+	String_Format1(&str, "&eDownload size: %f2 megabytes", &size);
+	LLabel_SetText(&s->lblStatus, &str);
+}
+
+static void CheckResourcesScreen_ResetArea(struct Context2D* ctx, int x, int y, int width, int height) {
+	int R = BitmapCol_R(Launcher_Theme.BackgroundColor) * 0.78f; /* 153 -> 120 */
+	int G = BitmapCol_G(Launcher_Theme.BackgroundColor) * 0.70f; /* 127 ->  89 */
+	int B = BitmapCol_B(Launcher_Theme.BackgroundColor) * 0.88f; /* 172 -> 151 */
+
+	Gradient_Noise(ctx, BitmapColor_RGB(R, G, B), 4, x, y, width, height);
+}
+
+static void CheckResourcesScreen_DrawBackground(struct LScreen* s, struct Context2D* ctx) {
+	int x, y, width, height;
+	BitmapCol color = BitmapColor_Scale(Launcher_Theme.BackgroundColor, 0.2f);
+	Context2D_Clear(ctx, color, 0, 0, ctx->width, ctx->height);
+	width  = Display_ScaleX(380);
+	height = Display_ScaleY(140);
+
+	x = Gui_CalcPos(ANCHOR_CENTRE, 0, width,  ctx->width);
+	y = Gui_CalcPos(ANCHOR_CENTRE, 0, height, ctx->height);
+	CheckResourcesScreen_ResetArea(ctx, x, y, width, height);
+}
+
+void CheckResourcesScreen_SetActive(void) {
+	struct CheckResourcesScreen* s = &CheckResourcesScreen;
+	LScreen_Reset((struct LScreen*)s);
+
+	s->widgets    = checkResources_widgets;
+	s->maxWidgets = Array_Elems(checkResources_widgets);
+
+	s->Activated      = CheckResourcesScreen_Activated;
+	s->DrawBackground = CheckResourcesScreen_DrawBackground;
+	s->ResetArea      = CheckResourcesScreen_ResetArea;
+	s->onEnterWidget  = (struct LWidget*)&s->btnYes;
+
+	Launcher_SetScreen((struct LScreen*)s);
+}
+
+
+/*########################################################################################################################*
+*----------------------------------------------------FetchResourcesScreen-------------------------------------------------*
+*#########################################################################################################################*/
+static struct FetchResourcesScreen {
+	LScreen_Layout
+	struct LLabel  lblStatus;
+	struct LButton btnCancel;
+	struct LSlider sdrProgress;
+} FetchResourcesScreen;
+
+#define FETCHRESOURCES_SCREEN_MAX_WIDGETS 3
+static struct LWidget* fetchResources_widgets[FETCHRESOURCES_SCREEN_MAX_WIDGETS];
+
+LAYOUTS fres_lblStatus[]   = { { ANCHOR_CENTRE, 0 }, { ANCHOR_CENTRE, -10 } };
+LAYOUTS fres_btnCancel[]   = { { ANCHOR_CENTRE, 0 }, { ANCHOR_CENTRE,  45 } };
+LAYOUTS fres_sdrProgress[] = { { ANCHOR_CENTRE, 0 }, { ANCHOR_CENTRE,  15 } };
+
+
+static void FetchResourcesScreen_Error(struct HttpRequest* req) {
+	cc_string str; char buffer[STRING_SIZE];
+	String_InitArray(str, buffer);
+
+	Launcher_DisplayHttpError(req, "downloading resources", &str);
+	LLabel_SetText(&FetchResourcesScreen.lblStatus, &str);
+}
+
+static void FetchResourcesScreen_Activated(struct LScreen* s_) {
+	struct FetchResourcesScreen* s = (struct FetchResourcesScreen*)s_;
+	s->lblStatus.small = true;
+
+	LLabel_Add(s,  &s->lblStatus,   "",    fres_lblStatus);
+	LButton_Add(s, &s->btnCancel,   120, 35, "Cancel",                   
+				CheckResourcesScreen_Next, fres_btnCancel);
+	LSlider_Add(s, &s->sdrProgress, 200, 12, BitmapColor_RGB(0, 220, 0), fres_sdrProgress);
+
+	Fetcher_ErrorCallback = FetchResourcesScreen_Error;
+	Fetcher_Run(); 
+}
+
+static void FetchResourcesScreen_UpdateStatus(struct FetchResourcesScreen* s, int reqID) {
+	cc_string str; char strBuffer[STRING_SIZE];
+	const char* name;
+	int count;
+
+	name = Fetcher_RequestName(reqID);
+	if (!name) return;
+
+	String_InitArray(str, strBuffer);
+	count = Fetcher_Downloaded + 1;
+	String_Format3(&str, "&eFetching %c.. (%i/%i)", name, &count, &Resources_MissingCount);
+
+	if (String_Equals(&str, &s->lblStatus.text)) return;
+	LLabel_SetText(&s->lblStatus, &str);
+}
+
+static void FetchResourcesScreen_UpdateProgress(struct FetchResourcesScreen* s) {
+	int reqID, progress;
+
+	if (!Http_GetCurrent(&reqID, &progress)) return;
+	FetchResourcesScreen_UpdateStatus(s, reqID);
+	/* making request still, haven't started download yet */
+	if (progress < 0 || progress > 100) return;
+
+	LSlider_SetProgress(&s->sdrProgress, progress);
+}
+
+static void FetchResourcesScreen_Tick(struct LScreen* s_) {
+	struct FetchResourcesScreen* s = (struct FetchResourcesScreen*)s_;
+	if (!Fetcher_Working) return;
+
+	FetchResourcesScreen_UpdateProgress(s);
+	Fetcher_Update();
+
+	if (!Fetcher_Completed) return;
+	if (Fetcher_Failed)     return;
+
+	Launcher_TryLoadTexturePack();
+	CheckResourcesScreen_Next(NULL);
+}
+
+void FetchResourcesScreen_SetActive(void) {
+	struct FetchResourcesScreen* s = &FetchResourcesScreen;
+	LScreen_Reset((struct LScreen*)s);
+
+	s->widgets    = fetchResources_widgets;
+	s->maxWidgets = Array_Elems(fetchResources_widgets);
+
+	s->Activated      = FetchResourcesScreen_Activated;
+	s->Tick           = FetchResourcesScreen_Tick;
+	s->DrawBackground = CheckResourcesScreen_DrawBackground;
+	s->ResetArea      = CheckResourcesScreen_ResetArea;
+
+	Launcher_SetScreen((struct LScreen*)s);
+}
+
+#endif
+
+/*########################################################################################################################*
+*--------------------------------------------------------ServersScreen----------------------------------------------------*
+*#########################################################################################################################*/
+static struct ServersScreen {
+	LScreen_Layout
+	struct LInput iptSearch, iptHash;
+	struct LButton btnBack, btnConnect, btnRefresh;
+	struct LTable table;
+	struct FontDesc rowFont;
+	float tableAcc;
+} ServersScreen;
+
+static struct LWidget* servers_widgets[6];
+
+LAYOUTS srv_iptSearch[] = { { ANCHOR_MIN, 10 }, { ANCHOR_MIN, 10 } };
+LAYOUTS srv_iptHash[]   = { { ANCHOR_MIN, 10 }, { ANCHOR_MAX, 10 } };
+LAYOUTS srv_table[5]    = { { ANCHOR_MIN, 10 }, { ANCHOR_MIN | LLAYOUT_EXTRA, 50 }, { LLAYOUT_WIDTH, 0 }, { LLAYOUT_HEIGHT, 50 } };
+
+LAYOUTS srv_btnBack[]    = { { ANCHOR_MAX,  10 }, { ANCHOR_MIN, 10 } };
+LAYOUTS srv_btnConnect[] = { { ANCHOR_MAX,  10 }, { ANCHOR_MAX, 10 } };
+LAYOUTS srv_btnRefresh[] = { { ANCHOR_MAX, 135 }, { ANCHOR_MIN, 10 } };
+	
+
+static void ServersScreen_Connect(void* w) {
+	struct LTable* table = &ServersScreen.table;
+	cc_string* hash      = &ServersScreen.iptHash.text;
+
+	if (!hash->length && table->rowsCount) { 
+		hash = &LTable_Get(table->topRow)->hash; 
+	}
+	Launcher_ConnectToServer(hash);
+}
+
+static void ServersScreen_Refresh(void* w) {
+	struct LButton* btn;
+	if (FetchServersTask.Base.working) return;
+
+	FetchServersTask_Run();
+	btn = &ServersScreen.btnRefresh;
+	LButton_SetConst(btn, "&eWorking..");
+}
+
+static void ServersScreen_HashFilter(cc_string* str) {
+	int lastIndex;
+	if (!str->length) return;
+
+	/* Server url look like http://www.classicube.net/server/play/aaaaa/ */
+	/* Trim it to only be the aaaaa */
+	if (str->buffer[str->length - 1] == '/') str->length--;
+
+	/* Trim the URL parts before the hash */
+	lastIndex = String_LastIndexOf(str, '/');
+	if (lastIndex == -1) return;
+	*str = String_UNSAFE_SubstringAt(str, lastIndex + 1);
+}
+
+static void ServersScreen_SearchChanged(struct LInput* w) {
+	struct ServersScreen* s = &ServersScreen;
+	LTable_ApplyFilter(&s->table);
+	LBackend_MarkDirty(&s->table);
+}
+
+static void ServersScreen_HashChanged(struct LInput* w) {
+	struct ServersScreen* s = &ServersScreen;
+	LTable_ShowSelected(&s->table);
+	LBackend_MarkDirty(&s->table);
+}
+
+static void ServersScreen_OnSelectedChanged(void) {
+	struct ServersScreen* s = &ServersScreen;
+	LBackend_MarkDirty(&s->iptHash);
+	LBackend_MarkDirty(&s->table);
+}
+
+static void ServersScreen_ReloadServers(struct ServersScreen* s) {
+	int i;
+	LTable_Sort(&s->table);
+
+	for (i = 0; i < FetchServersTask.numServers; i++) 
+	{
+		FetchFlagsTask_Add(&FetchServersTask.servers[i]);
+	}
+}
+
+static void ServersScreen_AddWidgets(struct ServersScreen* s) {
+	LInput_Add(s,  &s->iptSearch, 370, "Search servers..",               srv_iptSearch);
+	LInput_Add(s,  &s->iptHash,   475, "classicube.net/server/play/...", srv_iptHash);
+
+	LButton_Add(s, &s->btnBack,    110, 30, "Back",    
+				SwitchToMain,          srv_btnBack);
+	LButton_Add(s, &s->btnConnect, 130, 30, "Connect", 
+				ServersScreen_Connect, srv_btnConnect);
+	LButton_Add(s, &s->btnRefresh, 110, 30, "Refresh", 
+				ServersScreen_Refresh, srv_btnRefresh);
+
+	s->iptSearch.skipsEnter    = true;
+	s->iptSearch.TextChanged   = ServersScreen_SearchChanged;
+	s->iptHash.TextChanged     = ServersScreen_HashChanged;
+	s->iptHash.ClipboardFilter = ServersScreen_HashFilter;
+
+	s->table.filter       = &s->iptSearch.text;
+	s->table.selectedHash = &s->iptHash.text;
+	s->table.OnSelectedChanged = ServersScreen_OnSelectedChanged;
+
+	if (s->table.VTABLE) {
+		LScreen_AddWidget(s, &s->table);
+	} else {
+		LTable_Add(s, &s->table, srv_table);
+	}
+	LTable_Reset(&s->table);
+}
+
+static void ServersScreen_Activated(struct LScreen* s_) {
+	struct ServersScreen* s = (struct ServersScreen*)s_;
+	ServersScreen_AddWidgets(s);
+	s->tableAcc = 0.0f;
+
+	LInput_ClearText(&s->iptHash);
+	LInput_ClearText(&s->iptSearch);
+
+	ServersScreen_ReloadServers(s);
+	/* This is so typing on keyboard by default searchs server list */
+	/* But don't do that when it would cause on-screen keyboard to show */
+	if (Window_Main.SoftKeyboard) return;
+	LScreen_SelectWidget(s_, 0, (struct LWidget*)&s->iptSearch, false);
+}
+
+static void ServersScreen_Tick(struct LScreen* s_) {
+	struct ServersScreen* s = (struct ServersScreen*)s_;
+	LScreen_Tick(s_);
+	LWebTask_Tick(&FetchFlagsTask.Base, NULL);
+
+	if (!FetchServersTask.Base.working) return;
+	LWebTask_Tick(&FetchServersTask.Base, NULL);
+	if (!FetchServersTask.Base.completed) return;
+
+	if (FetchServersTask.Base.success) {
+		ServersScreen_ReloadServers(s);
+		LBackend_MarkDirty(&s->table);
+	}
+
+	LButton_SetConst(&s->btnRefresh, 
+				FetchServersTask.Base.success ? "Refresh" : "&cFailed");
+}
+
+static void ServersScreen_MouseWheel(struct LScreen* s_, float delta) {
+	struct ServersScreen* s = (struct ServersScreen*)s_;
+	s->table.VTABLE->MouseWheel(&s->table, delta);
+}
+
+static void ServersScreen_KeyDown(struct LScreen* s_, int key, cc_bool was) {
+	struct ServersScreen* s = (struct ServersScreen*)s_;
+	if (!LTable_HandlesKey(key)) {
+		LScreen_KeyDown(s_, key, was);
+	} else {
+		s->table.VTABLE->KeyDown(&s->table, key, was);
+	}
+}
+
+static void ServersScreen_MouseUp(struct LScreen* s_, int idx) {
+	struct ServersScreen* s = (struct ServersScreen*)s_;
+	s->table.VTABLE->OnUnselect(&s->table, idx);
+}
+
+void ServersScreen_SetActive(void) {
+	struct ServersScreen* s = &ServersScreen;
+	LScreen_Reset((struct LScreen*)s);
+
+	s->widgets    = servers_widgets;
+	s->maxWidgets = Array_Elems(servers_widgets);
+
+	s->Activated  = ServersScreen_Activated;
+	s->Tick       = ServersScreen_Tick;
+	s->MouseWheel = ServersScreen_MouseWheel;
+	s->KeyDown    = ServersScreen_KeyDown;
+	s->MouseUp    = ServersScreen_MouseUp;
+
+	s->onEnterWidget  = (struct LWidget*)&s->btnConnect;
+	s->onEscapeWidget = (struct LWidget*)&s->btnBack;
+
+	Launcher_SetScreen((struct LScreen*)s);
+}
+
+
+/*########################################################################################################################*
+*--------------------------------------------------------SettingsScreen---------------------------------------------------*
+*#########################################################################################################################*/
+static struct SettingsScreen {
+	LScreen_Layout
+	struct LButton btnMode, btnColours, btnBack;
+	struct LLabel  lblMode, lblColours;
+	struct LCheckbox cbExtra, cbEmpty, cbScale;
+	struct LLine sep;
+} SettingsScreen;
+
+#define SETTINGS_SCREEN_MAX_WIDGETS 9
+static struct LWidget* settings_widgets[SETTINGS_SCREEN_MAX_WIDGETS];
+
+LAYOUTS set_btnMode[]    = { { ANCHOR_CENTRE,     -135 }, { ANCHOR_CENTRE,  -70 } };
+LAYOUTS set_lblMode[]    = { { ANCHOR_CENTRE_MIN,  -70 }, { ANCHOR_CENTRE,  -70 } };
+LAYOUTS set_btnColours[] = { { ANCHOR_CENTRE,     -135 }, { ANCHOR_CENTRE,  -20 } };
+LAYOUTS set_lblColours[] = { { ANCHOR_CENTRE_MIN,  -70 }, { ANCHOR_CENTRE,  -20 } };
+
+LAYOUTS set_sep[]     = { { ANCHOR_CENTRE,        0 }, { ANCHOR_CENTRE,  15 } };
+LAYOUTS set_cbExtra[] = { { ANCHOR_CENTRE_MIN, -190 }, { ANCHOR_CENTRE,  44 } };
+LAYOUTS set_cbEmpty[] = { { ANCHOR_CENTRE_MIN, -190 }, { ANCHOR_CENTRE,  84 } };
+LAYOUTS set_cbScale[] = { { ANCHOR_CENTRE_MIN, -190 }, { ANCHOR_CENTRE, 124 } };
+LAYOUTS set_btnBack[] = { { ANCHOR_CENTRE,        0 }, { ANCHOR_CENTRE, 170 } };
+
+
+#if defined CC_BUILD_MOBILE
+static void SettingsScreen_LockOrientation(struct LCheckbox* w) {
+	Options_SetBool(OPT_LANDSCAPE_MODE, w->value);
+	Window_LockLandscapeOrientation(w->value);
+	LBackend_Redraw();
+}
+#else
+static void SettingsScreen_AutoClose(struct LCheckbox* w) {
+	Options_SetBool(LOPT_AUTO_CLOSE, w->value);
+}
+#endif
+static void SettingsScreen_ShowEmpty(struct LCheckbox* w) {
+	Launcher_ShowEmptyServers = w->value;
+	Options_SetBool(LOPT_SHOW_EMPTY, w->value);
+}
+
+static void SettingsScreen_DPIScaling(struct LCheckbox* w) {
+#if defined CC_BUILD_WIN
+	DisplayInfo.DPIScaling = w->value;
+	Options_SetBool(OPT_DPI_SCALING, w->value);
+	Window_ShowDialog("Restart required", "You must restart ClassiCube before display scaling takes effect");
+#else
+	Window_ShowDialog("Restart required", "Display scaling is currently only supported on Windows");
+#endif
+}
+
+static void SettingsScreen_AddWidgets(struct SettingsScreen* s) {
+	LLine_Add(s,   &s->sep, 380, set_sep);
+	LButton_Add(s, &s->btnMode, 110, 35, "Mode", 
+				SwitchToChooseMode, set_btnMode);
+	LLabel_Add(s,  &s->lblMode, "&eChange the enabled features", set_lblMode);
+
+	if (!Options_GetBool(OPT_CLASSIC_MODE, false)) {
+		LButton_Add(s, &s->btnColours, 110, 35, "Theme", 
+					SwitchToThemes, set_btnColours);
+		LLabel_Add(s,  &s->lblColours, "&eChange how the launcher looks", set_lblColours);
+	}
+
+#if defined CC_BUILD_MOBILE
+	LCheckbox_Add(s, &s->cbExtra, "Force landscape", 
+				SettingsScreen_LockOrientation, set_cbExtra);
+#else
+	LCheckbox_Add(s, &s->cbExtra, "Close this after game starts", 
+				SettingsScreen_AutoClose,       set_cbExtra);
+#endif
+
+	LCheckbox_Add(s, &s->cbEmpty, "Show empty servers in list", 
+				SettingsScreen_ShowEmpty,  set_cbEmpty);
+	LCheckbox_Add(s, &s->cbScale, "Use display scaling", 
+				SettingsScreen_DPIScaling, set_cbScale);
+	LButton_Add(s,   &s->btnBack, 80, 35, "Back", 
+				SwitchToMain, set_btnBack);
+}
+
+static void SettingsScreen_Activated(struct LScreen* s_) {
+	struct SettingsScreen* s = (struct SettingsScreen*)s_;
+	SettingsScreen_AddWidgets(s);
+
+#if defined CC_BUILD_MOBILE
+	LCheckbox_Set(&s->cbExtra, Options_GetBool(OPT_LANDSCAPE_MODE, false));
+#else
+	LCheckbox_Set(&s->cbExtra, Options_GetBool(LOPT_AUTO_CLOSE, false));
+#endif
+
+	LCheckbox_Set(&s->cbEmpty, Launcher_ShowEmptyServers);
+	LCheckbox_Set(&s->cbScale, DisplayInfo.DPIScaling);
+}
+
+void SettingsScreen_SetActive(void) {
+	struct SettingsScreen* s = &SettingsScreen;
+	LScreen_Reset((struct LScreen*)s);
+
+	s->widgets    = settings_widgets;
+	s->maxWidgets = Array_Elems(settings_widgets);
+
+	s->Activated      = SettingsScreen_Activated;
+	s->title          = "Options";
+	s->onEscapeWidget = (struct LWidget*)&s->btnBack;
+
+	Launcher_SetScreen((struct LScreen*)s);
+}
+
+
+/*########################################################################################################################*
+*----------------------------------------------------------ThemesScreen----------------------------------------------------*
+*#########################################################################################################################*/
+static struct ThemesScreen {
+	LScreen_Layout
+	struct LButton btnModern, btnClassic, btnNordic;
+	struct LButton btnCustom, btnBack;
+} ThemesScreen;
+
+#define THEME_SCREEN_MAX_WIDGETS 5
+static struct LWidget* themes_widgets[THEME_SCREEN_MAX_WIDGETS];
+
+LAYOUTS the_btnModern[]  = { { ANCHOR_CENTRE, 0 }, { ANCHOR_CENTRE, -120 } };
+LAYOUTS the_btnClassic[] = { { ANCHOR_CENTRE, 0 }, { ANCHOR_CENTRE,  -70 } };
+LAYOUTS the_btnNordic[]  = { { ANCHOR_CENTRE, 0 }, { ANCHOR_CENTRE,  -20 } };
+LAYOUTS the_btnCustom[]  = { { ANCHOR_CENTRE, 0 }, { ANCHOR_CENTRE,  120 } };
+LAYOUTS the_btnBack[]    = { { ANCHOR_CENTRE, 0 }, { ANCHOR_CENTRE,  170 } };
+
+
+static void ThemesScreen_Set(const struct LauncherTheme* theme) {
+	Launcher_Theme = *theme;
+	Launcher_SaveTheme();
+	LBackend_ThemeChanged();
+}
+
+static void ThemesScreen_Modern(void* w) {
+	ThemesScreen_Set(&Launcher_ModernTheme);
+}
+static void ThemesScreen_Classic(void* w) {
+	ThemesScreen_Set(&Launcher_ClassicTheme);
+}
+static void ThemesScreen_Nordic(void* w) {
+	ThemesScreen_Set(&Launcher_NordicTheme);
+}
+
+static void ThemesScreen_Activated(struct LScreen* s_) {
+	struct ThemesScreen* s = (struct ThemesScreen*)s_;
+
+	LButton_Add(s, &s->btnModern,  200, 35, "Modern",  
+				ThemesScreen_Modern,  the_btnModern);
+	LButton_Add(s, &s->btnClassic, 200, 35, "Classic", 
+				ThemesScreen_Classic, the_btnClassic);
+	LButton_Add(s, &s->btnNordic,  200, 35, "Nordic",  
+				ThemesScreen_Nordic,  the_btnNordic);
+	LButton_Add(s, &s->btnCustom,  200, 35, "Custom",  
+				SwitchToColours,      the_btnCustom);
+	LButton_Add(s, &s->btnBack,     80, 35, "Back",    
+				SwitchToSettings,     the_btnBack);
+}
+
+void ThemesScreen_SetActive(void) {
+	struct ThemesScreen* s = &ThemesScreen;
+	LScreen_Reset((struct LScreen*)s);
+
+	s->widgets    = themes_widgets;
+	s->maxWidgets = Array_Elems(themes_widgets);
+
+	s->Activated      = ThemesScreen_Activated;
+	s->title          = "Select theme";
+	s->onEscapeWidget = (struct LWidget*)&s->btnBack;
+
+	Launcher_SetScreen((struct LScreen*)s);
+}
+
+
+/*########################################################################################################################*
+*--------------------------------------------------------UpdatesScreen----------------------------------------------------*
+*#########################################################################################################################*/
+static struct UpdatesScreen {
+	LScreen_Layout
+	struct LLine seps[2];
+	struct LButton btnRel[2], btnDev[2], btnBack;
+	struct LLabel  lblYour, lblRel, lblDev, lblInfo, lblStatus;
+	int buildProgress, buildIndex;
+	cc_bool pendingFetch, release;
+} UpdatesScreen;
+
+#define UPDATESSCREEN_MAX_WIDGETS 12
+static struct LWidget* updates_widgets[UPDATESSCREEN_MAX_WIDGETS];
+
+LAYOUTS upd_lblYour[] = { { ANCHOR_CENTRE, -5 }, { ANCHOR_CENTRE, -120 } };
+LAYOUTS upd_seps0[]   = { { ANCHOR_CENTRE,  0 }, { ANCHOR_CENTRE, -100 } };
+LAYOUTS upd_seps1[]   = { { ANCHOR_CENTRE,  0 }, { ANCHOR_CENTRE,   -5 } };
+
+LAYOUTS upd_lblRel[]    = { { ANCHOR_CENTRE, -20 }, { ANCHOR_CENTRE, -75 } };
+LAYOUTS upd_lblDev[]    = { { ANCHOR_CENTRE, -30 }, { ANCHOR_CENTRE,  20 } };
+LAYOUTS upd_lblInfo[]   = { { ANCHOR_CENTRE,   0 }, { ANCHOR_CENTRE, 105 } };
+LAYOUTS upd_lblStatus[] = { { ANCHOR_CENTRE,   0 }, { ANCHOR_CENTRE, 130 } };
+LAYOUTS upd_btnBack[]   = { { ANCHOR_CENTRE,   0 }, { ANCHOR_CENTRE, 170 } };
+
+/* Update button layouts when 1 build */
+LAYOUTS upd_btnRel0_1[] = { { ANCHOR_CENTRE,   0 }, { ANCHOR_CENTRE, -40 } };
+LAYOUTS upd_btnDev0_1[] = { { ANCHOR_CENTRE,   0 }, { ANCHOR_CENTRE,  55 } };
+/* Update button layouts when 2 builds */
+LAYOUTS upd_btnRel0_2[] = { { ANCHOR_CENTRE, -80 }, { ANCHOR_CENTRE, -40 } };
+LAYOUTS upd_btnRel1_2[] = { { ANCHOR_CENTRE,  80 }, { ANCHOR_CENTRE, -40 } };
+LAYOUTS upd_btnDev0_2[] = { { ANCHOR_CENTRE, -80 }, { ANCHOR_CENTRE,  55 } };
+LAYOUTS upd_btnDev1_2[] = { { ANCHOR_CENTRE,  80 }, { ANCHOR_CENTRE,  55 } };
+
+
+CC_NOINLINE static void UpdatesScreen_FormatTime(cc_string* str, int delta) {
+	const char* span;
+	int unit, value = Math_AbsI(delta);
+
+	if (value < SECS_PER_MIN) {
+		span = "second";  unit = 1;
+	} else if (value < SECS_PER_HOUR) {
+		span = "minute";  unit = SECS_PER_MIN;
+	} else if (value < SECS_PER_DAY) {
+		span = "hour";    unit = SECS_PER_HOUR;
+	} else {
+		span = "day";     unit = SECS_PER_DAY;
+	}
+
+	value /= unit;
+	String_AppendInt(str, value);
+	String_Append(str, ' ');
+	String_AppendConst(str, span);
+
+	if (value > 1) String_Append(str, 's');
+	String_AppendConst(str, delta >= 0 ? " ago" : " in the future");
+}
+
+static void UpdatesScreen_Format(struct LLabel* lbl, const char* prefix, cc_uint64 timestamp) {
+	cc_string str; char buffer[STRING_SIZE];
+	TimeMS now;
+	int delta;
+
+	String_InitArray(str, buffer);
+	String_AppendConst(&str, prefix);
+
+	if (!timestamp) {
+		String_AppendConst(&str, "&cCheck failed");
+	} else {
+		now   = DateTime_CurrentUTC() - UNIX_EPOCH_SECONDS;
+		delta = (int)(now - timestamp);
+		UpdatesScreen_FormatTime(&str, delta);
+	}
+	LLabel_SetText(lbl, &str);
+}
+
+static void UpdatesScreen_FormatBoth(struct UpdatesScreen* s) {
+	UpdatesScreen_Format(&s->lblRel, "Latest release: ",   CheckUpdateTask.relTimestamp);
+	UpdatesScreen_Format(&s->lblDev, "Latest dev build: ", CheckUpdateTask.devTimestamp);
+}
+
+static void UpdatesScreen_UpdateHeader(struct UpdatesScreen* s, cc_string* str) {
+	const char* message;
+	if ( s->release) message = "&eFetching latest release ";
+	if (!s->release) message = "&eFetching latest dev build ";
+
+	String_Format2(str, "%c%c", message, Updater_Info.builds[s->buildIndex].name);
+}
+
+static void UpdatesScreen_DoFetch(struct UpdatesScreen* s) {
+	cc_string str; char strBuffer[STRING_SIZE];
+	cc_uint64 time;
+	
+	time = s->release ? CheckUpdateTask.relTimestamp : CheckUpdateTask.devTimestamp;
+	if (!time || FetchUpdateTask.Base.working) return;
+	FetchUpdateTask_Run(s->release, s->buildIndex);
+
+	s->pendingFetch  = false;
+	s->buildProgress = -1;
+	String_InitArray(str, strBuffer);
+
+	UpdatesScreen_UpdateHeader(s, &str);
+	String_AppendConst(&str, "..");
+	LLabel_SetText(&s->lblStatus, &str);
+}
+
+static void UpdatesScreen_Get(cc_bool release, int buildIndex) {
+	struct UpdatesScreen* s = &UpdatesScreen;
+	/* This code is deliberately split up to handle this particular case: */
+	/*  The user clicked this button before CheckUpdateTask completed, */
+	/*  therefore update fetching would not actually occur. (as timestamp is 0) */
+	/*  By storing requested build, it can be fetched later upon completion. */
+	s->pendingFetch = true;
+	s->release      = release;
+	s->buildIndex   = buildIndex;
+	UpdatesScreen_DoFetch(s);
+}
+
+static void UpdatesScreen_CheckTick(struct UpdatesScreen* s) {
+	if (!CheckUpdateTask.Base.working) return;
+	LWebTask_Tick(&CheckUpdateTask.Base, NULL);
+
+	if (!CheckUpdateTask.Base.completed) return;
+	UpdatesScreen_FormatBoth(s);
+	if (s->pendingFetch) UpdatesScreen_DoFetch(s);
+}
+
+static void UpdatesScreen_UpdateProgress(struct UpdatesScreen* s, struct LWebTask* task) {
+	cc_string str; char strBuffer[STRING_SIZE];
+	int progress = Http_CheckProgress(task->reqID);
+	if (progress == s->buildProgress) return;
+
+	s->buildProgress = progress;
+	if (progress < 0 || progress > 100) return;
+	String_InitArray(str, strBuffer);
+
+	UpdatesScreen_UpdateHeader(s, &str);
+	String_Format1(&str, " &a%i%%", &s->buildProgress);
+	LLabel_SetText(&s->lblStatus, &str);
+}
+
+static void FetchUpdatesError(struct HttpRequest* req) {
+	cc_string str; char strBuffer[STRING_SIZE];
+	String_InitArray(str, strBuffer);
+
+	Launcher_DisplayHttpError(req, "fetching update", &str);
+	LLabel_SetText(&UpdatesScreen.lblStatus, &str);
+}
+
+static void UpdatesScreen_FetchTick(struct UpdatesScreen* s) {
+	if (!FetchUpdateTask.Base.working) return;
+
+	LWebTask_Tick(&FetchUpdateTask.Base, FetchUpdatesError);
+	UpdatesScreen_UpdateProgress(s, &FetchUpdateTask.Base);
+	if (!FetchUpdateTask.Base.completed) return;
+
+	if (!FetchUpdateTask.Base.success) return;
+	/* FetchUpdateTask handles saving the updated file for us */
+	Launcher_ShouldExit   = true;
+	Launcher_ShouldUpdate = true;
+}
+
+static void UpdatesScreen_Rel_0(void* w) { UpdatesScreen_Get(true,  0); }
+static void UpdatesScreen_Rel_1(void* w) { UpdatesScreen_Get(true,  1); }
+static void UpdatesScreen_Dev_0(void* w) { UpdatesScreen_Get(false, 0); }
+static void UpdatesScreen_Dev_1(void* w) { UpdatesScreen_Get(false, 1); }
+
+static void UpdatesScreen_AddWidgets(struct UpdatesScreen* s) {
+	int builds = Updater_Info.numBuilds;
+
+	LLine_Add(s,   &s->seps[0],   320,                   upd_seps0);
+	LLine_Add(s,   &s->seps[1],   320,                   upd_seps1);
+	LLabel_Add(s,  &s->lblYour, "Your build: (unknown)", upd_lblYour);
+
+	LLabel_Add(s,  &s->lblRel, "Latest release: Checking..",   upd_lblRel);
+	LLabel_Add(s,  &s->lblDev, "Latest dev build: Checking..", upd_lblDev);
+	LLabel_Add(s,  &s->lblStatus, "",              upd_lblStatus);
+	LLabel_Add(s,  &s->lblInfo, Updater_Info.info, upd_lblInfo);
+	LButton_Add(s, &s->btnBack, 80, 35, "Back", 
+				SwitchToMain, upd_btnBack);
+
+	if (builds >= 1) {
+		LButton_Add(s, &s->btnRel[0], 130, 35, Updater_Info.builds[0].name,
+							UpdatesScreen_Rel_0, builds == 1 ? upd_btnRel0_1 : upd_btnRel0_2);
+		LButton_Add(s, &s->btnDev[0], 130, 35, Updater_Info.builds[0].name,
+							UpdatesScreen_Dev_0, builds == 1 ? upd_btnDev0_1 : upd_btnDev0_2);
+	}
+
+	if (builds >= 2) {
+		LButton_Add(s, &s->btnRel[1], 130, 35, Updater_Info.builds[1].name, 
+					UpdatesScreen_Rel_1, upd_btnRel1_2);
+		LButton_Add(s, &s->btnDev[1], 130, 35, Updater_Info.builds[1].name, 
+					UpdatesScreen_Dev_1, upd_btnDev1_2);
+	}
+}
+
+static void UpdatesScreen_Activated(struct LScreen* s_) {
+	struct UpdatesScreen* s = (struct UpdatesScreen*)s_;
+	cc_uint64 buildTime;
+	cc_result res;
+	UpdatesScreen_AddWidgets(s);
+
+	/* Initially fill out with update check result from main menu */
+	if (CheckUpdateTask.Base.completed && CheckUpdateTask.Base.success) {
+		UpdatesScreen_FormatBoth(s);
+	}
+	CheckUpdateTask_Run();
+	s->pendingFetch = false;
+
+	res = Updater_GetBuildTime(&buildTime);
+	if (res) { Logger_SysWarn(res, "getting build time"); return; }
+	UpdatesScreen_Format(&s->lblYour, "Your build: ", buildTime);
+}
+
+static void UpdatesScreen_Tick(struct LScreen* s_) {
+	struct UpdatesScreen* s = (struct UpdatesScreen*)s_;
+	LScreen_Tick(s_);
+
+	UpdatesScreen_FetchTick(s);
+	UpdatesScreen_CheckTick(s);
+}
+
+/* Aborts fetch if it is in progress */
+static void UpdatesScreen_Deactivated(struct LScreen* s_) {
+	struct UpdatesScreen* s = (struct UpdatesScreen*)s_;
+	s->buildProgress = -1;
+
+	FetchUpdateTask.Base.working = false;
+	s->lblStatus.text.length     = 0;
+}
+
+void UpdatesScreen_SetActive(void) {
+	struct UpdatesScreen* s = &UpdatesScreen;
+	LScreen_Reset((struct LScreen*)s);
+
+	s->widgets    = updates_widgets;
+	s->maxWidgets = Array_Elems(updates_widgets);
+
+	s->Activated      = UpdatesScreen_Activated;
+	s->Tick           = UpdatesScreen_Tick;
+	s->Deactivated    = UpdatesScreen_Deactivated;
+	s->title          = "Update game";
+	s->onEscapeWidget = (struct LWidget*)&s->btnBack;
+
+	Launcher_SetScreen((struct LScreen*)s);
+}
+#endif