From 3bf374d29899cdb07b755ebb99f73a3ded69ba7f Mon Sep 17 00:00:00 2001 From: s1lent Date: Tue, 14 Feb 2017 00:45:46 +0700 Subject: [PATCH] Add project Dedicated (hlds launcher without VGUI) --- .gitignore | 2 +- publish.gradle | 29 +- rehlds/common/BaseSystemModule.cpp | 140 +++++ rehlds/common/BaseSystemModule.h | 55 ++ rehlds/common/IAdminServer.h | 33 ++ rehlds/common/IBaseSystem.h | 59 +++ rehlds/common/IObjectContainer.h | 25 + rehlds/common/ISystemModule.h | 35 ++ rehlds/common/IVGuiModule.h | 54 ++ rehlds/common/ObjectDictionary.cpp | 483 ++++++++++++++++++ rehlds/common/ObjectDictionary.h | 73 +++ rehlds/common/ObjectList.cpp | 232 +++++++++ rehlds/common/ObjectList.h | 43 ++ rehlds/common/SteamAppStartUp.cpp | 181 +++++++ rehlds/common/SteamAppStartUp.h | 15 + rehlds/common/TextConsoleUnix.cpp | 294 +++++++++++ rehlds/common/TextConsoleUnix.h | 37 ++ rehlds/common/TextConsoleWin32.cpp | 250 +++++++++ rehlds/common/TextConsoleWin32.h | 39 ++ rehlds/common/TokenLine.cpp | 131 +++++ rehlds/common/TokenLine.h | 29 ++ rehlds/common/netapi.cpp | 208 ++++++++ rehlds/common/netapi.h | 25 + rehlds/common/textconsole.cpp | 344 +++++++++++++ rehlds/common/textconsole.h | 71 +++ rehlds/dedicated/build.gradle | 137 +++++ rehlds/dedicated/msvc/PostBuild.bat | 39 ++ rehlds/dedicated/msvc/dedicated.rc | Bin 0 -> 3220 bytes rehlds/dedicated/msvc/dedicated.sln | 22 + rehlds/dedicated/msvc/dedicated.vcxproj | 178 +++++++ .../dedicated/msvc/dedicated.vcxproj.filters | 71 +++ rehlds/dedicated/msvc/icon.ico | Bin 0 -> 117045 bytes rehlds/dedicated/msvc/resource.h | Bin 0 -> 902 bytes rehlds/dedicated/src/commandline.cpp | 354 +++++++++++++ rehlds/dedicated/src/conproc.cpp | 312 +++++++++++ rehlds/dedicated/src/conproc.h | 17 + rehlds/dedicated/src/dbg.cpp | 171 +++++++ rehlds/dedicated/src/dbg.h | 13 + rehlds/dedicated/src/dedicated.h | 13 + rehlds/dedicated/src/dedicated_exports.cpp | 13 + rehlds/dedicated/src/icommandline.h | 25 + rehlds/dedicated/src/isys.h | 31 ++ rehlds/dedicated/src/precompiled.cpp | 1 + rehlds/dedicated/src/precompiled.h | 21 + rehlds/dedicated/src/public_amalgamation.cpp | 11 + rehlds/dedicated/src/sys_ded.cpp | 201 ++++++++ rehlds/dedicated/src/sys_ded.h | 33 ++ rehlds/dedicated/src/sys_linux.cpp | 334 ++++++++++++ rehlds/dedicated/src/sys_window.cpp | 266 ++++++++++ rehlds/dedicated/src/vgui/vguihelpers.cpp | 157 ++++++ rehlds/dedicated/src/vgui/vguihelpers.h | 10 + rehlds/engine/filesystem.cpp | 2 +- rehlds/engine/filesystem_.h | 6 - rehlds/public/FileSystem.h | 15 +- rehlds/public/engine_hlds_api.h | 6 + rehlds/public/steam/steam_api.h | 53 +- rehlds/public/vgui/VGUI.h | 31 ++ settings.gradle | 1 + 58 files changed, 5390 insertions(+), 41 deletions(-) create mode 100644 rehlds/common/BaseSystemModule.cpp create mode 100644 rehlds/common/BaseSystemModule.h create mode 100644 rehlds/common/IAdminServer.h create mode 100644 rehlds/common/IBaseSystem.h create mode 100644 rehlds/common/IObjectContainer.h create mode 100644 rehlds/common/ISystemModule.h create mode 100644 rehlds/common/IVGuiModule.h create mode 100644 rehlds/common/ObjectDictionary.cpp create mode 100644 rehlds/common/ObjectDictionary.h create mode 100644 rehlds/common/ObjectList.cpp create mode 100644 rehlds/common/ObjectList.h create mode 100644 rehlds/common/SteamAppStartUp.cpp create mode 100644 rehlds/common/SteamAppStartUp.h create mode 100644 rehlds/common/TextConsoleUnix.cpp create mode 100644 rehlds/common/TextConsoleUnix.h create mode 100644 rehlds/common/TextConsoleWin32.cpp create mode 100644 rehlds/common/TextConsoleWin32.h create mode 100644 rehlds/common/TokenLine.cpp create mode 100644 rehlds/common/TokenLine.h create mode 100644 rehlds/common/netapi.cpp create mode 100644 rehlds/common/netapi.h create mode 100644 rehlds/common/textconsole.cpp create mode 100644 rehlds/common/textconsole.h create mode 100644 rehlds/dedicated/build.gradle create mode 100644 rehlds/dedicated/msvc/PostBuild.bat create mode 100644 rehlds/dedicated/msvc/dedicated.rc create mode 100644 rehlds/dedicated/msvc/dedicated.sln create mode 100644 rehlds/dedicated/msvc/dedicated.vcxproj create mode 100644 rehlds/dedicated/msvc/dedicated.vcxproj.filters create mode 100644 rehlds/dedicated/msvc/icon.ico create mode 100644 rehlds/dedicated/msvc/resource.h create mode 100644 rehlds/dedicated/src/commandline.cpp create mode 100644 rehlds/dedicated/src/conproc.cpp create mode 100644 rehlds/dedicated/src/conproc.h create mode 100644 rehlds/dedicated/src/dbg.cpp create mode 100644 rehlds/dedicated/src/dbg.h create mode 100644 rehlds/dedicated/src/dedicated.h create mode 100644 rehlds/dedicated/src/dedicated_exports.cpp create mode 100644 rehlds/dedicated/src/icommandline.h create mode 100644 rehlds/dedicated/src/isys.h create mode 100644 rehlds/dedicated/src/precompiled.cpp create mode 100644 rehlds/dedicated/src/precompiled.h create mode 100644 rehlds/dedicated/src/public_amalgamation.cpp create mode 100644 rehlds/dedicated/src/sys_ded.cpp create mode 100644 rehlds/dedicated/src/sys_ded.h create mode 100644 rehlds/dedicated/src/sys_linux.cpp create mode 100644 rehlds/dedicated/src/sys_window.cpp create mode 100644 rehlds/dedicated/src/vgui/vguihelpers.cpp create mode 100644 rehlds/dedicated/src/vgui/vguihelpers.h create mode 100644 rehlds/public/vgui/VGUI.h diff --git a/.gitignore b/.gitignore index 30fa6ef..63383d8 100644 --- a/.gitignore +++ b/.gitignore @@ -15,11 +15,11 @@ **/msvc/*.suo **/msvc/*.db **/msvc/*.opendb +**/msvc/PublishPath*.txt **/msvc/ipch **/msvc/.vs rehlds/version/appversion.h -rehlds/msvc/PublishPath*.txt rehlds/_rehldsTestImg rehlds/_dev publish diff --git a/publish.gradle b/publish.gradle index ee6d0bd..da624f1 100644 --- a/publish.gradle +++ b/publish.gradle @@ -2,11 +2,25 @@ import org.doomedsociety.gradlecpp.GradleCppUtils import org.apache.commons.io.FilenameUtils void _copyFileToDir(String from, String to) { + if (!project.file(from).exists()) { + println 'WARNING: Could not find: ' + from; + return; + } + + if (!project.file(to).exists()) { + project.file(to).mkdirs(); + } + def dst = new File(project.file(to), FilenameUtils.getName(from)) GradleCppUtils.copyFile(project.file(from), dst, false) } void _copyFile(String from, String to) { + if (!project.file(from).exists()) { + println 'WARNING: Could not find: ' + from; + return; + } + GradleCppUtils.copyFile(project.file(from), project.file(to), false) } @@ -22,19 +36,21 @@ task publishPrepareFiles { pubRootDir.mkdirs() - //bugfixed binaries - project.file('publish/publishRoot/bin/bugfixed').mkdirs() + // bugfixed binaries _copyFileToDir('publish/releaseRehldsFixes/swds.dll', 'publish/publishRoot/bin/bugfixed/') _copyFileToDir('publish/releaseRehldsFixes/swds.pdb', 'publish/publishRoot/bin/bugfixed/') _copyFile('publish/releaseRehldsFixes/libengine_i486.so', 'publish/publishRoot/bin/bugfixed/engine_i486.so') - //pure binaries - project.file('publish/publishRoot/bin/pure').mkdirs() + // pure binaries _copyFileToDir('publish/releaseRehldsNofixes/swds.dll', 'publish/publishRoot/bin/pure/') _copyFileToDir('publish/releaseRehldsNofixes/swds.pdb', 'publish/publishRoot/bin/pure/') _copyFile('publish/releaseRehldsNofixes/libengine_i486.so', 'publish/publishRoot/bin/pure/engine_i486.so') - //hlsdk + // dedicated binaries + _copyFile('publish/hlds.exe', 'publish/publishRoot/bin/hlds.exe') + _copyFile('publish/hlds_linux', 'publish/publishRoot/bin/hlds_linux') + + // hlsdk project.file('publish/publishRoot/hlsdk').mkdirs() copy { from 'rehlds/common' @@ -58,8 +74,7 @@ task publishPrepareFiles { into 'publish/publishRoot/hlsdk/engine' } - //flightrecorder - + // flightrecorder def flightRecJarTask = project(':flightrec/decoder').tasks.getByName('uberjar') println flightRecJarTask println flightRecJarTask.class.name diff --git a/rehlds/common/BaseSystemModule.cpp b/rehlds/common/BaseSystemModule.cpp new file mode 100644 index 0000000..2082d0b --- /dev/null +++ b/rehlds/common/BaseSystemModule.cpp @@ -0,0 +1,140 @@ +#include "BaseSystemModule.h" +#include + +BaseSystemModule::BaseSystemModule() +{ + m_State = MODULE_INACTIVE; +} + +BaseSystemModule::~BaseSystemModule() +{ + ; +} + +char *BaseSystemModule::GetName() +{ + return m_Name; +} + +char *BaseSystemModule::GetType() +{ + return "GenericModule"; +} + +char *BaseSystemModule::GetStatusLine() +{ + return "No status available.\n"; +} + +void BaseSystemModule::ExecuteCommand(int commandID, char *commandLine) +{ + m_System->DPrintf("WARNING! Undeclared ExecuteCommand().\n"); +} + +extern int COM_BuildNumber(); + +int BaseSystemModule::GetVersion() +{ + return COM_BuildNumber(); +} + +int BaseSystemModule::GetSerial() +{ + return m_Serial; +} + +IBaseSystem *BaseSystemModule::GetSystem() +{ + return m_System; +} + +bool BaseSystemModule::Init(IBaseSystem *system, int serial, char *name) +{ + if (!system) + return false; + + m_State = MODULE_PENDING; + m_System = system; + m_Serial = serial; + m_SystemTime = 0; + + if (name) + { + strncpy(m_Name, name, sizeof m_Name - 1); + m_Name[sizeof m_Name - 1] = '\0'; + } + + return true; +} + +void BaseSystemModule::RunFrame(double time) +{ + m_SystemTime = time; +} + +void BaseSystemModule::ShutDown() +{ + if (m_State == MODULE_UNLOAD) + return; + + m_Listener.Clear(); + m_State = MODULE_UNLOAD; + + // TODO: Check me! + if (!m_System->RemoveModule(this)) + { + m_System->DPrintf("ERROR! BaseSystemModule::ShutDown: faild to remove module %s.\n", m_Name); + } +} + +void BaseSystemModule::RegisterListener(ISystemModule *module) +{ + ISystemModule *listener = (ISystemModule *)m_Listener.GetFirst(); + while (listener) + { + if (listener->GetSerial() == module->GetSerial()) + { + m_System->DPrintf("WARNING! BaseSystemModule::RegisterListener: module %s already added.\n", module->GetName()); + return; + } + + listener = (ISystemModule *)m_Listener.GetNext(); + } + + m_Listener.Add(module); +} + +void BaseSystemModule::RemoveListener(ISystemModule *module) +{ + ISystemModule *listener = (ISystemModule *)m_Listener.GetFirst(); + while (listener) + { + if (listener->GetSerial() == module->GetSerial()) + { + m_Listener.Remove(module); + return; + } + + listener = (ISystemModule *)m_Listener.GetNext(); + } +} + +void BaseSystemModule::FireSignal(unsigned int signal, void *data) +{ + ISystemModule *listener = (ISystemModule *)m_Listener.GetFirst(); + while (listener) + { + listener->ReceiveSignal(this, signal, data); + listener = (ISystemModule *)m_Listener.GetNext(); + } +} + +void BaseSystemModule::ReceiveSignal(ISystemModule *module, unsigned int signal, void *data) +{ + m_System->DPrintf("WARNING! Unhandled signal (%i) from module %s.\n", signal, module->GetName()); +} + +int BaseSystemModule::GetState() +{ + return m_State; +} diff --git a/rehlds/common/BaseSystemModule.h b/rehlds/common/BaseSystemModule.h new file mode 100644 index 0000000..dc76fa7 --- /dev/null +++ b/rehlds/common/BaseSystemModule.h @@ -0,0 +1,55 @@ +#ifndef BASESYSTEMMODULE_H +#define BASESYSTEMMODULE_H +#ifdef _WIN32 +#pragma once +#endif + +#include "ObjectList.h" +#include "IBaseSystem.h" + +#define SIGNAL_MODULE_CLOSE 8 // closing down module + +enum ModuleState +{ + MODULE_INACTIVE, + MODULE_PENDING, + MODULE_RUNNING, + MODULE_LOAD, + MODULE_UNLOAD +}; + +// C4250 - 'class1' : inherits 'BaseSystemModule::member' via dominance +#pragma warning(disable:4250) + +class BaseSystemModule: virtual public ISystemModule { +public: + BaseSystemModule(); + virtual ~BaseSystemModule(); + + virtual bool Init(IBaseSystem *system, int serial, char *name); + virtual void RunFrame(double time); + virtual void ReceiveSignal(ISystemModule *module, unsigned int signal, void *data); + virtual void ExecuteCommand(int commandID, char *commandLine); + virtual void RegisterListener(ISystemModule *module); + virtual void RemoveListener(ISystemModule *module); + virtual IBaseSystem *GetSystem(); + virtual int GetSerial(); + virtual char *GetStatusLine(); + virtual char *GetType(); + virtual char *GetName(); + virtual int GetState(); + virtual int GetVersion(); + virtual void ShutDown(); + virtual char *COM_GetBaseDir() { return ""; } + void FireSignal(unsigned int signal, void *data); + +protected: + IBaseSystem *m_System; + ObjectList m_Listener; + char m_Name[255]; + unsigned int m_State; + unsigned int m_Serial; + double m_SystemTime; +}; + +#endif // BASESYSTEMMODULE_H diff --git a/rehlds/common/IAdminServer.h b/rehlds/common/IAdminServer.h new file mode 100644 index 0000000..41a5d21 --- /dev/null +++ b/rehlds/common/IAdminServer.h @@ -0,0 +1,33 @@ +#ifndef IADMINSERVER_H +#define IADMINSERVER_H +#ifdef _WIN32 +#pragma once +#endif + +#include "interface.h" + +// handle to a game window +typedef unsigned int ManageServerUIHandle_t; +class IManageServer; + +// Purpose: Interface to server administration functions +class IAdminServer: public IBaseInterface +{ +public: + // opens a manage server dialog for a local server + virtual ManageServerUIHandle_t OpenManageServerDialog(const char *serverName, const char *gameDir) = 0; + + // opens a manage server dialog to a remote server + virtual ManageServerUIHandle_t OpenManageServerDialog(unsigned int gameIP, unsigned int gamePort, const char *password) = 0; + + // forces the game info dialog closed + virtual void CloseManageServerDialog(ManageServerUIHandle_t gameDialog) = 0; + + // Gets a handle to the interface + virtual IManageServer *GetManageServerInterface(ManageServerUIHandle_t handle) = 0; +}; + +#define ADMINSERVER_INTERFACE_VERSION "AdminServer002" + + +#endif // IAdminServer_H diff --git a/rehlds/common/IBaseSystem.h b/rehlds/common/IBaseSystem.h new file mode 100644 index 0000000..20239d1 --- /dev/null +++ b/rehlds/common/IBaseSystem.h @@ -0,0 +1,59 @@ +#ifndef IBASESYSTEM_H +#define IBASESYSTEM_H +#ifdef _WIN32 +#pragma once +#endif + +#include "ISystemModule.h" + +class Panel; +class ObjectList; +class IFileSystem; +class IVGuiModule; + +class IBaseSystem: virtual public ISystemModule { +public: + virtual ~IBaseSystem() {} + + virtual double GetTime() = 0; + virtual unsigned int GetTick() = 0; + virtual void SetFPS(float fps) = 0; + + virtual void Printf(char *fmt, ...) = 0; + virtual void DPrintf(char *fmt, ...) = 0; + + virtual void RedirectOutput(char *buffer, int maxSize) = 0; + + virtual IFileSystem *GetFileSystem() = 0; + virtual unsigned char *LoadFile(const char *name, int *length) = 0; + virtual void FreeFile(unsigned char *fileHandle) = 0; + + virtual void SetTitle(char *text) = 0; + virtual void SetStatusLine(char *text) = 0; + + virtual void ShowConsole(bool visible) = 0; + virtual void LogConsole(char *filename) = 0; + + virtual bool InitVGUI(IVGuiModule *module) = 0; + +#ifdef _WIN32 + virtual Panel *GetPanel() = 0; +#endif // _WIN32 + + virtual bool RegisterCommand(char *name, ISystemModule *module, int commandID) = 0; + virtual void GetCommandMatches(char *string, ObjectList *pMatchList) = 0; + virtual void ExecuteString(char *commands) = 0; + virtual void ExecuteFile(char *filename) = 0; + virtual void Errorf(char *fmt, ...) = 0; + + virtual char *CheckParam(char *param) = 0; + + virtual bool AddModule(ISystemModule *module, char *name) = 0; + virtual ISystemModule *GetModule(char *interfacename, char *library, char *instancename = nullptr) = 0; + virtual bool RemoveModule(ISystemModule *module) = 0; + + virtual void Stop() = 0; + virtual char *COM_GetBaseDir() = 0; +}; + +#endif // IBASESYSTEM_H diff --git a/rehlds/common/IObjectContainer.h b/rehlds/common/IObjectContainer.h new file mode 100644 index 0000000..d44ffa3 --- /dev/null +++ b/rehlds/common/IObjectContainer.h @@ -0,0 +1,25 @@ +#ifndef IOBJECTCONTAINER_H +#define IOBJECTCONTAINER_H +#ifdef _WIN32 +#pragma once +#endif + +class IObjectContainer { +public: + virtual ~IObjectContainer() {} + + virtual void Init() = 0; + + virtual bool Add(void *newObject) = 0; + virtual bool Remove(void *object) = 0; + virtual void Clear(bool freeElementsMemory) = 0; + + virtual void *GetFirst() = 0; + virtual void *GetNext() = 0; + + virtual int CountElements() = 0;; + virtual bool Contains(void *object) = 0; + virtual bool IsEmpty() = 0; +}; + +#endif // IOBJECTCONTAINER_H diff --git a/rehlds/common/ISystemModule.h b/rehlds/common/ISystemModule.h new file mode 100644 index 0000000..334ffea --- /dev/null +++ b/rehlds/common/ISystemModule.h @@ -0,0 +1,35 @@ +#ifndef ISYSTEMMODULE_H +#define ISYSTEMMODULE_H +#ifdef _WIN32 +#pragma once +#endif + +#include "interface.h" + +class IBaseSystem; +class ISystemModule; + +class ISystemModule: public IBaseInterface { +public: + virtual ~ISystemModule() {} + virtual bool Init(IBaseSystem *system, int serial, char *name) = 0; + + virtual void RunFrame(double time) = 0; + virtual void ReceiveSignal(ISystemModule *module, unsigned int signal, void *data) = 0; + virtual void ExecuteCommand(int commandID, char *commandLine) = 0; + virtual void RegisterListener(ISystemModule *module) = 0; + virtual void RemoveListener(ISystemModule *module) = 0; + + virtual IBaseSystem *GetSystem() = 0; + + virtual int GetSerial() = 0; + virtual char *GetStatusLine() = 0; + virtual char *GetType() = 0; + virtual char *GetName() = 0; + + virtual int GetState() = 0; + virtual int GetVersion() = 0; + virtual void ShutDown() = 0; +}; + +#endif // ISYSTEMMODULE_H diff --git a/rehlds/common/IVGuiModule.h b/rehlds/common/IVGuiModule.h new file mode 100644 index 0000000..e95263a --- /dev/null +++ b/rehlds/common/IVGuiModule.h @@ -0,0 +1,54 @@ +#ifndef IVGUIMODULE_H +#define IVGUIMODULE_H +#ifdef _WIN32 +#pragma once +#endif + +#include +#include "interface.h" + +// Purpose: Standard interface to loading vgui modules +class IVGuiModule: public IBaseInterface +{ +public: + // called first to setup the module with the vgui + // returns true on success, false on failure + virtual bool Initialize(CreateInterfaceFn *vguiFactories, int factoryCount) = 0; + + // called after all the modules have been initialized + // modules should use this time to link to all the other module interfaces + virtual bool PostInitialize(CreateInterfaceFn *modules, int factoryCount) = 0; + + // called when the module is selected from the menu or otherwise activated + virtual bool Activate() = 0; + + // returns true if the module is successfully initialized and available + virtual bool IsValid() = 0; + + // requests that the UI is temporarily disabled and all data files saved + virtual void Deactivate() = 0; + + // restart from a Deactivate() + virtual void Reactivate() = 0; + + // called when the module is about to be shutdown + virtual void Shutdown() = 0; + + // returns a handle to the main module panel + virtual vgui2::VPANEL GetPanel() = 0; + + // sets the parent of the main module panel + virtual void SetParent(vgui2::VPANEL parent) = 0; + + // messages sent through through the panel returned by GetPanel(): + // + // "ConnectedToGame" "ip" "port" "gamedir" + // "DisconnectedFromGame" + // "ActiveGameName" "name" + // "LoadingStarted" "type" "name" + // "LoadingFinished" "type" "name" +}; + +#define VGUIMODULE_INTERFACE_VERSION "VGuiModuleAdminServer001" + +#endif // IVGUIMODULE_H diff --git a/rehlds/common/ObjectDictionary.cpp b/rehlds/common/ObjectDictionary.cpp new file mode 100644 index 0000000..3fbf34b --- /dev/null +++ b/rehlds/common/ObjectDictionary.cpp @@ -0,0 +1,483 @@ +#include "ObjectDictionary.h" +#include "maintypes.h" + +ObjectDictionary::ObjectDictionary() : + maxSize(0), + size(0), + entries(nullptr) +{ +} + +ObjectDictionary::~ObjectDictionary() +{ + if (entries) { + free(entries); + } +} + +void ObjectDictionary::Clear(bool freeObjectssMemory) +{ + if (freeObjectssMemory) + { + for (int i = 0; i < size; i++) + { + void *obj = entries[i].object; + if (obj) { + free(obj); + } + } + } + + size = 0; + CheckSize(); + ClearCache(); +} + +bool ObjectDictionary::Add(void *object, float key) +{ + if (size == maxSize && !CheckSize()) + return false; + + entry_t *p; + if (size && key < entries[size - 1].key) + { + p = &entries[FindClosestAsIndex(key)]; + + entry_t *e1 = &entries[size]; + entry_t *e2 = &entries[size - 1]; + + while (p->key <= key) { p++; } + while (p != e1) + { + e1->object = e2->object; + e1->key = e2->key; + + e1--; + e2--; + } + } + else + p = &entries[size]; + + p->key = key; + p->object = object; + size++; + + ClearCache(); + AddToCache(p); + + return true; +} + +int ObjectDictionary::FindClosestAsIndex(float key) +{ + UNTESTED + + if (size <= 0) + return -1; + + if (key <= entries->key) + return 0; + + int index = FindKeyInCache(key); + if (index >= 0) + return index; + + int middle; + int first = 0; + int last = size - 1; + float keyMiddle, keyNext; + + if (key == entries[last].key) + { + while (true) + { + middle = (last + first) >> 1; + keyMiddle = entries[middle].key; + + if (keyMiddle == key) + break; + + if (keyMiddle < key) + { + if (entries[middle + 1].key != key) + { + if (entries[middle + 1].key - key < key - keyMiddle) + ++middle; + break; + } + + first = (last + first) >> 1; + } + else + { + last = (last + first) >> 1; + } + } + } + else + { + middle = last; + } + + keyNext = entries[middle - 1].key; + while (keyNext == key) { + keyNext = entries[middle--].key; + } + + AddToCache(&entries[middle], key); + return middle; +} + +void ObjectDictionary::ClearCache() +{ + memset(cache, 0, sizeof(cache)); + cacheIndex = 0; +} + +bool ObjectDictionary::RemoveIndex(int index, bool freeObjectMemory) +{ + if (index < 0 || index >= size) + return false; + + entry_t *p = &entries[size - 1]; + entry_t *e1 = &entries[size]; + entry_t *e2 = &entries[size + 1]; + + if (freeObjectMemory && e1->object) + free(e1->object); + + while (p != e1) + { + e1->object = e2->object; + e1->key = e2->key; + + e1++; + e2++; + } + + p->object = nullptr; + p->key = 0; + size--; + + CheckSize(); + ClearCache(); + + return false; +} + +bool ObjectDictionary::RemoveIndexRange(int minIndex, int maxIndex) +{ + if (minIndex > maxIndex) + { + if (maxIndex < 0) + maxIndex = 0; + + if (minIndex >= size) + minIndex = size - 1; + } + else + { + if (minIndex < 0) + minIndex = 0; + + if (maxIndex >= size) + maxIndex = size - 1; + } + + int offset = minIndex + maxIndex - 1; + size -= offset; + CheckSize(); + return true; +} + +bool ObjectDictionary::Remove(void *object) +{ + bool found = false; + for (int i = 0; i < size; ++i) + { + if (entries[i].object == object) { + RemoveIndex(i); + found = true; + } + } + + return found ? true : false; +} + +bool ObjectDictionary::RemoveSingle(void *object) +{ + for (int i = 0; i < size; ++i) + { + if (entries[i].object == object) { + RemoveIndex(i); + return true; + } + } + + return false; +} + +bool ObjectDictionary::RemoveKey(float key) +{ + int i = FindClosestAsIndex(key); + if (entries[i].key == key) + { + int j = i; + do { + ++j; + } + while (key == entries[j + 1].key); + + return RemoveIndexRange(i, j); + } + + return false; +} + +bool ObjectDictionary::CheckSize() +{ + int newSize = maxSize; + if (size == maxSize) + { + newSize = 1 - (int)(maxSize * -1.25f); + } + else if (maxSize * 0.5f >size) + { + newSize = (int)(maxSize * 0.75f); + } + + if (newSize != maxSize) + { + entry_t *newEntries = (entry_t *)malloc(sizeof(entry_t)); + if (!newEntries) + return false; + + memset(&newEntries[size], 0, sizeof(entry_t) * (newSize - size)); + + if (entries && size) + { + memcpy(newEntries, entries, sizeof(entry_t) * size); + free(entries); + } + + entries = newEntries; + maxSize = newSize; + } + + return true; +} + +void ObjectDictionary::Init() +{ + size = 0; + maxSize = 0; + entries = nullptr; + + CheckSize(); + ClearCache(); +} + +void ObjectDictionary::Init(int baseSize) +{ + size = 0; + maxSize = 0; + entries = (entry_t *)Mem_ZeroMalloc(sizeof(entry_t) * baseSize); + + if (entries) { + maxSize = baseSize; + } +} + +bool ObjectDictionary::Add(void *object) +{ + return Add(object, 0); +} + +int ObjectDictionary::CountElements() +{ + return size; +} + +bool ObjectDictionary::IsEmpty() +{ + return (size == 0) ? true : false; +} + +bool ObjectDictionary::Contains(void *object) +{ + if (FindObjectInCache(object) >= 0) + return true; + + for (int i = 0; i < size; i++) + { + entry_t *e = &entries[i]; + if (e->object == object) { + AddToCache(e); + return true; + } + } + + return false; +} + +void *ObjectDictionary::GetFirst() +{ + currentEntry = 0; + return GetNext(); +} + +void *ObjectDictionary::GetLast() +{ + return (size > 0) ? entries[size - 1].object : nullptr; +} + +bool ObjectDictionary::ChangeKey(void *object, float newKey) +{ + int pos = FindObjectInCache(object); + if (pos < 0) + { + for (pos = 0; pos < size; pos++) + { + if (entries[pos].object == object) { + AddToCache(&entries[pos]); + break; + } + } + + if (pos == size) { + return false; + } + } + + entry_t *p, *e; + + p = &entries[pos]; + if (p->key == newKey) + return false; + + int newpos = FindClosestAsIndex(newKey); + e = &entries[newpos]; + if (pos < newpos) + { + if (e->key > newKey) + e--; + + entry_t *e2 = &entries[pos + 1]; + while (p < e) + { + p->object = e2->object; + p->key = e2->key; + + p++; + e2++; + } + } + else if (pos > newpos) + { + if (e->key > newKey) + e++; + + entry_t *e2 = &entries[pos - 1]; + while (p > e) + { + p->object = e2->object; + p->key = e2->key; + + p--; + e2--; + } + } + + p->object = object; + p->key = newKey; + ClearCache(); + + return true; +} + +bool ObjectDictionary::UnsafeChangeKey(void *object, float newKey) +{ + int pos = FindObjectInCache(object); + if (pos < 0) + { + for (pos = 0; pos < size; pos++) + { + if (entries[pos].object == object) { + break; + } + } + + if (pos == size) { + return false; + } + } + + entries[pos].key = newKey; + ClearCache(); + return true; +} + +void ObjectDictionary::AddToCache(entry_t *entry) +{ + int i = (cacheIndex % 32); + + cache[i].object = entry; + cache[i].key = entry->key; + cacheIndex++; +} + +void ObjectDictionary::AddToCache(entry_t *entry, float key) +{ + int i = (cacheIndex % 32); + + cache[i].object = entry; + cache[i].key = key; + cacheIndex++; +} + +int ObjectDictionary::FindKeyInCache(float key) +{ + for (auto& ch : cache) + { + if (ch.object && ch.key == key) { + return (entry_t *)ch.object - entries; + } + } + + return -1; +} + +int ObjectDictionary::FindObjectInCache(void *object) +{ + for (auto& ch : cache) + { + if (ch.object && ch.object == object) { + return (entry_t *)ch.object - entries; + } + } + + return -1; +} + +void *ObjectDictionary::FindClosestKey(float key) +{ + currentEntry = FindClosestAsIndex(key); + return GetNext(); +} + +void *ObjectDictionary::GetNext() +{ + if (currentEntry < 0 || currentEntry >= size) + return nullptr; + + return &entries[currentEntry++]; +} + +void *ObjectDictionary::FindExactKey(float key) +{ + if ((currentEntry = FindClosestAsIndex(key)) < 0) + return nullptr; + + return (entries[currentEntry].key == key) ? GetNext() : nullptr; +} diff --git a/rehlds/common/ObjectDictionary.h b/rehlds/common/ObjectDictionary.h new file mode 100644 index 0000000..d72d89b --- /dev/null +++ b/rehlds/common/ObjectDictionary.h @@ -0,0 +1,73 @@ +#ifndef OBJECTDICTIONARY_H +#define OBJECTDICTIONARY_H +#ifdef _WIN32 +#pragma once +#endif + +#include "IObjectContainer.h" + +typedef struct entry_s { + void *object; + float key; +} entry_t; + +class ObjectDictionary: public IObjectContainer { +public: + ObjectDictionary(); + virtual ~ObjectDictionary(); + + void Init(); + void Init(int baseSize); + + bool Add(void *object); + bool Contains(void *object); + bool IsEmpty(); + int CountElements(); + + void Clear(bool freeObjectssMemory = false); + + bool Add(void *object, float key); + bool ChangeKey(void *object, float newKey); + bool UnsafeChangeKey(void *object, float newKey); + + bool Remove(void *object); + bool RemoveSingle(void *object); + bool RemoveKey(float key); + bool RemoveRange(float startKey, float endKey); + + void *FindClosestKey(float key); + void *FindExactKey(float key); + + void *GetFirst(); + void *GetLast(); + void *GetNext(); + + int FindKeyInCache(float key); + int FindObjectInCache(void *object); + + void ClearCache(); + bool CheckSize(); + + void AddToCache(entry_t *entry); + void AddToCache(entry_t *entry, float key); + + bool RemoveIndex(int index, bool freeObjectMemory = false); + bool RemoveIndexRange(int minIndex, int maxIndex); + int FindClosestAsIndex(float key); + +protected: + + int currentEntry; + float findKey; + + enum { MAX_OBJECT_CACHE = 32 }; + + entry_t *entries; + entry_t cache[MAX_OBJECT_CACHE]; + + int cacheIndex; + int size; + int maxSize; +}; + +#endif // OBJECTDICTIONARY_H diff --git a/rehlds/common/ObjectList.cpp b/rehlds/common/ObjectList.cpp new file mode 100644 index 0000000..58bba64 --- /dev/null +++ b/rehlds/common/ObjectList.cpp @@ -0,0 +1,232 @@ +#include "ObjectList.h" + +ObjectList::ObjectList() +{ + head = tail = current = nullptr; + number = 0; +} + +ObjectList::~ObjectList() +{ + Clear(false); +} + +bool ObjectList::AddHead(void *newObject) +{ + // create new element + element_t *newElement = (element_t *)Mem_ZeroMalloc(sizeof(element_t)); + + // out of memory + if (!newElement) + return false; + + // insert element + newElement->object = newObject; + + if (head) + { + newElement->next = head; + head->prev = newElement; + } + + head = newElement; + + // if list was empty set new tail + if (!tail) + tail = head; + + number++; + + return true; +} + +void *ObjectList::RemoveHead() +{ + void *retObj; + + // check head is present + if (head) + { + retObj = head->object; + element_t *newHead = head->next; + if (newHead) + newHead->prev = nullptr; + + // if only one element is in list also update tail + // if we remove this prev element + if (tail == head) + tail = nullptr; + + free(head); + head = newHead; + + number--; + } + else + retObj = nullptr; + + return retObj; +} + +bool ObjectList::AddTail(void *newObject) +{ + // create new element + element_t *newElement = (element_t *)Mem_ZeroMalloc(sizeof(element_t)); + + // out of memory + if (!newElement) + return false; + + // insert element + newElement->object = newObject; + + if (tail) + { + newElement->prev = tail; + tail->next = newElement; + } + + tail = newElement; + + // if list was empty set new tail + if (!head) + head = tail; + + number++; + return true; +} + +void *ObjectList::RemoveTail() +{ + void *retObj; + + // check tail is present + if (tail) + { + retObj = tail->object; + element_t *newTail = tail->prev; + if (newTail) + newTail->next = nullptr; + + // if only one element is in list also update tail + // if we remove this prev element + if (head == tail) + head = nullptr; + + free(tail); + tail = newTail; + + number--; + + } + else + retObj = nullptr; + + return retObj; +} + +bool ObjectList::IsEmpty() +{ + return (head == nullptr); +} + +int ObjectList::CountElements() +{ + return number; +} + +bool ObjectList::Contains(void *object) +{ + element_t *e = head; + + while (e && e->object != object) { e = e->next; } + + if (e) + { + current = e; + return true; + } + else + { + return false; + } +} + +void ObjectList::Clear(bool freeElementsMemory) +{ + element_t *ne; + element_t *e = head; + + while (e) + { + ne = e->next; + + if (freeElementsMemory && e->object) + free(e->object); + + free(e); + e = ne; + } + + head = tail = current = nullptr; + number = 0; +} + +bool ObjectList::Remove(void *object) +{ + element_t *e = head; + + while (e && e->object != object) { e = e->next; } + + if (e) + { + if (e->prev) e->prev->next = e->next; + if (e->next) e->next->prev = e->prev; + + if (head == e) head = e->next; + if (tail == e) tail = e->prev; + if (current == e) current= e->next; + + free(e); + number--; + } + + return (e != nullptr); +} + +void ObjectList::Init() +{ + head = tail = current = nullptr; + number = 0; +} + +void *ObjectList::GetFirst() +{ + if (head) + { + current = head->next; + return head->object; + } + else + { + current = nullptr; + return nullptr; + } +} + +void *ObjectList::GetNext() +{ + void *retObj = nullptr; + if (current) + { + retObj = current->object; + current = current->next; + } + + return retObj; +} + +bool ObjectList::Add(void *newObject) +{ + return AddTail(newObject); +} diff --git a/rehlds/common/ObjectList.h b/rehlds/common/ObjectList.h new file mode 100644 index 0000000..821a82a --- /dev/null +++ b/rehlds/common/ObjectList.h @@ -0,0 +1,43 @@ +#ifndef OBJECTLIST_H +#define OBJECTLIST_H +#ifdef _WIN32 +#pragma once +#endif + +#include "IObjectContainer.h" + +class ObjectList: public IObjectContainer { +public: + void Init(); + bool Add(void *newObject); + void *GetFirst(); + void *GetNext(); + + ObjectList(); + virtual ~ObjectList(); + + void Clear(bool freeElementsMemory = false); + int CountElements(); + void *RemoveTail(); + void *RemoveHead(); + + bool AddTail(void *newObject); + bool AddHead(void *newObject); + bool Remove(void *object); + bool Contains(void *object); + bool IsEmpty(); + + typedef struct element_s { + element_s *prev; // pointer to the last element or NULL + element_s *next; // pointer to the next elemnet or NULL + void *object; // the element's object + } element_t; + +protected: + element_t *head; // first element in list + element_t *tail; // last element in list + element_t *current; // current element in list + int number; +}; + +#endif // OBJECTLIST_H diff --git a/rehlds/common/SteamAppStartUp.cpp b/rehlds/common/SteamAppStartUp.cpp new file mode 100644 index 0000000..ae38d12 --- /dev/null +++ b/rehlds/common/SteamAppStartUp.cpp @@ -0,0 +1,181 @@ +#ifdef _WIN32 +#include "SteamAppStartup.h" + +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#include +#include +#include + +#define STEAM_PARM "-steam" + +bool FileExists(const char *fileName) +{ + struct _stat statbuf; + return (_stat(fileName, &statbuf) == 0); +} + +// Handles launching the game indirectly via steam +void LaunchSelfViaSteam(const char *params) +{ + // calculate the details of our launch + char appPath[MAX_PATH]; + ::GetModuleFileName((HINSTANCE)GetModuleHandle(NULL), appPath, sizeof(appPath)); + + // strip out the exe name + char *slash = strrchr(appPath, '\\'); + if (slash) + { + *slash = '\0'; + } + + // save out our details to the registry + HKEY hKey; + if (ERROR_SUCCESS == RegOpenKey(HKEY_CURRENT_USER, "Software\\Valve\\Steam", &hKey)) + { + DWORD dwType = REG_SZ; + DWORD dwSize = static_cast( strlen(appPath) + 1 ); + RegSetValueEx(hKey, "TempAppPath", NULL, dwType, (LPBYTE)appPath, dwSize); + dwSize = static_cast( strlen(params) + 1 ); + RegSetValueEx(hKey, "TempAppCmdLine", NULL, dwType, (LPBYTE)params, dwSize); + // clear out the appID (since we don't know it yet) + dwType = REG_DWORD; + int appID = -1; + RegSetValueEx(hKey, "TempAppID", NULL, dwType, (LPBYTE)&appID, sizeof(appID)); + RegCloseKey(hKey); + } + + // search for an active steam instance + HWND hwnd = ::FindWindow("Valve_SteamIPC_Class", "Hidden Window"); + if (hwnd) + { + ::PostMessage(hwnd, WM_USER + 3, 0, 0); + } + else + { + // couldn't find steam, find and launch it + + // first, search backwards through our current set of directories + char steamExe[MAX_PATH] = ""; + char dir[MAX_PATH]; + + if (::GetCurrentDirectoryA(sizeof(dir), dir)) + { + char *slash = strrchr(dir, '\\'); + while (slash) + { + // see if steam_dev.exe is in the directory first + slash[1] = 0; + strcat(slash, "steam_dev.exe"); + FILE *f = fopen(dir, "rb"); + if (f) + { + // found it + fclose(f); + strcpy(steamExe, dir); + break; + } + + // see if steam.exe is in the directory + slash[1] = 0; + strcat(slash, "steam.exe"); + f = fopen(dir, "rb"); + if (f) + { + // found it + fclose(f); + strcpy(steamExe, dir); + break; + } + + // kill the string at the slash + slash[0] = 0; + + // move to the previous slash + slash = strrchr(dir, '\\'); + } + } + + if (!steamExe[0]) + { + // still not found, use the one in the registry + HKEY hKey; + if (ERROR_SUCCESS == RegOpenKey(HKEY_CURRENT_USER, "Software\\Valve\\Steam", &hKey)) + { + DWORD dwType; + DWORD dwSize = sizeof(steamExe); + RegQueryValueEx(hKey, "SteamExe", NULL, &dwType, (LPBYTE)steamExe, &dwSize); + RegCloseKey(hKey); + } + } + + if (!steamExe[0]) + { + // still no path, error + ::MessageBox(NULL, "Error running game: could not find steam.exe to launch", "Fatal Error", MB_OK | MB_ICONERROR); + return; + } + + // fix any slashes + for (char *slash = steamExe; *slash; slash++) + { + if (*slash == '/') + { + *slash = '\\'; + } + } + + // change to the steam directory + strcpy(dir, steamExe); + char *delimiter = strrchr(dir, '\\'); + if (delimiter) + { + *delimiter = 0; + _chdir(dir); + } + + // exec steam.exe, in silent mode, with the launch app param + char *args[4] = { steamExe, "-silent", "-applaunch", '\0' }; + _spawnv(_P_NOWAIT, steamExe, args); + } +} + +// Launches steam if necessary +bool ShouldLaunchAppViaSteam(const char *lpCmdLine, const char *steamFilesystemDllName, const char *stdioFilesystemDllName) +{ + // see if steam is on the command line + const char *steamStr = strstr(lpCmdLine, STEAM_PARM); + + // check the character following it is a whitespace or null + if (steamStr) + { + const char *postChar = steamStr + strlen(STEAM_PARM); + if (*postChar == 0 || isspace(*postChar)) + { + // we're running under steam already, let the app continue + return false; + } + } + + // we're not running under steam, see which filesystems are available + if (FileExists(stdioFilesystemDllName)) + { + // we're being run with a stdio filesystem, so we can continue without steam + return false; + } + + // make sure we have a steam filesystem available + if (!FileExists(steamFilesystemDllName)) + { + return false; + } + + // we have the steam filesystem, and no stdio filesystem, so we must need to be run under steam + // launch steam + LaunchSelfViaSteam(lpCmdLine); + return true; +} + +#endif // _WIN32 diff --git a/rehlds/common/SteamAppStartUp.h b/rehlds/common/SteamAppStartUp.h new file mode 100644 index 0000000..c30048e --- /dev/null +++ b/rehlds/common/SteamAppStartUp.h @@ -0,0 +1,15 @@ +#ifndef STEAMAPPSTARTUP_H +#define STEAMAPPSTARTUP_H +#ifdef _WIN32 +#pragma once +#endif + +// Call this first thing at startup +// Works out if the app is a steam app that is being ran outside of steam, +// and if so, launches steam and tells it to run us as a steam app +// +// if it returns true, then exit +// if it ruturns false, then continue with normal startup +bool ShouldLaunchAppViaSteam(const char *cmdLine, const char *steamFilesystemDllName, const char *stdioFilesystemDllName); + +#endif // STEAMAPPSTARTUP_H diff --git a/rehlds/common/TextConsoleUnix.cpp b/rehlds/common/TextConsoleUnix.cpp new file mode 100644 index 0000000..411d625 --- /dev/null +++ b/rehlds/common/TextConsoleUnix.cpp @@ -0,0 +1,294 @@ +#if !defined(_WIN32) + +#include "TextConsoleUnix.h" +#include "icommandline.h" + +#include +#include +#include +#include +#include +#include +#include + +CTextConsoleUnix console; + +CTextConsoleUnix::~CTextConsoleUnix() +{ + ShutDown(); +} + +bool CTextConsoleUnix::Init(IBaseSystem *system) +{ + static struct termios termNew; + sigset_t block_ttou; + + sigemptyset(&block_ttou); + sigaddset(&block_ttou, SIGTTOU); + sigprocmask(SIG_BLOCK, &block_ttou, NULL); + + tty = stdout; + + // this code is for echo-ing key presses to the connected tty + // (which is != STDOUT) + if (isatty(STDIN_FILENO)) + { + tty = fopen(ctermid(NULL), "w+"); + if (!tty) + { + printf("Unable to open tty(%s) for output\n", ctermid(NULL)); + tty = stdout; + } + else + { + // turn buffering off + setbuf(tty, NULL); + } + } + else + { + tty = fopen("/dev/null", "w+"); + if (!tty) + { + tty = stdout; + } + } + + tcgetattr(STDIN_FILENO, &termStored); + + memcpy(&termNew, &termStored, sizeof(struct termios)); + + // Disable canonical mode, and set buffer size to 1 byte + termNew.c_lflag &= (~ICANON); + termNew.c_cc[ VMIN ] = 1; + termNew.c_cc[ VTIME ] = 0; + + // disable echo + termNew.c_lflag &= (~ECHO); + + tcsetattr(STDIN_FILENO, TCSANOW, &termNew); + sigprocmask(SIG_UNBLOCK, &block_ttou, NULL); + + return CTextConsole::Init(); +} + +void CTextConsoleUnix::ShutDown() +{ + sigset_t block_ttou; + + sigemptyset(&block_ttou); + sigaddset(&block_ttou, SIGTTOU); + sigprocmask(SIG_BLOCK, &block_ttou, NULL); + tcsetattr(STDIN_FILENO, TCSANOW, &termStored); + sigprocmask(SIG_UNBLOCK, &block_ttou, NULL); + + CTextConsole::ShutDown(); +} + +// return 0 if the kb isn't hit +int CTextConsoleUnix::kbhit() +{ + fd_set rfds; + struct timeval tv; + + // Watch stdin (fd 0) to see when it has input. + FD_ZERO(&rfds); + FD_SET(STDIN_FILENO, &rfds); + + // Return immediately. + tv.tv_sec = 0; + tv.tv_usec = 0; + + // Must be in raw or cbreak mode for this to work correctly. + return select(STDIN_FILENO + 1, &rfds, NULL, NULL, &tv) != -1 && FD_ISSET(STDIN_FILENO, &rfds); +} + +char *CTextConsoleUnix::GetLine() +{ + // early return for 99.999% case :) + if (!kbhit()) + return NULL; + + escape_sequence_t es; + + es = ESCAPE_CLEAR; + sigset_t block_ttou; + + sigemptyset(&block_ttou); + sigaddset(&block_ttou, SIGTTOU); + sigaddset(&block_ttou, SIGTTIN); + sigprocmask(SIG_BLOCK, &block_ttou, NULL); + + while (true) + { + if (!kbhit()) + break; + + int nLen; + char ch = 0; + int numRead = read(STDIN_FILENO, &ch, 1); + if (!numRead) + break; + + switch (ch) + { + case '\n': // Enter + es = ESCAPE_CLEAR; + + nLen = ReceiveNewline(); + if (nLen) + { + sigprocmask(SIG_UNBLOCK, &block_ttou, NULL); + return m_szConsoleText; + } + break; + + case 127: // Backspace + case '\b': // Backspace + es = ESCAPE_CLEAR; + ReceiveBackspace(); + break; + + case '\t': // TAB + es = ESCAPE_CLEAR; + ReceiveTab(); + break; + + case 27: // Escape character + es = ESCAPE_RECEIVED; + break; + + case '[': // 2nd part of escape sequence + case 'O': + case 'o': + switch (es) + { + case ESCAPE_CLEAR: + case ESCAPE_BRACKET_RECEIVED: + es = ESCAPE_CLEAR; + ReceiveStandardChar(ch); + break; + + case ESCAPE_RECEIVED: + es = ESCAPE_BRACKET_RECEIVED; + break; + } + break; + case 'A': + if (es == ESCAPE_BRACKET_RECEIVED) + { + es = ESCAPE_CLEAR; + ReceiveUpArrow(); + } + else + { + es = ESCAPE_CLEAR; + ReceiveStandardChar(ch); + } + break; + case 'B': + if (es == ESCAPE_BRACKET_RECEIVED) + { + es = ESCAPE_CLEAR; + ReceiveDownArrow(); + } + else + { + es = ESCAPE_CLEAR; + ReceiveStandardChar(ch); + } + break; + case 'C': + if (es == ESCAPE_BRACKET_RECEIVED) + { + es = ESCAPE_CLEAR; + ReceiveRightArrow(); + } + else + { + es = ESCAPE_CLEAR; + ReceiveStandardChar(ch); + } + break; + case 'D': + if (es == ESCAPE_BRACKET_RECEIVED) + { + es = ESCAPE_CLEAR; + ReceiveLeftArrow(); + } + else + { + es = ESCAPE_CLEAR; + ReceiveStandardChar(ch); + } + break; + default: + // Just eat this char if it's an unsupported escape + if (es != ESCAPE_BRACKET_RECEIVED) + { + // dont' accept nonprintable chars + if ((ch >= ' ') && (ch <= '~')) + { + es = ESCAPE_CLEAR; + ReceiveStandardChar(ch); + } + } + break; + } + + fflush(stdout); + } + + sigprocmask(SIG_UNBLOCK, &block_ttou, NULL); + return NULL; +} + +void CTextConsoleUnix::PrintRaw(char *pszMsg, int nChars) +{ + if (nChars == 0) + { + printf("%s", pszMsg); + } + else + { + for (int nCount = 0; nCount < nChars; nCount++) + { + putchar(pszMsg[ nCount ]); + } + } +} + +void CTextConsoleUnix::Echo(char *pszMsg, int nChars) +{ + if (nChars == 0) + { + fputs(pszMsg, tty); + } + else + { + for (int nCount = 0; nCount < nChars; nCount++) + { + fputc(pszMsg[ nCount ], tty); + } + } +} + +int CTextConsoleUnix::GetWidth() +{ + struct winsize ws; + int nWidth = 0; + + if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0) + { + nWidth = (int)ws.ws_col; + } + + if (nWidth <= 1) + { + nWidth = 80; + } + + return nWidth; +} + +#endif // !defined(_WIN32) diff --git a/rehlds/common/TextConsoleUnix.h b/rehlds/common/TextConsoleUnix.h new file mode 100644 index 0000000..cd06735 --- /dev/null +++ b/rehlds/common/TextConsoleUnix.h @@ -0,0 +1,37 @@ +#ifndef TEXTCONSOLE_UNIX_H +#define TEXTCONSOLE_UNIX_H +#ifdef _WIN32 +#pragma once +#endif + +#include +#include "textconsole.h" + +enum escape_sequence_t +{ + ESCAPE_CLEAR, + ESCAPE_RECEIVED, + ESCAPE_BRACKET_RECEIVED +}; + +class CTextConsoleUnix: public CTextConsole { +public: + virtual ~CTextConsoleUnix(); + + bool Init(IBaseSystem *system = nullptr); + void ShutDown(); + void PrintRaw(char *pszMsg, int nChars = 0); + void Echo(char *pszMsg, int nChars = 0); + char *GetLine(); + int GetWidth(); + +private: + int kbhit(); + + struct termios termStored; + FILE *tty; +}; + +extern CTextConsoleUnix console; + +#endif // TEXTCONSOLE_UNIX_H diff --git a/rehlds/common/TextConsoleWin32.cpp b/rehlds/common/TextConsoleWin32.cpp new file mode 100644 index 0000000..c875740 --- /dev/null +++ b/rehlds/common/TextConsoleWin32.cpp @@ -0,0 +1,250 @@ +#if defined(_WIN32) +#include "TextConsoleWin32.h" + +CTextConsoleWin32 console; + +BOOL WINAPI ConsoleHandlerRoutine(DWORD CtrlType) +{ + // TODO ? + /*if (CtrlType != CTRL_C_EVENT && CtrlType != CTRL_BREAK_EVENT) + { + // don't quit on break or ctrl+c + m_System->Stop(); + }*/ + + return TRUE; +} + +// GetConsoleHwnd() helper function from MSDN Knowledge Base Article Q124103 +// needed, because HWND GetConsoleWindow(VOID) is not avaliable under Win95/98/ME +HWND GetConsoleHwnd() +{ + HWND hwndFound; // This is what is returned to the caller. + char pszNewWindowTitle[1024]; // Contains fabricated WindowTitle + char pszOldWindowTitle[1024]; // Contains original WindowTitle + + // Fetch current window title. + GetConsoleTitle(pszOldWindowTitle, sizeof(pszOldWindowTitle)); + + // Format a "unique" NewWindowTitle. + wsprintf(pszNewWindowTitle, "%d/%d", GetTickCount(), GetCurrentProcessId()); + + // Change current window title. + SetConsoleTitle(pszNewWindowTitle); + + // Ensure window title has been updated. + Sleep(40); + + // Look for NewWindowTitle. + hwndFound = FindWindow(nullptr, pszNewWindowTitle); + + // Restore original window title. + SetConsoleTitle(pszOldWindowTitle); + + return hwndFound; +} + +CTextConsoleWin32::~CTextConsoleWin32() +{ + ShutDown(); +} + +bool CTextConsoleWin32::Init(IBaseSystem *system) +{ + if (!AllocConsole()) + m_System = system; + + SetTitle(m_System ? m_System->GetName() : "Console"); + + hinput = GetStdHandle(STD_INPUT_HANDLE); + houtput = GetStdHandle(STD_OUTPUT_HANDLE); + + if (!SetConsoleCtrlHandler(&ConsoleHandlerRoutine, TRUE)) + { + Print("WARNING! TextConsole::Init: Could not attach console hook.\n"); + } + + Attrib = FOREGROUND_GREEN | FOREGROUND_INTENSITY | BACKGROUND_INTENSITY; + SetWindowPos(GetConsoleHwnd(), HWND_TOP, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOREPOSITION | SWP_SHOWWINDOW); + + return CTextConsole::Init(system); +} + +void CTextConsoleWin32::ShutDown() +{ + FreeConsole(); + CTextConsole::ShutDown(); +} + +void CTextConsoleWin32::SetVisible(bool visible) +{ + ShowWindow(GetConsoleHwnd(), visible ? SW_SHOW : SW_HIDE); + m_ConsoleVisible = visible; +} + +char *CTextConsoleWin32::GetLine() +{ + while (true) + { + INPUT_RECORD recs[1024]; + unsigned long numread; + unsigned long numevents; + + if (!GetNumberOfConsoleInputEvents(hinput, &numevents)) + { + if (m_System) + m_System->Errorf("CTextConsoleWin32::GetLine: !GetNumberOfConsoleInputEvents"); + + return nullptr; + } + + if (numevents <= 0) + break; + + if (!ReadConsoleInput(hinput, recs, ARRAYSIZE(recs), &numread)) + { + if (m_System) + m_System->Errorf("CTextConsoleWin32::GetLine: !ReadConsoleInput"); + return nullptr; + } + + if (numread == 0) + return nullptr; + + for (int i = 0; i < (int)numread; i++) + { + INPUT_RECORD *pRec = &recs[i]; + if (pRec->EventType != KEY_EVENT) + continue; + + if (pRec->Event.KeyEvent.bKeyDown) + { + // check for cursor keys + if (pRec->Event.KeyEvent.wVirtualKeyCode == VK_UP) + { + ReceiveUpArrow(); + } + else if (pRec->Event.KeyEvent.wVirtualKeyCode == VK_DOWN) + { + ReceiveDownArrow(); + } + else if (pRec->Event.KeyEvent.wVirtualKeyCode == VK_LEFT) + { + ReceiveLeftArrow(); + } + else if (pRec->Event.KeyEvent.wVirtualKeyCode == VK_RIGHT) + { + ReceiveRightArrow(); + } + else + { + int nLen; + char ch = pRec->Event.KeyEvent.uChar.AsciiChar; + switch (ch) + { + case '\r': // Enter + nLen = ReceiveNewline(); + if (nLen) + { + return m_szConsoleText; + } + break; + case '\b': // Backspace + ReceiveBackspace(); + break; + case '\t': // TAB + ReceiveTab(); + break; + default: + // dont' accept nonprintable chars + if ((ch >= ' ') && (ch <= '~')) + { + ReceiveStandardChar(ch); + } + break; + } + } + } + } + } + + return nullptr; +} + +void CTextConsoleWin32::PrintRaw(char *pszMsg, int nChars) +{ +#ifdef LAUNCHER_FIXES + char outputStr[2048]; + WCHAR unicodeStr[1024]; + + DWORD nSize = MultiByteToWideChar(CP_UTF8, 0, pszMsg, -1, NULL, 0); + if (nSize > sizeof(unicodeStr)) + return; + + MultiByteToWideChar(CP_UTF8, 0, pszMsg, -1, unicodeStr, nSize); + DWORD nLength = WideCharToMultiByte(CP_OEMCP, 0, unicodeStr, -1, 0, 0, NULL, NULL); + if (nLength > sizeof(outputStr)) + return; + + WideCharToMultiByte(CP_OEMCP, 0, unicodeStr, -1, outputStr, nLength, NULL, NULL); + WriteFile(houtput, outputStr, nChars ? nChars : strlen(outputStr), NULL, NULL); +#else + WriteFile(houtput, pszMsg, nChars ? nChars : strlen(pszMsg), NULL, NULL); +#endif +} + +void CTextConsoleWin32::Echo(char *pszMsg, int nChars) +{ + PrintRaw(pszMsg, nChars); +} + +int CTextConsoleWin32::GetWidth() +{ + CONSOLE_SCREEN_BUFFER_INFO csbi; + int nWidth = 0; + + if (GetConsoleScreenBufferInfo(houtput, &csbi)) { + nWidth = csbi.dwSize.X; + } + + if (nWidth <= 1) + nWidth = 80; + + return nWidth; +} + +void CTextConsoleWin32::SetStatusLine(char *pszStatus) +{ + strncpy(statusline, pszStatus, sizeof(statusline) - 1); + statusline[sizeof statusline - 2] = '\0'; + UpdateStatus(); +} + +void CTextConsoleWin32::UpdateStatus() +{ + COORD coord; + DWORD dwWritten = 0; + WORD wAttrib[ 80 ]; + + for (int i = 0; i < 80; i++) + { + wAttrib[i] = Attrib; // FOREGROUND_GREEN | FOREGROUND_INTENSITY | BACKGROUND_INTENSITY; + } + + coord.X = coord.Y = 0; + + WriteConsoleOutputAttribute(houtput, wAttrib, 80, coord, &dwWritten); + WriteConsoleOutputCharacter(houtput, statusline, 80, coord, &dwWritten); +} + +void CTextConsoleWin32::SetTitle(char *pszTitle) +{ + SetConsoleTitle(pszTitle); +} + +void CTextConsoleWin32::SetColor(WORD attrib) +{ + Attrib = attrib; +} + +#endif // defined(_WIN32) diff --git a/rehlds/common/TextConsoleWin32.h b/rehlds/common/TextConsoleWin32.h new file mode 100644 index 0000000..5ad91b2 --- /dev/null +++ b/rehlds/common/TextConsoleWin32.h @@ -0,0 +1,39 @@ +#ifndef TEXTCONSOLE_WIN32_H +#define TEXTCONSOLE_WIN32_H +#ifdef _WIN32 +#pragma once +#endif + +#include +#include "TextConsole.h" + +class CTextConsoleWin32: public CTextConsole { +public: + virtual ~CTextConsoleWin32(); + + bool Init(IBaseSystem *system = nullptr); + void ShutDown(); + + void SetTitle(char *pszTitle); + void SetStatusLine(char *pszStatus); + void UpdateStatus(); + + void PrintRaw(char * pszMsz, int nChars = 0); + void Echo(char * pszMsz, int nChars = 0); + char *GetLine(); + int GetWidth(); + + void SetVisible(bool visible); + void SetColor(WORD); + +private: + HANDLE hinput; // standard input handle + HANDLE houtput; // standard output handle + WORD Attrib; // attrib colours for status bar + + char statusline[81]; // first line in console is status line +}; + +extern CTextConsoleWin32 console; + +#endif // TEXTCONSOLE_WIN32_H diff --git a/rehlds/common/TokenLine.cpp b/rehlds/common/TokenLine.cpp new file mode 100644 index 0000000..e74eb0b --- /dev/null +++ b/rehlds/common/TokenLine.cpp @@ -0,0 +1,131 @@ +#include "TokenLine.h" +#include + +TokenLine::TokenLine() +{ + +} + +TokenLine::TokenLine(char *string) +{ + SetLine(string); +} + +TokenLine::~TokenLine() +{ + +} + +bool TokenLine::SetLine(const char *newLine) +{ + m_tokenNumber = 0; + + if (!newLine || (strlen(newLine) >= (MAX_LINE_CHARS - 1))) + { + memset(m_fullLine, 0, MAX_LINE_CHARS); + memset(m_tokenBuffer, 0, MAX_LINE_CHARS); + return false; + } + + strncpy(m_fullLine, newLine, MAX_LINE_CHARS - 1); + m_fullLine[MAX_LINE_CHARS - 1] = '\0'; + + strncpy(m_tokenBuffer, newLine, MAX_LINE_CHARS - 1); + m_tokenBuffer[MAX_LINE_CHARS - 1] = '\0'; + + // parse tokens + char *charPointer = m_tokenBuffer; + while (*charPointer && (m_tokenNumber < MAX_LINE_TOKENS)) + { + while (*charPointer && ((*charPointer <= 32) || (*charPointer > 126))) + charPointer++; + + if (*charPointer) + { + m_token[m_tokenNumber] = charPointer; + + // special treatment for quotes + if (*charPointer == '\"') + { + charPointer++; + m_token[m_tokenNumber] = charPointer; + while (*charPointer && (*charPointer != '\"')) + charPointer++; + } + else + { + m_token[m_tokenNumber] = charPointer; + while (*charPointer && ((*charPointer > 32) && (*charPointer <= 126))) + charPointer++; + } + + m_tokenNumber++; + + if (*charPointer) + { + *charPointer = '\0'; + charPointer++; + } + } + } + + return (m_tokenNumber != MAX_LINE_TOKENS); +} + +char *TokenLine::GetLine() +{ + return m_fullLine; +} + +char *TokenLine::GetToken(int i) +{ + if (i >= m_tokenNumber) + return NULL; + + return m_token[i]; +} + +// if the given parm is not present return NULL +// otherwise return the address of the following token, or an empty string +char *TokenLine::CheckToken(char *parm) +{ + for (int i = 0; i < m_tokenNumber; i++) + { + if (!m_token[i]) + continue; + + if (!strcmp(parm, m_token[i])) + { + char *ret = m_token[i + 1]; + + // if this token doesn't exist, since index i was the last + // return an empty string + if (m_tokenNumber == (i + 1)) + ret = ""; + + return ret; + } + } + + return NULL; +} + +int TokenLine::CountToken() +{ + int c = 0; + for (int i = 0; i < m_tokenNumber; i++) + { + if (m_token[i]) + c++; + } + + return c; +} + +char *TokenLine::GetRestOfLine(int i) +{ + if (i >= m_tokenNumber) + return NULL; + + return m_fullLine + (m_token[i] - m_tokenBuffer); +} diff --git a/rehlds/common/TokenLine.h b/rehlds/common/TokenLine.h new file mode 100644 index 0000000..dfa1792 --- /dev/null +++ b/rehlds/common/TokenLine.h @@ -0,0 +1,29 @@ +#ifndef TOKENLINE_H +#define TOKENLINE_H +#ifdef _WIN32 +#pragma once +#endif + +class TokenLine { +public: + TokenLine(); + TokenLine(char *string); + virtual ~TokenLine(); + + char *GetRestOfLine(int i); // returns all chars after token i + int CountToken(); // returns number of token + char *CheckToken(char *parm); // returns token after token parm or "" + char *GetToken(int i); // returns token i + char *GetLine(); // returns full line + bool SetLine(const char *newLine); // set new token line and parses it + +private: + enum { MAX_LINE_CHARS = 2048, MAX_LINE_TOKENS = 128 }; + + char m_tokenBuffer[MAX_LINE_CHARS]; + char m_fullLine[MAX_LINE_CHARS]; + char *m_token[MAX_LINE_TOKENS]; + int m_tokenNumber; +}; + +#endif // TOKENLINE_H diff --git a/rehlds/common/netapi.cpp b/rehlds/common/netapi.cpp new file mode 100644 index 0000000..7c69449 --- /dev/null +++ b/rehlds/common/netapi.cpp @@ -0,0 +1,208 @@ +#include +#include + +#ifdef _WIN32 + #include "winsock.h" +#else + #include + #include + #include + #include + #include + #include +#endif // _WIN32 + +#include "netapi.h" + +class CNetAPI: public INetAPI { +public: + virtual void NetAdrToSockAddr(netadr_t *a, struct sockaddr *s); + virtual void SockAddrToNetAdr(struct sockaddr *s, netadr_t *a); + + virtual char *AdrToString(netadr_t *a); + virtual bool StringToAdr(const char *s, netadr_t *a); + + virtual void GetSocketAddress(int socket, netadr_t *a); + virtual bool CompareAdr(netadr_t *a, netadr_t *b); + virtual void GetLocalIP(netadr_t *a); +}; + +// Expose interface +CNetAPI g_NetAPI; +INetAPI *net = (INetAPI *)&g_NetAPI; + +void CNetAPI::NetAdrToSockAddr(netadr_t *a, struct sockaddr *s) +{ + memset(s, 0, sizeof(*s)); + + if (a->type == NA_BROADCAST) + { + ((struct sockaddr_in *)s)->sin_family = AF_INET; + ((struct sockaddr_in *)s)->sin_port = a->port; + ((struct sockaddr_in *)s)->sin_addr.s_addr = INADDR_BROADCAST; + } + else if (a->type == NA_IP) + { + ((struct sockaddr_in *)s)->sin_family = AF_INET; + ((struct sockaddr_in *)s)->sin_addr.s_addr = *(int *)&a->ip; + ((struct sockaddr_in *)s)->sin_port = a->port; + } +} + +void CNetAPI::SockAddrToNetAdr(struct sockaddr *s, netadr_t *a) +{ + if (s->sa_family == AF_INET) + { + a->type = NA_IP; + *(int *)&a->ip = ((struct sockaddr_in *)s)->sin_addr.s_addr; + a->port = ((struct sockaddr_in *)s)->sin_port; + } +} + +char *CNetAPI::AdrToString(netadr_t *a) +{ + static char s[64]; + memset(s, 0, sizeof(s)); + + if (a) + { + if (a->type == NA_LOOPBACK) + { + sprintf(s, "loopback"); + } + else if (a->type == NA_IP) + { + sprintf(s, "%i.%i.%i.%i:%i", a->ip[0], a->ip[1], a->ip[2], a->ip[3], ntohs(a->port)); + } + } + + return s; +} + +bool StringToSockaddr(const char *s, struct sockaddr *sadr) +{ + struct hostent *h; + char *colon; + char copy[128]; + struct sockaddr_in *p; + + memset(sadr, 0, sizeof(*sadr)); + + p = (struct sockaddr_in *)sadr; + p->sin_family = AF_INET; + p->sin_port = 0; + + strcpy(copy, s); + + // strip off a trailing :port if present + for (colon = copy ; *colon ; colon++) + { + if (*colon == ':') + { + // terminate + *colon = '\0'; + + // Start at next character + p->sin_port = htons((short)atoi(colon + 1)); + } + } + + // Numeric IP, no DNS + if (copy[0] >= '0' && copy[0] <= '9' && strstr(copy, ".")) + { + *(int *)&p->sin_addr = inet_addr(copy); + } + else + { + // DNS it + if (!(h = gethostbyname(copy))) + { + return false; + } + + // Use first result + *(int *)&p->sin_addr = *(int *)h->h_addr_list[0]; + } + + return true; +} + +bool CNetAPI::StringToAdr(const char *s, netadr_t *a) +{ + struct sockaddr sadr; + if (!strcmp(s, "localhost")) + { + memset(a, 0, sizeof(*a)); + a->type = NA_LOOPBACK; + return true; + } + + if (!StringToSockaddr(s, &sadr)) + { + return false; + } + + SockAddrToNetAdr(&sadr, a); + return true; +} + +// Lookup the IP address for the specified IP socket +void CNetAPI::GetSocketAddress(int socket, netadr_t *a) +{ + char buff[512]; + struct sockaddr_in address; + int namelen; + + memset(a, 0, sizeof(*a)); + gethostname(buff, sizeof(buff)); + + // Ensure that it doesn't overrun the buffer + buff[sizeof buff - 1] = '\0'; + StringToAdr(buff, a); + + namelen = sizeof(address); + if (getsockname(socket, (struct sockaddr *)&address, (int *)&namelen) == 0) + { + a->port = address.sin_port; + } +} + +bool CNetAPI::CompareAdr(netadr_t *a, netadr_t *b) +{ + if (a->type != b->type) + { + return false; + } + + if (a->type == NA_LOOPBACK) + { + return true; + } + + if (a->type == NA_IP && + a->ip[0] == b->ip[0] && + a->ip[1] == b->ip[1] && + a->ip[2] == b->ip[2] && + a->ip[3] == b->ip[3] && + a->port == b->port) + { + return true; + } + + return false; +} + +void CNetAPI::GetLocalIP(netadr_t *a) +{ + char s[64]; + if(!::gethostname(s, 64)) + { + struct hostent *localip = ::gethostbyname(s); + if(localip) + { + a->type = NA_IP; + a->port = 0; + memcpy(a->ip, localip->h_addr_list[0], 4); + } + } +} diff --git a/rehlds/common/netapi.h b/rehlds/common/netapi.h new file mode 100644 index 0000000..8401c92 --- /dev/null +++ b/rehlds/common/netapi.h @@ -0,0 +1,25 @@ +#ifndef NETAPI_H +#define NETAPI_H +#ifdef _WIN32 +#pragma once +#endif + +#include "netadr.h" + +class INetAPI { +public: + virtual void NetAdrToSockAddr(netadr_t *a, struct sockaddr *s) = 0; // Convert a netadr_t to sockaddr + virtual void SockAddrToNetAdr(struct sockaddr *s, netadr_t *a) = 0; // Convert a sockaddr to netadr_t + + virtual char *AdrToString(netadr_t *a) = 0; // Convert a netadr_t to a string + virtual bool StringToAdr(const char *s, netadr_t *a) = 0; // Convert a string address to a netadr_t, doing DNS if needed + virtual void GetSocketAddress(int socket, netadr_t *a) = 0; // Look up IP address for socket + virtual bool CompareAdr(netadr_t *a, netadr_t *b) = 0; + + // return the IP of the local host + virtual void GetLocalIP(netadr_t *a) = 0; +}; + +extern INetAPI *net; + +#endif // NETAPI_H diff --git a/rehlds/common/textconsole.cpp b/rehlds/common/textconsole.cpp new file mode 100644 index 0000000..929784a --- /dev/null +++ b/rehlds/common/textconsole.cpp @@ -0,0 +1,344 @@ +#include +#include + +#include "textconsole.h" +#include "ObjectList.h" + +bool CTextConsole::Init(IBaseSystem *system) +{ + // NULL or a valid base system interface + m_System = system; + + memset(m_szConsoleText, 0, sizeof(m_szConsoleText)); + m_nConsoleTextLen = 0; + m_nCursorPosition = 0; + + memset(m_szSavedConsoleText, 0, sizeof(m_szSavedConsoleText)); + m_nSavedConsoleTextLen = 0; + + memset(m_aszLineBuffer, 0, sizeof(m_aszLineBuffer)); + m_nTotalLines = 0; + m_nInputLine = 0; + m_nBrowseLine = 0; + + // these are log messages, not related to console + Sys_Printf("\n"); + Sys_Printf("Console initialized.\n"); + + m_ConsoleVisible = true; + + return true; +} + +void CTextConsole::SetVisible(bool visible) +{ + m_ConsoleVisible = visible; +} + +bool CTextConsole::IsVisible() +{ + return m_ConsoleVisible; +} + +void CTextConsole::ShutDown() +{ + ; +} + +void CTextConsole::Print(char *pszMsg) +{ + if (m_nConsoleTextLen) + { + int nLen = m_nConsoleTextLen; + while (nLen--) + { + PrintRaw("\b \b"); + } + } + + PrintRaw(pszMsg); + + if (m_nConsoleTextLen) + { + PrintRaw(m_szConsoleText, m_nConsoleTextLen); + } + + UpdateStatus(); +} + +int CTextConsole::ReceiveNewline() +{ + int nLen = 0; + + Echo("\n"); + + if (m_nConsoleTextLen) + { + nLen = m_nConsoleTextLen; + + m_szConsoleText[ m_nConsoleTextLen ] = '\0'; + m_nConsoleTextLen = 0; + m_nCursorPosition = 0; + + // cache line in buffer, but only if it's not a duplicate of the previous line + if ((m_nInputLine == 0) || (strcmp(m_aszLineBuffer[ m_nInputLine - 1 ], m_szConsoleText))) + { + strncpy(m_aszLineBuffer[ m_nInputLine ], m_szConsoleText, MAX_CONSOLE_TEXTLEN); + m_nInputLine++; + + if (m_nInputLine > m_nTotalLines) + m_nTotalLines = m_nInputLine; + + if (m_nInputLine >= MAX_BUFFER_LINES) + m_nInputLine = 0; + + } + + m_nBrowseLine = m_nInputLine; + } + + return nLen; +} + +void CTextConsole::ReceiveBackspace() +{ + int nCount; + + if (m_nCursorPosition == 0) + { + return; + } + + m_nConsoleTextLen--; + m_nCursorPosition--; + + Echo("\b"); + + for (nCount = m_nCursorPosition; nCount < m_nConsoleTextLen; ++nCount) + { + m_szConsoleText[ nCount ] = m_szConsoleText[ nCount + 1 ]; + Echo(m_szConsoleText + nCount, 1); + } + + Echo(" "); + + nCount = m_nConsoleTextLen; + while (nCount >= m_nCursorPosition) + { + Echo("\b"); + nCount--; + } + + m_nBrowseLine = m_nInputLine; +} + +void CTextConsole::ReceiveTab() +{ + if (!m_System) + return; + + ObjectList matches; + m_szConsoleText[ m_nConsoleTextLen ] = '\0'; + m_System->GetCommandMatches(m_szConsoleText, &matches); + + if (matches.IsEmpty()) + return; + + if (matches.CountElements() == 1) + { + char *pszCmdName = (char *)matches.GetFirst(); + char *pszRest = pszCmdName + strlen(m_szConsoleText); + + if (pszRest) + { + Echo(pszRest); + strcat(m_szConsoleText, pszRest); + m_nConsoleTextLen += strlen(pszRest); + + Echo(" "); + strcat(m_szConsoleText, " "); + m_nConsoleTextLen++; + } + } + else + { + int nLongestCmd = 0; + int nCurrentColumn; + int nTotalColumns; + + char *pszCurrentCmd = (char *)matches.GetFirst(); + while (pszCurrentCmd) + { + if ((int)strlen(pszCurrentCmd) > nLongestCmd) + nLongestCmd = strlen(pszCurrentCmd); + + pszCurrentCmd = (char *)matches.GetNext(); + } + + nTotalColumns = (GetWidth() - 1) / (nLongestCmd + 1); + nCurrentColumn = 0; + + Echo("\n"); + + // Would be nice if these were sorted, but not that big a deal + pszCurrentCmd = (char *)matches.GetFirst(); + + while (pszCurrentCmd) + { + char szFormatCmd[256]; + if (++nCurrentColumn > nTotalColumns) + { + Echo("\n"); + nCurrentColumn = 1; + } + + _snprintf(szFormatCmd, sizeof(szFormatCmd), "%-*s ", nLongestCmd, pszCurrentCmd); + Echo(szFormatCmd); + + pszCurrentCmd = (char *)matches.GetNext(); + } + + Echo("\n"); + Echo(m_szConsoleText); + + // TODO: + // Tack on 'common' chars in all the matches, i.e. if I typed 'con' and all the + // matches begin with 'connect_' then print the matches but also complete the + // command up to that point at least. + } + + m_nCursorPosition = m_nConsoleTextLen; + m_nBrowseLine = m_nInputLine; +} + +void CTextConsole::ReceiveStandardChar(const char ch) +{ + int nCount; + + // If the line buffer is maxed out, ignore this char + if (m_nConsoleTextLen >= (sizeof(m_szConsoleText) - 2)) + { + return; + } + + nCount = m_nConsoleTextLen; + while (nCount > m_nCursorPosition) + { + m_szConsoleText[ nCount ] = m_szConsoleText[ nCount - 1 ]; + nCount--; + } + + m_szConsoleText[ m_nCursorPosition ] = ch; + + Echo(m_szConsoleText + m_nCursorPosition, m_nConsoleTextLen - m_nCursorPosition + 1); + + m_nConsoleTextLen++; + m_nCursorPosition++; + + nCount = m_nConsoleTextLen; + while (nCount > m_nCursorPosition) + { + Echo("\b"); + nCount--; + } + + m_nBrowseLine = m_nInputLine; +} + +void CTextConsole::ReceiveUpArrow() +{ + int nLastCommandInHistory = m_nInputLine + 1; + if (nLastCommandInHistory > m_nTotalLines) + nLastCommandInHistory = 0; + + if (m_nBrowseLine == nLastCommandInHistory) + return; + + if (m_nBrowseLine == m_nInputLine) + { + if (m_nConsoleTextLen > 0) + { + // Save off current text + strncpy(m_szSavedConsoleText, m_szConsoleText, m_nConsoleTextLen); + // No terminator, it's a raw buffer we always know the length of + } + + m_nSavedConsoleTextLen = m_nConsoleTextLen; + } + + m_nBrowseLine--; + if (m_nBrowseLine < 0) + { + m_nBrowseLine = m_nTotalLines - 1; + } + + // delete old line + while (m_nConsoleTextLen--) + { + Echo("\b \b"); + } + + // copy buffered line + Echo(m_aszLineBuffer[ m_nBrowseLine ]); + + strncpy(m_szConsoleText, m_aszLineBuffer[ m_nBrowseLine ], MAX_CONSOLE_TEXTLEN); + + m_nConsoleTextLen = strlen(m_aszLineBuffer[ m_nBrowseLine ]); + m_nCursorPosition = m_nConsoleTextLen; +} + +void CTextConsole::ReceiveDownArrow() +{ + if (m_nBrowseLine == m_nInputLine) + return; + + if (++m_nBrowseLine > m_nTotalLines) + m_nBrowseLine = 0; + + // delete old line + while (m_nConsoleTextLen--) + { + Echo("\b \b"); + } + + if (m_nBrowseLine == m_nInputLine) + { + if (m_nSavedConsoleTextLen > 0) + { + // Restore current text + strncpy(m_szConsoleText, m_szSavedConsoleText, m_nSavedConsoleTextLen); + // No terminator, it's a raw buffer we always know the length of + + Echo(m_szConsoleText, m_nSavedConsoleTextLen); + } + + m_nConsoleTextLen = m_nSavedConsoleTextLen; + } + else + { + // copy buffered line + Echo(m_aszLineBuffer[ m_nBrowseLine ]); + strncpy(m_szConsoleText, m_aszLineBuffer[ m_nBrowseLine ], MAX_CONSOLE_TEXTLEN); + m_nConsoleTextLen = strlen(m_aszLineBuffer[ m_nBrowseLine ]); + } + + m_nCursorPosition = m_nConsoleTextLen; +} + +void CTextConsole::ReceiveLeftArrow() +{ + if (m_nCursorPosition == 0) + return; + + Echo("\b"); + m_nCursorPosition--; +} + +void CTextConsole::ReceiveRightArrow() +{ + if (m_nCursorPosition == m_nConsoleTextLen) + return; + + Echo(m_szConsoleText + m_nCursorPosition, 1); + m_nCursorPosition++; +} diff --git a/rehlds/common/textconsole.h b/rehlds/common/textconsole.h new file mode 100644 index 0000000..e205f17 --- /dev/null +++ b/rehlds/common/textconsole.h @@ -0,0 +1,71 @@ +#ifndef TEXTCONSOLE_H +#define TEXTCONSOLE_H +#ifdef _WIN32 +#pragma once +#endif + +#include "IBaseSystem.h" + +#define MAX_CONSOLE_TEXTLEN 256 +#define MAX_BUFFER_LINES 30 + +class CTextConsole { +public: + virtual ~CTextConsole() {} + + virtual bool Init(IBaseSystem *system = nullptr); + virtual void ShutDown(); + virtual void Print(char *pszMsg); + + virtual void SetTitle(char *pszTitle) {} + virtual void SetStatusLine(char *pszStatus) {} + virtual void UpdateStatus() {} + + // Must be provided by children + virtual void PrintRaw(char *pszMsg, int nChars = 0) = 0; + virtual void Echo(char *pszMsg, int nChars = 0) = 0; + virtual char *GetLine() = 0; + virtual int GetWidth() = 0; + + virtual void SetVisible(bool visible); + virtual bool IsVisible(); + +protected: + char m_szConsoleText[MAX_CONSOLE_TEXTLEN]; // console text buffer + int m_nConsoleTextLen; // console textbuffer length + int m_nCursorPosition; // position in the current input line + + // Saved input data when scrolling back through command history + char m_szSavedConsoleText[MAX_CONSOLE_TEXTLEN]; // console text buffer + int m_nSavedConsoleTextLen; // console textbuffer length + + char m_aszLineBuffer[MAX_BUFFER_LINES][MAX_CONSOLE_TEXTLEN]; // command buffer last MAX_BUFFER_LINES commands + int m_nInputLine; // Current line being entered + int m_nBrowseLine; // current buffer line for up/down arrow + int m_nTotalLines; // # of nonempty lines in the buffer + + bool m_ConsoleVisible; + + IBaseSystem *m_System; + + int ReceiveNewline(); + void ReceiveBackspace(); + void ReceiveTab(); + void ReceiveStandardChar(const char ch); + void ReceiveUpArrow(); + void ReceiveDownArrow(); + void ReceiveLeftArrow(); + void ReceiveRightArrow(); +}; + +#include "SteamAppStartUp.h" + +#if defined(_WIN32) + #include "TextConsoleWin32.h" +#else + #include "TextConsoleUnix.h" +#endif // defined(_WIN32) + +void Sys_Printf(char *fmt, ...); + +#endif // TEXTCONSOLE_H diff --git a/rehlds/dedicated/build.gradle b/rehlds/dedicated/build.gradle new file mode 100644 index 0000000..47aff3a --- /dev/null +++ b/rehlds/dedicated/build.gradle @@ -0,0 +1,137 @@ +import org.doomedsociety.gradlecpp.cfg.ToolchainConfigUtils +import org.doomedsociety.gradlecpp.msvc.MsvcToolchainConfig +import org.doomedsociety.gradlecpp.gcc.GccToolchainConfig +import org.doomedsociety.gradlecpp.toolchain.icc.Icc +import org.doomedsociety.gradlecpp.toolchain.icc.IccCompilerPlugin +import org.doomedsociety.gradlecpp.GradleCppUtils +import org.gradle.nativeplatform.NativeExecutableSpec +import org.gradle.nativeplatform.NativeExecutableBinarySpec + +apply plugin: 'cpp' +apply plugin: 'windows-resources' +apply plugin: IccCompilerPlugin + +List getRcCompileTasks(NativeBinarySpec binary) +{ + def linkTask = GradleCppUtils.getLinkTask(binary) + def res = linkTask.taskDependencies.getDependencies(linkTask).findAll { Task t -> t instanceof WindowsResourceCompile } + return res as List +} + +void setupToolchain(NativeBinarySpec b) { + def cfg = rootProject.createToolchainConfig(b); + cfg.projectInclude(project, '/src', '/../engine', '/../common', '/../public', '/../public/rehlds'); + + cfg.singleDefines 'USE_BREAKPAD_HANDLER', 'DEDICATED', 'LAUNCHER_FIXES', '_CONSOLE' + + if (cfg instanceof MsvcToolchainConfig) { + cfg.compilerOptions.pchConfig = new MsvcToolchainConfig.PrecompiledHeadersConfig( + enabled: true, + pchHeader: 'precompiled.h', + pchSourceSet: 'dedicated_pch' + ); + + cfg.singleDefines('_CRT_SECURE_NO_WARNINGS'); + cfg.compilerOptions.args '/Ob2', '/Oi', '/GF', '/GR-'; + cfg.extraLibs "ws2_32.lib", "winmm.lib", "user32.lib", "advapi32.lib", "shell32.lib" + } + else if (cfg instanceof GccToolchainConfig) { + cfg.compilerOptions.pchConfig = new GccToolchainConfig.PrecompilerHeaderOptions( + enabled: true, + pchSourceSet: 'dedicated_pch' + ); + + cfg.compilerOptions.languageStandard = 'c++0x' + cfg.defines([ + '_strdup': 'strdup', + '_stricmp': 'strcasecmp', + '_strnicmp': 'strncasecmp', + '_vsnprintf': 'vsnprintf', + '_snprintf': 'snprintf', + ]); + + cfg.linkerOptions.stripSymbolTable = false; + cfg.linkerOptions.staticLibStdCpp = false; + cfg.compilerOptions.args '-Qoption,cpp,--treat_func_as_string_literal_cpp', '-fno-rtti', '-fno-exceptions' + } + + ToolchainConfigUtils.apply(project, cfg, b); +} + +model { + buildTypes { + release + } + + platforms { + x86 { + architecture "x86" + } + } + + toolChains { + visualCpp(VisualCpp) { + } + icc(Icc) { + } + } + + components { + dedicated(NativeExecutableSpec) { + targetPlatform 'x86' + baseName GradleCppUtils.windows ? 'hlds' : 'hlds_linux' + + sources { + dedicated_main(CppSourceSet) { + source { + srcDir "src" + include "**/*.cpp" + exclude "precompiled.cpp" + + if (GradleCppUtils.windows) { + exclude "sys_linux.cpp" + } else { + exclude "sys_window.cpp", "conproc.cpp" + } + } + + exportedHeaders { + srcDirs "vgui" + } + } + dedicated_pch(CppSourceSet) { + source { + srcDir "src" + include "precompiled.cpp" + } + } + + rc { + source { + srcDir "msvc" + include "dedicated.rc" + } + exportedHeaders { + srcDirs "msvc" + } + } + } + + binaries.all { + NativeExecutableBinarySpec b -> project.setupToolchain(b) + } + } + } +} + +task buildFixes { + dependsOn binaries.withType(NativeExecutableBinarySpec).matching { NativeExecutableBinarySpec blib -> + blib.buildable && blib.buildType.name == 'release' + } +} + +task buildRelease { + dependsOn binaries.withType(NativeExecutableBinarySpec).matching { NativeExecutableBinarySpec blib -> + blib.buildable && blib.buildType.name == 'release' + } +} diff --git a/rehlds/dedicated/msvc/PostBuild.bat b/rehlds/dedicated/msvc/PostBuild.bat new file mode 100644 index 0000000..8583878 --- /dev/null +++ b/rehlds/dedicated/msvc/PostBuild.bat @@ -0,0 +1,39 @@ +@echo OFF +:: +:: Post-build auto-deploy script +:: Create and fill PublishPath.txt file with path to deployment folder +:: I.e. PublishPath.txt should contain one line with a folder path +:: Call it so: +:: IF EXIST "$(ProjectDir)PostBuild.bat" (CALL "$(ProjectDir)PostBuild.bat" "$(TargetDir)" "$(TargetName)" "$(TargetExt)" "$(ProjectDir)") +:: + +SET targetDir=%~1 +SET targetName=%~2 +SET targetExt=%~3 +SET projectDir=%~4 +SET destination= + +IF NOT EXIST "%projectDir%\PublishPath.txt" ( + ECHO No deployment path specified. Create PublishPath.txt near PostBuild.bat with paths on separate lines for auto deployment. + exit /B 0 +) + +FOR /f "tokens=* delims= usebackq" %%a IN ("%projectDir%\PublishPath.txt") DO ( + ECHO Deploying to: %%a + IF NOT "%%a" == "" ( + copy /Y "%targetDir%%targetName%%targetExt%" "%%a" + IF NOT ERRORLEVEL 1 ( + IF EXIST "%targetDir%%targetName%.pdb" ( + copy /Y "%targetDir%%targetName%.pdb" "%%a" + ) + ) ELSE ( + ECHO PostBuild.bat ^(27^) : warning : Can't copy '%targetName%%targetExt%' to deploy path '%%a' + ) + ) +) + +IF "%%a" == "" ( + ECHO No deployment path specified. +) + +exit /B 0 \ No newline at end of file diff --git a/rehlds/dedicated/msvc/dedicated.rc b/rehlds/dedicated/msvc/dedicated.rc new file mode 100644 index 0000000000000000000000000000000000000000..94af5ed0d4f39e14de1cdcf0371ee6db47c72ea9 GIT binary patch literal 3220 zcmds(+l~@J5Qgj8#CK@f8#fy8@C2MBM0Pa`Y&0R+5D+Ed6b3XVKD+MschkGWGJ{#X znV3#b^;B1NSJ(N^?}}B-*@aDQWNTYm!kpL!+%>qwwl=gGe|3AoaQ4-{*n+tRzX6WH zt%2g(vKp~={OPo*t)MB}Q+vWK6%WtAF4&zxJ@&Mv1OBpovy!J4=b5LQcs`w7wU5@d zp7pI^ZTnz1c4bYl4c3>|vCm+-d@rm9ro%ekFBytAqI3~DYoV8iI-a1VrOkQ&2JJ`> zJzo8ZZ<|-wd8hQz1pkKl&?0}g!4I$Bz_+je;%O{O6ue`LuH;D*-hG;HM9s%@iv^=} z4nN-#SqYXJ@@8kx_b}%fc281KH1!&Kwiiuf&Zycu{3t|Gmx$?eY8_9y!Jj$|o%@U7 zayfgPafR3Fj6U&p?r-gYy#(6_w!CLXOr-1LyP|o?*AZ&3Q>e~3C|8UbR3p|KprmpBKP^|NXLD%OJ0ws9xs?eeiK?aK3Eb*e z)UhP?bGJJOh6*T&+vKS>8BX1TV(Hk3bjN7Yd!;+s%ez(Z@qFJpHK!NLx6;x?FN#ok oVSPx# literal 0 HcmV?d00001 diff --git a/rehlds/dedicated/msvc/dedicated.sln b/rehlds/dedicated/msvc/dedicated.sln new file mode 100644 index 0000000..bd19320 --- /dev/null +++ b/rehlds/dedicated/msvc/dedicated.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.25420.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "dedicated", "dedicated.vcxproj", "{D49883F3-5C5C-4B9F-B9C7-B31518F228D4}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {D49883F3-5C5C-4B9F-B9C7-B31518F228D4}.Debug|Win32.ActiveCfg = Debug|Win32 + {D49883F3-5C5C-4B9F-B9C7-B31518F228D4}.Debug|Win32.Build.0 = Debug|Win32 + {D49883F3-5C5C-4B9F-B9C7-B31518F228D4}.Release|Win32.ActiveCfg = Release|Win32 + {D49883F3-5C5C-4B9F-B9C7-B31518F228D4}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/rehlds/dedicated/msvc/dedicated.vcxproj b/rehlds/dedicated/msvc/dedicated.vcxproj new file mode 100644 index 0000000..c0e2592 --- /dev/null +++ b/rehlds/dedicated/msvc/dedicated.vcxproj @@ -0,0 +1,178 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + {D49883F3-5C5C-4B9F-B9C7-B31518F228D4} + Win32Proj + HLDSLauncher + dedicated + 8.1 + + + + Application + true + v140_xp + MultiByte + + + Application + false + v140_xp + true + MultiByte + + + + + + + + + + + + + true + hlds + + + false + hlds + + + + Use + Level3 + Disabled + WIN32;LAUNCHER_FIXES;_DEBUG;DEDICATED;_CRT_SECURE_NO_WARNINGS;USE_BREAKPAD_HANDLER;_CONSOLE;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + true + MultiThreadedDebug + $(ProjectDir)..\;$(ProjectDir)..\src\;$(ProjectDir)..\..\common;$(ProjectDir)..\..\engine;$(ProjectDir)..\..\public;$(ProjectDir)..\..\public\rehlds;%(AdditionalIncludeDirectories) + precompiled.h + + + Windows + true + ws2_32.lib;winmm.lib;%(AdditionalDependencies) + + + IF EXIST "$(ProjectDir)PreBuild.bat" (CALL "$(ProjectDir)PreBuild.bat" "$(ProjectDir)..\version\" "$(ProjectDir)..\") + + + Setup version from Git revision + + + IF EXIST "$(ProjectDir)PostBuild.bat" (CALL "$(ProjectDir)PostBuild.bat" "$(TargetDir)" "$(TargetName)" "$(TargetExt)" "$(ProjectDir)") + + + Automatic deployment script + + + echo Empty Action + + + Force build to run Pre-Build event + + + git.always.run + + + git.always.run + + + + + Level3 + Use + Full + true + true + WIN32;LAUNCHER_FIXES;NDEBUG;DEDICATED;_CRT_SECURE_NO_WARNINGS;USE_BREAKPAD_HANDLER;_CONSOLE;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + $(ProjectDir)..\;$(ProjectDir)..\..\common;$(ProjectDir)..\..\engine;$(ProjectDir)..\..\public;$(ProjectDir)..\..\public\rehlds;%(AdditionalIncludeDirectories) + MultiThreaded + false + StreamingSIMDExtensions2 + + + AnySuitable + true + true + precompiled.h + + + Windows + true + true + true + ws2_32.lib;winmm.lib;%(AdditionalDependencies) + + + IF EXIST "$(ProjectDir)PreBuild.bat" (CALL "$(ProjectDir)PreBuild.bat" "$(ProjectDir)..\version\" "$(ProjectDir)..\") + + + Setup version from Git revision + + + IF EXIST "$(ProjectDir)PostBuild.bat" (CALL "$(ProjectDir)PostBuild.bat" "$(TargetDir)" "$(TargetName)" "$(TargetExt)" "$(ProjectDir)") + + + Automatic deployment script + + + echo Empty Action + + + subversion.always.run + + + subversion.always.run + Force build to run Pre-Build event + + + + + + + + + + + + + + + + + + + + + Create + Create + + + + + true + true + + + + + + + + + + + \ No newline at end of file diff --git a/rehlds/dedicated/msvc/dedicated.vcxproj.filters b/rehlds/dedicated/msvc/dedicated.vcxproj.filters new file mode 100644 index 0000000..a6dcaad --- /dev/null +++ b/rehlds/dedicated/msvc/dedicated.vcxproj.filters @@ -0,0 +1,71 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {711de26d-f116-48c0-96c0-5d4189574ad1} + + + + + + src + + + src + + + src + + + src + + + src + + + src\vgui + + + + + src + + + src + + + src + + + src + + + src + + + src + + + src + + + src + + + src\vgui + + + src + + + + + + + + + \ No newline at end of file diff --git a/rehlds/dedicated/msvc/icon.ico b/rehlds/dedicated/msvc/icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..8b080d2f1fef5001359a45366addbd35ce0df840 GIT binary patch literal 117045 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 he%@`Y9jLouH=DFuY{#|z7= (MAX_PATH - 1)) + break; +#endif + } + + *pOut = '\0'; + + // Skip the space after the file name + if (*pSrc) + pSrc++; + + // Now read in parameters from file + FILE *fp = fopen(szFileName, "r"); + if (fp) + { + char c; + c = (char)fgetc(fp); + while (c != EOF) + { + // Turn return characters into spaces + if (c == '\n') + c = ' '; + + *pDst++ = c; + +#if 0 + // Don't go past the end, and allow for our terminating space character AND a terminating null character. + if ((pDst - pDestStart) >= (maxDestLen - 2)) + break; +#endif + + // Get the next character, if there are more + c = (char)fgetc(fp); + } + + // Add a terminating space character + *pDst++ = ' '; + + fclose(fp); + } + else + { + printf("Parameter file '%s' not found, skipping...", szFileName); + } +} + +// Purpose: Create a command line from the passed in string +// Note that if you pass in a @filename, then the routine will read settings from a file instead of the command line +void CCommandLine::CreateCmdLine(const char *commandline) +{ + if (m_pszCmdLine) + { + delete[] m_pszCmdLine; + m_pszCmdLine = nullptr; + } + + char szFull[4096]; + + char *pDst = szFull; + const char *pSrc = commandline; + + bool allowAtSign = true; + + while (*pSrc) + { + if (*pSrc == '@') + { + if (allowAtSign) + { + LoadParametersFromFile(pSrc, pDst, sizeof(szFull) - (pDst - szFull)); + continue; + } + } + + allowAtSign = isspace(*pSrc) != 0; + +#if 0 + // Don't go past the end. + if ((pDst - szFull) >= (sizeof(szFull) - 1)) + break; +#endif + *pDst++ = *pSrc++; + } + + *pDst = '\0'; + + int len = strlen(szFull) + 1; + m_pszCmdLine = new char[len]; + memcpy(m_pszCmdLine, szFull, len); +} + +// Purpose: Remove specified string ( and any args attached to it ) from command line +void CCommandLine::RemoveParm(const char *pszParm) +{ + if (!m_pszCmdLine) + return; + + if (!pszParm || *pszParm == '\0') + return; + + // Search for first occurrence of pszParm + char *p, *found; + char *pnextparam; + int n; + int curlen; + + p = m_pszCmdLine; + while (*p) + { + curlen = strlen(p); + found = strstr(p, pszParm); + + if (!found) + break; + + pnextparam = found + 1; + while (pnextparam && *pnextparam && (*pnextparam != '-') && (*pnextparam != '+')) + pnextparam++; + + if (pnextparam && *pnextparam) + { + // We are either at the end of the string, or at the next param. Just chop out the current param. + // # of characters after this param. + n = curlen - (pnextparam - p); + + memcpy(found, pnextparam, n); + found[n] = '\0'; + } + else + { + // Clear out rest of string. + n = pnextparam - found; + memset(found, 0, n); + } + } + + // Strip and trailing ' ' characters left over. + while (1) + { + int curpos = strlen(m_pszCmdLine); + if (curpos == 0 || m_pszCmdLine[ curpos - 1 ] != ' ') + break; + + m_pszCmdLine[curpos - 1] = '\0'; + } +} + +// Purpose: Append parameter and argument values to command line +void CCommandLine::AppendParm(const char *pszParm, const char *pszValues) +{ + int nNewLength = 0; + char *pCmdString; + + // Parameter. + nNewLength = strlen(pszParm); + + // Values + leading space character. + if (pszValues) + nNewLength += strlen(pszValues) + 1; + + // Terminal 0; + nNewLength++; + + if (!m_pszCmdLine) + { + m_pszCmdLine = new char[ nNewLength ]; + strcpy(m_pszCmdLine, pszParm); + if (pszValues) + { + strcat(m_pszCmdLine, " "); + strcat(m_pszCmdLine, pszValues); + } + + return; + } + + // Remove any remnants from the current Cmd Line. + RemoveParm(pszParm); + + nNewLength += strlen(m_pszCmdLine) + 1 + 1; + + pCmdString = new char[ nNewLength ]; + memset(pCmdString, 0, nNewLength); + + strcpy(pCmdString, m_pszCmdLine); // Copy old command line. + strcat(pCmdString, " "); // Put in a space + strcat(pCmdString, pszParm); + + if (pszValues) + { + strcat(pCmdString, " "); + strcat(pCmdString, pszValues); + } + + // Kill off the old one + delete[] m_pszCmdLine; + + // Point at the new command line. + m_pszCmdLine = pCmdString; +} + +void CCommandLine::SetParm(const char *pszParm, const char *pszValues) +{ + RemoveParm(pszParm); + AppendParm(pszParm, pszValues); +} + +void CCommandLine::SetParm(const char *pszParm, int iValue) +{ + char buf[64]; + _snprintf(buf, sizeof(buf), "%d", iValue); + SetParm(pszParm, buf); +} + +// Purpose: Search for the parameter in the current commandline +const char *CCommandLine::CheckParm(const char *psz, char **ppszValue) const +{ + static char sz[128] = ""; + + if (!m_pszCmdLine) + return nullptr; + + char *pret = strstr(m_pszCmdLine, psz); + if (!pret || !ppszValue) + return nullptr; + + *ppszValue = nullptr; + + // find the next whitespace + char *p1 = pret; + do { + ++p1; + } while (*p1 != ' ' && *p1); + + int i = 0; + char *p2 = p1 + 1; + + do { + if (p2[i] == '\0' || p2[i] == ' ') + break; + + sz[i++] = p2[i]; + } + while (i < sizeof(sz)); + + sz[i] = '\0'; + *ppszValue = sz; + return pret; +} + +const char *CCommandLine::GetCmdLine() const +{ + return m_pszCmdLine; +} diff --git a/rehlds/dedicated/src/conproc.cpp b/rehlds/dedicated/src/conproc.cpp new file mode 100644 index 0000000..962bd32 --- /dev/null +++ b/rehlds/dedicated/src/conproc.cpp @@ -0,0 +1,312 @@ +#include "precompiled.h" + +static HANDLE heventDone; +static HANDLE hfileBuffer; +static HANDLE heventChildSend; +static HANDLE heventParentSend; +static HANDLE hStdout; +static HANDLE hStdin; + +BOOL SetConsoleCXCY(HANDLE hStdout, int cx, int cy) +{ + CONSOLE_SCREEN_BUFFER_INFO info; + COORD coordMax; + + coordMax = GetLargestConsoleWindowSize(hStdout); + + if (cy > coordMax.Y) + cy = coordMax.Y; + + if (cx > coordMax.X) + cx = coordMax.X; + + if (!GetConsoleScreenBufferInfo(hStdout, &info)) + return FALSE; + + // height + info.srWindow.Left = 0; + info.srWindow.Right = info.dwSize.X - 1; + info.srWindow.Top = 0; + info.srWindow.Bottom = cy - 1; + + if (cy < info.dwSize.Y) + { + if (!SetConsoleWindowInfo(hStdout, TRUE, &info.srWindow)) + return FALSE; + + info.dwSize.Y = cy; + + if (!SetConsoleScreenBufferSize(hStdout, info.dwSize)) + return FALSE; + } + else if (cy > info.dwSize.Y) + { + info.dwSize.Y = cy; + + if (!SetConsoleScreenBufferSize(hStdout, info.dwSize)) + return FALSE; + + if (!SetConsoleWindowInfo(hStdout, TRUE, &info.srWindow)) + return FALSE; + } + + if (!GetConsoleScreenBufferInfo(hStdout, &info)) + return FALSE; + + // width + info.srWindow.Left = 0; + info.srWindow.Right = cx - 1; + info.srWindow.Top = 0; + info.srWindow.Bottom = info.dwSize.Y - 1; + + if (cx < info.dwSize.X) + { + if (!SetConsoleWindowInfo(hStdout, TRUE, &info.srWindow)) + return FALSE; + + info.dwSize.X = cx; + + if (!SetConsoleScreenBufferSize(hStdout, info.dwSize)) + return FALSE; + } + else if (cx > info.dwSize.X) + { + info.dwSize.X = cx; + + if (!SetConsoleScreenBufferSize(hStdout, info.dwSize)) + return FALSE; + + if (!SetConsoleWindowInfo(hStdout, TRUE, &info.srWindow)) + return FALSE; + } + + return TRUE; +} + +LPVOID GetMappedBuffer(HANDLE hfileBuffer) +{ + return MapViewOfFile(hfileBuffer, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0); +} + +void ReleaseMappedBuffer(LPVOID pBuffer) +{ + UnmapViewOfFile(pBuffer); +} + +BOOL GetScreenBufferLines(int *piLines) +{ + CONSOLE_SCREEN_BUFFER_INFO info; + + BOOL bRet = GetConsoleScreenBufferInfo(hStdout, &info); + if (bRet) + *piLines = info.dwSize.Y; + + return bRet; +} + +BOOL SetScreenBufferLines(int iLines) +{ + return SetConsoleCXCY(hStdout, 80, iLines); +} + +BOOL ReadText(LPTSTR pszText, int iBeginLine, int iEndLine) +{ + COORD coord; + DWORD dwRead; + BOOL bRet; + + coord.X = 0; + coord.Y = iBeginLine; + + bRet = ReadConsoleOutputCharacter(hStdout, pszText, 80 * (iEndLine - iBeginLine + 1), coord, &dwRead); + + // Make sure it's null terminated. + if (bRet) + pszText[dwRead] = '\0'; + + return bRet; +} + +int CharToCode(char c) +{ + char upper = toupper(c); + switch (c) + { + case 13: + return 28; + default: + break; + } + + if (isalpha(c)) + return (30 + upper - 65); + + if (isdigit(c)) + return (1 + upper - 47); + + return c; +} + +BOOL WriteText(LPCTSTR szText) +{ + DWORD dwWritten; + INPUT_RECORD rec; + char upper, *sz; + + sz = (LPTSTR)szText; + + while (*sz) + { + // 13 is the code for a carriage return (\n) instead of 10. + if (*sz == '\n') + *sz = '\r'; + + upper = toupper(*sz); + + rec.EventType = KEY_EVENT; + rec.Event.KeyEvent.bKeyDown = TRUE; + rec.Event.KeyEvent.wRepeatCount = 1; + rec.Event.KeyEvent.wVirtualKeyCode = upper; + rec.Event.KeyEvent.wVirtualScanCode = CharToCode(*sz); + rec.Event.KeyEvent.uChar.AsciiChar = *sz; + rec.Event.KeyEvent.uChar.UnicodeChar = *sz; + rec.Event.KeyEvent.dwControlKeyState = isupper(*sz) ? 0x80 : 0x0; + + WriteConsoleInput(hStdin, &rec, 1, &dwWritten); + rec.Event.KeyEvent.bKeyDown = FALSE; + WriteConsoleInput(hStdin, &rec, 1, &dwWritten); + sz++; + } + + return TRUE; +} + +unsigned _stdcall RequestProc(void *arg) +{ + int *pBuffer; + DWORD dwRet; + HANDLE heventWait[2]; + int iBeginLine, iEndLine; + + heventWait[0] = heventParentSend; + heventWait[1] = heventDone; + + while (1) + { + dwRet = WaitForMultipleObjects(2, heventWait, FALSE, INFINITE); + + // heventDone fired, so we're exiting. + if (dwRet == WAIT_OBJECT_0 + 1) + break; + + pBuffer = (int *)GetMappedBuffer(hfileBuffer); + + // hfileBuffer is invalid. Just leave. + if (!pBuffer) + { + sys->Printf("Request Proc: Invalid -HFILE handle\n"); + break; + } + + switch (pBuffer[0]) + { + case CCOM_WRITE_TEXT: + pBuffer[0] = WriteText((LPCTSTR)(pBuffer + 1)); + break; + + case CCOM_GET_TEXT: + iBeginLine = pBuffer[1]; + iEndLine = pBuffer[2]; + pBuffer[0] = ReadText((LPTSTR)(pBuffer + 1), iBeginLine, iEndLine); + break; + + case CCOM_GET_SCR_LINES: + pBuffer[0] = GetScreenBufferLines(&pBuffer[1]); + break; + + case CCOM_SET_SCR_LINES: + pBuffer[0] = SetScreenBufferLines(pBuffer[1]); + break; + } + + ReleaseMappedBuffer(pBuffer); + SetEvent(heventChildSend); + } + + _endthreadex(0); + return 0; +} + +void DeinitConProc() +{ + if (heventDone) + { + SetEvent(heventDone); + } +} + +void InitConProc() +{ + unsigned threadAddr; + HANDLE hFile = (HANDLE)0; + HANDLE heventParent = (HANDLE)0; + HANDLE heventChild = (HANDLE)0; + int WantHeight = 50; + char *p; + + // give external front ends a chance to hook into the console + if (CommandLine()->CheckParm("-HFILE", &p) && p) + { + hFile = (HANDLE)atoi(p); + } + + if (CommandLine()->CheckParm("-HPARENT", &p) && p) + { + heventParent = (HANDLE)atoi(p); + } + + if (CommandLine()->CheckParm("-HCHILD", &p) && p) + { + heventChild = (HANDLE)atoi(p); + } + + // ignore if we don't have all the events. + if (!hFile || !heventParent || !heventChild) + { + //sys->Printf ("\n\nNo external front end present.\n"); + return; + } + + sys->Printf("\n\nInitConProc: Setting up external control.\n"); + + hfileBuffer = hFile; + heventParentSend = heventParent; + heventChildSend = heventChild; + + // So we'll know when to go away. + heventDone = CreateEvent(NULL, FALSE, FALSE, NULL); + if (!heventDone) + { + sys->Printf("InitConProc: Couldn't create heventDone\n"); + return; + } + + if (!_beginthreadex(NULL, 0, RequestProc, NULL, 0, &threadAddr)) + { + CloseHandle(heventDone); + sys->Printf("InitConProc: Couldn't create third party thread\n"); + return; + } + + // save off the input/output handles. + hStdout = GetStdHandle(STD_OUTPUT_HANDLE); + hStdin = GetStdHandle(STD_INPUT_HANDLE); + + if (CommandLine()->CheckParm("-conheight", &p) && p) + { + WantHeight = atoi(p); + } + + // Force 80 character width, at least 25 character height + SetConsoleCXCY(hStdout, 80, WantHeight); +} diff --git a/rehlds/dedicated/src/conproc.h b/rehlds/dedicated/src/conproc.h new file mode 100644 index 0000000..c0bd3ff --- /dev/null +++ b/rehlds/dedicated/src/conproc.h @@ -0,0 +1,17 @@ +#ifndef CONPROC_H +#define CONPROC_H +#ifdef _WIN32 +#pragma once +#endif + +#include + +#define CCOM_WRITE_TEXT 0x2 // Param1 : Text +#define CCOM_GET_TEXT 0x3 // Param1 : Begin line, Param2 : End line +#define CCOM_GET_SCR_LINES 0x4 // No params +#define CCOM_SET_SCR_LINES 0x5 // Param1 : Number of lines + +void InitConProc(); +void DeinitConProc(); + +#endif // CONPROC_H diff --git a/rehlds/dedicated/src/dbg.cpp b/rehlds/dedicated/src/dbg.cpp new file mode 100644 index 0000000..f0b6b43 --- /dev/null +++ b/rehlds/dedicated/src/dbg.cpp @@ -0,0 +1,171 @@ +#include "precompiled.h" + +class CPerformanceCounter +{ +public: + CPerformanceCounter(); + + void InitializePerformanceCounter(); + double GetCurTime(); + +private: + int m_iLowShift; + double m_flPerfCounterFreq; + double m_flCurrentTime; + double m_flLastCurrentTime; +}; + +inline CPerformanceCounter::CPerformanceCounter() : + m_iLowShift(0), + m_flPerfCounterFreq(0), + m_flCurrentTime(0), + m_flLastCurrentTime(0) +{ + InitializePerformanceCounter(); +} + +inline void CPerformanceCounter::InitializePerformanceCounter() +{ +#ifdef _WIN32 + + LARGE_INTEGER performanceFreq; + QueryPerformanceFrequency(&performanceFreq); + + // get 32 out of the 64 time bits such that we have around + // 1 microsecond resolution + unsigned int lowpart, highpart; + lowpart = (unsigned int)performanceFreq.LowPart; + highpart = (unsigned int)performanceFreq.HighPart; + m_iLowShift = 0; + + while (highpart || (lowpart > 2000000.0)) + { + m_iLowShift++; + lowpart >>= 1; + lowpart |= (highpart & 1) << 31; + highpart >>= 1; + } + + m_flPerfCounterFreq = 1.0 / (double)lowpart; + +#endif // _WIN32 +} + +inline double CPerformanceCounter::GetCurTime() +{ +#ifdef _WIN32 + + static int sametimecount; + static unsigned int oldtime; + static int first = 1; + LARGE_INTEGER PerformanceCount; + unsigned int temp, t2; + double time; + + QueryPerformanceCounter(&PerformanceCount); + if (m_iLowShift == 0) + { + temp = (unsigned int)PerformanceCount.LowPart; + } + else + { + temp = ((unsigned int)PerformanceCount.LowPart >> m_iLowShift) | + ((unsigned int)PerformanceCount.HighPart << (32 - m_iLowShift)); + } + + if (first) + { + oldtime = temp; + first = 0; + } + else + { + // check for turnover or backward time + if ((temp <= oldtime) && ((oldtime - temp) < 0x10000000)) + { + // so we can't get stuck + oldtime = temp; + } + else + { + t2 = temp - oldtime; + + time = (double)t2 * m_flPerfCounterFreq; + oldtime = temp; + + m_flCurrentTime += time; + + if (m_flCurrentTime == m_flLastCurrentTime) + { + if (++sametimecount > 100000) + { + m_flCurrentTime += 1.0; + sametimecount = 0; + } + } + else + { + sametimecount = 0; + } + + m_flLastCurrentTime = m_flCurrentTime; + } + } + + return m_flCurrentTime; + +#else // _WIN32 + + struct timeval tp; + static int secbase = 0; + + gettimeofday(&tp, NULL); + + if (!secbase) + { + secbase = tp.tv_sec; + return (tp.tv_usec / 1000000.0); + } + + return ((tp.tv_sec - secbase) + tp.tv_usec / 1000000.0); + +#endif // _WIN32 +} + +void _LogFunctionTrace(const char *pFunctionName, const char *param) +{ + //{ + // char *entry; + //} +} + +double _StartFunctionTimer() +{ + //CPerformanceCounter::GetCurTime(); + return 0; +} + +void _LogFunctionTraceMaxTime(const char *pFunctionName, double startTime, double maxTime) +{ + //{ + // double timeDiff; + // CPerformanceCounter::GetCurTime(); + // _LogFunctionTrace(const char *pFunctionName, const char *param); + //} +} + +void ClearErrorLogs() +{ +} + +void Error(const char *pMsg, ...) +{ + //{ + // char logName; + // FILE *f; + // va_list args; + // { + // int i; + // } + //} +} diff --git a/rehlds/dedicated/src/dbg.h b/rehlds/dedicated/src/dbg.h new file mode 100644 index 0000000..4256ca5 --- /dev/null +++ b/rehlds/dedicated/src/dbg.h @@ -0,0 +1,13 @@ +#ifndef DBG_H +#define DBG_H +#ifdef _WIN32 +#pragma once +#endif + +void _LogFunctionTrace(const char *pFunctionName, const char *param); +double _StartFunctionTimer(); +void _LogFunctionTraceMaxTime(const char *pFunctionName, double startTime, double maxTime); +void ClearErrorLogs(); +void Error(const char *pMsg, ...); + +#endif // DBG_H diff --git a/rehlds/dedicated/src/dedicated.h b/rehlds/dedicated/src/dedicated.h new file mode 100644 index 0000000..37067f5 --- /dev/null +++ b/rehlds/dedicated/src/dedicated.h @@ -0,0 +1,13 @@ +#ifndef DEDICATED_H +#define DEDICATED_H +#ifdef _WIN32 +#pragma once +#endif + +typedef void (*NET_Sleep_t)(); +typedef void (*SleepType)(int msec); + +extern bool g_bVGui; +extern IDedicatedServerAPI *engineAPI; + +#endif // DEDICATED_H diff --git a/rehlds/dedicated/src/dedicated_exports.cpp b/rehlds/dedicated/src/dedicated_exports.cpp new file mode 100644 index 0000000..956fb15 --- /dev/null +++ b/rehlds/dedicated/src/dedicated_exports.cpp @@ -0,0 +1,13 @@ +#include "precompiled.h" + +class CDedicatedExports: IDedicatedExports { +public: + void Sys_Printf(char *text); +}; + +EXPOSE_SINGLE_INTERFACE(CDedicatedExports, IDedicatedExports, VENGINE_DEDICATEDEXPORTS_API_VERSION); + +void CDedicatedExports::Sys_Printf(char *text) +{ + ::Sys_Printf_Safe(text); +} diff --git a/rehlds/dedicated/src/icommandline.h b/rehlds/dedicated/src/icommandline.h new file mode 100644 index 0000000..3e65189 --- /dev/null +++ b/rehlds/dedicated/src/icommandline.h @@ -0,0 +1,25 @@ +#ifndef ICOMMANDLINE_H +#define ICOMMANDLINE_H +#ifdef _WIN32 +#pragma once +#endif + +// Interface to engine command line +class ICommandLine { +public: + virtual void CreateCmdLine(const char *commandline) = 0; + virtual void CreateCmdLine(int argc, const char **argv) = 0; + virtual const char *GetCmdLine() const = 0; + + // Check whether a particular parameter exists + virtual const char *CheckParm(const char *psz, char **ppszValue = nullptr) const = 0; + virtual void RemoveParm(const char *pszParm) = 0; + virtual void AppendParm(const char *pszParm, const char *pszValues) = 0; + + virtual void SetParm(const char *pszParm, const char *pszValues) = 0; + virtual void SetParm(const char *pszParm, int iValue) = 0; +}; + +ICommandLine *CommandLine(); + +#endif // ICOMMANDLINE_H diff --git a/rehlds/dedicated/src/isys.h b/rehlds/dedicated/src/isys.h new file mode 100644 index 0000000..1e67bcc --- /dev/null +++ b/rehlds/dedicated/src/isys.h @@ -0,0 +1,31 @@ +#ifndef ISYS_H +#define ISYS_H +#ifdef _WIN32 +#pragma once +#endif + +class ISys { +public: + virtual ~ISys() {} + + virtual void Sleep(int msec) = 0; + virtual bool GetExecutableName(char *out) = 0; + virtual void ErrorMessage(int level, const char *msg) = 0; + + virtual void WriteStatusText(char *szText) = 0; + virtual void UpdateStatus(int force) = 0; + + virtual long LoadLibrary(char *lib) = 0; + virtual void FreeLibrary(long library) = 0; + + virtual bool CreateConsoleWindow(void) = 0; + virtual void DestroyConsoleWindow(void) = 0; + + virtual void ConsoleOutput(char *string) = 0; + virtual char *ConsoleInput(void) = 0; + virtual void Printf(char *fmt, ...) = 0; +}; + +extern ISys *sys; + +#endif // ISYS_H diff --git a/rehlds/dedicated/src/precompiled.cpp b/rehlds/dedicated/src/precompiled.cpp new file mode 100644 index 0000000..5f656a4 --- /dev/null +++ b/rehlds/dedicated/src/precompiled.cpp @@ -0,0 +1 @@ +#include "precompiled.h" diff --git a/rehlds/dedicated/src/precompiled.h b/rehlds/dedicated/src/precompiled.h new file mode 100644 index 0000000..27a19de --- /dev/null +++ b/rehlds/dedicated/src/precompiled.h @@ -0,0 +1,21 @@ +#pragma once + +#include "osconfig.h" +#include "archtypes.h" +#include "mathlib.h" +#include "FileSystem.h" + +#include "common.h" + +#include "engine_hlds_api.h" +#include "idedicatedexports.h" +#include "icommandline.h" + +#include "sys_ded.h" +#include "icommandline.h" +#include "textconsole.h" +#include "vgui/vguihelpers.h" + +#ifdef _WIN32 + #include "conproc.h" +#endif // _WIN32 diff --git a/rehlds/dedicated/src/public_amalgamation.cpp b/rehlds/dedicated/src/public_amalgamation.cpp new file mode 100644 index 0000000..1d3ef03 --- /dev/null +++ b/rehlds/dedicated/src/public_amalgamation.cpp @@ -0,0 +1,11 @@ +#include "precompiled.h" + +#include "mem.cpp" +#include "interface.cpp" +#include "SteamAppStartUp.cpp" + +#include "ObjectList.cpp" + +#include "textconsole.cpp" +#include "TextConsoleUnix.cpp" +#include "TextConsoleWin32.cpp" diff --git a/rehlds/dedicated/src/sys_ded.cpp b/rehlds/dedicated/src/sys_ded.cpp new file mode 100644 index 0000000..0b96f2d --- /dev/null +++ b/rehlds/dedicated/src/sys_ded.cpp @@ -0,0 +1,201 @@ +#include "precompiled.h" + +long hDLLThirdParty = 0L; + +bool g_bVGui = false; +char *gpszCvars = nullptr; +bool gbAppHasBeenTerminated = false; + +CSysModule *g_pEngineModule = nullptr; +IDedicatedServerAPI *engineAPI = nullptr; + +IFileSystem *g_pFileSystemInterface = nullptr; +CSysModule *g_pFileSystemModule = nullptr; + +CreateInterfaceFn g_FilesystemFactoryFn; + +void Sys_Printf_Safe(char *text) +{ + if (sys) + { + sys->Printf("%s", text); + } +} + +void Sys_Printf(char *fmt, ...) +{ + // Dump text to debugging console. + va_list argptr; + char szText[1024]; + + va_start(argptr, fmt); + _vsnprintf(szText, sizeof(szText), fmt, argptr); + va_end(argptr); + + // Get Current text and append it. + if (g_bVGui) + { + VGUIPrintf(szText); + } + else + { + console.Print(szText); + } +} + +void Load3rdParty() +{ + // Only do this if the server operator wants the support. + // ( In case of malicious code, too ) + if (CommandLine()->CheckParm("-usegh")) + { + hDLLThirdParty = sys->LoadLibrary("ghostinj.dll"); + } +} + +void ProcessConsoleInput() +{ + if (!engineAPI) + return; + + char *inputLine = console.GetLine(); + if (inputLine) + { + char szBuf[256]; + _snprintf(szBuf, sizeof(szBuf), "%s\n", inputLine); + engineAPI->AddConsoleText(szBuf); + } +} + +char *UTIL_GetBaseDir() +{ + return "."; +} + +// Server Frame +int RunServer() +{ +#ifdef _WIN32 + // TODO: finish me! + /*if (g_bVGui) + { + vgui::ivgui()->SetSleep(0); + }*/ +#endif + + while (true) + { + if (gbAppHasBeenTerminated) + break; + + CreateInterfaceFn engineFactory = Sys_GetFactory(g_pEngineModule); + RunVGUIFrame(); + + if (engineFactory) + engineAPI = (IDedicatedServerAPI *)engineFactory(VENGINE_HLDS_API_VERSION, NULL); + + RunVGUIFrame(); + if (!engineAPI) + return -1; + + if (!engineAPI->Init(UTIL_GetBaseDir(), (char *)CommandLine()->GetCmdLine(), Sys_GetFactoryThis(), g_FilesystemFactoryFn)) { + return -1; + } + + RunVGUIFrame(); + + // TODO: finish me! + /*if (g_bVGui) + { + g_pFileSystemInterface->AddSearchPath("platform", "PLATFORM"); + + // find our configuration directory + char szConfigDir[512]; + const char *steamPath = getenv("SteamInstallPath"); + if (steamPath) + { + // put the config dir directly under steam + _snprintf(szConfigDir, sizeof(szConfigDir), "%s/config", steamPath); + } + else + { + // we're not running steam, so just put the config dir under the platform + strncpy(szConfigDir, "platform/config", sizeof(szConfigDir)); + } + }*/ + + RunVGUIFrame(); + + if (gpszCvars) { + engineAPI->AddConsoleText(gpszCvars); + } + + VGUIFinishedConfig(); + RunVGUIFrame(); + + bool bDone = false; + while (1) + { + if (bDone) + break; + + // Running really fast, yield some time to other apps + sys->Sleep(1); + +#ifdef _WIN32 + MSG msg; + while (PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)) + { + if (!GetMessage(&msg, NULL, 0, 0)) + { + bDone = true; + break; + } + + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + if (gbAppHasBeenTerminated) + break; + + + if (g_bVGui) + { + RunVGUIFrame(); + } + else +#endif // _WIN32 + { + ProcessConsoleInput(); + } + + if (!engineAPI->RunFrame()) + { + bDone = true; + } + + sys->UpdateStatus(0 /* don't force */); + } + + if (g_bVGui) + { + RunVGUIFrame(); + StopVGUI(); + } + else + { + sys->DestroyConsoleWindow(); + console.ShutDown(); + } + + auto iret = engineAPI->Shutdown(); + Sys_UnloadModule(g_pEngineModule); + VGUIFinishedConfig(); + + if (iret == DLL_CLOSE) + return iret; + } + + return 0; +} diff --git a/rehlds/dedicated/src/sys_ded.h b/rehlds/dedicated/src/sys_ded.h new file mode 100644 index 0000000..425089d --- /dev/null +++ b/rehlds/dedicated/src/sys_ded.h @@ -0,0 +1,33 @@ +#ifndef SYS_DED_H +#define SYS_DED_H +#ifdef _WIN32 +#pragma once +#endif + +#include "isys.h" +#include "dedicated.h" +#include "dll_state.h" + +#if !defined(_WIN32) +#include +#endif // !defined(_WIN32) + +int RunServer(); +void Load3rdParty(); +void Sys_Printf_Safe(char *text); +void Sys_Printf(char *fmt, ...); +void _log(const char *fmt, ...); + +extern long hDLLThirdParty; +extern char *gpszCvars; +extern bool gbAppHasBeenTerminated; +extern IFileSystem *g_pFileSystemInterface; +extern CSysModule *g_pEngineModule; +extern CSysModule *g_pFileSystemModule; +extern CreateInterfaceFn g_FilesystemFactoryFn; + +#ifdef _WIN32 +BOOL WINAPI MyHandlerRoutine(DWORD CtrlType); +#endif // _WIN32 + +#endif // SYS_DED_H diff --git a/rehlds/dedicated/src/sys_linux.cpp b/rehlds/dedicated/src/sys_linux.cpp new file mode 100644 index 0000000..e278730 --- /dev/null +++ b/rehlds/dedicated/src/sys_linux.cpp @@ -0,0 +1,334 @@ +#include "precompiled.h" + +class CSys: public ISys { +public: + virtual ~CSys(); + + void Sleep(int msec); + bool GetExecutableName(char *out); + NORETURN void ErrorMessage(int level, const char *msg); + + void WriteStatusText(char *szText); + void UpdateStatus(int force); + + long LoadLibrary(char *lib); + void FreeLibrary(long library); + + bool CreateConsoleWindow(); + void DestroyConsoleWindow(); + + void ConsoleOutput(char *string); + char *ConsoleInput(); + void Printf(char *fmt, ...); +}; + +CSys g_Sys; +ISys *sys = &g_Sys; +char g_szEXEName[MAX_PATH]; + +SleepType Sys_Sleep; +NET_Sleep_t NET_Sleep_Timeout = NULL; + +CSys::~CSys() +{ + sys = nullptr; +} + +// this checks if pause has run yet, +// tell the compiler it can change at any time +volatile bool g_bPaused = false; + +void CSys::Sleep(int msec) +{ + usleep(msec * 1000); +} + +void Sleep_Old(int msec) +{ + usleep(msec * 1000); +} + +void Sleep_Select(int msec) +{ + struct timeval tv; + + // Assumes msec < 1000 + tv.tv_sec = 0; + tv.tv_usec = 1000 * msec; + + select(1, NULL, NULL, NULL, &tv); +} + +void Sleep_Net(int msec) +{ + if (NET_Sleep_Timeout) + { + NET_Sleep_Timeout(); + return; + } + + // NET_Sleep_Timeout isn't hooked yet, fallback to the old method + Sleep_Old(msec); +} + +// linux runs on a 100Hz scheduling clock, so the minimum latency from +// usleep is 10msec. However, people want lower latency than this.. +// +// There are a few solutions, one is to use the realtime scheduler in the +// kernel BUT this needs root privelleges to start. It also can play +// unfriendly with other programs. +// +// Another solution is to use software timers, they use the RTC of the +// system and are accurate to microseconds (or so). +// +// timers, via setitimer() are used here +void Sleep_Timer(int msec) +{ + struct itimerval tm; + + tm.it_value.tv_sec = msec / 1000; // convert msec to seconds + tm.it_value.tv_usec = (msec % 1000) * 1E3; // get the number of msecs and change to micros + tm.it_interval.tv_sec = 0; + tm.it_interval.tv_usec = 0; + + g_bPaused = false; + + // set the timer to trigger + if (setitimer(ITIMER_REAL, &tm, NULL)) { + // wait for the signal + pause(); + } + + g_bPaused = true; +} + +void alarmFunc(int num) +{ + // reset the signal handler + signal(SIGALRM, alarmFunc); + + // paused is 0, the timer has fired before the pause was called... Lets queue it again + if (!g_bPaused) + { + struct itimerval itim; + itim.it_interval.tv_sec = 0; + itim.it_interval.tv_usec = 0; + itim.it_value.tv_sec = 0; + itim.it_value.tv_usec = 1000; // get it to run again real soon + setitimer(ITIMER_REAL, &itim, 0); + } + +} + +bool CSys::GetExecutableName(char *out) +{ + strcpy(out, g_szEXEName); + return true; +} + +// Engine is erroring out, display error in message box +void CSys::ErrorMessage(int level, const char *msg) +{ + puts(msg); + exit(-1); +} + +void CSys::WriteStatusText(char *szText) +{ +} + +void CSys::UpdateStatus(int force) +{ +} + +long CSys::LoadLibrary(char *lib) +{ + char cwd[1024]; + char absolute_lib[1024]; + + if (!getcwd(cwd, sizeof(cwd))) + ErrorMessage(1, "Sys_LoadLibrary: Couldn't determine current directory."); + + if (cwd[strlen(cwd) - 1] == '/') + cwd[strlen(cwd) - 1] = '\0'; + + _snprintf(absolute_lib, sizeof(absolute_lib), "%s/%s", cwd, lib); + + void *hDll = dlopen(absolute_lib, RTLD_NOW); + if (!hDll) + { + ErrorMessage(1, dlerror()); + } + + return (long)hDll; +} + +void CSys::FreeLibrary(long library) +{ + if (!library) + return; + + dlclose((void *)library); +} + +bool CSys::CreateConsoleWindow() +{ + return true; +} + +void CSys::DestroyConsoleWindow() +{ +} + +// Print text to the dedicated console +void CSys::ConsoleOutput(char *string) +{ + printf("%s", string); + fflush(stdout); +} + +char *CSys::ConsoleInput() +{ + return console.GetLine(); +} + +void CSys::Printf(char *fmt, ...) +{ + // Dump text to debugging console. + va_list argptr; + char szText[1024]; + + va_start(argptr, fmt); + _vsnprintf(szText, sizeof(szText), fmt, argptr); + va_end(argptr); + + // Get Current text and append it. + ConsoleOutput(szText); +} + +#define MAX_LINUX_CMDLINE 2048 +static char linuxCmdline[ MAX_LINUX_CMDLINE ]; + +void BuildCmdLine(int argc, char **argv) +{ + int len; + int i; + + for (len = 0, i = 1; i < argc; i++) + { + len += strlen(argv[i]) + 1; + } + + if (len > MAX_LINUX_CMDLINE) + { + printf("command line too long, %i max\n", MAX_LINUX_CMDLINE); + exit(-1); + return; + } + + linuxCmdline[0] = '\0'; + for (i = 1; i < argc; i++) + { + if (i > 1) { + strcat(linuxCmdline, " "); + } + + strcat(linuxCmdline, argv[ i ]); + } +} + +char *GetCommandLine() +{ + return linuxCmdline; +} + +int main(int argc, char *argv[]) +{ + _snprintf(g_szEXEName, sizeof(g_szEXEName), "%s", argv[0]); + BuildCmdLine(argc, argv); + + CommandLine()->CreateCmdLine(::GetCommandLine()); + CommandLine()->AppendParm("-steam", NULL); + + // Load engine + g_pEngineModule = Sys_LoadModule(ENGINE_LIB); + if (!g_pEngineModule) + { + sys->ErrorMessage(1, "Unable to load engine, image is corrupt."); + return -1; + } + + Sys_Sleep = Sleep_Old; + + char *pPingType; + int type; + if (CommandLine()->CheckParm("-pingboost", &pPingType) && pPingType) + { + type = atoi(pPingType); + switch (type) + { + case 1: + signal(SIGALRM, alarmFunc); + Sys_Sleep = Sleep_Timer; + break; + case 2: + Sys_Sleep = Sleep_Select; + break; + case 3: + Sys_Sleep = Sleep_Net; + + // we Sys_GetProcAddress NET_Sleep() from + //engine_i486.so later in this function + NET_Sleep_Timeout = (NET_Sleep_t)Sys_GetProcAddress(g_pEngineModule, "NET_Sleep_Timeout"); + break; + // just in case + default: + Sys_Sleep = Sleep_Old; + break; + } + } + + char *fsmodule; + if (CommandLine()->CheckParm("-pidfile", &fsmodule) && fsmodule) + { + FILE *pidFile = fopen(fsmodule, "w"); + if (pidFile) { + fprintf(pidFile, "%i\n", getpid()); + fclose(pidFile); + } + else + printf("Warning: unable to open pidfile (%s)\n", pPingType); + } + + g_pFileSystemModule = Sys_LoadModule(STDIO_FILESYSTEM_LIB); + + // Get FileSystem interface + g_FilesystemFactoryFn = Sys_GetFactory(g_pFileSystemModule); + if (!g_FilesystemFactoryFn) + return -1; + + IFileSystem *pFullFileSystem = (IFileSystem *)g_FilesystemFactoryFn(FILESYSTEM_INTERFACE_VERSION, NULL); + if (!pFullFileSystem) + return -1; + + pFullFileSystem->Mount(); + + if (!console.Init()) { + puts("Failed to initilise console."); + return 0; + } + + gbAppHasBeenTerminated = false; + RunServer(); + + if (gpszCvars) + free(gpszCvars); + + if (pFullFileSystem) + pFullFileSystem->Unmount(); + + Sys_UnloadModule(g_pFileSystemModule); + + exit(0); + return 0; +} diff --git a/rehlds/dedicated/src/sys_window.cpp b/rehlds/dedicated/src/sys_window.cpp new file mode 100644 index 0000000..77f90ce --- /dev/null +++ b/rehlds/dedicated/src/sys_window.cpp @@ -0,0 +1,266 @@ +#include "precompiled.h" + +class CSys: public ISys { +public: + virtual ~CSys(); + + void Sleep(int msec); + bool GetExecutableName(char *out); + void ErrorMessage(int level, const char *msg); + + void WriteStatusText(char *szText); + void UpdateStatus(int force); + + long LoadLibrary(char *lib); + void FreeLibrary(long library); + + bool CreateConsoleWindow(); + void DestroyConsoleWindow(); + + void ConsoleOutput(char *string); + char *ConsoleInput(); + void Printf(char *fmt, ...); +}; + +CSys g_Sys; +ISys *sys = &g_Sys; + +CSys::~CSys() +{ + sys = nullptr; +} + +void CSys::Sleep(int msec) +{ + ::Sleep(msec); +} + +bool CSys::GetExecutableName(char *out) +{ + if (!::GetModuleFileName((HINSTANCE)GetModuleHandle(NULL), out, 256)) + return false; + + return true; +} + +void CSys::ErrorMessage(int level, const char *msg) +{ + MessageBox(NULL, msg, "Half-Life Dedicated Server Error", MB_OK); + PostQuitMessage(0); +} + +void CSys::WriteStatusText(char *szText) +{ + SetConsoleTitle(szText); +} + +void CSys::UpdateStatus(int force) +{ + static double tLast = 0.0; + double tCurrent; + char szStatus[256]; + int n, nMax; + char szMap[32]; + float fps; + + if (!engineAPI) + return; + + tCurrent = (double)timeGetTime() * 0.001; + engineAPI->UpdateStatus(&fps, &n, &nMax, szMap); + + if (!force) + { + if ((tCurrent - tLast) < 0.5f) + return; + } + + tLast = tCurrent; + _snprintf(szStatus, sizeof(szStatus), "%.1f fps %2i/%2i on %16s", fps, n, nMax, szMap); + + console.SetStatusLine(szStatus); + console.UpdateStatus(); +} + +long CSys::LoadLibrary(char *lib) +{ + void *hDll = ::LoadLibrary(lib); + return (long)hDll; +} + +void CSys::FreeLibrary(long library) +{ + if (!library) + return; + + ::FreeLibrary((HMODULE)library); +} + +bool CSys::CreateConsoleWindow() +{ +#if 0 + if (!AllocConsole()) + { + return false; + } +#endif + + InitConProc(); + return true; +} + +void CSys::DestroyConsoleWindow() +{ + FreeConsole(); + + // shut down QHOST hooks if necessary + DeinitConProc(); +} + +void CSys::ConsoleOutput(char *string) +{ + if (g_bVGui) + { + VGUIPrintf(string); + } + else + { + console.Print(string); + } +} + +char *CSys::ConsoleInput() +{ + return console.GetLine(); +} + +void CSys::Printf(char *fmt, ...) +{ + // Dump text to debugging console. + va_list argptr; + char szText[1024]; + + va_start(argptr, fmt); + _vsnprintf(szText, sizeof(szText), fmt, argptr); + va_end(argptr); + + // Get Current text and append it. + ConsoleOutput(szText); +} + +int StartServer() +{ + // Startup winock + WORD version = MAKEWORD(2, 0); + WSADATA wsaData; + WSAStartup(version, &wsaData); + + CommandLine()->CreateCmdLine(GetCommandLine()); + + // Load engine + g_pEngineModule = Sys_LoadModule(ENGINE_LIB); + if (!g_pEngineModule) + { + MessageBox(NULL, "Unable to load engine, image is corrupt.", "Half-Life Dedicated Server Error", MB_OK); + return -1; + } + + g_pFileSystemModule = Sys_LoadModule(STDIO_FILESYSTEM_LIB); + + // Get FileSystem interface + g_FilesystemFactoryFn = Sys_GetFactory(g_pFileSystemModule); + if (!g_FilesystemFactoryFn) + return -1; + + IFileSystem *pFullFileSystem = (IFileSystem *)g_FilesystemFactoryFn(FILESYSTEM_INTERFACE_VERSION, NULL); + if (!pFullFileSystem) + return -1; + + pFullFileSystem->Mount(); + + char *pszValue = nullptr; + if (CommandLine()->CheckParm("-steam") + || (CommandLine()->CheckParm("-console", &pszValue) && !pszValue)) { + g_bVGui = true; + StartVGUI(); + } + else + { + if (!console.Init()) { + MessageBox(NULL, "Failed to initialize console.", "Half-Life Dedicated Server Error", MB_OK); + return 0; + } + + if (!sys->CreateConsoleWindow()) { + return 0; + } + + console.SetColor(FOREGROUND_RED | FOREGROUND_INTENSITY); + + if (!SetConsoleCtrlHandler(MyHandlerRoutine, TRUE)) { + MessageBox(NULL, "Unable to set control handler", "Half-Life Dedicated Server Error", MB_OK); + return 0; + } + } + + gbAppHasBeenTerminated = false; + Load3rdParty(); + + // TODO: finish me! + /*// run vgui + if (g_bVGui) + { + while (VGUIIsInConfig() && VGUIIsRunning()) + { + RunVGUIFrame(); + } + } + else*/ + { + RunServer(); + } + + if (gpszCvars) + free(gpszCvars); + + if (pFullFileSystem) + pFullFileSystem->Unmount(); + + Sys_UnloadModule(g_pFileSystemModule); + + if (hDLLThirdParty) + { + Sys_UnloadModule((CSysModule *)hDLLThirdParty); + hDLLThirdParty = 0L; + } + + WSACleanup(); + return 0; +} + +int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) +{ + if (ShouldLaunchAppViaSteam(lpCmdLine, STDIO_FILESYSTEM_LIB, STDIO_FILESYSTEM_LIB)) + return 0; + + auto command = CommandLineToArgvW(GetCommandLineW(), (int *)&lpCmdLine); + auto ret = StartServer(); + LocalFree(command); + return ret; +} + +BOOL WINAPI MyHandlerRoutine(DWORD CtrlType) +{ + switch (CtrlType) + { + case CTRL_C_EVENT: + case CTRL_BREAK_EVENT: + case CTRL_CLOSE_EVENT: + case CTRL_LOGOFF_EVENT: + case CTRL_SHUTDOWN_EVENT: + gbAppHasBeenTerminated = true; + return TRUE; + } + + return FALSE; +} diff --git a/rehlds/dedicated/src/vgui/vguihelpers.cpp b/rehlds/dedicated/src/vgui/vguihelpers.cpp new file mode 100644 index 0000000..5f06cc1 --- /dev/null +++ b/rehlds/dedicated/src/vgui/vguihelpers.cpp @@ -0,0 +1,157 @@ +#include "precompiled.h" + +CSysModule *g_pVGUIModule = nullptr; + +bool InitializeVGui(CreateInterfaceFn *factorylist, int factorycount) +{ +#ifdef _WIN32 + // TODO: finish me! + /*if (vgui::VGuiControls_Init("DEDICATED", factoryList, numFactories)) + { + filesystem()->AddSearchPath(".", "MAIN"); + return true; + }*/ +#endif + + return false; +} + +// Starts up the VGUI system and loads the base panel +int StartVGUI() +{ + // TODO: finish me! + /*g_pVGUIModule = Sys_LoadModule("vgui2.dll"); + + CreateInterfaceFn ifaceFactory = Sys_GetFactoryThis(); + CreateInterfaceFn adminFactory = Sys_GetFactory(g_pVGUIModule); + CreateInterfaceFn filesystemFactory = Sys_GetFactory(g_pFileSystemModule); + CreateInterfaceFn dedicatedFactory = Sys_GetFactory(g_pEngineModule); + + const int numFactories = 4; + if (!InitializeVGui(&ifaceFactory, numFactories)) + { + MessageBox(NULL, "Fatal Error: Could not initialize vgui.", "Steam - Fatal Error", MB_OK | MB_ICONERROR); + return -1; + } + + filesystem()->AddSearchPath("platform", "PLATFORM"); + + // find our configuration directory + char szConfigDir[512]; + const char *steamPath = getenv("SteamInstallPath"); + if (steamPath) + { + // put the config dir directly under steam + _snprintf(szConfigDir, sizeof(szConfigDir), "%s/config", steamPath); + } + else + { + // we're not running steam, so just put the config dir under the platform + strncpy(szConfigDir, "platform/config", sizeof(szConfigDir)); + } + + mkdir(szConfigDir); + filesystem()->AddSearchPath(szConfigDir, "CONFIG"); + + vgui::system()->SetUserConfigFile("DialogConfig.vdf", "CONFIG"); + + // Init the surface + g_pMainPanel = new CMainPanel(); + g_pMainPanel->SetVisible(true); + + vgui::surface()->SetEmbeddedPanel(g_pMainPanel->GetVPanel()); + + // load the scheme + vgui::scheme()->LoadSchemeFromFile("Resource/TrackerScheme.res", NULL); + + // localization + vgui::localize()->AddFile("Resource/platform_%language%.txt"); + vgui::localize()->AddFile("Resource/vgui_%language%.txt"); + vgui::localize()->AddFile("Admin/server_%language%.txt"); + + // Start vgui + vgui::ivgui()->Start(); + + // load the module + g_pFullFileSystem->GetLocalCopy("Platform/Admin/AdminServer.dll"); + g_hAdminServerModule = Sys_LoadModule("Platform/Admin/AdminServer.dll"); + Assert(g_hAdminServerModule != NULL); + CreateInterfaceFn adminFactory = NULL; + + if (!g_hAdminServerModule) + { + vgui::ivgui()->DPrintf2("Admin Error: module version (Admin/AdminServer.dll, %s) invalid, not loading\n", IMANAGESERVER_INTERFACE_VERSION); + } + else + { + // make sure we get the right version + adminFactory = Sys_GetFactory(g_hAdminServerModule); + g_pAdminServer = (IAdminServer *)adminFactory(ADMINSERVER_INTERFACE_VERSION, NULL); + g_pAdminVGuiModule = (IVGuiModule *)adminFactory("VGuiModuleAdminServer001", NULL); + Assert(g_pAdminServer != NULL); + Assert(g_pAdminVGuiModule != NULL); + if (!g_pAdminServer || !g_pAdminVGuiModule) + { + vgui::ivgui()->DPrintf2("Admin Error: module version (Admin/AdminServer.dll, %s) invalid, not loading\n", IMANAGESERVER_INTERFACE_VERSION); + } + } + + // finish initializing admin module + g_pAdminVGuiModule->Initialize(&dedicatedFactory, 1); + g_pAdminVGuiModule->PostInitialize(&adminFactory, 1); + g_pAdminVGuiModule->SetParent(g_pMainPanel->GetVPanel()); + + // finish setting up main panel + g_pMainPanel->Initialize(); + g_pMainPanel->Open(); +*/ + return 0; +} + +// Run a single VGUI frame +void StopVGUI() +{ +} + +void RunVGUIFrame() +{ + // TODO: finish me! + //vgui::ivgui()->RunFrame(); +} + +bool VGUIIsRunning() +{ + //return vgui::ivgui()->IsRunning(); + return false; +} + +bool VGUIIsStopping() +{ + //return g_pMainPanel->Stopping(); + return false; +} + +bool VGUIIsInConfig() +{ + //return g_pMainPanel->IsInConfig(); + return false; +} + +void VGUIFinishedConfig() +{ + /*Assert(g_pMainPanel); + + // engine is loaded, pass the message on + if (g_pMainPanel) + { + SetEvent(g_pMainPanel->GetShutdownHandle()); + }*/ +} + +void VGUIPrintf(const char *msg) +{ + /*if (g_pMainPanel) + { + g_pMainPanel->AddConsoleText(msg); + }*/ +} diff --git a/rehlds/dedicated/src/vgui/vguihelpers.h b/rehlds/dedicated/src/vgui/vguihelpers.h new file mode 100644 index 0000000..9710eeb --- /dev/null +++ b/rehlds/dedicated/src/vgui/vguihelpers.h @@ -0,0 +1,10 @@ +#pragma once + +int StartVGUI(); +void StopVGUI(); +void RunVGUIFrame(); +bool VGUIIsRunning(); +bool VGUIIsStopping(); +bool VGUIIsInConfig(); +void VGUIFinishedConfig(); +void VGUIPrintf(const char *msg); diff --git a/rehlds/engine/filesystem.cpp b/rehlds/engine/filesystem.cpp index 92fe9cb..b5c1bcc 100644 --- a/rehlds/engine/filesystem.cpp +++ b/rehlds/engine/filesystem.cpp @@ -51,7 +51,7 @@ bool FileSystem_LoadDLL(CreateInterfaceFn filesystemFactory) { if (!filesystemFactory) { - g_pFileSystemModule = Sys_LoadModule(FILESYSTEM_DLL_NAME); + g_pFileSystemModule = Sys_LoadModule(STDIO_FILESYSTEM_LIB); if (g_pFileSystemModule) { diff --git a/rehlds/engine/filesystem_.h b/rehlds/engine/filesystem_.h index 513237c..d548bda 100644 --- a/rehlds/engine/filesystem_.h +++ b/rehlds/engine/filesystem_.h @@ -36,12 +36,6 @@ #include "iregistry.h" #include "utlvector.h" -#ifdef _WIN32 -#define FILESYSTEM_DLL_NAME "filesystem_stdio.dll" -#else -#define FILESYSTEM_DLL_NAME "filesystem_stdio.so" -#endif - #ifdef HOOK_ENGINE #define g_fallbackLocalizationFiles (*pg_fallbackLocalizationFiles) #define s_pBaseDir (*ps_pBaseDir) diff --git a/rehlds/public/FileSystem.h b/rehlds/public/FileSystem.h index 4a3c621..41d26b5 100644 --- a/rehlds/public/FileSystem.h +++ b/rehlds/public/FileSystem.h @@ -1,6 +1,6 @@ //========= Copyright � 1996-2001, Valve LLC, All rights reserved. ============ // -// Purpose: +// Purpose: // // $NoKeywords: $ //============================================================================= @@ -15,6 +15,13 @@ #include #include +#ifdef _WIN32 + #define STDIO_FILESYSTEM_LIB "filesystem_stdio.dll" + #define STEAM_FILESYSTEM_LIB "filesystem_steam.dll" +#else + #define STDIO_FILESYSTEM_LIB "filesystem_stdio.so" + #define STEAM_FILESYSTEM_LIB "filesystem_steam.so" +#endif // _WIN32 //----------------------------------------------------------------------------- // Forward declarations @@ -77,7 +84,7 @@ public: // Add paths in priority order (mod dir, game dir, ....) // If one or more .pak files are in the specified directory, then they are // added after the file system path - // If the path is the relative path to a .bsp file, then any previous .bsp file + // If the path is the relative path to a .bsp file, then any previous .bsp file // override is cleared and the current .bsp is searched for an embedded PAK file // and this file becomes the highest priority search path ( i.e., it's looked at first // even before the mod's file system path ). @@ -121,7 +128,7 @@ public: // direct filesystem buffer access // returns a handle to a buffer containing the file data - // this is the optimal way to access the complete data for a file, + // this is the optimal way to access the complete data for a file, // since the file preloader has probably already got it in memory virtual void *GetReadBuffer( FileHandle_t file, int *outBufferSize, bool failIfNotInCache ) = 0; virtual void ReleaseReadBuffer( FileHandle_t file, void *readBuffer ) = 0; @@ -174,7 +181,7 @@ public: // interface for custom pack files > 4Gb virtual bool AddPackFile( const char *fullpath, const char *pathID ) = 0; - + // open a file but force the data to come from the steam cache, NOT from disk virtual FileHandle_t OpenFromCacheForRead( const char *pFileName, const char *pOptions, const char *pathID = 0L ) = 0; diff --git a/rehlds/public/engine_hlds_api.h b/rehlds/public/engine_hlds_api.h index 97fd29f..75708fd 100644 --- a/rehlds/public/engine_hlds_api.h +++ b/rehlds/public/engine_hlds_api.h @@ -31,6 +31,12 @@ #include "maintypes.h" #include "interface.h" +#ifdef _WIN32 + #define ENGINE_LIB "swds.dll" +#else + #define ENGINE_LIB "engine_i486.so" +#endif // _WIN32 + class IDedicatedServerAPI : public IBaseInterface { public: diff --git a/rehlds/public/steam/steam_api.h b/rehlds/public/steam/steam_api.h index 10f3037..d4f4609 100644 --- a/rehlds/public/steam/steam_api.h +++ b/rehlds/public/steam/steam_api.h @@ -1,6 +1,6 @@ //========= Copyright Valve Corporation, All rights reserved. ============// // -// Purpose: +// Purpose: // //============================================================================= @@ -28,35 +28,34 @@ #include "steamps3params.h" #endif - - // Steam API export macro #if defined( _WIN32 ) && !defined( _X360 ) #if defined( STEAM_API_EXPORTS ) - #define S_API extern "C" __declspec( dllexport ) + #define S_API extern "C" __declspec( dllexport ) #elif defined( STEAM_API_NODLL ) #define S_API extern "C" #else - #define S_API extern "C" __declspec( dllimport ) + #define S_API extern "C" __declspec( dllimport ) #endif // STEAM_API_EXPORTS #elif defined( GNUC ) #if defined( STEAM_API_EXPORTS ) - #define S_API extern "C" __attribute__ ((visibility("default"))) + #define S_API extern "C" __attribute__ ((visibility("default"))) #else - #define S_API extern "C" + #define S_API extern "C" #endif // STEAM_API_EXPORTS #else // !WIN32 #if defined( STEAM_API_EXPORTS ) - #define S_API extern "C" + #define S_API extern "C" #else - #define S_API extern "C" + #define S_API extern "C" #endif // STEAM_API_EXPORTS #endif class CCallbackBase; +#ifdef REHLDS_SELF #include "rehlds/platform.h" - +#endif //----------------------------------------------------------------------------------------------------------------------------------------------------------// // Steam API setup & shutdown @@ -68,10 +67,10 @@ class CCallbackBase; // S_API void SteamAPI_Init(); (see below) S_API void SteamAPI_Shutdown(); -// checks if a local Steam client is running +// checks if a local Steam client is running S_API bool SteamAPI_IsSteamRunning(); -// Detects if your executable was launched through the Steam client, and restarts your game through +// Detects if your executable was launched through the Steam client, and restarts your game through // the client if necessary. The Steam client will be started if it is not running. // // Returns: true if your executable was NOT launched through the Steam client. This function will @@ -96,10 +95,10 @@ S_API ISteamClient *SteamClient(); // VERSION_SAFE_STEAM_API_INTERFACES is usually not necessary, but it provides safety against releasing // new steam_api.dll's without recompiling/rereleasing modules that use it. // -// If you use VERSION_SAFE_STEAM_API_INTERFACES, then you should call SteamAPI_InitSafe(). Also, to get the +// If you use VERSION_SAFE_STEAM_API_INTERFACES, then you should call SteamAPI_InitSafe(). Also, to get the // Steam interfaces, you must create and Init() a CSteamAPIContext (below) and use the interfaces in there. // -// If you don't use VERSION_SAFE_STEAM_API_INTERFACES, then you can use SteamAPI_Init() and the SteamXXXX() +// If you don't use VERSION_SAFE_STEAM_API_INTERFACES, then you can use SteamAPI_Init() and the SteamXXXX() // functions below to get at the Steam interfaces. // #ifdef VERSION_SAFE_STEAM_API_INTERFACES @@ -132,7 +131,7 @@ S_API ISteamPS3OverlayRender * SteamPS3OverlayRender(); //----------------------------------------------------------------------------------------------------------------------------------------------------------// // steam callback helper functions // -// The following classes/macros are used to be able to easily multiplex callbacks +// The following classes/macros are used to be able to easily multiplex callbacks // from the Steam API into various objects in the app in a thread-safe manner // // These functors are triggered via the SteamAPI_RunCallbacks() function, mapping the callback @@ -152,7 +151,7 @@ S_API void SteamAPI_UnregisterCallResult( class CCallbackBase *pCallback, SteamA //----------------------------------------------------------------------------- -// Purpose: base for callbacks, +// Purpose: base for callbacks, // used only by CCallback, shouldn't be used directly //----------------------------------------------------------------------------- class CCallbackBase @@ -221,7 +220,7 @@ public: SteamAPI_UnregisterCallResult( this, m_hAPICall ); m_hAPICall = k_uAPICallInvalid; } - + } ~CCallResult() @@ -234,14 +233,14 @@ private: virtual void Run( void *pvParam ) { m_hAPICall = k_uAPICallInvalid; // caller unregisters for us - (m_pObj->*m_Func)( (P *)pvParam, false ); + (m_pObj->*m_Func)( (P *)pvParam, false ); } void Run( void *pvParam, bool bIOFailure, SteamAPICall_t hSteamAPICall ) { if ( hSteamAPICall == m_hAPICall ) { m_hAPICall = k_uAPICallInvalid; // caller unregisters for us - (m_pObj->*m_Func)( (P *)pvParam, bIOFailure ); + (m_pObj->*m_Func)( (P *)pvParam, bIOFailure ); } } int GetCallbackSizeBytes() @@ -271,7 +270,7 @@ public: // ::Register() for your object // Or, just call the regular constructor with (NULL, NULL) // CCallback() {} - + // constructor for initializing this object in owner's constructor CCallback( T *pObj, func_t func ) : m_pObj( pObj ), m_Func( func ) { @@ -301,13 +300,23 @@ public: m_pObj = pObj; m_Func = func; // SteamAPI_RegisterCallback sets k_ECallbackFlagsRegistered + +#ifdef REHLDS_SELF CRehldsPlatformHolder::get()->SteamAPI_RegisterCallback(this, P::k_iCallback); +#else + SteamAPI_RegisterCallback(this, P::k_iCallback); +#endif // REHLDS_SELF } void Unregister() { // SteamAPI_UnregisterCallback removes k_ECallbackFlagsRegistered + +#ifdef REHLDS_SELF CRehldsPlatformHolder::get()->SteamAPI_UnregisterCallback(this); +#else + SteamAPI_UnregisterCallback(this); +#endif // REHLDS_SELF } void SetGameserverFlag() { m_nCallbackFlags |= k_ECallbackFlagsGameServer; } @@ -380,7 +389,7 @@ S_API HSteamUser GetHSteamUser(); #ifdef VERSION_SAFE_STEAM_API_INTERFACES //----------------------------------------------------------------------------------------------------------------------------------------------------------// -// VERSION_SAFE_STEAM_API_INTERFACES uses CSteamAPIContext to provide interfaces to each module in a way that +// VERSION_SAFE_STEAM_API_INTERFACES uses CSteamAPIContext to provide interfaces to each module in a way that // lets them each specify the interface versions they are compiled with. // // It's important that these stay inlined in the header so the calling module specifies the interface versions @@ -488,7 +497,7 @@ inline bool CSteamAPIContext::Init() m_pSteamUserStats = SteamClient()->GetISteamUserStats( hSteamUser, hSteamPipe, STEAMUSERSTATS_INTERFACE_VERSION ); if ( !m_pSteamUserStats ) return false; - + m_pSteamApps = SteamClient()->GetISteamApps( hSteamUser, hSteamPipe, STEAMAPPS_INTERFACE_VERSION ); if ( !m_pSteamApps ) return false; diff --git a/rehlds/public/vgui/VGUI.h b/rehlds/public/vgui/VGUI.h new file mode 100644 index 0000000..50cf372 --- /dev/null +++ b/rehlds/public/vgui/VGUI.h @@ -0,0 +1,31 @@ +#ifndef VGUI_H +#define VGUI_H +#ifdef _WIN32 +#pragma once +#endif + +namespace vgui2 +{ + +// handle to an internal vgui panel +// this is the only handle to a panel that is valid across dll boundaries +typedef unsigned int VPANEL; + +// handles to vgui objects +// NULL values signify an invalid value +typedef unsigned long HScheme; +typedef unsigned long HTexture; +typedef unsigned long HCursor; + +typedef int HContext; +typedef unsigned long HPanel; +typedef unsigned long HFont; + +// the value of an invalid font handle +const VPANEL NULL_PANEL = 0; +const HFont INVALID_FONT = 0; +const HPanel INVALID_PANEL = 0xffffffff; + +} // namespace vgui + +#endif // VGUI_H diff --git a/settings.gradle b/settings.gradle index 0da4d36..4879f1d 100644 --- a/settings.gradle +++ b/settings.gradle @@ -2,4 +2,5 @@ rootProject.name = 'rehlds' include 'dep/cppunitlite' include 'dep/bzip2' include 'rehlds' +include 'rehlds/dedicated' include 'flightrec/decoder_api', 'flightrec/decoder'