Форум администраторов игровых серверов

Форум администраторов игровых серверов (https://forum.zone-game.info/TT.php)
-   Документация (https://forum.zone-game.info/forumdisplay.php?f=170)
-   -   [теория] Автопатчер (https://forum.zone-game.info/showthread.php?t=12825)

wamper 28.02.2011 06:37

[теория] Автопатчер
 
Вступление

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

Если смотреть официальную документацию, то у каждого оф-сервера есть 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. на эмудеве я посоветовал просто найти соответствующее вхождение хекс-редактором и заменить на свое. Но мы же джедаи программирования, надо чтобы можно было тока кнопочку нажать :oops:

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

Гайд пренодлежит мне!)
Просьба соблюдайте копирайты!)));)


Текущее время: 23:04. Часовой пояс GMT +3.

Powered by vBulletin® Version 3.8.6
Copyright ©2000 - 2022, Jelsoft Enterprises Ltd. Перевод: zCarot