Development of the game patcher
Game development *, .NET *
After writing the first game in front of us there was a problem, which we have not even thought about before. This development of the game patcher. we have identified the following requirements for our patcher:
Support Unity Games
User friendly
Showing gaming news
Versatility for all games developed by our studio
Flexible settings
And the most important thing: the ability to make small patches of large files
Link to source patcher at the end.
As usual before reinventing the wheel, I'm looking for ready-made solution to the problem. But either I googled bad, or only meets the requirements of this M2H Patcher with Unity Asset Store.
In the introduction, we spent a few days, and propolzovalis them for about a month (up to the first and also the last of breakage). One day patcher refused to do patch. After spending hours on the investigation I found out the reason.
The fact that this used to work patcher utility bsdiff & bspatch . For the utility must bsdiff max (17 * n, 9 * n + m) + O (1) memory. It so happens that the best machine in the office was only 4 GB of RAM, and a resource file was over 600 MB. Generally bsdiff refused to work with him (before the creation of the patch was obscene 30+ minutes).
Then I decided to collect all the same bike.
Algorithm
Now was naguglit comparison algorithm large binary files. Worthy candidates were two. It Rsync and suffix sorting algorithm of bsdiff.
Since the second were problems, I stayed on the ground.
Its essence is as follows. Splitting source file into equal-sized pieces (the chunks from the English. Chunk).
For each chunk believe two hash: strong and weak. Strong Hash - this is the usual MD5. Weak hash - a ring hash . Its feature is that if the hash from n to n + S-1 is equal to R, then the sequence of bytes from n + 1 to n + S can be calculated based on the R, byte n and byte n + S without the need to take into account the bytes lying within this interval.
Similarly, you need to calculate the resulting file. At the exit we should get two sequences hash chunks.
Next, we compare the weak hashes to files in seeking equal chunks. If the hashes match, the compare hashing strengths. The key of the algorithm is to create two signatures - fast and stable. Fast is used as a filter. Persistent used for a more accurate test.
At the exit we have a list of different chunks, which are recorded in the patch.
Creating a patch
For our game well-suited system, where the version number is indicated by an integer. So we usually have a bunch of folders with different versions of the current project: 1, 2, 3, etc.
The first thing to be done after pressing the button - is to determine which files have changed, delete, add. To do this, compare folders through
string[] files1 = Directory.GetFiles(folder1, "*.*", SearchOption.AllDirectories); string[] files2 = Directory.GetFiles(folder2, "*.*", SearchOption.AllDirectories);
and maintain a list of changes. If a file is added, then consider md5. If changed, we consider the old and new md5. These hashes are needed in order to determine whether it is possible to apply the patch, and if it is well established.
These data are collected in an archive with maximum compression via SharpZipLib . In the end we appends to a file patch_info.txt which stores data on the size of the chunk, a list of files with their hashes and actions.
Example:
1024 R star-draft_Data\level1 M settings.xml 5e54da0d0c1dfca2bbc623979b7bceef 7a64fb8bc102b9d6bc0862ca63cdbb8d A star-draft_Data\level0 a3d14f5ed8d05164d59025cc910226ea M star-draft_Data\resources.assets 02466b9218cbf482d562570d8c0c90c8 20f1f88b5036a168bdd26fe7f4f9dadd M patcher\version.txt c81e728d9d4c2f636f067f89cc14862c c4ca4238a0b923820dcc509a6f75849b
* R - removed, A - added , M - modified
Depending on the action there is a file itself, or a patch for the older version.
Now this patch can be put on any web hosting. I TESTED FOR dropbokse.
It is important to note that for the normal health system in the game folder should be based on the file. \ Patcher \ version.txt. It stores information about the current version of the game. It reads the patcher itself as a result of changes in the process of applying the patch. Patch builder tries to follow so you do not make a mistake, and the version in the file matches the version specified in the folder name.
patcher (screenshot -- On the left should be logos and games publisher, and the right news)
When you start the patcher reads the configuration file on the road ./patcher/configuration.xml and checks for validity.
Sample file with comments:
<?xml version="1.0"?> <root> <!-- Используется в заголовке окна --> <game_name>TestGame</game_name> <!-- Запускается при нажатии кнопки "Играть" --> <game_exe>Test.exe</game_exe> <!-- Открывается в браузере по умолчанию при нажатии на логотип игры--> <game_url>http://coolgame.com</game_url> <!-- URL файла с последней версией игры --> <check_version_url>http://coolgame.com/version.txt</check_version_url> <!-- URL каталога с патчами --> <patches_directory>http://coolgame.com/patches/</patches_directory> <!-- URL новостей игры --> <news_url>http://coolgame.com/news_for_patcher.html</news_url> <!-- Открывается в браузере по умолчанию при нажатии на логотип издателя--> <publisher_url>http://coolpublisher.com</publisher_url> </root>
?>
<?xml version="1.0"?> <root> <!-- Используется в заголовке окна --> <game_name>TestGame</game_name> <!-- Запускается при нажатии кнопки "Играть" --> <game_exe>Test.exe</game_exe> <!-- Открывается в браузере по умолчанию при нажатии на логотип игры--> <game_url>http://coolgame.com</game_url> <!-- URL файла с последней версией игры --> <check_version_url>http://coolgame.com/version.txt</check_version_url> <!-- URL каталога с патчами --> <patches_directory>http://coolgame.com/patches/</patches_directory> <!-- URL новостей игры --> <news_url>http://coolgame.com/news_for_patcher.html</news_url> <!-- Открывается в браузере по умолчанию при нажатии на логотип издателя--> <publisher_url>http://coolpublisher.com</publisher_url> </root>
First of all, the patcher will check its version of ./patcher/version.txt file. Then it will check the latest version of the game from the link settings. If the latest version of the update process under the scheme more than the start:
for (int i = current_version; i < last_version; i++) { DownloadPatch(URL + string.Format("{0}_{1}", i, i+1)); ApplyDownloadedPatch(); }
<last_version; i ++)
for (int i = current_version; i < last_version; i++) { DownloadPatch(URL + string.Format("{0}_{1}", i, i+1)); ApplyDownloadedPatch(); }
To apply the patch, you must first get a list of changed files. So the first thing we get from the downloaded archive patch_info.txt, parse it and looping cycle file.
If the file to be removed, then remove. If added, then unpack the archive. If changed then use the patch if the hashes match (so as not to spoil it).
At the end do not forget to check out the new md5 hash.
I tried to make sure that any exception to the patcher had a text description and solutions.
Just patcher is already localized into Russian and English languages .NET tools
Statistics
To test I immediately shoved it in our game client on Unity3D, which refused to work bsdiff.
Client version 1 - 1669 Mb
Client Version 2 - 1692 Mb (we added modelku with a bundle of textures)
Size of patch size chunks 1 KB and maximum compression of the archive - 11.8 Mb, which is very similar to the results of the patcher with bsdiff'om
Time create a patch on my car less than a minute, and the use of about 10 seconds.
The Source:
You must be registered to see links