fix: don't let expired free weekend licenses shadow owned games#1148
fix: don't let expired free weekend licenses shadow owned games#1148kiequoo wants to merge 1 commit intoutkarshdalal:masterfrom
Conversation
When a user played a free weekend and later purchased the game, both packages contain the same app ID. PICS processing was overwriting package_id unconditionally, so whichever package was processed last won — if the expired free weekend package landed last, the owned-apps query excluded the game because it only checked that one package_id. Pre-load expired package IDs once per PICS transaction batch and skip updating package_id when the incoming package is expired but the stored one is already valid.
📝 WalkthroughWalkthroughA new DAO query method retrieves expired package IDs from the license table. The SteamService now preloads these IDs and uses them to conditionally update existing SteamApp rows during PICS package processing, avoiding overwriting non-expired package IDs with expired ones. Changes
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@app/src/main/java/app/gamenative/service/SteamService.kt`:
- Around line 3849-3866: The current update logic only skips updating when the
incoming package is expired, which still allows a temporary free-weekend package
to overwrite a valid purchased package; instead compute a stable precedence
between the existing SteamApp.packageId and the incoming pkg.id before calling
appDao.update: use licenseDao.getExpiredPackageIds() plus whatever
expiry/start/purchase metadata is available on picsCallback.packages[pkg.id] (or
a lookup via licenseDao) to determine which package is higher-priority (e.g.,
prefer non-expired over expired; if both non-expired prefer the package with the
later expiry or explicit purchase timestamp; if tied use deterministic
tie-breaker like package id), and only call
appDao.update(steamApp.copy(packageId = selectedPkgId)) when selectedPkgId !=
steamApp.packageId; reference picsCallback.packages,
licenseDao.getExpiredPackageIds(), appDao.findApp(), appDao.update(), and
SteamApp.packageId when making the change.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 313034e5-f5a4-43fa-ab24-30021b59dfd0
📒 Files selected for processing (2)
app/src/main/java/app/gamenative/db/dao/SteamLicenseDao.ktapp/src/main/java/app/gamenative/service/SteamService.kt
| val expiredPkgIds = licenseDao.getExpiredPackageIds().toHashSet() | ||
| picsCallback.packages.values.forEach { pkg -> | ||
| val appIds = pkg.keyValues["appids"].children.map { it.asInteger() } | ||
| licenseDao.updateApps(pkg.id, appIds) | ||
|
|
||
| val depotIds = pkg.keyValues["depotids"].children.map { it.asInteger() } | ||
| licenseDao.updateDepots(pkg.id, depotIds) | ||
|
|
||
| val isPkgExpired = pkg.id in expiredPkgIds | ||
| // Insert a stub row (or update) of SteamApps to the database. | ||
| // Only update package_id with an expired package if the existing | ||
| // one is also expired — prevents free weekend licenses from | ||
| // shadowing a later real purchase of the same game. | ||
| appIds.forEach { appid -> | ||
| val steamApp = appDao.findApp(appid)?.copy(packageId = pkg.id) | ||
| val steamApp = appDao.findApp(appid) | ||
| if (steamApp != null) { | ||
| appDao.update(steamApp) | ||
| if (!isPkgExpired || steamApp.packageId in expiredPkgIds) { | ||
| appDao.update(steamApp.copy(packageId = pkg.id)) |
There was a problem hiding this comment.
This can still reproduce the disappearing-game bug if the purchase happens during the free weekend.
Line 3865 only protects against an incoming package that is already expired. If the active free-weekend package is processed after the purchase package, it still overwrites SteamApp.packageId; the game then disappears later when that weekend license flips to expired, because the owned-app query keys off that single packageId. This needs a stable precedence between multiple valid packages, not only a check once one of them is already expired.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/src/main/java/app/gamenative/service/SteamService.kt` around lines 3849 -
3866, The current update logic only skips updating when the incoming package is
expired, which still allows a temporary free-weekend package to overwrite a
valid purchased package; instead compute a stable precedence between the
existing SteamApp.packageId and the incoming pkg.id before calling
appDao.update: use licenseDao.getExpiredPackageIds() plus whatever
expiry/start/purchase metadata is available on picsCallback.packages[pkg.id] (or
a lookup via licenseDao) to determine which package is higher-priority (e.g.,
prefer non-expired over expired; if both non-expired prefer the package with the
later expiry or explicit purchase timestamp; if tied use deterministic
tie-breaker like package id), and only call
appDao.update(steamApp.copy(packageId = selectedPkgId)) when selectedPkgId !=
steamApp.packageId; reference picsCallback.packages,
licenseDao.getExpiredPackageIds(), appDao.findApp(), appDao.update(), and
SteamApp.packageId when making the change.
There was a problem hiding this comment.
Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!
There was a problem hiding this comment.
@kiequoo did you see this?
Only now, I'll look into it!
| // shadowing a later real purchase of the same game. | ||
| appIds.forEach { appid -> | ||
| val steamApp = appDao.findApp(appid)?.copy(packageId = pkg.id) | ||
| val steamApp = appDao.findApp(appid) |
Summary
I played Dying Light during a Steam free weekend, then later bought it. After purchasing, the game wasn't showing up in GameNative's library at all.
This is related to but distinct from the expired-license filtering work in #945, #982, and #985. Those PRs addressed expired free weekend games appearing in the library. This fixes the inverse: a game you genuinely own disappearing because the expired free weekend license shadows the real purchase.
Root cause: During PICS package processing, each package unconditionally overwrites the
packageIdon its associatedSteamApprows. If the expired free weekend package happened to be processed after the real purchase package, it clobberedpackageIdwith the expired license's ID — making the game invisible to the owned apps query (which, post-#985, correctly filters out expired packages).Fix: Before iterating packages, load the set of all expired package IDs. When updating an app's
packageId, skip the write if the incoming package is expired but the app's currentpackageIdis not — a real purchase already won, don't let an expired license shadow it.SteamLicenseDao: new query to fetch expired package IDs (license_flags & 8 != 0)SteamService: guard aroundpackageIdupdate — expired packages can only overwrite other expired packagesSummary by cubic
Fixes a bug where expired Steam free weekend licenses could hide games you own. During PICS updates, expired packages no longer overwrite a valid
packageId, keeping purchased games visible in the library.SteamLicenseDao.getExpiredPackageIds()to load expired package IDs (license_flags & 8 != 0) once per batch.SteamServiceto only let an expired package overwritepackageIdif the existing one is also expired.Written for commit 7c4b197. Summary will update on new commits.
Summary by CodeRabbit