Текущий архив: 2005.10.02;
Скачать: CL | DM;
Вниз
Протокол обмена между клиентом и сервером Найти похожие ветки
← →
Gek1 (2005-05-18 18:30) [0]Имееться ClientSocket, работающий в неблокирующим режиме.
Протокол обмена устроен так, что пакеты уходящие от сервера сжимаются алгоритмом Хаффмана. Каждый пакет имеет разную длинну.
Как известно - по пути пакеты могут резаться и склеиватся. Но получается что расжатый Хаффманом кусок пакета даст совсем другие данные. Практически всегда эти первая часть данных будет не поврежденна, что даст мне понять что за пакет идет и я смогу дождатся пока прийдет его полная длинна.
Но иногда рассжатый кусочек пакета настолько искажается, что мой алгоритм дает збои.
Сам протокол обмена я поменять не могу. Он придуман не мною и очень давно. Сервер тоже написан не мною. Я написал всего лишь клиент для сервера.
Возникает вопрос:
Можно ли всетаки как-то узнать длинну целого пакета, который отправлял сервер?
Мастера, подскажите пожалуйста что можно сделать в данной ситуации?
P.S. если понадобится код - приведу.
← →
Polevi © (2005-05-18 20:05) [1]напиши промежуточный сервис-прокси, отправляй данне через него
к каждому приходящему пакету прилепляй размер и отправляй куда надо
← →
Polevi © (2005-05-18 20:08) [2]стоп, не внимательно прочел
если данные отправляет сервер, тогда сервис должен работать на стороне сервера и подключаться к серверу надо через него
← →
Digitman © (2005-05-19 08:11) [3]
> Как известно - по пути пакеты могут резаться и склеиватся
тебя это заботить никак не должно
ТСР - протокол с гарантированной доставкой : поток данных, отправляемый передатчиком, гарантированно доставляется приемнику точно в той же последовательности, в какой он формируется на передающей стороне.
> работающий в неблокирующим режиме
есть подозрение, что у тебя ошибки в алгоритме клиента
← →
Slym © (2005-05-19 10:38) [4]Что ты имеешь ввиду под "пакеты уходящие от сервера"?
TCP пакеты или пакеты протокол обмена...
Сжимается каждый пакет отдельно, или весь поток?
Если сжимается поток то и приходящие пакеты должны распаковываться с учетом поточности...
← →
Gek1 (2005-05-19 12:30) [5]
> тогда сервис должен работать на стороне сервера
К сожалению никаких вещей на сервере производить нельзя. Сервер - будем считать черным ящиком, который никому не доступен физически.
> ТСР - протокол с гарантированной доставкой : поток данных,
> отправляемый передатчиком, гарантированно доставляется приемнику
> точно в той же последовательности, в какой он формируется
> на передающей стороне.
Я не говорю что TCP - протокол неправильно работает. Я имел ввиду другое, что когда я принимаю куски пакетов, порезанные TCP - протоколом, и пытаюсь расжать - там выходит проблема.
> Сжимается каждый пакет отдельно, или весь поток?
Сжимается поток данных. Причем в поток данных может входить один пакет или же несколько пакетов.
Прозьба не путать пакет TCP протокола с "этим" пакетом.
Под пакетом от сервера я имею ввиду некий тип данных, который дает мне полную информацию о определенной ситуации.
> что у тебя ошибки в алгоритме клиента
Сам подозревал. уже не впервый раз все проверял и с нуля переписывал - ошибок небыло обнаружено, а результат один и тот же.
Вот мой код:
procedure TCharacter.GameClientOnRead(Sender: TObject; Socket: TCustomWinSocket);
{Принимаем от Server-а некие данные}
var i,c,index : Integer;
SHex : String;
TempBuf : Parray;
TempBufLen : Integer;
MD5Out : PArray;
PacketLen : Integer;
begin
{ fIncomingBuf : PArray;
fIncomingBufLen : Integer; }
{Если fIncomingBufLen меньше нуля то выходим дисконектом}
if fIncomingBufLen < 0 then
begin
AddtoDebugLog(GetCharName+": "+"Error: IncomingBuf < 0");
GameClient.Close;
Exit;
end;
{Принимаем данные в TempBuff и дописываем содержимое в IncomingBuff}
New(TempBuf);
TempBufLen:=Socket.ReceiveLength;
try
Socket.ReceiveBuf(TempBuf^,TempBufLen);
SHex := "";
for i := 0 to TempBufLen - 1 do SHex := SHex + IntToHex(TempBuf^[i],2);
AddPacket2Debug(0,SHex);
except
AddToDebugLog(GetCharName+": "+"Error with receiving Some Data");
end;
for i := 0 to (TempBufLen-1) do
begin
fIncomingBuf^[fIncomingBufLen+i] := TempBuf^[i];
end;
fIncomingBufLen:=fIncomingBufLen+TempBufLen;
Dispose(TempBuf);
{Принятые данные добавлены в конец IncomingBuf}
{Если EncryptMetod 4, то есть еще MD5}
if fEncryptMetod = 4 then
begin
New(MD5Out);
MD5Encrypt(@MD5Obj,fIncomingBuf,MD5Out,fIncomingBufLen);
for i := 0 to fIncomingBufLen - 1 do fIncomingBuf^[i] := MD5Out^[i];
Dispose(MD5Out);
end;
{Расжимаем буфер}
PHuffDecompressLen := fIncomingBufLen * 4 + 4;
DecompressNew(fIncomingBuf,PHuffDecompress,PHuffDecompressLen,fIncomingBufLen);
{Если длинна PHuffDecompressLen равна нулю то выходим}
if PHuffDecompressLen <= 0 then Exit;
{Далее проверяем чтобы в буффере было N целых пакетов}
index:=0;
PacketLen := 1; {Любое положительное число}
while (index < PHuffDecompressLen) and (PacketLen > 0) do
begin
New(TempBuf);
for i := 0 to PHuffDecompressLen - (index + 1) do TempBuf^[i] := PHuffDecompress^[i+index];
TempBufLen := PHuffDecompressLen - index;
{Ф-ция GetPacketLen вернет:
0 если пакет маленький и надо еще кусок
-1 если пакет неизвестный
длинну, если длина >= пакету}
PacketLen := GetPacketLen(TempBuf,TempBufLen);
{Освободим TempBuf}
Dispose(TempBuf);
{Если длинна больше нуля, то увеличиваем индекс}
if PacketLen > 0 then index := index + PacketLen;
{Если длинна равна 0, то выходим}
if PacketLen = 0 then Exit;
{Если длинна меньше нуля, то:}
if PacketLen < 0 then
begin
{Выдаем ошибку о неизвестном пакете}
SHex := "";
for i := index to PHuffDecompressLen - 1 do SHex:= SHex + IntToHex(PHuffDecompress^[i],2);
AddtoDebugLog(GetCharName+": "+"Внимание! Обнаружен неизвестный пакет: "+SHex);
{Останавливаем соединение}
GameClient.Close;
{Выходим}
Exit;
end;
{Если длинна больше $8000, то:}
if PacketLen > $8000 then
begin
{Выдаем ошибку о неверной длинне пакета}
SHex := "";
for i := index to PHuffDecompressLen - 1 do SHex:= SHex + IntToHex(PHuffDecompress^[i],2);
AddtoDebugLog(GetCharName+": "+"Внимание! Неверная длинна пакета: "+SHex);
{Останавливаем соединение}
GameClient.Close;
{Выходим}
Exit;
end;
{Далее или у нас будет только N целых пакетов
или выйдем ранее из процедуры}
end; {End while (index < PHuffDecompressLen) and (PacketLen > 0)}
{Далее обработаем данные}
while (PHuffDecompressLen > 0) do
begin
PacketLen := GetPacketLen(PHuffDecompress,PHuffDecompressLen);
i := UnPackGamePackets(PHuffDecompress,PHuffDecompressLen);
if (i <> PacketLen) or (i = -1) then
begin
{Выдаем ошибку о неверной обработке пакета}
SHex := "";
for c := 0 to PHuffDecompressLen - 1 do SHex:= SHex + IntToHex(PHuffDecompress^[c],2);
AddtoDebugLog(GetCharName+": "+"Внимание! Неверно обработали пакет: "+SHex);
AddtoDebugLog(GetCharName+"i = "+IntToStr(i)+", PacketLen = "+IntToStr(PacketLen));
{Останавливаем соединение}
GameClient.Close;
{Выходим}
Exit;
end;
{Занесем в Debug обработанный пакет}
SHex := "";
for c := 0 to PacketLen - 1 do SHex:= SHex + IntToHex(PHuffDecompress^[c],2);
AddPacket2Debug(1,SHex);
{Вырезаем занесенный пакет}
for i := 0 to PHuffDecompressLen - (PacketLen + 1) do
PHuffDecompress^[i] := PHuffDecompress^[i+PacketLen];
PHuffDecompressLen := PHuffDecompressLen - PacketLen;
end;
{После обработки N целых пакетов
необходимо сбросить накапливаемый буффер}
fIncomingBufLen := 0;
end;
← →
Polevi © (2005-05-19 12:56) [6]кошмар
← →
Polevi © (2005-05-19 13:02) [7]если я правильно понял:
сервер формирует пакет данных, длина пакета разная в зависимости от типа
пакет сжимается и отправляется клиенту
правильность декомпресии возможна только в том случае если на вход декомпрессора подается сжатый пакет целиком - не больше и не меньше, в противном случае данные будут не правильно распакованы
поскольку клиент не знает размера сжатого пакета посланного сервером - "подать на вход декомпрессора сжатый пакет целиком" он не в состоянии
задача не имеет решения, кроме чегонибудь кривого типа таймаута.. да и то только в том случае если очередной пакет возвращается сервером по запросу
← →
Gek1 (2005-05-19 13:29) [8]
> сервер формирует пакет данных, длина пакета разная в зависимости
> от типа
> пакет сжимается и отправляется клиенту
Сервер формирует цепочку N целых пакетов данных (пакет может быть один, а может и быть несколько в цепочке)
Далее все это дело сервер сжимает и отправляет.
> правильность декомпресии возможна только в том случае если
> на вход декомпрессора подается сжатый пакет целиком - не
> больше и не меньше, в противном случае данные будут не правильно
> распакованы
абсолютно верно.
> поскольку клиент не знает размера сжатого пакета посланного
> сервером - "подать на вход декомпрессора сжатый пакет целиком"
> он не в состоянии
Вот если какието средства, чтобы узнать полную длинну сжатых данных, не трогая сервер?
> задача не имеет решения, кроме чегонибудь кривого типа таймаута..
> да и то только в том случае если очередной пакет возвращается
> сервером по запросу
Таймаут отпадает. Пакет может быть один.... маленьким ... и на его приход необходимо немедленно ответить.
Все таки есть какоето решение, но как это сделать - я не могу понять. Разработчики сервера сделали клиент давно и он рабочий. Узнать в чем изюминка этого алгоритма - невозможно. Вот у вас, Мастера, спрашиваю может у вас есть идеи как определить эту изюминку?
← →
Digitman © (2005-05-19 13:33) [9]
> Socket.ReceiveBuf(TempBuf^,TempBufLen);
вот и первая же ошибка , грубая причем
ReceiveBuf - это функция !
и возвращает она число РЕАЛЬНО прочитанных указанный в буфер байт, которое вовсе НЕ обязано быть точно равно требуемому значению в TempBufLen (т.е. результат ф-ции - число, меньшее или равное значению 2-го параметра)
а дальше смотреть твой код даже нет смысла..
← →
Маркони (2005-05-19 13:35) [10]Итого опишу задачу:
Сервер посылает блок данных большой, разом, одним куском. Клиент при приеме почему то получает эти данные кусками, хотя сервер ничего не режет а посылает одним блоком. Для правильной обработки данных Клиентом ему надо получить весь блок данных, а потом обработать. Вопрос... каким образом можно определить сколько данных надо принять и надо ли их дальше принимать если сервер послал целый пакет а клиент принимает его кусками... Таким пакетов может быть много так что надо как то определить это всё куски одно и того же пакета или это новый пакет.
← →
Gek1 (2005-05-19 13:40) [11]
> и возвращает она число РЕАЛЬНО прочитанных указанный в буфер
> байт, которое вовсе НЕ обязано быть точно равно требуемому
> значению в TempBufLen (т.е. результат ф-ции - число, меньшее
> или равное значению 2-го параметра)
Проверка стояла и я ее убрал когда переделывал алгоритм в очередной раз ...
По наблюдениям скажу что были проблемы и раньше, когда стояла проверка и ниразу не выловил чтобы ReceiveBuf() <> ReceiveLength.
Спасибо, что подметил... щас доделаю... Но почему то я уверен почти на 100% что дело не в ней.
← →
Polevi © (2005-05-19 13:48) [12]дело не в данной ошибке
клиенту просто недостаточно данных для реализации
ты уверен что сжатые пакеты не предваряются каким либо заголовком, содержащим размер ?
← →
Маркони (2005-05-19 13:54) [13]Я описал проблему так как есть +) Ты можешь вообще забыть про сжатие и тд итп представь что это один блок даных , чтоб его обработать надо получить его весь.
← →
Gek1 (2005-05-19 14:00) [14]
> ты уверен что сжатые пакеты не предваряются каким либо заголовком,
> содержащим размер
В данные размер не встаивается...
Иначе бы расжатые данные с длинной давали бы мне неверный результат всегда ....
Маркони прав.
Хочу добавить, проблема возникает очень редко. И как я заметил именно тогда, когда перегружен сервер и плохой интернет. Тогда сам TCP начинает жудко дробить данные на мелкие кусочки и посылать по кусочкам.
Как я понял надо на клиенской стороне заставить его сначала все это склеить назад и только потом дать знать что пришел пакет данных.
← →
Polevi © (2005-05-19 14:01) [15]осталось найти признак по которому определить конец блока
я понял что такого признака у тебя нет
← →
Маркони (2005-05-19 14:04) [16]А это невозможно вычеслить представь что приходит пакет содержаший только нули... но чтоб обработать его надо получить его вначале весь ...
← →
Digitman © (2005-05-19 14:12) [17]без знания тобой установленного прикл.протоколом признака размера сообщения в потоке "склеивать" что-либо бессмысленно
чтобы что-то "склеить", нужно знать полный исходный размер "неразбитого"
← →
Gek1 (2005-05-19 14:15) [18]
> Digitman © (19.05.05 14:12) [17]
Ну режет же не Серверное приложение... режет же сам протокол TCP ... он же должен передавать полную длинну? Вот как можно ее узнать?
← →
Polevi © (2005-05-19 14:22) [19]никак
← →
Digitman © (2005-05-19 14:28) [20]
> он же должен передавать полную длинну?
какую такую "длину" ?
у потока НЕТ длины ! на то он и поток ...
TCP - поточный протокол !
← →
Gek1 (2005-05-19 14:42) [21]Получается вариантов никаких...
Как понял единственный вариант - распаковывать кусок и молится, чтобы первые пару байтиков небыли испорченны.
← →
Alexander Panov © (2005-05-19 14:45) [22]Gek1 (19.05.05 14:42) [21]
Как понял единственный вариант - распаковывать кусок и молится, чтобы первые пару байтиков небыли испорченны.
Это вообще не вариант. Вернее, неправильный вариант.
Длину упакованного блока данных тебе нужно передавать в неупакованном виде.
Получив блок соответствующей длины, распаковываешь его. И всё.
← →
Polevi © (2005-05-19 14:49) [23]>Alexander Panov © (19.05.05 14:45) [22]
сервер не его
попал человек
← →
Digitman © (2005-05-19 14:49) [24]
> распаковывать кусок
какой кусок ?
← →
Gek1 (2005-05-19 14:55) [25]
> Digitman © (19.05.05 14:49) [24]
Сжатый порезаный кусок распаковываю и молусь, чтобы первые байт небыли испорченны. Бо по первому байту я определяю ID пакета, а если его длинна динамическая, то второй и третий байт - длинна. Но это все работает, когда данные не коверкаются при распаковке.
← →
Маркони (2005-05-19 14:55) [26]пакт порвался предпложим на два куска да так удачно что ... первые два пакета в запаковом виде в первой части а вторые во второй... получили кусок первый... распокавали весь из заголовка пакетов (а это первый байт пакета) получаем ага длинна 7 байт а унас там их 9 распаковалось смотрим по начало + 7 ага длинна пакета должна быть 2 байта ... то бишь нам теперь кажется... что мол мы приняли всё одним таким куском и следующий кусок будем уже воспринимать не как часть предудущего а как новый в резульатте распаковки получим мусор.... так как lzk получения правильной информация надо соединитье го было с предыдущем и распаковать весь.... вот такая хрень
← →
Digitman © (2005-05-19 15:09) [27]
> Gek1 (19.05.05 14:55) [25]
>
> > Digitman © (19.05.05 14:49) [24]
>
> Сжатый порезаный кусок распаковываю
да как ты его распакуешь, если этот кусок вполне может оказаться и в 1 байт размером ?
тебе же Alexander Panov © (19.05.05 14:45) [22] правильно говорит - прикладной протокол должен предусматривать передачу инф-ции о размере сжатых данных в несжатом виде, например, в 4-х байтах предшествующих "упаковке" ... зная это ты принимаешь сначала (хоть одним куском хоть четырьмя) эти самые 4 байта, берешь из них размер "упаковки" и далее ожидаешь/считываешь "куски" собственно "упаковки", "склеивая" их друг за другом в порядке поступления, до тех пор пока полный размер склеенных тобой "кусков" не будет точно равен тому значению, что ты получил из ранее принятых тех самых 4-х байт ... вот только ПОСЛЕ этого ты можешь смело передать склеенную тобой "упаковку" на дальнейшую обработку, как то "разжатие", "декодирование" и т.д. и т.п.
иными словами, протокол, реализованный сервером, ты не знаешь, тыкаешься как слепой, а алгоритм твой не будет работать как положено до тех пор пока ты не будешь знать ОТ и ДО этот протокол
← →
Sha © (2005-05-19 15:13) [28]Еще возможен такой вариант: сервер вставляет в поток данных спецсимволы сброса, которые сбрасывают алгоритм компрессии/декомпрессии в исходное состояние.
Это своеобразный признак конца/начала пакета.
В общем, имеет смысл детально разобраться с алгоритмом сжатия.
Кроме того, осталось невыясненным: клиент, написанный разработчиками, работает как надо?
← →
Gek1 (2005-05-19 15:45) [29]
> Digitman © (19.05.05 15:09) [27]
Проверял и пытался определить возможную длинну сжатом пакете.
Ни вначале ни в конце признаков не заметил.
Если все же судить, что в сжатом пакете должна присутствовать гдето длинна - тогда кто мне обьяснит как распакованный пакет с длинной вместе дает мне практически всегда правильные данные?
> Еще возможен такой вариант: сервер вставляет в поток данных
> спецсимволы сброса, которые сбрасывают алгоритм компрессии/декомпрессии
> в исходное состояние.
> Это своеобразный признак конца/начала пакета.
> В общем, имеет смысл детально разобраться с алгоритмом сжатия.
С дерева таблицы хаффмана данные только читаются. Не записываются. Да и вообще дерево - константа.
> Кроме того, осталось невыясненным: клиент, написанный разработчиками,
> работает как надо?
В том то и дело, что клиент, написанный разработчиками работает отлично.
Сам протокол закрыт и его алгоритм был разгадан кодерами. Сами кодеры похоже тоже сталкивались с такой проблемой. Проблема ясна (выше описана), но алгоритм как ее обойти неизвестен. Я считаю что все же есть гдето какаято изюминка. Может особенность хаффмана или еще чего либо.
Определить так мне и не удалось, поэтому спрашиваю у вас. Может у вас будет решение этой проблеммы.
← →
Sha © (2005-05-19 15:53) [30]> Сам протокол закрыт и его алгоритм был разгадан кодерами.
> Сами кодеры похоже тоже сталкивались с такой проблемой.
Тут противоречие.
← →
Gek1 (2005-05-19 16:16) [31]
> Sha © (19.05.05 15:53) [30]
Имеется виду что разгадали почти все:
Устройство пакетов, как криптуються .. различия криптации в разных версиях клиента. И тд и тп.
← →
Digitman © (2005-05-19 16:20) [32]
> Проверял и пытался определить возможную длинну сжатом пакете
при fEncryptMetod = 4 до разжатия у тебя и дело может не дойти
пытаясь декодировать произвольный шматок принятых данных, ты нарушаешь принципы, заложенные в http://www.faqs.org/rfcs/rfc1321.html
← →
Gek1 (2005-05-19 16:24) [33]
> при fEncryptMetod = 4 до разжатия у тебя и дело может не
> дойти
Это для других сервером. В моем случае я тестирую на сервере, где fEncryptMetod = 2. Поэтому это кусок все время пропускается.
← →
Digitman © (2005-05-19 16:28) [34]для работы MD5 требуется как минимум условие кратности четырем значения fIncomingBufLen
где ты это условие выполняешь ? нигде.
← →
Digitman © (2005-05-19 16:30) [35]
> я тестирую на сервере, где fEncryptMetod = 2. Поэтому это
> кусок все время пропускается
угу .. а когда будешь работать с MD5, то грабли тебе обеспечены при таком алгоритме
← →
Gek1 (2005-05-19 17:21) [36]Про MD5 спасибо, что подметил.
Но к сожалению у меня не выходит другое и все силы направленны на решение этой проблемы.
← →
Digitman © (2005-05-19 17:31) [37]что за процедура DecompressNew() ?
код можешь привести ?
← →
Gek1 (2005-05-19 17:36) [38]
Procedure DecompressNew(pInData, pOutData : Pointer; var iOut : Integer; iLen : Integer );
var mask : Word;
bitnum : Byte;
treepos : SHORT;
despos, value : Integer;
pIn : Word;
pInArray, pOutArray : PArray;
const
tree : array [0..511] of Word =
(
$0001, $0002, $0003, $0004, $0005, $0000, $0006, $0007,
$0008, $0009, $000A, $000B, $000C, $000D, $FF00, $000E,
$000F, $0010, $0011, $0012, $0013, $0014, $0015, $0016,
$FFFF, $0017, $0018, $0019, $001A, $001B, $001C, $001D,
$001E, $001F, $0020, $0021, $0022, $0023, $0024, $0025,
$0026, $0027, $0028, $FFC0, $0029, $002A, $002B, $002C,
$FFFA, $002D, $002E, $002F, $0030, $0031, $0032, $0033,
$FF89, $0034, $FFE0, $0035, $0036, $FFF2, $0037, $FFFB,
$0038, $0039, $003A, $003B, $003C, $FFFE, $003D, $003E,
$003F, $0040, $0041, $0042, $0043, $0044, $0045, $0046,
$0047, $0048, $FFCD, $0049, $004A, $004B, $004C, $004D,
$FF9B, $FF91, $FFFC, $FF9F, $004E, $004F, $FF92, $0050,
$0051, $FF8C, $0052, $0053, $0054, $FF01, $0055, $0056,
$0057, $0058, $0059, $005A, $FFF1, $FFF6, $005B, $005C,
$FFEB, $005D, $FF8B, $005E, $005F, $0060, $0061, $0062,
$0063, $0064, $FF8E, $0065, $FF97, $0066, $FFE6, $0067,
$0068, $0069, $006A, $006B, $006C, $006D, $006E, $006F,
$0070, $FFFD, $0071, $FFF9, $0072, $FF7D, $0073, $FF70,
$0074, $0075, $FFEC, $0076, $0077, $0078, $0079, $007A,
$007B, $007C, $007D, $007E, $007F, $0080, $0081, $FF9C,
$0082, $FFF8, $0083, $0084, $0085, $0086, $FF88, $0087,
$0088, $FFE1, $0089, $008A, $FF93, $FF16, $008B, $008C,
$008D, $008E, $008F, $0090, $FF90, $0091, $FFED, $0092,
$0093, $0094, $0095, $FFBE, $0096, $FF6F, $FFF3, $FFBF,
$0097, $0098, $0099, $009A, $FFE2, $009B, $009C, $009D,
$FF9D, $009E, $009F, $00A0, $00A1, $00A2, $FFE9, $00A3,
$FFE3, $00A4, $FFF5, $00A5, $00A6, $FF8D, $00A7, $00A8,
$00A9, $00AA, $FFF0, $00AB, $FFDE, $00AC, $00AD, $FF7C,
$00AE, $FF94, $00AF, $FFEA, $00B0, $FFF7, $00B1, $FFAC,
$FFEF, $FFDB, $FFE4, $00B2, $00B3, $00B4, $00B5, $00B6,
$00B7, $00B8, $00B9, $00BA, $00BB, $FF98, $00BC, $FFB2,
$00BD, $FFC3, $FFB1, $FF4E, $FFC5, $FF7A, $00BE, $FFE7,
$FFAD, $FFEE, $00BF, $FFC7, $FFBD, $00C0, $FF9E, $00C1,
$FFF4, $FFBC, $00C2, $00C3, $FFC9, $FF80, $FFE8, $FFCE,
$FFBA, $00C4, $FFA2, $FFDF, $00C5, $FF7F, $FFB6, $00C6,
$FFAE, $00C7, $FFC8, $FFA9, $FFD4, $00C8, $FF08, $00C9,
$FF5D, $FFAF, $FFCC, $FF85, $00CA, $FF8F, $FFD0, $FFD7,
$FF86, $FFD8, $00CB, $FFA6, $FFCA, $00CC, $FFAA, $FF40,
$00CD, $00CE, $00CF, $FF7E, $FFCB, $00D0, $FF7B, $FFD3,
$00D1, $00D2, $00D3, $FFA5, $00D4, $00D5, $FF96, $FFA8,
$00D6, $00D7, $00D8, $00D9, $00DA, $FFCF, $00DB, $00DC,
$00DD, $00DE, $00DF, $00E0, $00E1, $00E2, $00E3, $FF9A,
$FF60, $00E4, $FFD2, $00E5, $FF81, $00E6, $FF99, $00E7,
$00E8, $00E9, $FFC4, $00EA, $00EB, $FFB4, $00EC, $FF87,
$00ED, $FFB7, $FF6B, $00EE, $00EF, $FF95, $FFDD, $00F0,
$FFB9, $FFE5, $FFBB, $00F1, $FFA7, $FFB3, $FFC2, $FF8A,
$FFB5, $FFAB, $FFB8, $FFC6, $FFC1, $FFB0, $00F2, $FFD6,
$FF6A, $FF63, $FF75, $FF14, $FF82, $FF0D, $FF72, $FF2A,
$FF76, $FF32, $FF10, $FF6E, $FF34, $FF6D, $FF68, $FF37,
$FF1D, $FF31, $FF66, $FF2F, $FF67, $FF02, $FF50, $FF64,
$FF5B, $FF2E, $FF54, $FF47, $FF3D, $FF56, $FF18, $FF2D,
$FF25, $FF11, $FF38, $FF4F, $FF51, $FF2C, $FF0C, $FF71,
$FF0A, $FF55, $FF35, $FF23, $FF36, $FF4B, $FF53, $FF06,
$FF48, $FF5C, $FF3F, $FF26, $FF39, $FF24, $FF42, $FF07,
$FF1A, $FF27, $FF57, $FF28, $FF41, $FF3B, $FFD1, $00F3,
$00F4, $00F5, $00F6, $00F7, $FF6C, $FF61, $00F8, $00F9,
$FFA4, $FFA3, $FFA0, $FF1F, $FF69, $FFA1, $00FA, $00FB,
$FF0F, $00FC, $FF5F, $FFDC, $00FD, $00FE, $FF79, $FFD9,
$FF45, $FF84, $00FF, $FF05, $FF5E, $FF12, $FF0E, $FFDA,
$FFD5, $FF83, $FF29, $FF03, $FF74, $FF30, $FF77, $FF15,
$FF62, $FF13, $FF78, $FF33, $FF65, $FF73, $FF1C, $FF1B,
$FF2B, $FF58, $FF20, $FF3E, $FF3C, $FF1E, $FF49, $FF17,
$FF19, $FF59, $FF52, $FF43, $FF04, $FF5A, $FF3A, $FF22,
$FF44, $FF4D, $FF21, $FF4A, $FF4C, $FF46, $FF0B, $FF09
);
begin
bitnum := 8;
treepos := 0;
despos := 0;
pIn := 0;
pInArray := pInData;
pOutArray := pOutData;
value := 0;
mask := 0;
while true do
begin
if (bitnum = 8) then
begin
if (iLen = 0) then
begin
iOut := despos;
Exit;
end;
iLen := iLen - 1;
value := Byte(pInArray^[pIn]);
pIn := pIn + 1;
bitnum := 0;
mask := $80;
end;
if (value and mask) = mask then treepos := tree[treepos*2]
else treepos := tree[treepos*2+1];
mask := mask SHR 1;
bitnum := bitnum + 1;
if (treepos <= 0) then
begin
if (treepos = -256) then
begin
bitnum := 8;
treepos := 0;
continue;
end;
if (despos = iOut) then Exit;
pOutArray^[despos] := -treepos;
despos := despos + 1;
treepos := 0;
end;
end;
end;
← →
Digitman © (2005-05-19 17:51) [39]где и как инициализируется значение PHuffDecompress, передаваемое тобой параметром pOutData ?
← →
Gek1 (2005-05-19 18:07) [40]pOutArray := pOutData;
Далее в коде уже:
pOutArray^[despos] := -treepos;
← →
Digitman © (2005-05-19 18:18) [41]
> Gek1 (19.05.05 18:07) [40]
ты не понял.
я спросил, где и как у тебя инициализируется значение указательной переменной PHuffDecompress, которое ты передаешь в ф-цию DecompressNew ?
DecompressNew(fIncomingBuf,PHuffDecompress,PHuffDecompressLen,fIncomingBufLen);
ведь это выходной буфер декомпрессора !
ты же где-то память под него размером выделяешь ?
в твоем коде формирование значения PHuffDecompressLen я вижу, но нигде не вижу чтобы ты выделял память под буфер соотв.размера и записывал его адрес в переменную PHuffDecompress перед тем как передать ее параметром в ф-цию декомпрессии
← →
Gek1 (2005-05-19 18:22) [42]
> ведь это выходной буфер декомпрессора !
> ты же где-то память под него размером выделяешь ?
Как и fIncomingBuf - создаю в OnConnect
освобождаю в OnDisconnect
при этом тамже сбрасываю их длинны на ноль.
Размер буфферов 50к и 150к.
← →
Digitman © (2005-05-19 18:38) [43]
> Размер буфферов 50к и 150к.
значения от фонаря, надо понимать ...
← →
Gek1 (2005-05-20 09:37) [44]
> Digitman © (19.05.05 18:38) [43]
Я бы не сказал, что от фоноря...
Взяты были с запасом.
Первый буфер был выбран по непроверенному факту, что длинна пакета не может быть больше 0х8000.
Поэтому взял с запасом и сделал 50к.
Второй (собирающий) буфер (fIncomingBuf) был выбран из расчета густонаселенной местности. Было определен примерный обьем и расчитанно примерно максимально возможное его значение. Все эти подсчеты относительны, но взяты (на всякий пожарный) с запасом.
← →
Digitman © (2005-05-20 10:27) [45]
> Первый буфер
какой "первый" ? ты оперировать конкретными идентификаторами в своем коде можешь ?
> непроверенному факту, что длинна пакета не может быть больше
> 0х8000
т.е. от балды
> густонаселенной местности
что еще за "местность" ?
какое отношение имеет к алгоритму компрессии/декомпрессии ?
← →
Gek1 (2005-05-20 10:35) [46]
> какой "первый" ? ты оперировать конкретными идентификаторами
> в своем коде можешь ?
Могу. первый всмысле TempBuf.
> т.е. от балды
Ну чего так сразу ... я бы сказал с запасом. хотя в среднем пакеты размером в 100 байт и редко достигают нескольких кило.
Учитывая что сервер не отправляет пакеты по очереди а клеит (перед сжатием) их вместе, в случае если много информации поменялось. Например при коннекте - загружается вся обстановка и все параметры. Идет порядка нескольких десятков различных пакетов. Все они собираються в очередь ... потом сжимаются и отправляются. Из этих расчетов были выбраны буферы.
← →
Digitman © (2005-05-20 11:12) [47]
> Могу. первый всмысле TempBuf
про TempBuf вообще речи не идет.
вот TempBuf как раз можно и от балды выбрать
но лучше выбрать его кратным 4к
речь же идет о PHuffDecompress, т.е. буфере, в который Хаффман-декомпрессор записывает результат ...
← →
Gek1 (2005-05-20 11:24) [48]PHuffDecompress как и FincomingBuf имееют размер 150к.
> но лучше выбрать его кратным 4к
Это да. не подумал. Сделаю все кратными 4к.
← →
Digitman © (2005-05-20 11:26) [49]я точно не помню, но вроде бы алгоритм Хаффмана подразумевает, что для успешной декомпрессии данных нужно иметь как минимум полное дерево ... т.е. тебе из принимаемых "кусков" нужно сначала склеить как минимум блок, содержащий полные данные о дереве, прежде чем запускать код декомпрессии
← →
Gek1 (2005-05-20 11:29) [50]Digitman © (20.05.05 11:26) [49]
Именно. Иначе данные будут испорчены.
Но как я понял практически всегда первые пару байт получатся правильными.
К сожалению на это мне приходится расчитывать, потому как альтернативы не нашел.
← →
Digitman © (2005-05-20 11:31) [51]
> первые пару байт получатся правильными
и как ты интерпретируешь и используешь содержимое этих первых 2-х байт ?
← →
Gek1 (2005-05-20 11:35) [52]
> и как ты интерпретируешь и используешь содержимое этих первых
> 2-х байт ?
Смотри сам алогритм из [5]
А также ф-цию определения длинны пакета:
function TCharacter.GetPacketLen(PacketObj:PArray; Size : integer) : Integer;
{Ф-ция вернет:
0 если пакет маленький и надо еще кусок
-1 если пакет неизвестный
длинну, если длина >= пакету}
var PacketID : Word;
PacketLen : Word;
ResSize : Integer;
const
PacketLengths : array[0..255] of Word =
{Если длинна равна $00, то неизвестный пакет
Если длинна >= $8000 - значит определяем динамическую длинну}
( { $00 } $0068, $0005, $0007, $8000, $0002, $0005, $0005, $0007, $000E, $0005, $000B, $010A, $8000, $0003, $8000, $003D,
{ $10 } $00D7, $8000, $8000, $000A, $0006, $0009, $0001, $8000, $8000, $8000, $8000, $0025, $8000, $0005, $0004, $0008,
{ $20 } $0013, $0008, $0003, $001A, $0007, $0014, $0005, $0002, $0005, $0001, $0005, $0002, $0002, $0011, $000F, $000A,
{ $30 } $0005, $0001, $0002, $0002, $000A, $028D, $8000, $0008, $0007, $0009, $8000, $8000, $8000, $0002, $0025, $8000,
{ $40 } $00C9, $8000, $8000, $0229, $02C9, $0005, $8000, $000B, $0049, $005D, $0005, $0009, $8000, $8000, $0006, $0002,
{ $50 } $8000, $8000, $8000, $0002, $000C, $0001, $000B, $006E, $006A, $8000, $8000, $0004, $0002, $0049, $8000, $0031,
{ $60 } $0005, $0009, $000F, $000D, $0001, $0004, $8000, $0015, $8000, $8000, $0003, $0009, $0013, $0003, $000E, $8000,
{ $70 } $001C, $8000, $0005, $0002, $8000, $0023, $0010, $0011, $8000, $0009, $8000, $0002, $8000, $000D, $0002, $8000,
{ $80 } $003E, $8000, $0002, $0027, $0045, $0002, $8000, $8000, $0042, $8000, $8000, $8000, $000B, $8000, $8000, $8000,
{ $90 } $0013, $0041, $8000, $0063, $8000, $0009, $8000, $0002, $8000, $001A, $8000, $0102, $0135, $0033, $8000, $8000,
{ $a0 } $0003, $0009, $0009, $0009, $0095, $8000, $8000, $0004, $8000, $8000, $0005, $8000, $8000, $8000, $8000, $000D,
{ $b0 } $8000, $8000, $8000, $8000, $8000, $0040, $0009, $8000, $8000, $0003, $0006, $0009, $0003, $8000, $8000, $8000,
{ $c0 } $0024, $8000, $8000, $8000, $0006, $00CB, $0001, $0031, $0002, $0006, $0006, $0007, $8000, $0001, $8000, $004E,
{ $d0 } $8000, $0002, $0019, $8000, $8000, $8000, $8000, $8000, $8000, $010C, $8000, $8000, $0009, $0000, $0000, $0000,
{ $e0 } $0000, $0000, $0000, $0000, $0000, $0000, $0000, $0000, $0000, $0000, $0000, $0000, $0000, $0000, $0000, $0000,
{ $f0 } $8000, $0000, $0000, $0000, $0000, $0000, $0000, $0000, $0000, $0000, $0000, $0000, $0000, $0000, $0000, $0004 );
begin
{Если размер нулевой, то выходим}
if Size = $00 then
begin
Result := 0;
Exit;
end;
PacketID := PacketObj^[0];
PacketLen := PacketLengths[PacketID];
{Если пакет неизвестный, то вернем - 1}
if PacketLen = $00 then
begin
Result := -1;
Exit;
end;
{Если динамическая длинна, то посчитаем ее
или выдадим 0 если нехватает данных}
if PacketLen >= $8000 then
begin
if Size > 3 then
begin
ResSize := strtoint("$"+IntToHex(PacketObj^[1],2)+IntToHex(PacketObj^[2],2));
{Если пакет неполный, то выдадим 0}
if ResSize <= Size then Result := ResSize
else Result := 0; {if ResSize > Size}
end
else Result := 0; {if Size < 3}
end
else {Иначе выдаем фиксированную длинну пакета}
begin
ResSize := PacketLen;
{Если пакет неполный, то выдадим 0}
if ResSize <= Size then Result := ResSize
else Result := 0; {if ResSize > Size}
end;
end;
← →
Digitman © (2005-05-20 13:02) [53]в [5] - "кошмар" (см. [6])
ГДЕ в [5] интерпретация первых 2-х байт чего-то там ? ткни меня носом ....
← →
Gek1 (2005-05-20 13:28) [54]Вот в этом коде я проверяю блок данных на целостность пакетов:
{Далее проверяем чтобы в буффере было N целых пакетов}
index:=0;
PacketLen := 1; {Любое положительное число}
while (index < PHuffDecompressLen) and (PacketLen > 0) do
begin
New(TempBuf);
for i := 0 to PHuffDecompressLen - (index + 1) do TempBuf^[i] := PHuffDecompress^[i+index];
TempBufLen := PHuffDecompressLen - index;
{Ф-ция GetPacketLen вернет:
0 если пакет маленький и надо еще кусок
-1 если пакет неизвестный
длинну, если длина >= пакету}
PacketLen := GetPacketLen(TempBuf,TempBufLen);
{Освободим TempBuf}
Dispose(TempBuf);
{Если длинна больше нуля, то увеличиваем индекс}
if PacketLen > 0 then index := index + PacketLen;
{Если длинна равна 0, то выходим}
if PacketLen = 0 then Exit;
{Если длинна меньше нуля, то:}
if PacketLen < 0 then
begin
{Выдаем ошибку о неизвестном пакете}
SHex := "";
for i := index to PHuffDecompressLen - 1 do SHex:= SHex + IntToHex(PHuffDecompress^[i],2);
AddtoDebugLog(GetCharName+": "+"Внимание! Обнаружен неизвестный пакет: "+SHex);
{Останавливаем соединение}
GameClient.Close;
{Выходим}
Exit;
end;
{Если длинна больше $8000, то:}
if PacketLen > $8000 then
begin
{Выдаем ошибку о неверной длинне пакета}
SHex := "";
for i := index to PHuffDecompressLen - 1 do SHex:= SHex + IntToHex(PHuffDecompress^[i],2);
AddtoDebugLog(GetCharName+": "+"Внимание! Неверная длинна пакета: "+SHex);
{Останавливаем соединение}
GameClient.Close;
{Выходим}
Exit;
end;
{Далее или у нас будет только N целых пакетов
или выйдем ранее из процедуры}
end; {End while (index < PHuffDecompressLen) and (PacketLen > 0)}
← →
Digitman © (2005-05-20 13:42) [55]
> Gek1 (20.05.05 13:28) [54]
бред какой-то...
какие нафих "пакеты" ? Хаффман не знает никаких "пакетов" !
Хаффман сжимает некий блок инф-ции (ему по барабану, пакеты там или не пакеты), а сервер тебе передает то что выдал Хаффман...
ГДЕ в [54] обращение к тем самым "первым 2-ум байтам" ? ... покажи мне ! ... ткни меня носом ...
← →
Eraser © (2005-05-20 13:59) [56]Gek1
Как-то всё в твоём алгоритме перепутано.
Переработай алгоритм, чтобы приём данных осуществлялся так.
1. Чтение первых 4 байт - размер принимаемых данных.
2. Чтение потока данных, размер которых взят из п. 1.
3. Распаковка/дефифровка потока данных считанного из п. 2.
Куда уж проще.
← →
Digitman © (2005-05-20 14:04) [57]
> Eraser © (20.05.05 13:59) [56]
да не знает он ни черта протокол этот..
тычется как слепой котенок .. на основании ОБС ...
← →
Gek1 (2005-05-20 14:08) [58]
> Digitman © (20.05.05 13:42) [55]
Алгоритм работает так:
1. Принимаю данные
2. Дописываю в конец буфера
3. Расжимаю данные алгоритмом Хаффмана
4. Далее проверяем расжатый блок данных на наличие N целых пакетов. И если там оказывается не полные пакеты, то выходим и ждем пока прийдет новый кусок данных. Смотри [54]
5. Далее (если у нас N целых пакетов):
Обрабатываем каждый пакет и сбрасываем входной буфер на ноль.
Т.е суть алгоритма - принимать во входящий буфер сжатые куски данных, пока после распаковки там не окажется N целых пакетов.
> ГДЕ в [54] обращение к тем самым "первым 2-ум байтам" ?
> ... покажи мне ! ... ткни меня носом ...
В [54] смотри ф-цию GetPacketLen
А в описании этой ф-ции [52]:
PacketID := PacketObj^[0];
Здесь обратилиcь к первому байтику и узнали ID пакета.
Далее в [52] по коду:
{Если динамическая длинна, то посчитаем ее
или выдадим 0 если нехватает данных}
if PacketLen >= $8000 then
begin
if Size > 3 then
begin
ResSize := strtoint("$"+IntToHex(PacketObj^[1],2)+IntToHex(PacketObj^[2],2));
{Если пакет неполный, то выдадим 0}
if ResSize <= Size then Result := ResSize
else Result := 0; {if ResSize > Size}
end
else Result := 0; {if Size < 3}
end
Обрати внимание на:
ResSize := strtoint("$"+IntToHex(PacketObj^[1],2)+IntToHex(PacketObj^[2],2));
Здесь как раз в случае дин. длинны - определяем нужную длину пакета из 2-го и 3-го байта.
Т.е все динамические пакеты имеют один и тот же формат в начале:
· BYTE cmd
· BYTE[2] packet length
← →
Gek1 (2005-05-20 14:11) [59]
> Eraser © (20.05.05 13:59) [56]
Так не будет работать.
Поток данных может (и очень часто так бывает) содержать несколько пакетов с данными. каждый пакет может в свою очередь иметь дин. длинну, которую мне надо определить, чтобы посчитать полную длинну потока данных.
Проще некуда.
← →
Eraser © (2005-05-20 14:15) [60]Gek1 (20.05.05 14:11) [59]
1. Что за сервер? Приведи название программы.
2. каждый пакет может в свою очередь иметь дин. длинну
в TCP впринципе очень сложно формировать каждый пакет по отдельности - свой драйвер ядра для этого писать надо. Так что ты заблуждаешься. Где-то ДОЛЖНА передаваться длина данных... или стоп-последовательность (что врядли).
← →
Alexander Panov © (2005-05-20 14:18) [61]
> Где-то ДОЛЖНА передаваться длина данных... или стоп-последовательность
> (что врядли).
Возможен еще вариант - каждый запакованный пакет имеет одинаковую длину, определенную разработчиком.
← →
Eraser © (2005-05-20 14:20) [62]Alexander Panov © (20.05.05 14:18) [61]
Ну или так.
Только терми "пакет" в данном случае лучше не использовать, чтобы путаницы небыло.
← →
Digitman © (2005-05-20 14:23) [63]
> Gek1 (20.05.05 14:08) [58]
> 1. Принимаю данные
"шматок" данных ты рпринял, а не "данные".
> 2. Дописываю в конец буфера
да на здоровье ..
> 3. Расжимаю данные алгоритмом Хаффмана
какие "данные"-то ? только что принятый "шматок" или N "шматков", принятых ранее + текущий "шматок" ?
разжимать следует не непонятно что, а данные, соответствующие ЦЕЛОСТНОМУ сообщению передатчика !
а размер этого сообщения ты не знаешь !
← →
Gek1 (2005-05-20 14:25) [64]
> 1. Что за сервер? Приведи название программы.
Ultima Online
В основном используються POL сервера, поэтому я тестирую на POL095.
Хотя на Sphere глюки тоже были замечены.
> 2. каждый пакет может в свою очередь иметь дин. длинну
>
> в TCP впринципе очень сложно формировать каждый пакет по
> отдельности
Когда я говорил пакет, я имеел ввиду пакет с информацией, посланный самим сервером.
В случае с TCP - я писал TCP пакет.
Например:
0x1A Packet
Object Information (Variable # of bytes)
· BYTE cmd
· BYTE[2] blockSize
· BYTE[4] itemID
· BYTE[2] model #
· if (itemID & 0x80000000)
· BYTE[2] item count (or model # for corpses)
· if (model & 0x8000)
· BYTE Incr Counter (increment model by this #)
· BYTE[2] xLoc (only use lowest significant 15 bits)
· BYTE[2] yLoc
· if (xLoc & 0x8000)
· BYTE direction
· BYTE zLoc
· if (yLoc & 0x8000)
· BYTE[2] dye
· if (yLoc & 0x4000)
· BYTE flag byte (See top)
Вот это пакет 0x1A от сервера. Непутайте с TCP пакетами.
> Где-то ДОЛЖНА передаваться длина данных... или стоп-последовательность
> (что врядли).
В сжатой части длинна не обнаружена и стоп последовательность тоже.
Весь мой алгоритм опирается на данные, полученные после распаковки.
← →
Gek1 (2005-05-20 14:29) [65]
> Возможен еще вариант - каждый запакованный пакет имеет одинаковую
> длину, определенную разработчиком.
Необходимые пакеты собираються в цепочку .... сжимаються .. и отправляются клиенту.
Длинна сжатого потока данных будет всегда разна, потому как каждый раз цепочка стоиться из разных пакетов с информацией
← →
Eraser © (2005-05-20 14:29) [66]Gek1 (20.05.05 14:25) [64]
А где-нибудь есть SDK или описание протокола данной сетевой игрушки.
← →
Eraser © (2005-05-20 14:32) [67]Gek1 (20.05.05 14:29) [65]
Вот щас нашёл в сети, где можно скачать клиент, такую надпись
"Без файлов данного архива Вы не сможете играть на сервере. Файлы из данного архива необходимо разархивировать в папку с игрой. В данном архиве следующие файлы: client.exe, abyss.dll."
Скорее всего вся "логика" соединения в этих файлах... уже создана разработчиком сервера.
← →
Gek1 (2005-05-20 14:34) [68]
> "шматок" данных ты рпринял, а не "данные".
Да. шмоток ... кусочек ...
> какие "данные"-то ? только что принятый "шматок" или N "шматков",
> принятых ранее + текущий "шматок" ?
Принятый "шматок" дописываю в конец буффера.
если в прошлый раз пришел оборваный пакет, то буфер не будет пустым и новый "шматок" допишется к старым "шматкам".
если же буфер пустой, то будет первый "шматок" или не "шматок" - это вы проверим проверкой на наличие N целых пакетов.
← →
Eraser © (2005-05-20 14:36) [69]Gek1 (20.05.05 14:34) [68]
если в прошлый раз пришел оборваный пакет, то буфер не будет пустым и новый "шматок" допишется к старым "шматкам".
Всё. Я ретируюсь.. ))
← →
Gek1 (2005-05-20 14:39) [70]
> А где-нибудь есть SDK или описание протокола данной сетевой
> игрушки.
Если бы было SDK - я бы ВАМ и себе голову не морочил. Давно уже все бы работало. Игрушка старая. Описание протокола закрыто и никому в руки не попадало.
Есть только кодеры, которые потихонечку анализировали и разгадывали как он устроен, но до конца он неизвестен.
> Без файлов данного архива Вы не сможете играть на сервере.
> Файлы из данного архива необходимо разархивировать в папку
> с игрой. В данном архиве следующие файлы: client.exe, abyss.dll."
>
> Скорее всего вся "логика" соединения в этих файлах... уже
> создана разработчиком сервера.
Это ты нашел сам клиент от разрабочиков, только забыл его версию глянуть, так как криптация в них различна.
abyss.dll - на абисе небыл, но многие вставляют dll-ки, которые борятся с любителями искать "багов" в программах.
Сам тоже не люблю любителей "багов", поэтому стараюсь, чтобы они пользоватся ими не могли.
← →
Digitman © (2005-05-20 14:41) [71]
> Gek1 (20.05.05 14:34) [68]
> Принятый "шматок" дописываю в конец буффера
не возражаю.
> если в прошлый раз пришел оборваный пакет
в ПРОШЛЫЙ раз пришел не "оборваный пакет", а очередной фрагмент неких данных, упакоавнных Хафман-компрессором !!
НИ о каких пакетах НЕ может идти речи, пока "сжатые" данные НЕ "разжаты" !
а чтобы их "разжать" , нужно знать хотя бы длину данных, содержащих "сжатые" данные, перед сжатием представлявшие собой N пакетов
← →
Eraser © (2005-05-20 14:41) [72]Gek1 (20.05.05 14:39) [70]
Тогда со сниффером наперевес надо анализировать что там посылается...
← →
Gek1 (2005-05-20 14:42) [73]
> Всё. Я ретируюсь.. ))
:-)
Сам протокол очень сложен. Я начал это дело еще прошлым летом. Только зимой у меня получилось чтото сделать первое. Долго собирал всю информацию о протоколе и всех особенностях.
Много часов провел за обычным клиентом с логером, анализируя что означает каждый пакет.
← →
Gek1 (2005-05-20 14:48) [74]
> НИ о каких пакетах НЕ может идти речи, пока "сжатые" данные
> НЕ "разжаты" !
> а чтобы их "разжать" , нужно знать хотя бы длину данных,
> содержащих "сжатые" данные, перед сжатием представлявшие
> собой N пакетов
Ну не передает сервер длинну сжатого блока.
Я расжимаю все эти фрагменты и там уже анализирую.
Если проанализировав я определил, что там неполные данные, то не трогаяю буффер и жду пока прийдет еще один фрагмент.
Далее клею это фрагмент в конец и расжимаю опять все это дело.
Потом опять анализирую и тд
← →
Digitman © (2005-05-20 14:50) [75]
> Сам протокол очень сложен
как бы ни был он сложен, но до тех пор пока ты не знаешь те его соглашения, которые ты пытаешься использовать, - грош цена твоему коду, работать он как положено не будет, и затея твоя стоит ГОРАЗДО дороже того что ты предполагал на момент "зачатия"
← →
Eraser © (2005-05-20 14:51) [76]Gek1 (20.05.05 14:48) [74]
Ну не передает сервер длинну сжатого блока.
Значит длина пакета строго регламентирована!
Dump снифера многое бы прояснил... может быть.
← →
Gek1 (2005-05-20 14:54) [77]
> Значит длина пакета строго регламентирована!
> Dump снифера многое бы прояснил... может быть.
Дамп самого сжатого блока или история длин блоков?
← →
Eraser © (2005-05-20 14:55) [78]Gek1 (20.05.05 14:54) [77]
Дамп самого сжатого блока
А есть ещё и несжатый блок? Кстати, как ты определяешь сжатый это блок или нет? )
← →
Gek1 (2005-05-20 15:09) [79]
> А есть ещё и несжатый блок? Кстати, как ты определяешь сжатый
> это блок или нет? )
После того как отработает LoginClient ... стартует GameClient и все данные от сервера сжимаються.
← →
False_Delirium © (2005-05-20 15:13) [80]http://uo.menatwork.com.ua/files/injection/uoinj.rar
Всё это должно делаться насколько я помню.
← →
Gek1 (2005-05-20 15:16) [81]Вот пример сжатого блока:
14:10:32 Unknown Name, Server -> Client (Compressed), len: 5
0000: 81 68 E4 FE 80
14:10:32 Unknown Name, Server -> Client (Compressed), len: 564
0000: 46 41 87 D1 AB CE 48 00 00 00 00 00 00 00 00 00 00 00 00 01
0001: A0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 D0 00 00 00
0002: 00 00 00 00 00 00 00 00 00 00 00 00 D0 00 00 00 00 00 00 00
0003: 00 00 00 00 00 00 00 00 D0 00 00 00 00 00 00 00 00 00 00 00
0004: 00 00 00 00 D0 5B D0 11 BE 6F 00 00 00 00 00 00 00 5E F1 7F
0005: 1B 78 7E 78 E6 F3 0C 00 00 00 00 03 40 FA 32 0F 1F 11 A0 00
0006: 00 00 00 00 00 8F E8 ED 9A D9 C8 70 79 1C 3E 40 00 00 00 00
0007: 34 8A 72 1C 1C DD 20 F1 00 00 00 00 00 00 E1 F4 76 CC D5 E6
0008: D9 C8 70 73 74 E3 07 A0 00 00 00 06 80 68 8C F1 E3 8B 85 AB
0009: C5 E0 00 00 00 00 00 67 3C 46 AC 1D 80 00 00 00 00 00 03 40
000A: EA BD 0E 0F 17 68 31 A0 00 00 00 00 00 17 2F 2E D9 AD 48 F4
000B: CD E4 00 00 00 00 00 34 A1 19 D1 C3 07 8C 6C 1E 80 00 00 00
000C: 00 00 67 3C 46 AC 1D 80 00 00 00 00 00 03 40 EE EE 81 E5 AB
000D: C6 08 00 00 00 00 00 01 9C F1 1A B0 76 00 00 00 00 00 00 0D
000E: 65 CD 58 74 87 D2 D9 C8 7D 3C 80 00 00 00 00 19 CF 11 AB 07
000F: 60 00 00 00 00 00 00 D0 E0 95 F9 76 B1 F3 0C 00 00 00 00 00
0010: 00 CE 78 8D 58 3B 00 00 00 00 00 00 06 80 62 9C 87 07 37 48
0011: 3C 40 00 00 00 00 00 38 7D 1D B3 35 79 B6 71 AB A4 6A C3 32
0012: 07 88 7C 7C 80 00 00 1A AD 72 83 C7 90 00 00 00 00 00 00 04
0013: 67 48 3C 6D 47 F1 CD 0F A7 18 DF 20 00 00 00 01 A0 79 5E 87
0014: 07 8B B4 18 D0 00 00 00 00 00 0C E7 88 D5 83 B0 00 00 00 00
0015: 00 00 68 CE CE 4D 8D 8D E9 C7 CF 98 6E 03 B5 B9 CF 3C 40 00
0016: 00 00 33 9E 23 56 0E C0 00 00 00 00 00 01 A0 97 F5 C6 C6 B5
0017: 78 00 00 00 00 00 00 0C E7 88 D5 83 B0 00 00 00 00 00 00 68
0018: AB DD 36 16 81 E5 AB 04 00 00 00 00 00 03 39 E2 35 60 EC 00
0019: 00 00 00 00 00 1A AF C3 F0 8A F2 00 00 00 00 00 00 02 3F A3
001A: B6 6B 52 3D 33 79 00 00 00 00 00 0D 71 95 F9 76 B1 F3 0C 00
001B: 00 00 00 00 00 63 43 F1 C6 FF 1E 39 2D 63 71 E2 00 00 00 00
001C: 06 80 00 D0
И после распаковки и разборки - результат:
14:10:32 Unknown Name, Server -> Client: 0xA9, len: 1380
0000: A9 05 64 01 4D 69 72 61 6C 65 78 00 00 00 00 00 00 00 00 00
0001: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0002: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0003: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0004: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0005: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0006: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0007: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0008: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0009: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
000A: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
000B: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
000C: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
000D: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
000E: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
000F: 00 00 00 00 11 00 59 65 77 00 00 00 00 00 00 00 00 00 00 00
0010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 54 6F 77
0011: 6E 20 43 65 6E 74 65 72 00 00 00 00 00 00 00 00 00 00 00 00
0012: 00 00 00 00 00 00 00 00 01 4D 69 6E 6F 63 00 00 00 00 00 00
0013: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0014: 45 61 73 74 20 42 72 69 64 67 65 00 00 00 00 00 00 00 00 00
0015: 00 00 00 00 00 00 00 00 00 00 00 02 42 72 69 74 61 69 6E 00
0016: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0017: 00 00 00 43 61 73 74 6C 65 20 42 72 69 74 61 6E 69 61 00 00
0018: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 03 4D 6F 6F 6E 67
0019: 6C 6F 77 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
001A: 00 00 00 00 00 00 44 6F 63 6B 73 00 00 00 00 00 00 00 00 00
001B: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 04 54 72
001C: 69 6E 73 69 63 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
001D: 00 00 00 00 00 00 00 00 00 57 65 73 74 20 47 61 74 65 00 00
001E: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
001F: 05 4D 61 67 69 6E 63 69 61 00 00 00 00 00 00 00 00 00 00 00
0020: 00 00 00 00 00 00 00 00 00 00 00 00 44 6F 63 6B 73 00 00 00
0021: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0022: 00 00 00 06 4A 68 65 6C 6F 6D 00 00 00 00 00 00 00 00 00 00
0023: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 44 6F 63 6B 73
0024: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0025: 00 00 00 00 00 00 07 53 6B 61 72 61 20 42 72 61 65 00 00 00
0026: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 44 6F
0027: 63 6B 73 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0028: 00 00 00 00 00 00 00 00 00 08 56 65 73 70 65 72 00 00 00 00
0029: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
002A: 00 44 6F 63 6B 73 00 00 00 00 00 00 00 00 00 00 00 00 00 00
002B: 00 00 00 00 00 00 00 00 00 00 00 00 09 42 72 69 74 61 69 6E
002C: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
002D: 00 00 00 00 43 61 73 74 6C 65 20 42 6C 61 63 6B 74 68 6F 72
002E: 6E 65 00 00 00 00 00 00 00 00 00 00 00 00 00 0A 57 69 6E 64
002F: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0030: 00 00 00 00 00 00 00 4D 61 69 6E 20 45 6E 74 72 61 6E 63 65
0031: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0B 54
0032: 72 69 6E 73 69 63 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0033: 00 00 00 00 00 00 00 00 00 00 44 6F 63 6B 73 00 00 00 00 00
0034: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0035: 00 0C 42 75 63 63 61 6E 65 65 72 27 73 20 44 65 6E 00 00 00
0036: 00 00 00 00 00 00 00 00 00 00 00 00 00 44 6F 63 6B 73 00 00
0037: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0038: 00 00 00 00 0D 4F 63 63 6C 6F 00 00 00 00 00 00 00 00 00 00
0039: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 44 6F 63 6B
003A: 73 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
003B: 00 00 00 00 00 00 00 0E 4E 75 6A 68 65 6C 6D 00 00 00 00 00
003C: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 44
003D: 6F 63 6B 73 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
003E: 00 00 00 00 00 00 00 00 00 00 0F 43 6F 76 65 00 00 00 00 00
003F: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0040: 00 00 45 61 73 74 20 47 61 74 65 00 00 00 00 00 00 00 00 00
0041: 00 00 00 00 00 00 00 00 00 00 00 00 00 10 56 65 73 70 65 72
0042: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0043: 00 00 00 00 00 49 72 6F 6E 77 6F 6F 64 20 49 6E 6E 00 00 00
0044: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Как видно:
Пакет сервером сжался... ТСП протокол порезал его на 2 куска и отправил.
Я принял... проанализировал и получил готовый пакет.
(В данном случае готовый один пакет, но частенько могуть быть несколько готовых пакетов)
Поэтому в коде и фигурирует:
"Проверка N целых пакетов"
← →
Gek1 (2005-05-20 15:18) [82]
> False_Delirium © (20.05.05 15:13) [80]
В инжекте теже проблемы, но он похоже согласился с тем, что иногда будут глюки.
← →
Eraser © (2005-05-20 15:18) [83]Gek1 (20.05.05 15:16) [81]
Пакет сервером сжался... ТСП протокол порезал его на 2 куска и отправил.
Да причём тут вообще TCP?
Добрый совет: купи и главное прочитай хорошую книгу по TCP/IP.
← →
Gek1 (2005-05-20 15:21) [84]А теперь вопрос:
Где может передаваться длина или стоп последовательность в [81]?
Стоит тронуть там один байтик, и результат будет нарушен и неверен.
Вот поэтому я решил, что в сжатом блоке данных информации о их длинне нету. Может у вас получится обнаружить эту информацию.
← →
Digitman © (2005-05-20 15:24) [85]
> ТСП протокол порезал
ТСП протокол вообще ничего не "режет"
← →
Gek1 (2005-05-20 15:27) [86]
> Digitman © (20.05.05 15:24) [85]
Пошел я внимательно еще раз читать книжку.
А что по поводу [84]?
← →
Eraser © (2005-05-20 15:29) [87]Gek1 (20.05.05 15:21) [84]
На первый взгляд в том листинге, который ты привёл не видно этой стоп последовательности.
Советую пообращать внимание на самые первые 2 или 4 байта сообщения. Это возможно размер блока. Тут проверять надо... у меня к сожалению на это времени нету.
← →
Digitman © (2005-05-20 15:32) [88]
> А что по поводу [84]?
а по поводу [84] - изучать протокол за тебя никто не намерен. это твои проблемы.
← →
Sha © (2005-05-21 21:32) [89]> Gek1 (20.05.05 15:27) [86]
> А что по поводу [84]?
Все необходимые данные у тебя имеются.
Во-первых, надо обязательно внести исправление, указанное Digitman [9].
Во-вторых, переделай DecompressNew в boolean функцию,
возвращающую true, если ей удалось выделить и разжать
из накопленных данных команду/ответ сервера.
Ее надо немного подправить, так чтобы она разжимала
данных не больше длины команды сервера. Это легко сделать,
т.к. после получения первого разжатого байта для команды
фиксированной длины ты можешь определить ее длину
по ее типу, а для команды переменной длины, ты можешь
определить ее длину по следующим двум байтам.
Далее, если DecompressNew вернула true, ты отрабатываешь
разжатую команду сервера и повторяешь вызов DecompressNew
(с новым смещением во входном буфере) и обработку ответа сервера.
Когда DecompressNew вернет false, сдвигаешь данные в входном буфере и ждешь новых данных.
Успехов.
← →
Defunct © (2005-05-31 01:56) [90]всю ветку было лень читать. и тем более вникать.
Eraser © (20.05.05 15:18) [83]
Digitman © (20.05.05 15:24) [85]
TCP "режет" и "разбивает" большой кусок данных на пакеты.
Но в этом случае с вами согласен, именно в случае [81] ничего не резалось, иначе первый пакет был бы максимального объема, а второй - что осталось.
Gek1 (20.05.05 15:16) [81]
Очевидно. Сервер отправляет шапку зашифрованного сообщения коротким пакетом, а дальше данные.
← →
Sha © (2005-05-31 15:48) [91]> Defunct © (31.05.05 01:56) [90]
> Очевидно. Сервер отправляет шапку зашифрованного сообщения коротким пакетом, а дальше данные.
Ты, конечно, понимаешь, что на это полагаться нельзя.
← →
Defunct © (2005-06-01 21:36) [92]> Sha
Конечно, я же этот протокол не "курил". Просто по логике должно было бы быть так.
← →
Gek1 (2005-06-04 13:43) [93]Sha © (21.05.05 21:32) [89]
Этот метод бы подошел, если бы Сервер каждый информационный пакет данных сжимал и отправлял бы. Но сервер перед отправкой сжимает все информационные пакеты и отправляет одним целым сжатым пакетом.
Defunct © (31.05.05 01:56) [90]
в Случае [81] сервер собирался отправлять пакет 0xA9. Он его сжал и отправил целиком. Хотя мне до сих пор интересно почему сжатый пакет порезался на именно такие 2 кусочка.
Sha © (31.05.05 15:48) [91]
Шапок там не наблюдал. Та всевремя только один информационные пакеты. Причем если была бы шапка, а я пытался ее разбирать как инфорационный пакет, то это дело неработало бы и 5-ти минут.
А так это работает сутками. И иногда (замечено на плохих коннектах) мой алгоритм дает збой.
После долгих анализов обычного клиента заметил такую вешь:
Если игровым персонажем подбежать к домику, в котором много айтимов, то получается следующее:
Сервер начинает собирать всю информацию об айтимах в "пучки" пакетов. Каждый пучок, состоящий в среднем из 100-200 информативных пакетов сервер сжимает и отправляет. Но на клиентской стороне начинают происходит какието задержи в зборе пакетов. Такое ощущение что там в OnRead стоит сразу sleep(1000), а потом только читается принятый буфер и обрабатывается.
Я так решил, потому, что айтимы появлялись через некую задержку, после принятия их от сервера. Будто он с помощью sleep ждал на всякий случай еще каких то даных.
← →
Sha © (2005-06-05 11:32) [94]> Gek1 (04.06.05 13:43) [93]
> Этот метод бы подошел, если бы Сервер каждый информационный
> пакет данных сжимал и отправлял бы. Но сервер перед отправкой
> сжимает все информационные пакеты и отправляет одним целым
> сжатым пакетом.
Для приведенного тобой перехвата мой метод прекрасно работает.
Приведи перехваченные данные для случая, когда он, по-твоему, работать не должен.
← →
Gek1 (2005-06-06 14:57) [95]Sha © (05.06.05 11:32) [94]
Вот смотри пример:
Сервер хочет нам отправить 2 информационных пакета (по 2 байта):
0х3301 и 0х3300.
Так как сервер в момент отправки все пакеты соединяет, то получится так:
0х33013300
Далее Сервер сожмет эту цепочку данных алгоритмом Хаффмана и получатся такие 3 байтика:
0х4FE99A
Далее прием:
Вариант 1:
Получили 0х4FE99A. Распаковали моим способом и там получились 2 пакета: 0х3301 и 0х3300.
(Способ [89] отработает как вариант 2. см. ниже)
Вариант 2:
Данные по дороге к нам порезались и пришли первые 2 байта (0х4FE9). Но мы на клиенте еще не знаем сколько всего должно быть. Расжимаем и получаем 0х3301. Видим что информационный пакет целый и обрабатываем его. Дальше нам приходит еще один байтик: 0х9A
Расжимая его получаем 0х75. Дальше о последствиях понятно.
Если мой алгоритм дает збои только с этими редкими проблемами, то вариант [89] - не заработает никогда.
Этот пример думаю хорошо показывает какие збои могут возникнуть, когда фрагментируються сжатые данные между клиентом и сервером.
Таких вариантов очень много. Предусмотреть все - невозможно. Проблемы возникают в следствии фрагментации сжатых данных.
Кто может подсказать как с этим бороться?
← →
Sha © (2005-06-06 15:18) [96]> Gek1 (06.06.05 14:57) [95]
> Вот смотри пример:
> Сервер хочет нам отправить 2 информационных пакета
> (по 2 байта):
> 0х3301 и 0х3300.
> Так как сервер в момент отправки все пакеты соединяет, то
> получится так:
> 0х33013300
Ты реально ловил такие пакеты, или просто предполагаешь,
что они могут такими приходить?
← →
Digitman © (2005-06-06 15:20) [97]
> Gek1 (06.06.05 14:57) [95]
ты никак не можешь понять, что для успешной работы Хаффман-декомпрессора нужно на принимающей стороне поиметь сначала целое дерево, сформированное Хаффман-компрессором передающей стороны !
и уж имея целое дерево можно постепенно декомпрессировать поступающие фрагменты собственно сжатых данных..
← →
Sha © (2005-06-06 15:20) [98]В смысле рельно может придти 0х4FE99A
← →
Gek1 (2005-06-06 15:22) [99]Это реальный лог ... я веду лог всего что отправляю и что принимаю. Причем эти 2 пакета могут ходить по отдельности и могут вместе. Я привел пример, когда они вместе.
Смотри описание этого пакета:
0x33 Packet
Last Modified on Thursday, 19-Nov-1998
Pause/Resume Client (2 bytes)
· BYTE cmd
· BYTE pause/resume (0=pause, 1=resume)
← →
Gek1 (2005-06-06 15:24) [100]Digitman © (06.06.05 15:20) [97]
а как проверить на целостность дерева?
Sha © (06.06.05 15:20) [98]
Да может прийти 3 варианта:
0х4FE9 а потом 0х9A
0х4F а потом 0хE99A
или просто 0х4FE99A
← →
Digitman © (2005-06-06 15:46) [101]
> Gek1 (06.06.05 15:24) [100]
> а как проверить на целостность дерева?
а я почем знаю ?
протокол-то исследуешь ты, а не я ..
наеврно сам хаффман передает где-то в своем заголовке эту инфу ..
ну ты сам подумай (забудь на секунду про своего хаффмана) - можно ли, к примеру, декодировать поток принимаемых шифрованных данных, предваряемых ключем шифрования (т.е. ключ передается где-то ПЕРЕД собственно данными), если размер ключа заранее неизвестен ?
можно ли без отмычки и без лома открыть дверь, если ты получил лишь некую часть ключа и даже не знаешь как выглядит целый ключ ?
← →
Gek1 (2005-06-07 21:54) [102]Digitman © (06.06.05 15:46) [101]
Дерево - константа. Причем как со стороны сервера так и со стороны клиента. См. [38].
> ну ты сам подумай (забудь на секунду про своего хаффмана)
> - можно ли, к примеру, декодировать поток принимаемых шифрованных
> данных, предваряемых ключем шифрования (т.е. ключ передается
> где-то ПЕРЕД собственно данными), если размер ключа заранее
> неизвестен ?
>
> можно ли без отмычки и без лома открыть дверь, если ты получил
> лишь некую часть ключа и даже не знаешь как выглядит целый
> ключ ?
Конечно не совсем так. Отмычка как раз у нас есть (дерево хаффмана). Но мне понятно что ты имеешь виду и этот метод я как раз и пытаюсь полностью определить. И похоже уже получается.
В любом случае спасибо всем ВАМ за помошь мне.
← →
Sha © (2005-06-08 09:33) [103]Интересно было бы посмотреть на то, что получится.
Страницы: 1 2 3 вся ветка
Текущий архив: 2005.10.02;
Скачать: CL | DM;
Память: 0.84 MB
Время: 0.048 c