mirror of
https://github.com/zebrajr/SamRewritten.git
synced 2025-12-06 12:19:51 +01:00
Implement remainder of JSON achievement retrieval
Implement the rest of the logic needed for retrieving achievements via a JSON interface. Use YAJL for this encoding and decoding achievements and requests. Pull out most of the YAJL functionality into YAJL helpers. The YAJL wrapping and abstraction can still be done better, but now at least a good start is in place. Upgrade Achievement_t to use C++ types Delete GameEmulator.h/c since the sockets are feature-complete enough Next commit will implement storing achievements via JSON interface.
This commit is contained in:
parent
c6c1aaee9e
commit
6fe0acfbad
|
|
@ -1,435 +0,0 @@
|
|||
#include "GameEmulator.h"
|
||||
#include <csignal>
|
||||
#include <iostream>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include "common/functions.h"
|
||||
#include "globals.h"
|
||||
#include "gui/MainPickerWindow.h"
|
||||
|
||||
/****************************
|
||||
* SIGNAL CALLBACKS
|
||||
****************************/
|
||||
|
||||
/**
|
||||
* Used by the child process when the parent tells him to
|
||||
* stop the steam app. The child process will die.
|
||||
*/
|
||||
void
|
||||
handle_sigterm(int signum) {
|
||||
SteamAPI_Shutdown();
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by the parent process to remove the zombie process
|
||||
* of the child, once it terminated
|
||||
*/
|
||||
void
|
||||
handle_sigchld(int signum) {
|
||||
pid_t pid;
|
||||
GameEmulator* emulator = GameEmulator::get_instance();
|
||||
|
||||
while (true) {
|
||||
pid = waitpid(WAIT_ANY, NULL, WNOHANG);
|
||||
if (pid == 0)
|
||||
return;
|
||||
else if (pid == -1)
|
||||
return;
|
||||
else {
|
||||
std::cerr << "Steam game terminated." << std::endl;
|
||||
emulator->m_son_pid = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* When the parent receives SIGUSR1, it will read the pipe,
|
||||
* and if everything goes well, it should fill the member
|
||||
* achievements list, and the achievement count.
|
||||
* Once done, will update the view
|
||||
*
|
||||
* This one might need a little more documentation on the technical side.
|
||||
* This method is only received on the parent process. The son,
|
||||
* will retrieve the stats and achievements from steam, and once it is done,
|
||||
* it will send a SIGUSR1 to the parent back. The parent will then save the
|
||||
* new data, and update the view accordingly.
|
||||
*
|
||||
* TODO: Check for errors
|
||||
*/
|
||||
void handle_sigusr1_parent(int signum) {
|
||||
GameEmulator *inst = GameEmulator::get_instance();
|
||||
|
||||
read_count(inst->m_pipe[0], &inst->m_achievement_count, sizeof(unsigned));
|
||||
|
||||
if (inst->m_achievement_list != nullptr) {
|
||||
free(inst->m_achievement_list);
|
||||
inst->m_achievement_list = nullptr;
|
||||
}
|
||||
|
||||
inst->m_achievement_list = (Achievement_t*)malloc(inst->m_achievement_count * sizeof(Achievement_t));
|
||||
|
||||
if (!inst->m_achievement_list) {
|
||||
std::cerr << "ERROR: could not allocate memory." << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
for (unsigned i = 0; i < inst->m_achievement_count; i++) {
|
||||
read_count(inst->m_pipe[0], &(inst->m_achievement_list[i]), sizeof(Achievement_t));
|
||||
}
|
||||
|
||||
inst->update_view();
|
||||
}
|
||||
|
||||
/**
|
||||
* We are the child and we will receive data/stats of achievements to unlock/relock
|
||||
* then auto-commit changes and update the parent. Coodinating based on the number
|
||||
* of changes avoids potentially losing signals because of multiple being in flight.
|
||||
*
|
||||
* Data will have this shitty format:
|
||||
* - 1 unsigned for number of changes
|
||||
* - 1 char, 'a' for achievement, 's' for "stat"
|
||||
* - 1 unsigned int, 0 => locked, 1 => unlocked, or the stat progression
|
||||
* - The length of MAX_ID_LENGTH to get the achievement ID
|
||||
*
|
||||
* If someone wants to update this repo, maybe IPC is too low level for what I want
|
||||
* to achieve. This doesn't require super fast speed or anything, maybe see if
|
||||
* TCP server is easier to use. Or at least one type of interfacing because this code
|
||||
* gets really ugly over time, bad practices become usual.
|
||||
*/
|
||||
void handle_sigusr1_child(int signum) {
|
||||
GameEmulator *inst = GameEmulator::get_instance();
|
||||
ISteamUserStats *stats_api = SteamUserStats();
|
||||
int* pipe = inst->m_pipe;
|
||||
char type;
|
||||
|
||||
// Read number of changes
|
||||
unsigned num_changes;
|
||||
read_count(pipe[0], &num_changes, sizeof(unsigned));
|
||||
|
||||
for (unsigned i = 0; i < num_changes; i++)
|
||||
{
|
||||
read_count(pipe[0], &type, sizeof(char));
|
||||
|
||||
if (type == 'a') {
|
||||
// We want to edit an achievement
|
||||
unsigned value;
|
||||
char achievement_id[MAX_ACHIEVEMENT_ID_LENGTH];
|
||||
|
||||
read_count(pipe[0], &value, sizeof(unsigned));
|
||||
read_count(pipe[0], &achievement_id, MAX_ACHIEVEMENT_ID_LENGTH * sizeof(char));
|
||||
|
||||
if (value == 0) {
|
||||
// We want to relock an achievement
|
||||
if (!stats_api->ClearAchievement(achievement_id)) {
|
||||
std::cerr << "Relocking achievement " << achievement_id << " failed" << std::endl;
|
||||
//keep going so we don't corrupt the pipe
|
||||
} else {
|
||||
std::cerr << "Relocked achievement " << achievement_id << std::endl;
|
||||
}
|
||||
} else {
|
||||
// We want to unlock an achievement
|
||||
if (!stats_api->SetAchievement(achievement_id)) {
|
||||
std::cerr << "Unlocking achievement " << achievement_id << " failed " << std::endl;
|
||||
//keep going so we don't corrupt the pipe
|
||||
} else {
|
||||
std::cerr << "Unlocked achievement " << achievement_id << std::endl;
|
||||
}
|
||||
}
|
||||
} else if (type == 's') {
|
||||
// We want to edit a stat
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-commit and auto-update after we receive everything
|
||||
|
||||
if (!stats_api->StoreStats()) {
|
||||
std::cerr << "Committing changes failed" << std::endl;
|
||||
}
|
||||
|
||||
// After the child changes achievements/stats, it will retrieve all achievements,
|
||||
// and send a SIGUSR1 signal to the parent when done, and start writing
|
||||
// to the pipe.
|
||||
std::cerr << "Child is updating parent" << std::endl;
|
||||
inst->retrieve_achievements();
|
||||
|
||||
}
|
||||
|
||||
|
||||
/*********************************
|
||||
* CLASS METHODS DEFINITION
|
||||
********************************/
|
||||
|
||||
GameEmulator::GameEmulator() :
|
||||
m_CallbackUserStatsReceived( this, &GameEmulator::OnUserStatsReceived ),
|
||||
m_achievement_list( nullptr ),
|
||||
m_son_pid( -1 ),
|
||||
m_have_stats_been_requested( false )
|
||||
{
|
||||
|
||||
}
|
||||
// => Constructor
|
||||
|
||||
GameEmulator*
|
||||
GameEmulator::get_instance() {
|
||||
static GameEmulator me;
|
||||
return &me;
|
||||
}
|
||||
// => get_instance
|
||||
|
||||
bool
|
||||
GameEmulator::init_app(const std::string& app_id) {
|
||||
|
||||
if(m_son_pid > 0) {
|
||||
std::cerr << "Warning: trying to initialize a Steam App while one is already running." << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create the pipe
|
||||
// We never close it because they are bidirectional (I know it's ugly)
|
||||
if( pipe(m_pipe) == -1 ) {
|
||||
std::cerr << "Could not create a pipe. Exitting." << std::endl;
|
||||
std::cerr << "errno: " << errno << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
pid_t pid;
|
||||
if((pid = fork()) == 0) {
|
||||
//Son's process
|
||||
setenv("SteamAppId", app_id.c_str(), 1);
|
||||
if( !SteamAPI_Init() ) {
|
||||
std::cerr << "An error occurred launching the Steam API. Aborting." << std::endl;
|
||||
std::cerr << "Make sure you are trying to run an app you own, and running with lauch.sh" << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
signal(SIGTERM, handle_sigterm);
|
||||
// Communicate game stats to parent, and
|
||||
// read from parents stats to modify
|
||||
signal(SIGUSR1, handle_sigusr1_child);
|
||||
|
||||
retrieve_achievements();
|
||||
for(;;) {
|
||||
SteamAPI_RunCallbacks();
|
||||
sleep(1);
|
||||
}
|
||||
}
|
||||
else if (pid == -1) {
|
||||
std::cerr << "An error occurred while forking. Exitting." << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
else {
|
||||
//Main process
|
||||
signal(SIGCHLD, handle_sigchld);
|
||||
signal(SIGUSR1, handle_sigusr1_parent);
|
||||
m_son_pid = pid;
|
||||
}
|
||||
|
||||
//Only a successful parent will reach this
|
||||
return true;
|
||||
}
|
||||
// => init_app
|
||||
|
||||
|
||||
bool
|
||||
GameEmulator::kill_running_app() {
|
||||
if(m_son_pid > 0) {
|
||||
kill(m_son_pid, SIGTERM);
|
||||
free(m_achievement_list);
|
||||
m_achievement_list = nullptr;
|
||||
|
||||
// We will set the pid back to -1 when son's death is confirmed
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
if (g_main_gui != NULL)
|
||||
std::cerr << "Warning: trying to kill the Steam Game while it's not running." << std::endl;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
// => kill_running app
|
||||
|
||||
|
||||
void
|
||||
GameEmulator::retrieve_achievements() {
|
||||
if (!m_have_stats_been_requested) {
|
||||
m_have_stats_been_requested = true;
|
||||
ISteamUserStats *stats_api = SteamUserStats();
|
||||
if (!stats_api->RequestCurrentStats()) {
|
||||
std::cerr << "ERROR: User not logged in, exiting" << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
} else {
|
||||
std::cerr << "WARNING: Stats have already been requested" << std::endl;
|
||||
}
|
||||
}
|
||||
// => retrieve_achievements
|
||||
|
||||
void
|
||||
GameEmulator::update_view() {
|
||||
for(unsigned i = 0; i < m_achievement_count; i++) {
|
||||
g_main_gui->add_to_achievement_list(m_achievement_list[i]);
|
||||
}
|
||||
|
||||
g_main_gui->confirm_stats_list();
|
||||
}
|
||||
// => update_view
|
||||
|
||||
/**
|
||||
* This method must only be called on the parent process.
|
||||
* It reset the GUI window in anticipation of the child
|
||||
* process sending it new information on the achievements
|
||||
* via handle_sigusr1_parent
|
||||
*
|
||||
*/
|
||||
void
|
||||
GameEmulator::update_data_and_view() {
|
||||
// Must be run by the parent
|
||||
if(m_son_pid > 0) {
|
||||
g_main_gui->reset_achievements_list();
|
||||
g_main_gui->confirm_stats_list();
|
||||
|
||||
// Child will autoupdate the parent after committing changes
|
||||
} else {
|
||||
std::cerr << "Could not update data & view, no child found." << std::endl;
|
||||
}
|
||||
}
|
||||
// => update_data_and_view
|
||||
|
||||
bool
|
||||
GameEmulator::send_num_changes(unsigned num_changes) const {
|
||||
// We assume the son process is already running
|
||||
|
||||
// Write the number of changes
|
||||
write(m_pipe[1], &num_changes, sizeof(unsigned));
|
||||
|
||||
// Send it a signal after buffering the number of changes
|
||||
kill(m_son_pid, SIGUSR1);
|
||||
|
||||
return false; // Yeah error handling? Maybe later (TODO)
|
||||
}
|
||||
// => unlock_achievement
|
||||
|
||||
/**
|
||||
* The parent process requests the son process to unlock an
|
||||
* achievement. So this code will be executed in the parent process,
|
||||
* so send a message to the son, associated with an achievement ID
|
||||
*/
|
||||
bool
|
||||
GameEmulator::unlock_achievement(const char* ach_api_name) const {
|
||||
// We assume the son process is already running
|
||||
static const unsigned unlock_state = 1;
|
||||
|
||||
// Write "a1" (for achievement unlock) then the achievement id
|
||||
write(m_pipe[1], "a", sizeof(char));
|
||||
write(m_pipe[1], &unlock_state, sizeof(unsigned));
|
||||
write(m_pipe[1], ach_api_name, MAX_ACHIEVEMENT_ID_LENGTH * sizeof(char));
|
||||
|
||||
return false; // Yeah error handling? Maybe later (TODO)
|
||||
}
|
||||
// => unlock_achievement
|
||||
|
||||
bool
|
||||
GameEmulator::relock_achievement(const char* ach_api_name) const {
|
||||
// We assume the son process is already running
|
||||
static const unsigned unlock_state = 0;
|
||||
|
||||
// Write "a1" (for achievement unlock) then the achievement id
|
||||
write(m_pipe[1], "a", sizeof(char));
|
||||
write(m_pipe[1], &unlock_state, sizeof(unsigned));
|
||||
write(m_pipe[1], ach_api_name, MAX_ACHIEVEMENT_ID_LENGTH * sizeof(char));
|
||||
|
||||
return false; // Yeah error handling? Maybe later (TODO)
|
||||
}
|
||||
// => relock_achievement
|
||||
|
||||
//TODO: send stats
|
||||
|
||||
/*****************************************
|
||||
* STEAM API CALLBACKS BELOW
|
||||
****************************************/
|
||||
|
||||
/**
|
||||
* Retrieves all achievements data, then pipes the data to the
|
||||
* parent process.
|
||||
*/
|
||||
void
|
||||
GameEmulator::OnUserStatsReceived(UserStatsReceived_t *callback) {
|
||||
// Check if we received the values for the good app
|
||||
if (std::string(getenv("SteamAppId")) == std::to_string(callback->m_nGameID)) {
|
||||
if ( k_EResultOK == callback->m_eResult ) {
|
||||
|
||||
ISteamUserStats *stats_api = SteamUserStats();
|
||||
|
||||
// ==============================
|
||||
// RETRIEVE IDS
|
||||
// ==============================
|
||||
const unsigned num_ach = stats_api->GetNumAchievements();
|
||||
|
||||
if (num_ach == 0) {
|
||||
std::cerr << "No achievements for current game" << std::endl;
|
||||
}
|
||||
|
||||
if (m_achievement_list != nullptr) {
|
||||
free(m_achievement_list);
|
||||
m_achievement_list = nullptr;
|
||||
}
|
||||
|
||||
m_achievement_list = (Achievement_t*)malloc(num_ach * sizeof(Achievement_t));
|
||||
|
||||
for (unsigned i = 0; i < num_ach ; i++) {
|
||||
// TODO: strncpy is slow, because it fills the remaining space with NULLs
|
||||
// This is last stage optimisation but, could have used strcpy, or sprintf,
|
||||
// making sure strings are NULL terminated
|
||||
// see "man strncpy" for a possible implementation
|
||||
strncpy(
|
||||
m_achievement_list[i].id,
|
||||
stats_api->GetAchievementName(i),
|
||||
MAX_ACHIEVEMENT_ID_LENGTH);
|
||||
|
||||
strncpy(
|
||||
m_achievement_list[i].name,
|
||||
stats_api->GetAchievementDisplayAttribute(m_achievement_list[i].id, "name"),
|
||||
MAX_ACHIEVEMENT_NAME_LENGTH);
|
||||
|
||||
strncpy(
|
||||
m_achievement_list[i].desc,
|
||||
stats_api->GetAchievementDisplayAttribute(m_achievement_list[i].id, "desc"),
|
||||
MAX_ACHIEVEMENT_DESC_LENGTH);
|
||||
|
||||
// TODO
|
||||
// https://partner.steamgames.com/doc/api/ISteamUserStats#RequestGlobalAchievementPercentages
|
||||
//stats_api->GetAchievementAchievedPercent(m_achievement_list[i].id, &(m_achievement_list[i].global_achieved_rate));
|
||||
m_achievement_list[i].global_achieved_rate = 0;
|
||||
stats_api->GetAchievement(m_achievement_list[i].id, &(m_achievement_list[i].achieved));
|
||||
m_achievement_list[i].hidden = (bool)strcmp(stats_api->GetAchievementDisplayAttribute( m_achievement_list[i].id, "hidden" ), "0");
|
||||
m_achievement_list[i].icon_handle = stats_api->GetAchievementIcon( m_achievement_list[i].id );
|
||||
}
|
||||
|
||||
//Tell parent that he must read
|
||||
kill(getppid(), SIGUSR1);
|
||||
|
||||
|
||||
//Start writing
|
||||
write(m_pipe[1], &num_ach, sizeof(unsigned));
|
||||
|
||||
// We could send all the memory bloc at once, but the pipe buffer
|
||||
// might not be big enough on some systems, so let's just loop
|
||||
for(unsigned i = 0; i < num_ach; i++) {
|
||||
write(m_pipe[1], &(m_achievement_list[i]), sizeof(Achievement_t));
|
||||
}
|
||||
|
||||
m_have_stats_been_requested = false;
|
||||
} else {
|
||||
std::cerr << "Received stats for the game, but an error occurrred." << std::endl;
|
||||
}
|
||||
} else {
|
||||
std::cerr << "Received stats for wrong game" << std::endl;
|
||||
}
|
||||
}
|
||||
// => OnUserStatsReceived
|
||||
|
|
@ -1,106 +0,0 @@
|
|||
#pragma once
|
||||
#include <string>
|
||||
#include "types/Achievement.h"
|
||||
#include "../steam/steam_api.h"
|
||||
|
||||
/**
|
||||
* This class will play the part of being the emulated app
|
||||
* It is responsible for retrieving all stats and achievements
|
||||
* for a give steam app id.
|
||||
*
|
||||
* Technically, it calls fork, and the child process will have
|
||||
* the role of a steam app, that will retrieve all the data
|
||||
* and pipe it to the parent process.
|
||||
* It uses SIGUSR1, so don't be surprised if you get callbacks
|
||||
* triggered if you use this signal somewhere else.
|
||||
*/
|
||||
|
||||
class GameEmulator {
|
||||
public:
|
||||
/**
|
||||
* Singleton method to get the unique instance
|
||||
*/
|
||||
static GameEmulator* get_instance();
|
||||
|
||||
/**
|
||||
* Starts the Steam app corresponding to the given app_id
|
||||
* Will automatically call update_view
|
||||
*/
|
||||
bool init_app(const std::string& app_id);
|
||||
|
||||
/**
|
||||
* Will stop the currently running Steam app, launched with
|
||||
* init_app
|
||||
*/
|
||||
bool kill_running_app();
|
||||
|
||||
/**
|
||||
* Will update the main view, adding all achievements to the
|
||||
* list of achievements
|
||||
*/
|
||||
void update_view();
|
||||
|
||||
/**
|
||||
* Will refetch data from the steam API.
|
||||
* Will update the main view, adding all achievements to the
|
||||
* list of achievements
|
||||
*/
|
||||
void update_data_and_view();
|
||||
|
||||
/**
|
||||
* Send the number of changes to store to the child.
|
||||
* The child will then wait for that many changes.
|
||||
*/
|
||||
bool send_num_changes(unsigned num_changes) const;
|
||||
|
||||
/**
|
||||
* Will unlock the achivement given it's API name.
|
||||
* Will return the value of SetAchievement.
|
||||
* https://partner.steamgames.com/doc/api/ISteamUserStats#SetAchievement
|
||||
*/
|
||||
bool unlock_achievement(const char* ach_api_name) const;
|
||||
|
||||
/**
|
||||
* Will relock the achivement given it's API name.
|
||||
* Will return the value of ClearAchievement.
|
||||
* https://partner.steamgames.com/doc/api/ISteamUserStats#ClearAchievement
|
||||
*/
|
||||
bool relock_achievement(const char* ach_api_name) const;
|
||||
|
||||
/**
|
||||
* Will actually commit the stats and achievements previously
|
||||
* changed locally to the server and cause achievement
|
||||
* notifications to pop up
|
||||
* https://partner.steamgames.com/doc/api/ISteamUserStats#StoreStats
|
||||
*/
|
||||
// Child inherently does this now
|
||||
//bool commit_changes();
|
||||
|
||||
/**
|
||||
* Steam API callback to handle the received stats and achievements
|
||||
*/
|
||||
STEAM_CALLBACK( GameEmulator, OnUserStatsReceived, UserStatsReceived_t, m_CallbackUserStatsReceived );
|
||||
|
||||
/**
|
||||
* Prevent using the default constructor because we use the
|
||||
* singleton pattern
|
||||
*/
|
||||
GameEmulator(GameEmulator const&) = delete;
|
||||
void operator=(GameEmulator const&) = delete;
|
||||
|
||||
private:
|
||||
void retrieve_achievements();
|
||||
|
||||
Achievement_t *m_achievement_list;
|
||||
pid_t m_son_pid;
|
||||
bool m_have_stats_been_requested;
|
||||
int m_pipe[2];
|
||||
unsigned m_achievement_count;
|
||||
|
||||
friend void handle_sigchld(int);
|
||||
friend void handle_sigusr1_parent(int);
|
||||
friend void handle_sigusr1_child(int);
|
||||
|
||||
GameEmulator();
|
||||
~GameEmulator() {};
|
||||
};
|
||||
|
|
@ -8,8 +8,8 @@
|
|||
#include "types/Game.h"
|
||||
#include "types/Actions.h"
|
||||
#include "SteamAppDAO.h"
|
||||
#include "GameEmulator.h"
|
||||
#include "common/functions.h"
|
||||
#include "common/yajlHelpers.h"
|
||||
|
||||
#define MAX_PATH 1000
|
||||
|
||||
|
|
@ -44,11 +44,8 @@ MySteam::get_instance() {
|
|||
*/
|
||||
bool
|
||||
MySteam::launch_game(AppId_t appID) {
|
||||
// Print an error if a game is already launched, maybe allow multiple games at the same time in the future?
|
||||
// GameEmulator* emulator = GameEmulator::get_instance();
|
||||
|
||||
// //TODO if
|
||||
// emulator->init_app(appID);
|
||||
// Print an error if a game is already launched
|
||||
// allow multiple games at the same time in the future via new window launching
|
||||
|
||||
if (m_ipc_socket != nullptr) {
|
||||
std::cerr << "I will not launch the game as one is already running" << std::endl;
|
||||
|
|
@ -72,9 +69,6 @@ MySteam::launch_game(AppId_t appID) {
|
|||
*/
|
||||
bool
|
||||
MySteam::quit_game() {
|
||||
// GameEmulator* emulator = GameEmulator::get_instance();
|
||||
// return emulator->kill_running_app();
|
||||
|
||||
if (m_ipc_socket != nullptr) {
|
||||
m_ipc_socket->kill_server();
|
||||
delete m_ipc_socket;
|
||||
|
|
@ -250,9 +244,9 @@ MySteam::refresh_icons() {
|
|||
// => refresh_icons
|
||||
|
||||
|
||||
std::vector<std::pair<std::string, bool>>
|
||||
std::vector<Achievement_t>
|
||||
MySteam::get_achievements() {
|
||||
std::vector<std::pair<std::string, bool>> achs;
|
||||
std::vector<Achievement_t> achievements;
|
||||
std::string response;
|
||||
const unsigned char * buf;
|
||||
size_t len;
|
||||
|
|
@ -262,88 +256,23 @@ MySteam::get_achievements() {
|
|||
exit(0);
|
||||
}
|
||||
|
||||
achs.clear();
|
||||
achievements.clear();
|
||||
|
||||
// Maybe these MySteam functions should be moved to a MyGameClient.cpp?
|
||||
|
||||
//TODO encapsulate these into a json generator
|
||||
// TODO: could push this handle generation handling down into a YAJL
|
||||
// interfacing object
|
||||
yajl_gen handle = yajl_gen_alloc(NULL);
|
||||
yajl_gen_map_open(handle);
|
||||
|
||||
if (yajl_gen_string(handle, (const unsigned char *)SAM_ACTION_STR, strlen(SAM_ACTION_STR)) != yajl_gen_status_ok) {
|
||||
std::cerr << "failed to make json" << std::endl;
|
||||
return achs;
|
||||
}
|
||||
if (yajl_gen_string(handle, (const unsigned char *)GET_ACHIEVEMENTS_STR, strlen(GET_ACHIEVEMENTS_STR)) != yajl_gen_status_ok) {
|
||||
std::cerr << "failed to make json" << std::endl;
|
||||
return achs;
|
||||
}
|
||||
if (yajl_gen_map_close(handle) != yajl_gen_status_ok) {
|
||||
std::cerr << "failed to make json" << std::endl;
|
||||
return achs;
|
||||
}
|
||||
encode_request(handle, GET_ACHIEVEMENTS_STR);
|
||||
|
||||
yajl_gen_get_buf(handle, &buf, &len);
|
||||
response = m_ipc_socket->request_response(std::string((const char*)buf));
|
||||
yajl_gen_free(handle);
|
||||
|
||||
//parse response
|
||||
yajl_val node = yajl_tree_parse(response.c_str(), NULL, 0);
|
||||
achievements = decode_achievements(response);
|
||||
|
||||
if (node == NULL) {
|
||||
std::cerr << "parsing error";
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
const char * path1[] = { SAM_ACK_STR, (const char*)0 };
|
||||
yajl_val v = yajl_tree_get(node, path1, yajl_t_string);
|
||||
if (v == NULL || !YAJL_IS_STRING(v)) {
|
||||
std::cerr << "failed to parse " << SAM_ACK_STR << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (std::string(YAJL_GET_STRING(v)) != std::string(SAM_ACK_STR)) {
|
||||
std::cerr << "failed to receive ack" << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
const char * path2[] = { ACHIEVEMENT_LIST_STR, (const char*)0 };
|
||||
|
||||
v = yajl_tree_get(node, path2, yajl_t_array);
|
||||
if (v == NULL) {
|
||||
std::cerr << "parsing error" << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
yajl_val *w = YAJL_GET_ARRAY(v)->values;
|
||||
size_t array_len = YAJL_GET_ARRAY(v)->len;
|
||||
|
||||
for(unsigned i = 0; i < array_len; i++) {
|
||||
const char * path3[] = { ACHIEVEMENT_NAME_STR, (const char*)0 };
|
||||
const char * path4[] = { ACHIEVED_STR, (const char*)0 };
|
||||
|
||||
yajl_val cur_node = w[i];
|
||||
|
||||
yajl_val x = yajl_tree_get(cur_node, path3, yajl_t_string);
|
||||
if (x == NULL) {
|
||||
std::cerr << "parsing error" << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// why is bool parsing weird
|
||||
yajl_val y = yajl_tree_get(cur_node, path4, yajl_t_any);
|
||||
if (y == NULL) {
|
||||
std::cerr << "parsing error" << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
if (!YAJL_IS_TRUE(y) && !YAJL_IS_FALSE(y)) {
|
||||
std::cerr << "bool parsing error" << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
achs.push_back(std::pair<std::string, bool>(YAJL_GET_STRING(x), YAJL_IS_TRUE(y)));
|
||||
}
|
||||
|
||||
return achs;
|
||||
return achievements;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ public:
|
|||
*
|
||||
* TODO: maybe don't name this the same as GameServer::get_achievements?
|
||||
*/
|
||||
std::vector<std::pair<std::string, bool>> get_achievements();
|
||||
std::vector<Achievement_t> get_achievements();
|
||||
|
||||
/**
|
||||
* Adds a modification to be done on the launched app.
|
||||
|
|
|
|||
226
src/common/yajlHelpers.cpp
Normal file
226
src/common/yajlHelpers.cpp
Normal file
|
|
@ -0,0 +1,226 @@
|
|||
|
||||
#include "yajlHelpers.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
#include <cstring>
|
||||
#include <yajl/yajl_gen.h>
|
||||
#include <yajl/yajl_tree.h>
|
||||
#include "../types/Achievement.h"
|
||||
#include "../types/Actions.h"
|
||||
|
||||
void yajl_gen_string_wrap(yajl_gen handle, const char * a) {
|
||||
if (yajl_gen_string(handle, (const unsigned char *)a, strlen(a)) != yajl_gen_status_ok) {
|
||||
std::cerr << "failed to make json" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void encode_request(yajl_gen handle, const char * request) {
|
||||
if (yajl_gen_map_open(handle) != yajl_gen_status_ok) {
|
||||
std::cerr << "failed to make json" << std::endl;
|
||||
}
|
||||
|
||||
yajl_gen_string_wrap(handle, SAM_ACTION_STR);
|
||||
yajl_gen_string_wrap(handle, request);
|
||||
|
||||
if (yajl_gen_map_close(handle) != yajl_gen_status_ok) {
|
||||
std::cerr << "failed to make json" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
std::string decode_request(std::string request) {
|
||||
|
||||
yajl_val node = yajl_tree_parse(request.c_str(), NULL, 0);
|
||||
|
||||
if (node == NULL) {
|
||||
std::cerr << "Parsing error";
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
const char * path[] = { SAM_ACTION_STR, (const char*)0 };
|
||||
yajl_val v = yajl_tree_get(node, path, yajl_t_string);
|
||||
if (v == NULL || !YAJL_IS_STRING(v)) {
|
||||
std::cerr << "failed to get" << SAM_ACTION_STR << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
return std::string (YAJL_GET_STRING(v));
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode an individual achievement into a given YAJL handle
|
||||
*/
|
||||
void encode_achievement(yajl_gen handle, Achievement_t achievement) {
|
||||
|
||||
yajl_gen_string_wrap(handle, NAME_STR);
|
||||
yajl_gen_string_wrap(handle, achievement.name.c_str());
|
||||
|
||||
yajl_gen_string_wrap(handle, DESC_STR);
|
||||
yajl_gen_string_wrap(handle, achievement.desc.c_str());
|
||||
|
||||
yajl_gen_string_wrap(handle, ID_STR);
|
||||
yajl_gen_string_wrap(handle, achievement.id.c_str());
|
||||
|
||||
yajl_gen_string_wrap(handle, RATE_STR);
|
||||
if (yajl_gen_double(handle, (double)achievement.global_achieved_rate) != yajl_gen_status_ok) {
|
||||
std::cerr << "failed to make json" << std::endl;
|
||||
}
|
||||
|
||||
yajl_gen_string_wrap(handle, ICON_STR);
|
||||
if (yajl_gen_integer(handle, achievement.icon_handle) != yajl_gen_status_ok) {
|
||||
std::cerr << "failed to make json" << std::endl;
|
||||
}
|
||||
|
||||
yajl_gen_string_wrap(handle, ACHIEVED_STR);
|
||||
if (yajl_gen_bool(handle, achievement.achieved) != yajl_gen_status_ok) {
|
||||
std::cerr << "failed to make json" << std::endl;
|
||||
}
|
||||
|
||||
yajl_gen_string_wrap(handle, HIDDEN_STR);
|
||||
if (yajl_gen_bool(handle, achievement.hidden) != 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) {
|
||||
|
||||
yajl_gen_string_wrap(handle, ACHIEVEMENT_LIST_STR);
|
||||
|
||||
if (yajl_gen_array_open(handle) != yajl_gen_status_ok) {
|
||||
std::cerr << "failed to make json" << std::endl;
|
||||
}
|
||||
|
||||
for (Achievement_t achievement : achievements) {
|
||||
std::cout << "encoding achievement.id " << achievement.id << std::endl;
|
||||
|
||||
if (yajl_gen_map_open(handle) != yajl_gen_status_ok) {
|
||||
std::cerr << "failed to make json" << std::endl;
|
||||
}
|
||||
|
||||
encode_achievement(handle, achievement);
|
||||
|
||||
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<Achievement_t> decode_achievements(std::string response) {
|
||||
std::vector<Achievement_t> achievements;
|
||||
|
||||
//parse response
|
||||
yajl_val node = yajl_tree_parse(response.c_str(), NULL, 0);
|
||||
|
||||
if (node == NULL) {
|
||||
std::cerr << "parsing error";
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// TODO: separate out into decode_ack()
|
||||
const char * ack_path[] = { SAM_ACK_STR, (const char*)0 };
|
||||
yajl_val v = yajl_tree_get(node, ack_path, yajl_t_string);
|
||||
if (v == NULL || !YAJL_IS_STRING(v)) {
|
||||
std::cerr << "failed to parse " << SAM_ACK_STR << std::endl;
|
||||
}
|
||||
|
||||
if (std::string(YAJL_GET_STRING(v)) != std::string(SAM_ACK_STR)) {
|
||||
std::cerr << "failed to receive ack" << std::endl;
|
||||
}
|
||||
|
||||
// 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 * 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 };
|
||||
const char * icon_path[] = { ICON_STR, (const char*)0 };
|
||||
const char * achieved_path[] = { ACHIEVED_STR, (const char*)0 };
|
||||
const char * hidden_path[] = { HIDDEN_STR, (const char*)0 };
|
||||
|
||||
v = yajl_tree_get(node, list_path, yajl_t_array);
|
||||
if (v == NULL) {
|
||||
std::cerr << "parsing error" << std::endl;
|
||||
}
|
||||
|
||||
yajl_val *w = YAJL_GET_ARRAY(v)->values;
|
||||
size_t array_len = YAJL_GET_ARRAY(v)->len;
|
||||
|
||||
achievements.clear();
|
||||
achievements.resize(array_len);
|
||||
|
||||
for(unsigned i = 0; i < array_len; i++) {
|
||||
yajl_val cur_node = w[i];
|
||||
yajl_val cur_val;
|
||||
|
||||
// verification is done via the type argument to yajl_tree_get
|
||||
// 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;
|
||||
}
|
||||
achievements[i].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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
if (!YAJL_IS_DOUBLE(cur_val)) {
|
||||
std::cerr << "double float parsing error" << std::endl;
|
||||
}
|
||||
achievements[i].global_achieved_rate = YAJL_GET_DOUBLE(cur_val);
|
||||
|
||||
cur_val = yajl_tree_get(cur_node, icon_path, yajl_t_number);
|
||||
if (cur_val == NULL) {
|
||||
std::cerr << "parsing error" << std::endl;
|
||||
}
|
||||
if (!YAJL_IS_INTEGER(cur_val)) {
|
||||
std::cerr << "integer parsing error" << std::endl;
|
||||
}
|
||||
achievements[i].icon_handle = YAJL_GET_INTEGER(cur_val);
|
||||
|
||||
// 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;
|
||||
}
|
||||
if (!YAJL_IS_TRUE(cur_val) && !YAJL_IS_FALSE(cur_val)) {
|
||||
std::cerr << "bool parsing error" << std::endl;
|
||||
}
|
||||
achievements[i].achieved = YAJL_IS_TRUE(cur_val);
|
||||
|
||||
cur_val = yajl_tree_get(cur_node, hidden_path, yajl_t_any);
|
||||
if (cur_val == NULL) {
|
||||
std::cerr << "parsing error" << std::endl;
|
||||
}
|
||||
if (!YAJL_IS_TRUE(cur_val) && !YAJL_IS_FALSE(cur_val)) {
|
||||
std::cerr << "bool parsing error" << std::endl;
|
||||
}
|
||||
achievements[i].achieved = YAJL_IS_TRUE(cur_val);
|
||||
}
|
||||
|
||||
yajl_tree_free(node);
|
||||
return achievements;
|
||||
}
|
||||
36
src/common/yajlHelpers.h
Normal file
36
src/common/yajlHelpers.h
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <yajl/yajl_gen.h>
|
||||
#include <yajl/yajl_tree.h>
|
||||
#include "../types/Achievement.h"
|
||||
#include "../types/Actions.h"
|
||||
|
||||
/**
|
||||
* Encode a string into a YAJL handle and
|
||||
* handle error and coercion to C types for a string
|
||||
*/
|
||||
void yajl_gen_string_wrap(yajl_gen handle, const char * a);
|
||||
|
||||
/**
|
||||
* Encode a request
|
||||
*/
|
||||
void encode_request(yajl_gen handle, const char * request);
|
||||
|
||||
/**
|
||||
* Decode a request
|
||||
*/
|
||||
std::string decode_request(std::string request);
|
||||
|
||||
/**
|
||||
* Encode an achievement vector into a given YAJL handle
|
||||
* 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);
|
||||
|
||||
/**
|
||||
* Decode the achievement vector from a json response
|
||||
*/
|
||||
std::vector<Achievement_t> decode_achievements(std::string response);
|
||||
|
|
@ -1,6 +1,9 @@
|
|||
#include "GtkAchievementBoxRow.h"
|
||||
#include "../MySteam.h"
|
||||
#include "../globals.h"
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
|
||||
|
||||
extern "C"
|
||||
{
|
||||
|
|
@ -29,6 +32,9 @@ extern "C"
|
|||
}
|
||||
}
|
||||
|
||||
//keep this around until this function is refactored
|
||||
#define MAX_ACHIEVEMENT_NAME_LENGTH 256
|
||||
|
||||
GtkAchievementBoxRow::GtkAchievementBoxRow(const Achievement_t& data)
|
||||
:
|
||||
m_data(data)
|
||||
|
|
@ -46,7 +52,12 @@ m_data(data)
|
|||
sprintf(ach_locked_text, "%s", "Locked");
|
||||
pressed = FALSE;
|
||||
}
|
||||
sprintf(ach_title_text, "<b>%s</b>", data.name);
|
||||
// remove when this function is refactored
|
||||
if (data.name.length() > MAX_ACHIEVEMENT_NAME_LENGTH) {
|
||||
std::cerr << "Overflow going to occur, aborting" << std::endl;
|
||||
return;
|
||||
}
|
||||
sprintf(ach_title_text, "<b>%s</b>", data.name.c_str());
|
||||
sprintf(ach_player_percent_text, "Achieved by %.1f%% of the players", data.global_achieved_rate);
|
||||
|
||||
|
||||
|
|
@ -57,7 +68,7 @@ m_data(data)
|
|||
GtkWidget *ach_pic = gtk_image_new_from_icon_name("gtk-missing-image", GTK_ICON_SIZE_DIALOG);
|
||||
GtkWidget *title_desc_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
|
||||
GtkWidget *title_label = gtk_label_new("");
|
||||
GtkWidget *desc_label = gtk_label_new(data.desc);
|
||||
GtkWidget *desc_label = gtk_label_new(data.desc.c_str());
|
||||
GtkWidget *more_info_button = gtk_menu_button_new();
|
||||
GtkWidget *more_info_image = gtk_image_new_from_icon_name("gtk-about", GTK_ICON_SIZE_BUTTON);
|
||||
GtkWidget *lock_unlock_button = gtk_toggle_button_new_with_label(ach_locked_text);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
#include "gtk_callbacks.h"
|
||||
#include <iostream>
|
||||
#include "MainPickerWindow.h"
|
||||
#include "../GameEmulator.h"
|
||||
#include "../MySteam.h"
|
||||
#include "../globals.h"
|
||||
|
||||
|
|
@ -27,30 +26,16 @@ extern "C"
|
|||
std::cerr << "Saving stats and achievements." << std::endl;
|
||||
const std::map<std::string, bool> pending_achs = g_steam->get_pending_ach_modifications();
|
||||
const std::map<std::string, double> pending_stats = g_steam->get_pending_stat_modifications();
|
||||
GameEmulator* emulator = GameEmulator::get_instance();
|
||||
|
||||
// Send the number of changes then send that many changes
|
||||
const unsigned num_to_change = pending_achs.size(); //+ pending_stats.size();
|
||||
emulator->send_num_changes(num_to_change);
|
||||
//TODO:
|
||||
//
|
||||
//commit changes
|
||||
//MySteam::commit_changes() //reset pending changes too?
|
||||
//
|
||||
// pull out the same game achievement population code from on_game_row_activated
|
||||
// g_main_gui->reset_achievements_list();
|
||||
// g_main_gui->confirm_stats_list();
|
||||
|
||||
/**
|
||||
* TODO: Check for failures. But unlocking is done async because
|
||||
* the son process has to deal with it.
|
||||
*/
|
||||
for (auto const& [key, val] : pending_achs) {
|
||||
if(val) {
|
||||
std::cout << "Unlocking " << key << std::endl;
|
||||
emulator->unlock_achievement( key.c_str() );
|
||||
g_steam->remove_modification_ach(key);
|
||||
} else {
|
||||
std::cout << "Relocking " << key << std::endl;
|
||||
emulator->relock_achievement( key.c_str() );
|
||||
g_steam->remove_modification_ach(key);
|
||||
}
|
||||
}
|
||||
|
||||
// Child will inherently udpate the parent after committing changes
|
||||
emulator->update_data_and_view(); // This is async
|
||||
}
|
||||
// => on_store_button_clicked
|
||||
|
||||
|
|
@ -96,23 +81,14 @@ extern "C"
|
|||
if( appId != 0 ) {
|
||||
g_main_gui->switch_to_stats_page();
|
||||
g_steam->launch_game(appId);
|
||||
// get_achievements from game server
|
||||
std::vector<std::pair<std::string, bool>> ach_list = g_steam->get_achievements();
|
||||
// Get_achievements from game server
|
||||
std::vector<Achievement_t> achievements = g_steam->get_achievements();
|
||||
|
||||
g_main_gui->reset_achievements_list();
|
||||
|
||||
//TODO: just pass in the array directly?
|
||||
for(unsigned i = 0; i < ach_list.size(); i++) {
|
||||
|
||||
// For now, for compatibility reasons, just convert to an Achievement_t
|
||||
// These two types will need to be unified
|
||||
Achievement_t ach = { 0 };
|
||||
strncpy(ach.id, ach_list[i].first.c_str(), MAX_ACHIEVEMENT_ID_LENGTH);
|
||||
// incorrect
|
||||
strncpy(ach.name, ach_list[i].first.c_str(), MAX_ACHIEVEMENT_NAME_LENGTH);
|
||||
ach.achieved = ach_list[i].second;
|
||||
|
||||
g_main_gui->add_to_achievement_list(ach);
|
||||
for(Achievement_t achievement : achievements) {
|
||||
g_main_gui->add_to_achievement_list(achievement);
|
||||
}
|
||||
|
||||
g_main_gui->confirm_stats_list();
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
#include <yajl/yajl_tree.h>
|
||||
#include "MyGameSocket.h"
|
||||
#include "../types/Actions.h"
|
||||
#include "../common/yajlHelpers.h"
|
||||
|
||||
MyGameSocket::MyGameSocket(AppId_t appid) :
|
||||
MyServerSocket(appid),
|
||||
|
|
@ -13,23 +14,12 @@ m_CallbackUserStatsReceived( this, &MyGameSocket::OnUserStatsReceived )
|
|||
std::string
|
||||
MyGameSocket::process_request(std::string request) {
|
||||
|
||||
//
|
||||
//TODO encapsulate these into a json parser?
|
||||
yajl_val node = yajl_tree_parse(request.c_str(), NULL, 0);
|
||||
//encoding this response is still tightly coupled to the
|
||||
// logic in this function, so it's hard to push it to a helper
|
||||
|
||||
if (node == NULL) {
|
||||
std::cerr << "Parsing error";
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
std::string action = decode_request(request);
|
||||
|
||||
const char * path[] = { SAM_ACTION_STR, (const char*)0 };
|
||||
yajl_val v = yajl_tree_get(node, path, yajl_t_string);
|
||||
if (v == NULL || !YAJL_IS_STRING(v)) {
|
||||
std::cerr << "failed to get" << SAM_ACTION_STR << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
std::string action(YAJL_GET_STRING(v));
|
||||
std::string ret;
|
||||
const unsigned char * buf;
|
||||
size_t len;
|
||||
|
|
@ -39,53 +29,19 @@ MyGameSocket::process_request(std::string request) {
|
|||
yajl_gen handle = yajl_gen_alloc(NULL);
|
||||
yajl_gen_map_open(handle);
|
||||
|
||||
if (yajl_gen_string(handle, (const unsigned char *)SAM_ACK_STR, strlen(SAM_ACK_STR)) != yajl_gen_status_ok) {
|
||||
std::cerr << "failed to make json" << std::endl;
|
||||
}
|
||||
if (yajl_gen_string(handle, (const unsigned char *)SAM_ACK_STR, strlen(SAM_ACK_STR)) != yajl_gen_status_ok) {
|
||||
std::cerr << "failed to make json" << std::endl;
|
||||
}
|
||||
// generate ACK with same variable name and content
|
||||
yajl_gen_string_wrap(handle, SAM_ACK_STR);
|
||||
yajl_gen_string_wrap(handle, SAM_ACK_STR);
|
||||
|
||||
// TODO: change to enums? since it's JSON, using strings is necessary sometime
|
||||
// switch (request_type) {
|
||||
if (action == GET_ACHIEVEMENTS_STR) {
|
||||
//case GET_ACHIEVEMENTS:
|
||||
std::vector<Achievement_t> achievements = get_achievements();
|
||||
// Steam api is launched in this context, other possible imlementation: game_utils->get_achievements()
|
||||
|
||||
if (yajl_gen_string(handle, (const unsigned char *)ACHIEVEMENT_LIST_STR, strlen(ACHIEVEMENT_LIST_STR)) != yajl_gen_status_ok) {
|
||||
std::cerr << "failed to make json" << std::endl;
|
||||
}
|
||||
|
||||
if (yajl_gen_array_open(handle) != yajl_gen_status_ok) {
|
||||
std::cerr << "failed to make json" << std::endl;
|
||||
}
|
||||
|
||||
// append the achievements to the ack
|
||||
for (Achievement_t achievement : achievements) {
|
||||
std::cout << "achievement.id " << achievement.id << std::endl;
|
||||
|
||||
yajl_gen_map_open(handle);
|
||||
|
||||
if (yajl_gen_string(handle, (const unsigned char *)ACHIEVEMENT_NAME_STR, strlen(ACHIEVEMENT_NAME_STR)) != yajl_gen_status_ok) {
|
||||
std::cerr << "failed to make json" << std::endl;
|
||||
}
|
||||
if (yajl_gen_string(handle, (const unsigned char *)achievement.id, strlen(achievement.id)) != yajl_gen_status_ok) {
|
||||
std::cerr << "failed to make json" << std::endl;
|
||||
}
|
||||
|
||||
if (yajl_gen_string(handle, (const unsigned char *)ACHIEVED_STR, strlen(ACHIEVED_STR)) != yajl_gen_status_ok) {
|
||||
std::cerr << "failed to make json" << std::endl;
|
||||
}
|
||||
if (yajl_gen_bool(handle, achievement.achieved) != yajl_gen_status_ok) {
|
||||
std::cerr << "failed to make json" << std::endl;
|
||||
}
|
||||
|
||||
yajl_gen_map_close(handle);
|
||||
}
|
||||
|
||||
if (yajl_gen_array_close(handle) != yajl_gen_status_ok) {
|
||||
std::cerr << "failed to make json" << std::endl;
|
||||
}
|
||||
// Steam api is launched in this context, other possible implementation: game_utils->get_achievements()
|
||||
|
||||
// Append the achievements to the ack
|
||||
encode_achievements(handle, achievements);
|
||||
|
||||
//break;
|
||||
} else if (action == STORE_ACHIEVEMENTS_STR) {
|
||||
|
|
@ -114,8 +70,6 @@ MyGameSocket::process_request(std::string request) {
|
|||
ret = std::string((const char*)buf);
|
||||
yajl_gen_free(handle);
|
||||
|
||||
yajl_tree_free(node);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
@ -160,32 +114,23 @@ MyGameSocket::OnUserStatsReceived(UserStatsReceived_t *callback) {
|
|||
m_achievement_list.resize(num_ach);
|
||||
|
||||
for (unsigned i = 0; i < num_ach ; i++) {
|
||||
// TODO: strncpy is slow, because it fills the remaining space with NULLs
|
||||
// This is last stage optimisation but, could have used strcpy, or sprintf,
|
||||
// making sure strings are NULL terminated
|
||||
// see "man strncpy" for a possible implementation
|
||||
strncpy(
|
||||
m_achievement_list[i].id,
|
||||
stats_api->GetAchievementName(i),
|
||||
MAX_ACHIEVEMENT_ID_LENGTH);
|
||||
|
||||
strncpy(
|
||||
m_achievement_list[i].name,
|
||||
stats_api->GetAchievementDisplayAttribute(m_achievement_list[i].id, "name"),
|
||||
MAX_ACHIEVEMENT_NAME_LENGTH);
|
||||
m_achievement_list[i].id = stats_api->GetAchievementName(i);
|
||||
|
||||
strncpy(
|
||||
m_achievement_list[i].desc,
|
||||
stats_api->GetAchievementDisplayAttribute(m_achievement_list[i].id, "desc"),
|
||||
MAX_ACHIEVEMENT_DESC_LENGTH);
|
||||
const char * pchName = m_achievement_list[i].id.c_str();
|
||||
|
||||
m_achievement_list[i].name = stats_api->GetAchievementDisplayAttribute(pchName, "name");
|
||||
m_achievement_list[i].desc = stats_api->GetAchievementDisplayAttribute(pchName, "desc");
|
||||
|
||||
// TODO
|
||||
// https://partner.steamgames.com/doc/api/ISteamUserStats#RequestGlobalAchievementPercentages
|
||||
//stats_api->GetAchievementAchievedPercent(m_achievement_list[i].id, &(m_achievement_list[i].global_achieved_rate));
|
||||
m_achievement_list[i].global_achieved_rate = 0;
|
||||
stats_api->GetAchievement(m_achievement_list[i].id, &(m_achievement_list[i].achieved));
|
||||
m_achievement_list[i].hidden = (bool)strcmp(stats_api->GetAchievementDisplayAttribute( m_achievement_list[i].id, "hidden" ), "0");
|
||||
m_achievement_list[i].icon_handle = stats_api->GetAchievementIcon( m_achievement_list[i].id );
|
||||
stats_api->GetAchievement(pchName, &(m_achievement_list[i].achieved));
|
||||
m_achievement_list[i].hidden = (bool)strcmp(stats_api->GetAchievementDisplayAttribute(pchName, "hidden" ), "0");
|
||||
// TODO: incorrect as is
|
||||
//m_achievement_list[i].icon_handle = stats_api->GetAchievementIcon(pchName);
|
||||
m_achievement_list[i].icon_handle = 0;
|
||||
}
|
||||
|
||||
m_stats_callback_received = true;
|
||||
|
|
|
|||
|
|
@ -1,21 +1,15 @@
|
|||
#pragma once
|
||||
|
||||
#define MAX_ACHIEVEMENT_ID_LENGTH 256
|
||||
#define MAX_ACHIEVEMENT_DESC_LENGTH 256
|
||||
#define MAX_ACHIEVEMENT_NAME_LENGTH 100
|
||||
#include <string>
|
||||
|
||||
/**
|
||||
* Achievement structure.
|
||||
* It uses basic C types and has a fixed length, because they need
|
||||
* to be piped between different process quickly.
|
||||
* The set limits are arbitrary, we will assume that if the name or
|
||||
* desription is longer than the given size, the display won't look great,
|
||||
* so we trim the end.
|
||||
* Upgraded to use C++ types
|
||||
*/
|
||||
struct Achievement_t {
|
||||
char name[MAX_ACHIEVEMENT_NAME_LENGTH];
|
||||
char desc[MAX_ACHIEVEMENT_DESC_LENGTH];
|
||||
char id[MAX_ACHIEVEMENT_ID_LENGTH]; // I have no idea what the length limit of this is. Crossing fingers there's none above 256.
|
||||
std::string name;
|
||||
std::string desc;
|
||||
std::string id;
|
||||
float global_achieved_rate;
|
||||
int icon_handle; //0 : incorrect, error occurred, RTFM
|
||||
bool achieved;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
#pragma once
|
||||
|
||||
// Message format shall be
|
||||
// all GET format
|
||||
|
|
@ -5,10 +6,17 @@
|
|||
#define STORE_ACHIEVEMENTS_STR "STORE_ACHIEVEMENTS"
|
||||
#define QUIT_GAME_STR "QUIT_GAME"
|
||||
|
||||
// Mirroring structure of Achievement_t, should be combined with that
|
||||
#define SAM_ACTION_STR "SAM_ACTION"
|
||||
#define ACHIEVEMENT_LIST_STR "ACHIEVEMENT_LIST"
|
||||
#define ACHIEVEMENT_NAME_STR "ACHIEVEMENT_NAME"
|
||||
#define NAME_STR "NAME"
|
||||
#define DESC_STR "DESC"
|
||||
#define ID_STR "ID"
|
||||
#define RATE_STR "RATE"
|
||||
#define ICON_STR "ICON"
|
||||
#define ACHIEVED_STR "ACHIEVED"
|
||||
#define HIDDEN_STR "HIDDEN"
|
||||
|
||||
#define SAM_ACK_STR "SAM_ACK"
|
||||
|
||||
// TODO: Add SAM_START as an action to this too?
|
||||
|
|
@ -20,38 +28,68 @@ enum SAM_ACTION {
|
|||
INVALID
|
||||
};
|
||||
|
||||
// support for the full achievement type shall be added
|
||||
// for now just implement achieved/not achieved
|
||||
|
||||
/* JSON format shall be
|
||||
messages are sent as plaintext strings
|
||||
messages are delimited by the usual string NULL terminator
|
||||
|
||||
get all achievements for active game
|
||||
{
|
||||
GET_ACHIEVEMENTS_STR
|
||||
SAM_ACTION_STR: GET_ACHIEVEMENTS_STR
|
||||
}
|
||||
response
|
||||
{
|
||||
int NUM_ACH //maybe not necessary if we have .length function?
|
||||
[{
|
||||
str ACH_NAME
|
||||
bool ACHIEVED
|
||||
}]
|
||||
SAM_ACK: SAM_ACK,
|
||||
ACHIEVEMENT_LIST_STR:
|
||||
[
|
||||
{
|
||||
NAME_STR: "name"
|
||||
DESC_STR: "desc"
|
||||
ID_STR: "ID"
|
||||
RATE_STR: global_achieved_rate
|
||||
ICON_STR: icon
|
||||
ACHIEVED_STR: true/fase
|
||||
HIDDEN_STR: true/false
|
||||
},
|
||||
.
|
||||
.
|
||||
.
|
||||
]
|
||||
}
|
||||
|
||||
store a list of achievement changes
|
||||
can be reduced to not encode the whole achievement,
|
||||
only id and achieved status / stats changes
|
||||
{
|
||||
int NUM_ACH //maybe not necessary if we have .length function?
|
||||
[{
|
||||
str ACH_NAME
|
||||
bool ACHIEVED
|
||||
}]
|
||||
SAM_ACTION_STR: STORE_ACHIEVEMENTS_STR
|
||||
ACHIEVEMENT_LIST_STR:
|
||||
[
|
||||
{
|
||||
NAME_STR: ""
|
||||
DESC_STR: ""
|
||||
ID_STR: "ID"
|
||||
RATE_STR: 0
|
||||
ICON_STR: 0
|
||||
ACHIEVED_STR: true/fase
|
||||
HIDDEN_STR: false
|
||||
},
|
||||
.
|
||||
.
|
||||
.
|
||||
]
|
||||
}
|
||||
|
||||
response
|
||||
{
|
||||
SAM_ACK: SAM_ACK,
|
||||
}
|
||||
|
||||
quit active game
|
||||
{
|
||||
SAM_QUIT_STR
|
||||
SAM_ACTION_STR: SAM_QUIT_STR
|
||||
}
|
||||
response
|
||||
{
|
||||
SAM_ACK: SAM_ACK,
|
||||
}
|
||||
|
||||
*/
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user