Skip to content
Draft
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
84 changes: 83 additions & 1 deletion app/src/main/java/app/gamenative/service/SteamService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -146,11 +146,14 @@ import kotlinx.coroutines.flow.asStateFlow
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.FormBody
import org.json.JSONArray
import org.json.JSONObject
import android.util.Base64
import app.gamenative.data.DownloadingAppInfo
import app.gamenative.db.dao.DownloadingAppInfoDao
import app.gamenative.db.dao.EncryptedAppTicketDao
import app.gamenative.statsgen.Achievement
import app.gamenative.statsgen.StatsAchievementsGenerator
import kotlinx.coroutines.flow.update
import java.io.InputStream
import java.io.OutputStream
Expand Down Expand Up @@ -211,6 +214,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
private var _steamFamilyGroups: FamilyGroups? = null

private var _loginResult: LoginResult = LoginResult.Failed
Expand Down Expand Up @@ -2513,6 +2517,84 @@ 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()
generator.generateStatsAchievements(schemaArray, configDirectory)

val expandedRaw = runCatching { userStats.getExpandedAchievements("english") }.getOrNull()
if (!expandedRaw.isNullOrEmpty()) {
val nameToBlockBit = expandedRaw
.groupBy { (it.achievementId as? Number)?.toInt() ?: -1 }
.filter { it.key >= 0 }
.flatMap { (rawBlockId, list) ->
val blockId = rawBlockId / 100
list.mapIndexed { index, block ->
(block.name?.takeIf { it.isNotEmpty() } ?: "") to listOf(blockId, index)
}
}
.filter { it.first.isNotEmpty() }
.toMap()
if (nameToBlockBit.isNotEmpty()) {
val configDir = File(configDirectory)
if (!configDir.exists()) configDir.mkdirs()
val mappingJson = JSONObject()
nameToBlockBit.forEach { (name, pair) ->
mappingJson.put(name, JSONArray(pair))
}
File(configDir, "achievement_name_to_block.json").writeText(mappingJson.toString(), Charsets.UTF_8)
}
}
}

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 updates = blockBitmasks.map { (id, mask) -> id to mask }
// instance?._steamUserStats!!.storeUserStats(appId, steamUser.steamID!!, updates)
}

}

override fun onCreate() {
Expand Down Expand Up @@ -2618,7 +2700,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 @@ -2629,6 +2710,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
43 changes: 43 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,43 @@
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
)

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
Loading