Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion dll/dll/playtime.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

class PlaytimeCounter {
public:
explicit PlaytimeCounter(Local_Storage* local_storage);
explicit PlaytimeCounter(Local_Storage* local_storage, bool record_playtime = false);
~PlaytimeCounter();

// Tick the playtime counter, call regularly
Expand All @@ -37,12 +37,24 @@ class PlaytimeCounter {

// Get current playtime in seconds
uint64_t seconds() const;
uint64_t session_seconds() const;

// Pause/resume total or session accumulation (e.g. when game is unfocused)
void set_pause_total(bool pause);
void set_pause_session(bool pause);

bool get_record_playtime() const { return record_playtime; }

private:
Local_Storage* local_storage{};
bool record_playtime = false;
const std::string playtime_filename = "playtime.txt";
std::chrono::steady_clock::time_point last_tick{};
uint64_t playtime_seconds = 0;
uint64_t playtime_accumulator_ms = 0; // sub-second accumulation
uint64_t session_seconds_accumulated = 0; // session time accumulated per tick
bool pause_total = false;
bool pause_session = false;
mutable std::mutex mutex;
bool initialized = false;
uint64_t since_save = 0; // seconds since last save
Expand Down
5 changes: 5 additions & 0 deletions dll/dll/settings.h
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ struct Overlay_Appearance {
uint32 notification_duration_chat = 4000; // sliding animation duration duration (millisec)

std::string ach_unlock_datetime_format = "%Y/%m/%d - %H:%M:%S";
bool show_playtime_in_user_info = false;

float background_r = 0.12f;
float background_g = 0.11f;
Expand Down Expand Up @@ -303,6 +304,10 @@ class Settings {
// whether to record playtime
bool record_playtime = false;

// pause total / session playtime when the game window loses focus (ALT+TAB)
bool pause_total_when_unfocused = false;
bool pause_session_when_unfocused = false;

// bypass to make SetAchievement() always return true, prevent some games from breaking
bool achievement_bypass = false;

Expand Down
72 changes: 56 additions & 16 deletions dll/playtime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,15 @@

#include <limits>

PlaytimeCounter::PlaytimeCounter(Local_Storage* local_storage)
: local_storage(local_storage), last_tick(std::chrono::steady_clock::now())
PlaytimeCounter::PlaytimeCounter(Local_Storage* local_storage, bool record_playtime)
: local_storage(local_storage),
record_playtime(record_playtime),
last_tick(std::chrono::steady_clock::now())
{
load();
if (record_playtime) {
save();
}
}

PlaytimeCounter::~PlaytimeCounter()
Expand All @@ -46,23 +51,37 @@ void PlaytimeCounter::tick()
{
std::lock_guard<std::mutex> lock(mutex);

auto delta = std::chrono::duration_cast<std::chrono::seconds>(now - last_tick).count();
if (delta <= 0) return;
auto delta_ms = std::chrono::duration_cast<std::chrono::milliseconds>(now - last_tick).count();
if (delta_ms <= 0) return;

uint64_t inc = static_cast<uint64_t>(delta);
const uint64_t maxv = std::numeric_limits<uint64_t>::max();
if (playtime_seconds > maxv - inc) {
playtime_seconds = maxv;
} else {
playtime_seconds += inc;
}

last_tick = now;

since_save += delta;
if (since_save >= 60) {
since_save = 0;
need_save = true;
// Accumulate milliseconds, convert whole seconds
playtime_accumulator_ms += static_cast<uint64_t>(delta_ms);
uint64_t accrued_sec = playtime_accumulator_ms / 1000;
playtime_accumulator_ms %= 1000;

if (accrued_sec > 0) {
// Accumulate total playtime (unless paused)
if (!pause_total) {
const uint64_t maxv = std::numeric_limits<uint64_t>::max();
if (playtime_seconds > maxv - accrued_sec) {
playtime_seconds = maxv;
} else {
playtime_seconds += accrued_sec;
}

since_save += accrued_sec;
if (since_save >= 15) {
since_save = 0;
need_save = true;
}
}

// Accumulate session playtime (unless paused)
if (!pause_session) {
session_seconds_accumulated += accrued_sec;
}
}
}

Expand All @@ -76,6 +95,8 @@ void PlaytimeCounter::load()
std::lock_guard<std::mutex> lock(mutex);

playtime_seconds = 0;
playtime_accumulator_ms = 0;
session_seconds_accumulated = 0;

std::string data(32, '\0');
if (local_storage->get_data("", playtime_filename, data.data(), static_cast<unsigned int>(data.size()), 0) > 0) {
Expand All @@ -89,6 +110,7 @@ void PlaytimeCounter::load()

void PlaytimeCounter::save()
{
if (!record_playtime) return;
std::lock_guard<std::mutex> lock(mutex);

std::string data = std::to_string(playtime_seconds);
Expand All @@ -100,3 +122,21 @@ uint64_t PlaytimeCounter::seconds() const
std::lock_guard<std::mutex> lock(mutex);
return playtime_seconds;
}

uint64_t PlaytimeCounter::session_seconds() const
{
std::lock_guard<std::mutex> lock(mutex);
return session_seconds_accumulated;
}

void PlaytimeCounter::set_pause_total(bool pause)
{
std::lock_guard<std::mutex> lock(mutex);
pause_total = pause;
}

void PlaytimeCounter::set_pause_session(bool pause)
{
std::lock_guard<std::mutex> lock(mutex);
pause_session = pause;
}
9 changes: 9 additions & 0 deletions dll/settings_parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,10 @@ static void load_overlay_appearance(class Settings *settings_client, class Setti
} else if (name.compare("Achievement_Unlock_Datetime_Format") == 0) {
settings_client->overlay_appearance.ach_unlock_datetime_format = value;
settings_server->overlay_appearance.ach_unlock_datetime_format = value;
} else if (name.compare("Show_Playtime_In_User_Info") == 0) {
bool show = (std::stol(value, NULL) != 0);
settings_client->overlay_appearance.show_playtime_in_user_info = show;
settings_server->overlay_appearance.show_playtime_in_user_info = show;
} else if (name.compare("Achievement_Notification_Delay") == 0) {
float delay_sec = std::stof(value, NULL);
settings_client->achievement_notification_delay_ms = static_cast<int>(delay_sec * 1000.0f);
Expand Down Expand Up @@ -1762,6 +1766,11 @@ static void parse_stats_features(class Settings *settings_client, class Settings

settings_client->record_playtime = ini.GetBoolValue("main::stats", "record_playtime", settings_client->record_playtime);
settings_server->record_playtime = ini.GetBoolValue("main::stats", "record_playtime", settings_server->record_playtime);

settings_client->pause_total_when_unfocused = ini.GetBoolValue("main::stats", "pause_total_when_unfocused", settings_client->pause_total_when_unfocused);
settings_server->pause_total_when_unfocused = ini.GetBoolValue("main::stats", "pause_total_when_unfocused", settings_server->pause_total_when_unfocused);
settings_client->pause_session_when_unfocused = ini.GetBoolValue("main::stats", "pause_session_when_unfocused", settings_client->pause_session_when_unfocused);
settings_server->pause_session_when_unfocused = ini.GetBoolValue("main::stats", "pause_session_when_unfocused", settings_server->pause_session_when_unfocused);
}


Expand Down
29 changes: 26 additions & 3 deletions dll/steam_client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,21 @@
#include "dll/dll.h"


// Returns true if the game window is the currently focused foreground window.
// On non-Windows platforms, always returns true (no focus-based pausing).
static bool is_game_focused()
{
#if defined(__WINDOWS__)
HWND fg = GetForegroundWindow();
if (!fg) return false;
DWORD pid = 0;
GetWindowThreadProcessId(fg, &pid);
return pid == GetCurrentProcessId();
#else
return true;
#endif
}

void Steam_Client::background_thread_proc()
{
auto now_ms = (unsigned long long)std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now().time_since_epoch()).count();
Expand All @@ -38,6 +53,15 @@ void Steam_Client::background_thread_proc()
}

if (settings_client->record_playtime) {
// Only update pause flags when focus state actually changes,
// to avoid pointless mutex acquisitions on every tick.
static std::optional<bool> last_focused;
bool focused = is_game_focused();
if (!last_focused.has_value() || *last_focused != focused) {
last_focused = focused;
playtime_counter->set_pause_total(settings_client->pause_total_when_unfocused && !focused);
playtime_counter->set_pause_session(settings_client->pause_session_when_unfocused && !focused);
}
playtime_counter->tick(); // update playtime counter
}
}
Expand Down Expand Up @@ -98,7 +122,8 @@ Steam_Client::Steam_Client()

// client
PRINT_DEBUG("init client");
steam_overlay = new Steam_Overlay(settings_client, local_storage, callback_results_client, callbacks_client, run_every_runcb, network);
playtime_counter = new PlaytimeCounter(local_storage, settings_client->record_playtime);
steam_overlay = new Steam_Overlay(settings_client, local_storage, callback_results_client, callbacks_client, run_every_runcb, network, playtime_counter);

steam_user = new Steam_User(settings_client, local_storage, network, callback_results_client, callbacks_client, false);
steam_friends = new Steam_Friends(settings_client, local_storage, network, callback_results_client, callbacks_client, run_every_runcb, steam_overlay);
Expand Down Expand Up @@ -162,8 +187,6 @@ Steam_Client::Steam_Client()
PRINT_DEBUG("init AppTicket");
steam_app_ticket = new Steam_AppTicket(settings_client);

playtime_counter = new PlaytimeCounter(local_storage);

gameserver_has_ipv6_functions = false;
steamclient_version = 6; // default for C exports

Expand Down
8 changes: 6 additions & 2 deletions overlay_experimental/overlay/steam_overlay.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include <future>
#include <atomic>
#include <memory>
#include "dll/playtime.h"
#include "InGameOverlay/RendererHook.h"
#include "InGameOverlay/ImGui/imgui.h"
#include "overlay/steam_overlay_stats.h"
Expand Down Expand Up @@ -119,6 +120,7 @@ class Steam_Overlay
class SteamCallBacks* callbacks;
class RunEveryRunCB* run_every_runcb;
class Networking* network;
class PlaytimeCounter* playtime_counter;
class Steam_Overlay_Stats stats;

// friend id, show client window (to chat and accept invite maybe)
Expand Down Expand Up @@ -287,7 +289,7 @@ class Steam_Overlay
static void overlay_networking_callback(void* object, Common_Message* msg);

public:
Steam_Overlay(Settings* settings, Local_Storage *local_storage, SteamCallResults* callback_results, SteamCallBacks* callbacks, RunEveryRunCB* run_every_runcb, Networking *network);
Steam_Overlay(Settings* settings, Local_Storage *local_storage, SteamCallResults* callback_results, SteamCallBacks* callbacks, RunEveryRunCB* run_every_runcb, Networking *network, PlaytimeCounter* playtime_counter);

~Steam_Overlay();

Expand Down Expand Up @@ -322,10 +324,12 @@ class Steam_Overlay

#else // EMU_OVERLAY

class PlaytimeCounter;

class Steam_Overlay
{
public:
Steam_Overlay(Settings* settings, Local_Storage *local_storage, SteamCallResults* callback_results, SteamCallBacks* callbacks, RunEveryRunCB* run_every_runcb, Networking* network) {}
Steam_Overlay(Settings* settings, Local_Storage *local_storage, SteamCallResults* callback_results, SteamCallBacks* callbacks, RunEveryRunCB* run_every_runcb, Networking* network, PlaytimeCounter* playtime_counter) {}
~Steam_Overlay() {}

bool Ready() const { return false; }
Expand Down
3 changes: 3 additions & 0 deletions overlay_experimental/overlay/steam_overlay_stats.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#define _STEAM_OVERLAY_STATS_H_

#include <chrono>
#include <optional>
#include <string_view>
#include <unordered_map>
#include <vector>
Expand All @@ -25,6 +26,8 @@ class Steam_Overlay_Stats {
std::chrono::high_resolution_clock::now();
std::chrono::high_resolution_clock::time_point last_playtime =
std::chrono::high_resolution_clock::now();
std::chrono::high_resolution_clock::duration total_playtime_paused{0};
std::optional<std::chrono::high_resolution_clock::time_point> playtime_pause_start;
unsigned active_playtime_hr = 0;
unsigned active_playtime_min = 0;
unsigned active_playtime_sec = 0;
Expand Down
21 changes: 20 additions & 1 deletion overlay_experimental/steam_overlay.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -141,13 +141,14 @@ void Steam_Overlay::parse_key_combo()
}
}

Steam_Overlay::Steam_Overlay(Settings* settings, Local_Storage *local_storage, SteamCallResults* callback_results, SteamCallBacks* callbacks, RunEveryRunCB* run_every_runcb, Networking* network) :
Steam_Overlay::Steam_Overlay(Settings* settings, Local_Storage *local_storage, SteamCallResults* callback_results, SteamCallBacks* callbacks, RunEveryRunCB* run_every_runcb, Networking* network, PlaytimeCounter* playtime_counter) :
settings(settings),
local_storage(local_storage),
callback_results(callback_results),
callbacks(callbacks),
run_every_runcb(run_every_runcb),
network(network),
playtime_counter(playtime_counter),
stats(Steam_Overlay_Stats(settings))
{
// don't even bother initializing the overlay
Expand Down Expand Up @@ -1581,6 +1582,24 @@ void Steam_Overlay::render_main_window()
settings->get_local_name(),
settings->get_local_steam_id().ConvertToUint64(),
settings->get_local_game_id().AppID());

if (settings->record_playtime && playtime_counter && settings->overlay_appearance.show_playtime_in_user_info) {
uint64_t session_sec = playtime_counter->session_seconds();
unsigned ss = static_cast<unsigned>(session_sec % 60);
unsigned mm = static_cast<unsigned>((session_sec / 60) % 60);
unsigned hh = static_cast<unsigned>(session_sec / 3600);

uint64_t total_sec = playtime_counter->seconds();
unsigned total_h = static_cast<unsigned>(total_sec / 3600);
unsigned total_m = static_cast<unsigned>((total_sec % 3600) / 60);

char total_buf[32]{};
char session_buf[32]{};
snprintf(total_buf, sizeof(total_buf), "%uh %um", total_h, total_m);
snprintf(session_buf, sizeof(session_buf), "%02u:%02u:%02u", hh, mm, ss);

ImGui::LabelText("##playtime", "Total: %s Session: %s", total_buf, session_buf);
}
}

ImGui::Spacing();
Expand Down
37 changes: 36 additions & 1 deletion overlay_experimental/steam_overlay_stats.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,26 @@
#include "overlay/steam_overlay_translations.h"
#include <utility>

#if defined(_WIN32)
#include <windows.h>
#endif


// Returns true if the game window is the currently focused foreground window.
// On non-Windows platforms, always returns true (no focus-based pausing).
static bool is_game_focused()
{
#if defined(_WIN32)
HWND fg = GetForegroundWindow();
if (!fg) return false;
DWORD pid = 0;
GetWindowThreadProcessId(fg, &pid);
return pid == GetCurrentProcessId();
#else
return true;
#endif
}


Steam_Overlay_Stats::Steam_Overlay_Stats(class Settings* settings):
settings(settings)
Expand Down Expand Up @@ -44,10 +64,25 @@ void Steam_Overlay_Stats::update_playtime(const std::chrono::high_resolution_clo
).count();
if (update_duration_sec < 1) return;

// Pause accumulation when the game window loses focus (ALT+TAB)
if (!is_game_focused() && settings->pause_session_when_unfocused) {
if (!playtime_pause_start.has_value()) {
playtime_pause_start = now;
}
return; // freeze the display values
}

// Accumulate paused duration when focus returns
if (playtime_pause_start.has_value()) {
total_playtime_paused += now - *playtime_pause_start;
playtime_pause_start.reset();
}

last_playtime = now;

const auto effective_elapsed = (now - initial_time) - total_playtime_paused;
const auto time_duration_sec = (unsigned long long)std::chrono::duration_cast<std::chrono::seconds>(
now - initial_time
effective_elapsed
).count();
active_playtime_sec = static_cast<unsigned>(time_duration_sec % 60);

Expand Down
Loading