Register for your free account! | Forgot your password?

Go Back   elitepvpers > Popular Games > Silkroad Online > SRO Hacks, Bots, Cheats & Exploits
You last visited: Today at 12:45

  • Please register to post and access all features, it's quick, easy and FREE!

Advertisement



SilkroadPatchUtilites - DownloadServer Tools

Discussion on SilkroadPatchUtilites - DownloadServer Tools within the SRO Hacks, Bots, Cheats & Exploits forum part of the Silkroad Online category.

Reply
 
Old   #1

 
elite*gold: 260
Join Date: Aug 2008
Posts: 560
Received Thanks: 3,772
SilkroadPatchUtilites - DownloadServer Tools

This is a thread about one of my current projects, SilkroadPatchUtilities. This project is a collection of tools to help with creating your own DownloadServer (mostly relevant to emu projects). In other words, people will now be able to create their own "updates" through Silkroad.exe without having to redistribute modified PK2 files manually. In addition, new PK2 tools can be created using these concepts that are more friendly to other operating systems since there is no longer a reliance of manually calling functions from within the GFXFileManager.dll file. However, such tools have yet to be developed using this technology.

As with a lot of my projects, this is a developer's project. Most people will not have a need for anything here. There aren't any screenshots due to the nature of this project, but I have made a video of the concepts in action if you are interested:
The project is still in a beta state, but I have done some simple testing across different versions and it seems to be working as expected.

There are 3 main groups of projects included in this collection of tools. The first group are the simple tools to compress and decompress files in a format Silkroad.exe uses. The second group of tools implements a patch file downloader from official server versions to test the tools. The third group of tools implements a simple DownloadServer to show the basics.

In the first group, there are 5 projects:
CompressToLZMA - Compresses an input file into a LZMA compressed file Silkroad.exe can process.
CompressToZLIB - Compresses an input file into a ZLIB compressed file Silkroad.exe can process.
DecompressFromLZMA -Decompresses an input file from LZMA into the original file.
DecompressFromZLIB - Decompresses an input file from ZLIB into the original file.
BatchProcess - Wraps using all 4 tools into one utility while providing some extra features to help manage folders of files.

This set of tools implements the basic tools for allowing people to compress and decompress files into a format Silkroad.exe can process. The reason there are two formats is because ISRO updated the format while most of the other versions did not. This probably was due to the larger player base ISRO has and Joymax wanted better compression in their files. When RSRO bought their license, they happened to get the ISRO code so they too use lzma. CSRO and KSRO still use the ZLIB version and I'd imagine JSRO, VSRO, TSRO do as well, but I've not confirmed.

There is nothing special with the format of these files except there is an extra DWORD at the start of the file that tells the original file size. Once that is processed, the remaining data is simply the output stream from the respective compression used. As a result of this simplicity, compressing your own files into the format is equally as trivial. The generated files won't match the originals since different compression options are used, but in the end, the original data is the same.

In fact, once you look over the code required, you'd probably ask why this hasn't been done sooner! It's a lot simpler than I had though, but it still took a bit of time of reversing and tediously testing code to make the connections shown in the implementations. I started out with using direct asm code from the client, then moved into lower level SDK functions, then finally worked my way to the higher level function that greatly simplifies the amount of work to be done.

While I have my files coded to specifically process files with .lzma and .zlib extensions, this can be changed to any arbitrary extensions. In doing so, you can use the ZLIB versions to easily decompress the DownloadServer files from sl.rar (leaked JSRO files) if you wanted them. To do it by hand, simply add a ".zlib" extension to the end of any file and drag it on top of the existing precompiled DecompressFromZLIB.exe tool. I will also mention that people have already done this though, so it is nothing new (ZLIB wise).

I'll go ahead and paste the main CPP files for people to get an idea of how simple the code is if you don't want to download the entire package (sorry about the size, I have sqlite3 in my Common folder)

CompressToLZMA
Code:
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <vector>
#include <string>
#include "../Common/lzma/LzmaUtil/Lzma86Enc.h"
#include "../Common/lzma/LzmaUtil/Lzma86Dec.h"

int main(int argc, char * argv[])
{
	if(argc < 2)
	{
		printf("Usage:\n\tCompress <input file> ... [input file]\n");
		return -1;
	}
	for(int x = 1; x < argc; ++x)
	{
		std::vector<unsigned char> input_data;
		unsigned char * input_data_ptr = 0;
		SizeT input_data_size = 0;
		std::vector<unsigned char> output_data;
		unsigned char * output_data_ptr = 0;
		SizeT output_data_size = 0;
		FILE * infile = 0;
		FILE * outfile = 0;
		UInt64 unpackSize = 0;
		SRes result = 0;
		errno_t err = 0;
		size_t ind = 0;
		std::string ifn;
		std::string ofn;

		ofn = argv[x];
		ifn = argv[x];
		if((err = fopen_s(&infile, argv[x], "rb")) || infile == NULL)
		{
			printf("Error: Could not open input file [%s] due to fopen_s error (%i).\n", argv[x], err);
			continue;
		}

		fseek(infile, 0, SEEK_END);
		input_data_size = ftell(infile);
		input_data.resize(input_data_size + 1); // + 1 so [0] is always valid
		input_data_ptr = &input_data[0];
		fseek(infile, 0, SEEK_SET);
		fread(&input_data[0], 1, input_data.size(), infile);
		fclose(infile);

		output_data_size = static_cast<SizeT>(1.25 * input_data_size); // Add 25% extra space
		if(output_data_size < 4096) // Make sure smaller files have enough buffer space
		{
			output_data_size = 4096;
		}
		output_data.resize(output_data_size + 1); // + 1 so [0] is always valid
		output_data_ptr = &output_data[0];

		result = Lzma86_Encode(output_data_ptr, &output_data_size, input_data_ptr, input_data_size, 9, 0x02000000, SZ_FILTER_NO);
		switch(result)
		{
			case SZ_OK: 
			{
				ofn += ".lzma";
				if((err = fopen_s(&outfile, ofn.c_str(), "wb")) || outfile == NULL)
				{
					printf("Error: Could not save the output file [%s] for input file [%s] due to fopen_s error (%i).\n", ofn.c_str(), argv[x], err);
				}
				else
				{
					result = Lzma86_GetUnpackSize(output_data_ptr, output_data_size, &unpackSize);
					fwrite(&unpackSize, 1, 4, outfile);
					fwrite(output_data_ptr, 1, output_data_size, outfile);
					fclose(outfile);
					//printf("Information: %s was successfully compressed!\n", argv[x]);
				}
			} break;
			case SZ_ERROR_THREAD: printf("Error: SZ_ERROR_THREAD detected for input file [%s].\n", argv[x]); break;
			case SZ_ERROR_MEM: printf("Error: SZ_ERROR_MEM detected for input file [%s].\n", argv[x]); break;
			case SZ_ERROR_PARAM: printf("Error: SZ_ERROR_PARAM detected for input file [%s].\n", argv[x]); break;
			case SZ_ERROR_OUTPUT_EOF: printf("Error: SZ_ERROR_OUTPUT_EOF detected for input file [%s].\n", argv[x]); break;
			default: printf("Error: %i detected for input file [%s].\n", result, argv[x]); break;
		}
	}
	return 0;
}
CompressToZLIB
Code:
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <vector>
#include <string>
#include "../Common/zlib/zlib.h"

int main(int argc, char * argv[])
{
	if(argc < 2)
	{
		printf("Usage:\n\tCompress <input file> ... [input file]\n");
		return -1;
	}
	for(int x = 1; x < argc; ++x)
	{
		std::vector<unsigned char> input_data;
		unsigned char * input_data_ptr = 0;
		DWORD input_data_size = 0;
		std::vector<unsigned char> output_data;
		unsigned char * output_data_ptr = 0;
		DWORD output_data_size = 0;
		FILE * infile = 0;
		FILE * outfile = 0;
		int result = 0;
		errno_t err = 0;
		size_t ind = 0;
		std::string ifn;
		std::string ofn;

		ofn = argv[x];
		ifn = argv[x];
		if((err = fopen_s(&infile, argv[x], "rb")) || infile == NULL)
		{
			printf("Error: Could not open input file [%s] due to fopen_s error (%i).\n", argv[x], err);
			continue;
		}

		fseek(infile, 0, SEEK_END);
		input_data_size = ftell(infile);
		input_data.resize(input_data_size + 1); // + 1 so [0] is always valid
		input_data_ptr = &input_data[0];
		fseek(infile, 0, SEEK_SET);
		fread(&input_data[0], 1, input_data.size(), infile);
		fclose(infile);

		output_data_size = static_cast<DWORD>(1.25 * input_data_size); // Add 25% extra space
		if(output_data_size < 4096) // Make sure smaller files have enough buffer space
		{
			output_data_size = 4096;
		}
		output_data.resize(output_data_size);
		output_data_ptr = &output_data[0];

		result = compress(output_data_ptr, &output_data_size, input_data_ptr, input_data_size);
		switch(result)
		{
			case Z_OK: 
			{
				ofn += ".zlib";
				if((err = fopen_s(&outfile, ofn.c_str(), "wb")) || outfile == NULL)
				{
					printf("Error: Could not save the output file [%s] for input file [%s] due to fopen_s error (%i).\n", ofn.c_str(), argv[x], err);
				}
				else
				{
					fwrite(&input_data_size, 1, 4, outfile);
					fwrite(output_data_ptr, 1, output_data_size, outfile);
					fclose(outfile);
					//printf("Information: %s was successfully compressed!\n", argv[x]);
				}
			} break;
			case Z_MEM_ERROR: printf("Error: Z_MEM_ERROR detected for input file [%s].\n", argv[x]); break;
			case Z_BUF_ERROR: printf("Error: Z_BUF_ERROR detected for input file [%s].\n", argv[x]); break;
			default: printf("Error: %i detected for input file [%s].\n", result, argv[x]); break;
		}
	}
	return 0;
}
DecompressFromLZMA
Code:
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <vector>
#include <string>
#include "../Common/lzma/LzmaUtil/Lzma86Dec.h"

int main(int argc, char * argv[])
{
	if(argc < 2)
	{
		printf("Usage:\n\tDecompress <input file> ... [input file]\n");
		return -1;
	}
	for(int x = 1; x < argc; ++x)
	{
		std::vector<unsigned char> input_data;
		unsigned char * input_data_ptr = 0;
		SizeT input_data_size = 0;
		std::vector<unsigned char> output_data;
		unsigned char * output_data_ptr = 0;
		SizeT output_data_size = 0;
		FILE * infile = 0;
		FILE * outfile = 0;
		UInt64 unpackSize = 0;
		SRes result = 0;
		errno_t err = 0;
		size_t ind = 0;
		std::string ifn;
		std::string ofn;

		ofn = argv[x];
		ifn = argv[x];
		ind = ifn.find(".lzma");
		if(ind != ifn.size() - 5)
		{
			printf("Error: The input file should have a trailing extension of .lzma [%s]\n", argv[x]);
			continue;
		}

		if((err = fopen_s(&infile, argv[x], "rb")) || infile == NULL)
		{
			printf("Error: Could not open input file [%s] due to fopen_s error (%i).\n", argv[x], err);
			continue;
		}

		fseek(infile, 0, SEEK_END);
		input_data_size = ftell(infile);
		input_data.resize(input_data_size + 1); // + 1 so [0] is always valid
		input_data_ptr = &input_data[0];
		fseek(infile, 0, SEEK_SET);
		fread(&input_data[0], 1, input_data.size(), infile);
		fclose(infile);
		infile = 0;

		result = Lzma86_GetUnpackSize(input_data_ptr + 4, input_data_size - 4, &unpackSize);
		output_data_size = static_cast<DWORD>(unpackSize);
		if(output_data_size != *(SizeT *)input_data_ptr)
		{
			printf("Error: Unpack Size Mismatch (%ul) vs (%ul) for [%s]\n", output_data_size, *(SizeT *)input_data_ptr, argv[x]);
			continue;
		}

		output_data.resize(output_data_size + 1); // + 1 so [0] is always valid
		output_data_ptr = &output_data[0];

		input_data_ptr += 0x04;
		input_data_size -= 0x04;

		result = Lzma86_Decode(output_data_ptr, &output_data_size, input_data_ptr, &input_data_size);
		switch(result)
		{
			case SZ_OK: 
			{
				ind = ofn.find(".lzma");
				if(ind != ofn.size() - 5)
				{
					printf("Error: Could not generate the name of the output file for [%s]\n", argv[x]);
					continue;
				}
				ofn = ofn.substr(0, ind);
				if((err = fopen_s(&outfile, ofn.c_str(), "wb")) || outfile == NULL)
				{
					printf("Error: Could not save the output file [%s] for input file [%s] due to fopen_s error (%i).\n", ofn.c_str(), argv[x], err);
				}
				else
				{
					fwrite(output_data_ptr, 1, output_data_size, outfile);
					fclose(outfile);
					//printf("Information: %s was successfully decompressed!\n", argv[x]);
				}
			} break;
			case SZ_ERROR_DATA: printf("Error: SZ_ERROR_DATA detected for input file [%s].\n", argv[x]); break;
			case SZ_ERROR_MEM: printf("Error: SZ_ERROR_MEM detected for input file [%s].\n", argv[x]); break;
			case SZ_ERROR_UNSUPPORTED: printf("Error: SZ_ERROR_UNSUPPORTED detected for input file [%s].\n", argv[x]); break;
			case SZ_ERROR_INPUT_EOF: printf("Error: SZ_ERROR_INPUT_EOF detected for input file [%s].\n", argv[x]); break;
			default: printf("Error: %i detected for input file [%s].\n", result, argv[x]); break;
		}
	}
	return 0;
}
DecompressFromZLIB
Code:
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <vector>
#include <string>
#include "../Common/zlib/zlib.h"

int main(int argc, char * argv[])
{
	if(argc < 2)
	{
		printf("Usage:\n\tDecompress <input file> ... [input file]\n");
		return -1;
	}
	for(int x = 1; x < argc; ++x)
	{
		std::vector<unsigned char> input_data;
		unsigned char * input_data_ptr = 0;
		DWORD input_data_size = 0;
		std::vector<unsigned char> output_data;
		unsigned char * output_data_ptr = 0;
		DWORD output_data_size = 0;
		FILE * infile = 0;
		FILE * outfile = 0;
		int result = 0;
		errno_t err = 0;
		size_t ind = 0;
		std::string ifn;
		std::string ofn;

		ofn = argv[x];
		ifn = argv[x];
		ind = ifn.find(".zlib");
		if(ind != ifn.size() - 5)
		{
			printf("Error: The input file should have a trailing extension of .zlib [%s]\n", argv[x]);
			continue;
		}

		if((err = fopen_s(&infile, argv[x], "rb")) || infile == NULL)
		{
			printf("Error: Could not open input file [%s] due to fopen_s error (%i).\n", argv[x], err);
			continue;
		}

		fseek(infile, 0, SEEK_END);
		input_data_size = ftell(infile);
		input_data.resize(input_data_size + 1); // + 1 so [0] is always valid
		input_data_ptr = &input_data[0];
		fseek(infile, 0, SEEK_SET);
		fread(&input_data[0], 1, input_data.size(), infile);
		fclose(infile);
		infile = 0;

		output_data_size = *(DWORD *)input_data_ptr;
		if(output_data_size < 4096)
		{
			output_data_size = 4096;
		}
		output_data.resize(output_data_size);
		output_data_ptr = &output_data[0];

		input_data_ptr += 0x04;
		input_data_size -= 0x04;

		result = uncompress(output_data_ptr, &output_data_size, input_data_ptr, input_data_size);
		switch(result)
		{
			case Z_OK: 
			{
				ind = ofn.find(".zlib");
				if(ind != ofn.size() - 5)
				{
					printf("Error: Could not generate the name of the output file for [%s]\n", argv[x]);
					continue;
				}
				ofn = ofn.substr(0, ind);
				if((err = fopen_s(&outfile, ofn.c_str(), "wb")) || outfile == NULL)
				{
					printf("Error: Could not save the output file [%s] for input file [%s] due to fopen_s error (%i).\n", ofn.c_str(), argv[x], err);
				}
				else
				{
					fwrite(output_data_ptr, 1, output_data_size, outfile);
					fclose(outfile);
					//printf("Information: %s was successfully decompressed!\n", argv[x]);
				}
			} break;
			case Z_MEM_ERROR: printf("Error: Z_MEM_ERROR detected for input file [%s].\n", argv[x]); break;
			case Z_BUF_ERROR: printf("Error: Z_BUF_ERROR detected for input file [%s].\n", argv[x]); break;
			case Z_DATA_ERROR: printf("Error: Z_DATA_ERROR detected for input file [%s].\n", argv[x]); break;
			default: printf("Error: %i detected for input file [%s].\n", result, argv[x]); break;
		}
	}
	return 0;
}
For the sake of having well defined tools, I've created 4 different utilities. For a future release, I'd just make one main tool that could handle everything seamlessly. For this project, that's not as important since the concepts are being shown.

In the second group, there is 1 project:
SilkroadPatchDownloader - Implements a simple clientless that will intentionally trigger patch updates so the compressed files can be saved to disk.

I wrote this project so users will be able to download patch files from the servers and save them to disk. In addition, the code shows how to process these packets correctly so if someone wanted to emulate how Silkroad.exe processes patch packets, they can refer to the code as well.

Inside the /bin folder, there are a few batch files with a name beginning with 1_. These batch files start the SilkroadPatchDownloader.exe configured for specific Silkroad versions. Since there is a disconnect between the versions and which compression method they use, this has to be specified to the program. After you run one of the files, the patch files are downloaded into a folder named output/<locale>/<version>. A sample bat for isro is: "SilkroadPatchDownloader.exe lzma gwgt1.joymax.com 15779 18 254 > isro.log". 18 is the locale, 254 is the version to send, lzma is the compression method and then the standard GatewayServer address. I pipe the output to a file using >.

Once you have files downloaded, either zlib or lzma files, you can then drag them on top of the DecompressFromZLIB.exe or DecompressFromLZMA.exe programs to get the original files back. Alternatively, the batch files that begin with a 2_ name do this. They show how to decompress a folder of files from the lzma or zlib format. Simply right click and choose edit on these batch files to see the format required. In addition, check out the code itself for the programs as you go along.

The batch files with 3_ in their name show how to compress your files into a format ready for use by a DownloadServer. The organization of the input files is very deliberate in setting up a system where automating new patches is a lot easier. For an emu project, it's up to the coder to work out a system that works best for them though, my system is really basic to show the main concepts.

Here's the code for the PatchDownloader:
Code:
#define _CRT_SECURE_NO_WARNINGS
#include "../Common/Network.h"
#include "../Common/StreamUtility.h"
#include "../Common/SilkroadSecurity.h"
#include "../Common/DumpToString.h"
#include "../Common/RecursiveCreateDirectory.h"
#include <windows.h>
#include <conio.h>
#include <map>
#include <list>
#include <string>
#include <sstream>
#include <boost/filesystem.hpp>

namespace fs = boost::filesystem;

class SilkroadPatchDownloader : private Network
{
private:
	struct BigPacket
	{
		WORD opcode;
		WORD count;
		std::vector<char> data;
	};

	struct FileEntry
	{
		BYTE m_inpk2;
		DWORD m_id;
		DWORD m_filesize;
		std::string m_filename;
		std::string m_filepath;
		std::vector<char> m_data;

		FileEntry(DWORD id, const std::string & filename, std::string & filepath, DWORD filesize, BYTE inpk2)
			: m_id(id), m_filesize(filesize), m_filename(filename), m_filepath(filepath), m_inpk2(inpk2)
		{ }
	};

	struct ClientContext
	{
		int m_sid;
		SilkroadSecurity m_security;
		std::vector<char> m_packet;
		BigPacket m_big_packet;
		bool m_ping;
		DWORD m_last_action;
		std::list<FileEntry> m_files;
		int m_type;

		ClientContext(int sid)
			: m_sid(sid)
		{
			m_packet.reserve(8192);
			m_packet.resize(8192);
			m_packet.clear();
			m_ping = false;
			m_last_action = 0;
			m_type = 0;
		}
	};

	std::map<int, ClientContext *> m_current_clients;
	std::map<int, std::list<FileEntry>> m_client_files;
	std::map<int, int> m_key_to_type;
	HANDLE m_exit_event;
	fs::path m_full_path;
	std::string m_patch_host;
	unsigned short m_patch_port;
	BYTE m_patch_locale;
	DWORD m_patch_version;
	DWORD m_real_version;
	std::string m_ext;

private:
	void OnSessionStart(int sid, const char * const remoteHost, unsigned short remotePort, const char * const localHost, unsigned short localPort, int key)
	{
		printf("[%s][%s:%i][%s:%i]\n", __FUNCTION__, remoteHost, remotePort, localHost, localPort);
		if(sid == 0)
		{
			printf("Error: Could not connect to the server.\n");
			SetEvent(m_exit_event);
		}
		else if(key == -1)
		{
			ClientContext * client_context = new ClientContext(sid);
			client_context->m_security.SetSecurityMode(0x0E);
			m_current_clients.insert(std::make_pair(sid, client_context));
			client_context->m_type = 0;
			Network::PostRead(sid, client_context->m_security.GetNextReadSize(0, 0), true);
		}
		else
		{
			std::map<int, int>::iterator itr0 = m_key_to_type.find(key);
			if(itr0 == m_key_to_type.end())
			{
				PostError(sid, -1, "Invalid key");
				return;
			}

			int client_type = itr0->second;
			m_key_to_type.erase(itr0);

			if(client_type == 2)
			{
				std::map<int, std::list<FileEntry>>::iterator itr1 = m_client_files.find(key);
				if(itr1 == m_client_files.end())
				{
					PostError(sid, -1, "Invalid key");
					return;
				}
				ClientContext * client_context = new ClientContext(sid);
				client_context->m_security.SetSecurityMode(0x0E);
				client_context->m_files = itr1->second;
				client_context->m_type = client_type;
				m_client_files.erase(itr1);
				m_current_clients.insert(std::make_pair(sid, client_context));
				Network::PostRead(sid, client_context->m_security.GetNextReadSize(0, 0), true);
			}

			else
			{
				PostError(sid, -1, "Invalid client type (%i)", client_type);
			}
		}
	}

	void OnSessionRead(int sid, const char * const buffer, int size)
	{
		std::map<int, ClientContext *>::iterator itr = m_current_clients.find(sid);
		if(itr == m_current_clients.end())
		{
			PostError(sid, -1, "Unknown sid (%i)\n", sid);
			return;
		}
		ClientContext * client_context = itr->second;
		std::copy(buffer, buffer + size, std::back_inserter(client_context->m_packet));
		size_t readSize = client_context->m_security.GetNextReadSize(&client_context->m_packet[0], client_context->m_packet.size());
		if(readSize)
		{
			Network::PostRead(sid, readSize, true);
			return;
		}
		size_t readCount = client_context->m_security.GetReadProcessCount(&client_context->m_packet[0], client_context->m_packet.size());
		if(readCount == 0)
		{
			Network::PostError(sid, -1, "GetReadProcessCount returned 0.");
			return;
		}
		std::pair<int, std::vector<char>> result = client_context->m_security.ExtractPacket(&client_context->m_packet[0], client_context->m_packet.size());
		client_context->m_packet.clear();
		if(result.first < 0)
		{
			Network::PostError(sid, -1, "ExtractPacket returned an error (%i).", result.first);
			return;
		}
		HandlePacket(client_context, result.second, (result.first == 1));
		Network::PostRead(sid, client_context->m_security.GetNextReadSize(0, 0), true);
	}

	void OnSessionWrite(int sid, const char * const buffer, int size)
	{
	}

	void OnSessionError(int sid, int error, const char * msg)
	{
		printf("%s(%i, %i, %s)\n", __FUNCTION__, sid, error, strlen(msg) ? msg : "\"\"");
		std::map<int, ClientContext *>::iterator itr = m_current_clients.find(sid);
		if(itr != m_current_clients.end())
		{
			delete itr->second;
			m_current_clients.erase(itr);
		}
	}

	void OnPoll()
	{
		std::map<int, ClientContext *>::iterator itr = m_current_clients.begin();
		while(itr != m_current_clients.end())
		{
			ClientContext * client_context = itr->second;
			if(client_context->m_ping)
			{
				if(GetTickCount() - client_context->m_last_action >= 5000)
				{
					StreamBuilder response;
					SendPacket(client_context, 0x2002, response, false);
				}
			}
			++itr;
		}
	}

	void OnShutdown()
	{
	}

	void OnLog(const char * const file, int line, const char * const func, const char * const text)
	{
		printf("%s(%s, %i, %s, %s)\n", __FUNCTION__, file, line, func, text);
	}

private:
	void SendPacket(ClientContext * client_context, WORD opcode, StreamBuilder & data, bool encrypted)
	{
		StreamBuilder builder;
		builder.Write<WORD>(data.GetCount());
		builder.Write<WORD>(opcode);
		builder.Write<WORD>(0);
		builder.WriteArray<char>(data.GetStream(), data.GetCount());
		Send(client_context, builder.ToVector(), encrypted);
	}

	void SendPacket(ClientContext * client_context, WORD opcode, std::vector<char> & data, bool encrypted)
	{
		StreamBuilder builder;
		builder.Write<WORD>(data.size());
		builder.Write<WORD>(opcode);
		builder.Write<WORD>(0);
		builder.WriteArray<char>(&data[0], data.size());
		Send(client_context, builder.ToVector(), encrypted);
	}

	void Send(ClientContext * client_context, std::vector<char> output, bool encrypted)
	{
		//printf("[C->S][%i][%.4X][%i bytes]%s\n", client_context->m_sid, *(unsigned short *)&output[2], *(unsigned short *)&output[0], encrypted ? "[Enc]" : "");
		//printf("%s\n\n", DumpToString(&output[0] + 6, output.size() - 6).c_str());

		output = client_context->m_security.FormatC2SPacket(&output[0], output.size(), encrypted);
		PostWrite(client_context->m_sid, &output[0], output.size());
		client_context->m_last_action = GetTickCount();
	}

	bool HandlePacket(ClientContext * client_context, std::vector<char> & packet, bool encrypted)
	{
		StreamReader reader(&packet[0], packet.size());

		WORD size = reader.Read<WORD>();
		WORD opcode = reader.Read<WORD>();
		WORD security = reader.Read<WORD>();

		//printf("[S->C][%i][%.4X][%i bytes]\n", client_context->m_sid, opcode, size);
		//printf("%s\n\n", DumpToString(reader.GetCurStream(), reader.GetCurCount()).c_str());

		if(opcode == 0x5000)
		{
			if(client_context->m_security.Handshake(&packet[0], packet.size()) == false)
			{
				Network::PostError(client_context->m_sid, 1, "Handshake returned false.");
				return false;
			}
			packet = client_context->m_security.BuildHandshakePacket();
			Send(client_context, packet, false);

			if(*(unsigned short *)(&packet[2]) == 0x9000)
			{
				StreamBuilder response;
				response.Write<WORD>(strlen("SR_Client"));
				response.WriteArray<char>("SR_Client", strlen("SR_Client"));
				response.Write<BYTE>(0);
				SendPacket(client_context, 0x2001, response, true);
			}
		}
		else if(opcode == 0x2001)
		{
			WORD len = 0;
			std::string name;
			BYTE flag;

			len = reader.Read<WORD>();
			name.resize(len);
			reader.ReadArray(&name[0], len);
			flag = reader.Read<BYTE>();

			if(name == "GatewayServer")
			{
				StreamBuilder response;
				response.Write<BYTE>(m_patch_locale);
				response.Write<WORD>(strlen("SR_Client"));
				response.WriteArray<char>("SR_Client", strlen("SR_Client"));
				response.Write<DWORD>(m_patch_version);
				SendPacket(client_context, 0x6100, response, true);
			}
			else if(name == "DownloadServer")
			{
				// Nothing to do here //
			}
		}

		else if(opcode == 0x600D)
		{
			BYTE type = reader.Read<BYTE>();
			if(type == 1) // Header
			{
				client_context->m_big_packet.count = reader.Read<WORD>(); // how many 0x600D data segments
				client_context->m_big_packet.opcode = reader.Read<WORD>(); // opcode of the spanned packet
				client_context->m_big_packet.data.clear();
				client_context->m_big_packet.data.resize(6, 0);
			}
			else if(type == 0) // Data
			{
				std::copy(reader.GetCurStream(), reader.GetCurStream() + reader.GetCurCount(), std::back_inserter(client_context->m_big_packet.data));
				client_context->m_big_packet.count--;
				if(client_context->m_big_packet.count == 0)
				{
					*(unsigned short *)&client_context->m_big_packet.data[0] = client_context->m_big_packet.data.size() - 6; // This overflows on packets > 65k, check data size in packet handlers instead
					*(unsigned short *)&client_context->m_big_packet.data[2] = client_context->m_big_packet.opcode;
					HandlePacket(client_context, client_context->m_big_packet.data, false);
				}
			}
		}

		else if(opcode == 0xA100)
		{
			BYTE mode = reader.Read<BYTE>();
			if(mode == 0x01) // All set
			{
				PostError(client_context->m_sid, -1, "Version is current");
				SetEvent(m_exit_event);
			}
			else if(mode == 0x02) // Error
			{
				mode = reader.Read<BYTE>();
				if(mode == 0x02) // Updates available
				{
					WORD len;
					std::string ip;
					WORD port;
					DWORD version;
					BYTE file;

					m_client_files.insert(std::make_pair(client_context->m_sid, std::list<FileEntry>()));
					std::list<FileEntry> & files = m_client_files[client_context->m_sid];

					len = reader.Read<WORD>();

					ip.resize(len);
					reader.ReadArray(&ip[0], len); // download server

					port = reader.Read<WORD>();

					version = reader.Read<DWORD>();
					m_real_version = version;

					file = reader.Read<BYTE>();
					while(file)
					{
						DWORD id;
						DWORD filesize;
						std::string fn;
						std::string path;
						BYTE inpk2;

						id = reader.Read<DWORD>();

						len = reader.Read<WORD>();

						fn.resize(len);
						reader.ReadArray(&fn[0], len); // file name

						len = reader.Read<WORD>();

						path.resize(len);
						reader.ReadArray(&path[0], len); // file path

						filesize = reader.Read<DWORD>();

						inpk2 = reader.Read<BYTE>();

						files.push_back(FileEntry(id, fn, path, filesize, inpk2));

						file = reader.Read<BYTE>();
					}

					m_key_to_type.insert(std::make_pair(client_context->m_sid, 2));

					PostConnect(ip.c_str(), port, client_context->m_sid);

					PostError(client_context->m_sid, 0, "Now downloading updates");
				}
				else if(mode == 0x04) // Server down
				{
					PostError(client_context->m_sid, -1, "The server is down");
					SetEvent(m_exit_event);
				}
				else if(mode == 0x05) // Version too old
				{
					PostError(client_context->m_sid, -1, "Version is too old");
					SetEvent(m_exit_event);
				}
				else if(mode == 0x01) // Version too new
				{
					PostError(client_context->m_sid, -1, "Version is too new");
					SetEvent(m_exit_event);
				}
				else
				{
					PostError(client_context->m_sid, -1, "Unknown response (%i)", mode);
					SetEvent(m_exit_event);
				}
			}
		}

		else if(opcode == 0x1001)
		{
			std::copy(reader.GetCurStream(), reader.GetCurStream() + reader.GetCurCount(), std::back_inserter(client_context->m_files.front().m_data));
		}

		else if(opcode == 0x6005)
		{
			client_context->m_ping = true;
			if(!client_context->m_files.empty())
			{
				StreamBuilder response;
				response.Write<DWORD>(client_context->m_files.front().m_id);
				response.Write<DWORD>(0);
				SendPacket(client_context, 0x6004, response, false);
			}
		}

		else if(opcode == 0xA004)
		{
			FileEntry & fileEntry = client_context->m_files.front();

			std::stringstream ss;
			ss << m_full_path.file_string() << "\\" << "output" << "\\" << (int)m_patch_locale << "\\" << m_real_version << "\\";
			ss << fileEntry.m_filepath << "\\";

			RecursiveCreateDirectoryA(ss.str().c_str(), NULL);

			std::string fpth = ss.str();

			ss << fileEntry.m_filename;
			ss << "." << m_ext;

			fpth = ss.str();

			FILE * of = 0;
			errno_t err;
			if((err = fopen_s(&of, fpth.c_str(), "wb")) || of == NULL)
			{
				printf("Error: Could not create output file [%s]\n", fpth.c_str());
			}
			else
			{
				fwrite(&fileEntry.m_data[0], 1, fileEntry.m_filesize, of);
				fclose(of);
				printf("Information: Saved file [%s]\n", fileEntry.m_filename.c_str());
			}

			client_context->m_files.erase(client_context->m_files.begin());
			if(!client_context->m_files.empty())
			{
				StreamBuilder response;
				response.Write<DWORD>(client_context->m_files.front().m_id);
				response.Write<DWORD>(0);
				SendPacket(client_context, 0x6004, response, false);
			}
			else
			{
				PostError(client_context->m_sid, 0, "Done downloading updates");
				SetEvent(m_exit_event);
			}
		}

		return true;
	}

public:
	SilkroadPatchDownloader() : m_full_path(fs::initial_path<fs::path>())
	{
		m_exit_event = 0;
		m_patch_port = 0;
		m_patch_locale = 0;
		m_patch_version = 0;
		m_real_version = 0;
	}

	~SilkroadPatchDownloader()
	{
	}

	int Run(int argc, char * argv[])
	{
		if(argc != 6)
		{
			printf("Usage:\n\tSilkroadPatchDownloader <host> <port> <locale> <version> <lzma | zlib>\n");
			return -1;
		}

		m_ext = argv[1];
		m_patch_host = argv[2];
		m_patch_port = atoi(argv[3]);
		m_patch_locale = atoi(argv[4]);
		m_patch_version = atoi(argv[5]);

		if(m_ext != "lzma" && m_ext != "zlib")
		{
			printf("Error: Invalid extension [%s]. Valid is \"lzma\" or \"zlib\" (without quotes).\n", m_ext.c_str());
			return -1;
		}

		m_exit_event = CreateEvent(NULL, TRUE, FALSE, NULL);

		PostConnect(m_patch_host.c_str(), m_patch_port, -1);

		while(WaitForSingleObject(m_exit_event, 1) == WAIT_TIMEOUT)
		{
			Poll();
		}

		Shutdown();

		CloseHandle(m_exit_event);

		return 0;
	}
};

int main(int argc, char * argv[])
{
	return SilkroadPatchDownloader().Run(argc, argv);
}
In the third group, there is 1 project:
DownloadServerDemo - Implements a simple ISRO DownloadServer that will send patches to Silkroad.exe when it connects if the version does not match.

This project is better suited for study rather than running. Make sure to watch the YouTube video linked above to see an older version in action. I'd advise users to make lots of backups of their PK2 files before trying to mess with this, as you can easily patch yourself into a situation where you can't recover easily. Also, each patch happens to change the version of the PK2, so the code is not setup to allow for trivial PK2 editing.

I have implemented a simple system so you can patch the version number back to any version you need through the use of a dummy file, but any changes you made are going to stay until you repatch them back, which makes backups a must. With that said, there is no precompiled file since it doesn't make sense due to the nature of what's being implemented. The code works, but it's more for showing how to implement the server rather than simply implementing one anyone can use as-is. I'd advise anyone interested in running the exe to read through the source in it's entirety to make all the necessary changes that might be needed between different versions.

Here's the code for the DownloadServerDemo:
Code:
#define _CRT_SECURE_NO_WARNINGS
#include "../Common/Network.h"
#include "../Common/StreamUtility.h"
#include "../Common/SilkroadSecurity.h"
#include "../Common/DumpToString.h"
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <map>
#include <list>
#include <iostream>
#include <conio.h>
#include <boost/filesystem.hpp>

namespace fs = boost::filesystem;

// Set this version and recompile to test out patching between versions,
// in a real server, this would be configured by a file.
const DWORD current_version = 256;

void recurse(const fs::path & p, std::list<std::string> & files)
{
	fs::directory_iterator di(p);
	fs::directory_iterator de;
	while(di != de)
	{
		try
		{
			if(fs::is_directory(di->status()))
			{
				recurse(di->path(), files);
			}
			if(fs::is_regular_file(di->status()))
			{
				files.push_back(di->path().file_string());
			}
		}
		catch(const std::exception & e)
		{
			std::cout << e.what() << std::endl;
		}
		++di;
	}
}

void get_top_level_folders(const fs::path & p, std::list<std::string> & folders)
{
	fs::directory_iterator di(p);
	fs::directory_iterator de;
	while(di != de)
	{
		try
		{
			if(fs::is_directory(di->status()))
			{
				folders.push_back(di->path().file_string());
			}
		}
		catch(const std::exception & e)
		{
			std::cout << e.what() << std::endl;
		}
		++di;
	}
}

class DownloadServer : private Network
{
private:
	struct ClientContext
	{
		int m_sid;
		SilkroadSecurity m_security;
		std::vector<char> m_packet;
		DWORD m_last_action;
		int m_key;

		ClientContext(int sid)
			: m_sid(sid)
		{
			m_packet.reserve(8192);
			m_packet.resize(8192);
			m_packet.clear();
			m_last_action = 0;
			m_key = 0;
		}
	};

	struct FileEntry
	{
		BYTE m_inpk2;
		DWORD m_id;
		DWORD m_filesize;
		std::string m_filename;
		std::string m_filepath;
		std::vector<char> m_data;

		FileEntry(DWORD id, const std::string & filename, std::string & filepath, DWORD filesize, BYTE unknown)
			: m_id(id), m_filesize(filesize), m_filename(filename), m_filepath(filepath), m_inpk2(unknown)
		{ }

		FileEntry()
			: m_id(0), m_filesize(0), m_inpk2(0)
		{ }
	};

	std::map<int, ClientContext *> m_current_clients;
	std::map<int, FileEntry> m_server_files;
	std::map<int, std::vector<int>> m_version_files;
	int m_file_index;

private:
	void OnSessionStart(int sid, const char * const remoteHost, unsigned short remotePort, const char * const localHost, unsigned short localPort, int key)
	{
		if(key == -1)
		{
			ClientContext * client_context = new ClientContext(sid);
			client_context->m_security.SetSecurityMode(0x0E);
			client_context->m_security.GenerateHandshake();
			client_context->m_key = key;
			m_current_clients.insert(std::make_pair(sid, client_context));
			Network::PostRead(sid, client_context->m_security.GetNextReadSize(0, 0), true);
			Send(client_context, client_context->m_security.BuildHandshakePacket(), false);
		}
		else if(key == -2)
		{
			ClientContext * client_context = new ClientContext(sid);
			client_context->m_security.SetSecurityMode(0x0E);
			client_context->m_security.GenerateHandshake();
			client_context->m_key = key;
			m_current_clients.insert(std::make_pair(sid, client_context));
			Network::PostRead(sid, client_context->m_security.GetNextReadSize(0, 0), true);
			Send(client_context, client_context->m_security.BuildHandshakePacket(), false);
		}
		else
		{
			PostError(sid, -1, "Invalid key (%i)\n", key);
		}
	}

	void OnSessionRead(int sid, const char * const buffer, int size)
	{
		std::map<int, ClientContext *>::iterator itr = m_current_clients.find(sid);
		if(itr == m_current_clients.end())
		{
			PostError(sid, -1, "Unknown sid (%i)\n", sid);
			return;
		}
		ClientContext * client_context = itr->second;
		std::copy(buffer, buffer + size, std::back_inserter(client_context->m_packet));
		size_t readSize = client_context->m_security.GetNextReadSize(&client_context->m_packet[0], client_context->m_packet.size());
		if(readSize)
		{
			Network::PostRead(sid, readSize, true);
			return;
		}
		size_t readCount = client_context->m_security.GetReadProcessCount(&client_context->m_packet[0], client_context->m_packet.size());
		if(readCount == 0)
		{
			Network::PostError(sid, -1, "GetReadProcessCount returned 0.");
			return;
		}
		std::pair<int, std::vector<char>> result = client_context->m_security.ExtractPacket(&client_context->m_packet[0], client_context->m_packet.size());
		client_context->m_packet.clear();
		if(result.first < 0)
		{
			Network::PostError(sid, -1, "ExtractPacket returned an error (%i).", result.first);
			return;
		}
		HandlePacket(client_context, result.second, (result.first == 1));
		Network::PostRead(sid, client_context->m_security.GetNextReadSize(0, 0), true);
	}

	void OnSessionWrite(int sid, const char * const buffer, int size)
	{
	}

	void OnSessionError(int sid, int error, const char * msg)
	{
		printf("%s(%i, %i, %s)\n", __FUNCTION__, sid, error, strlen(msg) ? msg : "\"\"");
		std::map<int, ClientContext *>::iterator itr = m_current_clients.find(sid);
		if(itr != m_current_clients.end())
		{
			delete itr->second;
			m_current_clients.erase(itr);
		}
	}

	void OnPoll()
	{
	}

	void OnShutdown()
	{
	}

	void OnLog(const char * const file, int line, const char * const func, const char * const text)
	{
	}

private:
	void SendPacket(ClientContext * client_context, WORD opcode, StreamBuilder & data, bool encrypted)
	{
		StreamBuilder builder;
		builder.Write<WORD>(data.GetCount());
		builder.Write<WORD>(opcode);
		builder.Write<WORD>(0);
		builder.WriteArray<char>(data.GetStream(), data.GetCount());
		Send(client_context, builder.ToVector(), encrypted);
	}

	void SendPacket(ClientContext * client_context, WORD opcode, std::vector<char> & data, bool encrypted)
	{
		StreamBuilder builder;
		builder.Write<WORD>(data.size());
		builder.Write<WORD>(opcode);
		builder.Write<WORD>(0);
		builder.WriteArray<char>(&data[0], data.size());
		Send(client_context, builder.ToVector(), encrypted);
	}

	void Send(ClientContext * client_context, std::vector<char> output, bool encrypted)
	{
		printf("[S->C][%i][%.4X][%i bytes]%s\n", client_context->m_sid, *(unsigned short *)&output[2], *(unsigned short *)&output[0], encrypted ? "[Enc]" : "");
		printf("%s\n\n", DumpToString(&output[0] + 6, output.size() - 6).c_str());

		output = client_context->m_security.FormatS2CPacket(&output[0], output.size(), encrypted);

		PostWrite(client_context->m_sid, &output[0], output.size());

		client_context->m_last_action = GetTickCount();
	}

	bool HandlePacket(ClientContext * client_context, std::vector<char> & packet, bool encrypted)
	{
		StreamReader reader(&packet[0], packet.size());

		WORD size = reader.Read<WORD>();
		WORD opcode = reader.Read<WORD>();
		WORD security = reader.Read<WORD>();

		printf("[C->S][%i][%.4X][%i bytes]\n", client_context->m_sid, opcode, size);
		printf("%s\n\n", DumpToString(reader.GetCurStream(), reader.GetCurCount()).c_str());

		if(opcode == 0x5000)
		{
			if(client_context->m_security.Handshake(&packet[0], packet.size()) == false)
			{
				Network::PostError(client_context->m_sid, 1, "Handshake returned false.");
				return false;
			}
			packet = client_context->m_security.BuildHandshakePacket();
			Send(client_context, packet, false);
			return true;
		}

		if(opcode == 0x2001)
		{
			WORD len = reader.Read<WORD>();
			std::string name;
			name.resize(len);
			reader.ReadArray(&name[0], len);
			BYTE type = reader.Read<BYTE>();

			if(client_context->m_key == -1)
			{
				StreamBuilder builder;
				builder.Write<WORD>(14);
				builder.WriteArray<char>("GatewayServer", 14);
				builder.Write<BYTE>(0);
				SendPacket(client_context, 0x2001, builder, false);

				builder.Clear();

				builder.Write<BYTE>(1);
				builder.Write<WORD>(1);
				builder.Write<WORD>(0x2005);
				SendPacket(client_context, 0x600D, builder, false);

				builder.Clear();

				builder.Write<BYTE>(0x00); // Data flag
				builder.Write<BYTE>(0x01);
				builder.Write<BYTE>(0x00);
				builder.Write<BYTE>(0x01);
				builder.Write<BYTE>(0x81);
				builder.Write<BYTE>(0x09);
				builder.Write<BYTE>(0x05);
				builder.Write<BYTE>(0x00);
				builder.Write<BYTE>(0x00);
				builder.Write<BYTE>(0x00);
				builder.Write<BYTE>(0x02);
				SendPacket(client_context, 0x600D, builder, false);

				builder.Clear();

				builder.Write<BYTE>(1);
				builder.Write<WORD>(1);
				builder.Write<WORD>(0x6005);
				SendPacket(client_context, 0x600D, builder, false);

				builder.Clear();

				builder.Write<BYTE>(0x00); // Data flag
				builder.Write<BYTE>(0x03);
				builder.Write<BYTE>(0x00);
				builder.Write<BYTE>(0x02);
				builder.Write<BYTE>(0x00);
				builder.Write<BYTE>(0x02);
				SendPacket(client_context, 0x600D, builder, false);
			}
			else if(client_context->m_key == -2)
			{
				StreamBuilder builder;
				builder.Write<WORD>(14);
				builder.WriteArray<char>("DownloadServer", 14);
				builder.Write<BYTE>(0);
				SendPacket(client_context, 0x2001, builder, false);
			}

			return true;
		}

		if(opcode == 0x6104)
		{
			StreamBuilder builder;

			builder.Write<BYTE>(1);
			builder.Write<WORD>(1);
			builder.Write<WORD>(0xA104);
			SendPacket(client_context, 0x600D, builder, false);

			builder.Clear();

			builder.Write<BYTE>(0x00); // Data flag
			builder.Write<BYTE>(0x00);
			SendPacket(client_context, 0x600D, builder, false);

			return true;
		}

		if(opcode == 0x6100)
		{
			BYTE locale = reader.Read<BYTE>();
			WORD len = reader.Read<WORD>();
			std::string name;
			name.resize(len);
			reader.ReadArray(&name[0], len);
			DWORD version = reader.Read<DWORD>();

			if(version == current_version)
			{
				StreamBuilder builder;
				builder.Write<BYTE>(1);
				builder.Write<WORD>(1);
				builder.Write<WORD>(0xA100);
				SendPacket(client_context, 0x600D, builder, false);

				builder.Clear();

				builder.Write<BYTE>(0);
				builder.Write<BYTE>(1);
				SendPacket(client_context, 0x600D, builder, false);
			}

			// Not checking these so we can patch backwards and forwards as needed
			// for testing purposes.

			/*
			else if(version > current_version) // Too new
			{
				StreamBuilder builder;
				builder.Write<BYTE>(1);
				builder.Write<WORD>(1);
				builder.Write<WORD>(0xA100);
				SendPacket(client_context, 0x600D, builder, false);

				builder.Clear();

				builder.Write<BYTE>(0);
				builder.Write<BYTE>(2);
				builder.Write<BYTE>(1);
				SendPacket(client_context, 0x600D, builder, false);
			}
			else if(m_version_files.find(version + 1) == m_version_files.end() ) // Too old
			{
				StreamBuilder builder;
				builder.Write<BYTE>(1);
				builder.Write<WORD>(1);
				builder.Write<WORD>(0xA100);
				SendPacket(client_context, 0x600D, builder, false);

				builder.Clear();

				builder.Write<BYTE>(0);
				builder.Write<BYTE>(2);
				builder.Write<BYTE>(5);
				SendPacket(client_context, 0x600D, builder, false);
			}
			*/
			else
			{
				StreamBuilder builder;
				builder.Write<BYTE>(1);
				builder.Write<WORD>(1);
				builder.Write<WORD>(0xA100);
				SendPacket(client_context, 0x600D, builder, false);

				builder.Clear();

				builder.Write<BYTE>(0);
				builder.Write<BYTE>(2);
				builder.Write<BYTE>(2);
				builder.Write<WORD>(9);
				builder.WriteArray<char>("127.0.0.1", 9);
				builder.Write<WORD>(16002);
				builder.Write<DWORD>(current_version);

				// TODO: A real DownloadServer would need to implement proper 0x600D building
				// but for this simple demo, I'll just set it up to assume only packet is needed.
				//
				// We must have keep incremental version folders with this design!
				//
				// DownloadServerDemo might cause corruption if anything isn't correct. This is only a simple
				// implementation to show the concepts, not to provide a full implementation.
				//

				// Temp condition so we can patch back and forth easily, this won't be in a real server!
				if(version > current_version)
				{
					version = current_version - 1;
				}

				for(size_t x = version + 1; x <= current_version; ++x)
				{
					std::map<int, std::vector<int>>::iterator itr = m_version_files.find(x);
					if(itr == m_version_files.end())
						continue;
					for(size_t y = 0; y < itr->second.size(); ++y)
					{
						std::map<int, FileEntry>::iterator itr2 = m_server_files.find(itr->second[y]);
						if(itr2 == m_server_files.end())
							continue;

						FileEntry & fileEntry = itr2->second;

						builder.Write<BYTE>(1); // new file
						builder.Write<DWORD>(fileEntry.m_id);
						builder.Write<WORD>(fileEntry.m_filename.size());
						builder.WriteArray<char>(fileEntry.m_filename.c_str(), fileEntry.m_filename.size());
						builder.Write<WORD>(fileEntry.m_filepath.size());
						builder.WriteArray<char>(fileEntry.m_filepath.c_str(), fileEntry.m_filepath.size());
						builder.Write<DWORD>(fileEntry.m_filesize);
						builder.Write<BYTE>(fileEntry.m_inpk2);
					}
				}
				builder.Write<BYTE>(0);
				SendPacket(client_context, 0x600D, builder, false);
			}
		}

		if(opcode == 0x6004) // Client requests a specific file
		{
			DWORD id = reader.Read<DWORD>();
			DWORD unk2 = reader.Read<DWORD>(); // Might be the high word of the id, that's just not implemented

			std::map<int, FileEntry>::iterator itr2 = m_server_files.find(id);
			FileEntry & fileEntry = itr2->second; // Real server needs to handle invalid files!

			// The logic is simple, we send as many 0x1001 packets as needed containing the data.
			// Since the max packet size is 4096 total bytes, - 6 bytes header, that leaves us with
			// 4090 bytes of data. NOTE: My networking code handles packet queuing and dispatching
			// sends as needed. You cannot simply call WinSocks send(..) without checking to see
			// how many bytes were actually sent.

			StreamBuilder builder;
			size_t offset = 0;
			int input_data_size = fileEntry.m_filesize;
			unsigned char * input_data_ptr = (unsigned char *)&fileEntry.m_data[0];
			while(input_data_size > 0)
			{
				if(input_data_size >= 4090)
				{
					builder.WriteArray<unsigned char>(input_data_ptr + offset, 4090);
					offset += 4090;
					input_data_size -= 4090;
				}
				else
				{
					builder.WriteArray<unsigned char>(input_data_ptr + offset, input_data_size);
					offset += input_data_size;
					input_data_size -= input_data_size;
				}
				SendPacket(client_context, 0x1001, builder, false);
				builder.Clear();
			}
			builder.Clear();

			// Queue up the packet that tells the client the file has been downloaded.
			builder.Write<BYTE>(0x01);
			SendPacket(client_context, 0xA004, builder, false);
		}

		return true;
	}

public:
	DownloadServer()
	{
		m_file_index = 0;
	}

	~DownloadServer()
	{
	}

	int Run(int argc, char * argv[])
	{
		bool error = false;

		if(argc != 2)
		{
			printf("Usage:\n\tDownloadServerDemo <patch folder>\n");
			return -1;
		}

		fs::path full_path(fs::initial_path<fs::path>());

		if(PostAccept("127.0.0.1", 16000, -1) == false)
			error = true;

		if(PostAccept("127.0.0.1", 16002, -2) == false)
			error = true;

		if(!error)
		{
			// Somewhat messy logic, but basically we get a list of all version folders first.
			// Then, we collect all files for that version and store it into a second list.
			// This way, we can associate files with specific versions. Versions should
			// to be sequential though unless you implement otherwise. There are many ways
			// to accomplish this logic, I just choose one.
			//
			// To implement arbitrary version changing, you can specify an empty folder.
			// This way, a 'dummy.txt' file will be created (1 file is needed at minimal)
			// so the version can be changed through a patch.
			//

			std::list<std::string> folders;
			full_path = fs::system_complete(fs::path(argv[1]));
			if(!fs::exists(full_path))
			{
				std::cout << "Error: Path not found: " << full_path.file_string() << std::endl;
				return -1;
			}
			get_top_level_folders(full_path, folders);
			std::list<std::string>::iterator itr = folders.begin();
			while(itr != folders.end())
			{
				std::list<std::string> files;
				std::string folder = (*itr);
				int version = atoi(folder.substr(1 + folder.find_last_of("\\//")).c_str());
				recurse(fs::path(folder), files);

				std::list<std::string>::iterator itr2 = files.begin();
				while(itr2 != files.end())
				{
					std::string relpath = *itr2;
					if(!((relpath.find(".lzma") == relpath.size() - 5) || (relpath.find(".zlib") == relpath.size() - 5)))
					{
						++itr2;
						continue;
					}

					relpath.erase(relpath.begin(), 1 + relpath.begin() + folder.size());

					size_t ind = 0;

					std::string pth;
					ind = relpath.find_last_of("\\/");
					if(ind != std::string::npos)
					{
						pth = relpath.substr(0, ind);
					}
					ind++;

					std::string nme;
					nme = relpath.substr(ind);
					nme = nme.substr(0, nme.size() - 5);

					FileEntry entry;
					entry.m_id = ++m_file_index;
					entry.m_inpk2 = 1; // hard coded for now, another system needs to be setup to distinguish this
					entry.m_filename = nme;
					entry.m_filepath = pth;

					FILE * inFile = 0;
					errno_t err = 0;
					if((err = fopen_s(&inFile, (*itr2).c_str(), "rb")) || inFile == NULL)
					{
						printf("Could not open the input file: [%s]\n", (*itr2).c_str());
						return -1;
					}

					fseek(inFile, 0, SEEK_END);
					entry.m_filesize = ftell(inFile);
					fseek(inFile, 0, SEEK_SET);
					entry.m_data.resize(entry.m_filesize + 1);
					fread(&entry.m_data[0], 1, entry.m_filesize, inFile);
					fclose(inFile);

					m_server_files.insert(std::make_pair(entry.m_id, entry));
					m_version_files[version].push_back(entry.m_id);

					++itr2;
				}
				++itr;
			}

			if(m_version_files.empty()) // We need at least one file to patch, so we will create an empty text file
			{
				// lzma (isro, rsro)
				BYTE data[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x5D, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };

				// zlib (ksro, csro)
				//BYTE data[] = { 0x00, 0x00, 0x00, 0x00, 0x78, 0x9C, 0x03, 0x00, 0x00, 0x00, 0x00, 0x01 };

				FileEntry entry;
				entry.m_filename = "dummy.txt";
				entry.m_filepath = "";
				entry.m_filesize = sizeof(data);
				entry.m_id = 1;
				entry.m_inpk2 = 0;
				std::copy(data, data + sizeof(data), std::back_inserter(entry.m_data));
				m_server_files.insert(std::make_pair(entry.m_id, entry));
				m_version_files[current_version].push_back(entry.m_id);
			}

			while(!_kbhit())
			{
				Poll();
				Sleep(1);
			}
		}
		Shutdown();

		if(error)
			return -1;

		return 0;
	}
};

int main(int argc, char * argv[])
{
	return DownloadServer().Run(argc, argv);
}
As a sort of addendum, the LZMA SDK I am using can be found . I used the 4.65 version. As for the zlib code, you can get that from the main zlib site I believe, but I can't remember which version specifically it was, so you'd have to check the source files. There is no code that imposes any restrictive limitations on how you use my work, so it's pretty much "public domain" (except zlib, which has the zlib license, and boost has its own simple license too).

Now for some other comments. The project makes use of the Common code from my project, which I have not made a formal thread on these forums about yet. Boost is required to compile most of the code and I use Visual Studio 2008.

Right now, there is not much documentation to the code since it's all fairly new and still being revised over time. However, I wanted to go ahead and share my latest findings through this project since this information has never been fully explored and released. As mentioned before, most people won't ever have a need for this information or project, but it's another piece of the Silkroad development puzzle.

The last section of this post I want to add in some information about how the DownloadServer works in conjunction with the GatewayServer. This should make following the code a bit easier in DownloadServerDemo and SilkroadPatchDownloader.

1. Client connects to GatewayServer, GatewayServer sends handshake, Client handshakes and responds.

2. Client sends its identify packet to the GatewayServer (0x2001, "SR_Client"), GatewayServer sends its identify packet to the Client (0x2001, "GatewayServer")

3a. Client sends its version packet to the GatewayServer (0x6100), GatewayServer checks version against current version and responds with 0xA100 (implemented through 0x600D, which is a 'big packet')

3b. If the version is too old or too new, 0xA100 contains a flag to tell the client this. If the version matches, then the flag tells the client it does. If there is a network problem, there is a flag to tell the client the servers are offline.

3c. If the version is outdated, but within the range of the patches stored on the server, then the 0xA100 packet contains DownloadServer information. The packet format is pretty simple for this mode:
Quote:
[XX XX] - size
[00 A1] - opcode
[00 00] - security
[02] - error
[02] - updates available
[XX XX] - ip length
[XX ... XXX] - ip
[XX XX] - port
[XX XX XX XX] - current version

[XX] - new file flag? 1 = file, 0 = done
[XX XX XX XX] - file id, needed to send to the download server
[XX XX] - file name length
[XX ... XXX] - file name
[XX XX] - file path length
[XX ... XXX] - file path
[XX XX XX XX] - file size
[XX] - in PK2 flag

...

[00] - new file flag? 1 = file, 0 = done
4. The client connects to the DownloadServer just like it would to the WorldServer and repeats the 1st and 2nd steps.

5. Now comes the patching part. The client sends a file request through the 0x6004 packet, which simply is a DWORD of the file id followed by the DWORD0. I'd assume they were supporting 64bit ints, but Silkroad.exe only sends a DWORD.

6. The DownloadServer checks the id to obtain the file entry on the server and then sends the file in 4kb chunks, which is the max packet size, to the client through the 0x1001 packet. When all chunks have been sent, the 0xA004 packet is sent with a status code to signal success or failure (I only handle success, I've not looked into different errors yet).

7. The client buffers these 0x1001 packets until the 0xA004 packet is received. On success, it will decompress the file and then patch the PK2 using the GFXFileManager.dll as my original PK2Tools did. After the file has been patched, the client sends the next file to be downloaded through 0x6004 until all files have been patched.

8. After all files have been patched, Silkroad.exe runs replacer.exe with a special command line to patch media.pk2 with the latest version. Patches require at least one file at all times and the files have to be compressed using the format expected (lzma for rsro and isro, zlib for ksro and csro).

That wraps up how the DownloadServer works. Review the code to see a simple implementation of one. There's still a lot of work that would need to be done to make it ready for general use. Once you understand the concepts though, you can setup your own versioning system to make life easier on yourself.

Now, for emu projects, the tools do all the work of compressing and compressing, so only simple file loading code has to be implemented. It might be advisable to look into creating a tool that simply scans a folder for compressed files and generates a version 'package' list for the DownloadServer to use. Alternatively, you could write a tool that generates all of the packets that need to be sent and load them so you don't have to have your DownloadServer do any extra work. There's really a lot of different directions that you can go with it.

Phew! There's a lot of information to digest here as well as a lot of code to go through and understand. Don't expect to be able to understand everything right away. Make use of a Silkroad proxy if you need to trace packets or enable the debugging output in my tools through code to see everything step by step in real time.

Since there is so much here, I might have left out a few details or forgot to mention something, so if anything needs clarification, feel free to ask! I will be steadily working on more improved versions over the upcoming weeks while I work on other things.

Download: Attached (complete code + some binaries)
Virus Scan: ,

Enjoy!
Attached Files
File Type: zip SilkroadPatchUtilites_0_1.zip (4.24 MB, 1155 views)
pushedx is offline  
Thanks
58 Users
Old 07/08/2010, 00:07   #2
 
InvincibleNoOB's Avatar
 
elite*gold: 20
Join Date: Mar 2007
Posts: 4,277
Received Thanks: 2,990
#Approved.

Valuable information given on a plate. I'm grateful!
InvincibleNoOB is offline  
Old 07/08/2010, 14:41   #3
 
Epic_Rage's Avatar
 
elite*gold: 0
Join Date: Apr 2009
Posts: 642
Received Thanks: 377
Quote:
Originally Posted by InvincibleNoOB View Post
#Approved.

Valuable information given on a plate. I'm grateful!
Agreed.

This is guaranteed to revolutionize emulators/pservers ^^

Thanks alot drew
Epic_Rage is offline  
Old 07/08/2010, 16:44   #4
 
elite*gold: 0
Join Date: Apr 2010
Posts: 100
Received Thanks: 152
Very weird and difficult, but very nice thread.
ZSZC is offline  
Old 07/08/2010, 18:22   #5
 
Kape7's Avatar
 
elite*gold: 0
Join Date: Dec 2007
Posts: 3,210
Received Thanks: 6,292
Quote:
Originally Posted by ZSZC View Post
Very weird and difficult, but very nice thread.
Is easier when you already have the files compiled and ready to run, ne? XD

Nice thread indeed. Hmm now everyone is able to decompress the SMC files, so only missing thing is the database. Also is nice to have a tool which allows you to switch your silkroad version to any other you want. This have a lot of more possibilities, great ^^
Kape7 is offline  
Thanks
1 User
Old 07/08/2010, 20:27   #6
 
elite*gold: 0
Join Date: Oct 2007
Posts: 190
Received Thanks: 259
Good job..
lyzerk is offline  
Thanks
1 User
Old 07/08/2010, 21:26   #7
 
elite*gold: 0
Join Date: Sep 2009
Posts: 520
Received Thanks: 435
Very great pushedx, now the smc files working.
CraYu is offline  
Old 07/08/2010, 21:47   #8
 
Kape7's Avatar
 
elite*gold: 0
Join Date: Dec 2007
Posts: 3,210
Received Thanks: 6,292
Quote:
Originally Posted by HeavyLegend View Post
Very great pushedx, now the smc files working.
I doubt that you can make it works since the SMC.exe can't run alone, SMC_Updater.exe must run first, and for make it works you need to have the serverfiles running at the same time (and working).
I still get this annoying error:



Even with the serverfiles partially working (showing server list) and the config file well set the error keep appearing. XD
Kape7 is offline  
Old 07/08/2010, 22:18   #9
 
elite*gold: 0
Join Date: Sep 2009
Posts: 520
Received Thanks: 435
Have the same error as you, i think you must set the SMC_Updater.cfg right that it connect to your GateWayServer and DownloadServer.

EDIT: Fixed the problem now another error appear.



CraYu is offline  
Old 07/11/2010, 23:38   #10
 
elite*gold: 0
Join Date: May 2010
Posts: 268
Received Thanks: 274
Quote:
Originally Posted by kaperucito View Post
I doubt that you can make it works since the SMC.exe can't run alone, SMC_Updater.exe must run first, and for make it works you need to have the serverfiles running at the same time (and working).
I still get this annoying error:



Even with the serverfiles partially working (showing server list) and the config file well set the error keep appearing. XD
How you decompressed the files?
_Jefrey_ is offline  
Old 07/12/2010, 02:50   #11
 
Kape7's Avatar
 
elite*gold: 0
Join Date: Dec 2007
Posts: 3,210
Received Thanks: 6,292
Quote:
Originally Posted by HeavyLegend View Post
Have the same error as you, i think you must set the SMC_Updater.cfg right that it connect to your GateWayServer and DownloadServer.

EDIT: Fixed the problem now another error appear.



Nice, what did you put on the config file?

Quote:
How you decompressed the files?
Did you readed this thread? XD
I used a klevre's tool but them can be decompressed too with the Drew's code.
Kape7 is offline  
Old 07/12/2010, 10:36   #12
 
elite*gold: 0
Join Date: May 2010
Posts: 268
Received Thanks: 274
Quote:
Originally Posted by kaperucito View Post
Nice, what did you put on the config file?



Did you readed this thread? XD
I used a klevre's tool but them can be decompressed too with the Drew's code.
Well dude, i read it 2 times now.
But since I dont have any knowledge in programming, the tools just open and close at same time.
Do I need microsoft visual studio c++ for it?
I only understand that I need to use decompresser of ZLIB...

//EDIT:
Well now I got it decompressed, but it still wont give me the files which you got, like the config...
_Jefrey_ is offline  
Old 07/12/2010, 16:19   #13
 
Kape7's Avatar
 
elite*gold: 0
Join Date: Dec 2007
Posts: 3,210
Received Thanks: 6,292
Quote:
Originally Posted by _Jefrey_ View Post
Well dude, i read it 2 times now.
But since I dont have any knowledge in programming, the tools just open and close at same time.
Do I need microsoft visual studio c++ for it?
I only understand that I need to use decompresser of ZLIB...

//EDIT:
Well now I got it decompressed, but it still wont give me the files which you got, like the config...
You must create them manually.
Kape7 is offline  
Old 07/12/2010, 17:02   #14
 
elite*gold: 0
Join Date: May 2010
Posts: 268
Received Thanks: 274
Quote:
Originally Posted by kaperucito View Post
You must create them manually.
Ah thxx.
I decompressed them now
And also decompressed the sro_client.exe , but they give an korean error cant start client or something.Its seems like, that swsro-zszc-sjsro dont use the files.

As we see the pics in threads, last protocoll version 24.
24=1.024
_Jefrey_ is offline  
Old 07/12/2010, 17:13   #15
 
elite*gold: 0
Join Date: Sep 2009
Posts: 520
Received Thanks: 435
Nope its only the protocol of the smc, and my client working but doesnt show the start.

CraYu is offline  
Thanks
1 User
Reply


Similar Threads Similar Threads
Sro DownloadServer Demo
07/05/2010 - SRO Private Server - 5 Replies
YouTube - Silkroad Online DownloadServer Demo Credits: pushedx



All times are GMT +2. The time now is 12:45.


Powered by vBulletin®
Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
SEO by vBSEO ©2011, Crawlability, Inc.
This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

Support | Contact Us | FAQ | Advertising | Privacy Policy | Terms of Service | Abuse
Copyright ©2025 elitepvpers All Rights Reserved.