2019-04-13 16:21:56 +00:00
|
|
|
/* Copyright (C) 2019 Mr Goldberg
|
|
|
|
This file is part of the Goldberg Emulator
|
|
|
|
|
|
|
|
The Goldberg Emulator is free software; you can redistribute it and/or
|
|
|
|
modify it under the terms of the GNU Lesser General Public
|
|
|
|
License as published by the Free Software Foundation; either
|
|
|
|
version 3 of the License, or (at your option) any later version.
|
|
|
|
|
|
|
|
The Goldberg Emulator is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
Lesser General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU Lesser General Public
|
|
|
|
License along with the Goldberg Emulator; if not, see
|
|
|
|
<http://www.gnu.org/licenses/>. */
|
|
|
|
|
|
|
|
#ifndef BASE_INCLUDE
|
|
|
|
#define BASE_INCLUDE
|
|
|
|
|
|
|
|
#if defined(WIN32) || defined(_WIN32)
|
|
|
|
#define STEAM_WIN32
|
|
|
|
#pragma warning( disable : 4716)
|
2019-04-13 16:29:32 +00:00
|
|
|
#ifndef NOMINMAX
|
|
|
|
# define NOMINMAX
|
|
|
|
#endif
|
2019-04-13 16:21:56 +00:00
|
|
|
#endif
|
|
|
|
|
|
|
|
#define STEAM_API_EXPORTS
|
|
|
|
#include "../sdk_includes/steam_gameserver.h"
|
|
|
|
#include "../sdk_includes/steamdatagram_tickets.h"
|
|
|
|
|
|
|
|
#include <algorithm>
|
|
|
|
#include <vector>
|
|
|
|
#include <map>
|
|
|
|
#include <mutex>
|
|
|
|
|
|
|
|
//#define PRINT_DEBUG(...) {FILE *t = fopen("STEAM_LOG.txt", "a"); fprintf(t, __VA_ARGS__); fclose(t);}
|
|
|
|
#ifdef STEAM_WIN32
|
|
|
|
#include <winsock2.h>
|
|
|
|
#include <windows.h>
|
|
|
|
#include <ws2tcpip.h>
|
|
|
|
#include <processthreadsapi.h>
|
|
|
|
EXTERN_C IMAGE_DOS_HEADER __ImageBase;
|
|
|
|
#define PATH_SEPARATOR "\\"
|
|
|
|
#ifndef EMU_RELEASE_BUILD
|
|
|
|
#define PRINT_DEBUG(a, ...) do {FILE *t = fopen("STEAM_LOG.txt", "a"); fprintf(t, "%u " a, GetCurrentThreadId(), __VA_ARGS__); fclose(t); WSASetLastError(0);} while (0)
|
|
|
|
#endif
|
|
|
|
#else
|
2019-04-21 20:48:32 +00:00
|
|
|
#include <arpa/inet.h>
|
2019-04-13 16:21:56 +00:00
|
|
|
#define PATH_SEPARATOR "/"
|
|
|
|
#ifndef EMU_RELEASE_BUILD
|
|
|
|
#define PRINT_DEBUG(...) {FILE *t = fopen("STEAM_LOG.txt", "a"); fprintf(t, __VA_ARGS__); fclose(t);}
|
|
|
|
#endif
|
|
|
|
#endif
|
|
|
|
//#define PRINT_DEBUG(...) fprintf(stdout, __VA_ARGS__)
|
|
|
|
#ifdef EMU_RELEASE_BUILD
|
|
|
|
#define PRINT_DEBUG(...)
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#include "settings.h"
|
|
|
|
#include "local_storage.h"
|
|
|
|
#include "network.h"
|
|
|
|
|
|
|
|
#include "defines.h"
|
|
|
|
|
|
|
|
#define PUSH_BACK_IF_NOT_IN(vector, element) { if(std::find(vector.begin(), vector.end(), element) == vector.end()) vector.push_back(element); }
|
|
|
|
|
|
|
|
extern std::recursive_mutex global_mutex;
|
|
|
|
|
|
|
|
std::string get_env_variable(std::string name);
|
|
|
|
|
|
|
|
class CCallbackMgr
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
static void SetRegister(class CCallbackBase *pCallback, int iCallback) {
|
|
|
|
pCallback->m_nCallbackFlags |= CCallbackBase::k_ECallbackFlagsRegistered;
|
|
|
|
pCallback->m_iCallback = iCallback;
|
|
|
|
};
|
|
|
|
|
|
|
|
static void SetUnregister(class CCallbackBase *pCallback) {
|
|
|
|
if (pCallback)
|
|
|
|
pCallback->m_nCallbackFlags &= !CCallbackBase::k_ECallbackFlagsRegistered;
|
|
|
|
};
|
|
|
|
|
|
|
|
static bool isServer(class CCallbackBase *pCallback) {
|
|
|
|
return (pCallback->m_nCallbackFlags & CCallbackBase::k_ECallbackFlagsGameServer) != 0;
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
#define STEAM_CALLRESULT_TIMEOUT 120.0
|
|
|
|
struct Steam_Call_Result {
|
|
|
|
Steam_Call_Result(SteamAPICall_t a, int icb, void *r, unsigned int s, double r_in, bool run_cc_cb) {
|
|
|
|
api_call = a;
|
|
|
|
result.resize(s);
|
2019-06-17 21:08:23 +00:00
|
|
|
if (s > 0 && r != NULL)
|
|
|
|
memcpy(&(result[0]), r, s);
|
2019-04-13 16:21:56 +00:00
|
|
|
created = std::chrono::high_resolution_clock::now();
|
|
|
|
run_in = r_in;
|
|
|
|
run_call_completed_cb = run_cc_cb;
|
|
|
|
iCallback = icb;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool operator==(const struct Steam_Call_Result& a)
|
|
|
|
{
|
|
|
|
return a.api_call == api_call && a.callbacks == callbacks;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool timed_out() {
|
|
|
|
return check_timedout(created, STEAM_CALLRESULT_TIMEOUT);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool can_execute() {
|
|
|
|
return (!reserved) && (!to_delete) && check_timedout(created, run_in);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool has_cb() {
|
|
|
|
return callbacks.size() > 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
SteamAPICall_t api_call;
|
|
|
|
std::vector<class CCallbackBase *> callbacks;
|
|
|
|
std::vector<char> result;
|
|
|
|
bool to_delete = false;
|
|
|
|
bool reserved = false;
|
|
|
|
std::chrono::high_resolution_clock::time_point created;
|
|
|
|
double run_in;
|
|
|
|
bool run_call_completed_cb;
|
|
|
|
int iCallback;
|
|
|
|
};
|
|
|
|
|
|
|
|
int generate_random_int();
|
|
|
|
SteamAPICall_t generate_steam_api_call_id();
|
|
|
|
CSteamID generate_steam_id_user();
|
|
|
|
CSteamID generate_steam_id_server();
|
|
|
|
CSteamID generate_steam_id_anonserver();
|
|
|
|
CSteamID generate_steam_id_lobby();
|
|
|
|
std::string get_full_program_path();
|
2019-05-09 12:10:03 +00:00
|
|
|
std::string get_current_path();
|
|
|
|
std::string canonical_path(std::string path);
|
2019-04-13 16:21:56 +00:00
|
|
|
|
|
|
|
class SteamCallResults {
|
|
|
|
std::vector<struct Steam_Call_Result> callresults;
|
|
|
|
std::vector<class CCallbackBase *> completed_callbacks;
|
|
|
|
|
|
|
|
public:
|
|
|
|
void addCallCompleted(class CCallbackBase *cb) {
|
|
|
|
if (std::find(completed_callbacks.begin(), completed_callbacks.end(), cb) == completed_callbacks.end()) {
|
|
|
|
completed_callbacks.push_back(cb);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void rmCallCompleted(class CCallbackBase *cb) {
|
|
|
|
auto c = std::find(completed_callbacks.begin(), completed_callbacks.end(), cb);
|
|
|
|
if (c != completed_callbacks.end()) {
|
|
|
|
completed_callbacks.erase(c);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void addCallBack(SteamAPICall_t api_call, class CCallbackBase *cb) {
|
|
|
|
auto cb_result = std::find_if(callresults.begin(), callresults.end(), [api_call](struct Steam_Call_Result const& item) { return item.api_call == api_call; });
|
|
|
|
if (cb_result != callresults.end()) {
|
|
|
|
cb_result->callbacks.push_back(cb);
|
|
|
|
CCallbackMgr::SetRegister(cb, cb->GetICallback());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool exists(SteamAPICall_t api_call) {
|
|
|
|
auto cr = std::find_if(callresults.begin(), callresults.end(), [api_call](struct Steam_Call_Result const& item) { return item.api_call == api_call; });
|
|
|
|
if (cr == callresults.end()) return false;
|
|
|
|
if (cr->reserved) return false;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool callback_result(SteamAPICall_t api_call, void *copy_to, unsigned int size) {
|
|
|
|
auto cb_result = std::find_if(callresults.begin(), callresults.end(), [api_call](struct Steam_Call_Result const& item) { return item.api_call == api_call; });
|
|
|
|
if (cb_result != callresults.end()) {
|
|
|
|
if (cb_result->reserved) return false;
|
|
|
|
if (cb_result->result.size() > size) return false;
|
|
|
|
|
|
|
|
memcpy(copy_to, &(cb_result->result[0]), cb_result->result.size());
|
|
|
|
cb_result->to_delete = true;
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void rmCallBack(SteamAPICall_t api_call, class CCallbackBase *cb) {
|
|
|
|
auto cb_result = std::find_if(callresults.begin(), callresults.end(), [api_call](struct Steam_Call_Result const& item) { return item.api_call == api_call; });
|
|
|
|
if (cb_result != callresults.end()) {
|
|
|
|
auto it = std::find(cb_result->callbacks.begin(), cb_result->callbacks.end(), cb);
|
|
|
|
if (it != cb_result->callbacks.end()) {
|
|
|
|
cb_result->callbacks.erase(it);
|
|
|
|
CCallbackMgr::SetUnregister(cb);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void rmCallBack(class CCallbackBase *cb) {
|
|
|
|
//TODO: check if callback is callback or call result?
|
|
|
|
for (auto & cr: callresults) {
|
|
|
|
auto it = std::find(cr.callbacks.begin(), cr.callbacks.end(), cb);
|
|
|
|
if (it != cr.callbacks.end()) {
|
|
|
|
cr.callbacks.erase(it);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cr.callbacks.size() == 0) {
|
|
|
|
cr.to_delete = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
SteamAPICall_t addCallResult(SteamAPICall_t api_call, int iCallback, void *result, unsigned int size, double timeout=0.0, bool run_call_completed_cb=true) {
|
|
|
|
auto cb_result = std::find_if(callresults.begin(), callresults.end(), [api_call](struct Steam_Call_Result const& item) { return item.api_call == api_call; });
|
|
|
|
if (cb_result != callresults.end()) {
|
|
|
|
if (cb_result->reserved) {
|
|
|
|
std::vector<class CCallbackBase *> temp_cbs = cb_result->callbacks;
|
|
|
|
*cb_result = Steam_Call_Result(api_call, iCallback, result, size, timeout, run_call_completed_cb);
|
|
|
|
cb_result->callbacks = temp_cbs;
|
|
|
|
return cb_result->api_call;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
struct Steam_Call_Result res = Steam_Call_Result(api_call, iCallback, result, size, timeout, run_call_completed_cb);
|
|
|
|
callresults.push_back(res);
|
|
|
|
return callresults.back().api_call;
|
|
|
|
}
|
|
|
|
|
|
|
|
PRINT_DEBUG("addCallResult ERROR\n");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
SteamAPICall_t reserveCallResult() {
|
|
|
|
struct Steam_Call_Result res = Steam_Call_Result(generate_steam_api_call_id(), 0, NULL, 0, 0.0, true);
|
|
|
|
res.reserved = true;
|
|
|
|
callresults.push_back(res);
|
|
|
|
return callresults.back().api_call;
|
|
|
|
}
|
|
|
|
|
|
|
|
SteamAPICall_t addCallResult(int iCallback, void *result, unsigned int size, double timeout=0.0, bool run_call_completed_cb=true) {
|
|
|
|
return addCallResult(generate_steam_api_call_id(), iCallback, result, size, timeout, run_call_completed_cb);
|
|
|
|
}
|
|
|
|
|
|
|
|
void runCallResults() {
|
|
|
|
unsigned long current_size = callresults.size();
|
|
|
|
for (unsigned i = 0; i < current_size; ++i) {
|
|
|
|
unsigned index = i;
|
|
|
|
|
|
|
|
if (!callresults[index].to_delete) {
|
|
|
|
if (callresults[index].can_execute()) {
|
|
|
|
std::vector<char> result = callresults[index].result;
|
|
|
|
SteamAPICall_t api_call = callresults[index].api_call;
|
|
|
|
bool run_call_completed_cb = callresults[index].run_call_completed_cb;
|
|
|
|
int iCallback = callresults[index].iCallback;
|
|
|
|
if (run_call_completed_cb) {
|
|
|
|
callresults[index].run_call_completed_cb = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (callresults[index].has_cb()) {
|
|
|
|
std::vector<class CCallbackBase *> temp_cbs = callresults[index].callbacks;
|
|
|
|
callresults[index].to_delete = true;
|
|
|
|
for (auto & cb : temp_cbs) {
|
|
|
|
PRINT_DEBUG("Calling callresult %p %i\n", cb, cb->GetICallback());
|
|
|
|
global_mutex.unlock();
|
|
|
|
//TODO: unlock relock doesn't work if mutex was locked more than once.
|
|
|
|
if (run_call_completed_cb) { //run the right function depending on if it's a callback or a call result.
|
|
|
|
cb->Run(&(result[0]), false, api_call);
|
|
|
|
} else {
|
|
|
|
cb->Run(&(result[0]));
|
|
|
|
}
|
|
|
|
//COULD BE DELETED SO DON'T TOUCH CB
|
|
|
|
global_mutex.lock();
|
|
|
|
PRINT_DEBUG("callresult done\n");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (run_call_completed_cb) {
|
|
|
|
//can it happen that one is removed during the callback?
|
|
|
|
std::vector<class CCallbackBase *> callbacks = completed_callbacks;
|
|
|
|
for (auto & cb: callbacks) {
|
|
|
|
SteamAPICallCompleted_t data;
|
|
|
|
data.m_hAsyncCall = api_call;
|
|
|
|
data.m_iCallback = iCallback;
|
|
|
|
data.m_cubParam = result.size();
|
|
|
|
|
|
|
|
PRINT_DEBUG("Call complete cb %i %p %llu\n", iCallback, cb, api_call);
|
|
|
|
//TODO: check if this is a problem or not.
|
|
|
|
global_mutex.unlock();
|
|
|
|
cb->Run(&data);
|
|
|
|
global_mutex.lock();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (callresults[index].timed_out()) {
|
|
|
|
callresults[index].to_delete = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
PRINT_DEBUG("runCallResults erase to_delete\n");
|
|
|
|
auto c = std::begin(callresults);
|
|
|
|
while (c != std::end(callresults)) {
|
|
|
|
if (c->to_delete) {
|
|
|
|
if (c->timed_out()) {
|
|
|
|
c = callresults.erase(c);
|
|
|
|
} else {
|
|
|
|
++c;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
++c;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
struct Steam_Call_Back {
|
|
|
|
std::vector<class CCallbackBase *> callbacks;
|
|
|
|
std::vector<std::vector<char>> results;
|
|
|
|
};
|
|
|
|
|
|
|
|
class SteamCallBacks {
|
|
|
|
std::map<int, struct Steam_Call_Back> callbacks;
|
|
|
|
SteamCallResults *results;
|
|
|
|
public:
|
|
|
|
SteamCallBacks(SteamCallResults *results) {
|
|
|
|
this->results = results;
|
|
|
|
}
|
|
|
|
|
|
|
|
void addCallBack(int iCallback, class CCallbackBase *cb) {
|
|
|
|
PRINT_DEBUG("addCallBack %i\n", iCallback);
|
|
|
|
if (iCallback == SteamAPICallCompleted_t::k_iCallback) {
|
|
|
|
results->addCallCompleted(cb);
|
|
|
|
CCallbackMgr::SetRegister(cb, iCallback);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (std::find(callbacks[iCallback].callbacks.begin(), callbacks[iCallback].callbacks.end(), cb) == callbacks[iCallback].callbacks.end()) {
|
|
|
|
callbacks[iCallback].callbacks.push_back(cb);
|
|
|
|
CCallbackMgr::SetRegister(cb, iCallback);
|
|
|
|
for (auto & res: callbacks[iCallback].results) {
|
|
|
|
//TODO: timeout?
|
|
|
|
SteamAPICall_t api_id = results->addCallResult(iCallback, &(res[0]), res.size(), 0.0, false);
|
|
|
|
results->addCallBack(api_id, cb);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void addCBResult(int iCallback, void *result, unsigned int size, double timeout, bool dont_post_if_already) {
|
|
|
|
if (dont_post_if_already) {
|
|
|
|
for (auto & r : callbacks[iCallback].results) {
|
|
|
|
if (r.size() == size) {
|
|
|
|
if (memcmp(&(r[0]), result, size) == 0) {
|
|
|
|
//cb already posted
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
std::vector<char> temp;
|
|
|
|
temp.resize(size);
|
|
|
|
memcpy(&(temp[0]), result, size);
|
|
|
|
callbacks[iCallback].results.push_back(temp);
|
|
|
|
for (auto cb: callbacks[iCallback].callbacks) {
|
|
|
|
SteamAPICall_t api_id = results->addCallResult(iCallback, result, size, timeout, false);
|
|
|
|
results->addCallBack(api_id, cb);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void addCBResult(int iCallback, void *result, unsigned int size) {
|
|
|
|
addCBResult(iCallback, result, size, 0.0, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
void addCBResult(int iCallback, void *result, unsigned int size, bool dont_post_if_already) {
|
|
|
|
addCBResult(iCallback, result, size, 0.0, dont_post_if_already);
|
|
|
|
}
|
|
|
|
|
|
|
|
void addCBResult(int iCallback, void *result, unsigned int size, double timeout) {
|
|
|
|
addCBResult(iCallback, result, size, timeout, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
void rmCallBack(int iCallback, class CCallbackBase *cb) {
|
|
|
|
if (iCallback == SteamAPICallCompleted_t::k_iCallback) {
|
|
|
|
results->rmCallCompleted(cb);
|
|
|
|
CCallbackMgr::SetUnregister(cb);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto c = std::find(callbacks[iCallback].callbacks.begin(), callbacks[iCallback].callbacks.end(), cb);
|
|
|
|
if (c != callbacks[iCallback].callbacks.end()) {
|
|
|
|
callbacks[iCallback].callbacks.erase(c);
|
|
|
|
CCallbackMgr::SetUnregister(cb);
|
|
|
|
results->rmCallBack(cb);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void runCallBacks() {
|
|
|
|
for (auto & c : callbacks) {
|
|
|
|
std::vector<std::vector<char>> res_back = c.second.results;
|
|
|
|
c.second.results.clear();
|
|
|
|
for (auto r : res_back) {
|
|
|
|
for (auto cb: c.second.callbacks) {
|
|
|
|
//PRINT_DEBUG("Calling callback %i\n", cb->GetICallback());
|
|
|
|
//cb->Run(&(r[0]));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
struct Auth_Ticket_Data {
|
|
|
|
CSteamID id;
|
|
|
|
uint64 number;
|
|
|
|
};
|
|
|
|
|
|
|
|
class Auth_Ticket_Manager {
|
|
|
|
class Settings *settings;
|
|
|
|
class Networking *network;
|
|
|
|
class SteamCallBacks *callbacks;
|
|
|
|
|
|
|
|
void launch_callback(CSteamID id, EAuthSessionResponse resp);
|
|
|
|
void launch_callback_gs(CSteamID id, bool approved);
|
|
|
|
std::vector<struct Auth_Ticket_Data> inbound, outbound;
|
|
|
|
public:
|
|
|
|
Auth_Ticket_Manager(class Settings *settings, class Networking *network, class SteamCallBacks *callbacks);
|
|
|
|
|
|
|
|
void Callback(Common_Message *msg);
|
|
|
|
uint32 getTicket( void *pTicket, int cbMaxTicket, uint32 *pcbTicket );
|
|
|
|
void cancelTicket(uint32 number);
|
|
|
|
EBeginAuthSessionResult beginAuth(const void *pAuthTicket, int cbAuthTicket, CSteamID steamID);
|
|
|
|
bool endAuth(CSteamID id);
|
|
|
|
uint32 countInboundAuth();
|
|
|
|
bool SendUserConnectAndAuthenticate( uint32 unIPClient, const void *pvAuthBlob, uint32 cubAuthBlobSize, CSteamID *pSteamIDUser );
|
|
|
|
CSteamID fakeUser();
|
|
|
|
Auth_Ticket_Data getTicketData( void *pTicket, int cbMaxTicket, uint32 *pcbTicket );
|
|
|
|
};
|
|
|
|
|
|
|
|
struct RunCBs {
|
|
|
|
void (*function)(void *object);
|
|
|
|
void *object;
|
|
|
|
};
|
|
|
|
|
|
|
|
class RunEveryRunCB {
|
|
|
|
std::vector<struct RunCBs> cbs;
|
|
|
|
public:
|
|
|
|
void add(void (*cb)(void *object), void *object) {
|
|
|
|
remove(cb, object);
|
|
|
|
RunCBs rcb;
|
|
|
|
rcb.function = cb;
|
|
|
|
rcb.object = object;
|
|
|
|
cbs.push_back(rcb);
|
|
|
|
}
|
|
|
|
|
|
|
|
void remove(void (*cb)(void *object), void *object) {
|
|
|
|
auto c = std::begin(cbs);
|
|
|
|
while (c != std::end(cbs)) {
|
|
|
|
if (c->function == cb && c->object == object) {
|
|
|
|
c = cbs.erase(c);
|
|
|
|
} else {
|
|
|
|
++c;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void run() {
|
|
|
|
std::vector<struct RunCBs> temp_cbs = cbs;
|
|
|
|
for (auto c : temp_cbs) {
|
|
|
|
c.function(c.object);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
#ifdef EMU_EXPERIMENTAL_BUILD
|
|
|
|
bool crack_SteamAPI_RestartAppIfNecessary(uint32 unOwnAppID);
|
|
|
|
bool crack_SteamAPI_Init();
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#endif
|