From 3d496b541e7ce70e9174f2879a8e06f2f0be3177 Mon Sep 17 00:00:00 2001 From: Darryl Pogue Date: Sun, 12 Nov 2023 21:37:39 -0800 Subject: [PATCH 1/7] Add hsOptionalCall for X11/Wayland use This is essentially the same as the plOptionalWinCall introduced for DPI handling, except that it doesn't enforce WINAPI calling conventions and can also be used to dynamically load values as well as functions. --- Sources/Plasma/CoreLib/CMakeLists.txt | 1 + Sources/Plasma/CoreLib/hsOptionalCall.h | 186 ++++++++++++++++++++++++ 2 files changed, 187 insertions(+) create mode 100644 Sources/Plasma/CoreLib/hsOptionalCall.h diff --git a/Sources/Plasma/CoreLib/CMakeLists.txt b/Sources/Plasma/CoreLib/CMakeLists.txt index 539cdf4b3c..9ded69dffd 100644 --- a/Sources/Plasma/CoreLib/CMakeLists.txt +++ b/Sources/Plasma/CoreLib/CMakeLists.txt @@ -49,6 +49,7 @@ set(CoreLib_HEADERS hsLockGuard.h hsMatrix44.h hsMatrixMath.h + hsOptionalCall.h hsPoint2.h hsPoolVector.h hsQuat.h diff --git a/Sources/Plasma/CoreLib/hsOptionalCall.h b/Sources/Plasma/CoreLib/hsOptionalCall.h new file mode 100644 index 0000000000..1e95f0f44c --- /dev/null +++ b/Sources/Plasma/CoreLib/hsOptionalCall.h @@ -0,0 +1,186 @@ +/*==LICENSE==* + +CyanWorlds.com Engine - MMOG client, server and tools +Copyright (C) 2011 Cyan Worlds, Inc. + +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 . + +Additional permissions under GNU GPL version 3 section 7 + +If you modify this Program, or any covered work, by linking or +combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK, +NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent +JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK +(or a modified version of those libraries), +containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA, +PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG +JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the +licensors of this Program grant you additional +permission to convey the resulting work. Corresponding Source for a +non-source form of such a combination shall include the source code for +the parts of OpenSSL and IJG JPEG Library used as well as that of the covered +work. + +You can contact Cyan Worlds, Inc. by email legal@cyan.com + or by snail mail at: + Cyan Worlds, Inc. + 14617 N Newport Hwy + Mead, WA 99021 + +*==LICENSE==*/ + +#ifndef _hsOptionalCall_h +#define _hsOptionalCall_h + +#include +#include + +#include "HeadSpin.h" + +#ifdef HS_BUILD_FOR_WIN32 +# include "hsWindows.h" + + typedef const wchar* hsLibName_t; +#else +# include + + typedef const char* hsLibName_t; +#endif + +template class hsOptionalCall; + +template +class hsOptionalCall<_ReturnT(_ArgsT...)> +{ + using _FuncPtrT = _ReturnT(*)(_ArgsT...); + using _ResultT = typename std::conditional::value, std::nullptr_t, _ReturnT>::type; + + hsLibraryHndl fLibrary; + _FuncPtrT fProc; + +public: + hsOptionalCall(hsLibName_t lib, const char* func) + : fLibrary(), fProc() + { +#ifdef HS_BUILD_FOR_WIN32 + fLibrary = LoadLibraryW(lib); + if (fLibrary) { + fProc = (_FuncPtrT)GetProcAddress(fLibrary, func); + if (!fProc) { + FreeLibrary(fLibrary); + fLibrary = nullptr; + } + } +#else + fLibrary = dlopen(lib, RTLD_LAZY | RTLD_LOCAL); + if (fLibrary) { + fProc = (_FuncPtrT)dlsym(fLibrary, func); + if (!fProc) { + dlclose(fLibrary); + fLibrary = nullptr; + } + } +#endif + } + + ~hsOptionalCall() + { +#ifdef HS_BUILD_FOR_WIN32 + if (fLibrary) + FreeLibrary(fLibrary); +#else + if (fLibrary) + dlclose(fLibrary); +#endif + } + + std::optional<_ResultT> operator()(_ArgsT... args) const + { + if (fProc) { + if constexpr (std::is_void<_ReturnT>::value) { + return fProc(std::forward<_ArgsT>(args)...), nullptr; + } else { + return (_ResultT)fProc(std::forward<_ArgsT>(args)...); + } + } + return std::nullopt; + } + + operator bool() const + { + return fProc != nullptr; + } + + _FuncPtrT Get() const { return fProc; } +}; + +template +class hsOptionalCall<_ValueT> +{ + hsLibraryHndl fLibrary; + _ValueT* fValue; + +public: + hsOptionalCall(hsLibName_t lib, const char* func) + : fLibrary(), fValue() + { +#ifdef HS_BUILD_FOR_WIN32 + fLibrary = LoadLibraryW(lib); + if (fLibrary) { + fValue = (_ValueT*)GetProcAddress(fLibrary, func); + if (!fValue) { + FreeLibrary(fLibrary); + fLibrary = nullptr; + } + } +#else + fLibrary = dlopen(lib, RTLD_LAZY | RTLD_LOCAL); + if (fLibrary) { + fValue = (_ValueT*)dlsym(fLibrary, func); + if (!fValue) { + dlclose(fLibrary); + fLibrary = nullptr; + } + } +#endif + } + + ~hsOptionalCall() + { + fLibrary = nullptr; + } + + _ValueT operator *() const { + return *fValue; + } + + operator bool() const + { + return fValue != nullptr; + } + + _ValueT Get() const { return *fValue; } +}; + + +#ifdef HS_BUILD_FOR_APPLE +# define HS_OPTIONAL_LIB_SUFFIX ".dylib" +#else +# define HS_OPTIONAL_LIB_SUFFIX ".so" +#endif + +#define hsOptionalCallDecl(lib, func) \ + hsOptionalCall __##func(lib HS_OPTIONAL_LIB_SUFFIX, #func); + +#endif // _hsOptionalCall_h From fbb61cab69887a6b8017fdc98de01530d8723a02 Mon Sep 17 00:00:00 2001 From: Darryl Pogue Date: Thu, 29 Dec 2022 20:37:57 -0800 Subject: [PATCH 2/7] Barebones scaffolding for Linux client --- .github/workflows/ci.yml | 3 + Sources/Plasma/Apps/CMakeLists.txt | 10 +- Sources/Plasma/Apps/plClient/CMakeLists.txt | 14 + Sources/Plasma/Apps/plClient/linux/main.cpp | 696 ++++++++++++++++++++ 4 files changed, 719 insertions(+), 4 deletions(-) create mode 100644 Sources/Plasma/Apps/plClient/linux/main.cpp diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 33ef47dc5d..ef156ce701 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -206,6 +206,9 @@ jobs: libcairo2 \ libsecret-1-dev \ libtool \ + libx11-xcb-dev \ + libxcb-keysyms1-dev \ + libxcb-xfixes0-dev \ nasm \ ninja-build \ qtbase5-dev diff --git a/Sources/Plasma/Apps/CMakeLists.txt b/Sources/Plasma/Apps/CMakeLists.txt index db734838d5..7aac2b3f79 100644 --- a/Sources/Plasma/Apps/CMakeLists.txt +++ b/Sources/Plasma/Apps/CMakeLists.txt @@ -5,14 +5,16 @@ include_directories("${PLASMA_SOURCE_ROOT}/PubUtilLib") if(PLASMA_BUILD_CLIENT) add_subdirectory(plClient) +endif() - if(WIN32) +if(WIN32) + if(PLASMA_BUILD_CLIENT) add_subdirectory(plCrashHandler) endif() -endif() -if(PLASMA_BUILD_LAUNCHER AND WIN32) - add_subdirectory(plUruLauncher) + if(PLASMA_BUILD_LAUNCHER) + add_subdirectory(plUruLauncher) + endif() endif() if(PLASMA_BUILD_TOOLS) diff --git a/Sources/Plasma/Apps/plClient/CMakeLists.txt b/Sources/Plasma/Apps/plClient/CMakeLists.txt index d8a882c400..16c53d9572 100644 --- a/Sources/Plasma/Apps/plClient/CMakeLists.txt +++ b/Sources/Plasma/Apps/plClient/CMakeLists.txt @@ -120,6 +120,10 @@ elseif(APPLE) pfMetalPipelineShadersMSL21 pfMetalPipelineShadersMSL23 ) +elseif(UNIX AND NOT APPLE) + list(APPEND plClient_SOURCES + linux/main.cpp + ) else() list(APPEND plClient_SOURCES main.cpp @@ -246,6 +250,16 @@ target_link_libraries( ) target_include_directories(plClient PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}") +if(UNIX AND NOT APPLE) + target_link_libraries( + plClient + PRIVATE + xcb + xcb-xfixes + X11 + ) +endif() + if(PLASMA_EXTERNAL_RELEASE) set_target_properties(plClient PROPERTIES OUTPUT_NAME "UruExplorer") endif(PLASMA_EXTERNAL_RELEASE) diff --git a/Sources/Plasma/Apps/plClient/linux/main.cpp b/Sources/Plasma/Apps/plClient/linux/main.cpp new file mode 100644 index 0000000000..7d1f8baffa --- /dev/null +++ b/Sources/Plasma/Apps/plClient/linux/main.cpp @@ -0,0 +1,696 @@ +/*==LICENSE==* + +CyanWorlds.com Engine - MMOG client, server and tools +Copyright (C) 2011 Cyan Worlds, Inc. + +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 . + +Additional permissions under GNU GPL version 3 section 7 + +If you modify this Program, or any covered work, by linking or +combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK, +NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent +JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK +(or a modified version of those libraries), +containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA, +PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG +JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the +licensors of this Program grant you additional +permission to convey the resulting work. Corresponding Source for a +non-source form of such a combination shall include the source code for +the parts of OpenSSL and IJG JPEG Library used as well as that of the covered +work. + +You can contact Cyan Worlds, Inc. by email legal@cyan.com + or by snail mail at: + Cyan Worlds, Inc. + 14617 N Newport Hwy + Mead, WA 99021 + +*==LICENSE==*/ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "HeadSpin.h" +#include "plCmdParser.h" +#include "plPipeline.h" +#include "plProduct.h" +#include "pcSmallRect.h" +#include "hsStream.h" + +#include "plClient.h" +#include "plClientLoader.h" + +#include "pnEncryption/plChallengeHash.h" + +#include "plInputCore/plInputManager.h" +#include "plMessage/plInputEventMsg.h" +#include "plMessageBox/hsMessageBox.h" +#include "plNetClient/plNetClientMgr.h" +#include "plNetGameLib/plNetGameLib.h" +#include "plPhysX/plPXSimulation.h" +#include "plPipeline/hsG3DDeviceSelector.h" +#include "plProgressMgr/plProgressMgr.h" +#include "plResMgr/plVersion.h" +#include "plStatusLog/plStatusLog.h" + +#include "pfConsoleCore/pfConsoleEngine.h" + +extern bool gDataServerLocal; +extern bool gPythonLocal; +extern bool gSDLLocal; + +static plClientLoader gClient; +static xcb_connection_t* gXConn; +static pcSmallRect gWindowSize; +static hsSemaphore statusFlag; + +enum +{ + kArgSkipLoginDialog, + kArgServerIni, + kArgLocalData, + kArgLocalPython, + kArgLocalSDL, + kArgPlayerId, + kArgStartUpAgeName, + kArgPvdFile, + kArgSkipIntroMovies, + kArgRenderer +}; + +static const plCmdArgDef s_cmdLineArgs[] = { + { kCmdArgFlagged | kCmdTypeBool, "SkipLoginDialog", kArgSkipLoginDialog }, + { kCmdArgFlagged | kCmdTypeString, "ServerIni", kArgServerIni }, + { kCmdArgFlagged | kCmdTypeBool, "LocalData", kArgLocalData }, + { kCmdArgFlagged | kCmdTypeBool, "LocalPython", kArgLocalPython }, + { kCmdArgFlagged | kCmdTypeBool, "LocalSDL", kArgLocalSDL }, + { kCmdArgFlagged | kCmdTypeInt, "PlayerId", kArgPlayerId }, + { kCmdArgFlagged | kCmdTypeString, "Age", kArgStartUpAgeName }, + { kCmdArgFlagged | kCmdTypeString, "PvdFile", kArgPvdFile }, + { kCmdArgFlagged | kCmdTypeBool, "SkipIntroMovies", kArgSkipIntroMovies }, + { kCmdArgFlagged | kCmdTypeString, "Renderer", kArgRenderer }, +}; + +// +// For error logging +// +static plStatusLog* s_DebugLog = nullptr; +static void _DebugMessageProc(const char* msg) +{ +#if defined(HS_DEBUGGING) || !defined(PLASMA_EXTERNAL_RELEASE) + s_DebugLog->AddLine(plStatusLog::kRed, msg); +#endif // defined(HS_DEBUGGING) || !defined(PLASMA_EXTERNAL_RELEASE) +} + +static void _StatusMessageProc(const char* msg) +{ +#if defined(HS_DEBUGGING) || !defined(PLASMA_EXTERNAL_RELEASE) + s_DebugLog->AddLine(msg); +#endif // defined(HS_DEBUGGING) || !defined(PLASMA_EXTERNAL_RELEASE) +} + +template +static void DebugMsg(const char* format, _Args&&... args) +{ +#if defined(HS_DEBUGGING) || !defined(PLASMA_EXTERNAL_RELEASE) + s_DebugLog->AddLineF(plStatusLog::kYellow, format, std::forward<_Args>(args)...); +#endif // defined(HS_DEBUGGING) || !defined(PLASMA_EXTERNAL_RELEASE) +} + +static void DebugInit() +{ +#if defined(HS_DEBUGGING) || !defined(PLASMA_EXTERNAL_RELEASE) + plStatusLogMgr& mgr = plStatusLogMgr::GetInstance(); + s_DebugLog = mgr.CreateStatusLog(30, "plasmadbg.log", plStatusLog::kFilledBackground | + plStatusLog::kDeleteForMe | plStatusLog::kAlignToTop | plStatusLog::kTimestamp); + hsSetDebugMessageProc(_DebugMessageProc); + hsSetStatusMessageProc(_StatusMessageProc); +#endif // defined(HS_DEBUGGING) || !defined(PLASMA_EXTERNAL_RELEASE) +} + +// Stub all of these on non-Windows for now +void plClient::IResizeNativeDisplayDevice(int width, int height, bool windowed) +{ + hsStatusMessage(ST::format("Setting window size to {}×{}", width, height).c_str()); + + const uint32_t values[] = { uint32_t(width), uint32_t(height) }; + xcb_configure_window(gXConn, (xcb_window_t)(uintptr_t)fWindowHndl, + XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, + values); + + gWindowSize.fWidth = width; + gWindowSize.fHeight = height; +} + +void plClient::IChangeResolution(int width, int height) {} +void plClient::IUpdateProgressIndicator(plOperationProgress* progress) {} + +void plClient::ShowClientWindow() { + /* Map the window on the screen */ + xcb_map_window(gXConn, (xcb_window_t)(uintptr_t)fWindowHndl); + xcb_flush(gXConn); +} + +void plClient::FlashWindow() {} + + +PF_CONSOLE_LINK_ALL(); + +static hsSsize_t getpassword(char* password) +{ + struct termios old_t, new_t; + hsSsize_t nsize; + + if (tcgetattr(fileno(stdin), &old_t) != 0) + return -1; + + new_t = old_t; + new_t.c_lflag &= ~ECHO; + + if (tcsetattr(fileno(stdin), TCSAFLUSH, &new_t) != 0) + return -1; + + nsize = fscanf(stdin, "%s", password); + + (void)tcsetattr(fileno(stdin), TCSAFLUSH, &old_t); + fprintf(stdout, "\n"); + + return nsize; +} + +static void CalculateHash(const ST::string& username, const ST::string& password, ShaDigest& hash) +{ + // Hash username and password before sending over the 'net. + // -- Legacy compatibility: @gametap (and other usernames with domains in them) need + // to be hashed differently. + static const std::regex re_domain("[^@]+@([^.]+\\.)*([^.]+)\\.[^.]+"); + std::cmatch match; + std::regex_search(username.c_str(), match, re_domain); + if (match.empty() || ST::string(match[2].str()).compare_i("gametap") == 0) { + // Plain Usernames... + plSHA1Checksum shasum(password.size(), reinterpret_cast(password.c_str())); + uint32_t* dest = reinterpret_cast(hash); + const uint32_t* from = reinterpret_cast(shasum.GetValue()); + + dest[0] = hsToBE32(from[0]); + dest[1] = hsToBE32(from[1]); + dest[2] = hsToBE32(from[2]); + dest[3] = hsToBE32(from[3]); + dest[4] = hsToBE32(from[4]); + } + else { + // Domain-based Usernames... + CryptHashPassword(username, password, hash); + } +} + +static size_t CurlCallback(void *buffer, size_t size, size_t nmemb, void *param) +{ + static char status[256]; + + strncpy(status, (const char *)buffer, std::min(size * nmemb, 256)); + status[255] = 0; + + fprintf(stdout, "%s\n\n", status); + statusFlag.Signal(); + + return size * nmemb; +} + +static bool ConsoleLoginScreen() +{ + std::thread statusThread = std::thread([]() { + ST::string statusUrl = GetServerStatusUrl(); + if (statusUrl.empty()) { + statusFlag.Signal(); + return; + } + + CURL* hCurl = curl_easy_init(); + + // For reporting errors + char curlError[CURL_ERROR_SIZE]; + curl_easy_setopt(hCurl, CURLOPT_ERRORBUFFER, curlError); + curl_easy_setopt(hCurl, CURLOPT_FOLLOWLOCATION, 1); + curl_easy_setopt(hCurl, CURLOPT_MAXREDIRS, 5); + curl_easy_setopt(hCurl, CURLOPT_URL, statusUrl.c_str()); + curl_easy_setopt(hCurl, CURLOPT_USERAGENT, "UruClient/1.0"); + curl_easy_setopt(hCurl, CURLOPT_WRITEFUNCTION, &CurlCallback); + + if (curl_easy_perform(hCurl) != 0) { + fprintf(stderr, "%s\n\n", curlError); + statusFlag.Signal(); + } + + curl_easy_cleanup(hCurl); + }); + + statusFlag.Wait(); + statusThread.join(); + + fprintf(stdout, "[Use Ctrl+D to cancel]\nUsername or Email: "); + fflush(stdout); + + char username[kMaxAccountNameLength]; + char password[kMaxPasswordLength]; + + if (fscanf(stdin, "%s", username) != 1) { + return false; + } + + fprintf(stdout, "Password: "); + fflush(stdout); + + getpassword(password); + + ShaDigest namePassHash; + CalculateHash(username, password, namePassHash); + + NetCommSetAccountUsernamePassword(username, namePassHash); + + char16_t platform[] = u"linux"; + NetCommSetAuthTokenAndOS(nullptr, platform); + + if (!NetCliAuthQueryConnected()) + NetCommConnect(); + + NetCommAuthenticate(nullptr); + + while (!NetCommIsLoginComplete()) { + NetCommUpdate(); + } + + ENetError result = NetCommGetAuthResult(); + + if (!IS_NET_SUCCESS(result)) { + ST::printf(stdout, "{}\n", NetErrorToString(result)); + return false; + } + + return true; +} + +static uint32_t ParseRendererArgument(const ST::string& requested) +{ + using namespace ST::literals; + + static std::unordered_set dx_args { + "directx"_st, "direct3d"_st, "dx"_st, "d3d"_st + }; + + static std::unordered_set gl_args { + "opengl"_st, "gl"_st + }; + + if (dx_args.find(requested) != dx_args.end()) + return hsG3DDeviceSelector::kDevTypeDirect3D; + + if (gl_args.find(requested) != gl_args.end()) + return hsG3DDeviceSelector::kDevTypeOpenGL; + + return hsG3DDeviceSelector::kDevTypeUnknown; +} + +static bool XInit(xcb_connection_t* connection) +{ + gWindowSize.Set(0, 0, 800, 600); + + /* Get the first screen */ + const xcb_setup_t* setup = xcb_get_setup(connection); + xcb_screen_iterator_t iter = xcb_setup_roots_iterator(setup); + xcb_screen_t* screen = iter.data; + + const uint32_t event_mask = XCB_EVENT_MASK_EXPOSURE + | XCB_EVENT_MASK_KEY_PRESS + | XCB_EVENT_MASK_KEY_RELEASE + | XCB_EVENT_MASK_POINTER_MOTION + | XCB_EVENT_MASK_BUTTON_PRESS + | XCB_EVENT_MASK_BUTTON_RELEASE + | XCB_EVENT_MASK_ENTER_WINDOW + | XCB_EVENT_MASK_LEAVE_WINDOW + | XCB_EVENT_MASK_STRUCTURE_NOTIFY; + + /* Create the window */ + xcb_window_t window = xcb_generate_id(connection); + xcb_create_window(connection, /* Connection */ + XCB_COPY_FROM_PARENT, /* depth (same as root)*/ + window, /* window Id */ + screen->root, /* parent window */ + /* x, y */ + gWindowSize.fX, gWindowSize.fY, + /* width, height */ + gWindowSize.fWidth, gWindowSize.fHeight, + 10, /* border_width */ + XCB_WINDOW_CLASS_INPUT_OUTPUT, /* class */ + screen->root_visual, /* visual */ + XCB_CW_EVENT_MASK, /* masks */ + &event_mask); /* masks */ + + const char* title = ST::format("{}", plProduct::LongName()).c_str(); + xcb_change_property(connection, + XCB_PROP_MODE_REPLACE, + window, + XCB_ATOM_WM_NAME, + XCB_ATOM_STRING, + 8, + strlen(title), + title); + + Display* display = XOpenDisplay(nullptr); + + gClient.SetClientWindow((hsWindowHndl)(uintptr_t)window); + gClient.SetClientDisplay((hsWindowHndl)display); + gClient.Init(); + return true; +} + +static void PumpMessageQueueProc() +{ + static const unsigned char KEYCODE_LINUX_TO_HID[256] = { + 0,41,30,31,32,33,34,35,36,37,38,39,45,46,42,43,20,26,8,21,23,28,24,12,18,19, + 47,48,158,224,4,22,7,9,10,11,13,14,15,51,52,53,225,49,29,27,6,25,5,17,16,54, + 55,56,229,85,226,44,57,58,59,60,61,62,63,64,65,66,67,83,71,95,96,97,86,92, + 93,94,87,89,90,91,98,99,0,0,100,68,69,0,0,0,0,0,0,0,88,228,84,154,230,0,74, + 82,75,80,79,77,81,78,73,76,0,0,0,0,0,103,0,72,0,0,0,0,0,227,231,0,0,0,0,0,0, + 0,0,0,0,0,0,118,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,104,105,106,107,108,109,110,111,112,113,114,115,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + }; + + xcb_generic_event_t* event; + while ((event = xcb_poll_for_event(gXConn))) { + switch (event->response_type & ~0x80) + { + case XCB_CONFIGURE_NOTIFY: // Window resize + { + xcb_configure_notify_event_t* cne = reinterpret_cast(event); + gWindowSize.Set(cne->x, cne->y, cne->width, cne->height); + } + break; + + case XCB_KEY_PRESS: // Keyboard key press + case XCB_KEY_RELEASE: // Keyboard key release + { + xcb_key_press_event_t* kbe = reinterpret_cast(event); + + bool down = (kbe->response_type & ~0x80) == XCB_KEY_PRESS; + + /* X11 offsets Linux keycodes by 8 */ + uint32_t keycode = kbe->detail - 8; + plKeyDef key = KEY_UNMAPPED; + if (keycode < 256) + key = (plKeyDef)KEYCODE_LINUX_TO_HID[keycode]; + + if (key == KEY_Q) { // Quit when Q is hit + gClient->SetDone(true); + break; + } + + if (down) + gClient->SetQuitIntro(true); + + gClient->GetInputManager()->HandleKeyEvent(key, down, false); + } + break; + + case XCB_MOTION_NOTIFY: // Mouse Movement + { + xcb_motion_notify_event_t* me = reinterpret_cast(event); + + plIMouseXEventMsg* pXMsg = new plIMouseXEventMsg; + plIMouseYEventMsg* pYMsg = new plIMouseYEventMsg; + + pXMsg->fWx = me->event_x; + pXMsg->fX = (float)me->event_x / (float)gWindowSize.fWidth; + + pYMsg->fWy = me->event_y; + pYMsg->fY = (float)me->event_y / (float)gWindowSize.fHeight; + + gClient->GetInputManager()->MsgReceive(pXMsg); + gClient->GetInputManager()->MsgReceive(pYMsg); + + delete(pXMsg); + delete(pYMsg); + } + break; + + case XCB_BUTTON_PRESS: + { + xcb_button_press_event_t* bpe = reinterpret_cast(event); + + /* Handle scroll wheel */ + if (bpe->detail == XCB_BUTTON_INDEX_4 || bpe->detail == XCB_BUTTON_INDEX_5) + { + /* + case XCB_BUTTON_INDEX_4: + pMsg->fButton |= kWheelPos; + pMsg->SetWheelDelta(120.0f); + break; + case XCB_BUTTON_INDEX_5: + pMsg->fButton |= kWheelNeg; + pMsg->SetWheelDelta(-120.0f); + break; + */ + break; + } + + plIMouseXEventMsg* pXMsg = new plIMouseXEventMsg; + plIMouseYEventMsg* pYMsg = new plIMouseYEventMsg; + plIMouseBEventMsg* pBMsg = new plIMouseBEventMsg; + + pXMsg->fWx = bpe->event_x; + pXMsg->fX = (float)bpe->event_x / (float)gWindowSize.fWidth; + + pYMsg->fWy = bpe->event_y; + pYMsg->fY = (float)bpe->event_y / (float)gWindowSize.fHeight; + + switch (bpe->detail) { + case XCB_BUTTON_INDEX_1: + pBMsg->fButton |= kLeftButtonDown; + break; + case XCB_BUTTON_INDEX_2: + pBMsg->fButton |= kMiddleButtonDown; + break; + case XCB_BUTTON_INDEX_3: + pBMsg->fButton |= kRightButtonDown; + break; + default: + break; + } + + gClient->GetInputManager()->MsgReceive(pXMsg); + gClient->GetInputManager()->MsgReceive(pYMsg); + gClient->GetInputManager()->MsgReceive(pBMsg); + + delete(pXMsg); + delete(pYMsg); + delete(pBMsg); + } + break; + + case XCB_BUTTON_RELEASE: + { + xcb_button_release_event_t* bre = reinterpret_cast(event); + + plIMouseXEventMsg* pXMsg = new plIMouseXEventMsg; + plIMouseYEventMsg* pYMsg = new plIMouseYEventMsg; + plIMouseBEventMsg* pBMsg = new plIMouseBEventMsg; + + pXMsg->fWx = bre->event_x; + pXMsg->fX = (float)bre->event_x / (float)gWindowSize.fWidth; + + pYMsg->fWy = bre->event_y; + pYMsg->fY = (float)bre->event_y / (float)gWindowSize.fHeight; + + switch (bre->detail) { + case XCB_BUTTON_INDEX_1: + pBMsg->fButton |= kLeftButtonUp; + break; + case XCB_BUTTON_INDEX_2: + pBMsg->fButton |= kMiddleButtonUp; + break; + case XCB_BUTTON_INDEX_3: + pBMsg->fButton |= kRightButtonUp; + break; + default: + break; + } + + gClient->GetInputManager()->MsgReceive(pXMsg); + gClient->GetInputManager()->MsgReceive(pYMsg); + gClient->GetInputManager()->MsgReceive(pBMsg); + + delete(pXMsg); + delete(pYMsg); + delete(pBMsg); + } + break; + + default: + break; + } + + free(event); + } +} + +// Stub main function so it compiles on non-Windows +int main(int argc, const char** argv) +{ + PF_CONSOLE_INIT_ALL(); + + std::vector args; + args.reserve(argc); + for (size_t i = 0; i < argc; i++) { + args.push_back(ST::string::from_utf8(argv[i])); + } + + plCmdParser cmdParser(s_cmdLineArgs, std::size(s_cmdLineArgs)); + cmdParser.Parse(args); + + bool doIntroDialogs = true; +#ifndef PLASMA_EXTERNAL_RELEASE + if (cmdParser.IsSpecified(kArgSkipLoginDialog)) + doIntroDialogs = false; + if (cmdParser.IsSpecified(kArgLocalData)) + { + gDataServerLocal = true; + gPythonLocal = true; + } + if (cmdParser.IsSpecified(kArgLocalPython)) + gPythonLocal = true; + if (cmdParser.IsSpecified(kArgLocalSDL)) + gSDLLocal = true; + if (cmdParser.IsSpecified(kArgPlayerId)) + NetCommSetIniPlayerId(cmdParser.GetInt(kArgPlayerId)); + if (cmdParser.IsSpecified(kArgStartUpAgeName)) + NetCommSetIniStartUpAge(cmdParser.GetString(kArgStartUpAgeName)); + if (cmdParser.IsSpecified(kArgPvdFile)) + plPXSimulation::SetDefaultDebuggerEndpoint(cmdParser.GetString(kArgPvdFile)); + if (cmdParser.IsSpecified(kArgRenderer)) + gClient.SetRequestedRenderingBackend(ParseRendererArgument(cmdParser.GetString(kArgRenderer))); +#endif + + plFileName serverIni = "server.ini"; + if (cmdParser.IsSpecified(kArgServerIni)) + serverIni = cmdParser.GetString(kArgServerIni); + + // Load an optional general.ini + plFileName gipath = plFileName::Join(plFileSystem::GetInitPath(), "general.ini"); + FILE *generalini = plFileSystem::Open(gipath, "rb"); + if (generalini) + { + fclose(generalini); + pfConsoleEngine tempConsole; + tempConsole.ExecuteFile(gipath); + } + + // Set up to log errors by using hsDebugMessage + DebugInit(); + DebugMsg("Plasma 2.0.{}.{} - {}", PLASMA2_MAJOR_VERSION, PLASMA2_MINOR_VERSION, plProduct::ProductString()); + + FILE *serverIniFile = plFileSystem::Open(serverIni, "rb"); + if (serverIniFile) + { + fclose(serverIniFile); + pfConsoleEngine tempConsole; + tempConsole.ExecuteFile(serverIni); + } + else + { + hsMessageBox("No server.ini file found. Please check your URU installation.", "Error", hsMessageBoxNormal); + return 1; + } + + if (!XInitThreads()) { + hsMessageBox("Failed to initialize plClient", "Error", hsMessageBoxNormal); + return 1; + } + + /* Open the connection to the X server */ + gXConn = xcb_connect(nullptr, nullptr); + + if (!XInit(gXConn)) { + hsMessageBox("Failed to initialize plClient", "Error", hsMessageBoxNormal); + return 1; + } + + NetCliAuthAutoReconnectEnable(false); + NetCommStartup(); + + curl_global_init(CURL_GLOBAL_ALL); + + // Login stuff + if (!ConsoleLoginScreen()) { + gClient.ShutdownStart(); + gClient.ShutdownEnd(); + NetCommShutdown(); + + xcb_disconnect(gXConn); + + return 0; + } + + curl_global_cleanup(); + + NetCliAuthAutoReconnectEnable(true); + + // We should quite frankly be done initing the client by now. But, if not, spawn the good old + // "Starting URU, please wait..." dialog (not so yay) + if (!gClient.IsInited()) { + //HWND splashDialog = ::CreateDialog(hInst, MAKEINTRESOURCE(IDD_LOADING), nullptr, SplashDialogProc); + gClient.Wait(); + //::DestroyWindow(splashDialog); + } + + // Main loop + if (gClient && !gClient->GetDone()) { + // Must be done here due to the plClient* dereference. + if (cmdParser.IsSpecified(kArgSkipIntroMovies)) + gClient->SetFlag(plClient::kFlagSkipIntroMovies); + + gClient->SetMessagePumpProc(PumpMessageQueueProc); + gClient.Start(); + + do { + gClient->MainLoop(); + if (gClient->GetDone()) { + gClient.ShutdownStart(); + break; + } + + PumpMessageQueueProc(); + } while (true); + } + + gClient.ShutdownEnd(); + NetCommShutdown(); + + xcb_disconnect(gXConn); + + return 0; +} From f07e759309e5601f2b86f36e68874978ca8bb284 Mon Sep 17 00:00:00 2001 From: Darryl Pogue Date: Fri, 30 Dec 2022 16:05:46 -0800 Subject: [PATCH 3/7] Try to hook up X11 key bindings There is something weird with the text input stuff where things like Spacebar stop working for input controls. I've commented out the key-to-character stuff for now because it can make navigating the game difficult when random action keys get disabled. --- Sources/Plasma/Apps/plClient/CMakeLists.txt | 1 + Sources/Plasma/Apps/plClient/linux/main.cpp | 16 +++++++++++++++- .../Plasma/NucleusLib/pnInputCore/plInputMap.cpp | 2 +- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/Sources/Plasma/Apps/plClient/CMakeLists.txt b/Sources/Plasma/Apps/plClient/CMakeLists.txt index 16c53d9572..91d4c14a1c 100644 --- a/Sources/Plasma/Apps/plClient/CMakeLists.txt +++ b/Sources/Plasma/Apps/plClient/CMakeLists.txt @@ -256,6 +256,7 @@ if(UNIX AND NOT APPLE) PRIVATE xcb xcb-xfixes + xcb-keysyms X11 ) endif() diff --git a/Sources/Plasma/Apps/plClient/linux/main.cpp b/Sources/Plasma/Apps/plClient/linux/main.cpp index 7d1f8baffa..5fedaf966b 100644 --- a/Sources/Plasma/Apps/plClient/linux/main.cpp +++ b/Sources/Plasma/Apps/plClient/linux/main.cpp @@ -49,6 +49,7 @@ You can contact Cyan Worlds, Inc. by email legal@cyan.com #include #include #include +#include #include #include #include @@ -84,6 +85,7 @@ extern bool gSDLLocal; static plClientLoader gClient; static xcb_connection_t* gXConn; +static xcb_key_symbols_t* keysyms; static pcSmallRect gWindowSize; static hsSemaphore statusFlag; @@ -338,6 +340,9 @@ static bool XInit(xcb_connection_t* connection) { gWindowSize.Set(0, 0, 800, 600); + /* Get the X11 key mappings */ + keysyms = xcb_key_symbols_alloc(connection); + /* Get the first screen */ const xcb_setup_t* setup = xcb_get_setup(connection); xcb_screen_iterator_t iter = xcb_setup_roots_iterator(setup); @@ -433,7 +438,15 @@ static void PumpMessageQueueProc() if (down) gClient->SetQuitIntro(true); - gClient->GetInputManager()->HandleKeyEvent(key, down, false); + wchar_t c = 0; + xcb_keysym_t sym = xcb_key_press_lookup_keysym(keysyms, kbe, (kbe->state & XCB_MOD_MASK_SHIFT)); + + gClient->GetInputManager()->HandleKeyEvent(key, down, false, c); + + if (down && !xcb_is_cursor_key(sym) && !xcb_is_modifier_key(sym) && !xcb_is_function_key(sym) && !std::iscntrl((wchar_t)sym)) { + c = wchar_t(sym); + gClient->GetInputManager()->HandleKeyEvent(key, down, false, c); + } } break; @@ -502,6 +515,7 @@ static void PumpMessageQueueProc() break; } + gClient->SetQuitIntro(true); gClient->GetInputManager()->MsgReceive(pXMsg); gClient->GetInputManager()->MsgReceive(pYMsg); gClient->GetInputManager()->MsgReceive(pBMsg); diff --git a/Sources/Plasma/NucleusLib/pnInputCore/plInputMap.cpp b/Sources/Plasma/NucleusLib/pnInputCore/plInputMap.cpp index 338b6a34a1..2e230a339a 100644 --- a/Sources/Plasma/NucleusLib/pnInputCore/plInputMap.cpp +++ b/Sources/Plasma/NucleusLib/pnInputCore/plInputMap.cpp @@ -690,7 +690,7 @@ void plKeyMap::ICheckAndBindDupe(plKeyDef origKey, plKeyDef dupeKey) } const std::map plKeyMap::fKeyConversionEnglish = -{ +{ { KEY_A, ST_LITERAL("A")}, { KEY_B, ST_LITERAL("B")}, { KEY_C, ST_LITERAL("C")}, From f518f7affbb19493200592b16dd64f7e8830fa7c Mon Sep 17 00:00:00 2001 From: Darryl Pogue Date: Sun, 12 Mar 2023 21:30:21 -0700 Subject: [PATCH 4/7] Detect XFixes to hide the cursor on Linux --- Sources/Plasma/Apps/plClient/linux/main.cpp | 38 +++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/Sources/Plasma/Apps/plClient/linux/main.cpp b/Sources/Plasma/Apps/plClient/linux/main.cpp index 5fedaf966b..fcf13a4038 100644 --- a/Sources/Plasma/Apps/plClient/linux/main.cpp +++ b/Sources/Plasma/Apps/plClient/linux/main.cpp @@ -87,6 +87,7 @@ static plClientLoader gClient; static xcb_connection_t* gXConn; static xcb_key_symbols_t* keysyms; static pcSmallRect gWindowSize; +static bool gHasXFixes = false; static hsSemaphore statusFlag; enum @@ -348,6 +349,21 @@ static bool XInit(xcb_connection_t* connection) xcb_screen_iterator_t iter = xcb_setup_roots_iterator(setup); xcb_screen_t* screen = iter.data; + /* Check for XFixes support for hiding the cursor */ + const xcb_query_extension_reply_t* qe_reply = xcb_get_extension_data(gXConn, &xcb_xfixes_id); + if (qe_reply && qe_reply->present) + { + /* We *must* negotiate the XFixes version with the server */ + xcb_xfixes_query_version_cookie_t qv_cookie = xcb_xfixes_query_version(gXConn, XCB_XFIXES_MAJOR_VERSION, XCB_XFIXES_MINOR_VERSION); + xcb_xfixes_query_version_reply_t* qv_reply = xcb_xfixes_query_version_reply(gXConn, qv_cookie, nullptr); + +//#ifndef HS_DEBUGGING // Don't hide the cursor when debugging + gHasXFixes = qv_reply->major_version >= 4; +//#endif + + free(qv_reply); + } + const uint32_t event_mask = XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_KEY_RELEASE @@ -564,6 +580,28 @@ static void PumpMessageQueueProc() } break; + case XCB_ENTER_NOTIFY: // Mouse over windows + { + if (gHasXFixes) + { + xcb_enter_notify_event_t* ene = reinterpret_cast(event); + xcb_xfixes_hide_cursor(gXConn, ene->root); + xcb_flush(gXConn); + } + } + break; + + case XCB_LEAVE_NOTIFY: // Mouse off windows + { + if (gHasXFixes) + { + xcb_leave_notify_event_t* lne = reinterpret_cast(event); + xcb_xfixes_show_cursor(gXConn, lne->root); + xcb_flush(gXConn); + } + } + break; + default: break; } From 46643f7ea4e3ab12a91a9c7a37e2c7232b0c321e Mon Sep 17 00:00:00 2001 From: Darryl Pogue Date: Sun, 19 Feb 2023 00:26:46 -0800 Subject: [PATCH 5/7] Hook up password storing for Linux client --- Sources/Plasma/Apps/plClient/linux/main.cpp | 35 ++++++++++++++++++--- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/Sources/Plasma/Apps/plClient/linux/main.cpp b/Sources/Plasma/Apps/plClient/linux/main.cpp index fcf13a4038..0e2a3054cc 100644 --- a/Sources/Plasma/Apps/plClient/linux/main.cpp +++ b/Sources/Plasma/Apps/plClient/linux/main.cpp @@ -78,6 +78,7 @@ You can contact Cyan Worlds, Inc. by email legal@cyan.com #include "plStatusLog/plStatusLog.h" #include "pfConsoleCore/pfConsoleEngine.h" +#include "pfPasswordStore/pfPasswordStore.h" extern bool gDataServerLocal; extern bool gPythonLocal; @@ -278,16 +279,42 @@ static bool ConsoleLoginScreen() fflush(stdout); char username[kMaxAccountNameLength]; - char password[kMaxPasswordLength]; if (fscanf(stdin, "%s", username) != 1) { return false; } - fprintf(stdout, "Password: "); - fflush(stdout); + pfPasswordStore* store = pfPasswordStore::Instance(); + ST::string password = store->GetPassword(username); + + if (!password.empty()) { + fprintf(stdout, "Use saved password? [y/n] "); + fflush(stdout); + char c; + fscanf(stdin, " %c", &c); + if (c == 'n' || c == 'N') { + password = ST_LITERAL(""); + } + fprintf(stdout, "\n"); + } - getpassword(password); + if (password.empty()) { + fprintf(stdout, "Password: "); + fflush(stdout); + + char tmpPassword[kMaxPasswordLength]; + getpassword(tmpPassword); + password = tmpPassword; + + fprintf(stdout, "Save password? [y/n] "); + fflush(stdout); + char c; + fscanf(stdin, " %c", &c); + if (c == 'y' || c == 'Y') { + store->SetPassword(username, password); + } + fprintf(stdout, "\n"); + } ShaDigest namePassHash; CalculateHash(username, password, namePassHash); From dd6bc6445b865e614fa69860554fe964326e9a13 Mon Sep 17 00:00:00 2001 From: Darryl Pogue Date: Sun, 19 Feb 2023 12:39:36 -0800 Subject: [PATCH 6/7] Provide a username CLI flag for fast login --- Sources/Plasma/Apps/plClient/linux/main.cpp | 28 +++++++++++++-------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/Sources/Plasma/Apps/plClient/linux/main.cpp b/Sources/Plasma/Apps/plClient/linux/main.cpp index 0e2a3054cc..552bfc5ef4 100644 --- a/Sources/Plasma/Apps/plClient/linux/main.cpp +++ b/Sources/Plasma/Apps/plClient/linux/main.cpp @@ -102,7 +102,8 @@ enum kArgStartUpAgeName, kArgPvdFile, kArgSkipIntroMovies, - kArgRenderer + kArgRenderer, + kArgUsername }; static const plCmdArgDef s_cmdLineArgs[] = { @@ -116,6 +117,7 @@ static const plCmdArgDef s_cmdLineArgs[] = { { kCmdArgFlagged | kCmdTypeString, "PvdFile", kArgPvdFile }, { kCmdArgFlagged | kCmdTypeBool, "SkipIntroMovies", kArgSkipIntroMovies }, { kCmdArgFlagged | kCmdTypeString, "Renderer", kArgRenderer }, + { kCmdArgFlagged | kCmdTypeString, "Username", kArgUsername }, }; // @@ -244,7 +246,7 @@ static size_t CurlCallback(void *buffer, size_t size, size_t nmemb, void *param) return size * nmemb; } -static bool ConsoleLoginScreen() +static bool ConsoleLoginScreen(const ST::string& cliUsername) { std::thread statusThread = std::thread([]() { ST::string statusUrl = GetServerStatusUrl(); @@ -275,19 +277,22 @@ static bool ConsoleLoginScreen() statusFlag.Wait(); statusThread.join(); - fprintf(stdout, "[Use Ctrl+D to cancel]\nUsername or Email: "); - fflush(stdout); - - char username[kMaxAccountNameLength]; + ST::string username = cliUsername; + if (cliUsername.empty()) { + fprintf(stdout, "[Use Ctrl+D to cancel]\nUsername or Email: "); + fflush(stdout); - if (fscanf(stdin, "%s", username) != 1) { - return false; + char tmpUsername[kMaxAccountNameLength]; + if (fscanf(stdin, "%s", tmpUsername) != 1) { + return false; + } + username = tmpUsername; } pfPasswordStore* store = pfPasswordStore::Instance(); ST::string password = store->GetPassword(username); - if (!password.empty()) { + if (!password.empty() && cliUsername.empty()) { fprintf(stdout, "Use saved password? [y/n] "); fflush(stdout); char c; @@ -652,6 +657,7 @@ int main(int argc, const char** argv) cmdParser.Parse(args); bool doIntroDialogs = true; + ST::string cliUsername; #ifndef PLASMA_EXTERNAL_RELEASE if (cmdParser.IsSpecified(kArgSkipLoginDialog)) doIntroDialogs = false; @@ -672,6 +678,8 @@ int main(int argc, const char** argv) plPXSimulation::SetDefaultDebuggerEndpoint(cmdParser.GetString(kArgPvdFile)); if (cmdParser.IsSpecified(kArgRenderer)) gClient.SetRequestedRenderingBackend(ParseRendererArgument(cmdParser.GetString(kArgRenderer))); + if (cmdParser.IsSpecified(kArgUsername)) + cliUsername = cmdParser.GetString(kArgUsername); #endif plFileName serverIni = "server.ini"; @@ -724,7 +732,7 @@ int main(int argc, const char** argv) curl_global_init(CURL_GLOBAL_ALL); // Login stuff - if (!ConsoleLoginScreen()) { + if (!ConsoleLoginScreen(cliUsername)) { gClient.ShutdownStart(); gClient.ShutdownEnd(); NetCommShutdown(); From 9644a2afb891f6034a97aaae6f1c9e0c2614556d Mon Sep 17 00:00:00 2001 From: Darryl Pogue Date: Fri, 16 Jun 2023 21:46:31 -0700 Subject: [PATCH 7/7] Fix X11 defines breaking the build --- Sources/Plasma/Apps/plClient/linux/main.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Sources/Plasma/Apps/plClient/linux/main.cpp b/Sources/Plasma/Apps/plClient/linux/main.cpp index 552bfc5ef4..0bf2501724 100644 --- a/Sources/Plasma/Apps/plClient/linux/main.cpp +++ b/Sources/Plasma/Apps/plClient/linux/main.cpp @@ -54,6 +54,9 @@ You can contact Cyan Worlds, Inc. by email legal@cyan.com #include #include +// Undefine the problematic X11 stuff +#undef Status + #include "HeadSpin.h" #include "plCmdParser.h" #include "plPipeline.h"