diff --git a/misc/linux/install-desktop-entry.sh b/misc/linux/install-desktop-entry.sh index a3c3ab7c5..4c8024924 100644 --- a/misc/linux/install-desktop-entry.sh +++ b/misc/linux/install-desktop-entry.sh @@ -24,7 +24,16 @@ Exec=$GAME_DIR/ClassiCube Icon=$GAME_DIR/CCicon.png Path=$GAME_DIR Terminal=false -Categories=Game +Categories=Game; +Actions=singleplayer;resume; + +[Desktop Action singleplayer] +Name=Start singleplayer +Exec=$GAME_DIR/ClassiCube --singleplayer + +[Desktop Action resume] +Name=Resume last server +Exec=$GAME_DIR/ClassiCube --resume EOF chmod +x $DESKTOP_FILE diff --git a/src/LScreens.c b/src/LScreens.c index ad987250c..e17814971 100644 --- a/src/LScreens.c +++ b/src/LScreens.c @@ -19,6 +19,7 @@ #include "LBackend.h" #include "Http.h" #include "Game.h" +#include "main.h" #define LAYOUTS static const struct LLayout #define IsBackButton(btn) (btn == CCKEY_ESCAPE || btn == CCPAD_SELECT || btn == CCPAD_2) @@ -473,6 +474,11 @@ static void DirectConnectScreen_UrlFilter(cc_string* str) { str->length = 0; } +static cc_bool DirectConnectScreen_ParsePort(const cc_string* str) { + int port; + return Convert_ParseInt(str, &port) && port >= 0 && port <= 65535; +} + static void DirectConnectScreen_StartClient(void* w) { static const cc_string defMppass = String_FromConst("(none)"); const cc_string* user = &DirectConnectScreen.iptUsername.text; @@ -492,7 +498,8 @@ static void DirectConnectScreen_StartClient(void* w) { if (!user->length) { LLabel_SetConst(status, "&cUsername required"); return; } - if (!DirectUrl_ExtractAddress(addr, &ip, &port, &raw_port)) { + DirectUrl_ExtractAddress(addr, &ip, &port); + if (!DirectConnectScreen_ParsePort(&port)) { LLabel_SetConst(status, "&cInvalid port"); return; } if (Socket_ParseAddress(&ip, 25565, addrs, &numAddrs)) { @@ -721,34 +728,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; @@ -789,9 +768,8 @@ static void MainScreen_Register(void* w) { static void MainScreen_Resume(void* w) { struct ResumeInfo info; - MainScreen_GetResume(&info, true); - if (!info.valid) return; + if (!Resume_Parse(&info, true)) return; Launcher_StartGame(&info.user, &info.mppass, &info.ip, &info.port, &info.server, 1); } @@ -809,7 +787,7 @@ static void MainScreen_ResumeHover(void* w) { struct ResumeInfo info; if (s->signingIn) return; - MainScreen_GetResume(&info, false); + Resume_Parse(&info, false); if (!info.user.length) return; String_InitArray(str, strBuffer); diff --git a/src/Platform_NDS.c b/src/Platform_NDS.c index 8014b3cd6..7ae4bc6da 100644 --- a/src/Platform_NDS.c +++ b/src/Platform_NDS.c @@ -440,6 +440,7 @@ static void InitNetworking(void) { { int status = Wifi_AssocStatus(); if (status == ASSOCSTATUS_ASSOCIATED) return; + Platform_Log1("STATUS: %i", &status); if (status == ASSOCSTATUS_CANNOTCONNECT) { Platform_LogConst("Can't connect to WIFI"); diff --git a/src/Utils.c b/src/Utils.c index f6e27637d..ed4a4cf57 100644 --- a/src/Utils.c +++ b/src/Utils.c @@ -366,35 +366,3 @@ int EntryList_Find(struct StringsBuffer* list, const cc_string* key, char separa return -1; } - -/*########################################################################################################################* -*--------------------------------------------------------Direct URL-------------------------------------------------------* -*#########################################################################################################################*/ -cc_bool DirectUrl_Claims(const cc_string* input, cc_string* addr, cc_string* user, cc_string* mppass) { - static const cc_string prefix = String_FromConst("mc://"); - cc_string parts[6]; - if (!String_CaselessStarts(input, &prefix)) return false; - - /* mc://[ip:port]/[username]/[mppass] */ - if (String_UNSAFE_Split(input, '/', parts, 6) != 5) return false; - - *addr = parts[2]; - *user = parts[3]; - *mppass = parts[4]; - return true; -} - -cc_bool DirectUrl_ExtractAddress(const cc_string* addr, cc_string* ip, cc_string* port, int* portNum) { - static const cc_string defPort = String_FromConst("25565"); - int index = String_LastIndexOf(addr, ':'); - - /* 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); - } - return Convert_ParseInt(port, portNum) && *portNum >= 0 && *portNum <= 65535; -} diff --git a/src/Utils.h b/src/Utils.h index 885b035e5..c50696d42 100644 --- a/src/Utils.h +++ b/src/Utils.h @@ -77,8 +77,5 @@ CC_NOINLINE STRING_REF cc_string EntryList_UNSAFE_Get(struct StringsBuffer* list /* Finds the index of the entry whose key caselessly equals the given key. */ CC_NOINLINE int EntryList_Find(struct StringsBuffer* list, const cc_string* key, char separator); -cc_bool DirectUrl_Claims(const cc_string* STRING_REF input, cc_string* addr, cc_string* user, cc_string* mppass); -cc_bool DirectUrl_ExtractAddress(const cc_string* STRING_REF addr, cc_string* ip, cc_string* port, int* portNum); - CC_END_HEADER #endif diff --git a/src/main.c b/src/main.c index 241672875..9d2d265a6 100644 --- a/src/main.c +++ b/src/main.c @@ -9,7 +9,63 @@ #include "Launcher.h" #include "Server.h" #include "Options.h" +#include "main.h" +/*########################################################################################################################* +*-------------------------------------------------Complex argument parsing------------------------------------------------* +*#########################################################################################################################*/ +cc_bool Resume_Parse(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 true; + String_InitArray(info->mppass, info->_mppassBuffer); + Options_GetSecure(ROPT_MPPASS, &info->mppass); + + return + info->user.length && info->mppass.length && + info->ip.length && info->port.length; +} + +cc_bool DirectUrl_Claims(const cc_string* input, cc_string* addr, cc_string* user, cc_string* mppass) { + static const cc_string prefix = String_FromConst("mc://"); + cc_string parts[6]; + if (!String_CaselessStarts(input, &prefix)) return false; + + /* mc://[ip:port]/[username]/[mppass] */ + if (String_UNSAFE_Split(input, '/', parts, 6) != 5) return false; + + *addr = parts[2]; + *user = parts[3]; + *mppass = parts[4]; + return true; +} + +void DirectUrl_ExtractAddress(const cc_string* addr, cc_string* ip, cc_string* port) { + static const cc_string defPort = String_FromConst("25565"); + int index = String_LastIndexOf(addr, ':'); + + /* 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); + } +} + + +/*########################################################################################################################* +*------------------------------------------------------Game setup/run-----------------------------------------------------* +*#########################################################################################################################*/ static void RunGame(void) { cc_string title; char titleBuffer[STRING_SIZE]; int width = Options_GetInt(OPT_WINDOW_WIDTH, 0, DisplayInfo.Width, 0); @@ -78,9 +134,23 @@ static cc_bool IsOpenableFile(const cc_string* path) { return File_Exists(&str); } +static int ParseMPArgs(const cc_string* user, const cc_string* mppass, const cc_string* addr, const cc_string* port) { + String_Copy(&Game_Username, user); + String_Copy(&Game_Mppass, mppass); + String_Copy(&Server.Address, addr); + + if (!Convert_ParseInt(port, &Server.Port) || Server.Port < 0 || Server.Port > 65535) { + WarnInvalidArg("Invalid port", port); + return false; + } + return true; +} + static int RunProgram(int argc, char** argv) { cc_string args[GAME_MAX_CMDARGS]; int argsCount = Platform_GetCommandLineArgs(argc, argv, args); + struct ResumeInfo r; + cc_string host; #ifdef _MSC_VER /* NOTE: Make sure to comment this out before pushing a commit */ @@ -95,41 +165,47 @@ static int RunProgram(int argc, char** argv) { RunGame(); #else Launcher_Run(); - /* :hash to auto join server with the given hash */ + /* :[hash] - auto join server with the given hash */ } else if (argsCount == 1 && args[0].buffer[0] == ':') { args[0] = String_UNSAFE_SubstringAt(&args[0], 1); String_Copy(&Launcher_AutoHash, &args[0]); Launcher_Run(); - /* File path to auto load a map in singleplayer */ + /* --resume - try to resume to last server */ + } else if (argsCount == 1 && String_CaselessEqualsConst(&args[0], DEFAULT_RESUME_ARG)) { + if (!Resume_Parse(&r, true)) { + WarnInvalidArg("No server to resume to", &args[0]); + return 1; + } + + if (!ParseMPArgs(&r.user, &r.mppass, &r.ip, &r.port)) return 1; + RunGame(); + /* --singleplayer' - run singleplayer with default user */ + } else if (argsCount == 1 && String_CaselessEqualsConst(&args[0], DEFAULT_SINGLEPLAYER_ARG)) { + Options_Get(LOPT_USERNAME, &Game_Username, DEFAULT_USERNAME); + RunGame(); + /* [file path] - run singleplayer with auto loaded map */ } else if (argsCount == 1 && IsOpenableFile(&args[0])) { Options_Get(LOPT_USERNAME, &Game_Username, DEFAULT_USERNAME); String_Copy(&SP_AutoloadMap, &args[0]); /* TODO: don't copy args? */ RunGame(); #endif - } else if (argsCount == 1 && DirectUrl_Claims(&args[0], &args[1], &args[2], &args[3])) { - String_Copy(&Game_Username, &args[2]); - String_Copy(&Game_Mppass, &args[3]); - - if (!DirectUrl_ExtractAddress(&args[1], &Server.Address, &args[4], &Server.Port)) { - WarnInvalidArg("Invalid port", &args[4]); - return 1; - } - RunGame(); + /* mc://[addr]:[port]/[user]/[mppass] - run multiplayer using direct URL form arguments */ + } else if (argsCount == 1 && DirectUrl_Claims(&args[0], &host, &r.user, &r.mppass)) { + DirectUrl_ExtractAddress(&host, &r.ip, &r.port); + + if (!ParseMPArgs(&r.user, &r.mppass, &r.ip, &r.port)) return 1; + RunGame(); + /* [user] - run multiplayer using explicit username */ } else if (argsCount == 1) { String_Copy(&Game_Username, &args[0]); - RunGame(); + RunGame(); + /* 2 to 3 arguments - unsupported at present */ } else if (argsCount < 4) { WarnMissingArgs(argsCount, args); return 1; + /* [user] [mppass] [address] [port] - run multiplayer using explicit arguments */ } else { - String_Copy(&Game_Username, &args[0]); - String_Copy(&Game_Mppass, &args[1]); - String_Copy(&Server.Address,&args[2]); - - if (!Convert_ParseInt(&args[3], &Server.Port) || Server.Port < 0 || Server.Port > 65535) { - WarnInvalidArg("Invalid port", &args[3]); - return 1; - } + if (!ParseMPArgs(&args[0], &args[1], &args[2], &args[3])) return 1; RunGame(); } return 0; diff --git a/src/main.h b/src/main.h new file mode 100644 index 000000000..49bfb9ba5 --- /dev/null +++ b/src/main.h @@ -0,0 +1,24 @@ +#ifndef CC_MAIN_H +#define CC_MAIN_H +#include "String.h" +/* Utility constants and methods for command line arguments + Copyright 2014-2023 ClassiCube | Licensed under BSD-3 +*/ +CC_BEGIN_HEADER + +#define DEFAULT_SINGLEPLAYER_ARG "--singleplayer" +#define DEFAULT_RESUME_ARG "--resume" + +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 Resume_Parse(struct ResumeInfo* info, cc_bool full); + +cc_bool DirectUrl_Claims(const cc_string* STRING_REF input, cc_string* addr, cc_string* user, cc_string* mppass); +void DirectUrl_ExtractAddress(const cc_string* STRING_REF addr, cc_string* ip, cc_string* port); + +CC_END_HEADER +#endif