2019-11-30 10:31:41 +00:00
|
|
|
|
using System;
|
2019-11-05 14:20:12 +00:00
|
|
|
|
using System.Collections.Generic;
|
2019-11-17 03:09:46 +00:00
|
|
|
|
using System.Diagnostics;
|
2019-11-09 13:49:57 +00:00
|
|
|
|
using System.IO;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Text;
|
2019-11-30 11:20:23 +00:00
|
|
|
|
using DynamicData;
|
2019-11-09 13:49:57 +00:00
|
|
|
|
using Microsoft.Win32;
|
|
|
|
|
|
|
|
|
|
namespace Wabbajack.Common
|
|
|
|
|
{
|
2019-11-30 10:31:41 +00:00
|
|
|
|
[DebuggerDisplay("{" + nameof(Name) + "}")]
|
2019-11-09 13:49:57 +00:00
|
|
|
|
public class SteamGame
|
|
|
|
|
{
|
|
|
|
|
public int AppId;
|
|
|
|
|
public string Name;
|
|
|
|
|
public string InstallDir;
|
2019-11-17 01:42:42 +00:00
|
|
|
|
public Game? Game;
|
2019-11-30 11:20:23 +00:00
|
|
|
|
|
|
|
|
|
public HashSet<SteamWorkshopItem> WorkshopItems;
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Combined size of all installed workshop items on disk in bytes
|
|
|
|
|
/// </summary>
|
|
|
|
|
public int WorkshopItemsSize;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public class SteamWorkshopItem
|
|
|
|
|
{
|
|
|
|
|
public SteamGame Game;
|
|
|
|
|
public int ItemID;
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Size is in bytes
|
|
|
|
|
/// </summary>
|
|
|
|
|
public int Size;
|
2019-11-09 13:49:57 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Class for all Steam operations
|
|
|
|
|
/// </summary>
|
|
|
|
|
public class SteamHandler
|
|
|
|
|
{
|
2019-11-17 01:42:42 +00:00
|
|
|
|
private static readonly Lazy<SteamHandler> instance = new Lazy<SteamHandler>(
|
|
|
|
|
() => new SteamHandler(true),
|
|
|
|
|
isThreadSafe: true);
|
|
|
|
|
public static SteamHandler Instance => instance.Value;
|
|
|
|
|
|
2019-11-09 13:49:57 +00:00
|
|
|
|
private const string SteamRegKey = @"Software\Valve\Steam";
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Path to the Steam folder
|
|
|
|
|
/// </summary>
|
|
|
|
|
public string SteamPath { get; internal set; }
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// HashSet of all known Steam Libraries
|
|
|
|
|
/// </summary>
|
|
|
|
|
public HashSet<string> InstallFolders { get; internal set; }
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// HashSet of all known SteamGames
|
|
|
|
|
/// </summary>
|
|
|
|
|
public HashSet<SteamGame> Games { get; internal set; }
|
|
|
|
|
|
|
|
|
|
private string SteamConfig => Path.Combine(SteamPath, "config", "config.vdf");
|
|
|
|
|
|
|
|
|
|
public SteamHandler(bool init)
|
|
|
|
|
{
|
|
|
|
|
var steamKey = Registry.CurrentUser.OpenSubKey(SteamRegKey);
|
|
|
|
|
SteamPath = steamKey?.GetValue("SteamPath").ToString();
|
|
|
|
|
if(!init) return;
|
|
|
|
|
LoadInstallFolders();
|
|
|
|
|
LoadAllSteamGames();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Finds the installation path of a Steam game by ID
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="id">ID of the Steam game</param>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
public string GetGamePathById(int id)
|
|
|
|
|
{
|
|
|
|
|
return Games.FirstOrDefault(f => f.AppId == id)?.InstallDir;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Reads the config file and adds all found installation folders to the HashSet
|
|
|
|
|
/// </summary>
|
|
|
|
|
public void LoadInstallFolders()
|
|
|
|
|
{
|
|
|
|
|
var paths = new HashSet<string>();
|
|
|
|
|
|
|
|
|
|
File.ReadLines(SteamConfig, Encoding.UTF8).Do(l =>
|
|
|
|
|
{
|
|
|
|
|
if (!l.Contains("BaseInstallFolder_")) return;
|
|
|
|
|
var s = GetVdfValue(l);
|
|
|
|
|
s = Path.Combine(s, "steamapps");
|
|
|
|
|
paths.Add(s);
|
|
|
|
|
});
|
|
|
|
|
|
2019-12-09 22:38:26 +00:00
|
|
|
|
// Default path in the Steam folder isn't in the configs
|
|
|
|
|
paths.Add(Path.Combine(SteamPath, "steamapps"));
|
|
|
|
|
|
2019-11-09 13:49:57 +00:00
|
|
|
|
InstallFolders = paths;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Enumerates through all Steam Libraries to find and read all .afc files, adding the found game
|
|
|
|
|
/// to the HashSet
|
|
|
|
|
/// </summary>
|
|
|
|
|
public void LoadAllSteamGames()
|
|
|
|
|
{
|
|
|
|
|
var games = new HashSet<SteamGame>();
|
|
|
|
|
|
|
|
|
|
InstallFolders.Do(p =>
|
|
|
|
|
{
|
|
|
|
|
Directory.EnumerateFiles(p, "*.acf", SearchOption.TopDirectoryOnly).Do(f =>
|
|
|
|
|
{
|
|
|
|
|
var steamGame = new SteamGame();
|
2019-11-30 10:31:41 +00:00
|
|
|
|
var valid = false;
|
2019-11-09 13:49:57 +00:00
|
|
|
|
File.ReadAllLines(f, Encoding.UTF8).Do(l =>
|
|
|
|
|
{
|
2019-11-30 11:20:23 +00:00
|
|
|
|
if(l.Contains("\"appid\""))
|
|
|
|
|
if (!int.TryParse(GetVdfValue(l), out steamGame.AppId))
|
|
|
|
|
return;
|
|
|
|
|
if(l.Contains("\"name\""))
|
|
|
|
|
steamGame.Name = GetVdfValue(l);
|
|
|
|
|
if(l.Contains("\"installdir\""))
|
|
|
|
|
steamGame.InstallDir = Path.Combine(p, "common", GetVdfValue(l));
|
2019-11-30 10:31:41 +00:00
|
|
|
|
|
2019-11-30 11:20:23 +00:00
|
|
|
|
if (steamGame.AppId != 0 && !string.IsNullOrWhiteSpace(steamGame.Name) &&
|
|
|
|
|
!string.IsNullOrWhiteSpace(steamGame.InstallDir))
|
|
|
|
|
valid = true;
|
2019-11-09 13:49:57 +00:00
|
|
|
|
});
|
|
|
|
|
|
2019-11-30 10:31:41 +00:00
|
|
|
|
if (!valid)
|
|
|
|
|
return;
|
|
|
|
|
|
2019-11-17 01:42:42 +00:00
|
|
|
|
steamGame.Game = GameRegistry.Games.Values
|
2019-11-17 13:35:02 +00:00
|
|
|
|
.FirstOrDefault(g =>
|
|
|
|
|
g.SteamIDs.Contains(steamGame.AppId)
|
|
|
|
|
&&
|
|
|
|
|
g.RequiredFiles.TrueForAll(s => File.Exists(Path.Combine(steamGame.InstallDir, s)))
|
|
|
|
|
)?.Game;
|
2019-11-09 13:49:57 +00:00
|
|
|
|
games.Add(steamGame);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
Games = games;
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-30 11:20:23 +00:00
|
|
|
|
public void LoadWorkshopItems(SteamGame game)
|
|
|
|
|
{
|
|
|
|
|
if(game.WorkshopItems == null)
|
|
|
|
|
game.WorkshopItems = new HashSet<SteamWorkshopItem>();
|
|
|
|
|
|
|
|
|
|
InstallFolders.Do(p =>
|
|
|
|
|
{
|
|
|
|
|
var workshop = Path.Combine(p, "workshop");
|
2019-12-04 10:02:47 +00:00
|
|
|
|
if(!Directory.Exists(workshop))
|
|
|
|
|
return;
|
|
|
|
|
|
2019-11-30 11:20:23 +00:00
|
|
|
|
Directory.EnumerateFiles(workshop, "*.acf", SearchOption.TopDirectoryOnly).Do(f =>
|
|
|
|
|
{
|
|
|
|
|
if (Path.GetFileName(f) != $"appworkshop_{game.AppId}.acf")
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
var foundAppID = false;
|
|
|
|
|
var workshopItemsInstalled = 0;
|
|
|
|
|
var workshopItemDetails = 0;
|
|
|
|
|
var currentItem = new SteamWorkshopItem();
|
|
|
|
|
var bracketStart = 0;
|
|
|
|
|
var bracketEnd = 0;
|
|
|
|
|
var lines = File.ReadAllLines(f, Encoding.UTF8);
|
|
|
|
|
var end = false;
|
|
|
|
|
lines.Do(l =>
|
|
|
|
|
{
|
|
|
|
|
if (end)
|
|
|
|
|
return;
|
|
|
|
|
if(currentItem == null)
|
|
|
|
|
currentItem = new SteamWorkshopItem();
|
|
|
|
|
|
|
|
|
|
var currentLine = lines.IndexOf(l);
|
|
|
|
|
if (l.Contains("\"appid\"") && !foundAppID)
|
|
|
|
|
{
|
|
|
|
|
if (!int.TryParse(GetVdfValue(l), out var appID))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
foundAppID = true;
|
|
|
|
|
|
|
|
|
|
if (appID != game.AppId)
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!foundAppID)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (l.Contains("\"SizeOnDisk\""))
|
|
|
|
|
{
|
|
|
|
|
if (!int.TryParse(GetVdfValue(l), out var sizeOnDisk))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
game.WorkshopItemsSize = sizeOnDisk;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (l.Contains("\"WorkshopItemsInstalled\""))
|
|
|
|
|
workshopItemsInstalled = currentLine;
|
|
|
|
|
|
|
|
|
|
if (l.Contains("\"WorkshopItemDetails\""))
|
|
|
|
|
workshopItemDetails = currentLine;
|
|
|
|
|
|
|
|
|
|
if (workshopItemsInstalled == 0)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (currentLine <= workshopItemsInstalled + 1 && currentLine >= workshopItemDetails - 1)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (currentItem.ItemID == 0)
|
|
|
|
|
if (!int.TryParse(GetSingleVdfValue(l), out currentItem.ItemID))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (currentItem.ItemID == 0)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (bracketStart == 0 && l.Contains("{"))
|
|
|
|
|
bracketStart = currentLine;
|
|
|
|
|
|
|
|
|
|
if (bracketEnd == 0 && l.Contains("}"))
|
|
|
|
|
bracketEnd = currentLine;
|
|
|
|
|
|
|
|
|
|
if (bracketStart == 0)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (currentLine == bracketStart + 1)
|
|
|
|
|
if (!int.TryParse(GetVdfValue(l), out currentItem.Size))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (bracketStart == 0 || bracketEnd == 0 || currentItem.ItemID == 0 || currentItem.Size == 0)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
bracketStart = 0;
|
|
|
|
|
bracketEnd = 0;
|
|
|
|
|
currentItem.Game = game;
|
|
|
|
|
game.WorkshopItems.Add(currentItem);
|
|
|
|
|
currentItem = null;
|
|
|
|
|
end = true;
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-09 13:49:57 +00:00
|
|
|
|
private static string GetVdfValue(string line)
|
|
|
|
|
{
|
|
|
|
|
var trim = line.Trim('\t').Replace("\t", "");
|
|
|
|
|
string[] s = trim.Split('\"');
|
|
|
|
|
return s[3].Replace("\\\\", "\\");
|
|
|
|
|
}
|
2019-11-30 11:20:23 +00:00
|
|
|
|
|
|
|
|
|
private static string GetSingleVdfValue(string line)
|
|
|
|
|
{
|
|
|
|
|
var trim = line.Trim('\t').Replace("\t", "");
|
|
|
|
|
return trim.Split('\"')[1];
|
|
|
|
|
}
|
2019-11-09 13:49:57 +00:00
|
|
|
|
}
|
|
|
|
|
}
|