mirror of
https://github.com/zebrajr/SamRewritten.git
synced 2025-12-06 00:19:47 +01:00
commit
5988e0dd3f
3
.vscode/launch.json
vendored
3
.vscode/launch.json
vendored
|
|
@ -2,6 +2,7 @@
|
|||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
// iBomber pacific 206690
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
|
|
@ -9,7 +10,7 @@
|
|||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/bin/samrewritten",
|
||||
"args": [],
|
||||
"args": ["730", "--ls"],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "${workspaceFolder}",
|
||||
"environment": [
|
||||
|
|
|
|||
2
Makefile
2
Makefile
|
|
@ -15,7 +15,7 @@ all: ${CURDIR}/bin/samrewritten
|
|||
@echo -e "==== Use '\033[1mmake clean\033[0m' to remove object files"
|
||||
@echo -e "==== Nothing left to do."
|
||||
|
||||
dev: CXXFLAGS += -g
|
||||
dev: CXXFLAGS += -g -DDEBUG_CERR
|
||||
dev: ${CURDIR}/bin/samrewritten
|
||||
|
||||
clean:
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ Usage:
|
|||
-a, --app arg Set which AppId you want to use. Same as using positional
|
||||
'AppId'
|
||||
-i, --idle Set your Steam profile as 'ingame'. Ctrl+c to stop.
|
||||
--ls Display stats (TODO) and achievements for selected app.
|
||||
--ls Display achievements and stats for selected app.
|
||||
--sort arg Sort option for --ls. You can leave empty or set to
|
||||
'unlock_rate'
|
||||
--unlock arg Unlock achievements for an AppId. Separate achievement
|
||||
|
|
|
|||
4
TODO
Normal file
4
TODO
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
|
||||
-- done? I finished the stat encoding, but did not test. ALso, now do the decoding
|
||||
|
||||
Next task: try printing stats in the client side. I just finished decode_stats.
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
#include "../controller/MySteam.h"
|
||||
#include "../globals.h"
|
||||
#include "../common/cxxopts.hpp"
|
||||
#include "../common/functions.h"
|
||||
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
|
|
@ -44,7 +45,7 @@ bool go_cli_mode(int argc, char* argv[]) {
|
|||
("h,help", "Show CLI help.")
|
||||
("a,app", "Set which AppId you want to use. Same as using positional 'AppId'", cxxopts::value<AppId_t>())
|
||||
("i,idle", "Set your Steam profile as 'ingame'. Ctrl+c to stop.")
|
||||
("ls", "Display stats (TODO) and achievements for selected app.")
|
||||
("ls", "Display achievements and stats for selected app.")
|
||||
("sort", "Sort option for --ls. You can leave empty or set to 'unlock_rate'", cxxopts::value<std::string>())
|
||||
("unlock", "Unlock achievements for an AppId. Separate achievement names by a comma.", cxxopts::value<std::vector<std::string>>())
|
||||
("lock", "Lock achievements for an AppId. Separate achievement names by a comma.", cxxopts::value<std::vector<std::string>>())
|
||||
|
|
@ -104,8 +105,9 @@ bool go_cli_mode(int argc, char* argv[]) {
|
|||
}
|
||||
|
||||
g_steam->launch_app(app);
|
||||
g_steam->refresh_achievements();
|
||||
g_steam->refresh_stats_and_achievements();
|
||||
auto achievements = g_steam->get_achievements();
|
||||
auto stats = g_steam->get_stats();
|
||||
g_steam->quit_game();
|
||||
|
||||
if (result.count("sort") > 0)
|
||||
|
|
@ -121,7 +123,7 @@ bool go_cli_mode(int argc, char* argv[]) {
|
|||
|
||||
|
||||
// https://github.com/haarcuba/cpp-text-table -> worth? nah but best I've found
|
||||
std::cout << "API Name \t\tName \t\tDescription \t\tUnlock rate \t\tUnlocked" << std::endl;
|
||||
std::cout << "API Name \t\tName \t\tDescription \t\tUnlock rate \t\tUnlocked\n";
|
||||
std::cout << "--------------------------------------------------------------" << std::endl;
|
||||
for ( Achievement_t& it : achievements )
|
||||
{
|
||||
|
|
@ -132,6 +134,28 @@ bool go_cli_mode(int argc, char* argv[]) {
|
|||
<< it.global_achieved_rate << "% \t"
|
||||
<< (it.achieved ? "✔️" : "❌") << std::endl;
|
||||
}
|
||||
|
||||
std::cout << "\n";
|
||||
|
||||
if ( stats.size() == 0 )
|
||||
{
|
||||
std::cout << "No stats found for this app.." << std::endl;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cout << "\nSTATS\n";
|
||||
std::cout << "API Name \t\tValue \t\t Increment Only\n";
|
||||
std::cout << "----------------------------------------" << std::endl;
|
||||
for (auto stat : stats )
|
||||
{
|
||||
std::cout
|
||||
<< stat.id << " \t"
|
||||
<< GET_STAT_VALUE(stat) << " \t"
|
||||
<< (stat.incrementonly ? "Yes" : "No")
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (result.count("unlock") > 0)
|
||||
|
|
|
|||
|
|
@ -23,6 +23,15 @@ Downloader::download_file(const std::string& file_url, const std::string& local_
|
|||
curl = curl_easy_init();
|
||||
if (curl) {
|
||||
fp = fopen(local_path.c_str(),"wb");
|
||||
|
||||
if (fp == NULL)
|
||||
{
|
||||
std::cerr << "An error occurred downloading " << file_url << " and saving it to ";
|
||||
std::cerr << local_path << std::endl;
|
||||
zenity("An error occurred downloading a file. Please report it to the developers!");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
curl_easy_setopt(curl, CURLOPT_URL, file_url.c_str());
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, NULL);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);
|
||||
|
|
|
|||
|
|
@ -16,9 +16,11 @@ PerfMon::~PerfMon()
|
|||
void
|
||||
PerfMon::log(const std::string& what)
|
||||
{
|
||||
#ifdef DEBUG_CERR
|
||||
std::cerr
|
||||
<< "[PID:" << getpid()
|
||||
<< " TRD:" << std::this_thread::get_id()
|
||||
<< " TME:" << double(std::clock() - m_start) / CLOCKS_PER_SEC
|
||||
<< "] \t" << what << std::endl;
|
||||
#endif
|
||||
}
|
||||
|
|
@ -1096,7 +1096,6 @@ namespace cxxopts
|
|||
return m_count;
|
||||
}
|
||||
|
||||
// TODO: maybe default options should count towards the number of arguments
|
||||
bool
|
||||
has_default() const noexcept
|
||||
{
|
||||
|
|
|
|||
|
|
@ -87,12 +87,12 @@ void mkdir_default(const char *pathname)
|
|||
}
|
||||
}
|
||||
|
||||
std::string get_app_icon_path(AppId_t app_id) {
|
||||
return std::string(g_cache_folder) + "/" + std::to_string(app_id) + "/banner.jpg";
|
||||
std::string get_app_icon_path(std::string cache_folder, AppId_t app_id) {
|
||||
return cache_folder + "/" + std::to_string(app_id) + "/banner.jpg";
|
||||
}
|
||||
|
||||
std::string get_achievement_icon_path(AppId_t app_id, std::string id) {
|
||||
return std::string(g_cache_folder) + "/" + std::to_string(app_id) + "/" + id + ".jpg";
|
||||
std::string get_achievement_icon_path(std::string cache_folder, AppId_t app_id, std::string id) {
|
||||
return cache_folder + "/" + std::to_string(app_id) + "/" + id + ".jpg";
|
||||
}
|
||||
|
||||
void escape_html(std::string& data) {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,13 @@
|
|||
#include "../globals.h"
|
||||
#include "../../steam/steam_api.h"
|
||||
|
||||
/**
|
||||
* Stats hold a value of type any, but can be either int or float.
|
||||
* This macro casts the value to the right type.
|
||||
* The cast types are the ones used by YAJL
|
||||
*/
|
||||
#define GET_STAT_VALUE(stat) (stat.type == UserStatType::Integer ? std::any_cast<long long>(stat.value) : std::any_cast<double>(stat.value))
|
||||
|
||||
/**
|
||||
* Wrapper for fork()
|
||||
*/
|
||||
|
|
@ -47,12 +54,12 @@ void mkdir_default(const char *pathname);
|
|||
/**
|
||||
* Generate path to given app icon
|
||||
*/
|
||||
std::string get_app_icon_path(AppId_t app_id);
|
||||
std::string get_app_icon_path(std::string cache_folder, AppId_t app_id);
|
||||
|
||||
/**
|
||||
* Generate path to given app achievement icon
|
||||
*/
|
||||
std::string get_achievement_icon_path(AppId_t app_id, std::string id);
|
||||
std::string get_achievement_icon_path(std::string cache_folder, AppId_t app_id, std::string id);
|
||||
|
||||
/**
|
||||
* Escape html characters inline a string.
|
||||
|
|
@ -64,4 +71,4 @@ void escape_html(std::string& data);
|
|||
* Show a regular dialog box. Return value is ignored for now,
|
||||
* but feel free to add functionnlitie to this
|
||||
*/
|
||||
int zenity(const std::string text = "An internal error occurred, please open a Github issue with the console output to get it fixed!", const std::string type = "--error --no-wrap");
|
||||
int zenity(const std::string text = "An internal error occurred, please open a Github issue with the console output to get it fixed!", const std::string type = "--error --no-wrap");
|
||||
|
|
@ -12,6 +12,24 @@
|
|||
#include <bits/stdc++.h>
|
||||
|
||||
MySteam::MySteam() {
|
||||
// Cache folder
|
||||
if (getenv("XDG_CACHE_HOME")) {
|
||||
m_cache_folder = std::string(getenv("XDG_CACHE_HOME")) + "/SamRewritten";
|
||||
} else {
|
||||
m_cache_folder = std::string(getenv("HOME")) + "/.cache/SamRewritten";
|
||||
}
|
||||
mkdir_default(m_cache_folder.c_str());
|
||||
|
||||
// Runtime folder
|
||||
if (getenv("XDG_RUNTIME_DIR")) {
|
||||
m_runtime_folder = std::string(getenv("XDG_RUNTIME_DIR")) + "/SamRewritten";
|
||||
mkdir_default(m_runtime_folder.c_str());
|
||||
} else {
|
||||
std::cerr << "XDG_RUNTIME_DIR is not set! Your distribution is improper... falling back to cache dir" << std::endl;
|
||||
m_runtime_folder = m_cache_folder;
|
||||
}
|
||||
|
||||
// Steam folder
|
||||
std::string data_home_path;
|
||||
if (getenv("XDG_DATA_HOME") != NULL) {
|
||||
data_home_path = getenv("XDG_DATA_HOME");
|
||||
|
|
@ -74,14 +92,16 @@ MySteam::launch_app(AppId_t appID) {
|
|||
return false;
|
||||
}
|
||||
|
||||
// Set the appID BEFORE forking, otherwise the child won't be able to access it
|
||||
m_app_id = appID;
|
||||
m_ipc_socket = m_server_manager.quick_server_create(appID);
|
||||
|
||||
if (m_ipc_socket == nullptr) {
|
||||
std::cerr << "Failed to get connection to game" << std::endl;
|
||||
m_app_id = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
m_app_id = appID;
|
||||
return true;
|
||||
}
|
||||
// => launch_app
|
||||
|
|
@ -134,16 +154,6 @@ MySteam::refresh_owned_apps() {
|
|||
}
|
||||
// => refresh_owned_apps
|
||||
|
||||
/**
|
||||
* Tries to locate the steam folder in multiple locations,
|
||||
* which is not a failsafe implementation.
|
||||
*/
|
||||
std::string
|
||||
MySteam::get_steam_install_path() {
|
||||
return m_steam_install_dir;
|
||||
}
|
||||
// => get_steam_install_path
|
||||
|
||||
/**
|
||||
* Reminder that download_app_icon does check if the file is
|
||||
* already there before attempting to download from the web.
|
||||
|
|
@ -157,13 +167,12 @@ MySteam::refresh_app_icon(AppId_t app_id) {
|
|||
|
||||
void
|
||||
MySteam::refresh_achievement_icon(std::string id, std::string icon_download_name) {
|
||||
SteamAppDAO *appDAO = SteamAppDAO::get_instance();
|
||||
appDAO->download_achievement_icon(m_app_id, id, icon_download_name);
|
||||
SteamAppDAO::get_instance()->download_achievement_icon(m_app_id, id, icon_download_name);
|
||||
}
|
||||
// => refresh_achievement_icon
|
||||
|
||||
void
|
||||
MySteam::refresh_achievements() {
|
||||
MySteam::refresh_stats_and_achievements() {
|
||||
|
||||
if (m_ipc_socket == nullptr) {
|
||||
std::cerr << "Connection to game is broken" << std::endl;
|
||||
|
|
@ -178,6 +187,7 @@ MySteam::refresh_achievements() {
|
|||
}
|
||||
|
||||
m_achievements = decode_achievements(response);
|
||||
m_stats = decode_stats(response);
|
||||
|
||||
set_special_flags();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#pragma once
|
||||
#include "../types/Game.h"
|
||||
#include "../types/Achievement.h"
|
||||
#include "../types/StatValue.h"
|
||||
#include "../sockets/MyClientSocket.h"
|
||||
#include "GameServerManager.h"
|
||||
#include <string>
|
||||
|
|
@ -24,7 +25,31 @@ public:
|
|||
* This is not failsafe and may require some tweaking to add
|
||||
* support for your distribution
|
||||
*/
|
||||
std::string get_steam_install_path();
|
||||
std::string get_steam_install_path() const { return m_steam_install_dir; };
|
||||
|
||||
/**
|
||||
* Returns the absolute path to the runtime folder used by SAM.
|
||||
* It's used for storing the UNIX sockets used by the program.
|
||||
*
|
||||
* XDG Base Directory Specification is followed:
|
||||
* If present, this variable uses XDG_RUNTIME_DIR and takes the value
|
||||
* $XDG_RUNTIME_DIR/SamRewritten
|
||||
*
|
||||
* Warnings are properly issued if it is not set, but still defaults to the
|
||||
* cache folder for simplicity
|
||||
*/
|
||||
std::string get_runtime_path() const { return m_runtime_folder; };
|
||||
|
||||
/**
|
||||
* Returns the absolute path to the cache folder used by SAM
|
||||
*
|
||||
* XDG Base Directory Specification is followed:
|
||||
* If present, this variable uses XDG_CACHE_HOME and takes the value
|
||||
* $XDG_CACHE_HOME/SamRewritten
|
||||
* Otherwise, this variable properly defaults to
|
||||
* ~/.cache/SamRewritten
|
||||
*/
|
||||
std::string get_cache_path() const { return m_cache_folder; };
|
||||
|
||||
/**
|
||||
* Starts a process that will emulate a steam game with the
|
||||
|
|
@ -67,7 +92,7 @@ public:
|
|||
/**
|
||||
* Makes a list of all achievements for the currently running app
|
||||
*/
|
||||
void refresh_achievements();
|
||||
void refresh_stats_and_achievements();
|
||||
|
||||
/**
|
||||
* Get achievements of the launched app
|
||||
|
|
@ -78,9 +103,12 @@ public:
|
|||
* Make sure to call refresh_achievements at least once to get
|
||||
* correct results
|
||||
*
|
||||
* The same goes for the stats
|
||||
*
|
||||
* TODO: maybe don't name this the same as GameServer::get_achievements?
|
||||
*/
|
||||
std::vector<Achievement_t> get_achievements() const { return m_achievements; };
|
||||
std::vector<StatValue_t> get_stats() const { return m_stats; };
|
||||
|
||||
/**
|
||||
* Simple getter
|
||||
|
|
@ -128,8 +156,10 @@ private:
|
|||
*/
|
||||
void set_special_flags();
|
||||
|
||||
// Absolute path to Steam install dir
|
||||
// Absolute path to some important directories
|
||||
std::string m_steam_install_dir;
|
||||
std::string m_cache_folder;
|
||||
std::string m_runtime_folder;
|
||||
|
||||
// Current app_id
|
||||
AppId_t m_app_id;
|
||||
|
|
@ -139,6 +169,7 @@ private:
|
|||
|
||||
std::vector<Game_t> m_all_subscribed_apps;
|
||||
std::vector<Achievement_t> m_achievements;
|
||||
std::vector<StatValue_t> m_stats;
|
||||
|
||||
std::map<std::string, bool> m_pending_ach_modifications;
|
||||
std::map<std::string, double> m_pending_stat_modifications;
|
||||
|
|
|
|||
|
|
@ -29,14 +29,14 @@ SteamAppDAO::get_instance() {
|
|||
// => get_instance
|
||||
|
||||
bool
|
||||
SteamAppDAO::need_to_redownload(const char * file_path) {
|
||||
SteamAppDAO::need_to_redownload(const std::string file_path) {
|
||||
struct stat file_info;
|
||||
const std::time_t current_time(std::time(0));
|
||||
bool b_need_to_redownload = false;
|
||||
|
||||
if (file_exists(file_path)) {
|
||||
//Check the last time it was updated
|
||||
if (stat(file_path, &file_info) == 0) {
|
||||
if (stat(file_path.c_str(), &file_info) == 0) {
|
||||
//If a week has passed
|
||||
if (current_time - file_info.st_mtime > 60 * 60 * 24 * 7) {
|
||||
b_need_to_redownload = true;
|
||||
|
|
@ -46,7 +46,7 @@ SteamAppDAO::need_to_redownload(const char * file_path) {
|
|||
std::cerr << "~/.cache/SamRewritten/app_names exists but an error occurred analyzing it. To avoid further complications, ";
|
||||
std::cerr << "the program will stop here. Before retrying make sure you have enough privilege to read and write to ";
|
||||
std::cerr << "your home folder folder." << std::endl;
|
||||
zenity("An error occurred writing the cache files. Try deleting the cache folder (" + std::string(g_cache_folder) + ") and make sure you have enough permissions to write to it.");
|
||||
zenity("An error occurred writing the cache files. Try deleting the cache folder (" + g_steam->get_cache_path() + ") and make sure you have enough permissions to write to it.");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
|
@ -75,24 +75,25 @@ SteamAppDAO::update_name_database() {
|
|||
// TODO: turn this into a command line option, or a setting
|
||||
const int retrieval_strategy = 1;
|
||||
|
||||
static const char* file_url[2];
|
||||
static const char* local_file_name[2];
|
||||
static std::string file_url[2];
|
||||
static std::string local_file_name[2];
|
||||
const std::string cache_folder = g_steam->get_cache_path();
|
||||
int file_count = 0;
|
||||
|
||||
if (retrieval_strategy == 1) {
|
||||
file_url[0] = "https://raw.githubusercontent.com/PaulCombal/SteamAppsListDumps/master/game_achievements_list.json";
|
||||
local_file_name[0] = concat(g_cache_folder, "/game_list.json");
|
||||
local_file_name[0] = cache_folder + "/game_list.json";
|
||||
file_count = 1;
|
||||
} else if (retrieval_strategy == 2) {
|
||||
// need to download 2 files for this one
|
||||
file_url[0] = "http://api.steampowered.com/ISteamApps/GetAppList/v0002/";
|
||||
local_file_name[0] = concat(g_cache_folder, "/app_names");
|
||||
local_file_name[0] = cache_folder + "/app_names";
|
||||
file_url[1] = "https://raw.githubusercontent.com/PaulCombal/SteamAppsListDumps/master/not_games.json";
|
||||
local_file_name[1] = concat(g_cache_folder, "/not_games.json");
|
||||
local_file_name[1] = cache_folder + "/not_games.json";
|
||||
file_count = 2;
|
||||
} else if (retrieval_strategy == 3) {
|
||||
file_url[0] = "http://api.steampowered.com/ISteamApps/GetAppList/v0002/";
|
||||
local_file_name[0] = concat(g_cache_folder, "/app_names");
|
||||
local_file_name[0] = cache_folder + "/app_names";
|
||||
file_count = 1;
|
||||
}
|
||||
|
||||
|
|
@ -132,8 +133,9 @@ SteamAppDAO::get_app_name(AppId_t app_id) {
|
|||
|
||||
void
|
||||
SteamAppDAO::download_app_icon(AppId_t app_id) {
|
||||
const std::string local_folder(std::string(g_cache_folder) + "/" + std::to_string(app_id));
|
||||
const std::string local_path = get_app_icon_path(app_id);
|
||||
const std::string cache_folder = g_steam->get_cache_path();
|
||||
const std::string local_folder(cache_folder + "/" + std::to_string(app_id));
|
||||
const std::string local_path = get_app_icon_path(cache_folder, app_id);
|
||||
const std::string url("http://cdn.akamai.steamstatic.com/steam/apps/" + std::to_string(app_id) + "/header_292x136.jpg");
|
||||
|
||||
mkdir_default(local_folder.c_str());
|
||||
|
|
@ -142,43 +144,49 @@ SteamAppDAO::download_app_icon(AppId_t app_id) {
|
|||
|
||||
void
|
||||
SteamAppDAO::download_achievement_icon(AppId_t app_id, std::string id, std::string icon_download_name) {
|
||||
const std::string local_folder(std::string(g_cache_folder) + "/" + std::to_string(app_id));
|
||||
const std::string local_path = get_achievement_icon_path(app_id, id);
|
||||
const std::string cache_folder = g_steam->get_cache_path();
|
||||
const std::string local_folder(cache_folder + "/" + std::to_string(app_id));
|
||||
const std::string local_path = get_achievement_icon_path(cache_folder, app_id, id);
|
||||
const std::string url("http://media.steamcommunity.com/steamcommunity/public/images/apps/" + std::to_string(app_id) + "/" + icon_download_name);
|
||||
|
||||
#ifdef DEBUG_CERR
|
||||
std::cerr << "Asking DAO to download achievement icon " << id << " from " << url << std::endl;
|
||||
#endif
|
||||
|
||||
mkdir_default(local_folder.c_str());
|
||||
Downloader::get_instance()->download_file(url, local_path);
|
||||
}
|
||||
|
||||
void
|
||||
SteamAppDAO::parse_app_names(const char * file_path, std::map<AppId_t, std::string>* app_names) {
|
||||
SteamAppDAO::parse_app_names(const std::string file_path, std::map<AppId_t, std::string>* app_names) {
|
||||
app_names->clear();
|
||||
|
||||
size_t rd;
|
||||
yajl_val node;
|
||||
char errbuf[1024];
|
||||
char fileData[5000000];
|
||||
FILE *f = fopen(file_path, "rb");
|
||||
std::string file_contents;
|
||||
std::ifstream stream(file_path);
|
||||
|
||||
/* null plug buffers */
|
||||
fileData[0] = errbuf[0] = 0;
|
||||
|
||||
/* read the entire config file */
|
||||
rd = fread((void *) fileData, 1, sizeof(fileData) - 1, f);
|
||||
|
||||
/* file read error handling */
|
||||
if (rd == 0 && !feof(stdin)) {
|
||||
std::cerr << "Error encountered on file read: " << file_path << std::endl;
|
||||
zenity();
|
||||
exit(EXIT_FAILURE);
|
||||
} else if (rd >= sizeof(fileData) - 1) {
|
||||
std::cerr << "app_names file too big (just increase the buffer size)" << std::endl;
|
||||
if ( !stream.is_open() ) {
|
||||
std::cerr << "SteamAppDAO::parse_app_names: Unable to open file " << file_path << std::endl;
|
||||
zenity();
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
try {
|
||||
std::stringstream buffer;
|
||||
buffer << stream.rdbuf();
|
||||
file_contents = buffer.str();
|
||||
} catch(std::exception& e) {
|
||||
std::cerr << "SteamAppDAO::parse_app_names: " << e.what() << std::endl;
|
||||
zenity();
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// Stream is RAII, but we close it here since we won't use it after
|
||||
stream.close();
|
||||
|
||||
/* we have the whole config file in memory. let's parse it ... */
|
||||
node = yajl_tree_parse((const char *) fileData, errbuf, sizeof(errbuf));
|
||||
node = yajl_tree_parse(file_contents.c_str(), errbuf, sizeof(errbuf));
|
||||
|
||||
/* parse error handling */
|
||||
if (node == NULL) {
|
||||
|
|
@ -213,7 +221,6 @@ SteamAppDAO::parse_app_names(const char * file_path, std::map<AppId_t, std::stri
|
|||
app_names->insert(std::pair<AppId_t, std::string>(tmp_appid, tmp_appname));
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
yajl_tree_free(node);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -58,11 +58,11 @@ private:
|
|||
/**
|
||||
* Check if a given file exists and if so, if it's too old
|
||||
*/
|
||||
static bool need_to_redownload(const char * file_path);
|
||||
static bool need_to_redownload(const std::string file_path);
|
||||
|
||||
/**
|
||||
* Parse the app names from a given file and store to a give app names map
|
||||
*/
|
||||
static void parse_app_names(const char * file_path, std::map<AppId_t, std::string>* app_names);
|
||||
static void parse_app_names(const std::string file_path, std::map<AppId_t, std::string>* app_names);
|
||||
std::map<AppId_t, std::string> m_app_names;
|
||||
};
|
||||
|
|
@ -5,7 +5,8 @@ class MainPickerWindow;
|
|||
class MySteamClient;
|
||||
class PerfMon;
|
||||
|
||||
// Reason for the globals is that it's easier to access them in GTK callbacks
|
||||
// Reason for the globals is that it's easier to access them in GTK callbacks,
|
||||
// or the data they hold may be needed both by the parent process and its children.
|
||||
// All those variables are initialised in main.cpp
|
||||
|
||||
/**
|
||||
|
|
@ -16,42 +17,17 @@ class PerfMon;
|
|||
extern MySteamClient *g_steamclient;
|
||||
|
||||
/**
|
||||
* g_steam is the backend of all of this, the M of the MVC. It will analyse the
|
||||
* steam folder and give data about it: games owned, icons, etc.
|
||||
* g_steam is the backend of all of this. This is used to retrieve achievements,
|
||||
* stats, achievement icons, etc.
|
||||
*/
|
||||
extern MySteam *g_steam;
|
||||
|
||||
/**
|
||||
* g_main_gui is the main application window. If you can read this, it is very
|
||||
* glitchy, freezes instead of showing the loading widget, but still looks
|
||||
* half-decent. It's the V of the MVC.
|
||||
* g_main_gui is the main application window.
|
||||
*/
|
||||
extern MainPickerWindow *g_main_gui;
|
||||
|
||||
/**
|
||||
* The absolute path to the cache folder. It's global, because everyone will
|
||||
* need to write or read into this folder at some point.
|
||||
* XDG Base Directory Specification is followed:
|
||||
* If present, this variable uses XDG_CACHE_HOME and takes the value
|
||||
* $XDG_CACHE_HOME/SamRewritten
|
||||
* Otherwise, this variable properly defaults to
|
||||
* ~/.cache/SamRewritten
|
||||
* A basic performance monitor. Used to log stuff along with process info.
|
||||
*/
|
||||
extern char *g_cache_folder;
|
||||
|
||||
/**
|
||||
* The absolute path to the runtime folder. It's global, because everyone will
|
||||
* need to write or read into this folder at some point. It's used for storing
|
||||
* sockets used by the program.
|
||||
* XDG Base Directory Specification is followed:
|
||||
* If present, this variable uses XDG_RUNTIME_DIR and takes the value
|
||||
* $XDG_RUNTIME_DIR/SamRewritten
|
||||
* Warnings are properly issued if it is not set, but still defaults to the
|
||||
* cache folder for simplicity
|
||||
*/
|
||||
extern char *g_runtime_folder;
|
||||
|
||||
/**
|
||||
* A basic performance monitor
|
||||
*/
|
||||
extern PerfMon *g_perfmon;
|
||||
extern PerfMon *g_perfmon;
|
||||
|
|
@ -9,8 +9,9 @@
|
|||
#include <future>
|
||||
#include <glibmm-2.4/glibmm.h>
|
||||
|
||||
// TODO use #ifndef __VALGRIND_H or similar
|
||||
// #include <valgrind/valgrind.h>
|
||||
#ifdef DEBUG_CERR
|
||||
#include <valgrind/valgrind.h>
|
||||
#endif
|
||||
|
||||
AsyncGuiLoader::AsyncGuiLoader(MainPickerWindow* window)
|
||||
: m_window(window)
|
||||
|
|
@ -23,7 +24,7 @@ AsyncGuiLoader::load_achievements_idle()
|
|||
{
|
||||
if (m_achievement_idle_data.state == ACH_STATE_STARTED) {
|
||||
g_perfmon->log("Starting achievement retrieval");
|
||||
m_achievements_future = std::async(std::launch::async, []{g_steam->refresh_achievements();});
|
||||
m_achievements_future = std::async(std::launch::async, []{g_steam->refresh_stats_and_achievements();});
|
||||
m_achievement_idle_data.state = ACH_STATE_WAITING_FOR_ACHIEVEMENTS;
|
||||
return G_SOURCE_CONTINUE;
|
||||
}
|
||||
|
|
@ -32,9 +33,14 @@ AsyncGuiLoader::load_achievements_idle()
|
|||
if (m_achievements_future.wait_for(std::chrono::seconds(0)) == std::future_status::ready) {
|
||||
g_perfmon->log("Done retrieving achievements");
|
||||
|
||||
#if DEBUG_CERR
|
||||
std::cerr << "Printing achievement ID & icon:" << std::endl;
|
||||
for (auto a : g_steam->get_achievements()) {
|
||||
std::cerr << a.id << " " << a.icon_name << std::endl;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Fire off the schema parsing now.
|
||||
// TODO: figure out if all the icons are already there and skip parsing schema
|
||||
m_schema_parser_future = std::async(std::launch::async, [this]{return m_schema_parser.load_user_game_stats_schema();});
|
||||
m_achievement_idle_data.state = ACH_STATE_LOADING_GUI;
|
||||
}
|
||||
return G_SOURCE_CONTINUE;
|
||||
|
|
@ -56,7 +62,7 @@ AsyncGuiLoader::load_achievements_idle()
|
|||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
m_achievement_idle_data.state = ACH_STATE_WAITING_FOR_SCHEMA_PARSER;
|
||||
m_achievement_idle_data.state = ACH_STATE_DOWNLOADING_ICONS;
|
||||
m_achievement_idle_data.current_item = 0;
|
||||
return G_SOURCE_CONTINUE;
|
||||
}
|
||||
|
|
@ -67,23 +73,6 @@ AsyncGuiLoader::load_achievements_idle()
|
|||
return G_SOURCE_CONTINUE;
|
||||
}
|
||||
|
||||
if (m_achievement_idle_data.state == ACH_STATE_WAITING_FOR_SCHEMA_PARSER) {
|
||||
if (m_schema_parser_future.wait_for(std::chrono::seconds(0)) == std::future_status::ready) {
|
||||
g_perfmon->log("Done parsing schema to find achievement icon download names");
|
||||
if (!m_schema_parser_future.get()) {
|
||||
std::cerr << "Schema parsing failed, skipping icon downloads" << std::endl;
|
||||
m_achievement_idle_data.state = ACH_STATE_FINISHED;
|
||||
g_perfmon->log("Achievements retrieved, no icons.");
|
||||
m_window->show_no_achievements_found_placeholder();
|
||||
m_achievement_refresh_lock.unlock();
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
m_achievement_idle_data.state = ACH_STATE_DOWNLOADING_ICONS;
|
||||
}
|
||||
return G_SOURCE_CONTINUE;
|
||||
}
|
||||
|
||||
if (m_achievement_idle_data.state == ACH_STATE_DOWNLOADING_ICONS) {
|
||||
// this could hang if we failed to parse all the icon download names
|
||||
bool done_starting_downloads = (m_achievement_idle_data.current_item == g_steam->get_achievements().size());
|
||||
|
|
@ -94,16 +83,19 @@ AsyncGuiLoader::load_achievements_idle()
|
|||
m_window->show_no_achievements_found_placeholder();
|
||||
m_achievement_refresh_lock.unlock();
|
||||
|
||||
// See top of the file
|
||||
// VALGRIND_MONITOR_COMMAND("detailed_snapshot");
|
||||
#ifdef DEBUG_CERR
|
||||
VALGRIND_MONITOR_COMMAND("detailed_snapshot");
|
||||
#endif
|
||||
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
if ( !done_starting_downloads && (m_concurrent_icon_downloads < MAX_CONCURRENT_ICON_DOWNLOADS)) {
|
||||
const Achievement_t ach = g_steam->get_achievements()[m_achievement_idle_data.current_item];
|
||||
|
||||
// Fire off a new download thread
|
||||
std::string id = g_steam->get_achievements()[m_achievement_idle_data.current_item].id;
|
||||
std::string icon_download_name = m_schema_parser.get_icon_download_names()[id];
|
||||
std::string id = ach.id;
|
||||
std::string icon_download_name = ach.icon_name;
|
||||
|
||||
// Assuming it returns empty string on failing to lookup
|
||||
if (icon_download_name.empty()) {
|
||||
|
|
@ -147,10 +139,6 @@ AsyncGuiLoader::populate_achievements() {
|
|||
m_window->show_fetch_achievements_placeholder();
|
||||
|
||||
Glib::signal_idle().connect(sigc::mem_fun(this, &AsyncGuiLoader::load_achievements_idle), G_PRIORITY_LOW);
|
||||
// g_idle_add_full (G_PRIORITY_LOW,
|
||||
// this->load_achievements_idle,
|
||||
// data,
|
||||
// finish_load_achievements);
|
||||
|
||||
} else {
|
||||
std::cerr << "Not launching game/refreshing achievements because a refresh is already in progress" << std::endl;
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@ enum {
|
|||
ACH_STATE_STARTED, /* start state */
|
||||
ACH_STATE_WAITING_FOR_ACHIEVEMENTS, /* waiting for child to fetch achievements */
|
||||
ACH_STATE_LOADING_GUI, /* feeding game rows to the model */
|
||||
ACH_STATE_WAITING_FOR_SCHEMA_PARSER,/* waiting for stats schema parser to get icons download names */
|
||||
ACH_STATE_DOWNLOADING_ICONS, /* fire off icon downloads (to be added to the model) */
|
||||
ACH_STATE_FINISHED /* fin ish state */
|
||||
};
|
||||
|
|
@ -88,6 +87,5 @@ private:
|
|||
IdleData m_app_idle_data;
|
||||
IdleData m_achievement_idle_data;
|
||||
|
||||
UserGameStatsSchemaParser m_schema_parser;
|
||||
MainPickerWindow* m_window;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -284,7 +284,7 @@ MainPickerWindow::confirm_game_list() {
|
|||
*/
|
||||
void
|
||||
MainPickerWindow::refresh_app_icon(AppId_t app_id) {
|
||||
const std::string path = get_app_icon_path(app_id);
|
||||
const std::string path = get_app_icon_path(g_steam->get_cache_path(), app_id);
|
||||
|
||||
for ( AppBoxRow* i : m_app_list_rows )
|
||||
{
|
||||
|
|
@ -303,9 +303,8 @@ MainPickerWindow::refresh_app_icon(AppId_t app_id) {
|
|||
*/
|
||||
void
|
||||
MainPickerWindow::refresh_achievement_icon(AppId_t app_id, std::string id) {
|
||||
const std::string path = get_achievement_icon_path(app_id, id);
|
||||
const std::string path = get_achievement_icon_path(g_steam->get_cache_path(), app_id, id);
|
||||
|
||||
// replace_icon(path, m_achievement_list_rows[id]->get_main_widget(), 64, 64);
|
||||
for ( AchievementBoxRow* i : m_achievement_list_rows )
|
||||
{
|
||||
if ( i->get_achievement().id == id ) {
|
||||
|
|
|
|||
|
|
@ -63,6 +63,9 @@ encode_achievement(yajl_gen handle, Achievement_t achievement) {
|
|||
yajl_gen_string_wrap(handle, ID_STR);
|
||||
yajl_gen_string_wrap(handle, achievement.id.c_str());
|
||||
|
||||
yajl_gen_string_wrap(handle, ICON_STR);
|
||||
yajl_gen_string_wrap(handle, achievement.icon_name.c_str());
|
||||
|
||||
// https://github.com/lloyd/yajl/issues/222
|
||||
yajl_gen_string_wrap(handle, RATE_STR);
|
||||
if (yajl_gen_double(handle, (double)achievement.global_achieved_rate) != yajl_gen_status_ok) {
|
||||
|
|
@ -80,12 +83,60 @@ encode_achievement(yajl_gen handle, Achievement_t achievement) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode an individual stat into a given YAJL handle
|
||||
*/
|
||||
void
|
||||
encode_stat(yajl_gen handle, StatValue_t stat) {
|
||||
|
||||
yajl_gen_string_wrap(handle, STAT_DISPLAY_STR);
|
||||
yajl_gen_string_wrap(handle, stat.display_name.c_str());
|
||||
|
||||
yajl_gen_string_wrap(handle, STAT_ID_STR);
|
||||
yajl_gen_string_wrap(handle, stat.id.c_str());
|
||||
|
||||
yajl_gen_string_wrap(handle, STAT_INCREMENTONLY_STR);
|
||||
if (yajl_gen_bool(handle, stat.incrementonly) != yajl_gen_status_ok) {
|
||||
std::cerr << "failed to make json" << std::endl;
|
||||
}
|
||||
|
||||
yajl_gen_string_wrap(handle, STAT_PERMISSION_STR);
|
||||
if (yajl_gen_integer(handle, stat.permission) != yajl_gen_status_ok) {
|
||||
std::cerr << "failed to make json" << std::endl;
|
||||
}
|
||||
|
||||
yajl_gen_string_wrap(handle, STAT_TYPE_STR);
|
||||
if (yajl_gen_integer(handle, (int)stat.type) != yajl_gen_status_ok) {
|
||||
std::cerr << "failed to make json" << std::endl;
|
||||
}
|
||||
|
||||
if ( stat.type == UserStatType::Integer )
|
||||
{
|
||||
int value = std::any_cast<int>(stat.value);
|
||||
|
||||
yajl_gen_string_wrap(handle, STAT_VALUE_STR);
|
||||
if (yajl_gen_integer(handle, value) != yajl_gen_status_ok) {
|
||||
std::cerr << "failed to make json" << std::endl;
|
||||
}
|
||||
}
|
||||
else if ( stat.type == UserStatType::Float )
|
||||
{
|
||||
float value = std::any_cast<float>(stat.value);
|
||||
|
||||
yajl_gen_string_wrap(handle, STAT_VALUE_STR);
|
||||
if (yajl_gen_double(handle, value) != yajl_gen_status_ok) {
|
||||
std::cerr << "failed to make json" << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode an achievement vector into a given YAJL handle
|
||||
*/
|
||||
void
|
||||
encode_achievements(yajl_gen handle, std::vector<Achievement_t> achievements) {
|
||||
encode_achievements(yajl_gen handle, std::vector<Achievement_t> achievements, std::vector<StatValue_t> stats) {
|
||||
|
||||
// Achievements array
|
||||
yajl_gen_string_wrap(handle, ACHIEVEMENT_LIST_STR);
|
||||
|
||||
if (yajl_gen_array_open(handle) != yajl_gen_status_ok) {
|
||||
|
|
@ -107,9 +158,122 @@ encode_achievements(yajl_gen handle, std::vector<Achievement_t> achievements) {
|
|||
if (yajl_gen_array_close(handle) != yajl_gen_status_ok) {
|
||||
std::cerr << "failed to make json" << std::endl;
|
||||
}
|
||||
|
||||
// Stats array
|
||||
yajl_gen_string_wrap(handle, STAT_LIST_STR);
|
||||
|
||||
if (yajl_gen_array_open(handle) != yajl_gen_status_ok) {
|
||||
std::cerr << "failed to make json" << std::endl;
|
||||
}
|
||||
|
||||
for (StatValue_t stat : stats) {
|
||||
if (yajl_gen_map_open(handle) != yajl_gen_status_ok) {
|
||||
std::cerr << "failed to make json" << std::endl;
|
||||
}
|
||||
|
||||
encode_stat(handle, stat);
|
||||
|
||||
if (yajl_gen_map_close(handle) != yajl_gen_status_ok) {
|
||||
std::cerr << "failed to make json" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
if (yajl_gen_array_close(handle) != yajl_gen_status_ok) {
|
||||
std::cerr << "failed to make json" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
//parsing the array inline would not be nice, so just extract them all here
|
||||
std::vector<StatValue_t>
|
||||
decode_stats(std::string response) {
|
||||
std::vector<StatValue_t> stats;
|
||||
char error_buffer[500];
|
||||
|
||||
yajl_val node = yajl_tree_parse(response.c_str(), error_buffer, 500);
|
||||
|
||||
if (node == NULL) {
|
||||
std::cerr << "Parsing error: " << error_buffer << std::endl;
|
||||
zenity();
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
const char * stat_path[] = { STAT_LIST_STR, (const char*)0 };
|
||||
const char * type_path[] = { STAT_TYPE_STR, (const char*)0 };
|
||||
const char * id_path[] = { STAT_ID_STR, (const char*)0 };
|
||||
const char * display_path[] = { STAT_DISPLAY_STR, (const char*)0 };
|
||||
const char * value_path[] = { STAT_VALUE_STR, (const char*)0 };
|
||||
const char * incrementonly_path[] = { STAT_INCREMENTONLY_STR, (const char*)0 };
|
||||
const char * permission_path[] = { STAT_PERMISSION_STR, (const char*)0 };
|
||||
|
||||
yajl_val v = yajl_tree_get(node, stat_path, yajl_t_array);
|
||||
if (v == NULL) {
|
||||
std::cerr << "parsing error (no stat listing found)" << std::endl;
|
||||
}
|
||||
|
||||
yajl_val *w = YAJL_GET_ARRAY(v)->values;
|
||||
size_t array_len = YAJL_GET_ARRAY(v)->len;
|
||||
|
||||
stats.clear();
|
||||
stats.resize(array_len);
|
||||
|
||||
for(unsigned i = 0; i < array_len; i++) {
|
||||
yajl_val cur_node = w[i];
|
||||
yajl_val cur_val;
|
||||
|
||||
cur_val = yajl_tree_get(cur_node, type_path, yajl_t_number);
|
||||
if (cur_val == NULL) {
|
||||
std::cerr << "parsing error (stat type)" << std::endl;
|
||||
}
|
||||
stats[i].type = (UserStatType)YAJL_GET_INTEGER(cur_val);
|
||||
|
||||
cur_val = yajl_tree_get(cur_node, id_path, yajl_t_string);
|
||||
if (cur_val == NULL) {
|
||||
std::cerr << "parsing error (stat id)" << std::endl;
|
||||
}
|
||||
stats[i].id = YAJL_GET_STRING(cur_val);
|
||||
|
||||
cur_val = yajl_tree_get(cur_node, display_path, yajl_t_string);
|
||||
if (cur_val == NULL) {
|
||||
std::cerr << "parsing error (stat display)" << std::endl;
|
||||
}
|
||||
stats[i].display_name = YAJL_GET_STRING(cur_val);
|
||||
|
||||
cur_val = yajl_tree_get(cur_node, incrementonly_path, yajl_t_any);
|
||||
if (cur_val == NULL) {
|
||||
std::cerr << "parsing error (stat incrementonly)" << std::endl;
|
||||
}
|
||||
if (!YAJL_IS_TRUE(cur_val) && !YAJL_IS_FALSE(cur_val)) {
|
||||
std::cerr << "bool parsing error (stat incrementonly)" << std::endl;
|
||||
}
|
||||
stats[i].incrementonly = YAJL_IS_TRUE(cur_val);
|
||||
|
||||
cur_val = yajl_tree_get(cur_node, permission_path, yajl_t_number);
|
||||
if (cur_val == NULL) {
|
||||
std::cerr << "parsing error (stat permission)" << std::endl;
|
||||
}
|
||||
stats[i].permission = YAJL_GET_INTEGER(cur_val);
|
||||
|
||||
if (stats[i].type == UserStatType::Integer)
|
||||
{
|
||||
cur_val = yajl_tree_get(cur_node, value_path, yajl_t_number);
|
||||
if (cur_val == NULL) {
|
||||
std::cerr << "parsing error (stat value int)" << std::endl;
|
||||
}
|
||||
stats[i].value = YAJL_GET_INTEGER(cur_val);
|
||||
}
|
||||
else if (stats[i].type == UserStatType::Float) {
|
||||
cur_val = yajl_tree_get(cur_node, value_path, yajl_t_number);
|
||||
if (cur_val == NULL) {
|
||||
std::cerr << "parsing error (stat value float)" << std::endl;
|
||||
}
|
||||
stats[i].value = YAJL_GET_DOUBLE(cur_val);
|
||||
}
|
||||
else {
|
||||
std::cerr << "Unable to get stat value: Unsupported type. " << stats[i].id << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
std::vector<Achievement_t>
|
||||
decode_achievements(std::string response) {
|
||||
|
|
@ -128,6 +292,7 @@ decode_achievements(std::string response) {
|
|||
// dumb defines for required interface for yajl_tree
|
||||
const char * list_path[] = { ACHIEVEMENT_LIST_STR, (const char*)0 };
|
||||
const char * name_path[] = { NAME_STR, (const char*)0 };
|
||||
const char * icon_path[] = { ICON_STR, (const char*)0 };
|
||||
const char * desc_path[] = { DESC_STR, (const char*)0 };
|
||||
const char * id_path[] = { ID_STR, (const char*)0 };
|
||||
const char * rate_path[] = { RATE_STR, (const char*)0 };
|
||||
|
|
@ -154,25 +319,31 @@ decode_achievements(std::string response) {
|
|||
// and via YAJL_IS_* checks if type alone isn't sufficient
|
||||
cur_val = yajl_tree_get(cur_node, name_path, yajl_t_string);
|
||||
if (cur_val == NULL) {
|
||||
std::cerr << "parsing error" << std::endl;
|
||||
std::cerr << "parsing error (ach name)" << std::endl;
|
||||
}
|
||||
achievements[i].name = YAJL_GET_STRING(cur_val);
|
||||
|
||||
cur_val = yajl_tree_get(cur_node, icon_path, yajl_t_string);
|
||||
if (cur_val == NULL) {
|
||||
std::cerr << "parsing error (ach icon)" << std::endl;
|
||||
}
|
||||
achievements[i].icon_name = YAJL_GET_STRING(cur_val);
|
||||
|
||||
cur_val = yajl_tree_get(cur_node, desc_path, yajl_t_string);
|
||||
if (cur_val == NULL) {
|
||||
std::cerr << "parsing error" << std::endl;
|
||||
std::cerr << "parsing error (ach desc)" << std::endl;
|
||||
}
|
||||
achievements[i].desc = YAJL_GET_STRING(cur_val);
|
||||
|
||||
cur_val = yajl_tree_get(cur_node, id_path, yajl_t_string);
|
||||
if (cur_val == NULL) {
|
||||
std::cerr << "parsing error" << std::endl;
|
||||
std::cerr << "parsing error (ach id)" << std::endl;
|
||||
}
|
||||
achievements[i].id = YAJL_GET_STRING(cur_val);
|
||||
|
||||
cur_val = yajl_tree_get(cur_node, rate_path, yajl_t_number);
|
||||
if (cur_val == NULL) {
|
||||
std::cerr << "parsing error" << std::endl;
|
||||
std::cerr << "parsing error (ach rate)" << std::endl;
|
||||
}
|
||||
if (!YAJL_IS_DOUBLE(cur_val)) {
|
||||
std::cerr << "double float parsing error" << std::endl;
|
||||
|
|
@ -182,7 +353,7 @@ decode_achievements(std::string response) {
|
|||
// why is bool parsing weird
|
||||
cur_val = yajl_tree_get(cur_node, achieved_path, yajl_t_any);
|
||||
if (cur_val == NULL) {
|
||||
std::cerr << "parsing error" << std::endl;
|
||||
std::cerr << "parsing error (ach achieved)" << std::endl;
|
||||
}
|
||||
if (!YAJL_IS_TRUE(cur_val) && !YAJL_IS_FALSE(cur_val)) {
|
||||
std::cerr << "bool parsing error" << std::endl;
|
||||
|
|
@ -191,7 +362,7 @@ decode_achievements(std::string response) {
|
|||
|
||||
cur_val = yajl_tree_get(cur_node, hidden_path, yajl_t_any);
|
||||
if (cur_val == NULL) {
|
||||
std::cerr << "parsing error" << std::endl;
|
||||
std::cerr << "parsing error (ach hidden)" << std::endl;
|
||||
}
|
||||
if (!YAJL_IS_TRUE(cur_val) && !YAJL_IS_FALSE(cur_val)) {
|
||||
std::cerr << "bool parsing error" << std::endl;
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
#include <yajl/yajl_gen.h>
|
||||
#include <yajl/yajl_tree.h>
|
||||
#include "../types/Achievement.h"
|
||||
#include "../types/StatValue.h"
|
||||
#include "../types/Actions.h"
|
||||
#include "ProcessedGameServerRequest.h"
|
||||
|
||||
|
|
@ -35,13 +36,18 @@ void encode_request(yajl_gen handle, const char * request);
|
|||
* Encode an array at a time because decoding individual
|
||||
* achievements in YAJL would be messy
|
||||
*/
|
||||
void encode_achievements(yajl_gen handle, std::vector<Achievement_t> achievements);
|
||||
void encode_achievements(yajl_gen handle, std::vector<Achievement_t> achievements, std::vector<StatValue_t> stats);
|
||||
|
||||
/**
|
||||
* Decode the achievement vector from a json response
|
||||
*/
|
||||
std::vector<Achievement_t> decode_achievements(std::string response);
|
||||
|
||||
/**
|
||||
* Decode the stats vector from a json response
|
||||
*/
|
||||
std::vector<StatValue_t> decode_stats(std::string response);
|
||||
|
||||
/**
|
||||
* Encode just achievement changes into a handle
|
||||
*/
|
||||
|
|
|
|||
20
src/main.cpp
20
src/main.cpp
|
|
@ -3,12 +3,12 @@
|
|||
* this project. Thank you.
|
||||
*************************************/
|
||||
|
||||
#include "schema_parser/UserGameStatsSchemaParser.h"
|
||||
#include "controller/MySteam.h"
|
||||
#include "controller/MySteamClient.h"
|
||||
#include "controller/MyGameServer.h"
|
||||
#include "gui/MainPickerWindowFactory.h"
|
||||
#include "cli/cli_funcs.h"
|
||||
#include "common/functions.h"
|
||||
#include "common/PerfMon.h"
|
||||
#include "globals.h"
|
||||
|
||||
|
|
@ -21,12 +21,9 @@
|
|||
**************************************/
|
||||
MySteam* g_steam = nullptr;
|
||||
MainPickerWindow* g_main_gui = nullptr;
|
||||
char* g_cache_folder = nullptr;
|
||||
char* g_runtime_folder = nullptr;
|
||||
MySteamClient* g_steamclient = nullptr;
|
||||
PerfMon* g_perfmon = nullptr;
|
||||
|
||||
|
||||
/**************************************
|
||||
* Main entry point
|
||||
**************************************/
|
||||
|
|
@ -39,21 +36,6 @@ main(int argc, char *argv[])
|
|||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (getenv("XDG_CACHE_HOME")) {
|
||||
g_cache_folder = concat( getenv("XDG_CACHE_HOME"), "/SamRewritten" );
|
||||
} else {
|
||||
g_cache_folder = concat( getenv("HOME"), "/.cache/SamRewritten" );
|
||||
}
|
||||
mkdir_default(g_cache_folder);
|
||||
|
||||
if (getenv("XDG_RUNTIME_DIR")) {
|
||||
g_runtime_folder = concat( getenv("XDG_RUNTIME_DIR"), "/SamRewritten" );
|
||||
mkdir_default(g_runtime_folder);
|
||||
} else {
|
||||
std::cerr << "XDG_RUNTIME_DIR is not set! Your distribution is improper... falling back to cache dir" << std::endl;
|
||||
g_runtime_folder = g_cache_folder;
|
||||
}
|
||||
|
||||
g_perfmon = new PerfMon();
|
||||
g_steam = MySteam::get_instance();
|
||||
g_steamclient = new MySteamClient();
|
||||
|
|
|
|||
|
|
@ -28,6 +28,9 @@
|
|||
#include "KeyValue.h"
|
||||
#include <strings.h>
|
||||
|
||||
// https://stackoverflow.com/questions/8267847/why-should-i-initialize-static-class-variables-in-c
|
||||
KeyValue* KeyValue::Invalid = new KeyValue();
|
||||
|
||||
// Stream helper functions
|
||||
|
||||
int8_t read_value_u8(std::istream * is) {
|
||||
|
|
@ -74,11 +77,11 @@ std::string read_string(std::istream * is) {
|
|||
return std::string(buf);
|
||||
}
|
||||
|
||||
// returns NULL if no children or children not found
|
||||
// returns KeyValue::Invalid if no children or children not found
|
||||
//KeyValue* KeyValue::operator[](std::string key) {
|
||||
KeyValue* KeyValue::get(std::string key) {
|
||||
if (this->valid == false) {
|
||||
return NULL;
|
||||
return KeyValue::Invalid;
|
||||
}
|
||||
|
||||
KeyValue* select_child = NULL;
|
||||
|
|
@ -88,7 +91,8 @@ KeyValue* KeyValue::get(std::string key) {
|
|||
select_child = child;
|
||||
}
|
||||
}
|
||||
return select_child;
|
||||
|
||||
return select_child ?: KeyValue::Invalid;
|
||||
}
|
||||
|
||||
KeyValue* KeyValue::get2(std::string key1, std::string key2) {
|
||||
|
|
@ -139,10 +143,9 @@ int KeyValue::as_integer(int default_value) {
|
|||
case KeyValueType::String:
|
||||
case KeyValueType::WideString:
|
||||
{
|
||||
// eh exception handling
|
||||
try {
|
||||
return std::stoi(std::any_cast<std::string>(this->value));
|
||||
} catch (std::exception& e) {
|
||||
} catch (std::exception&) {
|
||||
return default_value;
|
||||
}
|
||||
}
|
||||
|
|
@ -163,25 +166,101 @@ int KeyValue::as_integer(int default_value) {
|
|||
break;
|
||||
}
|
||||
|
||||
case KeyValueType::Color:
|
||||
case KeyValueType::Pointer:
|
||||
default: {}
|
||||
}
|
||||
|
||||
return default_value;
|
||||
}
|
||||
|
||||
float KeyValue::as_float(float default_value) {
|
||||
if (this->valid == false) {
|
||||
return default_value;
|
||||
}
|
||||
|
||||
if (!this->value.has_value()) { // This check is not part of original C# SAM
|
||||
return default_value;
|
||||
}
|
||||
|
||||
switch (this->type) {
|
||||
case KeyValueType::String:
|
||||
case KeyValueType::WideString:
|
||||
{
|
||||
break;
|
||||
try {
|
||||
return std::stof(std::any_cast<std::string>(this->value));
|
||||
} catch (std::exception&) {
|
||||
return default_value;
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
case KeyValueType::Int32:
|
||||
{
|
||||
std::cout << "Invalid type referenced in stats parser!" << std::endl;
|
||||
return default_value;
|
||||
return std::any_cast<int32_t>(this->value);
|
||||
}
|
||||
|
||||
case KeyValueType::UInt64:
|
||||
{
|
||||
return std::any_cast<uint64_t>(this->value);
|
||||
}
|
||||
|
||||
case KeyValueType::Float32:
|
||||
{
|
||||
return std::any_cast<float>(this->value);
|
||||
}
|
||||
|
||||
default: {}
|
||||
}
|
||||
|
||||
return default_value;
|
||||
}
|
||||
|
||||
bool KeyValue::as_boolean(bool default_value) {
|
||||
if (this->valid == false)
|
||||
{
|
||||
return default_value;
|
||||
}
|
||||
|
||||
switch (this->type)
|
||||
{
|
||||
case KeyValueType::String:
|
||||
case KeyValueType::WideString:
|
||||
{
|
||||
try {
|
||||
int pr = std::stoi(std::any_cast<std::string>(this->value));
|
||||
return pr != 0;
|
||||
} catch (std::exception&) {
|
||||
return default_value;
|
||||
}
|
||||
}
|
||||
|
||||
case KeyValueType::Int32:
|
||||
{
|
||||
return std::any_cast<int32_t>(this->value) != 0;
|
||||
}
|
||||
|
||||
case KeyValueType::UInt64:
|
||||
{
|
||||
return std::any_cast<uint64_t>(this->value) != 0;
|
||||
}
|
||||
|
||||
case KeyValueType::Float32:
|
||||
{
|
||||
return std::any_cast<float>(this->value) != 0;
|
||||
}
|
||||
|
||||
default: {}
|
||||
}
|
||||
|
||||
return default_value;
|
||||
}
|
||||
|
||||
KeyValue* KeyValue::load_as_binary(std::string path) {
|
||||
#ifdef DEBUG_CERR
|
||||
std::cerr << "Parsing schema file " << path << std::endl;
|
||||
#endif
|
||||
|
||||
std::filebuf fb;
|
||||
if (!fb.open(path, std::ios::in)) {
|
||||
std::cerr << "Unable to open schema file: " << path << std::endl;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -46,8 +46,7 @@ enum class KeyValueType : unsigned char
|
|||
|
||||
class KeyValue {
|
||||
private:
|
||||
// invalid doesn't map easily to C++,
|
||||
// just use NULL for bad return of [] operator
|
||||
static KeyValue* Invalid;
|
||||
|
||||
public:
|
||||
std::string name = "<root>";
|
||||
|
|
@ -62,6 +61,8 @@ public:
|
|||
|
||||
std::string as_string(std::string default_value);
|
||||
int as_integer(int default_value);
|
||||
float as_float(float default_value);
|
||||
bool as_boolean(bool default_value);
|
||||
// Other as_type function can be implemented as needed
|
||||
|
||||
static KeyValue* load_as_binary(std::string path);
|
||||
|
|
@ -74,4 +75,4 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
};
|
||||
};
|
||||
|
|
@ -24,17 +24,23 @@
|
|||
// The original SAM is available at https://github.com/gibbed/SteamAchievementManager
|
||||
// To comply with copyright, the above license is included.
|
||||
|
||||
#include <strings.h>
|
||||
#include <memory>
|
||||
|
||||
#include "UserGameStatsSchemaParser.h"
|
||||
#include "KeyValue.h"
|
||||
#include "../types/UserStatType.h"
|
||||
#include "../controller/MySteam.h"
|
||||
#include "../globals.h"
|
||||
#include <strings.h>
|
||||
|
||||
#include "../types/IntegerStatDefinition.h"
|
||||
#include "../types/FloatStatDefinition.h"
|
||||
|
||||
|
||||
bool
|
||||
UserGameStatsSchemaParser::load_user_game_stats_schema() {
|
||||
m_icon_download_names.clear();
|
||||
clear_stat_definitions();
|
||||
|
||||
std::string appid_string = std::to_string(g_steam->get_current_appid());
|
||||
std::string schema_file = g_steam->get_steam_install_path() + "/appcache/stats/UserGameStatsSchema_" + appid_string + ".bin";
|
||||
|
|
@ -74,14 +80,34 @@ UserGameStatsSchemaParser::load_user_game_stats_schema() {
|
|||
|
||||
case UserStatType::Integer:
|
||||
{
|
||||
// don't care currently
|
||||
IntegerStatDefinition* def = new IntegerStatDefinition;
|
||||
def->type = UserStatType::Integer;
|
||||
def->Id = stat->get("name")->as_string("");
|
||||
def->DisplayName = stat->get2("display", "name")->as_string("");
|
||||
def->MinValue = stat->get("min")->as_integer(0);
|
||||
def->MaxValue = stat->get("max")->as_integer(0);
|
||||
def->MaxChange = stat->get("maxchange")->as_integer(0);
|
||||
def->IncrementOnly = stat->get("incrementonly")->as_boolean(false);
|
||||
def->DefaultValue = stat->get("default")->as_integer(0);
|
||||
def->Permission = stat->get("permission")->as_integer(0);
|
||||
m_stats.push_back(def);
|
||||
break;
|
||||
}
|
||||
|
||||
case UserStatType::Float:
|
||||
case UserStatType::AverageRate:
|
||||
{
|
||||
// don't care currently
|
||||
FloatStatDefinition* def = new FloatStatDefinition;
|
||||
def->type = UserStatType::Float;
|
||||
def->Id = stat->get("name")->as_string("");
|
||||
def->DisplayName = stat->get2("display", "name")->as_string("");
|
||||
def->MinValue = stat->get("min")->as_float(0);
|
||||
def->MaxValue = stat->get("max")->as_float(0);
|
||||
def->MaxChange = stat->get("maxchange")->as_float(0);
|
||||
def->IncrementOnly = stat->get("incrementonly")->as_boolean(false);
|
||||
def->DefaultValue = stat->get("default")->as_float(0);
|
||||
def->Permission = stat->get("permission")->as_integer(0);
|
||||
m_stats.push_back(def);
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -136,4 +162,13 @@ UserGameStatsSchemaParser::load_user_game_stats_schema() {
|
|||
|
||||
delete kv;
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
UserGameStatsSchemaParser::clear_stat_definitions() {
|
||||
for (auto p : m_stats )
|
||||
{
|
||||
delete p;
|
||||
}
|
||||
m_stats.clear();
|
||||
}
|
||||
|
|
@ -27,7 +27,11 @@
|
|||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "../types/StatDefinition.h"
|
||||
|
||||
|
||||
class UserGameStatsSchemaParser
|
||||
{
|
||||
|
|
@ -40,11 +44,22 @@ public:
|
|||
bool load_user_game_stats_schema();
|
||||
|
||||
/**
|
||||
* Simple getter
|
||||
* Simple getters
|
||||
*/
|
||||
std::map<std::string, std::string> get_icon_download_names() const { return m_icon_download_names; };
|
||||
std::vector<StatDefinition*> get_stat_definitions() const { return m_stats; };
|
||||
|
||||
/**
|
||||
* Because stats are pointers, we need to free them before clearing the array
|
||||
* Should I use make_unique instead? Probably
|
||||
*/
|
||||
void clear_stat_definitions();
|
||||
|
||||
private:
|
||||
// Mapping between achievement ID and the actual icon name on servers.
|
||||
// Icon name is retrieved by the stats schema parser
|
||||
std::map<std::string, std::string> m_icon_download_names;
|
||||
|
||||
// Container for app stats. Pointer for later casts
|
||||
std::vector<StatDefinition*> m_stats;
|
||||
};
|
||||
|
|
@ -53,6 +53,11 @@ MyClientSocket::request_response(std::string request)
|
|||
send_message(request);
|
||||
std::string ret = receive_message();
|
||||
disconnect();
|
||||
|
||||
#ifdef DEBUG_CERR
|
||||
std::cerr << "<-- Receiving " << ret << std::endl;
|
||||
#endif
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,11 +2,16 @@
|
|||
#include <yajl/yajl_tree.h>
|
||||
#include "MyGameSocket.h"
|
||||
#include "../types/Actions.h"
|
||||
#include "../types/IntegerStatDefinition.h"
|
||||
#include "../types/FloatStatDefinition.h"
|
||||
#include "../json/ProcessedGameServerRequest.h"
|
||||
#include "../json/yajlHelpers.h"
|
||||
#include "../globals.h"
|
||||
#include "../common/PerfMon.h"
|
||||
#include "../common/functions.h"
|
||||
#include "../schema_parser/UserGameStatsSchemaParser.h"
|
||||
|
||||
#include "../controller/MySteam.h"
|
||||
|
||||
MyGameSocket::MyGameSocket(AppId_t appid) :
|
||||
MyServerSocket(appid),
|
||||
|
|
@ -30,7 +35,8 @@ MyGameSocket::process_request(std::string request, bool& quit) {
|
|||
|
||||
switch (r.getAction()) {
|
||||
case GET_ACHIEVEMENTS:
|
||||
encode_achievements(handle, get_achievements()); // Write achievements to handle
|
||||
populate_stats_and_achievements();
|
||||
encode_achievements(handle, get_achievements(), get_stats()); // Write achievements to handle
|
||||
break;
|
||||
|
||||
case STORE_ACHIEVEMENTS:
|
||||
|
|
@ -56,16 +62,18 @@ MyGameSocket::process_request(std::string request, bool& quit) {
|
|||
ret = std::string((const char*)buf);
|
||||
yajl_gen_free(handle);
|
||||
|
||||
#ifdef DEBUG_CERR
|
||||
std::cerr << "--> Sending buffer " << ret << std::endl;
|
||||
#endif
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<Achievement_t>
|
||||
MyGameSocket::get_achievements() {
|
||||
|
||||
void
|
||||
MyGameSocket::populate_stats_and_achievements() {
|
||||
m_stats_callback_received = false;
|
||||
|
||||
ISteamUserStats *stats_api = SteamUserStats();
|
||||
if (!stats_api->RequestCurrentStats()) {
|
||||
if (!SteamUserStats()->RequestCurrentStats()) {
|
||||
std::cerr << "ERROR: User not logged in, exiting." << std::endl;
|
||||
zenity("Please login to Steam before using SamRewritten. If this is the case, please report it with a Github issue.");
|
||||
exit(EXIT_FAILURE);
|
||||
|
|
@ -79,16 +87,14 @@ MyGameSocket::get_achievements() {
|
|||
if (!get_global_stats())
|
||||
{
|
||||
std::cerr << "An error occurred getting global stats." << std::endl;
|
||||
return m_achievement_list;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!get_global_achievements_stats())
|
||||
{
|
||||
std::cerr << "An error occurred getting global achievements stats." << std::endl;
|
||||
return m_achievement_list;
|
||||
return;
|
||||
}
|
||||
|
||||
return m_achievement_list;
|
||||
}
|
||||
|
||||
bool
|
||||
|
|
@ -135,14 +141,16 @@ MyGameSocket::get_global_achievements_stats() {
|
|||
void
|
||||
MyGameSocket::OnUserStatsReceived(UserStatsReceived_t *callback) {
|
||||
m_achievement_list.clear();
|
||||
m_stats_list.clear();
|
||||
|
||||
// Check if we received the values for the correct app
|
||||
if (SteamUtils()->GetAppID() == callback->m_nGameID) {
|
||||
if ( k_EResultOK == callback->m_eResult ) {
|
||||
ISteamUserStats *stats_api = SteamUserStats();
|
||||
m_schema_parser.load_user_game_stats_schema();
|
||||
|
||||
// ==============================
|
||||
// RETRIEVE IDS
|
||||
// RETRIEVE ACHIEVEMENTS
|
||||
// ==============================
|
||||
const unsigned num_ach = stats_api->GetNumAchievements();
|
||||
|
||||
|
|
@ -153,17 +161,81 @@ MyGameSocket::OnUserStatsReceived(UserStatsReceived_t *callback) {
|
|||
m_achievement_list.resize(num_ach);
|
||||
|
||||
for (unsigned i = 0; i < num_ach ; i++) {
|
||||
const char * pchName = stats_api->GetAchievementName(i);
|
||||
|
||||
m_achievement_list[i].id = stats_api->GetAchievementName(i);
|
||||
|
||||
const char * pchName = m_achievement_list[i].id.c_str();
|
||||
|
||||
m_achievement_list[i].id = pchName;
|
||||
m_achievement_list[i].name = stats_api->GetAchievementDisplayAttribute(pchName, "name");
|
||||
m_achievement_list[i].desc = stats_api->GetAchievementDisplayAttribute(pchName, "desc");
|
||||
|
||||
// Value set in OnGlobalAchievementPercentagesReceived
|
||||
m_achievement_list[i].global_achieved_rate = 0;
|
||||
stats_api->GetAchievement(pchName, &(m_achievement_list[i].achieved));
|
||||
m_achievement_list[i].hidden = (bool)strcmp(stats_api->GetAchievementDisplayAttribute(pchName, "hidden" ), "0");
|
||||
m_achievement_list[i].icon_name = m_schema_parser.get_icon_download_names()[m_achievement_list[i].id];
|
||||
}
|
||||
|
||||
// ============================
|
||||
// RETRIEVE STATS
|
||||
// ============================
|
||||
|
||||
for ( auto def : m_schema_parser.get_stat_definitions() )
|
||||
{
|
||||
StatValue_t sv;
|
||||
|
||||
if ( def->type == UserStatType::Integer )
|
||||
{
|
||||
IntegerStatDefinition* cast = dynamic_cast<IntegerStatDefinition*>(def);
|
||||
|
||||
if (cast == NULL) {
|
||||
std::cerr << "Cast error to int for " << def->Id << ", please report to Github!" << std::endl;
|
||||
continue;
|
||||
}
|
||||
|
||||
int value;
|
||||
bool success = stats_api->GetStat(def->Id.c_str(), &value);
|
||||
if ( !success )
|
||||
{
|
||||
std::cerr << "Unable to get int stat " << def->Id << std::endl;
|
||||
continue;
|
||||
}
|
||||
|
||||
sv.type = cast->type;
|
||||
sv.display_name = cast->DisplayName;
|
||||
sv.id = cast->Id;
|
||||
sv.incrementonly = cast->IncrementOnly;
|
||||
sv.original_value = value;
|
||||
sv.value = value;
|
||||
sv.permission = cast->Permission;
|
||||
|
||||
m_stats_list.push_back(sv);
|
||||
}
|
||||
else if ( def->type == UserStatType::Float )
|
||||
{
|
||||
FloatStatDefinition* cast = dynamic_cast<FloatStatDefinition*>(def);
|
||||
|
||||
if (cast == NULL) {
|
||||
std::cerr << "Cast error to float for " << def->Id << ", please report to Github!" << std::endl;
|
||||
continue;
|
||||
}
|
||||
|
||||
float value;
|
||||
bool success = stats_api->GetStat(def->Id.c_str(), &value);
|
||||
if ( !success )
|
||||
{
|
||||
std::cerr << "Unable to get float stat " << def->Id << std::endl;
|
||||
continue;
|
||||
}
|
||||
|
||||
sv.type = cast->type;
|
||||
sv.display_name = cast->DisplayName;
|
||||
sv.id = cast->Id;
|
||||
sv.incrementonly = cast->IncrementOnly;
|
||||
sv.original_value = value;
|
||||
sv.value = value;
|
||||
sv.permission = cast->Permission;
|
||||
|
||||
m_stats_list.push_back(sv);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
|
|
@ -183,7 +255,10 @@ MyGameSocket::OnGlobalStatsReceived(GlobalStatsReceived_t *callback, bool bIOFai
|
|||
std::cerr << "GlobalStatsReceived_t failed! Enum: " << callback->m_eResult << std::endl;
|
||||
}
|
||||
|
||||
#ifdef DEBUG_CERR
|
||||
std::cout << "Got stats, maybe I can do cool stuff with them, gotta check." << std::endl;
|
||||
#endif
|
||||
|
||||
m_global_callback_received = true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
#pragma once
|
||||
#include "MyServerSocket.h"
|
||||
#include "../schema_parser/UserGameStatsSchemaParser.h"
|
||||
#include "../types/Achievement.h"
|
||||
#include "../types/StatValue.h"
|
||||
#include "../../steam/steam_api.h"
|
||||
|
||||
#include <iostream>
|
||||
|
|
@ -13,13 +15,19 @@
|
|||
class MyGameSocket : public MyServerSocket
|
||||
{
|
||||
public:
|
||||
MyGameSocket(AppId_t appid);
|
||||
|
||||
std::string process_request(std::string request, bool& quit);
|
||||
std::vector<Achievement_t> get_achievements(void);
|
||||
void populate_stats_and_achievements(void);
|
||||
bool get_global_stats(void);
|
||||
bool get_global_achievements_stats(void);
|
||||
void process_changes(std::vector<AchievementChange_t> changes);
|
||||
|
||||
MyGameSocket(AppId_t appid);
|
||||
/**
|
||||
* Simple getters
|
||||
*/
|
||||
std::vector<Achievement_t> get_achievements() const { return m_achievement_list; };
|
||||
std::vector<StatValue_t> get_stats() const { return m_stats_list; };
|
||||
|
||||
/**
|
||||
* Steam API callback to handle the received stats and achievements
|
||||
|
|
@ -27,6 +35,8 @@ public:
|
|||
STEAM_CALLBACK( MyGameSocket, OnUserStatsReceived, UserStatsReceived_t, m_CallbackUserStatsReceived );
|
||||
|
||||
private:
|
||||
UserGameStatsSchemaParser m_schema_parser;
|
||||
|
||||
std::atomic_bool m_stats_callback_received;
|
||||
std::atomic_bool m_global_callback_received;
|
||||
std::atomic_bool m_global_achievements_callback_ready;
|
||||
|
|
@ -38,6 +48,6 @@ private:
|
|||
CCallResult< MyGameSocket, GlobalAchievementPercentagesReady_t > m_GlobalAchievementPercentagesReadyCallResult;
|
||||
|
||||
std::vector<Achievement_t> m_achievement_list;
|
||||
|
||||
std::vector<StatValue_t> m_stats_list;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -66,7 +66,9 @@ MyServerSocket::run_server()
|
|||
|
||||
if (quit)
|
||||
{
|
||||
#ifdef DEBUG_CERR
|
||||
std::cout << "Shutting down server safely." << std:: endl;
|
||||
#endif
|
||||
// destruction of this object will take care of shutdown
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,12 +2,13 @@
|
|||
|
||||
#include "../globals.h"
|
||||
#include "../common/functions.h"
|
||||
#include "../controller/MySteam.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
MySocket::MySocket(AppId_t appid) : m_appid(appid), m_socket_fd(-1)
|
||||
{
|
||||
m_socket_path = std::string(g_runtime_folder) + "/" + std::to_string(m_appid) + "-ipc.sock";
|
||||
m_socket_path = g_steam->get_runtime_path() + "/" + std::to_string(m_appid) + "-ipc.sock";
|
||||
}
|
||||
|
||||
MySocket::~MySocket()
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ struct Achievement_t {
|
|||
std::string name;
|
||||
std::string desc;
|
||||
std::string id;
|
||||
std::string icon_name;
|
||||
float global_achieved_rate;
|
||||
bool achieved;
|
||||
bool hidden;
|
||||
|
|
|
|||
|
|
@ -9,13 +9,24 @@
|
|||
// Mirroring structure of Achievement_t, should be combined with that
|
||||
#define SAM_ACTION_STR "SAM_ACTION"
|
||||
#define ACHIEVEMENT_LIST_STR "ACHIEVEMENT_LIST"
|
||||
#define STAT_LIST_STR "STAT_LIST"
|
||||
#define NAME_STR "NAME"
|
||||
#define DESC_STR "DESC"
|
||||
#define ICON_STR "ICON"
|
||||
#define ID_STR "ID"
|
||||
#define RATE_STR "RATE"
|
||||
#define ACHIEVED_STR "ACHIEVED"
|
||||
#define HIDDEN_STR "HIDDEN"
|
||||
|
||||
// Mirroring structure of StatValue_t, should be combined with that
|
||||
#define STAT_TYPE_STR "STAT_TYPE"
|
||||
#define STAT_ID_STR "STAT_ID"
|
||||
#define STAT_DISPLAY_STR "STAT_DISPLAY"
|
||||
#define STAT_VALUE_STR "STAT_VALUE"
|
||||
#define STAT_INCREMENTONLY_STR "STAT_INCREMENTONLY"
|
||||
#define STAT_PERMISSION_STR "STAT_PERMISSION"
|
||||
|
||||
// Misc
|
||||
#define SAM_ACK_STR "SAM_ACK"
|
||||
|
||||
// TODO: Add SAM_START as an action to this too?
|
||||
|
|
@ -65,6 +76,20 @@ Server --> Client
|
|||
.
|
||||
.
|
||||
.
|
||||
],
|
||||
STAT_LIST_STR:
|
||||
[
|
||||
{
|
||||
STAT_DISPLAY_STR: "desc",
|
||||
STAT_ID_STR: "ID",
|
||||
STAT_INCREMENTONLY_STR: true/false,
|
||||
STAT_PERMISSION_STR: <int value>,
|
||||
STAT_TYPE_STR: <int cast of UserStatType>,
|
||||
STAT_VALUE_STR: <int or float>
|
||||
},
|
||||
.
|
||||
.
|
||||
.
|
||||
]
|
||||
}
|
||||
|
||||
|
|
|
|||
35
src/types/FloatStatDefinition.h
Normal file
35
src/types/FloatStatDefinition.h
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
/* Copyright (c) 2019 Rick (rick 'at' gibbed 'dot' us)
|
||||
*
|
||||
* This software is provided 'as-is', without any express or implied
|
||||
* warranty. In no event will the authors be held liable for any damages
|
||||
* arising from the use of this software.
|
||||
*
|
||||
* Permission is granted to anyone to use this software for any purpose,
|
||||
* including commercial applications, and to alter it and redistribute it
|
||||
* freely, subject to the following restrictions:
|
||||
*
|
||||
* 1. The origin of this software must not be misrepresented; you must not
|
||||
* claim that you wrote the original software. If you use this software
|
||||
* in a product, an acknowledgment in the product documentation would
|
||||
* be appreciated but is not required.
|
||||
*
|
||||
* 2. Altered source versions must be plainly marked as such, and must not
|
||||
* be misrepresented as being the original software.
|
||||
*
|
||||
* 3. This notice may not be removed or altered from any source
|
||||
* distribution.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "StatDefinition.h"
|
||||
|
||||
struct FloatStatDefinition : StatDefinition
|
||||
{
|
||||
float MinValue;
|
||||
float MaxValue;
|
||||
float MaxChange;
|
||||
bool IncrementOnly;
|
||||
float DefaultValue;
|
||||
};
|
||||
|
||||
typedef struct FloatStatDefinition FloatStatDefinition;
|
||||
35
src/types/IntegerStatDefinition.h
Normal file
35
src/types/IntegerStatDefinition.h
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
/* Copyright (c) 2019 Rick (rick 'at' gibbed 'dot' us)
|
||||
*
|
||||
* This software is provided 'as-is', without any express or implied
|
||||
* warranty. In no event will the authors be held liable for any damages
|
||||
* arising from the use of this software.
|
||||
*
|
||||
* Permission is granted to anyone to use this software for any purpose,
|
||||
* including commercial applications, and to alter it and redistribute it
|
||||
* freely, subject to the following restrictions:
|
||||
*
|
||||
* 1. The origin of this software must not be misrepresented; you must not
|
||||
* claim that you wrote the original software. If you use this software
|
||||
* in a product, an acknowledgment in the product documentation would
|
||||
* be appreciated but is not required.
|
||||
*
|
||||
* 2. Altered source versions must be plainly marked as such, and must not
|
||||
* be misrepresented as being the original software.
|
||||
*
|
||||
* 3. This notice may not be removed or altered from any source
|
||||
* distribution.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "StatDefinition.h"
|
||||
|
||||
struct IntegerStatDefinition : StatDefinition
|
||||
{
|
||||
int MinValue;
|
||||
int MaxValue;
|
||||
int MaxChange;
|
||||
bool IncrementOnly;
|
||||
int DefaultValue;
|
||||
};
|
||||
|
||||
typedef struct IntegerStatDefinition IntegerStatDefinition;
|
||||
39
src/types/StatDefinition.h
Normal file
39
src/types/StatDefinition.h
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
/* Copyright (c) 2019 Rick (rick 'at' gibbed 'dot' us)
|
||||
*
|
||||
* This software is provided 'as-is', without any express or implied
|
||||
* warranty. In no event will the authors be held liable for any damages
|
||||
* arising from the use of this software.
|
||||
*
|
||||
* Permission is granted to anyone to use this software for any purpose,
|
||||
* including commercial applications, and to alter it and redistribute it
|
||||
* freely, subject to the following restrictions:
|
||||
*
|
||||
* 1. The origin of this software must not be misrepresented; you must not
|
||||
* claim that you wrote the original software. If you use this software
|
||||
* in a product, an acknowledgment in the product documentation would
|
||||
* be appreciated but is not required.
|
||||
*
|
||||
* 2. Altered source versions must be plainly marked as such, and must not
|
||||
* be misrepresented as being the original software.
|
||||
*
|
||||
* 3. This notice may not be removed or altered from any source
|
||||
* distribution.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include "UserStatType.h"
|
||||
|
||||
struct StatDefinition
|
||||
{
|
||||
std::string Id;
|
||||
std::string DisplayName;
|
||||
int Permission;
|
||||
UserStatType type;
|
||||
|
||||
// https://stackoverflow.com/questions/36736167/c-cast-struct-dynamically-based-on-subtype
|
||||
// We need this or any other virtual member to make Base polymorphic
|
||||
virtual ~StatDefinition () { }
|
||||
};
|
||||
|
||||
typedef struct StatDefinition StatDefinition;
|
||||
18
src/types/StatValue.h
Normal file
18
src/types/StatValue.h
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <any>
|
||||
|
||||
#include "UserStatType.h"
|
||||
|
||||
struct StatValue_t {
|
||||
UserStatType type;
|
||||
std::string id;
|
||||
std::string display_name;
|
||||
std::any value;
|
||||
std::any original_value;
|
||||
bool incrementonly;
|
||||
int permission;
|
||||
};
|
||||
|
||||
typedef struct StatValue_t StatValue_t;
|
||||
Loading…
Reference in New Issue
Block a user