Making A Patch System For My Game

So I have a working patch system using the Mac’s terminal with the following commands(I’m adding this because I have seen a lot of similar questions around):

diff -Naur oldfile newfile > patch.txt // this will make a 'patch' in the execution directory

Then to apply the patch:

patch -p0 < patch.txt

I have verified this working. It works like a charm.

However now for my question. I want to do the same type of thing on Windows. I haven’t been able to figure this one out. I tried using UnixUtils and it just breaks my files(Not a Valid windows application). I also looked at WinMerge but haven’t figured out how it works. Is there anything that I can do to make a patch file that people could download and apply? Any help on this one would be great. I am looking for something pretty simple that I can manage.

I would like to figure out any information on this subject. I am already aware of the Crafty system and Hegza Patcher.

Update 12/27/2016:

This still has a few bugs but you can start looking into this system as well. This is a repo of many scripts that I have been working on an planning to release to the public. Far from complete but part of this collection is a Patching System (not done):

and a bit of documentation I put together for that patching library:

I have put this aside for now, but if there is still interest I will start working on this again.

Alright Folks I have done it! I have made a patch system that will work for me. This goes way beyond just making a patch file. You are more than welcome to use my code.

Background: I store all of my raw files on Google Drive (15GB free file hosting). This will essentially work with any server or file hosting out there.

Any spaces are considered invalid in file names for url usage. So I had to format the text that I read from files to make sure it met valid character criteria.

You can’t edit files that are currently open, that’s a security measure on all operating systems. So I had to create a separate patching system.

First off, right when the user opens my game I have it look in my Google Drive for a text file called “version.txt”. This simply has the version in it like “1.0.1”. I connect to my google drive, read the file, remove all white spaces, change it to a number, and finally check to make sure that number is greater than the current version number of the game running. It does this extremely quickly (normally less than a second but I added wait times for visuals).

To make a file on google drive downloadable for everyone you have to make it public, there is a lot of info about how to do this online – very easy. Second you have to make a link out of the file like the following:

 http://googledrive.com/host/<Google Drive Folder>/<filename>

mine looks like this:

 http://googledrive.com/host/0B9EJGlco2Hy6fmUxQzN3ZVAtYkczRzA0S29ubjhfVGVBUFpHTm9HRmxOeldraDhhOXBCa1E/level0

Simply copy and paste the link into a browser to make sure it worked (It should start to immediately download).

Next, if it recognizes that version.txt version number is greater than the game version number than I notify the user that there is an update and give them the choice of whether or not to download it. If they accept to download it then I will close their game and open the patcher system.

The patcher system is much more complicated and is where all the magic takes place. It opens and reads a file called “patch.txt”, it has the diff of the files.

How do I get a diff of my files?

This is where my code could be improved but what I did wasn’t code it at all. I downloaded a free program for windows called “WinMerge”. In Winmerge you can compair two folders and it will tell you everything that is different between the two folders. I simply copied that list of file names it produced into the patch.txt file seperate by enter/return spaces. On a Mac there is a “diff” command that you can run and make it spit out to a text file.

The patch.txt file simply will list the files that need to be downloaded for that patch. Now if a user gets very behind on patches I don’t want to have to make them download the same file over and over to work through the patches to be up to date. So I made it so it will work by downloading all the files for all patches up to the current one, all at once ;). My patch.txt file looks like the following:

 patch
 1.0.0
 level0
 level2
 level3
 patch
 1.0.1
 level1
 level2
 leve3
 leve4

So what it does is if you are on v0.0.9 you will need to download files: level0, level1, level2, level3, and level4. I makes sure that you don’t download level2 or level3 twice. Cool eh?

With the files downloaded it will find the data folder of my game, called “The Dark Tower”, and replace the files there with the new ones. It does this relatively so the patcher and “The Dark Tower” game can be anywhere on the computer and it will work. However, the patcher and the dark tower game need to be in the same folder in order for it to work. It’s not sophisticated enough to scan your computer for the .exe file.

Also this needs to work on both Mac and Windows and they have a different folder structure. Have no fear! I have taken care of that as well, The Windows is fully tested and working but the Mac version isn’t.

With that extensive explanation here is the code to check if there is an update (The Dark Tower Game.exe):

 var buildVersion:String = "1.0.0";
 private var updateDone:boolean = false;
 private var downloadingFiles:boolean = false;
 private var savePath:String = "";
 private var originalMsg : String = "";
 private var download:WWW;
 private var patch:String = "";
 private var rawDataFolder = "";
 private var rawExc = "";
 
 Start(){    
         checkForUpdates();
 }
 function Update(){
     if(downloadingFiles){
         updateMsg = originalMsg+"

Download Progress: “+(download.progress * 100).ToString(”#00.00")+"%
";
}
}
//-------------------------FOR PATCHING -----------------------------------------------------
function checkForUpdates(){ //for the patcher. Check the version first.
buildVersion = “1.0.0”;
updating = true;
showUpdateButton = false;
updateMsg = "

Checking For Updates…"; //GUI update for user
yield WaitForSeconds(1.0f); //make it visible
var url = “https://googledrive.com/host/0B9EJGlco2Hy6fkU4QVVzMlQ0QnBqTmVjazZTR2dTSkJJal83eEhlYVJsU1puSjA2b3h3VU0/version.txt”; //txt file with version # in it
updateMsg = "

Establishing Connection…";//GUI update for user
var patchwww:WWW = WWW(url); //create new web connection to the build number site
yield patchwww; // wait for download to finish
var updateVersion = (patchwww.text).Trim();
if (updateVersion == buildVersion){ //check version
updateMsg = "

Currently update to date.";
yield WaitForSeconds(1.0f);
updating = false;
showGUI = true;
}
else {
patch = patchwww.text;
updateMsg = "Update Available.

Current Version: “+buildVersion+”
Patch Version: “+patchwww.text+”

Would you like to download updates?

This will close this program
and
will launch the patching program.";
showUpdateButton = true;
}
}
//----------------------------------------------------------------
function applyUpdate(){
updateMsg = "

Identifying Platform";
if(Application.platform == RuntimePlatform.OSXPlayer || Application.platform == RuntimePlatform.OSXEditor){
Application.OpenURL(Application.dataPath+“/…/…/TheDarkTowerPatcher.app”);
}
else if(Application.platform == RuntimePlatform.WindowsPlayer || Application.platform == RuntimePlatform.WindowsEditor){
Application.OpenURL(Application.dataPath+“/…/TheDarkTowerPatcher.exe”);
}
Application.Quit();
}
GUI.skin = myskin;
GUI.skin.font = font;
GUI.Label(Rect(Screen.width-100, Screen.height-80, 100, 80),“<size=20>”+guiTextVersion+“”);
if(updating){
if(backgroundImage)
GUI.DrawTexture(Rect(0,0,Screen.width,Screen.height), backgroundImage);
GUI.Box(Rect(Screen.width0.5f-250,Screen.height0.5f-250,500,300), “<size=25>”+updateMsg+“”);

         if(showUpdateButton == true){
             if(GUI.Button(Rect(Screen.width*0.5f-100,Screen.height*0.5f+82,200,100),"Update")){
                 GetComponent.<AudioSource>().clip = MouseClickSound;
                 GetComponent.<AudioSource>().Play();
                 showUpdateButton = false;
                 applyUpdate();
             }
             if(GUI.Button(Rect(Screen.width*0.5f-100,Screen.height*0.5f+192,200,100),"Cancle")){
                 GetComponent.<AudioSource>().clip = MouseClickSound;
                 GetComponent.<AudioSource>().Play();
                 updating = false;
                 showGUI = true;
             }
         }
         if(errorOccured == true || updateDone == true){
             if(GUI.Button(Rect(Screen.width*0.5f-100,Screen.height*0.5f+192,200,100),"Okay")){
                 GetComponent.<AudioSource>().clip = MouseClickSound;
                 GetComponent.<AudioSource>().Play();
                 updating = false;
                 showGUI = true;
             }
         }
     }

Here is the code for the actual patcher that will patch The Dark Tower Game (The Dark Tower Game Patcher.exe)

#pragma strict
 import System.Collections.Generic;
 import System.Linq;					//to make the ToList function work
 
var font:Font;
var patcherVersion:String = "Patcher Version 1.0.0";
var backgroundImage:Texture2D;
var myskin:GUISkin;
private var updateMsg:String = "";
private var showUpdateButton:boolean = false;
private var originalMsg:String = "";
private var savePath:String ="";
private var download:WWW;
private var errorOccured:boolean = false;
private var updateDone:boolean = false;
private var updating:boolean = false;
private var version:String = "";

function Start () {
	checkVersion();
}
function checkVersion(){	//for the patcher. Check the version first. 
	updateMsg = "

Checking the Current Version";
var buildVersion = Application.dataPath;
if(Application.platform == RuntimePlatform.OSXPlayer || Application.platform == RuntimePlatform.OSXEditor){
buildVersion += “/…/…/”; //finds the exectuable
}
else if(Application.platform == RuntimePlatform.WindowsPlayer || Application.platform == RuntimePlatform.WindowsEditor){
buildVersion += “/…/”; //finds the exectuable
}
buildVersion += “version.txt”;//have this file next to app/exe

	version = System.IO.File.ReadAllText(buildVersion);
	version = version.Replace(".","");
	version = version.Trim();
	
	updateMsg = "

Checking For Updates…"; //GUI update for user
yield WaitForSeconds(1.0f); //make it visible
var url = “https://googledrive.com/host/0B9EJGlco2Hy6fkU4QVVzMlQ0QnBqTmVjazZTR2dTSkJJal83eEhlYVJsU1puSjA2b3h3VU0/version.txt”; //txt file with version # in it
updateMsg = "

Establishing Connection…“;//GUI update for user
var patchwww:WWW = WWW(url); //create new web connection to the build number site
yield patchwww; // wait for download to finish
var convertedText = patchwww.text;
convertedText = convertedText.Replace(”.“,”“);
convertedText = convertedText.Replace(”
“,”“);
convertedText = convertedText.Replace(”\r",“”);
convertedText = convertedText.Trim();

  	var availablePatch = parseFloat(convertedText);
   	if (parseFloat(version) >= availablePatch){    //check version
      	updateMsg = "

Currently update to date.

Would you like to launch The Dark Tower?";
updateDone = true;
}
else {
applyUpdate();
}
}
//----------------------------------------------------------------
function applyUpdate(){
updateMsg = "

Identifying Platform";
//Find the place to save all of the data
savePath = Application.dataPath;
if(Application.platform == RuntimePlatform.OSXPlayer || Application.platform == RuntimePlatform.OSXEditor){
updateMsg = "

Identifying updates for the Mac platform

Establishing Connection…";
savePath += “/…/…/Contents/”;
}
else if(Application.platform == RuntimePlatform.WindowsPlayer || Application.platform == RuntimePlatform.WindowsEditor){
updateMsg = "

Identifying updates for the Windows platform

Establishing Connection…";
savePath += “/…/TheDarkTower_Data/”;
}
//open and read the patch file contents
var currPatch:String = “https://googledrive.com/host/0B9EJGlco2Hy6fkU4QVVzMlQ0QnBqTmVjazZTR2dTSkJJal83eEhlYVJsU1puSjA2b3h3VU0/patch.txt”;

	var patchlist:WWW = WWW(currPatch); 
    yield patchlist;
      
    var fileList = patchlist.text;
	var fileListArray:String[] = fileList.Split("

" [0]); //every single line in patch.txt seperated into an array
var fileSaveSuccess = true;

	//generate the list of files to download (removing duplicates)
	var downloadList = Array();
	var convertedString:float;
	var addFiles:boolean = false;
	var found = false;
	
	for(var j =0; j < fileListArray.Length; j++){ //Look through list and build files to download list
		if(fileListArray[j].Trim() == "patch"){
			convertedString = parseFloat(fileListArray[(j+1)].Replace(".","").Trim());
			if(convertedString > parseFloat(version)) {
				addFiles = true;
				j = j + 2;
			}
		}

		if(addFiles == true){
			for(var k = 0; k < downloadList.length; k++) {
				if(downloadList[k] == fileListArray[j].Trim()){
					found = true;
					break;
				}
			}
			if(found == false)
				downloadList.Push(fileListArray[j].Trim());
		}
		found = false;
	}
	for (var i = 0; i < downloadList.length; i++) //<-----START DOWNLOADING INDIVIDUAL FILES
	 {
	 	updateMsg = "

File “+(i+1)+” of “+downloadList.length+”

Downloading "+downloadList*;*
_ var fileName = downloadList*.ToString().Trim();*_

_* updateMsg = "

Downloading File “+(i+1)+” of “+downloadList.length+”
";_
_
originalMsg = "

Downloading File “+(i+1)+” of “+downloadList.length+”
“;_
_
yield downloadFile(fileName);_
_
if(errorOccured == true){_
_
break;_
_
}_
_
}_
_
if(errorOccured == false){_
_
var versionURL = “https://googledrive.com/host/0B9EJGlco2Hy6fkU4QVVzMlQ0QnBqTmVjazZTR2dTSkJJal83eEhlYVJsU1puSjA2b3h3VU0/version.txt”;_
_
var versionText:WWW = WWW(versionURL);_
_
yield versionText;_
_
System.IO.File.WriteAllBytes (savePath+”/…/version.txt", versionText.bytes);_
_
updateMsg = "

Successfully Updated.

Would you like to relaunch The Dark Tower?";_
_
updateDone = true;_
_
}_
_
}_
_
//----------------------------------------------------------------_
function downloadFile(file:String){
_
var rawDataFolder = “http://googledrive.com/host/0B9EJGlco2Hy6fjBneExJUXRNSDBKaUx6akcyeU9kMTV6Zlp1UXRlOUQwRXN1QTkzYm96d2s/”;_
_
var url:String = (rawDataFolder+file).ToString();*_

* download = WWW(url); //download file from platforms raw folder*
* while(!download.isDone){*
* if(download.error){*
* errorOccured = true;*
* }*
* else {*
_ updateMsg = originalMsg+"

“+file+”
Download Progress: “+(download.progress * 100).ToString(”##0.00")+“%”;_

* }*
* }*
yield download; // wait for download to finish

try{
* updateMsg +=“…saving…”;*
* System.IO.File.WriteAllBytes (savePath+file, download.bytes); *
* updateMsg +=“success!”;*
}
*catch(error){ *
_* updateMsg ="Update Failed with error message:

“+error.ToString();_
_
errorOccured = true;_
_
}_
_
}_
function openDarkTowerApp(){
_
var fileToOpen:String = Application.dataPath;_
_
if(Application.platform == RuntimePlatform.OSXPlayer || Application.platform == RuntimePlatform.OSXEditor){_
_
fileToOpen += “/…/…/TheDarkTower.app”;_
_
}_
_
else if(Application.platform == RuntimePlatform.WindowsPlayer || Application.platform == RuntimePlatform.WindowsEditor){_
_
fileToOpen += “/…/TheDarkTower.exe”;_
_
}_
_
Application.OpenURL(fileToOpen);_
_
Application.Quit();_
_
}_
function OnGUI(){
_
GUI.skin = myskin;_
_
GUI.skin.font = font;_
_
GUI.Label(Rect(Screen.width-130, Screen.height-80, 100, 80),”<size=20>“+patcherVersion+”“);_
_
if(backgroundImage)_
_
GUI.DrawTexture(Rect(0,0,Screen.width,Screen.height), backgroundImage);_
_ GUI.Box(Rect(Screen.width0.5f-250,Screen.height*0.5f-250,500,300), “<size=25>”+updateMsg+”");_

* if(updateDone == true){*
_ if(GUI.Button(Rect(Screen.width0.5f-100,Screen.height0.5f+82,200,100),“Yes”)){
* updateDone = false;*
* openDarkTowerApp();*
* }*
if(GUI.Button(Rect(Screen.width0.5f-100,Screen.height0.5f+192,200,100),“No”)){
* Application.Quit();*
* }*
* }*
* if(errorOccured == true){*
if(GUI.Button(Rect(Screen.width0.5f-100,Screen.height0.5f+192,200,100),“Close”)){
* Application.Quit();*
* }*
* }*
}
Final few thoughts. This patcher doesn’t account for a “man in the middle” attack. Even though it says “https://” it isn’t secure. Unity doesn’t care about security like that. So you will have to do some research on certificates and authentication to protect your users against these kinds of attacks. However, for me it is fine since these downloads will more than likely not be on public networks and will be done over home networks that a much more secure(hopefully). Also this isn’t a huge game just something between my friends so it’s not going to be a problem.
Hope this teaches people that they want to know about patching and gives them an alternative to paying $30+ for a patching system. Granted those systems on the Unity Asset store are going to be much more robust than what I have got here but with a little more coding I think people can make it as strong as they would like.
Just a review of what you will need:
Patch system right next to game launcher.
version.txt in container folder (where patcher and game are)
version.txt on server (for me google drive)
patch.txt that contains all files for patch on server
updated version in code in game so it recognizes correct version(could change this around to look at version.txt in local directory)
Here is a picture of my final folder structure for windows
[53801-folder-setup.png|53801]*

Good Luck!
_*
EDIT: I have since made edits to my main game (not the patcher) that will check the version.txt file just like the patcher does, just for consistency sake. I would probably suggest to edit the code to do the same… its easier to follow that way.

Use www.patchkit.net is the best way to create a patch for your game :smiley: