2020-01-03 17:03:09 +00:00
|
|
|
|
using System;
|
|
|
|
|
using System.IO;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Security;
|
|
|
|
|
using DynamicData;
|
|
|
|
|
using Microsoft.Win32;
|
|
|
|
|
|
|
|
|
|
namespace Wabbajack.Common.StoreHandlers
|
|
|
|
|
{
|
|
|
|
|
public class SteamGame : AStoreGame
|
|
|
|
|
{
|
|
|
|
|
public override Game Game { get; internal set; }
|
|
|
|
|
public override string Name { get; internal set; }
|
2020-03-25 22:30:43 +00:00
|
|
|
|
public override AbsolutePath Path { get; internal set; }
|
2020-01-03 17:03:09 +00:00
|
|
|
|
public override int ID { get; internal set; }
|
|
|
|
|
public override StoreType Type { get; internal set; } = StoreType.STEAM;
|
|
|
|
|
|
|
|
|
|
public string Universe;
|
|
|
|
|
|
|
|
|
|
public List<SteamWorkshopItem> WorkshopItems;
|
|
|
|
|
public int WorkshopItemsSize;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public class SteamWorkshopItem
|
|
|
|
|
{
|
|
|
|
|
public SteamGame Game;
|
|
|
|
|
public int ItemID;
|
|
|
|
|
public int Size;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public class SteamHandler : AStoreHandler
|
|
|
|
|
{
|
|
|
|
|
public override List<AStoreGame> Games { get; set; }
|
|
|
|
|
public override StoreType Type { get; internal set; } = StoreType.STEAM;
|
|
|
|
|
|
|
|
|
|
private const string SteamRegKey = @"Software\Valve\Steam";
|
|
|
|
|
|
2020-01-03 17:22:30 +00:00
|
|
|
|
public string SteamPath { get; set; }
|
2020-01-03 17:03:09 +00:00
|
|
|
|
private List<string> SteamUniverses { get; set; }
|
|
|
|
|
|
|
|
|
|
private string SteamConfig => Path.Combine(SteamPath, "config", "config.vdf");
|
|
|
|
|
|
|
|
|
|
public override bool Init()
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var steamKey = Registry.CurrentUser.OpenSubKey(SteamRegKey);
|
|
|
|
|
|
|
|
|
|
var steamPathKey = steamKey?.GetValue("SteamPath");
|
|
|
|
|
if (steamPathKey == null)
|
|
|
|
|
{
|
|
|
|
|
Utils.Error(new StoreException("Could not open the SteamPath registry key!"));
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SteamPath = steamPathKey.ToString();
|
|
|
|
|
if (string.IsNullOrWhiteSpace(SteamPath))
|
|
|
|
|
{
|
|
|
|
|
Utils.Error(new StoreException("Path to the Steam Directory from registry is Null or Empty!"));
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!Directory.Exists(SteamPath))
|
|
|
|
|
{
|
|
|
|
|
Utils.Error(new StoreException($"Path to the Steam Directory from registry does not exists: {SteamPath}"));
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (File.Exists(SteamConfig))
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
Utils.Error(new StoreException($"The Steam config file could not be read: {SteamConfig}"));
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
catch (SecurityException se)
|
|
|
|
|
{
|
|
|
|
|
Utils.Error(se, "SteamHandler could not read from registry!");
|
|
|
|
|
}
|
|
|
|
|
catch (UnauthorizedAccessException uae)
|
|
|
|
|
{
|
|
|
|
|
Utils.Error(uae, "SteamHandler could not read from registry!");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void LoadUniverses()
|
|
|
|
|
{
|
|
|
|
|
SteamUniverses = new List<string>();
|
|
|
|
|
|
|
|
|
|
File.ReadAllLines(SteamConfig).Do(l =>
|
|
|
|
|
{
|
2020-01-05 00:36:43 +00:00
|
|
|
|
if (!l.ContainsCaseInsensitive("BaseInstallFolder_")) return;
|
2020-01-03 17:03:09 +00:00
|
|
|
|
var s = GetVdfValue(l);
|
|
|
|
|
s = Path.Combine(s, "steamapps");
|
|
|
|
|
if (!Directory.Exists(s))
|
|
|
|
|
{
|
|
|
|
|
Utils.Log($"Directory {s} does not exist, skipping");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SteamUniverses.Add(s);
|
|
|
|
|
Utils.Log($"Steam Library found at {s}");
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
Utils.Log($"Total number of Steam Libraries found: {SteamUniverses.Count}");
|
|
|
|
|
|
|
|
|
|
// Default path in the Steam folder isn't in the configs
|
|
|
|
|
if(Directory.Exists(Path.Combine(SteamPath, "steamapps")))
|
|
|
|
|
SteamUniverses.Add(Path.Combine(SteamPath, "steamapps"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override bool LoadAllGames()
|
|
|
|
|
{
|
|
|
|
|
if(SteamUniverses == null)
|
|
|
|
|
LoadUniverses();
|
|
|
|
|
|
|
|
|
|
if (SteamUniverses.Count == 0)
|
|
|
|
|
{
|
2020-01-13 21:11:07 +00:00
|
|
|
|
Utils.Log("Could not find any Steam Libraries");
|
2020-01-03 17:03:09 +00:00
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-03 17:22:30 +00:00
|
|
|
|
if(Games == null)
|
|
|
|
|
Games = new List<AStoreGame>();
|
|
|
|
|
|
2020-01-03 17:03:09 +00:00
|
|
|
|
SteamUniverses.Do(u =>
|
|
|
|
|
{
|
2020-01-04 10:16:32 +00:00
|
|
|
|
Utils.Log($"Searching for Steam Games in {u}");
|
|
|
|
|
|
2020-01-03 17:03:09 +00:00
|
|
|
|
Directory.EnumerateFiles(u, "*.acf", SearchOption.TopDirectoryOnly).Where(File.Exists).Do(f =>
|
|
|
|
|
{
|
|
|
|
|
var game = new SteamGame();
|
2020-01-04 02:49:22 +00:00
|
|
|
|
var gotID = false;
|
2020-01-03 17:03:09 +00:00
|
|
|
|
|
|
|
|
|
File.ReadAllLines(f).Do(l =>
|
|
|
|
|
{
|
2020-01-05 00:36:43 +00:00
|
|
|
|
if (l.ContainsCaseInsensitive("\"appid\""))
|
2020-01-03 17:03:09 +00:00
|
|
|
|
{
|
|
|
|
|
if (!int.TryParse(GetVdfValue(l), out var id))
|
|
|
|
|
return;
|
|
|
|
|
game.ID = id;
|
2020-01-04 02:49:22 +00:00
|
|
|
|
gotID = true;
|
2020-01-03 17:03:09 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-01-05 00:36:43 +00:00
|
|
|
|
if (l.ContainsCaseInsensitive("\"name\""))
|
2020-01-03 17:03:09 +00:00
|
|
|
|
game.Name = GetVdfValue(l);
|
|
|
|
|
|
2020-01-05 00:36:43 +00:00
|
|
|
|
if (!l.ContainsCaseInsensitive("\"installdir\""))
|
2020-01-04 10:16:32 +00:00
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
var path = Path.Combine(u, "common", GetVdfValue(l));
|
|
|
|
|
if (Directory.Exists(path))
|
2020-03-25 22:30:43 +00:00
|
|
|
|
game.Path = (AbsolutePath)path;
|
2020-01-03 17:03:09 +00:00
|
|
|
|
});
|
|
|
|
|
|
2020-03-25 22:30:43 +00:00
|
|
|
|
if (!gotID || !game.Path.IsDirectory) return;
|
2020-01-03 17:03:09 +00:00
|
|
|
|
|
2020-01-04 02:27:13 +00:00
|
|
|
|
var gameMeta = GameRegistry.Games.Values.FirstOrDefault(g =>
|
|
|
|
|
{
|
2020-01-04 02:49:22 +00:00
|
|
|
|
return (g.SteamIDs?.Contains(game.ID) ?? false)
|
2020-03-25 22:30:43 +00:00
|
|
|
|
&& (g.RequiredFiles?.TrueForAll(file => game.Path.Combine(file).Exists) ?? true);
|
2020-01-04 02:27:13 +00:00
|
|
|
|
});
|
2020-01-03 17:03:09 +00:00
|
|
|
|
|
|
|
|
|
if (gameMeta == null)
|
2020-01-04 10:16:32 +00:00
|
|
|
|
{
|
2020-01-13 21:11:07 +00:00
|
|
|
|
Utils.Log($"Steam Game \"{game.Name}\" ({game.ID}) is not supported, skipping");
|
2020-01-03 17:03:09 +00:00
|
|
|
|
return;
|
2020-01-04 10:16:32 +00:00
|
|
|
|
}
|
2020-01-03 17:03:09 +00:00
|
|
|
|
|
|
|
|
|
game.Game = gameMeta.Game;
|
|
|
|
|
game.Universe = u;
|
|
|
|
|
|
2020-01-13 21:11:07 +00:00
|
|
|
|
Utils.Log($"Found Steam Game: \"{game.Name}\" ({game.ID}) at {game.Path}");
|
2020-01-03 17:03:09 +00:00
|
|
|
|
|
|
|
|
|
LoadWorkshopItems(game);
|
|
|
|
|
|
|
|
|
|
Games.Add(game);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
Utils.Log($"Total number of Steam Games found: {Games.Count}");
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-04 10:16:32 +00:00
|
|
|
|
private static void LoadWorkshopItems(SteamGame game)
|
2020-01-03 17:03:09 +00:00
|
|
|
|
{
|
|
|
|
|
if(game.WorkshopItems == null)
|
|
|
|
|
game.WorkshopItems = new List<SteamWorkshopItem>();
|
|
|
|
|
|
|
|
|
|
var workshop = Path.Combine(game.Universe, "workshop");
|
|
|
|
|
if (!Directory.Exists(workshop))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
Directory.EnumerateFiles(workshop, "*.acf", SearchOption.TopDirectoryOnly).Where(File.Exists).Do(f =>
|
|
|
|
|
{
|
|
|
|
|
if (Path.GetFileName(f) != $"appworkshop{game.ID}.acf")
|
|
|
|
|
return;
|
|
|
|
|
|
2020-01-13 21:11:07 +00:00
|
|
|
|
Utils.Log($"Found Steam Workshop item file {f} for \"{game.Name}\"");
|
2020-01-03 17:03:09 +00:00
|
|
|
|
|
|
|
|
|
var lines = File.ReadAllLines(f);
|
|
|
|
|
var end = false;
|
|
|
|
|
var foundAppID = false;
|
|
|
|
|
var workshopItemsInstalled = 0;
|
|
|
|
|
var workshopItemDetails = 0;
|
|
|
|
|
var bracketStart = 0;
|
|
|
|
|
var bracketEnd = 0;
|
|
|
|
|
|
|
|
|
|
var currentItem = new SteamWorkshopItem();
|
|
|
|
|
|
|
|
|
|
lines.Do(l =>
|
|
|
|
|
{
|
|
|
|
|
if (end)
|
|
|
|
|
return;
|
|
|
|
|
if(currentItem == null)
|
|
|
|
|
currentItem = new SteamWorkshopItem();
|
|
|
|
|
|
|
|
|
|
var currentLine = lines.IndexOf(l);
|
2020-01-05 00:36:43 +00:00
|
|
|
|
if (l.ContainsCaseInsensitive("\"appid\"") && !foundAppID)
|
2020-01-03 17:03:09 +00:00
|
|
|
|
{
|
|
|
|
|
if (!int.TryParse(GetVdfValue(l), out var appID))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
foundAppID = true;
|
|
|
|
|
|
|
|
|
|
if (appID != game.ID)
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!foundAppID)
|
|
|
|
|
return;
|
|
|
|
|
|
2020-01-05 00:36:43 +00:00
|
|
|
|
if (l.ContainsCaseInsensitive("\"SizeOnDisk\""))
|
2020-01-03 17:03:09 +00:00
|
|
|
|
{
|
|
|
|
|
if (!int.TryParse(GetVdfValue(l), out var sizeOnDisk))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
game.WorkshopItemsSize += sizeOnDisk;
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-05 00:36:43 +00:00
|
|
|
|
if (l.ContainsCaseInsensitive("\"WorkshopItemsInstalled\""))
|
2020-01-03 17:03:09 +00:00
|
|
|
|
workshopItemsInstalled = currentLine;
|
|
|
|
|
|
2020-01-05 00:36:43 +00:00
|
|
|
|
if (l.ContainsCaseInsensitive("\"WorkshopItemDetails\""))
|
2020-01-03 17:03:09 +00:00
|
|
|
|
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);
|
|
|
|
|
|
2020-01-13 21:11:07 +00:00
|
|
|
|
Utils.Log($"Found Steam Workshop item {currentItem.ItemID}");
|
2020-01-03 17:03:09 +00:00
|
|
|
|
|
|
|
|
|
currentItem = null;
|
|
|
|
|
end = true;
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static string GetVdfValue(string line)
|
|
|
|
|
{
|
|
|
|
|
var trim = line.Trim('\t').Replace("\t", "");
|
|
|
|
|
string[] s = trim.Split('\"');
|
|
|
|
|
return s[3].Replace("\\\\", "\\");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static string GetSingleVdfValue(string line)
|
|
|
|
|
{
|
|
|
|
|
var trim = line.Trim('\t').Replace("\t", "");
|
|
|
|
|
return trim.Split('\"')[1];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|