Compare commits

...

2 Commits

Author SHA1 Message Date
Mr_Goldberg 5234d8b0e6
Implement leaderboard scores. 2022-08-07 04:37:57 -04:00
Mr_Goldberg e59508a696
generate_emu_config.py fixes and improvements. 2022-08-07 04:36:50 -04:00
4 changed files with 204 additions and 34 deletions

View File

@ -47,6 +47,7 @@ public:
static constexpr auto settings_storage_folder = "settings"; static constexpr auto settings_storage_folder = "settings";
static constexpr auto remote_storage_folder = "remote"; static constexpr auto remote_storage_folder = "remote";
static constexpr auto stats_storage_folder = "stats"; static constexpr auto stats_storage_folder = "stats";
static constexpr auto leaderboard_storage_folder = "leaderboard";
static constexpr auto user_data_storage = "local"; static constexpr auto user_data_storage = "local";
static constexpr auto screenshots_folder = "screenshots"; static constexpr auto screenshots_folder = "screenshots";
static constexpr auto game_settings_folder = "steam_settings"; static constexpr auto game_settings_folder = "steam_settings";

View File

@ -21,10 +21,17 @@
#include "base.h" #include "base.h"
#include "../overlay_experimental/steam_overlay.h" #include "../overlay_experimental/steam_overlay.h"
struct Steam_Leaderboard_Score {
CSteamID steam_id;
int32 score = 0;
std::vector<int32> score_details;
};
struct Steam_Leaderboard { struct Steam_Leaderboard {
std::string name; std::string name;
ELeaderboardSortMethod sort_method; ELeaderboardSortMethod sort_method;
ELeaderboardDisplayType display_type; ELeaderboardDisplayType display_type;
Steam_Leaderboard_Score self_score;
}; };
struct achievement_trigger { struct achievement_trigger {
@ -121,6 +128,60 @@ void save_achievements()
local_storage->write_json_file("", achievements_user_file, user_achievements); local_storage->write_json_file("", achievements_user_file, user_achievements);
} }
void save_leaderboard_score(Steam_Leaderboard *leaderboard)
{
std::vector<uint32_t> output;
uint64_t steam_id = leaderboard->self_score.steam_id.ConvertToUint64();
output.push_back(steam_id & 0xFFFFFFFF);
output.push_back(steam_id >> 32);
output.push_back(leaderboard->self_score.score);
output.push_back(leaderboard->self_score.score_details.size());
for (auto &s : leaderboard->self_score.score_details) {
output.push_back(s);
}
std::string leaderboard_name = ascii_to_lowercase(leaderboard->name);
local_storage->store_data(Local_Storage::leaderboard_storage_folder, leaderboard_name, (char* )output.data(), sizeof(uint32_t) * output.size());
}
std::vector<Steam_Leaderboard_Score> load_leaderboard_scores(std::string name)
{
std::vector<Steam_Leaderboard_Score> out;
std::string leaderboard_name = ascii_to_lowercase(name);
unsigned size = local_storage->file_size(Local_Storage::leaderboard_storage_folder, leaderboard_name);
if (size == 0 || (size % sizeof(uint32_t)) != 0) return out;
std::vector<uint32_t> output(size / sizeof(uint32_t));
if (local_storage->get_data(Local_Storage::leaderboard_storage_folder, leaderboard_name, (char* )output.data(), size) != size) return out;
unsigned i = 0;
while (true) {
if ((i + 4) > output.size()) break;
Steam_Leaderboard_Score score;
score.steam_id = CSteamID((uint64)output[i] + (((uint64)output[i + 1]) << 32));
i += 2;
score.score = output[i];
i += 1;
unsigned count = output[i];
i += 1;
if ((i + count) > output.size()) break;
for (unsigned j = 0; j < count; ++j) {
score.score_details.push_back(output[i]);
i += 1;
}
PRINT_DEBUG("loaded leaderboard score %llu %u\n", score.steam_id.ConvertToUint64(), score.score);
out.push_back(score);
}
return out;
}
public: public:
Steam_User_Stats(Settings *settings, Local_Storage *local_storage, class SteamCallResults *callback_results, class SteamCallBacks *callbacks, Steam_Overlay* overlay): Steam_User_Stats(Settings *settings, Local_Storage *local_storage, class SteamCallResults *callback_results, class SteamCallBacks *callbacks, Steam_Overlay* overlay):
settings(settings), settings(settings),
@ -734,6 +795,14 @@ SteamAPICall_t FindOrCreateLeaderboard( const char *pchLeaderboardName, ELeaderb
leaderboard.name = std::string(pchLeaderboardName); leaderboard.name = std::string(pchLeaderboardName);
leaderboard.sort_method = eLeaderboardSortMethod; leaderboard.sort_method = eLeaderboardSortMethod;
leaderboard.display_type = eLeaderboardDisplayType; leaderboard.display_type = eLeaderboardDisplayType;
std::vector<Steam_Leaderboard_Score> scores = load_leaderboard_scores(pchLeaderboardName);
for (auto &s : scores) {
if (s.steam_id == settings->get_local_steam_id()) {
leaderboard.self_score = s;
}
}
leaderboards.push_back(leaderboard); leaderboards.push_back(leaderboard);
leader = leaderboards.size(); leader = leaderboards.size();
} }
@ -826,10 +895,12 @@ SteamAPICall_t DownloadLeaderboardEntries( SteamLeaderboard_t hSteamLeaderboard,
{ {
PRINT_DEBUG("DownloadLeaderboardEntries %llu %i %i %i\n", hSteamLeaderboard, eLeaderboardDataRequest, nRangeStart, nRangeEnd); PRINT_DEBUG("DownloadLeaderboardEntries %llu %i %i %i\n", hSteamLeaderboard, eLeaderboardDataRequest, nRangeStart, nRangeEnd);
std::lock_guard<std::recursive_mutex> lock(global_mutex); std::lock_guard<std::recursive_mutex> lock(global_mutex);
if (hSteamLeaderboard > leaderboards.size() || hSteamLeaderboard <= 0) return k_uAPICallInvalid; //might return callresult even if hSteamLeaderboard is invalid
LeaderboardScoresDownloaded_t data; LeaderboardScoresDownloaded_t data;
data.m_hSteamLeaderboard = hSteamLeaderboard; data.m_hSteamLeaderboard = hSteamLeaderboard;
data.m_hSteamLeaderboardEntries = 123; data.m_hSteamLeaderboardEntries = hSteamLeaderboard;
data.m_cEntryCount = 0; data.m_cEntryCount = leaderboards[hSteamLeaderboard - 1].self_score.steam_id.IsValid();
return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data));
} }
@ -843,10 +914,19 @@ SteamAPICall_t DownloadLeaderboardEntriesForUsers( SteamLeaderboard_t hSteamLead
{ {
PRINT_DEBUG("DownloadLeaderboardEntriesForUsers %i %llu\n", cUsers, cUsers > 0 ? prgUsers[0].ConvertToUint64() : 0); PRINT_DEBUG("DownloadLeaderboardEntriesForUsers %i %llu\n", cUsers, cUsers > 0 ? prgUsers[0].ConvertToUint64() : 0);
std::lock_guard<std::recursive_mutex> lock(global_mutex); std::lock_guard<std::recursive_mutex> lock(global_mutex);
if (hSteamLeaderboard > leaderboards.size() || hSteamLeaderboard <= 0) return k_uAPICallInvalid; //might return callresult even if hSteamLeaderboard is invalid
bool get_for_current_id = false;
for (int i = 0; i < cUsers; ++i) {
if (prgUsers[i] == settings->get_local_steam_id()) {
get_for_current_id = true;
}
}
LeaderboardScoresDownloaded_t data; LeaderboardScoresDownloaded_t data;
data.m_hSteamLeaderboard = hSteamLeaderboard; data.m_hSteamLeaderboard = hSteamLeaderboard;
data.m_hSteamLeaderboardEntries = 123; data.m_hSteamLeaderboardEntries = hSteamLeaderboard;
data.m_cEntryCount = 0; data.m_cEntryCount = get_for_current_id && leaderboards[hSteamLeaderboard - 1].self_score.steam_id.IsValid();
return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data));
} }
@ -868,7 +948,20 @@ SteamAPICall_t DownloadLeaderboardEntriesForUsers( SteamLeaderboard_t hSteamLead
bool GetDownloadedLeaderboardEntry( SteamLeaderboardEntries_t hSteamLeaderboardEntries, int index, LeaderboardEntry_t *pLeaderboardEntry, int32 *pDetails, int cDetailsMax ) bool GetDownloadedLeaderboardEntry( SteamLeaderboardEntries_t hSteamLeaderboardEntries, int index, LeaderboardEntry_t *pLeaderboardEntry, int32 *pDetails, int cDetailsMax )
{ {
PRINT_DEBUG("GetDownloadedLeaderboardEntry\n"); PRINT_DEBUG("GetDownloadedLeaderboardEntry\n");
return false; std::lock_guard<std::recursive_mutex> lock(global_mutex);
if (hSteamLeaderboardEntries > leaderboards.size() || hSteamLeaderboardEntries <= 0) return false;
if (index > 0) return false;
LeaderboardEntry_t entry = {};
entry.m_steamIDUser = leaderboards[hSteamLeaderboardEntries - 1].self_score.steam_id;
entry.m_nGlobalRank = 1;
entry.m_nScore = leaderboards[hSteamLeaderboardEntries - 1].self_score.score;
for (int i = 0; i < leaderboards[hSteamLeaderboardEntries - 1].self_score.score_details.size() && i < cDetailsMax; ++i) {
pDetails[i] = leaderboards[hSteamLeaderboardEntries - 1].self_score.score_details[i];
}
*pLeaderboardEntry = entry;
return true;
} }
@ -879,17 +972,39 @@ bool GetDownloadedLeaderboardEntry( SteamLeaderboardEntries_t hSteamLeaderboardE
STEAM_CALL_RESULT( LeaderboardScoreUploaded_t ) STEAM_CALL_RESULT( LeaderboardScoreUploaded_t )
SteamAPICall_t UploadLeaderboardScore( SteamLeaderboard_t hSteamLeaderboard, ELeaderboardUploadScoreMethod eLeaderboardUploadScoreMethod, int32 nScore, const int32 *pScoreDetails, int cScoreDetailsCount ) SteamAPICall_t UploadLeaderboardScore( SteamLeaderboard_t hSteamLeaderboard, ELeaderboardUploadScoreMethod eLeaderboardUploadScoreMethod, int32 nScore, const int32 *pScoreDetails, int cScoreDetailsCount )
{ {
PRINT_DEBUG("UploadLeaderboardScore\n"); PRINT_DEBUG("UploadLeaderboardScore %i\n", nScore);
std::lock_guard<std::recursive_mutex> lock(global_mutex); std::lock_guard<std::recursive_mutex> lock(global_mutex);
if (hSteamLeaderboard > leaderboards.size() || hSteamLeaderboard <= 0) return k_uAPICallInvalid; //TODO: might return callresult even if hSteamLeaderboard is invalid
Steam_Leaderboard_Score score;
score.score = nScore;
score.steam_id = settings->get_local_steam_id();
for (int i = 0; i < cScoreDetailsCount; ++i) {
score.score_details.push_back(pScoreDetails[i]);
}
bool changed = false;
if (eLeaderboardUploadScoreMethod == k_ELeaderboardUploadScoreMethodKeepBest) {
if (leaderboards[hSteamLeaderboard - 1].self_score.score <= score.score) {
leaderboards[hSteamLeaderboard - 1].self_score = score;
changed = true;
}
} else {
if (leaderboards[hSteamLeaderboard - 1].self_score.score != score.score) changed = true;
leaderboards[hSteamLeaderboard - 1].self_score = score;
}
if (changed) {
save_leaderboard_score(&(leaderboards[hSteamLeaderboard - 1]));
}
LeaderboardScoreUploaded_t data; LeaderboardScoreUploaded_t data;
data.m_bSuccess = 1; //needs to be success or DOA6 freezes when uploading score. data.m_bSuccess = 1; //needs to be success or DOA6 freezes when uploading score.
//data.m_bSuccess = 0; //data.m_bSuccess = 0;
data.m_hSteamLeaderboard = hSteamLeaderboard; data.m_hSteamLeaderboard = hSteamLeaderboard;
data.m_nScore = nScore; data.m_nScore = nScore;
//data.m_bScoreChanged = 1; data.m_bScoreChanged = changed;
data.m_bScoreChanged = 0; data.m_nGlobalRankNew = 1;
//data.m_nGlobalRankNew = 1;
data.m_nGlobalRankNew = 0;
data.m_nGlobalRankPrevious = 0; data.m_nGlobalRankPrevious = 0;
return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data));
} }

View File

@ -3,22 +3,28 @@ USERNAME = ""
PASSWORD = "" PASSWORD = ""
#steam ids with public profiles that own a lot of games #steam ids with public profiles that own a lot of games
TOP_OWNER_IDS = [76561198028121353, 76561198001237877, 76561198001678750, 76561198237402290] TOP_OWNER_IDS = [76561198028121353, 76561198001237877, 76561198355625888, 76561198001678750, 76561198237402290]
from stats_schema_achievement_gen import achievements_gen from stats_schema_achievement_gen import achievements_gen
from steam.client import SteamClient from steam.client import SteamClient
from steam.client.cdn import CDNClient from steam.client.cdn import CDNClient
from steam.enums import common from steam.enums import common
from steam.enums.common import EResult
from steam.enums.emsg import EMsg from steam.enums.emsg import EMsg
from steam.core.msg import MsgProto from steam.core.msg import MsgProto
import os import os
import sys import sys
import json
prompt_for_unavailable = True
if len(sys.argv) < 2: if len(sys.argv) < 2:
print("\nUsage: {} appid\n\nExample: {} 480\n".format(sys.argv[0], sys.argv[0])) print("\nUsage: {} appid appid appid etc..\n\nExample: {} 480\n".format(sys.argv[0], sys.argv[0]))
exit(1) exit(1)
appid = int(sys.argv[1]) appids = []
for id in sys.argv[1:]:
appids += [int(id)]
client = SteamClient() client = SteamClient()
if not os.path.exists("login_temp"): if not os.path.exists("login_temp"):
@ -28,16 +34,40 @@ client.set_credential_location("login_temp")
if (len(USERNAME) == 0 or len(PASSWORD) == 0): if (len(USERNAME) == 0 or len(PASSWORD) == 0):
client.cli_login() client.cli_login()
else: else:
while True: result = client.login(USERNAME, password=PASSWORD)
ret = client.login(USERNAME, password=PASSWORD) while result in (EResult.AccountLogonDenied, EResult.InvalidLoginAuthCode,
if ret != common.EResult.OK: EResult.AccountLoginDeniedNeedTwoFactor, EResult.TwoFactorCodeMismatch,
if ret == common.EResult.TryAnotherCM: EResult.TryAnotherCM, EResult.ServiceUnavailable,
continue EResult.InvalidPassword,
print("error logging in with set username and password, trying cli login. error was:", ret) ):
client.cli_login()
break if result == EResult.InvalidPassword:
else: print("invalid password, the password you set is wrong.")
break exit(1)
elif result in (EResult.AccountLogonDenied, EResult.InvalidLoginAuthCode):
prompt = ("Enter email code: " if result == EResult.AccountLogonDenied else
"Incorrect code. Enter email code: ")
auth_code, two_factor_code = input(prompt), None
elif result in (EResult.AccountLoginDeniedNeedTwoFactor, EResult.TwoFactorCodeMismatch):
prompt = ("Enter 2FA code: " if result == EResult.AccountLoginDeniedNeedTwoFactor else
"Incorrect code. Enter 2FA code: ")
auth_code, two_factor_code = None, input(prompt)
elif result in (EResult.TryAnotherCM, EResult.ServiceUnavailable):
if prompt_for_unavailable and result == EResult.ServiceUnavailable:
while True:
answer = input("Steam is down. Keep retrying? [y/n]: ").lower()
if answer in 'yn': break
prompt_for_unavailable = False
if answer == 'n': break
client.reconnect(maxdelay=15)
result = client.login(USERNAME, PASSWORD, None, auth_code, two_factor_code)
def get_stats_schema(client, game_id, owner_id): def get_stats_schema(client, game_id, owner_id):
message = MsgProto(EMsg.ClientGetUserStats) message = MsgProto(EMsg.ClientGetUserStats)
@ -49,27 +79,50 @@ def get_stats_schema(client, game_id, owner_id):
client.send(message) client.send(message)
return client.wait_msg(EMsg.ClientGetUserStatsResponse, timeout=5) return client.wait_msg(EMsg.ClientGetUserStatsResponse, timeout=5)
def generate_achievement_stats(client, game_id, output_directory): def generate_achievement_stats(client, game_id, output_directory, backup_directory):
steam_id_list = TOP_OWNER_IDS + [client.steam_id] steam_id_list = TOP_OWNER_IDS + [client.steam_id]
for x in steam_id_list: for x in steam_id_list:
out = get_stats_schema(client, game_id, x) out = get_stats_schema(client, game_id, x)
if out is not None: if out is not None:
if len(out.body.schema) > 0: if len(out.body.schema) > 0:
with open('UserGameStatsSchema_{}.bin'.format(appid), 'wb') as f: with open(os.path.join(backup_directory, 'UserGameStatsSchema_{}.bin'.format(appid)), 'wb') as f:
f.write(out.body.schema) f.write(out.body.schema)
achievements, stats = achievements_gen.generate_stats_achievements(out.body.schema, output_directory) achievements, stats = achievements_gen.generate_stats_achievements(out.body.schema, output_directory)
return return
else: else:
print("no schema", out) print("no schema", out)
out_dir = os.path.join("{}".format( "{}_output".format(appid)), "steam_settings") for appid in appids:
backup_dir = os.path.join("backup","{}".format(appid))
out_dir = os.path.join("{}".format( "{}_output".format(appid)), "steam_settings")
if not os.path.exists(out_dir): if not os.path.exists(backup_dir):
os.makedirs(out_dir) os.makedirs(backup_dir)
print("outputting config to", out_dir) if not os.path.exists(out_dir):
os.makedirs(out_dir)
generate_achievement_stats(client, appid, out_dir) print("outputting config to", out_dir)
with open(os.path.join(out_dir, "steam_appid.txt"), 'w') as f:
f.write(str(appid))
raw = client.get_product_info(apps=[appid])
game_info = raw["apps"][appid]
if "common" in game_info:
game_info_common = game_info["common"]
if "community_visible_stats" in game_info_common:
generate_achievement_stats(client, appid, out_dir, backup_dir)
with open(os.path.join(out_dir, "steam_appid.txt"), 'w') as f:
f.write(str(appid))
if "depots" in game_info:
if "branches" in game_info["depots"]:
if "public" in game_info["depots"]["branches"]:
if "buildid" in game_info["depots"]["branches"]["public"]:
buildid = game_info["depots"]["branches"]["public"]["buildid"]
with open(os.path.join(out_dir, "build_id.txt"), 'w') as f:
f.write(str(buildid))
game_info_backup = json.dumps(game_info, indent=4)
with open(os.path.join(backup_dir, "product_info.json"), "w") as f:
f.write(game_info_backup)

View File

@ -4,10 +4,11 @@ This script depends on python files that are in subfolders so make sure to downl
Using the script: python generate_emu_config.py appid Using the script: python generate_emu_config.py appid
You can also pass multiple appids to generate multiple configs: python generate_emu_config.py appid appid appid
The appid is the number in the steam url. The appid is the number in the steam url.
The first time you run the script it will ask you for your steam username, password and email code. The email code should only be asked the first time and the sentry will be saved to the login_temp folder.
The email code will only be asked the first time and the sentry will be saved to the login_temp folder.
This script will not save your username/password anywhere. If you don't want to always type it you must This script will not save your username/password anywhere. If you don't want to always type it you must
open up the script in a text editor and change: open up the script in a text editor and change: