Jessica James
4 years ago
commit
389794f82f
23 changed files with 1666 additions and 0 deletions
@ -0,0 +1,3 @@ |
|||
.vs/ |
|||
Debug/ |
|||
Release/ |
@ -0,0 +1,28 @@ |
|||
|
|||
Microsoft Visual Studio Solution File, Format Version 12.00 |
|||
# Visual Studio 15 |
|||
VisualStudioVersion = 15.0.26228.9 |
|||
MinimumVisualStudioVersion = 10.0.40219.1 |
|||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Rx_TCPLink", "Rx_TCPLink\Rx_TCPLink.vcxproj", "{988779E2-4B8D-4997-82B6-25BC4E0A705E}" |
|||
EndProject |
|||
Global |
|||
GlobalSection(SolutionConfigurationPlatforms) = preSolution |
|||
Debug|x64 = Debug|x64 |
|||
Debug|x86 = Debug|x86 |
|||
Release|x64 = Release|x64 |
|||
Release|x86 = Release|x86 |
|||
EndGlobalSection |
|||
GlobalSection(ProjectConfigurationPlatforms) = postSolution |
|||
{988779E2-4B8D-4997-82B6-25BC4E0A705E}.Debug|x64.ActiveCfg = Debug|x64 |
|||
{988779E2-4B8D-4997-82B6-25BC4E0A705E}.Debug|x64.Build.0 = Debug|x64 |
|||
{988779E2-4B8D-4997-82B6-25BC4E0A705E}.Debug|x86.ActiveCfg = Debug|Win32 |
|||
{988779E2-4B8D-4997-82B6-25BC4E0A705E}.Debug|x86.Build.0 = Debug|Win32 |
|||
{988779E2-4B8D-4997-82B6-25BC4E0A705E}.Release|x64.ActiveCfg = Release|x64 |
|||
{988779E2-4B8D-4997-82B6-25BC4E0A705E}.Release|x64.Build.0 = Release|x64 |
|||
{988779E2-4B8D-4997-82B6-25BC4E0A705E}.Release|x86.ActiveCfg = Release|Win32 |
|||
{988779E2-4B8D-4997-82B6-25BC4E0A705E}.Release|x86.Build.0 = Release|Win32 |
|||
EndGlobalSection |
|||
GlobalSection(SolutionProperties) = preSolution |
|||
HideSolutionNode = FALSE |
|||
EndGlobalSection |
|||
EndGlobal |
@ -0,0 +1,149 @@ |
|||
#define _CRT_SECURE_NO_WARNINGS |
|||
|
|||
#include "DiscordRpc.h" |
|||
#include <cstdlib> |
|||
#include <cstring> |
|||
#include <chrono> |
|||
#include "discord_rpc.h" |
|||
|
|||
static constexpr int GDI_TEAM_NUM = 0; |
|||
static constexpr int NOD_TEAM_NUM = 1; |
|||
|
|||
int64_t now_seconds() { |
|||
return std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count(); |
|||
} |
|||
|
|||
extern "C" __declspec(dllexport) void UpdateDiscordRPC(wchar_t* in_server_name, wchar_t* in_level_name, int in_player_count, int in_max_players, int in_team_num, int in_time_elapsed, int in_time_remaining, int in_is_firestorm, wchar_t* in_image_name) { |
|||
// Convert our input strings
|
|||
char server_name[128]; |
|||
size_t server_name_length{}; |
|||
char level_name[128]; |
|||
size_t level_name_length{}; |
|||
char image_name[128]; |
|||
size_t image_name_length{}; |
|||
|
|||
server_name_length = std::wcstombs(server_name, in_server_name, sizeof(server_name)); |
|||
level_name_length = std::wcstombs(level_name, in_level_name, sizeof(level_name)); |
|||
image_name_length = std::wcstombs(image_name, in_image_name, sizeof(image_name)); |
|||
|
|||
// Populate our presence
|
|||
DiscordRichPresence presence{}; |
|||
|
|||
// state
|
|||
if (*level_name != '\0') { |
|||
// We're in a match
|
|||
presence.state = level_name; |
|||
} |
|||
else { |
|||
// We're not in a level; we must be at the main menu
|
|||
presence.state = "Main Menu"; |
|||
} |
|||
|
|||
// details, party info
|
|||
if (*server_name != '\0') { |
|||
// We're in a multiplayer server
|
|||
presence.details = server_name; |
|||
|
|||
presence.partySize = in_player_count; |
|||
presence.partyMax = in_max_players; |
|||
} |
|||
else { |
|||
// We're not in any server
|
|||
if (strcmp(level_name, "Black Dawn") == 0) { |
|||
// We're playing campaign
|
|||
presence.details = "Campaign"; |
|||
} |
|||
else if (*level_name != '\0') { |
|||
// We're playing skirmish
|
|||
presence.details = "Singleplayer Skirmish"; |
|||
} |
|||
// else // we're sitting at the main menu
|
|||
} |
|||
|
|||
if (in_is_firestorm == 0) { |
|||
presence.largeImageKey = "renegadex"; |
|||
presence.largeImageText = "Renegade X"; |
|||
|
|||
if (in_team_num == GDI_TEAM_NUM) { |
|||
presence.smallImageKey = "gdi"; |
|||
presence.smallImageText = "GDI"; |
|||
} |
|||
else if (in_team_num == NOD_TEAM_NUM) { |
|||
presence.smallImageKey = "nod"; |
|||
presence.smallImageText = "Nod"; |
|||
} |
|||
} |
|||
else { |
|||
presence.largeImageKey = "fs"; |
|||
presence.largeImageText = "Renegade X: Firestorm"; |
|||
|
|||
// For map-specfic images
|
|||
if (*image_name != '\0') { |
|||
presence.largeImageKey = image_name; |
|||
} |
|||
|
|||
if (in_team_num == GDI_TEAM_NUM) { |
|||
presence.smallImageKey = "tsgdi"; |
|||
presence.smallImageText = "GDI"; |
|||
} |
|||
else if (in_team_num == NOD_TEAM_NUM) { |
|||
presence.smallImageKey = "tsnod"; |
|||
presence.smallImageText = "Nod"; |
|||
} |
|||
} |
|||
|
|||
|
|||
|
|||
// Discord client logic:
|
|||
// If endTimestamp is present: display time remaining
|
|||
|
|||
// If the match is over (negative in_time_remaining); don't display anything
|
|||
if (presence.endTimestamp >= 0) { |
|||
presence.startTimestamp = now_seconds() - in_time_elapsed; |
|||
|
|||
// If it's a timed match, display the time remaining
|
|||
if (in_time_remaining != 0) { |
|||
presence.endTimestamp = now_seconds() + in_time_remaining; |
|||
} |
|||
} |
|||
|
|||
// Update our presence
|
|||
Discord_UpdatePresence(&presence); |
|||
} |
|||
|
|||
//typedef struct DiscordRichPresence {
|
|||
// const char* state; /* max 128 bytes */
|
|||
// const char* details; /* max 128 bytes */
|
|||
// int64_t startTimestamp;
|
|||
// int64_t endTimestamp;
|
|||
// const char* largeImageKey; /* max 32 bytes */
|
|||
// const char* largeImageText; /* max 128 bytes */
|
|||
// const char* smallImageKey; /* max 32 bytes */
|
|||
// const char* smallImageText; /* max 128 bytes */
|
|||
// const char* partyId; /* max 128 bytes */
|
|||
// int partySize;
|
|||
// int partyMax;
|
|||
// const char* matchSecret; /* max 128 bytes */
|
|||
// const char* joinSecret; /* max 128 bytes */
|
|||
// const char* spectateSecret; /* max 128 bytes */
|
|||
// int8_t instance;
|
|||
//} DiscordRichPresence;
|
|||
|
|||
/*char buffer[256];
|
|||
DiscordRichPresence discordPresence; |
|||
memset(&discordPresence, 0, sizeof(discordPresence)); |
|||
discordPresence.state = "West of House"; |
|||
sprintf(buffer, "Frustration level: %d", FrustrationLevel); |
|||
discordPresence.details = buffer; |
|||
discordPresence.startTimestamp = StartTime; |
|||
discordPresence.endTimestamp = time(0) + 5 * 60; |
|||
discordPresence.largeImageKey = "canary-large"; |
|||
discordPresence.smallImageKey = "ptb-small"; |
|||
discordPresence.partyId = "party1234"; |
|||
discordPresence.partySize = 1; |
|||
discordPresence.partyMax = 6; |
|||
discordPresence.matchSecret = "xyzzy"; |
|||
discordPresence.joinSecret = "join"; |
|||
discordPresence.spectateSecret = "look"; |
|||
discordPresence.instance = 0; |
|||
Discord_UpdatePresence(&discordPresence);*/ |
@ -0,0 +1,29 @@ |
|||
/**
|
|||
* Copyright (C) 2018 Jessica James. |
|||
* |
|||
* Permission to use, copy, modify, and/or distribute this software for any |
|||
* purpose with or without fee is hereby granted, provided that the above |
|||
* copyright notice and this permission notice appear in all copies. |
|||
* |
|||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
|||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
|||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY |
|||
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
|||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION |
|||
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN |
|||
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
|||
* |
|||
* Written by Jessica James <jessica.aj@outlook.com> |
|||
*/ |
|||
|
|||
#pragma once |
|||
|
|||
#ifdef __cplusplus |
|||
extern "C" { |
|||
#endif // __cplusplus
|
|||
|
|||
__declspec(dllexport) void UpdateDiscordRPC(wchar_t* in_server_name, wchar_t* in_level_name, int in_player_count, int in_max_players, int in_team_num, int in_time_elapsed, int in_time_remaining, int is_in_firestorm, wchar_t* in_image_name); |
|||
|
|||
#ifdef __cplusplus |
|||
} |
|||
#endif // __cplusplus
|
@ -0,0 +1,148 @@ |
|||
#define _WINSOCK_DEPRECATED_NO_WARNINGS |
|||
#include <WinSock2.h> |
|||
#include <Windows.h> |
|||
#include <Iphlpapi.h> |
|||
#include <string> |
|||
#include <iomanip> |
|||
#include <sstream> |
|||
#include "UDKArray.h" |
|||
|
|||
static std::string s_hwid; |
|||
|
|||
struct HWID { |
|||
union { |
|||
uint64_t hwid; |
|||
struct { |
|||
uint32_t left; |
|||
uint32_t right; |
|||
}; |
|||
}; |
|||
} hwid; |
|||
|
|||
#pragma pack(push, 4) |
|||
struct ByteArrayWrapper { |
|||
UDKArray<uint8_t> data; |
|||
}; |
|||
#pragma pack(pop) |
|||
|
|||
std::string GetSystemDriveGUID() { |
|||
char* system_drive; |
|||
char system_drive_mount_point[64]; |
|||
char system_drive_volume_name[64]; |
|||
char* system_drive_guid_start; |
|||
char* system_drive_guid_end; |
|||
|
|||
system_drive = getenv("SystemDrive"); |
|||
if (system_drive != NULL) { |
|||
if (GetVolumePathNameA(system_drive, system_drive_mount_point, sizeof(system_drive_mount_point))) { |
|||
if (GetVolumeNameForVolumeMountPointA(system_drive_mount_point, system_drive_volume_name, sizeof(system_drive_volume_name))) { |
|||
// Find start of guid
|
|||
system_drive_guid_start = system_drive_volume_name; |
|||
while (*system_drive_guid_start != '\0') { |
|||
if (*system_drive_guid_start == '{') { |
|||
// Found start of guid; interate over '{' and break
|
|||
++system_drive_guid_start; |
|||
break; |
|||
} |
|||
|
|||
++system_drive_guid_start; |
|||
} |
|||
|
|||
// Find end of guid
|
|||
system_drive_guid_end = system_drive_guid_start; |
|||
while (*system_drive_guid_end != '\0') { |
|||
if (*system_drive_guid_end == '}') { |
|||
// Found end of guid; break
|
|||
break; |
|||
} |
|||
|
|||
++system_drive_guid_end; |
|||
} |
|||
|
|||
// Copy guid to out_guid
|
|||
return std::string{ system_drive_guid_start, system_drive_guid_end }; |
|||
} |
|||
} |
|||
} |
|||
|
|||
return {}; |
|||
} |
|||
|
|||
void fill_mac_hwid() { |
|||
IP_ADAPTER_ADDRESSES addresses[32]; |
|||
PIP_ADAPTER_ADDRESSES node; |
|||
ULONG addresses_size = sizeof(addresses); |
|||
|
|||
if (GetAdaptersAddresses(AF_UNSPEC, 0, NULL, addresses, &addresses_size) == ERROR_SUCCESS) { |
|||
for (node = addresses; node != NULL; node = node->Next) { |
|||
if (node->PhysicalAddressLength > 0) { |
|||
static_assert(MAX_ADAPTER_ADDRESS_LENGTH <= sizeof(struct HWID), "HWID struct is not large enough to fit adapter physical address"); |
|||
memcpy(&hwid, node->PhysicalAddress, node->PhysicalAddressLength); |
|||
|
|||
if (hwid.hwid != 0) { |
|||
return; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
hwid.hwid = 0; |
|||
} |
|||
|
|||
/** Integer to string (hexadecimal) conversion tables */ |
|||
|
|||
const char hexadecimal_rep_table_upper[][3] = |
|||
{ |
|||
"00", "01", "02", "03", "04", "05", "06", "07", "08", "09", "0A", "0B", "0C", "0D", "0E", "0F", |
|||
"10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "1A", "1B", "1C", "1D", "1E", "1F", |
|||
"20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "2A", "2B", "2C", "2D", "2E", "2F", |
|||
"30", "31", "32", "33", "34", "35", "36", "37", "38", "39", "3A", "3B", "3C", "3D", "3E", "3F", |
|||
"40", "41", "42", "43", "44", "45", "46", "47", "48", "49", "4A", "4B", "4C", "4D", "4E", "4F", |
|||
"50", "51", "52", "53", "54", "55", "56", "57", "58", "59", "5A", "5B", "5C", "5D", "5E", "5F", |
|||
"60", "61", "62", "63", "64", "65", "66", "67", "68", "69", "6A", "6B", "6C", "6D", "6E", "6F", |
|||
"70", "71", "72", "73", "74", "75", "76", "77", "78", "79", "7A", "7B", "7C", "7D", "7E", "7F", |
|||
"80", "81", "82", "83", "84", "85", "86", "87", "88", "89", "8A", "8B", "8C", "8D", "8E", "8F", |
|||
"90", "91", "92", "93", "94", "95", "96", "97", "98", "99", "9A", "9B", "9C", "9D", "9E", "9F", |
|||
"A0", "A1", "A2", "A3", "A4", "A5", "A6", "A7", "A8", "A9", "AA", "AB", "AC", "AD", "AE", "AF", |
|||
"B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "BA", "BB", "BC", "BD", "BE", "BF", |
|||
"C0", "C1", "C2", "C3", "C4", "C5", "C6", "C7", "C8", "C9", "CA", "CB", "CC", "CD", "CE", "CF", |
|||
"D0", "D1", "D2", "D3", "D4", "D5", "D6", "D7", "D8", "D9", "DA", "DB", "DC", "DD", "DE", "DF", |
|||
"E0", "E1", "E2", "E3", "E4", "E5", "E6", "E7", "E8", "E9", "EA", "EB", "EC", "ED", "EE", "EF", |
|||
"F0", "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "FA", "FB", "FC", "FD", "FE", "FF" |
|||
}; |
|||
|
|||
template<typename T> |
|||
std::string to_hex(T in_integer) { |
|||
std::string result; |
|||
uint8_t* begin = reinterpret_cast<uint8_t*>(&in_integer); |
|||
uint8_t* itr = begin + sizeof(T); |
|||
|
|||
result.reserve(sizeof(in_integer) * 2); |
|||
while (itr != begin) { |
|||
--itr; |
|||
result += hexadecimal_rep_table_upper[*itr]; |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
|
|||
extern "C" __declspec(dllexport) void c_token(ByteArrayWrapper* out_hwid) { |
|||
std::string hwid_str; |
|||
|
|||
// Try mac address
|
|||
fill_mac_hwid(); |
|||
if (hwid.hwid != 0) { |
|||
hwid_str = 'm'; |
|||
hwid_str += to_hex(hwid.left) + to_hex(hwid.right); // keep this format so it matches old
|
|||
} |
|||
else { |
|||
// Fallback to System Drive GUID
|
|||
hwid_str = 'd'; |
|||
hwid_str += GetSystemDriveGUID(); |
|||
} |
|||
|
|||
// Return hwid_str through out_hwid
|
|||
out_hwid->data.reserve(hwid_str.size()); |
|||
out_hwid->data.ArrayNum = hwid_str.size(); |
|||
memcpy(out_hwid->data.Data, hwid_str.data(), hwid_str.size()); |
|||
} |
@ -0,0 +1,90 @@ |
|||
#include <iostream> |
|||
#include <vector> |
|||
#include <algorithm> |
|||
#include <Windows.h> |
|||
#include <psapi.h> |
|||
#include "discord_rpc.h" |
|||
#include "discord_register.h" |
|||
#include "Rx_TCPLink.h" |
|||
#include "DiscordRpc.h" |
|||
#include "Viewport.h" |
|||
|
|||
extern "C" ReallocFunctionPtrType g_realloc_function = nullptr; |
|||
|
|||
void* searchMemory(const std::vector<byte>& bytes) |
|||
{ |
|||
MODULEINFO mi; |
|||
|
|||
GetModuleInformation(GetCurrentProcess(), GetModuleHandle(nullptr), &mi, sizeof mi); |
|||
|
|||
byte* moduleHead = static_cast<byte*>(mi.lpBaseOfDll); |
|||
byte* moduleTail = moduleHead + mi.SizeOfImage; |
|||
|
|||
byte* region = moduleHead; |
|||
|
|||
while (region < moduleTail) |
|||
{ |
|||
MEMORY_BASIC_INFORMATION mbi; |
|||
|
|||
if (!VirtualQuery(region, &mbi, sizeof mbi)) |
|||
{ |
|||
break; |
|||
} |
|||
|
|||
byte* regionTail = region + mbi.RegionSize; |
|||
|
|||
constexpr DWORD protectionFlags = PAGE_EXECUTE | PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY; |
|||
|
|||
if (mbi.State & MEM_COMMIT && !(mbi.Protect & PAGE_GUARD) && mbi.AllocationProtect & protectionFlags) |
|||
{ |
|||
void* result = std::search(region, regionTail, bytes.begin(), bytes.end()); |
|||
|
|||
if (result != regionTail) |
|||
{ |
|||
return result; |
|||
} |
|||
} |
|||
|
|||
region = regionTail; |
|||
} |
|||
|
|||
return nullptr; |
|||
} |
|||
|
|||
#pragma pack(push, 4) |
|||
struct FDLLBindInitData |
|||
{ |
|||
INT Version; |
|||
ReallocFunctionPtrType ReallocFunctionPtr; |
|||
}; |
|||
#pragma pack(pop) |
|||
|
|||
void initViewport() |
|||
{ |
|||
#if _WIN64 |
|||
const auto address = searchMemory({ |
|||
0x48, 0x89, 0x5C, 0x24, 0x08, 0x48, 0x89, 0x6C, 0x24, 0x10, 0x48, 0x89, |
|||
0x74, 0x24, 0x18, 0x57, 0x48, 0x83, 0xEC, 0x30, 0x33, 0xC0, 0x49, 0x8B, |
|||
0xE8, 0x48, 0x8B, 0xFA, 0x48, 0x8B, 0xF1, 0x89, 0x42, 0x08, 0x39, 0x42, |
|||
0x0C, 0x74, 0x19, 0x48, 0x8B, 0x0A, 0x89, 0x42, 0x0C, 0x48, 0x85, 0xC9, |
|||
0x74, 0x0E, 0x44, 0x8D, 0x40, 0x08, 0x33, 0xD2, 0xE8, 0xA3, 0x5D, 0xC0 }); |
|||
#else |
|||
const auto address = searchMemory({ |
|||
0x53, 0x56, 0x8B, 0x74, 0x24, 0x0C, 0x83, 0x7E, 0x08, 0x00, 0x57, 0x8B, |
|||
0xD9, 0xC7, 0x46, 0x04, 0x00, 0x00, 0x00, 0x00, 0x74, 0x1C, 0x8B, 0x06, |
|||
0xC7, 0x46, 0x08, 0x00, 0x00, 0x00, 0x00, 0x85, 0xC0, 0x74, 0x0F, 0x6A, |
|||
0x08, 0x6A, 0x00, 0x50, 0xE8, 0x53, 0x75, 0xC6, 0xFF, 0x83, 0xC4, 0x0C, |
|||
0x89, 0x06, 0x8B, 0x03, 0x8B, 0x50, 0x0C, 0x8B, 0xCB, 0xFF, 0xD2, 0x8B }); |
|||
#endif |
|||
|
|||
Viewport::setReadPixelsFunction(reinterpret_cast<Viewport::ReadPixelsFunctionType>(address)); |
|||
} |
|||
|
|||
extern "C" __declspec(dllexport) void DLLBindInit(FDLLBindInitData* in_init_data) { |
|||
g_realloc_function = in_init_data->ReallocFunctionPtr; |
|||
|
|||
init_wsa(); |
|||
Discord_Initialize("482746941256499220", nullptr, 1, nullptr); |
|||
UpdateDiscordRPC(L"", L"", 0, 0, 0, 0, 0, 0, L""); |
|||
initViewport(); |
|||
} |
@ -0,0 +1,254 @@ |
|||
#define _WINSOCK_DEPRECATED_NO_WARNINGS |
|||
#define PIO_APC_ROUTINE_DEFINED |
|||
#include <WinSock2.h> |
|||
#include <IPExport.h> |
|||
#include <winternl.h> |
|||
#include <IcmpAPI.h> |
|||
#include <WS2tcpip.h> |
|||
#include<unordered_map> |
|||
#include <string> |
|||
#include <atomic> |
|||
#include <mutex> |
|||
#include <forward_list> |
|||
|
|||
// Forward declarations and aliases
|
|||
struct PingContext; |
|||
using request_map = std::unordered_map<std::wstring, std::shared_ptr<PingContext>>; |
|||
|
|||
// Constants
|
|||
constexpr int PING_IN_PROGRESS = -1; |
|||
constexpr int PING_FAILED = -2; |
|||
|
|||
unsigned char icmp_request_data[]{ "Renegade X Game Client Ping Data" }; |
|||
constexpr size_t icmp_request_data_length = sizeof(icmp_request_data) - 1; |
|||
constexpr DWORD icmp_reply_buffer_size = sizeof(ICMP_ECHO_REPLY) + icmp_request_data_length + 8 + sizeof(IO_STATUS_BLOCK); |
|||
constexpr DWORD icmp_timeout = 1000; // 1 second
|
|||
|
|||
// Statics
|
|||
std::atomic<unsigned int> s_pings_in_progress{ 0 }; // Note, this is ALL ping requests in progress, not necessarily the ones in `s_pings` (and therefore, not necessarily contexts that still exist)
|
|||
std::atomic<bool> s_ping_thread_active{ false }; |
|||
std::shared_ptr<request_map> s_pings; |
|||
std::forward_list<std::weak_ptr<PingContext>> s_pending_requests; |
|||
std::thread s_ping_thread; |
|||
//std::mutex s_pings_mutex;
|
|||
std::mutex s_pending_requests_mutex; |
|||
|
|||
struct PingContext { |
|||
HANDLE icmp_handle{}; |
|||
IPAddr destination{}; |
|||
std::weak_ptr<request_map> request_group; |
|||
std::unique_ptr<unsigned char[]> reply_buffer; |
|||
std::atomic<int> ping_time_milliseconds{ PING_IN_PROGRESS }; |
|||
|
|||
// Performs any single-run cleanup needed
|
|||
void finish() { |
|||
// Cleanup handle
|
|||
IcmpCloseHandle(icmp_handle); |
|||
icmp_handle = nullptr; |
|||
|
|||
// Mark no longer in progress
|
|||
--s_pings_in_progress; |
|||
} |
|||
|
|||
PingContext() { |
|||
++s_pings_in_progress; |
|||
} |
|||
|
|||
~PingContext() { |
|||
if (icmp_handle != nullptr) { |
|||
IcmpCloseHandle(icmp_handle); |
|||
--s_pings_in_progress; |
|||
} |
|||
} |
|||
}; |
|||
|
|||
IPAddr parse_address(const wchar_t* in_str) { |
|||
IN_ADDR buffer_addr{}; |
|||
InetPtonW(AF_INET, in_str, &buffer_addr); |
|||
|
|||
return buffer_addr.S_un.S_addr; |
|||
} |
|||
|
|||
void WINAPI icmp_ping_callback(void* in_context, PIO_STATUS_BLOCK in_status_block, ULONG) { |
|||
std::unique_ptr<std::weak_ptr<PingContext>> weak_context{ static_cast<std::weak_ptr<PingContext>*>(in_context) }; |
|||
auto strong_context = weak_context->lock(); |
|||
|
|||
if (strong_context == nullptr) { |
|||
// Context has been destroyed already; s_pings must have changed
|
|||
return; |
|||
} |
|||
|
|||
// Cleanup handle
|
|||
strong_context->finish(); |
|||
|
|||
if (NT_ERROR(in_status_block->Status)) { |
|||
// Ping failed somehow; set status to error
|
|||
strong_context->ping_time_milliseconds = PING_FAILED; |
|||
return; |
|||
} |
|||
|
|||
// Verify ping reply size
|
|||
if (in_status_block->Information < icmp_request_data_length) { |
|||
// Ping did not reply with same data as sent; set status to error
|
|||
strong_context->ping_time_milliseconds = PING_FAILED; |
|||
return; |
|||
} |
|||
|
|||
DWORD reply_count = IcmpParseReplies(strong_context->reply_buffer.get(), icmp_reply_buffer_size); |
|||
if (reply_count <= 0) { |
|||
// No response was received; this should never happen since one of the above checks would certainly have been hit
|
|||
strong_context->ping_time_milliseconds = PING_FAILED; |
|||
return; |
|||
} |
|||
|
|||
ICMP_ECHO_REPLY* replies = reinterpret_cast<ICMP_ECHO_REPLY*>(strong_context->reply_buffer.get()); |
|||
for (DWORD index = 0; index < reply_count; ++index) { // reply_count should never be anything other than 1, but hey, whatever
|
|||
ICMP_ECHO_REPLY& reply = replies[index]; |
|||
|
|||
// Verify success
|
|||
if (reply.Status != IP_SUCCESS) { |
|||
strong_context->ping_time_milliseconds = PING_FAILED; |
|||
return; |
|||
} |
|||
|
|||
// Verify reply size; this is probably redundant, but hey whatever
|
|||
if (reply.DataSize != icmp_request_data_length) { |
|||
strong_context->ping_time_milliseconds = PING_FAILED; |
|||
return; |
|||
} |
|||
|
|||
// Verify ping data patches
|
|||
if (std::memcmp(icmp_request_data, reply.Data, icmp_request_data_length) != 0) { |
|||
strong_context->ping_time_milliseconds = PING_FAILED; |
|||
return; |
|||
} |
|||
|
|||
// Ping request successful; set it
|
|||
strong_context->ping_time_milliseconds = reply.RoundTripTime; |
|||
} |
|||
} |
|||
|
|||
void process_pending_requests() { |
|||
std::unique_lock<std::mutex> lock{ s_pending_requests_mutex }; |
|||
// Loop until there are no pings in progress
|
|||
while (s_pings_in_progress != 0) { |
|||
// Process pending any requests in the queue
|
|||
while (!s_pending_requests.empty()) { |
|||
// Pop front pending request
|
|||
auto pending_request = s_pending_requests.front().lock(); |
|||
s_pending_requests.pop_front(); |
|||
|
|||
if (pending_request != nullptr) { |
|||
// Kick off ICMP request
|
|||
IcmpSendEcho2( |
|||
pending_request->icmp_handle, |
|||
nullptr, // event
|
|||
&icmp_ping_callback, |
|||
new std::weak_ptr<PingContext>(pending_request), |
|||
pending_request->destination, |
|||
icmp_request_data, |
|||
icmp_request_data_length, |
|||
nullptr, // options
|
|||
pending_request->reply_buffer.get(), |
|||
icmp_reply_buffer_size, |
|||
icmp_timeout); |
|||
} |
|||
} |
|||
|
|||
// Process any thread alerts / ICMP callbacks
|
|||
lock.unlock(); |
|||
SleepEx(10, TRUE); |
|||
lock.lock(); |
|||
} |
|||
|
|||
s_ping_thread_active = false; |
|||
} |
|||
|
|||
void queue_pending_request(std::weak_ptr<PingContext> in_pending_request) { |
|||
std::lock_guard<std::mutex> lock{ s_pending_requests_mutex }; |
|||
s_pending_requests.push_front(std::move(in_pending_request)); |
|||
|
|||
// Start thread if necessary
|
|||
if (!s_ping_thread_active) { |
|||
// Join any existing thread
|
|||
if (s_ping_thread.joinable()) { |
|||
s_ping_thread.join(); |
|||
} |
|||
|
|||
// Mark processing active
|
|||
s_ping_thread_active = true; |
|||
|
|||
// Spin up thread
|
|||
s_ping_thread = std::thread(&process_pending_requests); |
|||
} |
|||
} |
|||
|
|||
extern "C" __declspec(dllexport) bool start_ping_request(const wchar_t* in_str) { |
|||
// Parse address to ping
|
|||
IPAddr destination_address = parse_address(in_str); |
|||
if (destination_address == 0) { |
|||
// Failed to parse
|
|||
return false; |
|||
} |
|||
|
|||
// Get ICMP handle to send request
|
|||
HANDLE icmp_handle = IcmpCreateFile(); |
|||
if (icmp_handle == INVALID_HANDLE_VALUE) { |
|||
// Failed to open handle for ICMP requests
|
|||
return false; |
|||
} |
|||
|
|||
// Build request context
|
|||
auto context = std::make_shared<PingContext>(); |
|||
context->icmp_handle = icmp_handle; |
|||
context->destination = destination_address; |
|||
context->reply_buffer = std::make_unique<unsigned char[]>(icmp_reply_buffer_size); |
|||
{ |
|||
//std::lock_guard<std::mutex> guard{ s_pings_mutex };
|
|||
|
|||
if (s_pings == nullptr) { |
|||
s_pings = std::make_shared<request_map>(); |
|||
} |
|||
|
|||
context->request_group = s_pings; |
|||
s_pings->emplace(in_str, context); |
|||
} |
|||
|
|||
// Queue request context
|
|||
queue_pending_request(context); |
|||
|
|||
return true; |
|||
} |
|||
|
|||
extern "C" __declspec(dllexport) int get_ping(const wchar_t* in_str) { |
|||
//std::lock_guard<std::mutex> guard{ s_pings_mutex };
|
|||
|
|||
// Safety check that s_pings actually exists
|
|||
if (s_pings == nullptr) { |
|||
// Ping request does not exist
|
|||
return PING_FAILED; |
|||
} |
|||
|
|||
// Search for IP in ping list
|
|||
auto itr = s_pings->find(in_str); |
|||
if (itr == s_pings->end()) { |
|||
// Ping request does not exist
|
|||
return PING_FAILED; |
|||
} |
|||
|
|||
// This is a massive hack, but we need to be able to join the processing thread when we're done to prevent a shutdown crash
|
|||
if (!s_ping_thread_active && s_ping_thread.joinable()) { |
|||
s_ping_thread.join(); |
|||
} |
|||
|
|||
return itr->second->ping_time_milliseconds; |
|||
} |
|||
|
|||
extern "C" __declspec(dllexport) void clear_pings() { |
|||
//std::lock_guard<std::mutex> guard{ s_pings_mutex };
|
|||
s_pings = nullptr; |
|||
|
|||
if (!s_ping_thread_active && s_ping_thread.joinable()) { |
|||
s_ping_thread.join(); |
|||
} |
|||
} |
@ -0,0 +1,238 @@ |
|||
/**
|
|||
* Copyright (C) 2016-2018 Jessica James. |
|||
* |
|||
* Permission to use, copy, modify, and/or distribute this software for any |
|||
* purpose with or without fee is hereby granted, provided that the above |
|||
* copyright notice and this permission notice appear in all copies. |
|||
* |
|||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
|||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
|||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY |
|||
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
|||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION |
|||
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN |
|||
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
|||
* |
|||
* Written by Jessica James <jessica.aj@outlook.com> |
|||
*/ |
|||
|
|||
#include <stdint.h> |
|||
#include <stdbool.h> |
|||
#include <wchar.h> |
|||
|
|||
#define _WINSOCK_DEPRECATED_NO_WARNINGS |
|||
#include <WinSock2.h> |
|||
#include <Windows.h> |
|||
#include <Iphlpapi.h> |
|||
|
|||
/** Relative path to the launcher executable */ |
|||
#define LAUNCHER_PATH "..\\..\\..\\Launcher\\Renegade X Launcher.exe" |
|||
|
|||
bool init_wsa_success; |
|||
|
|||
#pragma pack(push, 4) |
|||
struct socket_t { |
|||
uint64_t sock; |
|||
} s_socket; |
|||
|
|||
struct AcceptedSocket |
|||
{ |
|||
uint64_t sock; |
|||
int32_t addr; |
|||
int32_t port; |
|||
} accepted_socket; |
|||
#pragma pack(pop) |
|||
|
|||
void init_wsa() |
|||
{ |
|||
WSADATA wsadata; |
|||
init_wsa_success = WSAStartup(WINSOCK_VERSION, &wsadata); |
|||
} |
|||
|
|||
void cleanup_wsa() |
|||
{ |
|||
if (init_wsa_success) |
|||
WSACleanup(); |
|||
} |
|||
|
|||
__declspec(dllexport) struct socket_t* c_socket() |
|||
{ |
|||
s_socket.sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); |
|||
return &s_socket; |
|||
} |
|||
|
|||
// since UDK only supports IPv4, just return the IP address as an integer (or 0 on failure).
|
|||
/*int32_t c_resolve(unsigned char *hostname)
|
|||
{ |
|||
struct hostent *host_entry; |
|||
IN_ADDR address; |
|||
|
|||
host_entry = gethostbyname(hostname); |
|||
if (host_entry == NULL) |
|||
return 0; |
|||
|
|||
memcpy(&address, host_entry->h_addr_list[0], host_entry->h_length); |
|||
|
|||
return address.s_addr; |
|||
}*/ |
|||
|
|||
struct sockaddr_in helper_make_sockaddr_in(int32_t in_address, int32_t in_port) |
|||
{ |
|||
struct sockaddr_in address; |
|||
IN_ADDR in_addr; |
|||
|
|||
memset(&address, 0, sizeof(address)); |
|||
|
|||
in_addr.s_addr = htonl(in_address); |
|||
address.sin_addr = in_addr; |
|||
address.sin_family = AF_INET; |
|||
address.sin_port = htons(in_port); |
|||
|
|||
return address; |
|||
} |
|||
|
|||
// Binds to in_port, or fails
|
|||
__declspec(dllexport) int32_t c_bind(struct socket_t* in_socket, int32_t in_port) |
|||
{ |
|||
struct sockaddr_in address; |
|||
|
|||
address = helper_make_sockaddr_in(INADDR_ANY, in_port); |
|||
|
|||
if (bind(in_socket->sock, (struct sockaddr *) &address, sizeof(address)) == 0) |
|||
return in_port; |
|||
|
|||
return 0; |
|||
} |
|||
|
|||
// Binds to in_port, or the next available
|
|||
__declspec(dllexport) int32_t c_bind_next(struct socket_t* in_socket, int32_t in_port) |
|||
{ |
|||
struct sockaddr_in address; |
|||
|
|||
address = helper_make_sockaddr_in(INADDR_ANY, in_port); |
|||
|
|||
while (true) |
|||
{ |
|||
if (bind(in_socket->sock, (struct sockaddr *) &address, sizeof(address)) == 0) |
|||
return in_port; |
|||
|
|||
if (WSAGetLastError() != WSAEADDRINUSE) |
|||
return 0; |
|||
|
|||
address.sin_port = htons(++in_port); |
|||
} |
|||
} |
|||
|
|||
// Binds to any port
|
|||
__declspec(dllexport) int32_t c_bind_any(struct socket_t* in_socket) |
|||
{ |
|||
struct sockaddr_in address; |
|||
|
|||
address = helper_make_sockaddr_in(INADDR_ANY, 0); |
|||
|
|||
if (bind(in_socket->sock, (struct sockaddr *) &address, sizeof(address)) == 0) |
|||
return ntohs(address.sin_port); |
|||
|
|||
return 0; |
|||
} |
|||
|
|||
__declspec(dllexport) int32_t c_listen(struct socket_t* in_socket) |
|||
{ |
|||
return listen(in_socket->sock, SOMAXCONN); |
|||
} |
|||
|
|||
__declspec(dllexport) struct AcceptedSocket *c_accept(struct socket_t* in_socket) |
|||
{ |
|||
struct sockaddr addr; |
|||
int size; |
|||
|
|||
size = sizeof(addr); |
|||
accepted_socket.sock = accept(in_socket->sock, &addr, &size); |
|||
|
|||
if (accepted_socket.sock != INVALID_SOCKET) |
|||
{ |
|||
accepted_socket.addr = ((struct sockaddr_in *) &addr)->sin_addr.s_addr; |
|||
accepted_socket.port = ((struct sockaddr_in *) &addr)->sin_port; |
|||
} |
|||
|
|||
return &accepted_socket; |
|||
} |
|||
|
|||
__declspec(dllexport) int32_t c_connect(struct socket_t* in_socket, int32_t in_address, int32_t in_port) |
|||
{ |
|||
struct sockaddr_in address; |
|||
|
|||
address = helper_make_sockaddr_in(in_address, in_port); |
|||
|
|||
return connect(in_socket->sock, (struct sockaddr *) &address, sizeof(address)); |
|||
} |
|||
|
|||
__declspec(dllexport) int32_t c_close(struct socket_t* in_socket) |
|||
{ |
|||
return closesocket(in_socket->sock); |
|||
} |
|||
|
|||
__declspec(dllexport) int32_t c_recv(struct socket_t* in_socket, unsigned char *out_buffer, int32_t in_buffer_size) |
|||
{ |
|||
return recv(in_socket->sock, out_buffer, in_buffer_size, 0); |
|||
} |
|||
|
|||
__declspec(dllexport) int32_t c_send(struct socket_t* in_socket, unsigned char *in_buffer, int32_t in_buffer_size) |
|||
{ |
|||
return send(in_socket->sock, in_buffer, in_buffer_size, 0); |
|||
} |
|||
|
|||
__declspec(dllexport) int32_t c_set_blocking(struct socket_t* in_socket, int32_t in_value) |
|||
{ |
|||
unsigned long block_mode; |
|||
block_mode = in_value == 0 ? 1 : 0; |
|||
return ioctlsocket(in_socket->sock, FIONBIO, &block_mode); |
|||
} |
|||
|
|||
__declspec(dllexport) int32_t c_get_last_error() |
|||
{ |
|||
return GetLastError(); |
|||
} |
|||
|
|||
__declspec(dllexport) int32_t c_check_status(struct socket_t* in_socket) |
|||
{ |
|||
fd_set set_read, set_err; |
|||
struct timeval time_val; |
|||
int value; |
|||
|
|||
time_val.tv_sec = 0; |
|||
time_val.tv_usec = 0; |
|||
FD_ZERO(&set_read); |
|||
FD_ZERO(&set_err); |
|||
FD_SET(in_socket->sock, &set_read); |
|||
FD_SET(in_socket->sock, &set_err); |
|||
|
|||
// check for success
|
|||
value = select(in_socket->sock + 1, NULL, &set_read, &set_err, &time_val); |
|||
if (value > 0) // socket can be written to
|
|||
{ |
|||
if (FD_ISSET(in_socket->sock, &set_read)) |
|||
return 1; |
|||
if (FD_ISSET(in_socket->sock, &set_err)) |
|||
return -1; |
|||
} |
|||
|
|||
return value; |
|||
} |
|||
|
|||
__declspec(dllexport) bool UpdateGame() { |
|||
STARTUPINFOA startupInfo; |
|||
PROCESS_INFORMATION processInformation; |
|||
|
|||
memset(&startupInfo, 0, sizeof(startupInfo)); |
|||
memset(&processInformation, 0, sizeof(processInformation)); |
|||
|
|||
// Create process
|
|||
if (!CreateProcessA(LAUNCHER_PATH, NULL, NULL, NULL, false, 0, NULL, NULL, &startupInfo, &processInformation)) { |
|||
return false; |
|||
} |
|||
|
|||
// Process successfully created; exit the application so that patching doesn't fail horribly
|
|||
exit(EXIT_SUCCESS); |
|||
return true; // unreachable
|
|||
} |
@ -0,0 +1,33 @@ |
|||
/**
|
|||
* Copyright (C) 2016 Jessica James. |
|||
* |
|||
* Permission to use, copy, modify, and/or distribute this software for any |
|||
* purpose with or without fee is hereby granted, provided that the above |
|||
* copyright notice and this permission notice appear in all copies. |
|||
* |
|||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
|||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
|||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY |
|||
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
|||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION |
|||
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN |
|||
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
|||
* |
|||
* Written by Jessica James <jessica.aj@outlook.com> |
|||
*/ |
|||
|
|||
#if defined __cplusplus |
|||
extern "C" |
|||
{ |
|||
#endif // __cplusplus
|
|||
|
|||
void init_wsa(); |
|||
void cleanup_wsa(); |
|||
|
|||
typedef void* (*ReallocFunctionPtrType)(void* Original, DWORD Count, DWORD Alignment); |
|||
extern ReallocFunctionPtrType g_realloc_function; |
|||
|
|||
#if defined __cplusplus |
|||
} |
|||
|
|||
#endif //__cplusplus
|
@ -0,0 +1,80 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
|||
<ItemGroup Label="ProjectConfigurations"> |
|||
<ProjectConfiguration Include="Debug|Win32"> |
|||
<Configuration>Debug</Configuration> |
|||
<Platform>Win32</Platform> |
|||
</ProjectConfiguration> |
|||
<ProjectConfiguration Include="Release|Win32"> |
|||
<Configuration>Release</Configuration> |
|||
<Platform>Win32</Platform> |
|||
</ProjectConfiguration> |
|||
<ProjectConfiguration Include="Debug|x64"> |
|||
<Configuration>Debug</Configuration> |
|||
<Platform>x64</Platform> |
|||
</ProjectConfiguration> |
|||
<ProjectConfiguration Include="Release|x64"> |
|||
<Configuration>Release</Configuration> |
|||
<Platform>x64</Platform> |
|||
</ProjectConfiguration> |
|||
</ItemGroup> |
|||
<PropertyGroup Label="Globals"> |
|||
<ProjectGuid>{988779E2-4B8D-4997-82B6-25BC4E0A705E}</ProjectGuid> |
|||
<RootNamespace>Rx_TCPLink</RootNamespace> |
|||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion> |
|||
</PropertyGroup> |
|||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> |
|||
<PropertyGroup Label="Configuration"> |
|||
<ConfigurationType>DynamicLibrary</ConfigurationType> |
|||
<UseDebugLibraries>false</UseDebugLibraries> |
|||
<PlatformToolset>v142</PlatformToolset> |
|||
<CharacterSet>Unicode</CharacterSet> |
|||
</PropertyGroup> |
|||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> |
|||
<ImportGroup Label="ExtensionSettings"> |
|||
</ImportGroup> |
|||
<ImportGroup Label="Shared"> |
|||
</ImportGroup> |
|||
<ImportGroup Label="PropertySheets"> |
|||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> |
|||
</ImportGroup> |
|||
<PropertyGroup Label="UserMacros" /> |
|||
<PropertyGroup /> |
|||
<ItemDefinitionGroup> |
|||
<ClCompile> |
|||
<WarningLevel>Level3</WarningLevel> |
|||
<Optimization>Disabled</Optimization> |
|||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary> |
|||
<StructMemberAlignment>Default</StructMemberAlignment> |
|||
<AdditionalIncludeDirectories Condition="'$(Platform)'=='x64'">..\discord-rpc\win64-static\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> |
|||
<AdditionalIncludeDirectories Condition="'$(Platform)'=='Win32'">..\discord-rpc\win32-static\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> |
|||
</ClCompile> |
|||
<Link> |
|||
<OptimizeReferences>true</OptimizeReferences> |
|||
<AdditionalLibraryDirectories Condition="'$(Platform)'=='x64'">..\discord-rpc\win64-static\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> |
|||
<AdditionalLibraryDirectories Condition="'$(Platform)'=='Win32'">..\discord-rpc\win32-static\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> |
|||
<AdditionalDependencies>Ws2_32.lib;iphlpapi.lib;discord-rpc.lib;%(AdditionalDependencies)</AdditionalDependencies> |
|||
</Link> |
|||
</ItemDefinitionGroup> |
|||
<ItemGroup> |
|||
<ClCompile Include="DiscordRpc.cpp" /> |
|||
<ClCompile Include="HWID.cpp" /> |
|||
<ClCompile Include="Main.cpp" /> |
|||
<ClCompile Include="Ping.cpp" /> |
|||
<ClCompile Include="Rx_TCPLink.c" /> |
|||
<ClCompile Include="Screenshot.cpp" /> |
|||
</ItemGroup> |
|||
<ItemGroup> |
|||
<ClInclude Condition="'$(Platform)'=='x64'" Include="..\discord-rpc\win64-static\include\discord_register.h" /> |
|||
<ClInclude Condition="'$(Platform)'=='x64'" Include="..\discord-rpc\win64-static\include\discord_rpc.h" /> |
|||
<ClInclude Condition="'$(Platform)'=='Win32'" Include="..\discord-rpc\win32-static\include\discord_register.h" /> |
|||
<ClInclude Condition="'$(Platform)'=='Win32'" Include="..\discord-rpc\win32-static\include\discord_rpc.h" /> |
|||
<ClInclude Include="DiscordRpc.h" /> |
|||
<ClInclude Include="Rx_TCPLink.h" /> |
|||
<ClInclude Include="UDKArray.h" /> |
|||
<ClInclude Include="Viewport.h" /> |
|||
</ItemGroup> |
|||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> |
|||
<ImportGroup Label="ExtensionTargets"> |
|||
</ImportGroup> |
|||
</Project> |
@ -0,0 +1,11 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
|||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> |
|||
<LocalDebuggerCommand>D:\RenX\SVN\UDK_Uncooked\Binaries\Win64\UDK.exe</LocalDebuggerCommand> |
|||
<DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor> |
|||
</PropertyGroup> |
|||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> |
|||
<LocalDebuggerCommand>D:\RenX\SVN\UDK_Uncooked\Binaries\Win32\UDK.exe</LocalDebuggerCommand> |
|||
<DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor> |
|||
</PropertyGroup> |
|||
</Project> |
@ -0,0 +1,165 @@ |
|||
/**
|
|||
* Copyright (C) 2019 Jessica James. |
|||
* |
|||
* Permission to use, copy, modify, and/or distribute this software for any |
|||
* purpose with or without fee is hereby granted, provided that the above |
|||
* copyright notice and this permission notice appear in all copies. |
|||
* |
|||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
|||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
|||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY |
|||
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
|||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION |
|||
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN |
|||
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
|||
* |
|||
* Written by Jessica James <jessica.aj@outlook.com> |
|||
*/ |
|||
|
|||
#include <atlimage.h> |
|||
#include <string> |
|||
#include <ctime> |
|||
#include <fstream> |
|||
#include "Viewport.h" |
|||
|
|||
Viewport::ReadPixelsFunctionType Viewport::readPixelsFunction = nullptr; |
|||
|
|||
/** Filename related constants */ |
|||
static constexpr wchar_t prefix[]{ L"ScreenCapture " }; |
|||
static constexpr size_t prefix_length = sizeof(prefix) / sizeof(wchar_t) - 1; |
|||
static constexpr wchar_t extension[]{ L".jpg" }; |
|||
static constexpr size_t extension_length = sizeof(extension) / sizeof(wchar_t) - 1; |
|||
static constexpr size_t max_date_length = FILENAME_MAX - prefix_length - extension_length; |
|||
|
|||
#pragma pack(push, 4) |
|||
struct ScreenCapture |
|||
{ |
|||
UDKArray<uint8_t> data; |
|||
|
|||
// For pushing data to captures
|
|||
template<typename T> |
|||
void push(const T& in_data) { |
|||
// Expand capture as necessary
|
|||
int required_size = data.ArrayNum + sizeof(T); |
|||
if (required_size >= data.ArrayMax) { |
|||
// Expand to minimum required size, or double array size, whichever is larger
|
|||
data.Reallocate(max(required_size, data.ArrayMax * 2)); |
|||
} |
|||
|
|||
// Copy data to capture
|
|||
std::memcpy(data.Data + data.ArrayNum, &in_data, sizeof(T)); |
|||
data.ArrayNum += sizeof(T); |
|||
} |
|||
|
|||
// For popping from captures
|
|||
template<typename T> |
|||
T pop() { |
|||
T result{}; |
|||
if (sizeof(T) > data.ArrayNum) { |
|||
return result; |
|||
} |
|||
|
|||
// Copy item to result
|
|||
uint8_t* item_begin = data.Data + data.ArrayNum - sizeof(T); |
|||
std::memcpy(&result, item_begin, sizeof(T)); |
|||
|
|||
// Pop item
|
|||
data.ArrayNum -= sizeof(T); |
|||
|
|||
// Return result
|
|||
return result; |
|||
} |
|||
}; |
|||
#pragma pack(pop) |
|||
|
|||
extern "C" __declspec(dllexport) void take_ss(Viewport::DllBindReference* viewport, ScreenCapture* result) |
|||
{ |
|||
// Read in screenshot data from UDK
|
|||
viewport->value->readPixels(result->data, { 6, 0, 1, 16000.0F }); |
|||
int sizeX = viewport->value->getSizeX(); |
|||
int sizeY = viewport->value->getSizeY(); |
|||
|
|||
// Import screenCapture data into CImage
|
|||
CImage outputImage; |
|||
outputImage.Create(sizeX, sizeY, 32); |
|||
for (int row = 0; row < sizeY; ++row) |
|||
{ |
|||
const void* screenCaptureRowAddress = &result->data[row * sizeX * sizeof(UDKColor)]; |
|||
void* outputRowAddress = outputImage.GetPixelAddress(0, row); |
|||
memcpy(outputRowAddress, screenCaptureRowAddress, sizeX * 4); |
|||
} |
|||
|
|||
// Save JPEG image data to memory stream
|
|||
auto stream = SHCreateMemStream(NULL, 0); |
|||
outputImage.Save(stream, Gdiplus::ImageFormatJPEG); |
|||
stream->Seek({}, STREAM_SEEK_SET, 0); |
|||
|
|||
// Get stream size
|
|||
STATSTG stream_stat{}; |
|||
stream->Stat(&stream_stat, STATFLAG::STATFLAG_NONAME); |
|||
auto stream_size = stream_stat.cbSize.QuadPart; |
|||
|
|||
// Read data from stream into buffer
|
|||
result->data.ArrayNum = stream_size; |
|||
stream->Read(result->data.Data, result->data.ArrayMax, nullptr); |
|||
stream->Release(); |
|||
} |
|||
|
|||
static_assert(sizeof(UDKColor) == 4, "UDKColor size != 4"); |
|||
|
|||
extern "C" __declspec(dllexport) void write_ss(ScreenCapture* screenCapture) |
|||
{ |
|||
// Get current date
|
|||
auto currentTime = std::time(nullptr); |
|||
auto currentTm = localtime(¤tTime); |
|||
wchar_t timeString[max_date_length + 1]; |
|||
std::wcsftime(timeString, max_date_length, L"%F - %H-%M-%S", currentTm); |
|||
|
|||
// Build filename
|
|||
std::wstring filename{ prefix }; |
|||
filename += timeString; |
|||
filename += extension; |
|||
|
|||
// Write to file
|
|||
std::ofstream file{ filename, std::ios::binary }; |
|||
file.write(reinterpret_cast<char*>(screenCapture->data.Data), screenCapture->data.ArrayNum); |
|||
} |
|||
|
|||
constexpr size_t byte_buffer_size = 256; |
|||
|
|||
/**
|
|||
* Copies data from a dynamic UDK byte array to a static UDK byte array |
|||
* |
|||
* @param out_destination Byte buffer to copy data to |
|||
* @param in_source Byte array to copy data from |
|||
* @param in_offset Position of in_source to start copying from |
|||
* @return Total number of bytes copied (will never exceed byte_buffer_size) |
|||
*/ |
|||
extern "C" __declspec(dllexport) int copy_array_to_buffer(uint8_t* out_destination, ScreenCapture* in_source, int in_offset) { |
|||
// Sanity check length and offset
|
|||
if (in_offset < 0 |
|||
|| in_source == nullptr |
|||
|| in_source->data.ArrayNum <= in_offset |
|||
|| out_destination == nullptr) { |
|||
return 0; |
|||
} |
|||
|
|||
size_t length = min(byte_buffer_size, in_source->data.ArrayNum - in_offset); // Remaining bytes in array or buffer size, whichever is smaller
|
|||
std::memcpy(out_destination, in_source->data.Data + in_offset, length); |
|||
return length; |
|||
} |
|||
|
|||
/** static shit */ |
|||
|
|||
uint8_t* s_cap_buffer = nullptr; |
|||
ScreenCapture* s_capture = nullptr; |
|||
|
|||
extern "C" __declspec(dllexport) void set_cap(uint8_t* buffer, ScreenCapture* screenCapture) { |
|||
s_cap_buffer = buffer; |
|||
s_capture = screenCapture; |
|||
} |
|||
|
|||
extern "C" __declspec(dllexport) int read_cap(int in_offset) { |
|||
int result = copy_array_to_buffer(s_cap_buffer, s_capture, in_offset); |
|||
return result; |
|||
} |
@ -0,0 +1,75 @@ |
|||
/**
|
|||
* Copyright (C) 2019 Jessica James. |
|||
* |
|||
* Permission to use, copy, modify, and/or distribute this software for any |
|||
* purpose with or without fee is hereby granted, provided that the above |
|||
* copyright notice and this permission notice appear in all copies. |
|||
* |
|||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
|||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
|||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY |
|||
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
|||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION |
|||
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN |
|||
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
|||
* |
|||
* Written by Jessica James <jessica.aj@outlook.com> |
|||
*/ |
|||
|
|||
#pragma once |
|||
|
|||
#include <algorithm> |
|||
#include "Rx_TCPLink.h" |
|||
|
|||
#pragma pack(push, 4) |
|||
template<typename DataT> |
|||
struct UDKArray |
|||
{ |
|||
void Reallocate(int NewNum) |
|||
{ |
|||
ArrayMax = NewNum; |
|||
Data = static_cast<DataT*>((*g_realloc_function)(Data, ArrayMax * sizeof(DataT), 8)); |
|||
} |
|||
|
|||
void reserve(int in_size) { |
|||
if (in_size > ArrayMax) { |
|||
Reallocate(in_size); |
|||
} |
|||
} |
|||
|
|||
DataT& operator[](const int index) |
|||
{ |
|||
return Data[index]; |
|||
} |
|||
|
|||
const DataT& operator[](const int index) const |
|||
{ |
|||
return Data[index]; |
|||
} |
|||
|
|||
void push_back(const DataT& in_item) { |
|||
if (ArrayNum >= ArrayMax) { |
|||
Reallocate(max(ArrayNum * 2, 8)); |
|||
} |
|||
|
|||
Data[ArrayNum] = in_item; |
|||
++ArrayNum; |
|||
} |
|||
|
|||
void pop_back() { |
|||
--ArrayNum; |
|||
} |
|||
|
|||
DataT& back() const { |
|||
return Data[ArrayNum - 1]; |
|||
} |
|||
|
|||
DataT& front() const { |
|||
return Data[0]; |
|||
} |
|||
|
|||
DataT* Data; |
|||
int ArrayNum; |
|||
int ArrayMax; |
|||
}; |
|||
#pragma pack(pop) |
@ -0,0 +1,105 @@ |
|||
/**
|
|||
* Copyright (C) 2019 Jessica James. |
|||
* |
|||
* Permission to use, copy, modify, and/or distribute this software for any |
|||
* purpose with or without fee is hereby granted, provided that the above |
|||
* copyright notice and this permission notice appear in all copies. |
|||
* |
|||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
|||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
|||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY |
|||
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
|||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION |
|||
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN |
|||
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
|||
* |
|||
* Written by Jessica James <jessica.aj@outlook.com> |
|||
*/ |
|||
|
|||
#pragma once |
|||
|
|||
#include <cinttypes> |
|||
#include "UDKArray.h" |
|||
|
|||
#pragma pack(push, 4) |
|||
struct UDKColor |
|||
{ |
|||
union { |
|||
struct { |
|||
uint8_t b, g, r, a; |
|||
}; |
|||
|
|||
uint8_t bytes[4]; |
|||
uint32_t uint; |
|||
int32_t sint; |
|||
}; |
|||
}; |
|||
|
|||
static_assert(sizeof(UDKColor) == 4, "UDKColor has unexpected extra data; must be exactly 4"); |
|||
|
|||
struct ReadPixelsMetadata |
|||
{ |
|||
int cubeFace; |
|||
int compresssionMode; |
|||
int convertLinearToGamma; |
|||
float depth; |
|||
}; |
|||
|
|||
class Viewport |
|||
{ |
|||
public: |
|||
int readPixels(UDKArray<uint8_t>& result, ReadPixelsMetadata metadata) |
|||
{ |
|||
int value = readPixels(reinterpret_cast<UDKArray<UDKColor>&>(result), metadata); |
|||
|
|||
// expand to bytes
|
|||
result.ArrayNum *= sizeof(UDKColor); |
|||
result.ArrayMax *= sizeof(UDKColor); |
|||
return value; |
|||
} |
|||
|
|||
int readPixels(UDKArray<UDKColor>& result, ReadPixelsMetadata metadata) |
|||
{ |
|||
return readPixelsFunction(this, result, metadata); |
|||
} |
|||
|
|||
int getSizeX() const |
|||
{ |
|||
return callVirtualFunction<2, int>(); |
|||
} |
|||
|
|||
int getSizeY() const |
|||
{ |
|||
return callVirtualFunction<3, int>(); |
|||
} |
|||
|
|||
using ReadPixelsFunctionType = int(__thiscall*)(void*, UDKArray<UDKColor>&, ReadPixelsMetadata); |
|||
|
|||
static void setReadPixelsFunction(const ReadPixelsFunctionType value) |
|||
{ |
|||
readPixelsFunction = value; |
|||
} |
|||
|
|||
static ReadPixelsFunctionType readPixelsFunction; |
|||
|
|||
struct DllBindReference |
|||
{ |
|||
Viewport* value; |
|||
}; |
|||
|
|||
private: |
|||
template<size_t Index, typename ReturnType, typename... ArgumentList> ReturnType callVirtualFunction(ArgumentList... arguments) |
|||
{ |
|||
using FunctionType = ReturnType(__thiscall*)(void*, ArgumentList...); |
|||
const auto p = (*reinterpret_cast<FunctionType**>(this))[Index]; |
|||
return p(this, arguments...); |
|||
} |
|||
|
|||
template<size_t Index, typename ReturnType, typename... ArgumentList> ReturnType callVirtualFunction(ArgumentList... arguments) const |
|||
{ |
|||
using FunctionType = ReturnType(__thiscall*)(const void*, ArgumentList...); |
|||
const auto p = (*reinterpret_cast<FunctionType*const*>(this))[Index]; |
|||
return p(this, arguments...); |
|||
} |
|||
}; |
|||
#pragma pack(pop) |
@ -0,0 +1,32 @@ |
|||
/**
|
|||
* Copyright (C) 2016 Jessica James. |
|||
* |
|||
* Permission to use, copy, modify, and/or distribute this software for any |
|||
* purpose with or without fee is hereby granted, provided that the above |
|||
* copyright notice and this permission notice appear in all copies. |
|||
* |
|||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
|||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
|||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY |
|||
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
|||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION |
|||
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN |
|||
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
|||
* |
|||
* Written by Jessica James <jessica.aj@outlook.com> |
|||
*/ |
|||
|
|||
#include "Rx_TCPLink.h" |
|||
|
|||
class WSAInitCleanup |
|||
{ |
|||
public: |
|||
WSAInitCleanup() |
|||
{ |
|||
init_wsa(); |
|||
} |
|||
~WSAInitCleanup() |
|||
{ |
|||
cleanup_wsa(); |
|||
} |
|||
} _wsa_init_cleanup; |
Binary file not shown.
@ -0,0 +1,26 @@ |
|||
#pragma once |
|||
|
|||
#if defined(DISCORD_DYNAMIC_LIB) |
|||
#if defined(_WIN32) |
|||
#if defined(DISCORD_BUILDING_SDK) |
|||
#define DISCORD_EXPORT __declspec(dllexport) |
|||
#else |
|||
#define DISCORD_EXPORT __declspec(dllimport) |
|||
#endif |
|||
#else |
|||
#define DISCORD_EXPORT __attribute__((visibility("default"))) |
|||
#endif |
|||
#else |
|||
#define DISCORD_EXPORT |
|||
#endif |
|||
|
|||
#ifdef __cplusplus |
|||
extern "C" { |
|||
#endif |
|||
|
|||
DISCORD_EXPORT void Discord_Register(const char* applicationId, const char* command); |
|||
DISCORD_EXPORT void Discord_RegisterSteamGame(const char* applicationId, const char* steamId); |
|||
|
|||
#ifdef __cplusplus |
|||
} |
|||
#endif |
@ -0,0 +1,87 @@ |
|||
#pragma once |
|||
#include <stdint.h> |
|||
|
|||
// clang-format off
|
|||
|
|||
#if defined(DISCORD_DYNAMIC_LIB) |
|||
# if defined(_WIN32) |
|||
# if defined(DISCORD_BUILDING_SDK) |
|||
# define DISCORD_EXPORT __declspec(dllexport) |
|||
# else |
|||
# define DISCORD_EXPORT __declspec(dllimport) |
|||
# endif |
|||
# else |
|||
# define DISCORD_EXPORT __attribute__((visibility("default"))) |
|||
# endif |
|||
#else |
|||
# define DISCORD_EXPORT |
|||
#endif |
|||
|
|||
// clang-format on
|
|||
|
|||
#ifdef __cplusplus |
|||
extern "C" { |
|||
#endif |
|||
|
|||
typedef struct DiscordRichPresence { |
|||
const char* state; /* max 128 bytes */ |
|||
const char* details; /* max 128 bytes */ |
|||
int64_t startTimestamp; |
|||
int64_t endTimestamp; |
|||
const char* largeImageKey; /* max 32 bytes */ |
|||
const char* largeImageText; /* max 128 bytes */ |
|||
const char* smallImageKey; /* max 32 bytes */ |
|||
const char* smallImageText; /* max 128 bytes */ |
|||
const char* partyId; /* max 128 bytes */ |
|||
int partySize; |
|||
int partyMax; |
|||
const char* matchSecret; /* max 128 bytes */ |
|||
const char* joinSecret; /* max 128 bytes */ |
|||
const char* spectateSecret; /* max 128 bytes */ |
|||
int8_t instance; |
|||
} DiscordRichPresence; |
|||
|
|||
typedef struct DiscordUser { |
|||
const char* userId; |
|||
const char* username; |
|||
const char* discriminator; |
|||
const char* avatar; |
|||
} DiscordUser; |
|||
|
|||
typedef struct DiscordEventHandlers { |
|||
void (*ready)(const DiscordUser* request); |
|||
void (*disconnected)(int errorCode, const char* message); |
|||
void (*errored)(int errorCode, const char* message); |
|||
void (*joinGame)(const char* joinSecret); |
|||
void (*spectateGame)(const char* spectateSecret); |
|||
void (*joinRequest)(const DiscordUser* request); |
|||
} DiscordEventHandlers; |
|||
|
|||
#define DISCORD_REPLY_NO 0 |
|||
#define DISCORD_REPLY_YES 1 |
|||
#define DISCORD_REPLY_IGNORE 2 |
|||
|
|||
DISCORD_EXPORT void Discord_Initialize(const char* applicationId, |
|||
DiscordEventHandlers* handlers, |
|||
int autoRegister, |
|||
const char* optionalSteamId); |
|||
DISCORD_EXPORT void Discord_Shutdown(void); |
|||
|
|||
/* checks for incoming messages, dispatches callbacks */ |
|||
DISCORD_EXPORT void Discord_RunCallbacks(void); |
|||
|
|||
/* If you disable the lib starting its own io thread, you'll need to call this from your own */ |
|||
#ifdef DISCORD_DISABLE_IO_THREAD |
|||
DISCORD_EXPORT void Discord_UpdateConnection(void); |
|||
#endif |
|||
|
|||
DISCORD_EXPORT void Discord_UpdatePresence(const DiscordRichPresence* presence); |
|||
DISCORD_EXPORT void Discord_ClearPresence(void); |
|||
|
|||
DISCORD_EXPORT void Discord_Respond(const char* userid, /* DISCORD_REPLY_ */ int reply); |
|||
|
|||
DISCORD_EXPORT void Discord_UpdateHandlers(DiscordEventHandlers* handlers); |
|||
|
|||
#ifdef __cplusplus |
|||
} /* extern "C" */ |
|||
#endif |
Binary file not shown.
Binary file not shown.
@ -0,0 +1,26 @@ |
|||
#pragma once |
|||
|
|||
#if defined(DISCORD_DYNAMIC_LIB) |
|||
#if defined(_WIN32) |
|||
#if defined(DISCORD_BUILDING_SDK) |
|||
#define DISCORD_EXPORT __declspec(dllexport) |
|||
#else |
|||
#define DISCORD_EXPORT __declspec(dllimport) |
|||
#endif |
|||
#else |
|||
#define DISCORD_EXPORT __attribute__((visibility("default"))) |
|||
#endif |
|||
#else |
|||
#define DISCORD_EXPORT |
|||
#endif |
|||
|
|||
#ifdef __cplusplus |
|||
extern "C" { |
|||
#endif |
|||
|
|||
DISCORD_EXPORT void Discord_Register(const char* applicationId, const char* command); |
|||
DISCORD_EXPORT void Discord_RegisterSteamGame(const char* applicationId, const char* steamId); |
|||
|
|||
#ifdef __cplusplus |
|||
} |
|||
#endif |
@ -0,0 +1,87 @@ |
|||
#pragma once |
|||
#include <stdint.h> |
|||
|
|||
// clang-format off
|
|||
|
|||
#if defined(DISCORD_DYNAMIC_LIB) |
|||
# if defined(_WIN32) |
|||
# if defined(DISCORD_BUILDING_SDK) |
|||
# define DISCORD_EXPORT __declspec(dllexport) |
|||
# else |
|||
# define DISCORD_EXPORT __declspec(dllimport) |
|||
# endif |
|||
# else |
|||
# define DISCORD_EXPORT __attribute__((visibility("default"))) |
|||
# endif |
|||
#else |
|||
# define DISCORD_EXPORT |
|||
#endif |
|||
|
|||
// clang-format on
|
|||
|
|||
#ifdef __cplusplus |
|||
extern "C" { |
|||
#endif |
|||
|
|||
typedef struct DiscordRichPresence { |
|||
const char* state; /* max 128 bytes */ |
|||
const char* details; /* max 128 bytes */ |
|||
int64_t startTimestamp; |
|||
int64_t endTimestamp; |
|||
const char* largeImageKey; /* max 32 bytes */ |
|||
const char* largeImageText; /* max 128 bytes */ |
|||
const char* smallImageKey; /* max 32 bytes */ |
|||
const char* smallImageText; /* max 128 bytes */ |
|||
const char* partyId; /* max 128 bytes */ |
|||
int partySize; |
|||
int partyMax; |
|||
const char* matchSecret; /* max 128 bytes */ |
|||
const char* joinSecret; /* max 128 bytes */ |
|||
const char* spectateSecret; /* max 128 bytes */ |
|||
int8_t instance; |
|||
} DiscordRichPresence; |
|||
|
|||
typedef struct DiscordUser { |
|||
const char* userId; |
|||
const char* username; |
|||
const char* discriminator; |
|||
const char* avatar; |
|||
} DiscordUser; |
|||
|
|||
typedef struct DiscordEventHandlers { |
|||
void (*ready)(const DiscordUser* request); |
|||
void (*disconnected)(int errorCode, const char* message); |
|||
void (*errored)(int errorCode, const char* message); |
|||
void (*joinGame)(const char* joinSecret); |
|||
void (*spectateGame)(const char* spectateSecret); |
|||
void (*joinRequest)(const DiscordUser* request); |
|||
} DiscordEventHandlers; |
|||
|
|||
#define DISCORD_REPLY_NO 0 |
|||
#define DISCORD_REPLY_YES 1 |
|||
#define DISCORD_REPLY_IGNORE 2 |
|||
|
|||
DISCORD_EXPORT void Discord_Initialize(const char* applicationId, |
|||
DiscordEventHandlers* handlers, |
|||
int autoRegister, |
|||
const char* optionalSteamId); |
|||
DISCORD_EXPORT void Discord_Shutdown(void); |
|||
|
|||
/* checks for incoming messages, dispatches callbacks */ |
|||
DISCORD_EXPORT void Discord_RunCallbacks(void); |
|||
|
|||
/* If you disable the lib starting its own io thread, you'll need to call this from your own */ |
|||
#ifdef DISCORD_DISABLE_IO_THREAD |
|||
DISCORD_EXPORT void Discord_UpdateConnection(void); |
|||
#endif |
|||
|
|||
DISCORD_EXPORT void Discord_UpdatePresence(const DiscordRichPresence* presence); |
|||
DISCORD_EXPORT void Discord_ClearPresence(void); |
|||
|
|||
DISCORD_EXPORT void Discord_Respond(const char* userid, /* DISCORD_REPLY_ */ int reply); |
|||
|
|||
DISCORD_EXPORT void Discord_UpdateHandlers(DiscordEventHandlers* handlers); |
|||
|
|||
#ifdef __cplusplus |
|||
} /* extern "C" */ |
|||
#endif |
Binary file not shown.
Loading…
Reference in new issue