[теория] Автопатчер - Форум администраторов игровых серверов
Форум администраторов игровых серверов StormWall - Защита от DDos атак
Регистрация Мнения Справка Пользователи Календарь Все разделы прочитаны
Вернуться   Форум администраторов игровых серверов > MMO > Другие игры / Other games > Perfect World > Документация

Документация Различные статьи и мануалы по установке, настройке и редактированию серверной и клиентской части игры Perfect World.

Ответ
Опции темы
Непрочитано 28.02.2011, 06:37   #1
Изгнанные

Автор темы (Топик Стартер) [теория] Автопатчер

Вступление

Здесь я постараюсь объяснить суть работы официального патчера ПВ. Если вы пробовали бету моего апдейтера, заснифирить, углядеть или что-то еще, что вам не помогло, вам будет как минимум полезно.

Если смотреть официальную документацию, то у каждого оф-сервера есть 4 сервера для патчей. patch1— тот куда заливают непереведенные патчи китайцы, patch2 — туда наши заливают перевод, чтобы китаёзы сделали патч, patch4 — сюда китаёзы заливают непосредственно патч для тестов. Если все с этим патчем хорошо, то он перемещается с patch4 на patch3, откуда все пользователи его благополучно и скачивают.

Лично нам интересен момент когда китайцы делают патч для тестов. Т.к. тестить-то нам, пряморуким ходячим хомосапиенсам не надо, мы после него сразу на 4 пункт переходим ))

Структура

Для того, чтобы сделать патчер/патч нам нужны несколько вещей: умение делать цифровую подпись файлов с помощью связки открытого и закрытого ключа, умение подсчитывать проверочные суммы файлов с помощью алгоритма md5, умение пользоваться base64 с пользовательским словарем и знакомство с алгоритмом/либой для упаковки zlib.

Рассмотрим файлы, необходимые для обновления с обоих сторон.

Те кто пытались достучаться до сервера обновлений ру(май/ки и других)-офа, могли заметить следующию структуру:
— patch3.pwonline.ru/CPW/
—— pid
—— patcher
—— element
—— launcher
Эти папки — основа для любого клиента любой игры созданной Perfect World Ltd. Каждая такая папка, кроме pid, состоит из одной подпапки с файлами с аналогичным названием (т.е. есть папка patch3.pwonline.ru/CPW/patcher/patcher/), в которой содержаться упакованные файлы для загрузки, и файлами для проверки:
——— version — файл, в котором содержится номер текущей версии на сервере;
——— files.md5 — файл со списком всех-всех файлов клиента, с md5-суммами и цифровой подписью внизу;
——— v-X.inc, где X — разница между версией на сервере и версией клиента — файлы, в которых содержаться только пути к изменившимся/добавленным/удаленным файлам игры, их md5-сумма и цифровая подпись.
Папка pid содержит единственный файл info.

В клиенте ПВ два уровня патчера:
1) Perfect World/launcher/launcher.exe — та мини-программа, которая лезет на сервер обновлений только для того, чтобы узнать не нужно ли обновлять себя и программу из пункта 2;
2) Perfect World//patcher/patcher.exe — основной патчер. да-да, это тот, что качает кусочки упакованного содержимого pck-файлов, карты, списки серверов, словом все что в папке element.
Конфиги с номером текущей версии клиента и ошибками, произошедшими во время обновления, хранятся в корне клиента в папке Perfect World/config/.

Вариант первый, простой запуск через launcher.exe.

1) первым делом launcher.exe сверяет /pid/info с Perfect World/patcher/server/pid.ini:
— если содержимое одинаковое, то ланчер идет на пункт 2;
— если нет, то выдаст какую-то ошибку. Попробуйте кто-нибудь, я уже не помню.
2) далее, ланчер качает /patcher/version и сверяет с Perfect World/config/patcher/version.sw:
— если версия клиента отстает от серверной, то происходит процесс обновления, о котором будет сказано отдельным абзацем, а потом запуск патчера;
— если версии совпадают, то происходит запуск патчера;
— если версия на сервере меньше клиентской вылетит ошибка, смысл которой будет, что сервер временно не доступен.
3) запускается патчер. Проверяет pid, а затем он качает /element/version и сверяет с Perfect World/config/element/version.sw:
— если версия клиента отстает от серверной, то происходит процесс обновления, о котором будет сказано отдельным абзацем, а потом запуск патчера;
— если версии совпадают, то происходит запуск патчера;
— если версия на сервере меньше клиентской вылетит ошибка, смысл которой будет, что сервер временно не доступен.
Да, Ctrl-C, Ctrl-V.

Вариант второй, полная проверка и запуск через Perfect World/launcher/FixIt.bat.
1) launcher.exe запускается с ключом полной проверки FullCheck. Он проверяет все файлы с серверными (об этом так же чуть позже) и заменяет отличающиеся файлы из папки патчера и своей на серверные. Тем не менее, проверка /pid/info с Perfect World/patcher/server/pid.ini так же осуществляется!
2) запускается патчер, который поступит совершенно так же как и ланчер, только для папки elements, если нажать кнопку «Проверка».


1. Обычное обновление
Для примера будем считать, что ланчер этот этап прошел.
Как мы уже выяснили, патчер уже знает версию сервера и клиента. Посчитав разность и отставание версии клиента, он стучится к файлам v-X.inc .
Допустим, у меня свежий клиент с обновлением 73. Но сегодня нивал выпустил патч 74, поэтому патчер качает файлик http://patch3.pwonline.ru/CPW/element/v-1.inc
Его содержимое:
Код HTML:
# 73 74 118
!3007c149c0ffc0785fc21dce583ede62 /aW50ZXJmYWNlcw==/bG9hZGluZy5zdGY=
-----BEGIN ELEMENT SIGNATURE-----
iWp9dTEEyZuQFVp/DiDNdCVWJbCb6dn3rbz0CImfMfSxnp9XXawQygcS9JkBIPAw
K05VnT+rCXXKGcNrlmxZwWx5tkY2iYlCidOltJsD1YBqbt+Ck7 1v1r07rvJp9lVW
yjS0zizU0/JW3rJfofBAkIin2wQjGhSSiA+IE2PIbNc=
Переведем на понятный язык:
Код HTML:
# ClientVersion ServerVersion BytesToDownload
FLAGmd5sum PathToFile
-----BEGIN ELEMENT SIGNATURE-----
SIGNKEY
ClientVersion — версия клиент;
ServerVersion — версия сервера;
BytesToDownload — придется скачать ровно столько байт;
FLAG — состояние файла. Нам известны два типа флагов — добавлен (+), изменен (!), но, вероятно, есть и удаление;
md5sum — md5-сумма файла, перед закачкой клиент сверится, т.к. возможно вы уже пытались обновится и у вас произошел обрыв;
PathToFile — путь к файлу относительно корневой директории. Если путь начинается со слеша (/), то это путь относительно корневой директории, если нет, то относительно текущей. У первого файла слеш точно должен быть! Сам путь получается в очень забавном виде: делается base64_encode (base64.ru для опытов) для каждой папки до файла и самого файла, в массиве этих строк для каждой строки делается замена слеша (/) на минус (-), потом объединяется с помощью тех же слешей в путь. Для распакованных паков опускается pck на конце. Приведу пример: файл из пака interfaces.pck/loading.stf, который как раз в этом обновлении, патчер будет качать по адресу patch3.pwonline.ru/CPW/element/element/aW50ZXJmYWNlcw==/bG9hZGluZy5zdGY=. Файл по этому адресу упакован смешанным алгоритмом с помощью zlib.
SIGNKEY — подпись файла (именно файла v-1.inc в нашем случае) с помощью закрытого ключа с серверной стороны. Эта подпись проверяется клиентом у которого свой открытый ключ (он прописан в launcher.exe и patcher.exe) до загрузки файлов, и если результат будет отрицательным — загрузки файлов не будет! Подпись осуществляется для текста с самого начала файла до конца списка файлов. После расчета подписи, патчер на сервере дописывает в конец следующее:

Код HTML:
\n-----BEGIN ELEMENT SIGNATURE-----\n
ПОДПИСЬ_РАЗБИТАЯ_ПО_64_СИМВО ЛА_НА_СТРОКУ
Каждый следующий файл (v-2.inc, например), содержит в себе все меньшие. Т.е. в файле v-10.inc содержаться все новые/модифицированные файлы из v-1, v-2, v-3, v-4, v-5 .. v-9. Что заставляет нас подписывать все файлы каждый раз, после обновления

2. Полная проверка

А тут для примера будем думать, что мы запустили ланчер с помощью FixIt (полная проверка патчера, в общем).
От автора: вы не подумайте что не хочу для element рассмотреть, просто размещать здесь 5 мегабайт текста не хочется
В этом случае, ланчер качается полный список файлов (files.md5) и проверяет отличающиеся файлы.
Сначала он скачает для себя http://patch3.pwonline.ru/CPW/launcher/files.md5
Его содержимое:
Код HTML:
# 2
0445fb1b766e098bd767285a880cd553 /Zml4aXQuYmF0
6011e90ea67d338fbb399560b5ea7f1a bGF1bmNoZXIuYm1w
f4a55cd348d4ca26ec588be3a98c5734 bGF1bmNoZXIuZXhl
90f6235a3bb787828ba01efd67ad841d c3RyaW5ndGFiLnR4dA==
e40ef2ebd497d0a15421658643bd05c6 cGFja2RsbC5kbGw=
344a159d62deb21acb18313c96b24d1c dW5pY293cy5kbGw=
-----BEGIN ELEMENT SIGNATURE-----
Z5aRDLdAR6XSCtwMYu6y3FQtINfxn2kxB2YsiaElirAJOFkbUa f7t5cB6K50yUZv
o4mIt6LgOHW4wvKrzX6zY3dVrbJu+z+fDLuRmckJbnPu8HzGLn vzgP3hnIvSHEGP
+hnpl58SkkMYaHCJXd+2bpFwOR8WFsDXlCYDx6dHI+k=
и проверит суммы, потом для патчера http://patch3.pwonline.ru/CPW/patcher/files.md5

Код HTML:
# 5
80ccae5334d030fa9da7aafa60125189 /c2VydmVy/cGlkLmluaQ==
0b9a282d2fd208716162e1292268ffbb dXBkYXRlc2VydmVyLnR4dA==
44e9d53e2a1b869d49a074a054b8c866 /c2tpbg==/aW1hZ2U=/Y29tYm8tYmcuYm1w
797bf5ee4384b5ef246dca080e1051f3 Y29tYm8tZXhwYW5kYmcuYm1w
7d12b5ae21a001a043d6a6ef482ef309 Y29tYm8tZm9jdXNlZC5ibXA=
6ee841d62f57e4ac5a533df9a86375e9 Y29tYm8taG92ZXIuYm1w
b50d2b0b6d3ab004c4ed397b332c8e5e Y29tYm8tbWFwLmJtcA==
1c491d0bd38d197c314d2d47a75117f6 Y29tYm8tc2VsZWN0ZWQuYm1w
b6eac91f513b61c4735302134f936230 Y29tYm9zbWFsbC1iZy5ibXA=
7a2fcffd7d9c39362009cc15a09f8d58 Y29tYm9zbWFsbC1leHBhbmRiZy5ibXA=
[...здесь пропущено много строк...]
9f4ec0abc9b7217ee7b26d19714eb38b OTcwMC5pbmk=
11a4017214ce0ade915d32b4c4bc561b Z2Vmb3JjZTJfNDAwLmluaQ==
0c73592438b247d9bf352e1bcfd4a09c Z2Vmb3JjZTYyMDAuaW5p
e0cf04af20b21f2d55f73b71170c47fe dG50Mi5pbmk=
-----BEGIN ELEMENT SIGNATURE-----
jTJK8aW9bZBcV9DK8syw8AUwcVMAiJKl2Gy0fzi4AyEpzux0fp qm4vFuzoJlBAIz
rfs8k/o0mBaNOJOcQTDyZULOsR+H2sSzJPISHUXj19Og+RJNdY34B3OM 5ZwEBF74
QXT4pP3CkGa/eJ4ATVj004dq5OWy+8bN1G0H2oDKt2U=
проверит суммы и обновит файлы патчера, если необходимо.
отличие от файлов v-X.inc:
1) присутствует только версия на сервере;
2) нет флагов;
3) присутствуют все файлы.
Последнее — особенно важно. Чисто теоретически, можно написать программу, которая будет выкачивать ЦЕЛИКОМ клинт с сервера обновлений! Т.е. скачали три полных списка (launcher, patcher + element — http://patch3.pwonline.ru/CPW/element/files.md5), и распаковали в нужном порядке

О .cup файлах
Это мини-патч, в каком-то виде. Фактически, внутри этого пака таже самая структура, что и на сервере, только там есть содержимое исключительно папки element. Опытов с ними я не проводил, но знаю, что обычно они содержат ограниченное количество изменений (т.е. внутри нет files.md5 — оно и понятно )

Собственно, не знаю что еще можно рассказать о структуре, так что перейдем к программистской части.

Программируем?
Вроде все просто. А ведь так и есть. Итак, приведу сложные моменты в реализации всего:
1) понять смешанный алгоритм упаковки;
2) понять какой алгоритм подписи и как подписывать;
3) найти в клиенте открытый ключ и заменить на свой (если не знаете почему, погуглите rsa private public key).

Приведу примеры решений описанных выше проблем.
Важно! Все примеры написаны на java и являются кусками нескольких классов! Возможно я скопирую не все, что нужно, но смысл работы понять можно.

1. упаковка отличается только тем, что в начало упакованного архива дописывается размер файла до упаковки

Код HTML:
/**
	 * Интерфейс упаковщика для строковых входных переменных.
	 * @param input откуда
	 * @param output куда
	 * @throws Exception
	 */
	public void doPakage(String input, String output) throws Exception {
		doPakage(new File(input),new File(output));
	}
	
	/**
	 * Упаковщик файлов.
	 * @param fileInput откуда
	 * @param fileOutput куда
	 * @throws FileNotFoundException 
	 * @throws IOException 
	 */
	public void doPakage(File fileInput, File fileOutput) throws FileNotFoundException, IOException {
		// создадим новый файл, должно просто стереть старый.
		fileOutput.createNewFile();
		
		InputStream fileInputStream = new FileInputStream(fileInput);
		OutputStream fileOutputStream = new FileOutputStream(fileOutput);

		byte[] buffer = new byte[(int)fileInput.length()]; 
		byte[] buffer2 = new byte[(int)fileInput.length()]; 
		
		ByteBuffer temp = ByteBuffer.allocate(4); 
		temp.order(ByteOrder.LITTLE_ENDIAN);
		temp.putInt((int)fileInput.length());
		
		int compressedDataLength = (int)fileInput.length(); 
		
		fileInputStream.read(buffer);
		
		// упаковка файла
		Deflater compresser = new Deflater(Deflater.BEST_SPEED,false); 
		compresser.setInput(buffer); 
		compresser.finish(); 
		
		compressedDataLength = compresser.deflate(buffer2); 
		
		if(fileInput.length() <= compressedDataLength) 
			buffer2 = buffer; 
		
		fileOutputStream.write(temp.array());
		fileOutputStream.write(buffer2, 0, compressedDataLength); 
		fileOutputStream.flush(); 
		fileOutputStream.close(); 
		fileInputStream.close();
2. мы выяснили, что после каждого нового патча, необходимо заново переподписать все листы (именно все, v-X.inc и files.md5). Кусочек класса подписи:

Код HTML:
private PrivateKey privateKey;	// приватный ключ
	private PublicKey publicKey;	// паблик ключ
	
	private BigInteger modus;		// сюда мы подгрузим ключи, чтобы не приходилось каждый раз генерировать
	private BigInteger privateX;
	private BigInteger publicX;
	
	/**
	 * Надпись перед подписью.
	 */
	public String signatereText = "-----BEGIN ELEMENT SIGNATURE-----\n";

	/**
	 * Генерация ключей.
	 * @throws NoSuchAlgorithmException
	 * @throws InvalidKeySpecException
	 * @throws NoSuchProviderException
	 */
	private void doGenerate() throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchProviderException {
		KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
		SecureRandom random = SecureRandom.getInstance("SHA1PRNG", "SUN");
		keyGen.initialize(1024, random);
		KeyPair pair = keyGen.generateKeyPair();
		privateKey = pair.getPrivate();
		publicKey = pair.getPublic();
		KeyFactory kf = KeyFactory.getInstance("RSA");
		RSAPrivateKeySpec prks = kf.getKeySpec(privateKey, RSAPrivateKeySpec.class);
		modus = prks.getModulus();
		privateX = prks.getPrivateExponent();
		RSAPublicKeySpec pubks = kf.getKeySpec(publicKey, RSAPublicKeySpec.class);
		publicX = pubks.getPublicExponent();
	}
	
	/**
	 * Загрузка ключей из входных переменных.
	 * @param privateXL
	 * @param publicXL
	 * @param modusL
	 * @throws NoSuchAlgorithmException
	 * @throws InvalidKeySpecException
	 */
	private void doLoad(BigInteger privateXL, BigInteger publicXL, BigInteger modusL) throws NoSuchAlgorithmException, InvalidKeySpecException  {
		modus = modusL;
		privateX = privateXL;
		publicX = publicXL;
		KeyFactory kf = KeyFactory.getInstance("RSA");
		RSAPrivateKeySpec new_prks = new RSAPrivateKeySpec(modus, privateX);
                RSAPublicKeySpec new_pubks = new RSAPublicKeySpec(modus, publicX);
                privateKey = kf.generatePrivate(new_prks);
                publicKey = kf.generatePublic(new_pubks);
	}
	
	/**
	 * Подпись для строки what
	 * @param what
	 * @return
	 * @throws IOException
	 * @throws NoSuchAlgorithmException
	 * @throws InvalidKeyException
	 * @throws SignatureException
	 */
	public String doSignature(String what) throws IOException, NoSuchAlgorithmException, InvalidKeyException, SignatureException {
		String encryptedText;
		byte[] textBytes = what.getBytes();
		Signature dsa = Signature.getInstance("MD5withRSA"); 
		dsa.initSign(privateKey); 
		dsa.update(textBytes); 
		byte[] encryptedBytes = dsa.sign(); 
		encryptedText = CPWBase64.encodeBytes(encryptedBytes).toString();	
		return signatereText + encryptedText;
	}
	
	/**
	 * Рассчет подписи файла what и запись этой подписи в конец файла.
	 * @param what
	 * @param fos
	 * @return
	 * @throws IOException
	 * @throws NoSuchAlgorithmException
	 * @throws InvalidKeyException
	 * @throws SignatureException
	 */
	public boolean doSignature(File what, OutputStream fos) throws IOException, NoSuchAlgorithmException, InvalidKeyException, SignatureException {
		Signature dsa = Signature.getInstance("MD5withRSA"); 
		dsa.initSign(privateKey); 
		FileInputStream fis = new FileInputStream(what); 
		BufferedInputStream bufin = new BufferedInputStream(fis); 
		byte[] buffer = new byte[1024]; 
		int len; 
		while(bufin.available() != 0) { 
		    len = bufin.read(buffer); 
		    dsa.update(buffer, 0, len); 
		} 
		bufin.close(); 
		fis.close();
		byte[] realSig = dsa.sign(); 
		fos.write(signatereText.getBytes()); 
		String signature = CPWBase64.encodeBytes(realSig);
		for(int i=0; i<signature.length(); i=i+64) {
			int b=i+64;
			if(b > signature.length()) b = signature.length();
			fos.write(new String(signature.substring(i,b)+"\n").getBytes()); 
		}
		return true;
	}
Кстати, проблема хранения md5 сумм файлов, изменений версии от версии — достаточно трудоемкая, если не использовать базы данных! Поэтому я, к примеру, использую MySQL
3. на эмудеве я посоветовал просто найти соответствующее вхождение хекс-редактором и заменить на свое. Но мы же джедаи программирования, надо чтобы можно было тока кнопочку нажать ops:

ПомогЭ тык спасибо!)

Гайд пренодлежит мне!)
Просьба соблюдайте копирайты!)))
wamper вне форума Отправить сообщение для wamper с помощью ICQ Отправить сообщение для wamper с помощью Skype™ Ответить с цитированием
Сказали спасибо:
Ответ


Здесь присутствуют: 1 (пользователей: 0 , гостей: 1)
 
Опции темы

Ваши права в разделе
Вы не можете создавать новые темы
Вы не можете отвечать в темах
Вы не можете прикреплять вложения
Вы не можете редактировать свои сообщения

BB коды Вкл.
Смайлы Вкл.
[IMG] код Вкл.
HTML код Выкл.

Быстрый переход


© 2007–2024 «Форум администраторов игровых серверов»
Защита сайта от DDoS атак — StormWall
Работает на Булке неизвестной версии с переводом от zCarot
Текущее время: 14:32. Часовой пояс GMT +3.

Вверх