Skip to content

Commit

Permalink
Updated to version 1.0.0.3
Browse files Browse the repository at this point in the history
Properly error handle events when the user does not have access to
WAR.exe or the dependency files
- Now prompts to restart the launcher as Administrator to gain file
access
- Now prompts to automatically remove the Read-Only attribute on all
files in the game directory to gain write access

Added two new launch arguments --CustomDeps and --CheckDepHash details
are in the README.md

New method of dependency version checking
- Now checks by file version number instead of hash

Minor code optimizations

Launcher version updated to 1.0.0.3
  • Loading branch information
ThiconZ committed Apr 9, 2017
1 parent 4e9a3ca commit f4710f5
Show file tree
Hide file tree
Showing 5 changed files with 189 additions and 44 deletions.
13 changes: 9 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,22 @@ This project is an attempt to provide a better, open source, alternative to the

![Screenshot](http://i.imgur.com/YTGiy1M.png)

It features the following over the current official one as of Feb 27 2017:
It features the following over the current official launcher as of Apr 9 2017:

* Added a minimize button to the main window
* On successful game start, the launcher will automatically minimize
* On game exit, the launcher will automatically restore to the screen
* Users can start the game multiple times within the same launcher session.
* Users can start the game multiple times within the same launcher session
* Ex. if a user launches the game, exits the game, they can click Connect to launch the game again without issues
* Greatly improved the error handling
* Added better dependency file handling and checks
* By default it will allow the user to have dependency files with a higher file version number than the embedded ones without attempting to replace them with the embedded files.
* Added launch arguments
* --Debug starts the launcher and begins exporting debug information to 3 files in the current directory, an application configuration file named RoR_Configs.txt, a DxDiag report named RoR_DxDiag.txt, and an MSInfo32 report named RoR_MSInfo.txt.
* --NoErrors will suppress most error popup windows and some error messages, but continue to give short error text messages on the main window for critical messages
* Bug fixes for numerous issues including the Connect button not always working
* --NoErrors will suppress most error popup windows and some error messages, but continue to give short error text messages on the main window for critical messages.
* --CustomDeps disables dependency file checking. This will assume the user has all the required dependency files and that they will work with the launcher.
* --CheckDepHash enables the old method of dependency file hash comparisons instead of file version comparisons to determine if an embedded dependency file needs to replace an external dependency file. This can be helpful if a user wants to make sure their external dependency files are identical to the ones that come embedded.
* Bug fixes and optimizations for numerous issues including the Connect button not always working
* Cleaned up the window interfaces from the developer perspective in visual studio (fixes some issues at the same time)
* Uses a new Mythic Patch Handler (MYPHandler) that no longer requires Performance Counters
* This is also open source and can be found [here](https://github.com/ThiconZ/Mythic-Patch-Handler).
25 changes: 25 additions & 0 deletions RoRLauncher/Client.cs
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,31 @@ public static void Handle(PacketIn packet)
// Minimize the launcher once the game process is started
MainWindow.mainWindow.ModifyWindowState(System.Windows.WindowState.Minimized);
}
catch (System.UnauthorizedAccessException ex)
{
if (MainWindow.mainWindow.NoErrorMode == false)
Client.Popup("Access to WAR.exe is denied. Please obtain access permissions to the Warhammer directory and files and try again: " + Environment.NewLine + ex.ToString());
else
Client.Print("Error starting game. User lacks file access permissions");

// Prompt the user to restart the launcher as admin - this normally fixes this error
if (MainWindow.mainWindow.IsAdministrator() == false)
{
if (System.Windows.MessageBox.Show("Running the launcher as Administrator may resolve this error. Would you like to restart it as Administrator now?", "Restart as Administrator", System.Windows.MessageBoxButton.YesNo) == System.Windows.MessageBoxResult.Yes)
{
MainWindow.mainWindow.RestartAsAdmin();
}
}
else
{
if (System.Windows.MessageBox.Show("Removing the Read-Only attribute from files in the game directory may resolve this error. Would you like to remove this attribute and restart the application now? Note this will also restart the application as Administrator.", "Remove Read-Only Attribute", System.Windows.MessageBoxButton.YesNo) == System.Windows.MessageBoxResult.Yes)
{
// User must have read/write access to the directory for this to be allowed
MainWindow.mainWindow.RemoveReadOnly(new DirectoryInfo(System.IO.Directory.GetCurrentDirectory()));
MainWindow.mainWindow.RestartAsAdmin();
}
}
}
catch (System.Exception ex)
{
if (MainWindow.mainWindow.NoErrorMode == false)
Expand Down
191 changes: 153 additions & 38 deletions RoRLauncher/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Net;
using System.Reflection;
using System.Security.Cryptography;
using System.Security.Principal;
using System.Text;
using System.Windows;
using System.Windows.Controls;
Expand All @@ -33,6 +34,10 @@ public partial class MainWindow : Window

public bool NoErrorMode = false;

public bool CustomDependencyMode = false;

public bool CheckDependencyHashes = false;

internal string Error
{
get
Expand Down Expand Up @@ -104,18 +109,7 @@ private void worker_DownloadXMLCompleted(object sender, RunWorkerCompletedEventA
}
else
{
XElement arg_170_0;
if (this.doc.Descendants("News").Count<XElement>() <= 0)
{
arg_170_0 = null;
}
else
{
arg_170_0 = (from n in this.doc.Descendants("News")
orderby (uint)n.Element("Id") descending
select n).First<XElement>();
}
XElement xElement2 = arg_170_0;
XElement xElement2 = this.doc.Descendants((XName)"News").Count<XElement>() > 0 ? this.doc.Descendants((XName)"News").OrderByDescending<XElement, uint>((Func<XElement, uint>)(n => (uint)n.Element((XName)"Id"))).First<XElement>() : (XElement)null;
if (xElement2 != null)
{
this.PatchTitle.Text = (string)xElement2.Element("Date");
Expand Down Expand Up @@ -210,19 +204,60 @@ byte[] ComputeDependencyHash(string fileName)
}
}

private bool CheckForDependency(string fileName)
/// <summary>
/// Compares the Internal and External dependecy file versions.
/// </summary>
/// <param name="fileName">Complete file name and extension of the dependency. Must be the same internally and externally.</param>
/// <returns>Returns False if Internal version is grater than or equal to External version.</returns>
private bool CompareDependencyFileVersion(string fileName)
{
// Load the dependency files in a way that won't lock them from future use

FileVersionInfo externalFile = FileVersionInfo.GetVersionInfo(Directory.GetCurrentDirectory() + "/" + fileName);
Version externalVersion = new Version(externalFile.FileVersion);

Stream stream = System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream("RoRLauncher.deps." + fileName);
byte[] bytes = new byte[stream.Length];
stream.Read(bytes, 0, bytes.Length);
stream.Close();
stream.Dispose();
Version internalVersion = new Version(Assembly.ReflectionOnlyLoad(bytes).GetName().Version.ToString());

// Returns false if internal file version is greater than or equal to the external file
if (internalVersion >= externalVersion)
{
return false;
}

return true;
}

/// <summary>
/// Check if dependency file already exists. If it does, determine if it should be replaced by the internal dependency file.
/// </summary>
/// <param name="fileName">Complete file name and extension of the dependency. Must be the same internally and externally.</param>
/// <param name="hashCheck">The check be done based on file hashes and not file versions.</param>
/// <returns>Returns False if the file should be replaced.</returns>
private bool CheckForDependency(string fileName, bool hashCheck = false)
{
// Check if the files we're going to unpack already exist in the current location
if (File.Exists(Directory.GetCurrentDirectory() + "/" + fileName) == true)
{
// If the files are here, do an md5 check to see if they are the right versions before unpacking new ones
// Could change this to be a file version check instead if we want to allow the user to have a modified version of the file as long as it meets the version number requirements
byte[] externalHash;
byte[] internalHash;
externalHash = ComputeFileHash(Directory.GetCurrentDirectory() + "/" + fileName);
internalHash = ComputeDependencyHash(fileName);
if (hashCheck == false)
{
// If CompareDependencyFileVersion is false then we should replace the external file with the internal one
return CompareDependencyFileVersion(fileName);
}
else
{
// If the files are here, do an md5 check to see if they are the right versions before unpacking new ones
byte[] externalHash;
byte[] internalHash;
externalHash = ComputeFileHash(Directory.GetCurrentDirectory() + "/" + fileName);
internalHash = ComputeDependencyHash(fileName);

return externalHash.SequenceEqual(internalHash);
return externalHash.SequenceEqual(internalHash);
}
}
else
{
Expand All @@ -234,15 +269,15 @@ private void worker_Unpack(object sender, DoWorkEventArgs e)
{
// Do safe unpacking of dependencies
// If one happens to be locked open in another program and is the right version it will still work this way, previously it would throw an error
if (CheckForDependency("HashDictionary.dll") == false)
if (CheckForDependency("HashDictionary.dll", CheckDependencyHashes) == false)
{
this.Unpack("HashDictionary.dll");
}
if (CheckForDependency("MYPHandler.dll") == false)
if (CheckForDependency("MYPHandler.dll", CheckDependencyHashes) == false)
{
this.Unpack("MYPHandler.dll");
}
if (CheckForDependency("ICSharpCode.SharpZipLib.dll") == false)
if (CheckForDependency("ICSharpCode.SharpZipLib.dll", CheckDependencyHashes) == false)
{
this.Unpack("ICSharpCode.SharpZipLib.dll");
}
Expand Down Expand Up @@ -300,12 +335,6 @@ public MainWindow()
// Pull status/connection information from the server
this.ConnectToServers();

// Unpack required library dependencies to the current directory
// NOTE: these are never cleaned up by the program - maybe it should try to delete them when closed?
BackgroundWorker backgroundWorker2 = new BackgroundWorker();
backgroundWorker2.DoWork += new DoWorkEventHandler(this.worker_Unpack);
backgroundWorker2.RunWorkerAsync();

// Make sure the mainWindow is created and visible before moving onto command line options
// Without this errors will occur from the Debug mode option
mainWindow.Show();
Expand All @@ -325,6 +354,25 @@ public MainWindow()
{
NoErrorMode = true;
}
// The user is claiming to be running custom dependency libs so we should not check if internal ones should be unpacked
else if (option.ToLower().Contains("--customdeps") == true)
{
CustomDependencyMode = true;
}
// User wants to have the dependency files checked by hashes and not versions
else if (option.ToLower().Contains("--checkdephash") == true)
{
CheckDependencyHashes = true;
}
}

// Unpack required library dependencies to the current directory
// NOTE: these are never cleaned up by the program - maybe it should try to delete them when closed?
if(CustomDependencyMode == false)
{
BackgroundWorker backgroundWorker2 = new BackgroundWorker();
backgroundWorker2.DoWork += new DoWorkEventHandler(this.worker_Unpack);
backgroundWorker2.RunWorkerAsync();
}
}

Expand Down Expand Up @@ -480,11 +528,9 @@ private void PATCH_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)

private void client_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
{
double num = double.Parse(e.BytesReceived.ToString());
double num2 = double.Parse(e.TotalBytesToReceive.ToString());
double num3 = num / num2 * 100.0;
this.ProgressBarFiller.Width = (double)((int)System.Math.Floor(num3 * 469.0 / 100.0));
this.ProgressText.Text = System.Math.Truncate(num3).ToString() + "%";
double progress = (double.Parse(e.BytesReceived.ToString()) / double.Parse(e.TotalBytesToReceive.ToString())) * 100.0;
this.ProgressBarFiller.Width = (double)((int)System.Math.Floor(progress * 469.0 / 100.0));
this.ProgressText.Text = System.Math.Truncate(progress).ToString() + "%";
}

private void client_DownloadFileCompleted(object sender, AsyncCompletedEventArgs e)
Expand Down Expand Up @@ -588,8 +634,29 @@ private bool Unpack(string fileName)
catch (System.Exception arg)
{
this.Popup("Error unpacking:" + Environment.NewLine + arg);
bool result = false;
return result;

if (arg is UnauthorizedAccessException)
{
// Prompt the user to restart the launcher as admin - this normally fixes this error
if (IsAdministrator() == false)
{
if (System.Windows.MessageBox.Show("Running the launcher as Administrator may resolve this error. Would you like to restart it as Administrator now?", "Restart as Administrator", System.Windows.MessageBoxButton.YesNo) == System.Windows.MessageBoxResult.Yes)
{
MainWindow.mainWindow.RestartAsAdmin();
}
}
else
{
if (System.Windows.MessageBox.Show("Removing the Read-Only attribute from files in the game directory may resolve this error. Would you like to remove this attribute and restart the application now? Note this will also restart the application as Administrator.", "Remove Read-Only Attribute", System.Windows.MessageBoxButton.YesNo) == System.Windows.MessageBoxResult.Yes)
{
// User must have read/write access to the directory for this to be allowed
RemoveReadOnly(new DirectoryInfo(System.IO.Directory.GetCurrentDirectory()));
MainWindow.mainWindow.RestartAsAdmin();
}
}
}

return false;
}
try
{
Expand All @@ -600,8 +667,7 @@ private bool Unpack(string fileName)
catch (System.Exception arg2)
{
this.Popup("Error unpacking 2:" + Environment.NewLine + arg2);
bool result = false;
return result;
return false;
}
return true;
}
Expand Down Expand Up @@ -678,5 +744,54 @@ private void CreateFullDebugDump()
Error = "Error creating dump files. --NoErrors was set on the command-line, remove it and run the launcher again for the full error message.";
}
}

public void RestartAsAdmin()
{
// This is required to prevent a critical error that prevents this from finishing
Client._Socket.Shutdown(System.Net.Sockets.SocketShutdown.Send);

Client._Socket.Close();
Client._Socket.Dispose();
Client.Close();
var exeName = System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName;
ProcessStartInfo startInfo = new ProcessStartInfo(exeName);
startInfo.Verb = "runas";
startInfo.Arguments = Environment.CommandLine;
System.Diagnostics.Process.Start(startInfo);

// If something stopped Shutdown, force an unclean exit because we don't care anymore
try
{
Application.Current.Shutdown();
}
catch
{
Process.GetCurrentProcess().Kill();
}
return;
}

public bool IsAdministrator()
{
WindowsIdentity identity = WindowsIdentity.GetCurrent();
WindowsPrincipal principal = new WindowsPrincipal(identity);
return principal.IsInRole(WindowsBuiltInRole.Administrator);
}

public void RemoveReadOnly(DirectoryInfo directory)
{
if (directory != null)
{
directory.Attributes = FileAttributes.Normal;
foreach (FileInfo file in directory.GetFiles())
{
file.Attributes = FileAttributes.Normal;
}
foreach (DirectoryInfo dir in directory.GetDirectories())
{
RemoveReadOnly(dir);
}
}
}
}
}
4 changes: 2 additions & 2 deletions RoRLauncher/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,5 +51,5 @@
// 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.2")]
[assembly: AssemblyFileVersion("1.0.0.2")]
[assembly: AssemblyVersion("1.0.0.3")]
[assembly: AssemblyFileVersion("1.0.0.3")]
Binary file modified RoRLauncher/deps/MYPHandler.dll
Binary file not shown.

0 comments on commit f4710f5

Please sign in to comment.