diff --git a/MPF.Frontend/Tools/PhysicalTool.cs b/MPF.Frontend/Tools/PhysicalTool.cs
index 89a11ae8e..a1b985e0d 100644
--- a/MPF.Frontend/Tools/PhysicalTool.cs
+++ b/MPF.Frontend/Tools/PhysicalTool.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
+using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using Newtonsoft.Json;
@@ -155,6 +156,303 @@ public static bool GetBusEncryptionEnabled(Drive? drive)
}
}
+ #endregion
+
+ #region Computer
+
+ ///
+ /// Get info for discs containing Steam2 (sis/sim/sid) depots
+ ///
+ /// Drive to extract information from
+ /// Steam2 information on success, null otherwise
+ public static string? GetSteamSimSidInfo(Drive? drive)
+ {
+ // If there's no drive path, we can't get any sis files
+ if (string.IsNullOrEmpty(drive?.Name))
+ return null;
+
+ // If the folder no longer exists, we can't get any information
+ if (!Directory.Exists(drive!.Name))
+ return null;
+
+ try
+ {
+ string? steamInfo = "";
+ var steamDepotIdList = new List();
+
+ // ? needed due to note in note in https://learn.microsoft.com/en-us/dotnet/api/system.io.directory.getfiles
+#if NETFRAMEWORK
+ string[] sisPaths = Directory.GetFiles(drive.Name, "?*.sis", SearchOption.AllDirectories);
+#else
+ var options = new EnumerationOptions { MatchCasing = MatchCasing.CaseInsensitive, RecurseSubdirectories = true };
+ string[] sisPaths = Directory.GetFiles(drive.Name, "?*.sis", options);
+#endif
+
+ foreach (string sis in sisPaths)
+ {
+ if (!File.Exists(sis))
+ continue;
+
+ string filename = Path.GetFileName(sis);
+
+ // Skips csm/csd sku sis files
+ if (filename.ToLower() == "sku.sis")
+ continue;
+
+ long sisSize = new FileInfo(sis).Length;
+
+ // Arbitrary filesize cap, a disc would need over 100x the normal amount of depots to go over this.
+ if (sisSize > 10000)
+ continue;
+
+ // Read the sku sis file
+ using var fileStream = new FileStream(sis, FileMode.Open, FileAccess.Read);
+ var skuSisDeserializer = new SabreTools.Serialization.Readers.SkuSis();
+ var skuSis = skuSisDeserializer.Deserialize(fileStream);
+
+ if (skuSis != null && skuSis.VDFObject != null)
+ {
+ JToken? upper = null;
+
+ // One single case is known where this isn't true, RID 70147. As of writing, the page claims
+ // it's .sis/.csm/.csd, this isn't true, it's .sis/.sim/.sid. It's one of the latest
+ // .sis/.sim/.sim discs, if not the latest, and the only one currently known to use "sku" instead
+ // of "SKU". It's most likely safe to remove this, but to err on the side of caution, this should
+ // remain to avoid any risk of double-filling info until a much larger amount of testing is done.
+ if (skuSis.VDFObject.TryGetValue("SKU", out var steamSimSidValue))
+ upper = steamSimSidValue;
+
+ if (upper == null)
+ continue;
+
+ if (upper["depots"] == null)
+ continue;
+
+ var depotArr = upper["depots"]?.ToObject>();
+
+ if (depotArr == null)
+ continue;
+
+ steamDepotIdList.AddRange(depotArr.Values.ToList());
+ }
+ }
+
+ var sortedArray = steamDepotIdList.Select(long.Parse).ToArray();
+ Array.Sort(sortedArray);
+
+#if NET20 || NET35
+ var compatibilitySortedArray = sortedArray.Select(x => x.ToString()).ToArray();
+ steamInfo = string.Join("\n", compatibilitySortedArray);
+#else
+ steamInfo = string.Join("\n", sortedArray);
+#endif
+
+ if (steamInfo == "")
+ return null;
+ else
+ return steamInfo;
+ }
+ catch
+ {
+ // Absorb the exception
+ return null;
+ }
+ }
+
+ ///
+ /// Get info for discs containing Steam3 (sis/csm/csd) depots
+ ///
+ /// Drive to extract information from
+ /// Steam Csm/Csd information on success, null otherwise
+ public static string? GetSteamCsmCsdInfo(Drive? drive)
+ {
+ // If there's no drive path, we can't get exe name
+ if (string.IsNullOrEmpty(drive?.Name))
+ return null;
+
+ // If the folder no longer exists, we can't get any information
+ if (!Directory.Exists(drive!.Name))
+ return null;
+
+ try
+ {
+ string? steamInfo = "";
+ var steamDepotIdDict = new SortedDictionary();
+
+ // ? needed due to note in note in https://learn.microsoft.com/en-us/dotnet/api/system.io.directory.getfiles
+#if NETFRAMEWORK
+ string[] sisPaths = Directory.GetFiles(drive.Name, "?*.sis", SearchOption.AllDirectories);
+#else
+ var options = new EnumerationOptions { MatchCasing = MatchCasing.CaseInsensitive, RecurseSubdirectories = true };
+ string[] sisPaths = Directory.GetFiles(drive.Name, "?*.sis", options);
+#endif
+
+ foreach (string sis in sisPaths)
+ {
+ if (!File.Exists(sis))
+ continue;
+
+ string filename = Path.GetFileName(sis);
+
+ // Skips steam2 sku sis files
+ if (filename.ToLower() != "sku.sis")
+ continue;
+
+ long sisSize = new FileInfo(sis).Length;
+
+ // Arbitrary filesize cap, a disc would need over 100x the normal amount of depots to go over this.
+ if (sisSize > 10000)
+ continue;
+
+ // Read the sku sis file
+ using var fileStream = new FileStream(sis, FileMode.Open, FileAccess.Read);
+ var skuSisDeserializer = new SabreTools.Serialization.Readers.SkuSis();
+ var skuSis = skuSisDeserializer.Deserialize(fileStream);
+
+ if (skuSis != null && skuSis.VDFObject != null)
+ {
+ JToken? upper = null;
+
+ if (skuSis.VDFObject.TryGetValue("sku", out var steamCsmCsdValue))
+ upper = steamCsmCsdValue;
+
+ if (upper == null)
+ continue;
+
+ if (upper["manifests"] == null)
+ continue;
+
+ var depotArr = upper["manifests"]?.ToObject>();
+
+ if (depotArr == null)
+ continue;
+
+ foreach (var depot in depotArr)
+#if NETFRAMEWORK
+ steamDepotIdDict.Add(depot.Key, depot.Value); // Always fine in practice.
+#else
+ steamDepotIdDict.TryAdd(depot.Key, depot.Value); // Mainly here to allow easier bulk testing.
+#endif
+ }
+ }
+
+ foreach (var depot in steamDepotIdDict)
+ {
+ steamInfo += $"{depot.Key} ({depot.Value})\n";
+ }
+
+ if (steamInfo == "")
+ return null;
+ else
+ return steamInfo;
+ }
+ catch
+ {
+ // Absorb the exception
+ return null;
+ }
+ }
+
+ ///
+ /// Get info for discs containing Steam2 (sis/sim/sid) depots
+ ///
+ /// Drive to extract information from
+ /// Steam2 information on success, null otherwise
+ public static string? GetSteamAppInfo(Drive? drive)
+ {
+ // If there's no drive path, we can't get exe name
+ if (string.IsNullOrEmpty(drive?.Name))
+ return null;
+
+ // If the folder no longer exists, we can't get any information
+ if (!Directory.Exists(drive!.Name))
+ return null;
+
+ try
+ {
+ string? steamInfo = "";
+ var steamAppIdList = new List();
+
+ // ? needed due to note in note in https://learn.microsoft.com/en-us/dotnet/api/system.io.directory.getfiles
+#if NETFRAMEWORK
+ string[] sisPaths = Directory.GetFiles(drive.Name, "?*.sis", SearchOption.AllDirectories);
+#else
+ var options = new EnumerationOptions { MatchCasing = MatchCasing.CaseInsensitive, RecurseSubdirectories = true };
+ string[] sisPaths = Directory.GetFiles(drive.Name, "?*.sis", options);
+#endif
+
+ // Looping needed in case i.e. this is a coverdisc with multiple steam game installers on it.
+ foreach (string sis in sisPaths)
+ {
+ if (!File.Exists(sis))
+ continue;
+
+ string filename = Path.GetFileName(sis);
+
+ long sisSize = new FileInfo(sis).Length;
+
+ // Arbitrary filesize cap, a disc would need over 100x the normal amount of depots to go over this.
+ if (sisSize > 10000)
+ continue;
+
+ // Read the sku sis file
+ using var fileStream = new FileStream(sis, FileMode.Open, FileAccess.Read);
+ var skuSisDeserializer = new SabreTools.Serialization.Readers.SkuSis();
+ var skuSis = skuSisDeserializer.Deserialize(fileStream);
+
+ if (skuSis != null && skuSis.VDFObject != null)
+ {
+ JToken? upper = null;
+
+ if (skuSis.VDFObject.TryGetValue("SKU", out var steamSimSidValue))
+ upper = steamSimSidValue;
+ else if (skuSis.VDFObject.TryGetValue("sku", out var steamCsmCsdValue))
+ upper = steamCsmCsdValue;
+
+ if (upper == null)
+ continue;
+
+ string? appsString = null;
+
+ // Some newer Steam3 discs (RID 114373 & 114374, 126740) use Apps instead of apps.
+ if (upper["apps"] != null)
+ appsString = "apps";
+ else if (upper["Apps"] != null)
+ appsString = "Apps";
+ else
+ continue;
+
+ var appArr = upper[appsString]?.ToObject>();
+
+ if (appArr == null)
+ continue;
+
+ steamAppIdList.AddRange(appArr.Values.ToList());
+ }
+ }
+
+ var sortedArray = steamAppIdList.Select(long.Parse).ToArray();
+ Array.Sort(sortedArray);
+
+#if NET20 || NET35
+ var compatibilitySortedArray = sortedArray.Select(x => x.ToString()).ToArray();
+ steamInfo = string.Join(", ", compatibilitySortedArray);
+#else
+ steamInfo = string.Join(", ", sortedArray);
+#endif
+
+ if (steamInfo == "")
+ return null;
+ else
+ return steamInfo;
+ }
+ catch
+ {
+ // Absorb the exception
+ return null;
+ }
+ }
+
#endregion
#region PlayStation
diff --git a/MPF.Frontend/Tools/SubmissionGenerator.cs b/MPF.Frontend/Tools/SubmissionGenerator.cs
index b601adc0b..64b3e354b 100644
--- a/MPF.Frontend/Tools/SubmissionGenerator.cs
+++ b/MPF.Frontend/Tools/SubmissionGenerator.cs
@@ -817,6 +817,13 @@ private static bool ProcessSystem(SubmissionInfo info, RedumpSystem? system, Dri
case RedumpSystem.SonyElectronicBook:
info.CommonDiscInfo.Category ??= DiscCategory.Multimedia;
break;
+
+ case RedumpSystem.IBMPCcompatible:
+ case RedumpSystem.AppleMacintosh:
+ info.CommonDiscInfo.CommentsSpecialFields[SiteCode.SteamAppID] = PhysicalTool.GetSteamAppInfo(drive) ?? string.Empty;
+ info.CommonDiscInfo.ContentsSpecialFields[SiteCode.SteamSimSidDepotID] = PhysicalTool.GetSteamSimSidInfo(drive) ?? string.Empty;
+ info.CommonDiscInfo.ContentsSpecialFields[SiteCode.SteamCsmCsdDepotID] = PhysicalTool.GetSteamCsmCsdInfo(drive) ?? string.Empty;
+ break;
case RedumpSystem.IncredibleTechnologiesEagle:
info.CommonDiscInfo.EXEDateBuildDate ??= addPlaceholders ? RequiredValue : string.Empty;