Add options to write appinfo and manifest as JSON

This to enable integration with automation suites that reads JSON files.
pull/671/head
Andreas Aronsson 1 month ago
parent 77f9067eaa
commit 6cf7988230

@ -9,6 +9,9 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Threading;
using System.Threading.Tasks;
using SteamKit2;
@ -912,6 +915,11 @@ namespace DepotDownloader
return null;
}
if (Config.WriteManifestJson)
{
WriteManifestToJsonFile(depot, newManifest);
}
var stagingDir = Path.Combine(depot.InstallDir, STAGING_DIR);
var filesAfterExclusions = newManifest.Files.AsParallel().Where(f => TestIsFileIncluded(f.FileName)).ToList();
@ -1429,5 +1437,160 @@ namespace DepotDownloader
sw.WriteLine($"{file.TotalSize,14:d} {file.Chunks.Count,6:d} {sha1Hash} {(int)file.Flags,5:x} {file.FileName}");
}
}
private static void WriteManifestToJsonFile(DepotDownloadInfo depot, DepotManifest manifest)
{
var fileName = $"manifest_{depot.DepotId}_{depot.ManifestId}.json";
var filePath = Path.Combine(depot.InstallDir, CONFIG_DIR, fileName);
using var sw = new StreamWriter(filePath);
var manifestFiles = manifest.Files ?? [];
var uniqueChunks = new HashSet<byte[]>(new ChunkIdComparer());
foreach (var file in manifestFiles)
{
foreach (var chunk in file.Chunks)
{
uniqueChunks.Add(chunk.ChunkID);
}
}
var filesArray = new JsonArray();
foreach (var file in manifestFiles)
{
var sha1Hash = Convert.ToHexString(file.FileHash).ToLower();
var fileObject = new JsonObject
{
{ "size", file.TotalSize },
{ "chunks", file.Chunks.Count },
{ "sha", sha1Hash },
{ "flags", Convert.ToString((int)file.Flags, 16) },
{ "name", file.FileName }
};
filesArray.Add(fileObject);
}
var jsonObject = new JsonObject
{
{ "description", $"Content Manifest for Depot {depot.DepotId}" },
{ "depotId", depot.DepotId },
{ "manifestId", depot.ManifestId },
{ "manifestCreationTime", manifest.CreationTime },
{ "totalFiles", manifestFiles.Count },
{ "totalChunks", uniqueChunks.Count },
{ "totalBytesOnDisk", manifest.TotalUncompressedSize },
{ "totalBytesCompressed", manifest.TotalCompressedSize },
{ "files", filesArray }
};
var options = new JsonSerializerOptions
{
WriteIndented = true // Pretty print
};
sw.WriteLine(jsonObject.ToJsonString(options));
Console.WriteLine($"Wrote {fileName}");
}
public static async Task DumpAppInfoJsonFile(uint appId)
{
if (steam3 != null && appId != INVALID_APP_ID)
{
await steam3.RequestAppInfo(appId);
}
if (steam3?.AppInfo == null)
{
return;
}
if (!steam3.AppInfo.TryGetValue(appId, out var appInfo) || appInfo == null)
{
return;
}
WriteAppInfoToJsonFile(appInfo, Config.InstallDirectory);
}
private static void WriteAppInfoToJsonFile(SteamApps.PICSProductInfoCallback.PICSProductInfo appInfo, string installDir)
{
var fileName = $"appinfo_{appInfo.ID}.json";
var filePath = Path.Combine(installDir, CONFIG_DIR, fileName);
using var sw = new StreamWriter(filePath);
// Will traverse the KeyValue chain and fill the parent JSON object.
void Traverse(KeyValue kv, JsonObject parentObj)
{
if (kv.Name != null)
{
if (kv.Value == null)
{
var childObj = new JsonObject();
foreach (var child in kv.Children)
{
Traverse(child, childObj);
}
parentObj[kv.Name] = childObj;
}
else
{
parentObj[kv.Name] = kv.Value;
}
}
else
{
Console.WriteLine("KeyValue.Name was null");
}
}
var jsonObject = new JsonObject();
// Dynamically walk appInfo properties in case it is extended in the future.
foreach (var prop in appInfo.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
var name = prop.Name;
var value = prop.GetValue(appInfo);
switch (value)
{
case int or short or long or uint or ushort or ulong: // ID, ChangeNumber
jsonObject[name] = Convert.ToInt64(value);
break;
case float or double: // Future proofing
jsonObject[name] = Convert.ToDouble(value);
break;
case bool boolean: // MissingToken, OnlyPublic, UseHttp
jsonObject[name] = boolean;
break;
case string str: // Future proofing
jsonObject[name] = str;
break;
case null: // SHAHash, HttpUri
jsonObject[name] = null;
break;
case byte[] bytes: // SHAHash
jsonObject[name] = Convert.ToHexString(bytes);
break;
case KeyValue keyValue: // KeyValues
{
var values = new JsonObject();
Traverse(keyValue, values);
jsonObject[name] = values;
break;
}
case Uri uri: // HttpUri
jsonObject[name] = uri.ToString();
break;
default:
Console.WriteLine($"Warning: Unhandled property in AppInfo JSON output: {name} ({value})");
break;
}
}
var options = new JsonSerializerOptions
{
WriteIndented = true // Pretty print
};
sw.WriteLine(jsonObject.ToJsonString(options));
Console.WriteLine($"Wrote {fileName}");
}
}
}

@ -28,5 +28,6 @@
<PackageReference Include="protobuf-net" Version="3.2.56" />
<PackageReference Include="QRCoder" Version="1.7.0" />
<PackageReference Include="SteamKit2" Version="3.3.1" />
<PackageReference Include="System.Text.Json" Version="9.0.10" />
</ItemGroup>
</Project>

@ -32,5 +32,7 @@ namespace DepotDownloader
public bool UseQrCode { get; set; }
public bool SkipAppConfirmation { get; set; }
public bool WriteAppInfoJson { get; set; }
public bool WriteManifestJson { get; set; }
}
}

@ -156,6 +156,8 @@ namespace DepotDownloader
ContentDownloader.Config.MaxDownloads = GetParameter(args, "-max-downloads", 8);
ContentDownloader.Config.LoginID = HasParameter(args, "-loginid") ? GetParameter<uint>(args, "-loginid") : null;
ContentDownloader.Config.WriteAppInfoJson = HasParameter(args, "-appinfo-json");
ContentDownloader.Config.WriteManifestJson = HasParameter(args, "-manifest-json");
#endregion
@ -529,6 +531,8 @@ namespace DepotDownloader
Console.WriteLine(" -max-downloads <#> - maximum number of chunks to download concurrently. (default: 8).");
Console.WriteLine(" -loginid <#> - a unique 32-bit integer Steam LogonID in decimal, required if running multiple instances of DepotDownloader concurrently.");
Console.WriteLine(" -use-lancache - forces downloads over the local network via a Lancache instance.");
Console.WriteLine(" -appinfo-json - writes appinfo as JSON to the config directory.");
Console.WriteLine(" -manifest-json - writes human readable manifest as JSON to the config directory.");
Console.WriteLine();
Console.WriteLine(" -debug - enable verbose debug logging.");
Console.WriteLine(" -V or --version - print version and runtime.");

@ -174,6 +174,11 @@ namespace DepotDownloader
Console.WriteLine("Got AppInfo for {0}", app.ID);
AppInfo[app.ID] = app;
if (ContentDownloader.Config.WriteAppInfoJson)
{
await ContentDownloader.DumpAppInfoJsonFile(appId);
}
}
foreach (var app in appInfo.UnknownApps)

@ -102,6 +102,8 @@ Parameter | Description
`-cellid <#>` | the overridden CellID of the content server to download from.
`-max-downloads <#>` | maximum number of chunks to download concurrently. (default: 8).
`-use-lancache` | forces downloads over the local network via a Lancache instance.
`-appinfo-json` | writes appinfo as JSON to the config directory.
`-manifest-json` | writes human readable manifest as JSON to the config directory.
#### Other

Loading…
Cancel
Save