Skip to content
Merged
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
31 changes: 19 additions & 12 deletions src/Hook/Hooks_Package.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
#include "Hooks_SteamUI.h"
#include "dllmain.h"
#include "Utils/VehCommon.h"
#include <thread>

namespace {
RESOLVE_FUNC(CUtlMemoryGrow, void*, CUtlVector<AppId_t>*, int);
Expand Down Expand Up @@ -134,9 +133,6 @@ namespace Hooks_Package {
UNHOOK_END();
}

constexpr size_t kBatchSize = 50;
constexpr DWORD kBatchSleepMs = 20;

void NotifyLicenseChanged() {
PackageInfo* pPkg = g_pInjectedPackageInfo;
if (!pPkg) {
Expand All @@ -159,20 +155,27 @@ namespace Hooks_Package {

// ── Add depots that are newly loaded ──
std::vector<AppId_t> additions = LuaConfig::TakePendingAdditions();
std::unordered_set<AppId_t> addedIds;
LOG_PACKAGE_DEBUG("NotifyLicenseChanged: processing {} additions", additions.size());
if (!additions.empty()) {
uint32_t oldSize = pPkg->AppIdVec.m_Size;
if (CUtlMemoryGrowWrap(&pPkg->AppIdVec, additions.size())) {
// An applied addition invalidates any UI removal that has not
// reached the UI thread yet.
for (AppId_t id : additions)
Hooks_SteamUI::CancelRemoval(id);

for (size_t i = 0; i < additions.size(); ++i) {
pPkg->AppIdVec.m_Memory.m_pMemory[oldSize + i] = additions[i];
addedIds.insert(additions[i]);
LOG_PACKAGE_DEBUG("NotifyLicenseChanged: inserted AppId {} at [{}]", additions[i], oldSize + i);
}
}else {
LOG_PACKAGE_WARN("NotifyLicenseChanged: failed to grow AppId vector for additions");
}
}

if (additions.empty() && removedCount == 0) {
if (addedIds.empty() && removedCount == 0) {
LOG_PACKAGE_DEBUG("NotifyLicenseChanged: no changes");
return;
}
Expand All @@ -182,16 +185,20 @@ namespace Hooks_Package {
LOG_PACKAGE_WARN("NotifyLicenseChanged: failed to mark license as changed");
return;
}
LOG_PACKAGE_INFO("NotifyLicenseChanged: {} added, {} removed", additions.size(), removedCount);
LOG_PACKAGE_INFO("NotifyLicenseChanged: {} added, {} removed", addedIds.size(), removedCount);

// every kBatchSize ids changed, sleep kBatchSleepMs milliseconds
size_t i = 0;
// Queue UI removals for the main-thread RunFrame hook to drain.
// Never touch MarkAppChange from this (FileWatcher) thread.
size_t queuedRemovalCount = 0;
for (AppId_t id : removals) {
if (++i % kBatchSize == 0) {
LOG_PACKAGE_DEBUG("NotifyLicenseChanged: processed {} removals, sleeping for {} ms...", i, kBatchSleepMs);
std::this_thread::sleep_for(std::chrono::milliseconds(kBatchSleepMs));
// ParseFile unloads the old file before parsing the replacement.
// Do not queue that transient removal when the id was added again.
if (!addedIds.contains(id)) {
Hooks_SteamUI::QueueRemoval(id);
++queuedRemovalCount;
}
Hooks_SteamUI::RemoveAppAndSendChange(id);
}
LOG_PACKAGE_DEBUG("NotifyLicenseChanged: queued {} UI removals, skipped {} transient removals",
queuedRemovalCount, removals.size() - queuedRemovalCount);
}
}
114 changes: 89 additions & 25 deletions src/Hook/Hooks_SteamUI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,60 +4,124 @@
#include "dllmain.h"
#include "steam_messages.pb.h"
#include "Utils/VehCommon.h"
#include <mutex>

namespace {
namespace
{
RESOLVE_FUNC(RepeatedFieldUint32_Add, void, void* field, const uint32* value);

CAPTURE_THIS_FUNC(GetAppByID, CSteamApp*, g_pController,void* pThis, AppId_t appId, bool bCreate);
CAPTURE_THIS_FUNC(MarkAppChange,void*,g_pAppChangeSource,void* pThis,AppId_t appId, EAppChangeFlags changeFlags);

HOOK_FUNC(FillInAppOverview,void*,void* pThis,void* pAppOverview,CSteamApp* pApp)
HOOK_FUNC(FillInAppOverview, void *, void *pThis, void *pAppOverview, CSteamApp *pApp)
{
if (pApp && LuaConfig::HasDepot(pApp->nAppID,false)) {
if (pApp && LuaConfig::HasDepot(pApp->nAppID, false))
{
uint32_t t = LuaConfig::GetPurchaseTime(pApp->nAppID);
if(t) {
if (t)
{
pApp->PurchasedTime = t;
LOG_STEAMUI_TRACE("FillInAppOverview: set PurchasedTime={} for appId={}",
pApp->PurchasedTime, pApp->nAppID);
}
}
return oFillInAppOverview(pThis, pAppOverview, pApp);
}
}

namespace Hooks_SteamUI {
void Install() {
// Apps to drop from the library: queued off-thread, marked on the UI thread.
std::mutex g_removalMutex;
std::vector<AppId_t> g_pendingRemovals;
std::unordered_set<AppId_t> g_removedAppIds;

// A full rebuild never lists removed_appid for apps still in the map
// so re-assert our set after the snapshot is built.
HOOK_FUNC(BuildCompleteAppOverviewChange, void, void *pController,
CAppOverview_Change *pChange, void *optionalCallbackSlot)
{
oBuildCompleteAppOverviewChange(pController, pChange, optionalCallbackSlot);
std::lock_guard<std::mutex> lock(g_removalMutex);
if (pChange && !g_removedAppIds.empty() && oRepeatedFieldUint32_Add)
{
auto* field = pChange->mutable_removed_appid();
for (AppId_t appId : g_removedAppIds){
oRepeatedFieldUint32_Add(field, &appId);
}
LOG_STEAMUI_DEBUG("BuildCompleteAppOverviewChange: appended {} removed_appid entries",
g_removedAppIds.size());
}
}


// Clearing ownership makes ShouldShowAppInLibrary() false (delta drops it,
// the full snapshot skips it); MarkAppChange triggers the flush.
HOOK_FUNC(CSteamUIAppControllerRunFrame, void *, void *pController)
{
if (CAPTURE_READY(GetAppByID) && CAPTURE_READY(MarkAppChange))
{
std::vector<AppId_t> draining;
{
std::lock_guard<std::mutex> lock(g_removalMutex);
draining.swap(g_pendingRemovals);
}
for (AppId_t appId : draining)
{
if (LuaConfig::IsOwned(appId))
{
LOG_STEAMUI_DEBUG("RunFrame: appId {} is owned again, skipping removal", appId);
continue;
}
if (CSteamApp *pApp = oGetAppByID(g_pController, appId, false))
{
// Only remove from the library if it's not already uninstalled
pApp->OwnershipFlags = k_EAppOwnershipFlags_None;
if(pApp->AppStateFlags == k_EAppStateUninstalled){
std::lock_guard<std::mutex> lock(g_removalMutex);
g_removedAppIds.insert(appId);
}
}

oMarkAppChange(g_pAppChangeSource, appId, EAppChangeFlags::AppInfoOrConfig);
}
}
return oCSteamUIAppControllerRunFrame(pController);
}
}

namespace Hooks_SteamUI
{
void Install()
{
ARM_CAPTURE_U(GetAppByID);
ARM_CAPTURE_U(MarkAppChange);

RESOLVE_U(RepeatedFieldUint32_Add);

HOOK_BEGIN();
INSTALL_HOOK_U(FillInAppOverview);
INSTALL_HOOK_U(BuildCompleteAppOverviewChange);
INSTALL_HOOK_U(CSteamUIAppControllerRunFrame);
HOOK_END();

}

void Uninstall() {
void Uninstall()
{
UNHOOK_BEGIN();
UNINSTALL_HOOK(FillInAppOverview);
UNINSTALL_HOOK(BuildCompleteAppOverviewChange);
UNINSTALL_HOOK(CSteamUIAppControllerRunFrame);
UNHOOK_END();
}

void RemoveAppAndSendChange(AppId_t appId) {
// skip on owned apps
if(LuaConfig::IsOwned(appId)){
LOG_STEAMUI_WARN("RemoveAppAndSendChange: appId={} is owned, skipping", appId);
return;
}
if(CAPTURE_READY(GetAppByID) && CAPTURE_READY(MarkAppChange)) {
CSteamApp* pApp = oGetAppByID(g_pController, appId, false);
if(pApp) {
pApp->OwnershipFlags = k_EAppOwnershipFlags_None;
LOG_STEAMUI_DEBUG("RemoveAppAndSendChange: cleared owned flag for appId={}", appId);
oMarkAppChange(g_pAppChangeSource, appId, EAppChangeFlags::AddedOrCreated);
} else {
LOG_STEAMUI_WARN("RemoveAppAndSendChange: appId={} not found in GetAppByID", appId);
}
}
void QueueRemoval(AppId_t appId)
{
std::lock_guard<std::mutex> lock(g_removalMutex);
g_pendingRemovals.push_back(appId);
}

void CancelRemoval(AppId_t appId)
{
std::lock_guard<std::mutex> lock(g_removalMutex);
std::erase(g_pendingRemovals, appId);
g_removedAppIds.erase(appId);
}
}
7 changes: 4 additions & 3 deletions src/Hook/Hooks_SteamUI.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ namespace Hooks_SteamUI {
void Install();
void Uninstall();

// Clears ownership flag for the given appId and
// sends an app change notification to update the library UI.
void RemoveAppAndSendChange(AppId_t appId);
// Queues an appId for removal from the library UI
void QueueRemoval(AppId_t appId);
// Cancels a queued removal when the app is added again before the UI drains it.
void CancelRemoval(AppId_t appId);
}
31 changes: 31 additions & 0 deletions src/Steam/Enums.h
Original file line number Diff line number Diff line change
Expand Up @@ -1923,4 +1923,35 @@ enum class EAppChangeFlags {
GameAction = 0x2000,
LibraryAssetCleanup = 0x4000,
MRURegenerated = 0x8000,
};

// Steam account types
enum EAccountType
{
k_EAccountTypeInvalid = 0,
k_EAccountTypeIndividual = 1, // single user account
k_EAccountTypeMultiseat = 2, // multiseat (e.g. cybercafe) account
k_EAccountTypeGameServer = 3, // game server account
k_EAccountTypeAnonGameServer = 4, // anonymous game server account
k_EAccountTypePending = 5, // pending
k_EAccountTypeContentServer = 6, // content server
k_EAccountTypeClan = 7,
k_EAccountTypeChat = 8,
k_EAccountTypeConsoleUser = 9, // Fake SteamID for local PSN account on PS3 or Live account on 360, etc.
k_EAccountTypeAnonUser = 10,

// Max of 16 items in this field
k_EAccountTypeMax
};

// Steam universes. Each universe is a self-contained Steam instance.
enum EUniverse
{
k_EUniverseInvalid = 0,
k_EUniversePublic = 1,
k_EUniverseBeta = 2,
k_EUniverseInternal = 3,
k_EUniverseDev = 4,
// k_EUniverseRC = 5, // no such universe anymore
k_EUniverseMax
};
Loading
Loading