Skip to content
This repository has been archived by the owner on Jul 1, 2020. It is now read-only.

Integrating SimplePatchTool

Süleyman Yasir KULA edited this page Mar 6, 2020 · 19 revisions

Prerequisites

  • take a look at the Glossary to familiarize yourself with the terms that are used throughout the wiki
  • SimplePatchTool needs to know where to download VersionInfo.info from. See Generating versionInfoURL to generate this url
  • if this is a self patching app (i.e. this app will be able to patch itself), then you'll need to create a self patcher. You'll find instructions on where to put the self patcher later on
  • while creating patches, it is possible to sign the important patch files with an RSA key pair. This optional feature offers increased protection against man-in-the-middle attacks. If you are interested, then you need to generate an RSA key pair and embed the public key's contents into your application (e.g. in a string constant)

Integration

To add patching support to your app, you simply create a SimplePatchTool object (SimplePatchToolCore namespace), configure it and then run it. SimplePatchTool will run in a separate thread in the background.

Creating SimplePatchTool

public SimplePatchTool( string rootPath, string versionInfoURL ): creates a new SimplePatchTool instance

  • rootPath: the path of the application directory (you may use Path.GetDirectoryName(PatchUtils.GetCurrentExecutablePath()), if your executable resides at the root of the application directory)
  • versionInfoURL: download URL of the VersionInfo.info (if you don't have a versionInfoURL yet, see Generating versionInfoURL)

Configuring SimplePatchTool

SimplePatchTool UseRepairPatch( bool canRepairPatch ): sets whether or not repair patch can be used to patch the application (default: true)

SimplePatchTool UseIncrementalPatch( bool canIncrementalPatch ): sets whether or not incremental patches can be used to patch the application (default: true)

SimplePatchTool UseInstallerPatch( bool canInstallerPatch ): sets whether or not installer patch can be used to patch the application (default: true)

SimplePatchTool CheckForMultipleRunningInstances( bool checkForMultipleRunningInstances ): if checkForMultipleRunningInstances is set to true, patcher aborts if there are multiple running instance of the application (default: true)

SimplePatchTool VerifyFilesOnServer( bool verifyFiles ): if verifyFiles is set to true, files on the server will be verified (default: false)

SimplePatchTool UseCustomDownloadHandler( DownloadHandlerFactory factoryFunction ): instructs SimplePatchTool to use a custom download handler. By default, a WebClient based download handler is used but on some platforms, WebClient may not support https urls (e.g. Unity). In such cases, you may want to use a custom download handler implementation that supports https. DownloadHandlerFactory has the following signature: delegate IDownloadHandler DownloadHandlerFactory(). If you are using Unity, you should call this function as follows:

// Default IDownloadHandler implementation doesn't support https in Unity
patcher.UseCustomDownloadHandler( () => new CookieAwareWebClient() );

SimplePatchTool UseCustomFreeSpaceCalculator( FreeDiskSpaceCalculator freeSpaceCalculatorFunction ): by default, SimplePatchTool uses the DriveInfo.AvailableFreeSpace property to determine the free space of a drive but on some platforms, it may not be supported (e.g. Unity). In such cases, you may want to use a custom function to calculate the free space of a drive correctly (or, you can use a function that returns long.MaxValue to skip free space check entirely). FreeDiskSpaceCalculator has the following signature: delegate long FreeDiskSpaceCalculator( string drive ). If you are using Unity, you should call this function as follows:

// Default implementation (DriveInfo.AvailableFreeSpace) throws NotImplementedException in Unity 5.6.2,
// so skip this stage until a Unity-compatible solution is found
patcher.UseCustomFreeSpaceCalculator( ( drive ) => long.MaxValue );

SimplePatchTool UseVersionInfoVerifier( XMLVerifier verifierFunction ): instructs SimplePatchTool to verify VersionInfo.xml with the provided function. XMLVerifier has the following signature: delegate bool XMLVerifier( ref string xmlContents ). This function must return true only if the contents of VersionInfo (xmlContents) is genuine. If VersionInfo is encrypted, then xmlContents should be decrypted. If you aren't planning to sign/encrypt your VersionInfo, then don't call this function. If you are planning to sign your VersionInfo with an RSA key, simply call this function as follows:

patcher.UseVersionInfoVerifier( ( ref string xml ) => XMLSigner.VerifyXMLContents( xml, publicRSAKey ) );

SimplePatchTool UsePatchInfoVerifier( XMLVerifier verifierFunction ): instructs SimplePatchTool to verify PatchInfo.xml with the provided function. Similar to UseVersionInfoVerifier, if you are planning to sign your PatchInfo with an RSA key, simply call this function as follows:

patcher.UsePatchInfoVerifier( ( ref string xml ) => XMLSigner.VerifyXMLContents( xml, publicRSAKey ) );

SimplePatchTool LogProgress( bool value ): sets whether or not SimplePatchTool should log any IOperationProgress data. This interface has two properties: int Percentage { get; } (between 0 and 100) and string ProgressInfo { get; } (localized description for the progress). Currently, two operations provide progress info: DownloadProgress and FilePatchProgress (default: true)

SimplePatchTool LogToFile( bool value ): sets whether or not SimplePatchTool should write logs to a file. This log file will be located inside the application directory with name spt_logs.txt (default: true)

SimplePatchTool SilentMode( bool silent ): sets whether or not SimplePatchTool should run silently (i.e. no logs) (default: false)

Executing SimplePatchTool

bool CheckForUpdates( bool checkVersionOnly = true ): asynchronously checks whether or not app is up-to-date in a separate thread. Returns false, if SimplePatchTool is already checking for updates or applying a patch. If checkVersionOnly is set to true, only the version number of the app (e.g. 1.0.0) is compared against the VersionInfo's version number. Otherwise, hashes and sizes of the files in the application directory are compared against VersionInfo (i.e. verifying integrity of files)

bool Run( bool selfPatching ): starts patching the app asynchronously in a separate thread. It is not mandatory to check for updates beforehand because this function internally checks for updates, as well. Self patching requires a self patcher. This function returns false, if SimplePatchTool is already running

NOTE: any application can patch itself with self patching, which eliminates the need for a launcher; but it is not recommended for large applications because if user terminates the self patcher before it updates all the files in the application directory (which may take some time for large applications), then the application may become corrupt.

bool ApplySelfPatch( string selfPatcherExecutable, string postSelfPatchExecutable = null ): terminates the app and runs the self patcher. You must pass the path of the self patcher's executable to the selfPatcherExecutable parameter. The default path can be retrieved via PatchUtils.GetDefaultSelfPatcherExecutablePath. If you'd like to launch an executable after self patcher executes successfully (e.g. to restart the app after self patching is completed), pass the path of that executable to the postSelfPatchExecutable parameter. This function should only be called if Operation is SelfPatching and Result is Success (see below). Returns false, if something goes wrong (you can check the log file to see the issue)

Fetching SimplePatchTool's progress

There are two ways to fetch SimplePatchTool's progress:

1. Calling the following methods and properties manually from time to time

string FetchLog(): fetches the next log that SimplePatchTool has generated. Returns null, if there is no log in the queue

IOperationProgress FetchProgress(): returns an IOperationProgress instance if patcher's progress has changed, null otherwise

IOperationProgress FetchOverallProgress(): returns an IOperationProgress instance if patcher's overall progress has changed, null otherwise. Under normal circumstances, patch will finish when overall progress' Percentage reaches 100

bool IsRunning { get; }: returns true if SimplePatchTool is currently checking for updates or applying a patch

void Cancel(): force stops SimplePatchTool

PatchOperation Operation { get; }: returns CheckingForUpdates, if last operation was (or currently running operation is) CheckForUpdates; Patching, if it was/is Run(false); SelfPatching, if it was/is Run(true) and ApplyingSelfPatch, if it was/is ApplySelfPatch

PatchMethod PatchMethod { get; }: returns the patch method that SimplePatchTool is currently using (None, RepairPatch, IncrementalPatch or InstallerPatch)

PatchStage PatchStage { get; }: returns the current stage of the patcher (e.g. CheckingUpdates, DownloadingFiles, DeletingObsoleteFiles and so on)

string NewVersion { get; }: returns the version number of the new version (e.g. 1.2.0) or null, if it isn't determined yet (i.e. VersionInfo isn't downloaded yet)

2. Using a SimplePatchTool.IListener object

You can register a listener to SimplePatchTool using the SetListener function. This listener will receive the following callbacks:

void Started(); // SimplePatchTool has started running an operation
void LogReceived( string log );
void ProgressChanged( IOperationProgress progress );
void OverallProgressChanged( IOperationProgress progress );
void PatchStageChanged( PatchStage stage );
void PatchMethodChanged( PatchMethod method );
void VersionInfoFetched( VersionInfo versionInfo ); // Can be used to modify the VersionInfo at runtime (e.g. add ignored paths to it)
void VersionFetched( string currentVersion, string newVersion ); // New version's version number is fetched
void Finished(); // Currently running operation has finished

Though you can implement the SimplePatchTool.IListener interface yourself, be aware that any expensive operations performed in these callbacks will block the patcher's thread. Instead, you are recommended to use the PatcherAsyncListener class which has a dedicated event for each callback and runs these events in a separate thread (if you are using Unity, use the PatcherListener class to receive callbacks on the main thread).

Fetching the result of the operation

PatchResult Result { get; }: returns different values for different Operation's. Its value should be checked after IsRunning returns false:

  • CheckingForUpdates: returns PatchResult.AlreadyUpToDate if app is up-to-date, PatchResult.Success if there is an update for the app, and PatchResult.Failed if there was an error while checking for updates
  • Patching/SelfPatching: returns PatchResult.AlreadyUpToDate if app is already up-to-date, PatchResult.Success if app is updated successfully, and PatchResult.Failed if there was an error while updating the app. In self patching mode, a value of PatchResult.Success means that we are ready to launch the self patcher via ApplySelfPatch
  • ApplyingSelfPatch: returns PatchResult.Success if self patcher has successfully started (in which case, currently running application will terminate promptly), and PatchResult.Failed if there was an error while trying to start the self patcher's executable

PatchFailReason FailReason { get; }: if Result is PatchResult.Failed, this property stores why the patcher has failed (e.g. Cancelled, InsufficientSpace, XmlDeserializeError and so on). You may want to execute special logic for the following cases:

  • RequiresAdminPriviledges: we need admin permissions to update the files in the application directory
  • FilesAreNotUpToDateAfterPatch: after applying the patch, app is somehow still not up-to-date
  • UnderMaintenance_AbortApp: servers are currently under maintenance and users should not be allowed to launch the app (maintenance check requires manual setup after generating a patch)
  • UnderMaintenance_CanLaunchApp: servers are currently under maintenance but users can continue using the app (maintenance check requires manual setup after generating a patch)
  • MultipleRunningInstances: multiple instance of the application are running
  • SelfPatcherNotFound: self patcher's executable couldn't be started because it doesn't exist

string FailDetails { get; }: if Result is PatchResult.Failed, returns a localized string that briefly explains why the patcher has failed

Example code: SimplePatchToolConsoleApp.Program.CheckForUpdates and SimplePatchToolConsoleApp.Program.ApplyPatch. Also see the WinForms-based launcher example. For Unity, you can see the example scenes.

Clone this wiki locally