mirror of
https://github.com/BeamMP/BeamMP-Launcher.git
synced 2025-07-04 00:47:23 +00:00
lots of work, added atomic queue, changed githuyb workflows and added patterns with memory functions. Changed from MS Detours to MinHook.
now works with lua prototype
This commit is contained in:
parent
6dfeba1e49
commit
19d7120b13
2
.github/workflows/cmake-windows.yml
vendored
2
.github/workflows/cmake-windows.yml
vendored
@ -18,7 +18,7 @@ jobs:
|
|||||||
uses: lukka/run-vcpkg@v7
|
uses: lukka/run-vcpkg@v7
|
||||||
id: runvcpkg
|
id: runvcpkg
|
||||||
with:
|
with:
|
||||||
vcpkgArguments: 'zlib discord-rpc rapidjson openssl detours'
|
vcpkgArguments: 'zlib discord-rpc rapidjson openssl minhook'
|
||||||
vcpkgDirectory: '${{ runner.workspace }}/b/vcpkg'
|
vcpkgDirectory: '${{ runner.workspace }}/b/vcpkg'
|
||||||
vcpkgGitCommitId: '75522bb1f2e7d863078bcd06322348f053a9e33f'
|
vcpkgGitCommitId: '75522bb1f2e7d863078bcd06322348f053a9e33f'
|
||||||
vcpkgTriplet: 'x64-windows-static'
|
vcpkgTriplet: 'x64-windows-static'
|
||||||
|
2
.github/workflows/release-build.yml
vendored
2
.github/workflows/release-build.yml
vendored
@ -42,7 +42,7 @@ jobs:
|
|||||||
uses: lukka/run-vcpkg@main
|
uses: lukka/run-vcpkg@main
|
||||||
id: runvcpkg
|
id: runvcpkg
|
||||||
with:
|
with:
|
||||||
vcpkgArguments: 'zlib discord-rpc rapidjson openssl detours'
|
vcpkgArguments: 'zlib discord-rpc rapidjson openssl minhook'
|
||||||
vcpkgDirectory: '${{ runner.workspace }}/b/vcpkg'
|
vcpkgDirectory: '${{ runner.workspace }}/b/vcpkg'
|
||||||
vcpkgGitCommitId: '75522bb1f2e7d863078bcd06322348f053a9e33f'
|
vcpkgGitCommitId: '75522bb1f2e7d863078bcd06322348f053a9e33f'
|
||||||
vcpkgTriplet: 'x64-windows-static'
|
vcpkgTriplet: 'x64-windows-static'
|
||||||
|
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -10,3 +10,6 @@
|
|||||||
[submodule "include/easyloggingpp"]
|
[submodule "include/easyloggingpp"]
|
||||||
path = include/easyloggingpp
|
path = include/easyloggingpp
|
||||||
url = https://github.com/amrayn/easyloggingpp.git
|
url = https://github.com/amrayn/easyloggingpp.git
|
||||||
|
[submodule "include/atomic_queue"]
|
||||||
|
path = include/atomic_queue
|
||||||
|
url = https://github.com/max0x7ba/atomic_queue.git
|
||||||
|
@ -9,6 +9,7 @@ if (WIN32)
|
|||||||
STRING(REPLACE "/MDd" "/MTd" CMAKE_C_FLAGS_DEBUG ${CMAKE_C_FLAGS_DEBUG})
|
STRING(REPLACE "/MDd" "/MTd" CMAKE_C_FLAGS_DEBUG ${CMAKE_C_FLAGS_DEBUG})
|
||||||
find_package(ZLIB REQUIRED)
|
find_package(ZLIB REQUIRED)
|
||||||
find_package(OpenSSL REQUIRED)
|
find_package(OpenSSL REQUIRED)
|
||||||
|
find_package(minhook CONFIG REQUIRED)
|
||||||
#-DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-windows-static
|
#-DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-windows-static
|
||||||
set(VcpkgRoot ${_VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET})
|
set(VcpkgRoot ${_VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET})
|
||||||
include_directories(${VcpkgRoot}/include)
|
include_directories(${VcpkgRoot}/include)
|
||||||
@ -31,10 +32,9 @@ set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DDEBUG")
|
|||||||
|
|
||||||
add_executable(${PROJECT_NAME}
|
add_executable(${PROJECT_NAME}
|
||||||
src/main.cpp include/easyloggingpp/src/easylogging++.cc
|
src/main.cpp include/easyloggingpp/src/easylogging++.cc
|
||||||
src/Launcher.cpp include/Launcher.h
|
src/Launcher.cpp include/Launcher.h include/Memory/Hook.h
|
||||||
src/Memory/Definitions.cpp include/Memory/Definitions.h
|
src/Memory/Definitions.cpp include/Memory/Definitions.h
|
||||||
src/Memory/Memory.cpp include/Memory/Memory.h include/Memory/Patterns.h
|
src/Memory/Memory.cpp include/Memory/Memory.h include/Memory/Patterns.h
|
||||||
src/Memory/Detours.cpp include/Memory/Detours.h
|
|
||||||
src/Memory/BeamNG.cpp include/Memory/BeamNG.h
|
src/Memory/BeamNG.cpp include/Memory/BeamNG.h
|
||||||
src/Memory/GELua.cpp include/Memory/GELua.h
|
src/Memory/GELua.cpp include/Memory/GELua.h
|
||||||
src/Memory/IPC.cpp include/Memory/IPC.h
|
src/Memory/IPC.cpp include/Memory/IPC.h
|
||||||
@ -52,11 +52,12 @@ add_executable(${PROJECT_NAME}
|
|||||||
|
|
||||||
if (WIN32)
|
if (WIN32)
|
||||||
target_link_libraries(${PROJECT_NAME} PRIVATE ${VcpkgRoot}/lib/discord-rpc.lib
|
target_link_libraries(${PROJECT_NAME} PRIVATE ${VcpkgRoot}/lib/discord-rpc.lib
|
||||||
ZLIB::ZLIB discord-rpc OpenSSL::SSL OpenSSL::Crypto ws2_32 wx::net wx::core wx::base Dbghelp comsuppw detours)
|
ZLIB::ZLIB discord-rpc OpenSSL::SSL OpenSSL::Crypto ws2_32 wx::net wx::core wx::base Dbghelp comsuppw minhook::minhook)
|
||||||
else(WIN32) #MINGW
|
else(WIN32) #MINGW
|
||||||
add_definitions("-D_WIN32_WINNT=0x0600")
|
add_definitions("-D_WIN32_WINNT=0x0600")
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Os -s --static")
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Os -s --static")
|
||||||
target_link_libraries(${PROJECT_NAME} discord-rpc ssl crypto ws2_32 ssp crypt32 z Dbghelp comsuppw detours)
|
target_link_libraries(${PROJECT_NAME} discord-rpc ssl crypto ws2_32 ssp crypt32 z Dbghelp comsuppw minhook::minhook)
|
||||||
endif(WIN32)
|
endif(WIN32)
|
||||||
add_definitions(-DELPP_NO_DEFAULT_LOG_FILE)
|
add_definitions(-DELPP_NO_DEFAULT_LOG_FILE)
|
||||||
target_include_directories(${PROJECT_NAME} PRIVATE "include")
|
target_include_directories(${PROJECT_NAME} PRIVATE "include")
|
||||||
|
target_include_directories(${PROJECT_NAME} PRIVATE "include/atomic_queue/include")
|
||||||
|
@ -74,12 +74,12 @@ private: //variables
|
|||||||
std::string BeamVersion{};
|
std::string BeamVersion{};
|
||||||
std::string BeamUserPath{};
|
std::string BeamUserPath{};
|
||||||
std::string DiscordMessage{};
|
std::string DiscordMessage{};
|
||||||
std::string Version{"3.0"};
|
std::string Version{"2.0"};
|
||||||
Server ServerHandler{this};
|
Server ServerHandler{this};
|
||||||
std::string TargetBuild{"default"};
|
std::string TargetBuild{"default"};
|
||||||
static std::atomic<bool> Shutdown, Exit;
|
static std::atomic<bool> Shutdown, Exit;
|
||||||
std::string FullVersion{Version + ".0"};
|
std::string FullVersion{Version + ".99"};
|
||||||
VersionParser SupportedVersion{"0.24.1.1"};
|
VersionParser SupportedVersion{"0.24.1.2"};
|
||||||
IPC IPCToGame{"BeamMP_OUT", "BeamMP_Sem1", "BeamMP_Sem2", 0x1900000};
|
IPC IPCToGame{"BeamMP_OUT", "BeamMP_Sem1", "BeamMP_Sem2", 0x1900000};
|
||||||
IPC IPCFromGame{"BeamMP_IN", "BeamMP_Sem3", "BeamMP_Sem4", 0x1900000};
|
IPC IPCFromGame{"BeamMP_IN", "BeamMP_Sem3", "BeamMP_Sem4", 0x1900000};
|
||||||
};
|
};
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
///
|
///
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "Memory/Detours.h"
|
#include "Memory/Hook.h"
|
||||||
#include "Memory/GELua.h"
|
#include "Memory/GELua.h"
|
||||||
#include "Memory/IPC.h"
|
#include "Memory/IPC.h"
|
||||||
#include <memory>
|
#include <memory>
|
||||||
@ -15,13 +15,14 @@ public:
|
|||||||
static void EntryPoint();
|
static void EntryPoint();
|
||||||
static void SendIPC(const std::string& Data);
|
static void SendIPC(const std::string& Data);
|
||||||
private:
|
private:
|
||||||
static std::unique_ptr<Detours> TickCountDetour;
|
static inline std::unique_ptr<Hook<def::GEUpdate>> TickCountDetour;
|
||||||
static std::unique_ptr<Detours> OpenJITDetour;
|
static inline std::unique_ptr<Hook<def::lua_open_jit>> OpenJITDetour;
|
||||||
static std::unique_ptr<IPC> IPCFromLauncher;
|
static inline std::unique_ptr<IPC> IPCFromLauncher;
|
||||||
static std::unique_ptr<IPC> IPCToLauncher;
|
static inline std::unique_ptr<IPC> IPCToLauncher;
|
||||||
|
static inline uint64_t GameBaseAddr;
|
||||||
|
static inline uint64_t DllBaseAddr;
|
||||||
static int lua_open_jit_D(lua_State* State);
|
static int lua_open_jit_D(lua_State* State);
|
||||||
static void RegisterGEFunctions();
|
static void RegisterGEFunctions();
|
||||||
static uint32_t GetTickCount_D();
|
static int GetTickCount_D(void* GEState, void* Param2, void* Param3, void* Param4);
|
||||||
static uint64_t GameBaseAddr;
|
static void IPCListener();
|
||||||
static uint64_t DllBaseAddr;
|
|
||||||
};
|
};
|
||||||
|
@ -10,12 +10,14 @@ typedef struct lua_State lua_State;
|
|||||||
typedef int (*lua_CFunction)(lua_State*);
|
typedef int (*lua_CFunction)(lua_State*);
|
||||||
extern int lua_gettop(lua_State *L);
|
extern int lua_gettop(lua_State *L);
|
||||||
namespace def {
|
namespace def {
|
||||||
typedef unsigned long (*GetTickCount)();
|
typedef int (*GEUpdate)(void* Param1, void* Param2, void* Param3, void* Param4);
|
||||||
|
typedef uint32_t (*GetTickCount)();
|
||||||
typedef int (*lua_open_jit)(lua_State* L);
|
typedef int (*lua_open_jit)(lua_State* L);
|
||||||
typedef void (*lua_get_field)(lua_State* L, int idx, const char* k);
|
typedef void (*lua_get_field)(lua_State* L, int idx, const char* k);
|
||||||
typedef const char* (*lua_push_fstring)(lua_State* L, const char* fmt, ...);
|
typedef const char* (*lua_push_fstring)(lua_State* L, const char* fmt, ...);
|
||||||
typedef int (*lua_p_call)(lua_State* L, int arg, int res, int err);
|
typedef int (*lua_p_call)(lua_State* L, int arg, int res, int err);
|
||||||
typedef void (*lua_pushcclosure)(lua_State* L, lua_CFunction fn, int n);
|
typedef void (*lua_pushcclosure)(lua_State* L, lua_CFunction fn, int n);
|
||||||
|
typedef int (*lua_settop)(lua_State* L, int idx);
|
||||||
typedef void (*lua_settable)(lua_State* L, int idx);
|
typedef void (*lua_settable)(lua_State* L, int idx);
|
||||||
typedef void (*lua_createtable)(lua_State* L, int narray, int nrec);
|
typedef void (*lua_createtable)(lua_State* L, int narray, int nrec);
|
||||||
typedef void (*lua_setfield)(lua_State* L, int idx, const char* k);
|
typedef void (*lua_setfield)(lua_State* L, int idx, const char* k);
|
||||||
|
@ -1,17 +0,0 @@
|
|||||||
///
|
|
||||||
/// Created by Anonymous275 on 1/21/22
|
|
||||||
/// Copyright (c) 2021-present Anonymous275 read the LICENSE file for more info.
|
|
||||||
///
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
class Detours{
|
|
||||||
void* targetPtr;
|
|
||||||
void* detourFunc;
|
|
||||||
public:
|
|
||||||
Detours(void* src, void* dest) : targetPtr(src), detourFunc(dest){};
|
|
||||||
void Attach();
|
|
||||||
void Detach();
|
|
||||||
|
|
||||||
private:
|
|
||||||
bool Attached = false;
|
|
||||||
};
|
|
@ -9,17 +9,19 @@
|
|||||||
class GELua {
|
class GELua {
|
||||||
public:
|
public:
|
||||||
static void FindAddresses();
|
static void FindAddresses();
|
||||||
static def::GetTickCount GetTickCount;
|
static inline def::GEUpdate GEUpdate;
|
||||||
static def::lua_open_jit lua_open_jit;
|
static inline def::lua_settop lua_settop;
|
||||||
static def::lua_push_fstring lua_push_fstring;
|
static inline def::GetTickCount GetTickCount;
|
||||||
static def::lua_get_field lua_get_field;
|
static inline def::lua_open_jit lua_open_jit;
|
||||||
static def::lua_p_call lua_p_call;
|
static inline def::lua_push_fstring lua_push_fstring;
|
||||||
static def::lua_createtable lua_createtable;
|
static inline def::lua_get_field lua_get_field;
|
||||||
static def::lua_pushcclosure lua_pushcclosure;
|
static inline def::lua_p_call lua_p_call;
|
||||||
static def::lua_setfield lua_setfield;
|
static inline def::lua_createtable lua_createtable;
|
||||||
static def::lua_settable lua_settable;
|
static inline def::lua_pushcclosure lua_pushcclosure;
|
||||||
static def::lua_tolstring lua_tolstring;
|
static inline def::lua_setfield lua_setfield;
|
||||||
static lua_State* State;
|
static inline def::lua_settable lua_settable;
|
||||||
|
static inline def::lua_tolstring lua_tolstring;
|
||||||
|
static inline lua_State* State;
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace GELuaTable {
|
namespace GELuaTable {
|
||||||
|
48
include/Memory/Hook.h
Normal file
48
include/Memory/Hook.h
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
///
|
||||||
|
/// Created by Anonymous275 on 1/21/22
|
||||||
|
/// Copyright (c) 2021-present Anonymous275 read the LICENSE file for more info.
|
||||||
|
///
|
||||||
|
#define WIN32_LEAN_AND_MEAN
|
||||||
|
#include "Memory/Memory.h"
|
||||||
|
#include <MinHook.h>
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
template <class FuncType>
|
||||||
|
class Hook {
|
||||||
|
FuncType targetPtr;
|
||||||
|
FuncType detourFunc;
|
||||||
|
bool Attached = false;
|
||||||
|
public:
|
||||||
|
|
||||||
|
Hook(FuncType src, FuncType dest) : targetPtr(src), detourFunc(dest) {
|
||||||
|
auto status = MH_CreateHook((void*)targetPtr, (void*)detourFunc, (void**)&Original);
|
||||||
|
if(status != MH_OK) {
|
||||||
|
Memory::Print(std::string("MH Error -> ") + MH_StatusToString(status));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Enable() {
|
||||||
|
if(!Attached){
|
||||||
|
auto status = MH_EnableHook((void*)targetPtr);
|
||||||
|
if(status != MH_OK) {
|
||||||
|
Memory::Print(std::string("MH Error -> ") + MH_StatusToString(status));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Attached = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Disable() {
|
||||||
|
if(Attached){
|
||||||
|
auto status = MH_DisableHook((void*)targetPtr);
|
||||||
|
if(status != MH_OK) {
|
||||||
|
Memory::Print(std::string("MH Error -> ") + MH_StatusToString(status));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Attached = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FuncType Original{};
|
||||||
|
};
|
@ -45,4 +45,12 @@ namespace Patterns {
|
|||||||
"\x48\x89\x5c\x24\x00\x48\x89\x74\x24\x00\x57\x48\x83\xec\x00\x49\x8b\xf8\x8b\xda\x48\x8b\xf1\xe8",
|
"\x48\x89\x5c\x24\x00\x48\x89\x74\x24\x00\x57\x48\x83\xec\x00\x49\x8b\xf8\x8b\xda\x48\x8b\xf1\xe8",
|
||||||
"xxxx?xxxx?xxxx?xxxxxxxxx"
|
"xxxx?xxxx?xxxx?xxxxxxxxx"
|
||||||
};
|
};
|
||||||
|
const char* GEUpdate[2] {
|
||||||
|
"\x48\x89\x5c\x24\x00\x48\x89\x6c\x24\x00\x56\x57\x41\x56\x48\x83\xec\x00\x4c\x8b\x31\x49\x8b\xf0",
|
||||||
|
"xxxx?xxxx?xxxxxxx?xxxxxx"
|
||||||
|
};
|
||||||
|
const char* lua_settop[2] {
|
||||||
|
"\x4c\x8b\xc1\x85\xd2\x7e\x00\x48\x8b\x41\x00\x48\x8b\x49",
|
||||||
|
"xxxxxx?xxx?xxx"
|
||||||
|
};
|
||||||
}
|
}
|
@ -22,6 +22,7 @@ public:
|
|||||||
const std::string& getModList();
|
const std::string& getModList();
|
||||||
const std::string& getUIStatus();
|
const std::string& getUIStatus();
|
||||||
const std::string& getMap();
|
const std::string& getMap();
|
||||||
|
void StartUDP();
|
||||||
void setModLoaded();
|
void setModLoaded();
|
||||||
bool Terminated();
|
bool Terminated();
|
||||||
int getPing() const;
|
int getPing() const;
|
||||||
|
1
include/atomic_queue
Submodule
1
include/atomic_queue
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit fad31557de3acc40e704ad63bb11e7089190c16a
|
@ -13,10 +13,9 @@
|
|||||||
void Launcher::HandleIPC(const std::string& Data) {
|
void Launcher::HandleIPC(const std::string& Data) {
|
||||||
char Code = Data.at(0), SubCode = 0;
|
char Code = Data.at(0), SubCode = 0;
|
||||||
if(Data.length() > 1)SubCode = Data.at(1);
|
if(Data.length() > 1)SubCode = Data.at(1);
|
||||||
LOG(INFO) << Data;
|
|
||||||
switch (Code) {
|
switch (Code) {
|
||||||
case 'A':
|
case 'A':
|
||||||
SendIPC(Data.substr(0,1));
|
ServerHandler.StartUDP();
|
||||||
break;
|
break;
|
||||||
case 'B':
|
case 'B':
|
||||||
ServerHandler.Close();
|
ServerHandler.Close();
|
||||||
@ -24,6 +23,7 @@ void Launcher::HandleIPC(const std::string& Data) {
|
|||||||
LOG(INFO) << "Sent Server List";
|
LOG(INFO) << "Sent Server List";
|
||||||
break;
|
break;
|
||||||
case 'C':
|
case 'C':
|
||||||
|
ServerHandler.Close();
|
||||||
ServerHandler.Connect(Data);
|
ServerHandler.Connect(Data);
|
||||||
while(ServerHandler.getModList().empty() && !ServerHandler.Terminated()){
|
while(ServerHandler.getModList().empty() && !ServerHandler.Terminated()){
|
||||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||||
@ -32,17 +32,10 @@ void Launcher::HandleIPC(const std::string& Data) {
|
|||||||
else SendIPC("L"+ServerHandler.getModList());
|
else SendIPC("L"+ServerHandler.getModList());
|
||||||
break;
|
break;
|
||||||
case 'U':
|
case 'U':
|
||||||
if(SubCode == 'l')SendIPC("Ul" + ServerHandler.getUIStatus());
|
SendIPC("Ul" + ServerHandler.getUIStatus());
|
||||||
else if(SubCode == 'p') {
|
|
||||||
if(ServerHandler.getPing() > 800) {
|
if(ServerHandler.getPing() > 800) {
|
||||||
SendIPC("Up-2");
|
SendIPC("Up-2");
|
||||||
}else SendIPC("Up" + std::to_string(ServerHandler.getPing()));
|
}else SendIPC("Up" + std::to_string(ServerHandler.getPing()));
|
||||||
} else if(!SubCode) {
|
|
||||||
std::string Ping;
|
|
||||||
if(ServerHandler.getPing() > 800)Ping = "-2";
|
|
||||||
else Ping = std::to_string(ServerHandler.getPing());
|
|
||||||
SendIPC("Ul" + ServerHandler.getUIStatus() + "\nUp" + Ping);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case 'M':
|
case 'M':
|
||||||
SendIPC(ServerHandler.getMap());
|
SendIPC(ServerHandler.getMap());
|
||||||
@ -73,7 +66,6 @@ void Launcher::HandleIPC(const std::string& Data) {
|
|||||||
|
|
||||||
void Server::ServerParser(const std::string& Data) {
|
void Server::ServerParser(const std::string& Data) {
|
||||||
if(Data.empty())return;
|
if(Data.empty())return;
|
||||||
LOG(INFO) << "IPC Parser: " << Data;
|
|
||||||
char Code = Data.at(0),SubCode = 0;
|
char Code = Data.at(0),SubCode = 0;
|
||||||
if(Data.length() > 1)SubCode = Data.at(1);
|
if(Data.length() > 1)SubCode = Data.at(1);
|
||||||
switch (Code) {
|
switch (Code) {
|
||||||
|
@ -33,6 +33,7 @@ Launcher::Launcher(int argc, char* argv[]) : CurrentPath(std::filesystem::path(a
|
|||||||
|
|
||||||
void Launcher::Abort() {
|
void Launcher::Abort() {
|
||||||
Shutdown.store(true);
|
Shutdown.store(true);
|
||||||
|
ServerHandler.Close();
|
||||||
if(DiscordRPC.joinable()) {
|
if(DiscordRPC.joinable()) {
|
||||||
DiscordRPC.join();
|
DiscordRPC.join();
|
||||||
}
|
}
|
||||||
@ -145,10 +146,10 @@ void Launcher::ListenIPC() {
|
|||||||
void Launcher::SendIPC(const std::string& Data, bool core) {
|
void Launcher::SendIPC(const std::string& Data, bool core) {
|
||||||
static std::mutex Lock;
|
static std::mutex Lock;
|
||||||
std::scoped_lock Guard(Lock);
|
std::scoped_lock Guard(Lock);
|
||||||
if(core) {
|
if(core)IPCToGame.send("C" + Data);
|
||||||
IPCToGame.send("C" + Data);
|
else IPCToGame.send("G" + Data);
|
||||||
} else {
|
if(IPCToGame.send_timed_out()) {
|
||||||
IPCToGame.send("G" + Data);
|
LOG(WARNING) << "Timed out while sending \"" << Data << "\"";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,56 +3,39 @@
|
|||||||
/// Copyright (c) 2021-present Anonymous275 read the LICENSE file for more info.
|
/// Copyright (c) 2021-present Anonymous275 read the LICENSE file for more info.
|
||||||
///
|
///
|
||||||
|
|
||||||
|
|
||||||
|
#include "atomic_queue/atomic_queue.h"
|
||||||
#include "Memory/BeamNG.h"
|
#include "Memory/BeamNG.h"
|
||||||
#include "Memory/Memory.h"
|
#include "Memory/Memory.h"
|
||||||
|
|
||||||
uint32_t BeamNG::GetTickCount_D() {
|
atomic_queue::AtomicQueue2<std::string, 1000> AtomicQueue;
|
||||||
if(GELua::State != nullptr) {
|
|
||||||
IPCFromLauncher->try_receive();
|
|
||||||
if(!IPCFromLauncher->receive_timed_out()) {
|
|
||||||
if(IPCFromLauncher->msg()[0] == 'C') {
|
|
||||||
GELua::lua_get_field(GELua::State, -10002, "handleCoreMsg");
|
|
||||||
Memory::Print(std::string("Sending to handleCoreMsg -> ") + char(IPCFromLauncher->msg()[1]) + std::to_string(IPCFromLauncher->msg().size()) );
|
|
||||||
} else {
|
|
||||||
GELua::lua_get_field(GELua::State, -10002, "handleGameMsg");
|
|
||||||
Memory::Print(std::string("Sending to handleGameMsg -> ") + char(IPCFromLauncher->msg()[1]) + std::to_string(IPCFromLauncher->msg().size()) );
|
|
||||||
}
|
|
||||||
GELua::lua_push_fstring(GELua::State, "%s", &IPCFromLauncher->c_str()[1]);
|
|
||||||
GELua::lua_p_call(GELua::State, 1, 0, 0);
|
|
||||||
IPCFromLauncher->confirm_receive();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Memory::GetTickCount();
|
|
||||||
}
|
|
||||||
|
|
||||||
int BeamNG::lua_open_jit_D(lua_State* State) {
|
int BeamNG::lua_open_jit_D(lua_State* State) {
|
||||||
Memory::Print("Got lua State");
|
Memory::Print("Got lua State");
|
||||||
GELua::State = State;
|
GELua::State = State;
|
||||||
RegisterGEFunctions();
|
RegisterGEFunctions();
|
||||||
OpenJITDetour->Detach();
|
return OpenJITDetour->Original(State);
|
||||||
int r = GELua::lua_open_jit(State);
|
|
||||||
OpenJITDetour->Attach();
|
|
||||||
return r;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void BeamNG::EntryPoint() {
|
void BeamNG::EntryPoint() {
|
||||||
|
auto status = MH_Initialize();
|
||||||
|
if(status != MH_OK)Memory::Print(std::string("MH Error -> ") + MH_StatusToString(status));
|
||||||
Memory::Print("PID : " + std::to_string(Memory::GetPID()));
|
Memory::Print("PID : " + std::to_string(Memory::GetPID()));
|
||||||
GELua::FindAddresses();
|
GELua::FindAddresses();
|
||||||
/*GameBaseAddr = Memory::GetModuleBase(GameModule);
|
/*GameBaseAddr = Memory::GetModuleBase(GameModule);
|
||||||
DllBaseAddr = Memory::GetModuleBase(DllModule);*/
|
DllBaseAddr = Memory::GetModuleBase(DllModule);*/
|
||||||
TickCountDetour = std::make_unique<Detours>((void*)GELua::GetTickCount, (void*)GetTickCount_D);
|
OpenJITDetour = std::make_unique<Hook<def::lua_open_jit>>(GELua::lua_open_jit, lua_open_jit_D);
|
||||||
TickCountDetour->Attach();
|
OpenJITDetour->Enable();
|
||||||
OpenJITDetour = std::make_unique<Detours>((void*)GELua::lua_open_jit, (void*)lua_open_jit_D);
|
|
||||||
OpenJITDetour->Attach();
|
|
||||||
IPCToLauncher = std::make_unique<IPC>("BeamMP_IN", "BeamMP_Sem3", "BeamMP_Sem4", 0x1900000);
|
IPCToLauncher = std::make_unique<IPC>("BeamMP_IN", "BeamMP_Sem3", "BeamMP_Sem4", 0x1900000);
|
||||||
IPCFromLauncher = std::make_unique<IPC>("BeamMP_OUT", "BeamMP_Sem1", "BeamMP_Sem2", 0x1900000);
|
IPCFromLauncher = std::make_unique<IPC>("BeamMP_OUT", "BeamMP_Sem1", "BeamMP_Sem2", 0x1900000);
|
||||||
|
IPCListener();
|
||||||
}
|
}
|
||||||
|
|
||||||
int Core(lua_State* L) {
|
int Core(lua_State* L) {
|
||||||
if(lua_gettop(L) == 1) {
|
if(lua_gettop(L) == 1) {
|
||||||
size_t Size;
|
size_t Size;
|
||||||
const char* Data = GELua::lua_tolstring(L, 1, &Size);
|
const char* Data = GELua::lua_tolstring(L, 1, &Size);
|
||||||
Memory::Print("Core -> " + std::string(Data) + " - " + std::to_string(Size));
|
//Memory::Print("Core -> " + std::string(Data) + " - " + std::to_string(Size));
|
||||||
std::string msg(Data, Size);
|
std::string msg(Data, Size);
|
||||||
BeamNG::SendIPC("C" + msg);
|
BeamNG::SendIPC("C" + msg);
|
||||||
}
|
}
|
||||||
@ -63,18 +46,28 @@ int Game(lua_State* L) {
|
|||||||
if(lua_gettop(L) == 1) {
|
if(lua_gettop(L) == 1) {
|
||||||
size_t Size;
|
size_t Size;
|
||||||
const char* Data = GELua::lua_tolstring(L, 1, &Size);
|
const char* Data = GELua::lua_tolstring(L, 1, &Size);
|
||||||
Memory::Print("Game -> " + std::string(Data) + " - " + std::to_string(Size));
|
//Memory::Print("Game -> " + std::string(Data) + " - " + std::to_string(Size));
|
||||||
std::string msg(Data, Size);
|
std::string msg(Data, Size);
|
||||||
BeamNG::SendIPC("G" + msg);
|
BeamNG::SendIPC("G" + msg);
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int LuaPop(lua_State* L) {
|
||||||
|
std::string MSG;
|
||||||
|
if (AtomicQueue.try_pop(MSG)) {
|
||||||
|
GELua::lua_push_fstring(L, "%s", MSG.c_str());
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
void BeamNG::RegisterGEFunctions() {
|
void BeamNG::RegisterGEFunctions() {
|
||||||
Memory::Print("Registering GE Functions");
|
Memory::Print("Registering GE Functions");
|
||||||
GELuaTable::Begin(GELua::State);
|
GELuaTable::Begin(GELua::State);
|
||||||
GELuaTable::InsertFunction(GELua::State, "Core", Core);
|
GELuaTable::InsertFunction(GELua::State, "Core", Core);
|
||||||
GELuaTable::InsertFunction(GELua::State, "Game", Game);
|
GELuaTable::InsertFunction(GELua::State, "Game", Game);
|
||||||
|
GELuaTable::InsertFunction(GELua::State, "try_pop", LuaPop);
|
||||||
GELuaTable::End(GELua::State, "MP");
|
GELuaTable::End(GELua::State, "MP");
|
||||||
Memory::Print("Registered!");
|
Memory::Print("Registered!");
|
||||||
}
|
}
|
||||||
@ -83,9 +76,15 @@ void BeamNG::SendIPC(const std::string& Data) {
|
|||||||
IPCToLauncher->send(Data);
|
IPCToLauncher->send(Data);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<Detours> BeamNG::TickCountDetour;
|
void BeamNG::IPCListener() {
|
||||||
std::unique_ptr<Detours> BeamNG::OpenJITDetour;
|
int TimeOuts = 0;
|
||||||
std::unique_ptr<IPC> BeamNG::IPCFromLauncher;
|
while(TimeOuts < 20) {
|
||||||
std::unique_ptr<IPC> BeamNG::IPCToLauncher;
|
IPCFromLauncher->receive();
|
||||||
uint64_t BeamNG::GameBaseAddr;
|
if (!IPCFromLauncher->receive_timed_out()) {
|
||||||
uint64_t BeamNG::DllBaseAddr;
|
TimeOuts = 0;
|
||||||
|
AtomicQueue.push(IPCFromLauncher->msg());
|
||||||
|
IPCFromLauncher->confirm_receive();
|
||||||
|
} else TimeOuts++;
|
||||||
|
}
|
||||||
|
Memory::Print("IPC System shutting down");
|
||||||
|
}
|
||||||
|
@ -1,29 +0,0 @@
|
|||||||
///
|
|
||||||
/// Created by Anonymous275 on 1/21/22
|
|
||||||
/// Copyright (c) 2021-present Anonymous275 read the LICENSE file for more info.
|
|
||||||
///
|
|
||||||
|
|
||||||
#define WIN32_LEAN_AND_MEAN
|
|
||||||
#include "Memory/Detours.h"
|
|
||||||
#include <windows.h>
|
|
||||||
#include "detours/detours.h"
|
|
||||||
|
|
||||||
void Detours::Attach() {
|
|
||||||
if(!Attached){
|
|
||||||
DetourTransactionBegin();
|
|
||||||
DetourUpdateThread(GetCurrentThread());
|
|
||||||
DetourAttach(&targetPtr,detourFunc);
|
|
||||||
DetourTransactionCommit();
|
|
||||||
Attached = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Detours::Detach() {
|
|
||||||
if(Attached){
|
|
||||||
DetourTransactionBegin();
|
|
||||||
DetourUpdateThread(GetCurrentThread());
|
|
||||||
DetourDetach(&targetPtr,detourFunc);
|
|
||||||
DetourTransactionCommit();
|
|
||||||
Attached = false;
|
|
||||||
}
|
|
||||||
}
|
|
@ -10,29 +10,37 @@
|
|||||||
const char* GameModule = "BeamNG.drive.x64.exe";
|
const char* GameModule = "BeamNG.drive.x64.exe";
|
||||||
const char* DllModule = "libbeamng.x64.dll";
|
const char* DllModule = "libbeamng.x64.dll";
|
||||||
|
|
||||||
void GELua::FindAddresses() {
|
std::string GetHex(uint64_t num) {
|
||||||
GELua::State = nullptr;
|
char buffer[30];
|
||||||
GetTickCount = reinterpret_cast<def::GetTickCount>(Memory::FindPattern(GameModule, Patterns::GetTickCount));
|
sprintf(buffer, "%llx", num);
|
||||||
lua_open_jit = reinterpret_cast<def::lua_open_jit>(Memory::FindPattern(GameModule, Patterns::open_jit));
|
return std::string{buffer};
|
||||||
lua_push_fstring = reinterpret_cast<def::lua_push_fstring>(Memory::FindPattern(GameModule, Patterns::push_fstring));
|
|
||||||
lua_get_field = reinterpret_cast<def::lua_get_field>(Memory::FindPattern(GameModule, Patterns::get_field));
|
|
||||||
lua_p_call = reinterpret_cast<def::lua_p_call>(Memory::FindPattern(GameModule, Patterns::p_call));
|
|
||||||
lua_createtable = reinterpret_cast<def::lua_createtable>(Memory::FindPattern(GameModule, Patterns::lua_createtable));
|
|
||||||
lua_pushcclosure = reinterpret_cast<def::lua_pushcclosure>(Memory::FindPattern(GameModule, Patterns::lua_pushcclosure));
|
|
||||||
lua_setfield = reinterpret_cast<def::lua_setfield>(Memory::FindPattern(GameModule, Patterns::lua_setfield));
|
|
||||||
lua_settable = reinterpret_cast<def::lua_settable>(Memory::FindPattern(GameModule, Patterns::lua_settable));
|
|
||||||
lua_tolstring = reinterpret_cast<def::lua_tolstring>(Memory::FindPattern(GameModule, Patterns::lua_tolstring));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GELua::FindAddresses() {
|
||||||
def::GetTickCount GELua::GetTickCount;
|
GELua::State = nullptr;
|
||||||
def::lua_open_jit GELua::lua_open_jit;
|
auto Base = Memory::GetModuleBase(GameModule);
|
||||||
def::lua_push_fstring GELua::lua_push_fstring;
|
GetTickCount = reinterpret_cast<def::GetTickCount>(Memory::FindPattern(GameModule, Patterns::GetTickCount));
|
||||||
def::lua_get_field GELua::lua_get_field;
|
Memory::Print("GetTickCount -> " + GetHex(reinterpret_cast<uint64_t>(GetTickCount) - Base));
|
||||||
def::lua_p_call GELua::lua_p_call;
|
lua_open_jit = reinterpret_cast<def::lua_open_jit>(Memory::FindPattern(GameModule, Patterns::open_jit));
|
||||||
def::lua_createtable GELua::lua_createtable;
|
Memory::Print("lua_open_jit -> " + GetHex(reinterpret_cast<uint64_t>(lua_open_jit) - Base));
|
||||||
def::lua_pushcclosure GELua::lua_pushcclosure;
|
lua_push_fstring = reinterpret_cast<def::lua_push_fstring>(Memory::FindPattern(GameModule, Patterns::push_fstring));
|
||||||
def::lua_setfield GELua::lua_setfield;
|
Memory::Print("lua_push_fstring -> " + GetHex(reinterpret_cast<uint64_t>(lua_push_fstring) - Base));
|
||||||
def::lua_settable GELua::lua_settable;
|
lua_get_field = reinterpret_cast<def::lua_get_field>(Memory::FindPattern(GameModule, Patterns::get_field));
|
||||||
def::lua_tolstring GELua::lua_tolstring;
|
Memory::Print("lua_get_field -> " + GetHex(reinterpret_cast<uint64_t>(lua_get_field) - Base));
|
||||||
lua_State* GELua::State;
|
lua_p_call = reinterpret_cast<def::lua_p_call>(Memory::FindPattern(GameModule, Patterns::p_call));
|
||||||
|
Memory::Print("lua_p_call -> " + GetHex(reinterpret_cast<uint64_t>(lua_p_call) - Base));
|
||||||
|
lua_createtable = reinterpret_cast<def::lua_createtable>(Memory::FindPattern(GameModule, Patterns::lua_createtable));
|
||||||
|
Memory::Print("lua_createtable -> " + GetHex(reinterpret_cast<uint64_t>(lua_createtable) - Base));
|
||||||
|
lua_pushcclosure = reinterpret_cast<def::lua_pushcclosure>(Memory::FindPattern(GameModule, Patterns::lua_pushcclosure));
|
||||||
|
Memory::Print("lua_pushcclosure -> " + GetHex(reinterpret_cast<uint64_t>(lua_pushcclosure) - Base));
|
||||||
|
lua_setfield = reinterpret_cast<def::lua_setfield>(Memory::FindPattern(GameModule, Patterns::lua_setfield));
|
||||||
|
Memory::Print("lua_setfield -> " + GetHex(reinterpret_cast<uint64_t>(lua_setfield) - Base));
|
||||||
|
lua_settable = reinterpret_cast<def::lua_settable>(Memory::FindPattern(GameModule, Patterns::lua_settable));
|
||||||
|
Memory::Print("lua_settable -> " + GetHex(reinterpret_cast<uint64_t>(lua_settable) - Base));
|
||||||
|
lua_tolstring = reinterpret_cast<def::lua_tolstring>(Memory::FindPattern(GameModule, Patterns::lua_tolstring));
|
||||||
|
Memory::Print("lua_tolstring -> " + GetHex(reinterpret_cast<uint64_t>(lua_tolstring) - Base));
|
||||||
|
GEUpdate = reinterpret_cast<def::GEUpdate>(Memory::FindPattern(GameModule, Patterns::GEUpdate));
|
||||||
|
Memory::Print("GEUpdate -> " + GetHex(reinterpret_cast<uint64_t>(GEUpdate) - Base));
|
||||||
|
lua_settop = reinterpret_cast<def::lua_settop>(Memory::FindPattern(GameModule, Patterns::lua_settop));
|
||||||
|
Memory::Print("lua_settop -> " + GetHex(reinterpret_cast<uint64_t>(lua_settop) - Base));
|
||||||
|
}
|
||||||
|
@ -10,11 +10,11 @@
|
|||||||
IPC::IPC(const char* MemID, const char* SemID, const char* SemID2, size_t Size) noexcept : Size_(Size) {
|
IPC::IPC(const char* MemID, const char* SemID, const char* SemID2, size_t Size) noexcept : Size_(Size) {
|
||||||
SemHandle_ = OpenSemaphoreA(SYNCHRONIZE | SEMAPHORE_MODIFY_STATE, FALSE, SemID);
|
SemHandle_ = OpenSemaphoreA(SYNCHRONIZE | SEMAPHORE_MODIFY_STATE, FALSE, SemID);
|
||||||
if(SemHandle_ == nullptr) {
|
if(SemHandle_ == nullptr) {
|
||||||
SemHandle_ = CreateSemaphoreA(nullptr, 0, 2, SemID);
|
SemHandle_ = CreateSemaphoreA(nullptr, 0, 1, SemID);
|
||||||
}
|
}
|
||||||
SemConfHandle_ = OpenSemaphoreA(SYNCHRONIZE | SEMAPHORE_MODIFY_STATE, FALSE, SemID2);
|
SemConfHandle_ = OpenSemaphoreA(SYNCHRONIZE | SEMAPHORE_MODIFY_STATE, FALSE, SemID2);
|
||||||
if(SemConfHandle_ == nullptr) {
|
if(SemConfHandle_ == nullptr) {
|
||||||
SemConfHandle_ = CreateSemaphoreA(nullptr, 0, 2, SemID2);
|
SemConfHandle_ = CreateSemaphoreA(nullptr, 0, 1, SemID2);
|
||||||
}
|
}
|
||||||
MemoryHandle_ = OpenFileMappingA(FILE_MAP_ALL_ACCESS, FALSE, MemID);
|
MemoryHandle_ = OpenFileMappingA(FILE_MAP_ALL_ACCESS, FALSE, MemID);
|
||||||
if(MemoryHandle_ == nullptr) {
|
if(MemoryHandle_ == nullptr) {
|
||||||
|
@ -5,12 +5,12 @@
|
|||||||
|
|
||||||
#define WIN32_LEAN_AND_MEAN
|
#define WIN32_LEAN_AND_MEAN
|
||||||
#undef UNICODE
|
#undef UNICODE
|
||||||
#include <string>
|
|
||||||
#include <windows.h>
|
|
||||||
#include <tlhelp32.h>
|
|
||||||
#include <psapi.h>
|
|
||||||
#include "Memory/Memory.h"
|
#include "Memory/Memory.h"
|
||||||
#include "Memory/BeamNG.h"
|
#include "Memory/BeamNG.h"
|
||||||
|
#include <string>
|
||||||
|
#include <tlhelp32.h>
|
||||||
|
#include <psapi.h>
|
||||||
|
|
||||||
|
|
||||||
uint32_t Memory::GetBeamNGPID() {
|
uint32_t Memory::GetBeamNGPID() {
|
||||||
SetLastError(0);
|
SetLastError(0);
|
||||||
@ -46,7 +46,6 @@ uint64_t Memory::FindPattern(const char* module, const char* Pattern[]) {
|
|||||||
auto base = uint64_t(mInfo.lpBaseOfDll);
|
auto base = uint64_t(mInfo.lpBaseOfDll);
|
||||||
auto size = uint32_t(mInfo.SizeOfImage);
|
auto size = uint32_t(mInfo.SizeOfImage);
|
||||||
auto len = strlen(Pattern[1]);
|
auto len = strlen(Pattern[1]);
|
||||||
|
|
||||||
for(auto i = 0; i < size - len; i++) {
|
for(auto i = 0; i < size - len; i++) {
|
||||||
bool found = true;
|
bool found = true;
|
||||||
for(auto j = 0; j < len && found; j++) {
|
for(auto j = 0; j < len && found; j++) {
|
||||||
@ -56,7 +55,6 @@ uint64_t Memory::FindPattern(const char* module, const char* Pattern[]) {
|
|||||||
return base+i;
|
return base+i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,7 +47,6 @@ void Server::TCPClientMain() {
|
|||||||
char Code = 'C';
|
char Code = 'C';
|
||||||
send(TCPSocket, &Code, 1, 0);
|
send(TCPSocket, &Code, 1, 0);
|
||||||
SyncResources();
|
SyncResources();
|
||||||
UDPConnection = std::thread(&Server::UDPMain, this);
|
|
||||||
while(!Terminate.load()) {
|
while(!Terminate.load()) {
|
||||||
ServerParser(TCPRcv());
|
ServerParser(TCPRcv());
|
||||||
}
|
}
|
||||||
@ -55,6 +54,13 @@ void Server::TCPClientMain() {
|
|||||||
KillSocket(TCPSocket);
|
KillSocket(TCPSocket);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Server::StartUDP() {
|
||||||
|
if(TCPConnection.joinable() && !UDPConnection.joinable()) {
|
||||||
|
LOG(INFO) << "Connecting UDP";
|
||||||
|
UDPConnection = std::thread(&Server::UDPMain, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Server::UDPSend(std::string Data) {
|
void Server::UDPSend(std::string Data) {
|
||||||
if(ClientID == -1 || UDPSocket == -1)return;
|
if(ClientID == -1 || UDPSocket == -1)return;
|
||||||
if(Data.length() > 400){
|
if(Data.length() > 400){
|
||||||
@ -153,6 +159,8 @@ void Server::PingLoop() {
|
|||||||
|
|
||||||
void Server::Close() {
|
void Server::Close() {
|
||||||
Terminate.store(true);
|
Terminate.store(true);
|
||||||
|
KillSocket(TCPSocket);
|
||||||
|
KillSocket(UDPSocket);
|
||||||
Ping = -1;
|
Ping = -1;
|
||||||
if(TCPConnection.joinable()) {
|
if(TCPConnection.joinable()) {
|
||||||
TCPConnection.join();
|
TCPConnection.join();
|
||||||
@ -163,8 +171,6 @@ void Server::Close() {
|
|||||||
if(AutoPing.joinable()) {
|
if(AutoPing.joinable()) {
|
||||||
AutoPing.join();
|
AutoPing.join();
|
||||||
}
|
}
|
||||||
KillSocket(TCPSocket);
|
|
||||||
KillSocket(UDPSocket);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string &Server::getMap() {
|
const std::string &Server::getMap() {
|
||||||
|
@ -14,7 +14,7 @@ int main(int argc, char* argv[]) {
|
|||||||
launcher.CheckKey();
|
launcher.CheckKey();
|
||||||
launcher.QueryRegistry();
|
launcher.QueryRegistry();
|
||||||
//UI call
|
//UI call
|
||||||
launcher.SetupMOD();
|
//launcher.SetupMOD();
|
||||||
launcher.LaunchGame();
|
launcher.LaunchGame();
|
||||||
launcher.WaitForGame();
|
launcher.WaitForGame();
|
||||||
LOG(INFO) << "Launcher shutting down";
|
LOG(INFO) << "Launcher shutting down";
|
||||||
|
Loading…
x
Reference in New Issue
Block a user