@ -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}" ) ;
}
}
}