diff --git a/Build/Updater.exe b/Build/Updater.exe index 4ad1e3bb05cf3121b314ba391730189578d94c47..d28f4c9bcdeff89fff65dbe83b674a2ca482ab2a 100644 Binary files a/Build/Updater.exe and b/Build/Updater.exe differ diff --git a/Source/Tools/Tools.sln b/Source/Tools/Tools.sln new file mode 100644 index 0000000000000000000000000000000000000000..9c384d7604503d20090dc638247d9c5acc867e85 --- /dev/null +++ b/Source/Tools/Tools.sln @@ -0,0 +1,20 @@ + +Microsoft Visual Studio Solution File, Format Version 10.00 +# Visual Studio 2008 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Updater", "Updater\Updater.csproj", "{2A0BA1B2-A0F1-479F-9083-2EC11D6B70DF}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {2A0BA1B2-A0F1-479F-9083-2EC11D6B70DF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2A0BA1B2-A0F1-479F-9083-2EC11D6B70DF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2A0BA1B2-A0F1-479F-9083-2EC11D6B70DF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2A0BA1B2-A0F1-479F-9083-2EC11D6B70DF}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Source/Tools/Updater/Helpers/EmbeddedAssembly.cs b/Source/Tools/Updater/Helpers/EmbeddedAssembly.cs new file mode 100644 index 0000000000000000000000000000000000000000..43d3d44227d5f2db1dd19e14370418aa7f17e607 --- /dev/null +++ b/Source/Tools/Updater/Helpers/EmbeddedAssembly.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Security.Cryptography; + +//Source: http://www.codeproject.com/Articles/528178/Load-DLL-From-Embedded-Resource +namespace mxd.GZDBUpdater +{ + public static class EmbeddedAssembly + { + private static Dictionary<string, Assembly> dic = new Dictionary<string, Assembly>(); + + public static void Load(string embeddedResource, string filename) + { + byte[] ba; + Assembly asm; + Assembly curAsm = Assembly.GetExecutingAssembly(); + + using(Stream stm = curAsm.GetManifestResourceStream(embeddedResource)) + { + // Either the file is not existed or it is not mark as embedded resource + if(stm == null) throw new Exception(embeddedResource + " is not found in Embedded Resources."); + + // Get byte[] from the file from embedded resource + ba = new byte[(int)stm.Length]; + stm.Read(ba, 0, (int)stm.Length); + try + { + asm = Assembly.Load(ba); + + // Add the assembly/dll into dictionary + dic.Add(asm.FullName, asm); + return; + } + catch + { + // Purposely do nothing + // Unmanaged dll or assembly cannot be loaded directly from byte[] + // Let the process fall through for next part + } + } + + bool fileOk = false; + string tempFile; + + using(SHA1CryptoServiceProvider sha1 = new SHA1CryptoServiceProvider()) + { + string fileHash = BitConverter.ToString(sha1.ComputeHash(ba)).Replace("-", string.Empty); + tempFile = Path.GetTempPath() + filename; + + if(File.Exists(tempFile)) + { + byte[] bb = File.ReadAllBytes(tempFile); + string fileHash2 = BitConverter.ToString(sha1.ComputeHash(bb)).Replace("-", string.Empty); + fileOk = (fileHash == fileHash2); + } + } + + if(!fileOk) + { + File.WriteAllBytes(tempFile, ba); + } + + asm = Assembly.LoadFile(tempFile); + dic.Add(asm.FullName, asm); + } + + public static Assembly Get(string assemblyFullName) + { + if(dic == null || dic.Count == 0) return null; + return (dic.ContainsKey(assemblyFullName) ? dic[assemblyFullName] : null); + } + } +} diff --git a/Source/Tools/Updater/Helpers/TaskbarProgress.cs b/Source/Tools/Updater/Helpers/TaskbarProgress.cs new file mode 100644 index 0000000000000000000000000000000000000000..bb53ee35e61b276dca54ac15bcfc2ef4204cc88a --- /dev/null +++ b/Source/Tools/Updater/Helpers/TaskbarProgress.cs @@ -0,0 +1,66 @@ +using System; +using System.Runtime.InteropServices; + +namespace mxd.GZDBUpdater +{ + // http://stackoverflow.com/questions/1295890/windows-7-progress-bar-in-taskbar-in-c + public static class TaskbarProgress + { + public enum TaskbarStates + { + NoProgress = 0, + Indeterminate = 0x1, + Normal = 0x2, + Error = 0x4, + Paused = 0x8 + } + + [ComImportAttribute()] + [GuidAttribute("ea1afb91-9e28-4b86-90e9-9e9f8a5eefaf")] + [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + private interface ITaskbarList3 + { + // ITaskbarList + [PreserveSig] + void HrInit(); + [PreserveSig] + void AddTab(IntPtr hwnd); + [PreserveSig] + void DeleteTab(IntPtr hwnd); + [PreserveSig] + void ActivateTab(IntPtr hwnd); + [PreserveSig] + void SetActiveAlt(IntPtr hwnd); + + // ITaskbarList2 + [PreserveSig] + void MarkFullscreenWindow(IntPtr hwnd, [MarshalAs(UnmanagedType.Bool)] bool fFullscreen); + + // ITaskbarList3 + [PreserveSig] + void SetProgressValue(IntPtr hwnd, UInt64 ullCompleted, UInt64 ullTotal); + [PreserveSig] + void SetProgressState(IntPtr hwnd, TaskbarStates state); + } + + [GuidAttribute("56FDF344-FD6D-11d0-958A-006097C9A090")] + [ClassInterfaceAttribute(ClassInterfaceType.None)] + [ComImportAttribute()] + private class TaskbarInstance + { + } + + private static ITaskbarList3 taskbarInstance = (ITaskbarList3)new TaskbarInstance(); + private static bool taskbarSupported = Environment.OSVersion.Version >= new Version(6, 1); + + public static void SetState(IntPtr windowHandle, TaskbarStates taskbarState) + { + if(taskbarSupported) taskbarInstance.SetProgressState(windowHandle, taskbarState); + } + + public static void SetValue(IntPtr windowHandle, double progressValue, double progressMax) + { + if(taskbarSupported) taskbarInstance.SetProgressValue(windowHandle, (ulong)progressValue, (ulong)progressMax); + } + } +} diff --git a/Source/Tools/Updater/Helpers/Webdata.cs b/Source/Tools/Updater/Helpers/Webdata.cs new file mode 100644 index 0000000000000000000000000000000000000000..cb4ac7b98782e89879a1c340f3ce838cac118f4a --- /dev/null +++ b/Source/Tools/Updater/Helpers/Webdata.cs @@ -0,0 +1,104 @@ +using System; +using System.Net; +using System.IO; + +namespace mxd.GZDBUpdater +{ + public delegate void BytesDownloadedEventHandler(ByteArgs e); + + public class ByteArgs : EventArgs + { + public int Downloaded; + public int Total; + } + + static class Webdata + { + public static event BytesDownloadedEventHandler BytesDownloaded; + + public static bool SaveWebFile(string url, string file, string targetFolder) + { + try + { + MemoryStream memoryStream = DownloadWebFile(Path.Combine(url, file)); + if(memoryStream == null) return false; + + //Convert the downloaded stream to a byte array + byte[] downloadedData = memoryStream.ToArray(); + + //Release resources + memoryStream.Close(); + + //Write bytes to the specified file + FileStream newFile = new FileStream(targetFolder + file, FileMode.Create); + newFile.Write(downloadedData, 0, downloadedData.Length); + newFile.Close(); + + return !MainForm.AppClosing; + } + catch(Exception e) + { + //We may not be connected to the internet + //Or the URL may be incorrect + MainForm.ErrorDescription = "Failed to download the update...\n" + e.Message; + return false; + } + } + + public static MemoryStream DownloadWebFile(string url) + { + //open a data stream from the supplied URL + WebRequest webReq = WebRequest.Create(url); + WebResponse webResponse = webReq.GetResponse(); + Stream dataStream = webResponse.GetResponseStream(); + + //Download the data in chuncks + byte[] dataBuffer = new byte[1024]; + + //Get the total size of the download + int dataLength = (int)webResponse.ContentLength; + + //lets declare our downloaded bytes event args + ByteArgs byteArgs = new ByteArgs(); + + byteArgs.Downloaded = 0; + byteArgs.Total = dataLength; + + //we need to test for a null as if an event is not consumed we will get an exception + if(BytesDownloaded != null) BytesDownloaded(byteArgs); + + //Download the data + MemoryStream memoryStream = new MemoryStream(); + while(!MainForm.AppClosing) + { + //Let's try and read the data + int bytesFromStream = dataStream.Read(dataBuffer, 0, dataBuffer.Length); + if(bytesFromStream == 0) + { + byteArgs.Downloaded = dataLength; + byteArgs.Total = dataLength; + if(BytesDownloaded != null) BytesDownloaded(byteArgs); + + //Download complete + break; + } + else + { + //Write the downloaded data + memoryStream.Write(dataBuffer, 0, bytesFromStream); + + byteArgs.Downloaded += bytesFromStream; + byteArgs.Total = dataLength; + if(BytesDownloaded != null) BytesDownloaded(byteArgs); + } + } + + //Release resources + dataStream.Close(); + + // Rewind and return the stream + memoryStream.Position = 0; + return (MainForm.AppClosing ? null : memoryStream); + } + } +} diff --git a/Source/Tools/Updater/MainForm.Designer.cs b/Source/Tools/Updater/MainForm.Designer.cs new file mode 100644 index 0000000000000000000000000000000000000000..a39ac6f8be66052022e42b003cdb739ae5ea3967 --- /dev/null +++ b/Source/Tools/Updater/MainForm.Designer.cs @@ -0,0 +1,89 @@ +namespace mxd.GZDBUpdater +{ + partial class MainForm + { + /// <summary> + /// Required designer variable. + /// </summary> + private System.ComponentModel.IContainer components = null; + + /// <summary> + /// Clean up any resources being used. + /// </summary> + /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param> + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// <summary> + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// </summary> + private void InitializeComponent() + { + this.progressbar = new System.Windows.Forms.ProgressBar(); + this.label1 = new System.Windows.Forms.Label(); + this.cancel = new System.Windows.Forms.Button(); + this.SuspendLayout(); + // + // progressBar1 + // + this.progressbar.Location = new System.Drawing.Point(10, 43); + this.progressbar.Name = "progressbar"; + this.progressbar.Size = new System.Drawing.Size(230, 23); + this.progressbar.TabIndex = 0; + // + // line1 + // + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(7, 17); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(64, 13); + this.label1.TabIndex = 3; + this.label1.Text = " Initializing..."; + // + // cancel + // + this.cancel.Location = new System.Drawing.Point(246, 43); + this.cancel.Name = "cancel"; + this.cancel.Size = new System.Drawing.Size(75, 23); + this.cancel.TabIndex = 4; + this.cancel.Text = "Cancel"; + this.cancel.UseVisualStyleBackColor = true; + this.cancel.Click += new System.EventHandler(this.cancel_Click); + // + // Updater + // + this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; + this.ClientSize = new System.Drawing.Size(333, 76); + this.Controls.Add(this.cancel); + this.Controls.Add(this.label1); + this.Controls.Add(this.progressbar); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + this.MaximizeBox = false; + this.Name = "MainForm"; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; + this.Text = "GZDoom Builder Updater"; + this.Load += new System.EventHandler(this.MainForm_Load); + this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.MainForm_FormClosing); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.ProgressBar progressbar; + private System.Windows.Forms.Label label1; + private System.Windows.Forms.Button cancel; + } +} + diff --git a/Source/Tools/Updater/MainForm.cs b/Source/Tools/Updater/MainForm.cs new file mode 100644 index 0000000000000000000000000000000000000000..8ba570179328520b1171d5929c309e9886933b30 --- /dev/null +++ b/Source/Tools/Updater/MainForm.cs @@ -0,0 +1,474 @@ +#region ======================== Namespaces + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Reflection; +using System.Windows.Forms; +using System.IO; +using System.Diagnostics; +using System.Threading; +using SharpCompress.Archive; +using SharpCompress.Common; +using SharpCompress.Reader; +using System.Security.AccessControl; +using System.Security.Principal; + +#endregion + +namespace mxd.GZDBUpdater +{ + public partial class MainForm : Form + { + #region ======================== Variables + + private string processToEnd = string.Empty; + private string downloadFile = string.Empty; + private const string revisionwildcard = "[REVNUM]"; + private string URL = string.Empty; + private readonly string updateFolder = Application.StartupPath + @"\_update\"; + private string appFileName = string.Empty; + private static BackgroundWorker worker; + private static bool appclosing; + private static MainForm me; + private const string ERROR_TITLE = "Updater"; + + #endregion + + #region ======================== Delegates + + private delegate void SetLabelCallback(Label label, string text); + private delegate void UpdateProgressBarCallback(ByteArgs args, int step, int totalsteps); + private delegate void CloseDelegate(); + + #endregion + + #region ======================== Properties + + public static string ErrorDescription; + public static bool AppClosing { get { return appclosing; } } + + #endregion + + #region ======================== Constructor + + public MainForm() + { + if(!CheckPremissions(Application.StartupPath)) + { + ErrorDescription = "Update failed: your account does not have write access to the destination folder \"" + Application.StartupPath + "\""; + InvokeClose(); + } + else if(!File.Exists("Updater.ini")) + { + ErrorDescription = "Unable to locate 'Updater.ini'..."; + InvokeClose(); + } + else if(!LoadConfig("Updater.ini")) + { + InvokeClose(); + } + else + { + me = this; + InitializeComponent(); + } + } + + #endregion + + #region ======================== Updater thread + + private void BackgroundWorker(object sender, DoWorkEventArgs e) + { + UpdateLabel(label1, "1/6: Checking revisions..."); + if(!UpdateRequired()) + { + e.Cancel = true; + return; + } + PreDownload(); + + UpdateLabel(label1, "2/6: Downloading Update..."); + Webdata.BytesDownloaded += WebdataOnBytesDownloaded; + if(!Webdata.SaveWebFile(URL, downloadFile, updateFolder)) + { + e.Cancel = true; + Webdata.BytesDownloaded -= WebdataOnBytesDownloaded; + return; + } + + // Check if the editor is running... + try + { + Process[] processes = Process.GetProcesses(); + List<Process> toclose = new List<Process>(); + + // Gather all running editor processes... + foreach(Process process in processes) + { + if(process.ProcessName == processToEnd + && Path.GetDirectoryName(process.MainModule.FileName) == Application.StartupPath) + { + toclose.Add(process); + } + } + + // Close them + if(toclose.Count > 0) + { + TaskbarProgress.SetState(this.Handle, TaskbarProgress.TaskbarStates.Paused); + if(MessageBox.Show(this, "The editor needs to be closed.\n\nPress OK to close it and proceed with update.\nPress Cancel to cancel update.", + ERROR_TITLE, MessageBoxButtons.OKCancel) == DialogResult.OK) + { + UpdateLabel(label1, "3/6: Stopping " + processToEnd); + Thread.Sleep(500); + + foreach(Process p in toclose) + { + if(p != null) p.Kill(); + } + } + else + { + e.Cancel = true; + return; + } + } + } + catch(Exception ex) + { + ErrorDescription = "Failed to stop the main process...\n" + ex.Message; + e.Cancel = true; + return; + } + + UpdateLabel(label1, "4/6: Decompressing package..."); + Thread.Sleep(500); + if(!Unpack(updateFolder + downloadFile, Application.StartupPath)) + { + e.Cancel = true; + return; + } + + UpdateLabel(label1, "5/6: Moving files..."); + Thread.Sleep(500); + MoveFiles(); + + UpdateLabel(label1, "6/6: Wrapping up..."); + Thread.Sleep(500); + PostDownload(); + } + + private static void StopBackgroundWorker() + { + if(worker != null && !worker.CancellationPending) + { + me.UpdateLabel(me.label1, "Stopping Background Thread..."); + worker.CancelAsync(); + while(worker.IsBusy) Application.DoEvents(); + } + } + + private void WorkerOnRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) + { + InvokeClose(); + } + + #endregion + + #region ======================== Methods + + private void UpdateLabel(Label label, string text) + { + if(label.InvokeRequired) + { + SetLabelCallback d = UpdateLabel; + label.Invoke(d, new object[] { label, text }); + } + else + { + label.Text = text; + label.Refresh(); + Invalidate(); + } + } + + private void InvokeClose() + { + if(this.Disposing || this.IsDisposed) return; + if(this.InvokeRequired) + { + CloseDelegate d = Close; + this.Invoke(d); + } + else + { + if(!appclosing && !string.IsNullOrEmpty(ErrorDescription)) + { + if(!string.IsNullOrEmpty(URL)) + { + ErrorDescription += Environment.NewLine + Environment.NewLine + "Would you like to download the update manually?"; + TaskbarProgress.SetState(this.Handle, TaskbarProgress.TaskbarStates.Error); + if(MessageBox.Show(this, ErrorDescription, ERROR_TITLE, MessageBoxButtons.YesNo) == DialogResult.Yes) + Process.Start(URL); + } + else + { + MessageBox.Show(this, ErrorDescription, ERROR_TITLE, MessageBoxButtons.OK); + } + } + + WrapUp(); + Close(); + } + } + + private bool UpdateRequired() + { + // Get local revision number + int localrev = -1; + if(File.Exists(appFileName)) + { + var info = FileVersionInfo.GetVersionInfo(appFileName); + localrev = info.ProductPrivatePart; + } + + // Get remote revision number + int remoterev; + using(MemoryStream stream = Webdata.DownloadWebFile(Path.Combine(URL, "Version.txt"))) + { + if(stream == null) + { + ErrorDescription = "Failed to retrieve remote revision info."; + return false; + } + + string s; + using(StreamReader reader = new StreamReader(stream)) + { + s = reader.ReadToEnd(); + } + + if(!int.TryParse(s, out remoterev)) + { + ErrorDescription = "Failed to retrieve remote revision number."; + return false; + } + } + + // Replace wildcard with remoterev + downloadFile = downloadFile.Replace(revisionwildcard, remoterev.ToString()); + + if(remoterev > 0 && remoterev <= localrev) + { + URL = string.Empty; + ErrorDescription = "Your version is up to date!"; + } + return remoterev > localrev; + } + + private bool LoadConfig(string filename) + { + string[] lines = File.ReadAllLines(filename); + foreach (string line in lines) + { + if(line.StartsWith("URL")) + { + URL = line.Substring(3).Trim(); + } + else if(line.StartsWith("FileName")) + { + appFileName = line.Substring(8).Trim(); + processToEnd = Path.GetFileNameWithoutExtension(appFileName); + } + else if(line.StartsWith("UpdateName")) + { + downloadFile = line.Substring(10).Trim(); + } + } + + // Sanity cheks + if(string.IsNullOrEmpty(URL)) + { + ErrorDescription = "URL is not specified in " + filename + "!"; + return false; + } + + if(string.IsNullOrEmpty(appFileName) || string.IsNullOrEmpty(processToEnd)) + { + ErrorDescription = "FileName is not specified in " + filename + "!"; + return false; + } + + if(string.IsNullOrEmpty(downloadFile) || !downloadFile.Contains(revisionwildcard)) + { + ErrorDescription = "UpdateName is invalid or not specified in " + filename + "!"; + return false; + } + + return true; + } + + private bool Unpack(string file, string unZipTo) + { + try + { + using(IArchive arc = ArchiveFactory.Open(file)) + { + if(!arc.IsComplete) + { + ErrorDescription = "Update failed: downloaded file is not complete..."; + return false; + } + + IReader reader = arc.ExtractAllEntries(); + + // Get number of files... + int curentry = 0; + int totalentries = 0; + foreach(var entry in arc.Entries) totalentries++; + + string ourname = Path.GetFileName(Process.GetCurrentProcess().MainModule.FileName); + + // Unpack all + while(reader.MoveToNextEntry()) + { + if(appclosing) break; + if(reader.Entry.IsDirectory || Path.GetFileName(reader.Entry.Key) == ourname) continue; // Don't try to overrite ourselves... + reader.WriteEntryToDirectory(unZipTo, ExtractOptions.ExtractFullPath | ExtractOptions.Overwrite); + UpdateProgressBar(new ByteArgs { Downloaded = curentry++, Total = totalentries }, 1, 2); + } + } + } + catch(Exception e) + { + ErrorDescription = "Update failed: failed to unpack the update...\n" + e.Message; + return false; + } + + return true; + } + + private static bool CheckPremissions(string path) + { + try + { + DirectoryInfo di = new DirectoryInfo(path); + DirectorySecurity acl = di.GetAccessControl(); + AuthorizationRuleCollection rules = acl.GetAccessRules(true, true, typeof(NTAccount)); + + WindowsIdentity currentUser = WindowsIdentity.GetCurrent(); + WindowsPrincipal principal = new WindowsPrincipal(currentUser); + foreach(AuthorizationRule rule in rules) + { + FileSystemAccessRule fsAccessRule = rule as FileSystemAccessRule; + if(fsAccessRule == null) continue; + + if((fsAccessRule.FileSystemRights & FileSystemRights.WriteData) > 0) + { + NTAccount ntAccount = rule.IdentityReference as NTAccount; + if(ntAccount == null) continue; + if(principal.IsInRole(ntAccount.Value)) return true; + } + } + } + catch(UnauthorizedAccessException) { } + + return false; + } + + private void PreDownload() + { + if(!Directory.Exists(updateFolder)) Directory.CreateDirectory(updateFolder); + } + + private void PostDownload() + { + if(!File.Exists(appFileName)) + { + ErrorDescription = "Unable to located updated executable ('" + appFileName + "')"; + return; + } + + if(appclosing) return; + Process.Start(new ProcessStartInfo { FileName = appFileName }); + } + + private void WrapUp() + { + if(Directory.Exists(updateFolder)) Directory.Delete(updateFolder, true); + } + + private void MoveFiles() + { + DirectoryInfo di = new DirectoryInfo(updateFolder); + FileInfo[] files = di.GetFiles(); + + foreach (FileInfo fi in files) + { + if(fi.Name != downloadFile) File.Copy(updateFolder + fi.Name, Application.StartupPath + fi.Name, true); + } + } + + private void UpdateProgressBar(ByteArgs e, int step, int totalsteps) + { + if(progressbar.InvokeRequired) + { + UpdateProgressBarCallback d = UpdateProgressBar; + progressbar.Invoke(d, new object[] { e, step, totalsteps }); + } + else + { + int stepsize = (int)Math.Round((float)progressbar.Maximum / totalsteps); + float ratio = (float)e.Downloaded / e.Total; + int val = (int)Math.Floor(stepsize * step + stepsize * ratio); + + if(val <= progressbar.Maximum) + { + progressbar.Value = val; + TaskbarProgress.SetValue(this.Handle, progressbar.Value, progressbar.Maximum); + } + progressbar.Refresh(); + Invalidate(); + } + } + + #endregion + + #region ======================== Events + + private void MainForm_Load(object sender, EventArgs e) + { + Version version = Assembly.GetEntryAssembly().GetName().Version; + this.Text += " v" + version.Major + "." + version.Minor; + + worker = new BackgroundWorker(); + worker.DoWork += BackgroundWorker; + worker.RunWorkerCompleted += WorkerOnRunWorkerCompleted; + worker.WorkerSupportsCancellation = true; + worker.RunWorkerAsync(); + } + + private void cancel_Click(object sender, EventArgs e) + { + ErrorDescription = string.Empty; + appclosing = true; + StopBackgroundWorker(); + InvokeClose(); + } + + private void MainForm_FormClosing(object sender, FormClosingEventArgs e) + { + appclosing = true; + StopBackgroundWorker(); + } + + private void WebdataOnBytesDownloaded(ByteArgs ba) + { + UpdateProgressBar(ba, 0, 2); + } + + #endregion + } +} \ No newline at end of file diff --git a/Source/Tools/Updater/MainForm.resx b/Source/Tools/Updater/MainForm.resx new file mode 100644 index 0000000000000000000000000000000000000000..ff31a6db56e23b5a334f34387830ba5b4bd33eb8 --- /dev/null +++ b/Source/Tools/Updater/MainForm.resx @@ -0,0 +1,120 @@ +<?xml version="1.0" encoding="utf-8"?> +<root> + <!-- + Microsoft ResX Schema + + Version 2.0 + + The primary goals of this format is to allow a simple XML format + that is mostly human readable. The generation and parsing of the + various data types are done through the TypeConverter classes + associated with the data types. + + Example: + + ... ado.net/XML headers & schema ... + <resheader name="resmimetype">text/microsoft-resx</resheader> + <resheader name="version">2.0</resheader> + <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> + <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> + <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data> + <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> + <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> + <value>[base64 mime encoded serialized .NET Framework object]</value> + </data> + <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> + <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> + <comment>This is a comment</comment> + </data> + + There are any number of "resheader" rows that contain simple + name/value pairs. + + Each data row contains a name, and value. The row also contains a + type or mimetype. Type corresponds to a .NET class that support + text/value conversion through the TypeConverter architecture. + Classes that don't support this are serialized and stored with the + mimetype set. + + The mimetype is used for serialized objects, and tells the + ResXResourceReader how to depersist the object. This is currently not + extensible. For a given mimetype the value must be set accordingly: + + Note - application/x-microsoft.net.object.binary.base64 is the format + that the ResXResourceWriter will generate, however the reader can + read any of the formats listed below. + + mimetype: application/x-microsoft.net.object.binary.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.soap.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Soap.SoapFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.bytearray.base64 + value : The object must be serialized into a byte array + : using a System.ComponentModel.TypeConverter + : and then encoded with base64 encoding. + --> + <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> + <xsd:import namespace="http://www.w3.org/XML/1998/namespace" /> + <xsd:element name="root" msdata:IsDataSet="true"> + <xsd:complexType> + <xsd:choice maxOccurs="unbounded"> + <xsd:element name="metadata"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + <xsd:attribute name="name" use="required" type="xsd:string" /> + <xsd:attribute name="type" type="xsd:string" /> + <xsd:attribute name="mimetype" type="xsd:string" /> + <xsd:attribute ref="xml:space" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="assembly"> + <xsd:complexType> + <xsd:attribute name="alias" type="xsd:string" /> + <xsd:attribute name="name" type="xsd:string" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="data"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" /> + <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> + <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> + <xsd:attribute ref="xml:space" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="resheader"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required" /> + </xsd:complexType> + </xsd:element> + </xsd:choice> + </xsd:complexType> + </xsd:element> + </xsd:schema> + <resheader name="resmimetype"> + <value>text/microsoft-resx</value> + </resheader> + <resheader name="version"> + <value>2.0</value> + </resheader> + <resheader name="reader"> + <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> + <resheader name="writer"> + <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> +</root> \ No newline at end of file diff --git a/Source/Tools/Updater/Program.cs b/Source/Tools/Updater/Program.cs new file mode 100644 index 0000000000000000000000000000000000000000..6b5693a522b2815dc46eb49624734379f73f5141 --- /dev/null +++ b/Source/Tools/Updater/Program.cs @@ -0,0 +1,29 @@ +using System; +using System.Reflection; +using System.Windows.Forms; + +namespace mxd.GZDBUpdater +{ + static class Program + { + /// <summary> + /// The main entry point for the application. + /// </summary> + [STAThread] + static void Main() + { + EmbeddedAssembly.Load("mxd.GZDBUpdater.Resources.SharpCompress.dll", "SharpCompress.dll"); + AppDomain.CurrentDomain.AssemblyResolve += AssemblyResolve; + + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + MainForm form = new MainForm(); + if(!form.IsDisposed) Application.Run(form); + } + + private static Assembly AssemblyResolve(object sender, ResolveEventArgs args) + { + return EmbeddedAssembly.Get(args.Name); + } + } +} \ No newline at end of file diff --git a/Source/Tools/Updater/Properties/AssemblyInfo.cs b/Source/Tools/Updater/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000000000000000000000000000000000..14f97f67131f27ad47130e54cc7c08683447aae0 --- /dev/null +++ b/Source/Tools/Updater/Properties/AssemblyInfo.cs @@ -0,0 +1,33 @@ +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("GZDoom Builder Updater")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("GZDoom Builder Updater")] +[assembly: AssemblyCopyright("Copyright © MaxED 2016")] +[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("cc4d2cfe-28a3-46e9-8dc3-83e551752985")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +[assembly: AssemblyVersion("1.1.0.0")] +[assembly: AssemblyFileVersion("1.1.0.0")] diff --git a/Source/Tools/Updater/Properties/Resources.Designer.cs b/Source/Tools/Updater/Properties/Resources.Designer.cs new file mode 100644 index 0000000000000000000000000000000000000000..7a6ddd996192e9c6defd9eaf3fa56d088a565ecc --- /dev/null +++ b/Source/Tools/Updater/Properties/Resources.Designer.cs @@ -0,0 +1,63 @@ +//------------------------------------------------------------------------------ +// <auto-generated> +// This code was generated by a tool. +// Runtime Version:2.0.50727.5466 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// </auto-generated> +//------------------------------------------------------------------------------ + +namespace mxd.GZDBUpdater.Properties { + using System; + + + /// <summary> + /// A strongly-typed resource class, for looking up localized strings, etc. + /// </summary> + // 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", "2.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() { + } + + /// <summary> + /// Returns the cached ResourceManager instance used by this class. + /// </summary> + [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("mxd.GZDBUpdater.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// <summary> + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// </summary> + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + } +} diff --git a/Source/Tools/Updater/Properties/Resources.resx b/Source/Tools/Updater/Properties/Resources.resx new file mode 100644 index 0000000000000000000000000000000000000000..5ea0895e324fa7a86681adc56938bad2f2367ba0 --- /dev/null +++ b/Source/Tools/Updater/Properties/Resources.resx @@ -0,0 +1,120 @@ +<?xml version="1.0" encoding="utf-8"?> +<root> + <!-- + Microsoft ResX Schema + + Version 2.0 + + The primary goals of this format is to allow a simple XML format + that is mostly human readable. The generation and parsing of the + various data types are done through the TypeConverter classes + associated with the data types. + + Example: + + ... ado.net/XML headers & schema ... + <resheader name="resmimetype">text/microsoft-resx</resheader> + <resheader name="version">2.0</resheader> + <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> + <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> + <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data> + <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> + <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> + <value>[base64 mime encoded serialized .NET Framework object]</value> + </data> + <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> + <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> + <comment>This is a comment</comment> + </data> + + There are any number of "resheader" rows that contain simple + name/value pairs. + + Each data row contains a name, and value. The row also contains a + type or mimetype. Type corresponds to a .NET class that support + text/value conversion through the TypeConverter architecture. + Classes that don't support this are serialized and stored with the + mimetype set. + + The mimetype is used for serialized objects, and tells the + ResXResourceReader how to depersist the object. This is currently not + extensible. For a given mimetype the value must be set accordingly: + + Note - application/x-microsoft.net.object.binary.base64 is the format + that the ResXResourceWriter will generate, however the reader can + read any of the formats listed below. + + mimetype: application/x-microsoft.net.object.binary.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.soap.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Soap.SoapFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.bytearray.base64 + value : The object must be serialized into a byte array + : using a System.ComponentModel.TypeConverter + : and then encoded with base64 encoding. + --> + <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> + <xsd:import namespace="http://www.w3.org/XML/1998/namespace" /> + <xsd:element name="root" msdata:IsDataSet="true"> + <xsd:complexType> + <xsd:choice maxOccurs="unbounded"> + <xsd:element name="metadata"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + <xsd:attribute name="name" use="required" type="xsd:string" /> + <xsd:attribute name="type" type="xsd:string" /> + <xsd:attribute name="mimetype" type="xsd:string" /> + <xsd:attribute ref="xml:space" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="assembly"> + <xsd:complexType> + <xsd:attribute name="alias" type="xsd:string" /> + <xsd:attribute name="name" type="xsd:string" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="data"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" /> + <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> + <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> + <xsd:attribute ref="xml:space" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="resheader"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required" /> + </xsd:complexType> + </xsd:element> + </xsd:choice> + </xsd:complexType> + </xsd:element> + </xsd:schema> + <resheader name="resmimetype"> + <value>text/microsoft-resx</value> + </resheader> + <resheader name="version"> + <value>2.0</value> + </resheader> + <resheader name="reader"> + <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> + <resheader name="writer"> + <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> +</root> \ No newline at end of file diff --git a/Source/Tools/Updater/Resources/GZDBU.ico b/Source/Tools/Updater/Resources/GZDBU.ico new file mode 100644 index 0000000000000000000000000000000000000000..5091522de9a9038be6149189e15a1cc9bc4a0ad6 Binary files /dev/null and b/Source/Tools/Updater/Resources/GZDBU.ico differ diff --git a/Source/Tools/Updater/Resources/SharpCompress.dll b/Source/Tools/Updater/Resources/SharpCompress.dll new file mode 100644 index 0000000000000000000000000000000000000000..3052a3925a62def37fcc5ce8599e787eb16f3969 Binary files /dev/null and b/Source/Tools/Updater/Resources/SharpCompress.dll differ diff --git a/Source/Tools/Updater/Updater.csproj b/Source/Tools/Updater/Updater.csproj new file mode 100644 index 0000000000000000000000000000000000000000..e8b02942c914ecbf52d31554cc6861ea360f79a8 --- /dev/null +++ b/Source/Tools/Updater/Updater.csproj @@ -0,0 +1,138 @@ +<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="3.5"> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <ProductVersion>9.0.30729</ProductVersion> + <SchemaVersion>2.0</SchemaVersion> + <ProjectGuid>{2A0BA1B2-A0F1-479F-9083-2EC11D6B70DF}</ProjectGuid> + <OutputType>WinExe</OutputType> + <AppDesignerFolder>Properties</AppDesignerFolder> + <RootNamespace>mxd.GZDBUpdater</RootNamespace> + <AssemblyName>Updater</AssemblyName> + <FileUpgradeFlags> + </FileUpgradeFlags> + <OldToolsVersion>2.0</OldToolsVersion> + <UpgradeBackupLocation> + </UpgradeBackupLocation> + <TargetFrameworkVersion>v3.5</TargetFrameworkVersion> + <IsWebBootstrapper>false</IsWebBootstrapper> + <ApplicationIcon>Resources\GZDBU.ico</ApplicationIcon> + <PublishUrl>publish\</PublishUrl> + <Install>true</Install> + <InstallFrom>Disk</InstallFrom> + <UpdateEnabled>false</UpdateEnabled> + <UpdateMode>Foreground</UpdateMode> + <UpdateInterval>7</UpdateInterval> + <UpdateIntervalUnits>Days</UpdateIntervalUnits> + <UpdatePeriodically>false</UpdatePeriodically> + <UpdateRequired>false</UpdateRequired> + <MapFileExtensions>true</MapFileExtensions> + <ApplicationRevision>0</ApplicationRevision> + <ApplicationVersion>1.0.0.%2a</ApplicationVersion> + <UseApplicationTrust>false</UseApplicationTrust> + <BootstrapperEnabled>true</BootstrapperEnabled> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <OutputPath>..\..\..\Build\</OutputPath> + <DefineConstants>DEBUG;TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <PlatformTarget>x86</PlatformTarget> + <UseVSHostingProcess>false</UseVSHostingProcess> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + <DebugType>none</DebugType> + <Optimize>true</Optimize> + <OutputPath>..\..\..\Build\</OutputPath> + <DefineConstants>TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <PlatformTarget>x86</PlatformTarget> + <UseVSHostingProcess>false</UseVSHostingProcess> + </PropertyGroup> + <ItemGroup> + <Reference Include="SharpCompress, Version=0.11.5.0, Culture=neutral, processorArchitecture=x86"> + <SpecificVersion>False</SpecificVersion> + <HintPath>Resources\SharpCompress.dll</HintPath> + <Private>False</Private> + </Reference> + <Reference Include="System" /> + <Reference Include="System.Core"> + <RequiredTargetFramework>3.5</RequiredTargetFramework> + </Reference> + <Reference Include="System.Drawing" /> + <Reference Include="System.Windows.Forms" /> + </ItemGroup> + <ItemGroup> + <Compile Include="Helpers\EmbeddedAssembly.cs" /> + <Compile Include="MainForm.cs"> + <SubType>Form</SubType> + </Compile> + <Compile Include="MainForm.Designer.cs"> + <DependentUpon>MainForm.cs</DependentUpon> + </Compile> + <Compile Include="Program.cs" /> + <Compile Include="Properties\AssemblyInfo.cs" /> + <EmbeddedResource Include="MainForm.resx"> + <SubType>Designer</SubType> + <DependentUpon>MainForm.cs</DependentUpon> + </EmbeddedResource> + <EmbeddedResource Include="Properties\Resources.resx"> + <Generator>ResXFileCodeGenerator</Generator> + <LastGenOutput>Resources.Designer.cs</LastGenOutput> + <SubType>Designer</SubType> + </EmbeddedResource> + <Compile Include="Properties\Resources.Designer.cs"> + <AutoGen>True</AutoGen> + <DependentUpon>Resources.resx</DependentUpon> + <DesignTime>True</DesignTime> + </Compile> + <None Include="Updater.ini"> + <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> + </None> + <Compile Include="Helpers\TaskbarProgress.cs" /> + <Compile Include="Helpers\Webdata.cs" /> + </ItemGroup> + <ItemGroup> + <BootstrapperPackage Include="Microsoft.Net.Client.3.5"> + <Visible>False</Visible> + <ProductName>.NET Framework Client Profile</ProductName> + <Install>false</Install> + </BootstrapperPackage> + <BootstrapperPackage Include="Microsoft.Net.Framework.2.0"> + <Visible>False</Visible> + <ProductName>.NET Framework 2.0 %28x86%29</ProductName> + <Install>true</Install> + </BootstrapperPackage> + <BootstrapperPackage Include="Microsoft.Net.Framework.3.0"> + <Visible>False</Visible> + <ProductName>.NET Framework 3.0 %28x86%29</ProductName> + <Install>false</Install> + </BootstrapperPackage> + <BootstrapperPackage Include="Microsoft.Net.Framework.3.5"> + <Visible>False</Visible> + <ProductName>.NET Framework 3.5</ProductName> + <Install>false</Install> + </BootstrapperPackage> + <BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1"> + <Visible>False</Visible> + <ProductName>.NET Framework 3.5 SP1</ProductName> + <Install>false</Install> + </BootstrapperPackage> + </ItemGroup> + <ItemGroup> + <Content Include="Resources\GZDBU.ico" /> + <EmbeddedResource Include="Resources\SharpCompress.dll" /> + </ItemGroup> + <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> + <!-- To modify your build process, add your task inside one of the targets below and uncomment it. + Other similar extension points exist, see Microsoft.Common.targets. + <Target Name="BeforeBuild"> + </Target> + <Target Name="AfterBuild"> + </Target> + --> +</Project> \ No newline at end of file diff --git a/Source/Tools/Updater/Updater.ini b/Source/Tools/Updater/Updater.ini new file mode 100644 index 0000000000000000000000000000000000000000..ecf1e5c03b14fd6edc5f20edd252147abbd0aef0 --- /dev/null +++ b/Source/Tools/Updater/Updater.ini @@ -0,0 +1,3 @@ +URL http://devbuilds.drdteam.org/doombuilder2-gzdb/ +FileName Builder.exe +UpdateName GZDoom_Builder-r[REVNUM].7z \ No newline at end of file