Tutorial Nenjine


Preamble

Hello again. After losing the project files to my old launcher, I decided to start work on a new and imrpoved one. Later I plan on making a tutorial on how to make a full launcher that also updates itself, but to get that working first we need a simple updater. So in this tutorial, I'm going to show you:

You'll also want to have somewhere you can host some files from, like github or a webserver, that isn't going to have a dynamic address. This is important because if the address changes, then your updater will no longer be able to get files.

The Setup

  • Execute Shell
  • Before we dive into coding, were going to need to install an extension from the market place, as there is no way to launch other programs from inside a game provided by yoyo games normally. The extension that we need to install is the Execute Shell extension by Samuel Venable.

    After you follow the link, you'll need to login to Yoyo Games to be able add to your account and download the extension. Once the extension is added to your account, in the IDE on the top menu navigate to Marketplace and then to My Library where youll be able to download and import the extension.

    Now were going to create an object and setup a few variables in the create event:

    local_version		= -1;
    live_version		= -1;
    file			= "";
    version			= "";
    download_path		= "";
    file_name		= "Gaem";
    

    To quickly run through: local_version is the version number of the game stored on the computer, live_version is the version number of the most up to date version, file and version are used to keep track of async callbacks, download_path is used to store where the server tells us to get the game zip from, and file_name is used for file names.

    if (!directory_exists("game_files")) { directory_create("game_files") };
    

    The next set we want to do is to create a new folder called "game_files" in the working directory, so that we can keep the game files and the updater files seperate.

    Fetching the local version

  • .ini files
  • Next, we want to find out what our local version is.

    if (file_exists("updaterConfig.ini")) {
    	ini_open("updaterConfig.ini");
    		
    		local_version = ini_read_real(file_name, "version", 0);
    		
    	ini_close();
    } else {
    	ini_open("updaterConfig.ini");
    		
    		ini_write_real(file_name, "version", 0);
    		
    	ini_close();
    };
    

    This is a pretty standard piece of code that checks to see if we have created an .ini file to track the version number. If we have, load it and get the version number, and if not, we need to create the file and set the version number to 0.

    Getting with HTTP

    Lastly before we move out of the create event, we need to request a copy of the version.ini file from the server with the http_get_file function.

    version = http_get_file("http://nenjine.com/version.ini", "version.ini");
    

    You'll want to replace the address provided with the address for wherever you are hosting the versions.ini file.

    The contents of the .ini file should look something like this, but with the address of where you are hosting your game files:

    [Gaem]
    version=1
    path="http://www.nenjine.com/Gaem.zip"
    

    Doing things in step out, of time

    Now were going to move to the http async event. Were going to start with a switch

    switch (async_load[? "id"]) {
    
    };
    

    Because we'll be requesting more than one file from the server and we want to use them differently, we'll need to keep track of what file were recieving when the http async event triggers.

    When we call the http_get_file function is called, it returns an id for us to use to figure out which file was recieved. We'll use these in our switch statement:

    switch (async_load[? "id"]) {
    
    	case version:
    	
    	break;
    	
    };
    

    Whats new?

    When we ask for the version.ini file from the server, the game will call back constantly while the file is being downloaded. Untill the whole file is downloaded we can't access it, so we want to check every call back to see if the file is finished downloading yet:

    case version:
    
    	if (async_load[? "status"] = 0) {
    		
    	};
    	
    break;
    

    Once the file has been downloaded, we want to load it and then get the live_version and the download_path so that we can check if our game is up to date or not, and then close it again.

    if (async_load[? "status"] = 0) {
    	
    	ini_open("version.ini");
    	
    		live_version	= ini_read_real(file_name, "version", 0);
    		download_path	= ini_read_string(file_name, "path", "")
    	
    	ini_close();
    	
    };
    

    To update or to run

  • game_save_id
  • strings
  • Now that we know what the local version and live versions are, we can decide whether to update to the latest version, or to just run the version we currently have.

    if (local_version < live_version) {
    	
    	file = http_get_file(download_path, game_save_id + "\\game_files\\" + file_name + ".zip");
    

    Here we request the files for the game using the address given to us from the server's version.ini file. We download it to the 'game_files' folder that we created at the start, and save it as a zip file.

    Notice here the double backslashes. These are important as backslashes are used for inserting special characters via character codes, so we need to use two back slashes to get a single backslash for our file path.

    } else {
    	
    	execute_shell(game_save_id + "\\game_files\\" + file_name + ".exe", "");
    	
    	game_end();
    	
    };
    

    Now we want to use a function from the extension that we installed earlier. The execute_shell function will run a given string as a command in the shell. If we give it the full path to the exe of our game it will simply run it. Then, we close the updater.

    Downloading the update

    When we download the game files, we'll be getting the file id returned, and so we'll add it to our switch.

    switch (async_load[? "id"]) {
    	
    	case version:
    		
    		...
    		
    	break;
    	
    	case file:
    	
    	break;
    	
    };
    

    Then the same as before, we want to make sure that we are only trying to work with the downloaded files once they've finished downloading.

    case file:
    	
    	if (async_load[? "status"] = 0) {
    		
    	};
    	
    break;
    

    Unzipping and updating

  • zip_unzip
  • Were almost there! All we need to do now, is to update our local version, unzip the files and run them.

    ini_open("updaterConfig.ini");
    		
    	ini_write_real(file_name, "version", live_version);
    		
    ini_close();
    

    This is very similar to how we initialy read our local version, but instead were writing the live version to the file, now that we have the live version.

    To unzip the game files, we get a nice and tidy zip_unzip function to use:

    if (zip_unzip(async_load[? "result"], game_save_id + "\\game_files\\") > 0) {
    	
    };
    

    To use zip_unzip, we need to give it the path to the file, which we get from the http async event, and the location that we want to upzip the files to, which will be in the game files folder we created at the start. The function returns 0 or less if it fails to unzip.

    if (zip_unzip(async_load[? "result"], game_save_id + "\\game_files\\") > 0) {
    	
    	execute_shell(game_save_id + "\\game_files\\" + file_name + ".exe", "");
    	game_end();
    
    };
    

    We use the same code as we did before to run the game and close the updater.

    Hiding the window

    To hide the window that appears when the updater launches we can include a line of code before the first frame is rendered, in the create event.

    draw_enable_drawevent(false);
    

    And thats it!

    Postamble

    I personally think that having an updater is more valuable and more over looked than good sound design in hobbyist game development. If you want to have multiple games live version data held in the same .ini, you can, you just need to have a different section for each game.

    As always, if you need a hand with anything found in this tutorial, or need anything clarified, feel free to contact me.

    13 06 21 19 01 23