/* Copyright (c) 2017 Rick (rick 'at' gibbed 'dot' us) * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would * be appreciated but is not required. * * 2. Altered source versions must be plainly marked as such, and must not * be misrepresented as being the original software. * * 3. This notice may not be removed or altered from any source * distribution. */ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Drawing; using System.Globalization; using System.IO; using System.Linq; using System.Net; using System.Windows.Forms; using System.Xml.XPath; using APITypes = SAM.API.Types; namespace SAM.Picker { internal partial class GamePicker : Form { private readonly API.Client _SteamClient; private readonly Dictionary _Games; private readonly List _FilteredGames; private int _SelectedGameIndex; private readonly List _LogosAttempted; private readonly ConcurrentQueue _LogoQueue; // ReSharper disable PrivateFieldCanBeConvertedToLocalVariable private readonly API.Callbacks.AppDataChanged _AppDataChangedCallback; // ReSharper restore PrivateFieldCanBeConvertedToLocalVariable public GamePicker(API.Client client) { this._Games = new Dictionary(); this._FilteredGames = new List(); this._SelectedGameIndex = -1; this._LogosAttempted = new List(); this._LogoQueue = new ConcurrentQueue(); this.InitializeComponent(); var blank = new Bitmap(this._LogoImageList.ImageSize.Width, this._LogoImageList.ImageSize.Height); using (var g = Graphics.FromImage(blank)) { g.Clear(Color.DimGray); } this._LogoImageList.Images.Add("Blank", blank); this._SteamClient = client; this._AppDataChangedCallback = client.CreateAndRegisterCallback(); this._AppDataChangedCallback.OnRun += this.OnAppDataChanged; this.AddGames(); } private void OnAppDataChanged(APITypes.AppDataChanged param) { if (param.Result == true && this._Games.ContainsKey(param.Id)) { var game = this._Games[param.Id]; game.Name = this._SteamClient.SteamApps001.GetAppData(game.Id, "name"); this.AddGameToLogoQueue(game); } } private void DoDownloadList(object sender, DoWorkEventArgs e) { var pairs = new List>(); byte[] bytes; using (var downloader = new WebClient()) { bytes = downloader.DownloadData(new Uri(string.Format("http://gib.me/sam/games.xml"))); } using (var stream = new MemoryStream(bytes, false)) { var document = new XPathDocument(stream); var navigator = document.CreateNavigator(); var nodes = navigator.Select("/games/game"); while (nodes.MoveNext()) { string type = nodes.Current.GetAttribute("type", ""); if (type == string.Empty) { type = "normal"; } pairs.Add(new KeyValuePair((uint)nodes.Current.ValueAsLong, type)); } } e.Result = pairs; } private void OnDownloadList(object sender, RunWorkerCompletedEventArgs e) { if (e.Error == null && e.Cancelled == false) { var pairs = (List>)e.Result; foreach (var kv in pairs) { this.AddGame(kv.Key, kv.Value); } } else { this.AddDefaultGames(); //MessageBox.Show(e.ToString(), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); } this.RefreshGames(); this._RefreshGamesButton.Enabled = true; this.DownloadNextLogo(); } private void RefreshGames() { this._FilteredGames.Clear(); foreach (var info in this._Games.Values.OrderBy(gi => gi.Name)) { if (info.Type == "normal" && _FilterGamesMenuItem.Checked == false) { continue; } if (info.Type == "demo" && this._FilterDemosMenuItem.Checked == false) { continue; } if (info.Type == "mod" && this._FilterModsMenuItem.Checked == false) { continue; } if (info.Type == "junk" && this._FilterJunkMenuItem.Checked == false) { continue; } this._FilteredGames.Add(info); } this._GameListView.BeginUpdate(); this._GameListView.VirtualListSize = this._FilteredGames.Count; if (this._FilteredGames.Count > 0) { this._GameListView.RedrawItems(0, this._FilteredGames.Count - 1, true); } this._GameListView.EndUpdate(); this._PickerStatusLabel.Text = string.Format( "Displaying {0} games. Total {1} games.", this._GameListView.Items.Count, this._Games.Count); } private void OnGameListViewRetrieveVirtualItem(object sender, RetrieveVirtualItemEventArgs e) { var info = this._FilteredGames[e.ItemIndex]; e.Item = new ListViewItem() { Text = info.Name, ImageIndex = info.ImageIndex, }; } private void DoDownloadLogo(object sender, DoWorkEventArgs e) { var info = (GameInfo)e.Argument; var logoPath = string.Format( "http://media.steamcommunity.com/steamcommunity/public/images/apps/{0}/{1}.jpg", info.Id, info.Logo); using (var downloader = new WebClient()) { var data = downloader.DownloadData(new Uri(logoPath)); try { using (var stream = new MemoryStream(data, false)) { var bitmap = new Bitmap(stream); e.Result = new LogoInfo(info.Id, bitmap); } } catch (Exception) { e.Result = new LogoInfo(info.Id, null); } } } private void OnDownloadLogo(object sender, RunWorkerCompletedEventArgs e) { if (e.Error != null || e.Cancelled == true) { return; } var logoInfo = (LogoInfo)e.Result; if (logoInfo.Bitmap != null && this._Games.TryGetValue(logoInfo.Id, out var gameInfo)) { this._GameListView.BeginUpdate(); var imageIndex = this._LogoImageList.Images.Count; this._LogoImageList.Images.Add(gameInfo.Logo, logoInfo.Bitmap); gameInfo.ImageIndex = imageIndex; this._GameListView.EndUpdate(); } this.DownloadNextLogo(); } private void DownloadNextLogo() { if (this._LogoWorker.IsBusy == true) { return; } GameInfo info; if (this._LogoQueue.TryDequeue(out info) == false) { this._DownloadStatusLabel.Visible = false; return; } this._DownloadStatusLabel.Text = string.Format( "Downloading {0} game icons...", this._LogoQueue.Count); this._DownloadStatusLabel.Visible = true; this._LogoWorker.RunWorkerAsync(info); } private void AddGameToLogoQueue(GameInfo info) { string logo = this._SteamClient.SteamApps001.GetAppData(info.Id, "logo"); if (logo == null) { return; } info.Logo = logo; int imageIndex = this._LogoImageList.Images.IndexOfKey(logo); if (imageIndex >= 0) { info.ImageIndex = imageIndex; } else if (this._LogosAttempted.Contains(logo) == false) { this._LogosAttempted.Add(logo); this._LogoQueue.Enqueue(info); this.DownloadNextLogo(); } } private bool OwnsGame(uint id) { return this._SteamClient.SteamApps003.IsSubscribedApp(id); } private void AddGame(uint id, string type) { if (this._Games.ContainsKey(id)) { return; } if (this.OwnsGame(id) == false) { return; } var info = new GameInfo(id, type); info.Name = this._SteamClient.SteamApps001.GetAppData(info.Id, "name"); this._Games.Add(id, info); this.AddGameToLogoQueue(info); } private void AddGames() { this._Games.Clear(); this._RefreshGamesButton.Enabled = false; this._ListWorker.RunWorkerAsync(); } private void AddDefaultGames() { this.AddGame(480, "normal"); // Spacewar } private void OnTimer(object sender, EventArgs e) { this._CallbackTimer.Enabled = false; this._SteamClient.RunCallbacks(false); this._CallbackTimer.Enabled = true; } private void OnSelectGame(object sender, ListViewItemSelectionChangedEventArgs e) { if (e.IsSelected == true && e.ItemIndex != this._SelectedGameIndex) { this._SelectedGameIndex = e.ItemIndex; } else if (e.IsSelected == true && e.ItemIndex == this._SelectedGameIndex) { this._SelectedGameIndex = -1; } } private void OnActivateGame(object sender, EventArgs e) { if (this._SelectedGameIndex < 0) { return; } var index = this._SelectedGameIndex; if (index < 0 || index >= this._FilteredGames.Count) { return; } var info = this._FilteredGames[index]; if (info == null) { return; } try { Process.Start("SAM.Game.exe", info.Id.ToString(CultureInfo.InvariantCulture)); } catch (Win32Exception) { MessageBox.Show( this, "Failed to start SAM.Game.exe.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); } } private void OnRefresh(object sender, EventArgs e) { this._AddGameTextBox.Text = ""; this.AddGames(); } private void OnAddGame(object sender, EventArgs e) { uint id; if (uint.TryParse(this._AddGameTextBox.Text, out id) == false) { MessageBox.Show( this, "Please enter a valid game ID.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } if (this.OwnsGame(id) == false) { MessageBox.Show(this, "You don't own that game.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } this._AddGameTextBox.Text = ""; this._Games.Clear(); this.AddGame(id, "normal"); this._FilterGamesMenuItem.Checked = true; this.RefreshGames(); this.DownloadNextLogo(); } private void OnFilterUpdate(object sender, EventArgs e) { this.RefreshGames(); } } }