Skip to content
Closed
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
4 changes: 2 additions & 2 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -210,8 +210,8 @@ dependencies {
// JavaSteam
val localBuild = false // Change to 'true' needed when building JavaSteam manually
if (localBuild) {
implementation(files("../../JavaSteam/build/libs/javasteam-1.8.0-11-SNAPSHOT.jar"))
implementation(files("../../JavaSteam/javasteam-depotdownloader/build/libs/javasteam-depotdownloader-1.8.0-11-SNAPSHOT.jar"))
implementation(files("../../JavaSteam/build/libs/javasteam-1.8.0-12-SNAPSHOT.jar"))
implementation(files("../../JavaSteam/javasteam-depotdownloader/build/libs/javasteam-depotdownloader-1.8.0-12-SNAPSHOT.jar"))
implementation(libs.bundles.javasteam.dev)
} else {
implementation(libs.javasteam) {
Expand Down
7 changes: 7 additions & 0 deletions app/src/main/java/app/gamenative/PrefManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,13 @@ object PrefManager {
setPref(STEAM_OFFLINE_MODE, value)
}

private val SHOW_ACHIEVEMENT_POPUPS = booleanPreferencesKey("show_achievement_popups")
var showAchievementPopups: Boolean
get() = getPref(SHOW_ACHIEVEMENT_POPUPS, false)
set(value) {
setPref(SHOW_ACHIEVEMENT_POPUPS, value)
}

private val USE_LEGACY_DRM = booleanPreferencesKey("use_legacy_drm")
var useLegacyDRM: Boolean
get() = getPref(USE_LEGACY_DRM, false)
Expand Down
173 changes: 171 additions & 2 deletions app/src/main/java/app/gamenative/service/SteamService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ import `in`.dragonbra.javasteam.steam.handlers.steamuser.SteamUser
import `in`.dragonbra.javasteam.steam.handlers.steamuser.callback.LoggedOffCallback
import `in`.dragonbra.javasteam.steam.handlers.steamuser.callback.LoggedOnCallback
import `in`.dragonbra.javasteam.steam.handlers.steamuser.callback.PlayingSessionStateCallback
import `in`.dragonbra.javasteam.steam.handlers.steamuserstats.Stats
import `in`.dragonbra.javasteam.steam.handlers.steamuserstats.SteamUserStats
import `in`.dragonbra.javasteam.steam.handlers.steamworkshop.SteamWorkshop
import `in`.dragonbra.javasteam.steam.steamclient.AsyncJobFailedException
Expand Down Expand Up @@ -157,8 +158,11 @@ import java.util.concurrent.CopyOnWriteArrayList
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.FormBody
import org.json.JSONArray
import org.json.JSONObject
import com.winlator.container.ContainerManager
import app.gamenative.statsgen.Achievement

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if Achievement class is used anywhere in SteamService.kt
rg -n '\bAchievement\b' app/src/main/java/app/gamenative/service/SteamService.kt | grep -v 'import'

Repository: utkarshdalal/GameNative

Length of output: 184


Remove unused import Achievement at line 164.

The Achievement class is imported but never used in this file. The only occurrence of "Achievement" outside the import statement is in a string literal at line 2138.

🤖 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` at line 164, Remove
the unused import of Achievement from SteamService.kt: delete the line importing
app.gamenative.statsgen.Achievement since the class is not referenced in the
file (only appears inside a string literal), ensuring no other references to
Achievement exist in methods like those around the import area so compilation
and imports remain clean.

import app.gamenative.statsgen.StatsAchievementsGenerator

@AndroidEntryPoint
class SteamService : Service(), IChallengeUrlChanged {
Expand Down Expand Up @@ -214,6 +218,7 @@ class SteamService : Service(), IChallengeUrlChanged {
private var _steamApps: SteamApps? = null
private var _steamFriends: SteamFriends? = null
private var _steamCloud: SteamCloud? = null
private var _steamUserStats: SteamUserStats? = null

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Missing cleanup of _steamUserStats in clearValues().

Other Steam handlers (_steamUser, _steamApps, _steamFriends, _steamCloud) are set to null in clearValues() around line 3007-3010, but _steamUserStats is not. This could lead to holding a stale reference after logout or disconnect.

🛠️ Proposed fix in clearValues()

Add this line alongside the other handler cleanup:

_steamUserStats = null
🤖 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` at line 221,
clearValues() currently nulls other Steam handlers but omits `_steamUserStats`,
leaving a stale reference; update the `clearValues()` method to set
`_steamUserStats` to null alongside `_steamUser`, `_steamApps`, `_steamFriends`,
and `_steamCloud` so the SteamService no longer retains the handler after
logout/disconnect and allows proper GC of the SteamUserStats instance.

private var _steamFamilyGroups: FamilyGroups? = null

private var _loginResult: LoginResult = LoginResult.Failed
Expand Down Expand Up @@ -2115,7 +2120,7 @@ class SteamService : Service(), IChallengeUrlChanged {
}
}

suspend fun closeApp(appId: Int, isOffline: Boolean, prefixToPath: (String) -> String) = withContext(Dispatchers.IO) {
suspend fun closeApp(context: Context, appId: Int, isOffline: Boolean, prefixToPath: (String) -> String) = withContext(Dispatchers.IO) {
async {
if (isOffline || !isConnected) {
return@async
Expand All @@ -2127,6 +2132,12 @@ class SteamService : Service(), IChallengeUrlChanged {
}

try {
try {
syncAchievementsFromGoldberg(context, appId)
} catch (e: Exception) {
Timber.e(e, "Achievement sync failed for appId=$appId, continuing with cloud save sync")
}

val maxAttempts = 3
for (attempt in 1..maxAttempts) {
try {
Expand Down Expand Up @@ -2600,6 +2611,164 @@ class SteamService : Service(), IChallengeUrlChanged {
return emptySet()
}
}

suspend fun generateAchievements(appId: Int, configDirectory: String) {
val steamUser = instance!!._steamUser!!
val userStats = instance?._steamUserStats!!.getUserStats(appId, steamUser.steamID!!).await()
val schemaArray = userStats.schema.toByteArray()
val generator = StatsAchievementsGenerator()
val result = generator.generateStatsAchievements(schemaArray, configDirectory)

val nameToBlockBit = result.nameToBlockBit
Timber.d("nameToBlockBit size=${nameToBlockBit.size} for appId=$appId")
if (nameToBlockBit.isNotEmpty()) {
val configDir = File(configDirectory)
if (!configDir.exists()) configDir.mkdirs()
val mappingJson = JSONObject()
nameToBlockBit.forEach { (name, pair) ->
mappingJson.put(name, JSONArray(listOf(pair.first, pair.second)))
}
File(configDir, "achievement_name_to_block.json").writeText(mappingJson.toString(), Charsets.UTF_8)
}
}
Comment on lines +2615 to +2633

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Improve null safety and add result validation.

This function has several null safety concerns:

  1. Line 2616-2617: Mixing instance!! and instance?._steamUserStats!! is inconsistent and risky. If instance or _steamUserStats is null, this will crash.
  2. No result validation: getUserStats returns a callback that should be checked for success before accessing schema.
  3. Called from runBlocking: Per SteamUtils.generateAchievementsFile, this is called via runBlocking without checking if the service is initialized, which could cause crashes during early container setup.
🛠️ Proposed fix with proper null safety and result checking
 suspend fun generateAchievements(appId: Int, configDirectory: String) {
-    val steamUser = instance!!._steamUser!!
-    val userStats = instance?._steamUserStats!!.getUserStats(appId, steamUser.steamID!!).await()
+    val steamUser = instance?._steamUser ?: run {
+        Timber.w("Cannot generate achievements: steamUser not initialized")
+        return
+    }
+    val steamUserStats = instance?._steamUserStats ?: run {
+        Timber.w("Cannot generate achievements: steamUserStats not initialized")
+        return
+    }
+    val steamId = steamUser.steamID ?: run {
+        Timber.w("Cannot generate achievements: steamID is null")
+        return
+    }
+    val userStats = steamUserStats.getUserStats(appId, steamId).await()
+    if (userStats.result != EResult.OK) {
+        Timber.w("getUserStats failed for appId=$appId: ${userStats.result}")
+        return
+    }
     val schemaArray = userStats.schema.toByteArray()
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
suspend fun generateAchievements(appId: Int, configDirectory: String) {
val steamUser = instance!!._steamUser!!
val userStats = instance?._steamUserStats!!.getUserStats(appId, steamUser.steamID!!).await()
val schemaArray = userStats.schema.toByteArray()
val generator = StatsAchievementsGenerator()
val result = generator.generateStatsAchievements(schemaArray, configDirectory)
val nameToBlockBit = result.nameToBlockBit
Timber.d("nameToBlockBit size=${nameToBlockBit.size} for appId=$appId")
if (nameToBlockBit.isNotEmpty()) {
val configDir = File(configDirectory)
if (!configDir.exists()) configDir.mkdirs()
val mappingJson = JSONObject()
nameToBlockBit.forEach { (name, pair) ->
mappingJson.put(name, JSONArray(listOf(pair.first, pair.second)))
}
File(configDir, "achievement_name_to_block.json").writeText(mappingJson.toString(), Charsets.UTF_8)
}
}
suspend fun generateAchievements(appId: Int, configDirectory: String) {
val steamUser = instance?._steamUser ?: run {
Timber.w("Cannot generate achievements: steamUser not initialized")
return
}
val steamUserStats = instance?._steamUserStats ?: run {
Timber.w("Cannot generate achievements: steamUserStats not initialized")
return
}
val steamId = steamUser.steamID ?: run {
Timber.w("Cannot generate achievements: steamID is null")
return
}
val userStats = steamUserStats.getUserStats(appId, steamId).await()
if (userStats.result != EResult.OK) {
Timber.w("getUserStats failed for appId=$appId: ${userStats.result}")
return
}
val schemaArray = userStats.schema.toByteArray()
val generator = StatsAchievementsGenerator()
val result = generator.generateStatsAchievements(schemaArray, configDirectory)
val nameToBlockBit = result.nameToBlockBit
Timber.d("nameToBlockBit size=${nameToBlockBit.size} for appId=$appId")
if (nameToBlockBit.isNotEmpty()) {
val configDir = File(configDirectory)
if (!configDir.exists()) configDir.mkdirs()
val mappingJson = JSONObject()
nameToBlockBit.forEach { (name, pair) ->
mappingJson.put(name, JSONArray(listOf(pair.first, pair.second)))
}
File(configDir, "achievement_name_to_block.json").writeText(mappingJson.toString(), Charsets.UTF_8)
}
}
🤖 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 2615 -
2633, The generateAchievements function uses unsafe force-unwraps and doesn’t
validate the getUserStats result; update generateAchievements to first
null-check instance, instance._steamUser and instance._steamUserStats and return
early with a log if any are null (avoid using !!), then call getUserStats(appId,
steamUser.steamID).await() and validate the returned object for success and
non-null schema before accessing schema.toByteArray(), and handle/ log failure
cases; after obtaining a valid schema call
StatsAchievementsGenerator.generateStatsAchievements and verify its result
(non-null nameToBlockBit) before writing the achievement_name_to_block.json
(ensure configDirectory creation still occurs), and ensure callers (e.g.,
SteamUtils.generateAchievementsFile) only runBlocking this method after the
service is initialized or add an internal initialized check to fail-fast with a
clear log instead of crashing.


suspend fun syncAchievementsFromGoldberg(context: Context, appId: Int) {
val imageFs = ImageFs.find(context)
val gseSavesDir = File(
imageFs.rootDir,
"${ImageFs.WINEPREFIX}/drive_c/users/xuser/AppData/Roaming/GSE Saves/$appId"
)
var goldbergAchFile = File(gseSavesDir, "achievements.json")
if (!goldbergAchFile.exists()) {
Timber.d("No Goldberg achievements.json at ${goldbergAchFile.absolutePath}, checking userdata path")
val accountId = userSteamId?.accountID?.toInt()
if (accountId != null) {
val userdataDir = File(
imageFs.rootDir,
"${ImageFs.WINEPREFIX}/drive_c/Program Files (x86)/Steam/userdata/$accountId/$appId"
)
goldbergAchFile = File(userdataDir, "achievements.json")
}
if (!goldbergAchFile.exists()) {
Timber.d("No Goldberg achievements.json found for appId=$appId")
return
}
}

val unlockedNames = mutableSetOf<String>()
try {
val json = JSONObject(goldbergAchFile.readText(Charsets.UTF_8))
for (name in json.keys()) {
val entry = json.optJSONObject(name) ?: continue
if (entry.optBoolean("earned", false)) {
unlockedNames.add(name)
}
}
} catch (e: Exception) {
Timber.e(e, "Failed to parse Goldberg achievements.json for appId=$appId")
return
}

if (unlockedNames.isEmpty()) {
Timber.d("No earned achievements found in Goldberg output for appId=$appId")
return
}

val configDirectory = findSteamSettingsDir(context, appId)
if (configDirectory == null) {
Timber.w("Could not find steam_settings directory for appId=$appId")
return
}

Timber.i("Found ${unlockedNames.size} earned achievements for appId=$appId, syncing to Steam")

@cubic-dev-ai cubic-dev-ai Bot Mar 9, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: syncAchievementsFromGoldberg reads and parses achievements but the storeAchievementUnlocks call is commented out, making it a no-op. The log message "syncing to Steam" is misleading since nothing is actually synced. If this is intentionally WIP, consider either removing the call from closeApp until the sync is ready, or at minimum fixing the log to say "found (not yet synced)".

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/src/main/java/app/gamenative/service/SteamService.kt, line 2683:

<comment>`syncAchievementsFromGoldberg` reads and parses achievements but the `storeAchievementUnlocks` call is commented out, making it a no-op. The log message "syncing to Steam" is misleading since nothing is actually synced. If this is intentionally WIP, consider either removing the call from `closeApp` until the sync is ready, or at minimum fixing the log to say "found (not yet synced)".</comment>

<file context>
@@ -2600,6 +2611,164 @@ class SteamService : Service(), IChallengeUrlChanged {
+                return
+            }
+
+            Timber.i("Found ${unlockedNames.size} earned achievements for appId=$appId, syncing to Steam")
+//            val result = storeAchievementUnlocks(appId, configDirectory, unlockedNames)
+//            result.onSuccess {
</file context>
Suggested change
Timber.i("Found ${unlockedNames.size} earned achievements for appId=$appId, syncing to Steam")
Timber.i("Found ${unlockedNames.size} earned achievements for appId=$appId (sync not yet implemented)")
Fix with Cubic

// val result = storeAchievementUnlocks(appId, configDirectory, unlockedNames)
// result.onSuccess {
// Timber.i("Successfully synced achievements to Steam for appId=$appId")
// }.onFailure { e ->
// Timber.e(e, "Failed to sync achievements to Steam for appId=$appId")
// }
Comment on lines +2683 to +2689

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Incomplete implementation: sync call is commented out.

The function logs "Found ${unlockedNames.size} earned achievements for appId=$appId, syncing to Steam" but the actual storeAchievementUnlocks call is commented out. Per the commit message, this was done to make the code compile, but:

  1. The log message is misleading as no actual sync occurs
  2. This leaves the achievement sync feature non-functional

Consider either:

  • Updating the log to reflect the actual state (e.g., "would sync to Steam" or "sync disabled")
  • Adding a TODO comment explaining when this will be enabled
  • Tracking this in an issue for follow-up
🤖 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 2683 -
2689, The log claims achievements are being synced but the call to
storeAchievementUnlocks is commented out; either re-enable the sync by
uncommenting and restoring the storeAchievementUnlocks(appId, configDirectory,
unlockedNames) call and its result handling (onSuccess/onFailure) or change the
Timber.i message to accurately reflect that syncing is disabled (e.g., "would
sync to Steam" or "sync disabled"), and add a TODO referencing the decision
and/or an issue ID; look for unlockedNames, appId, storeAchievementUnlocks, and
the Timber.i call to apply the fix.

}

private fun findSteamSettingsDir(context: Context, appId: Int): String? {
val appDirPath = getAppDirPath(appId)
val appDirSettings = File(appDirPath, "steam_settings")
if (File(appDirSettings, "achievement_name_to_block.json").exists()) {
return appDirSettings.absolutePath
}

val container = ContainerUtils.getContainer(context, "STEAM_$appId")
val coldclientSettings = File(
container.rootDir,
".wine/drive_c/Program Files (x86)/Steam/steam_settings"
)
if (File(coldclientSettings, "achievement_name_to_block.json").exists()) {
return coldclientSettings.absolutePath
}

return null
}
Comment on lines +2692 to +2709

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Missing exception handling for getContainer call.

ContainerUtils.getContainer throws an Exception if the container doesn't exist for the given appId. This exception will propagate up and could crash the achievement sync flow in syncAchievementsFromGoldberg.

🛡️ Proposed fix with try-catch
 private fun findSteamSettingsDir(context: Context, appId: Int): String? {
     val appDirPath = getAppDirPath(appId)
     val appDirSettings = File(appDirPath, "steam_settings")
     if (File(appDirSettings, "achievement_name_to_block.json").exists()) {
         return appDirSettings.absolutePath
     }

-    val container = ContainerUtils.getContainer(context, "STEAM_$appId")
-    val coldclientSettings = File(
-        container.rootDir,
-        ".wine/drive_c/Program Files (x86)/Steam/steam_settings"
-    )
-    if (File(coldclientSettings, "achievement_name_to_block.json").exists()) {
-        return coldclientSettings.absolutePath
+    try {
+        val container = ContainerUtils.getContainer(context, "STEAM_$appId")
+        val coldclientSettings = File(
+            container.rootDir,
+            ".wine/drive_c/Program Files (x86)/Steam/steam_settings"
+        )
+        if (File(coldclientSettings, "achievement_name_to_block.json").exists()) {
+            return coldclientSettings.absolutePath
+        }
+    } catch (e: Exception) {
+        Timber.d("Container not found for appId=$appId: ${e.message}")
     }

     return null
 }
🤖 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 2692 -
2709, The call to ContainerUtils.getContainer in findSteamSettingsDir can throw
and needs to be guarded so it doesn't crash syncAchievementsFromGoldberg; wrap
the getContainer(context, "STEAM_$appId") invocation in a try-catch that catches
Exception, log or report the error (using the existing logging mechanism) and
treat it as a missing container by returning null or continuing, ensuring
findSteamSettingsDir returns null when the container lookup fails rather than
letting the exception propagate.


suspend fun storeAchievementUnlocks(
appId: Int,
configDirectory: String,
unlockedNames: Set<String>
): Result<Unit> = runCatching {
val mappingFile = File(configDirectory, "achievement_name_to_block.json")
if (!mappingFile.exists()) {
throw IllegalStateException("achievement_name_to_block.json not found in $configDirectory")
}
val mappingJson = JSONObject(mappingFile.readText(Charsets.UTF_8))
val nameToBlockBit = mutableMapOf<String, Pair<Int, Int>>()
for (key in mappingJson.keys()) {
val arr = mappingJson.optJSONArray(key) ?: continue
if (arr.length() >= 2) {
nameToBlockBit[key] = Pair(arr.getInt(0), arr.getInt(1))
}
}
if (nameToBlockBit.isEmpty()) return@runCatching

val steamUser = instance!!._steamUser!!
val userStats = instance?._steamUserStats!!.getUserStats(appId, steamUser.steamID!!).await()
if (userStats.result != EResult.OK) {
throw IllegalStateException("getUserStats failed: ${userStats.result}")
}
val rawBlocks = userStats.achievementBlocks ?: emptyList()
val blockBitmasks = mutableMapOf<Int, Int>()
for (block in rawBlocks) {
val blockId = (block.achievementId as? Number)?.toInt() ?: continue
var bitmask = 0
val unlockTimes = block.unlockTime ?: emptyList()
for (i in unlockTimes.indices) {
val t = unlockTimes[i]
if ((t as? Number)?.toLong() != 0L) bitmask = bitmask or (1 shl i)
}
blockBitmasks[blockId] = bitmask
}
for (name in unlockedNames) {
val (blockId, bitIndex) = nameToBlockBit[name] ?: continue
val current = blockBitmasks.getOrDefault(blockId, 0)
blockBitmasks[blockId] = current or (1 shl bitIndex)
}
val statsToStore = blockBitmasks.map { (id, mask) -> Stats(statId = id, statValue = mask) }
Timber.d("storeUserStats: appId=$appId, crcStats=${userStats.crcStats}, stats=$statsToStore")
val mySteamId = steamUser.steamID!!
// val callback = instance?._steamUserStats!!.storeUserStats(
// appId, statsToStore, mySteamId, mySteamId, userStats.crcStats
// ).await()
// if (callback.result != EResult.OK) {
// throw IllegalStateException("storeUserStats failed: ${callback.result}")
// }
// if (callback.statsOutOfDate) {
// Timber.w("Stats were out of date on server for appId=$appId")
// }
// if (callback.statsFailedValidation.isNotEmpty()) {
// Timber.w("${callback.statsFailedValidation.size} stats failed validation for appId=$appId")
// callback.statsFailedValidation.forEach { f ->
// Timber.w(" statId=${f.statId} reverted to ${f.revertedStatValue}")
// }
// }
Comment on lines +2755 to +2769

@cubic-dev-ai cubic-dev-ai Bot Mar 9, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: storeAchievementUnlocks no longer persists unlock stats because the storeUserStats(...) call is commented out.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/src/main/java/app/gamenative/service/SteamService.kt, line 2755:

<comment>`storeAchievementUnlocks` no longer persists unlock stats because the `storeUserStats(...)` call is commented out.</comment>

<file context>
@@ -2752,21 +2752,21 @@ class SteamService : Service(), IChallengeUrlChanged {
-                    Timber.w("  statId=${f.statId} reverted to ${f.revertedStatValue}")
-                }
-            }
+//            val callback = instance?._steamUserStats!!.storeUserStats(
+//                appId, statsToStore, mySteamId, mySteamId, userStats.crcStats
+//            ).await()
</file context>
Suggested change
// val callback = instance?._steamUserStats!!.storeUserStats(
// appId, statsToStore, mySteamId, mySteamId, userStats.crcStats
// ).await()
// if (callback.result != EResult.OK) {
// throw IllegalStateException("storeUserStats failed: ${callback.result}")
// }
// if (callback.statsOutOfDate) {
// Timber.w("Stats were out of date on server for appId=$appId")
// }
// if (callback.statsFailedValidation.isNotEmpty()) {
// Timber.w("${callback.statsFailedValidation.size} stats failed validation for appId=$appId")
// callback.statsFailedValidation.forEach { f ->
// Timber.w(" statId=${f.statId} reverted to ${f.revertedStatValue}")
// }
// }
val callback = instance?._steamUserStats!!.storeUserStats(
appId, statsToStore, mySteamId, mySteamId, userStats.crcStats
).await()
if (callback.result != EResult.OK) {
throw IllegalStateException("storeUserStats failed: ${callback.result}")
}
if (callback.statsOutOfDate) {
Timber.w("Stats were out of date on server for appId=$appId")
}
if (callback.statsFailedValidation.isNotEmpty()) {
Timber.w("${callback.statsFailedValidation.size} stats failed validation for appId=$appId")
callback.statsFailedValidation.forEach { f ->
Timber.w(" statId=${f.statId} reverted to ${f.revertedStatValue}")
}
}
Fix with Cubic

}

}

override fun onCreate() {
Expand Down Expand Up @@ -2704,7 +2873,6 @@ class SteamService : Service(), IChallengeUrlChanged {
removeHandler(SteamMasterServer::class.java)
removeHandler(SteamWorkshop::class.java)
removeHandler(SteamScreenshots::class.java)
removeHandler(SteamUserStats::class.java)
}

// create the callback manager which will route callbacks to function calls
Expand All @@ -2715,6 +2883,7 @@ class SteamService : Service(), IChallengeUrlChanged {
_steamApps = steamClient!!.getHandler(SteamApps::class.java)
_steamFriends = steamClient!!.getHandler(SteamFriends::class.java)
_steamCloud = steamClient!!.getHandler(SteamCloud::class.java)
_steamUserStats = steamClient!!.getHandler(SteamUserStats::class.java)

_unifiedFriends = SteamUnifiedFriends(this)
_steamFamilyGroups = steamClient!!.getHandler<SteamUnifiedMessages>()!!.createService<FamilyGroups>()
Expand Down
44 changes: 44 additions & 0 deletions app/src/main/java/app/gamenative/statsgen/Models.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package app.gamenative.statsgen

data class Achievement(
val name: String,
val displayName: Map<String, String>? = null,
val description: Map<String, String>? = null,
val hidden: Int = 0,
val icon: String? = null,
val iconGray: String? = null,
val icongray: String? = null,
val progress: Map<String, Any>? = null,
val unlocked: Boolean? = null,
val unlockTimestamp: Int? = null,
val formattedUnlockTime: String? = null
)

data class Stat(
val id: String,
val name: String,
val type: String,
val default: String = "0",
val global: String = "0",
val min: String? = null
)

data class ProcessingResult(
val achievements: List<Achievement>,
val stats: List<Stat>,
val copyDefaultUnlockedImg: Boolean,
val copyDefaultLockedImg: Boolean,
val nameToBlockBit: Map<String, Pair<Int, Int>> = emptyMap(),
)

object StatType {
const val STAT_TYPE_INT = "1"
const val STAT_TYPE_FLOAT = "2"
const val STAT_TYPE_AVGRATE = "3"
const val STAT_TYPE_BITS = "4"

const val ACHIEVEMENTS = "ACHIEVEMENTS"
const val INT = "INT"
const val FLOAT = "FLOAT"
const val AVGRATE = "AVGRATE"
}
Loading