Среда, 01.05.2024, 05:05
Personal dimanche13 site
Главная | Регистрация | Вход Приветствую Вас Гость | RSS
Категории каталога
Мои статьи [22]
BlitzMax [17]
раздел содержит статьи относящиеся к языку программирования BlitzMax
SDL [9]
раздел содержит статьи на тему SDL
Code::Blocks [3]
в этом разделе рассказывается как прикрутить движок к интегрированной среде разработки(IDE) Code::Blocks
Форма входа
Поиск
Друзья сайта
Главная » Статьи » Мои статьи

Формат War-файла

Как уже было сказано ранее, в статье о формате pud-файла, Близзарды не горят желанием светить всю медиа-составляющую своих игр. А потому они тщательно ее скрывают. Нетрудно догадаться что один из самых больших файлов игры, как раз-таки содержит то, что от нас так поспешно хотели скрыть. Файл Maindat.war  в папке Data из игры Вар2, наталкивает на мысль о том, что в нем находятся нужные нам вкусности, не только своим названием, но и своим размером. WAR - Warcraft ARchive. Сразу хочу оговориться, что я не считаю себя супер-хакером или кем-то в этом духе, но открыв файл в 16-ричном редакторе, сразу бросается в глаза стройность рядов 00 в определенных местах, а как говорят бывалые вскрыватели ресурсов игр, это ни что иное как FAT (или таблица расположения файлов - по нашему). Первые 4 байта как правило у Близзардов, да и не только у них - это заголовок. Дальше 2 байта(слово) с количеством файлов(секций или секторов) в этом файле, дальше 2 байта чепухи, и наконец начинаются смещения к началу секторов, друг за другом, количеством прочитанным ранее. До этого я докумекал сам, пока не нашел формат этого файла всрытый уже кем-то другим. Скачал с сайта wotsit.org - там много информации о файлах по их расширениям. Вот то, что было указано там:

WAR File Format

00:0000-00:0003 L Id caption number $19000000
00:0004-00:0005 W Number of entries ( max enrty number +1 )
00:0006-00:0007 W Archive Id ( used for entry determination)
  1000=Maindat.war
  2000=Snddat.war
  3000=Rezdat.war
  4000=Strdat.war
  5000=Sfxdat.sud
  6000=Muddat.cud
00:0008-XX:XXXX L Entry offsets in in file
  $FFFFFFFF=unavailable (used in demo version)
XX:XXXX RXX Entries

  PseudoEntry=everything shorter than 4 bytes
  (counted by substracting offsets)

  UsefulEntry

  Index : Feature :

  00 W Lower Entry Length word
  =entry datasize-4
  02 B Higher Entry Length word
  03 B LZSS Compression flag
  00=uncompressed
  20=compressed

  LZSS Compression scheme :

  4096 Bytes buffering system

  00 B Flags bits=bytes in next 8 records
   
Bit set = any duplicity , use
  the same byte in buffer
  on current position
  record = byte
 

Bit clear = duplicity on position
  in lower byte and lower
  nibble offset of length
  in higher byte higher
  nibble

Информации скажу я вам немного, зато все по делу. Как видим мы были недалеко от истины в наших рассуждениях. Первые 4 байта и правда заголовок, потом количество секций и идентификатор архива, разный для разных файлов. Да, как видите архивы Варкрафта могут храниться в фалах не только с расширением war, но и sud, cud и наверняка других. Вслед за идентефикатором файла идут смещения (размер 4 байта каждое смещение) относительно начала файла до начала секции. Стоит остановиться. Почему я называю части этого архива секциями, а не файлами? Просто в моем понимании файл - это часть данных имеющих название, ведь у вас на диске каждый файл имеет название, а у секций названий нет, только порядковые номера. Так вот, а вначале каждой секции, если перейти на ее начало, есть тоже маленький заголовок в 4 байта: 1- байт это флаг обозначающий, заархивирована ли секция ( в этом случае байт = 0х20 ), либо не заархивирова( в этом случае этот байт = 0х00 ) и остальные 3 байта - это размер распакованной секции, если она конечно заархивирована. Отлично. Теперь мы знаем что:

1) файл имеет заголовок (0х00000019 для вар2 и 0х00000018 для первого вар-а)

2) кол-во секций в файле

3) идентификатор архива (никакой смысловой нагрузки не несет)

4) смещения от начала файла до секций, ровно стока штук скока в пункте 2

5) сами секции

Хорошо, уже можем написать немного кода:

' это тип для сектора, который описывает файл в архиве
Type _Sector

 Field start_address:Int ' начальный адрес файла
 Field length:Int ' размер в байтах
 Field num:Int ' порядковый номер
 Field comp_flag:Byte ' флаг компрессии
 Field uncomp_length:Int ' длина разархивированного участка
 Field data_ptr:Byte Ptr ' указатель на массив данных
 
 Global list:TList = New TList ' список всех секторов
 
 Method New()
  list.addlast( Self )
 End Method
 
End Type

' =================================
Global file_name:String=  "Maindat.war"

Local stream:TStream = OpenFile( file_name )
If( Not stream) Then Print "in file not found"; End

Local out_stream:TStream = WriteFile( file_name + ".debug.txt" )
If( Not out_stream) Then Print "out file not found"; End

Local header:Int = stream.ReadInt()
Local count:Int = stream.ReadShort()
Local unknown:Short = stream.ReadShort()

out_stream.WriteLine("---------------START------------------")
out_stream.WriteLine("header is : " + show_hex( header ) )
out_stream.WriteLine("section count : " + show_hex( count , 2 ) + " dec: " + count )
out_stream.WriteLine("unknown : " + show_hex( unknown , 2 ) )

Global last_pos:Int 

' пройдемся по всем файлам
For Local i:Int = 0 Until count
 
 ' прочитаем начальный адрес 
 Local start_address:Int = stream.ReadInt() 
 ' запомним позицию
 last_pos = stream.pos()
 ' прочитаем адрес следующего файла 
 Local end_address:Int = stream.ReadInt() 
 ' длина текущего файла
  Local length:Int 

  ' перейдем на начало текущего файла
  stream.seek( start_address )
  
  ' если текущий - это последний, то он кончается там же, где кончается и файл  
  If( i = (count - 1) ) Then end_address = stream.size() 
  
  ' рассчитаем длинну файла 
  length = end_address - start_address 
  
  ' создадим новый сектор и заполним его данными
  Local o:_Sector = New _Sector
  o.start_address = start_address
  o.length = Max( 1 , length - 4 ) ' ( еще 4 байта служебной информации вначале )
  o.num = i
  
  ' если длина файла больше 4 байт
  If( length > 4 )

  ' читаем еще 4 байта
  Local temp:Int = stream.ReadInt()

  ' из них 3 байта - это размер файла после разархивирования
  o.uncomp_length = temp & $00FFFFFF
  ' и 1 байт - показывает архивный ли файл или нет
  o.comp_flag = temp Shr 24
  
  End If 

  ' читаем файл в буффер
  o.data_ptr = MemAlloc( o.length )
  stream.readbytes( o.data_ptr, o.length )
  
  ' сохраним информацию о заполненой секции в файл
  out_stream.WriteLine("--------------------------------------")
  out_stream.WriteLine("num: " + i )
  out_stream.WriteLine("start_adddress : " + show_hex( start_address) )
  out_stream.WriteLine("end_adddress : " + show_hex( end_address) )
  out_stream.WriteLine("length : " + o.length )
  out_stream.WriteLine("comp_flag : " + show_hex( o.comp_flag , 1 ) )
  out_stream.WriteLine("uncomp_length : " + show_hex( o.uncomp_length ) + " dec : " + o.uncomp_length )

 ' вернемся обратно к запомненной позиции
 stream.seek( last_pos ) 
 
Next

' выведем для сведения скока все-таки получилось файлов
Print "~ncount : " + _Sector.List.count()

' закроем поток
stream.close()
out_stream.WriteLine("----------------END-------------------")
out_stream.close()

end

Я уже использовал тип _Sector для примера с pud-файлом, и мне кажется что это наиболее удачное решение для организации одной секции такого вида информации. Для текущих целей я добавил еще парочку полей с данными необходимых нам для работы с war-архивами. Я сделал достаточно коментариев чтобы понять что творится в каждой строке программы. Можно было бы уже на этом этапе извлекать секции в отдельные файлы, но разве это имеет смысл, когда выяснилось что только 5 или 6 из них не заархивированны, а остальные содержат архивы. В небезызвестном документе, который я уже приводил как пример лаконичности и четкости рамок мысли, есть даже подсказка что это LZSS-архивация и даже, то как ее расшифровать. Честно скажу ниасилил, да и не пытался особо, я люблю когда описание эээ немного подлиннее и поконкретнее, поэтому я решил использовать для разархивирования наработки уже сделанные другими... Поиски мои привели меня даже на сайт Apple где лежала функция обрадовавшая меня своим названием (decompress_lzss) и ничего что на С, этот язык мне тоже знаком, тем более что я давно хотел проверить как подключать С-функции к БМаксу. Но как я не пытался, эта функция работать как надо не хотела. Она выдавала все что угодно, только не правильный результат. Тогда, слави Гугл-единый, откопал код такого же извиняюсь археолога Варкрафта как и я, в опен сорс. Оказывается не я один копаюсь в дерьме мамонтов (я думаю никто не посмеет оспорить, что это мамонты ). :) Так вот из кода была выдрана, самым наглым образом, функция разархивации, подрейхтована напильником и подключена к БМаксу. Надо бы по честному связаться с ним и сказать спасибо, правда в том месте стоит коммент

// FIXME: If the decompression is too slow, optimise this loop :->

думаю это означает, что он и сам не очень доволен кодом, а значит не будет кричать о копирайтах и другой чуши. :) Так вот функция была внедрена и показала себя отлично. Все секции были извлечены. Да, но остался вопрос, что каждая секция означает, какую информацию она в себе несет? Эти вопросы не так сложны как кажутся, но и тут есть подвох. Мы привыкли (в виндовз) что расширение файла означает его принадлежность к какому-либо формату. А у секций, нет расширений, да что говорить у них и имен-то нет. (Эко их Близзарды уделали). Но не беда, открыв файлы 16-ричным редактором легко заметить по первым байтам, к каким категориям принадлежат эти файлы. Так например файл начинающийся символами FORM - означает звуковой файл XMI. RIFF - звуковой файл WAV , SMK2 - анимационный ролик и т.д. По размеру секции тоже можно кое-что догадаться, например размер 768- байт сразу наталкивает о мысли, что в нем хранится палитра. Так как игра старая, то она использовала только 256 цветов, которые брала из палитры. А палитра заполнялсь значениями RGB, три байта на 1 цвет палитры (3 * 256 = 768). В связи с этим рисунки имеют структуру простого массива, где в каждой ячейке лежит байт указывающий на цвет (индекс в таблице палитры), другими словами RAW-рисунок. Так, например, размер 64004 наталкивает на мысль, что это RAW-рисунок во весь экран режим VGA( 320x200 ) 320 х 200 = 64000 и еще 4 байта это наверняка его размеры в первых 4 байтах. Так и есть первые 4 байта 4001С800 - что значит 0х0140 = это 320 в десятичной и С8 - это 200. Обычно секции рисунков и палитр находятся рядом. Этим же образом смотрятся и другие файлы. С файлами анимации и стадий постройки сданий, несколько сложнее, там тоже свой формат. Но вас это пугать не должно, он тоже описан :) 

Спасибо за внимание, в аттаче исходник и файл распаковщик в ехе.

http://dimanche.ucoz.ru/war_extractor.zip



Категория: Мои статьи | Добавил: dimanche (08.01.2009)
Просмотров: 2235 | Комментарии: 2 | Рейтинг: 5.0/1 |
Всего комментариев: 2
2 satodoroshott  
0
то что я искал, спасибо

1 SBJoker  
0
Познавательно :)

Имя *:
Email *:
Код *:
Copyright MyCorp © 2024