///|
/// A validated Steam app manifest. Decimal identifiers stay as strings.
pub(all) struct SteamApp {
app_id : String
name : String
install_dir : String
size_on_disk : Int64?
build_id : String?
} derive(Eq, Debug)
///|
pub(all) struct SteamLibrary {
index : String
path : String
label : String?
app_ids : Array[String]
} derive(Eq, Debug)
///|
pub suberror SteamError {
InvalidManifest(String)
} derive(Debug)
///|
fn steam_find(entries : Array[Entry], key : String) -> Value? {
for entry in entries {
if entry.key.equal_ignore_ascii_case(key) {
return Some(entry.value)
}
}
None
}
///|
fn steam_text(entries : Array[Entry], key : String) -> String? {
match steam_find(entries, key) {
Some(Text(s)) => Some(s)
_ => None
}
}
///|
fn decimal_id(text : String) -> Bool {
!text.is_empty() && text.all(c => c >= '0' && c <= '9')
}
///|
/// Interpret an AppState object without touching the filesystem.
pub fn read_app_manifest(document : Document) -> SteamApp raise SteamError {
guard steam_find(document.entries, "AppState") is Some(Object(entries)) else {
raise InvalidManifest("Expected an AppState object")
}
guard steam_text(entries, "appid") is Some(app_id) && decimal_id(app_id) else {
raise InvalidManifest("appid must be a nonempty decimal string")
}
guard steam_text(entries, "name") is Some(name) && !name.is_empty() else {
raise InvalidManifest("name must be a nonempty string")
}
guard steam_text(entries, "installdir") is Some(install_dir) &&
!install_dir.is_empty() else {
raise InvalidManifest("installdir must be a nonempty string")
}
let size_on_disk = match steam_text(entries, "SizeOnDisk") {
None => None
Some(raw) => {
if !decimal_id(raw) {
raise InvalidManifest(
"SizeOnDisk must be a nonnegative decimal integer",
)
}
let value = @strconv.parse_int64(raw[:]) catch {
_ => raise InvalidManifest("SizeOnDisk exceeds Int64 range")
}
Some(value)
}
}
{
app_id,
name,
install_dir,
size_on_disk,
build_id: steam_text(entries, "buildid"),
}
}
///|
/// Read current object-style and legacy string-style libraryfolders entries.
pub fn read_library_folders(
document : Document,
) -> Array[SteamLibrary] raise SteamError {
guard steam_find(document.entries, "libraryfolders") is Some(Object(entries)) else {
raise InvalidManifest("Expected a libraryfolders object")
}
let libraries : Array[SteamLibrary] = []
for entry in entries {
if !decimal_id(entry.key) {
continue
}
match entry.value {
Text(path) => {
if path.is_empty() {
raise InvalidManifest("Library path must not be empty")
}
libraries.push({ index: entry.key, path, label: None, app_ids: [], })
}
Object(fields) => {
guard steam_text(fields, "path") is Some(path) && !path.is_empty() else {
raise InvalidManifest("Library entry is missing its path")
}
let app_ids = match steam_find(fields, "apps") {
Some(Object(apps)) =>
apps.filter_map(e => {
if decimal_id(e.key) {
Some(e.key)
} else {
None
}
})
None => []
_ => raise InvalidManifest("Library apps must be an object")
}
libraries.push({
index: entry.key,
path,
label: steam_text(fields, "label"),
app_ids,
})
}
}
}
libraries
}