Синтаксис и структура (строение) .blend файла

Список разделов Уроки и Часто Задаваемые Вопросы (ЧАВО, FAQ) Blender 3D

Модераторы: exooman, PORSHNE

  • 8

Сообщение #1 Илья Белкин » 27.07.2016, 14:37

Все вопросы и предложения убедительно прошу писать в эту тему.

В связи с тем, что я не нашёл описание на русском, то решил взять эту инициативу на себя. Более того, не нашёл я и официального описания, так что воспользовался этим: http://www.atmind.nl/blender/mystery_ot_blend.html (кстати, спасибо большое автору) и вот этим немного: https://wiki.blender.org/index.php/Dev:Source/Architecture/SDNA_Notes

Перед тем, как разбирать как хранится информация в файле, мы рассмотрим что такое блоки данных (они же дата-блоки они же data-blocks). Как ни странно это блоки с данными внутри них, причём это абсолютно любые данные, будь-то анимация, текстура, материал, mesh или любая другая информация, которая нужна для данного проекта. Все эти блоки данных могут ссылаться друг на друга, чтобы не приходилось хранить одну и ту же информацию дважды. Предположим, что у нас есть два объекта. Какие характеристики присущи объектам в 3D пространстве? Возьмём базовые: форма. Предположим, что у нас есть объект-1 - шар и объект-2 - куб, и материал, предположим, что металлический шар и бетонный куб. Итого у нас есть 6 блоков данных: форма-шар, форма-куб, материал-бетон, материал-металл и два объекта с ссылками на материал и форму. Теперь нам не нужен бетонный куб, а нужен металлический. Вопрос: сколько блоков данных нам понадобится? Все кто думает, что опять 6 - ошибается, нам не нужно хранить два раза блок с информацией о металлическом материале. В объекте 2 мы ссылаемся на тот же блок данных с информацией о металлическом материале, что и в объекте 1. Итого у нас 5 дата-блоков. Блок данных это по сути структура (как в си), в которой хранится информация и блоки данных могут ссылаться друг на друга, чтобы не хранить повторно одну и ту же информацию.

Итак, как же блендер сохраняет данные? Блендер сохраняет данные в файл в том же виде, в котором они хранятся в оперативной памяти компьютера, добавляя к ним заголовки блоков и блок ДНК, описывающий данные во всех этих блоках. Благодаря тому, что всё записывается «как есть» сохранение и загрузка происходят очень быстро.
Файл .blend состоит из: заголовка, блоков с данными, внутри предпоследнего блока с названием DNA1 лежит ДНК этого файла, описывающий все предыдущие блоки. Предпоследний, потому что последний блок всегда ENDB означающий конец файла.

Начнём по порядку, с заголовка. Заголовок состоит всегда из 12 байт, и имеет такое строение:
Изображение

Как мы видим из таблицы, размер указателя напрямую зависит от разрядности системы.
С помощью данной программы вы можете разобрать файл на байты и посмотреть на первые 12 байт заголовка. У меня он выглядит вот так:
Код: Выделить всё
0x42(B)66 | 0x4c(L)76 | 0x45(E)69 | 0x4e(N)78 | 0x44(D)68 | 0x45(E)69 | 0x52(R)82 | 0x2d(-)45 | 0x76(v)118 | 0x32(2)50 | 0x37(7)55 | 0x35(5)53

Программа (Си)
Код: Выделить всё
/*
Программу следует поместить в отдельную папку
и сохранить в неё блендер файл с именем untitled.blend
Затем скомпилировать и запустить. Вы получите
TextBlendBytes.txt в котором каждый байт представлен
ввиде: HEX(символ из ASCII)DEC
*/

#include <stdio.h>

void write_bytes (FILE *ofile, unsigned char* c, int n, int* num_symb);
void check_num_symb (int *num_symb, FILE *ofile);
int nub_symb_should = 4; //колличество байт на строку (при выводе)

void main()
{
   FILE *file;
   file = fopen("untitled.blend", "r");
   FILE *ofile;
   ofile = fopen("TextBlendBytes.txt", "wt");
   unsigned char c[512]; //сюда читаем из фала
   int num_symb = 0; //Текущее кол-во символов в строке

   int n; //кол-во прочитанных байт. fread возвращает сколько байт ему удалось прочесть
   while ((n = fread (c, 1, 512, file)) != 0) //читать по 512 байт пока это возможно
   {
      write_bytes (ofile, c, n, &num_symb); //выводим те самые 512 байт
   }
   fclose (file);
   fclose (ofile);
}

/*
*   Функция выводит информацию побайтно, байт выводит в виде: HEX(символ из ASCII)DEC.
*   Нули групирует и выводит "null кол-во times", из-за несметного колличества оных.
*   *ofile ссылка на файл для вывода
*   *с ссылка на первый элемент массива
*   n колличество эллементов для вывода
*   *num_symb ссылка на колличество символов в строке
*/
void write_bytes (FILE *ofile, unsigned char* c, int n, int* num_symb) {
   int wieder = 0, i;//wieder - кол-во нулей подряд, i - счетчик цикла
   for (i = 0; i < n; i++) {
      if (c[i] == 0) {
         wieder++;
      }
      else if (c[i] > ' ' && c[i] < '~') {//если символ под таким номером есть в ASCII
         if (wieder != 0) {//проверяем на наличие и выводим нули
            fprintf(ofile, "null %d times | ", wieder);
            *num_symb+=1;
            wieder = 0;
            check_num_symb (num_symb, ofile);
         }
         //выводим текущий байт
         fprintf(ofile, "Ox%x(%c)%d | ", c[i], c[i], c[i]);
         *num_symb+=1;
         check_num_symb (num_symb, ofile);
      }
      else {
         if (wieder != 0) {//проверяем на наличие и выводим нули
            fprintf(ofile, "null %d times | ", wieder);
            *num_symb+=1;
            wieder = 0;
            check_num_symb (num_symb, ofile);
         }
         //выводим текущий байт
         fprintf(ofile, "Ox%x()%d | ", c[i], c[i]);
         *num_symb+=1;
         check_num_symb (num_symb, ofile);
      }
   }
   if (wieder != 0) {
      fprintf(ofile, "null %d times | ", wieder);
      wieder = 0;
   }
}

/*
*   Проверяет есть ли в строке нужное кол-во символов, начинает новую.
*   *num_symb ссылка на колличество символов в строке
*   *ofile ссылка на файл для вывода
*/
void check_num_symb (int *num_symb, FILE *ofile) {
   if (*num_symb == nub_symb_should) {
      *num_symb = 0;
      fprintf(ofile, "\n");
   }
}

Далее рассмотрим заголовки блоков. Они состоят из 16 байт + размер указателя, то есть 20 байт для 32х битной системы и 24 для 64х. Заголовок имеет такое строение:
Изображение

Немного про запись int в последовательность байт. Есть два варианта: от старшего к младшему (big endian) и от младшего к старшему (little endian) возьмём пример 0x4A3B2C1D, и таким образом, байты этого числа будут записаны в последовательности {0x4A, 0x3B, 0x2C, 0x1D} для big endian и {0x1D, 0x2C, 0x3B, 0x4A} для little.
Про указатель. В связи с тем, что блендер сохраняет данные в файл в том же виде, в котором они хранятся в оперативной памяти компьютера (дописывая заголовки) указатели сохраняются так же в том же виде. А так как, загружая файл, данные могут записаться на другие отделы оперативной памяти, то нам нужно знать старые адреса данных, чтобы понимать куда указывал указатель.
ДНК индекс - это индекс ДНК структуры, которая находится в DNA1 блоке.
Далее следует количество структур данного вида, которое вы найдёте в этом блоке.
С помощью предыдущей программы можете рассмотреть первый заголовок (начиная с 13 байта), у меня он выглядит так:
Код: Выделить всё
0x52(R)82 | 0x45(E)69 | 0x4e(N)78 | 0x44(D)68 | 0x48(H)72 | null 3 times | 0x30(0)48 | 0x1c()28 | 0x88()136 | 0x90()144 | 0xfd()253 | 0x7f()127 | null 6 times | 0x1()1 | null 3 times
Если мы воспользуемся этой программой, то мы сможем увидеть информацию про каждый блок.

Программа (Си)
Код: Выделить всё
/*
Программу следует поместить в отдельную папку
и сохранить в неё блендер файл с именем untitled.blend
Затем скомпилировать и запустить. Вы получите
TextBlendWithHeaders.txt
*/

#include <stdio.h>

int write_block_header (FILE *ofile, unsigned char* c, int pointer_size, char is_little_endian);
void write_bytes (FILE *ofile, unsigned char* c, int n, int* num_symb);
unsigned int byte_to_int (int n_bytes, unsigned char* array, char is_little_endian);
int write_header (FILE *ofile, unsigned char* c, char* is_little_endian);
void check_num_symb (int *num_symb, FILE *ofile);
int nub_symb_should = 4;

void main()
{
   FILE *file;
   file = fopen("untitled.blend", "r");
   FILE *ofile;
   ofile = fopen("TextBlendWithHeaders.txt", "wt");
   unsigned char c[512]; //сюда читаем из фала
   int num_symb = 0; //Текущее кол-во символов в строке
   char is_little_endian = 1;//little или big endian

   fread (c, 1, 12, file); //читаем хэдер, он состоит всегда из 12 байт
   int pointer_size = write_header (ofile, c, &is_little_endian);//размер указателя в байтах

   //Читаем следующие 16+размер указателя байт, это собственно размер хэдера блока файла
   while ((fread (c, 1, pointer_size+16, file)) == pointer_size+16)
   {
      //кол-во байт в блоке
      int byte_num = write_block_header (ofile, c, pointer_size, is_little_endian);
      while (byte_num >= 512) {
         //что-бы сильно память не забивать, читаем по 512 байт
         fread (c, 1, 512, file);
         write_bytes (ofile, c, 512, &num_symb);
         byte_num -= 512;
      }
      //ещё один раз, так как byte_num не обязательно кратно 512
      fread (c, 1, byte_num, file);
      write_bytes (ofile, c, byte_num, &num_symb);
   }
   fclose (file);
   fclose (ofile);
}

/*
*   Эта функция обрабатывает и выводит хэдер файла
*   Хэдер состоит из:
*      1) Идентификатор. 7 байт:
*         Всегда {'B', 'L', 'E', 'N', 'D', 'E', 'R'}
*      2) Разрядность системы или размер указателя. 1 байт.
*         '_'означает 32битная с 4х байтным указателем, '-' 64бита и 8байт
*      3) Порядок  байт. 1 байт:
*         'v' little endian, 'V' big endian
*      4) Версия блендера. 3 байта:
*         Пример: {'2', '7', '5'} означает 2.75
*
*   *ofile ссылка на файл для вывода
*   *c ссылка на 1й элемент массива откуда читаем
*   *is_little_endian ссылка на тип порядка байт
*
*   return размер указателя в байтах
*/
int write_header (FILE *ofile, unsigned char* c, char* is_little_endian) {
   //забиваем на первые 7 байт, они нам не нужны
   if (c[7] == '_') {
      fprintf(ofile, "32 bit, ");
   } else fprintf(ofile, "64 bit, ");
   if (c[8] == 'V') {
      fprintf(ofile, "big endian, ");
      *is_little_endian = 0;
   } else fprintf(ofile, "little endian, ");
   fprintf(ofile, "version: %c.%c%c\n", c[9], c[10], c[11]);
   return (c[7] == '_' ? 4:8);
}

/*
*   Эта функция обрабатывает и выводит хэдеры блоков
*   Хэдер состоит из:
*      1) Иия блока. 4 байта:
*         Пример: {'R', 'E', 'N', 'D'} или {'S', 'C', 0, 0}
*      2) размер блока после хэдера в байтах. int из 4х байт:
*         int из 4х байт разрезан на 4 части, напомню char это 1 байт.
*         Пример: 0x4A3B2C1D в Big endian {0x4A, 0x3B, 0x2C, 0x1D},
*         а в little endian {0x1D, 0x2C, 0x3B, 0x4A}
*      3) Указатель на старый адрес данных. (размер указателя) байт:
*         загружая файл, блендер может положить структуру на новое место,
*         и тогда он обновляет указатель на неё
*      4) Индекс ДНК структуры (SDNA structure, находится в DNA1 блоке). int из 4х байт.
*      5) Количество ДНК структур расположенных в этом блоке. int из 4х байт.
*
*   *ofile ссылка на файл для вывода
*   *c ссылка на 1й элемент массива откуда читаем
*   pointer_size размер указателя
*   *is_little_endian ссылка на тип порядка байт
*
*   return размер блока после хэдера в байтах
*/
int write_block_header (FILE *ofile, unsigned char* c, int pointer_size, char is_little_endian) {
   fprintf(ofile, "\n<----------------------->\n");
   fprintf(ofile, "Name: ");
   int i;
   for (i = 0; i < 4; i++) {
      if (c[i] != 0) {//так как 0ли это пустые символы, то их не выводим
         fprintf(ofile, "%c", c[i]);
      }
   }
   int res = byte_to_int(4, &c[4], is_little_endian);
   fprintf(ofile, ", size: %d", res);
   fprintf(ofile, ", SDNA index: %d", byte_to_int(4, &c[8+pointer_size], is_little_endian));
   fprintf(ofile, ", SDNA count: %d\n", byte_to_int(4, &c[12+pointer_size], is_little_endian));
   return res;
}

/*
*   Функция выводит информацию побайтно, байт выводит в виде: HEX(символ из ASCII)DEC.
*   Нули групирует и выводит "null кол-во times", из-за несметного колличества оных.
*   *ofile ссылка на файл для вывода
*   *с ссылка на первый элемент массива
*   n колличество эллементов для вывода
*   *num_symb ссылка на колличество символов в строке
*/
void write_bytes (FILE *ofile, unsigned char* c, int n, int* num_symb) {
   int wieder = 0, i;//wieder - кол-во нулей подряд, i - счетчик цикла
   for (i = 0; i < n; i++) {
      if (c[i] == 0) {
         wieder++;
      }
      else if (c[i] > ' ' && c[i] < '~') {//если символ под таким номером есть в ASCII
         if (wieder != 0) {//проверяем на наличие и выводим нули
            fprintf(ofile, "null %d times | ", wieder);
            *num_symb+=1;
            wieder = 0;
            check_num_symb (num_symb, ofile);
         }
         //выводим текущий байт
         fprintf(ofile, "0x%x(%c)%d | ", c[i], c[i], c[i]);
         *num_symb+=1;
         check_num_symb (num_symb, ofile);
      }
      else {
         if (wieder != 0) {//проверяем на наличие и выводим нули
            fprintf(ofile, "null %d times | ", wieder);
            *num_symb+=1;
            wieder = 0;
            check_num_symb (num_symb, ofile);
         }
         //выводим текущий байт
         fprintf(ofile, "0x%x()%d | ", c[i], c[i]);
         *num_symb+=1;
         check_num_symb (num_symb, ofile);
      }
   }
   if (wieder != 0) {
      fprintf(ofile, "null %d times | ", wieder);
      wieder = 0;
   }
}

/*
*   Проверяет есть ли в строке нужное кол-во символов, начинает новую.
*   *num_symb ссылка на колличество символов в строке
*   *ofile ссылка на файл для вывода
*/
void check_num_symb (int *num_symb, FILE *ofile) {
   if (*num_symb == nub_symb_should) {
      *num_symb = 0;
      fprintf(ofile, "\n");
   }
}

/*
*   Собирает число разрезанное на n_bytes частей
*   ACHTUNG!!! Битовые операции, вроде можно и без них, но мне так проще
*
*   n_bytes колличество байт в числе
*   *array ссылка на первый элемент массива с числом
*   is_little_endian порядок байт
*
*   return собранное число   
*/
unsigned int byte_to_int (int n_bytes, unsigned char* array, char is_little_endian){
   unsigned int res = 0, i;
   if (is_little_endian) {
      for (i = 0; i < n_bytes; i++) {
         //каждые последующие 8 бит (1 байт) сдвигаем на 8 бит влево,
         //относительно предыдущих
         res = res | (unsigned int)((unsigned int)array[i] << i*8);
      }
   }
   else {
      for (i = 0; i < n_bytes; i++) {
         //первые 8 бит сдвигаются на n_bytes*8 бит влево,
         //последующие на (n_bytes-i)*8 бит
         res = res | (unsigned int)((unsigned int)array[i] << (n_bytes-i)*8);
      }
   }
   return res;
}

И вот подходим к страшной и мистической ДНК структуре. Для начала пара слов о ней. Благодаря этой структуре все файлы .blend могут быть прочитаны и интерпретированы любой версией блендера, а также количество структур в файле и переменных в структуре может изменяться без ущерба совместимости файла с любой версией блендера. Собственно говоря, она описывает то, как интерпретировать набор байт, записанный в блоке файла. Как же выглядит структура?
Изображение

Как мы видим, данный блок состоит из массива имён, массива типов, размеров этих типов, и структур. Имена могут быть 3х видов: “имя” -> просто имя, “*имя” -> указатель, “имя[число]” -> массив. Массив типов имеет то же количество элементов, что и массив длинн типов. И их элементы соответственны, например: names[n] = "int", length[n] = 4; names[q] = "char", length[q] = 1…
В структурах же хранятся: индекс типа из массива типов и потом н-ное количество пар индекс типа из массива типов, индекс имени из массива имён.
Собственно на порядковый номер этих структур ссылается ДНК индекс из заголовка блока.
С помощью ранее упомянутой программы вы можете получить файл с данными о заголовках блоков и найти блок с именем DNA1, это и есть блок ДНК структуры, а данная программа выведет описание структур в читаемом виде.

Программа (Си)
Код: Выделить всё
/*
Программу следует поместить в отдельную папку
и сохранить в неё блендер файл с именем untitled.blend
Затем скомпилировать и запустить. Вы получите
TextBlendWhithDNA.txt
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int write_block_header (FILE *ofile, unsigned char* c, int pointer_size, char is_little_endian, int *num_symb);
void write_bytes (FILE *ofile, unsigned char* c, int n, int* num_symb);
unsigned int byte_to_int (int n_bytes, unsigned char* array, char is_little_endian);
int write_header (FILE *ofile, unsigned char* c, char* is_little_endian);
void DNA_block_bearbeiten (int n, FILE *file, FILE *ofile, char is_little_endian);
void int_to_byte (int n_bytes, unsigned char* array, unsigned int val);
void check_num_symb (int *num_symb, FILE *ofile);
int nub_symb_should = 4; //колличество байт на строку (при выводе)

struct structs { //описание структуры из ДНК файла (блок DNA1)
   int index;//как не странно индекс структуры, в хэдере блока подписан как SDNA index:
   char* type;//тип структуры
   int size;//размер в байтах
   short num_fields;//количество полей(переменных)
   char** names;//имена полей (*имя -> указатель, имя[число] -> массив)
   char** types;//типы полей
   short* lendthes;//размеры полей в байтах
   struct structs *next;//следующая структура
   struct structs *prev;//предыдущая структура
};

void write_DNA (FILE *ofile, struct structs *struct_poi);

void main()
{
   FILE *file;
   file = fopen("untitled.blend", "r");
   FILE *ofile;
   ofile = fopen("TextBlendWhithDNA.txt", "wt");
   unsigned char c[512]; //сюда читаем из фала
   int num_symb = 0; //Текущее кол-во символов в строке
   char is_little_endian = 1;//little или big endian

   fread (c, 1, 12, file); //читаем хэдер, он состоит всегда из 12 байт
   int pointer_size = write_header (ofile, c, &is_little_endian);//размер указателя в байтах

   //Читаем следующие 16+размер указателя байт, это собственно размер хэдера блока файла
   while ((fread (c, 1, pointer_size+16, file)) == pointer_size+16)
   {
      //кол-во байт в блоке
      int byte_num = write_block_header (ofile, c, pointer_size, is_little_endian, &num_symb);
      if (byte_num >= 0) {//если это не DNA1
         while (byte_num >= 512) {
            //что-бы сильно память не забивать, читаем по 512 байт
            fread (c, 1, 512, file);
            write_bytes (ofile, c, 512, &num_symb);
            byte_num -= 512;
         }
         //ещё один раз, так как byte_num не обязательно кратно 512
         fread (c, 1, byte_num, file);
         write_bytes (ofile, c, byte_num, &num_symb);
      }
      else {//если это DNA1
         DNA_block_bearbeiten ((-1*byte_num), file, ofile, is_little_endian);
      }
   }
   fclose (file);
   fclose (ofile);
}

/*
*   Эта функция обрабатывает и выводит хэдер файла
*   Хэдер состоит из:
*      1) Идентификатор. 7 байт:
*         Всегда {'B', 'L', 'E', 'N', 'D', 'E', 'R'}
*      2) Разрядность системы или размер указателя. 1 байт.
*         '_'означает 32битная с 4х байтным указателем, '-' 64бита и 8байт
*      3) Порядок байт. 1 байт:
*         'v' little endian, 'V' big endian
*      4) Версия блендера. 3 байта:
*         Пример: {'2', '7', '5'} означает 2.75
*
*   *ofile ссылка на файл для вывода
*   *c ссылка на 1й элемент массива откуда читаем
*   *is_little_endian ссылка на тип порядка байт
*
*   return размер указателя в байтах
*/
int write_header (FILE *ofile, unsigned char* c, char* is_little_endian) {
   //забиваем на первые 7 байт, они нам не нужны
   if (c[7] == '_') {
      fprintf(ofile, "32 bit, ");
   } else fprintf(ofile, "64 bit, ");
   if (c[8] == 'V') {
      fprintf(ofile, "big endian, ");
      *is_little_endian = 0;
   } else fprintf(ofile, "little endian, ");
   fprintf(ofile, "version: %c.%c%c\n", c[9], c[10], c[11]);
   return (c[7] == '_' ? 4:8);
}

/*
*   Эта функция обрабатывает и выводит хэдеры блоков
*   Хэдер состоит из:
*      1) Иия блока. 4 байта:
*         Пример: {'R', 'E', 'N', 'D'} или {'S', 'C', 0, 0}
*      2) размер блока после хэдера в байтах. int из 4х байт:
*         int из 4х байт разрезан на 4 части, напомню char это 1 байт.
*         Пример: 0x4A3B2C1D в Big endian {0x4A, 0x3B, 0x2C, 0x1D},
*         а в little endian {0x1D, 0x2C, 0x3B, 0x4A}
*      3) Указатель на старый адрес данных. (размер указателя) байт:
*         загружая файл, блендер может положить структуру на новое место,
*         и тогда он обновляет указатель на неё
*      4) Индекс ДНК структуры (SDNA structure, находится в DNA1 блоке). int из 4х байт.
*      5) Количество ДНК структур расположенных в этом блоке. int из 4х байт.
*
*   *ofile ссылка на файл для вывода
*   *c ссылка на 1й элемент массива откуда читаем
*   pointer_size размер указателя
*   *is_little_endian ссылка на тип порядка байт
*   *num_symb ссылка на колличество символов в строке
*
*   return размер блока после хэдера в байтах
*/
int write_block_header (FILE *ofile, unsigned char* c, int pointer_size, char is_little_endian, int *num_symb) {
   *num_symb = 0;
   fprintf(ofile, "\n<----------------------->\n");
   fprintf(ofile, "Name: ");
   int i;
   for (i = 0; i < 4; i++) {
      if (c[i] != 0) {//так как 0ли это пустые символы, то их не выводим
         fprintf(ofile, "%c", c[i]);
      }
   }
   int res = byte_to_int(4, &c[4], is_little_endian);
   fprintf(ofile, ", size: %d", res);
   fprintf(ofile, ", SDNA index: %d", byte_to_int(4, &c[8+pointer_size], is_little_endian));
   fprintf(ofile, ", SDNA count: %d\n", byte_to_int(4, &c[12+pointer_size], is_little_endian));
   fprintf(ofile, "Old pointer: ");
   int old = nub_symb_should;
   nub_symb_should = 100;
   int becsuse_i_need_it = 0;
   write_bytes (ofile, &c[8], pointer_size, &becsuse_i_need_it);
   nub_symb_should = old;
   fseek(ofile, -3, SEEK_CUR);
   fprintf(ofile, ";\n\n");
   if (c[0] == 'D' && c[1] == 'N' && c[2] == 'A' && c[3] == '1') {
      //если это DNA1 блок, то его нужно обработать по особенному,
      //поэтому вернём отрицательное значение
      return (-1*res);
   }
   else
      return res;
}

/*
*   Подходим к страшному, структкра DNA1 блока или SDNA или ДНК:
*   1) Идентификатор. 4 байта:
*      Всегда {'S', 'D', 'N', 'A'}
*   2) Идентификатор части имён. 4 байта:
*      Всегда {'N', 'A', 'M', 'E'}
*   3) Кол-во имён. int из 4х байт
*   4) Имена записанные одно за другим, разделённые 0лями, в самом конце тоже 0
*      имеют 3 вида: просто имя, *имя -> указатель, имя[число] -> массив.
*      Короче как в си, слева прикрутить тип, вот и объявлена переменная
*   5) Идентификатор части типов. 4 байта:
*      Всегда {'T', 'Y', 'P', 'E'}
*   6) Кол-во типов. int из 4х байт
*   7) Типы записанные один за другим, разделённые 0лями, в самом конце тоже 0
*   8) Идентификатор части размера типов. 4 байта:
*      Всегда {'T', 'L', 'E', 'N'} Memento mori
*   9) Размеры типов записанные по два байта (short разрезаный по двум char-ам)
*      их то же кол-во, что и типов, и эти два массива соотносятся друг с другом.
*      то есть names[n] = "int", length[n] = 4; names[q] = "char", length[q] = 1...
*   10) Идентификатор части структур. 4 байта:
*      Всегда {'S', 'T', 'R', 'C'}
*   11) Кол-во структур. int из 4х байт
*   12) Структуры, состоят из подотделов:
*      а) Индекс типа структуры из массива типов. short из 2х байт
*      б) Кол-во полей (переменных) в структуре. short из 2х байт
*      в) Тут блок н-ного колличества, упомянутого выше, полей, состоящих из пар тип - имя:
*         1. Индекс типа поля из массива типов. short из 2х байт
*         2. Индекс имени поля из массива имён. short из 2х байт
*
*   n - кол-во байт в блоке
*   *file ссылка на файл, откуда читать
*   *ofile ссылка на файл, куда писать
*   is_little_endian порядок байт
*/
void DNA_block_bearbeiten (int n, FILE *file, FILE *ofile, char is_little_endian) {
   char header[12];
   fread (header, 1, 12, file);
   int num = byte_to_int (4, &header[8], is_little_endian);//кол-во имён
   char** names = malloc (num*sizeof(char**));//Выделяем память под 2мерный динамический массив имён
   int length_of_word = 3, i; //длинна слова сразу равна 3м, так как в первые 2 байта запишем длинну слова
   char aktuel_ch;
   for (i = 0; i < num; i++) {
      while ((aktuel_ch = getc (file)) != 0) {//по одному читаем символы, пока не ноль
         //под каждый символ имени выделяем по байту памяти
         names[i] = realloc (names[i], length_of_word*sizeof(char));
         names[i][length_of_word-1] = aktuel_ch;
         length_of_word++;
      }
      //не забываем вставить '\0' иначе будут проблемы с чтением этой строки
      names[i] = realloc (names[i], length_of_word*sizeof(char));
      names[i][length_of_word-1] = '\0';
      //вставляем в первые два байта длинну строки
      int_to_byte (2, names[i], length_of_word);
      length_of_word = 3;
   }
   fread (header, 1, 8, file);
   num = byte_to_int (4, &header[4], is_little_endian);//кол-во типов
   char** types = malloc (num*sizeof(char**));//Выделяем память под 2мерный динамический массив типов
   for (i = 0; i < num; i++) {
      while ((aktuel_ch = getc (file)) != 0) {//по одному читаем символы, пока не ноль
         //под каждый символ типа выделяем по байту памяти
         types[i] = realloc (types[i], length_of_word*sizeof(char));
         types[i][length_of_word-1] = aktuel_ch;
         length_of_word++;
      }
      //не забываем вставить '\0' иначе будут проблемы с чтением этой строки
      types[i] = realloc (types[i], length_of_word*sizeof(char));
      types[i][length_of_word-1] = '\0';
      //вставляем в первые два байта длинну строки
      int_to_byte (2, types[i], length_of_word);
      length_of_word = 3;
   }
   fread (header, 1, 4, file);//что-бы указатель в файле сдвинулся на эти 4 байта, в которых (TLEN)
   short* length_of_type = malloc (num*sizeof(short));//выделяем место под массив размеров типов
   
   for (i = 0; i < num; i++) {
      header [0] = getc (file);
      header [1] = getc (file);
      //соеденяем два байта вместе, получаем размер переменной типа short
      length_of_type[i] = byte_to_int (2, header, is_little_endian);
   }
   fread (header, 1, 8, file);
   num = byte_to_int (4, &header[4], is_little_endian);//кол-во структур
   struct structs *struct_poi = malloc (sizeof (struct structs));//выделяем память под это кол-во структур
   if (!struct_poi)
   {
      printf("Allocation error.\n");
      exit(0);
   }
   struct structs *first_struct_poi = struct_poi;//сохраняем указатель на первую
   struct_poi->prev = NULL;//так как это первая структура, то указатель на предыдущую нулевой
   short num_fields;
   char numb[2];//вся инфа здесь по 2 байта, так что удобно ёё кидать в такой массив
   short j;
   for (i = 0; i < num; i++) {
      struct_poi->index = i;//присваеваем индекс структуре
      fread (numb, 1, 2, file);//читаем индекс типа структуры
      struct_poi->type = malloc (byte_to_int(2, types[byte_to_int (2, numb, is_little_endian)], 1)*sizeof (char));//выделяем память под тип
      struct_poi->type = strncpy (struct_poi->type, &types[byte_to_int (2, numb, is_little_endian)][2], byte_to_int(2, types[byte_to_int (2, numb, is_little_endian)], 1)*sizeof (char));//копируем тип из массива в структуру
      struct_poi->size = length_of_type[byte_to_int (2, numb, is_little_endian)];//сохраняем размер
      fread (numb, 1, 2, file);
      num_fields = byte_to_int (2, numb, is_little_endian);//кол-во полей
      struct_poi->num_fields = num_fields;//сохр. в структуру
      struct_poi->names = malloc (num_fields*sizeof (char**));//место под массив имён
      struct_poi->types = malloc (num_fields*sizeof (char**));//место под массив типов
      struct_poi->lendthes = malloc (num_fields*sizeof (short));//место под массив размеров типов
      for (j = 0; j < num_fields; j++) {
         fread (numb, 1, 2, file);//читаем индекс типа поля
         struct_poi->types[j] = malloc (byte_to_int(2, types[byte_to_int (2, numb, is_little_endian)], 1)*sizeof (char));//выделяем память под тип
         struct_poi->types[j] = strncpy (struct_poi->types[j], &types[byte_to_int (2, numb, is_little_endian)][2], byte_to_int(2, types[byte_to_int (2, numb, is_little_endian)], 1)*sizeof (char));//копируем тип из массива в структуру
         struct_poi->lendthes[j] = length_of_type[byte_to_int (2, numb, is_little_endian)];//сохраняем размер
         fread (numb, 1, 2, file);//читаем индекс имени поля
         struct_poi->names[j] = malloc (byte_to_int(2, names[byte_to_int (2, numb, is_little_endian)], 1)*sizeof (char));//выделяем память под имя
         struct_poi->names[j] = strncpy (struct_poi->names[j], &names[byte_to_int (2, numb, is_little_endian)][2], byte_to_int(2, names[byte_to_int (2, numb, is_little_endian)], 1)*sizeof (char));//копируем имя из массива в структуру
      }
      struct structs *struct_poi_temp = struct_poi;//сохр. указатель на текущ. структуру
      struct_poi->next = malloc (sizeof (struct structs));//выделяем место под  следущую
      if (!struct_poi->next)
      {
         printf("Allocation error.\n");
         exit(0);
      }
      struct_poi = struct_poi->next;//двигаем указатель к следующей
      struct_poi->prev = struct_poi_temp;//запоминаем адрес предыдущей
   }
   //сделан один лишний шаг вперёд, так что идём назад, и подчищаем память
   struct_poi = struct_poi->prev;
   free (struct_poi->next);
   struct_poi->next = NULL;
   free (names);
   free (types);
   free (length_of_type);
   write_DNA (ofile, first_struct_poi);
}

/*
*   Выводит красиво описание структур. Шаблон:
*   struct# номер структуры, type: тип структуры = размер типа byte
*      тип поля имя поля = размер типа byte
*
*   *ofile ссылка на файл, куда писать
*   *struct_poi ссылка на первую структуру
*/
void write_DNA (FILE *ofile, struct structs *struct_poi) {
   int i;
   while (1) {
      fprintf(ofile, "struct# %d, type: %s = %d byte\n", struct_poi->index, struct_poi->type, struct_poi->size);
      for (i = 0; i < struct_poi->num_fields; i++) {
         fprintf(ofile, "\t%s %s = %d byte\n", struct_poi->types[i], struct_poi->names[i], struct_poi->lendthes[i]);
      }
      if (struct_poi->next == NULL) break;
      struct_poi = struct_poi->next;
   }
}

/*
*   Функция выводит информацию побайтно, байт выводит в виде: HEX(символ из ASCII)DEC.
*   Нули групирует и выводит "null кол-во times", из-за несметного колличества оных.
*   *ofile ссылка на файл для вывода
*   *с ссылка на первый элемент массива
*   n колличество эллементов для вывода
*   *num_symb ссылка на колличество символов в строке
*/
void write_bytes (FILE *ofile, unsigned char* c, int n, int* num_symb) {
   int wieder = 0, i;//wieder - кол-во нулей подряд, i - счетчик цикла
   for (i = 0; i < n; i++) {
      if (c[i] == 0) {
         wieder++;
      }
      else if (c[i] > ' ' && c[i] < '~') {//если символ под таким номером есть в ASCII
         if (wieder != 0) {//проверяем на наличие и выводим нули
            fprintf(ofile, "null %d times | ", wieder);
            *num_symb+=1;
            wieder = 0;
            check_num_symb (num_symb, ofile);
         }
         //выводим текущий байт
         fprintf(ofile, "0x%x(%c)%d | ", c[i], c[i], c[i]);
         *num_symb+=1;
         check_num_symb (num_symb, ofile);
      }
      else {
         if (wieder != 0) {//проверяем на наличие и выводим нули
            fprintf(ofile, "null %d times | ", wieder);
            *num_symb+=1;
            wieder = 0;
            check_num_symb (num_symb, ofile);
         }
         //выводим текущий байт
         fprintf(ofile, "0x%x()%d | ", c[i], c[i]);
         *num_symb+=1;
         check_num_symb (num_symb, ofile);
      }
   }
   if (wieder != 0) {
      fprintf(ofile, "null %d times | ", wieder);
      wieder = 0;
   }
}

/*
*   Проверяет есть ли в строке нужное кол-во символов, начинает новую.
*   *num_symb ссылка на колличество символов в строке
*   *ofile ссылка на файл для вывода
*/
void check_num_symb (int *num_symb, FILE *ofile) {
   if (*num_symb == nub_symb_should) {
      *num_symb = 0;
      /*fseek(ofile, -3, SEEK_CUR);*/
      fprintf(ofile, "\n");
   }
}

/*
*   Собирает число разрезанное на n_bytes частей
*   ACHTUNG!!! Битовые операции, вроде можно и без них, но мне так проще
*
*   n_bytes колличество байт в числе
*   *array ссылка на первый элемент массива с числом
*   is_little_endian порядок байт
*
*   return собранное число   
*/
unsigned int byte_to_int (int n_bytes, unsigned char* array, char is_little_endian){
   unsigned int res = 0, i;
   if (is_little_endian) {
      for (i = 0; i < n_bytes; i++) {
         //каждые последующие 8 бит (1 байт) сдвигаем на 8 бит влево,
         //относительно предыдущих
         res = res | (unsigned int)((unsigned int)array[i] << i*8);
      }
   }
   else {
      for (i = 0; i < n_bytes; i++) {
         //первые 8 бит сдвигаются на n_bytes*8 бит влево,
         //последующие на (n_bytes-i)*8 бит
         res = res | (unsigned int)((unsigned int)array[i] << (n_bytes-i)*8);
      }
   }
   return res;
}

/*
*   Разбирает число в массив байт, только little endian
*   ACHTUNG!!! Битовые операции, вроде можно и без них, но мне так проще
*
*   *n_bytes колличество байт в числе
*   *array ссылка на первый элемент массива куда разбирать
*   val число подлежащее распилу
*/
void int_to_byte (int n_bytes, unsigned char* array, unsigned int val){
   unsigned int i;
   for (i = 0; i < n_bytes; i++) {
      //накладываем маску 00000000 00000000 00000000 11111111 и присваеваем
      array[i] = (char)val&0xFF;
      //сдвигаем на 8 бит вправо
      val = val >> 8;
   }
}

Давайте же посмотрим как читается файл-блок. Для примера возьмём mesh. Смотрим на блок файла с названием ME/0/0, индекс ДНК структуры в моей версии блендера - 57. Ищем в ДНК описание структуры под таким номером, это структура с именем Mesh. Первым ёё элементом является id типа ID, ищем в ДНК описание с именем ID, у меня это описание с индексом 10. Первым её элементом является void *next, ищем описание void в ДНК. Такового нет, значит это простой тип и данные можно считывать. Символ '*' в имени значит, что это ссылка. Поэтому читаем 4/8 байт ссылки из блока ME/0/0, дальше void *prev, ID *newid, Library *lib. Оговорюсь, что ссылка на объекты тоже простой тип и может читаться сразу. Дальше идёт массив char name[66], а для имени вы можете использовать только 63 символа, первые два символа ME обозначают mesh, а ещё один байт всегда ноль и означает конец имени. Без этого нуля в имя может добавиться мусор, вот как здесь:
Код: Выделить всё
| 0x4d(M)77 | 0x45(E)69 | 0x43(C)67 | 0x75(u)117 | 0x62(b)98 | 0x65(e)101 | null 1 times | 0x70(p)112 | 0x68(h)104 | 0x65(e)101 | 0x72(r)114 | 0x65(e)101 | null 56 times |
Всё после null 1 times - мусор. Дальше мы дочитываем структуру id и продолжаем Mesh. Напомню, что данные лежат подряд в блоке файла ME/0/0, то есть сначала там лежит 120 байт структуры id, а дальше сразу идут данные из Mesh.
Eщё один пример: вывести все имена сцен. Алгоритм решения данной задачи: (1) Находим в блоке ДНК (DNA1) (2) описание структуры с названием Scene запоминаем порядковый номер описания (в моём случае 196 (всё зависит от версии блендера)). (3) Смотрим описание структуры, пока что первым полем там всегда выступало ID, но в любом, случае находим поле типа ID с именем id попутно считаем отступ в байтах от начала блока до поля id (для каждого поля указан тип, размер каждого типа тоже есть). (4) Теперь ищем в блоке ДНК описание структуры с именем ID. (5) И в описании ищем поле типа char и именем name[n] (вместо n какое-то число, в моей версии 66), попутно считаем отступ в байтах от начала блока, прибавляем его к предыдущему отступу. (6) Теперь находим блок с SDNA id равным сохранённому порядковому номеру описания (тот, что у меня - 196), и отступаем сдвигаемся на количество байт, указанных в отступе. Проверяем, что последующие 2 байта это SC (0x53, 0x43) (для объекта эти 2 байта будут - OB, для мэша - ME...) и дальше читаем до 0x00. Проделываем пункт (6) пока есть новые сцены (имена не повторяются). Имя записано в формате UTF-8 (1 байт на латинский символ, 2 - на кириллицу, ну вы знаете, UTF-8...)
Изображение
(6)
Изображение

Вот приблизительно всё что я хотел разсказать про структуру .blend файлов.
Илья Белкин M
Аватара
Откуда: ¯\_(ツ)_/¯
Сообщения: 182



Вернуться в Blender 3D

Кто сейчас на форуме (по активности за 5 минут)

Сейчас этот раздел просматривают: 1 гость