diff --git a/CMakeLists.txt b/CMakeLists.txt index 4da8386..8d5dbe2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,11 +1,11 @@ cmake_minimum_required(VERSION 3.15) project(xiloader CXX) -set_property(GLOBAL PROPERTY CXX_STANDARD 17) -set_property(GLOBAL PROPERTY CXX_STANDARD_REQUIRED ON) -set_property(GLOBAL PROPERTY CXX_EXTENSIONS ON) -set_property(GLOBAL PROPERTY LINKER_LANGUAGE CXX) -set_property(GLOBAL PROPERTY USE_FOLDERS ON) +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS ON) +set(LINKER_LANGUAGE CXX) +set(USE_FOLDERS ON) set(CMAKE_POSITION_INDEPENDENT_CODE ON) @@ -17,9 +17,13 @@ elseif(CMAKE_SIZEOF_VOID_P EQUAL 4) endif() include(cmake/CPM.cmake) -include(cmake/detours.cmake) + include(cmake/argparse.cmake) +include(cmake/detours.cmake) +include(cmake/ftxui.cmake) +include(cmake/json.cmake) include(cmake/mbedtls.cmake) +include(cmake/qr-code-generator.cmake) configure_file( src/xiloader.rc.in @@ -28,6 +32,7 @@ configure_file( # xiloader add_executable(xiloader + src/command_handler.h src/console.cpp src/console.h src/defines.h @@ -36,6 +41,7 @@ add_executable(xiloader src/functions.cpp src/functions.h src/main.cpp + src/menus.h src/network.cpp src/network.h src/polcore.h @@ -49,14 +55,19 @@ target_include_directories(xiloader PUBLIC ${PROJECT_SOURCE_DIR}/xiloader) target_link_libraries(xiloader PUBLIC detours - argparse - crypt32 - psapi - ws2_32 - iphlpapi - MbedTLS::mbedtls - MbedTLS::mbedcrypto - MbedTLS::mbedx509) + argparse + crypt32 + psapi + ws2_32 + iphlpapi + MbedTLS::mbedtls + MbedTLS::mbedcrypto + MbedTLS::mbedx509 + nlohmann_json::nlohmann_json + ftxui::screen + ftxui::dom + ftxui::component + qr-code-generator) message(STATUS "CMAKE_VERSION: ${CMAKE_VERSION}") message(STATUS "CMAKE_BUILD_TYPE: ${CMAKE_BUILD_TYPE}") diff --git a/cmake/CPM.cmake b/cmake/CPM.cmake index fb92af1..38bc882 100644 --- a/cmake/CPM.cmake +++ b/cmake/CPM.cmake @@ -1,4 +1,4 @@ -set(CPM_DOWNLOAD_VERSION 0.34.1) +set(CPM_DOWNLOAD_VERSION 0.42.0) if(CPM_SOURCE_CACHE) # Expand relative path. This is important if the provided path contains a tilde (~) diff --git a/cmake/ftxui.cmake b/cmake/ftxui.cmake new file mode 100644 index 0000000..3e74c57 --- /dev/null +++ b/cmake/ftxui.cmake @@ -0,0 +1,5 @@ +CPMAddPackage( + NAME ftxui + GITHUB_REPOSITORY arthursonzogni/ftxui + GIT_TAG v6.1.9 +) diff --git a/cmake/json.cmake b/cmake/json.cmake new file mode 100644 index 0000000..be952b2 --- /dev/null +++ b/cmake/json.cmake @@ -0,0 +1,8 @@ +CPMAddPackage( + NAME nlohmann_json + GITHUB_REPOSITORY nlohmann/json + GIT_TAG v3.12.0 + OPTIONS + "JSON_ImplicitConversions OFF" #Recommended by the author + "JSON_BuildTests OFF" +) diff --git a/cmake/mbedtls.cmake b/cmake/mbedtls.cmake index cf20e6b..a2df70d 100644 --- a/cmake/mbedtls.cmake +++ b/cmake/mbedtls.cmake @@ -1,7 +1,7 @@ CPMAddPackage( NAME mbedtls GITHUB_REPOSITORY Mbed-TLS/mbedtls - GIT_TAG mbedtls-3.6.4 + GIT_TAG mbedtls-3.6.5 OPTIONS "ENABLE_PROGRAMS OFF" "ENABLE_TESTING OFF" diff --git a/cmake/qr-code-generator.cmake b/cmake/qr-code-generator.cmake new file mode 100644 index 0000000..fd51216 --- /dev/null +++ b/cmake/qr-code-generator.cmake @@ -0,0 +1,19 @@ +CPMAddPackage( + NAME qr-code-generator + GITHUB_REPOSITORY nayuki/QR-Code-generator + GIT_TAG 720f62bddb7226106071d4728c292cb1df519ceb + LANGUAGES CXX + DOWNLOAD_ONLY +) + +if(qr-code-generator_ADDED) + add_library(qr-code-generator + STATIC + ${qr-code-generator_SOURCE_DIR}/cpp/qrcodegen.cpp + ${qr-code-generator_SOURCE_DIR}/cpp/qrcodegen.hpp + ) + target_include_directories(qr-code-generator + SYSTEM INTERFACE + ${qr-code-generator_SOURCE_DIR}/cpp + ) +endif() diff --git a/src/command_handler.h b/src/command_handler.h new file mode 100644 index 0000000..1b7941c --- /dev/null +++ b/src/command_handler.h @@ -0,0 +1,203 @@ +/* +=========================================================================== + + Copyright (c) 2025 LandSandBoat Dev Teams + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see http://www.gnu.org/licenses/ + +=========================================================================== +*/ + +#pragma once + +/* Externals */ +namespace globals +{ + extern std::string g_ServerAddress; + extern std::string g_Username; + extern std::string g_Password; + extern std::string g_OtpCode; + extern char g_SessionHash[16]; + extern std::string g_Email; + extern std::array g_VersionNumber; + extern uint16_t g_ServerPort; + extern uint16_t g_LoginDataPort; + extern uint16_t g_LoginViewPort; + extern uint16_t g_LoginAuthPort; + extern char* g_CharacterList; + extern bool g_IsRunning; + extern bool g_FirstLogin; +} // namespace globals + +#include "defines.h" +#include "helpers.h" +#include "network.h" + +#include +#include + +using json = nlohmann::json; + +bool handleLoginCommand(int8_t command, json& login_reply_json, uint32_t& accountId, SOCKET& sock) +{ + /* Handle the obtained result.. */ + switch (command) + { + case 0x0001: // Success (Login) + { + std::optional maybeAccountId = jsonGet(login_reply_json, "account_id"); + if (maybeAccountId.has_value()) + { + accountId = maybeAccountId.value(); + } + else + { + xiloader::console::output(xiloader::color::error, "xi_connect failed to reply with an account ID"); + break; + } + + auto maybeSessionHash = jsonGet(login_reply_json, "session_hash"); + + if (maybeSessionHash.has_value()) + { + std::memcpy(&globals::g_SessionHash, maybeSessionHash.value().data(), maybeSessionHash.value().size()); + } + else + { + xiloader::console::output(xiloader::color::error, "xi_connect failed to reply with a valid session hash"); + break; + } + + xiloader::console::output(xiloader::color::success, "Successfully logged in as %s!", globals::g_Username.c_str()); + + shutdown(sock, SD_BOTH); + + return true; + } + case 0x0002: // Error (Login) + { + xiloader::console::output(xiloader::color::error, "Failed to login. Invalid username or password."); + + return false; + } + case 0x0003: // Success (Create Account) + { + xiloader::console::output(xiloader::color::success, "Account successfully created!"); + + return false; + } + case 0x0004: // Error (Create Account) + { + xiloader::console::output(xiloader::color::error, "Failed to create the new account. Username already taken."); + + return false; + } + case 0x0006: // Success (Changed Password) + { + xiloader::console::output(xiloader::color::success, "Password updated successfully!"); + globals::g_Password.clear(); + + return false; + } + case 0x0007: // Error (Changed Password) + { + xiloader::console::output(xiloader::color::error, "Failed to change password."); + globals::g_Password.clear(); + + return false; + } + + // Commands 0x0008 through 0x0008 are currently unused + + case 0x000A: + { + xiloader::console::output(xiloader::color::error, "Failed to login. Account already logged in."); + + return false; + } + case 0x000B: + { + xiloader::console::output(xiloader::color::error, "Failed to login. Expected xiloader version mismatch; check with your provider."); + + return false; + } + + // LOGIN_SUCCESS_CREATE_TOTP + case 0x0010: + { + std::string uri = ""; + std::optional maybeURI = jsonGet(login_reply_json, "TOTP_uri"); + if (maybeURI.has_value()) + { + uri = maybeURI.value(); + } + else + { + xiloader::console::output(xiloader::color::error, "xi_connect failed to reply with a valid TOTP_uri"); + return false; + } + + qrcodegen::QrCode qrCode = qrcodegen::QrCode::encodeText(uri.c_str(), qrcodegen::QrCode::Ecc::LOW); + + std::string svgString = qrToSvgString(qrCode, 4); + std::string svgFilePath = writeSvgToDisk("temp.svg", svgString); + + xiloader::console::output(xiloader::color::info, "Open your authenticator application on your phone and prepare to scan the QR code."); + + bool display = menus::okCancelDialog("Open QR code in my default browser"); + if (display) + { + ShellExecuteA(nullptr, "open", svgFilePath.c_str(), nullptr, nullptr, SW_SHOWNORMAL); + xiloader::console::output(xiloader::color::info, "Once saved please use the \"Validate 2FA OTP\" option to verify the OTP."); + } + + return false; + } + + // LOGIN_SUCCESS_VERIFY_TOTP + case 0x0011: + { + xiloader::console::output(xiloader::color::info, "Your TOTP has been registered with the server"); + xiloader::console::output(xiloader::color::info, "You are now required to use an OTP to login."); + + std::optional maybeRecoveryCode = jsonGet(login_reply_json, "recovery_code"); + if (maybeRecoveryCode.has_value()) + { + xiloader::console::output(xiloader::color::info, "Your recovery code is '%s'. Please write this down!", maybeRecoveryCode.value().c_str()); + xiloader::console::output(xiloader::color::info, "Tip: Try using shift + leftclick to select the text if it doesn't work.", maybeRecoveryCode.value().c_str()); + xiloader::console::output(xiloader::color::info, "You may remove you may remove your TOTP with this recovery code."); + } + + std::string svgFilePath = getTemporaryPath() + "temp.svg"; + if (std::filesystem::exists(svgFilePath)) + { + std::filesystem::remove(svgFilePath); + } + + return false; + } + + // LOGIN_SUCCESS_REMOVE_TOTP + case 0x0012: + { + xiloader::console::output(xiloader::color::info, "Your TOTP has been removed."); + xiloader::console::output(xiloader::color::info, "You no longer need to use an OTP code to login."); + + return false; + } + + } + + return false; +} diff --git a/src/console.h b/src/console.h index 7f56aa5..cee9a0c 100644 --- a/src/console.h +++ b/src/console.h @@ -21,12 +21,7 @@ This file is part of DarkStar-server source code. =========================================================================== */ -#ifndef __XILOADER_CONSOLE_H_INCLUDED__ -#define __XILOADER_CONSOLE_H_INCLUDED__ - -#if defined (_MSC_VER) && (_MSC_VER >= 1020) #pragma once -#endif #include #include @@ -94,7 +89,7 @@ namespace xiloader /** * @brief Prints a text fragment with the specified color to the console. - * + * * @param c The color to print the fragment with. * @param message The fragment to print. */ @@ -152,6 +147,21 @@ namespace xiloader std::cout << std::endl; } + static void printMultiLine(std::string msg, const std::string delimiter, const xiloader::color color) + { + auto pos = msg.find(delimiter); + + while (pos != -1) + { + xiloader::console::output(color, "%s", msg.substr(0, pos).c_str()); + msg.erase(0, pos + delimiter.length()); + + pos = msg.find(delimiter); + } + + xiloader::console::output(color, "%s", msg.c_str()); + } + /** * @brief Hides the console window. */ @@ -164,5 +174,3 @@ namespace xiloader }; }; // namespace xiloader - -#endif // __XILOADER_CONSOLE_H_INCLUDED__ diff --git a/src/helpers.h b/src/helpers.h index ffda76b..8276cd0 100644 --- a/src/helpers.h +++ b/src/helpers.h @@ -21,9 +21,182 @@ along with this program. If not, see http://www.gnu.org/licenses/ #pragma once #include +#include +#include +#include +#include +#include + +#include "qrcodegen.hpp" +#include + +using json = nlohmann::json; template T& ref(U* buf, std::size_t index) { - return *reinterpret_cast(reinterpret_cast(buf) + index); + return *reinterpret_cast(reinterpret_cast(buf) + index); +} + +// from https://github.com/nayuki/QR-Code-generator/blob/master/cpp/QrCodeGeneratorDemo.cpp +// Slightly modified +static std::string qrToSvgString(const qrcodegen::QrCode& qr, int border) +{ + if (border < 0) + { + throw std::domain_error("Border must be non-negative"); + } + + if (border > std::numeric_limits::max() / 2 || border * 2 > std::numeric_limits::max() - qr.getSize()) + { + throw std::overflow_error("Border too large"); + } + + std::ostringstream sb; + sb << "\n"; + sb << "\n"; + sb << "\n"; + sb << "\t\n"; + sb << "\t\n"; + sb << "\n"; + return sb.str(); +} + +static std::string getTemporaryPath() +{ + return std::filesystem::temp_directory_path().generic_string(); +} + +// Return filename for later deletion +// Only input filename as "name.ext" and don't include the path +static std::string writeSvgToDisk(std::string filename, std::string contents) +{ + filename = getTemporaryPath() + filename; + std::ofstream svg(filename, std::ios::out | std::ios::trunc); + + svg.write(contents.c_str(), contents.length()); + + svg.flush(); + svg.close(); + + return filename; +} + +template +struct always_false : std::false_type +{ +}; + +template +inline constexpr bool always_false_v = always_false::value; + +template +inline std::optional jsonGet(const json& jsonInput, std::string key) +{ + if (!jsonInput.contains(key)) + { + return std::nullopt; + } + + // Check types first, boolean can match a number + if constexpr (std::is_same_v) + { + if (!jsonInput[key].is_string()) + { + return std::nullopt; + } + } + else if constexpr (std::is_same_v) + { + if (!jsonInput[key].is_boolean()) + { + return std::nullopt; + } + } + else if constexpr (std::is_floating_point::value) + { + if (!jsonInput[key].is_number_float()) + { + return std::nullopt; + } + } + else if constexpr (std::is_signed::value) + { + if (!jsonInput[key].is_number_unsigned()) + { + return std::nullopt; + } + } + else if constexpr (std::is_unsigned::value) + { + if (!jsonInput[key].is_number_unsigned()) + { + return std::nullopt; + } + } + else + { + static_assert(always_false_v, "Trying to extract unsupported type from jsonGet"); + } + + return jsonInput[key].get(); +} + +// Required partial specialization for size arg +template +inline typename std::optional> jsonGet(const json& jsonInput, std::string key) +{ + if (!jsonInput.contains(key)) + { + return std::nullopt; + } + + if (!jsonInput[key].is_array()) + { + return std::nullopt; + } + + if (jsonInput[key].size() != size) + { + return std::nullopt; + } + + for (uint32_t i = 0; i < size; i++) + { + // JSON arrays can support mixed types, so make sure EVERY index is correct. + if constexpr (std::is_signed::value) + { + if (!jsonInput[key][i].is_number()) + { + return std::nullopt; + } + } + else if constexpr (std::is_unsigned::value) + { + if (!jsonInput[key][i].is_number_unsigned()) + { + return std::nullopt; + } + } + else + { + static_assert(always_false_v, "Trying to extract unsupported type from jsonGetArray"); + } + } + + return jsonInput[key].get>(); } diff --git a/src/main.cpp b/src/main.cpp index 799dd13..e2ced1e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -22,32 +22,42 @@ This file is part of DarkStar-server source code. =========================================================================== */ +#define NOMINMAX 1 // Interferes with std::numeric_limits + #include "defines.h" #include +#include +#include +#include #include #include "console.h" #include "functions.h" +#include "helpers.h" #include "network.h" #include "argparse/argparse.hpp" +#include + +using json = nlohmann::json; /* Global Variables */ namespace globals { - xiloader::Language g_Language = xiloader::Language::English; // The language of the loader to be used for polcore. - std::string g_ServerAddress = "127.0.0.1"; // The server address to connect to. - uint16_t g_ServerPort = 51220; // The server lobby server port to connect to. - uint16_t g_LoginDataPort = 54230; // Login server data port to connect to - uint16_t g_LoginViewPort = 54001; // Login view port to connect to - uint16_t g_LoginAuthPort = 54231; // Login auth port to connect to - std::string g_Username = ""; // The username being logged in with. - std::string g_Password = ""; // The password being logged in with. - char g_SessionHash[16] = {}; // Session hash sent from auth - std::string g_Email = ""; // Email, currently unused - std::string g_VersionNumber = "1.1.5"; // xiloader version number sent to auth server. Must be x.x.x with single characters for 'x'. Remember to also change in xiloader.rc.in - bool g_FirstLogin = false; // set to true when --user --pass are both set to allow for autologin + xiloader::Language g_Language = xiloader::Language::English; // The language of the loader to be used for polcore. + std::string g_ServerAddress = "127.0.0.1"; // The server address to connect to. + uint16_t g_ServerPort = 51220; // The server lobby server port to connect to. + uint16_t g_LoginDataPort = 54230; // Login server data port to connect to + uint16_t g_LoginViewPort = 54001; // Login view port to connect to + uint16_t g_LoginAuthPort = 54231; // Login auth port to connect to + std::string g_Username = ""; // The username being logged in with. + std::string g_Password = ""; // The password being logged in with. + std::string g_OtpCode = ""; // The OTP code the user input + char g_SessionHash[16] = {}; // Session hash sent from auth + std::string g_Email = ""; // Email, currently unused + std::array g_VersionNumber = { 2, 0, 0 }; // xiloader version number sent to auth server. Must be x.x.x with single characters for 'x'. Remember to also change in xiloader.rc.in + bool g_FirstLogin = false; // set to true when --user --pass are both set to allow for autologin char* g_CharacterList = NULL; // Pointer to the character list data being sent from the server. bool g_IsRunning = false; // Flag to determine if the network threads should hault. @@ -387,6 +397,10 @@ int __cdecl main(int argc, char* argv[]) .help("The password being logged in with.") .append(); + args.add_argument("--otp", "--otp-code") + .help("The otp code being logged in with.") + .append(); + args.add_argument("--email", "--email") .help("The email being logged in with.") .append(); @@ -425,6 +439,10 @@ int __cdecl main(int argc, char* argv[]) .help("(optional) Determines whether or not to hide the console window after FFXI starts.") .append(); + args.add_argument("--json", "--json-file") + .help("(optional) The json file to load arguments in from") + .append(); + try { args.parse_args(argc, argv); @@ -445,46 +463,137 @@ int __cdecl main(int argc, char* argv[]) globals::g_Username = args.is_used("--user") ? args.get("--user") : globals::g_Username; globals::g_Password = args.is_used("--pass") ? args.get("--pass") : globals::g_Password; + globals::g_OtpCode = args.is_used("--otp") ? args.get("--otp") : globals::g_OtpCode; globals::g_Email = args.is_used("--email") ? args.get("--email") : globals::g_Email; + std::string jsonFilename = args.is_used("--json") ? args.get("--json") : std::string {}; + if (args.is_used("--user") && args.is_used("--pass")) { globals::g_FirstLogin = true; } + auto setLanguage = [&](std::string language) + { + if (!language.empty()) + { + if (!_strnicmp(language.c_str(), "JP", 2) || !_strnicmp(language.c_str(), "0", 1)) + { + globals::g_Language = xiloader::Language::Japanese; + } + if (!_strnicmp(language.c_str(), "US", 2) || !_strnicmp(language.c_str(), "1", 1)) + { + globals::g_Language = xiloader::Language::English; + } + if (!_strnicmp(language.c_str(), "EU", 2) || !_strnicmp(language.c_str(), "2", 1)) + { + globals::g_Language = xiloader::Language::European; + } + } + }; + if (args.is_used("--lang")) { std::string language = args.get("--lang"); - if (!_strnicmp(language.c_str(), "JP", 2) || !_strnicmp(language.c_str(), "0", 1)) - { - globals::g_Language = xiloader::Language::Japanese; - } - if (!_strnicmp(language.c_str(), "US", 2) || !_strnicmp(language.c_str(), "1", 1)) + setLanguage(language); + } + + bool bUseHairpinFix = args.is_used("--hairpin") ? args.get("--hairpin") : false; + + globals::g_Hide = args.is_used("--hide") ? args.get("--hide") : globals::g_Hide; + + bool readInJsonArgs = false; + if (!jsonFilename.empty()) + { + std::string extension = ".json"; + + bool endsInJsonExtension = std::equal(extension.rbegin(), extension.rend(), jsonFilename.rbegin()); + + if (endsInJsonExtension && std::filesystem::exists(jsonFilename)) { - globals::g_Language = xiloader::Language::English; + std::ifstream jsonFile(jsonFilename); + json jsonData = json::parse(jsonFile, nullptr, false); + + jsonFile.close(); + + if (jsonData.is_discarded()) // not valid json + { + xiloader::console::output(xiloader::color::error, "--json was specified but the file at the input arg is not valid json"); + return 1; + } + else + { + readInJsonArgs = true; + + auto maybeUsername = jsonGet(jsonData, "username"); + auto maybePassword = jsonGet(jsonData, "password"); + + globals::g_Username = maybeUsername.value_or(globals::g_Username); + globals::g_Password = maybeUsername.value_or(globals::g_Password); + + // Set autologin if it isn't set already + if (maybeUsername.has_value() && maybePassword.has_value()) + { + globals::g_FirstLogin = true; + } + + globals::g_ServerAddress = jsonGet(jsonData, "server").value_or(globals::g_ServerAddress); + globals::g_ServerPort = jsonGet(jsonData, "serverport").value_or(globals::g_ServerPort); + + globals::g_LoginDataPort = jsonGet(jsonData, "dataport").value_or(globals::g_LoginDataPort); + globals::g_LoginViewPort = jsonGet(jsonData, "viewport").value_or(globals::g_LoginViewPort); + globals::g_LoginAuthPort = jsonGet(jsonData, "authport").value_or(globals::g_LoginAuthPort); + + // try string and int + auto maybeOtpString = jsonGet(jsonData, "otp"); + auto maybeOtpInt = jsonGet(jsonData, "otp"); + + if (maybeOtpString.has_value()) + { + globals::g_OtpCode = maybeOtpString.value(); + } + else if (maybeOtpInt.has_value()) + { + globals::g_OtpCode = std::to_string(maybeOtpInt.value()); + } + + globals::g_OtpCode = jsonGet(jsonData, "otp").value_or(globals::g_OtpCode); + globals::g_Email = jsonGet(jsonData, "email").value_or(globals::g_Email); + + bUseHairpinFix = jsonGet(jsonData, "hairpin").value_or(bUseHairpinFix); + globals::g_Hide = jsonGet(jsonData, "hide").value_or(globals::g_Hide); + + std::string language = jsonGet(jsonData, "language").value_or({}); + + setLanguage(language); + } } - if (!_strnicmp(language.c_str(), "EU", 2) || !_strnicmp(language.c_str(), "2", 1)) + else { - globals::g_Language = xiloader::Language::European; + xiloader::console::output(xiloader::color::error, "--json was specified but the file at the input arg does not exist."); + return 1; } } - bool bUseHairpinFix = args.is_used("--hairpin") ? args.get("--hairpin") : false; - - globals::g_Hide = args.is_used("--hide") ? args.get("--hide") : globals::g_Hide; + std::array version = globals::g_VersionNumber; /* Output the banner.. */ time_t currentTime = time(NULL); int currentYear = localtime(¤tTime)->tm_year + 1900; // Year is returned as the number of years since 1900. xiloader::console::output(xiloader::color::lightred, "=========================================================="); xiloader::console::output(xiloader::color::lightgreen, "DarkStar Boot Loader (c) 2015 DarkStar Team"); - xiloader::console::output(xiloader::color::lightgreen, "LandSandBoat Boot Loader (c) 2021-%d LandSandBoat Team (v%s)", currentYear, globals::g_VersionNumber.c_str()); + xiloader::console::output(xiloader::color::lightgreen, "LandSandBoat Boot Loader (c) 2021-%d LandSandBoat Team (v%u.%u.%u)", currentYear, version[0], version[1], version[2]); xiloader::console::output(xiloader::color::lightblue, "Using %s", MBEDTLS_VERSION_STRING_FULL); // this prints "Using Mbed TLS #.#.#" xiloader::console::output(xiloader::color::lightpurple, "Git Repo : https://github.com/LandSandBoat/xiloader"); xiloader::console::output(xiloader::color::lightpurple, "Bug Reports: https://github.com/LandSandBoat/xiloader/issues"); xiloader::console::output(xiloader::color::lightred, "=========================================================="); + if (readInJsonArgs) + { + xiloader::console::output(xiloader::color::info, "Read in arguments from json file."); + } + /* Initialize Winsock */ WSADATA wsaData = { 0 }; auto ret = WSAStartup(MAKEWORD(2, 2), &wsaData); @@ -569,15 +678,17 @@ int __cdecl main(int argc, char* argv[]) /* Attempt to create connection to the login server.. */ if (!xiloader::network::CreateConnection(&sock, loginport.c_str())) { - sock.s = INVALID_SOCKET; - xiloader::console::output(xiloader::color::error, "Failed to initialize connection to server on port %s!", loginport); + sock.s = INVALID_SOCKET; + int err = WSAGetLastError(); + xiloader::console::output(xiloader::color::error, "Failed to initialize connection to server on port %s, winsock error: %d", loginport.c_str(), err); } /* Attempt to create listening server for POL thread*/ if (!xiloader::network::CreateListenServer(&polsock, IPPROTO_TCP, serverport.c_str())) { polsock = INVALID_SOCKET; - xiloader::console::output(xiloader::color::error, "Failed to initialize listen server on port %s!", serverport); + int err = WSAGetLastError(); + xiloader::console::output(xiloader::color::error, "Failed to initialize listen server on port %s, winsock error: %d", serverport.c_str(), err); } // Check if sockets are invalid diff --git a/src/menus.h b/src/menus.h new file mode 100644 index 0000000..222b505 --- /dev/null +++ b/src/menus.h @@ -0,0 +1,504 @@ +/* +=========================================================================== + + Copyright (c) 2025 LandSandBoat Dev Teams + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see http://www.gnu.org/licenses/ + +=========================================================================== +*/ + +#pragma once + +#include "ftxui/component/captured_mouse.hpp" // for ftxui +#include "ftxui/component/component.hpp" // for Menu +#include "ftxui/component/component_options.hpp" // for MenuOption +#include "ftxui/component/screen_interactive.hpp" // for ScreenInteractive + +enum class MenuSelection : uint8_t +{ + None = 0, + Login = 1, + CreateAccount = 2, + ChangePassword = 3, + TwoFactorSubmenu = 4, // Not a command to be processed + RegisterTwoFactorOTP = 5, + RemoveTwoFactorOTP = 6, + RegenerateTwoFactorRemovalCode = 7, + ValidateTwoFactorOTP = 8, + Exit = 255, +}; + +namespace menus +{ + using namespace ftxui; + + MenuSelection twoFactorSubMenu() + { + auto screen = ScreenInteractive::TerminalOutput(); + int selected = 0; // Container::Vertical requires `int` input + + // clang-format off + auto menu = Container::Vertical( + { + MenuEntry("1) Register 2FA OTP"), + MenuEntry("2) Remove 2FA OTP"), + MenuEntry("3) Regenerate 2FA OTP removal code"), + MenuEntry("4) Validate 2FA OTP"), + MenuEntry("5) Exit Menu"), + }, + &selected); + + menu |= border; // Add border + + menu |= CatchEvent([&](Event event) + { + if (event == Event::Character('1')) // Select "Register OTP" + { + selected = 0; + screen.Exit(); + return true; + } + else if (event == Event::Character('2')) // Select "Remove OTP" + { + selected = 1; + screen.Exit(); + return true; + } + else if (event == Event::Character('3')) // Select "Regenerate 2FA OTP removal code" + { + selected = 2; + screen.Exit(); + return true; + } + else if (event == Event::Character('4')) // Select "Validate 2FA OTP" + { + selected = 3; + screen.Exit(); + return true; + } + else if (event == Event::Character('5')) // Select "Exit Menu" + { + selected = 4; + screen.Exit(); + return true; + } + else if (event == event.Return) + { + screen.Exit(); + return true; + } + + return false; + }); + // clang-format on + + screen.Loop(menu); + + // Because ftxui is rigid with its input, return a menu selection here + switch (selected) + { + case 0: + { + return MenuSelection::RegisterTwoFactorOTP; + } + case 1: + { + return MenuSelection::RemoveTwoFactorOTP; + } + case 2: + { + return MenuSelection::RegenerateTwoFactorRemovalCode; + } + case 3: + { + return MenuSelection::ValidateTwoFactorOTP; + } + case 4: + default: + { + return MenuSelection::None; // In this instance, the caller will replay the main menu + } + } + } + + MenuSelection mainMenu() + { + auto screen = ScreenInteractive::TerminalOutput(); + int selected = 0; // Container::Vertical requires `int` input + + // clang-format off + auto menu = Container::Vertical( + { + MenuEntry("1) Login"), + MenuEntry("2) Create New Account"), + MenuEntry("3) Change Account Password"), + MenuEntry("4) 2FA Options"), + MenuEntry("5) Exit"), + }, + &selected); + + menu |= border; // Add border + + menu |= CatchEvent([&](Event event) + { + + if (event == Event::Character('1')) // Select "login" + { + selected = 0; + screen.Exit(); + return true; + } + else if (event == Event::Character('2')) // Select "create new account" + { + selected = 1; + screen.Exit(); + return true; + } + else if (event == Event::Character('3')) // Select "change account password" + { + selected = 2; + screen.Exit(); + return true; + } + else if (event == Event::Character('4')) // Select "2FA Settings" + { + selected = 3; + screen.Exit(); + return true; + } + else if (event == Event::Character('5')) // Select "Exit" + { + selected = 4; + screen.Exit(); + return true; + } + else if (event == event.Return) + { + screen.Exit(); + return true; + } + + return false; + }); + // clang-format on + + screen.Loop(menu); + + // Because ftxui is rigid with its input, get a menu selection here + switch (selected) + { + case 0: + { + return MenuSelection::Login; + } + case 1: + { + return MenuSelection::CreateAccount; + } + case 2: + { + return MenuSelection::ChangePassword; + } + case 3: + { + return MenuSelection::TwoFactorSubmenu; + } + case 4: + { + return MenuSelection::Exit; + } + [[fallthrough]]; + default: + { + return MenuSelection::None; // In this instance, we will exit + } + } + + return MenuSelection::None; + } + + void enterCredentialsWithOTP(std::string& username, std::string& password, std::string& OTP) + { + ftxui::InputOption password_option; + password_option.password = true; + + ftxui::Component input_username = ftxui::Input(&username, ""); + ftxui::Component input_password = ftxui::Input(&password, "", password_option); + ftxui::Component input_otp = ftxui::Input(&OTP, ""); + + // The component tree: + auto component = ftxui::Container::Vertical({ + input_username, + input_password, + input_otp, + }); + + auto screen = ftxui::ScreenInteractive::TerminalOutput(); + + // clang-format off + component |= ftxui::CatchEvent([&](ftxui::Event event) + { + if (event == event.Return) + { + if (input_username->Focused()) + { + input_password->TakeFocus(); + } + else if (input_password->Focused()) + { + input_otp->TakeFocus(); + } + else if (input_otp->Focused()) + { + screen.Exit(); + } + return true; + } + return false; + }); + // clang-format on + + // Tweak how the component tree is rendered: + // clang-format off + auto renderer = ftxui::Renderer(component, [&] + { + return ftxui::vbox({ ftxui::hbox(ftxui::text(" Username: "), input_username->Render()), + ftxui::hbox(ftxui::text(" Password: "), input_password->Render()), + ftxui::hbox(ftxui::text(" OTP Code: "), input_otp->Render()) + }) | ftxui::border; + }); + // clang-format on + + screen.Loop(renderer); + } + + void enterCredentialsNoOTP(std::string& username, std::string& password) + { + ftxui::InputOption password_option; + password_option.password = true; + + ftxui::Component input_username = ftxui::Input(&username, ""); + ftxui::Component input_password = ftxui::Input(&password, "", password_option); + + // The component tree: + auto component = ftxui::Container::Vertical({ + input_username, + input_password, + }); + + auto screen = ftxui::ScreenInteractive::TerminalOutput(); + + // clang-format off + component |= ftxui::CatchEvent([&](ftxui::Event event) + { + if (event == event.Return) + { + if (input_username->Focused()) + { + input_password->TakeFocus(); + } + else if (input_password->Focused()) + { + screen.Exit(); + } + return true; + } + return false; + }); + // clang-format on + + // Tweak how the component tree is rendered: + // clang-format off + auto renderer = ftxui::Renderer(component, [&] + { + return ftxui::vbox({ ftxui::hbox(ftxui::text(" Username: "), input_username->Render()), + ftxui::hbox(ftxui::text(" Password: "), input_password->Render()), + }) | ftxui::border; + }); + // clang-format on + + screen.Loop(renderer); + } + + bool confirmNewPassword(std::string& confirmed_password) + { + ftxui::InputOption password_option; + password_option.password = true; + + std::string new_password = ""; + confirmed_password = ""; + + ftxui::Component input_password = ftxui::Input(&new_password, "", password_option); + ftxui::Component input_confirmed_password = ftxui::Input(&confirmed_password, "", password_option); + + // The component tree: + auto component = ftxui::Container::Vertical({ + input_password, + input_confirmed_password, + }); + + auto screen = ftxui::ScreenInteractive::TerminalOutput(); + + // clang-format off + component |= ftxui::CatchEvent([&](ftxui::Event event) + { + if (event == event.Return) + { + if (input_password->Focused()) // 1st password field focused + { + input_confirmed_password->TakeFocus(); + } + else // confirmed password focused + { + screen.Exit(); + } + return true; + } + return false; + }); + // clang-format on + + // Tweak how the component tree is rendered: + // clang-format off + auto renderer = ftxui::Renderer(component, [&] + { + return ftxui::vbox({ ftxui::hbox(ftxui::text(" Password : "), input_password->Render()), + ftxui::hbox(ftxui::text(" Confirm Password: "), input_confirmed_password->Render()) + }) | ftxui::border; + }); + // clang-format on + + screen.Loop(renderer); + + if (confirmed_password != new_password) + { + return false; + } + + return true; + } + + bool okCancelDialog(std::string input) + { + auto screen = ScreenInteractive::TerminalOutput(); + int selected = 0; + + // clang-format off + auto menu = Container::Vertical( + { + MenuEntry("1) " + input ), + MenuEntry("2) Cancel"), + }, + &selected); + + menu |= border; // Add border + + menu |= CatchEvent([&](Event event) + { + + if (event == Event::Character('1')) // Select "ok" + { + selected = 0; + screen.Exit(); + return true; + } + else if (event == Event::Character('2')) // Select "cancel" + { + selected = 1; + screen.Exit(); + return true; + } + else if (event == event.Return) + { + screen.Exit(); + return true; + } + + return false; + }); + // clang-format on + + screen.Loop(menu); + + return selected == 0; + } + bool createNewAccount(std::string& username, std::string& confirmed_password) + { + ftxui::InputOption password_option; + password_option.password = true; + + std::string password = ""; + username = ""; + confirmed_password = ""; + + ftxui::Component input_username = ftxui::Input(&username, ""); + ftxui::Component input_password = ftxui::Input(&password, "", password_option); + ftxui::Component input_confirmed_password = ftxui::Input(&confirmed_password, "", password_option); + + // The component tree: + auto component = ftxui::Container::Vertical({ + input_username, + input_password, + input_confirmed_password, + }); + + auto screen = ftxui::ScreenInteractive::TerminalOutput(); + + // clang-format off + component |= ftxui::CatchEvent([&](ftxui::Event event) + { + if (event == event.Return) + { + if (input_username->Focused()) + { + input_password->TakeFocus(); + } + else if (input_password->Focused()) // 1st password field focused + { + input_confirmed_password->TakeFocus(); + } + else // confirmed password focused + { + screen.Exit(); + } + return true; + } + return false; + }); + // clang-format on + + // Tweak how the component tree is rendered: + // clang-format off + auto renderer = ftxui::Renderer(component, [&] + { + return ftxui::vbox( + { + ftxui::hbox(ftxui::text(" Username : "), input_username->Render()), + ftxui::hbox(ftxui::text(" Password : "), input_password->Render()), + ftxui::hbox(ftxui::text(" Confirm Password: "), input_confirmed_password->Render()) + }) | ftxui::border; + }); + // clang-format on + + screen.Loop(renderer); + + if (confirmed_password != password) + { + return false; + } + + return true; + } +} // namespace menus diff --git a/src/network.cpp b/src/network.cpp index 3e6187f..8acadfe 100644 --- a/src/network.cpp +++ b/src/network.cpp @@ -22,26 +22,31 @@ This file is part of DarkStar-server source code. */ #include "helpers.h" +#include "menus.h" #include "network.h" #include #include +#include "helpers.h" +#include "command_handler.h" + /* Externals */ namespace globals { - extern std::string g_ServerAddress; - extern std::string g_Username; - extern std::string g_Password; - extern char g_SessionHash[16]; - extern std::string g_Email; - extern std::string g_VersionNumber; - extern uint16_t g_ServerPort; - extern uint16_t g_LoginDataPort; - extern uint16_t g_LoginViewPort; - extern uint16_t g_LoginAuthPort; - extern char* g_CharacterList; - extern bool g_IsRunning; - extern bool g_FirstLogin; + extern std::string g_ServerAddress; + extern std::string g_Username; + extern std::string g_Password; + extern std::string g_OtpCode; + extern char g_SessionHash[16]; + extern std::string g_Email; + extern std::array g_VersionNumber; + extern uint16_t g_ServerPort; + extern uint16_t g_LoginDataPort; + extern uint16_t g_LoginViewPort; + extern uint16_t g_LoginAuthPort; + extern char* g_CharacterList; + extern bool g_IsRunning; + extern bool g_FirstLogin; } // mbed tls state @@ -223,6 +228,10 @@ namespace xiloader sock->LocalAddress = their_inaddr_ptr->sin_addr.S_un.S_addr; sock->ServerAddress = inet_addr(globals::g_ServerAddress.c_str()); + unsigned char recvBuffer[4096] = {}; + + mbedtls_ssl_conf_read_timeout(&sslState::conf, 1000); + return 1; } @@ -237,60 +246,45 @@ namespace xiloader */ bool network::CreateListenServer(SOCKET* sock, int protocol, const char* port) { - struct addrinfo hints; - memset(&hints, 0x00, sizeof(hints)); - - hints.ai_family = AF_INET; - hints.ai_socktype = protocol == IPPROTO_UDP ? SOCK_DGRAM : SOCK_STREAM; - hints.ai_protocol = protocol; - hints.ai_flags = AI_PASSIVE; - - /* Attempt to resolve the local address.. */ - struct addrinfo* addr = NULL; - if (getaddrinfo(NULL, port, &hints, &addr)) - { - xiloader::console::output(xiloader::color::error, "Failed to obtain local address information."); - return false; - } + sockaddr_in sin = {}; + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = inet_addr("127.0.0.1"); + sin.sin_port = htons(51220); /* Create the listening socket.. */ - *sock = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol); + *sock = socket(AF_INET, protocol == IPPROTO_UDP ? SOCK_DGRAM : SOCK_STREAM, protocol); if (*sock == INVALID_SOCKET) { xiloader::console::output(xiloader::color::error, "Failed to create listening socket."); - freeaddrinfo(addr); return false; } + BOOL enable = 1; + /* Set socket option on internal server to allow sharing the port for multibox users */ - if (setsockopt(*sock, SOL_SOCKET, SO_REUSEADDR, (char*)(&[] { return TRUE; }), sizeof(BOOL)) == SOCKET_ERROR) + if (setsockopt(*sock, SOL_SOCKET, SO_REUSEADDR, (char*)&enable, sizeof(BOOL)) == SOCKET_ERROR) { xiloader::console::output(xiloader::color::error, "Failed to set reusable address option on socket. %d", WSAGetLastError()); - - freeaddrinfo(addr); return false; } /* Bind to the local address.. */ - if (bind(*sock, addr->ai_addr, (int)addr->ai_addrlen) == SOCKET_ERROR) + if (bind(*sock, reinterpret_cast(&sin), sizeof(sin)) == SOCKET_ERROR) { - xiloader::console::output(xiloader::color::error, "Failed to bind to listening socket."); + xiloader::console::output(xiloader::color::error, "Failed to bind to listening socket. %d", WSAGetLastError()); - freeaddrinfo(addr); closesocket(*sock); *sock = INVALID_SOCKET; return false; } - freeaddrinfo(addr); - /* Attempt to listen for clients if we are using TCP.. */ if (protocol == IPPROTO_TCP) { if (listen(*sock, SOMAXCONN) == SOCKET_ERROR) { - xiloader::console::output(xiloader::color::error, "Failed to listen for connections."); + xiloader::console::output(xiloader::color::error, "Failed to listen for connections, %d", WSAGetLastError()); closesocket(*sock); *sock = INVALID_SOCKET; @@ -336,9 +330,12 @@ namespace xiloader */ bool network::VerifyAccount(datasocket* sock) { - unsigned char recvBuffer[1024] = { 0 }; - unsigned char sendBuffer[1024] = { 0 }; + unsigned char recvBuffer[8192] = { 0 }; + unsigned char sendBuffer[8192] = { 0 }; std::string new_password = ""; + + int8_t command = 0; // Same datatype as in xi_connect + /* Create connection if required.. */ // TODO: fix this check for TLS @@ -353,216 +350,191 @@ namespace xiloader if (bUseAutoLogin) xiloader::console::output(xiloader::color::lightgreen, "Autologin activated!"); - // TODO: kill all labels and gotos if (!bUseAutoLogin) { - xiloader::console::output("=========================================================="); - xiloader::console::output("What would you like to do?"); - xiloader::console::output(" 1.) Login"); - xiloader::console::output(" 2.) Create New Account"); - xiloader::console::output(" 3.) Change Account Password"); - xiloader::console::output("=========================================================="); - printf("\nEnter a selection: "); - - std::string input; - std::cin >> input; - std::cout << std::endl; - - /* User wants to log into an existing account or modify an existing account's password. */ - if (input == "1" || input == "3") + auto selected = MenuSelection::None; + + // Check for None, because the 2FA submenu can select None and needs to replay the main menu. + // Any other option is a "real" selection + while (selected == MenuSelection::None) { - if (input == "3") - xiloader::console::output("Before resetting your password, first verify your account details."); - xiloader::console::output("Please enter your login information."); - std::cout << "\nUsername: "; - std::cin >> globals::g_Username; - std::cout << "Password: "; - globals::g_Password.clear(); - - /* Read in each char and instead of displaying it. display a "*" */ - char ch; - while ((ch = static_cast(_getch())) != '\r') + xiloader::console::output("What would you like to do?"); + selected = menus::mainMenu(); + + if (selected == MenuSelection::TwoFactorSubmenu) + { + xiloader::console::output("What would you like to do?"); + selected = menus::twoFactorSubMenu(); + } + } + + globals::g_Username = ""; + globals::g_Password = ""; + globals::g_OtpCode = ""; + + switch (selected) + { + case MenuSelection::Login: + { + menus::enterCredentialsWithOTP(globals::g_Username, globals::g_Password, globals::g_OtpCode); + + command = 0x10; // login + break; + } + case MenuSelection::CreateAccount: { - if (ch == '\0') - continue; - else if (ch == '\b') + xiloader::console::output("Please enter your desired login information."); + xiloader::console::output("Username (3-15 characters)"); + xiloader::console::output("Password (6-32 characters)"); + + while (!menus::createNewAccount(globals::g_Username, globals::g_Password)) { - if (globals::g_Password.size()) - { - globals::g_Password.pop_back(); - std::cout << "\b \b"; - } + xiloader::console::output(xiloader::color::error, "Passwords did not match! Please try again."); } - else + + command = 0x20; // create account + break; + } + + case MenuSelection::ChangePassword: + { + xiloader::console::output("Before resetting your password, first verify your account details."); + xiloader::console::output("Please enter your login information. The OTP code is only necessary if you have one registered."); + + menus::enterCredentialsWithOTP(globals::g_Username, globals::g_Password, globals::g_OtpCode); + + xiloader::console::output("Enter new password (6-32 characters): "); + + while (!menus::confirmNewPassword(new_password)) { - globals::g_Password.push_back(ch); - std::cout << '*'; + xiloader::console::output(xiloader::color::error, "Passwords did not match! Please try again."); } + + command = 0x30; // change password + break; } - std::cout << std::endl; + case MenuSelection::RegisterTwoFactorOTP: + { + menus::enterCredentialsNoOTP(globals::g_Username, globals::g_Password); - char event_code = (input == "1") ? 0x10 : 0x30; + command = 0x31; // create TOTP + break; + } - if (input == "3") + case MenuSelection::RemoveTwoFactorOTP: { - std::string confirmed_password = ""; - do - { - std::cout << "Enter new password (6-32 characters): "; - confirmed_password = ""; - new_password = ""; - std::cin >> new_password; - std::cout << "Repeat Password : "; - std::cin >> confirmed_password; - std::cout << std::endl; - - if (new_password != confirmed_password) - { - xiloader::console::output(xiloader::color::error, "Passwords did not match! Please try again."); - } - } while (new_password != confirmed_password); - new_password = confirmed_password; + xiloader::console::output("Please enter your login information; your OTP code may be substituted for the recovery code"); + menus::enterCredentialsWithOTP(globals::g_Username, globals::g_Password, globals::g_OtpCode); + + command = 0x32; + break; } - sendBuffer[0x39] = event_code; - } - /* User wants to create a new account.. */ - else if (input == "2") - { - create_account: - xiloader::console::output("Please enter your desired login information."); - std::cout << "\nUsername (3-15 characters): "; - std::cin >> globals::g_Username; - std::cout << "Password (6-32 characters): "; - globals::g_Password.clear(); - std::cin >> globals::g_Password; - std::cout << "Repeat Password : "; - std::cin >> input; - std::cout << std::endl; - - // TODO: warn if username/password is too long - - if (input != globals::g_Password) + + case MenuSelection::RegenerateTwoFactorRemovalCode: { - xiloader::console::output(xiloader::color::error, "Passwords did not match! Please try again."); - goto create_account; + xiloader::console::output("Please enter your login information; your OTP code may be substituted for the recovery code"); + menus::enterCredentialsWithOTP(globals::g_Username, globals::g_Password, globals::g_OtpCode); + + command = 0x33; // Regenerate recovery code + break; } + case MenuSelection::ValidateTwoFactorOTP: // also enables OTP + { + menus::enterCredentialsWithOTP(globals::g_Username, globals::g_Password, globals::g_OtpCode); - sendBuffer[0x39] = 0x20; - } + command = 0x34; + break; + } + case MenuSelection::Exit: + { + exit(0); // Bit ugly, can't really exit properly with the current code flow + break; + } - std::cout << std::endl; + default: + { + xiloader::console::output("Invalid menu selection"); + return 0; + } + } } else { /* User has auto-login enabled.. */ - sendBuffer[0x39] = 0x10; + command = 0x10; globals::g_FirstLogin = false; } - sendBuffer[0] = 0xFF; // Magic for new xiloader bits - - sendBuffer[1] = 0x00; // Feature flags, none used yet. - sendBuffer[2] = 0x00; - sendBuffer[3] = 0x00; - sendBuffer[4] = 0x00; - sendBuffer[5] = 0x00; - sendBuffer[6] = 0x00; - sendBuffer[7] = 0x00; - sendBuffer[8] = 0x00; + json login_json; + login_json["username"] = globals::g_Username; + login_json["password"] = globals::g_Password; + login_json["otp"] = globals::g_OtpCode; + login_json["new_password"] = new_password; + login_json["version"] = globals::g_VersionNumber; + login_json["command"] = command; - /* Copy username and password into buffer.. */ - memcpy(sendBuffer + 0x09, globals::g_Username.c_str(), globals::g_Username.length()); - memcpy(sendBuffer + 0x19, globals::g_Password.c_str(), globals::g_Password.length()); + std::string str = login_json.dump(); + const char* strBuffer = str.c_str(); + size_t strBufferLen = strlen(strBuffer); - /* Copy changed password into buffer */ - memcpy(sendBuffer + 0x40, new_password.c_str(), 32); + /* Send info to server and obtain response.. */ + mbedtls_ssl_write(&sslState::ssl, reinterpret_cast(strBuffer), strBufferLen); - // 17 byte wide operator specific space starting at 0x50 // This region will be used for anything server operators may install into custom launchers. + std::memset(recvBuffer, 0, sizeof(recvBuffer)); - /* Copy version number into buffer */ - memcpy(sendBuffer + 0x61, globals::g_VersionNumber.c_str(), 5); + if (mbedtls_ssl_read(&sslState::ssl, recvBuffer, sizeof(recvBuffer)) == MBEDTLS_ERR_SSL_TIMEOUT) + { + xiloader::console::output(xiloader::color::error, "Remote failed to reply within the timeout, exiting..."); + exit(2); + } - /* Send info to server and obtain response.. */ - mbedtls_ssl_write(&sslState::ssl, reinterpret_cast(sendBuffer), 102); - mbedtls_ssl_read(&sslState::ssl, recvBuffer, 21); + json login_reply_json = json::parse(recvBuffer, nullptr, false); - /* Handle the obtained result.. */ - switch (recvBuffer[0]) + if (login_reply_json.is_discarded()) { - case 0x0001: // Success (Login) - { - xiloader::console::output(xiloader::color::success, "Successfully logged in as %s!", globals::g_Username.c_str()); + xiloader::console::output(xiloader::color::error, "Bad json reply from remote"); + closesocket(sock->s); + sock->s = INVALID_SOCKET; + return false; + } - sock->AccountId = ref(recvBuffer, 1); - std::memcpy(&globals::g_SessionHash, recvBuffer + 5, sizeof(globals::g_SessionHash)); + bool retval = false; - shutdown(sock->s, SD_BOTH); - closesocket(sock->s); - sock->s = INVALID_SOCKET; - return true; - } - case 0x0002: // Error (Login) - { - xiloader::console::output(xiloader::color::error, "Failed to login. Invalid username or password."); - closesocket(sock->s); - sock->s = INVALID_SOCKET; - return false; - } - case 0x0003: // Success (Create Account) - { - xiloader::console::output(xiloader::color::success, "Account successfully created!"); - closesocket(sock->s); - sock->s = INVALID_SOCKET; - return false; - } - case 0x0004: // Error (Create Account) - { - xiloader::console::output(xiloader::color::error, "Failed to create the new account. Username already taken."); - closesocket(sock->s); - sock->s = INVALID_SOCKET; - return false; - } - case 0x0006: // Success (Changed Password) + std::string errorMessage = jsonGet(login_reply_json, "error_message").value_or({}); // {} = empty string + + if (errorMessage.empty()) + { + auto maybeCommand = jsonGet(login_reply_json, "result"); + if (maybeCommand.has_value()) { - xiloader::console::output(xiloader::color::success, "Password updated successfully!"); - std::cout << std::endl; - globals::g_Password.clear(); - closesocket(sock->s); - sock->s = INVALID_SOCKET; - return false; + command = maybeCommand.value(); } - case 0x0007: // Error (Changed Password) + else { - xiloader::console::output(xiloader::color::error, "Failed to change password."); - std::cout << std::endl; - globals::g_Password.clear(); + xiloader::console::output(xiloader::color::error, "xi_connect didn't send a proper reply command"); closesocket(sock->s); sock->s = INVALID_SOCKET; return false; } - // Commands 0x0008 through 0x0008 are currently unused + retval = handleLoginCommand(command, login_reply_json, sock->AccountId, sock->s); + } + else + { + xiloader::console::output(xiloader::color::error, "Error from remote:"); - case 0x000A: - { - xiloader::console::output(xiloader::color::error, "Failed to login. Account already logged in."); - closesocket(sock->s); - sock->s = INVALID_SOCKET; - return false; - } - case 0x000B: - { - xiloader::console::output(xiloader::color::error, "Failed to login. Expected xiloader version mismatch; check with your provider."); - closesocket(sock->s); - sock->s = INVALID_SOCKET; - return false; - } + xiloader::console::printMultiLine(errorMessage, "\n", xiloader::color::error); + return false; + } + + // Socket is already closed if handleLoginCommand is true + if (!retval) + { + closesocket(sock->s); } - /* We should not get here.. */ - closesocket(sock->s); sock->s = INVALID_SOCKET; - return false; + return retval; } /** diff --git a/src/network.h b/src/network.h index a9b9862..20c352d 100644 --- a/src/network.h +++ b/src/network.h @@ -81,7 +81,7 @@ namespace xiloader * @return Non-important return. */ static DWORD __stdcall PolDataComm(LPVOID lpParam); - + public: /** @@ -115,7 +115,7 @@ namespace xiloader * @return True on success, false otherwise. */ static bool CreateListenServer(SOCKET* sock, int protocol, const char* port); - + /** * @brief Resolves the given hostname to its long ip format. * @@ -134,7 +134,7 @@ namespace xiloader * @return True on success, false otherwise. */ static bool VerifyAccount(datasocket* sock); - + /** * @brief Starts the data communication between the client and server. *