2016-10-06 14:30:24 +00:00
#region = = = = = = = = = = = = = = = = = = = = = = = = Namespaces
using System ;
using System.Collections.Generic ;
using System.ComponentModel ;
using System.Diagnostics ;
2016-11-04 12:41:50 +00:00
using System.Drawing ;
2016-10-08 21:09:55 +00:00
using System.IO ;
using System.Reflection ;
2016-10-06 14:30:24 +00:00
using System.Security.AccessControl ;
using System.Security.Principal ;
2016-10-08 21:09:55 +00:00
using System.Threading ;
using System.Windows.Forms ;
using SharpCompress.Archives ;
using SharpCompress.Readers ;
2022-03-13 15:51:56 +00:00
using Microsoft.Win32 ;
2016-10-06 14:30:24 +00:00
#endregion
namespace mxd.GZDBUpdater
{
public partial class MainForm : Form
{
#region = = = = = = = = = = = = = = = = = = = = = = = = Variables
private string processToEnd = string . Empty ;
private string downloadFile = string . Empty ;
2018-04-14 15:11:19 +00:00
private string platform = string . Empty ;
2016-10-06 14:30:24 +00:00
private const string revisionwildcard = "[REVNUM]" ;
2018-04-14 15:11:19 +00:00
private const string platformwildcard = "[PLATFORM]" ;
2016-10-06 14:30:24 +00:00
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 ;
2019-12-24 07:12:45 +00:00
private const string MESSAGEBOX_TITLE = "Ultimate Doom Builder Updater" ;
2022-03-13 15:51:56 +00:00
private bool useInstaller = false ;
2016-10-06 14:30:24 +00:00
#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 ; } }
2016-11-04 12:41:50 +00:00
public static Icon AppIcon { get { return me . Icon ; } }
2016-10-06 14:30:24 +00:00
2016-11-04 12:41:50 +00:00
#endregion
2016-10-06 14:30:24 +00:00
#region = = = = = = = = = = = = = = = = = = = = = = = = Constructor
public MainForm ( )
{
2022-03-13 15:51:56 +00:00
// Check if we should use the installer or just unpack the update
string installLocation = ( string ) Registry . GetValue ( @"HKEY_CURRENT_USER\SOFTWARE\Ultimate Doom Builder" , "Location" , null ) ;
if ( installLocation ! = null )
{
string ourpath = Path . GetDirectoryName ( Process . GetCurrentProcess ( ) . MainModule . FileName ) ;
if ( ourpath = = installLocation )
useInstaller = true ;
}
if ( ! CheckPremissions ( Application . StartupPath ) )
2016-10-06 14:30:24 +00:00
{
2016-10-08 21:09:55 +00:00
ErrorDescription = "Update failed: your user account does not have write access to the destination folder \"" + Application . StartupPath + "\"\n\nMove the editor to a folder with write access,\nor run the updater as Administrator." ;
2016-10-06 14:30:24 +00:00
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...
2016-10-08 21:09:55 +00:00
if ( ! EditorClosed ( ) )
{
// Error or user canceled
e . Cancel = true ;
return ;
}
2022-03-13 15:51:56 +00:00
// Rename the updater so that a new version can be written
UpdateLabel ( label1 , "4/6: Renaming updater..." ) ;
Thread . Sleep ( 500 ) ;
if ( ! RenameUpdater ( ) )
2016-10-08 21:09:55 +00:00
{
e . Cancel = true ;
return ;
}
2022-03-13 15:51:56 +00:00
// Install the update
if ( useInstaller )
{
UpdateLabel ( label1 , "5/6: Running installer..." ) ;
Thread . Sleep ( 500 ) ;
if ( ! InstallUpdateWithInstaller ( ) )
{
e . Cancel = true ;
return ;
}
}
else
{
UpdateLabel ( label1 , "5/6: Decompressing package..." ) ;
Thread . Sleep ( 500 ) ;
if ( ! Unpack ( updateFolder + downloadFile , Application . StartupPath ) )
{
e . Cancel = true ;
return ;
}
}
// This currently doesn't do anything, since the directory it tries to move the files
// from is empty (bar the update file)
/ *
2016-10-08 21:09:55 +00:00
UpdateLabel ( label1 , "5/6: Moving files..." ) ;
Thread . Sleep ( 500 ) ;
MoveFiles ( ) ;
2022-03-13 15:51:56 +00:00
* /
2016-10-08 21:09:55 +00:00
UpdateLabel ( label1 , "6/6: Wrapping up..." ) ;
Thread . Sleep ( 500 ) ;
PostDownload ( ) ;
}
2022-03-13 15:51:56 +00:00
private bool InstallUpdateWithInstaller ( )
{
Process process ;
try
{
// Run the installer in silent mode. This will skip any user interaction (if possible)
process = Process . Start ( updateFolder + downloadFile , "/silent" ) ;
}
catch ( Exception ex )
{
ErrorDescription = $"Failed to run installer: {ex.Message}" ;
return false ;
}
process . WaitForExit ( ) ;
// Check if the installer ran successfully. See https://jrsoftware.org/ishelp/index.php?topic=setupexitcodes
if ( process . ExitCode = = 2 )
{
ErrorDescription = "Installer aborted by the user." ;
return false ;
}
else if ( process . ExitCode > 0 )
{
ErrorDescription = $"Installer failed with exit code {process.ExitCode}." ;
return false ;
}
return true ;
}
private bool RenameUpdater ( )
{
string ourname = Path . GetFileName ( Process . GetCurrentProcess ( ) . MainModule . FileName ) ;
string movename = ourname + ".old" ;
try
{
if ( File . Exists ( movename ) )
File . Delete ( movename ) ;
File . Move ( ourname , movename ) ;
}
catch ( Exception e )
{
ErrorDescription = $"Failed to rename updater from {ourname} to {movename}: {e.Message}" ;
return false ;
}
return true ;
}
2016-10-08 21:09:55 +00:00
private bool EditorClosed ( )
{
2016-10-06 14:30:24 +00:00
try
{
2016-11-04 12:41:50 +00:00
// Gather processes...
List < Process > toclose = GetProcesses ( processToEnd ) ;
2016-10-06 14:30:24 +00:00
2016-10-08 21:09:55 +00:00
// Ask the user how to proceed...
2016-10-06 14:30:24 +00:00
if ( toclose . Count > 0 )
{
TaskbarProgress . SetState ( this . Handle , TaskbarProgress . TaskbarStates . Paused ) ;
2016-11-04 12:41:50 +00:00
UpdateBlockedForm form = new UpdateBlockedForm ( ) ;
switch ( form . ShowDialog ( this ) )
2016-10-06 14:30:24 +00:00
{
2016-11-04 12:41:50 +00:00
case DialogResult . Cancel : return false ;
case DialogResult . OK :
2016-10-08 21:09:55 +00:00
UpdateLabel ( label1 , "3/6: Stopping " + processToEnd ) ;
2016-11-04 12:41:50 +00:00
Thread . Sleep ( 50 ) ;
toclose = GetProcesses ( processToEnd ) ; // Re-gather processes
2016-10-08 21:09:55 +00:00
foreach ( Process p in toclose ) if ( p ! = null ) p . Kill ( ) ;
2016-11-04 12:41:50 +00:00
return true ;
2016-10-06 14:30:24 +00:00
}
}
}
catch ( Exception ex )
{
2016-11-04 12:41:50 +00:00
ErrorDescription = "Failed to stop the editor process...\n" + ex . Message ;
2016-10-08 21:09:55 +00:00
return false ;
2016-10-06 14:30:24 +00:00
}
2016-10-08 21:09:55 +00:00
return true ;
2016-10-06 14:30:24 +00:00
}
2016-11-04 12:41:50 +00:00
private static List < Process > GetProcesses ( string processToEnd )
{
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 ) ;
}
return toclose ;
}
2016-10-06 14:30:24 +00:00
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 ) ;
2016-10-08 21:09:55 +00:00
if ( MessageBox . Show ( this , ErrorDescription , MESSAGEBOX_TITLE , MessageBoxButtons . YesNo ) = = DialogResult . Yes )
2016-10-06 14:30:24 +00:00
Process . Start ( URL ) ;
}
else
{
2016-10-08 21:09:55 +00:00
MessageBox . Show ( this , ErrorDescription , MESSAGEBOX_TITLE , MessageBoxButtons . OK ) ;
2016-10-06 14:30:24 +00:00
}
}
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 ;
2016-10-08 21:09:55 +00:00
using ( MemoryStream stream = Webdata . DownloadWebFile ( Path . Combine ( URL , "Versions.txt" ) ) )
2016-10-06 14:30:24 +00:00
{
if ( stream = = null )
{
2016-10-08 21:09:55 +00:00
if ( string . IsNullOrEmpty ( ErrorDescription ) ) ErrorDescription = "Failed to retrieve remote revision info." ;
2016-10-06 14:30:24 +00:00
return false ;
}
string s ;
using ( StreamReader reader = new StreamReader ( stream ) )
{
2016-10-08 21:09:55 +00:00
s = reader . ReadLine ( ) ; // First line should be editor revision
2016-10-06 14:30:24 +00:00
}
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 ( ) ) ;
2018-04-14 15:11:19 +00:00
downloadFile = downloadFile . Replace ( platformwildcard , platform . ToString ( ) ) ;
2016-10-06 14:30:24 +00:00
2018-04-14 15:11:19 +00:00
if ( remoterev > 0 & & remoterev < = localrev )
2016-10-06 14:30:24 +00:00
{
URL = string . Empty ;
ErrorDescription = "Your version is up to date!" ;
}
return remoterev > localrev ;
}
private bool LoadConfig ( string filename )
{
string [ ] lines = File . ReadAllLines ( filename ) ;
2022-03-13 15:51:56 +00:00
2016-10-06 14:30:24 +00:00
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 ) ;
}
2022-03-13 15:51:56 +00:00
else if ( ! useInstaller & & line . StartsWith ( "UpdateName" ) )
2016-10-06 14:30:24 +00:00
{
downloadFile = line . Substring ( 10 ) . Trim ( ) ;
}
2022-03-13 15:51:56 +00:00
else if ( useInstaller & & line . StartsWith ( "InstallerName" ) )
{
downloadFile = line . Substring ( 13 ) . Trim ( ) ;
}
2018-04-14 15:11:19 +00:00
else if ( line . StartsWith ( "Platform" ) )
{
platform = line . Substring ( 9 ) . Trim ( ) ;
}
2016-10-06 14:30:24 +00:00
}
// 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 ;
2016-10-08 21:09:55 +00:00
int totalentries = arc . NumEntries ;
2016-10-06 14:30:24 +00:00
string ourname = Path . GetFileName ( Process . GetCurrentProcess ( ) . MainModule . FileName ) ;
// Unpack all
2016-10-08 21:09:55 +00:00
ExtractionOptions options = new ExtractionOptions { ExtractFullPath = true , Overwrite = true } ;
2016-10-06 14:30:24 +00:00
while ( reader . MoveToNextEntry ( ) )
{
if ( appclosing ) break ;
2022-03-13 15:51:56 +00:00
// if(reader.Entry.IsDirectory || Path.GetFileName(reader.Entry.Key) == ourname) continue; // Don't try to overrite ourselves...
2016-10-08 21:09:55 +00:00
reader . WriteEntryToDirectory ( unZipTo , options ) ;
2016-10-06 14:30:24 +00:00
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 ( ) ;
2016-10-08 21:09:55 +00:00
foreach ( FileInfo fi in files )
2016-10-06 14:30:24 +00:00
{
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 ;
2016-10-08 21:09:55 +00:00
this . Text + = " v" + version . Major + "." + version . Revision . ToString ( "#00" ) ;
2016-10-06 14:30:24 +00:00
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
}
}