diff --git a/App.config b/App.config index 193aecc6..3916e0e4 100644 --- a/App.config +++ b/App.config @@ -1,6 +1,6 @@ - - - - - + + + + + \ No newline at end of file diff --git a/DepotDownloaderGUI.csproj b/DepotDownloaderGUI.csproj index 162ed103..e1d68cf6 100644 --- a/DepotDownloaderGUI.csproj +++ b/DepotDownloaderGUI.csproj @@ -36,6 +36,7 @@ + diff --git a/DepotDownloaderGUI.sln b/DepotDownloaderGUI.sln index 2ce557eb..8fa8e3b9 100644 --- a/DepotDownloaderGUI.sln +++ b/DepotDownloaderGUI.sln @@ -1,25 +1,25 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.30907.101 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DepotDownloaderGUI", "DepotDownloaderGUI.csproj", "{9FE2AF44-2D88-43DB-9B8F-EA410552D8CE}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {9FE2AF44-2D88-43DB-9B8F-EA410552D8CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9FE2AF44-2D88-43DB-9B8F-EA410552D8CE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9FE2AF44-2D88-43DB-9B8F-EA410552D8CE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9FE2AF44-2D88-43DB-9B8F-EA410552D8CE}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {B49312C0-32E2-42A5-9DC8-892A707C0EF8} - EndGlobalSection -EndGlobal + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30907.101 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DepotDownloaderGUI", "DepotDownloaderGUI.csproj", "{9FE2AF44-2D88-43DB-9B8F-EA410552D8CE}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {9FE2AF44-2D88-43DB-9B8F-EA410552D8CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9FE2AF44-2D88-43DB-9B8F-EA410552D8CE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9FE2AF44-2D88-43DB-9B8F-EA410552D8CE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9FE2AF44-2D88-43DB-9B8F-EA410552D8CE}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {B49312C0-32E2-42A5-9DC8-892A707C0EF8} + EndGlobalSection +EndGlobal diff --git a/Form1.Designer.cs b/Form1.Designer.cs index c34f5d35..7a3a0b32 100644 --- a/Form1.Designer.cs +++ b/Form1.Designer.cs @@ -29,247 +29,249 @@ namespace DepotDownloaderGUI /// private void InitializeComponent() { - this.components = new System.ComponentModel.Container(); - System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(Form1)); - this.label1 = new System.Windows.Forms.Label(); - this.label2 = new System.Windows.Forms.Label(); - this.label3 = new System.Windows.Forms.Label(); - this.label4 = new System.Windows.Forms.Label(); - this.label5 = new System.Windows.Forms.Label(); - this.label6 = new System.Windows.Forms.Label(); - this.label7 = new System.Windows.Forms.Label(); - this.label8 = new System.Windows.Forms.Label(); - this.textBox1 = new System.Windows.Forms.TextBox(); - this.textBox2 = new System.Windows.Forms.TextBox(); - this.textBox3 = new System.Windows.Forms.TextBox(); - this.textBox4 = new System.Windows.Forms.TextBox(); - this.textBox5 = new System.Windows.Forms.TextBox(); - this.textBox8 = new System.Windows.Forms.TextBox(); - this.button1 = new System.Windows.Forms.Button(); - this.numericUpDown1 = new System.Windows.Forms.NumericUpDown(); - this.numericUpDown2 = new System.Windows.Forms.NumericUpDown(); - this.button2 = new System.Windows.Forms.Button(); - this.button3 = new System.Windows.Forms.Button(); - this.button4 = new System.Windows.Forms.Button(); - this.label9 = new System.Windows.Forms.Label(); - this.label10 = new System.Windows.Forms.Label(); - this.toolTip1 = new System.Windows.Forms.ToolTip(this.components); - ((System.ComponentModel.ISupportInitialize)(this.numericUpDown1)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.numericUpDown2)).BeginInit(); - this.SuspendLayout(); - // - // label1 - // - resources.ApplyResources(this.label1, "label1"); - this.label1.Name = "label1"; - this.toolTip1.SetToolTip(this.label1, resources.GetString("label1.ToolTip")); - // - // label2 - // - resources.ApplyResources(this.label2, "label2"); - this.label2.Name = "label2"; - this.toolTip1.SetToolTip(this.label2, resources.GetString("label2.ToolTip")); - // - // label3 - // - resources.ApplyResources(this.label3, "label3"); - this.label3.Name = "label3"; - this.toolTip1.SetToolTip(this.label3, resources.GetString("label3.ToolTip")); - // - // label4 - // - resources.ApplyResources(this.label4, "label4"); - this.label4.Name = "label4"; - this.toolTip1.SetToolTip(this.label4, resources.GetString("label4.ToolTip")); - // - // label5 - // - resources.ApplyResources(this.label5, "label5"); - this.label5.Name = "label5"; - this.toolTip1.SetToolTip(this.label5, resources.GetString("label5.ToolTip")); - // - // label6 - // - resources.ApplyResources(this.label6, "label6"); - this.label6.Name = "label6"; - this.toolTip1.SetToolTip(this.label6, resources.GetString("label6.ToolTip")); - // - // label7 - // - resources.ApplyResources(this.label7, "label7"); - this.label7.Name = "label7"; - this.toolTip1.SetToolTip(this.label7, resources.GetString("label7.ToolTip")); - // - // label8 - // - resources.ApplyResources(this.label8, "label8"); - this.label8.Name = "label8"; - this.toolTip1.SetToolTip(this.label8, resources.GetString("label8.ToolTip")); - // - // textBox1 - // - resources.ApplyResources(this.textBox1, "textBox1"); - this.textBox1.Name = "textBox1"; - // - // textBox2 - // - resources.ApplyResources(this.textBox2, "textBox2"); - this.textBox2.Name = "textBox2"; - // - // textBox3 - // - resources.ApplyResources(this.textBox3, "textBox3"); - this.textBox3.Name = "textBox3"; - // - // textBox4 - // - resources.ApplyResources(this.textBox4, "textBox4"); - this.textBox4.Name = "textBox4"; - // - // textBox5 - // - resources.ApplyResources(this.textBox5, "textBox5"); - this.textBox5.Name = "textBox5"; - // - // textBox8 - // - resources.ApplyResources(this.textBox8, "textBox8"); - this.textBox8.Name = "textBox8"; - // - // button1 - // - resources.ApplyResources(this.button1, "button1"); - this.button1.Name = "button1"; - this.toolTip1.SetToolTip(this.button1, resources.GetString("button1.ToolTip")); - this.button1.UseVisualStyleBackColor = true; - this.button1.Click += new System.EventHandler(this.button1_Click); - // - // numericUpDown1 - // - resources.ApplyResources(this.numericUpDown1, "numericUpDown1"); - this.numericUpDown1.Increment = new decimal(new int[] { - 5, - 0, - 0, - 0}); - this.numericUpDown1.Maximum = new decimal(new int[] { - 1000, - 0, - 0, - 0}); - this.numericUpDown1.Minimum = new decimal(new int[] { - 10, - 0, - 0, - 0}); - this.numericUpDown1.Name = "numericUpDown1"; - this.numericUpDown1.Value = new decimal(new int[] { - 10, - 0, - 0, - 0}); - // - // numericUpDown2 - // - resources.ApplyResources(this.numericUpDown2, "numericUpDown2"); - this.numericUpDown2.Increment = new decimal(new int[] { - 5, - 0, - 0, - 0}); - this.numericUpDown2.Maximum = new decimal(new int[] { - 1000, - 0, - 0, - 0}); - this.numericUpDown2.Minimum = new decimal(new int[] { - 10, - 0, - 0, - 0}); - this.numericUpDown2.Name = "numericUpDown2"; - this.numericUpDown2.Value = new decimal(new int[] { - 10, - 0, - 0, - 0}); - // - // button2 - // - resources.ApplyResources(this.button2, "button2"); - this.button2.Name = "button2"; - this.button2.UseVisualStyleBackColor = true; - this.button2.Click += new System.EventHandler(this.button2_Click); - // - // button3 - // - resources.ApplyResources(this.button3, "button3"); - this.button3.Name = "button3"; - this.toolTip1.SetToolTip(this.button3, resources.GetString("button3.ToolTip")); - this.button3.UseVisualStyleBackColor = true; - this.button3.Click += new System.EventHandler(this.button3_Click); - // - // button4 - // - resources.ApplyResources(this.button4, "button4"); - this.button4.Name = "button4"; - this.toolTip1.SetToolTip(this.button4, resources.GetString("button4.ToolTip")); - this.button4.UseVisualStyleBackColor = true; - this.button4.Click += new System.EventHandler(this.button4_Click); - // - // label9 - // - resources.ApplyResources(this.label9, "label9"); - this.label9.Name = "label9"; - // - // label10 - // - resources.ApplyResources(this.label10, "label10"); - this.label10.Name = "label10"; - this.toolTip1.SetToolTip(this.label10, resources.GetString("label10.ToolTip")); - // - // toolTip1 - // - this.toolTip1.AutomaticDelay = 0; - this.toolTip1.AutoPopDelay = 0; - this.toolTip1.InitialDelay = 0; - this.toolTip1.IsBalloon = true; - this.toolTip1.ReshowDelay = 600; - this.toolTip1.ToolTipIcon = System.Windows.Forms.ToolTipIcon.Info; - this.toolTip1.ToolTipTitle = "Info"; - // - // Form1 - // - resources.ApplyResources(this, "$this"); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.Controls.Add(this.label10); - this.Controls.Add(this.label9); - this.Controls.Add(this.button4); - this.Controls.Add(this.button3); - this.Controls.Add(this.button2); - this.Controls.Add(this.numericUpDown2); - this.Controls.Add(this.numericUpDown1); - this.Controls.Add(this.button1); - this.Controls.Add(this.textBox8); - this.Controls.Add(this.textBox5); - this.Controls.Add(this.textBox4); - this.Controls.Add(this.textBox3); - this.Controls.Add(this.textBox2); - this.Controls.Add(this.textBox1); - this.Controls.Add(this.label8); - this.Controls.Add(this.label7); - this.Controls.Add(this.label6); - this.Controls.Add(this.label5); - this.Controls.Add(this.label4); - this.Controls.Add(this.label3); - this.Controls.Add(this.label2); - this.Controls.Add(this.label1); - this.Name = "Form1"; - ((System.ComponentModel.ISupportInitialize)(this.numericUpDown1)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.numericUpDown2)).EndInit(); - this.ResumeLayout(false); - this.PerformLayout(); - + this.components = new System.ComponentModel.Container(); + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(Form1)); + this.label1 = new System.Windows.Forms.Label(); + this.label2 = new System.Windows.Forms.Label(); + this.label3 = new System.Windows.Forms.Label(); + this.label4 = new System.Windows.Forms.Label(); + this.label5 = new System.Windows.Forms.Label(); + this.label6 = new System.Windows.Forms.Label(); + this.label7 = new System.Windows.Forms.Label(); + this.label8 = new System.Windows.Forms.Label(); + this.textBox1 = new System.Windows.Forms.TextBox(); + this.textBox2 = new System.Windows.Forms.TextBox(); + this.textBox3 = new System.Windows.Forms.TextBox(); + this.textBox4 = new System.Windows.Forms.TextBox(); + this.textBox5 = new System.Windows.Forms.TextBox(); + this.textBox8 = new System.Windows.Forms.TextBox(); + this.button1 = new System.Windows.Forms.Button(); + this.numericUpDown1 = new System.Windows.Forms.NumericUpDown(); + this.numericUpDown2 = new System.Windows.Forms.NumericUpDown(); + this.button2 = new System.Windows.Forms.Button(); + this.button3 = new System.Windows.Forms.Button(); + this.button4 = new System.Windows.Forms.Button(); + this.label9 = new System.Windows.Forms.Label(); + this.label10 = new System.Windows.Forms.Label(); + this.toolTip1 = new System.Windows.Forms.ToolTip(this.components); + ((System.ComponentModel.ISupportInitialize)(this.numericUpDown1)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.numericUpDown2)).BeginInit(); + this.SuspendLayout(); + // + // label1 + // + resources.ApplyResources(this.label1, "label1"); + this.label1.Name = "label1"; + this.toolTip1.SetToolTip(this.label1, resources.GetString("label1.ToolTip")); + // + // label2 + // + resources.ApplyResources(this.label2, "label2"); + this.label2.Name = "label2"; + this.toolTip1.SetToolTip(this.label2, resources.GetString("label2.ToolTip")); + // + // label3 + // + resources.ApplyResources(this.label3, "label3"); + this.label3.Name = "label3"; + this.toolTip1.SetToolTip(this.label3, resources.GetString("label3.ToolTip")); + // + // label4 + // + resources.ApplyResources(this.label4, "label4"); + this.label4.Name = "label4"; + this.toolTip1.SetToolTip(this.label4, resources.GetString("label4.ToolTip")); + // + // label5 + // + resources.ApplyResources(this.label5, "label5"); + this.label5.Name = "label5"; + this.toolTip1.SetToolTip(this.label5, resources.GetString("label5.ToolTip")); + // + // label6 + // + resources.ApplyResources(this.label6, "label6"); + this.label6.Name = "label6"; + this.toolTip1.SetToolTip(this.label6, resources.GetString("label6.ToolTip")); + // + // label7 + // + resources.ApplyResources(this.label7, "label7"); + this.label7.Name = "label7"; + this.toolTip1.SetToolTip(this.label7, resources.GetString("label7.ToolTip")); + // + // label8 + // + resources.ApplyResources(this.label8, "label8"); + this.label8.Name = "label8"; + this.toolTip1.SetToolTip(this.label8, resources.GetString("label8.ToolTip")); + // + // textBox1 + // + resources.ApplyResources(this.textBox1, "textBox1"); + this.textBox1.Name = "textBox1"; + // + // textBox2 + // + resources.ApplyResources(this.textBox2, "textBox2"); + this.textBox2.Name = "textBox2"; + // + // textBox3 + // + resources.ApplyResources(this.textBox3, "textBox3"); + this.textBox3.Name = "textBox3"; + // + // textBox4 + // + resources.ApplyResources(this.textBox4, "textBox4"); + this.textBox4.Name = "textBox4"; + // + // textBox5 + // + resources.ApplyResources(this.textBox5, "textBox5"); + this.textBox5.Name = "textBox5"; + // + // textBox8 + // + resources.ApplyResources(this.textBox8, "textBox8"); + this.textBox8.Name = "textBox8"; + // + // button1 + // + resources.ApplyResources(this.button1, "button1"); + this.button1.Name = "button1"; + this.toolTip1.SetToolTip(this.button1, resources.GetString("button1.ToolTip")); + this.button1.UseVisualStyleBackColor = true; + this.button1.Click += new System.EventHandler(this.button1_Click); + // + // numericUpDown1 + // + resources.ApplyResources(this.numericUpDown1, "numericUpDown1"); + this.numericUpDown1.Increment = new decimal(new int[] { + 5, + 0, + 0, + 0}); + this.numericUpDown1.Maximum = new decimal(new int[] { + 1000, + 0, + 0, + 0}); + this.numericUpDown1.Minimum = new decimal(new int[] { + 10, + 0, + 0, + 0}); + this.numericUpDown1.Name = "numericUpDown1"; + this.numericUpDown1.Value = new decimal(new int[] { + 10, + 0, + 0, + 0}); + // + // numericUpDown2 + // + resources.ApplyResources(this.numericUpDown2, "numericUpDown2"); + this.numericUpDown2.Increment = new decimal(new int[] { + 5, + 0, + 0, + 0}); + this.numericUpDown2.Maximum = new decimal(new int[] { + 1000, + 0, + 0, + 0}); + this.numericUpDown2.Minimum = new decimal(new int[] { + 10, + 0, + 0, + 0}); + this.numericUpDown2.Name = "numericUpDown2"; + this.numericUpDown2.Value = new decimal(new int[] { + 10, + 0, + 0, + 0}); + // + // button2 + // + resources.ApplyResources(this.button2, "button2"); + this.button2.Name = "button2"; + this.button2.UseVisualStyleBackColor = true; + this.button2.Click += new System.EventHandler(this.button2_Click); + // + // button3 + // + resources.ApplyResources(this.button3, "button3"); + this.button3.Name = "button3"; + this.toolTip1.SetToolTip(this.button3, resources.GetString("button3.ToolTip")); + this.button3.UseVisualStyleBackColor = true; + this.button3.Click += new System.EventHandler(this.button3_Click); + // + // button4 + // + resources.ApplyResources(this.button4, "button4"); + this.button4.Name = "button4"; + this.toolTip1.SetToolTip(this.button4, resources.GetString("button4.ToolTip")); + this.button4.UseVisualStyleBackColor = true; + this.button4.Click += new System.EventHandler(this.button4_Click); + // + // label9 + // + resources.ApplyResources(this.label9, "label9"); + this.label9.Name = "label9"; + // + // label10 + // + resources.ApplyResources(this.label10, "label10"); + this.label10.Name = "label10"; + this.toolTip1.SetToolTip(this.label10, resources.GetString("label10.ToolTip")); + // + // toolTip1 + // + this.toolTip1.AutomaticDelay = 0; + this.toolTip1.AutoPopDelay = 0; + this.toolTip1.InitialDelay = 0; + this.toolTip1.IsBalloon = true; + this.toolTip1.ReshowDelay = 600; + this.toolTip1.ToolTipIcon = System.Windows.Forms.ToolTipIcon.Info; + this.toolTip1.ToolTipTitle = "Info"; + // + // Form1 + // + resources.ApplyResources(this, "$this"); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.Controls.Add(this.label10); + this.Controls.Add(this.label9); + this.Controls.Add(this.button4); + this.Controls.Add(this.button3); + this.Controls.Add(this.button2); + this.Controls.Add(this.numericUpDown2); + this.Controls.Add(this.numericUpDown1); + this.Controls.Add(this.button1); + this.Controls.Add(this.textBox8); + this.Controls.Add(this.textBox5); + this.Controls.Add(this.textBox4); + this.Controls.Add(this.textBox3); + this.Controls.Add(this.textBox2); + this.Controls.Add(this.textBox1); + this.Controls.Add(this.label8); + this.Controls.Add(this.label7); + this.Controls.Add(this.label6); + this.Controls.Add(this.label5); + this.Controls.Add(this.label4); + this.Controls.Add(this.label3); + this.Controls.Add(this.label2); + this.Controls.Add(this.label1); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle; + this.MaximizeBox = false; + this.Name = "Form1"; + ((System.ComponentModel.ISupportInitialize)(this.numericUpDown1)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.numericUpDown2)).EndInit(); + this.ResumeLayout(false); + this.PerformLayout(); + } #endregion diff --git a/Form1.cs b/Form1.cs index 7a45e274..822bdb09 100644 --- a/Form1.cs +++ b/Form1.cs @@ -1,71 +1,102 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Data; -using System.Drawing; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Windows.Forms; -using System.IO; -using System.Drawing.Text; - -namespace DepotDownloaderGUI -{ - public partial class Form1 : Form - { - [System.Runtime.InteropServices.DllImport("gdi32.dll")] - private static extern IntPtr AddFontMemResourceEx(IntPtr pbFont, uint cbFont, - IntPtr pdv, [System.Runtime.InteropServices.In] ref uint pcFonts); - - private PrivateFontCollection fonts = new PrivateFontCollection(); - - Font Poppins; - - string Command; - public Form1() - { - InitializeComponent(); - byte[] fontData = Properties.Resources.Poppins_Medium; - IntPtr fontPtr = System.Runtime.InteropServices.Marshal.AllocCoTaskMem(fontData.Length); - System.Runtime.InteropServices.Marshal.Copy(fontData, 0, fontPtr, fontData.Length); - uint dummy = 0; - fonts.AddMemoryFont(fontPtr, Properties.Resources.Poppins_Medium.Length); - AddFontMemResourceEx(fontPtr, (uint)Properties.Resources.Poppins_Medium.Length, IntPtr.Zero, ref dummy); - System.Runtime.InteropServices.Marshal.FreeCoTaskMem(fontPtr); - Poppins = new Font(fonts.Families[0], 18.0F); - Directory.SetCurrentDirectory("./depotdownloader/"); - textBox2.PasswordChar = '*'; - label9.Font = Poppins; - FormBorderStyle = FormBorderStyle.FixedSingle; - MaximizeBox = false; - } - - - private void button1_Click(object sender, EventArgs e) - { - if (textBox2.Text == "") - Command = $"/k dotnet DepotDownloader.dll -app {textBox3.Text} -depot {textBox4.Text} -manifest {textBox5.Text} -max-servers {numericUpDown1.Value} -max-downloads {numericUpDown2.Value} -dir ../YourGame {textBox8.Text}"; - else - Command = $"/k dotnet DepotDownloader.dll -app {textBox3.Text} -depot {textBox4.Text} -manifest {textBox5.Text} -username {textBox1.Text} -password {textBox2.Text} -max-servers {numericUpDown1.Value} -max-downloads {numericUpDown2.Value} -dir ../YourGame {textBox8.Text}"; - System.Diagnostics.Process.Start("cmd.exe", Command); - } - - - private void button2_Click(object sender, EventArgs e) - { - System.Diagnostics.Process.Start("https://github.com/mmvanheusden/SteamDepotDownloaderGUI/discussions/5"); - } - - private void button3_Click(object sender, EventArgs e) - { - System.Diagnostics.Process.Start("https://steamdb.info/instantsearch/"); - } - - private void button4_Click(object sender, EventArgs e) - { - System.Diagnostics.Process.Start("https://github.com/mmvanheusden/SteamDepotDownloaderGUI"); - } - - } -} +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; +using System.IO; +using System.Drawing.Text; +using System.Net; +using System.IO.Compression; + +namespace DepotDownloaderGUI +{ + public partial class Form1 : Form + { + [System.Runtime.InteropServices.DllImport("gdi32.dll")] + private static extern IntPtr AddFontMemResourceEx(IntPtr pbFont, uint cbFont, + IntPtr pdv, [System.Runtime.InteropServices.In] ref uint pcFonts); + + private PrivateFontCollection fonts = new PrivateFontCollection(); + + Font Poppins; + + string Command; + public Form1() + { + InitializeComponent(); + if (!Directory.Exists("DepotDownloader")) + { + //Download + string Download = "https://github.com/SteamRE/DepotDownloader/releases/download/DepotDownloader_2.4.3/depotdownloader-2.4.3.zip"; + string zipname = "depotdownloader-2.4.3.zip"; + string extractPath = "DepotDownloader"; + WebClient myWebClient = new WebClient(); + myWebClient.DownloadFile(Download, zipname); + //Extract + ZipFile.ExtractToDirectory(zipname, extractPath); + //Delete + File.Delete(zipname); + } + byte[] fontData = Properties.Resources.Poppins_Medium; + IntPtr fontPtr = System.Runtime.InteropServices.Marshal.AllocCoTaskMem(fontData.Length); + System.Runtime.InteropServices.Marshal.Copy(fontData, 0, fontPtr, fontData.Length); + uint dummy = 0; + fonts.AddMemoryFont(fontPtr, Properties.Resources.Poppins_Medium.Length); + AddFontMemResourceEx(fontPtr, (uint)Properties.Resources.Poppins_Medium.Length, IntPtr.Zero, ref dummy); + System.Runtime.InteropServices.Marshal.FreeCoTaskMem(fontPtr); + Poppins = new Font(fonts.Families[0], 18.0F); + Directory.SetCurrentDirectory("./DepotDownloader/"); + //textBox2.PasswordChar = '*'; -Added into Designer + label9.Font = Poppins; + //FormBorderStyle = FormBorderStyle.FixedSingle; -Added into Designer + //MaximizeBox = false; -Added into Designer + } + + + private void button1_Click(object sender, EventArgs e) + { + FolderBrowserDialog folderDlg = new FolderBrowserDialog(); + DialogResult result = folderDlg.ShowDialog(); + if (result == DialogResult.OK & result != DialogResult.Cancel) + { + string selectedpath = folderDlg.SelectedPath; + if (textBox2.Text.Length <= 0) + { + //Todo Remember Password tick + //Command = $"/k dotnet DepotDownloader.dll -app {textBox3.Text} -depot {textBox4.Text} -manifest {textBox5.Text} -max-servers {numericUpDown1.Value} -max-downloads {numericUpDown2.Value} -dir ../YourGame {textBox8.Text}"; + Command = $"/k dotnet DepotDownloader.dll -app {textBox3.Text} -depot {textBox4.Text} -manifest {textBox5.Text} -max-servers {numericUpDown1.Value} -max-downloads {numericUpDown2.Value} -dir " + '"' + selectedpath + '"' + " " + textBox8.Text; + } + else + { + Command = $"/k dotnet DepotDownloader.dll -app {textBox3.Text} -depot {textBox4.Text} -manifest {textBox5.Text} -username {textBox1.Text} -password {textBox2.Text} -max-servers {numericUpDown1.Value} -max-downloads {numericUpDown2.Value} -dir " + '"' + selectedpath + '"' + " " + textBox8.Text; + } + + System.Diagnostics.Process.Start("cmd.exe", Command); + } + else + { + return; + } + } + + + private void button2_Click(object sender, EventArgs e) + { + System.Diagnostics.Process.Start("https://github.com/mmvanheusden/SteamDepotDownloaderGUI/discussions/5"); + } + + private void button3_Click(object sender, EventArgs e) + { + System.Diagnostics.Process.Start("https://steamdb.info/instantsearch/"); + } + + private void button4_Click(object sender, EventArgs e) + { + System.Diagnostics.Process.Start("https://github.com/mmvanheusden/SteamDepotDownloaderGUI"); + } + } +} diff --git a/Form1.resx b/Form1.resx index 7a3f3993..a48d7a30 100644 --- a/Form1.resx +++ b/Form1.resx @@ -1,787 +1,790 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - - True - - - - Segoe UI, 12.75pt - - - 12, 44 - - - 87, 23 - - - 0 - - - Username - - - 17, 17 - - - Your Steam Username - - - label1 - - - System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - $this - - - 21 - - - True - - - Segoe UI, 12.75pt - - - 12, 72 - - - 80, 23 - - - 1 - - - Password - - - Your Steam Password - - - label2 - - - System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - $this - - - 20 - - - True - - - Segoe UI, 12.75pt - - - 12, 100 - - - 63, 23 - - - 2 - - - App ID - - - The app's unique ID, which can be found on the SteamDB website. - - - label3 - - - System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - $this - - - 19 - - - True - - - Segoe UI, 12.75pt - - - 11, 127 - - - 79, 23 - - - 3 - - - Depot ID - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + True + + + + Segoe UI, 12.75pt + + + 12, 45 + + + 87, 23 + + + 0 + + + Username + + + 17, 17 + + + Your Steam Username + + + label1 + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 21 + + + True + + + Segoe UI, 12.75pt + + + 12, 75 + + + 80, 23 + + + 1 + + + Password + + + Your Steam Password + + + label2 + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 20 + + + True + + + Segoe UI, 12.75pt + + + 12, 105 + + + 63, 23 + + + 2 + + + App ID + + + The app's unique ID, which can be found on the SteamDB website. + + + label3 + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 19 + + + True + + + Segoe UI, 12.75pt + + + 12, 135 + + + 79, 23 + + + 3 + + + Depot ID + + The app's unique Depot ID, which can be found on the -SteamDB website at 'Depots' in the sidebar. - - - label4 - - - System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - $this - - - 18 - - - True - - - Segoe UI, 12.75pt - - - 11, 158 - - - 97, 23 - - - 4 - - - Manifest ID - - +SteamDB website at 'Depots' in the sidebar. + + + label4 + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 18 + + + True + + + Segoe UI, 12.75pt + + + 12, 165 + + + 97, 23 + + + 4 + + + Manifest ID + + The app's unique Manifest ID, which can be found on the -SteamDB website at 'Manifests' in the sidebar. - - - label5 - - - System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - $this - - - 17 - - - True - - - Segoe UI, 12.75pt - - - 11, 192 - - - 101, 23 - - - 5 - - - Max Servers - - +SteamDB website at 'Manifests' in the sidebar. + + + label5 + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 17 + + + True + + + Segoe UI, 12.75pt + + + 12, 195 + + + 101, 23 + + + 5 + + + Max Servers + + Maximum number of content servers to use. -Speeds up download drastically - - - label6 - - - System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - $this - - - 16 - - - True - - - Segoe UI, 12.75pt - - - 12, 223 - - - 103, 23 - - - 6 - - - Max Chunks - - +Speeds up download drastically + + + label6 + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 16 + + + True + + + Segoe UI, 12.75pt + + + 12, 225 + + + 103, 23 + + + 6 + + + Max Chunks + + Maximum number of chunks to download concurrently. -Speeds up download drastically - - - label7 - - - System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - $this - - - 15 - - - True - - - Segoe UI, 12.75pt - - - 11, 249 - - - 165, 23 - - - 7 - - - Optional Arguments - - +Speeds up download drastically + + + label7 + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 15 + + + True + + + Segoe UI, 12.75pt + + + 12, 255 + + + 165, 23 + + + 7 + + + Optional Arguments + + Optional Parameters. -For advanced users - - - label8 - - - System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - $this - - - 14 - - - Segoe UI, 12.75pt - - - 117, 44 - - - 209, 30 - - - 1 - - - textBox1 - - - System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - $this - - - 13 - - - Segoe UI, 12.75pt - - - 117, 73 - - - 209, 30 - - - 2 - - - textBox2 - - - System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - $this - - - 12 - - - Segoe UI, 12.75pt - - - 117, 101 - - - 209, 30 - - - 3 - - - textBox3 - - - System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - $this - - - 11 - - - Segoe UI, 12.75pt - - - 117, 130 - - - 209, 30 - - - 4 - - - textBox4 - - - System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - $this - - - 10 - - - Segoe UI, 12.75pt - - - 117, 159 - - - 209, 30 - - - 5 - - - textBox5 - - - System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - $this - - - 9 - - - Segoe UI, 12.75pt - - - 182, 249 - - - 144, 30 - - - 8 - - - textBox8 - - - System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - $this - - - 8 - - - Segoe UI Semibold, 9.75pt, style=Bold - - - 209, 307 - - - 117, 25 - - - 11 - - - Start Download - - - Starts the download - - - button1 - - - System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - $this - - - 7 - - - Segoe UI, 12.75pt - - - 117, 188 - - - 209, 30 - - - 6 - - - numericUpDown1 - - - System.Windows.Forms.NumericUpDown, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - $this - - - 6 - - - Segoe UI, 12.75pt - - - 117, 216 - - - 209, 30 - - - 7 - - - numericUpDown2 - - - System.Windows.Forms.NumericUpDown, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - $this - - - 5 - - - Segoe UI Semibold, 9.75pt, style=Bold - - - 209, 280 - - - 117, 23 - - - 9 - - - Need Help? - - - button2 - - - System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - $this - - - 4 - - - Segoe UI Semibold, 9.75pt, style=Bold - - - 12, 280 - - - 164, 26 - - - 8 - - - SteamDB Instant Search - - - Brings you to the SteamDB Instant game search page. - - - button3 - - - System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - $this - - - 3 - - - Segoe UI Semibold, 9.75pt, style=Bold - - - 12, 309 - - - 164, 23 - - - 10 - - - GitHub Repo - - - Brings you to the GitHub Repository page. - - - button4 - - - System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - $this - - - 2 - - - True - - - Segoe UI Semibold, 18pt, style=Bold - - - 7, -1 - - - 296, 32 - - - 13 - - - Steam Depot Downloader - - - label9 - - - System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - $this - - - 1 - - - True - - - 200, 335 - - - 134, 13 - - - 14 - - - Star this project on GitHub! - - - Don't forget!! - - - label10 - - - System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - $this - - - 0 - - - True - - - 6, 13 - - - 334, 351 - - - Steam Depot Downloader - - - toolTip1 - - - System.Windows.Forms.ToolTip, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - Form1 - - - System.Windows.Forms.Form, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - +For advanced users + + + label8 + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 14 + + + Segoe UI, 12.75pt + + + 120, 45 + + + 210, 30 + + + 1 + + + textBox1 + + + System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 13 + + + Segoe UI, 12.75pt + + + 120, 75 + + + * + + + 210, 30 + + + 2 + + + textBox2 + + + System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 12 + + + Segoe UI, 12.75pt + + + 120, 105 + + + 210, 30 + + + 3 + + + textBox3 + + + System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 11 + + + Segoe UI, 12.75pt + + + 120, 135 + + + 210, 30 + + + 4 + + + textBox4 + + + System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 10 + + + Segoe UI, 12.75pt + + + 120, 165 + + + 210, 30 + + + 5 + + + textBox5 + + + System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 9 + + + Segoe UI, 12.75pt + + + 180, 255 + + + 150, 30 + + + 8 + + + textBox8 + + + System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 8 + + + Segoe UI Semibold, 9.75pt, style=Bold + + + 210, 320 + + + 120, 25 + + + 11 + + + Start Download + + + Starts the download + + + button1 + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 7 + + + Segoe UI, 12.75pt + + + 120, 195 + + + 210, 30 + + + 6 + + + numericUpDown1 + + + System.Windows.Forms.NumericUpDown, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 6 + + + Segoe UI, 12.75pt + + + 120, 225 + + + 210, 30 + + + 7 + + + numericUpDown2 + + + System.Windows.Forms.NumericUpDown, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 5 + + + Segoe UI Semibold, 9.75pt, style=Bold + + + 210, 290 + + + 120, 25 + + + 9 + + + Need Help? + + + button2 + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 4 + + + Segoe UI Semibold, 9.75pt, style=Bold + + + 12, 290 + + + 165, 25 + + + 8 + + + SteamDB Instant Search + + + Brings you to the SteamDB Instant game search page. + + + button3 + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 3 + + + Segoe UI Semibold, 9.75pt, style=Bold + + + 12, 320 + + + 165, 25 + + + 10 + + + GitHub Repo + + + Brings you to the GitHub Repository page. + + + button4 + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 2 + + + True + + + Segoe UI Semibold, 18pt, style=Bold + + + 7, 0 + + + 296, 32 + + + 13 + + + Steam Depot Downloader + + + label9 + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 1 + + + True + + + 195, 350 + + + 134, 13 + + + 14 + + + Star this project on GitHub! + + + Don't forget!! + + + label10 + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 0 + + + True + + + 6, 13 + + + 334, 361 + + + Steam Depot Downloader + + + toolTip1 + + + System.Windows.Forms.ToolTip, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Form1 + + + System.Windows.Forms.Form, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + \ No newline at end of file diff --git a/LICENSE.md b/LICENSE.md index f288702d..3877ae0a 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,674 +1,674 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Copyright (C) - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/Program.cs b/Program.cs index 7fd30769..17e58a4b 100644 --- a/Program.cs +++ b/Program.cs @@ -1,22 +1,22 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using System.Windows.Forms; - -namespace DepotDownloaderGUI -{ - static class Program - { - /// - /// The main entry point for the application. - /// - [STAThread] - static void Main() - { - Application.EnableVisualStyles(); - Application.SetCompatibleTextRenderingDefault(false); - Application.Run(new Form1()); - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace DepotDownloaderGUI +{ + static class Program + { + /// + /// The main entry point for the application. + /// + [STAThread] + static void Main() + { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.Run(new Form1()); + } + } +} diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs index 6fa48596..8c32b627 100644 --- a/Properties/AssemblyInfo.cs +++ b/Properties/AssemblyInfo.cs @@ -1,36 +1,36 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("DepotDownloaderGUI")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("DepotDownloaderGUI")] -[assembly: AssemblyCopyright("Copyright © 2021")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("9fe2af44-2d88-43db-9b8f-ea410552d8ce")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("DepotDownloaderGUI")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("DepotDownloaderGUI")] +[assembly: AssemblyCopyright("Copyright © 2021")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("9fe2af44-2d88-43db-9b8f-ea410552d8ce")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Properties/Resources.Designer.cs b/Properties/Resources.Designer.cs index c25f5a63..93334ab7 100644 --- a/Properties/Resources.Designer.cs +++ b/Properties/Resources.Designer.cs @@ -1,73 +1,73 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace DepotDownloaderGUI.Properties { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Resources { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Resources() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("DepotDownloaderGUI.Properties.Resources", typeof(Resources).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized resource of type System.Byte[]. - /// - internal static byte[] Poppins_Medium { - get { - object obj = ResourceManager.GetObject("Poppins_Medium", resourceCulture); - return ((byte[])(obj)); - } - } - } -} +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace DepotDownloaderGUI.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("DepotDownloaderGUI.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized resource of type System.Byte[]. + /// + internal static byte[] Poppins_Medium { + get { + object obj = ResourceManager.GetObject("Poppins_Medium", resourceCulture); + return ((byte[])(obj)); + } + } + } +} diff --git a/Properties/Resources.resx b/Properties/Resources.resx index 982dce15..1e2118dd 100644 --- a/Properties/Resources.resx +++ b/Properties/Resources.resx @@ -1,124 +1,124 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - - ..\Resources\Poppins-Medium.ttf;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + ..\Resources\Poppins-Medium.ttf;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + \ No newline at end of file diff --git a/Properties/Settings.Designer.cs b/Properties/Settings.Designer.cs index 5173fb0d..31199b88 100644 --- a/Properties/Settings.Designer.cs +++ b/Properties/Settings.Designer.cs @@ -1,29 +1,29 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - - -namespace DepotDownloaderGUI.Properties -{ - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] - internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase - { - - private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); - - public static Settings Default - { - get - { - return defaultInstance; - } - } - } -} +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + + +namespace DepotDownloaderGUI.Properties +{ + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase + { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default + { + get + { + return defaultInstance; + } + } + } +} diff --git a/Properties/Settings.settings b/Properties/Settings.settings index 39645652..abf36c5d 100644 --- a/Properties/Settings.settings +++ b/Properties/Settings.settings @@ -1,7 +1,7 @@ - - - - - - - + + + + + + + diff --git a/README.md b/README.md index b68112c5..79968b4d 100644 --- a/README.md +++ b/README.md @@ -1,79 +1,79 @@ - - -Banner - - - -

- - GitHub all releases - GitHub release (latest by date including pre-releases) - GitHub last commit - - - Visitor Count -

- -# SteamDepotDownloaderGUI - -A simple GUI tool based on [**DepotDownloader**][depotdownloader] for downloading older versions of Steam games. - -![The Program](https://raw.githubusercontent.com/mmvanheusden/SteamDepotDownloaderGUI/master/src/readme.md/hero.png "The Program") - -## What can you do with the program? -You can download older versions of Steam games and software :sunglasses: - -## Want an example? -Take a look at [**This**][subnauticawiki] example. - -## YouTube Tutorial - -YouTube Tutorial - - -## How use the program? - -### step 1: -Download [**.NET Core 2.0**][dotnet] It is required for the program to work properly. -### step 2: -Download and unzip the program. (You can download it [**Here**][latest]) -### step 3: -Run DepotDownloaderGUI.exe. -### step 4: -First enter your Steam credentials at "Username" and "Password" (Don't worry, it's safe!) -### step 5: -Click on "SteamDB Instant Search". -### step 6: -Enter your game of choice at the search bar and select the game. -### step 7: -Copy the App ID and paste it in the program at "App ID" -### step 8: -Click on "Depots" in the sidebar. -### step 9: -Select the Windows depot (usually it is Win32) -### step 10: -Copy the Depot ID and paste it in the program at "Depot ID" -### step 11: -Click on "Manifests" in the sidebar -### step 12: -Choose a Manifest(game version) of choice and copy its ID and paste it in the program at "Manifest ID" -### step 13: -If you want a faster download speed, increase the max servers and max chunks. -### step 14: -Click on "Start Download", a terminal will pop up and will show the download progress. -### step 15: -Once the download is done, close the program. -The downloaded game is stored in the folder "YourGame" -> :warning: **Once the download progress is complete, Move the downloaded files to a different folder, Or else your next game download will mix with your old download!** -### Enjoy your game! - -## Need help? -Just place your cursor on a text label or button and a help balloon will appear! -### If you have any questions, use the shiny new ✨GitHub Discussions✨ page! - - -[latest]: https://github.com/mmvanheusden/SteamDepotDownloaderGUI/releases/latest -[steamdb]: https://steamdb.info/ -[depotdownloader]: https://github.com/SteamRE/DepotDownloader -[subnauticawiki]: https://github.com/mmvanheusden/SteamDepotDownloaderGUI/wiki/How-to-Download-older-versions-of-Subnautica -[dotnet]: https://dotnet.microsoft.com/download/dotnet-core/thank-you/runtime-2.0.9-windows-x64-installer + + +Banner + + + +

+ + GitHub all releases + GitHub release (latest by date including pre-releases) + GitHub last commit + + + Visitor Count +

+ +# SteamDepotDownloaderGUI + +A simple GUI tool based on [**DepotDownloader**][depotdownloader] for downloading older versions of Steam games. + +![The Program](https://raw.githubusercontent.com/mmvanheusden/SteamDepotDownloaderGUI/master/src/readme.md/hero.png "The Program") + +## What can you do with the program? +You can download older versions of Steam games and software :sunglasses: + +## Want an example? +Take a look at [**This**][subnauticawiki] example. + +## YouTube Tutorial + +YouTube Tutorial + + +## How use the program? + +### step 1: +Download [**.NET Core 2.0**][dotnet] It is required for the program to work properly. +### step 2: +Download and unzip the program. (You can download it [**Here**][latest]) +### step 3: +Run DepotDownloaderGUI.exe. +### step 4: +First enter your Steam credentials at "Username" and "Password" (Don't worry, it's safe!) +### step 5: +Click on "SteamDB Instant Search". +### step 6: +Enter your game of choice at the search bar and select the game. +### step 7: +Copy the App ID and paste it in the program at "App ID" +### step 8: +Click on "Depots" in the sidebar. +### step 9: +Select the Windows depot (usually it is Win32) +### step 10: +Copy the Depot ID and paste it in the program at "Depot ID" +### step 11: +Click on "Manifests" in the sidebar +### step 12: +Choose a Manifest(game version) of choice and copy its ID and paste it in the program at "Manifest ID" +### step 13: +If you want a faster download speed, increase the max servers and max chunks. +### step 14: +Click on "Start Download", a terminal will pop up and will show the download progress. +### step 15: +Once the download is done, close the program. +The downloaded game is stored in the folder "YourGame" +> :warning: **Once the download progress is complete, Move the downloaded files to a different folder, Or else your next game download will mix with your old download!** +### Enjoy your game! + +## Need help? +Just place your cursor on a text label or button and a help balloon will appear! +### If you have any questions, use the shiny new ✨GitHub Discussions✨ page! + + +[latest]: https://github.com/mmvanheusden/SteamDepotDownloaderGUI/releases/latest +[steamdb]: https://steamdb.info/ +[depotdownloader]: https://github.com/SteamRE/DepotDownloader +[subnauticawiki]: https://github.com/mmvanheusden/SteamDepotDownloaderGUI/wiki/How-to-Download-older-versions-of-Subnautica +[dotnet]: https://dotnet.microsoft.com/download/dotnet-core/thank-you/runtime-2.0.9-windows-x64-installer diff --git a/depotdownloader/DepotDownloader.sln b/depotdownloader/DepotDownloader.sln index 326e6207..631e75ec 100644 --- a/depotdownloader/DepotDownloader.sln +++ b/depotdownloader/DepotDownloader.sln @@ -1,22 +1,22 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.26228.4 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DepotDownloader", "DepotDownloader\DepotDownloader.csproj", "{39159C47-ACD3-449F-96CA-4F30C8ED147A}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {39159C47-ACD3-449F-96CA-4F30C8ED147A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {39159C47-ACD3-449F-96CA-4F30C8ED147A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {39159C47-ACD3-449F-96CA-4F30C8ED147A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {39159C47-ACD3-449F-96CA-4F30C8ED147A}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26228.4 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DepotDownloader", "DepotDownloader\DepotDownloader.csproj", "{39159C47-ACD3-449F-96CA-4F30C8ED147A}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {39159C47-ACD3-449F-96CA-4F30C8ED147A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {39159C47-ACD3-449F-96CA-4F30C8ED147A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {39159C47-ACD3-449F-96CA-4F30C8ED147A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {39159C47-ACD3-449F-96CA-4F30C8ED147A}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/depotdownloader/DepotDownloader/AccountSettingsStore.cs b/depotdownloader/DepotDownloader/AccountSettingsStore.cs index 2a8c9836..5ff111f7 100644 --- a/depotdownloader/DepotDownloader/AccountSettingsStore.cs +++ b/depotdownloader/DepotDownloader/AccountSettingsStore.cs @@ -1,90 +1,90 @@ -using System; -using System.Collections.Generic; -using ProtoBuf; -using System.IO; -using System.IO.Compression; -using System.IO.IsolatedStorage; -using System.Linq; -using SteamKit2; -using SteamKit2.Discovery; - -namespace DepotDownloader -{ - [ProtoContract] - class AccountSettingsStore - { - [ProtoMember(1, IsRequired=false)] - public Dictionary SentryData { get; private set; } - - [ProtoMember(2, IsRequired = false)] - public System.Collections.Concurrent.ConcurrentDictionary ContentServerPenalty { get; private set; } - - [ProtoMember(3, IsRequired = false)] - public Dictionary LoginKeys { get; private set; } - - string FileName = null; - - AccountSettingsStore() - { - SentryData = new Dictionary(); - ContentServerPenalty = new System.Collections.Concurrent.ConcurrentDictionary(); - LoginKeys = new Dictionary(); - } - - static bool Loaded - { - get { return Instance != null; } - } - - public static AccountSettingsStore Instance = null; - static readonly IsolatedStorageFile IsolatedStorage = IsolatedStorageFile.GetUserStoreForAssembly(); - - public static void LoadFromFile(string filename) - { - if (Loaded) - throw new Exception("Config already loaded"); - - if (IsolatedStorage.FileExists(filename)) - { - try - { - using (var fs = IsolatedStorage.OpenFile(filename, FileMode.Open, FileAccess.Read)) - using (DeflateStream ds = new DeflateStream(fs, CompressionMode.Decompress)) - { - Instance = ProtoBuf.Serializer.Deserialize(ds); - } - } - catch (IOException ex) - { - Console.WriteLine("Failed to load account settings: {0}", ex.Message); - Instance = new AccountSettingsStore(); - } - } - else - { - Instance = new AccountSettingsStore(); - } - - Instance.FileName = filename; - } - - public static void Save() - { - if (!Loaded) - throw new Exception("Saved config before loading"); - - try - { - using (var fs = IsolatedStorage.OpenFile(Instance.FileName, FileMode.Create, FileAccess.Write)) - using (DeflateStream ds = new DeflateStream(fs, CompressionMode.Compress)) - { - ProtoBuf.Serializer.Serialize(ds, Instance); - } - } - catch (IOException ex) - { - Console.WriteLine("Failed to save account settings: {0}", ex.Message); - } - } - } -} +using System; +using System.Collections.Generic; +using ProtoBuf; +using System.IO; +using System.IO.Compression; +using System.IO.IsolatedStorage; +using System.Linq; +using SteamKit2; +using SteamKit2.Discovery; + +namespace DepotDownloader +{ + [ProtoContract] + class AccountSettingsStore + { + [ProtoMember(1, IsRequired=false)] + public Dictionary SentryData { get; private set; } + + [ProtoMember(2, IsRequired = false)] + public System.Collections.Concurrent.ConcurrentDictionary ContentServerPenalty { get; private set; } + + [ProtoMember(3, IsRequired = false)] + public Dictionary LoginKeys { get; private set; } + + string FileName = null; + + AccountSettingsStore() + { + SentryData = new Dictionary(); + ContentServerPenalty = new System.Collections.Concurrent.ConcurrentDictionary(); + LoginKeys = new Dictionary(); + } + + static bool Loaded + { + get { return Instance != null; } + } + + public static AccountSettingsStore Instance = null; + static readonly IsolatedStorageFile IsolatedStorage = IsolatedStorageFile.GetUserStoreForAssembly(); + + public static void LoadFromFile(string filename) + { + if (Loaded) + throw new Exception("Config already loaded"); + + if (IsolatedStorage.FileExists(filename)) + { + try + { + using (var fs = IsolatedStorage.OpenFile(filename, FileMode.Open, FileAccess.Read)) + using (DeflateStream ds = new DeflateStream(fs, CompressionMode.Decompress)) + { + Instance = ProtoBuf.Serializer.Deserialize(ds); + } + } + catch (IOException ex) + { + Console.WriteLine("Failed to load account settings: {0}", ex.Message); + Instance = new AccountSettingsStore(); + } + } + else + { + Instance = new AccountSettingsStore(); + } + + Instance.FileName = filename; + } + + public static void Save() + { + if (!Loaded) + throw new Exception("Saved config before loading"); + + try + { + using (var fs = IsolatedStorage.OpenFile(Instance.FileName, FileMode.Create, FileAccess.Write)) + using (DeflateStream ds = new DeflateStream(fs, CompressionMode.Compress)) + { + ProtoBuf.Serializer.Serialize(ds, Instance); + } + } + catch (IOException ex) + { + Console.WriteLine("Failed to save account settings: {0}", ex.Message); + } + } + } +} diff --git a/depotdownloader/DepotDownloader/CDNClientPool.cs b/depotdownloader/DepotDownloader/CDNClientPool.cs index c468cdd0..49aa1d8b 100644 --- a/depotdownloader/DepotDownloader/CDNClientPool.cs +++ b/depotdownloader/DepotDownloader/CDNClientPool.cs @@ -1,196 +1,196 @@ -using SteamKit2; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Threading; -using System.Threading.Tasks; - -namespace DepotDownloader -{ - /// - /// CDNClientPool provides a pool of connections to CDN endpoints, requesting CDN tokens as needed - /// - class CDNClientPool - { - private const int ServerEndpointMinimumSize = 8; - - private readonly Steam3Session steamSession; - private readonly uint appId; - public CDNClient CDNClient { get; } -#if STEAMKIT_UNRELEASED - public CDNClient.Server ProxyServer { get; private set; } -#endif - - private readonly ConcurrentStack activeConnectionPool; - private readonly BlockingCollection availableServerEndpoints; - - private readonly AutoResetEvent populatePoolEvent; - private readonly Task monitorTask; - private readonly CancellationTokenSource shutdownToken; - public CancellationTokenSource ExhaustedToken { get; set; } - - public CDNClientPool(Steam3Session steamSession, uint appId) - { - this.steamSession = steamSession; - this.appId = appId; - CDNClient = new CDNClient(steamSession.steamClient); - - activeConnectionPool = new ConcurrentStack(); - availableServerEndpoints = new BlockingCollection(); - - populatePoolEvent = new AutoResetEvent(true); - shutdownToken = new CancellationTokenSource(); - - monitorTask = Task.Factory.StartNew(ConnectionPoolMonitorAsync).Unwrap(); - } - - public void Shutdown() - { - shutdownToken.Cancel(); - monitorTask.Wait(); - } - - private async Task> FetchBootstrapServerListAsync() - { - var backoffDelay = 0; - - while (!shutdownToken.IsCancellationRequested) - { - try - { - var cdnServers = await ContentServerDirectoryService.LoadAsync(this.steamSession.steamClient.Configuration, ContentDownloader.Config.CellID, shutdownToken.Token); - if (cdnServers != null) - { - return cdnServers; - } - } - catch (Exception ex) - { - Console.WriteLine("Failed to retrieve content server list: {0}", ex.Message); - - if (ex is SteamKitWebRequestException e && e.StatusCode == (HttpStatusCode)429) - { - // If we're being throttled, add a delay to the next request - backoffDelay = Math.Min(5, ++backoffDelay); - await Task.Delay(TimeSpan.FromSeconds(backoffDelay)); - } - } - } - - return null; - } - - private async Task ConnectionPoolMonitorAsync() - { - bool didPopulate = false; - - while (!shutdownToken.IsCancellationRequested) - { - populatePoolEvent.WaitOne(TimeSpan.FromSeconds(1)); - - // We want the Steam session so we can take the CellID from the session and pass it through to the ContentServer Directory Service - if (availableServerEndpoints.Count < ServerEndpointMinimumSize && steamSession.steamClient.IsConnected) - { - var servers = await FetchBootstrapServerListAsync().ConfigureAwait(false); - - if (servers == null || servers.Count == 0) - { - ExhaustedToken?.Cancel(); - return; - } - -#if STEAMKIT_UNRELEASED - ProxyServer = servers.Where(x => x.UseAsProxy).FirstOrDefault(); -#endif - - var weightedCdnServers = servers - .Where(server => - { -#if STEAMKIT_UNRELEASED - var isEligibleForApp = server.AllowedAppIds == null || server.AllowedAppIds.Contains(appId); - return isEligibleForApp && (server.Type == "SteamCache" || server.Type == "CDN"); -#else - return server.Type == "SteamCache" || server.Type == "CDN"; -#endif - }) - .Select(server => - { - AccountSettingsStore.Instance.ContentServerPenalty.TryGetValue(server.Host, out var penalty); - - return (server, penalty); - }) - .OrderBy(pair => pair.penalty).ThenBy(pair => pair.server.WeightedLoad); - - foreach (var (server, weight) in weightedCdnServers) - { - for (var i = 0; i < server.NumEntries; i++) - { - availableServerEndpoints.Add(server); - } - } - - didPopulate = true; - } - else if (availableServerEndpoints.Count == 0 && !steamSession.steamClient.IsConnected && didPopulate) - { - ExhaustedToken?.Cancel(); - return; - } - } - } - - private CDNClient.Server BuildConnection(CancellationToken token) - { - if (availableServerEndpoints.Count < ServerEndpointMinimumSize) - { - populatePoolEvent.Set(); - } - - return availableServerEndpoints.Take(token); - } - - public CDNClient.Server GetConnection(CancellationToken token) - { - if (!activeConnectionPool.TryPop(out var connection)) - { - connection = BuildConnection(token); - } - - return connection; - } - - public async Task AuthenticateConnection(uint appId, uint depotId, CDNClient.Server server) - { - var host = steamSession.ResolveCDNTopLevelHost(server.Host); - var cdnKey = $"{depotId:D}:{host}"; - - steamSession.RequestCDNAuthToken(appId, depotId, host, cdnKey); - - if (steamSession.CDNAuthTokens.TryGetValue(cdnKey, out var authTokenCallbackPromise)) - { - var result = await authTokenCallbackPromise.Task; - return result.Token; - } - else - { - throw new Exception($"Failed to retrieve CDN token for server {server.Host} depot {depotId}"); - } - } - - public void ReturnConnection(CDNClient.Server server) - { - if (server == null) return; - - activeConnectionPool.Push(server); - } - - public void ReturnBrokenConnection(CDNClient.Server server) - { - if (server == null) return; - - // Broken connections are not returned to the pool - } - } -} +using SteamKit2; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Threading; +using System.Threading.Tasks; + +namespace DepotDownloader +{ + /// + /// CDNClientPool provides a pool of connections to CDN endpoints, requesting CDN tokens as needed + /// + class CDNClientPool + { + private const int ServerEndpointMinimumSize = 8; + + private readonly Steam3Session steamSession; + private readonly uint appId; + public CDNClient CDNClient { get; } +#if STEAMKIT_UNRELEASED + public CDNClient.Server ProxyServer { get; private set; } +#endif + + private readonly ConcurrentStack activeConnectionPool; + private readonly BlockingCollection availableServerEndpoints; + + private readonly AutoResetEvent populatePoolEvent; + private readonly Task monitorTask; + private readonly CancellationTokenSource shutdownToken; + public CancellationTokenSource ExhaustedToken { get; set; } + + public CDNClientPool(Steam3Session steamSession, uint appId) + { + this.steamSession = steamSession; + this.appId = appId; + CDNClient = new CDNClient(steamSession.steamClient); + + activeConnectionPool = new ConcurrentStack(); + availableServerEndpoints = new BlockingCollection(); + + populatePoolEvent = new AutoResetEvent(true); + shutdownToken = new CancellationTokenSource(); + + monitorTask = Task.Factory.StartNew(ConnectionPoolMonitorAsync).Unwrap(); + } + + public void Shutdown() + { + shutdownToken.Cancel(); + monitorTask.Wait(); + } + + private async Task> FetchBootstrapServerListAsync() + { + var backoffDelay = 0; + + while (!shutdownToken.IsCancellationRequested) + { + try + { + var cdnServers = await ContentServerDirectoryService.LoadAsync(this.steamSession.steamClient.Configuration, ContentDownloader.Config.CellID, shutdownToken.Token); + if (cdnServers != null) + { + return cdnServers; + } + } + catch (Exception ex) + { + Console.WriteLine("Failed to retrieve content server list: {0}", ex.Message); + + if (ex is SteamKitWebRequestException e && e.StatusCode == (HttpStatusCode)429) + { + // If we're being throttled, add a delay to the next request + backoffDelay = Math.Min(5, ++backoffDelay); + await Task.Delay(TimeSpan.FromSeconds(backoffDelay)); + } + } + } + + return null; + } + + private async Task ConnectionPoolMonitorAsync() + { + bool didPopulate = false; + + while (!shutdownToken.IsCancellationRequested) + { + populatePoolEvent.WaitOne(TimeSpan.FromSeconds(1)); + + // We want the Steam session so we can take the CellID from the session and pass it through to the ContentServer Directory Service + if (availableServerEndpoints.Count < ServerEndpointMinimumSize && steamSession.steamClient.IsConnected) + { + var servers = await FetchBootstrapServerListAsync().ConfigureAwait(false); + + if (servers == null || servers.Count == 0) + { + ExhaustedToken?.Cancel(); + return; + } + +#if STEAMKIT_UNRELEASED + ProxyServer = servers.Where(x => x.UseAsProxy).FirstOrDefault(); +#endif + + var weightedCdnServers = servers + .Where(server => + { +#if STEAMKIT_UNRELEASED + var isEligibleForApp = server.AllowedAppIds == null || server.AllowedAppIds.Contains(appId); + return isEligibleForApp && (server.Type == "SteamCache" || server.Type == "CDN"); +#else + return server.Type == "SteamCache" || server.Type == "CDN"; +#endif + }) + .Select(server => + { + AccountSettingsStore.Instance.ContentServerPenalty.TryGetValue(server.Host, out var penalty); + + return (server, penalty); + }) + .OrderBy(pair => pair.penalty).ThenBy(pair => pair.server.WeightedLoad); + + foreach (var (server, weight) in weightedCdnServers) + { + for (var i = 0; i < server.NumEntries; i++) + { + availableServerEndpoints.Add(server); + } + } + + didPopulate = true; + } + else if (availableServerEndpoints.Count == 0 && !steamSession.steamClient.IsConnected && didPopulate) + { + ExhaustedToken?.Cancel(); + return; + } + } + } + + private CDNClient.Server BuildConnection(CancellationToken token) + { + if (availableServerEndpoints.Count < ServerEndpointMinimumSize) + { + populatePoolEvent.Set(); + } + + return availableServerEndpoints.Take(token); + } + + public CDNClient.Server GetConnection(CancellationToken token) + { + if (!activeConnectionPool.TryPop(out var connection)) + { + connection = BuildConnection(token); + } + + return connection; + } + + public async Task AuthenticateConnection(uint appId, uint depotId, CDNClient.Server server) + { + var host = steamSession.ResolveCDNTopLevelHost(server.Host); + var cdnKey = $"{depotId:D}:{host}"; + + steamSession.RequestCDNAuthToken(appId, depotId, host, cdnKey); + + if (steamSession.CDNAuthTokens.TryGetValue(cdnKey, out var authTokenCallbackPromise)) + { + var result = await authTokenCallbackPromise.Task; + return result.Token; + } + else + { + throw new Exception($"Failed to retrieve CDN token for server {server.Host} depot {depotId}"); + } + } + + public void ReturnConnection(CDNClient.Server server) + { + if (server == null) return; + + activeConnectionPool.Push(server); + } + + public void ReturnBrokenConnection(CDNClient.Server server) + { + if (server == null) return; + + // Broken connections are not returned to the pool + } + } +} diff --git a/depotdownloader/DepotDownloader/ContentDownloader.cs b/depotdownloader/DepotDownloader/ContentDownloader.cs index e48f6310..07884279 100644 --- a/depotdownloader/DepotDownloader/ContentDownloader.cs +++ b/depotdownloader/DepotDownloader/ContentDownloader.cs @@ -1,1296 +1,1296 @@ -using SteamKit2; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading; -using System.Threading.Tasks; - -namespace DepotDownloader -{ - public class ContentDownloaderException : System.Exception - { - public ContentDownloaderException( String value ) : base( value ) {} - } - - static class ContentDownloader - { - public const uint INVALID_APP_ID = uint.MaxValue; - public const uint INVALID_DEPOT_ID = uint.MaxValue; - public const ulong INVALID_MANIFEST_ID = ulong.MaxValue; - public const string DEFAULT_BRANCH = "Public"; - - public static DownloadConfig Config = new DownloadConfig(); - - private static Steam3Session steam3; - private static Steam3Session.Credentials steam3Credentials; - private static CDNClientPool cdnPool; - - private const string DEFAULT_DOWNLOAD_DIR = "depots"; - private const string CONFIG_DIR = ".DepotDownloader"; - private static readonly string STAGING_DIR = Path.Combine( CONFIG_DIR, "staging" ); - - private sealed class DepotDownloadInfo - { - public uint id { get; private set; } - public string installDir { get; private set; } - public string contentName { get; private set; } - - public ulong manifestId { get; private set; } - public byte[] depotKey; - - public DepotDownloadInfo( uint depotid, ulong manifestId, string installDir, string contentName ) - { - this.id = depotid; - this.manifestId = manifestId; - this.installDir = installDir; - this.contentName = contentName; - } - } - - static bool CreateDirectories( uint depotId, uint depotVersion, out string installDir ) - { - installDir = null; - try - { - if ( string.IsNullOrWhiteSpace( ContentDownloader.Config.InstallDirectory ) ) - { - Directory.CreateDirectory( DEFAULT_DOWNLOAD_DIR ); - - string depotPath = Path.Combine( DEFAULT_DOWNLOAD_DIR, depotId.ToString() ); - Directory.CreateDirectory( depotPath ); - - installDir = Path.Combine( depotPath, depotVersion.ToString() ); - Directory.CreateDirectory( installDir ); - - Directory.CreateDirectory( Path.Combine( installDir, CONFIG_DIR ) ); - Directory.CreateDirectory( Path.Combine( installDir, STAGING_DIR ) ); - } - else - { - Directory.CreateDirectory( ContentDownloader.Config.InstallDirectory ); - - installDir = ContentDownloader.Config.InstallDirectory; - - Directory.CreateDirectory( Path.Combine( installDir, CONFIG_DIR ) ); - Directory.CreateDirectory( Path.Combine( installDir, STAGING_DIR ) ); - } - } - catch - { - return false; - } - - return true; - } - - static bool TestIsFileIncluded( string filename ) - { - if ( !Config.UsingFileList ) - return true; - - foreach ( string fileListEntry in Config.FilesToDownload ) - { - if ( fileListEntry.Equals( filename, StringComparison.OrdinalIgnoreCase ) ) - return true; - } - - foreach ( Regex rgx in Config.FilesToDownloadRegex ) - { - Match m = rgx.Match( filename ); - - if ( m.Success ) - return true; - } - - return false; - } - - static bool AccountHasAccess( uint depotId ) - { - if ( steam3 == null || steam3.steamUser.SteamID == null || ( steam3.Licenses == null && steam3.steamUser.SteamID.AccountType != EAccountType.AnonUser ) ) - return false; - - IEnumerable licenseQuery; - if ( steam3.steamUser.SteamID.AccountType == EAccountType.AnonUser ) - { - licenseQuery = new List() { 17906 }; - } - else - { - licenseQuery = steam3.Licenses.Select( x => x.PackageID ).Distinct(); - } - - steam3.RequestPackageInfo( licenseQuery ); - - foreach ( var license in licenseQuery ) - { - SteamApps.PICSProductInfoCallback.PICSProductInfo package; - if ( steam3.PackageInfo.TryGetValue( license, out package ) && package != null ) - { - if ( package.KeyValues[ "appids" ].Children.Any( child => child.AsUnsignedInteger() == depotId ) ) - return true; - - if ( package.KeyValues[ "depotids" ].Children.Any( child => child.AsUnsignedInteger() == depotId ) ) - return true; - } - } - - return false; - } - - internal static KeyValue GetSteam3AppSection( uint appId, EAppInfoSection section ) - { - if ( steam3 == null || steam3.AppInfo == null ) - { - return null; - } - - SteamApps.PICSProductInfoCallback.PICSProductInfo app; - if ( !steam3.AppInfo.TryGetValue( appId, out app ) || app == null ) - { - return null; - } - - KeyValue appinfo = app.KeyValues; - string section_key; - - switch ( section ) - { - case EAppInfoSection.Common: - section_key = "common"; - break; - case EAppInfoSection.Extended: - section_key = "extended"; - break; - case EAppInfoSection.Config: - section_key = "config"; - break; - case EAppInfoSection.Depots: - section_key = "depots"; - break; - default: - throw new NotImplementedException(); - } - - KeyValue section_kv = appinfo.Children.Where( c => c.Name == section_key ).FirstOrDefault(); - return section_kv; - } - - static uint GetSteam3AppBuildNumber( uint appId, string branch ) - { - if ( appId == INVALID_APP_ID ) - return 0; - - - KeyValue depots = ContentDownloader.GetSteam3AppSection( appId, EAppInfoSection.Depots ); - KeyValue branches = depots[ "branches" ]; - KeyValue node = branches[ branch ]; - - if ( node == KeyValue.Invalid ) - return 0; - - KeyValue buildid = node[ "buildid" ]; - - if ( buildid == KeyValue.Invalid ) - return 0; - - return uint.Parse( buildid.Value ); - } - - static ulong GetSteam3DepotManifest( uint depotId, uint appId, string branch ) - { - KeyValue depots = GetSteam3AppSection( appId, EAppInfoSection.Depots ); - KeyValue depotChild = depots[ depotId.ToString() ]; - - if ( depotChild == KeyValue.Invalid ) - return INVALID_MANIFEST_ID; - - // Shared depots can either provide manifests, or leave you relying on their parent app. - // It seems that with the latter, "sharedinstall" will exist (and equals 2 in the one existance I know of). - // Rather than relay on the unknown sharedinstall key, just look for manifests. Test cases: 111710, 346680. - if ( depotChild[ "manifests" ] == KeyValue.Invalid && depotChild[ "depotfromapp" ] != KeyValue.Invalid ) - { - uint otherAppId = depotChild["depotfromapp"].AsUnsignedInteger(); - if ( otherAppId == appId ) - { - // This shouldn't ever happen, but ya never know with Valve. Don't infinite loop. - Console.WriteLine( "App {0}, Depot {1} has depotfromapp of {2}!", - appId, depotId, otherAppId ); - return INVALID_MANIFEST_ID; - } - - steam3.RequestAppInfo( otherAppId ); - - return GetSteam3DepotManifest( depotId, otherAppId, branch ); - } - - var manifests = depotChild[ "manifests" ]; - var manifests_encrypted = depotChild[ "encryptedmanifests" ]; - - if ( manifests.Children.Count == 0 && manifests_encrypted.Children.Count == 0 ) - return INVALID_MANIFEST_ID; - - var node = manifests[ branch ]; - - if ( branch != "Public" && node == KeyValue.Invalid ) - { - var node_encrypted = manifests_encrypted[ branch ]; - if ( node_encrypted != KeyValue.Invalid ) - { - string password = Config.BetaPassword; - if ( password == null ) - { - Console.Write( "Please enter the password for branch {0}: ", branch ); - Config.BetaPassword = password = Console.ReadLine(); - } - - var encrypted_v1 = node_encrypted[ "encrypted_gid" ]; - var encrypted_v2 = node_encrypted[ "encrypted_gid_2" ]; - - if ( encrypted_v1 != KeyValue.Invalid ) - { - byte[] input = Util.DecodeHexString( encrypted_v1.Value ); - byte[] manifest_bytes = CryptoHelper.VerifyAndDecryptPassword( input, password ); - - if ( manifest_bytes == null ) - { - Console.WriteLine( "Password was invalid for branch {0}", branch ); - return INVALID_MANIFEST_ID; - } - - return BitConverter.ToUInt64( manifest_bytes, 0 ); - } - else if ( encrypted_v2 != KeyValue.Invalid ) - { - // Submit the password to Steam now to get encryption keys - steam3.CheckAppBetaPassword( appId, Config.BetaPassword ); - - if ( !steam3.AppBetaPasswords.ContainsKey( branch ) ) - { - Console.WriteLine( "Password was invalid for branch {0}", branch ); - return INVALID_MANIFEST_ID; - } - - byte[] input = Util.DecodeHexString( encrypted_v2.Value ); - byte[] manifest_bytes; - try - { - manifest_bytes = CryptoHelper.SymmetricDecryptECB( input, steam3.AppBetaPasswords[ branch ] ); - } - catch ( Exception e ) - { - Console.WriteLine( "Failed to decrypt branch {0}: {1}", branch, e.Message ); - return INVALID_MANIFEST_ID; - } - - return BitConverter.ToUInt64( manifest_bytes, 0 ); - } - else - { - Console.WriteLine( "Unhandled depot encryption for depotId {0}", depotId ); - return INVALID_MANIFEST_ID; - } - - } - - return INVALID_MANIFEST_ID; - } - - if ( node.Value == null ) - return INVALID_MANIFEST_ID; - - return UInt64.Parse( node.Value ); - } - - static string GetAppOrDepotName( uint depotId, uint appId ) - { - if ( depotId == INVALID_DEPOT_ID ) - { - KeyValue info = GetSteam3AppSection( appId, EAppInfoSection.Common ); - - if ( info == null ) - return String.Empty; - - return info[ "name" ].AsString(); - } - else - { - KeyValue depots = GetSteam3AppSection( appId, EAppInfoSection.Depots ); - - if ( depots == null ) - return String.Empty; - - KeyValue depotChild = depots[ depotId.ToString() ]; - - if ( depotChild == null ) - return String.Empty; - - return depotChild[ "name" ].AsString(); - } - } - - public static bool InitializeSteam3( string username, string password ) - { - string loginKey = null; - - if ( username != null && Config.RememberPassword ) - { - _ = AccountSettingsStore.Instance.LoginKeys.TryGetValue( username, out loginKey ); - } - - steam3 = new Steam3Session( - new SteamUser.LogOnDetails() - { - Username = username, - Password = loginKey == null ? password : null, - ShouldRememberPassword = Config.RememberPassword, - LoginKey = loginKey, - LoginID = Config.LoginID ?? 0x534B32, // "SK2" - } - ); - - steam3Credentials = steam3.WaitForCredentials(); - - if ( !steam3Credentials.IsValid ) - { - Console.WriteLine( "Unable to get steam3 credentials." ); - return false; - } - - return true; - } - - public static void ShutdownSteam3() - { - if (cdnPool != null) - { - cdnPool.Shutdown(); - cdnPool = null; - } - - if ( steam3 == null ) - return; - - steam3.TryWaitForLoginKey(); - steam3.Disconnect(); - } - - public static async Task DownloadPubfileAsync( uint appId, ulong publishedFileId ) - { - var details = steam3.GetPublishedFileDetails( appId, publishedFileId ); - - if ( !string.IsNullOrEmpty( details?.file_url ) ) - { - await DownloadWebFile( appId, details.filename, details.file_url ); - } - else if ( details?.hcontent_file > 0 ) - { - await DownloadAppAsync( appId, new List<(uint, ulong)>() { ( appId, details.hcontent_file ) }, DEFAULT_BRANCH, null, null, null, false, true ); - } - else - { - Console.WriteLine( "Unable to locate manifest ID for published file {0}", publishedFileId ); - } - } - - public static async Task DownloadUGCAsync( uint appId, ulong ugcId ) - { - SteamCloud.UGCDetailsCallback details = null; - - if ( steam3.steamUser.SteamID.AccountType != EAccountType.AnonUser ) - { - steam3.GetUGCDetails( ugcId ); - } - else - { - Console.WriteLine( $"Unable to query UGC details for {ugcId} from an anonymous account" ); - } - - if ( !string.IsNullOrEmpty( details?.URL ) ) - { - await DownloadWebFile( appId, details.FileName, details.URL ); - } - else - { - await DownloadAppAsync( appId, new List<(uint, ulong)>() { ( appId, ugcId ) }, DEFAULT_BRANCH, null, null, null, false, true ); - } - } - - private static async Task DownloadWebFile( uint appId, string fileName, string url ) - { - string installDir; - if ( !CreateDirectories( appId, 0, out installDir ) ) - { - Console.WriteLine( "Error: Unable to create install directories!" ); - return; - } - - var stagingDir = Path.Combine( installDir, STAGING_DIR ); - var fileStagingPath = Path.Combine( stagingDir, fileName ); - var fileFinalPath = Path.Combine( installDir, fileName ); - - Directory.CreateDirectory( Path.GetDirectoryName( fileFinalPath ) ); - Directory.CreateDirectory( Path.GetDirectoryName( fileStagingPath ) ); - - using ( var file = File.OpenWrite( fileStagingPath ) ) - using ( var client = new HttpClient() ) - { - Console.WriteLine( "Downloading {0}", fileName ); - var responseStream = await client.GetStreamAsync( url ); - await responseStream.CopyToAsync( file ); - } - - if ( File.Exists( fileFinalPath ) ) - { - File.Delete( fileFinalPath ); - } - - File.Move( fileStagingPath, fileFinalPath ); - } - - public static async Task DownloadAppAsync( uint appId, List<(uint depotId, ulong manifestId)> depotManifestIds, string branch, string os, string arch, string language, bool lv, bool isUgc ) - { - cdnPool = new CDNClientPool(steam3, appId); - - // Load our configuration data containing the depots currently installed - string configPath = ContentDownloader.Config.InstallDirectory; - if (string.IsNullOrWhiteSpace(configPath)) - { - configPath = DEFAULT_DOWNLOAD_DIR; - } - - Directory.CreateDirectory(Path.Combine(configPath, CONFIG_DIR)); - DepotConfigStore.LoadFromFile(Path.Combine(configPath, CONFIG_DIR, "depot.config")); - - if ( steam3 != null ) - steam3.RequestAppInfo( appId ); - - if ( !AccountHasAccess( appId ) ) - { - if ( steam3.RequestFreeAppLicense( appId ) ) - { - Console.WriteLine( "Obtained FreeOnDemand license for app {0}", appId ); - - // Fetch app info again in case we didn't get it fully without a license. - steam3.RequestAppInfo( appId, true ); - } - else - { - string contentName = GetAppOrDepotName( INVALID_DEPOT_ID, appId ); - throw new ContentDownloaderException( String.Format( "App {0} ({1}) is not available from this account.", appId, contentName ) ); - } - } - - var hasSpecificDepots = depotManifestIds.Count > 0; - var depotIdsFound = new List(); - var depotIdsExpected = depotManifestIds.Select( x => x.Item1 ).ToList(); - KeyValue depots = GetSteam3AppSection( appId, EAppInfoSection.Depots ); - - if ( isUgc ) - { - var workshopDepot = depots["workshopdepot"].AsUnsignedInteger(); - if ( workshopDepot != 0 && !depotIdsExpected.Contains( workshopDepot ) ) - { - depotIdsExpected.Add( workshopDepot ); - depotManifestIds = depotManifestIds.Select( pair => ( workshopDepot, pair.manifestId ) ).ToList(); - } - - depotIdsFound.AddRange( depotIdsExpected ); - } - else - { - Console.WriteLine( "Using app branch: '{0}'.", branch ); - - if ( depots != null ) - { - foreach ( var depotSection in depots.Children ) - { - uint id = INVALID_DEPOT_ID; - if ( depotSection.Children.Count == 0 ) - continue; - - if ( !uint.TryParse( depotSection.Name, out id ) ) - continue; - - if ( hasSpecificDepots && !depotIdsExpected.Contains( id ) ) - continue; - - if ( !hasSpecificDepots ) - { - var depotConfig = depotSection[ "config" ]; - if ( depotConfig != KeyValue.Invalid ) - { - if ( !Config.DownloadAllPlatforms && - depotConfig["oslist"] != KeyValue.Invalid && - !string.IsNullOrWhiteSpace( depotConfig["oslist"].Value ) ) - { - var oslist = depotConfig["oslist"].Value.Split( ',' ); - if ( Array.IndexOf( oslist, os ?? Util.GetSteamOS() ) == -1 ) - continue; - } - - if ( depotConfig["osarch"] != KeyValue.Invalid && - !string.IsNullOrWhiteSpace( depotConfig["osarch"].Value ) ) - { - var depotArch = depotConfig["osarch"].Value; - if ( depotArch != ( arch ?? Util.GetSteamArch() ) ) - continue; - } - - if ( !Config.DownloadAllLanguages && - depotConfig["language"] != KeyValue.Invalid && - !string.IsNullOrWhiteSpace( depotConfig["language"].Value ) ) - { - var depotLang = depotConfig["language"].Value; - if ( depotLang != ( language ?? "english" ) ) - continue; - } - - if ( !lv && - depotConfig["lowviolence"] != KeyValue.Invalid && - depotConfig["lowviolence"].AsBoolean() ) - continue; - } - } - - depotIdsFound.Add( id ); - - if ( !hasSpecificDepots ) - depotManifestIds.Add( ( id, ContentDownloader.INVALID_MANIFEST_ID ) ); - } - } - if ( depotManifestIds.Count == 0 && !hasSpecificDepots ) - { - throw new ContentDownloaderException( String.Format( "Couldn't find any depots to download for app {0}", appId ) ); - } - else if ( depotIdsFound.Count < depotIdsExpected.Count ) - { - var remainingDepotIds = depotIdsExpected.Except( depotIdsFound ); - throw new ContentDownloaderException( String.Format( "Depot {0} not listed for app {1}", string.Join(", ", remainingDepotIds), appId ) ); - } - } - - var infos = new List(); - - foreach ( var depotManifest in depotManifestIds ) - { - var info = GetDepotInfo( depotManifest.Item1, appId, depotManifest.Item2, branch ); - if ( info != null ) - { - infos.Add( info ); - } - } - - try - { - await DownloadSteam3Async( appId, infos ).ConfigureAwait( false ); - } - catch ( OperationCanceledException ) - { - Console.WriteLine( "App {0} was not completely downloaded.", appId ); - throw; - } - } - - static DepotDownloadInfo GetDepotInfo( uint depotId, uint appId, ulong manifestId, string branch ) - { - if ( steam3 != null && appId != INVALID_APP_ID ) - steam3.RequestAppInfo( ( uint )appId ); - - string contentName = GetAppOrDepotName( depotId, appId ); - - if ( !AccountHasAccess( depotId ) ) - { - Console.WriteLine( "Depot {0} ({1}) is not available from this account.", depotId, contentName ); - - return null; - } - - // Skip requesting an app ticket - steam3.AppTickets[ depotId ] = null; - - if (manifestId == INVALID_MANIFEST_ID) - { - manifestId = GetSteam3DepotManifest(depotId, appId, branch); - if (manifestId == INVALID_MANIFEST_ID && branch != "public") - { - Console.WriteLine("Warning: Depot {0} does not have branch named \"{1}\". Trying public branch.", depotId, branch); - branch = "public"; - manifestId = GetSteam3DepotManifest(depotId, appId, branch); - } - - if (manifestId == INVALID_MANIFEST_ID) - { - Console.WriteLine("Depot {0} ({1}) missing public subsection or manifest section.", depotId, contentName); - return null; - } - } - - uint uVersion = GetSteam3AppBuildNumber( appId, branch ); - - string installDir; - if ( !CreateDirectories( depotId, uVersion, out installDir ) ) - { - Console.WriteLine( "Error: Unable to create install directories!" ); - return null; - } - - steam3.RequestDepotKey( depotId, appId ); - if ( !steam3.DepotKeys.ContainsKey( depotId ) ) - { - Console.WriteLine( "No valid depot key for {0}, unable to download.", depotId ); - return null; - } - - byte[] depotKey = steam3.DepotKeys[ depotId ]; - - var info = new DepotDownloadInfo( depotId, manifestId, installDir, contentName ); - info.depotKey = depotKey; - return info; - } - - private class ChunkMatch - { - public ChunkMatch( ProtoManifest.ChunkData oldChunk, ProtoManifest.ChunkData newChunk ) - { - OldChunk = oldChunk; - NewChunk = newChunk; - } - public ProtoManifest.ChunkData OldChunk { get; private set; } - public ProtoManifest.ChunkData NewChunk { get; private set; } - } - - private class DepotFilesData - { - public DepotDownloadInfo depotDownloadInfo; - public DepotDownloadCounter depotCounter; - public string stagingDir; - public ProtoManifest manifest; - public ProtoManifest previousManifest; - public List filteredFiles; - public HashSet allFileNames; - } - - private class FileStreamData - { - public FileStream fileStream; - public SemaphoreSlim fileLock; - public int chunksToDownload; - } - - private class GlobalDownloadCounter - { - public ulong TotalBytesCompressed; - public ulong TotalBytesUncompressed; - } - - private class DepotDownloadCounter - { - public ulong CompleteDownloadSize; - public ulong SizeDownloaded; - public ulong DepotBytesCompressed; - public ulong DepotBytesUncompressed; - - } - - private static async Task DownloadSteam3Async(uint appId, List depots) - { - CancellationTokenSource cts = new CancellationTokenSource(); - cdnPool.ExhaustedToken = cts; - - GlobalDownloadCounter downloadCounter = new GlobalDownloadCounter(); - var depotsToDownload = new List(depots.Count); - var allFileNamesAllDepots = new HashSet(); - - // First, fetch all the manifests for each depot (including previous manifests) and perform the initial setup - foreach (var depot in depots) - { - var depotFileData = await ProcessDepotManifestAndFiles(cts, appId, depot); - - if (depotFileData != null) - { - depotsToDownload.Add(depotFileData); - allFileNamesAllDepots.UnionWith(depotFileData.allFileNames); - } - - cts.Token.ThrowIfCancellationRequested(); - } - - foreach (var depotFileData in depotsToDownload) - { - await DownloadSteam3AsyncDepotFiles(cts, appId, downloadCounter, depotFileData, allFileNamesAllDepots); - } - - Console.WriteLine("Total downloaded: {0} bytes ({1} bytes uncompressed) from {2} depots", - downloadCounter.TotalBytesCompressed, downloadCounter.TotalBytesUncompressed, depots.Count); - } - - private static async Task ProcessDepotManifestAndFiles(CancellationTokenSource cts, - uint appId, DepotDownloadInfo depot) - { - DepotDownloadCounter depotCounter = new DepotDownloadCounter(); - - Console.WriteLine("Processing depot {0} - {1}", depot.id, depot.contentName); - - ProtoManifest oldProtoManifest = null; - ProtoManifest newProtoManifest = null; - string configDir = Path.Combine(depot.installDir, CONFIG_DIR); - - ulong lastManifestId = INVALID_MANIFEST_ID; - DepotConfigStore.Instance.InstalledManifestIDs.TryGetValue(depot.id, out lastManifestId); - - // In case we have an early exit, this will force equiv of verifyall next run. - DepotConfigStore.Instance.InstalledManifestIDs[depot.id] = INVALID_MANIFEST_ID; - DepotConfigStore.Save(); - - if (lastManifestId != INVALID_MANIFEST_ID) - { - var oldManifestFileName = Path.Combine(configDir, string.Format("{0}_{1}.bin", depot.id, lastManifestId)); - - if (File.Exists(oldManifestFileName)) - { - byte[] expectedChecksum, currentChecksum; - - try - { - expectedChecksum = File.ReadAllBytes(oldManifestFileName + ".sha"); - } - catch (IOException) - { - expectedChecksum = null; - } - - oldProtoManifest = ProtoManifest.LoadFromFile(oldManifestFileName, out currentChecksum); - - if (expectedChecksum == null || !expectedChecksum.SequenceEqual(currentChecksum)) - { - // We only have to show this warning if the old manifest ID was different - if (lastManifestId != depot.manifestId) - Console.WriteLine("Manifest {0} on disk did not match the expected checksum.", lastManifestId); - oldProtoManifest = null; - } - } - } - - if (lastManifestId == depot.manifestId && oldProtoManifest != null) - { - newProtoManifest = oldProtoManifest; - Console.WriteLine("Already have manifest {0} for depot {1}.", depot.manifestId, depot.id); - } - else - { - var newManifestFileName = Path.Combine(configDir, string.Format("{0}_{1}.bin", depot.id, depot.manifestId)); - if (newManifestFileName != null) - { - byte[] expectedChecksum, currentChecksum; - - try - { - expectedChecksum = File.ReadAllBytes(newManifestFileName + ".sha"); - } - catch (IOException) - { - expectedChecksum = null; - } - - newProtoManifest = ProtoManifest.LoadFromFile(newManifestFileName, out currentChecksum); - - if (newProtoManifest != null && (expectedChecksum == null || !expectedChecksum.SequenceEqual(currentChecksum))) - { - Console.WriteLine("Manifest {0} on disk did not match the expected checksum.", depot.manifestId); - newProtoManifest = null; - } - } - - if (newProtoManifest != null) - { - Console.WriteLine("Already have manifest {0} for depot {1}.", depot.manifestId, depot.id); - } - else - { - Console.Write("Downloading depot manifest..."); - - DepotManifest depotManifest = null; - - do - { - cts.Token.ThrowIfCancellationRequested(); - - CDNClient.Server connection = null; - - try - { - connection = cdnPool.GetConnection(cts.Token); - var cdnToken = await cdnPool.AuthenticateConnection(appId, depot.id, connection); - -#if STEAMKIT_UNRELEASED - depotManifest = await cdnPool.CDNClient.DownloadManifestAsync(depot.id, depot.manifestId, - connection, cdnToken, depot.depotKey, proxyServer: cdnPool.ProxyServer).ConfigureAwait(false); -#else - depotManifest = await cdnPool.CDNClient.DownloadManifestAsync(depot.id, depot.manifestId, - connection, cdnToken, depot.depotKey).ConfigureAwait(false); -#endif - - cdnPool.ReturnConnection(connection); - } - catch (TaskCanceledException) - { - Console.WriteLine("Connection timeout downloading depot manifest {0} {1}", depot.id, depot.manifestId); - } - catch (SteamKitWebRequestException e) - { - cdnPool.ReturnBrokenConnection(connection); - - if (e.StatusCode == HttpStatusCode.Unauthorized || e.StatusCode == HttpStatusCode.Forbidden) - { - Console.WriteLine("Encountered 401 for depot manifest {0} {1}. Aborting.", depot.id, depot.manifestId); - break; - } - else if (e.StatusCode == HttpStatusCode.NotFound) - { - Console.WriteLine("Encountered 404 for depot manifest {0} {1}. Aborting.", depot.id, depot.manifestId); - break; - } - else - { - Console.WriteLine("Encountered error downloading depot manifest {0} {1}: {2}", depot.id, depot.manifestId, e.StatusCode); - } - } - catch (OperationCanceledException) - { - break; - } - catch (Exception e) - { - cdnPool.ReturnBrokenConnection(connection); - Console.WriteLine("Encountered error downloading manifest for depot {0} {1}: {2}", depot.id, depot.manifestId, e.Message); - } - } - while (depotManifest == null); - - if (depotManifest == null) - { - Console.WriteLine("\nUnable to download manifest {0} for depot {1}", depot.manifestId, depot.id); - cts.Cancel(); - } - - // Throw the cancellation exception if requested so that this task is marked failed - cts.Token.ThrowIfCancellationRequested(); - - byte[] checksum; - - newProtoManifest = new ProtoManifest(depotManifest, depot.manifestId); - newProtoManifest.SaveToFile(newManifestFileName, out checksum); - File.WriteAllBytes(newManifestFileName + ".sha", checksum); - - Console.WriteLine(" Done!"); - } - } - - newProtoManifest.Files.Sort((x, y) => string.Compare(x.FileName, y.FileName, StringComparison.Ordinal)); - - Console.WriteLine("Manifest {0} ({1})", depot.manifestId, newProtoManifest.CreationTime); - - if (Config.DownloadManifestOnly) - { - StringBuilder manifestBuilder = new StringBuilder(); - string txtManifest = Path.Combine(depot.installDir, string.Format("manifest_{0}_{1}.txt", depot.id, depot.manifestId)); - manifestBuilder.Append(string.Format("{0}\n\n", newProtoManifest.CreationTime)); - - foreach (var file in newProtoManifest.Files) - { - if (file.Flags.HasFlag(EDepotFileFlag.Directory)) - continue; - - manifestBuilder.Append(string.Format("{0}\n", file.FileName)); - manifestBuilder.Append(string.Format("\t{0}\n", file.TotalSize)); - manifestBuilder.Append(string.Format("\t{0}\n", BitConverter.ToString(file.FileHash).Replace("-", ""))); - } - - File.WriteAllText(txtManifest, manifestBuilder.ToString()); - return null; - } - - string stagingDir = Path.Combine(depot.installDir, STAGING_DIR); - - var filesAfterExclusions = newProtoManifest.Files.AsParallel().Where(f => TestIsFileIncluded(f.FileName)).ToList(); - var allFileNames = new HashSet(filesAfterExclusions.Count); - - // Pre-process - filesAfterExclusions.ForEach(file => - { - allFileNames.Add(file.FileName); - - var fileFinalPath = Path.Combine(depot.installDir, file.FileName); - var fileStagingPath = Path.Combine(stagingDir, file.FileName); - - if (file.Flags.HasFlag(EDepotFileFlag.Directory)) - { - Directory.CreateDirectory(fileFinalPath); - Directory.CreateDirectory(fileStagingPath); - } - else - { - // Some manifests don't explicitly include all necessary directories - Directory.CreateDirectory(Path.GetDirectoryName(fileFinalPath)); - Directory.CreateDirectory(Path.GetDirectoryName(fileStagingPath)); - - depotCounter.CompleteDownloadSize += file.TotalSize; - } - }); - - return new DepotFilesData - { - depotDownloadInfo = depot, - depotCounter = depotCounter, - stagingDir = stagingDir, - manifest = newProtoManifest, - previousManifest = oldProtoManifest, - filteredFiles = filesAfterExclusions, - allFileNames = allFileNames - }; - } - - private static async Task DownloadSteam3AsyncDepotFiles(CancellationTokenSource cts, uint appId, - GlobalDownloadCounter downloadCounter, DepotFilesData depotFilesData, HashSet allFileNamesAllDepots) - { - var depot = depotFilesData.depotDownloadInfo; - var depotCounter = depotFilesData.depotCounter; - - Console.WriteLine("Downloading depot {0} - {1}", depot.id, depot.contentName); - - var files = depotFilesData.filteredFiles.Where(f => !f.Flags.HasFlag(EDepotFileFlag.Directory)).ToArray(); - var networkChunkQueue = new ConcurrentQueue<(FileStreamData fileStreamData, ProtoManifest.FileData fileData, ProtoManifest.ChunkData chunk)>(); - - await Util.InvokeAsync( - files.Select(file => new Func(async () => - await Task.Run(() => DownloadSteam3AsyncDepotFile(cts, depotFilesData, file, networkChunkQueue)))), - maxDegreeOfParallelism: Config.MaxDownloads - ); - - await Util.InvokeAsync( - networkChunkQueue.Select(q => new Func(async () => - await Task.Run(() => DownloadSteam3AsyncDepotFileChunk(cts, appId, downloadCounter, depotFilesData, - q.fileData, q.fileStreamData, q.chunk)))), - maxDegreeOfParallelism: Config.MaxDownloads - ); - - // Check for deleted files if updating the depot. - if (depotFilesData.previousManifest != null) - { - var previousFilteredFiles = depotFilesData.previousManifest.Files.AsParallel().Where(f => TestIsFileIncluded(f.FileName)).Select(f => f.FileName).ToHashSet(); - - // Check if we are writing to a single output directory. If not, each depot folder is managed independently - if (string.IsNullOrWhiteSpace(ContentDownloader.Config.InstallDirectory)) - { - // Of the list of files in the previous manifest, remove any file names that exist in the current set of all file names - previousFilteredFiles.ExceptWith(depotFilesData.allFileNames); - } - else - { - // Of the list of files in the previous manifest, remove any file names that exist in the current set of all file names across all depots being downloaded - previousFilteredFiles.ExceptWith(allFileNamesAllDepots); - } - - foreach(var existingFileName in previousFilteredFiles) - { - string fileFinalPath = Path.Combine(depot.installDir, existingFileName); - - if (!File.Exists(fileFinalPath)) - continue; - - File.Delete(fileFinalPath); - Console.WriteLine("Deleted {0}", fileFinalPath); - } - } - - DepotConfigStore.Instance.InstalledManifestIDs[depot.id] = depot.manifestId; - DepotConfigStore.Save(); - - Console.WriteLine("Depot {0} - Downloaded {1} bytes ({2} bytes uncompressed)", depot.id, depotCounter.DepotBytesCompressed, depotCounter.DepotBytesUncompressed); - } - - private static void DownloadSteam3AsyncDepotFile( - CancellationTokenSource cts, - DepotFilesData depotFilesData, - ProtoManifest.FileData file, - ConcurrentQueue<(FileStreamData, ProtoManifest.FileData, ProtoManifest.ChunkData)> networkChunkQueue) - { - cts.Token.ThrowIfCancellationRequested(); - - var depot = depotFilesData.depotDownloadInfo; - var stagingDir = depotFilesData.stagingDir; - var depotDownloadCounter = depotFilesData.depotCounter; - var oldProtoManifest = depotFilesData.previousManifest; - - string fileFinalPath = Path.Combine(depot.installDir, file.FileName); - string fileStagingPath = Path.Combine(stagingDir, file.FileName); - - // This may still exist if the previous run exited before cleanup - if (File.Exists(fileStagingPath)) - { - File.Delete(fileStagingPath); - } - - FileStream fs = null; - List neededChunks; - FileInfo fi = new FileInfo(fileFinalPath); - if (!fi.Exists) - { - Console.WriteLine("Pre-allocating {0}", fileFinalPath); - - // create new file. need all chunks - fs = File.Create(fileFinalPath); - fs.SetLength((long)file.TotalSize); - neededChunks = new List(file.Chunks); - } - else - { - // open existing - ProtoManifest.FileData oldManifestFile = null; - if (oldProtoManifest != null) - { - oldManifestFile = oldProtoManifest.Files.SingleOrDefault(f => f.FileName == file.FileName); - } - - if (oldManifestFile != null) - { - neededChunks = new List(); - - if (Config.VerifyAll || !oldManifestFile.FileHash.SequenceEqual(file.FileHash)) - { - // we have a version of this file, but it doesn't fully match what we want - if (Config.VerifyAll) - { - Console.WriteLine("Validating {0}", fileFinalPath); - } - - var matchingChunks = new List(); - - foreach (var chunk in file.Chunks) - { - var oldChunk = oldManifestFile.Chunks.FirstOrDefault(c => c.ChunkID.SequenceEqual(chunk.ChunkID)); - if (oldChunk != null) - { - matchingChunks.Add(new ChunkMatch(oldChunk, chunk)); - } - else - { - neededChunks.Add(chunk); - } - } - - var orderedChunks = matchingChunks.OrderBy(x => x.OldChunk.Offset); - - File.Move(fileFinalPath, fileStagingPath); - - fs = File.Open(fileFinalPath, FileMode.Create); - fs.SetLength((long)file.TotalSize); - - using (var fsOld = File.Open(fileStagingPath, FileMode.Open)) - { - foreach (var match in orderedChunks) - { - fsOld.Seek((long)match.OldChunk.Offset, SeekOrigin.Begin); - - byte[] tmp = new byte[match.OldChunk.UncompressedLength]; - fsOld.Read(tmp, 0, tmp.Length); - - byte[] adler = Util.AdlerHash(tmp); - if (!adler.SequenceEqual(match.OldChunk.Checksum)) - { - neededChunks.Add(match.NewChunk); - } - else - { - fs.Seek((long)match.NewChunk.Offset, SeekOrigin.Begin); - fs.Write(tmp, 0, tmp.Length); - } - } - } - - File.Delete(fileStagingPath); - } - } - else - { - // No old manifest or file not in old manifest. We must validate. - - fs = File.Open(fileFinalPath, FileMode.Open); - if ((ulong)fi.Length != file.TotalSize) - { - fs.SetLength((long)file.TotalSize); - } - - Console.WriteLine("Validating {0}", fileFinalPath); - neededChunks = Util.ValidateSteam3FileChecksums(fs, file.Chunks.OrderBy(x => x.Offset).ToArray()); - } - - if (neededChunks.Count() == 0) - { - lock (depotDownloadCounter) - { - depotDownloadCounter.SizeDownloaded += (ulong)file.TotalSize; - Console.WriteLine("{0,6:#00.00}% {1}", ((float)depotDownloadCounter.SizeDownloaded / (float)depotDownloadCounter.CompleteDownloadSize) * 100.0f, fileFinalPath); - } - - if (fs != null) - fs.Dispose(); - return; - } - else - { - var sizeOnDisk = (file.TotalSize - (ulong)neededChunks.Select(x => (long)x.UncompressedLength).Sum()); - lock (depotDownloadCounter) - { - depotDownloadCounter.SizeDownloaded += sizeOnDisk; - } - } - } - - FileStreamData fileStreamData = new FileStreamData - { - fileStream = fs, - fileLock = new SemaphoreSlim(1), - chunksToDownload = neededChunks.Count - }; - - foreach (var chunk in neededChunks) - { - networkChunkQueue.Enqueue((fileStreamData, file, chunk)); - } - } - - private static async Task DownloadSteam3AsyncDepotFileChunk( - CancellationTokenSource cts, uint appId, - GlobalDownloadCounter downloadCounter, - DepotFilesData depotFilesData, - ProtoManifest.FileData file, - FileStreamData fileStreamData, - ProtoManifest.ChunkData chunk) - { - cts.Token.ThrowIfCancellationRequested(); - - var depot = depotFilesData.depotDownloadInfo; - var depotDownloadCounter = depotFilesData.depotCounter; - - string chunkID = Util.EncodeHexString(chunk.ChunkID); - - DepotManifest.ChunkData data = new DepotManifest.ChunkData(); - data.ChunkID = chunk.ChunkID; - data.Checksum = chunk.Checksum; - data.Offset = chunk.Offset; - data.CompressedLength = chunk.CompressedLength; - data.UncompressedLength = chunk.UncompressedLength; - - CDNClient.DepotChunk chunkData = null; - - do - { - cts.Token.ThrowIfCancellationRequested(); - - CDNClient.Server connection = null; - - try - { - connection = cdnPool.GetConnection(cts.Token); - var cdnToken = await cdnPool.AuthenticateConnection(appId, depot.id, connection); - -#if STEAMKIT_UNRELEASED - chunkData = await cdnPool.CDNClient.DownloadDepotChunkAsync(depot.id, data, - connection, cdnToken, depot.depotKey, proxyServer: cdnPool.ProxyServer).ConfigureAwait(false); -#else - chunkData = await cdnPool.CDNClient.DownloadDepotChunkAsync(depot.id, data, - connection, cdnToken, depot.depotKey).ConfigureAwait(false); -#endif - - cdnPool.ReturnConnection(connection); - } - catch (TaskCanceledException) - { - Console.WriteLine("Connection timeout downloading chunk {0}", chunkID); - } - catch (SteamKitWebRequestException e) - { - cdnPool.ReturnBrokenConnection(connection); - - if (e.StatusCode == HttpStatusCode.Unauthorized || e.StatusCode == HttpStatusCode.Forbidden) - { - Console.WriteLine("Encountered 401 for chunk {0}. Aborting.", chunkID); - break; - } - else - { - Console.WriteLine("Encountered error downloading chunk {0}: {1}", chunkID, e.StatusCode); - } - } - catch (OperationCanceledException) - { - break; - } - catch (Exception e) - { - cdnPool.ReturnBrokenConnection(connection); - Console.WriteLine("Encountered unexpected error downloading chunk {0}: {1}", chunkID, e.Message); - } - } - while (chunkData == null); - - if (chunkData == null) - { - Console.WriteLine("Failed to find any server with chunk {0} for depot {1}. Aborting.", chunkID, depot.id); - cts.Cancel(); - } - - // Throw the cancellation exception if requested so that this task is marked failed - cts.Token.ThrowIfCancellationRequested(); - - try - { - await fileStreamData.fileLock.WaitAsync().ConfigureAwait(false); - - fileStreamData.fileStream.Seek((long)chunkData.ChunkInfo.Offset, SeekOrigin.Begin); - await fileStreamData.fileStream.WriteAsync(chunkData.Data, 0, chunkData.Data.Length); - } - finally - { - fileStreamData.fileLock.Release(); - } - - int remainingChunks = Interlocked.Decrement(ref fileStreamData.chunksToDownload); - if (remainingChunks == 0) - { - fileStreamData.fileStream.Dispose(); - fileStreamData.fileLock.Dispose(); - } - - ulong sizeDownloaded = 0; - lock (depotDownloadCounter) - { - sizeDownloaded = depotDownloadCounter.SizeDownloaded + (ulong)chunkData.Data.Length; - depotDownloadCounter.SizeDownloaded = sizeDownloaded; - depotDownloadCounter.DepotBytesCompressed += chunk.CompressedLength; - depotDownloadCounter.DepotBytesUncompressed += chunk.UncompressedLength; - } - - lock (downloadCounter) - { - downloadCounter.TotalBytesCompressed += chunk.CompressedLength; - downloadCounter.TotalBytesUncompressed += chunk.UncompressedLength; - } - - if (remainingChunks == 0) - { - var fileFinalPath = Path.Combine(depot.installDir, file.FileName); - Console.WriteLine("{0,6:#00.00}% {1}", ((float)sizeDownloaded / (float)depotDownloadCounter.CompleteDownloadSize) * 100.0f, fileFinalPath); - } - - } - } -} +using SteamKit2; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; + +namespace DepotDownloader +{ + public class ContentDownloaderException : System.Exception + { + public ContentDownloaderException( String value ) : base( value ) {} + } + + static class ContentDownloader + { + public const uint INVALID_APP_ID = uint.MaxValue; + public const uint INVALID_DEPOT_ID = uint.MaxValue; + public const ulong INVALID_MANIFEST_ID = ulong.MaxValue; + public const string DEFAULT_BRANCH = "Public"; + + public static DownloadConfig Config = new DownloadConfig(); + + private static Steam3Session steam3; + private static Steam3Session.Credentials steam3Credentials; + private static CDNClientPool cdnPool; + + private const string DEFAULT_DOWNLOAD_DIR = "depots"; + private const string CONFIG_DIR = ".DepotDownloader"; + private static readonly string STAGING_DIR = Path.Combine( CONFIG_DIR, "staging" ); + + private sealed class DepotDownloadInfo + { + public uint id { get; private set; } + public string installDir { get; private set; } + public string contentName { get; private set; } + + public ulong manifestId { get; private set; } + public byte[] depotKey; + + public DepotDownloadInfo( uint depotid, ulong manifestId, string installDir, string contentName ) + { + this.id = depotid; + this.manifestId = manifestId; + this.installDir = installDir; + this.contentName = contentName; + } + } + + static bool CreateDirectories( uint depotId, uint depotVersion, out string installDir ) + { + installDir = null; + try + { + if ( string.IsNullOrWhiteSpace( ContentDownloader.Config.InstallDirectory ) ) + { + Directory.CreateDirectory( DEFAULT_DOWNLOAD_DIR ); + + string depotPath = Path.Combine( DEFAULT_DOWNLOAD_DIR, depotId.ToString() ); + Directory.CreateDirectory( depotPath ); + + installDir = Path.Combine( depotPath, depotVersion.ToString() ); + Directory.CreateDirectory( installDir ); + + Directory.CreateDirectory( Path.Combine( installDir, CONFIG_DIR ) ); + Directory.CreateDirectory( Path.Combine( installDir, STAGING_DIR ) ); + } + else + { + Directory.CreateDirectory( ContentDownloader.Config.InstallDirectory ); + + installDir = ContentDownloader.Config.InstallDirectory; + + Directory.CreateDirectory( Path.Combine( installDir, CONFIG_DIR ) ); + Directory.CreateDirectory( Path.Combine( installDir, STAGING_DIR ) ); + } + } + catch + { + return false; + } + + return true; + } + + static bool TestIsFileIncluded( string filename ) + { + if ( !Config.UsingFileList ) + return true; + + foreach ( string fileListEntry in Config.FilesToDownload ) + { + if ( fileListEntry.Equals( filename, StringComparison.OrdinalIgnoreCase ) ) + return true; + } + + foreach ( Regex rgx in Config.FilesToDownloadRegex ) + { + Match m = rgx.Match( filename ); + + if ( m.Success ) + return true; + } + + return false; + } + + static bool AccountHasAccess( uint depotId ) + { + if ( steam3 == null || steam3.steamUser.SteamID == null || ( steam3.Licenses == null && steam3.steamUser.SteamID.AccountType != EAccountType.AnonUser ) ) + return false; + + IEnumerable licenseQuery; + if ( steam3.steamUser.SteamID.AccountType == EAccountType.AnonUser ) + { + licenseQuery = new List() { 17906 }; + } + else + { + licenseQuery = steam3.Licenses.Select( x => x.PackageID ).Distinct(); + } + + steam3.RequestPackageInfo( licenseQuery ); + + foreach ( var license in licenseQuery ) + { + SteamApps.PICSProductInfoCallback.PICSProductInfo package; + if ( steam3.PackageInfo.TryGetValue( license, out package ) && package != null ) + { + if ( package.KeyValues[ "appids" ].Children.Any( child => child.AsUnsignedInteger() == depotId ) ) + return true; + + if ( package.KeyValues[ "depotids" ].Children.Any( child => child.AsUnsignedInteger() == depotId ) ) + return true; + } + } + + return false; + } + + internal static KeyValue GetSteam3AppSection( uint appId, EAppInfoSection section ) + { + if ( steam3 == null || steam3.AppInfo == null ) + { + return null; + } + + SteamApps.PICSProductInfoCallback.PICSProductInfo app; + if ( !steam3.AppInfo.TryGetValue( appId, out app ) || app == null ) + { + return null; + } + + KeyValue appinfo = app.KeyValues; + string section_key; + + switch ( section ) + { + case EAppInfoSection.Common: + section_key = "common"; + break; + case EAppInfoSection.Extended: + section_key = "extended"; + break; + case EAppInfoSection.Config: + section_key = "config"; + break; + case EAppInfoSection.Depots: + section_key = "depots"; + break; + default: + throw new NotImplementedException(); + } + + KeyValue section_kv = appinfo.Children.Where( c => c.Name == section_key ).FirstOrDefault(); + return section_kv; + } + + static uint GetSteam3AppBuildNumber( uint appId, string branch ) + { + if ( appId == INVALID_APP_ID ) + return 0; + + + KeyValue depots = ContentDownloader.GetSteam3AppSection( appId, EAppInfoSection.Depots ); + KeyValue branches = depots[ "branches" ]; + KeyValue node = branches[ branch ]; + + if ( node == KeyValue.Invalid ) + return 0; + + KeyValue buildid = node[ "buildid" ]; + + if ( buildid == KeyValue.Invalid ) + return 0; + + return uint.Parse( buildid.Value ); + } + + static ulong GetSteam3DepotManifest( uint depotId, uint appId, string branch ) + { + KeyValue depots = GetSteam3AppSection( appId, EAppInfoSection.Depots ); + KeyValue depotChild = depots[ depotId.ToString() ]; + + if ( depotChild == KeyValue.Invalid ) + return INVALID_MANIFEST_ID; + + // Shared depots can either provide manifests, or leave you relying on their parent app. + // It seems that with the latter, "sharedinstall" will exist (and equals 2 in the one existance I know of). + // Rather than relay on the unknown sharedinstall key, just look for manifests. Test cases: 111710, 346680. + if ( depotChild[ "manifests" ] == KeyValue.Invalid && depotChild[ "depotfromapp" ] != KeyValue.Invalid ) + { + uint otherAppId = depotChild["depotfromapp"].AsUnsignedInteger(); + if ( otherAppId == appId ) + { + // This shouldn't ever happen, but ya never know with Valve. Don't infinite loop. + Console.WriteLine( "App {0}, Depot {1} has depotfromapp of {2}!", + appId, depotId, otherAppId ); + return INVALID_MANIFEST_ID; + } + + steam3.RequestAppInfo( otherAppId ); + + return GetSteam3DepotManifest( depotId, otherAppId, branch ); + } + + var manifests = depotChild[ "manifests" ]; + var manifests_encrypted = depotChild[ "encryptedmanifests" ]; + + if ( manifests.Children.Count == 0 && manifests_encrypted.Children.Count == 0 ) + return INVALID_MANIFEST_ID; + + var node = manifests[ branch ]; + + if ( branch != "Public" && node == KeyValue.Invalid ) + { + var node_encrypted = manifests_encrypted[ branch ]; + if ( node_encrypted != KeyValue.Invalid ) + { + string password = Config.BetaPassword; + if ( password == null ) + { + Console.Write( "Please enter the password for branch {0}: ", branch ); + Config.BetaPassword = password = Console.ReadLine(); + } + + var encrypted_v1 = node_encrypted[ "encrypted_gid" ]; + var encrypted_v2 = node_encrypted[ "encrypted_gid_2" ]; + + if ( encrypted_v1 != KeyValue.Invalid ) + { + byte[] input = Util.DecodeHexString( encrypted_v1.Value ); + byte[] manifest_bytes = CryptoHelper.VerifyAndDecryptPassword( input, password ); + + if ( manifest_bytes == null ) + { + Console.WriteLine( "Password was invalid for branch {0}", branch ); + return INVALID_MANIFEST_ID; + } + + return BitConverter.ToUInt64( manifest_bytes, 0 ); + } + else if ( encrypted_v2 != KeyValue.Invalid ) + { + // Submit the password to Steam now to get encryption keys + steam3.CheckAppBetaPassword( appId, Config.BetaPassword ); + + if ( !steam3.AppBetaPasswords.ContainsKey( branch ) ) + { + Console.WriteLine( "Password was invalid for branch {0}", branch ); + return INVALID_MANIFEST_ID; + } + + byte[] input = Util.DecodeHexString( encrypted_v2.Value ); + byte[] manifest_bytes; + try + { + manifest_bytes = CryptoHelper.SymmetricDecryptECB( input, steam3.AppBetaPasswords[ branch ] ); + } + catch ( Exception e ) + { + Console.WriteLine( "Failed to decrypt branch {0}: {1}", branch, e.Message ); + return INVALID_MANIFEST_ID; + } + + return BitConverter.ToUInt64( manifest_bytes, 0 ); + } + else + { + Console.WriteLine( "Unhandled depot encryption for depotId {0}", depotId ); + return INVALID_MANIFEST_ID; + } + + } + + return INVALID_MANIFEST_ID; + } + + if ( node.Value == null ) + return INVALID_MANIFEST_ID; + + return UInt64.Parse( node.Value ); + } + + static string GetAppOrDepotName( uint depotId, uint appId ) + { + if ( depotId == INVALID_DEPOT_ID ) + { + KeyValue info = GetSteam3AppSection( appId, EAppInfoSection.Common ); + + if ( info == null ) + return String.Empty; + + return info[ "name" ].AsString(); + } + else + { + KeyValue depots = GetSteam3AppSection( appId, EAppInfoSection.Depots ); + + if ( depots == null ) + return String.Empty; + + KeyValue depotChild = depots[ depotId.ToString() ]; + + if ( depotChild == null ) + return String.Empty; + + return depotChild[ "name" ].AsString(); + } + } + + public static bool InitializeSteam3( string username, string password ) + { + string loginKey = null; + + if ( username != null && Config.RememberPassword ) + { + _ = AccountSettingsStore.Instance.LoginKeys.TryGetValue( username, out loginKey ); + } + + steam3 = new Steam3Session( + new SteamUser.LogOnDetails() + { + Username = username, + Password = loginKey == null ? password : null, + ShouldRememberPassword = Config.RememberPassword, + LoginKey = loginKey, + LoginID = Config.LoginID ?? 0x534B32, // "SK2" + } + ); + + steam3Credentials = steam3.WaitForCredentials(); + + if ( !steam3Credentials.IsValid ) + { + Console.WriteLine( "Unable to get steam3 credentials." ); + return false; + } + + return true; + } + + public static void ShutdownSteam3() + { + if (cdnPool != null) + { + cdnPool.Shutdown(); + cdnPool = null; + } + + if ( steam3 == null ) + return; + + steam3.TryWaitForLoginKey(); + steam3.Disconnect(); + } + + public static async Task DownloadPubfileAsync( uint appId, ulong publishedFileId ) + { + var details = steam3.GetPublishedFileDetails( appId, publishedFileId ); + + if ( !string.IsNullOrEmpty( details?.file_url ) ) + { + await DownloadWebFile( appId, details.filename, details.file_url ); + } + else if ( details?.hcontent_file > 0 ) + { + await DownloadAppAsync( appId, new List<(uint, ulong)>() { ( appId, details.hcontent_file ) }, DEFAULT_BRANCH, null, null, null, false, true ); + } + else + { + Console.WriteLine( "Unable to locate manifest ID for published file {0}", publishedFileId ); + } + } + + public static async Task DownloadUGCAsync( uint appId, ulong ugcId ) + { + SteamCloud.UGCDetailsCallback details = null; + + if ( steam3.steamUser.SteamID.AccountType != EAccountType.AnonUser ) + { + steam3.GetUGCDetails( ugcId ); + } + else + { + Console.WriteLine( $"Unable to query UGC details for {ugcId} from an anonymous account" ); + } + + if ( !string.IsNullOrEmpty( details?.URL ) ) + { + await DownloadWebFile( appId, details.FileName, details.URL ); + } + else + { + await DownloadAppAsync( appId, new List<(uint, ulong)>() { ( appId, ugcId ) }, DEFAULT_BRANCH, null, null, null, false, true ); + } + } + + private static async Task DownloadWebFile( uint appId, string fileName, string url ) + { + string installDir; + if ( !CreateDirectories( appId, 0, out installDir ) ) + { + Console.WriteLine( "Error: Unable to create install directories!" ); + return; + } + + var stagingDir = Path.Combine( installDir, STAGING_DIR ); + var fileStagingPath = Path.Combine( stagingDir, fileName ); + var fileFinalPath = Path.Combine( installDir, fileName ); + + Directory.CreateDirectory( Path.GetDirectoryName( fileFinalPath ) ); + Directory.CreateDirectory( Path.GetDirectoryName( fileStagingPath ) ); + + using ( var file = File.OpenWrite( fileStagingPath ) ) + using ( var client = new HttpClient() ) + { + Console.WriteLine( "Downloading {0}", fileName ); + var responseStream = await client.GetStreamAsync( url ); + await responseStream.CopyToAsync( file ); + } + + if ( File.Exists( fileFinalPath ) ) + { + File.Delete( fileFinalPath ); + } + + File.Move( fileStagingPath, fileFinalPath ); + } + + public static async Task DownloadAppAsync( uint appId, List<(uint depotId, ulong manifestId)> depotManifestIds, string branch, string os, string arch, string language, bool lv, bool isUgc ) + { + cdnPool = new CDNClientPool(steam3, appId); + + // Load our configuration data containing the depots currently installed + string configPath = ContentDownloader.Config.InstallDirectory; + if (string.IsNullOrWhiteSpace(configPath)) + { + configPath = DEFAULT_DOWNLOAD_DIR; + } + + Directory.CreateDirectory(Path.Combine(configPath, CONFIG_DIR)); + DepotConfigStore.LoadFromFile(Path.Combine(configPath, CONFIG_DIR, "depot.config")); + + if ( steam3 != null ) + steam3.RequestAppInfo( appId ); + + if ( !AccountHasAccess( appId ) ) + { + if ( steam3.RequestFreeAppLicense( appId ) ) + { + Console.WriteLine( "Obtained FreeOnDemand license for app {0}", appId ); + + // Fetch app info again in case we didn't get it fully without a license. + steam3.RequestAppInfo( appId, true ); + } + else + { + string contentName = GetAppOrDepotName( INVALID_DEPOT_ID, appId ); + throw new ContentDownloaderException( String.Format( "App {0} ({1}) is not available from this account.", appId, contentName ) ); + } + } + + var hasSpecificDepots = depotManifestIds.Count > 0; + var depotIdsFound = new List(); + var depotIdsExpected = depotManifestIds.Select( x => x.Item1 ).ToList(); + KeyValue depots = GetSteam3AppSection( appId, EAppInfoSection.Depots ); + + if ( isUgc ) + { + var workshopDepot = depots["workshopdepot"].AsUnsignedInteger(); + if ( workshopDepot != 0 && !depotIdsExpected.Contains( workshopDepot ) ) + { + depotIdsExpected.Add( workshopDepot ); + depotManifestIds = depotManifestIds.Select( pair => ( workshopDepot, pair.manifestId ) ).ToList(); + } + + depotIdsFound.AddRange( depotIdsExpected ); + } + else + { + Console.WriteLine( "Using app branch: '{0}'.", branch ); + + if ( depots != null ) + { + foreach ( var depotSection in depots.Children ) + { + uint id = INVALID_DEPOT_ID; + if ( depotSection.Children.Count == 0 ) + continue; + + if ( !uint.TryParse( depotSection.Name, out id ) ) + continue; + + if ( hasSpecificDepots && !depotIdsExpected.Contains( id ) ) + continue; + + if ( !hasSpecificDepots ) + { + var depotConfig = depotSection[ "config" ]; + if ( depotConfig != KeyValue.Invalid ) + { + if ( !Config.DownloadAllPlatforms && + depotConfig["oslist"] != KeyValue.Invalid && + !string.IsNullOrWhiteSpace( depotConfig["oslist"].Value ) ) + { + var oslist = depotConfig["oslist"].Value.Split( ',' ); + if ( Array.IndexOf( oslist, os ?? Util.GetSteamOS() ) == -1 ) + continue; + } + + if ( depotConfig["osarch"] != KeyValue.Invalid && + !string.IsNullOrWhiteSpace( depotConfig["osarch"].Value ) ) + { + var depotArch = depotConfig["osarch"].Value; + if ( depotArch != ( arch ?? Util.GetSteamArch() ) ) + continue; + } + + if ( !Config.DownloadAllLanguages && + depotConfig["language"] != KeyValue.Invalid && + !string.IsNullOrWhiteSpace( depotConfig["language"].Value ) ) + { + var depotLang = depotConfig["language"].Value; + if ( depotLang != ( language ?? "english" ) ) + continue; + } + + if ( !lv && + depotConfig["lowviolence"] != KeyValue.Invalid && + depotConfig["lowviolence"].AsBoolean() ) + continue; + } + } + + depotIdsFound.Add( id ); + + if ( !hasSpecificDepots ) + depotManifestIds.Add( ( id, ContentDownloader.INVALID_MANIFEST_ID ) ); + } + } + if ( depotManifestIds.Count == 0 && !hasSpecificDepots ) + { + throw new ContentDownloaderException( String.Format( "Couldn't find any depots to download for app {0}", appId ) ); + } + else if ( depotIdsFound.Count < depotIdsExpected.Count ) + { + var remainingDepotIds = depotIdsExpected.Except( depotIdsFound ); + throw new ContentDownloaderException( String.Format( "Depot {0} not listed for app {1}", string.Join(", ", remainingDepotIds), appId ) ); + } + } + + var infos = new List(); + + foreach ( var depotManifest in depotManifestIds ) + { + var info = GetDepotInfo( depotManifest.Item1, appId, depotManifest.Item2, branch ); + if ( info != null ) + { + infos.Add( info ); + } + } + + try + { + await DownloadSteam3Async( appId, infos ).ConfigureAwait( false ); + } + catch ( OperationCanceledException ) + { + Console.WriteLine( "App {0} was not completely downloaded.", appId ); + throw; + } + } + + static DepotDownloadInfo GetDepotInfo( uint depotId, uint appId, ulong manifestId, string branch ) + { + if ( steam3 != null && appId != INVALID_APP_ID ) + steam3.RequestAppInfo( ( uint )appId ); + + string contentName = GetAppOrDepotName( depotId, appId ); + + if ( !AccountHasAccess( depotId ) ) + { + Console.WriteLine( "Depot {0} ({1}) is not available from this account.", depotId, contentName ); + + return null; + } + + // Skip requesting an app ticket + steam3.AppTickets[ depotId ] = null; + + if (manifestId == INVALID_MANIFEST_ID) + { + manifestId = GetSteam3DepotManifest(depotId, appId, branch); + if (manifestId == INVALID_MANIFEST_ID && branch != "public") + { + Console.WriteLine("Warning: Depot {0} does not have branch named \"{1}\". Trying public branch.", depotId, branch); + branch = "public"; + manifestId = GetSteam3DepotManifest(depotId, appId, branch); + } + + if (manifestId == INVALID_MANIFEST_ID) + { + Console.WriteLine("Depot {0} ({1}) missing public subsection or manifest section.", depotId, contentName); + return null; + } + } + + uint uVersion = GetSteam3AppBuildNumber( appId, branch ); + + string installDir; + if ( !CreateDirectories( depotId, uVersion, out installDir ) ) + { + Console.WriteLine( "Error: Unable to create install directories!" ); + return null; + } + + steam3.RequestDepotKey( depotId, appId ); + if ( !steam3.DepotKeys.ContainsKey( depotId ) ) + { + Console.WriteLine( "No valid depot key for {0}, unable to download.", depotId ); + return null; + } + + byte[] depotKey = steam3.DepotKeys[ depotId ]; + + var info = new DepotDownloadInfo( depotId, manifestId, installDir, contentName ); + info.depotKey = depotKey; + return info; + } + + private class ChunkMatch + { + public ChunkMatch( ProtoManifest.ChunkData oldChunk, ProtoManifest.ChunkData newChunk ) + { + OldChunk = oldChunk; + NewChunk = newChunk; + } + public ProtoManifest.ChunkData OldChunk { get; private set; } + public ProtoManifest.ChunkData NewChunk { get; private set; } + } + + private class DepotFilesData + { + public DepotDownloadInfo depotDownloadInfo; + public DepotDownloadCounter depotCounter; + public string stagingDir; + public ProtoManifest manifest; + public ProtoManifest previousManifest; + public List filteredFiles; + public HashSet allFileNames; + } + + private class FileStreamData + { + public FileStream fileStream; + public SemaphoreSlim fileLock; + public int chunksToDownload; + } + + private class GlobalDownloadCounter + { + public ulong TotalBytesCompressed; + public ulong TotalBytesUncompressed; + } + + private class DepotDownloadCounter + { + public ulong CompleteDownloadSize; + public ulong SizeDownloaded; + public ulong DepotBytesCompressed; + public ulong DepotBytesUncompressed; + + } + + private static async Task DownloadSteam3Async(uint appId, List depots) + { + CancellationTokenSource cts = new CancellationTokenSource(); + cdnPool.ExhaustedToken = cts; + + GlobalDownloadCounter downloadCounter = new GlobalDownloadCounter(); + var depotsToDownload = new List(depots.Count); + var allFileNamesAllDepots = new HashSet(); + + // First, fetch all the manifests for each depot (including previous manifests) and perform the initial setup + foreach (var depot in depots) + { + var depotFileData = await ProcessDepotManifestAndFiles(cts, appId, depot); + + if (depotFileData != null) + { + depotsToDownload.Add(depotFileData); + allFileNamesAllDepots.UnionWith(depotFileData.allFileNames); + } + + cts.Token.ThrowIfCancellationRequested(); + } + + foreach (var depotFileData in depotsToDownload) + { + await DownloadSteam3AsyncDepotFiles(cts, appId, downloadCounter, depotFileData, allFileNamesAllDepots); + } + + Console.WriteLine("Total downloaded: {0} bytes ({1} bytes uncompressed) from {2} depots", + downloadCounter.TotalBytesCompressed, downloadCounter.TotalBytesUncompressed, depots.Count); + } + + private static async Task ProcessDepotManifestAndFiles(CancellationTokenSource cts, + uint appId, DepotDownloadInfo depot) + { + DepotDownloadCounter depotCounter = new DepotDownloadCounter(); + + Console.WriteLine("Processing depot {0} - {1}", depot.id, depot.contentName); + + ProtoManifest oldProtoManifest = null; + ProtoManifest newProtoManifest = null; + string configDir = Path.Combine(depot.installDir, CONFIG_DIR); + + ulong lastManifestId = INVALID_MANIFEST_ID; + DepotConfigStore.Instance.InstalledManifestIDs.TryGetValue(depot.id, out lastManifestId); + + // In case we have an early exit, this will force equiv of verifyall next run. + DepotConfigStore.Instance.InstalledManifestIDs[depot.id] = INVALID_MANIFEST_ID; + DepotConfigStore.Save(); + + if (lastManifestId != INVALID_MANIFEST_ID) + { + var oldManifestFileName = Path.Combine(configDir, string.Format("{0}_{1}.bin", depot.id, lastManifestId)); + + if (File.Exists(oldManifestFileName)) + { + byte[] expectedChecksum, currentChecksum; + + try + { + expectedChecksum = File.ReadAllBytes(oldManifestFileName + ".sha"); + } + catch (IOException) + { + expectedChecksum = null; + } + + oldProtoManifest = ProtoManifest.LoadFromFile(oldManifestFileName, out currentChecksum); + + if (expectedChecksum == null || !expectedChecksum.SequenceEqual(currentChecksum)) + { + // We only have to show this warning if the old manifest ID was different + if (lastManifestId != depot.manifestId) + Console.WriteLine("Manifest {0} on disk did not match the expected checksum.", lastManifestId); + oldProtoManifest = null; + } + } + } + + if (lastManifestId == depot.manifestId && oldProtoManifest != null) + { + newProtoManifest = oldProtoManifest; + Console.WriteLine("Already have manifest {0} for depot {1}.", depot.manifestId, depot.id); + } + else + { + var newManifestFileName = Path.Combine(configDir, string.Format("{0}_{1}.bin", depot.id, depot.manifestId)); + if (newManifestFileName != null) + { + byte[] expectedChecksum, currentChecksum; + + try + { + expectedChecksum = File.ReadAllBytes(newManifestFileName + ".sha"); + } + catch (IOException) + { + expectedChecksum = null; + } + + newProtoManifest = ProtoManifest.LoadFromFile(newManifestFileName, out currentChecksum); + + if (newProtoManifest != null && (expectedChecksum == null || !expectedChecksum.SequenceEqual(currentChecksum))) + { + Console.WriteLine("Manifest {0} on disk did not match the expected checksum.", depot.manifestId); + newProtoManifest = null; + } + } + + if (newProtoManifest != null) + { + Console.WriteLine("Already have manifest {0} for depot {1}.", depot.manifestId, depot.id); + } + else + { + Console.Write("Downloading depot manifest..."); + + DepotManifest depotManifest = null; + + do + { + cts.Token.ThrowIfCancellationRequested(); + + CDNClient.Server connection = null; + + try + { + connection = cdnPool.GetConnection(cts.Token); + var cdnToken = await cdnPool.AuthenticateConnection(appId, depot.id, connection); + +#if STEAMKIT_UNRELEASED + depotManifest = await cdnPool.CDNClient.DownloadManifestAsync(depot.id, depot.manifestId, + connection, cdnToken, depot.depotKey, proxyServer: cdnPool.ProxyServer).ConfigureAwait(false); +#else + depotManifest = await cdnPool.CDNClient.DownloadManifestAsync(depot.id, depot.manifestId, + connection, cdnToken, depot.depotKey).ConfigureAwait(false); +#endif + + cdnPool.ReturnConnection(connection); + } + catch (TaskCanceledException) + { + Console.WriteLine("Connection timeout downloading depot manifest {0} {1}", depot.id, depot.manifestId); + } + catch (SteamKitWebRequestException e) + { + cdnPool.ReturnBrokenConnection(connection); + + if (e.StatusCode == HttpStatusCode.Unauthorized || e.StatusCode == HttpStatusCode.Forbidden) + { + Console.WriteLine("Encountered 401 for depot manifest {0} {1}. Aborting.", depot.id, depot.manifestId); + break; + } + else if (e.StatusCode == HttpStatusCode.NotFound) + { + Console.WriteLine("Encountered 404 for depot manifest {0} {1}. Aborting.", depot.id, depot.manifestId); + break; + } + else + { + Console.WriteLine("Encountered error downloading depot manifest {0} {1}: {2}", depot.id, depot.manifestId, e.StatusCode); + } + } + catch (OperationCanceledException) + { + break; + } + catch (Exception e) + { + cdnPool.ReturnBrokenConnection(connection); + Console.WriteLine("Encountered error downloading manifest for depot {0} {1}: {2}", depot.id, depot.manifestId, e.Message); + } + } + while (depotManifest == null); + + if (depotManifest == null) + { + Console.WriteLine("\nUnable to download manifest {0} for depot {1}", depot.manifestId, depot.id); + cts.Cancel(); + } + + // Throw the cancellation exception if requested so that this task is marked failed + cts.Token.ThrowIfCancellationRequested(); + + byte[] checksum; + + newProtoManifest = new ProtoManifest(depotManifest, depot.manifestId); + newProtoManifest.SaveToFile(newManifestFileName, out checksum); + File.WriteAllBytes(newManifestFileName + ".sha", checksum); + + Console.WriteLine(" Done!"); + } + } + + newProtoManifest.Files.Sort((x, y) => string.Compare(x.FileName, y.FileName, StringComparison.Ordinal)); + + Console.WriteLine("Manifest {0} ({1})", depot.manifestId, newProtoManifest.CreationTime); + + if (Config.DownloadManifestOnly) + { + StringBuilder manifestBuilder = new StringBuilder(); + string txtManifest = Path.Combine(depot.installDir, string.Format("manifest_{0}_{1}.txt", depot.id, depot.manifestId)); + manifestBuilder.Append(string.Format("{0}\n\n", newProtoManifest.CreationTime)); + + foreach (var file in newProtoManifest.Files) + { + if (file.Flags.HasFlag(EDepotFileFlag.Directory)) + continue; + + manifestBuilder.Append(string.Format("{0}\n", file.FileName)); + manifestBuilder.Append(string.Format("\t{0}\n", file.TotalSize)); + manifestBuilder.Append(string.Format("\t{0}\n", BitConverter.ToString(file.FileHash).Replace("-", ""))); + } + + File.WriteAllText(txtManifest, manifestBuilder.ToString()); + return null; + } + + string stagingDir = Path.Combine(depot.installDir, STAGING_DIR); + + var filesAfterExclusions = newProtoManifest.Files.AsParallel().Where(f => TestIsFileIncluded(f.FileName)).ToList(); + var allFileNames = new HashSet(filesAfterExclusions.Count); + + // Pre-process + filesAfterExclusions.ForEach(file => + { + allFileNames.Add(file.FileName); + + var fileFinalPath = Path.Combine(depot.installDir, file.FileName); + var fileStagingPath = Path.Combine(stagingDir, file.FileName); + + if (file.Flags.HasFlag(EDepotFileFlag.Directory)) + { + Directory.CreateDirectory(fileFinalPath); + Directory.CreateDirectory(fileStagingPath); + } + else + { + // Some manifests don't explicitly include all necessary directories + Directory.CreateDirectory(Path.GetDirectoryName(fileFinalPath)); + Directory.CreateDirectory(Path.GetDirectoryName(fileStagingPath)); + + depotCounter.CompleteDownloadSize += file.TotalSize; + } + }); + + return new DepotFilesData + { + depotDownloadInfo = depot, + depotCounter = depotCounter, + stagingDir = stagingDir, + manifest = newProtoManifest, + previousManifest = oldProtoManifest, + filteredFiles = filesAfterExclusions, + allFileNames = allFileNames + }; + } + + private static async Task DownloadSteam3AsyncDepotFiles(CancellationTokenSource cts, uint appId, + GlobalDownloadCounter downloadCounter, DepotFilesData depotFilesData, HashSet allFileNamesAllDepots) + { + var depot = depotFilesData.depotDownloadInfo; + var depotCounter = depotFilesData.depotCounter; + + Console.WriteLine("Downloading depot {0} - {1}", depot.id, depot.contentName); + + var files = depotFilesData.filteredFiles.Where(f => !f.Flags.HasFlag(EDepotFileFlag.Directory)).ToArray(); + var networkChunkQueue = new ConcurrentQueue<(FileStreamData fileStreamData, ProtoManifest.FileData fileData, ProtoManifest.ChunkData chunk)>(); + + await Util.InvokeAsync( + files.Select(file => new Func(async () => + await Task.Run(() => DownloadSteam3AsyncDepotFile(cts, depotFilesData, file, networkChunkQueue)))), + maxDegreeOfParallelism: Config.MaxDownloads + ); + + await Util.InvokeAsync( + networkChunkQueue.Select(q => new Func(async () => + await Task.Run(() => DownloadSteam3AsyncDepotFileChunk(cts, appId, downloadCounter, depotFilesData, + q.fileData, q.fileStreamData, q.chunk)))), + maxDegreeOfParallelism: Config.MaxDownloads + ); + + // Check for deleted files if updating the depot. + if (depotFilesData.previousManifest != null) + { + var previousFilteredFiles = depotFilesData.previousManifest.Files.AsParallel().Where(f => TestIsFileIncluded(f.FileName)).Select(f => f.FileName).ToHashSet(); + + // Check if we are writing to a single output directory. If not, each depot folder is managed independently + if (string.IsNullOrWhiteSpace(ContentDownloader.Config.InstallDirectory)) + { + // Of the list of files in the previous manifest, remove any file names that exist in the current set of all file names + previousFilteredFiles.ExceptWith(depotFilesData.allFileNames); + } + else + { + // Of the list of files in the previous manifest, remove any file names that exist in the current set of all file names across all depots being downloaded + previousFilteredFiles.ExceptWith(allFileNamesAllDepots); + } + + foreach(var existingFileName in previousFilteredFiles) + { + string fileFinalPath = Path.Combine(depot.installDir, existingFileName); + + if (!File.Exists(fileFinalPath)) + continue; + + File.Delete(fileFinalPath); + Console.WriteLine("Deleted {0}", fileFinalPath); + } + } + + DepotConfigStore.Instance.InstalledManifestIDs[depot.id] = depot.manifestId; + DepotConfigStore.Save(); + + Console.WriteLine("Depot {0} - Downloaded {1} bytes ({2} bytes uncompressed)", depot.id, depotCounter.DepotBytesCompressed, depotCounter.DepotBytesUncompressed); + } + + private static void DownloadSteam3AsyncDepotFile( + CancellationTokenSource cts, + DepotFilesData depotFilesData, + ProtoManifest.FileData file, + ConcurrentQueue<(FileStreamData, ProtoManifest.FileData, ProtoManifest.ChunkData)> networkChunkQueue) + { + cts.Token.ThrowIfCancellationRequested(); + + var depot = depotFilesData.depotDownloadInfo; + var stagingDir = depotFilesData.stagingDir; + var depotDownloadCounter = depotFilesData.depotCounter; + var oldProtoManifest = depotFilesData.previousManifest; + + string fileFinalPath = Path.Combine(depot.installDir, file.FileName); + string fileStagingPath = Path.Combine(stagingDir, file.FileName); + + // This may still exist if the previous run exited before cleanup + if (File.Exists(fileStagingPath)) + { + File.Delete(fileStagingPath); + } + + FileStream fs = null; + List neededChunks; + FileInfo fi = new FileInfo(fileFinalPath); + if (!fi.Exists) + { + Console.WriteLine("Pre-allocating {0}", fileFinalPath); + + // create new file. need all chunks + fs = File.Create(fileFinalPath); + fs.SetLength((long)file.TotalSize); + neededChunks = new List(file.Chunks); + } + else + { + // open existing + ProtoManifest.FileData oldManifestFile = null; + if (oldProtoManifest != null) + { + oldManifestFile = oldProtoManifest.Files.SingleOrDefault(f => f.FileName == file.FileName); + } + + if (oldManifestFile != null) + { + neededChunks = new List(); + + if (Config.VerifyAll || !oldManifestFile.FileHash.SequenceEqual(file.FileHash)) + { + // we have a version of this file, but it doesn't fully match what we want + if (Config.VerifyAll) + { + Console.WriteLine("Validating {0}", fileFinalPath); + } + + var matchingChunks = new List(); + + foreach (var chunk in file.Chunks) + { + var oldChunk = oldManifestFile.Chunks.FirstOrDefault(c => c.ChunkID.SequenceEqual(chunk.ChunkID)); + if (oldChunk != null) + { + matchingChunks.Add(new ChunkMatch(oldChunk, chunk)); + } + else + { + neededChunks.Add(chunk); + } + } + + var orderedChunks = matchingChunks.OrderBy(x => x.OldChunk.Offset); + + File.Move(fileFinalPath, fileStagingPath); + + fs = File.Open(fileFinalPath, FileMode.Create); + fs.SetLength((long)file.TotalSize); + + using (var fsOld = File.Open(fileStagingPath, FileMode.Open)) + { + foreach (var match in orderedChunks) + { + fsOld.Seek((long)match.OldChunk.Offset, SeekOrigin.Begin); + + byte[] tmp = new byte[match.OldChunk.UncompressedLength]; + fsOld.Read(tmp, 0, tmp.Length); + + byte[] adler = Util.AdlerHash(tmp); + if (!adler.SequenceEqual(match.OldChunk.Checksum)) + { + neededChunks.Add(match.NewChunk); + } + else + { + fs.Seek((long)match.NewChunk.Offset, SeekOrigin.Begin); + fs.Write(tmp, 0, tmp.Length); + } + } + } + + File.Delete(fileStagingPath); + } + } + else + { + // No old manifest or file not in old manifest. We must validate. + + fs = File.Open(fileFinalPath, FileMode.Open); + if ((ulong)fi.Length != file.TotalSize) + { + fs.SetLength((long)file.TotalSize); + } + + Console.WriteLine("Validating {0}", fileFinalPath); + neededChunks = Util.ValidateSteam3FileChecksums(fs, file.Chunks.OrderBy(x => x.Offset).ToArray()); + } + + if (neededChunks.Count() == 0) + { + lock (depotDownloadCounter) + { + depotDownloadCounter.SizeDownloaded += (ulong)file.TotalSize; + Console.WriteLine("{0,6:#00.00}% {1}", ((float)depotDownloadCounter.SizeDownloaded / (float)depotDownloadCounter.CompleteDownloadSize) * 100.0f, fileFinalPath); + } + + if (fs != null) + fs.Dispose(); + return; + } + else + { + var sizeOnDisk = (file.TotalSize - (ulong)neededChunks.Select(x => (long)x.UncompressedLength).Sum()); + lock (depotDownloadCounter) + { + depotDownloadCounter.SizeDownloaded += sizeOnDisk; + } + } + } + + FileStreamData fileStreamData = new FileStreamData + { + fileStream = fs, + fileLock = new SemaphoreSlim(1), + chunksToDownload = neededChunks.Count + }; + + foreach (var chunk in neededChunks) + { + networkChunkQueue.Enqueue((fileStreamData, file, chunk)); + } + } + + private static async Task DownloadSteam3AsyncDepotFileChunk( + CancellationTokenSource cts, uint appId, + GlobalDownloadCounter downloadCounter, + DepotFilesData depotFilesData, + ProtoManifest.FileData file, + FileStreamData fileStreamData, + ProtoManifest.ChunkData chunk) + { + cts.Token.ThrowIfCancellationRequested(); + + var depot = depotFilesData.depotDownloadInfo; + var depotDownloadCounter = depotFilesData.depotCounter; + + string chunkID = Util.EncodeHexString(chunk.ChunkID); + + DepotManifest.ChunkData data = new DepotManifest.ChunkData(); + data.ChunkID = chunk.ChunkID; + data.Checksum = chunk.Checksum; + data.Offset = chunk.Offset; + data.CompressedLength = chunk.CompressedLength; + data.UncompressedLength = chunk.UncompressedLength; + + CDNClient.DepotChunk chunkData = null; + + do + { + cts.Token.ThrowIfCancellationRequested(); + + CDNClient.Server connection = null; + + try + { + connection = cdnPool.GetConnection(cts.Token); + var cdnToken = await cdnPool.AuthenticateConnection(appId, depot.id, connection); + +#if STEAMKIT_UNRELEASED + chunkData = await cdnPool.CDNClient.DownloadDepotChunkAsync(depot.id, data, + connection, cdnToken, depot.depotKey, proxyServer: cdnPool.ProxyServer).ConfigureAwait(false); +#else + chunkData = await cdnPool.CDNClient.DownloadDepotChunkAsync(depot.id, data, + connection, cdnToken, depot.depotKey).ConfigureAwait(false); +#endif + + cdnPool.ReturnConnection(connection); + } + catch (TaskCanceledException) + { + Console.WriteLine("Connection timeout downloading chunk {0}", chunkID); + } + catch (SteamKitWebRequestException e) + { + cdnPool.ReturnBrokenConnection(connection); + + if (e.StatusCode == HttpStatusCode.Unauthorized || e.StatusCode == HttpStatusCode.Forbidden) + { + Console.WriteLine("Encountered 401 for chunk {0}. Aborting.", chunkID); + break; + } + else + { + Console.WriteLine("Encountered error downloading chunk {0}: {1}", chunkID, e.StatusCode); + } + } + catch (OperationCanceledException) + { + break; + } + catch (Exception e) + { + cdnPool.ReturnBrokenConnection(connection); + Console.WriteLine("Encountered unexpected error downloading chunk {0}: {1}", chunkID, e.Message); + } + } + while (chunkData == null); + + if (chunkData == null) + { + Console.WriteLine("Failed to find any server with chunk {0} for depot {1}. Aborting.", chunkID, depot.id); + cts.Cancel(); + } + + // Throw the cancellation exception if requested so that this task is marked failed + cts.Token.ThrowIfCancellationRequested(); + + try + { + await fileStreamData.fileLock.WaitAsync().ConfigureAwait(false); + + fileStreamData.fileStream.Seek((long)chunkData.ChunkInfo.Offset, SeekOrigin.Begin); + await fileStreamData.fileStream.WriteAsync(chunkData.Data, 0, chunkData.Data.Length); + } + finally + { + fileStreamData.fileLock.Release(); + } + + int remainingChunks = Interlocked.Decrement(ref fileStreamData.chunksToDownload); + if (remainingChunks == 0) + { + fileStreamData.fileStream.Dispose(); + fileStreamData.fileLock.Dispose(); + } + + ulong sizeDownloaded = 0; + lock (depotDownloadCounter) + { + sizeDownloaded = depotDownloadCounter.SizeDownloaded + (ulong)chunkData.Data.Length; + depotDownloadCounter.SizeDownloaded = sizeDownloaded; + depotDownloadCounter.DepotBytesCompressed += chunk.CompressedLength; + depotDownloadCounter.DepotBytesUncompressed += chunk.UncompressedLength; + } + + lock (downloadCounter) + { + downloadCounter.TotalBytesCompressed += chunk.CompressedLength; + downloadCounter.TotalBytesUncompressed += chunk.UncompressedLength; + } + + if (remainingChunks == 0) + { + var fileFinalPath = Path.Combine(depot.installDir, file.FileName); + Console.WriteLine("{0,6:#00.00}% {1}", ((float)sizeDownloaded / (float)depotDownloadCounter.CompleteDownloadSize) * 100.0f, fileFinalPath); + } + + } + } +} diff --git a/depotdownloader/DepotDownloader/DepotConfigStore.cs b/depotdownloader/DepotDownloader/DepotConfigStore.cs index 79a414ab..a16afb24 100644 --- a/depotdownloader/DepotDownloader/DepotConfigStore.cs +++ b/depotdownloader/DepotDownloader/DepotConfigStore.cs @@ -1,58 +1,58 @@ -using System; -using System.Collections.Generic; -using ProtoBuf; -using System.IO; -using System.IO.Compression; - -namespace DepotDownloader -{ - [ProtoContract] - class DepotConfigStore - { - [ProtoMember(1)] - public Dictionary InstalledManifestIDs { get; private set; } - - string FileName = null; - - DepotConfigStore() - { - InstalledManifestIDs = new Dictionary(); - } - - static bool Loaded - { - get { return Instance != null; } - } - - public static DepotConfigStore Instance = null; - - public static void LoadFromFile(string filename) - { - if (Loaded) - throw new Exception("Config already loaded"); - - if (File.Exists(filename)) - { - using (FileStream fs = File.Open(filename, FileMode.Open)) - using (DeflateStream ds = new DeflateStream(fs, CompressionMode.Decompress)) - Instance = ProtoBuf.Serializer.Deserialize(ds); - } - else - { - Instance = new DepotConfigStore(); - } - - Instance.FileName = filename; - } - - public static void Save() - { - if (!Loaded) - throw new Exception("Saved config before loading"); - - using (FileStream fs = File.Open(Instance.FileName, FileMode.Create)) - using (DeflateStream ds = new DeflateStream(fs, CompressionMode.Compress)) - ProtoBuf.Serializer.Serialize(ds, Instance); - } - } -} +using System; +using System.Collections.Generic; +using ProtoBuf; +using System.IO; +using System.IO.Compression; + +namespace DepotDownloader +{ + [ProtoContract] + class DepotConfigStore + { + [ProtoMember(1)] + public Dictionary InstalledManifestIDs { get; private set; } + + string FileName = null; + + DepotConfigStore() + { + InstalledManifestIDs = new Dictionary(); + } + + static bool Loaded + { + get { return Instance != null; } + } + + public static DepotConfigStore Instance = null; + + public static void LoadFromFile(string filename) + { + if (Loaded) + throw new Exception("Config already loaded"); + + if (File.Exists(filename)) + { + using (FileStream fs = File.Open(filename, FileMode.Open)) + using (DeflateStream ds = new DeflateStream(fs, CompressionMode.Decompress)) + Instance = ProtoBuf.Serializer.Deserialize(ds); + } + else + { + Instance = new DepotConfigStore(); + } + + Instance.FileName = filename; + } + + public static void Save() + { + if (!Loaded) + throw new Exception("Saved config before loading"); + + using (FileStream fs = File.Open(Instance.FileName, FileMode.Create)) + using (DeflateStream ds = new DeflateStream(fs, CompressionMode.Compress)) + ProtoBuf.Serializer.Serialize(ds, Instance); + } + } +} diff --git a/depotdownloader/DepotDownloader/DepotDownloader.csproj b/depotdownloader/DepotDownloader/DepotDownloader.csproj index 56756550..1562c0ac 100644 --- a/depotdownloader/DepotDownloader/DepotDownloader.csproj +++ b/depotdownloader/DepotDownloader/DepotDownloader.csproj @@ -1,11 +1,11 @@ - - - Exe - netcoreapp2.0 - false - - - - - + + + Exe + netcoreapp2.0 + false + + + + + \ No newline at end of file diff --git a/depotdownloader/DepotDownloader/DownloadConfig.cs b/depotdownloader/DepotDownloader/DownloadConfig.cs index 3f805acd..780fd56a 100644 --- a/depotdownloader/DepotDownloader/DownloadConfig.cs +++ b/depotdownloader/DepotDownloader/DownloadConfig.cs @@ -1,33 +1,33 @@ -using System.Collections.Generic; -using System.Text.RegularExpressions; - -namespace DepotDownloader -{ - class DownloadConfig - { - public int CellID { get; set; } - public bool DownloadAllPlatforms { get; set; } - public bool DownloadAllLanguages { get; set; } - public bool DownloadManifestOnly { get; set; } - public string InstallDirectory { get; set; } - - public bool UsingFileList { get; set; } - public List FilesToDownload { get; set; } - public List FilesToDownloadRegex { get; set; } - - public bool UsingExclusionList { get; set; } - - public string BetaPassword { get; set; } - - public bool VerifyAll { get; set; } - - public int MaxServers { get; set; } - public int MaxDownloads { get; set; } - - public string SuppliedPassword { get; set; } - public bool RememberPassword { get; set; } - - // A Steam LoginID to allow multiple concurrent connections - public uint? LoginID {get; set; } - } -} +using System.Collections.Generic; +using System.Text.RegularExpressions; + +namespace DepotDownloader +{ + class DownloadConfig + { + public int CellID { get; set; } + public bool DownloadAllPlatforms { get; set; } + public bool DownloadAllLanguages { get; set; } + public bool DownloadManifestOnly { get; set; } + public string InstallDirectory { get; set; } + + public bool UsingFileList { get; set; } + public List FilesToDownload { get; set; } + public List FilesToDownloadRegex { get; set; } + + public bool UsingExclusionList { get; set; } + + public string BetaPassword { get; set; } + + public bool VerifyAll { get; set; } + + public int MaxServers { get; set; } + public int MaxDownloads { get; set; } + + public string SuppliedPassword { get; set; } + public bool RememberPassword { get; set; } + + // A Steam LoginID to allow multiple concurrent connections + public uint? LoginID {get; set; } + } +} diff --git a/depotdownloader/DepotDownloader/Program.cs b/depotdownloader/DepotDownloader/Program.cs index d76770fb..c522c7e4 100644 --- a/depotdownloader/DepotDownloader/Program.cs +++ b/depotdownloader/DepotDownloader/Program.cs @@ -1,416 +1,416 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Text.RegularExpressions; -using SteamKit2; -using System.ComponentModel; -using System.Threading.Tasks; -using System.Runtime.InteropServices; -using System.Linq; - -namespace DepotDownloader -{ - class Program - { - static int Main( string[] args ) - => MainAsync( args ).GetAwaiter().GetResult(); - - static async Task MainAsync( string[] args ) - { - if ( args.Length == 0 ) - { - PrintUsage(); - return 1; - } - - DebugLog.Enabled = false; - - AccountSettingsStore.LoadFromFile( "account.config" ); - - #region Common Options - - if ( HasParameter( args, "-debug" ) ) - { - DebugLog.Enabled = true; - DebugLog.AddListener( ( category, message ) => - { - Console.WriteLine( "[{0}] {1}", category, message ); - }); - } - - string username = GetParameter( args, "-username" ) ?? GetParameter( args, "-user" ); - string password = GetParameter( args, "-password" ) ?? GetParameter( args, "-pass" ); - ContentDownloader.Config.RememberPassword = HasParameter( args, "-remember-password" ); - - ContentDownloader.Config.DownloadManifestOnly = HasParameter( args, "-manifest-only" ); - - int cellId = GetParameter( args, "-cellid", -1 ); - if ( cellId == -1 ) - { - cellId = 0; - } - - ContentDownloader.Config.CellID = cellId; - - string fileList = GetParameter( args, "-filelist" ); - string[] files = null; - - if ( fileList != null ) - { - try - { - string fileListData = File.ReadAllText(fileList); - files = fileListData.Split( new char[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries ); - - ContentDownloader.Config.UsingFileList = true; - ContentDownloader.Config.FilesToDownload = new List(); - ContentDownloader.Config.FilesToDownloadRegex = new List(); - - var isWindows = RuntimeInformation.IsOSPlatform( OSPlatform.Windows ); - foreach ( var fileEntry in files ) - { - try - { - string fileEntryProcessed; - if ( isWindows ) - { - // On Windows, ensure that forward slashes can match either forward or backslashes in depot paths - fileEntryProcessed = fileEntry.Replace( "/", "[\\\\|/]" ); - } - else - { - // On other systems, treat / normally - fileEntryProcessed = fileEntry; - } - Regex rgx = new Regex( fileEntryProcessed, RegexOptions.Compiled | RegexOptions.IgnoreCase ); - ContentDownloader.Config.FilesToDownloadRegex.Add( rgx ); - } - catch - { - // For anything that can't be processed as a Regex, allow both forward and backward slashes to match - // on Windows - if( isWindows ) - { - ContentDownloader.Config.FilesToDownload.Add( fileEntry.Replace( "/", "\\" ) ); - } - ContentDownloader.Config.FilesToDownload.Add( fileEntry ); - continue; - } - } - - Console.WriteLine( "Using filelist: '{0}'.", fileList ); - } - catch (Exception ex) - { - Console.WriteLine( "Warning: Unable to load filelist: {0}", ex.ToString() ); - } - } - - ContentDownloader.Config.InstallDirectory = GetParameter( args, "-dir" ); - - ContentDownloader.Config.VerifyAll = HasParameter( args, "-verify-all" ) || HasParameter( args, "-verify_all" ) || HasParameter( args, "-validate" ); - ContentDownloader.Config.MaxServers = GetParameter( args, "-max-servers", 20 ); - ContentDownloader.Config.MaxDownloads = GetParameter( args, "-max-downloads", 8 ); - ContentDownloader.Config.MaxServers = Math.Max( ContentDownloader.Config.MaxServers, ContentDownloader.Config.MaxDownloads ); - ContentDownloader.Config.LoginID = HasParameter( args, "-loginid" ) ? (uint?)GetParameter( args, "-loginid" ) : null; - - #endregion - - uint appId = GetParameter( args, "-app", ContentDownloader.INVALID_APP_ID ); - if ( appId == ContentDownloader.INVALID_APP_ID ) - { - Console.WriteLine( "Error: -app not specified!" ); - return 1; - } - - ulong pubFile = GetParameter( args, "-pubfile", ContentDownloader.INVALID_MANIFEST_ID ); - ulong ugcId = GetParameter( args, "-ugc", ContentDownloader.INVALID_MANIFEST_ID ); - if ( pubFile != ContentDownloader.INVALID_MANIFEST_ID ) - { - #region Pubfile Downloading - - if ( InitializeSteam( username, password ) ) - { - try - { - await ContentDownloader.DownloadPubfileAsync( appId, pubFile ).ConfigureAwait( false ); - } - catch ( Exception ex ) when ( - ex is ContentDownloaderException - || ex is OperationCanceledException ) - { - Console.WriteLine( ex.Message ); - return 1; - } - catch ( Exception e ) - { - Console.WriteLine( "Download failed to due to an unhandled exception: {0}", e.Message ); - throw; - } - finally - { - ContentDownloader.ShutdownSteam3(); - } - } - else - { - Console.WriteLine( "Error: InitializeSteam failed" ); - return 1; - } - - #endregion - } - else if ( ugcId != ContentDownloader.INVALID_MANIFEST_ID ) - { - #region UGC Downloading - - if ( InitializeSteam( username, password ) ) - { - try - { - await ContentDownloader.DownloadUGCAsync( appId, ugcId ).ConfigureAwait( false ); - } - catch ( Exception ex ) when ( - ex is ContentDownloaderException - || ex is OperationCanceledException ) - { - Console.WriteLine( ex.Message ); - return 1; - } - catch ( Exception e ) - { - Console.WriteLine( "Download failed to due to an unhandled exception: {0}", e.Message ); - throw; - } - finally - { - ContentDownloader.ShutdownSteam3(); - } - } - else - { - Console.WriteLine( "Error: InitializeSteam failed" ); - return 1; - } - - #endregion - } - else - { - #region App downloading - - string branch = GetParameter( args, "-branch" ) ?? GetParameter( args, "-beta" ) ?? ContentDownloader.DEFAULT_BRANCH; - ContentDownloader.Config.BetaPassword = GetParameter( args, "-betapassword" ); - - ContentDownloader.Config.DownloadAllPlatforms = HasParameter( args, "-all-platforms" ); - string os = GetParameter( args, "-os", null ); - - if ( ContentDownloader.Config.DownloadAllPlatforms && !String.IsNullOrEmpty( os ) ) - { - Console.WriteLine("Error: Cannot specify -os when -all-platforms is specified."); - return 1; - } - - string arch = GetParameter( args, "-osarch", null ); - - ContentDownloader.Config.DownloadAllLanguages = HasParameter( args, "-all-languages" ); - string language = GetParameter( args, "-language", null ); - - if ( ContentDownloader.Config.DownloadAllLanguages && !String.IsNullOrEmpty( language ) ) - { - Console.WriteLine( "Error: Cannot specify -language when -all-languages is specified." ); - return 1; - } - - bool lv = HasParameter( args, "-lowviolence" ); - - List<(uint, ulong)> depotManifestIds = new List<(uint, ulong)>(); - bool isUGC = false; - - List depotIdList = GetParameterList( args, "-depot" ); - List manifestIdList = GetParameterList( args, "-manifest" ); - if ( manifestIdList.Count > 0 ) - { - if ( depotIdList.Count != manifestIdList.Count ) - { - Console.WriteLine( "Error: -manifest requires one id for every -depot specified" ); - return 1; - } - - var zippedDepotManifest = depotIdList.Zip( manifestIdList, ( depotId, manifestId ) => ( depotId, manifestId ) ); - depotManifestIds.AddRange( zippedDepotManifest ); - } - else - { - depotManifestIds.AddRange( depotIdList.Select( depotId => ( depotId, ContentDownloader.INVALID_MANIFEST_ID ) ) ); - } - - if ( InitializeSteam( username, password ) ) - { - try - { - await ContentDownloader.DownloadAppAsync( appId, depotManifestIds, branch, os, arch, language, lv, isUGC ).ConfigureAwait( false ); - } - catch ( Exception ex ) when ( - ex is ContentDownloaderException - || ex is OperationCanceledException ) - { - Console.WriteLine( ex.Message ); - return 1; - } - catch ( Exception e ) - { - Console.WriteLine( "Download failed to due to an unhandled exception: {0}", e.Message ); - throw; - } - finally - { - ContentDownloader.ShutdownSteam3(); - } - } - else - { - Console.WriteLine( "Error: InitializeSteam failed" ); - return 1; - } - - #endregion - } - - return 0; - } - - static bool InitializeSteam( string username, string password ) - { - if ( username != null && password == null && ( !ContentDownloader.Config.RememberPassword || !AccountSettingsStore.Instance.LoginKeys.ContainsKey( username ) ) ) - { - do - { - Console.Write( "Enter account password for \"{0}\": ", username ); - if ( Console.IsInputRedirected ) - { - password = Console.ReadLine(); - } - else - { - // Avoid console echoing of password - password = Util.ReadPassword(); - } - Console.WriteLine(); - } while ( String.Empty == password ); - } - else if ( username == null ) - { - Console.WriteLine( "No username given. Using anonymous account with dedicated server subscription." ); - } - - // capture the supplied password in case we need to re-use it after checking the login key - ContentDownloader.Config.SuppliedPassword = password; - - return ContentDownloader.InitializeSteam3( username, password ); - } - - static int IndexOfParam( string[] args, string param ) - { - for ( int x = 0; x < args.Length; ++x ) - { - if ( args[ x ].Equals( param, StringComparison.OrdinalIgnoreCase ) ) - return x; - } - return -1; - } - static bool HasParameter( string[] args, string param ) - { - return IndexOfParam( args, param ) > -1; - } - - static T GetParameter( string[] args, string param, T defaultValue = default( T ) ) - { - int index = IndexOfParam( args, param ); - - if ( index == -1 || index == ( args.Length - 1 ) ) - return defaultValue; - - string strParam = args[ index + 1 ]; - - var converter = TypeDescriptor.GetConverter( typeof( T ) ); - if ( converter != null ) - { - return ( T )converter.ConvertFromString( strParam ); - } - - return default( T ); - } - - static List GetParameterList(string[] args, string param) - { - List list = new List(); - int index = IndexOfParam(args, param); - - if (index == -1 || index == (args.Length - 1)) - return list; - - index++; - - while (index < args.Length) - { - string strParam = args[index]; - - if (strParam[0] == '-') break; - - var converter = TypeDescriptor.GetConverter(typeof(T)); - if (converter != null) - { - list.Add((T)converter.ConvertFromString(strParam)); - } - - index++; - } - - return list; - } - - static void PrintUsage() - { - Console.WriteLine(); - Console.WriteLine( "Usage - downloading one or all depots for an app:" ); - Console.WriteLine( "\tdepotdownloader -app [-depot [-manifest ]]" ); - Console.WriteLine( "\t\t[-username [-password ]] [other options]" ); - Console.WriteLine(); - Console.WriteLine("Usage - downloading a workshop item using pubfile id"); - Console.WriteLine( "\tdepotdownloader -app -pubfile [-username [-password ]]" ); - Console.WriteLine("Usage - downloading a workshop item using ugc id"); - Console.WriteLine("\tdepotdownloader -app -ugc [-username [-password ]]"); - Console.WriteLine(); - Console.WriteLine( "Parameters:" ); - Console.WriteLine( "\t-app <#>\t\t\t\t- the AppID to download." ); - Console.WriteLine( "\t-depot <#>\t\t\t\t- the DepotID to download." ); - Console.WriteLine( "\t-manifest \t\t\t- manifest id of content to download (requires -depot, default: current for branch)." ); - Console.WriteLine( "\t-beta \t\t\t- download from specified branch if available (default: Public)." ); - Console.WriteLine( "\t-betapassword \t\t- branch password if applicable." ); - Console.WriteLine( "\t-all-platforms\t\t\t- downloads all platform-specific depots when -app is used." ); - Console.WriteLine( "\t-os \t\t\t\t- the operating system for which to download the game (windows, macos or linux, default: OS the program is currently running on)" ); - Console.WriteLine( "\t-osarch \t\t\t\t- the architecture for which to download the game (32 or 64, default: the host's architecture)" ); - Console.WriteLine( "\t-all-languages\t\t\t\t- download all language-specific depots when -app is used." ); - Console.WriteLine( "\t-language \t\t\t\t- the language for which to download the game (default: english)" ); - Console.WriteLine( "\t-lowviolence\t\t\t\t- download low violence depots when -app is used." ); - Console.WriteLine(); - Console.WriteLine( "\t-ugc <#>\t\t\t\t- the UGC ID to download." ); - Console.WriteLine( "\t-pubfile <#>\t\t\t- the PublishedFileId to download. (Will automatically resolve to UGC id)" ); - Console.WriteLine(); - Console.WriteLine( "\t-username \t\t- the username of the account to login to for restricted content."); - Console.WriteLine( "\t-password \t\t- the password of the account to login to for restricted content." ); - Console.WriteLine( "\t-remember-password\t\t- if set, remember the password for subsequent logins of this user." ); - Console.WriteLine(); - Console.WriteLine( "\t-dir \t\t- the directory in which to place downloaded files." ); - Console.WriteLine( "\t-filelist \t- a list of files to download (from the manifest). Can optionally use regex to download only certain files." ); - Console.WriteLine( "\t-validate\t\t\t\t- Include checksum verification of files already downloaded" ); - Console.WriteLine(); - Console.WriteLine( "\t-manifest-only\t\t\t- downloads a human readable manifest for any depots that would be downloaded." ); - Console.WriteLine( "\t-cellid <#>\t\t\t\t- the overridden CellID of the content server to download from." ); - Console.WriteLine( "\t-max-servers <#>\t\t- maximum number of content servers to use. (default: 20)." ); - Console.WriteLine( "\t-max-downloads <#>\t\t- maximum number of chunks to download concurrently. (default: 8)." ); - Console.WriteLine( "\t-loginid <#>\t\t- a unique 32-bit integer Steam LogonID in decimal, required if running multiple instances of DepotDownloader concurrently." ); - } - } -} +using System; +using System.Collections.Generic; +using System.IO; +using System.Text.RegularExpressions; +using SteamKit2; +using System.ComponentModel; +using System.Threading.Tasks; +using System.Runtime.InteropServices; +using System.Linq; + +namespace DepotDownloader +{ + class Program + { + static int Main( string[] args ) + => MainAsync( args ).GetAwaiter().GetResult(); + + static async Task MainAsync( string[] args ) + { + if ( args.Length == 0 ) + { + PrintUsage(); + return 1; + } + + DebugLog.Enabled = false; + + AccountSettingsStore.LoadFromFile( "account.config" ); + + #region Common Options + + if ( HasParameter( args, "-debug" ) ) + { + DebugLog.Enabled = true; + DebugLog.AddListener( ( category, message ) => + { + Console.WriteLine( "[{0}] {1}", category, message ); + }); + } + + string username = GetParameter( args, "-username" ) ?? GetParameter( args, "-user" ); + string password = GetParameter( args, "-password" ) ?? GetParameter( args, "-pass" ); + ContentDownloader.Config.RememberPassword = HasParameter( args, "-remember-password" ); + + ContentDownloader.Config.DownloadManifestOnly = HasParameter( args, "-manifest-only" ); + + int cellId = GetParameter( args, "-cellid", -1 ); + if ( cellId == -1 ) + { + cellId = 0; + } + + ContentDownloader.Config.CellID = cellId; + + string fileList = GetParameter( args, "-filelist" ); + string[] files = null; + + if ( fileList != null ) + { + try + { + string fileListData = File.ReadAllText(fileList); + files = fileListData.Split( new char[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries ); + + ContentDownloader.Config.UsingFileList = true; + ContentDownloader.Config.FilesToDownload = new List(); + ContentDownloader.Config.FilesToDownloadRegex = new List(); + + var isWindows = RuntimeInformation.IsOSPlatform( OSPlatform.Windows ); + foreach ( var fileEntry in files ) + { + try + { + string fileEntryProcessed; + if ( isWindows ) + { + // On Windows, ensure that forward slashes can match either forward or backslashes in depot paths + fileEntryProcessed = fileEntry.Replace( "/", "[\\\\|/]" ); + } + else + { + // On other systems, treat / normally + fileEntryProcessed = fileEntry; + } + Regex rgx = new Regex( fileEntryProcessed, RegexOptions.Compiled | RegexOptions.IgnoreCase ); + ContentDownloader.Config.FilesToDownloadRegex.Add( rgx ); + } + catch + { + // For anything that can't be processed as a Regex, allow both forward and backward slashes to match + // on Windows + if( isWindows ) + { + ContentDownloader.Config.FilesToDownload.Add( fileEntry.Replace( "/", "\\" ) ); + } + ContentDownloader.Config.FilesToDownload.Add( fileEntry ); + continue; + } + } + + Console.WriteLine( "Using filelist: '{0}'.", fileList ); + } + catch (Exception ex) + { + Console.WriteLine( "Warning: Unable to load filelist: {0}", ex.ToString() ); + } + } + + ContentDownloader.Config.InstallDirectory = GetParameter( args, "-dir" ); + + ContentDownloader.Config.VerifyAll = HasParameter( args, "-verify-all" ) || HasParameter( args, "-verify_all" ) || HasParameter( args, "-validate" ); + ContentDownloader.Config.MaxServers = GetParameter( args, "-max-servers", 20 ); + ContentDownloader.Config.MaxDownloads = GetParameter( args, "-max-downloads", 8 ); + ContentDownloader.Config.MaxServers = Math.Max( ContentDownloader.Config.MaxServers, ContentDownloader.Config.MaxDownloads ); + ContentDownloader.Config.LoginID = HasParameter( args, "-loginid" ) ? (uint?)GetParameter( args, "-loginid" ) : null; + + #endregion + + uint appId = GetParameter( args, "-app", ContentDownloader.INVALID_APP_ID ); + if ( appId == ContentDownloader.INVALID_APP_ID ) + { + Console.WriteLine( "Error: -app not specified!" ); + return 1; + } + + ulong pubFile = GetParameter( args, "-pubfile", ContentDownloader.INVALID_MANIFEST_ID ); + ulong ugcId = GetParameter( args, "-ugc", ContentDownloader.INVALID_MANIFEST_ID ); + if ( pubFile != ContentDownloader.INVALID_MANIFEST_ID ) + { + #region Pubfile Downloading + + if ( InitializeSteam( username, password ) ) + { + try + { + await ContentDownloader.DownloadPubfileAsync( appId, pubFile ).ConfigureAwait( false ); + } + catch ( Exception ex ) when ( + ex is ContentDownloaderException + || ex is OperationCanceledException ) + { + Console.WriteLine( ex.Message ); + return 1; + } + catch ( Exception e ) + { + Console.WriteLine( "Download failed to due to an unhandled exception: {0}", e.Message ); + throw; + } + finally + { + ContentDownloader.ShutdownSteam3(); + } + } + else + { + Console.WriteLine( "Error: InitializeSteam failed" ); + return 1; + } + + #endregion + } + else if ( ugcId != ContentDownloader.INVALID_MANIFEST_ID ) + { + #region UGC Downloading + + if ( InitializeSteam( username, password ) ) + { + try + { + await ContentDownloader.DownloadUGCAsync( appId, ugcId ).ConfigureAwait( false ); + } + catch ( Exception ex ) when ( + ex is ContentDownloaderException + || ex is OperationCanceledException ) + { + Console.WriteLine( ex.Message ); + return 1; + } + catch ( Exception e ) + { + Console.WriteLine( "Download failed to due to an unhandled exception: {0}", e.Message ); + throw; + } + finally + { + ContentDownloader.ShutdownSteam3(); + } + } + else + { + Console.WriteLine( "Error: InitializeSteam failed" ); + return 1; + } + + #endregion + } + else + { + #region App downloading + + string branch = GetParameter( args, "-branch" ) ?? GetParameter( args, "-beta" ) ?? ContentDownloader.DEFAULT_BRANCH; + ContentDownloader.Config.BetaPassword = GetParameter( args, "-betapassword" ); + + ContentDownloader.Config.DownloadAllPlatforms = HasParameter( args, "-all-platforms" ); + string os = GetParameter( args, "-os", null ); + + if ( ContentDownloader.Config.DownloadAllPlatforms && !String.IsNullOrEmpty( os ) ) + { + Console.WriteLine("Error: Cannot specify -os when -all-platforms is specified."); + return 1; + } + + string arch = GetParameter( args, "-osarch", null ); + + ContentDownloader.Config.DownloadAllLanguages = HasParameter( args, "-all-languages" ); + string language = GetParameter( args, "-language", null ); + + if ( ContentDownloader.Config.DownloadAllLanguages && !String.IsNullOrEmpty( language ) ) + { + Console.WriteLine( "Error: Cannot specify -language when -all-languages is specified." ); + return 1; + } + + bool lv = HasParameter( args, "-lowviolence" ); + + List<(uint, ulong)> depotManifestIds = new List<(uint, ulong)>(); + bool isUGC = false; + + List depotIdList = GetParameterList( args, "-depot" ); + List manifestIdList = GetParameterList( args, "-manifest" ); + if ( manifestIdList.Count > 0 ) + { + if ( depotIdList.Count != manifestIdList.Count ) + { + Console.WriteLine( "Error: -manifest requires one id for every -depot specified" ); + return 1; + } + + var zippedDepotManifest = depotIdList.Zip( manifestIdList, ( depotId, manifestId ) => ( depotId, manifestId ) ); + depotManifestIds.AddRange( zippedDepotManifest ); + } + else + { + depotManifestIds.AddRange( depotIdList.Select( depotId => ( depotId, ContentDownloader.INVALID_MANIFEST_ID ) ) ); + } + + if ( InitializeSteam( username, password ) ) + { + try + { + await ContentDownloader.DownloadAppAsync( appId, depotManifestIds, branch, os, arch, language, lv, isUGC ).ConfigureAwait( false ); + } + catch ( Exception ex ) when ( + ex is ContentDownloaderException + || ex is OperationCanceledException ) + { + Console.WriteLine( ex.Message ); + return 1; + } + catch ( Exception e ) + { + Console.WriteLine( "Download failed to due to an unhandled exception: {0}", e.Message ); + throw; + } + finally + { + ContentDownloader.ShutdownSteam3(); + } + } + else + { + Console.WriteLine( "Error: InitializeSteam failed" ); + return 1; + } + + #endregion + } + + return 0; + } + + static bool InitializeSteam( string username, string password ) + { + if ( username != null && password == null && ( !ContentDownloader.Config.RememberPassword || !AccountSettingsStore.Instance.LoginKeys.ContainsKey( username ) ) ) + { + do + { + Console.Write( "Enter account password for \"{0}\": ", username ); + if ( Console.IsInputRedirected ) + { + password = Console.ReadLine(); + } + else + { + // Avoid console echoing of password + password = Util.ReadPassword(); + } + Console.WriteLine(); + } while ( String.Empty == password ); + } + else if ( username == null ) + { + Console.WriteLine( "No username given. Using anonymous account with dedicated server subscription." ); + } + + // capture the supplied password in case we need to re-use it after checking the login key + ContentDownloader.Config.SuppliedPassword = password; + + return ContentDownloader.InitializeSteam3( username, password ); + } + + static int IndexOfParam( string[] args, string param ) + { + for ( int x = 0; x < args.Length; ++x ) + { + if ( args[ x ].Equals( param, StringComparison.OrdinalIgnoreCase ) ) + return x; + } + return -1; + } + static bool HasParameter( string[] args, string param ) + { + return IndexOfParam( args, param ) > -1; + } + + static T GetParameter( string[] args, string param, T defaultValue = default( T ) ) + { + int index = IndexOfParam( args, param ); + + if ( index == -1 || index == ( args.Length - 1 ) ) + return defaultValue; + + string strParam = args[ index + 1 ]; + + var converter = TypeDescriptor.GetConverter( typeof( T ) ); + if ( converter != null ) + { + return ( T )converter.ConvertFromString( strParam ); + } + + return default( T ); + } + + static List GetParameterList(string[] args, string param) + { + List list = new List(); + int index = IndexOfParam(args, param); + + if (index == -1 || index == (args.Length - 1)) + return list; + + index++; + + while (index < args.Length) + { + string strParam = args[index]; + + if (strParam[0] == '-') break; + + var converter = TypeDescriptor.GetConverter(typeof(T)); + if (converter != null) + { + list.Add((T)converter.ConvertFromString(strParam)); + } + + index++; + } + + return list; + } + + static void PrintUsage() + { + Console.WriteLine(); + Console.WriteLine( "Usage - downloading one or all depots for an app:" ); + Console.WriteLine( "\tdepotdownloader -app [-depot [-manifest ]]" ); + Console.WriteLine( "\t\t[-username [-password ]] [other options]" ); + Console.WriteLine(); + Console.WriteLine("Usage - downloading a workshop item using pubfile id"); + Console.WriteLine( "\tdepotdownloader -app -pubfile [-username [-password ]]" ); + Console.WriteLine("Usage - downloading a workshop item using ugc id"); + Console.WriteLine("\tdepotdownloader -app -ugc [-username [-password ]]"); + Console.WriteLine(); + Console.WriteLine( "Parameters:" ); + Console.WriteLine( "\t-app <#>\t\t\t\t- the AppID to download." ); + Console.WriteLine( "\t-depot <#>\t\t\t\t- the DepotID to download." ); + Console.WriteLine( "\t-manifest \t\t\t- manifest id of content to download (requires -depot, default: current for branch)." ); + Console.WriteLine( "\t-beta \t\t\t- download from specified branch if available (default: Public)." ); + Console.WriteLine( "\t-betapassword \t\t- branch password if applicable." ); + Console.WriteLine( "\t-all-platforms\t\t\t- downloads all platform-specific depots when -app is used." ); + Console.WriteLine( "\t-os \t\t\t\t- the operating system for which to download the game (windows, macos or linux, default: OS the program is currently running on)" ); + Console.WriteLine( "\t-osarch \t\t\t\t- the architecture for which to download the game (32 or 64, default: the host's architecture)" ); + Console.WriteLine( "\t-all-languages\t\t\t\t- download all language-specific depots when -app is used." ); + Console.WriteLine( "\t-language \t\t\t\t- the language for which to download the game (default: english)" ); + Console.WriteLine( "\t-lowviolence\t\t\t\t- download low violence depots when -app is used." ); + Console.WriteLine(); + Console.WriteLine( "\t-ugc <#>\t\t\t\t- the UGC ID to download." ); + Console.WriteLine( "\t-pubfile <#>\t\t\t- the PublishedFileId to download. (Will automatically resolve to UGC id)" ); + Console.WriteLine(); + Console.WriteLine( "\t-username \t\t- the username of the account to login to for restricted content."); + Console.WriteLine( "\t-password \t\t- the password of the account to login to for restricted content." ); + Console.WriteLine( "\t-remember-password\t\t- if set, remember the password for subsequent logins of this user." ); + Console.WriteLine(); + Console.WriteLine( "\t-dir \t\t- the directory in which to place downloaded files." ); + Console.WriteLine( "\t-filelist \t- a list of files to download (from the manifest). Can optionally use regex to download only certain files." ); + Console.WriteLine( "\t-validate\t\t\t\t- Include checksum verification of files already downloaded" ); + Console.WriteLine(); + Console.WriteLine( "\t-manifest-only\t\t\t- downloads a human readable manifest for any depots that would be downloaded." ); + Console.WriteLine( "\t-cellid <#>\t\t\t\t- the overridden CellID of the content server to download from." ); + Console.WriteLine( "\t-max-servers <#>\t\t- maximum number of content servers to use. (default: 20)." ); + Console.WriteLine( "\t-max-downloads <#>\t\t- maximum number of chunks to download concurrently. (default: 8)." ); + Console.WriteLine( "\t-loginid <#>\t\t- a unique 32-bit integer Steam LogonID in decimal, required if running multiple instances of DepotDownloader concurrently." ); + } + } +} diff --git a/depotdownloader/DepotDownloader/Properties/AssemblyInfo.cs b/depotdownloader/DepotDownloader/Properties/AssemblyInfo.cs index 216d43e5..6c79ae50 100644 --- a/depotdownloader/DepotDownloader/Properties/AssemblyInfo.cs +++ b/depotdownloader/DepotDownloader/Properties/AssemblyInfo.cs @@ -1,34 +1,34 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle( "DepotDownloader" )] -[assembly: AssemblyDescription("Steam Downloading Utility")] -[assembly: AssemblyConfiguration( "" )] -[assembly: AssemblyCompany("SteamRE Team")] -[assembly: AssemblyProduct( "DepotDownloader" )] -[assembly: AssemblyCopyright("Copyright © SteamRE Team 2017")] -[assembly: AssemblyTrademark( "" )] -[assembly: AssemblyCulture( "" )] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible( false )] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid( "df2ab32a-923c-46e3-a1b4-c901ee92ec94" )] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("2.3.0.0")] +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle( "DepotDownloader" )] +[assembly: AssemblyDescription("Steam Downloading Utility")] +[assembly: AssemblyConfiguration( "" )] +[assembly: AssemblyCompany("SteamRE Team")] +[assembly: AssemblyProduct( "DepotDownloader" )] +[assembly: AssemblyCopyright("Copyright © SteamRE Team 2017")] +[assembly: AssemblyTrademark( "" )] +[assembly: AssemblyCulture( "" )] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible( false )] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid( "df2ab32a-923c-46e3-a1b4-c901ee92ec94" )] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("2.3.0.0")] diff --git a/depotdownloader/DepotDownloader/Properties/launchSettings.json b/depotdownloader/DepotDownloader/Properties/launchSettings.json index 2fda5705..77dc59ee 100644 --- a/depotdownloader/DepotDownloader/Properties/launchSettings.json +++ b/depotdownloader/DepotDownloader/Properties/launchSettings.json @@ -1,7 +1,7 @@ -{ - "profiles": { - "DepotDownloader": { - "commandName": "Project" - } - } +{ + "profiles": { + "DepotDownloader": { + "commandName": "Project" + } + } } \ No newline at end of file diff --git a/depotdownloader/DepotDownloader/ProtoManifest.cs b/depotdownloader/DepotDownloader/ProtoManifest.cs index f521c5cc..1bddb862 100644 --- a/depotdownloader/DepotDownloader/ProtoManifest.cs +++ b/depotdownloader/DepotDownloader/ProtoManifest.cs @@ -1,163 +1,163 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.IO.Compression; - -using ProtoBuf; -using SteamKit2; - -namespace DepotDownloader -{ - [ProtoContract()] - class ProtoManifest - { - // Proto ctor - private ProtoManifest() - { - Files = new List(); - } - - public ProtoManifest(DepotManifest sourceManifest, ulong id) : this() - { - sourceManifest.Files.ForEach(f => Files.Add(new FileData(f))); - ID = id; - CreationTime = sourceManifest.CreationTime; - } - - [ProtoContract()] - public class FileData - { - // Proto ctor - private FileData() - { - Chunks = new List(); - } - - public FileData(DepotManifest.FileData sourceData) : this() - { - FileName = sourceData.FileName; - sourceData.Chunks.ForEach(c => Chunks.Add(new ChunkData(c))); - Flags = sourceData.Flags; - TotalSize = sourceData.TotalSize; - FileHash = sourceData.FileHash; - } - - [ProtoMember(1)] - public string FileName { get; internal set; } - - /// - /// Gets the chunks that this file is composed of. - /// - [ProtoMember(2)] - public List Chunks { get; private set; } - - /// - /// Gets the file flags - /// - [ProtoMember(3)] - public EDepotFileFlag Flags { get; private set; } - - /// - /// Gets the total size of this file. - /// - [ProtoMember(4)] - public ulong TotalSize { get; private set; } - - /// - /// Gets the hash of this file. - /// - [ProtoMember(5)] - public byte[] FileHash { get; private set; } - } - - [ProtoContract(SkipConstructor = true)] - public class ChunkData - { - public ChunkData(DepotManifest.ChunkData sourceChunk) - { - ChunkID = sourceChunk.ChunkID; - Checksum = sourceChunk.Checksum; - Offset = sourceChunk.Offset; - CompressedLength = sourceChunk.CompressedLength; - UncompressedLength = sourceChunk.UncompressedLength; - } - - /// - /// Gets the SHA-1 hash chunk id. - /// - [ProtoMember(1)] - public byte[] ChunkID { get; private set; } - - /// - /// Gets the expected Adler32 checksum of this chunk. - /// - [ProtoMember(2)] - public byte[] Checksum { get; private set; } - - /// - /// Gets the chunk offset. - /// - [ProtoMember(3)] - public ulong Offset { get; private set; } - - /// - /// Gets the compressed length of this chunk. - /// - [ProtoMember(4)] - public uint CompressedLength { get; private set; } - - /// - /// Gets the decompressed length of this chunk. - /// - [ProtoMember(5)] - public uint UncompressedLength { get; private set; } - } - - [ProtoMember(1)] - public List Files { get; private set; } - - [ProtoMember(2)] - public ulong ID { get; private set; } - - [ProtoMember(3)] - public DateTime CreationTime { get; private set; } - - public static ProtoManifest LoadFromFile(string filename, out byte[] checksum) - { - if (!File.Exists(filename)) - { - checksum = null; - return null; - } - - using (MemoryStream ms = new MemoryStream()) - { - using (FileStream fs = File.Open(filename, FileMode.Open)) - using (DeflateStream ds = new DeflateStream(fs, CompressionMode.Decompress)) - ds.CopyTo(ms); - - checksum = Util.SHAHash(ms.ToArray()); - - ms.Seek(0, SeekOrigin.Begin); - return ProtoBuf.Serializer.Deserialize(ms); - } - } - - public void SaveToFile(string filename, out byte[] checksum) - { - - using (MemoryStream ms = new MemoryStream()) - { - ProtoBuf.Serializer.Serialize(ms, this); - - checksum = Util.SHAHash(ms.ToArray()); - - ms.Seek(0, SeekOrigin.Begin); - - using (FileStream fs = File.Open(filename, FileMode.Create)) - using (DeflateStream ds = new DeflateStream(fs, CompressionMode.Compress)) - ms.CopyTo(ds); - } - } - } -} +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; + +using ProtoBuf; +using SteamKit2; + +namespace DepotDownloader +{ + [ProtoContract()] + class ProtoManifest + { + // Proto ctor + private ProtoManifest() + { + Files = new List(); + } + + public ProtoManifest(DepotManifest sourceManifest, ulong id) : this() + { + sourceManifest.Files.ForEach(f => Files.Add(new FileData(f))); + ID = id; + CreationTime = sourceManifest.CreationTime; + } + + [ProtoContract()] + public class FileData + { + // Proto ctor + private FileData() + { + Chunks = new List(); + } + + public FileData(DepotManifest.FileData sourceData) : this() + { + FileName = sourceData.FileName; + sourceData.Chunks.ForEach(c => Chunks.Add(new ChunkData(c))); + Flags = sourceData.Flags; + TotalSize = sourceData.TotalSize; + FileHash = sourceData.FileHash; + } + + [ProtoMember(1)] + public string FileName { get; internal set; } + + /// + /// Gets the chunks that this file is composed of. + /// + [ProtoMember(2)] + public List Chunks { get; private set; } + + /// + /// Gets the file flags + /// + [ProtoMember(3)] + public EDepotFileFlag Flags { get; private set; } + + /// + /// Gets the total size of this file. + /// + [ProtoMember(4)] + public ulong TotalSize { get; private set; } + + /// + /// Gets the hash of this file. + /// + [ProtoMember(5)] + public byte[] FileHash { get; private set; } + } + + [ProtoContract(SkipConstructor = true)] + public class ChunkData + { + public ChunkData(DepotManifest.ChunkData sourceChunk) + { + ChunkID = sourceChunk.ChunkID; + Checksum = sourceChunk.Checksum; + Offset = sourceChunk.Offset; + CompressedLength = sourceChunk.CompressedLength; + UncompressedLength = sourceChunk.UncompressedLength; + } + + /// + /// Gets the SHA-1 hash chunk id. + /// + [ProtoMember(1)] + public byte[] ChunkID { get; private set; } + + /// + /// Gets the expected Adler32 checksum of this chunk. + /// + [ProtoMember(2)] + public byte[] Checksum { get; private set; } + + /// + /// Gets the chunk offset. + /// + [ProtoMember(3)] + public ulong Offset { get; private set; } + + /// + /// Gets the compressed length of this chunk. + /// + [ProtoMember(4)] + public uint CompressedLength { get; private set; } + + /// + /// Gets the decompressed length of this chunk. + /// + [ProtoMember(5)] + public uint UncompressedLength { get; private set; } + } + + [ProtoMember(1)] + public List Files { get; private set; } + + [ProtoMember(2)] + public ulong ID { get; private set; } + + [ProtoMember(3)] + public DateTime CreationTime { get; private set; } + + public static ProtoManifest LoadFromFile(string filename, out byte[] checksum) + { + if (!File.Exists(filename)) + { + checksum = null; + return null; + } + + using (MemoryStream ms = new MemoryStream()) + { + using (FileStream fs = File.Open(filename, FileMode.Open)) + using (DeflateStream ds = new DeflateStream(fs, CompressionMode.Decompress)) + ds.CopyTo(ms); + + checksum = Util.SHAHash(ms.ToArray()); + + ms.Seek(0, SeekOrigin.Begin); + return ProtoBuf.Serializer.Deserialize(ms); + } + } + + public void SaveToFile(string filename, out byte[] checksum) + { + + using (MemoryStream ms = new MemoryStream()) + { + ProtoBuf.Serializer.Serialize(ms, this); + + checksum = Util.SHAHash(ms.ToArray()); + + ms.Seek(0, SeekOrigin.Begin); + + using (FileStream fs = File.Open(filename, FileMode.Create)) + using (DeflateStream ds = new DeflateStream(fs, CompressionMode.Compress)) + ms.CopyTo(ds); + } + } + } +} diff --git a/depotdownloader/DepotDownloader/Steam3Session.cs b/depotdownloader/DepotDownloader/Steam3Session.cs index 43b99c9e..25d35962 100644 --- a/depotdownloader/DepotDownloader/Steam3Session.cs +++ b/depotdownloader/DepotDownloader/Steam3Session.cs @@ -1,771 +1,771 @@ -using SteamKit2; -using SteamKit2.Internal; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace DepotDownloader -{ - - class Steam3Session - { - public class Credentials - { - public bool LoggedOn { get; set; } - public ulong SessionToken { get; set; } - - public bool IsValid - { - get { return LoggedOn; } - } - } - - public ReadOnlyCollection Licenses - { - get; - private set; - } - - public Dictionary AppTickets { get; private set; } - public Dictionary AppTokens { get; private set; } - public Dictionary PackageTokens { get; private set; } - public Dictionary DepotKeys { get; private set; } - public ConcurrentDictionary> CDNAuthTokens { get; private set; } - public Dictionary AppInfo { get; private set; } - public Dictionary PackageInfo { get; private set; } - public Dictionary AppBetaPasswords { get; private set; } - - public SteamClient steamClient; - public SteamUser steamUser; - SteamApps steamApps; - SteamCloud steamCloud; - SteamUnifiedMessages.UnifiedService steamPublishedFile; - - CallbackManager callbacks; - - bool authenticatedUser; - bool bConnected; - bool bConnecting; - bool bAborted; - bool bExpectingDisconnectRemote; - bool bDidDisconnect; - bool bDidReceiveLoginKey; - int connectionBackoff; - int seq; // more hack fixes - DateTime connectTime; - - // input - SteamUser.LogOnDetails logonDetails; - - // output - Credentials credentials; - - static readonly TimeSpan STEAM3_TIMEOUT = TimeSpan.FromSeconds( 30 ); - - - public Steam3Session( SteamUser.LogOnDetails details ) - { - this.logonDetails = details; - - this.authenticatedUser = details.Username != null; - this.credentials = new Credentials(); - this.bConnected = false; - this.bConnecting = false; - this.bAborted = false; - this.bExpectingDisconnectRemote = false; - this.bDidDisconnect = false; - this.bDidReceiveLoginKey = false; - this.seq = 0; - - this.AppTickets = new Dictionary(); - this.AppTokens = new Dictionary(); - this.PackageTokens = new Dictionary(); - this.DepotKeys = new Dictionary(); - this.CDNAuthTokens = new ConcurrentDictionary>(); - this.AppInfo = new Dictionary(); - this.PackageInfo = new Dictionary(); - this.AppBetaPasswords = new Dictionary(); - - this.steamClient = new SteamClient(); - - this.steamUser = this.steamClient.GetHandler(); - this.steamApps = this.steamClient.GetHandler(); - this.steamCloud = this.steamClient.GetHandler(); - var steamUnifiedMessages = this.steamClient.GetHandler(); - this.steamPublishedFile = steamUnifiedMessages.CreateService(); - - this.callbacks = new CallbackManager( this.steamClient ); - - this.callbacks.Subscribe( ConnectedCallback ); - this.callbacks.Subscribe( DisconnectedCallback ); - this.callbacks.Subscribe( LogOnCallback ); - this.callbacks.Subscribe( SessionTokenCallback ); - this.callbacks.Subscribe( LicenseListCallback ); - this.callbacks.Subscribe( UpdateMachineAuthCallback ); - this.callbacks.Subscribe( LoginKeyCallback ); - - Console.Write( "Connecting to Steam3..." ); - - if ( authenticatedUser ) - { - FileInfo fi = new FileInfo( String.Format( "{0}.sentryFile", logonDetails.Username ) ); - if ( AccountSettingsStore.Instance.SentryData != null && AccountSettingsStore.Instance.SentryData.ContainsKey( logonDetails.Username ) ) - { - logonDetails.SentryFileHash = Util.SHAHash( AccountSettingsStore.Instance.SentryData[ logonDetails.Username ] ); - } - else if ( fi.Exists && fi.Length > 0 ) - { - var sentryData = File.ReadAllBytes( fi.FullName ); - logonDetails.SentryFileHash = Util.SHAHash( sentryData ); - AccountSettingsStore.Instance.SentryData[ logonDetails.Username ] = sentryData; - AccountSettingsStore.Save(); - } - } - - Connect(); - } - - public delegate bool WaitCondition(); - private object steamLock = new object(); - - public bool WaitUntilCallback( Action submitter, WaitCondition waiter ) - { - while ( !bAborted && !waiter() ) - { - lock (steamLock) - { - submitter(); - } - - int seq = this.seq; - do - { - lock (steamLock) - { - WaitForCallbacks(); - } - } - while ( !bAborted && this.seq == seq && !waiter() ); - } - - return bAborted; - } - - public Credentials WaitForCredentials() - { - if ( credentials.IsValid || bAborted ) - return credentials; - - WaitUntilCallback( () => { }, () => { return credentials.IsValid; } ); - - return credentials; - } - - public void RequestAppInfo( uint appId, bool bForce = false ) - { - if ( ( AppInfo.ContainsKey( appId ) && !bForce ) || bAborted ) - return; - - bool completed = false; - Action cbMethodTokens = ( appTokens ) => - { - completed = true; - if ( appTokens.AppTokensDenied.Contains( appId ) ) - { - Console.WriteLine( "Insufficient privileges to get access token for app {0}", appId ); - } - - foreach ( var token_dict in appTokens.AppTokens ) - { - this.AppTokens[ token_dict.Key ] = token_dict.Value; - } - }; - - WaitUntilCallback( () => - { - callbacks.Subscribe( steamApps.PICSGetAccessTokens( new List() { appId }, new List() { } ), cbMethodTokens ); - }, () => { return completed; } ); - - completed = false; - Action cbMethod = ( appInfo ) => - { - completed = !appInfo.ResponsePending; - - foreach ( var app_value in appInfo.Apps ) - { - var app = app_value.Value; - - Console.WriteLine( "Got AppInfo for {0}", app.ID ); - AppInfo[ app.ID ] = app; - } - - foreach ( var app in appInfo.UnknownApps ) - { - AppInfo[ app ] = null; - } - }; - - SteamApps.PICSRequest request = new SteamApps.PICSRequest( appId ); - if ( AppTokens.ContainsKey( appId ) ) - { - request.AccessToken = AppTokens[ appId ]; - request.Public = false; - } - - WaitUntilCallback( () => - { - callbacks.Subscribe( steamApps.PICSGetProductInfo( new List() { request }, new List() { } ), cbMethod ); - }, () => { return completed; } ); - } - - public void RequestPackageInfo( IEnumerable packageIds ) - { - List packages = packageIds.ToList(); - packages.RemoveAll( pid => PackageInfo.ContainsKey( pid ) ); - - if ( packages.Count == 0 || bAborted ) - return; - - bool completed = false; - Action cbMethod = ( packageInfo ) => - { - completed = !packageInfo.ResponsePending; - - foreach ( var package_value in packageInfo.Packages ) - { - var package = package_value.Value; - PackageInfo[ package.ID ] = package; - } - - foreach ( var package in packageInfo.UnknownPackages ) - { - PackageInfo[package] = null; - } - }; - - var packageRequests = new List(); - - foreach ( var package in packages ) - { - var request = new SteamApps.PICSRequest( package ); - - if ( PackageTokens.TryGetValue( package, out var token ) ) - { - request.AccessToken = token; - request.Public = false; - } - - packageRequests.Add( request ); - } - - WaitUntilCallback( () => - { - callbacks.Subscribe( steamApps.PICSGetProductInfo( new List(), packageRequests ), cbMethod ); - }, () => { return completed; } ); - } - - public bool RequestFreeAppLicense( uint appId ) - { - bool success = false; - bool completed = false; - Action cbMethod = ( resultInfo ) => - { - completed = true; - success = resultInfo.GrantedApps.Contains( appId ); - }; - - WaitUntilCallback( () => - { - callbacks.Subscribe( steamApps.RequestFreeLicense( appId ), cbMethod ); - }, () => { return completed; } ); - - return success; - } - - public void RequestAppTicket( uint appId ) - { - if ( AppTickets.ContainsKey( appId ) || bAborted ) - return; - - - if ( !authenticatedUser ) - { - AppTickets[ appId ] = null; - return; - } - - bool completed = false; - Action cbMethod = ( appTicket ) => - { - completed = true; - - if ( appTicket.Result != EResult.OK ) - { - Console.WriteLine( "Unable to get appticket for {0}: {1}", appTicket.AppID, appTicket.Result ); - Abort(); - } - else - { - Console.WriteLine( "Got appticket for {0}!", appTicket.AppID ); - AppTickets[ appTicket.AppID ] = appTicket.Ticket; - } - }; - - WaitUntilCallback( () => - { - callbacks.Subscribe( steamApps.GetAppOwnershipTicket( appId ), cbMethod ); - }, () => { return completed; } ); - } - - public void RequestDepotKey( uint depotId, uint appid = 0 ) - { - if ( DepotKeys.ContainsKey( depotId ) || bAborted ) - return; - - bool completed = false; - - Action cbMethod = ( depotKey ) => - { - completed = true; - Console.WriteLine( "Got depot key for {0} result: {1}", depotKey.DepotID, depotKey.Result ); - - if ( depotKey.Result != EResult.OK ) - { - Abort(); - return; - } - - DepotKeys[ depotKey.DepotID ] = depotKey.DepotKey; - }; - - WaitUntilCallback( () => - { - callbacks.Subscribe( steamApps.GetDepotDecryptionKey( depotId, appid ), cbMethod ); - }, () => { return completed; } ); - } - - public string ResolveCDNTopLevelHost(string host) - { - // SteamPipe CDN shares tokens with all hosts - if (host.EndsWith( ".steampipe.steamcontent.com" ) ) - { - return "steampipe.steamcontent.com"; - } - else if (host.EndsWith(".steamcontent.com")) - { - return "steamcontent.com"; - } - - return host; - } - - public void RequestCDNAuthToken( uint appid, uint depotid, string host, string cdnKey ) - { - if ( CDNAuthTokens.ContainsKey( cdnKey ) || bAborted ) - return; - - if ( !CDNAuthTokens.TryAdd( cdnKey, new TaskCompletionSource() ) ) - return; - - bool completed = false; - var timeoutDate = DateTime.Now.AddSeconds( 10 ); - Action cbMethod = ( cdnAuth ) => - { - completed = true; - Console.WriteLine( "Got CDN auth token for {0} result: {1} (expires {2})", host, cdnAuth.Result, cdnAuth.Expiration ); - - if ( cdnAuth.Result != EResult.OK ) - { - Abort(); - return; - } - - CDNAuthTokens[cdnKey].TrySetResult( cdnAuth ); - }; - - WaitUntilCallback( () => - { - callbacks.Subscribe( steamApps.GetCDNAuthToken( appid, depotid, host ), cbMethod ); - }, () => { return completed || DateTime.Now >= timeoutDate; } ); - } - - public void CheckAppBetaPassword( uint appid, string password ) - { - bool completed = false; - Action cbMethod = ( appPassword ) => - { - completed = true; - - Console.WriteLine( "Retrieved {0} beta keys with result: {1}", appPassword.BetaPasswords.Count, appPassword.Result ); - - foreach ( var entry in appPassword.BetaPasswords ) - { - AppBetaPasswords[ entry.Key ] = entry.Value; - } - }; - - WaitUntilCallback( () => - { - callbacks.Subscribe( steamApps.CheckAppBetaPassword( appid, password ), cbMethod ); - }, () => { return completed; } ); - } - - public CPublishedFile_GetItemInfo_Response.WorkshopItemInfo GetPubfileItemInfo( uint appId, PublishedFileID pubFile ) - { - var pubFileRequest = new CPublishedFile_GetItemInfo_Request() { app_id = appId }; - pubFileRequest.workshop_items.Add( new CPublishedFile_GetItemInfo_Request.WorkshopItem() { published_file_id = pubFile } ); - - bool completed = false; - CPublishedFile_GetItemInfo_Response.WorkshopItemInfo details = null; - - Action cbMethod = callback => - { - completed = true; - if ( callback.Result == EResult.OK ) - { - var response = callback.GetDeserializedResponse(); - details = response.workshop_items.FirstOrDefault(); - } - else - { - throw new Exception( $"EResult {(int)callback.Result} ({callback.Result}) while retrieving UGC id for pubfile {pubFile}."); - } - }; - - WaitUntilCallback(() => - { - callbacks.Subscribe( steamPublishedFile.SendMessage( api => api.GetItemInfo( pubFileRequest ) ), cbMethod ); - }, () => { return completed; }); - - return details; - } - - public PublishedFileDetails GetPublishedFileDetails(uint appId, PublishedFileID pubFile) - { - var pubFileRequest = new CPublishedFile_GetDetails_Request() { appid = appId }; - pubFileRequest.publishedfileids.Add( pubFile ); - - bool completed = false; - PublishedFileDetails details = null; - - Action cbMethod = callback => - { - completed = true; - if (callback.Result == EResult.OK) - { - var response = callback.GetDeserializedResponse(); - details = response.publishedfiledetails.FirstOrDefault(); - } - else - { - throw new Exception($"EResult {(int)callback.Result} ({callback.Result}) while retrieving file details for pubfile {pubFile}."); - } - }; - - WaitUntilCallback(() => - { - callbacks.Subscribe(steamPublishedFile.SendMessage(api => api.GetDetails(pubFileRequest)), cbMethod); - }, () => { return completed; }); - - return details; - } - - - public SteamCloud.UGCDetailsCallback GetUGCDetails(UGCHandle ugcHandle) - { - bool completed = false; - SteamCloud.UGCDetailsCallback details = null; - - Action cbMethod = callback => - { - completed = true; - if (callback.Result == EResult.OK) - { - details = callback; - } - else if (callback.Result == EResult.FileNotFound) - { - details = null; - } - else - { - throw new Exception($"EResult {(int)callback.Result} ({callback.Result}) while retrieving UGC details for {ugcHandle}."); - } - }; - - WaitUntilCallback(() => - { - callbacks.Subscribe(steamCloud.RequestUGCDetails(ugcHandle), cbMethod); - }, () => { return completed; }); - - return details; - } - - void Connect() - { - bAborted = false; - bConnected = false; - bConnecting = true; - connectionBackoff = 0; - bExpectingDisconnectRemote = false; - bDidDisconnect = false; - bDidReceiveLoginKey = false; - this.connectTime = DateTime.Now; - this.steamClient.Connect(); - } - - private void Abort( bool sendLogOff = true ) - { - Disconnect( sendLogOff ); - } - public void Disconnect( bool sendLogOff = true ) - { - if ( sendLogOff ) - { - steamUser.LogOff(); - } - - steamClient.Disconnect(); - bConnected = false; - bConnecting = false; - bAborted = true; - - // flush callbacks until our disconnected event - while ( !bDidDisconnect ) - { - callbacks.RunWaitAllCallbacks( TimeSpan.FromMilliseconds( 100 ) ); - } - } - - public void TryWaitForLoginKey() - { - if ( logonDetails.Username == null || !credentials.LoggedOn || !ContentDownloader.Config.RememberPassword ) return; - - var totalWaitPeriod = DateTime.Now.AddSeconds( 3 ); - - while ( true ) - { - DateTime now = DateTime.Now; - if ( now >= totalWaitPeriod ) break; - - if ( bDidReceiveLoginKey ) break; - - callbacks.RunWaitAllCallbacks( TimeSpan.FromMilliseconds( 100 ) ); - } - } - - private void WaitForCallbacks() - { - callbacks.RunWaitCallbacks( TimeSpan.FromSeconds( 1 ) ); - - TimeSpan diff = DateTime.Now - connectTime; - - if ( diff > STEAM3_TIMEOUT && !bConnected ) - { - Console.WriteLine( "Timeout connecting to Steam3." ); - Abort(); - - return; - } - } - - private void ConnectedCallback( SteamClient.ConnectedCallback connected ) - { - Console.WriteLine( " Done!" ); - bConnecting = false; - bConnected = true; - if ( !authenticatedUser ) - { - Console.Write( "Logging anonymously into Steam3..." ); - steamUser.LogOnAnonymous(); - } - else - { - Console.Write( "Logging '{0}' into Steam3...", logonDetails.Username ); - steamUser.LogOn( logonDetails ); - } - } - - private void DisconnectedCallback( SteamClient.DisconnectedCallback disconnected ) - { - bDidDisconnect = true; - - if ( disconnected.UserInitiated || bExpectingDisconnectRemote ) - { - Console.WriteLine( "Disconnected from Steam" ); - } - else if ( connectionBackoff >= 10 ) - { - Console.WriteLine( "Could not connect to Steam after 10 tries" ); - Abort( false ); - } - else if ( !bAborted ) - { - if ( bConnecting ) - { - Console.WriteLine( "Connection to Steam failed. Trying again" ); - } - else - { - Console.WriteLine( "Lost connection to Steam. Reconnecting" ); - } - - Thread.Sleep( 1000 * ++connectionBackoff ); - steamClient.Connect(); - } - } - - private void LogOnCallback( SteamUser.LoggedOnCallback loggedOn ) - { - bool isSteamGuard = loggedOn.Result == EResult.AccountLogonDenied; - bool is2FA = loggedOn.Result == EResult.AccountLoginDeniedNeedTwoFactor; - bool isLoginKey = ContentDownloader.Config.RememberPassword && logonDetails.LoginKey != null && loggedOn.Result == EResult.InvalidPassword; - - if ( isSteamGuard || is2FA || isLoginKey ) - { - bExpectingDisconnectRemote = true; - Abort( false ); - - if ( !isLoginKey ) - { - Console.WriteLine( "This account is protected by Steam Guard." ); - } - - if ( is2FA ) - { - Console.Write( "Please enter your 2 factor auth code from your authenticator app: " ); - logonDetails.TwoFactorCode = Console.ReadLine(); - } - else if ( isLoginKey ) - { - AccountSettingsStore.Instance.LoginKeys.Remove( logonDetails.Username ); - AccountSettingsStore.Save(); - - logonDetails.LoginKey = null; - - if ( ContentDownloader.Config.SuppliedPassword != null ) - { - Console.WriteLine( "Login key was expired. Connecting with supplied password." ); - logonDetails.Password = ContentDownloader.Config.SuppliedPassword; - } - else - { - Console.Write( "Login key was expired. Please enter your password: " ); - logonDetails.Password = Util.ReadPassword(); - } - } - else - { - Console.Write( "Please enter the authentication code sent to your email address: " ); - logonDetails.AuthCode = Console.ReadLine(); - } - - Console.Write( "Retrying Steam3 connection..." ); - Connect(); - - return; - } - else if ( loggedOn.Result == EResult.ServiceUnavailable ) - { - Console.WriteLine( "Unable to login to Steam3: {0}", loggedOn.Result ); - Abort( false ); - - return; - } - else if ( loggedOn.Result != EResult.OK ) - { - Console.WriteLine( "Unable to login to Steam3: {0}", loggedOn.Result ); - Abort(); - - return; - } - - Console.WriteLine( " Done!" ); - - this.seq++; - credentials.LoggedOn = true; - - if ( ContentDownloader.Config.CellID == 0 ) - { - Console.WriteLine( "Using Steam3 suggested CellID: " + loggedOn.CellID ); - ContentDownloader.Config.CellID = ( int )loggedOn.CellID; - } - } - - private void SessionTokenCallback( SteamUser.SessionTokenCallback sessionToken ) - { - Console.WriteLine( "Got session token!" ); - credentials.SessionToken = sessionToken.SessionToken; - } - - private void LicenseListCallback( SteamApps.LicenseListCallback licenseList ) - { - if ( licenseList.Result != EResult.OK ) - { - Console.WriteLine( "Unable to get license list: {0} ", licenseList.Result ); - Abort(); - - return; - } - - Console.WriteLine( "Got {0} licenses for account!", licenseList.LicenseList.Count ); - Licenses = licenseList.LicenseList; - - foreach ( var license in licenseList.LicenseList ) - { - if ( license.AccessToken > 0 ) - { - PackageTokens.TryAdd( license.PackageID, license.AccessToken ); - } - } - } - - private void UpdateMachineAuthCallback( SteamUser.UpdateMachineAuthCallback machineAuth ) - { - byte[] hash = Util.SHAHash( machineAuth.Data ); - Console.WriteLine( "Got Machine Auth: {0} {1} {2} {3}", machineAuth.FileName, machineAuth.Offset, machineAuth.BytesToWrite, machineAuth.Data.Length, hash ); - - AccountSettingsStore.Instance.SentryData[ logonDetails.Username ] = machineAuth.Data; - AccountSettingsStore.Save(); - - var authResponse = new SteamUser.MachineAuthDetails - { - BytesWritten = machineAuth.BytesToWrite, - FileName = machineAuth.FileName, - FileSize = machineAuth.BytesToWrite, - Offset = machineAuth.Offset, - - SentryFileHash = hash, // should be the sha1 hash of the sentry file we just wrote - - OneTimePassword = machineAuth.OneTimePassword, // not sure on this one yet, since we've had no examples of steam using OTPs - - LastError = 0, // result from win32 GetLastError - Result = EResult.OK, // if everything went okay, otherwise ~who knows~ - - JobID = machineAuth.JobID, // so we respond to the correct server job - }; - - // send off our response - steamUser.SendMachineAuthResponse( authResponse ); - } - - private void LoginKeyCallback( SteamUser.LoginKeyCallback loginKey ) - { - Console.WriteLine( "Accepted new login key for account {0}", logonDetails.Username ); - - AccountSettingsStore.Instance.LoginKeys[ logonDetails.Username ] = loginKey.LoginKey; - AccountSettingsStore.Save(); - - steamUser.AcceptNewLoginKey( loginKey ); - - bDidReceiveLoginKey = true; - } - - - } -} +using SteamKit2; +using SteamKit2.Internal; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace DepotDownloader +{ + + class Steam3Session + { + public class Credentials + { + public bool LoggedOn { get; set; } + public ulong SessionToken { get; set; } + + public bool IsValid + { + get { return LoggedOn; } + } + } + + public ReadOnlyCollection Licenses + { + get; + private set; + } + + public Dictionary AppTickets { get; private set; } + public Dictionary AppTokens { get; private set; } + public Dictionary PackageTokens { get; private set; } + public Dictionary DepotKeys { get; private set; } + public ConcurrentDictionary> CDNAuthTokens { get; private set; } + public Dictionary AppInfo { get; private set; } + public Dictionary PackageInfo { get; private set; } + public Dictionary AppBetaPasswords { get; private set; } + + public SteamClient steamClient; + public SteamUser steamUser; + SteamApps steamApps; + SteamCloud steamCloud; + SteamUnifiedMessages.UnifiedService steamPublishedFile; + + CallbackManager callbacks; + + bool authenticatedUser; + bool bConnected; + bool bConnecting; + bool bAborted; + bool bExpectingDisconnectRemote; + bool bDidDisconnect; + bool bDidReceiveLoginKey; + int connectionBackoff; + int seq; // more hack fixes + DateTime connectTime; + + // input + SteamUser.LogOnDetails logonDetails; + + // output + Credentials credentials; + + static readonly TimeSpan STEAM3_TIMEOUT = TimeSpan.FromSeconds( 30 ); + + + public Steam3Session( SteamUser.LogOnDetails details ) + { + this.logonDetails = details; + + this.authenticatedUser = details.Username != null; + this.credentials = new Credentials(); + this.bConnected = false; + this.bConnecting = false; + this.bAborted = false; + this.bExpectingDisconnectRemote = false; + this.bDidDisconnect = false; + this.bDidReceiveLoginKey = false; + this.seq = 0; + + this.AppTickets = new Dictionary(); + this.AppTokens = new Dictionary(); + this.PackageTokens = new Dictionary(); + this.DepotKeys = new Dictionary(); + this.CDNAuthTokens = new ConcurrentDictionary>(); + this.AppInfo = new Dictionary(); + this.PackageInfo = new Dictionary(); + this.AppBetaPasswords = new Dictionary(); + + this.steamClient = new SteamClient(); + + this.steamUser = this.steamClient.GetHandler(); + this.steamApps = this.steamClient.GetHandler(); + this.steamCloud = this.steamClient.GetHandler(); + var steamUnifiedMessages = this.steamClient.GetHandler(); + this.steamPublishedFile = steamUnifiedMessages.CreateService(); + + this.callbacks = new CallbackManager( this.steamClient ); + + this.callbacks.Subscribe( ConnectedCallback ); + this.callbacks.Subscribe( DisconnectedCallback ); + this.callbacks.Subscribe( LogOnCallback ); + this.callbacks.Subscribe( SessionTokenCallback ); + this.callbacks.Subscribe( LicenseListCallback ); + this.callbacks.Subscribe( UpdateMachineAuthCallback ); + this.callbacks.Subscribe( LoginKeyCallback ); + + Console.Write( "Connecting to Steam3..." ); + + if ( authenticatedUser ) + { + FileInfo fi = new FileInfo( String.Format( "{0}.sentryFile", logonDetails.Username ) ); + if ( AccountSettingsStore.Instance.SentryData != null && AccountSettingsStore.Instance.SentryData.ContainsKey( logonDetails.Username ) ) + { + logonDetails.SentryFileHash = Util.SHAHash( AccountSettingsStore.Instance.SentryData[ logonDetails.Username ] ); + } + else if ( fi.Exists && fi.Length > 0 ) + { + var sentryData = File.ReadAllBytes( fi.FullName ); + logonDetails.SentryFileHash = Util.SHAHash( sentryData ); + AccountSettingsStore.Instance.SentryData[ logonDetails.Username ] = sentryData; + AccountSettingsStore.Save(); + } + } + + Connect(); + } + + public delegate bool WaitCondition(); + private object steamLock = new object(); + + public bool WaitUntilCallback( Action submitter, WaitCondition waiter ) + { + while ( !bAborted && !waiter() ) + { + lock (steamLock) + { + submitter(); + } + + int seq = this.seq; + do + { + lock (steamLock) + { + WaitForCallbacks(); + } + } + while ( !bAborted && this.seq == seq && !waiter() ); + } + + return bAborted; + } + + public Credentials WaitForCredentials() + { + if ( credentials.IsValid || bAborted ) + return credentials; + + WaitUntilCallback( () => { }, () => { return credentials.IsValid; } ); + + return credentials; + } + + public void RequestAppInfo( uint appId, bool bForce = false ) + { + if ( ( AppInfo.ContainsKey( appId ) && !bForce ) || bAborted ) + return; + + bool completed = false; + Action cbMethodTokens = ( appTokens ) => + { + completed = true; + if ( appTokens.AppTokensDenied.Contains( appId ) ) + { + Console.WriteLine( "Insufficient privileges to get access token for app {0}", appId ); + } + + foreach ( var token_dict in appTokens.AppTokens ) + { + this.AppTokens[ token_dict.Key ] = token_dict.Value; + } + }; + + WaitUntilCallback( () => + { + callbacks.Subscribe( steamApps.PICSGetAccessTokens( new List() { appId }, new List() { } ), cbMethodTokens ); + }, () => { return completed; } ); + + completed = false; + Action cbMethod = ( appInfo ) => + { + completed = !appInfo.ResponsePending; + + foreach ( var app_value in appInfo.Apps ) + { + var app = app_value.Value; + + Console.WriteLine( "Got AppInfo for {0}", app.ID ); + AppInfo[ app.ID ] = app; + } + + foreach ( var app in appInfo.UnknownApps ) + { + AppInfo[ app ] = null; + } + }; + + SteamApps.PICSRequest request = new SteamApps.PICSRequest( appId ); + if ( AppTokens.ContainsKey( appId ) ) + { + request.AccessToken = AppTokens[ appId ]; + request.Public = false; + } + + WaitUntilCallback( () => + { + callbacks.Subscribe( steamApps.PICSGetProductInfo( new List() { request }, new List() { } ), cbMethod ); + }, () => { return completed; } ); + } + + public void RequestPackageInfo( IEnumerable packageIds ) + { + List packages = packageIds.ToList(); + packages.RemoveAll( pid => PackageInfo.ContainsKey( pid ) ); + + if ( packages.Count == 0 || bAborted ) + return; + + bool completed = false; + Action cbMethod = ( packageInfo ) => + { + completed = !packageInfo.ResponsePending; + + foreach ( var package_value in packageInfo.Packages ) + { + var package = package_value.Value; + PackageInfo[ package.ID ] = package; + } + + foreach ( var package in packageInfo.UnknownPackages ) + { + PackageInfo[package] = null; + } + }; + + var packageRequests = new List(); + + foreach ( var package in packages ) + { + var request = new SteamApps.PICSRequest( package ); + + if ( PackageTokens.TryGetValue( package, out var token ) ) + { + request.AccessToken = token; + request.Public = false; + } + + packageRequests.Add( request ); + } + + WaitUntilCallback( () => + { + callbacks.Subscribe( steamApps.PICSGetProductInfo( new List(), packageRequests ), cbMethod ); + }, () => { return completed; } ); + } + + public bool RequestFreeAppLicense( uint appId ) + { + bool success = false; + bool completed = false; + Action cbMethod = ( resultInfo ) => + { + completed = true; + success = resultInfo.GrantedApps.Contains( appId ); + }; + + WaitUntilCallback( () => + { + callbacks.Subscribe( steamApps.RequestFreeLicense( appId ), cbMethod ); + }, () => { return completed; } ); + + return success; + } + + public void RequestAppTicket( uint appId ) + { + if ( AppTickets.ContainsKey( appId ) || bAborted ) + return; + + + if ( !authenticatedUser ) + { + AppTickets[ appId ] = null; + return; + } + + bool completed = false; + Action cbMethod = ( appTicket ) => + { + completed = true; + + if ( appTicket.Result != EResult.OK ) + { + Console.WriteLine( "Unable to get appticket for {0}: {1}", appTicket.AppID, appTicket.Result ); + Abort(); + } + else + { + Console.WriteLine( "Got appticket for {0}!", appTicket.AppID ); + AppTickets[ appTicket.AppID ] = appTicket.Ticket; + } + }; + + WaitUntilCallback( () => + { + callbacks.Subscribe( steamApps.GetAppOwnershipTicket( appId ), cbMethod ); + }, () => { return completed; } ); + } + + public void RequestDepotKey( uint depotId, uint appid = 0 ) + { + if ( DepotKeys.ContainsKey( depotId ) || bAborted ) + return; + + bool completed = false; + + Action cbMethod = ( depotKey ) => + { + completed = true; + Console.WriteLine( "Got depot key for {0} result: {1}", depotKey.DepotID, depotKey.Result ); + + if ( depotKey.Result != EResult.OK ) + { + Abort(); + return; + } + + DepotKeys[ depotKey.DepotID ] = depotKey.DepotKey; + }; + + WaitUntilCallback( () => + { + callbacks.Subscribe( steamApps.GetDepotDecryptionKey( depotId, appid ), cbMethod ); + }, () => { return completed; } ); + } + + public string ResolveCDNTopLevelHost(string host) + { + // SteamPipe CDN shares tokens with all hosts + if (host.EndsWith( ".steampipe.steamcontent.com" ) ) + { + return "steampipe.steamcontent.com"; + } + else if (host.EndsWith(".steamcontent.com")) + { + return "steamcontent.com"; + } + + return host; + } + + public void RequestCDNAuthToken( uint appid, uint depotid, string host, string cdnKey ) + { + if ( CDNAuthTokens.ContainsKey( cdnKey ) || bAborted ) + return; + + if ( !CDNAuthTokens.TryAdd( cdnKey, new TaskCompletionSource() ) ) + return; + + bool completed = false; + var timeoutDate = DateTime.Now.AddSeconds( 10 ); + Action cbMethod = ( cdnAuth ) => + { + completed = true; + Console.WriteLine( "Got CDN auth token for {0} result: {1} (expires {2})", host, cdnAuth.Result, cdnAuth.Expiration ); + + if ( cdnAuth.Result != EResult.OK ) + { + Abort(); + return; + } + + CDNAuthTokens[cdnKey].TrySetResult( cdnAuth ); + }; + + WaitUntilCallback( () => + { + callbacks.Subscribe( steamApps.GetCDNAuthToken( appid, depotid, host ), cbMethod ); + }, () => { return completed || DateTime.Now >= timeoutDate; } ); + } + + public void CheckAppBetaPassword( uint appid, string password ) + { + bool completed = false; + Action cbMethod = ( appPassword ) => + { + completed = true; + + Console.WriteLine( "Retrieved {0} beta keys with result: {1}", appPassword.BetaPasswords.Count, appPassword.Result ); + + foreach ( var entry in appPassword.BetaPasswords ) + { + AppBetaPasswords[ entry.Key ] = entry.Value; + } + }; + + WaitUntilCallback( () => + { + callbacks.Subscribe( steamApps.CheckAppBetaPassword( appid, password ), cbMethod ); + }, () => { return completed; } ); + } + + public CPublishedFile_GetItemInfo_Response.WorkshopItemInfo GetPubfileItemInfo( uint appId, PublishedFileID pubFile ) + { + var pubFileRequest = new CPublishedFile_GetItemInfo_Request() { app_id = appId }; + pubFileRequest.workshop_items.Add( new CPublishedFile_GetItemInfo_Request.WorkshopItem() { published_file_id = pubFile } ); + + bool completed = false; + CPublishedFile_GetItemInfo_Response.WorkshopItemInfo details = null; + + Action cbMethod = callback => + { + completed = true; + if ( callback.Result == EResult.OK ) + { + var response = callback.GetDeserializedResponse(); + details = response.workshop_items.FirstOrDefault(); + } + else + { + throw new Exception( $"EResult {(int)callback.Result} ({callback.Result}) while retrieving UGC id for pubfile {pubFile}."); + } + }; + + WaitUntilCallback(() => + { + callbacks.Subscribe( steamPublishedFile.SendMessage( api => api.GetItemInfo( pubFileRequest ) ), cbMethod ); + }, () => { return completed; }); + + return details; + } + + public PublishedFileDetails GetPublishedFileDetails(uint appId, PublishedFileID pubFile) + { + var pubFileRequest = new CPublishedFile_GetDetails_Request() { appid = appId }; + pubFileRequest.publishedfileids.Add( pubFile ); + + bool completed = false; + PublishedFileDetails details = null; + + Action cbMethod = callback => + { + completed = true; + if (callback.Result == EResult.OK) + { + var response = callback.GetDeserializedResponse(); + details = response.publishedfiledetails.FirstOrDefault(); + } + else + { + throw new Exception($"EResult {(int)callback.Result} ({callback.Result}) while retrieving file details for pubfile {pubFile}."); + } + }; + + WaitUntilCallback(() => + { + callbacks.Subscribe(steamPublishedFile.SendMessage(api => api.GetDetails(pubFileRequest)), cbMethod); + }, () => { return completed; }); + + return details; + } + + + public SteamCloud.UGCDetailsCallback GetUGCDetails(UGCHandle ugcHandle) + { + bool completed = false; + SteamCloud.UGCDetailsCallback details = null; + + Action cbMethod = callback => + { + completed = true; + if (callback.Result == EResult.OK) + { + details = callback; + } + else if (callback.Result == EResult.FileNotFound) + { + details = null; + } + else + { + throw new Exception($"EResult {(int)callback.Result} ({callback.Result}) while retrieving UGC details for {ugcHandle}."); + } + }; + + WaitUntilCallback(() => + { + callbacks.Subscribe(steamCloud.RequestUGCDetails(ugcHandle), cbMethod); + }, () => { return completed; }); + + return details; + } + + void Connect() + { + bAborted = false; + bConnected = false; + bConnecting = true; + connectionBackoff = 0; + bExpectingDisconnectRemote = false; + bDidDisconnect = false; + bDidReceiveLoginKey = false; + this.connectTime = DateTime.Now; + this.steamClient.Connect(); + } + + private void Abort( bool sendLogOff = true ) + { + Disconnect( sendLogOff ); + } + public void Disconnect( bool sendLogOff = true ) + { + if ( sendLogOff ) + { + steamUser.LogOff(); + } + + steamClient.Disconnect(); + bConnected = false; + bConnecting = false; + bAborted = true; + + // flush callbacks until our disconnected event + while ( !bDidDisconnect ) + { + callbacks.RunWaitAllCallbacks( TimeSpan.FromMilliseconds( 100 ) ); + } + } + + public void TryWaitForLoginKey() + { + if ( logonDetails.Username == null || !credentials.LoggedOn || !ContentDownloader.Config.RememberPassword ) return; + + var totalWaitPeriod = DateTime.Now.AddSeconds( 3 ); + + while ( true ) + { + DateTime now = DateTime.Now; + if ( now >= totalWaitPeriod ) break; + + if ( bDidReceiveLoginKey ) break; + + callbacks.RunWaitAllCallbacks( TimeSpan.FromMilliseconds( 100 ) ); + } + } + + private void WaitForCallbacks() + { + callbacks.RunWaitCallbacks( TimeSpan.FromSeconds( 1 ) ); + + TimeSpan diff = DateTime.Now - connectTime; + + if ( diff > STEAM3_TIMEOUT && !bConnected ) + { + Console.WriteLine( "Timeout connecting to Steam3." ); + Abort(); + + return; + } + } + + private void ConnectedCallback( SteamClient.ConnectedCallback connected ) + { + Console.WriteLine( " Done!" ); + bConnecting = false; + bConnected = true; + if ( !authenticatedUser ) + { + Console.Write( "Logging anonymously into Steam3..." ); + steamUser.LogOnAnonymous(); + } + else + { + Console.Write( "Logging '{0}' into Steam3...", logonDetails.Username ); + steamUser.LogOn( logonDetails ); + } + } + + private void DisconnectedCallback( SteamClient.DisconnectedCallback disconnected ) + { + bDidDisconnect = true; + + if ( disconnected.UserInitiated || bExpectingDisconnectRemote ) + { + Console.WriteLine( "Disconnected from Steam" ); + } + else if ( connectionBackoff >= 10 ) + { + Console.WriteLine( "Could not connect to Steam after 10 tries" ); + Abort( false ); + } + else if ( !bAborted ) + { + if ( bConnecting ) + { + Console.WriteLine( "Connection to Steam failed. Trying again" ); + } + else + { + Console.WriteLine( "Lost connection to Steam. Reconnecting" ); + } + + Thread.Sleep( 1000 * ++connectionBackoff ); + steamClient.Connect(); + } + } + + private void LogOnCallback( SteamUser.LoggedOnCallback loggedOn ) + { + bool isSteamGuard = loggedOn.Result == EResult.AccountLogonDenied; + bool is2FA = loggedOn.Result == EResult.AccountLoginDeniedNeedTwoFactor; + bool isLoginKey = ContentDownloader.Config.RememberPassword && logonDetails.LoginKey != null && loggedOn.Result == EResult.InvalidPassword; + + if ( isSteamGuard || is2FA || isLoginKey ) + { + bExpectingDisconnectRemote = true; + Abort( false ); + + if ( !isLoginKey ) + { + Console.WriteLine( "This account is protected by Steam Guard." ); + } + + if ( is2FA ) + { + Console.Write( "Please enter your 2 factor auth code from your authenticator app: " ); + logonDetails.TwoFactorCode = Console.ReadLine(); + } + else if ( isLoginKey ) + { + AccountSettingsStore.Instance.LoginKeys.Remove( logonDetails.Username ); + AccountSettingsStore.Save(); + + logonDetails.LoginKey = null; + + if ( ContentDownloader.Config.SuppliedPassword != null ) + { + Console.WriteLine( "Login key was expired. Connecting with supplied password." ); + logonDetails.Password = ContentDownloader.Config.SuppliedPassword; + } + else + { + Console.Write( "Login key was expired. Please enter your password: " ); + logonDetails.Password = Util.ReadPassword(); + } + } + else + { + Console.Write( "Please enter the authentication code sent to your email address: " ); + logonDetails.AuthCode = Console.ReadLine(); + } + + Console.Write( "Retrying Steam3 connection..." ); + Connect(); + + return; + } + else if ( loggedOn.Result == EResult.ServiceUnavailable ) + { + Console.WriteLine( "Unable to login to Steam3: {0}", loggedOn.Result ); + Abort( false ); + + return; + } + else if ( loggedOn.Result != EResult.OK ) + { + Console.WriteLine( "Unable to login to Steam3: {0}", loggedOn.Result ); + Abort(); + + return; + } + + Console.WriteLine( " Done!" ); + + this.seq++; + credentials.LoggedOn = true; + + if ( ContentDownloader.Config.CellID == 0 ) + { + Console.WriteLine( "Using Steam3 suggested CellID: " + loggedOn.CellID ); + ContentDownloader.Config.CellID = ( int )loggedOn.CellID; + } + } + + private void SessionTokenCallback( SteamUser.SessionTokenCallback sessionToken ) + { + Console.WriteLine( "Got session token!" ); + credentials.SessionToken = sessionToken.SessionToken; + } + + private void LicenseListCallback( SteamApps.LicenseListCallback licenseList ) + { + if ( licenseList.Result != EResult.OK ) + { + Console.WriteLine( "Unable to get license list: {0} ", licenseList.Result ); + Abort(); + + return; + } + + Console.WriteLine( "Got {0} licenses for account!", licenseList.LicenseList.Count ); + Licenses = licenseList.LicenseList; + + foreach ( var license in licenseList.LicenseList ) + { + if ( license.AccessToken > 0 ) + { + PackageTokens.TryAdd( license.PackageID, license.AccessToken ); + } + } + } + + private void UpdateMachineAuthCallback( SteamUser.UpdateMachineAuthCallback machineAuth ) + { + byte[] hash = Util.SHAHash( machineAuth.Data ); + Console.WriteLine( "Got Machine Auth: {0} {1} {2} {3}", machineAuth.FileName, machineAuth.Offset, machineAuth.BytesToWrite, machineAuth.Data.Length, hash ); + + AccountSettingsStore.Instance.SentryData[ logonDetails.Username ] = machineAuth.Data; + AccountSettingsStore.Save(); + + var authResponse = new SteamUser.MachineAuthDetails + { + BytesWritten = machineAuth.BytesToWrite, + FileName = machineAuth.FileName, + FileSize = machineAuth.BytesToWrite, + Offset = machineAuth.Offset, + + SentryFileHash = hash, // should be the sha1 hash of the sentry file we just wrote + + OneTimePassword = machineAuth.OneTimePassword, // not sure on this one yet, since we've had no examples of steam using OTPs + + LastError = 0, // result from win32 GetLastError + Result = EResult.OK, // if everything went okay, otherwise ~who knows~ + + JobID = machineAuth.JobID, // so we respond to the correct server job + }; + + // send off our response + steamUser.SendMachineAuthResponse( authResponse ); + } + + private void LoginKeyCallback( SteamUser.LoginKeyCallback loginKey ) + { + Console.WriteLine( "Accepted new login key for account {0}", logonDetails.Username ); + + AccountSettingsStore.Instance.LoginKeys[ logonDetails.Username ] = loginKey.LoginKey; + AccountSettingsStore.Save(); + + steamUser.AcceptNewLoginKey( loginKey ); + + bDidReceiveLoginKey = true; + } + + + } +} diff --git a/depotdownloader/DepotDownloader/Util.cs b/depotdownloader/DepotDownloader/Util.cs index 9a4e58e3..7d866487 100644 --- a/depotdownloader/DepotDownloader/Util.cs +++ b/depotdownloader/DepotDownloader/Util.cs @@ -1,170 +1,170 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.InteropServices; -using System.Security.Cryptography; -using System.Text; -using System.Threading.Tasks; - -namespace DepotDownloader -{ - static class Util - { - public static string GetSteamOS() - { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - return "windows"; - } - else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) - { - return "macos"; - } - else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - { - return "linux"; - } - - return "unknown"; - } - - public static string GetSteamArch() - { - return Environment.Is64BitOperatingSystem ? "64" : "32"; - } - - public static string ReadPassword() - { - ConsoleKeyInfo keyInfo; - StringBuilder password = new StringBuilder(); - - do - { - keyInfo = Console.ReadKey( true ); - - if ( keyInfo.Key == ConsoleKey.Backspace ) - { - if ( password.Length > 0 ) - password.Remove( password.Length - 1, 1 ); - continue; - } - - /* Printable ASCII characters only */ - char c = keyInfo.KeyChar; - if ( c >= ' ' && c <= '~' ) - password.Append( c ); - } while ( keyInfo.Key != ConsoleKey.Enter ); - - return password.ToString(); - } - - // Validate a file against Steam3 Chunk data - public static List ValidateSteam3FileChecksums(FileStream fs, ProtoManifest.ChunkData[] chunkdata) - { - var neededChunks = new List(); - int read; - - foreach (var data in chunkdata) - { - byte[] chunk = new byte[data.UncompressedLength]; - fs.Seek((long)data.Offset, SeekOrigin.Begin); - read = fs.Read(chunk, 0, (int)data.UncompressedLength); - - byte[] tempchunk; - if (read < data.UncompressedLength) - { - tempchunk = new byte[read]; - Array.Copy(chunk, 0, tempchunk, 0, read); - } - else - { - tempchunk = chunk; - } - - byte[] adler = AdlerHash(tempchunk); - if (!adler.SequenceEqual(data.Checksum)) - { - neededChunks.Add(data); - } - } - - return neededChunks; - } - - public static byte[] AdlerHash(byte[] input) - { - uint a = 0, b = 0; - for (int i = 0; i < input.Length; i++) - { - a = (a + input[i]) % 65521; - b = (b + a) % 65521; - } - return BitConverter.GetBytes(a | (b << 16)); - } - - public static byte[] SHAHash( byte[] input ) - { - using (var sha = SHA1.Create()) - { - var output = sha.ComputeHash( input ); - - return output; - } - } - - public static byte[] DecodeHexString( string hex ) - { - if ( hex == null ) - return null; - - int chars = hex.Length; - byte[] bytes = new byte[ chars / 2 ]; - - for ( int i = 0 ; i < chars ; i += 2 ) - bytes[ i / 2 ] = Convert.ToByte( hex.Substring( i, 2 ), 16 ); - - return bytes; - } - - public static string EncodeHexString( byte[] input ) - { - return input.Aggregate( new StringBuilder(), - ( sb, v ) => sb.Append( v.ToString( "x2" ) ) - ).ToString(); - } - - public static async Task InvokeAsync(IEnumerable> taskFactories, int maxDegreeOfParallelism) - { - if (taskFactories == null) throw new ArgumentNullException(nameof(taskFactories)); - if (maxDegreeOfParallelism <= 0) throw new ArgumentException(nameof(maxDegreeOfParallelism)); - - Func[] queue = taskFactories.ToArray(); - - if (queue.Length == 0) - { - return; - } - - List tasksInFlight = new List(maxDegreeOfParallelism); - int index = 0; - - do - { - while (tasksInFlight.Count < maxDegreeOfParallelism && index < queue.Length) - { - Func taskFactory = queue[index++]; - - tasksInFlight.Add(taskFactory()); - } - - Task completedTask = await Task.WhenAny(tasksInFlight).ConfigureAwait(false); - - await completedTask.ConfigureAwait(false); - - tasksInFlight.Remove(completedTask); - } - while (index < queue.Length || tasksInFlight.Count != 0); - } - } -} +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; + +namespace DepotDownloader +{ + static class Util + { + public static string GetSteamOS() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return "windows"; + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + return "macos"; + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + return "linux"; + } + + return "unknown"; + } + + public static string GetSteamArch() + { + return Environment.Is64BitOperatingSystem ? "64" : "32"; + } + + public static string ReadPassword() + { + ConsoleKeyInfo keyInfo; + StringBuilder password = new StringBuilder(); + + do + { + keyInfo = Console.ReadKey( true ); + + if ( keyInfo.Key == ConsoleKey.Backspace ) + { + if ( password.Length > 0 ) + password.Remove( password.Length - 1, 1 ); + continue; + } + + /* Printable ASCII characters only */ + char c = keyInfo.KeyChar; + if ( c >= ' ' && c <= '~' ) + password.Append( c ); + } while ( keyInfo.Key != ConsoleKey.Enter ); + + return password.ToString(); + } + + // Validate a file against Steam3 Chunk data + public static List ValidateSteam3FileChecksums(FileStream fs, ProtoManifest.ChunkData[] chunkdata) + { + var neededChunks = new List(); + int read; + + foreach (var data in chunkdata) + { + byte[] chunk = new byte[data.UncompressedLength]; + fs.Seek((long)data.Offset, SeekOrigin.Begin); + read = fs.Read(chunk, 0, (int)data.UncompressedLength); + + byte[] tempchunk; + if (read < data.UncompressedLength) + { + tempchunk = new byte[read]; + Array.Copy(chunk, 0, tempchunk, 0, read); + } + else + { + tempchunk = chunk; + } + + byte[] adler = AdlerHash(tempchunk); + if (!adler.SequenceEqual(data.Checksum)) + { + neededChunks.Add(data); + } + } + + return neededChunks; + } + + public static byte[] AdlerHash(byte[] input) + { + uint a = 0, b = 0; + for (int i = 0; i < input.Length; i++) + { + a = (a + input[i]) % 65521; + b = (b + a) % 65521; + } + return BitConverter.GetBytes(a | (b << 16)); + } + + public static byte[] SHAHash( byte[] input ) + { + using (var sha = SHA1.Create()) + { + var output = sha.ComputeHash( input ); + + return output; + } + } + + public static byte[] DecodeHexString( string hex ) + { + if ( hex == null ) + return null; + + int chars = hex.Length; + byte[] bytes = new byte[ chars / 2 ]; + + for ( int i = 0 ; i < chars ; i += 2 ) + bytes[ i / 2 ] = Convert.ToByte( hex.Substring( i, 2 ), 16 ); + + return bytes; + } + + public static string EncodeHexString( byte[] input ) + { + return input.Aggregate( new StringBuilder(), + ( sb, v ) => sb.Append( v.ToString( "x2" ) ) + ).ToString(); + } + + public static async Task InvokeAsync(IEnumerable> taskFactories, int maxDegreeOfParallelism) + { + if (taskFactories == null) throw new ArgumentNullException(nameof(taskFactories)); + if (maxDegreeOfParallelism <= 0) throw new ArgumentException(nameof(maxDegreeOfParallelism)); + + Func[] queue = taskFactories.ToArray(); + + if (queue.Length == 0) + { + return; + } + + List tasksInFlight = new List(maxDegreeOfParallelism); + int index = 0; + + do + { + while (tasksInFlight.Count < maxDegreeOfParallelism && index < queue.Length) + { + Func taskFactory = queue[index++]; + + tasksInFlight.Add(taskFactory()); + } + + Task completedTask = await Task.WhenAny(tasksInFlight).ConfigureAwait(false); + + await completedTask.ConfigureAwait(false); + + tasksInFlight.Remove(completedTask); + } + while (index < queue.Length || tasksInFlight.Count != 0); + } + } +} diff --git a/depotdownloader/DepotDownloader/app.config b/depotdownloader/DepotDownloader/app.config index b9dbca8b..75579a19 100644 --- a/depotdownloader/DepotDownloader/app.config +++ b/depotdownloader/DepotDownloader/app.config @@ -1,14 +1,14 @@ - - - - - - - - - - - - - - + + + + + + + + + + + + + + diff --git a/depotdownloader/DepotDownloader/runtimeconfig.template.json b/depotdownloader/DepotDownloader/runtimeconfig.template.json index 59603843..7c0d4363 100644 --- a/depotdownloader/DepotDownloader/runtimeconfig.template.json +++ b/depotdownloader/DepotDownloader/runtimeconfig.template.json @@ -1,3 +1,3 @@ - { - "rollForwardOnNoCandidateFx": 2 + { + "rollForwardOnNoCandidateFx": 2 } \ No newline at end of file diff --git a/depotdownloader/LICENSE b/depotdownloader/LICENSE index d159169d..89e08fb0 100644 --- a/depotdownloader/LICENSE +++ b/depotdownloader/LICENSE @@ -1,339 +1,339 @@ - GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 - - Copyright (C) 1989, 1991 Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -License is intended to guarantee your freedom to share and change free -software--to make sure the software is free for all its users. This -General Public License applies to most of the Free Software -Foundation's software and to any other program whose authors commit to -using it. (Some other Free Software Foundation software is covered by -the GNU Lesser General Public License instead.) You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -this service if you wish), that you receive source code or can get it -if you want it, that you can change the software or use pieces of it -in new free programs; and that you know you can do these things. - - To protect your rights, we need to make restrictions that forbid -anyone to deny you these rights or to ask you to surrender the rights. -These restrictions translate to certain responsibilities for you if you -distribute copies of the software, or if you modify it. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must give the recipients all the rights that -you have. You must make sure that they, too, receive or can get the -source code. And you must show them these terms so they know their -rights. - - We protect your rights with two steps: (1) copyright the software, and -(2) offer you this license which gives you legal permission to copy, -distribute and/or modify the software. - - Also, for each author's protection and ours, we want to make certain -that everyone understands that there is no warranty for this free -software. If the software is modified by someone else and passed on, we -want its recipients to know that what they have is not the original, so -that any problems introduced by others will not reflect on the original -authors' reputations. - - Finally, any free program is threatened constantly by software -patents. We wish to avoid the danger that redistributors of a free -program will individually obtain patent licenses, in effect making the -program proprietary. To prevent this, we have made it clear that any -patent must be licensed for everyone's free use or not licensed at all. - - The precise terms and conditions for copying, distribution and -modification follow. - - GNU GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License applies to any program or other work which contains -a notice placed by the copyright holder saying it may be distributed -under the terms of this General Public License. The "Program", below, -refers to any such program or work, and a "work based on the Program" -means either the Program or any derivative work under copyright law: -that is to say, a work containing the Program or a portion of it, -either verbatim or with modifications and/or translated into another -language. (Hereinafter, translation is included without limitation in -the term "modification".) Each licensee is addressed as "you". - -Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running the Program is not restricted, and the output from the Program -is covered only if its contents constitute a work based on the -Program (independent of having been made by running the Program). -Whether that is true depends on what the Program does. - - 1. You may copy and distribute verbatim copies of the Program's -source code as you receive it, in any medium, provided that you -conspicuously and appropriately publish on each copy an appropriate -copyright notice and disclaimer of warranty; keep intact all the -notices that refer to this License and to the absence of any warranty; -and give any other recipients of the Program a copy of this License -along with the Program. - -You may charge a fee for the physical act of transferring a copy, and -you may at your option offer warranty protection in exchange for a fee. - - 2. You may modify your copy or copies of the Program or any portion -of it, thus forming a work based on the Program, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) You must cause the modified files to carry prominent notices - stating that you changed the files and the date of any change. - - b) You must cause any work that you distribute or publish, that in - whole or in part contains or is derived from the Program or any - part thereof, to be licensed as a whole at no charge to all third - parties under the terms of this License. - - c) If the modified program normally reads commands interactively - when run, you must cause it, when started running for such - interactive use in the most ordinary way, to print or display an - announcement including an appropriate copyright notice and a - notice that there is no warranty (or else, saying that you provide - a warranty) and that users may redistribute the program under - these conditions, and telling the user how to view a copy of this - License. (Exception: if the Program itself is interactive but - does not normally print such an announcement, your work based on - the Program is not required to print an announcement.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Program, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Program, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Program. - -In addition, mere aggregation of another work not based on the Program -with the Program (or with a work based on the Program) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may copy and distribute the Program (or a work based on it, -under Section 2) in object code or executable form under the terms of -Sections 1 and 2 above provided that you also do one of the following: - - a) Accompany it with the complete corresponding machine-readable - source code, which must be distributed under the terms of Sections - 1 and 2 above on a medium customarily used for software interchange; or, - - b) Accompany it with a written offer, valid for at least three - years, to give any third party, for a charge no more than your - cost of physically performing source distribution, a complete - machine-readable copy of the corresponding source code, to be - distributed under the terms of Sections 1 and 2 above on a medium - customarily used for software interchange; or, - - c) Accompany it with the information you received as to the offer - to distribute corresponding source code. (This alternative is - allowed only for noncommercial distribution and only if you - received the program in object code or executable form with such - an offer, in accord with Subsection b above.) - -The source code for a work means the preferred form of the work for -making modifications to it. For an executable work, complete source -code means all the source code for all modules it contains, plus any -associated interface definition files, plus the scripts used to -control compilation and installation of the executable. However, as a -special exception, the source code distributed need not include -anything that is normally distributed (in either source or binary -form) with the major components (compiler, kernel, and so on) of the -operating system on which the executable runs, unless that component -itself accompanies the executable. - -If distribution of executable or object code is made by offering -access to copy from a designated place, then offering equivalent -access to copy the source code from the same place counts as -distribution of the source code, even though third parties are not -compelled to copy the source along with the object code. - - 4. You may not copy, modify, sublicense, or distribute the Program -except as expressly provided under this License. Any attempt -otherwise to copy, modify, sublicense or distribute the Program is -void, and will automatically terminate your rights under this License. -However, parties who have received copies, or rights, from you under -this License will not have their licenses terminated so long as such -parties remain in full compliance. - - 5. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Program or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Program (or any work based on the -Program), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Program or works based on it. - - 6. Each time you redistribute the Program (or any work based on the -Program), the recipient automatically receives a license from the -original licensor to copy, distribute or modify the Program subject to -these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties to -this License. - - 7. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Program at all. For example, if a patent -license would not permit royalty-free redistribution of the Program by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Program. - -If any portion of this section is held invalid or unenforceable under -any particular circumstance, the balance of the section is intended to -apply and the section as a whole is intended to apply in other -circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system, which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 8. If the distribution and/or use of the Program is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Program under this License -may add an explicit geographical distribution limitation excluding -those countries, so that distribution is permitted only in or among -countries not thus excluded. In such case, this License incorporates -the limitation as if written in the body of this License. - - 9. The Free Software Foundation may publish revised and/or new versions -of the General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - -Each version is given a distinguishing version number. If the Program -specifies a version number of this License which applies to it and "any -later version", you have the option of following the terms and conditions -either of that version or of any later version published by the Free -Software Foundation. If the Program does not specify a version number of -this License, you may choose any version ever published by the Free Software -Foundation. - - 10. If you wish to incorporate parts of the Program into other free -programs whose distribution conditions are different, write to the author -to ask for permission. For software which is copyrighted by the Free -Software Foundation, write to the Free Software Foundation; we sometimes -make exceptions for this. Our decision will be guided by the two goals -of preserving the free status of all derivatives of our free software and -of promoting the sharing and reuse of software generally. - - NO WARRANTY - - 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY -FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN -OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES -PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED -OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS -TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE -PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, -REPAIR OR CORRECTION. - - 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR -REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, -INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING -OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED -TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY -YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER -PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE -POSSIBILITY OF SUCH DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -Also add information on how to contact you by electronic and paper mail. - -If the program is interactive, make it output a short notice like this -when it starts in an interactive mode: - - Gnomovision version 69, Copyright (C) year name of author - Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, the commands you use may -be called something other than `show w' and `show c'; they could even be -mouse-clicks or menu items--whatever suits your program. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the program, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the program - `Gnomovision' (which makes passes at compilers) written by James Hacker. - - , 1 April 1989 - Ty Coon, President of Vice - -This General Public License does not permit incorporating your program into -proprietary programs. If your program is a subroutine library, you may -consider it more useful to permit linking proprietary applications with the -library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/depotdownloader/README.md b/depotdownloader/README.md index e71b5519..323453ec 100644 --- a/depotdownloader/README.md +++ b/depotdownloader/README.md @@ -1,55 +1,55 @@ -DepotDownloader -=============== - -Steam depot downloader utilizing the SteamKit2 library. Supports .NET Core 2.0 - -### Downloading one or all depots for an app -``` -dotnet DepotDownloader.dll -app [-depot [-manifest ]] - [-username [-password ]] [other options] -``` - -For example: `dotnet DepotDownloader.dll -app 730 -depot 731 -manifest 7617088375292372759` - -### Downloading a workshop item using pubfile id -``` -dotnet DepotDownloader.dll -app -pubfile [-username [-password ]] -``` - -For example: `dotnet DepotDownloader.dll -app 730 -pubfile 1885082371` - -### Downloading a workshop item using ugc id -``` -dotnet DepotDownloader.dll -app -ugc [-username [-password ]] -``` - -For example: `dotnet DepotDownloader.dll -app 730 -ugc 770604181014286929` - -## Parameters - -Parameter | Description ---------- | ----------- --app \<#> | the AppID to download. --depot \<#> | the DepotID to download. --manifest \ | manifest id of content to download (requires -depot, default: current for branch). --ugc \<#> | the UGC ID to download. --beta \ | download from specified branch if available (default: Public). --betapassword \ | branch password if applicable. --all-platforms | downloads all platform-specific depots when -app is used. --os \ | the operating system for which to download the game (windows, macos or linux, default: OS the program is currently running on) --osarch \ | the architecture for which to download the game (32 or 64, default: the host's architecture) --all-languages | download all language-specific depots when -app is used. --language \ | the language for which to download the game (default: english) --lowviolence | download low violence depots when -app is used. --pubfile \<#> | the PublishedFileId to download. (Will automatically resolve to UGC id) --username \ | the username of the account to login to for restricted content. --password \ | the password of the account to login to for restricted content. --remember-password | if set, remember the password for subsequent logins of this user. --dir \ | the directory in which to place downloaded files. --filelist \ | a list of files to download (from the manifest). Can optionally use regex to download only certain files. --validate | Include checksum verification of files already downloaded --manifest-only | downloads a human readable manifest for any depots that would be downloaded. --cellid \<#> | the overridden CellID of the content server to download from. --max-servers \<#> | maximum number of content servers to use. (default: 20). --max-downloads \<#> | maximum number of chunks to download concurrently. (default: 8). --loginid \<#> | a unique 32-bit integer Steam LogonID in decimal, required if running multiple instances of DepotDownloader concurrently. +DepotDownloader +=============== + +Steam depot downloader utilizing the SteamKit2 library. Supports .NET Core 2.0 + +### Downloading one or all depots for an app +``` +dotnet DepotDownloader.dll -app [-depot [-manifest ]] + [-username [-password ]] [other options] +``` + +For example: `dotnet DepotDownloader.dll -app 730 -depot 731 -manifest 7617088375292372759` + +### Downloading a workshop item using pubfile id +``` +dotnet DepotDownloader.dll -app -pubfile [-username [-password ]] +``` + +For example: `dotnet DepotDownloader.dll -app 730 -pubfile 1885082371` + +### Downloading a workshop item using ugc id +``` +dotnet DepotDownloader.dll -app -ugc [-username [-password ]] +``` + +For example: `dotnet DepotDownloader.dll -app 730 -ugc 770604181014286929` + +## Parameters + +Parameter | Description +--------- | ----------- +-app \<#> | the AppID to download. +-depot \<#> | the DepotID to download. +-manifest \ | manifest id of content to download (requires -depot, default: current for branch). +-ugc \<#> | the UGC ID to download. +-beta \ | download from specified branch if available (default: Public). +-betapassword \ | branch password if applicable. +-all-platforms | downloads all platform-specific depots when -app is used. +-os \ | the operating system for which to download the game (windows, macos or linux, default: OS the program is currently running on) +-osarch \ | the architecture for which to download the game (32 or 64, default: the host's architecture) +-all-languages | download all language-specific depots when -app is used. +-language \ | the language for which to download the game (default: english) +-lowviolence | download low violence depots when -app is used. +-pubfile \<#> | the PublishedFileId to download. (Will automatically resolve to UGC id) +-username \ | the username of the account to login to for restricted content. +-password \ | the password of the account to login to for restricted content. +-remember-password | if set, remember the password for subsequent logins of this user. +-dir \ | the directory in which to place downloaded files. +-filelist \ | a list of files to download (from the manifest). Can optionally use regex to download only certain files. +-validate | Include checksum verification of files already downloaded +-manifest-only | downloads a human readable manifest for any depots that would be downloaded. +-cellid \<#> | the overridden CellID of the content server to download from. +-max-servers \<#> | maximum number of content servers to use. (default: 20). +-max-downloads \<#> | maximum number of chunks to download concurrently. (default: 8). +-loginid \<#> | a unique 32-bit integer Steam LogonID in decimal, required if running multiple instances of DepotDownloader concurrently.