Skip to content

Commit 56b1fec

Browse files
committed
Add appinfo v29
Requires ValveResourceFormat/ValveKeyValue#99
1 parent a8069e8 commit 56b1fec

File tree

3 files changed

+103
-8
lines changed

3 files changed

+103
-8
lines changed

README.md

+23
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,29 @@ This is mostly intended as an example on how to read these files.
44

55
## appinfo.vdf
66
```
7+
uint32 - MAGIC: 29 44 56 07
8+
uint32 - UNIVERSE: 1
9+
int64 - Offset to string table from start of the file
10+
---- repeated app sections ----
11+
uint32 - AppID
12+
uint32 - size // until end of binary_vdf
13+
uint32 - infoState // mostly 2, sometimes 1 (may indicate prerelease or no info)
14+
uint32 - lastUpdated
15+
uint64 - picsToken
16+
20bytes - SHA1 // of text appinfo vdf, as seen in CMsgClientPICSProductInfoResponse.AppInfo.sha
17+
uint32 - changeNumber
18+
20bytes - SHA1 // of binary_vdf
19+
variable - binary_vdf
20+
---- end of section ----
21+
uint32 - EOF: 0
22+
23+
---- offset to the string table ----
24+
uint32 - Count of strings
25+
null-term strings[count]
26+
```
27+
28+
## appinfo.vdf (before june 2024)
29+
```
730
uint32 - MAGIC: 28 44 56 07
831
uint32 - UNIVERSE: 1
932
---- repeated app sections ----

SteamAppInfo.sln

+9-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11

22
Microsoft Visual Studio Solution File, Format Version 12.00
3-
# Visual Studio Version 16
4-
VisualStudioVersion = 16.0.30011.22
3+
# Visual Studio Version 17
4+
VisualStudioVersion = 17.10.35013.160
55
MinimumVisualStudioVersion = 10.0.40219.1
6-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SteamAppInfoParser", "SteamAppInfoParser\SteamAppInfoParser.csproj", "{6B285D2E-0613-434D-86B5-A51CEA7D4445}"
6+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SteamAppInfoParser", "SteamAppInfoParser\SteamAppInfoParser.csproj", "{6B285D2E-0613-434D-86B5-A51CEA7D4445}"
7+
EndProject
8+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ValveKeyValue", "..\..\ValveResourceFormat\ValveKeyValue\ValveKeyValue\ValveKeyValue\ValveKeyValue.csproj", "{DA073F8F-2816-4F11-983E-8C0A18F290BB}"
79
EndProject
810
Global
911
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -15,6 +17,10 @@ Global
1517
{6B285D2E-0613-434D-86B5-A51CEA7D4445}.Debug|Any CPU.Build.0 = Debug|Any CPU
1618
{6B285D2E-0613-434D-86B5-A51CEA7D4445}.Release|Any CPU.ActiveCfg = Release|Any CPU
1719
{6B285D2E-0613-434D-86B5-A51CEA7D4445}.Release|Any CPU.Build.0 = Release|Any CPU
20+
{DA073F8F-2816-4F11-983E-8C0A18F290BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21+
{DA073F8F-2816-4F11-983E-8C0A18F290BB}.Debug|Any CPU.Build.0 = Debug|Any CPU
22+
{DA073F8F-2816-4F11-983E-8C0A18F290BB}.Release|Any CPU.ActiveCfg = Release|Any CPU
23+
{DA073F8F-2816-4F11-983E-8C0A18F290BB}.Release|Any CPU.Build.0 = Release|Any CPU
1824
EndGlobalSection
1925
GlobalSection(SolutionProperties) = preSolution
2026
HideSolutionNode = FALSE

SteamAppInfoParser/AppInfo.cs

+71-5
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
using System;
2+
using System.Buffers;
23
using System.Collections.Generic;
34
using System.Collections.ObjectModel;
45
using System.IO;
6+
using System.Text;
57
using ValveKeyValue;
68

79
namespace SteamAppInfoParser
810
{
911
class AppInfo
1012
{
13+
private const uint Magic29 = 0x07_56_44_29;
1114
private const uint Magic28 = 0x07_56_44_28;
1215
private const uint Magic = 0x07_56_44_27;
1316

@@ -34,13 +37,33 @@ public void Read(Stream input)
3437
using var reader = new BinaryReader(input);
3538
var magic = reader.ReadUInt32();
3639

37-
if (magic != Magic && magic != Magic28)
40+
if (magic != Magic && magic != Magic28 && magic != Magic29)
3841
{
3942
throw new InvalidDataException($"Unknown magic header: {magic:X}");
4043
}
4144

4245
Universe = (EUniverse)reader.ReadUInt32();
4346

47+
var options = new KVSerializerOptions();
48+
49+
if (magic == Magic29)
50+
{
51+
var stringTableOffset = reader.ReadInt64();
52+
var offset = reader.BaseStream.Position;
53+
reader.BaseStream.Position = stringTableOffset;
54+
var stringCount = reader.ReadUInt32();
55+
var stringPool = new string[stringCount];
56+
57+
for (var i = 0; i < stringCount; i++)
58+
{
59+
stringPool[i] = ReadNullTermUtf8String(reader.BaseStream);
60+
}
61+
62+
reader.BaseStream.Position = offset;
63+
64+
options.StringPool = stringPool;
65+
}
66+
4467
var deserializer = KVSerializer.Create(KVSerializationFormat.KeyValues1Binary);
4568

4669
do
@@ -52,7 +75,8 @@ public void Read(Stream input)
5275
break;
5376
}
5477

55-
reader.ReadUInt32(); // size until end of Data
78+
var size = reader.ReadUInt32(); // size until end of Data
79+
var end = reader.BaseStream.Position + size;
5680

5781
var app = new App
5882
{
@@ -64,20 +88,62 @@ public void Read(Stream input)
6488
ChangeNumber = reader.ReadUInt32(),
6589
};
6690

67-
if (magic == Magic28)
91+
if (magic == Magic28 || magic == Magic29)
6892
{
6993
app.BinaryDataHash = new ReadOnlyCollection<byte>(reader.ReadBytes(20));
7094
}
7195

72-
app.Data = deserializer.Deserialize(input);
96+
app.Data = deserializer.Deserialize(input, options);
97+
98+
if (reader.BaseStream.Position != end)
99+
{
100+
throw new InvalidDataException();
101+
}
73102

74103
Apps.Add(app);
75104
} while (true);
76105
}
77106

78-
public static DateTime DateTimeFromUnixTime(uint unixTime)
107+
private static DateTime DateTimeFromUnixTime(uint unixTime)
79108
{
80109
return new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc).AddSeconds(unixTime);
81110
}
111+
112+
private static string ReadNullTermUtf8String(Stream stream)
113+
{
114+
var buffer = ArrayPool<byte>.Shared.Rent(32);
115+
116+
try
117+
{
118+
var position = 0;
119+
120+
do
121+
{
122+
var b = stream.ReadByte();
123+
124+
if (b <= 0) // null byte or stream ended
125+
{
126+
break;
127+
}
128+
129+
if (position >= buffer.Length)
130+
{
131+
var newBuffer = ArrayPool<byte>.Shared.Rent(buffer.Length * 2);
132+
Buffer.BlockCopy(buffer, 0, newBuffer, 0, buffer.Length);
133+
ArrayPool<byte>.Shared.Return(buffer);
134+
buffer = newBuffer;
135+
}
136+
137+
buffer[position++] = (byte)b;
138+
}
139+
while (true);
140+
141+
return Encoding.UTF8.GetString(buffer[..position]);
142+
}
143+
finally
144+
{
145+
ArrayPool<byte>.Shared.Return(buffer);
146+
}
147+
}
82148
}
83149
}

0 commit comments

Comments
 (0)