You are not logged in.
1. Система ресурсов в целом
Ресурсы в IV, отличаются от предыдущих игр серии не сколько форматом, сколько подходом. Если для объектов RenderWare была определена какая-то структура, тэги и блоки, то в IV этого нет.То что скрывается в блоке, начинающемся с 'RSC\5' - это просто два непрерывных блока памяти, упакованных и сохраненных на диске.
В них содержится один (и только один) объект, со всеми включенными в него объектами. (Т.е., файл с расширением .wtd содержит в себе объект rage::pgDictionary<rage::grcTexture>. При этом, в файле сохраняется не только сама коллекция, но и все включенные в нее объекты текстур, используемые ими хэши, имена и, конечно, пиксельные данные). Все указатели заменяются на смещения в файле. При чтении, смещения заменяются обратно на указатели в памяти.
Предположительно, это все происходит автоматически, с помощью переопределенного оператора new.
(здесь должна быть диаграмка, но прямо сейчас мне лень ее рисовать)
Следствие один: чтобы прочесть ресурс - достаточно одной операции чтения.
Следствие два: описание структуры в памяти, автоматически является форматом ресурса.
Как упоминалось, используется два блока памяти. В одном из них хранится сам объект, в другом - используемые данные Direct3D (тексельные, вертексные или индексные буфера). Для простоты, далее по тексту, они будут называться "CPU блок" и "GPU блок".
Любой указатель преобразуется в 32-битное число. Четыре старших бита в нем - код блока
(5 - CPU блок, 6 - GPU блок), младшие 28 - смещение в блоке (для GPU блока, возможны случаи, когда структура выравнивается на размер страницы, т.е. младшие 12 бит заменяются нулями, а содержавшееся в них значение используется, например, как идентификатор формата данных).
Размеры блоков хранятся в RSC-заголовке (более подробно, это будет описано позже). Пока же, просто приведу вывод моего маленького распаковщика:
C:\...cuments\My Work\_reverse engineering\gta\_gta4_data\rscexw>rscexw.exe w_bat.wdr w_bat.wdru Input file: w_bat.wdr Loaded resource file: version=110, CPU_size = 0x1000, GPU_size = 0x3000 Output file: w_bat.wdru in = 6132, out = 16384 OutSize = 16384, err = 0 Complete.
Тип ресурса определяется расширением (и только расширением), для каждого типа, определен объект, который содержится в этом ресурсе и набор обработчиков для загрузки, инициализации, освобождения, поиска и прочих рутинных операций.
Соответствие типов и объектов (все названия, кроме CHtmlDocument, получены из RTTI):
wad: rage::pgDictionary<rage::crAnimation>
wbd: rage::pgDictionary<rage::phBound>
wbn: rage::pgBaseWrapper<rage::datOwner<rage::phBound>>
wbs: rage::grbTargetManager
wdd: rage::pgDictionary<gtaDrawable>
wdr: gtaDrawable
wft: gtaFragType
whm: CHtmlDocument
wtd: rage::pgDictionary<rage::grcTexture>
PS. Как можно понять по приведенному списку, R* использует достаточно продвинутый C++. Поэтому, если возникнут вопросы шаблонам, оператору new, множественому наследованию и т.д. - лучше создать отдельную тему "Вопросы по C++" и спросить там.
Как обычно, вопросы, дополнения и исправления приветствуются.
Offline
@Capushon - Насколько я понял - оследовательности действий педов (более продвинутая версия PedAttractor). Т.е. можно создавать не просто абстрактных педов, но и сразу назначать им сложные осмысленные действия. (Без необходимости кодить новый CTaskComplex...)
Разберу точнее - обязательно расскажу
Offline
Он отвечает за "оживление" города разными случайными сценками типа когда водители чинят свои машины, рабочие что-то делают, полиция гонится за нарушителями и т.д.
Подробнее здесь:
http://www.gtamodding.com/index.php?title=Scenarios.dat
http://www.gtaforums.com/index.php?showtopic=380874
Offline
Пока у меня есть свободная минутка - несколько простых и повсеместно встречающихся структур.
В оригинале, большая часть описываемого - не struct, а class. Я здесь объявляю все, как структуры, чтобы не описывать права доступа (public/protected/private), по которым у меня все равно нет информации.
Аналогично, для краткости я опускаю нэймспейсы (вместо rage::grcTexture пишу просто grcTexture и т.д.).
template<typename T> struct CSimpleCollection { // size = 8 T * pData; // +0 массив элементов типа T WORD wCount; // +4 количество занятых элементов массива WORD wSize; // +6 общее количество элементов масива }; template<typename T> struct CPtrCollection { // size = 8 T** ppData; // +0 массив указателей на элементы типа T WORD wCount; // +4 количество занятых элементов массива WORD wSize; // +6 общее количество элементов масива };
Если внутри RSC виден указатель (число вида 0x5xxxxxxx или 0x6xxxxxxx), за которым следуют два одинаковых WORD - с очень большой вероятностью, это та или иная коллекция.
Мне встречались коллекции, у которых wCount больше wSize. Предположительно, это какой-то глюк экспорта ресурса. Влюбом случае, количество элементов берется из wCount. wSize нужен только коду, при добавлении новых элементов в коллекцию.
Для CPtrCollection, нужне не забывать проверять значения указателей на нулевое значение.
datBase. Это общий предок для большинства объектов RAGE. Возможно, он содержал какую-то диагностику или трассировку, но в релизный файл это не попало.
Внутри ресурса, в единственном поле этой структуры (и всех ее потомков) содержится относительно произвольное число (например, 0x695384). Это остатки от экспортера ресурсов, а именно - указатель на VMT этого класса в памяти редактора ресурсов).
При экспорте ресурсов, в этом поле может быть записано произвольное значение.
struct datBase { // size = 4 // ВНИМАНИЕ! Это поле не требуется явно описывать, если в структуре есть хотя бы один виртуальный метод - его автоматически добавит компилятор DWORD __vmt; // +0 указатель на таблицу виртуальных методов. };
pgBase - объект, который может взаимодействовать с менеджером памяти.
pMap указывает на массив из 128 DWORD (512 байт). Первый элемент массива должен быть нулевым.
struct pgBase : public datBase { // size = 8 DWORD * pMap; // +4 указатель на карту распределения памяти };
pgDictionary - коллекция именованных объектов. Многие ресурсы (wad, wdd, wtd), представляют собой pgDictionary.
Сам pgDictionary не содержит имен. От них берутся только хэши (а имена есть только в содержащихся объектах).
Хэши должны быть отсортированы по возрастанию. Порядок хэшей должен совпадать с порядком элементов в m_data.
template<struct T> struct pgDictionary : public pgBase { // size = 0x20 pgDictionary<T> * m_pParent; // +8 dictionary более высокого уровня (внутри ресурса - обычно NULL) DWORD m_dwUsageCount; // +0x0C количество ссылок на объект. Как только доходит до нуля, объет освобождается (внутри ресурса содержит 1) CSimpleCollection<DWORD> m_nameHashes; // +0x10 коллекция хэшей имен содержащихся объектов. CPtrCollection<T> m_data; // +0x18 сами элементы, содержащиеся в словаре };
Last edited by listener (20-12-2008 19:03)
Offline
И еще немножко. То, что у меня накопано по форматам моделей (накопано мало, выкладываю то, что есть. Как будет еще - допишу/поправлю).
Я опускаю некоторые промежуточные классы не критичные для чтения ресурсов (например, я не описываю отдельно grcVertexBuffer, потому что, в PC версии везде используется унаследованный от него grcVertexBufferD3D)
По смещению 0 в .wdr находится объект gtaDrawable, в .wdd - pgDictionary<gtaDrawable>
struct rmcDrawable : public pgBase { // size = 0x80 grmShaderGroup * m_shaderGroup; // +8 void * _fC; // +0xC - указатель на неразобранный объект размером 0x40 байт BYTE __unknown1[48]; // +0x10 -- не разобрано CPtrCollection<grmModel> * m_models[4]; // +0x40 - массив из четырех указателей на коллекции моделей (ВНИМАНИЕ! Еще раз: массив указателей на коллекции указателей на модели) BYTE __unknown2; // +0x50 -- не разобрано }; struct gtaDrawable : public rmcDrawable { // size = 0x88 CSimpleCollection<CLightAttr> m_lightAttrs; // +0x80 };
struct grmModel : public datBase { // size = 0x1C CPtrCollection<grmGeometry> m_geometries; // +4 void * _fC; void * _f10; BYTE _f14; BYTE _f15; BYTE _f16; BYTE _f17; BYTE _f18; BYTE _f19; WORD _f1A; }; struct grmGeometry : public datBase { // size = 0x4C DWORD _f4; // +4 DWORD _f8; grcVertexBufferD3D * m_vertexBuffers[4]; // +0xC - четыре указателя на объекты вертексных буферов (неиспользуемые указатели - NULL) grcIndexBufferD3D *m_indexBuffers[4]; // +0x1C - аналогично, индексные буфера DWORD m_dwIndexCount; // +0x2C DWORD m_dwFaceCount; // +0x30 WORD m_wVertexCount; // +0x34 WORD m_wIndicesPerFace; // +0x36 void * _f38; WORD m_wVertexStride; WORD _f3E; DWORD _f40; void * _f44; DWORD _f48; };
struct grmShaderGroup : public datBase { // size = 0x50 pgDictionary<grcTexture> * m_txd; // +4 CPtrCollection<grmShaderFx> m_shaders; // +8 CPtrCollection<void> _f10; // коллекция неизвестных структур CPtrCollection<void> _f18; // коллекция неизвестных структур CPtrCollection<void> _f20; // коллекция неизвестных структур CPtrCollection<void> _f28; // коллекция неизвестных структур CSimpleCollection<DWORD> _f30; CSimpleCollection<CSimpleCollection<DWORD>> _f38; CSimpleCollection<DWORD> _f40; CSimpleCollection<DWORD> _f48; };
UPD (2008-12-21): добавлено несколько полей от aru, добавлен grmShaderGroup
Last edited by listener (21-12-2008 21:13)
Offline
Объекты буферов разобраны практически полностью:
struct grcVertexBufferD3D : public datBase { // size = 0x20 WORD m_wVertexCount; // +4 - количество вертексов BYTE m_bLocked; // +6 - флаг (см. описание IDirect3DVertexBuffer9::Lock) BYTE _align1; // +7 - выравнивание BYTE * m_pLockedData; // +8 - указатель на locked buffer, в файле совпадает с m_pVertexData DWORD m_dwVertexSize; // +0xC - размер вертекса в байтах void * _f10; DWORD m_dwLockThreadId;// +0x14 - thread id, потока использующего буфер (в файле, 0) BYTE * m_pVertexData; // +18 - уазатель на сами данные (в GPU seg) IDirect3DVertexBuffer9 * piVertexBuffer; // +0x1C - указатель на соответствущий D3D объект (в файле, 0) }; struct grcIndexBufferD3D : public datBase { // size = 0x10 DWORD m_dwIndexCount; // +4 WORD * m_pIndexData; // +8 IDirect3DIndexBuffer9 * piIndexBuffer; // +0xC };
Offline
Сообщения этой темы выделены в отдельное обсуждение
Черно-белый фильтр при аресте и смерти
Offline
Полный список native functions в GTA IV PC
http://public.sannybuilder.com/GTA4/gta_natives_pc.txt
Там больше сотни новых функций по сравнению с боксом.
Offline
Виртуальняе методы класса fiDevice (и его сабклассов)
class fiDevice { public: fiDevice () {} virtual ~fiDevice () {} virtual HANDLE open (char * pszName, bool bReadonly) = 0; // m4 virtual HANDLE openBulk (char * pszName, DWORD * ) { return INVALID_HANDLE_VALUE; } // m8 virtual HANDLE create (char * pszName) { return INVALID_HANDLE_VALUE; } // mC virtual HANDLE _m10 (char * pszName) = 0; virtual int read (HANDLE hFile, void * pBuffer, int nCount) = 0; // m14 virtual int readBulk (HANDLE hFile, int nOffset, int nOffsetHight, void * buf, int nLength) { return -1; } // _m18 virtual int writeBulk (HANDLE hFile, int nOffset, int nOffsetHigh, void * buf, int nLength) { return -1; } virtual int write (HANDLE hFile, void * pBuffer, int nCount) = 0; virtual int seek (HANDLE hFile, int nPos, int nWhence) = 0; // _m24 virtual int close (HANDLE hFile) = 0; virtual int closeBulk (HANDLE hFile) { return -1; } // _m2C virtual int filelength (HANDLE hFile); // _m30 virtual int _m34 (HANDLE) { return 0; } virtual bool unlink (char * pszName) { return false; } virtual bool rename (char * pszOldName, char * pszNewName) { return false; } virtual bool mkdir (char * pszName) { return false; } virtual bool rmdir (char * pszName) { return false; } virtual __int64 filelengthLong (char * pszName); // get length of the contained file virtual __int64 getFileTime (char * pszName) = 0; // m4C virtual bool setFileTime (char * pszName, int nFileTime, int nFileTimeHigh) = 0; // m50 virtual HANDLE findFirst (char * pszName, void * pResults) { return INVALID_HANDLE_VALUE; } // m54 virtual bool findNext (HANDLE hFind, void * pResults) { return false; } // m58 virtual int findClose (HANDLE hFind) { return -1; } // m5C virtual bool truncate (HANDLE hFile) { return false; } // m60 virtual DWORD getFileAttrs (HANDLE hFile) { return 0xFFFFFFFF; } // m64 virtual bool setFileAttrs (HANDLE hFile, DWORD dwAttrs) { return false; } // m68 virtual int getResourceVersion (char * pszName, DWORD _a4, DWORD * dwFlags); // m6C virtual HANDLE getContainerHandle() { return INVALID_HANDLE_VALUE; } virtual __int64 _m74 (char * pszName) { return 0LL; }; virtual void _m78 () {} virtual int _m7C (char * pszName) { return 0; }; };
С этим, файловые операции должны стать слегко понятнее.
Но, вообще, после ночи копания в PC-шном стриминге, мое мнение о тех, кто делал PC-шный порт, сделало попытку упать еще ниже (попытка провалилась, потому что падать ниже, уже некуда). Двухкратное (а то и трехкратное) дублирование ресурсов в памяти - это норма. Система хитрых ресурс-менеджеров, которая была предназначена для того, чтобы экономить каждый байт крохотной памяти консоли - выкинута, и вместо нее навернуто что-то монстровое, расходующее память в немерянных количествах.
В общем, предварительная оценка потери ресурсов исключительно на неэффективность PC-шного стриминга/ресурс-менеджмента - ~10%. (Интересно, можно ли их отыграть обратно)
Last edited by listener (11-01-2009 10:11)
Offline
Частично отреверсенный american.gxt (все миссии и часть MAIN):
http://public.sannybuilder.com/GTA4/ame … versed.rar
Offline
Продолжая тему RSC.
Все, что было сказано на тему флагов, на самом деле, совсем не так.
Ресурс представляет собой не один непрерывный блок памяти, а набор страниц. В поле флагов кодируется размер и количество страниц. Ресурс должен содержать как минимум одну большую страницу и от нуля до четырех страниц меньшего размера. Любой непрерывный блок не должен пересекать границы страницы.
Как это считается: допустим, соответствущая часть поля flags, содержит значение 0x1045.
Получаем базовый размер страницы 1024 байта (1 << ((0x1145 >> 11)+8)) и количество страниц - 0x45.
Верно? Не совсем. На самом деле, будет создано шесть страниц: 4 страницы по 16 килобайт, одна - 4 килобайта и одна - размером в килобайт.
На количество странц отводится всего 11 бит. Из них старшие семь - это действительно количество страниц (больших страниц), а четрые младших - определяют необходимость выделения страниц меньшего размера (каждый бит отвечает за страницу своего размера). Размер страницы всегдя является степенью двойки (если это не так, он округляется вверх).
Зачем это было сделано: страничный механизм был сделан для того, чтобы убрать потребность в непрерывных блоках большого размера (loadingscreens.wtd в распакованном виде занимает ~40M, что с округлением дает нам 64М. Найти непрерывный кусок такого размера в 256М видеопамяти PS3 может быть очень проблематичным (В отличие от 18-ти кусков по два мегабайта).
Блоков должно быть мало, т.к., при правке каждого указателя делается проход по всем блокам этого ресурса. Отсюда - такой странный метод.
Неприятное следствие: если загрузка ресурса получается очень простой, то с записью не все так гладко. При записи мало просто посчитать размер блока, нужно еще и найти относительно оптимальную раскладку структр по блокам. К счастью, эту задачу нужно решить один раз, после чего, найденный алгоритм будет применим для любых данных, хранящихся в RSC.
Напоследок, немножко исходников: http://public.sannybuilder.com/sources/pgbase.rar
Построение списка блоков, загрузка wtd и проверка на выход структур за границы блоков.
Offline
А native-Функции все работают? Наверняка среди них есть NOP и unsupported функции. Есть список всех таких функций?
[small][/small]
Offline
Да, 105 функций не имеют обработчика. (список в аттаче)
У меня скрипт, который помечает natives в базе, автоматически определяет (чтобы не переименовывать сто раз nullsub-заглушку.
Что касается unsupported - это делается диффом между списком всех вызываемых из скриптов функций и полным списком.
А в чем глубинный смысл вопроса?
Offline
@3Doomer -
думаю, он делает удобный компилятор...
Я? :crazy: Ты меня с кем-то перепутал.
@listener -
А в чем глубинный смысл вопроса?
Глубинного смысла нет. Я просто хотел узать, какие функции не работают (Ну для поиковика сделать так, что-бы не искать неработающие функции).
Что касается unsupported - это делается диффом между списком всех вызываемых из скриптов функций и полным списком.
То есть все функции, которые не вызываются из оригинальных скриптов, нерабочие? Или я неправильно понял?
[small][/small]
Offline
Скажем так: они могут работать, но гарантии никакой.
Скорее всего, просто взяли список опкодов VC/SA, загнали их в natives (я видел список оригинальных имен - они такие же, как имена natives), а потом написали код к тому, что реально использовалось.
Offline
Я извиняюсь за подъем старой темы, но появился вопрос. Меня интересует, как организована работа экстра деталей транспорта(extra1-9). Как они комбинируются (ведь на разных машинах они появляются в разных вариация) и самый главный вопрос - где и как это всё можно отредактировать?
Offline
@flashTrash - подробно с этим пока никто не разбирался.
В основном, все хранится в wft (там все меши, колы и часть текстур (системные, типа карт нормалей))
Иерархия сборки компонентов, осталась та же, что и в SA.
полного редактора wft, насколько я знаю, еще не написали.
Самое подробное, что есть по wft - мои шаблоны для 010
Offline
Как утверждал автор занозы, он не нашел, где в wtf файле хранятся настройки экстр(ну чтобы они одновременно например появлялись). Наверно опять всё зашито.
GTA 4 меня всё больше разочаровывает в плане модинга:cry:
Offline
@flashTrash - появление/одновременность экстр, это больше в коде, чем в моделях.
А что касается моддинга... Так копаться надо, пробовать, писать инструменты. Где был бы моддинг SA, если бы не было sannybuilder?
Offline